Compare commits

...
Author SHA1 Message Date
nmzik 596ecdcca0 Fix shader CFG structurization for nested early exits 2026-08-19 00:14:51 +02:00
nmzikandshadowbeat070 5d83ce82af renderer: track and materialize deferred DCC clears
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-08-18 19:10:36 +02:00
shadowbeat070andnmzik 4fb72e9db7 graphics: add --gpu-assisted-validation and report fatal VkResults (#268)
* graphics: report the VkResult when a submit or fence wait fails

* graphics: add a --gpu-assisted-validation flag

* graphics: imply Vulkan validation for GPU-assisted validation

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-18 14:44:48 +02:00
Stefanos Costaandnmzik c4ca8f4bf8 shader: fix the Linux and macOS build (#285)
* fixed linux build
- added correct uint long translation
-  replaced throw with non-constexpr function so that it fails in case of malformed table

* shader: remove redundant opcode table comment

* shader: use EXIT for invalid opcode tables

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-18 14:41:48 +02:00
Hashim Khan a4b970d97d launcher: explain unsupported mods-style patch JSON (#248)
When a _Patches file has no patches array (often a shadPS4/ETAHen mods
list), show a clear status instead of "Loaded 0 patch(es)". Fixes #174.
2026-08-18 14:22:40 +02:00
nmzik 8cfc9cd7a8 renderer: execute attachmentless PS side-effect draws 2026-08-18 14:03:36 +02:00
nmzik e39162fc52 shader: remove validation from program cache key 2026-08-18 14:03:36 +02:00
Hashim Khan 6bf69297c6 launcher: keep window open when game folder prompt is dismissed (#245)
Calling QApplication::quit() after the user closes the first-run game
folder dialog can segfault during nested modal shutdown. Leave the
launcher open so settings can still be configured. Fixes #121.
2026-08-18 04:18:56 +02:00
SoporteChilemat 95d282854d Build audio_out2_port_tests with kyty_tests (#242) 2026-08-18 04:08:16 +02:00
nmzik c59a4565bc renderer: simplify texture cache queries
Remove the duplicate texture page-overlap check and avoid shared-owner refcount traffic for already validated query candidates.
2026-08-18 03:53:45 +02:00
nmzik 79e424182b renderer: simplify image buffer owner lookup 2026-08-18 03:53:45 +02:00
nmzik fe867d03f6 renderer: remove duplicate buffer containment checks 2026-08-18 03:53:45 +02:00
nmzik ae4e695425 renderer: remove duplicate texture candidate checks 2026-08-18 03:53:45 +02:00
nmzik 6407add888 renderer: align texture overlap retirement 2026-08-18 03:53:45 +02:00
nmzik 840d742230 renderer: align buffer synchronization flow 2026-08-18 03:53:45 +02:00
nmzik a798e12d7f renderer: reuse buffer overlap merge for images 2026-08-18 03:53:45 +02:00
nmzik d80e914b34 renderer: align buffer overlap merging 2026-08-18 03:53:45 +02:00
nmzik 395c34ecab renderer: recreate insufficient texture cache matches 2026-08-18 03:53:45 +02:00
nmzik 1e8317159e renderer: align maybe-dirty image handling 2026-08-18 03:53:45 +02:00
nmzik d0e08496be renderer: handle clean DMA copies before overlap checks 2026-08-18 03:53:45 +02:00
nmzik 377fb8f50a shader: use ISA-style decoder opcode names 2026-08-18 03:53:45 +02:00
nmzik 4220a747cb libc: remove duplicate Mtx ABI implementations
No idea why these were reimplemented in the first place at all. The loaded guest libc already provides them; Kyty only needs the lower libkernel pthread imports.
2026-08-18 03:53:45 +02:00
nmzik e24f29d514 renderer: diagnostics 2026-08-18 03:53:45 +02:00
nmzikandshadowbeat070 54aa8efc98 libc: match PS5 mutex unlock result semantics
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-08-18 03:53:45 +02:00
nmzikandshadowbeat070 89fdb1214a agc: ignore empty multi-DCB submissions
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
2026-08-18 03:53:45 +02:00
nmzik 01275fb211 shader_recompiler: rename/clean up typed SSA SPIR-V emitter 2026-08-18 03:53:45 +02:00
nmzik 5970c4ef95 shader_recompiler: remove legacy SPIR-V emitter 2026-08-18 03:53:44 +02:00
nmzik 24b40d4c03 renderer: add warning log 2026-08-18 03:53:44 +02:00
nmzik 146253afb6 renderer&guest_gpu: minor cleanup 2026-08-18 03:53:44 +02:00
nmzik c759236ccb renderer: support depth-compatible uint image views 2026-08-18 03:53:44 +02:00
nmzik ce39238210 renderer: preserve requested depth stencil view aspects 2026-08-18 03:53:44 +02:00
nmzik 330889e50b shader: implement RDNA2 frexp mantissa 2026-08-18 03:53:44 +02:00
nmzik 1be9568a02 shader: implement RDNA2 frexp exponent 2026-08-18 03:53:44 +02:00
nmzik 8ce06ccdf6 shader: route nested shared regions 2026-08-18 03:53:44 +02:00
nmzik 1dbac4fd79 shader: repair nested merge structurization 2026-08-18 03:53:44 +02:00
nmzik d80fbea51d shader: resolve subtractive dword SETPC tables 2026-08-18 03:53:44 +02:00
shadowbeat070 e239210279 Rewrite RenderDoc capture flow and host exceptions 2026-08-18 03:53:44 +02:00
nmzik e7c8bcb3a4 recompiler: deduplication cleanup 2026-08-18 03:53:44 +02:00
nmzik 0f3540b1ff shader: unify stage input metadata 2026-08-18 03:53:44 +02:00
nmzik 5ea92d95a7 add playgo hack 2026-08-18 03:53:44 +02:00
nmzik 5a32e270ad kernel: export scePthreadCancel 2026-08-18 03:53:44 +02:00
nmzik e857e332ac graphics: handle PS5 DMA_DATA prefetch 2026-08-18 03:53:44 +02:00
nmzik 44302cbf96 shader: implement RDNA2 V_MED3_I16 2026-08-18 03:53:44 +02:00
nmzik 6b306f64d5 graphics: execute valid PM4 rewind packets 2026-08-18 03:53:44 +02:00
nmzik e1366eb91d graphics: resolve Standard4KB color targets 2026-08-18 03:53:44 +02:00
nmzik 2a1a8e72e3 shader: preserve exec-masked FLAT address roots 2026-08-18 03:53:44 +02:00
nmzik 191cdff231 graphics: support tiled render target mip views 2026-08-18 03:53:43 +02:00
nmzik 7d8ee8b6b5 audio: provide live NGS2 system info 2026-08-18 03:53:43 +02:00
nmzik 01a1036640 audio: retire destroyed NGS2 racks 2026-08-18 03:53:43 +02:00
nmzik 7850cbeaef rename legacy Graphics -> Agc 2026-08-18 03:53:43 +02:00
nmzik af931d73c7 remove logging in hot path 2026-08-18 03:53:43 +02:00
nmzik 3a7cd93765 agc: ABIs fix - EventWrite/WriteData 2026-08-18 03:53:43 +02:00
nmzik a34bcbd951 agc: ABIs fix - acb/dcb 2026-08-18 03:53:43 +02:00
nmzik b0bd6470f3 graphics: accept AGC vertex-buffer-only shaders 2026-08-18 03:53:43 +02:00
nmzik 93398d6d0f graphics: sign-extend vector bitfield extracts 2026-08-18 03:53:43 +02:00
nmzik 4e343408dc graphics: size compute LDS from shader state 2026-08-18 03:53:43 +02:00
nmzik 1375d650a7 graphics: support RDNA2 dependency waits 2026-08-18 03:53:43 +02:00
nmzik d7f1bcaa19 graphics: support signed 16-bit integer packing 2026-08-18 03:53:43 +02:00
nmzik 2bb396f58f graphics: support SDWA arithmetic right shifts 2026-08-18 03:53:43 +02:00
nmzik af1ab60786 graphics: intersect texture views with resource mips 2026-08-18 03:53:43 +02:00
nmzik 430de9bd15 graphics: support dynamic indirect image tables 2026-08-18 03:53:43 +02:00
nmzik 1c4171c9eb graphics: accept SDWA byte conversion sources 2026-08-18 03:53:43 +02:00
nmzik 443bbcbf90 graphics: separate scalar address bindings 2026-08-18 03:53:43 +02:00
nmzik 83ee040b4d graphics: specialize invariant indirect image tables 2026-08-18 03:53:43 +02:00
Senaxx 980d89f739 graphics: support dynamic storage mip writes 2026-08-18 03:53:43 +02:00
Senaxx f785974cc1 graphics: accept storage mip ranges 2026-08-18 03:53:43 +02:00
nmzik 547b9b96cc graphics: upload sparse PRT image backing 2026-08-18 03:53:43 +02:00
nmzik 989477efad graphics: derive pixel stage activity from shader outputs 2026-08-18 03:53:43 +02:00
nmzik 6d273e4d9c graphics: accept forced HTile resummarization 2026-08-18 03:53:43 +02:00
Senaxx d7fd4a2864 graphics: accept asymmetric Z clip state 2026-08-18 03:53:42 +02:00
nmzik 5e27a28ab0 shader: fix missing homogeneous semantics 2026-08-18 03:53:42 +02:00
nmzik 67e550f2e7 agc: implement owner resource unregister 2026-08-18 03:53:42 +02:00
nmzik aec9b97b0c dialog: implement common dialog usage query 2026-08-18 03:53:42 +02:00
nmzik e4a95b2c26 playgo: reject missing package metadata 2026-08-18 03:53:42 +02:00
nmzik 6e82c376f6 kernel: allow renaming open save files 2026-08-18 03:53:42 +02:00
nmzik 4f1bc505cc graphics: support depth-tiled color targets 2026-08-18 03:53:42 +02:00
nmzik 484433b180 shader: fix V_ADD_NC_U32 2026-08-18 03:53:42 +02:00
nmzik 70f6af407e new ABI stubs 2026-08-18 03:53:42 +02:00
nmzik 7556a74f19 shader: implement s_bfe_i32 2026-08-18 03:53:42 +02:00
nmzik b4809e7170 Fix SPIR-V float control contract (Preserve FP32 Inf and NaN) 2026-08-18 03:53:42 +02:00
nmzik 0df20eb300 fix opcode regression 2026-08-18 03:53:42 +02:00
nmzik 5bfdf4267e Optimize decoder 2026-08-18 03:53:42 +02:00
nmzik f3cdf217ea shader: evaluate unsigned min in runtime descriptors 2026-08-18 03:53:42 +02:00
nmzik f453a752bb shader: snapshot wide SMEM sources before writes 2026-08-18 03:53:42 +02:00
nmzik 28a913894d graphics: implement RDNA2 64-bit first-set-bit 2026-08-18 03:53:42 +02:00
nmzik 0ffacf22e6 graphics: accept phi-mediated SRT cycles 2026-08-18 03:53:42 +02:00
nmzik 13e87a8e58 graphics: implement RDNA2 alignbyte shader opcode 2026-08-18 03:53:42 +02:00
nmzik 30d57bed06 graphics: honor primitive restart state 2026-08-18 03:53:42 +02:00
nmzik 965c30956d graphics: clamp storage buffers to committed memory 2026-08-18 03:53:42 +02:00
nmzik 09fec6815f shader: handle disabled RDNA2 traps 2026-08-18 03:53:42 +02:00
nmzik 00bd19194a shader: mask vector carry outputs by exec 2026-08-18 03:53:42 +02:00
nmzik 4a7bc55afa shader: implement RDNA2 subrev borrow 2026-08-18 03:53:42 +02:00
nmzik 9c316e6988 shader: implement RDNA2 v_cmp_gt_u64 2026-08-18 03:53:42 +02:00
nmzik a3c3cbb722 agc: fix async DMA data ABI 2026-08-18 03:53:42 +02:00
nmzik b42380ced6 shader_recompiler: add SSA 2026-08-18 03:53:41 +02:00
Hashim Khanandnmzik bc2f077c37 launcher: quote emulator paths that contain spaces (#244)
* launcher: quote emulator paths that contain spaces

Run the Linux helper script as a bash file argument instead of bash -c,
and build a properly quoted cmd.exe /K command on Windows so install
folders like "Sony Playstation 5" still launch. Fixes #179.

* launcher: fix cmd command quoting

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-16 22:13:12 +02:00
Hashim Khan 498d63b620 docs: fix mismatched screenshot alt texts in README (#249)
Align Neptunia ReVerse, Hellboy, and Paleo Pines image alts with their
captions so screen readers and link previews describe the right titles.
2026-08-16 22:05:07 +02:00
Hashim Khanandnmzik 566a45017e window: toggle borderless fullscreen with F11 and Alt+Enter (#246)
* window: toggle borderless fullscreen with F11 and Alt+Enter

Match the existing SDL_WINDOW_FULLSCREEN_DESKTOP startup path so testers
can switch modes at runtime. Reserve F11 in host input mappings. Fixes #217.

* window: consume fullscreen hotkey input

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-15 04:07:07 +02:00
nmzik 7a40dad90f agc: ABI fixes 2026-08-11 17:44:57 +02:00
nmzik a9e843b38e ci: link IME common code into dialog tests 2026-08-11 16:48:20 +02:00
nmzik 9a76719544 gpu: validate depth and cube texture descriptors
Accept SDK-valid depth sampler modulation and validate cube storage images as complete six-layer groups.
2026-08-11 16:45:32 +02:00
nmzik 472c8213db gpu: accept CB/DB release without GL2 writeback 2026-08-11 16:12:38 +02:00
nmzik a4f9c022ff ampr: make virtual address range query non-destructive 2026-08-11 16:12:38 +02:00
nmzik 97514884b1 Unify stack walking 2026-08-11 16:12:38 +02:00
nmzik 4980efa9b7 refactor(subsystems&ime): RAII lifecycle 2026-08-11 16:12:38 +02:00
nmzik 34cf130e4d Refactor PM4 control register decoding 2026-08-11 16:12:38 +02:00
nmzik 21aa7b66ce graphics: replace raw GPU enum values with typed formats 2026-08-11 16:12:38 +02:00
nmzik 11b95bed20 graphics: simplify render target format and color mapping resolution 2026-08-11 16:12:38 +02:00
243 changed files with 55595 additions and 49256 deletions

No files matched your search

+875
View File
@@ -0,0 +1,875 @@
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015-2026 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#pragma once
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html
//
#if !defined(RENDERDOC_NO_STDINT)
#include <stdint.h>
#endif
#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
#define RENDERDOC_CC __cdecl
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__sun__) || defined(__OpenBSD__)
#define RENDERDOC_CC
#elif defined(__APPLE__)
#define RENDERDOC_CC
#else
#error "Unknown platform"
#endif
#ifdef __cplusplus
extern "C" {
#endif
//////////////////////////////////////////////////////////////////////////////////////////////////
// Constants not used directly in below API
// This is a GUID/magic value used for when applications pass a path where shader debug
// information can be found to match up with a stripped shader.
// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue =
// RENDERDOC_ShaderDebugMagicValue_value
#define RENDERDOC_ShaderDebugMagicValue_struct \
{ \
0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
}
// as an alternative when you want a byte array (assuming x86 endianness):
#define RENDERDOC_ShaderDebugMagicValue_bytearray \
{ \
0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
}
// truncated version when only a uint64_t is available (e.g. Vulkan tags):
#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL
// this is a magic value for vulkan user tags to indicate which dispatchable API objects are which
// for object annotations
#define RENDERDOC_APIObjectAnnotationHelper 0xfbb3b337b664d0adULL
//////////////////////////////////////////////////////////////////////////////////////////////////
// RenderDoc capture options
//
typedef enum RENDERDOC_CaptureOption
{
// Allow the application to enable vsync
//
// Default - enabled
//
// 1 - The application can enable or disable vsync at will
// 0 - vsync is force disabled
eRENDERDOC_Option_AllowVSync = 0,
// Allow the application to enable fullscreen
//
// Default - enabled
//
// 1 - The application can enable or disable fullscreen at will
// 0 - fullscreen is force disabled
eRENDERDOC_Option_AllowFullscreen = 1,
// Record API debugging events and messages
//
// Default - disabled
//
// 1 - Enable built-in API debugging features and records the results into
// the capture, which is matched up with events on replay
// 0 - no API debugging is forcibly enabled
eRENDERDOC_Option_APIValidation = 2,
eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum
// Capture CPU callstacks for API events
//
// Default - disabled
//
// 1 - Enables capturing of callstacks
// 0 - no callstacks are captured
eRENDERDOC_Option_CaptureCallstacks = 3,
// When capturing CPU callstacks, only capture them from actions.
// This option does nothing without the above option being enabled
//
// Default - disabled
//
// 1 - Only captures callstacks for actions.
// Ignored if CaptureCallstacks is disabled
// 0 - Callstacks, if enabled, are captured for every event.
eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4,
eRENDERDOC_Option_CaptureCallstacksOnlyActions = 4,
// Specify a delay in seconds to wait for a debugger to attach, after
// creating or injecting into a process, before continuing to allow it to run.
//
// 0 indicates no delay, and the process will run immediately after injection
//
// Default - 0 seconds
//
eRENDERDOC_Option_DelayForDebugger = 5,
// Verify buffer access. This includes checking the memory returned by a Map() call to
// detect any out-of-bounds modification, as well as initialising buffers with undefined contents
// to a marker value to catch use of uninitialised memory.
//
// NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do
// not do the same kind of interception & checking and undefined contents are really undefined.
//
// Default - disabled
//
// 1 - Verify buffer access
// 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in
// RenderDoc.
eRENDERDOC_Option_VerifyBufferAccess = 6,
// The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites.
// This option now controls the filling of uninitialised buffers with 0xdddddddd which was
// previously always enabled
eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess,
// Hooks any system API calls that create child processes, and injects
// RenderDoc into them recursively with the same options.
//
// Default - disabled
//
// 1 - Hooks into spawned child processes
// 0 - Child processes are not hooked by RenderDoc
eRENDERDOC_Option_HookIntoChildren = 7,
// By default RenderDoc only includes resources in the final capture necessary
// for that frame, this allows you to override that behaviour.
//
// Default - disabled
//
// 1 - all live resources at the time of capture are included in the capture
// and available for inspection
// 0 - only the resources referenced by the captured frame are included
eRENDERDOC_Option_RefAllResources = 8,
// **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or
// getting it will be ignored, to allow compatibility with older versions.
// In v1.1 the option acts as if it's always enabled.
//
// By default RenderDoc skips saving initial states for resources where the
// previous contents don't appear to be used, assuming that writes before
// reads indicate previous contents aren't used.
//
// Default - disabled
//
// 1 - initial contents at the start of each captured frame are saved, even if
// they are later overwritten or cleared before being used.
// 0 - unless a read is detected, initial contents will not be saved and will
// appear as black or empty data.
eRENDERDOC_Option_SaveAllInitials = 9,
// In APIs that allow for the recording of command lists to be replayed later,
// RenderDoc may choose to not capture command lists before a frame capture is
// triggered, to reduce overheads. This means any command lists recorded once
// and replayed many times will not be available and may cause a failure to
// capture.
//
// NOTE: This is only true for APIs where multithreading is difficult or
// discouraged. Newer APIs like Vulkan and D3D12 will ignore this option
// and always capture all command lists since the API is heavily oriented
// around it and the overheads have been reduced by API design.
//
// 1 - All command lists are captured from the start of the application
// 0 - Command lists are only captured if their recording begins during
// the period when a frame capture is in progress.
eRENDERDOC_Option_CaptureAllCmdLists = 10,
// Mute API debugging output when the API validation mode option is enabled
//
// Default - enabled
//
// 1 - Mute any API debug messages from being displayed or passed through
// 0 - API debugging is displayed as normal
eRENDERDOC_Option_DebugOutputMute = 11,
// Option to allow vendor extensions to be used even when they may be
// incompatible with RenderDoc and cause corrupted replays or crashes.
//
// Default - inactive
//
// No values are documented, this option should only be used when absolutely
// necessary as directed by a RenderDoc developer.
eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12,
// Define a soft memory limit which some APIs may aim to keep overhead under where
// possible. Anything above this limit will where possible be saved directly to disk during
// capture.
// This will cause increased disk space use (which may cause a capture to fail if disk space is
// exhausted) as well as slower capture times.
//
// Not all memory allocations may be deferred like this so it is not a guarantee of a memory
// limit.
//
// Units are in MBs, suggested values would range from 200MB to 1000MB.
//
// Default - 0 Megabytes
eRENDERDOC_Option_SoftMemoryLimit = 13,
} RENDERDOC_CaptureOption;
// Sets an option that controls how RenderDoc behaves on capture.
//
// Returns 1 if the option and value are valid
// Returns 0 if either is invalid and the option is unchanged
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val);
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val);
// Gets the current value of an option as a uint32_t
//
// If the option is invalid, 0xffffffff is returned
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt);
// Gets the current value of an option as a float
//
// If the option is invalid, -FLT_MAX is returned
typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt);
typedef enum RENDERDOC_InputButton
{
// '0' - '9' matches ASCII values
eRENDERDOC_Key_0 = 0x30,
eRENDERDOC_Key_1 = 0x31,
eRENDERDOC_Key_2 = 0x32,
eRENDERDOC_Key_3 = 0x33,
eRENDERDOC_Key_4 = 0x34,
eRENDERDOC_Key_5 = 0x35,
eRENDERDOC_Key_6 = 0x36,
eRENDERDOC_Key_7 = 0x37,
eRENDERDOC_Key_8 = 0x38,
eRENDERDOC_Key_9 = 0x39,
// 'A' - 'Z' matches ASCII values
eRENDERDOC_Key_A = 0x41,
eRENDERDOC_Key_B = 0x42,
eRENDERDOC_Key_C = 0x43,
eRENDERDOC_Key_D = 0x44,
eRENDERDOC_Key_E = 0x45,
eRENDERDOC_Key_F = 0x46,
eRENDERDOC_Key_G = 0x47,
eRENDERDOC_Key_H = 0x48,
eRENDERDOC_Key_I = 0x49,
eRENDERDOC_Key_J = 0x4A,
eRENDERDOC_Key_K = 0x4B,
eRENDERDOC_Key_L = 0x4C,
eRENDERDOC_Key_M = 0x4D,
eRENDERDOC_Key_N = 0x4E,
eRENDERDOC_Key_O = 0x4F,
eRENDERDOC_Key_P = 0x50,
eRENDERDOC_Key_Q = 0x51,
eRENDERDOC_Key_R = 0x52,
eRENDERDOC_Key_S = 0x53,
eRENDERDOC_Key_T = 0x54,
eRENDERDOC_Key_U = 0x55,
eRENDERDOC_Key_V = 0x56,
eRENDERDOC_Key_W = 0x57,
eRENDERDOC_Key_X = 0x58,
eRENDERDOC_Key_Y = 0x59,
eRENDERDOC_Key_Z = 0x5A,
// leave the rest of the ASCII range free
// in case we want to use it later
eRENDERDOC_Key_NonPrintable = 0x100,
eRENDERDOC_Key_Divide,
eRENDERDOC_Key_Multiply,
eRENDERDOC_Key_Subtract,
eRENDERDOC_Key_Plus,
eRENDERDOC_Key_F1,
eRENDERDOC_Key_F2,
eRENDERDOC_Key_F3,
eRENDERDOC_Key_F4,
eRENDERDOC_Key_F5,
eRENDERDOC_Key_F6,
eRENDERDOC_Key_F7,
eRENDERDOC_Key_F8,
eRENDERDOC_Key_F9,
eRENDERDOC_Key_F10,
eRENDERDOC_Key_F11,
eRENDERDOC_Key_F12,
eRENDERDOC_Key_Home,
eRENDERDOC_Key_End,
eRENDERDOC_Key_Insert,
eRENDERDOC_Key_Delete,
eRENDERDOC_Key_PageUp,
eRENDERDOC_Key_PageDn,
eRENDERDOC_Key_Backspace,
eRENDERDOC_Key_Tab,
eRENDERDOC_Key_PrtScrn,
eRENDERDOC_Key_Pause,
eRENDERDOC_Key_Max,
} RENDERDOC_InputButton;
// Sets which key or keys can be used to toggle focus between multiple windows
//
// If keys is NULL or num is 0, toggle keys will be disabled
typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num);
// Sets which key or keys can be used to capture the next frame
//
// If keys is NULL or num is 0, captures keys will be disabled
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num);
typedef enum RENDERDOC_OverlayBits
{
// This single bit controls whether the overlay is enabled or disabled globally
eRENDERDOC_Overlay_Enabled = 0x1,
// Show the average framerate over several seconds as well as min/max
eRENDERDOC_Overlay_FrameRate = 0x2,
// Show the current frame number
eRENDERDOC_Overlay_FrameNumber = 0x4,
// Show a list of recent captures, and how many captures have been made
eRENDERDOC_Overlay_CaptureList = 0x8,
// Default values for the overlay mask
eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate |
eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList),
// Enable all bits
eRENDERDOC_Overlay_All = 0x7ffffff,
// Disable all bits
eRENDERDOC_Overlay_None = 0,
} RENDERDOC_OverlayBits;
// returns the overlay bits that have been set
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)(void);
// sets the overlay bits with an and & or mask
typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or);
// this function will attempt to remove RenderDoc's hooks in the application.
//
// Note: that this can only work correctly if done immediately after
// the module is loaded, before any API work happens. RenderDoc will remove its
// injected hooks and shut down. Behaviour is undefined if this is called
// after any API functions have been called, and there is still no guarantee of
// success.
typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)(void);
// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers.
typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown;
// This function will unload RenderDoc's crash handler.
//
// If you use your own crash handler and don't want RenderDoc's handler to
// intercede, you can call this function to unload it and any unhandled
// exceptions will pass to the next handler.
typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)(void);
// Sets the capture file path template
//
// pathtemplate is a UTF-8 string that gives a template for how captures will be named
// and where they will be saved.
//
// Any extension is stripped off the path, and captures are saved in the directory
// specified, and named with the filename and the frame number appended. If the
// directory does not exist it will be created, including any parent directories.
//
// If pathtemplate is NULL, the template will remain unchanged
//
// Example:
//
// SetCaptureFilePathTemplate("my_captures/example");
//
// Capture #1 -> my_captures/example_frame123.rdc
// Capture #2 -> my_captures/example_frame456.rdc
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate);
// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string
typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)(void);
// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers.
typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate;
typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate;
// returns the number of captures that have been made
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)(void);
// This function returns the details of a capture, by index. New captures are added
// to the end of the list.
//
// filename will be filled with the absolute path to the capture file, as a UTF-8 string
// pathlength will be written with the length in bytes of the filename string
// timestamp will be written with the time of the capture, in seconds since the Unix epoch
//
// Any of the parameters can be NULL and they'll be skipped.
//
// The function will return 1 if the capture index is valid, or 0 if the index is invalid
// If the index is invalid, the values will be unchanged
//
// Note: when captures are deleted in the UI they will remain in this list, so the
// capture path may not exist anymore.
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename,
uint32_t *pathlength, uint64_t *timestamp);
// Sets the comments associated with a capture file. These comments are displayed in the
// UI program when opening.
//
// filePath should be a path to the capture file to add comments to. If set to NULL or ""
// the most recent capture file created made will be used instead.
// comments should be a NULL-terminated UTF-8 string to add as comments.
//
// Any existing comments will be overwritten.
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath,
const char *comments);
// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)(void);
// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers.
// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for
// backwards compatibility with old code, it is castable either way since it's ABI compatible
// as the same function pointer type.
typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected;
// This function will launch the Replay UI associated with the RenderDoc library injected
// into the running application.
//
// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter
// to connect to this application
// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open
// if cmdline is NULL, the command line will be empty.
//
// returns the PID of the replay UI if successful, 0 if not successful.
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl,
const char *cmdline);
// RenderDoc can return a higher version than requested if it's backwards compatible,
// this function returns the actual version returned. If a parameter is NULL, it will be
// ignored and the others will be filled out.
typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch);
// Requests that the replay UI show itself (if hidden or not the current top window). This can be
// used in conjunction with IsTargetControlConnected and LaunchReplayUI to intelligently handle
// showing the UI after making a capture.
//
// This will return 1 if the request was successfully passed on, though it's not guaranteed that
// the UI will be on top in all cases depending on OS rules. It will return 0 if there is no current
// target control connection to make such a request, or if there was another error
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_ShowReplayUI)(void);
//////////////////////////////////////////////////////////////////////////
// Capturing functions
//
// A device pointer is a pointer to the API's root handle.
//
// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc
typedef void *RENDERDOC_DevicePointer;
// A window handle is the OS's native window handle
//
// This would be an HWND, GLXDrawable, etc
typedef void *RENDERDOC_WindowHandle;
// A helper macro for Vulkan, where the device handle cannot be used directly.
//
// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use.
//
// Specifically, the value needed is the dispatch table pointer, which sits as the first
// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and
// indirect once.
#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst)))
// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will
// respond to keypresses. Neither parameter can be NULL
typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device,
RENDERDOC_WindowHandle wndHandle);
// capture the next frame on whichever window and API is currently considered active
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)(void);
// capture the next N frames on whichever window and API is currently considered active
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames);
// When choosing either a device pointer or a window handle to capture, you can pass NULL.
// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify
// any API rendering to a specific window, or a specific API instance rendering to any window,
// or in the simplest case of one window and one API, you can just pass NULL for both.
//
// In either case, if there are two or more possible matching (device,window) pairs it
// is undefined which one will be captured.
//
// Note: for headless rendering you can pass NULL for the window handle and either specify
// a device pointer or leave it NULL as above.
// Immediately starts capturing API calls on the specified device pointer and window handle.
//
// If there is no matching thing to capture (e.g. no supported API has been initialised),
// this will do nothing.
//
// The results are undefined (including crashes) if two captures are started overlapping,
// even on separate devices and/oror windows.
typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device,
RENDERDOC_WindowHandle wndHandle);
// Returns whether or not a frame capture is currently ongoing anywhere.
//
// This will return 1 if a capture is ongoing, and 0 if there is no capture running
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)(void);
// Ends capturing immediately.
//
// This will return 1 if the capture succeeded, and 0 if there was an error capturing.
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device,
RENDERDOC_WindowHandle wndHandle);
// Ends capturing immediately and discard any data stored without saving to disk.
//
// This will return 1 if the capture was discarded, and 0 if there was an error or no capture
// was in progress
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device,
RENDERDOC_WindowHandle wndHandle);
// Only valid to be called between a call to StartFrameCapture and EndFrameCapture. Gives a custom
// title to the capture produced which will be displayed in the UI.
//
// If multiple captures are ongoing, this title will be applied to the first capture to end after
// this call. The second capture to end will have no title, unless this function is called again.
//
// Calling this function has no effect if no capture is currently running, and if it is called
// multiple times only the last title will be used.
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureTitle)(const char *title);
// Annotations API:
//
// These functions allow you to specify annotations either on a per-command level, or a per-object
// level.
//
// Basic types of annotations are supported, as well as vector versions and references to API objects.
//
// The annotations are stored as keys, with the key being a dot-separated path allowing arbitrary
// nesting and user organisation. The keys are sorted in human order so `foo.2.bar` will be displayed
// before `foo.10.bar` to allow creation of arrays if desired.
//
// Deleting an annotation can be done by assigning an empty value to it.
// the type of an annotation value, or Empty to delete an annotation
typedef enum RENDERDOC_AnnotationType
{
eRENDERDOC_Empty,
eRENDERDOC_Bool,
eRENDERDOC_Int32,
eRENDERDOC_UInt32,
eRENDERDOC_Int64,
eRENDERDOC_UInt64,
eRENDERDOC_Float,
eRENDERDOC_Double,
eRENDERDOC_String,
eRENDERDOC_APIObject,
eRENDERDOC_AnnotationMax = 0x7FFFFFFF,
} RENDERDOC_AnnotationType;
// a union with vector annotation value data
typedef union RENDERDOC_AnnotationVectorValue
{
bool boolean[4];
int32_t int32[4];
int64_t int64[4];
uint32_t uint32[4];
uint64_t uint64[4];
float float32[4];
double float64[4];
} RENDERDOC_AnnotationVectorValue;
// a union with scalar annotation value data
typedef union RENDERDOC_AnnotationValue
{
bool boolean;
int32_t int32;
int64_t int64;
uint32_t uint32;
uint64_t uint64;
float float32;
double float64;
RENDERDOC_AnnotationVectorValue vector;
const char *string;
void *apiObject;
} RENDERDOC_AnnotationValue;
// a struct for specifying a GL object, as we don't have pointers we can use so instead we specify a
// pointer to this struct giving both the type and the name
typedef struct RENDERDOC_GLResourceReference
{
// this is the same GLenum identifier as passed to glObjectLabel
uint32_t identifier;
uint32_t name;
} GLResourceReference;
// simple C++ helpers to avoid the need for a temporary objects for value passing and GL object specification
#ifdef __cplusplus
struct RDGLObjectHelper
{
RENDERDOC_GLResourceReference gl;
RDGLObjectHelper(uint32_t identifier, uint32_t name)
{
gl.identifier = identifier;
gl.name = name;
}
operator RENDERDOC_GLResourceReference *() { return &gl; }
};
struct RDAnnotationHelper
{
RENDERDOC_AnnotationValue val;
RDAnnotationHelper(bool b) { val.boolean = b; }
RDAnnotationHelper(int32_t i) { val.int32 = i; }
RDAnnotationHelper(int64_t i) { val.int64 = i; }
RDAnnotationHelper(uint32_t i) { val.uint32 = i; }
RDAnnotationHelper(uint64_t i) { val.uint64 = i; }
RDAnnotationHelper(float f) { val.float32 = f; }
RDAnnotationHelper(double d) { val.float64 = d; }
RDAnnotationHelper(const char *s) { val.string = s; }
operator RENDERDOC_AnnotationValue *() { return &val; }
};
#endif
// The device is specified in the same way as other API calls that take a RENDERDOC_DevicePointer
// to specify the device.
//
// The object or queue/commandbuffer will depend on the graphics API in question.
//
// Return value:
// 0 - The annotation was applied successfully.
// 1 - The device is unknown/invalid
// 2 - The device is valid but the annotation is not supported for API-specific reasons, such as an
// unrecognised or invalid object or queue/commandbuffer
// 3 - The call is ill-formed or invalid e.g. empty is specified with a value pointer, or non-empty
// is specified with a NULL value pointer
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_SetObjectAnnotation)(RENDERDOC_DevicePointer device,
void *object, const char *key,
RENDERDOC_AnnotationType valueType,
uint32_t valueVectorWidth,
const RENDERDOC_AnnotationValue *value);
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_SetCommandAnnotation)(
RENDERDOC_DevicePointer device, void *queueOrCommandBuffer, const char *key,
RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth,
const RENDERDOC_AnnotationValue *value);
//////////////////////////////////////////////////////////////////////////////////////////////////
// RenderDoc API versions
//
// RenderDoc uses semantic versioning (http://semver.org/).
//
// MAJOR version is incremented when incompatible API changes happen.
// MINOR version is incremented when functionality is added in a backwards-compatible manner.
// PATCH version is incremented when backwards-compatible bug fixes happen.
//
// Note that this means the API returned can be higher than the one you might have requested.
// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned
// instead of 1.0.0. You can check this with the GetAPIVersion entry point
typedef enum RENDERDOC_Version
{
eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00
eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01
eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02
eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00
eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01
eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02
eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00
eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00
eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00
eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01
eRENDERDOC_API_Version_1_4_2 = 10402, // RENDERDOC_API_1_4_2 = 1 04 02
eRENDERDOC_API_Version_1_5_0 = 10500, // RENDERDOC_API_1_5_0 = 1 05 00
eRENDERDOC_API_Version_1_6_0 = 10600, // RENDERDOC_API_1_6_0 = 1 06 00
eRENDERDOC_API_Version_1_7_0 = 10700, // RENDERDOC_API_1_7_0 = 1 07 00
} RENDERDOC_Version;
// API version changelog:
//
// 1.0.0 - initial release
// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered
// by keypress or TriggerCapture, instead of Start/EndFrameCapture.
// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation
// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new
// function pointer is added to the end of the struct, the original layout is identical
// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote
// replay/remote server concept in replay UI)
// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these
// are captures and not debug logging files. This is the first API version in the v1.0
// branch.
// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be
// displayed in the UI program on load.
// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions
// which allows users to opt-in to allowing unsupported vendor extensions to function.
// Should be used at the user's own risk.
// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to
// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to
// 0xdddddddd of uninitialised buffer contents.
// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop
// capturing without saving anything to disk.
// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening
// 1.4.2 - Refactor: Renamed 'draws' to 'actions' in callstack capture option.
// 1.5.0 - Added feature: ShowReplayUI() to request that the replay UI show itself if connected
// 1.6.0 - Added feature: SetCaptureTitle() which can be used to set a title for a
// capture made with StartFrameCapture() or EndFrameCapture()
// 1.7.0 - Added feature: SetObjectAnnotation() / SetCommandAnnotation() for adding rich
// annotations to objects and command streams
typedef struct RENDERDOC_API_1_7_0
{
pRENDERDOC_GetAPIVersion GetAPIVersion;
pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32;
pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32;
pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32;
pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32;
pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys;
pRENDERDOC_SetCaptureKeys SetCaptureKeys;
pRENDERDOC_GetOverlayBits GetOverlayBits;
pRENDERDOC_MaskOverlayBits MaskOverlayBits;
// Shutdown was renamed to RemoveHooks in 1.4.1.
// These unions allow old code to continue compiling without changes
union
{
pRENDERDOC_Shutdown Shutdown;
pRENDERDOC_RemoveHooks RemoveHooks;
};
pRENDERDOC_UnloadCrashHandler UnloadCrashHandler;
// Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2.
// These unions allow old code to continue compiling without changes
union
{
// deprecated name
pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate;
// current name
pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate;
};
union
{
// deprecated name
pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate;
// current name
pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate;
};
pRENDERDOC_GetNumCaptures GetNumCaptures;
pRENDERDOC_GetCapture GetCapture;
pRENDERDOC_TriggerCapture TriggerCapture;
// IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1.
// This union allows old code to continue compiling without changes
union
{
// deprecated name
pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected;
// current name
pRENDERDOC_IsTargetControlConnected IsTargetControlConnected;
};
pRENDERDOC_LaunchReplayUI LaunchReplayUI;
pRENDERDOC_SetActiveWindow SetActiveWindow;
pRENDERDOC_StartFrameCapture StartFrameCapture;
pRENDERDOC_IsFrameCapturing IsFrameCapturing;
pRENDERDOC_EndFrameCapture EndFrameCapture;
// new function in 1.1.0
pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture;
// new function in 1.2.0
pRENDERDOC_SetCaptureFileComments SetCaptureFileComments;
// new function in 1.4.0
pRENDERDOC_DiscardFrameCapture DiscardFrameCapture;
// new function in 1.5.0
pRENDERDOC_ShowReplayUI ShowReplayUI;
// new function in 1.6.0
pRENDERDOC_SetCaptureTitle SetCaptureTitle;
// new functions in 1.7.0
pRENDERDOC_SetObjectAnnotation SetObjectAnnotation;
pRENDERDOC_SetCommandAnnotation SetCommandAnnotation;
} RENDERDOC_API_1_7_0;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_0;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_1;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_2;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_0;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_1;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_2;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_2_0;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_3_0;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_0;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_1;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_2;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_5_0;
typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_6_0;
//////////////////////////////////////////////////////////////////////////////////////////////////
// RenderDoc API entry point
//
// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available.
//
// The name is the same as the typedef - "RENDERDOC_GetAPI"
//
// This function is not thread safe, and should not be called on multiple threads at once.
// Ideally, call this once as early as possible in your application's startup, before doing
// any API work, since some configuration functionality etc has to be done also before
// initialising any APIs.
//
// Parameters:
// version is a single value from the RENDERDOC_Version above.
//
// outAPIPointers will be filled out with a pointer to the corresponding struct of function
// pointers.
//
// Returns:
// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested
// 0 - if the requested version is not supported or the arguments are invalid.
//
typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers);
#ifdef __cplusplus
} // extern "C"
#endif
+33 -19
View File
@@ -203,14 +203,21 @@ file(GLOB kyty_emulator_src CONFIGURE_DEPENDS
"${KYTY_SOURCE_DIR}/graphics/shader/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/cfg/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/cfg/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/decompiler/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/decompiler/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/emitter/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/emitter/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/cfg/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/cfg/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/decode/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/decode/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/translate/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/frontend/translate/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/backend/spirv/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/backend/spirv/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/opcodes/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/opcodes/*.h"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/opcodes/*.inc"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/*.h"
"${KYTY_SOURCE_DIR}/graphics/presentation/*.cpp"
"${KYTY_SOURCE_DIR}/graphics/presentation/*.h"
"${KYTY_SOURCE_DIR}/graphics/presentation/window/*.cpp"
@@ -322,6 +329,7 @@ set(inc_headers
${KYTY_THIRD_PARTY_DIR}/fmt/include
${KYTY_THIRD_PARTY_DIR}/xxhash
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
${KYTY_THIRD_PARTY_DIR}/renderdoc
${KYTY_THIRD_PARTY_DIR}/stb
)
@@ -367,9 +375,8 @@ add_kyty_full_emulator_test(shader_cfg_tests "${KYTY_TESTS_DIR}/shaderCfgTests.c
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ScalarProvenanceTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ReadLaneElimination.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ScalarProvenance.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/SrtWalker.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ReadLaneElimination.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
)
target_link_libraries(scalar_provenance_tests fmt::fmt)
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
@@ -378,6 +385,7 @@ add_executable(page_manager_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/PageManagerTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
)
target_link_libraries(page_manager_tests common)
target_include_directories(page_manager_tests PRIVATE ${inc_headers})
add_executable(bit_array_tests EXCLUDE_FROM_ALL
@@ -387,6 +395,7 @@ target_include_directories(bit_array_tests PRIVATE ${inc_headers})
add_executable(ime_dialog_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ImeDialogTests.cpp"
"${KYTY_SOURCE_DIR}/libs/imeCommon.cpp"
"${KYTY_SOURCE_DIR}/libs/imeDialog.cpp"
)
target_include_directories(ime_dialog_tests PRIVATE ${inc_headers})
@@ -411,9 +420,8 @@ add_executable(shader_stage_runtime_tests EXCLUDE_FROM_ALL
"${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/ResourceMaterialization.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ScalarProvenance.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/SrtWalker.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})
@@ -422,13 +430,11 @@ add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ResourceTrackingTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/guest_gpu/gpu_format.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/hostMemory.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ScalarProvenance.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/SrtWalker.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/SrtPatcher.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ResourceTracking.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ResourceMaterialization.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/ShaderInfoCollection.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/BindingLayout.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/SrtWalker.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp"
"${KYTY_SOURCE_DIR}/graphics/shader/recompiler/ir/passes/BindingLayout.cpp"
)
target_link_libraries(resource_tracking_tests fmt::fmt)
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
@@ -449,6 +455,9 @@ add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
target_link_libraries(event_queue_lifetime_tests common fmt::fmt)
target_include_directories(event_queue_lifetime_tests PRIVATE ${inc_headers})
add_kyty_full_emulator_test(kernel_file_system_tests
"${KYTY_TESTS_DIR}/KernelFileSystemTests.cpp")
add_executable(sync_on_address_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/SyncOnAddressTests.cpp"
"${KYTY_SOURCE_DIR}/kernel/syncOnAddress.cpp"
@@ -509,9 +518,12 @@ if(BUILD_TESTING)
add_test(NAME shader_stage_runtime COMMAND $<TARGET_FILE:shader_stage_runtime_tests>)
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
add_test(NAME 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 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 virtual_memory_allocation
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
add_test(NAME guest_red_zone_patcher
@@ -560,9 +572,11 @@ if(BUILD_TESTING)
shader_stage_runtime_tests
resource_tracking_tests
event_queue_lifetime_tests
kernel_file_system_tests
sync_on_address_tests
shader_recompiler_compute_tests
virtual_memory_allocation_tests
audio_out2_port_tests
)
endif()
+3 -3
View File
@@ -55,7 +55,7 @@ graphical glitches, low compatibility, and poor performance.
<tr>
<td align="center">
<strong>Neptunia ReVerse</strong><br>
<img src="docs/screenshots/ps5-04.png" width="300" alt="Minecraft Legends running in KytyPS5">
<img src="docs/screenshots/ps5-04.png" width="300" alt="Neptunia ReVerse running in KytyPS5">
</td>
<td align="center">
<strong>SILENT HILL: The Short Message</strong><br>
@@ -65,11 +65,11 @@ graphical glitches, low compatibility, and poor performance.
<tr>
<td align="center">
<strong>Hellboy</strong><br>
<img src="docs/screenshots/ps5-02.png" width="300" alt="Disgaea 6 running in KytyPS5">
<img src="docs/screenshots/ps5-02.png" width="300" alt="Hellboy running in KytyPS5">
</td>
<td align="center">
<strong>Paleo Pines</strong><br>
<img src="docs/screenshots/ps5-06.png" width="300" alt="Dreaming Sarah running in KytyPS5">
<img src="docs/screenshots/ps5-06.png" width="300" alt="Paleo Pines running in KytyPS5">
</td>
</tr>
</table>
+1 -1
View File
@@ -35,7 +35,7 @@ static std::string BuildFatalReport(const char* title, std::string_view text, co
static int DbgReport(const char* title, std::string_view text, const char* file, int line) {
Log::WriteFatal(BuildFatalReport(title, text, file, line));
SubsystemsListSingleton::Instance()->ShutdownAll();
Subsystems::EmergencyShutdownActive();
return 1;
}
-21
View File
@@ -1,21 +0,0 @@
#include "common/commonSubsystem.h"
#include "common/byteBuffer.h" // IWYU pragma: associated
#include "common/common.h" // IWYU pragma: associated
#include "common/debug.h" // IWYU pragma: associated
#include "common/hash.h" // IWYU pragma: associated
#include "common/magicEnum.h" // IWYU pragma: associated
#include "common/singleton.h" // IWYU pragma: associated
#include "common/virtualMemory.h"
namespace Common {
KYTY_SUBSYSTEM_INIT(Common) {
VirtualMemory::Init();
}
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Common) {}
KYTY_SUBSYSTEM_DESTROY(Common) {}
} // namespace Common
-12
View File
@@ -1,12 +0,0 @@
#ifndef KYTY_COMMON_COMMON_SUBSYSTEM_H_
#define KYTY_COMMON_COMMON_SUBSYSTEM_H_
#include "common/subsystems.h"
namespace Common {
KYTY_SUBSYSTEM_DEFINE(Common);
} // namespace Common
#endif /* KYTY_COMMON_COMMON_SUBSYSTEM_H_ */
+12 -4
View File
@@ -9,15 +9,15 @@ namespace Config {
static std::unique_ptr<ConfigOptions> g_config;
KYTY_SUBSYSTEM_INIT(Config) {
void Initialize() {
EXIT_IF(g_config != nullptr);
g_config = std::make_unique<ConfigOptions>();
}
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Config) {}
KYTY_SUBSYSTEM_DESTROY(Config) {}
void Shutdown() {
g_config.reset();
}
void Load(const ConfigOptions& cfg) {
EXIT_IF(g_config == nullptr);
@@ -93,6 +93,10 @@ bool SpirvDebugPrintfEnabled() {
return g_config->spirv_debug_printf_enabled;
}
bool GpuAssistedValidationEnabled() {
return g_config->gpu_assisted_validation_enabled && g_config->vulkan_validation_enabled;
}
bool RenderDocEnabled() {
return g_config->renderdoc_enabled;
}
@@ -101,6 +105,10 @@ bool ReadbackLinearImagesEnabled() {
return g_config->readback_linear_images;
}
bool PlayGoHackEnabled() {
return g_config->playgo_hack_enabled;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool RedZoneProtectionEnabled() {
return g_config->red_zone_protection_enabled;
+13 -2
View File
@@ -2,7 +2,6 @@
#define KYTY_COMMON_EMULATOR_CONFIG_H_
#include "common/common.h"
#include "common/subsystems.h"
#include <filesystem>
#include <string>
@@ -10,7 +9,14 @@
namespace Config {
KYTY_SUBSYSTEM_DEFINE(Config);
void Initialize();
void Shutdown();
struct Lifecycle {
static constexpr const char* name = "Config";
static constexpr auto initialize = Config::Initialize;
static constexpr auto shutdown = Config::Shutdown;
};
enum class ShaderOptimizationType { None, Size, Performance };
@@ -43,8 +49,10 @@ struct ConfigOptions {
std::filesystem::path printf_output_file = "_kyty.txt";
ProfilerDirection profiler_direction = ProfilerDirection::None;
bool spirv_debug_printf_enabled = false;
bool gpu_assisted_validation_enabled = false;
bool renderdoc_enabled = false;
bool readback_linear_images = false;
bool playgo_hack_enabled = false;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool red_zone_protection_enabled = false;
#endif
@@ -77,8 +85,11 @@ ProfilerDirection GetProfilerDirection();
bool SpirvDebugPrintfEnabled();
bool GpuAssistedValidationEnabled();
bool RenderDocEnabled();
bool ReadbackLinearImagesEnabled();
bool PlayGoHackEnabled();
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool RedZoneProtectionEnabled();
#endif
+10 -79
View File
@@ -2,7 +2,6 @@
#include <atomic>
#include <cstdio>
#include <cstdlib>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#include <windows.h> // IWYU pragma: keep
@@ -28,54 +27,14 @@ namespace Common::HostException {
static std::atomic<Handler> g_handler {nullptr};
static std::atomic_uint32_t g_install_state {0};
static thread_local bool g_in_exception_filter = false;
static_assert(decltype(g_handler)::is_always_lock_free);
static_assert(decltype(g_install_state)::is_always_lock_free);
[[noreturn]] static void FailFast(const char* reason) noexcept {
std::fputs("HostException fail-fast: ", stderr);
std::fputs(reason != nullptr ? reason : "unspecified", stderr);
std::fputc('\n', stderr);
std::fflush(stderr);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(EXCEPTION_NONCONTINUABLE_EXCEPTION));
#endif
std::_Exit(321);
}
class FilterScope final {
public:
FilterScope() noexcept {
if (g_in_exception_filter) {
FailFast("nested exception while resolving a host fault");
}
g_in_exception_filter = true;
}
~FilterScope() { g_in_exception_filter = false; }
KYTY_CLASS_NO_COPY(FilterScope);
};
static Handler LoadInstalledHandler() noexcept {
if (g_install_state.load(std::memory_order_acquire) == 0) {
FailFast("host exception handler is not installed");
}
const auto handler = g_handler.load(std::memory_order_acquire);
if (handler == nullptr) {
FailFast("host exception callback is null");
}
return handler;
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
FilterScope filter_scope;
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) noexcept {
auto* exception_record = exception->ExceptionRecord;
if (exception_record->ExceptionCode == DBG_PRINTEXCEPTION_C ||
@@ -105,12 +64,6 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
} else if (exception_record->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) {
info.type = ExceptionType::IllegalInstruction;
} else {
printf("Unhandled win exception: code=0x%08" PRIx32 ", addr=0x%016" PRIx64
", rip=0x%016" PRIx64 ", rsp=0x%016" PRIx64 ", rbp=0x%016" PRIx64 "\n",
static_cast<uint32_t>(exception_record->ExceptionCode),
reinterpret_cast<uint64_t>(exception_record->ExceptionAddress),
exception->ContextRecord->Rip, exception->ContextRecord->Rsp,
exception->ContextRecord->Rbp);
return EXCEPTION_CONTINUE_SEARCH;
}
@@ -131,28 +84,21 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
info.r14 = exception->ContextRecord->R14;
info.r15 = exception->ContextRecord->R15;
const auto handler = LoadInstalledHandler();
return handler(info) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
const auto handler = g_handler.load(std::memory_order_acquire);
if (handler != nullptr && handler(info)) {
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
#elif defined(__APPLE__)
static std::atomic<Handler> g_handler {nullptr};
static std::atomic_uint32_t g_install_state {0};
static thread_local bool g_in_exception_filter = false;
static_assert(decltype(g_handler)::is_always_lock_free);
static_assert(decltype(g_install_state)::is_always_lock_free);
[[noreturn]] static void FailFast(const char* reason) noexcept {
std::fputs("HostException fail-fast: ", stderr);
std::fputs(reason != nullptr ? reason : "unspecified", stderr);
std::fputc('\n', stderr);
std::fflush(stderr);
std::_Exit(321);
}
// Translate the x86-64 page-fault error code (mcontext __es.__err) into an access type.
// bit 1 (0x2) = write, bit 4 (0x10) = instruction fetch, otherwise a read.
static AccessViolationType DecodeAccess(uint64_t err) {
@@ -170,11 +116,6 @@ static AccessViolationType DecodeAccess(uint64_t err) {
// re-executing the faulting instruction against the now-fixed protection. An unresolved
// fault restores the default disposition so the retry terminates the process.
static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
if (g_in_exception_filter) {
FailFast("nested exception while resolving a host fault");
}
g_in_exception_filter = true;
auto* uc = static_cast<ucontext_t*>(uctx);
const auto* mc = uc->uc_mcontext;
const auto& ss = mc->__ss;
@@ -210,14 +151,7 @@ static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
info.r15 = ss.__r15;
const auto handler = g_handler.load(std::memory_order_acquire);
if (handler == nullptr) {
FailFast("host exception callback is null");
}
const bool resolved = handler(info);
g_in_exception_filter = false;
if (resolved) {
if (handler != nullptr && handler(info)) {
return; // retry the faulting instruction against the fixed mapping
}
@@ -244,8 +178,6 @@ static void ChainToDefault(int signal_number) noexcept {
}
static void SignalHandler(int signal_number, siginfo_t* signal_info, void* native_context) {
FilterScope filter_scope;
auto* context = static_cast<ucontext_t*>(native_context);
auto* gregs = context->uc_mcontext.gregs;
@@ -289,9 +221,8 @@ static void SignalHandler(int signal_number, siginfo_t* signal_info, void* nativ
info.r14 = static_cast<uint64_t>(gregs[REG_R14]);
info.r15 = static_cast<uint64_t>(gregs[REG_R15]);
const auto handler = LoadInstalledHandler();
if (handler(info)) {
const auto handler = g_handler.load(std::memory_order_acquire);
if (handler != nullptr && handler(info)) {
return;
}
@@ -313,7 +244,7 @@ bool InstallHandler(Handler handler) {
g_handler.store(handler, std::memory_order_release);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (AddVectoredExceptionHandler(1, ExceptionFilter) == nullptr) {
if (AddVectoredExceptionHandler(0, ExceptionFilter) == nullptr) {
g_handler.store(nullptr, std::memory_order_release);
g_install_state.store(0, std::memory_order_release);
printf("AddVectoredExceptionHandler() failed\n");
+2 -8
View File
@@ -149,7 +149,7 @@ void WriteFatal(fmt::text_style style, std::string_view text) {
Flush();
}
KYTY_SUBSYSTEM_INIT(Log) {
void Initialize() {
g_initialized = true;
switch (Config::GetPrintfDirection()) {
case Config::OutputDirection::Silent: g_direction = Direction::Silent; break;
@@ -161,13 +161,7 @@ KYTY_SUBSYSTEM_INIT(Log) {
SetupLogger();
}
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Log) {
Flush();
std::lock_guard lock(g_logger_mutex);
g_logger.reset();
}
KYTY_SUBSYSTEM_DESTROY(Log) {
void Shutdown() {
Flush();
std::lock_guard lock(g_logger_mutex);
g_logger.reset();
+9 -2
View File
@@ -2,7 +2,6 @@
#define KYTY_COMMON_LOGGING_LOG_H_
#include "common/common.h"
#include "common/subsystems.h"
#include <fmt/color.h>
#include <fmt/printf.h>
@@ -10,7 +9,15 @@
namespace Log {
KYTY_SUBSYSTEM_DEFINE(Log);
void Initialize();
void Shutdown();
struct Lifecycle {
static constexpr const char* name = "Log";
static constexpr auto initialize = Log::Initialize;
static constexpr auto shutdown = Log::Shutdown;
static constexpr auto emergency_shutdown = Log::Shutdown;
};
enum class Direction { Silent, Console, File };
+1 -49
View File
@@ -15,56 +15,8 @@
#include "common/assert.h"
#include "common/platform/sysDbg.h"
struct FrameS {
struct FrameS* next;
void* ret_addr;
};
int WalkStack(int z_stack_depth, void** z_stack_trace) {
CONTEXT context;
// KNONVOLATILE_CONTEXT_POINTERS NvContext;
PRUNTIME_FUNCTION runtime_function = nullptr;
PVOID handler_data = nullptr;
ULONG64 establisher_frame = 0;
ULONG64 image_base = 0;
RtlCaptureContext(&context);
int frame = 0;
while (true) {
if (frame >= z_stack_depth) {
break;
}
z_stack_trace[frame] = reinterpret_cast<void*>(context.Rip);
frame++;
runtime_function = RtlLookupFunctionEntry(context.Rip, &image_base, nullptr);
if (runtime_function == nullptr) {
break;
}
// RtlZeroMemory(&NvContext, sizeof(KNONVOLATILE_CONTEXT_POINTERS));
RtlVirtualUnwind(0, image_base, context.Rip, runtime_function, &context, &handler_data,
&establisher_frame, nullptr /*&NvContext*/);
if (context.Rip == 0u) {
break;
}
}
return frame;
}
void SysStackWalk(void** stack, int* depth) {
int n = WalkStack(*depth, stack);
*depth = n;
// unwind_info_t info = {stack, 0, *depth};
// _Unwind_Backtrace(&trace_fcn, &info);
// *depth = info.depth;
*depth = static_cast<int>(CaptureStackBackTrace(0, static_cast<DWORD>(*depth), stack, nullptr));
}
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
+5 -14
View File
@@ -1,7 +1,6 @@
#include "common/profiler.h"
#include "common/emulatorConfig.h"
#include "common/subsystems.h"
#include <algorithm>
#include <common/TracyProtocol.hpp>
@@ -55,13 +54,7 @@ void SetThreadName(const char* name) {
}
}
void Close() {
if (tracy::ProfilerAvailable()) {
tracy::ShutdownProfiler();
}
}
KYTY_SUBSYSTEM_INIT(Profiler) {
void Initialize() {
switch (Config::GetProfilerDirection()) {
case Config::ProfilerDirection::Network:
if (!tracy::ProfilerAvailable()) {
@@ -78,12 +71,10 @@ KYTY_SUBSYSTEM_INIT(Profiler) {
}
}
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Profiler) {
Close();
}
KYTY_SUBSYSTEM_DESTROY(Profiler) {
Close();
void Shutdown() {
if (tracy::ProfilerAvailable()) {
tracy::ShutdownProfiler();
}
}
} // namespace Profiler
+9 -2
View File
@@ -2,7 +2,6 @@
#define KYTY_COMMON_PROFILER_H_
#include "common/common.h"
#include "common/subsystems.h"
#include <cstdint>
#include <optional>
@@ -42,7 +41,15 @@ private:
void EndBlock();
void SetThreadName(const char* name);
KYTY_SUBSYSTEM_DEFINE(Profiler);
void Initialize();
void Shutdown();
struct Lifecycle {
static constexpr const char* name = "Profiler";
static constexpr auto initialize = Profiler::Initialize;
static constexpr auto shutdown = Profiler::Shutdown;
static constexpr auto emergency_shutdown = Profiler::Shutdown;
};
} // namespace Profiler
+25 -214
View File
@@ -1,232 +1,43 @@
#include "common/subsystems.h"
#include "common/assert.h"
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <memory>
#include <string>
namespace Common {
class SubsystemPrivate {
public:
SubsystemPrivate() = default;
virtual ~SubsystemPrivate() = default;
static Subsystems* g_active_subsystems = nullptr;
KYTY_CLASS_NO_COPY(SubsystemPrivate);
Subsystems::Subsystems(bool print): m_print(print) {
m_active.reserve(16);
g_active_subsystems = this;
}
bool failed {false};
std::string fail_msg;
};
class SubsystemsListPrivate {
public:
explicit SubsystemsListPrivate(SubsystemsList& p): parent(p) {}
virtual ~SubsystemsListPrivate() = default;
void SetArgs(int argc, char** argv) {
this->m_argc = argc;
this->m_argv = argv;
Subsystems::~Subsystems() {
Destroy();
if (g_active_subsystems == this) {
g_active_subsystems = nullptr;
}
}
void Add(Subsystem* s, std::initializer_list<Subsystem*> deps) {
EXIT_IF(!s);
const char* name = s->Id();
EXIT_IF(FindByName(name) != nullptr);
auto nl = std::make_unique<SubsListStruct>();
nl->s = s;
nl->name = name;
nl->deps = nullptr;
nl->next = std::move(list);
nl->prev_init = nullptr;
auto* node = nl.get();
list = std::move(nl);
for (auto* dep: deps) {
const char* str = dep->Id();
auto l = std::make_unique<DepsListStruct>();
l->dep_name = str;
l->next = std::move(node->deps);
node->deps = std::move(l);
void Subsystems::Destroy() {
for (auto it = m_active.rbegin(); it != m_active.rend(); ++it) {
if (it->shutdown != nullptr) {
it->shutdown();
}
node->initialized = false;
}
m_active.clear();
}
bool InitAll(bool print_msg) {
while (SubsListStruct* n = FindNextToInitialize()) {
n->s->Init(&parent);
if (n->s->m_p->failed) {
fail_msg = n->s->m_p->fail_msg.c_str();
fail_name = n->name;
return false;
}
if (print_msg) {
printf("Initialized: %s\n", n->name);
}
n->initialized = true;
SubsListStruct* last = last_init;
last_init = n;
n->prev_init = last;
void Subsystems::EmergencyShutdown() {
for (auto it = m_active.rbegin(); it != m_active.rend(); ++it) {
if (it->emergency_shutdown != nullptr) {
it->emergency_shutdown();
}
return true;
}
m_active.clear();
}
void DestroyAll(bool print_msg) {
for (SubsListStruct* n = last_init; n != nullptr; n = n->prev_init) {
n->s->Destroy(&parent);
n->initialized = false;
if (print_msg) {
printf("Destroyed: %s\n", n->name);
}
}
last_init = nullptr;
void Subsystems::EmergencyShutdownActive() {
if (g_active_subsystems != nullptr) {
g_active_subsystems->EmergencyShutdown();
}
void ShutdownAll() {
for (SubsListStruct* n = last_init; n != nullptr; n = n->prev_init) {
n->s->UnexpectedShutdown(&parent);
n->initialized = false;
}
last_init = nullptr;
}
struct DepsListStruct {
const char* dep_name;
std::unique_ptr<DepsListStruct> next;
};
struct SubsListStruct {
Subsystem* s;
const char* name;
std::unique_ptr<DepsListStruct> deps;
std::unique_ptr<SubsListStruct> next;
SubsListStruct* prev_init;
bool initialized;
};
[[nodiscard]] SubsListStruct* FindByName(const char* name) const {
for (SubsListStruct* n = list.get(); n != nullptr; n = n->next.get()) {
if (std::strcmp(n->name, name) == 0) {
return n;
}
}
return nullptr;
}
[[nodiscard]] SubsListStruct* FindNextToInitialize() const {
for (SubsListStruct* n = list.get(); n != nullptr; n = n->next.get()) {
if (n->initialized) {
continue;
}
DepsListStruct* d = n->deps.get();
for (; d != nullptr; d = d->next.get()) {
SubsListStruct* s = FindByName(d->dep_name);
if ((s == nullptr) || !s->initialized) {
break;
}
}
if (d == nullptr) {
return n;
}
}
return nullptr;
}
KYTY_CLASS_NO_COPY(SubsystemsListPrivate);
std::unique_ptr<SubsListStruct> list;
SubsListStruct* last_init = nullptr;
int m_argc = 0;
char** m_argv = nullptr;
const char* fail_msg = nullptr;
const char* fail_name = nullptr;
SubsystemsList& parent;
};
SubsystemsList::SubsystemsList(): m_p(std::make_unique<SubsystemsListPrivate>(*this)) {}
SubsystemsList::~SubsystemsList() = default;
void SubsystemsList::Add(Subsystem* s, std::initializer_list<Subsystem*> deps) {
m_p->Add(s, deps);
}
bool SubsystemsList::InitAll(bool print_msg) {
return m_p->InitAll(print_msg);
}
void SubsystemsList::DestroyAll(bool print_msg) {
m_p->DestroyAll(print_msg);
}
int* SubsystemsList::GetArgc() {
return &m_p->m_argc;
}
char** SubsystemsList::GetArgv() {
return m_p->m_argv;
}
Subsystem::Subsystem(): m_p(std::make_unique<SubsystemPrivate>()) {}
Subsystem::~Subsystem() = default;
void Subsystem::Fail(const char* format, ...) {
va_list args {};
va_start(args, format);
va_list args_copy {};
va_copy(args_copy, args);
int len = std::vsnprintf(nullptr, 0, format, args_copy);
va_end(args_copy);
if (len > 0) {
std::string msg(static_cast<size_t>(len) + 1, '\0');
std::vsnprintf(msg.data(), msg.size(), format, args);
m_p->fail_msg = msg.c_str();
m_p->failed = true;
}
va_end(args);
}
const char* SubsystemsList::GetFailName() const {
return m_p->fail_name;
}
const char* SubsystemsList::GetFailMsg() const {
return m_p->fail_msg;
}
void SubsystemsList::SetArgs(int argc, char* argv[]) {
m_p->SetArgs(argc, argv);
}
void SubsystemsList::ShutdownAll() {
m_p->ShutdownAll();
}
} // namespace Common
+41 -70
View File
@@ -2,90 +2,61 @@
#define KYTY_COMMON_SUBSYSTEMS_H_
#include "common/common.h"
#include "common/singleton.h"
#include <initializer_list>
#include <memory>
#include <cstdio>
#include <vector>
namespace Common {
class Subsystem;
class SubsystemsListPrivate;
class SubsystemPrivate;
class SubsystemsList {
class Subsystems {
public:
SubsystemsList();
virtual ~SubsystemsList();
void SetArgs(int argc, char* argv[]);
using Callback = void (*)();
// void Add(Subsystem* s, const char* name, ...);
void Add(Subsystem* s, std::initializer_list<Subsystem*> deps);
explicit Subsystems(bool print = false);
~Subsystems();
bool InitAll(bool print_msg = false);
void DestroyAll(bool print_msg = false);
template <typename Lifecycle>
void Initialize() {
Lifecycle::initialize();
m_active.push_back({ShutdownCallback<Lifecycle>(), EmergencyCallback<Lifecycle>()});
if (m_print) {
std::printf("Initialized: %s\n", Lifecycle::name);
}
}
int* GetArgc();
char** GetArgv();
void Destroy();
void EmergencyShutdown();
[[nodiscard]] const char* GetFailName() const;
[[nodiscard]] const char* GetFailMsg() const;
static void EmergencyShutdownActive();
void ShutdownAll();
static SubsystemsList* Instance() { return Common::Singleton<SubsystemsList>::Instance(); }
KYTY_CLASS_NO_COPY(SubsystemsList);
KYTY_CLASS_NO_COPY(Subsystems);
private:
std::unique_ptr<SubsystemsListPrivate> m_p;
template <typename Lifecycle>
static consteval Callback ShutdownCallback() {
if constexpr (requires { Lifecycle::shutdown; }) {
return Lifecycle::shutdown;
}
return nullptr;
}
template <typename Lifecycle>
static consteval Callback EmergencyCallback() {
if constexpr (requires { Lifecycle::emergency_shutdown; }) {
return Lifecycle::emergency_shutdown;
}
return nullptr;
}
struct Entry {
Callback shutdown;
Callback emergency_shutdown;
};
std::vector<Entry> m_active;
bool m_print;
};
using SubsystemsListSingleton = Common::Singleton<SubsystemsList>;
class Subsystem {
public:
Subsystem();
virtual ~Subsystem();
virtual const char* Id() = 0;
virtual void Init(SubsystemsList* parent) = 0;
virtual void Destroy(SubsystemsList* parent) = 0;
virtual void UnexpectedShutdown(SubsystemsList* parent) = 0;
friend class SubsystemsListPrivate;
KYTY_CLASS_NO_COPY(Subsystem);
protected:
void Fail(const char* format, ...) KYTY_FORMAT_PRINTF(2, 3);
private:
std::unique_ptr<SubsystemPrivate> m_p;
};
#define KYTY_SUBSYSTEM_DEFINE(s) \
class s##Subsystem: public Common::Subsystem { \
public: \
static Subsystem* Instance() { return Common::Singleton<s##Subsystem>::Instance(); } \
const char* Id() { return #s; } \
void Init(Common::SubsystemsList* parent); \
void Destroy(Common::SubsystemsList* parent); \
void UnexpectedShutdown(Common::SubsystemsList* parent); \
}; \
typedef Common::Singleton<s##Subsystem> s##SubsystemSingleton;
#define KYTY_SUBSYSTEM_INIT(s) \
void s##Subsystem::Init([[maybe_unused]] Common::SubsystemsList* parent)
#define KYTY_SUBSYSTEM_DESTROY(s) \
void s##Subsystem::Destroy([[maybe_unused]] Common::SubsystemsList* parent)
#define KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(s) \
void s##Subsystem::UnexpectedShutdown([[maybe_unused]] Common::SubsystemsList* parent)
// #define KYTY_SUBSYSTEM_ADD(list, s, ...) list.push_back(s##SubsystemSingleton::Instance(), #s,
// __VA_ARGS__); #define KYTY_SUBSYSTEM_ADD2(list, ...) list.Add2(s##SubsystemSingleton::Instance(),
// __VA_ARGS__);
} // namespace Common
#endif /* KYTY_COMMON_SUBSYSTEMS_H_ */
+1 -5
View File
@@ -252,15 +252,11 @@ static thread_id_t g_main_thread;
static int g_main_thread_int;
static std::atomic<int> g_thread_counter = 0;
KYTY_SUBSYSTEM_INIT(Threads) {
void InitializeThreads() {
g_main_thread = std::this_thread::get_id();
g_main_thread_int = Thread::GetThreadIdUnique();
}
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Threads) {}
KYTY_SUBSYSTEM_DESTROY(Threads) {}
Thread::Thread(thread_func_t func, void* arg)
: m_thread(std::make_unique<ThreadPrivate>(func, arg)) {
while (!m_thread->started) {
+1 -2
View File
@@ -2,14 +2,13 @@
#define KYTY_COMMON_THREADS_H_
#include "common/common.h"
#include "common/subsystems.h"
#include <memory>
#include <string>
namespace Common {
KYTY_SUBSYSTEM_DEFINE(Threads);
void InitializeThreads();
using thread_func_t = void (*)(void*);
using wait_poll_func_t = void (*)();
+18 -36
View File
@@ -2,7 +2,6 @@
#include "common/abi.h"
#include "common/assert.h"
#include "common/commonSubsystem.h"
#include "common/emulatorConfig.h"
#include "common/file.h"
#include "common/logging/log.h"
@@ -43,7 +42,7 @@ static void KytyClose() {
LOGF("done!\n");
Common::SubsystemsListSingleton::Instance()->ShutdownAll();
Common::Subsystems::EmergencyShutdownActive();
}
static void MountOrCreateDir(const std::filesystem::path& dir, const std::string& point) {
@@ -105,30 +104,13 @@ static void ClearDebugTextureFolder() {
}
}
static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path& param_json) {
static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path& param_json,
Common::Subsystems& subsystems) {
EXIT_IF(!Common::Thread::IsMainThread());
auto* slist = Common::SubsystemsList::Instance();
auto* audio = Libs::Audio::AudioSubsystem::Instance();
auto* config = Config::ConfigSubsystem::Instance();
auto* controller = Libs::Controller::ControllerSubsystem::Instance();
auto* core = Common::CommonSubsystem::Instance();
auto* file_system = Libs::LibKernel::FileSystem::FileSystemSubsystem::Instance();
auto* graphics = Libs::Graphics::GraphicsSubsystem::Instance();
auto* log = Log::LogSubsystem::Instance();
auto* memory = Libs::LibKernel::Memory::MemorySubsystem::Instance();
auto* network = Libs::Network::NetworkSubsystem::Instance();
auto* profiler = Profiler::ProfilerSubsystem::Instance();
auto* pthread = Libs::LibKernel::PthreadSubsystem::Instance();
auto* timer = Loader::Timer::TimerSubsystem::Instance();
slist->Add(config, {core});
slist->InitAll(true);
subsystems.Initialize<Config::Lifecycle>();
Config::Load(cfg);
slist->Add(log, {core, config});
slist->InitAll(true);
subsystems.Initialize<Log::Lifecycle>();
if (Common::File::IsFileExisting(param_json)) {
Loader::SystemContentLoadParamSfo(param_json);
@@ -138,17 +120,16 @@ static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path&
}
}
slist->Add(audio, {core, log, pthread, memory});
slist->Add(controller, {core, log, config});
slist->Add(file_system, {core, log, pthread});
slist->Add(graphics, {core, log, pthread, memory, config, profiler, controller});
slist->Add(memory, {core, log});
slist->Add(network, {core, log, pthread});
slist->Add(profiler, {core, config});
slist->Add(pthread, {core, log, timer});
slist->Add(timer, {core, log});
slist->InitAll(true);
// Initialization order is explicit; destruction is automatic and reversed.
subsystems.Initialize<Loader::Timer::Lifecycle>();
subsystems.Initialize<Libs::LibKernel::PthreadLifecycle>();
subsystems.Initialize<Profiler::Lifecycle>();
subsystems.Initialize<Libs::Network::Lifecycle>();
subsystems.Initialize<Libs::LibKernel::Memory::Lifecycle>();
subsystems.Initialize<Libs::LibKernel::FileSystem::Lifecycle>();
subsystems.Initialize<Libs::Controller::Lifecycle>();
subsystems.Initialize<Libs::Audio::Lifecycle>();
subsystems.Initialize<Libs::Graphics::Lifecycle>();
}
static void LoadElf(const std::filesystem::path& elf, bool dbg_print_reloc = false,
@@ -189,8 +170,9 @@ void Run(const RunOptions& options) {
EXIT("ELF is required\n");
}
const auto param_json = options.app0_dir / "sce_sys" / "param.json";
Init(options.config, param_json);
const auto param_json = options.app0_dir / "sce_sys" / "param.json";
Common::Subsystems subsystems(true);
Init(options.config, param_json, subsystems);
ClearDebugTextureFolder();
@@ -112,6 +112,7 @@ public:
void SetCeComplete(bool complete) { m_ce_complete = complete; }
void WaitCe();
void WaitDeDiff(uint32_t diff);
void WaitForRewind(bool valid);
void IncrementDe();
void IncrementCe();
@@ -224,6 +224,7 @@ constexpr auto MakeOpcodeDispatchTable() {
g_cp_op_func[Pm4::IT_EVENT_WRITE_EOS] = CpOpEventWriteEos;
g_cp_op_func[Pm4::IT_DMA_DATA] = CpOpDmaData;
g_cp_op_func[Pm4::IT_ACQUIRE_MEM] = CpOpAcquireMem;
g_cp_op_func[Pm4::IT_REWIND] = CpOpRewind;
g_cp_op_func[Pm4::IT_SET_CONTEXT_REG] = CpOpSetContextReg;
g_cp_op_func[Pm4::IT_SET_SH_REG] = CpOpSetShaderReg;
g_cp_op_func[Pm4::IT_SET_UCONFIG_REG] = CpOpSetUconfigReg;
@@ -133,6 +133,7 @@ uint32_t CpOpDrawIndirectMulti(CommandProcessor&, uint32_t, const uint32_t*, uin
uint32_t CpOpDrawIndex(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpDrawIndexOffset(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpPfpSyncMe(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpRewind(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpIndexType(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpNumInstances(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
uint32_t CpOpDrawIndexAuto(CommandProcessor&, uint32_t, const uint32_t*, uint32_t, uint32_t);
@@ -103,6 +103,128 @@ void LogUnknownReleaseMemGcr(uint32_t gcr_cntl) {
} // namespace
static HW::BlendControl DecodeBlendControl(uint32_t value) {
HW::BlendControl r {};
r.color_srcblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, COLOR_SRCBLEND);
r.color_comb_fcn = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, COLOR_COMB_FCN);
r.color_destblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, COLOR_DESTBLEND);
r.alpha_srcblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ALPHA_SRCBLEND);
r.alpha_comb_fcn = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ALPHA_COMB_FCN);
r.alpha_destblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ALPHA_DESTBLEND);
r.separate_alpha_blend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, SEPARATE_ALPHA_BLEND) != 0;
r.enable = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ENABLE) != 0;
return r;
}
static HW::ClipControl DecodeClipControl(uint32_t value) {
HW::ClipControl r {};
r.user_clip_planes = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, UCP_ENA);
r.user_clip_plane_mode = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, PS_UCP_MODE);
r.dx_clip_space = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, DX_CLIP_SPACE_DEF) != 0;
r.vertex_kill_any = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, VTX_KILL_OR) != 0;
r.min_z_clip_disable = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, ZCLIP_NEAR_DISABLE) != 0;
r.max_z_clip_disable = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, ZCLIP_FAR_DISABLE) != 0;
r.user_clip_plane_negate_y = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, PS_UCP_Y_SCALE_NEG) != 0;
r.clip_disable = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, CLIP_DISABLE) != 0;
r.user_clip_plane_cull_only = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, UCP_CULL_ONLY_ENA) != 0;
r.cull_on_clipping_error_disable =
KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, DIS_CLIP_ERR_DETECT) != 0;
r.linear_attribute_clip_enable =
KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, DX_LINEAR_ATTR_CLIP_ENA) != 0;
r.force_viewport_index_from_vs_enable =
KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, VTE_VPORT_PROVOKE_DISABLE) != 0;
return r;
}
static HW::ColorControl DecodeColorControl(uint32_t value) {
HW::ColorControl r {};
r.mode = KYTY_PM4_GET(value, CB_COLOR_CONTROL, MODE);
r.op = KYTY_PM4_GET(value, CB_COLOR_CONTROL, ROP3);
return r;
}
static HW::DepthControl DecodeDepthControl(uint32_t value) {
HW::DepthControl r {};
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;
r.z_write_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, Z_WRITE_ENABLE) != 0;
r.depth_bounds_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, DEPTH_BOUNDS_ENABLE) != 0;
r.zfunc = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, ZFUNC);
r.backface_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, BACKFACE_ENABLE) != 0;
r.stencilfunc = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, STENCILFUNC);
r.stencilfunc_bf = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, STENCILFUNC_BF);
r.color_writes_on_depth_fail_enable =
KYTY_PM4_GET(value, DB_DEPTH_CONTROL, ENABLE_COLOR_WRITES_ON_DEPTH_FAIL) != 0;
r.color_writes_on_depth_pass_disable =
KYTY_PM4_GET(value, DB_DEPTH_CONTROL, DISABLE_COLOR_WRITES_ON_DEPTH_PASS) != 0;
return r;
}
static HW::ModeControl DecodeModeControl(uint32_t value) {
HW::ModeControl r {};
r.cull_front = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, CULL_FRONT) != 0;
r.cull_back = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, CULL_BACK) != 0;
r.face = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, FACE) != 0;
r.poly_mode = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLY_MODE);
r.polymode_front_ptype = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLYMODE_FRONT_PTYPE);
r.polymode_back_ptype = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLYMODE_BACK_PTYPE);
r.poly_offset_front_enable =
KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLY_OFFSET_FRONT_ENABLE) != 0;
r.poly_offset_back_enable =
KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLY_OFFSET_BACK_ENABLE) != 0;
r.vtx_window_offset_enable =
KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, VTX_WINDOW_OFFSET_ENABLE) != 0;
r.provoking_vtx_last = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, PROVOKING_VTX_LAST) != 0;
r.persp_corr_dis = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, PERSP_CORR_DIS) != 0;
return r;
}
static HW::RenderControl DecodeRenderControl(uint32_t value) {
HW::RenderControl r {};
r.depth_clear_enable = KYTY_PM4_GET(value, DB_RENDER_CONTROL, DEPTH_CLEAR_ENABLE) != 0;
r.stencil_clear_enable = KYTY_PM4_GET(value, DB_RENDER_CONTROL, STENCIL_CLEAR_ENABLE) != 0;
r.resummarize_enable = KYTY_PM4_GET(value, DB_RENDER_CONTROL, RESUMMARIZE_ENABLE) != 0;
r.stencil_compress_disable =
KYTY_PM4_GET(value, DB_RENDER_CONTROL, STENCIL_COMPRESS_DISABLE) != 0;
r.depth_compress_disable = KYTY_PM4_GET(value, DB_RENDER_CONTROL, DEPTH_COMPRESS_DISABLE) != 0;
r.copy_centroid = KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_CENTROID) != 0;
r.copy_sample = KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_SAMPLE);
return r;
}
static HW::ScanModeControl DecodeScanModeControl(uint32_t value) {
HW::ScanModeControl r {};
r.msaa_enable = KYTY_PM4_GET(value, PA_SC_MODE_CNTL_0, MSAA_ENABLE) != 0;
r.vport_scissor_enable = KYTY_PM4_GET(value, PA_SC_MODE_CNTL_0, VPORT_SCISSOR_ENABLE) != 0;
r.line_stipple_enable = KYTY_PM4_GET(value, PA_SC_MODE_CNTL_0, LINE_STIPPLE_ENABLE) != 0;
return r;
}
static HW::StencilControl DecodeStencilControl(uint32_t value) {
HW::StencilControl r {};
r.stencil_fail = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILFAIL);
r.stencil_zpass = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZPASS);
r.stencil_zfail = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZFAIL);
r.stencil_fail_bf = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILFAIL_BF);
r.stencil_zpass_bf = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZPASS_BF);
r.stencil_zfail_bf = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZFAIL_BF);
return r;
}
static HW::AaConfig ParseAaConfig(uint32_t value) {
HW::AaConfig r;
@@ -429,26 +551,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetBlendControl) {
uint32_t param = (cmd_offset - Pm4::CB_BLEND0_CONTROL) / 1;
HW::BlendControl r;
r.color_srcblend = (buffer[0] >> Pm4::CB_BLEND0_CONTROL_COLOR_SRCBLEND_SHIFT) &
Pm4::CB_BLEND0_CONTROL_COLOR_SRCBLEND_MASK;
r.color_comb_fcn = (buffer[0] >> Pm4::CB_BLEND0_CONTROL_COLOR_COMB_FCN_SHIFT) &
Pm4::CB_BLEND0_CONTROL_COLOR_COMB_FCN_MASK;
r.color_destblend = (buffer[0] >> Pm4::CB_BLEND0_CONTROL_COLOR_DESTBLEND_SHIFT) &
Pm4::CB_BLEND0_CONTROL_COLOR_DESTBLEND_MASK;
r.alpha_srcblend = (buffer[0] >> Pm4::CB_BLEND0_CONTROL_ALPHA_SRCBLEND_SHIFT) &
Pm4::CB_BLEND0_CONTROL_ALPHA_SRCBLEND_MASK;
r.alpha_comb_fcn = (buffer[0] >> Pm4::CB_BLEND0_CONTROL_ALPHA_COMB_FCN_SHIFT) &
Pm4::CB_BLEND0_CONTROL_ALPHA_COMB_FCN_MASK;
r.alpha_destblend = (buffer[0] >> Pm4::CB_BLEND0_CONTROL_ALPHA_DESTBLEND_SHIFT) &
Pm4::CB_BLEND0_CONTROL_ALPHA_DESTBLEND_MASK;
r.separate_alpha_blend = ((buffer[0] >> Pm4::CB_BLEND0_CONTROL_SEPARATE_ALPHA_BLEND_SHIFT) &
Pm4::CB_BLEND0_CONTROL_SEPARATE_ALPHA_BLEND_MASK) != 0;
r.enable = ((buffer[0] >> Pm4::CB_BLEND0_CONTROL_ENABLE_SHIFT) &
Pm4::CB_BLEND0_CONTROL_ENABLE_MASK) != 0;
cp.GetCtx().SetBlendControl(param, r);
cp.GetCtx().SetBlendControl(param, DecodeBlendControl(buffer[0]));
return 1;
}
@@ -457,38 +560,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetClipControl) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0016900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::PA_CL_CLIP_CNTL);
HW::ClipControl r;
// r.user_clip_planes = buffer[0] & 0x3fu;
// r.user_clip_plane_mode = (buffer[0] >> 14u) & 0x3u;
// r.clip_space = (buffer[0] >> 19u) & 0x1u;
// r.vertex_kill_mode = (buffer[0] >> 21u) & 0x1u;
// r.min_z_clip_enable = (buffer[0] >> 26u) & 0x1u;
// r.max_z_clip_enable = (buffer[0] >> 27u) & 0x1u;
// r.user_clip_plane_negate_y = (buffer[0] & 0x00002000u) != 0;
// r.clip_enable = (buffer[0] & 0x00010000u) != 0;
// r.user_clip_plane_cull_only = (buffer[0] & 0x00020000u) != 0;
// r.cull_on_clipping_error_disable = (buffer[0] & 0x00100000u) != 0;
// r.linear_attribute_clip_enable = (buffer[0] & 0x01000000u) != 0;
// r.force_viewport_index_from_vs_enable = (buffer[0] & 0x02000000u) != 0;
r.user_clip_planes = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, UCP_ENA);
r.user_clip_plane_mode = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, PS_UCP_MODE);
r.dx_clip_space = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, DX_CLIP_SPACE_DEF) != 0;
r.vertex_kill_any = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, VTX_KILL_OR) != 0;
r.min_z_clip_disable = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, ZCLIP_NEAR_DISABLE) != 0;
r.max_z_clip_disable = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, ZCLIP_FAR_DISABLE) != 0;
r.user_clip_plane_negate_y = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, PS_UCP_Y_SCALE_NEG) != 0;
r.clip_disable = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, CLIP_DISABLE) != 0;
r.user_clip_plane_cull_only = KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, UCP_CULL_ONLY_ENA) != 0;
r.cull_on_clipping_error_disable =
KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, DIS_CLIP_ERR_DETECT) != 0;
r.linear_attribute_clip_enable =
KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, DX_LINEAR_ATTR_CLIP_ENA) != 0;
r.force_viewport_index_from_vs_enable =
KYTY_PM4_GET(buffer[0], PA_CL_CLIP_CNTL, VTE_VPORT_PROVOKE_DISABLE) != 0;
cp.GetCtx().SetClipControl(r);
cp.GetCtx().SetClipControl(DecodeClipControl(buffer[0]));
return 1;
}
@@ -497,12 +569,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetColorControl) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0016900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::CB_COLOR_CONTROL);
HW::ColorControl r;
r.mode = KYTY_PM4_GET(buffer[0], CB_COLOR_CONTROL, MODE);
r.op = KYTY_PM4_GET(buffer[0], CB_COLOR_CONTROL, ROP3);
cp.GetCtx().SetColorControl(r);
cp.GetCtx().SetColorControl(DecodeColorControl(buffer[0]));
return 1;
}
@@ -526,9 +593,12 @@ KYTY_HW_CTX_PARSER(HwCtxSetColorInfo) {
// r.format = (buffer[4] >> 2u) & 0x1fu;
// r.channel_type = (buffer[4] >> 8u) & 0x7u;
// r.channel_order = (buffer[4] >> 11u) & 0x3u;
r.format = KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, FORMAT);
r.channel_type = KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, NUMBER_TYPE);
r.channel_order = KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, COMP_SWAP);
r.format =
static_cast<Prospero::ChannelLayout>(KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, FORMAT));
r.channel_type =
static_cast<Prospero::ChannelType>(KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, NUMBER_TYPE));
r.channel_order =
static_cast<Prospero::ChannelOrder>(KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, COMP_SWAP));
r.cmask_fast_clear_enable = KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, FAST_CLEAR) != 0;
r.fmask_compression_enable = KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, COMPRESSION) != 0;
r.blend_clamp = KYTY_PM4_GET(buffer[0], CB_COLOR0_INFO, BLEND_CLAMP) != 0;
@@ -560,22 +630,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetDepthControl) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0016900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::DB_DEPTH_CONTROL);
HW::DepthControl r;
r.stencil_enable = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, STENCIL_ENABLE) != 0;
r.z_enable = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, Z_ENABLE) != 0;
r.z_write_enable = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, Z_WRITE_ENABLE) != 0;
r.depth_bounds_enable = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, DEPTH_BOUNDS_ENABLE) != 0;
r.zfunc = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, ZFUNC);
r.backface_enable = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, BACKFACE_ENABLE) != 0;
r.stencilfunc = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, STENCILFUNC);
r.stencilfunc_bf = KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, STENCILFUNC_BF);
r.color_writes_on_depth_fail_enable =
KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, ENABLE_COLOR_WRITES_ON_DEPTH_FAIL) != 0;
r.color_writes_on_depth_pass_disable =
KYTY_PM4_GET(buffer[0], DB_DEPTH_CONTROL, DISABLE_COLOR_WRITES_ON_DEPTH_PASS) != 0;
cp.GetCtx().SetDepthControl(r);
cp.GetCtx().SetDepthControl(DecodeDepthControl(buffer[0]));
return 1;
}
@@ -879,24 +934,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetModeControl) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0016900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::PA_SU_SC_MODE_CNTL);
HW::ModeControl r;
r.cull_front = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, CULL_FRONT) != 0;
r.cull_back = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, CULL_BACK) != 0;
r.face = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, FACE) != 0;
r.poly_mode = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, POLY_MODE);
r.polymode_front_ptype = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, POLYMODE_FRONT_PTYPE);
r.polymode_back_ptype = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, POLYMODE_BACK_PTYPE);
r.poly_offset_front_enable =
KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, POLY_OFFSET_FRONT_ENABLE) != 0;
r.poly_offset_back_enable =
KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, POLY_OFFSET_BACK_ENABLE) != 0;
r.vtx_window_offset_enable =
KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, VTX_WINDOW_OFFSET_ENABLE) != 0;
r.provoking_vtx_last = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, PROVOKING_VTX_LAST) != 0;
r.persp_corr_dis = KYTY_PM4_GET(buffer[0], PA_SU_SC_MODE_CNTL, PERSP_CORR_DIS) != 0;
cp.GetCtx().SetModeControl(r);
cp.GetCtx().SetModeControl(DecodeModeControl(buffer[0]));
return 1;
}
@@ -962,19 +1000,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetRenderControl) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0016900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::DB_RENDER_CONTROL);
HW::RenderControl r;
r.depth_clear_enable = KYTY_PM4_GET(buffer[0], DB_RENDER_CONTROL, DEPTH_CLEAR_ENABLE) != 0;
r.stencil_clear_enable = KYTY_PM4_GET(buffer[0], DB_RENDER_CONTROL, STENCIL_CLEAR_ENABLE) != 0;
r.resummarize_enable = KYTY_PM4_GET(buffer[0], DB_RENDER_CONTROL, RESUMMARIZE_ENABLE) != 0;
r.stencil_compress_disable =
KYTY_PM4_GET(buffer[0], DB_RENDER_CONTROL, STENCIL_COMPRESS_DISABLE) != 0;
r.depth_compress_disable =
KYTY_PM4_GET(buffer[0], DB_RENDER_CONTROL, DEPTH_COMPRESS_DISABLE) != 0;
r.copy_centroid = KYTY_PM4_GET(buffer[0], DB_RENDER_CONTROL, COPY_CENTROID) != 0;
r.copy_sample = KYTY_PM4_GET(buffer[0], DB_RENDER_CONTROL, COPY_SAMPLE);
cp.GetCtx().SetRenderControl(r);
cp.GetCtx().SetRenderControl(DecodeRenderControl(buffer[0]));
return 1;
}
@@ -1042,9 +1068,12 @@ KYTY_HW_CTX_PARSER(HwCtxSetRenderTarget) {
// info.format = (buffer[4] >> 2u) & 0x1fu;
// info.channel_type = (buffer[4] >> 8u) & 0x7u;
// info.channel_order = (buffer[4] >> 11u) & 0x3u;
info.format = KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, FORMAT);
info.channel_type = KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, NUMBER_TYPE);
info.channel_order = KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, COMP_SWAP);
info.format =
static_cast<Prospero::ChannelLayout>(KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, FORMAT));
info.channel_type =
static_cast<Prospero::ChannelType>(KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, NUMBER_TYPE));
info.channel_order =
static_cast<Prospero::ChannelOrder>(KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, COMP_SWAP));
info.cmask_fast_clear_enable = KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, FAST_CLEAR) != 0;
info.fmask_compression_enable = KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, COMPRESSION) != 0;
info.blend_clamp = KYTY_PM4_GET(buffer[4], CB_COLOR0_INFO, BLEND_CLAMP) != 0;
@@ -1066,10 +1095,12 @@ KYTY_HW_CTX_PARSER(HwCtxSetRenderTarget) {
// attrib.num_fragments = (buffer[5] >> 15u) & 0x3u;
attrib.force_dest_alpha_to_one =
KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, FORCE_DST_ALPHA_1) != 0;
attrib.tile_mode = KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, TILE_MODE_INDEX);
attrib.fmask_tile_mode = KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, FMASK_TILE_MODE_INDEX);
attrib.num_samples = KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, NUM_SAMPLES);
attrib.num_fragments = KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, NUM_FRAGMENTS);
attrib.tile_mode =
static_cast<Prospero::TileMode>(KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, TILE_MODE_INDEX));
attrib.fmask_tile_mode = static_cast<Prospero::TileMode>(
KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, FMASK_TILE_MODE_INDEX));
attrib.num_samples = KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, NUM_SAMPLES);
attrib.num_fragments = KYTY_PM4_GET(buffer[5], CB_COLOR0_ATTRIB, NUM_FRAGMENTS);
// dcc.max_uncompressed_block_size = (buffer[6] >> 2u) & 0x3u;
// dcc.max_compressed_block_size = (buffer[6] >> 5u) & 0x3u;
@@ -1162,13 +1193,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetScanModeControl) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0016900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::PA_SC_MODE_CNTL_0);
HW::ScanModeControl r;
r.msaa_enable = KYTY_PM4_GET(buffer[0], PA_SC_MODE_CNTL_0, MSAA_ENABLE) != 0;
r.vport_scissor_enable = KYTY_PM4_GET(buffer[0], PA_SC_MODE_CNTL_0, VPORT_SCISSOR_ENABLE) != 0;
r.line_stipple_enable = KYTY_PM4_GET(buffer[0], PA_SC_MODE_CNTL_0, LINE_STIPPLE_ENABLE) != 0;
cp.GetCtx().SetScanModeControl(r);
cp.GetCtx().SetScanModeControl(DecodeScanModeControl(buffer[0]));
return 1;
}
@@ -1208,16 +1233,7 @@ KYTY_HW_CTX_PARSER(HwCtxSetStencilControl) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xC0016900);
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::DB_STENCIL_CONTROL);
HW::StencilControl r;
r.stencil_fail = KYTY_PM4_GET(buffer[0], DB_STENCIL_CONTROL, STENCILFAIL);
r.stencil_zpass = KYTY_PM4_GET(buffer[0], DB_STENCIL_CONTROL, STENCILZPASS);
r.stencil_zfail = KYTY_PM4_GET(buffer[0], DB_STENCIL_CONTROL, STENCILZFAIL);
r.stencil_fail_bf = KYTY_PM4_GET(buffer[0], DB_STENCIL_CONTROL, STENCILFAIL_BF);
r.stencil_zpass_bf = KYTY_PM4_GET(buffer[0], DB_STENCIL_CONTROL, STENCILZPASS_BF);
r.stencil_zfail_bf = KYTY_PM4_GET(buffer[0], DB_STENCIL_CONTROL, STENCILZFAIL_BF);
cp.GetCtx().SetStencilControl(r);
cp.GetCtx().SetStencilControl(DecodeStencilControl(buffer[0]));
return 1;
}
@@ -1568,7 +1584,7 @@ KYTY_HW_UC_PARSER(HwUcSetPrimitiveType) {
uint32_t prim_type = KYTY_PM4_GET(buffer[0], VGT_PRIMITIVE_TYPE, PRIM_TYPE);
cp.GetUcfg().SetPrimitiveType(prim_type);
cp.GetUcfg().SetPrimitiveType(static_cast<Prospero::PrimitiveType>(prim_type));
return 1;
}
@@ -1611,16 +1627,10 @@ KYTY_HW_UC_PARSER(HwUcSetIaMultiVgtParam) {
return num_values;
}
static void HwIgnoreMultiPrimIbReset([[maybe_unused]] uint32_t value) {}
KYTY_HW_UC_PARSER(HwUcSetMultiPrimIbReset) {
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
for (uint32_t i = 0; i < num_values; i++) {
HwIgnoreMultiPrimIbReset(buffer[i]);
}
return num_values;
EXIT_NOT_IMPLEMENTED(cmd_offset != Pm4::GE_MULTI_PRIM_IB_RESET_EN);
cp.GetUcfg().SetPrimitiveResetControl(buffer[0]);
return 1;
}
static void HwUcIgnoreBorderColorTableAddr([[maybe_unused]] uint32_t cmd_offset,
@@ -1820,6 +1830,17 @@ KYTY_CP_OP_PARSER(CpOpPfpSyncMe) {
return 1;
}
KYTY_CP_OP_PARSER(CpOpRewind) {
KYTY_PROFILER_FUNCTION();
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0005900);
EXIT_NOT_IMPLEMENTED((buffer[0] & ~0x81000000u) != 0);
cp.WaitForRewind((buffer[0] & 0x80000000u) != 0);
return 1;
}
KYTY_CP_OP_PARSER(CpOpSetPredication) {
KYTY_PROFILER_FUNCTION();
@@ -3106,9 +3127,12 @@ void GraphicsInitJmpTablesCxIndirect() {
g_hw_ctx_indirect_func[cmd_offset] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
uint32_t slot = (cmd_offset - Pm4::CB_COLOR0_INFO) / 15;
HW::ColorInfo info;
info.format = KYTY_PM4_GET(value, CB_COLOR0_INFO, FORMAT);
info.channel_type = KYTY_PM4_GET(value, CB_COLOR0_INFO, NUMBER_TYPE);
info.channel_order = KYTY_PM4_GET(value, CB_COLOR0_INFO, COMP_SWAP);
info.format =
static_cast<Prospero::ChannelLayout>(KYTY_PM4_GET(value, CB_COLOR0_INFO, FORMAT));
info.channel_type = static_cast<Prospero::ChannelType>(
KYTY_PM4_GET(value, CB_COLOR0_INFO, NUMBER_TYPE));
info.channel_order =
static_cast<Prospero::ChannelOrder>(KYTY_PM4_GET(value, CB_COLOR0_INFO, COMP_SWAP));
info.cmask_fast_clear_enable = KYTY_PM4_GET(value, CB_COLOR0_INFO, FAST_CLEAR) != 0;
info.fmask_compression_enable = KYTY_PM4_GET(value, CB_COLOR0_INFO, COMPRESSION) != 0;
info.blend_clamp = KYTY_PM4_GET(value, CB_COLOR0_INFO, BLEND_CLAMP) != 0;
@@ -3133,10 +3157,12 @@ void GraphicsInitJmpTablesCxIndirect() {
HW::ColorAttrib attrib;
attrib.force_dest_alpha_to_one =
KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, FORCE_DST_ALPHA_1) != 0;
attrib.tile_mode = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, TILE_MODE_INDEX);
attrib.fmask_tile_mode = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, FMASK_TILE_MODE_INDEX);
attrib.num_samples = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, NUM_SAMPLES);
attrib.num_fragments = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, NUM_FRAGMENTS);
attrib.tile_mode = static_cast<Prospero::TileMode>(
KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, TILE_MODE_INDEX));
attrib.fmask_tile_mode = static_cast<Prospero::TileMode>(
KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, FMASK_TILE_MODE_INDEX));
attrib.num_samples = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, NUM_SAMPLES);
attrib.num_fragments = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB, NUM_FRAGMENTS);
cp.GetCtx().SetColorAttrib(slot, attrib);
};
}
@@ -3290,8 +3316,9 @@ void GraphicsInitJmpTablesCxIndirect() {
g_hw_ctx_indirect_func[cmd_offset] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
uint32_t slot = (cmd_offset - Pm4::CB_COLOR0_ATTRIB3);
HW::ColorAttrib3 attrib3;
attrib3.depth = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB3, MIP0_DEPTH);
attrib3.tile_mode = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB3, COLOR_SW_MODE);
attrib3.depth = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB3, MIP0_DEPTH);
attrib3.tile_mode = static_cast<Prospero::TileMode>(
KYTY_PM4_GET(value, CB_COLOR0_ATTRIB3, COLOR_SW_MODE));
attrib3.dimension = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB3, RESOURCE_TYPE);
attrib3.cmask_pipe_aligned = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB3, CMASK_PIPE_ALIGNED);
attrib3.dcc_pipe_aligned = KYTY_PM4_GET(value, CB_COLOR0_ATTRIB3, DCC_PIPE_ALIGNED);
@@ -3524,7 +3551,7 @@ void GraphicsInitJmpTablesCxIndirect() {
cp.GetCtx().SetReuseOff(value);
};
g_hw_ctx_indirect_func[Pm4::VGT_MULTI_PRIM_IB_RESET_INDX] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwIgnoreMultiPrimIbReset(value);
cp.GetCtx().SetPrimitiveResetIndex(value);
};
g_hw_ctx_indirect_func[Pm4::VGT_TESS_DISTRIBUTION] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
cp.GetCtx().SetTessDistribution(value);
@@ -3691,39 +3718,12 @@ void GraphicsInitJmpTablesCxIndirect() {
for (uint32_t slot = 0; slot < 8; slot++) {
g_hw_ctx_indirect_func[Pm4::CB_BLEND0_CONTROL + slot] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
uint32_t param = cmd_offset - Pm4::CB_BLEND0_CONTROL;
HW::BlendControl r;
r.color_srcblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, COLOR_SRCBLEND);
r.color_comb_fcn = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, COLOR_COMB_FCN);
r.color_destblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, COLOR_DESTBLEND);
r.alpha_srcblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ALPHA_SRCBLEND);
r.alpha_comb_fcn = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ALPHA_COMB_FCN);
r.alpha_destblend = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ALPHA_DESTBLEND);
r.separate_alpha_blend =
KYTY_PM4_GET(value, CB_BLEND0_CONTROL, SEPARATE_ALPHA_BLEND) != 0;
r.enable = KYTY_PM4_GET(value, CB_BLEND0_CONTROL, ENABLE) != 0;
cp.GetCtx().SetBlendControl(param, r);
cp.GetCtx().SetBlendControl(param, DecodeBlendControl(value));
};
}
g_hw_ctx_indirect_func[Pm4::PA_SU_SC_MODE_CNTL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HW::ModeControl r;
r.cull_front = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, CULL_FRONT) != 0;
r.cull_back = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, CULL_BACK) != 0;
r.face = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, FACE) != 0;
r.poly_mode = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLY_MODE);
r.polymode_front_ptype = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLYMODE_FRONT_PTYPE);
r.polymode_back_ptype = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLYMODE_BACK_PTYPE);
r.poly_offset_front_enable =
KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLY_OFFSET_FRONT_ENABLE) != 0;
r.poly_offset_back_enable =
KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, POLY_OFFSET_BACK_ENABLE) != 0;
r.vtx_window_offset_enable =
KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, VTX_WINDOW_OFFSET_ENABLE) != 0;
r.provoking_vtx_last = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, PROVOKING_VTX_LAST) != 0;
r.persp_corr_dis = KYTY_PM4_GET(value, PA_SU_SC_MODE_CNTL, PERSP_CORR_DIS) != 0;
cp.GetCtx().SetModeControl(r);
cp.GetCtx().SetModeControl(DecodeModeControl(value));
};
g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_DB_FMT_CNTL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
@@ -3891,28 +3891,11 @@ void GraphicsInitJmpTablesCxIndirect() {
};
g_hw_ctx_indirect_func[Pm4::DB_STENCIL_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HW::StencilControl r;
r.stencil_fail = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILFAIL);
r.stencil_zpass = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZPASS);
r.stencil_zfail = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZFAIL);
r.stencil_fail_bf = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILFAIL_BF);
r.stencil_zpass_bf = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZPASS_BF);
r.stencil_zfail_bf = KYTY_PM4_GET(value, DB_STENCIL_CONTROL, STENCILZFAIL_BF);
cp.GetCtx().SetStencilControl(r);
cp.GetCtx().SetStencilControl(DecodeStencilControl(value));
};
g_hw_ctx_indirect_func[Pm4::DB_RENDER_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HW::RenderControl r;
r.depth_clear_enable = KYTY_PM4_GET(value, DB_RENDER_CONTROL, DEPTH_CLEAR_ENABLE) != 0;
r.stencil_clear_enable = KYTY_PM4_GET(value, DB_RENDER_CONTROL, STENCIL_CLEAR_ENABLE) != 0;
r.resummarize_enable = KYTY_PM4_GET(value, DB_RENDER_CONTROL, RESUMMARIZE_ENABLE) != 0;
r.stencil_compress_disable =
KYTY_PM4_GET(value, DB_RENDER_CONTROL, STENCIL_COMPRESS_DISABLE) != 0;
r.depth_compress_disable =
KYTY_PM4_GET(value, DB_RENDER_CONTROL, DEPTH_COMPRESS_DISABLE) != 0;
r.copy_centroid = KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_CENTROID) != 0;
r.copy_sample = KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_SAMPLE);
cp.GetCtx().SetRenderControl(r);
cp.GetCtx().SetRenderControl(DecodeRenderControl(value));
};
g_hw_ctx_indirect_func[Pm4::DB_STENCILREFMASK] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
@@ -3934,23 +3917,7 @@ void GraphicsInitJmpTablesCxIndirect() {
};
g_hw_ctx_indirect_func[Pm4::PA_CL_CLIP_CNTL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HW::ClipControl r;
r.user_clip_planes = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, UCP_ENA);
r.user_clip_plane_mode = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, PS_UCP_MODE);
r.dx_clip_space = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, DX_CLIP_SPACE_DEF) != 0;
r.vertex_kill_any = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, VTX_KILL_OR) != 0;
r.min_z_clip_disable = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, ZCLIP_NEAR_DISABLE) != 0;
r.max_z_clip_disable = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, ZCLIP_FAR_DISABLE) != 0;
r.user_clip_plane_negate_y = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, PS_UCP_Y_SCALE_NEG) != 0;
r.clip_disable = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, CLIP_DISABLE) != 0;
r.user_clip_plane_cull_only = KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, UCP_CULL_ONLY_ENA) != 0;
r.cull_on_clipping_error_disable =
KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, DIS_CLIP_ERR_DETECT) != 0;
r.linear_attribute_clip_enable =
KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, DX_LINEAR_ATTR_CLIP_ENA) != 0;
r.force_viewport_index_from_vs_enable =
KYTY_PM4_GET(value, PA_CL_CLIP_CNTL, VTE_VPORT_PROVOKE_DISABLE) != 0;
cp.GetCtx().SetClipControl(r);
cp.GetCtx().SetClipControl(DecodeClipControl(value));
};
g_hw_ctx_indirect_func[Pm4::PA_SU_LINE_CNTL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
@@ -3978,11 +3945,7 @@ void GraphicsInitJmpTablesCxIndirect() {
};
g_hw_ctx_indirect_func[Pm4::PA_SC_MODE_CNTL_0] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HW::ScanModeControl r;
r.msaa_enable = KYTY_PM4_GET(value, PA_SC_MODE_CNTL_0, MSAA_ENABLE) != 0;
r.vport_scissor_enable = KYTY_PM4_GET(value, PA_SC_MODE_CNTL_0, VPORT_SCISSOR_ENABLE) != 0;
r.line_stipple_enable = KYTY_PM4_GET(value, PA_SC_MODE_CNTL_0, LINE_STIPPLE_ENABLE) != 0;
cp.GetCtx().SetScanModeControl(r);
cp.GetCtx().SetScanModeControl(DecodeScanModeControl(value));
};
g_hw_ctx_indirect_func[Pm4::PA_SC_MODE_CNTL_1] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreScanModeControl1(value);
@@ -4041,27 +4004,11 @@ void GraphicsInitJmpTablesCxIndirect() {
HwCtxIgnoreAlphaToMaskRegister(value);
};
g_hw_ctx_indirect_func[Pm4::CB_COLOR_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HW::ColorControl r;
r.mode = KYTY_PM4_GET(value, CB_COLOR_CONTROL, MODE);
r.op = KYTY_PM4_GET(value, CB_COLOR_CONTROL, ROP3);
cp.GetCtx().SetColorControl(r);
cp.GetCtx().SetColorControl(DecodeColorControl(value));
};
g_hw_ctx_indirect_func[Pm4::DB_DEPTH_CONTROL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HW::DepthControl r;
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;
r.z_write_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, Z_WRITE_ENABLE) != 0;
r.depth_bounds_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, DEPTH_BOUNDS_ENABLE) != 0;
r.zfunc = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, ZFUNC);
r.backface_enable = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, BACKFACE_ENABLE) != 0;
r.stencilfunc = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, STENCILFUNC);
r.stencilfunc_bf = KYTY_PM4_GET(value, DB_DEPTH_CONTROL, STENCILFUNC_BF);
r.color_writes_on_depth_fail_enable =
KYTY_PM4_GET(value, DB_DEPTH_CONTROL, ENABLE_COLOR_WRITES_ON_DEPTH_FAIL) != 0;
r.color_writes_on_depth_pass_disable =
KYTY_PM4_GET(value, DB_DEPTH_CONTROL, DISABLE_COLOR_WRITES_ON_DEPTH_PASS) != 0;
cp.GetCtx().SetDepthControl(r);
cp.GetCtx().SetDepthControl(DecodeDepthControl(value));
};
g_hw_ctx_indirect_func[Pm4::DB_EQAA] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
@@ -4467,7 +4414,7 @@ void GraphicsInitJmpTablesUcIndirect() {
g_hw_uc_indirect_func[Pm4::VGT_PRIMITIVE_TYPE] = [](KYTY_HW_UC_INDIRECT_ARGS) {
uint32_t prim_type = KYTY_PM4_GET(value, VGT_PRIMITIVE_TYPE, PRIM_TYPE);
cp.GetUcfg().SetPrimitiveType(prim_type);
cp.GetUcfg().SetPrimitiveType(static_cast<Prospero::PrimitiveType>(prim_type));
};
g_hw_uc_indirect_func[Pm4::VGT_INDEX_TYPE] = [](KYTY_HW_UC_INDIRECT_ARGS) {
cp.SetIndexType(value & 0x3u);
@@ -4482,7 +4429,7 @@ void GraphicsInitJmpTablesUcIndirect() {
HwUcIgnoreIaMultiVgtParam(value);
};
g_hw_uc_indirect_func[Pm4::GE_MULTI_PRIM_IB_RESET_EN] = [](KYTY_HW_UC_INDIRECT_ARGS) {
HwIgnoreMultiPrimIbReset(value);
cp.GetUcfg().SetPrimitiveResetControl(value);
};
g_hw_uc_indirect_func[Pm4::TA_CS_BC_BASE_ADDR] = [](KYTY_HW_UC_INDIRECT_ARGS) {
HwUcIgnoreBorderColorTableAddr(cmd_offset, value);
+30 -15
View File
@@ -3,15 +3,8 @@
#include "common/common.h"
#include <type_traits>
namespace Libs::Graphics::Prospero {
template <typename T>
constexpr auto GpuEnumValue(T value) {
return static_cast<std::underlying_type_t<T>>(value);
}
enum class PrimitiveType : uint32_t {
kNone = 0,
kPointList = 1,
@@ -115,32 +108,52 @@ enum class ChannelOrder : uint32_t {
kAltReversed = 3,
};
// Maps a shader's logical RGBA export to the host color-attachment components. Two bits per
// component keep this cheap to carry in shader and pipeline keys.
// Selects the logical shader component written to each physical color-attachment component.
// Two bits per component keep this cheap to carry in shader and pipeline keys.
struct ColorComponentMapping {
uint8_t packed = 0xe4u; // RGBA: 0, 1, 2, 3
static constexpr uint8_t kIdentity = 0xe4u;
uint8_t packed = kIdentity;
[[nodiscard]] constexpr uint32_t Map(uint32_t component) const {
return component < 4u ? (packed >> (component * 2u)) & 0x3u : component;
}
[[nodiscard]] constexpr uint32_t ApplyMask(uint32_t mask) const {
[[nodiscard]] constexpr uint32_t ApplyMask(uint32_t logical_mask) const {
uint32_t mapped = 0;
for (uint32_t component = 0; component < 4u; component++) {
mapped |= ((mask >> component) & 1u) << Map(component);
for (uint32_t physical_component = 0; physical_component < 4u; physical_component++) {
mapped |= ((logical_mask >> Map(physical_component)) & 1u) << physical_component;
}
return mapped;
}
[[nodiscard]] constexpr bool IsIdentity() const { return packed == 0xe4u; }
// This mapping selects an intermediate component; next selects the final logical component.
[[nodiscard]] constexpr ColorComponentMapping Then(ColorComponentMapping next) const {
ColorComponentMapping result {0u};
for (uint32_t physical_component = 0; physical_component < 4u; physical_component++) {
result.packed |= static_cast<uint8_t>(next.Map(Map(physical_component))
<< (physical_component * 2u));
}
return result;
}
[[nodiscard]] constexpr bool IsIdentity() const { return packed == kIdentity; }
bool operator==(const ColorComponentMapping&) const = default;
};
static_assert(sizeof(ColorComponentMapping) == sizeof(uint8_t));
inline constexpr ColorComponentMapping ColorMappingAbgr {0x1bu}; // 3, 2, 1, 0
inline constexpr ColorComponentMapping ColorMappingRgba {ColorComponentMapping::kIdentity};
inline constexpr ColorComponentMapping ColorMappingGr {0xe1u}; // 1, 0, 2, 3
inline constexpr ColorComponentMapping ColorMappingRabg {0x6cu}; // 0, 3, 2, 1
inline constexpr ColorComponentMapping ColorMappingRgab {0xb4u}; // 0, 1, 3, 2
inline constexpr ColorComponentMapping ColorMappingBgra {0xc6u}; // 2, 1, 0, 3
inline constexpr ColorComponentMapping ColorMappingAbgr {0x1bu}; // 3, 2, 1, 0
inline constexpr ColorComponentMapping ColorMappingAgba {0x27u}; // 3, 1, 2, 0
inline constexpr ColorComponentMapping ColorMappingArbg {0x63u}; // 3, 0, 2, 1
inline constexpr ColorComponentMapping ColorMappingAgbr {0x87u}; // 3, 1, 0, 2
inline constexpr ColorComponentMapping ColorMappingArgb {0x93u}; // 3, 0, 1, 2
enum class DepthFormat : uint32_t {
kInvalid = 0,
@@ -388,9 +401,11 @@ enum class BufferFormat : uint32_t {
k8Srgb = 128,
k8_8Srgb = 129,
k8_8_8_8Srgb = 130,
k10_10_10_2Float = 131,
k9_9_9_5Float = 132,
k5_6_5UNorm = 133,
k5_5_5_1UNorm = 134,
k1_5_5_5UNorm = 135,
k4_4_4_4UNorm = 136,
kFmask8_S4_F4 = 161,
kBc1UNorm = 169,
+167 -80
View File
@@ -3,129 +3,216 @@
#include "graphics/guest_gpu/gpu_defs.h"
#include <array>
#include <bit>
namespace Libs::Graphics::Prospero {
namespace {
struct FormatInfo {
uint32_t format;
uint32_t bytes_per_element;
uint32_t block_compressed_bytes_per_block;
uint32_t render_target_bytes_per_element;
bool supported_sampled_texture_format;
bool unsigned_integer_texture_format;
BufferFormat format;
uint32_t bytes_per_element;
uint32_t block_compressed_bytes_per_block;
uint32_t render_target_bytes_per_element;
bool supported_sampled_texture_format;
bool unsigned_integer_texture_format;
};
constexpr FormatInfo kFormatInfo[] = {
{GpuEnumValue(BufferFormat::k8UNorm), 1, 0, 1, true, false},
{GpuEnumValue(BufferFormat::k8SNorm), 0, 0, 1, false, false},
{GpuEnumValue(BufferFormat::k8UInt), 1, 0, 1, true, true},
{GpuEnumValue(BufferFormat::k16UNorm), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::k16SNorm), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::k16UInt), 2, 0, 2, true, true},
{GpuEnumValue(BufferFormat::k16SInt), 2, 0, 2, false, false},
{GpuEnumValue(BufferFormat::k16Float), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::k8_8UNorm), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::k8_8SNorm), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::k8_8UInt), 2, 0, 2, true, true},
{GpuEnumValue(BufferFormat::k8_8SInt), 2, 0, 2, false, false},
{GpuEnumValue(BufferFormat::k32UInt), 4, 0, 4, true, true},
{GpuEnumValue(BufferFormat::k32SInt), 4, 0, 4, false, false},
{GpuEnumValue(BufferFormat::k32Float), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k16_16UNorm), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k16_16SNorm), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k16_16UInt), 4, 0, 4, true, true},
{GpuEnumValue(BufferFormat::k16_16SInt), 4, 0, 4, false, false},
{GpuEnumValue(BufferFormat::k16_16Float), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k11_11_10Float), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k10_10_10_2UNorm), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k10_10_10_2UInt), 4, 0, 4, true, true},
{GpuEnumValue(BufferFormat::k8_8_8_8UNorm), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k8_8_8_8SNorm), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k8_8_8_8UInt), 4, 0, 4, true, true},
{GpuEnumValue(BufferFormat::k8_8_8_8SInt), 4, 0, 4, false, false},
{GpuEnumValue(BufferFormat::k32_32UInt), 8, 0, 8, true, true},
{GpuEnumValue(BufferFormat::k32_32SInt), 8, 0, 8, false, false},
{GpuEnumValue(BufferFormat::k32_32Float), 8, 0, 8, true, false},
{GpuEnumValue(BufferFormat::k16_16_16_16UNorm), 8, 0, 8, true, false},
{GpuEnumValue(BufferFormat::k16_16_16_16SNorm), 8, 0, 8, true, false},
{GpuEnumValue(BufferFormat::k16_16_16_16UInt), 8, 0, 8, true, true},
{GpuEnumValue(BufferFormat::k16_16_16_16SInt), 8, 0, 8, false, false},
{GpuEnumValue(BufferFormat::k16_16_16_16Float), 8, 0, 8, true, false},
{GpuEnumValue(BufferFormat::k32_32_32UInt), 12, 0, 12, true, true},
{GpuEnumValue(BufferFormat::k32_32_32SInt), 12, 0, 12, false, false},
{GpuEnumValue(BufferFormat::k32_32_32Float), 12, 0, 12, true, false},
{GpuEnumValue(BufferFormat::k32_32_32_32UInt), 16, 0, 16, true, true},
{GpuEnumValue(BufferFormat::k32_32_32_32SInt), 16, 0, 16, false, false},
{GpuEnumValue(BufferFormat::k32_32_32_32Float), 16, 0, 16, true, false},
{GpuEnumValue(BufferFormat::k8Srgb), 1, 0, 0, true, false},
{GpuEnumValue(BufferFormat::k8_8Srgb), 2, 0, 0, true, false},
{GpuEnumValue(BufferFormat::k8_8_8_8Srgb), 4, 0, 4, true, false},
{GpuEnumValue(BufferFormat::k9_9_9_5Float), 4, 0, 0, true, false},
{GpuEnumValue(BufferFormat::k5_6_5UNorm), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::k5_5_5_1UNorm), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::k4_4_4_4UNorm), 2, 0, 2, true, false},
{GpuEnumValue(BufferFormat::kFmask8_S4_F4), 1, 0, 1, true, false},
{GpuEnumValue(BufferFormat::kBc1UNorm), 0, 8, 0, true, false},
{GpuEnumValue(BufferFormat::kBc1Srgb), 0, 8, 0, true, false},
{GpuEnumValue(BufferFormat::kBc2UNorm), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc2Srgb), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc3UNorm), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc3Srgb), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc4UNorm), 0, 8, 0, true, false},
{GpuEnumValue(BufferFormat::kBc4SNorm), 0, 8, 0, true, false},
{GpuEnumValue(BufferFormat::kBc5UNorm), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc5SNorm), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc6UFloat), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc6SFloat), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc7UNorm), 0, 16, 0, true, false},
{GpuEnumValue(BufferFormat::kBc7Srgb), 0, 16, 0, true, false},
{BufferFormat::k8UNorm, 1, 0, 1, true, false},
{BufferFormat::k8SNorm, 0, 0, 1, false, false},
{BufferFormat::k8UInt, 1, 0, 1, true, true},
{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::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::k32UInt, 4, 0, 4, true, true},
{BufferFormat::k32SInt, 4, 0, 4, false, false},
{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_16Float, 4, 0, 4, true, false},
{BufferFormat::k11_11_10Float, 4, 0, 4, true, false},
{BufferFormat::k10_10_10_2UNorm, 4, 0, 4, true, false},
{BufferFormat::k10_10_10_2UInt, 4, 0, 4, true, true},
{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::k32_32UInt, 8, 0, 8, true, true},
{BufferFormat::k32_32SInt, 8, 0, 8, false, false},
{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_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_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_32Float, 16, 0, 16, true, false},
{BufferFormat::k8Srgb, 1, 0, 0, true, false},
{BufferFormat::k8_8Srgb, 2, 0, 0, true, false},
{BufferFormat::k8_8_8_8Srgb, 4, 0, 4, true, false},
{BufferFormat::k9_9_9_5Float, 4, 0, 0, true, false},
{BufferFormat::k5_6_5UNorm, 2, 0, 2, true, false},
{BufferFormat::k5_5_5_1UNorm, 2, 0, 2, true, false},
{BufferFormat::k1_5_5_5UNorm, 0, 0, 2, false, false},
{BufferFormat::k4_4_4_4UNorm, 2, 0, 2, true, false},
{BufferFormat::kFmask8_S4_F4, 1, 0, 1, true, false},
{BufferFormat::kBc1UNorm, 0, 8, 0, true, false},
{BufferFormat::kBc1Srgb, 0, 8, 0, true, false},
{BufferFormat::kBc2UNorm, 0, 16, 0, true, false},
{BufferFormat::kBc2Srgb, 0, 16, 0, true, false},
{BufferFormat::kBc3UNorm, 0, 16, 0, true, false},
{BufferFormat::kBc3Srgb, 0, 16, 0, true, false},
{BufferFormat::kBc4UNorm, 0, 8, 0, true, false},
{BufferFormat::kBc4SNorm, 0, 8, 0, true, false},
{BufferFormat::kBc5UNorm, 0, 16, 0, true, false},
{BufferFormat::kBc5SNorm, 0, 16, 0, true, false},
{BufferFormat::kBc6UFloat, 0, 16, 0, true, false},
{BufferFormat::kBc6SFloat, 0, 16, 0, true, false},
{BufferFormat::kBc7UNorm, 0, 16, 0, true, false},
{BufferFormat::kBc7Srgb, 0, 16, 0, true, false},
};
constexpr uint8_t RenderTargetTypeBit(ChannelType type) {
return static_cast<uint8_t>(1u << static_cast<uint32_t>(type));
}
constexpr auto kUNormType = RenderTargetTypeBit(ChannelType::kUNorm);
constexpr auto kIntegerTypes =
RenderTargetTypeBit(ChannelType::kUInt) | RenderTargetTypeBit(ChannelType::kSInt);
constexpr auto kNormalizedIntegerTypes =
kUNormType | RenderTargetTypeBit(ChannelType::kSNorm) | kIntegerTypes;
struct RenderTargetLayoutInfo {
ChannelLayout layout;
BufferFormat first_linear_format;
uint8_t linear_type_mask;
uint8_t components;
BufferFormat extra_format = BufferFormat::kInvalid;
ChannelType extra_type = ChannelType::kUNorm;
ChannelOrderSupport order_support = ChannelOrderSupport::kAll;
};
constexpr RenderTargetLayoutInfo kRenderTargetLayouts[] = {
{ChannelLayout::k8, BufferFormat::k8UNorm, kNormalizedIntegerTypes, 1, BufferFormat::k8Srgb,
ChannelType::kSrgb},
{ChannelLayout::k16, BufferFormat::k16UNorm, kNormalizedIntegerTypes, 1, BufferFormat::k16Float,
ChannelType::kFloat},
{ChannelLayout::k8_8, BufferFormat::k8_8UNorm, kNormalizedIntegerTypes, 2,
BufferFormat::k8_8Srgb, ChannelType::kSrgb},
{ChannelLayout::k32, BufferFormat::k32UInt, kIntegerTypes, 1, BufferFormat::k32Float,
ChannelType::kFloat},
{ChannelLayout::k16_16, BufferFormat::k16_16UNorm, kNormalizedIntegerTypes, 2,
BufferFormat::k16_16Float, ChannelType::kFloat},
{ChannelLayout::k11_11_10, BufferFormat::k11_11_10UNorm, kNormalizedIntegerTypes, 3,
BufferFormat::k11_11_10Float, ChannelType::kFloat},
{ChannelLayout::k10_11_11, BufferFormat::k10_11_11UNorm, kNormalizedIntegerTypes, 3,
BufferFormat::k10_11_11Float, ChannelType::kFloat},
{ChannelLayout::k2_10_10_10, BufferFormat::k2_10_10_10UNorm, kNormalizedIntegerTypes, 4},
{ChannelLayout::k10_10_10_2, BufferFormat::k10_10_10_2UNorm, kNormalizedIntegerTypes, 4},
{ChannelLayout::k8_8_8_8, BufferFormat::k8_8_8_8UNorm, kNormalizedIntegerTypes, 4,
BufferFormat::k8_8_8_8Srgb, ChannelType::kSrgb},
{ChannelLayout::k32_32, BufferFormat::k32_32UInt, kIntegerTypes, 2, BufferFormat::k32_32Float,
ChannelType::kFloat},
{ChannelLayout::k16_16_16_16, BufferFormat::k16_16_16_16UNorm, kNormalizedIntegerTypes, 4,
BufferFormat::k16_16_16_16Float, ChannelType::kFloat},
{ChannelLayout::k32_32_32_32, BufferFormat::k32_32_32_32UInt, kIntegerTypes, 4,
BufferFormat::k32_32_32_32Float, ChannelType::kFloat},
{ChannelLayout::k5_6_5, BufferFormat::k5_6_5UNorm, kUNormType, 3},
{ChannelLayout::k5_5_5_1, BufferFormat::k5_5_5_1UNorm, kUNormType, 4},
{ChannelLayout::k1_5_5_5, BufferFormat::k1_5_5_5UNorm, kUNormType, 4},
{ChannelLayout::k4_4_4_4, BufferFormat::k4_4_4_4UNorm, kUNormType, 4},
{ChannelLayout::k10_10_10_2Float, BufferFormat::kInvalid, 0, 4, BufferFormat::k10_10_10_2Float,
ChannelType::kFloat, ChannelOrderSupport::kStandardOnly},
};
const RenderTargetLayoutInfo* FindRenderTargetLayout(ChannelLayout layout) {
for (const auto& info: kRenderTargetLayouts) {
if (info.layout == layout) {
return &info;
}
}
return nullptr;
}
constexpr auto MakeFormatInfoLookup() {
constexpr uint32_t kMaxFormat = GpuEnumValue(BufferFormat::kBc7Srgb);
constexpr size_t kMaxFormat = static_cast<size_t>(BufferFormat::kBc7Srgb);
std::array<const FormatInfo*, kMaxFormat + 1> lookup {};
for (const auto& info: kFormatInfo) {
lookup[info.format] = &info;
lookup[static_cast<size_t>(info.format)] = &info;
}
return lookup;
}
constexpr auto kFormatInfoLookup = MakeFormatInfoLookup();
const FormatInfo* FindFormatInfo(uint32_t format) {
return format < kFormatInfoLookup.size() ? kFormatInfoLookup[format] : nullptr;
const FormatInfo* FindFormatInfo(BufferFormat format) {
const auto index = static_cast<size_t>(format);
return index < kFormatInfoLookup.size() ? kFormatInfoLookup[index] : nullptr;
}
} // namespace
uint32_t NumBytesPerElement(uint32_t format) {
RenderTargetFormatEncoding ResolveRenderTargetFormat(ChannelLayout layout, ChannelType type) {
const auto* layout_info = FindRenderTargetLayout(layout);
const auto raw_type = static_cast<uint32_t>(type);
if (layout_info == nullptr || raw_type >= 8u) {
return {};
}
auto format = BufferFormat::kInvalid;
if (layout_info->extra_format != BufferFormat::kInvalid && type == layout_info->extra_type) {
format = layout_info->extra_format;
} else if ((layout_info->linear_type_mask & (1u << raw_type)) != 0) {
const auto first_type =
std::countr_zero(static_cast<uint32_t>(layout_info->linear_type_mask));
format = static_cast<BufferFormat>(static_cast<uint32_t>(layout_info->first_linear_format) +
raw_type - first_type);
}
if (format == BufferFormat::kInvalid) {
return {};
}
return {format, layout_info->components, layout_info->order_support};
}
uint32_t NumBytesPerElement(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr ? info->bytes_per_element : 0;
}
uint32_t BlockCompressedBytesPerBlock(uint32_t format) {
uint32_t BlockCompressedBytesPerBlock(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr ? info->block_compressed_bytes_per_block : 0;
}
uint32_t RenderTargetBytesPerElement(uint32_t format) {
uint32_t RenderTargetBytesPerElement(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr ? info->render_target_bytes_per_element : 0;
}
bool IsSupportedTextureFormat(uint32_t format) {
bool IsSupportedTextureFormat(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr && info->supported_sampled_texture_format;
}
bool IsUintTextureFormat(uint32_t format) {
bool IsUintTextureFormat(BufferFormat format) {
const auto* info = FindFormatInfo(format);
return info != nullptr && info->unsigned_integer_texture_format;
}
bool IsFmaskTextureFormat(uint32_t format) {
return format == GpuEnumValue(BufferFormat::kFmask8_S4_F4);
bool IsFmaskTextureFormat(BufferFormat format) {
return format == BufferFormat::kFmask8_S4_F4;
}
} // namespace Libs::Graphics::Prospero
+40 -7
View File
@@ -1,16 +1,49 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GUEST_GPU_GPU_FORMAT_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GUEST_GPU_GPU_FORMAT_H_
#include "common/common.h"
#include "graphics/guest_gpu/gpu_defs.h"
namespace Libs::Graphics::Prospero {
uint32_t NumBytesPerElement(uint32_t format);
uint32_t BlockCompressedBytesPerBlock(uint32_t format);
uint32_t RenderTargetBytesPerElement(uint32_t format);
bool IsSupportedTextureFormat(uint32_t format);
bool IsUintTextureFormat(uint32_t format);
bool IsFmaskTextureFormat(uint32_t format);
enum class ChannelOrderSupport : uint8_t {
kNone,
kStandardOnly,
kAll,
};
struct RenderTargetFormatEncoding {
BufferFormat buffer_format = BufferFormat::kInvalid;
uint8_t components = 0;
ChannelOrderSupport order_support = ChannelOrderSupport::kNone;
[[nodiscard]] constexpr bool IsValid() const {
return buffer_format != BufferFormat::kInvalid && components >= 1u && components <= 4u &&
order_support != ChannelOrderSupport::kNone;
}
[[nodiscard]] constexpr bool SupportsOrder(ChannelOrder order) const {
switch (order_support) {
case ChannelOrderSupport::kStandardOnly: return order == ChannelOrder::kStandard;
case ChannelOrderSupport::kAll:
switch (order) {
case ChannelOrder::kStandard:
case ChannelOrder::kAlt:
case ChannelOrder::kReversed:
case ChannelOrder::kAltReversed: return true;
}
return false;
default: return false;
}
}
};
RenderTargetFormatEncoding ResolveRenderTargetFormat(ChannelLayout layout, ChannelType type);
uint32_t NumBytesPerElement(BufferFormat format);
uint32_t BlockCompressedBytesPerBlock(BufferFormat format);
uint32_t RenderTargetBytesPerElement(BufferFormat format);
bool IsSupportedTextureFormat(BufferFormat format);
bool IsUintTextureFormat(BufferFormat format);
bool IsFmaskTextureFormat(BufferFormat format);
} // namespace Libs::Graphics::Prospero
+71 -171
View File
@@ -13,6 +13,7 @@
#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"
@@ -36,7 +37,7 @@ static thread_local CommandProcessor* g_current_processor = nullptr;
static thread_local Pm4Execution* g_current_execution = nullptr;
static thread_local bool g_gpu_mutex_owned = false;
static thread_local bool g_gpu_thread = false;
static thread_local GpuState* g_gpu_state = nullptr;
static thread_local GuestGpu* g_gpu_state = nullptr;
class GpuMutexLock final {
public:
@@ -59,115 +60,30 @@ private:
Common::Mutex& m_mutex;
};
struct OwnedCmdBuffer {
OwnedCmdBuffer() = default;
explicit OwnedCmdBuffer(const uint32_t* data, uint32_t count) {
EXIT_IF(data == nullptr && count != 0);
if (count != 0) {
m_words.assign(data, data + count);
}
}
[[nodiscard]] bool Empty() const noexcept { return m_words.empty(); }
[[nodiscard]] uint32_t Size() const noexcept { return static_cast<uint32_t>(m_words.size()); }
[[nodiscard]] uint32_t* Data() noexcept { return m_words.data(); }
private:
std::vector<uint32_t> m_words;
};
class GpuState {
public:
static constexpr uint32_t ComputePipeCount = 7;
static constexpr uint32_t QueuesPerComputePipe = 8;
static constexpr uint32_t ComputeQueueCount = ComputePipeCount * QueuesPerComputePipe;
static constexpr uint32_t QueueCount = 1 + ComputeQueueCount;
explicit GpuState(RenderContext& renderer): m_renderer(renderer) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
m_gfx_cp = std::make_unique<CommandProcessor>(renderer);
m_thread = std::jthread(ThreadRun, this);
}
~GpuState();
KYTY_CLASS_NO_COPY(GpuState);
void Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
uint32_t num_const_dw, bool trigger_agc_interrupt_on_done);
void SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
bool trigger_agc_interrupt_on_done);
void SubmitFlipPreparation(uint64_t request_id);
void Done();
void Shutdown();
[[nodiscard]] bool IsStopping();
void SendCommand(Common::UniqueFunction<void>&& command);
void SendCommandSync(Common::UniqueFunction<void>&& command);
int GetFrameNum();
[[nodiscard]] static bool IsGpuThread() noexcept { return g_gpu_thread; }
private:
enum class SubmissionType { Graphics, Compute, FlipPreparation };
struct Submission {
SubmissionType type = SubmissionType::Graphics;
uint32_t queue_id = 0;
OwnedCmdBuffer commands;
OwnedCmdBuffer constant_commands;
Pm4Execution command_execution;
Pm4Execution constant_execution;
bool trigger_agc_interrupt_on_done = false;
bool reset_processor = false;
bool started = false;
bool command_complete = false;
bool constant_complete = false;
bool blocked = false;
uint64_t flip_request_id = 0;
};
void Enqueue(Submission submission);
void WaitForIdle();
void ProcessCommands();
bool Process(Submission& submission);
static void ThreadRun(void* data);
CommandProcessor& GetProcessor(uint32_t queue_id);
RenderContext& m_renderer;
Common::Mutex m_submission_mutex;
Common::Mutex m_queue_mutex;
std::mutex m_shutdown_mutex;
Common::CondVar m_work_available;
Common::CondVar m_idle;
std::array<std::deque<Submission>, QueueCount> m_queues;
std::deque<Common::UniqueFunction<void>> m_commands;
std::atomic_uint32_t m_pending_commands {0};
uint32_t m_next_queue = 0;
uint32_t m_submission_count = 0;
bool m_processing = false;
bool m_graphics_done = true;
bool m_accepting = true;
bool m_stopping = false;
bool m_shutdown_complete = false;
std::unique_ptr<CommandProcessor> m_gfx_cp;
std::array<std::unique_ptr<CommandProcessor>, ComputeQueueCount> m_compute_cp;
uint64_t m_submit_id = 0;
std::atomic_int m_done_num = 0;
std::jthread m_thread;
friend class CommandProcessor;
};
static bool GraphicsRunDebugDumpEnabled() {
return Config::GraphicsDebugDumpEnabled() &&
Config::GetPrintfDirection() != Config::OutputDirection::Silent;
}
GpuState::~GpuState() {
GuestGpu::OwnedCmdBuffer::OwnedCmdBuffer(const uint32_t* data, uint32_t count) {
EXIT_IF(data == nullptr && count != 0);
if (count != 0) {
m_words.assign(data, data + count);
}
}
GuestGpu::GuestGpu(RenderContext& renderer): m_renderer(renderer) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
GraphicsInitJmpTables();
m_gfx_cp = std::make_unique<CommandProcessor>(renderer);
m_thread = std::jthread(ThreadRun, this);
}
GuestGpu::~GuestGpu() {
Shutdown();
}
void GpuState::Shutdown() {
void GuestGpu::Shutdown() {
std::lock_guard shutdown_lock(m_shutdown_mutex);
if (m_shutdown_complete) {
return;
@@ -184,12 +100,12 @@ void GpuState::Shutdown() {
m_shutdown_complete = true;
}
bool GpuState::IsStopping() {
bool GuestGpu::IsStopping() {
Common::LockGuard lock(m_queue_mutex);
return m_stopping;
}
void GpuState::SendCommand(Common::UniqueFunction<void>&& command) {
void GuestGpu::SendCommand(Common::UniqueFunction<void>&& command) {
EXIT_IF(!command);
if (IsGpuThread()) {
command();
@@ -202,7 +118,7 @@ void GpuState::SendCommand(Common::UniqueFunction<void>&& command) {
m_work_available.Signal();
}
void GpuState::ProcessCommands() {
void GuestGpu::ProcessCommands() {
EXIT_IF(!IsGpuThread());
while (m_pending_commands.load(std::memory_order_acquire) != 0) {
Common::UniqueFunction<void> command;
@@ -217,7 +133,7 @@ void GpuState::ProcessCommands() {
}
}
void GpuState::SendCommandSync(Common::UniqueFunction<void>&& command) {
void GuestGpu::SendCommandSync(Common::UniqueFunction<void>&& command) {
EXIT_IF(!command);
if (IsGpuThread()) {
command();
@@ -231,8 +147,12 @@ void GpuState::SendCommandSync(Common::UniqueFunction<void>&& command) {
done.acquire();
}
void GpuState::Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
void GuestGpu::Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
uint32_t num_const_dw, bool trigger_agc_interrupt_on_done) {
if (num_draw_dw == 0) {
return;
}
EXIT_IF(cmd_draw_buffer == nullptr);
GpuMutexLock lock(m_submission_mutex);
Submission submission;
submission.type = SubmissionType::Graphics;
@@ -245,8 +165,9 @@ void GpuState::Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t*
Enqueue(std::move(submission));
}
void GpuState::SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
void GuestGpu::SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
bool trigger_agc_interrupt_on_done) {
EXIT_IF(cmd_buffer == nullptr || num_dw == 0);
GpuMutexLock lock(m_submission_mutex);
constexpr uint32_t compute_queue_base = 0x20u;
@@ -262,7 +183,7 @@ void GpuState::SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_
Enqueue(std::move(submission));
}
void GpuState::SubmitFlipPreparation(uint64_t request_id) {
void GuestGpu::SubmitFlipPreparation(uint64_t request_id) {
GpuMutexLock lock(m_submission_mutex);
Submission submission;
submission.type = SubmissionType::FlipPreparation;
@@ -273,20 +194,26 @@ void GpuState::SubmitFlipPreparation(uint64_t request_id) {
Enqueue(std::move(submission));
}
void GpuState::Done() {
void GuestGpu::Done() {
GpuMutexLock lock(m_submission_mutex);
if (!IsGpuThread()) {
WaitForIdle();
}
if (RenderDocCaptureInProgress()) {
SendCommandSync([this] {
Common::LockGuard render_lock(m_renderer.GetMutex());
RenderDocEndCapture();
});
}
m_graphics_done = true;
m_done_num++;
}
int GpuState::GetFrameNum() {
int GuestGpu::GetFrameNum() const {
return m_done_num;
}
CommandProcessor& GpuState::GetProcessor(uint32_t queue_id) {
CommandProcessor& GuestGpu::GetProcessor(uint32_t queue_id) {
EXIT_IF(queue_id >= QueueCount);
if (queue_id == 0) {
return *m_gfx_cp;
@@ -374,6 +301,12 @@ void CommandProcessor::WaitDeDiff(uint32_t diff) {
}
}
void CommandProcessor::WaitForRewind(bool valid) {
if (!valid) {
SuspendPm4();
}
}
void CommandProcessor::IncrementDe() {
m_de_count++;
}
@@ -489,6 +422,13 @@ void CommandProcessor::DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cach
default: return false;
}
};
if (dst_sel == 2) {
// kNowhere discards the GL2 prefetch destination without a guest-visible write.
if (src_sel != 3) {
EXIT("unsupported dmaData nowhere source selector 0x%02" PRIx8 "\n", src_sel);
}
return;
}
bool dst_gds = false;
if (!decode_gds(dst_sel, dst_gds)) {
EXIT("unsupported dmaData destination selector 0x%02" PRIx8 "\n", dst_sel);
@@ -511,7 +451,7 @@ void CommandProcessor::DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cach
dst_gds, src_gds);
}
void GpuState::Enqueue(Submission submission) {
void GuestGpu::Enqueue(Submission submission) {
EXIT_IF(submission.queue_id >= QueueCount);
Common::LockGuard lock(m_queue_mutex);
EXIT_IF(!m_accepting);
@@ -520,15 +460,15 @@ void GpuState::Enqueue(Submission submission) {
m_work_available.Signal();
}
void GpuState::WaitForIdle() {
void GuestGpu::WaitForIdle() {
Common::LockGuard lock(m_queue_mutex);
while (m_processing || !m_commands.empty() || m_submission_count != 0) {
m_idle.Wait(&m_queue_mutex);
}
}
void GpuState::ThreadRun(void* data) {
auto* gpu = static_cast<GpuState*>(data);
void GuestGpu::ThreadRun(void* data) {
auto* gpu = static_cast<GuestGpu*>(data);
EXIT_IF(gpu == nullptr);
KYTY_PROFILER_THREAD("Thread_Gpu");
g_gpu_thread = true;
@@ -624,9 +564,13 @@ void GpuState::ThreadRun(void* data) {
}
}
bool GpuState::Process(Submission& submission) {
auto& cp = GetProcessor(submission.queue_id);
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) {
cp.Reset();
@@ -1384,9 +1328,14 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
case 0x00:
switch (eop_event_type) {
case 0x04:
if (event_index == 0x05) {
write64(false);
return;
}
break;
case 0x14:
case 0x28:
if ((eop_event_type == 0x04 && event_index == 0x05) ||
(eop_event_type == 0x28 && event_index == 0x00)) {
if (event_index == 0x00) {
write64(false);
return;
}
@@ -1657,57 +1606,8 @@ void CommandProcessor::SynchronizeGpu() {
GetScheduler().FinishCurrent();
}
Gpu::Gpu(RenderContext& renderer) {
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
GraphicsInitJmpTables();
m_state = std::make_unique<GpuState>(renderer);
}
Gpu::~Gpu() = default;
void Gpu::Shutdown() {
m_state->Shutdown();
}
bool Gpu::IsStopping() {
return m_state->IsStopping();
}
void Gpu::SendCommand(Common::UniqueFunction<void>&& command) {
m_state->SendCommand(std::move(command));
}
void Gpu::SendCommandSync(Common::UniqueFunction<void>&& command) {
m_state->SendCommandSync(std::move(command));
}
void Gpu::Submit(uint32_t* draw_commands, uint32_t draw_size_dw, uint32_t* constant_commands,
uint32_t constant_size_dw, bool trigger_agc_interrupt_on_done) {
EXIT_IF(draw_commands == nullptr || draw_size_dw == 0);
m_state->Submit(draw_commands, draw_size_dw, constant_commands, constant_size_dw,
trigger_agc_interrupt_on_done);
}
void Gpu::SubmitCompute(uint32_t queue, uint32_t* commands, uint32_t size_dw,
bool trigger_agc_interrupt_on_done) {
EXIT_IF(commands == nullptr || size_dw == 0);
m_state->SubmitCompute(queue, commands, size_dw, trigger_agc_interrupt_on_done);
}
void Gpu::SubmitFlipPreparation(uint64_t request_id) {
m_state->SubmitFlipPreparation(request_id);
}
void Gpu::Done() {
m_state->Done();
}
int Gpu::GetFrameNum() const {
return m_state->GetFrameNum();
}
bool Gpu::IsGpuThread() noexcept {
return GpuState::IsGpuThread();
bool GuestGpu::IsGpuThread() noexcept {
return g_gpu_thread;
}
} // namespace Libs::Graphics
+83 -6
View File
@@ -3,20 +3,28 @@
#include "common/abi.h"
#include "common/common.h"
#include "common/threads.h"
#include "common/uniqueFunction.h"
#include "graphics/guest_gpu/command_processor/commandProcessor.h"
#include <array>
#include <atomic>
#include <cstdint>
#include <deque>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
namespace Libs::Graphics {
class GpuState;
class RenderContext;
class Gpu final {
class GuestGpu final {
public:
explicit Gpu(RenderContext& renderer);
~Gpu();
KYTY_CLASS_NO_COPY(Gpu);
explicit GuestGpu(RenderContext& renderer);
~GuestGpu();
KYTY_CLASS_NO_COPY(GuestGpu);
void Shutdown();
[[nodiscard]] bool IsStopping();
@@ -34,7 +42,76 @@ public:
[[nodiscard]] static bool IsGpuThread() noexcept;
private:
std::unique_ptr<GpuState> m_state;
static constexpr uint32_t ComputePipeCount = 7;
static constexpr uint32_t QueuesPerComputePipe = 8;
static constexpr uint32_t ComputeQueueCount = ComputePipeCount * QueuesPerComputePipe;
static constexpr uint32_t QueueCount = 1 + ComputeQueueCount;
class OwnedCmdBuffer {
public:
OwnedCmdBuffer() = default;
explicit OwnedCmdBuffer(const uint32_t* data, uint32_t count);
[[nodiscard]] bool Empty() const noexcept { return m_words.empty(); }
[[nodiscard]] uint32_t Size() const noexcept {
return static_cast<uint32_t>(m_words.size());
}
[[nodiscard]] uint32_t* Data() noexcept { return m_words.data(); }
private:
std::vector<uint32_t> m_words;
};
enum class SubmissionType { Graphics, Compute, FlipPreparation };
struct Submission {
SubmissionType type = SubmissionType::Graphics;
uint32_t queue_id = 0;
OwnedCmdBuffer commands;
OwnedCmdBuffer constant_commands;
Pm4Execution command_execution;
Pm4Execution constant_execution;
bool trigger_agc_interrupt_on_done = false;
bool reset_processor = false;
bool started = false;
bool command_complete = false;
bool constant_complete = false;
bool blocked = false;
uint64_t flip_request_id = 0;
};
void Enqueue(Submission submission);
void WaitForIdle();
void ProcessCommands();
bool Process(Submission& submission);
static void ThreadRun(void* data);
CommandProcessor& GetProcessor(uint32_t queue_id);
RenderContext& m_renderer;
Common::Mutex m_submission_mutex;
Common::Mutex m_queue_mutex;
std::mutex m_shutdown_mutex;
Common::CondVar m_work_available;
Common::CondVar m_idle;
std::array<std::deque<Submission>, QueueCount> m_queues;
std::deque<Common::UniqueFunction<void>> m_commands;
std::atomic_uint32_t m_pending_commands {0};
uint32_t m_next_queue = 0;
uint32_t m_submission_count = 0;
bool m_processing = false;
bool m_graphics_done = true;
bool m_accepting = true;
bool m_stopping = false;
bool m_shutdown_complete = false;
std::unique_ptr<CommandProcessor> m_gfx_cp;
std::array<std::unique_ptr<CommandProcessor>, ComputeQueueCount> m_compute_cp;
uint64_t m_submit_id = 0;
std::atomic_int m_done_num = 0;
std::jthread m_thread;
friend class CommandProcessor;
};
} // namespace Libs::Graphics
+45 -42
View File
@@ -29,27 +29,27 @@ struct ColorView {
struct ColorInfo {
bool fmask_compression_enable = false;
// uint32_t fmask_compression_mode = 0;
bool fmask_data_compression_disable = false;
bool fmask_one_frag_mode = false;
bool cmask_fast_clear_enable = false;
bool dcc_compression_enable = false;
bool blend_clamp = false;
bool blend_bypass = false;
bool round_mode = false;
uint32_t cmask_is_linear = 0;
uint32_t cmask_addr_type = 0;
bool alt_tile_mode = false;
uint32_t format = 0;
uint32_t channel_type = 0;
uint32_t channel_order = 0;
bool fmask_data_compression_disable = false;
bool fmask_one_frag_mode = false;
bool cmask_fast_clear_enable = false;
bool dcc_compression_enable = false;
bool blend_clamp = false;
bool blend_bypass = false;
bool round_mode = false;
uint32_t cmask_is_linear = 0;
uint32_t cmask_addr_type = 0;
bool alt_tile_mode = false;
Prospero::ChannelLayout format = Prospero::ChannelLayout::kInvalid;
Prospero::ChannelType channel_type = Prospero::ChannelType::kUNorm;
Prospero::ChannelOrder channel_order = Prospero::ChannelOrder::kStandard;
};
struct ColorAttrib {
bool force_dest_alpha_to_one = false;
uint32_t tile_mode = 0;
uint32_t fmask_tile_mode = 0;
uint32_t num_samples = 0;
uint32_t num_fragments = 0;
bool force_dest_alpha_to_one = false;
Prospero::TileMode tile_mode = Prospero::TileMode::kLinear;
Prospero::TileMode fmask_tile_mode = Prospero::TileMode::kLinear;
uint32_t num_samples = 0;
uint32_t num_fragments = 0;
};
struct ColorAttrib2 {
@@ -59,11 +59,11 @@ struct ColorAttrib2 {
};
struct ColorAttrib3 {
uint32_t depth = 0;
uint32_t tile_mode = 0;
uint32_t dimension = 0;
bool cmask_pipe_aligned = false;
bool dcc_pipe_aligned = false;
uint32_t depth = 0;
Prospero::TileMode tile_mode = Prospero::TileMode::kLinear;
uint32_t dimension = 0;
bool cmask_pipe_aligned = false;
bool dcc_pipe_aligned = false;
};
struct ColorDccControl {
@@ -132,7 +132,7 @@ struct RenderTarget {
};
struct DepthZInfo {
uint32_t format = 0;
Prospero::DepthFormat format = Prospero::DepthFormat::kInvalid;
uint32_t num_samples = 0;
Prospero::TextureCompatiblePlaneCompression texture_compatibility =
Prospero::TextureCompatiblePlaneCompression::kDisable;
@@ -144,16 +144,16 @@ struct DepthZInfo {
[[nodiscard]] static DepthZInfo Decode(uint32_t value) {
DepthZInfo info;
info.format = value & 0x3u;
info.format = static_cast<Prospero::DepthFormat>(value & 0x3u);
info.num_samples = (value >> 2u) & 0x3u;
info.texture_compatibility = static_cast<Prospero::TextureCompatiblePlaneCompression>(
value & Prospero::GpuEnumValue(Prospero::TextureCompatiblePlaneCompression::kBitMask));
value & static_cast<uint32_t>(Prospero::TextureCompatiblePlaneCompression::kBitMask));
info.partially_resident = (value & 0x00001000u) != 0;
info.max_mip_level = static_cast<uint8_t>((value >> 16u) & 0x0fu);
info.expclear_enabled = (value & 0x08000000u) != 0;
info.htile_acceleration = (value & 0x20000000u) != 0;
info.z_compare_base = static_cast<Prospero::ZCompareBase>(
value & Prospero::GpuEnumValue(Prospero::ZCompareBase::kBitMask));
value & static_cast<uint32_t>(Prospero::ZCompareBase::kBitMask));
return info;
}
@@ -167,7 +167,7 @@ struct DepthZInfo {
};
struct DepthStencilInfo {
uint32_t format = 0;
Prospero::StencilFormat format = Prospero::StencilFormat::kInvalid;
Prospero::TextureCompatibleStencil texture_compatibility =
Prospero::TextureCompatibleStencil::kDisable;
bool expclear_enabled = false;
@@ -176,9 +176,9 @@ struct DepthStencilInfo {
[[nodiscard]] static DepthStencilInfo Decode(uint32_t value) {
DepthStencilInfo info;
info.format = value & 0x1u;
info.format = static_cast<Prospero::StencilFormat>(value & 0x1u);
info.texture_compatibility = static_cast<Prospero::TextureCompatibleStencil>(
value & Prospero::GpuEnumValue(Prospero::TextureCompatibleStencil::kBitMask));
value & static_cast<uint32_t>(Prospero::TextureCompatibleStencil::kBitMask));
info.partially_resident = (value & 0x00001000u) != 0;
info.expclear_enabled = (value & 0x08000000u) != 0;
info.htile_stencil_disabled = (value & 0x20000000u) != 0;
@@ -295,10 +295,7 @@ struct ClipControl {
bool linear_attribute_clip_enable = false;
bool force_viewport_index_from_vs_enable = false;
[[nodiscard]] bool IsZClipModeRepresentable() const {
return min_z_clip_disable == max_z_clip_disable;
}
[[nodiscard]] bool IsZClipEnabled() const { return !min_z_clip_disable; }
[[nodiscard]] bool IsZClipEnabled() const { return !min_z_clip_disable && !max_z_clip_disable; }
};
struct DepthControl {
@@ -941,8 +938,10 @@ public:
[[nodiscard]] uint8_t GetStencilClearValue() const { return m_stencil_clear_value; }
void SetStencilClearValue(uint8_t clear_value) { m_stencil_clear_value = clear_value; }
[[nodiscard]] float GetLineWidth() const { return m_line_width; }
void SetLineWidth(float width) { m_line_width = width; }
[[nodiscard]] float GetLineWidth() const { return m_line_width; }
void SetLineWidth(float width) { m_line_width = width; }
[[nodiscard]] uint32_t GetPrimitiveResetIndex() const { return m_primitive_reset_index; }
void SetPrimitiveResetIndex(uint32_t index) { m_primitive_reset_index = index; }
[[nodiscard]] const ShaderRegisters& GetShaderRegisters() const { return m_sh_regs; }
@@ -987,7 +986,8 @@ public:
void SetScShaderControl(uint32_t value) { m_sh_regs.m_paScShaderControl = value; }
private:
float m_line_width = 1.0f;
float m_line_width = 1.0f;
uint32_t m_primitive_reset_index = 0xffffffffu;
BlendControl m_blend_control[8];
BlendColor m_blend_color;
@@ -1029,12 +1029,14 @@ public:
void Reset() { *this = UserConfig(); }
void SetPrimitiveType(uint32_t prim_type) { m_prim_type = prim_type; }
[[nodiscard]] uint32_t GetPrimType() const { return m_prim_type; }
void SetPrimitiveType(Prospero::PrimitiveType prim_type) { m_prim_type = prim_type; }
[[nodiscard]] Prospero::PrimitiveType GetPrimType() const { return m_prim_type; }
void SetIndexOffset(uint32_t index_offset) { m_index_offset = index_offset; }
[[nodiscard]] uint32_t GetIndexOffset() const { return m_index_offset; }
void SetObjectId(uint32_t object_id) { m_object_id = object_id; }
[[nodiscard]] uint32_t GetObjectId() const { return m_object_id; }
void SetPrimitiveResetControl(uint32_t control) { m_primitive_reset_control = control; }
[[nodiscard]] uint32_t GetPrimitiveResetControl() const { return m_primitive_reset_control; }
[[nodiscard]] const GeControl& GetGeControl() const { return m_ge_cntl; }
void SetGeControl(const GeControl& control) { m_ge_cntl = control; }
@@ -1049,9 +1051,10 @@ public:
void SetGdsOaAddress(uint32_t value) { m_gds_oa.counters[m_gds_oa.GetIndex()].address = value; }
private:
uint32_t m_prim_type = 0;
uint32_t m_index_offset = 0;
uint32_t m_object_id = 0;
Prospero::PrimitiveType m_prim_type = Prospero::PrimitiveType::kNone;
uint32_t m_index_offset = 0;
uint32_t m_object_id = 0;
uint32_t m_primitive_reset_control = 0;
GeControl m_ge_cntl;
GeUserVgprEn m_ge_user_vgpr_en;
+39 -35
View File
@@ -265,7 +265,7 @@ static bool GetMipTailLayout(const TileBlockLayout& block, MipTailLayout& out) {
return false;
}
bool TileGetTextureElementLayout(uint32_t format, TileTextureElementLayout& out) {
bool TileGetTextureElementLayout(Prospero::BufferFormat format, TileTextureElementLayout& out) {
if (const auto bytes = Prospero::NumBytesPerElement(format); std::has_single_bit(bytes)) {
out = {bytes, 1, 1};
return true;
@@ -277,10 +277,10 @@ bool TileGetTextureElementLayout(uint32_t format, TileTextureElementLayout& out)
return false;
}
bool TileGetTextureBlockLayout(uint32_t format, uint32_t tile, bool volume,
bool TileGetTextureBlockLayout(Prospero::BufferFormat format, Prospero::TileMode tile, bool volume,
TileTextureBlockLayout& out) {
TileBlockFamily family = TileBlockFamily::Count;
switch (static_cast<Prospero::TileMode>(tile)) {
switch (tile) {
case Prospero::TileMode::kStandard256B:
if (!volume) family = TileBlockFamily::Standard256B;
break;
@@ -1130,15 +1130,18 @@ static bool TileGetHtileSize(uint32_t width, uint32_t height, TileSizeAlign& hti
return true;
}
bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t z_format,
uint32_t stencil_format, bool htile, TileSizeAlign& stencil_size,
TileSizeAlign& htile_size, TileSizeAlign& depth_size,
uint32_t num_fragments_log2) {
bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch,
Prospero::DepthFormat z_format, Prospero::StencilFormat stencil_format,
bool htile, TileSizeAlign& stencil_size, TileSizeAlign& htile_size,
TileSizeAlign& depth_size, uint32_t num_fragments_log2) {
EXIT_IF(pitch != 0);
// Prospero derives uncompressed depth/stencil as independent 64 KiB block surfaces.
if (width > 0 && width <= 16384 && height > 0 && height <= 16384 &&
(z_format == 1 || z_format == 3) && stencil_format <= 1 && num_fragments_log2 <= 3) {
const uint32_t depth_bytes = z_format == 1 ? 2u : 4u;
(z_format == Prospero::DepthFormat::kZ16 || z_format == Prospero::DepthFormat::kZ32F) &&
(stencil_format == Prospero::StencilFormat::kInvalid ||
stencil_format == Prospero::StencilFormat::k8UInt) &&
num_fragments_log2 <= 3) {
const uint32_t depth_bytes = z_format == Prospero::DepthFormat::kZ16 ? 2u : 4u;
uint32_t depth_block_width = 0;
uint32_t depth_block_height = 0;
uint32_t stencil_block_width = 0;
@@ -1146,7 +1149,7 @@ bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t
const bool valid_blocks =
Gen5Msaa64KBBlockSizeFromElementBytes(depth_bytes, num_fragments_log2,
&depth_block_width, &depth_block_height) &&
(stencil_format == 0 ||
(stencil_format == Prospero::StencilFormat::kInvalid ||
Gen5Msaa64KBBlockSizeFromElementBytes(1, num_fragments_log2, &stencil_block_width,
&stencil_block_height));
const uint32_t fragments = 1u << num_fragments_log2;
@@ -1155,7 +1158,7 @@ bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t
AlignUp(height, depth_block_height) * depth_bytes * fragments
: 0;
const uint64_t stencil_bytes_total =
stencil_format == 1 && valid_blocks
stencil_format == Prospero::StencilFormat::k8UInt && valid_blocks
? static_cast<uint64_t>(AlignUp(width, stencil_block_width)) *
AlignUp(height, stencil_block_height) * fragments
: 0;
@@ -1163,7 +1166,7 @@ bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t
const bool htile_valid = !htile || TileGetHtileSize(width, height, calculated_htile);
if (depth_bytes_total <= UINT32_MAX && stencil_bytes_total <= UINT32_MAX && htile_valid) {
depth_size = {static_cast<uint32_t>(depth_bytes_total), 65536};
stencil_size = stencil_format == 1
stencil_size = stencil_format == Prospero::StencilFormat::k8UInt
? TileSizeAlign {static_cast<uint32_t>(stencil_bytes_total), 65536}
: TileSizeAlign {};
htile_size = calculated_htile;
@@ -1236,31 +1239,29 @@ bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitc
if (levels > max_levels) {
return false;
}
uint32_t format = 0;
auto format = Prospero::BufferFormat::kInvalid;
switch (bytes_per_element) {
case 1: format = Prospero::GpuEnumValue(Prospero::BufferFormat::k8UNorm); break;
case 2: format = Prospero::GpuEnumValue(Prospero::BufferFormat::k16UNorm); break;
case 4: format = Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float); break;
case 8: format = Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float); break;
case 16: format = Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32_32_32Float); break;
case 1: format = Prospero::BufferFormat::k8UNorm; break;
case 2: format = Prospero::BufferFormat::k16UNorm; break;
case 4: format = Prospero::BufferFormat::k32Float; break;
case 8: format = Prospero::BufferFormat::k16_16_16_16Float; break;
case 16: format = Prospero::BufferFormat::k32_32_32_32Float; break;
default: return false;
}
TileGetTextureSize(format, width, height, levels,
Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget), &total_size,
level_sizes, padded_size);
TileGetTextureSize(format, width, height, levels, Prospero::TileMode::kRenderTarget,
&total_size, level_sizes, padded_size);
return total_size.size != 0 && total_size.align == 65536;
}
void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32_t levels,
uint32_t tile, TileSizeAlign* total_size, TileSizeOffset* level_sizes,
TilePaddedSize* padded_size) {
void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
uint32_t levels, Prospero::TileMode tile, TileSizeAlign* total_size,
TileSizeOffset* level_sizes, TilePaddedSize* padded_size) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(levels == 0 || levels > 16);
TileTextureElementLayout element {};
if (static_cast<Prospero::TileMode>(tile) == Prospero::TileMode::kLinear &&
TileGetTextureElementLayout(format, element)) {
if (tile == Prospero::TileMode::kLinear && TileGetTextureElementLayout(format, element)) {
uint32_t mip_pitch[16] {};
uint32_t mip_height[16] {};
uint32_t mip_size[16] {};
@@ -1302,26 +1303,27 @@ void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32
}
if (total_size != nullptr && total_size->size == 0) {
std::vector<std::string> list;
list.push_back(fmt::format("format = {}", format));
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 = {}", tile));
list.push_back(fmt::format("tile = {}", static_cast<uint32_t>(tile)));
EXIT("unknown format:\n%s\n", Common::Concat(list, '\n').c_str());
}
}
void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
uint32_t levels, uint32_t tile, bool volume_texture,
TileSizeAlign& total_size) {
void TileGetTextureTotalSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
uint32_t depth, uint32_t levels, Prospero::TileMode tile,
bool volume_texture, TileSizeAlign& total_size) {
EXIT_NOT_IMPLEMENTED(depth == 0);
if (volume_texture && static_cast<Prospero::TileMode>(tile) != Prospero::TileMode::kLinear) {
if (volume_texture && tile != Prospero::TileMode::kLinear) {
TileSurfaceLayout layout {};
const TileSurfaceDescription description {
format, tile, TileSurfaceDimension::Dim3D, width, height, depth, levels, 1};
if (!TileGetTiledTextureLayout(description, layout)) {
EXIT("unsupported 3D texture layout: format=%u tile=%u extent=%ux%ux%u levels=%u\n",
format, tile, width, height, depth, levels);
static_cast<uint32_t>(format), static_cast<uint32_t>(tile), width, height, depth,
levels);
}
EXIT_NOT_IMPLEMENTED(layout.total_size > UINT32_MAX);
total_size.size = static_cast<uint32_t>(layout.total_size);
@@ -1337,9 +1339,10 @@ void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, u
total_size.size = static_cast<uint32_t>(total);
}
uint32_t TileGetTexturePitch(uint32_t format, uint32_t width, uint32_t tile) {
uint32_t TileGetTexturePitch(Prospero::BufferFormat format, uint32_t width,
Prospero::TileMode tile) {
uint32_t pitch = width;
switch (static_cast<Prospero::TileMode>(tile)) {
switch (tile) {
case Prospero::TileMode::kLinear: {
TileTextureElementLayout element {};
if (TileGetTextureElementLayout(format, element) && element.texel_width == 1 &&
@@ -1348,6 +1351,7 @@ uint32_t TileGetTexturePitch(uint32_t format, uint32_t width, uint32_t tile) {
}
break;
}
case Prospero::TileMode::kStandard4KB:
case Prospero::TileMode::kStandard64KB:
case Prospero::TileMode::kPrt:
case Prospero::TileMode::kDepth:
+23 -21
View File
@@ -3,6 +3,7 @@
#include "common/abi.h"
#include "common/common.h"
#include "graphics/guest_gpu/gpu_defs.h"
namespace Libs::Graphics {
@@ -65,14 +66,14 @@ enum class TileSurfaceDimension : uint32_t {
};
struct TileSurfaceDescription {
uint32_t format = 0;
uint32_t tile_mode = 0;
TileSurfaceDimension dimension = TileSurfaceDimension::Dim2D;
uint32_t width = 0;
uint32_t height = 0;
uint32_t depth = 1;
uint32_t levels = 1;
uint32_t layers = 1;
Prospero::BufferFormat format = Prospero::BufferFormat::kInvalid;
Prospero::TileMode tile_mode = Prospero::TileMode::kLinear;
TileSurfaceDimension dimension = TileSurfaceDimension::Dim2D;
uint32_t width = 0;
uint32_t height = 0;
uint32_t depth = 1;
uint32_t levels = 1;
uint32_t layers = 1;
};
// Width/height fields are measured in addressable elements, not texture texels.
@@ -98,8 +99,8 @@ struct TileSurfaceLayout {
bool TileGetBlockLayout(TileBlockFamily family, uint32_t bytes_per_element,
TileBlockLayout& layout);
bool TileGetTextureElementLayout(uint32_t format, TileTextureElementLayout& layout);
bool TileGetTextureBlockLayout(uint32_t format, uint32_t tile, bool volume,
bool TileGetTextureElementLayout(Prospero::BufferFormat format, TileTextureElementLayout& layout);
bool TileGetTextureBlockLayout(Prospero::BufferFormat format, Prospero::TileMode tile, bool volume,
TileTextureBlockLayout& layout);
bool TileGetTiledTextureLayout(const TileSurfaceDescription& description,
TileSurfaceLayout& layout);
@@ -110,10 +111,10 @@ bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t b
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
uint32_t block_z, uint32_t& byte_offset);
bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t z_format,
uint32_t stencil_format, bool htile, TileSizeAlign& stencil_size,
TileSizeAlign& htile_size, TileSizeAlign& depth_size,
uint32_t num_fragments_log2 = 0);
bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch,
Prospero::DepthFormat z_format, Prospero::StencilFormat stencil_format,
bool htile, TileSizeAlign& stencil_size, TileSizeAlign& htile_size,
TileSizeAlign& depth_size, uint32_t num_fragments_log2 = 0);
uint32_t TileGetRenderTargetPitch(uint32_t width, uint32_t bytes_per_element,
uint32_t num_fragments_log2 = 0);
uint32_t TileGetDepthPitch(uint32_t width, uint32_t bytes_per_element,
@@ -125,13 +126,14 @@ bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t
uint32_t bytes_per_element, uint32_t levels,
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
TilePaddedSize* padded_size);
void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32_t levels,
uint32_t tile, TileSizeAlign* total_size, TileSizeOffset* level_sizes,
TilePaddedSize* padded_size);
void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
uint32_t levels, uint32_t tile, bool volume_texture,
TileSizeAlign& total_size);
uint32_t TileGetTexturePitch(uint32_t format, uint32_t width, uint32_t tile);
void TileGetTextureSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
uint32_t levels, Prospero::TileMode tile, TileSizeAlign* total_size,
TileSizeOffset* level_sizes, TilePaddedSize* padded_size);
void TileGetTextureTotalSize(Prospero::BufferFormat format, uint32_t width, uint32_t height,
uint32_t depth, uint32_t levels, Prospero::TileMode tile,
bool volume_texture, TileSizeAlign& total_size);
uint32_t TileGetTexturePitch(Prospero::BufferFormat format, uint32_t width,
Prospero::TileMode tile);
} // namespace Libs::Graphics
+9 -8
View File
@@ -1,5 +1,6 @@
#include "graphics/host_gpu/pageManager.h"
#include "common/platform/sysDbg.h"
#include "graphics/host_gpu/regionDefinitions.h"
#include "kernel/memory.h"
@@ -52,20 +53,20 @@ constexpr uint32_t READ_WRITE_PROTECTION = PAGE_READWRITE;
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
void* frames[16] {};
const auto frame_count =
CaptureStackBackTrace(0, static_cast<DWORD>(std::size(frames)), frames, nullptr);
const auto image_base = reinterpret_cast<uintptr_t>(GetModuleHandleW(nullptr));
for (uint16_t i = 0; i < frame_count; i++) {
for (int i = 0; i < frame_count; i++) {
const auto address = reinterpret_cast<uintptr_t>(frames[i]);
std::fprintf(stderr, " frame[%u]=0x%016" PRIxPTR " image_rva=0x%016" PRIxPTR "\n", i,
std::fprintf(stderr, " frame[%d]=0x%016" PRIxPTR " image_rva=0x%016" PRIxPTR "\n", i,
address, address >= image_base ? address - image_base : 0);
}
#elif !defined(__APPLE__)
void* frames[16] {};
const int frame_count = ::backtrace(frames, static_cast<int>(std::size(frames)));
#else
::backtrace_symbols_fd(frames, frame_count, STDERR_FILENO);
#endif
#endif
std::fflush(stderr);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
+52 -104
View File
@@ -257,11 +257,7 @@ void BufferCache::InvalidateMemory(uint64_t vaddr, uint64_t size) {
}
void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size, bool is_write) {
if (Gpu::IsGpuThread()) {
ReadMemoryOnGpu(vaddr, size, is_write);
return;
}
if (CommandScheduler::InDeferredOperation()) {
if (!GuestGpu::IsGpuThread() && CommandScheduler::InDeferredOperation()) {
EXIT("unsupported buffer readback from an asynchronous GPU completion, "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
@@ -405,17 +401,14 @@ BufferCache::CachedBuffer& BufferCache::GetOrCreateBuffer(CommandBuffer& command
uint64_t size) {
const auto begin = AlignDown(vaddr);
const auto end = AlignUp(vaddr + size);
auto it = m_buffers.upper_bound(vaddr);
if (it != m_buffers.begin()) {
auto previous = std::prev(it);
if (previous->second->buffer->IsInBounds(vaddr, size)) {
it = previous;
const auto next = m_buffers.upper_bound(vaddr);
if (next != m_buffers.begin()) {
auto owner = std::prev(next);
if (owner->second->buffer->IsInBounds(vaddr, size)) {
owner->second->tick_accessed_last = m_gc_tick;
return *owner->second;
}
}
if (it != m_buffers.end() && it->second->buffer->IsInBounds(vaddr, size)) {
it->second->tick_accessed_last = m_gc_tick;
return *it->second;
}
CacheRange merged {.address = begin, .size = end - begin};
using Iterator = decltype(m_buffers.begin());
@@ -435,21 +428,6 @@ BufferCache::CachedBuffer& BufferCache::GetOrCreateBuffer(CommandBuffer& command
overlaps.push_back(candidate);
}
}
for (const auto overlap: overlaps) {
auto& old = *overlap->second;
std::vector<std::pair<uint64_t, uint64_t>> uploads;
m_memory_tracker.ForEachUploadRange(
old.vaddr, old.size, false,
[&](uint64_t address, uint64_t bytes) noexcept {
uploads.emplace_back(address, bytes);
},
[&]() noexcept {
for (const auto& [address, bytes]: uploads) {
Upload(command, *old.buffer, old.buffer->Offset(address),
reinterpret_cast<const void*>(address), bytes);
}
});
}
auto cached = std::make_unique<CachedBuffer>();
cached->vaddr = merged.address;
@@ -473,6 +451,24 @@ BufferCache::CachedBuffer& BufferCache::GetOrCreateBuffer(CommandBuffer& command
return *m_buffers.emplace(cached->vaddr, std::move(cached)).first->second;
}
bool BufferCache::SynchronizeBuffer(CommandBuffer& command, Buffer& buffer, uint64_t vaddr,
uint64_t size, bool is_written, bool is_texel_buffer) {
std::vector<std::pair<uint64_t, uint64_t>> uploads;
m_memory_tracker.ForEachUploadRange(
vaddr, size, is_written,
[&](uint64_t address, uint64_t bytes) noexcept { uploads.emplace_back(address, bytes); },
[&]() noexcept {
for (const auto& [address, bytes]: uploads) {
Upload(command, buffer, buffer.Offset(address),
reinterpret_cast<const void*>(address), bytes);
}
});
if (is_texel_buffer && !is_written) {
return SynchronizeBufferFromImage(buffer, vaddr, size);
}
return false;
}
BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size,
bool is_written, bool is_read, bool is_formatted) {
if (command.IsInvalid() || command.IsExecute()) {
@@ -504,25 +500,14 @@ BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr,
(void)m_texture_cache.InvalidateMemoryFromGPU(vaddr, size, true);
}
auto& cached = GetOrCreateBuffer(command, vaddr, size);
std::vector<std::pair<uint64_t, uint64_t>> uploads;
m_memory_tracker.ForEachUploadRange(
vaddr, size, is_written,
[&](uint64_t address, uint64_t bytes) noexcept { uploads.emplace_back(address, bytes); },
[&]() noexcept {
for (const auto& [address, bytes]: uploads) {
Upload(command, *cached.buffer, cached.buffer->Offset(address),
reinterpret_cast<const void*>(address), bytes);
}
});
auto& cached = GetOrCreateBuffer(command, vaddr, size);
(void)SynchronizeBuffer(command, *cached.buffer, vaddr, size, is_written,
is_formatted && is_read);
if (is_written) {
m_gpu_modified_ranges.Add(vaddr, size);
}
auto owner = cached.buffer;
uint64_t offset = cached.buffer->Offset(vaddr);
if (is_formatted && is_read && !is_written) {
(void)SynchronizeBufferFromImage(*owner, vaddr, size);
}
return {owner, owner->Handle(), offset};
}
@@ -542,13 +527,13 @@ ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t siz
size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("BufferCache: invalid image source\n");
}
auto find_owner = [&]() {
auto find_owner = [&]() -> CachedBuffer* {
auto owner = m_buffers.upper_bound(vaddr);
if (owner == m_buffers.begin()) {
return m_buffers.end();
return nullptr;
}
--owner;
return owner->second->buffer->IsInBounds(vaddr, size) ? owner : m_buffers.end();
return owner->second->buffer->IsInBounds(vaddr, size) ? owner->second.get() : nullptr;
};
{
@@ -559,85 +544,47 @@ ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t siz
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, vaddr, size,
"image source");
auto owner = find_owner();
if (has_dirty_buffer_source && owner == m_buffers.end()) {
CacheRange merged {.address = AlignDown(vaddr),
.size = AlignUp(vaddr + size) - AlignDown(vaddr)};
using Iterator = decltype(m_buffers.begin());
std::vector<Iterator> overlaps;
auto first = m_buffers.lower_bound(merged.address);
if (first != m_buffers.begin()) {
auto previous = std::prev(first);
if (ResolveOverlap(merged, {previous->second->vaddr, previous->second->size})) {
first = previous;
}
}
for (auto candidate = first; candidate != m_buffers.end(); ++candidate) {
if (candidate->first >= merged.address + merged.size) {
break;
}
if (ResolveOverlap(merged, {candidate->second->vaddr, candidate->second->size})) {
overlaps.push_back(candidate);
}
}
if (overlaps.empty()) {
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");
}
auto cached = std::make_unique<CachedBuffer>();
cached->vaddr = merged.address;
cached->size = merged.size;
cached->tick_accessed_last = m_gc_tick;
cached->buffer =
std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::DeviceLocal,
merged.address, AllFlags, merged.size);
for (const auto overlap: overlaps) {
const auto& old = *overlap->second;
cached->buffer->CopyFrom(m_scheduler.Current(), *old.buffer, 0,
old.vaddr - cached->vaddr, old.size);
m_scheduler.Current().RetainResourceUntilFence(old.buffer);
}
for (const auto overlap: overlaps) {
if (overlap->second->size > m_total_used_memory) {
EXIT("BufferCache: allocation accounting underflow\n");
}
m_total_used_memory -= overlap->second->size;
m_buffers.erase(overlap);
}
m_total_used_memory += cached->size;
owner = m_buffers.emplace(cached->vaddr, std::move(cached)).first;
if (!owner->second->buffer->IsInBounds(vaddr, size)) {
auto& cached = GetOrCreateBuffer(m_scheduler.Current(), vaddr, size);
owner = &cached;
const auto resolved = m_buffers.find(cached.vaddr);
if (resolved == m_buffers.end() || resolved->second.get() != &cached ||
!cached.buffer->IsInBounds(vaddr, size)) {
EXIT("BufferCache: merged image source does not contain the requested range\n");
}
}
if (owner != m_buffers.end() && !cpu_modified &&
(!gpu_modified || has_dirty_buffer_source)) {
owner->second->tick_accessed_last = m_gc_tick;
return {owner->second->buffer.get(), owner->second->buffer->Offset(vaddr)};
if (owner != nullptr && !cpu_modified && (!gpu_modified || has_dirty_buffer_source)) {
owner->tick_accessed_last = m_gc_tick;
return {owner->buffer.get(), owner->buffer->Offset(vaddr)};
}
if (has_dirty_buffer_source && owner == m_buffers.end()) {
if (has_dirty_buffer_source && owner == nullptr) {
EXIT("BufferCache: GPU-dirty image source could not resolve its native owner\n");
}
}
auto [staging, stage_offset] = m_staging_buffer.Map(size, 16);
if (staging == nullptr || !Libs::LibKernel::Memory::TryReadBacking(vaddr, staging, size)) {
if (staging == nullptr || (!Libs::LibKernel::Memory::TryReadBacking(vaddr, staging, size) &&
!Libs::LibKernel::Memory::TryReadPrtBacking(vaddr, staging, size))) {
EXIT("BufferCache: failed to read mapped guest image backing\n");
}
m_staging_buffer.Commit();
const auto dirty = m_gpu_modified_ranges.Intersections(vaddr, size);
const bool has_dirty_buffer_source = !dirty.empty();
auto owner = find_owner();
if (has_dirty_buffer_source && owner == m_buffers.end()) {
auto* owner = find_owner();
if (has_dirty_buffer_source && owner == nullptr) {
EXIT("BufferCache: GPU-dirty image source lost its native owner\n");
}
if (owner == m_buffers.end() ||
if (owner == nullptr ||
(m_memory_tracker.IsRegionGpuModified(vaddr, size) && !has_dirty_buffer_source)) {
return {&m_staging_buffer, stage_offset};
}
auto& cached = *owner->second;
auto& cached = *owner;
cached.tick_accessed_last = m_gc_tick;
std::vector<std::pair<uint64_t, uint64_t>> uploads;
m_memory_tracker.ForEachUploadRange(
@@ -724,10 +671,11 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
if ((dst_memory && dst_vaddr == 0) || (src_memory && src_vaddr == 0) || size == 0 ||
((dst_gds || src_gds) && ((dst_vaddr | src_vaddr | size) & 3u) != 0) ||
size > UINT64_MAX - dst_vaddr || size > UINT64_MAX - src_vaddr || (dst_gds && src_gds) ||
(dst_gds == src_gds && src_vaddr < dst_vaddr + size && dst_vaddr < src_vaddr + size) ||
(dst_gds && (dst_vaddr > m_gds_buffer.Size() || size > m_gds_buffer.Size() - dst_vaddr)) ||
(src_gds && (src_vaddr > m_gds_buffer.Size() || size > m_gds_buffer.Size() - src_vaddr))) {
EXIT("BufferCache: invalid or overlapping copy range\n");
EXIT("BufferCache: invalid copy range, src=0x%016" PRIx64 " dst=0x%016" PRIx64
" 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 =
+2
View File
@@ -89,6 +89,8 @@ private:
const void* source, uint64_t size);
[[nodiscard]] CachedBuffer& GetOrCreateBuffer(CommandBuffer& command, uint64_t vaddr,
uint64_t size);
[[nodiscard]] bool SynchronizeBuffer(CommandBuffer& command, Buffer& buffer, uint64_t vaddr,
uint64_t size, bool is_written, bool is_texel_buffer);
[[nodiscard]] bool SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uint64_t size);
[[nodiscard]] std::vector<DownloadRange> RecordDownloads(std::span<const DownloadCopy> copies);
void PublishDownloads(std::span<const DownloadRange> downloads);
+3 -3
View File
@@ -13,7 +13,7 @@
namespace Libs::Graphics {
class CommandScheduler;
class Gpu;
class GuestGpu;
class GpuResourceManager {
public:
@@ -23,7 +23,7 @@ public:
[[nodiscard]] BufferCache& GetBufferCache() { return m_buffer_cache; }
[[nodiscard]] TextureCache& GetTextureCache() { return m_texture_cache; }
void SetGpu(Gpu* gpu) noexcept { m_gpu = gpu; }
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);
@@ -39,7 +39,7 @@ private:
TextureCache m_texture_cache;
mutable std::shared_mutex m_mapped_ranges_mutex;
RangeSet m_mapped_ranges;
Gpu* m_gpu = nullptr;
GuestGpu* m_gpu = nullptr;
};
} // namespace Libs::Graphics
+146 -93
View File
@@ -22,7 +22,7 @@
#include <cstring>
#include <limits>
#include <mutex>
#include <set>
#include <span>
#include <tuple>
#include <vulkan/vulkan_format_traits.hpp>
@@ -252,7 +252,7 @@ void TextureCache::DeleteImage(ImageId id) {
EXIT("TextureCache: deleting a GPU-modified image without resolving its contents\n");
}
m_download_images.erase(id);
if (owner->info.metadata.kind == ImageMetadataKind::Htile) {
if (owner->info.HasMetadata()) {
m_surface_metas.erase(owner->info.metadata.range.address);
}
UnregisterImage(id);
@@ -274,25 +274,9 @@ void TextureCache::DeleteImage(ImageId id) {
}
}
void TextureCache::DeleteImages(std::span<const ImageId> ids,
std::optional<ImageId> native_source) {
std::set<std::pair<uint32_t, uint32_t>> unique;
for (const auto id: ids) {
if (!id || !unique.emplace(id.index, id.generation).second) {
continue;
}
auto owner = ResolveOwner(id);
if (owner == nullptr) {
continue;
}
if (native_source == id) {
ClearGpuModified(id);
} else if (owner->IsGpuModified()) {
DownloadImage(id);
ClearGpuModified(id);
}
DeleteImage(id);
}
void TextureCache::FreeImage(ImageId id) {
ClearGpuModified(id);
DeleteImage(id);
}
void TextureCache::RetainImage(CommandBuffer& command, ImageId id) {
@@ -316,6 +300,14 @@ void TextureCache::TouchImage(Image& image) {
}
}
void TextureCache::MarkAsMaybeDirty(ImageId id, Image& image) {
image.MarkMaybeCpuDirty();
if (image.NeedsMaybeCpuHash()) {
image.SetMaybeCpuHash(image.HashGuestEdges());
}
UntrackImage(id);
}
void TextureCache::TrackImage(ImageId id) {
auto& image = ResolveImage(id);
if (!image.registered) {
@@ -399,11 +391,7 @@ void TextureCache::UntrackImageHead(ImageId id) {
const auto size = address - begin;
image.track_addr = address;
if (image.track_addr == image.track_addr_end) {
image.MarkMaybeCpuDirty();
if (image.NeedsMaybeCpuHash()) {
image.SetMaybeCpuHash(image.HashGuestEdges());
}
UntrackImage(id);
MarkAsMaybeDirty(id, image);
}
if (size != 0) {
m_page_manager.UpdatePageWatchers<false>(begin, size);
@@ -420,11 +408,7 @@ void TextureCache::UntrackImageTail(ImageId id) {
const auto size = end - address;
image.track_addr_end = address;
if (image.track_addr == image.track_addr_end) {
image.MarkMaybeCpuDirty();
if (image.NeedsMaybeCpuHash()) {
image.SetMaybeCpuHash(image.HashGuestEdges());
}
UntrackImage(id);
MarkAsMaybeDirty(id, image);
}
if (size != 0) {
m_page_manager.UpdatePageWatchers<false>(address, size);
@@ -516,7 +500,7 @@ ImageId TextureCache::GetNullImage(const ImageDesc& desc) {
info.pitch = 1;
info.bytes_per_block = std::max(desc.info.bytes_per_block, 1u);
info.samples = 1;
info.tile_mode = Prospero::GpuEnumValue(Prospero::TileMode::kLinear);
info.tile_mode = Prospero::TileMode::kLinear;
info.mip_layout[0] = {0, info.bytes_per_block, 1, 1};
const auto id = InsertImage(info);
m_null_images.emplace(format, id);
@@ -724,7 +708,6 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
if (cached.binding.is_bound || cached.binding.is_target) {
cached.binding.needs_rebind = true;
}
bool copied = false;
if (cached.backing.samples == replacement.backing.samples) {
const bool copy_supported =
cached.backing.samples == 1 || cached.backing.format == replacement.backing.format ||
@@ -732,7 +715,6 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
ImageViewOps::FormatsCompatible(cached.backing.format, replacement.backing.format));
if (copy_supported) {
CopyImage(replacement_id, cached_id);
copied = true;
} else {
LOGF_COLOR(Log::Color::BrightYellow,
"TextureCache: unsupported cross-format multisample depth copy\n");
@@ -749,18 +731,12 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested, BindingTyp
RetainImage(command, cached_id);
RetainImage(command, replacement_id);
CommitGpuWrite(replacement);
copied = true;
} else {
LOGF_COLOR(Log::Color::BrightYellow,
"TextureCache: unsupported unequal-sample depth overlap copy (%u -> %u)\n",
cached.backing.samples, replacement.backing.samples);
}
if (copied) {
DeleteImages(std::array {cached_id}, cached_id);
} else {
ClearGpuModified(cached_id);
DeleteImage(cached_id);
}
FreeImage(cached_id);
return replacement_id;
}
@@ -782,7 +758,7 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
if (requested.BlockExtent() != cached.info.BlockExtent() ||
requested_block != cached_block) {
if (safe_to_delete) {
DeleteImages(std::array {cached_id}, cached_id);
FreeImage(cached_id);
}
return {merged_id};
}
@@ -799,7 +775,7 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
}
if (requested.tile_mode != cached.info.tile_mode) {
if (safe_to_delete) {
DeleteImages(std::array {cached_id}, cached_id);
FreeImage(cached_id);
}
return {merged_id};
}
@@ -822,7 +798,8 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
requested.data.address, requested.resources.levels, requested.resources.layers,
cached.info.resources.levels, cached.info.resources.layers, requested.data.size,
cached.info.data.size, static_cast<uint32_t>(requested.type),
static_cast<uint32_t>(cached.info.type), requested.tile_mode, cached.info.tile_mode);
static_cast<uint32_t>(cached.info.type), static_cast<uint32_t>(requested.tile_mode),
static_cast<uint32_t>(cached.info.tile_mode));
}
if (requested.data.address > cached.info.data.address) {
@@ -834,7 +811,7 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
}
}
if (safe_to_delete) {
DeleteImages(std::array {cached_id}, cached_id);
FreeImage(cached_id);
}
return {};
}
@@ -848,13 +825,13 @@ TextureCache::OverlapResult TextureCache::ResolveOverlap(const ImageInfo& reques
if (merged_id) {
ResolveImage(merged_id).binding.is_target = true;
}
DeleteImages(std::array {cached_id}, cached_id);
FreeImage(cached_id);
return {merged_id};
}
if (merged_id) {
CopyImageMip(merged_id, cached_id, static_cast<uint32_t>(mip),
static_cast<uint32_t>(layer));
DeleteImages(std::array {cached_id}, cached_id);
FreeImage(cached_id);
}
}
}
@@ -873,7 +850,7 @@ ImageId TextureCache::ExpandImage(const ImageInfo& info, ImageId source_id) {
InitializeImage(expanded_id,
ImageDesc {.info = info, .view_info = {}, .type = UploadBinding(source)});
CopyImage(expanded_id, source_id);
DeleteImages(std::array {source_id}, source_id);
FreeImage(source_id);
return expanded_id;
}
@@ -905,7 +882,7 @@ TextureCache::ColorTransferPlan
TextureCache::BuildColorTransfer(const Image& image, BindingType binding,
TransferDirection direction) const {
const auto& info = image.info;
uint32_t format = info.guest_format;
auto format = info.guest_format;
uint32_t layers = info.TransferLayers();
bool volume = info.IsVolume();
bool allow_depth_tile = direction == TransferDirection::Upload;
@@ -923,7 +900,7 @@ TextureCache::BuildColorTransfer(const Image& image, BindingType binding,
EXIT("TextureCache: invalid color-attachment upload\n");
}
format = ImageOps::RenderTargetTransferFormat(info.bytes_per_block);
allow_depth_tile = false;
allow_depth_tile = true;
plan.swap_bgra16 = info.bgra16;
owner = "RenderTarget";
break;
@@ -949,7 +926,7 @@ TextureCache::BuildColorTransfer(const Image& image, BindingType binding,
format = binding == BindingType::RenderTarget
? ImageOps::RenderTargetTransferFormat(info.bytes_per_block)
: info.guest_format;
allow_depth_tile = binding == BindingType::Storage;
allow_depth_tile = binding == BindingType::Storage || binding == BindingType::RenderTarget;
plan.swap_bgra16 = info.bgra16;
}
@@ -957,8 +934,7 @@ TextureCache::BuildColorTransfer(const Image& image, BindingType binding,
info.resources.levels, layers, info.tile_mode,
info.data.size, allow_depth_tile, volume, owner);
plan.regions = TextureBuildImageCopies(plan.layout);
plan.tiled = static_cast<Prospero::TileMode>(plan.layout.surface.description.tile_mode) !=
Prospero::TileMode::kLinear;
plan.tiled = plan.layout.surface.description.tile_mode != Prospero::TileMode::kLinear;
if (plan.tiled) {
if (!TextureBuildGpuTileInfos(info.data.size, plan.regions, plan.layout,
info.resources.levels, plan.tiles)) {
@@ -1007,7 +983,7 @@ void TextureCache::UploadImage(Image& image, const ImageDesc& desc, Buffer& sour
" size=0x%016" PRIx64 " format=%u tile=%u family=%u extent=%ux%ux%u "
"pitch=%u levels=%u layers=%u samples=%u\n",
static_cast<uint32_t>(desc.type), info.data.address, info.data.size,
info.guest_format, info.tile_mode,
static_cast<uint32_t>(info.guest_format), static_cast<uint32_t>(info.tile_mode),
static_cast<uint32_t>(plan.layout.surface.texture.block.family), info.extent.width,
info.extent.height, info.extent.depth, info.pitch, info.resources.levels,
info.resources.layers, info.samples);
@@ -1045,7 +1021,7 @@ void TextureCache::UploadImage(Image& image, const ImageDesc& desc, Buffer& sour
copy.bufferImageHeight = info.extent.height;
copy.imageSubresource = {vk::ImageAspectFlagBits::eDepth, 0, layer, 1};
copy.imageExtent = {info.extent.width, info.extent.height, 1};
if (static_cast<Prospero::TileMode>(info.tile_mode) != Prospero::TileMode::kLinear) {
if (info.tile_mode != Prospero::TileMode::kLinear) {
tiles.push_back({block.family, block.bytes_per_element, offset, full_slice_size, offset,
full_slice_size, 0, info.extent.width, info.extent.height, 1,
info.pitch});
@@ -1192,11 +1168,8 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
FindImagesInRegion(desc.info.data.address, desc.info.data.size, false);
for (const auto id: candidates) {
const auto owner = ResolveOwner(id);
if (owner == nullptr) {
continue;
}
if (SameBacking(owner->info, desc.info, exact_format)) {
const auto& image = ResolveImage(id);
if (SameBacking(image.info, desc.info, exact_format)) {
result = id;
}
}
@@ -1205,12 +1178,8 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
int32_t view_layer = -1;
if (!result) {
for (const auto candidate: candidates) {
view_mip = -1;
view_layer = -1;
const auto owner = ResolveOwner(candidate);
if (owner == nullptr) {
continue;
}
view_mip = -1;
view_layer = -1;
const auto& merged_info = result ? ResolveImage(result).info : desc.info;
const auto overlap = ResolveOverlap(merged_info, desc.type, candidate, result);
if (overlap.image) {
@@ -1226,7 +1195,8 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
if (exact_format && resolved.info.pixel_format != desc.info.pixel_format) {
result = {};
} else if (resolved.info.resources < desc.info.resources) {
result = ExpandImage(desc.info, result);
FreeImage(result);
result = {};
}
}
if (!result) {
@@ -1357,6 +1327,19 @@ vk::ImageView TextureCache::FindRenderTarget(ImageId id, const ImageDesc& desc)
image.MarkGpuModified();
image.usage.render_target = true;
RefreshImage(id, desc);
// DCC uses a separate metadata allocation. Register it when the color target is bound,
// matching the point where CMask/FMask will be registered. Preserve a PendingDcc entry
// because the metadata fill may have run before this bind.
if (desc.info.metadata.kind == ImageMetadataKind::Dcc) {
image.info.metadata = desc.info.metadata;
auto [metadata, inserted] = m_surface_metas.try_emplace(
desc.info.metadata.range.address, MetaDataInfo {.type = MetaDataInfo::Type::Dcc});
if (!inserted && metadata->second.type == MetaDataInfo::Type::PendingDcc) {
metadata->second.type = MetaDataInfo::Type::Dcc;
} else if (!inserted && metadata->second.type != MetaDataInfo::Type::Dcc) {
EXIT("TextureCache: color target reuses non-DCC metadata\n");
}
}
CommitGpuWrite(image);
TrackImageDownloadLocked(id, image);
const auto view = image.FindView(desc.view_info);
@@ -1379,8 +1362,20 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
RefreshImage(id, desc);
if (desc.info.HasMetadata()) {
image.info.metadata = desc.info.metadata;
m_surface_metas.try_emplace(desc.info.metadata.range.address,
MetaDataInfo {.clear_mask = image.info.htile_clear_mask});
auto [metadata, inserted] =
m_surface_metas.try_emplace(desc.info.metadata.range.address,
MetaDataInfo {.type = MetaDataInfo::Type::HTile,
.clear_mask = image.info.htile_clear_mask});
if (!inserted && metadata->second.type == MetaDataInfo::Type::PendingDcc) {
// PendingDcc fills use DCC-specific encoding. Do not reinterpret a speculative
// DCC/buffer fill as HTile state if the address is later classified as depth metadata.
metadata->second.type = MetaDataInfo::Type::HTile;
metadata->second.clear_mask = image.info.htile_clear_mask;
metadata->second.fill_value = 0xffffffffu;
metadata->second.fill_size = 0;
} else if (!inserted && metadata->second.type != MetaDataInfo::Type::HTile) {
EXIT("TextureCache: depth target reuses non-HTile metadata\n");
}
}
CommitGpuWrite(image);
if (desc.info.HasStencil()) {
@@ -1788,17 +1783,17 @@ bool TextureCache::InvalidateMemoryFromGPU(uint64_t address, uint64_t size,
CacheLock lock(*this, m_lock);
bool found = false;
for (const auto id: FindImagesInRegion(address, size, true)) {
auto owner = ResolveOwner(id);
if (owner == nullptr || owner->depth_id || !owner->Overlaps(address, size)) {
auto& image = ResolveImage(id);
if (image.depth_id || !image.Overlaps(address, size)) {
continue;
}
if (owner->IsGpuModified()) {
if (image.IsGpuModified()) {
if (!formatted_buffer_write) {
EXIT("TextureCache: buffer write aliases GPU-modified image\n");
}
ClearGpuModified(id);
image.ClearGpuModified();
}
owner->MarkBufferModified();
image.MarkBufferModified();
found = true;
}
return found;
@@ -1811,13 +1806,13 @@ TextureCache::RegionInfo TextureCache::QueryRegion(uint64_t address, uint64_t si
}
CacheLock lock(*this, m_lock);
for (const auto id: FindImagesInRegion(address, size, true)) {
auto owner = ResolveOwner(id);
if (owner == nullptr || owner->depth_id || !owner->Overlaps(address, size, true)) {
const auto& image = ResolveImage(id);
if (image.depth_id) {
continue;
}
result.image_pages = true;
result.image_bytes |= owner->Overlaps(address, size);
result.gpu_image_bytes |= owner->GpuOverlaps(address, size);
result.image_bytes |= image.Overlaps(address, size);
result.gpu_image_bytes |= image.GpuOverlaps(address, size);
}
return result;
}
@@ -1842,11 +1837,7 @@ void TextureCache::InvalidateCpuAliases(uint64_t address, uint64_t size) {
} else if (image_begin < page_begin) {
UntrackImageTail(id);
} else {
owner->MarkMaybeCpuDirty();
if (owner->NeedsMaybeCpuHash()) {
owner->SetMaybeCpuHash(owner->HashGuestEdges());
}
UntrackImage(id);
MarkAsMaybeDirty(id, *owner);
}
}
}
@@ -1860,33 +1851,90 @@ void TextureCache::ClearGpuModified(ImageId id) {
}
bool TextureCache::IsMeta(uint64_t address) {
CacheLock lock(*this, m_lock);
return m_surface_metas.contains(address);
}
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice) {
CacheLock lock(*this, m_lock);
const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end()) {
return found != m_surface_metas.end() && found->second.type != MetaDataInfo::Type::PendingDcc;
}
bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice, uint32_t* fill_value) {
CacheLock lock(*this, m_lock);
const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
slice >= 32) {
return false;
}
if (fill_value != nullptr) {
*fill_value = found->second.fill_value;
}
return (found->second.clear_mask & (1u << slice)) != 0;
}
bool TextureCache::ClearMeta(uint64_t address) {
CacheLock lock(*this, m_lock);
const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end()) {
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
found->second.type == MetaDataInfo::Type::Dcc) {
// Preserve the broad metadata-clear operation for CMask/FMask/HTile. DCC requires a
// validated fill value, so an arbitrary compute write must not clear it.
return false;
}
found->second.clear_mask = UINT32_MAX;
return true;
}
bool TextureCache::TryConsumeDccFill(uint64_t address, uint64_t size, uint32_t fill_value) {
if (!GuestRange {address, size}.Valid()) {
EXIT("TextureCache: invalid DCC fill range\n");
}
// DCC fills use a repeated byte code. Require all four bytes of the detected dword to agree,
// and mark only recognized deferred-clear encodings as logically clear.
const auto dcc_clear_mask = [fill_value] {
const auto code = static_cast<uint8_t>(fill_value);
if (fill_value != static_cast<uint32_t>(code) * 0x01010101u) {
return 0u;
}
switch (code) {
case 0x00:
case 0x20:
case 0x40:
case 0x80:
case 0xc0: return UINT32_MAX;
default: return 0u;
}
}();
CacheLock lock(*this, m_lock);
auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end()) {
// This dispatch may precede color-target discovery. Retain it in the shared
// metadata map, but PendingDcc remains invisible to IsMeta until registration. Returning
// false lets the guest dispatch execute and initialize memory while the type is uncertain.
m_surface_metas.emplace(address, MetaDataInfo {.type = MetaDataInfo::Type::PendingDcc,
.clear_mask = dcc_clear_mask,
.fill_value = fill_value,
.fill_size = size});
return false;
}
if (found->second.type == MetaDataInfo::Type::PendingDcc) {
found->second.clear_mask = dcc_clear_mask;
found->second.fill_value = fill_value;
found->second.fill_size = size;
return false;
}
if (found->second.type == MetaDataInfo::Type::Dcc) {
found->second.clear_mask = dcc_clear_mask;
found->second.fill_value = fill_value;
found->second.fill_size = size;
return true;
}
// The strict DCC path must not reinterpret a CMask/FMask/HTile allocation as DCC.
return false;
}
bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) {
CacheLock lock(*this, m_lock);
const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end()) {
if (found == m_surface_metas.end() || found->second.type == MetaDataInfo::Type::PendingDcc ||
slice >= 32) {
return false;
}
if (is_clear) {
@@ -1901,8 +1949,13 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
if (!GuestRange {address, size}.Valid()) {
EXIT("TextureCache: invalid unmap range\n");
}
CacheLock lock(*this, m_lock);
auto images = FindImagesInRegion(address, size, false);
CacheLock lock(*this, m_lock);
const auto end = address + size;
for (auto metadata = m_surface_metas.lower_bound(address);
metadata != m_surface_metas.end() && metadata->first < end;) {
metadata = m_surface_metas.erase(metadata);
}
auto images = FindImagesInRegion(address, size, false);
if (!images.empty()) {
m_scheduler.Finish();
}
+27 -14
View File
@@ -13,9 +13,7 @@
#include <compare>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <span>
#include <utility>
#include <vector>
@@ -71,8 +69,12 @@ public:
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
[[nodiscard]] bool IsMeta(uint64_t address);
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice,
uint32_t* fill_value = nullptr);
[[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);
@@ -90,7 +92,17 @@ private:
};
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 this one shadPS4-style 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 };
Type type = Type::PendingDcc;
uint32_t clear_mask = 0;
uint32_t fill_value = 0xffffffffu;
uint64_t fill_size = 0;
};
struct OverlapResult {
@@ -110,17 +122,18 @@ private:
void RegisterImage(ImageId id);
void UnregisterImage(ImageId id);
void DeleteImage(ImageId id);
void DeleteImages(std::span<const ImageId> ids, std::optional<ImageId> native_source = {});
void RetainImage(CommandBuffer& command, ImageId id);
void TouchImage(Image& image);
void TrackImage(ImageId id);
void TrackImageHead(ImageId id);
void TrackImageTail(ImageId id);
void UntrackImage(ImageId id);
void UntrackImageHead(ImageId id);
void UntrackImageTail(ImageId id);
void TrackImageDownload(ImageId id);
void TrackImageDownloadLocked(ImageId id, Image& image);
void FreeImage(ImageId id);
void RetainImage(CommandBuffer& command, ImageId id);
void TouchImage(Image& image);
void TrackImage(ImageId id);
void TrackImageHead(ImageId id);
void TrackImageTail(ImageId id);
void UntrackImage(ImageId id);
void UntrackImageHead(ImageId id);
void UntrackImageTail(ImageId id);
void MarkAsMaybeDirty(ImageId id, Image& image);
void TrackImageDownload(ImageId id);
void TrackImageDownloadLocked(ImageId id, Image& image);
[[nodiscard]] static bool SameBacking(const ImageInfo& cached, const ImageInfo& requested,
bool exact_format);
[[nodiscard]] static BindingType UploadBinding(const Image& image);
@@ -21,6 +21,28 @@ 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;
}
if (!info.metadata_clear_supported) {
info.color_clear_value = {};
}
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
RenderColorInfo& r,
@@ -55,20 +77,22 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
}
// 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.color_clear_value = {};
r.type = RenderColorType::NoColorOutput;
r.desc = {};
r.base_addr = 0;
r.image_id = {};
r.image_view = nullptr;
r.format = vk::Format::eUndefined;
r.extent = {};
r.base_mip_level = 0;
r.base_array_layer = 0;
r.buffer_size = 0;
r.samples = 1;
r.export_mapping = {};
r.color_clear_enable = false;
r.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);
@@ -101,14 +125,15 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
" 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,
rt.attrib3.tile_mode, rt.attrib3.dimension, rt.info.format, rt.info.channel_type,
rt.info.channel_order);
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));
}
}
// CB_COLOR_CONTROL describes the color-buffer operation / ROP3 logic op.
// ROP3 Copy is the normal color write path, not a render-target clear.
// SRGB clear words are still encoded as normalized component values.
// 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;
@@ -129,17 +154,21 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
if (volume && samples != 1) {
EXIT("multisampled 3D render targets are unsupported\n");
}
const uint32_t depth = volume ? rt.attrib3.depth + 1u : 1u;
const bool standard64 =
rt.attrib3.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB);
const uint32_t depth = volume ? rt.attrib3.depth + 1u : 1u;
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;
const bool texture_tile = standard4 || standard64 || depth_tile;
switch (rt.attrib3.tile_mode) {
case Prospero::GpuEnumValue(Prospero::TileMode::kLinear):
case Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB):
case Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget):
case Prospero::TileMode::kLinear:
case Prospero::TileMode::kStandard4KB:
case Prospero::TileMode::kStandard64KB:
case Prospero::TileMode::kDepth:
case Prospero::TileMode::kRenderTarget:
tile = !RenderIsColorTileModeLinear(rt.attrib3.tile_mode);
break;
default: EXIT("unknown tile mode: %u\n", rt.attrib3.tile_mode);
default: EXIT("unknown tile mode: %u\n", static_cast<uint32_t>(rt.attrib3.tile_mode));
}
if (!tile && levels > 1) {
EXIT("linear mipmapped render targets are unsupported\n");
@@ -147,6 +176,9 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
if (samples > 1 && (!tile || levels != 1)) {
EXIT("multisampled render targets require a single-mip tiled surface\n");
}
if (texture_tile && samples != 1) {
EXIT("texture-tiled color render targets do not support multisampling\n");
}
width = rt.attrib2.width + 1;
height = rt.attrib2.height + 1;
@@ -157,31 +189,38 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
EXIT("render-target format has no valid element size\n");
}
const auto transfer_format = ImageOps::RenderTargetTransferFormat(bytes_per_element);
if (standard64 &&
(rt.attrib3.dimension != 1 || rt.attrib3.depth != 0 || levels != 1 ||
rt.view.current_mip_level != 0 || view.base_layer != 0 || view.image_layers != 1 ||
samples != 1 || bytes_per_element != 4 || rt.pitch.pitch_div8_minus1 != 0 ||
(rt.base.addr & 0xffffu) != 0 || rt.info.fmask_compression_enable ||
rt.info.fmask_data_compression_disable || rt.info.fmask_one_frag_mode ||
rt.info.cmask_fast_clear_enable || rt.info.dcc_compression_enable ||
rt.info.cmask_is_linear != 0 || rt.info.cmask_addr_type != 0 || rt.info.alt_tile_mode ||
rt.cmask.addr != 0 || rt.fmask.addr != 0 || rt.dcc_addr.addr != 0 ||
rt.dcc.data_write_on_dcc_clear_to_reg)) {
EXIT("unsupported Standard64KB render target: addr=0x%016" PRIx64
TileTextureBlockLayout texture_tile_layout {};
if (texture_tile &&
(!TileGetTextureBlockLayout(transfer_format, rt.attrib3.tile_mode, volume,
texture_tile_layout) ||
rt.pitch.pitch_div8_minus1 != 0 ||
(rt.base.addr & (texture_tile_layout.block.block_size - 1u)) != 0 ||
rt.info.fmask_compression_enable || rt.info.fmask_data_compression_disable ||
rt.info.fmask_one_frag_mode || rt.info.cmask_fast_clear_enable ||
rt.info.dcc_compression_enable || rt.info.cmask_is_linear != 0 ||
rt.info.cmask_addr_type != 0 || rt.info.alt_tile_mode || rt.cmask.addr != 0 ||
rt.fmask.addr != 0 || rt.dcc_addr.addr != 0 || rt.dcc.data_write_on_dcc_clear_to_reg)) {
EXIT("unsupported texture-tiled render target: addr=0x%016" PRIx64 " tile=%u"
" dimension=%u depth=%u levels=%u layer=%u/%u samples=%u fragments=%u bpe=%u"
" cmask=0x%016" PRIx64 " fmask=0x%016" PRIx64 " dcc=0x%016" PRIx64 "\n",
rt.base.addr, rt.attrib3.dimension, rt.attrib3.depth, levels, view.base_layer,
view.image_layers, rt.attrib.num_samples, rt.attrib.num_fragments, bytes_per_element,
rt.cmask.addr, rt.fmask.addr, rt.dcc_addr.addr);
rt.base.addr, static_cast<uint32_t>(rt.attrib3.tile_mode), rt.attrib3.dimension,
rt.attrib3.depth, levels, view.base_layer, view.image_layers, rt.attrib.num_samples,
rt.attrib.num_fragments, bytes_per_element, rt.cmask.addr, rt.fmask.addr,
rt.dcc_addr.addr);
}
if ((standard64 || depth_tile) && (rt.attrib3.dimension != 1 || rt.attrib3.depth != 0 ||
view.base_layer != 0 || view.image_layers != 1)) {
EXIT("unsupported 64KB texture-tiled render-target view: dimension=%u depth=%u"
" layer=%u/%u\n",
rt.attrib3.dimension, rt.attrib3.depth, view.base_layer, view.image_layers);
}
if (rt.pitch.pitch_div8_minus1 != 0) {
pitch = (rt.pitch.pitch_div8_minus1 + 1u) << 3u;
} else if (tile) {
if (volume) {
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
} else if (standard64) {
pitch = TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float),
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);
}
@@ -207,18 +246,17 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
1};
if (!tile || !TileGetTiledTextureLayout(description, volume_layout)) {
EXIT("unsupported 3D render-target layout: %ux%ux%u levels=%u tile=%u\n", width, height,
depth, levels, rt.attrib3.tile_mode);
depth, levels, static_cast<uint32_t>(rt.attrib3.tile_mode));
}
size = volume_layout.block_slice_size;
backing_size = volume_layout.total_size;
} else if (tile) {
TileSizeAlign layout {};
bool valid_layout = false;
if (standard64) {
TileGetTextureSize(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float), width,
height, levels, rt.attrib3.tile_mode, &layout, mip_sizes,
mip_padded);
valid_layout = layout.size != 0 && layout.align == 65536;
if (texture_tile) {
TileGetTextureSize(transfer_format, width, height, levels, rt.attrib3.tile_mode,
&layout, mip_sizes, mip_padded);
valid_layout = layout.size != 0 && layout.align == texture_tile_layout.block.block_size;
} else {
valid_layout =
levels == 1 ? TileGetRenderTargetSize(width, height, pitch, bytes_per_element,
@@ -278,8 +316,9 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
" extent=%ux%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u"
" fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 " samples=%u tile=%s\n",
rt_slot, rt.base.addr, backing_size, width, height, depth, rt.view.current_mip_level,
view_extent.width, view_extent.height, levels, pitch, rt.info.format,
rt.info.channel_type, rt.info.channel_order, samples, tile ? "tiled" : "linear");
view_extent.width, view_extent.height, levels, pitch,
static_cast<uint32_t>(rt.info.format), static_cast<uint32_t>(rt.info.channel_type),
static_cast<uint32_t>(rt.info.channel_order), samples, tile ? "tiled" : "linear");
}
TextureCache::ImageDesc desc {};
@@ -294,6 +333,13 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
desc.info.bytes_per_block = bytes_per_element;
desc.info.samples = samples;
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;
}
for (uint32_t level = 0; level < levels; level++) {
if (volume) {
const auto& mip = volume_layout.mips[level];
@@ -340,7 +386,7 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
r.samples = samples;
r.export_mapping = target_format.export_mapping;
r.color_clear_enable = false;
r.color_clear_value = {};
ResolveDccClearInfo(r, target_format.format, has_dcc, rt.clear_word0.word0);
BindRenderTarget(r.image_id);
}
@@ -31,7 +31,9 @@ struct RenderColorInfo {
uint32_t target_slot = 0;
uint32_t samples = 1;
Prospero::ColorComponentMapping export_mapping;
bool color_clear_enable = false;
bool color_clear_enable = false;
bool metadata_clear_supported = false;
bool metadata_fixed_clear_supported = false;
vk::ClearColorValue color_clear_value {};
};
+28 -12
View File
@@ -17,10 +17,29 @@
#include <algorithm>
#include <bit>
#include <cstdio>
#include <cstring>
#include <memory>
namespace Libs::Graphics {
namespace {
void ReportVulkanFatal(const char* what, vk::Result result, uint32_t slot, uint64_t submit_seq,
uint32_t debug_op, uint64_t debug_submit, uint32_t arg0, uint32_t arg1,
uint32_t arg2, uint32_t arg3, uint64_t arg4) {
LOGF("%s failed: %s (%d), slot=%u submit_seq=%" 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), slot, submit_seq, debug_op,
debug_submit, arg0, arg1, arg2, arg3, arg4);
std::printf("%s failed: %s (%d), slot=%u submit_seq=%" 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), slot, submit_seq,
debug_op, debug_submit, arg0, arg1, arg2, arg3, arg4);
std::fflush(stdout);
}
} // namespace
FenceResourceRetainer::~FenceResourceRetainer() {
if (!m_resources.empty()) {
EXIT("fence resource retainer destroyed before release\n");
@@ -169,8 +188,9 @@ void CommandBuffer::Execute(const SubmitInfo& submit) {
auto result = graphics.device.resetFences(1, &fence);
if (result != vk::Result::eSuccess) {
LOGF("vkResetFences failed before submit: %s (%d)\n", VulkanToString(result).c_str(),
static_cast<int>(result));
ReportVulkanFatal("vkResetFences (before submit)", result, m_slot->id, m_submit_seq,
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2,
m_debug_arg3, m_debug_arg4);
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
@@ -192,11 +212,9 @@ void CommandBuffer::Execute(const SubmitInfo& submit) {
m_fence_waited = false;
if (result != vk::Result::eSuccess) {
LOGF("vkQueueSubmit failed: %s (%d), slot=%u submit_seq=%" PRIu64
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
VulkanToString(result).c_str(), static_cast<int>(result), m_slot->id, m_submit_seq,
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg4);
ReportVulkanFatal("vkQueueSubmit", result, m_slot->id, m_submit_seq, m_debug_op,
m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg4);
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
}
@@ -213,11 +231,9 @@ void CommandBuffer::WaitForFenceOnly() {
auto device = m_graphics.device;
auto result = device.waitForFences(1, &m_slot->fence, VK_TRUE, UINT64_MAX);
if (result != vk::Result::eSuccess) {
LOGF("vkWaitForFences failed: %s (%d), slot=%u submit_seq=%" PRIu64
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
VulkanToString(result).c_str(), static_cast<int>(result), m_slot->id, m_submit_seq,
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg4);
ReportVulkanFatal("vkWaitForFences", result, m_slot->id, m_submit_seq, m_debug_op,
m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
m_debug_arg4);
}
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
m_fence_waited = true;
+51 -53
View File
@@ -71,14 +71,16 @@ void uc_print(const char* func, const HW::UserConfig& uc) {
LOGF("\t GetPrimType() = 0x%08" PRIx32 "\n"
"\t GetIndexOffset() = 0x%08" PRIx32 "\n"
"\t GetObjectId() = 0x%08" PRIx32 "\n"
"\t primitive_reset = 0x%08" PRIx32 "\n"
"\t primitive_group_size = 0x%04" PRIx16 "\n"
"\t vertex_group_size = 0x%04" PRIx16 "\n"
"\t en_user_vgpr1 = %s\n"
"\t en_user_vgpr2 = %s\n"
"\t en_user_vgpr3 = %s\n",
uc.GetPrimType(), uc.GetIndexOffset(), uc.GetObjectId(), ge_cntl.primitive_group_size,
ge_cntl.vertex_group_size, user_en.vgpr1 ? "true" : "false",
user_en.vgpr2 ? "true" : "false", user_en.vgpr3 ? "true" : "false");
static_cast<uint32_t>(uc.GetPrimType()), uc.GetIndexOffset(), uc.GetObjectId(),
uc.GetPrimitiveResetControl(), ge_cntl.primitive_group_size, ge_cntl.vertex_group_size,
user_en.vgpr1 ? "true" : "false", user_en.vgpr2 ? "true" : "false",
user_en.vgpr3 ? "true" : "false");
}
void uc_check(const HW::UserConfig& uc) {
@@ -141,11 +143,12 @@ std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt)
fmt::format("\t info.cmask_addr_type = 0x{:08x}\n", rt.info.cmask_addr_type));
dst.push_back(fmt::format("\t info.alt_tile_mode = {}\n",
rt.info.alt_tile_mode ? "true" : "false"));
dst.push_back(fmt::format("\t info.format = 0x{:08x}\n", rt.info.format));
dst.push_back(
fmt::format("\t info.channel_type = 0x{:08x}\n", rt.info.channel_type));
dst.push_back(
fmt::format("\t info.channel_order = 0x{:08x}\n", rt.info.channel_order));
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",
@@ -154,10 +157,10 @@ std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt)
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.tile_mode = 0x{:08x}\n", rt.attrib.tile_mode));
dst.push_back(
fmt::format("\t attrib.fmask_tile_mode = 0x{:08x}\n", rt.attrib.fmask_tile_mode));
dst.push_back(fmt::format("\t attrib.tile_mode = 0x{:08x}\n",
static_cast<uint32_t>(rt.attrib.tile_mode)));
dst.push_back(fmt::format("\t attrib.fmask_tile_mode = 0x{:08x}\n",
static_cast<uint32_t>(rt.attrib.fmask_tile_mode)));
dst.push_back(
fmt::format("\t attrib.num_samples = 0x{:08x}\n", rt.attrib.num_samples));
dst.push_back(
@@ -168,8 +171,8 @@ std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt)
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", rt.attrib3.tile_mode));
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.cmask_pipe_aligned = {}\n",
@@ -212,22 +215,22 @@ std::vector<std::string> rt_print(const char* func, const HW::RenderTarget& rt)
return dst;
}
static bool RenderIsColorTileMode(uint32_t tile_mode) {
static bool RenderIsColorTileMode(Prospero::TileMode tile_mode) {
// AGC CxRenderTarget::TileMode shifted by CB_COLOR*_ATTRIB3.COLOR_SW_MODE.
switch (tile_mode) {
case Prospero::GpuEnumValue(Prospero::TileMode::kLinear):
case Prospero::GpuEnumValue(Prospero::TileMode::kStandard256B):
case Prospero::GpuEnumValue(Prospero::TileMode::kStandard4KB):
case Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB):
case Prospero::GpuEnumValue(Prospero::TileMode::kPrt):
case Prospero::GpuEnumValue(Prospero::TileMode::kDepth):
case Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget): return true;
case Prospero::TileMode::kLinear:
case Prospero::TileMode::kStandard256B:
case Prospero::TileMode::kStandard4KB:
case Prospero::TileMode::kStandard64KB:
case Prospero::TileMode::kPrt:
case Prospero::TileMode::kDepth:
case Prospero::TileMode::kRenderTarget: return true;
default: return false;
}
}
bool RenderIsColorTileModeLinear(uint32_t tile_mode) {
return tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kLinear);
bool RenderIsColorTileModeLinear(Prospero::TileMode tile_mode) {
return tile_mode == Prospero::TileMode::kLinear;
}
static bool RenderIsColorDimension(uint32_t dimension) {
@@ -361,7 +364,8 @@ static void RtCheck(const HW::RenderTarget& rt) {
}
}
if (!RenderIsColorTileMode(rt.attrib3.tile_mode)) {
EXIT("unknown PS5 render-target tile mode: 0x%08" PRIx32 "\n", rt.attrib3.tile_mode);
EXIT("unknown PS5 render-target tile mode: 0x%08" PRIx32 "\n",
static_cast<uint32_t>(rt.attrib3.tile_mode));
}
if (!RenderIsColorDimension(rt.attrib3.dimension)) {
EXIT("unknown PS5 render-target dimension: 0x%08" PRIx32 "\n", rt.attrib3.dimension);
@@ -456,13 +460,14 @@ static void ZPrint(const char* func, const HW::DepthRenderTarget& z) {
"\t height = 0x%08" PRIx32 "\n"
"\t size.x_max = 0x%04" PRIx16 "\n"
"\t size.y_max = 0x%04" PRIx16 "\n",
z.z_info.format, z.z_info.num_samples,
Prospero::GpuEnumValue(z.z_info.texture_compatibility),
static_cast<uint32_t>(z.z_info.format), z.z_info.num_samples,
static_cast<uint32_t>(z.z_info.texture_compatibility),
z.z_info.htile_acceleration ? "true" : "false",
z.z_info.expclear_enabled ? "true" : "false",
Prospero::GpuEnumValue(z.z_info.z_compare_base),
static_cast<uint32_t>(z.z_info.z_compare_base),
z.z_info.partially_resident ? "true" : "false", z.z_info.max_mip_level,
z.stencil_info.format, Prospero::GpuEnumValue(z.stencil_info.texture_compatibility),
static_cast<uint32_t>(z.stencil_info.format),
static_cast<uint32_t>(z.stencil_info.texture_compatibility),
z.stencil_info.htile_stencil_disabled ? "true" : "false",
z.stencil_info.expclear_enabled ? "true" : "false",
z.stencil_info.partially_resident ? "true" : "false", z.depth_info.addr5_swizzle_mask,
@@ -491,15 +496,16 @@ static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
EXIT_NOT_IMPLEMENTED(!z.z_info.HasValidTextureCompatibility());
EXIT_NOT_IMPLEMENTED(!z.stencil_info.HasValidTextureCompatibility());
if (z.z_info.format == 0) {
EXIT_NOT_IMPLEMENTED(z.z_info.format != 0);
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 != 0x00000001 && z.z_info.format != 0x00000003);
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) {
@@ -513,16 +519,17 @@ static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
EXIT_NOT_IMPLEMENTED(z.z_info.max_mip_level != 0);
}
if (z.stencil_info.format == 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 != 0x00000001);
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 != 0 || z.stencil_info.format != 0) {
if (z.z_info.format != Prospero::DepthFormat::kInvalid ||
z.stencil_info.format != Prospero::StencilFormat::kInvalid) {
EXIT_NOT_IMPLEMENTED(z.depth_info.addr5_swizzle_mask != 0x00000000);
EXIT_NOT_IMPLEMENTED(z.depth_info.array_mode != 0x00000000);
@@ -557,7 +564,8 @@ static void ZCheck(const HW::DepthRenderTarget& z, const HW::DepthControl& dc,
EXIT_NOT_IMPLEMENTED(z.z_read_base_addr != z.z_write_base_addr);
EXIT_NOT_IMPLEMENTED(z.stencil_read_base_addr != z.stencil_write_base_addr);
EXIT_NOT_IMPLEMENTED(z.z_write_base_addr == 0);
if (z.stencil_info.format != 0 && z.stencil_write_base_addr == 0) {
if (z.stencil_info.format != Prospero::StencilFormat::kInvalid &&
z.stencil_write_base_addr == 0) {
LOGF("\t warning: stencil format is set without a stencil base address, continuing "
"without stencil attachment\n");
}
@@ -609,8 +617,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.IsZClipModeRepresentable() ||
c.user_clip_plane_negate_y || c.clip_disable ||
c.vertex_kill_any || c.user_clip_plane_negate_y || c.clip_disable ||
c.user_clip_plane_cull_only || c.cull_on_clipping_error_disable ||
c.force_viewport_index_from_vs_enable);
}
@@ -634,17 +641,6 @@ static void RcPrint(const char* func, const HW::RenderControl& c) {
static void RcCheck(const HW::RenderControl& c) {
// EXIT_NOT_IMPLEMENTED(c.depth_clear_enable != false);
// EXIT_NOT_IMPLEMENTED(c.stencil_clear_enable != false);
if ((c.resummarize_enable || c.copy_centroid || c.copy_sample != 0)) {
static std::atomic<uint32_t> log_count {0};
if (log_count.fetch_add(1) < 16) {
LOGF("\t warning: unsupported PS5 render-control metadata state, continuing: "
"resummarize=%s copy_centroid=%s copy_sample=%" PRIu8 "\n",
c.resummarize_enable ? "true" : "false", c.copy_centroid ? "true" : "false",
c.copy_sample);
}
return;
}
EXIT_NOT_IMPLEMENTED(c.resummarize_enable != false);
// EXIT_NOT_IMPLEMENTED(c.stencil_compress_disable != false);
// EXIT_NOT_IMPLEMENTED(c.depth_compress_disable != false);
EXIT_NOT_IMPLEMENTED(c.copy_centroid != false);
@@ -1218,8 +1214,9 @@ void hw_check(const RenderCommandBuffer& buffer) {
const auto& mrt = hw.GetRenderTarget(i);
LOGF("\t RT%u addr=0x%010" PRIx64 " mask=0x%x fmt=0x%08" PRIx32
" width=%u height=%u tile=%u\n",
i, mrt.base.addr, (hw.GetRenderTargetMask() >> (i * 4u)) & 0x0fu, mrt.info.format,
mrt.attrib2.width + 1, mrt.attrib2.height + 1, mrt.attrib3.tile_mode);
i, mrt.base.addr, (hw.GetRenderTargetMask() >> (i * 4u)) & 0x0fu,
static_cast<uint32_t>(mrt.info.format), mrt.attrib2.width + 1,
mrt.attrib2.height + 1, static_cast<uint32_t>(mrt.attrib3.tile_mode));
}
}
if (rc.depth_clear_enable && hw.GetDepthClearValue() != 0.0f &&
@@ -1258,9 +1255,10 @@ void hw_print(const RenderCommandBuffer& buffer) {
"\t GetRenderTargetMask() = 0x%08" PRIx32 "\n"
"\t GetDepthClearValue() = %f\n"
"\t GetStencilClearValue() = %" PRIu8 "\n"
"\t GetLineWidth() = %f\n",
"\t GetLineWidth() = %f\n"
"\t primitive_reset_index = 0x%08" PRIx32 "\n",
hw.GetRenderTargetMask(), hw.GetDepthClearValue(), hw.GetStencilClearValue(),
hw.GetLineWidth());
hw.GetLineWidth(), hw.GetPrimitiveResetIndex());
LOGF("%s", Common::Concat(rt_print("RenderTraget:", rt), "").c_str());
+1 -1
View File
@@ -34,7 +34,7 @@ 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);
bool RenderIsColorTileModeLinear(uint32_t tile_mode);
bool RenderIsColorTileModeLinear(Prospero::TileMode tile_mode);
void hw_print(const RenderCommandBuffer& buffer);
void hw_check(const RenderCommandBuffer& buffer);
void LogDrawPhase(const char* draw_name, const char* phase);
@@ -67,9 +67,8 @@ 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) {
return fail == Prospero::GpuEnumValue(Prospero::StencilOp::kReplaceOp) ||
pass == Prospero::GpuEnumValue(Prospero::StencilOp::kReplaceOp) ||
depth_fail == Prospero::GpuEnumValue(Prospero::StencilOp::kReplaceOp);
constexpr auto replace_op = static_cast<uint8_t>(Prospero::StencilOp::kReplaceOp);
return fail == replace_op || pass == replace_op || depth_fail == replace_op;
}
[[nodiscard]] static vk::Format ResolveHostDepthAttachmentFormat(const RenderCommandBuffer& buffer,
@@ -103,15 +102,14 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
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::GpuEnumValue(Prospero::StencilFormat::kInvalid);
const bool depth_active =
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;
const bool stencil_active = has_stencil && (dc.stencil_enable || rc.stencil_clear_enable);
if (!depth_active && !stencil_active) {
@@ -122,9 +120,8 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
DepthFatal("invalid PS5 depth texture-compatibility encoding");
}
const bool attachment_unbound =
z.z_info.format == Prospero::GpuEnumValue(Prospero::DepthFormat::kInvalid) &&
z.stencil_info.format == Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid) &&
z.z_info.num_samples == 0 &&
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 &&
@@ -169,25 +166,24 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
DepthFatal("invalid depth view: base=%u last=%u", z.depth_view.slice_start,
z.depth_view.slice_max);
}
if (rc.resummarize_enable || rc.copy_centroid || rc.copy_sample != 0 ||
z.z_info.expclear_enabled || z.stencil_info.expclear_enabled ||
z.z_info.partially_resident || z.stencil_info.partially_resident ||
z.z_info.max_mip_level != 0 || z.depth_view.current_mip_level != 0 ||
z.depth_info.addr5_swizzle_mask != 0 || z.depth_info.array_mode != 0 ||
z.depth_info.pipe_config != 0 || z.depth_info.bank_width != 0 ||
z.depth_info.bank_height != 0 || z.depth_info.macro_tile_aspect != 0 ||
z.depth_info.num_banks != 0 || z.htile_surface.linear != 0 ||
z.htile_surface.full_cache != 0 || z.htile_surface.htile_uses_preload_win != 0 ||
z.htile_surface.preload != 0 || z.htile_surface.prefetch_width != 0 ||
z.htile_surface.prefetch_height != 0 || z.htile_surface.dst_outside_zero_to_one != 0 ||
z.z_read_base_addr == 0 || z.z_write_base_addr != z.z_read_base_addr ||
(z.z_read_base_addr & 0xffffu) != 0 ||
if (rc.copy_centroid || rc.copy_sample != 0 || z.z_info.expclear_enabled ||
z.stencil_info.expclear_enabled || z.z_info.partially_resident ||
z.stencil_info.partially_resident || z.z_info.max_mip_level != 0 ||
z.depth_view.current_mip_level != 0 || z.depth_info.addr5_swizzle_mask != 0 ||
z.depth_info.array_mode != 0 || z.depth_info.pipe_config != 0 ||
z.depth_info.bank_width != 0 || z.depth_info.bank_height != 0 ||
z.depth_info.macro_tile_aspect != 0 || z.depth_info.num_banks != 0 ||
z.htile_surface.linear != 0 || z.htile_surface.full_cache != 0 ||
z.htile_surface.htile_uses_preload_win != 0 || z.htile_surface.preload != 0 ||
z.htile_surface.prefetch_width != 0 || z.htile_surface.prefetch_height != 0 ||
z.htile_surface.dst_outside_zero_to_one != 0 || z.z_read_base_addr == 0 ||
z.z_write_base_addr != z.z_read_base_addr || (z.z_read_base_addr & 0xffffu) != 0 ||
dc.zfunc > static_cast<uint8_t>(vk::CompareOp::eAlways)) {
DepthFatal("unsupported depth register state");
}
if (has_stencil) {
if (z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::k8UInt) ||
!htile_stencil_compat || z.stencil_read_base_addr == 0 ||
if (z.stencil_info.format != Prospero::StencilFormat::k8UInt || !htile_stencil_compat ||
z.stencil_read_base_addr == 0 ||
z.stencil_write_base_addr != z.stencil_read_base_addr ||
(z.stencil_read_base_addr & 0xffffu) != 0 || z.depth_view.stencil_write_disable) {
DepthFatal("unsupported stencil attachment state");
@@ -229,7 +225,7 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
DepthFatal("no host depth/stencil format supports required usage for %s",
VulkanToString(ideal_format).c_str());
}
const uint32_t guest_format = Prospero::GpuEnumValue(policy->guest_format);
const auto guest_format = policy->guest_format;
const uint32_t bytes = policy->bytes_per_element;
const auto pitch = TileGetDepthPitch(width, bytes, z.z_info.num_samples);
if (z.pitch_height_valid && ((static_cast<uint64_t>(z.pitch_div8_minus1) + 1u) * 8u != pitch ||
@@ -343,7 +339,7 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
desc.info.pitch = pitch;
desc.info.bytes_per_block = bytes;
desc.info.samples = samples;
desc.info.tile_mode = Prospero::GpuEnumValue(Prospero::TileMode::kDepth);
desc.info.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;
@@ -646,13 +646,13 @@ void Validate(const ImageInfo& info) {
}
}
uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element) {
Prospero::BufferFormat RenderTargetTransferFormat(uint32_t bytes_per_element) {
switch (bytes_per_element) {
case 1: return Prospero::GpuEnumValue(Prospero::BufferFormat::k8UNorm);
case 2: return Prospero::GpuEnumValue(Prospero::BufferFormat::k16UNorm);
case 4: return Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float);
case 8: return Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float);
case 16: return Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32_32_32Float);
case 1: return Prospero::BufferFormat::k8UNorm;
case 2: return Prospero::BufferFormat::k16UNorm;
case 4: return Prospero::BufferFormat::k32Float;
case 8: return Prospero::BufferFormat::k16_16_16_16Float;
case 16: return Prospero::BufferFormat::k32_32_32_32Float;
default: EXIT("unsupported render-target element size: %u\n", bytes_per_element);
}
}
+3 -2
View File
@@ -55,6 +55,7 @@ struct ImageBinding {
bool is_target = false;
bool needs_rebind = false;
bool force_general = false;
bool shader_write = false;
};
class Image final {
@@ -188,8 +189,8 @@ private:
namespace ImageOps {
void Validate(const ImageInfo& info);
[[nodiscard]] uint32_t RenderTargetTransferFormat(uint32_t bytes_per_element);
void Validate(const ImageInfo& info);
[[nodiscard]] Prospero::BufferFormat RenderTargetTransferFormat(uint32_t bytes_per_element);
} // namespace ImageOps
@@ -67,14 +67,14 @@ struct ImageInfo {
ImageMetadataInfo metadata;
uint32_t htile_clear_mask = UINT32_MAX;
vk::Format pixel_format = vk::Format::eUndefined;
uint32_t guest_format = 0;
Prospero::BufferFormat guest_format = Prospero::BufferFormat::kInvalid;
Prospero::ImageType type = Prospero::ImageType::kColor2D;
vk::Extent3D extent = {1, 1, 1};
ImageSubresources resources;
uint32_t pitch = 0;
uint32_t bytes_per_block = 0;
uint32_t samples = 1;
uint32_t tile_mode = 0;
Prospero::TileMode tile_mode = Prospero::TileMode::kLinear;
bool bgra16 = false;
std::array<ImageMipInfo, 16> mip_layout {};
@@ -86,9 +86,7 @@ struct ImageInfo {
[[nodiscard]] bool IsBlock() const noexcept {
return Prospero::BlockCompressedBytesPerBlock(guest_format) != 0;
}
[[nodiscard]] bool IsTiled() const noexcept {
return tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kLinear);
}
[[nodiscard]] bool IsTiled() const noexcept { return tile_mode != Prospero::TileMode::kLinear; }
[[nodiscard]] constexpr bool IsVolume() const noexcept {
return type == Prospero::ImageType::kColor3D;
}
@@ -214,7 +212,6 @@ struct DepthFormatPolicy {
Prospero::DepthFormat depth_format;
Prospero::BufferFormat guest_format;
uint32_t bytes_per_element;
vk::Format sampled_view_format;
vk::Format depth_attachment_format;
std::array<vk::Format, 3> stencil_attachment_formats;
};
@@ -223,21 +220,19 @@ inline constexpr std::array<DepthFormatPolicy, 2> DEPTH_FORMAT_POLICIES {{
{Prospero::DepthFormat::kZ16,
Prospero::BufferFormat::k16UNorm,
2,
vk::Format::eR16Unorm,
vk::Format::eD16Unorm,
{vk::Format::eD16UnormS8Uint, vk::Format::eD24UnormS8Uint, vk::Format::eD32SfloatS8Uint}},
{Prospero::DepthFormat::kZ32F,
Prospero::BufferFormat::k32Float,
4,
vk::Format::eR32Sfloat,
vk::Format::eD32Sfloat,
{vk::Format::eD32SfloatS8Uint, vk::Format::eUndefined, vk::Format::eUndefined}},
}};
[[nodiscard]] inline constexpr const DepthFormatPolicy*
FindDepthFormatPolicy(uint32_t depth_format) noexcept {
FindDepthFormatPolicy(Prospero::DepthFormat depth_format) noexcept {
for (const auto& policy: DEPTH_FORMAT_POLICIES) {
if (Prospero::GpuEnumValue(policy.depth_format) == depth_format) {
if (policy.depth_format == depth_format) {
return &policy;
}
}
@@ -245,9 +240,9 @@ FindDepthFormatPolicy(uint32_t depth_format) noexcept {
}
[[nodiscard]] inline constexpr const DepthFormatPolicy*
FindGuestDepthFormatPolicy(uint32_t guest_format) noexcept {
FindGuestDepthFormatPolicy(Prospero::BufferFormat guest_format) noexcept {
for (const auto& policy: DEPTH_FORMAT_POLICIES) {
if (Prospero::GpuEnumValue(policy.guest_format) == guest_format) {
if (policy.guest_format == guest_format) {
return &policy;
}
}
@@ -269,10 +264,11 @@ FindGuestDepthFormatPolicy(uint32_t guest_format) noexcept {
return has_stencil ? policy.stencil_attachment_formats.front() : policy.depth_attachment_format;
}
[[nodiscard]] inline constexpr vk::Format DepthAttachmentFormat(uint32_t depth_format,
uint32_t stencil_format) noexcept {
[[nodiscard]] inline constexpr vk::Format
DepthAttachmentFormat(Prospero::DepthFormat depth_format,
Prospero::StencilFormat stencil_format) noexcept {
bool has_stencil = false;
switch (static_cast<Prospero::StencilFormat>(stencil_format)) {
switch (stencil_format) {
case Prospero::StencilFormat::kInvalid: break;
case Prospero::StencilFormat::k8UInt: has_stencil = true; break;
default: return vk::Format::eUndefined;
@@ -318,26 +314,6 @@ inline bool ImageInfo::IsDepth() const noexcept {
return std::bit_cast<uint32_t>(static_cast<float>(value) / 65535.0f);
}
[[nodiscard]] inline constexpr bool IsSupportedSampledDepthFormat(vk::Format image_format,
uint32_t guest_format,
vk::Format view_format) noexcept {
const auto* policy = FindGuestDepthFormatPolicy(guest_format);
return policy != nullptr && view_format == policy->sampled_view_format &&
(image_format == policy->depth_attachment_format ||
IsStencilAttachmentFormat(*policy, image_format));
}
[[nodiscard]] inline constexpr bool IsSupportedSampledDepthFormat(vk::Format image_format,
vk::Format view_format) noexcept {
for (const auto& policy: DEPTH_FORMAT_POLICIES) {
if (IsSupportedSampledDepthFormat(image_format, Prospero::GpuEnumValue(policy.guest_format),
view_format)) {
return true;
}
}
return false;
}
[[nodiscard]] inline constexpr bool IsSupportedDepthTargetFormat(const ImageInfo& info) {
const auto* policy = FindGuestDepthFormatPolicy(info.guest_format);
return policy != nullptr && info.bytes_per_block == policy->bytes_per_element &&
@@ -383,10 +359,10 @@ CanUseVideoOutNativeWithoutUpload(VideoOutCompression compression, bool render_t
}
struct VideoOutPixelFormatInfo {
vk::Format format = vk::Format::eUndefined;
uint32_t guest_format = 0;
uint32_t bytes_per_element = 0;
bool bgra16 = false;
vk::Format format = vk::Format::eUndefined;
Prospero::BufferFormat guest_format = Prospero::BufferFormat::kInvalid;
uint32_t bytes_per_element = 0;
bool bgra16 = false;
};
struct VideoOutFormatPolicy {
@@ -396,23 +372,17 @@ struct VideoOutFormatPolicy {
inline constexpr std::array<VideoOutFormatPolicy, 6> VIDEO_OUT_FORMAT_POLICIES {{
{0x8000000022000000ull,
{vk::Format::eR8G8B8A8Srgb, Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8Srgb), 4,
false}},
{vk::Format::eR8G8B8A8Srgb, Prospero::BufferFormat::k8_8_8_8Srgb, 4, false}},
{0x8000000000000000ull,
{vk::Format::eB8G8R8A8Srgb, Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8Srgb), 4,
false}},
{vk::Format::eB8G8R8A8Srgb, Prospero::BufferFormat::k8_8_8_8Srgb, 4, false}},
{0x8100000022000000ull,
{vk::Format::eA2B10G10R10UnormPack32,
Prospero::GpuEnumValue(Prospero::BufferFormat::k10_10_10_2UNorm), 4, false}},
{vk::Format::eA2B10G10R10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
{0x8100000000000000ull,
{vk::Format::eA2R10G10B10UnormPack32,
Prospero::GpuEnumValue(Prospero::BufferFormat::k10_10_10_2UNorm), 4, false}},
{vk::Format::eA2R10G10B10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
{0xc001000622000000ull,
{vk::Format::eR16G16B16A16Sfloat,
Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float), 8, false}},
{vk::Format::eR16G16B16A16Sfloat, Prospero::BufferFormat::k16_16_16_16Float, 8, false}},
{0xc001000600000000ull,
{vk::Format::eR16G16B16A16Sfloat,
Prospero::GpuEnumValue(Prospero::BufferFormat::k16_16_16_16Float), 8, true}},
{vk::Format::eR16G16B16A16Sfloat, Prospero::BufferFormat::k16_16_16_16Float, 8, true}},
}};
[[nodiscard]] inline bool DecodeVideoOutPixelFormat(uint64_t pixel_format,
@@ -439,15 +409,15 @@ inline constexpr std::array<VideoOutFormatPolicy, 6> VIDEO_OUT_FORMAT_POLICIES {
}
[[nodiscard]] inline constexpr bool
IsSupportedDisplayRenderTargetTileMode(uint32_t tile_mode) noexcept {
return tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
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::GpuEnumValue(Prospero::TileMode::kStandard64KB) ||
info.data.address == 0 || (info.data.address & 0xffffu) != 0 || info.extent.width == 0 ||
info.extent.height == 0 || info.bytes_per_block != 4 || info.resources.levels != 1 ||
info.resources.layers != 1 || info.samples != 1) {
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 =
@@ -460,7 +430,7 @@ IsSupportedDisplayRenderTargetTileMode(uint32_t tile_mode) noexcept {
}
[[nodiscard]] inline constexpr bool IsTiledRenderTarget(const ImageInfo& info) noexcept {
return info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) ||
return info.tile_mode == Prospero::TileMode::kRenderTarget ||
IsSupportedStandard64RenderTarget(info);
}
@@ -36,17 +36,6 @@ namespace {
}
}
[[nodiscard]] bool IsDepthViewFormat(vk::Format format) {
switch (format) {
case vk::Format::eD16Unorm:
case vk::Format::eR16Unorm:
case vk::Format::eD32Sfloat:
case vk::Format::eR32Sfloat:
case vk::Format::eR32Uint: return true;
default: return false;
}
}
[[nodiscard]] bool IsValidViewType(const VulkanImage& image, const ImageViewInfo& info) {
switch (image.image_type) {
case vk::ImageType::e1D:
@@ -327,13 +316,13 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) {
const auto& image = backing;
auto normalized = view_info;
const bool is_storage = static_cast<bool>(normalized.usage & vk::ImageUsageFlagBits::eStorage);
normalized.aspect = FullAspectMask(image.format);
if (normalized.aspect & vk::ImageAspectFlagBits::eDepth &&
IsDepthViewFormat(normalized.format)) {
const auto image_aspect = FullAspectMask(image.format);
if (image_aspect & vk::ImageAspectFlagBits::eDepth &&
ImageViewOps::IsFormatDepthCompatible(normalized.format)) {
normalized.format = image.format;
normalized.aspect = vk::ImageAspectFlagBits::eDepth;
}
if (normalized.aspect & vk::ImageAspectFlagBits::eStencil &&
if (image_aspect & vk::ImageAspectFlagBits::eStencil &&
IsStencilViewFormat(normalized.format)) {
normalized.format = image.format;
normalized.aspect = vk::ImageAspectFlagBits::eStencil;
@@ -12,8 +12,25 @@ namespace ImageViewOps {
[[nodiscard]] vk::ImageAspectFlags DepthAspectMask(vk::Format format);
[[nodiscard]] bool FormatsCompatible(vk::Format base, vk::Format view) noexcept;
[[nodiscard]] inline bool IsFormatDepthCompatible(vk::Format format) noexcept {
switch (format) {
case vk::Format::eD32Sfloat:
case vk::Format::eR32Sfloat:
case vk::Format::eR32Uint:
case vk::Format::eD16Unorm:
case vk::Format::eR16Unorm: return true;
default: return false;
}
}
} // namespace ImageViewOps
[[nodiscard]] inline bool IsSupportedSampledDepthFormat(vk::Format image_format,
vk::Format view_format) noexcept {
return DepthAspectTransferFormat(image_format) != vk::Format::eUndefined &&
ImageViewOps::IsFormatDepthCompatible(view_format);
}
[[nodiscard]] inline bool IsValidImageSwizzle(uint32_t swizzle) noexcept {
if ((swizzle & ~0xfffu) != 0) {
return false;
@@ -86,8 +103,14 @@ SelectSampledDepthView(vk::Format image_format, vk::Format view_format, uint32_t
[[nodiscard]] inline bool
IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
return resource.kind == ShaderRecompiler::IR::ResourceKind::Image &&
(resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
if (resource.kind != ShaderRecompiler::IR::ResourceKind::Image &&
resource.kind != ShaderRecompiler::IR::ResourceKind::ImageUint) {
return false;
}
if (resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint && resource.depth_compare) {
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) &&
@@ -95,14 +118,6 @@ IsSupportedSampledDepthResource(const ShaderRecompiler::IR::ImageResource& resou
!resource.written && !resource.atomic;
}
[[nodiscard]] inline bool
IsSupportedSampledDepthUintResource(const ShaderRecompiler::IR::ImageResource& resource) noexcept {
return resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint &&
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
!resource.written && !resource.atomic && !resource.depth_compare;
}
inline void ValidateStorageColorView(vk::Format image_format, vk::Format view_format,
uint32_t swizzle) noexcept {
if (!ImageViewOps::FormatsCompatible(image_format, view_format) ||
@@ -120,7 +135,11 @@ IsSupportedStorageImageResource(const ShaderRecompiler::IR::ImageResource& resou
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D ||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) &&
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.written &&
((resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None &&
resource.mip_count == 1u) ||
(resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage &&
resource.mip_count != 0u)) &&
resource.written &&
(!resource.atomic ||
(resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint &&
resource.read)) &&
@@ -14,156 +14,67 @@
namespace Libs::Graphics {
namespace {
struct RenderTargetFormatMapping {
Prospero::ChannelLayout layout;
Prospero::ChannelType type;
Prospero::ChannelOrder order;
RenderTargetFormatInfo info;
// Rows are channel order; columns are the number of physical components minus one. Unused
// selectors complete each entry to a permutation so logical write masks can be inverted.
constexpr Prospero::ColorComponentMapping kRenderTargetColorMappings[4][4] = {
{Prospero::ColorMappingRgba, Prospero::ColorMappingRgba, Prospero::ColorMappingRgba,
Prospero::ColorMappingRgba},
{Prospero::ColorMappingGr, Prospero::ColorMappingRabg, Prospero::ColorMappingRgab,
Prospero::ColorMappingBgra},
{Prospero::ColorMappingBgra, Prospero::ColorMappingGr, Prospero::ColorMappingBgra,
Prospero::ColorMappingAbgr},
{Prospero::ColorMappingAgba, Prospero::ColorMappingArbg, Prospero::ColorMappingAgbr,
Prospero::ColorMappingArgb},
};
constexpr RenderTargetFormatMapping kRenderTargetFormats[] = {
{Prospero::ChannelLayout::k8,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8Uint, 1}},
{Prospero::ChannelLayout::k8_8,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8G8Unorm, 2}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8G8B8A8Unorm, 4}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kSNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8G8B8A8Snorm, 4}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kSrgb,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8G8B8A8Srgb, 4}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kAlt,
{vk::Format::eB8G8R8A8Unorm, 4}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kSNorm,
Prospero::ChannelOrder::kAlt,
{vk::Format::eB8G8R8A8Snorm, 4}},
{Prospero::ChannelLayout::k8_8_8_8,
Prospero::ChannelType::kSrgb,
Prospero::ChannelOrder::kAlt,
{vk::Format::eB8G8R8A8Srgb, 4}},
{Prospero::ChannelLayout::k5_5_5_1,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR5G5B5A1UnormPack16, 2}},
{Prospero::ChannelLayout::k4_4_4_4,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kReversed,
{vk::Format::eB4G4R4A4UnormPack16, 2}},
{Prospero::ChannelLayout::k10_10_10_2,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eA2B10G10R10UnormPack32, 4}},
{Prospero::ChannelLayout::k10_10_10_2,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kAlt,
{vk::Format::eA2R10G10B10UnormPack32, 4}},
{Prospero::ChannelLayout::k11_11_10,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eB10G11R11UfloatPack32, 4}},
{Prospero::ChannelLayout::k5_6_5,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eB5G6R5UnormPack16, 2}},
{Prospero::ChannelLayout::k16,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16Unorm, 2}},
{Prospero::ChannelLayout::k16,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16Uint, 2}},
{Prospero::ChannelLayout::k16,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16Sfloat, 2}},
{Prospero::ChannelLayout::k16_16,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16Unorm, 4}},
{Prospero::ChannelLayout::k16_16,
Prospero::ChannelType::kSNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16Snorm, 4}},
{Prospero::ChannelLayout::k16_16,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16Uint, 4}},
{Prospero::ChannelLayout::k16_16,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16Sfloat, 4}},
{Prospero::ChannelLayout::k16_16_16_16,
Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16B16A16Unorm, 8}},
{Prospero::ChannelLayout::k16_16_16_16,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16B16A16Uint, 8}},
{Prospero::ChannelLayout::k16_16_16_16,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR16G16B16A16Sfloat, 8}},
{Prospero::ChannelLayout::k16_16_16_16,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kAlt,
{vk::Format::eR16G16B16A16Sfloat, 8, Prospero::ColorMappingBgra}},
{Prospero::ChannelLayout::k16_16_16_16,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kReversed,
{vk::Format::eR16G16B16A16Sfloat, 8, Prospero::ColorMappingAbgr}},
{Prospero::ChannelLayout::k32,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR32Sfloat, 4}},
{Prospero::ChannelLayout::k32_32,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR32G32Uint, 8}},
{Prospero::ChannelLayout::k32_32,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR32G32Sfloat, 8}},
{Prospero::ChannelLayout::k32_32_32_32,
Prospero::ChannelType::kFloat,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR32G32B32A32Sfloat, 16}},
struct RenderTargetHostFormatInfo {
vk::Format format = vk::Format::eUndefined;
Prospero::ColorComponentMapping host_to_storage;
};
RenderTargetHostFormatInfo ResolveRenderTargetHostFormat(Prospero::BufferFormat guest_format,
Prospero::ChannelOrder order) {
if (order == Prospero::ChannelOrder::kAlt) {
switch (guest_format) {
case Prospero::BufferFormat::k8_8_8_8UNorm:
return {vk::Format::eB8G8R8A8Unorm, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k8_8_8_8SNorm:
return {vk::Format::eB8G8R8A8Snorm, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k8_8_8_8Srgb:
return {vk::Format::eB8G8R8A8Srgb, Prospero::ColorMappingBgra};
case Prospero::BufferFormat::k10_10_10_2UNorm:
return {vk::Format::eA2R10G10B10UnormPack32, Prospero::ColorMappingBgra};
default: break;
}
}
switch (guest_format) {
case Prospero::BufferFormat::k5_5_5_1UNorm:
return {vk::Format::eA1R5G5B5UnormPack16, 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), {}};
}
}
} // namespace
// TODO: cleanup!
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t raw_layout, uint32_t raw_type,
uint32_t raw_order) {
const auto layout = static_cast<Prospero::ChannelLayout>(raw_layout);
const auto type = static_cast<Prospero::ChannelType>(raw_type);
const auto order = static_cast<Prospero::ChannelOrder>(raw_order);
if (layout == Prospero::ChannelLayout::k8 && type == Prospero::ChannelType::kUNorm &&
raw_order <= Prospero::GpuEnumValue(Prospero::ChannelOrder::kAltReversed)) {
return {vk::Format::eR8Unorm, 1};
}
for (const auto& mapping: kRenderTargetFormats) {
if (mapping.layout == layout && mapping.type == type && mapping.order == order) {
return mapping.info;
RenderTargetFormatInfo TextureGetRenderTargetFormat(Prospero::ChannelLayout layout,
Prospero::ChannelType type,
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 order_mapping =
kRenderTargetColorMappings[static_cast<size_t>(order)][encoding.components - 1u];
return {host.format, bytes, host.host_to_storage.Then(order_mapping)};
}
}
EXIT("unsupported render-target format combination: layout=%u type=%u order=%u\n", raw_layout,
raw_type, raw_order);
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 {
@@ -208,12 +119,12 @@ vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle) {
return components;
}
vk::Format TextureGetFormat(uint32_t fmt) {
const auto vk_format = VulkanFormat(fmt);
vk::Format TextureGetFormat(Prospero::BufferFormat format) {
const auto vk_format = VulkanFormat(format);
if (vk_format != vk::Format::eUndefined) {
return vk_format;
}
EXIT("unknown format: fmt = %u\n", fmt);
EXIT("unknown format: fmt = %u\n", static_cast<uint32_t>(format));
return vk::Format::eUndefined;
}
@@ -271,13 +182,14 @@ uint64_t SetLinearUploadLevels(TextureUploadMipLayout* mips,
} // namespace
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint32_t width, uint32_t height,
uint32_t levels, uint32_t depth, uint32_t tile,
uint64_t upload_size, bool allow_depth_tile,
bool volume_texture, const char* owner) {
TextureUploadLayout TextureCalcUploadLayout(Prospero::BufferFormat format, uint32_t width,
uint32_t height, uint32_t levels, uint32_t depth,
Prospero::TileMode tile, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner) {
TextureUploadLayout layout {};
layout.surface.description = {
fmt,
format,
tile,
volume_texture ? TileSurfaceDimension::Dim3D : TileSurfaceDimension::Dim2D,
width,
@@ -287,35 +199,39 @@ TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint32_t width, uint32
volume_texture ? 1u : depth,
};
const auto& description = layout.surface.description;
const auto tile_mode = static_cast<Prospero::TileMode>(description.tile_mode);
const auto tile_mode = description.tile_mode;
TileTextureElementLayout element {};
if (fmt == 0) {
if (format == Prospero::BufferFormat::kInvalid) {
EXIT("%s: legacy texture upload format unsupported: fmt=0 tile=%u size=%" PRIu64
" extent=%ux%u levels=%u\n",
owner, description.tile_mode, upload_size, width, height, levels);
owner, static_cast<uint32_t>(description.tile_mode), upload_size, width, height,
levels);
}
switch (tile_mode) {
case Prospero::TileMode::kLinear:
if (!TileGetTextureElementLayout(fmt, element)) {
EXIT("%s: unsupported linear texture format: fmt=%u\n", owner, fmt);
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>(fmt), description.tile_mode, upload_size, width,
height, levels);
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>(fmt), description.tile_mode, upload_size, width,
height, levels);
owner, static_cast<uint32_t>(format),
static_cast<uint32_t>(description.tile_mode), upload_size, width, height,
levels);
}
break;
}
@@ -324,12 +240,12 @@ TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint32_t width, uint32
element = {layout.surface.texture.block.bytes_per_element,
layout.surface.texture.texel_width, layout.surface.texture.texel_height};
}
layout.pitch = TileGetTexturePitch(fmt, width, description.tile_mode);
layout.pitch = TileGetTexturePitch(format, width, description.tile_mode);
if (tile_mode == Prospero::TileMode::kLinear) {
TileSizeOffset level_sizes[16] {};
TilePaddedSize padded_sizes[16] {};
TileGetTextureSize(fmt, width, height, levels, description.tile_mode, nullptr, level_sizes,
padded_sizes);
TileGetTextureSize(format, width, height, levels, description.tile_mode, nullptr,
level_sizes, padded_sizes);
for (uint32_t level = 0; level < levels; ++level) {
layout.mips[level] = {level_sizes[level].offset, level_sizes[level].size,
padded_sizes[level].width, padded_sizes[level].height};
@@ -356,10 +272,9 @@ std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayo
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 && static_cast<Prospero::TileMode>(description.tile_mode) !=
Prospero::TileMode::kLinear
? description.width
: layout.pitch;
uint32_t mip_pitch = volume_texture && description.tile_mode != Prospero::TileMode::kLinear
? description.width
: layout.pitch;
std::vector<vk::BufferImageCopy> regions;
regions.reserve(GetTextureRegionCount(depth, description.levels, volume_texture));
@@ -376,8 +291,7 @@ std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayo
1};
region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0;
region.imageExtent = {mip_width, mip_height, 1};
const bool linear = static_cast<Prospero::TileMode>(description.tile_mode) ==
Prospero::TileMode::kLinear;
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;
@@ -35,12 +35,15 @@ struct TextureUploadLayout {
};
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle);
vk::Format TextureGetFormat(uint32_t fmt);
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t layout, uint32_t type, uint32_t order);
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint32_t width, uint32_t height,
uint32_t levels, uint32_t depth, uint32_t tile,
uint64_t upload_size, bool allow_depth_tile,
bool volume_texture, const char* owner);
vk::Format TextureGetFormat(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,
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,
@@ -94,10 +94,14 @@ vk::DescriptorBufferInfo BufferInfo(const BufferView& view) {
} // namespace
vk::DescriptorImageInfo DescriptorCache::MakeImageInfo(const TextureBinding& texture) {
EXIT_IF(!texture.image_id || texture.image_view == nullptr ||
texture.layout == vk::ImageLayout::eUndefined);
return {nullptr, texture.image_view, texture.layout};
vk::DescriptorImageInfo DescriptorCache::MakeImageInfo(const TextureBinding& texture,
uint32_t element) {
const auto view =
texture.mip_views.empty()
? (element == 0u ? texture.image_view : vk::ImageView {})
: (element < texture.mip_views.size() ? texture.mip_views[element] : vk::ImageView {});
EXIT_IF(!texture.image_id || view == nullptr || texture.layout == vk::ImageLayout::eUndefined);
return {nullptr, view, texture.layout};
}
DescriptorCache::~DescriptorCache() {
@@ -229,14 +233,17 @@ VulkanDescriptorSet& DescriptorCache::GetDescriptor(Stage
auto* set = Allocate(stage, program);
EXIT_NOT_IMPLEMENTED(set == nullptr);
const auto descriptor_count = program.info.buffers.size() + program.info.images.size() +
program.info.samplers.size() + program.info.addresses.size() + 3u;
size_t descriptor_count = 0;
for (const auto& binding: program.bindings.descriptors) {
descriptor_count += DescriptorCount(binding);
}
std::vector<vk::DescriptorBufferInfo> buffer_infos;
std::vector<vk::DescriptorImageInfo> image_infos;
std::vector<vk::WriteDescriptorSet> writes;
buffer_infos.reserve(descriptor_count);
image_infos.reserve(descriptor_count);
writes.reserve(program.bindings.descriptors.size());
std::vector<uint32_t> image_occurrences(data.images.size());
for (const auto& binding: program.bindings.descriptors) {
vk::WriteDescriptorSet write {};
@@ -273,7 +280,7 @@ VulkanDescriptorSet& DescriptorCache::GetDescriptor(Stage
default: {
for (const auto resource: binding.resources) {
const auto& texture = data.images.at(resource);
image_infos.push_back(MakeImageInfo(texture));
image_infos.push_back(MakeImageInfo(texture, image_occurrences.at(resource)++));
}
break;
}
@@ -286,6 +293,12 @@ VulkanDescriptorSet& DescriptorCache::GetDescriptor(Stage
}
writes.push_back(write);
}
for (uint32_t i = 0; i < data.images.size(); i++) {
const auto expected = data.images[i].mip_views.empty()
? 1u
: static_cast<uint32_t>(data.images[i].mip_views.size());
EXIT_IF(image_occurrences[i] != expected);
}
m_graphics.device.updateDescriptorSets(static_cast<uint32_t>(writes.size()), writes.data(), 0,
nullptr);
return *set;
@@ -47,10 +47,11 @@ public:
enum class Stage { Unknown, Vertex, Pixel, Compute };
struct TextureBinding {
ImageId image_id;
vk::ImageView image_view = nullptr;
TextureCache::ImageDesc desc;
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
ImageId image_id;
vk::ImageView image_view = nullptr;
TextureCache::ImageDesc desc;
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
std::vector<vk::ImageView> mip_views;
};
struct NativeDescriptors {
@@ -94,7 +95,8 @@ private:
int next_free_pool = -1;
};
static vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture);
static vk::DescriptorImageInfo MakeImageInfo(const TextureBinding& texture,
uint32_t element = 0);
void CreatePool();
VulkanDescriptorSet* Allocate(Stage stage, const ShaderRecompiler::IR::Program& program);
vk::DescriptorSetLayout
@@ -23,10 +23,11 @@
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ir/BindingLayout.h"
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/shader.h"
#include "kernel/memory.h"
#include <algorithm>
#include <atomic>
@@ -59,7 +60,7 @@ static void CopyNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& so
}
static Prospero::ImageType TextureType(const ShaderTextureResource& descriptor) {
const auto type = static_cast<Prospero::ImageType>(descriptor.Type());
const auto type = descriptor.Type();
return type == Prospero::ImageType::kCube ? Prospero::ImageType::kColor2DArray : type;
}
@@ -91,11 +92,12 @@ static BufferView NativeStorageBuffer(RenderContext& context, CommandBuffer& com
if (stride != 0 && records > UINT64_MAX / stride) {
EXIT("storage buffer descriptor footprint overflow\n");
}
const auto size = stride != 0 ? static_cast<uint64_t>(stride) * records : records;
if (address == 0 || size == 0) {
const auto requested_size = stride != 0 ? static_cast<uint64_t>(stride) * records : records;
if (address == 0 || requested_size == 0) {
BindNullStorageBuffer(context, result);
return result;
}
const auto size = Libs::LibKernel::Memory::ClampRangeSize(address, requested_size);
const auto& graphics = context.GetGraphics();
const auto alignment = graphics.StorageMinAlignment();
if (alignment == 0 ||
@@ -227,14 +229,14 @@ bool IsSupportedSampledVideoOutView(const ShaderRecompiler::IR::ImageResource& r
return image.usage.video_out && image.info.resources.layers == 1 &&
IsSupportedSampledColorResource(resource) &&
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) &&
descriptor.Depth() == 0 && descriptor.BaseArray5() == 0;
descriptor.Type() == Prospero::ImageType::kColor2D && descriptor.Depth() == 0 &&
descriptor.BaseArray5() == 0;
}
bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor, const Image& image) {
const auto width = static_cast<uint32_t>(descriptor.Width5()) + 1u;
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
const auto type = static_cast<Prospero::ImageType>(descriptor.Type());
const auto type = descriptor.Type();
const bool multisampled = IsMultisampledTexture(type);
const auto samples = multisampled ? 1u << descriptor.LastLevel() : 1u;
const auto pitch =
@@ -269,9 +271,8 @@ bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor, c
(supported_2d || supported_array || supported_cube || supported_msaa_2d ||
supported_msaa_array) &&
levels_ok && descriptor.MinLod() == 0 &&
descriptor.TileMode() == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) &&
descriptor.BCSwizzle() == 0 && (!descriptor.MsaaDepth() || multisampled) &&
pitch >= width && pitch == image.info.pitch;
descriptor.TileMode() == Prospero::TileMode::kDepth && descriptor.BCSwizzle() == 0 &&
(!descriptor.MsaaDepth() || multisampled) && pitch >= width && pitch == image.info.pitch;
}
bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor, const Image& image) {
@@ -283,13 +284,13 @@ bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor, co
(static_cast<uint32_t>(descriptor.TileMode()) << 20u) |
(static_cast<uint32_t>(descriptor.Type()) << 28u);
const uint32_t field4_expected = descriptor.Depth() | (descriptor.BaseArray5() << 16u);
const uint32_t field5_expected =
0x00700000u | (static_cast<uint32_t>(descriptor.MaxMip()) << 4u);
const bool common = (descriptor.fields[1] & field1_reserved_mask) == 0 &&
(descriptor.fields[2] & field2_reserved_mask) == 0 &&
descriptor.fields[3] == field3_expected &&
descriptor.fields[4] == field4_expected &&
descriptor.fields[5] == field5_expected;
const uint32_t field5_expected = (static_cast<uint32_t>(descriptor.PerfMod5()) << 20u) |
(static_cast<uint32_t>(descriptor.MaxMip()) << 4u);
const bool common = (descriptor.fields[1] & field1_reserved_mask) == 0 &&
(descriptor.fields[2] & field2_reserved_mask) == 0 &&
descriptor.fields[3] == field3_expected &&
descriptor.fields[4] == field4_expected &&
descriptor.fields[5] == field5_expected;
if (!common || (descriptor.fields[6] == 0 && descriptor.fields[7] != 0)) {
return false;
}
@@ -300,8 +301,8 @@ bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor, co
const uint32_t expected_control = htile_control | (descriptor.MsaaDepth() ? (1u << 10u) : 0u);
const auto metadata_addr = descriptor.MetaAddr() << 8u;
return (descriptor.fields[6] & 0x00ffffffu) == expected_control && metadata_addr != 0 &&
descriptor.TileMode() == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) &&
image.info.tile_mode == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) &&
descriptor.TileMode() == Prospero::TileMode::kDepth &&
image.info.tile_mode == Prospero::TileMode::kDepth &&
image.info.metadata.kind == ImageMetadataKind::Htile &&
image.info.metadata.range.Valid() && image.info.metadata.range.address == metadata_addr;
}
@@ -315,8 +316,7 @@ static void ValidateDepthTargetBinding(const ShaderRecompiler::IR::ImageResource
const bool encoding_ok =
image != nullptr && IsSupportedDepthTextureEncoding(descriptor, *image);
const bool format_ok =
image != nullptr &&
IsSupportedSampledDepthFormat(image->info.pixel_format, descriptor.Format(), view_format);
image != nullptr && IsSupportedSampledDepthFormat(image->info.pixel_format, view_format);
if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) {
return;
}
@@ -332,23 +332,21 @@ static void ValidateDepthTargetBinding(const ShaderRecompiler::IR::ImageResource
resource_ok, descriptor_ok, encoding_ok, format_ok, static_cast<uint32_t>(resource.kind),
static_cast<uint32_t>(resource.dimension), static_cast<uint32_t>(resource.mip_mode),
resource.read, resource.written, resource.atomic, resource.depth_compare,
descriptor.Format(), descriptor.DstSelXYZW(),
static_cast<uint32_t>(descriptor.Format()), descriptor.DstSelXYZW(),
image == nullptr ? static_cast<int>(vk::Format::eUndefined)
: static_cast<int>(image->info.pixel_format),
static_cast<int>(view_format), image == nullptr ? 0u : image->info.resources.layers,
descriptor.Type(), descriptor.BaseArray5(), descriptor.Depth(), descriptor_pitch,
image == nullptr ? 0u : image->info.pitch, descriptor.Base40(), size, descriptor.fields[0],
descriptor.fields[1], descriptor.fields[2], descriptor.fields[3], descriptor.fields[4],
descriptor.fields[5], descriptor.fields[6], descriptor.fields[7]);
static_cast<uint32_t>(descriptor.Type()), descriptor.BaseArray5(), descriptor.Depth(),
descriptor_pitch, image == nullptr ? 0u : image->info.pitch, descriptor.Base40(), size,
descriptor.fields[0], descriptor.fields[1], descriptor.fields[2], descriptor.fields[3],
descriptor.fields[4], descriptor.fields[5], descriptor.fields[6], descriptor.fields[7]);
}
static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderTextureResource& descriptor) {
const auto tile = descriptor.TileMode();
const bool is_color_1d =
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor1D);
const bool is_color_1d_array =
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor1DArray);
const auto tile = descriptor.TileMode();
const bool is_color_1d = descriptor.Type() == Prospero::ImageType::kColor1D;
const bool is_color_1d_array = descriptor.Type() == Prospero::ImageType::kColor1DArray;
const bool valid_1d_slice =
(is_color_1d && descriptor.Depth() == 0 && descriptor.BaseArray5() == 0) ||
(is_color_1d_array && descriptor.BaseArray5() <= descriptor.Depth());
@@ -358,24 +356,27 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim1DArray &&
is_color_1d_array && descriptor.Height5() == 0 &&
descriptor.BaseArray5() <= descriptor.Depth();
const bool is_color_2d =
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D);
const bool is_color_2d_array =
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray);
const bool is_color_2d = descriptor.Type() == Prospero::ImageType::kColor2D;
const bool is_color_2d_array = descriptor.Type() == Prospero::ImageType::kColor2DArray;
const bool valid_2d_slice =
(is_color_2d && descriptor.Depth() == 0 && descriptor.BaseArray5() == 0) ||
(is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth());
const bool is_2d =
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D && valid_2d_slice;
const bool is_cube = resource.cube && descriptor.Type() == Prospero::ImageType::kCube &&
descriptor.Width5() == descriptor.Height5() &&
descriptor.BaseArray5() <= descriptor.Depth() &&
(descriptor.Depth() - descriptor.BaseArray5() + 1u) % 6u == 0;
const bool is_2d_array =
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray &&
is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth();
((!resource.cube && is_color_2d_array && descriptor.BaseArray5() <= descriptor.Depth()) ||
is_cube);
const bool is_3d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D &&
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor3D) &&
descriptor.Type() == Prospero::ImageType::kColor3D &&
descriptor.BaseArray5() == 0;
TileTextureBlockLayout tile_layout {};
bool supported_tile = false;
switch (static_cast<Prospero::TileMode>(tile)) {
switch (tile) {
case Prospero::TileMode::kLinear: supported_tile = true; break;
case Prospero::TileMode::kDepth:
supported_tile =
@@ -403,10 +404,14 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
const bool supported_swizzle =
IsValidImageSwizzle(swizzle) &&
(swizzle == DstSel(4, 5, 6, 7) || !resource.read || resource.atomic);
const auto view_last_level =
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage
? descriptor.LastLevel()
: std::min(descriptor.LastLevel(), descriptor.MaxMip());
return (is_1d || is_1d_array || is_2d || is_2d_array || is_3d) && supported_tile &&
descriptor.BaseLevel() == descriptor.LastLevel() &&
descriptor.LastLevel() <= descriptor.MaxMip() && descriptor.MinLod() == 0 &&
supported_swizzle && descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth();
descriptor.BaseLevel() <= view_last_level && view_last_level <= descriptor.MaxMip() &&
descriptor.MinLod() == 0 && supported_swizzle && descriptor.BCSwizzle() == 0 &&
!descriptor.MsaaDepth();
}
static bool IsSupportedStorageTextureEncoding(const ShaderTextureResource& descriptor) {
@@ -435,14 +440,12 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
const bool encoding_ok = IsSupportedStorageTextureEncoding(descriptor);
const bool uint_resource =
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint;
const bool raw_sint_storage =
format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32SInt) && uint_resource &&
resource.written && !resource.read && !resource.atomic;
const bool raw_sint_storage = format == Prospero::BufferFormat::k32SInt && uint_resource &&
resource.written && !resource.read && !resource.atomic;
const bool format_ok =
raw_sint_storage ||
(Prospero::IsSupportedTextureFormat(format) &&
uint_resource == Prospero::IsUintTextureFormat(format) &&
(!resource.atomic || format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt)));
raw_sint_storage || (Prospero::IsSupportedTextureFormat(format) &&
uint_resource == Prospero::IsUintTextureFormat(format) &&
(!resource.atomic || format == Prospero::BufferFormat::k32UInt));
if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) {
return;
}
@@ -461,24 +464,23 @@ void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
IsValidImageSwizzle(descriptor.DstSelXYZW()), descriptor.Base40(), size,
static_cast<uint32_t>(descriptor.Width5()) + 1u,
static_cast<uint32_t>(descriptor.Height5()) + 1u,
static_cast<uint32_t>(descriptor.Depth()) + 1u, descriptor.Type(), format,
descriptor.TileMode(), descriptor.DstSelXYZW(), resource.read, resource.written,
descriptor.fields[0], descriptor.fields[1], descriptor.fields[2], descriptor.fields[3],
descriptor.fields[4], descriptor.fields[5], descriptor.fields[6], descriptor.fields[7]);
static_cast<uint32_t>(descriptor.Depth()) + 1u, static_cast<uint32_t>(descriptor.Type()),
static_cast<uint32_t>(format), static_cast<uint32_t>(descriptor.TileMode()),
descriptor.DstSelXYZW(), resource.read, resource.written, descriptor.fields[0],
descriptor.fields[1], descriptor.fields[2], descriptor.fields[3], descriptor.fields[4],
descriptor.fields[5], descriptor.fields[6], descriptor.fields[7]);
}
struct NullImageSpec {
vk::Format format;
uint32_t guest_format;
vk::Format format;
Prospero::BufferFormat guest_format;
};
static NullImageSpec NullTextureSpec(const ShaderRecompiler::IR::ImageResource& resource) {
const bool uint_image = resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint ||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint;
return uint_image ? NullImageSpec {vk::Format::eR32Uint,
Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt)}
: NullImageSpec {vk::Format::eR32Sfloat,
Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float)};
return uint_image ? NullImageSpec {vk::Format::eR32Uint, Prospero::BufferFormat::k32UInt}
: NullImageSpec {vk::Format::eR32Sfloat, Prospero::BufferFormat::k32Float};
}
static TextureCache::ImageDesc NullTextureDesc(const ShaderRecompiler::IR::ImageResource& resource,
@@ -504,7 +506,7 @@ static TextureCache::ImageDesc NullTextureDesc(const ShaderRecompiler::IR::Image
}
static void PopulateTextureMipLayout(ImageInfo& info) {
if (info.IsVolume() && info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kLinear)) {
if (info.IsVolume() && info.tile_mode != Prospero::TileMode::kLinear) {
TileSurfaceLayout surface {};
const TileSurfaceDescription description {
info.guest_format, info.tile_mode, TileSurfaceDimension::Dim3D, info.extent.width,
@@ -630,12 +632,15 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
const auto type = TextureType(descriptor);
const bool multisampled = IsMultisampledTexture(type);
const auto levels = multisampled ? 1u : static_cast<uint32_t>(descriptor.MaxMip()) + 1u;
const auto tile = descriptor.TileMode();
const bool depth_tile = tile == Prospero::GpuEnumValue(Prospero::TileMode::kDepth);
const bool msaa_tile =
depth_tile || tile == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
const bool dynamic_storage =
storage && resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
const auto view_last_level =
!multisampled && !dynamic_storage ? std::min(last_level, descriptor.MaxMip()) : last_level;
const auto tile = descriptor.TileMode();
const bool depth_tile = tile == Prospero::TileMode::kDepth;
const bool msaa_tile = depth_tile || tile == Prospero::TileMode::kRenderTarget;
const bool msaa_array = type == Prospero::ImageType::kColor2DMsaaArray;
if ((!multisampled && (base_level > last_level || last_level >= levels)) ||
if ((!multisampled && (base_level > view_last_level || view_last_level >= levels)) ||
(multisampled &&
(base_level != 0 || last_level == 0 || last_level > 3 ||
descriptor.MaxMip() != last_level || !msaa_tile ||
@@ -644,7 +649,8 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
EXIT("unsupported texture mip view: base=%u last=%u levels=%u max=%u type=%u tile=%u "
"kind=%u dimension=%u mip_mode=%u read=%d written=%d "
"dwords=%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x\n",
base_level, last_level, levels, descriptor.MaxMip(), descriptor.Type(), tile,
base_level, last_level, levels, descriptor.MaxMip(),
static_cast<uint32_t>(descriptor.Type()), static_cast<uint32_t>(tile),
static_cast<uint32_t>(resource.kind), static_cast<uint32_t>(resource.dimension),
static_cast<uint32_t>(resource.mip_mode), resource.read, resource.written,
descriptor.fields[0], descriptor.fields[1], descriptor.fields[2], descriptor.fields[3],
@@ -653,7 +659,7 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
}
const auto samples = multisampled ? 1u << last_level : 1u;
const auto view_levels =
multisampled ? 1u : static_cast<uint32_t>(last_level - base_level) + 1u;
multisampled ? 1u : static_cast<uint32_t>(view_last_level - base_level) + 1u;
const auto depth = static_cast<uint32_t>(descriptor.Depth()) + 1u;
const auto format = descriptor.Format();
const bool sampled_numeric_class =
@@ -664,7 +670,7 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) &&
!sampled_numeric_class) {
EXIT("sampled image numeric class mismatch: kind=%u format=%u addr=0x%016" PRIx64 "\n",
static_cast<uint32_t>(resource.kind), format, address);
static_cast<uint32_t>(resource.kind), static_cast<uint32_t>(format), address);
}
const bool volume = type == Prospero::ImageType::kColor3D;
@@ -694,15 +700,14 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
ValidateStorageTexture(resource, descriptor, size.size);
}
const auto pixel_format = TextureGetFormat(format);
const auto storage_view_format =
storage && format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32SInt)
? vk::Format::eR32Uint
: SrgbStorageViewFormat(pixel_format);
const auto view_format = storage && storage_view_format != vk::Format::eUndefined
? storage_view_format
: pixel_format;
const auto block_bytes = Prospero::BlockCompressedBytesPerBlock(format);
const auto pixel_format = TextureGetFormat(format);
const auto storage_view_format = storage && format == Prospero::BufferFormat::k32SInt
? vk::Format::eR32Uint
: SrgbStorageViewFormat(pixel_format);
const auto view_format = storage && storage_view_format != vk::Format::eUndefined
? storage_view_format
: pixel_format;
const auto block_bytes = Prospero::BlockCompressedBytesPerBlock(format);
TextureCache::ImageDesc desc {};
desc.info.data = {address, size.size};
desc.info.pixel_format = pixel_format;
@@ -788,9 +793,10 @@ void RenderExecutor::BindImage(ImageId id, bool storage) {
return;
}
if (image.binding.is_bound) {
image.binding.force_general |= storage;
image.binding.force_general |= image.binding.shader_write != storage;
}
image.binding.is_bound = true;
image.binding.shader_write |= storage;
TrackImageBinding(id);
}
@@ -906,8 +912,27 @@ void RenderExecutor::RebindImages(CommandBuffer& buffer,
BindImage(images[i].image_id,
images[i].desc.type == TextureCache::BindingType::Storage);
}
auto& binding = images[i];
binding.image_view = texture_cache.FindTexture(binding.image_id, binding.desc);
auto& binding = images[i];
binding.mip_views.clear();
const auto& resource = program.info.images[i];
if (resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::DynamicStorage) {
EXIT_IF(resource.mip_count == 0u ||
resource.mip_count != binding.desc.view_info.level_count);
binding.mip_views.reserve(resource.mip_count);
for (uint32_t mip = 0; mip < resource.mip_count; mip++) {
auto desc = binding.desc;
desc.view_info.base_level += mip;
desc.view_info.level_count = 1;
binding.mip_views.push_back(texture_cache.FindTexture(binding.image_id, desc));
}
binding.image_view = binding.mip_views.front();
} else {
auto desc = binding.desc;
if (desc.type == TextureCache::BindingType::Storage) {
desc.view_info.level_count = 1;
}
binding.image_view = texture_cache.FindTexture(binding.image_id, desc);
}
auto& image = texture_cache.GetImage(binding.image_id);
const bool storage = binding.desc.type == TextureCache::BindingType::Storage;
image.usage.storage |= storage;
@@ -996,8 +1021,11 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
range, vk_buffer);
} else if ((image.binding.force_general || image.binding.is_target) &&
!image.info.IsDepth()) {
const vk::AccessFlags2 storage_access = image.binding.shader_write
? vk::AccessFlagBits2::eShaderWrite
: vk::AccessFlags2 {};
image.Transit(vk::ImageLayout::eGeneral,
vk::AccessFlagBits2::eShaderRead |
vk::AccessFlagBits2::eShaderRead | storage_access |
vk::AccessFlagBits2::eColorAttachmentRead |
vk::AccessFlagBits2::eColorAttachmentWrite,
{}, vk_buffer);
@@ -55,8 +55,9 @@ bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other)
PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology, bool ps_active,
std::span<const uint32_t> vs_spirv, std::span<const uint32_t> ps_spirv) {
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
bool primitive_restart_enable, bool ps_active, std::span<const uint32_t> vs_spirv,
std::span<const uint32_t> ps_spirv) {
KYTY_PROFILER_BLOCK("PipelineCache::CreatePipeline(Gfx)", profiler::colors::DeepOrangeA200);
EXIT_IF(colors == nullptr);
@@ -120,6 +121,12 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
depth.samples);
}
}
if (color_count == 0 && !with_depth) {
attachment_samples = render_sample_count(ctx.GetAaConfig().msaa_num_samples);
EXIT_IF(!static_cast<bool>(
m_graphics.GetPhysicalDeviceProperties().limits.framebufferNoAttachmentsSampleCounts &
vulkan_sample_count(attachment_samples)));
}
EXIT_IF(attachment_samples == 0 ||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {});
@@ -130,12 +137,12 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
}
}
const auto& clip_control = ctx.GetClipControl();
EXIT_NOT_IMPLEMENTED(!clip_control.IsZClipModeRepresentable());
static_params.negative_one_to_one = !clip_control.dx_clip_space;
static_params.depth_clip_enable = clip_control.IsZClipEnabled();
static_params.topology = topology;
static_params.samples = attachment_samples;
const auto& clip_control = ctx.GetClipControl();
static_params.negative_one_to_one = !clip_control.dx_clip_space;
static_params.depth_clip_enable = clip_control.IsZClipEnabled();
static_params.topology = topology;
static_params.primitive_restart_enable = primitive_restart_enable;
static_params.samples = attachment_samples;
static_params.sample_shading_enable =
ps_active && attachment_samples > 1 && ps_input_info->ps_sample_shading;
if (static_params.sample_shading_enable && !m_graphics.sample_rate_shading_enabled) {
@@ -38,6 +38,7 @@ struct PipelineStaticParameters {
bool depth_clip_enable = true;
int scissor_ltrb[4] = {0};
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
bool primitive_restart_enable = false;
uint32_t samples = 1;
bool sample_shading_enable = false;
bool with_depth = false;
@@ -79,9 +80,9 @@ static_assert(std::is_standard_layout_v<PipelineStaticParameters>);
static_assert(alignof(PipelineStaticParameters) == 1);
static_assert(sizeof(PipelineStaticParameters) ==
sizeof(float[3]) + sizeof(float[3]) + sizeof(bool) * 2 + sizeof(int[4]) +
sizeof(vk::PrimitiveTopology) + sizeof(uint32_t) + sizeof(bool) * 4 +
sizeof(vk::CompareOp) + sizeof(bool) + sizeof(float) * 2 + sizeof(bool) +
sizeof(PipelineStencilStaticState) * 2 + sizeof(uint32_t) +
sizeof(vk::PrimitiveTopology) + sizeof(bool) + sizeof(uint32_t) +
sizeof(bool) * 4 + sizeof(vk::CompareOp) + sizeof(bool) + sizeof(float) * 2 +
sizeof(bool) + sizeof(PipelineStencilStaticState) * 2 + sizeof(uint32_t) +
sizeof(uint32_t[RENDER_COLOR_ATTACHMENTS_MAX]) + sizeof(bool) * 3 +
sizeof(uint8_t[RENDER_COLOR_ATTACHMENTS_MAX]) * 6 +
sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) * 3 + sizeof(float) * 4);
@@ -122,8 +123,8 @@ public:
CreateGraphicsPipeline(RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo& depth,
ShaderVertexInputInfo& vs_input_info, RenderCommandBuffer& command,
ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
bool ps_active, std::span<const uint32_t> vs_spirv,
std::span<const uint32_t> ps_spirv);
bool primitive_restart_enable, bool ps_active,
std::span<const uint32_t> vs_spirv, std::span<const uint32_t> ps_spirv);
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
const HW::ComputeShaderInfo& cs_regs,
std::span<const uint32_t> cs_spirv);
@@ -2,7 +2,7 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_SHADERRESOURCEBARRIER_H_
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include <vector>
@@ -1,6 +1,6 @@
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
#include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
namespace Libs::Graphics {
@@ -24,6 +24,8 @@
namespace Libs::Graphics {
// IDK: maybe we can remove it?
constexpr uint8_t kTemporaryVertexAttribFormat113 =
static_cast<uint8_t>(Prospero::VertexAttribFormat::k16_16SInt);
constexpr uint32_t kTemporaryPs5BufferFormat121 = 121u;
static bool NarrowInputFormat(vk::Format& format, uint32_t& size, uint32_t used_components) {
@@ -88,8 +90,9 @@ static bool NarrowInputFormat(vk::Format& format, uint32_t& size, uint32_t used_
static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format, uint32_t& size,
uint32_t used_components) {
auto fmt = res.Format();
if (fmt == Prospero::GpuEnumValue(Prospero::VertexAttribFormat::k16_16SInt)) {
const auto fmt = res.Format();
const auto raw_format = res.RawFormat();
if (raw_format == kTemporaryVertexAttribFormat113) {
static bool logged_113 = false;
if (!logged_113) {
LOGF("InputFormat: temporary: accepting invalid PS5 buffer format 113 as "
@@ -99,12 +102,12 @@ static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format,
format = vk::Format::eR32G32B32A32Sfloat;
size = 4;
if (NarrowInputFormat(format, size, used_components)) {
LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", fmt,
LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", raw_format,
VulkanToString(format).c_str(), used_components);
}
return;
}
if (fmt == kTemporaryPs5BufferFormat121) {
if (raw_format == kTemporaryPs5BufferFormat121) {
static bool logged_121 = false;
if (!logged_121) {
LOGF("InputFormat: accepting PS5 buffer format 121 as vk::Format::eR16G16Sfloat\n");
@@ -113,13 +116,13 @@ static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format,
format = vk::Format::eR16G16Sfloat;
size = 2;
if (NarrowInputFormat(format, size, used_components)) {
LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", fmt,
LOGF("InputFormat: narrowing fmt=%u to %s for used_components=%u\n", raw_format,
VulkanToString(format).c_str(), used_components);
}
return;
}
switch (static_cast<Prospero::BufferFormat>(fmt)) {
switch (fmt) {
case Prospero::BufferFormat::k32_32_32_32Float:
format = vk::Format::eR32G32B32A32Sfloat;
size = 4;
@@ -329,7 +332,7 @@ static void GetInputFormat(const ShaderBufferResource& res, vk::Format& format,
size = 1;
break;
default:
EXIT("unknown format: fmt = %u\n", fmt);
EXIT("unknown format: fmt = %u\n", raw_format);
format = vk::Format::eUndefined;
size = 4;
break;
@@ -723,7 +726,8 @@ void CreatePipelineInternal(
input_assembly.pNext = nullptr;
input_assembly.flags = {};
input_assembly.topology = static_params.topology;
input_assembly.primitiveRestartEnable = VK_FALSE;
input_assembly.primitiveRestartEnable =
static_params.primitive_restart_enable ? VK_TRUE : VK_FALSE;
vk::Viewport viewport {};
viewport.x = static_params.viewport_offset[0] - static_params.viewport_scale[0];
+6 -5
View File
@@ -212,11 +212,12 @@ private:
const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
bool log_setup_phases, DrawRenderState& state);
void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw, DrawRenderState& state,
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
const DrawIndexBufferSource& index_source, bool log_pipeline_phase,
bool set_bind_debug, bool set_auto_debug);
void ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw, DrawRenderState& state,
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
const DrawIndexBufferSource& index_source,
bool primitive_restart_enable, bool log_pipeline_phase,
bool set_bind_debug, bool set_auto_debug);
[[nodiscard]] RenderState AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth);
[[nodiscard]] bool ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer& buffer,
@@ -19,8 +19,8 @@
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/shader.h"
#include "kernel/eventQueue.h"
#include "kernel/pthread.h"
@@ -93,7 +93,7 @@ bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t grou
const auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(raw);
if (!resource.formatted || !resource.written || resource.read || resource.atomic ||
resource.scalar || resource.max_byte_extent != 16 || descriptor.Stride() != 16 ||
descriptor.Format() != Prospero::GpuEnumValue(Prospero::BufferFormat::k32_32_32_32UInt) ||
descriptor.Format() != Prospero::BufferFormat::k32_32_32_32UInt ||
descriptor.SwizzleEnabled() || descriptor.IndexStride() != 0 || descriptor.AddTid() ||
resource.packed_stride != descriptor.PackedStride() || raw.dword_count != 4 ||
program.user_data_base != 0 || resources.user_data.size() != 8) {
@@ -139,6 +139,21 @@ static bool TryConsumeComputeImageClear(const ShaderComputeInputInfo& input, Com
}
auto& cache = command.GetContext().GetTextureCache();
if (!cache.ClearImageFromBuffer(command, descriptor.Base48(), size, packed_clear)) {
// Recognized metadata-fill shaders access DCC as an ordinary storage buffer and may run
// before the render target is bound. TryConsumeDccFill either consumes registered state
// or retains a PendingDcc fill while allowing the dispatch to run.
const bool registered_metadata =
cache.TryConsumeDccFill(descriptor.Base48(), size, packed_clear);
static std::atomic<uint32_t> logged_metadata_clears {0};
if (logged_metadata_clears.fetch_add(1, std::memory_order_relaxed) < 32) {
LOGF("GraphicsRenderDispatchDirect: %s metadata clear shader=0x%016" PRIx64
" addr=0x%016" PRIx64 " size=0x%016" PRIx64 " value=0x%08" PRIx32 "\n",
registered_metadata ? "tracked" : "deferred", input.stage.program->shader_hash,
descriptor.Base48(), size, packed_clear);
}
if (registered_metadata) {
return true;
}
return false;
}
static std::atomic<uint32_t> logged_clears {0};
@@ -246,7 +261,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
LOGF(" CS buffer[%u]: source=%u usage=%s addr=0x%012" PRIx64
" stride=%u records=%u format=%u\n",
i, buffer.source, buffer.written ? "read-write" : "read-only", r.Base48(),
r.Stride(), r.NumRecords(), r.Format());
r.Stride(), r.NumRecords(), r.RawFormat());
}
for (uint32_t i = 0; i < program.info.images.size(); i++) {
const auto& image = program.info.images[i];
@@ -258,13 +273,14 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
image.kind == ShaderRecompiler::IR::ResourceKind::ImageUint)
? "true"
: "false",
r.Base40(), static_cast<uint32_t>(r.Type()), r.Format(),
r.Base40(), static_cast<uint32_t>(r.Type()), static_cast<uint32_t>(r.Format()),
static_cast<uint32_t>(r.Width5()) + 1u, static_cast<uint32_t>(r.Height5()) + 1u,
static_cast<uint32_t>(r.Depth()) + 1u,
std::max<uint32_t>(static_cast<uint32_t>(r.LastLevel()),
static_cast<uint32_t>(r.MaxMip())) +
1u,
r.TileMode());
r.Type() == Prospero::ImageType::kColor2DMsaa ||
r.Type() == Prospero::ImageType::kColor2DMsaaArray
? 1u
: static_cast<uint32_t>(r.MaxMip()) + 1u,
static_cast<uint32_t>(r.TileMode()));
}
for (uint32_t i = 0; i < program.info.samplers.size(); i++) {
const auto r = DecodeNativeDescriptor<ShaderSamplerResource>(resources.samplers[i]);
@@ -26,7 +26,7 @@ RenderContext::~RenderContext() {
void RenderContext::InitializeGpu(VideoOut::VideoOutDriver* video_out) {
EXIT_IF(m_gpu != nullptr);
m_video_out = video_out;
m_gpu = std::make_unique<Gpu>(*this);
m_gpu = std::make_unique<GuestGpu>(*this);
m_gpu_resources.SetGpu(m_gpu.get());
}
@@ -45,7 +45,7 @@ void RenderContext::ShutdownGpu() {
}
}
Gpu& RenderContext::GetGpu() const {
GuestGpu& RenderContext::GetGpu() const {
EXIT_IF(m_gpu == nullptr);
return *m_gpu;
}
@@ -24,7 +24,7 @@ class VideoOutDriver;
namespace Libs::Graphics {
constexpr int AGC_USER_INTERRUPT_EVENT = 0x1800;
class Gpu;
class GuestGpu;
class RenderContext {
public:
@@ -35,7 +35,7 @@ public:
[[nodiscard]] GraphicContext& GetGraphics() const noexcept { return m_graphics; }
void InitializeGpu(VideoOut::VideoOutDriver* video_out);
void ShutdownGpu();
[[nodiscard]] Gpu& GetGpu() const;
[[nodiscard]] GuestGpu& GetGpu() const;
[[nodiscard]] VideoOut::VideoOutDriver& GetVideoOut() const;
Common::Mutex& GetMutex() { return m_mutex; }
@@ -67,7 +67,7 @@ private:
PipelineCache m_pipeline_cache;
SamplerCache m_sampler_cache;
GpuResourceManager m_gpu_resources;
std::unique_ptr<Gpu> m_gpu;
std::unique_ptr<GuestGpu> m_gpu;
VideoOut::VideoOutDriver* m_video_out = nullptr;
Common::Mutex m_eop_mutex;
+172 -59
View File
@@ -22,8 +22,8 @@
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/shader.h"
#include "kernel/eventQueue.h"
#include "kernel/memory.h"
@@ -35,9 +35,11 @@
#include <atomic>
#include <bit>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <limits>
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <unordered_map>
@@ -123,7 +125,7 @@ static void LogFramebufferSkip(const char* draw_name, const RenderColorInfo& col
log_id, draw_name, RenderColorTypeName(color.type), color.base_addr, color.buffer_size,
color.image_id ? "yes" : "no", VulkanToString(depth.format).c_str(),
depth.image_id ? "yes" : "no", depth.vaddr_num, ctx.GetRenderTargetMask(),
ucfg.GetPrimType(), index_count, flags);
static_cast<uint32_t>(ucfg.GetPrimType()), index_count, flags);
}
static void LogMrtState(const char* draw_name, const RenderCommandBuffer& buffer,
@@ -179,10 +181,12 @@ static void LogMrtState(const char* draw_name, const RenderCommandBuffer& buffer
" fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32
" width=%u height=%u tile=%u"
" blend=%s src=%u dst=%u alpha_src=%u alpha_dst=%u\n",
log_id, i, rt.base.addr, ctm, csm, ps_input_info.target_output_mode[i], rt.info.format,
rt.info.channel_type, rt.info.channel_order, rt.attrib2.width + 1,
rt.attrib2.height + 1, rt.attrib3.tile_mode, bc.enable ? "true" : "false",
bc.color_srcblend, bc.color_destblend, bc.alpha_srcblend, bc.alpha_destblend);
log_id, i, rt.base.addr, ctm, csm, ps_input_info.target_output_mode[i],
static_cast<uint32_t>(rt.info.format), static_cast<uint32_t>(rt.info.channel_type),
static_cast<uint32_t>(rt.info.channel_order), rt.attrib2.width + 1,
rt.attrib2.height + 1, static_cast<uint32_t>(rt.attrib3.tile_mode),
bc.enable ? "true" : "false", bc.color_srcblend, bc.color_destblend, bc.alpha_srcblend,
bc.alpha_destblend);
}
}
@@ -225,7 +229,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
"scissor=(%d,%d)-(%d,%d)\n",
log_id, buffer.GetContext().GetGpu().GetFrameNum(), draw_name,
RenderColorTypeName(color.type), color.base_addr, extent.width, extent.height,
ucfg.GetPrimType(), index_count, flags, ctx.GetRenderTargetMask(),
static_cast<uint32_t>(ucfg.GetPrimType()), index_count, flags, ctx.GetRenderTargetMask(),
color.color_clear_enable ? "true" : "false", color.color_clear_value.float32[0],
color.color_clear_value.float32[1], color.color_clear_value.float32[2],
color.color_clear_value.float32[3], cc.mode, cc.op, bc.enable ? "true" : "false",
@@ -289,8 +293,7 @@ static void LogDrawInputState(const RenderCommandBuffer& buffer, const RenderCol
const auto& rd = vs_input_info.resources_dst[res_index];
const auto offset = b.attr_offsets[ai];
if (offset + 4u <= b.stride &&
r.Format() ==
Prospero::GpuEnumValue(Prospero::BufferFormat::k8_8_8_8UNorm)) {
r.Format() == Prospero::BufferFormat::k8_8_8_8UNorm) {
uint32_t packed = 0;
std::memcpy(&packed, rec_bytes + offset, sizeof(packed));
const auto r8 = (packed >> 0u) & 0xffu;
@@ -337,6 +340,9 @@ static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandB
framebuffer_extent = colors[0].extent;
} else if (depth.image_id) {
framebuffer_extent = {depth.width, depth.height};
} else {
const auto& limits = buffer.GetGraphics().GetPhysicalDeviceProperties().limits;
framebuffer_extent = {limits.maxFramebufferWidth, limits.maxFramebufferHeight};
}
const auto final_scissor = calc_final_scissor(vp, ctx.GetScanModeControl(), framebuffer_extent);
@@ -371,8 +377,8 @@ static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandB
(use_front ? poly_offset.front_scale : poly_offset.back_scale) / 16.0f;
}
// CB_COLOR_CONTROL.operation controls special CB operations, not the normal color component
// write mask. Use CB_TARGET_MASK here so scanout passes with mode=Disable do not go black.
// Color-control operation selects special color-buffer paths, not the normal component write
// mask. Attachment availability therefore follows the target write mask.
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
ret.color_write_enable[i] =
(i < color_count &&
@@ -502,6 +508,12 @@ static bool ShouldSkipGeShader(const RenderCommandBuffer& buffer) {
sh_regs.m_geMaxOutputPerSubgroup > 0x00000040;
if (unsupported_stage_mask || unsupported_gs_stage || ge_group_size || ge_shader_regs) {
static std::once_flag warning_once;
std::call_once(warning_once, [] {
std::printf("Warning: game uses unsupported graphics pipelines; some draw calls were "
"skipped.\n");
});
const auto log_id = g_shader_stage_log_count.fetch_add(1);
if (log_id < 32) {
LOGF("Skipping unsupported GE shader draw: stages=0x%08" PRIx32
@@ -540,6 +552,71 @@ struct DrawCallInfo {
uint32_t first_instance = 0;
};
static bool ResolveDccAttachmentClear(TextureCache& cache, const RenderColorInfo& target,
const ImageViewInfo& view,
vk::ClearColorValue& clear_value) {
if (target.desc.info.metadata.kind != ImageMetadataKind::Dcc) {
return false;
}
// A DCC fast clear may update metadata only and leave the color allocation stale. Vulkan has
// no guest DCC state, so materialize the deferred value when the surface is next bound.
const auto metadata_address = target.desc.info.metadata.range.address;
uint32_t metadata_value = 0xffffffffu;
if (!cache.IsMetaCleared(metadata_address, view.base_layer, &metadata_value)) {
return false;
}
// Translate recognized constant and register-backed DCC states into a host clear value.
clear_value = {};
switch (static_cast<uint8_t>(metadata_value)) {
case 0x00: break;
case 0x20:
if (!target.metadata_clear_supported) {
return false;
}
clear_value = target.color_clear_value;
break;
case 0x40:
if (!target.metadata_fixed_clear_supported) {
return false;
}
clear_value.float32[3] = 1.0f;
break;
case 0x80:
if (!target.metadata_fixed_clear_supported) {
return false;
}
clear_value.float32[0] = 1.0f;
clear_value.float32[1] = 1.0f;
clear_value.float32[2] = 1.0f;
break;
case 0xc0:
if (!target.metadata_fixed_clear_supported) {
return false;
}
clear_value.float32[0] = 1.0f;
clear_value.float32[1] = 1.0f;
clear_value.float32[2] = 1.0f;
clear_value.float32[3] = 1.0f;
break;
default: return false;
}
for (uint32_t layer = 1; layer < view.layer_count; layer++) {
if (!cache.IsMetaCleared(metadata_address, view.base_layer + layer)) {
return false;
}
}
// Consume only after every layer in this Vulkan view can be materialized together.
for (uint32_t layer = 0; layer < view.layer_count; layer++) {
if (!cache.TouchMeta(metadata_address, view.base_layer + layer, false)) {
EXIT("failed to consume DCC clear state\n");
}
}
return true;
}
RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors,
uint32_t color_count, RenderDepthInfo& depth) {
EXIT_IF(colors == nullptr || color_count > RENDER_COLOR_ATTACHMENTS_MAX);
@@ -580,14 +657,20 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
ImageSubresourceRange {view.base_level, view.level_count, view.base_layer,
view.layer_count},
buffer.Handle());
state.width = std::min(state.width, target.extent.width);
state.height = std::min(state.height, target.extent.height);
state.num_layers = std::min(state.num_layers, view.layer_count);
auto& attachment = state.color_attachments[i];
attachment.image_view = target.image_view;
attachment.image_layout = layout;
attachment.clear_value = target.color_clear_value.uint32;
attachment.is_clear = target.color_clear_enable;
state.width = std::min(state.width, target.extent.width);
state.height = std::min(state.height, target.extent.height);
state.num_layers = std::min(state.num_layers, view.layer_count);
auto& attachment = state.color_attachments[i];
attachment.image_view = target.image_view;
attachment.image_layout = layout;
attachment.clear_value = target.color_clear_value.uint32;
vk::ClearColorValue metadata_clear_value {};
const bool metadata_clear =
ResolveDccAttachmentClear(cache, target, view, metadata_clear_value);
if (metadata_clear) {
attachment.clear_value = metadata_clear_value.uint32;
}
attachment.is_clear = target.color_clear_enable || metadata_clear;
}
if (depth.image_id) {
const auto owner = cache.ResolveOwner(depth.image_id);
@@ -639,8 +722,12 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
attachment.has_stencil = static_cast<bool>(aspects & vk::ImageAspectFlagBits::eStencil);
attachment.stencil_clear = depth.stencil_clear_enable;
}
if (attachment_samples == 0 ||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) {
if (color_count == 0 && !depth.image_id) {
const auto& limits = buffer.GetGraphics().GetPhysicalDeviceProperties().limits;
state.width = limits.maxFramebufferWidth;
state.height = limits.maxFramebufferHeight;
} else if (attachment_samples == 0 ||
vulkan_sample_count(attachment_samples) == vk::SampleCountFlagBits {}) {
EXIT("render state has no valid attachments\n");
}
if (state.num_layers == std::numeric_limits<uint32_t>::max()) {
@@ -652,22 +739,11 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
return state;
}
static bool DrawHasActivePixelShader(const RenderCommandBuffer& buffer,
const DrawRenderState& state, const DrawCallInfo& draw) {
EXIT_IF(draw.name == nullptr);
const auto& ctx = buffer.GetRegisters();
const auto& sh_ctx = buffer.GetShaders();
const bool with_depth = (state.depth_info.format != vk::Format::eUndefined &&
static_cast<bool>(state.depth_info.image_id));
if (state.color_count != 0 || !with_depth) {
return true;
}
const auto& sh_regs = ctx.GetShaderRegisters();
const auto& ps = sh_ctx.GetPs();
return ShaderAddressValid(ps.ps_regs.data_addr) &&
PixelShaderHasDepthOrCoverageSideEffects(sh_regs);
static bool DrawHasActivePixelShader(const RenderCommandBuffer& buffer) {
const auto& ctx = buffer.GetRegisters();
const auto& sh_regs = ctx.GetShaderRegisters();
const bool has_color_output = (ctx.GetRenderTargetMask() & sh_regs.m_cbShaderMask) != 0;
return has_color_output || PixelShaderHasDepthOrCoverageSideEffects(sh_regs);
}
enum class CbColorMode : uint8_t {
@@ -682,10 +758,11 @@ enum class CbColorMode : uint8_t {
static bool ConsumeMetadataColorOperation(const RenderCommandBuffer& buffer) {
const auto& ctx = buffer.GetRegisters();
const auto mode = ctx.GetColorControl().mode;
// These AGC CB modes run color-buffer metadata/decompression operations. The shader is a
// dummy vehicle for the CB, and its exported color must not be applied as a normal draw.
// Kyty currently stores host images as expanded Vulkan images and does not track CMASK/DCC
// metadata state, so the matching host operation is a no-op.
// These special modes run color-buffer metadata or decompression operations. The shader is a
// vehicle for that operation, and its exported color must not be applied as a normal draw.
// Kyty stores expanded Vulkan images rather than compressed guest surfaces, so no equivalent
// hardware pass is emitted. Tracked DCC clear state is materialized on attachment bind;
// future CMask/FMask support can consume its state through the same TextureCache path.
return mode == static_cast<uint8_t>(CbColorMode::EliminateFastClear) ||
mode == static_cast<uint8_t>(CbColorMode::FmaskDecompress) ||
mode == static_cast<uint8_t>(CbColorMode::DccDecompress);
@@ -844,7 +921,7 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
topology = vk::PrimitiveTopology::ePointList;
switch (static_cast<Prospero::PrimitiveType>(ucfg.GetPrimType())) {
switch (ucfg.GetPrimType()) {
case Prospero::PrimitiveType::kNone: return false;
case Prospero::PrimitiveType::kPointList:
topology = vk::PrimitiveTopology::ePointList;
@@ -867,19 +944,54 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
break;
case Prospero::PrimitiveType::kRectListLegacy:
if (!auto_draw) {
EXIT("unknown primitive type: %u\n", ucfg.GetPrimType());
EXIT("unknown primitive type: %u\n", static_cast<uint32_t>(ucfg.GetPrimType()));
}
topology = vk::PrimitiveTopology::eTriangleStrip;
break;
case Prospero::PrimitiveType::kQuadListLegacy:
topology = vk::PrimitiveTopology::eTriangleFan;
break;
default: EXIT("unknown primitive type: %u\n", ucfg.GetPrimType());
default: EXIT("unknown primitive type: %u\n", static_cast<uint32_t>(ucfg.GetPrimType()));
}
return true;
}
static bool ResolvePrimitiveRestart(const RenderCommandBuffer& buffer,
vk::PrimitiveTopology topology, uint32_t index_type_and_size) {
const auto control = buffer.GetUserConfig().GetPrimitiveResetControl();
EXIT_NOT_IMPLEMENTED((control & ~0x3u) != 0);
if ((control & 0x1u) == 0) {
return false;
}
switch (buffer.GetUserConfig().GetPrimType()) {
case Prospero::PrimitiveType::kLineStrip:
case Prospero::PrimitiveType::kTriFan:
case Prospero::PrimitiveType::kTriStrip: break;
default: return false;
}
if (topology != vk::PrimitiveTopology::eLineStrip &&
topology != vk::PrimitiveTopology::eTriangleStrip &&
topology != vk::PrimitiveTopology::eTriangleFan) {
return false;
}
uint32_t index_mask = 0;
switch (static_cast<Prospero::IndexType>(index_type_and_size)) {
case Prospero::IndexType::kIndex8: index_mask = 0xffu; break;
case Prospero::IndexType::kIndex16: index_mask = 0xffffu; break;
case Prospero::IndexType::kIndex32: index_mask = 0xffffffffu; break;
default: EXIT("unknown index_type_and_size: %u\n", index_type_and_size);
}
const auto reset_index = buffer.GetRegisters().GetPrimitiveResetIndex();
if ((control & 0x2u) != 0 && (reset_index & ~index_mask) != 0) {
return false;
}
EXIT_NOT_IMPLEMENTED((reset_index & index_mask) != index_mask);
return true;
}
bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw,
uint32_t render_target_slice_offset,
@@ -908,14 +1020,14 @@ bool RenderExecutor::PrepareDrawRenderState(uint64_t submit_id, RenderCommandBuf
}
ResolveRenderDepthTarget(submit_id, buffer, state.depth_info);
state.ps_active = DrawHasActivePixelShader(buffer);
const bool with_depth = (state.depth_info.format != vk::Format::eUndefined &&
static_cast<bool>(state.depth_info.image_id));
if (state.color_count == 0 && !with_depth) {
if (state.color_count == 0 && !with_depth && !state.ps_active) {
LogFramebufferSkip(draw.name, state.color_info[0], state.depth_info, buffer,
draw.index_count, draw.flags);
return false;
}
state.ps_active = DrawHasActivePixelShader(buffer, state, draw);
return true;
}
@@ -1051,7 +1163,7 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
const DrawEmitInfo& emit) {
EXIT_IF(draw.name == nullptr);
switch (static_cast<Prospero::PrimitiveType>(ucfg.GetPrimType())) {
switch (ucfg.GetPrimType()) {
case Prospero::PrimitiveType::kPointList:
case Prospero::PrimitiveType::kLineList:
case Prospero::PrimitiveType::kLineStrip:
@@ -1069,7 +1181,7 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
break;
case Prospero::PrimitiveType::kRectListLegacy:
if (emit.indexed) {
EXIT("unknown primitive type: %u\n", ucfg.GetPrimType());
EXIT("unknown primitive type: %u\n", static_cast<uint32_t>(ucfg.GetPrimType()));
}
// Sarah
EXIT_NOT_IMPLEMENTED(!(draw.index_count == 3 && vs_input_info.buffers_num == 0));
@@ -1087,7 +1199,7 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
}
}
break;
default: EXIT("unknown primitive type: %u\n", ucfg.GetPrimType());
default: EXIT("unknown primitive type: %u\n", static_cast<uint32_t>(ucfg.GetPrimType()));
}
}
@@ -1095,8 +1207,8 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
const DrawCallInfo& draw, DrawRenderState& state,
vk::PrimitiveTopology topology, const DrawEmitInfo& emit,
const DrawIndexBufferSource& index_source,
bool log_pipeline_phase, bool set_bind_debug,
bool set_auto_debug) {
bool primitive_restart_enable, bool log_pipeline_phase,
bool set_bind_debug, bool set_auto_debug) {
EXIT_IF(draw.name == nullptr);
auto& ucfg = buffer.GetUserConfig();
@@ -1113,7 +1225,8 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
}
auto& pipeline = m_context.GetPipelineCache().CreateGraphicsPipeline(
state.color_info, state.color_count, state.depth_info, state.vs_input_info, buffer,
&state.ps_input_info, topology, state.ps_active, state.vs_shader, state.ps_shader);
&state.ps_input_info, topology, primitive_restart_enable, state.ps_active, state.vs_shader,
state.ps_shader);
// Resource preparation above may synchronously finish and restart the scheduler. From this
// point onward, every operation targets the current command buffer and cannot touch guest
@@ -1237,6 +1350,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
vk::IndexType index_type = vk::IndexType::eUint16;
uint64_t index_size = 0;
bool expand_index8_to_u16 = false;
const bool primitive_restart = ResolvePrimitiveRestart(buffer, topology, index_type_and_size);
switch (static_cast<Prospero::IndexType>(index_type_and_size)) {
case Prospero::IndexType::kIndex16:
@@ -1267,7 +1381,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
const auto* src = static_cast<const uint8_t*>(index_addr);
expanded_indices.resize(index_count);
for (uint32_t i = 0; i < index_count; i++) {
expanded_indices[i] = src[i];
expanded_indices[i] = primitive_restart && src[i] == 0xffu ? 0xffffu : src[i];
}
}
@@ -1297,8 +1411,8 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
emit.indexed = true;
emit.vertex_offset = vertex_offset;
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, true, true,
false);
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source,
primitive_restart, true, true, false);
ResetBindings();
}
@@ -1387,9 +1501,8 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u
}
LogDrawStateIfNeeded(buffer, draw, state, false,
ucfg.GetPrimType() ==
Prospero::GpuEnumValue(Prospero::PrimitiveType::kRectListLegacy),
0, nullptr);
ucfg.GetPrimType() == Prospero::PrimitiveType::kRectListLegacy, 0,
nullptr);
const auto vertex_offset = ResolveVertexOffset(ucfg.GetIndexOffset(), state.vs_input_info) +
static_cast<int32_t>(first_vertex);
@@ -1398,7 +1511,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u
DrawIndexBufferSource index_source {};
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false, false,
true);
false, true);
ResetBindings();
}
+5 -5
View File
@@ -83,11 +83,11 @@ constexpr FormatMapping kFormatMappings[] = {
};
constexpr auto MakeFormatLookup() {
constexpr auto kMaxFormat = Prospero::GpuEnumValue(Prospero::BufferFormat::kBc7Srgb);
constexpr auto kMaxFormat = static_cast<size_t>(Prospero::BufferFormat::kBc7Srgb);
std::array<vk::Format, kMaxFormat + 1> lookup {};
lookup.fill(vk::Format::eUndefined);
for (const auto& mapping: kFormatMappings) {
lookup[Prospero::GpuEnumValue(mapping.guest)] = mapping.host;
lookup[static_cast<size_t>(mapping.guest)] = mapping.host;
}
return lookup;
}
@@ -112,9 +112,9 @@ std::string VulkanToString(vk::QueueFlags value) {
return vk::to_string(value);
}
vk::Format VulkanFormat(uint32_t guest_format) {
return guest_format < kFormatLookup.size() ? kFormatLookup[guest_format]
: vk::Format::eUndefined;
vk::Format VulkanFormat(Prospero::BufferFormat guest_format) {
const auto index = static_cast<size_t>(guest_format);
return index < kFormatLookup.size() ? kFormatLookup[index] : vk::Format::eUndefined;
}
void RequireVulkanSuccess(vk::Result result, const char* operation) {
+3 -6
View File
@@ -10,6 +10,8 @@
#define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
#include "graphics/guest_gpu/gpu_defs.h"
#include <cstdint>
#include <string>
#include <vector>
@@ -20,16 +22,11 @@ namespace Libs::Graphics {
using VulkanMemoryBarrier = vk::MemoryBarrier;
template <typename Handle>
[[nodiscard]] void* VulkanHandleToPointer(Handle handle) {
return reinterpret_cast<void*>(static_cast<typename Handle::CType>(handle));
}
std::string VulkanToString(vk::Result value);
std::string VulkanToString(vk::Format value);
std::string VulkanToString(vk::ImageLayout value);
std::string VulkanToString(vk::QueueFlags value);
vk::Format VulkanFormat(uint32_t guest_format);
vk::Format VulkanFormat(Prospero::BufferFormat guest_format);
void RequireVulkanSuccess(vk::Result result, const char* operation);
template <typename T, typename Enumerator>
+20 -122
View File
@@ -32,64 +32,21 @@ namespace DialogIme = Libs::Dialog::ImeDialog;
namespace Ime {
enum class Type : uint32_t { Default = 0, BasicLatin = 1, Url = 2, Mail = 3, Number = 4 };
enum class EnterLabel : uint32_t { Default = 0, Send = 1, Search = 2, Go = 3 };
enum class Alignment : uint32_t { Start = 0, Center = 1, End = 2 };
enum class ExternalAction : uint8_t {
None,
Text,
Backspace,
MoveLeft,
MoveRight,
Cancel,
Accept,
Newline,
};
using Type = ImeCommon::Type;
using EnterLabel = ImeCommon::EnterLabel;
using Alignment = ImeCommon::Alignment;
using ExternalAction = ImeCommon::ExternalAction;
using ExternalInput = ImeCommon::ExternalInput;
using HostSnapshot = ImeCommon::HostSnapshot;
struct Keycode {
uint16_t keycode = 0;
char16_t character = u'\0';
uint32_t status = 0;
uint32_t type = 0;
int32_t user_id = 0;
uint32_t resource_id = 0;
uint64_t timestamp = 0;
};
struct ExternalInput {
Keycode key;
ExternalAction action = ExternalAction::None;
std::u16string text;
};
struct HostSnapshot {
uint64_t generation = 0;
Type type = Type::Default;
EnterLabel enter_label = EnterLabel::Default;
uint32_t option = 0;
uint32_t max_text_length = 0;
uint32_t cursor = 0;
uint32_t disable_device = 0;
bool key_panel_visible = true;
float posx = 0.0f;
float posy = 0.0f;
Alignment horizontal_alignment = Alignment::Start;
Alignment vertical_alignment = Alignment::Start;
uint32_t panel_width = 0;
uint32_t panel_height = 0;
std::u16string text;
std::u16string title;
std::u16string placeholder;
};
constexpr uint32_t OPTION_MULTILINE = 0x00000001;
constexpr uint32_t OPTION_NO_AUTO_CAPITALIZE = 0x00000002;
constexpr uint32_t OPTION_PASSWORD = 0x00000004;
constexpr uint32_t OPTION_FIXED_POSITION = 0x00000040;
constexpr uint32_t OPTION_DISABLE_POSITION_ADJ = 0x00000800;
constexpr uint32_t OPTION_USE_OVER_2K = 0x00004000;
constexpr uint32_t DISABLE_DEVICE_CONTROLLER = 0x00000001;
constexpr uint32_t DISABLE_DEVICE_EXT_KEYBOARD = 0x00000002;
constexpr uint32_t OPTION_MULTILINE = ImeCommon::OPTION_MULTILINE;
constexpr uint32_t OPTION_NO_AUTO_CAPITALIZE = ImeCommon::OPTION_NO_AUTO_CAPITALIZE;
constexpr uint32_t OPTION_PASSWORD = ImeCommon::OPTION_PASSWORD;
constexpr uint32_t OPTION_FIXED_POSITION = ImeCommon::OPTION_FIXED_POSITION;
constexpr uint32_t OPTION_DISABLE_POSITION_ADJ = ImeCommon::OPTION_DISABLE_POSITION_ADJUST;
constexpr uint32_t OPTION_USE_OVER_2K = ImeCommon::OPTION_USE_OVER_2K;
constexpr uint32_t DISABLE_DEVICE_CONTROLLER = ImeCommon::DISABLE_DEVICE_CONTROLLER;
constexpr uint32_t DISABLE_DEVICE_EXT_KEYBOARD = ImeCommon::DISABLE_DEVICE_EXT_KEYBOARD;
constexpr uint64_t CORE_GENERATION_BIT = uint64_t {1} << 63;
uint64_t PackCoreGeneration(uint64_t generation) {
@@ -108,49 +65,11 @@ bool GetHostSnapshot(HostSnapshot* snapshot) {
if (snapshot == nullptr) {
return false;
}
CoreIme::HostSnapshot core;
if (CoreIme::GetHostSnapshot(&core)) {
snapshot->generation = PackCoreGeneration(core.generation);
snapshot->type = static_cast<Type>(core.type);
snapshot->enter_label = static_cast<EnterLabel>(core.enter_label);
snapshot->option = core.option;
snapshot->max_text_length = core.max_text_length;
snapshot->cursor = core.cursor;
snapshot->disable_device = core.disable_device;
snapshot->key_panel_visible = core.key_panel_visible;
snapshot->posx = core.posx;
snapshot->posy = core.posy;
snapshot->horizontal_alignment = static_cast<Alignment>(core.horizontal_alignment);
snapshot->vertical_alignment = static_cast<Alignment>(core.vertical_alignment);
snapshot->panel_width = core.panel_width;
snapshot->panel_height = core.panel_height;
snapshot->text = std::move(core.text);
snapshot->title.clear();
snapshot->placeholder.clear();
if (CoreIme::GetHostSnapshot(snapshot)) {
snapshot->generation = PackCoreGeneration(snapshot->generation);
return true;
}
DialogIme::HostSnapshot dialog;
if (!DialogIme::GetHostSnapshot(&dialog)) {
return false;
}
snapshot->generation = dialog.generation;
snapshot->type = static_cast<Type>(dialog.type);
snapshot->enter_label = static_cast<EnterLabel>(dialog.enter_label);
snapshot->option = dialog.option;
snapshot->max_text_length = dialog.max_text_length;
snapshot->cursor = dialog.cursor;
snapshot->disable_device = dialog.disable_device;
snapshot->key_panel_visible = dialog.key_panel_visible;
snapshot->posx = dialog.posx;
snapshot->posy = dialog.posy;
snapshot->horizontal_alignment = static_cast<Alignment>(dialog.horizontal_alignment);
snapshot->vertical_alignment = static_cast<Alignment>(dialog.vertical_alignment);
snapshot->panel_width = dialog.panel_width;
snapshot->panel_height = dialog.panel_height;
snapshot->text = std::move(dialog.text);
snapshot->title = std::move(dialog.title);
snapshot->placeholder = std::move(dialog.placeholder);
return true;
return DialogIme::GetHostSnapshot(snapshot);
}
bool HostInsertText(uint64_t generation, std::u16string_view text) {
@@ -175,30 +94,9 @@ bool HostCancel(uint64_t generation) {
}
bool HostQueueExternalInput(uint64_t generation, ExternalInput input) {
if (!IsCoreGeneration(generation)) {
DialogIme::ExternalInput dialog;
dialog.key.keycode = input.key.keycode;
dialog.key.character = input.key.character;
dialog.key.status = input.key.status;
dialog.key.type = input.key.type;
dialog.key.user_id = input.key.user_id;
dialog.key.resource_id = input.key.resource_id;
dialog.key.timestamp = input.key.timestamp;
dialog.action = static_cast<DialogIme::ExternalAction>(input.action);
dialog.text = std::move(input.text);
return DialogIme::HostQueueExternalInput(generation, std::move(dialog));
}
CoreIme::ExternalInput core;
core.key.keycode = input.key.keycode;
core.key.character = input.key.character;
core.key.status = input.key.status;
core.key.type = input.key.type;
core.key.user_id = input.key.user_id;
core.key.resource_id = input.key.resource_id;
core.key.timestamp = input.key.timestamp;
core.action = static_cast<CoreIme::ExternalAction>(input.action);
core.text = std::move(input.text);
return CoreIme::HostQueueExternalInput(UnpackGeneration(generation), std::move(core));
return IsCoreGeneration(generation)
? CoreIme::HostQueueExternalInput(UnpackGeneration(generation), std::move(input))
: DialogIme::HostQueueExternalInput(generation, std::move(input));
}
} // namespace Ime
+71 -275
View File
@@ -1,15 +1,12 @@
#include "graphics/presentation/renderDoc.h"
#include "SDL_syswm.h"
#include "SDL_version.h"
#include "SDL_video.h"
#include "common/logging/log.h"
#include <array>
#include <atomic>
#include <cstdint>
#include <cstring>
#include <chrono>
#include <filesystem>
#include <renderdoc_app.h>
#include <string>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
@@ -21,362 +18,161 @@
#undef max
#else
#include <dlfcn.h>
// RenderDoc uses Windows-style names in its cross-platform API.
#define __cdecl
using HMODULE = void*;
#endif
namespace Libs::Graphics {
using RenderDocDevicePointer = void*;
using RenderDocWindowHandle = void*;
enum RenderDocVersion {
eRENDERDOC_API_Version_1_4_2 = 10402,
};
enum RenderDocInputButton {
eRENDERDOC_Key_NonPrintable = 0x100,
eRENDERDOC_Key_Divide,
eRENDERDOC_Key_Multiply,
eRENDERDOC_Key_Subtract,
eRENDERDOC_Key_Plus,
eRENDERDOC_Key_F1,
};
using pRENDERDOC_SetCaptureKeys = void(__cdecl*)(RenderDocInputButton* keys, int num);
using pRENDERDOC_SetCaptureFilePathTemplate = void(__cdecl*)(const char* pathtemplate);
using pRENDERDOC_GetCaptureFilePathTemplate = const char*(__cdecl*)();
using pRENDERDOC_GetNumCaptures = uint32_t(__cdecl*)();
using pRENDERDOC_GetCapture = uint32_t(__cdecl*)(uint32_t idx, char* filename, uint32_t* pathlength,
uint64_t* timestamp);
using pRENDERDOC_UnloadCrashHandler = void(__cdecl*)();
using pRENDERDOC_SetActiveWindow = void(__cdecl*)(RenderDocDevicePointer device,
RenderDocWindowHandle wndHandle);
using pRENDERDOC_StartFrameCapture = void(__cdecl*)(RenderDocDevicePointer device,
RenderDocWindowHandle wndHandle);
using pRENDERDOC_IsFrameCapturing = uint32_t(__cdecl*)();
using pRENDERDOC_EndFrameCapture = uint32_t(__cdecl*)(RenderDocDevicePointer device,
RenderDocWindowHandle wndHandle);
using pRENDERDOC_GetAPI = int(__cdecl*)(RenderDocVersion version, void** out_api_pointers);
struct RenderDocApi {
void* GetAPIVersion;
void* SetCaptureOptionU32;
void* SetCaptureOptionF32;
void* GetCaptureOptionU32;
void* GetCaptureOptionF32;
void* SetFocusToggleKeys;
pRENDERDOC_SetCaptureKeys SetCaptureKeys;
void* GetOverlayBits;
void* MaskOverlayBits;
void* RemoveHooks;
pRENDERDOC_UnloadCrashHandler UnloadCrashHandler;
pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate;
pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate;
pRENDERDOC_GetNumCaptures GetNumCaptures;
pRENDERDOC_GetCapture GetCapture;
void* TriggerCapture;
void* IsTargetControlConnected;
void* LaunchReplayUI;
pRENDERDOC_SetActiveWindow SetActiveWindow;
pRENDERDOC_StartFrameCapture StartFrameCapture;
pRENDERDOC_IsFrameCapturing IsFrameCapturing;
pRENDERDOC_EndFrameCapture EndFrameCapture;
void* TriggerMultiFrameCapture;
void* SetCaptureFileComments;
void* DiscardFrameCapture;
};
enum class RenderDocState : uint32_t {
Idle,
Requested,
Capturing,
};
static RenderDocApi* g_api = nullptr;
static HMODULE g_module = nullptr;
static RenderDocDevicePointer g_device = nullptr;
static RenderDocWindowHandle g_window = nullptr;
static RENDERDOC_API_1_6_0* g_api = nullptr;
static std::atomic<RenderDocState> g_state = RenderDocState::Idle;
static std::atomic_bool g_init_done = false;
static std::atomic_bool g_unavailable_log = false;
static RenderDocDevicePointer GetRenderDocDevicePointer(vk::Instance instance) {
if (instance == nullptr) {
return nullptr;
}
return VulkanHandleToPointer(instance);
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static bool BindRenderDocApi(HMODULE module) {
auto* get_api = reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(module, "RENDERDOC_GetAPI"));
if (get_api == nullptr) {
return false;
}
void* api = nullptr;
if (get_api(eRENDERDOC_API_Version_1_4_2, &api) == 0 || api == nullptr) {
return false;
}
g_module = module;
g_api = static_cast<RenderDocApi*>(api);
g_api->SetCaptureFilePathTemplate("_RenderDoc/kyty");
g_api->SetCaptureKeys(nullptr, 0);
g_api->UnloadCrashHandler();
char module_path[MAX_PATH] = {};
GetModuleFileNameA(module, module_path, sizeof(module_path));
LOGF("RenderDoc: bound API from %s\n", module_path);
return true;
}
static RenderDocWindowHandle GetRenderDocWindowHandle(SDL_Window* window) {
if (window == nullptr) {
return nullptr;
}
SDL_SysWMinfo info {};
SDL_VERSION(&info.version);
if (SDL_GetWindowWMInfo(window, &info) != SDL_TRUE || info.subsystem != SDL_SYSWM_WINDOWS) {
return nullptr;
}
return info.info.win.window;
}
#else
static bool BindRenderDocApi(HMODULE module) {
static bool BindRenderDocApi(void* module) {
auto* get_api = reinterpret_cast<pRENDERDOC_GetAPI>(::dlsym(module, "RENDERDOC_GetAPI"));
#endif
if (get_api == nullptr) {
return false;
}
void* api = nullptr;
if (get_api(eRENDERDOC_API_Version_1_4_2, &api) == 0 || api == nullptr) {
if (get_api(eRENDERDOC_API_Version_1_6_0, &api) != 1 || api == nullptr) {
return false;
}
g_module = module;
g_api = static_cast<RenderDocApi*>(api);
g_api->SetCaptureFilePathTemplate("_RenderDoc/kyty");
g_api = static_cast<RENDERDOC_API_1_6_0*>(api);
g_api->SetCaptureKeys(nullptr, 0);
g_api->UnloadCrashHandler();
Dl_info info {};
if (::dladdr(reinterpret_cast<void*>(get_api), &info) != 0 && info.dli_fname != nullptr) {
LOGF("RenderDoc: bound API from %s\n", info.dli_fname);
} else {
LOGF("RenderDoc: bound API\n");
}
LOGF("RenderDoc: API 1.6.0 bound\n");
return true;
}
static RenderDocWindowHandle GetRenderDocWindowHandle(SDL_Window* window) {
if (window == nullptr) {
return nullptr;
}
SDL_SysWMinfo info {};
SDL_VERSION(&info.version);
if (SDL_GetWindowWMInfo(window, &info) != SDL_TRUE) {
return nullptr;
}
#if defined(SDL_VIDEO_DRIVER_X11)
if (info.subsystem == SDL_SYSWM_X11) {
// RenderDoc takes the raw xlib Window id in the pointer slot, not a Display*.
return reinterpret_cast<RenderDocWindowHandle>(
static_cast<uintptr_t>(info.info.x11.window));
}
#endif
// Wayland capture works without an active-window handle.
static std::atomic_bool logged = false;
if (!logged.exchange(true)) {
LOGF("RenderDoc: no native window handle for SDL subsystem %d (Wayland?); the in-app "
"overlay is unavailable, but --rd captures still work\n",
static_cast<int>(info.subsystem));
}
return nullptr;
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static bool IsAvailable() {
return g_api != nullptr && g_device != nullptr && g_window != nullptr;
}
#else
static bool IsAvailable() {
return g_api != nullptr && g_device != nullptr;
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
void RenderDocInit() {
bool expected = false;
if (!g_init_done.compare_exchange_strong(expected, true)) {
if (g_api != nullptr) {
return;
}
HKEY h_reg_key;
LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Classes\\RenderDoc.RDCCapture.1\\DefaultIcon\\", 0,
KEY_READ, &h_reg_key);
if (result != ERROR_SUCCESS) {
return;
}
std::array<wchar_t, MAX_PATH> key_str {};
DWORD str_sz_out {key_str.size()};
result = RegQueryValueExW(h_reg_key, L"", 0, NULL, (LPBYTE)key_str.data(), &str_sz_out);
RegCloseKey(h_reg_key);
if (result != ERROR_SUCCESS) {
return;
}
std::filesystem::path path {key_str.cbegin(), key_str.cend()};
path = path.parent_path().append("renderdoc.dll");
const auto path_to_lib = path.generic_string();
auto* module = LoadLibraryA(path_to_lib.c_str());
auto* module = GetModuleHandleA("renderdoc.dll");
if (module == nullptr) {
return;
HKEY key = nullptr;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Classes\\RenderDoc.RDCCapture.1\\DefaultIcon\\", 0, KEY_READ,
&key) != ERROR_SUCCESS) {
return;
}
std::array<wchar_t, MAX_PATH> path_buffer {};
DWORD path_size = static_cast<DWORD>(path_buffer.size() * sizeof(wchar_t));
const auto result = RegQueryValueExW(
key, L"", nullptr, nullptr, reinterpret_cast<LPBYTE>(path_buffer.data()), &path_size);
RegCloseKey(key);
if (result != ERROR_SUCCESS) {
return;
}
auto path = std::filesystem::path(path_buffer.data()).parent_path() / "renderdoc.dll";
module = LoadLibraryW(path.c_str());
if (module == nullptr) {
return;
}
}
if (!BindRenderDocApi(module)) {
LOGF("RenderDoc: API 1.4.2 is not available; in-app capture disabled\n");
FreeLibrary(module);
return;
LOGF("RenderDoc: API 1.6.0 is unavailable\n");
}
}
#else
void RenderDocInit() {
bool expected = false;
if (!g_init_done.compare_exchange_strong(expected, true)) {
if (g_api != nullptr) {
return;
}
// Prefer an injected RenderDoc instance.
auto* module = ::dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
if (module == nullptr) {
module = ::dlopen("librenderdoc.so", RTLD_NOW);
}
if (module == nullptr) {
LOGF("RenderDoc: librenderdoc.so was not found; in-app capture disabled\n");
return;
}
if (!BindRenderDocApi(module)) {
LOGF("RenderDoc: API 1.4.2 is not available; in-app capture disabled\n");
LOGF("RenderDoc: API 1.6.0 is unavailable\n");
::dlclose(module);
return;
}
}
#endif
void RenderDocSetActiveWindow(vk::Instance instance, SDL_Window* window) {
if (g_api == nullptr) {
return;
}
g_device = GetRenderDocDevicePointer(instance);
g_window = GetRenderDocWindowHandle(window);
if (g_device == nullptr || g_window == nullptr) {
LOGF("RenderDoc: active Vulkan window was not registered\n");
return;
}
g_api->SetActiveWindow(g_device, g_window);
LOGF("RenderDoc: active Vulkan window registered\n");
}
void RenderDocRequestCapture() {
if (!IsAvailable()) {
if (g_api == nullptr) {
if (!g_unavailable_log.exchange(true)) {
LOGF("RenderDoc: capture requested, but RenderDoc is not available\n");
LOGF("RenderDoc: capture requested, but RenderDoc is unavailable\n");
}
return;
}
RenderDocState expected = RenderDocState::Idle;
if (g_state.compare_exchange_strong(expected, RenderDocState::Requested)) {
LOGF("RenderDoc: capture requested; next complete presented frame will be captured\n");
} else {
LOGF("RenderDoc: capture request ignored because a capture is already pending\n");
LOGF("RenderDoc: capture requested\n");
}
}
static void LogNewestCapture() {
const auto count = g_api->GetNumCaptures();
if (count == 0) {
return;
}
char filename[4096] = {};
uint32_t path_length = sizeof(filename);
uint64_t timestamp = 0;
if (g_api->GetCapture(count - 1, filename, &path_length, &timestamp) != 0) {
filename[sizeof(filename) - 1] = '\0';
LOGF("RenderDoc: wrote capture %s\n", filename);
}
bool RenderDocCaptureRequested() {
return g_state.load(std::memory_order_acquire) == RenderDocState::Requested;
}
void RenderDocOnPresent() {
if (!IsAvailable()) {
bool RenderDocCaptureInProgress() {
return g_state.load(std::memory_order_acquire) == RenderDocState::Capturing;
}
void RenderDocStartCapture() {
RenderDocState expected = RenderDocState::Requested;
if (g_api == nullptr || !g_state.compare_exchange_strong(expected, RenderDocState::Capturing,
std::memory_order_acq_rel)) {
return;
}
switch (g_state.load()) {
case RenderDocState::Idle: return;
case RenderDocState::Requested:
if (g_api->IsFrameCapturing() != 0) {
LOGF("RenderDoc: capture request ignored because RenderDoc is already capturing\n");
g_state.store(RenderDocState::Idle);
return;
}
if (g_api->IsFrameCapturing() != 0) {
g_state.store(RenderDocState::Idle, std::memory_order_release);
LOGF("RenderDoc: capture request ignored because a capture is already active\n");
return;
}
g_api->StartFrameCapture(nullptr, nullptr);
if (g_api->IsFrameCapturing() == 0) {
LOGF("RenderDoc: StartFrameCapture returned, but RenderDoc is not capturing\n");
g_state.store(RenderDocState::Idle);
return;
}
g_state.store(RenderDocState::Capturing);
LOGF("RenderDoc: capture started\n");
return;
case RenderDocState::Capturing: break;
const auto capture_id = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
const auto capture_path = "_RenderDoc/kyty_" + std::to_string(capture_id);
g_api->SetCaptureFilePathTemplate(capture_path.c_str());
g_api->StartFrameCapture(nullptr, nullptr);
if (g_api->IsFrameCapturing() == 0) {
g_state.store(RenderDocState::Idle, std::memory_order_release);
LOGF("RenderDoc: capture failed to start\n");
return;
}
LOGF("RenderDoc: capture started\n");
}
void RenderDocEndCapture() {
if (g_api == nullptr || !RenderDocCaptureInProgress()) {
return;
}
const auto ok = g_api->EndFrameCapture(nullptr, nullptr);
g_state.store(RenderDocState::Idle);
if (ok != 0) {
const auto count = g_api->GetNumCaptures();
LOGF("RenderDoc: capture finished, count=%u\n", count);
LogNewestCapture();
} else {
LOGF("RenderDoc: capture failed\n");
}
g_state.store(RenderDocState::Idle, std::memory_order_release);
LOGF(ok != 0 ? "RenderDoc: capture finished\n" : "RenderDoc: capture failed\n");
}
} // namespace Libs::Graphics
+5 -7
View File
@@ -1,17 +1,15 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_RENDERDOC_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_RENDERDOC_H_
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
struct SDL_Window;
namespace Libs::Graphics {
void RenderDocInit();
void RenderDocSetActiveWindow(vk::Instance instance, SDL_Window* window);
void RenderDocRequestCapture();
void RenderDocOnPresent();
void RenderDocStartCapture();
void RenderDocEndCapture();
[[nodiscard]] bool RenderDocCaptureRequested();
[[nodiscard]] bool RenderDocCaptureInProgress();
} // namespace Libs::Graphics
+1 -2
View File
@@ -534,8 +534,7 @@ Graphics::ImageInfo BufferAttributeGroup::ImageInfo(const VideoOutBuffer& buffer
if (!Graphics::DecodeVideoOutPixelFormat(attribute.pixel_format, pixel_format)) {
EXIT("unsupported video-out pixel format: 0x%016" PRIx64 "\n", attribute.pixel_format);
}
const auto tile_mode =
Graphics::Prospero::GpuEnumValue(Graphics::Prospero::TileMode::kRenderTarget);
const auto tile_mode = Graphics::Prospero::TileMode::kRenderTarget;
const auto pitch =
Graphics::TileGetTexturePitch(pixel_format.guest_format, attribute.width, tile_mode);
Graphics::TileSizeAlign total {};
@@ -119,7 +119,8 @@ public:
}
const bool reserved =
binding.key == SDLK_ESCAPE || binding.key == SDLK_SPACE || binding.key == SDLK_F1;
binding.key == SDLK_ESCAPE || binding.key == SDLK_SPACE || binding.key == SDLK_F1 ||
binding.key == SDLK_F11;
if (binding.control == INVALID_CONTROL || reserved ||
(binding.key == SDLK_UNKNOWN && binding.mouse_button == 0)) {
EXIT("Invalid input mapping: %s\n", value.c_str());
@@ -22,7 +22,6 @@
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "common/subsystems.h"
#include "common/systemInfo.h"
#include "common/threads.h"
#include "common/timer.h"
@@ -33,7 +32,6 @@
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/imeOverlay.h"
#include "graphics/presentation/presenter.h"
#include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/videoOut.h"
#include "graphics/presentation/window/windowInternal.h"
#include "libs/controller.h"
@@ -867,7 +865,6 @@ void Presenter::Present(Frame& frame, bool reuse) {
continue;
}
RenderDocOnPresent();
m_impl->presented_ime_revision.store(ime_visual.revision, std::memory_order_release);
window.UpdateTitle();
m_impl->frames.Release(&frame, true);
@@ -22,7 +22,6 @@
#include "common/logging/log.h"
#include "common/profiler.h"
#include "common/stringUtils.h"
#include "common/subsystems.h"
#include "common/systemInfo.h"
#include "common/threads.h"
#include "common/timer.h"
@@ -33,7 +32,6 @@
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/imeOverlay.h"
#include "graphics/presentation/presenter.h"
#include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/videoOut.h"
#include "graphics/presentation/window.h"
#include "graphics/presentation/window/windowInternal.h"
@@ -556,6 +554,14 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
EXIT_NOT_IMPLEMENTED(supported_features2.features.depthBiasClamp != VK_TRUE);
features12.timelineSemaphore = VK_TRUE;
if (Config::GpuAssistedValidationEnabled()) {
if (supported_features12.bufferDeviceAddress != VK_TRUE) {
EXIT("--gpu-assisted-validation requires bufferDeviceAddress, unsupported by this "
"device\n");
}
features12.bufferDeviceAddress = VK_TRUE;
}
vk::PhysicalDeviceFeatures device_features {};
device_features.fragmentStoresAndAtomics = VK_TRUE;
device_features.samplerAnisotropy = VK_TRUE;
@@ -573,6 +579,9 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
graphics.sample_rate_shading_enabled = true;
device_features.vertexPipelineStoresAndAtomics =
supported_features2.features.vertexPipelineStoresAndAtomics;
if (Config::GpuAssistedValidationEnabled()) {
device_features.shaderInt64 = supported_features2.features.shaderInt64;
}
if (graphics.subgroup_size_control_enabled &&
supported_features13.subgroupSizeControl == VK_TRUE) {
@@ -602,11 +611,11 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
robustness2_ext_enabled && robustness2.robustImageAccess2 == VK_TRUE ? "true" : "false");
vk::DeviceCreateInfo create_info {};
create_info.sType = vk::StructureType::eDeviceCreateInfo;
create_info.pNext = &features13;
create_info.flags = {};
create_info.pQueueCreateInfos = &queue_create_info;
create_info.queueCreateInfoCount = 1;
create_info.sType = vk::StructureType::eDeviceCreateInfo;
create_info.pNext = &features13;
create_info.flags = {};
create_info.pQueueCreateInfos = &queue_create_info;
create_info.queueCreateInfoCount = 1;
create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
create_info.ppEnabledExtensionNames = device_extensions.data();
create_info.pEnabledFeatures = &device_features;
@@ -835,23 +844,26 @@ void WindowContext::CreateVulkan() {
app_info.engineVersion = 1;
app_info.apiVersion = VULKAN_TARGET_API_VERSION; // NOLINT
vk::ValidationFeatureEnableEXT enabled_features[] = {
if (Config::SpirvDebugPrintfEnabled() && Config::GpuAssistedValidationEnabled()) {
EXIT("--spirv-debug-printf and --gpu-assisted-validation are mutually exclusive\n");
}
vk::ValidationFeatureEnableEXT enabled_features[3] = {};
uint32_t enabled_features_count = 0;
#ifdef KYTY_ENABLE_BEST_PRACTICES
vk::ValidationFeatureEnableEXT::eBestPractices,
enabled_features[enabled_features_count++] = vk::ValidationFeatureEnableEXT::eBestPractices;
#endif
#ifdef KYTY_ENABLE_DEBUG_PRINTF
vk::ValidationFeatureEnableEXT::eDebugPrintf,
#endif
};
uint32_t enabled_features_count =
sizeof(enabled_features) / sizeof(vk::ValidationFeatureEnableEXT);
#ifdef KYTY_ENABLE_DEBUG_PRINTF
if (!Config::SpirvDebugPrintfEnabled()) {
enabled_features_count--;
if (Config::SpirvDebugPrintfEnabled()) {
enabled_features[enabled_features_count++] = vk::ValidationFeatureEnableEXT::eDebugPrintf;
}
#endif
if (Config::GpuAssistedValidationEnabled()) {
enabled_features[enabled_features_count++] = vk::ValidationFeatureEnableEXT::eGpuAssisted;
LOGF("Vulkan GPU-assisted validation is enabled; expect a large slowdown\n");
std::printf("Vulkan GPU-assisted validation is enabled; expect a large slowdown\n");
std::fflush(stdout);
}
vk::ValidationFeaturesEXT validation_features {};
validation_features.sType = vk::StructureType::eValidationFeaturesEXT;
@@ -987,7 +999,6 @@ void WindowContext::CreateVulkan() {
render_context = std::make_unique<RenderContext>(graphic_ctx);
LibKernel::Memory::InstallGpuResources(&render_context->GetGpuResources());
presenter = std::make_unique<Presenter>(*this);
RenderDocSetActiveWindow(graphic_ctx.instance, window);
}
void WindowContext::RefreshSurfaceCapabilities() {
+35 -1
View File
@@ -219,7 +219,24 @@ static void GameEventTerminate(WindowLoopState& game) {
game.need_exit = true;
}
static void ToggleDesktopFullscreen() {
if (g_window == nullptr || g_window->window == nullptr) {
return;
}
const auto flags = static_cast<uint32_t>(SDL_GetWindowFlags(g_window->window));
const bool fullscreen =
(flags & static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP)) != 0u;
const auto mode =
fullscreen ? 0u : static_cast<uint32_t>(SDL_WINDOW_FULLSCREEN_DESKTOP);
if (SDL_SetWindowFullscreen(g_window->window, mode) != 0) {
LOGF("Toggle fullscreen failed: %s\n", SDL_GetError());
}
}
static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
static SDL_Keycode fullscreen_key = SDLK_UNKNOWN;
#ifdef KYTY_DBG_INPUT
LOGF("Key: time = %.04f, %s%s, %s%s, %s, scan = %d, key = %d, mod = %04" PRIx16 "\n",
key.timestamp_seconds, (key.down ? "down" : ""), (key.up ? "up" : ""),
@@ -237,13 +254,30 @@ static void GameEventKeyboard(WindowLoopState& game, const EventKeyboard& key) {
RenderDocRequestCapture();
}
break;
case SDLK_F11:
if (!key.repeat) {
ToggleDesktopFullscreen();
}
break;
case SDLK_RETURN:
case SDLK_KP_ENTER:
if (!key.repeat && (key.mod & KMOD_ALT) != 0) {
ToggleDesktopFullscreen();
fullscreen_key = key.key_code;
}
break;
default: break;
}
}
#endif
if ((key.down || key.up) && !key.repeat) {
const bool fullscreen_key_event =
fullscreen_key != SDLK_UNKNOWN && key.key_code == fullscreen_key;
if (fullscreen_key_event && key.up) {
fullscreen_key = SDLK_UNKNOWN;
}
if ((key.down || key.up) && !key.repeat && !fullscreen_key_event) {
HostInputKey(key.key_code, key.down);
}
}
@@ -1,51 +0,0 @@
#include "graphics/shader/recompiler/ExecMask.h"
namespace Libs::Graphics::ShaderRecompiler::Exec {
Mask FullMask(uint32_t wave_size) {
return {0xffffffffu, wave_size == 64u ? 0xffffffffu : 0u};
}
Mask ApplyWaveSize(Mask mask, uint32_t wave_size) {
const auto full = FullMask(wave_size);
mask.low &= full.low;
mask.high &= full.high;
return mask;
}
Mask ReadExec(const State& state) {
return ApplyWaveSize(state.exec, state.wave_size);
}
void WriteExec(State& state, Mask mask) {
state.exec = ApplyWaveSize(mask, state.wave_size);
}
void AndExec(State& state, Mask mask) {
WriteExec(state, {state.exec.low & mask.low, state.exec.high & mask.high});
}
void OrExec(State& state, Mask mask) {
WriteExec(state, {state.exec.low | mask.low, state.exec.high | mask.high});
}
void XorExec(State& state, Mask mask) {
WriteExec(state, {state.exec.low ^ mask.low, state.exec.high ^ mask.high});
}
void InvertExec(State& state) {
const auto full = FullMask(state.wave_size);
WriteExec(state, {~state.exec.low & full.low, ~state.exec.high & full.high});
}
bool IsExecZero(const State& state) {
const auto exec = ReadExec(state);
return exec.low == 0u && exec.high == 0u;
}
bool IsVccZero(const State& state) {
const auto vcc = ApplyWaveSize(state.vcc, state.wave_size);
return vcc.low == 0u && vcc.high == 0u;
}
} // namespace Libs::Graphics::ShaderRecompiler::Exec
-34
View File
@@ -1,34 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXECMASK_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXECMASK_H_
#include "common/common.h"
namespace Libs::Graphics::ShaderRecompiler::Exec {
struct Mask {
uint32_t low = 0;
uint32_t high = 0;
};
struct State {
uint32_t wave_size = 64;
Mask exec;
Mask vcc;
bool scc = false;
};
Mask FullMask(uint32_t wave_size);
Mask ApplyWaveSize(Mask mask, uint32_t wave_size);
Mask ReadExec(const State& state);
void WriteExec(State& state, Mask mask);
void AndExec(State& state, Mask mask);
void OrExec(State& state, Mask mask);
void XorExec(State& state, Mask mask);
void InvertExec(State& state);
bool IsExecZero(const State& state);
bool IsVccZero(const State& state);
} // namespace Libs::Graphics::ShaderRecompiler::Exec
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_EXECMASK_H_ */
@@ -2,18 +2,21 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "graphics/shader/recompiler/cfg/ShaderCFG.h"
#include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
#include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
#include "graphics/shader/recompiler/ir/BindingLayout.h"
#include "graphics/shader/recompiler/ir/ReadLaneElimination.h"
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/ResourceTracking.h"
#include "graphics/shader/recompiler/ir/ScalarProvenance.h"
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
#include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h"
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
#include "graphics/shader/recompiler/frontend/translate/Translate.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/recompiler/ir/ShaderInfoCollection.h"
#include "graphics/shader/recompiler/ir/SrtPatcher.h"
#include "graphics/shader/recompiler/ir/SrtWalker.h"
#include "graphics/shader/recompiler/ir/ValueProgram.h"
#include "graphics/shader/recompiler/ir/passes/BindingLayout.h"
#include "graphics/shader/recompiler/ir/passes/ConstantPropagation.h"
#include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h"
#include "graphics/shader/recompiler/ir/passes/ReadLaneElimination.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h"
#include "graphics/shader/recompiler/ir/passes/ShaderInfoCollection.h"
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
#include "graphics/shader/recompiler/ir/passes/SsaRewrite.h"
#include <algorithm>
#include <array>
@@ -43,7 +46,7 @@ std::string MakeIrDump(const CFG::Graph& cfg, const IR::Program& ir) {
std::string dump = "CFG:\n";
dump += CFG::GraphToString(cfg);
dump += "\nIR:\n";
dump += IR::ProgramToString(ir);
dump += ir.values != nullptr ? IR::ValueProgramToString(*ir.values) : IR::ProgramToString(ir);
return dump;
}
@@ -56,10 +59,22 @@ const char* StageName(ShaderType stage) {
}
}
std::string FormatCfgFailure(const CFG::Graph& cfg, const CompileOptions& options,
const std::string& reason) {
const auto block_id = cfg.failure_block != UINT32_MAX ? cfg.failure_block : cfg.entry_block;
return CFG::FormatBlockDiagnostic(cfg, block_id, StageName(options.stage), reason);
void LogDispatcherFallback(const CompileOptions& options, const CFG::Graph& cfg, const char* phase,
const std::string& reason) {
const auto* block = cfg.FindBlock(cfg.failure_block);
const auto start = block != nullptr ? block->start_pc : UINT32_MAX;
const auto end = block != nullptr ? block->end_pc : UINT32_MAX;
const auto predecessors = block != nullptr ? block->predecessors.size() : 0u;
const auto successors = block != nullptr ? block->successors.size() : 0u;
LOGF("%s CFG dispatcher fallback: stage=%s hash=0x%016" PRIx64
" phase=%s failure=%s block=%" PRIu32 " pc=0x%08" PRIx32 "..0x%08" PRIx32 " preds=%" PRIu64
" succs=%" PRIu64 " blocks=%" PRIu64 " loops=%" PRIu64 " back_edges=%" PRIu64
" reason=%s\n",
GetDumpLabel(options), StageName(options.stage), options.shader_hash, phase,
CFG::FailureKindToString(cfg.failure_kind).c_str(), cfg.failure_block, start, end,
static_cast<uint64_t>(predecessors), static_cast<uint64_t>(successors),
static_cast<uint64_t>(cfg.blocks.size()), static_cast<uint64_t>(cfg.natural_loops.size()),
static_cast<uint64_t>(cfg.back_edges.size()), reason.c_str());
}
bool InstructionMaySplitSpirvBlock(const IR::Instruction& inst) {
@@ -94,21 +109,31 @@ bool InstructionMaySplitSpirvBlock(const IR::Instruction& inst) {
case IR::Opcode::FlatStoreByte:
case IR::Opcode::FlatStoreShort:
case IR::Opcode::FlatStoreDword:
case IR::Opcode::DsReadUbyte:
case IR::Opcode::DsReadSbyte:
case IR::Opcode::DsReadUshort:
case IR::Opcode::DsReadSshort:
case IR::Opcode::DsReadB32:
case IR::Opcode::DsMinF32:
case IR::Opcode::DsMaxF32:
case IR::Opcode::DsWriteByte:
case IR::Opcode::DsWriteShort:
case IR::Opcode::DsWriteB32:
case IR::Opcode::DsWriteAddtidB32:
case IR::Opcode::DsReadAddtidB32:
case IR::Opcode::DsAppend:
case IR::Opcode::DsConsume:
case IR::Opcode::ImageLoad:
case IR::Opcode::ImageStore:
case IR::Opcode::ImageSample:
case IR::Opcode::ImageGather4:
case IR::Opcode::Export: return true;
default: return false;
}
}
bool NeedsDispatcherForStructuredLoopHeader(const IR::Program& ir, std::string* reason) {
bool NeedsDispatcherForStructuredLoopHeader(const IR::Program& ir, uint32_t* failure_block,
std::string* reason) {
for (const auto& block: ir.blocks) {
if (!block.terminator.loop_header) {
continue;
@@ -122,6 +147,9 @@ bool NeedsDispatcherForStructuredLoopHeader(const IR::Program& ir, std::string*
"lowering emits internal control flow: {}",
block.id, IR::InstructionToString(inst).c_str());
}
if (failure_block != nullptr) {
*failure_block = block.id;
}
return true;
}
}
@@ -199,19 +227,19 @@ uint32_t DecodedDstSize(const Decoder::Instruction& inst) {
}
uint32_t EmbeddedFetchDstSize(const Decoder::Instruction& inst) {
return inst.opcode == Decoder::Opcode::VMadU64U32 ? 2u : DecodedDstSize(inst);
return inst.opcode == Decoder::Opcode::V_MAD_U64_U32 ? 2u : DecodedDstSize(inst);
}
bool EmbeddedFetchHasBranch(Decoder::Opcode opcode) {
switch (opcode) {
case Decoder::Opcode::SSetpcB64:
case Decoder::Opcode::SBranch:
case Decoder::Opcode::SCbranchScc0:
case Decoder::Opcode::SCbranchScc1:
case Decoder::Opcode::SCbranchVccz:
case Decoder::Opcode::SCbranchVccnz:
case Decoder::Opcode::SCbranchExecz:
case Decoder::Opcode::SCbranchExecnz: return true;
case Decoder::Opcode::S_SETPC_B64:
case Decoder::Opcode::S_BRANCH:
case Decoder::Opcode::S_CBRANCH_SCC0:
case Decoder::Opcode::S_CBRANCH_SCC1:
case Decoder::Opcode::S_CBRANCH_VCCZ:
case Decoder::Opcode::S_CBRANCH_VCCNZ:
case Decoder::Opcode::S_CBRANCH_EXECZ:
case Decoder::Opcode::S_CBRANCH_EXECNZ: return true;
default: return false;
}
}
@@ -260,32 +288,32 @@ bool TryDecodedSmemOffset(const std::array<EmbeddedFetchSgprInfo, 108>& sgprs,
bool IsEmbeddedFetchSLoad(const Decoder::Instruction& inst) {
switch (inst.opcode) {
case Decoder::Opcode::SLoadDword:
case Decoder::Opcode::SLoadDwordx2:
case Decoder::Opcode::SLoadDwordx4:
case Decoder::Opcode::SLoadDwordx8:
case Decoder::Opcode::SLoadDwordx16: return true;
case Decoder::Opcode::S_LOAD_DWORD:
case Decoder::Opcode::S_LOAD_DWORDX2:
case Decoder::Opcode::S_LOAD_DWORDX4:
case Decoder::Opcode::S_LOAD_DWORDX8:
case Decoder::Opcode::S_LOAD_DWORDX16: return true;
default: return false;
}
}
bool IsEmbeddedFetchBufferLoad(const Decoder::Instruction& inst) {
switch (inst.opcode) {
case Decoder::Opcode::BufferLoadFormatX:
case Decoder::Opcode::BufferLoadFormatXy:
case Decoder::Opcode::BufferLoadFormatXyz:
case Decoder::Opcode::BufferLoadFormatXyzw: return true;
case Decoder::Opcode::BUFFER_LOAD_FORMAT_X:
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XY:
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZ:
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZW: return true;
default: return false;
}
}
bool IsEmbeddedFetchAttribPropagationAlu(const Decoder::Instruction& inst) {
switch (inst.opcode) {
case Decoder::Opcode::SBfeU32:
case Decoder::Opcode::SAndB32:
case Decoder::Opcode::SAddI32:
case Decoder::Opcode::SAddU32:
case Decoder::Opcode::SLshlB32: return true;
case Decoder::Opcode::S_BFE_U32:
case Decoder::Opcode::S_AND_B32:
case Decoder::Opcode::S_ADD_I32:
case Decoder::Opcode::S_ADD_U32:
case Decoder::Opcode::S_LSHL_B32: return true;
default: return false;
}
}
@@ -308,9 +336,6 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
uint32_t user_data_base, uint32_t user_data_count,
uint32_t wave_size) {
EmbeddedFetchData data;
if (input_info == nullptr || !input_info->fetch_embedded) {
return data;
}
data.loads.reserve(input_info->resources_num);
int32_t offset_candidate = -1;
bool offset_conflict = false;
@@ -348,10 +373,11 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
uint32_t sad_zero = 0;
const bool vertex_offset_add =
vertex_index_accumulator && IsDecodedSgpr(inst.src0) &&
((inst.opcode == Decoder::Opcode::VAddI32 && IsDecodedVgpr(inst.src1) &&
((inst.opcode == Decoder::Opcode::V_ADD_I32 && IsDecodedVgpr(inst.src1) &&
inst.src1.reg == inst.dst.reg) ||
(user_data_base == 8 && inst.dst.reg == 5 && inst.opcode == Decoder::Opcode::VSadU32 &&
IsDecodedVgpr(inst.src2) && inst.src2.reg == inst.dst.reg &&
(user_data_base == 8 && inst.dst.reg == 5 &&
inst.opcode == Decoder::Opcode::V_SAD_U32 && IsDecodedVgpr(inst.src2) &&
inst.src2.reg == inst.dst.reg &&
TryDecodedOperandConstant(sgprs, inst.src1, sad_zero) && sad_zero == 0));
if (data.loads.empty() && vertex_offset_add) {
const auto reg = DecodedSgprReg(inst.src0);
@@ -364,7 +390,7 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
}
}
switch (inst.opcode) {
case Decoder::Opcode::VWritelaneB32: {
case Decoder::Opcode::V_WRITELANE_B32: {
uint32_t lane = 0;
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgprs.size()) {
vgprs[inst.dst.reg] = {};
@@ -380,7 +406,7 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
}
break;
}
case Decoder::Opcode::VReadlaneB32: {
case Decoder::Opcode::V_READLANE_B32: {
uint32_t lane = 0;
if (track_vector_lanes && IsDecodedSgpr(inst.dst) &&
DecodedSgprReg(inst.dst) < sgprs.size() && IsDecodedVgpr(inst.src0) &&
@@ -394,7 +420,7 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
}
break;
}
case Decoder::Opcode::SMovB32:
case Decoder::Opcode::S_MOV_B32:
if (IsDecodedSgpr(inst.dst) && IsDecodedSgpr(inst.src0) &&
DecodedSgprReg(inst.src0) < sgprs.size()) {
sgprs[DecodedSgprReg(inst.dst)] = sgprs[DecodedSgprReg(inst.src0)];
@@ -410,7 +436,7 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
}
}
break;
case Decoder::Opcode::SMovkI32:
case Decoder::Opcode::S_MOVK_I32:
if (IsDecodedSgpr(inst.dst)) {
auto& dst = sgprs[DecodedSgprReg(inst.dst)];
dst.type = EmbeddedFetchValueType::Constant;
@@ -471,7 +497,7 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
} else {
ClearEmbeddedFetchSgprs(sgprs, inst.dst, DecodedDstSize(inst));
}
} else if (inst.opcode == Decoder::Opcode::VCndmaskB32) {
} else if (inst.opcode == Decoder::Opcode::V_CNDMASK_B32) {
if (IsDecodedVgpr(inst.dst) && inst.dst.reg < vgprs.size()) {
ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg);
}
@@ -493,11 +519,11 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
auto& dst = sgprs[DecodedSgprReg(inst.dst)];
dst.type = EmbeddedFetchValueType::Constant;
switch (inst.opcode) {
case Decoder::Opcode::SAndB32: dst.value = src0 & src1; break;
case Decoder::Opcode::SLshlB32:
case Decoder::Opcode::S_AND_B32: dst.value = src0 & src1; break;
case Decoder::Opcode::S_LSHL_B32:
dst.value = src0 << (src1 & 31u);
break;
case Decoder::Opcode::SBfeU32:
case Decoder::Opcode::S_BFE_U32:
dst.value = src0 >> (src1 & 31u);
break;
default: dst.value = src0 + src1; break;
@@ -526,9 +552,9 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
}
break;
}
if (inst.opcode == Decoder::Opcode::VMovreldB32) {
if (inst.opcode == Decoder::Opcode::V_MOVRELD_B32) {
vector_lanes.clear();
} else if (inst.opcode != Decoder::Opcode::VWritelaneB32 && IsDecodedVgpr(inst.dst)) {
} else if (inst.opcode != Decoder::Opcode::V_WRITELANE_B32 && IsDecodedVgpr(inst.dst)) {
for (uint32_t i = 0; i < EmbeddedFetchDstSize(inst) && inst.dst.reg + i < vgprs.size();
i++) {
ClearEmbeddedFetchVectorLanes(&vector_lanes, inst.dst.reg + i);
@@ -559,9 +585,6 @@ bool IsIrFetchPrologLoad(const IR::Instruction& inst) {
int ResolveEmbeddedFetchResource(const ShaderVertexInputInfo* input_info,
const EmbeddedFetchLoad& load) {
if (input_info == nullptr) {
return -1;
}
if (load.attrib_id >= 0 && load.attrib_id < input_info->resources_num &&
input_info->resources_dst[load.attrib_id].attr_id == load.attrib_id) {
return load.attrib_id;
@@ -583,7 +606,7 @@ int ResolveEmbeddedFetchResource(const ShaderVertexInputInfo* input_info,
uint32_t RewriteEmbeddedVertexFetches(IR::Program& ir, const ShaderVertexInputInfo* input_info,
const std::vector<EmbeddedFetchLoad>& loads) {
if (input_info == nullptr || loads.empty()) {
if (loads.empty()) {
return 0;
}
@@ -697,27 +720,25 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
bool dispatcher_fallback = false;
std::string dispatcher_reason;
if (cfg.irreducible) {
dispatcher_fallback = true;
dispatcher_reason = cfg.unsupported_reason;
const auto diagnostic = FormatCfgFailure(cfg, options, cfg.unsupported_reason);
LOGF("%s irreducible CFG detected: %s\n", GetDumpLabel(options), diagnostic.c_str());
dispatcher_fallback = true;
dispatcher_reason = cfg.unsupported_reason;
LogDispatcherFallback(options, cfg, "build", dispatcher_reason);
} else {
std::string structure_error;
const auto unstructured_cfg = cfg;
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " CFG Structurize\n",
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
if (!CFG::Structurize(cfg, &structure_error)) {
const auto diagnostic = FormatCfgFailure(cfg, options, structure_error);
LOGF("%s structured CFG bug/failure: %s\n", GetDumpLabel(options), diagnostic.c_str());
dispatcher_fallback = true;
dispatcher_reason = structure_error;
const auto failure_kind = cfg.failure_kind;
const auto failure_block = cfg.failure_block;
cfg = unstructured_cfg;
cfg.unsupported = true;
cfg.failure_kind = failure_kind;
cfg.failure_block = failure_block;
cfg.unsupported_reason = structure_error;
LogDispatcherFallback(options, cfg, "structurize", dispatcher_reason);
cfg = unstructured_cfg;
cfg.unsupported = true;
cfg.failure_kind = failure_kind;
cfg.failure_block = failure_block;
cfg.unsupported_reason = structure_error;
} else {
LOGF("%s structured CFG success: blocks=%" PRIu64 "\n", GetDumpLabel(options),
static_cast<uint64_t>(cfg.blocks.size()));
@@ -732,34 +753,134 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
IR::Program ir;
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " IR LowerProgram\n", GetDumpLabel(options),
StageName(options.stage), options.shader_hash);
if (!IR::LowerProgram(decoded, cfg, options.stage, options.wave_size, ir, error)) {
const auto lower_program = [&](IR::Program& output) {
output = {};
if (!IR::LowerProgram(decoded, cfg, options.stage, options.wave_size, output, error)) {
return false;
}
output.lane_mask_mode = options.lane_mask_mode;
output.shader_hash = options.shader_hash;
output.user_data_base = options.user_data_base;
output.user_data_count = options.user_data_count;
return true;
};
if (!lower_program(ir)) {
return false;
}
ir.lane_mask_mode = options.lane_mask_mode;
ir.shader_hash = options.shader_hash;
ir.user_data_base = options.user_data_base;
ir.user_data_count = options.user_data_count;
if (!dispatcher_fallback) {
const auto select_dispatcher = [&](uint32_t failure_block, const std::string& reason,
const CFG::Graph& diagnostic_cfg) {
dispatcher_fallback = true;
dispatcher_reason = reason;
cfg.failure_kind = CFG::FailureKind::StructuredControlFlow;
cfg.failure_block = failure_block;
cfg.unsupported_reason = reason;
ir.cfg_failure_kind = CFG::FailureKind::StructuredControlFlow;
ir.fallback_reason = reason;
LogDispatcherFallback(options, diagnostic_cfg, "emit", reason);
};
const auto retry_budget = cfg.natural_loops.size();
for (size_t retry = 0;; retry++) {
std::string emitter_reason;
uint32_t failure_block = UINT32_MAX;
if (!NeedsDispatcherForStructuredLoopHeader(ir, &failure_block, &emitter_reason)) {
break;
}
if (retry >= retry_budget) {
select_dispatcher(failure_block,
emitter_reason + "; no-clone loop-header isolation exhausted",
cfg);
break;
}
const auto* source_block = cfg.FindBlock(failure_block);
const auto source_pc = source_block != nullptr ? source_block->start_pc : UINT32_MAX;
const auto previous_cfg = cfg;
const auto blocks_before = cfg.blocks.size();
std::string isolation_error;
if (!CFG::IsolateLoopHeader(cfg, failure_block, &isolation_error)) {
const auto failed_cfg = cfg;
cfg = previous_cfg;
select_dispatcher(failure_block,
fmt::format("{}; no-clone loop-header isolation failed: {}",
emitter_reason, isolation_error),
failed_cfg);
break;
}
IR::Program isolated_ir;
if (!lower_program(isolated_ir)) {
const auto lower_error = error != nullptr ? *error : "IR lowering failed";
if (error != nullptr) {
error->clear();
}
cfg = previous_cfg;
select_dispatcher(failure_block,
fmt::format("{}; isolated loop-header lowering failed: {}",
emitter_reason, lower_error),
cfg);
break;
}
ir = std::move(isolated_ir);
LOGF("%s CFG isolated semantic loop header: stage=%s hash=0x%016" PRIx64
" source_block=%" PRIu32 " pc=0x%08" PRIx32 " blocks=%" PRIu64 "->%" PRIu64
" reason=%s\n",
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
failure_block, source_pc, static_cast<uint64_t>(blocks_before),
static_cast<uint64_t>(cfg.blocks.size()), emitter_reason.c_str());
}
}
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " IR LowerProgram blocks=%" PRIu64
" elapsed_ms=%" PRIu64 "\n",
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
static_cast<uint64_t>(ir.blocks.size()), phase_ms());
const ShaderVertexInputInfo* vertex = nullptr;
const ShaderPixelInputInfo* pixel = nullptr;
const ShaderComputeInputInfo* compute = nullptr;
switch (options.stage) {
case ShaderType::Vertex: vertex = options.input_info.vertex; break;
case ShaderType::Pixel: pixel = options.input_info.pixel; break;
case ShaderType::Compute: compute = options.input_info.compute; break;
default: break;
}
EmbeddedFetchData embedded_fetch;
if (options.stage == ShaderType::Vertex && options.vertex_input_info != nullptr &&
options.vertex_input_info->fetch_embedded) {
embedded_fetch =
DetectEmbeddedVertexFetch(decoded, options.vertex_input_info, ir.user_data_base,
ir.user_data_count, options.wave_size);
auto rewritten =
RewriteEmbeddedVertexFetches(ir, options.vertex_input_info, embedded_fetch.loads);
if (options.stage == ShaderType::Vertex && vertex->fetch_embedded) {
embedded_fetch = DetectEmbeddedVertexFetch(decoded, vertex, ir.user_data_base,
ir.user_data_count, options.wave_size);
auto rewritten = RewriteEmbeddedVertexFetches(ir, vertex, embedded_fetch.loads);
if (rewritten > 0 || !embedded_fetch.loads.empty()) {
LOGF("%s embedded vertex fetch rewrite: detected=%" PRIu64 " rewritten=%" PRIu32 "\n",
GetDumpLabel(options), static_cast<uint64_t>(embedded_fetch.loads.size()),
rewritten);
}
}
if (!IR::BuildScalarProvenance(ir, error)) {
ir.dispatcher_fallback = dispatcher_fallback;
if (!dispatcher_reason.empty()) {
ir.fallback_reason = dispatcher_reason;
}
ir.values = std::make_shared<IR::ValueProgram>();
if (!Frontend::TranslateProgram(ir, *ir.values, vertex, pixel, compute, error)) {
return false;
}
ir.blocks.clear();
IR::RewriteToSsa(ir.values->blocks);
IR::ConstantPropagationPass(ir.values->blocks);
for (auto& info: ir.values->block_info) {
info.condition = info.condition.Resolve();
info.indirect_target = info.indirect_target.Resolve();
}
IR::RemoveIdentities(ir.values->blocks);
IR::EliminateDeadCode(ir.values->blocks);
const auto read_lane_stats = IR::EliminateReadLane(*ir.values, ir.wave_size);
if (read_lane_stats.rewritten_reads != 0) {
LOGF("%s read-lane elimination: reads=%" PRIu32 "\n", GetDumpLabel(options),
read_lane_stats.rewritten_reads);
IR::ConstantPropagationPass(ir.values->blocks);
IR::RemoveIdentities(ir.values->blocks);
IR::EliminateDeadCode(ir.values->blocks);
}
std::string srt_error;
if (!IR::BuildSrtPlan(ir, &srt_error)) {
LOGF("%s SRT planning failed: %s\n", GetDumpLabel(options), srt_error.c_str());
@@ -768,29 +889,14 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
}
return false;
}
ir.dispatcher_fallback = dispatcher_fallback;
if (!dispatcher_reason.empty()) {
ir.fallback_reason = dispatcher_reason;
}
if (!IR::PatchSrtReads(ir, error) || !IR::TrackResources(ir, error)) {
IR::EliminateDeadCode(ir.values->blocks);
if (!IR::TrackResources(ir, error)) {
return false;
}
IR::EliminateDeadCode(ir.values->blocks);
if (options.stage == ShaderType::Vertex) {
ir.info.vertex_offset_sgpr = embedded_fetch.vertex_offset_sgpr;
}
if (!dispatcher_fallback) {
std::string emitter_reason;
if (NeedsDispatcherForStructuredLoopHeader(ir, &emitter_reason)) {
dispatcher_fallback = true;
dispatcher_reason = emitter_reason;
ir.dispatcher_fallback = true;
ir.cfg_failure_kind = CFG::FailureKind::StructuredControlFlow;
ir.fallback_reason = emitter_reason;
LOGF("%s structured CFG emitter fallback: %s\n", GetDumpLabel(options),
emitter_reason.c_str());
}
}
IR::ResourceSnapshot resources;
if (options.resource_snapshot != nullptr) {
@@ -809,6 +915,7 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
runtime.shader_base = options.shader_base != 0 ? options.shader_base
: reinterpret_cast<uint64_t>(code.data());
runtime.read_memory = options.read_memory;
runtime.read_specialization_memory = options.read_specialization_memory;
if (runtime.read_memory == nullptr && options.user_data == nullptr) {
runtime.read_memory = ReadZeroMemory;
}
@@ -822,16 +929,10 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
return false;
}
ShaderVertexInputInfo default_vertex {};
ShaderPixelInputInfo default_pixel {};
ShaderComputeInputInfo default_compute {};
IR::ShaderInfoOptions info_options;
info_options.vertex =
options.vertex_input_info != nullptr ? options.vertex_input_info : &default_vertex;
info_options.pixel =
options.pixel_input_info != nullptr ? options.pixel_input_info : &default_pixel;
info_options.compute =
options.compute_input_info != nullptr ? options.compute_input_info : &default_compute;
IR::ShaderInfoOptions info_options;
info_options.vertex = vertex;
info_options.pixel = pixel;
info_options.compute = compute;
if (!IR::CollectShaderInfo(ir, info_options, error)) {
return false;
}
@@ -841,11 +942,6 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
if (!IR::AllocateBindings(ir, layout_options, error)) {
return false;
}
const auto read_lane_stats = IR::EliminateReadLane(ir);
if (read_lane_stats.rewritten_reads != 0) {
LOGF("%s read-lane elimination: reads=%" PRIu32 " shadow_writes=%" PRIu32 "\n",
GetDumpLabel(options), read_lane_stats.rewritten_reads, read_lane_stats.shadow_writes);
}
std::string ir_dump;
if (options.dump_ir) {
ir_dump = MakeIrDump(cfg, ir);
@@ -858,8 +954,8 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
std::string emit_error;
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n",
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
if (!Spirv::EmitProgram(ir, resources, options.vertex_input_info, options.pixel_input_info,
options.compute_input_info, spirv, &emit_error)) {
if (!Spirv::EmitProgram(ir, resources, options.input_info, spirv, &emit_error)) {
LOGF("%s typed SPIR-V emit failed: %s\n", GetDumpLabel(options), emit_error.c_str());
if (dispatcher_fallback && error != nullptr) {
*error = fmt::format("dispatcher fallback failed after {}: {}",
dispatcher_reason.c_str(), emit_error.c_str());
@@ -873,10 +969,6 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
" elapsed_ms=%" PRIu64 "\n",
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
static_cast<uint64_t>(spirv.size()), phase_ms());
if (dispatcher_fallback) {
LOGF("%s dispatcher fallback used: %s\n", GetDumpLabel(options), dispatcher_reason.c_str());
}
result.spirv = std::move(spirv);
result.program = std::move(ir);
result.resources = std::move(resources);
@@ -3,7 +3,7 @@
#include "common/common.h"
#include "common/stringUtils.h"
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include "graphics/shader/shader.h"
#include <optional>
@@ -13,26 +13,25 @@
namespace Libs::Graphics::ShaderRecompiler {
struct CompileOptions {
ShaderType stage = ShaderType::Compute;
ShaderLaneMaskMode lane_mask_mode = ShaderLaneMaskMode::NativeWave;
uint32_t wave_size = 64;
uint32_t user_data_base = 0;
uint32_t user_data_count = 64;
uint64_t shader_hash = 0;
uint64_t shader_base = 0;
std::optional<uint64_t> flat_memory_base;
uint32_t descriptor_set = 0;
uint32_t push_constant_offset = 0;
bool dump_ir = true;
bool early_dump = false;
const char* dump_label = nullptr;
const uint32_t* user_data = nullptr;
IR::SrtMemoryReader read_memory = nullptr;
void* read_memory_data = nullptr;
const IR::ResourceSnapshot* resource_snapshot = nullptr;
const ShaderVertexInputInfo* vertex_input_info = nullptr;
const ShaderPixelInputInfo* pixel_input_info = nullptr;
const ShaderComputeInputInfo* compute_input_info = nullptr;
ShaderType stage = ShaderType::Compute;
ShaderLaneMaskMode lane_mask_mode = ShaderLaneMaskMode::NativeWave;
uint32_t wave_size = 64;
uint32_t user_data_base = 0;
uint32_t user_data_count = 64;
uint64_t shader_hash = 0;
uint64_t shader_base = 0;
std::optional<uint64_t> flat_memory_base;
uint32_t descriptor_set = 0;
uint32_t push_constant_offset = 0;
bool dump_ir = true;
bool early_dump = false;
const char* dump_label = nullptr;
const uint32_t* user_data = nullptr;
IR::SrtMemoryReader read_memory = nullptr;
IR::SrtMemoryReader read_specialization_memory = nullptr;
void* read_memory_data = nullptr;
const IR::ResourceSnapshot* resource_snapshot = nullptr;
ShaderStageInputInfo input_info;
};
struct CompileResult {
@@ -1,4 +1,4 @@
#include "graphics/shader/recompiler/emitter/SpirvBuilder.h"
#include "graphics/shader/recompiler/backend/spirv/SpirvBuilder.h"
#include <cstdio>
#include <cstring>
@@ -0,0 +1,392 @@
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include "graphics/shader/recompiler/ir/ValueProgram.h"
#include "graphics/shader/shader.h"
#include <algorithm>
#include <array>
#include <cinttypes>
#include <cstdio>
namespace Libs::Graphics::ShaderRecompiler::Spirv {
using ShaderError::Fail;
namespace {
bool ImageBinding(const IR::ImageResource& image, IR::DescriptorBindingKind& kind) {
using Kind = IR::DescriptorBindingKind;
using Dim = Decoder::ImageDimension;
if (image.kind == IR::ResourceKind::Image || image.kind == IR::ResourceKind::ImageUint) {
const bool integer = image.kind == IR::ResourceKind::ImageUint;
switch (image.dimension) {
case Dim::Dim1D: kind = integer ? Kind::SampledUint1D : Kind::Sampled1D; return true;
case Dim::Dim1DArray:
kind = integer ? Kind::SampledUint1DArray : Kind::Sampled1DArray;
return true;
case Dim::Dim2D: kind = integer ? Kind::SampledUint2D : Kind::Sampled2D; return true;
case Dim::Dim2DMsaa:
kind = integer ? Kind::SampledUint2DMsaa : Kind::Sampled2DMsaa;
return true;
case Dim::Dim3D: kind = integer ? Kind::SampledUint3D : Kind::Sampled3D; return true;
case Dim::Dim2DArray:
kind = integer ? Kind::SampledUint2DArray : Kind::Sampled2DArray;
return true;
case Dim::Dim2DMsaaArray:
kind = integer ? Kind::SampledUint2DMsaaArray : Kind::Sampled2DMsaaArray;
return true;
case Dim::Unknown: return false;
}
}
const bool uint_image = image.kind == IR::ResourceKind::StorageImageUint;
if (image.kind != IR::ResourceKind::StorageImage && !uint_image) {
return false;
}
switch (image.dimension) {
case Dim::Dim1D: kind = uint_image ? Kind::StorageUint1D : Kind::Storage1D; return true;
case Dim::Dim1DArray:
kind = uint_image ? Kind::StorageUint1DArray : Kind::Storage1DArray;
return true;
case Dim::Dim2D: kind = uint_image ? Kind::StorageUint2D : Kind::Storage2D; return true;
case Dim::Dim3D: kind = uint_image ? Kind::StorageUint3D : Kind::Storage3D; return true;
case Dim::Dim2DArray:
kind = uint_image ? Kind::StorageUint2DArray : Kind::Storage2DArray;
return true;
case Dim::Dim2DMsaa:
case Dim::Dim2DMsaaArray: return false;
case Dim::Unknown: return false;
}
return false;
}
bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
using Kind = IR::DescriptorBindingKind;
constexpr auto KindCount = static_cast<size_t>(Kind::Count);
std::array<std::vector<uint32_t>, KindCount> expected;
std::array<bool, KindCount> present {};
const auto Dense = [](size_t size) {
std::vector<uint32_t> values(size);
for (uint32_t i = 0; i < values.size(); i++) {
values[i] = i;
}
return values;
};
auto Expect = [&](Kind kind, std::vector<uint32_t> resources = {}) {
const auto index = static_cast<size_t>(kind);
present[index] = true;
expected[index] = std::move(resources);
};
if (!program.info.buffers.empty()) {
Expect(Kind::Buffers, Dense(program.info.buffers.size()));
}
for (uint32_t i = 0; i < program.info.images.size(); i++) {
Kind kind;
if (!ImageBinding(program.info.images[i], kind)) {
return Fail(error, "native shader plan has an invalid image class");
}
present[static_cast<size_t>(kind)] = true;
const auto dynamic = program.info.images[i].mip_mode == IR::ImageMipMode::DynamicStorage;
const auto count = dynamic ? program.info.images[i].mip_count : 1u;
if (count == 0u || (!dynamic && program.info.images[i].mip_count != 1u)) {
return Fail(error, "native shader plan has an invalid image mip descriptor count");
}
expected[static_cast<size_t>(kind)].insert(expected[static_cast<size_t>(kind)].end(), count,
i);
}
if (!program.info.samplers.empty()) {
Expect(Kind::Samplers, Dense(program.info.samplers.size()));
}
const auto uses_gds = program.values != nullptr &&
std::ranges::any_of(program.values->blocks, [](const IR::Block* block) {
return std::ranges::any_of(*block, [](const IR::Inst& inst) {
return inst.GetOpcode() == IR::ValueOpcode::GetGdsResource;
});
});
if (uses_gds) {
Expect(Kind::Gds);
}
if (!program.info.addresses.empty()) {
Expect(Kind::AddressMemory, Dense(program.info.addresses.size()));
}
const bool uses_flattened_runtime =
program.values != nullptr &&
(!program.values->srt_reads.empty() ||
std::ranges::any_of(program.info.images, [](const IR::ImageResource& image) {
return image.indirect_mapping_capacity != 0u;
}));
if (uses_flattened_runtime) {
Expect(Kind::FlattenedSrt);
}
if (program.bindings.ShaderDataDwords() != 0 && program.bindings.push_constant_size == 0) {
Expect(Kind::UserData);
}
std::array<bool, KindCount> seen {};
for (uint32_t i = 0; i < program.bindings.descriptors.size(); i++) {
const auto& binding = program.bindings.descriptors[i];
const auto kind = static_cast<size_t>(binding.kind);
if (kind >= KindCount || seen[kind] || !present[kind] ||
binding.resources != expected[kind]) {
return Fail(error, "native descriptor groups do not match shader topology");
}
for (uint32_t j = 0; j < i; j++) {
if (program.bindings.descriptors[j].binding == binding.binding) {
return Fail(error, "native descriptor binding numbers are not unique");
}
}
seen[kind] = true;
}
for (size_t i = 0; i < KindCount; i++) {
if (present[i] != seen[i]) {
return Fail(error, "native shader plan is missing a required descriptor group");
}
}
const auto has_user_storage = present[static_cast<size_t>(Kind::UserData)];
if (program.bindings.buffer_offset_dword != program.bindings.user_data_registers.size() ||
program.bindings.buffer_offset_count != program.info.buffers.size() ||
(!has_user_storage &&
program.bindings.push_constant_size != program.bindings.ShaderDataDwords() * 4u) ||
(has_user_storage && program.bindings.push_constant_size != 0) ||
!std::is_sorted(program.bindings.user_data_registers.begin(),
program.bindings.user_data_registers.end()) ||
std::adjacent_find(program.bindings.user_data_registers.begin(),
program.bindings.user_data_registers.end()) !=
program.bindings.user_data_registers.end()) {
return Fail(error, "native user-data layout is inconsistent");
}
if (program.values == nullptr) {
return Fail(error, "native shader plan has no typed SSA");
}
const auto planning_only_handle = [&](const IR::Inst& handle) {
return !handle.Uses().empty() &&
std::ranges::all_of(handle.Uses(), [&](const IR::Use& use) {
const auto op = use.user->GetOpcode();
if (op != IR::ValueOpcode::LoadAddressU32 &&
op != IR::ValueOpcode::ReadConstBuffer) {
return false;
}
const auto index = use.user->Flags<IR::MemoryFlags>().index;
return index < program.values->memory_info.size() &&
program.values->memory_info[index].planning_only;
});
};
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
const auto dense = inst.Flags<uint32_t>();
switch (inst.GetOpcode()) {
case IR::ValueOpcode::GetBufferResource:
if (planning_only_handle(inst)) {
break;
}
if (dense >= program.info.buffers.size()) {
return Fail(error, "typed buffer handle has an invalid dense resource");
}
break;
case IR::ValueOpcode::GetAddressResource:
if (planning_only_handle(inst)) {
break;
}
if (dense >= program.info.addresses.size()) {
return Fail(error, "typed address handle has an invalid dense resource");
}
break;
case IR::ValueOpcode::GetImageResource:
if (dense >= program.info.images.size()) {
return Fail(error, "typed image handle has an invalid dense resource");
}
break;
case IR::ValueOpcode::GetSamplerResource:
if (dense >= program.info.samplers.size()) {
return Fail(error, "typed sampler handle has an invalid dense resource");
}
break;
case IR::ValueOpcode::ReadConst: {
const auto slot = inst.Arg(1).Resolve();
if (!slot.IsImmediate() || slot.GetType() != IR::Type::U32 ||
slot.U32() >= program.values->srt_reads.size()) {
return Fail(error, "flattened SRT read has an invalid dense slot");
}
break;
}
default: break;
}
}
}
return true;
}
} // namespace
static constexpr size_t InitialEmitterVectorReserve = 4096;
static void ReportReserveExceeded(const IR::Program& program, const char* vector_name,
size_t size) {
if (size <= InitialEmitterVectorReserve) {
return;
}
std::printf("SPIR-V emitter reserve exceeded: hash=0x%016" PRIx64
" stage=%u vector=%s size=%zu reserve=%zu\n",
program.shader_hash, static_cast<unsigned>(program.stage), vector_name, size,
InitialEmitterVectorReserve);
}
void CollectValueRegisterMirrors(const IR::ValueProgram& program,
std::vector<Emitter::RegisterBinding>& registers) {
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
switch (inst.GetOpcode()) {
case IR::ValueOpcode::SetScalarRegister:
case IR::ValueOpcode::SetThreadBitScalarRegister:
if (block != program.blocks.front()) {
Emitter::CollectRegister(
registers,
{IR::RegisterFile::Scalar, IR::RegIndex(inst.Arg(0).ScalarRegister())});
}
break;
case IR::ValueOpcode::SetVectorRegister:
Emitter::CollectRegister(
registers,
{IR::RegisterFile::Vector, IR::RegIndex(inst.Arg(0).VectorRegister())});
break;
case IR::ValueOpcode::SetM0:
if (block != program.blocks.front()) {
Emitter::CollectRegister(registers, {IR::RegisterFile::M0, 0});
}
break;
case IR::ValueOpcode::SetExecLo:
case IR::ValueOpcode::SetExecHi:
if (block != program.blocks.front()) {
Emitter::CollectRegister(
registers, {IR::RegisterFile::Exec,
inst.GetOpcode() == IR::ValueOpcode::SetExecLo ? 0u : 1u});
}
break;
case IR::ValueOpcode::SetVccLo:
case IR::ValueOpcode::SetVccHi:
if (block != program.blocks.front()) {
Emitter::CollectRegister(
registers, {IR::RegisterFile::Vcc,
inst.GetOpcode() == IR::ValueOpcode::SetVccLo ? 0u : 1u});
}
break;
default: break;
}
}
}
}
bool ValueProgramHasOpcode(const IR::ValueProgram& program, IR::ValueOpcode opcode) {
return std::ranges::any_of(program.blocks, [opcode](const IR::Block* block) {
return std::ranges::any_of(
*block, [opcode](const IR::Inst& inst) { return inst.GetOpcode() == opcode; });
});
}
bool ProgramRequiresExactSubgroupSize(const IR::Program& program) {
if (program.values == nullptr) {
return false;
}
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
switch (inst.GetOpcode()) {
case IR::ValueOpcode::Ballot:
case IR::ValueOpcode::DppMoveU32:
case IR::ValueOpcode::DppUpdateU32:
case IR::ValueOpcode::WqmMask:
case IR::ValueOpcode::ReadFirstLane:
case IR::ValueOpcode::ReadLane:
case IR::ValueOpcode::WriteLane:
case IR::ValueOpcode::Permlane16U32:
case IR::ValueOpcode::SwizzleU32:
case IR::ValueOpcode::DataAppend:
case IR::ValueOpcode::DataConsume:
case IR::ValueOpcode::GdsDataAppend:
case IR::ValueOpcode::GdsDataConsume: return true;
default: break;
}
}
}
return false;
}
bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources,
ShaderStageInputInfo input_info, std::vector<uint32_t>& spirv,
std::string* error) {
using namespace Emitter;
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex &&
program.stage != ShaderType::Pixel) {
SetError(error, "binary SPIR-V emitter supports compute, vertex, and pixel shaders");
return false;
}
if (!program.srt_plan_complete || !program.resource_tracking_complete ||
!program.shader_info_complete || !program.binding_layout_complete) {
SetError(error, "SPIR-V emitter requires a fully planned native shader program");
return false;
}
if (!IR::ValidateResourceSnapshot(program, resources, error)) {
return false;
}
if (!IR::ValidateResourceSpecialization(program, resources, error)) {
return false;
}
if (!ValidateNativeProgram(program, error)) {
return false;
}
if (program.values == nullptr) {
SetError(error, "SPIR-V emitter requires planned typed SSA");
return false;
}
const auto& value_program = *program.values;
if (!IR::ValidateValueProgram(value_program, true, error)) {
return false;
}
EmitterState state(program, resources, input_info);
state.stage = program.stage;
state.wave_size = program.wave_size;
state.per_invocation_masks = program.lane_mask_mode == ShaderLaneMaskMode::PerInvocation;
state.exact_subgroup_operations =
!state.per_invocation_masks && ProgramRequiresExactSubgroupSize(program);
state.registers.reserve(InitialEmitterVectorReserve);
state.inputs.reserve(InitialEmitterVectorReserve);
state.outputs.reserve(InitialEmitterVectorReserve);
state.interface_variables.reserve(InitialEmitterVectorReserve);
state.reachable_blocks.reserve(InitialEmitterVectorReserve);
CollectValueRegisterMirrors(value_program, state.registers);
CopyProgramInputsAndOutputs(state, program);
state.needs_subgroup_ballot = ProgramNeedsSubgroupBallot(program) ||
(!state.per_invocation_masks &&
ValueProgramHasOpcode(value_program, IR::ValueOpcode::Ballot));
state.needs_subgroup_shuffle = ProgramNeedsSubgroupShuffle(program);
state.needs_subgroup_local_invocation_id = ProgramNeedsSubgroupLocalInvocationId(program);
state.needs_compute_derivatives = ProgramNeedsComputeDerivatives(program);
state.needs_image_gather_extended = ProgramNeedsImageGatherExtended(program);
state.needs_function_lds = ProgramNeedsFunctionLds(program);
state.needs_pixel_valid_mask = ProgramNeedsPixelValidMask(program);
AllocateInputVariables(state);
AllocateOutputVariables(state);
AllocateDescriptorVariables(state);
EmitHeaderAndTypes(state);
AllocateRegisterVariables(state);
if (!EmitValueProgram(state, value_program, error)) {
return false;
}
ReportReserveExceeded(program, "registers", state.registers.size());
ReportReserveExceeded(program, "inputs", state.inputs.size());
ReportReserveExceeded(program, "outputs", state.outputs.size());
ReportReserveExceeded(program, "interface_variables", state.interface_variables.size());
ReportReserveExceeded(program, "reachable_blocks", state.reachable_blocks.size());
auto binary = state.builder.Build();
if (binary.empty()) {
SetError(error, "SPIR-V builder returned an empty module");
return false;
}
spirv = std::move(binary);
return true;
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
@@ -3,7 +3,7 @@
#include "common/common.h"
#include "common/stringUtils.h"
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
#include <vector>
@@ -12,10 +12,7 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv {
bool ProgramRequiresExactSubgroupSize(const IR::Program& program);
bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources,
const ShaderVertexInputInfo* vertex_input_info,
const ShaderPixelInputInfo* pixel_input_info,
const ShaderComputeInputInfo* compute_input_info, std::vector<uint32_t>& spirv,
std::string* error);
ShaderStageInputInfo input_info, std::vector<uint32_t>& spirv, std::string* error);
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
@@ -0,0 +1,677 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include <array>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
struct Pair {
uint32_t low = 0;
uint32_t high = 0;
};
uint32_t NewUnary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t value) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, value});
return result;
}
uint32_t NewBinary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t lhs,
uint32_t rhs) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, lhs, rhs});
return result;
}
uint32_t NewSelect(EmitterState& state, uint32_t type, uint32_t condition, uint32_t true_value,
uint32_t false_value) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({OpSelect, type, result, condition, true_value, false_value});
return result;
}
Pair ExtractPair(EmitterState& state, uint32_t value) {
Pair result {state.builder.AllocateId(), state.builder.AllocateId()};
state.builder.AddFunction({OpCompositeExtract, state.uint_type, result.low, value, 0});
state.builder.AddFunction({OpCompositeExtract, state.uint_type, result.high, value, 1});
return result;
}
uint32_t MakePair(EmitterState& state, uint32_t low, uint32_t high) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeConstruct, state.uint_pair_type, result, low, high});
return result;
}
uint32_t Compare64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value,
uint32_t high_compare, uint32_t low_compare, bool equal_compare = false,
bool not_equal = false) {
const auto lhs = ExtractPair(state, lhs_value);
const auto rhs = ExtractPair(state, rhs_value);
const auto high_equal = NewBinary(state, OpIEqual, state.bool_type, lhs.high, rhs.high);
const auto low_equal = NewBinary(state, OpIEqual, state.bool_type, lhs.low, rhs.low);
if (equal_compare) {
return NewBinary(
state, not_equal ? OpLogicalOr : OpLogicalAnd, state.bool_type,
not_equal ? NewUnary(state, OpLogicalNot, state.bool_type, high_equal) : high_equal,
not_equal ? NewUnary(state, OpLogicalNot, state.bool_type, low_equal) : low_equal);
}
const auto high_result = NewBinary(state, high_compare, state.bool_type, lhs.high, rhs.high);
const auto low_result = NewBinary(state, low_compare, state.bool_type, lhs.low, rhs.low);
const auto low_path = NewBinary(state, OpLogicalAnd, state.bool_type, high_equal, low_result);
return NewBinary(state, OpLogicalOr, state.bool_type, high_result, low_path);
}
uint32_t EmitAdd64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value) {
const auto lhs = ExtractPair(state, lhs_value);
const auto rhs = ExtractPair(state, rhs_value);
const auto carry = EmitAddCarryValues(state, lhs.low, rhs.low, ConstantU32(state, 0));
const auto high0 = NewBinary(state, OpIAdd, state.uint_type, lhs.high, rhs.high);
const auto high = NewBinary(state, OpIAdd, state.uint_type, high0, carry.carry);
return MakePair(state, carry.sum, high);
}
uint32_t EmitSub64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value) {
const auto lhs = ExtractPair(state, lhs_value);
const auto rhs = ExtractPair(state, rhs_value);
const auto low = NewBinary(state, OpISub, state.uint_type, lhs.low, rhs.low);
const auto borrow = NewBinary(state, OpULessThan, state.bool_type, lhs.low, rhs.low);
const auto borrow_u32 =
NewSelect(state, state.uint_type, borrow, ConstantU32(state, 1), ConstantU32(state, 0));
const auto high0 = NewBinary(state, OpISub, state.uint_type, lhs.high, rhs.high);
const auto high = NewBinary(state, OpISub, state.uint_type, high0, borrow_u32);
return MakePair(state, low, high);
}
uint32_t EmitMulHigh(EmitterState& state, uint32_t lhs, uint32_t rhs, bool signed_value) {
const auto operand_type = signed_value ? state.int_type : state.uint_type;
const auto pair_type = signed_value ? state.int_pair_type : state.uint_pair_type;
uint32_t lhs_operand = lhs;
uint32_t rhs_operand = rhs;
if (signed_value) {
lhs_operand = NewUnary(state, OpBitcast, state.int_type, lhs);
rhs_operand = NewUnary(state, OpBitcast, state.int_type, rhs);
}
const auto extended = state.builder.AllocateId();
state.builder.AddFunction({signed_value ? OpSMulExtended : OpUMulExtended, pair_type, extended,
lhs_operand, rhs_operand});
const auto high = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeExtract, operand_type, high, extended, 1});
return signed_value ? NewUnary(state, OpBitcast, state.uint_type, high) : high;
}
uint32_t EmitMul64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value) {
const auto lhs = ExtractPair(state, lhs_value);
const auto rhs = ExtractPair(state, rhs_value);
const auto low = NewBinary(state, OpIMul, state.uint_type, lhs.low, rhs.low);
const auto high0 = EmitMulHigh(state, lhs.low, rhs.low, false);
const auto high1 = NewBinary(state, OpIMul, state.uint_type, lhs.low, rhs.high);
const auto high2 = NewBinary(state, OpIMul, state.uint_type, lhs.high, rhs.low);
return MakePair(state, low,
NewBinary(state, OpIAdd, state.uint_type,
NewBinary(state, OpIAdd, state.uint_type, high0, high1), high2));
}
uint32_t EmitBitwise64(EmitterState& state, uint32_t opcode, uint32_t lhs_value,
uint32_t rhs_value) {
const auto lhs = ExtractPair(state, lhs_value);
const auto rhs = ExtractPair(state, rhs_value);
return MakePair(state, NewBinary(state, opcode, state.uint_type, lhs.low, rhs.low),
NewBinary(state, opcode, state.uint_type, lhs.high, rhs.high));
}
uint32_t EmitShift64(EmitterState& state, uint32_t opcode, uint32_t value, uint32_t shift) {
const auto pair = ExtractPair(state, value);
uint32_t low = 0;
uint32_t high = 0;
if (opcode == OpShiftLeftLogical) {
EmitShiftLeftLogicalU64Values(state, pair.low, pair.high, shift, low, high);
} else if (opcode == OpShiftRightLogical) {
EmitShiftRightLogicalU64Values(state, pair.low, pair.high, shift, low, high);
} else {
const auto logical = EmitShift64(state, OpShiftRightLogical, value, shift);
const auto logical_pair = ExtractPair(state, logical);
const auto sign =
NewBinary(state, OpSLessThan, state.bool_type, pair.high, ConstantU32(state, 0));
const auto inverse =
NewBinary(state, OpISub, state.uint_type, ConstantU32(state, 64), shift);
const auto fill = NewBinary(state, OpShiftLeftLogical, state.uint_type,
ConstantU32(state, 0xffffffffu), inverse);
const auto high_shift =
NewBinary(state, OpISub, state.uint_type, shift, ConstantU32(state, 32));
const auto high_fill = NewSelect(
state, state.uint_type,
NewBinary(state, OpUGreaterThanEqual, state.bool_type, shift, ConstantU32(state, 32)),
ConstantU32(state, 0xffffffffu), fill);
const auto fill_low =
NewSelect(state, state.uint_type, sign,
NewSelect(state, state.uint_type,
NewBinary(state, OpUGreaterThanEqual, state.bool_type, shift,
ConstantU32(state, 32)),
NewBinary(state, OpShiftLeftLogical, state.uint_type,
ConstantU32(state, 0xffffffffu), high_shift),
ConstantU32(state, 0)),
ConstantU32(state, 0));
const auto fill_high =
NewSelect(state, state.uint_type, sign, high_fill, ConstantU32(state, 0));
return MakePair(
state, NewBinary(state, OpBitwiseOr, state.uint_type, logical_pair.low, fill_low),
NewBinary(state, OpBitwiseOr, state.uint_type, logical_pair.high, fill_high));
}
return MakePair(state, low, high);
}
uint32_t EmitFindMsb64(EmitterState& state, uint32_t value) {
const auto pair = ExtractPair(state, value);
const auto high_i = state.builder.AllocateId();
const auto low_i = state.builder.AllocateId();
state.builder.AddFunction(
{OpExtInst, state.int_type, high_i, state.glsl_std450, GlslFindUMsb, pair.high});
state.builder.AddFunction(
{OpExtInst, state.int_type, low_i, state.glsl_std450, GlslFindUMsb, pair.low});
const auto high = NewUnary(state, OpBitcast, state.uint_type, high_i);
const auto low = NewUnary(state, OpBitcast, state.uint_type, low_i);
const auto high_nonzero =
NewBinary(state, OpINotEqual, state.bool_type, pair.high, ConstantU32(state, 0));
return NewSelect(state, state.uint_type, high_nonzero,
NewBinary(state, OpIAdd, state.uint_type, high, ConstantU32(state, 32)), low);
}
uint32_t EmitMinMax3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bool signed_value,
bool max_value) {
const auto ab = signed_value ? EmitMinMaxI32Value(state, a, b, max_value)
: EmitMinMaxU32Value(state, a, b, max_value);
return signed_value ? EmitMinMaxI32Value(state, ab, c, max_value)
: EmitMinMaxU32Value(state, ab, c, max_value);
}
uint32_t EmitMed3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bool signed_value) {
const auto minimum = EmitMinMax3(state, a, b, c, signed_value, false);
const auto maximum = EmitMinMax3(state, a, b, c, signed_value, true);
const auto ab = NewBinary(state, OpIAdd, state.uint_type, a, b);
const auto abc = NewBinary(state, OpIAdd, state.uint_type, ab, c);
return NewBinary(state, OpISub, state.uint_type,
NewBinary(state, OpISub, state.uint_type, abc, minimum), maximum);
}
uint32_t EmitFMinMax3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c, bool max_value) {
return EmitMinMaxF32Value(state, EmitMinMaxF32Value(state, a, b, max_value), c, max_value);
}
uint32_t EmitFMed3(EmitterState& state, uint32_t a, uint32_t b, uint32_t c) {
const auto min_ab = EmitMinMaxF32Value(state, a, b, false);
const auto min3 = EmitMinMaxF32Value(state, min_ab, c, false);
const auto max_ab = EmitMinMaxF32Value(state, a, b, true);
const auto high_min = EmitMinMaxF32Value(state, max_ab, c, false);
const auto median = EmitMinMaxF32Value(state, min_ab, high_min, true);
const auto nan_ab = NewBinary(state, OpLogicalOr, state.bool_type,
EmitClassifyF32(state, a).nan, EmitClassifyF32(state, b).nan);
const auto any_nan =
NewBinary(state, OpLogicalOr, state.bool_type, nan_ab, EmitClassifyF32(state, c).nan);
return NewSelect(state, state.float_type, any_nan, min3, median);
}
uint32_t EmitExt(EmitterState& state, uint32_t type, uint32_t opcode,
std::initializer_list<uint32_t> args) {
const auto result = state.builder.AllocateId();
std::vector<uint32_t> words {OpExtInst, type, result, state.glsl_std450, opcode};
words.insert(words.end(), args.begin(), args.end());
state.builder.AddFunction(words);
return result;
}
uint32_t EmitF32ToU32(EmitterState& state, uint32_t src, bool signed_value) {
const auto trunc = EmitTruncF32Value(state, src);
const auto converted_raw = state.builder.AllocateId();
if (signed_value) {
const auto converted_i = state.builder.AllocateId();
state.builder.AddFunction({OpConvertFToS, state.int_type, converted_i, trunc});
state.builder.AddFunction({OpBitcast, state.uint_type, converted_raw, converted_i});
} else {
state.builder.AddFunction({OpConvertFToU, state.uint_type, converted_raw, trunc});
}
const auto nan = EmitClassifyF32(state, src).nan;
if (signed_value) {
const auto below = NewBinary(state, OpFOrdLessThanEqual, state.bool_type, src,
ConstantF32(state, 0xcf000000u));
const auto above = NewBinary(state, OpFOrdGreaterThanEqual, state.bool_type, src,
ConstantF32(state, 0x4f000000u));
const auto high = NewSelect(state, state.uint_type, above, ConstantU32(state, 0x7fffffffu),
converted_raw);
const auto low =
NewSelect(state, state.uint_type, below, ConstantU32(state, 0x80000000u), high);
return NewSelect(state, state.uint_type, nan, ConstantU32(state, 0), low);
}
const auto below =
NewBinary(state, OpFOrdLessThanEqual, state.bool_type, src, ConstantF32(state, 0));
const auto above = NewBinary(state, OpFOrdGreaterThanEqual, state.bool_type, src,
ConstantF32(state, 0x4f800000u));
const auto zero = NewBinary(state, OpLogicalOr, state.bool_type, nan, below);
const auto high =
NewSelect(state, state.uint_type, above, ConstantU32(state, 0xffffffffu), converted_raw);
return NewSelect(state, state.uint_type, zero, ConstantU32(state, 0), high);
}
uint32_t EmitPackHalf(EmitterState& state, uint32_t src) {
return EmitExt(state, state.uint_type, GlslPackHalf2x16, {src});
}
uint32_t EmitUnpackHalf(EmitterState& state, uint32_t bits) {
const auto pair = EmitExt(state, state.vec2_float_type, GlslUnpackHalf2x16, {bits});
const auto result = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeExtract, state.float_type, result, pair, 0});
return result;
}
} // namespace
bool EmitValueAlu(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto op = inst.GetOpcode();
const auto unary = [&](uint32_t spirv, IR::Type type) {
ctx.Emit(inst, spirv, type, {ctx.Arg(inst, 0)});
return true;
};
const auto binary = [&](uint32_t spirv, IR::Type type) {
ctx.Emit(inst, spirv, type, {ctx.Arg(inst, 0), ctx.Arg(inst, 1)});
return true;
};
const auto ext_unary = [&](uint32_t ext) {
ctx.Define(inst, EmitExt(state, state.float_type, ext, {ctx.Arg(inst, 0)}));
return true;
};
switch (op) {
case IR::ValueOpcode::BitCastU16F16:
case IR::ValueOpcode::BitCastF16U16:
case IR::ValueOpcode::ConvertU32U16:
case IR::ValueOpcode::ConvertU32U8:
case IR::ValueOpcode::PackUint2x32:
case IR::ValueOpcode::UnpackUint2x32: ctx.Define(inst, ctx.Arg(inst, 0)); return true;
case IR::ValueOpcode::BitCastU32F32: return unary(OpBitcast, IR::Type::U32);
case IR::ValueOpcode::BitCastF32U32: return unary(OpBitcast, IR::Type::F32);
case IR::ValueOpcode::ConvertU16U32:
ctx.Emit(inst, OpBitwiseAnd, IR::Type::U16,
{ctx.Arg(inst, 0), ConstantU32(state, 0xffffu)});
return true;
case IR::ValueOpcode::ConvertU8U32:
ctx.Emit(inst, OpBitwiseAnd, IR::Type::U8,
{ctx.Arg(inst, 0), ConstantU32(state, 0xffu)});
return true;
case IR::ValueOpcode::ConvertF16F32: {
const auto pair = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeConstruct, state.vec2_float_type, pair,
ctx.Arg(inst, 0), ConstantF32(state, 0)});
ctx.Define(inst, EmitPackHalf(state, pair));
return true;
}
case IR::ValueOpcode::ConvertF32F16:
ctx.Define(inst, EmitUnpackHalf(state, ctx.Arg(inst, 0)));
return true;
case IR::ValueOpcode::ConvertS32F32:
ctx.Define(inst, EmitF32ToU32(state, ctx.Arg(inst, 0), true));
return true;
case IR::ValueOpcode::ConvertU32F32:
ctx.Define(inst, EmitF32ToU32(state, ctx.Arg(inst, 0), false));
return true;
case IR::ValueOpcode::ConvertF32S32: {
const auto signed_value = NewUnary(state, OpBitcast, state.int_type, ctx.Arg(inst, 0));
ctx.Emit(inst, OpConvertSToF, IR::Type::F32, {signed_value});
return true;
}
case IR::ValueOpcode::ConvertF32U32: return unary(OpConvertUToF, IR::Type::F32);
case IR::ValueOpcode::CompositeConstructU32x2:
ctx.Emit(inst, OpCompositeConstruct, IR::Type::U32x2,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1)});
return true;
case IR::ValueOpcode::CompositeConstructF32x2:
ctx.Emit(inst, OpCompositeConstruct, IR::Type::F32x2,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1)});
return true;
case IR::ValueOpcode::CompositeConstructU32x4:
ctx.Emit(inst, OpCompositeConstruct, IR::Type::U32x4,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2), ctx.Arg(inst, 3)});
return true;
case IR::ValueOpcode::CompositeExtractU32x2:
case IR::ValueOpcode::CompositeExtractU32x4:
ctx.Emit(inst, OpCompositeExtract, IR::Type::U32,
{ctx.Arg(inst, 0), inst.Arg(1).U32()});
return true;
case IR::ValueOpcode::PackHalf2x16:
ctx.Define(inst, EmitExt(state, state.uint_type, GlslPackHalf2x16, {ctx.Arg(inst, 0)}));
return true;
case IR::ValueOpcode::PackSnorm2x16:
ctx.Define(inst,
EmitExt(state, state.uint_type, GlslPackSnorm2x16, {ctx.Arg(inst, 0)}));
return true;
case IR::ValueOpcode::PackUnorm2x16:
ctx.Define(inst,
EmitExt(state, state.uint_type, GlslPackUnorm2x16, {ctx.Arg(inst, 0)}));
return true;
case IR::ValueOpcode::PackFloat2x16Rtz: {
const auto low = EmitF32ToF16RtzBits(state, ctx.Arg(inst, 0));
const auto high =
NewBinary(state, OpShiftLeftLogical, state.uint_type,
EmitF32ToF16RtzBits(state, ctx.Arg(inst, 1)), ConstantU32(state, 16));
ctx.Define(inst, NewBinary(state, OpBitwiseOr, state.uint_type, low, high));
return true;
}
case IR::ValueOpcode::FPAbs32:
ctx.Define(inst, EmitFAbsValue(state, ctx.Arg(inst, 0)));
return true;
case IR::ValueOpcode::FPNeg32:
ctx.Define(inst, EmitFNegateValue(state, ctx.Arg(inst, 0)));
return true;
case IR::ValueOpcode::FPSaturate32:
ctx.Define(inst, EmitExt(state, state.float_type, GlslFClamp,
{ctx.Arg(inst, 0), ConstantF32(state, 0),
ConstantF32(state, 0x3f800000u)}));
return true;
case IR::ValueOpcode::BitFieldInsert:
ctx.Emit(inst, OpBitFieldInsert, IR::Type::U32,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2), ctx.Arg(inst, 3)});
return true;
case IR::ValueOpcode::BitFieldUExtract:
ctx.Emit(inst, OpBitFieldUExtract, IR::Type::U32,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2)});
return true;
case IR::ValueOpcode::BitFieldSExtract:
ctx.Emit(inst, OpBitFieldSExtract, IR::Type::U32,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2)});
return true;
case IR::ValueOpcode::SelectU1:
ctx.Emit(inst, OpSelect, IR::Type::U1,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2)});
return true;
case IR::ValueOpcode::SelectU32:
ctx.Emit(inst, OpSelect, IR::Type::U32,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2)});
return true;
case IR::ValueOpcode::SelectF32:
ctx.Emit(inst, OpSelect, IR::Type::F32,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2)});
return true;
case IR::ValueOpcode::IAdd32: return binary(OpIAdd, IR::Type::U32);
case IR::ValueOpcode::ISub32: return binary(OpISub, IR::Type::U32);
case IR::ValueOpcode::IMul32: return binary(OpIMul, IR::Type::U32);
case IR::ValueOpcode::UDiv32: return binary(OpUDiv, IR::Type::U32);
case IR::ValueOpcode::IAdd64:
ctx.Define(inst, EmitAdd64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::ISub64:
ctx.Define(inst, EmitSub64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::IMul64:
ctx.Define(inst, EmitMul64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::IAddCarry32:
ctx.Emit(inst, OpIAddCarry, IR::Type::U32x2, {ctx.Arg(inst, 0), ctx.Arg(inst, 1)});
return true;
case IR::ValueOpcode::SMulHi:
ctx.Define(inst, EmitMulHigh(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), true));
return true;
case IR::ValueOpcode::UMulHi:
ctx.Define(inst, EmitMulHigh(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), false));
return true;
case IR::ValueOpcode::IAbs32: {
const auto value = ctx.Arg(inst, 0);
const auto neg = NewUnary(state, OpSNegate, state.uint_type, value);
const auto negative =
NewBinary(state, OpSLessThan, state.bool_type, value, ConstantU32(state, 0));
ctx.Define(inst, NewSelect(state, state.uint_type, negative, neg, value));
return true;
}
case IR::ValueOpcode::ShiftLeftLogical32: return binary(OpShiftLeftLogical, IR::Type::U32);
case IR::ValueOpcode::ShiftRightLogical32:
return binary(OpShiftRightLogical, IR::Type::U32);
case IR::ValueOpcode::ShiftRightArithmetic32:
return binary(OpShiftRightArithmetic, IR::Type::U32);
case IR::ValueOpcode::ShiftLeftLogical64:
ctx.Define(inst,
EmitShift64(state, OpShiftLeftLogical, ctx.Arg(inst, 0), ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::ShiftRightLogical64:
ctx.Define(inst,
EmitShift64(state, OpShiftRightLogical, ctx.Arg(inst, 0), ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::ShiftRightArithmetic64:
ctx.Define(inst, EmitShift64(state, OpShiftRightArithmetic, ctx.Arg(inst, 0),
ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::BitwiseAnd32: return binary(OpBitwiseAnd, IR::Type::U32);
case IR::ValueOpcode::BitwiseOr32: return binary(OpBitwiseOr, IR::Type::U32);
case IR::ValueOpcode::BitwiseXor32: return binary(OpBitwiseXor, IR::Type::U32);
case IR::ValueOpcode::BitwiseNot32: return unary(OpNot, IR::Type::U32);
case IR::ValueOpcode::BitwiseAnd64:
ctx.Define(inst,
EmitBitwise64(state, OpBitwiseAnd, ctx.Arg(inst, 0), ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::BitwiseOr64:
ctx.Define(inst, EmitBitwise64(state, OpBitwiseOr, ctx.Arg(inst, 0), ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::BitReverse32: return unary(OpBitReverse, IR::Type::U32);
case IR::ValueOpcode::BitCount32: return unary(OpBitCount, IR::Type::U32);
case IR::ValueOpcode::BitCount64: {
const auto pair = ExtractPair(state, ctx.Arg(inst, 0));
ctx.Define(inst, NewBinary(state, OpIAdd, state.uint_type,
NewUnary(state, OpBitCount, state.uint_type, pair.low),
NewUnary(state, OpBitCount, state.uint_type, pair.high)));
return true;
}
case IR::ValueOpcode::FindILsb32: {
const auto value = EmitExt(state, state.int_type, GlslFindILsb, {ctx.Arg(inst, 0)});
ctx.Define(inst, NewUnary(state, OpBitcast, state.uint_type, value));
return true;
}
case IR::ValueOpcode::FindUMsb32: {
const auto value = EmitExt(state, state.int_type, GlslFindUMsb, {ctx.Arg(inst, 0)});
ctx.Define(inst, NewUnary(state, OpBitcast, state.uint_type, value));
return true;
}
case IR::ValueOpcode::FindUMsb64:
ctx.Define(inst, EmitFindMsb64(state, ctx.Arg(inst, 0)));
return true;
case IR::ValueOpcode::FindSMsb32: {
const auto value =
EmitExt(state, state.int_type, 74u,
{NewUnary(state, OpBitcast, state.int_type, ctx.Arg(inst, 0))});
ctx.Define(inst, NewUnary(state, OpBitcast, state.uint_type, value));
return true;
}
case IR::ValueOpcode::SMin32:
ctx.Define(inst, EmitMinMaxI32Value(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), false));
return true;
case IR::ValueOpcode::SMax32:
ctx.Define(inst, EmitMinMaxI32Value(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), true));
return true;
case IR::ValueOpcode::UMin32:
ctx.Define(inst, EmitMinMaxU32Value(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), false));
return true;
case IR::ValueOpcode::UMax32:
ctx.Define(inst, EmitMinMaxU32Value(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), true));
return true;
case IR::ValueOpcode::SMinTri32:
ctx.Define(inst, EmitMinMax3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
ctx.Arg(inst, 2), true, false));
return true;
case IR::ValueOpcode::SMaxTri32:
ctx.Define(inst, EmitMinMax3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
ctx.Arg(inst, 2), true, true));
return true;
case IR::ValueOpcode::UMinTri32:
ctx.Define(inst, EmitMinMax3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
ctx.Arg(inst, 2), false, false));
return true;
case IR::ValueOpcode::UMaxTri32:
ctx.Define(inst, EmitMinMax3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
ctx.Arg(inst, 2), false, true));
return true;
case IR::ValueOpcode::SMedTri32:
ctx.Define(inst,
EmitMed3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2), true));
return true;
case IR::ValueOpcode::UMedTri32:
ctx.Define(
inst, EmitMed3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2), false));
return true;
case IR::ValueOpcode::SLessThan32: return binary(OpSLessThan, IR::Type::U1);
case IR::ValueOpcode::ULessThan32: return binary(OpULessThan, IR::Type::U1);
case IR::ValueOpcode::IEqual32: return binary(OpIEqual, IR::Type::U1);
case IR::ValueOpcode::SLessThanEqual32: return binary(OpSLessThanEqual, IR::Type::U1);
case IR::ValueOpcode::ULessThanEqual32: return binary(OpULessThanEqual, IR::Type::U1);
case IR::ValueOpcode::SGreaterThan32: return binary(OpSGreaterThan, IR::Type::U1);
case IR::ValueOpcode::UGreaterThan32: return binary(OpUGreaterThan, IR::Type::U1);
case IR::ValueOpcode::INotEqual32: return binary(OpINotEqual, IR::Type::U1);
case IR::ValueOpcode::SGreaterThanEqual32: return binary(OpSGreaterThanEqual, IR::Type::U1);
case IR::ValueOpcode::UGreaterThanEqual32: return binary(OpUGreaterThanEqual, IR::Type::U1);
case IR::ValueOpcode::IEqual64:
ctx.Define(inst,
Compare64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), 0, 0, true, false));
return true;
case IR::ValueOpcode::INotEqual64:
ctx.Define(inst,
Compare64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), 0, 0, true, true));
return true;
case IR::ValueOpcode::ULessThan64:
ctx.Define(inst, Compare64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), OpULessThan,
OpULessThan));
return true;
case IR::ValueOpcode::SLessThan64:
ctx.Define(inst, Compare64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), OpSLessThan,
OpULessThan));
return true;
case IR::ValueOpcode::UGreaterThan64:
ctx.Define(inst, Compare64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), OpUGreaterThan,
OpUGreaterThan));
return true;
case IR::ValueOpcode::SGreaterThan64:
ctx.Define(inst, Compare64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), OpSGreaterThan,
OpUGreaterThan));
return true;
case IR::ValueOpcode::ULessThanEqual64: {
const auto greater = Compare64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
OpUGreaterThan, OpUGreaterThan);
ctx.Define(inst, NewUnary(state, OpLogicalNot, state.bool_type, greater));
return true;
}
case IR::ValueOpcode::SLessThanEqual64: {
const auto greater = Compare64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
OpSGreaterThan, OpUGreaterThan);
ctx.Define(inst, NewUnary(state, OpLogicalNot, state.bool_type, greater));
return true;
}
case IR::ValueOpcode::UGreaterThanEqual64: {
const auto less =
Compare64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), OpULessThan, OpULessThan);
ctx.Define(inst, NewUnary(state, OpLogicalNot, state.bool_type, less));
return true;
}
case IR::ValueOpcode::SGreaterThanEqual64: {
const auto less =
Compare64(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), OpSLessThan, OpULessThan);
ctx.Define(inst, NewUnary(state, OpLogicalNot, state.bool_type, less));
return true;
}
case IR::ValueOpcode::LogicalOr: return binary(OpLogicalOr, IR::Type::U1);
case IR::ValueOpcode::LogicalAnd: return binary(OpLogicalAnd, IR::Type::U1);
case IR::ValueOpcode::LogicalXor: return binary(OpLogicalNotEqual, IR::Type::U1);
case IR::ValueOpcode::LogicalNot: return unary(OpLogicalNot, IR::Type::U1);
case IR::ValueOpcode::FPOrdEqual32: return binary(OpFOrdEqual, IR::Type::U1);
case IR::ValueOpcode::FPUnordEqual32: return binary(OpFUnordEqual, IR::Type::U1);
case IR::ValueOpcode::FPOrdNotEqual32: return binary(OpFOrdNotEqual, IR::Type::U1);
case IR::ValueOpcode::FPUnordNotEqual32: return binary(OpFUnordNotEqual, IR::Type::U1);
case IR::ValueOpcode::FPOrdLessThan32: return binary(OpFOrdLessThan, IR::Type::U1);
case IR::ValueOpcode::FPUnordLessThan32: return binary(OpFUnordLessThan, IR::Type::U1);
case IR::ValueOpcode::FPOrdGreaterThan32: return binary(OpFOrdGreaterThan, IR::Type::U1);
case IR::ValueOpcode::FPUnordGreaterThan32:
return binary(OpFUnordGreaterThan, IR::Type::U1);
case IR::ValueOpcode::FPOrdLessThanEqual32:
return binary(OpFOrdLessThanEqual, IR::Type::U1);
case IR::ValueOpcode::FPUnordLessThanEqual32:
return binary(OpFUnordLessThanEqual, IR::Type::U1);
case IR::ValueOpcode::FPOrdGreaterThanEqual32:
return binary(OpFOrdGreaterThanEqual, IR::Type::U1);
case IR::ValueOpcode::FPUnordGreaterThanEqual32:
return binary(OpFUnordGreaterThanEqual, IR::Type::U1);
case IR::ValueOpcode::FPIsNan32:
ctx.Emit(inst, OpFUnordNotEqual, IR::Type::U1, {ctx.Arg(inst, 0), ctx.Arg(inst, 0)});
return true;
case IR::ValueOpcode::FPCmpClass32:
ctx.Define(inst, EmitClassMaskF32(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::FPAdd32: return binary(OpFAdd, IR::Type::F32);
case IR::ValueOpcode::FPSub32: return binary(OpFSub, IR::Type::F32);
case IR::ValueOpcode::FPMul32: return binary(OpFMul, IR::Type::F32);
case IR::ValueOpcode::FPFma32:
ctx.Define(inst, EmitExt(state, state.float_type, GlslFma,
{ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2)}));
return true;
case IR::ValueOpcode::FPMin32:
ctx.Define(inst, EmitMinMaxF32Value(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), false));
return true;
case IR::ValueOpcode::FPMax32:
ctx.Define(inst, EmitMinMaxF32Value(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), true));
return true;
case IR::ValueOpcode::FPMinTri32:
ctx.Define(inst, EmitFMinMax3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
ctx.Arg(inst, 2), false));
return true;
case IR::ValueOpcode::FPMaxTri32:
ctx.Define(inst, EmitFMinMax3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1),
ctx.Arg(inst, 2), true));
return true;
case IR::ValueOpcode::FPMedTri32:
ctx.Define(inst,
EmitFMed3(state, ctx.Arg(inst, 0), ctx.Arg(inst, 1), ctx.Arg(inst, 2)));
return true;
case IR::ValueOpcode::FPRecip32: {
const auto source = EmitFlushF32DenormToSignedZero(state, ctx.Arg(inst, 0));
ctx.Define(inst, NewBinary(state, OpFDiv, state.float_type,
ConstantF32(state, 0x3f800000u), source));
return true;
}
case IR::ValueOpcode::FPRecipIFlag32:
// Integer-to-float inputs used by IFLAG cannot be denormal.
ctx.Define(inst, NewBinary(state, OpFDiv, state.float_type,
ConstantF32(state, 0x3f800000u), ctx.Arg(inst, 0)));
return true;
case IR::ValueOpcode::FPRecipSqrt32:
ctx.Define(inst, EmitExt(state, state.float_type, GlslInverseSqrt,
{EmitFlushF32DenormToSignedZero(state, ctx.Arg(inst, 0))}));
return true;
case IR::ValueOpcode::FPSqrt:
ctx.Define(inst, EmitExt(state, state.float_type, GlslSqrt,
{EmitFlushF32DenormToSignedZero(state, ctx.Arg(inst, 0))}));
return true;
case IR::ValueOpcode::FPSin:
case IR::ValueOpcode::FPCos: {
auto source = EmitTrigCycleF32(state, ctx.Arg(inst, 0), op == IR::ValueOpcode::FPSin);
source =
NewBinary(state, OpFMul, state.float_type, source, ConstantF32(state, 0x40c90fdbu));
ctx.Define(inst, EmitExt(state, state.float_type,
op == IR::ValueOpcode::FPSin ? GlslSin : GlslCos, {source}));
return true;
}
case IR::ValueOpcode::FPExp2:
ctx.Define(inst, EmitExt(state, state.float_type, GlslExp2,
{EmitFlushF32DenormToSignedZero(state, ctx.Arg(inst, 0))}));
return true;
case IR::ValueOpcode::FPLog2:
ctx.Define(inst, EmitExt(state, state.float_type, GlslLog2,
{EmitFlushF32DenormToSignedZero(state, ctx.Arg(inst, 0))}));
return true;
case IR::ValueOpcode::FPLdexp: {
const auto exponent = NewUnary(state, OpBitcast, state.int_type, ctx.Arg(inst, 1));
ctx.Define(inst,
EmitExt(state, state.float_type, GlslLdexp, {ctx.Arg(inst, 0), exponent}));
return true;
}
case IR::ValueOpcode::FPRoundEven32: return ext_unary(GlslRoundEven);
case IR::ValueOpcode::FPFloor32: return ext_unary(GlslFloor);
case IR::ValueOpcode::FPCeil32: return ext_unary(GlslCeil);
case IR::ValueOpcode::FPTrunc32: return ext_unary(GlslTrunc);
case IR::ValueOpcode::FPFract32: return ext_unary(GlslFract);
default: return false;
}
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -0,0 +1,423 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
uint32_t EmitSelectValueU32(EmitterState& state, uint32_t cond, uint32_t true_value,
uint32_t false_value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpSelect, state.uint_type, ret, cond, true_value, false_value});
return ret;
}
uint32_t EmitAndConstant(EmitterState& state, uint32_t value, uint32_t mask);
void EmitShiftLeftLogicalU64Values(EmitterState& state, uint32_t low, uint32_t high, uint32_t shift,
uint32_t& out_low, uint32_t& out_high) {
const auto amount = EmitAndConstant(state, shift, 63u);
const auto dword_shift = EmitAndConstant(state, amount, 31u);
const auto less32 = state.builder.AllocateId();
const auto non_zero = state.builder.AllocateId();
const auto carry_count_base = state.builder.AllocateId();
const auto carry_count = state.builder.AllocateId();
const auto low_part = state.builder.AllocateId();
const auto high_part = state.builder.AllocateId();
const auto carry_part = state.builder.AllocateId();
const auto carry = state.builder.AllocateId();
const auto high_less = state.builder.AllocateId();
const auto high_ge = state.builder.AllocateId();
state.builder.AddFunction(
{OpULessThan, state.bool_type, less32, amount, ConstantU32(state, 32)});
state.builder.AddFunction(
{OpINotEqual, state.bool_type, non_zero, amount, ConstantU32(state, 0)});
state.builder.AddFunction(
{OpISub, state.uint_type, carry_count_base, ConstantU32(state, 32), dword_shift});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, carry_count, carry_count_base, ConstantU32(state, 31)});
state.builder.AddFunction({OpShiftLeftLogical, state.uint_type, low_part, low, dword_shift});
state.builder.AddFunction({OpShiftLeftLogical, state.uint_type, high_part, high, dword_shift});
state.builder.AddFunction({OpShiftRightLogical, state.uint_type, carry_part, low, carry_count});
state.builder.AddFunction(
{OpSelect, state.uint_type, carry, non_zero, carry_part, ConstantU32(state, 0)});
state.builder.AddFunction({OpBitwiseOr, state.uint_type, high_less, high_part, carry});
state.builder.AddFunction({OpShiftLeftLogical, state.uint_type, high_ge, low, dword_shift});
out_low = EmitSelectValueU32(state, less32, low_part, ConstantU32(state, 0));
out_high = EmitSelectValueU32(state, less32, high_less, high_ge);
}
void EmitShiftRightLogicalU64Values(EmitterState& state, uint32_t low, uint32_t high,
uint32_t shift, uint32_t& out_low, uint32_t& out_high) {
const auto amount = EmitAndConstant(state, shift, 63u);
const auto dword_shift = EmitAndConstant(state, amount, 31u);
const auto less32 = state.builder.AllocateId();
const auto non_zero = state.builder.AllocateId();
const auto carry_count_base = state.builder.AllocateId();
const auto carry_count = state.builder.AllocateId();
const auto low_part = state.builder.AllocateId();
const auto high_part = state.builder.AllocateId();
const auto carry_part = state.builder.AllocateId();
const auto carry = state.builder.AllocateId();
const auto low_less = state.builder.AllocateId();
const auto low_ge = state.builder.AllocateId();
state.builder.AddFunction(
{OpULessThan, state.bool_type, less32, amount, ConstantU32(state, 32)});
state.builder.AddFunction(
{OpINotEqual, state.bool_type, non_zero, amount, ConstantU32(state, 0)});
state.builder.AddFunction(
{OpISub, state.uint_type, carry_count_base, ConstantU32(state, 32), dword_shift});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, carry_count, carry_count_base, ConstantU32(state, 31)});
state.builder.AddFunction({OpShiftRightLogical, state.uint_type, low_part, low, dword_shift});
state.builder.AddFunction({OpShiftRightLogical, state.uint_type, high_part, high, dword_shift});
state.builder.AddFunction({OpShiftLeftLogical, state.uint_type, carry_part, high, carry_count});
state.builder.AddFunction(
{OpSelect, state.uint_type, carry, non_zero, carry_part, ConstantU32(state, 0)});
state.builder.AddFunction({OpBitwiseOr, state.uint_type, low_less, low_part, carry});
state.builder.AddFunction({OpShiftRightLogical, state.uint_type, low_ge, high, dword_shift});
out_low = EmitSelectValueU32(state, less32, low_less, low_ge);
out_high = EmitSelectValueU32(state, less32, high_part, ConstantU32(state, 0));
}
uint32_t EmitAndConstant(EmitterState& state, uint32_t value, uint32_t mask) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, ret, value, ConstantU32(state, mask)});
return ret;
}
AddCarryResult EmitAddCarryValues(EmitterState& state, uint32_t lhs, uint32_t rhs,
uint32_t carry_in) {
const auto pair0 = state.builder.AllocateId();
const auto sum0 = state.builder.AllocateId();
const auto carry0 = state.builder.AllocateId();
const auto pair1 = state.builder.AllocateId();
const auto sum1 = state.builder.AllocateId();
const auto carry1 = state.builder.AllocateId();
const auto carry = state.builder.AllocateId();
state.builder.AddFunction({OpIAddCarry, state.uint_pair_type, pair0, lhs, rhs});
state.builder.AddFunction({OpCompositeExtract, state.uint_type, sum0, pair0, 0});
state.builder.AddFunction({OpCompositeExtract, state.uint_type, carry0, pair0, 1});
state.builder.AddFunction({OpIAddCarry, state.uint_pair_type, pair1, sum0, carry_in});
state.builder.AddFunction({OpCompositeExtract, state.uint_type, sum1, pair1, 0});
state.builder.AddFunction({OpCompositeExtract, state.uint_type, carry1, pair1, 1});
state.builder.AddFunction({OpBitwiseOr, state.uint_type, carry, carry0, carry1});
return {sum1, carry};
}
uint32_t EmitShiftRightConstant(EmitterState& state, uint32_t value, uint32_t shift) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{OpShiftRightLogical, state.uint_type, ret, value, ConstantU32(state, shift)});
return ret;
}
uint32_t EmitOrU32(EmitterState& state, uint32_t lhs, uint32_t rhs) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpBitwiseOr, state.uint_type, ret, lhs, rhs});
return ret;
}
uint32_t EmitCompareU32Constant(EmitterState& state, uint32_t opcode, uint32_t value,
uint32_t constant) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({opcode, state.bool_type, ret, value, ConstantU32(state, constant)});
return ret;
}
uint32_t EmitSubConstantMinusU32(EmitterState& state, uint32_t constant, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpISub, state.uint_type, ret, ConstantU32(state, constant), value});
return ret;
}
uint32_t EmitF32ToF16RtzBits(EmitterState& state, uint32_t f32) {
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, state.uint_type, bits, f32});
const auto sign = EmitAndConstant(state, EmitShiftRightConstant(state, bits, 16), 0x8000u);
const auto exp = EmitAndConstant(state, EmitShiftRightConstant(state, bits, 23), 0xffu);
const auto mant = EmitAndConstant(state, bits, 0x007fffffu);
const auto half_exp = state.builder.AllocateId();
const auto normal_exp = state.builder.AllocateId();
const auto normal_mant = EmitShiftRightConstant(state, mant, 13);
const auto normal_payload = state.builder.AllocateId();
const auto normal = state.builder.AllocateId();
state.builder.AddFunction({OpISub, state.uint_type, half_exp, exp, ConstantU32(state, 112)});
state.builder.AddFunction(
{OpShiftLeftLogical, state.uint_type, normal_exp, half_exp, ConstantU32(state, 10)});
state.builder.AddFunction(
{OpBitwiseOr, state.uint_type, normal_payload, normal_exp, normal_mant});
state.builder.AddFunction({OpBitwiseOr, state.uint_type, normal, sign, normal_payload});
const auto mant_with_hidden = EmitOrU32(state, mant, ConstantU32(state, 0x00800000u));
const auto raw_sub_shift = EmitSubConstantMinusU32(state, 126, exp);
const auto exp_lt_103 = EmitCompareU32Constant(state, OpULessThan, exp, 103);
const auto exp_gt_112 = EmitCompareU32Constant(state, OpUGreaterThan, exp, 112);
const auto sub_shift_low =
EmitSelectValueU32(state, exp_lt_103, ConstantU32(state, 31), raw_sub_shift);
const auto sub_shift =
EmitSelectValueU32(state, exp_gt_112, ConstantU32(state, 14), sub_shift_low);
const auto sub_mant = state.builder.AllocateId();
const auto subnormal = state.builder.AllocateId();
state.builder.AddFunction(
{OpShiftRightLogical, state.uint_type, sub_mant, mant_with_hidden, sub_shift});
state.builder.AddFunction({OpBitwiseOr, state.uint_type, subnormal, sign, sub_mant});
const auto nan_payload =
EmitOrU32(state, EmitShiftRightConstant(state, mant, 13), ConstantU32(state, 0x0200u));
const auto nan =
EmitOrU32(state, sign, EmitOrU32(state, ConstantU32(state, 0x7c00u), nan_payload));
const auto inf = EmitOrU32(state, sign, ConstantU32(state, 0x7c00u));
const auto max_finite = EmitOrU32(state, sign, ConstantU32(state, 0x7bffu));
const auto mant_zero = EmitCompareU32Constant(state, OpIEqual, mant, 0);
const auto special = EmitSelectValueU32(state, mant_zero, inf, nan);
const auto exp_le_112 = EmitCompareU32Constant(state, OpULessThanEqual, exp, 112);
const auto exp_ge_143 = EmitCompareU32Constant(state, OpUGreaterThanEqual, exp, 143);
const auto exp_eq_255 = EmitCompareU32Constant(state, OpIEqual, exp, 255);
const auto finite0 = EmitSelectValueU32(state, exp_le_112, subnormal, normal);
const auto finite1 = EmitSelectValueU32(state, exp_lt_103, sign, finite0);
const auto finite2 = EmitSelectValueU32(state, exp_ge_143, max_finite, finite1);
return EmitAndConstant(state, EmitSelectValueU32(state, exp_eq_255, special, finite2), 0xffffu);
}
uint32_t EmitMinMaxU32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) {
const auto cond = state.builder.AllocateId();
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{max_value ? OpUGreaterThan : OpULessThan, state.bool_type, cond, lhs, rhs});
state.builder.AddFunction({OpSelect, state.uint_type, ret, cond, lhs, rhs});
return ret;
}
uint32_t EmitMinMaxI32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) {
const auto cond = state.builder.AllocateId();
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{max_value ? OpSGreaterThan : OpSLessThan, state.bool_type, cond, lhs, rhs});
state.builder.AddFunction({OpSelect, state.uint_type, ret, cond, lhs, rhs});
return ret;
}
uint32_t EmitBitcastF32ToU32(EmitterState& state, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, state.uint_type, ret, value});
return ret;
}
uint32_t EmitBitcastU32ToF32(EmitterState& state, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, state.float_type, ret, value});
return ret;
}
uint32_t EmitAndU32(EmitterState& state, uint32_t lhs, uint32_t rhs) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpBitwiseAnd, state.uint_type, ret, lhs, rhs});
return ret;
}
uint32_t EmitLogicalAndBool(EmitterState& state, uint32_t lhs, uint32_t rhs) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpLogicalAnd, state.bool_type, ret, lhs, rhs});
return ret;
}
uint32_t EmitLogicalOrBool(EmitterState& state, uint32_t lhs, uint32_t rhs) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpLogicalOr, state.bool_type, ret, lhs, rhs});
return ret;
}
uint32_t EmitLogicalNotBool(EmitterState& state, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpLogicalNot, state.bool_type, ret, value});
return ret;
}
F32Class EmitClassifyF32(EmitterState& state, uint32_t value) {
F32Class cls;
cls.bits = EmitBitcastF32ToU32(state, value);
const auto abs_bits = EmitAndConstant(state, cls.bits, 0x7fffffffu);
const auto exponent_bits = EmitAndConstant(state, abs_bits, 0x7f800000u);
const auto mantissa_bits = EmitAndConstant(state, abs_bits, 0x007fffffu);
const auto exponent_max = EmitCompareU32Constant(state, OpIEqual, exponent_bits, 0x7f800000u);
const auto mantissa_nonzero = EmitCompareU32Constant(state, OpINotEqual, mantissa_bits, 0);
const auto quiet_bit_clear =
EmitCompareU32Constant(state, OpIEqual, EmitAndConstant(state, cls.bits, 0x00400000u), 0);
cls.nan = EmitLogicalAndBool(state, exponent_max, mantissa_nonzero);
cls.snan = EmitLogicalAndBool(state, cls.nan, quiet_bit_clear);
cls.zero = EmitCompareU32Constant(state, OpIEqual, abs_bits, 0);
cls.quiet_bits = EmitOrU32(state, cls.bits, ConstantU32(state, 0x00400000u));
return cls;
}
uint32_t EmitClassMaskBitMatch(EmitterState& state, uint32_t mask, uint32_t bit,
uint32_t class_match) {
const auto selected =
EmitCompareU32Constant(state, OpINotEqual, EmitAndConstant(state, mask, 1u << bit), 0);
return EmitLogicalAndBool(state, selected, class_match);
}
uint32_t EmitClassMaskF32(EmitterState& state, uint32_t value, uint32_t mask) {
const auto bits = EmitBitcastF32ToU32(state, value);
const auto sign_bits = EmitAndConstant(state, bits, 0x80000000u);
const auto abs_bits = EmitAndConstant(state, bits, 0x7fffffffu);
const auto exponent_bits = EmitAndConstant(state, abs_bits, 0x7f800000u);
const auto mantissa_bits = EmitAndConstant(state, abs_bits, 0x007fffffu);
const auto quiet_bits = EmitAndConstant(state, mantissa_bits, 0x00400000u);
const auto sign = EmitCompareU32Constant(state, OpINotEqual, sign_bits, 0);
const auto positive = EmitLogicalNotBool(state, sign);
const auto exponent_zero = EmitCompareU32Constant(state, OpIEqual, exponent_bits, 0);
const auto exponent_nonzero = EmitLogicalNotBool(state, exponent_zero);
const auto exponent_inf = EmitCompareU32Constant(state, OpIEqual, exponent_bits, 0x7f800000u);
const auto finite_exponent = EmitLogicalNotBool(state, exponent_inf);
const auto mantissa_zero = EmitCompareU32Constant(state, OpIEqual, mantissa_bits, 0);
const auto mantissa_nonzero = EmitLogicalNotBool(state, mantissa_zero);
const auto quiet = EmitCompareU32Constant(state, OpINotEqual, quiet_bits, 0);
const auto nan_common = EmitLogicalAndBool(state, exponent_inf, mantissa_nonzero);
const auto snan = EmitLogicalAndBool(state, nan_common, EmitLogicalNotBool(state, quiet));
const auto qnan = EmitLogicalAndBool(state, nan_common, quiet);
const auto inf = EmitLogicalAndBool(state, exponent_inf, mantissa_zero);
const auto normal = EmitLogicalAndBool(state, exponent_nonzero, finite_exponent);
const auto denorm = EmitLogicalAndBool(state, exponent_zero, mantissa_nonzero);
const auto zero = EmitCompareU32Constant(state, OpIEqual, abs_bits, 0);
uint32_t match = EmitClassMaskBitMatch(state, mask, 0, snan);
match = EmitLogicalOrBool(state, match, EmitClassMaskBitMatch(state, mask, 1, qnan));
match = EmitLogicalOrBool(
state, match, EmitClassMaskBitMatch(state, mask, 2, EmitLogicalAndBool(state, inf, sign)));
match = EmitLogicalOrBool(
state, match,
EmitClassMaskBitMatch(state, mask, 3, EmitLogicalAndBool(state, normal, sign)));
match = EmitLogicalOrBool(
state, match,
EmitClassMaskBitMatch(state, mask, 4, EmitLogicalAndBool(state, denorm, sign)));
match = EmitLogicalOrBool(
state, match, EmitClassMaskBitMatch(state, mask, 5, EmitLogicalAndBool(state, zero, sign)));
match = EmitLogicalOrBool(
state, match,
EmitClassMaskBitMatch(state, mask, 6, EmitLogicalAndBool(state, zero, positive)));
match = EmitLogicalOrBool(
state, match,
EmitClassMaskBitMatch(state, mask, 7, EmitLogicalAndBool(state, denorm, positive)));
match = EmitLogicalOrBool(
state, match,
EmitClassMaskBitMatch(state, mask, 8, EmitLogicalAndBool(state, normal, positive)));
return EmitLogicalOrBool(
state, match,
EmitClassMaskBitMatch(state, mask, 9, EmitLogicalAndBool(state, inf, positive)));
}
uint32_t EmitMinMaxF32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) {
const auto lhs_class = EmitClassifyF32(state, lhs);
const auto rhs_class = EmitClassifyF32(state, rhs);
const auto numeric_cond = state.builder.AllocateId();
state.builder.AddFunction({max_value ? OpFOrdGreaterThanEqual : OpFOrdLessThan, state.bool_type,
numeric_cond, lhs, rhs});
const auto ordered_bits =
EmitSelectValueU32(state, numeric_cond, lhs_class.bits, rhs_class.bits);
const auto both_zero = EmitLogicalAndBool(state, lhs_class.zero, rhs_class.zero);
const auto zero_bits = max_value ? EmitAndU32(state, lhs_class.bits, rhs_class.bits)
: EmitOrU32(state, lhs_class.bits, rhs_class.bits);
const auto numeric_bits = EmitSelectValueU32(state, both_zero, zero_bits, ordered_bits);
const auto rhs_nan_bits =
EmitSelectValueU32(state, rhs_class.nan, lhs_class.bits, numeric_bits);
const auto non_snan_bits =
EmitSelectValueU32(state, lhs_class.nan, rhs_class.bits, rhs_nan_bits);
const auto rhs_snan_bits =
EmitSelectValueU32(state, rhs_class.snan, rhs_class.quiet_bits, non_snan_bits);
const auto result_bits =
EmitSelectValueU32(state, lhs_class.snan, lhs_class.quiet_bits, rhs_snan_bits);
return EmitBitcastU32ToF32(state, result_bits);
}
uint32_t EmitTruncF32Value(EmitterState& state, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{OpExtInst, state.float_type, ret, state.glsl_std450, GlslTrunc, value});
return ret;
}
uint32_t EmitFlushF32DenormToSignedZero(EmitterState& state, uint32_t value) {
const auto bits = state.builder.AllocateId();
const auto abs_bits = state.builder.AllocateId();
const auto sign_bits = state.builder.AllocateId();
const auto non_zero = state.builder.AllocateId();
const auto subnormal = state.builder.AllocateId();
const auto flush = state.builder.AllocateId();
const auto selected = state.builder.AllocateId();
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, state.uint_type, bits, value});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, abs_bits, bits, ConstantU32(state, 0x7fffffffu)});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, sign_bits, bits, ConstantU32(state, 0x80000000u)});
state.builder.AddFunction(
{OpINotEqual, state.bool_type, non_zero, abs_bits, ConstantU32(state, 0)});
state.builder.AddFunction(
{OpULessThan, state.bool_type, subnormal, abs_bits, ConstantU32(state, 0x00800000u)});
state.builder.AddFunction({OpLogicalAnd, state.bool_type, flush, non_zero, subnormal});
state.builder.AddFunction({OpSelect, state.uint_type, selected, flush, sign_bits, bits});
state.builder.AddFunction({OpBitcast, state.float_type, ret, selected});
return ret;
}
uint32_t EmitTrigCycleF32(EmitterState& state, uint32_t src, bool preserve_signed_zero) {
const auto fract = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
const auto abs_bits = state.builder.AllocateId();
const auto large = state.builder.AllocateId();
const auto finite = state.builder.AllocateId();
const auto large_finite = state.builder.AllocateId();
const auto reduced = state.builder.AllocateId();
state.builder.AddFunction(
{OpExtInst, state.float_type, fract, state.glsl_std450, GlslFract, src});
state.builder.AddFunction({OpBitcast, state.uint_type, bits, src});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, abs_bits, bits, ConstantU32(state, 0x7fffffffu)});
state.builder.AddFunction(
{OpUGreaterThanEqual, state.bool_type, large, abs_bits, ConstantU32(state, 0x4b000000u)});
state.builder.AddFunction(
{OpULessThan, state.bool_type, finite, abs_bits, ConstantU32(state, 0x7f800000u)});
state.builder.AddFunction({OpLogicalAnd, state.bool_type, large_finite, large, finite});
state.builder.AddFunction(
{OpSelect, state.float_type, reduced, large_finite, ConstantF32(state, 0), fract});
if (!preserve_signed_zero) {
return reduced;
}
const auto zero = state.builder.AllocateId();
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpIEqual, state.bool_type, zero, abs_bits, ConstantU32(state, 0)});
state.builder.AddFunction({OpSelect, state.float_type, ret, zero, src, reduced});
return ret;
}
uint32_t EmitFNegateValue(EmitterState& state, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpFNegate, state.float_type, ret, value});
return ret;
}
uint32_t EmitFAbsValue(EmitterState& state, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{OpExtInst, state.float_type, ret, state.glsl_std450, GlslFAbs, value});
return ret;
}
uint32_t EmitF16BitsToF32(EmitterState& state, uint32_t bits) {
const auto unpacked = state.builder.AllocateId();
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{OpExtInst, state.vec2_float_type, unpacked, state.glsl_std450, GlslUnpackHalf2x16, bits});
state.builder.AddFunction({OpCompositeExtract, state.float_type, ret, unpacked, 0});
return ret;
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -1,16 +1,16 @@
#include "common/assert.h"
#include "graphics/guest_gpu/gpu_defs.h"
#include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
#include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include "graphics/shader/recompiler/ir/ValueProgram.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
uint32_t PixelParameterMappedLocation(const EmitterState& state, uint32_t attr) {
const auto* ps = state.pixel_input_info;
if (state.stage != ShaderType::Pixel || ps == nullptr) {
if (state.stage != ShaderType::Pixel) {
return attr;
}
return ShaderPixelParameterMappedLocation(*ps, attr);
return ShaderPixelParameterMappedLocation(*state.input_info.pixel, attr);
}
uint32_t PixelParameterLocation(const EmitterState& state, uint32_t attr) {
@@ -21,16 +21,15 @@ uint32_t PixelParameterLocation(const EmitterState& state, uint32_t attr) {
active_inputs[active_count++] = input.location;
}
}
return state.stage == ShaderType::Pixel && state.pixel_input_info != nullptr
? ShaderPixelParameterLocation(*state.pixel_input_info,
return state.stage == ShaderType::Pixel
? ShaderPixelParameterLocation(*state.input_info.pixel,
{active_inputs.data(), active_count}, attr)
: attr;
}
bool PixelParameterIsFlat(const EmitterState& state, uint32_t attr) {
const auto* ps = state.pixel_input_info;
return state.stage == ShaderType::Pixel && ps != nullptr &&
ShaderPixelParameterIsFlat(*ps, attr);
return state.stage == ShaderType::Pixel &&
ShaderPixelParameterIsFlat(*state.input_info.pixel, attr);
}
void SetError(std::string* error, const char* message) {
@@ -47,196 +46,10 @@ void CollectRegister(std::vector<RegisterBinding>& registers, IR::Register reg)
registers.push_back({reg, 0});
}
IR::Operand MakeRegisterOperand(IR::RegisterFile file, uint32_t index) {
IR::Operand operand;
operand.kind = IR::OperandKind::Register;
operand.reg = {file, index};
return operand;
}
IR::Register SccRegister() {
return {IR::RegisterFile::Scc, static_cast<uint32_t>(Decoder::OperandKind::Scc)};
}
IR::Operand SccOperand() {
return MakeRegisterOperand(IR::RegisterFile::Scc,
static_cast<uint32_t>(Decoder::OperandKind::Scc));
}
bool IsInactiveWave32ExecHigh(const EmitterState& state, IR::Register reg) {
return state.wave_size == 32u && reg.file == IR::RegisterFile::Exec && reg.index == 1u;
}
bool IsMaskRegisterFile(IR::RegisterFile file) {
return file == IR::RegisterFile::Exec || file == IR::RegisterFile::Vcc;
}
bool IsSccOperand(const IR::Operand& operand) {
return operand.kind == IR::OperandKind::Register && operand.reg.file == IR::RegisterFile::Scc;
}
void CollectMaskStateRegisters(std::vector<RegisterBinding>& registers) {
CollectRegister(registers, {IR::RegisterFile::Exec, 0});
CollectRegister(registers, {IR::RegisterFile::Exec, 1});
CollectRegister(registers, {IR::RegisterFile::Vcc, 0});
CollectRegister(registers, {IR::RegisterFile::Vcc, 1});
CollectRegister(registers, SccRegister());
}
void CollectSequentialRegisters(std::vector<RegisterBinding>& registers, const IR::Operand& base,
uint32_t count) {
if (base.kind != IR::OperandKind::Register) {
return;
}
for (uint32_t i = 0; i < count; i++) {
auto reg = base.reg;
reg.index += i;
CollectRegister(registers, reg);
}
}
uint32_t MaxCollectedVectorRegisterEnd(const std::vector<RegisterBinding>& registers) {
uint32_t max_end = 0;
for (const auto& binding: registers) {
if (binding.reg.file == IR::RegisterFile::Vector) {
max_end = std::max(max_end, binding.reg.index + 1u);
}
}
return std::min(max_end, 256u);
}
void CollectMoveRelSourceRegisters(const IR::Program& program,
std::vector<RegisterBinding>& registers) {
const auto max_vector_end = MaxCollectedVectorRegisterEnd(registers);
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (inst.op != IR::Opcode::MoveRelSourceU32 ||
inst.src[0].kind != IR::OperandKind::Register ||
inst.src[0].reg.file != IR::RegisterFile::Vector) {
continue;
}
const auto base_index = inst.src[0].reg.index;
const auto limit = std::min(std::max(max_vector_end, base_index + 1u), 256u);
CollectSequentialRegisters(registers, inst.src[0], limit - base_index);
}
}
}
bool IsPairDwordOpcode(IR::Opcode op) {
switch (op) {
case IR::Opcode::MoveU64:
case IR::Opcode::WqmB64:
case IR::Opcode::SaveexecB64:
case IR::Opcode::BitwiseAndU64:
case IR::Opcode::BitwiseAndNotU64:
case IR::Opcode::BitwiseOrU64:
case IR::Opcode::BitwiseOrNotU64:
case IR::Opcode::BitwiseXorU64:
case IR::Opcode::BitwiseNandU64:
case IR::Opcode::BitwiseNorU64:
case IR::Opcode::BitwiseXnorU64:
case IR::Opcode::BitwiseNotU64:
case IR::Opcode::BitFieldMaskU64:
case IR::Opcode::BitFieldExtractU64:
case IR::Opcode::BitReplicateB64B32:
case IR::Opcode::ShiftLeftLogicalU64:
case IR::Opcode::ShiftRightLogicalU64:
case IR::Opcode::SelectU64: return true;
default: return false;
}
}
uint32_t PairDwordSourceCount(IR::Opcode op, uint32_t src_count) {
switch (op) {
case IR::Opcode::BitFieldMaskU64: return 0;
case IR::Opcode::BitReplicateB64B32: return 0;
case IR::Opcode::BitFieldExtractU64: return 1;
case IR::Opcode::ShiftLeftLogicalU64:
case IR::Opcode::ShiftRightLogicalU64:
case IR::Opcode::BitwiseNotU64:
case IR::Opcode::SaveexecB64:
case IR::Opcode::WqmB64:
case IR::Opcode::MoveU64: return std::min<uint32_t>(src_count, 1u);
default: return src_count;
}
}
void CollectRegisters(const IR::Program& program, std::vector<RegisterBinding>& registers) {
CollectMaskStateRegisters(registers);
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (inst.dst.kind == IR::OperandKind::Register) {
CollectRegister(registers, inst.dst.reg);
}
if (inst.dst2.kind == IR::OperandKind::Register) {
CollectRegister(registers, inst.dst2.reg);
}
for (uint32_t i = 0; i < inst.src_count; i++) {
if (inst.src[i].kind == IR::OperandKind::Register) {
CollectRegister(registers, inst.src[i].reg);
}
}
if (IsPairDwordOpcode(inst.op)) {
CollectSequentialRegisters(registers, inst.dst, 2);
const uint32_t first_pair_src = inst.op == IR::Opcode::SelectU64 ? 1u : 0u;
const uint32_t pair_src_count = PairDwordSourceCount(inst.op, inst.src_count);
for (uint32_t i = first_pair_src; i < pair_src_count; i++) {
CollectSequentialRegisters(registers, inst.src[i], 2);
}
}
if (inst.op == IR::Opcode::BitCountU64 || inst.op == IR::Opcode::FindMsbFromHighU64) {
CollectSequentialRegisters(registers, inst.src[0], 2);
}
if (IR::IsCompare64Opcode(inst.op)) {
CollectSequentialRegisters(registers, inst.src[0], 2);
CollectSequentialRegisters(registers, inst.src[1], 2);
}
if (IR::IsCompareOpcode(inst.op) && inst.dst.kind == IR::OperandKind::Register &&
inst.dst.reg.file != IR::RegisterFile::Scc) {
CollectSequentialRegisters(registers, inst.dst, 2);
}
if (inst.op == IR::Opcode::IAddCarryU32 || inst.op == IR::Opcode::ISubBorrowU32) {
CollectSequentialRegisters(registers, inst.dst2, 2);
}
if (inst.op == IR::Opcode::UMadU64U32) {
CollectSequentialRegisters(registers, inst.dst, 2);
CollectSequentialRegisters(registers, inst.dst2, 2);
CollectSequentialRegisters(registers, inst.src[2], 2);
}
if (inst.op == IR::Opcode::SBufferLoadDword &&
inst.memory.kind == IR::ResourceKind::ScalarBuffer) {
CollectRegister(registers, {IR::RegisterFile::Scalar, inst.memory.resource * 4u});
}
if ((inst.op == IR::Opcode::ImageSample || inst.op == IR::Opcode::ImageGather4) &&
inst.memory.image_address_components > 1u) {
CollectSequentialRegisters(registers, inst.dst, inst.memory.data_dwords);
CollectSequentialRegisters(registers, inst.src[0],
inst.memory.image_address_components);
}
if (inst.op == IR::Opcode::ImageGetLod) {
CollectSequentialRegisters(registers, inst.dst, inst.memory.data_dwords);
CollectSequentialRegisters(registers, inst.src[0],
inst.memory.image_address_components);
}
if (inst.op == IR::Opcode::ImageLoad) {
CollectSequentialRegisters(registers, inst.dst, inst.memory.data_dwords);
CollectSequentialRegisters(registers, inst.src[0],
inst.memory.image_address_components);
}
if (inst.op == IR::Opcode::ImageStore) {
CollectSequentialRegisters(registers, inst.src[0], inst.memory.data_dwords);
CollectSequentialRegisters(registers, inst.src[1],
inst.memory.image_address_components);
}
if (inst.memory.kind == IR::ResourceKind::StorageImageUint) {
CollectSequentialRegisters(registers, inst.src[1],
inst.memory.image_address_components);
}
}
}
CollectMoveRelSourceRegisters(program, registers);
}
bool HasOutput(const std::vector<OutputBinding>& outputs, IR::StageOutputKind kind,
uint32_t index) {
return std::any_of(outputs.begin(), outputs.end(), [kind, index](const OutputBinding& binding) {
@@ -276,9 +89,12 @@ uint32_t OutputVariableForExport(const EmitterState& state, const IR::ExportInfo
}
bool ProgramNeedsComputeDerivatives(const IR::Program& program) {
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (inst.op == IR::Opcode::ImageGetLod) {
if (program.values == nullptr) {
return false;
}
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() == IR::ValueOpcode::ImageQueryLod) {
return true;
}
}
@@ -287,9 +103,12 @@ bool ProgramNeedsComputeDerivatives(const IR::Program& program) {
}
bool ProgramNeedsImageGatherExtended(const IR::Program& program) {
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (inst.op == IR::Opcode::ImageGather4) {
if (program.values == nullptr) {
return false;
}
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() == IR::ValueOpcode::ImageGatherRaw) {
return true;
}
}
@@ -297,34 +116,43 @@ bool ProgramNeedsImageGatherExtended(const IR::Program& program) {
return false;
}
bool IsLdsOpcode(IR::Opcode op) {
switch (op) {
case IR::Opcode::DsReadUbyte:
case IR::Opcode::DsReadSbyte:
case IR::Opcode::DsReadUshort:
case IR::Opcode::DsReadSshort:
case IR::Opcode::DsReadB32:
case IR::Opcode::DsWriteByte:
case IR::Opcode::DsWriteShort:
case IR::Opcode::DsWriteB32:
case IR::Opcode::DsMinF32:
case IR::Opcode::DsMaxF32:
case IR::Opcode::DsSwizzleB32:
case IR::Opcode::DsWriteAddtidB32:
case IR::Opcode::DsReadAddtidB32: return true;
default: return false;
}
}
bool ProgramNeedsFunctionLds(const IR::Program& program) {
if (program.stage == ShaderType::Compute) {
if (program.stage == ShaderType::Compute || program.values == nullptr) {
return false;
}
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (IsLdsOpcode(inst.op)) {
return true;
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
switch (inst.GetOpcode()) {
case IR::ValueOpcode::SwizzleU32: return true;
case IR::ValueOpcode::LoadSharedU8:
case IR::ValueOpcode::LoadSharedU16:
case IR::ValueOpcode::LoadSharedU32:
case IR::ValueOpcode::WriteSharedU8:
case IR::ValueOpcode::WriteSharedU16:
case IR::ValueOpcode::WriteSharedU32:
case IR::ValueOpcode::SharedAtomicFMin32:
case IR::ValueOpcode::SharedAtomicFMax32:
case IR::ValueOpcode::SharedAtomicSwap32:
case IR::ValueOpcode::SharedAtomicIAdd32:
case IR::ValueOpcode::SharedAtomicISub32:
case IR::ValueOpcode::SharedAtomicSMin32:
case IR::ValueOpcode::SharedAtomicUMin32:
case IR::ValueOpcode::SharedAtomicSMax32:
case IR::ValueOpcode::SharedAtomicUMax32:
case IR::ValueOpcode::SharedAtomicAnd32:
case IR::ValueOpcode::SharedAtomicOr32:
case IR::ValueOpcode::SharedAtomicXor32:
case IR::ValueOpcode::DataAppend:
case IR::ValueOpcode::DataConsume: {
const auto index = inst.Flags<IR::MemoryFlags>().index;
if (index < program.values->memory_info.size() &&
program.values->memory_info[index].kind == IR::ResourceKind::Lds) {
return true;
}
break;
}
default: break;
}
}
}
@@ -332,13 +160,18 @@ bool ProgramNeedsFunctionLds(const IR::Program& program) {
}
bool ProgramNeedsPixelValidMask(const IR::Program& program) {
if (program.stage != ShaderType::Pixel) {
if (program.stage != ShaderType::Pixel || program.values == nullptr) {
return false;
}
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (inst.op == IR::Opcode::Export && inst.export_info.vm) {
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() != IR::ValueOpcode::SetAttribute) {
continue;
}
const auto index = inst.Flags<IR::ExportFlags>().index;
if (index < program.values->export_info.size() &&
program.values->export_info[index].vm) {
return true;
}
}
@@ -346,20 +179,14 @@ bool ProgramNeedsPixelValidMask(const IR::Program& program) {
return false;
}
bool InstructionHasDppSource(const IR::Instruction& inst) {
for (uint32_t i = 0; i < inst.src_count && i < 3u; i++) {
if (inst.src[i].dpp) {
return true;
}
}
return false;
}
bool ProgramNeedsSubgroupBallot(const IR::Program& program) {
if (program.values == nullptr) {
return false;
}
if (program.lane_mask_mode == ShaderLaneMaskMode::PerInvocation) {
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (inst.op == IR::Opcode::WqmB64) {
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() == IR::ValueOpcode::WqmMask) {
return true;
}
}
@@ -369,13 +196,22 @@ bool ProgramNeedsSubgroupBallot(const IR::Program& program) {
}
bool ProgramNeedsSubgroupShuffle(const IR::Program& program) {
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (inst.op == IR::Opcode::ReadFirstLaneU32 || inst.op == IR::Opcode::ReadLaneU32 ||
inst.op == IR::Opcode::Permlane16B32 || inst.op == IR::Opcode::Permlanex16B32 ||
inst.op == IR::Opcode::DsSwizzleB32 || inst.op == IR::Opcode::DsConsume ||
inst.op == IR::Opcode::DsAppend || InstructionHasDppSource(inst)) {
return true;
if (program.values == nullptr) {
return false;
}
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
switch (inst.GetOpcode()) {
case IR::ValueOpcode::DppMoveU32:
case IR::ValueOpcode::ReadFirstLane:
case IR::ValueOpcode::ReadLane:
case IR::ValueOpcode::Permlane16U32:
case IR::ValueOpcode::DataAppend:
case IR::ValueOpcode::DataConsume:
case IR::ValueOpcode::GdsDataAppend:
case IR::ValueOpcode::GdsDataConsume:
case IR::ValueOpcode::SwizzleU32: return true;
default: break;
}
}
}
@@ -383,25 +219,24 @@ bool ProgramNeedsSubgroupShuffle(const IR::Program& program) {
}
bool ProgramNeedsSubgroupLocalInvocationId(const IR::Program& program) {
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (inst.op == IR::Opcode::WqmB64 || inst.op == IR::Opcode::DsSwizzleB32 ||
inst.op == IR::Opcode::DsConsume || inst.op == IR::Opcode::DsAppend ||
inst.op == IR::Opcode::WriteLaneU32 || inst.op == IR::Opcode::Permlane16B32 ||
inst.op == IR::Opcode::Permlanex16B32 || inst.op == IR::Opcode::Export ||
InstructionHasDppSource(inst)) {
return true;
}
if (IR::IsCompareOpcode(inst.op)) {
return true;
}
if (inst.dst.kind == IR::OperandKind::Register &&
inst.dst.reg.file == IR::RegisterFile::Vector) {
return true;
}
if (inst.dst2.kind == IR::OperandKind::Register &&
inst.dst2.reg.file == IR::RegisterFile::Vector) {
return true;
if (program.values == nullptr) {
return false;
}
for (const auto* block: program.values->blocks) {
for (const auto& inst: *block) {
switch (inst.GetOpcode()) {
case IR::ValueOpcode::DppMoveU32:
case IR::ValueOpcode::DppUpdateU32:
case IR::ValueOpcode::WqmMask:
case IR::ValueOpcode::LaneId:
case IR::ValueOpcode::WriteLane:
case IR::ValueOpcode::Permlane16U32:
case IR::ValueOpcode::DataAppend:
case IR::ValueOpcode::DataConsume:
case IR::ValueOpcode::GdsDataAppend:
case IR::ValueOpcode::GdsDataConsume:
case IR::ValueOpcode::SwizzleU32: return true;
default: break;
}
}
}
@@ -418,9 +253,6 @@ uint32_t PointerForRegister(const EmitterState& state, IR::Register reg) {
}
uint32_t ConstantU32(EmitterState& state, uint32_t value);
void EmitStoreU32(EmitterState& state, const IR::Operand& dst, uint32_t value);
uint32_t EmitSubgroupLocalInvocationId(EmitterState& state);
uint32_t EmitLaneIndexActiveBool(EmitterState& state, uint32_t lane);
[[noreturn]] void ExitDescriptorBindingFailure(const EmitterState& state,
IR::DescriptorBindingKind kind, uint32_t resource,
const char* reason) {
@@ -584,6 +416,13 @@ uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32
return sampled_image;
}
uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc,
ImageViewKind view, uint32_t image_resource) {
auto selected = mem;
selected.resource = image_resource;
return MakeSampledImage(state, selected, use_pc, view);
}
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource, bool uint_image,
uint32_t use_pc, ImageViewKind view) {
(void)use_pc;
@@ -608,6 +447,48 @@ uint32_t LoadStorageImageDescriptor(EmitterState& state, uint32_t resource, bool
return image;
}
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, bool uint_image,
ImageViewKind view, uint32_t mip_lod, uint32_t coord, uint32_t texel) {
const auto kind = StorageBindingKind(uint_image, view);
const auto binding = ResourceForDescriptor(state, kind, resource);
const auto& image = state.program.info.images.at(resource);
const auto LoadAt = [&](uint32_t array_index) {
const auto pointer = DescriptorElementPointer(
state, StorageImagePointerType(state, uint_image, view),
StorageImageVariable(state, uint_image, view), array_index, kind, resource,
"storage image descriptor array was not emitted");
const auto descriptor = state.builder.AllocateId();
state.builder.AddFunction(
{OpLoad, StorageImageType(state, uint_image, view), descriptor, pointer});
return descriptor;
};
if (image.mip_mode != IR::ImageMipMode::DynamicStorage) {
state.builder.AddFunction({OpImageWrite, LoadAt(binding.array_index), coord, texel});
return;
}
if (image.mip_count == 0u) {
ExitDescriptorBindingFailure(state, kind, resource,
"dynamic storage image has no mip descriptors");
}
const auto merge_label = state.builder.AllocateId();
std::vector<uint32_t> labels(image.mip_count);
std::vector<uint32_t> words {OpSwitch, mip_lod, merge_label};
for (uint32_t mip = 0; mip < image.mip_count; mip++) {
labels[mip] = state.builder.AllocateId();
words.push_back(mip);
words.push_back(labels[mip]);
}
state.builder.AddFunction({OpSelectionMerge, merge_label, SelectionControlNone});
state.builder.AddFunction(words);
for (uint32_t mip = 0; mip < image.mip_count; mip++) {
state.builder.AddFunction({OpLabel, labels[mip]});
state.builder.AddFunction({OpImageWrite, LoadAt(binding.array_index + mip), coord, texel});
state.builder.AddFunction({OpBranch, merge_label});
}
state.builder.AddFunction({OpLabel, merge_label});
}
uint32_t ExecutionModelForStage(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex: return ExecutionModelVertex;
@@ -0,0 +1,52 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include <algorithm>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
bool UserDataDwordIndex(const EmitterState& state, IR::Register reg, uint32_t& dword_index) {
if (reg.file != IR::RegisterFile::Scalar) {
return false;
}
const auto& registers = state.program.bindings.user_data_registers;
const auto found = std::lower_bound(registers.begin(), registers.end(), reg.index);
if (found == registers.end() || *found != reg.index) {
return false;
}
dword_index = static_cast<uint32_t>(found - registers.begin());
return true;
}
uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) {
if (state.push_constant_variable != 0) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, state.ptr_push_constant_uint, pointer,
state.push_constant_variable, ConstantU32(state, 0),
ConstantU32(state, dword_index)});
state.builder.AddFunction({OpLoad, state.uint_type, value, pointer});
return value;
}
if (state.vsharp_storage_variable != 0) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, state.ptr_storage_buffer_uint, pointer,
state.vsharp_storage_variable, ConstantU32(state, 0),
ConstantU32(state, dword_index)});
state.builder.AddFunction({OpLoad, state.uint_type, value, pointer});
return value;
}
return ConstantU32(state, 0);
}
uint32_t InitialRegisterValue(const EmitterState& state, IR::Register reg) {
if (reg.file != IR::RegisterFile::Exec) {
return 0;
}
if (state.per_invocation_masks) {
return reg.index == 0 ? 1u : 0u;
}
return reg.index == 0 || state.wave_size == 64u ? 0xffffffffu : 0u;
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -0,0 +1,570 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include <algorithm>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
uint32_t EmitWqmLaneU32(EmitterState& state, uint32_t src) {
uint32_t ret = ConstantU32(state, 0);
for (uint32_t i = 0; i < 8u; i++) {
const auto mask = 0x0fu << (i * 4u);
const auto masked = state.builder.AllocateId();
const auto non_zero = state.builder.AllocateId();
const auto expanded = state.builder.AllocateId();
const auto combined = state.builder.AllocateId();
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, masked, src, ConstantU32(state, mask)});
state.builder.AddFunction(
{OpINotEqual, state.bool_type, non_zero, masked, ConstantU32(state, 0)});
state.builder.AddFunction({OpSelect, state.uint_type, expanded, non_zero,
ConstantU32(state, mask), ConstantU32(state, 0)});
state.builder.AddFunction({OpBitwiseOr, state.uint_type, combined, ret, expanded});
ret = combined;
}
return ret;
}
uint32_t BoolAsU32(ValueEmitContext& ctx, uint32_t value) {
const auto result = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({OpSelect, ctx.state.uint_type, result, value,
ConstantU32(ctx.state, 1), ConstantU32(ctx.state, 0)});
return result;
}
void StoreRegisterMirror(ValueEmitContext& ctx, IR::Register reg, uint32_t value) {
if (const auto pointer = PointerForRegister(ctx.state, reg); pointer != 0) {
if (IsInactiveWave32ExecHigh(ctx.state, reg)) {
value = ConstantU32(ctx.state, 0);
}
ctx.state.builder.AddFunction({OpStore, pointer, value});
}
}
bool IsPrologue(const ValueEmitContext& ctx) {
return !ctx.program.blocks.empty() && ctx.current_block == ctx.program.blocks.front();
}
uint32_t EmitBuiltinU32(ValueEmitContext& ctx, IR::StageInputKind kind, uint32_t component) {
auto& state = ctx.state;
if (kind == IR::StageInputKind::LocalInvocationIndex) {
return EmitLocalInvocationIndex(state);
}
const auto variable = InputVariableForKind(state, kind);
if (variable == 0) {
return ConstantU32(state, 0);
}
if (kind == IR::StageInputKind::FrontFacing) {
const auto value = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({OpLoad, state.bool_type, value, variable});
state.builder.AddFunction(
{OpSelect, state.uint_type, bits, value, ConstantU32(state, 1), ConstantU32(state, 0)});
return bits;
}
if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex) {
const auto value = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({OpLoad, state.int_type, value, variable});
state.builder.AddFunction({OpBitcast, state.uint_type, bits, value});
return bits;
}
if (kind == IR::StageInputKind::FragCoord) {
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, state.ptr_input_float, pointer, variable,
ConstantU32(state, component)});
state.builder.AddFunction({OpLoad, state.float_type, value, pointer});
state.builder.AddFunction({OpBitcast, state.uint_type, bits, value});
return bits;
}
return EmitInputComponentU32(state, kind, component);
}
uint32_t EmitWqm(ValueEmitContext& ctx, uint32_t active) {
auto& state = ctx.state;
const auto ballot = state.builder.AllocateId();
state.builder.AddFunction({OpGroupNonUniformBallot, state.vec4_uint_type, ballot,
ConstantU32(state, ScopeSubgroup), active});
const auto low = state.builder.AllocateId();
const auto high = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeExtract, state.uint_type, low, ballot, 0});
state.builder.AddFunction({OpCompositeExtract, state.uint_type, high, ballot, 1});
const auto wqm_low = EmitWqmLaneU32(state, low);
const auto wqm_high = EmitWqmLaneU32(state, high);
const auto lane = EmitSubgroupLocalInvocationId(state);
const auto upper = state.builder.AllocateId();
const auto mask = state.builder.AllocateId();
const auto bit_lane = state.builder.AllocateId();
const auto bit = state.builder.AllocateId();
const auto hit = state.builder.AllocateId();
const auto result = state.builder.AllocateId();
state.builder.AddFunction(
{OpUGreaterThanEqual, state.bool_type, upper, lane, ConstantU32(state, 32)});
state.builder.AddFunction({OpSelect, state.uint_type, mask, upper, wqm_high, wqm_low});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, bit_lane, lane, ConstantU32(state, 31)});
state.builder.AddFunction(
{OpShiftLeftLogical, state.uint_type, bit, ConstantU32(state, 1), bit_lane});
state.builder.AddFunction({OpBitwiseAnd, state.uint_type, hit, mask, bit});
state.builder.AddFunction({OpINotEqual, state.bool_type, result, hit, ConstantU32(state, 0)});
return result;
}
uint32_t EmitDppWriteCondition(ValueEmitContext& ctx, const IR::DppMoveFlags& flags,
uint32_t exec) {
auto& state = ctx.state;
const auto lane = EmitSubgroupLocalInvocationId(state);
const auto bank_shift = state.builder.AllocateId();
const auto row_shift = state.builder.AllocateId();
const auto bank = state.builder.AllocateId();
const auto row = state.builder.AllocateId();
const auto bank_bit = state.builder.AllocateId();
const auto row_bit = state.builder.AllocateId();
const auto bank_hit = state.builder.AllocateId();
const auto row_hit = state.builder.AllocateId();
const auto bank_ok = state.builder.AllocateId();
const auto row_ok = state.builder.AllocateId();
const auto masks_ok = state.builder.AllocateId();
state.builder.AddFunction(
{OpShiftRightLogical, state.uint_type, bank_shift, lane, ConstantU32(state, 2)});
state.builder.AddFunction(
{OpShiftRightLogical, state.uint_type, row_shift, lane, ConstantU32(state, 4)});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, bank, bank_shift, ConstantU32(state, 3)});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, row, row_shift, ConstantU32(state, 3)});
state.builder.AddFunction(
{OpShiftLeftLogical, state.uint_type, bank_bit, ConstantU32(state, 1), bank});
state.builder.AddFunction(
{OpShiftLeftLogical, state.uint_type, row_bit, ConstantU32(state, 1), row});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, bank_hit, ConstantU32(state, flags.bank_mask), bank_bit});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, row_hit, ConstantU32(state, flags.row_mask), row_bit});
state.builder.AddFunction(
{OpINotEqual, state.bool_type, bank_ok, bank_hit, ConstantU32(state, 0)});
state.builder.AddFunction(
{OpINotEqual, state.bool_type, row_ok, row_hit, ConstantU32(state, 0)});
state.builder.AddFunction({OpLogicalAnd, state.bool_type, masks_ok, bank_ok, row_ok});
uint32_t writable = masks_ok;
if (!flags.bound_control) {
const auto target = EmitDppTargetLane(state, flags.control);
const auto bounded = state.builder.AllocateId();
state.builder.AddFunction({OpLogicalAnd, state.bool_type, bounded, writable, target.valid});
writable = bounded;
}
const auto result = state.builder.AllocateId();
state.builder.AddFunction({OpLogicalAnd, state.bool_type, result, exec, writable});
return result;
}
uint32_t EmitAttribute(ValueEmitContext& ctx, uint32_t attr, uint32_t chan) {
auto& state = ctx.state;
const auto* input = InputBindingForParameter(state, attr);
if (input == nullptr || input->variable_id == 0) {
return ConstantU32(state, 0);
}
if (state.stage == ShaderType::Vertex) {
return EmitVertexParameterComponentU32(state, *input, chan & 3u);
}
const auto vector = state.builder.AllocateId();
const auto component = state.builder.AllocateId();
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({OpLoad, state.vec4_float_type, vector, input->variable_id});
state.builder.AddFunction({OpCompositeExtract, state.float_type, component, vector, chan & 3u});
state.builder.AddFunction({OpBitcast, state.uint_type, bits, component});
return bits;
}
bool MrtUsesUint(const EmitterState& state, const IR::ExportInfo& exp) {
return state.stage == ShaderType::Pixel && exp.kind == IR::ExportTargetKind::Mrt &&
exp.index < std::size(state.input_info.pixel->target_output_mode) &&
state.input_info.pixel->target_output_mode[exp.index] == 7u;
}
uint32_t ExportRawComponent(ValueEmitContext& ctx, uint32_t vector, uint32_t component) {
const auto value = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{OpCompositeExtract, ctx.state.uint_type, value, vector, component});
return value;
}
uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo& exp,
bool uint_output) {
auto& state = ctx.state;
if (exp.compr && !uint_output) {
uint32_t f32[4] = {ConstantF32(state, 0), ConstantF32(state, 0), ConstantF32(state, 0),
ConstantF32(state, 0x3f800000u)};
for (uint32_t pair = 0; pair < 2u; pair++) {
if ((exp.en & (3u << (pair * 2u))) == 0u) {
continue;
}
const auto packed = state.builder.AllocateId();
const auto unpacked = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeExtract, state.uint_type, packed, data, pair});
state.builder.AddFunction({OpExtInst, state.vec2_float_type, unpacked,
state.glsl_std450, GlslUnpackHalf2x16, packed});
for (uint32_t lane = 0; lane < 2u; lane++) {
const auto component = pair * 2u + lane;
if (((exp.en >> component) & 1u) != 0u) {
f32[component] = state.builder.AllocateId();
state.builder.AddFunction(
{OpCompositeExtract, state.float_type, f32[component], unpacked, lane});
}
}
}
const auto vector = state.builder.AllocateId();
state.builder.AddFunction(
{OpCompositeConstruct, state.vec4_float_type, vector, f32[0], f32[1], f32[2], f32[3]});
return vector;
}
uint32_t raw[4] = {
ConstantU32(state, 0),
ConstantU32(state, 0),
ConstantU32(state, 0),
ConstantU32(state, uint_output ? 1u : 0x3f800000u),
};
if (exp.compr) {
for (uint32_t pair = 0; pair < 2u; pair++) {
if ((exp.en & (3u << (pair * 2u))) == 0u) {
continue;
}
const auto packed = ExportRawComponent(ctx, data, pair);
for (uint32_t lane = 0; lane < 2u; lane++) {
const auto component = pair * 2u + lane;
if (((exp.en >> component) & 1u) == 0u) {
continue;
}
raw[component] = state.builder.AllocateId();
state.builder.AddFunction({OpBitFieldUExtract, state.uint_type, raw[component],
packed, ConstantU32(state, lane * 16u),
ConstantU32(state, 16)});
}
}
} else {
for (uint32_t component = 0; component < 4u; component++) {
if (((exp.en >> component) & 1u) != 0u) {
raw[component] = ExportRawComponent(ctx, data, component);
}
}
}
if (uint_output) {
const auto vector = state.builder.AllocateId();
state.builder.AddFunction(
{OpCompositeConstruct, state.vec4_uint_type, vector, raw[0], raw[1], raw[2], raw[3]});
return vector;
}
uint32_t f32[4] {};
for (uint32_t component = 0; component < 4u; component++) {
f32[component] = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, state.float_type, f32[component], raw[component]});
}
const auto vector = state.builder.AllocateId();
state.builder.AddFunction(
{OpCompositeConstruct, state.vec4_float_type, vector, f32[0], f32[1], f32[2], f32[3]});
return vector;
}
void EmitExport(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto& exp = ctx.Export(inst);
const auto exec = ctx.Arg(inst, 1);
if (state.stage == ShaderType::Pixel && exp.vm && state.needs_pixel_valid_mask &&
state.pixel_valid_mask_variable != 0) {
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
{OpSelect, state.uint_type, value, exec, ConstantU32(state, 1), ConstantU32(state, 0)});
state.builder.AddFunction({OpStore, state.pixel_valid_mask_variable, value});
}
if (exp.kind == IR::ExportTargetKind::Null || exp.kind == IR::ExportTargetKind::Primitive ||
exp.en == 0u) {
return;
}
EmitIfCondition(state, exec, [&]() {
const auto data = ctx.Arg(inst, 0);
if (exp.kind == IR::ExportTargetKind::MrtZ) {
if ((exp.en & 1u) != 0u && state.depth_variable != 0) {
const auto raw = ExportRawComponent(ctx, data, 0);
const auto f32 = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, state.float_type, f32, raw});
state.builder.AddFunction({OpStore, state.depth_variable, f32});
}
if ((exp.en & 4u) != 0u && state.sample_mask_variable != 0) {
const auto raw = ExportRawComponent(ctx, data, 2);
const auto value = state.builder.AllocateId();
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, state.int_type, value, raw});
state.builder.AddFunction({OpAccessChain, state.ptr_output_int, pointer,
state.sample_mask_variable, ConstantU32(state, 0)});
state.builder.AddFunction({OpStore, pointer, value});
}
return;
}
const auto variable = OutputVariableForExport(state, exp);
if (variable == 0) {
return;
}
const bool uint_output = MrtUsesUint(state, exp);
const auto vector_type = uint_output ? state.vec4_uint_type : state.vec4_float_type;
auto value = ExportVector(ctx, data, exp, uint_output);
if (state.stage == ShaderType::Pixel && exp.kind == IR::ExportTargetKind::Mrt &&
exp.index < state.input_info.pixel->target_export_mapping.size()) {
const auto mapping = state.input_info.pixel->target_export_mapping[exp.index];
if (!mapping.IsIdentity()) {
const auto mapped = state.builder.AllocateId();
state.builder.AddFunction({OpVectorShuffle, vector_type, mapped, value, value,
mapping.Map(0), mapping.Map(1), mapping.Map(2),
mapping.Map(3)});
value = mapped;
}
}
if (exp.kind == IR::ExportTargetKind::Position) {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, state.ptr_output_vec4_float, pointer,
variable, ConstantU32(state, 0)});
state.builder.AddFunction({OpStore, pointer, value});
} else {
state.builder.AddFunction({OpStore, variable, value});
}
});
}
} // namespace
bool EmitValueFlow(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
switch (inst.GetOpcode()) {
case IR::ValueOpcode::Identity:
case IR::ValueOpcode::ConditionRef: ctx.Define(inst, ctx.Arg(inst, 0)); return true;
case IR::ValueOpcode::SetScalarRegister:
StoreRegisterMirror(
ctx, {IR::RegisterFile::Scalar, IR::RegIndex(inst.Arg(0).ScalarRegister())},
ctx.Arg(inst, 1));
return true;
case IR::ValueOpcode::SetThreadBitScalarRegister:
StoreRegisterMirror(
ctx, {IR::RegisterFile::Scalar, IR::RegIndex(inst.Arg(0).ScalarRegister())},
BoolAsU32(ctx, ctx.Arg(inst, 1)));
return true;
case IR::ValueOpcode::SetVectorRegister:
StoreRegisterMirror(
ctx, {IR::RegisterFile::Vector, IR::RegIndex(inst.Arg(0).VectorRegister())},
ctx.Arg(inst, 1));
return true;
case IR::ValueOpcode::SetScc: ctx.Arg(inst, 0); return true;
case IR::ValueOpcode::SetExec: ctx.Arg(inst, 0); return true;
case IR::ValueOpcode::SetExecLo:
case IR::ValueOpcode::SetExecHi:
if (!IsPrologue(ctx)) {
StoreRegisterMirror(ctx,
{IR::RegisterFile::Exec,
inst.GetOpcode() == IR::ValueOpcode::SetExecLo ? 0u : 1u},
ctx.Arg(inst, 0));
}
return true;
case IR::ValueOpcode::SetVcc: ctx.Arg(inst, 0); return true;
case IR::ValueOpcode::SetVccLo:
case IR::ValueOpcode::SetVccHi:
if (!IsPrologue(ctx)) {
StoreRegisterMirror(ctx,
{IR::RegisterFile::Vcc,
inst.GetOpcode() == IR::ValueOpcode::SetVccLo ? 0u : 1u},
ctx.Arg(inst, 0));
}
return true;
case IR::ValueOpcode::SetM0:
StoreRegisterMirror(ctx, {IR::RegisterFile::M0, 0}, ctx.Arg(inst, 0));
return true;
case IR::ValueOpcode::GetScalarRegister:
case IR::ValueOpcode::GetThreadBitScalarRegister:
case IR::ValueOpcode::GetVectorRegister:
case IR::ValueOpcode::GetScc:
case IR::ValueOpcode::GetExec:
case IR::ValueOpcode::GetExecLo:
case IR::ValueOpcode::GetExecHi:
case IR::ValueOpcode::GetVcc:
case IR::ValueOpcode::GetVccLo:
case IR::ValueOpcode::GetVccHi:
case IR::ValueOpcode::GetM0: return true;
case IR::ValueOpcode::Void:
case IR::ValueOpcode::Reference:
case IR::ValueOpcode::ReferenceU32:
case IR::ValueOpcode::Prologue:
case IR::ValueOpcode::Epilogue:
case IR::ValueOpcode::ControlNop:
case IR::ValueOpcode::Waitcnt:
case IR::ValueOpcode::Sendmsg:
case IR::ValueOpcode::TtraceData:
case IR::ValueOpcode::InstPrefetch: return true;
case IR::ValueOpcode::Barrier: {
const auto semantics = MemorySemanticsAcquireRelease | MemorySemanticsWorkgroupMemory;
state.builder.AddFunction({OpControlBarrier, ConstantU32(state, ScopeWorkgroup),
ConstantU32(state, ScopeWorkgroup),
ConstantU32(state, semantics)});
return true;
}
case IR::ValueOpcode::GetUserData: {
const auto reg = inst.Arg(0).ScalarRegister();
uint32_t dword = 0;
if (!UserDataDwordIndex(state, {IR::RegisterFile::Scalar, IR::RegIndex(reg)}, dword)) {
ctx.Define(inst, ConstantU32(state, 0));
} else {
ctx.Define(inst, EmitShaderDataDwordLoad(state, dword));
}
return true;
}
case IR::ValueOpcode::GetBuiltin:
ctx.Define(inst, EmitBuiltinU32(ctx, static_cast<IR::StageInputKind>(inst.Arg(0).U32()),
inst.Arg(1).U32()));
return true;
case IR::ValueOpcode::UndefU1:
case IR::ValueOpcode::UndefU8:
case IR::ValueOpcode::UndefU16:
case IR::ValueOpcode::UndefU32:
case IR::ValueOpcode::UndefU64:
state.builder.AddFunction({OpUndef, ctx.TypeId(inst.GetType()), ctx.Result(inst)});
return true;
case IR::ValueOpcode::DppMoveU32: {
const auto flags = inst.Flags<IR::DppMoveFlags>();
const auto target = EmitDppTargetLane(state, flags.control);
const auto shuffled = state.builder.AllocateId();
state.builder.AddFunction({OpGroupNonUniformShuffle, state.uint_type, shuffled,
ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 0),
target.lane});
if (flags.fetch_inactive) {
ctx.Define(inst, shuffled);
return true;
}
const auto ballot = state.builder.AllocateId();
state.builder.AddFunction({OpGroupNonUniformBallot, state.vec4_uint_type, ballot,
ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 1)});
const auto source_active = EmitBallotLaneActiveBool(state, ballot, target.lane);
const auto can_fetch = state.builder.AllocateId();
state.builder.AddFunction(
{OpLogicalAnd, state.bool_type, can_fetch, target.valid, source_active});
ctx.Emit(inst, OpSelect, IR::Type::U32, {can_fetch, shuffled, ConstantU32(state, 0)});
return true;
}
case IR::ValueOpcode::DppUpdateU32: {
const auto flags = inst.Flags<IR::DppMoveFlags>();
const auto write = EmitDppWriteCondition(ctx, flags, ctx.Arg(inst, 2));
ctx.Emit(inst, OpSelect, IR::Type::U32, {write, ctx.Arg(inst, 0), ctx.Arg(inst, 1)});
return true;
}
case IR::ValueOpcode::WqmMask:
ctx.Define(inst, EmitWqm(ctx, ctx.Arg(inst, 0)));
return true;
case IR::ValueOpcode::LaneId:
ctx.Define(inst, EmitSubgroupLocalInvocationId(state));
return true;
case IR::ValueOpcode::Ballot:
if (state.per_invocation_masks) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeConstruct, state.vec4_uint_type, result,
BoolAsU32(ctx, ctx.Arg(inst, 0)), ConstantU32(state, 0),
ConstantU32(state, 0), ConstantU32(state, 0)});
ctx.Define(inst, result);
} else {
ctx.Emit(inst, OpGroupNonUniformBallot, IR::Type::U32x4,
{ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 0)});
}
return true;
case IR::ValueOpcode::ReadFirstLane: {
const auto ballot = state.builder.AllocateId();
const auto lane = state.builder.AllocateId();
state.builder.AddFunction({OpGroupNonUniformBallot, state.vec4_uint_type, ballot,
ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 1)});
state.builder.AddFunction({OpGroupNonUniformBallotFindLSB, state.uint_type, lane,
ConstantU32(state, ScopeSubgroup), ballot});
ctx.Emit(inst, OpGroupNonUniformShuffle, IR::Type::U32,
{ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 0), lane});
return true;
}
case IR::ValueOpcode::ReadLane:
ctx.Emit(inst, OpGroupNonUniformShuffle, IR::Type::U32,
{ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 0), ctx.Arg(inst, 1)});
return true;
case IR::ValueOpcode::WriteLane: {
const auto hit = state.builder.AllocateId();
state.builder.AddFunction({OpIEqual, state.bool_type, hit,
EmitSubgroupLocalInvocationId(state), ctx.Arg(inst, 2)});
ctx.Emit(inst, OpSelect, IR::Type::U32, {hit, ctx.Arg(inst, 1), ctx.Arg(inst, 0)});
return true;
}
case IR::ValueOpcode::Permlane16U32: {
const auto flags = inst.Flags<IR::PermlaneFlags>();
const auto subid = EmitSubgroupLocalInvocationId(state);
const auto row = state.builder.AllocateId();
const auto row_value = state.builder.AllocateId();
const auto lane = state.builder.AllocateId();
const auto lane8 = state.builder.AllocateId();
const auto shift = state.builder.AllocateId();
const auto upper = state.builder.AllocateId();
const auto selected = state.builder.AllocateId();
const auto shifted = state.builder.AllocateId();
const auto index = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, row, subid, ConstantU32(state, 0xfffffff0u)});
if (flags.x16) {
state.builder.AddFunction(
{OpBitwiseXor, state.uint_type, row_value, row, ConstantU32(state, 16)});
} else {
state.builder.AddFunction({OpCopyObject, state.uint_type, row_value, row});
}
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, lane, subid, ConstantU32(state, 15)});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, lane8, lane, ConstantU32(state, 7)});
state.builder.AddFunction(
{OpShiftLeftLogical, state.uint_type, shift, lane8, ConstantU32(state, 2)});
state.builder.AddFunction(
{OpUGreaterThanEqual, state.bool_type, upper, lane, ConstantU32(state, 8)});
state.builder.AddFunction(
{OpSelect, state.uint_type, selected, upper, ctx.Arg(inst, 2), ctx.Arg(inst, 1)});
state.builder.AddFunction(
{OpShiftRightLogical, state.uint_type, shifted, selected, shift});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, index, shifted, ConstantU32(state, 15)});
state.builder.AddFunction({OpBitwiseOr, state.uint_type, target, row_value, index});
const auto shuffled = state.builder.AllocateId();
state.builder.AddFunction({OpGroupNonUniformShuffle, state.uint_type, shuffled,
ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 0),
target});
uint32_t result = shuffled;
if (!flags.fetch_inactive) {
const auto source_exec = state.builder.AllocateId();
state.builder.AddFunction({OpGroupNonUniformShuffle, state.bool_type, source_exec,
ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 3),
target});
result = state.builder.AllocateId();
state.builder.AddFunction({OpSelect, state.uint_type, result, source_exec, shuffled,
ConstantU32(state, 0)});
}
ctx.Define(inst, result);
return true;
}
case IR::ValueOpcode::GetAttribute:
ctx.Define(inst, EmitAttribute(ctx, inst.Arg(0).U32(), inst.Arg(1).U32()));
return true;
case IR::ValueOpcode::SetAttribute: EmitExport(ctx, inst); return true;
case IR::ValueOpcode::GetShaderBase:
// Guest S_GETPC values stay shader-relative in SPIR-V, matching the runtime ABI. The
// runtime descriptor evaluator supplies the mapped shader base for host-side planning.
ctx.Define(inst, ctx.Def(IR::Value(uint64_t {0})));
return true;
case IR::ValueOpcode::GetSrtResource:
case IR::ValueOpcode::GetBufferResource:
case IR::ValueOpcode::GetAddressResource:
case IR::ValueOpcode::GetImageResource:
case IR::ValueOpcode::GetSamplerResource:
case IR::ValueOpcode::GetLdsResource:
case IR::ValueOpcode::GetGdsResource:
case IR::ValueOpcode::MakeImageAddress: return true;
default: return false;
}
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -0,0 +1,247 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
uint32_t EmitTrueBool(EmitterState& state) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{OpINotEqual, state.bool_type, ret, ConstantU32(state, 1), ConstantU32(state, 0)});
return ret;
}
DppTargetLane EmitDppQuadPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control) {
const auto quad_base = state.builder.AllocateId();
const auto lane = state.builder.AllocateId();
const auto shift = state.builder.AllocateId();
const auto selected0 = state.builder.AllocateId();
const auto selected = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, quad_base, subid, ConstantU32(state, 0xfffffffcu)});
state.builder.AddFunction({OpBitwiseAnd, state.uint_type, lane, subid, ConstantU32(state, 3)});
state.builder.AddFunction(
{OpShiftLeftLogical, state.uint_type, shift, lane, ConstantU32(state, 1)});
state.builder.AddFunction(
{OpShiftRightLogical, state.uint_type, selected0, ConstantU32(state, control), shift});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, selected, selected0, ConstantU32(state, 3)});
state.builder.AddFunction({OpBitwiseOr, state.uint_type, target, quad_base, selected});
return {target, EmitTrueBool(state)};
}
DppTargetLane EmitDppRowShiftTargetLane(EmitterState& state, uint32_t subid, uint32_t amount,
bool left) {
const auto row = state.builder.AllocateId();
const auto lane = state.builder.AllocateId();
const auto lane_shifted = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
const auto valid = state.builder.AllocateId();
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, row, subid, ConstantU32(state, 0xfffffff0u)});
state.builder.AddFunction({OpBitwiseAnd, state.uint_type, lane, subid, ConstantU32(state, 15)});
if (left) {
state.builder.AddFunction(
{OpIAdd, state.uint_type, lane_shifted, lane, ConstantU32(state, amount)});
state.builder.AddFunction(
{OpULessThan, state.bool_type, valid, lane, ConstantU32(state, 16u - amount)});
} else {
state.builder.AddFunction(
{OpISub, state.uint_type, lane_shifted, lane, ConstantU32(state, amount)});
state.builder.AddFunction(
{OpUGreaterThanEqual, state.bool_type, valid, lane, ConstantU32(state, amount)});
}
state.builder.AddFunction({OpBitwiseOr, state.uint_type, target, row, lane_shifted});
return {target, valid};
}
DppTargetLane EmitDppRowRotateRightTargetLane(EmitterState& state, uint32_t subid,
uint32_t amount) {
const auto row = state.builder.AllocateId();
const auto lane = state.builder.AllocateId();
const auto in_high = state.builder.AllocateId();
const auto minus = state.builder.AllocateId();
const auto plus = state.builder.AllocateId();
const auto selected = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, row, subid, ConstantU32(state, 0xfffffff0u)});
state.builder.AddFunction({OpBitwiseAnd, state.uint_type, lane, subid, ConstantU32(state, 15)});
state.builder.AddFunction(
{OpUGreaterThanEqual, state.bool_type, in_high, lane, ConstantU32(state, amount)});
state.builder.AddFunction({OpISub, state.uint_type, minus, lane, ConstantU32(state, amount)});
state.builder.AddFunction(
{OpIAdd, state.uint_type, plus, lane, ConstantU32(state, 16u - amount)});
state.builder.AddFunction({OpSelect, state.uint_type, selected, in_high, minus, plus});
state.builder.AddFunction({OpBitwiseOr, state.uint_type, target, row, selected});
return {target, EmitTrueBool(state)};
}
DppTargetLane EmitDppMirrorTargetLane(EmitterState& state, uint32_t subid, bool half_row) {
const auto base_mask = half_row ? 0xfffffff8u : 0xfffffff0u;
const auto lane_mask = half_row ? 7u : 15u;
const auto base = state.builder.AllocateId();
const auto lane = state.builder.AllocateId();
const auto mirrored = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, base, subid, ConstantU32(state, base_mask)});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, lane, subid, ConstantU32(state, lane_mask)});
state.builder.AddFunction(
{OpISub, state.uint_type, mirrored, ConstantU32(state, lane_mask), lane});
state.builder.AddFunction({OpBitwiseOr, state.uint_type, target, base, mirrored});
return {target, EmitTrueBool(state)};
}
DppTargetLane EmitDppTargetLane(EmitterState& state, uint32_t control) {
const auto subid = EmitSubgroupLocalInvocationId(state);
if (control <= 0xffu) {
return EmitDppQuadPermTargetLane(state, subid, control);
}
if (control >= 0x101u && control <= 0x10fu) {
return EmitDppRowShiftTargetLane(state, subid, control & 0xfu, true);
}
if (control >= 0x111u && control <= 0x11fu) {
return EmitDppRowShiftTargetLane(state, subid, control & 0xfu, false);
}
if (control >= 0x121u && control <= 0x12fu) {
return EmitDppRowRotateRightTargetLane(state, subid, control & 0xfu);
}
if (control == 0x140u) {
return EmitDppMirrorTargetLane(state, subid, false);
}
if (control == 0x141u) {
return EmitDppMirrorTargetLane(state, subid, true);
}
return {subid, EmitTrueBool(state)};
}
uint32_t EmitSubgroupLocalInvocationId(EmitterState& state) {
if (state.subgroup_local_invocation_id_variable == 0) {
return ConstantU32(state, 0);
}
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
{OpLoad, state.uint_type, value, state.subgroup_local_invocation_id_variable});
return value;
}
uint32_t InputVariableForKind(const EmitterState& state, IR::StageInputKind kind) {
for (const auto& input: state.inputs) {
if (input.kind == kind) {
return input.variable_id;
}
}
return 0;
}
const InputBinding* InputBindingForParameter(const EmitterState& state, uint32_t location) {
for (const auto& input: state.inputs) {
if (input.kind == IR::StageInputKind::Parameter && input.location == location) {
return &input;
}
}
return nullptr;
}
uint32_t EmitInputComponentU32(EmitterState& state, IR::StageInputKind kind, uint32_t component) {
const auto variable = InputVariableForKind(state, kind);
if (variable == 0) {
return ConstantU32(state, 0);
}
const auto pointer = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
{OpAccessChain, state.ptr_input_uint, pointer, variable, ConstantU32(state, component)});
state.builder.AddFunction({OpLoad, state.uint_type, value, pointer});
return value;
}
uint32_t EmitLocalInvocationIndex(EmitterState& state) {
const auto variable = InputVariableForKind(state, IR::StageInputKind::LocalInvocationIndex);
if (variable == 0) {
return ConstantU32(state, 0);
}
const auto value = state.builder.AllocateId();
state.builder.AddFunction({OpLoad, state.uint_type, value, variable});
return value;
}
uint32_t VertexInputDefaultComponentU32(EmitterState& state, VertexInputScalarKind kind,
uint32_t component) {
if ((component & 3u) != 3u) {
return ConstantU32(state, 0);
}
return ConstantU32(state, kind == VertexInputScalarKind::Float ? 0x3f800000u : 1u);
}
uint32_t EmitVertexParameterComponentU32(EmitterState& state, const InputBinding& input,
uint32_t component) {
const auto count = VertexParameterComponentCount(state, input);
const auto kind = VertexParameterScalarKind(state, input.location);
if (component >= count) {
return VertexInputDefaultComponentU32(state, kind, component);
}
const auto scalar_type = VertexParameterScalarType(state, kind);
uint32_t raw = state.builder.AllocateId();
if (count == 1u) {
state.builder.AddFunction({OpLoad, scalar_type, raw, input.variable_id});
} else {
const auto pointer_type = VertexParameterScalarPointerType(state, kind);
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, pointer_type, pointer, input.variable_id,
ConstantU32(state, component)});
state.builder.AddFunction({OpLoad, scalar_type, raw, pointer});
}
if (kind == VertexInputScalarKind::Uint) {
return raw;
}
const auto bits = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, state.uint_type, bits, raw});
return bits;
}
uint32_t EmitSubgroupLaneActiveBool(EmitterState& state, uint32_t lane) {
const auto active_ballot = state.builder.AllocateId();
state.builder.AddFunction({OpGroupNonUniformBallot, state.vec4_uint_type, active_ballot,
ConstantU32(state, ScopeSubgroup), EmitTrueBool(state)});
return EmitBallotLaneActiveBool(state, active_ballot, lane);
}
uint32_t EmitBallotLaneActiveBool(EmitterState& state, uint32_t active_ballot, uint32_t lane) {
const auto low = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeExtract, state.uint_type, low, active_ballot, 0});
uint32_t mask = low;
if (state.wave_size == 64u) {
const auto high = state.builder.AllocateId();
const auto in_high = state.builder.AllocateId();
const auto selected = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeExtract, state.uint_type, high, active_ballot, 1});
state.builder.AddFunction(
{OpUGreaterThanEqual, state.bool_type, in_high, lane, ConstantU32(state, 32)});
state.builder.AddFunction({OpSelect, state.uint_type, selected, in_high, high, low});
mask = selected;
}
const auto lane_low = state.builder.AllocateId();
const auto bit = state.builder.AllocateId();
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, lane_low, lane, ConstantU32(state, 31)});
state.builder.AddFunction(
{OpShiftLeftLogical, state.uint_type, bit, ConstantU32(state, 1), lane_low});
const auto hit = state.builder.AllocateId();
const auto active = state.builder.AllocateId();
const auto in_range = state.builder.AllocateId();
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpBitwiseAnd, state.uint_type, hit, mask, bit});
state.builder.AddFunction({OpINotEqual, state.bool_type, active, hit, ConstantU32(state, 0)});
state.builder.AddFunction(
{OpULessThan, state.bool_type, in_range, lane, ConstantU32(state, state.wave_size)});
state.builder.AddFunction({OpLogicalAnd, state.bool_type, ret, active, in_range});
return ret;
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -0,0 +1,636 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include <algorithm>
#include <bit>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
uint32_t DmaskComponentIndex(uint32_t dmask, uint32_t component) {
uint32_t index = 0;
for (uint32_t i = 0; i < component; i++) {
index += (dmask >> i) & 1u;
}
return index;
}
uint32_t ImageGatherComponent(uint32_t dmask) {
switch (dmask) {
case 0x2u: return 1;
case 0x4u: return 2;
case 0x8u: return 3;
default: return 0;
}
}
uint32_t Unary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t value) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, value});
return result;
}
uint32_t Binary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t lhs, uint32_t rhs) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, lhs, rhs});
return result;
}
bool HasFlag(const IR::MemoryInfo& mem, uint32_t flag) {
return (mem.image_sample_flags & flag) != 0u;
}
bool UsesA16(const IR::MemoryInfo& mem) {
return HasFlag(mem, Decoder::ImageSampleFlagA16);
}
bool ComponentUsesA16(const IR::MemoryInfo& mem, uint32_t component) {
if (!UsesA16(mem)) return false;
uint32_t cursor = 0;
if (HasFlag(mem, Decoder::ImageSampleFlagOffset)) {
if (component == cursor) return false;
cursor++;
}
if (HasFlag(mem, Decoder::ImageSampleFlagBias)) {
if (component == cursor) return true;
cursor++;
}
if (HasFlag(mem, Decoder::ImageSampleFlagCompare) && component == cursor) return false;
return true;
}
uint32_t HalfComponent(const IR::MemoryInfo& mem, uint32_t component) {
if (!UsesA16(mem)) return component * 2u;
uint32_t half = 0;
for (uint32_t index = 0; index < component; index++) {
const auto width = ComponentUsesA16(mem, index) ? 1u : 2u;
if (width == 2u && (half & 1u) != 0u) half++;
half += width;
}
if (!ComponentUsesA16(mem, component) && (half & 1u) != 0u) half++;
return half;
}
uint32_t AddressU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
uint32_t component) {
const auto half = HalfComponent(mem, component);
const auto packed = half / 2u;
if (packed >= address.NumArgs()) return ConstantU32(ctx.state, 0);
auto value = ctx.Def(address.Arg(packed));
if (ComponentUsesA16(mem, component)) {
if ((half & 1u) != 0u) {
value = Binary(ctx.state, OpShiftRightLogical, ctx.state.uint_type, value,
ConstantU32(ctx.state, 16));
}
value = Binary(ctx.state, OpBitwiseAnd, ctx.state.uint_type, value,
ConstantU32(ctx.state, 0xffffu));
}
return value;
}
uint32_t AddressF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
uint32_t component) {
const auto value = AddressU32(ctx, mem, address, component);
return ComponentUsesA16(mem, component)
? EmitF16BitsToF32(ctx.state, value)
: Unary(ctx.state, OpBitcast, ctx.state.float_type, value);
}
ImageSampleLayout Layout(const IR::MemoryInfo& mem, ImageViewKind view) {
ImageSampleLayout layout;
uint32_t cursor = 0;
if (HasFlag(mem, Decoder::ImageSampleFlagOffset)) layout.offset = cursor++;
if (HasFlag(mem, Decoder::ImageSampleFlagBias)) layout.bias = cursor++;
if (HasFlag(mem, Decoder::ImageSampleFlagCompare)) layout.dref = cursor++;
if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) {
layout.grad_x = cursor;
cursor += ImageViewSpatialComponents(view);
layout.grad_y = cursor;
cursor += ImageViewSpatialComponents(view);
}
layout.coord = cursor;
cursor += ImageViewCoordinateComponents(view);
if (HasFlag(mem, Decoder::ImageSampleFlagLod)) layout.lod = cursor++;
return layout;
}
uint32_t ZeroF32(EmitterState& state) {
return ConstantF32(state, 0);
}
uint32_t ZeroU32x4(EmitterState& state) {
const auto zero = ConstantU32(state, 0);
const auto value = state.builder.AllocateId();
state.builder.AddType(
{OpConstantComposite, state.vec4_uint_type, value, zero, zero, zero, zero});
return value;
}
uint32_t CubeAxis(EmitterState& state, uint32_t value) {
return Binary(state, OpFSub, state.float_type, value, ConstantF32(state, 0x3f800000u));
}
uint32_t CubeLayer(EmitterState& state, uint32_t value) {
const auto guest = state.builder.AllocateId();
state.builder.AddFunction({OpConvertFToU, state.uint_type, guest, value});
const auto padding =
Binary(state, OpShiftLeftLogical, state.uint_type,
Binary(state, OpShiftRightLogical, state.uint_type, guest, ConstantU32(state, 3)),
ConstantU32(state, 1));
const auto host = Binary(state, OpISub, state.uint_type, guest, padding);
const auto result = state.builder.AllocateId();
state.builder.AddFunction({OpConvertUToF, state.float_type, result, host});
return result;
}
uint32_t CoordF32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
uint32_t first, uint32_t components) {
auto x = AddressF32(ctx, mem, address, first);
if (components == 1u) return x;
auto y = mem.image_address_components > first + 1u ? AddressF32(ctx, mem, address, first + 1u)
: ZeroF32(ctx.state);
if (mem.image_cube) {
x = CubeAxis(ctx.state, x);
y = CubeAxis(ctx.state, y);
}
const auto result = ctx.state.builder.AllocateId();
if (components == 3u) {
auto z = mem.image_address_components > first + 2u
? AddressF32(ctx, mem, address, first + 2u)
: ZeroF32(ctx.state);
if (mem.image_cube) z = CubeLayer(ctx.state, z);
ctx.state.builder.AddFunction(
{OpCompositeConstruct, ctx.state.vec3_float_type, result, x, y, z});
} else {
ctx.state.builder.AddFunction(
{OpCompositeConstruct, ctx.state.vec2_float_type, result, x, y});
}
return result;
}
uint32_t CoordU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
ImageViewKind view) {
const auto components = ImageViewCoordinateComponents(view);
const auto x = AddressU32(ctx, mem, address, 0);
if (components == 1u) return x;
const auto y = mem.image_address_components > 1u ? AddressU32(ctx, mem, address, 1)
: ConstantU32(ctx.state, 0);
const auto result = ctx.state.builder.AllocateId();
if (components == 3u) {
const auto z = mem.image_address_components > 2u ? AddressU32(ctx, mem, address, 2)
: ConstantU32(ctx.state, 0);
ctx.state.builder.AddFunction(
{OpCompositeConstruct, ctx.state.vec3_uint_type, result, x, y, z});
} else {
ctx.state.builder.AddFunction(
{OpCompositeConstruct, ctx.state.vec2_uint_type, result, x, y});
}
return result;
}
uint32_t LodU32(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
ImageViewKind view) {
const auto component = ImageViewCoordinateComponents(view);
return mem.image_has_mip && mem.image_address_components > component
? AddressU32(ctx, mem, address, component)
: ConstantU32(ctx.state, 0);
}
uint32_t FloatBits(ValueEmitContext& ctx, uint32_t value) {
return Unary(ctx.state, OpBitcast, ctx.state.uint_type, value);
}
uint32_t ResultVector(ValueEmitContext& ctx, uint32_t value, bool integer, bool dref) {
uint32_t component[4] {};
for (uint32_t index = 0; index < 4u; index++) {
if (dref) {
component[index] = index == 0u ? FloatBits(ctx, value) : ConstantU32(ctx.state, 0);
continue;
}
const auto scalar = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({OpCompositeExtract,
integer ? ctx.state.uint_type : ctx.state.float_type, scalar,
value, index});
component[index] = integer ? scalar : FloatBits(ctx, scalar);
}
const auto result = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({OpCompositeConstruct, ctx.state.vec4_uint_type, result,
component[0], component[1], component[2], component[3]});
return result;
}
uint32_t QueryDimensions(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
const IR::Inst& address) {
const auto pc = inst.Flags<IR::MemoryFlags>().pc;
const auto view = SampledImageViewKind(ctx.state, mem, pc);
const auto image = LoadSampledImageDescriptor(ctx.state, mem, pc, view);
const auto size = ctx.state.builder.AllocateId();
if (ImageSpirvMultisampled(view) != 0u) {
ctx.state.builder.AddFunction(
{OpImageQuerySize, ImageViewSizeType(ctx.state, view), size, image});
} else {
ctx.state.builder.AddFunction({OpImageQuerySizeLod, ImageViewSizeType(ctx.state, view),
size, image, AddressU32(ctx, mem, address, 0)});
}
const auto components = ImageViewCoordinateComponents(view);
uint32_t result[4] = {ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0),
ConstantU32(ctx.state, 0), ConstantU32(ctx.state, 0)};
if (components == 1u) {
result[0] = size;
} else {
for (uint32_t index = 0; index < components; index++) {
result[index] = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{OpCompositeExtract, ctx.state.uint_type, result[index], size, index});
}
}
if (ImageSpirvMultisampled(view) == 0u) {
result[3] = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({OpImageQueryLevels, ctx.state.uint_type, result[3], image});
}
const auto vector = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({OpCompositeConstruct, ctx.state.vec4_uint_type, vector,
result[0], result[1], result[2], result[3]});
return vector;
}
uint32_t PackedOffset(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const IR::Inst& address,
const ImageSampleLayout& layout, ImageViewKind view) {
const auto components = ImageViewSpatialComponents(view);
const auto zero = ConstantI32(ctx.state, 0);
if (layout.offset == NoImageComponent || mem.image_address_components <= layout.offset) {
if (components == 1u) return zero;
const auto result = ctx.state.builder.AllocateId();
if (components == 3u) {
ctx.state.builder.AddFunction(
{OpCompositeConstruct, ctx.state.vec3_int_type, result, zero, zero, zero});
} else {
ctx.state.builder.AddFunction(
{OpCompositeConstruct, ctx.state.vec2_int_type, result, zero, zero});
}
return result;
}
const auto packed = Unary(ctx.state, OpBitcast, ctx.state.int_type,
AddressU32(ctx, mem, address, layout.offset));
uint32_t values[3] = {zero, zero, zero};
for (uint32_t index = 0; index < components; index++) {
values[index] = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({OpBitFieldSExtract, ctx.state.int_type, values[index],
packed, ConstantU32(ctx.state, index * 8u),
ConstantU32(ctx.state, 6)});
}
if (components == 1u) return values[0];
const auto result = ctx.state.builder.AllocateId();
if (components == 3u) {
ctx.state.builder.AddFunction({OpCompositeConstruct, ctx.state.vec3_int_type, result,
values[0], values[1], values[2]});
} else {
ctx.state.builder.AddFunction(
{OpCompositeConstruct, ctx.state.vec2_int_type, result, values[0], values[1]});
}
return result;
}
uint32_t HorizontalOffsets(EmitterState& state, ImageViewKind view) {
const auto components = ImageViewSpatialComponents(view);
const auto array_type = state.builder.AllocateId();
const auto count = ConstantU32(state, 4);
const auto element_type = components == 1u ? state.int_type : state.vec2_int_type;
state.builder.AddType({OpTypeArray, array_type, element_type, count});
uint32_t offsets[4] {};
for (uint32_t index = 0; index < 4u; index++) {
const auto x = ConstantI32(state, static_cast<int32_t>(index) - 1);
if (components == 1u) {
offsets[index] = x;
} else {
offsets[index] = state.builder.AllocateId();
state.builder.AddType({OpConstantComposite, state.vec2_int_type, offsets[index], x,
ConstantI32(state, 0)});
}
}
const auto value = state.builder.AllocateId();
state.builder.AddType(
{OpConstantComposite, array_type, value, offsets[0], offsets[1], offsets[2], offsets[3]});
return value;
}
uint32_t InverseSwizzle(uint32_t swizzle, uint32_t component) {
for (uint32_t source = 0; source < 4u; source++) {
if (((swizzle >> (source * 3u)) & 7u) == 4u + component) return source;
}
return UINT32_MAX;
}
uint32_t StoreTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t data, bool integer) {
const auto swizzle = ctx.state.program.info.images[mem.resource].storage_swizzle;
uint32_t values[4] {};
const auto dmask = mem.dmask != 0u ? mem.dmask : 1u;
for (uint32_t component = 0; component < 4u; component++) {
const auto source = InverseSwizzle(swizzle, component);
uint32_t raw = ConstantU32(ctx.state, 0);
if (source < 4u && ((dmask >> source) & 1u) != 0u) {
const auto packed_index = DmaskComponentIndex(dmask, source);
raw = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{OpCompositeExtract, ctx.state.uint_type, raw, data, packed_index});
}
values[component] = integer ? raw : Unary(ctx.state, OpBitcast, ctx.state.float_type, raw);
}
const auto texel = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({OpCompositeConstruct,
integer ? ctx.state.vec4_uint_type : ctx.state.vec4_float_type,
texel, values[0], values[1], values[2], values[3]});
return texel;
}
uint32_t ImageAtomicOpcode(IR::ValueOpcode opcode) {
switch (opcode) {
case IR::ValueOpcode::ImageAtomicIAdd32: return OpAtomicIAdd;
case IR::ValueOpcode::ImageAtomicUMin32: return OpAtomicUMin;
case IR::ValueOpcode::ImageAtomicUMax32: return OpAtomicUMax;
case IR::ValueOpcode::ImageAtomicAnd32: return OpAtomicAnd;
case IR::ValueOpcode::ImageAtomicOr32: return OpAtomicOr;
case IR::ValueOpcode::ImageAtomicXor32: return OpAtomicXor;
default: return 0;
}
}
} // namespace
bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto op = inst.GetOpcode();
if (op < IR::ValueOpcode::ImageQueryDimensions || op > IR::ValueOpcode::ImageAtomicXor32) {
return false;
}
auto& state = ctx.state;
const auto& mem = ctx.Memory(inst);
const auto pc = inst.Flags<IR::MemoryFlags>().pc;
const auto image_arg = inst.Arg(0);
ctx.ResourceIndex(image_arg, IR::ValueOpcode::GetImageResource);
const bool sampled = op == IR::ValueOpcode::ImageQueryLod ||
op == IR::ValueOpcode::ImageSampleRaw ||
op == IR::ValueOpcode::ImageGatherRaw;
const auto* address = ctx.ImageAddress(inst.Arg(sampled ? 2 : 1));
if (address == nullptr) return true;
if (op == IR::ValueOpcode::ImageQueryDimensions) {
ctx.Define(inst, QueryDimensions(ctx, inst, mem, *address));
return true;
}
if (op == IR::ValueOpcode::ImageQueryLod) {
const auto view = SampledImageViewKind(state, mem, pc);
const auto sampled = MakeSampledImage(state, mem, pc, view);
const auto lod = state.builder.AllocateId();
state.builder.AddFunction(
{OpImageQueryLod, state.vec2_float_type, lod, sampled,
CoordF32(ctx, mem, *address, 0, ImageViewSpatialComponents(view))});
uint32_t values[4] = {ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0),
ConstantU32(state, 0)};
for (uint32_t index = 0; index < 2u; index++) {
const auto component = state.builder.AllocateId();
state.builder.AddFunction(
{OpCompositeExtract, state.float_type, component, lod, index});
values[index] = FloatBits(ctx, component);
}
const auto result = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeConstruct, state.vec4_uint_type, result, values[0],
values[1], values[2], values[3]});
ctx.Define(inst, result);
return true;
}
if (op == IR::ValueOpcode::ImageRead) {
const auto view = SampledImageViewKind(state, mem, pc);
const bool integer = mem.kind == IR::ResourceKind::ImageUint;
const auto condition = ctx.Arg(inst, 2);
ctx.Define(
inst,
EmitValueOrDefaultIfCondition(
state, condition, state.vec4_uint_type, ZeroU32x4(state), [&]() {
const auto image = LoadSampledImageDescriptor(state, mem, pc, view);
const auto color = state.builder.AllocateId();
const auto coord = CoordU32(ctx, mem, *address, view);
if (ImageSpirvMultisampled(view) != 0u) {
state.builder.AddFunction(
{OpImageFetch, integer ? state.vec4_uint_type : state.vec4_float_type,
color, image, coord, ImageOperandsSampleMask,
AddressU32(ctx, mem, *address, ImageViewCoordinateComponents(view))});
} else {
state.builder.AddFunction(
{OpImageFetch, integer ? state.vec4_uint_type : state.vec4_float_type,
color, image, coord, ImageOperandsLodMask,
LodU32(ctx, mem, *address, view)});
}
return ResultVector(ctx, color, integer, false);
}));
return true;
}
if (op == IR::ValueOpcode::ImageWrite) {
const auto uint_image = mem.kind == IR::ResourceKind::StorageImageUint;
const auto view = StorageImageViewKind(state, mem, uint_image, pc);
EmitIfCondition(state, ctx.Arg(inst, 3), [&]() {
const auto mip_lod =
state.program.info.images[mem.resource].mip_mode == IR::ImageMipMode::DynamicStorage
? LodU32(ctx, mem, *address, view)
: 0u;
const auto coord = CoordU32(ctx, mem, *address, view);
const auto texel = StoreTexel(ctx, mem, ctx.Arg(inst, 2), uint_image);
EmitStorageImageWrite(state, mem.resource, uint_image, view, mip_lod, coord, texel);
});
return true;
}
if (op == IR::ValueOpcode::ImageSampleRaw || op == IR::ValueOpcode::ImageGatherRaw) {
const auto view = SampledImageViewKind(state, mem, pc);
const auto layout = Layout(mem, view);
const bool integer = mem.kind == IR::ResourceKind::ImageUint;
const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare);
const auto coord =
CoordF32(ctx, mem, *address, layout.coord, ImageViewCoordinateComponents(view));
if (op == IR::ValueOpcode::ImageGatherRaw) {
const auto sampled = MakeSampledImage(state, mem, pc, view);
const auto sample = state.builder.AllocateId();
std::vector<uint32_t> words =
dref ? std::vector<uint32_t> {OpImageDrefGather,
state.vec4_float_type,
sample,
sampled,
coord,
layout.dref != NoImageComponent
? AddressF32(ctx, mem, *address, layout.dref)
: ZeroF32(state)}
: std::vector<uint32_t> {
OpImageGather, integer ? state.vec4_uint_type : state.vec4_float_type,
sample, sampled,
coord, ConstantU32(state, ImageGatherComponent(mem.dmask))};
if (HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) {
words.push_back(ImageOperandsConstOffsetsMask);
words.push_back(HorizontalOffsets(state, view));
} else if (layout.offset != NoImageComponent) {
words.push_back(ImageOperandsOffsetMask);
words.push_back(PackedOffset(ctx, mem, *address, layout, view));
}
state.builder.AddFunction(words);
ctx.Define(inst, ResultVector(ctx, sample, integer && !dref, false));
return true;
}
const bool explicit_lod = HasFlag(mem, Decoder::ImageSampleFlagDerivative) ||
HasFlag(mem, Decoder::ImageSampleFlagLod) ||
HasFlag(mem, Decoder::ImageSampleFlagLevelZero) ||
state.stage != ShaderType::Pixel;
const auto opcode = explicit_lod
? (dref ? OpImageSampleDrefExplicitLod : OpImageSampleExplicitLod)
: (dref ? OpImageSampleDrefImplicitLod : OpImageSampleImplicitLod);
const auto result_type =
dref ? state.float_type : (integer ? state.vec4_uint_type : state.vec4_float_type);
const auto dref_value =
dref ? (layout.dref != NoImageComponent ? AddressF32(ctx, mem, *address, layout.dref)
: ZeroF32(state))
: 0u;
uint32_t operand_mask = 0;
std::vector<uint32_t> operands;
if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) {
operand_mask |= ImageOperandsGradMask;
operands.push_back(
CoordF32(ctx, mem, *address, layout.grad_x, ImageViewSpatialComponents(view)));
operands.push_back(
CoordF32(ctx, mem, *address, layout.grad_y, ImageViewSpatialComponents(view)));
} else if (explicit_lod) {
operand_mask |= ImageOperandsLodMask;
operands.push_back(HasFlag(mem, Decoder::ImageSampleFlagLod) &&
layout.lod != NoImageComponent
? AddressF32(ctx, mem, *address, layout.lod)
: ZeroF32(state));
} else if (layout.bias != NoImageComponent) {
operand_mask |= ImageOperandsBiasMask;
operands.push_back(AddressF32(ctx, mem, *address, layout.bias));
}
const auto EmitSample = [&](uint32_t resource) {
const auto sampled = MakeSampledImage(state, mem, pc, view, resource);
const auto sample = state.builder.AllocateId();
std::vector<uint32_t> words {opcode, result_type, sample, sampled, coord};
if (dref) {
words.push_back(dref_value);
}
if (operand_mask != 0u) {
words.push_back(operand_mask);
words.insert(words.end(), operands.begin(), operands.end());
}
state.builder.AddFunction(words);
return sample;
};
const auto& image = state.program.info.images[mem.resource];
if (image.indirect_root != mem.resource) {
ctx.Define(inst, ResultVector(ctx, EmitSample(mem.resource), integer, dref));
return true;
}
const auto* handle = image_arg.ResolveInstruction();
const auto* source = image.source < ctx.program.descriptor_sources.size()
? &ctx.program.descriptor_sources[image.source]
: nullptr;
if (handle == nullptr || source == nullptr || !source->indirect_image.has_value() ||
source->indirect_image->key_arg >= handle->NumArgs()) {
ctx.Fail(inst, "has invalid indirect image key provenance");
return true;
}
const auto key = ctx.Def(handle->Arg(source->indirect_image->key_arg));
if (state.flattened_srt_variable == 0 || image.indirect_mapping_capacity == 0u ||
image.indirect_resources.size() < 2u) {
ctx.Fail(inst, "has no indirect image runtime mapping");
return true;
}
const auto LoadMapping = [&](uint32_t index) {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, state.ptr_storage_buffer_uint, pointer,
state.flattened_srt_variable, ConstantU32(state, 0), index});
const auto value = state.builder.AllocateId();
state.builder.AddFunction({OpLoad, state.uint_type, value, pointer});
return value;
};
const auto mapping = ConstantU32(state, image.indirect_mapping_offset);
auto low = ConstantU32(state, 0u);
auto high = LoadMapping(mapping);
auto selected = ConstantU32(state, 0u);
for (uint32_t iteration = 0; iteration < std::bit_width(image.indirect_mapping_capacity);
iteration++) {
const auto active = Binary(state, OpULessThan, state.bool_type, low, high);
const auto mid =
Binary(state, OpShiftRightLogical, state.uint_type,
Binary(state, OpIAdd, state.uint_type, low, high), ConstantU32(state, 1u));
const auto probe = state.builder.AllocateId();
state.builder.AddFunction(
{OpSelect, state.uint_type, probe, active, mid, ConstantU32(state, 0u)});
const auto entry = Binary(state, OpIAdd, state.uint_type, mapping,
Binary(state, OpIAdd, state.uint_type,
Binary(state, OpShiftLeftLogical, state.uint_type,
probe, ConstantU32(state, 1u)),
ConstantU32(state, 1u)));
const auto mapped_key = LoadMapping(entry);
const auto candidate =
LoadMapping(Binary(state, OpIAdd, state.uint_type, entry, ConstantU32(state, 1u)));
const auto equal = Binary(state, OpIEqual, state.bool_type, mapped_key, key);
const auto match = Binary(state, OpLogicalAnd, state.bool_type, active, equal);
const auto next_selected = state.builder.AllocateId();
state.builder.AddFunction(
{OpSelect, state.uint_type, next_selected, match, candidate, selected});
selected = next_selected;
const auto less = Binary(state, OpULessThan, state.bool_type, mapped_key, key);
const auto take_upper = Binary(state, OpLogicalAnd, state.bool_type, active, less);
const auto take_lower = Binary(state, OpLogicalAnd, state.bool_type, active,
Unary(state, OpLogicalNot, state.bool_type, less));
const auto next_low = state.builder.AllocateId();
state.builder.AddFunction(
{OpSelect, state.uint_type, next_low, take_upper,
Binary(state, OpIAdd, state.uint_type, mid, ConstantU32(state, 1u)), low});
low = next_low;
const auto next_high = state.builder.AllocateId();
state.builder.AddFunction(
{OpSelect, state.uint_type, next_high, take_lower, mid, high});
high = next_high;
}
const auto default_label = state.builder.AllocateId();
const auto merge_label = state.builder.AllocateId();
std::vector<uint32_t> labels(image.indirect_resources.size() - 1u);
std::vector<uint32_t> switch_words {OpSwitch, selected, default_label};
for (uint32_t candidate = 1; candidate < image.indirect_resources.size(); candidate++) {
labels[candidate - 1u] = state.builder.AllocateId();
switch_words.push_back(candidate);
switch_words.push_back(labels[candidate - 1u]);
}
state.builder.AddFunction({OpSelectionMerge, merge_label, SelectionControlNone});
state.builder.AddFunction(switch_words);
std::vector<uint32_t> phi_words {OpPhi, result_type, state.builder.AllocateId()};
state.builder.AddFunction({OpLabel, default_label});
phi_words.push_back(EmitSample(image.indirect_resources[0]));
phi_words.push_back(default_label);
state.builder.AddFunction({OpBranch, merge_label});
for (uint32_t candidate = 1; candidate < image.indirect_resources.size(); candidate++) {
state.builder.AddFunction({OpLabel, labels[candidate - 1u]});
phi_words.push_back(EmitSample(image.indirect_resources[candidate]));
phi_words.push_back(labels[candidate - 1u]);
state.builder.AddFunction({OpBranch, merge_label});
}
state.builder.AddFunction({OpLabel, merge_label});
state.builder.AddFunction(phi_words);
ctx.Define(inst, ResultVector(ctx, phi_words[2], integer, dref));
return true;
}
const auto atomic_opcode = ImageAtomicOpcode(op);
if (atomic_opcode != 0u) {
const auto view = StorageImageViewKind(state, mem, true, pc);
ctx.Define(inst, EmitValueOrZeroIfCondition(state, ctx.Arg(inst, 3), [&]() {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(
{OpImageTexelPointer, state.ptr_image_uint, pointer,
StorageImageDescriptorPointer(state, mem.resource, true, pc, view),
CoordU32(ctx, mem, *address, view), ConstantU32(state, 0)});
const auto old = state.builder.AllocateId();
state.builder.AddFunction({atomic_opcode, state.uint_type, old, pointer,
ConstantU32(state, ScopeDevice),
ConstantU32(state, MemorySemanticsNone),
ctx.Arg(inst, 2)});
EmitDeviceAtomicMemoryBarrier(state);
return old;
}));
return true;
}
return false;
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -0,0 +1,729 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include <algorithm>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
uint32_t Unary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t value) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, value});
return result;
}
uint32_t Binary(EmitterState& state, uint32_t opcode, uint32_t type, uint32_t lhs, uint32_t rhs) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({opcode, type, result, lhs, rhs});
return result;
}
uint32_t Select(EmitterState& state, uint32_t type, uint32_t condition, uint32_t true_value,
uint32_t false_value) {
const auto result = state.builder.AllocateId();
state.builder.AddFunction({OpSelect, type, result, condition, true_value, false_value});
return result;
}
uint32_t AndCondition(EmitterState& state, uint32_t lhs, uint32_t rhs) {
if (lhs == 0) return rhs;
if (rhs == 0) return lhs;
return Binary(state, OpLogicalAnd, state.bool_type, lhs, rhs);
}
uint32_t BufferByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
uint32_t index, uint32_t offset, uint32_t soffset) {
auto& state = ctx.state;
const auto packed =
ConstantU32(state, StorageBufferPackedStride(state, mem, inst.Flags<IR::MemoryFlags>().pc));
if (mem.formatted && !mem.typed && inst.GetOpcode() == IR::ValueOpcode::StoreBufferU32) {
const auto add_tid_bit = Binary(
state, OpBitwiseAnd, state.uint_type,
Binary(state, OpShiftRightLogical, state.uint_type, packed, ConstantU32(state, 20)),
ConstantU32(state, 1));
const auto add_tid =
Binary(state, OpINotEqual, state.bool_type, add_tid_bit, ConstantU32(state, 0));
const auto lane = Binary(state, OpBitwiseAnd, state.uint_type,
EmitLocalInvocationIndex(state), ConstantU32(state, 63));
index = Select(state, state.uint_type, add_tid,
Binary(state, OpIAdd, state.uint_type, index, lane), index);
}
const auto stride =
Binary(state, OpBitwiseAnd, state.uint_type, packed, ConstantU32(state, 0x3fffu));
const auto swizzle =
Binary(state, OpBitwiseAnd, state.uint_type,
Binary(state, OpShiftRightLogical, state.uint_type, packed, ConstantU32(state, 14)),
ConstantU32(state, 1));
const auto use_swizzle =
Binary(state, OpLogicalAnd, state.bool_type,
Binary(state, OpINotEqual, state.bool_type, stride, ConstantU32(state, 0)),
Binary(state, OpINotEqual, state.bool_type, swizzle, ConstantU32(state, 0)));
const auto stride_enum =
Binary(state, OpBitwiseAnd, state.uint_type,
Binary(state, OpShiftRightLogical, state.uint_type, packed, ConstantU32(state, 16)),
ConstantU32(state, 3));
const auto index_shift =
Binary(state, OpIAdd, state.uint_type, stride_enum, ConstantU32(state, 3));
const auto index_stride =
Binary(state, OpShiftLeftLogical, state.uint_type, ConstantU32(state, 8), stride_enum);
const auto index_mask =
Binary(state, OpISub, state.uint_type, index_stride, ConstantU32(state, 1));
offset = Binary(state, OpIAdd, state.uint_type, offset, ConstantU32(state, mem.offset));
const auto linear = Binary(state, OpIAdd, state.uint_type,
Binary(state, OpIMul, state.uint_type, index, stride), offset);
const auto index_msb = Binary(state, OpShiftRightLogical, state.uint_type, index, index_shift);
const auto index_lsb = Binary(state, OpBitwiseAnd, state.uint_type, index, index_mask);
const auto offset_msb =
Binary(state, OpBitwiseAnd, state.uint_type, offset, ConstantU32(state, ~3u));
const auto offset_lsb =
Binary(state, OpBitwiseAnd, state.uint_type, offset, ConstantU32(state, 3));
const auto msb =
Binary(state, OpIMul, state.uint_type,
Binary(state, OpIAdd, state.uint_type,
Binary(state, OpIMul, state.uint_type, index_msb, stride), offset_msb),
index_stride);
const auto lsb =
Binary(state, OpIAdd, state.uint_type,
Binary(state, OpShiftLeftLogical, state.uint_type, index_lsb, ConstantU32(state, 2)),
offset_lsb);
const auto address = Select(state, state.uint_type, use_swizzle,
Binary(state, OpIAdd, state.uint_type, msb, lsb), linear);
return Binary(state, OpIAdd, state.uint_type, address, soffset);
}
uint32_t AddU64Low(EmitterState& state, uint32_t low, uint32_t high, uint32_t add_low,
uint32_t add_high, uint32_t& out_high) {
const auto result = Binary(state, OpIAdd, state.uint_type, low, add_low);
const auto carry = Binary(state, OpULessThan, state.bool_type, result, low);
out_high = Binary(
state, OpIAdd, state.uint_type, Binary(state, OpIAdd, state.uint_type, high, add_high),
Select(state, state.uint_type, carry, ConstantU32(state, 1), ConstantU32(state, 0)));
return result;
}
uint32_t AddressByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
uint32_t low, uint32_t high) {
auto& state = ctx.state;
auto immediate = static_cast<int32_t>(mem.offset);
if (mem.kind == IR::ResourceKind::ScalarAddress) {
immediate = static_cast<int32_t>(static_cast<uint32_t>(immediate) & ~3u);
low = Binary(state, OpBitwiseAnd, state.uint_type, low, ConstantU32(state, ~3u));
}
const auto immediate_low = ConstantU32(state, static_cast<uint32_t>(immediate));
const auto immediate_high = ConstantU32(state, immediate < 0 ? UINT32_MAX : 0u);
const auto base = state.program.info.addresses[mem.resource].specialized_base;
if (mem.kind == IR::ResourceKind::Scratch) {
low = AddU64Low(state, low, high, immediate_low, immediate_high, high);
const auto valid = Binary(state, OpIEqual, state.bool_type, high, ConstantU32(state, 0));
return Select(state, state.uint_type, valid, low, ConstantU32(state, UINT32_MAX));
}
if (mem.kind == IR::ResourceKind::Flat ||
(mem.kind == IR::ResourceKind::Global &&
state.program.info.addresses[mem.resource].unbased)) {
low = AddU64Low(state, low, high, immediate_low, immediate_high, high);
const auto base_low = ConstantU32(state, static_cast<uint32_t>(base));
const auto base_high = ConstantU32(state, static_cast<uint32_t>(base >> 32u));
const auto relative = Binary(state, OpISub, state.uint_type, low, base_low);
const auto borrow = Binary(state, OpULessThan, state.bool_type, low, base_low);
const auto relative_high = Binary(
state, OpISub, state.uint_type, Binary(state, OpISub, state.uint_type, high, base_high),
Select(state, state.uint_type, borrow, ConstantU32(state, 1), ConstantU32(state, 0)));
const auto valid =
Binary(state, OpIEqual, state.bool_type, relative_high, ConstantU32(state, 0));
return Select(state, state.uint_type, valid, relative, ConstantU32(state, UINT32_MAX));
}
const auto initial = base + static_cast<uint64_t>(static_cast<int64_t>(immediate));
auto relative_high = ConstantU32(state, static_cast<uint32_t>(initial >> 32u));
const auto relative = AddU64Low(state, ConstantU32(state, static_cast<uint32_t>(initial)),
relative_high, low, ConstantU32(state, 0), relative_high);
const auto valid =
Binary(state, OpIEqual, state.bool_type, relative_high, ConstantU32(state, 0));
return Select(state, state.uint_type, valid, relative, ConstantU32(state, UINT32_MAX));
}
uint32_t ByteAddress(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
if (mem.kind == IR::ResourceKind::Buffer) {
return BufferByteAddress(ctx, inst, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2),
ctx.Arg(inst, 3));
}
if (mem.kind == IR::ResourceKind::Lds || mem.kind == IR::ResourceKind::Gds) {
return ctx.Arg(inst, 1);
}
return AddressByteAddress(ctx, inst, mem, ctx.Arg(inst, 1), ctx.Arg(inst, 2));
}
uint32_t DwordIndex(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
return Binary(ctx.state, OpShiftRightLogical, ctx.state.uint_type, ByteAddress(ctx, inst, mem),
ConstantU32(ctx.state, 2));
}
uint32_t InBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t index, uint32_t pc) {
if (mem.kind == IR::ResourceKind::Lds) {
return EmitLdsElementInBounds(ctx.state, index);
}
if (mem.kind == IR::ResourceKind::Gds) {
return EmitGdsElementInBounds(ctx.state, index);
}
return EmitStorageBufferElementInBounds(ctx.state, mem, index, pc);
}
uint32_t Pointer(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t index, uint32_t pc) {
return EmitMemoryElementPointer(ctx.state, mem, index, pc);
}
uint32_t LoadWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem,
uint32_t extra_offset = 0) {
mem.offset += extra_offset;
const auto pc = inst.Flags<IR::MemoryFlags>().pc;
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto index = DwordIndex(ctx, inst, mem);
return EmitValueOrZeroIfCondition(ctx.state, InBounds(ctx, mem, index, pc), [&]() {
const auto value = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{OpLoad, ctx.state.uint_type, value, Pointer(ctx, mem, index, pc)});
return value;
});
});
}
uint32_t LoadSubword(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem, uint32_t bits,
bool sign_extend, uint32_t extra_offset = 0) {
mem.offset += extra_offset;
const auto pc = inst.Flags<IR::MemoryFlags>().pc;
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto address = ByteAddress(ctx, inst, mem);
const auto index = Binary(ctx.state, OpShiftRightLogical, ctx.state.uint_type, address,
ConstantU32(ctx.state, 2));
return EmitValueOrZeroIfCondition(ctx.state, InBounds(ctx, mem, index, pc), [&]() {
const auto word = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{OpLoad, ctx.state.uint_type, word, Pointer(ctx, mem, index, pc)});
const auto byte = Binary(ctx.state, OpBitwiseAnd, ctx.state.uint_type, address,
ConstantU32(ctx.state, 3));
const auto shift = Binary(ctx.state, OpShiftLeftLogical, ctx.state.uint_type, byte,
ConstantU32(ctx.state, 3));
const auto value =
Binary(ctx.state, OpBitwiseAnd, ctx.state.uint_type,
Binary(ctx.state, OpShiftRightLogical, ctx.state.uint_type, word, shift),
ConstantU32(ctx.state, bits == 8u ? 0xffu : 0xffffu));
if (!sign_extend) return value;
const auto left = Binary(ctx.state, OpShiftLeftLogical, ctx.state.uint_type, value,
ConstantU32(ctx.state, 32u - bits));
return Binary(ctx.state, OpShiftRightArithmetic, ctx.state.uint_type, left,
ConstantU32(ctx.state, 32u - bits));
});
});
}
Prospero::BufferFormat BufferFormat(const ValueEmitContext& ctx, const IR::Inst& inst,
const IR::MemoryInfo& mem) {
return mem.typed ? Format::DecodeTBufferFormat(mem.data_format, mem.number_format)
: StorageBufferFormat(ctx.state, mem, inst.Flags<IR::MemoryFlags>().pc);
}
IR::MemoryInfo RebaseFormattedComponent(IR::MemoryInfo mem, Prospero::BufferFormat format,
uint32_t component) {
if (!mem.typed) {
const auto component_slot = mem.component_index * 4u;
mem.offset = mem.offset >= component_slot ? mem.offset - component_slot : 0u;
mem.offset += Format::GetFormatComponentByteOffset(format, component);
}
return mem;
}
uint32_t FormattedLoad(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
const auto format = BufferFormat(ctx, inst, mem);
if (!Format::IsKnownFormat(format)) return LoadWord(ctx, inst, mem);
const auto info = Format::GetFormatInfo(format);
auto component = mem.component_index;
if (!mem.typed) {
const auto selector =
GetDstSel(ctx.state.program.info.buffers[mem.resource].descriptor_swizzle, component);
if (selector == 0u) return ConstantU32(ctx.state, 0);
if (selector == 1u) {
const auto integer = info.type == Format::ComponentType::Uint ||
info.type == Format::ComponentType::Sint;
return ConstantU32(ctx.state, integer ? 1u : 0x3f800000u);
}
if (selector < 4u) {
ExitDescriptorBindingFailure(ctx.state, IR::DescriptorBindingKind::Buffers,
mem.resource, "buffer descriptor has reserved dst_sel");
}
component = selector - 4u;
}
if (component >= info.component_count) return ConstantU32(ctx.state, 0);
uint32_t raw = 0;
const auto bits = info.component_bits[component];
if (info.packed_bitfield) {
raw = LoadWord(ctx, inst, RebaseFormattedComponent(mem, format, component));
const auto type =
IsSignedFormatComponent(info.type) ? ctx.state.int_type : ctx.state.uint_type;
const auto source =
type == ctx.state.int_type ? Unary(ctx.state, OpBitcast, type, raw) : raw;
const auto extracted = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{IsSignedFormatComponent(info.type) ? OpBitFieldSExtract : OpBitFieldUExtract, type,
extracted, source, ConstantU32(ctx.state, info.component_bit_offset[component]),
ConstantU32(ctx.state, bits)});
raw = type == ctx.state.int_type
? Unary(ctx.state, OpBitcast, ctx.state.uint_type, extracted)
: extracted;
} else if (bits == 32u) {
raw = LoadWord(ctx, inst, RebaseFormattedComponent(mem, format, component));
} else {
raw = LoadSubword(ctx, inst, RebaseFormattedComponent(mem, format, component), bits,
IsSignedFormatComponent(info.type));
}
return NormalizeFormatComponent(ctx.state, info, component, raw);
}
template <typename Fn>
uint32_t AtomicUpdate(EmitterState& state, uint32_t pointer, IR::ResourceKind kind, Fn&& desired) {
const auto scope = kind == IR::ResourceKind::Lds ? ScopeWorkgroup : ScopeDevice;
const auto memory = kind == IR::ResourceKind::Lds ? MemorySemanticsWorkgroupMemory
: MemorySemanticsUniformMemory;
const auto preheader = state.builder.AllocateId();
const auto header = state.builder.AllocateId();
const auto cont = state.builder.AllocateId();
const auto merge = state.builder.AllocateId();
const auto initial = state.builder.AllocateId();
const auto observed = state.builder.AllocateId();
const auto exchanged = state.builder.AllocateId();
state.builder.AddFunction({OpBranch, preheader});
state.builder.AddFunction({OpLabel, preheader});
state.builder.AddFunction({OpAtomicLoad, state.uint_type, initial, pointer,
ConstantU32(state, scope), ConstantU32(state, MemorySemanticsNone)});
state.builder.AddFunction({OpBranch, header});
state.builder.AddFunction({OpLabel, header});
state.builder.AddFunction(
{OpPhi, state.uint_type, observed, initial, preheader, exchanged, cont});
const auto next = desired(observed);
state.builder.AddFunction({OpAtomicCompareExchange, state.uint_type, exchanged, pointer,
ConstantU32(state, scope), ConstantU32(state, MemorySemanticsNone),
ConstantU32(state, MemorySemanticsNone), next, observed});
const auto success = Binary(state, OpIEqual, state.bool_type, exchanged, observed);
state.builder.AddFunction({OpLoopMerge, merge, cont, LoopControlNone});
state.builder.AddFunction({OpBranchConditional, success, merge, cont});
state.builder.AddFunction({OpLabel, cont});
state.builder.AddFunction({OpBranch, header});
state.builder.AddFunction({OpLabel, merge});
state.builder.AddFunction({OpMemoryBarrier, ConstantU32(state, scope),
ConstantU32(state, MemorySemanticsAcquireRelease | memory)});
return observed;
}
void StoreSubword(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem, uint32_t bits,
uint32_t extra_offset = 0) {
mem.offset += extra_offset;
const auto pc = inst.Flags<IR::MemoryFlags>().pc;
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto address = ByteAddress(ctx, inst, mem);
const auto index = Binary(ctx.state, OpShiftRightLogical, ctx.state.uint_type, address,
ConstantU32(ctx.state, 2));
EmitIfCondition(ctx.state, InBounds(ctx, mem, index, pc), [&]() {
const auto pointer = Pointer(ctx, mem, index, pc);
const auto shift = Binary(ctx.state, OpShiftLeftLogical, ctx.state.uint_type,
Binary(ctx.state, OpBitwiseAnd, ctx.state.uint_type, address,
ConstantU32(ctx.state, 3)),
ConstantU32(ctx.state, 3));
const auto mask = Binary(ctx.state, OpShiftLeftLogical, ctx.state.uint_type,
ConstantU32(ctx.state, bits == 8u ? 0xffu : 0xffffu), shift);
const auto value = Binary(ctx.state, OpShiftLeftLogical, ctx.state.uint_type,
Binary(ctx.state, OpBitwiseAnd, ctx.state.uint_type,
ctx.Arg(inst, inst.NumArgs() - 2),
ConstantU32(ctx.state, bits == 8u ? 0xffu : 0xffffu)),
shift);
const auto merge = [&](uint32_t old) {
return Binary(ctx.state, OpBitwiseOr, ctx.state.uint_type,
Binary(ctx.state, OpBitwiseAnd, ctx.state.uint_type, old,
Unary(ctx.state, OpNot, ctx.state.uint_type, mask)),
value);
};
if (mem.kind == IR::ResourceKind::Lds || mem.kind == IR::ResourceKind::Gds) {
AtomicUpdate(ctx.state, pointer, mem.kind, merge);
} else {
const auto old = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({OpLoad, ctx.state.uint_type, old, pointer});
ctx.state.builder.AddFunction({OpStore, pointer, merge(old)});
}
});
});
}
void StoreWord(ValueEmitContext& ctx, const IR::Inst& inst, IR::MemoryInfo mem,
uint32_t extra_offset = 0) {
mem.offset += extra_offset;
const auto pc = inst.Flags<IR::MemoryFlags>().pc;
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto index = DwordIndex(ctx, inst, mem);
EmitIfCondition(ctx.state, InBounds(ctx, mem, index, pc), [&]() {
ctx.state.builder.AddFunction(
{OpStore, Pointer(ctx, mem, index, pc), ctx.Arg(inst, inst.NumArgs() - 2)});
});
});
}
void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
const auto format = BufferFormat(ctx, inst, mem);
if (!Format::IsKnownFormat(format)) {
StoreWord(ctx, inst, mem);
return;
}
const auto info = Format::GetFormatInfo(format);
if (mem.component_index >= info.component_count) return;
const auto bits = info.component_bits[mem.component_index];
const auto component_mem = RebaseFormattedComponent(mem, format, mem.component_index);
if (bits == 8u || bits == 16u) {
StoreSubword(ctx, inst, component_mem, bits);
} else {
StoreWord(ctx, inst, component_mem);
}
}
uint32_t AtomicOpcode(IR::ValueOpcode opcode) {
switch (opcode) {
case IR::ValueOpcode::BufferAtomicSwap32:
case IR::ValueOpcode::SharedAtomicSwap32:
case IR::ValueOpcode::GdsAtomicSwap32: return OpAtomicExchange;
case IR::ValueOpcode::BufferAtomicIAdd32:
case IR::ValueOpcode::SharedAtomicIAdd32:
case IR::ValueOpcode::GdsAtomicIAdd32: return OpAtomicIAdd;
case IR::ValueOpcode::BufferAtomicISub32:
case IR::ValueOpcode::SharedAtomicISub32:
case IR::ValueOpcode::GdsAtomicISub32: return OpAtomicISub;
case IR::ValueOpcode::BufferAtomicSMin32:
case IR::ValueOpcode::SharedAtomicSMin32:
case IR::ValueOpcode::GdsAtomicSMin32: return OpAtomicSMin;
case IR::ValueOpcode::BufferAtomicUMin32:
case IR::ValueOpcode::SharedAtomicUMin32:
case IR::ValueOpcode::GdsAtomicUMin32: return OpAtomicUMin;
case IR::ValueOpcode::BufferAtomicSMax32:
case IR::ValueOpcode::SharedAtomicSMax32:
case IR::ValueOpcode::GdsAtomicSMax32: return OpAtomicSMax;
case IR::ValueOpcode::BufferAtomicUMax32:
case IR::ValueOpcode::SharedAtomicUMax32:
case IR::ValueOpcode::GdsAtomicUMax32: return OpAtomicUMax;
case IR::ValueOpcode::BufferAtomicAnd32:
case IR::ValueOpcode::SharedAtomicAnd32:
case IR::ValueOpcode::GdsAtomicAnd32: return OpAtomicAnd;
case IR::ValueOpcode::BufferAtomicOr32:
case IR::ValueOpcode::SharedAtomicOr32:
case IR::ValueOpcode::GdsAtomicOr32: return OpAtomicOr;
case IR::ValueOpcode::BufferAtomicXor32:
case IR::ValueOpcode::SharedAtomicXor32:
case IR::ValueOpcode::GdsAtomicXor32: return OpAtomicXor;
default: return 0;
}
}
uint32_t EmitAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
bool return_value) {
const auto pc = inst.Flags<IR::MemoryFlags>().pc;
const auto result =
EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto index = DwordIndex(ctx, inst, mem);
return EmitValueOrZeroIfCondition(ctx.state, InBounds(ctx, mem, index, pc), [&]() {
const auto value = ctx.Arg(inst, inst.NumArgs() - 2);
const auto old = ctx.state.builder.AllocateId();
const auto scope = mem.kind == IR::ResourceKind::Lds ? ScopeWorkgroup : ScopeDevice;
ctx.state.builder.AddFunction({AtomicOpcode(inst.GetOpcode()), ctx.state.uint_type,
old, Pointer(ctx, mem, index, pc),
ConstantU32(ctx.state, scope),
ConstantU32(ctx.state, MemorySemanticsNone), value});
if (mem.kind == IR::ResourceKind::Lds) {
const auto semantics =
MemorySemanticsAcquireRelease | MemorySemanticsWorkgroupMemory;
ctx.state.builder.AddFunction({OpMemoryBarrier,
ConstantU32(ctx.state, ScopeWorkgroup),
ConstantU32(ctx.state, semantics)});
} else {
EmitDeviceAtomicMemoryBarrier(ctx.state);
}
return old;
});
});
return return_value ? result : ConstantU32(ctx.state, 0);
}
uint32_t FloatAtomicReplacement(EmitterState& state, uint32_t old, uint32_t source,
bool max_value) {
struct OrderedBits {
uint32_t nan;
uint32_t zero;
uint32_t key;
};
const auto classify = [&](uint32_t bits) {
const auto value = Unary(state, OpBitcast, state.float_type, bits);
const auto cls = EmitClassifyF32(state, value);
const auto negative = Binary(
state, OpINotEqual, state.bool_type,
Binary(state, OpBitwiseAnd, state.uint_type, bits, ConstantU32(state, 0x80000000u)),
ConstantU32(state, 0));
const auto negative_key = Unary(state, OpNot, state.uint_type, bits);
const auto positive_key =
Binary(state, OpBitwiseXor, state.uint_type, bits, ConstantU32(state, 0x80000000u));
return OrderedBits {cls.nan, cls.zero,
Select(state, state.uint_type, negative, negative_key, positive_key)};
};
const auto source_class = classify(source);
const auto old_class = classify(old);
const auto unordered =
Binary(state, OpLogicalOr, state.bool_type,
Binary(state, OpLogicalOr, state.bool_type, source_class.nan, old_class.nan),
Binary(state, OpLogicalAnd, state.bool_type, source_class.zero, old_class.zero));
const auto ordered = Unary(state, OpLogicalNot, state.bool_type, unordered);
const auto compare = Binary(state, max_value ? OpUGreaterThan : OpULessThan, state.bool_type,
source_class.key, old_class.key);
return Select(state, state.uint_type,
Binary(state, OpLogicalAnd, state.bool_type, ordered, compare), source, old);
}
uint32_t FloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
bool max_value) {
const auto pc = inst.Flags<IR::MemoryFlags>().pc;
return EmitValueOrZeroIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto index = DwordIndex(ctx, inst, mem);
return EmitValueOrZeroIfCondition(ctx.state, InBounds(ctx, mem, index, pc), [&]() {
const auto pointer = Pointer(ctx, mem, index, pc);
const auto source = ctx.Arg(inst, inst.NumArgs() - 2);
return AtomicUpdate(ctx.state, pointer, mem.kind, [&](uint32_t old) {
return FloatAtomicReplacement(ctx.state, old, source, max_value);
});
});
});
}
uint32_t SharedFloatAtomic(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem,
bool max_value) {
const auto pc = inst.Flags<IR::MemoryFlags>().pc;
EmitIfCondition(ctx.state, ctx.Arg(inst, inst.NumArgs() - 1), [&]() {
const auto index = DwordIndex(ctx, inst, mem);
EmitIfCondition(ctx.state, InBounds(ctx, mem, index, pc), [&]() {
ctx.state.builder.AddFunction({OpStore, ctx.scratch_u32_variable, ctx.Arg(inst, 2)});
const auto data = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{OpLoad, ctx.state.uint_type, data, ctx.scratch_u32_variable});
AtomicUpdate(ctx.state, Pointer(ctx, mem, index, pc), mem.kind, [&](uint32_t old) {
const auto old_f = Unary(ctx.state, OpBitcast, ctx.state.float_type, old);
const auto compare_f =
Unary(ctx.state, OpBitcast, ctx.state.float_type, ctx.Arg(inst, 3));
const auto data_f = Unary(ctx.state, OpBitcast, ctx.state.float_type, data);
const auto compare = Binary(
ctx.state, max_value ? OpFOrdGreaterThan : OpFOrdLessThan, ctx.state.bool_type,
max_value ? old_f : compare_f, max_value ? compare_f : old_f);
return Unary(ctx.state, OpBitcast, ctx.state.uint_type,
Select(ctx.state, ctx.state.float_type, compare, data_f, old_f));
});
});
});
return 0;
}
uint32_t AppendConsume(ValueEmitContext& ctx, const IR::Inst& inst, bool gds, bool append) {
auto& state = ctx.state;
const auto m0 = ctx.Arg(inst, 1);
const auto base =
Binary(state, OpShiftRightLogical, state.uint_type, m0, ConstantU32(state, 16));
const auto size = Binary(state, OpBitwiseAnd, state.uint_type, m0, ConstantU32(state, 0xffffu));
const auto address =
Binary(state, OpIAdd, state.uint_type, base, ConstantU32(state, ctx.Memory(inst).offset));
const auto index =
Binary(state, OpShiftRightLogical, state.uint_type, address, ConstantU32(state, 2));
const auto exec = ctx.Arg(inst, 2);
const auto ballot = state.builder.AllocateId();
state.builder.AddFunction({OpGroupNonUniformBallot, state.vec4_uint_type, ballot,
ConstantU32(state, ScopeSubgroup), exec});
const auto low = state.builder.AllocateId();
const auto high = state.builder.AllocateId();
state.builder.AddFunction({OpCompositeExtract, state.uint_type, low, ballot, 0});
state.builder.AddFunction({OpCompositeExtract, state.uint_type, high, ballot, 1});
const auto count_low = state.per_invocation_masks ? low : ctx.Arg(inst, 3);
const auto count_high = state.per_invocation_masks ? high : ctx.Arg(inst, 4);
const auto count =
Binary(state, OpIAdd, state.uint_type, Unary(state, OpBitCount, state.uint_type, count_low),
Unary(state, OpBitCount, state.uint_type, count_high));
const auto first = state.builder.AllocateId();
state.builder.AddFunction({OpGroupNonUniformBallotFindLSB, state.uint_type, first,
ConstantU32(state, ScopeSubgroup), ballot});
const auto is_first =
Binary(state, OpIEqual, state.bool_type, EmitSubgroupLocalInvocationId(state), first);
const auto storage_bounds =
gds ? EmitGdsElementInBounds(state, index) : EmitLdsElementInBounds(state, index);
const auto m0_bounds =
gds ? Binary(state, OpINotEqual, state.bool_type, size, ConstantU32(state, 0))
: Binary(state, OpULessThan, state.bool_type,
ConstantU32(state, ctx.Memory(inst).offset + 3u), size);
const auto condition = AndCondition(
state, is_first, AndCondition(state, exec, AndCondition(state, storage_bounds, m0_bounds)));
const auto atomic = EmitValueOrZeroIfCondition(state, condition, [&]() {
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
{append ? OpAtomicIAdd : OpAtomicISub, state.uint_type, value,
gds ? EmitGdsElementPointer(state, index) : EmitLdsElementPointer(state, index),
ConstantU32(state, gds ? ScopeDevice : ScopeWorkgroup),
ConstantU32(state, MemorySemanticsNone), count});
return value;
});
const auto result = state.builder.AllocateId();
state.builder.AddFunction({OpGroupNonUniformShuffle, state.uint_type, result,
ConstantU32(state, ScopeSubgroup), atomic, first});
return result;
}
} // namespace
bool EmitValueMemory(ValueEmitContext& ctx, const IR::Inst& inst) {
auto& state = ctx.state;
const auto op = inst.GetOpcode();
if ((op == IR::ValueOpcode::LoadAddressU32 || op == IR::ValueOpcode::ReadConstBuffer) &&
ctx.Memory(inst).planning_only) {
return true;
}
if (op == IR::ValueOpcode::ReadConst) {
if (state.flattened_srt_variable == 0) {
ctx.Fail(inst, "requires the flattened SRT descriptor");
return true;
}
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, state.ptr_storage_buffer_uint, pointer,
state.flattened_srt_variable, ConstantU32(state, 0),
ctx.Arg(inst, 1)});
ctx.Emit(inst, OpLoad, IR::Type::U32, {pointer});
return true;
}
if (op == IR::ValueOpcode::ReadConstBuffer) {
auto mem = ctx.Memory(inst);
mem.kind = IR::ResourceKind::ScalarBuffer;
const auto address = Binary(state, OpIAdd, state.uint_type, ctx.Arg(inst, 1),
ConstantU32(state, mem.offset));
const auto index =
Binary(state, OpShiftRightLogical, state.uint_type, address, ConstantU32(state, 2));
const auto pc = inst.Flags<IR::MemoryFlags>().pc;
const auto condition = EmitStorageBufferElementInBounds(state, mem, index, pc);
ctx.Define(inst, EmitValueOrZeroIfCondition(state, condition, [&]() {
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
{OpLoad, state.uint_type, value,
EmitStorageBufferElementPointer(state, mem, index, pc)});
return value;
}));
return true;
}
const bool load_address = op == IR::ValueOpcode::LoadAddressU8 ||
op == IR::ValueOpcode::LoadAddressU16 ||
op == IR::ValueOpcode::LoadAddressU32;
const bool load_buffer = op == IR::ValueOpcode::LoadBufferU8 ||
op == IR::ValueOpcode::LoadBufferU16 ||
op == IR::ValueOpcode::LoadBufferU32;
const bool load_shared =
op == IR::ValueOpcode::LoadSharedU8 || op == IR::ValueOpcode::LoadSharedU16 ||
op == IR::ValueOpcode::LoadSharedU32 || op == IR::ValueOpcode::LoadGdsU8 ||
op == IR::ValueOpcode::LoadGdsU16 || op == IR::ValueOpcode::LoadGdsU32;
if (load_address || load_buffer || load_shared) {
auto mem = ctx.Memory(inst);
if (load_shared)
mem.kind = op == IR::ValueOpcode::LoadGdsU8 || op == IR::ValueOpcode::LoadGdsU16 ||
op == IR::ValueOpcode::LoadGdsU32
? IR::ResourceKind::Gds
: IR::ResourceKind::Lds;
uint32_t value = 0;
if (op == IR::ValueOpcode::LoadBufferU32 && mem.formatted)
value = FormattedLoad(ctx, inst, mem);
else if (op == IR::ValueOpcode::LoadAddressU8 || op == IR::ValueOpcode::LoadBufferU8 ||
op == IR::ValueOpcode::LoadSharedU8 || op == IR::ValueOpcode::LoadGdsU8)
value = LoadSubword(ctx, inst, mem, 8, false);
else if (op == IR::ValueOpcode::LoadAddressU16 || op == IR::ValueOpcode::LoadBufferU16 ||
op == IR::ValueOpcode::LoadSharedU16 || op == IR::ValueOpcode::LoadGdsU16)
value = LoadSubword(ctx, inst, mem, 16, false);
else
value = LoadWord(ctx, inst, mem);
ctx.Define(inst, value);
return true;
}
const bool store_address = op == IR::ValueOpcode::StoreAddressU8 ||
op == IR::ValueOpcode::StoreAddressU16 ||
op == IR::ValueOpcode::StoreAddressU32;
const bool store_buffer = op == IR::ValueOpcode::StoreBufferU8 ||
op == IR::ValueOpcode::StoreBufferU16 ||
op == IR::ValueOpcode::StoreBufferU32;
const bool store_shared =
op == IR::ValueOpcode::WriteSharedU8 || op == IR::ValueOpcode::WriteSharedU16 ||
op == IR::ValueOpcode::WriteSharedU32 || op == IR::ValueOpcode::WriteGdsU8 ||
op == IR::ValueOpcode::WriteGdsU16 || op == IR::ValueOpcode::WriteGdsU32;
if (store_address || store_buffer || store_shared) {
auto mem = ctx.Memory(inst);
if (store_shared)
mem.kind = op == IR::ValueOpcode::WriteGdsU8 || op == IR::ValueOpcode::WriteGdsU16 ||
op == IR::ValueOpcode::WriteGdsU32
? IR::ResourceKind::Gds
: IR::ResourceKind::Lds;
if (op == IR::ValueOpcode::StoreBufferU32 && mem.formatted)
FormattedStore(ctx, inst, mem);
else if (op == IR::ValueOpcode::StoreAddressU8 || op == IR::ValueOpcode::StoreBufferU8 ||
op == IR::ValueOpcode::WriteSharedU8 || op == IR::ValueOpcode::WriteGdsU8)
StoreSubword(ctx, inst, mem, 8);
else if (op == IR::ValueOpcode::StoreAddressU16 || op == IR::ValueOpcode::StoreBufferU16 ||
op == IR::ValueOpcode::WriteSharedU16 || op == IR::ValueOpcode::WriteGdsU16)
StoreSubword(ctx, inst, mem, 16);
else
StoreWord(ctx, inst, mem);
return true;
}
const auto atomic_opcode = AtomicOpcode(op);
if (atomic_opcode != 0) {
auto mem = ctx.Memory(inst);
if (op >= IR::ValueOpcode::SharedAtomicSwap32 && op <= IR::ValueOpcode::SharedAtomicXor32)
mem.kind = IR::ResourceKind::Lds;
else if (op >= IR::ValueOpcode::GdsAtomicSwap32 && op <= IR::ValueOpcode::GdsAtomicXor32)
mem.kind = IR::ResourceKind::Gds;
ctx.Define(inst, EmitAtomic(ctx, inst, mem, true));
return true;
}
if (op == IR::ValueOpcode::BufferAtomicFMin32 || op == IR::ValueOpcode::BufferAtomicFMax32) {
ctx.Define(inst, FloatAtomic(ctx, inst, ctx.Memory(inst),
op == IR::ValueOpcode::BufferAtomicFMax32));
return true;
}
if (op == IR::ValueOpcode::SharedAtomicFMin32 || op == IR::ValueOpcode::SharedAtomicFMax32 ||
op == IR::ValueOpcode::GdsAtomicFMin32 || op == IR::ValueOpcode::GdsAtomicFMax32) {
auto mem = ctx.Memory(inst);
mem.kind = op == IR::ValueOpcode::GdsAtomicFMin32 || op == IR::ValueOpcode::GdsAtomicFMax32
? IR::ResourceKind::Gds
: IR::ResourceKind::Lds;
SharedFloatAtomic(ctx, inst, mem,
op == IR::ValueOpcode::SharedAtomicFMax32 ||
op == IR::ValueOpcode::GdsAtomicFMax32);
return true;
}
if (op == IR::ValueOpcode::DataAppend || op == IR::ValueOpcode::DataConsume ||
op == IR::ValueOpcode::GdsDataAppend || op == IR::ValueOpcode::GdsDataConsume) {
ctx.Define(inst, AppendConsume(ctx, inst,
op == IR::ValueOpcode::GdsDataAppend ||
op == IR::ValueOpcode::GdsDataConsume,
op == IR::ValueOpcode::DataAppend ||
op == IR::ValueOpcode::GdsDataAppend));
return true;
}
if (op == IR::ValueOpcode::SwizzleU32) {
ctx.state.builder.AddFunction({OpStore, ctx.scratch_u32_variable, ctx.Arg(inst, 0)});
const auto source = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction(
{OpLoad, ctx.state.uint_type, source, ctx.scratch_u32_variable});
const auto target =
EmitDsSwizzleTargetLane(state, EmitSubgroupLocalInvocationId(state),
inst.Arg(1).IsImmediate() ? inst.Arg(1).U32() : 0);
const auto shuffled = state.builder.AllocateId();
state.builder.AddFunction({OpGroupNonUniformShuffle, state.uint_type, shuffled,
ConstantU32(state, ScopeSubgroup), source, target});
const auto source_exec = state.builder.AllocateId();
state.builder.AddFunction({OpGroupNonUniformShuffle, state.bool_type, source_exec,
ConstantU32(state, ScopeSubgroup), ctx.Arg(inst, 2), target});
const auto source_active =
AndCondition(state, source_exec, EmitSubgroupLaneActiveBool(state, target));
ctx.Define(inst,
Select(state, state.uint_type, source_active, shuffled, ConstantU32(state, 0)));
return true;
}
return false;
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -0,0 +1,371 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
uint32_t EmitAddU32(EmitterState& state, uint32_t lhs, uint32_t rhs) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpIAdd, state.uint_type, ret, lhs, rhs});
return ret;
}
uint32_t EmitBinaryU32(EmitterState& state, uint32_t opcode, uint32_t lhs, uint32_t rhs) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({opcode, state.uint_type, ret, lhs, rhs});
return ret;
}
uint32_t StorageBufferPackedStride(const EmitterState& state, const IR::MemoryInfo& mem,
uint32_t use_pc) {
(void)use_pc;
if (mem.resource >= state.program.info.buffers.size()) {
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::Buffers, mem.resource,
"buffer specialization is missing");
}
return state.program.info.buffers[mem.resource].packed_stride;
}
Prospero::BufferFormat StorageBufferFormat(const EmitterState& state, const IR::MemoryInfo& mem,
uint32_t use_pc) {
(void)use_pc;
if (mem.resource >= state.program.info.buffers.size()) {
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::Buffers, mem.resource,
"buffer specialization is missing");
}
return state.program.info.buffers[mem.resource].descriptor_format;
}
void EmitStorageBufferOffsets(EmitterState& state) {
for (uint32_t i = 0; i < state.program.bindings.buffer_offset_count; i++) {
const auto word =
EmitShaderDataDwordLoad(state, state.program.bindings.buffer_offset_dword + i / 4u);
const auto shift = ConstantU32(state, (i % 4u) * 8u + 2u);
state.storage_buffer_offsets[i] = EmitBinaryU32(
state, OpBitwiseAnd, EmitBinaryU32(state, OpShiftRightLogical, word, shift),
ConstantU32(state, 0x3fu));
}
}
bool IsAddressMemoryKind(IR::ResourceKind kind) {
return kind == IR::ResourceKind::ScalarAddress || kind == IR::ResourceKind::Flat ||
kind == IR::ResourceKind::Global || kind == IR::ResourceKind::Scratch;
}
DescriptorResourceBinding
StorageBufferBindingForMemory(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc) {
(void)use_pc;
const auto binding =
ResourceForDescriptor(state, IR::DescriptorBindingKind::Buffers, mem.resource);
if (state.storage_buffer_variable == 0) {
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::Buffers, mem.resource,
"storage buffer descriptor array was not emitted");
}
return binding;
}
uint32_t EmitStorageBufferIndex(EmitterState& state, const IR::MemoryInfo& mem, uint32_t index,
uint32_t use_pc) {
if (IsAddressMemoryKind(mem.kind)) {
return index;
}
const auto binding = StorageBufferBindingForMemory(state, mem, use_pc);
return EmitAddU32(state, index, state.storage_buffer_offsets[binding.array_index]);
}
uint32_t EmitStorageBufferObjectPointer(EmitterState& state, const IR::MemoryInfo& mem,
uint32_t use_pc) {
if (IsAddressMemoryKind(mem.kind)) {
if (state.address_memory_variable == 0) {
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::AddressMemory,
mem.resource, "address memory binding was not emitted");
}
const auto binding =
ResourceForDescriptor(state, IR::DescriptorBindingKind::AddressMemory, mem.resource);
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, state.ptr_storage_buffer, pointer,
state.address_memory_variable,
ConstantU32(state, binding.array_index)});
return pointer;
}
const auto binding = StorageBufferBindingForMemory(state, mem, use_pc);
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, state.ptr_storage_buffer, pointer,
state.storage_buffer_variable,
ConstantU32(state, binding.array_index)});
return pointer;
}
uint32_t EmitStorageBufferElementInBounds(EmitterState& state, const IR::MemoryInfo& mem,
uint32_t index, uint32_t use_pc) {
index = EmitStorageBufferIndex(state, mem, index, use_pc);
const auto object = EmitStorageBufferObjectPointer(state, mem, use_pc);
const auto length = state.builder.AllocateId();
const auto in_bounds = state.builder.AllocateId();
state.builder.AddFunction({OpArrayLength, state.uint_type, length, object, 0});
state.builder.AddFunction({OpULessThan, state.bool_type, in_bounds, index, length});
return in_bounds;
}
uint32_t EmitStorageBufferElementPointer(EmitterState& state, const IR::MemoryInfo& mem,
uint32_t index, uint32_t use_pc) {
index = EmitStorageBufferIndex(state, mem, index, use_pc);
if (IsAddressMemoryKind(mem.kind)) {
if (state.address_memory_variable == 0) {
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::AddressMemory,
mem.resource, "address memory binding was not emitted");
}
const auto binding =
ResourceForDescriptor(state, IR::DescriptorBindingKind::AddressMemory, mem.resource);
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(
{OpAccessChain, state.ptr_storage_buffer_uint, pointer, state.address_memory_variable,
ConstantU32(state, binding.array_index), ConstantU32(state, 0), index});
return pointer;
}
const auto binding = StorageBufferBindingForMemory(state, mem, use_pc);
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(
{OpAccessChain, state.ptr_storage_buffer_uint, pointer, state.storage_buffer_variable,
ConstantU32(state, binding.array_index), ConstantU32(state, 0), index});
return pointer;
}
uint32_t EmitLdsElementPointer(EmitterState& state, uint32_t index) {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(
{OpAccessChain, state.ptr_workgroup_uint, pointer, state.lds_variable, index});
return pointer;
}
uint32_t LdsDwordCount(const EmitterState& state) {
return state.stage == ShaderType::Compute ? state.input_info.compute->lds_size_dwords : 8192u;
}
uint32_t EmitLdsElementInBounds(EmitterState& state, uint32_t index) {
const auto in_bounds = state.builder.AllocateId();
state.builder.AddFunction(
{OpULessThan, state.bool_type, in_bounds, index, ConstantU32(state, LdsDwordCount(state))});
return in_bounds;
}
uint32_t EmitMemoryElementPointer(EmitterState& state, const IR::MemoryInfo& mem, uint32_t index,
uint32_t use_pc) {
if (mem.kind == IR::ResourceKind::Lds) {
return EmitLdsElementPointer(state, index);
}
if (mem.kind == IR::ResourceKind::Gds) {
return EmitGdsElementPointer(state, index);
}
return EmitStorageBufferElementPointer(state, mem, index, use_pc);
}
uint32_t EmitTBufferBitcastF32ToU32(EmitterState& state, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, state.uint_type, ret, value});
return ret;
}
uint32_t EmitTBufferBitcastU32ToF32(EmitterState& state, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, state.float_type, ret, value});
return ret;
}
uint32_t EmitTBufferBitcastU32ToI32(EmitterState& state, uint32_t value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({OpBitcast, state.int_type, ret, value});
return ret;
}
uint32_t EmitTBufferCompareU32Constant(EmitterState& state, uint32_t opcode, uint32_t value,
uint32_t constant) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction({opcode, state.bool_type, ret, value, ConstantU32(state, constant)});
return ret;
}
uint32_t EmitTBufferSelectF32(EmitterState& state, uint32_t condition, uint32_t true_value,
uint32_t false_value) {
const auto ret = state.builder.AllocateId();
state.builder.AddFunction(
{OpSelect, state.float_type, ret, condition, true_value, false_value});
return ret;
}
bool IsSignedFormatComponent(Format::ComponentType type) {
return type == Format::ComponentType::Sint || type == Format::ComponentType::Snorm ||
type == Format::ComponentType::Sscaled;
}
uint32_t EmitHalfToF32Bits(EmitterState& state, uint32_t raw) {
const auto unpacked = state.builder.AllocateId();
const auto value = state.builder.AllocateId();
state.builder.AddFunction(
{OpExtInst, state.vec2_float_type, unpacked, state.glsl_std450, GlslUnpackHalf2x16, raw});
state.builder.AddFunction({OpCompositeExtract, state.float_type, value, unpacked, 0});
return EmitTBufferBitcastF32ToU32(state, value);
}
uint32_t EmitUFloatToF32Bits(EmitterState& state, uint32_t raw, uint32_t bits) {
const auto mantissa_bits = bits == 11u ? 6u : 5u;
const auto mantissa_mask = (1u << mantissa_bits) - 1u;
const auto mantissa =
EmitBinaryU32(state, OpBitwiseAnd, raw, ConstantU32(state, mantissa_mask));
const auto exponent = EmitBinaryU32(
state, OpBitwiseAnd,
EmitBinaryU32(state, OpShiftRightLogical, raw, ConstantU32(state, mantissa_bits)),
ConstantU32(state, 0x1fu));
const auto exponent_32 = EmitAddU32(state, exponent, ConstantU32(state, 127u - 15u));
const auto exponent_bits =
EmitBinaryU32(state, OpShiftLeftLogical, exponent_32, ConstantU32(state, 23));
const auto mantissa_bits_32 =
EmitBinaryU32(state, OpShiftLeftLogical, mantissa, ConstantU32(state, 23u - mantissa_bits));
const auto normal_bits = EmitBinaryU32(state, OpBitwiseOr, exponent_bits, mantissa_bits_32);
const auto normal = EmitTBufferBitcastU32ToF32(state, normal_bits);
const auto special_bits =
EmitBinaryU32(state, OpBitwiseOr, ConstantU32(state, 0x7f800000u), mantissa_bits_32);
const auto special = EmitTBufferBitcastU32ToF32(state, special_bits);
const auto mantissa_f32 = state.builder.AllocateId();
const auto subnormal = state.builder.AllocateId();
state.builder.AddFunction({OpConvertUToF, state.float_type, mantissa_f32, mantissa});
state.builder.AddFunction(
{OpFMul, state.float_type, subnormal, mantissa_f32,
ConstantF32Value(state, std::ldexp(1.0f, 1 - 15 - static_cast<int>(mantissa_bits)))});
const auto zero_exp = EmitTBufferCompareU32Constant(state, OpIEqual, exponent, 0);
const auto special_exp = EmitTBufferCompareU32Constant(state, OpIEqual, exponent, 31);
const auto finite = EmitTBufferSelectF32(state, zero_exp, subnormal, normal);
const auto result = EmitTBufferSelectF32(state, special_exp, special, finite);
return EmitTBufferBitcastF32ToU32(state, result);
}
uint32_t NormalizeFormatComponent(EmitterState& state, const Format::BufferFormatInfo& info,
uint32_t component, uint32_t raw) {
const auto bits = info.component_bits[component];
switch (info.type) {
case Format::ComponentType::Uint:
case Format::ComponentType::Sint: return raw;
case Format::ComponentType::Uscaled: {
const auto value = state.builder.AllocateId();
state.builder.AddFunction({OpConvertUToF, state.float_type, value, raw});
return EmitTBufferBitcastF32ToU32(state, value);
}
case Format::ComponentType::Sscaled: {
const auto signed_raw = EmitTBufferBitcastU32ToI32(state, raw);
const auto value = state.builder.AllocateId();
state.builder.AddFunction({OpConvertSToF, state.float_type, value, signed_raw});
return EmitTBufferBitcastF32ToU32(state, value);
}
case Format::ComponentType::Unorm: {
const auto value = state.builder.AllocateId();
const auto normalized = state.builder.AllocateId();
const auto max_value = static_cast<float>((1u << bits) - 1u);
state.builder.AddFunction({OpConvertUToF, state.float_type, value, raw});
state.builder.AddFunction(
{OpFDiv, state.float_type, normalized, value, ConstantF32Value(state, max_value)});
return EmitTBufferBitcastF32ToU32(state, normalized);
}
case Format::ComponentType::Snorm: {
const auto signed_raw = EmitTBufferBitcastU32ToI32(state, raw);
const auto value = state.builder.AllocateId();
const auto normalized = state.builder.AllocateId();
const auto clamped = state.builder.AllocateId();
const auto max_value = static_cast<float>((1u << (bits - 1u)) - 1u);
state.builder.AddFunction({OpConvertSToF, state.float_type, value, signed_raw});
state.builder.AddFunction(
{OpFDiv, state.float_type, normalized, value, ConstantF32Value(state, max_value)});
state.builder.AddFunction({OpExtInst, state.float_type, clamped, state.glsl_std450,
GlslFMax, normalized, ConstantF32Value(state, -1.0f)});
return EmitTBufferBitcastF32ToU32(state, clamped);
}
case Format::ComponentType::Float:
if (bits == 32u) {
return raw;
}
if (bits == 16u) {
return EmitHalfToF32Bits(state, raw);
}
return EmitUFloatToF32Bits(state, raw, bits);
default: return raw;
}
}
void EmitDeviceAtomicMemoryBarrier(EmitterState& state) {
const auto semantics = MemorySemanticsAcquireRelease | MemorySemanticsUniformMemory;
state.builder.AddFunction(
{OpMemoryBarrier, ConstantU32(state, ScopeDevice), ConstantU32(state, semantics)});
}
uint32_t EmitGdsElementInBounds(EmitterState& state, uint32_t index) {
const auto length = state.builder.AllocateId();
const auto in_bounds = state.builder.AllocateId();
state.builder.AddFunction({OpArrayLength, state.uint_type, length, state.gds_variable, 0});
state.builder.AddFunction({OpULessThan, state.bool_type, in_bounds, index, length});
return in_bounds;
}
uint32_t EmitGdsElementPointer(EmitterState& state, uint32_t index) {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction({OpAccessChain, state.ptr_storage_buffer_uint, pointer,
state.gds_variable, ConstantU32(state, 0), index});
return pointer;
}
uint32_t EmitDsSwizzleTargetLane(EmitterState& state, uint32_t subid, uint32_t control) {
if ((control & 0xc000u) == 0xc000u) {
const uint32_t mask = control & 0x1fu;
const uint32_t rotate = (control >> 5u) & 0x1fu;
const uint32_t rotate_delta = (control & 0x400u) != 0u ? ((32u - rotate) & 0x1fu) : rotate;
const auto lane = EmitAndConstant(state, subid, 31);
const auto rotated_sum = EmitAddU32(state, lane, ConstantU32(state, rotate_delta));
const auto rotated = EmitAndConstant(state, rotated_sum, 31);
const auto kept = EmitAndConstant(state, lane, mask);
const auto moved = EmitAndConstant(state, rotated, (~mask) & 31u);
const auto combined = EmitOrU32(state, kept, moved);
const auto base = EmitAndConstant(state, subid, 0xffffffe0u);
return EmitOrU32(state, base, combined);
}
if ((control & 0x8000u) != 0) {
const auto lane2 = state.builder.AllocateId();
const auto shift = state.builder.AllocateId();
const auto perm0 = state.builder.AllocateId();
const auto perm = state.builder.AllocateId();
const auto base = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, lane2, subid, ConstantU32(state, 3)});
state.builder.AddFunction(
{OpShiftLeftLogical, state.uint_type, shift, lane2, ConstantU32(state, 1)});
state.builder.AddFunction(
{OpShiftRightLogical, state.uint_type, perm0, ConstantU32(state, control), shift});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, perm, perm0, ConstantU32(state, 3)});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, base, subid, ConstantU32(state, 0xfffffffcu)});
state.builder.AddFunction({OpBitwiseOr, state.uint_type, target, base, perm});
return target;
}
const auto lane = state.builder.AllocateId();
const auto masked = state.builder.AllocateId();
const auto ored = state.builder.AllocateId();
const auto xored = state.builder.AllocateId();
const auto base = state.builder.AllocateId();
const auto target = state.builder.AllocateId();
state.builder.AddFunction({OpBitwiseAnd, state.uint_type, lane, subid, ConstantU32(state, 31)});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, masked, lane, ConstantU32(state, control & 0x1fu)});
state.builder.AddFunction(
{OpBitwiseOr, state.uint_type, ored, masked, ConstantU32(state, (control >> 5u) & 0x1fu)});
state.builder.AddFunction(
{OpBitwiseXor, state.uint_type, xored, ored, ConstantU32(state, (control >> 10u) & 0x1fu)});
state.builder.AddFunction(
{OpBitwiseAnd, state.uint_type, base, subid, ConstantU32(state, 0xffffffe0u)});
state.builder.AddFunction({OpBitwiseOr, state.uint_type, target, base, xored});
return target;
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -1,4 +1,6 @@
#include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include <algorithm>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
@@ -57,45 +59,43 @@ uint32_t ConstantF32Value(EmitterState& state, float value) {
}
VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint32_t location) {
if (state.stage != ShaderType::Vertex || state.vertex_input_info == nullptr ||
location >= ShaderVertexInputInfo::RES_MAX ||
location >= static_cast<uint32_t>(state.vertex_input_info->resources_num)) {
if (state.stage != ShaderType::Vertex || location >= ShaderVertexInputInfo::RES_MAX ||
location >= static_cast<uint32_t>(state.input_info.vertex->resources_num)) {
return VertexInputScalarKind::Float;
}
switch (state.vertex_input_info->resources[location].Format()) {
case 5:
case 11:
case 18:
case 20:
case 27:
case 60:
case 62:
case 69:
case 72:
case 75: return VertexInputScalarKind::Uint;
case 6:
case 12:
case 19:
case 21:
case 28:
case 61:
case 63:
case 70:
case 73:
case 76: return VertexInputScalarKind::Sint;
switch (state.input_info.vertex->resources[location].Format()) {
case Prospero::BufferFormat::k8UInt:
case Prospero::BufferFormat::k16UInt:
case Prospero::BufferFormat::k8_8UInt:
case Prospero::BufferFormat::k32UInt:
case Prospero::BufferFormat::k16_16UInt:
case Prospero::BufferFormat::k8_8_8_8UInt:
case Prospero::BufferFormat::k32_32UInt:
case Prospero::BufferFormat::k16_16_16_16UInt:
case Prospero::BufferFormat::k32_32_32UInt:
case Prospero::BufferFormat::k32_32_32_32UInt: return VertexInputScalarKind::Uint;
case Prospero::BufferFormat::k8SInt:
case Prospero::BufferFormat::k16SInt:
case Prospero::BufferFormat::k8_8SInt:
case Prospero::BufferFormat::k32SInt:
case Prospero::BufferFormat::k16_16SInt:
case Prospero::BufferFormat::k8_8_8_8SInt:
case Prospero::BufferFormat::k32_32SInt:
case Prospero::BufferFormat::k16_16_16_16SInt:
case Prospero::BufferFormat::k32_32_32SInt:
case Prospero::BufferFormat::k32_32_32_32SInt: return VertexInputScalarKind::Sint;
default: return VertexInputScalarKind::Float;
}
}
uint32_t VertexParameterComponentCount(const EmitterState& state, const InputBinding& input) {
uint32_t count = input.component_count;
if (state.stage == ShaderType::Vertex && state.vertex_input_info != nullptr &&
input.location < ShaderVertexInputInfo::RES_MAX &&
input.location < static_cast<uint32_t>(state.vertex_input_info->resources_num) &&
state.vertex_input_info->resources_dst[input.location].registers_num > 0) {
if (state.stage == ShaderType::Vertex && input.location < ShaderVertexInputInfo::RES_MAX &&
input.location < static_cast<uint32_t>(state.input_info.vertex->resources_num) &&
state.input_info.vertex->resources_dst[input.location].registers_num > 0) {
count = static_cast<uint32_t>(
state.vertex_input_info->resources_dst[input.location].registers_num);
state.input_info.vertex->resources_dst[input.location].registers_num);
}
return std::clamp(count, 1u, 4u);
}
@@ -175,9 +175,9 @@ uint32_t VertexParameterInputPointerType(const EmitterState& state, VertexInputS
}
static bool MrtUsesUintOutput(const EmitterState& state, uint32_t index) {
return state.stage == ShaderType::Pixel && state.pixel_input_info != nullptr &&
index < std::size(state.pixel_input_info->target_output_mode) &&
state.pixel_input_info->target_output_mode[index] == 7u;
return state.stage == ShaderType::Pixel &&
index < std::size(state.input_info.pixel->target_output_mode) &&
state.input_info.pixel->target_output_mode[index] == 7u;
}
void AllocateInputVariables(EmitterState& state) {
@@ -258,8 +258,8 @@ void AddInputAnnotationsAndNames(EmitterState& state) {
if (flat) {
state.builder.AddAnnotation({OpDecorate, input.variable_id, DecorationFlat});
}
if (state.stage == ShaderType::Pixel && state.pixel_input_info != nullptr &&
state.pixel_input_info->ps_no_perspective && !flat) {
if (state.stage == ShaderType::Pixel && state.input_info.pixel->ps_no_perspective &&
!flat) {
state.builder.AddAnnotation(
{OpDecorate, input.variable_id, DecorationNoPerspective});
}
@@ -413,6 +413,11 @@ void EmitHeaderAndTypes(EmitterState& state) {
state.vec3_float_type = state.builder.AllocateId();
state.vec4_float_type = state.builder.AllocateId();
state.ptr_func_uint = state.builder.AllocateId();
state.ptr_func_bool = state.builder.AllocateId();
state.ptr_func_float = state.builder.AllocateId();
state.ptr_func_uint_pair = state.builder.AllocateId();
state.ptr_func_vec4_uint = state.builder.AllocateId();
state.ptr_func_vec2_float = state.builder.AllocateId();
state.ptr_input_float = state.builder.AllocateId();
state.ptr_input_bool = state.builder.AllocateId();
state.ptr_input_int = state.builder.AllocateId();
@@ -481,6 +486,7 @@ void EmitHeaderAndTypes(EmitterState& state) {
state.builder.AddCapability({CapabilitySampled1D});
state.builder.AddCapability({CapabilityImage1D});
state.builder.AddCapability({CapabilityImageQuery});
state.builder.AddCapability({CapabilitySignedZeroInfNanPreserve});
if (state.needs_image_gather_extended) {
state.builder.AddCapability({CapabilityImageGatherExtended});
}
@@ -504,20 +510,22 @@ void EmitHeaderAndTypes(EmitterState& state) {
state.builder.AddCapability({CapabilityComputeDerivativeGroupQuadsKHR});
state.builder.AddExtension("SPV_KHR_compute_shader_derivatives");
}
state.builder.AddExtension("SPV_KHR_float_controls");
state.builder.AddExtInstImport(state.glsl_std450, "GLSL.std.450");
state.builder.AddMemoryModel({AddressingModelLogical, MemoryModelGLSL450});
state.builder.AddEntryPoint(ExecutionModelForStage(state.stage), state.main_func, "main",
state.interface_variables);
// GCN/RDNA arithmetic preserves 32-bit signed zero, infinity, and NaN. Declaring that
// contract prevents host compilers from treating synthesized IEEE values as finite.
state.builder.AddExecutionMode({state.main_func, ExecutionModeSignedZeroInfNanPreserve, 32u});
if (state.stage == ShaderType::Compute) {
uint32_t local_x = state.needs_compute_derivatives ? 2u : 1u;
uint32_t local_y = state.needs_compute_derivatives ? 2u : 1u;
uint32_t local_z = 1u;
if (state.compute_input_info != nullptr) {
const auto* cs = state.compute_input_info;
local_x = cs->threads_num[0] != 0u ? cs->threads_num[0] : local_x;
local_y = cs->threads_num[1] != 0u ? cs->threads_num[1] : local_y;
local_z = cs->threads_num[2] != 0u ? cs->threads_num[2] : local_z;
}
uint32_t local_x = state.needs_compute_derivatives ? 2u : 1u;
uint32_t local_y = state.needs_compute_derivatives ? 2u : 1u;
uint32_t local_z = 1u;
const auto* cs = state.input_info.compute;
local_x = cs->threads_num[0] != 0u ? cs->threads_num[0] : local_x;
local_y = cs->threads_num[1] != 0u ? cs->threads_num[1] : local_y;
local_z = cs->threads_num[2] != 0u ? cs->threads_num[2] : local_z;
state.builder.AddExecutionMode(
{state.main_func, ExecutionModeLocalSize, local_x, local_y, local_z});
}
@@ -526,10 +534,9 @@ void EmitHeaderAndTypes(EmitterState& state) {
if (state.depth_variable != 0) {
state.builder.AddExecutionMode({state.main_func, ExecutionModeDepthReplacing});
}
if (state.pixel_input_info != nullptr && state.pixel_input_info->ps_early_z &&
!state.pixel_input_info->ps_pixel_kill_enable &&
!state.pixel_input_info->ps_depth_export_enable &&
!state.pixel_input_info->ps_sample_mask_export_enable) {
if (state.input_info.pixel->ps_early_z && !state.input_info.pixel->ps_pixel_kill_enable &&
!state.input_info.pixel->ps_depth_export_enable &&
!state.input_info.pixel->ps_sample_mask_export_enable) {
state.builder.AddExecutionMode({state.main_func, ExecutionModeEarlyFragmentTests});
}
}
@@ -568,6 +575,16 @@ void EmitHeaderAndTypes(EmitterState& state) {
state.builder.AddType({OpTypeVector, state.vec4_float_type, state.float_type, 4});
state.builder.AddType(
{OpTypePointer, state.ptr_func_uint, StorageClassFunction, state.uint_type});
state.builder.AddType(
{OpTypePointer, state.ptr_func_bool, StorageClassFunction, state.bool_type});
state.builder.AddType(
{OpTypePointer, state.ptr_func_float, StorageClassFunction, state.float_type});
state.builder.AddType(
{OpTypePointer, state.ptr_func_uint_pair, StorageClassFunction, state.uint_pair_type});
state.builder.AddType(
{OpTypePointer, state.ptr_func_vec4_uint, StorageClassFunction, state.vec4_uint_type});
state.builder.AddType(
{OpTypePointer, state.ptr_func_vec2_float, StorageClassFunction, state.vec2_float_type});
state.builder.AddType(
{OpTypePointer, state.ptr_input_float, StorageClassInput, state.float_type});
state.builder.AddType(
@@ -716,7 +733,8 @@ void EmitHeaderAndTypes(EmitterState& state) {
if (state.stage == ShaderType::Compute || state.needs_function_lds) {
const auto storage_class =
state.needs_function_lds ? StorageClassFunction : StorageClassWorkgroup;
const auto lds_size = ConstantU32(state, state.needs_function_lds ? 8192u : 1024u);
const auto lds_dwords = std::max(LdsDwordCount(state), 1u);
const auto lds_size = ConstantU32(state, lds_dwords);
state.builder.AddType({OpTypeArray, state.lds_array_type, state.uint_type, lds_size});
state.builder.AddType(
{OpTypePointer, state.ptr_workgroup_array, storage_class, state.lds_array_type});
@@ -0,0 +1,567 @@
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
#include <algorithm>
#include <bit>
#include <fmt/format.h>
#include <functional>
#include <unordered_set>
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
namespace {
void EmitKillIfBoolFalse(EmitterState& state, uint32_t active) {
const auto kill_label = state.builder.AllocateId();
const auto merge_label = state.builder.AllocateId();
const auto inactive = state.builder.AllocateId();
state.builder.AddFunction({OpLogicalNot, state.bool_type, inactive, active});
state.builder.AddFunction({OpSelectionMerge, merge_label, SelectionControlNone});
state.builder.AddFunction({OpBranchConditional, inactive, kill_label, merge_label});
state.builder.AddFunction({OpLabel, kill_label});
state.builder.AddFunction({OpKill});
state.builder.AddFunction({OpLabel, merge_label});
}
void EmitKillIfPixelValidMaskInactive(EmitterState& state) {
if (state.pixel_valid_mask_variable == 0) {
return;
}
const auto mask_value = state.builder.AllocateId();
const auto active = state.builder.AllocateId();
state.builder.AddFunction(
{OpLoad, state.uint_type, mask_value, state.pixel_valid_mask_variable});
state.builder.AddFunction(
{OpINotEqual, state.bool_type, active, mask_value, ConstantU32(state, 0)});
EmitKillIfBoolFalse(state, active);
}
uint32_t BoolConstant(EmitterState& state, bool value) {
const auto id = state.builder.AllocateId();
state.builder.AddType({value ? 41u : 42u, state.bool_type, id});
return id;
}
uint32_t ConstantPair(EmitterState& state, uint64_t value) {
const auto id = state.builder.AllocateId();
state.builder.AddType({OpConstantComposite, state.uint_pair_type, id,
ConstantU32(state, static_cast<uint32_t>(value)),
ConstantU32(state, static_cast<uint32_t>(value >> 32u))});
return id;
}
uint32_t PhiPointerType(const EmitterState& state, IR::Type type) {
switch (type) {
case IR::Type::U1: return state.ptr_func_bool;
case IR::Type::U8:
case IR::Type::U16:
case IR::Type::U32:
case IR::Type::F16: return state.ptr_func_uint;
case IR::Type::F32: return state.ptr_func_float;
case IR::Type::U64:
case IR::Type::U32x2: return state.ptr_func_uint_pair;
case IR::Type::U32x4: return state.ptr_func_vec4_uint;
case IR::Type::F32x2: return state.ptr_func_vec2_float;
default: return 0;
}
}
void StorePhiEdge(ValueEmitContext& ctx, const IR::Block* from, const IR::Block* to) {
if (to == nullptr) {
return;
}
for (const auto& phi: *to) {
if (phi.GetOpcode() != IR::ValueOpcode::Phi) {
continue;
}
for (size_t index = 0; index < phi.NumArgs(); index++) {
if (phi.PhiBlock(index) == from) {
ctx.state.builder.AddFunction(
{OpStore, ctx.phi_variables.at(&phi), ctx.Def(phi.Arg(index))});
break;
}
}
}
}
const IR::Block* TargetBlock(const IR::ValueProgram& program, uint32_t id) {
const auto found = std::ranges::find_if(
program.block_info, [&](const IR::ValueBlockInfo& info) { return info.id == id; });
if (found == program.block_info.end()) {
return nullptr;
}
return program.blocks[static_cast<size_t>(found - program.block_info.begin())];
}
void EmitReturn(ValueEmitContext& ctx) {
EmitKillIfPixelValidMaskInactive(ctx.state);
ctx.state.builder.AddFunction({OpReturn});
}
void EmitStructuredTerminator(ValueEmitContext& ctx, const IR::Block* block,
const IR::ValueBlockInfo& info) {
const auto& term = info.terminator;
const auto emit_merge = [&]() {
if (term.loop_header) {
const auto* merge = TargetBlock(ctx.program, term.merge_block);
const auto* cont = TargetBlock(ctx.program, term.continue_block);
if (merge != nullptr && cont != nullptr) {
ctx.state.builder.AddFunction(
{OpLoopMerge, ctx.Label(merge), ctx.Label(cont), LoopControlNone});
}
} else if (term.kind == CFG::TerminatorKind::ConditionalBranch &&
term.merge_block != UINT32_MAX) {
if (const auto* merge = TargetBlock(ctx.program, term.merge_block); merge != nullptr) {
ctx.state.builder.AddFunction(
{OpSelectionMerge, ctx.Label(merge), SelectionControlNone});
}
}
};
switch (term.kind) {
case CFG::TerminatorKind::Branch: {
const auto* target = TargetBlock(ctx.program, term.true_block);
if (target == nullptr) {
EmitReturn(ctx);
return;
}
StorePhiEdge(ctx, block, target);
emit_merge();
ctx.state.builder.AddFunction({OpBranch, ctx.Label(target)});
return;
}
case CFG::TerminatorKind::ConditionalBranch: {
const auto* true_block = TargetBlock(ctx.program, term.true_block);
const auto* false_block = TargetBlock(ctx.program, term.false_block);
if (true_block == nullptr || false_block == nullptr || info.condition.IsEmpty()) {
EmitReturn(ctx);
return;
}
StorePhiEdge(ctx, block, true_block);
StorePhiEdge(ctx, block, false_block);
const auto condition = ctx.Def(info.condition);
emit_merge();
ctx.state.builder.AddFunction(
{OpBranchConditional, condition, ctx.Label(true_block), ctx.Label(false_block)});
return;
}
default: EmitReturn(ctx); return;
}
}
void EmitDispatcherTarget(ValueEmitContext& ctx, const IR::Block* from, uint32_t target) {
const auto* block = TargetBlock(ctx.program, target);
if (block != nullptr) {
StorePhiEdge(ctx, from, block);
}
}
void EmitDispatcherTerminator(ValueEmitContext& ctx, const IR::Block* block,
const IR::ValueBlockInfo& info, uint32_t exit_label) {
const auto& term = info.terminator;
switch (term.kind) {
case CFG::TerminatorKind::Branch:
EmitDispatcherTarget(ctx, block, term.true_block);
ctx.state.builder.AddFunction(
{OpStore, ctx.state.dispatch_pc_variable, ConstantU32(ctx.state, term.true_block)});
break;
case CFG::TerminatorKind::ConditionalBranch: {
EmitDispatcherTarget(ctx, block, term.true_block);
EmitDispatcherTarget(ctx, block, term.false_block);
const auto selected = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({OpSelect, ctx.state.uint_type, selected,
ctx.Def(info.condition),
ConstantU32(ctx.state, term.true_block),
ConstantU32(ctx.state, term.false_block)});
ctx.state.builder.AddFunction({OpStore, ctx.state.dispatch_pc_variable, selected});
break;
}
case CFG::TerminatorKind::IndirectBranch: {
for (const auto target: term.indirect_targets) {
EmitDispatcherTarget(ctx, block, target);
}
for (const auto target: term.indirect_selector_targets) {
EmitDispatcherTarget(ctx, block, target);
}
uint32_t selected = ConstantU32(ctx.state, UINT32_MAX);
if (!info.indirect_target.IsEmpty()) {
const auto selector = ctx.Def(info.indirect_target);
const auto& values = term.indirect_selector_code != UINT32_MAX
? term.indirect_selector_values
: term.indirect_target_pcs;
const auto& targets = term.indirect_selector_code != UINT32_MAX
? term.indirect_selector_targets
: term.indirect_targets;
for (size_t index = 0; index < std::min(values.size(), targets.size()); index++) {
const auto match = ctx.state.builder.AllocateId();
const auto next = ctx.state.builder.AllocateId();
ctx.state.builder.AddFunction({OpIEqual, ctx.state.bool_type, match, selector,
ConstantU32(ctx.state, values[index])});
ctx.state.builder.AddFunction({OpSelect, ctx.state.uint_type, next, match,
ConstantU32(ctx.state, targets[index]),
selected});
selected = next;
}
}
ctx.state.builder.AddFunction({OpStore, ctx.state.dispatch_pc_variable, selected});
break;
}
default:
ctx.state.builder.AddFunction(
{OpStore, ctx.state.dispatch_pc_variable, ConstantU32(ctx.state, UINT32_MAX)});
break;
}
ctx.state.builder.AddFunction({OpBranch, exit_label});
}
bool EmitInstruction(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto finish = [&]() {
if (const auto found = ctx.dispatcher_variables.find(&inst);
found != ctx.dispatcher_variables.end()) {
ctx.state.builder.AddFunction(
{OpStore, found->second, ctx.Def(IR::Value(const_cast<IR::Inst*>(&inst)))});
}
return true;
};
if (inst.GetOpcode() == IR::ValueOpcode::Phi) {
ctx.state.builder.AddFunction(
{OpLoad, ctx.TypeId(inst.GetType()), ctx.Result(inst), ctx.phi_variables.at(&inst)});
return finish();
}
if (EmitValueFlow(ctx, inst) || EmitValueAlu(ctx, inst) || EmitValueMemory(ctx, inst) ||
EmitValueImage(ctx, inst)) {
return finish();
}
ctx.Fail(inst, "has no direct SPIR-V emitter");
return false;
}
void EmitBlock(ValueEmitContext& ctx, const IR::Block* block) {
ctx.current_block = block;
ctx.state.builder.AddFunction({OpLabel, ctx.Label(block)});
for (const auto& inst: *block) {
if (!EmitInstruction(ctx, inst)) {
return;
}
}
}
void EmitStructuredFunction(ValueEmitContext& ctx) {
ctx.state.builder.AddFunction({OpBranch, ctx.Label(ctx.program.blocks.front())});
for (size_t index = 0; index < ctx.program.blocks.size() && !ctx.failed; index++) {
EmitBlock(ctx, ctx.program.blocks[index]);
EmitStructuredTerminator(ctx, ctx.program.blocks[index], ctx.program.block_info[index]);
}
}
void EmitDispatcherFunction(ValueEmitContext& ctx) {
auto& state = ctx.state;
const auto* entry = ctx.program.blocks.front();
state.builder.AddFunction({OpBranch, ctx.Label(entry)});
EmitBlock(ctx, entry);
EmitDispatcherTerminator(ctx, entry, ctx.program.block_info.front(),
state.dispatch_header_label);
state.builder.AddFunction({OpLabel, state.dispatch_header_label});
const auto pc = state.builder.AllocateId();
state.builder.AddFunction({OpLoad, state.uint_type, pc, state.dispatch_pc_variable});
const auto done = state.builder.AllocateId();
state.builder.AddFunction(
{OpIEqual, state.bool_type, done, pc, ConstantU32(ctx.state, UINT32_MAX)});
state.builder.AddFunction(
{OpLoopMerge, state.dispatch_merge_label, state.dispatch_continue_label, LoopControlNone});
state.builder.AddFunction(
{OpBranchConditional, done, state.dispatch_merge_label, state.dispatch_select_label});
state.builder.AddFunction({OpLabel, state.dispatch_select_label});
state.builder.AddFunction(
{OpSelectionMerge, state.dispatch_after_switch_label, SelectionControlNone});
std::vector<uint32_t> words {OpSwitch, pc, state.dispatch_default_label};
for (size_t index = 1; index < ctx.program.blocks.size(); index++) {
words.push_back(ctx.program.block_info[index].id);
words.push_back(ctx.Label(ctx.program.blocks[index]));
}
state.builder.AddFunction(words);
state.builder.AddFunction({OpLabel, state.dispatch_default_label});
state.builder.AddFunction(
{OpStore, state.dispatch_pc_variable, ConstantU32(ctx.state, UINT32_MAX)});
state.builder.AddFunction({OpBranch, state.dispatch_after_switch_label});
for (size_t index = 1; index < ctx.program.blocks.size() && !ctx.failed; index++) {
EmitBlock(ctx, ctx.program.blocks[index]);
EmitDispatcherTerminator(ctx, ctx.program.blocks[index], ctx.program.block_info[index],
state.dispatch_after_switch_label);
}
state.builder.AddFunction({OpLabel, state.dispatch_after_switch_label});
state.builder.AddFunction({OpBranch, state.dispatch_continue_label});
state.builder.AddFunction({OpLabel, state.dispatch_continue_label});
state.builder.AddFunction({OpBranch, state.dispatch_header_label});
state.builder.AddFunction({OpLabel, state.dispatch_merge_label});
EmitReturn(ctx);
}
} // namespace
uint32_t ValueEmitContext::TypeId(IR::Type type) const {
switch (type) {
case IR::Type::U1: return state.bool_type;
case IR::Type::U8:
case IR::Type::U16:
case IR::Type::U32:
case IR::Type::F16: return state.uint_type;
case IR::Type::U64:
case IR::Type::U32x2: return state.uint_pair_type;
case IR::Type::F32: return state.float_type;
case IR::Type::U32x4: return state.vec4_uint_type;
case IR::Type::F32x2: return state.vec2_float_type;
default: return 0;
}
}
uint32_t ValueEmitContext::Def(IR::Value value) {
value = value.Resolve();
if (value.IsImmediate()) {
switch (value.GetType()) {
case IR::Type::U1: return BoolConstant(state, value.U1());
case IR::Type::U8: return ConstantU32(state, value.U8());
case IR::Type::U16: return ConstantU32(state, value.U16());
case IR::Type::U32: return ConstantU32(state, value.U32());
case IR::Type::U64: return ConstantPair(state, value.U64());
case IR::Type::F16: return ConstantU32(state, value.F16Bits());
case IR::Type::F32:
return ConstantF32(state, std::bit_cast<uint32_t>(value.F32Value()));
default: break;
}
}
const auto* inst = value.ResolveInstruction();
if (inst == nullptr) {
failed = true;
error = "direct SPIR-V emitter received a non-value argument";
return ConstantU32(state, 0);
}
if (program.dispatcher_fallback && current_block != nullptr &&
inst->Parent() != current_block) {
if (const auto found = dispatcher_variables.find(inst);
found != dispatcher_variables.end()) {
const auto id = state.builder.AllocateId();
state.builder.AddFunction({OpLoad, TypeId(inst->GetType()), id, found->second});
return id;
}
}
if (const auto found = definitions.find(inst); found != definitions.end()) {
return found->second;
}
const auto id = state.builder.AllocateId();
definitions.emplace(inst, id);
return id;
}
uint32_t ValueEmitContext::Arg(const IR::Inst& inst, size_t index) {
return Def(inst.Arg(index));
}
uint32_t ValueEmitContext::Result(const IR::Inst& inst) {
if (const auto found = definitions.find(&inst); found != definitions.end()) {
return found->second;
}
const auto id = state.builder.AllocateId();
definitions.emplace(&inst, id);
return id;
}
uint32_t ValueEmitContext::Emit(const IR::Inst& inst, uint32_t opcode, IR::Type type,
std::initializer_list<uint32_t> args) {
std::vector<uint32_t> words {opcode, TypeId(type), Result(inst)};
words.insert(words.end(), args.begin(), args.end());
state.builder.AddFunction(words);
return Result(inst);
}
uint32_t ValueEmitContext::Define(const IR::Inst& inst, uint32_t value) {
if (const auto found = definitions.find(&inst); found != definitions.end()) {
if (found->second != value) {
state.builder.AddFunction({OpCopyObject, TypeId(inst.GetType()), found->second, value});
}
return found->second;
}
definitions.emplace(&inst, value);
return value;
}
uint32_t ValueEmitContext::ResourceIndex(IR::Value value, IR::ValueOpcode opcode) {
const auto* inst = value.ResolveInstruction();
if (inst == nullptr || inst->GetOpcode() != opcode) {
failed = true;
error = "typed resource handle has the wrong producer";
return 0;
}
return inst->Flags<uint32_t>();
}
const IR::Inst* ValueEmitContext::ImageAddress(IR::Value value) {
const auto* inst = value.ResolveInstruction();
if (inst == nullptr || inst->GetOpcode() != IR::ValueOpcode::MakeImageAddress) {
failed = true;
error = "typed image address was not constructed by MakeImageAddress";
return nullptr;
}
return inst;
}
const IR::MemoryInfo& ValueEmitContext::Memory(const IR::Inst& inst) const {
return program.memory_info.at(inst.Flags<IR::MemoryFlags>().index);
}
const IR::ExportInfo& ValueEmitContext::Export(const IR::Inst& inst) const {
return program.export_info.at(inst.Flags<IR::ExportFlags>().index);
}
uint32_t ValueEmitContext::Label(const IR::Block* block) const {
return labels.at(block);
}
size_t ValueEmitContext::BlockIndex(const IR::Block* block) const {
return static_cast<size_t>(std::ranges::find(program.blocks, block) - program.blocks.begin());
}
void ValueEmitContext::Fail(const IR::Inst& inst, const char* reason) {
failed = true;
error = fmt::format("typed opcode {} {}", IR::ValueOpcodeName(inst.GetOpcode()), reason);
}
bool EmitValueProgram(EmitterState& state, const IR::ValueProgram& program, std::string* error) {
ValueEmitContext ctx(state, program);
for (const auto* block: program.blocks) {
ctx.labels.emplace(block, state.builder.AllocateId());
for (const auto& inst: *block) {
if (inst.GetOpcode() != IR::ValueOpcode::Phi) {
continue;
}
const auto ptr_type = PhiPointerType(state, inst.GetType());
if (ptr_type == 0) {
ctx.failed = true;
ctx.error = fmt::format("typed opcode Phi of type {} cannot be spilled across a "
"control-flow edge",
IR::TypeName(inst.GetType()));
break;
}
ctx.phi_variables.emplace(&inst, state.builder.AllocateId());
}
}
if (ctx.failed) {
SetError(error, ctx.error.c_str());
return false;
}
if (program.dispatcher_fallback) {
const auto mark_cross_block = [&](IR::Value value, const IR::Block* consumer) {
value = value.Resolve();
const auto* definition = value.TryInstruction();
if (definition == nullptr || definition->Parent() == consumer ||
PhiPointerType(state, definition->GetType()) == 0) {
return;
}
if (!ctx.dispatcher_variables.contains(definition)) {
ctx.dispatcher_variables.emplace(definition, state.builder.AllocateId());
}
};
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
for (size_t index = 0; index < inst.NumArgs(); index++) {
const auto* consumer =
inst.GetOpcode() == IR::ValueOpcode::Phi ? inst.PhiBlock(index) : block;
mark_cross_block(inst.Arg(index), consumer);
}
}
}
for (size_t index = 0; index < program.blocks.size(); index++) {
mark_cross_block(program.block_info[index].condition, program.blocks[index]);
mark_cross_block(program.block_info[index].indirect_target, program.blocks[index]);
}
state.dispatcher_fallback = true;
state.dispatch_pc_variable = state.builder.AllocateId();
state.dispatch_header_label = state.builder.AllocateId();
state.dispatch_select_label = state.builder.AllocateId();
state.dispatch_default_label = state.builder.AllocateId();
state.dispatch_after_switch_label = state.builder.AllocateId();
state.dispatch_continue_label = state.builder.AllocateId();
state.dispatch_merge_label = state.builder.AllocateId();
}
for (const auto* block: program.blocks) {
if (std::ranges::any_of(*block, [](const IR::Inst& inst) {
return inst.GetOpcode() == IR::ValueOpcode::SwizzleU32 ||
inst.GetOpcode() == IR::ValueOpcode::SharedAtomicFMin32 ||
inst.GetOpcode() == IR::ValueOpcode::SharedAtomicFMax32 ||
inst.GetOpcode() == IR::ValueOpcode::GdsAtomicFMin32 ||
inst.GetOpcode() == IR::ValueOpcode::GdsAtomicFMax32;
})) {
ctx.scratch_u32_variable = state.builder.AllocateId();
break;
}
}
state.builder.AddFunction(
{OpFunction, state.void_type, state.main_func, FunctionControlNone, state.func_type});
state.builder.AddFunction({OpLabel, state.entry_label});
if (state.needs_function_lds) {
state.builder.AddFunction(
{OpVariable, state.ptr_workgroup_array, state.lds_variable, StorageClassFunction});
}
if (state.pixel_valid_mask_variable != 0) {
state.builder.AddFunction({OpVariable, state.ptr_func_uint, state.pixel_valid_mask_variable,
StorageClassFunction});
}
if (program.dispatcher_fallback) {
state.builder.AddFunction(
{OpVariable, state.ptr_func_uint, state.dispatch_pc_variable, StorageClassFunction});
}
for (const auto& binding: state.registers) {
state.builder.AddFunction(
{OpVariable, state.ptr_func_uint, binding.pointer_id, StorageClassFunction});
}
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
if (inst.GetOpcode() != IR::ValueOpcode::Phi) {
continue;
}
state.builder.AddFunction({OpVariable, PhiPointerType(state, inst.GetType()),
ctx.phi_variables.at(&inst), StorageClassFunction});
}
}
if (program.dispatcher_fallback) {
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
if (const auto found = ctx.dispatcher_variables.find(&inst);
found != ctx.dispatcher_variables.end()) {
state.builder.AddFunction({OpVariable, PhiPointerType(state, inst.GetType()),
found->second, StorageClassFunction});
}
}
}
}
if (ctx.scratch_u32_variable != 0) {
state.builder.AddFunction(
{OpVariable, state.ptr_func_uint, ctx.scratch_u32_variable, StorageClassFunction});
}
for (const auto& binding: state.registers) {
state.builder.AddFunction({OpStore, binding.pointer_id,
ConstantU32(state, InitialRegisterValue(state, binding.reg))});
}
if (state.pixel_valid_mask_variable != 0) {
state.builder.AddFunction(
{OpStore, state.pixel_valid_mask_variable, ConstantU32(state, 1)});
}
EmitStorageBufferOffsets(state);
if (program.blocks.empty()) {
EmitReturn(ctx);
} else if (program.dispatcher_fallback) {
EmitDispatcherFunction(ctx);
} else {
EmitStructuredFunction(ctx);
}
state.builder.AddFunction({OpFunctionEnd});
if (ctx.failed) {
SetError(error, ctx.error.c_str());
return false;
}
return true;
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter
@@ -1,477 +0,0 @@
#include "graphics/shader/recompiler/decompiler/MemoryOps.h"
#include <fmt/format.h>
#include <iterator>
namespace Libs::Graphics::ShaderRecompiler::Decoder {
namespace {
struct MemoryOpcodeInfo {
uint32_t opcode = 0;
Opcode decoded = Opcode::Unsupported;
uint32_t data_dwords = 1;
uint32_t data_bits = 32;
bool data_signed = false;
bool typed = false;
bool formatted = false;
};
constexpr MemoryOpcodeInfo SMEM_OPS[] = {
{0x00u, Opcode::SLoadDword, 1, 32}, {0x01u, Opcode::SLoadDwordx2, 2, 32},
{0x02u, Opcode::SLoadDwordx4, 4, 32}, {0x03u, Opcode::SLoadDwordx8, 8, 32},
{0x04u, Opcode::SLoadDwordx16, 16, 32}, {0x08u, Opcode::SBufferLoadDword, 1, 32},
{0x09u, Opcode::SBufferLoadDwordx2, 2, 32}, {0x0au, Opcode::SBufferLoadDwordx4, 4, 32},
{0x0bu, Opcode::SBufferLoadDwordx8, 8, 32}, {0x0cu, Opcode::SBufferLoadDwordx16, 16, 32},
};
constexpr MemoryOpcodeInfo MUBUF_OPS[] = {
{0x00u, Opcode::BufferLoadFormatX, 1, 32, false, false, true},
{0x01u, Opcode::BufferLoadFormatXy, 2, 32, false, false, true},
{0x02u, Opcode::BufferLoadFormatXyz, 3, 32, false, false, true},
{0x03u, Opcode::BufferLoadFormatXyzw, 4, 32, false, false, true},
{0x04u, Opcode::BufferStoreFormatX, 1, 32, false, false, true},
{0x05u, Opcode::BufferStoreFormatXy, 2, 32, false, false, true},
{0x06u, Opcode::BufferStoreFormatXyz, 3, 32, false, false, true},
{0x07u, Opcode::BufferStoreFormatXyzw, 4, 32, false, false, true},
{0x08u, Opcode::BufferLoadUbyte, 1, 8},
{0x09u, Opcode::BufferLoadSbyte, 1, 8, true},
{0x0au, Opcode::BufferLoadUshort, 1, 16},
{0x0bu, Opcode::BufferLoadSshort, 1, 16, true},
{0x0cu, Opcode::BufferLoadDword, 1, 32},
{0x0du, Opcode::BufferLoadDwordx2, 2, 32},
{0x0eu, Opcode::BufferLoadDwordx4, 4, 32},
{0x0fu, Opcode::BufferLoadDwordx3, 3, 32},
{0x18u, Opcode::BufferStoreByte, 1, 8},
{0x1au, Opcode::BufferStoreShort, 1, 16},
{0x1cu, Opcode::BufferStoreDword, 1, 32},
{0x1du, Opcode::BufferStoreDwordx2, 2, 32},
{0x1eu, Opcode::BufferStoreDwordx4, 4, 32},
{0x1fu, Opcode::BufferStoreDwordx3, 3, 32},
{0x30u, Opcode::BufferAtomicSwap, 1, 32},
{0x32u, Opcode::BufferAtomicAdd, 1, 32},
{0x33u, Opcode::BufferAtomicSub, 1, 32},
{0x35u, Opcode::BufferAtomicSMin, 1, 32},
{0x36u, Opcode::BufferAtomicUMin, 1, 32},
{0x37u, Opcode::BufferAtomicSMax, 1, 32},
{0x38u, Opcode::BufferAtomicUMax, 1, 32},
{0x39u, Opcode::BufferAtomicAnd, 1, 32},
{0x3au, Opcode::BufferAtomicOr, 1, 32},
{0x3bu, Opcode::BufferAtomicXor, 1, 32},
{0x3fu, Opcode::BufferAtomicFMin, 1, 32},
{0x40u, Opcode::BufferAtomicFMax, 1, 32},
};
constexpr MemoryOpcodeInfo MTBUF_OPS[] = {
{0x00u, Opcode::TBufferLoadFormatX, 1, 32, false, true, true},
{0x01u, Opcode::TBufferLoadFormatXy, 2, 32, false, true, true},
{0x02u, Opcode::TBufferLoadFormatXyz, 3, 32, false, true, true},
{0x03u, Opcode::TBufferLoadFormatXyzw, 4, 32, false, true, true},
{0x04u, Opcode::TBufferStoreFormatX, 1, 32, false, true, true},
{0x05u, Opcode::TBufferStoreFormatXy, 2, 32, false, true, true},
{0x06u, Opcode::TBufferStoreFormatXyz, 3, 32, false, true, true},
{0x07u, Opcode::TBufferStoreFormatXyzw, 4, 32, false, true, true},
};
constexpr MemoryOpcodeInfo FLAT_OPS[] = {
{0x08u, Opcode::FlatLoadUbyte, 1, 8}, {0x09u, Opcode::FlatLoadSbyte, 1, 8, true},
{0x0au, Opcode::FlatLoadUshort, 1, 16}, {0x0bu, Opcode::FlatLoadSshort, 1, 16, true},
{0x0cu, Opcode::FlatLoadDword, 1, 32}, {0x0du, Opcode::FlatLoadDwordx2, 2, 32},
{0x0eu, Opcode::FlatLoadDwordx4, 4, 32}, {0x0fu, Opcode::FlatLoadDwordx3, 3, 32},
{0x18u, Opcode::FlatStoreByte, 1, 8}, {0x1au, Opcode::FlatStoreShort, 1, 16},
{0x1cu, Opcode::FlatStoreDword, 1, 32}, {0x1du, Opcode::FlatStoreDwordx2, 2, 32},
{0x1eu, Opcode::FlatStoreDwordx4, 4, 32}, {0x1fu, Opcode::FlatStoreDwordx3, 3, 32},
};
constexpr MemoryOpcodeInfo DS_OPS[] = {
{0x00u, Opcode::DsAddU32, 1, 32}, {0x01u, Opcode::DsSubU32, 1, 32},
{0x05u, Opcode::DsMinI32, 1, 32}, {0x06u, Opcode::DsMaxI32, 1, 32},
{0x07u, Opcode::DsMinU32, 1, 32}, {0x08u, Opcode::DsMaxU32, 1, 32},
{0x09u, Opcode::DsAndB32, 1, 32}, {0x0au, Opcode::DsOrB32, 1, 32},
{0x0bu, Opcode::DsXorB32, 1, 32}, {0x0du, Opcode::DsWriteB32, 1, 32},
{0x0eu, Opcode::DsWrite2B32, 2, 32}, {0x0fu, Opcode::DsWrite2St64B32, 2, 32},
{0x12u, Opcode::DsMinF32, 1, 32}, {0x13u, Opcode::DsMaxF32, 1, 32},
{0x1eu, Opcode::DsWriteByte, 1, 8}, {0x1fu, Opcode::DsWriteShort, 1, 16},
{0x20u, Opcode::DsAddRtnU32, 1, 32}, {0x21u, Opcode::DsSubRtnU32, 1, 32},
{0x25u, Opcode::DsMinRtnI32, 1, 32}, {0x26u, Opcode::DsMaxRtnI32, 1, 32},
{0x27u, Opcode::DsMinRtnU32, 1, 32}, {0x28u, Opcode::DsMaxRtnU32, 1, 32},
{0x29u, Opcode::DsAndRtnB32, 1, 32}, {0x2au, Opcode::DsOrRtnB32, 1, 32},
{0x2bu, Opcode::DsXorRtnB32, 1, 32}, {0x2du, Opcode::DsWrxchgRtnB32, 1, 32},
{0x35u, Opcode::DsSwizzleB32, 1, 32}, {0x36u, Opcode::DsReadB32, 1, 32},
{0x37u, Opcode::DsRead2B32, 2, 32}, {0x38u, Opcode::DsRead2B32, 2, 32},
{0x39u, Opcode::DsReadSbyte, 1, 8, true}, {0x3au, Opcode::DsReadUbyte, 1, 8},
{0x3bu, Opcode::DsReadSshort, 1, 16, true}, {0x3cu, Opcode::DsReadUshort, 1, 16},
{0x3du, Opcode::DsConsume, 1, 32}, {0x3eu, Opcode::DsAppend, 1, 32},
{0x4du, Opcode::DsWriteB64, 2, 32}, {0x4eu, Opcode::DsWrite2B64, 4, 32},
{0x4fu, Opcode::DsWrite2St64B64, 4, 32}, {0x76u, Opcode::DsReadB64, 2, 32},
{0x77u, Opcode::DsRead2B64, 4, 32}, {0x78u, Opcode::DsRead2St64B64, 4, 32},
{0xb0u, Opcode::DsWriteAddtidB32, 1, 32}, {0xb1u, Opcode::DsReadAddtidB32, 1, 32},
{0xdeu, Opcode::DsWriteB96, 3, 32}, {0xdfu, Opcode::DsWriteB128, 4, 32},
{0xfeu, Opcode::DsReadB96, 3, 32}, {0xffu, Opcode::DsReadB128, 4, 32},
};
const MemoryOpcodeInfo* LookupMemoryOpcode(const MemoryOpcodeInfo* ops, uint32_t count,
uint32_t opcode) {
for (uint32_t i = 0; i < count; i++) {
if (ops[i].opcode == opcode) {
return &ops[i];
}
}
return nullptr;
}
uint32_t SignExtendU32(uint32_t value, uint32_t bits) {
if (bits == 0u || bits >= 32u) {
return value;
}
const uint32_t sign = 1u << (bits - 1u);
return (value ^ sign) - sign;
}
void MarkMemoryUnsupported(Instruction& inst, Family family, uint32_t opcode, const char* reason) {
inst.family = family;
inst.opcode_id = opcode;
inst.opcode = Opcode::Unsupported;
SetUnsupported(inst, family, opcode, reason);
}
void ApplyMemoryInfo(Instruction& inst, const MemoryOpcodeInfo* info) {
if (info == nullptr) {
inst.opcode = Opcode::Unsupported;
return;
}
inst.opcode = info->decoded;
inst.data_dwords = info->data_dwords;
inst.data_bits = info->data_bits;
inst.data_signed = info->data_signed;
inst.typed = info->typed;
inst.formatted = info->formatted;
}
bool IsDsWriteOpcode(Opcode opcode) {
switch (opcode) {
case Opcode::DsWriteByte:
case Opcode::DsWriteShort:
case Opcode::DsWrite2B32:
case Opcode::DsWrite2St64B32:
case Opcode::DsWrite2B64:
case Opcode::DsWrite2St64B64:
case Opcode::DsWriteB32:
case Opcode::DsWriteB64:
case Opcode::DsWriteB96:
case Opcode::DsWriteB128: return true;
default: return false;
}
}
bool IsDsAtomicOpcode(Opcode opcode) {
switch (opcode) {
case Opcode::DsAddU32:
case Opcode::DsAddRtnU32:
case Opcode::DsSubU32:
case Opcode::DsSubRtnU32:
case Opcode::DsMinI32:
case Opcode::DsMinRtnI32:
case Opcode::DsMaxI32:
case Opcode::DsMaxRtnI32:
case Opcode::DsMinU32:
case Opcode::DsMinRtnU32:
case Opcode::DsMaxU32:
case Opcode::DsMaxRtnU32:
case Opcode::DsAndB32:
case Opcode::DsAndRtnB32:
case Opcode::DsOrB32:
case Opcode::DsOrRtnB32:
case Opcode::DsXorB32:
case Opcode::DsXorRtnB32:
case Opcode::DsWrxchgRtnB32: return true;
default: return false;
}
}
uint32_t DsSourceCount(Opcode opcode) {
switch (opcode) {
case Opcode::DsWrite2B32:
case Opcode::DsWrite2St64B32:
case Opcode::DsWrite2B64:
case Opcode::DsWrite2St64B64:
case Opcode::DsMinF32:
case Opcode::DsMaxF32: return 3u;
case Opcode::DsReadAddtidB32:
case Opcode::DsConsume:
case Opcode::DsAppend: return 0u;
default: return IsDsWriteOpcode(opcode) || IsDsAtomicOpcode(opcode) ? 2u : 1u;
}
}
bool IsFlatStoreOpcode(Opcode opcode) {
switch (opcode) {
case Opcode::FlatStoreByte:
case Opcode::FlatStoreShort:
case Opcode::FlatStoreDword:
case Opcode::FlatStoreDwordx2:
case Opcode::FlatStoreDwordx3:
case Opcode::FlatStoreDwordx4: return true;
default: return false;
}
}
} // namespace
bool DecodeSmem(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
std::string* error) {
if (word_index + 1u >= code.size()) {
if (error != nullptr) {
*error = fmt::format("truncated SMEM instruction at pc 0x{:08x}", pc);
}
return false;
}
const uint32_t word0 = code[word_index];
const uint32_t word1 = code[word_index + 1u];
const uint32_t opcode = (word0 >> 18u) & 0xffu;
const uint32_t sdst = (word0 >> 6u) & 0x7fu;
const uint32_t sbase = word0 & 0x3fu;
const uint32_t soffset = (word1 >> 25u) & 0x7fu;
inst.pc = pc;
inst.word = word0;
inst.word_count = 2;
inst.offset = SignExtendU32(word1 & 0x1fffffu, 21u);
inst.glc = ((word0 >> 16u) & 1u) != 0;
inst.family = Family::SMEM;
inst.opcode_id = opcode;
const auto* info =
LookupMemoryOpcode(SMEM_OPS, static_cast<uint32_t>(std::size(SMEM_OPS)), opcode);
ApplyMemoryInfo(inst, info);
SetRawWords(inst, code, word_index, 2);
if (inst.opcode == Opcode::Unsupported) {
MarkMemoryUnsupported(inst, Family::SMEM, opcode, "SMEM opcode is not implemented");
}
DecodeScalarDestination(sdst, pc, inst.dst, nullptr);
// SMEM encodes SBASE in SGPR pairs. Scalar-buffer loads still use the same
// pair index; their descriptor operand consumes four SGPRs from that base.
DecodeScalarSource(sbase * 2u, pc, inst.src0, nullptr);
DecodeScalarSource(soffset, pc, inst.src1, nullptr);
inst.src_count = 2;
return true;
}
bool DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
Instruction& inst, std::string* error) {
if (word_index + 1u >= code.size()) {
if (error != nullptr) {
*error = fmt::format("truncated MUBUF instruction at pc 0x{:08x}", pc);
}
return false;
}
const uint32_t word0 = code[word_index];
const uint32_t word1 = code[word_index + 1u];
const uint32_t opcode = ((word0 >> 18u) & 0x7fu) | (((word0 >> 25u) & 1u) << 7u);
const uint32_t vdata = (word1 >> 8u) & 0xffu;
const uint32_t vaddr = word1 & 0xffu;
const uint32_t srsrc = (word1 >> 16u) & 0x1fu;
const uint32_t soffset = (word1 >> 24u) & 0xffu;
inst.pc = pc;
inst.word = word0;
inst.word_count = 2;
inst.offset = word0 & 0xfffu;
inst.idxen = ((word0 >> 13u) & 1u) != 0;
inst.offen = ((word0 >> 12u) & 1u) != 0;
inst.glc = ((word0 >> 14u) & 1u) != 0;
inst.slc = ((word1 >> 22u) & 1u) != 0;
inst.family = Family::MUBUF;
inst.opcode_id = opcode;
const auto* info =
LookupMemoryOpcode(MUBUF_OPS, static_cast<uint32_t>(std::size(MUBUF_OPS)), opcode);
ApplyMemoryInfo(inst, info);
SetRawWords(inst, code, word_index, 2);
if (inst.opcode == Opcode::Unsupported) {
MarkMemoryUnsupported(inst, Family::MUBUF, opcode, "MUBUF opcode is not implemented");
}
DecodeVectorGpr(vdata, inst.dst, nullptr);
DecodeVectorGpr(vaddr, inst.src0, nullptr);
DecodeScalarSource(srsrc * 4u, pc, inst.src1, nullptr);
DecodeScalarSource(soffset, pc, inst.src2, nullptr);
inst.src_count = 3;
return true;
}
bool DecodeMtbuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
Instruction& inst, std::string* error) {
if (word_index + 1u >= code.size()) {
if (error != nullptr) {
*error = fmt::format("truncated MTBUF instruction at pc 0x{:08x}", pc);
}
return false;
}
const uint32_t word0 = code[word_index];
const uint32_t word1 = code[word_index + 1u];
const uint32_t opcode = ((word0 >> 16u) & 0x7u) | (((word1 >> 21u) & 1u) << 3u);
const uint32_t dfmt = (word0 >> 19u) & 0xfu;
const uint32_t nfmt = (word0 >> 23u) & 0x7u;
const uint32_t vdata = (word1 >> 8u) & 0xffu;
const uint32_t vaddr = word1 & 0xffu;
const uint32_t srsrc = (word1 >> 16u) & 0x1fu;
const uint32_t soffset = (word1 >> 24u) & 0xffu;
inst.pc = pc;
inst.word = word0;
inst.word_count = 2;
inst.offset = word0 & 0xfffu;
inst.idxen = ((word0 >> 13u) & 1u) != 0;
inst.offen = ((word0 >> 12u) & 1u) != 0;
inst.glc = ((word0 >> 14u) & 1u) != 0;
inst.slc = ((word1 >> 22u) & 1u) != 0;
inst.family = Family::MTBUF;
inst.opcode_id = opcode;
inst.data_format = dfmt;
inst.number_format = nfmt;
const auto* info =
LookupMemoryOpcode(MTBUF_OPS, static_cast<uint32_t>(std::size(MTBUF_OPS)), opcode);
ApplyMemoryInfo(inst, info);
SetRawWords(inst, code, word_index, 2);
if (inst.opcode == Opcode::Unsupported) {
MarkMemoryUnsupported(inst, Family::MTBUF, opcode, "MTBUF opcode is not implemented");
}
DecodeVectorGpr(vdata, inst.dst, nullptr);
DecodeVectorGpr(vaddr, inst.src0, nullptr);
DecodeScalarSource(srsrc * 4u, pc, inst.src1, nullptr);
DecodeScalarSource(soffset, pc, inst.src2, nullptr);
inst.src_count = 3;
return true;
}
bool DecodeFlat(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
std::string* error) {
if (word_index + 1u >= code.size()) {
if (error != nullptr) {
*error = fmt::format("truncated FLAT instruction at pc 0x{:08x}", pc);
}
return false;
}
const uint32_t word0 = code[word_index];
const uint32_t word1 = code[word_index + 1u];
const uint32_t offset = word0 & 0xfffu;
const uint32_t dlc = (word0 >> 12u) & 1u;
const uint32_t lds = (word0 >> 13u) & 1u;
const uint32_t seg = (word0 >> 14u) & 0x3u;
const uint32_t opcode = (word0 >> 18u) & 0x7fu;
const uint32_t vdst = (word1 >> 24u) & 0xffu;
const uint32_t saddr = (word1 >> 16u) & 0x7fu;
const uint32_t data = (word1 >> 8u) & 0xffu;
const uint32_t addr = word1 & 0xffu;
inst.pc = pc;
inst.word = word0;
inst.word_count = 2;
inst.offset = seg == 0u ? (offset & 0x7ffu) : SignExtendU32(offset, 12u);
inst.glc = ((word0 >> 16u) & 1u) != 0;
inst.slc = ((word0 >> 17u) & 1u) != 0;
inst.family = Family::FLAT;
inst.opcode_id = opcode;
inst.memory_segment = seg;
const auto* info =
LookupMemoryOpcode(FLAT_OPS, static_cast<uint32_t>(std::size(FLAT_OPS)), opcode);
ApplyMemoryInfo(inst, info);
SetRawWords(inst, code, word_index, 2);
if (dlc != 0 || lds != 0 || inst.glc || inst.slc || seg == 3u) {
SetUnsupported(inst, Family::FLAT, opcode, "FLAT modifiers or segment are not implemented");
return true;
}
if (inst.opcode == Opcode::Unsupported) {
MarkMemoryUnsupported(inst, Family::FLAT, opcode, "FLAT opcode is not implemented");
return true;
}
DecodeVectorGpr(IsFlatStoreOpcode(inst.opcode) ? data : vdst, inst.dst, nullptr);
DecodeVectorGpr(addr, inst.src0, nullptr);
inst.src_count = 1;
if (seg == 0u || saddr == 0x7du || saddr == 0x7fu) {
DecodeVectorGpr(addr + 1u, inst.src1, nullptr);
inst.src_count = 2;
} else {
DecodeScalarSource(saddr, pc, inst.src1, nullptr);
inst.src_count = 2;
}
return true;
}
bool DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
std::string* error) {
if (word_index + 1u >= code.size()) {
if (error != nullptr) {
*error = fmt::format("truncated DS instruction at pc 0x{:08x}", pc);
}
return false;
}
const uint32_t word0 = code[word_index];
const uint32_t word1 = code[word_index + 1u];
const uint32_t opcode = (word0 >> 18u) & 0xffu;
const uint32_t offset0 = word0 & 0xffu;
const uint32_t offset1 = (word0 >> 8u) & 0xffu;
const uint32_t vdst = (word1 >> 24u) & 0xffu;
const uint32_t data1 = (word1 >> 16u) & 0xffu;
const uint32_t data0 = (word1 >> 8u) & 0xffu;
const uint32_t addr = word1 & 0xffu;
inst.pc = pc;
inst.word = word0;
inst.word_count = 2;
inst.offset = offset0 | (offset1 << 8u);
inst.gds = ((word0 >> 17u) & 1u) != 0u;
inst.family = Family::DS;
inst.opcode_id = opcode;
const auto* info = LookupMemoryOpcode(DS_OPS, static_cast<uint32_t>(std::size(DS_OPS)), opcode);
ApplyMemoryInfo(inst, info);
SetRawWords(inst, code, word_index, 2);
if (inst.opcode == Opcode::Unsupported) {
MarkMemoryUnsupported(inst, Family::DS, opcode, "DS opcode is not implemented");
}
if (inst.opcode == Opcode::DsSwizzleB32 && inst.offset >= 0xe000u) {
SetUnsupported(inst, Family::DS, opcode, "DS swizzle FFT mode is not implemented");
}
if (inst.gds &&
(inst.opcode == Opcode::DsSwizzleB32 || inst.opcode == Opcode::DsWriteAddtidB32 ||
inst.opcode == Opcode::DsReadAddtidB32)) {
SetUnsupported(inst, Family::DS, opcode, "DS swizzle/addtid is available only for LDS");
}
if (inst.opcode == Opcode::DsWriteAddtidB32 && data1 != 0u) {
SetUnsupported(inst, Family::DS, opcode,
"DS write addtid data1 operand is not implemented");
}
if (inst.opcode == Opcode::DsReadAddtidB32 && (data0 != 0u || data1 != 0u)) {
SetUnsupported(inst, Family::DS, opcode,
"DS read addtid data operands are not implemented");
}
if (inst.opcode == Opcode::DsWrite2B32 || opcode == 0x37u) {
inst.offset = offset0 * 4u;
inst.secondary_offset = offset1 * 4u;
} else if (inst.opcode == Opcode::DsWrite2St64B32 || opcode == 0x38u) {
inst.offset = offset0 * 256u;
inst.secondary_offset = offset1 * 256u;
} else if (inst.opcode == Opcode::DsWrite2B64 || inst.opcode == Opcode::DsRead2B64) {
inst.offset = offset0 * 8u;
inst.secondary_offset = offset1 * 8u;
} else if (inst.opcode == Opcode::DsWrite2St64B64 || inst.opcode == Opcode::DsRead2St64B64) {
inst.offset = offset0 * 512u;
inst.secondary_offset = offset1 * 512u;
}
DecodeVectorGpr(vdst, inst.dst, nullptr);
DecodeVectorGpr(addr, inst.src0, nullptr);
DecodeVectorGpr(data0, inst.src1, nullptr);
DecodeVectorGpr(data1, inst.src2, nullptr);
inst.src_count = DsSourceCount(inst.opcode);
return true;
}
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
@@ -1,21 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_MEMORYOPS_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_MEMORYOPS_H_
#include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
namespace Libs::Graphics::ShaderRecompiler::Decoder {
bool DecodeSmem(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
std::string* error);
bool DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
Instruction& inst, std::string* error);
bool DecodeMtbuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
Instruction& inst, std::string* error);
bool DecodeFlat(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
std::string* error);
bool DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
std::string* error);
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_MEMORYOPS_H_ */
@@ -1,45 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_DECOMPILER_OPCODETABLE_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_DECOMPILER_OPCODETABLE_H_
#include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
#include <cstddef>
namespace Libs::Graphics::ShaderRecompiler::Decoder::Detail {
struct OpcodeMap {
uint32_t encoding = 0;
Opcode decoded = Opcode::Unknown;
};
template <typename Entry, size_t N>
constexpr const Entry* FindOpcode(const Entry (&table)[N], uint32_t encoding) {
for (const auto& entry: table) {
if (entry.encoding == encoding) {
return &entry;
}
}
return nullptr;
}
template <typename Entry, size_t N>
constexpr Opcode LookupOpcode(const Entry (&table)[N], uint32_t encoding) {
const auto* entry = FindOpcode(table, encoding);
return entry != nullptr ? entry->decoded : Opcode::Unsupported;
}
template <typename Entry, size_t N>
constexpr bool HasUniqueEncodings(const Entry (&table)[N]) {
for (size_t i = 0; i < N; i++) {
for (size_t j = i + 1; j < N; j++) {
if (table[i].encoding == table[j].encoding) {
return false;
}
}
}
return true;
}
} // namespace Libs::Graphics::ShaderRecompiler::Decoder::Detail
#endif // EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_DECOMPILER_OPCODETABLE_H_
@@ -1,277 +0,0 @@
#include "graphics/shader/recompiler/decompiler/ScalarAluOps.h"
#include "graphics/shader/recompiler/decompiler/OpcodeTable.h"
namespace Libs::Graphics::ShaderRecompiler::Decoder {
namespace {
using Detail::OpcodeMap;
constexpr OpcodeMap SOP2_OPS[] = {
{0x00u, Opcode::SAddU32}, {0x01u, Opcode::SSubU32}, {0x02u, Opcode::SAddI32},
{0x03u, Opcode::SSubI32}, {0x04u, Opcode::SAddcU32}, {0x05u, Opcode::SSubbU32},
{0x06u, Opcode::SMinI32}, {0x07u, Opcode::SMinU32}, {0x08u, Opcode::SMaxI32},
{0x09u, Opcode::SMaxU32}, {0x0au, Opcode::SCselectB32}, {0x0bu, Opcode::SCselectB64},
{0x0eu, Opcode::SAndB32}, {0x0fu, Opcode::SAndB64}, {0x10u, Opcode::SOrB32},
{0x11u, Opcode::SOrB64}, {0x12u, Opcode::SXorB32}, {0x13u, Opcode::SXorB64},
{0x14u, Opcode::SAndn2B32}, {0x15u, Opcode::SAndn2B64}, {0x16u, Opcode::SOrn2B32},
{0x17u, Opcode::SOrn2B64}, {0x18u, Opcode::SNandB32}, {0x19u, Opcode::SNandB64},
{0x1au, Opcode::SNorB32}, {0x1bu, Opcode::SNorB64}, {0x1cu, Opcode::SXnorB32},
{0x1du, Opcode::SXnorB64}, {0x1eu, Opcode::SLshlB32}, {0x1fu, Opcode::SLshlB64},
{0x20u, Opcode::SLshrB32}, {0x21u, Opcode::SLshrB64}, {0x22u, Opcode::SAshrI32},
{0x24u, Opcode::SBfmB32}, {0x25u, Opcode::SBfmB64}, {0x26u, Opcode::SMulI32},
{0x27u, Opcode::SBfeU32}, {0x29u, Opcode::SBfeU64}, {0x2eu, Opcode::SLshl1AddU32},
{0x2fu, Opcode::SLshl2AddU32}, {0x30u, Opcode::SLshl3AddU32}, {0x31u, Opcode::SLshl4AddU32},
{0x32u, Opcode::SPackLlB32B16}, {0x33u, Opcode::SPackLhB32B16}, {0x34u, Opcode::SPackHhB32B16},
{0x35u, Opcode::SMulHiU32},
};
constexpr OpcodeMap SOP1_OPS[] = {
{0x03u, Opcode::SMovB32},
{0x04u, Opcode::SMovB64},
{0x07u, Opcode::SNotB32},
{0x08u, Opcode::SNotB64},
{0x0au, Opcode::SWqmB64},
{0x0bu, Opcode::SBrevB32},
{0x0fu, Opcode::SBcnt1I32B32},
{0x10u, Opcode::SBcnt1I32B64},
{0x13u, Opcode::SFf1I32B32},
{0x16u, Opcode::SFlbitI32B64},
{0x1bu, Opcode::SBitset0B32},
{0x1du, Opcode::SBitset1B32},
{0x1fu, Opcode::SGetpcB64},
{0x20u, Opcode::SSetpcB64},
{0x24u, Opcode::SAndSaveexecB64},
{0x28u, Opcode::SOrn2SaveexecB64},
{0x34u, Opcode::SAbsI32},
{0x37u, Opcode::SAndn1SaveexecB64},
{0x3bu, Opcode::SBitreplicateB64B32},
{0x3cu, Opcode::SAndSaveexecB32},
{0x44u, Opcode::SAndn1SaveexecB32},
};
constexpr OpcodeMap SOPC_OPS[] = {
{0x00u, Opcode::SCmpEqI32}, {0x01u, Opcode::SCmpLgI32}, {0x02u, Opcode::SCmpGtI32},
{0x03u, Opcode::SCmpGeI32}, {0x04u, Opcode::SCmpLtI32}, {0x05u, Opcode::SCmpLeI32},
{0x06u, Opcode::SCmpEqU32}, {0x07u, Opcode::SCmpLgU32}, {0x08u, Opcode::SCmpGtU32},
{0x09u, Opcode::SCmpGeU32}, {0x0au, Opcode::SCmpLtU32}, {0x0bu, Opcode::SCmpLeU32},
{0x0cu, Opcode::SBitcmp0B32}, {0x0du, Opcode::SBitcmp1B32}, {0x12u, Opcode::SCmpEqU64},
{0x13u, Opcode::SCmpLgU64},
};
constexpr OpcodeMap SOPK_OPS[] = {
{0x00u, Opcode::SMovkI32}, {0x03u, Opcode::SCmpEqI32}, {0x04u, Opcode::SCmpLgI32},
{0x05u, Opcode::SCmpGtI32}, {0x06u, Opcode::SCmpGeI32}, {0x07u, Opcode::SCmpLtI32},
{0x08u, Opcode::SCmpLeI32}, {0x09u, Opcode::SCmpEqU32}, {0x0au, Opcode::SCmpLgU32},
{0x0bu, Opcode::SCmpGtU32}, {0x0cu, Opcode::SCmpGeU32}, {0x0du, Opcode::SCmpLtU32},
{0x0eu, Opcode::SCmpLeU32}, {0x0fu, Opcode::SAddI32}, {0x10u, Opcode::SMulkI32},
{0x13u, Opcode::SSetregB32}, {0x17u, Opcode::SWaitcnt}, {0x18u, Opcode::SWaitcnt},
{0x19u, Opcode::SWaitcnt}, {0x1au, Opcode::SWaitcnt},
};
constexpr OpcodeMap SOPP_OPS[] = {
{0x00u, Opcode::SNop}, {0x01u, Opcode::SEndpgm}, {0x02u, Opcode::SBranch},
{0x04u, Opcode::SCbranchScc0}, {0x05u, Opcode::SCbranchScc1}, {0x06u, Opcode::SCbranchVccz},
{0x07u, Opcode::SCbranchVccnz}, {0x08u, Opcode::SCbranchExecz}, {0x09u, Opcode::SCbranchExecnz},
{0x0au, Opcode::SBarrier}, {0x0cu, Opcode::SWaitcnt}, {0x0eu, Opcode::SSleep},
{0x10u, Opcode::SSendmsg}, {0x16u, Opcode::STtraceData}, {0x20u, Opcode::SInstPrefetch},
};
static_assert(Detail::HasUniqueEncodings(SOP1_OPS));
static_assert(Detail::HasUniqueEncodings(SOP2_OPS));
static_assert(Detail::HasUniqueEncodings(SOPK_OPS));
static_assert(Detail::HasUniqueEncodings(SOPC_OPS));
static_assert(Detail::HasUniqueEncodings(SOPP_OPS));
bool DecodeBinarySources(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
Instruction& inst, uint32_t ssrc0, uint32_t ssrc1, std::string* error) {
if (!DecodeScalarSource(ssrc0, pc, inst.src0, error) ||
!DecodeScalarSource(ssrc1, pc, inst.src1, error)) {
return false;
}
inst.src_count = 2;
return ReadLiteralOperands(code, word_index, inst, error);
}
} // namespace
bool DecodeSop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
std::string* error) {
const uint32_t word = code[word_index];
const uint32_t opcode = (word >> 8u) & 0xffu;
const uint32_t ssrc0 = word & 0xffu;
const uint32_t sdst = (word >> 16u) & 0x7fu;
inst.pc = pc;
inst.word = word;
inst.family = Family::SOP1;
inst.opcode_id = opcode;
inst.opcode = Detail::LookupOpcode(SOP1_OPS, opcode);
SetRawWords(inst, code, word_index, 1);
if (inst.opcode == Opcode::Unsupported) {
SetUnsupported(inst, Family::SOP1, opcode, "SOP1 opcode is not implemented");
return true;
}
switch (inst.opcode) {
case Opcode::SGetpcB64:
inst.src_count = 0;
return DecodeScalarDestination(sdst, pc, inst.dst, error);
case Opcode::SSetpcB64:
inst.src_count = 1;
inst.dst.kind = OperandKind::Null;
if (!DecodeScalarSource(ssrc0, pc, inst.src0, error)) {
return false;
}
return ReadLiteralOperands(code, word_index, inst, error);
default: break;
}
if (!DecodeScalarSource(ssrc0, pc, inst.src0, error) ||
!DecodeScalarDestination(sdst, pc, inst.dst, error)) {
return false;
}
inst.src_count = 1;
return ReadLiteralOperands(code, word_index, inst, error);
}
bool DecodeSop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
std::string* error) {
const uint32_t word = code[word_index];
const uint32_t opcode = (word >> 23u) & 0x7fu;
const uint32_t ssrc1 = (word >> 8u) & 0xffu;
const uint32_t ssrc0 = word & 0xffu;
const uint32_t sdst = (word >> 16u) & 0x7fu;
inst.pc = pc;
inst.word = word;
inst.family = Family::SOP2;
inst.opcode_id = opcode;
inst.opcode = Detail::LookupOpcode(SOP2_OPS, opcode);
SetRawWords(inst, code, word_index, 1);
if (inst.opcode == Opcode::Unsupported) {
SetUnsupported(inst, Family::SOP2, opcode, "SOP2 opcode is not implemented");
return true;
}
if (!DecodeScalarDestination(sdst, pc, inst.dst, error)) {
return false;
}
return DecodeBinarySources(pc, code, word_index, inst, ssrc0, ssrc1, error);
}
bool DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
std::string* error) {
const uint32_t word = code[word_index];
const uint32_t opcode = (word >> 23u) & 0x1fu;
const uint32_t sdst = (word >> 16u) & 0x7fu;
const auto imm = static_cast<int16_t>(word & 0xffffu);
(void)error;
inst.pc = pc;
inst.word = word;
inst.family = Family::SOPK;
inst.opcode_id = opcode;
inst.opcode = Detail::LookupOpcode(SOPK_OPS, opcode);
inst.src0.kind = OperandKind::IntegerInlineConstant;
inst.src0.signed_val = imm;
inst.src0.value = static_cast<uint32_t>(imm);
inst.src_count = 1;
SetRawWords(inst, code, word_index, 1);
if (inst.opcode == Opcode::Unsupported) {
SetUnsupported(inst, Family::SOPK, opcode, "SOPK opcode is not implemented");
return true;
}
switch (inst.opcode) {
case Opcode::SMovkI32: return DecodeScalarDestination(sdst, pc, inst.dst, error);
case Opcode::SWaitcnt: {
const uint32_t waitcnt = word & 0xffffu;
inst.dst.kind = OperandKind::Null;
inst.src0.signed_val = static_cast<int32_t>(waitcnt);
inst.src0.value = waitcnt;
inst.src_count = 1;
return true;
}
case Opcode::SSetregB32:
inst.dst.kind = OperandKind::Null;
inst.src1.kind = OperandKind::LiteralConstant;
inst.src1.value = word & 0xffffu;
inst.src1.signed_val = static_cast<int32_t>(imm);
inst.src_count = 2;
return DecodeScalarSource(sdst, pc, inst.src0, error);
default: break;
}
inst.src1 = inst.src0;
if (!DecodeScalarSource(sdst, pc, inst.src0, error)) {
return false;
}
if (inst.opcode == Opcode::SAddI32 || inst.opcode == Opcode::SMulkI32) {
inst.src_count = 2;
return DecodeScalarDestination(sdst, pc, inst.dst, error);
}
inst.dst.kind = OperandKind::Scc;
inst.src_count = 2;
return true;
}
bool DecodeSopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
std::string* error) {
const uint32_t word = code[word_index];
const uint32_t ssrc1 = (word >> 8u) & 0xffu;
const uint32_t ssrc0 = word & 0xffu;
const uint32_t opcode = (word >> 16u) & 0x7fu;
inst.pc = pc;
inst.word = word;
inst.family = Family::SOPC;
inst.opcode_id = opcode;
inst.opcode = Detail::LookupOpcode(SOPC_OPS, opcode);
inst.dst.kind = OperandKind::Scc;
SetRawWords(inst, code, word_index, 1);
if (inst.opcode == Opcode::Unsupported) {
SetUnsupported(inst, Family::SOPC, opcode, "SOPC opcode is not implemented");
return true;
}
return DecodeBinarySources(pc, code, word_index, inst, ssrc0, ssrc1, error);
}
bool DecodeSopp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
std::string* error) {
const uint32_t word = code[word_index];
const uint32_t opcode = (word >> 16u) & 0x7fu;
const uint32_t simm = word & 0xffffu;
(void)error;
inst.pc = pc;
inst.word = word;
inst.family = Family::SOPP;
inst.opcode_id = opcode;
inst.opcode = Detail::LookupOpcode(SOPP_OPS, opcode);
inst.src0.kind = OperandKind::LiteralConstant;
inst.src0.value = simm;
inst.src0.signed_val = static_cast<int16_t>(simm);
inst.src_count = (inst.opcode == Opcode::SNop || inst.opcode == Opcode::SWaitcnt ||
inst.opcode == Opcode::SSleep || inst.opcode == Opcode::SSendmsg ||
inst.opcode == Opcode::STtraceData || inst.opcode == Opcode::SInstPrefetch)
? 1
: 0;
inst.branch_offset = static_cast<int32_t>(static_cast<int16_t>(simm)) * 4;
inst.branch_target = pc + 4u + static_cast<uint32_t>(inst.branch_offset);
SetRawWords(inst, code, word_index, 1);
if (inst.opcode == Opcode::Unsupported) {
SetUnsupported(inst, Family::SOPP, opcode, "SOPP control-flow opcode is not implemented");
}
return true;
}
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
File diff suppressed because it is too large. Load diff
@@ -1,686 +0,0 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERDECODER_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERDECODER_H_
#include "common/common.h"
#include "common/stringUtils.h"
#include <span>
#include <vector>
namespace Libs::Graphics::ShaderRecompiler::Decoder {
enum class Family {
Unknown,
SOP1,
SOP2,
SOPK,
SOPC,
SOPP,
VOP1,
VOP2,
VOP3,
VOP3P,
VOPC,
VINTRP,
SMEM,
MUBUF,
MTBUF,
FLAT,
DS,
MIMG,
EXP
};
enum class Opcode {
Unknown,
Unsupported,
SMovB32,
SMovB64,
SMovkI32,
SAbsI32,
SBrevB32,
SBcnt1I32B32,
SBcnt1I32B64,
SFf1I32B32,
SFlbitI32B64,
SBitreplicateB64B32,
SGetpcB64,
SSetpcB64,
SAndSaveexecB32,
SAndn1SaveexecB32,
SAndSaveexecB64,
SOrn2SaveexecB64,
SAndn1SaveexecB64,
SNotB32,
SNotB64,
SWqmB64,
SAddU32,
SAddcU32,
SSubbU32,
SSubU32,
SAddI32,
SSubI32,
SBitcmp0B32,
SBitcmp1B32,
SBitset0B32,
SBitset1B32,
SMinI32,
SMaxI32,
SMinU32,
SMaxU32,
SAndB32,
SAndB64,
SAndn2B32,
SAndn2B64,
SOrB32,
SOrB64,
SOrn2B32,
SOrn2B64,
SXorB32,
SXorB64,
SNandB32,
SNandB64,
SNorB32,
SNorB64,
SXnorB32,
SXnorB64,
SLshlB32,
SLshlB64,
SLshl1AddU32,
SLshl2AddU32,
SLshl3AddU32,
SLshl4AddU32,
SLshrB32,
SLshrB64,
SAshrI32,
SMulI32,
SMulHiU32,
SMulkI32,
SBfeU32,
SBfeU64,
SBfmB32,
SBfmB64,
SCselectB32,
SCselectB64,
SPackLlB32B16,
SPackLhB32B16,
SPackHhB32B16,
SCmpEqI32,
SCmpLgI32,
SCmpGtI32,
SCmpGeI32,
SCmpLtI32,
SCmpLeI32,
SCmpEqU32,
SCmpLgU32,
SCmpGtU32,
SCmpGeU32,
SCmpLtU32,
SCmpLeU32,
SCmpEqU64,
SCmpLgU64,
VNop,
VMovB32,
VMovreldB32,
VMovrelsB32,
VReadfirstlaneB32,
VReadlaneB32,
VWritelaneB32,
VPermlane16B32,
VPermlanex16B32,
VCubeidF32,
VCubescF32,
VCubetcF32,
VCubemaF32,
VCndmaskB32,
VDot2cF32F16,
VCvtF32I32,
VCvtF32U32,
VCvtU32F32,
VCvtI32F32,
VCvtF16F32,
VCvtF32F16,
VCvtF16U16,
VCvtU16F16,
VCvtF16I16,
VCvtI16F16,
VCvtRpiI32F32,
VCvtFlrI32F32,
VCvtOffF32I4,
VCvtF32Ubyte0,
VCvtF32Ubyte1,
VCvtF32Ubyte2,
VCvtF32Ubyte3,
VRcpF32,
VFractF32,
VTruncF32,
VCeilF32,
VRndneF32,
VFloorF32,
VExpF32,
VLogF32,
VRsqF32,
VSqrtF32,
VRcpF16,
VSqrtF16,
VRsqF16,
VLogF16,
VExpF16,
VFloorF16,
VCeilF16,
VTruncF16,
VRndneF16,
VSinF32,
VCosF32,
VNotB32,
VBfrevB32,
VFfbhU32,
VFfblB32,
VAddF32,
VSubF32,
VSubrevF32,
VMulF32,
VMacF32,
VMadmkF32,
VMadakF32,
VMinF32,
VMaxF32,
VAddF16,
VSubF16,
VSubrevF16,
VMulF16,
VFmacF16,
VFmamkF16,
VFmaakF16,
VMinI32,
VMaxI32,
VMulI32I24,
VMulU32U24,
VMulLoU32,
VMulHiU32,
VMulLoI32,
VMulHiI32,
VCvtPkrtzF16F32,
VCvtPkU8F32,
VMadF32,
VMadI32I24,
VMadU32U24,
VMadU64U32,
VFmaF32,
VFmaF16,
VPackB32F16,
VBfeU32,
VBfeI32,
VBfiB32,
VAlignbitB32,
VMin3F32,
VMin3I32,
VMin3U32,
VMin3F16,
VMax3F32,
VMax3I32,
VMax3U32,
VMax3F16,
VMed3F32,
VMed3I32,
VMed3U32,
VMed3F16,
VSadU32,
VAdd3U32,
VLshlAddU32,
VAddLshlU32,
VXadU32,
VLshlOrB32,
VAndOrB32,
VOr3B32,
VXor3B32,
VAddI32,
VSubI32,
VSubrevI32,
VBfmB32,
VLdexpF32,
VCvtPknormI16F32,
VCvtPknormU16F32,
VCvtPkU16U32,
VPkMadI16,
VPkMulLoU16,
VPkAddI16,
VPkSubI16,
VPkLshlrevB16,
VPkLshrrevB16,
VPkAshrrevI16,
VPkMaxI16,
VPkMinI16,
VPkMadU16,
VPkAddF16,
VPkMulF16,
VPkMinF16,
VPkMaxF16,
VPkFmaF16,
VPkFmacF16,
VPkAddU16,
VPkSubU16,
VPkMaxU16,
VPkMinU16,
VMadMixloF16,
VMadMixhiF16,
VAddNcU32,
VAddcU32,
VSubNcU32,
VSubrevNcU32,
VAddNcU16,
VSubNcU16,
VMaxU16,
VMaxI16,
VMinU16,
VMinI16,
VAddNcI16,
VSubNcI16,
VAndB32,
VOrB32,
VXorB32,
VXnorB32,
VLshlB32,
VLshlrevB32,
VLshrB32,
VLshrrevB32,
VAshrI32,
VAshrrevI32,
VLshlrevB16,
VLshrrevB16,
VAshrrevI16,
VBcntU32B32,
VMbcntLoU32B32,
VMbcntHiU32B32,
VMinU32,
VMaxU32,
VMaxF16,
VMinF16,
VCmpFF32,
VCmpLtF32,
VCmpEqF32,
VCmpLeF32,
VCmpGtF32,
VCmpLgF32,
VCmpGeF32,
VCmpOF32,
VCmpUF32,
VCmpNgeF32,
VCmpNlgF32,
VCmpNgtF32,
VCmpNleF32,
VCmpNeqF32,
VCmpNltF32,
VCmpTruF32,
VCmpxLtF32,
VCmpxEqF32,
VCmpxLeF32,
VCmpxGtF32,
VCmpxLgF32,
VCmpxGeF32,
VCmpxNgeF32,
VCmpxNlgF32,
VCmpxNgtF32,
VCmpxNleF32,
VCmpxNeqF32,
VCmpxNltF32,
VCmpFI32,
VCmpLtI32,
VCmpEqI32,
VCmpLeI32,
VCmpGtI32,
VCmpNeI32,
VCmpGeI32,
VCmpTI32,
VCmpClassF32,
VCmpLtI16,
VCmpEqI16,
VCmpLeI16,
VCmpGtI16,
VCmpNeI16,
VCmpGeI16,
VCmpLtF16,
VCmpEqF16,
VCmpLeF16,
VCmpGtF16,
VCmpLgF16,
VCmpGeF16,
VCmpNeqF16,
VCmpxLtF16,
VCmpxEqF16,
VCmpxLeF16,
VCmpxGtF16,
VCmpxGeF16,
VCmpxNeqF16,
VCmpxNltF16,
VCmpxLtI32,
VCmpxEqI32,
VCmpxLeI32,
VCmpxGtI32,
VCmpxNeI32,
VCmpxGeI32,
VCmpLtU16,
VCmpEqU16,
VCmpLeU16,
VCmpGtU16,
VCmpNeU16,
VCmpGeU16,
VCmpFU32,
VCmpLtU32,
VCmpEqU32,
VCmpLeU32,
VCmpGtU32,
VCmpNeU32,
VCmpGeU32,
VCmpTU32,
VCmpEqI64,
VCmpNeU64,
VCmpxLtU32,
VCmpxEqU32,
VCmpxLeU32,
VCmpxGtU32,
VCmpxNeU32,
VCmpxGeU32,
SLoadDword,
SLoadDwordx2,
SLoadDwordx4,
SLoadDwordx8,
SLoadDwordx16,
SBufferLoadDword,
SBufferLoadDwordx2,
SBufferLoadDwordx4,
SBufferLoadDwordx8,
SBufferLoadDwordx16,
BufferLoadFormatX,
BufferLoadFormatXy,
BufferLoadFormatXyz,
BufferLoadFormatXyzw,
BufferStoreFormatX,
BufferStoreFormatXy,
BufferStoreFormatXyz,
BufferStoreFormatXyzw,
BufferLoadUbyte,
BufferLoadSbyte,
BufferLoadUshort,
BufferLoadSshort,
BufferLoadDword,
BufferLoadDwordx2,
BufferLoadDwordx3,
BufferLoadDwordx4,
BufferStoreByte,
BufferStoreShort,
BufferStoreDword,
BufferStoreDwordx2,
BufferStoreDwordx3,
BufferStoreDwordx4,
TBufferLoadFormatX,
TBufferLoadFormatXy,
TBufferLoadFormatXyz,
TBufferLoadFormatXyzw,
TBufferStoreFormatX,
TBufferStoreFormatXy,
TBufferStoreFormatXyz,
TBufferStoreFormatXyzw,
BufferAtomicSwap,
BufferAtomicAdd,
BufferAtomicSub,
BufferAtomicSMin,
BufferAtomicUMin,
BufferAtomicSMax,
BufferAtomicUMax,
BufferAtomicAnd,
BufferAtomicOr,
BufferAtomicXor,
BufferAtomicFMin,
BufferAtomicFMax,
FlatLoadUbyte,
FlatLoadSbyte,
FlatLoadUshort,
FlatLoadSshort,
FlatLoadDword,
FlatLoadDwordx2,
FlatLoadDwordx3,
FlatLoadDwordx4,
FlatStoreByte,
FlatStoreShort,
FlatStoreDword,
FlatStoreDwordx2,
FlatStoreDwordx3,
FlatStoreDwordx4,
DsAddU32,
DsAddRtnU32,
DsSubU32,
DsSubRtnU32,
DsMinI32,
DsMinRtnI32,
DsMaxI32,
DsMaxRtnI32,
DsMinU32,
DsMinRtnU32,
DsMaxU32,
DsMaxRtnU32,
DsAndB32,
DsAndRtnB32,
DsOrB32,
DsOrRtnB32,
DsXorB32,
DsXorRtnB32,
DsWrxchgRtnB32,
DsMinF32,
DsMaxF32,
DsSwizzleB32,
DsConsume,
DsAppend,
DsReadSbyte,
DsReadUbyte,
DsReadSshort,
DsReadUshort,
DsRead2B32,
DsReadB32,
DsReadB64,
DsRead2B64,
DsRead2St64B64,
DsReadB96,
DsReadB128,
DsWriteByte,
DsWriteShort,
DsWrite2B32,
DsWrite2St64B32,
DsWrite2B64,
DsWrite2St64B64,
DsWriteB32,
DsWriteB64,
DsWriteB96,
DsWriteB128,
DsWriteAddtidB32,
DsReadAddtidB32,
ImageGetResinfo,
ImageGetLod,
ImageLoad,
ImageLoadMip,
ImageStore,
ImageStoreMip,
ImageAtomicAdd,
ImageAtomicUMin,
ImageAtomicUMax,
ImageAtomicAnd,
ImageAtomicOr,
ImageAtomicXor,
ImageSample,
ImageGather4Lz,
ImageGather4C,
ImageGather4CLz,
ImageGather4LzO,
ImageGather4CO,
ImageGather4CLzO,
ImageGather4H,
VInterpP1F32,
VInterpP2F32,
VInterpMovF32,
SNop,
SWaitcnt,
SBarrier,
SBranch,
SCbranchScc0,
SCbranchScc1,
SCbranchVccz,
SCbranchVccnz,
SCbranchExecz,
SCbranchExecnz,
SSendmsg,
SSetregB32,
SSleep,
STtraceData,
SInstPrefetch,
SEndpgm,
Exp
};
enum class OperandKind {
Unknown,
LiteralConstant,
IntegerInlineConstant,
FloatInlineConstant,
Sgpr,
VccLo,
VccHi,
VccZ,
ExecLo,
ExecHi,
ExecZ,
Scc,
M0,
PopsExitingWaveId,
Null,
Vgpr,
};
enum ImageSampleFlag : uint32_t {
ImageSampleFlagLod = 1u << 0u,
ImageSampleFlagBias = 1u << 1u,
ImageSampleFlagDerivative = 1u << 2u,
ImageSampleFlagCompare = 1u << 3u,
ImageSampleFlagOffset = 1u << 4u,
ImageSampleFlagLevelZero = 1u << 5u,
ImageSampleFlagLodClamp = 1u << 6u,
ImageSampleFlagA16 = 1u << 7u,
ImageSampleFlagCd = 1u << 8u,
ImageSampleFlagGatherHorizontal = 1u << 9u,
};
enum class ImageDimension : uint32_t {
Unknown,
Dim1D,
Dim1DArray,
Dim2D,
Dim3D,
Dim2DArray,
Dim2DMsaa,
Dim2DMsaaArray,
};
constexpr uint32_t MaxInstructionRawWords = 5u;
constexpr uint32_t MaxImageNsaAddressComponents = 12u;
struct Operand {
OperandKind kind = OperandKind::Unknown;
uint32_t value = 0;
int32_t signed_val = 0;
float float_val = 0.0f;
uint32_t reg = 0;
uint32_t sdwa_sel = 6;
uint32_t sdwa_dst_unused = 0;
uint32_t omod = 0;
uint32_t dpp_ctrl = 0;
uint32_t dpp_row_mask = 0xf;
uint32_t dpp_bank_mask = 0xf;
bool sdwa_sext = false;
bool dpp_fetch_inactive = false;
bool dpp_bound_ctrl = false;
bool op_sel = false;
bool op_sel_hi = false;
bool negate = false;
bool negate_hi = false;
bool absolute = false;
bool clamp = false;
bool dpp = false;
};
struct Instruction {
uint32_t pc = 0;
uint32_t word = 0;
uint32_t word_count = 1;
uint32_t raw[MaxInstructionRawWords] = {};
uint32_t raw_count = 1;
Family family = Family::Unknown;
uint32_t opcode_id = 0;
Opcode opcode = Opcode::Unknown;
Operand dst;
Operand dst2;
Operand src0;
Operand src1;
Operand src2;
Operand src3;
uint32_t src_count = 0;
uint32_t offset = 0;
uint32_t secondary_offset = 0;
uint32_t dmask = 0;
uint32_t data_dwords = 1;
uint32_t data_bits = 32;
uint32_t data_format = 0;
uint32_t number_format = 0;
uint32_t image_sample_flags = 0;
ImageDimension image_dimension = ImageDimension::Unknown;
uint32_t image_address_components = 0;
uint32_t image_nsa_dwords = 0;
uint32_t image_nsa_addr[MaxImageNsaAddressComponents] = {};
uint32_t memory_segment = 0;
bool data_signed = false;
bool typed = false;
bool formatted = false;
bool gds = false;
bool glc = false;
bool slc = false;
bool idxen = false;
bool offen = false;
int32_t branch_offset = 0;
uint32_t branch_target = 0;
struct {
uint32_t target = 0;
uint32_t en = 0;
bool done = false;
bool compr = false;
bool vm = false;
} exp;
std::string unsupported_reason;
};
struct Program {
std::span<const uint32_t> code;
std::vector<Instruction> instructions;
};
bool DecodeProgram(std::span<const uint32_t> code, Program& program, std::string* error);
bool DecodeScalarSource(uint32_t code, uint32_t pc, Operand& operand, std::string* error);
bool DecodeScalarDestination(uint32_t code, uint32_t pc, Operand& operand, std::string* error);
bool DecodeVectorGpr(uint32_t reg, Operand& operand, std::string* error);
bool ReadLiteralOperands(std::span<const uint32_t> code, uint32_t word_index, Instruction& inst,
std::string* error);
void SetRawWords(Instruction& inst, std::span<const uint32_t> code, uint32_t word_index,
uint32_t word_count);
void SetUnsupported(Instruction& inst, Family family, uint32_t opcode_id, const char* reason);
std::string FamilyToString(Family family);
std::string OpcodeToString(Opcode opcode);
std::string OperandToString(const Operand& operand);
const char* ImageDimensionToString(ImageDimension dimension);
std::string InstructionToString(const Instruction& inst);
std::string ProgramToString(const Program& program);
} // namespace Libs::Graphics::ShaderRecompiler::Decoder
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_SHADERDECODER_H_ */
@@ -1,525 +0,0 @@
#include "graphics/shader/recompiler/emitter/SpirvEmitter.h"
#include "graphics/shader/recompiler/emitter/spirvEmitterInternal.h"
#include "graphics/shader/recompiler/ir/SrtWalker.h"
#include <algorithm>
#include <array>
#include <cinttypes>
#include <cstdio>
namespace Libs::Graphics::ShaderRecompiler::Spirv {
namespace {
bool Fail(std::string* error, const std::string& message) {
if (error != nullptr) {
*error = message;
}
return false;
}
bool ImageBinding(const IR::ImageResource& image, IR::DescriptorBindingKind& kind) {
using Kind = IR::DescriptorBindingKind;
using Dim = Decoder::ImageDimension;
if (image.kind == IR::ResourceKind::Image || image.kind == IR::ResourceKind::ImageUint) {
const bool integer = image.kind == IR::ResourceKind::ImageUint;
switch (image.dimension) {
case Dim::Dim1D: kind = integer ? Kind::SampledUint1D : Kind::Sampled1D; return true;
case Dim::Dim1DArray:
kind = integer ? Kind::SampledUint1DArray : Kind::Sampled1DArray;
return true;
case Dim::Dim2D: kind = integer ? Kind::SampledUint2D : Kind::Sampled2D; return true;
case Dim::Dim2DMsaa:
kind = integer ? Kind::SampledUint2DMsaa : Kind::Sampled2DMsaa;
return true;
case Dim::Dim3D: kind = integer ? Kind::SampledUint3D : Kind::Sampled3D; return true;
case Dim::Dim2DArray:
kind = integer ? Kind::SampledUint2DArray : Kind::Sampled2DArray;
return true;
case Dim::Dim2DMsaaArray:
kind = integer ? Kind::SampledUint2DMsaaArray : Kind::Sampled2DMsaaArray;
return true;
case Dim::Unknown: return false;
}
}
const bool uint_image = image.kind == IR::ResourceKind::StorageImageUint;
if (image.kind != IR::ResourceKind::StorageImage && !uint_image) {
return false;
}
switch (image.dimension) {
case Dim::Dim1D: kind = uint_image ? Kind::StorageUint1D : Kind::Storage1D; return true;
case Dim::Dim1DArray:
kind = uint_image ? Kind::StorageUint1DArray : Kind::Storage1DArray;
return true;
case Dim::Dim2D: kind = uint_image ? Kind::StorageUint2D : Kind::Storage2D; return true;
case Dim::Dim3D: kind = uint_image ? Kind::StorageUint3D : Kind::Storage3D; return true;
case Dim::Dim2DArray:
kind = uint_image ? Kind::StorageUint2DArray : Kind::Storage2DArray;
return true;
case Dim::Dim2DMsaa:
case Dim::Dim2DMsaaArray: return false;
case Dim::Unknown: return false;
}
return false;
}
bool NeedsSampler(IR::Opcode op) {
return op == IR::Opcode::ImageSample || op == IR::Opcode::ImageGather4 ||
op == IR::Opcode::ImageGetLod;
}
bool IsBufferLoad(IR::Opcode op) {
switch (op) {
case IR::Opcode::BufferLoadUbyte:
case IR::Opcode::BufferLoadSbyte:
case IR::Opcode::BufferLoadUshort:
case IR::Opcode::BufferLoadSshort:
case IR::Opcode::BufferLoadDword: return true;
default: return false;
}
}
bool IsBufferStore(IR::Opcode op) {
return op == IR::Opcode::BufferStoreByte || op == IR::Opcode::BufferStoreShort ||
op == IR::Opcode::BufferStoreDword;
}
bool IsFlatLoad(IR::Opcode op) {
return op == IR::Opcode::FlatLoadUbyte || op == IR::Opcode::FlatLoadSbyte ||
op == IR::Opcode::FlatLoadUshort || op == IR::Opcode::FlatLoadSshort ||
op == IR::Opcode::FlatLoadDword;
}
bool IsFlatStore(IR::Opcode op) {
return op == IR::Opcode::FlatStoreByte || op == IR::Opcode::FlatStoreShort ||
op == IR::Opcode::FlatStoreDword;
}
bool IsDsKind(IR::ResourceKind kind) {
return kind == IR::ResourceKind::Lds || kind == IR::ResourceKind::Gds;
}
bool IsDsRead(IR::Opcode op) {
switch (op) {
case IR::Opcode::DsReadUbyte:
case IR::Opcode::DsReadSbyte:
case IR::Opcode::DsReadUshort:
case IR::Opcode::DsReadSshort:
case IR::Opcode::DsReadB32: return true;
default: return false;
}
}
bool IsDsWrite(IR::Opcode op) {
switch (op) {
case IR::Opcode::DsWriteByte:
case IR::Opcode::DsWriteShort:
case IR::Opcode::DsWriteB32: return true;
default: return false;
}
}
bool IsAtomic(IR::Opcode op) {
switch (op) {
case IR::Opcode::AtomicSwapU32:
case IR::Opcode::AtomicAddU32:
case IR::Opcode::AtomicSubU32:
case IR::Opcode::AtomicSMinI32:
case IR::Opcode::AtomicUMinU32:
case IR::Opcode::AtomicSMaxI32:
case IR::Opcode::AtomicUMaxU32:
case IR::Opcode::AtomicAndU32:
case IR::Opcode::AtomicOrU32:
case IR::Opcode::AtomicXorU32:
case IR::Opcode::AtomicFMinF32:
case IR::Opcode::AtomicFMaxF32: return true;
default: return false;
}
}
uint32_t BufferAddressOperandCount(const IR::Instruction& inst) {
return 1u + (inst.memory.idxen ? 1u : 0u) + (inst.memory.offen ? 1u : 0u);
}
bool ValidateInstructionContract(const IR::Instruction& inst, std::string* error) {
if (static_cast<size_t>(inst.op) >= static_cast<size_t>(IR::Opcode::Count)) {
return Fail(error, "IR instruction opcode is out of range");
}
if (inst.src_count > std::size(inst.src)) {
return Fail(error, "instruction source count exceeds fixed IR storage");
}
const auto kind = inst.memory.kind;
const auto flat_kind = kind == IR::ResourceKind::Flat || kind == IR::ResourceKind::Global ||
kind == IR::ResourceKind::Scratch;
const auto buffer_address_count = BufferAddressOperandCount(inst);
if ((IsBufferLoad(inst.op) &&
(kind != IR::ResourceKind::Buffer || inst.src_count != buffer_address_count)) ||
(IsBufferStore(inst.op) &&
(kind != IR::ResourceKind::Buffer || inst.src_count != buffer_address_count + 1u)) ||
(inst.op == IR::Opcode::SLoadDword &&
(kind != IR::ResourceKind::ScalarBuffer || inst.src_count != 1)) ||
(inst.op == IR::Opcode::SBufferLoadDword &&
(kind != IR::ResourceKind::ScalarBuffer || inst.src_count != 1)) ||
(IsFlatLoad(inst.op) && (!flat_kind || inst.src_count != 2)) ||
(IsFlatStore(inst.op) && (!flat_kind || inst.src_count != 3))) {
return Fail(error, "memory instruction opcode/kind/operand contract is invalid");
}
const auto ds_kind = IsDsKind(kind);
const auto ds_resource = !ds_kind || inst.memory.resource == 0;
if ((IsDsRead(inst.op) && (!ds_kind || !ds_resource || inst.src_count != 1 ||
inst.dst.kind != IR::OperandKind::Register)) ||
(IsDsWrite(inst.op) && (!ds_kind || !ds_resource || inst.src_count != 2 ||
inst.dst.kind != IR::OperandKind::Null)) ||
((inst.op == IR::Opcode::DsAppend || inst.op == IR::Opcode::DsConsume) &&
(!ds_kind || !ds_resource || inst.src_count != 1 ||
inst.dst.kind != IR::OperandKind::Register)) ||
((inst.op == IR::Opcode::DsMinF32 || inst.op == IR::Opcode::DsMaxF32) &&
(!ds_kind || !ds_resource || inst.src_count != 3 ||
inst.dst.kind != IR::OperandKind::Null)) ||
(inst.op == IR::Opcode::DsWriteAddtidB32 &&
(kind != IR::ResourceKind::Lds || !ds_resource || inst.src_count != 2 ||
inst.dst.kind != IR::OperandKind::Null)) ||
(inst.op == IR::Opcode::DsReadAddtidB32 &&
(kind != IR::ResourceKind::Lds || !ds_resource || inst.src_count != 1 ||
inst.dst.kind != IR::OperandKind::Register)) ||
(inst.op == IR::Opcode::DsSwizzleB32 &&
(kind != IR::ResourceKind::None || inst.src_count != 2 ||
inst.dst.kind != IR::OperandKind::Register))) {
return Fail(error, "DS instruction has an invalid resource/operand contract");
}
if ((inst.op == IR::Opcode::ImageSample || inst.op == IR::Opcode::ImageGather4 ||
inst.op == IR::Opcode::ImageGetLod || inst.op == IR::Opcode::ImageLoad ||
inst.op == IR::Opcode::ImageGetResinfo) &&
((kind != IR::ResourceKind::Image && kind != IR::ResourceKind::ImageUint) ||
inst.src_count != 1)) {
return Fail(error, "sampled image instruction has an invalid resource class");
}
if (inst.op == IR::Opcode::ImageGather4 &&
(inst.memory.image_dimension == Decoder::ImageDimension::Dim1D ||
inst.memory.image_dimension == Decoder::ImageDimension::Dim1DArray)) {
return Fail(error, "1D image gather is not supported by SPIR-V");
}
if (inst.op == IR::Opcode::ImageStore &&
((kind != IR::ResourceKind::StorageImage && kind != IR::ResourceKind::StorageImageUint) ||
inst.src_count != 2)) {
return Fail(error, "storage image instruction has an invalid resource class");
}
const auto float_atomic =
inst.op == IR::Opcode::AtomicFMinF32 || inst.op == IR::Opcode::AtomicFMaxF32;
if (float_atomic &&
(kind != IR::ResourceKind::Buffer || inst.src_count != buffer_address_count + 1u)) {
return Fail(error, "floating-point atomic instruction must target a buffer");
}
if (IsAtomic(inst.op) &&
!((kind == IR::ResourceKind::Buffer && inst.src_count == buffer_address_count + 1u) ||
((kind == IR::ResourceKind::Lds || kind == IR::ResourceKind::Gds) &&
inst.memory.resource == 0 && inst.src_count == 2 &&
(inst.dst.kind == IR::OperandKind::Null ||
inst.dst.kind == IR::OperandKind::Register)) ||
(kind == IR::ResourceKind::StorageImageUint && inst.src_count == 2))) {
return Fail(error, "atomic instruction has an invalid resource/operand contract");
}
return true;
}
bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
using Kind = IR::DescriptorBindingKind;
constexpr auto KindCount = static_cast<size_t>(Kind::Count);
std::array<std::vector<uint32_t>, KindCount> expected;
std::array<bool, KindCount> present {};
const auto Dense = [](size_t size) {
std::vector<uint32_t> values(size);
for (uint32_t i = 0; i < values.size(); i++) {
values[i] = i;
}
return values;
};
auto Expect = [&](Kind kind, std::vector<uint32_t> resources = {}) {
const auto index = static_cast<size_t>(kind);
present[index] = true;
expected[index] = std::move(resources);
};
if (!program.info.buffers.empty()) {
Expect(Kind::Buffers, Dense(program.info.buffers.size()));
}
for (uint32_t i = 0; i < program.info.images.size(); i++) {
Kind kind;
if (!ImageBinding(program.info.images[i], kind)) {
return Fail(error, "native shader plan has an invalid image class");
}
present[static_cast<size_t>(kind)] = true;
expected[static_cast<size_t>(kind)].push_back(i);
}
if (!program.info.samplers.empty()) {
Expect(Kind::Samplers, Dense(program.info.samplers.size()));
}
const auto uses_gds =
std::any_of(program.blocks.begin(), program.blocks.end(), [](const auto& block) {
return std::any_of(
block.instructions.begin(), block.instructions.end(),
[](const auto& inst) { return inst.memory.kind == IR::ResourceKind::Gds; });
});
if (uses_gds) {
Expect(Kind::Gds);
}
if (!program.info.addresses.empty()) {
Expect(Kind::AddressMemory, Dense(program.info.addresses.size()));
}
if (!program.srt.reads.empty()) {
Expect(Kind::FlattenedSrt);
}
if (program.bindings.ShaderDataDwords() != 0 && program.bindings.push_constant_size == 0) {
Expect(Kind::UserData);
}
std::array<bool, KindCount> seen {};
for (uint32_t i = 0; i < program.bindings.descriptors.size(); i++) {
const auto& binding = program.bindings.descriptors[i];
const auto kind = static_cast<size_t>(binding.kind);
if (kind >= KindCount || seen[kind] || !present[kind] ||
binding.resources != expected[kind]) {
return Fail(error, "native descriptor groups do not match shader topology");
}
for (uint32_t j = 0; j < i; j++) {
if (program.bindings.descriptors[j].binding == binding.binding) {
return Fail(error, "native descriptor binding numbers are not unique");
}
}
seen[kind] = true;
}
for (size_t i = 0; i < KindCount; i++) {
if (present[i] != seen[i]) {
return Fail(error, "native shader plan is missing a required descriptor group");
}
}
const auto has_user_storage = present[static_cast<size_t>(Kind::UserData)];
if (program.bindings.buffer_offset_dword != program.bindings.user_data_registers.size() ||
program.bindings.buffer_offset_count != program.info.buffers.size() ||
(!has_user_storage &&
program.bindings.push_constant_size != program.bindings.ShaderDataDwords() * 4u) ||
(has_user_storage && program.bindings.push_constant_size != 0) ||
!std::is_sorted(program.bindings.user_data_registers.begin(),
program.bindings.user_data_registers.end()) ||
std::adjacent_find(program.bindings.user_data_registers.begin(),
program.bindings.user_data_registers.end()) !=
program.bindings.user_data_registers.end()) {
return Fail(error, "native user-data layout is inconsistent");
}
for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) {
if (!ValidateInstructionContract(inst, error)) {
return false;
}
const auto memory = inst.memory.kind;
if ((memory == IR::ResourceKind::Buffer || (memory == IR::ResourceKind::ScalarBuffer &&
inst.op == IR::Opcode::SBufferLoadDword)) &&
inst.memory.resource >= program.info.buffers.size()) {
return Fail(error, "buffer instruction has an invalid dense resource");
}
if ((memory == IR::ResourceKind::Image || memory == IR::ResourceKind::ImageUint ||
memory == IR::ResourceKind::StorageImage ||
memory == IR::ResourceKind::StorageImageUint) &&
(inst.memory.resource >= program.info.images.size() ||
program.info.images[inst.memory.resource].kind != memory ||
program.info.images[inst.memory.resource].dimension !=
inst.memory.image_dimension)) {
return Fail(error, "image instruction has an invalid dense resource");
}
const bool address =
inst.op == IR::Opcode::SLoadDword || memory == IR::ResourceKind::Flat ||
memory == IR::ResourceKind::Global || memory == IR::ResourceKind::Scratch;
if (address && (inst.memory.resource >= program.info.addresses.size() ||
program.info.addresses[inst.memory.resource].kind != memory)) {
return Fail(error, "address instruction has an invalid dense resource");
}
if (NeedsSampler(inst.op) && inst.memory.sampler >= program.info.samplers.size()) {
return Fail(error, "sample instruction has an invalid dense sampler");
}
if (inst.op == IR::Opcode::LoadSrtDword &&
(inst.src_count != 1 || inst.src[0].kind != IR::OperandKind::ImmediateU32 ||
inst.src[0].imm >= program.srt.reads.size())) {
return Fail(error, "flattened SRT instruction has an invalid dense slot");
}
}
}
return true;
}
} // namespace
static constexpr size_t InitialEmitterVectorReserve = 4096;
static void ReportReserveExceeded(const IR::Program& program, const char* vector_name,
size_t size) {
if (size <= InitialEmitterVectorReserve) {
return;
}
std::printf("SPIR-V emitter reserve exceeded: hash=0x%016" PRIx64
" stage=%u vector=%s size=%zu reserve=%zu\n",
program.shader_hash, static_cast<unsigned>(program.stage), vector_name, size,
InitialEmitterVectorReserve);
}
static bool IsExecDestination(const IR::Operand& operand) {
return operand.kind == IR::OperandKind::Register && operand.reg.file == IR::RegisterFile::Exec;
}
static bool IsUniformZeroExecWrite(const IR::Program& program, const IR::Instruction& inst) {
if (inst.op != IR::Opcode::BitFieldMaskU64 || !IsExecDestination(inst.dst) ||
inst.src_count < 1u) {
return false;
}
uint32_t count = 0;
return IR::FoldScalarConstant(program.provenance, inst.scalar_sources[0], count) &&
(count & 63u) == 0u;
}
static bool ProducesLaneMask(const IR::Instruction& inst) {
switch (inst.op) {
case IR::Opcode::IAddCarryU32:
case IR::Opcode::ISubBorrowU32:
case IR::Opcode::UMadU64U32: return inst.dst2.kind != IR::OperandKind::Null;
default: return false;
}
}
bool ProgramRequiresExactSubgroupSize(const IR::Program& program) {
for (const auto& block: program.blocks) {
if (block.terminator.kind == CFG::TerminatorKind::ConditionalBranch) {
switch (block.terminator.condition) {
case CFG::BranchCondition::VccZero:
case CFG::BranchCondition::VccNonZero:
case CFG::BranchCondition::ExecZero:
case CFG::BranchCondition::ExecNonZero: return true;
default: break;
}
}
for (const auto& inst: block.instructions) {
if (Emitter::InstructionHasDppSource(inst)) {
return true;
}
if (IR::IsCompareOpcode(inst.op) && !Emitter::IsSccOperand(inst.dst)) {
return true;
}
if ((IsExecDestination(inst.dst) || IsExecDestination(inst.dst2)) &&
!IsUniformZeroExecWrite(program, inst)) {
return true;
}
if (ProducesLaneMask(inst)) {
return true;
}
if (inst.op == IR::Opcode::SelectMaskU32 || inst.op == IR::Opcode::SelectMaskF32Bits) {
return true;
}
if (inst.op == IR::Opcode::SaveexecB32 || inst.op == IR::Opcode::SaveexecB64) {
return true;
}
switch (inst.op) {
case IR::Opcode::WqmB64:
if (inst.dst.kind == IR::OperandKind::Register &&
inst.dst.reg.file != IR::RegisterFile::Scalar) {
return true;
}
break;
case IR::Opcode::MaskedBitCountLowU32:
case IR::Opcode::MaskedBitCountHighU32:
case IR::Opcode::ReadFirstLaneU32:
case IR::Opcode::ReadLaneU32:
case IR::Opcode::WriteLaneU32:
case IR::Opcode::Permlane16B32:
case IR::Opcode::Permlanex16B32:
case IR::Opcode::DsSwizzleB32:
case IR::Opcode::DsConsume:
case IR::Opcode::DsAppend: return true;
default: break;
}
}
}
return false;
}
bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resources,
const ShaderVertexInputInfo* vertex_input_info,
const ShaderPixelInputInfo* pixel_input_info,
const ShaderComputeInputInfo* compute_input_info, std::vector<uint32_t>& spirv,
std::string* error) {
using namespace Emitter;
if (program.stage != ShaderType::Compute && program.stage != ShaderType::Vertex &&
program.stage != ShaderType::Pixel) {
SetError(error, "binary SPIR-V emitter supports compute, vertex, and pixel shaders");
return false;
}
if (!program.srt_patching_complete || !program.resource_tracking_complete ||
!program.shader_info_complete || !program.binding_layout_complete) {
SetError(error, "SPIR-V emitter requires a fully planned native shader program");
return false;
}
if (!IR::ValidateResourceSnapshot(program, resources, error)) {
return false;
}
if (!IR::ValidateResourceSpecialization(program, resources, error)) {
return false;
}
if (!ValidateNativeProgram(program, error)) {
return false;
}
EmitterState state(program, resources);
state.vertex_input_info = vertex_input_info;
state.pixel_input_info = pixel_input_info;
state.compute_input_info = compute_input_info;
state.stage = program.stage;
state.wave_size = program.wave_size;
state.per_invocation_masks = program.lane_mask_mode == ShaderLaneMaskMode::PerInvocation;
state.exact_subgroup_operations =
!state.per_invocation_masks && ProgramRequiresExactSubgroupSize(program);
state.registers.reserve(InitialEmitterVectorReserve);
state.inputs.reserve(InitialEmitterVectorReserve);
state.outputs.reserve(InitialEmitterVectorReserve);
state.interface_variables.reserve(InitialEmitterVectorReserve);
state.reachable_blocks.reserve(InitialEmitterVectorReserve);
CollectRegisters(program, state.registers);
CopyProgramInputsAndOutputs(state, program);
state.needs_subgroup_ballot = ProgramNeedsSubgroupBallot(program);
state.needs_subgroup_shuffle = ProgramNeedsSubgroupShuffle(program);
state.needs_subgroup_local_invocation_id = ProgramNeedsSubgroupLocalInvocationId(program);
state.needs_compute_derivatives = ProgramNeedsComputeDerivatives(program);
state.needs_image_gather_extended = ProgramNeedsImageGatherExtended(program);
state.needs_function_lds = ProgramNeedsFunctionLds(program);
state.needs_pixel_valid_mask = ProgramNeedsPixelValidMask(program);
ComputeReachableBlocks(state, program);
AllocateInputVariables(state);
AllocateOutputVariables(state);
AllocateDescriptorVariables(state);
EmitHeaderAndTypes(state);
AllocateRegisterVariables(state);
AllocateBlockLabels(state, program);
AllocateDispatcherState(state, program);
EmitFunction(state, program);
if (state.unsupported_ir_instruction) {
char pc[9] {};
std::snprintf(pc, sizeof(pc), "%08x", state.unsupported_ir_pc);
return Fail(error, "SPIR-V emitter has no handler for IR opcode " +
std::string(IR::OpcodeName(state.unsupported_ir_opcode)) +
" at pc 0x" + pc);
}
ReportReserveExceeded(program, "registers", state.registers.size());
ReportReserveExceeded(program, "inputs", state.inputs.size());
ReportReserveExceeded(program, "outputs", state.outputs.size());
ReportReserveExceeded(program, "interface_variables", state.interface_variables.size());
ReportReserveExceeded(program, "reachable_blocks", state.reachable_blocks.size());
auto binary = state.builder.Build();
if (binary.empty()) {
SetError(error, "SPIR-V builder returned an empty module");
return false;
}
spirv = std::move(binary);
return true;
}
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
Loaded 100 of 243 files, more files were not shown because too many files have changed in this diff. Show more