From cfd4408d27888bc7516128af564083770c86cc55 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sun, 23 Aug 2026 01:34:44 -0400 Subject: [PATCH 01/35] Review LSFG frame generation and design the Android-side integration Reviews the two existing Android LSFG integrations and traces WinNative's own present path to decide where frame generation belongs. LSFG-Android is a standalone overlay app. It cannot reach another app's swapchain, so it runs on a MediaProjection capture and composites through a system overlay, at a self-reported 50-80 ms latency cost. Its framegen library also owns a separate VkDevice and synchronises with vkDeviceWaitIdle because cross-device semaphores do not exist, which would put a full device stall on our critical path. Eden PR 4263 reimplements the same shader chain inside the emulator's own Vulkan renderer: one device, one command stream, generated frames written straight into an owned storage image and presented ahead of the real frame. It also drops the DXBC translator - current Lossless Scaling resources are already SPIR-V, so only a PE resource walk and a binding renumber are needed. WinNative is structurally in eden's position, not LSFG-Android's: the guest hands finished frames over DRI3 with the Android AHB modifier, and vkr_texture_import_ahb turns them into VkImages in the compositor's own device before anything is presented. Frame generation therefore belongs in vk_renderer.c, entirely on the Android side, with no Wine involvement. Documents the composite-target redirect the swapchain images cannot serve, the multi-present ordering, the pacer's probe and backoff loop, and the container-local Lossless.dll discovery that eden cannot offer. Also corrects the latency premise: interpolation always adds one output interval, so the Android-side placement removes the capture and guest round-trip overhead rather than reducing input latency below baseline. --- docs/lsfg-frame-generation.md | 408 ++++++++++++++++++++++++++++++++++ 1 file changed, 408 insertions(+) create mode 100644 docs/lsfg-frame-generation.md diff --git a/docs/lsfg-frame-generation.md b/docs/lsfg-frame-generation.md new file mode 100644 index 000000000..3db4c54e5 --- /dev/null +++ b/docs/lsfg-frame-generation.md @@ -0,0 +1,408 @@ +# LSFG frame generation in WinNative + +Review of the two existing Android LSFG integrations, of WinNative's own +renderer, and the design for putting frame generation on the Android side of +the Wine boundary. + +## Sources reviewed + +| Source | Revision | +|---|---| +| `FrankBarretta/LSFG-Android` | `da867e9` | +| └ `lsfg-vk-android` (submodule, branch of `PancakeTAS/lsfg-vk` 1.0.0) | `b55b182` | +| └ `LSFG-Android-Application` (submodule) | `b847541` | +| `eden-emu/eden` PR #4263 — *[vulkan, android] Initial implementation of LSFG-VK* | `469c9af` (base `dc95cd0`) | +| WinNative | `acc130ee` | + +The two clones live outside the repo at `~/Build/lsfg-research/`. Nothing from +either is vendored yet. + +## 1. What LSFG actually is + +Lossless Scaling Frame Generation 3.1 is a fixed-function chain of **compute +shaders** that takes two consecutive presented frames and synthesises one or +more intermediate frames by estimating a dense optical flow field between them +and warping along it. There is no neural network runtime and no external +dependency — it is 25 compute shaders and a pile of intermediate images. + +The shaders themselves are **not redistributable**. They ship as `RCDATA` +resources inside `Lossless.dll` from the user's own paid copy of Lossless +Scaling. Every implementation — upstream `lsfg-vk`, LSFG-Android, and eden — +requires the user to supply their own DLL and extracts the resources on-device. +None of them bundle it, and neither can WinNative. + +The chain, as reconstructed in eden (`lsfg_*.h`): + +| Stage | Shader IDs | Shape | Role | +|---|---|---|---| +| `mipmaps` | 255 | 7 levels | Luma pyramid of the input frame pair | +| `alpha` | 290–293 | 4 stages × 7 mip levels | Coarse-to-fine flow estimate, 3-deep history | +| `beta` | 298–302 | 5 stages, 6 outputs | Flow refinement | +| `gamma` | 280, 282–285 | 5 stages × 7 mip levels | Flow upsampling / confidence | +| `delta` | 280–281, 286–289, 294–297 | 10 stages × 3 instances | Occlusion + warp field | +| `generate` | 256 | 1 per generated frame | Final warp/blend into the target image | + +Two knobs matter for cost: **flow scale** (0.25–1.0, resolution of the flow +pyramid relative to output) and **multiplier** (2×–4×, how many frames are +generated per real frame). Only `generate` runs per generated frame; everything +above it is shared across all generations from the same frame pair, which is why +3× costs far less than 1.5× the cost of 2×. + +Shader variants: resource IDs `+49` are native fp16, `+98` are native fp32. Both +are already SPIR-V in current Lossless Scaling builds — eden's `IsSpirvModule` / +`AdoptSpirvModule` just re-numbers the descriptor bindings in set/binding order +and hands the words to `vkCreateShaderModule`. **No DXBC translator is needed**; +that was upstream `lsfg-vk`'s path and eden explicitly dropped it (commit +`6dd3098 Remove requirement on dxbc`). This removes DXVK's `dxbc` and `pe-parse` +from the dependency list entirely — the PE resource walk is ~250 lines of plain +parsing in `lossless_dll.cpp`. + +## 2. How LSFG-Android bakes it in — and why WinNative must not copy it + +LSFG-Android is a *standalone overlay app*. It cannot see into another app's +Vulkan swapchain, because Android 12+ blocks loading external code into +non-debuggable processes, so there is no equivalent of Linux's implicit layer +mechanism. Its pipeline is therefore: + +``` +target game → SurfaceFlinger → MediaProjection capture → app's VkDevice + → AHardwareBuffer → framegen's *own* VkDevice → AHB back + → SYSTEM_ALERT_WINDOW / accessibility overlay composited over the game +``` + +Its own README puts the cost at **50–80 ms of added latency versus the Linux +Vulkan layer**, and calls it a platform constraint rather than a bug. It also +needs `SYSTEM_ALERT_WINDOW` + screen capture + an `AccessibilityService`, which +is a Play-policy violation and a per-session consent prompt. + +Two structural details are worth carrying forward even though the model is not: + +- **`vkGetMemoryFdKHR` fails on AHB-imported memory on both Adreno and Mali.** + Upstream `lsfg-vk`'s FD-based image sharing is unusable on Android; the fork + replaced it with a direct `AHardwareBuffer*` path + (`VK_ANDROID_external_memory_android_hardware_buffer` + + `VkImportAndroidHardwareBufferInfoANDROID` with a dedicated allocation). +- **`framegen` owns its own `VkInstance`/`VkDevice`.** Cross-device + synchronisation is not expressible with Vulkan semaphores, so the fork added a + `waitIdle()` entry point that calls `vkDeviceWaitIdle`. Consuming the library + as-is means a **full device stall per frame**. + +That second point is decisive. Linking `lsfg-vk-android`'s `framegen` into +WinNative would give us a second Vulkan device, AHB round-trips in both +directions, and a `vkDeviceWaitIdle` on the critical path — inside a compositor +that already has the frame sitting in a `VkImage` on the right device. The +library's API surface exists to serve a process that *cannot* reach the game's +images. WinNative can. + +## 3. How eden PR #4263 bakes it in — the right model + +Eden reimplements the LSFG chain **inside the emulator's own Vulkan renderer**: +same `VkInstance`, same `VkDevice`, same `Scheduler`, same command stream. No +second device, no AHB, no `waitIdle`. `~4700` lines across `src/video_core/`. + +The integration contract is small (`renderer_vulkan.cpp::Composite`): + +```cpp +blit_swapchain.DrawToFrame(device, rasterizer, frame, framebuffers, ...); + +void(frame_gen.WantedGenerations(present_manager.MaxExtraFrames())); +frame_gen.Process(device, frame, swapchain.GetImageFormat(), GuestExtent(framebuffers)); + +for (size_t generation = 0; generation < frame_gen.GeneratedFrameCount(); ++generation) { + Frame* generated = present_manager.GetRenderFrame(); + blit_swapchain.PrepareFrame(device, generated, render_window.GetFramebufferLayout()); + frame_gen.GenerateInto(device, generated, generation); + scheduler.Flush(*generated->render_ready); + present_manager.Present(generated); +} + +scheduler.Flush(*frame->render_ready); +present_manager.Present(frame); +``` + +The pieces that make it work: + +- **Composite into an owned off-screen image, not the swapchain image.** + `Frame::image` gains `VK_IMAGE_USAGE_STORAGE_BIT` (guarded by a + `VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT` probe) plus `TRANSFER_DST` and + `SAMPLED`, and a matching `storage_view`. `generate` writes straight into it, + and the existing `CopyToSwapchain` blit is unchanged. This sidesteps the fact + that Android swapchain images are almost never storage-capable. +- **A 2-deep input ring.** `Process` copies the just-composited frame into + `chain->Input(count)` and dispatches the shared part of the chain. Frame N−1 is + still in the other slot. +- **Generated frames are presented *before* the real frame.** Interpolation + produces frames that belong between N−1 and N, so N is held back one slot. + This is the source of the added latency, and it is unavoidable for any + interpolating (as opposed to extrapolating) generator. +- **Present queue depth is raised to make room**: `image_count` becomes + `clamp((generations + 1) * (queue_target + 1), swapchainImages, 7)`. +- **`FrameGenPacer` decides the multiplier at runtime** rather than trusting the + setting. It smooths the real frame interval (EMA 0.25), rejects burst frames, + requires a 1 s stabilisation window, and periodically *probes* one extra + generation — keeping it only if measured throughput improves by ≥1.15× and the + base rate does not collapse below 0.70×, with 5/15/30/60 s backoff on repeated + failures. This is the part that stops frame gen from making a GPU-bound game + slower, and it is worth porting behaviourally rather than reinventing. +- **A two-frame warm-up** (`LSFG_REQUIRED_FRAMES` + `LSFG_RECURRENCE_FRAMES`) + before anything is emitted, so the history slots are valid. + +Android-side plumbing in the same PR that we would need an analogue of: +`LosslessScalingHelper.kt` (SAF pick → copy to internal storage → +`prepareLosslessDll()` → cache build → delete the copy), `LosslessManagerFragment`, +and a `supportsFrameGeneration()` capability probe. + +**Licence note:** eden is GPL-3.0-or-later and so is WinNative (`LICENSE`, GPLv3). +Eden's `lsfg_*` files carry dual `Eden Emulator Project` / `lsfg-vk` GPL-3.0 +headers. Porting them into WinNative is licence-compatible provided the +copyright headers are preserved verbatim. Upstream `lsfg-vk` itself is MIT, so +the algorithm is also reachable from the MIT side if we ever want a looser +licence — but the eden tree is the one that already works without DXBC. + +## 4. WinNative's renderer, traced + +A Wine game's frame reaches the display like this: + +1. **Guest**: DXVK (or wined3d/OpenGL-on-Zink) renders with the guest Vulkan + driver inside the container. +2. **DRI3 handoff**: the guest presents to its X11 window via + `PixmapFromBuffers` carrying the private Android modifier + (`ANDROID_NATIVE_BUFFER_MODIFIER`), whose single ancillary FD is an + `AHardwareBuffer` socket handle — `DRI3Extension.java:41` / `:203`. +3. **Host import**: the Java X server imports it as a `GPUImage`, and + `GPUImage.nativeImportAhbToVulkan` → `vkr_texture_import_ahb` + (`vk_image.c:1116`) turns it into a real `VkImage` in **WinNative's own + `VkDevice`**, zero-copy. +4. **Composite**: `VulkanRenderer.java` snapshots the scene into a ~2.8 KB + `ByteBuffer` and calls `nativeRenderFrame` on the `XServerSurfaceView` render + thread. `record_and_submit_frame` (`vk_renderer.c:2096`) acquires a swapchain + image, runs `draw_scene_pass`, walks the effect chain through ping-pong + `VkOffscreen` targets (SGSR1, CRT, HDR, …), and ends the last render pass + directly in the swapchain framebuffer. +5. **Present**: one `vkQueueSubmit`, one `vkQueuePresentKHR` (`:2431`). + +Relevant current parameters: + +- `VK_FRAMES_IN_FLIGHT` is **2**; swapchain is `minImageCount + 1` (3 on most + devices), capped at `VK_MAX_SWAPCHAIN_IMAGES` = 8. +- Swapchain `imageUsage` is `COLOR_ATTACHMENT_BIT` only (`+ TRANSFER_SRC` when + the recorder is active) — **not storage-capable**. +- Default present mode is `VK_PRESENT_MODE_FIFO_KHR`, settable via + `nativeSetPresentMode`. +- Render mode is `RENDERMODE_WHEN_DIRTY`. The render thread is woken by + `requestRenderCoalesced()`, which posts a Choreographer callback, so the + compositor's present cadence is already slaved to the game's update cadence. +- FPS pacing is *not* done by sleeping in the renderer — it is back-pressure on + `PresentIdleNotify` in the X Present extension (`vk_renderer.c:3027`). + +**The key finding: the game's finished frame is already a `VkImage` in +WinNative's own Vulkan device, on the Android side of the Wine boundary, before +anything is presented.** WinNative is structurally in eden's position, not +LSFG-Android's. Everything MediaProjection exists to work around is already +solved here by DRI3+AHB. + +## 5. Design + +Put the LSFG chain in `vk_renderer.c`'s device, driven from +`record_and_submit_frame`. Nothing crosses into Wine; nothing touches +MediaProjection; no second `VkDevice`. + +``` +guest DXVK ──DRI3/AHB──> VkImage (host) ──> draw_scene_pass ──> effect chain + │ + ┌─────────────┴──────────────┐ + ▼ ▼ + composite target LSFG input ring + (STORAGE|SAMPLED| (2 × R8G8B8A8) + TRANSFER_SRC/DST) │ + │ shared chain: mipmaps + │ → alpha → beta + │ → gamma → delta + │ │ + │ generate × N ────┐ + │ │ + ▼ ▼ + present(real frame N) ◄── after ── present(gen 0..N-1) +``` + +### 5.1 Redirect the composite off the swapchain + +Today the last render pass writes straight into +`r->swapchain_framebuffers[image_index]`. Frame gen needs the composited result +to be *readable* (it becomes next frame's LSFG input) and needs generated frames +to be *storage-writable*. Both fail on Android swapchain images. + +Add a `VkCompositeTarget` ring — same shape as the existing `VkOffscreen` but +with `STORAGE | SAMPLED | TRANSFER_SRC | TRANSFER_DST | COLOR_ATTACHMENT`, sized +to the swapchain extent, `(max_generations + 1) × (queue_target + 1)` deep. When +frame gen is off, the existing direct-to-swapchain path stays exactly as it is — +this must be a zero-cost bypass, not a new mandatory copy. + +With frame gen on, the final effect pass (or `draw_scene_pass` when there are no +effects) targets a composite image, and a trivial blit moves it into the acquired +swapchain image. That blit is the same operation the recorder already performs at +`vk_renderer.c:2346`, so the code pattern exists. + +### 5.2 The chain + +Port eden's `lsfg_*` files to C in `app/src/main/cpp/winlator/vk/lsfg/`, +preserving the GPL-3.0 headers. The port is mostly mechanical — eden's +`LsfgImage`/`LsfgPass`/`LsfgBarriers`/`LsfgDescriptorWriter` are thin RAII +wrappers over exactly the objects `vk_state.h` already models by hand, and +`vk_renderer.c` already has `vkr_image_barrier`, a descriptor pool with +`vkr_free_descriptor_set`, and a suballocator. + +Compute pipelines are new to this renderer — everything today is graphics +pipelines with a fullscreen triangle — so `create_pipelines` needs a compute +path, and the device needs `VK_DESCRIPTOR_TYPE_STORAGE_IMAGE` pool capacity. + +### 5.3 Presenting the extra frames + +The natural fit is to keep **one `nativeRenderFrame` call per real frame** and +emit the extra presents inside it. With `VK_PRESENT_MODE_FIFO_KHR`, queuing +N+1 presents in one go makes the driver display them on N+1 consecutive vblanks +— the pacing falls out of FIFO for free, with no sleeps and no render-mode +change on the Java side. + +Ordering per real frame, matching eden: + +1. Composite frame N into a composite target. +2. Copy it into the LSFG input ring, dispatch the shared chain. +3. For `g` in `0..N_gen-1`: acquire a swapchain image, `generate` into a + composite target, blit to the swapchain image, submit, present. +4. Acquire, blit composite N to the swapchain image, submit, present. + +Consequences to handle: + +- `VK_FRAMES_IN_FLIGHT` must rise from 2 to at least `max_generations + 2`, and + swapchain `minImageCount` must be requested as + `clamp((generations + 1) * (queue_target + 1), minImageCount + 1, 8)`. +- `vkAcquireNextImageKHR` is called N+1 times per real frame, so each pending + present needs its own `image_available`/`render_finished` semaphore and fence + — the current per-`frame_index` arrays need to be indexed per *present*, not + per composite. +- The Present-extension back-pressure at `vk_renderer.c:3027` releases guest + buffers based on presents. It must count **real** presents only, or the guest + will be told it can render N× faster than it can and the pacer will fight + itself. + +### 5.4 Pacing + +Port `FrameGenPacer` behaviourally. Its value is not the multiplier arithmetic +but the probe/backoff loop: on a phone SoC the LSFG chain competes with the game +for the same GPU, and a naive fixed 3× on a GPU-bound title lowers the real frame +rate more than the generated frames add. The probe measures that and backs off. + +WinNative-specific inputs the pacer should use that eden does not have: +`currentFpsLimit`, the display's actual refresh rate (never generate above it), +and thermal state — sustained frame gen on a phone is a thermal decision as much +as a perf one. + +### 5.5 What gets interpolated + +LSFG must see the **game content**, not the whole composited desktop. In +WinNative's favour: the touch-control overlay, `MangoHudView`, and the drawer are +Android views layered over the `SurfaceView`, so they are already outside the +Vulkan composite. Inside it, the software cursor and the effect chain are not. + +- Run frame gen **after** the effect chain, so SGSR/CRT/HDR output is what gets + interpolated and generated frames match the real ones visually. +- The software cursor should be excluded and re-drawn per present, otherwise it + ghosts. `draw_scene_pass` already draws it separately from windows, so this is + a matter of splitting the pass, not a redesign. + +### 5.6 `Lossless.dll` acquisition — a WinNative advantage + +Eden and LSFG-Android both need the user to hand over a DLL through SAF from a +Windows machine. WinNative runs Windows games and ships Steam integration, so +Lossless Scaling can be **installed into the container from Steam directly**, and +the DLL located at +`/drive_c/Program Files (x86)/Steam/steamapps/common/Lossless Scaling/Lossless.dll`. + +Offer both: auto-detect inside the container drives, with the SAF picker as the +fallback. Extraction, SPIR-V adoption and caching happen once on the Android side +(cache keyed on file size + hash + variant, as eden does); the DLL is never +loaded or executed, only parsed. + +### 5.7 Settings surface + +Per-container and per-shortcut, alongside the existing graphics options, gated +on a capability probe (compute + storage-image support + the DLL being present): +enable, multiplier (2×–4×), flow scale (auto / 25–100 %), fp16 preference, +queue depth, and a target rate cap. + +## 6. Latency — the honest accounting + +The premise that moving frame gen to the Android side "would actually help with +the input latency" needs one correction, because it changes what we should +promise users. + +**Interpolating frame generation always adds latency.** Real frame N cannot be +shown until the frames synthesised between N−1 and N have been shown first. At +2× the penalty is one output interval; at 60 Hz that is ~16.7 ms. No placement of +the code changes this — it is inherent to interpolation. LSFG does not reduce +input-to-photon latency, and enabling it will make a game feel *slightly* less +responsive while looking substantially smoother. + +What the Android-side placement does buy is large, though: + +| | Wine-side layer | LSFG-Android overlay | **WinNative Android-side** | +|---|---|---|---| +| Vulkan devices | 2 (guest + host) | 2 + capture | **1** | +| Copies of each generated frame | AHB export → X pixmap → import → composite | capture → AHB → AHB → overlay composite | **1 blit to swapchain** | +| Guest round-trips per generated frame | 1 full DRI3/Present cycle | n/a | **0** | +| Frame-gen code runs under | box64/FEX or arm64ec translation | native | **native** | +| Cross-device sync | semaphores + protocol | `vkDeviceWaitIdle` per frame | **barriers in one command buffer** | +| Added latency vs. no frame gen | interpolation + ~2 protocol round-trips | interpolation + 50–80 ms | **interpolation only** | + +So the correct claim is: Android-side placement makes frame generation cost +*only* its unavoidable interpolation delay, instead of that plus a capture +pipeline (LSFG-Android) or plus a guest round-trip per generated frame +(Wine-side layer). It is the difference between frame gen being usable and not. + +There is also a genuine smoothness win that is not latency: today the +compositor's present cadence is slaved to the guest's update cadence, so a 30 fps +game produces 30 presents/sec and the whole surface — including scrolling and +cursor motion — updates at 30 Hz. Frame gen decouples display cadence from game +cadence. + +If reducing input latency is the actual goal, the levers are elsewhere and worth +tracking separately: present mode (`MAILBOX` vs `FIFO`), the +`PresentIdleNotify` back-pressure depth, DXVK's `maxFrameLatency`, and the +Choreographer coalescing in `requestRenderCoalesced`. + +## 7. Work plan + +| Phase | Work | Verifiable by | +|---|---|---| +| 1 | `Lossless.dll` PE resource walk, SPIR-V adoption, on-device cache, capability probe, container auto-detect + SAF fallback | 25 modules cached; status surfaces in settings | +| 2 | Composite-target ring + swapchain blit, behind an off-by-default flag | Pixel-identical output, no measurable cost with the flag off | +| 3 | Compute pipeline support + `mipmaps`/`alpha`/`beta`/`gamma`/`delta`/`generate` port | Flow-pyramid debug dump matches eden's on the same input pair | +| 4 | Multi-present per composite; semaphore/fence rework; `VK_FRAMES_IN_FLIGHT` and swapchain depth; real-present-only back-pressure | 2× shows 2 presents per guest frame; no validation errors | +| 5 | Pacer with probe/backoff, refresh-rate ceiling, thermal input | 3× on a GPU-bound title backs off instead of losing real frames | +| 6 | Settings UI, per-container/per-shortcut persistence, HUD counters (real vs total) | End to end on device | + +Phases 1 and 2 are independent and both land safely with frame gen disabled. + +## 8. Risks + +- **Storage-image format support.** Generated frames are written by a compute + shader into `B8G8R8A8_UNORM`/`R8G8B8A8_UNORM`. Probe + `VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT` and disable frame gen rather than + failing at pipeline creation, exactly as eden's `CanStoreToFrame` does. +- **Adreno 6xx and Mali.** LSFG-Android reports end-to-end frame gen working on + Adreno 7xx-class and newer. The chain is 25 dispatches over a 7-level pyramid; + on older parts the shared chain alone may exceed the frame budget. The pacer's + probe handles this gracefully, but the capability gate should be conservative. +- **Memory.** 7 mip levels × alpha history × beta/gamma/delta temporaries at + swapchain resolution is a real allocation. Flow scale is the mitigation and + should default to auto. +- **Interaction with SGSR1.** Frame gen must sit after upscaling, or it + interpolates at the wrong resolution and the upscaler re-processes synthesised + content. +- **Recorder.** `GameRecorder` currently blits every presented frame. It should + capture real frames only, or recordings get generated frames at an + inconsistent cadence. +- **Licence hygiene.** Preserve eden's and lsfg-vk's GPL-3.0 headers on every + ported file. Never bundle, download, or cache-and-redistribute any part of + `Lossless.dll`. From e6d0642d72068f514f5fff92afef5df78206bc60 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sun, 23 Aug 2026 01:49:34 -0400 Subject: [PATCH 02/35] Extract LSFG shaders from Lossless.dll and probe GPU support First phase of Android-side frame generation. Nothing here touches the renderer yet, so the build is unchanged at runtime. The shader chain lives as RCDATA resources inside the user's own copy of Lossless.dll and is not redistributable, so it has to be extracted on-device. Current Lossless Scaling builds already store those resources as SPIR-V, which is why this needs only a PE resource walk and a descriptor binding renumber rather than the DXBC translator upstream lsfg-vk carries. lsfg_dll.c walks the PE headers, section table and resource tree to collect the 25 RCDATA blobs (255, 256 and 280-302) for the native fp16 and fp32 variants, renumbers each module's binding decorations into set/binding order, and writes them to an on-device cache through a temp file plus rename so a failed build cannot leave a half-written cache behind. The DLL is mapped read-only and never loaded or executed. Both variants are cached at install time when the DLL carries both, so flipping the fp16 preference later does not require the user to supply the DLL again. The staged copy is deleted as soon as the cache is written. lsfg_probe.c answers whether the selected driver can run the chain at all, resolving its own entry points from a private dlopen rather than the global dispatch table so it cannot disturb a live renderer. It requires a compute queue plus storage and sampled support for the three formats the chain needs, and reports unsupported instead of failing later at pipeline creation. LosslessScaling adds container-local discovery, which eden cannot offer: Lossless Scaling installed into a container's Steam library is found directly under drive_c or a mapped drive, with the storage picker as the fallback. Verified with a synthetic PE and SPIR-V generator: 23 assertions covering variant selection and fallback, binding renumbering, cache round-trip and every rejection path, run clean under ASan and UBSan on the host and on device for both arm64 and x86_64. The two parse paths were additionally fuzzed with 3000 mutated PE files and 2000 mutated cache files with -fno-sanitize-recover, no crashes or undefined behaviour. --- app/src/main/cpp/CMakeLists.txt | 3 + app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c | 707 ++++++++++++++++++ app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h | 59 ++ app/src/main/cpp/winlator/vk/lsfg/lsfg_jni.c | 75 ++ .../main/cpp/winlator/vk/lsfg/lsfg_probe.c | 169 +++++ .../main/cpp/winlator/vk/lsfg/lsfg_probe.h | 14 + .../runtime/display/lsfg/LosslessScaling.java | 243 ++++++ 7 files changed, 1270 insertions(+) create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_jni.c create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_probe.c create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_probe.h create mode 100644 app/src/main/runtime/display/lsfg/LosslessScaling.java diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 4f006c89e..abcca2229 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -147,6 +147,9 @@ add_library(winlator SHARED winlator/vk/vk_dispatch.c winlator/vk/vk_image.c winlator/vk/vk_renderer.c + winlator/vk/lsfg/lsfg_dll.c + winlator/vk/lsfg/lsfg_probe.c + winlator/vk/lsfg/lsfg_jni.c ) add_dependencies(winlator winlator_shaders) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c new file mode 100644 index 000000000..b0537856a --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c @@ -0,0 +1,707 @@ +#include "lsfg_dll.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_TAG "LsfgDll" +#define LSFG_LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LSFG_LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LSFG_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) + +#define DOS_MAGIC 0x5A4Du +#define PE_SIGNATURE 0x00004550u +#define PE32_MAGIC 0x010Bu +#define PE32_PLUS_MAGIC 0x020Bu + +#define DOS_LFANEW_OFFSET 0x3Cu +#define COFF_HEADER_SIZE 20u +#define OPTIONAL_HEADER_SIZE_OFFSET 16u +#define SECTION_HEADER_SIZE 40u +#define DATA_DIRECTORY_ENTRY_SIZE 8u +#define DATA_DIRECTORY_OFFSET_PE32 96u +#define DATA_DIRECTORY_OFFSET_PE32P 112u +#define RESOURCE_DATA_DIRECTORY_INDEX 2u + +#define RESOURCE_DIRECTORY_SIZE 16u +#define RESOURCE_NAMED_COUNT_OFFSET 12u +#define RESOURCE_ID_COUNT_OFFSET 14u +#define RESOURCE_ENTRY_SIZE 8u +#define RESOURCE_SUBDIRECTORY_FLAG 0x80000000u +#define RESOURCE_TYPE_RCDATA 10u + +#define SPIRV_MAGIC 0x07230203u +#define SPIRV_HEADER_WORDS 5u +#define SPIRV_WORD_COUNT_SHIFT 16u +#define SPIRV_OPCODE_MASK 0xFFFFu +#define SPIRV_OP_FUNCTION 54u +#define SPIRV_OP_DECORATE 71u +#define SPIRV_DECORATION_BINDING 33u +#define SPIRV_DECORATION_DESCRIPTOR_SET 34u +#define DECORATION_LITERAL_WORD 3u + +#define VARIANT_FP16_OFFSET 49u +#define VARIANT_FP32_OFFSET 98u + +#define MAX_RESOURCE_ID 512u + +#define CACHE_MAGIC 0x4746534Cu +#define CACHE_VERSION 1u + +#define MAX_SPIRV_WORDS (16u * 1024u * 1024u) + +typedef struct PeSection { + uint32_t virtual_address; + uint32_t virtual_size; + uint32_t raw_address; + uint32_t raw_size; +} PeSection; + +typedef struct PeImage { + const uint8_t* data; + size_t size; + PeSection* sections; + uint32_t section_count; +} PeImage; + +typedef struct ResourceTable { + const uint8_t* data[MAX_RESOURCE_ID]; + uint32_t size[MAX_RESOURCE_ID]; +} ResourceTable; + +typedef struct CacheHeader { + uint32_t magic; + uint32_t version; + uint64_t source_size; + uint64_t source_hash; + uint32_t module_count; + uint32_t variant; +} CacheHeader; + +typedef struct BindingSlot { + uint32_t set; + uint32_t binding; + size_t literal_offset; +} BindingSlot; + +static const uint32_t* build_shader_ids(size_t* out_count) { + static uint32_t ids[LSFG_SHADER_COUNT]; + static size_t count = 0; + if (count == 0) { + ids[count++] = LSFG_SHADER_MIPMAPS; + ids[count++] = LSFG_SHADER_GENERATE; + for (uint32_t id = LSFG_SHADER_PERF_FIRST; id <= LSFG_SHADER_PERF_LAST; id++) { + ids[count++] = id; + } + } + if (out_count) *out_count = count; + return ids; +} + +const uint32_t* lsfg_shader_ids(size_t* out_count) { + return build_shader_ids(out_count); +} + +static uint64_t fnv1a64(const uint8_t* data, size_t size) { + uint64_t hash = 1469598103934665603ULL; + for (size_t i = 0; i < size; i++) { + hash ^= (uint64_t)data[i]; + hash *= 1099511628211ULL; + } + return hash; +} + +static bool pe_read_u16(const PeImage* image, size_t offset, uint16_t* out_value) { + if (offset > image->size || image->size - offset < sizeof(uint16_t)) return false; + memcpy(out_value, image->data + offset, sizeof(uint16_t)); + return true; +} + +static bool pe_read_u32(const PeImage* image, size_t offset, uint32_t* out_value) { + if (offset > image->size || image->size - offset < sizeof(uint32_t)) return false; + memcpy(out_value, image->data + offset, sizeof(uint32_t)); + return true; +} + +static bool pe_find_header(const PeImage* image, size_t* out_offset) { + uint16_t dos_magic = 0; + if (!pe_read_u16(image, 0, &dos_magic) || dos_magic != DOS_MAGIC) return false; + + uint32_t pe_offset = 0; + if (!pe_read_u32(image, DOS_LFANEW_OFFSET, &pe_offset)) return false; + + uint32_t signature = 0; + if (!pe_read_u32(image, pe_offset, &signature) || signature != PE_SIGNATURE) return false; + + *out_offset = (size_t)pe_offset; + return true; +} + +static bool pe_find_data_directory(const PeImage* image, size_t optional_header_offset, + size_t* out_offset) { + uint16_t optional_magic = 0; + if (!pe_read_u16(image, optional_header_offset, &optional_magic)) return false; + + switch (optional_magic) { + case PE32_MAGIC: + *out_offset = optional_header_offset + DATA_DIRECTORY_OFFSET_PE32; + return true; + case PE32_PLUS_MAGIC: + *out_offset = optional_header_offset + DATA_DIRECTORY_OFFSET_PE32P; + return true; + default: + return false; + } +} + +static bool pe_read_sections(PeImage* image, size_t pe_offset) { + uint16_t section_count = 0; + uint16_t optional_header_size = 0; + if (!pe_read_u16(image, pe_offset + 4 + 2, §ion_count) || + !pe_read_u16(image, pe_offset + 4 + OPTIONAL_HEADER_SIZE_OFFSET, &optional_header_size)) { + return false; + } + if (section_count == 0) return false; + + image->sections = (PeSection*)calloc(section_count, sizeof(PeSection)); + if (!image->sections) return false; + + const size_t table_offset = pe_offset + 4 + COFF_HEADER_SIZE + optional_header_size; + for (uint16_t i = 0; i < section_count; i++) { + const size_t offset = table_offset + (size_t)i * SECTION_HEADER_SIZE; + PeSection* section = &image->sections[i]; + if (!pe_read_u32(image, offset + 8, §ion->virtual_size) || + !pe_read_u32(image, offset + 12, §ion->virtual_address) || + !pe_read_u32(image, offset + 16, §ion->raw_size) || + !pe_read_u32(image, offset + 20, §ion->raw_address)) { + free(image->sections); + image->sections = NULL; + return false; + } + } + image->section_count = section_count; + return true; +} + +static bool pe_rva_to_offset(const PeImage* image, uint32_t rva, size_t* out_offset) { + for (uint32_t i = 0; i < image->section_count; i++) { + const PeSection* section = &image->sections[i]; + const uint32_t span = section->virtual_size > section->raw_size ? section->virtual_size + : section->raw_size; + if (span == 0 || rva < section->virtual_address) continue; + const uint32_t relative = rva - section->virtual_address; + if (relative < span) { + *out_offset = (size_t)section->raw_address + relative; + return true; + } + } + return false; +} + +static bool resource_entry_count(const PeImage* image, size_t directory_offset, size_t* out_total) { + uint16_t named_count = 0; + uint16_t id_count = 0; + if (!pe_read_u16(image, directory_offset + RESOURCE_NAMED_COUNT_OFFSET, &named_count) || + !pe_read_u16(image, directory_offset + RESOURCE_ID_COUNT_OFFSET, &id_count)) { + return false; + } + *out_total = (size_t)named_count + (size_t)id_count; + return true; +} + +static bool resource_entry_at(const PeImage* image, size_t directory_offset, size_t index, + uint32_t* out_id, uint32_t* out_offset, bool* out_is_directory, + bool* out_is_named) { + const size_t offset = + directory_offset + RESOURCE_DIRECTORY_SIZE + index * RESOURCE_ENTRY_SIZE; + uint32_t name = 0; + uint32_t data = 0; + if (!pe_read_u32(image, offset, &name) || !pe_read_u32(image, offset + 4, &data)) return false; + + *out_id = name & ~RESOURCE_SUBDIRECTORY_FLAG; + *out_offset = data & ~RESOURCE_SUBDIRECTORY_FLAG; + *out_is_directory = (data & RESOURCE_SUBDIRECTORY_FLAG) != 0; + *out_is_named = (name & RESOURCE_SUBDIRECTORY_FLAG) != 0; + return true; +} + +static bool resource_read_leaf(const PeImage* image, size_t leaf_offset, + const uint8_t** out_data, uint32_t* out_size) { + uint32_t data_rva = 0; + uint32_t data_size = 0; + if (!pe_read_u32(image, leaf_offset, &data_rva) || + !pe_read_u32(image, leaf_offset + 4, &data_size) || data_size == 0) { + return false; + } + + size_t data_offset = 0; + if (!pe_rva_to_offset(image, data_rva, &data_offset)) return false; + if (data_offset > image->size || image->size - data_offset < data_size) return false; + + *out_data = image->data + data_offset; + *out_size = data_size; + return true; +} + +static bool collect_rcdata(const PeImage* image, size_t resource_base, ResourceTable* out_table) { + size_t type_total = 0; + if (!resource_entry_count(image, resource_base, &type_total)) return false; + + for (size_t t = 0; t < type_total; t++) { + uint32_t type_id = 0; + uint32_t type_offset = 0; + bool type_is_directory = false; + bool type_is_named = false; + if (!resource_entry_at(image, resource_base, t, &type_id, &type_offset, + &type_is_directory, &type_is_named)) { + return false; + } + if (type_is_named || type_id != RESOURCE_TYPE_RCDATA || !type_is_directory) continue; + + const size_t name_base = resource_base + type_offset; + size_t name_total = 0; + if (!resource_entry_count(image, name_base, &name_total)) return false; + + for (size_t n = 0; n < name_total; n++) { + uint32_t name_id = 0; + uint32_t name_offset = 0; + bool name_is_directory = false; + bool name_is_named = false; + if (!resource_entry_at(image, name_base, n, &name_id, &name_offset, + &name_is_directory, &name_is_named)) { + return false; + } + if (name_is_named || !name_is_directory || name_id >= MAX_RESOURCE_ID) continue; + + const size_t language_base = resource_base + name_offset; + size_t language_total = 0; + if (!resource_entry_count(image, language_base, &language_total)) return false; + + for (size_t l = 0; l < language_total; l++) { + uint32_t language_id = 0; + uint32_t language_offset = 0; + bool language_is_directory = false; + bool language_is_named = false; + if (!resource_entry_at(image, language_base, l, &language_id, &language_offset, + &language_is_directory, &language_is_named)) { + return false; + } + if (language_is_directory) continue; + + const uint8_t* data = NULL; + uint32_t size = 0; + if (!resource_read_leaf(image, resource_base + language_offset, &data, &size)) { + continue; + } + out_table->data[name_id] = data; + out_table->size[name_id] = size; + break; + } + } + } + return true; +} + +static bool pe_open(const char* path, PeImage* out_image, int* out_fd, size_t* out_mapped) { + const int fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) return false; + + struct stat info; + if (fstat(fd, &info) != 0 || info.st_size <= 0) { + close(fd); + return false; + } + + const size_t size = (size_t)info.st_size; + void* mapped = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (mapped == MAP_FAILED) { + close(fd); + return false; + } + + out_image->data = (const uint8_t*)mapped; + out_image->size = size; + out_image->sections = NULL; + out_image->section_count = 0; + *out_fd = fd; + *out_mapped = size; + return true; +} + +static void pe_close(PeImage* image, int fd, size_t mapped) { + if (image->sections) { + free(image->sections); + image->sections = NULL; + } + if (image->data) munmap((void*)image->data, mapped); + if (fd >= 0) close(fd); + image->data = NULL; + image->size = 0; +} + +static LsfgStatus parse_resources(PeImage* image, ResourceTable* table) { + size_t pe_offset = 0; + if (!pe_find_header(image, &pe_offset)) return LSFG_NOT_PORTABLE_EXECUTABLE; + if (!pe_read_sections(image, pe_offset)) return LSFG_NOT_PORTABLE_EXECUTABLE; + + size_t data_directory = 0; + if (!pe_find_data_directory(image, pe_offset + 4 + COFF_HEADER_SIZE, &data_directory)) { + return LSFG_NOT_PORTABLE_EXECUTABLE; + } + + uint32_t resource_rva = 0; + if (!pe_read_u32(image, + data_directory + RESOURCE_DATA_DIRECTORY_INDEX * DATA_DIRECTORY_ENTRY_SIZE, + &resource_rva) || + resource_rva == 0) { + return LSFG_MISSING_SHADERS; + } + + size_t resource_base = 0; + if (!pe_rva_to_offset(image, resource_rva, &resource_base)) return LSFG_MISSING_SHADERS; + if (!collect_rcdata(image, resource_base, table)) return LSFG_MISSING_SHADERS; + return LSFG_OK; +} + +static bool is_spirv_module(const uint8_t* blob, uint32_t size) { + if (blob == NULL || size < SPIRV_HEADER_WORDS * sizeof(uint32_t)) return false; + if (size % sizeof(uint32_t) != 0) return false; + uint32_t magic = 0; + memcpy(&magic, blob, sizeof(magic)); + return magic == SPIRV_MAGIC; +} + +static uint32_t lookup_descriptor_set(const uint32_t* words, size_t word_count, uint32_t target) { + size_t offset = SPIRV_HEADER_WORDS; + while (offset < word_count) { + const uint32_t length = words[offset] >> SPIRV_WORD_COUNT_SHIFT; + const uint32_t opcode = words[offset] & SPIRV_OPCODE_MASK; + if (length == 0 || offset + length > word_count) break; + if (opcode == SPIRV_OP_FUNCTION) break; + if (opcode == SPIRV_OP_DECORATE && length >= 4 && + words[offset + 2] == SPIRV_DECORATION_DESCRIPTOR_SET && words[offset + 1] == target) { + return words[offset + 3]; + } + offset += length; + } + return 0; +} + +static bool renumber_bindings(uint32_t* words, size_t word_count) { + size_t slot_count = 0; + size_t offset = SPIRV_HEADER_WORDS; + while (offset < word_count) { + const uint32_t length = words[offset] >> SPIRV_WORD_COUNT_SHIFT; + const uint32_t opcode = words[offset] & SPIRV_OPCODE_MASK; + if (length == 0 || offset + length > word_count) return false; + if (opcode == SPIRV_OP_FUNCTION) break; + if (opcode == SPIRV_OP_DECORATE && length >= 4 && + words[offset + 2] == SPIRV_DECORATION_BINDING) { + slot_count++; + } + offset += length; + } + if (slot_count == 0) return true; + + BindingSlot* slots = (BindingSlot*)calloc(slot_count, sizeof(BindingSlot)); + if (!slots) return false; + + size_t index = 0; + offset = SPIRV_HEADER_WORDS; + while (offset < word_count && index < slot_count) { + const uint32_t length = words[offset] >> SPIRV_WORD_COUNT_SHIFT; + const uint32_t opcode = words[offset] & SPIRV_OPCODE_MASK; + if (length == 0 || offset + length > word_count) break; + if (opcode == SPIRV_OP_FUNCTION) break; + if (opcode == SPIRV_OP_DECORATE && length >= 4 && + words[offset + 2] == SPIRV_DECORATION_BINDING) { + slots[index].binding = words[offset + 3]; + slots[index].literal_offset = offset + DECORATION_LITERAL_WORD; + slots[index].set = lookup_descriptor_set(words, word_count, words[offset + 1]); + index++; + } + offset += length; + } + + if (index != slot_count) { + free(slots); + return false; + } + + for (size_t i = 1; i < slot_count; i++) { + const BindingSlot key = slots[i]; + size_t j = i; + while (j > 0 && (slots[j - 1].set > key.set || + (slots[j - 1].set == key.set && slots[j - 1].binding > key.binding))) { + slots[j] = slots[j - 1]; + j--; + } + slots[j] = key; + } + + for (size_t i = 0; i < slot_count; i++) { + words[slots[i].literal_offset] = (uint32_t)i; + } + + free(slots); + return true; +} + +static bool adopt_spirv(const uint8_t* blob, uint32_t size, uint32_t** out_words, + uint32_t* out_word_count) { + if (!is_spirv_module(blob, size)) return false; + + const size_t word_count = size / sizeof(uint32_t); + if (word_count > MAX_SPIRV_WORDS) return false; + + uint32_t* words = (uint32_t*)malloc(word_count * sizeof(uint32_t)); + if (!words) return false; + memcpy(words, blob, word_count * sizeof(uint32_t)); + + if (!renumber_bindings(words, word_count)) { + free(words); + return false; + } + + *out_words = words; + *out_word_count = (uint32_t)word_count; + return true; +} + +static bool has_native_variant(const ResourceTable* table, uint32_t variant_offset) { + size_t count = 0; + const uint32_t* ids = build_shader_ids(&count); + for (size_t i = 0; i < count; i++) { + const uint32_t id = ids[i] + variant_offset; + if (id >= MAX_RESOURCE_ID) return false; + if (!is_spirv_module(table->data[id], table->size[id])) return false; + } + return true; +} + +static LsfgVariant select_variant(const ResourceTable* table, bool prefer_fp16) { + if (prefer_fp16 && has_native_variant(table, VARIANT_FP16_OFFSET)) return LSFG_VARIANT_FP16; + if (has_native_variant(table, VARIANT_FP32_OFFSET)) return LSFG_VARIANT_FP32; + if (has_native_variant(table, VARIANT_FP16_OFFSET)) return LSFG_VARIANT_FP16; + return LSFG_VARIANT_NONE; +} + +static uint32_t variant_offset(LsfgVariant variant) { + return variant == LSFG_VARIANT_FP16 ? VARIANT_FP16_OFFSET : VARIANT_FP32_OFFSET; +} + +static bool write_cache(const char* cache_path, const CacheHeader* header, + const LsfgModuleSet* set) { + char temp_path[PATH_MAX]; + const int written = snprintf(temp_path, sizeof(temp_path), "%s.tmp", cache_path); + if (written <= 0 || (size_t)written >= sizeof(temp_path)) return false; + + FILE* file = fopen(temp_path, "wb"); + if (!file) return false; + + bool ok = fwrite(header, sizeof(*header), 1, file) == 1; + for (uint32_t i = 0; ok && i < set->count; i++) { + const LsfgModule* module = &set->modules[i]; + ok = fwrite(&module->id, sizeof(module->id), 1, file) == 1 && + fwrite(&module->word_count, sizeof(module->word_count), 1, file) == 1 && + fwrite(module->words, sizeof(uint32_t), module->word_count, file) == + module->word_count; + } + + if (ok) ok = fflush(file) == 0; + if (ok) ok = fsync(fileno(file)) == 0; + fclose(file); + + if (!ok || rename(temp_path, cache_path) != 0) { + unlink(temp_path); + return false; + } + return true; +} + +LsfgStatus lsfg_validate_dll(const char* dll_path) { + if (!dll_path) return LSFG_NOT_INSTALLED; + + PeImage image; + int fd = -1; + size_t mapped_size = 0; + if (!pe_open(dll_path, &image, &fd, &mapped_size)) return LSFG_NOT_INSTALLED; + + ResourceTable* table = (ResourceTable*)calloc(1, sizeof(ResourceTable)); + if (!table) { + pe_close(&image, fd, mapped_size); + return LSFG_UNREADABLE_FILE; + } + + LsfgStatus status = parse_resources(&image, table); + if (status == LSFG_OK && select_variant(table, true) == LSFG_VARIANT_NONE) { + status = LSFG_MISSING_SHADERS; + } + + free(table); + pe_close(&image, fd, mapped_size); + return status; +} + +LsfgStatus lsfg_build_cache(const char* dll_path, const char* cache_path, bool prefer_fp16) { + if (!dll_path || !cache_path) return LSFG_NOT_INSTALLED; + + PeImage image; + int fd = -1; + size_t mapped_size = 0; + if (!pe_open(dll_path, &image, &fd, &mapped_size)) return LSFG_NOT_INSTALLED; + + ResourceTable* table = (ResourceTable*)calloc(1, sizeof(ResourceTable)); + if (!table) { + pe_close(&image, fd, mapped_size); + return LSFG_UNREADABLE_FILE; + } + + LsfgStatus status = parse_resources(&image, table); + if (status != LSFG_OK) { + free(table); + pe_close(&image, fd, mapped_size); + return status; + } + + const LsfgVariant variant = select_variant(table, prefer_fp16); + if (variant == LSFG_VARIANT_NONE) { + free(table); + pe_close(&image, fd, mapped_size); + return LSFG_MISSING_SHADERS; + } + + LsfgModuleSet set; + memset(&set, 0, sizeof(set)); + set.variant = variant; + + const uint32_t offset = variant_offset(variant); + size_t id_count = 0; + const uint32_t* ids = build_shader_ids(&id_count); + for (size_t i = 0; i < id_count; i++) { + const uint32_t resource_id = ids[i] + offset; + uint32_t* words = NULL; + uint32_t word_count = 0; + if (!adopt_spirv(table->data[resource_id], table->size[resource_id], &words, + &word_count)) { + status = LSFG_TRANSLATION_FAILED; + break; + } + set.modules[set.count].id = ids[i]; + set.modules[set.count].words = words; + set.modules[set.count].word_count = word_count; + set.count++; + } + + if (status == LSFG_OK) { + CacheHeader header; + memset(&header, 0, sizeof(header)); + header.magic = CACHE_MAGIC; + header.version = CACHE_VERSION; + header.source_size = (uint64_t)image.size; + header.source_hash = fnv1a64(image.data, image.size); + header.module_count = set.count; + header.variant = (uint32_t)variant; + + if (!write_cache(cache_path, &header, &set)) status = LSFG_CACHE_UNUSABLE; + } + + if (status == LSFG_OK) { + LSFG_LOGI("Cached %u LSFG shader modules (variant=%s)", set.count, + variant == LSFG_VARIANT_FP16 ? "fp16" : "fp32"); + } else { + LSFG_LOGE("Shader cache build failed with status %d", (int)status); + } + + lsfg_release_modules(&set); + free(table); + pe_close(&image, fd, mapped_size); + return status; +} + +LsfgStatus lsfg_load_modules(const char* cache_path, LsfgModuleSet* out_set) { + if (!cache_path || !out_set) return LSFG_CACHE_UNUSABLE; + memset(out_set, 0, sizeof(*out_set)); + + FILE* file = fopen(cache_path, "rb"); + if (!file) return LSFG_NOT_INSTALLED; + + CacheHeader header; + if (fread(&header, sizeof(header), 1, file) != 1 || header.magic != CACHE_MAGIC || + header.version != CACHE_VERSION || header.module_count != LSFG_SHADER_COUNT) { + fclose(file); + return LSFG_CACHE_UNUSABLE; + } + + out_set->variant = (LsfgVariant)header.variant; + + LsfgStatus status = LSFG_OK; + for (uint32_t i = 0; i < header.module_count; i++) { + uint32_t id = 0; + uint32_t word_count = 0; + if (fread(&id, sizeof(id), 1, file) != 1 || + fread(&word_count, sizeof(word_count), 1, file) != 1 || word_count == 0 || + word_count > MAX_SPIRV_WORDS) { + status = LSFG_CACHE_UNUSABLE; + break; + } + + uint32_t* words = (uint32_t*)malloc((size_t)word_count * sizeof(uint32_t)); + if (!words) { + status = LSFG_CACHE_UNUSABLE; + break; + } + if (fread(words, sizeof(uint32_t), word_count, file) != word_count) { + free(words); + status = LSFG_CACHE_UNUSABLE; + break; + } + + out_set->modules[out_set->count].id = id; + out_set->modules[out_set->count].words = words; + out_set->modules[out_set->count].word_count = word_count; + out_set->count++; + } + fclose(file); + + if (status == LSFG_OK) { + size_t id_count = 0; + const uint32_t* ids = build_shader_ids(&id_count); + for (size_t i = 0; i < id_count; i++) { + if (!lsfg_find_module(out_set, ids[i], NULL)) { + status = LSFG_MISSING_SHADERS; + break; + } + } + } + + if (status != LSFG_OK) lsfg_release_modules(out_set); + return status; +} + +void lsfg_release_modules(LsfgModuleSet* set) { + if (!set) return; + for (uint32_t i = 0; i < set->count; i++) { + free(set->modules[i].words); + set->modules[i].words = NULL; + } + set->count = 0; + set->variant = LSFG_VARIANT_NONE; +} + +const uint32_t* lsfg_find_module(const LsfgModuleSet* set, uint32_t id, + uint32_t* out_word_count) { + if (!set) return NULL; + for (uint32_t i = 0; i < set->count; i++) { + if (set->modules[i].id != id) continue; + if (out_word_count) *out_word_count = set->modules[i].word_count; + return set->modules[i].words; + } + return NULL; +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h new file mode 100644 index 000000000..2e009e013 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum LsfgStatus { + LSFG_OK = 0, + LSFG_NOT_INSTALLED = 1, + LSFG_UNREADABLE_FILE = 2, + LSFG_NOT_PORTABLE_EXECUTABLE = 3, + LSFG_MISSING_SHADERS = 4, + LSFG_TRANSLATION_FAILED = 5, + LSFG_CACHE_UNUSABLE = 6 +} LsfgStatus; + +typedef enum LsfgVariant { + LSFG_VARIANT_NONE = 0, + LSFG_VARIANT_FP16 = 1, + LSFG_VARIANT_FP32 = 2 +} LsfgVariant; + +#define LSFG_SHADER_MIPMAPS 255u +#define LSFG_SHADER_GENERATE 256u +#define LSFG_SHADER_PERF_FIRST 280u +#define LSFG_SHADER_PERF_LAST 302u +#define LSFG_SHADER_COUNT 25u + +typedef struct LsfgModule { + uint32_t id; + uint32_t* words; + uint32_t word_count; +} LsfgModule; + +typedef struct LsfgModuleSet { + LsfgModule modules[LSFG_SHADER_COUNT]; + uint32_t count; + LsfgVariant variant; +} LsfgModuleSet; + +const uint32_t* lsfg_shader_ids(size_t* out_count); + +LsfgStatus lsfg_validate_dll(const char* dll_path); + +LsfgStatus lsfg_build_cache(const char* dll_path, const char* cache_path, bool prefer_fp16); + +LsfgStatus lsfg_load_modules(const char* cache_path, LsfgModuleSet* out_set); + +void lsfg_release_modules(LsfgModuleSet* set); + +const uint32_t* lsfg_find_module(const LsfgModuleSet* set, uint32_t id, uint32_t* out_word_count); + +#ifdef __cplusplus +} +#endif diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_jni.c b/app/src/main/cpp/winlator/vk/lsfg/lsfg_jni.c new file mode 100644 index 000000000..f1355efa3 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_jni.c @@ -0,0 +1,75 @@ +#include +#include +#include + +#include "lsfg_dll.h" +#include "lsfg_probe.h" + +#define LSFG_FN(name) Java_com_winlator_cmod_runtime_display_lsfg_LosslessScaling_##name + +static char* copy_utf(JNIEnv* env, jstring value) { + if (!value) return NULL; + const char* chars = (*env)->GetStringUTFChars(env, value, NULL); + if (!chars) return NULL; + char* copy = strdup(chars); + (*env)->ReleaseStringUTFChars(env, value, chars); + return copy; +} + +JNIEXPORT jint JNICALL LSFG_FN(nativeValidateDll)(JNIEnv* env, jclass clazz, jstring dllPath) { + (void)clazz; + char* path = copy_utf(env, dllPath); + if (!path) return (jint)LSFG_NOT_INSTALLED; + const LsfgStatus status = lsfg_validate_dll(path); + free(path); + return (jint)status; +} + +JNIEXPORT jint JNICALL LSFG_FN(nativeBuildCache)(JNIEnv* env, jclass clazz, jstring dllPath, + jstring cachePath, jboolean preferFp16) { + (void)clazz; + char* dll = copy_utf(env, dllPath); + char* cache = copy_utf(env, cachePath); + LsfgStatus status = LSFG_NOT_INSTALLED; + if (dll && cache) status = lsfg_build_cache(dll, cache, preferFp16 == JNI_TRUE); + free(dll); + free(cache); + return (jint)status; +} + +JNIEXPORT jint JNICALL LSFG_FN(nativeInspectCache)(JNIEnv* env, jclass clazz, jstring cachePath) { + (void)clazz; + char* cache = copy_utf(env, cachePath); + if (!cache) return (jint)LSFG_NOT_INSTALLED; + + LsfgModuleSet set; + const LsfgStatus status = lsfg_load_modules(cache, &set); + if (status == LSFG_OK) lsfg_release_modules(&set); + free(cache); + return (jint)status; +} + +JNIEXPORT jint JNICALL LSFG_FN(nativeCacheVariant)(JNIEnv* env, jclass clazz, jstring cachePath) { + (void)clazz; + char* cache = copy_utf(env, cachePath); + if (!cache) return (jint)LSFG_VARIANT_NONE; + + LsfgModuleSet set; + LsfgVariant variant = LSFG_VARIANT_NONE; + if (lsfg_load_modules(cache, &set) == LSFG_OK) { + variant = set.variant; + lsfg_release_modules(&set); + } + free(cache); + return (jint)variant; +} + +JNIEXPORT jboolean JNICALL LSFG_FN(nativeSupportsFrameGeneration)(JNIEnv* env, jclass clazz, + jstring driverName, + jobject context) { + (void)clazz; + char* driver = copy_utf(env, driverName); + const bool supported = lsfg_probe_support(env, context, driver); + free(driver); + return supported ? JNI_TRUE : JNI_FALSE; +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_probe.c b/app/src/main/cpp/winlator/vk/lsfg/lsfg_probe.c new file mode 100644 index 000000000..9f11f2a5a --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_probe.c @@ -0,0 +1,169 @@ +#include "lsfg_probe.h" + +#include +#include +#include +#include +#include + +#include "vk_driver.h" + +#define LOG_TAG "LsfgProbe" +#define PROBE_LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define PROBE_LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) + +#define REQUIRED_FEATURES \ + (VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) + +#define MAX_PROBE_DEVICES 8 +#define MAX_PROBE_QUEUE_FAMILIES 16 + +typedef struct ProbeApi { + PFN_vkGetInstanceProcAddr GetInstanceProcAddr; + PFN_vkCreateInstance CreateInstance; + PFN_vkDestroyInstance DestroyInstance; + PFN_vkEnumeratePhysicalDevices EnumeratePhysicalDevices; + PFN_vkGetPhysicalDeviceProperties GetPhysicalDeviceProperties; + PFN_vkGetPhysicalDeviceFormatProperties GetPhysicalDeviceFormatProperties; + PFN_vkGetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceQueueFamilyProperties; +} ProbeApi; + +static const VkFormat kRequiredFormats[] = { + VK_FORMAT_R8G8B8A8_UNORM, + VK_FORMAT_R8_UNORM, + VK_FORMAT_R16G16B16A16_SFLOAT, +}; + +static bool load_probe_api(void* library, ProbeApi* api) { + memset(api, 0, sizeof(*api)); + + api->GetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym(library, "vkGetInstanceProcAddr"); + if (!api->GetInstanceProcAddr) return false; + + api->CreateInstance = + (PFN_vkCreateInstance)api->GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"); + return api->CreateInstance != NULL; +} + +static bool load_instance_api(ProbeApi* api, VkInstance instance) { + api->DestroyInstance = + (PFN_vkDestroyInstance)api->GetInstanceProcAddr(instance, "vkDestroyInstance"); + api->EnumeratePhysicalDevices = (PFN_vkEnumeratePhysicalDevices)api->GetInstanceProcAddr( + instance, "vkEnumeratePhysicalDevices"); + api->GetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties)api->GetInstanceProcAddr( + instance, "vkGetPhysicalDeviceProperties"); + api->GetPhysicalDeviceFormatProperties = + (PFN_vkGetPhysicalDeviceFormatProperties)api->GetInstanceProcAddr( + instance, "vkGetPhysicalDeviceFormatProperties"); + api->GetPhysicalDeviceQueueFamilyProperties = + (PFN_vkGetPhysicalDeviceQueueFamilyProperties)api->GetInstanceProcAddr( + instance, "vkGetPhysicalDeviceQueueFamilyProperties"); + + return api->DestroyInstance && api->EnumeratePhysicalDevices && + api->GetPhysicalDeviceProperties && api->GetPhysicalDeviceFormatProperties && + api->GetPhysicalDeviceQueueFamilyProperties; +} + +static bool has_compute_queue(const ProbeApi* api, VkPhysicalDevice device) { + uint32_t count = 0; + api->GetPhysicalDeviceQueueFamilyProperties(device, &count, NULL); + if (count == 0) return false; + if (count > MAX_PROBE_QUEUE_FAMILIES) count = MAX_PROBE_QUEUE_FAMILIES; + + VkQueueFamilyProperties families[MAX_PROBE_QUEUE_FAMILIES]; + api->GetPhysicalDeviceQueueFamilyProperties(device, &count, families); + + for (uint32_t i = 0; i < count; i++) { + if (families[i].queueCount > 0 && (families[i].queueFlags & VK_QUEUE_COMPUTE_BIT)) { + return true; + } + } + return false; +} + +static bool has_required_formats(const ProbeApi* api, VkPhysicalDevice device) { + const size_t format_count = sizeof(kRequiredFormats) / sizeof(kRequiredFormats[0]); + for (size_t i = 0; i < format_count; i++) { + VkFormatProperties properties; + memset(&properties, 0, sizeof(properties)); + api->GetPhysicalDeviceFormatProperties(device, kRequiredFormats[i], &properties); + if ((properties.optimalTilingFeatures & REQUIRED_FEATURES) != REQUIRED_FEATURES) { + PROBE_LOGW("Format %d lacks storage or sampled support", (int)kRequiredFormats[i]); + return false; + } + } + return true; +} + +static bool probe_instance(ProbeApi* api, VkInstance instance) { + uint32_t device_count = 0; + if (api->EnumeratePhysicalDevices(instance, &device_count, NULL) != VK_SUCCESS || + device_count == 0) { + return false; + } + if (device_count > MAX_PROBE_DEVICES) device_count = MAX_PROBE_DEVICES; + + VkPhysicalDevice devices[MAX_PROBE_DEVICES]; + if (api->EnumeratePhysicalDevices(instance, &device_count, devices) != VK_SUCCESS) { + return false; + } + + for (uint32_t i = 0; i < device_count; i++) { + if (!has_compute_queue(api, devices[i])) continue; + if (!has_required_formats(api, devices[i])) continue; + + VkPhysicalDeviceProperties properties; + memset(&properties, 0, sizeof(properties)); + api->GetPhysicalDeviceProperties(devices[i], &properties); + PROBE_LOGI("Frame generation supported on %s", properties.deviceName); + return true; + } + return false; +} + +bool lsfg_probe_support(JNIEnv* env, jobject context, const char* driver_name) { + void* library = winlator_open_vulkan(env, context, driver_name); + if (!library) { + PROBE_LOGW("Vulkan driver could not be opened for probing"); + return false; + } + + ProbeApi api; + if (!load_probe_api(library, &api)) { + PROBE_LOGW("vkGetInstanceProcAddr unavailable in the selected driver"); + dlclose(library); + return false; + } + + VkApplicationInfo app_info; + memset(&app_info, 0, sizeof(app_info)); + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.pApplicationName = "WinNative"; + app_info.apiVersion = VK_API_VERSION_1_1; + + VkInstanceCreateInfo create_info; + memset(&create_info, 0, sizeof(create_info)); + create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + create_info.pApplicationInfo = &app_info; + + VkInstance instance = VK_NULL_HANDLE; + if (api.CreateInstance(&create_info, NULL, &instance) != VK_SUCCESS) { + app_info.apiVersion = VK_API_VERSION_1_0; + if (api.CreateInstance(&create_info, NULL, &instance) != VK_SUCCESS) { + PROBE_LOGW("vkCreateInstance failed during probe"); + dlclose(library); + return false; + } + } + + bool supported = false; + if (load_instance_api(&api, instance)) { + supported = probe_instance(&api, instance); + } + + if (api.DestroyInstance) api.DestroyInstance(instance, NULL); + dlclose(library); + + if (!supported) PROBE_LOGI("Frame generation unsupported on this device"); + return supported; +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_probe.h b/app/src/main/cpp/winlator/vk/lsfg/lsfg_probe.h new file mode 100644 index 000000000..d81a82b56 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_probe.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool lsfg_probe_support(JNIEnv* env, jobject context, const char* driver_name); + +#ifdef __cplusplus +} +#endif diff --git a/app/src/main/runtime/display/lsfg/LosslessScaling.java b/app/src/main/runtime/display/lsfg/LosslessScaling.java new file mode 100644 index 000000000..b94ee13f7 --- /dev/null +++ b/app/src/main/runtime/display/lsfg/LosslessScaling.java @@ -0,0 +1,243 @@ +package com.winlator.cmod.runtime.display.lsfg; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +import com.winlator.cmod.runtime.container.Container; +import com.winlator.cmod.runtime.system.ApplicationLogGate; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; + +public final class LosslessScaling { + public static final int STATUS_OK = 0; + public static final int STATUS_NOT_INSTALLED = 1; + public static final int STATUS_UNREADABLE_FILE = 2; + public static final int STATUS_NOT_PORTABLE_EXECUTABLE = 3; + public static final int STATUS_MISSING_SHADERS = 4; + public static final int STATUS_TRANSLATION_FAILED = 5; + public static final int STATUS_CACHE_UNUSABLE = 6; + + public static final int VARIANT_NONE = 0; + public static final int VARIANT_FP16 = 1; + public static final int VARIANT_FP32 = 2; + + private static final String TAG = "LosslessScaling"; + private static final String DLL_NAME = "Lossless.dll"; + private static final String STORE_DIR = "lsfg"; + private static final String CACHE_FP32 = "shaders-fp32.cache"; + private static final String CACHE_FP16 = "shaders-fp16.cache"; + private static final String STAGED_DLL = "Lossless.staged"; + + private static final String[] DRIVE_C_CANDIDATES = { + "Program Files (x86)/Steam/steamapps/common/Lossless Scaling/" + DLL_NAME, + "Program Files/Steam/steamapps/common/Lossless Scaling/" + DLL_NAME, + "Program Files (x86)/Lossless Scaling/" + DLL_NAME, + "Program Files/Lossless Scaling/" + DLL_NAME, + }; + + private static final String[] DRIVE_ROOT_CANDIDATES = { + "SteamLibrary/steamapps/common/Lossless Scaling/" + DLL_NAME, + "steamapps/common/Lossless Scaling/" + DLL_NAME, + "Steam/steamapps/common/Lossless Scaling/" + DLL_NAME, + "Lossless Scaling/" + DLL_NAME, + DLL_NAME, + }; + + private static Boolean gpuSupported = null; + private static String gpuSupportedDriver = null; + + static { + System.loadLibrary("winlator"); + } + + private LosslessScaling() {} + + public static File getStoreDir(Context context) { + return new File(context.getFilesDir(), STORE_DIR); + } + + public static File getCacheFile(Context context, boolean fp16) { + return new File(getStoreDir(context), fp16 ? CACHE_FP16 : CACHE_FP32); + } + + public static File resolveCacheFile(Context context, boolean preferFp16) { + File preferred = getCacheFile(context, preferFp16); + if (preferred.isFile()) return preferred; + File fallback = getCacheFile(context, !preferFp16); + return fallback.isFile() ? fallback : null; + } + + public static boolean isInstalled(Context context) { + return resolveCacheFile(context, false) != null; + } + + public static int getStatus(Context context, boolean preferFp16) { + File cache = resolveCacheFile(context, preferFp16); + if (cache == null) return STATUS_NOT_INSTALLED; + return nativeInspectCache(cache.getAbsolutePath()); + } + + public static int getVariant(Context context, boolean preferFp16) { + File cache = resolveCacheFile(context, preferFp16); + if (cache == null) return VARIANT_NONE; + return nativeCacheVariant(cache.getAbsolutePath()); + } + + public static int validate(File dll) { + if (dll == null || !dll.isFile()) return STATUS_NOT_INSTALLED; + return nativeValidateDll(dll.getAbsolutePath()); + } + + public static int installFrom(Context context, File dll) { + if (dll == null || !dll.isFile()) return STATUS_NOT_INSTALLED; + + File store = getStoreDir(context); + if (!store.isDirectory() && !store.mkdirs()) return STATUS_CACHE_UNUSABLE; + + File fp32 = getCacheFile(context, false); + File fp16 = getCacheFile(context, true); + deleteQuietly(fp32); + deleteQuietly(fp16); + + final String source = dll.getAbsolutePath(); + int status = nativeBuildCache(source, fp32.getAbsolutePath(), false); + if (status != STATUS_OK) { + deleteQuietly(fp32); + return status; + } + + if (nativeCacheVariant(fp32.getAbsolutePath()) == VARIANT_FP16) { + if (!fp32.renameTo(fp16)) { + deleteQuietly(fp32); + return STATUS_CACHE_UNUSABLE; + } + logInstalled(VARIANT_FP16, false); + return STATUS_OK; + } + + if (nativeBuildCache(source, fp16.getAbsolutePath(), true) != STATUS_OK + || nativeCacheVariant(fp16.getAbsolutePath()) != VARIANT_FP16) { + deleteQuietly(fp16); + logInstalled(VARIANT_FP32, false); + return STATUS_OK; + } + + logInstalled(VARIANT_FP32, true); + return STATUS_OK; + } + + public static int installFrom(Context context, Uri uri) { + if (uri == null) return STATUS_NOT_INSTALLED; + + File staged = new File(context.getCacheDir(), STORE_DIR + File.separator + STAGED_DLL); + File parent = staged.getParentFile(); + if (parent != null && !parent.isDirectory() && !parent.mkdirs()) { + return STATUS_CACHE_UNUSABLE; + } + + try { + copyToFile(context, uri, staged); + } catch (IOException e) { + deleteQuietly(staged); + Log.w(TAG, "Unable to stage the selected file", e); + return STATUS_UNREADABLE_FILE; + } + + try { + return installFrom(context, staged); + } finally { + deleteQuietly(staged); + } + } + + public static boolean remove(Context context) { + boolean removed = deleteQuietly(getCacheFile(context, false)); + removed |= deleteQuietly(getCacheFile(context, true)); + return removed; + } + + public static List findInContainers(Collection containers) { + LinkedHashSet found = new LinkedHashSet<>(); + if (containers == null) return new ArrayList<>(found); + + for (Container container : containers) { + if (container == null || container.getRootDir() == null) continue; + + File driveC = new File(container.getRootDir(), ".wine/drive_c"); + for (String candidate : DRIVE_C_CANDIDATES) { + addIfReadable(found, new File(driveC, candidate)); + } + + for (String[] drive : container.drivesIterator()) { + if (drive.length < 2 || drive[1] == null || drive[1].isEmpty()) continue; + File root = new File(drive[1]); + for (String candidate : DRIVE_ROOT_CANDIDATES) { + addIfReadable(found, new File(root, candidate)); + } + } + } + return new ArrayList<>(found); + } + + public static boolean isSupportedByGpu(Context context, String driverName) { + final String key = driverName == null ? "" : driverName; + if (gpuSupported != null && key.equals(gpuSupportedDriver)) return gpuSupported; + + boolean supported = nativeSupportsFrameGeneration(driverName, context); + gpuSupported = supported; + gpuSupportedDriver = key; + return supported; + } + + public static void invalidateGpuSupport() { + gpuSupported = null; + gpuSupportedDriver = null; + } + + private static void logInstalled(int variant, boolean bothVariants) { + if (!ApplicationLogGate.isEnabled()) return; + Log.i(TAG, "Shader cache built: variant=" + + (variant == VARIANT_FP16 ? "fp16" : "fp32") + + (bothVariants ? " (fp16 alternate available)" : "")); + } + + private static void addIfReadable(LinkedHashSet target, File candidate) { + if (candidate.isFile() && candidate.canRead()) target.add(candidate); + } + + private static void copyToFile(Context context, Uri uri, File destination) throws IOException { + try (InputStream input = context.getContentResolver().openInputStream(uri)) { + if (input == null) throw new IOException("openInputStream returned null"); + try (OutputStream output = new FileOutputStream(destination)) { + byte[] buffer = new byte[64 * 1024]; + int read; + while ((read = input.read(buffer)) > 0) output.write(buffer, 0, read); + output.flush(); + } + } + } + + private static boolean deleteQuietly(File file) { + return file != null && file.isFile() && file.delete(); + } + + private static native int nativeValidateDll(String dllPath); + + private static native int nativeBuildCache(String dllPath, String cachePath, + boolean preferFp16); + + private static native int nativeInspectCache(String cachePath); + + private static native int nativeCacheVariant(String cachePath); + + private static native boolean nativeSupportsFrameGeneration(String driverName, Context context); +} From 8501ad5540e11c1c0bcb11ec230dd2632c1ba688 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sun, 23 Aug 2026 01:57:45 -0400 Subject: [PATCH 03/35] Land the composited frame in an owned image before it reaches the swapchain Second phase of Android-side frame generation. Frame generation itself is still absent; this only builds the target it will need and leaves it off. Interpolation has to read the frame it just presented, and the next one has to be generated straight into a storage image. Android swapchain images serve neither: they are created COLOR_ATTACHMENT only, and storage support on a swapchain format is not something a driver owes us. So when the path is armed the scene and effect chain now end in a composite target the renderer owns, and a blit moves that into the acquired swapchain image. The composite pass carries the same attachment format as the swapchain pass so every existing scene and effect pipeline stays render-pass compatible with both, and only the final layout differs: GENERAL, so a compute dispatch and the blit can both read the finished frame. Targets rotate on frame_index, which gives the two-deep history the chain wants for free and is already fenced by the per-frame in-flight wait. Kept off the default path deliberately. With frame generation unrequested the render pass, framebuffer and submit are the ones that ran before, and the swapchain is created with exactly its previous usage flags - the TRANSFER_DST the blit needs is requested only while the path is armed, and toggling it recreates the swapchain the way the recorder's TRANSFER_SRC already does, so nobody pays for framebuffer compression they lose to a usage flag they never use. Support is gated on the live swapchain format rather than assumed: storage, sampled, colour attachment and both blit bits, plus TRANSFER_DST being offered at all. A device that fails the check reports unsupported up front instead of failing later at pipeline creation. One bug found while reviewing this and fixed here rather than left for the next phase: the acquire semaphore was waited on at COLOR_ATTACHMENT_OUTPUT, which was correct while a render pass was the first thing to touch the swapchain image. On the composite path the first access is a transfer write, and TRANSFER precedes COLOR_ATTACHMENT_OUTPUT in pipeline order, so the blit could have run before the presentation engine released the image. The wait stage now includes TRANSFER whenever the composite path is active. Native and Java both build clean. The composite path itself still needs running on a real Wine session to confirm, which the x86_64 emulator here cannot host; validation layers can be turned on through the existing enable_vulkan_validation_layers preference when it is. --- app/src/main/cpp/winlator/vk/vk_renderer.c | 262 +++++++++++++++++- app/src/main/cpp/winlator/vk/vk_state.h | 17 ++ .../display/renderer/VulkanRenderer.java | 20 ++ 3 files changed, 294 insertions(+), 5 deletions(-) diff --git a/app/src/main/cpp/winlator/vk/vk_renderer.c b/app/src/main/cpp/winlator/vk/vk_renderer.c index d065ed625..84bd0f230 100644 --- a/app/src/main/cpp/winlator/vk/vk_renderer.c +++ b/app/src/main/cpp/winlator/vk/vk_renderer.c @@ -66,6 +66,9 @@ static bool create_offscreen(VkRenderer* r, uint32_t w, uint32_t h, bool need_se static void destroy_offscreen(VkRenderer* r); static bool create_sgsr1_resources(VkRenderer* r, uint32_t w, uint32_t h); static void destroy_sgsr1_resources(VkRenderer* r); +static void destroy_composite_targets(VkRenderer* r); +static bool create_composite_targets(VkRenderer* r, uint32_t w, uint32_t h, uint32_t count); +static bool composite_format_supported(VkRenderer* r); static bool create_quad_vbo(VkRenderer* r); static void destroy_quad_vbo(VkRenderer* r); static bool is_plain_rotation_transform(VkSurfaceTransformFlagBitsKHR transform); @@ -691,6 +694,52 @@ static bool create_render_passes(VkRenderer* r) { } } + { + VkAttachmentDescription att = {0}; + att.format = r->swapchain_format; + att.samples = VK_SAMPLE_COUNT_1_BIT; + att.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + att.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + att.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + att.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + att.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + att.finalLayout = VK_IMAGE_LAYOUT_GENERAL; + + VkAttachmentReference ref = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + + VkSubpassDescription sp = {0}; + sp.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + sp.colorAttachmentCount = 1; + sp.pColorAttachments = &ref; + + VkSubpassDependency deps[2] = {0}; + deps[0].srcSubpass = VK_SUBPASS_EXTERNAL; + deps[0].dstSubpass = 0; + deps[0].srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT + | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + deps[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + deps[0].srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT; + deps[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + deps[1].srcSubpass = 0; + deps[1].dstSubpass = VK_SUBPASS_EXTERNAL; + deps[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + deps[1].dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT + | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + deps[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + deps[1].dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT; + + VkRenderPassCreateInfo rci = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; + rci.attachmentCount = 1; + rci.pAttachments = &att; + rci.subpassCount = 1; + rci.pSubpasses = &sp; + rci.dependencyCount = 2; + rci.pDependencies = deps; + if (vkCreateRenderPass(r->device, &rci, NULL, &r->pipelines.composite_pass) != VK_SUCCESS) { + return false; + } + } + return true; } @@ -1039,6 +1088,7 @@ static void destroy_pipelines(VkRenderer* r) { if (r->pipelines.sampler_set_layout) vkDestroyDescriptorSetLayout(r->device, r->pipelines.sampler_set_layout, NULL); if (r->pipelines.swapchain_pass) vkDestroyRenderPass(r->device, r->pipelines.swapchain_pass, NULL); if (r->pipelines.offscreen_pass) vkDestroyRenderPass(r->device, r->pipelines.offscreen_pass, NULL); + if (r->pipelines.composite_pass) vkDestroyRenderPass(r->device, r->pipelines.composite_pass, NULL); memset(&r->pipelines, 0, sizeof(r->pipelines)); r->pipelines_built = false; } @@ -1205,6 +1255,9 @@ static bool create_swapchain(VkRenderer* r, uint32_t fallback_width, uint32_t fa && (caps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT)) { sci.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; // blit source for the encoder mirror } + bool transfer_dst_capable = (caps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0; + r->swapchain_transfer_dst = r->framegen_requested && transfer_dst_capable; + if (r->swapchain_transfer_dst) sci.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; sci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; sci.preTransform = pre_transform; sci.compositeAlpha = (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) @@ -1245,6 +1298,7 @@ static bool create_swapchain(VkRenderer* r, uint32_t fallback_width, uint32_t fa goto fail; } r->swapchain_image_count = got; + r->framegen_supported = transfer_dst_capable && composite_format_supported(r); if (!r->pipelines_built) { if (!create_pipelines(r)) goto fail; @@ -1305,6 +1359,7 @@ static void destroy_swapchain_resources(VkRenderer* r) { } static void destroy_swapchain(VkRenderer* r) { + destroy_composite_targets(r); destroy_swapchain_resources(r); if (r->swapchain) { vkDestroySwapchainKHR(r->device, r->swapchain, NULL); r->swapchain = VK_NULL_HANDLE; } } @@ -1681,6 +1736,107 @@ static void destroy_offscreen(VkRenderer* r) { r->offscreen_built = false; } +static void destroy_one_composite(VkRenderer* r, VkCompositeTarget* c) { + if (c->framebuffer) vkDestroyFramebuffer(r->device, c->framebuffer, NULL); + if (c->view) vkDestroyImageView(r->device, c->view, NULL); + if (c->image) vkDestroyImage(r->device, c->image, NULL); + if (c->memory) vkFreeMemory(r->device, c->memory, NULL); + memset(c, 0, sizeof(*c)); +} + +static void destroy_composite_targets(VkRenderer* r) { + for (uint32_t i = 0; i < VK_MAX_COMPOSITE_TARGETS; i++) { + destroy_one_composite(r, &r->composite[i]); + } + r->composite_count = 0; + r->composite_built = false; +} + +static bool create_one_composite(VkRenderer* r, VkCompositeTarget* c, uint32_t w, uint32_t h) { + c->width = w; + c->height = h; + + VkImageCreateInfo ic = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + ic.imageType = VK_IMAGE_TYPE_2D; + ic.format = r->swapchain_format; + ic.extent.width = w; + ic.extent.height = h; + ic.extent.depth = 1; + ic.mipLevels = 1; + ic.arrayLayers = 1; + ic.samples = VK_SAMPLE_COUNT_1_BIT; + ic.tiling = VK_IMAGE_TILING_OPTIMAL; + ic.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT + | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + ic.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + ic.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + if (vkCreateImage(r->device, &ic, NULL, &c->image) != VK_SUCCESS) return false; + + VkMemoryRequirements mr; + vkGetImageMemoryRequirements(r->device, c->image, &mr); + VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + ai.allocationSize = mr.size; + ai.memoryTypeIndex = vkr_find_memory_type(r, mr.memoryTypeBits, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (ai.memoryTypeIndex == UINT32_MAX) return false; + if (vkAllocateMemory(r->device, &ai, NULL, &c->memory) != VK_SUCCESS) return false; + vkBindImageMemory(r->device, c->image, c->memory, 0); + + VkImageViewCreateInfo vi = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + vi.image = c->image; + vi.viewType = VK_IMAGE_VIEW_TYPE_2D; + vi.format = ic.format; + vi.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + vi.subresourceRange.levelCount = 1; + vi.subresourceRange.layerCount = 1; + if (vkCreateImageView(r->device, &vi, NULL, &c->view) != VK_SUCCESS) return false; + + VkFramebufferCreateInfo fbci = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + fbci.renderPass = r->pipelines.composite_pass; + fbci.attachmentCount = 1; + fbci.pAttachments = &c->view; + fbci.width = w; + fbci.height = h; + fbci.layers = 1; + if (vkCreateFramebuffer(r->device, &fbci, NULL, &c->framebuffer) != VK_SUCCESS) return false; + + return true; +} + +static bool create_composite_targets(VkRenderer* r, uint32_t w, uint32_t h, uint32_t count) { + if (count == 0 || count > VK_MAX_COMPOSITE_TARGETS) return false; + if (r->composite_built && r->composite_count == count + && r->composite[0].width == w && r->composite[0].height == h) { + return true; + } + + destroy_composite_targets(r); + for (uint32_t i = 0; i < count; i++) { + if (!create_one_composite(r, &r->composite[i], w, h)) { + destroy_composite_targets(r); + return false; + } + } + r->composite_count = count; + r->composite_built = true; + return true; +} + +static bool composite_format_supported(VkRenderer* r) { + if (r->swapchain_format == VK_FORMAT_UNDEFINED) return false; + + VkFormatProperties props; + memset(&props, 0, sizeof(props)); + vkGetPhysicalDeviceFormatProperties(r->physical_device, r->swapchain_format, &props); + + const VkFormatFeatureFlags required = VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT + | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT + | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT + | VK_FORMAT_FEATURE_BLIT_SRC_BIT + | VK_FORMAT_FEATURE_BLIT_DST_BIT; + return (props.optimalTilingFeatures & required) == required; +} + static bool create_sgsr1_resources(VkRenderer* r, uint32_t w, uint32_t h) { if (r->sgsr1.built && r->sgsr1.width == w && r->sgsr1.height == h) return true; @@ -2182,6 +2338,28 @@ static bool record_and_submit_frame(VkRenderer* r) { destroy_sgsr1_resources(r); } + bool via_composite = r->framegen_requested && r->framegen_supported + && r->swapchain_transfer_dst; + if (via_composite) { + bool composite_stale = !r->composite_built + || r->composite[0].width != r->swapchain_extent.width + || r->composite[0].height != r->swapchain_extent.height; + if (composite_stale) { + wait_inflight_frames(r); + if (!create_composite_targets(r, r->swapchain_extent.width, + r->swapchain_extent.height, VK_FRAMES_IN_FLIGHT)) { + VK_LOGW("Composite targets unavailable; frame generation path disabled"); + r->framegen_supported = false; + via_composite = false; + } + } + } else if (r->composite_built) { + wait_inflight_frames(r); + destroy_composite_targets(r); + } + + VkCompositeTarget* composite = via_composite ? &r->composite[r->frame_index] : NULL; + uint32_t image_index = 0; VkResult acq = vkAcquireNextImageKHR(r->device, r->swapchain, UINT64_MAX, f->image_available, VK_NULL_HANDLE, &image_index); @@ -2254,6 +2432,11 @@ static bool record_and_submit_frame(VkRenderer* r) { has_effects = full_ok && (!wants_sgsr1 || r->sgsr1.built); } + VkRenderPass final_pass = composite ? r->pipelines.composite_pass + : r->pipelines.swapchain_pass; + VkFramebuffer final_fb = composite ? composite->framebuffer + : r->swapchain_framebuffers[image_index]; + VkClearValue clear = {0}; clear.color.float32[0] = 0.0f; clear.color.float32[1] = 0.0f; @@ -2287,8 +2470,8 @@ static bool record_and_submit_frame(VkRenderer* r) { VkEffectSlot* eff = &snap.effects[i]; if (last) { - rpbi.renderPass = r->pipelines.swapchain_pass; - rpbi.framebuffer = r->swapchain_framebuffers[image_index]; + rpbi.renderPass = final_pass; + rpbi.framebuffer = final_fb; rpbi.renderArea.extent = r->swapchain_extent; } else { rpbi.renderPass = r->pipelines.offscreen_pass; @@ -2308,8 +2491,8 @@ static bool record_and_submit_frame(VkRenderer* r) { } } else { VkRenderPassBeginInfo rpbi = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; - rpbi.renderPass = r->pipelines.swapchain_pass; - rpbi.framebuffer = r->swapchain_framebuffers[image_index]; + rpbi.renderPass = final_pass; + rpbi.framebuffer = final_fb; rpbi.renderArea.extent = r->swapchain_extent; rpbi.clearValueCount = 1; rpbi.pClearValues = &clear; @@ -2319,6 +2502,33 @@ static bool record_and_submit_frame(VkRenderer* r) { vkCmdEndRenderPass(f->cmd); } + if (composite) { + VkImage disp_img = r->swapchain_images[image_index]; + vkr_image_barrier(f->cmd, disp_img, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, VK_ACCESS_TRANSFER_WRITE_BIT); + + VkImageBlit blit = {0}; + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.layerCount = 1; + blit.srcOffsets[1].x = (int32_t)composite->width; + blit.srcOffsets[1].y = (int32_t)composite->height; + blit.srcOffsets[1].z = 1; + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.layerCount = 1; + blit.dstOffsets[1].x = (int32_t)r->swapchain_extent.width; + blit.dstOffsets[1].y = (int32_t)r->swapchain_extent.height; + blit.dstOffsets[1].z = 1; + vkCmdBlitImage(f->cmd, composite->image, VK_IMAGE_LAYOUT_GENERAL, + disp_img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_NEAREST); + + vkr_image_barrier(f->cmd, disp_img, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, 0); + } + // Blit the final composited image (in PRESENT_SRC after the render pass) into the encoder image. if (rec_this_frame) { VkImage disp_img = r->swapchain_images[image_index]; @@ -2389,7 +2599,9 @@ static bool record_and_submit_frame(VkRenderer* r) { // The mirror's acquire/present-ready semaphores are appended only when capturing this frame. VkPipelineStageFlags wait_stages[2] = { - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT }; + composite ? (VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT) + : VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT }; VkSemaphore wait_sems[2] = { f->image_available, r->rec.acquire[r->frame_index] }; VkSemaphore signal_sems[2] = { render_finished, rec_this_frame ? r->rec.present_ready[rec_index] : VK_NULL_HANDLE }; @@ -3088,6 +3300,46 @@ JNIEXPORT void JNICALL JNI_FN(nativeSetPresentMode)(JNIEnv* env, jclass clazz, j pthread_mutex_unlock(&r->render_mutex); } +JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationEnabled)(JNIEnv* env, jclass clazz, + jlong handle, jboolean enabled) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r) return; + + const bool want = (enabled == JNI_TRUE); + if (r->framegen_requested == want) return; + + pthread_mutex_lock(&r->render_mutex); + r->framegen_requested = want; + if (r->surface && r->swapchain) { + lifecycle_begin(r); + if (r->device) vkDeviceWaitIdle(r->device); + uint32_t fw = r->surface_extent.width; + uint32_t fh = r->surface_extent.height; + destroy_sgsr1_resources(r); + destroy_offscreen(r); + destroy_swapchain(r); + if (!create_swapchain(r, fw, fh)) { + VK_LOGE("Swapchain re-create failed in nativeSetFrameGenerationEnabled"); + } else { + pthread_mutex_lock(&r->scene_mutex); + r->surface_ready = true; + pthread_mutex_unlock(&r->scene_mutex); + } + } + pthread_mutex_unlock(&r->render_mutex); + VK_LOGI("Frame generation composite path %s (supported=%d)", + want ? "enabled" : "disabled", (int)r->framegen_supported); +} + +JNIEXPORT jboolean JNICALL JNI_FN(nativeIsFrameGenerationSupported)(JNIEnv* env, jclass clazz, + jlong handle) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r) return JNI_FALSE; + return r->framegen_supported ? JNI_TRUE : JNI_FALSE; +} + // ============================================================ // JNI entry points for Java Texture / GPUImage // ============================================================ diff --git a/app/src/main/cpp/winlator/vk/vk_state.h b/app/src/main/cpp/winlator/vk/vk_state.h index 0eae82af5..a7f39a10e 100644 --- a/app/src/main/cpp/winlator/vk/vk_state.h +++ b/app/src/main/cpp/winlator/vk/vk_state.h @@ -25,6 +25,7 @@ // Encoder input-surface swapchains can expose many more images than a display swapchain. #define VK_MAX_RECORD_IMAGES 32 #define VK_MAX_EFFECTS 8 +#define VK_MAX_COMPOSITE_TARGETS 8 #define VK_MAX_RENDERABLE_WINDOWS 64 // Number of in-flight upload slots. Each slot owns a persistently-mapped staging buffer, // fence, and command pool. An upload only blocks when this many uploads are still pending @@ -188,6 +189,7 @@ typedef struct VkPipelineSet { // Render passes VkRenderPass swapchain_pass; // load=clear, store=store, final=present VkRenderPass offscreen_pass; // load=clear, store=store, final=shader-read + VkRenderPass composite_pass; } VkPipelineSet; // ============================================================ @@ -221,6 +223,14 @@ typedef struct VkSgsr1State { uint32_t height; } VkSgsr1State; +typedef struct VkCompositeTarget { + VkImage image; + VkImageView view; + VkDeviceMemory memory; + VkFramebuffer framebuffer; + uint32_t width, height; +} VkCompositeTarget; + // Recording mirror: a second swapchain on a MediaCodec input surface; each frame is blitted from // the display swapchain into it and co-presented. Gated on rec.active. typedef struct VkRecordSwap { @@ -398,6 +408,13 @@ typedef struct VkRenderer { bool offscreen_built; VkSgsr1State sgsr1; + VkCompositeTarget composite[VK_MAX_COMPOSITE_TARGETS]; + uint32_t composite_count; + bool composite_built; + bool framegen_supported; + bool framegen_requested; + bool swapchain_transfer_dst; + // record_blit_src adds TRANSFER_SRC usage to the display swapchain (toggled by start/stop recording). bool record_blit_src; VkRecordSwap rec; diff --git a/app/src/main/runtime/display/renderer/VulkanRenderer.java b/app/src/main/runtime/display/renderer/VulkanRenderer.java index a804fe339..5f3131a72 100644 --- a/app/src/main/runtime/display/renderer/VulkanRenderer.java +++ b/app/src/main/runtime/display/renderer/VulkanRenderer.java @@ -217,6 +217,9 @@ public void attachSurface(Surface surface) { if (requestedScaleFilter != SCALE_FILTER_OFF) { nativeSetScaleFilter(nativeHandle, requestedScaleFilter); } + if (frameGenerationRequested) { + nativeSetFrameGenerationEnabled(nativeHandle, true); + } destroyed.set(false); xServer.windowManager.addOnWindowModificationListener(this); xServer.pointer.addOnPointerMotionListener(this); @@ -884,6 +887,21 @@ public void setPresentMode(int mode) { if (nativeHandle != 0) nativeSetPresentMode(nativeHandle, mode); } + private boolean frameGenerationRequested = false; + + public void setFrameGenerationEnabled(boolean enabled) { + frameGenerationRequested = enabled; + if (nativeHandle != 0) nativeSetFrameGenerationEnabled(nativeHandle, enabled); + } + + public boolean isFrameGenerationRequested() { + return frameGenerationRequested; + } + + public boolean isFrameGenerationSupported() { + return nativeHandle != 0 && nativeIsFrameGenerationSupported(nativeHandle); + } + public static int parsePresentMode(String name) { if (name == null) return PRESENT_MODE_FIFO; switch (name.trim().toLowerCase()) { @@ -937,4 +955,6 @@ private static native long nativeCreate(boolean enableValidationLayers, private static native void nativeSetFpsLimit(long handle, int fps); private static native void nativeSetPresentMode(long handle, int mode); private static native void nativeSetScaleFilter(long handle, int mode); + private static native void nativeSetFrameGenerationEnabled(long handle, boolean enabled); + private static native boolean nativeIsFrameGenerationSupported(long handle); } From b539df6c8ce5bac440ff2391cd30df1e1d205d1d Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sun, 23 Aug 2026 02:21:43 -0400 Subject: [PATCH 04/35] Require Lossless Scaling 3.2.2 and say so when the DLL is older Testing against a real install corrected a premise I had recorded wrongly. The design doc claimed current Lossless Scaling builds ship the chain as SPIR-V so no DXBC translator is needed. The second half holds, the first half does not: the base chain IDs have always been DXBC. What 3.2.2 added was a second set of precompiled SPIR-V copies at base+49 (fp16, IDs 304-351) and base+98 (fp32, IDs 353-400), under the release note "Added shaders intended for use by the lsfg-vk project". Verified on device against Lossless Scaling 3.2.1.0: RCDATA IDs 101-302, every one of them DXBC, and none of the variant IDs present. The parser correctly refused it, but as a bare MISSING_SHADERS, which reads as a corrupt or wrong file when the copy is in fact perfectly good and merely one version behind. LSFG_DLL_TOO_OLD now covers exactly that case - base chain present, variants absent - so the UI can tell the user to update through Steam rather than send them looking for a different file. Anything genuinely incomplete still reports MISSING_SHADERS. The alternative to a version floor is vendoring DXVK's DXBC translator the way upstream lsfg-vk does. That is a far larger dependency than the PE resource walk it would sit next to, and it would exist only to support a superseded version, so the floor is the better trade. Covered by a dxbc-only fixture in the regression suite, and confirmed against the real 3.2.1 DLL on the tablet. --- app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c | 21 ++++++++++-- app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h | 5 ++- .../runtime/display/lsfg/LosslessScaling.java | 3 ++ docs/lsfg-frame-generation.md | 34 ++++++++++++++----- 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c index b0537856a..437a10011 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c @@ -492,6 +492,22 @@ static LsfgVariant select_variant(const ResourceTable* table, bool prefer_fp16) return LSFG_VARIANT_NONE; } +static bool has_base_chain(const ResourceTable* table) { + size_t count = 0; + const uint32_t* ids = build_shader_ids(&count); + for (size_t i = 0; i < count; i++) { + if (ids[i] >= MAX_RESOURCE_ID || table->data[ids[i]] == NULL) return false; + } + return true; +} + +static LsfgStatus classify_missing_variant(const ResourceTable* table) { + if (!has_base_chain(table)) return LSFG_MISSING_SHADERS; + LSFG_LOGW("Lossless.dll carries the shader chain but no precompiled SPIR-V variants; " + "version %s or newer is required", LSFG_MIN_LOSSLESS_VERSION); + return LSFG_DLL_TOO_OLD; +} + static uint32_t variant_offset(LsfgVariant variant) { return variant == LSFG_VARIANT_FP16 ? VARIANT_FP16_OFFSET : VARIANT_FP32_OFFSET; } @@ -541,7 +557,7 @@ LsfgStatus lsfg_validate_dll(const char* dll_path) { LsfgStatus status = parse_resources(&image, table); if (status == LSFG_OK && select_variant(table, true) == LSFG_VARIANT_NONE) { - status = LSFG_MISSING_SHADERS; + status = classify_missing_variant(table); } free(table); @@ -572,9 +588,10 @@ LsfgStatus lsfg_build_cache(const char* dll_path, const char* cache_path, bool p const LsfgVariant variant = select_variant(table, prefer_fp16); if (variant == LSFG_VARIANT_NONE) { + status = classify_missing_variant(table); free(table); pe_close(&image, fd, mapped_size); - return LSFG_MISSING_SHADERS; + return status; } LsfgModuleSet set; diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h index 2e009e013..c38abc3fb 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h @@ -15,9 +15,12 @@ typedef enum LsfgStatus { LSFG_NOT_PORTABLE_EXECUTABLE = 3, LSFG_MISSING_SHADERS = 4, LSFG_TRANSLATION_FAILED = 5, - LSFG_CACHE_UNUSABLE = 6 + LSFG_CACHE_UNUSABLE = 6, + LSFG_DLL_TOO_OLD = 7 } LsfgStatus; +#define LSFG_MIN_LOSSLESS_VERSION "3.2.2" + typedef enum LsfgVariant { LSFG_VARIANT_NONE = 0, LSFG_VARIANT_FP16 = 1, diff --git a/app/src/main/runtime/display/lsfg/LosslessScaling.java b/app/src/main/runtime/display/lsfg/LosslessScaling.java index b94ee13f7..0cc382162 100644 --- a/app/src/main/runtime/display/lsfg/LosslessScaling.java +++ b/app/src/main/runtime/display/lsfg/LosslessScaling.java @@ -25,6 +25,9 @@ public final class LosslessScaling { public static final int STATUS_MISSING_SHADERS = 4; public static final int STATUS_TRANSLATION_FAILED = 5; public static final int STATUS_CACHE_UNUSABLE = 6; + public static final int STATUS_DLL_TOO_OLD = 7; + + public static final String MIN_LOSSLESS_VERSION = "3.2.2"; public static final int VARIANT_NONE = 0; public static final int VARIANT_FP16 = 1; diff --git a/docs/lsfg-frame-generation.md b/docs/lsfg-frame-generation.md index 3db4c54e5..7c85b9a33 100644 --- a/docs/lsfg-frame-generation.md +++ b/docs/lsfg-frame-generation.md @@ -48,14 +48,26 @@ generated per real frame). Only `generate` runs per generated frame; everything above it is shared across all generations from the same frame pair, which is why 3× costs far less than 1.5× the cost of 2×. -Shader variants: resource IDs `+49` are native fp16, `+98` are native fp32. Both -are already SPIR-V in current Lossless Scaling builds — eden's `IsSpirvModule` / -`AdoptSpirvModule` just re-numbers the descriptor bindings in set/binding order -and hands the words to `vkCreateShaderModule`. **No DXBC translator is needed**; -that was upstream `lsfg-vk`'s path and eden explicitly dropped it (commit -`6dd3098 Remove requirement on dxbc`). This removes DXVK's `dxbc` and `pe-parse` -from the dependency list entirely — the PE resource walk is ~250 lines of plain -parsing in `lossless_dll.cpp`. +Shader variants: the base chain IDs above hold **DXBC**. Lossless Scaling +**3.2.2** added precompiled SPIR-V copies of the same shaders at `base + 49` +(native fp16, IDs 304–351) and `base + 98` (native fp32, IDs 353–400) — its +release note reads, in full, "Added shaders intended for use by the lsfg-vk +project." + +Those precompiled blobs are what make a DXBC translator unnecessary: eden's +`IsSpirvModule` / `AdoptSpirvModule` just re-numbers the descriptor bindings in +set/binding order and hands the words to `vkCreateShaderModule`, and eden +dropped the translator outright (commit `6dd3098 Remove requirement on dxbc`). +Upstream `lsfg-vk` still carries DXVK's `dxbc` because it also supports older +builds — `src/extract/trans.cpp` runs `dxvk::DxbcModule` over the base IDs. + +**This makes 3.2.2 a hard floor for us.** Verified against a real install: +Lossless Scaling 3.2.1.0 carries RCDATA IDs 101–302, all DXBC, and none of the +`+49`/`+98` variants. The alternative to requiring 3.2.2 is vendoring DXVK's +DXBC translator, which is a far larger dependency than the ~250-line PE walk +and is not worth it to support a single superseded version. + +The port therefore needs only the resource walk, not a shader compiler. ## 2. How LSFG-Android bakes it in — and why WinNative must not copy it @@ -324,6 +336,12 @@ fallback. Extraction, SPIR-V adoption and caching happen once on the Android sid (cache keyed on file size + hash + variant, as eden does); the DLL is never loaded or executed, only parsed. +The version floor needs to surface as its own status rather than a generic +failure — a 3.2.1 install is a correct, licensed copy that simply predates the +SPIR-V blobs, and the fix is a Steam update, not a reinstall. `LSFG_DLL_TOO_OLD` +covers exactly the case where the base chain IDs are present but the `+49`/`+98` +variants are not. + ### 5.7 Settings surface Per-container and per-shortcut, alongside the existing graphics options, gated From 266861cb6292dee222d262b4f9afa8c1eda229b3 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sun, 23 Aug 2026 09:13:01 -0400 Subject: [PATCH 05/35] Drop the version floor; the SPIR-V blobs are not downloadable at all Retracts the previous commit's premise. It claimed Lossless Scaling 3.2.2 adds precompiled SPIR-V variants and set that as a version floor. Measuring the actual Steam data on device shows there is no version to move to. The installed depot manifests match the public branch PICS gids exactly, so this is the current build and not a stale download. Public is buildId 19655272 from 2025-08-19; every other branch is older, and linux_testing is byte-identical to public on depot 993091. There is nothing newer to fetch, on any branch. Scanning the whole 311 MB, 456-file install for the SPIR-V magic word finds zero occurrences, so the blobs are not in some other file either. The DLL carries RCDATA 101-302, all 202 of them DXBC. This lines up with what the working Android implementation actually does: upstream lsfg-vk and its Android fork run the DXBC path by default through DXVK's translator in src/extract/trans.cpp, and expose the precompiled SPIR-V only as an opt-in FP16 toggle for Mali parts missing vulkanMemoryModel. Eden's translator-free path assumes a Lossless build carrying those blobs, and nothing currently downloadable carries them. So a DXBC to SPIR-V translator is not optional after all, and the work plan gains a phase for it. The resource walk still stands and still picks up the SPIR-V path for free should a future build ship the variants. LSFG_DLL_TOO_OLD is renamed LSFG_NO_SPIRV_VARIANTS, since it is the normal result for every build on Steam today and a signal to take the DXBC path, not an error telling the user to go update something. No Steam downloader code was touched: there is no manifest pin to remove. depot.config is WinNative's own record of what it installed, written by DepotConfigStore, and downloadApp already force-refreshes PICS before resolving depots. --- app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c | 6 +-- app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h | 4 +- .../runtime/display/lsfg/LosslessScaling.java | 4 +- docs/lsfg-frame-generation.md | 40 +++++++++++++------ 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c index 437a10011..d67830b02 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c @@ -503,9 +503,9 @@ static bool has_base_chain(const ResourceTable* table) { static LsfgStatus classify_missing_variant(const ResourceTable* table) { if (!has_base_chain(table)) return LSFG_MISSING_SHADERS; - LSFG_LOGW("Lossless.dll carries the shader chain but no precompiled SPIR-V variants; " - "version %s or newer is required", LSFG_MIN_LOSSLESS_VERSION); - return LSFG_DLL_TOO_OLD; + LSFG_LOGW("Lossless.dll carries the DXBC shader chain but no precompiled SPIR-V variants; " + "a DXBC translator is required for this build"); + return LSFG_NO_SPIRV_VARIANTS; } static uint32_t variant_offset(LsfgVariant variant) { diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h index c38abc3fb..39dede14f 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h @@ -16,10 +16,10 @@ typedef enum LsfgStatus { LSFG_MISSING_SHADERS = 4, LSFG_TRANSLATION_FAILED = 5, LSFG_CACHE_UNUSABLE = 6, - LSFG_DLL_TOO_OLD = 7 + LSFG_NO_SPIRV_VARIANTS = 7 } LsfgStatus; -#define LSFG_MIN_LOSSLESS_VERSION "3.2.2" + typedef enum LsfgVariant { LSFG_VARIANT_NONE = 0, diff --git a/app/src/main/runtime/display/lsfg/LosslessScaling.java b/app/src/main/runtime/display/lsfg/LosslessScaling.java index 0cc382162..eaacfa998 100644 --- a/app/src/main/runtime/display/lsfg/LosslessScaling.java +++ b/app/src/main/runtime/display/lsfg/LosslessScaling.java @@ -25,9 +25,7 @@ public final class LosslessScaling { public static final int STATUS_MISSING_SHADERS = 4; public static final int STATUS_TRANSLATION_FAILED = 5; public static final int STATUS_CACHE_UNUSABLE = 6; - public static final int STATUS_DLL_TOO_OLD = 7; - - public static final String MIN_LOSSLESS_VERSION = "3.2.2"; + public static final int STATUS_NO_SPIRV_VARIANTS = 7; public static final int VARIANT_NONE = 0; public static final int VARIANT_FP16 = 1; diff --git a/docs/lsfg-frame-generation.md b/docs/lsfg-frame-generation.md index 7c85b9a33..982423acc 100644 --- a/docs/lsfg-frame-generation.md +++ b/docs/lsfg-frame-generation.md @@ -61,13 +61,29 @@ dropped the translator outright (commit `6dd3098 Remove requirement on dxbc`). Upstream `lsfg-vk` still carries DXVK's `dxbc` because it also supports older builds — `src/extract/trans.cpp` runs `dxvk::DxbcModule` over the base IDs. -**This makes 3.2.2 a hard floor for us.** Verified against a real install: -Lossless Scaling 3.2.1.0 carries RCDATA IDs 101–302, all DXBC, and none of the -`+49`/`+98` variants. The alternative to requiring 3.2.2 is vendoring DXVK's -DXBC translator, which is a far larger dependency than the ~250-line PE walk -and is not worth it to support a single superseded version. - -The port therefore needs only the resource walk, not a shader compiler. +**Those blobs are not obtainable from Steam today, so a DXBC translator is +required.** Measured on device against a fresh download of the current build: + +- `Lossless.dll` (5,435,904 bytes, FileVersion 3.2.1.0) carries RCDATA IDs + 101–302, **all 202 of them DXBC**, and none of the `+49`/`+98` variants. +- Scanning the **entire 311 MB / 456-file install** for the SPIR-V magic word + returns **zero occurrences** — the blobs are not hiding in another file. +- The public branch is `buildId 19655272`, last updated 2025-08-19, and the + installed manifests match its PICS gids exactly, so this *is* the current + build, not a stale download. Every other branch (`beta`, `linux_testing`, + `legacy_*`) is older; `linux_testing` is byte-identical to `public` on depot + 993091. There is nothing newer to fetch. + +This matches what the working Android implementation actually does: upstream +`lsfg-vk` and its Android fork run the **DXBC path by default**, linking +DXVK's `dxbc` in `src/extract/trans.cpp`, and treat the precompiled SPIR-V as +an opt-in FP16 toggle for Mali parts that lack `vulkanMemoryModel`. Eden's +translator-free path assumes a Lossless build that carries the blobs; that +assumption does not hold for anything currently downloadable. + +So the port needs the resource walk **and** a DXBC→SPIR-V translator. The walk +still lands the SPIR-V path for free if a future build ships the variants — +`LSFG_NO_SPIRV_VARIANTS` marks exactly that case. ## 2. How LSFG-Android bakes it in — and why WinNative must not copy it @@ -336,11 +352,10 @@ fallback. Extraction, SPIR-V adoption and caching happen once on the Android sid (cache keyed on file size + hash + variant, as eden does); the DLL is never loaded or executed, only parsed. -The version floor needs to surface as its own status rather than a generic -failure — a 3.2.1 install is a correct, licensed copy that simply predates the -SPIR-V blobs, and the fix is a Steam update, not a reinstall. `LSFG_DLL_TOO_OLD` -covers exactly the case where the base chain IDs are present but the `+49`/`+98` -variants are not. +`LSFG_NO_SPIRV_VARIANTS` distinguishes "valid DLL, no precompiled SPIR-V" from a +genuinely broken one. It is the expected result for every build currently on +Steam, and it is the signal to take the DXBC path rather than an error to show +the user. ### 5.7 Settings surface @@ -394,6 +409,7 @@ Choreographer coalescing in `requestRenderCoalesced`. | Phase | Work | Verifiable by | |---|---|---| | 1 | `Lossless.dll` PE resource walk, SPIR-V adoption, on-device cache, capability probe, container auto-detect + SAF fallback | 25 modules cached; status surfaces in settings | +| 1b | DXBC→SPIR-V translation for the base chain IDs (vendor DXVK's `dxbc`, as `lsfg-vk` does) | Translated module byte-compares against a known-good SPIR-V blob | | 2 | Composite-target ring + swapchain blit, behind an off-by-default flag | Pixel-identical output, no measurable cost with the flag off | | 3 | Compute pipeline support + `mipmaps`/`alpha`/`beta`/`gamma`/`delta`/`generate` port | Flow-pyramid debug dump matches eden's on the same input pair | | 4 | Multi-present per composite; semaphore/fence rework; `VK_FRAMES_IN_FLIGHT` and swapchain depth; real-present-only back-pressure | 2× shows 2 presents per guest frame; no validation errors | From 557c501f1040ad9e78646a6996f6ebeb632ffd81 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sun, 23 Aug 2026 09:25:14 -0400 Subject: [PATCH 06/35] Translate the Lossless DXBC shaders so the chain runs on a real DLL Eden reaches vkCreateShaderModule with a map of shader id to SPIR-V words and everything below that map is source-agnostic. That seam is what makes eden's approach portable, so it is kept exactly: the precompiled-SPIR-V path is unchanged, and a second producer fills the same map when the variants are absent. That second producer is DXVK's dxbc, vendored under cpp/thirdparty/dxbc from the same subset lsfg-vk uses, zlib licensed. lsfg_dxbc.cpp follows lsfg-vk's trans.cpp step for step, including its encounter-order binding renumber, which is what pairs with DXVK's output. Eden's set/binding sort stays on the precompiled path where it belongs. Which producer ran is recorded in the cache header and surfaced as the variant, so the source is visible in diagnostics without changing anything downstream. Validation now matches eden's ParseShaderSpans as well: a DLL is valid when the base chain ids are present, with no requirement that the SPIR-V variants exist. The previous stricter check rejected files eden accepts. Verified against the real 5.4 MB Lossless.dll on the tablet, arm64, Adreno 750: all 25 modules translate, 352889 SPIR-V words, about 40 ms for the whole set, and all 25 pass spirv-val --target-env vulkan1.3. Disassembly of the output pinned down two runtime requirements that were being taken on faith. The modules are SPIR-V 1.6, so the device must expose Vulkan 1.3 or vkCreateShaderModule will reject them, and they declare OpCapability VulkanMemoryModel with OpMemoryModel Logical Vulkan. The latter is exactly what eden's "This GPU driver does not support the Vulkan memory model" string is about, which also confirms eden is running DXVK-translated SPIR-V - it just gets it pre-translated when the DLL supplies it. The probe now requires Vulkan 1.3, vulkanMemoryModel, shaderStorageImageWriteWithoutFormat and shaderStorageImageExtendedFormats rather than discovering the gap at pipeline creation. A cache-header bug is fixed in passing: the variant written was the local one, still NONE on the translated path, so the source was lost on reload. Regression suite is 25 assertions, green on device. Two fixtures were wrong rather than the code: they carried only variant ids and no base chain, which no real DLL does. --- app/src/main/cpp/CMakeLists.txt | 3 + app/src/main/cpp/thirdparty/dxbc/.gitignore | 3 + app/src/main/cpp/thirdparty/dxbc/.gitmodules | 0 .../main/cpp/thirdparty/dxbc/CMakeLists.txt | 29 + app/src/main/cpp/thirdparty/dxbc/LICENSE.md | 24 + app/src/main/cpp/thirdparty/dxbc/README.md | 4 + .../dxbc/include/dxbc/dxbc_analysis.h | 110 + .../dxbc/include/dxbc/dxbc_chunk_isgn.h | 68 + .../dxbc/include/dxbc/dxbc_chunk_shex.h | 39 + .../dxbc/include/dxbc/dxbc_common.h | 72 + .../dxbc/include/dxbc/dxbc_compiler.h | 1292 +++ .../dxbc/include/dxbc/dxbc_decoder.h | 509 + .../thirdparty/dxbc/include/dxbc/dxbc_defs.h | 104 + .../thirdparty/dxbc/include/dxbc/dxbc_enums.h | 654 ++ .../dxbc/include/dxbc/dxbc_header.h | 48 + .../dxbc/include/dxbc/dxbc_include.h | 20 + .../dxbc/include/dxbc/dxbc_modinfo.h | 59 + .../dxbc/include/dxbc/dxbc_module.h | 137 + .../thirdparty/dxbc/include/dxbc/dxbc_names.h | 26 + .../dxbc/include/dxbc/dxbc_options.h | 74 + .../dxbc/include/dxbc/dxbc_reader.h | 78 + .../thirdparty/dxbc/include/dxbc/dxbc_tag.h | 47 + .../thirdparty/dxbc/include/dxbc/dxbc_util.h | 164 + .../thirdparty/dxbc/include/dxvk/dxvk_hash.h | 41 + .../dxbc/include/dxvk/dxvk_limits.h | 23 + .../dxbc/include/dxvk/dxvk_pipelayout.h | 107 + .../dxbc/include/spirv/spirv_code_buffer.h | 236 + .../dxbc/include/spirv/spirv_include.h | 15 + .../dxbc/include/spirv/spirv_instruction.h | 158 + .../dxbc/include/spirv/spirv_module.h | 1350 +++ .../include/spirv/thirdparty/GLSL.std.450.h | 131 + .../dxbc/include/spirv/thirdparty/spirv.hpp | 5214 +++++++++++ .../thirdparty/dxbc/include/util/log/log.h | 44 + .../dxbc/include/util/log/log_debug.h | 49 + .../thirdparty/dxbc/include/util/rc/util_rc.h | 38 + .../dxbc/include/util/rc/util_rc_ptr.h | 189 + .../thirdparty/dxbc/include/util/util_bit.h | 719 ++ .../thirdparty/dxbc/include/util/util_enum.h | 7 + .../thirdparty/dxbc/include/util/util_error.h | 31 + .../thirdparty/dxbc/include/util/util_flags.h | 110 + .../dxbc/include/util/util_likely.h | 11 + .../thirdparty/dxbc/include/util/util_math.h | 40 + .../dxbc/include/util/util_small_vector.h | 214 + .../dxbc/include/util/util_string.h | 240 + .../dxbc/src/dxbc/dxbc_analysis.cpp | 254 + .../dxbc/src/dxbc/dxbc_chunk_isgn.cpp | 112 + .../dxbc/src/dxbc/dxbc_chunk_shex.cpp | 24 + .../thirdparty/dxbc/src/dxbc/dxbc_common.cpp | 30 + .../dxbc/src/dxbc/dxbc_compiler.cpp | 8242 +++++++++++++++++ .../thirdparty/dxbc/src/dxbc/dxbc_decoder.cpp | 360 + .../thirdparty/dxbc/src/dxbc/dxbc_defs.cpp | 1255 +++ .../thirdparty/dxbc/src/dxbc/dxbc_header.cpp | 30 + .../thirdparty/dxbc/src/dxbc/dxbc_module.cpp | 120 + .../thirdparty/dxbc/src/dxbc/dxbc_names.cpp | 445 + .../thirdparty/dxbc/src/dxbc/dxbc_reader.cpp | 58 + .../thirdparty/dxbc/src/dxbc/dxbc_util.cpp | 26 + .../dxbc/src/spirv/spirv_code_buffer.cpp | 166 + .../dxbc/src/spirv/spirv_module.cpp | 4085 ++++++++ .../cpp/thirdparty/dxbc/src/util/util_log.cpp | 11 + .../thirdparty/dxbc/src/util/util_string.cpp | 234 + app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c | 47 +- app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h | 8 +- .../main/cpp/winlator/vk/lsfg/lsfg_dxbc.cpp | 87 + app/src/main/cpp/winlator/vk/lsfg/lsfg_dxbc.h | 18 + .../main/cpp/winlator/vk/lsfg/lsfg_probe.c | 54 +- .../runtime/display/lsfg/LosslessScaling.java | 2 +- docs/lsfg-frame-generation.md | 49 +- 67 files changed, 28207 insertions(+), 41 deletions(-) create mode 100644 app/src/main/cpp/thirdparty/dxbc/.gitignore create mode 100644 app/src/main/cpp/thirdparty/dxbc/.gitmodules create mode 100644 app/src/main/cpp/thirdparty/dxbc/CMakeLists.txt create mode 100644 app/src/main/cpp/thirdparty/dxbc/LICENSE.md create mode 100644 app/src/main/cpp/thirdparty/dxbc/README.md create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_analysis.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_chunk_isgn.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_chunk_shex.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_common.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_compiler.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_decoder.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_defs.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_enums.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_header.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_include.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_modinfo.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_module.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_names.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_options.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_reader.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_tag.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_util.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxvk/dxvk_hash.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxvk/dxvk_limits.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/dxvk/dxvk_pipelayout.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_code_buffer.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_include.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_instruction.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_module.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/spirv/thirdparty/GLSL.std.450.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/spirv/thirdparty/spirv.hpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/util/log/log.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/util/log/log_debug.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/util/rc/util_rc.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/util/rc/util_rc_ptr.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/util/util_bit.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/util/util_enum.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/util/util_error.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/util/util_flags.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/util/util_likely.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/util/util_math.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/util/util_small_vector.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/include/util/util_string.h create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_analysis.cpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_chunk_isgn.cpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_chunk_shex.cpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_common.cpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_compiler.cpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_decoder.cpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_defs.cpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_header.cpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_module.cpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_names.cpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_reader.cpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_util.cpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/spirv/spirv_code_buffer.cpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/spirv/spirv_module.cpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/util/util_log.cpp create mode 100644 app/src/main/cpp/thirdparty/dxbc/src/util/util_string.cpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_dxbc.cpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_dxbc.h diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index abcca2229..e6dc261ca 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -38,6 +38,7 @@ if(NOT xz_POPULATED) set(BUILD_SHARED_LIBS "${_winlator_saved_bsl}") endif() +add_subdirectory(thirdparty/dxbc EXCLUDE_FROM_ALL) add_subdirectory(patchelf) add_subdirectory(adrenotools) add_subdirectory(wn-steam-client) @@ -148,6 +149,7 @@ add_library(winlator SHARED winlator/vk/vk_image.c winlator/vk/vk_renderer.c winlator/vk/lsfg/lsfg_dll.c + winlator/vk/lsfg/lsfg_dxbc.cpp winlator/vk/lsfg/lsfg_probe.c winlator/vk/lsfg/lsfg_jni.c ) @@ -172,6 +174,7 @@ target_link_libraries(winlator jnigraphics vulkan adrenotools + dxbc libzstd_static liblzma curl::curl diff --git a/app/src/main/cpp/thirdparty/dxbc/.gitignore b/app/src/main/cpp/thirdparty/dxbc/.gitignore new file mode 100644 index 000000000..4522fe54d --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/.gitignore @@ -0,0 +1,3 @@ +# cmake files +.cache/ +build/ diff --git a/app/src/main/cpp/thirdparty/dxbc/.gitmodules b/app/src/main/cpp/thirdparty/dxbc/.gitmodules new file mode 100644 index 000000000..e69de29bb diff --git a/app/src/main/cpp/thirdparty/dxbc/CMakeLists.txt b/app/src/main/cpp/thirdparty/dxbc/CMakeLists.txt new file mode 100644 index 000000000..2651d7b8d --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.10) + +project(dxbc + VERSION 2.6.2 + DESCRIPTION "DXVK's DXBC" + LANGUAGES CXX) + +file(GLOB SOURCES + "src/dxbc/*.cpp" + "src/spirv/*.cpp" + "src/util/*.cpp" +) + +add_library(dxbc STATIC ${SOURCES}) + +# target +set_target_properties(dxbc PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON) +target_include_directories(dxbc SYSTEM + PUBLIC include/dxbc + PUBLIC include/spirv include/util include/dxvk) +target_compile_options(dxbc PRIVATE + -fPIC) + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set_target_properties(dxbc PROPERTIES + EXPORT_COMPILE_COMMANDS ON) +endif() diff --git a/app/src/main/cpp/thirdparty/dxbc/LICENSE.md b/app/src/main/cpp/thirdparty/dxbc/LICENSE.md new file mode 100644 index 000000000..35d56c0ba --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/LICENSE.md @@ -0,0 +1,24 @@ + Copyright (c) 2017 Philip Rebohle + Copyright (c) 2019 Joshua Ashton + Copyright (c) 2019 Robin Kertels + Copyright (c) 2023 Jeffrey Ellison + + zlib/libpng license + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +– The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + +– Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +– This notice may not be removed or altered from any source distribution. diff --git a/app/src/main/cpp/thirdparty/dxbc/README.md b/app/src/main/cpp/thirdparty/dxbc/README.md new file mode 100644 index 000000000..ac2b63665 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/README.md @@ -0,0 +1,4 @@ +This GitHub repo hosts the files for running DXVK's shader translation independently of DXVK. + +Please check out the original project: +https://github.com/doitsujin/dxvk diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_analysis.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_analysis.h new file mode 100644 index 000000000..6c8b0325b --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_analysis.h @@ -0,0 +1,110 @@ +#pragma once + +#include "dxbc_chunk_isgn.h" +#include "dxbc_decoder.h" +#include "dxbc_defs.h" +#include "dxbc_names.h" +#include "dxbc_modinfo.h" +#include "dxbc_util.h" + +namespace dxvk { + + /** + * \brief Info about unordered access views + * + * Stores whether an UAV is accessed with typed + * read or atomic instructions. This information + * will be used to generate image types. + */ + struct DxbcUavInfo { + bool accessTypedLoad = false; + bool accessAtomicOp = false; + bool sparseFeedback = false; + bool nonInvariantAccess = false; + DxvkAccessOp accessOp = DxvkAccessOp::None; + VkAccessFlags accessFlags = 0; + }; + + /** + * \brief Info about shader resource views + * + * Stores whether an SRV is accessed with + * sparse feedback. Useful for buffers. + */ + struct DxbcSrvInfo { + bool sparseFeedback = false; + }; + + /** + * \brief Counts cull and clip distances + */ + struct DxbcClipCullInfo { + uint32_t numClipPlanes = 0; + uint32_t numCullPlanes = 0; + }; + + /** + * \brief Shader analysis info + */ + struct DxbcAnalysisInfo { + std::array uavInfos; + std::array srvInfos; + std::array xRegMasks; + + DxbcClipCullInfo clipCullIn; + DxbcClipCullInfo clipCullOut; + + DxbcBindingMask bindings = { }; + + bool usesDerivatives = false; + bool usesKill = false; + }; + + /** + * \brief DXBC shader analysis pass + * + * Collects information about the shader itself + * and the resources used by the shader, which + * will later be used by the actual compiler. + */ + class DxbcAnalyzer { + + public: + + DxbcAnalyzer( + const DxbcModuleInfo& moduleInfo, + const DxbcProgramInfo& programInfo, + const Rc& isgn, + const Rc& osgn, + const Rc& psgn, + DxbcAnalysisInfo& analysis); + + ~DxbcAnalyzer(); + + /** + * \brief Processes a single instruction + * \param [in] ins The instruction + */ + void processInstruction( + const DxbcShaderInstruction& ins); + + private: + + Rc m_isgn; + Rc m_osgn; + Rc m_psgn; + + DxbcAnalysisInfo* m_analysis = nullptr; + + DxbcClipCullInfo getClipCullInfo( + const Rc& sgn) const; + + void setUavAccessOp(uint32_t uav, DxvkAccessOp op); + + static DxvkAccessOp getStoreAccessOp(DxbcRegMask writeMask, const DxbcRegister& src); + + static DxvkAccessOp getConstantStoreOp(uint32_t value); + + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_chunk_isgn.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_chunk_isgn.h new file mode 100644 index 000000000..9b4b79186 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_chunk_isgn.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +#include "dxbc_common.h" +#include "dxbc_decoder.h" +#include "dxbc_enums.h" +#include "dxbc_reader.h" + +namespace dxvk { + + /** + * \brief Signature entry + * + * Stores the semantic name of an input or + * output and the corresponding register. + */ + struct DxbcSgnEntry { + std::string semanticName; + uint32_t semanticIndex; + uint32_t registerId; + DxbcRegMask componentMask; + DxbcRegMask componentUsed; + DxbcScalarType componentType; + DxbcSystemValue systemValue; + uint32_t streamId; + }; + + /** + * \brief Input/Output signature chunk + * + * Stores information about the input and + * output registers used by the shader stage. + */ + class DxbcIsgn : public RcObject { + + public: + + DxbcIsgn(DxbcReader reader, DxbcTag tag); + ~DxbcIsgn(); + + auto begin() const { return m_entries.cbegin(); } + auto end () const { return m_entries.cend(); } + + const DxbcSgnEntry* findByRegister( + uint32_t registerId) const; + + const DxbcSgnEntry* find( + const std::string& semanticName, + uint32_t semanticIndex, + uint32_t streamIndex) const; + + DxbcRegMask regMask( + uint32_t registerId) const; + + uint32_t maxRegisterCount() const; + + static bool compareSemanticNames( + const std::string& a, + const std::string& b); + + private: + + std::vector m_entries; + + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_chunk_shex.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_chunk_shex.h new file mode 100644 index 000000000..8deecc867 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_chunk_shex.h @@ -0,0 +1,39 @@ +#pragma once + +#include "dxbc_common.h" +#include "dxbc_decoder.h" +#include "dxbc_reader.h" + +namespace dxvk { + + /** + * \brief Shader code chunk + * + * Stores the DXBC shader code itself, as well + * as some meta info about the shader, i.e. what + * type of shader this is. + */ + class DxbcShex : public RcObject { + + public: + + DxbcShex(DxbcReader reader); + ~DxbcShex(); + + DxbcProgramInfo programInfo() const { + return m_programInfo; + } + + DxbcCodeSlice slice() const { + return DxbcCodeSlice(m_code.data(), + m_code.data() + m_code.size()); + } + + private: + + DxbcProgramInfo m_programInfo; + std::vector m_code; + + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_common.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_common.h new file mode 100644 index 000000000..d9d420767 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_common.h @@ -0,0 +1,72 @@ +#pragma once + +#include "dxbc_include.h" + +namespace dxvk { + + /** + * \brief DXBC Program type + * + * Defines the shader stage that a DXBC + * module has been compiled form. + */ + enum class DxbcProgramType : uint16_t { + PixelShader = 0, + VertexShader = 1, + GeometryShader = 2, + HullShader = 3, + DomainShader = 4, + ComputeShader = 5, + + Count + }; + + using DxbcProgramTypeFlags = Flags; + + + /** + * \brief DXBC shader info + * + * Stores the shader program type. + */ + class DxbcProgramInfo { + + public: + + DxbcProgramInfo() { } + DxbcProgramInfo(DxbcProgramType type) + : m_type(type) { } + + /** + * \brief Program type + * \returns Program type + */ + DxbcProgramType type() const { + return m_type; + } + + /** + * \brief Vulkan shader stage + * + * The \c VkShaderStageFlagBits constant + * that corresponds to the program type. + * \returns Vulkan shaer stage + */ + VkShaderStageFlagBits shaderStage() const; + + /** + * \brief SPIR-V execution model + * + * The execution model that corresponds + * to the Vulkan shader stage. + * \returns SPIR-V execution model + */ + spv::ExecutionModel executionModel() const; + + private: + + DxbcProgramType m_type = DxbcProgramType::PixelShader; + + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_compiler.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_compiler.h new file mode 100644 index 000000000..f63d1e11a --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_compiler.h @@ -0,0 +1,1292 @@ +#pragma once + +#include +#include +#include + +#include "../spirv/spirv_module.h" + +#include "dxbc_analysis.h" +#include "dxbc_chunk_isgn.h" +#include "dxbc_decoder.h" +#include "dxbc_defs.h" +#include "dxbc_modinfo.h" +#include "dxbc_names.h" +#include "dxbc_util.h" + +namespace dxvk { + + /** + * \brief Vector type + * + * Convenience struct that stores a scalar + * type and a component count. The compiler + * can use this to generate SPIR-V types. + */ + struct DxbcVectorType { + DxbcScalarType ctype; + uint32_t ccount; + }; + + + /** + * \brief Array type + * + * Convenience struct that stores a scalar type, a + * component count and an array size. An array of + * length 0 will be evaluated to a vector type. The + * compiler can use this to generate SPIR-V types. + */ + struct DxbcArrayType { + DxbcScalarType ctype; + uint32_t ccount; + uint32_t alength; + }; + + + /** + * \brief Register info + * + * Stores the array type of a register and + * its storage class. The compiler can use + * this to generate SPIR-V pointer types. + */ + struct DxbcRegisterInfo { + DxbcArrayType type; + spv::StorageClass sclass; + }; + + + /** + * \brief Register value + * + * Stores a vector type and a SPIR-V ID that + * represents an intermediate value. This is + * used to track the type of such values. + */ + struct DxbcRegisterValue { + DxbcVectorType type; + uint32_t id; + }; + + + /** + * \brief Register pointer + * + * Stores a vector type and a SPIR-V ID that + * represents a pointer to such a vector. This + * can be used to load registers conveniently. + */ + struct DxbcRegisterPointer { + DxbcVectorType type; + uint32_t id; + }; + + + struct DxbcXreg { + uint32_t ccount = 0; + uint32_t alength = 0; + uint32_t varId = 0; + }; + + + struct DxbcGreg { + DxbcResourceType type = DxbcResourceType::Raw; + uint32_t elementStride = 0; + uint32_t elementCount = 0; + uint32_t varId = 0; + }; + + + /** + * \brief Specialization constant properties + * + * Stores the name, data type and initial + * value of a specialization constant. + */ + struct DxbcSpecConstant { + DxbcScalarType ctype; + uint32_t ccount; + uint32_t value; + const char* name; + }; + + + /** + * \brief Helper struct for conditional execution + * + * Stores a set of labels required to implement either + * an if-then block or an if-then-else block. This is + * not used to implement control flow instructions. + */ + struct DxbcConditional { + uint32_t labelIf = 0; + uint32_t labelElse = 0; + uint32_t labelEnd = 0; + }; + + + struct DxbcXfbVar { + uint32_t varId = 0; + uint32_t streamId = 0; + uint32_t outputId = 0; + DxbcRegMask srcMask = 0; + DxbcRegMask dstMask = 0; + uint32_t location = 0; + uint32_t component = 0; + }; + + + struct DxbcIndexRange { + DxbcOperandType type; + uint32_t start; + uint32_t length; + }; + + + /** + * \brief Vertex shader-specific structure + */ + struct DxbcCompilerVsPart { + uint32_t functionId = 0; + + uint32_t builtinVertexId = 0; + uint32_t builtinInstanceId = 0; + uint32_t builtinBaseVertex = 0; + uint32_t builtinBaseInstance = 0; + }; + + + /** + * \brief Geometry shader-specific structure + */ + struct DxbcCompilerGsPart { + DxbcPrimitive inputPrimitive = DxbcPrimitive::Undefined; + DxbcPrimitiveTopology outputTopology = DxbcPrimitiveTopology::Undefined; + uint32_t outputVertexCount = 0; + uint32_t functionId = 0; + + uint32_t builtinLayer = 0; + uint32_t builtinViewportId = 0; + uint32_t builtinInvocationId = 0; + uint32_t invocationCount = 0; + + bool needsOutputSetup = false; + }; + + + /** + * \brief Pixel shader-specific structure + */ + struct DxbcCompilerPsPart { + uint32_t functionId = 0; + + uint32_t builtinFragCoord = 0; + uint32_t builtinDepth = 0; + uint32_t builtinStencilRef = 0; + uint32_t builtinIsFrontFace = 0; + uint32_t builtinSampleId = 0; + uint32_t builtinSampleMaskIn = 0; + uint32_t builtinSampleMaskOut = 0; + uint32_t builtinLayer = 0; + uint32_t builtinViewportId = 0; + uint32_t builtinInnerCoverageId = 0; + + uint32_t pushConstantId = 0; + }; + + + /** + * \brief Compute shader-specific structure + */ + struct DxbcCompilerCsPart { + uint32_t functionId = 0; + + uint32_t workgroupSizeX = 0; + uint32_t workgroupSizeY = 0; + uint32_t workgroupSizeZ = 0; + + uint32_t builtinGlobalInvocationId = 0; + uint32_t builtinLocalInvocationId = 0; + uint32_t builtinLocalInvocationIndex = 0; + uint32_t builtinWorkgroupId = 0; + }; + + + /** + * \brief Hull shader fork/join phase + * + * Defines a function and built-in variables + * for a single fork or join phase sub-program. + */ + struct DxbcCompilerHsForkJoinPhase { + uint32_t functionId = 0; + uint32_t instanceCount = 1; + + uint32_t instanceId = 0; + uint32_t instanceIdPtr = 0; + }; + + + /** + * \brief Hull shader control point phase + * + * Defines the function for the control + * point phase program of a hull shader. + */ + struct DxbcCompilerHsControlPointPhase { + uint32_t functionId = 0; + }; + + + /** + * \brief Hull shader phase + * + * Used to identify the current + * phase and function ID. + */ + enum class DxbcCompilerHsPhase : uint32_t { + None, ///< No active phase + Decl, ///< \c hs_decls + ControlPoint, ///< \c hs_control_point_phase + Fork, ///< \c hs_fork_phase + Join, ///< \c hs_join_phase + }; + + + /** + * \brief Hull shader-specific structure + */ + struct DxbcCompilerHsPart { + DxbcCompilerHsPhase currPhaseType = DxbcCompilerHsPhase::None; + size_t currPhaseId = 0; + + float maxTessFactor = 64.0f; + + uint32_t vertexCountIn = 0; + uint32_t vertexCountOut = 0; + + uint32_t builtinInvocationId = 0; + uint32_t builtinTessLevelOuter = 0; + uint32_t builtinTessLevelInner = 0; + + uint32_t outputPerPatch = 0; + uint32_t outputPerVertex = 0; + + uint32_t invocationBlockBegin = 0; + uint32_t invocationBlockEnd = 0; + + uint32_t outputPerPatchMask = 0; + + DxbcCompilerHsControlPointPhase cpPhase; + std::vector forkPhases; + std::vector joinPhases; + }; + + + /** + * \brief Domain shader-specific structure + */ + struct DxbcCompilerDsPart { + uint32_t functionId = 0; + + uint32_t builtinTessCoord = 0; + uint32_t builtinTessLevelOuter = 0; + uint32_t builtinTessLevelInner = 0; + + uint32_t vertexCountIn = 0; + + uint32_t inputPerPatch = 0; + uint32_t inputPerVertex = 0; + }; + + + enum class DxbcCfgBlockType : uint32_t { + If, Loop, Switch, + }; + + + struct DxbcCfgBlockIf { + uint32_t ztestId; + uint32_t labelIf; + uint32_t labelElse; + uint32_t labelEnd; + size_t headerPtr; + }; + + + struct DxbcCfgBlockLoop { + uint32_t labelHeader; + uint32_t labelBegin; + uint32_t labelContinue; + uint32_t labelBreak; + }; + + + struct DxbcSwitchLabel { + SpirvSwitchCaseLabel desc; + DxbcSwitchLabel* next; + }; + + + struct DxbcCfgBlockSwitch { + size_t insertPtr; + uint32_t selectorId; + uint32_t labelBreak; + uint32_t labelCase; + uint32_t labelDefault; + DxbcSwitchLabel* labelCases; + }; + + + struct DxbcCfgBlock { + DxbcCfgBlockType type; + + union { + DxbcCfgBlockIf b_if; + DxbcCfgBlockLoop b_loop; + DxbcCfgBlockSwitch b_switch; + }; + }; + + + struct DxbcBufferInfo { + DxbcImageInfo image; + DxbcScalarType stype; + DxbcResourceType type; + uint32_t typeId; + uint32_t varId; + uint32_t stride; + uint32_t coherence; + bool isSsbo; + }; + + + /** + * \brief DXBC to SPIR-V shader compiler + * + * Processes instructions from a DXBC shader and creates + * a DXVK shader object, which contains the SPIR-V module + * and information about the shader resource bindings. + */ + class DxbcCompiler { + + public: + + DxbcCompiler( + const std::string& fileName, + const DxbcModuleInfo& moduleInfo, + const DxbcProgramInfo& programInfo, + const Rc& isgn, + const Rc& osgn, + const Rc& psgn, + const DxbcAnalysisInfo& analysis); + ~DxbcCompiler(); + + /** + * \brief Processes a single instruction + * \param [in] ins The instruction + */ + void processInstruction( + const DxbcShaderInstruction& ins); + + /** + * \brief Emits transform feedback passthrough + * + * Writes all captured input variables to the + * corresponding xfb outputs, and sets up the + * geometry shader for point-to-point mode. + */ + void processXfbPassthrough(); + + /** + * \brief Finalizes the shader + * \returns The final shader object + */ + SpirvCodeBuffer finalize(); + + /** + * \brief Extracts immediate constant buffer data + * + * Only defined if the ICB needs to be backed by a + * uniform buffer. + * \returns Immediate constant buffer data + */ + std::vector getIcbData() const { + return std::move(m_icbData); + } + + private: + + DxbcModuleInfo m_moduleInfo; + DxbcProgramInfo m_programInfo; + SpirvModule m_module; + + Rc m_isgn; + Rc m_osgn; + Rc m_psgn; + + const DxbcAnalysisInfo* m_analysis; + + /////////////////////////////////////////////////////// + // Resource slot description for the shader. This will + // be used to map D3D11 bindings to DXVK bindings. + std::vector m_bindings; + + //////////////////////////////////////////////// + // Temporary r# vector registers with immediate + // indexing, and x# vector array registers. + std::vector m_rRegs; + std::vector m_xRegs; + + ///////////////////////////////////////////// + // Thread group shared memory (g#) registers + std::vector m_gRegs; + + /////////////////////////////////////////////////////////// + // v# registers as defined by the shader. The type of each + // of these inputs is either float4 or an array of float4. + std::array< + DxbcRegisterPointer, + DxbcMaxInterfaceRegs> m_vRegs; + std::vector m_vMappings; + + ////////////////////////////////////////////////////////// + // o# registers as defined by the shader. In the fragment + // shader stage, these registers are typed by the signature, + // in all other stages, they are float4 registers or arrays. + std::array< + DxbcRegisterPointer, + DxbcMaxInterfaceRegs> m_oRegs; + std::vector m_oMappings; + + ///////////////////////////////////////////// + // xfb output registers for geometry shaders + std::vector m_xfbVars; + + ///////////////////////////////////////////// + // Dynamically indexed input and output regs + std::vector m_indexRanges = { }; + + ////////////////////////////////////////////////////// + // Shader resource variables. These provide access to + // constant buffers, samplers, textures, and UAVs. + std::array m_constantBuffers; + std::array m_samplers; + std::array m_textures; + std::array m_uavs; + + bool m_hasGloballyCoherentUav = false; + bool m_hasRasterizerOrderedUav = false; + + /////////////////////////////////////////////// + // Control flow information. Stores labels for + // currently active if-else blocks and loops. + std::vector m_controlFlowBlocks; + + bool m_topLevelIsUniform = true; + + uint64_t m_uavRdMask = 0u; + uint64_t m_uavWrMask = 0u; + + ////////////////////////////////////////////// + // Function state tracking. Required in order + // to properly end functions in some cases. + bool m_insideFunction = false; + + /////////////////////////////////////////////////////////// + // Array of input values. Since v# registers are indexable + // in DXBC, we need to copy them into an array first. + uint32_t m_vArrayLength = 0; + uint32_t m_vArrayLengthId = 0; + + uint32_t m_vArray = 0; + + //////////////////////////////////////////////////// + // Per-vertex input and output blocks. Depending on + // the shader stage, these may be declared as arrays. + uint32_t m_positionIn = 0; + uint32_t m_positionOut = 0; + + uint32_t m_clipDistances = 0; + uint32_t m_cullDistances = 0; + + uint32_t m_primitiveIdIn = 0; + uint32_t m_primitiveIdOut = 0; + + ////////////////////////////////////////////////// + // Immediate constant buffer. If defined, this is + // an array of four-component uint32 vectors. + uint32_t m_icbArray = 0; + std::vector m_icbData; + + uint32_t m_icbComponents = 0u; + uint32_t m_icbSize = 0u; + + /////////////////////////////////////////////////// + // Sample pos array. If defined, this iis an array + // of 32 four-component float vectors. + uint32_t m_samplePositions = 0; + + //////////////////////////////////////////// + // Struct type used for UAV counter buffers + uint32_t m_uavCtrStructType = 0; + uint32_t m_uavCtrPointerType = 0; + + //////////////////////////////// + // Function IDs for subroutines + std::unordered_map m_subroutines; + + /////////////////////////////////////////////////// + // Entry point description - we'll need to declare + // the function ID and all input/output variables. + uint32_t m_entryPointId = 0; + bool m_hasRawAccessChains = false; + + //////////////////////////////////////////// + // Inter-stage shader interface slots. Also + // covers vertex input and fragment output. + uint32_t m_inputMask = 0u; + uint32_t m_outputMask = 0u; + + /////////////////////////////////// + // Shader-specific data structures + DxbcCompilerVsPart m_vs; + DxbcCompilerHsPart m_hs; + DxbcCompilerDsPart m_ds; + DxbcCompilerGsPart m_gs; + DxbcCompilerPsPart m_ps; + DxbcCompilerCsPart m_cs; + + ////////////////////// + // Global state stuff + bool m_precise = true; + + DxbcOpcode m_lastOp = DxbcOpcode::Nop; + DxbcOpcode m_currOp = DxbcOpcode::Nop; + + VkPrimitiveTopology m_inputTopology = VK_PRIMITIVE_TOPOLOGY_MAX_ENUM; + VkPrimitiveTopology m_outputTopology = VK_PRIMITIVE_TOPOLOGY_MAX_ENUM; + + ///////////////////////////////////////////////////// + // Shader interface and metadata declaration methods + void emitDcl( + const DxbcShaderInstruction& ins); + + void emitDclGlobalFlags( + const DxbcShaderInstruction& ins); + + void emitDclIndexRange( + const DxbcShaderInstruction& ins); + + void emitDclTemps( + const DxbcShaderInstruction& ins); + + void emitDclIndexableTemp( + const DxbcShaderInstruction& ins); + + void emitDclInterfaceReg( + const DxbcShaderInstruction& ins); + + void emitDclInput( + uint32_t regIdx, + uint32_t regDim, + DxbcRegMask regMask, + DxbcSystemValue sv, + DxbcInterpolationMode im); + + void emitDclOutput( + uint32_t regIdx, + uint32_t regDim, + DxbcRegMask regMask, + DxbcSystemValue sv, + DxbcInterpolationMode im); + + void emitDclConstantBuffer( + const DxbcShaderInstruction& ins); + + void emitDclConstantBufferVar( + uint32_t regIdx, + uint32_t numConstants, + uint32_t numComponents, + const char* name); + + void emitDclSampler( + const DxbcShaderInstruction& ins); + + void emitDclStream( + const DxbcShaderInstruction& ins); + + void emitDclResourceTyped( + const DxbcShaderInstruction& ins); + + void emitDclResourceRawStructured( + const DxbcShaderInstruction& ins); + + void emitDclThreadGroupSharedMemory( + const DxbcShaderInstruction& ins); + + void emitDclGsInputPrimitive( + const DxbcShaderInstruction& ins); + + void emitDclGsOutputTopology( + const DxbcShaderInstruction& ins); + + void emitDclMaxOutputVertexCount( + const DxbcShaderInstruction& ins); + + void emitDclInputControlPointCount( + const DxbcShaderInstruction& ins); + + void emitDclOutputControlPointCount( + const DxbcShaderInstruction& ins); + + void emitDclHsMaxTessFactor( + const DxbcShaderInstruction& ins); + + void emitDclTessDomain( + const DxbcShaderInstruction& ins); + + void emitDclTessPartitioning( + const DxbcShaderInstruction& ins); + + void emitDclTessOutputPrimitive( + const DxbcShaderInstruction& ins); + + void emitDclThreadGroup( + const DxbcShaderInstruction& ins); + + void emitDclGsInstanceCount( + const DxbcShaderInstruction& ins); + + uint32_t emitDclUavCounter( + uint32_t regId); + + //////////////////////// + // Custom data handlers + void emitDclImmediateConstantBuffer( + const DxbcShaderInstruction& ins); + + void emitDclImmediateConstantBufferBaked( + uint32_t dwordCount, + const uint32_t* dwordArray, + uint32_t componentCount); + + void emitDclImmediateConstantBufferUbo( + uint32_t dwordCount, + const uint32_t* dwordArray, + uint32_t componentCount); + + void emitCustomData( + const DxbcShaderInstruction& ins); + + ////////////////////////////// + // Instruction class handlers + void emitVectorAlu( + const DxbcShaderInstruction& ins); + + void emitVectorCmov( + const DxbcShaderInstruction& ins); + + void emitVectorCmp( + const DxbcShaderInstruction& ins); + + void emitVectorDeriv( + const DxbcShaderInstruction& ins); + + void emitVectorDot( + const DxbcShaderInstruction& ins); + + void emitVectorIdiv( + const DxbcShaderInstruction& ins); + + void emitVectorImul( + const DxbcShaderInstruction& ins); + + void emitVectorMsad( + const DxbcShaderInstruction& ins); + + void emitVectorShift( + const DxbcShaderInstruction& ins); + + void emitVectorSinCos( + const DxbcShaderInstruction& ins); + + void emitGeometryEmit( + const DxbcShaderInstruction& ins); + + void emitAtomic( + const DxbcShaderInstruction& ins); + + void emitAtomicCounter( + const DxbcShaderInstruction& ins); + + void emitBarrier( + const DxbcShaderInstruction& ins); + + void emitBitExtract( + const DxbcShaderInstruction& ins); + + void emitBitInsert( + const DxbcShaderInstruction& ins); + + void emitBitScan( + const DxbcShaderInstruction& ins); + + void emitBufferQuery( + const DxbcShaderInstruction& ins); + + void emitBufferLoad( + const DxbcShaderInstruction& ins); + + void emitBufferStore( + const DxbcShaderInstruction& ins); + + void emitConvertFloat16( + const DxbcShaderInstruction& ins); + + void emitConvertFloat64( + const DxbcShaderInstruction& ins); + + void emitHullShaderPhase( + const DxbcShaderInstruction& ins); + + void emitHullShaderInstCnt( + const DxbcShaderInstruction& ins); + + void emitInterpolate( + const DxbcShaderInstruction& ins); + + void emitSparseCheckAccess( + const DxbcShaderInstruction& ins); + + void emitTextureQuery( + const DxbcShaderInstruction& ins); + + void emitTextureQueryLod( + const DxbcShaderInstruction& ins); + + void emitTextureQueryMs( + const DxbcShaderInstruction& ins); + + void emitTextureQueryMsPos( + const DxbcShaderInstruction& ins); + + void emitTextureFetch( + const DxbcShaderInstruction& ins); + + void emitTextureGather( + const DxbcShaderInstruction& ins); + + void emitTextureSample( + const DxbcShaderInstruction& ins); + + void emitTypedUavLoad( + const DxbcShaderInstruction& ins); + + void emitTypedUavStore( + const DxbcShaderInstruction& ins); + + ///////////////////////////////////// + // Control flow instruction handlers + void emitControlFlowIf( + const DxbcShaderInstruction& ins); + + void emitControlFlowElse( + const DxbcShaderInstruction& ins); + + void emitControlFlowEndIf( + const DxbcShaderInstruction& ins); + + void emitControlFlowSwitch( + const DxbcShaderInstruction& ins); + + void emitControlFlowCase( + const DxbcShaderInstruction& ins); + + void emitControlFlowDefault( + const DxbcShaderInstruction& ins); + + void emitControlFlowEndSwitch( + const DxbcShaderInstruction& ins); + + void emitControlFlowLoop( + const DxbcShaderInstruction& ins); + + void emitControlFlowEndLoop( + const DxbcShaderInstruction& ins); + + void emitControlFlowBreak( + const DxbcShaderInstruction& ins); + + void emitControlFlowBreakc( + const DxbcShaderInstruction& ins); + + void emitControlFlowRet( + const DxbcShaderInstruction& ins); + + void emitControlFlowRetc( + const DxbcShaderInstruction& ins); + + void emitControlFlowDiscard( + const DxbcShaderInstruction& ins); + + void emitControlFlowLabel( + const DxbcShaderInstruction& ins); + + void emitControlFlowCall( + const DxbcShaderInstruction& ins); + + void emitControlFlowCallc( + const DxbcShaderInstruction& ins); + + void emitControlFlow( + const DxbcShaderInstruction& ins); + + //////////////////////////////////////////////// + // Constant building methods. These are used to + // generate constant vectors that store the same + // value in each component. + DxbcRegisterValue emitBuildConstVecf32( + float x, + float y, + float z, + float w, + const DxbcRegMask& writeMask); + + DxbcRegisterValue emitBuildConstVecu32( + uint32_t x, + uint32_t y, + uint32_t z, + uint32_t w, + const DxbcRegMask& writeMask); + + DxbcRegisterValue emitBuildConstVeci32( + int32_t x, + int32_t y, + int32_t z, + int32_t w, + const DxbcRegMask& writeMask); + + DxbcRegisterValue emitBuildConstVecf64( + double xy, + double zw, + const DxbcRegMask& writeMask); + + DxbcRegisterValue emitBuildVector( + DxbcRegisterValue scalar, + uint32_t count); + + DxbcRegisterValue emitBuildZeroVector( + DxbcVectorType type); + + ///////////////////////////////////////// + // Generic register manipulation methods + DxbcRegisterValue emitRegisterBitcast( + DxbcRegisterValue srcValue, + DxbcScalarType dstType); + + DxbcRegisterValue emitRegisterSwizzle( + DxbcRegisterValue value, + DxbcRegSwizzle swizzle, + DxbcRegMask writeMask); + + DxbcRegisterValue emitRegisterExtract( + DxbcRegisterValue value, + DxbcRegMask mask); + + DxbcRegisterValue emitRegisterInsert( + DxbcRegisterValue dstValue, + DxbcRegisterValue srcValue, + DxbcRegMask srcMask); + + DxbcRegisterValue emitRegisterConcat( + DxbcRegisterValue value1, + DxbcRegisterValue value2); + + DxbcRegisterValue emitRegisterExtend( + DxbcRegisterValue value, + uint32_t size); + + DxbcRegisterValue emitRegisterAbsolute( + DxbcRegisterValue value); + + DxbcRegisterValue emitRegisterNegate( + DxbcRegisterValue value); + + DxbcRegisterValue emitRegisterZeroTest( + DxbcRegisterValue value, + DxbcZeroTest test); + + DxbcRegisterValue emitRegisterMaskBits( + DxbcRegisterValue value, + uint32_t mask); + + DxbcRegisterValue emitSrcOperandModifiers( + DxbcRegisterValue value, + DxbcRegModifiers modifiers); + + DxbcRegisterValue emitDstOperandModifiers( + DxbcRegisterValue value, + DxbcOpModifiers modifiers); + + /////////////////////////// + // Sparse feedback methods + uint32_t emitExtractSparseTexel( + uint32_t texelTypeId, + uint32_t resultId); + + void emitStoreSparseFeedback( + const DxbcRegister& feedbackRegister, + uint32_t resultId); + + //////////////////////////////// + // Pointer manipulation methods + DxbcRegisterPointer emitArrayAccess( + DxbcRegisterPointer pointer, + spv::StorageClass sclass, + uint32_t index); + + /////////////////////////////////////// + // Image register manipulation methods + uint32_t emitLoadSampledImage( + const DxbcShaderResource& textureResource, + const DxbcSampler& samplerResource, + bool isDepthCompare); + + //////////////////////// + // Address load methods + DxbcRegisterPointer emitGetTempPtr( + const DxbcRegister& operand); + + DxbcRegisterPointer emitGetIndexableTempPtr( + const DxbcRegister& operand); + + DxbcRegisterPointer emitGetInputPtr( + const DxbcRegister& operand); + + DxbcRegisterPointer emitGetOutputPtr( + const DxbcRegister& operand); + + DxbcRegisterPointer emitGetConstBufPtr( + const DxbcRegister& operand); + + DxbcRegisterPointer emitGetImmConstBufPtr( + const DxbcRegister& operand); + + DxbcRegisterPointer emitGetOperandPtr( + const DxbcRegister& operand); + + DxbcRegisterPointer emitGetAtomicPointer( + const DxbcRegister& operand, + const DxbcRegister& address); + + ////////////////////////// + // Resource query methods + DxbcRegisterValue emitQueryBufferSize( + const DxbcRegister& resource); + + DxbcRegisterValue emitQueryTexelBufferSize( + const DxbcRegister& resource); + + DxbcRegisterValue emitQueryTextureLods( + const DxbcRegister& resource); + + DxbcRegisterValue emitQueryTextureSamples( + const DxbcRegister& resource); + + DxbcRegisterValue emitQueryTextureSize( + const DxbcRegister& resource, + DxbcRegisterValue lod); + + //////////////////////////////////// + // Buffer index calculation methods + DxbcRegisterValue emitCalcBufferIndexStructured( + DxbcRegisterValue structId, + DxbcRegisterValue structOffset, + uint32_t structStride); + + DxbcRegisterValue emitCalcBufferIndexRaw( + DxbcRegisterValue byteOffset); + + DxbcRegisterValue emitCalcTexCoord( + DxbcRegisterValue coordVector, + const DxbcImageInfo& imageInfo); + + DxbcRegisterValue emitLoadTexCoord( + const DxbcRegister& coordReg, + const DxbcImageInfo& imageInfo); + + ////////////////////////////// + // Operand load/store methods + DxbcRegisterValue emitIndexLoad( + DxbcRegIndex index); + + DxbcRegisterValue emitValueLoad( + DxbcRegisterPointer ptr); + + void emitValueStore( + DxbcRegisterPointer ptr, + DxbcRegisterValue value, + DxbcRegMask writeMask); + + DxbcRegisterValue emitRegisterLoadRaw( + const DxbcRegister& reg); + + DxbcRegisterValue emitConstantBufferLoad( + const DxbcRegister& reg, + DxbcRegMask writeMask); + + DxbcRegisterValue emitRegisterLoad( + const DxbcRegister& reg, + DxbcRegMask writeMask); + + void emitRegisterStore( + const DxbcRegister& reg, + DxbcRegisterValue value); + + //////////////////////////// + // Input/output preparation + void emitInputSetup(); + void emitInputSetup(uint32_t vertexCount); + + void emitOutputSetup(); + void emitOutputDepthClamp(); + + void emitInitWorkgroupMemory(); + + ////////////////////////////////////////// + // System value load methods (per shader) + DxbcRegisterValue emitVsSystemValueLoad( + DxbcSystemValue sv, + DxbcRegMask mask); + + DxbcRegisterValue emitGsSystemValueLoad( + DxbcSystemValue sv, + DxbcRegMask mask, + uint32_t vertexId); + + DxbcRegisterValue emitPsSystemValueLoad( + DxbcSystemValue sv, + DxbcRegMask mask); + + /////////////////////////////////////////// + // System value store methods (per shader) + void emitVsSystemValueStore( + DxbcSystemValue sv, + DxbcRegMask mask, + const DxbcRegisterValue& value); + + void emitHsSystemValueStore( + DxbcSystemValue sv, + DxbcRegMask mask, + const DxbcRegisterValue& value); + + void emitDsSystemValueStore( + DxbcSystemValue sv, + DxbcRegMask mask, + const DxbcRegisterValue& value); + + void emitGsSystemValueStore( + DxbcSystemValue sv, + DxbcRegMask mask, + const DxbcRegisterValue& value); + + void emitPsSystemValueStore( + DxbcSystemValue sv, + DxbcRegMask mask, + const DxbcRegisterValue& value); + + /////////////////////////////// + // Special system value stores + void emitClipCullStore( + DxbcSystemValue sv, + uint32_t dstArray); + + void emitClipCullLoad( + DxbcSystemValue sv, + uint32_t srcArray); + + void emitPointSizeStore(); + + ////////////////////////////////////// + // Common function definition methods + void emitInit(); + + void emitFunctionBegin( + uint32_t entryPoint, + uint32_t returnType, + uint32_t funcType); + + void emitFunctionEnd(); + + void emitFunctionLabel(); + + void emitMainFunctionBegin(); + + ///////////////////////////////// + // Shader initialization methods + void emitVsInit(); + void emitHsInit(); + void emitDsInit(); + void emitGsInit(); + void emitPsInit(); + void emitCsInit(); + + /////////////////////////////// + // Shader finalization methods + void emitVsFinalize(); + void emitHsFinalize(); + void emitDsFinalize(); + void emitGsFinalize(); + void emitPsFinalize(); + void emitCsFinalize(); + + /////////////////////// + // Xfb related methods + void emitXfbOutputDeclarations(); + + void emitXfbOutputSetup( + uint32_t streamId, + bool passthrough); + + /////////////////////////////// + // Hull shader phase methods + void emitHsControlPointPhase( + const DxbcCompilerHsControlPointPhase& phase); + + void emitHsForkJoinPhase( + const DxbcCompilerHsForkJoinPhase& phase); + + void emitHsPhaseBarrier(); + + void emitHsInvocationBlockBegin( + uint32_t count); + + void emitHsInvocationBlockEnd(); + + void emitHsOutputSetup(); + + uint32_t emitTessInterfacePerPatch( + spv::StorageClass storageClass); + + uint32_t emitTessInterfacePerVertex( + spv::StorageClass storageClass, + uint32_t vertexCount); + + ////////////// + // Misc stuff + void emitDclInputArray( + uint32_t vertexCount); + + uint32_t emitDclClipCullDistanceArray( + uint32_t length, + spv::BuiltIn builtIn, + spv::StorageClass storageClass); + + DxbcCompilerHsControlPointPhase emitNewHullShaderControlPointPhase(); + + DxbcCompilerHsControlPointPhase emitNewHullShaderPassthroughPhase(); + + DxbcCompilerHsForkJoinPhase emitNewHullShaderForkJoinPhase(); + + uint32_t emitSamplePosArray(); + + void emitFloatControl(); + + /////////////////////////////// + // Variable definition methods + uint32_t emitNewVariable( + const DxbcRegisterInfo& info); + + uint32_t emitNewBuiltinVariable( + const DxbcRegisterInfo& info, + spv::BuiltIn builtIn, + const char* name); + + uint32_t emitBuiltinTessLevelOuter( + spv::StorageClass storageClass); + + uint32_t emitBuiltinTessLevelInner( + spv::StorageClass storageClass); + + uint32_t emitPushConstants(); + + //////////////// + // Misc methods + DxbcCfgBlock* cfgFindBlock( + const std::initializer_list& types); + + DxbcBufferInfo getBufferInfo( + const DxbcRegister& reg); + + uint32_t getTexSizeDim( + const DxbcImageInfo& imageType) const; + + uint32_t getTexLayerDim( + const DxbcImageInfo& imageType) const; + + uint32_t getTexCoordDim( + const DxbcImageInfo& imageType) const; + + DxbcRegMask getTexCoordMask( + const DxbcImageInfo& imageType) const; + + DxbcVectorType getInputRegType( + uint32_t regIdx) const; + + DxbcVectorType getOutputRegType( + uint32_t regIdx) const; + + DxbcImageInfo getResourceType( + DxbcResourceDim resourceType, + bool isUav) const; + + spv::ImageFormat getScalarImageFormat( + DxbcScalarType type) const; + + bool isDoubleType( + DxbcScalarType type) const; + + DxbcRegisterPointer getIndexableTempPtr( + const DxbcRegister& operand, + DxbcRegisterValue vectorId); + + bool caseBlockIsFallthrough() const; + + uint32_t getUavCoherence( + uint32_t registerId, + DxbcUavFlags flags); + + bool ignoreInputSystemValue( + DxbcSystemValue sv) const; + + void emitUavBarrier( + uint64_t readMask, + uint64_t writeMask); + + /////////////////////////// + // Type definition methods + uint32_t getScalarTypeId( + DxbcScalarType type); + + uint32_t getVectorTypeId( + const DxbcVectorType& type); + + uint32_t getArrayTypeId( + const DxbcArrayType& type); + + uint32_t getPointerTypeId( + const DxbcRegisterInfo& type); + + uint32_t getSparseResultTypeId( + uint32_t baseType); + + uint32_t getFunctionId( + uint32_t functionNr); + + DxbcCompilerHsForkJoinPhase* getCurrentHsForkJoinPhase(); + + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_decoder.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_decoder.h new file mode 100644 index 000000000..771837152 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_decoder.h @@ -0,0 +1,509 @@ +#pragma once + +#include + +#include "dxbc_common.h" +#include "dxbc_decoder.h" +#include "dxbc_defs.h" +#include "dxbc_enums.h" +#include "dxbc_names.h" + +namespace dxvk { + + constexpr size_t DxbcMaxRegIndexDim = 3; + + struct DxbcRegister; + + /** + * \brief Source operand modifiers + * + * These are applied after loading + * an operand register. + */ + enum class DxbcRegModifier : uint32_t { + Neg = 0, + Abs = 1, + }; + + using DxbcRegModifiers = Flags; + + + /** + * \brief Constant buffer binding + * + * Stores information required to + * access a constant buffer. + */ + struct DxbcConstantBuffer { + uint32_t varId = 0; + uint32_t size = 0; + }; + + /** + * \brief Sampler binding + * + * Stores a sampler variable that can be + * used together with a texture resource. + */ + struct DxbcSampler { + uint32_t varId = 0; + uint32_t typeId = 0; + }; + + + /** + * \brief Image type information + */ + struct DxbcImageInfo { + spv::Dim dim = spv::Dim1D; + uint32_t array = 0; + uint32_t ms = 0; + uint32_t sampled = 0; + VkImageViewType vtype = VK_IMAGE_VIEW_TYPE_MAX_ENUM; + }; + + + /** + * \brief Shader resource binding + * + * Stores a resource variable + * and associated type IDs. + */ + struct DxbcShaderResource { + DxbcResourceType type = DxbcResourceType::Typed; + DxbcImageInfo imageInfo; + uint32_t varId = 0; + uint32_t specId = 0; + DxbcScalarType sampledType = DxbcScalarType::Float32; + uint32_t sampledTypeId = 0; + uint32_t imageTypeId = 0; + uint32_t colorTypeId = 0; + uint32_t depthTypeId = 0; + uint32_t structStride = 0; + bool isRawSsbo = false; + }; + + + /** + * \brief Unordered access binding + * + * Stores a resource variable that is provided + * by a UAV, as well as associated type IDs. + */ + struct DxbcUav { + DxbcResourceType type = DxbcResourceType::Typed; + DxbcImageInfo imageInfo; + uint32_t varId = 0; + uint32_t ctrId = 0; + uint32_t specId = 0; + DxbcScalarType sampledType = DxbcScalarType::Float32; + uint32_t sampledTypeId = 0; + uint32_t imageTypeId = 0; + uint32_t structStride = 0; + uint32_t coherence = 0; + bool isRawSsbo = false; + }; + + + /** + * \brief Component swizzle + * + * Maps vector components to + * other vector components. + */ + class DxbcRegSwizzle { + + public: + + DxbcRegSwizzle() { } + DxbcRegSwizzle(uint32_t x, uint32_t y, uint32_t z, uint32_t w) + : m_mask((x << 0) | (y << 2) | (z << 4) | (w << 6)) { } + + uint32_t operator [] (uint32_t id) const { + return (m_mask >> (id + id)) & 0x3; + } + + bool operator == (const DxbcRegSwizzle& other) const { return m_mask == other.m_mask; } + bool operator != (const DxbcRegSwizzle& other) const { return m_mask != other.m_mask; } + + private: + + uint8_t m_mask = 0; + + }; + + + /** + * \brief Component mask + * + * Enables access to certain + * subset of vector components. + */ + class DxbcRegMask { + + public: + + DxbcRegMask() { } + DxbcRegMask(uint32_t mask) : m_mask(mask) { } + DxbcRegMask(bool x, bool y, bool z, bool w) + : m_mask((x ? 0x1 : 0) | (y ? 0x2 : 0) + | (z ? 0x4 : 0) | (w ? 0x8 : 0)) { } + + uint32_t raw() const { + return m_mask; + } + + bool operator [] (uint32_t id) const { + return (m_mask >> id) & 1; + } + + uint32_t popCount() const { + const uint8_t n[16] = { 0, 1, 1, 2, 1, 2, 2, 3, + 1, 2, 2, 3, 2, 3, 3, 4 }; + return n[m_mask & 0xF]; + } + + uint32_t firstSet() const { + const uint8_t n[16] = { 4, 0, 1, 0, 2, 0, 1, 0, + 3, 0, 1, 0, 2, 0, 1, 0 }; + return n[m_mask & 0xF]; + } + + uint32_t minComponents() const { + const uint8_t n[16] = { 0, 1, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4 }; + return n[m_mask & 0xF]; + } + + bool operator == (const DxbcRegMask& other) const { return m_mask == other.m_mask; } + bool operator != (const DxbcRegMask& other) const { return m_mask != other.m_mask; } + + DxbcRegMask& operator |= (const DxbcRegMask& other) { + m_mask |= other.m_mask; + return *this; + } + + static DxbcRegMask firstN(uint32_t n) { + return DxbcRegMask(n >= 1, n >= 2, n >= 3, n >= 4); + } + + static DxbcRegMask select(uint32_t n) { + return DxbcRegMask(n == 0, n == 1, n == 2, n == 3); + } + + std::string maskString() const { + std::string out = ""; + out += (m_mask & 0x1) ? "x" : ""; + out += (m_mask & 0x2) ? "y" : ""; + out += (m_mask & 0x4) ? "z" : ""; + out += (m_mask & 0x8) ? "w" : ""; + return out; + } + + explicit operator bool () const { + return m_mask != 0; + } + + private: + + uint8_t m_mask = 0; + + }; + + + /** + * \brief System value mapping + * + * Maps a system value to a given set of + * components of an input or output register. + */ + struct DxbcSvMapping { + uint32_t regId; + DxbcRegMask regMask; + DxbcSystemValue sv; + }; + + + struct DxbcRegIndex { + DxbcRegister* relReg; + int32_t offset; + }; + + + /** + * \brief Instruction operand + */ + struct DxbcRegister { + DxbcOperandType type; + DxbcScalarType dataType; + DxbcComponentCount componentCount; + + uint32_t idxDim; + DxbcRegIndex idx[DxbcMaxRegIndexDim]; + + DxbcRegMask mask; + DxbcRegSwizzle swizzle; + DxbcRegModifiers modifiers; + + union { + uint32_t u32_4[4]; + uint32_t u32_1; + } imm; + }; + + + /** + * \brief Instruction result modifiers + * + * Modifiers that are applied + * to all destination operands. + */ + struct DxbcOpModifiers { + bool saturate; + bool precise; + }; + + + /** + * \brief Opcode controls + * + * Instruction-specific controls. Usually, + * only one of the members will be valid. + */ + class DxbcShaderOpcodeControls { + + public: + + DxbcShaderOpcodeControls() + : m_bits(0) { } + + DxbcShaderOpcodeControls(uint32_t bits) + : m_bits(bits) { } + + DxbcInstructionReturnType returnType() const { + return DxbcInstructionReturnType(bit::extract(m_bits, 11, 11)); + } + + DxbcGlobalFlags globalFlags() const { + return DxbcGlobalFlags(bit::extract(m_bits, 11, 14)); + } + + DxbcZeroTest zeroTest() const { + return DxbcZeroTest(bit::extract(m_bits, 18, 18)); + } + + DxbcSyncFlags syncFlags() const { + return DxbcSyncFlags(bit::extract(m_bits, 11, 14)); + } + + DxbcResourceDim resourceDim() const { + return DxbcResourceDim(bit::extract(m_bits, 11, 15)); + } + + DxbcResinfoType resinfoType() const { + return DxbcResinfoType(bit::extract(m_bits, 11, 12)); + } + + DxbcInterpolationMode interpolation() const { + return DxbcInterpolationMode(bit::extract(m_bits, 11, 14)); + } + + DxbcSamplerMode samplerMode() const { + return DxbcSamplerMode(bit::extract(m_bits, 11, 14)); + } + + DxbcPrimitiveTopology primitiveTopology() const { + return DxbcPrimitiveTopology(bit::extract(m_bits, 11, 17)); + } + + DxbcPrimitive primitive() const { + return DxbcPrimitive(bit::extract(m_bits, 11, 16)); + } + + DxbcTessDomain tessDomain() const { + return DxbcTessDomain(bit::extract(m_bits, 11, 12)); + } + + DxbcTessOutputPrimitive tessOutputPrimitive() const { + return DxbcTessOutputPrimitive(bit::extract(m_bits, 11, 13)); + } + + DxbcTessPartitioning tessPartitioning() const { + return DxbcTessPartitioning(bit::extract(m_bits, 11, 13)); + } + + DxbcUavFlags uavFlags() const { + return DxbcUavFlags(bit::extract(m_bits, 16, 17)); + } + + DxbcConstantBufferAccessType accessType() const { + return DxbcConstantBufferAccessType(bit::extract(m_bits, 11, 11)); + } + + uint32_t controlPointCount() const { + return bit::extract(m_bits, 11, 16); + } + + bool precise() const { + return bit::extract(m_bits, 19, 22) != 0; + } + + private: + + uint32_t m_bits; + + }; + + + /** + * \brief Sample controls + * + * Constant texel offset with + * values raning from -8 to 7. + */ + struct DxbcShaderSampleControls { + int u, v, w; + }; + + + /** + * \brief Immediate value + * + * Immediate argument represented either + * as a 32-bit or 64-bit unsigned integer, + * or a 32-bit or 32-bit floating point number. + */ + union DxbcImmediate { + float f32; + double f64; + uint32_t u32; + uint64_t u64; + }; + + + /** + * \brief Shader instruction + * + * Note that this structure may store pointer to + * external structures, such as the original code + * buffer. This is safe to use if and only if: + * - The \ref DxbcDecodeContext that created it + * still exists and was not moved + * - The code buffer that was being decoded + * still exists and was not moved. + */ + struct DxbcShaderInstruction { + DxbcOpcode op; + DxbcInstClass opClass; + DxbcOpModifiers modifiers; + DxbcShaderOpcodeControls controls; + DxbcShaderSampleControls sampleControls; + + uint32_t dstCount; + uint32_t srcCount; + uint32_t immCount; + + const DxbcRegister* dst; + const DxbcRegister* src; + const DxbcImmediate* imm; + + DxbcCustomDataClass customDataType; + uint32_t customDataSize; + const uint32_t* customData; + }; + + + /** + * \brief DXBC code slice + * + * Convenient pointer pair that allows + * reading the code word stream safely. + */ + class DxbcCodeSlice { + + public: + + DxbcCodeSlice( + const uint32_t* ptr, + const uint32_t* end) + : m_ptr(ptr), m_end(end) { } + + const uint32_t* ptrAt(uint32_t id) const; + + uint32_t at(uint32_t id) const; + uint32_t read(); + + DxbcCodeSlice take(uint32_t n) const; + DxbcCodeSlice skip(uint32_t n) const; + + bool atEnd() const { + return m_ptr == m_end; + } + + private: + + const uint32_t* m_ptr = nullptr; + const uint32_t* m_end = nullptr; + + }; + + + /** + * \brief Decode context + * + * Stores data that is required to decode a single + * instruction. This data is not persistent, so it + * should be forwarded to the compiler right away. + */ + class DxbcDecodeContext { + + public: + + /** + * \brief Retrieves current instruction + * + * This is only valid after a call to \ref decode. + * \returns Reference to last decoded instruction + */ + const DxbcShaderInstruction& getInstruction() const { + return m_instruction; + } + + /** + * \brief Decodes an instruction + * + * This also advances the given code slice by the + * number of dwords consumed by the instruction. + * \param [in] code Code slice + */ + void decodeInstruction(DxbcCodeSlice& code); + + private: + + DxbcShaderInstruction m_instruction; + + std::array m_dstOperands; + std::array m_srcOperands; + std::array m_immOperands; + std::array m_indices; + + // Index into the indices array. Used when decoding + // instruction operands with relative indexing. + uint32_t m_indexId = 0; + + void decodeCustomData(DxbcCodeSlice code); + void decodeOperation(DxbcCodeSlice code); + + void decodeComponentSelection(DxbcRegister& reg, uint32_t token); + void decodeOperandExtensions(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token); + void decodeOperandImmediates(DxbcCodeSlice& code, DxbcRegister& reg); + void decodeOperandIndex(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token); + + void decodeRegister(DxbcCodeSlice& code, DxbcRegister& reg, DxbcScalarType type); + void decodeImm32(DxbcCodeSlice& code, DxbcImmediate& imm, DxbcScalarType type); + + void decodeOperand(DxbcCodeSlice& code, const DxbcInstOperandFormat& format); + + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_defs.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_defs.h new file mode 100644 index 000000000..8782481af --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_defs.h @@ -0,0 +1,104 @@ +#pragma once + +#include "dxbc_enums.h" + +namespace dxvk { + + constexpr size_t DxbcMaxInterfaceRegs = 32; + constexpr size_t DxbcMaxOperandCount = 8; + + /** + * \brief Operand kind + * + * In the instruction format definition, this specified + * whether an operand uses an actual operand token, or + * whether it is stored as an immediate value. + */ + enum class DxbcOperandKind { + DstReg, ///< Destination register + SrcReg, ///< Source register + Imm32, ///< Constant number + }; + + /** + * \brief Instruction class + * + * Instructions with a similar format are grouped into + * instruction classes in order to make implementing + * new instructions easier. + */ + enum class DxbcInstClass { + Declaration, ///< Interface or resource declaration + CustomData, ///< Immediate constant buffer + ControlFlow, ///< Control flow instructions + GeometryEmit, ///< Special geometry shader instructions + Atomic, ///< Atomic operations + AtomicCounter, ///< Atomic counter operations + Barrier, ///< Execution or memory barrier + BitExtract, ///< Bit field extract operations + BitInsert, ///< Bit field insert operations + BitScan, ///< Bit scan operations + BufferQuery, ///< Buffer query instruction + BufferLoad, ///< Structured or raw buffer load + BufferStore, ///< Structured or raw buffer store + ConvertFloat16, ///< 16-bit float packing/unpacking + ConvertFloat64, ///< 64-bit float conversion + HullShaderPhase, ///< Hull shader phase declaration + HullShaderInstCnt, ///< Hull shader phase instance count + Interpolate, ///< Input attribute interpolation + NoOperation, ///< The most useful instruction class + SparseCheckAccess, ///< Verifies sparse resource access + TextureQuery, ///< Texture query instruction + TextureQueryLod, ///< Texture LOD query instruction + TextureQueryMs, ///< Multisample texture query + TextureQueryMsPos, ///< Sample position query + TextureFetch, ///< Texture fetch instruction + TextureGather, ///< Texture gather instruction + TextureSample, ///< Texture sampling instruction + TypedUavLoad, ///< Typed UAV load + TypedUavStore, ///< Typed UAV store + VectorAlu, ///< Component-wise vector instructions + VectorCmov, ///< Component-wise conditional move + VectorCmp, ///< Component-wise vector comparison + VectorDeriv, ///< Vector derivatives + VectorDot, ///< Dot product instruction + VectorIdiv, ///< Component-wise integer division + VectorImul, ///< Component-wise integer multiplication + VectorMsad, ///< Component-wise sum of absolute difference + VectorShift, ///< Bit shift operations on vectors + VectorSinCos, ///< Sine and Cosine instruction + Undefined, ///< Instruction code not defined + }; + + /** + * \brief Instruction operand format + * + * Stores the kind and the expected data type + * of an operand. Used when parsing instructions. + */ + struct DxbcInstOperandFormat { + DxbcOperandKind kind; + DxbcScalarType type; + }; + + /** + * \brief Instruction format + * + * Defines the instruction class as well as + * the format of the insttruction operands. + */ + struct DxbcInstFormat { + uint32_t operandCount = 0; + DxbcInstClass instructionClass = DxbcInstClass::Undefined; + DxbcInstOperandFormat operands[DxbcMaxOperandCount]; + }; + + /** + * \brief Retrieves instruction format info + * + * \param [in] opcode The opcode to retrieve + * \returns Instruction format info + */ + DxbcInstFormat dxbcInstructionFormat(DxbcOpcode opcode); + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_enums.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_enums.h new file mode 100644 index 000000000..f6d29a5b8 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_enums.h @@ -0,0 +1,654 @@ +#pragma once + +#include "dxbc_include.h" + +namespace dxvk { + + /** + * \brief Instruction code listing + */ + enum class DxbcOpcode : uint32_t { + Add = 0, + And = 1, + Break = 2, + Breakc = 3, + Call = 4, + Callc = 5, + Case = 6, + Continue = 7, + Continuec = 8, + Cut = 9, + Default = 10, + DerivRtx = 11, + DerivRty = 12, + Discard = 13, + Div = 14, + Dp2 = 15, + Dp3 = 16, + Dp4 = 17, + Else = 18, + Emit = 19, + EmitThenCut = 20, + EndIf = 21, + EndLoop = 22, + EndSwitch = 23, + Eq = 24, + Exp = 25, + Frc = 26, + FtoI = 27, + FtoU = 28, + Ge = 29, + IAdd = 30, + If = 31, + IEq = 32, + IGe = 33, + ILt = 34, + IMad = 35, + IMax = 36, + IMin = 37, + IMul = 38, + INe = 39, + INeg = 40, + IShl = 41, + IShr = 42, + ItoF = 43, + Label = 44, + Ld = 45, + LdMs = 46, + Log = 47, + Loop = 48, + Lt = 49, + Mad = 50, + Min = 51, + Max = 52, + CustomData = 53, + Mov = 54, + Movc = 55, + Mul = 56, + Ne = 57, + Nop = 58, + Not = 59, + Or = 60, + ResInfo = 61, + Ret = 62, + Retc = 63, + RoundNe = 64, + RoundNi = 65, + RoundPi = 66, + RoundZ = 67, + Rsq = 68, + Sample = 69, + SampleC = 70, + SampleClz = 71, + SampleL = 72, + SampleD = 73, + SampleB = 74, + Sqrt = 75, + Switch = 76, + SinCos = 77, + UDiv = 78, + ULt = 79, + UGe = 80, + UMul = 81, + UMad = 82, + UMax = 83, + UMin = 84, + UShr = 85, + UtoF = 86, + Xor = 87, + DclResource = 88, + DclConstantBuffer = 89, + DclSampler = 90, + DclIndexRange = 91, + DclGsOutputPrimitiveTopology = 92, + DclGsInputPrimitive = 93, + DclMaxOutputVertexCount = 94, + DclInput = 95, + DclInputSgv = 96, + DclInputSiv = 97, + DclInputPs = 98, + DclInputPsSgv = 99, + DclInputPsSiv = 100, + DclOutput = 101, + DclOutputSgv = 102, + DclOutputSiv = 103, + DclTemps = 104, + DclIndexableTemp = 105, + DclGlobalFlags = 106, + Reserved0 = 107, + Lod = 108, + Gather4 = 109, + SamplePos = 110, + SampleInfo = 111, + Reserved1 = 112, + HsDecls = 113, + HsControlPointPhase = 114, + HsForkPhase = 115, + HsJoinPhase = 116, + EmitStream = 117, + CutStream = 118, + EmitThenCutStream = 119, + InterfaceCall = 120, + BufInfo = 121, + DerivRtxCoarse = 122, + DerivRtxFine = 123, + DerivRtyCoarse = 124, + DerivRtyFine = 125, + Gather4C = 126, + Gather4Po = 127, + Gather4PoC = 128, + Rcp = 129, + F32toF16 = 130, + F16toF32 = 131, + UAddc = 132, + USubb = 133, + CountBits = 134, + FirstBitHi = 135, + FirstBitLo = 136, + FirstBitShi = 137, + UBfe = 138, + IBfe = 139, + Bfi = 140, + BfRev = 141, + Swapc = 142, + DclStream = 143, + DclFunctionBody = 144, + DclFunctionTable = 145, + DclInterface = 146, + DclInputControlPointCount = 147, + DclOutputControlPointCount = 148, + DclTessDomain = 149, + DclTessPartitioning = 150, + DclTessOutputPrimitive = 151, + DclHsMaxTessFactor = 152, + DclHsForkPhaseInstanceCount = 153, + DclHsJoinPhaseInstanceCount = 154, + DclThreadGroup = 155, + DclUavTyped = 156, + DclUavRaw = 157, + DclUavStructured = 158, + DclThreadGroupSharedMemoryRaw = 159, + DclThreadGroupSharedMemoryStructured = 160, + DclResourceRaw = 161, + DclResourceStructured = 162, + LdUavTyped = 163, + StoreUavTyped = 164, + LdRaw = 165, + StoreRaw = 166, + LdStructured = 167, + StoreStructured = 168, + AtomicAnd = 169, + AtomicOr = 170, + AtomicXor = 171, + AtomicCmpStore = 172, + AtomicIAdd = 173, + AtomicIMax = 174, + AtomicIMin = 175, + AtomicUMax = 176, + AtomicUMin = 177, + ImmAtomicAlloc = 178, + ImmAtomicConsume = 179, + ImmAtomicIAdd = 180, + ImmAtomicAnd = 181, + ImmAtomicOr = 182, + ImmAtomicXor = 183, + ImmAtomicExch = 184, + ImmAtomicCmpExch = 185, + ImmAtomicIMax = 186, + ImmAtomicIMin = 187, + ImmAtomicUMax = 188, + ImmAtomicUMin = 189, + Sync = 190, + DAdd = 191, + DMax = 192, + DMin = 193, + DMul = 194, + DEq = 195, + DGe = 196, + DLt = 197, + DNe = 198, + DMov = 199, + DMovc = 200, + DtoF = 201, + FtoD = 202, + EvalSnapped = 203, + EvalSampleIndex = 204, + EvalCentroid = 205, + DclGsInstanceCount = 206, + Abort = 207, + DebugBreak = 208, + ReservedBegin11_1 = 209, + DDiv = 210, + DFma = 211, + DRcp = 212, + Msad = 213, + DtoI = 214, + DtoU = 215, + ItoD = 216, + UtoD = 217, + ReservedBegin11_2 = 218, + Gather4S = 219, + Gather4CS = 220, + Gather4PoS = 221, + Gather4PoCS = 222, + LdS = 223, + LdMsS = 224, + LdUavTypedS = 225, + LdRawS = 226, + LdStructuredS = 227, + SampleLS = 228, + SampleClzS = 229, + SampleClampS = 230, + SampleBClampS = 231, + SampleDClampS = 232, + SampleCClampS = 233, + CheckAccessFullyMapped = 234, + }; + + + /** + * \brief Extended opcode + */ + enum class DxbcExtOpcode : uint32_t { + Empty = 0, + SampleControls = 1, + ResourceDim = 2, + ResourceReturnType = 3, + }; + + + /** + * \brief Operand type + * + * Selects the 'register file' from which + * to retrieve an operand's value. + */ + enum class DxbcOperandType : uint32_t { + Temp = 0, + Input = 1, + Output = 2, + IndexableTemp = 3, + Imm32 = 4, + Imm64 = 5, + Sampler = 6, + Resource = 7, + ConstantBuffer = 8, + ImmediateConstantBuffer = 9, + Label = 10, + InputPrimitiveId = 11, + OutputDepth = 12, + Null = 13, + Rasterizer = 14, + OutputCoverageMask = 15, + Stream = 16, + FunctionBody = 17, + FunctionTable = 18, + Interface = 19, + FunctionInput = 20, + FunctionOutput = 21, + OutputControlPointId = 22, + InputForkInstanceId = 23, + InputJoinInstanceId = 24, + InputControlPoint = 25, + OutputControlPoint = 26, + InputPatchConstant = 27, + InputDomainPoint = 28, + ThisPointer = 29, + UnorderedAccessView = 30, + ThreadGroupSharedMemory = 31, + InputThreadId = 32, + InputThreadGroupId = 33, + InputThreadIdInGroup = 34, + InputCoverageMask = 35, + InputThreadIndexInGroup = 36, + InputGsInstanceId = 37, + OutputDepthGe = 38, + OutputDepthLe = 39, + CycleCounter = 40, + OutputStencilRef = 41, + InputInnerCoverage = 42, + }; + + + /** + * \brief Number of components + * + * Used by operands to determine whether the + * operand has one, four or zero components. + */ + enum class DxbcComponentCount : uint32_t { + Component0 = 0, + Component1 = 1, + Component4 = 2, + }; + + + /** + * \brief Component selection mode + * + * When an operand has four components, the + * component selection mode deterines which + * components are used for the operation. + */ + enum class DxbcRegMode : uint32_t { + Mask = 0, + Swizzle = 1, + Select1 = 2, + }; + + + /** + * \brief Index representation + * + * Determines how an operand + * register index is stored. + */ + enum class DxbcOperandIndexRepresentation : uint32_t { + Imm32 = 0, + Imm64 = 1, + Relative = 2, + Imm32Relative = 3, + Imm64Relative = 4, + }; + + + /** + * \brief Extended operand type + */ + enum class DxbcOperandExt : uint32_t { + OperandModifier = 1, + }; + + + /** + * \brief Resource dimension + * The type of a resource. + */ + enum class DxbcResourceDim : uint32_t { + Unknown = 0, + Buffer = 1, + Texture1D = 2, + Texture2D = 3, + Texture2DMs = 4, + Texture3D = 5, + TextureCube = 6, + Texture1DArr = 7, + Texture2DArr = 8, + Texture2DMsArr = 9, + TextureCubeArr = 10, + RawBuffer = 11, + StructuredBuffer = 12, + }; + + + /** + * \brief Resource return type + * Data type for resource read ops. + */ + enum class DxbcResourceReturnType : uint32_t { + Unorm = 1, + Snorm = 2, + Sint = 3, + Uint = 4, + Float = 5, + Mixed = 6, /// ? + Double = 7, + Continued = 8, /// ? + Unused = 9, /// ? + }; + + + /** + * \brief Register component type + * Data type of a register component. + */ + enum class DxbcRegisterComponentType : uint32_t { + Unknown = 0, + Uint32 = 1, + Sint32 = 2, + Float32 = 3, + }; + + + /** + * \brief Instruction return type + */ + enum class DxbcInstructionReturnType : uint32_t { + Float = 0, + Uint = 1, + }; + + + enum class DxbcSystemValue : uint32_t { + None = 0, + Position = 1, + ClipDistance = 2, + CullDistance = 3, + RenderTargetId = 4, + ViewportId = 5, + VertexId = 6, + PrimitiveId = 7, + InstanceId = 8, + IsFrontFace = 9, + SampleIndex = 10, + FinalQuadUeq0EdgeTessFactor = 11, + FinalQuadVeq0EdgeTessFactor = 12, + FinalQuadUeq1EdgeTessFactor = 13, + FinalQuadVeq1EdgeTessFactor = 14, + FinalQuadUInsideTessFactor = 15, + FinalQuadVInsideTessFactor = 16, + FinalTriUeq0EdgeTessFactor = 17, + FinalTriVeq0EdgeTessFactor = 18, + FinalTriWeq0EdgeTessFactor = 19, + FinalTriInsideTessFactor = 20, + FinalLineDetailTessFactor = 21, + FinalLineDensityTessFactor = 22, + Target = 64, + Depth = 65, + Coverage = 66, + DepthGe = 67, + DepthLe = 68 + }; + + + enum class DxbcInterpolationMode : uint32_t { + Undefined = 0, + Constant = 1, + Linear = 2, + LinearCentroid = 3, + LinearNoPerspective = 4, + LinearNoPerspectiveCentroid = 5, + LinearSample = 6, + LinearNoPerspectiveSample = 7, + }; + + + enum class DxbcGlobalFlag : uint32_t { + RefactoringAllowed = 0, + DoublePrecision = 1, + EarlyFragmentTests = 2, + RawStructuredBuffers = 3, + }; + + using DxbcGlobalFlags = Flags; + + enum class DxbcZeroTest : uint32_t { + TestZ = 0, + TestNz = 1, + }; + + enum class DxbcResinfoType : uint32_t { + Float = 0, + RcpFloat = 1, + Uint = 2, + }; + + enum class DxbcSyncFlag : uint32_t { + ThreadsInGroup = 0, + ThreadGroupSharedMemory = 1, + UavMemoryGroup = 2, + UavMemoryGlobal = 3, + }; + + using DxbcSyncFlags = Flags; + + + /** + * \brief Geometry shader input primitive + */ + enum class DxbcPrimitive : uint32_t { + Undefined = 0, + Point = 1, + Line = 2, + Triangle = 3, + LineAdj = 6, + TriangleAdj = 7, + Patch1 = 8, + Patch2 = 9, + Patch3 = 10, + Patch4 = 11, + Patch5 = 12, + Patch6 = 13, + Patch7 = 14, + Patch8 = 15, + Patch9 = 16, + Patch10 = 17, + Patch11 = 18, + Patch12 = 19, + Patch13 = 20, + Patch14 = 21, + Patch15 = 22, + Patch16 = 23, + Patch17 = 24, + Patch18 = 25, + Patch19 = 26, + Patch20 = 27, + Patch21 = 28, + Patch22 = 29, + Patch23 = 30, + Patch24 = 31, + Patch25 = 32, + Patch26 = 33, + Patch27 = 34, + Patch28 = 35, + Patch29 = 36, + Patch30 = 37, + Patch31 = 38, + Patch32 = 39, + }; + + + /** + * \brief Geometry shader output topology + */ + enum class DxbcPrimitiveTopology : uint32_t { + Undefined = 0, + PointList = 1, + LineList = 2, + LineStrip = 3, + TriangleList = 4, + TriangleStrip = 5, + LineListAdj = 10, + LineStripAdj = 11, + TriangleListAdj = 12, + TriangleStripAdj = 13, + }; + + + /** + * \brief Sampler operation mode + */ + enum class DxbcSamplerMode : uint32_t { + Default = 0, + Comparison = 1, + Mono = 2, + }; + + + /** + * \brief Scalar value type + * + * Enumerates possible register component + * types. Scalar types are represented as + * a one-component vector type. + */ + enum class DxbcScalarType : uint32_t { + Uint32 = 0, + Uint64 = 1, + Sint32 = 2, + Sint64 = 3, + Float32 = 4, + Float64 = 5, + Bool = 6, + }; + + + /** + * \brief Tessellator domain + */ + enum class DxbcTessDomain : uint32_t { + Undefined = 0, + Isolines = 1, + Triangles = 2, + Quads = 3, + }; + + /** + * \brief Tessellator partitioning + */ + enum class DxbcTessPartitioning : uint32_t { + Undefined = 0, + Integer = 1, + Pow2 = 2, + FractOdd = 3, + FractEven = 4, + }; + + /** + * \brief UAV definition flags + */ + enum class DxbcUavFlag : uint32_t { + GloballyCoherent = 0, + RasterizerOrdered = 1, + }; + + using DxbcUavFlags = Flags; + + /** + * \brief Tessellator output primitive + */ + enum class DxbcTessOutputPrimitive : uint32_t { + Undefined = 0, + Point = 1, + Line = 2, + TriangleCw = 3, + TriangleCcw = 4, + }; + + /** + * \brief Custom data class + * + * Stores which type of custom data is + * referenced by the instruction. + */ + enum class DxbcCustomDataClass : uint32_t { + Comment = 0, + DebugInfo = 1, + Opaque = 2, + ImmConstBuf = 3, + }; + + + enum class DxbcResourceType : uint32_t { + Typed = 0, + Raw = 1, + Structured = 2, + }; + + + enum class DxbcConstantBufferAccessType : uint32_t { + StaticallyIndexed = 0, + DynamicallyIndexed = 1, + }; + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_header.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_header.h new file mode 100644 index 000000000..8eca0d499 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_header.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include "dxbc_reader.h" + +namespace dxvk { + + /** + * \brief DXBC header + * + * Stores information about the shader file itself + * and the data chunks stored inside the file. + */ + class DxbcHeader { + + public: + + DxbcHeader(DxbcReader& reader); + ~DxbcHeader(); + + /** + * \brief Number of chunks + * \returns Chunk count + */ + uint32_t numChunks() const { + return m_chunkOffsets.size(); + } + + /** + * \brief Chunk offset + * + * Retrieves the offset of a chunk, in + * bytes, from the start of the file. + * \param [in] chunkId Chunk index + * \returns Byte offset of that chunk + */ + uint32_t chunkOffset(uint32_t chunkId) const { + return m_chunkOffsets.at(chunkId); + } + + private: + + std::vector m_chunkOffsets; + + }; + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_include.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_include.h new file mode 100644 index 000000000..b33162227 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_include.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include "dxvk_limits.h" +#include "dxvk_pipelayout.h" + +#include "log/log.h" +#include "log/log_debug.h" + +#include "rc/util_rc.h" +#include "rc/util_rc_ptr.h" + +#include "util_bit.h" +#include "util_enum.h" +#include "util_error.h" +#include "util_string.h" +#include "util_flags.h" +#include "util_small_vector.h" diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_modinfo.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_modinfo.h new file mode 100644 index 000000000..13e733dde --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_modinfo.h @@ -0,0 +1,59 @@ +#pragma once + +#include "dxbc_options.h" + +namespace dxvk { + + /** + * \brief Tessellation info + * + * Stores the maximum tessellation factor + * to export from tessellation shaders. + */ + struct DxbcTessInfo { + float maxTessFactor; + }; + + /** + * \brief Xfb capture entry + * + * Stores an output variable to capture, + * as well as the buffer to write it to. + */ + struct DxbcXfbEntry { + const char* semanticName; + uint32_t semanticIndex; + uint32_t componentIndex; + uint32_t componentCount; + uint32_t streamId; + uint32_t bufferId; + uint32_t offset; + }; + + /** + * \brief Xfb info + * + * Stores capture entries and output buffer + * strides. This structure must only be + * defined if \c entryCount is non-zero. + */ + struct DxbcXfbInfo { + uint32_t entryCount; + DxbcXfbEntry entries[128]; + uint32_t strides[4]; + int32_t rasterizedStream; + }; + + /** + * \brief Shader module info + * + * Stores information which may affect shader compilation. + * This data can be supplied by the client API implementation. + */ + struct DxbcModuleInfo { + DxbcOptions options; + DxbcTessInfo* tess; + DxbcXfbInfo* xfb; + }; + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_module.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_module.h new file mode 100644 index 000000000..3ef75cfe1 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_module.h @@ -0,0 +1,137 @@ +#pragma once + +#include "dxbc_chunk_isgn.h" +#include "dxbc_chunk_shex.h" +#include "dxbc_header.h" +#include "dxbc_modinfo.h" +#include "dxbc_reader.h" +#include "dxbc_util.h" + +#include "spirv_code_buffer.h" + +#include + +// References used for figuring out DXBC: +// - https://github.com/tgjones/slimshader-cpp +// - Wine + +namespace dxvk { + + class DxbcAnalyzer; + class DxbcCompiler; + + /** + * \brief Immediate constant buffer properties + */ + struct DxbcIcbInfo { + size_t size = 0u; + const void* data = nullptr; + }; + + + /** + * \brief DXBC shader module + * + * Reads the DXBC byte code and extracts information + * about the resource bindings and the instruction + * stream. A module can then be compiled to SPIR-V. + */ + class DxbcModule { + + public: + + DxbcModule(DxbcReader& reader); + ~DxbcModule(); + + /** + * \brief Shader type + * \returns Shader type + */ + std::optional programInfo() const { + if (m_shexChunk == nullptr) + return std::nullopt; + + return m_shexChunk->programInfo(); + } + + /** + * \brief Queries shader binding mask + * + * Only valid after successfully compiling the shader. + */ + std::optional bindings() const { + return m_bindings; + } + + /** + * \brief Retrieves immediate constant buffer info + * + * Only valid after successfully compiling the shader. + * \returns Immediate constant buffer data + */ + DxbcIcbInfo icbInfo() const { + DxbcIcbInfo result = { }; + result.size = m_icb.size() * sizeof(uint32_t); + result.data = m_icb.data(); + return result; + } + + /** + * \brief Input and output signature chunks + * + * Parts of the D3D11 API need access to the + * input or output signature of the shader. + */ + Rc isgn() const { return m_isgnChunk; } + Rc osgn() const { return m_osgnChunk; } + + /** + * \brief Compiles DXBC shader to SPIR-V module + * + * \param [in] moduleInfo DXBC module info + * \param [in] fileName File name, will be added to + * the compiled SPIR-V for debugging purposes. + * \returns The compiled shader object + */ + SpirvCodeBuffer compile( + const DxbcModuleInfo& moduleInfo, + const std::string& fileName); + + /** + * \brief Compiles a pass-through geometry shader + * + * Applications can pass a vertex shader to create + * a geometry shader with stream output. In this + * case, we have to create a passthrough geometry + * shader, which operates in point to point mode. + * \param [in] moduleInfo DXBC module info + * \param [in] fileName SPIR-V shader name + */ + SpirvCodeBuffer compilePassthroughShader( + const DxbcModuleInfo& moduleInfo, + const std::string& fileName) const; + + private: + + DxbcHeader m_header; + + Rc m_isgnChunk; + Rc m_osgnChunk; + Rc m_psgnChunk; + Rc m_shexChunk; + + std::vector m_icb; + + std::optional m_bindings; + + void runAnalyzer( + DxbcAnalyzer& analyzer, + DxbcCodeSlice slice) const; + + void runCompiler( + DxbcCompiler& compiler, + DxbcCodeSlice slice) const; + + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_names.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_names.h new file mode 100644 index 000000000..52235ae59 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_names.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "dxbc_common.h" +#include "dxbc_enums.h" + +namespace dxvk { + + std::ostream& operator << (std::ostream& os, DxbcOpcode e); + std::ostream& operator << (std::ostream& os, DxbcExtOpcode e); + std::ostream& operator << (std::ostream& os, DxbcOperandType e); + std::ostream& operator << (std::ostream& os, DxbcOperandExt e); + std::ostream& operator << (std::ostream& os, DxbcComponentCount e); + std::ostream& operator << (std::ostream& os, DxbcRegMode e); + std::ostream& operator << (std::ostream& os, DxbcOperandIndexRepresentation e); + std::ostream& operator << (std::ostream& os, DxbcResourceDim e); + std::ostream& operator << (std::ostream& os, DxbcResourceReturnType e); + std::ostream& operator << (std::ostream& os, DxbcRegisterComponentType e); + std::ostream& operator << (std::ostream& os, DxbcInstructionReturnType e); + std::ostream& operator << (std::ostream& os, DxbcSystemValue e); + std::ostream& operator << (std::ostream& os, DxbcProgramType e); + std::ostream& operator << (std::ostream& os, DxbcCustomDataClass e); + std::ostream& operator << (std::ostream& os, DxbcScalarType e); + +} // namespace dxvk diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_options.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_options.h new file mode 100644 index 000000000..0fe46a767 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_options.h @@ -0,0 +1,74 @@ +#pragma once + +#include + +#include "util_flags.h" + +namespace dxvk { + + struct D3D11Options; + + enum class DxbcFloatControlFlag : uint32_t { + DenormFlushToZero32, + DenormPreserve64, + PreserveNan32, + PreserveNan64, + }; + + using DxbcFloatControlFlags = Flags; + + struct DxbcOptions { + DxbcOptions() {} + + // Clamp oDepth in fragment shaders if the depth + // clip device feature is not supported + bool useDepthClipWorkaround = false; + + /// Determines whether format qualifiers + /// on typed UAV loads are required + bool supportsTypedUavLoadR32 = false; + + /// Determines whether raw access chains are supported + bool supportsRawAccessChains = false; + + /// Clear thread-group shared memory to zero + bool zeroInitWorkgroupMemory = false; + + /// Declare vertex positions as invariant + bool invariantPosition = false; + + /// Insert memory barriers after TGSM stoes + bool forceVolatileTgsmAccess = false; + + /// Try to detect hazards in UAV access and insert + /// barriers when we know control flow is uniform. + bool forceComputeUavBarriers = false; + + /// Replace ld_ms with ld + bool disableMsaa = false; + + /// Force sample rate shading by using sample + /// interpolation for fragment shader inputs + bool forceSampleRateShading = false; + + // Enable per-sample interlock if supported + bool enableSampleShadingInterlock = false; + + /// Use tightly packed arrays for immediate + /// constant buffers if possible + bool supportsTightIcbPacking = false; + + /// Whether exporting point size is required + bool needsPointSizeExport = true; + + /// Whether to enable sincos emulation + bool sincosEmulation = false; + + /// Float control flags + DxbcFloatControlFlags floatControl; + + /// Minimum storage buffer alignment + VkDeviceSize minSsboAlignment = 0; + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_reader.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_reader.h new file mode 100644 index 000000000..a16000179 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_reader.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include + +#include "dxbc_tag.h" + +namespace dxvk { + + /** + * \brief DXBC bytecode reader + * + * Holds references to the shader byte code and + * provides methods to read + */ + class DxbcReader { + + public: + + DxbcReader(const char* data, size_t size) + : DxbcReader(data, size, 0) { } + + auto readu8 () { return this->readNum (); } + auto readu16() { return this->readNum(); } + auto readu32() { return this->readNum(); } + auto readu64() { return this->readNum(); } + + auto readi8 () { return this->readNum (); } + auto readi16() { return this->readNum (); } + auto readi32() { return this->readNum (); } + auto readi64() { return this->readNum (); } + + auto readf32() { return this->readNum (); } + auto readf64() { return this->readNum (); } + + template + auto readEnum() { + using Tx = std::underlying_type_t; + return static_cast(this->readNum()); + } + + DxbcTag readTag(); + + std::string readString(); + + void read(void* dst, size_t n); + + void skip(size_t n); + + DxbcReader clone(size_t pos) const; + + DxbcReader resize(size_t size) const; + + bool eof() const { + return m_pos >= m_size; + } + + void store(std::ostream&& stream) const; + + private: + + DxbcReader(const char* data, size_t size, size_t pos) + : m_data(data), m_size(size), m_pos(pos) { } + + const char* m_data = nullptr; + size_t m_size = 0; + size_t m_pos = 0; + + template + T readNum() { + T result; + this->read(&result, sizeof(result)); + return result; + } + + }; + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_tag.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_tag.h new file mode 100644 index 000000000..2ba17509c --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_tag.h @@ -0,0 +1,47 @@ +#pragma once + +#include "dxbc_include.h" + +namespace dxvk { + + /** + * \brief Four-character tag + * + * Used to identify chunks in the + * compiled DXBC file by name. + */ + class DxbcTag { + + public: + + DxbcTag() { + for (size_t i = 0; i < 4; i++) + m_chars[i] = '\0'; + } + + DxbcTag(const char* tag) { + for (size_t i = 0; i < 4; i++) + m_chars[i] = tag[i]; + } + + bool operator == (const DxbcTag& other) const { + bool result = true; + for (size_t i = 0; i < 4; i++) + result &= m_chars[i] == other.m_chars[i]; + return result; + } + + bool operator != (const DxbcTag& other) const { + return !this->operator == (other); + } + + const char* operator & () const { return m_chars; } + char* operator & () { return m_chars; } + + private: + + char m_chars[4]; + + }; + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_util.h b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_util.h new file mode 100644 index 000000000..7d98af6ee --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxbc/dxbc_util.h @@ -0,0 +1,164 @@ +#pragma once + +#include "dxbc_common.h" +#include "dxbc_enums.h" + +namespace dxvk { + + /** + * \brief Push constant struct + */ + struct DxbcPushConstants { + uint32_t rasterizerSampleCount; + }; + + + /** + * \brief Binding numbers and properties + */ + enum DxbcBindingProperties : uint32_t { + DxbcConstBufBindingIndex = 0, + DxbcConstBufBindingCount = 16, + DxbcSamplerBindingIndex = DxbcConstBufBindingIndex + + DxbcConstBufBindingCount, + DxbcSamplerBindingCount = 16, + DxbcResourceBindingIndex = DxbcSamplerBindingIndex + + DxbcSamplerBindingCount, + DxbcResourceBindingCount = 128, + DxbcStageBindingCount = DxbcConstBufBindingCount + + DxbcSamplerBindingCount + + DxbcResourceBindingCount, + DxbcUavBindingIndex = DxbcStageBindingCount * 6, + DxbcUavBindingCount = 64, + }; + + + /** + * \brief Shader binding mask + * + * Stores a bit masks of resource bindings + * that are accessed by any given shader. + */ + struct DxbcBindingMask { + uint32_t cbvMask = 0u; + uint32_t samplerMask = 0u; + uint64_t uavMask = 0u; + std::array srvMask = { }; + + void reset() { + cbvMask = 0u; + samplerMask = 0u; + uavMask = 0u; + srvMask = { }; + } + + bool empty() const { + uint64_t mask = (uint64_t(cbvMask) | uint64_t(samplerMask) << 32u) + | (uavMask | srvMask[0] | srvMask[1]); + return !mask; + } + + DxbcBindingMask operator & (const DxbcBindingMask& other) const { + DxbcBindingMask result = *this; + result.cbvMask &= other.cbvMask; + result.samplerMask &= other.samplerMask; + result.uavMask &= other.uavMask; + result.srvMask[0] &= other.srvMask[0]; + result.srvMask[1] &= other.srvMask[1]; + return result; + } + }; + + + /** + * \brief Computes first binding index for a given stage + * + * \param [in] stage The shader stage + * \returns Index of first binding + */ + inline uint32_t computeStageBindingOffset(DxbcProgramType stage) { + return DxbcStageBindingCount * uint32_t(stage); + } + + + /** + * \brief Computes first UAV binding index offset for a given stage + * + * \param [in] stage The shader stage + * \returns Index of first UAV binding + */ + inline uint32_t computeStageUavBindingOffset(DxbcProgramType stage) { + return DxbcUavBindingIndex + + DxbcUavBindingCount * (stage == DxbcProgramType::ComputeShader ? 2 : 0); + } + + + /** + * \brief Computes constant buffer binding index + * + * \param [in] stage Shader stage + * \param [in] index Constant buffer index + * \returns Binding index + */ + inline uint32_t computeConstantBufferBinding(DxbcProgramType stage, uint32_t index) { + return computeStageBindingOffset(stage) + DxbcConstBufBindingIndex + index; + } + + + /** + * \brief Computes sampler binding index + * + * \param [in] stage Shader stage + * \param [in] index Sampler index + * \returns Binding index + */ + inline uint32_t computeSamplerBinding(DxbcProgramType stage, uint32_t index) { + return computeStageBindingOffset(stage) + DxbcSamplerBindingIndex + index; + } + + + /** + * \brief Computes resource binding index + * + * \param [in] stage Shader stage + * \param [in] index Resource index + * \returns Binding index + */ + inline uint32_t computeSrvBinding(DxbcProgramType stage, uint32_t index) { + return computeStageBindingOffset(stage) + DxbcResourceBindingIndex + index; + } + + + /** + * \brief Computes UAV binding offset + * + * \param [in] stage Shader stage + * \param [in] index UAV index + * \returns Binding index + */ + inline uint32_t computeUavBinding(DxbcProgramType stage, uint32_t index) { + return computeStageUavBindingOffset(stage) + index; + } + + + /** + * \brief Computes UAV counter binding offset + * + * \param [in] stage Shader stage + * \param [in] index UAV index + * \returns Binding index + */ + inline uint32_t computeUavCounterBinding(DxbcProgramType stage, uint32_t index) { + return computeStageUavBindingOffset(stage) + DxbcUavBindingCount + index; + } + + /** + * \brief Primitive vertex count + * + * Calculates the number of vertices + * for a given primitive type. + */ + uint32_t primitiveVertexCount( + DxbcPrimitive primitive); + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxvk/dxvk_hash.h b/app/src/main/cpp/thirdparty/dxbc/include/dxvk/dxvk_hash.h new file mode 100644 index 000000000..120e268cb --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxvk/dxvk_hash.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +namespace dxvk { + + struct DxvkEq { + template + size_t operator () (const T& a, const T& b) const { + return a.eq(b); + } + }; + + struct DxvkHash { + template + size_t operator () (const T& object) const { + return object.hash(); + } + }; + + class DxvkHashState { + + public: + + void add(size_t hash) { + m_value ^= hash + 0x9e3779b9 + + (m_value << 6) + + (m_value >> 2); + } + + operator size_t () const { + return m_value; + } + + private: + + size_t m_value = 0; + + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxvk/dxvk_limits.h b/app/src/main/cpp/thirdparty/dxbc/include/dxvk/dxvk_limits.h new file mode 100644 index 000000000..4903c32ce --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxvk/dxvk_limits.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace dxvk { + + enum DxvkLimits : size_t { + MaxNumRenderTargets = 8, + MaxNumVertexAttributes = 32, + MaxNumVertexBindings = 32, + MaxNumXfbBuffers = 4, + MaxNumXfbStreams = 4, + MaxNumViewports = 16, + MaxNumResourceSlots = 1216, + MaxNumQueuedCommandBuffers = 32, + MaxNumQueryCountPerPool = 128, + MaxNumSpecConstants = 12, + MaxUniformBufferSize = 65536, + MaxVertexBindingStride = 2048, + MaxPushConstantSize = 128, + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/dxvk/dxvk_pipelayout.h b/app/src/main/cpp/thirdparty/dxbc/include/dxvk/dxvk_pipelayout.h new file mode 100644 index 000000000..9873719c7 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/dxvk/dxvk_pipelayout.h @@ -0,0 +1,107 @@ +#pragma once + +#include + +#include + +#include "dxvk_hash.h" + +#include "util_math.h" +#include "util_bit.h" +#include "util_flags.h" + +namespace dxvk { + + class DxvkDevice; + class DxvkPipelineManager; + + /** + * \brief Order-invariant atomic access operation + * + * Information used to optimize barriers when a resource + * is accessed exlusively via order-invariant stores. + */ + struct DxvkAccessOp { + enum OpType : uint16_t { + None = 0x0u, + Or = 0x1u, + And = 0x2u, + Xor = 0x3u, + Add = 0x4u, + IMin = 0x5u, + IMax = 0x6u, + UMin = 0x7u, + UMax = 0x8u, + + StoreF = 0xdu, + StoreUi = 0xeu, + StoreSi = 0xfu, + }; + + DxvkAccessOp() = default; + DxvkAccessOp(OpType t) + : op(uint16_t(t)) { } + + DxvkAccessOp(OpType t, uint16_t constant) + : op(uint16_t(t) | (constant << 4u)) { } + + uint16_t op = 0u; + + bool operator == (const DxvkAccessOp& t) const { return op == t.op; } + bool operator != (const DxvkAccessOp& t) const { return op != t.op; } + + template, bool> = true> + explicit operator T() const { return op; } + }; + + static_assert(sizeof(DxvkAccessOp) == sizeof(uint16_t)); + + /** + * \brief Binding info + * + * Stores metadata for a single binding in + * a given shader, or for the whole pipeline. + */ + struct DxvkBindingInfo { + VkDescriptorType descriptorType = VK_DESCRIPTOR_TYPE_MAX_ENUM; ///< Vulkan descriptor type + uint32_t resourceBinding = 0u; ///< API binding slot for the resource + VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_MAX_ENUM; ///< Image view type + VkShaderStageFlagBits stage = VK_SHADER_STAGE_FLAG_BITS_MAX_ENUM; ///< Shader stage + VkAccessFlags access = 0u; ///< Access mask for the resource + DxvkAccessOp accessOp = DxvkAccessOp::None; ///< Order-invariant store type, if any + bool uboSet = false; ///< Whether to include this in the UBO set + bool isMultisampled = false; ///< Multisampled binding + + /** + * \brief Computes descriptor set index for the given binding + * + * This is determines based on the shader stages that use the binding. + * \returns Descriptor set index + */ + uint32_t computeSetIndex() const; + + /** + * \brief Numeric value of the binding + * + * Used when sorting bindings. + * \returns Numeric value + */ + uint32_t value() const; + + /** + * \brief Checks for equality + * + * \param [in] other Binding to compare to + * \returns \c true if both bindings are equal + */ + bool eq(const DxvkBindingInfo& other) const; + + /** + * \brief Hashes binding info + * \returns Binding hash + */ + size_t hash() const; + + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_code_buffer.h b/app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_code_buffer.h new file mode 100644 index 000000000..4e33d6dcb --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_code_buffer.h @@ -0,0 +1,236 @@ +#pragma once + +#include +#include +#include + +#include "spirv_instruction.h" + +namespace dxvk { + + /** + * \brief SPIR-V code buffer + * + * Helper class for generating SPIR-V shaders. + * Stores arbitrary SPIR-V instructions in a + * format that can be read by Vulkan drivers. + */ + class SpirvCodeBuffer { + + public: + + SpirvCodeBuffer(); + explicit SpirvCodeBuffer(uint32_t size); + SpirvCodeBuffer(const SpirvCodeBuffer &) = default; + SpirvCodeBuffer(SpirvCodeBuffer &&) = default; + SpirvCodeBuffer(uint32_t size, const uint32_t* data); + SpirvCodeBuffer(std::istream& stream); + + template + SpirvCodeBuffer(const uint32_t (&data)[N]) + : SpirvCodeBuffer(N, data) { } + + ~SpirvCodeBuffer(); + + SpirvCodeBuffer &operator=(const SpirvCodeBuffer &) = default; + SpirvCodeBuffer &operator=(SpirvCodeBuffer &&) = default; + + /** + * \brief Code data + * \returns Code data + */ + const uint32_t* data() const { return m_code.data(); } + uint32_t* data() { return m_code.data(); } + + /** + * \brief Code size, in dwords + * \returns Code size, in dwords + */ + uint32_t dwords() const { + return m_code.size(); + } + + /** + * \brief Code size, in bytes + * \returns Code size, in bytes + */ + size_t size() const { + return m_code.size() * sizeof(uint32_t); + } + + /** + * \brief Begin instruction iterator + * + * Points to the first instruction in the instruction + * block. The header, if any, will be skipped over. + * \returns Instruction iterator + */ + SpirvInstructionIterator begin() { + return SpirvInstructionIterator( + m_code.data(), 0, m_code.size()); + } + + /** + * \brief End instruction iterator + * + * Points to the end of the instruction block. + * \returns Instruction iterator + */ + SpirvInstructionIterator end() { + return SpirvInstructionIterator(nullptr, 0, 0); + } + + /** + * \brief Allocates a new ID + * + * Returns a new valid ID and increments the + * maximum ID count stored in the header. + * \returns The new SPIR-V ID + */ + uint32_t allocId(); + + /** + * \brief Appends an instruction + * + * Slightly faster than individually adding words. + * \param [in] ins Instruction + */ + void append(const SpirvInstruction& ins); + + /** + * \brief Merges two code buffers + * + * This is useful to generate declarations or + * the SPIR-V header at the same time as the + * code when doing so in advance is impossible. + * \param [in] other Code buffer to append + */ + void append(const SpirvCodeBuffer& other); + + /** + * \brief Appends an 32-bit word to the buffer + * \param [in] word The word to append + */ + void putWord(uint32_t word); + + /** + * \brief Appends an instruction word to the buffer + * + * Adds a single word containing both the word count + * and the op code number for a single instruction. + * \param [in] opCode Operand code + * \param [in] wordCount Number of words + */ + void putIns(spv::Op opCode, uint16_t wordCount); + + /** + * \brief Appends a 32-bit integer to the buffer + * \param [in] value The number to add + */ + void putInt32(uint32_t word); + + /** + * \brief Appends a 64-bit integer to the buffer + * + * A 64-bit integer will take up two 32-bit words. + * \param [in] value 64-bit value to add + */ + void putInt64(uint64_t value); + + /** + * \brief Appends a 32-bit float to the buffer + * \param [in] value The number to add + */ + void putFloat32(float value); + + /** + * \brief Appends a 64-bit float to the buffer + * \param [in] value The number to add + */ + void putFloat64(double value); + + /** + * \brief Appends a literal string to the buffer + * \param [in] str String to append to the buffer + */ + void putStr(const char* str); + + /** + * \brief Adds the header to the buffer + * + * \param [in] version SPIR-V version + * \param [in] boundIds Number of bound IDs + */ + void putHeader(uint32_t version, uint32_t boundIds); + + /** + * \brief Erases given number of dwords + * + * Removes data from the code buffer, starting + * at the current insertion offset. + * \param [in] size Number of words to remove + */ + void erase(size_t size); + + /** + * \brief Computes length of a literal string + * + * \param [in] str The string to check + * \returns Number of words consumed by a string + */ + uint32_t strLen(const char* str); + + /** + * \brief Stores the SPIR-V module to a stream + * + * The ability to save modules to a file + * exists mostly for debugging purposes. + * \param [in] stream Output stream + */ + void store(std::ostream& stream) const; + + /** + * \brief Retrieves current insertion pointer + * + * Sometimes it may be necessay to insert code into the + * middle of the stream rather than appending it. This + * retrieves the current function pointer. Note that the + * pointer will become invalid if any code is inserted + * before the current pointer location. + * \returns Current instruction pointr + */ + size_t getInsertionPtr() const { + return m_ptr; + } + + /** + * \brief Sets insertion pointer to a specific value + * + * Sets the insertion pointer to a value that was + * previously retrieved by \ref getInsertionPtr. + * \returns Current instruction pointr + */ + void beginInsertion(size_t ptr) { + m_ptr = ptr; + } + + /** + * \brief Sets insertion pointer to the end + * + * After this call, new instructions will be + * appended to the stream. In other words, + * this will restore default behaviour. + * \returns Previous instruction pointer + */ + size_t endInsertion() { + return std::exchange(m_ptr, m_code.size()); + } + + private: + + std::vector m_code; + size_t m_ptr = 0; + + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_include.h b/app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_include.h new file mode 100644 index 000000000..36651dcb1 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_include.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +#include "log/log.h" +#include "log/log_debug.h" + +#include "util_error.h" +#include "util_flags.h" +#include "util_likely.h" +#include "util_string.h" + +#include "rc/util_rc.h" +#include "rc/util_rc_ptr.h" diff --git a/app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_instruction.h b/app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_instruction.h new file mode 100644 index 000000000..1f9314157 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_instruction.h @@ -0,0 +1,158 @@ +#pragma once + +#include "spirv_include.h" + +namespace dxvk { + + /** + * \brief SPIR-V instruction + * + * Helps parsing a single instruction, providing + * access to the op code, instruction length and + * instruction arguments. + */ + class SpirvInstruction { + + public: + + SpirvInstruction() { } + SpirvInstruction(uint32_t* code, uint32_t offset, uint32_t length) + : m_code(code), m_offset(offset), m_length(length) { } + + /** + * \brief SPIR-V Op code + * \returns The op code + */ + spv::Op opCode() const { + return static_cast( + this->arg(0) & spv::OpCodeMask); + } + + /** + * \brief Instruction length + * \returns Number of DWORDs + */ + uint32_t length() const { + return this->arg(0) >> spv::WordCountShift; + } + + /** + * \brief Instruction offset + * \returns Offset in DWORDs + */ + uint32_t offset() const { + return m_offset; + } + + /** + * \brief Argument value + * + * Retrieves an argument DWORD. Note that some instructions + * take 64-bit arguments which require more than one DWORD. + * Arguments start at index 1. Calling this method with an + * argument ID of 0 will return the opcode token. + * \param [in] idx Argument index, starting at 1 + * \returns The argument value + */ + uint32_t arg(uint32_t idx) const { + const uint32_t index = m_offset + idx; + return index < m_length ? m_code[index] : 0; + } + + /** + * \brief Argument string + * + * Retrieves a pointer to a UTF-8-encoded string. + * \param [in] idx Argument index, starting at 1 + * \returns Pointer to the literal string + */ + const char* chr(uint32_t idx) const { + const uint32_t index = m_offset + idx; + return index < m_length ? reinterpret_cast(&m_code[index]) : nullptr; + } + + /** + * \brief Changes the value of an argument + * + * \param [in] idx Argument index, starting at 1 + * \param [in] word New argument word + */ + void setArg(uint32_t idx, uint32_t word) const { + if (m_offset + idx < m_length) + m_code[m_offset + idx] = word; + } + + private: + + uint32_t* m_code = nullptr; + uint32_t m_offset = 0; + uint32_t m_length = 0; + + }; + + + /** + * \brief SPIR-V instruction iterator + * + * Convenient iterator that can be used + * to process raw SPIR-V shader code. + */ + class SpirvInstructionIterator { + + public: + + SpirvInstructionIterator() { } + SpirvInstructionIterator(uint32_t* code, uint32_t offset, uint32_t length) + : m_code (length != 0 ? code : nullptr), + m_offset(length != 0 ? offset : 0), + m_length(length) { + if ((length >= 5) && (offset == 0) && (m_code[0] == spv::MagicNumber)) + this->advance(5); + } + + SpirvInstructionIterator& operator ++ () { + this->advance(SpirvInstruction(m_code, m_offset, m_length).length()); + return *this; + } + + SpirvInstructionIterator operator ++ (int) { + SpirvInstructionIterator result = *this; + this->advance(SpirvInstruction(m_code, m_offset, m_length).length()); + return result; + } + + SpirvInstruction operator * () const { + return SpirvInstruction(m_code, m_offset, m_length); + } + + bool operator == (const SpirvInstructionIterator& other) const { + return this->m_code == other.m_code + && this->m_offset == other.m_offset + && this->m_length == other.m_length; + } + + bool operator != (const SpirvInstructionIterator& other) const { + return this->m_code != other.m_code + || this->m_offset != other.m_offset + || this->m_length != other.m_length; + } + + private: + + uint32_t* m_code = nullptr; + uint32_t m_offset = 0; + uint32_t m_length = 0; + + void advance(uint32_t n) { + if (m_offset + n < m_length) { + m_offset += n; + } else { + m_code = nullptr; + m_offset = 0; + m_length = 0; + } + } + + }; + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_module.h b/app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_module.h new file mode 100644 index 000000000..02b7b7cf7 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/spirv/spirv_module.h @@ -0,0 +1,1350 @@ +#pragma once + +#include +#include +#include + +#include "spirv_code_buffer.h" + +namespace dxvk { + + struct SpirvPhiLabel { + uint32_t varId = 0; + uint32_t labelId = 0; + }; + + struct SpirvSwitchCaseLabel { + uint32_t literal = 0; + uint32_t labelId = 0; + }; + + struct SpirvMemoryOperands { + uint32_t flags = 0; + uint32_t alignment = 0; + uint32_t makeAvailable = 0; + uint32_t makeVisible = 0; + }; + + struct SpirvImageOperands { + uint32_t flags = 0; + uint32_t sLodBias = 0; + uint32_t sLod = 0; + uint32_t sConstOffset = 0; + uint32_t sGradX = 0; + uint32_t sGradY = 0; + uint32_t gOffset = 0; + uint32_t gConstOffsets = 0; + uint32_t sSampleId = 0; + uint32_t sMinLod = 0; + uint32_t makeAvailable = 0; + uint32_t makeVisible = 0; + bool sparse = false; + }; + + constexpr uint32_t spvVersion(uint32_t major, uint32_t minor) { + return (major << 16) | (minor << 8); + } + + /** + * \brief SPIR-V module + * + * This class generates a code buffer containing a full + * SPIR-V shader module. Ensures that the module layout + * is valid, as defined in the SPIR-V 1.0 specification, + * section 2.4 "Logical Layout of a Module". + */ + class SpirvModule { + + public: + + explicit SpirvModule(uint32_t version); + + ~SpirvModule(); + + SpirvCodeBuffer compile(); + + size_t getInsertionPtr() { + return m_code.getInsertionPtr(); + } + + void beginInsertion(size_t ptr) { + m_code.beginInsertion(ptr); + } + + void endInsertion() { + m_code.endInsertion(); + } + + uint32_t getBlockId() const { + return m_blockId; + } + + uint32_t allocateId(); + + bool hasCapability( + spv::Capability capability); + + void enableCapability( + spv::Capability capability); + + void enableExtension( + const char* extensionName); + + void addEntryPoint( + uint32_t entryPointId, + spv::ExecutionModel executionModel, + const char* name); + + void setMemoryModel( + spv::AddressingModel addressModel, + spv::MemoryModel memoryModel); + + void setExecutionMode( + uint32_t entryPointId, + spv::ExecutionMode executionMode); + + void setExecutionMode( + uint32_t entryPointId, + spv::ExecutionMode executionMode, + uint32_t argCount, + const uint32_t* args); + + void setInvocations( + uint32_t entryPointId, + uint32_t invocations); + + void setLocalSize( + uint32_t entryPointId, + uint32_t x, + uint32_t y, + uint32_t z); + + void setOutputVertices( + uint32_t entryPointId, + uint32_t vertexCount); + + uint32_t addDebugString( + const char* string); + + void setDebugSource( + spv::SourceLanguage language, + uint32_t version, + uint32_t file, + const char* source); + + void setDebugName( + uint32_t expressionId, + const char* debugName); + + void setDebugMemberName( + uint32_t structId, + uint32_t memberId, + const char* debugName); + + uint32_t constBool( + bool v); + + uint32_t consti32( + int32_t v); + + uint32_t consti64( + int64_t v); + + uint32_t constu32( + uint32_t v); + + uint32_t constu64( + uint64_t v); + + uint32_t constf32( + float v); + + uint32_t constf64( + double v); + + uint32_t constvec4i32( + int32_t x, + int32_t y, + int32_t z, + int32_t w); + + uint32_t constvec4b32( + bool x, + bool y, + bool z, + bool w); + + uint32_t constvec4u32( + uint32_t x, + uint32_t y, + uint32_t z, + uint32_t w); + + uint32_t constvec2f32( + float x, + float y); + + uint32_t constvec3f32( + float x, + float y, + float z); + + uint32_t constvec4f32( + float x, + float y, + float z, + float w); + + uint32_t constfReplicant( + float replicant, + uint32_t count); + + uint32_t constbReplicant( + bool replicant, + uint32_t count); + + uint32_t constiReplicant( + int32_t replicant, + uint32_t count); + + uint32_t constuReplicant( + int32_t replicant, + uint32_t count); + + uint32_t constComposite( + uint32_t typeId, + uint32_t constCount, + const uint32_t* constIds); + + uint32_t constUndef( + uint32_t typeId); + + uint32_t constNull( + uint32_t typeId); + + uint32_t lateConst32( + uint32_t typeId); + + void setLateConst( + uint32_t constId, + const uint32_t* argIds); + + uint32_t specConstBool( + bool v); + + uint32_t specConst32( + uint32_t typeId, + uint32_t value); + + void decorate( + uint32_t object, + spv::Decoration decoration); + + void decorateArrayStride( + uint32_t object, + uint32_t stride); + + void decorateBinding( + uint32_t object, + uint32_t binding); + + void decorateBlock( + uint32_t object); + + void decorateBuiltIn( + uint32_t object, + spv::BuiltIn builtIn); + + void decorateComponent( + uint32_t object, + uint32_t location); + + void decorateDescriptorSet( + uint32_t object, + uint32_t set); + + void decorateIndex( + uint32_t object, + uint32_t index); + + void decorateLocation( + uint32_t object, + uint32_t location); + + void decorateSpecId( + uint32_t object, + uint32_t specId); + + void decorateXfb( + uint32_t object, + uint32_t streamId, + uint32_t bufferId, + uint32_t offset, + uint32_t stride); + + void memberDecorateBuiltIn( + uint32_t structId, + uint32_t memberId, + spv::BuiltIn builtIn); + + void memberDecorate( + uint32_t structId, + uint32_t memberId, + spv::Decoration decoration); + + void memberDecorateMatrixStride( + uint32_t structId, + uint32_t memberId, + uint32_t stride); + + void memberDecorateOffset( + uint32_t structId, + uint32_t memberId, + uint32_t offset); + + uint32_t defVoidType(); + + uint32_t defBoolType(); + + uint32_t defIntType( + uint32_t width, + uint32_t isSigned); + + uint32_t defFloatType( + uint32_t width); + + uint32_t defVectorType( + uint32_t elementType, + uint32_t elementCount); + + uint32_t defMatrixType( + uint32_t columnType, + uint32_t columnCount); + + uint32_t defArrayType( + uint32_t typeId, + uint32_t length); + + uint32_t defArrayTypeUnique( + uint32_t typeId, + uint32_t length); + + uint32_t defRuntimeArrayType( + uint32_t typeId); + + uint32_t defRuntimeArrayTypeUnique( + uint32_t typeId); + + uint32_t defFunctionType( + uint32_t returnType, + uint32_t argCount, + const uint32_t* argTypes); + + uint32_t defStructType( + uint32_t memberCount, + const uint32_t* memberTypes); + + uint32_t defStructTypeUnique( + uint32_t memberCount, + const uint32_t* memberTypes); + + uint32_t defPointerType( + uint32_t variableType, + spv::StorageClass storageClass); + + uint32_t defSamplerType(); + + uint32_t defImageType( + uint32_t sampledType, + spv::Dim dimensionality, + uint32_t depth, + uint32_t arrayed, + uint32_t multisample, + uint32_t sampled, + spv::ImageFormat format); + + uint32_t defSampledImageType( + uint32_t imageType); + + uint32_t newVar( + uint32_t pointerType, + spv::StorageClass storageClass); + + uint32_t newVarInit( + uint32_t pointerType, + spv::StorageClass storageClass, + uint32_t initialValue); + + void functionBegin( + uint32_t returnType, + uint32_t functionId, + uint32_t functionType, + spv::FunctionControlMask functionControl); + + uint32_t functionParameter( + uint32_t parameterType); + + void functionEnd(); + + uint32_t opAccessChain( + uint32_t resultType, + uint32_t composite, + uint32_t indexCount, + const uint32_t* indexArray); + + uint32_t opArrayLength( + uint32_t resultType, + uint32_t structure, + uint32_t memberId); + + uint32_t opAny( + uint32_t resultType, + uint32_t vector); + + uint32_t opAll( + uint32_t resultType, + uint32_t vector); + + uint32_t opAtomicLoad( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics); + + void opAtomicStore( + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value); + + uint32_t opAtomicExchange( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value); + + uint32_t opAtomicCompareExchange( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t equal, + uint32_t unequal, + uint32_t value, + uint32_t comparator); + + uint32_t opAtomicIIncrement( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics); + + uint32_t opAtomicIDecrement( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics); + + uint32_t opAtomicIAdd( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value); + + uint32_t opAtomicISub( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value); + + uint32_t opAtomicSMin( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value); + + uint32_t opAtomicSMax( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value); + + uint32_t opAtomicUMin( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value); + + uint32_t opAtomicUMax( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value); + + uint32_t opAtomicAnd( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value); + + uint32_t opAtomicOr( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value); + + uint32_t opAtomicXor( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value); + + uint32_t opBitcast( + uint32_t resultType, + uint32_t operand); + + uint32_t opBitCount( + uint32_t resultType, + uint32_t operand); + + uint32_t opBitReverse( + uint32_t resultType, + uint32_t operand); + + uint32_t opFindILsb( + uint32_t resultType, + uint32_t operand); + + uint32_t opFindUMsb( + uint32_t resultType, + uint32_t operand); + + uint32_t opFindSMsb( + uint32_t resultType, + uint32_t operand); + + uint32_t opBitFieldInsert( + uint32_t resultType, + uint32_t base, + uint32_t insert, + uint32_t offset, + uint32_t count); + + uint32_t opBitFieldSExtract( + uint32_t resultType, + uint32_t base, + uint32_t offset, + uint32_t count); + + uint32_t opBitFieldUExtract( + uint32_t resultType, + uint32_t base, + uint32_t offset, + uint32_t count); + + uint32_t opBitwiseAnd( + uint32_t resultType, + uint32_t operand1, + uint32_t operand2); + + uint32_t opBitwiseOr( + uint32_t resultType, + uint32_t operand1, + uint32_t operand2); + + uint32_t opBitwiseXor( + uint32_t resultType, + uint32_t operand1, + uint32_t operand2); + + uint32_t opNot( + uint32_t resultType, + uint32_t operand); + + uint32_t opShiftLeftLogical( + uint32_t resultType, + uint32_t base, + uint32_t shift); + + uint32_t opShiftRightArithmetic( + uint32_t resultType, + uint32_t base, + uint32_t shift); + + uint32_t opShiftRightLogical( + uint32_t resultType, + uint32_t base, + uint32_t shift); + + uint32_t opConvertFtoS( + uint32_t resultType, + uint32_t operand); + + uint32_t opConvertFtoU( + uint32_t resultType, + uint32_t operand); + + uint32_t opConvertStoF( + uint32_t resultType, + uint32_t operand); + + uint32_t opConvertUtoF( + uint32_t resultType, + uint32_t operand); + + uint32_t opCompositeConstruct( + uint32_t resultType, + uint32_t valueCount, + const uint32_t* valueArray); + + uint32_t opCompositeExtract( + uint32_t resultType, + uint32_t composite, + uint32_t indexCount, + const uint32_t* indexArray); + + uint32_t opCompositeInsert( + uint32_t resultType, + uint32_t object, + uint32_t composite, + uint32_t indexCount, + const uint32_t* indexArray); + + uint32_t opDpdx( + uint32_t resultType, + uint32_t operand); + + uint32_t opDpdy( + uint32_t resultType, + uint32_t operand); + + uint32_t opDpdxCoarse( + uint32_t resultType, + uint32_t operand); + + uint32_t opDpdyCoarse( + uint32_t resultType, + uint32_t operand); + + uint32_t opDpdxFine( + uint32_t resultType, + uint32_t operand); + + uint32_t opDpdyFine( + uint32_t resultType, + uint32_t operand); + + uint32_t opVectorExtractDynamic( + uint32_t resultType, + uint32_t vector, + uint32_t index); + + uint32_t opVectorShuffle( + uint32_t resultType, + uint32_t vectorLeft, + uint32_t vectorRight, + uint32_t indexCount, + const uint32_t* indexArray); + + uint32_t opSNegate( + uint32_t resultType, + uint32_t operand); + + uint32_t opFNegate( + uint32_t resultType, + uint32_t operand); + + uint32_t opSAbs( + uint32_t resultType, + uint32_t operand); + + uint32_t opFAbs( + uint32_t resultType, + uint32_t operand); + + uint32_t opFSign( + uint32_t resultType, + uint32_t operand); + + uint32_t opFMix( + uint32_t resultType, + uint32_t x, + uint32_t y, + uint32_t a); + + uint32_t opCross( + uint32_t resultType, + uint32_t x, + uint32_t y); + + uint32_t opIAdd( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opISub( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opFAdd( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opFSub( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opSDiv( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opUDiv( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opSRem( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opUMod( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opFDiv( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opIMul( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opFMul( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opVectorTimesScalar( + uint32_t resultType, + uint32_t vector, + uint32_t scalar); + + uint32_t opMatrixTimesMatrix( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opMatrixTimesVector( + uint32_t resultType, + uint32_t matrix, + uint32_t vector); + + uint32_t opVectorTimesMatrix( + uint32_t resultType, + uint32_t vector, + uint32_t matrix); + + uint32_t opTranspose( + uint32_t resultType, + uint32_t matrix); + + uint32_t opInverse( + uint32_t resultType, + uint32_t matrix); + + uint32_t opFFma( + uint32_t resultType, + uint32_t a, + uint32_t b, + uint32_t c); + + uint32_t opFMax( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opFMin( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opNMax( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opNMin( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opSMax( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opSMin( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opUMax( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opUMin( + uint32_t resultType, + uint32_t a, + uint32_t b); + + uint32_t opFClamp( + uint32_t resultType, + uint32_t x, + uint32_t minVal, + uint32_t maxVal); + + uint32_t opNClamp( + uint32_t resultType, + uint32_t x, + uint32_t minVal, + uint32_t maxVal); + + uint32_t opIEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opINotEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opSLessThan( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opSLessThanEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opSGreaterThan( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opSGreaterThanEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opULessThan( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opULessThanEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opUGreaterThan( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opUGreaterThanEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opFOrdEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opFUnordNotEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opFOrdLessThan( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opFOrdLessThanEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opFOrdGreaterThan( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opFOrdGreaterThanEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opLogicalEqual( + uint32_t resultType, + uint32_t operand1, + uint32_t operand2); + + uint32_t opLogicalNotEqual( + uint32_t resultType, + uint32_t operand1, + uint32_t operand2); + + uint32_t opLogicalAnd( + uint32_t resultType, + uint32_t operand1, + uint32_t operand2); + + uint32_t opLogicalOr( + uint32_t resultType, + uint32_t operand1, + uint32_t operand2); + + uint32_t opLogicalNot( + uint32_t resultType, + uint32_t operand); + + uint32_t opDot( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2); + + uint32_t opSin( + uint32_t resultType, + uint32_t vector); + + uint32_t opCos( + uint32_t resultType, + uint32_t vector); + + uint32_t opSqrt( + uint32_t resultType, + uint32_t operand); + + uint32_t opInverseSqrt( + uint32_t resultType, + uint32_t operand); + + uint32_t opNormalize( + uint32_t resultType, + uint32_t operand); + + uint32_t opRawAccessChain( + uint32_t resultType, + uint32_t base, + uint32_t stride, + uint32_t index, + uint32_t offset, + uint32_t operand); + + uint32_t opReflect( + uint32_t resultType, + uint32_t incident, + uint32_t normal); + + uint32_t opLength( + uint32_t resultType, + uint32_t operand); + + uint32_t opExp2( + uint32_t resultType, + uint32_t operand); + + uint32_t opExp( + uint32_t resultType, + uint32_t operand); + + uint32_t opLog2( + uint32_t resultType, + uint32_t operand); + + uint32_t opPow( + uint32_t resultType, + uint32_t base, + uint32_t exponent); + + uint32_t opFract( + uint32_t resultType, + uint32_t operand); + + uint32_t opCeil( + uint32_t resultType, + uint32_t operand); + + uint32_t opFloor( + uint32_t resultType, + uint32_t operand); + + uint32_t opRound( + uint32_t resultType, + uint32_t operand); + + uint32_t opRoundEven( + uint32_t resultType, + uint32_t operand); + + uint32_t opTrunc( + uint32_t resultType, + uint32_t operand); + + uint32_t opFConvert( + uint32_t resultType, + uint32_t operand); + + uint32_t opPackHalf2x16( + uint32_t resultType, + uint32_t operand); + + uint32_t opUnpackHalf2x16( + uint32_t resultType, + uint32_t operand); + + uint32_t opSelect( + uint32_t resultType, + uint32_t condition, + uint32_t operand1, + uint32_t operand2); + + uint32_t opIsNan( + uint32_t resultType, + uint32_t operand); + + uint32_t opIsInf( + uint32_t resultType, + uint32_t operand); + + uint32_t opFunctionCall( + uint32_t resultType, + uint32_t functionId, + uint32_t argCount, + const uint32_t* argIds); + + void opLabel( + uint32_t labelId); + + uint32_t opLoad( + uint32_t typeId, + uint32_t pointerId); + + uint32_t opLoad( + uint32_t typeId, + uint32_t pointerId, + const SpirvMemoryOperands& operands); + + void opStore( + uint32_t pointerId, + uint32_t valueId); + + void opStore( + uint32_t pointerId, + uint32_t valueId, + const SpirvMemoryOperands& operands); + + uint32_t opInterpolateAtCentroid( + uint32_t resultType, + uint32_t interpolant); + + uint32_t opInterpolateAtSample( + uint32_t resultType, + uint32_t interpolant, + uint32_t sample); + + uint32_t opInterpolateAtOffset( + uint32_t resultType, + uint32_t interpolant, + uint32_t offset); + + uint32_t opImage( + uint32_t resultType, + uint32_t sampledImage); + + uint32_t opImageRead( + uint32_t resultType, + uint32_t image, + uint32_t coordinates, + const SpirvImageOperands& operands); + + void opImageWrite( + uint32_t image, + uint32_t coordinates, + uint32_t texel, + const SpirvImageOperands& operands); + + uint32_t opImageSparseTexelsResident( + uint32_t resultType, + uint32_t residentCode); + + uint32_t opImageTexelPointer( + uint32_t resultType, + uint32_t image, + uint32_t coordinates, + uint32_t sample); + + uint32_t opSampledImage( + uint32_t resultType, + uint32_t image, + uint32_t sampler); + + uint32_t opImageQuerySizeLod( + uint32_t resultType, + uint32_t image, + uint32_t lod); + + uint32_t opImageQuerySize( + uint32_t resultType, + uint32_t image); + + uint32_t opImageQueryLevels( + uint32_t resultType, + uint32_t image); + + uint32_t opImageQueryLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates); + + uint32_t opImageQuerySamples( + uint32_t resultType, + uint32_t image); + + uint32_t opImageFetch( + uint32_t resultType, + uint32_t image, + uint32_t coordinates, + const SpirvImageOperands& operands); + + uint32_t opImageGather( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + uint32_t component, + const SpirvImageOperands& operands); + + uint32_t opImageDrefGather( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + uint32_t reference, + const SpirvImageOperands& operands); + + uint32_t opImageSampleImplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + const SpirvImageOperands& operands); + + uint32_t opImageSampleExplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + const SpirvImageOperands& operands); + + uint32_t opImageSampleProjImplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + const SpirvImageOperands& operands); + + uint32_t opImageSampleProjExplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + const SpirvImageOperands& operands); + + uint32_t opImageSampleDrefImplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + uint32_t reference, + const SpirvImageOperands& operands); + + uint32_t opImageSampleDrefExplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + uint32_t reference, + const SpirvImageOperands& operands); + + uint32_t opImageSampleProjDrefImplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + uint32_t reference, + const SpirvImageOperands& operands); + + uint32_t opImageSampleProjDrefExplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + uint32_t reference, + const SpirvImageOperands& operands); + + uint32_t opGroupNonUniformBallot( + uint32_t resultType, + uint32_t execution, + uint32_t predicate); + + uint32_t opGroupNonUniformBallotBitCount( + uint32_t resultType, + uint32_t execution, + uint32_t operation, + uint32_t ballot); + + uint32_t opGroupNonUniformElect( + uint32_t resultType, + uint32_t execution); + + uint32_t opGroupNonUniformBroadcastFirst( + uint32_t resultType, + uint32_t execution, + uint32_t value); + + void opControlBarrier( + uint32_t execution, + uint32_t memory, + uint32_t semantics); + + void opMemoryBarrier( + uint32_t memory, + uint32_t semantics); + + void opLoopMerge( + uint32_t mergeBlock, + uint32_t continueTarget, + uint32_t loopControl); + + void opSelectionMerge( + uint32_t mergeBlock, + uint32_t selectionControl); + + void opBranch( + uint32_t label); + + void opBranchConditional( + uint32_t condition, + uint32_t trueLabel, + uint32_t falseLabel); + + void opSwitch( + uint32_t selector, + uint32_t jumpDefault, + uint32_t caseCount, + const SpirvSwitchCaseLabel* caseLabels); + + uint32_t opPhi( + uint32_t resultType, + uint32_t sourceCount, + const SpirvPhiLabel* sourceLabels); + + void opReturn(); + + void opDemoteToHelperInvocation(); + + void opEmitVertex( + uint32_t streamId); + + void opEndPrimitive( + uint32_t streamId); + + void opBeginInvocationInterlock(); + + void opEndInvocationInterlock(); + + uint32_t opSinCos( + uint32_t x, + bool useBuiltIn); + + private: + + uint32_t m_version; + uint32_t m_id = 1; + uint32_t m_instExtGlsl450 = 0; + uint32_t m_blockId = 0; + + SpirvCodeBuffer m_capabilities; + SpirvCodeBuffer m_extensions; + SpirvCodeBuffer m_instExt; + SpirvCodeBuffer m_memoryModel; + SpirvCodeBuffer m_entryPoints; + SpirvCodeBuffer m_execModeInfo; + SpirvCodeBuffer m_debugNames; + SpirvCodeBuffer m_annotations; + SpirvCodeBuffer m_typeConstDefs; + SpirvCodeBuffer m_variables; + SpirvCodeBuffer m_code; + + std::unordered_set m_lateConsts; + + std::vector m_interfaceVars; + + uint32_t defType( + spv::Op op, + uint32_t argCount, + const uint32_t* argIds); + + uint32_t defConst( + spv::Op op, + uint32_t typeId, + uint32_t argCount, + const uint32_t* argIds); + + void instImportGlsl450(); + + uint32_t getMemoryOperandWordCount( + const SpirvMemoryOperands& op) const; + + void putMemoryOperands( + const SpirvMemoryOperands& op); + + uint32_t getImageOperandWordCount( + const SpirvImageOperands& op) const; + + void putImageOperands( + const SpirvImageOperands& op); + + bool isInterfaceVar( + spv::StorageClass sclass) const; + + void classifyBlocks( + std::unordered_set& reachableBlocks, + std::unordered_set& mergeBlocks); + + static constexpr double sincosTaylorFactor(uint32_t power) { + double result = 1.0; + + for (uint32_t i = 1; i <= power; i++) + result *= pi * 0.25f / double(i); + + return result; + } + + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/spirv/thirdparty/GLSL.std.450.h b/app/src/main/cpp/thirdparty/dxbc/include/spirv/thirdparty/GLSL.std.450.h new file mode 100644 index 000000000..0594f907a --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/spirv/thirdparty/GLSL.std.450.h @@ -0,0 +1,131 @@ +/* +** Copyright (c) 2014-2024 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and/or associated documentation files (the "Materials"), +** to deal in the Materials without restriction, including without limitation +** the rights to use, copy, modify, merge, publish, distribute, sublicense, +** and/or sell copies of the Materials, and to permit persons to whom the +** Materials are 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 Materials. +** +** MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +** STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +** HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +** +** THE MATERIALS ARE 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 MATERIALS OR THE USE OR OTHER DEALINGS +** IN THE MATERIALS. +*/ + +#ifndef GLSLstd450_H +#define GLSLstd450_H + +static const int GLSLstd450Version = 100; +static const int GLSLstd450Revision = 3; + +enum GLSLstd450 { + GLSLstd450Bad = 0, // Don't use + + GLSLstd450Round = 1, + GLSLstd450RoundEven = 2, + GLSLstd450Trunc = 3, + GLSLstd450FAbs = 4, + GLSLstd450SAbs = 5, + GLSLstd450FSign = 6, + GLSLstd450SSign = 7, + GLSLstd450Floor = 8, + GLSLstd450Ceil = 9, + GLSLstd450Fract = 10, + + GLSLstd450Radians = 11, + GLSLstd450Degrees = 12, + GLSLstd450Sin = 13, + GLSLstd450Cos = 14, + GLSLstd450Tan = 15, + GLSLstd450Asin = 16, + GLSLstd450Acos = 17, + GLSLstd450Atan = 18, + GLSLstd450Sinh = 19, + GLSLstd450Cosh = 20, + GLSLstd450Tanh = 21, + GLSLstd450Asinh = 22, + GLSLstd450Acosh = 23, + GLSLstd450Atanh = 24, + GLSLstd450Atan2 = 25, + + GLSLstd450Pow = 26, + GLSLstd450Exp = 27, + GLSLstd450Log = 28, + GLSLstd450Exp2 = 29, + GLSLstd450Log2 = 30, + GLSLstd450Sqrt = 31, + GLSLstd450InverseSqrt = 32, + + GLSLstd450Determinant = 33, + GLSLstd450MatrixInverse = 34, + + GLSLstd450Modf = 35, // second operand needs an OpVariable to write to + GLSLstd450ModfStruct = 36, // no OpVariable operand + GLSLstd450FMin = 37, + GLSLstd450UMin = 38, + GLSLstd450SMin = 39, + GLSLstd450FMax = 40, + GLSLstd450UMax = 41, + GLSLstd450SMax = 42, + GLSLstd450FClamp = 43, + GLSLstd450UClamp = 44, + GLSLstd450SClamp = 45, + GLSLstd450FMix = 46, + GLSLstd450IMix = 47, // Reserved + GLSLstd450Step = 48, + GLSLstd450SmoothStep = 49, + + GLSLstd450Fma = 50, + GLSLstd450Frexp = 51, // second operand needs an OpVariable to write to + GLSLstd450FrexpStruct = 52, // no OpVariable operand + GLSLstd450Ldexp = 53, + + GLSLstd450PackSnorm4x8 = 54, + GLSLstd450PackUnorm4x8 = 55, + GLSLstd450PackSnorm2x16 = 56, + GLSLstd450PackUnorm2x16 = 57, + GLSLstd450PackHalf2x16 = 58, + GLSLstd450PackDouble2x32 = 59, + GLSLstd450UnpackSnorm2x16 = 60, + GLSLstd450UnpackUnorm2x16 = 61, + GLSLstd450UnpackHalf2x16 = 62, + GLSLstd450UnpackSnorm4x8 = 63, + GLSLstd450UnpackUnorm4x8 = 64, + GLSLstd450UnpackDouble2x32 = 65, + + GLSLstd450Length = 66, + GLSLstd450Distance = 67, + GLSLstd450Cross = 68, + GLSLstd450Normalize = 69, + GLSLstd450FaceForward = 70, + GLSLstd450Reflect = 71, + GLSLstd450Refract = 72, + + GLSLstd450FindILsb = 73, + GLSLstd450FindSMsb = 74, + GLSLstd450FindUMsb = 75, + + GLSLstd450InterpolateAtCentroid = 76, + GLSLstd450InterpolateAtSample = 77, + GLSLstd450InterpolateAtOffset = 78, + + GLSLstd450NMin = 79, + GLSLstd450NMax = 80, + GLSLstd450NClamp = 81, + + GLSLstd450Count +}; + +#endif // #ifndef GLSLstd450_H diff --git a/app/src/main/cpp/thirdparty/dxbc/include/spirv/thirdparty/spirv.hpp b/app/src/main/cpp/thirdparty/dxbc/include/spirv/thirdparty/spirv.hpp new file mode 100644 index 000000000..f439e472f --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/spirv/thirdparty/spirv.hpp @@ -0,0 +1,5214 @@ +// Copyright (c) 2014-2024 The Khronos Group Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and/or associated documentation files (the "Materials"), +// to deal in the Materials without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Materials, and to permit persons to whom the +// Materials are 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 Materials. +// +// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +// STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +// HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +// +// THE MATERIALS ARE 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 MATERIALS OR THE USE OR OTHER DEALINGS +// IN THE MATERIALS. + +// This header is automatically generated by the same tool that creates +// the Binary Section of the SPIR-V specification. + +// Enumeration tokens for SPIR-V, in various styles: +// C, C++, C++11, JSON, Lua, Python, C#, D, Beef +// +// - C will have tokens with a "Spv" prefix, e.g.: SpvSourceLanguageGLSL +// - C++ will have tokens in the "spv" name space, e.g.: spv::SourceLanguageGLSL +// - C++11 will use enum classes in the spv namespace, e.g.: spv::SourceLanguage::GLSL +// - Lua will use tables, e.g.: spv.SourceLanguage.GLSL +// - Python will use dictionaries, e.g.: spv['SourceLanguage']['GLSL'] +// - C# will use enum classes in the Specification class located in the "Spv" namespace, +// e.g.: Spv.Specification.SourceLanguage.GLSL +// - D will have tokens under the "spv" module, e.g: spv.SourceLanguage.GLSL +// - Beef will use enum classes in the Specification class located in the "Spv" namespace, +// e.g.: Spv.Specification.SourceLanguage.GLSL +// +// Some tokens act like mask values, which can be OR'd together, +// while others are mutually exclusive. The mask-like ones have +// "Mask" in their name, and a parallel enum that has the shift +// amount (1 << x) for each corresponding enumerant. + +#ifndef spirv_HPP +#define spirv_HPP + +namespace spv { + +typedef unsigned int Id; + +#define SPV_VERSION 0x10600 +#define SPV_REVISION 1 + +static const unsigned int MagicNumber = 0x07230203; +static const unsigned int Version = 0x00010600; +static const unsigned int Revision = 1; +static const unsigned int OpCodeMask = 0xffff; +static const unsigned int WordCountShift = 16; + +enum SourceLanguage { + SourceLanguageUnknown = 0, + SourceLanguageESSL = 1, + SourceLanguageGLSL = 2, + SourceLanguageOpenCL_C = 3, + SourceLanguageOpenCL_CPP = 4, + SourceLanguageHLSL = 5, + SourceLanguageCPP_for_OpenCL = 6, + SourceLanguageSYCL = 7, + SourceLanguageHERO_C = 8, + SourceLanguageNZSL = 9, + SourceLanguageWGSL = 10, + SourceLanguageSlang = 11, + SourceLanguageZig = 12, + SourceLanguageRust = 13, + SourceLanguageMax = 0x7fffffff, +}; + +enum ExecutionModel { + ExecutionModelVertex = 0, + ExecutionModelTessellationControl = 1, + ExecutionModelTessellationEvaluation = 2, + ExecutionModelGeometry = 3, + ExecutionModelFragment = 4, + ExecutionModelGLCompute = 5, + ExecutionModelKernel = 6, + ExecutionModelTaskNV = 5267, + ExecutionModelMeshNV = 5268, + ExecutionModelRayGenerationKHR = 5313, + ExecutionModelRayGenerationNV = 5313, + ExecutionModelIntersectionKHR = 5314, + ExecutionModelIntersectionNV = 5314, + ExecutionModelAnyHitKHR = 5315, + ExecutionModelAnyHitNV = 5315, + ExecutionModelClosestHitKHR = 5316, + ExecutionModelClosestHitNV = 5316, + ExecutionModelMissKHR = 5317, + ExecutionModelMissNV = 5317, + ExecutionModelCallableKHR = 5318, + ExecutionModelCallableNV = 5318, + ExecutionModelTaskEXT = 5364, + ExecutionModelMeshEXT = 5365, + ExecutionModelMax = 0x7fffffff, +}; + +enum AddressingModel { + AddressingModelLogical = 0, + AddressingModelPhysical32 = 1, + AddressingModelPhysical64 = 2, + AddressingModelPhysicalStorageBuffer64 = 5348, + AddressingModelPhysicalStorageBuffer64EXT = 5348, + AddressingModelMax = 0x7fffffff, +}; + +enum MemoryModel { + MemoryModelSimple = 0, + MemoryModelGLSL450 = 1, + MemoryModelOpenCL = 2, + MemoryModelVulkan = 3, + MemoryModelVulkanKHR = 3, + MemoryModelMax = 0x7fffffff, +}; + +enum ExecutionMode { + ExecutionModeInvocations = 0, + ExecutionModeSpacingEqual = 1, + ExecutionModeSpacingFractionalEven = 2, + ExecutionModeSpacingFractionalOdd = 3, + ExecutionModeVertexOrderCw = 4, + ExecutionModeVertexOrderCcw = 5, + ExecutionModePixelCenterInteger = 6, + ExecutionModeOriginUpperLeft = 7, + ExecutionModeOriginLowerLeft = 8, + ExecutionModeEarlyFragmentTests = 9, + ExecutionModePointMode = 10, + ExecutionModeXfb = 11, + ExecutionModeDepthReplacing = 12, + ExecutionModeDepthGreater = 14, + ExecutionModeDepthLess = 15, + ExecutionModeDepthUnchanged = 16, + ExecutionModeLocalSize = 17, + ExecutionModeLocalSizeHint = 18, + ExecutionModeInputPoints = 19, + ExecutionModeInputLines = 20, + ExecutionModeInputLinesAdjacency = 21, + ExecutionModeTriangles = 22, + ExecutionModeInputTrianglesAdjacency = 23, + ExecutionModeQuads = 24, + ExecutionModeIsolines = 25, + ExecutionModeOutputVertices = 26, + ExecutionModeOutputPoints = 27, + ExecutionModeOutputLineStrip = 28, + ExecutionModeOutputTriangleStrip = 29, + ExecutionModeVecTypeHint = 30, + ExecutionModeContractionOff = 31, + ExecutionModeInitializer = 33, + ExecutionModeFinalizer = 34, + ExecutionModeSubgroupSize = 35, + ExecutionModeSubgroupsPerWorkgroup = 36, + ExecutionModeSubgroupsPerWorkgroupId = 37, + ExecutionModeLocalSizeId = 38, + ExecutionModeLocalSizeHintId = 39, + ExecutionModeNonCoherentColorAttachmentReadEXT = 4169, + ExecutionModeNonCoherentDepthAttachmentReadEXT = 4170, + ExecutionModeNonCoherentStencilAttachmentReadEXT = 4171, + ExecutionModeSubgroupUniformControlFlowKHR = 4421, + ExecutionModePostDepthCoverage = 4446, + ExecutionModeDenormPreserve = 4459, + ExecutionModeDenormFlushToZero = 4460, + ExecutionModeSignedZeroInfNanPreserve = 4461, + ExecutionModeRoundingModeRTE = 4462, + ExecutionModeRoundingModeRTZ = 4463, + ExecutionModeNonCoherentTileAttachmentReadQCOM = 4489, + ExecutionModeTileShadingRateQCOM = 4490, + ExecutionModeEarlyAndLateFragmentTestsAMD = 5017, + ExecutionModeStencilRefReplacingEXT = 5027, + ExecutionModeCoalescingAMDX = 5069, + ExecutionModeIsApiEntryAMDX = 5070, + ExecutionModeMaxNodeRecursionAMDX = 5071, + ExecutionModeStaticNumWorkgroupsAMDX = 5072, + ExecutionModeShaderIndexAMDX = 5073, + ExecutionModeMaxNumWorkgroupsAMDX = 5077, + ExecutionModeStencilRefUnchangedFrontAMD = 5079, + ExecutionModeStencilRefGreaterFrontAMD = 5080, + ExecutionModeStencilRefLessFrontAMD = 5081, + ExecutionModeStencilRefUnchangedBackAMD = 5082, + ExecutionModeStencilRefGreaterBackAMD = 5083, + ExecutionModeStencilRefLessBackAMD = 5084, + ExecutionModeQuadDerivativesKHR = 5088, + ExecutionModeRequireFullQuadsKHR = 5089, + ExecutionModeSharesInputWithAMDX = 5102, + ExecutionModeOutputLinesEXT = 5269, + ExecutionModeOutputLinesNV = 5269, + ExecutionModeOutputPrimitivesEXT = 5270, + ExecutionModeOutputPrimitivesNV = 5270, + ExecutionModeDerivativeGroupQuadsKHR = 5289, + ExecutionModeDerivativeGroupQuadsNV = 5289, + ExecutionModeDerivativeGroupLinearKHR = 5290, + ExecutionModeDerivativeGroupLinearNV = 5290, + ExecutionModeOutputTrianglesEXT = 5298, + ExecutionModeOutputTrianglesNV = 5298, + ExecutionModePixelInterlockOrderedEXT = 5366, + ExecutionModePixelInterlockUnorderedEXT = 5367, + ExecutionModeSampleInterlockOrderedEXT = 5368, + ExecutionModeSampleInterlockUnorderedEXT = 5369, + ExecutionModeShadingRateInterlockOrderedEXT = 5370, + ExecutionModeShadingRateInterlockUnorderedEXT = 5371, + ExecutionModeSharedLocalMemorySizeINTEL = 5618, + ExecutionModeRoundingModeRTPINTEL = 5620, + ExecutionModeRoundingModeRTNINTEL = 5621, + ExecutionModeFloatingPointModeALTINTEL = 5622, + ExecutionModeFloatingPointModeIEEEINTEL = 5623, + ExecutionModeMaxWorkgroupSizeINTEL = 5893, + ExecutionModeMaxWorkDimINTEL = 5894, + ExecutionModeNoGlobalOffsetINTEL = 5895, + ExecutionModeNumSIMDWorkitemsINTEL = 5896, + ExecutionModeSchedulerTargetFmaxMhzINTEL = 5903, + ExecutionModeMaximallyReconvergesKHR = 6023, + ExecutionModeFPFastMathDefault = 6028, + ExecutionModeStreamingInterfaceINTEL = 6154, + ExecutionModeRegisterMapInterfaceINTEL = 6160, + ExecutionModeNamedBarrierCountINTEL = 6417, + ExecutionModeMaximumRegistersINTEL = 6461, + ExecutionModeMaximumRegistersIdINTEL = 6462, + ExecutionModeNamedMaximumRegistersINTEL = 6463, + ExecutionModeMax = 0x7fffffff, +}; + +enum StorageClass { + StorageClassUniformConstant = 0, + StorageClassInput = 1, + StorageClassUniform = 2, + StorageClassOutput = 3, + StorageClassWorkgroup = 4, + StorageClassCrossWorkgroup = 5, + StorageClassPrivate = 6, + StorageClassFunction = 7, + StorageClassGeneric = 8, + StorageClassPushConstant = 9, + StorageClassAtomicCounter = 10, + StorageClassImage = 11, + StorageClassStorageBuffer = 12, + StorageClassTileImageEXT = 4172, + StorageClassTileAttachmentQCOM = 4491, + StorageClassNodePayloadAMDX = 5068, + StorageClassCallableDataKHR = 5328, + StorageClassCallableDataNV = 5328, + StorageClassIncomingCallableDataKHR = 5329, + StorageClassIncomingCallableDataNV = 5329, + StorageClassRayPayloadKHR = 5338, + StorageClassRayPayloadNV = 5338, + StorageClassHitAttributeKHR = 5339, + StorageClassHitAttributeNV = 5339, + StorageClassIncomingRayPayloadKHR = 5342, + StorageClassIncomingRayPayloadNV = 5342, + StorageClassShaderRecordBufferKHR = 5343, + StorageClassShaderRecordBufferNV = 5343, + StorageClassPhysicalStorageBuffer = 5349, + StorageClassPhysicalStorageBufferEXT = 5349, + StorageClassHitObjectAttributeNV = 5385, + StorageClassTaskPayloadWorkgroupEXT = 5402, + StorageClassCodeSectionINTEL = 5605, + StorageClassDeviceOnlyINTEL = 5936, + StorageClassHostOnlyINTEL = 5937, + StorageClassMax = 0x7fffffff, +}; + +enum Dim { + Dim1D = 0, + Dim2D = 1, + Dim3D = 2, + DimCube = 3, + DimRect = 4, + DimBuffer = 5, + DimSubpassData = 6, + DimTileImageDataEXT = 4173, + DimMax = 0x7fffffff, +}; + +enum SamplerAddressingMode { + SamplerAddressingModeNone = 0, + SamplerAddressingModeClampToEdge = 1, + SamplerAddressingModeClamp = 2, + SamplerAddressingModeRepeat = 3, + SamplerAddressingModeRepeatMirrored = 4, + SamplerAddressingModeMax = 0x7fffffff, +}; + +enum SamplerFilterMode { + SamplerFilterModeNearest = 0, + SamplerFilterModeLinear = 1, + SamplerFilterModeMax = 0x7fffffff, +}; + +enum ImageFormat { + ImageFormatUnknown = 0, + ImageFormatRgba32f = 1, + ImageFormatRgba16f = 2, + ImageFormatR32f = 3, + ImageFormatRgba8 = 4, + ImageFormatRgba8Snorm = 5, + ImageFormatRg32f = 6, + ImageFormatRg16f = 7, + ImageFormatR11fG11fB10f = 8, + ImageFormatR16f = 9, + ImageFormatRgba16 = 10, + ImageFormatRgb10A2 = 11, + ImageFormatRg16 = 12, + ImageFormatRg8 = 13, + ImageFormatR16 = 14, + ImageFormatR8 = 15, + ImageFormatRgba16Snorm = 16, + ImageFormatRg16Snorm = 17, + ImageFormatRg8Snorm = 18, + ImageFormatR16Snorm = 19, + ImageFormatR8Snorm = 20, + ImageFormatRgba32i = 21, + ImageFormatRgba16i = 22, + ImageFormatRgba8i = 23, + ImageFormatR32i = 24, + ImageFormatRg32i = 25, + ImageFormatRg16i = 26, + ImageFormatRg8i = 27, + ImageFormatR16i = 28, + ImageFormatR8i = 29, + ImageFormatRgba32ui = 30, + ImageFormatRgba16ui = 31, + ImageFormatRgba8ui = 32, + ImageFormatR32ui = 33, + ImageFormatRgb10a2ui = 34, + ImageFormatRg32ui = 35, + ImageFormatRg16ui = 36, + ImageFormatRg8ui = 37, + ImageFormatR16ui = 38, + ImageFormatR8ui = 39, + ImageFormatR64ui = 40, + ImageFormatR64i = 41, + ImageFormatMax = 0x7fffffff, +}; + +enum ImageChannelOrder { + ImageChannelOrderR = 0, + ImageChannelOrderA = 1, + ImageChannelOrderRG = 2, + ImageChannelOrderRA = 3, + ImageChannelOrderRGB = 4, + ImageChannelOrderRGBA = 5, + ImageChannelOrderBGRA = 6, + ImageChannelOrderARGB = 7, + ImageChannelOrderIntensity = 8, + ImageChannelOrderLuminance = 9, + ImageChannelOrderRx = 10, + ImageChannelOrderRGx = 11, + ImageChannelOrderRGBx = 12, + ImageChannelOrderDepth = 13, + ImageChannelOrderDepthStencil = 14, + ImageChannelOrdersRGB = 15, + ImageChannelOrdersRGBx = 16, + ImageChannelOrdersRGBA = 17, + ImageChannelOrdersBGRA = 18, + ImageChannelOrderABGR = 19, + ImageChannelOrderMax = 0x7fffffff, +}; + +enum ImageChannelDataType { + ImageChannelDataTypeSnormInt8 = 0, + ImageChannelDataTypeSnormInt16 = 1, + ImageChannelDataTypeUnormInt8 = 2, + ImageChannelDataTypeUnormInt16 = 3, + ImageChannelDataTypeUnormShort565 = 4, + ImageChannelDataTypeUnormShort555 = 5, + ImageChannelDataTypeUnormInt101010 = 6, + ImageChannelDataTypeSignedInt8 = 7, + ImageChannelDataTypeSignedInt16 = 8, + ImageChannelDataTypeSignedInt32 = 9, + ImageChannelDataTypeUnsignedInt8 = 10, + ImageChannelDataTypeUnsignedInt16 = 11, + ImageChannelDataTypeUnsignedInt32 = 12, + ImageChannelDataTypeHalfFloat = 13, + ImageChannelDataTypeFloat = 14, + ImageChannelDataTypeUnormInt24 = 15, + ImageChannelDataTypeUnormInt101010_2 = 16, + ImageChannelDataTypeUnormInt10X6EXT = 17, + ImageChannelDataTypeUnsignedIntRaw10EXT = 19, + ImageChannelDataTypeUnsignedIntRaw12EXT = 20, + ImageChannelDataTypeUnormInt2_101010EXT = 21, + ImageChannelDataTypeUnsignedInt10X6EXT = 22, + ImageChannelDataTypeUnsignedInt12X4EXT = 23, + ImageChannelDataTypeUnsignedInt14X2EXT = 24, + ImageChannelDataTypeUnormInt12X4EXT = 25, + ImageChannelDataTypeUnormInt14X2EXT = 26, + ImageChannelDataTypeMax = 0x7fffffff, +}; + +enum ImageOperandsShift { + ImageOperandsBiasShift = 0, + ImageOperandsLodShift = 1, + ImageOperandsGradShift = 2, + ImageOperandsConstOffsetShift = 3, + ImageOperandsOffsetShift = 4, + ImageOperandsConstOffsetsShift = 5, + ImageOperandsSampleShift = 6, + ImageOperandsMinLodShift = 7, + ImageOperandsMakeTexelAvailableShift = 8, + ImageOperandsMakeTexelAvailableKHRShift = 8, + ImageOperandsMakeTexelVisibleShift = 9, + ImageOperandsMakeTexelVisibleKHRShift = 9, + ImageOperandsNonPrivateTexelShift = 10, + ImageOperandsNonPrivateTexelKHRShift = 10, + ImageOperandsVolatileTexelShift = 11, + ImageOperandsVolatileTexelKHRShift = 11, + ImageOperandsSignExtendShift = 12, + ImageOperandsZeroExtendShift = 13, + ImageOperandsNontemporalShift = 14, + ImageOperandsOffsetsShift = 16, + ImageOperandsMax = 0x7fffffff, +}; + +enum ImageOperandsMask { + ImageOperandsMaskNone = 0, + ImageOperandsBiasMask = 0x00000001, + ImageOperandsLodMask = 0x00000002, + ImageOperandsGradMask = 0x00000004, + ImageOperandsConstOffsetMask = 0x00000008, + ImageOperandsOffsetMask = 0x00000010, + ImageOperandsConstOffsetsMask = 0x00000020, + ImageOperandsSampleMask = 0x00000040, + ImageOperandsMinLodMask = 0x00000080, + ImageOperandsMakeTexelAvailableMask = 0x00000100, + ImageOperandsMakeTexelAvailableKHRMask = 0x00000100, + ImageOperandsMakeTexelVisibleMask = 0x00000200, + ImageOperandsMakeTexelVisibleKHRMask = 0x00000200, + ImageOperandsNonPrivateTexelMask = 0x00000400, + ImageOperandsNonPrivateTexelKHRMask = 0x00000400, + ImageOperandsVolatileTexelMask = 0x00000800, + ImageOperandsVolatileTexelKHRMask = 0x00000800, + ImageOperandsSignExtendMask = 0x00001000, + ImageOperandsZeroExtendMask = 0x00002000, + ImageOperandsNontemporalMask = 0x00004000, + ImageOperandsOffsetsMask = 0x00010000, +}; + +enum FPFastMathModeShift { + FPFastMathModeNotNaNShift = 0, + FPFastMathModeNotInfShift = 1, + FPFastMathModeNSZShift = 2, + FPFastMathModeAllowRecipShift = 3, + FPFastMathModeFastShift = 4, + FPFastMathModeAllowContractShift = 16, + FPFastMathModeAllowContractFastINTELShift = 16, + FPFastMathModeAllowReassocShift = 17, + FPFastMathModeAllowReassocINTELShift = 17, + FPFastMathModeAllowTransformShift = 18, + FPFastMathModeMax = 0x7fffffff, +}; + +enum FPFastMathModeMask { + FPFastMathModeMaskNone = 0, + FPFastMathModeNotNaNMask = 0x00000001, + FPFastMathModeNotInfMask = 0x00000002, + FPFastMathModeNSZMask = 0x00000004, + FPFastMathModeAllowRecipMask = 0x00000008, + FPFastMathModeFastMask = 0x00000010, + FPFastMathModeAllowContractMask = 0x00010000, + FPFastMathModeAllowContractFastINTELMask = 0x00010000, + FPFastMathModeAllowReassocMask = 0x00020000, + FPFastMathModeAllowReassocINTELMask = 0x00020000, + FPFastMathModeAllowTransformMask = 0x00040000, +}; + +enum FPRoundingMode { + FPRoundingModeRTE = 0, + FPRoundingModeRTZ = 1, + FPRoundingModeRTP = 2, + FPRoundingModeRTN = 3, + FPRoundingModeMax = 0x7fffffff, +}; + +enum LinkageType { + LinkageTypeExport = 0, + LinkageTypeImport = 1, + LinkageTypeLinkOnceODR = 2, + LinkageTypeMax = 0x7fffffff, +}; + +enum AccessQualifier { + AccessQualifierReadOnly = 0, + AccessQualifierWriteOnly = 1, + AccessQualifierReadWrite = 2, + AccessQualifierMax = 0x7fffffff, +}; + +enum FunctionParameterAttribute { + FunctionParameterAttributeZext = 0, + FunctionParameterAttributeSext = 1, + FunctionParameterAttributeByVal = 2, + FunctionParameterAttributeSret = 3, + FunctionParameterAttributeNoAlias = 4, + FunctionParameterAttributeNoCapture = 5, + FunctionParameterAttributeNoWrite = 6, + FunctionParameterAttributeNoReadWrite = 7, + FunctionParameterAttributeRuntimeAlignedINTEL = 5940, + FunctionParameterAttributeMax = 0x7fffffff, +}; + +enum Decoration { + DecorationRelaxedPrecision = 0, + DecorationSpecId = 1, + DecorationBlock = 2, + DecorationBufferBlock = 3, + DecorationRowMajor = 4, + DecorationColMajor = 5, + DecorationArrayStride = 6, + DecorationMatrixStride = 7, + DecorationGLSLShared = 8, + DecorationGLSLPacked = 9, + DecorationCPacked = 10, + DecorationBuiltIn = 11, + DecorationNoPerspective = 13, + DecorationFlat = 14, + DecorationPatch = 15, + DecorationCentroid = 16, + DecorationSample = 17, + DecorationInvariant = 18, + DecorationRestrict = 19, + DecorationAliased = 20, + DecorationVolatile = 21, + DecorationConstant = 22, + DecorationCoherent = 23, + DecorationNonWritable = 24, + DecorationNonReadable = 25, + DecorationUniform = 26, + DecorationUniformId = 27, + DecorationSaturatedConversion = 28, + DecorationStream = 29, + DecorationLocation = 30, + DecorationComponent = 31, + DecorationIndex = 32, + DecorationBinding = 33, + DecorationDescriptorSet = 34, + DecorationOffset = 35, + DecorationXfbBuffer = 36, + DecorationXfbStride = 37, + DecorationFuncParamAttr = 38, + DecorationFPRoundingMode = 39, + DecorationFPFastMathMode = 40, + DecorationLinkageAttributes = 41, + DecorationNoContraction = 42, + DecorationInputAttachmentIndex = 43, + DecorationAlignment = 44, + DecorationMaxByteOffset = 45, + DecorationAlignmentId = 46, + DecorationMaxByteOffsetId = 47, + DecorationNoSignedWrap = 4469, + DecorationNoUnsignedWrap = 4470, + DecorationWeightTextureQCOM = 4487, + DecorationBlockMatchTextureQCOM = 4488, + DecorationBlockMatchSamplerQCOM = 4499, + DecorationExplicitInterpAMD = 4999, + DecorationNodeSharesPayloadLimitsWithAMDX = 5019, + DecorationNodeMaxPayloadsAMDX = 5020, + DecorationTrackFinishWritingAMDX = 5078, + DecorationPayloadNodeNameAMDX = 5091, + DecorationPayloadNodeBaseIndexAMDX = 5098, + DecorationPayloadNodeSparseArrayAMDX = 5099, + DecorationPayloadNodeArraySizeAMDX = 5100, + DecorationPayloadDispatchIndirectAMDX = 5105, + DecorationOverrideCoverageNV = 5248, + DecorationPassthroughNV = 5250, + DecorationViewportRelativeNV = 5252, + DecorationSecondaryViewportRelativeNV = 5256, + DecorationPerPrimitiveEXT = 5271, + DecorationPerPrimitiveNV = 5271, + DecorationPerViewNV = 5272, + DecorationPerTaskNV = 5273, + DecorationPerVertexKHR = 5285, + DecorationPerVertexNV = 5285, + DecorationNonUniform = 5300, + DecorationNonUniformEXT = 5300, + DecorationRestrictPointer = 5355, + DecorationRestrictPointerEXT = 5355, + DecorationAliasedPointer = 5356, + DecorationAliasedPointerEXT = 5356, + DecorationHitObjectShaderRecordBufferNV = 5386, + DecorationBindlessSamplerNV = 5398, + DecorationBindlessImageNV = 5399, + DecorationBoundSamplerNV = 5400, + DecorationBoundImageNV = 5401, + DecorationSIMTCallINTEL = 5599, + DecorationReferencedIndirectlyINTEL = 5602, + DecorationClobberINTEL = 5607, + DecorationSideEffectsINTEL = 5608, + DecorationVectorComputeVariableINTEL = 5624, + DecorationFuncParamIOKindINTEL = 5625, + DecorationVectorComputeFunctionINTEL = 5626, + DecorationStackCallINTEL = 5627, + DecorationGlobalVariableOffsetINTEL = 5628, + DecorationCounterBuffer = 5634, + DecorationHlslCounterBufferGOOGLE = 5634, + DecorationHlslSemanticGOOGLE = 5635, + DecorationUserSemantic = 5635, + DecorationUserTypeGOOGLE = 5636, + DecorationFunctionRoundingModeINTEL = 5822, + DecorationFunctionDenormModeINTEL = 5823, + DecorationRegisterINTEL = 5825, + DecorationMemoryINTEL = 5826, + DecorationNumbanksINTEL = 5827, + DecorationBankwidthINTEL = 5828, + DecorationMaxPrivateCopiesINTEL = 5829, + DecorationSinglepumpINTEL = 5830, + DecorationDoublepumpINTEL = 5831, + DecorationMaxReplicatesINTEL = 5832, + DecorationSimpleDualPortINTEL = 5833, + DecorationMergeINTEL = 5834, + DecorationBankBitsINTEL = 5835, + DecorationForcePow2DepthINTEL = 5836, + DecorationStridesizeINTEL = 5883, + DecorationWordsizeINTEL = 5884, + DecorationTrueDualPortINTEL = 5885, + DecorationBurstCoalesceINTEL = 5899, + DecorationCacheSizeINTEL = 5900, + DecorationDontStaticallyCoalesceINTEL = 5901, + DecorationPrefetchINTEL = 5902, + DecorationStallEnableINTEL = 5905, + DecorationFuseLoopsInFunctionINTEL = 5907, + DecorationMathOpDSPModeINTEL = 5909, + DecorationAliasScopeINTEL = 5914, + DecorationNoAliasINTEL = 5915, + DecorationInitiationIntervalINTEL = 5917, + DecorationMaxConcurrencyINTEL = 5918, + DecorationPipelineEnableINTEL = 5919, + DecorationBufferLocationINTEL = 5921, + DecorationIOPipeStorageINTEL = 5944, + DecorationFunctionFloatingPointModeINTEL = 6080, + DecorationSingleElementVectorINTEL = 6085, + DecorationVectorComputeCallableFunctionINTEL = 6087, + DecorationMediaBlockIOINTEL = 6140, + DecorationStallFreeINTEL = 6151, + DecorationFPMaxErrorDecorationINTEL = 6170, + DecorationLatencyControlLabelINTEL = 6172, + DecorationLatencyControlConstraintINTEL = 6173, + DecorationConduitKernelArgumentINTEL = 6175, + DecorationRegisterMapKernelArgumentINTEL = 6176, + DecorationMMHostInterfaceAddressWidthINTEL = 6177, + DecorationMMHostInterfaceDataWidthINTEL = 6178, + DecorationMMHostInterfaceLatencyINTEL = 6179, + DecorationMMHostInterfaceReadWriteModeINTEL = 6180, + DecorationMMHostInterfaceMaxBurstINTEL = 6181, + DecorationMMHostInterfaceWaitRequestINTEL = 6182, + DecorationStableKernelArgumentINTEL = 6183, + DecorationHostAccessINTEL = 6188, + DecorationInitModeINTEL = 6190, + DecorationImplementInRegisterMapINTEL = 6191, + DecorationCacheControlLoadINTEL = 6442, + DecorationCacheControlStoreINTEL = 6443, + DecorationMax = 0x7fffffff, +}; + +enum BuiltIn { + BuiltInPosition = 0, + BuiltInPointSize = 1, + BuiltInClipDistance = 3, + BuiltInCullDistance = 4, + BuiltInVertexId = 5, + BuiltInInstanceId = 6, + BuiltInPrimitiveId = 7, + BuiltInInvocationId = 8, + BuiltInLayer = 9, + BuiltInViewportIndex = 10, + BuiltInTessLevelOuter = 11, + BuiltInTessLevelInner = 12, + BuiltInTessCoord = 13, + BuiltInPatchVertices = 14, + BuiltInFragCoord = 15, + BuiltInPointCoord = 16, + BuiltInFrontFacing = 17, + BuiltInSampleId = 18, + BuiltInSamplePosition = 19, + BuiltInSampleMask = 20, + BuiltInFragDepth = 22, + BuiltInHelperInvocation = 23, + BuiltInNumWorkgroups = 24, + BuiltInWorkgroupSize = 25, + BuiltInWorkgroupId = 26, + BuiltInLocalInvocationId = 27, + BuiltInGlobalInvocationId = 28, + BuiltInLocalInvocationIndex = 29, + BuiltInWorkDim = 30, + BuiltInGlobalSize = 31, + BuiltInEnqueuedWorkgroupSize = 32, + BuiltInGlobalOffset = 33, + BuiltInGlobalLinearId = 34, + BuiltInSubgroupSize = 36, + BuiltInSubgroupMaxSize = 37, + BuiltInNumSubgroups = 38, + BuiltInNumEnqueuedSubgroups = 39, + BuiltInSubgroupId = 40, + BuiltInSubgroupLocalInvocationId = 41, + BuiltInVertexIndex = 42, + BuiltInInstanceIndex = 43, + BuiltInCoreIDARM = 4160, + BuiltInCoreCountARM = 4161, + BuiltInCoreMaxIDARM = 4162, + BuiltInWarpIDARM = 4163, + BuiltInWarpMaxIDARM = 4164, + BuiltInSubgroupEqMask = 4416, + BuiltInSubgroupEqMaskKHR = 4416, + BuiltInSubgroupGeMask = 4417, + BuiltInSubgroupGeMaskKHR = 4417, + BuiltInSubgroupGtMask = 4418, + BuiltInSubgroupGtMaskKHR = 4418, + BuiltInSubgroupLeMask = 4419, + BuiltInSubgroupLeMaskKHR = 4419, + BuiltInSubgroupLtMask = 4420, + BuiltInSubgroupLtMaskKHR = 4420, + BuiltInBaseVertex = 4424, + BuiltInBaseInstance = 4425, + BuiltInDrawIndex = 4426, + BuiltInPrimitiveShadingRateKHR = 4432, + BuiltInDeviceIndex = 4438, + BuiltInViewIndex = 4440, + BuiltInShadingRateKHR = 4444, + BuiltInTileOffsetQCOM = 4492, + BuiltInTileDimensionQCOM = 4493, + BuiltInTileApronSizeQCOM = 4494, + BuiltInBaryCoordNoPerspAMD = 4992, + BuiltInBaryCoordNoPerspCentroidAMD = 4993, + BuiltInBaryCoordNoPerspSampleAMD = 4994, + BuiltInBaryCoordSmoothAMD = 4995, + BuiltInBaryCoordSmoothCentroidAMD = 4996, + BuiltInBaryCoordSmoothSampleAMD = 4997, + BuiltInBaryCoordPullModelAMD = 4998, + BuiltInFragStencilRefEXT = 5014, + BuiltInRemainingRecursionLevelsAMDX = 5021, + BuiltInShaderIndexAMDX = 5073, + BuiltInViewportMaskNV = 5253, + BuiltInSecondaryPositionNV = 5257, + BuiltInSecondaryViewportMaskNV = 5258, + BuiltInPositionPerViewNV = 5261, + BuiltInViewportMaskPerViewNV = 5262, + BuiltInFullyCoveredEXT = 5264, + BuiltInTaskCountNV = 5274, + BuiltInPrimitiveCountNV = 5275, + BuiltInPrimitiveIndicesNV = 5276, + BuiltInClipDistancePerViewNV = 5277, + BuiltInCullDistancePerViewNV = 5278, + BuiltInLayerPerViewNV = 5279, + BuiltInMeshViewCountNV = 5280, + BuiltInMeshViewIndicesNV = 5281, + BuiltInBaryCoordKHR = 5286, + BuiltInBaryCoordNV = 5286, + BuiltInBaryCoordNoPerspKHR = 5287, + BuiltInBaryCoordNoPerspNV = 5287, + BuiltInFragSizeEXT = 5292, + BuiltInFragmentSizeNV = 5292, + BuiltInFragInvocationCountEXT = 5293, + BuiltInInvocationsPerPixelNV = 5293, + BuiltInPrimitivePointIndicesEXT = 5294, + BuiltInPrimitiveLineIndicesEXT = 5295, + BuiltInPrimitiveTriangleIndicesEXT = 5296, + BuiltInCullPrimitiveEXT = 5299, + BuiltInLaunchIdKHR = 5319, + BuiltInLaunchIdNV = 5319, + BuiltInLaunchSizeKHR = 5320, + BuiltInLaunchSizeNV = 5320, + BuiltInWorldRayOriginKHR = 5321, + BuiltInWorldRayOriginNV = 5321, + BuiltInWorldRayDirectionKHR = 5322, + BuiltInWorldRayDirectionNV = 5322, + BuiltInObjectRayOriginKHR = 5323, + BuiltInObjectRayOriginNV = 5323, + BuiltInObjectRayDirectionKHR = 5324, + BuiltInObjectRayDirectionNV = 5324, + BuiltInRayTminKHR = 5325, + BuiltInRayTminNV = 5325, + BuiltInRayTmaxKHR = 5326, + BuiltInRayTmaxNV = 5326, + BuiltInInstanceCustomIndexKHR = 5327, + BuiltInInstanceCustomIndexNV = 5327, + BuiltInObjectToWorldKHR = 5330, + BuiltInObjectToWorldNV = 5330, + BuiltInWorldToObjectKHR = 5331, + BuiltInWorldToObjectNV = 5331, + BuiltInHitTNV = 5332, + BuiltInHitKindKHR = 5333, + BuiltInHitKindNV = 5333, + BuiltInCurrentRayTimeNV = 5334, + BuiltInHitTriangleVertexPositionsKHR = 5335, + BuiltInHitMicroTriangleVertexPositionsNV = 5337, + BuiltInHitMicroTriangleVertexBarycentricsNV = 5344, + BuiltInIncomingRayFlagsKHR = 5351, + BuiltInIncomingRayFlagsNV = 5351, + BuiltInRayGeometryIndexKHR = 5352, + BuiltInHitIsSphereNV = 5359, + BuiltInHitIsLSSNV = 5360, + BuiltInHitSpherePositionNV = 5361, + BuiltInWarpsPerSMNV = 5374, + BuiltInSMCountNV = 5375, + BuiltInWarpIDNV = 5376, + BuiltInSMIDNV = 5377, + BuiltInHitLSSPositionsNV = 5396, + BuiltInHitKindFrontFacingMicroTriangleNV = 5405, + BuiltInHitKindBackFacingMicroTriangleNV = 5406, + BuiltInHitSphereRadiusNV = 5420, + BuiltInHitLSSRadiiNV = 5421, + BuiltInClusterIDNV = 5436, + BuiltInCullMaskKHR = 6021, + BuiltInMax = 0x7fffffff, +}; + +enum SelectionControlShift { + SelectionControlFlattenShift = 0, + SelectionControlDontFlattenShift = 1, + SelectionControlMax = 0x7fffffff, +}; + +enum SelectionControlMask { + SelectionControlMaskNone = 0, + SelectionControlFlattenMask = 0x00000001, + SelectionControlDontFlattenMask = 0x00000002, +}; + +enum LoopControlShift { + LoopControlUnrollShift = 0, + LoopControlDontUnrollShift = 1, + LoopControlDependencyInfiniteShift = 2, + LoopControlDependencyLengthShift = 3, + LoopControlMinIterationsShift = 4, + LoopControlMaxIterationsShift = 5, + LoopControlIterationMultipleShift = 6, + LoopControlPeelCountShift = 7, + LoopControlPartialCountShift = 8, + LoopControlInitiationIntervalINTELShift = 16, + LoopControlMaxConcurrencyINTELShift = 17, + LoopControlDependencyArrayINTELShift = 18, + LoopControlPipelineEnableINTELShift = 19, + LoopControlLoopCoalesceINTELShift = 20, + LoopControlMaxInterleavingINTELShift = 21, + LoopControlSpeculatedIterationsINTELShift = 22, + LoopControlNoFusionINTELShift = 23, + LoopControlLoopCountINTELShift = 24, + LoopControlMaxReinvocationDelayINTELShift = 25, + LoopControlMax = 0x7fffffff, +}; + +enum LoopControlMask { + LoopControlMaskNone = 0, + LoopControlUnrollMask = 0x00000001, + LoopControlDontUnrollMask = 0x00000002, + LoopControlDependencyInfiniteMask = 0x00000004, + LoopControlDependencyLengthMask = 0x00000008, + LoopControlMinIterationsMask = 0x00000010, + LoopControlMaxIterationsMask = 0x00000020, + LoopControlIterationMultipleMask = 0x00000040, + LoopControlPeelCountMask = 0x00000080, + LoopControlPartialCountMask = 0x00000100, + LoopControlInitiationIntervalINTELMask = 0x00010000, + LoopControlMaxConcurrencyINTELMask = 0x00020000, + LoopControlDependencyArrayINTELMask = 0x00040000, + LoopControlPipelineEnableINTELMask = 0x00080000, + LoopControlLoopCoalesceINTELMask = 0x00100000, + LoopControlMaxInterleavingINTELMask = 0x00200000, + LoopControlSpeculatedIterationsINTELMask = 0x00400000, + LoopControlNoFusionINTELMask = 0x00800000, + LoopControlLoopCountINTELMask = 0x01000000, + LoopControlMaxReinvocationDelayINTELMask = 0x02000000, +}; + +enum FunctionControlShift { + FunctionControlInlineShift = 0, + FunctionControlDontInlineShift = 1, + FunctionControlPureShift = 2, + FunctionControlConstShift = 3, + FunctionControlOptNoneEXTShift = 16, + FunctionControlOptNoneINTELShift = 16, + FunctionControlMax = 0x7fffffff, +}; + +enum FunctionControlMask { + FunctionControlMaskNone = 0, + FunctionControlInlineMask = 0x00000001, + FunctionControlDontInlineMask = 0x00000002, + FunctionControlPureMask = 0x00000004, + FunctionControlConstMask = 0x00000008, + FunctionControlOptNoneEXTMask = 0x00010000, + FunctionControlOptNoneINTELMask = 0x00010000, +}; + +enum MemorySemanticsShift { + MemorySemanticsAcquireShift = 1, + MemorySemanticsReleaseShift = 2, + MemorySemanticsAcquireReleaseShift = 3, + MemorySemanticsSequentiallyConsistentShift = 4, + MemorySemanticsUniformMemoryShift = 6, + MemorySemanticsSubgroupMemoryShift = 7, + MemorySemanticsWorkgroupMemoryShift = 8, + MemorySemanticsCrossWorkgroupMemoryShift = 9, + MemorySemanticsAtomicCounterMemoryShift = 10, + MemorySemanticsImageMemoryShift = 11, + MemorySemanticsOutputMemoryShift = 12, + MemorySemanticsOutputMemoryKHRShift = 12, + MemorySemanticsMakeAvailableShift = 13, + MemorySemanticsMakeAvailableKHRShift = 13, + MemorySemanticsMakeVisibleShift = 14, + MemorySemanticsMakeVisibleKHRShift = 14, + MemorySemanticsVolatileShift = 15, + MemorySemanticsMax = 0x7fffffff, +}; + +enum MemorySemanticsMask { + MemorySemanticsMaskNone = 0, + MemorySemanticsAcquireMask = 0x00000002, + MemorySemanticsReleaseMask = 0x00000004, + MemorySemanticsAcquireReleaseMask = 0x00000008, + MemorySemanticsSequentiallyConsistentMask = 0x00000010, + MemorySemanticsUniformMemoryMask = 0x00000040, + MemorySemanticsSubgroupMemoryMask = 0x00000080, + MemorySemanticsWorkgroupMemoryMask = 0x00000100, + MemorySemanticsCrossWorkgroupMemoryMask = 0x00000200, + MemorySemanticsAtomicCounterMemoryMask = 0x00000400, + MemorySemanticsImageMemoryMask = 0x00000800, + MemorySemanticsOutputMemoryMask = 0x00001000, + MemorySemanticsOutputMemoryKHRMask = 0x00001000, + MemorySemanticsMakeAvailableMask = 0x00002000, + MemorySemanticsMakeAvailableKHRMask = 0x00002000, + MemorySemanticsMakeVisibleMask = 0x00004000, + MemorySemanticsMakeVisibleKHRMask = 0x00004000, + MemorySemanticsVolatileMask = 0x00008000, +}; + +enum MemoryAccessShift { + MemoryAccessVolatileShift = 0, + MemoryAccessAlignedShift = 1, + MemoryAccessNontemporalShift = 2, + MemoryAccessMakePointerAvailableShift = 3, + MemoryAccessMakePointerAvailableKHRShift = 3, + MemoryAccessMakePointerVisibleShift = 4, + MemoryAccessMakePointerVisibleKHRShift = 4, + MemoryAccessNonPrivatePointerShift = 5, + MemoryAccessNonPrivatePointerKHRShift = 5, + MemoryAccessAliasScopeINTELMaskShift = 16, + MemoryAccessNoAliasINTELMaskShift = 17, + MemoryAccessMax = 0x7fffffff, +}; + +enum MemoryAccessMask { + MemoryAccessMaskNone = 0, + MemoryAccessVolatileMask = 0x00000001, + MemoryAccessAlignedMask = 0x00000002, + MemoryAccessNontemporalMask = 0x00000004, + MemoryAccessMakePointerAvailableMask = 0x00000008, + MemoryAccessMakePointerAvailableKHRMask = 0x00000008, + MemoryAccessMakePointerVisibleMask = 0x00000010, + MemoryAccessMakePointerVisibleKHRMask = 0x00000010, + MemoryAccessNonPrivatePointerMask = 0x00000020, + MemoryAccessNonPrivatePointerKHRMask = 0x00000020, + MemoryAccessAliasScopeINTELMaskMask = 0x00010000, + MemoryAccessNoAliasINTELMaskMask = 0x00020000, +}; + +enum Scope { + ScopeCrossDevice = 0, + ScopeDevice = 1, + ScopeWorkgroup = 2, + ScopeSubgroup = 3, + ScopeInvocation = 4, + ScopeQueueFamily = 5, + ScopeQueueFamilyKHR = 5, + ScopeShaderCallKHR = 6, + ScopeMax = 0x7fffffff, +}; + +enum GroupOperation { + GroupOperationReduce = 0, + GroupOperationInclusiveScan = 1, + GroupOperationExclusiveScan = 2, + GroupOperationClusteredReduce = 3, + GroupOperationPartitionedReduceNV = 6, + GroupOperationPartitionedInclusiveScanNV = 7, + GroupOperationPartitionedExclusiveScanNV = 8, + GroupOperationMax = 0x7fffffff, +}; + +enum KernelEnqueueFlags { + KernelEnqueueFlagsNoWait = 0, + KernelEnqueueFlagsWaitKernel = 1, + KernelEnqueueFlagsWaitWorkGroup = 2, + KernelEnqueueFlagsMax = 0x7fffffff, +}; + +enum KernelProfilingInfoShift { + KernelProfilingInfoCmdExecTimeShift = 0, + KernelProfilingInfoMax = 0x7fffffff, +}; + +enum KernelProfilingInfoMask { + KernelProfilingInfoMaskNone = 0, + KernelProfilingInfoCmdExecTimeMask = 0x00000001, +}; + +enum Capability { + CapabilityMatrix = 0, + CapabilityShader = 1, + CapabilityGeometry = 2, + CapabilityTessellation = 3, + CapabilityAddresses = 4, + CapabilityLinkage = 5, + CapabilityKernel = 6, + CapabilityVector16 = 7, + CapabilityFloat16Buffer = 8, + CapabilityFloat16 = 9, + CapabilityFloat64 = 10, + CapabilityInt64 = 11, + CapabilityInt64Atomics = 12, + CapabilityImageBasic = 13, + CapabilityImageReadWrite = 14, + CapabilityImageMipmap = 15, + CapabilityPipes = 17, + CapabilityGroups = 18, + CapabilityDeviceEnqueue = 19, + CapabilityLiteralSampler = 20, + CapabilityAtomicStorage = 21, + CapabilityInt16 = 22, + CapabilityTessellationPointSize = 23, + CapabilityGeometryPointSize = 24, + CapabilityImageGatherExtended = 25, + CapabilityStorageImageMultisample = 27, + CapabilityUniformBufferArrayDynamicIndexing = 28, + CapabilitySampledImageArrayDynamicIndexing = 29, + CapabilityStorageBufferArrayDynamicIndexing = 30, + CapabilityStorageImageArrayDynamicIndexing = 31, + CapabilityClipDistance = 32, + CapabilityCullDistance = 33, + CapabilityImageCubeArray = 34, + CapabilitySampleRateShading = 35, + CapabilityImageRect = 36, + CapabilitySampledRect = 37, + CapabilityGenericPointer = 38, + CapabilityInt8 = 39, + CapabilityInputAttachment = 40, + CapabilitySparseResidency = 41, + CapabilityMinLod = 42, + CapabilitySampled1D = 43, + CapabilityImage1D = 44, + CapabilitySampledCubeArray = 45, + CapabilitySampledBuffer = 46, + CapabilityImageBuffer = 47, + CapabilityImageMSArray = 48, + CapabilityStorageImageExtendedFormats = 49, + CapabilityImageQuery = 50, + CapabilityDerivativeControl = 51, + CapabilityInterpolationFunction = 52, + CapabilityTransformFeedback = 53, + CapabilityGeometryStreams = 54, + CapabilityStorageImageReadWithoutFormat = 55, + CapabilityStorageImageWriteWithoutFormat = 56, + CapabilityMultiViewport = 57, + CapabilitySubgroupDispatch = 58, + CapabilityNamedBarrier = 59, + CapabilityPipeStorage = 60, + CapabilityGroupNonUniform = 61, + CapabilityGroupNonUniformVote = 62, + CapabilityGroupNonUniformArithmetic = 63, + CapabilityGroupNonUniformBallot = 64, + CapabilityGroupNonUniformShuffle = 65, + CapabilityGroupNonUniformShuffleRelative = 66, + CapabilityGroupNonUniformClustered = 67, + CapabilityGroupNonUniformQuad = 68, + CapabilityShaderLayer = 69, + CapabilityShaderViewportIndex = 70, + CapabilityUniformDecoration = 71, + CapabilityCoreBuiltinsARM = 4165, + CapabilityTileImageColorReadAccessEXT = 4166, + CapabilityTileImageDepthReadAccessEXT = 4167, + CapabilityTileImageStencilReadAccessEXT = 4168, + CapabilityCooperativeMatrixLayoutsARM = 4201, + CapabilityFragmentShadingRateKHR = 4422, + CapabilitySubgroupBallotKHR = 4423, + CapabilityDrawParameters = 4427, + CapabilityWorkgroupMemoryExplicitLayoutKHR = 4428, + CapabilityWorkgroupMemoryExplicitLayout8BitAccessKHR = 4429, + CapabilityWorkgroupMemoryExplicitLayout16BitAccessKHR = 4430, + CapabilitySubgroupVoteKHR = 4431, + CapabilityStorageBuffer16BitAccess = 4433, + CapabilityStorageUniformBufferBlock16 = 4433, + CapabilityStorageUniform16 = 4434, + CapabilityUniformAndStorageBuffer16BitAccess = 4434, + CapabilityStoragePushConstant16 = 4435, + CapabilityStorageInputOutput16 = 4436, + CapabilityDeviceGroup = 4437, + CapabilityMultiView = 4439, + CapabilityVariablePointersStorageBuffer = 4441, + CapabilityVariablePointers = 4442, + CapabilityAtomicStorageOps = 4445, + CapabilitySampleMaskPostDepthCoverage = 4447, + CapabilityStorageBuffer8BitAccess = 4448, + CapabilityUniformAndStorageBuffer8BitAccess = 4449, + CapabilityStoragePushConstant8 = 4450, + CapabilityDenormPreserve = 4464, + CapabilityDenormFlushToZero = 4465, + CapabilitySignedZeroInfNanPreserve = 4466, + CapabilityRoundingModeRTE = 4467, + CapabilityRoundingModeRTZ = 4468, + CapabilityRayQueryProvisionalKHR = 4471, + CapabilityRayQueryKHR = 4472, + CapabilityUntypedPointersKHR = 4473, + CapabilityRayTraversalPrimitiveCullingKHR = 4478, + CapabilityRayTracingKHR = 4479, + CapabilityTextureSampleWeightedQCOM = 4484, + CapabilityTextureBoxFilterQCOM = 4485, + CapabilityTextureBlockMatchQCOM = 4486, + CapabilityTileShadingQCOM = 4495, + CapabilityTextureBlockMatch2QCOM = 4498, + CapabilityFloat16ImageAMD = 5008, + CapabilityImageGatherBiasLodAMD = 5009, + CapabilityFragmentMaskAMD = 5010, + CapabilityStencilExportEXT = 5013, + CapabilityImageReadWriteLodAMD = 5015, + CapabilityInt64ImageEXT = 5016, + CapabilityShaderClockKHR = 5055, + CapabilityShaderEnqueueAMDX = 5067, + CapabilityQuadControlKHR = 5087, + CapabilityBFloat16TypeKHR = 5116, + CapabilityBFloat16DotProductKHR = 5117, + CapabilityBFloat16CooperativeMatrixKHR = 5118, + CapabilitySampleMaskOverrideCoverageNV = 5249, + CapabilityGeometryShaderPassthroughNV = 5251, + CapabilityShaderViewportIndexLayerEXT = 5254, + CapabilityShaderViewportIndexLayerNV = 5254, + CapabilityShaderViewportMaskNV = 5255, + CapabilityShaderStereoViewNV = 5259, + CapabilityPerViewAttributesNV = 5260, + CapabilityFragmentFullyCoveredEXT = 5265, + CapabilityMeshShadingNV = 5266, + CapabilityImageFootprintNV = 5282, + CapabilityMeshShadingEXT = 5283, + CapabilityFragmentBarycentricKHR = 5284, + CapabilityFragmentBarycentricNV = 5284, + CapabilityComputeDerivativeGroupQuadsKHR = 5288, + CapabilityComputeDerivativeGroupQuadsNV = 5288, + CapabilityFragmentDensityEXT = 5291, + CapabilityShadingRateNV = 5291, + CapabilityGroupNonUniformPartitionedNV = 5297, + CapabilityShaderNonUniform = 5301, + CapabilityShaderNonUniformEXT = 5301, + CapabilityRuntimeDescriptorArray = 5302, + CapabilityRuntimeDescriptorArrayEXT = 5302, + CapabilityInputAttachmentArrayDynamicIndexing = 5303, + CapabilityInputAttachmentArrayDynamicIndexingEXT = 5303, + CapabilityUniformTexelBufferArrayDynamicIndexing = 5304, + CapabilityUniformTexelBufferArrayDynamicIndexingEXT = 5304, + CapabilityStorageTexelBufferArrayDynamicIndexing = 5305, + CapabilityStorageTexelBufferArrayDynamicIndexingEXT = 5305, + CapabilityUniformBufferArrayNonUniformIndexing = 5306, + CapabilityUniformBufferArrayNonUniformIndexingEXT = 5306, + CapabilitySampledImageArrayNonUniformIndexing = 5307, + CapabilitySampledImageArrayNonUniformIndexingEXT = 5307, + CapabilityStorageBufferArrayNonUniformIndexing = 5308, + CapabilityStorageBufferArrayNonUniformIndexingEXT = 5308, + CapabilityStorageImageArrayNonUniformIndexing = 5309, + CapabilityStorageImageArrayNonUniformIndexingEXT = 5309, + CapabilityInputAttachmentArrayNonUniformIndexing = 5310, + CapabilityInputAttachmentArrayNonUniformIndexingEXT = 5310, + CapabilityUniformTexelBufferArrayNonUniformIndexing = 5311, + CapabilityUniformTexelBufferArrayNonUniformIndexingEXT = 5311, + CapabilityStorageTexelBufferArrayNonUniformIndexing = 5312, + CapabilityStorageTexelBufferArrayNonUniformIndexingEXT = 5312, + CapabilityRayTracingPositionFetchKHR = 5336, + CapabilityRayTracingNV = 5340, + CapabilityRayTracingMotionBlurNV = 5341, + CapabilityVulkanMemoryModel = 5345, + CapabilityVulkanMemoryModelKHR = 5345, + CapabilityVulkanMemoryModelDeviceScope = 5346, + CapabilityVulkanMemoryModelDeviceScopeKHR = 5346, + CapabilityPhysicalStorageBufferAddresses = 5347, + CapabilityPhysicalStorageBufferAddressesEXT = 5347, + CapabilityComputeDerivativeGroupLinearKHR = 5350, + CapabilityComputeDerivativeGroupLinearNV = 5350, + CapabilityRayTracingProvisionalKHR = 5353, + CapabilityCooperativeMatrixNV = 5357, + CapabilityFragmentShaderSampleInterlockEXT = 5363, + CapabilityFragmentShaderShadingRateInterlockEXT = 5372, + CapabilityShaderSMBuiltinsNV = 5373, + CapabilityFragmentShaderPixelInterlockEXT = 5378, + CapabilityDemoteToHelperInvocation = 5379, + CapabilityDemoteToHelperInvocationEXT = 5379, + CapabilityDisplacementMicromapNV = 5380, + CapabilityRayTracingOpacityMicromapEXT = 5381, + CapabilityShaderInvocationReorderNV = 5383, + CapabilityBindlessTextureNV = 5390, + CapabilityRayQueryPositionFetchKHR = 5391, + CapabilityCooperativeVectorNV = 5394, + CapabilityAtomicFloat16VectorNV = 5404, + CapabilityRayTracingDisplacementMicromapNV = 5409, + CapabilityRawAccessChainsNV = 5414, + CapabilityRayTracingSpheresGeometryNV = 5418, + CapabilityRayTracingLinearSweptSpheresGeometryNV = 5419, + CapabilityCooperativeMatrixReductionsNV = 5430, + CapabilityCooperativeMatrixConversionsNV = 5431, + CapabilityCooperativeMatrixPerElementOperationsNV = 5432, + CapabilityCooperativeMatrixTensorAddressingNV = 5433, + CapabilityCooperativeMatrixBlockLoadsNV = 5434, + CapabilityCooperativeVectorTrainingNV = 5435, + CapabilityRayTracingClusterAccelerationStructureNV = 5437, + CapabilityTensorAddressingNV = 5439, + CapabilitySubgroupShuffleINTEL = 5568, + CapabilitySubgroupBufferBlockIOINTEL = 5569, + CapabilitySubgroupImageBlockIOINTEL = 5570, + CapabilitySubgroupImageMediaBlockIOINTEL = 5579, + CapabilityRoundToInfinityINTEL = 5582, + CapabilityFloatingPointModeINTEL = 5583, + CapabilityIntegerFunctions2INTEL = 5584, + CapabilityFunctionPointersINTEL = 5603, + CapabilityIndirectReferencesINTEL = 5604, + CapabilityAsmINTEL = 5606, + CapabilityAtomicFloat32MinMaxEXT = 5612, + CapabilityAtomicFloat64MinMaxEXT = 5613, + CapabilityAtomicFloat16MinMaxEXT = 5616, + CapabilityVectorComputeINTEL = 5617, + CapabilityVectorAnyINTEL = 5619, + CapabilityExpectAssumeKHR = 5629, + CapabilitySubgroupAvcMotionEstimationINTEL = 5696, + CapabilitySubgroupAvcMotionEstimationIntraINTEL = 5697, + CapabilitySubgroupAvcMotionEstimationChromaINTEL = 5698, + CapabilityVariableLengthArrayINTEL = 5817, + CapabilityFunctionFloatControlINTEL = 5821, + CapabilityFPGAMemoryAttributesINTEL = 5824, + CapabilityFPFastMathModeINTEL = 5837, + CapabilityArbitraryPrecisionIntegersINTEL = 5844, + CapabilityArbitraryPrecisionFloatingPointINTEL = 5845, + CapabilityUnstructuredLoopControlsINTEL = 5886, + CapabilityFPGALoopControlsINTEL = 5888, + CapabilityKernelAttributesINTEL = 5892, + CapabilityFPGAKernelAttributesINTEL = 5897, + CapabilityFPGAMemoryAccessesINTEL = 5898, + CapabilityFPGAClusterAttributesINTEL = 5904, + CapabilityLoopFuseINTEL = 5906, + CapabilityFPGADSPControlINTEL = 5908, + CapabilityMemoryAccessAliasingINTEL = 5910, + CapabilityFPGAInvocationPipeliningAttributesINTEL = 5916, + CapabilityFPGABufferLocationINTEL = 5920, + CapabilityArbitraryPrecisionFixedPointINTEL = 5922, + CapabilityUSMStorageClassesINTEL = 5935, + CapabilityRuntimeAlignedAttributeINTEL = 5939, + CapabilityIOPipesINTEL = 5943, + CapabilityBlockingPipesINTEL = 5945, + CapabilityFPGARegINTEL = 5948, + CapabilityDotProductInputAll = 6016, + CapabilityDotProductInputAllKHR = 6016, + CapabilityDotProductInput4x8Bit = 6017, + CapabilityDotProductInput4x8BitKHR = 6017, + CapabilityDotProductInput4x8BitPacked = 6018, + CapabilityDotProductInput4x8BitPackedKHR = 6018, + CapabilityDotProduct = 6019, + CapabilityDotProductKHR = 6019, + CapabilityRayCullMaskKHR = 6020, + CapabilityCooperativeMatrixKHR = 6022, + CapabilityReplicatedCompositesEXT = 6024, + CapabilityBitInstructions = 6025, + CapabilityGroupNonUniformRotateKHR = 6026, + CapabilityFloatControls2 = 6029, + CapabilityAtomicFloat32AddEXT = 6033, + CapabilityAtomicFloat64AddEXT = 6034, + CapabilityLongCompositesINTEL = 6089, + CapabilityOptNoneEXT = 6094, + CapabilityOptNoneINTEL = 6094, + CapabilityAtomicFloat16AddEXT = 6095, + CapabilityDebugInfoModuleINTEL = 6114, + CapabilityBFloat16ConversionINTEL = 6115, + CapabilitySplitBarrierINTEL = 6141, + CapabilityArithmeticFenceEXT = 6144, + CapabilityFPGAClusterAttributesV2INTEL = 6150, + CapabilityFPGAKernelAttributesv2INTEL = 6161, + CapabilityTaskSequenceINTEL = 6162, + CapabilityFPMaxErrorINTEL = 6169, + CapabilityFPGALatencyControlINTEL = 6171, + CapabilityFPGAArgumentInterfacesINTEL = 6174, + CapabilityGlobalVariableHostAccessINTEL = 6187, + CapabilityGlobalVariableFPGADecorationsINTEL = 6189, + CapabilitySubgroupBufferPrefetchINTEL = 6220, + CapabilitySubgroup2DBlockIOINTEL = 6228, + CapabilitySubgroup2DBlockTransformINTEL = 6229, + CapabilitySubgroup2DBlockTransposeINTEL = 6230, + CapabilitySubgroupMatrixMultiplyAccumulateINTEL = 6236, + CapabilityTernaryBitwiseFunctionINTEL = 6241, + CapabilityGroupUniformArithmeticKHR = 6400, + CapabilityTensorFloat32RoundingINTEL = 6425, + CapabilityMaskedGatherScatterINTEL = 6427, + CapabilityCacheControlsINTEL = 6441, + CapabilityRegisterLimitsINTEL = 6460, + CapabilityMax = 0x7fffffff, +}; + +enum RayFlagsShift { + RayFlagsOpaqueKHRShift = 0, + RayFlagsNoOpaqueKHRShift = 1, + RayFlagsTerminateOnFirstHitKHRShift = 2, + RayFlagsSkipClosestHitShaderKHRShift = 3, + RayFlagsCullBackFacingTrianglesKHRShift = 4, + RayFlagsCullFrontFacingTrianglesKHRShift = 5, + RayFlagsCullOpaqueKHRShift = 6, + RayFlagsCullNoOpaqueKHRShift = 7, + RayFlagsSkipBuiltinPrimitivesNVShift = 8, + RayFlagsSkipTrianglesKHRShift = 8, + RayFlagsSkipAABBsKHRShift = 9, + RayFlagsForceOpacityMicromap2StateEXTShift = 10, + RayFlagsMax = 0x7fffffff, +}; + +enum RayFlagsMask { + RayFlagsMaskNone = 0, + RayFlagsOpaqueKHRMask = 0x00000001, + RayFlagsNoOpaqueKHRMask = 0x00000002, + RayFlagsTerminateOnFirstHitKHRMask = 0x00000004, + RayFlagsSkipClosestHitShaderKHRMask = 0x00000008, + RayFlagsCullBackFacingTrianglesKHRMask = 0x00000010, + RayFlagsCullFrontFacingTrianglesKHRMask = 0x00000020, + RayFlagsCullOpaqueKHRMask = 0x00000040, + RayFlagsCullNoOpaqueKHRMask = 0x00000080, + RayFlagsSkipBuiltinPrimitivesNVMask = 0x00000100, + RayFlagsSkipTrianglesKHRMask = 0x00000100, + RayFlagsSkipAABBsKHRMask = 0x00000200, + RayFlagsForceOpacityMicromap2StateEXTMask = 0x00000400, +}; + +enum RayQueryIntersection { + RayQueryIntersectionRayQueryCandidateIntersectionKHR = 0, + RayQueryIntersectionRayQueryCommittedIntersectionKHR = 1, + RayQueryIntersectionMax = 0x7fffffff, +}; + +enum RayQueryCommittedIntersectionType { + RayQueryCommittedIntersectionTypeRayQueryCommittedIntersectionNoneKHR = 0, + RayQueryCommittedIntersectionTypeRayQueryCommittedIntersectionTriangleKHR = 1, + RayQueryCommittedIntersectionTypeRayQueryCommittedIntersectionGeneratedKHR = 2, + RayQueryCommittedIntersectionTypeMax = 0x7fffffff, +}; + +enum RayQueryCandidateIntersectionType { + RayQueryCandidateIntersectionTypeRayQueryCandidateIntersectionTriangleKHR = 0, + RayQueryCandidateIntersectionTypeRayQueryCandidateIntersectionAABBKHR = 1, + RayQueryCandidateIntersectionTypeMax = 0x7fffffff, +}; + +enum FragmentShadingRateShift { + FragmentShadingRateVertical2PixelsShift = 0, + FragmentShadingRateVertical4PixelsShift = 1, + FragmentShadingRateHorizontal2PixelsShift = 2, + FragmentShadingRateHorizontal4PixelsShift = 3, + FragmentShadingRateMax = 0x7fffffff, +}; + +enum FragmentShadingRateMask { + FragmentShadingRateMaskNone = 0, + FragmentShadingRateVertical2PixelsMask = 0x00000001, + FragmentShadingRateVertical4PixelsMask = 0x00000002, + FragmentShadingRateHorizontal2PixelsMask = 0x00000004, + FragmentShadingRateHorizontal4PixelsMask = 0x00000008, +}; + +enum FPDenormMode { + FPDenormModePreserve = 0, + FPDenormModeFlushToZero = 1, + FPDenormModeMax = 0x7fffffff, +}; + +enum FPOperationMode { + FPOperationModeIEEE = 0, + FPOperationModeALT = 1, + FPOperationModeMax = 0x7fffffff, +}; + +enum QuantizationModes { + QuantizationModesTRN = 0, + QuantizationModesTRN_ZERO = 1, + QuantizationModesRND = 2, + QuantizationModesRND_ZERO = 3, + QuantizationModesRND_INF = 4, + QuantizationModesRND_MIN_INF = 5, + QuantizationModesRND_CONV = 6, + QuantizationModesRND_CONV_ODD = 7, + QuantizationModesMax = 0x7fffffff, +}; + +enum OverflowModes { + OverflowModesWRAP = 0, + OverflowModesSAT = 1, + OverflowModesSAT_ZERO = 2, + OverflowModesSAT_SYM = 3, + OverflowModesMax = 0x7fffffff, +}; + +enum PackedVectorFormat { + PackedVectorFormatPackedVectorFormat4x8Bit = 0, + PackedVectorFormatPackedVectorFormat4x8BitKHR = 0, + PackedVectorFormatMax = 0x7fffffff, +}; + +enum CooperativeMatrixOperandsShift { + CooperativeMatrixOperandsMatrixASignedComponentsKHRShift = 0, + CooperativeMatrixOperandsMatrixBSignedComponentsKHRShift = 1, + CooperativeMatrixOperandsMatrixCSignedComponentsKHRShift = 2, + CooperativeMatrixOperandsMatrixResultSignedComponentsKHRShift = 3, + CooperativeMatrixOperandsSaturatingAccumulationKHRShift = 4, + CooperativeMatrixOperandsMax = 0x7fffffff, +}; + +enum CooperativeMatrixOperandsMask { + CooperativeMatrixOperandsMaskNone = 0, + CooperativeMatrixOperandsMatrixASignedComponentsKHRMask = 0x00000001, + CooperativeMatrixOperandsMatrixBSignedComponentsKHRMask = 0x00000002, + CooperativeMatrixOperandsMatrixCSignedComponentsKHRMask = 0x00000004, + CooperativeMatrixOperandsMatrixResultSignedComponentsKHRMask = 0x00000008, + CooperativeMatrixOperandsSaturatingAccumulationKHRMask = 0x00000010, +}; + +enum CooperativeMatrixLayout { + CooperativeMatrixLayoutRowMajorKHR = 0, + CooperativeMatrixLayoutColumnMajorKHR = 1, + CooperativeMatrixLayoutRowBlockedInterleavedARM = 4202, + CooperativeMatrixLayoutColumnBlockedInterleavedARM = 4203, + CooperativeMatrixLayoutMax = 0x7fffffff, +}; + +enum CooperativeMatrixUse { + CooperativeMatrixUseMatrixAKHR = 0, + CooperativeMatrixUseMatrixBKHR = 1, + CooperativeMatrixUseMatrixAccumulatorKHR = 2, + CooperativeMatrixUseMax = 0x7fffffff, +}; + +enum CooperativeMatrixReduceShift { + CooperativeMatrixReduceRowShift = 0, + CooperativeMatrixReduceColumnShift = 1, + CooperativeMatrixReduce2x2Shift = 2, + CooperativeMatrixReduceMax = 0x7fffffff, +}; + +enum CooperativeMatrixReduceMask { + CooperativeMatrixReduceMaskNone = 0, + CooperativeMatrixReduceRowMask = 0x00000001, + CooperativeMatrixReduceColumnMask = 0x00000002, + CooperativeMatrixReduce2x2Mask = 0x00000004, +}; + +enum TensorClampMode { + TensorClampModeUndefined = 0, + TensorClampModeConstant = 1, + TensorClampModeClampToEdge = 2, + TensorClampModeRepeat = 3, + TensorClampModeRepeatMirrored = 4, + TensorClampModeMax = 0x7fffffff, +}; + +enum TensorAddressingOperandsShift { + TensorAddressingOperandsTensorViewShift = 0, + TensorAddressingOperandsDecodeFuncShift = 1, + TensorAddressingOperandsMax = 0x7fffffff, +}; + +enum TensorAddressingOperandsMask { + TensorAddressingOperandsMaskNone = 0, + TensorAddressingOperandsTensorViewMask = 0x00000001, + TensorAddressingOperandsDecodeFuncMask = 0x00000002, +}; + +enum InitializationModeQualifier { + InitializationModeQualifierInitOnDeviceReprogramINTEL = 0, + InitializationModeQualifierInitOnDeviceResetINTEL = 1, + InitializationModeQualifierMax = 0x7fffffff, +}; + +enum HostAccessQualifier { + HostAccessQualifierNoneINTEL = 0, + HostAccessQualifierReadINTEL = 1, + HostAccessQualifierWriteINTEL = 2, + HostAccessQualifierReadWriteINTEL = 3, + HostAccessQualifierMax = 0x7fffffff, +}; + +enum LoadCacheControl { + LoadCacheControlUncachedINTEL = 0, + LoadCacheControlCachedINTEL = 1, + LoadCacheControlStreamingINTEL = 2, + LoadCacheControlInvalidateAfterReadINTEL = 3, + LoadCacheControlConstCachedINTEL = 4, + LoadCacheControlMax = 0x7fffffff, +}; + +enum StoreCacheControl { + StoreCacheControlUncachedINTEL = 0, + StoreCacheControlWriteThroughINTEL = 1, + StoreCacheControlWriteBackINTEL = 2, + StoreCacheControlStreamingINTEL = 3, + StoreCacheControlMax = 0x7fffffff, +}; + +enum NamedMaximumNumberOfRegisters { + NamedMaximumNumberOfRegistersAutoINTEL = 0, + NamedMaximumNumberOfRegistersMax = 0x7fffffff, +}; + +enum MatrixMultiplyAccumulateOperandsShift { + MatrixMultiplyAccumulateOperandsMatrixASignedComponentsINTELShift = 0, + MatrixMultiplyAccumulateOperandsMatrixBSignedComponentsINTELShift = 1, + MatrixMultiplyAccumulateOperandsMatrixCBFloat16INTELShift = 2, + MatrixMultiplyAccumulateOperandsMatrixResultBFloat16INTELShift = 3, + MatrixMultiplyAccumulateOperandsMatrixAPackedInt8INTELShift = 4, + MatrixMultiplyAccumulateOperandsMatrixBPackedInt8INTELShift = 5, + MatrixMultiplyAccumulateOperandsMatrixAPackedInt4INTELShift = 6, + MatrixMultiplyAccumulateOperandsMatrixBPackedInt4INTELShift = 7, + MatrixMultiplyAccumulateOperandsMatrixATF32INTELShift = 8, + MatrixMultiplyAccumulateOperandsMatrixBTF32INTELShift = 9, + MatrixMultiplyAccumulateOperandsMatrixAPackedFloat16INTELShift = 10, + MatrixMultiplyAccumulateOperandsMatrixBPackedFloat16INTELShift = 11, + MatrixMultiplyAccumulateOperandsMatrixAPackedBFloat16INTELShift = 12, + MatrixMultiplyAccumulateOperandsMatrixBPackedBFloat16INTELShift = 13, + MatrixMultiplyAccumulateOperandsMax = 0x7fffffff, +}; + +enum MatrixMultiplyAccumulateOperandsMask { + MatrixMultiplyAccumulateOperandsMaskNone = 0, + MatrixMultiplyAccumulateOperandsMatrixASignedComponentsINTELMask = 0x00000001, + MatrixMultiplyAccumulateOperandsMatrixBSignedComponentsINTELMask = 0x00000002, + MatrixMultiplyAccumulateOperandsMatrixCBFloat16INTELMask = 0x00000004, + MatrixMultiplyAccumulateOperandsMatrixResultBFloat16INTELMask = 0x00000008, + MatrixMultiplyAccumulateOperandsMatrixAPackedInt8INTELMask = 0x00000010, + MatrixMultiplyAccumulateOperandsMatrixBPackedInt8INTELMask = 0x00000020, + MatrixMultiplyAccumulateOperandsMatrixAPackedInt4INTELMask = 0x00000040, + MatrixMultiplyAccumulateOperandsMatrixBPackedInt4INTELMask = 0x00000080, + MatrixMultiplyAccumulateOperandsMatrixATF32INTELMask = 0x00000100, + MatrixMultiplyAccumulateOperandsMatrixBTF32INTELMask = 0x00000200, + MatrixMultiplyAccumulateOperandsMatrixAPackedFloat16INTELMask = 0x00000400, + MatrixMultiplyAccumulateOperandsMatrixBPackedFloat16INTELMask = 0x00000800, + MatrixMultiplyAccumulateOperandsMatrixAPackedBFloat16INTELMask = 0x00001000, + MatrixMultiplyAccumulateOperandsMatrixBPackedBFloat16INTELMask = 0x00002000, +}; + +enum RawAccessChainOperandsShift { + RawAccessChainOperandsRobustnessPerComponentNVShift = 0, + RawAccessChainOperandsRobustnessPerElementNVShift = 1, + RawAccessChainOperandsMax = 0x7fffffff, +}; + +enum RawAccessChainOperandsMask { + RawAccessChainOperandsMaskNone = 0, + RawAccessChainOperandsRobustnessPerComponentNVMask = 0x00000001, + RawAccessChainOperandsRobustnessPerElementNVMask = 0x00000002, +}; + +enum FPEncoding { + FPEncodingBFloat16KHR = 0, + FPEncodingMax = 0x7fffffff, +}; + +enum CooperativeVectorMatrixLayout { + CooperativeVectorMatrixLayoutRowMajorNV = 0, + CooperativeVectorMatrixLayoutColumnMajorNV = 1, + CooperativeVectorMatrixLayoutInferencingOptimalNV = 2, + CooperativeVectorMatrixLayoutTrainingOptimalNV = 3, + CooperativeVectorMatrixLayoutMax = 0x7fffffff, +}; + +enum ComponentType { + ComponentTypeFloat16NV = 0, + ComponentTypeFloat32NV = 1, + ComponentTypeFloat64NV = 2, + ComponentTypeSignedInt8NV = 3, + ComponentTypeSignedInt16NV = 4, + ComponentTypeSignedInt32NV = 5, + ComponentTypeSignedInt64NV = 6, + ComponentTypeUnsignedInt8NV = 7, + ComponentTypeUnsignedInt16NV = 8, + ComponentTypeUnsignedInt32NV = 9, + ComponentTypeUnsignedInt64NV = 10, + ComponentTypeSignedInt8PackedNV = 1000491000, + ComponentTypeUnsignedInt8PackedNV = 1000491001, + ComponentTypeFloatE4M3NV = 1000491002, + ComponentTypeFloatE5M2NV = 1000491003, + ComponentTypeMax = 0x7fffffff, +}; + +enum Op { + OpNop = 0, + OpUndef = 1, + OpSourceContinued = 2, + OpSource = 3, + OpSourceExtension = 4, + OpName = 5, + OpMemberName = 6, + OpString = 7, + OpLine = 8, + OpExtension = 10, + OpExtInstImport = 11, + OpExtInst = 12, + OpMemoryModel = 14, + OpEntryPoint = 15, + OpExecutionMode = 16, + OpCapability = 17, + OpTypeVoid = 19, + OpTypeBool = 20, + OpTypeInt = 21, + OpTypeFloat = 22, + OpTypeVector = 23, + OpTypeMatrix = 24, + OpTypeImage = 25, + OpTypeSampler = 26, + OpTypeSampledImage = 27, + OpTypeArray = 28, + OpTypeRuntimeArray = 29, + OpTypeStruct = 30, + OpTypeOpaque = 31, + OpTypePointer = 32, + OpTypeFunction = 33, + OpTypeEvent = 34, + OpTypeDeviceEvent = 35, + OpTypeReserveId = 36, + OpTypeQueue = 37, + OpTypePipe = 38, + OpTypeForwardPointer = 39, + OpConstantTrue = 41, + OpConstantFalse = 42, + OpConstant = 43, + OpConstantComposite = 44, + OpConstantSampler = 45, + OpConstantNull = 46, + OpSpecConstantTrue = 48, + OpSpecConstantFalse = 49, + OpSpecConstant = 50, + OpSpecConstantComposite = 51, + OpSpecConstantOp = 52, + OpFunction = 54, + OpFunctionParameter = 55, + OpFunctionEnd = 56, + OpFunctionCall = 57, + OpVariable = 59, + OpImageTexelPointer = 60, + OpLoad = 61, + OpStore = 62, + OpCopyMemory = 63, + OpCopyMemorySized = 64, + OpAccessChain = 65, + OpInBoundsAccessChain = 66, + OpPtrAccessChain = 67, + OpArrayLength = 68, + OpGenericPtrMemSemantics = 69, + OpInBoundsPtrAccessChain = 70, + OpDecorate = 71, + OpMemberDecorate = 72, + OpDecorationGroup = 73, + OpGroupDecorate = 74, + OpGroupMemberDecorate = 75, + OpVectorExtractDynamic = 77, + OpVectorInsertDynamic = 78, + OpVectorShuffle = 79, + OpCompositeConstruct = 80, + OpCompositeExtract = 81, + OpCompositeInsert = 82, + OpCopyObject = 83, + OpTranspose = 84, + OpSampledImage = 86, + OpImageSampleImplicitLod = 87, + OpImageSampleExplicitLod = 88, + OpImageSampleDrefImplicitLod = 89, + OpImageSampleDrefExplicitLod = 90, + OpImageSampleProjImplicitLod = 91, + OpImageSampleProjExplicitLod = 92, + OpImageSampleProjDrefImplicitLod = 93, + OpImageSampleProjDrefExplicitLod = 94, + OpImageFetch = 95, + OpImageGather = 96, + OpImageDrefGather = 97, + OpImageRead = 98, + OpImageWrite = 99, + OpImage = 100, + OpImageQueryFormat = 101, + OpImageQueryOrder = 102, + OpImageQuerySizeLod = 103, + OpImageQuerySize = 104, + OpImageQueryLod = 105, + OpImageQueryLevels = 106, + OpImageQuerySamples = 107, + OpConvertFToU = 109, + OpConvertFToS = 110, + OpConvertSToF = 111, + OpConvertUToF = 112, + OpUConvert = 113, + OpSConvert = 114, + OpFConvert = 115, + OpQuantizeToF16 = 116, + OpConvertPtrToU = 117, + OpSatConvertSToU = 118, + OpSatConvertUToS = 119, + OpConvertUToPtr = 120, + OpPtrCastToGeneric = 121, + OpGenericCastToPtr = 122, + OpGenericCastToPtrExplicit = 123, + OpBitcast = 124, + OpSNegate = 126, + OpFNegate = 127, + OpIAdd = 128, + OpFAdd = 129, + OpISub = 130, + OpFSub = 131, + OpIMul = 132, + OpFMul = 133, + OpUDiv = 134, + OpSDiv = 135, + OpFDiv = 136, + OpUMod = 137, + OpSRem = 138, + OpSMod = 139, + OpFRem = 140, + OpFMod = 141, + OpVectorTimesScalar = 142, + OpMatrixTimesScalar = 143, + OpVectorTimesMatrix = 144, + OpMatrixTimesVector = 145, + OpMatrixTimesMatrix = 146, + OpOuterProduct = 147, + OpDot = 148, + OpIAddCarry = 149, + OpISubBorrow = 150, + OpUMulExtended = 151, + OpSMulExtended = 152, + OpAny = 154, + OpAll = 155, + OpIsNan = 156, + OpIsInf = 157, + OpIsFinite = 158, + OpIsNormal = 159, + OpSignBitSet = 160, + OpLessOrGreater = 161, + OpOrdered = 162, + OpUnordered = 163, + OpLogicalEqual = 164, + OpLogicalNotEqual = 165, + OpLogicalOr = 166, + OpLogicalAnd = 167, + OpLogicalNot = 168, + OpSelect = 169, + OpIEqual = 170, + OpINotEqual = 171, + OpUGreaterThan = 172, + OpSGreaterThan = 173, + OpUGreaterThanEqual = 174, + OpSGreaterThanEqual = 175, + OpULessThan = 176, + OpSLessThan = 177, + OpULessThanEqual = 178, + OpSLessThanEqual = 179, + OpFOrdEqual = 180, + OpFUnordEqual = 181, + OpFOrdNotEqual = 182, + OpFUnordNotEqual = 183, + OpFOrdLessThan = 184, + OpFUnordLessThan = 185, + OpFOrdGreaterThan = 186, + OpFUnordGreaterThan = 187, + OpFOrdLessThanEqual = 188, + OpFUnordLessThanEqual = 189, + OpFOrdGreaterThanEqual = 190, + OpFUnordGreaterThanEqual = 191, + OpShiftRightLogical = 194, + OpShiftRightArithmetic = 195, + OpShiftLeftLogical = 196, + OpBitwiseOr = 197, + OpBitwiseXor = 198, + OpBitwiseAnd = 199, + OpNot = 200, + OpBitFieldInsert = 201, + OpBitFieldSExtract = 202, + OpBitFieldUExtract = 203, + OpBitReverse = 204, + OpBitCount = 205, + OpDPdx = 207, + OpDPdy = 208, + OpFwidth = 209, + OpDPdxFine = 210, + OpDPdyFine = 211, + OpFwidthFine = 212, + OpDPdxCoarse = 213, + OpDPdyCoarse = 214, + OpFwidthCoarse = 215, + OpEmitVertex = 218, + OpEndPrimitive = 219, + OpEmitStreamVertex = 220, + OpEndStreamPrimitive = 221, + OpControlBarrier = 224, + OpMemoryBarrier = 225, + OpAtomicLoad = 227, + OpAtomicStore = 228, + OpAtomicExchange = 229, + OpAtomicCompareExchange = 230, + OpAtomicCompareExchangeWeak = 231, + OpAtomicIIncrement = 232, + OpAtomicIDecrement = 233, + OpAtomicIAdd = 234, + OpAtomicISub = 235, + OpAtomicSMin = 236, + OpAtomicUMin = 237, + OpAtomicSMax = 238, + OpAtomicUMax = 239, + OpAtomicAnd = 240, + OpAtomicOr = 241, + OpAtomicXor = 242, + OpPhi = 245, + OpLoopMerge = 246, + OpSelectionMerge = 247, + OpLabel = 248, + OpBranch = 249, + OpBranchConditional = 250, + OpSwitch = 251, + OpKill = 252, + OpReturn = 253, + OpReturnValue = 254, + OpUnreachable = 255, + OpLifetimeStart = 256, + OpLifetimeStop = 257, + OpGroupAsyncCopy = 259, + OpGroupWaitEvents = 260, + OpGroupAll = 261, + OpGroupAny = 262, + OpGroupBroadcast = 263, + OpGroupIAdd = 264, + OpGroupFAdd = 265, + OpGroupFMin = 266, + OpGroupUMin = 267, + OpGroupSMin = 268, + OpGroupFMax = 269, + OpGroupUMax = 270, + OpGroupSMax = 271, + OpReadPipe = 274, + OpWritePipe = 275, + OpReservedReadPipe = 276, + OpReservedWritePipe = 277, + OpReserveReadPipePackets = 278, + OpReserveWritePipePackets = 279, + OpCommitReadPipe = 280, + OpCommitWritePipe = 281, + OpIsValidReserveId = 282, + OpGetNumPipePackets = 283, + OpGetMaxPipePackets = 284, + OpGroupReserveReadPipePackets = 285, + OpGroupReserveWritePipePackets = 286, + OpGroupCommitReadPipe = 287, + OpGroupCommitWritePipe = 288, + OpEnqueueMarker = 291, + OpEnqueueKernel = 292, + OpGetKernelNDrangeSubGroupCount = 293, + OpGetKernelNDrangeMaxSubGroupSize = 294, + OpGetKernelWorkGroupSize = 295, + OpGetKernelPreferredWorkGroupSizeMultiple = 296, + OpRetainEvent = 297, + OpReleaseEvent = 298, + OpCreateUserEvent = 299, + OpIsValidEvent = 300, + OpSetUserEventStatus = 301, + OpCaptureEventProfilingInfo = 302, + OpGetDefaultQueue = 303, + OpBuildNDRange = 304, + OpImageSparseSampleImplicitLod = 305, + OpImageSparseSampleExplicitLod = 306, + OpImageSparseSampleDrefImplicitLod = 307, + OpImageSparseSampleDrefExplicitLod = 308, + OpImageSparseSampleProjImplicitLod = 309, + OpImageSparseSampleProjExplicitLod = 310, + OpImageSparseSampleProjDrefImplicitLod = 311, + OpImageSparseSampleProjDrefExplicitLod = 312, + OpImageSparseFetch = 313, + OpImageSparseGather = 314, + OpImageSparseDrefGather = 315, + OpImageSparseTexelsResident = 316, + OpNoLine = 317, + OpAtomicFlagTestAndSet = 318, + OpAtomicFlagClear = 319, + OpImageSparseRead = 320, + OpSizeOf = 321, + OpTypePipeStorage = 322, + OpConstantPipeStorage = 323, + OpCreatePipeFromPipeStorage = 324, + OpGetKernelLocalSizeForSubgroupCount = 325, + OpGetKernelMaxNumSubgroups = 326, + OpTypeNamedBarrier = 327, + OpNamedBarrierInitialize = 328, + OpMemoryNamedBarrier = 329, + OpModuleProcessed = 330, + OpExecutionModeId = 331, + OpDecorateId = 332, + OpGroupNonUniformElect = 333, + OpGroupNonUniformAll = 334, + OpGroupNonUniformAny = 335, + OpGroupNonUniformAllEqual = 336, + OpGroupNonUniformBroadcast = 337, + OpGroupNonUniformBroadcastFirst = 338, + OpGroupNonUniformBallot = 339, + OpGroupNonUniformInverseBallot = 340, + OpGroupNonUniformBallotBitExtract = 341, + OpGroupNonUniformBallotBitCount = 342, + OpGroupNonUniformBallotFindLSB = 343, + OpGroupNonUniformBallotFindMSB = 344, + OpGroupNonUniformShuffle = 345, + OpGroupNonUniformShuffleXor = 346, + OpGroupNonUniformShuffleUp = 347, + OpGroupNonUniformShuffleDown = 348, + OpGroupNonUniformIAdd = 349, + OpGroupNonUniformFAdd = 350, + OpGroupNonUniformIMul = 351, + OpGroupNonUniformFMul = 352, + OpGroupNonUniformSMin = 353, + OpGroupNonUniformUMin = 354, + OpGroupNonUniformFMin = 355, + OpGroupNonUniformSMax = 356, + OpGroupNonUniformUMax = 357, + OpGroupNonUniformFMax = 358, + OpGroupNonUniformBitwiseAnd = 359, + OpGroupNonUniformBitwiseOr = 360, + OpGroupNonUniformBitwiseXor = 361, + OpGroupNonUniformLogicalAnd = 362, + OpGroupNonUniformLogicalOr = 363, + OpGroupNonUniformLogicalXor = 364, + OpGroupNonUniformQuadBroadcast = 365, + OpGroupNonUniformQuadSwap = 366, + OpCopyLogical = 400, + OpPtrEqual = 401, + OpPtrNotEqual = 402, + OpPtrDiff = 403, + OpColorAttachmentReadEXT = 4160, + OpDepthAttachmentReadEXT = 4161, + OpStencilAttachmentReadEXT = 4162, + OpTerminateInvocation = 4416, + OpTypeUntypedPointerKHR = 4417, + OpUntypedVariableKHR = 4418, + OpUntypedAccessChainKHR = 4419, + OpUntypedInBoundsAccessChainKHR = 4420, + OpSubgroupBallotKHR = 4421, + OpSubgroupFirstInvocationKHR = 4422, + OpUntypedPtrAccessChainKHR = 4423, + OpUntypedInBoundsPtrAccessChainKHR = 4424, + OpUntypedArrayLengthKHR = 4425, + OpUntypedPrefetchKHR = 4426, + OpSubgroupAllKHR = 4428, + OpSubgroupAnyKHR = 4429, + OpSubgroupAllEqualKHR = 4430, + OpGroupNonUniformRotateKHR = 4431, + OpSubgroupReadInvocationKHR = 4432, + OpExtInstWithForwardRefsKHR = 4433, + OpTraceRayKHR = 4445, + OpExecuteCallableKHR = 4446, + OpConvertUToAccelerationStructureKHR = 4447, + OpIgnoreIntersectionKHR = 4448, + OpTerminateRayKHR = 4449, + OpSDot = 4450, + OpSDotKHR = 4450, + OpUDot = 4451, + OpUDotKHR = 4451, + OpSUDot = 4452, + OpSUDotKHR = 4452, + OpSDotAccSat = 4453, + OpSDotAccSatKHR = 4453, + OpUDotAccSat = 4454, + OpUDotAccSatKHR = 4454, + OpSUDotAccSat = 4455, + OpSUDotAccSatKHR = 4455, + OpTypeCooperativeMatrixKHR = 4456, + OpCooperativeMatrixLoadKHR = 4457, + OpCooperativeMatrixStoreKHR = 4458, + OpCooperativeMatrixMulAddKHR = 4459, + OpCooperativeMatrixLengthKHR = 4460, + OpConstantCompositeReplicateEXT = 4461, + OpSpecConstantCompositeReplicateEXT = 4462, + OpCompositeConstructReplicateEXT = 4463, + OpTypeRayQueryKHR = 4472, + OpRayQueryInitializeKHR = 4473, + OpRayQueryTerminateKHR = 4474, + OpRayQueryGenerateIntersectionKHR = 4475, + OpRayQueryConfirmIntersectionKHR = 4476, + OpRayQueryProceedKHR = 4477, + OpRayQueryGetIntersectionTypeKHR = 4479, + OpImageSampleWeightedQCOM = 4480, + OpImageBoxFilterQCOM = 4481, + OpImageBlockMatchSSDQCOM = 4482, + OpImageBlockMatchSADQCOM = 4483, + OpImageBlockMatchWindowSSDQCOM = 4500, + OpImageBlockMatchWindowSADQCOM = 4501, + OpImageBlockMatchGatherSSDQCOM = 4502, + OpImageBlockMatchGatherSADQCOM = 4503, + OpGroupIAddNonUniformAMD = 5000, + OpGroupFAddNonUniformAMD = 5001, + OpGroupFMinNonUniformAMD = 5002, + OpGroupUMinNonUniformAMD = 5003, + OpGroupSMinNonUniformAMD = 5004, + OpGroupFMaxNonUniformAMD = 5005, + OpGroupUMaxNonUniformAMD = 5006, + OpGroupSMaxNonUniformAMD = 5007, + OpFragmentMaskFetchAMD = 5011, + OpFragmentFetchAMD = 5012, + OpReadClockKHR = 5056, + OpAllocateNodePayloadsAMDX = 5074, + OpEnqueueNodePayloadsAMDX = 5075, + OpTypeNodePayloadArrayAMDX = 5076, + OpFinishWritingNodePayloadAMDX = 5078, + OpNodePayloadArrayLengthAMDX = 5090, + OpIsNodePayloadValidAMDX = 5101, + OpConstantStringAMDX = 5103, + OpSpecConstantStringAMDX = 5104, + OpGroupNonUniformQuadAllKHR = 5110, + OpGroupNonUniformQuadAnyKHR = 5111, + OpHitObjectRecordHitMotionNV = 5249, + OpHitObjectRecordHitWithIndexMotionNV = 5250, + OpHitObjectRecordMissMotionNV = 5251, + OpHitObjectGetWorldToObjectNV = 5252, + OpHitObjectGetObjectToWorldNV = 5253, + OpHitObjectGetObjectRayDirectionNV = 5254, + OpHitObjectGetObjectRayOriginNV = 5255, + OpHitObjectTraceRayMotionNV = 5256, + OpHitObjectGetShaderRecordBufferHandleNV = 5257, + OpHitObjectGetShaderBindingTableRecordIndexNV = 5258, + OpHitObjectRecordEmptyNV = 5259, + OpHitObjectTraceRayNV = 5260, + OpHitObjectRecordHitNV = 5261, + OpHitObjectRecordHitWithIndexNV = 5262, + OpHitObjectRecordMissNV = 5263, + OpHitObjectExecuteShaderNV = 5264, + OpHitObjectGetCurrentTimeNV = 5265, + OpHitObjectGetAttributesNV = 5266, + OpHitObjectGetHitKindNV = 5267, + OpHitObjectGetPrimitiveIndexNV = 5268, + OpHitObjectGetGeometryIndexNV = 5269, + OpHitObjectGetInstanceIdNV = 5270, + OpHitObjectGetInstanceCustomIndexNV = 5271, + OpHitObjectGetWorldRayDirectionNV = 5272, + OpHitObjectGetWorldRayOriginNV = 5273, + OpHitObjectGetRayTMaxNV = 5274, + OpHitObjectGetRayTMinNV = 5275, + OpHitObjectIsEmptyNV = 5276, + OpHitObjectIsHitNV = 5277, + OpHitObjectIsMissNV = 5278, + OpReorderThreadWithHitObjectNV = 5279, + OpReorderThreadWithHintNV = 5280, + OpTypeHitObjectNV = 5281, + OpImageSampleFootprintNV = 5283, + OpTypeCooperativeVectorNV = 5288, + OpCooperativeVectorMatrixMulNV = 5289, + OpCooperativeVectorOuterProductAccumulateNV = 5290, + OpCooperativeVectorReduceSumAccumulateNV = 5291, + OpCooperativeVectorMatrixMulAddNV = 5292, + OpCooperativeMatrixConvertNV = 5293, + OpEmitMeshTasksEXT = 5294, + OpSetMeshOutputsEXT = 5295, + OpGroupNonUniformPartitionNV = 5296, + OpWritePackedPrimitiveIndices4x8NV = 5299, + OpFetchMicroTriangleVertexPositionNV = 5300, + OpFetchMicroTriangleVertexBarycentricNV = 5301, + OpCooperativeVectorLoadNV = 5302, + OpCooperativeVectorStoreNV = 5303, + OpReportIntersectionKHR = 5334, + OpReportIntersectionNV = 5334, + OpIgnoreIntersectionNV = 5335, + OpTerminateRayNV = 5336, + OpTraceNV = 5337, + OpTraceMotionNV = 5338, + OpTraceRayMotionNV = 5339, + OpRayQueryGetIntersectionTriangleVertexPositionsKHR = 5340, + OpTypeAccelerationStructureKHR = 5341, + OpTypeAccelerationStructureNV = 5341, + OpExecuteCallableNV = 5344, + OpRayQueryGetClusterIdNV = 5345, + OpHitObjectGetClusterIdNV = 5346, + OpTypeCooperativeMatrixNV = 5358, + OpCooperativeMatrixLoadNV = 5359, + OpCooperativeMatrixStoreNV = 5360, + OpCooperativeMatrixMulAddNV = 5361, + OpCooperativeMatrixLengthNV = 5362, + OpBeginInvocationInterlockEXT = 5364, + OpEndInvocationInterlockEXT = 5365, + OpCooperativeMatrixReduceNV = 5366, + OpCooperativeMatrixLoadTensorNV = 5367, + OpCooperativeMatrixStoreTensorNV = 5368, + OpCooperativeMatrixPerElementOpNV = 5369, + OpTypeTensorLayoutNV = 5370, + OpTypeTensorViewNV = 5371, + OpCreateTensorLayoutNV = 5372, + OpTensorLayoutSetDimensionNV = 5373, + OpTensorLayoutSetStrideNV = 5374, + OpTensorLayoutSliceNV = 5375, + OpTensorLayoutSetClampValueNV = 5376, + OpCreateTensorViewNV = 5377, + OpTensorViewSetDimensionNV = 5378, + OpTensorViewSetStrideNV = 5379, + OpDemoteToHelperInvocation = 5380, + OpDemoteToHelperInvocationEXT = 5380, + OpIsHelperInvocationEXT = 5381, + OpTensorViewSetClipNV = 5382, + OpTensorLayoutSetBlockSizeNV = 5384, + OpCooperativeMatrixTransposeNV = 5390, + OpConvertUToImageNV = 5391, + OpConvertUToSamplerNV = 5392, + OpConvertImageToUNV = 5393, + OpConvertSamplerToUNV = 5394, + OpConvertUToSampledImageNV = 5395, + OpConvertSampledImageToUNV = 5396, + OpSamplerImageAddressingModeNV = 5397, + OpRawAccessChainNV = 5398, + OpRayQueryGetIntersectionSpherePositionNV = 5427, + OpRayQueryGetIntersectionSphereRadiusNV = 5428, + OpRayQueryGetIntersectionLSSPositionsNV = 5429, + OpRayQueryGetIntersectionLSSRadiiNV = 5430, + OpRayQueryGetIntersectionLSSHitValueNV = 5431, + OpHitObjectGetSpherePositionNV = 5432, + OpHitObjectGetSphereRadiusNV = 5433, + OpHitObjectGetLSSPositionsNV = 5434, + OpHitObjectGetLSSRadiiNV = 5435, + OpHitObjectIsSphereHitNV = 5436, + OpHitObjectIsLSSHitNV = 5437, + OpRayQueryIsSphereHitNV = 5438, + OpRayQueryIsLSSHitNV = 5439, + OpSubgroupShuffleINTEL = 5571, + OpSubgroupShuffleDownINTEL = 5572, + OpSubgroupShuffleUpINTEL = 5573, + OpSubgroupShuffleXorINTEL = 5574, + OpSubgroupBlockReadINTEL = 5575, + OpSubgroupBlockWriteINTEL = 5576, + OpSubgroupImageBlockReadINTEL = 5577, + OpSubgroupImageBlockWriteINTEL = 5578, + OpSubgroupImageMediaBlockReadINTEL = 5580, + OpSubgroupImageMediaBlockWriteINTEL = 5581, + OpUCountLeadingZerosINTEL = 5585, + OpUCountTrailingZerosINTEL = 5586, + OpAbsISubINTEL = 5587, + OpAbsUSubINTEL = 5588, + OpIAddSatINTEL = 5589, + OpUAddSatINTEL = 5590, + OpIAverageINTEL = 5591, + OpUAverageINTEL = 5592, + OpIAverageRoundedINTEL = 5593, + OpUAverageRoundedINTEL = 5594, + OpISubSatINTEL = 5595, + OpUSubSatINTEL = 5596, + OpIMul32x16INTEL = 5597, + OpUMul32x16INTEL = 5598, + OpConstantFunctionPointerINTEL = 5600, + OpFunctionPointerCallINTEL = 5601, + OpAsmTargetINTEL = 5609, + OpAsmINTEL = 5610, + OpAsmCallINTEL = 5611, + OpAtomicFMinEXT = 5614, + OpAtomicFMaxEXT = 5615, + OpAssumeTrueKHR = 5630, + OpExpectKHR = 5631, + OpDecorateString = 5632, + OpDecorateStringGOOGLE = 5632, + OpMemberDecorateString = 5633, + OpMemberDecorateStringGOOGLE = 5633, + OpVmeImageINTEL = 5699, + OpTypeVmeImageINTEL = 5700, + OpTypeAvcImePayloadINTEL = 5701, + OpTypeAvcRefPayloadINTEL = 5702, + OpTypeAvcSicPayloadINTEL = 5703, + OpTypeAvcMcePayloadINTEL = 5704, + OpTypeAvcMceResultINTEL = 5705, + OpTypeAvcImeResultINTEL = 5706, + OpTypeAvcImeResultSingleReferenceStreamoutINTEL = 5707, + OpTypeAvcImeResultDualReferenceStreamoutINTEL = 5708, + OpTypeAvcImeSingleReferenceStreaminINTEL = 5709, + OpTypeAvcImeDualReferenceStreaminINTEL = 5710, + OpTypeAvcRefResultINTEL = 5711, + OpTypeAvcSicResultINTEL = 5712, + OpSubgroupAvcMceGetDefaultInterBaseMultiReferencePenaltyINTEL = 5713, + OpSubgroupAvcMceSetInterBaseMultiReferencePenaltyINTEL = 5714, + OpSubgroupAvcMceGetDefaultInterShapePenaltyINTEL = 5715, + OpSubgroupAvcMceSetInterShapePenaltyINTEL = 5716, + OpSubgroupAvcMceGetDefaultInterDirectionPenaltyINTEL = 5717, + OpSubgroupAvcMceSetInterDirectionPenaltyINTEL = 5718, + OpSubgroupAvcMceGetDefaultIntraLumaShapePenaltyINTEL = 5719, + OpSubgroupAvcMceGetDefaultInterMotionVectorCostTableINTEL = 5720, + OpSubgroupAvcMceGetDefaultHighPenaltyCostTableINTEL = 5721, + OpSubgroupAvcMceGetDefaultMediumPenaltyCostTableINTEL = 5722, + OpSubgroupAvcMceGetDefaultLowPenaltyCostTableINTEL = 5723, + OpSubgroupAvcMceSetMotionVectorCostFunctionINTEL = 5724, + OpSubgroupAvcMceGetDefaultIntraLumaModePenaltyINTEL = 5725, + OpSubgroupAvcMceGetDefaultNonDcLumaIntraPenaltyINTEL = 5726, + OpSubgroupAvcMceGetDefaultIntraChromaModeBasePenaltyINTEL = 5727, + OpSubgroupAvcMceSetAcOnlyHaarINTEL = 5728, + OpSubgroupAvcMceSetSourceInterlacedFieldPolarityINTEL = 5729, + OpSubgroupAvcMceSetSingleReferenceInterlacedFieldPolarityINTEL = 5730, + OpSubgroupAvcMceSetDualReferenceInterlacedFieldPolaritiesINTEL = 5731, + OpSubgroupAvcMceConvertToImePayloadINTEL = 5732, + OpSubgroupAvcMceConvertToImeResultINTEL = 5733, + OpSubgroupAvcMceConvertToRefPayloadINTEL = 5734, + OpSubgroupAvcMceConvertToRefResultINTEL = 5735, + OpSubgroupAvcMceConvertToSicPayloadINTEL = 5736, + OpSubgroupAvcMceConvertToSicResultINTEL = 5737, + OpSubgroupAvcMceGetMotionVectorsINTEL = 5738, + OpSubgroupAvcMceGetInterDistortionsINTEL = 5739, + OpSubgroupAvcMceGetBestInterDistortionsINTEL = 5740, + OpSubgroupAvcMceGetInterMajorShapeINTEL = 5741, + OpSubgroupAvcMceGetInterMinorShapeINTEL = 5742, + OpSubgroupAvcMceGetInterDirectionsINTEL = 5743, + OpSubgroupAvcMceGetInterMotionVectorCountINTEL = 5744, + OpSubgroupAvcMceGetInterReferenceIdsINTEL = 5745, + OpSubgroupAvcMceGetInterReferenceInterlacedFieldPolaritiesINTEL = 5746, + OpSubgroupAvcImeInitializeINTEL = 5747, + OpSubgroupAvcImeSetSingleReferenceINTEL = 5748, + OpSubgroupAvcImeSetDualReferenceINTEL = 5749, + OpSubgroupAvcImeRefWindowSizeINTEL = 5750, + OpSubgroupAvcImeAdjustRefOffsetINTEL = 5751, + OpSubgroupAvcImeConvertToMcePayloadINTEL = 5752, + OpSubgroupAvcImeSetMaxMotionVectorCountINTEL = 5753, + OpSubgroupAvcImeSetUnidirectionalMixDisableINTEL = 5754, + OpSubgroupAvcImeSetEarlySearchTerminationThresholdINTEL = 5755, + OpSubgroupAvcImeSetWeightedSadINTEL = 5756, + OpSubgroupAvcImeEvaluateWithSingleReferenceINTEL = 5757, + OpSubgroupAvcImeEvaluateWithDualReferenceINTEL = 5758, + OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminINTEL = 5759, + OpSubgroupAvcImeEvaluateWithDualReferenceStreaminINTEL = 5760, + OpSubgroupAvcImeEvaluateWithSingleReferenceStreamoutINTEL = 5761, + OpSubgroupAvcImeEvaluateWithDualReferenceStreamoutINTEL = 5762, + OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminoutINTEL = 5763, + OpSubgroupAvcImeEvaluateWithDualReferenceStreaminoutINTEL = 5764, + OpSubgroupAvcImeConvertToMceResultINTEL = 5765, + OpSubgroupAvcImeGetSingleReferenceStreaminINTEL = 5766, + OpSubgroupAvcImeGetDualReferenceStreaminINTEL = 5767, + OpSubgroupAvcImeStripSingleReferenceStreamoutINTEL = 5768, + OpSubgroupAvcImeStripDualReferenceStreamoutINTEL = 5769, + OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeMotionVectorsINTEL = 5770, + OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeDistortionsINTEL = 5771, + OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeReferenceIdsINTEL = 5772, + OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeMotionVectorsINTEL = 5773, + OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeDistortionsINTEL = 5774, + OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeReferenceIdsINTEL = 5775, + OpSubgroupAvcImeGetBorderReachedINTEL = 5776, + OpSubgroupAvcImeGetTruncatedSearchIndicationINTEL = 5777, + OpSubgroupAvcImeGetUnidirectionalEarlySearchTerminationINTEL = 5778, + OpSubgroupAvcImeGetWeightingPatternMinimumMotionVectorINTEL = 5779, + OpSubgroupAvcImeGetWeightingPatternMinimumDistortionINTEL = 5780, + OpSubgroupAvcFmeInitializeINTEL = 5781, + OpSubgroupAvcBmeInitializeINTEL = 5782, + OpSubgroupAvcRefConvertToMcePayloadINTEL = 5783, + OpSubgroupAvcRefSetBidirectionalMixDisableINTEL = 5784, + OpSubgroupAvcRefSetBilinearFilterEnableINTEL = 5785, + OpSubgroupAvcRefEvaluateWithSingleReferenceINTEL = 5786, + OpSubgroupAvcRefEvaluateWithDualReferenceINTEL = 5787, + OpSubgroupAvcRefEvaluateWithMultiReferenceINTEL = 5788, + OpSubgroupAvcRefEvaluateWithMultiReferenceInterlacedINTEL = 5789, + OpSubgroupAvcRefConvertToMceResultINTEL = 5790, + OpSubgroupAvcSicInitializeINTEL = 5791, + OpSubgroupAvcSicConfigureSkcINTEL = 5792, + OpSubgroupAvcSicConfigureIpeLumaINTEL = 5793, + OpSubgroupAvcSicConfigureIpeLumaChromaINTEL = 5794, + OpSubgroupAvcSicGetMotionVectorMaskINTEL = 5795, + OpSubgroupAvcSicConvertToMcePayloadINTEL = 5796, + OpSubgroupAvcSicSetIntraLumaShapePenaltyINTEL = 5797, + OpSubgroupAvcSicSetIntraLumaModeCostFunctionINTEL = 5798, + OpSubgroupAvcSicSetIntraChromaModeCostFunctionINTEL = 5799, + OpSubgroupAvcSicSetBilinearFilterEnableINTEL = 5800, + OpSubgroupAvcSicSetSkcForwardTransformEnableINTEL = 5801, + OpSubgroupAvcSicSetBlockBasedRawSkipSadINTEL = 5802, + OpSubgroupAvcSicEvaluateIpeINTEL = 5803, + OpSubgroupAvcSicEvaluateWithSingleReferenceINTEL = 5804, + OpSubgroupAvcSicEvaluateWithDualReferenceINTEL = 5805, + OpSubgroupAvcSicEvaluateWithMultiReferenceINTEL = 5806, + OpSubgroupAvcSicEvaluateWithMultiReferenceInterlacedINTEL = 5807, + OpSubgroupAvcSicConvertToMceResultINTEL = 5808, + OpSubgroupAvcSicGetIpeLumaShapeINTEL = 5809, + OpSubgroupAvcSicGetBestIpeLumaDistortionINTEL = 5810, + OpSubgroupAvcSicGetBestIpeChromaDistortionINTEL = 5811, + OpSubgroupAvcSicGetPackedIpeLumaModesINTEL = 5812, + OpSubgroupAvcSicGetIpeChromaModeINTEL = 5813, + OpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL = 5814, + OpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL = 5815, + OpSubgroupAvcSicGetInterRawSadsINTEL = 5816, + OpVariableLengthArrayINTEL = 5818, + OpSaveMemoryINTEL = 5819, + OpRestoreMemoryINTEL = 5820, + OpArbitraryFloatSinCosPiINTEL = 5840, + OpArbitraryFloatCastINTEL = 5841, + OpArbitraryFloatCastFromIntINTEL = 5842, + OpArbitraryFloatCastToIntINTEL = 5843, + OpArbitraryFloatAddINTEL = 5846, + OpArbitraryFloatSubINTEL = 5847, + OpArbitraryFloatMulINTEL = 5848, + OpArbitraryFloatDivINTEL = 5849, + OpArbitraryFloatGTINTEL = 5850, + OpArbitraryFloatGEINTEL = 5851, + OpArbitraryFloatLTINTEL = 5852, + OpArbitraryFloatLEINTEL = 5853, + OpArbitraryFloatEQINTEL = 5854, + OpArbitraryFloatRecipINTEL = 5855, + OpArbitraryFloatRSqrtINTEL = 5856, + OpArbitraryFloatCbrtINTEL = 5857, + OpArbitraryFloatHypotINTEL = 5858, + OpArbitraryFloatSqrtINTEL = 5859, + OpArbitraryFloatLogINTEL = 5860, + OpArbitraryFloatLog2INTEL = 5861, + OpArbitraryFloatLog10INTEL = 5862, + OpArbitraryFloatLog1pINTEL = 5863, + OpArbitraryFloatExpINTEL = 5864, + OpArbitraryFloatExp2INTEL = 5865, + OpArbitraryFloatExp10INTEL = 5866, + OpArbitraryFloatExpm1INTEL = 5867, + OpArbitraryFloatSinINTEL = 5868, + OpArbitraryFloatCosINTEL = 5869, + OpArbitraryFloatSinCosINTEL = 5870, + OpArbitraryFloatSinPiINTEL = 5871, + OpArbitraryFloatCosPiINTEL = 5872, + OpArbitraryFloatASinINTEL = 5873, + OpArbitraryFloatASinPiINTEL = 5874, + OpArbitraryFloatACosINTEL = 5875, + OpArbitraryFloatACosPiINTEL = 5876, + OpArbitraryFloatATanINTEL = 5877, + OpArbitraryFloatATanPiINTEL = 5878, + OpArbitraryFloatATan2INTEL = 5879, + OpArbitraryFloatPowINTEL = 5880, + OpArbitraryFloatPowRINTEL = 5881, + OpArbitraryFloatPowNINTEL = 5882, + OpLoopControlINTEL = 5887, + OpAliasDomainDeclINTEL = 5911, + OpAliasScopeDeclINTEL = 5912, + OpAliasScopeListDeclINTEL = 5913, + OpFixedSqrtINTEL = 5923, + OpFixedRecipINTEL = 5924, + OpFixedRsqrtINTEL = 5925, + OpFixedSinINTEL = 5926, + OpFixedCosINTEL = 5927, + OpFixedSinCosINTEL = 5928, + OpFixedSinPiINTEL = 5929, + OpFixedCosPiINTEL = 5930, + OpFixedSinCosPiINTEL = 5931, + OpFixedLogINTEL = 5932, + OpFixedExpINTEL = 5933, + OpPtrCastToCrossWorkgroupINTEL = 5934, + OpCrossWorkgroupCastToPtrINTEL = 5938, + OpReadPipeBlockingINTEL = 5946, + OpWritePipeBlockingINTEL = 5947, + OpFPGARegINTEL = 5949, + OpRayQueryGetRayTMinKHR = 6016, + OpRayQueryGetRayFlagsKHR = 6017, + OpRayQueryGetIntersectionTKHR = 6018, + OpRayQueryGetIntersectionInstanceCustomIndexKHR = 6019, + OpRayQueryGetIntersectionInstanceIdKHR = 6020, + OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR = 6021, + OpRayQueryGetIntersectionGeometryIndexKHR = 6022, + OpRayQueryGetIntersectionPrimitiveIndexKHR = 6023, + OpRayQueryGetIntersectionBarycentricsKHR = 6024, + OpRayQueryGetIntersectionFrontFaceKHR = 6025, + OpRayQueryGetIntersectionCandidateAABBOpaqueKHR = 6026, + OpRayQueryGetIntersectionObjectRayDirectionKHR = 6027, + OpRayQueryGetIntersectionObjectRayOriginKHR = 6028, + OpRayQueryGetWorldRayDirectionKHR = 6029, + OpRayQueryGetWorldRayOriginKHR = 6030, + OpRayQueryGetIntersectionObjectToWorldKHR = 6031, + OpRayQueryGetIntersectionWorldToObjectKHR = 6032, + OpAtomicFAddEXT = 6035, + OpTypeBufferSurfaceINTEL = 6086, + OpTypeStructContinuedINTEL = 6090, + OpConstantCompositeContinuedINTEL = 6091, + OpSpecConstantCompositeContinuedINTEL = 6092, + OpCompositeConstructContinuedINTEL = 6096, + OpConvertFToBF16INTEL = 6116, + OpConvertBF16ToFINTEL = 6117, + OpControlBarrierArriveINTEL = 6142, + OpControlBarrierWaitINTEL = 6143, + OpArithmeticFenceEXT = 6145, + OpTaskSequenceCreateINTEL = 6163, + OpTaskSequenceAsyncINTEL = 6164, + OpTaskSequenceGetINTEL = 6165, + OpTaskSequenceReleaseINTEL = 6166, + OpTypeTaskSequenceINTEL = 6199, + OpSubgroupBlockPrefetchINTEL = 6221, + OpSubgroup2DBlockLoadINTEL = 6231, + OpSubgroup2DBlockLoadTransformINTEL = 6232, + OpSubgroup2DBlockLoadTransposeINTEL = 6233, + OpSubgroup2DBlockPrefetchINTEL = 6234, + OpSubgroup2DBlockStoreINTEL = 6235, + OpSubgroupMatrixMultiplyAccumulateINTEL = 6237, + OpBitwiseFunctionINTEL = 6242, + OpGroupIMulKHR = 6401, + OpGroupFMulKHR = 6402, + OpGroupBitwiseAndKHR = 6403, + OpGroupBitwiseOrKHR = 6404, + OpGroupBitwiseXorKHR = 6405, + OpGroupLogicalAndKHR = 6406, + OpGroupLogicalOrKHR = 6407, + OpGroupLogicalXorKHR = 6408, + OpRoundFToTF32INTEL = 6426, + OpMaskedGatherINTEL = 6428, + OpMaskedScatterINTEL = 6429, + OpMax = 0x7fffffff, +}; + +#ifdef SPV_ENABLE_UTILITY_CODE +#ifndef __cplusplus +#include +#endif +inline void HasResultAndType(Op opcode, bool *hasResult, bool *hasResultType) { + *hasResult = *hasResultType = false; + switch (opcode) { + default: /* unknown opcode */ break; + case OpNop: *hasResult = false; *hasResultType = false; break; + case OpUndef: *hasResult = true; *hasResultType = true; break; + case OpSourceContinued: *hasResult = false; *hasResultType = false; break; + case OpSource: *hasResult = false; *hasResultType = false; break; + case OpSourceExtension: *hasResult = false; *hasResultType = false; break; + case OpName: *hasResult = false; *hasResultType = false; break; + case OpMemberName: *hasResult = false; *hasResultType = false; break; + case OpString: *hasResult = true; *hasResultType = false; break; + case OpLine: *hasResult = false; *hasResultType = false; break; + case OpExtension: *hasResult = false; *hasResultType = false; break; + case OpExtInstImport: *hasResult = true; *hasResultType = false; break; + case OpExtInst: *hasResult = true; *hasResultType = true; break; + case OpMemoryModel: *hasResult = false; *hasResultType = false; break; + case OpEntryPoint: *hasResult = false; *hasResultType = false; break; + case OpExecutionMode: *hasResult = false; *hasResultType = false; break; + case OpCapability: *hasResult = false; *hasResultType = false; break; + case OpTypeVoid: *hasResult = true; *hasResultType = false; break; + case OpTypeBool: *hasResult = true; *hasResultType = false; break; + case OpTypeInt: *hasResult = true; *hasResultType = false; break; + case OpTypeFloat: *hasResult = true; *hasResultType = false; break; + case OpTypeVector: *hasResult = true; *hasResultType = false; break; + case OpTypeMatrix: *hasResult = true; *hasResultType = false; break; + case OpTypeImage: *hasResult = true; *hasResultType = false; break; + case OpTypeSampler: *hasResult = true; *hasResultType = false; break; + case OpTypeSampledImage: *hasResult = true; *hasResultType = false; break; + case OpTypeArray: *hasResult = true; *hasResultType = false; break; + case OpTypeRuntimeArray: *hasResult = true; *hasResultType = false; break; + case OpTypeStruct: *hasResult = true; *hasResultType = false; break; + case OpTypeOpaque: *hasResult = true; *hasResultType = false; break; + case OpTypePointer: *hasResult = true; *hasResultType = false; break; + case OpTypeFunction: *hasResult = true; *hasResultType = false; break; + case OpTypeEvent: *hasResult = true; *hasResultType = false; break; + case OpTypeDeviceEvent: *hasResult = true; *hasResultType = false; break; + case OpTypeReserveId: *hasResult = true; *hasResultType = false; break; + case OpTypeQueue: *hasResult = true; *hasResultType = false; break; + case OpTypePipe: *hasResult = true; *hasResultType = false; break; + case OpTypeForwardPointer: *hasResult = false; *hasResultType = false; break; + case OpConstantTrue: *hasResult = true; *hasResultType = true; break; + case OpConstantFalse: *hasResult = true; *hasResultType = true; break; + case OpConstant: *hasResult = true; *hasResultType = true; break; + case OpConstantComposite: *hasResult = true; *hasResultType = true; break; + case OpConstantSampler: *hasResult = true; *hasResultType = true; break; + case OpConstantNull: *hasResult = true; *hasResultType = true; break; + case OpSpecConstantTrue: *hasResult = true; *hasResultType = true; break; + case OpSpecConstantFalse: *hasResult = true; *hasResultType = true; break; + case OpSpecConstant: *hasResult = true; *hasResultType = true; break; + case OpSpecConstantComposite: *hasResult = true; *hasResultType = true; break; + case OpSpecConstantOp: *hasResult = true; *hasResultType = true; break; + case OpFunction: *hasResult = true; *hasResultType = true; break; + case OpFunctionParameter: *hasResult = true; *hasResultType = true; break; + case OpFunctionEnd: *hasResult = false; *hasResultType = false; break; + case OpFunctionCall: *hasResult = true; *hasResultType = true; break; + case OpVariable: *hasResult = true; *hasResultType = true; break; + case OpImageTexelPointer: *hasResult = true; *hasResultType = true; break; + case OpLoad: *hasResult = true; *hasResultType = true; break; + case OpStore: *hasResult = false; *hasResultType = false; break; + case OpCopyMemory: *hasResult = false; *hasResultType = false; break; + case OpCopyMemorySized: *hasResult = false; *hasResultType = false; break; + case OpAccessChain: *hasResult = true; *hasResultType = true; break; + case OpInBoundsAccessChain: *hasResult = true; *hasResultType = true; break; + case OpPtrAccessChain: *hasResult = true; *hasResultType = true; break; + case OpArrayLength: *hasResult = true; *hasResultType = true; break; + case OpGenericPtrMemSemantics: *hasResult = true; *hasResultType = true; break; + case OpInBoundsPtrAccessChain: *hasResult = true; *hasResultType = true; break; + case OpDecorate: *hasResult = false; *hasResultType = false; break; + case OpMemberDecorate: *hasResult = false; *hasResultType = false; break; + case OpDecorationGroup: *hasResult = true; *hasResultType = false; break; + case OpGroupDecorate: *hasResult = false; *hasResultType = false; break; + case OpGroupMemberDecorate: *hasResult = false; *hasResultType = false; break; + case OpVectorExtractDynamic: *hasResult = true; *hasResultType = true; break; + case OpVectorInsertDynamic: *hasResult = true; *hasResultType = true; break; + case OpVectorShuffle: *hasResult = true; *hasResultType = true; break; + case OpCompositeConstruct: *hasResult = true; *hasResultType = true; break; + case OpCompositeExtract: *hasResult = true; *hasResultType = true; break; + case OpCompositeInsert: *hasResult = true; *hasResultType = true; break; + case OpCopyObject: *hasResult = true; *hasResultType = true; break; + case OpTranspose: *hasResult = true; *hasResultType = true; break; + case OpSampledImage: *hasResult = true; *hasResultType = true; break; + case OpImageSampleImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSampleExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSampleDrefImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSampleDrefExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSampleProjImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSampleProjExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSampleProjDrefImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSampleProjDrefExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageFetch: *hasResult = true; *hasResultType = true; break; + case OpImageGather: *hasResult = true; *hasResultType = true; break; + case OpImageDrefGather: *hasResult = true; *hasResultType = true; break; + case OpImageRead: *hasResult = true; *hasResultType = true; break; + case OpImageWrite: *hasResult = false; *hasResultType = false; break; + case OpImage: *hasResult = true; *hasResultType = true; break; + case OpImageQueryFormat: *hasResult = true; *hasResultType = true; break; + case OpImageQueryOrder: *hasResult = true; *hasResultType = true; break; + case OpImageQuerySizeLod: *hasResult = true; *hasResultType = true; break; + case OpImageQuerySize: *hasResult = true; *hasResultType = true; break; + case OpImageQueryLod: *hasResult = true; *hasResultType = true; break; + case OpImageQueryLevels: *hasResult = true; *hasResultType = true; break; + case OpImageQuerySamples: *hasResult = true; *hasResultType = true; break; + case OpConvertFToU: *hasResult = true; *hasResultType = true; break; + case OpConvertFToS: *hasResult = true; *hasResultType = true; break; + case OpConvertSToF: *hasResult = true; *hasResultType = true; break; + case OpConvertUToF: *hasResult = true; *hasResultType = true; break; + case OpUConvert: *hasResult = true; *hasResultType = true; break; + case OpSConvert: *hasResult = true; *hasResultType = true; break; + case OpFConvert: *hasResult = true; *hasResultType = true; break; + case OpQuantizeToF16: *hasResult = true; *hasResultType = true; break; + case OpConvertPtrToU: *hasResult = true; *hasResultType = true; break; + case OpSatConvertSToU: *hasResult = true; *hasResultType = true; break; + case OpSatConvertUToS: *hasResult = true; *hasResultType = true; break; + case OpConvertUToPtr: *hasResult = true; *hasResultType = true; break; + case OpPtrCastToGeneric: *hasResult = true; *hasResultType = true; break; + case OpGenericCastToPtr: *hasResult = true; *hasResultType = true; break; + case OpGenericCastToPtrExplicit: *hasResult = true; *hasResultType = true; break; + case OpBitcast: *hasResult = true; *hasResultType = true; break; + case OpSNegate: *hasResult = true; *hasResultType = true; break; + case OpFNegate: *hasResult = true; *hasResultType = true; break; + case OpIAdd: *hasResult = true; *hasResultType = true; break; + case OpFAdd: *hasResult = true; *hasResultType = true; break; + case OpISub: *hasResult = true; *hasResultType = true; break; + case OpFSub: *hasResult = true; *hasResultType = true; break; + case OpIMul: *hasResult = true; *hasResultType = true; break; + case OpFMul: *hasResult = true; *hasResultType = true; break; + case OpUDiv: *hasResult = true; *hasResultType = true; break; + case OpSDiv: *hasResult = true; *hasResultType = true; break; + case OpFDiv: *hasResult = true; *hasResultType = true; break; + case OpUMod: *hasResult = true; *hasResultType = true; break; + case OpSRem: *hasResult = true; *hasResultType = true; break; + case OpSMod: *hasResult = true; *hasResultType = true; break; + case OpFRem: *hasResult = true; *hasResultType = true; break; + case OpFMod: *hasResult = true; *hasResultType = true; break; + case OpVectorTimesScalar: *hasResult = true; *hasResultType = true; break; + case OpMatrixTimesScalar: *hasResult = true; *hasResultType = true; break; + case OpVectorTimesMatrix: *hasResult = true; *hasResultType = true; break; + case OpMatrixTimesVector: *hasResult = true; *hasResultType = true; break; + case OpMatrixTimesMatrix: *hasResult = true; *hasResultType = true; break; + case OpOuterProduct: *hasResult = true; *hasResultType = true; break; + case OpDot: *hasResult = true; *hasResultType = true; break; + case OpIAddCarry: *hasResult = true; *hasResultType = true; break; + case OpISubBorrow: *hasResult = true; *hasResultType = true; break; + case OpUMulExtended: *hasResult = true; *hasResultType = true; break; + case OpSMulExtended: *hasResult = true; *hasResultType = true; break; + case OpAny: *hasResult = true; *hasResultType = true; break; + case OpAll: *hasResult = true; *hasResultType = true; break; + case OpIsNan: *hasResult = true; *hasResultType = true; break; + case OpIsInf: *hasResult = true; *hasResultType = true; break; + case OpIsFinite: *hasResult = true; *hasResultType = true; break; + case OpIsNormal: *hasResult = true; *hasResultType = true; break; + case OpSignBitSet: *hasResult = true; *hasResultType = true; break; + case OpLessOrGreater: *hasResult = true; *hasResultType = true; break; + case OpOrdered: *hasResult = true; *hasResultType = true; break; + case OpUnordered: *hasResult = true; *hasResultType = true; break; + case OpLogicalEqual: *hasResult = true; *hasResultType = true; break; + case OpLogicalNotEqual: *hasResult = true; *hasResultType = true; break; + case OpLogicalOr: *hasResult = true; *hasResultType = true; break; + case OpLogicalAnd: *hasResult = true; *hasResultType = true; break; + case OpLogicalNot: *hasResult = true; *hasResultType = true; break; + case OpSelect: *hasResult = true; *hasResultType = true; break; + case OpIEqual: *hasResult = true; *hasResultType = true; break; + case OpINotEqual: *hasResult = true; *hasResultType = true; break; + case OpUGreaterThan: *hasResult = true; *hasResultType = true; break; + case OpSGreaterThan: *hasResult = true; *hasResultType = true; break; + case OpUGreaterThanEqual: *hasResult = true; *hasResultType = true; break; + case OpSGreaterThanEqual: *hasResult = true; *hasResultType = true; break; + case OpULessThan: *hasResult = true; *hasResultType = true; break; + case OpSLessThan: *hasResult = true; *hasResultType = true; break; + case OpULessThanEqual: *hasResult = true; *hasResultType = true; break; + case OpSLessThanEqual: *hasResult = true; *hasResultType = true; break; + case OpFOrdEqual: *hasResult = true; *hasResultType = true; break; + case OpFUnordEqual: *hasResult = true; *hasResultType = true; break; + case OpFOrdNotEqual: *hasResult = true; *hasResultType = true; break; + case OpFUnordNotEqual: *hasResult = true; *hasResultType = true; break; + case OpFOrdLessThan: *hasResult = true; *hasResultType = true; break; + case OpFUnordLessThan: *hasResult = true; *hasResultType = true; break; + case OpFOrdGreaterThan: *hasResult = true; *hasResultType = true; break; + case OpFUnordGreaterThan: *hasResult = true; *hasResultType = true; break; + case OpFOrdLessThanEqual: *hasResult = true; *hasResultType = true; break; + case OpFUnordLessThanEqual: *hasResult = true; *hasResultType = true; break; + case OpFOrdGreaterThanEqual: *hasResult = true; *hasResultType = true; break; + case OpFUnordGreaterThanEqual: *hasResult = true; *hasResultType = true; break; + case OpShiftRightLogical: *hasResult = true; *hasResultType = true; break; + case OpShiftRightArithmetic: *hasResult = true; *hasResultType = true; break; + case OpShiftLeftLogical: *hasResult = true; *hasResultType = true; break; + case OpBitwiseOr: *hasResult = true; *hasResultType = true; break; + case OpBitwiseXor: *hasResult = true; *hasResultType = true; break; + case OpBitwiseAnd: *hasResult = true; *hasResultType = true; break; + case OpNot: *hasResult = true; *hasResultType = true; break; + case OpBitFieldInsert: *hasResult = true; *hasResultType = true; break; + case OpBitFieldSExtract: *hasResult = true; *hasResultType = true; break; + case OpBitFieldUExtract: *hasResult = true; *hasResultType = true; break; + case OpBitReverse: *hasResult = true; *hasResultType = true; break; + case OpBitCount: *hasResult = true; *hasResultType = true; break; + case OpDPdx: *hasResult = true; *hasResultType = true; break; + case OpDPdy: *hasResult = true; *hasResultType = true; break; + case OpFwidth: *hasResult = true; *hasResultType = true; break; + case OpDPdxFine: *hasResult = true; *hasResultType = true; break; + case OpDPdyFine: *hasResult = true; *hasResultType = true; break; + case OpFwidthFine: *hasResult = true; *hasResultType = true; break; + case OpDPdxCoarse: *hasResult = true; *hasResultType = true; break; + case OpDPdyCoarse: *hasResult = true; *hasResultType = true; break; + case OpFwidthCoarse: *hasResult = true; *hasResultType = true; break; + case OpEmitVertex: *hasResult = false; *hasResultType = false; break; + case OpEndPrimitive: *hasResult = false; *hasResultType = false; break; + case OpEmitStreamVertex: *hasResult = false; *hasResultType = false; break; + case OpEndStreamPrimitive: *hasResult = false; *hasResultType = false; break; + case OpControlBarrier: *hasResult = false; *hasResultType = false; break; + case OpMemoryBarrier: *hasResult = false; *hasResultType = false; break; + case OpAtomicLoad: *hasResult = true; *hasResultType = true; break; + case OpAtomicStore: *hasResult = false; *hasResultType = false; break; + case OpAtomicExchange: *hasResult = true; *hasResultType = true; break; + case OpAtomicCompareExchange: *hasResult = true; *hasResultType = true; break; + case OpAtomicCompareExchangeWeak: *hasResult = true; *hasResultType = true; break; + case OpAtomicIIncrement: *hasResult = true; *hasResultType = true; break; + case OpAtomicIDecrement: *hasResult = true; *hasResultType = true; break; + case OpAtomicIAdd: *hasResult = true; *hasResultType = true; break; + case OpAtomicISub: *hasResult = true; *hasResultType = true; break; + case OpAtomicSMin: *hasResult = true; *hasResultType = true; break; + case OpAtomicUMin: *hasResult = true; *hasResultType = true; break; + case OpAtomicSMax: *hasResult = true; *hasResultType = true; break; + case OpAtomicUMax: *hasResult = true; *hasResultType = true; break; + case OpAtomicAnd: *hasResult = true; *hasResultType = true; break; + case OpAtomicOr: *hasResult = true; *hasResultType = true; break; + case OpAtomicXor: *hasResult = true; *hasResultType = true; break; + case OpPhi: *hasResult = true; *hasResultType = true; break; + case OpLoopMerge: *hasResult = false; *hasResultType = false; break; + case OpSelectionMerge: *hasResult = false; *hasResultType = false; break; + case OpLabel: *hasResult = true; *hasResultType = false; break; + case OpBranch: *hasResult = false; *hasResultType = false; break; + case OpBranchConditional: *hasResult = false; *hasResultType = false; break; + case OpSwitch: *hasResult = false; *hasResultType = false; break; + case OpKill: *hasResult = false; *hasResultType = false; break; + case OpReturn: *hasResult = false; *hasResultType = false; break; + case OpReturnValue: *hasResult = false; *hasResultType = false; break; + case OpUnreachable: *hasResult = false; *hasResultType = false; break; + case OpLifetimeStart: *hasResult = false; *hasResultType = false; break; + case OpLifetimeStop: *hasResult = false; *hasResultType = false; break; + case OpGroupAsyncCopy: *hasResult = true; *hasResultType = true; break; + case OpGroupWaitEvents: *hasResult = false; *hasResultType = false; break; + case OpGroupAll: *hasResult = true; *hasResultType = true; break; + case OpGroupAny: *hasResult = true; *hasResultType = true; break; + case OpGroupBroadcast: *hasResult = true; *hasResultType = true; break; + case OpGroupIAdd: *hasResult = true; *hasResultType = true; break; + case OpGroupFAdd: *hasResult = true; *hasResultType = true; break; + case OpGroupFMin: *hasResult = true; *hasResultType = true; break; + case OpGroupUMin: *hasResult = true; *hasResultType = true; break; + case OpGroupSMin: *hasResult = true; *hasResultType = true; break; + case OpGroupFMax: *hasResult = true; *hasResultType = true; break; + case OpGroupUMax: *hasResult = true; *hasResultType = true; break; + case OpGroupSMax: *hasResult = true; *hasResultType = true; break; + case OpReadPipe: *hasResult = true; *hasResultType = true; break; + case OpWritePipe: *hasResult = true; *hasResultType = true; break; + case OpReservedReadPipe: *hasResult = true; *hasResultType = true; break; + case OpReservedWritePipe: *hasResult = true; *hasResultType = true; break; + case OpReserveReadPipePackets: *hasResult = true; *hasResultType = true; break; + case OpReserveWritePipePackets: *hasResult = true; *hasResultType = true; break; + case OpCommitReadPipe: *hasResult = false; *hasResultType = false; break; + case OpCommitWritePipe: *hasResult = false; *hasResultType = false; break; + case OpIsValidReserveId: *hasResult = true; *hasResultType = true; break; + case OpGetNumPipePackets: *hasResult = true; *hasResultType = true; break; + case OpGetMaxPipePackets: *hasResult = true; *hasResultType = true; break; + case OpGroupReserveReadPipePackets: *hasResult = true; *hasResultType = true; break; + case OpGroupReserveWritePipePackets: *hasResult = true; *hasResultType = true; break; + case OpGroupCommitReadPipe: *hasResult = false; *hasResultType = false; break; + case OpGroupCommitWritePipe: *hasResult = false; *hasResultType = false; break; + case OpEnqueueMarker: *hasResult = true; *hasResultType = true; break; + case OpEnqueueKernel: *hasResult = true; *hasResultType = true; break; + case OpGetKernelNDrangeSubGroupCount: *hasResult = true; *hasResultType = true; break; + case OpGetKernelNDrangeMaxSubGroupSize: *hasResult = true; *hasResultType = true; break; + case OpGetKernelWorkGroupSize: *hasResult = true; *hasResultType = true; break; + case OpGetKernelPreferredWorkGroupSizeMultiple: *hasResult = true; *hasResultType = true; break; + case OpRetainEvent: *hasResult = false; *hasResultType = false; break; + case OpReleaseEvent: *hasResult = false; *hasResultType = false; break; + case OpCreateUserEvent: *hasResult = true; *hasResultType = true; break; + case OpIsValidEvent: *hasResult = true; *hasResultType = true; break; + case OpSetUserEventStatus: *hasResult = false; *hasResultType = false; break; + case OpCaptureEventProfilingInfo: *hasResult = false; *hasResultType = false; break; + case OpGetDefaultQueue: *hasResult = true; *hasResultType = true; break; + case OpBuildNDRange: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleDrefImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleDrefExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleProjImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleProjExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleProjDrefImplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseSampleProjDrefExplicitLod: *hasResult = true; *hasResultType = true; break; + case OpImageSparseFetch: *hasResult = true; *hasResultType = true; break; + case OpImageSparseGather: *hasResult = true; *hasResultType = true; break; + case OpImageSparseDrefGather: *hasResult = true; *hasResultType = true; break; + case OpImageSparseTexelsResident: *hasResult = true; *hasResultType = true; break; + case OpNoLine: *hasResult = false; *hasResultType = false; break; + case OpAtomicFlagTestAndSet: *hasResult = true; *hasResultType = true; break; + case OpAtomicFlagClear: *hasResult = false; *hasResultType = false; break; + case OpImageSparseRead: *hasResult = true; *hasResultType = true; break; + case OpSizeOf: *hasResult = true; *hasResultType = true; break; + case OpTypePipeStorage: *hasResult = true; *hasResultType = false; break; + case OpConstantPipeStorage: *hasResult = true; *hasResultType = true; break; + case OpCreatePipeFromPipeStorage: *hasResult = true; *hasResultType = true; break; + case OpGetKernelLocalSizeForSubgroupCount: *hasResult = true; *hasResultType = true; break; + case OpGetKernelMaxNumSubgroups: *hasResult = true; *hasResultType = true; break; + case OpTypeNamedBarrier: *hasResult = true; *hasResultType = false; break; + case OpNamedBarrierInitialize: *hasResult = true; *hasResultType = true; break; + case OpMemoryNamedBarrier: *hasResult = false; *hasResultType = false; break; + case OpModuleProcessed: *hasResult = false; *hasResultType = false; break; + case OpExecutionModeId: *hasResult = false; *hasResultType = false; break; + case OpDecorateId: *hasResult = false; *hasResultType = false; break; + case OpGroupNonUniformElect: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformAll: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformAny: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformAllEqual: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBroadcast: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBroadcastFirst: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBallot: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformInverseBallot: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBallotBitExtract: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBallotBitCount: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBallotFindLSB: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBallotFindMSB: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformShuffle: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformShuffleXor: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformShuffleUp: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformShuffleDown: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformIAdd: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformFAdd: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformIMul: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformFMul: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformSMin: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformUMin: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformFMin: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformSMax: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformUMax: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformFMax: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBitwiseAnd: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBitwiseOr: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformBitwiseXor: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformLogicalAnd: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformLogicalOr: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformLogicalXor: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformQuadBroadcast: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformQuadSwap: *hasResult = true; *hasResultType = true; break; + case OpCopyLogical: *hasResult = true; *hasResultType = true; break; + case OpPtrEqual: *hasResult = true; *hasResultType = true; break; + case OpPtrNotEqual: *hasResult = true; *hasResultType = true; break; + case OpPtrDiff: *hasResult = true; *hasResultType = true; break; + case OpColorAttachmentReadEXT: *hasResult = true; *hasResultType = true; break; + case OpDepthAttachmentReadEXT: *hasResult = true; *hasResultType = true; break; + case OpStencilAttachmentReadEXT: *hasResult = true; *hasResultType = true; break; + case OpTerminateInvocation: *hasResult = false; *hasResultType = false; break; + case OpTypeUntypedPointerKHR: *hasResult = true; *hasResultType = false; break; + case OpUntypedVariableKHR: *hasResult = true; *hasResultType = true; break; + case OpUntypedAccessChainKHR: *hasResult = true; *hasResultType = true; break; + case OpUntypedInBoundsAccessChainKHR: *hasResult = true; *hasResultType = true; break; + case OpSubgroupBallotKHR: *hasResult = true; *hasResultType = true; break; + case OpSubgroupFirstInvocationKHR: *hasResult = true; *hasResultType = true; break; + case OpUntypedPtrAccessChainKHR: *hasResult = true; *hasResultType = true; break; + case OpUntypedInBoundsPtrAccessChainKHR: *hasResult = true; *hasResultType = true; break; + case OpUntypedArrayLengthKHR: *hasResult = true; *hasResultType = true; break; + case OpUntypedPrefetchKHR: *hasResult = false; *hasResultType = false; break; + case OpSubgroupAllKHR: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAnyKHR: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAllEqualKHR: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformRotateKHR: *hasResult = true; *hasResultType = true; break; + case OpSubgroupReadInvocationKHR: *hasResult = true; *hasResultType = true; break; + case OpExtInstWithForwardRefsKHR: *hasResult = true; *hasResultType = true; break; + case OpTraceRayKHR: *hasResult = false; *hasResultType = false; break; + case OpExecuteCallableKHR: *hasResult = false; *hasResultType = false; break; + case OpConvertUToAccelerationStructureKHR: *hasResult = true; *hasResultType = true; break; + case OpIgnoreIntersectionKHR: *hasResult = false; *hasResultType = false; break; + case OpTerminateRayKHR: *hasResult = false; *hasResultType = false; break; + case OpSDot: *hasResult = true; *hasResultType = true; break; + case OpUDot: *hasResult = true; *hasResultType = true; break; + case OpSUDot: *hasResult = true; *hasResultType = true; break; + case OpSDotAccSat: *hasResult = true; *hasResultType = true; break; + case OpUDotAccSat: *hasResult = true; *hasResultType = true; break; + case OpSUDotAccSat: *hasResult = true; *hasResultType = true; break; + case OpTypeCooperativeMatrixKHR: *hasResult = true; *hasResultType = false; break; + case OpCooperativeMatrixLoadKHR: *hasResult = true; *hasResultType = true; break; + case OpCooperativeMatrixStoreKHR: *hasResult = false; *hasResultType = false; break; + case OpCooperativeMatrixMulAddKHR: *hasResult = true; *hasResultType = true; break; + case OpCooperativeMatrixLengthKHR: *hasResult = true; *hasResultType = true; break; + case OpConstantCompositeReplicateEXT: *hasResult = true; *hasResultType = true; break; + case OpSpecConstantCompositeReplicateEXT: *hasResult = true; *hasResultType = true; break; + case OpCompositeConstructReplicateEXT: *hasResult = true; *hasResultType = true; break; + case OpTypeRayQueryKHR: *hasResult = true; *hasResultType = false; break; + case OpRayQueryInitializeKHR: *hasResult = false; *hasResultType = false; break; + case OpRayQueryTerminateKHR: *hasResult = false; *hasResultType = false; break; + case OpRayQueryGenerateIntersectionKHR: *hasResult = false; *hasResultType = false; break; + case OpRayQueryConfirmIntersectionKHR: *hasResult = false; *hasResultType = false; break; + case OpRayQueryProceedKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionTypeKHR: *hasResult = true; *hasResultType = true; break; + case OpImageSampleWeightedQCOM: *hasResult = true; *hasResultType = true; break; + case OpImageBoxFilterQCOM: *hasResult = true; *hasResultType = true; break; + case OpImageBlockMatchSSDQCOM: *hasResult = true; *hasResultType = true; break; + case OpImageBlockMatchSADQCOM: *hasResult = true; *hasResultType = true; break; + case OpImageBlockMatchWindowSSDQCOM: *hasResult = true; *hasResultType = true; break; + case OpImageBlockMatchWindowSADQCOM: *hasResult = true; *hasResultType = true; break; + case OpImageBlockMatchGatherSSDQCOM: *hasResult = true; *hasResultType = true; break; + case OpImageBlockMatchGatherSADQCOM: *hasResult = true; *hasResultType = true; break; + case OpGroupIAddNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpGroupFAddNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpGroupFMinNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpGroupUMinNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpGroupSMinNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpGroupFMaxNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpGroupUMaxNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpGroupSMaxNonUniformAMD: *hasResult = true; *hasResultType = true; break; + case OpFragmentMaskFetchAMD: *hasResult = true; *hasResultType = true; break; + case OpFragmentFetchAMD: *hasResult = true; *hasResultType = true; break; + case OpReadClockKHR: *hasResult = true; *hasResultType = true; break; + case OpAllocateNodePayloadsAMDX: *hasResult = true; *hasResultType = true; break; + case OpEnqueueNodePayloadsAMDX: *hasResult = false; *hasResultType = false; break; + case OpTypeNodePayloadArrayAMDX: *hasResult = true; *hasResultType = false; break; + case OpFinishWritingNodePayloadAMDX: *hasResult = true; *hasResultType = true; break; + case OpNodePayloadArrayLengthAMDX: *hasResult = true; *hasResultType = true; break; + case OpIsNodePayloadValidAMDX: *hasResult = true; *hasResultType = true; break; + case OpConstantStringAMDX: *hasResult = true; *hasResultType = false; break; + case OpSpecConstantStringAMDX: *hasResult = true; *hasResultType = false; break; + case OpGroupNonUniformQuadAllKHR: *hasResult = true; *hasResultType = true; break; + case OpGroupNonUniformQuadAnyKHR: *hasResult = true; *hasResultType = true; break; + case OpHitObjectRecordHitMotionNV: *hasResult = false; *hasResultType = false; break; + case OpHitObjectRecordHitWithIndexMotionNV: *hasResult = false; *hasResultType = false; break; + case OpHitObjectRecordMissMotionNV: *hasResult = false; *hasResultType = false; break; + case OpHitObjectGetWorldToObjectNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetObjectToWorldNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetObjectRayDirectionNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetObjectRayOriginNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectTraceRayMotionNV: *hasResult = false; *hasResultType = false; break; + case OpHitObjectGetShaderRecordBufferHandleNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetShaderBindingTableRecordIndexNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectRecordEmptyNV: *hasResult = false; *hasResultType = false; break; + case OpHitObjectTraceRayNV: *hasResult = false; *hasResultType = false; break; + case OpHitObjectRecordHitNV: *hasResult = false; *hasResultType = false; break; + case OpHitObjectRecordHitWithIndexNV: *hasResult = false; *hasResultType = false; break; + case OpHitObjectRecordMissNV: *hasResult = false; *hasResultType = false; break; + case OpHitObjectExecuteShaderNV: *hasResult = false; *hasResultType = false; break; + case OpHitObjectGetCurrentTimeNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetAttributesNV: *hasResult = false; *hasResultType = false; break; + case OpHitObjectGetHitKindNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetPrimitiveIndexNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetGeometryIndexNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetInstanceIdNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetInstanceCustomIndexNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetWorldRayDirectionNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetWorldRayOriginNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetRayTMaxNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetRayTMinNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectIsEmptyNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectIsHitNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectIsMissNV: *hasResult = true; *hasResultType = true; break; + case OpReorderThreadWithHitObjectNV: *hasResult = false; *hasResultType = false; break; + case OpReorderThreadWithHintNV: *hasResult = false; *hasResultType = false; break; + case OpTypeHitObjectNV: *hasResult = true; *hasResultType = false; break; + case OpImageSampleFootprintNV: *hasResult = true; *hasResultType = true; break; + case OpTypeCooperativeVectorNV: *hasResult = true; *hasResultType = false; break; + case OpCooperativeVectorMatrixMulNV: *hasResult = true; *hasResultType = true; break; + case OpCooperativeVectorOuterProductAccumulateNV: *hasResult = false; *hasResultType = false; break; + case OpCooperativeVectorReduceSumAccumulateNV: *hasResult = false; *hasResultType = false; break; + case OpCooperativeVectorMatrixMulAddNV: *hasResult = true; *hasResultType = true; break; + case OpCooperativeMatrixConvertNV: *hasResult = true; *hasResultType = true; break; + case OpEmitMeshTasksEXT: *hasResult = false; *hasResultType = false; break; + case OpSetMeshOutputsEXT: *hasResult = false; *hasResultType = false; break; + case OpGroupNonUniformPartitionNV: *hasResult = true; *hasResultType = true; break; + case OpWritePackedPrimitiveIndices4x8NV: *hasResult = false; *hasResultType = false; break; + case OpFetchMicroTriangleVertexPositionNV: *hasResult = true; *hasResultType = true; break; + case OpFetchMicroTriangleVertexBarycentricNV: *hasResult = true; *hasResultType = true; break; + case OpCooperativeVectorLoadNV: *hasResult = true; *hasResultType = true; break; + case OpCooperativeVectorStoreNV: *hasResult = false; *hasResultType = false; break; + case OpReportIntersectionKHR: *hasResult = true; *hasResultType = true; break; + case OpIgnoreIntersectionNV: *hasResult = false; *hasResultType = false; break; + case OpTerminateRayNV: *hasResult = false; *hasResultType = false; break; + case OpTraceNV: *hasResult = false; *hasResultType = false; break; + case OpTraceMotionNV: *hasResult = false; *hasResultType = false; break; + case OpTraceRayMotionNV: *hasResult = false; *hasResultType = false; break; + case OpRayQueryGetIntersectionTriangleVertexPositionsKHR: *hasResult = true; *hasResultType = true; break; + case OpTypeAccelerationStructureKHR: *hasResult = true; *hasResultType = false; break; + case OpExecuteCallableNV: *hasResult = false; *hasResultType = false; break; + case OpRayQueryGetClusterIdNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetClusterIdNV: *hasResult = true; *hasResultType = true; break; + case OpTypeCooperativeMatrixNV: *hasResult = true; *hasResultType = false; break; + case OpCooperativeMatrixLoadNV: *hasResult = true; *hasResultType = true; break; + case OpCooperativeMatrixStoreNV: *hasResult = false; *hasResultType = false; break; + case OpCooperativeMatrixMulAddNV: *hasResult = true; *hasResultType = true; break; + case OpCooperativeMatrixLengthNV: *hasResult = true; *hasResultType = true; break; + case OpBeginInvocationInterlockEXT: *hasResult = false; *hasResultType = false; break; + case OpEndInvocationInterlockEXT: *hasResult = false; *hasResultType = false; break; + case OpCooperativeMatrixReduceNV: *hasResult = true; *hasResultType = true; break; + case OpCooperativeMatrixLoadTensorNV: *hasResult = true; *hasResultType = true; break; + case OpCooperativeMatrixStoreTensorNV: *hasResult = false; *hasResultType = false; break; + case OpCooperativeMatrixPerElementOpNV: *hasResult = true; *hasResultType = true; break; + case OpTypeTensorLayoutNV: *hasResult = true; *hasResultType = false; break; + case OpTypeTensorViewNV: *hasResult = true; *hasResultType = false; break; + case OpCreateTensorLayoutNV: *hasResult = true; *hasResultType = true; break; + case OpTensorLayoutSetDimensionNV: *hasResult = true; *hasResultType = true; break; + case OpTensorLayoutSetStrideNV: *hasResult = true; *hasResultType = true; break; + case OpTensorLayoutSliceNV: *hasResult = true; *hasResultType = true; break; + case OpTensorLayoutSetClampValueNV: *hasResult = true; *hasResultType = true; break; + case OpCreateTensorViewNV: *hasResult = true; *hasResultType = true; break; + case OpTensorViewSetDimensionNV: *hasResult = true; *hasResultType = true; break; + case OpTensorViewSetStrideNV: *hasResult = true; *hasResultType = true; break; + case OpDemoteToHelperInvocation: *hasResult = false; *hasResultType = false; break; + case OpIsHelperInvocationEXT: *hasResult = true; *hasResultType = true; break; + case OpTensorViewSetClipNV: *hasResult = true; *hasResultType = true; break; + case OpTensorLayoutSetBlockSizeNV: *hasResult = true; *hasResultType = true; break; + case OpCooperativeMatrixTransposeNV: *hasResult = true; *hasResultType = true; break; + case OpConvertUToImageNV: *hasResult = true; *hasResultType = true; break; + case OpConvertUToSamplerNV: *hasResult = true; *hasResultType = true; break; + case OpConvertImageToUNV: *hasResult = true; *hasResultType = true; break; + case OpConvertSamplerToUNV: *hasResult = true; *hasResultType = true; break; + case OpConvertUToSampledImageNV: *hasResult = true; *hasResultType = true; break; + case OpConvertSampledImageToUNV: *hasResult = true; *hasResultType = true; break; + case OpSamplerImageAddressingModeNV: *hasResult = false; *hasResultType = false; break; + case OpRawAccessChainNV: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionSpherePositionNV: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionSphereRadiusNV: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionLSSPositionsNV: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionLSSRadiiNV: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionLSSHitValueNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetSpherePositionNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetSphereRadiusNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetLSSPositionsNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectGetLSSRadiiNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectIsSphereHitNV: *hasResult = true; *hasResultType = true; break; + case OpHitObjectIsLSSHitNV: *hasResult = true; *hasResultType = true; break; + case OpRayQueryIsSphereHitNV: *hasResult = true; *hasResultType = true; break; + case OpRayQueryIsLSSHitNV: *hasResult = true; *hasResultType = true; break; + case OpSubgroupShuffleINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupShuffleDownINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupShuffleUpINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupShuffleXorINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupBlockReadINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupBlockWriteINTEL: *hasResult = false; *hasResultType = false; break; + case OpSubgroupImageBlockReadINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupImageBlockWriteINTEL: *hasResult = false; *hasResultType = false; break; + case OpSubgroupImageMediaBlockReadINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupImageMediaBlockWriteINTEL: *hasResult = false; *hasResultType = false; break; + case OpUCountLeadingZerosINTEL: *hasResult = true; *hasResultType = true; break; + case OpUCountTrailingZerosINTEL: *hasResult = true; *hasResultType = true; break; + case OpAbsISubINTEL: *hasResult = true; *hasResultType = true; break; + case OpAbsUSubINTEL: *hasResult = true; *hasResultType = true; break; + case OpIAddSatINTEL: *hasResult = true; *hasResultType = true; break; + case OpUAddSatINTEL: *hasResult = true; *hasResultType = true; break; + case OpIAverageINTEL: *hasResult = true; *hasResultType = true; break; + case OpUAverageINTEL: *hasResult = true; *hasResultType = true; break; + case OpIAverageRoundedINTEL: *hasResult = true; *hasResultType = true; break; + case OpUAverageRoundedINTEL: *hasResult = true; *hasResultType = true; break; + case OpISubSatINTEL: *hasResult = true; *hasResultType = true; break; + case OpUSubSatINTEL: *hasResult = true; *hasResultType = true; break; + case OpIMul32x16INTEL: *hasResult = true; *hasResultType = true; break; + case OpUMul32x16INTEL: *hasResult = true; *hasResultType = true; break; + case OpConstantFunctionPointerINTEL: *hasResult = true; *hasResultType = true; break; + case OpFunctionPointerCallINTEL: *hasResult = true; *hasResultType = true; break; + case OpAsmTargetINTEL: *hasResult = true; *hasResultType = false; break; + case OpAsmINTEL: *hasResult = true; *hasResultType = true; break; + case OpAsmCallINTEL: *hasResult = true; *hasResultType = true; break; + case OpAtomicFMinEXT: *hasResult = true; *hasResultType = true; break; + case OpAtomicFMaxEXT: *hasResult = true; *hasResultType = true; break; + case OpAssumeTrueKHR: *hasResult = false; *hasResultType = false; break; + case OpExpectKHR: *hasResult = true; *hasResultType = true; break; + case OpDecorateString: *hasResult = false; *hasResultType = false; break; + case OpMemberDecorateString: *hasResult = false; *hasResultType = false; break; + case OpVmeImageINTEL: *hasResult = true; *hasResultType = true; break; + case OpTypeVmeImageINTEL: *hasResult = true; *hasResultType = false; break; + case OpTypeAvcImePayloadINTEL: *hasResult = true; *hasResultType = false; break; + case OpTypeAvcRefPayloadINTEL: *hasResult = true; *hasResultType = false; break; + case OpTypeAvcSicPayloadINTEL: *hasResult = true; *hasResultType = false; break; + case OpTypeAvcMcePayloadINTEL: *hasResult = true; *hasResultType = false; break; + case OpTypeAvcMceResultINTEL: *hasResult = true; *hasResultType = false; break; + case OpTypeAvcImeResultINTEL: *hasResult = true; *hasResultType = false; break; + case OpTypeAvcImeResultSingleReferenceStreamoutINTEL: *hasResult = true; *hasResultType = false; break; + case OpTypeAvcImeResultDualReferenceStreamoutINTEL: *hasResult = true; *hasResultType = false; break; + case OpTypeAvcImeSingleReferenceStreaminINTEL: *hasResult = true; *hasResultType = false; break; + case OpTypeAvcImeDualReferenceStreaminINTEL: *hasResult = true; *hasResultType = false; break; + case OpTypeAvcRefResultINTEL: *hasResult = true; *hasResultType = false; break; + case OpTypeAvcSicResultINTEL: *hasResult = true; *hasResultType = false; break; + case OpSubgroupAvcMceGetDefaultInterBaseMultiReferencePenaltyINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceSetInterBaseMultiReferencePenaltyINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetDefaultInterShapePenaltyINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceSetInterShapePenaltyINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetDefaultInterDirectionPenaltyINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceSetInterDirectionPenaltyINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetDefaultIntraLumaShapePenaltyINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetDefaultInterMotionVectorCostTableINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetDefaultHighPenaltyCostTableINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetDefaultMediumPenaltyCostTableINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetDefaultLowPenaltyCostTableINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceSetMotionVectorCostFunctionINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetDefaultIntraLumaModePenaltyINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetDefaultNonDcLumaIntraPenaltyINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetDefaultIntraChromaModeBasePenaltyINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceSetAcOnlyHaarINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceSetSourceInterlacedFieldPolarityINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceSetSingleReferenceInterlacedFieldPolarityINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceSetDualReferenceInterlacedFieldPolaritiesINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceConvertToImePayloadINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceConvertToImeResultINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceConvertToRefPayloadINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceConvertToRefResultINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceConvertToSicPayloadINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceConvertToSicResultINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetMotionVectorsINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetInterDistortionsINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetBestInterDistortionsINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetInterMajorShapeINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetInterMinorShapeINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetInterDirectionsINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetInterMotionVectorCountINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetInterReferenceIdsINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcMceGetInterReferenceInterlacedFieldPolaritiesINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeInitializeINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeSetSingleReferenceINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeSetDualReferenceINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeRefWindowSizeINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeAdjustRefOffsetINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeConvertToMcePayloadINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeSetMaxMotionVectorCountINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeSetUnidirectionalMixDisableINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeSetEarlySearchTerminationThresholdINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeSetWeightedSadINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeEvaluateWithSingleReferenceINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeEvaluateWithDualReferenceINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeEvaluateWithDualReferenceStreaminINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeEvaluateWithSingleReferenceStreamoutINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeEvaluateWithDualReferenceStreamoutINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminoutINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeEvaluateWithDualReferenceStreaminoutINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeConvertToMceResultINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeGetSingleReferenceStreaminINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeGetDualReferenceStreaminINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeStripSingleReferenceStreamoutINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeStripDualReferenceStreamoutINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeMotionVectorsINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeDistortionsINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeReferenceIdsINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeMotionVectorsINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeDistortionsINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeReferenceIdsINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeGetBorderReachedINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeGetTruncatedSearchIndicationINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeGetUnidirectionalEarlySearchTerminationINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeGetWeightingPatternMinimumMotionVectorINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcImeGetWeightingPatternMinimumDistortionINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcFmeInitializeINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcBmeInitializeINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcRefConvertToMcePayloadINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcRefSetBidirectionalMixDisableINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcRefSetBilinearFilterEnableINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcRefEvaluateWithSingleReferenceINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcRefEvaluateWithDualReferenceINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcRefEvaluateWithMultiReferenceINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcRefEvaluateWithMultiReferenceInterlacedINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcRefConvertToMceResultINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicInitializeINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicConfigureSkcINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicConfigureIpeLumaINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicConfigureIpeLumaChromaINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicGetMotionVectorMaskINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicConvertToMcePayloadINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicSetIntraLumaShapePenaltyINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicSetIntraLumaModeCostFunctionINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicSetIntraChromaModeCostFunctionINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicSetBilinearFilterEnableINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicSetSkcForwardTransformEnableINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicSetBlockBasedRawSkipSadINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicEvaluateIpeINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicEvaluateWithSingleReferenceINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicEvaluateWithDualReferenceINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicEvaluateWithMultiReferenceINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicEvaluateWithMultiReferenceInterlacedINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicConvertToMceResultINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicGetIpeLumaShapeINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicGetBestIpeLumaDistortionINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicGetBestIpeChromaDistortionINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicGetPackedIpeLumaModesINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicGetIpeChromaModeINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL: *hasResult = true; *hasResultType = true; break; + case OpSubgroupAvcSicGetInterRawSadsINTEL: *hasResult = true; *hasResultType = true; break; + case OpVariableLengthArrayINTEL: *hasResult = true; *hasResultType = true; break; + case OpSaveMemoryINTEL: *hasResult = true; *hasResultType = true; break; + case OpRestoreMemoryINTEL: *hasResult = false; *hasResultType = false; break; + case OpArbitraryFloatSinCosPiINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatCastINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatCastFromIntINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatCastToIntINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatAddINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatSubINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatMulINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatDivINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatGTINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatGEINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatLTINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatLEINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatEQINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatRecipINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatRSqrtINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatCbrtINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatHypotINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatSqrtINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatLogINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatLog2INTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatLog10INTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatLog1pINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatExpINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatExp2INTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatExp10INTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatExpm1INTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatSinINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatCosINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatSinCosINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatSinPiINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatCosPiINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatASinINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatASinPiINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatACosINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatACosPiINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatATanINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatATanPiINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatATan2INTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatPowINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatPowRINTEL: *hasResult = true; *hasResultType = true; break; + case OpArbitraryFloatPowNINTEL: *hasResult = true; *hasResultType = true; break; + case OpLoopControlINTEL: *hasResult = false; *hasResultType = false; break; + case OpAliasDomainDeclINTEL: *hasResult = true; *hasResultType = false; break; + case OpAliasScopeDeclINTEL: *hasResult = true; *hasResultType = false; break; + case OpAliasScopeListDeclINTEL: *hasResult = true; *hasResultType = false; break; + case OpFixedSqrtINTEL: *hasResult = true; *hasResultType = true; break; + case OpFixedRecipINTEL: *hasResult = true; *hasResultType = true; break; + case OpFixedRsqrtINTEL: *hasResult = true; *hasResultType = true; break; + case OpFixedSinINTEL: *hasResult = true; *hasResultType = true; break; + case OpFixedCosINTEL: *hasResult = true; *hasResultType = true; break; + case OpFixedSinCosINTEL: *hasResult = true; *hasResultType = true; break; + case OpFixedSinPiINTEL: *hasResult = true; *hasResultType = true; break; + case OpFixedCosPiINTEL: *hasResult = true; *hasResultType = true; break; + case OpFixedSinCosPiINTEL: *hasResult = true; *hasResultType = true; break; + case OpFixedLogINTEL: *hasResult = true; *hasResultType = true; break; + case OpFixedExpINTEL: *hasResult = true; *hasResultType = true; break; + case OpPtrCastToCrossWorkgroupINTEL: *hasResult = true; *hasResultType = true; break; + case OpCrossWorkgroupCastToPtrINTEL: *hasResult = true; *hasResultType = true; break; + case OpReadPipeBlockingINTEL: *hasResult = true; *hasResultType = true; break; + case OpWritePipeBlockingINTEL: *hasResult = true; *hasResultType = true; break; + case OpFPGARegINTEL: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetRayTMinKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetRayFlagsKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionTKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionInstanceCustomIndexKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionInstanceIdKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionGeometryIndexKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionPrimitiveIndexKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionBarycentricsKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionFrontFaceKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionCandidateAABBOpaqueKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionObjectRayDirectionKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionObjectRayOriginKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetWorldRayDirectionKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetWorldRayOriginKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionObjectToWorldKHR: *hasResult = true; *hasResultType = true; break; + case OpRayQueryGetIntersectionWorldToObjectKHR: *hasResult = true; *hasResultType = true; break; + case OpAtomicFAddEXT: *hasResult = true; *hasResultType = true; break; + case OpTypeBufferSurfaceINTEL: *hasResult = true; *hasResultType = false; break; + case OpTypeStructContinuedINTEL: *hasResult = false; *hasResultType = false; break; + case OpConstantCompositeContinuedINTEL: *hasResult = false; *hasResultType = false; break; + case OpSpecConstantCompositeContinuedINTEL: *hasResult = false; *hasResultType = false; break; + case OpCompositeConstructContinuedINTEL: *hasResult = true; *hasResultType = true; break; + case OpConvertFToBF16INTEL: *hasResult = true; *hasResultType = true; break; + case OpConvertBF16ToFINTEL: *hasResult = true; *hasResultType = true; break; + case OpControlBarrierArriveINTEL: *hasResult = false; *hasResultType = false; break; + case OpControlBarrierWaitINTEL: *hasResult = false; *hasResultType = false; break; + case OpArithmeticFenceEXT: *hasResult = true; *hasResultType = true; break; + case OpTaskSequenceCreateINTEL: *hasResult = true; *hasResultType = true; break; + case OpTaskSequenceAsyncINTEL: *hasResult = false; *hasResultType = false; break; + case OpTaskSequenceGetINTEL: *hasResult = true; *hasResultType = true; break; + case OpTaskSequenceReleaseINTEL: *hasResult = false; *hasResultType = false; break; + case OpTypeTaskSequenceINTEL: *hasResult = true; *hasResultType = false; break; + case OpSubgroupBlockPrefetchINTEL: *hasResult = false; *hasResultType = false; break; + case OpSubgroup2DBlockLoadINTEL: *hasResult = false; *hasResultType = false; break; + case OpSubgroup2DBlockLoadTransformINTEL: *hasResult = false; *hasResultType = false; break; + case OpSubgroup2DBlockLoadTransposeINTEL: *hasResult = false; *hasResultType = false; break; + case OpSubgroup2DBlockPrefetchINTEL: *hasResult = false; *hasResultType = false; break; + case OpSubgroup2DBlockStoreINTEL: *hasResult = false; *hasResultType = false; break; + case OpSubgroupMatrixMultiplyAccumulateINTEL: *hasResult = true; *hasResultType = true; break; + case OpBitwiseFunctionINTEL: *hasResult = true; *hasResultType = true; break; + case OpGroupIMulKHR: *hasResult = true; *hasResultType = true; break; + case OpGroupFMulKHR: *hasResult = true; *hasResultType = true; break; + case OpGroupBitwiseAndKHR: *hasResult = true; *hasResultType = true; break; + case OpGroupBitwiseOrKHR: *hasResult = true; *hasResultType = true; break; + case OpGroupBitwiseXorKHR: *hasResult = true; *hasResultType = true; break; + case OpGroupLogicalAndKHR: *hasResult = true; *hasResultType = true; break; + case OpGroupLogicalOrKHR: *hasResult = true; *hasResultType = true; break; + case OpGroupLogicalXorKHR: *hasResult = true; *hasResultType = true; break; + case OpRoundFToTF32INTEL: *hasResult = true; *hasResultType = true; break; + case OpMaskedGatherINTEL: *hasResult = true; *hasResultType = true; break; + case OpMaskedScatterINTEL: *hasResult = false; *hasResultType = false; break; + } +} +inline const char* SourceLanguageToString(SourceLanguage value) { + switch (value) { + case SourceLanguageUnknown: return "Unknown"; + case SourceLanguageESSL: return "ESSL"; + case SourceLanguageGLSL: return "GLSL"; + case SourceLanguageOpenCL_C: return "OpenCL_C"; + case SourceLanguageOpenCL_CPP: return "OpenCL_CPP"; + case SourceLanguageHLSL: return "HLSL"; + case SourceLanguageCPP_for_OpenCL: return "CPP_for_OpenCL"; + case SourceLanguageSYCL: return "SYCL"; + case SourceLanguageHERO_C: return "HERO_C"; + case SourceLanguageNZSL: return "NZSL"; + case SourceLanguageWGSL: return "WGSL"; + case SourceLanguageSlang: return "Slang"; + case SourceLanguageZig: return "Zig"; + case SourceLanguageRust: return "Rust"; + default: return "Unknown"; + } +} + +inline const char* ExecutionModelToString(ExecutionModel value) { + switch (value) { + case ExecutionModelVertex: return "Vertex"; + case ExecutionModelTessellationControl: return "TessellationControl"; + case ExecutionModelTessellationEvaluation: return "TessellationEvaluation"; + case ExecutionModelGeometry: return "Geometry"; + case ExecutionModelFragment: return "Fragment"; + case ExecutionModelGLCompute: return "GLCompute"; + case ExecutionModelKernel: return "Kernel"; + case ExecutionModelTaskNV: return "TaskNV"; + case ExecutionModelMeshNV: return "MeshNV"; + case ExecutionModelRayGenerationKHR: return "RayGenerationKHR"; + case ExecutionModelIntersectionKHR: return "IntersectionKHR"; + case ExecutionModelAnyHitKHR: return "AnyHitKHR"; + case ExecutionModelClosestHitKHR: return "ClosestHitKHR"; + case ExecutionModelMissKHR: return "MissKHR"; + case ExecutionModelCallableKHR: return "CallableKHR"; + case ExecutionModelTaskEXT: return "TaskEXT"; + case ExecutionModelMeshEXT: return "MeshEXT"; + default: return "Unknown"; + } +} + +inline const char* AddressingModelToString(AddressingModel value) { + switch (value) { + case AddressingModelLogical: return "Logical"; + case AddressingModelPhysical32: return "Physical32"; + case AddressingModelPhysical64: return "Physical64"; + case AddressingModelPhysicalStorageBuffer64: return "PhysicalStorageBuffer64"; + default: return "Unknown"; + } +} + +inline const char* MemoryModelToString(MemoryModel value) { + switch (value) { + case MemoryModelSimple: return "Simple"; + case MemoryModelGLSL450: return "GLSL450"; + case MemoryModelOpenCL: return "OpenCL"; + case MemoryModelVulkan: return "Vulkan"; + default: return "Unknown"; + } +} + +inline const char* ExecutionModeToString(ExecutionMode value) { + switch (value) { + case ExecutionModeInvocations: return "Invocations"; + case ExecutionModeSpacingEqual: return "SpacingEqual"; + case ExecutionModeSpacingFractionalEven: return "SpacingFractionalEven"; + case ExecutionModeSpacingFractionalOdd: return "SpacingFractionalOdd"; + case ExecutionModeVertexOrderCw: return "VertexOrderCw"; + case ExecutionModeVertexOrderCcw: return "VertexOrderCcw"; + case ExecutionModePixelCenterInteger: return "PixelCenterInteger"; + case ExecutionModeOriginUpperLeft: return "OriginUpperLeft"; + case ExecutionModeOriginLowerLeft: return "OriginLowerLeft"; + case ExecutionModeEarlyFragmentTests: return "EarlyFragmentTests"; + case ExecutionModePointMode: return "PointMode"; + case ExecutionModeXfb: return "Xfb"; + case ExecutionModeDepthReplacing: return "DepthReplacing"; + case ExecutionModeDepthGreater: return "DepthGreater"; + case ExecutionModeDepthLess: return "DepthLess"; + case ExecutionModeDepthUnchanged: return "DepthUnchanged"; + case ExecutionModeLocalSize: return "LocalSize"; + case ExecutionModeLocalSizeHint: return "LocalSizeHint"; + case ExecutionModeInputPoints: return "InputPoints"; + case ExecutionModeInputLines: return "InputLines"; + case ExecutionModeInputLinesAdjacency: return "InputLinesAdjacency"; + case ExecutionModeTriangles: return "Triangles"; + case ExecutionModeInputTrianglesAdjacency: return "InputTrianglesAdjacency"; + case ExecutionModeQuads: return "Quads"; + case ExecutionModeIsolines: return "Isolines"; + case ExecutionModeOutputVertices: return "OutputVertices"; + case ExecutionModeOutputPoints: return "OutputPoints"; + case ExecutionModeOutputLineStrip: return "OutputLineStrip"; + case ExecutionModeOutputTriangleStrip: return "OutputTriangleStrip"; + case ExecutionModeVecTypeHint: return "VecTypeHint"; + case ExecutionModeContractionOff: return "ContractionOff"; + case ExecutionModeInitializer: return "Initializer"; + case ExecutionModeFinalizer: return "Finalizer"; + case ExecutionModeSubgroupSize: return "SubgroupSize"; + case ExecutionModeSubgroupsPerWorkgroup: return "SubgroupsPerWorkgroup"; + case ExecutionModeSubgroupsPerWorkgroupId: return "SubgroupsPerWorkgroupId"; + case ExecutionModeLocalSizeId: return "LocalSizeId"; + case ExecutionModeLocalSizeHintId: return "LocalSizeHintId"; + case ExecutionModeNonCoherentColorAttachmentReadEXT: return "NonCoherentColorAttachmentReadEXT"; + case ExecutionModeNonCoherentDepthAttachmentReadEXT: return "NonCoherentDepthAttachmentReadEXT"; + case ExecutionModeNonCoherentStencilAttachmentReadEXT: return "NonCoherentStencilAttachmentReadEXT"; + case ExecutionModeSubgroupUniformControlFlowKHR: return "SubgroupUniformControlFlowKHR"; + case ExecutionModePostDepthCoverage: return "PostDepthCoverage"; + case ExecutionModeDenormPreserve: return "DenormPreserve"; + case ExecutionModeDenormFlushToZero: return "DenormFlushToZero"; + case ExecutionModeSignedZeroInfNanPreserve: return "SignedZeroInfNanPreserve"; + case ExecutionModeRoundingModeRTE: return "RoundingModeRTE"; + case ExecutionModeRoundingModeRTZ: return "RoundingModeRTZ"; + case ExecutionModeNonCoherentTileAttachmentReadQCOM: return "NonCoherentTileAttachmentReadQCOM"; + case ExecutionModeTileShadingRateQCOM: return "TileShadingRateQCOM"; + case ExecutionModeEarlyAndLateFragmentTestsAMD: return "EarlyAndLateFragmentTestsAMD"; + case ExecutionModeStencilRefReplacingEXT: return "StencilRefReplacingEXT"; + case ExecutionModeCoalescingAMDX: return "CoalescingAMDX"; + case ExecutionModeIsApiEntryAMDX: return "IsApiEntryAMDX"; + case ExecutionModeMaxNodeRecursionAMDX: return "MaxNodeRecursionAMDX"; + case ExecutionModeStaticNumWorkgroupsAMDX: return "StaticNumWorkgroupsAMDX"; + case ExecutionModeShaderIndexAMDX: return "ShaderIndexAMDX"; + case ExecutionModeMaxNumWorkgroupsAMDX: return "MaxNumWorkgroupsAMDX"; + case ExecutionModeStencilRefUnchangedFrontAMD: return "StencilRefUnchangedFrontAMD"; + case ExecutionModeStencilRefGreaterFrontAMD: return "StencilRefGreaterFrontAMD"; + case ExecutionModeStencilRefLessFrontAMD: return "StencilRefLessFrontAMD"; + case ExecutionModeStencilRefUnchangedBackAMD: return "StencilRefUnchangedBackAMD"; + case ExecutionModeStencilRefGreaterBackAMD: return "StencilRefGreaterBackAMD"; + case ExecutionModeStencilRefLessBackAMD: return "StencilRefLessBackAMD"; + case ExecutionModeQuadDerivativesKHR: return "QuadDerivativesKHR"; + case ExecutionModeRequireFullQuadsKHR: return "RequireFullQuadsKHR"; + case ExecutionModeSharesInputWithAMDX: return "SharesInputWithAMDX"; + case ExecutionModeOutputLinesEXT: return "OutputLinesEXT"; + case ExecutionModeOutputPrimitivesEXT: return "OutputPrimitivesEXT"; + case ExecutionModeDerivativeGroupQuadsKHR: return "DerivativeGroupQuadsKHR"; + case ExecutionModeDerivativeGroupLinearKHR: return "DerivativeGroupLinearKHR"; + case ExecutionModeOutputTrianglesEXT: return "OutputTrianglesEXT"; + case ExecutionModePixelInterlockOrderedEXT: return "PixelInterlockOrderedEXT"; + case ExecutionModePixelInterlockUnorderedEXT: return "PixelInterlockUnorderedEXT"; + case ExecutionModeSampleInterlockOrderedEXT: return "SampleInterlockOrderedEXT"; + case ExecutionModeSampleInterlockUnorderedEXT: return "SampleInterlockUnorderedEXT"; + case ExecutionModeShadingRateInterlockOrderedEXT: return "ShadingRateInterlockOrderedEXT"; + case ExecutionModeShadingRateInterlockUnorderedEXT: return "ShadingRateInterlockUnorderedEXT"; + case ExecutionModeSharedLocalMemorySizeINTEL: return "SharedLocalMemorySizeINTEL"; + case ExecutionModeRoundingModeRTPINTEL: return "RoundingModeRTPINTEL"; + case ExecutionModeRoundingModeRTNINTEL: return "RoundingModeRTNINTEL"; + case ExecutionModeFloatingPointModeALTINTEL: return "FloatingPointModeALTINTEL"; + case ExecutionModeFloatingPointModeIEEEINTEL: return "FloatingPointModeIEEEINTEL"; + case ExecutionModeMaxWorkgroupSizeINTEL: return "MaxWorkgroupSizeINTEL"; + case ExecutionModeMaxWorkDimINTEL: return "MaxWorkDimINTEL"; + case ExecutionModeNoGlobalOffsetINTEL: return "NoGlobalOffsetINTEL"; + case ExecutionModeNumSIMDWorkitemsINTEL: return "NumSIMDWorkitemsINTEL"; + case ExecutionModeSchedulerTargetFmaxMhzINTEL: return "SchedulerTargetFmaxMhzINTEL"; + case ExecutionModeMaximallyReconvergesKHR: return "MaximallyReconvergesKHR"; + case ExecutionModeFPFastMathDefault: return "FPFastMathDefault"; + case ExecutionModeStreamingInterfaceINTEL: return "StreamingInterfaceINTEL"; + case ExecutionModeRegisterMapInterfaceINTEL: return "RegisterMapInterfaceINTEL"; + case ExecutionModeNamedBarrierCountINTEL: return "NamedBarrierCountINTEL"; + case ExecutionModeMaximumRegistersINTEL: return "MaximumRegistersINTEL"; + case ExecutionModeMaximumRegistersIdINTEL: return "MaximumRegistersIdINTEL"; + case ExecutionModeNamedMaximumRegistersINTEL: return "NamedMaximumRegistersINTEL"; + default: return "Unknown"; + } +} + +inline const char* StorageClassToString(StorageClass value) { + switch (value) { + case StorageClassUniformConstant: return "UniformConstant"; + case StorageClassInput: return "Input"; + case StorageClassUniform: return "Uniform"; + case StorageClassOutput: return "Output"; + case StorageClassWorkgroup: return "Workgroup"; + case StorageClassCrossWorkgroup: return "CrossWorkgroup"; + case StorageClassPrivate: return "Private"; + case StorageClassFunction: return "Function"; + case StorageClassGeneric: return "Generic"; + case StorageClassPushConstant: return "PushConstant"; + case StorageClassAtomicCounter: return "AtomicCounter"; + case StorageClassImage: return "Image"; + case StorageClassStorageBuffer: return "StorageBuffer"; + case StorageClassTileImageEXT: return "TileImageEXT"; + case StorageClassTileAttachmentQCOM: return "TileAttachmentQCOM"; + case StorageClassNodePayloadAMDX: return "NodePayloadAMDX"; + case StorageClassCallableDataKHR: return "CallableDataKHR"; + case StorageClassIncomingCallableDataKHR: return "IncomingCallableDataKHR"; + case StorageClassRayPayloadKHR: return "RayPayloadKHR"; + case StorageClassHitAttributeKHR: return "HitAttributeKHR"; + case StorageClassIncomingRayPayloadKHR: return "IncomingRayPayloadKHR"; + case StorageClassShaderRecordBufferKHR: return "ShaderRecordBufferKHR"; + case StorageClassPhysicalStorageBuffer: return "PhysicalStorageBuffer"; + case StorageClassHitObjectAttributeNV: return "HitObjectAttributeNV"; + case StorageClassTaskPayloadWorkgroupEXT: return "TaskPayloadWorkgroupEXT"; + case StorageClassCodeSectionINTEL: return "CodeSectionINTEL"; + case StorageClassDeviceOnlyINTEL: return "DeviceOnlyINTEL"; + case StorageClassHostOnlyINTEL: return "HostOnlyINTEL"; + default: return "Unknown"; + } +} + +inline const char* DimToString(Dim value) { + switch (value) { + case Dim1D: return "1D"; + case Dim2D: return "2D"; + case Dim3D: return "3D"; + case DimCube: return "Cube"; + case DimRect: return "Rect"; + case DimBuffer: return "Buffer"; + case DimSubpassData: return "SubpassData"; + case DimTileImageDataEXT: return "TileImageDataEXT"; + default: return "Unknown"; + } +} + +inline const char* SamplerAddressingModeToString(SamplerAddressingMode value) { + switch (value) { + case SamplerAddressingModeNone: return "None"; + case SamplerAddressingModeClampToEdge: return "ClampToEdge"; + case SamplerAddressingModeClamp: return "Clamp"; + case SamplerAddressingModeRepeat: return "Repeat"; + case SamplerAddressingModeRepeatMirrored: return "RepeatMirrored"; + default: return "Unknown"; + } +} + +inline const char* SamplerFilterModeToString(SamplerFilterMode value) { + switch (value) { + case SamplerFilterModeNearest: return "Nearest"; + case SamplerFilterModeLinear: return "Linear"; + default: return "Unknown"; + } +} + +inline const char* ImageFormatToString(ImageFormat value) { + switch (value) { + case ImageFormatUnknown: return "Unknown"; + case ImageFormatRgba32f: return "Rgba32f"; + case ImageFormatRgba16f: return "Rgba16f"; + case ImageFormatR32f: return "R32f"; + case ImageFormatRgba8: return "Rgba8"; + case ImageFormatRgba8Snorm: return "Rgba8Snorm"; + case ImageFormatRg32f: return "Rg32f"; + case ImageFormatRg16f: return "Rg16f"; + case ImageFormatR11fG11fB10f: return "R11fG11fB10f"; + case ImageFormatR16f: return "R16f"; + case ImageFormatRgba16: return "Rgba16"; + case ImageFormatRgb10A2: return "Rgb10A2"; + case ImageFormatRg16: return "Rg16"; + case ImageFormatRg8: return "Rg8"; + case ImageFormatR16: return "R16"; + case ImageFormatR8: return "R8"; + case ImageFormatRgba16Snorm: return "Rgba16Snorm"; + case ImageFormatRg16Snorm: return "Rg16Snorm"; + case ImageFormatRg8Snorm: return "Rg8Snorm"; + case ImageFormatR16Snorm: return "R16Snorm"; + case ImageFormatR8Snorm: return "R8Snorm"; + case ImageFormatRgba32i: return "Rgba32i"; + case ImageFormatRgba16i: return "Rgba16i"; + case ImageFormatRgba8i: return "Rgba8i"; + case ImageFormatR32i: return "R32i"; + case ImageFormatRg32i: return "Rg32i"; + case ImageFormatRg16i: return "Rg16i"; + case ImageFormatRg8i: return "Rg8i"; + case ImageFormatR16i: return "R16i"; + case ImageFormatR8i: return "R8i"; + case ImageFormatRgba32ui: return "Rgba32ui"; + case ImageFormatRgba16ui: return "Rgba16ui"; + case ImageFormatRgba8ui: return "Rgba8ui"; + case ImageFormatR32ui: return "R32ui"; + case ImageFormatRgb10a2ui: return "Rgb10a2ui"; + case ImageFormatRg32ui: return "Rg32ui"; + case ImageFormatRg16ui: return "Rg16ui"; + case ImageFormatRg8ui: return "Rg8ui"; + case ImageFormatR16ui: return "R16ui"; + case ImageFormatR8ui: return "R8ui"; + case ImageFormatR64ui: return "R64ui"; + case ImageFormatR64i: return "R64i"; + default: return "Unknown"; + } +} + +inline const char* ImageChannelOrderToString(ImageChannelOrder value) { + switch (value) { + case ImageChannelOrderR: return "R"; + case ImageChannelOrderA: return "A"; + case ImageChannelOrderRG: return "RG"; + case ImageChannelOrderRA: return "RA"; + case ImageChannelOrderRGB: return "RGB"; + case ImageChannelOrderRGBA: return "RGBA"; + case ImageChannelOrderBGRA: return "BGRA"; + case ImageChannelOrderARGB: return "ARGB"; + case ImageChannelOrderIntensity: return "Intensity"; + case ImageChannelOrderLuminance: return "Luminance"; + case ImageChannelOrderRx: return "Rx"; + case ImageChannelOrderRGx: return "RGx"; + case ImageChannelOrderRGBx: return "RGBx"; + case ImageChannelOrderDepth: return "Depth"; + case ImageChannelOrderDepthStencil: return "DepthStencil"; + case ImageChannelOrdersRGB: return "sRGB"; + case ImageChannelOrdersRGBx: return "sRGBx"; + case ImageChannelOrdersRGBA: return "sRGBA"; + case ImageChannelOrdersBGRA: return "sBGRA"; + case ImageChannelOrderABGR: return "ABGR"; + default: return "Unknown"; + } +} + +inline const char* ImageChannelDataTypeToString(ImageChannelDataType value) { + switch (value) { + case ImageChannelDataTypeSnormInt8: return "SnormInt8"; + case ImageChannelDataTypeSnormInt16: return "SnormInt16"; + case ImageChannelDataTypeUnormInt8: return "UnormInt8"; + case ImageChannelDataTypeUnormInt16: return "UnormInt16"; + case ImageChannelDataTypeUnormShort565: return "UnormShort565"; + case ImageChannelDataTypeUnormShort555: return "UnormShort555"; + case ImageChannelDataTypeUnormInt101010: return "UnormInt101010"; + case ImageChannelDataTypeSignedInt8: return "SignedInt8"; + case ImageChannelDataTypeSignedInt16: return "SignedInt16"; + case ImageChannelDataTypeSignedInt32: return "SignedInt32"; + case ImageChannelDataTypeUnsignedInt8: return "UnsignedInt8"; + case ImageChannelDataTypeUnsignedInt16: return "UnsignedInt16"; + case ImageChannelDataTypeUnsignedInt32: return "UnsignedInt32"; + case ImageChannelDataTypeHalfFloat: return "HalfFloat"; + case ImageChannelDataTypeFloat: return "Float"; + case ImageChannelDataTypeUnormInt24: return "UnormInt24"; + case ImageChannelDataTypeUnormInt101010_2: return "UnormInt101010_2"; + case ImageChannelDataTypeUnormInt10X6EXT: return "UnormInt10X6EXT"; + case ImageChannelDataTypeUnsignedIntRaw10EXT: return "UnsignedIntRaw10EXT"; + case ImageChannelDataTypeUnsignedIntRaw12EXT: return "UnsignedIntRaw12EXT"; + case ImageChannelDataTypeUnormInt2_101010EXT: return "UnormInt2_101010EXT"; + case ImageChannelDataTypeUnsignedInt10X6EXT: return "UnsignedInt10X6EXT"; + case ImageChannelDataTypeUnsignedInt12X4EXT: return "UnsignedInt12X4EXT"; + case ImageChannelDataTypeUnsignedInt14X2EXT: return "UnsignedInt14X2EXT"; + case ImageChannelDataTypeUnormInt12X4EXT: return "UnormInt12X4EXT"; + case ImageChannelDataTypeUnormInt14X2EXT: return "UnormInt14X2EXT"; + default: return "Unknown"; + } +} + +inline const char* FPRoundingModeToString(FPRoundingMode value) { + switch (value) { + case FPRoundingModeRTE: return "RTE"; + case FPRoundingModeRTZ: return "RTZ"; + case FPRoundingModeRTP: return "RTP"; + case FPRoundingModeRTN: return "RTN"; + default: return "Unknown"; + } +} + +inline const char* LinkageTypeToString(LinkageType value) { + switch (value) { + case LinkageTypeExport: return "Export"; + case LinkageTypeImport: return "Import"; + case LinkageTypeLinkOnceODR: return "LinkOnceODR"; + default: return "Unknown"; + } +} + +inline const char* AccessQualifierToString(AccessQualifier value) { + switch (value) { + case AccessQualifierReadOnly: return "ReadOnly"; + case AccessQualifierWriteOnly: return "WriteOnly"; + case AccessQualifierReadWrite: return "ReadWrite"; + default: return "Unknown"; + } +} + +inline const char* FunctionParameterAttributeToString(FunctionParameterAttribute value) { + switch (value) { + case FunctionParameterAttributeZext: return "Zext"; + case FunctionParameterAttributeSext: return "Sext"; + case FunctionParameterAttributeByVal: return "ByVal"; + case FunctionParameterAttributeSret: return "Sret"; + case FunctionParameterAttributeNoAlias: return "NoAlias"; + case FunctionParameterAttributeNoCapture: return "NoCapture"; + case FunctionParameterAttributeNoWrite: return "NoWrite"; + case FunctionParameterAttributeNoReadWrite: return "NoReadWrite"; + case FunctionParameterAttributeRuntimeAlignedINTEL: return "RuntimeAlignedINTEL"; + default: return "Unknown"; + } +} + +inline const char* DecorationToString(Decoration value) { + switch (value) { + case DecorationRelaxedPrecision: return "RelaxedPrecision"; + case DecorationSpecId: return "SpecId"; + case DecorationBlock: return "Block"; + case DecorationBufferBlock: return "BufferBlock"; + case DecorationRowMajor: return "RowMajor"; + case DecorationColMajor: return "ColMajor"; + case DecorationArrayStride: return "ArrayStride"; + case DecorationMatrixStride: return "MatrixStride"; + case DecorationGLSLShared: return "GLSLShared"; + case DecorationGLSLPacked: return "GLSLPacked"; + case DecorationCPacked: return "CPacked"; + case DecorationBuiltIn: return "BuiltIn"; + case DecorationNoPerspective: return "NoPerspective"; + case DecorationFlat: return "Flat"; + case DecorationPatch: return "Patch"; + case DecorationCentroid: return "Centroid"; + case DecorationSample: return "Sample"; + case DecorationInvariant: return "Invariant"; + case DecorationRestrict: return "Restrict"; + case DecorationAliased: return "Aliased"; + case DecorationVolatile: return "Volatile"; + case DecorationConstant: return "Constant"; + case DecorationCoherent: return "Coherent"; + case DecorationNonWritable: return "NonWritable"; + case DecorationNonReadable: return "NonReadable"; + case DecorationUniform: return "Uniform"; + case DecorationUniformId: return "UniformId"; + case DecorationSaturatedConversion: return "SaturatedConversion"; + case DecorationStream: return "Stream"; + case DecorationLocation: return "Location"; + case DecorationComponent: return "Component"; + case DecorationIndex: return "Index"; + case DecorationBinding: return "Binding"; + case DecorationDescriptorSet: return "DescriptorSet"; + case DecorationOffset: return "Offset"; + case DecorationXfbBuffer: return "XfbBuffer"; + case DecorationXfbStride: return "XfbStride"; + case DecorationFuncParamAttr: return "FuncParamAttr"; + case DecorationFPRoundingMode: return "FPRoundingMode"; + case DecorationFPFastMathMode: return "FPFastMathMode"; + case DecorationLinkageAttributes: return "LinkageAttributes"; + case DecorationNoContraction: return "NoContraction"; + case DecorationInputAttachmentIndex: return "InputAttachmentIndex"; + case DecorationAlignment: return "Alignment"; + case DecorationMaxByteOffset: return "MaxByteOffset"; + case DecorationAlignmentId: return "AlignmentId"; + case DecorationMaxByteOffsetId: return "MaxByteOffsetId"; + case DecorationNoSignedWrap: return "NoSignedWrap"; + case DecorationNoUnsignedWrap: return "NoUnsignedWrap"; + case DecorationWeightTextureQCOM: return "WeightTextureQCOM"; + case DecorationBlockMatchTextureQCOM: return "BlockMatchTextureQCOM"; + case DecorationBlockMatchSamplerQCOM: return "BlockMatchSamplerQCOM"; + case DecorationExplicitInterpAMD: return "ExplicitInterpAMD"; + case DecorationNodeSharesPayloadLimitsWithAMDX: return "NodeSharesPayloadLimitsWithAMDX"; + case DecorationNodeMaxPayloadsAMDX: return "NodeMaxPayloadsAMDX"; + case DecorationTrackFinishWritingAMDX: return "TrackFinishWritingAMDX"; + case DecorationPayloadNodeNameAMDX: return "PayloadNodeNameAMDX"; + case DecorationPayloadNodeBaseIndexAMDX: return "PayloadNodeBaseIndexAMDX"; + case DecorationPayloadNodeSparseArrayAMDX: return "PayloadNodeSparseArrayAMDX"; + case DecorationPayloadNodeArraySizeAMDX: return "PayloadNodeArraySizeAMDX"; + case DecorationPayloadDispatchIndirectAMDX: return "PayloadDispatchIndirectAMDX"; + case DecorationOverrideCoverageNV: return "OverrideCoverageNV"; + case DecorationPassthroughNV: return "PassthroughNV"; + case DecorationViewportRelativeNV: return "ViewportRelativeNV"; + case DecorationSecondaryViewportRelativeNV: return "SecondaryViewportRelativeNV"; + case DecorationPerPrimitiveEXT: return "PerPrimitiveEXT"; + case DecorationPerViewNV: return "PerViewNV"; + case DecorationPerTaskNV: return "PerTaskNV"; + case DecorationPerVertexKHR: return "PerVertexKHR"; + case DecorationNonUniform: return "NonUniform"; + case DecorationRestrictPointer: return "RestrictPointer"; + case DecorationAliasedPointer: return "AliasedPointer"; + case DecorationHitObjectShaderRecordBufferNV: return "HitObjectShaderRecordBufferNV"; + case DecorationBindlessSamplerNV: return "BindlessSamplerNV"; + case DecorationBindlessImageNV: return "BindlessImageNV"; + case DecorationBoundSamplerNV: return "BoundSamplerNV"; + case DecorationBoundImageNV: return "BoundImageNV"; + case DecorationSIMTCallINTEL: return "SIMTCallINTEL"; + case DecorationReferencedIndirectlyINTEL: return "ReferencedIndirectlyINTEL"; + case DecorationClobberINTEL: return "ClobberINTEL"; + case DecorationSideEffectsINTEL: return "SideEffectsINTEL"; + case DecorationVectorComputeVariableINTEL: return "VectorComputeVariableINTEL"; + case DecorationFuncParamIOKindINTEL: return "FuncParamIOKindINTEL"; + case DecorationVectorComputeFunctionINTEL: return "VectorComputeFunctionINTEL"; + case DecorationStackCallINTEL: return "StackCallINTEL"; + case DecorationGlobalVariableOffsetINTEL: return "GlobalVariableOffsetINTEL"; + case DecorationCounterBuffer: return "CounterBuffer"; + case DecorationHlslSemanticGOOGLE: return "HlslSemanticGOOGLE"; + case DecorationUserTypeGOOGLE: return "UserTypeGOOGLE"; + case DecorationFunctionRoundingModeINTEL: return "FunctionRoundingModeINTEL"; + case DecorationFunctionDenormModeINTEL: return "FunctionDenormModeINTEL"; + case DecorationRegisterINTEL: return "RegisterINTEL"; + case DecorationMemoryINTEL: return "MemoryINTEL"; + case DecorationNumbanksINTEL: return "NumbanksINTEL"; + case DecorationBankwidthINTEL: return "BankwidthINTEL"; + case DecorationMaxPrivateCopiesINTEL: return "MaxPrivateCopiesINTEL"; + case DecorationSinglepumpINTEL: return "SinglepumpINTEL"; + case DecorationDoublepumpINTEL: return "DoublepumpINTEL"; + case DecorationMaxReplicatesINTEL: return "MaxReplicatesINTEL"; + case DecorationSimpleDualPortINTEL: return "SimpleDualPortINTEL"; + case DecorationMergeINTEL: return "MergeINTEL"; + case DecorationBankBitsINTEL: return "BankBitsINTEL"; + case DecorationForcePow2DepthINTEL: return "ForcePow2DepthINTEL"; + case DecorationStridesizeINTEL: return "StridesizeINTEL"; + case DecorationWordsizeINTEL: return "WordsizeINTEL"; + case DecorationTrueDualPortINTEL: return "TrueDualPortINTEL"; + case DecorationBurstCoalesceINTEL: return "BurstCoalesceINTEL"; + case DecorationCacheSizeINTEL: return "CacheSizeINTEL"; + case DecorationDontStaticallyCoalesceINTEL: return "DontStaticallyCoalesceINTEL"; + case DecorationPrefetchINTEL: return "PrefetchINTEL"; + case DecorationStallEnableINTEL: return "StallEnableINTEL"; + case DecorationFuseLoopsInFunctionINTEL: return "FuseLoopsInFunctionINTEL"; + case DecorationMathOpDSPModeINTEL: return "MathOpDSPModeINTEL"; + case DecorationAliasScopeINTEL: return "AliasScopeINTEL"; + case DecorationNoAliasINTEL: return "NoAliasINTEL"; + case DecorationInitiationIntervalINTEL: return "InitiationIntervalINTEL"; + case DecorationMaxConcurrencyINTEL: return "MaxConcurrencyINTEL"; + case DecorationPipelineEnableINTEL: return "PipelineEnableINTEL"; + case DecorationBufferLocationINTEL: return "BufferLocationINTEL"; + case DecorationIOPipeStorageINTEL: return "IOPipeStorageINTEL"; + case DecorationFunctionFloatingPointModeINTEL: return "FunctionFloatingPointModeINTEL"; + case DecorationSingleElementVectorINTEL: return "SingleElementVectorINTEL"; + case DecorationVectorComputeCallableFunctionINTEL: return "VectorComputeCallableFunctionINTEL"; + case DecorationMediaBlockIOINTEL: return "MediaBlockIOINTEL"; + case DecorationStallFreeINTEL: return "StallFreeINTEL"; + case DecorationFPMaxErrorDecorationINTEL: return "FPMaxErrorDecorationINTEL"; + case DecorationLatencyControlLabelINTEL: return "LatencyControlLabelINTEL"; + case DecorationLatencyControlConstraintINTEL: return "LatencyControlConstraintINTEL"; + case DecorationConduitKernelArgumentINTEL: return "ConduitKernelArgumentINTEL"; + case DecorationRegisterMapKernelArgumentINTEL: return "RegisterMapKernelArgumentINTEL"; + case DecorationMMHostInterfaceAddressWidthINTEL: return "MMHostInterfaceAddressWidthINTEL"; + case DecorationMMHostInterfaceDataWidthINTEL: return "MMHostInterfaceDataWidthINTEL"; + case DecorationMMHostInterfaceLatencyINTEL: return "MMHostInterfaceLatencyINTEL"; + case DecorationMMHostInterfaceReadWriteModeINTEL: return "MMHostInterfaceReadWriteModeINTEL"; + case DecorationMMHostInterfaceMaxBurstINTEL: return "MMHostInterfaceMaxBurstINTEL"; + case DecorationMMHostInterfaceWaitRequestINTEL: return "MMHostInterfaceWaitRequestINTEL"; + case DecorationStableKernelArgumentINTEL: return "StableKernelArgumentINTEL"; + case DecorationHostAccessINTEL: return "HostAccessINTEL"; + case DecorationInitModeINTEL: return "InitModeINTEL"; + case DecorationImplementInRegisterMapINTEL: return "ImplementInRegisterMapINTEL"; + case DecorationCacheControlLoadINTEL: return "CacheControlLoadINTEL"; + case DecorationCacheControlStoreINTEL: return "CacheControlStoreINTEL"; + default: return "Unknown"; + } +} + +inline const char* BuiltInToString(BuiltIn value) { + switch (value) { + case BuiltInPosition: return "Position"; + case BuiltInPointSize: return "PointSize"; + case BuiltInClipDistance: return "ClipDistance"; + case BuiltInCullDistance: return "CullDistance"; + case BuiltInVertexId: return "VertexId"; + case BuiltInInstanceId: return "InstanceId"; + case BuiltInPrimitiveId: return "PrimitiveId"; + case BuiltInInvocationId: return "InvocationId"; + case BuiltInLayer: return "Layer"; + case BuiltInViewportIndex: return "ViewportIndex"; + case BuiltInTessLevelOuter: return "TessLevelOuter"; + case BuiltInTessLevelInner: return "TessLevelInner"; + case BuiltInTessCoord: return "TessCoord"; + case BuiltInPatchVertices: return "PatchVertices"; + case BuiltInFragCoord: return "FragCoord"; + case BuiltInPointCoord: return "PointCoord"; + case BuiltInFrontFacing: return "FrontFacing"; + case BuiltInSampleId: return "SampleId"; + case BuiltInSamplePosition: return "SamplePosition"; + case BuiltInSampleMask: return "SampleMask"; + case BuiltInFragDepth: return "FragDepth"; + case BuiltInHelperInvocation: return "HelperInvocation"; + case BuiltInNumWorkgroups: return "NumWorkgroups"; + case BuiltInWorkgroupSize: return "WorkgroupSize"; + case BuiltInWorkgroupId: return "WorkgroupId"; + case BuiltInLocalInvocationId: return "LocalInvocationId"; + case BuiltInGlobalInvocationId: return "GlobalInvocationId"; + case BuiltInLocalInvocationIndex: return "LocalInvocationIndex"; + case BuiltInWorkDim: return "WorkDim"; + case BuiltInGlobalSize: return "GlobalSize"; + case BuiltInEnqueuedWorkgroupSize: return "EnqueuedWorkgroupSize"; + case BuiltInGlobalOffset: return "GlobalOffset"; + case BuiltInGlobalLinearId: return "GlobalLinearId"; + case BuiltInSubgroupSize: return "SubgroupSize"; + case BuiltInSubgroupMaxSize: return "SubgroupMaxSize"; + case BuiltInNumSubgroups: return "NumSubgroups"; + case BuiltInNumEnqueuedSubgroups: return "NumEnqueuedSubgroups"; + case BuiltInSubgroupId: return "SubgroupId"; + case BuiltInSubgroupLocalInvocationId: return "SubgroupLocalInvocationId"; + case BuiltInVertexIndex: return "VertexIndex"; + case BuiltInInstanceIndex: return "InstanceIndex"; + case BuiltInCoreIDARM: return "CoreIDARM"; + case BuiltInCoreCountARM: return "CoreCountARM"; + case BuiltInCoreMaxIDARM: return "CoreMaxIDARM"; + case BuiltInWarpIDARM: return "WarpIDARM"; + case BuiltInWarpMaxIDARM: return "WarpMaxIDARM"; + case BuiltInSubgroupEqMask: return "SubgroupEqMask"; + case BuiltInSubgroupGeMask: return "SubgroupGeMask"; + case BuiltInSubgroupGtMask: return "SubgroupGtMask"; + case BuiltInSubgroupLeMask: return "SubgroupLeMask"; + case BuiltInSubgroupLtMask: return "SubgroupLtMask"; + case BuiltInBaseVertex: return "BaseVertex"; + case BuiltInBaseInstance: return "BaseInstance"; + case BuiltInDrawIndex: return "DrawIndex"; + case BuiltInPrimitiveShadingRateKHR: return "PrimitiveShadingRateKHR"; + case BuiltInDeviceIndex: return "DeviceIndex"; + case BuiltInViewIndex: return "ViewIndex"; + case BuiltInShadingRateKHR: return "ShadingRateKHR"; + case BuiltInTileOffsetQCOM: return "TileOffsetQCOM"; + case BuiltInTileDimensionQCOM: return "TileDimensionQCOM"; + case BuiltInTileApronSizeQCOM: return "TileApronSizeQCOM"; + case BuiltInBaryCoordNoPerspAMD: return "BaryCoordNoPerspAMD"; + case BuiltInBaryCoordNoPerspCentroidAMD: return "BaryCoordNoPerspCentroidAMD"; + case BuiltInBaryCoordNoPerspSampleAMD: return "BaryCoordNoPerspSampleAMD"; + case BuiltInBaryCoordSmoothAMD: return "BaryCoordSmoothAMD"; + case BuiltInBaryCoordSmoothCentroidAMD: return "BaryCoordSmoothCentroidAMD"; + case BuiltInBaryCoordSmoothSampleAMD: return "BaryCoordSmoothSampleAMD"; + case BuiltInBaryCoordPullModelAMD: return "BaryCoordPullModelAMD"; + case BuiltInFragStencilRefEXT: return "FragStencilRefEXT"; + case BuiltInRemainingRecursionLevelsAMDX: return "RemainingRecursionLevelsAMDX"; + case BuiltInShaderIndexAMDX: return "ShaderIndexAMDX"; + case BuiltInViewportMaskNV: return "ViewportMaskNV"; + case BuiltInSecondaryPositionNV: return "SecondaryPositionNV"; + case BuiltInSecondaryViewportMaskNV: return "SecondaryViewportMaskNV"; + case BuiltInPositionPerViewNV: return "PositionPerViewNV"; + case BuiltInViewportMaskPerViewNV: return "ViewportMaskPerViewNV"; + case BuiltInFullyCoveredEXT: return "FullyCoveredEXT"; + case BuiltInTaskCountNV: return "TaskCountNV"; + case BuiltInPrimitiveCountNV: return "PrimitiveCountNV"; + case BuiltInPrimitiveIndicesNV: return "PrimitiveIndicesNV"; + case BuiltInClipDistancePerViewNV: return "ClipDistancePerViewNV"; + case BuiltInCullDistancePerViewNV: return "CullDistancePerViewNV"; + case BuiltInLayerPerViewNV: return "LayerPerViewNV"; + case BuiltInMeshViewCountNV: return "MeshViewCountNV"; + case BuiltInMeshViewIndicesNV: return "MeshViewIndicesNV"; + case BuiltInBaryCoordKHR: return "BaryCoordKHR"; + case BuiltInBaryCoordNoPerspKHR: return "BaryCoordNoPerspKHR"; + case BuiltInFragSizeEXT: return "FragSizeEXT"; + case BuiltInFragInvocationCountEXT: return "FragInvocationCountEXT"; + case BuiltInPrimitivePointIndicesEXT: return "PrimitivePointIndicesEXT"; + case BuiltInPrimitiveLineIndicesEXT: return "PrimitiveLineIndicesEXT"; + case BuiltInPrimitiveTriangleIndicesEXT: return "PrimitiveTriangleIndicesEXT"; + case BuiltInCullPrimitiveEXT: return "CullPrimitiveEXT"; + case BuiltInLaunchIdKHR: return "LaunchIdKHR"; + case BuiltInLaunchSizeKHR: return "LaunchSizeKHR"; + case BuiltInWorldRayOriginKHR: return "WorldRayOriginKHR"; + case BuiltInWorldRayDirectionKHR: return "WorldRayDirectionKHR"; + case BuiltInObjectRayOriginKHR: return "ObjectRayOriginKHR"; + case BuiltInObjectRayDirectionKHR: return "ObjectRayDirectionKHR"; + case BuiltInRayTminKHR: return "RayTminKHR"; + case BuiltInRayTmaxKHR: return "RayTmaxKHR"; + case BuiltInInstanceCustomIndexKHR: return "InstanceCustomIndexKHR"; + case BuiltInObjectToWorldKHR: return "ObjectToWorldKHR"; + case BuiltInWorldToObjectKHR: return "WorldToObjectKHR"; + case BuiltInHitTNV: return "HitTNV"; + case BuiltInHitKindKHR: return "HitKindKHR"; + case BuiltInCurrentRayTimeNV: return "CurrentRayTimeNV"; + case BuiltInHitTriangleVertexPositionsKHR: return "HitTriangleVertexPositionsKHR"; + case BuiltInHitMicroTriangleVertexPositionsNV: return "HitMicroTriangleVertexPositionsNV"; + case BuiltInHitMicroTriangleVertexBarycentricsNV: return "HitMicroTriangleVertexBarycentricsNV"; + case BuiltInIncomingRayFlagsKHR: return "IncomingRayFlagsKHR"; + case BuiltInRayGeometryIndexKHR: return "RayGeometryIndexKHR"; + case BuiltInHitIsSphereNV: return "HitIsSphereNV"; + case BuiltInHitIsLSSNV: return "HitIsLSSNV"; + case BuiltInHitSpherePositionNV: return "HitSpherePositionNV"; + case BuiltInWarpsPerSMNV: return "WarpsPerSMNV"; + case BuiltInSMCountNV: return "SMCountNV"; + case BuiltInWarpIDNV: return "WarpIDNV"; + case BuiltInSMIDNV: return "SMIDNV"; + case BuiltInHitLSSPositionsNV: return "HitLSSPositionsNV"; + case BuiltInHitKindFrontFacingMicroTriangleNV: return "HitKindFrontFacingMicroTriangleNV"; + case BuiltInHitKindBackFacingMicroTriangleNV: return "HitKindBackFacingMicroTriangleNV"; + case BuiltInHitSphereRadiusNV: return "HitSphereRadiusNV"; + case BuiltInHitLSSRadiiNV: return "HitLSSRadiiNV"; + case BuiltInClusterIDNV: return "ClusterIDNV"; + case BuiltInCullMaskKHR: return "CullMaskKHR"; + default: return "Unknown"; + } +} + +inline const char* ScopeToString(Scope value) { + switch (value) { + case ScopeCrossDevice: return "CrossDevice"; + case ScopeDevice: return "Device"; + case ScopeWorkgroup: return "Workgroup"; + case ScopeSubgroup: return "Subgroup"; + case ScopeInvocation: return "Invocation"; + case ScopeQueueFamily: return "QueueFamily"; + case ScopeShaderCallKHR: return "ShaderCallKHR"; + default: return "Unknown"; + } +} + +inline const char* GroupOperationToString(GroupOperation value) { + switch (value) { + case GroupOperationReduce: return "Reduce"; + case GroupOperationInclusiveScan: return "InclusiveScan"; + case GroupOperationExclusiveScan: return "ExclusiveScan"; + case GroupOperationClusteredReduce: return "ClusteredReduce"; + case GroupOperationPartitionedReduceNV: return "PartitionedReduceNV"; + case GroupOperationPartitionedInclusiveScanNV: return "PartitionedInclusiveScanNV"; + case GroupOperationPartitionedExclusiveScanNV: return "PartitionedExclusiveScanNV"; + default: return "Unknown"; + } +} + +inline const char* KernelEnqueueFlagsToString(KernelEnqueueFlags value) { + switch (value) { + case KernelEnqueueFlagsNoWait: return "NoWait"; + case KernelEnqueueFlagsWaitKernel: return "WaitKernel"; + case KernelEnqueueFlagsWaitWorkGroup: return "WaitWorkGroup"; + default: return "Unknown"; + } +} + +inline const char* CapabilityToString(Capability value) { + switch (value) { + case CapabilityMatrix: return "Matrix"; + case CapabilityShader: return "Shader"; + case CapabilityGeometry: return "Geometry"; + case CapabilityTessellation: return "Tessellation"; + case CapabilityAddresses: return "Addresses"; + case CapabilityLinkage: return "Linkage"; + case CapabilityKernel: return "Kernel"; + case CapabilityVector16: return "Vector16"; + case CapabilityFloat16Buffer: return "Float16Buffer"; + case CapabilityFloat16: return "Float16"; + case CapabilityFloat64: return "Float64"; + case CapabilityInt64: return "Int64"; + case CapabilityInt64Atomics: return "Int64Atomics"; + case CapabilityImageBasic: return "ImageBasic"; + case CapabilityImageReadWrite: return "ImageReadWrite"; + case CapabilityImageMipmap: return "ImageMipmap"; + case CapabilityPipes: return "Pipes"; + case CapabilityGroups: return "Groups"; + case CapabilityDeviceEnqueue: return "DeviceEnqueue"; + case CapabilityLiteralSampler: return "LiteralSampler"; + case CapabilityAtomicStorage: return "AtomicStorage"; + case CapabilityInt16: return "Int16"; + case CapabilityTessellationPointSize: return "TessellationPointSize"; + case CapabilityGeometryPointSize: return "GeometryPointSize"; + case CapabilityImageGatherExtended: return "ImageGatherExtended"; + case CapabilityStorageImageMultisample: return "StorageImageMultisample"; + case CapabilityUniformBufferArrayDynamicIndexing: return "UniformBufferArrayDynamicIndexing"; + case CapabilitySampledImageArrayDynamicIndexing: return "SampledImageArrayDynamicIndexing"; + case CapabilityStorageBufferArrayDynamicIndexing: return "StorageBufferArrayDynamicIndexing"; + case CapabilityStorageImageArrayDynamicIndexing: return "StorageImageArrayDynamicIndexing"; + case CapabilityClipDistance: return "ClipDistance"; + case CapabilityCullDistance: return "CullDistance"; + case CapabilityImageCubeArray: return "ImageCubeArray"; + case CapabilitySampleRateShading: return "SampleRateShading"; + case CapabilityImageRect: return "ImageRect"; + case CapabilitySampledRect: return "SampledRect"; + case CapabilityGenericPointer: return "GenericPointer"; + case CapabilityInt8: return "Int8"; + case CapabilityInputAttachment: return "InputAttachment"; + case CapabilitySparseResidency: return "SparseResidency"; + case CapabilityMinLod: return "MinLod"; + case CapabilitySampled1D: return "Sampled1D"; + case CapabilityImage1D: return "Image1D"; + case CapabilitySampledCubeArray: return "SampledCubeArray"; + case CapabilitySampledBuffer: return "SampledBuffer"; + case CapabilityImageBuffer: return "ImageBuffer"; + case CapabilityImageMSArray: return "ImageMSArray"; + case CapabilityStorageImageExtendedFormats: return "StorageImageExtendedFormats"; + case CapabilityImageQuery: return "ImageQuery"; + case CapabilityDerivativeControl: return "DerivativeControl"; + case CapabilityInterpolationFunction: return "InterpolationFunction"; + case CapabilityTransformFeedback: return "TransformFeedback"; + case CapabilityGeometryStreams: return "GeometryStreams"; + case CapabilityStorageImageReadWithoutFormat: return "StorageImageReadWithoutFormat"; + case CapabilityStorageImageWriteWithoutFormat: return "StorageImageWriteWithoutFormat"; + case CapabilityMultiViewport: return "MultiViewport"; + case CapabilitySubgroupDispatch: return "SubgroupDispatch"; + case CapabilityNamedBarrier: return "NamedBarrier"; + case CapabilityPipeStorage: return "PipeStorage"; + case CapabilityGroupNonUniform: return "GroupNonUniform"; + case CapabilityGroupNonUniformVote: return "GroupNonUniformVote"; + case CapabilityGroupNonUniformArithmetic: return "GroupNonUniformArithmetic"; + case CapabilityGroupNonUniformBallot: return "GroupNonUniformBallot"; + case CapabilityGroupNonUniformShuffle: return "GroupNonUniformShuffle"; + case CapabilityGroupNonUniformShuffleRelative: return "GroupNonUniformShuffleRelative"; + case CapabilityGroupNonUniformClustered: return "GroupNonUniformClustered"; + case CapabilityGroupNonUniformQuad: return "GroupNonUniformQuad"; + case CapabilityShaderLayer: return "ShaderLayer"; + case CapabilityShaderViewportIndex: return "ShaderViewportIndex"; + case CapabilityUniformDecoration: return "UniformDecoration"; + case CapabilityCoreBuiltinsARM: return "CoreBuiltinsARM"; + case CapabilityTileImageColorReadAccessEXT: return "TileImageColorReadAccessEXT"; + case CapabilityTileImageDepthReadAccessEXT: return "TileImageDepthReadAccessEXT"; + case CapabilityTileImageStencilReadAccessEXT: return "TileImageStencilReadAccessEXT"; + case CapabilityCooperativeMatrixLayoutsARM: return "CooperativeMatrixLayoutsARM"; + case CapabilityFragmentShadingRateKHR: return "FragmentShadingRateKHR"; + case CapabilitySubgroupBallotKHR: return "SubgroupBallotKHR"; + case CapabilityDrawParameters: return "DrawParameters"; + case CapabilityWorkgroupMemoryExplicitLayoutKHR: return "WorkgroupMemoryExplicitLayoutKHR"; + case CapabilityWorkgroupMemoryExplicitLayout8BitAccessKHR: return "WorkgroupMemoryExplicitLayout8BitAccessKHR"; + case CapabilityWorkgroupMemoryExplicitLayout16BitAccessKHR: return "WorkgroupMemoryExplicitLayout16BitAccessKHR"; + case CapabilitySubgroupVoteKHR: return "SubgroupVoteKHR"; + case CapabilityStorageBuffer16BitAccess: return "StorageBuffer16BitAccess"; + case CapabilityStorageUniform16: return "StorageUniform16"; + case CapabilityStoragePushConstant16: return "StoragePushConstant16"; + case CapabilityStorageInputOutput16: return "StorageInputOutput16"; + case CapabilityDeviceGroup: return "DeviceGroup"; + case CapabilityMultiView: return "MultiView"; + case CapabilityVariablePointersStorageBuffer: return "VariablePointersStorageBuffer"; + case CapabilityVariablePointers: return "VariablePointers"; + case CapabilityAtomicStorageOps: return "AtomicStorageOps"; + case CapabilitySampleMaskPostDepthCoverage: return "SampleMaskPostDepthCoverage"; + case CapabilityStorageBuffer8BitAccess: return "StorageBuffer8BitAccess"; + case CapabilityUniformAndStorageBuffer8BitAccess: return "UniformAndStorageBuffer8BitAccess"; + case CapabilityStoragePushConstant8: return "StoragePushConstant8"; + case CapabilityDenormPreserve: return "DenormPreserve"; + case CapabilityDenormFlushToZero: return "DenormFlushToZero"; + case CapabilitySignedZeroInfNanPreserve: return "SignedZeroInfNanPreserve"; + case CapabilityRoundingModeRTE: return "RoundingModeRTE"; + case CapabilityRoundingModeRTZ: return "RoundingModeRTZ"; + case CapabilityRayQueryProvisionalKHR: return "RayQueryProvisionalKHR"; + case CapabilityRayQueryKHR: return "RayQueryKHR"; + case CapabilityUntypedPointersKHR: return "UntypedPointersKHR"; + case CapabilityRayTraversalPrimitiveCullingKHR: return "RayTraversalPrimitiveCullingKHR"; + case CapabilityRayTracingKHR: return "RayTracingKHR"; + case CapabilityTextureSampleWeightedQCOM: return "TextureSampleWeightedQCOM"; + case CapabilityTextureBoxFilterQCOM: return "TextureBoxFilterQCOM"; + case CapabilityTextureBlockMatchQCOM: return "TextureBlockMatchQCOM"; + case CapabilityTileShadingQCOM: return "TileShadingQCOM"; + case CapabilityTextureBlockMatch2QCOM: return "TextureBlockMatch2QCOM"; + case CapabilityFloat16ImageAMD: return "Float16ImageAMD"; + case CapabilityImageGatherBiasLodAMD: return "ImageGatherBiasLodAMD"; + case CapabilityFragmentMaskAMD: return "FragmentMaskAMD"; + case CapabilityStencilExportEXT: return "StencilExportEXT"; + case CapabilityImageReadWriteLodAMD: return "ImageReadWriteLodAMD"; + case CapabilityInt64ImageEXT: return "Int64ImageEXT"; + case CapabilityShaderClockKHR: return "ShaderClockKHR"; + case CapabilityShaderEnqueueAMDX: return "ShaderEnqueueAMDX"; + case CapabilityQuadControlKHR: return "QuadControlKHR"; + case CapabilityBFloat16TypeKHR: return "BFloat16TypeKHR"; + case CapabilityBFloat16DotProductKHR: return "BFloat16DotProductKHR"; + case CapabilityBFloat16CooperativeMatrixKHR: return "BFloat16CooperativeMatrixKHR"; + case CapabilitySampleMaskOverrideCoverageNV: return "SampleMaskOverrideCoverageNV"; + case CapabilityGeometryShaderPassthroughNV: return "GeometryShaderPassthroughNV"; + case CapabilityShaderViewportIndexLayerEXT: return "ShaderViewportIndexLayerEXT"; + case CapabilityShaderViewportMaskNV: return "ShaderViewportMaskNV"; + case CapabilityShaderStereoViewNV: return "ShaderStereoViewNV"; + case CapabilityPerViewAttributesNV: return "PerViewAttributesNV"; + case CapabilityFragmentFullyCoveredEXT: return "FragmentFullyCoveredEXT"; + case CapabilityMeshShadingNV: return "MeshShadingNV"; + case CapabilityImageFootprintNV: return "ImageFootprintNV"; + case CapabilityMeshShadingEXT: return "MeshShadingEXT"; + case CapabilityFragmentBarycentricKHR: return "FragmentBarycentricKHR"; + case CapabilityComputeDerivativeGroupQuadsKHR: return "ComputeDerivativeGroupQuadsKHR"; + case CapabilityFragmentDensityEXT: return "FragmentDensityEXT"; + case CapabilityGroupNonUniformPartitionedNV: return "GroupNonUniformPartitionedNV"; + case CapabilityShaderNonUniform: return "ShaderNonUniform"; + case CapabilityRuntimeDescriptorArray: return "RuntimeDescriptorArray"; + case CapabilityInputAttachmentArrayDynamicIndexing: return "InputAttachmentArrayDynamicIndexing"; + case CapabilityUniformTexelBufferArrayDynamicIndexing: return "UniformTexelBufferArrayDynamicIndexing"; + case CapabilityStorageTexelBufferArrayDynamicIndexing: return "StorageTexelBufferArrayDynamicIndexing"; + case CapabilityUniformBufferArrayNonUniformIndexing: return "UniformBufferArrayNonUniformIndexing"; + case CapabilitySampledImageArrayNonUniformIndexing: return "SampledImageArrayNonUniformIndexing"; + case CapabilityStorageBufferArrayNonUniformIndexing: return "StorageBufferArrayNonUniformIndexing"; + case CapabilityStorageImageArrayNonUniformIndexing: return "StorageImageArrayNonUniformIndexing"; + case CapabilityInputAttachmentArrayNonUniformIndexing: return "InputAttachmentArrayNonUniformIndexing"; + case CapabilityUniformTexelBufferArrayNonUniformIndexing: return "UniformTexelBufferArrayNonUniformIndexing"; + case CapabilityStorageTexelBufferArrayNonUniformIndexing: return "StorageTexelBufferArrayNonUniformIndexing"; + case CapabilityRayTracingPositionFetchKHR: return "RayTracingPositionFetchKHR"; + case CapabilityRayTracingNV: return "RayTracingNV"; + case CapabilityRayTracingMotionBlurNV: return "RayTracingMotionBlurNV"; + case CapabilityVulkanMemoryModel: return "VulkanMemoryModel"; + case CapabilityVulkanMemoryModelDeviceScope: return "VulkanMemoryModelDeviceScope"; + case CapabilityPhysicalStorageBufferAddresses: return "PhysicalStorageBufferAddresses"; + case CapabilityComputeDerivativeGroupLinearKHR: return "ComputeDerivativeGroupLinearKHR"; + case CapabilityRayTracingProvisionalKHR: return "RayTracingProvisionalKHR"; + case CapabilityCooperativeMatrixNV: return "CooperativeMatrixNV"; + case CapabilityFragmentShaderSampleInterlockEXT: return "FragmentShaderSampleInterlockEXT"; + case CapabilityFragmentShaderShadingRateInterlockEXT: return "FragmentShaderShadingRateInterlockEXT"; + case CapabilityShaderSMBuiltinsNV: return "ShaderSMBuiltinsNV"; + case CapabilityFragmentShaderPixelInterlockEXT: return "FragmentShaderPixelInterlockEXT"; + case CapabilityDemoteToHelperInvocation: return "DemoteToHelperInvocation"; + case CapabilityDisplacementMicromapNV: return "DisplacementMicromapNV"; + case CapabilityRayTracingOpacityMicromapEXT: return "RayTracingOpacityMicromapEXT"; + case CapabilityShaderInvocationReorderNV: return "ShaderInvocationReorderNV"; + case CapabilityBindlessTextureNV: return "BindlessTextureNV"; + case CapabilityRayQueryPositionFetchKHR: return "RayQueryPositionFetchKHR"; + case CapabilityCooperativeVectorNV: return "CooperativeVectorNV"; + case CapabilityAtomicFloat16VectorNV: return "AtomicFloat16VectorNV"; + case CapabilityRayTracingDisplacementMicromapNV: return "RayTracingDisplacementMicromapNV"; + case CapabilityRawAccessChainsNV: return "RawAccessChainsNV"; + case CapabilityRayTracingSpheresGeometryNV: return "RayTracingSpheresGeometryNV"; + case CapabilityRayTracingLinearSweptSpheresGeometryNV: return "RayTracingLinearSweptSpheresGeometryNV"; + case CapabilityCooperativeMatrixReductionsNV: return "CooperativeMatrixReductionsNV"; + case CapabilityCooperativeMatrixConversionsNV: return "CooperativeMatrixConversionsNV"; + case CapabilityCooperativeMatrixPerElementOperationsNV: return "CooperativeMatrixPerElementOperationsNV"; + case CapabilityCooperativeMatrixTensorAddressingNV: return "CooperativeMatrixTensorAddressingNV"; + case CapabilityCooperativeMatrixBlockLoadsNV: return "CooperativeMatrixBlockLoadsNV"; + case CapabilityCooperativeVectorTrainingNV: return "CooperativeVectorTrainingNV"; + case CapabilityRayTracingClusterAccelerationStructureNV: return "RayTracingClusterAccelerationStructureNV"; + case CapabilityTensorAddressingNV: return "TensorAddressingNV"; + case CapabilitySubgroupShuffleINTEL: return "SubgroupShuffleINTEL"; + case CapabilitySubgroupBufferBlockIOINTEL: return "SubgroupBufferBlockIOINTEL"; + case CapabilitySubgroupImageBlockIOINTEL: return "SubgroupImageBlockIOINTEL"; + case CapabilitySubgroupImageMediaBlockIOINTEL: return "SubgroupImageMediaBlockIOINTEL"; + case CapabilityRoundToInfinityINTEL: return "RoundToInfinityINTEL"; + case CapabilityFloatingPointModeINTEL: return "FloatingPointModeINTEL"; + case CapabilityIntegerFunctions2INTEL: return "IntegerFunctions2INTEL"; + case CapabilityFunctionPointersINTEL: return "FunctionPointersINTEL"; + case CapabilityIndirectReferencesINTEL: return "IndirectReferencesINTEL"; + case CapabilityAsmINTEL: return "AsmINTEL"; + case CapabilityAtomicFloat32MinMaxEXT: return "AtomicFloat32MinMaxEXT"; + case CapabilityAtomicFloat64MinMaxEXT: return "AtomicFloat64MinMaxEXT"; + case CapabilityAtomicFloat16MinMaxEXT: return "AtomicFloat16MinMaxEXT"; + case CapabilityVectorComputeINTEL: return "VectorComputeINTEL"; + case CapabilityVectorAnyINTEL: return "VectorAnyINTEL"; + case CapabilityExpectAssumeKHR: return "ExpectAssumeKHR"; + case CapabilitySubgroupAvcMotionEstimationINTEL: return "SubgroupAvcMotionEstimationINTEL"; + case CapabilitySubgroupAvcMotionEstimationIntraINTEL: return "SubgroupAvcMotionEstimationIntraINTEL"; + case CapabilitySubgroupAvcMotionEstimationChromaINTEL: return "SubgroupAvcMotionEstimationChromaINTEL"; + case CapabilityVariableLengthArrayINTEL: return "VariableLengthArrayINTEL"; + case CapabilityFunctionFloatControlINTEL: return "FunctionFloatControlINTEL"; + case CapabilityFPGAMemoryAttributesINTEL: return "FPGAMemoryAttributesINTEL"; + case CapabilityFPFastMathModeINTEL: return "FPFastMathModeINTEL"; + case CapabilityArbitraryPrecisionIntegersINTEL: return "ArbitraryPrecisionIntegersINTEL"; + case CapabilityArbitraryPrecisionFloatingPointINTEL: return "ArbitraryPrecisionFloatingPointINTEL"; + case CapabilityUnstructuredLoopControlsINTEL: return "UnstructuredLoopControlsINTEL"; + case CapabilityFPGALoopControlsINTEL: return "FPGALoopControlsINTEL"; + case CapabilityKernelAttributesINTEL: return "KernelAttributesINTEL"; + case CapabilityFPGAKernelAttributesINTEL: return "FPGAKernelAttributesINTEL"; + case CapabilityFPGAMemoryAccessesINTEL: return "FPGAMemoryAccessesINTEL"; + case CapabilityFPGAClusterAttributesINTEL: return "FPGAClusterAttributesINTEL"; + case CapabilityLoopFuseINTEL: return "LoopFuseINTEL"; + case CapabilityFPGADSPControlINTEL: return "FPGADSPControlINTEL"; + case CapabilityMemoryAccessAliasingINTEL: return "MemoryAccessAliasingINTEL"; + case CapabilityFPGAInvocationPipeliningAttributesINTEL: return "FPGAInvocationPipeliningAttributesINTEL"; + case CapabilityFPGABufferLocationINTEL: return "FPGABufferLocationINTEL"; + case CapabilityArbitraryPrecisionFixedPointINTEL: return "ArbitraryPrecisionFixedPointINTEL"; + case CapabilityUSMStorageClassesINTEL: return "USMStorageClassesINTEL"; + case CapabilityRuntimeAlignedAttributeINTEL: return "RuntimeAlignedAttributeINTEL"; + case CapabilityIOPipesINTEL: return "IOPipesINTEL"; + case CapabilityBlockingPipesINTEL: return "BlockingPipesINTEL"; + case CapabilityFPGARegINTEL: return "FPGARegINTEL"; + case CapabilityDotProductInputAll: return "DotProductInputAll"; + case CapabilityDotProductInput4x8Bit: return "DotProductInput4x8Bit"; + case CapabilityDotProductInput4x8BitPacked: return "DotProductInput4x8BitPacked"; + case CapabilityDotProduct: return "DotProduct"; + case CapabilityRayCullMaskKHR: return "RayCullMaskKHR"; + case CapabilityCooperativeMatrixKHR: return "CooperativeMatrixKHR"; + case CapabilityReplicatedCompositesEXT: return "ReplicatedCompositesEXT"; + case CapabilityBitInstructions: return "BitInstructions"; + case CapabilityGroupNonUniformRotateKHR: return "GroupNonUniformRotateKHR"; + case CapabilityFloatControls2: return "FloatControls2"; + case CapabilityAtomicFloat32AddEXT: return "AtomicFloat32AddEXT"; + case CapabilityAtomicFloat64AddEXT: return "AtomicFloat64AddEXT"; + case CapabilityLongCompositesINTEL: return "LongCompositesINTEL"; + case CapabilityOptNoneEXT: return "OptNoneEXT"; + case CapabilityAtomicFloat16AddEXT: return "AtomicFloat16AddEXT"; + case CapabilityDebugInfoModuleINTEL: return "DebugInfoModuleINTEL"; + case CapabilityBFloat16ConversionINTEL: return "BFloat16ConversionINTEL"; + case CapabilitySplitBarrierINTEL: return "SplitBarrierINTEL"; + case CapabilityArithmeticFenceEXT: return "ArithmeticFenceEXT"; + case CapabilityFPGAClusterAttributesV2INTEL: return "FPGAClusterAttributesV2INTEL"; + case CapabilityFPGAKernelAttributesv2INTEL: return "FPGAKernelAttributesv2INTEL"; + case CapabilityTaskSequenceINTEL: return "TaskSequenceINTEL"; + case CapabilityFPMaxErrorINTEL: return "FPMaxErrorINTEL"; + case CapabilityFPGALatencyControlINTEL: return "FPGALatencyControlINTEL"; + case CapabilityFPGAArgumentInterfacesINTEL: return "FPGAArgumentInterfacesINTEL"; + case CapabilityGlobalVariableHostAccessINTEL: return "GlobalVariableHostAccessINTEL"; + case CapabilityGlobalVariableFPGADecorationsINTEL: return "GlobalVariableFPGADecorationsINTEL"; + case CapabilitySubgroupBufferPrefetchINTEL: return "SubgroupBufferPrefetchINTEL"; + case CapabilitySubgroup2DBlockIOINTEL: return "Subgroup2DBlockIOINTEL"; + case CapabilitySubgroup2DBlockTransformINTEL: return "Subgroup2DBlockTransformINTEL"; + case CapabilitySubgroup2DBlockTransposeINTEL: return "Subgroup2DBlockTransposeINTEL"; + case CapabilitySubgroupMatrixMultiplyAccumulateINTEL: return "SubgroupMatrixMultiplyAccumulateINTEL"; + case CapabilityTernaryBitwiseFunctionINTEL: return "TernaryBitwiseFunctionINTEL"; + case CapabilityGroupUniformArithmeticKHR: return "GroupUniformArithmeticKHR"; + case CapabilityTensorFloat32RoundingINTEL: return "TensorFloat32RoundingINTEL"; + case CapabilityMaskedGatherScatterINTEL: return "MaskedGatherScatterINTEL"; + case CapabilityCacheControlsINTEL: return "CacheControlsINTEL"; + case CapabilityRegisterLimitsINTEL: return "RegisterLimitsINTEL"; + default: return "Unknown"; + } +} + +inline const char* RayQueryIntersectionToString(RayQueryIntersection value) { + switch (value) { + case RayQueryIntersectionRayQueryCandidateIntersectionKHR: return "RayQueryCandidateIntersectionKHR"; + case RayQueryIntersectionRayQueryCommittedIntersectionKHR: return "RayQueryCommittedIntersectionKHR"; + default: return "Unknown"; + } +} + +inline const char* RayQueryCommittedIntersectionTypeToString(RayQueryCommittedIntersectionType value) { + switch (value) { + case RayQueryCommittedIntersectionTypeRayQueryCommittedIntersectionNoneKHR: return "RayQueryCommittedIntersectionNoneKHR"; + case RayQueryCommittedIntersectionTypeRayQueryCommittedIntersectionTriangleKHR: return "RayQueryCommittedIntersectionTriangleKHR"; + case RayQueryCommittedIntersectionTypeRayQueryCommittedIntersectionGeneratedKHR: return "RayQueryCommittedIntersectionGeneratedKHR"; + default: return "Unknown"; + } +} + +inline const char* RayQueryCandidateIntersectionTypeToString(RayQueryCandidateIntersectionType value) { + switch (value) { + case RayQueryCandidateIntersectionTypeRayQueryCandidateIntersectionTriangleKHR: return "RayQueryCandidateIntersectionTriangleKHR"; + case RayQueryCandidateIntersectionTypeRayQueryCandidateIntersectionAABBKHR: return "RayQueryCandidateIntersectionAABBKHR"; + default: return "Unknown"; + } +} + +inline const char* FPDenormModeToString(FPDenormMode value) { + switch (value) { + case FPDenormModePreserve: return "Preserve"; + case FPDenormModeFlushToZero: return "FlushToZero"; + default: return "Unknown"; + } +} + +inline const char* FPOperationModeToString(FPOperationMode value) { + switch (value) { + case FPOperationModeIEEE: return "IEEE"; + case FPOperationModeALT: return "ALT"; + default: return "Unknown"; + } +} + +inline const char* QuantizationModesToString(QuantizationModes value) { + switch (value) { + case QuantizationModesTRN: return "TRN"; + case QuantizationModesTRN_ZERO: return "TRN_ZERO"; + case QuantizationModesRND: return "RND"; + case QuantizationModesRND_ZERO: return "RND_ZERO"; + case QuantizationModesRND_INF: return "RND_INF"; + case QuantizationModesRND_MIN_INF: return "RND_MIN_INF"; + case QuantizationModesRND_CONV: return "RND_CONV"; + case QuantizationModesRND_CONV_ODD: return "RND_CONV_ODD"; + default: return "Unknown"; + } +} + +inline const char* OverflowModesToString(OverflowModes value) { + switch (value) { + case OverflowModesWRAP: return "WRAP"; + case OverflowModesSAT: return "SAT"; + case OverflowModesSAT_ZERO: return "SAT_ZERO"; + case OverflowModesSAT_SYM: return "SAT_SYM"; + default: return "Unknown"; + } +} + +inline const char* PackedVectorFormatToString(PackedVectorFormat value) { + switch (value) { + case PackedVectorFormatPackedVectorFormat4x8Bit: return "PackedVectorFormat4x8Bit"; + default: return "Unknown"; + } +} + +inline const char* CooperativeMatrixLayoutToString(CooperativeMatrixLayout value) { + switch (value) { + case CooperativeMatrixLayoutRowMajorKHR: return "RowMajorKHR"; + case CooperativeMatrixLayoutColumnMajorKHR: return "ColumnMajorKHR"; + case CooperativeMatrixLayoutRowBlockedInterleavedARM: return "RowBlockedInterleavedARM"; + case CooperativeMatrixLayoutColumnBlockedInterleavedARM: return "ColumnBlockedInterleavedARM"; + default: return "Unknown"; + } +} + +inline const char* CooperativeMatrixUseToString(CooperativeMatrixUse value) { + switch (value) { + case CooperativeMatrixUseMatrixAKHR: return "MatrixAKHR"; + case CooperativeMatrixUseMatrixBKHR: return "MatrixBKHR"; + case CooperativeMatrixUseMatrixAccumulatorKHR: return "MatrixAccumulatorKHR"; + default: return "Unknown"; + } +} + +inline const char* TensorClampModeToString(TensorClampMode value) { + switch (value) { + case TensorClampModeUndefined: return "Undefined"; + case TensorClampModeConstant: return "Constant"; + case TensorClampModeClampToEdge: return "ClampToEdge"; + case TensorClampModeRepeat: return "Repeat"; + case TensorClampModeRepeatMirrored: return "RepeatMirrored"; + default: return "Unknown"; + } +} + +inline const char* InitializationModeQualifierToString(InitializationModeQualifier value) { + switch (value) { + case InitializationModeQualifierInitOnDeviceReprogramINTEL: return "InitOnDeviceReprogramINTEL"; + case InitializationModeQualifierInitOnDeviceResetINTEL: return "InitOnDeviceResetINTEL"; + default: return "Unknown"; + } +} + +inline const char* HostAccessQualifierToString(HostAccessQualifier value) { + switch (value) { + case HostAccessQualifierNoneINTEL: return "NoneINTEL"; + case HostAccessQualifierReadINTEL: return "ReadINTEL"; + case HostAccessQualifierWriteINTEL: return "WriteINTEL"; + case HostAccessQualifierReadWriteINTEL: return "ReadWriteINTEL"; + default: return "Unknown"; + } +} + +inline const char* LoadCacheControlToString(LoadCacheControl value) { + switch (value) { + case LoadCacheControlUncachedINTEL: return "UncachedINTEL"; + case LoadCacheControlCachedINTEL: return "CachedINTEL"; + case LoadCacheControlStreamingINTEL: return "StreamingINTEL"; + case LoadCacheControlInvalidateAfterReadINTEL: return "InvalidateAfterReadINTEL"; + case LoadCacheControlConstCachedINTEL: return "ConstCachedINTEL"; + default: return "Unknown"; + } +} + +inline const char* StoreCacheControlToString(StoreCacheControl value) { + switch (value) { + case StoreCacheControlUncachedINTEL: return "UncachedINTEL"; + case StoreCacheControlWriteThroughINTEL: return "WriteThroughINTEL"; + case StoreCacheControlWriteBackINTEL: return "WriteBackINTEL"; + case StoreCacheControlStreamingINTEL: return "StreamingINTEL"; + default: return "Unknown"; + } +} + +inline const char* NamedMaximumNumberOfRegistersToString(NamedMaximumNumberOfRegisters value) { + switch (value) { + case NamedMaximumNumberOfRegistersAutoINTEL: return "AutoINTEL"; + default: return "Unknown"; + } +} + +inline const char* FPEncodingToString(FPEncoding value) { + switch (value) { + case FPEncodingBFloat16KHR: return "BFloat16KHR"; + default: return "Unknown"; + } +} + +inline const char* CooperativeVectorMatrixLayoutToString(CooperativeVectorMatrixLayout value) { + switch (value) { + case CooperativeVectorMatrixLayoutRowMajorNV: return "RowMajorNV"; + case CooperativeVectorMatrixLayoutColumnMajorNV: return "ColumnMajorNV"; + case CooperativeVectorMatrixLayoutInferencingOptimalNV: return "InferencingOptimalNV"; + case CooperativeVectorMatrixLayoutTrainingOptimalNV: return "TrainingOptimalNV"; + default: return "Unknown"; + } +} + +inline const char* ComponentTypeToString(ComponentType value) { + switch (value) { + case ComponentTypeFloat16NV: return "Float16NV"; + case ComponentTypeFloat32NV: return "Float32NV"; + case ComponentTypeFloat64NV: return "Float64NV"; + case ComponentTypeSignedInt8NV: return "SignedInt8NV"; + case ComponentTypeSignedInt16NV: return "SignedInt16NV"; + case ComponentTypeSignedInt32NV: return "SignedInt32NV"; + case ComponentTypeSignedInt64NV: return "SignedInt64NV"; + case ComponentTypeUnsignedInt8NV: return "UnsignedInt8NV"; + case ComponentTypeUnsignedInt16NV: return "UnsignedInt16NV"; + case ComponentTypeUnsignedInt32NV: return "UnsignedInt32NV"; + case ComponentTypeUnsignedInt64NV: return "UnsignedInt64NV"; + case ComponentTypeSignedInt8PackedNV: return "SignedInt8PackedNV"; + case ComponentTypeUnsignedInt8PackedNV: return "UnsignedInt8PackedNV"; + case ComponentTypeFloatE4M3NV: return "FloatE4M3NV"; + case ComponentTypeFloatE5M2NV: return "FloatE5M2NV"; + default: return "Unknown"; + } +} + +inline const char* OpToString(Op value) { + switch (value) { + case OpNop: return "OpNop"; + case OpUndef: return "OpUndef"; + case OpSourceContinued: return "OpSourceContinued"; + case OpSource: return "OpSource"; + case OpSourceExtension: return "OpSourceExtension"; + case OpName: return "OpName"; + case OpMemberName: return "OpMemberName"; + case OpString: return "OpString"; + case OpLine: return "OpLine"; + case OpExtension: return "OpExtension"; + case OpExtInstImport: return "OpExtInstImport"; + case OpExtInst: return "OpExtInst"; + case OpMemoryModel: return "OpMemoryModel"; + case OpEntryPoint: return "OpEntryPoint"; + case OpExecutionMode: return "OpExecutionMode"; + case OpCapability: return "OpCapability"; + case OpTypeVoid: return "OpTypeVoid"; + case OpTypeBool: return "OpTypeBool"; + case OpTypeInt: return "OpTypeInt"; + case OpTypeFloat: return "OpTypeFloat"; + case OpTypeVector: return "OpTypeVector"; + case OpTypeMatrix: return "OpTypeMatrix"; + case OpTypeImage: return "OpTypeImage"; + case OpTypeSampler: return "OpTypeSampler"; + case OpTypeSampledImage: return "OpTypeSampledImage"; + case OpTypeArray: return "OpTypeArray"; + case OpTypeRuntimeArray: return "OpTypeRuntimeArray"; + case OpTypeStruct: return "OpTypeStruct"; + case OpTypeOpaque: return "OpTypeOpaque"; + case OpTypePointer: return "OpTypePointer"; + case OpTypeFunction: return "OpTypeFunction"; + case OpTypeEvent: return "OpTypeEvent"; + case OpTypeDeviceEvent: return "OpTypeDeviceEvent"; + case OpTypeReserveId: return "OpTypeReserveId"; + case OpTypeQueue: return "OpTypeQueue"; + case OpTypePipe: return "OpTypePipe"; + case OpTypeForwardPointer: return "OpTypeForwardPointer"; + case OpConstantTrue: return "OpConstantTrue"; + case OpConstantFalse: return "OpConstantFalse"; + case OpConstant: return "OpConstant"; + case OpConstantComposite: return "OpConstantComposite"; + case OpConstantSampler: return "OpConstantSampler"; + case OpConstantNull: return "OpConstantNull"; + case OpSpecConstantTrue: return "OpSpecConstantTrue"; + case OpSpecConstantFalse: return "OpSpecConstantFalse"; + case OpSpecConstant: return "OpSpecConstant"; + case OpSpecConstantComposite: return "OpSpecConstantComposite"; + case OpSpecConstantOp: return "OpSpecConstantOp"; + case OpFunction: return "OpFunction"; + case OpFunctionParameter: return "OpFunctionParameter"; + case OpFunctionEnd: return "OpFunctionEnd"; + case OpFunctionCall: return "OpFunctionCall"; + case OpVariable: return "OpVariable"; + case OpImageTexelPointer: return "OpImageTexelPointer"; + case OpLoad: return "OpLoad"; + case OpStore: return "OpStore"; + case OpCopyMemory: return "OpCopyMemory"; + case OpCopyMemorySized: return "OpCopyMemorySized"; + case OpAccessChain: return "OpAccessChain"; + case OpInBoundsAccessChain: return "OpInBoundsAccessChain"; + case OpPtrAccessChain: return "OpPtrAccessChain"; + case OpArrayLength: return "OpArrayLength"; + case OpGenericPtrMemSemantics: return "OpGenericPtrMemSemantics"; + case OpInBoundsPtrAccessChain: return "OpInBoundsPtrAccessChain"; + case OpDecorate: return "OpDecorate"; + case OpMemberDecorate: return "OpMemberDecorate"; + case OpDecorationGroup: return "OpDecorationGroup"; + case OpGroupDecorate: return "OpGroupDecorate"; + case OpGroupMemberDecorate: return "OpGroupMemberDecorate"; + case OpVectorExtractDynamic: return "OpVectorExtractDynamic"; + case OpVectorInsertDynamic: return "OpVectorInsertDynamic"; + case OpVectorShuffle: return "OpVectorShuffle"; + case OpCompositeConstruct: return "OpCompositeConstruct"; + case OpCompositeExtract: return "OpCompositeExtract"; + case OpCompositeInsert: return "OpCompositeInsert"; + case OpCopyObject: return "OpCopyObject"; + case OpTranspose: return "OpTranspose"; + case OpSampledImage: return "OpSampledImage"; + case OpImageSampleImplicitLod: return "OpImageSampleImplicitLod"; + case OpImageSampleExplicitLod: return "OpImageSampleExplicitLod"; + case OpImageSampleDrefImplicitLod: return "OpImageSampleDrefImplicitLod"; + case OpImageSampleDrefExplicitLod: return "OpImageSampleDrefExplicitLod"; + case OpImageSampleProjImplicitLod: return "OpImageSampleProjImplicitLod"; + case OpImageSampleProjExplicitLod: return "OpImageSampleProjExplicitLod"; + case OpImageSampleProjDrefImplicitLod: return "OpImageSampleProjDrefImplicitLod"; + case OpImageSampleProjDrefExplicitLod: return "OpImageSampleProjDrefExplicitLod"; + case OpImageFetch: return "OpImageFetch"; + case OpImageGather: return "OpImageGather"; + case OpImageDrefGather: return "OpImageDrefGather"; + case OpImageRead: return "OpImageRead"; + case OpImageWrite: return "OpImageWrite"; + case OpImage: return "OpImage"; + case OpImageQueryFormat: return "OpImageQueryFormat"; + case OpImageQueryOrder: return "OpImageQueryOrder"; + case OpImageQuerySizeLod: return "OpImageQuerySizeLod"; + case OpImageQuerySize: return "OpImageQuerySize"; + case OpImageQueryLod: return "OpImageQueryLod"; + case OpImageQueryLevels: return "OpImageQueryLevels"; + case OpImageQuerySamples: return "OpImageQuerySamples"; + case OpConvertFToU: return "OpConvertFToU"; + case OpConvertFToS: return "OpConvertFToS"; + case OpConvertSToF: return "OpConvertSToF"; + case OpConvertUToF: return "OpConvertUToF"; + case OpUConvert: return "OpUConvert"; + case OpSConvert: return "OpSConvert"; + case OpFConvert: return "OpFConvert"; + case OpQuantizeToF16: return "OpQuantizeToF16"; + case OpConvertPtrToU: return "OpConvertPtrToU"; + case OpSatConvertSToU: return "OpSatConvertSToU"; + case OpSatConvertUToS: return "OpSatConvertUToS"; + case OpConvertUToPtr: return "OpConvertUToPtr"; + case OpPtrCastToGeneric: return "OpPtrCastToGeneric"; + case OpGenericCastToPtr: return "OpGenericCastToPtr"; + case OpGenericCastToPtrExplicit: return "OpGenericCastToPtrExplicit"; + case OpBitcast: return "OpBitcast"; + case OpSNegate: return "OpSNegate"; + case OpFNegate: return "OpFNegate"; + case OpIAdd: return "OpIAdd"; + case OpFAdd: return "OpFAdd"; + case OpISub: return "OpISub"; + case OpFSub: return "OpFSub"; + case OpIMul: return "OpIMul"; + case OpFMul: return "OpFMul"; + case OpUDiv: return "OpUDiv"; + case OpSDiv: return "OpSDiv"; + case OpFDiv: return "OpFDiv"; + case OpUMod: return "OpUMod"; + case OpSRem: return "OpSRem"; + case OpSMod: return "OpSMod"; + case OpFRem: return "OpFRem"; + case OpFMod: return "OpFMod"; + case OpVectorTimesScalar: return "OpVectorTimesScalar"; + case OpMatrixTimesScalar: return "OpMatrixTimesScalar"; + case OpVectorTimesMatrix: return "OpVectorTimesMatrix"; + case OpMatrixTimesVector: return "OpMatrixTimesVector"; + case OpMatrixTimesMatrix: return "OpMatrixTimesMatrix"; + case OpOuterProduct: return "OpOuterProduct"; + case OpDot: return "OpDot"; + case OpIAddCarry: return "OpIAddCarry"; + case OpISubBorrow: return "OpISubBorrow"; + case OpUMulExtended: return "OpUMulExtended"; + case OpSMulExtended: return "OpSMulExtended"; + case OpAny: return "OpAny"; + case OpAll: return "OpAll"; + case OpIsNan: return "OpIsNan"; + case OpIsInf: return "OpIsInf"; + case OpIsFinite: return "OpIsFinite"; + case OpIsNormal: return "OpIsNormal"; + case OpSignBitSet: return "OpSignBitSet"; + case OpLessOrGreater: return "OpLessOrGreater"; + case OpOrdered: return "OpOrdered"; + case OpUnordered: return "OpUnordered"; + case OpLogicalEqual: return "OpLogicalEqual"; + case OpLogicalNotEqual: return "OpLogicalNotEqual"; + case OpLogicalOr: return "OpLogicalOr"; + case OpLogicalAnd: return "OpLogicalAnd"; + case OpLogicalNot: return "OpLogicalNot"; + case OpSelect: return "OpSelect"; + case OpIEqual: return "OpIEqual"; + case OpINotEqual: return "OpINotEqual"; + case OpUGreaterThan: return "OpUGreaterThan"; + case OpSGreaterThan: return "OpSGreaterThan"; + case OpUGreaterThanEqual: return "OpUGreaterThanEqual"; + case OpSGreaterThanEqual: return "OpSGreaterThanEqual"; + case OpULessThan: return "OpULessThan"; + case OpSLessThan: return "OpSLessThan"; + case OpULessThanEqual: return "OpULessThanEqual"; + case OpSLessThanEqual: return "OpSLessThanEqual"; + case OpFOrdEqual: return "OpFOrdEqual"; + case OpFUnordEqual: return "OpFUnordEqual"; + case OpFOrdNotEqual: return "OpFOrdNotEqual"; + case OpFUnordNotEqual: return "OpFUnordNotEqual"; + case OpFOrdLessThan: return "OpFOrdLessThan"; + case OpFUnordLessThan: return "OpFUnordLessThan"; + case OpFOrdGreaterThan: return "OpFOrdGreaterThan"; + case OpFUnordGreaterThan: return "OpFUnordGreaterThan"; + case OpFOrdLessThanEqual: return "OpFOrdLessThanEqual"; + case OpFUnordLessThanEqual: return "OpFUnordLessThanEqual"; + case OpFOrdGreaterThanEqual: return "OpFOrdGreaterThanEqual"; + case OpFUnordGreaterThanEqual: return "OpFUnordGreaterThanEqual"; + case OpShiftRightLogical: return "OpShiftRightLogical"; + case OpShiftRightArithmetic: return "OpShiftRightArithmetic"; + case OpShiftLeftLogical: return "OpShiftLeftLogical"; + case OpBitwiseOr: return "OpBitwiseOr"; + case OpBitwiseXor: return "OpBitwiseXor"; + case OpBitwiseAnd: return "OpBitwiseAnd"; + case OpNot: return "OpNot"; + case OpBitFieldInsert: return "OpBitFieldInsert"; + case OpBitFieldSExtract: return "OpBitFieldSExtract"; + case OpBitFieldUExtract: return "OpBitFieldUExtract"; + case OpBitReverse: return "OpBitReverse"; + case OpBitCount: return "OpBitCount"; + case OpDPdx: return "OpDPdx"; + case OpDPdy: return "OpDPdy"; + case OpFwidth: return "OpFwidth"; + case OpDPdxFine: return "OpDPdxFine"; + case OpDPdyFine: return "OpDPdyFine"; + case OpFwidthFine: return "OpFwidthFine"; + case OpDPdxCoarse: return "OpDPdxCoarse"; + case OpDPdyCoarse: return "OpDPdyCoarse"; + case OpFwidthCoarse: return "OpFwidthCoarse"; + case OpEmitVertex: return "OpEmitVertex"; + case OpEndPrimitive: return "OpEndPrimitive"; + case OpEmitStreamVertex: return "OpEmitStreamVertex"; + case OpEndStreamPrimitive: return "OpEndStreamPrimitive"; + case OpControlBarrier: return "OpControlBarrier"; + case OpMemoryBarrier: return "OpMemoryBarrier"; + case OpAtomicLoad: return "OpAtomicLoad"; + case OpAtomicStore: return "OpAtomicStore"; + case OpAtomicExchange: return "OpAtomicExchange"; + case OpAtomicCompareExchange: return "OpAtomicCompareExchange"; + case OpAtomicCompareExchangeWeak: return "OpAtomicCompareExchangeWeak"; + case OpAtomicIIncrement: return "OpAtomicIIncrement"; + case OpAtomicIDecrement: return "OpAtomicIDecrement"; + case OpAtomicIAdd: return "OpAtomicIAdd"; + case OpAtomicISub: return "OpAtomicISub"; + case OpAtomicSMin: return "OpAtomicSMin"; + case OpAtomicUMin: return "OpAtomicUMin"; + case OpAtomicSMax: return "OpAtomicSMax"; + case OpAtomicUMax: return "OpAtomicUMax"; + case OpAtomicAnd: return "OpAtomicAnd"; + case OpAtomicOr: return "OpAtomicOr"; + case OpAtomicXor: return "OpAtomicXor"; + case OpPhi: return "OpPhi"; + case OpLoopMerge: return "OpLoopMerge"; + case OpSelectionMerge: return "OpSelectionMerge"; + case OpLabel: return "OpLabel"; + case OpBranch: return "OpBranch"; + case OpBranchConditional: return "OpBranchConditional"; + case OpSwitch: return "OpSwitch"; + case OpKill: return "OpKill"; + case OpReturn: return "OpReturn"; + case OpReturnValue: return "OpReturnValue"; + case OpUnreachable: return "OpUnreachable"; + case OpLifetimeStart: return "OpLifetimeStart"; + case OpLifetimeStop: return "OpLifetimeStop"; + case OpGroupAsyncCopy: return "OpGroupAsyncCopy"; + case OpGroupWaitEvents: return "OpGroupWaitEvents"; + case OpGroupAll: return "OpGroupAll"; + case OpGroupAny: return "OpGroupAny"; + case OpGroupBroadcast: return "OpGroupBroadcast"; + case OpGroupIAdd: return "OpGroupIAdd"; + case OpGroupFAdd: return "OpGroupFAdd"; + case OpGroupFMin: return "OpGroupFMin"; + case OpGroupUMin: return "OpGroupUMin"; + case OpGroupSMin: return "OpGroupSMin"; + case OpGroupFMax: return "OpGroupFMax"; + case OpGroupUMax: return "OpGroupUMax"; + case OpGroupSMax: return "OpGroupSMax"; + case OpReadPipe: return "OpReadPipe"; + case OpWritePipe: return "OpWritePipe"; + case OpReservedReadPipe: return "OpReservedReadPipe"; + case OpReservedWritePipe: return "OpReservedWritePipe"; + case OpReserveReadPipePackets: return "OpReserveReadPipePackets"; + case OpReserveWritePipePackets: return "OpReserveWritePipePackets"; + case OpCommitReadPipe: return "OpCommitReadPipe"; + case OpCommitWritePipe: return "OpCommitWritePipe"; + case OpIsValidReserveId: return "OpIsValidReserveId"; + case OpGetNumPipePackets: return "OpGetNumPipePackets"; + case OpGetMaxPipePackets: return "OpGetMaxPipePackets"; + case OpGroupReserveReadPipePackets: return "OpGroupReserveReadPipePackets"; + case OpGroupReserveWritePipePackets: return "OpGroupReserveWritePipePackets"; + case OpGroupCommitReadPipe: return "OpGroupCommitReadPipe"; + case OpGroupCommitWritePipe: return "OpGroupCommitWritePipe"; + case OpEnqueueMarker: return "OpEnqueueMarker"; + case OpEnqueueKernel: return "OpEnqueueKernel"; + case OpGetKernelNDrangeSubGroupCount: return "OpGetKernelNDrangeSubGroupCount"; + case OpGetKernelNDrangeMaxSubGroupSize: return "OpGetKernelNDrangeMaxSubGroupSize"; + case OpGetKernelWorkGroupSize: return "OpGetKernelWorkGroupSize"; + case OpGetKernelPreferredWorkGroupSizeMultiple: return "OpGetKernelPreferredWorkGroupSizeMultiple"; + case OpRetainEvent: return "OpRetainEvent"; + case OpReleaseEvent: return "OpReleaseEvent"; + case OpCreateUserEvent: return "OpCreateUserEvent"; + case OpIsValidEvent: return "OpIsValidEvent"; + case OpSetUserEventStatus: return "OpSetUserEventStatus"; + case OpCaptureEventProfilingInfo: return "OpCaptureEventProfilingInfo"; + case OpGetDefaultQueue: return "OpGetDefaultQueue"; + case OpBuildNDRange: return "OpBuildNDRange"; + case OpImageSparseSampleImplicitLod: return "OpImageSparseSampleImplicitLod"; + case OpImageSparseSampleExplicitLod: return "OpImageSparseSampleExplicitLod"; + case OpImageSparseSampleDrefImplicitLod: return "OpImageSparseSampleDrefImplicitLod"; + case OpImageSparseSampleDrefExplicitLod: return "OpImageSparseSampleDrefExplicitLod"; + case OpImageSparseSampleProjImplicitLod: return "OpImageSparseSampleProjImplicitLod"; + case OpImageSparseSampleProjExplicitLod: return "OpImageSparseSampleProjExplicitLod"; + case OpImageSparseSampleProjDrefImplicitLod: return "OpImageSparseSampleProjDrefImplicitLod"; + case OpImageSparseSampleProjDrefExplicitLod: return "OpImageSparseSampleProjDrefExplicitLod"; + case OpImageSparseFetch: return "OpImageSparseFetch"; + case OpImageSparseGather: return "OpImageSparseGather"; + case OpImageSparseDrefGather: return "OpImageSparseDrefGather"; + case OpImageSparseTexelsResident: return "OpImageSparseTexelsResident"; + case OpNoLine: return "OpNoLine"; + case OpAtomicFlagTestAndSet: return "OpAtomicFlagTestAndSet"; + case OpAtomicFlagClear: return "OpAtomicFlagClear"; + case OpImageSparseRead: return "OpImageSparseRead"; + case OpSizeOf: return "OpSizeOf"; + case OpTypePipeStorage: return "OpTypePipeStorage"; + case OpConstantPipeStorage: return "OpConstantPipeStorage"; + case OpCreatePipeFromPipeStorage: return "OpCreatePipeFromPipeStorage"; + case OpGetKernelLocalSizeForSubgroupCount: return "OpGetKernelLocalSizeForSubgroupCount"; + case OpGetKernelMaxNumSubgroups: return "OpGetKernelMaxNumSubgroups"; + case OpTypeNamedBarrier: return "OpTypeNamedBarrier"; + case OpNamedBarrierInitialize: return "OpNamedBarrierInitialize"; + case OpMemoryNamedBarrier: return "OpMemoryNamedBarrier"; + case OpModuleProcessed: return "OpModuleProcessed"; + case OpExecutionModeId: return "OpExecutionModeId"; + case OpDecorateId: return "OpDecorateId"; + case OpGroupNonUniformElect: return "OpGroupNonUniformElect"; + case OpGroupNonUniformAll: return "OpGroupNonUniformAll"; + case OpGroupNonUniformAny: return "OpGroupNonUniformAny"; + case OpGroupNonUniformAllEqual: return "OpGroupNonUniformAllEqual"; + case OpGroupNonUniformBroadcast: return "OpGroupNonUniformBroadcast"; + case OpGroupNonUniformBroadcastFirst: return "OpGroupNonUniformBroadcastFirst"; + case OpGroupNonUniformBallot: return "OpGroupNonUniformBallot"; + case OpGroupNonUniformInverseBallot: return "OpGroupNonUniformInverseBallot"; + case OpGroupNonUniformBallotBitExtract: return "OpGroupNonUniformBallotBitExtract"; + case OpGroupNonUniformBallotBitCount: return "OpGroupNonUniformBallotBitCount"; + case OpGroupNonUniformBallotFindLSB: return "OpGroupNonUniformBallotFindLSB"; + case OpGroupNonUniformBallotFindMSB: return "OpGroupNonUniformBallotFindMSB"; + case OpGroupNonUniformShuffle: return "OpGroupNonUniformShuffle"; + case OpGroupNonUniformShuffleXor: return "OpGroupNonUniformShuffleXor"; + case OpGroupNonUniformShuffleUp: return "OpGroupNonUniformShuffleUp"; + case OpGroupNonUniformShuffleDown: return "OpGroupNonUniformShuffleDown"; + case OpGroupNonUniformIAdd: return "OpGroupNonUniformIAdd"; + case OpGroupNonUniformFAdd: return "OpGroupNonUniformFAdd"; + case OpGroupNonUniformIMul: return "OpGroupNonUniformIMul"; + case OpGroupNonUniformFMul: return "OpGroupNonUniformFMul"; + case OpGroupNonUniformSMin: return "OpGroupNonUniformSMin"; + case OpGroupNonUniformUMin: return "OpGroupNonUniformUMin"; + case OpGroupNonUniformFMin: return "OpGroupNonUniformFMin"; + case OpGroupNonUniformSMax: return "OpGroupNonUniformSMax"; + case OpGroupNonUniformUMax: return "OpGroupNonUniformUMax"; + case OpGroupNonUniformFMax: return "OpGroupNonUniformFMax"; + case OpGroupNonUniformBitwiseAnd: return "OpGroupNonUniformBitwiseAnd"; + case OpGroupNonUniformBitwiseOr: return "OpGroupNonUniformBitwiseOr"; + case OpGroupNonUniformBitwiseXor: return "OpGroupNonUniformBitwiseXor"; + case OpGroupNonUniformLogicalAnd: return "OpGroupNonUniformLogicalAnd"; + case OpGroupNonUniformLogicalOr: return "OpGroupNonUniformLogicalOr"; + case OpGroupNonUniformLogicalXor: return "OpGroupNonUniformLogicalXor"; + case OpGroupNonUniformQuadBroadcast: return "OpGroupNonUniformQuadBroadcast"; + case OpGroupNonUniformQuadSwap: return "OpGroupNonUniformQuadSwap"; + case OpCopyLogical: return "OpCopyLogical"; + case OpPtrEqual: return "OpPtrEqual"; + case OpPtrNotEqual: return "OpPtrNotEqual"; + case OpPtrDiff: return "OpPtrDiff"; + case OpColorAttachmentReadEXT: return "OpColorAttachmentReadEXT"; + case OpDepthAttachmentReadEXT: return "OpDepthAttachmentReadEXT"; + case OpStencilAttachmentReadEXT: return "OpStencilAttachmentReadEXT"; + case OpTerminateInvocation: return "OpTerminateInvocation"; + case OpTypeUntypedPointerKHR: return "OpTypeUntypedPointerKHR"; + case OpUntypedVariableKHR: return "OpUntypedVariableKHR"; + case OpUntypedAccessChainKHR: return "OpUntypedAccessChainKHR"; + case OpUntypedInBoundsAccessChainKHR: return "OpUntypedInBoundsAccessChainKHR"; + case OpSubgroupBallotKHR: return "OpSubgroupBallotKHR"; + case OpSubgroupFirstInvocationKHR: return "OpSubgroupFirstInvocationKHR"; + case OpUntypedPtrAccessChainKHR: return "OpUntypedPtrAccessChainKHR"; + case OpUntypedInBoundsPtrAccessChainKHR: return "OpUntypedInBoundsPtrAccessChainKHR"; + case OpUntypedArrayLengthKHR: return "OpUntypedArrayLengthKHR"; + case OpUntypedPrefetchKHR: return "OpUntypedPrefetchKHR"; + case OpSubgroupAllKHR: return "OpSubgroupAllKHR"; + case OpSubgroupAnyKHR: return "OpSubgroupAnyKHR"; + case OpSubgroupAllEqualKHR: return "OpSubgroupAllEqualKHR"; + case OpGroupNonUniformRotateKHR: return "OpGroupNonUniformRotateKHR"; + case OpSubgroupReadInvocationKHR: return "OpSubgroupReadInvocationKHR"; + case OpExtInstWithForwardRefsKHR: return "OpExtInstWithForwardRefsKHR"; + case OpTraceRayKHR: return "OpTraceRayKHR"; + case OpExecuteCallableKHR: return "OpExecuteCallableKHR"; + case OpConvertUToAccelerationStructureKHR: return "OpConvertUToAccelerationStructureKHR"; + case OpIgnoreIntersectionKHR: return "OpIgnoreIntersectionKHR"; + case OpTerminateRayKHR: return "OpTerminateRayKHR"; + case OpSDot: return "OpSDot"; + case OpUDot: return "OpUDot"; + case OpSUDot: return "OpSUDot"; + case OpSDotAccSat: return "OpSDotAccSat"; + case OpUDotAccSat: return "OpUDotAccSat"; + case OpSUDotAccSat: return "OpSUDotAccSat"; + case OpTypeCooperativeMatrixKHR: return "OpTypeCooperativeMatrixKHR"; + case OpCooperativeMatrixLoadKHR: return "OpCooperativeMatrixLoadKHR"; + case OpCooperativeMatrixStoreKHR: return "OpCooperativeMatrixStoreKHR"; + case OpCooperativeMatrixMulAddKHR: return "OpCooperativeMatrixMulAddKHR"; + case OpCooperativeMatrixLengthKHR: return "OpCooperativeMatrixLengthKHR"; + case OpConstantCompositeReplicateEXT: return "OpConstantCompositeReplicateEXT"; + case OpSpecConstantCompositeReplicateEXT: return "OpSpecConstantCompositeReplicateEXT"; + case OpCompositeConstructReplicateEXT: return "OpCompositeConstructReplicateEXT"; + case OpTypeRayQueryKHR: return "OpTypeRayQueryKHR"; + case OpRayQueryInitializeKHR: return "OpRayQueryInitializeKHR"; + case OpRayQueryTerminateKHR: return "OpRayQueryTerminateKHR"; + case OpRayQueryGenerateIntersectionKHR: return "OpRayQueryGenerateIntersectionKHR"; + case OpRayQueryConfirmIntersectionKHR: return "OpRayQueryConfirmIntersectionKHR"; + case OpRayQueryProceedKHR: return "OpRayQueryProceedKHR"; + case OpRayQueryGetIntersectionTypeKHR: return "OpRayQueryGetIntersectionTypeKHR"; + case OpImageSampleWeightedQCOM: return "OpImageSampleWeightedQCOM"; + case OpImageBoxFilterQCOM: return "OpImageBoxFilterQCOM"; + case OpImageBlockMatchSSDQCOM: return "OpImageBlockMatchSSDQCOM"; + case OpImageBlockMatchSADQCOM: return "OpImageBlockMatchSADQCOM"; + case OpImageBlockMatchWindowSSDQCOM: return "OpImageBlockMatchWindowSSDQCOM"; + case OpImageBlockMatchWindowSADQCOM: return "OpImageBlockMatchWindowSADQCOM"; + case OpImageBlockMatchGatherSSDQCOM: return "OpImageBlockMatchGatherSSDQCOM"; + case OpImageBlockMatchGatherSADQCOM: return "OpImageBlockMatchGatherSADQCOM"; + case OpGroupIAddNonUniformAMD: return "OpGroupIAddNonUniformAMD"; + case OpGroupFAddNonUniformAMD: return "OpGroupFAddNonUniformAMD"; + case OpGroupFMinNonUniformAMD: return "OpGroupFMinNonUniformAMD"; + case OpGroupUMinNonUniformAMD: return "OpGroupUMinNonUniformAMD"; + case OpGroupSMinNonUniformAMD: return "OpGroupSMinNonUniformAMD"; + case OpGroupFMaxNonUniformAMD: return "OpGroupFMaxNonUniformAMD"; + case OpGroupUMaxNonUniformAMD: return "OpGroupUMaxNonUniformAMD"; + case OpGroupSMaxNonUniformAMD: return "OpGroupSMaxNonUniformAMD"; + case OpFragmentMaskFetchAMD: return "OpFragmentMaskFetchAMD"; + case OpFragmentFetchAMD: return "OpFragmentFetchAMD"; + case OpReadClockKHR: return "OpReadClockKHR"; + case OpAllocateNodePayloadsAMDX: return "OpAllocateNodePayloadsAMDX"; + case OpEnqueueNodePayloadsAMDX: return "OpEnqueueNodePayloadsAMDX"; + case OpTypeNodePayloadArrayAMDX: return "OpTypeNodePayloadArrayAMDX"; + case OpFinishWritingNodePayloadAMDX: return "OpFinishWritingNodePayloadAMDX"; + case OpNodePayloadArrayLengthAMDX: return "OpNodePayloadArrayLengthAMDX"; + case OpIsNodePayloadValidAMDX: return "OpIsNodePayloadValidAMDX"; + case OpConstantStringAMDX: return "OpConstantStringAMDX"; + case OpSpecConstantStringAMDX: return "OpSpecConstantStringAMDX"; + case OpGroupNonUniformQuadAllKHR: return "OpGroupNonUniformQuadAllKHR"; + case OpGroupNonUniformQuadAnyKHR: return "OpGroupNonUniformQuadAnyKHR"; + case OpHitObjectRecordHitMotionNV: return "OpHitObjectRecordHitMotionNV"; + case OpHitObjectRecordHitWithIndexMotionNV: return "OpHitObjectRecordHitWithIndexMotionNV"; + case OpHitObjectRecordMissMotionNV: return "OpHitObjectRecordMissMotionNV"; + case OpHitObjectGetWorldToObjectNV: return "OpHitObjectGetWorldToObjectNV"; + case OpHitObjectGetObjectToWorldNV: return "OpHitObjectGetObjectToWorldNV"; + case OpHitObjectGetObjectRayDirectionNV: return "OpHitObjectGetObjectRayDirectionNV"; + case OpHitObjectGetObjectRayOriginNV: return "OpHitObjectGetObjectRayOriginNV"; + case OpHitObjectTraceRayMotionNV: return "OpHitObjectTraceRayMotionNV"; + case OpHitObjectGetShaderRecordBufferHandleNV: return "OpHitObjectGetShaderRecordBufferHandleNV"; + case OpHitObjectGetShaderBindingTableRecordIndexNV: return "OpHitObjectGetShaderBindingTableRecordIndexNV"; + case OpHitObjectRecordEmptyNV: return "OpHitObjectRecordEmptyNV"; + case OpHitObjectTraceRayNV: return "OpHitObjectTraceRayNV"; + case OpHitObjectRecordHitNV: return "OpHitObjectRecordHitNV"; + case OpHitObjectRecordHitWithIndexNV: return "OpHitObjectRecordHitWithIndexNV"; + case OpHitObjectRecordMissNV: return "OpHitObjectRecordMissNV"; + case OpHitObjectExecuteShaderNV: return "OpHitObjectExecuteShaderNV"; + case OpHitObjectGetCurrentTimeNV: return "OpHitObjectGetCurrentTimeNV"; + case OpHitObjectGetAttributesNV: return "OpHitObjectGetAttributesNV"; + case OpHitObjectGetHitKindNV: return "OpHitObjectGetHitKindNV"; + case OpHitObjectGetPrimitiveIndexNV: return "OpHitObjectGetPrimitiveIndexNV"; + case OpHitObjectGetGeometryIndexNV: return "OpHitObjectGetGeometryIndexNV"; + case OpHitObjectGetInstanceIdNV: return "OpHitObjectGetInstanceIdNV"; + case OpHitObjectGetInstanceCustomIndexNV: return "OpHitObjectGetInstanceCustomIndexNV"; + case OpHitObjectGetWorldRayDirectionNV: return "OpHitObjectGetWorldRayDirectionNV"; + case OpHitObjectGetWorldRayOriginNV: return "OpHitObjectGetWorldRayOriginNV"; + case OpHitObjectGetRayTMaxNV: return "OpHitObjectGetRayTMaxNV"; + case OpHitObjectGetRayTMinNV: return "OpHitObjectGetRayTMinNV"; + case OpHitObjectIsEmptyNV: return "OpHitObjectIsEmptyNV"; + case OpHitObjectIsHitNV: return "OpHitObjectIsHitNV"; + case OpHitObjectIsMissNV: return "OpHitObjectIsMissNV"; + case OpReorderThreadWithHitObjectNV: return "OpReorderThreadWithHitObjectNV"; + case OpReorderThreadWithHintNV: return "OpReorderThreadWithHintNV"; + case OpTypeHitObjectNV: return "OpTypeHitObjectNV"; + case OpImageSampleFootprintNV: return "OpImageSampleFootprintNV"; + case OpTypeCooperativeVectorNV: return "OpTypeCooperativeVectorNV"; + case OpCooperativeVectorMatrixMulNV: return "OpCooperativeVectorMatrixMulNV"; + case OpCooperativeVectorOuterProductAccumulateNV: return "OpCooperativeVectorOuterProductAccumulateNV"; + case OpCooperativeVectorReduceSumAccumulateNV: return "OpCooperativeVectorReduceSumAccumulateNV"; + case OpCooperativeVectorMatrixMulAddNV: return "OpCooperativeVectorMatrixMulAddNV"; + case OpCooperativeMatrixConvertNV: return "OpCooperativeMatrixConvertNV"; + case OpEmitMeshTasksEXT: return "OpEmitMeshTasksEXT"; + case OpSetMeshOutputsEXT: return "OpSetMeshOutputsEXT"; + case OpGroupNonUniformPartitionNV: return "OpGroupNonUniformPartitionNV"; + case OpWritePackedPrimitiveIndices4x8NV: return "OpWritePackedPrimitiveIndices4x8NV"; + case OpFetchMicroTriangleVertexPositionNV: return "OpFetchMicroTriangleVertexPositionNV"; + case OpFetchMicroTriangleVertexBarycentricNV: return "OpFetchMicroTriangleVertexBarycentricNV"; + case OpCooperativeVectorLoadNV: return "OpCooperativeVectorLoadNV"; + case OpCooperativeVectorStoreNV: return "OpCooperativeVectorStoreNV"; + case OpReportIntersectionKHR: return "OpReportIntersectionKHR"; + case OpIgnoreIntersectionNV: return "OpIgnoreIntersectionNV"; + case OpTerminateRayNV: return "OpTerminateRayNV"; + case OpTraceNV: return "OpTraceNV"; + case OpTraceMotionNV: return "OpTraceMotionNV"; + case OpTraceRayMotionNV: return "OpTraceRayMotionNV"; + case OpRayQueryGetIntersectionTriangleVertexPositionsKHR: return "OpRayQueryGetIntersectionTriangleVertexPositionsKHR"; + case OpTypeAccelerationStructureKHR: return "OpTypeAccelerationStructureKHR"; + case OpExecuteCallableNV: return "OpExecuteCallableNV"; + case OpRayQueryGetClusterIdNV: return "OpRayQueryGetClusterIdNV"; + case OpHitObjectGetClusterIdNV: return "OpHitObjectGetClusterIdNV"; + case OpTypeCooperativeMatrixNV: return "OpTypeCooperativeMatrixNV"; + case OpCooperativeMatrixLoadNV: return "OpCooperativeMatrixLoadNV"; + case OpCooperativeMatrixStoreNV: return "OpCooperativeMatrixStoreNV"; + case OpCooperativeMatrixMulAddNV: return "OpCooperativeMatrixMulAddNV"; + case OpCooperativeMatrixLengthNV: return "OpCooperativeMatrixLengthNV"; + case OpBeginInvocationInterlockEXT: return "OpBeginInvocationInterlockEXT"; + case OpEndInvocationInterlockEXT: return "OpEndInvocationInterlockEXT"; + case OpCooperativeMatrixReduceNV: return "OpCooperativeMatrixReduceNV"; + case OpCooperativeMatrixLoadTensorNV: return "OpCooperativeMatrixLoadTensorNV"; + case OpCooperativeMatrixStoreTensorNV: return "OpCooperativeMatrixStoreTensorNV"; + case OpCooperativeMatrixPerElementOpNV: return "OpCooperativeMatrixPerElementOpNV"; + case OpTypeTensorLayoutNV: return "OpTypeTensorLayoutNV"; + case OpTypeTensorViewNV: return "OpTypeTensorViewNV"; + case OpCreateTensorLayoutNV: return "OpCreateTensorLayoutNV"; + case OpTensorLayoutSetDimensionNV: return "OpTensorLayoutSetDimensionNV"; + case OpTensorLayoutSetStrideNV: return "OpTensorLayoutSetStrideNV"; + case OpTensorLayoutSliceNV: return "OpTensorLayoutSliceNV"; + case OpTensorLayoutSetClampValueNV: return "OpTensorLayoutSetClampValueNV"; + case OpCreateTensorViewNV: return "OpCreateTensorViewNV"; + case OpTensorViewSetDimensionNV: return "OpTensorViewSetDimensionNV"; + case OpTensorViewSetStrideNV: return "OpTensorViewSetStrideNV"; + case OpDemoteToHelperInvocation: return "OpDemoteToHelperInvocation"; + case OpIsHelperInvocationEXT: return "OpIsHelperInvocationEXT"; + case OpTensorViewSetClipNV: return "OpTensorViewSetClipNV"; + case OpTensorLayoutSetBlockSizeNV: return "OpTensorLayoutSetBlockSizeNV"; + case OpCooperativeMatrixTransposeNV: return "OpCooperativeMatrixTransposeNV"; + case OpConvertUToImageNV: return "OpConvertUToImageNV"; + case OpConvertUToSamplerNV: return "OpConvertUToSamplerNV"; + case OpConvertImageToUNV: return "OpConvertImageToUNV"; + case OpConvertSamplerToUNV: return "OpConvertSamplerToUNV"; + case OpConvertUToSampledImageNV: return "OpConvertUToSampledImageNV"; + case OpConvertSampledImageToUNV: return "OpConvertSampledImageToUNV"; + case OpSamplerImageAddressingModeNV: return "OpSamplerImageAddressingModeNV"; + case OpRawAccessChainNV: return "OpRawAccessChainNV"; + case OpRayQueryGetIntersectionSpherePositionNV: return "OpRayQueryGetIntersectionSpherePositionNV"; + case OpRayQueryGetIntersectionSphereRadiusNV: return "OpRayQueryGetIntersectionSphereRadiusNV"; + case OpRayQueryGetIntersectionLSSPositionsNV: return "OpRayQueryGetIntersectionLSSPositionsNV"; + case OpRayQueryGetIntersectionLSSRadiiNV: return "OpRayQueryGetIntersectionLSSRadiiNV"; + case OpRayQueryGetIntersectionLSSHitValueNV: return "OpRayQueryGetIntersectionLSSHitValueNV"; + case OpHitObjectGetSpherePositionNV: return "OpHitObjectGetSpherePositionNV"; + case OpHitObjectGetSphereRadiusNV: return "OpHitObjectGetSphereRadiusNV"; + case OpHitObjectGetLSSPositionsNV: return "OpHitObjectGetLSSPositionsNV"; + case OpHitObjectGetLSSRadiiNV: return "OpHitObjectGetLSSRadiiNV"; + case OpHitObjectIsSphereHitNV: return "OpHitObjectIsSphereHitNV"; + case OpHitObjectIsLSSHitNV: return "OpHitObjectIsLSSHitNV"; + case OpRayQueryIsSphereHitNV: return "OpRayQueryIsSphereHitNV"; + case OpRayQueryIsLSSHitNV: return "OpRayQueryIsLSSHitNV"; + case OpSubgroupShuffleINTEL: return "OpSubgroupShuffleINTEL"; + case OpSubgroupShuffleDownINTEL: return "OpSubgroupShuffleDownINTEL"; + case OpSubgroupShuffleUpINTEL: return "OpSubgroupShuffleUpINTEL"; + case OpSubgroupShuffleXorINTEL: return "OpSubgroupShuffleXorINTEL"; + case OpSubgroupBlockReadINTEL: return "OpSubgroupBlockReadINTEL"; + case OpSubgroupBlockWriteINTEL: return "OpSubgroupBlockWriteINTEL"; + case OpSubgroupImageBlockReadINTEL: return "OpSubgroupImageBlockReadINTEL"; + case OpSubgroupImageBlockWriteINTEL: return "OpSubgroupImageBlockWriteINTEL"; + case OpSubgroupImageMediaBlockReadINTEL: return "OpSubgroupImageMediaBlockReadINTEL"; + case OpSubgroupImageMediaBlockWriteINTEL: return "OpSubgroupImageMediaBlockWriteINTEL"; + case OpUCountLeadingZerosINTEL: return "OpUCountLeadingZerosINTEL"; + case OpUCountTrailingZerosINTEL: return "OpUCountTrailingZerosINTEL"; + case OpAbsISubINTEL: return "OpAbsISubINTEL"; + case OpAbsUSubINTEL: return "OpAbsUSubINTEL"; + case OpIAddSatINTEL: return "OpIAddSatINTEL"; + case OpUAddSatINTEL: return "OpUAddSatINTEL"; + case OpIAverageINTEL: return "OpIAverageINTEL"; + case OpUAverageINTEL: return "OpUAverageINTEL"; + case OpIAverageRoundedINTEL: return "OpIAverageRoundedINTEL"; + case OpUAverageRoundedINTEL: return "OpUAverageRoundedINTEL"; + case OpISubSatINTEL: return "OpISubSatINTEL"; + case OpUSubSatINTEL: return "OpUSubSatINTEL"; + case OpIMul32x16INTEL: return "OpIMul32x16INTEL"; + case OpUMul32x16INTEL: return "OpUMul32x16INTEL"; + case OpConstantFunctionPointerINTEL: return "OpConstantFunctionPointerINTEL"; + case OpFunctionPointerCallINTEL: return "OpFunctionPointerCallINTEL"; + case OpAsmTargetINTEL: return "OpAsmTargetINTEL"; + case OpAsmINTEL: return "OpAsmINTEL"; + case OpAsmCallINTEL: return "OpAsmCallINTEL"; + case OpAtomicFMinEXT: return "OpAtomicFMinEXT"; + case OpAtomicFMaxEXT: return "OpAtomicFMaxEXT"; + case OpAssumeTrueKHR: return "OpAssumeTrueKHR"; + case OpExpectKHR: return "OpExpectKHR"; + case OpDecorateString: return "OpDecorateString"; + case OpMemberDecorateString: return "OpMemberDecorateString"; + case OpVmeImageINTEL: return "OpVmeImageINTEL"; + case OpTypeVmeImageINTEL: return "OpTypeVmeImageINTEL"; + case OpTypeAvcImePayloadINTEL: return "OpTypeAvcImePayloadINTEL"; + case OpTypeAvcRefPayloadINTEL: return "OpTypeAvcRefPayloadINTEL"; + case OpTypeAvcSicPayloadINTEL: return "OpTypeAvcSicPayloadINTEL"; + case OpTypeAvcMcePayloadINTEL: return "OpTypeAvcMcePayloadINTEL"; + case OpTypeAvcMceResultINTEL: return "OpTypeAvcMceResultINTEL"; + case OpTypeAvcImeResultINTEL: return "OpTypeAvcImeResultINTEL"; + case OpTypeAvcImeResultSingleReferenceStreamoutINTEL: return "OpTypeAvcImeResultSingleReferenceStreamoutINTEL"; + case OpTypeAvcImeResultDualReferenceStreamoutINTEL: return "OpTypeAvcImeResultDualReferenceStreamoutINTEL"; + case OpTypeAvcImeSingleReferenceStreaminINTEL: return "OpTypeAvcImeSingleReferenceStreaminINTEL"; + case OpTypeAvcImeDualReferenceStreaminINTEL: return "OpTypeAvcImeDualReferenceStreaminINTEL"; + case OpTypeAvcRefResultINTEL: return "OpTypeAvcRefResultINTEL"; + case OpTypeAvcSicResultINTEL: return "OpTypeAvcSicResultINTEL"; + case OpSubgroupAvcMceGetDefaultInterBaseMultiReferencePenaltyINTEL: return "OpSubgroupAvcMceGetDefaultInterBaseMultiReferencePenaltyINTEL"; + case OpSubgroupAvcMceSetInterBaseMultiReferencePenaltyINTEL: return "OpSubgroupAvcMceSetInterBaseMultiReferencePenaltyINTEL"; + case OpSubgroupAvcMceGetDefaultInterShapePenaltyINTEL: return "OpSubgroupAvcMceGetDefaultInterShapePenaltyINTEL"; + case OpSubgroupAvcMceSetInterShapePenaltyINTEL: return "OpSubgroupAvcMceSetInterShapePenaltyINTEL"; + case OpSubgroupAvcMceGetDefaultInterDirectionPenaltyINTEL: return "OpSubgroupAvcMceGetDefaultInterDirectionPenaltyINTEL"; + case OpSubgroupAvcMceSetInterDirectionPenaltyINTEL: return "OpSubgroupAvcMceSetInterDirectionPenaltyINTEL"; + case OpSubgroupAvcMceGetDefaultIntraLumaShapePenaltyINTEL: return "OpSubgroupAvcMceGetDefaultIntraLumaShapePenaltyINTEL"; + case OpSubgroupAvcMceGetDefaultInterMotionVectorCostTableINTEL: return "OpSubgroupAvcMceGetDefaultInterMotionVectorCostTableINTEL"; + case OpSubgroupAvcMceGetDefaultHighPenaltyCostTableINTEL: return "OpSubgroupAvcMceGetDefaultHighPenaltyCostTableINTEL"; + case OpSubgroupAvcMceGetDefaultMediumPenaltyCostTableINTEL: return "OpSubgroupAvcMceGetDefaultMediumPenaltyCostTableINTEL"; + case OpSubgroupAvcMceGetDefaultLowPenaltyCostTableINTEL: return "OpSubgroupAvcMceGetDefaultLowPenaltyCostTableINTEL"; + case OpSubgroupAvcMceSetMotionVectorCostFunctionINTEL: return "OpSubgroupAvcMceSetMotionVectorCostFunctionINTEL"; + case OpSubgroupAvcMceGetDefaultIntraLumaModePenaltyINTEL: return "OpSubgroupAvcMceGetDefaultIntraLumaModePenaltyINTEL"; + case OpSubgroupAvcMceGetDefaultNonDcLumaIntraPenaltyINTEL: return "OpSubgroupAvcMceGetDefaultNonDcLumaIntraPenaltyINTEL"; + case OpSubgroupAvcMceGetDefaultIntraChromaModeBasePenaltyINTEL: return "OpSubgroupAvcMceGetDefaultIntraChromaModeBasePenaltyINTEL"; + case OpSubgroupAvcMceSetAcOnlyHaarINTEL: return "OpSubgroupAvcMceSetAcOnlyHaarINTEL"; + case OpSubgroupAvcMceSetSourceInterlacedFieldPolarityINTEL: return "OpSubgroupAvcMceSetSourceInterlacedFieldPolarityINTEL"; + case OpSubgroupAvcMceSetSingleReferenceInterlacedFieldPolarityINTEL: return "OpSubgroupAvcMceSetSingleReferenceInterlacedFieldPolarityINTEL"; + case OpSubgroupAvcMceSetDualReferenceInterlacedFieldPolaritiesINTEL: return "OpSubgroupAvcMceSetDualReferenceInterlacedFieldPolaritiesINTEL"; + case OpSubgroupAvcMceConvertToImePayloadINTEL: return "OpSubgroupAvcMceConvertToImePayloadINTEL"; + case OpSubgroupAvcMceConvertToImeResultINTEL: return "OpSubgroupAvcMceConvertToImeResultINTEL"; + case OpSubgroupAvcMceConvertToRefPayloadINTEL: return "OpSubgroupAvcMceConvertToRefPayloadINTEL"; + case OpSubgroupAvcMceConvertToRefResultINTEL: return "OpSubgroupAvcMceConvertToRefResultINTEL"; + case OpSubgroupAvcMceConvertToSicPayloadINTEL: return "OpSubgroupAvcMceConvertToSicPayloadINTEL"; + case OpSubgroupAvcMceConvertToSicResultINTEL: return "OpSubgroupAvcMceConvertToSicResultINTEL"; + case OpSubgroupAvcMceGetMotionVectorsINTEL: return "OpSubgroupAvcMceGetMotionVectorsINTEL"; + case OpSubgroupAvcMceGetInterDistortionsINTEL: return "OpSubgroupAvcMceGetInterDistortionsINTEL"; + case OpSubgroupAvcMceGetBestInterDistortionsINTEL: return "OpSubgroupAvcMceGetBestInterDistortionsINTEL"; + case OpSubgroupAvcMceGetInterMajorShapeINTEL: return "OpSubgroupAvcMceGetInterMajorShapeINTEL"; + case OpSubgroupAvcMceGetInterMinorShapeINTEL: return "OpSubgroupAvcMceGetInterMinorShapeINTEL"; + case OpSubgroupAvcMceGetInterDirectionsINTEL: return "OpSubgroupAvcMceGetInterDirectionsINTEL"; + case OpSubgroupAvcMceGetInterMotionVectorCountINTEL: return "OpSubgroupAvcMceGetInterMotionVectorCountINTEL"; + case OpSubgroupAvcMceGetInterReferenceIdsINTEL: return "OpSubgroupAvcMceGetInterReferenceIdsINTEL"; + case OpSubgroupAvcMceGetInterReferenceInterlacedFieldPolaritiesINTEL: return "OpSubgroupAvcMceGetInterReferenceInterlacedFieldPolaritiesINTEL"; + case OpSubgroupAvcImeInitializeINTEL: return "OpSubgroupAvcImeInitializeINTEL"; + case OpSubgroupAvcImeSetSingleReferenceINTEL: return "OpSubgroupAvcImeSetSingleReferenceINTEL"; + case OpSubgroupAvcImeSetDualReferenceINTEL: return "OpSubgroupAvcImeSetDualReferenceINTEL"; + case OpSubgroupAvcImeRefWindowSizeINTEL: return "OpSubgroupAvcImeRefWindowSizeINTEL"; + case OpSubgroupAvcImeAdjustRefOffsetINTEL: return "OpSubgroupAvcImeAdjustRefOffsetINTEL"; + case OpSubgroupAvcImeConvertToMcePayloadINTEL: return "OpSubgroupAvcImeConvertToMcePayloadINTEL"; + case OpSubgroupAvcImeSetMaxMotionVectorCountINTEL: return "OpSubgroupAvcImeSetMaxMotionVectorCountINTEL"; + case OpSubgroupAvcImeSetUnidirectionalMixDisableINTEL: return "OpSubgroupAvcImeSetUnidirectionalMixDisableINTEL"; + case OpSubgroupAvcImeSetEarlySearchTerminationThresholdINTEL: return "OpSubgroupAvcImeSetEarlySearchTerminationThresholdINTEL"; + case OpSubgroupAvcImeSetWeightedSadINTEL: return "OpSubgroupAvcImeSetWeightedSadINTEL"; + case OpSubgroupAvcImeEvaluateWithSingleReferenceINTEL: return "OpSubgroupAvcImeEvaluateWithSingleReferenceINTEL"; + case OpSubgroupAvcImeEvaluateWithDualReferenceINTEL: return "OpSubgroupAvcImeEvaluateWithDualReferenceINTEL"; + case OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminINTEL: return "OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminINTEL"; + case OpSubgroupAvcImeEvaluateWithDualReferenceStreaminINTEL: return "OpSubgroupAvcImeEvaluateWithDualReferenceStreaminINTEL"; + case OpSubgroupAvcImeEvaluateWithSingleReferenceStreamoutINTEL: return "OpSubgroupAvcImeEvaluateWithSingleReferenceStreamoutINTEL"; + case OpSubgroupAvcImeEvaluateWithDualReferenceStreamoutINTEL: return "OpSubgroupAvcImeEvaluateWithDualReferenceStreamoutINTEL"; + case OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminoutINTEL: return "OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminoutINTEL"; + case OpSubgroupAvcImeEvaluateWithDualReferenceStreaminoutINTEL: return "OpSubgroupAvcImeEvaluateWithDualReferenceStreaminoutINTEL"; + case OpSubgroupAvcImeConvertToMceResultINTEL: return "OpSubgroupAvcImeConvertToMceResultINTEL"; + case OpSubgroupAvcImeGetSingleReferenceStreaminINTEL: return "OpSubgroupAvcImeGetSingleReferenceStreaminINTEL"; + case OpSubgroupAvcImeGetDualReferenceStreaminINTEL: return "OpSubgroupAvcImeGetDualReferenceStreaminINTEL"; + case OpSubgroupAvcImeStripSingleReferenceStreamoutINTEL: return "OpSubgroupAvcImeStripSingleReferenceStreamoutINTEL"; + case OpSubgroupAvcImeStripDualReferenceStreamoutINTEL: return "OpSubgroupAvcImeStripDualReferenceStreamoutINTEL"; + case OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeMotionVectorsINTEL: return "OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeMotionVectorsINTEL"; + case OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeDistortionsINTEL: return "OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeDistortionsINTEL"; + case OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeReferenceIdsINTEL: return "OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeReferenceIdsINTEL"; + case OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeMotionVectorsINTEL: return "OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeMotionVectorsINTEL"; + case OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeDistortionsINTEL: return "OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeDistortionsINTEL"; + case OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeReferenceIdsINTEL: return "OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeReferenceIdsINTEL"; + case OpSubgroupAvcImeGetBorderReachedINTEL: return "OpSubgroupAvcImeGetBorderReachedINTEL"; + case OpSubgroupAvcImeGetTruncatedSearchIndicationINTEL: return "OpSubgroupAvcImeGetTruncatedSearchIndicationINTEL"; + case OpSubgroupAvcImeGetUnidirectionalEarlySearchTerminationINTEL: return "OpSubgroupAvcImeGetUnidirectionalEarlySearchTerminationINTEL"; + case OpSubgroupAvcImeGetWeightingPatternMinimumMotionVectorINTEL: return "OpSubgroupAvcImeGetWeightingPatternMinimumMotionVectorINTEL"; + case OpSubgroupAvcImeGetWeightingPatternMinimumDistortionINTEL: return "OpSubgroupAvcImeGetWeightingPatternMinimumDistortionINTEL"; + case OpSubgroupAvcFmeInitializeINTEL: return "OpSubgroupAvcFmeInitializeINTEL"; + case OpSubgroupAvcBmeInitializeINTEL: return "OpSubgroupAvcBmeInitializeINTEL"; + case OpSubgroupAvcRefConvertToMcePayloadINTEL: return "OpSubgroupAvcRefConvertToMcePayloadINTEL"; + case OpSubgroupAvcRefSetBidirectionalMixDisableINTEL: return "OpSubgroupAvcRefSetBidirectionalMixDisableINTEL"; + case OpSubgroupAvcRefSetBilinearFilterEnableINTEL: return "OpSubgroupAvcRefSetBilinearFilterEnableINTEL"; + case OpSubgroupAvcRefEvaluateWithSingleReferenceINTEL: return "OpSubgroupAvcRefEvaluateWithSingleReferenceINTEL"; + case OpSubgroupAvcRefEvaluateWithDualReferenceINTEL: return "OpSubgroupAvcRefEvaluateWithDualReferenceINTEL"; + case OpSubgroupAvcRefEvaluateWithMultiReferenceINTEL: return "OpSubgroupAvcRefEvaluateWithMultiReferenceINTEL"; + case OpSubgroupAvcRefEvaluateWithMultiReferenceInterlacedINTEL: return "OpSubgroupAvcRefEvaluateWithMultiReferenceInterlacedINTEL"; + case OpSubgroupAvcRefConvertToMceResultINTEL: return "OpSubgroupAvcRefConvertToMceResultINTEL"; + case OpSubgroupAvcSicInitializeINTEL: return "OpSubgroupAvcSicInitializeINTEL"; + case OpSubgroupAvcSicConfigureSkcINTEL: return "OpSubgroupAvcSicConfigureSkcINTEL"; + case OpSubgroupAvcSicConfigureIpeLumaINTEL: return "OpSubgroupAvcSicConfigureIpeLumaINTEL"; + case OpSubgroupAvcSicConfigureIpeLumaChromaINTEL: return "OpSubgroupAvcSicConfigureIpeLumaChromaINTEL"; + case OpSubgroupAvcSicGetMotionVectorMaskINTEL: return "OpSubgroupAvcSicGetMotionVectorMaskINTEL"; + case OpSubgroupAvcSicConvertToMcePayloadINTEL: return "OpSubgroupAvcSicConvertToMcePayloadINTEL"; + case OpSubgroupAvcSicSetIntraLumaShapePenaltyINTEL: return "OpSubgroupAvcSicSetIntraLumaShapePenaltyINTEL"; + case OpSubgroupAvcSicSetIntraLumaModeCostFunctionINTEL: return "OpSubgroupAvcSicSetIntraLumaModeCostFunctionINTEL"; + case OpSubgroupAvcSicSetIntraChromaModeCostFunctionINTEL: return "OpSubgroupAvcSicSetIntraChromaModeCostFunctionINTEL"; + case OpSubgroupAvcSicSetBilinearFilterEnableINTEL: return "OpSubgroupAvcSicSetBilinearFilterEnableINTEL"; + case OpSubgroupAvcSicSetSkcForwardTransformEnableINTEL: return "OpSubgroupAvcSicSetSkcForwardTransformEnableINTEL"; + case OpSubgroupAvcSicSetBlockBasedRawSkipSadINTEL: return "OpSubgroupAvcSicSetBlockBasedRawSkipSadINTEL"; + case OpSubgroupAvcSicEvaluateIpeINTEL: return "OpSubgroupAvcSicEvaluateIpeINTEL"; + case OpSubgroupAvcSicEvaluateWithSingleReferenceINTEL: return "OpSubgroupAvcSicEvaluateWithSingleReferenceINTEL"; + case OpSubgroupAvcSicEvaluateWithDualReferenceINTEL: return "OpSubgroupAvcSicEvaluateWithDualReferenceINTEL"; + case OpSubgroupAvcSicEvaluateWithMultiReferenceINTEL: return "OpSubgroupAvcSicEvaluateWithMultiReferenceINTEL"; + case OpSubgroupAvcSicEvaluateWithMultiReferenceInterlacedINTEL: return "OpSubgroupAvcSicEvaluateWithMultiReferenceInterlacedINTEL"; + case OpSubgroupAvcSicConvertToMceResultINTEL: return "OpSubgroupAvcSicConvertToMceResultINTEL"; + case OpSubgroupAvcSicGetIpeLumaShapeINTEL: return "OpSubgroupAvcSicGetIpeLumaShapeINTEL"; + case OpSubgroupAvcSicGetBestIpeLumaDistortionINTEL: return "OpSubgroupAvcSicGetBestIpeLumaDistortionINTEL"; + case OpSubgroupAvcSicGetBestIpeChromaDistortionINTEL: return "OpSubgroupAvcSicGetBestIpeChromaDistortionINTEL"; + case OpSubgroupAvcSicGetPackedIpeLumaModesINTEL: return "OpSubgroupAvcSicGetPackedIpeLumaModesINTEL"; + case OpSubgroupAvcSicGetIpeChromaModeINTEL: return "OpSubgroupAvcSicGetIpeChromaModeINTEL"; + case OpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL: return "OpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL"; + case OpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL: return "OpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL"; + case OpSubgroupAvcSicGetInterRawSadsINTEL: return "OpSubgroupAvcSicGetInterRawSadsINTEL"; + case OpVariableLengthArrayINTEL: return "OpVariableLengthArrayINTEL"; + case OpSaveMemoryINTEL: return "OpSaveMemoryINTEL"; + case OpRestoreMemoryINTEL: return "OpRestoreMemoryINTEL"; + case OpArbitraryFloatSinCosPiINTEL: return "OpArbitraryFloatSinCosPiINTEL"; + case OpArbitraryFloatCastINTEL: return "OpArbitraryFloatCastINTEL"; + case OpArbitraryFloatCastFromIntINTEL: return "OpArbitraryFloatCastFromIntINTEL"; + case OpArbitraryFloatCastToIntINTEL: return "OpArbitraryFloatCastToIntINTEL"; + case OpArbitraryFloatAddINTEL: return "OpArbitraryFloatAddINTEL"; + case OpArbitraryFloatSubINTEL: return "OpArbitraryFloatSubINTEL"; + case OpArbitraryFloatMulINTEL: return "OpArbitraryFloatMulINTEL"; + case OpArbitraryFloatDivINTEL: return "OpArbitraryFloatDivINTEL"; + case OpArbitraryFloatGTINTEL: return "OpArbitraryFloatGTINTEL"; + case OpArbitraryFloatGEINTEL: return "OpArbitraryFloatGEINTEL"; + case OpArbitraryFloatLTINTEL: return "OpArbitraryFloatLTINTEL"; + case OpArbitraryFloatLEINTEL: return "OpArbitraryFloatLEINTEL"; + case OpArbitraryFloatEQINTEL: return "OpArbitraryFloatEQINTEL"; + case OpArbitraryFloatRecipINTEL: return "OpArbitraryFloatRecipINTEL"; + case OpArbitraryFloatRSqrtINTEL: return "OpArbitraryFloatRSqrtINTEL"; + case OpArbitraryFloatCbrtINTEL: return "OpArbitraryFloatCbrtINTEL"; + case OpArbitraryFloatHypotINTEL: return "OpArbitraryFloatHypotINTEL"; + case OpArbitraryFloatSqrtINTEL: return "OpArbitraryFloatSqrtINTEL"; + case OpArbitraryFloatLogINTEL: return "OpArbitraryFloatLogINTEL"; + case OpArbitraryFloatLog2INTEL: return "OpArbitraryFloatLog2INTEL"; + case OpArbitraryFloatLog10INTEL: return "OpArbitraryFloatLog10INTEL"; + case OpArbitraryFloatLog1pINTEL: return "OpArbitraryFloatLog1pINTEL"; + case OpArbitraryFloatExpINTEL: return "OpArbitraryFloatExpINTEL"; + case OpArbitraryFloatExp2INTEL: return "OpArbitraryFloatExp2INTEL"; + case OpArbitraryFloatExp10INTEL: return "OpArbitraryFloatExp10INTEL"; + case OpArbitraryFloatExpm1INTEL: return "OpArbitraryFloatExpm1INTEL"; + case OpArbitraryFloatSinINTEL: return "OpArbitraryFloatSinINTEL"; + case OpArbitraryFloatCosINTEL: return "OpArbitraryFloatCosINTEL"; + case OpArbitraryFloatSinCosINTEL: return "OpArbitraryFloatSinCosINTEL"; + case OpArbitraryFloatSinPiINTEL: return "OpArbitraryFloatSinPiINTEL"; + case OpArbitraryFloatCosPiINTEL: return "OpArbitraryFloatCosPiINTEL"; + case OpArbitraryFloatASinINTEL: return "OpArbitraryFloatASinINTEL"; + case OpArbitraryFloatASinPiINTEL: return "OpArbitraryFloatASinPiINTEL"; + case OpArbitraryFloatACosINTEL: return "OpArbitraryFloatACosINTEL"; + case OpArbitraryFloatACosPiINTEL: return "OpArbitraryFloatACosPiINTEL"; + case OpArbitraryFloatATanINTEL: return "OpArbitraryFloatATanINTEL"; + case OpArbitraryFloatATanPiINTEL: return "OpArbitraryFloatATanPiINTEL"; + case OpArbitraryFloatATan2INTEL: return "OpArbitraryFloatATan2INTEL"; + case OpArbitraryFloatPowINTEL: return "OpArbitraryFloatPowINTEL"; + case OpArbitraryFloatPowRINTEL: return "OpArbitraryFloatPowRINTEL"; + case OpArbitraryFloatPowNINTEL: return "OpArbitraryFloatPowNINTEL"; + case OpLoopControlINTEL: return "OpLoopControlINTEL"; + case OpAliasDomainDeclINTEL: return "OpAliasDomainDeclINTEL"; + case OpAliasScopeDeclINTEL: return "OpAliasScopeDeclINTEL"; + case OpAliasScopeListDeclINTEL: return "OpAliasScopeListDeclINTEL"; + case OpFixedSqrtINTEL: return "OpFixedSqrtINTEL"; + case OpFixedRecipINTEL: return "OpFixedRecipINTEL"; + case OpFixedRsqrtINTEL: return "OpFixedRsqrtINTEL"; + case OpFixedSinINTEL: return "OpFixedSinINTEL"; + case OpFixedCosINTEL: return "OpFixedCosINTEL"; + case OpFixedSinCosINTEL: return "OpFixedSinCosINTEL"; + case OpFixedSinPiINTEL: return "OpFixedSinPiINTEL"; + case OpFixedCosPiINTEL: return "OpFixedCosPiINTEL"; + case OpFixedSinCosPiINTEL: return "OpFixedSinCosPiINTEL"; + case OpFixedLogINTEL: return "OpFixedLogINTEL"; + case OpFixedExpINTEL: return "OpFixedExpINTEL"; + case OpPtrCastToCrossWorkgroupINTEL: return "OpPtrCastToCrossWorkgroupINTEL"; + case OpCrossWorkgroupCastToPtrINTEL: return "OpCrossWorkgroupCastToPtrINTEL"; + case OpReadPipeBlockingINTEL: return "OpReadPipeBlockingINTEL"; + case OpWritePipeBlockingINTEL: return "OpWritePipeBlockingINTEL"; + case OpFPGARegINTEL: return "OpFPGARegINTEL"; + case OpRayQueryGetRayTMinKHR: return "OpRayQueryGetRayTMinKHR"; + case OpRayQueryGetRayFlagsKHR: return "OpRayQueryGetRayFlagsKHR"; + case OpRayQueryGetIntersectionTKHR: return "OpRayQueryGetIntersectionTKHR"; + case OpRayQueryGetIntersectionInstanceCustomIndexKHR: return "OpRayQueryGetIntersectionInstanceCustomIndexKHR"; + case OpRayQueryGetIntersectionInstanceIdKHR: return "OpRayQueryGetIntersectionInstanceIdKHR"; + case OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR: return "OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR"; + case OpRayQueryGetIntersectionGeometryIndexKHR: return "OpRayQueryGetIntersectionGeometryIndexKHR"; + case OpRayQueryGetIntersectionPrimitiveIndexKHR: return "OpRayQueryGetIntersectionPrimitiveIndexKHR"; + case OpRayQueryGetIntersectionBarycentricsKHR: return "OpRayQueryGetIntersectionBarycentricsKHR"; + case OpRayQueryGetIntersectionFrontFaceKHR: return "OpRayQueryGetIntersectionFrontFaceKHR"; + case OpRayQueryGetIntersectionCandidateAABBOpaqueKHR: return "OpRayQueryGetIntersectionCandidateAABBOpaqueKHR"; + case OpRayQueryGetIntersectionObjectRayDirectionKHR: return "OpRayQueryGetIntersectionObjectRayDirectionKHR"; + case OpRayQueryGetIntersectionObjectRayOriginKHR: return "OpRayQueryGetIntersectionObjectRayOriginKHR"; + case OpRayQueryGetWorldRayDirectionKHR: return "OpRayQueryGetWorldRayDirectionKHR"; + case OpRayQueryGetWorldRayOriginKHR: return "OpRayQueryGetWorldRayOriginKHR"; + case OpRayQueryGetIntersectionObjectToWorldKHR: return "OpRayQueryGetIntersectionObjectToWorldKHR"; + case OpRayQueryGetIntersectionWorldToObjectKHR: return "OpRayQueryGetIntersectionWorldToObjectKHR"; + case OpAtomicFAddEXT: return "OpAtomicFAddEXT"; + case OpTypeBufferSurfaceINTEL: return "OpTypeBufferSurfaceINTEL"; + case OpTypeStructContinuedINTEL: return "OpTypeStructContinuedINTEL"; + case OpConstantCompositeContinuedINTEL: return "OpConstantCompositeContinuedINTEL"; + case OpSpecConstantCompositeContinuedINTEL: return "OpSpecConstantCompositeContinuedINTEL"; + case OpCompositeConstructContinuedINTEL: return "OpCompositeConstructContinuedINTEL"; + case OpConvertFToBF16INTEL: return "OpConvertFToBF16INTEL"; + case OpConvertBF16ToFINTEL: return "OpConvertBF16ToFINTEL"; + case OpControlBarrierArriveINTEL: return "OpControlBarrierArriveINTEL"; + case OpControlBarrierWaitINTEL: return "OpControlBarrierWaitINTEL"; + case OpArithmeticFenceEXT: return "OpArithmeticFenceEXT"; + case OpTaskSequenceCreateINTEL: return "OpTaskSequenceCreateINTEL"; + case OpTaskSequenceAsyncINTEL: return "OpTaskSequenceAsyncINTEL"; + case OpTaskSequenceGetINTEL: return "OpTaskSequenceGetINTEL"; + case OpTaskSequenceReleaseINTEL: return "OpTaskSequenceReleaseINTEL"; + case OpTypeTaskSequenceINTEL: return "OpTypeTaskSequenceINTEL"; + case OpSubgroupBlockPrefetchINTEL: return "OpSubgroupBlockPrefetchINTEL"; + case OpSubgroup2DBlockLoadINTEL: return "OpSubgroup2DBlockLoadINTEL"; + case OpSubgroup2DBlockLoadTransformINTEL: return "OpSubgroup2DBlockLoadTransformINTEL"; + case OpSubgroup2DBlockLoadTransposeINTEL: return "OpSubgroup2DBlockLoadTransposeINTEL"; + case OpSubgroup2DBlockPrefetchINTEL: return "OpSubgroup2DBlockPrefetchINTEL"; + case OpSubgroup2DBlockStoreINTEL: return "OpSubgroup2DBlockStoreINTEL"; + case OpSubgroupMatrixMultiplyAccumulateINTEL: return "OpSubgroupMatrixMultiplyAccumulateINTEL"; + case OpBitwiseFunctionINTEL: return "OpBitwiseFunctionINTEL"; + case OpGroupIMulKHR: return "OpGroupIMulKHR"; + case OpGroupFMulKHR: return "OpGroupFMulKHR"; + case OpGroupBitwiseAndKHR: return "OpGroupBitwiseAndKHR"; + case OpGroupBitwiseOrKHR: return "OpGroupBitwiseOrKHR"; + case OpGroupBitwiseXorKHR: return "OpGroupBitwiseXorKHR"; + case OpGroupLogicalAndKHR: return "OpGroupLogicalAndKHR"; + case OpGroupLogicalOrKHR: return "OpGroupLogicalOrKHR"; + case OpGroupLogicalXorKHR: return "OpGroupLogicalXorKHR"; + case OpRoundFToTF32INTEL: return "OpRoundFToTF32INTEL"; + case OpMaskedGatherINTEL: return "OpMaskedGatherINTEL"; + case OpMaskedScatterINTEL: return "OpMaskedScatterINTEL"; + default: return "Unknown"; + } +} + +#endif /* SPV_ENABLE_UTILITY_CODE */ + +// Overload bitwise operators for mask bit combining + +inline ImageOperandsMask operator|(ImageOperandsMask a, ImageOperandsMask b) { return ImageOperandsMask(unsigned(a) | unsigned(b)); } +inline ImageOperandsMask operator&(ImageOperandsMask a, ImageOperandsMask b) { return ImageOperandsMask(unsigned(a) & unsigned(b)); } +inline ImageOperandsMask operator^(ImageOperandsMask a, ImageOperandsMask b) { return ImageOperandsMask(unsigned(a) ^ unsigned(b)); } +inline ImageOperandsMask operator~(ImageOperandsMask a) { return ImageOperandsMask(~unsigned(a)); } +inline FPFastMathModeMask operator|(FPFastMathModeMask a, FPFastMathModeMask b) { return FPFastMathModeMask(unsigned(a) | unsigned(b)); } +inline FPFastMathModeMask operator&(FPFastMathModeMask a, FPFastMathModeMask b) { return FPFastMathModeMask(unsigned(a) & unsigned(b)); } +inline FPFastMathModeMask operator^(FPFastMathModeMask a, FPFastMathModeMask b) { return FPFastMathModeMask(unsigned(a) ^ unsigned(b)); } +inline FPFastMathModeMask operator~(FPFastMathModeMask a) { return FPFastMathModeMask(~unsigned(a)); } +inline SelectionControlMask operator|(SelectionControlMask a, SelectionControlMask b) { return SelectionControlMask(unsigned(a) | unsigned(b)); } +inline SelectionControlMask operator&(SelectionControlMask a, SelectionControlMask b) { return SelectionControlMask(unsigned(a) & unsigned(b)); } +inline SelectionControlMask operator^(SelectionControlMask a, SelectionControlMask b) { return SelectionControlMask(unsigned(a) ^ unsigned(b)); } +inline SelectionControlMask operator~(SelectionControlMask a) { return SelectionControlMask(~unsigned(a)); } +inline LoopControlMask operator|(LoopControlMask a, LoopControlMask b) { return LoopControlMask(unsigned(a) | unsigned(b)); } +inline LoopControlMask operator&(LoopControlMask a, LoopControlMask b) { return LoopControlMask(unsigned(a) & unsigned(b)); } +inline LoopControlMask operator^(LoopControlMask a, LoopControlMask b) { return LoopControlMask(unsigned(a) ^ unsigned(b)); } +inline LoopControlMask operator~(LoopControlMask a) { return LoopControlMask(~unsigned(a)); } +inline FunctionControlMask operator|(FunctionControlMask a, FunctionControlMask b) { return FunctionControlMask(unsigned(a) | unsigned(b)); } +inline FunctionControlMask operator&(FunctionControlMask a, FunctionControlMask b) { return FunctionControlMask(unsigned(a) & unsigned(b)); } +inline FunctionControlMask operator^(FunctionControlMask a, FunctionControlMask b) { return FunctionControlMask(unsigned(a) ^ unsigned(b)); } +inline FunctionControlMask operator~(FunctionControlMask a) { return FunctionControlMask(~unsigned(a)); } +inline MemorySemanticsMask operator|(MemorySemanticsMask a, MemorySemanticsMask b) { return MemorySemanticsMask(unsigned(a) | unsigned(b)); } +inline MemorySemanticsMask operator&(MemorySemanticsMask a, MemorySemanticsMask b) { return MemorySemanticsMask(unsigned(a) & unsigned(b)); } +inline MemorySemanticsMask operator^(MemorySemanticsMask a, MemorySemanticsMask b) { return MemorySemanticsMask(unsigned(a) ^ unsigned(b)); } +inline MemorySemanticsMask operator~(MemorySemanticsMask a) { return MemorySemanticsMask(~unsigned(a)); } +inline MemoryAccessMask operator|(MemoryAccessMask a, MemoryAccessMask b) { return MemoryAccessMask(unsigned(a) | unsigned(b)); } +inline MemoryAccessMask operator&(MemoryAccessMask a, MemoryAccessMask b) { return MemoryAccessMask(unsigned(a) & unsigned(b)); } +inline MemoryAccessMask operator^(MemoryAccessMask a, MemoryAccessMask b) { return MemoryAccessMask(unsigned(a) ^ unsigned(b)); } +inline MemoryAccessMask operator~(MemoryAccessMask a) { return MemoryAccessMask(~unsigned(a)); } +inline KernelProfilingInfoMask operator|(KernelProfilingInfoMask a, KernelProfilingInfoMask b) { return KernelProfilingInfoMask(unsigned(a) | unsigned(b)); } +inline KernelProfilingInfoMask operator&(KernelProfilingInfoMask a, KernelProfilingInfoMask b) { return KernelProfilingInfoMask(unsigned(a) & unsigned(b)); } +inline KernelProfilingInfoMask operator^(KernelProfilingInfoMask a, KernelProfilingInfoMask b) { return KernelProfilingInfoMask(unsigned(a) ^ unsigned(b)); } +inline KernelProfilingInfoMask operator~(KernelProfilingInfoMask a) { return KernelProfilingInfoMask(~unsigned(a)); } +inline RayFlagsMask operator|(RayFlagsMask a, RayFlagsMask b) { return RayFlagsMask(unsigned(a) | unsigned(b)); } +inline RayFlagsMask operator&(RayFlagsMask a, RayFlagsMask b) { return RayFlagsMask(unsigned(a) & unsigned(b)); } +inline RayFlagsMask operator^(RayFlagsMask a, RayFlagsMask b) { return RayFlagsMask(unsigned(a) ^ unsigned(b)); } +inline RayFlagsMask operator~(RayFlagsMask a) { return RayFlagsMask(~unsigned(a)); } +inline FragmentShadingRateMask operator|(FragmentShadingRateMask a, FragmentShadingRateMask b) { return FragmentShadingRateMask(unsigned(a) | unsigned(b)); } +inline FragmentShadingRateMask operator&(FragmentShadingRateMask a, FragmentShadingRateMask b) { return FragmentShadingRateMask(unsigned(a) & unsigned(b)); } +inline FragmentShadingRateMask operator^(FragmentShadingRateMask a, FragmentShadingRateMask b) { return FragmentShadingRateMask(unsigned(a) ^ unsigned(b)); } +inline FragmentShadingRateMask operator~(FragmentShadingRateMask a) { return FragmentShadingRateMask(~unsigned(a)); } +inline CooperativeMatrixOperandsMask operator|(CooperativeMatrixOperandsMask a, CooperativeMatrixOperandsMask b) { return CooperativeMatrixOperandsMask(unsigned(a) | unsigned(b)); } +inline CooperativeMatrixOperandsMask operator&(CooperativeMatrixOperandsMask a, CooperativeMatrixOperandsMask b) { return CooperativeMatrixOperandsMask(unsigned(a) & unsigned(b)); } +inline CooperativeMatrixOperandsMask operator^(CooperativeMatrixOperandsMask a, CooperativeMatrixOperandsMask b) { return CooperativeMatrixOperandsMask(unsigned(a) ^ unsigned(b)); } +inline CooperativeMatrixOperandsMask operator~(CooperativeMatrixOperandsMask a) { return CooperativeMatrixOperandsMask(~unsigned(a)); } +inline CooperativeMatrixReduceMask operator|(CooperativeMatrixReduceMask a, CooperativeMatrixReduceMask b) { return CooperativeMatrixReduceMask(unsigned(a) | unsigned(b)); } +inline CooperativeMatrixReduceMask operator&(CooperativeMatrixReduceMask a, CooperativeMatrixReduceMask b) { return CooperativeMatrixReduceMask(unsigned(a) & unsigned(b)); } +inline CooperativeMatrixReduceMask operator^(CooperativeMatrixReduceMask a, CooperativeMatrixReduceMask b) { return CooperativeMatrixReduceMask(unsigned(a) ^ unsigned(b)); } +inline CooperativeMatrixReduceMask operator~(CooperativeMatrixReduceMask a) { return CooperativeMatrixReduceMask(~unsigned(a)); } +inline TensorAddressingOperandsMask operator|(TensorAddressingOperandsMask a, TensorAddressingOperandsMask b) { return TensorAddressingOperandsMask(unsigned(a) | unsigned(b)); } +inline TensorAddressingOperandsMask operator&(TensorAddressingOperandsMask a, TensorAddressingOperandsMask b) { return TensorAddressingOperandsMask(unsigned(a) & unsigned(b)); } +inline TensorAddressingOperandsMask operator^(TensorAddressingOperandsMask a, TensorAddressingOperandsMask b) { return TensorAddressingOperandsMask(unsigned(a) ^ unsigned(b)); } +inline TensorAddressingOperandsMask operator~(TensorAddressingOperandsMask a) { return TensorAddressingOperandsMask(~unsigned(a)); } +inline MatrixMultiplyAccumulateOperandsMask operator|(MatrixMultiplyAccumulateOperandsMask a, MatrixMultiplyAccumulateOperandsMask b) { return MatrixMultiplyAccumulateOperandsMask(unsigned(a) | unsigned(b)); } +inline MatrixMultiplyAccumulateOperandsMask operator&(MatrixMultiplyAccumulateOperandsMask a, MatrixMultiplyAccumulateOperandsMask b) { return MatrixMultiplyAccumulateOperandsMask(unsigned(a) & unsigned(b)); } +inline MatrixMultiplyAccumulateOperandsMask operator^(MatrixMultiplyAccumulateOperandsMask a, MatrixMultiplyAccumulateOperandsMask b) { return MatrixMultiplyAccumulateOperandsMask(unsigned(a) ^ unsigned(b)); } +inline MatrixMultiplyAccumulateOperandsMask operator~(MatrixMultiplyAccumulateOperandsMask a) { return MatrixMultiplyAccumulateOperandsMask(~unsigned(a)); } +inline RawAccessChainOperandsMask operator|(RawAccessChainOperandsMask a, RawAccessChainOperandsMask b) { return RawAccessChainOperandsMask(unsigned(a) | unsigned(b)); } +inline RawAccessChainOperandsMask operator&(RawAccessChainOperandsMask a, RawAccessChainOperandsMask b) { return RawAccessChainOperandsMask(unsigned(a) & unsigned(b)); } +inline RawAccessChainOperandsMask operator^(RawAccessChainOperandsMask a, RawAccessChainOperandsMask b) { return RawAccessChainOperandsMask(unsigned(a) ^ unsigned(b)); } +inline RawAccessChainOperandsMask operator~(RawAccessChainOperandsMask a) { return RawAccessChainOperandsMask(~unsigned(a)); } + +} // end namespace spv + +#endif // #ifndef spirv_HPP diff --git a/app/src/main/cpp/thirdparty/dxbc/include/util/log/log.h b/app/src/main/cpp/thirdparty/dxbc/include/util/log/log.h new file mode 100644 index 000000000..b12428c08 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/util/log/log.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +namespace dxvk { + + enum class LogLevel : uint32_t { + Trace = 0, + Debug = 1, + Info = 2, + Warn = 3, + Error = 4, + None = 5, + }; + + /** + * \brief Logger + * + * Logger for one DLL. Creates a text file and + * writes all log messages to that file. + */ + class Logger { + + public: + + Logger() {} + Logger(const std::string& file_name) {} + ~Logger() {} + + static void trace(const std::string& message) {} + static void debug(const std::string& message) {} + static void info (const std::string& message) {} + static void warn (const std::string& message) {} + static void err (const std::string& message) {} + static void log (LogLevel level, const std::string& message) {} + + static LogLevel logLevel() { + return LogLevel::Warn; + } + + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/util/log/log_debug.h b/app/src/main/cpp/thirdparty/dxbc/include/util/log/log_debug.h new file mode 100644 index 000000000..34c61106c --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/util/log/log_debug.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include "log/log.h" + +#ifdef _MSC_VER +#define METHOD_NAME __FUNCSIG__ +#else +#define METHOD_NAME __PRETTY_FUNCTION__ +#endif + +#define TRACE_ENABLED + +#ifdef TRACE_ENABLED +#define TRACE(...) \ + do { dxvk::debug::trace(METHOD_NAME, ##__VA_ARGS__); } while (0) +#else +#define TRACE(...) \ + do { } while (0) +#endif + +namespace dxvk::debug { + + std::string methodName(const std::string& prettyName); + + inline void traceArgs(std::stringstream& stream) { } + + template + void traceArgs(std::stringstream& stream, const Arg1& arg1) { + stream << arg1; + } + + template + void traceArgs(std::stringstream& stream, const Arg1& arg1, const Arg2& arg2, const Args&... args) { + stream << arg1 << ","; + traceArgs(stream, arg2, args...); + } + + template + void trace(const std::string& funcName, const Args&... args) { + std::stringstream stream; + stream << methodName(funcName) << "("; + traceArgs(stream, args...); + stream << ")"; + Logger::trace(stream.str()); + } + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/util/rc/util_rc.h b/app/src/main/cpp/thirdparty/dxbc/include/util/rc/util_rc.h new file mode 100644 index 000000000..b92cf00b4 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/util/rc/util_rc.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "../util_likely.h" + +namespace dxvk { + + /** + * \brief Reference-counted object + */ + class RcObject { + + public: + + /** + * \brief Increments reference count + * \returns New reference count + */ + force_inline uint32_t incRef() { + return ++m_refCount; + } + + /** + * \brief Decrements reference count + * \returns New reference count + */ + force_inline uint32_t decRef() { + return --m_refCount; + } + + private: + + std::atomic m_refCount = { 0u }; + + }; + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/include/util/rc/util_rc_ptr.h b/app/src/main/cpp/thirdparty/dxbc/include/util/rc/util_rc_ptr.h new file mode 100644 index 000000000..ac465758c --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/util/rc/util_rc_ptr.h @@ -0,0 +1,189 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace dxvk { + + /** + * \brief Pointer for reference-counted objects + * + * This only requires the given type to implement \c incRef + * and \c decRef methods that adjust the reference count. + * \tparam T Object type + */ + template + class Rc { + template + friend class Rc; + public: + + Rc() = default; + Rc(std::nullptr_t) { } + + Rc(T* object) + : m_object(object) { + this->incRef(); + } + + Rc(const Rc& other) + : m_object(other.m_object) { + this->incRef(); + } + + template + Rc(const Rc& other) + : m_object(other.m_object) { + this->incRef(); + } + + Rc(Rc&& other) + : m_object(other.m_object) { + other.m_object = nullptr; + } + + template + Rc(Rc&& other) + : m_object(other.m_object) { + other.m_object = nullptr; + } + + Rc& operator = (std::nullptr_t) { + this->decRef(); + m_object = nullptr; + return *this; + } + + Rc& operator = (const Rc& other) { + other.incRef(); + this->decRef(); + m_object = other.m_object; + return *this; + } + + template + Rc& operator = (const Rc& other) { + other.incRef(); + this->decRef(); + m_object = other.m_object; + return *this; + } + + Rc& operator = (Rc&& other) { + this->decRef(); + this->m_object = other.m_object; + other.m_object = nullptr; + return *this; + } + + template + Rc& operator = (Rc&& other) { + this->decRef(); + this->m_object = other.m_object; + other.m_object = nullptr; + return *this; + } + + ~Rc() { + this->decRef(); + } + + T& operator * () const { return *m_object; } + T* operator -> () const { return m_object; } + T* ptr() const { return m_object; } + + template bool operator == (const Rc& other) const { return m_object == other.m_object; } + template bool operator != (const Rc& other) const { return m_object != other.m_object; } + + template bool operator == (Tx* other) const { return m_object == other; } + template bool operator != (Tx* other) const { return m_object != other; } + + bool operator == (std::nullptr_t) const { return m_object == nullptr; } + bool operator != (std::nullptr_t) const { return m_object != nullptr; } + + explicit operator bool () const { + return m_object != nullptr; + } + + /** + * \brief Sets pointer without acquiring a reference + * + * Must only be use when a reference has been taken via + * other means. + * \param [in] object Object pointer + */ + void unsafeInsert(T* object) { + this->decRef(); + m_object = object; + } + + /** + * \brief Extracts raw pointer + * + * Sets the smart pointer to null without decrementing the + * reference count. Must only be used when the reference + * count is decremented in some other way. + * \returns Pointer to owned object + */ + T* unsafeExtract() { + return std::exchange(m_object, nullptr); + } + + /** + * \brief Creates smart pointer without taking reference + * + * Must only be used when a refernece has been obtained via other means. + * \param [in] object Pointer to object to take ownership of + */ + static Rc unsafeCreate(T* object) { + return Rc(object, false); + } + + private: + + T* m_object = nullptr; + + explicit Rc(T* object, bool) + : m_object(object) { } + + force_inline void incRef() const { + if (m_object != nullptr) + m_object->incRef(); + } + + force_inline void decRef() const { + if (m_object != nullptr) { + if constexpr (std::is_void_vdecRef())>) { + m_object->decRef(); + } else { + // Deprecated, objects should manage themselves now. + if (!m_object->decRef()) + delete m_object; + } + } + } + + }; + + template + bool operator == (Tx* a, const Rc& b) { return b == a; } + + template + bool operator != (Tx* a, const Rc& b) { return b != a; } + + struct RcHash { + template + size_t operator () (const Rc& rc) const { + return reinterpret_cast(rc.ptr()) / sizeof(T); + } + }; + +} + +template +std::ostream& operator << (std::ostream& os, const dxvk::Rc& rc) { + return os << rc.ptr(); +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/util/util_bit.h b/app/src/main/cpp/thirdparty/dxbc/include/util/util_bit.h new file mode 100644 index 000000000..2a5302581 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/util/util_bit.h @@ -0,0 +1,719 @@ +#pragma once + +#if (defined(__x86_64__) && !defined(__arm64ec__)) || (defined(_M_X64) && !defined(_M_ARM64EC)) \ + || defined(__i386__) || defined(_M_IX86) || defined(__e2k__) + #define DXVK_ARCH_X86 + #if defined(__x86_64__) || defined(_M_X64) || defined(__e2k__) + #define DXVK_ARCH_X86_64 + #endif +#elif defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) + #define DXVK_ARCH_ARM64 +#endif + +#ifdef DXVK_ARCH_X86 + #ifndef _MSC_VER + #if defined(_WIN32) && (defined(__AVX__) || defined(__AVX2__)) + #error "AVX-enabled builds not supported due to stack alignment issues." + #endif + #if defined(__WINE__) && defined(__clang__) + #pragma push_macro("_WIN32") + #undef _WIN32 + #endif + #include + #if defined(__WINE__) && defined(__clang__) + #pragma pop_macro("_WIN32") + #endif + #else + #include + #endif +#endif + +#include "util_likely.h" +#include "util_math.h" + +#include +#include +#include +#include +#include +#include + +namespace dxvk::bit { + + template + T cast(const J& src) { + static_assert(sizeof(T) == sizeof(J)); + static_assert(std::is_trivially_copyable::value && std::is_trivial::value); + + T dst; + std::memcpy(&dst, &src, sizeof(T)); + return dst; + } + + template + T extract(T value, uint32_t fst, uint32_t lst) { + return (value >> fst) & ~(~T(0) << (lst - fst + 1)); + } + + template + T popcnt(T n) { + n -= ((n >> 1u) & T(0x5555555555555555ull)); + n = (n & T(0x3333333333333333ull)) + ((n >> 2u) & T(0x3333333333333333ull)); + n = (n + (n >> 4u)) & T(0x0f0f0f0f0f0f0f0full); + n *= T(0x0101010101010101ull); + return n >> (8u * (sizeof(T) - 1u)); + } + + inline uint32_t tzcnt(uint32_t n) { + #if defined(_MSC_VER) && !defined(__clang__) + if(n == 0) + return 32; + return _tzcnt_u32(n); + #elif defined(__BMI__) + return __tzcnt_u32(n); + #elif defined(DXVK_ARCH_X86) && (defined(__GNUC__) || defined(__clang__)) + // tzcnt is encoded as rep bsf, so we can use it on all + // processors, but the behaviour of zero inputs differs: + // - bsf: zf = 1, cf = ?, result = ? + // - tzcnt: zf = 0, cf = 1, result = 32 + // We'll have to handle this case manually. + uint32_t res; + uint32_t tmp; + asm ( + "tzcnt %2, %0;" + "mov $32, %1;" + "test %2, %2;" + "cmovz %1, %0;" + : "=&r" (res), "=&r" (tmp) + : "r" (n) + : "cc"); + return res; + #elif defined(__GNUC__) || defined(__clang__) + return n != 0 ? __builtin_ctz(n) : 32; + #else + uint32_t r = 31; + n &= -n; + r -= (n & 0x0000FFFF) ? 16 : 0; + r -= (n & 0x00FF00FF) ? 8 : 0; + r -= (n & 0x0F0F0F0F) ? 4 : 0; + r -= (n & 0x33333333) ? 2 : 0; + r -= (n & 0x55555555) ? 1 : 0; + return n != 0 ? r : 32; + #endif + } + + inline uint32_t tzcnt(uint64_t n) { + #if defined(DXVK_ARCH_X86_64) && defined(_MSC_VER) && !defined(__clang__) + if(n == 0) + return 64; + return (uint32_t)_tzcnt_u64(n); + #elif defined(DXVK_ARCH_X86_64) && defined(__BMI__) + return __tzcnt_u64(n); + #elif defined(DXVK_ARCH_X86_64) && (defined(__GNUC__) || defined(__clang__)) + uint64_t res; + uint64_t tmp; + asm ( + "tzcnt %2, %0;" + "mov $64, %1;" + "test %2, %2;" + "cmovz %1, %0;" + : "=&r" (res), "=&r" (tmp) + : "r" (n) + : "cc"); + return res; + #elif defined(__GNUC__) || defined(__clang__) + return n != 0 ? __builtin_ctzll(n) : 64; + #else + uint32_t lo = uint32_t(n); + if (lo) { + return tzcnt(lo); + } else { + uint32_t hi = uint32_t(n >> 32); + return tzcnt(hi) + 32; + } + #endif + } + + inline uint32_t bsf(uint32_t n) { + #if (defined(__GNUC__) || defined(__clang__)) && !defined(__BMI__) && defined(DXVK_ARCH_X86) + uint32_t res; + asm ("tzcnt %1,%0" + : "=r" (res) + : "r" (n) + : "cc"); + return res; + #else + return tzcnt(n); + #endif + } + + inline uint32_t bsf(uint64_t n) { + #if (defined(__GNUC__) || defined(__clang__)) && !defined(__BMI__) && defined(DXVK_ARCH_X86_64) + uint64_t res; + asm ("tzcnt %1,%0" + : "=r" (res) + : "r" (n) + : "cc"); + return res; + #else + return tzcnt(n); + #endif + } + + inline uint32_t lzcnt(uint32_t n) { + #if defined(_MSC_VER) && !defined(__clang__) && !defined(__LZCNT__) + unsigned long bsr; + if(n == 0) + return 32; + _BitScanReverse(&bsr, n); + return 31-bsr; + #elif (defined(_MSC_VER) && !defined(__clang__)) || defined(__LZCNT__) + return _lzcnt_u32(n); + #elif defined(__GNUC__) || defined(__clang__) + return n != 0 ? __builtin_clz(n) : 32; + #else + uint32_t r = 0; + + if (n == 0) return 32; + + if (n <= 0x0000FFFF) { r += 16; n <<= 16; } + if (n <= 0x00FFFFFF) { r += 8; n <<= 8; } + if (n <= 0x0FFFFFFF) { r += 4; n <<= 4; } + if (n <= 0x3FFFFFFF) { r += 2; n <<= 2; } + if (n <= 0x7FFFFFFF) { r += 1; n <<= 1; } + + return r; + #endif + } + + inline uint32_t lzcnt(uint64_t n) { + #if defined(_MSC_VER) && !defined(__clang__) && !defined(__LZCNT__) && defined(DXVK_ARCH_X86_64) + unsigned long bsr; + if(n == 0) + return 64; + _BitScanReverse64(&bsr, n); + return 63-bsr; + #elif defined(DXVK_ARCH_X86_64) && ((defined(_MSC_VER) && !defined(__clang__)) && defined(__LZCNT__)) + return _lzcnt_u64(n); + #elif defined(DXVK_ARCH_X86_64) && (defined(__GNUC__) || defined(__clang__)) + return n != 0 ? __builtin_clzll(n) : 64; + #else + uint32_t lo = uint32_t(n); + uint32_t hi = uint32_t(n >> 32u); + return hi ? lzcnt(hi) : lzcnt(lo) + 32u; + #endif + } + + template + uint32_t pack(T& dst, uint32_t& shift, T src, uint32_t count) { + constexpr uint32_t Bits = 8 * sizeof(T); + if (likely(shift < Bits)) + dst |= src << shift; + shift += count; + return shift > Bits ? shift - Bits : 0; + } + + template + uint32_t unpack(T& dst, T src, uint32_t& shift, uint32_t count) { + constexpr uint32_t Bits = 8 * sizeof(T); + if (likely(shift < Bits)) + dst = (src >> shift) & ((T(1) << count) - 1); + shift += count; + return shift > Bits ? shift - Bits : 0; + } + + + /** + * \brief Clears cache lines of memory + * + * Uses non-temporal stores. The memory region offset + * and size are assumed to be aligned to 64 bytes. + * \param [in] mem Memory region to clear + * \param [in] size Number of bytes to clear + */ + inline void bclear(void* mem, size_t size) { + #if defined(DXVK_ARCH_X86) && (defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER)) + auto zero = _mm_setzero_si128(); + + #if defined(__clang__) + #pragma nounroll + #elif defined(__GNUC__) + #pragma GCC unroll 0 + #endif + for (size_t i = 0; i < size; i += 64u) { + auto* ptr = reinterpret_cast<__m128i*>(mem) + i / sizeof(zero); + _mm_stream_si128(ptr + 0u, zero); + _mm_stream_si128(ptr + 1u, zero); + _mm_stream_si128(ptr + 2u, zero); + _mm_stream_si128(ptr + 3u, zero); + } + #else + std::memset(mem, 0, size); + #endif + } + + + /** + * \brief Compares two aligned structs bit by bit + * + * \param [in] a First struct + * \param [in] b Second struct + * \returns \c true if the structs are equal + */ + template + bool bcmpeq(const T* a, const T* b) { + static_assert(alignof(T) >= 16); + #if defined(DXVK_ARCH_X86) && (defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER)) + auto ai = reinterpret_cast(a); + auto bi = reinterpret_cast(b); + + size_t i = 0; + + #if defined(__clang__) + #pragma nounroll + #elif defined(__GNUC__) + #pragma GCC unroll 0 + #endif + + for ( ; i < 2 * (sizeof(T) / 32); i += 2) { + __m128i eq0 = _mm_cmpeq_epi8( + _mm_load_si128(ai + i), + _mm_load_si128(bi + i)); + __m128i eq1 = _mm_cmpeq_epi8( + _mm_load_si128(ai + i + 1), + _mm_load_si128(bi + i + 1)); + __m128i eq = _mm_and_si128(eq0, eq1); + + int mask = _mm_movemask_epi8(eq); + if (mask != 0xFFFF) + return false; + } + + for ( ; i < sizeof(T) / 16; i++) { + __m128i eq = _mm_cmpeq_epi8( + _mm_load_si128(ai + i), + _mm_load_si128(bi + i)); + + int mask = _mm_movemask_epi8(eq); + if (mask != 0xFFFF) + return false; + } + + return true; + #else + return !std::memcmp(a, b, sizeof(T)); + #endif + } + + template + class bitset { + static constexpr size_t Dwords = align(Bits, 32) / 32; + public: + + constexpr bitset() + : m_dwords() { + + } + + constexpr bool get(uint32_t idx) const { + uint32_t dword = 0; + uint32_t bit = idx; + + // Compiler doesn't remove this otherwise. + if constexpr (Dwords > 1) { + dword = idx / 32; + bit = idx % 32; + } + + return m_dwords[dword] & (1u << bit); + } + + constexpr void set(uint32_t idx, bool value) { + uint32_t dword = 0; + uint32_t bit = idx; + + // Compiler doesn't remove this otherwise. + if constexpr (Dwords > 1) { + dword = idx / 32; + bit = idx % 32; + } + + if (value) + m_dwords[dword] |= 1u << bit; + else + m_dwords[dword] &= ~(1u << bit); + } + + constexpr bool exchange(uint32_t idx, bool value) { + bool oldValue = get(idx); + set(idx, value); + return oldValue; + } + + constexpr void flip(uint32_t idx) { + uint32_t dword = 0; + uint32_t bit = idx; + + // Compiler doesn't remove this otherwise. + if constexpr (Dwords > 1) { + dword = idx / 32; + bit = idx % 32; + } + + m_dwords[dword] ^= 1u << bit; + } + + constexpr void setAll() { + if constexpr (Bits % 32 == 0) { + for (size_t i = 0; i < Dwords; i++) + m_dwords[i] = std::numeric_limits::max(); + } + else { + for (size_t i = 0; i < Dwords - 1; i++) + m_dwords[i] = std::numeric_limits::max(); + + m_dwords[Dwords - 1] = (1u << (Bits % 32)) - 1; + } + } + + constexpr void clearAll() { + for (size_t i = 0; i < Dwords; i++) + m_dwords[i] = 0; + } + + constexpr bool any() const { + for (size_t i = 0; i < Dwords; i++) { + if (m_dwords[i] != 0) + return true; + } + + return false; + } + + constexpr uint32_t& dword(uint32_t idx) { + return m_dwords[idx]; + } + + constexpr size_t bitCount() { + return Bits; + } + + constexpr size_t dwordCount() { + return Dwords; + } + + constexpr bool operator [] (uint32_t idx) const { + return get(idx); + } + + constexpr void setN(uint32_t bits) { + uint32_t fullDwords = bits / 32; + uint32_t offset = bits % 32; + + for (size_t i = 0; i < fullDwords; i++) + m_dwords[i] = std::numeric_limits::max(); + + if (offset > 0) + m_dwords[fullDwords] = (1u << offset) - 1; + } + + private: + + uint32_t m_dwords[Dwords]; + + }; + + class bitvector { + public: + + bool get(uint32_t idx) const { + uint32_t dword = idx / 32; + uint32_t bit = idx % 32; + + return m_dwords[dword] & (1u << bit); + } + + void ensureSize(uint32_t bitCount) { + uint32_t dword = bitCount / 32; + if (unlikely(dword >= m_dwords.size())) { + m_dwords.resize(dword + 1); + } + m_bitCount = std::max(m_bitCount, bitCount); + } + + void set(uint32_t idx, bool value) { + ensureSize(idx + 1); + + uint32_t dword = 0; + uint32_t bit = idx; + + if (value) + m_dwords[dword] |= 1u << bit; + else + m_dwords[dword] &= ~(1u << bit); + } + + bool exchange(uint32_t idx, bool value) { + ensureSize(idx + 1); + + bool oldValue = get(idx); + set(idx, value); + return oldValue; + } + + void flip(uint32_t idx) { + ensureSize(idx + 1); + + uint32_t dword = idx / 32; + uint32_t bit = idx % 32; + + m_dwords[dword] ^= 1u << bit; + } + + void setAll() { + if (m_bitCount % 32 == 0) { + for (size_t i = 0; i < m_dwords.size(); i++) + m_dwords[i] = std::numeric_limits::max(); + } + else { + for (size_t i = 0; i < m_dwords.size() - 1; i++) + m_dwords[i] = std::numeric_limits::max(); + + m_dwords[m_dwords.size() - 1] = (1u << (m_bitCount % 32)) - 1; + } + } + + void clearAll() { + for (size_t i = 0; i < m_dwords.size(); i++) + m_dwords[i] = 0; + } + + bool any() const { + for (size_t i = 0; i < m_dwords.size(); i++) { + if (m_dwords[i] != 0) + return true; + } + + return false; + } + + uint32_t& dword(uint32_t idx) { + return m_dwords[idx]; + } + + size_t bitCount() const { + return m_bitCount; + } + + size_t dwordCount() const { + return m_dwords.size(); + } + + bool operator [] (uint32_t idx) const { + return get(idx); + } + + void setN(uint32_t bits) { + ensureSize(bits); + + uint32_t fullDwords = bits / 32; + uint32_t offset = bits % 32; + + for (size_t i = 0; i < fullDwords; i++) + m_dwords[i] = std::numeric_limits::max(); + + if (offset > 0) + m_dwords[fullDwords] = (1u << offset) - 1; + } + + private: + + std::vector m_dwords; + uint32_t m_bitCount = 0; + + }; + + template + class BitMask { + + public: + + class iterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = T; + using difference_type = T; + using pointer = const T*; + using reference = T; + + explicit iterator(T flags) + : m_mask(flags) { } + + iterator& operator ++ () { + m_mask &= m_mask - 1; + return *this; + } + + iterator operator ++ (int) { + iterator retval = *this; + m_mask &= m_mask - 1; + return retval; + } + + T operator * () const { + return bsf(m_mask); + } + + bool operator == (iterator other) const { return m_mask == other.m_mask; } + bool operator != (iterator other) const { return m_mask != other.m_mask; } + + private: + + T m_mask; + + }; + + BitMask() + : m_mask(0) { } + + explicit BitMask(T n) + : m_mask(n) { } + + iterator begin() { + return iterator(m_mask); + } + + iterator end() { + return iterator(0); + } + + private: + + T m_mask; + + }; + + + /** + * \brief Encodes float as fixed point + * + * Rounds away from zero. If this is not suitable for + * certain use cases, implement round to nearest even. + * \tparam T Integer type, may be signed + * \tparam I Integer bits + * \tparam F Fractional bits + * \param n Float to encode + * \returns Encoded fixed-point value + */ + template + T encodeFixed(float n) { + if (n != n) + return 0u; + + n *= float(1u << F); + + if constexpr (std::is_signed_v) { + n = std::max(n, -float(1u << (I + F - 1u))); + n = std::min(n, float(1u << (I + F - 1u)) - 1.0f); + n += n < 0.0f ? -0.5f : 0.5f; + } else { + n = std::max(n, 0.0f); + n = std::min(n, float(1u << (I + F)) - 1.0f); + n += 0.5f; + } + + T result = T(n); + + if constexpr (std::is_signed_v) + result &= ((T(1u) << (I + F)) - 1u); + + return result; + } + + + /** + * \brief Decodes fixed-point integer to float + * + * \tparam T Integer type, may be signed + * \tparam I Integer bits + * \tparam F Fractional bits + * \param n Number to decode + * \returns Decoded number + */ + template + float decodeFixed(T n) { + // Sign-extend as necessary + if constexpr (std::is_signed_v) + n -= (n & (T(1u) << (I + F - 1u))) << 1u; + + return float(n) / float(1u << F); + } + + + /** + * \brief Inserts one null bit after each bit + */ + inline uint32_t split2(uint32_t c) { + c = (c ^ (c << 8u)) & 0x00ff00ffu; + c = (c ^ (c << 4u)) & 0x0f0f0f0fu; + c = (c ^ (c << 2u)) & 0x33333333u; + c = (c ^ (c << 1u)) & 0x55555555u; + return c; + } + + + /** + * \brief Inserts two null bits after each bit + */ + inline uint64_t split3(uint64_t c) { + c = (c | c << 32u) & 0x001f00000000ffffull; + c = (c | c << 16u) & 0x001f0000ff0000ffull; + c = (c | c << 8u) & 0x100f00f00f00f00full; + c = (c | c << 4u) & 0x10c30c30c30c30c3ull; + c = (c | c << 2u) & 0x1249249249249249ull; + return c; + } + + + /** + * \brief Interleaves bits from two integers + * + * Both numbers must fit into 16 bits. + * \param [in] x X coordinate + * \param [in] y Y coordinate + * \returns Morton code of x and y + */ + inline uint32_t interleave(uint16_t x, uint16_t y) { + return split2(x) | (split2(y) << 1u); + } + + + /** + * \brief Interleaves bits from three integers + * + * All three numbers must fit into 16 bits. + */ + inline uint64_t interleave(uint16_t x, uint16_t y, uint16_t z) { + return split3(x) | (split3(y) << 1u) | (split3(z) << 2u); + } + + + /** + * \brief 48-bit integer storage type + */ + struct uint48_t { + explicit uint48_t(uint64_t n) + : a(uint16_t(n)), b(uint16_t(n >> 16)), c(uint16_t(n >> 32)) { } + + uint16_t a; + uint16_t b; + uint16_t c; + + explicit operator uint64_t () const { + // GCC generates worse code if we promote to uint64 directly + uint32_t lo = uint32_t(a) | (uint32_t(b) << 16); + return uint64_t(lo) | (uint64_t(c) << 32); + } + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/util/util_enum.h b/app/src/main/cpp/thirdparty/dxbc/include/util/util_enum.h new file mode 100644 index 000000000..85b9b21b9 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/util/util_enum.h @@ -0,0 +1,7 @@ +#pragma once + +#define ENUM_NAME(name) \ + case name: return os << #name + +#define ENUM_DEFAULT(name) \ + default: return os << static_cast(e) diff --git a/app/src/main/cpp/thirdparty/dxbc/include/util/util_error.h b/app/src/main/cpp/thirdparty/dxbc/include/util/util_error.h new file mode 100644 index 000000000..2cfd45ffd --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/util/util_error.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +namespace dxvk { + + /** + * \brief DXVK error + * + * A generic exception class that stores a + * message. Exceptions should be logged. + */ + class DxvkError { + + public: + + DxvkError() { } + DxvkError(std::string&& message) + : m_message(std::move(message)) { } + + const std::string& message() const { + return m_message; + } + + private: + + std::string m_message; + + }; + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/include/util/util_flags.h b/app/src/main/cpp/thirdparty/dxbc/include/util/util_flags.h new file mode 100644 index 000000000..f67b4a2ef --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/util/util_flags.h @@ -0,0 +1,110 @@ +#pragma once + +#include + +#include "util_bit.h" + +namespace dxvk { + + template + class Flags { + + public: + + using IntType = std::underlying_type_t; + + Flags() { } + + Flags(IntType t) + : m_bits(t) { } + + template + Flags(T f, Tx... fx) { + this->set(f, fx...); + } + + template + void set(Tx... fx) { + m_bits |= bits(fx...); + } + + void set(Flags flags) { + m_bits |= flags.m_bits; + } + + template + void clr(Tx... fx) { + m_bits &= ~bits(fx...); + } + + void clr(Flags flags) { + m_bits &= ~flags.m_bits; + } + + template + bool any(Tx... fx) const { + return (m_bits & bits(fx...)) != 0; + } + + template + bool all(Tx... fx) const { + const IntType mask = bits(fx...); + return (m_bits & mask) == mask; + } + + bool test(T f) const { + return this->any(f); + } + + bool isClear() const { + return m_bits == 0; + } + + void clrAll() { + m_bits = 0; + } + + IntType raw() const { + return m_bits; + } + + Flags operator & (const Flags& other) const { + return Flags(m_bits & other.m_bits); + } + + Flags operator | (const Flags& other) const { + return Flags(m_bits | other.m_bits); + } + + Flags operator ^ (const Flags& other) const { + return Flags(m_bits ^ other.m_bits); + } + + bool operator == (const Flags& other) const { + return m_bits == other.m_bits; + } + + bool operator != (const Flags& other) const { + return m_bits != other.m_bits; + } + + private: + + IntType m_bits = 0; + + static IntType bit(T f) { + return IntType(1) << static_cast(f); + } + + template + static IntType bits(T f, Tx... fx) { + return bit(f) | bits(fx...); + } + + static IntType bits() { + return 0; + } + + }; + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/include/util/util_likely.h b/app/src/main/cpp/thirdparty/dxbc/include/util/util_likely.h new file mode 100644 index 000000000..84795ba7c --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/util/util_likely.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __GNUC__ +#define likely(x) __builtin_expect(bool(x),1) +#define unlikely(x) __builtin_expect(bool(x),0) +#define force_inline inline __attribute__((always_inline)) +#else +#define likely(x) (x) +#define unlikely(x) (x) +#define force_inline inline +#endif diff --git a/app/src/main/cpp/thirdparty/dxbc/include/util/util_math.h b/app/src/main/cpp/thirdparty/dxbc/include/util/util_math.h new file mode 100644 index 000000000..9276d9337 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/util/util_math.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +namespace dxvk { + + constexpr size_t CACHE_LINE_SIZE = 64; + constexpr double pi = 3.14159265359; + + template + constexpr T clamp(T n, T lo, T hi) { + if (n < lo) return lo; + if (n > hi) return hi; + return n; + } + + template + constexpr T align(T what, U to) { + return (what + to - 1) & ~(to - 1); + } + + template + constexpr T alignDown(T what, U to) { + return (what / to) * to; + } + + // Equivalent of std::clamp for use with floating point numbers + // Handles (-){INFINITY,NAN} cases. + // Will return min in cases of NAN, etc. + inline float fclamp(float value, float min, float max) { + return std::fmin( + std::fmax(value, min), max); + } + + template + inline T divCeil(T dividend, T divisor) { + return (dividend + divisor - 1) / divisor; + } + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/util/util_small_vector.h b/app/src/main/cpp/thirdparty/dxbc/include/util/util_small_vector.h new file mode 100644 index 000000000..861c9e0e0 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/util/util_small_vector.h @@ -0,0 +1,214 @@ +#pragma once + +#include +#include +#include +#include + +#include "util_bit.h" +#include "util_likely.h" + +namespace dxvk { + + template + class small_vector { + using storage = std::aligned_storage_t; + public: + + constexpr static size_t MinCapacity = N; + + small_vector() { } + + small_vector(size_t size) { + resize(size); + } + + small_vector(const small_vector& other) { + reserve(other.m_size); + + for (size_t i = 0; i < other.m_size; i++) + *ptr(i) = *other.ptr(i); + + m_size = other.m_size; + } + + small_vector& operator = (const small_vector& other) { + for (size_t i = 0; i < m_size; i++) + ptr(i)->~T(); + + reserve(other.m_size); + + for (size_t i = 0; i < other.m_size; i++) + *ptr(i) = *other.ptr(i); + + m_size = other.m_size; + return *this; + } + + small_vector(small_vector&& other) { + if (other.m_size <= N) { + for (size_t i = 0; i < other.m_size; i++) + new (&u.m_data[i]) T(std::move(*other.ptr(i))); + } else { + u.m_ptr = other.u.m_ptr; + m_capacity = other.m_capacity; + + other.u.m_ptr = nullptr; + other.m_capacity = N; + } + + m_size = other.m_size; + other.m_size = 0; + } + + small_vector& operator = (small_vector&& other) { + for (size_t i = 0; i < m_size; i++) + ptr(i)->~T(); + + if (m_capacity > N) + delete[] u.m_ptr; + + if (other.m_size <= N) { + m_capacity = N; + + for (size_t i = 0; i < other.m_size; i++) + new (&u.m_data[i]) T(std::move(*other.ptr(i))); + } else { + u.m_ptr = other.u.m_ptr; + m_capacity = other.m_capacity; + + other.u.m_ptr = nullptr; + other.m_capacity = N; + } + + m_size = other.m_size; + other.m_size = 0; + return *this; + } + + ~small_vector() { + for (size_t i = 0; i < m_size; i++) + ptr(i)->~T(); + + if (m_capacity > N) + delete[] u.m_ptr; + } + + size_t size() const { + return m_size; + } + + void reserve(size_t n) { + if (likely(n <= m_capacity)) + return; + + n = pick_capacity(n); + + storage* data = new storage[n]; + + for (size_t i = 0; i < m_size; i++) { + new (&data[i]) T(std::move(*ptr(i))); + ptr(i)->~T(); + } + + if (m_capacity > N) + delete[] u.m_ptr; + + m_capacity = n; + u.m_ptr = data; + } + + const T* data() const { return ptr(0); } + T* data() { return ptr(0); } + + void resize(size_t n) { + reserve(n); + + for (size_t i = n; i < m_size; i++) + ptr(i)->~T(); + + for (size_t i = m_size; i < n; i++) + new (ptr(i)) T(); + + m_size = n; + } + + void push_back(const T& object) { + reserve(m_size + 1); + new (ptr(m_size++)) T(object); + } + + void push_back(T&& object) { + reserve(m_size + 1); + new (ptr(m_size++)) T(std::move(object)); + } + + template + T& emplace_back(Args... args) { + reserve(m_size + 1); + return *(new (ptr(m_size++)) T(std::forward(args)...)); + } + + void erase(size_t idx) { + ptr(idx)->~T(); + + for (size_t i = idx; i < m_size - 1; i++) { + new (ptr(i)) T(std::move(*ptr(i + 1))); + ptr(i + 1)->~T(); + } + } + + void pop_back() { + ptr(--m_size)->~T(); + } + + void clear() { + for (size_t i = 0; i < m_size; i++) + ptr(i)->~T(); + + m_size = 0; + } + + bool empty() const { + return m_size == 0; + } + + T& operator [] (size_t idx) { return *ptr(idx); } + const T& operator [] (size_t idx) const { return *ptr(idx); } + + T& front() { return *ptr(0); } + const T& front() const { return *ptr(0); } + + T& back() { return *ptr(m_size - 1); } + const T& back() const { return *ptr(m_size - 1); } + + private: + + size_t m_capacity = N; + size_t m_size = 0; + + union { + storage* m_ptr; + storage m_data[N]; + } u; + + size_t pick_capacity(size_t n) { + // Pick next largest power of two for the new capacity + return size_t(1u) << ((sizeof(n) * 8u) - bit::lzcnt(n - 1)); + } + + T* ptr(size_t idx) { + return m_capacity == N + ? reinterpret_cast(&u.m_data[idx]) + : reinterpret_cast(&u.m_ptr[idx]); + } + + const T* ptr(size_t idx) const { + return m_capacity == N + ? reinterpret_cast(&u.m_data[idx]) + : reinterpret_cast(&u.m_ptr[idx]); + } + + }; + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/include/util/util_string.h b/app/src/main/cpp/thirdparty/dxbc/include/util/util_string.h new file mode 100644 index 000000000..5fe00fbf5 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/include/util/util_string.h @@ -0,0 +1,240 @@ +#pragma once + +#include +#include +#include +#include + +#include "util_bit.h" +#include "util_likely.h" + +namespace dxvk::str { + + template struct UnicodeChar { }; + template<> struct UnicodeChar<1> { using type = uint8_t; }; + template<> struct UnicodeChar<2> { using type = uint16_t; }; + template<> struct UnicodeChar<4> { using type = uint32_t; }; + + template + using UnicodeCharType = typename UnicodeChar::type; + + const uint8_t* decodeTypedChar( + const uint8_t* begin, + const uint8_t* end, + uint32_t& ch); + + const uint16_t* decodeTypedChar( + const uint16_t* begin, + const uint16_t* end, + uint32_t& ch); + + const uint32_t* decodeTypedChar( + const uint32_t* begin, + const uint32_t* end, + uint32_t& ch); + + size_t encodeTypedChar( + uint8_t* begin, + uint8_t* end, + uint32_t ch); + + size_t encodeTypedChar( + uint16_t* begin, + uint16_t* end, + uint32_t ch); + + size_t encodeTypedChar( + uint32_t* begin, + uint32_t* end, + uint32_t ch); + + /** + * \brief Decodes a single character + * + * Note that \c begin and \c end must not be equal. + * \param [in] begin Pointer to current position within the input string + * \param [in] end Pointer to the end of the input string + * \param [out] ch Pointer to the decoded character code + * \returns Pointer to next character in the input string + */ + template + const T* decodeChar( + const T* begin, + const T* end, + uint32_t& ch) { + using CharType = UnicodeCharType; + + const CharType* result = decodeTypedChar( + reinterpret_cast(begin), + reinterpret_cast(end), + ch); + + return reinterpret_cast(result); + } + + /** + * \brief Encodes a character + * + * Note that \c begin and \c end may be both be \c nullptr or equal, in + * which case only the length of the encoded character will be returned. + * \param [in] begin Pointer to current position within the output string + * \param [in] end Pointer to the end of the output string + * \param [in] ch Character to encode + * \returns If begin is \c nullptr , the number of units required to encode + * the character. Otherwise, the number of units written to the output. + * This may return \c 0 for characters that cannot be written or encoded. + */ + template + size_t encodeChar( + T* begin, + T* end, + uint32_t ch) { + using CharType = UnicodeCharType; + + return encodeTypedChar( + reinterpret_cast(begin), + reinterpret_cast(end), + ch); + } + + /** + * \brief Computes length of a null-terminated string + * + * \param [in] begin Start of input string + * \returns Number of characters in input string, + * excluding the terminating null character + */ + template + size_t length(const S* string) { + size_t result = 0; + + while (string[result]) + result += 1; + + return result; + } + + /** + * \brief Converts string from one encoding to another + * + * The output string arguments may be \c nullptr. In that case, the + * total length of the transcoded string will be returned, in units + * of the output character type. The output string will only be + * null-terminated if the input string is also null-terminated. + * \tparam D Output character type + * \tparam S Input character type + * \param [in] dstBegin Start of output string + * \param [in] dstLength Length of output string + * \param [in] srcBegin Start of input string + * \param [in] srcLength Length of input string + * \returns If \c dstBegin is \c nullptr , the total number of output + * characters required to store the output string. Otherwise, the + * total number of characters written to the output string. + */ + template + size_t transcodeString( + D* dstBegin, + size_t dstLength, + const S* srcBegin, + size_t srcLength) { + size_t totalLength = 0; + + auto dstEnd = dstBegin + dstLength; + auto srcEnd = srcBegin + srcLength; + + while (srcBegin < srcEnd) { + uint32_t ch; + + srcBegin = decodeChar(srcBegin, srcEnd, ch); + + if (dstBegin) + totalLength += encodeChar(dstBegin + totalLength, dstEnd, ch); + else + totalLength += encodeChar(nullptr, nullptr, ch); + + if (!ch) + break; + } + + return totalLength; + } + + /** + * \brief Creates string object from wide char array + * + * \param [in] ws Null-terminated wide string + * \returns Regular string object + */ + std::string fromws(const wchar_t* ws); + + /** + * \brief Creates wide string object from char array + * + * \param [in] mbs Null-terminated string + * \returns Wide string object + */ + std::wstring tows(const char* mbs); + +#ifdef _WIN32 + using path_string = std::wstring; + inline path_string topath(const char* mbs) { return tows(mbs); } +#else + using path_string = std::string; + inline path_string topath(const char* mbs) { return std::string(mbs); } +#endif + + inline void format1(std::stringstream&) { } + + template + void format1(std::stringstream& str, const wchar_t *arg, const Tx&... args) { + str << fromws(arg); + format1(str, args...); + } + + template + void format1(std::stringstream& str, const T& arg, const Tx&... args) { + str << arg; + format1(str, args...); + } + + template + std::string format(const Args&... args) { + std::stringstream stream; + format1(stream, args...); + return stream.str(); + } + + inline void strlcpy(char* dst, const char* src, size_t count) { + if (count > 0) { + std::strncpy(dst, src, count - 1); + dst[count - 1] = '\0'; + } + } + + /** + * \brief Split string at one or more delimiters characters + * + * \param [in] string String to split + * \param [in] delims Delimiter characters + * \returns Vector of substring views + */ + inline std::vector split(std::string_view string, std::string_view delims = " ") { + std::vector tokens; + + for (size_t start = 0; start < string.size(); ) { + // Find first delimiter + const auto end = string.find_first_of(delims, start); + + // Add non-empty tokens + if (start != end) + tokens.emplace_back(string.substr(start, end-start)); + + // Break at the end of string + if (end == std::string_view::npos) + break; + + start = end + 1; + } + return tokens; + } +} diff --git a/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_analysis.cpp b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_analysis.cpp new file mode 100644 index 000000000..6064e9ee5 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_analysis.cpp @@ -0,0 +1,254 @@ +#include "dxbc_analysis.h" + +namespace dxvk { + + DxbcAnalyzer::DxbcAnalyzer( + const DxbcModuleInfo& moduleInfo, + const DxbcProgramInfo& programInfo, + const Rc& isgn, + const Rc& osgn, + const Rc& psgn, + DxbcAnalysisInfo& analysis) + : m_isgn (isgn), + m_osgn (osgn), + m_psgn (psgn), + m_analysis(&analysis) { + // Get number of clipping and culling planes from the + // input and output signatures. We will need this to + // declare the shader input and output interfaces. + m_analysis->clipCullIn = getClipCullInfo(m_isgn); + m_analysis->clipCullOut = getClipCullInfo(m_osgn); + } + + + DxbcAnalyzer::~DxbcAnalyzer() { + + } + + + void DxbcAnalyzer::processInstruction(const DxbcShaderInstruction& ins) { + switch (ins.opClass) { + case DxbcInstClass::Atomic: { + const uint32_t operandId = ins.dstCount - 1; + + if (ins.dst[operandId].type == DxbcOperandType::UnorderedAccessView) { + const uint32_t registerId = ins.dst[operandId].idx[0].offset; + m_analysis->uavInfos[registerId].accessAtomicOp = true; + m_analysis->uavInfos[registerId].accessFlags |= VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + + // Check whether the atomic operation is order-invariant + DxvkAccessOp op = DxvkAccessOp::None; + + switch (ins.op) { + case DxbcOpcode::AtomicAnd: op = DxvkAccessOp::And; break; + case DxbcOpcode::AtomicOr: op = DxvkAccessOp::Or; break; + case DxbcOpcode::AtomicXor: op = DxvkAccessOp::Xor; break; + case DxbcOpcode::AtomicIAdd: op = DxvkAccessOp::Add; break; + case DxbcOpcode::AtomicIMax: op = DxvkAccessOp::IMax; break; + case DxbcOpcode::AtomicIMin: op = DxvkAccessOp::IMin; break; + case DxbcOpcode::AtomicUMax: op = DxvkAccessOp::UMax; break; + case DxbcOpcode::AtomicUMin: op = DxvkAccessOp::UMin; break; + default: break; + } + + setUavAccessOp(registerId, op); + } + } break; + + case DxbcInstClass::TextureSample: + case DxbcInstClass::TextureGather: + case DxbcInstClass::TextureQueryLod: + case DxbcInstClass::VectorDeriv: { + m_analysis->usesDerivatives = true; + } break; + + case DxbcInstClass::ControlFlow: { + if (ins.op == DxbcOpcode::Discard) + m_analysis->usesKill = true; + } break; + + case DxbcInstClass::BufferLoad: { + uint32_t operandId = ins.op == DxbcOpcode::LdStructured ? 2 : 1; + bool sparseFeedback = ins.dstCount == 2; + + if (ins.src[operandId].type == DxbcOperandType::UnorderedAccessView) { + const uint32_t registerId = ins.src[operandId].idx[0].offset; + m_analysis->uavInfos[registerId].accessFlags |= VK_ACCESS_SHADER_READ_BIT; + m_analysis->uavInfos[registerId].sparseFeedback |= sparseFeedback; + + setUavAccessOp(registerId, DxvkAccessOp::None); + } else if (ins.src[operandId].type == DxbcOperandType::Resource) { + const uint32_t registerId = ins.src[operandId].idx[0].offset; + m_analysis->srvInfos[registerId].sparseFeedback |= sparseFeedback; + } + } break; + + case DxbcInstClass::BufferStore: { + if (ins.dst[0].type == DxbcOperandType::UnorderedAccessView) { + const uint32_t registerId = ins.dst[0].idx[0].offset; + m_analysis->uavInfos[registerId].accessFlags |= VK_ACCESS_SHADER_WRITE_BIT; + + setUavAccessOp(registerId, getStoreAccessOp(ins.dst[0].mask, ins.src[ins.srcCount - 1u])); + } + } break; + + case DxbcInstClass::TypedUavLoad: { + const uint32_t registerId = ins.src[1].idx[0].offset; + m_analysis->uavInfos[registerId].accessTypedLoad = true; + m_analysis->uavInfos[registerId].accessFlags |= VK_ACCESS_SHADER_READ_BIT; + + setUavAccessOp(registerId, DxvkAccessOp::None); + } break; + + case DxbcInstClass::TypedUavStore: { + const uint32_t registerId = ins.dst[0].idx[0].offset; + m_analysis->uavInfos[registerId].accessFlags |= VK_ACCESS_SHADER_WRITE_BIT; + + // The UAV format may change between dispatches, so be conservative here + // and only allow this optimization when the app is writing zeroes. + DxvkAccessOp storeOp = getStoreAccessOp(DxbcRegMask(0xf), ins.src[1u]); + + if (storeOp != DxvkAccessOp(DxvkAccessOp::OpType::StoreUi, 0u)) + storeOp = DxvkAccessOp::None; + + setUavAccessOp(registerId, storeOp); + } break; + + case DxbcInstClass::Declaration: { + switch (ins.op) { + case DxbcOpcode::DclConstantBuffer: { + uint32_t registerId = ins.dst[0].idx[0].offset; + + if (registerId < DxbcConstBufBindingCount) + m_analysis->bindings.cbvMask |= 1u << registerId; + } break; + + case DxbcOpcode::DclSampler: { + uint32_t registerId = ins.dst[0].idx[0].offset; + + if (registerId < DxbcSamplerBindingCount) + m_analysis->bindings.samplerMask |= 1u << registerId; + } break; + + case DxbcOpcode::DclResource: + case DxbcOpcode::DclResourceRaw: + case DxbcOpcode::DclResourceStructured: { + uint32_t registerId = ins.dst[0].idx[0].offset; + + uint32_t idx = registerId / 64u; + uint32_t bit = registerId % 64u; + + if (registerId < DxbcResourceBindingCount) + m_analysis->bindings.srvMask[idx] |= uint64_t(1u) << bit; + } break; + + case DxbcOpcode::DclUavTyped: + case DxbcOpcode::DclUavRaw: + case DxbcOpcode::DclUavStructured: { + uint32_t registerId = ins.dst[0].idx[0].offset; + + if (registerId < DxbcUavBindingCount) + m_analysis->bindings.uavMask |= uint64_t(1u) << registerId; + } break; + + default: ; + } + } break; + + default: + break; + } + + for (uint32_t i = 0; i < ins.dstCount; i++) { + if (ins.dst[i].type == DxbcOperandType::IndexableTemp) { + uint32_t index = ins.dst[i].idx[0].offset; + m_analysis->xRegMasks[index] |= ins.dst[i].mask; + } + } + } + + + DxbcClipCullInfo DxbcAnalyzer::getClipCullInfo(const Rc& sgn) const { + DxbcClipCullInfo result; + + if (sgn != nullptr) { + for (auto e = sgn->begin(); e != sgn->end(); e++) { + const uint32_t componentCount = e->componentMask.popCount(); + + if (e->systemValue == DxbcSystemValue::ClipDistance) + result.numClipPlanes += componentCount; + if (e->systemValue == DxbcSystemValue::CullDistance) + result.numCullPlanes += componentCount; + } + } + + return result; + } + + + void DxbcAnalyzer::setUavAccessOp(uint32_t uav, DxvkAccessOp op) { + if (m_analysis->uavInfos[uav].accessOp == DxvkAccessOp::None) + m_analysis->uavInfos[uav].accessOp = op; + + // Maintain ordering if the UAV is accessed via other operations as well + if (op == DxvkAccessOp::None || m_analysis->uavInfos[uav].accessOp != op) + m_analysis->uavInfos[uav].nonInvariantAccess = true; + } + + + DxvkAccessOp DxbcAnalyzer::getStoreAccessOp(DxbcRegMask writeMask, const DxbcRegister& src) { + if (src.type != DxbcOperandType::Imm32) + return DxvkAccessOp::None; + + // Trivial case, same value is written to all components + if (src.componentCount == DxbcComponentCount::Component1) + return getConstantStoreOp(src.imm.u32_1); + + if (src.componentCount != DxbcComponentCount::Component4) + return DxvkAccessOp::None; + + // Otherwise, make sure that all written components are equal + DxvkAccessOp op = DxvkAccessOp::None; + + for (uint32_t i = 0u; i < 4u; i++) { + if (!writeMask[i]) + continue; + + // If the written value can't be represented, skip + DxvkAccessOp scalarOp = getConstantStoreOp(src.imm.u32_4[i]); + + if (scalarOp == DxvkAccessOp::None) + return DxvkAccessOp::None; + + // First component written + if (op == DxvkAccessOp::None) + op = scalarOp; + + // Conflicting store ops + if (op != scalarOp) + return DxvkAccessOp::None; + } + + return op; + } + + + DxvkAccessOp DxbcAnalyzer::getConstantStoreOp(uint32_t value) { + constexpr uint32_t mask = 0xfffu; + + uint32_t ubits = value & mask; + uint32_t fbits = (value >> 20u); + + if (value == ubits) + return DxvkAccessOp(DxvkAccessOp::OpType::StoreUi, ubits); + + if (value == (ubits | ~mask)) + return DxvkAccessOp(DxvkAccessOp::OpType::StoreSi, ubits); + + if (value == (fbits << 20u)) + return DxvkAccessOp(DxvkAccessOp::OpType::StoreF, fbits); + + return DxvkAccessOp::None; + } + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_chunk_isgn.cpp b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_chunk_isgn.cpp new file mode 100644 index 000000000..80e540f23 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_chunk_isgn.cpp @@ -0,0 +1,112 @@ +#include "dxbc_chunk_isgn.h" + +namespace dxvk { + + DxbcIsgn::DxbcIsgn(DxbcReader reader, DxbcTag tag) { + uint32_t elementCount = reader.readu32(); + reader.skip(sizeof(uint32_t)); + + std::array componentTypes = { + DxbcScalarType::Uint32, DxbcScalarType::Uint32, + DxbcScalarType::Sint32, DxbcScalarType::Float32, + }; + + // https://github.com/DarkStarSword/3d-fixes/blob/master/dx11shaderanalyse.py#L101 + bool hasStream = (tag == "ISG1") || (tag == "OSG1") || (tag == "PSG1") || (tag == "OSG5"); + bool hasPrecision = (tag == "ISG1") || (tag == "OSG1") || (tag == "PSG1"); + + for (uint32_t i = 0; i < elementCount; i++) { + DxbcSgnEntry entry; + entry.streamId = hasStream ? reader.readu32() : 0; + entry.semanticName = reader.clone(reader.readu32()).readString(); + entry.semanticIndex = reader.readu32(); + entry.systemValue = static_cast(reader.readu32()); + entry.componentType = componentTypes.at(reader.readu32()); + entry.registerId = reader.readu32(); + + uint32_t mask = reader.readu32(); + + entry.componentMask = bit::extract(mask, 0, 3); + entry.componentUsed = bit::extract(mask, 8, 11); + + if (hasPrecision) + reader.readu32(); + + m_entries.push_back(entry); + } + } + + + DxbcIsgn::~DxbcIsgn() { + + } + + + const DxbcSgnEntry* DxbcIsgn::findByRegister(uint32_t registerId) const { + for (auto e = this->begin(); e != this->end(); e++) { + if (e->registerId == registerId) + return &(*e); + } + + return nullptr; + } + + + const DxbcSgnEntry* DxbcIsgn::find( + const std::string& semanticName, + uint32_t semanticIndex, + uint32_t streamId) const { + for (auto e = this->begin(); e != this->end(); e++) { + if (e->semanticIndex == semanticIndex + && e->streamId == streamId + && compareSemanticNames(semanticName, e->semanticName)) + return &(*e); + } + + return nullptr; + } + + + DxbcRegMask DxbcIsgn::regMask( + uint32_t registerId) const { + DxbcRegMask mask; + + for (auto e = this->begin(); e != this->end(); e++) { + if (e->registerId == registerId) + mask |= e->componentMask; + } + + return mask; + } + + + uint32_t DxbcIsgn::maxRegisterCount() const { + uint32_t result = 0; + for (auto e = this->begin(); e != this->end(); e++) + result = std::max(result, e->registerId + 1); + return result; + } + + + bool DxbcIsgn::compareSemanticNames( + const std::string& a, const std::string& b) { + if (a.size() != b.size()) + return false; + + for (size_t i = 0; i < a.size(); i++) { + char ac = a[i]; + char bc = b[i]; + + if (ac != bc) { + if (ac >= 'A' && ac <= 'Z') ac += 'a' - 'A'; + if (bc >= 'A' && bc <= 'Z') bc += 'a' - 'A'; + + if (ac != bc) + return false; + } + } + + return true; + } + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_chunk_shex.cpp b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_chunk_shex.cpp new file mode 100644 index 000000000..552329b88 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_chunk_shex.cpp @@ -0,0 +1,24 @@ +#include "dxbc_chunk_shex.h" + +namespace dxvk { + + DxbcShex::DxbcShex(DxbcReader reader) { + // The shader version and type are stored in a 32-bit unit, + // where the first byte contains the major and minor version + // numbers, and the high word contains the program type. + reader.skip(2); + auto pType = reader.readEnum(); + m_programInfo = DxbcProgramInfo(pType); + + // Read the actual shader code as an array of DWORDs. + auto codeLength = reader.readu32() - 2; + m_code.resize(codeLength); + reader.read(m_code.data(), codeLength * sizeof(uint32_t)); + } + + + DxbcShex::~DxbcShex() { + + } + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_common.cpp b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_common.cpp new file mode 100644 index 000000000..db3d71529 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_common.cpp @@ -0,0 +1,30 @@ +#include "dxbc_common.h" + +namespace dxvk { + + VkShaderStageFlagBits DxbcProgramInfo::shaderStage() const { + switch (m_type) { + case DxbcProgramType::PixelShader : return VK_SHADER_STAGE_FRAGMENT_BIT; + case DxbcProgramType::VertexShader : return VK_SHADER_STAGE_VERTEX_BIT; + case DxbcProgramType::GeometryShader : return VK_SHADER_STAGE_GEOMETRY_BIT; + case DxbcProgramType::HullShader : return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; + case DxbcProgramType::DomainShader : return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; + case DxbcProgramType::ComputeShader : return VK_SHADER_STAGE_COMPUTE_BIT; + default: throw DxvkError("DxbcProgramInfo::shaderStage: Unsupported program type"); + } + } + + + spv::ExecutionModel DxbcProgramInfo::executionModel() const { + switch (m_type) { + case DxbcProgramType::PixelShader : return spv::ExecutionModelFragment; + case DxbcProgramType::VertexShader : return spv::ExecutionModelVertex; + case DxbcProgramType::GeometryShader : return spv::ExecutionModelGeometry; + case DxbcProgramType::HullShader : return spv::ExecutionModelTessellationControl; + case DxbcProgramType::DomainShader : return spv::ExecutionModelTessellationEvaluation; + case DxbcProgramType::ComputeShader : return spv::ExecutionModelGLCompute; + default: throw DxvkError("DxbcProgramInfo::executionModel: Unsupported program type"); + } + } + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_compiler.cpp b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_compiler.cpp new file mode 100644 index 000000000..0b6286fe5 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_compiler.cpp @@ -0,0 +1,8242 @@ +#include "dxbc_compiler.h" + +namespace dxvk { + + constexpr uint32_t Icb_BindingSlotId = 14; + constexpr uint32_t Icb_MaxBakedDwords = 64; + + DxbcCompiler::DxbcCompiler( + const std::string& fileName, + const DxbcModuleInfo& moduleInfo, + const DxbcProgramInfo& programInfo, + const Rc& isgn, + const Rc& osgn, + const Rc& psgn, + const DxbcAnalysisInfo& analysis) + : m_moduleInfo (moduleInfo), + m_programInfo(programInfo), + m_module (spvVersion(1, 6)), + m_isgn (isgn), + m_osgn (osgn), + m_psgn (psgn), + m_analysis (&analysis) { + // Declare an entry point ID. We'll need it during the + // initialization phase where the execution mode is set. + m_entryPointId = m_module.allocateId(); + + // Set the shader name so that we recognize it in renderdoc + m_module.setDebugSource( + spv::SourceLanguageUnknown, 0, + m_module.addDebugString(fileName.c_str()), + nullptr); + + // Set the memory model. This is the same for all shaders. + m_module.enableCapability( + spv::CapabilityVulkanMemoryModel); + + m_module.setMemoryModel( + spv::AddressingModelLogical, + spv::MemoryModelVulkan); + + // Make sure our interface registers are clear + for (uint32_t i = 0; i < DxbcMaxInterfaceRegs; i++) { + m_vRegs.at(i) = DxbcRegisterPointer { }; + m_oRegs.at(i) = DxbcRegisterPointer { }; + } + + this->emitInit(); + } + + + DxbcCompiler::~DxbcCompiler() { + + } + + + void DxbcCompiler::processInstruction(const DxbcShaderInstruction& ins) { + m_lastOp = m_currOp; + m_currOp = ins.op; + + switch (ins.opClass) { + case DxbcInstClass::Declaration: + return this->emitDcl(ins); + + case DxbcInstClass::CustomData: + return this->emitCustomData(ins); + + case DxbcInstClass::Atomic: + return this->emitAtomic(ins); + + case DxbcInstClass::AtomicCounter: + return this->emitAtomicCounter(ins); + + case DxbcInstClass::Barrier: + return this->emitBarrier(ins); + + case DxbcInstClass::BitExtract: + return this->emitBitExtract(ins); + + case DxbcInstClass::BitInsert: + return this->emitBitInsert(ins); + + case DxbcInstClass::BitScan: + return this->emitBitScan(ins); + + case DxbcInstClass::BufferQuery: + return this->emitBufferQuery(ins); + + case DxbcInstClass::BufferLoad: + return this->emitBufferLoad(ins); + + case DxbcInstClass::BufferStore: + return this->emitBufferStore(ins); + + case DxbcInstClass::ConvertFloat16: + return this->emitConvertFloat16(ins); + + case DxbcInstClass::ConvertFloat64: + return this->emitConvertFloat64(ins); + + case DxbcInstClass::ControlFlow: + return this->emitControlFlow(ins); + + case DxbcInstClass::GeometryEmit: + return this->emitGeometryEmit(ins); + + case DxbcInstClass::HullShaderPhase: + return this->emitHullShaderPhase(ins); + + case DxbcInstClass::HullShaderInstCnt: + return this->emitHullShaderInstCnt(ins); + + case DxbcInstClass::Interpolate: + return this->emitInterpolate(ins); + + case DxbcInstClass::NoOperation: + return; + + case DxbcInstClass::SparseCheckAccess: + return this->emitSparseCheckAccess(ins); + + case DxbcInstClass::TextureQuery: + return this->emitTextureQuery(ins); + + case DxbcInstClass::TextureQueryLod: + return this->emitTextureQueryLod(ins); + + case DxbcInstClass::TextureQueryMs: + return this->emitTextureQueryMs(ins); + + case DxbcInstClass::TextureQueryMsPos: + return this->emitTextureQueryMsPos(ins); + + case DxbcInstClass::TextureFetch: + return this->emitTextureFetch(ins); + + case DxbcInstClass::TextureGather: + return this->emitTextureGather(ins); + + case DxbcInstClass::TextureSample: + return this->emitTextureSample(ins); + + case DxbcInstClass::TypedUavLoad: + return this->emitTypedUavLoad(ins); + + case DxbcInstClass::TypedUavStore: + return this->emitTypedUavStore(ins); + + case DxbcInstClass::VectorAlu: + return this->emitVectorAlu(ins); + + case DxbcInstClass::VectorCmov: + return this->emitVectorCmov(ins); + + case DxbcInstClass::VectorCmp: + return this->emitVectorCmp(ins); + + case DxbcInstClass::VectorDeriv: + return this->emitVectorDeriv(ins); + + case DxbcInstClass::VectorDot: + return this->emitVectorDot(ins); + + case DxbcInstClass::VectorIdiv: + return this->emitVectorIdiv(ins); + + case DxbcInstClass::VectorImul: + return this->emitVectorImul(ins); + + case DxbcInstClass::VectorMsad: + return this->emitVectorMsad(ins); + + case DxbcInstClass::VectorShift: + return this->emitVectorShift(ins); + + case DxbcInstClass::VectorSinCos: + return this->emitVectorSinCos(ins); + + default: + Logger::warn( + str::format("DxbcCompiler: Unhandled opcode class: ", + ins.op)); + } + } + + + void DxbcCompiler::processXfbPassthrough() { + m_module.setExecutionMode (m_entryPointId, spv::ExecutionModeInputPoints); + m_module.setExecutionMode (m_entryPointId, spv::ExecutionModeOutputPoints); + m_module.setOutputVertices(m_entryPointId, 1); + + for (auto e = m_isgn->begin(); e != m_isgn->end(); e++) { + emitDclInput(e->registerId, 1, + e->componentMask, DxbcSystemValue::None, + DxbcInterpolationMode::Undefined); + } + + // Figure out which streams to enable + uint32_t streamMask = 0; + + for (size_t i = 0; i < m_xfbVars.size(); i++) + streamMask |= 1u << m_xfbVars[i].streamId; + + for (uint32_t streamId : bit::BitMask(streamMask)) { + emitXfbOutputSetup(streamId, true); + m_module.opEmitVertex(m_module.constu32(streamId)); + } + + // End the main function + emitFunctionEnd(); + + // For pass-through we always assume points + m_inputTopology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + } + + + SpirvCodeBuffer DxbcCompiler::finalize() { + // Depending on the shader type, this will prepare + // input registers, call various shader functions + // and write back the output registers. + switch (m_programInfo.type()) { + case DxbcProgramType::VertexShader: this->emitVsFinalize(); break; + case DxbcProgramType::HullShader: this->emitHsFinalize(); break; + case DxbcProgramType::DomainShader: this->emitDsFinalize(); break; + case DxbcProgramType::GeometryShader: this->emitGsFinalize(); break; + case DxbcProgramType::PixelShader: this->emitPsFinalize(); break; + case DxbcProgramType::ComputeShader: this->emitCsFinalize(); break; + default: throw DxvkError("Invalid shader stage"); + } + + // Emit float control mode if the extension is supported + this->emitFloatControl(); + + // Declare the entry point, we now have all the + // information we need, including the interfaces + m_module.addEntryPoint(m_entryPointId, + m_programInfo.executionModel(), "main"); + m_module.setDebugName(m_entryPointId, "main"); + + return m_module.compile(); + } + + + void DxbcCompiler::emitDcl(const DxbcShaderInstruction& ins) { + switch (ins.op) { + case DxbcOpcode::DclGlobalFlags: + return this->emitDclGlobalFlags(ins); + + case DxbcOpcode::DclIndexRange: + return this->emitDclIndexRange(ins); + + case DxbcOpcode::DclTemps: + return this->emitDclTemps(ins); + + case DxbcOpcode::DclIndexableTemp: + return this->emitDclIndexableTemp(ins); + + case DxbcOpcode::DclInput: + case DxbcOpcode::DclInputSgv: + case DxbcOpcode::DclInputSiv: + case DxbcOpcode::DclInputPs: + case DxbcOpcode::DclInputPsSgv: + case DxbcOpcode::DclInputPsSiv: + case DxbcOpcode::DclOutput: + case DxbcOpcode::DclOutputSgv: + case DxbcOpcode::DclOutputSiv: + return this->emitDclInterfaceReg(ins); + + case DxbcOpcode::DclConstantBuffer: + return this->emitDclConstantBuffer(ins); + + case DxbcOpcode::DclSampler: + return this->emitDclSampler(ins); + + case DxbcOpcode::DclStream: + return this->emitDclStream(ins); + + case DxbcOpcode::DclUavTyped: + case DxbcOpcode::DclResource: + return this->emitDclResourceTyped(ins); + + case DxbcOpcode::DclUavRaw: + case DxbcOpcode::DclResourceRaw: + case DxbcOpcode::DclUavStructured: + case DxbcOpcode::DclResourceStructured: + return this->emitDclResourceRawStructured(ins); + + case DxbcOpcode::DclThreadGroupSharedMemoryRaw: + case DxbcOpcode::DclThreadGroupSharedMemoryStructured: + return this->emitDclThreadGroupSharedMemory(ins); + + case DxbcOpcode::DclGsInputPrimitive: + return this->emitDclGsInputPrimitive(ins); + + case DxbcOpcode::DclGsOutputPrimitiveTopology: + return this->emitDclGsOutputTopology(ins); + + case DxbcOpcode::DclMaxOutputVertexCount: + return this->emitDclMaxOutputVertexCount(ins); + + case DxbcOpcode::DclInputControlPointCount: + return this->emitDclInputControlPointCount(ins); + + case DxbcOpcode::DclOutputControlPointCount: + return this->emitDclOutputControlPointCount(ins); + + case DxbcOpcode::DclHsMaxTessFactor: + return this->emitDclHsMaxTessFactor(ins); + + case DxbcOpcode::DclTessDomain: + return this->emitDclTessDomain(ins); + + case DxbcOpcode::DclTessPartitioning: + return this->emitDclTessPartitioning(ins); + + case DxbcOpcode::DclTessOutputPrimitive: + return this->emitDclTessOutputPrimitive(ins); + + case DxbcOpcode::DclThreadGroup: + return this->emitDclThreadGroup(ins); + + case DxbcOpcode::DclGsInstanceCount: + return this->emitDclGsInstanceCount(ins); + + default: + Logger::warn( + str::format("DxbcCompiler: Unhandled opcode: ", + ins.op)); + } + } + + + void DxbcCompiler::emitDclGlobalFlags(const DxbcShaderInstruction& ins) { + const DxbcGlobalFlags flags = ins.controls.globalFlags(); + + if (flags.test(DxbcGlobalFlag::RefactoringAllowed)) + m_precise = false; + + if (flags.test(DxbcGlobalFlag::EarlyFragmentTests)) + m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeEarlyFragmentTests); + } + + + void DxbcCompiler::emitDclIndexRange(const DxbcShaderInstruction& ins) { + // dcl_index_range has one operand: + // (0) Range start, either an input or output register + // (1) Range end + uint32_t index = ins.dst[0].idxDim - 1u; + + DxbcIndexRange range = { }; + range.type = ins.dst[0].type; + range.start = ins.dst[0].idx[index].offset; + range.length = ins.imm[0].u32; + + m_indexRanges.push_back(range); + } + + + void DxbcCompiler::emitDclTemps(const DxbcShaderInstruction& ins) { + // dcl_temps has one operand: + // (imm0) Number of temp registers + + // Ignore this and declare temps on demand. + } + + + void DxbcCompiler::emitDclIndexableTemp(const DxbcShaderInstruction& ins) { + // dcl_indexable_temps has three operands: + // (imm0) Array register index (x#) + // (imm1) Number of vectors stored in the array + // (imm2) Component count of each individual vector. This is + // always 4 in fxc-generated binaries and therefore useless. + const uint32_t regId = ins.imm[0].u32; + + DxbcRegisterInfo info; + info.type.ctype = DxbcScalarType::Float32; + info.type.ccount = m_analysis->xRegMasks.at(regId).minComponents(); + info.type.alength = ins.imm[1].u32; + info.sclass = spv::StorageClassPrivate; + + if (regId >= m_xRegs.size()) + m_xRegs.resize(regId + 1); + + m_xRegs.at(regId).ccount = info.type.ccount; + m_xRegs.at(regId).alength = info.type.alength; + m_xRegs.at(regId).varId = emitNewVariable(info); + + m_module.setDebugName(m_xRegs.at(regId).varId, + str::format("x", regId).c_str()); + } + + + void DxbcCompiler::emitDclInterfaceReg(const DxbcShaderInstruction& ins) { + switch (ins.dst[0].type) { + case DxbcOperandType::InputControlPoint: + if (m_programInfo.type() != DxbcProgramType::HullShader) + break; + [[fallthrough]]; + + case DxbcOperandType::Input: + case DxbcOperandType::Output: { + // dcl_input and dcl_output instructions + // have the following operands: + // (dst0) The register to declare + // (imm0) The system value (optional) + uint32_t regDim = 0; + uint32_t regIdx = 0; + + // In the vertex and fragment shader stage, the + // operand indices will have the following format: + // (0) Register index + // + // In other stages, the input and output registers + // may be declared as arrays of a fixed size: + // (0) Array length + // (1) Register index + if (ins.dst[0].idxDim == 2) { + regDim = ins.dst[0].idx[0].offset; + regIdx = ins.dst[0].idx[1].offset; + } else if (ins.dst[0].idxDim == 1) { + regIdx = ins.dst[0].idx[0].offset; + } else { + Logger::err(str::format( + "DxbcCompiler: ", ins.op, + ": Invalid index dimension")); + return; + } + + // This declaration may map an output register to a system + // value. If that is the case, the system value type will + // be stored in the second operand. + const bool hasSv = + ins.op == DxbcOpcode::DclInputSgv + || ins.op == DxbcOpcode::DclInputSiv + || ins.op == DxbcOpcode::DclInputPsSgv + || ins.op == DxbcOpcode::DclInputPsSiv + || ins.op == DxbcOpcode::DclOutputSgv + || ins.op == DxbcOpcode::DclOutputSiv; + + DxbcSystemValue sv = DxbcSystemValue::None; + + if (hasSv) + sv = static_cast(ins.imm[0].u32); + + // In the pixel shader, inputs are declared with an + // interpolation mode that is part of the op token. + const bool hasInterpolationMode = + ins.op == DxbcOpcode::DclInputPs + || ins.op == DxbcOpcode::DclInputPsSiv; + + DxbcInterpolationMode im = DxbcInterpolationMode::Undefined; + + if (hasInterpolationMode) + im = ins.controls.interpolation(); + + // Declare the actual input/output variable + switch (ins.op) { + case DxbcOpcode::DclInput: + case DxbcOpcode::DclInputSgv: + case DxbcOpcode::DclInputSiv: + case DxbcOpcode::DclInputPs: + case DxbcOpcode::DclInputPsSgv: + case DxbcOpcode::DclInputPsSiv: + this->emitDclInput(regIdx, regDim, ins.dst[0].mask, sv, im); + break; + + case DxbcOpcode::DclOutput: + case DxbcOpcode::DclOutputSgv: + case DxbcOpcode::DclOutputSiv: + this->emitDclOutput(regIdx, regDim, ins.dst[0].mask, sv, im); + break; + + default: + Logger::err(str::format( + "DxbcCompiler: Unexpected opcode: ", + ins.op)); + } + } break; + + case DxbcOperandType::InputThreadId: { + m_cs.builtinGlobalInvocationId = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 3, 0 }, + spv::StorageClassInput }, + spv::BuiltInGlobalInvocationId, + "vThreadId"); + } break; + + case DxbcOperandType::InputThreadGroupId: { + m_cs.builtinWorkgroupId = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 3, 0 }, + spv::StorageClassInput }, + spv::BuiltInWorkgroupId, + "vThreadGroupId"); + } break; + + case DxbcOperandType::InputThreadIdInGroup: { + m_cs.builtinLocalInvocationId = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 3, 0 }, + spv::StorageClassInput }, + spv::BuiltInLocalInvocationId, + "vThreadIdInGroup"); + } break; + + case DxbcOperandType::InputThreadIndexInGroup: { + m_cs.builtinLocalInvocationIndex = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassInput }, + spv::BuiltInLocalInvocationIndex, + "vThreadIndexInGroup"); + } break; + + case DxbcOperandType::InputCoverageMask: { + m_ps.builtinSampleMaskIn = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 1 }, + spv::StorageClassInput }, + spv::BuiltInSampleMask, + "vCoverage"); + } break; + + case DxbcOperandType::OutputCoverageMask: { + m_ps.builtinSampleMaskOut = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 1 }, + spv::StorageClassOutput }, + spv::BuiltInSampleMask, + "oMask"); + } break; + + case DxbcOperandType::OutputDepth: { + m_module.setExecutionMode(m_entryPointId, + spv::ExecutionModeDepthReplacing); + m_ps.builtinDepth = emitNewBuiltinVariable({ + { DxbcScalarType::Float32, 1, 0 }, + spv::StorageClassOutput }, + spv::BuiltInFragDepth, + "oDepth"); + } break; + + case DxbcOperandType::OutputStencilRef: { + m_module.enableExtension("SPV_EXT_shader_stencil_export"); + m_module.enableCapability(spv::CapabilityStencilExportEXT); + m_module.setExecutionMode(m_entryPointId, + spv::ExecutionModeStencilRefReplacingEXT); + m_ps.builtinStencilRef = emitNewBuiltinVariable({ + { DxbcScalarType::Sint32, 1, 0 }, + spv::StorageClassOutput }, + spv::BuiltInFragStencilRefEXT, + "oStencilRef"); + } break; + + case DxbcOperandType::OutputDepthGe: { + m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeDepthReplacing); + m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeDepthGreater); + m_ps.builtinDepth = emitNewBuiltinVariable({ + { DxbcScalarType::Float32, 1, 0 }, + spv::StorageClassOutput }, + spv::BuiltInFragDepth, + "oDepthGe"); + } break; + + case DxbcOperandType::OutputDepthLe: { + m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeDepthReplacing); + m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeDepthLess); + m_ps.builtinDepth = emitNewBuiltinVariable({ + { DxbcScalarType::Float32, 1, 0 }, + spv::StorageClassOutput }, + spv::BuiltInFragDepth, + "oDepthLe"); + } break; + + case DxbcOperandType::InputPrimitiveId: { + m_primitiveIdIn = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassInput }, + spv::BuiltInPrimitiveId, + "vPrim"); + } break; + + case DxbcOperandType::InputDomainPoint: { + m_ds.builtinTessCoord = emitNewBuiltinVariable({ + { DxbcScalarType::Float32, 3, 0 }, + spv::StorageClassInput }, + spv::BuiltInTessCoord, + "vDomain"); + } break; + + case DxbcOperandType::InputForkInstanceId: + case DxbcOperandType::InputJoinInstanceId: { + auto phase = this->getCurrentHsForkJoinPhase(); + + phase->instanceIdPtr = m_module.newVar( + m_module.defPointerType( + m_module.defIntType(32, 0), + spv::StorageClassFunction), + spv::StorageClassFunction); + + m_module.opStore(phase->instanceIdPtr, phase->instanceId); + m_module.setDebugName(phase->instanceIdPtr, + ins.dst[0].type == DxbcOperandType::InputForkInstanceId + ? "vForkInstanceId" : "vJoinInstanceId"); + } break; + + case DxbcOperandType::OutputControlPointId: { + // This system value map to the invocation + // ID, which has been declared already. + } break; + + case DxbcOperandType::InputPatchConstant: + case DxbcOperandType::OutputControlPoint: { + // These have been declared as global input and + // output arrays, so there's nothing left to do. + } break; + + case DxbcOperandType::InputGsInstanceId: { + m_gs.builtinInvocationId = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassInput }, + spv::BuiltInInvocationId, + "vInstanceID"); + } break; + + case DxbcOperandType::InputInnerCoverage: { + m_module.enableExtension("SPV_EXT_fragment_fully_covered"); + m_module.enableCapability(spv::CapabilityFragmentFullyCoveredEXT); + + // This is bool in SPIR-V but uint32 in DXBC. A bool value of + // false must be 0, and bit 1 must be set to represent true. + uint32_t builtinId = emitNewBuiltinVariable({ + { DxbcScalarType::Bool, 1, 0 }, + spv::StorageClassInput }, + spv::BuiltInFullyCoveredEXT, + nullptr); + + m_ps.builtinInnerCoverageId = emitNewVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassPrivate }); + + m_module.setDebugName(m_ps.builtinInnerCoverageId, "vInnerCoverage"); + + uint32_t boolTypeId = m_module.defBoolType(); + uint32_t uintTypeId = m_module.defIntType(32, 0); + + m_module.opStore(m_ps.builtinInnerCoverageId, + m_module.opSelect(uintTypeId, + m_module.opLoad(boolTypeId, builtinId), + m_module.constu32(1), + m_module.constu32(0))); + } break; + + default: + Logger::err(str::format( + "DxbcCompiler: Unsupported operand type declaration: ", + ins.dst[0].type)); + + } + } + + + void DxbcCompiler::emitDclInput( + uint32_t regIdx, + uint32_t regDim, + DxbcRegMask regMask, + DxbcSystemValue sv, + DxbcInterpolationMode im) { + // Avoid declaring the same variable multiple times. + // This may happen when multiple system values are + // mapped to different parts of the same register. + if (m_vRegs.at(regIdx).id == 0 && sv == DxbcSystemValue::None) { + const DxbcVectorType regType = getInputRegType(regIdx); + + DxbcRegisterInfo info; + info.type.ctype = regType.ctype; + info.type.ccount = regType.ccount; + info.type.alength = regDim; + info.sclass = spv::StorageClassInput; + + const uint32_t varId = emitNewVariable(info); + + m_module.decorateLocation(varId, regIdx); + m_module.setDebugName(varId, str::format("v", regIdx).c_str()); + + m_vRegs.at(regIdx) = { regType, varId }; + + // Interpolation mode, used in pixel shaders + if (im == DxbcInterpolationMode::Constant) + m_module.decorate(varId, spv::DecorationFlat); + + if (im == DxbcInterpolationMode::LinearCentroid + || im == DxbcInterpolationMode::LinearNoPerspectiveCentroid) + m_module.decorate(varId, spv::DecorationCentroid); + + if (im == DxbcInterpolationMode::LinearNoPerspective + || im == DxbcInterpolationMode::LinearNoPerspectiveCentroid + || im == DxbcInterpolationMode::LinearNoPerspectiveSample) + m_module.decorate(varId, spv::DecorationNoPerspective); + + if (im == DxbcInterpolationMode::LinearSample + || im == DxbcInterpolationMode::LinearNoPerspectiveSample) { + m_module.enableCapability(spv::CapabilitySampleRateShading); + m_module.decorate(varId, spv::DecorationSample); + } + + if (m_moduleInfo.options.forceSampleRateShading) { + if (im == DxbcInterpolationMode::Linear + || im == DxbcInterpolationMode::LinearNoPerspective) { + m_module.enableCapability(spv::CapabilitySampleRateShading); + m_module.decorate(varId, spv::DecorationSample); + } + } + + // Declare the input slot as defined + m_inputMask |= 1u << regIdx; + m_vArrayLength = std::max(m_vArrayLength, regIdx + 1); + } else if (sv != DxbcSystemValue::None) { + // Add a new system value mapping if needed + bool skipSv = sv == DxbcSystemValue::ClipDistance + || sv == DxbcSystemValue::CullDistance; + + if (!skipSv) + m_vMappings.push_back({ regIdx, regMask, sv }); + } + } + + + void DxbcCompiler::emitDclOutput( + uint32_t regIdx, + uint32_t regDim, + DxbcRegMask regMask, + DxbcSystemValue sv, + DxbcInterpolationMode im) { + // Add a new system value mapping if needed. Clip + // and cull distances are handled separately. + if (sv != DxbcSystemValue::None + && sv != DxbcSystemValue::ClipDistance + && sv != DxbcSystemValue::CullDistance) + m_oMappings.push_back({ regIdx, regMask, sv }); + + if (m_programInfo.type() == DxbcProgramType::HullShader) { + // Hull shaders don't use standard outputs + if (getCurrentHsForkJoinPhase() != nullptr) + m_hs.outputPerPatchMask |= 1 << regIdx; + } else if (m_oRegs.at(regIdx).id == 0) { + // Avoid declaring the same variable multiple times. + // This may happen when multiple system values are + // mapped to different parts of the same register. + const DxbcVectorType regType = getOutputRegType(regIdx); + + DxbcRegisterInfo info; + info.type.ctype = regType.ctype; + info.type.ccount = regType.ccount; + info.type.alength = regDim; + info.sclass = spv::StorageClassOutput; + + // In xfb mode, we set up the actual + // output vars when emitting a vertex + if (m_moduleInfo.xfb != nullptr) + info.sclass = spv::StorageClassPrivate; + + // In geometry shaders, don't duplicate system value outputs + // to stay within device limits. The pixel shader will read + // all GS system value outputs as system value inputs. + if (m_programInfo.type() == DxbcProgramType::GeometryShader && sv != DxbcSystemValue::None) + info.sclass = spv::StorageClassPrivate; + + const uint32_t varId = this->emitNewVariable(info); + m_module.setDebugName(varId, str::format("o", regIdx).c_str()); + + if (info.sclass == spv::StorageClassOutput) { + m_module.decorateLocation(varId, regIdx); + + // Add index decoration for potential dual-source blending + if (m_programInfo.type() == DxbcProgramType::PixelShader) + m_module.decorateIndex(varId, 0); + + // Declare vertex positions in all stages as invariant, even if + // this is not the last stage, to help with potential Z fighting. + if (sv == DxbcSystemValue::Position && m_moduleInfo.options.invariantPosition) + m_module.decorate(varId, spv::DecorationInvariant); + } + + m_oRegs.at(regIdx) = { regType, varId }; + + // Declare the output slot as defined + m_outputMask |= 1u << regIdx; + } + } + + + void DxbcCompiler::emitDclConstantBuffer(const DxbcShaderInstruction& ins) { + // dcl_constant_buffer has one operand with two indices: + // (0) Constant buffer register ID (cb#) + // (1) Number of constants in the buffer + uint32_t bufferId = ins.dst[0].idx[0].offset; + uint32_t elementCount = ins.dst[0].idx[1].offset; + + // With dynamic indexing, games will often index constant buffers + // out of bounds. Declare an upper bound to stay within spec. + if (ins.controls.accessType() == DxbcConstantBufferAccessType::DynamicallyIndexed) + elementCount = 4096; + + this->emitDclConstantBufferVar(bufferId, elementCount, 4u, + str::format("cb", bufferId).c_str()); + } + + + void DxbcCompiler::emitDclConstantBufferVar( + uint32_t regIdx, + uint32_t numConstants, + uint32_t numComponents, + const char* name) { + // Uniform buffer data is stored as a fixed-size array + // of 4x32-bit vectors. SPIR-V requires explicit strides. + const uint32_t arrayType = m_module.defArrayTypeUnique( + getVectorTypeId({ DxbcScalarType::Float32, numComponents }), + m_module.constu32(numConstants)); + m_module.decorateArrayStride(arrayType, sizeof(uint32_t) * numComponents); + + // SPIR-V requires us to put that array into a + // struct and decorate that struct as a block. + const uint32_t structType = m_module.defStructTypeUnique(1, &arrayType); + + m_module.decorate(structType, spv::DecorationBlock); + m_module.memberDecorateOffset(structType, 0, 0); + + m_module.setDebugName (structType, str::format(name, "_t").c_str()); + m_module.setDebugMemberName (structType, 0, "m"); + + // Variable that we'll use to access the buffer + const uint32_t varId = m_module.newVar( + m_module.defPointerType(structType, spv::StorageClassUniform), + spv::StorageClassUniform); + + m_module.setDebugName(varId, name); + + // Compute the DXVK binding slot index for the buffer. + // D3D11 needs to bind the actual buffers to this slot. + uint32_t bindingId = computeConstantBufferBinding( + m_programInfo.type(), regIdx); + + m_module.decorateDescriptorSet(varId, 0); + m_module.decorateBinding(varId, bindingId); + + DxbcConstantBuffer buf; + buf.varId = varId; + buf.size = numConstants; + m_constantBuffers.at(regIdx) = buf; + + // Store descriptor info for the shader interface + DxvkBindingInfo binding = { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }; + binding.viewType = VK_IMAGE_VIEW_TYPE_MAX_ENUM; + binding.access = VK_ACCESS_UNIFORM_READ_BIT; + binding.resourceBinding = bindingId; + binding.uboSet = true; + m_bindings.push_back(binding); + } + + + void DxbcCompiler::emitDclSampler(const DxbcShaderInstruction& ins) { + // dclSampler takes one operand: + // (dst0) The sampler register to declare + const uint32_t samplerId = ins.dst[0].idx[0].offset; + + // The sampler type is opaque, but we still have to + // define a pointer and a variable in oder to use it + const uint32_t samplerType = m_module.defSamplerType(); + const uint32_t samplerPtrType = m_module.defPointerType( + samplerType, spv::StorageClassUniformConstant); + + // Define the sampler variable + const uint32_t varId = m_module.newVar(samplerPtrType, + spv::StorageClassUniformConstant); + m_module.setDebugName(varId, + str::format("s", samplerId).c_str()); + + m_samplers.at(samplerId).varId = varId; + m_samplers.at(samplerId).typeId = samplerType; + + // Compute binding slot index for the sampler + uint32_t bindingId = computeSamplerBinding( + m_programInfo.type(), samplerId); + + m_module.decorateDescriptorSet(varId, 0); + m_module.decorateBinding(varId, bindingId); + + // Store descriptor info for the shader interface + DxvkBindingInfo binding = { VK_DESCRIPTOR_TYPE_SAMPLER }; + binding.viewType = VK_IMAGE_VIEW_TYPE_MAX_ENUM; + binding.resourceBinding = bindingId; + m_bindings.push_back(binding); + } + + + void DxbcCompiler::emitDclStream(const DxbcShaderInstruction& ins) { + if (ins.dst[0].idx[0].offset != 0 && m_moduleInfo.xfb == nullptr) + Logger::err("Dxbc: Multiple streams not supported"); + } + + + void DxbcCompiler::emitDclResourceTyped(const DxbcShaderInstruction& ins) { + // dclResource takes two operands: + // (dst0) The resource register ID + // (imm0) The resource return type + const uint32_t registerId = ins.dst[0].idx[0].offset; + + // We also handle unordered access views here + const bool isUav = ins.op == DxbcOpcode::DclUavTyped; + + if (isUav) { + if (m_moduleInfo.options.supportsTypedUavLoadR32) + m_module.enableCapability(spv::CapabilityStorageImageReadWithoutFormat); + m_module.enableCapability(spv::CapabilityStorageImageWriteWithoutFormat); + } + + // Defines the type of the resource (texture2D, ...) + const DxbcResourceDim resourceType = ins.controls.resourceDim(); + + // Defines the type of a read operation. DXBC has the ability + // to define four different types whereas SPIR-V only allows + // one, but in practice this should not be much of a problem. + auto xType = static_cast( + bit::extract(ins.imm[0].u32, 0, 3)); + auto yType = static_cast( + bit::extract(ins.imm[0].u32, 4, 7)); + auto zType = static_cast( + bit::extract(ins.imm[0].u32, 8, 11)); + auto wType = static_cast( + bit::extract(ins.imm[0].u32, 12, 15)); + + if ((xType != yType) || (xType != zType) || (xType != wType)) + Logger::warn("DxbcCompiler: dcl_resource: Ignoring resource return types"); + + // Declare the actual sampled type + const DxbcScalarType sampledType = [xType] { + switch (xType) { + // FIXME is this correct? There's no documentation about it + case DxbcResourceReturnType::Mixed: return DxbcScalarType::Uint32; + // FIXME do we have to manually clamp writes to SNORM/UNORM resources? + case DxbcResourceReturnType::Snorm: return DxbcScalarType::Float32; + case DxbcResourceReturnType::Unorm: return DxbcScalarType::Float32; + case DxbcResourceReturnType::Float: return DxbcScalarType::Float32; + case DxbcResourceReturnType::Sint: return DxbcScalarType::Sint32; + case DxbcResourceReturnType::Uint: return DxbcScalarType::Uint32; + default: throw DxvkError(str::format("DxbcCompiler: Invalid sampled type: ", xType)); + } + }(); + + // Declare the resource type + const uint32_t sampledTypeId = getScalarTypeId(sampledType); + const DxbcImageInfo typeInfo = getResourceType(resourceType, isUav); + + // Declare additional capabilities if necessary + switch (resourceType) { + case DxbcResourceDim::Buffer: + m_module.enableCapability(isUav + ? spv::CapabilityImageBuffer + : spv::CapabilitySampledBuffer); + break; + + case DxbcResourceDim::Texture1D: + case DxbcResourceDim::Texture1DArr: + m_module.enableCapability(isUav + ? spv::CapabilityImage1D + : spv::CapabilitySampled1D); + break; + + case DxbcResourceDim::TextureCubeArr: + m_module.enableCapability( + spv::CapabilitySampledCubeArray); + break; + + default: + // No additional capabilities required + break; + } + + // If the read-without-format capability is not set and this + // image is access via a typed load, or if atomic operations + // are used,, we must define the image format explicitly. + spv::ImageFormat imageFormat = spv::ImageFormatUnknown; + + if (isUav) { + if ((m_analysis->uavInfos[registerId].accessAtomicOp) + || (m_analysis->uavInfos[registerId].accessTypedLoad + && !m_moduleInfo.options.supportsTypedUavLoadR32)) + imageFormat = getScalarImageFormat(sampledType); + } + + // We do not know whether the image is going to be used as + // a color image or a depth image yet, but we can pick the + // correct type when creating a sampled image object. + const uint32_t imageTypeId = m_module.defImageType(sampledTypeId, + typeInfo.dim, 0, typeInfo.array, typeInfo.ms, typeInfo.sampled, + imageFormat); + + // We'll declare the texture variable with the color type + // and decide which one to use when the texture is sampled. + const uint32_t resourcePtrType = m_module.defPointerType( + imageTypeId, spv::StorageClassUniformConstant); + + const uint32_t varId = m_module.newVar(resourcePtrType, + spv::StorageClassUniformConstant); + + m_module.setDebugName(varId, + str::format(isUav ? "u" : "t", registerId).c_str()); + + // Compute the DXVK binding slot index for the resource. + // D3D11 needs to bind the actual resource to this slot. + uint32_t bindingId = isUav + ? computeUavBinding(m_programInfo.type(), registerId) + : computeSrvBinding(m_programInfo.type(), registerId); + + m_module.decorateDescriptorSet(varId, 0); + m_module.decorateBinding(varId, bindingId); + + // Declare a specialization constant which will + // store whether or not the resource is bound. + if (isUav) { + DxbcUav uav; + uav.type = DxbcResourceType::Typed; + uav.imageInfo = typeInfo; + uav.varId = varId; + uav.ctrId = 0; + uav.sampledType = sampledType; + uav.sampledTypeId = sampledTypeId; + uav.imageTypeId = imageTypeId; + uav.structStride = 0; + uav.coherence = getUavCoherence(registerId, ins.controls.uavFlags()); + uav.isRawSsbo = false; + m_uavs.at(registerId) = uav; + } else { + DxbcShaderResource res; + res.type = DxbcResourceType::Typed; + res.imageInfo = typeInfo; + res.varId = varId; + res.sampledType = sampledType; + res.sampledTypeId = sampledTypeId; + res.imageTypeId = imageTypeId; + res.colorTypeId = imageTypeId; + res.depthTypeId = 0; + res.structStride = 0; + res.isRawSsbo = false; + + if ((sampledType == DxbcScalarType::Float32) + && (resourceType == DxbcResourceDim::Texture1D + || resourceType == DxbcResourceDim::Texture1DArr + || resourceType == DxbcResourceDim::Texture2D + || resourceType == DxbcResourceDim::Texture2DArr + || resourceType == DxbcResourceDim::TextureCube + || resourceType == DxbcResourceDim::TextureCubeArr)) { + res.depthTypeId = m_module.defImageType(sampledTypeId, + typeInfo.dim, 1, typeInfo.array, typeInfo.ms, typeInfo.sampled, + spv::ImageFormatUnknown); + } + + m_textures.at(registerId) = res; + } + + // Store descriptor info for the shader interface + DxvkBindingInfo binding = { }; + binding.viewType = typeInfo.vtype; + binding.resourceBinding = bindingId; + binding.isMultisampled = typeInfo.ms; + + if (isUav) { + binding.descriptorType = resourceType == DxbcResourceDim::Buffer + ? VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER + : VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + binding.access = m_analysis->uavInfos[registerId].accessFlags; + + if (!m_analysis->uavInfos[registerId].nonInvariantAccess) + binding.accessOp = m_analysis->uavInfos[registerId].accessOp; + + if (!(binding.access & VK_ACCESS_SHADER_WRITE_BIT)) + m_module.decorate(varId, spv::DecorationNonWritable); + if (!(binding.access & VK_ACCESS_SHADER_READ_BIT)) + m_module.decorate(varId, spv::DecorationNonReadable); + } else { + binding.descriptorType = resourceType == DxbcResourceDim::Buffer + ? VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER + : VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + binding.access = VK_ACCESS_SHADER_READ_BIT; + } + + m_bindings.push_back(binding); + } + + + void DxbcCompiler::emitDclResourceRawStructured(const DxbcShaderInstruction& ins) { + // dcl_resource_raw and dcl_uav_raw take one argument: + // (dst0) The resource register ID + // dcl_resource_structured and dcl_uav_structured take two arguments: + // (dst0) The resource register ID + // (imm0) Structure stride, in bytes + const uint32_t registerId = ins.dst[0].idx[0].offset; + + const bool isUav = ins.op == DxbcOpcode::DclUavRaw + || ins.op == DxbcOpcode::DclUavStructured; + + const bool isStructured = ins.op == DxbcOpcode::DclUavStructured + || ins.op == DxbcOpcode::DclResourceStructured; + + const DxbcScalarType sampledType = DxbcScalarType::Uint32; + const uint32_t sampledTypeId = getScalarTypeId(sampledType); + + const DxbcImageInfo typeInfo = { spv::DimBuffer, 0, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_MAX_ENUM }; + + // Declare the resource type + uint32_t resTypeId = 0; + uint32_t varId = 0; + + // Write back resource info + DxbcResourceType resType = isStructured + ? DxbcResourceType::Structured + : DxbcResourceType::Raw; + + uint32_t resStride = isStructured + ? ins.imm[0].u32 + : 0; + + uint32_t resAlign = isStructured + ? (resStride & -resStride) + : 16; + + // Compute the DXVK binding slot index for the resource. + uint32_t bindingId = isUav + ? computeUavBinding(m_programInfo.type(), registerId) + : computeSrvBinding(m_programInfo.type(), registerId); + + // Test whether we should use a raw SSBO for this resource + bool hasSparseFeedback = isUav + ? m_analysis->uavInfos[registerId].sparseFeedback + : m_analysis->srvInfos[registerId].sparseFeedback; + + bool useRawSsbo = m_moduleInfo.options.minSsboAlignment <= resAlign && !hasSparseFeedback; + + if (useRawSsbo) { + uint32_t elemType = getScalarTypeId(DxbcScalarType::Uint32); + uint32_t arrayType = m_module.defRuntimeArrayTypeUnique(elemType); + uint32_t structType = m_module.defStructTypeUnique(1, &arrayType); + uint32_t ptrType = m_module.defPointerType(structType, spv::StorageClassStorageBuffer); + + resTypeId = m_module.defPointerType(elemType, spv::StorageClassStorageBuffer); + varId = m_module.newVar(ptrType, spv::StorageClassStorageBuffer); + + m_module.decorateArrayStride(arrayType, sizeof(uint32_t)); + m_module.decorate(structType, spv::DecorationBlock); + m_module.memberDecorateOffset(structType, 0, 0); + + m_module.setDebugName(structType, + str::format(isUav ? "u" : "t", registerId, "_t").c_str()); + m_module.setDebugMemberName(structType, 0, "m"); + } else { + // Structured and raw buffers are represented as + // texel buffers consisting of 32-bit integers. + m_module.enableCapability(isUav + ? spv::CapabilityImageBuffer + : spv::CapabilitySampledBuffer); + + resTypeId = m_module.defImageType(sampledTypeId, + typeInfo.dim, 0, typeInfo.array, typeInfo.ms, typeInfo.sampled, + spv::ImageFormatR32ui); + + varId = m_module.newVar( + m_module.defPointerType(resTypeId, spv::StorageClassUniformConstant), + spv::StorageClassUniformConstant); + } + + m_module.setDebugName(varId, + str::format(isUav ? "u" : "t", registerId).c_str()); + + m_module.decorateDescriptorSet(varId, 0); + m_module.decorateBinding(varId, bindingId); + + if (isUav) { + DxbcUav uav; + uav.type = resType; + uav.imageInfo = typeInfo; + uav.varId = varId; + uav.ctrId = 0; + uav.sampledType = sampledType; + uav.sampledTypeId = sampledTypeId; + uav.imageTypeId = resTypeId; + uav.structStride = resStride; + uav.coherence = getUavCoherence(registerId, ins.controls.uavFlags()); + uav.isRawSsbo = useRawSsbo; + m_uavs.at(registerId) = uav; + } else { + DxbcShaderResource res; + res.type = resType; + res.imageInfo = typeInfo; + res.varId = varId; + res.sampledType = sampledType; + res.sampledTypeId = sampledTypeId; + res.imageTypeId = resTypeId; + res.colorTypeId = resTypeId; + res.depthTypeId = 0; + res.structStride = resStride; + res.isRawSsbo = useRawSsbo; + m_textures.at(registerId) = res; + } + + // Store descriptor info for the shader interface + DxvkBindingInfo binding = { }; + binding.descriptorType = useRawSsbo + ? VK_DESCRIPTOR_TYPE_STORAGE_BUFFER + : (isUav ? VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER : VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + binding.viewType = VK_IMAGE_VIEW_TYPE_MAX_ENUM; + binding.resourceBinding = bindingId; + binding.access = VK_ACCESS_SHADER_READ_BIT; + + if (isUav) { + binding.access = m_analysis->uavInfos[registerId].accessFlags; + + if (!m_analysis->uavInfos[registerId].nonInvariantAccess) + binding.accessOp = m_analysis->uavInfos[registerId].accessOp; + } + + if (useRawSsbo || isUav) { + if (!(binding.access & VK_ACCESS_SHADER_WRITE_BIT)) + m_module.decorate(varId, spv::DecorationNonWritable); + if (!(binding.access & VK_ACCESS_SHADER_READ_BIT)) + m_module.decorate(varId, spv::DecorationNonReadable); + } + + m_bindings.push_back(binding); + + // If supported, we'll be using raw access chains to access this + if (!m_hasRawAccessChains && m_moduleInfo.options.supportsRawAccessChains) { + m_module.enableExtension("SPV_NV_raw_access_chains"); + m_module.enableCapability(spv::CapabilityRawAccessChainsNV); + + m_hasRawAccessChains = true; + } + } + + + void DxbcCompiler::emitDclThreadGroupSharedMemory(const DxbcShaderInstruction& ins) { + // dcl_tgsm_raw takes two arguments: + // (dst0) The resource register ID + // (imm0) Block size, in bytes + // dcl_tgsm_structured takes three arguments: + // (dst0) The resource register ID + // (imm0) Structure stride, in bytes + // (imm1) Structure count + const bool isStructured = ins.op == DxbcOpcode::DclThreadGroupSharedMemoryStructured; + + const uint32_t regId = ins.dst[0].idx[0].offset; + + if (regId >= m_gRegs.size()) + m_gRegs.resize(regId + 1); + + const uint32_t elementStride = isStructured ? ins.imm[0].u32 : 0; + const uint32_t elementCount = isStructured ? ins.imm[1].u32 : ins.imm[0].u32; + + DxbcRegisterInfo varInfo; + varInfo.type.ctype = DxbcScalarType::Uint32; + varInfo.type.ccount = 1; + varInfo.type.alength = isStructured + ? elementCount * elementStride / 4 + : elementCount / 4; + varInfo.sclass = spv::StorageClassWorkgroup; + + m_gRegs[regId].type = isStructured + ? DxbcResourceType::Structured + : DxbcResourceType::Raw; + m_gRegs[regId].elementStride = elementStride; + m_gRegs[regId].elementCount = elementCount; + m_gRegs[regId].varId = emitNewVariable(varInfo); + + m_module.setDebugName(m_gRegs[regId].varId, + str::format("g", regId).c_str()); + } + + + void DxbcCompiler::emitDclGsInputPrimitive(const DxbcShaderInstruction& ins) { + // The input primitive type is stored within in the + // control bits of the opcode token. In SPIR-V, we + // have to define an execution mode. + const auto mode = [&] { + switch (ins.controls.primitive()) { + case DxbcPrimitive::Point: return std::make_pair(VK_PRIMITIVE_TOPOLOGY_POINT_LIST, spv::ExecutionModeInputPoints); + case DxbcPrimitive::Line: return std::make_pair(VK_PRIMITIVE_TOPOLOGY_LINE_LIST, spv::ExecutionModeInputLines); + case DxbcPrimitive::Triangle: return std::make_pair(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, spv::ExecutionModeTriangles); + case DxbcPrimitive::LineAdj: return std::make_pair(VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY, spv::ExecutionModeInputLinesAdjacency); + case DxbcPrimitive::TriangleAdj: return std::make_pair(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY, spv::ExecutionModeInputTrianglesAdjacency); + default: throw DxvkError("DxbcCompiler: Unsupported primitive type"); + } + }(); + + m_gs.inputPrimitive = ins.controls.primitive(); + m_module.setExecutionMode(m_entryPointId, mode.second); + m_inputTopology = mode.first; + + emitDclInputArray(primitiveVertexCount(m_gs.inputPrimitive)); + } + + + void DxbcCompiler::emitDclGsOutputTopology(const DxbcShaderInstruction& ins) { + // The input primitive topology is stored within in the + // control bits of the opcode token. In SPIR-V, we have + // to define an execution mode. + auto mode = [&] { + switch (ins.controls.primitiveTopology()) { + case DxbcPrimitiveTopology::PointList: return std::make_pair(VK_PRIMITIVE_TOPOLOGY_POINT_LIST, spv::ExecutionModeOutputPoints); + case DxbcPrimitiveTopology::LineStrip: return std::make_pair(VK_PRIMITIVE_TOPOLOGY_LINE_LIST, spv::ExecutionModeOutputLineStrip); + case DxbcPrimitiveTopology::TriangleStrip: return std::make_pair(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, spv::ExecutionModeOutputTriangleStrip); + default: throw DxvkError("DxbcCompiler: Unsupported primitive topology"); + } + }(); + + m_outputTopology = mode.first; + m_module.setExecutionMode(m_entryPointId, mode.second); + } + + + void DxbcCompiler::emitDclMaxOutputVertexCount(const DxbcShaderInstruction& ins) { + // dcl_max_output_vertex_count has one operand: + // (imm0) The maximum number of vertices + m_gs.outputVertexCount = ins.imm[0].u32; + + m_module.setOutputVertices(m_entryPointId, m_gs.outputVertexCount); + } + + + void DxbcCompiler::emitDclInputControlPointCount(const DxbcShaderInstruction& ins) { + // dcl_input_control_points has the control point + // count embedded within the opcode token. + if (m_programInfo.type() == DxbcProgramType::HullShader) { + m_hs.vertexCountIn = ins.controls.controlPointCount(); + + emitDclInputArray(m_hs.vertexCountIn); + } else { + m_ds.vertexCountIn = ins.controls.controlPointCount(); + + m_ds.inputPerPatch = emitTessInterfacePerPatch (spv::StorageClassInput); + m_ds.inputPerVertex = emitTessInterfacePerVertex(spv::StorageClassInput, m_ds.vertexCountIn); + } + } + + + void DxbcCompiler::emitDclOutputControlPointCount(const DxbcShaderInstruction& ins) { + // dcl_output_control_points has the control point + // count embedded within the opcode token. + m_hs.vertexCountOut = ins.controls.controlPointCount(); + + m_hs.outputPerPatch = emitTessInterfacePerPatch(spv::StorageClassPrivate); + m_hs.outputPerVertex = emitTessInterfacePerVertex(spv::StorageClassOutput, m_hs.vertexCountOut); + + m_module.setOutputVertices(m_entryPointId, m_hs.vertexCountOut); + } + + + void DxbcCompiler::emitDclHsMaxTessFactor(const DxbcShaderInstruction& ins) { + m_hs.maxTessFactor = ins.imm[0].f32; + } + + + void DxbcCompiler::emitDclTessDomain(const DxbcShaderInstruction& ins) { + auto mode = [&] { + switch (ins.controls.tessDomain()) { + case DxbcTessDomain::Isolines: return std::make_pair(VK_PRIMITIVE_TOPOLOGY_LINE_LIST, spv::ExecutionModeIsolines); + case DxbcTessDomain::Triangles: return std::make_pair(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, spv::ExecutionModeTriangles); + case DxbcTessDomain::Quads: return std::make_pair(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, spv::ExecutionModeQuads); + default: throw DxvkError("Dxbc: Invalid tess domain"); + } + }(); + + m_outputTopology = mode.first; + m_module.setExecutionMode(m_entryPointId, mode.second); + } + + + void DxbcCompiler::emitDclTessPartitioning(const DxbcShaderInstruction& ins) { + const spv::ExecutionMode executionMode = [&] { + switch (ins.controls.tessPartitioning()) { + case DxbcTessPartitioning::Pow2: + case DxbcTessPartitioning::Integer: return spv::ExecutionModeSpacingEqual; + case DxbcTessPartitioning::FractOdd: return spv::ExecutionModeSpacingFractionalOdd; + case DxbcTessPartitioning::FractEven: return spv::ExecutionModeSpacingFractionalEven; + default: throw DxvkError("Dxbc: Invalid tess partitioning"); + } + }(); + + m_module.setExecutionMode(m_entryPointId, executionMode); + } + + + void DxbcCompiler::emitDclTessOutputPrimitive(const DxbcShaderInstruction& ins) { + switch (ins.controls.tessOutputPrimitive()) { + case DxbcTessOutputPrimitive::Point: + m_module.setExecutionMode(m_entryPointId, spv::ExecutionModePointMode); + break; + + case DxbcTessOutputPrimitive::Line: + break; + + case DxbcTessOutputPrimitive::TriangleCw: + m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeVertexOrderCw); + break; + + case DxbcTessOutputPrimitive::TriangleCcw: + m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeVertexOrderCcw); + break; + + default: + throw DxvkError("Dxbc: Invalid tess output primitive"); + } + } + + + void DxbcCompiler::emitDclThreadGroup(const DxbcShaderInstruction& ins) { + // dcl_thread_group has three operands: + // (imm0) Number of threads in X dimension + // (imm1) Number of threads in Y dimension + // (imm2) Number of threads in Z dimension + m_cs.workgroupSizeX = ins.imm[0].u32; + m_cs.workgroupSizeY = ins.imm[1].u32; + m_cs.workgroupSizeZ = ins.imm[2].u32; + + m_module.setLocalSize(m_entryPointId, + ins.imm[0].u32, ins.imm[1].u32, ins.imm[2].u32); + } + + + void DxbcCompiler::emitDclGsInstanceCount(const DxbcShaderInstruction& ins) { + // dcl_gs_instance_count has one operand: + // (imm0) Number of geometry shader invocations + m_module.setInvocations(m_entryPointId, ins.imm[0].u32); + m_gs.invocationCount = ins.imm[0].u32; + } + + + uint32_t DxbcCompiler::emitDclUavCounter(uint32_t regId) { + // Declare a structure type which holds the UAV counter + if (m_uavCtrStructType == 0) { + const uint32_t t_u32 = m_module.defIntType(32, 0); + const uint32_t t_struct = m_module.defStructTypeUnique(1, &t_u32); + + m_module.decorate(t_struct, spv::DecorationBlock); + m_module.memberDecorateOffset(t_struct, 0, 0); + + m_module.setDebugName (t_struct, "uav_meta"); + m_module.setDebugMemberName(t_struct, 0, "ctr"); + + m_uavCtrStructType = t_struct; + m_uavCtrPointerType = m_module.defPointerType( + t_struct, spv::StorageClassStorageBuffer); + } + + // Declare the buffer variable + const uint32_t varId = m_module.newVar( + m_uavCtrPointerType, spv::StorageClassStorageBuffer); + + m_module.setDebugName(varId, + str::format("u", regId, "_meta").c_str()); + + uint32_t bindingId = computeUavCounterBinding( + m_programInfo.type(), regId); + + m_module.decorateDescriptorSet(varId, 0); + m_module.decorateBinding(varId, bindingId); + + // Declare the storage buffer binding + DxvkBindingInfo binding = { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER }; + binding.resourceBinding = bindingId; + binding.viewType = VK_IMAGE_VIEW_TYPE_MAX_ENUM; + binding.access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + m_bindings.push_back(binding); + + return varId; + } + + + void DxbcCompiler::emitDclImmediateConstantBuffer(const DxbcShaderInstruction& ins) { + if (m_icbArray) + throw DxvkError("DxbcCompiler: Immediate constant buffer already declared"); + + if ((ins.customDataSize & 0x3) != 0) + throw DxvkError("DxbcCompiler: Immediate constant buffer size not a multiple of four DWORDs"); + + // A lot of the time we'll be dealing with a scalar or vec2 + // array here, there's no reason to emit all those zeroes. + uint32_t componentCount = 1u; + + for (uint32_t i = 0; i < ins.customDataSize; i += 4u) { + for (uint32_t c = componentCount; c < 4u; c++) { + if (ins.customData[i + c]) + componentCount = c + 1u; + } + + if (componentCount == 4u) + break; + } + + uint32_t vectorCount = (ins.customDataSize / 4u); + uint32_t dwordCount = vectorCount * componentCount; + + if (dwordCount <= Icb_MaxBakedDwords) { + this->emitDclImmediateConstantBufferBaked( + ins.customDataSize, ins.customData, componentCount); + } else { + this->emitDclImmediateConstantBufferUbo( + ins.customDataSize, ins.customData, componentCount); + } + } + + + void DxbcCompiler::emitDclImmediateConstantBufferBaked( + uint32_t dwordCount, + const uint32_t* dwordArray, + uint32_t componentCount) { + // Declare individual vector constants as 4x32-bit vectors + small_vector vectorIds; + + DxbcVectorType vecType; + vecType.ctype = DxbcScalarType::Uint32; + vecType.ccount = componentCount; + + uint32_t vectorTypeId = getVectorTypeId(vecType); + + for (uint32_t i = 0; i < dwordCount; i += 4u) { + std::array scalarIds = { }; + + for (uint32_t c = 0; c < componentCount; c++) + scalarIds[c] = m_module.constu32(dwordArray[i + c]); + + uint32_t id = scalarIds[0]; + + if (componentCount > 1u) + id = m_module.constComposite(vectorTypeId, componentCount, scalarIds.data()); + + vectorIds.push_back(id); + } + + // Pad array with one entry of zeroes so that we can + // handle out-of-bounds accesses more conveniently. + vectorIds.push_back(emitBuildZeroVector(vecType).id); + + // Declare the array that contains all the vectors + DxbcArrayType arrInfo; + arrInfo.ctype = DxbcScalarType::Uint32; + arrInfo.ccount = componentCount; + arrInfo.alength = vectorIds.size(); + + uint32_t arrayTypeId = getArrayTypeId(arrInfo); + uint32_t arrayId = m_module.constComposite( + arrayTypeId, vectorIds.size(), vectorIds.data()); + + // Declare the variable that will hold the constant + // data and initialize it with the constant array. + uint32_t pointerTypeId = m_module.defPointerType( + arrayTypeId, spv::StorageClassPrivate); + + m_icbArray = m_module.newVarInit( + pointerTypeId, spv::StorageClassPrivate, + arrayId); + + m_module.setDebugName(m_icbArray, "icb"); + m_module.decorate(m_icbArray, spv::DecorationNonWritable); + + m_icbComponents = componentCount; + m_icbSize = dwordCount / 4u; + } + + + void DxbcCompiler::emitDclImmediateConstantBufferUbo( + uint32_t dwordCount, + const uint32_t* dwordArray, + uint32_t componentCount) { + uint32_t vectorCount = dwordCount / 4u; + + // Tightly pack vec2 or scalar arrays if possible. Don't bother with + // vec3 since we'd rather have properly vectorized loads in that case. + if (m_moduleInfo.options.supportsTightIcbPacking && componentCount <= 2u) + m_icbComponents = componentCount; + else + m_icbComponents = 4u; + + // Immediate constant buffer can be read out of bounds, declare + // it with the maximum possible size and rely on robustness. + this->emitDclConstantBufferVar(Icb_BindingSlotId, 4096u, m_icbComponents, "icb"); + + m_icbData.reserve(vectorCount * componentCount); + + for (uint32_t i = 0; i < dwordCount; i += 4u) { + for (uint32_t c = 0; c < m_icbComponents; c++) + m_icbData.push_back(dwordArray[i + c]); + } + + m_icbSize = vectorCount; + } + + + void DxbcCompiler::emitCustomData(const DxbcShaderInstruction& ins) { + switch (ins.customDataType) { + case DxbcCustomDataClass::ImmConstBuf: + return emitDclImmediateConstantBuffer(ins); + + default: + Logger::warn(str::format( + "DxbcCompiler: Unsupported custom data block: ", + ins.customDataType)); + } + } + + + void DxbcCompiler::emitVectorAlu(const DxbcShaderInstruction& ins) { + std::array src; + + for (uint32_t i = 0; i < ins.srcCount; i++) + src.at(i) = emitRegisterLoad(ins.src[i], ins.dst[0].mask); + + DxbcRegisterValue dst; + dst.type.ctype = ins.dst[0].dataType; + dst.type.ccount = ins.dst[0].mask.popCount(); + + if (isDoubleType(ins.dst[0].dataType)) + dst.type.ccount /= 2; + + const uint32_t typeId = getVectorTypeId(dst.type); + + switch (ins.op) { + ///////////////////// + // Move instructions + case DxbcOpcode::Mov: + case DxbcOpcode::DMov: + dst.id = src.at(0).id; + break; + + ///////////////////////////////////// + // ALU operations on float32 numbers + case DxbcOpcode::Add: + case DxbcOpcode::DAdd: + dst.id = m_module.opFAdd(typeId, + src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::Div: + case DxbcOpcode::DDiv: + dst.id = m_module.opFDiv(typeId, + src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::Exp: + dst.id = m_module.opExp2( + typeId, src.at(0).id); + break; + + case DxbcOpcode::Frc: + dst.id = m_module.opFract( + typeId, src.at(0).id); + break; + + case DxbcOpcode::Log: + dst.id = m_module.opLog2( + typeId, src.at(0).id); + break; + + case DxbcOpcode::Mad: + case DxbcOpcode::DFma: + if (ins.controls.precise()) { + // FXC only emits precise mad if the shader explicitly uses + // the HLSL mad()/fma() intrinsics, let's preserve that. + dst.id = m_module.opFFma(typeId, + src.at(0).id, src.at(1).id, src.at(2).id); + } else { + dst.id = m_module.opFMul(typeId, src.at(0).id, src.at(1).id); + dst.id = m_module.opFAdd(typeId, dst.id, src.at(2).id); + } + break; + + case DxbcOpcode::Max: + case DxbcOpcode::DMax: + dst.id = m_module.opNMax(typeId, + src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::Min: + case DxbcOpcode::DMin: + dst.id = m_module.opNMin(typeId, + src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::Mul: + case DxbcOpcode::DMul: + dst.id = m_module.opFMul(typeId, + src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::Rcp: + dst.id = m_module.opFDiv(typeId, + emitBuildConstVecf32( + 1.0f, 1.0f, 1.0f, 1.0f, + ins.dst[0].mask).id, + src.at(0).id); + break; + + case DxbcOpcode::DRcp: + dst.id = m_module.opFDiv(typeId, + emitBuildConstVecf64(1.0, 1.0, + ins.dst[0].mask).id, + src.at(0).id); + break; + + case DxbcOpcode::RoundNe: + dst.id = m_module.opRoundEven( + typeId, src.at(0).id); + break; + + case DxbcOpcode::RoundNi: + dst.id = m_module.opFloor( + typeId, src.at(0).id); + break; + + case DxbcOpcode::RoundPi: + dst.id = m_module.opCeil( + typeId, src.at(0).id); + break; + + case DxbcOpcode::RoundZ: + dst.id = m_module.opTrunc( + typeId, src.at(0).id); + break; + + case DxbcOpcode::Rsq: + dst.id = m_module.opInverseSqrt( + typeId, src.at(0).id); + break; + + case DxbcOpcode::Sqrt: + dst.id = m_module.opSqrt( + typeId, src.at(0).id); + break; + + ///////////////////////////////////// + // ALU operations on signed integers + case DxbcOpcode::IAdd: + dst.id = m_module.opIAdd(typeId, + src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::IMad: + case DxbcOpcode::UMad: + dst.id = m_module.opIAdd(typeId, + m_module.opIMul(typeId, + src.at(0).id, src.at(1).id), + src.at(2).id); + break; + + case DxbcOpcode::IMax: + dst.id = m_module.opSMax(typeId, + src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::IMin: + dst.id = m_module.opSMin(typeId, + src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::INeg: + dst.id = m_module.opSNegate( + typeId, src.at(0).id); + break; + + /////////////////////////////////////// + // ALU operations on unsigned integers + case DxbcOpcode::UMax: + dst.id = m_module.opUMax(typeId, + src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::UMin: + dst.id = m_module.opUMin(typeId, + src.at(0).id, src.at(1).id); + break; + + /////////////////////////////////////// + // Bit operations on unsigned integers + case DxbcOpcode::And: + dst.id = m_module.opBitwiseAnd(typeId, + src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::Not: + dst.id = m_module.opNot( + typeId, src.at(0).id); + break; + + case DxbcOpcode::Or: + dst.id = m_module.opBitwiseOr(typeId, + src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::Xor: + dst.id = m_module.opBitwiseXor(typeId, + src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::CountBits: + dst.id = m_module.opBitCount( + typeId, src.at(0).id); + break; + + case DxbcOpcode::BfRev: + dst.id = m_module.opBitReverse( + typeId, src.at(0).id); + break; + + /////////////////////////// + // Conversion instructions + case DxbcOpcode::ItoF: + dst.id = m_module.opConvertStoF( + typeId, src.at(0).id); + break; + + case DxbcOpcode::UtoF: + dst.id = m_module.opConvertUtoF( + typeId, src.at(0).id); + break; + + case DxbcOpcode::FtoI: + dst.id = m_module.opConvertFtoS( + typeId, src.at(0).id); + break; + + case DxbcOpcode::FtoU: + dst.id = m_module.opConvertFtoU( + typeId, src.at(0).id); + break; + + default: + Logger::warn(str::format( + "DxbcCompiler: Unhandled instruction: ", + ins.op)); + return; + } + + if (ins.controls.precise() || m_precise) + m_module.decorate(dst.id, spv::DecorationNoContraction); + + // Store computed value + dst = emitDstOperandModifiers(dst, ins.modifiers); + emitRegisterStore(ins.dst[0], dst); + } + + + void DxbcCompiler::emitVectorCmov(const DxbcShaderInstruction& ins) { + // movc and swapc have the following operands: + // (dst0) The first destination register + // (dst1) The second destination register (swapc only) + // (src0) The condition vector + // (src1) Vector to select from if the condition is not 0 + // (src2) Vector to select from if the condition is 0 + DxbcRegMask condMask = ins.dst[0].mask; + + if (ins.dst[0].dataType == DxbcScalarType::Float64) { + condMask = DxbcRegMask( + condMask[0] && condMask[1], + condMask[2] && condMask[3], + false, false); + } + + const DxbcRegisterValue condition = emitRegisterLoad(ins.src[0], condMask); + const DxbcRegisterValue selectTrue = emitRegisterLoad(ins.src[1], ins.dst[0].mask); + const DxbcRegisterValue selectFalse = emitRegisterLoad(ins.src[2], ins.dst[0].mask); + + uint32_t componentCount = condMask.popCount(); + + // We'll compare against a vector of zeroes to generate a + // boolean vector, which in turn will be used by OpSelect + uint32_t zeroType = m_module.defIntType(32, 0); + uint32_t boolType = m_module.defBoolType(); + + uint32_t zero = m_module.constu32(0); + + if (componentCount > 1) { + zeroType = m_module.defVectorType(zeroType, componentCount); + boolType = m_module.defVectorType(boolType, componentCount); + + const std::array zeroVec = { zero, zero, zero, zero }; + zero = m_module.constComposite(zeroType, componentCount, zeroVec.data()); + } + + // In case of swapc, the second destination operand receives + // the output that a cmov instruction would normally get + const uint32_t trueIndex = ins.op == DxbcOpcode::Swapc ? 1 : 0; + + for (uint32_t i = 0; i < ins.dstCount; i++) { + DxbcRegisterValue result; + result.type.ctype = ins.dst[i].dataType; + result.type.ccount = componentCount; + result.id = m_module.opSelect( + getVectorTypeId(result.type), + m_module.opINotEqual(boolType, condition.id, zero), + i == trueIndex ? selectTrue.id : selectFalse.id, + i != trueIndex ? selectTrue.id : selectFalse.id); + + result = emitDstOperandModifiers(result, ins.modifiers); + emitRegisterStore(ins.dst[i], result); + } + } + + void DxbcCompiler::emitVectorCmp(const DxbcShaderInstruction& ins) { + // Compare instructions have three operands: + // (dst0) The destination register + // (src0) The first vector to compare + // (src1) The second vector to compare + uint32_t componentCount = ins.dst[0].mask.popCount(); + + // For 64-bit operations, we'll return a 32-bit + // vector, so we have to adjust the read mask + DxbcRegMask srcMask = ins.dst[0].mask; + + if (isDoubleType(ins.src[0].dataType)) { + srcMask = DxbcRegMask( + componentCount > 0, componentCount > 0, + componentCount > 1, componentCount > 1); + } + + const std::array src = { + emitRegisterLoad(ins.src[0], srcMask), + emitRegisterLoad(ins.src[1], srcMask), + }; + + // Condition, which is a boolean vector used + // to select between the ~0u and 0u vectors. + uint32_t condition = 0; + uint32_t conditionType = m_module.defBoolType(); + + if (componentCount > 1) + conditionType = m_module.defVectorType(conditionType, componentCount); + + bool invert = false; + + switch (ins.op) { + case DxbcOpcode::Ne: + case DxbcOpcode::DNe: + invert = true; + [[fallthrough]]; + + case DxbcOpcode::Eq: + case DxbcOpcode::DEq: + condition = m_module.opFOrdEqual( + conditionType, src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::Ge: + case DxbcOpcode::DGe: + condition = m_module.opFOrdGreaterThanEqual( + conditionType, src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::Lt: + case DxbcOpcode::DLt: + condition = m_module.opFOrdLessThan( + conditionType, src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::IEq: + condition = m_module.opIEqual( + conditionType, src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::IGe: + condition = m_module.opSGreaterThanEqual( + conditionType, src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::ILt: + condition = m_module.opSLessThan( + conditionType, src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::INe: + condition = m_module.opINotEqual( + conditionType, src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::UGe: + condition = m_module.opUGreaterThanEqual( + conditionType, src.at(0).id, src.at(1).id); + break; + + case DxbcOpcode::ULt: + condition = m_module.opULessThan( + conditionType, src.at(0).id, src.at(1).id); + break; + + default: + Logger::warn(str::format( + "DxbcCompiler: Unhandled instruction: ", + ins.op)); + return; + } + + // Generate constant vectors for selection + uint32_t sFalse = m_module.constu32( 0u); + uint32_t sTrue = m_module.constu32(~0u); + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = componentCount; + + const uint32_t typeId = getVectorTypeId(result.type); + + if (componentCount > 1) { + const std::array vFalse = { sFalse, sFalse, sFalse, sFalse }; + const std::array vTrue = { sTrue, sTrue, sTrue, sTrue }; + + sFalse = m_module.constComposite(typeId, componentCount, vFalse.data()); + sTrue = m_module.constComposite(typeId, componentCount, vTrue .data()); + } + + if (invert) + std::swap(sFalse, sTrue); + + // Perform component-wise mask selection + // based on the condition evaluated above. + result.id = m_module.opSelect( + typeId, condition, sTrue, sFalse); + + emitRegisterStore(ins.dst[0], result); + } + + + void DxbcCompiler::emitVectorDeriv(const DxbcShaderInstruction& ins) { + // Derivative instructions have two operands: + // (dst0) Destination register for the derivative + // (src0) The operand to compute the derivative of + DxbcRegisterValue value = emitRegisterLoad(ins.src[0], ins.dst[0].mask); + const uint32_t typeId = getVectorTypeId(value.type); + + switch (ins.op) { + case DxbcOpcode::DerivRtx: + value.id = m_module.opDpdx(typeId, value.id); + break; + + case DxbcOpcode::DerivRty: + value.id = m_module.opDpdy(typeId, value.id); + break; + + case DxbcOpcode::DerivRtxCoarse: + value.id = m_module.opDpdxCoarse(typeId, value.id); + break; + + case DxbcOpcode::DerivRtyCoarse: + value.id = m_module.opDpdyCoarse(typeId, value.id); + break; + + case DxbcOpcode::DerivRtxFine: + value.id = m_module.opDpdxFine(typeId, value.id); + break; + + case DxbcOpcode::DerivRtyFine: + value.id = m_module.opDpdyFine(typeId, value.id); + break; + + default: + Logger::warn(str::format( + "DxbcCompiler: Unhandled instruction: ", + ins.op)); + return; + } + + value = emitDstOperandModifiers(value, ins.modifiers); + emitRegisterStore(ins.dst[0], value); + } + + + void DxbcCompiler::emitVectorDot(const DxbcShaderInstruction& ins) { + const DxbcRegMask srcMask(true, + ins.op >= DxbcOpcode::Dp2, + ins.op >= DxbcOpcode::Dp3, + ins.op >= DxbcOpcode::Dp4); + + const std::array src = { + emitRegisterLoad(ins.src[0], srcMask), + emitRegisterLoad(ins.src[1], srcMask), + }; + + DxbcRegisterValue dst; + dst.type.ctype = ins.dst[0].dataType; + dst.type.ccount = 1; + dst.id = 0; + + uint32_t componentType = getVectorTypeId(dst.type); + uint32_t componentCount = srcMask.popCount(); + + for (uint32_t i = 0; i < componentCount; i++) { + if (dst.id) { + dst.id = m_module.opFFma(componentType, + m_module.opCompositeExtract(componentType, src.at(0).id, 1, &i), + m_module.opCompositeExtract(componentType, src.at(1).id, 1, &i), + dst.id); + } else { + dst.id = m_module.opFMul(componentType, + m_module.opCompositeExtract(componentType, src.at(0).id, 1, &i), + m_module.opCompositeExtract(componentType, src.at(1).id, 1, &i)); + } + + // Unconditionally mark as precise since the exact order of operation + // matters for some games, even if the instruction itself is not marked + // as precise. + m_module.decorate(dst.id, spv::DecorationNoContraction); + } + + dst = emitDstOperandModifiers(dst, ins.modifiers); + emitRegisterStore(ins.dst[0], dst); + } + + + void DxbcCompiler::emitVectorIdiv(const DxbcShaderInstruction& ins) { + // udiv has four operands: + // (dst0) Quotient destination register + // (dst1) Remainder destination register + // (src0) The first vector to compare + // (src1) The second vector to compare + if (ins.dst[0].type == DxbcOperandType::Null + && ins.dst[1].type == DxbcOperandType::Null) + return; + + // FIXME support this if applications require it + if (ins.dst[0].type != DxbcOperandType::Null + && ins.dst[1].type != DxbcOperandType::Null + && ins.dst[0].mask != ins.dst[1].mask) { + Logger::warn("DxbcCompiler: Idiv with different destination masks not supported"); + return; + } + + // Load source operands as integers with the + // mask of one non-NULL destination operand + const DxbcRegMask srcMask = + ins.dst[0].type != DxbcOperandType::Null + ? ins.dst[0].mask + : ins.dst[1].mask; + + const std::array src = { + emitRegisterLoad(ins.src[0], srcMask), + emitRegisterLoad(ins.src[1], srcMask), + }; + + // Division by zero will return 0xffffffff for both results + auto bvecId = getVectorTypeId({ DxbcScalarType::Bool, srcMask.popCount() }); + + DxbcRegisterValue const0 = emitBuildConstVecu32( 0u, 0u, 0u, 0u, srcMask); + DxbcRegisterValue constff = emitBuildConstVecu32(~0u, ~0u, ~0u, ~0u, srcMask); + + uint32_t cmpValue = m_module.opINotEqual(bvecId, src.at(1).id, const0.id); + + // Compute results only if the destination + // operands are not NULL. + if (ins.dst[0].type != DxbcOperandType::Null) { + DxbcRegisterValue quotient; + quotient.type.ctype = ins.dst[0].dataType; + quotient.type.ccount = ins.dst[0].mask.popCount(); + + quotient.id = m_module.opUDiv( + getVectorTypeId(quotient.type), + src.at(0).id, src.at(1).id); + + quotient.id = m_module.opSelect( + getVectorTypeId(quotient.type), + cmpValue, quotient.id, constff.id); + + quotient = emitDstOperandModifiers(quotient, ins.modifiers); + emitRegisterStore(ins.dst[0], quotient); + } + + if (ins.dst[1].type != DxbcOperandType::Null) { + DxbcRegisterValue remainder; + remainder.type.ctype = ins.dst[1].dataType; + remainder.type.ccount = ins.dst[1].mask.popCount(); + + remainder.id = m_module.opUMod( + getVectorTypeId(remainder.type), + src.at(0).id, src.at(1).id); + + remainder.id = m_module.opSelect( + getVectorTypeId(remainder.type), + cmpValue, remainder.id, constff.id); + + remainder = emitDstOperandModifiers(remainder, ins.modifiers); + emitRegisterStore(ins.dst[1], remainder); + } + } + + + void DxbcCompiler::emitVectorImul(const DxbcShaderInstruction& ins) { + // imul and umul have four operands: + // (dst0) High destination register + // (dst1) Low destination register + // (src0) The first vector to compare + // (src1) The second vector to compare + if (ins.dst[0].type == DxbcOperandType::Null) { + if (ins.dst[1].type == DxbcOperandType::Null) + return; + + // If dst0 is NULL, this instruction behaves just + // like any other three-operand ALU instruction + const std::array src = { + emitRegisterLoad(ins.src[0], ins.dst[1].mask), + emitRegisterLoad(ins.src[1], ins.dst[1].mask), + }; + + DxbcRegisterValue result; + result.type.ctype = ins.dst[1].dataType; + result.type.ccount = ins.dst[1].mask.popCount(); + result.id = m_module.opIMul( + getVectorTypeId(result.type), + src.at(0).id, src.at(1).id); + + result = emitDstOperandModifiers(result, ins.modifiers); + emitRegisterStore(ins.dst[1], result); + } else { + // TODO implement this + Logger::warn("DxbcCompiler: Extended Imul not yet supported"); + } + } + + + void DxbcCompiler::emitVectorMsad(const DxbcShaderInstruction& ins) { + // msad has four operands: + // (dst0) Destination + // (src0) Reference (packed uint8) + // (src1) Source (packed uint8) + // (src2) Accumulator + DxbcRegisterValue refReg = emitRegisterLoad(ins.src[0], ins.dst[0].mask); + DxbcRegisterValue srcReg = emitRegisterLoad(ins.src[1], ins.dst[0].mask); + DxbcRegisterValue result = emitRegisterLoad(ins.src[2], ins.dst[0].mask); + + auto typeId = getVectorTypeId(result.type); + auto bvecId = getVectorTypeId({ DxbcScalarType::Bool, result.type.ccount }); + + for (uint32_t i = 0; i < 4; i++) { + auto shift = m_module.constu32(8 * i); + auto count = m_module.constu32(8); + + auto ref = m_module.opBitFieldUExtract(typeId, refReg.id, shift, count); + auto src = m_module.opBitFieldUExtract(typeId, srcReg.id, shift, count); + + auto zero = emitBuildConstVecu32(0, 0, 0, 0, ins.dst[0].mask); + auto mask = m_module.opINotEqual(bvecId, ref, zero.id); + + auto diff = m_module.opSAbs(typeId, m_module.opISub(typeId, ref, src)); + result.id = m_module.opSelect(typeId, mask, m_module.opIAdd(typeId, result.id, diff), result.id); + } + + result = emitDstOperandModifiers(result, ins.modifiers); + emitRegisterStore(ins.dst[0], result); + } + + + void DxbcCompiler::emitVectorShift(const DxbcShaderInstruction& ins) { + // Shift operations have three operands: + // (dst0) The destination register + // (src0) The register to shift + // (src1) The shift amount (scalar) + DxbcRegisterValue shiftReg = emitRegisterLoad(ins.src[0], ins.dst[0].mask); + DxbcRegisterValue countReg = emitRegisterLoad(ins.src[1], ins.dst[0].mask); + + if (ins.src[1].type != DxbcOperandType::Imm32) + countReg = emitRegisterMaskBits(countReg, 0x1F); + + if (countReg.type.ccount == 1) + countReg = emitRegisterExtend(countReg, shiftReg.type.ccount); + + DxbcRegisterValue result; + result.type.ctype = ins.dst[0].dataType; + result.type.ccount = ins.dst[0].mask.popCount(); + + switch (ins.op) { + case DxbcOpcode::IShl: + result.id = m_module.opShiftLeftLogical( + getVectorTypeId(result.type), + shiftReg.id, countReg.id); + break; + + case DxbcOpcode::IShr: + result.id = m_module.opShiftRightArithmetic( + getVectorTypeId(result.type), + shiftReg.id, countReg.id); + break; + + case DxbcOpcode::UShr: + result.id = m_module.opShiftRightLogical( + getVectorTypeId(result.type), + shiftReg.id, countReg.id); + break; + + default: + Logger::warn(str::format( + "DxbcCompiler: Unhandled instruction: ", + ins.op)); + return; + } + + result = emitDstOperandModifiers(result, ins.modifiers); + emitRegisterStore(ins.dst[0], result); + } + + + void DxbcCompiler::emitVectorSinCos(const DxbcShaderInstruction& ins) { + // sincos has three operands: + // (dst0) Destination register for sin(x) + // (dst1) Destination register for cos(x) + // (src0) Source operand x + + // Load source operand as 32-bit float vector. + const DxbcRegisterValue srcValue = emitRegisterLoad( + ins.src[0], DxbcRegMask(true, true, true, true)); + + uint32_t typeId = getScalarTypeId(srcValue.type.ctype); + + DxbcRegisterValue sinVector = { }; + sinVector.type.ctype = DxbcScalarType::Float32; + + DxbcRegisterValue cosVector = { }; + cosVector.type.ctype = DxbcScalarType::Float32; + + // Only compute sincos for enabled components + std::array sinIds = { }; + std::array cosIds = { }; + + for (uint32_t i = 0; i < 4; i++) { + const uint32_t sinIndex = 0u; + const uint32_t cosIndex = 1u; + + if (ins.dst[0].mask[i] || ins.dst[1].mask[i]) { + uint32_t sincosId = m_module.opSinCos(m_module.opCompositeExtract(typeId, srcValue.id, 1u, &i), !m_moduleInfo.options.sincosEmulation); + + if (ins.dst[0].type != DxbcOperandType::Null && ins.dst[0].mask[i]) + sinIds[sinVector.type.ccount++] = m_module.opCompositeExtract(typeId, sincosId, 1u, &sinIndex); + + if (ins.dst[1].type != DxbcOperandType::Null && ins.dst[1].mask[i]) + cosIds[cosVector.type.ccount++] = m_module.opCompositeExtract(typeId, sincosId, 1u, &cosIndex); + } + } + + if (sinVector.type.ccount) { + sinVector.id = sinVector.type.ccount > 1u + ? m_module.opCompositeConstruct(getVectorTypeId(sinVector.type), sinVector.type.ccount, sinIds.data()) + : sinIds[0]; + + emitRegisterStore(ins.dst[0], sinVector); + } + + if (cosVector.type.ccount) { + cosVector.id = cosVector.type.ccount > 1u + ? m_module.opCompositeConstruct(getVectorTypeId(cosVector.type), cosVector.type.ccount, cosIds.data()) + : cosIds[0]; + + emitRegisterStore(ins.dst[1], cosVector); + } + } + + + void DxbcCompiler::emitGeometryEmit(const DxbcShaderInstruction& ins) { + // In xfb mode we might have multiple streams, so + // we have to figure out which stream to write to + uint32_t streamId = 0; + uint32_t streamVar = 0; + + if (m_moduleInfo.xfb != nullptr) { + streamId = ins.dstCount > 0 ? ins.dst[0].idx[0].offset : 0; + streamVar = m_module.constu32(streamId); + } + + // Checking the negation is easier for EmitThenCut/EmitThenCutStream + bool doEmit = ins.op != DxbcOpcode::Cut && ins.op != DxbcOpcode::CutStream; + bool doCut = ins.op != DxbcOpcode::Emit && ins.op != DxbcOpcode::EmitStream; + + if (doEmit) { + if (m_gs.needsOutputSetup) + emitOutputSetup(); + emitClipCullStore(DxbcSystemValue::ClipDistance, m_clipDistances); + emitClipCullStore(DxbcSystemValue::CullDistance, m_cullDistances); + emitXfbOutputSetup(streamId, false); + m_module.opEmitVertex(streamVar); + } + + if (doCut) + m_module.opEndPrimitive(streamVar); + } + + + void DxbcCompiler::emitAtomic(const DxbcShaderInstruction& ins) { + // atomic_* operations have the following operands: + // (dst0) Destination u# or g# register + // (src0) Index into the texture or buffer + // (src1) The source value for the operation + // (src2) Second source operand (optional) + // imm_atomic_* operations have the following operands: + // (dst0) Register that receives the result + // (dst1) Destination u# or g# register + // (srcX) As above + const DxbcBufferInfo bufferInfo = getBufferInfo(ins.dst[ins.dstCount - 1]); + + bool isImm = ins.dstCount == 2; + bool isUav = ins.dst[ins.dstCount - 1].type == DxbcOperandType::UnorderedAccessView; + bool isSsbo = bufferInfo.isSsbo; + + // Retrieve destination pointer for the atomic operation> + const DxbcRegisterPointer pointer = emitGetAtomicPointer( + ins.dst[ins.dstCount - 1], ins.src[0]); + + // Load source values + std::array src; + + for (uint32_t i = 1; i < ins.srcCount; i++) { + src[i - 1] = emitRegisterBitcast( + emitRegisterLoad(ins.src[i], DxbcRegMask(true, false, false, false)), + pointer.type.ctype); + } + + // Define memory scope and semantics based on the operands + uint32_t scope = 0; + uint32_t semantics = 0; + + if (isUav) { + scope = spv::ScopeQueueFamily; + semantics = spv::MemorySemanticsAcquireReleaseMask; + + semantics |= isSsbo + ? spv::MemorySemanticsUniformMemoryMask + : spv::MemorySemanticsImageMemoryMask; + } else { + scope = spv::ScopeWorkgroup; + semantics = spv::MemorySemanticsWorkgroupMemoryMask + | spv::MemorySemanticsAcquireReleaseMask; + } + + const uint32_t scopeId = m_module.constu32(scope); + const uint32_t semanticsId = m_module.constu32(semantics); + + // Perform the atomic operation on the given pointer + DxbcRegisterValue value; + value.type = pointer.type; + value.id = 0; + + // The result type, which is a scalar integer + const uint32_t typeId = getVectorTypeId(value.type); + + switch (ins.op) { + case DxbcOpcode::AtomicCmpStore: + case DxbcOpcode::ImmAtomicCmpExch: + value.id = m_module.opAtomicCompareExchange( + typeId, pointer.id, scopeId, semanticsId, + m_module.constu32(spv::MemorySemanticsMaskNone), + src[1].id, src[0].id); + break; + + case DxbcOpcode::ImmAtomicExch: + value.id = m_module.opAtomicExchange(typeId, + pointer.id, scopeId, semanticsId, + src[0].id); + break; + + case DxbcOpcode::AtomicIAdd: + case DxbcOpcode::ImmAtomicIAdd: + value.id = m_module.opAtomicIAdd(typeId, + pointer.id, scopeId, semanticsId, + src[0].id); + break; + + case DxbcOpcode::AtomicAnd: + case DxbcOpcode::ImmAtomicAnd: + value.id = m_module.opAtomicAnd(typeId, + pointer.id, scopeId, semanticsId, + src[0].id); + break; + + case DxbcOpcode::AtomicOr: + case DxbcOpcode::ImmAtomicOr: + value.id = m_module.opAtomicOr(typeId, + pointer.id, scopeId, semanticsId, + src[0].id); + break; + + case DxbcOpcode::AtomicXor: + case DxbcOpcode::ImmAtomicXor: + value.id = m_module.opAtomicXor(typeId, + pointer.id, scopeId, semanticsId, + src[0].id); + break; + + case DxbcOpcode::AtomicIMin: + case DxbcOpcode::ImmAtomicIMin: + value.id = m_module.opAtomicSMin(typeId, + pointer.id, scopeId, semanticsId, + src[0].id); + break; + + case DxbcOpcode::AtomicIMax: + case DxbcOpcode::ImmAtomicIMax: + value.id = m_module.opAtomicSMax(typeId, + pointer.id, scopeId, semanticsId, + src[0].id); + break; + + case DxbcOpcode::AtomicUMin: + case DxbcOpcode::ImmAtomicUMin: + value.id = m_module.opAtomicUMin(typeId, + pointer.id, scopeId, semanticsId, + src[0].id); + break; + + case DxbcOpcode::AtomicUMax: + case DxbcOpcode::ImmAtomicUMax: + value.id = m_module.opAtomicUMax(typeId, + pointer.id, scopeId, semanticsId, + src[0].id); + break; + + default: + Logger::warn(str::format( + "DxbcCompiler: Unhandled instruction: ", + ins.op)); + return; + } + + // Write back the result to the destination + // register if this is an imm_atomic_* opcode. + if (isImm) + emitRegisterStore(ins.dst[0], value); + } + + + void DxbcCompiler::emitAtomicCounter(const DxbcShaderInstruction& ins) { + // imm_atomic_alloc and imm_atomic_consume have the following operands: + // (dst0) The register that will hold the old counter value + // (dst1) The UAV whose counter is going to be modified + const uint32_t registerId = ins.dst[1].idx[0].offset; + + if (m_uavs.at(registerId).ctrId == 0) + m_uavs.at(registerId).ctrId = emitDclUavCounter(registerId); + + // Get a pointer to the atomic counter in question + DxbcRegisterInfo ptrType; + ptrType.type.ctype = DxbcScalarType::Uint32; + ptrType.type.ccount = 1; + ptrType.type.alength = 0; + ptrType.sclass = spv::StorageClassStorageBuffer; + + uint32_t zeroId = m_module.consti32(0); + uint32_t ptrId = m_module.opAccessChain( + getPointerTypeId(ptrType), + m_uavs.at(registerId).ctrId, + 1, &zeroId); + + // Define memory scope and semantics based on the operands + uint32_t scope = spv::ScopeQueueFamily; + uint32_t semantics = spv::MemorySemanticsUniformMemoryMask + | spv::MemorySemanticsAcquireReleaseMask; + + uint32_t scopeId = m_module.constu32(scope); + uint32_t semanticsId = m_module.constu32(semantics); + + // Compute the result value + DxbcRegisterValue value; + value.type.ctype = DxbcScalarType::Uint32; + value.type.ccount = 1; + + uint32_t typeId = getVectorTypeId(value.type); + + switch (ins.op) { + case DxbcOpcode::ImmAtomicAlloc: + value.id = m_module.opAtomicIAdd(typeId, ptrId, + scopeId, semanticsId, m_module.constu32(1)); + break; + + case DxbcOpcode::ImmAtomicConsume: + value.id = m_module.opAtomicISub(typeId, ptrId, + scopeId, semanticsId, m_module.constu32(1)); + value.id = m_module.opISub(typeId, value.id, + m_module.constu32(1)); + break; + + default: + Logger::warn(str::format( + "DxbcCompiler: Unhandled instruction: ", + ins.op)); + return; + } + + // Store the result + emitRegisterStore(ins.dst[0], value); + } + + + void DxbcCompiler::emitBarrier(const DxbcShaderInstruction& ins) { + // sync takes no operands. Instead, the synchronization + // scope is defined by the operand control bits. + const DxbcSyncFlags flags = ins.controls.syncFlags(); + + uint32_t executionScope = spv::ScopeInvocation; + uint32_t memoryScope = spv::ScopeInvocation; + uint32_t memorySemantics = 0; + + if (flags.test(DxbcSyncFlag::ThreadsInGroup)) + executionScope = spv::ScopeWorkgroup; + + if (flags.test(DxbcSyncFlag::ThreadGroupSharedMemory)) { + memoryScope = spv::ScopeWorkgroup; + memorySemantics |= spv::MemorySemanticsWorkgroupMemoryMask + | spv::MemorySemanticsAcquireReleaseMask + | spv::MemorySemanticsMakeAvailableMask + | spv::MemorySemanticsMakeVisibleMask; + } + + if (flags.test(DxbcSyncFlag::UavMemoryGroup)) { + memoryScope = spv::ScopeWorkgroup; + memorySemantics |= spv::MemorySemanticsImageMemoryMask + | spv::MemorySemanticsUniformMemoryMask + | spv::MemorySemanticsAcquireReleaseMask + | spv::MemorySemanticsMakeAvailableMask + | spv::MemorySemanticsMakeVisibleMask; + } + + if (flags.test(DxbcSyncFlag::UavMemoryGlobal)) { + memoryScope = spv::ScopeQueueFamily; + + if (m_programInfo.type() == DxbcProgramType::ComputeShader && !m_hasGloballyCoherentUav) + memoryScope = spv::ScopeWorkgroup; + + memorySemantics |= spv::MemorySemanticsImageMemoryMask + | spv::MemorySemanticsUniformMemoryMask + | spv::MemorySemanticsAcquireReleaseMask + | spv::MemorySemanticsMakeAvailableMask + | spv::MemorySemanticsMakeVisibleMask; + } + + if (executionScope != spv::ScopeInvocation) { + m_module.opControlBarrier( + m_module.constu32(executionScope), + m_module.constu32(memoryScope), + m_module.constu32(memorySemantics)); + } else if (memoryScope != spv::ScopeInvocation) { + m_module.opMemoryBarrier( + m_module.constu32(memoryScope), + m_module.constu32(memorySemantics)); + } else { + Logger::warn("DxbcCompiler: sync instruction has no effect"); + } + } + + + void DxbcCompiler::emitBitExtract(const DxbcShaderInstruction& ins) { + // ibfe and ubfe take the following arguments: + // (dst0) The destination register + // (src0) Number of bits to extact + // (src1) Offset of the bits to extract + // (src2) Register to extract bits from + const bool isSigned = ins.op == DxbcOpcode::IBfe; + + DxbcRegisterValue bitCnt = emitRegisterLoad(ins.src[0], ins.dst[0].mask); + DxbcRegisterValue bitOfs = emitRegisterLoad(ins.src[1], ins.dst[0].mask); + + if (ins.src[0].type != DxbcOperandType::Imm32) + bitCnt = emitRegisterMaskBits(bitCnt, 0x1F); + + if (ins.src[1].type != DxbcOperandType::Imm32) + bitOfs = emitRegisterMaskBits(bitOfs, 0x1F); + + const DxbcRegisterValue src = emitRegisterLoad(ins.src[2], ins.dst[0].mask); + + const uint32_t componentCount = src.type.ccount; + std::array componentIds = {{ 0, 0, 0, 0 }}; + + for (uint32_t i = 0; i < componentCount; i++) { + const DxbcRegisterValue currBitCnt = emitRegisterExtract(bitCnt, DxbcRegMask::select(i)); + const DxbcRegisterValue currBitOfs = emitRegisterExtract(bitOfs, DxbcRegMask::select(i)); + const DxbcRegisterValue currSrc = emitRegisterExtract(src, DxbcRegMask::select(i)); + + const uint32_t typeId = getVectorTypeId(currSrc.type); + + componentIds[i] = isSigned + ? m_module.opBitFieldSExtract(typeId, currSrc.id, currBitOfs.id, currBitCnt.id) + : m_module.opBitFieldUExtract(typeId, currSrc.id, currBitOfs.id, currBitCnt.id); + } + + DxbcRegisterValue result; + result.type = src.type; + result.id = componentCount > 1 + ? m_module.opCompositeConstruct( + getVectorTypeId(result.type), + componentCount, componentIds.data()) + : componentIds[0]; + emitRegisterStore(ins.dst[0], result); + } + + + void DxbcCompiler::emitBitInsert(const DxbcShaderInstruction& ins) { + // ibfe and ubfe take the following arguments: + // (dst0) The destination register + // (src0) Number of bits to extact + // (src1) Offset of the bits to extract + // (src2) Register to take bits from + // (src3) Register to replace bits in + DxbcRegisterValue bitCnt = emitRegisterLoad(ins.src[0], ins.dst[0].mask); + DxbcRegisterValue bitOfs = emitRegisterLoad(ins.src[1], ins.dst[0].mask); + + if (ins.src[0].type != DxbcOperandType::Imm32) + bitCnt = emitRegisterMaskBits(bitCnt, 0x1F); + + if (ins.src[1].type != DxbcOperandType::Imm32) + bitOfs = emitRegisterMaskBits(bitOfs, 0x1F); + + const DxbcRegisterValue insert = emitRegisterLoad(ins.src[2], ins.dst[0].mask); + const DxbcRegisterValue base = emitRegisterLoad(ins.src[3], ins.dst[0].mask); + + const uint32_t componentCount = base.type.ccount; + std::array componentIds = {{ 0, 0, 0, 0 }}; + + for (uint32_t i = 0; i < componentCount; i++) { + const DxbcRegisterValue currBitCnt = emitRegisterExtract(bitCnt, DxbcRegMask::select(i)); + const DxbcRegisterValue currBitOfs = emitRegisterExtract(bitOfs, DxbcRegMask::select(i)); + const DxbcRegisterValue currInsert = emitRegisterExtract(insert, DxbcRegMask::select(i)); + const DxbcRegisterValue currBase = emitRegisterExtract(base, DxbcRegMask::select(i)); + + componentIds[i] = m_module.opBitFieldInsert( + getVectorTypeId(currBase.type), + currBase.id, currInsert.id, + currBitOfs.id, currBitCnt.id); + } + + DxbcRegisterValue result; + result.type = base.type; + result.id = componentCount > 1 + ? m_module.opCompositeConstruct( + getVectorTypeId(result.type), + componentCount, componentIds.data()) + : componentIds[0]; + emitRegisterStore(ins.dst[0], result); + } + + + void DxbcCompiler::emitBitScan(const DxbcShaderInstruction& ins) { + // firstbit(lo|hi|shi) have two operands: + // (dst0) The destination operant + // (src0) Source operand to scan + DxbcRegisterValue src = emitRegisterLoad(ins.src[0], ins.dst[0].mask); + + DxbcRegisterValue dst; + dst.type.ctype = ins.dst[0].dataType; + dst.type.ccount = ins.dst[0].mask.popCount(); + + // Result type, should be an unsigned integer + const uint32_t typeId = getVectorTypeId(dst.type); + + switch (ins.op) { + case DxbcOpcode::FirstBitLo: dst.id = m_module.opFindILsb(typeId, src.id); break; + case DxbcOpcode::FirstBitHi: dst.id = m_module.opFindUMsb(typeId, src.id); break; + case DxbcOpcode::FirstBitShi: dst.id = m_module.opFindSMsb(typeId, src.id); break; + default: Logger::warn(str::format("DxbcCompiler: Unhandled instruction: ", ins.op)); return; + } + + // The 'Hi' variants are counted from the MSB in DXBC + // rather than the LSB, so we have to invert the number + if (ins.op == DxbcOpcode::FirstBitHi || ins.op == DxbcOpcode::FirstBitShi) { + uint32_t boolTypeId = m_module.defBoolType(); + + if (dst.type.ccount > 1) + boolTypeId = m_module.defVectorType(boolTypeId, dst.type.ccount); + + DxbcRegisterValue const31 = emitBuildConstVecu32(31u, 31u, 31u, 31u, ins.dst[0].mask); + DxbcRegisterValue constff = emitBuildConstVecu32(~0u, ~0u, ~0u, ~0u, ins.dst[0].mask); + + dst.id = m_module.opSelect(typeId, + m_module.opINotEqual(boolTypeId, dst.id, constff.id), + m_module.opISub(typeId, const31.id, dst.id), + constff.id); + } + + // No modifiers are supported + emitRegisterStore(ins.dst[0], dst); + } + + + void DxbcCompiler::emitBufferQuery(const DxbcShaderInstruction& ins) { + // bufinfo takes two arguments + // (dst0) The destination register + // (src0) The buffer register to query + const DxbcBufferInfo bufferInfo = getBufferInfo(ins.src[0]); + bool isSsbo = bufferInfo.isSsbo; + + // We'll store this as a scalar unsigned integer + DxbcRegisterValue result = isSsbo + ? emitQueryBufferSize(ins.src[0]) + : emitQueryTexelBufferSize(ins.src[0]); + + uint32_t typeId = getVectorTypeId(result.type); + + // Adjust returned size if this is a raw or structured + // buffer, as emitQueryTexelBufferSize only returns the + // number of typed elements in the buffer. + if (bufferInfo.type == DxbcResourceType::Raw) { + result.id = m_module.opIMul(typeId, + result.id, m_module.constu32(4)); + } else if (bufferInfo.type == DxbcResourceType::Structured) { + result.id = m_module.opUDiv(typeId, result.id, + m_module.constu32(bufferInfo.stride / 4)); + } + + // Store the result. The scalar will be extended to a + // vector if the write mask consists of more than one + // component, which is the desired behaviour. + emitRegisterStore(ins.dst[0], result); + } + + + void DxbcCompiler::emitBufferLoad(const DxbcShaderInstruction& ins) { + // ld_raw takes three arguments: + // (dst0) Destination register + // (src0) Byte offset + // (src1) Source register + // ld_structured takes four arguments: + // (dst0) Destination register + // (src0) Structure index + // (src1) Byte offset + // (src2) Source register + const bool isStructured = ins.op == DxbcOpcode::LdStructured + || ins.op == DxbcOpcode::LdStructuredS; + + // Source register. The exact way we access + // the data depends on the register type. + const DxbcRegister& dstReg = ins.dst[0]; + const DxbcRegister& srcReg = isStructured ? ins.src[2] : ins.src[1]; + + if (dstReg.type == DxbcOperandType::UnorderedAccessView) + emitUavBarrier(uint64_t(1u) << srcReg.idx[0].offset, 0u); + + // Retrieve common info about the buffer + const DxbcBufferInfo bufferInfo = getBufferInfo(srcReg); + + // Shared memory is the only type of buffer that + // is not accessed through a texel buffer view + bool isTgsm = srcReg.type == DxbcOperandType::ThreadGroupSharedMemory; + bool isSsbo = bufferInfo.isSsbo; + + // Common types and IDs used while loading the data + uint32_t bufferId = isTgsm || isSsbo ? 0 : m_module.opLoad(bufferInfo.typeId, bufferInfo.varId); + + uint32_t vectorTypeId = getVectorTypeId({ DxbcScalarType::Uint32, 4 }); + uint32_t scalarTypeId = getVectorTypeId({ DxbcScalarType::Uint32, 1 }); + + // Since all data is represented as a sequence of 32-bit + // integers, we have to load each component individually. + std::array ccomps = { 0, 0, 0, 0 }; + std::array scomps = { 0, 0, 0, 0 }; + uint32_t scount = 0; + + // The sparse feedback ID will be non-zero for sparse + // instructions on input. We need to reset it to 0. + SpirvMemoryOperands memoryOperands; + SpirvImageOperands imageOperands; + imageOperands.sparse = ins.dstCount == 2; + + uint32_t coherence = bufferInfo.coherence; + + if (isTgsm && m_moduleInfo.options.forceVolatileTgsmAccess) { + memoryOperands.flags |= spv::MemoryAccessVolatileMask; + coherence = spv::ScopeWorkgroup; + } + + if (coherence) { + memoryOperands.flags |= spv::MemoryAccessNonPrivatePointerMask; + + if (coherence != spv::ScopeInvocation) { + memoryOperands.flags |= spv::MemoryAccessMakePointerVisibleMask; + memoryOperands.makeVisible = m_module.constu32(coherence); + + imageOperands.flags = spv::ImageOperandsNonPrivateTexelMask + | spv::ImageOperandsMakeTexelVisibleMask; + imageOperands.makeVisible = m_module.constu32(coherence); + } + } + + uint32_t sparseFeedbackId = 0; + + bool useRawAccessChains = m_hasRawAccessChains && isSsbo && !imageOperands.sparse; + + DxbcRegisterValue index = emitRegisterLoad(ins.src[0], DxbcRegMask(true, false, false, false)); + DxbcRegisterValue offset = index; + + if (isStructured) + offset = emitRegisterLoad(ins.src[1], DxbcRegMask(true, false, false, false)); + + DxbcRegisterValue elementIndex = { }; + + uint32_t baseAlignment = sizeof(uint32_t); + + if (useRawAccessChains) { + memoryOperands.flags |= spv::MemoryAccessAlignedMask; + + if (isStructured && ins.src[1].type == DxbcOperandType::Imm32) { + baseAlignment = bufferInfo.stride | ins.src[1].imm.u32_1; + baseAlignment = baseAlignment & -baseAlignment; + baseAlignment = std::min(baseAlignment, uint32_t(m_moduleInfo.options.minSsboAlignment)); + } + } else { + elementIndex = isStructured + ? emitCalcBufferIndexStructured(index, offset, bufferInfo.stride) + : emitCalcBufferIndexRaw(offset); + } + + uint32_t readMask = 0u; + + for (uint32_t i = 0; i < 4; i++) { + if (dstReg.mask[i]) + readMask |= 1u << srcReg.swizzle[i]; + } + + while (readMask) { + uint32_t sindex = bit::tzcnt(readMask); + uint32_t scount = bit::tzcnt(~(readMask >> sindex)); + uint32_t zero = 0; + + if (useRawAccessChains) { + uint32_t alignment = baseAlignment; + uint32_t offsetId = offset.id; + + if (sindex) { + offsetId = m_module.opIAdd(scalarTypeId, + offsetId, m_module.constu32(sizeof(uint32_t) * sindex)); + alignment |= sizeof(uint32_t) * sindex; + } + + DxbcRegisterInfo storeInfo; + storeInfo.type.ctype = DxbcScalarType::Uint32; + storeInfo.type.ccount = scount; + storeInfo.type.alength = 0; + storeInfo.sclass = spv::StorageClassStorageBuffer; + + uint32_t loadTypeId = getArrayTypeId(storeInfo.type); + uint32_t ptrTypeId = getPointerTypeId(storeInfo); + + uint32_t accessChain = isStructured + ? m_module.opRawAccessChain(ptrTypeId, bufferInfo.varId, + m_module.constu32(bufferInfo.stride), index.id, offsetId, + spv::RawAccessChainOperandsRobustnessPerElementNVMask) + : m_module.opRawAccessChain(ptrTypeId, bufferInfo.varId, + m_module.constu32(0), m_module.constu32(0), offsetId, + spv::RawAccessChainOperandsRobustnessPerComponentNVMask); + + memoryOperands.alignment = alignment & -alignment; + + uint32_t vectorId = m_module.opLoad(loadTypeId, accessChain, memoryOperands); + + for (uint32_t i = 0; i < scount; i++) { + ccomps[sindex + i] = vectorId; + + if (scount > 1) { + ccomps[sindex + i] = m_module.opCompositeExtract( + scalarTypeId, vectorId, 1, &i); + } + } + + readMask &= ~(((1u << scount) - 1u) << sindex); + } else { + uint32_t elementIndexAdjusted = m_module.opIAdd( + getVectorTypeId(elementIndex.type), elementIndex.id, + m_module.consti32(sindex)); + + if (isTgsm) { + ccomps[sindex] = m_module.opLoad(scalarTypeId, + m_module.opAccessChain(bufferInfo.typeId, + bufferInfo.varId, 1, &elementIndexAdjusted), + memoryOperands); + } else if (isSsbo) { + uint32_t indices[2] = { m_module.constu32(0), elementIndexAdjusted }; + ccomps[sindex] = m_module.opLoad(scalarTypeId, + m_module.opAccessChain(bufferInfo.typeId, + bufferInfo.varId, 2, indices), + memoryOperands); + } else { + uint32_t resultTypeId = vectorTypeId; + uint32_t resultId = 0; + + if (imageOperands.sparse) + resultTypeId = getSparseResultTypeId(vectorTypeId); + + if (srcReg.type == DxbcOperandType::Resource) { + resultId = m_module.opImageFetch(resultTypeId, + bufferId, elementIndexAdjusted, imageOperands); + } else if (srcReg.type == DxbcOperandType::UnorderedAccessView) { + resultId = m_module.opImageRead(resultTypeId, + bufferId, elementIndexAdjusted, imageOperands); + } else { + throw DxvkError("DxbcCompiler: Invalid operand type for strucured/raw load"); + } + + // Only read sparse feedback once. This may be somewhat inaccurate + // for reads that straddle pages, but we can't easily emulate this. + if (imageOperands.sparse) { + imageOperands.sparse = false; + sparseFeedbackId = resultId; + + resultId = emitExtractSparseTexel(vectorTypeId, resultId); + } + + ccomps[sindex] = m_module.opCompositeExtract(scalarTypeId, resultId, 1, &zero); + } + + readMask &= readMask - 1; + } + } + + for (uint32_t i = 0; i < 4; i++) { + uint32_t sindex = srcReg.swizzle[i]; + + if (dstReg.mask[i]) + scomps[scount++] = ccomps[sindex]; + } + + DxbcRegisterValue result = { }; + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = scount; + result.id = scomps[0]; + + if (scount > 1) { + result.id = m_module.opCompositeConstruct( + getVectorTypeId(result.type), + scount, scomps.data()); + } + + emitRegisterStore(dstReg, result); + + if (sparseFeedbackId) + emitStoreSparseFeedback(ins.dst[1], sparseFeedbackId); + } + + + void DxbcCompiler::emitBufferStore(const DxbcShaderInstruction& ins) { + // store_raw takes three arguments: + // (dst0) Destination register + // (src0) Byte offset + // (src1) Source register + // store_structured takes four arguments: + // (dst0) Destination register + // (src0) Structure index + // (src1) Byte offset + // (src2) Source register + const bool isStructured = ins.op == DxbcOpcode::StoreStructured; + + // Source register. The exact way we access + // the data depends on the register type. + const DxbcRegister& dstReg = ins.dst[0]; + const DxbcRegister& srcReg = isStructured ? ins.src[2] : ins.src[1]; + + if (dstReg.type == DxbcOperandType::UnorderedAccessView) + emitUavBarrier(0u, uint64_t(1u) << dstReg.idx[0].offset); + + DxbcRegisterValue value = emitRegisterLoad(srcReg, dstReg.mask); + value = emitRegisterBitcast(value, DxbcScalarType::Uint32); + + // Retrieve common info about the buffer + const DxbcBufferInfo bufferInfo = getBufferInfo(dstReg); + + // Thread Group Shared Memory is not accessed through a texel buffer view + bool isTgsm = dstReg.type == DxbcOperandType::ThreadGroupSharedMemory; + bool isSsbo = bufferInfo.isSsbo; + + uint32_t bufferId = isTgsm || isSsbo ? 0 : m_module.opLoad(bufferInfo.typeId, bufferInfo.varId); + + uint32_t scalarTypeId = getVectorTypeId({ DxbcScalarType::Uint32, 1 }); + uint32_t vectorTypeId = getVectorTypeId({ DxbcScalarType::Uint32, 4 }); + + // Set memory operands according to resource properties + SpirvMemoryOperands memoryOperands; + SpirvImageOperands imageOperands; + + uint32_t coherence = bufferInfo.coherence; + + if (isTgsm && m_moduleInfo.options.forceVolatileTgsmAccess) { + memoryOperands.flags |= spv::MemoryAccessVolatileMask; + coherence = spv::ScopeWorkgroup; + } + + if (coherence) { + memoryOperands.flags |= spv::MemoryAccessNonPrivatePointerMask; + + if (coherence != spv::ScopeInvocation) { + memoryOperands.flags |= spv::MemoryAccessMakePointerAvailableMask; + memoryOperands.makeAvailable = m_module.constu32(coherence); + + imageOperands.flags = spv::ImageOperandsNonPrivateTexelMask + | spv::ImageOperandsMakeTexelAvailableMask; + imageOperands.makeAvailable = m_module.constu32(coherence); + } + } + + // Compute flat element index as necessary + bool useRawAccessChains = isSsbo && m_hasRawAccessChains; + + DxbcRegisterValue index = emitRegisterLoad(ins.src[0], DxbcRegMask(true, false, false, false)); + DxbcRegisterValue offset = index; + + if (isStructured) + offset = emitRegisterLoad(ins.src[1], DxbcRegMask(true, false, false, false)); + + DxbcRegisterValue elementIndex = { }; + + uint32_t baseAlignment = sizeof(uint32_t); + + if (useRawAccessChains) { + memoryOperands.flags |= spv::MemoryAccessAlignedMask; + + if (isStructured && ins.src[1].type == DxbcOperandType::Imm32) { + baseAlignment = bufferInfo.stride | ins.src[1].imm.u32_1; + baseAlignment = baseAlignment & -baseAlignment; + baseAlignment = std::min(baseAlignment, uint32_t(m_moduleInfo.options.minSsboAlignment)); + } + } else { + elementIndex = isStructured + ? emitCalcBufferIndexStructured(index, offset, bufferInfo.stride) + : emitCalcBufferIndexRaw(offset); + } + + uint32_t writeMask = dstReg.mask.raw(); + + while (writeMask) { + uint32_t sindex = bit::tzcnt(writeMask); + uint32_t scount = bit::tzcnt(~(writeMask >> sindex)); + + if (useRawAccessChains) { + uint32_t alignment = baseAlignment; + uint32_t offsetId = offset.id; + + if (sindex) { + offsetId = m_module.opIAdd(scalarTypeId, + offsetId, m_module.constu32(sizeof(uint32_t) * sindex)); + alignment = alignment | (sizeof(uint32_t) * sindex); + } + + DxbcRegisterInfo storeInfo; + storeInfo.type.ctype = DxbcScalarType::Uint32; + storeInfo.type.ccount = scount; + storeInfo.type.alength = 0; + storeInfo.sclass = spv::StorageClassStorageBuffer; + + uint32_t storeTypeId = getArrayTypeId(storeInfo.type); + uint32_t ptrTypeId = getPointerTypeId(storeInfo); + + uint32_t accessChain = isStructured + ? m_module.opRawAccessChain(ptrTypeId, bufferInfo.varId, + m_module.constu32(bufferInfo.stride), index.id, offsetId, + spv::RawAccessChainOperandsRobustnessPerElementNVMask) + : m_module.opRawAccessChain(ptrTypeId, bufferInfo.varId, + m_module.constu32(0), m_module.constu32(0), offsetId, + spv::RawAccessChainOperandsRobustnessPerComponentNVMask); + + uint32_t valueId = value.id; + + if (scount < value.type.ccount) { + if (scount == 1) { + valueId = m_module.opCompositeExtract(storeTypeId, value.id, 1, &sindex); + } else { + std::array indices = { sindex, sindex + 1u, sindex + 2u, sindex + 3u }; + valueId = m_module.opVectorShuffle(storeTypeId, value.id, value.id, scount, indices.data()); + } + } + + memoryOperands.alignment = alignment & -alignment; + m_module.opStore(accessChain, valueId, memoryOperands); + + writeMask &= ~(((1u << scount) - 1u) << sindex); + } else { + uint32_t srcComponentId = value.type.ccount > 1 + ? m_module.opCompositeExtract(scalarTypeId, + value.id, 1, &sindex) + : value.id; + + uint32_t elementIndexAdjusted = sindex != 0 + ? m_module.opIAdd(getVectorTypeId(elementIndex.type), + elementIndex.id, m_module.consti32(sindex)) + : elementIndex.id; + + if (isTgsm) { + m_module.opStore( + m_module.opAccessChain(bufferInfo.typeId, + bufferInfo.varId, 1, &elementIndexAdjusted), + srcComponentId, memoryOperands); + } else if (isSsbo) { + uint32_t indices[2] = { m_module.constu32(0), elementIndexAdjusted }; + m_module.opStore( + m_module.opAccessChain(bufferInfo.typeId, + bufferInfo.varId, 2, indices), + srcComponentId, memoryOperands); + } else if (dstReg.type == DxbcOperandType::UnorderedAccessView) { + const std::array srcVectorIds = { + srcComponentId, srcComponentId, + srcComponentId, srcComponentId, + }; + + m_module.opImageWrite( + bufferId, elementIndexAdjusted, + m_module.opCompositeConstruct(vectorTypeId, + 4, srcVectorIds.data()), + imageOperands); + } else { + throw DxvkError("DxbcCompiler: Invalid operand type for strucured/raw store"); + } + + writeMask &= writeMask - 1u; + } + } + } + + + void DxbcCompiler::emitConvertFloat16(const DxbcShaderInstruction& ins) { + // f32tof16 takes two operands: + // (dst0) Destination register as a uint32 vector + // (src0) Source register as a float32 vector + // f16tof32 takes two operands: + // (dst0) Destination register as a float32 vector + // (src0) Source register as a uint32 vector + const DxbcRegisterValue src = emitRegisterLoad(ins.src[0], ins.dst[0].mask); + + // We handle both packing and unpacking here + const bool isPack = ins.op == DxbcOpcode::F32toF16; + + // The conversion instructions do not map very well to the + // SPIR-V pack instructions, which operate on 2D vectors. + std::array scalarIds = {{ 0, 0, 0, 0 }}; + + const uint32_t componentCount = src.type.ccount; + + // These types are used in both pack and unpack operations + const uint32_t t_u32 = getVectorTypeId({ DxbcScalarType::Uint32, 1 }); + const uint32_t t_f32 = getVectorTypeId({ DxbcScalarType::Float32, 1 }); + const uint32_t t_f32v2 = getVectorTypeId({ DxbcScalarType::Float32, 2 }); + + // Constant zero-bit pattern, used for packing + const uint32_t zerof32 = isPack ? m_module.constf32(0.0f) : 0; + + for (uint32_t i = 0; i < componentCount; i++) { + const DxbcRegisterValue componentValue + = emitRegisterExtract(src, DxbcRegMask::select(i)); + + if (isPack) { // f32tof16 + const std::array packIds = + {{ componentValue.id, zerof32 }}; + + scalarIds[i] = m_module.opPackHalf2x16(t_u32, + m_module.opCompositeConstruct(t_f32v2, packIds.size(), packIds.data())); + } else { // f16tof32 + const uint32_t zeroIndex = 0; + + scalarIds[i] = m_module.opCompositeExtract(t_f32, + m_module.opUnpackHalf2x16(t_f32v2, componentValue.id), + 1, &zeroIndex); + } + } + + DxbcRegisterValue result; + result.type.ctype = ins.dst[0].dataType; + result.type.ccount = componentCount; + + uint32_t typeId = getVectorTypeId(result.type); + result.id = componentCount > 1 + ? m_module.opCompositeConstruct(typeId, + componentCount, scalarIds.data()) + : scalarIds[0]; + + if (isPack) { + // Some drivers return infinity if the input value is above a certain + // threshold, but D3D wants us to return infinity only if the input is + // actually infinite. Fix this up to return the maximum representable + // 16-bit floating point number instead, but preserve input infinity. + uint32_t t_bvec = getVectorTypeId({ DxbcScalarType::Bool, componentCount }); + uint32_t f16Infinity = m_module.constuReplicant(0x7C00, componentCount); + uint32_t f16Unsigned = m_module.constuReplicant(0x7FFF, componentCount); + + uint32_t isInputInf = m_module.opIsInf(t_bvec, src.id); + uint32_t isValueInf = m_module.opIEqual(t_bvec, f16Infinity, + m_module.opBitwiseAnd(typeId, result.id, f16Unsigned)); + + result.id = m_module.opSelect(getVectorTypeId(result.type), + m_module.opLogicalAnd(t_bvec, isValueInf, m_module.opLogicalNot(t_bvec, isInputInf)), + m_module.opISub(typeId, result.id, m_module.constuReplicant(1, componentCount)), + result.id); + } + + // Store result in the destination register + emitRegisterStore(ins.dst[0], result); + } + + + void DxbcCompiler::emitConvertFloat64(const DxbcShaderInstruction& ins) { + // ftod and dtof take the following operands: + // (dst0) Destination operand + // (src0) Number to convert + uint32_t dstBits = ins.dst[0].mask.popCount(); + + DxbcRegMask srcMask = isDoubleType(ins.dst[0].dataType) + ? DxbcRegMask(dstBits >= 2, dstBits >= 4, false, false) + : DxbcRegMask(dstBits >= 1, dstBits >= 1, dstBits >= 2, dstBits >= 2); + + // Perform actual conversion, destination modifiers are not applied + DxbcRegisterValue val = emitRegisterLoad(ins.src[0], srcMask); + + DxbcRegisterValue result; + result.type.ctype = ins.dst[0].dataType; + result.type.ccount = val.type.ccount; + + switch (ins.op) { + case DxbcOpcode::DtoF: + case DxbcOpcode::FtoD: + result.id = m_module.opFConvert( + getVectorTypeId(result.type), val.id); + break; + + case DxbcOpcode::DtoI: + result.id = m_module.opConvertFtoS( + getVectorTypeId(result.type), val.id); + break; + + case DxbcOpcode::DtoU: + result.id = m_module.opConvertFtoU( + getVectorTypeId(result.type), val.id); + break; + + case DxbcOpcode::ItoD: + result.id = m_module.opConvertStoF( + getVectorTypeId(result.type), val.id); + break; + + case DxbcOpcode::UtoD: + result.id = m_module.opConvertUtoF( + getVectorTypeId(result.type), val.id); + break; + + default: + Logger::warn(str::format("DxbcCompiler: Unhandled instruction: ", ins.op)); + return; + } + + emitRegisterStore(ins.dst[0], result); + } + + + void DxbcCompiler::emitHullShaderInstCnt(const DxbcShaderInstruction& ins) { + this->getCurrentHsForkJoinPhase()->instanceCount = ins.imm[0].u32; + } + + + void DxbcCompiler::emitHullShaderPhase(const DxbcShaderInstruction& ins) { + switch (ins.op) { + case DxbcOpcode::HsDecls: { + if (m_hs.currPhaseType != DxbcCompilerHsPhase::None) + Logger::err("DXBC: HsDecls not the first phase in hull shader"); + + m_hs.currPhaseType = DxbcCompilerHsPhase::Decl; + } break; + + case DxbcOpcode::HsControlPointPhase: { + m_hs.cpPhase = this->emitNewHullShaderControlPointPhase(); + + m_hs.currPhaseType = DxbcCompilerHsPhase::ControlPoint; + m_hs.currPhaseId = 0; + + m_module.setDebugName(m_hs.cpPhase.functionId, "hs_control_point"); + } break; + + case DxbcOpcode::HsForkPhase: { + auto phase = this->emitNewHullShaderForkJoinPhase(); + m_hs.forkPhases.push_back(phase); + + m_hs.currPhaseType = DxbcCompilerHsPhase::Fork; + m_hs.currPhaseId = m_hs.forkPhases.size() - 1; + + m_module.setDebugName(phase.functionId, + str::format("hs_fork_", m_hs.currPhaseId).c_str()); + } break; + + case DxbcOpcode::HsJoinPhase: { + auto phase = this->emitNewHullShaderForkJoinPhase(); + m_hs.joinPhases.push_back(phase); + + m_hs.currPhaseType = DxbcCompilerHsPhase::Join; + m_hs.currPhaseId = m_hs.joinPhases.size() - 1; + + m_module.setDebugName(phase.functionId, + str::format("hs_join_", m_hs.currPhaseId).c_str()); + } break; + + default: + Logger::warn(str::format( + "DxbcCompiler: Unhandled instruction: ", + ins.op)); + } + } + + + void DxbcCompiler::emitInterpolate(const DxbcShaderInstruction& ins) { + m_module.enableCapability(spv::CapabilityInterpolationFunction); + + // The SPIR-V instructions operate on input variable pointers, + // which are all declared as four-component float vectors. + uint32_t registerId = ins.src[0].idx[0].offset; + + DxbcRegisterValue result; + result.type = getInputRegType(registerId); + + switch (ins.op) { + case DxbcOpcode::EvalCentroid: { + result.id = m_module.opInterpolateAtCentroid( + getVectorTypeId(result.type), + m_vRegs.at(registerId).id); + } break; + + case DxbcOpcode::EvalSampleIndex: { + const DxbcRegisterValue sampleIndex = emitRegisterLoad( + ins.src[1], DxbcRegMask(true, false, false, false)); + + result.id = m_module.opInterpolateAtSample( + getVectorTypeId(result.type), + m_vRegs.at(registerId).id, + sampleIndex.id); + } break; + + case DxbcOpcode::EvalSnapped: { + // The offset is encoded as a 4-bit fixed point value + DxbcRegisterValue offset = emitRegisterLoad( + ins.src[1], DxbcRegMask(true, true, false, false)); + offset.id = m_module.opBitFieldSExtract( + getVectorTypeId(offset.type), offset.id, + m_module.consti32(0), m_module.consti32(4)); + + offset.type.ctype = DxbcScalarType::Float32; + offset.id = m_module.opConvertStoF( + getVectorTypeId(offset.type), offset.id); + + offset.id = m_module.opFMul( + getVectorTypeId(offset.type), offset.id, + m_module.constvec2f32(1.0f / 16.0f, 1.0f / 16.0f)); + + result.id = m_module.opInterpolateAtOffset( + getVectorTypeId(result.type), + m_vRegs.at(registerId).id, + offset.id); + } break; + + default: + Logger::warn(str::format( + "DxbcCompiler: Unhandled instruction: ", + ins.op)); + return; + } + + result = emitRegisterSwizzle(result, + ins.src[0].swizzle, ins.dst[0].mask); + emitRegisterStore(ins.dst[0], result); + } + + + void DxbcCompiler::emitSparseCheckAccess( + const DxbcShaderInstruction& ins) { + // check_access_mapped has two operands: + // (dst0) The destination register + // (src0) The residency code + m_module.enableCapability(spv::CapabilitySparseResidency); + + DxbcRegisterValue srcValue = emitRegisterLoad(ins.src[0], ins.dst[0].mask); + + uint32_t boolId = m_module.opImageSparseTexelsResident( + m_module.defBoolType(), srcValue.id); + + DxbcRegisterValue dstValue; + dstValue.type = { DxbcScalarType::Uint32, 1 }; + dstValue.id = m_module.opSelect(getScalarTypeId(DxbcScalarType::Uint32), + boolId, m_module.constu32(~0u), m_module.constu32(0)); + + emitRegisterStore(ins.dst[0], dstValue); + } + + + void DxbcCompiler::emitTextureQuery(const DxbcShaderInstruction& ins) { + // resinfo has three operands: + // (dst0) The destination register + // (src0) Resource LOD to query + // (src1) Resource to query + const DxbcBufferInfo resourceInfo = getBufferInfo(ins.src[1]); + const DxbcResinfoType resinfoType = ins.controls.resinfoType(); + + // Read the exact LOD for the image query + const DxbcRegisterValue mipLod = emitRegisterLoad( + ins.src[0], DxbcRegMask(true, false, false, false)); + + const DxbcScalarType returnType = resinfoType == DxbcResinfoType::Uint + ? DxbcScalarType::Uint32 : DxbcScalarType::Float32; + + // Query the size of the selected mip level, as well as the + // total number of mip levels. We will have to combine the + // result into a four-component vector later. + DxbcRegisterValue imageSize = emitQueryTextureSize(ins.src[1], mipLod); + DxbcRegisterValue imageLevels = emitQueryTextureLods(ins.src[1]); + + // If the mip level is out of bounds, D3D requires us to return + // zero before applying modifiers, whereas SPIR-V is undefined, + // so we need to fix it up manually here. + imageSize.id = m_module.opSelect(getVectorTypeId(imageSize.type), + m_module.opULessThan(m_module.defBoolType(), mipLod.id, imageLevels.id), + imageSize.id, emitBuildZeroVector(imageSize.type).id); + + // Convert intermediates to the requested type + if (returnType == DxbcScalarType::Float32) { + imageSize.type.ctype = DxbcScalarType::Float32; + imageSize.id = m_module.opConvertUtoF( + getVectorTypeId(imageSize.type), + imageSize.id); + + imageLevels.type.ctype = DxbcScalarType::Float32; + imageLevels.id = m_module.opConvertUtoF( + getVectorTypeId(imageLevels.type), + imageLevels.id); + } + + // If the selected return type is rcpFloat, we need + // to compute the reciprocal of the image dimensions, + // but not the array size, so we need to separate it. + const uint32_t imageCoordDim = imageSize.type.ccount; + + DxbcRegisterValue imageLayers; + imageLayers.type = imageSize.type; + imageLayers.id = 0; + + if (resinfoType == DxbcResinfoType::RcpFloat && resourceInfo.image.array) { + imageLayers = emitRegisterExtract(imageSize, DxbcRegMask::select(imageCoordDim - 1)); + imageSize = emitRegisterExtract(imageSize, DxbcRegMask::firstN(imageCoordDim - 1)); + } + + if (resinfoType == DxbcResinfoType::RcpFloat) { + imageSize.id = m_module.opFDiv( + getVectorTypeId(imageSize.type), + emitBuildConstVecf32(1.0f, 1.0f, 1.0f, 1.0f, + DxbcRegMask::firstN(imageSize.type.ccount)).id, + imageSize.id); + } + + // Concatenate result vectors and scalars to form a + // 4D vector. Unused components will be set to zero. + std::array vectorIds = { imageSize.id, 0, 0, 0 }; + uint32_t numVectorIds = 1; + + if (imageLayers.id != 0) + vectorIds[numVectorIds++] = imageLayers.id; + + if (imageCoordDim < 3) { + const uint32_t zero = returnType == DxbcScalarType::Uint32 + ? m_module.constu32(0) + : m_module.constf32(0.0f); + + for (uint32_t i = imageCoordDim; i < 3; i++) + vectorIds[numVectorIds++] = zero; + } + + vectorIds[numVectorIds++] = imageLevels.id; + + // Create the actual result vector + DxbcRegisterValue result; + result.type.ctype = returnType; + result.type.ccount = 4; + result.id = m_module.opCompositeConstruct( + getVectorTypeId(result.type), + numVectorIds, vectorIds.data()); + + // Swizzle components using the resource swizzle + // and the destination operand's write mask + result = emitRegisterSwizzle(result, + ins.src[1].swizzle, ins.dst[0].mask); + emitRegisterStore(ins.dst[0], result); + } + + + void DxbcCompiler::emitTextureQueryLod(const DxbcShaderInstruction& ins) { + // All sample instructions have at least these operands: + // (dst0) The destination register + // (src0) Texture coordinates + // (src1) The texture itself + // (src2) The sampler object + const DxbcRegister& texCoordReg = ins.src[0]; + const DxbcRegister& textureReg = ins.src[1]; + const DxbcRegister& samplerReg = ins.src[2]; + + // Texture and sampler register IDs + const auto& texture = m_textures.at(textureReg.idx[0].offset); + const auto& sampler = m_samplers.at(samplerReg.idx[0].offset); + + // Load texture coordinates + const DxbcRegisterValue coord = emitRegisterLoad(texCoordReg, + DxbcRegMask::firstN(getTexLayerDim(texture.imageInfo))); + + // Query the LOD. The result is a two-dimensional float32 + // vector containing the mip level and virtual LOD numbers. + const uint32_t sampledImageId = emitLoadSampledImage(texture, sampler, false); + const uint32_t queriedLodId = m_module.opImageQueryLod( + getVectorTypeId({ DxbcScalarType::Float32, 2 }), + sampledImageId, coord.id); + + // Build the result array vector by filling up + // the remaining two components with zeroes. + const uint32_t zero = m_module.constf32(0.0f); + const std::array resultIds + = {{ queriedLodId, zero, zero }}; + + DxbcRegisterValue result; + result.type = DxbcVectorType { DxbcScalarType::Float32, 4 }; + result.id = m_module.opCompositeConstruct( + getVectorTypeId(result.type), + resultIds.size(), resultIds.data()); + + result = emitRegisterSwizzle(result, ins.src[1].swizzle, ins.dst[0].mask); + emitRegisterStore(ins.dst[0], result); + } + + + void DxbcCompiler::emitTextureQueryMs(const DxbcShaderInstruction& ins) { + // sampleinfo has two operands: + // (dst0) The destination register + // (src0) Resource to query + DxbcRegisterValue sampleCount = emitQueryTextureSamples(ins.src[0]); + + if (ins.controls.returnType() != DxbcInstructionReturnType::Uint) { + sampleCount.type = { DxbcScalarType::Float32, 1 }; + sampleCount.id = m_module.opConvertUtoF( + getVectorTypeId(sampleCount.type), + sampleCount.id); + } + + emitRegisterStore(ins.dst[0], sampleCount); + } + + + void DxbcCompiler::emitTextureQueryMsPos(const DxbcShaderInstruction& ins) { + // samplepos has three operands: + // (dst0) The destination register + // (src0) Resource to query + // (src1) Sample index + if (m_samplePositions == 0) + m_samplePositions = emitSamplePosArray(); + + // The lookup index is qual to the sample count plus the + // sample index, or 0 if the resource cannot be queried. + DxbcRegisterValue sampleCount = emitQueryTextureSamples(ins.src[0]); + DxbcRegisterValue sampleIndex = emitRegisterLoad( + ins.src[1], DxbcRegMask(true, false, false, false)); + + uint32_t lookupIndex = m_module.opIAdd( + getVectorTypeId(sampleCount.type), + sampleCount.id, sampleIndex.id); + + // Validate the parameters + uint32_t sampleCountValid = m_module.opULessThanEqual( + m_module.defBoolType(), + sampleCount.id, + m_module.constu32(16)); + + uint32_t sampleIndexValid = m_module.opULessThan( + m_module.defBoolType(), + sampleIndex.id, + sampleCount.id); + + // If the lookup cannot be performed, set the lookup + // index to zero, which will return a zero vector. + lookupIndex = m_module.opSelect( + getVectorTypeId(sampleCount.type), + m_module.opLogicalAnd( + m_module.defBoolType(), + sampleCountValid, + sampleIndexValid), + lookupIndex, + m_module.constu32(0)); + + // Load sample pos vector and write the masked + // components to the destination register. + DxbcRegisterPointer samplePos; + samplePos.type.ctype = DxbcScalarType::Float32; + samplePos.type.ccount = 2; + samplePos.id = m_module.opAccessChain( + m_module.defPointerType( + getVectorTypeId(samplePos.type), + spv::StorageClassPrivate), + m_samplePositions, 1, &lookupIndex); + + // Expand to vec4 by appending zeroes + DxbcRegisterValue result = emitValueLoad(samplePos); + + DxbcRegisterValue zero; + zero.type.ctype = DxbcScalarType::Float32; + zero.type.ccount = 2; + zero.id = m_module.constvec2f32(0.0f, 0.0f); + + result = emitRegisterConcat(result, zero); + + emitRegisterStore(ins.dst[0], + emitRegisterSwizzle(result, + ins.src[0].swizzle, + ins.dst[0].mask)); + } + + + void DxbcCompiler::emitTextureFetch(const DxbcShaderInstruction& ins) { + // ld has three operands: + // (dst0) The destination register + // (src0) Source address + // (src1) Source texture + // ld2dms has four operands: + // (dst0) The destination register + // (src0) Source address + // (src1) Source texture + // (src2) Sample number + const auto& texture = m_textures.at(ins.src[1].idx[0].offset); + const uint32_t imageLayerDim = getTexLayerDim(texture.imageInfo); + + bool isMultisampled = ins.op == DxbcOpcode::LdMs + || ins.op == DxbcOpcode::LdMsS; + + // Load the texture coordinates. The last component + // contains the LOD if the resource is an image. + const DxbcRegisterValue address = emitRegisterLoad( + ins.src[0], DxbcRegMask(true, true, true, true)); + + // Additional image operands. This will store + // the LOD and the address offset if present. + SpirvImageOperands imageOperands; + imageOperands.sparse = ins.dstCount == 2; + + if (ins.sampleControls.u != 0 || ins.sampleControls.v != 0 || ins.sampleControls.w != 0) { + const std::array offsetIds = { + imageLayerDim >= 1 ? m_module.consti32(ins.sampleControls.u) : 0, + imageLayerDim >= 2 ? m_module.consti32(ins.sampleControls.v) : 0, + imageLayerDim >= 3 ? m_module.consti32(ins.sampleControls.w) : 0, + }; + + imageOperands.flags |= spv::ImageOperandsConstOffsetMask; + imageOperands.sConstOffset = offsetIds[0]; + + if (imageLayerDim > 1) { + imageOperands.sConstOffset = m_module.constComposite( + getVectorTypeId({ DxbcScalarType::Sint32, imageLayerDim }), + imageLayerDim, offsetIds.data()); + } + } + + // The LOD is not present when reading from + // a buffer or from a multisample texture. + if (texture.imageInfo.dim != spv::DimBuffer && texture.imageInfo.ms == 0) { + DxbcRegisterValue imageLod; + + if (!isMultisampled) { + imageLod = emitRegisterExtract( + address, DxbcRegMask(false, false, false, true)); + } else { + // If we force-disabled MSAA, fetch from LOD 0 + imageLod.type = { DxbcScalarType::Uint32, 1 }; + imageLod.id = m_module.constu32(0); + } + + imageOperands.flags |= spv::ImageOperandsLodMask; + imageOperands.sLod = imageLod.id; + } + + // The ld2dms instruction has a sample index, but we + // are only allowed to set it for multisample views + if (isMultisampled && texture.imageInfo.ms == 1) { + DxbcRegisterValue sampleId = emitRegisterLoad( + ins.src[2], DxbcRegMask(true, false, false, false)); + + imageOperands.flags |= spv::ImageOperandsSampleMask; + imageOperands.sSampleId = sampleId.id; + } + + // Extract coordinates from address + const DxbcRegisterValue coord = emitCalcTexCoord(address, texture.imageInfo); + + // Reading a typed image or buffer view + // always returns a four-component vector. + const uint32_t imageId = m_module.opLoad(texture.imageTypeId, texture.varId); + + DxbcVectorType texelType; + texelType.ctype = texture.sampledType; + texelType.ccount = 4; + + uint32_t texelTypeId = getVectorTypeId(texelType); + uint32_t resultTypeId = texelTypeId; + uint32_t resultId = 0; + + if (imageOperands.sparse) + resultTypeId = getSparseResultTypeId(texelTypeId); + + resultId = m_module.opImageFetch(resultTypeId, + imageId, coord.id, imageOperands); + + DxbcRegisterValue result; + result.type = texelType; + result.id = imageOperands.sparse + ? emitExtractSparseTexel(texelTypeId, resultId) + : resultId; + + // Swizzle components using the texture swizzle + // and the destination operand's write mask + result = emitRegisterSwizzle(result, + ins.src[1].swizzle, ins.dst[0].mask); + + emitRegisterStore(ins.dst[0], result); + + if (imageOperands.sparse) + emitStoreSparseFeedback(ins.dst[1], resultId); + } + + + void DxbcCompiler::emitTextureGather(const DxbcShaderInstruction& ins) { + // Gather4 takes the following operands: + // (dst0) The destination register + // (dst1) The residency code for sparse ops + // (src0) Texture coordinates + // (src1) The texture itself + // (src2) The sampler, with a component selector + // Gather4C takes the following additional operand: + // (src3) The depth reference value + // The Gather4Po variants take an additional operand + // which defines an extended constant offset. + // TODO reduce code duplication by moving some common code + // in both sample() and gather() into separate methods + const bool isExtendedGather = ins.op == DxbcOpcode::Gather4Po + || ins.op == DxbcOpcode::Gather4PoC + || ins.op == DxbcOpcode::Gather4PoS + || ins.op == DxbcOpcode::Gather4PoCS; + + const DxbcRegister& texCoordReg = ins.src[0]; + const DxbcRegister& textureReg = ins.src[1 + isExtendedGather]; + const DxbcRegister& samplerReg = ins.src[2 + isExtendedGather]; + + // Texture and sampler register IDs + const auto& texture = m_textures.at(textureReg.idx[0].offset); + const auto& sampler = m_samplers.at(samplerReg.idx[0].offset); + + // Image type, which stores the image dimensions etc. + const uint32_t imageLayerDim = getTexLayerDim(texture.imageInfo); + + // Load the texture coordinates. SPIR-V allows these + // to be float4 even if not all components are used. + DxbcRegisterValue coord = emitLoadTexCoord(texCoordReg, texture.imageInfo); + + // Load reference value for depth-compare operations + const bool isDepthCompare = ins.op == DxbcOpcode::Gather4C + || ins.op == DxbcOpcode::Gather4PoC + || ins.op == DxbcOpcode::Gather4CS + || ins.op == DxbcOpcode::Gather4PoCS; + + const DxbcRegisterValue referenceValue = isDepthCompare + ? emitRegisterLoad(ins.src[3 + isExtendedGather], + DxbcRegMask(true, false, false, false)) + : DxbcRegisterValue(); + + // Accumulate additional image operands. + SpirvImageOperands imageOperands; + imageOperands.sparse = ins.dstCount == 2; + + if (isExtendedGather) { + m_module.enableCapability(spv::CapabilityImageGatherExtended); + + DxbcRegisterValue gatherOffset = emitRegisterLoad( + ins.src[1], DxbcRegMask::firstN(imageLayerDim)); + + imageOperands.flags |= spv::ImageOperandsOffsetMask; + imageOperands.gOffset = gatherOffset.id; + } else if (ins.sampleControls.u != 0 || ins.sampleControls.v != 0 || ins.sampleControls.w != 0) { + const std::array offsetIds = { + imageLayerDim >= 1 ? m_module.consti32(ins.sampleControls.u) : 0, + imageLayerDim >= 2 ? m_module.consti32(ins.sampleControls.v) : 0, + imageLayerDim >= 3 ? m_module.consti32(ins.sampleControls.w) : 0, + }; + + imageOperands.flags |= spv::ImageOperandsConstOffsetMask; + imageOperands.sConstOffset = offsetIds[0]; + + if (imageLayerDim > 1) { + imageOperands.sConstOffset = m_module.constComposite( + getVectorTypeId({ DxbcScalarType::Sint32, imageLayerDim }), + imageLayerDim, offsetIds.data()); + } + } + + // Gathering texels always returns a four-component + // vector, even for the depth-compare variants. + uint32_t sampledImageId = emitLoadSampledImage(texture, sampler, isDepthCompare); + + DxbcVectorType texelType; + texelType.ctype = texture.sampledType; + texelType.ccount = 4; + + uint32_t texelTypeId = getVectorTypeId(texelType); + uint32_t resultTypeId = texelTypeId; + uint32_t resultId = 0; + + if (imageOperands.sparse) + resultTypeId = getSparseResultTypeId(texelTypeId); + + if (sampledImageId) { + switch (ins.op) { + // Simple image gather operation + case DxbcOpcode::Gather4: + case DxbcOpcode::Gather4S: + case DxbcOpcode::Gather4Po: + case DxbcOpcode::Gather4PoS: { + resultId = m_module.opImageGather( + resultTypeId, sampledImageId, coord.id, + m_module.consti32(samplerReg.swizzle[0]), + imageOperands); + } break; + + // Depth-compare operation + case DxbcOpcode::Gather4C: + case DxbcOpcode::Gather4CS: + case DxbcOpcode::Gather4PoC: + case DxbcOpcode::Gather4PoCS: { + resultId = m_module.opImageDrefGather( + resultTypeId, sampledImageId, coord.id, + referenceValue.id, imageOperands); + } break; + + default: + Logger::warn(str::format( + "DxbcCompiler: Unhandled instruction: ", + ins.op)); + return; + } + } else { + Logger::warn(str::format("DxbcCompiler: ", ins.op, ": Unsupported image type")); + resultId = m_module.constNull(resultTypeId); + } + + // If necessary, deal with the sparse result + DxbcRegisterValue result; + result.type = texelType; + result.id = imageOperands.sparse + ? emitExtractSparseTexel(texelTypeId, resultId) + : resultId; + + // Swizzle components using the texture swizzle + // and the destination operand's write mask + result = emitRegisterSwizzle(result, + textureReg.swizzle, ins.dst[0].mask); + + emitRegisterStore(ins.dst[0], result); + + if (imageOperands.sparse) + emitStoreSparseFeedback(ins.dst[1], resultId); + } + + + void DxbcCompiler::emitTextureSample(const DxbcShaderInstruction& ins) { + // All sample instructions have at least these operands: + // (dst0) The destination register + // (src0) Texture coordinates + // (src1) The texture itself + // (src2) The sampler object + const DxbcRegister& texCoordReg = ins.src[0]; + const DxbcRegister& textureReg = ins.src[1]; + const DxbcRegister& samplerReg = ins.src[2]; + + // Texture and sampler register IDs + const auto& texture = m_textures.at(textureReg.idx[0].offset); + const auto& sampler = m_samplers.at(samplerReg.idx[0].offset); + const uint32_t imageLayerDim = getTexLayerDim(texture.imageInfo); + + // Load the texture coordinates. SPIR-V allows these + // to be float4 even if not all components are used. + DxbcRegisterValue coord = emitLoadTexCoord(texCoordReg, texture.imageInfo); + + // Load reference value for depth-compare operations + const bool isDepthCompare = ins.op == DxbcOpcode::SampleC + || ins.op == DxbcOpcode::SampleClz + || ins.op == DxbcOpcode::SampleCClampS + || ins.op == DxbcOpcode::SampleClzS; + + const DxbcRegisterValue referenceValue = isDepthCompare + ? emitRegisterLoad(ins.src[3], DxbcRegMask(true, false, false, false)) + : DxbcRegisterValue(); + + // Load explicit gradients for sample operations that require them + const bool hasExplicitGradients = ins.op == DxbcOpcode::SampleD + || ins.op == DxbcOpcode::SampleDClampS; + + const DxbcRegisterValue explicitGradientX = hasExplicitGradients + ? emitRegisterLoad(ins.src[3], DxbcRegMask::firstN(imageLayerDim)) + : DxbcRegisterValue(); + + const DxbcRegisterValue explicitGradientY = hasExplicitGradients + ? emitRegisterLoad(ins.src[4], DxbcRegMask::firstN(imageLayerDim)) + : DxbcRegisterValue(); + + // LOD for certain sample operations + const bool hasLod = ins.op == DxbcOpcode::SampleL + || ins.op == DxbcOpcode::SampleLS + || ins.op == DxbcOpcode::SampleB + || ins.op == DxbcOpcode::SampleBClampS; + + const DxbcRegisterValue lod = hasLod + ? emitRegisterLoad(ins.src[3], DxbcRegMask(true, false, false, false)) + : DxbcRegisterValue(); + + // Min LOD for certain sparse operations + const bool hasMinLod = ins.op == DxbcOpcode::SampleClampS + || ins.op == DxbcOpcode::SampleBClampS + || ins.op == DxbcOpcode::SampleDClampS + || ins.op == DxbcOpcode::SampleCClampS; + + const DxbcRegisterValue minLod = hasMinLod && ins.src[ins.srcCount - 1].type != DxbcOperandType::Null + ? emitRegisterLoad(ins.src[ins.srcCount - 1], DxbcRegMask(true, false, false, false)) + : DxbcRegisterValue(); + + // Accumulate additional image operands. These are + // not part of the actual operand token in SPIR-V. + SpirvImageOperands imageOperands; + imageOperands.sparse = ins.dstCount == 2; + + if (ins.sampleControls.u != 0 || ins.sampleControls.v != 0 || ins.sampleControls.w != 0) { + const std::array offsetIds = { + imageLayerDim >= 1 ? m_module.consti32(ins.sampleControls.u) : 0, + imageLayerDim >= 2 ? m_module.consti32(ins.sampleControls.v) : 0, + imageLayerDim >= 3 ? m_module.consti32(ins.sampleControls.w) : 0, + }; + + imageOperands.flags |= spv::ImageOperandsConstOffsetMask; + imageOperands.sConstOffset = offsetIds[0]; + + if (imageLayerDim > 1) { + imageOperands.sConstOffset = m_module.constComposite( + getVectorTypeId({ DxbcScalarType::Sint32, imageLayerDim }), + imageLayerDim, offsetIds.data()); + } + } + + if (hasMinLod) { + m_module.enableCapability(spv::CapabilityMinLod); + + imageOperands.flags |= spv::ImageOperandsMinLodMask; + imageOperands.sMinLod = minLod.id; + } + + // Combine the texture and the sampler into a sampled image + uint32_t sampledImageId = emitLoadSampledImage(texture, sampler, isDepthCompare); + + // Sampling an image always returns a four-component + // vector, whereas depth-compare ops return a scalar. + DxbcVectorType texelType; + texelType.ctype = texture.sampledType; + texelType.ccount = isDepthCompare ? 1 : 4; + + uint32_t texelTypeId = getVectorTypeId(texelType); + uint32_t resultTypeId = texelTypeId; + uint32_t resultId = 0; + + if (imageOperands.sparse) + resultTypeId = getSparseResultTypeId(texelTypeId); + + if (sampledImageId) { + switch (ins.op) { + // Simple image sample operation + case DxbcOpcode::Sample: + case DxbcOpcode::SampleClampS: { + resultId = m_module.opImageSampleImplicitLod( + resultTypeId, sampledImageId, coord.id, + imageOperands); + } break; + + // Depth-compare operation + case DxbcOpcode::SampleC: + case DxbcOpcode::SampleCClampS: { + resultId = m_module.opImageSampleDrefImplicitLod( + resultTypeId, sampledImageId, coord.id, + referenceValue.id, imageOperands); + } break; + + // Depth-compare operation on mip level zero + case DxbcOpcode::SampleClz: + case DxbcOpcode::SampleClzS: { + imageOperands.flags |= spv::ImageOperandsLodMask; + imageOperands.sLod = m_module.constf32(0.0f); + + resultId = m_module.opImageSampleDrefExplicitLod( + resultTypeId, sampledImageId, coord.id, + referenceValue.id, imageOperands); + } break; + + // Sample operation with explicit gradients + case DxbcOpcode::SampleD: + case DxbcOpcode::SampleDClampS: { + imageOperands.flags |= spv::ImageOperandsGradMask; + imageOperands.sGradX = explicitGradientX.id; + imageOperands.sGradY = explicitGradientY.id; + + resultId = m_module.opImageSampleExplicitLod( + resultTypeId, sampledImageId, coord.id, + imageOperands); + } break; + + // Sample operation with explicit LOD + case DxbcOpcode::SampleL: + case DxbcOpcode::SampleLS: { + imageOperands.flags |= spv::ImageOperandsLodMask; + imageOperands.sLod = lod.id; + + resultId = m_module.opImageSampleExplicitLod( + resultTypeId, sampledImageId, coord.id, + imageOperands); + } break; + + // Sample operation with LOD bias + case DxbcOpcode::SampleB: + case DxbcOpcode::SampleBClampS: { + imageOperands.flags |= spv::ImageOperandsBiasMask; + imageOperands.sLodBias = lod.id; + + resultId = m_module.opImageSampleImplicitLod( + resultTypeId, sampledImageId, coord.id, + imageOperands); + } break; + + default: + Logger::warn(str::format( + "DxbcCompiler: Unhandled instruction: ", + ins.op)); + return; + } + } else { + Logger::warn(str::format("DxbcCompiler: ", ins.op, ": Unsupported image type")); + resultId = m_module.constNull(resultTypeId); + } + + DxbcRegisterValue result; + result.type = texelType; + result.id = imageOperands.sparse + ? emitExtractSparseTexel(texelTypeId, resultId) + : resultId; + + // Swizzle components using the texture swizzle + // and the destination operand's write mask + if (result.type.ccount != 1) { + result = emitRegisterSwizzle(result, + textureReg.swizzle, ins.dst[0].mask); + } + + emitRegisterStore(ins.dst[0], result); + + if (imageOperands.sparse) + emitStoreSparseFeedback(ins.dst[1], resultId); + } + + + void DxbcCompiler::emitTypedUavLoad(const DxbcShaderInstruction& ins) { + // load_uav_typed has three operands: + // (dst0) The destination register + // (src0) The texture or buffer coordinates + // (src1) The UAV to load from + const uint32_t registerId = ins.src[1].idx[0].offset; + const DxbcUav uavInfo = m_uavs.at(registerId); + + emitUavBarrier(uint64_t(1u) << registerId, 0u); + + // Load texture coordinates + DxbcRegisterValue texCoord = emitLoadTexCoord( + ins.src[0], uavInfo.imageInfo); + + SpirvImageOperands imageOperands; + imageOperands.sparse = ins.dstCount == 2; + + if (uavInfo.coherence) { + imageOperands.flags |= spv::ImageOperandsNonPrivateTexelMask + | spv::ImageOperandsMakeTexelVisibleMask; + imageOperands.makeVisible = m_module.constu32(uavInfo.coherence); + } + + DxbcVectorType texelType; + texelType.ctype = uavInfo.sampledType; + texelType.ccount = 4; + + uint32_t texelTypeId = getVectorTypeId(texelType); + uint32_t resultTypeId = texelTypeId; + uint32_t resultId = 0; + + if (imageOperands.sparse) + resultTypeId = getSparseResultTypeId(texelTypeId); + + // Load source value from the UAV + resultId = m_module.opImageRead(resultTypeId, + m_module.opLoad(uavInfo.imageTypeId, uavInfo.varId), + texCoord.id, imageOperands); + + // Apply component swizzle and mask + DxbcRegisterValue uavValue; + uavValue.type = texelType; + uavValue.id = imageOperands.sparse + ? emitExtractSparseTexel(texelTypeId, resultId) + : resultId; + + uavValue = emitRegisterSwizzle(uavValue, + ins.src[1].swizzle, ins.dst[0].mask); + + emitRegisterStore(ins.dst[0], uavValue); + + if (imageOperands.sparse) + emitStoreSparseFeedback(ins.dst[1], resultId); + } + + + void DxbcCompiler::emitTypedUavStore(const DxbcShaderInstruction& ins) { + // store_uav_typed has three operands: + // (dst0) The destination UAV + // (src0) The texture or buffer coordinates + // (src1) The value to store + const DxbcBufferInfo uavInfo = getBufferInfo(ins.dst[0]); + emitUavBarrier(0u, uint64_t(1u) << ins.dst[0].idx[0].offset); + + // Set image operands for coherent access if necessary + SpirvImageOperands imageOperands; + + if (uavInfo.coherence) { + imageOperands.flags |= spv::ImageOperandsNonPrivateTexelMask + | spv::ImageOperandsMakeTexelAvailableMask; + imageOperands.makeAvailable = m_module.constu32(uavInfo.coherence); + } + + // Load texture coordinates + DxbcRegisterValue texCoord = emitLoadTexCoord(ins.src[0], uavInfo.image); + + // Load the value that will be written to the image. We'll + // have to cast it to the component type of the image. + const DxbcRegisterValue texValue = emitRegisterBitcast( + emitRegisterLoad(ins.src[1], DxbcRegMask(true, true, true, true)), + uavInfo.stype); + + // Write the given value to the image + m_module.opImageWrite( + m_module.opLoad(uavInfo.typeId, uavInfo.varId), + texCoord.id, texValue.id, imageOperands); + } + + + void DxbcCompiler::emitControlFlowIf(const DxbcShaderInstruction& ins) { + // Load the first component of the condition + // operand and perform a zero test on it. + const DxbcRegisterValue condition = emitRegisterLoad( + ins.src[0], DxbcRegMask(true, false, false, false)); + + // Declare the 'if' block. We do not know if there + // will be an 'else' block or not, so we'll assume + // that there is one and leave it empty otherwise. + DxbcCfgBlock block; + block.type = DxbcCfgBlockType::If; + block.b_if.ztestId = emitRegisterZeroTest(condition, ins.controls.zeroTest()).id; + block.b_if.labelIf = m_module.allocateId(); + block.b_if.labelElse = 0; + block.b_if.labelEnd = m_module.allocateId(); + block.b_if.headerPtr = m_module.getInsertionPtr(); + m_controlFlowBlocks.push_back(block); + + // We'll insert the branch instruction when closing + // the block, since we don't know whether or not an + // else block is needed right now. + m_module.opLabel(block.b_if.labelIf); + } + + + void DxbcCompiler::emitControlFlowElse(const DxbcShaderInstruction& ins) { + if (m_controlFlowBlocks.size() == 0 + || m_controlFlowBlocks.back().type != DxbcCfgBlockType::If + || m_controlFlowBlocks.back().b_if.labelElse != 0) + throw DxvkError("DxbcCompiler: 'Else' without 'If' found"); + + // Set the 'Else' flag so that we do + // not insert a dummy block on 'EndIf' + DxbcCfgBlock& block = m_controlFlowBlocks.back(); + block.b_if.labelElse = m_module.allocateId(); + + // Close the 'If' block by branching to + // the merge block we declared earlier + m_module.opBranch(block.b_if.labelEnd); + m_module.opLabel (block.b_if.labelElse); + } + + + void DxbcCompiler::emitControlFlowEndIf(const DxbcShaderInstruction& ins) { + if (m_controlFlowBlocks.size() == 0 + || m_controlFlowBlocks.back().type != DxbcCfgBlockType::If) + throw DxvkError("DxbcCompiler: 'EndIf' without 'If' found"); + + // Remove the block from the stack, it's closed + DxbcCfgBlock block = m_controlFlowBlocks.back(); + m_controlFlowBlocks.pop_back(); + + // Write out the 'if' header + m_module.beginInsertion(block.b_if.headerPtr); + + m_module.opSelectionMerge( + block.b_if.labelEnd, + spv::SelectionControlMaskNone); + + m_module.opBranchConditional( + block.b_if.ztestId, + block.b_if.labelIf, + block.b_if.labelElse != 0 + ? block.b_if.labelElse + : block.b_if.labelEnd); + + m_module.endInsertion(); + + // End the active 'if' or 'else' block + m_module.opBranch(block.b_if.labelEnd); + m_module.opLabel (block.b_if.labelEnd); + } + + + void DxbcCompiler::emitControlFlowSwitch(const DxbcShaderInstruction& ins) { + // Load the selector as a scalar unsigned integer + const DxbcRegisterValue selector = emitRegisterLoad( + ins.src[0], DxbcRegMask(true, false, false, false)); + + // Declare switch block. We cannot insert the switch + // instruction itself yet because the number of case + // statements and blocks is unknown at this point. + DxbcCfgBlock block; + block.type = DxbcCfgBlockType::Switch; + block.b_switch.insertPtr = m_module.getInsertionPtr(); + block.b_switch.selectorId = selector.id; + block.b_switch.labelBreak = m_module.allocateId(); + block.b_switch.labelCase = m_module.allocateId(); + block.b_switch.labelDefault = 0; + block.b_switch.labelCases = nullptr; + m_controlFlowBlocks.push_back(block); + + // Define the first 'case' label + m_module.opLabel(block.b_switch.labelCase); + } + + + void DxbcCompiler::emitControlFlowCase(const DxbcShaderInstruction& ins) { + if (m_controlFlowBlocks.size() == 0 + || m_controlFlowBlocks.back().type != DxbcCfgBlockType::Switch) + throw DxvkError("DxbcCompiler: 'Case' without 'Switch' found"); + + // The source operand must be a 32-bit immediate. + if (ins.src[0].type != DxbcOperandType::Imm32) + throw DxvkError("DxbcCompiler: Invalid operand type for 'Case'"); + + // Use the last label allocated for 'case'. + DxbcCfgBlockSwitch* block = &m_controlFlowBlocks.back().b_switch; + + if (caseBlockIsFallthrough()) { + block->labelCase = m_module.allocateId(); + + m_module.opBranch(block->labelCase); + m_module.opLabel (block->labelCase); + } + + DxbcSwitchLabel label; + label.desc.literal = ins.src[0].imm.u32_1; + label.desc.labelId = block->labelCase; + label.next = block->labelCases; + block->labelCases = new DxbcSwitchLabel(label); + } + + + void DxbcCompiler::emitControlFlowDefault(const DxbcShaderInstruction& ins) { + if (m_controlFlowBlocks.size() == 0 + || m_controlFlowBlocks.back().type != DxbcCfgBlockType::Switch) + throw DxvkError("DxbcCompiler: 'Default' without 'Switch' found"); + + DxbcCfgBlockSwitch* block = &m_controlFlowBlocks.back().b_switch; + + if (caseBlockIsFallthrough()) { + block->labelCase = m_module.allocateId(); + + m_module.opBranch(block->labelCase); + m_module.opLabel (block->labelCase); + } + + // Set the last label allocated for 'case' as the default label. + block->labelDefault = block->labelCase; + } + + + void DxbcCompiler::emitControlFlowEndSwitch(const DxbcShaderInstruction& ins) { + if (m_controlFlowBlocks.size() == 0 + || m_controlFlowBlocks.back().type != DxbcCfgBlockType::Switch) + throw DxvkError("DxbcCompiler: 'EndSwitch' without 'Switch' found"); + + // Remove the block from the stack, it's closed + DxbcCfgBlock block = m_controlFlowBlocks.back(); + m_controlFlowBlocks.pop_back(); + + if (!block.b_switch.labelDefault) { + block.b_switch.labelDefault = caseBlockIsFallthrough() + ? block.b_switch.labelBreak + : block.b_switch.labelCase; + } + + // Close the current 'case' block + m_module.opBranch(block.b_switch.labelBreak); + + // Insert the 'switch' statement. For that, we need to + // gather all the literal-label pairs for the construct. + m_module.beginInsertion(block.b_switch.insertPtr); + m_module.opSelectionMerge( + block.b_switch.labelBreak, + spv::SelectionControlMaskNone); + + // We'll restore the original order of the case labels here + std::vector jumpTargets; + for (auto i = block.b_switch.labelCases; i != nullptr; i = i->next) + jumpTargets.insert(jumpTargets.begin(), i->desc); + + m_module.opSwitch( + block.b_switch.selectorId, + block.b_switch.labelDefault, + jumpTargets.size(), + jumpTargets.data()); + m_module.endInsertion(); + + // Destroy the list of case labels + // FIXME we're leaking memory if compilation fails. + DxbcSwitchLabel* caseLabel = block.b_switch.labelCases; + + while (caseLabel != nullptr) + delete std::exchange(caseLabel, caseLabel->next); + + // Begin new block after switch blocks + m_module.opLabel(block.b_switch.labelBreak); + } + + + void DxbcCompiler::emitControlFlowLoop(const DxbcShaderInstruction& ins) { + // Declare the 'loop' block + DxbcCfgBlock block; + block.type = DxbcCfgBlockType::Loop; + block.b_loop.labelHeader = m_module.allocateId(); + block.b_loop.labelBegin = m_module.allocateId(); + block.b_loop.labelContinue = m_module.allocateId(); + block.b_loop.labelBreak = m_module.allocateId(); + m_controlFlowBlocks.push_back(block); + + m_module.opBranch(block.b_loop.labelHeader); + m_module.opLabel (block.b_loop.labelHeader); + + m_module.opLoopMerge( + block.b_loop.labelBreak, + block.b_loop.labelContinue, + spv::LoopControlMaskNone); + + m_module.opBranch(block.b_loop.labelBegin); + m_module.opLabel (block.b_loop.labelBegin); + } + + + void DxbcCompiler::emitControlFlowEndLoop(const DxbcShaderInstruction& ins) { + if (m_controlFlowBlocks.size() == 0 + || m_controlFlowBlocks.back().type != DxbcCfgBlockType::Loop) + throw DxvkError("DxbcCompiler: 'EndLoop' without 'Loop' found"); + + // Remove the block from the stack, it's closed + const DxbcCfgBlock block = m_controlFlowBlocks.back(); + m_controlFlowBlocks.pop_back(); + + // Declare the continue block + m_module.opBranch(block.b_loop.labelContinue); + m_module.opLabel (block.b_loop.labelContinue); + + // Declare the merge block + m_module.opBranch(block.b_loop.labelHeader); + m_module.opLabel (block.b_loop.labelBreak); + } + + + void DxbcCompiler::emitControlFlowBreak(const DxbcShaderInstruction& ins) { + const bool isBreak = ins.op == DxbcOpcode::Break; + + DxbcCfgBlock* cfgBlock = isBreak + ? cfgFindBlock({ DxbcCfgBlockType::Loop, DxbcCfgBlockType::Switch }) + : cfgFindBlock({ DxbcCfgBlockType::Loop }); + + if (cfgBlock == nullptr) + throw DxvkError("DxbcCompiler: 'Break' or 'Continue' outside 'Loop' or 'Switch' found"); + + if (cfgBlock->type == DxbcCfgBlockType::Loop) { + m_module.opBranch(isBreak + ? cfgBlock->b_loop.labelBreak + : cfgBlock->b_loop.labelContinue); + } else /* if (cfgBlock->type == DxbcCfgBlockType::Switch) */ { + m_module.opBranch(cfgBlock->b_switch.labelBreak); + } + + // Subsequent instructions assume that there is an open block + const uint32_t labelId = m_module.allocateId(); + m_module.opLabel(labelId); + + // If this is on the same level as a switch-case construct, + // rather than being nested inside an 'if' statement, close + // the current 'case' block. + if (m_controlFlowBlocks.back().type == DxbcCfgBlockType::Switch) + cfgBlock->b_switch.labelCase = labelId; + } + + + void DxbcCompiler::emitControlFlowBreakc(const DxbcShaderInstruction& ins) { + const bool isBreak = ins.op == DxbcOpcode::Breakc; + + DxbcCfgBlock* cfgBlock = isBreak + ? cfgFindBlock({ DxbcCfgBlockType::Loop, DxbcCfgBlockType::Switch }) + : cfgFindBlock({ DxbcCfgBlockType::Loop }); + + if (cfgBlock == nullptr) + throw DxvkError("DxbcCompiler: 'Breakc' or 'Continuec' outside 'Loop' or 'Switch' found"); + + // Perform zero test on the first component of the condition + const DxbcRegisterValue condition = emitRegisterLoad( + ins.src[0], DxbcRegMask(true, false, false, false)); + + const DxbcRegisterValue zeroTest = emitRegisterZeroTest( + condition, ins.controls.zeroTest()); + + // We basically have to wrap this into an 'if' block + const uint32_t breakBlock = m_module.allocateId(); + const uint32_t mergeBlock = m_module.allocateId(); + + m_module.opSelectionMerge(mergeBlock, + spv::SelectionControlMaskNone); + + m_module.opBranchConditional( + zeroTest.id, breakBlock, mergeBlock); + + m_module.opLabel(breakBlock); + + if (cfgBlock->type == DxbcCfgBlockType::Loop) { + m_module.opBranch(isBreak + ? cfgBlock->b_loop.labelBreak + : cfgBlock->b_loop.labelContinue); + } else /* if (cfgBlock->type == DxbcCfgBlockType::Switch) */ { + m_module.opBranch(cfgBlock->b_switch.labelBreak); + } + + m_module.opLabel(mergeBlock); + } + + + void DxbcCompiler::emitControlFlowRet(const DxbcShaderInstruction& ins) { + if (m_controlFlowBlocks.size() != 0) { + uint32_t labelId = m_module.allocateId(); + + m_module.opReturn(); + m_module.opLabel(labelId); + + // return can be used in place of break to terminate a case block + if (m_controlFlowBlocks.back().type == DxbcCfgBlockType::Switch) + m_controlFlowBlocks.back().b_switch.labelCase = labelId; + + m_topLevelIsUniform = false; + } else { + // Last instruction in the current function + this->emitFunctionEnd(); + } + } + + + void DxbcCompiler::emitControlFlowRetc(const DxbcShaderInstruction& ins) { + // Perform zero test on the first component of the condition + const DxbcRegisterValue condition = emitRegisterLoad( + ins.src[0], DxbcRegMask(true, false, false, false)); + + const DxbcRegisterValue zeroTest = emitRegisterZeroTest( + condition, ins.controls.zeroTest()); + + // We basically have to wrap this into an 'if' block + const uint32_t returnLabel = m_module.allocateId(); + const uint32_t continueLabel = m_module.allocateId(); + + m_module.opSelectionMerge(continueLabel, + spv::SelectionControlMaskNone); + + m_module.opBranchConditional( + zeroTest.id, returnLabel, continueLabel); + + m_module.opLabel(returnLabel); + m_module.opReturn(); + + m_module.opLabel(continueLabel); + + // The return condition may be non-uniform + m_topLevelIsUniform = false; + } + + + void DxbcCompiler::emitControlFlowDiscard(const DxbcShaderInstruction& ins) { + // Discard actually has an operand that determines + // whether or not the fragment should be discarded + const DxbcRegisterValue condition = emitRegisterLoad( + ins.src[0], DxbcRegMask(true, false, false, false)); + + const DxbcRegisterValue zeroTest = emitRegisterZeroTest( + condition, ins.controls.zeroTest()); + + DxbcConditional cond; + cond.labelIf = m_module.allocateId(); + cond.labelEnd = m_module.allocateId(); + + m_module.opSelectionMerge(cond.labelEnd, spv::SelectionControlMaskNone); + m_module.opBranchConditional(zeroTest.id, cond.labelIf, cond.labelEnd); + + m_module.opLabel(cond.labelIf); + m_module.opDemoteToHelperInvocation(); + m_module.opBranch(cond.labelEnd); + + m_module.opLabel(cond.labelEnd); + + m_module.enableCapability(spv::CapabilityDemoteToHelperInvocation); + + // Discard is just retc in a trenchcoat + m_topLevelIsUniform = false; + } + + + void DxbcCompiler::emitControlFlowLabel(const DxbcShaderInstruction& ins) { + uint32_t functionNr = ins.dst[0].idx[0].offset; + uint32_t functionId = getFunctionId(functionNr); + + this->emitFunctionBegin( + functionId, + m_module.defVoidType(), + m_module.defFunctionType( + m_module.defVoidType(), 0, nullptr)); + + m_module.opLabel(m_module.allocateId()); + m_module.setDebugName(functionId, str::format("label", functionNr).c_str()); + + m_insideFunction = true; + + // We have to assume that this function gets + // called from non-uniform control flow + m_topLevelIsUniform = false; + } + + + void DxbcCompiler::emitControlFlowCall(const DxbcShaderInstruction& ins) { + uint32_t functionNr = ins.src[0].idx[0].offset; + uint32_t functionId = getFunctionId(functionNr); + + m_module.opFunctionCall( + m_module.defVoidType(), + functionId, 0, nullptr); + } + + + void DxbcCompiler::emitControlFlowCallc(const DxbcShaderInstruction& ins) { + uint32_t functionNr = ins.src[1].idx[0].offset; + uint32_t functionId = getFunctionId(functionNr); + + // Perform zero test on the first component of the condition + const DxbcRegisterValue condition = emitRegisterLoad( + ins.src[0], DxbcRegMask(true, false, false, false)); + + const DxbcRegisterValue zeroTest = emitRegisterZeroTest( + condition, ins.controls.zeroTest()); + + // We basically have to wrap this into an 'if' block + const uint32_t callLabel = m_module.allocateId(); + const uint32_t skipLabel = m_module.allocateId(); + + m_module.opSelectionMerge(skipLabel, + spv::SelectionControlMaskNone); + + m_module.opBranchConditional( + zeroTest.id, callLabel, skipLabel); + + m_module.opLabel(callLabel); + m_module.opFunctionCall( + m_module.defVoidType(), + functionId, 0, nullptr); + + m_module.opBranch(skipLabel); + m_module.opLabel(skipLabel); + } + + + void DxbcCompiler::emitControlFlow(const DxbcShaderInstruction& ins) { + switch (ins.op) { + case DxbcOpcode::If: + this->emitUavBarrier(0, 0); + this->emitControlFlowIf(ins); + break; + + case DxbcOpcode::Else: + this->emitControlFlowElse(ins); + break; + + case DxbcOpcode::EndIf: + this->emitControlFlowEndIf(ins); + this->emitUavBarrier(0, 0); + break; + + case DxbcOpcode::Switch: + this->emitUavBarrier(0, 0); + this->emitControlFlowSwitch(ins); + break; + + case DxbcOpcode::Case: + this->emitControlFlowCase(ins); + break; + + case DxbcOpcode::Default: + this->emitControlFlowDefault(ins); + break; + + case DxbcOpcode::EndSwitch: + this->emitControlFlowEndSwitch(ins); + this->emitUavBarrier(0, 0); + break; + + case DxbcOpcode::Loop: + this->emitUavBarrier(0, 0); + this->emitControlFlowLoop(ins); + break; + + case DxbcOpcode::EndLoop: + this->emitControlFlowEndLoop(ins); + this->emitUavBarrier(0, 0); + break; + + case DxbcOpcode::Break: + case DxbcOpcode::Continue: + this->emitControlFlowBreak(ins); + break; + + case DxbcOpcode::Breakc: + case DxbcOpcode::Continuec: + this->emitControlFlowBreakc(ins); + break; + + case DxbcOpcode::Ret: + this->emitControlFlowRet(ins); + break; + + case DxbcOpcode::Retc: + this->emitUavBarrier(0, 0); + this->emitControlFlowRetc(ins); + break; + + case DxbcOpcode::Discard: + this->emitControlFlowDiscard(ins); + break; + + case DxbcOpcode::Label: + this->emitControlFlowLabel(ins); + break; + + case DxbcOpcode::Call: + this->emitUavBarrier(0, 0); + this->emitControlFlowCall(ins); + this->emitUavBarrier(-1, -1); + break; + + case DxbcOpcode::Callc: + this->emitUavBarrier(0, 0); + this->emitControlFlowCallc(ins); + this->emitUavBarrier(-1, -1); + break; + + default: + Logger::warn(str::format( + "DxbcCompiler: Unhandled instruction: ", + ins.op)); + } + } + + + DxbcRegisterValue DxbcCompiler::emitBuildConstVecf32( + float x, + float y, + float z, + float w, + const DxbcRegMask& writeMask) { + // TODO refactor these functions into one single template + std::array ids = { 0, 0, 0, 0 }; + uint32_t componentIndex = 0; + + if (writeMask[0]) ids[componentIndex++] = m_module.constf32(x); + if (writeMask[1]) ids[componentIndex++] = m_module.constf32(y); + if (writeMask[2]) ids[componentIndex++] = m_module.constf32(z); + if (writeMask[3]) ids[componentIndex++] = m_module.constf32(w); + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Float32; + result.type.ccount = componentIndex; + result.id = componentIndex > 1 + ? m_module.constComposite( + getVectorTypeId(result.type), + componentIndex, ids.data()) + : ids[0]; + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitBuildConstVecu32( + uint32_t x, + uint32_t y, + uint32_t z, + uint32_t w, + const DxbcRegMask& writeMask) { + std::array ids = { 0, 0, 0, 0 }; + uint32_t componentIndex = 0; + + if (writeMask[0]) ids[componentIndex++] = m_module.constu32(x); + if (writeMask[1]) ids[componentIndex++] = m_module.constu32(y); + if (writeMask[2]) ids[componentIndex++] = m_module.constu32(z); + if (writeMask[3]) ids[componentIndex++] = m_module.constu32(w); + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = componentIndex; + result.id = componentIndex > 1 + ? m_module.constComposite( + getVectorTypeId(result.type), + componentIndex, ids.data()) + : ids[0]; + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitBuildConstVeci32( + int32_t x, + int32_t y, + int32_t z, + int32_t w, + const DxbcRegMask& writeMask) { + std::array ids = { 0, 0, 0, 0 }; + uint32_t componentIndex = 0; + + if (writeMask[0]) ids[componentIndex++] = m_module.consti32(x); + if (writeMask[1]) ids[componentIndex++] = m_module.consti32(y); + if (writeMask[2]) ids[componentIndex++] = m_module.consti32(z); + if (writeMask[3]) ids[componentIndex++] = m_module.consti32(w); + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Sint32; + result.type.ccount = componentIndex; + result.id = componentIndex > 1 + ? m_module.constComposite( + getVectorTypeId(result.type), + componentIndex, ids.data()) + : ids[0]; + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitBuildConstVecf64( + double xy, + double zw, + const DxbcRegMask& writeMask) { + std::array ids = { 0, 0 }; + uint32_t componentIndex = 0; + + if (writeMask[0] && writeMask[1]) ids[componentIndex++] = m_module.constf64(xy); + if (writeMask[2] && writeMask[3]) ids[componentIndex++] = m_module.constf64(zw); + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Float64; + result.type.ccount = componentIndex; + result.id = componentIndex > 1 + ? m_module.constComposite( + getVectorTypeId(result.type), + componentIndex, ids.data()) + : ids[0]; + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitBuildVector( + DxbcRegisterValue scalar, + uint32_t count) { + if (count == 1) + return scalar; + + std::array scalarIds = + { scalar.id, scalar.id, scalar.id, scalar.id }; + + DxbcRegisterValue result; + result.type.ctype = scalar.type.ctype; + result.type.ccount = count; + result.id = m_module.constComposite( + getVectorTypeId(result.type), + count, scalarIds.data()); + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitBuildZeroVector( + DxbcVectorType type) { + DxbcRegisterValue result; + result.type.ctype = type.ctype; + result.type.ccount = 1; + + switch (type.ctype) { + case DxbcScalarType::Float32: result.id = m_module.constf32(0.0f); break; + case DxbcScalarType::Uint32: result.id = m_module.constu32(0u); break; + case DxbcScalarType::Sint32: result.id = m_module.consti32(0); break; + default: throw DxvkError("DxbcCompiler: Invalid scalar type"); + } + + return emitBuildVector(result, type.ccount); + } + + + DxbcRegisterValue DxbcCompiler::emitRegisterBitcast( + DxbcRegisterValue srcValue, + DxbcScalarType dstType) { + DxbcScalarType srcType = srcValue.type.ctype; + + if (srcType == dstType) + return srcValue; + + DxbcRegisterValue result; + result.type.ctype = dstType; + result.type.ccount = srcValue.type.ccount; + + if (isDoubleType(srcType)) result.type.ccount *= 2; + if (isDoubleType(dstType)) result.type.ccount /= 2; + + result.id = m_module.opBitcast( + getVectorTypeId(result.type), + srcValue.id); + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitRegisterSwizzle( + DxbcRegisterValue value, + DxbcRegSwizzle swizzle, + DxbcRegMask writeMask) { + if (value.type.ccount == 1) + return emitRegisterExtend(value, writeMask.popCount()); + + std::array indices; + + uint32_t dstIndex = 0; + + for (uint32_t i = 0; i < 4; i++) { + if (writeMask[i]) + indices[dstIndex++] = swizzle[i]; + } + + // If the swizzle combined with the mask can be reduced + // to a no-op, we don't need to insert any instructions. + bool isIdentitySwizzle = dstIndex == value.type.ccount; + + for (uint32_t i = 0; i < dstIndex && isIdentitySwizzle; i++) + isIdentitySwizzle &= indices[i] == i; + + if (isIdentitySwizzle) + return value; + + // Use OpCompositeExtract if the resulting vector contains + // only one component, and OpVectorShuffle if it is a vector. + DxbcRegisterValue result; + result.type.ctype = value.type.ctype; + result.type.ccount = dstIndex; + + const uint32_t typeId = getVectorTypeId(result.type); + + if (dstIndex == 1) { + result.id = m_module.opCompositeExtract( + typeId, value.id, 1, indices.data()); + } else { + result.id = m_module.opVectorShuffle( + typeId, value.id, value.id, + dstIndex, indices.data()); + } + + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitRegisterExtract( + DxbcRegisterValue value, + DxbcRegMask mask) { + return emitRegisterSwizzle(value, + DxbcRegSwizzle(0, 1, 2, 3), mask); + } + + + DxbcRegisterValue DxbcCompiler::emitRegisterInsert( + DxbcRegisterValue dstValue, + DxbcRegisterValue srcValue, + DxbcRegMask srcMask) { + DxbcRegisterValue result; + result.type = dstValue.type; + + const uint32_t typeId = getVectorTypeId(result.type); + + if (srcMask.popCount() == 0) { + // Nothing to do if the insertion mask is empty + result.id = dstValue.id; + } else if (dstValue.type.ccount == 1) { + // Both values are scalar, so the first component + // of the write mask decides which one to take. + result.id = srcMask[0] ? srcValue.id : dstValue.id; + } else if (srcValue.type.ccount == 1) { + // The source value is scalar. Since OpVectorShuffle + // requires both arguments to be vectors, we have to + // use OpCompositeInsert to modify the vector instead. + const uint32_t componentId = srcMask.firstSet(); + + result.id = m_module.opCompositeInsert(typeId, + srcValue.id, dstValue.id, 1, &componentId); + } else { + // Both arguments are vectors. We can determine which + // components to take from which vector and use the + // OpVectorShuffle instruction. + std::array components; + uint32_t srcComponentId = dstValue.type.ccount; + + for (uint32_t i = 0; i < dstValue.type.ccount; i++) + components.at(i) = srcMask[i] ? srcComponentId++ : i; + + result.id = m_module.opVectorShuffle( + typeId, dstValue.id, srcValue.id, + dstValue.type.ccount, components.data()); + } + + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitRegisterConcat( + DxbcRegisterValue value1, + DxbcRegisterValue value2) { + std::array ids = + {{ value1.id, value2.id }}; + + DxbcRegisterValue result; + result.type.ctype = value1.type.ctype; + result.type.ccount = value1.type.ccount + value2.type.ccount; + result.id = m_module.opCompositeConstruct( + getVectorTypeId(result.type), + ids.size(), ids.data()); + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitRegisterExtend( + DxbcRegisterValue value, + uint32_t size) { + if (size == 1) + return value; + + std::array ids = {{ + value.id, value.id, + value.id, value.id, + }}; + + DxbcRegisterValue result; + result.type.ctype = value.type.ctype; + result.type.ccount = size; + result.id = m_module.opCompositeConstruct( + getVectorTypeId(result.type), + size, ids.data()); + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitRegisterAbsolute( + DxbcRegisterValue value) { + const uint32_t typeId = getVectorTypeId(value.type); + + switch (value.type.ctype) { + case DxbcScalarType::Float32: value.id = m_module.opFAbs(typeId, value.id); break; + case DxbcScalarType::Float64: value.id = m_module.opFAbs(typeId, value.id); break; + case DxbcScalarType::Sint32: value.id = m_module.opSAbs(typeId, value.id); break; + case DxbcScalarType::Sint64: value.id = m_module.opSAbs(typeId, value.id); break; + default: Logger::warn("DxbcCompiler: Cannot get absolute value for given type"); + } + + return value; + } + + + DxbcRegisterValue DxbcCompiler::emitRegisterNegate( + DxbcRegisterValue value) { + const uint32_t typeId = getVectorTypeId(value.type); + + switch (value.type.ctype) { + case DxbcScalarType::Float32: value.id = m_module.opFNegate(typeId, value.id); break; + case DxbcScalarType::Float64: value.id = m_module.opFNegate(typeId, value.id); break; + case DxbcScalarType::Sint32: value.id = m_module.opSNegate(typeId, value.id); break; + case DxbcScalarType::Sint64: value.id = m_module.opSNegate(typeId, value.id); break; + default: Logger::warn("DxbcCompiler: Cannot negate given type"); + } + + return value; + } + + + DxbcRegisterValue DxbcCompiler::emitRegisterZeroTest( + DxbcRegisterValue value, + DxbcZeroTest test) { + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Bool; + result.type.ccount = 1; + + const uint32_t zeroId = m_module.constu32(0u); + const uint32_t typeId = getVectorTypeId(result.type); + + result.id = test == DxbcZeroTest::TestZ + ? m_module.opIEqual (typeId, value.id, zeroId) + : m_module.opINotEqual(typeId, value.id, zeroId); + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitRegisterMaskBits( + DxbcRegisterValue value, + uint32_t mask) { + DxbcRegisterValue maskVector = emitBuildConstVecu32( + mask, mask, mask, mask, DxbcRegMask::firstN(value.type.ccount)); + + DxbcRegisterValue result; + result.type = value.type; + result.id = m_module.opBitwiseAnd( + getVectorTypeId(result.type), + value.id, maskVector.id); + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitSrcOperandModifiers( + DxbcRegisterValue value, + DxbcRegModifiers modifiers) { + if (modifiers.test(DxbcRegModifier::Abs)) + value = emitRegisterAbsolute(value); + + if (modifiers.test(DxbcRegModifier::Neg)) + value = emitRegisterNegate(value); + return value; + } + + + uint32_t DxbcCompiler::emitExtractSparseTexel( + uint32_t texelTypeId, + uint32_t resultId) { + uint32_t index = 1; + + return m_module.opCompositeExtract( + texelTypeId, resultId, 1, &index); + } + + + void DxbcCompiler::emitStoreSparseFeedback( + const DxbcRegister& feedbackRegister, + uint32_t resultId) { + if (feedbackRegister.type != DxbcOperandType::Null) { + uint32_t index = 0; + + DxbcRegisterValue result; + result.type = { DxbcScalarType::Uint32, 1 }; + result.id = m_module.opCompositeExtract( + getScalarTypeId(DxbcScalarType::Uint32), + resultId, 1, &index); + + emitRegisterStore(feedbackRegister, result); + } + } + + + DxbcRegisterValue DxbcCompiler::emitDstOperandModifiers( + DxbcRegisterValue value, + DxbcOpModifiers modifiers) { + const uint32_t typeId = getVectorTypeId(value.type); + + if (modifiers.saturate) { + DxbcRegMask mask; + DxbcRegisterValue vec0, vec1; + + if (value.type.ctype == DxbcScalarType::Float32) { + mask = DxbcRegMask::firstN(value.type.ccount); + vec0 = emitBuildConstVecf32(0.0f, 0.0f, 0.0f, 0.0f, mask); + vec1 = emitBuildConstVecf32(1.0f, 1.0f, 1.0f, 1.0f, mask); + } else if (value.type.ctype == DxbcScalarType::Float64) { + mask = DxbcRegMask::firstN(value.type.ccount * 2); + vec0 = emitBuildConstVecf64(0.0, 0.0, mask); + vec1 = emitBuildConstVecf64(1.0, 1.0, mask); + } + + if (mask) + value.id = m_module.opNClamp(typeId, value.id, vec0.id, vec1.id); + } + + return value; + } + + + DxbcRegisterPointer DxbcCompiler::emitArrayAccess( + DxbcRegisterPointer pointer, + spv::StorageClass sclass, + uint32_t index) { + uint32_t ptrTypeId = m_module.defPointerType( + getVectorTypeId(pointer.type), sclass); + + DxbcRegisterPointer result; + result.type = pointer.type; + result.id = m_module.opAccessChain( + ptrTypeId, pointer.id, 1, &index); + return result; + } + + + uint32_t DxbcCompiler::emitLoadSampledImage( + const DxbcShaderResource& textureResource, + const DxbcSampler& samplerResource, + bool isDepthCompare) { + uint32_t baseId = isDepthCompare + ? textureResource.depthTypeId + : textureResource.colorTypeId; + + if (!baseId) + return 0; + + uint32_t sampledImageType = m_module.defSampledImageType(baseId); + + return m_module.opSampledImage(sampledImageType, + m_module.opLoad(textureResource.imageTypeId, textureResource.varId), + m_module.opLoad(samplerResource.typeId, samplerResource.varId)); + } + + + DxbcRegisterPointer DxbcCompiler::emitGetTempPtr( + const DxbcRegister& operand) { + // r# regs are indexed as follows: + // (0) register index (immediate) + uint32_t regIdx = operand.idx[0].offset; + + if (regIdx >= m_rRegs.size()) + m_rRegs.resize(regIdx + 1, 0u); + + if (!m_rRegs.at(regIdx)) { + DxbcRegisterInfo info; + info.type.ctype = DxbcScalarType::Float32; + info.type.ccount = 4; + info.type.alength = 0; + info.sclass = spv::StorageClassPrivate; + + uint32_t varId = emitNewVariable(info); + m_rRegs.at(regIdx) = varId; + + m_module.setDebugName(varId, + str::format("r", regIdx).c_str()); + } + + DxbcRegisterPointer result; + result.type.ctype = DxbcScalarType::Float32; + result.type.ccount = 4; + result.id = m_rRegs.at(regIdx); + return result; + } + + + DxbcRegisterPointer DxbcCompiler::emitGetIndexableTempPtr( + const DxbcRegister& operand) { + return getIndexableTempPtr(operand, emitIndexLoad(operand.idx[1])); + } + + + DxbcRegisterPointer DxbcCompiler::emitGetInputPtr( + const DxbcRegister& operand) { + // In the vertex and pixel stages, + // v# regs are indexed as follows: + // (0) register index (relative) + // + // In the tessellation and geometry + // stages, the index has two dimensions: + // (0) vertex index (relative) + // (1) register index (relative) + DxbcRegisterPointer result; + result.type.ctype = DxbcScalarType::Float32; + result.type.ccount = 4; + + std::array indices = {{ 0, 0 }}; + + for (uint32_t i = 0; i < operand.idxDim; i++) + indices.at(i) = emitIndexLoad(operand.idx[i]).id; + + // Pick the input array depending on + // the program type and operand type + struct InputArray { + uint32_t id; + spv::StorageClass sclass; + }; + + const InputArray array = [&] () -> InputArray { + switch (operand.type) { + case DxbcOperandType::InputControlPoint: + return m_programInfo.type() == DxbcProgramType::HullShader + ? InputArray { m_vArray, spv::StorageClassPrivate } + : InputArray { m_ds.inputPerVertex, spv::StorageClassInput }; + case DxbcOperandType::InputPatchConstant: + return m_programInfo.type() == DxbcProgramType::HullShader + ? InputArray { m_hs.outputPerPatch, spv::StorageClassPrivate } + : InputArray { m_ds.inputPerPatch, spv::StorageClassInput }; + case DxbcOperandType::OutputControlPoint: + return InputArray { m_hs.outputPerVertex, spv::StorageClassOutput }; + default: + return { m_vArray, spv::StorageClassPrivate }; + } + }(); + + DxbcRegisterInfo info; + info.type.ctype = result.type.ctype; + info.type.ccount = result.type.ccount; + info.type.alength = 0; + info.sclass = array.sclass; + + result.id = m_module.opAccessChain( + getPointerTypeId(info), array.id, + operand.idxDim, indices.data()); + + return result; + } + + + DxbcRegisterPointer DxbcCompiler::emitGetOutputPtr( + const DxbcRegister& operand) { + if (m_programInfo.type() == DxbcProgramType::HullShader) { + // Hull shaders are special in that they have two sets of + // output registers, one for per-patch values and one for + // per-vertex values. + DxbcRegisterPointer result; + result.type.ctype = DxbcScalarType::Float32; + result.type.ccount = 4; + + uint32_t registerId = emitIndexLoad(operand.idx[0]).id; + + if (m_hs.currPhaseType == DxbcCompilerHsPhase::ControlPoint) { + std::array indices = {{ + m_module.opLoad(m_module.defIntType(32, 0), m_hs.builtinInvocationId), + registerId, + }}; + + uint32_t ptrTypeId = m_module.defPointerType( + getVectorTypeId(result.type), + spv::StorageClassOutput); + + result.id = m_module.opAccessChain( + ptrTypeId, m_hs.outputPerVertex, + indices.size(), indices.data()); + } else { + uint32_t ptrTypeId = m_module.defPointerType( + getVectorTypeId(result.type), + spv::StorageClassPrivate); + + result.id = m_module.opAccessChain( + ptrTypeId, m_hs.outputPerPatch, + 1, ®isterId); + } + + return result; + } else { + // Regular shaders have their output + // registers set up at declaration time + return m_oRegs.at(operand.idx[0].offset); + } + } + + + DxbcRegisterPointer DxbcCompiler::emitGetImmConstBufPtr( + const DxbcRegister& operand) { + DxbcRegisterValue constId = emitIndexLoad(operand.idx[0]); + + if (m_icbArray) { + // We pad the icb array with an extra zero vector, so we can + // clamp the index and get correct robustness behaviour. + constId.id = m_module.opUMin(getVectorTypeId(constId.type), + constId.id, m_module.constu32(m_icbSize)); + + DxbcRegisterInfo ptrInfo; + ptrInfo.type.ctype = DxbcScalarType::Uint32; + ptrInfo.type.ccount = m_icbComponents; + ptrInfo.type.alength = 0; + ptrInfo.sclass = spv::StorageClassPrivate; + + DxbcRegisterPointer result; + result.type.ctype = ptrInfo.type.ctype; + result.type.ccount = ptrInfo.type.ccount; + result.id = m_module.opAccessChain( + getPointerTypeId(ptrInfo), + m_icbArray, 1, &constId.id); + return result; + } else if (m_constantBuffers.at(Icb_BindingSlotId).varId != 0) { + const std::array indices = + {{ m_module.consti32(0), constId.id }}; + + DxbcRegisterInfo ptrInfo; + ptrInfo.type.ctype = DxbcScalarType::Float32; + ptrInfo.type.ccount = m_icbComponents; + ptrInfo.type.alength = 0; + ptrInfo.sclass = spv::StorageClassUniform; + + DxbcRegisterPointer result; + result.type.ctype = ptrInfo.type.ctype; + result.type.ccount = ptrInfo.type.ccount; + result.id = m_module.opAccessChain( + getPointerTypeId(ptrInfo), + m_constantBuffers.at(Icb_BindingSlotId).varId, + indices.size(), indices.data()); + return result; + } else { + throw DxvkError("DxbcCompiler: Immediate constant buffer not defined"); + } + } + + + DxbcRegisterPointer DxbcCompiler::emitGetOperandPtr( + const DxbcRegister& operand) { + switch (operand.type) { + case DxbcOperandType::Temp: + return emitGetTempPtr(operand); + + case DxbcOperandType::IndexableTemp: + return emitGetIndexableTempPtr(operand); + + case DxbcOperandType::Input: + case DxbcOperandType::InputControlPoint: + case DxbcOperandType::InputPatchConstant: + case DxbcOperandType::OutputControlPoint: + return emitGetInputPtr(operand); + + case DxbcOperandType::Output: + return emitGetOutputPtr(operand); + + case DxbcOperandType::ImmediateConstantBuffer: + return emitGetImmConstBufPtr(operand); + + case DxbcOperandType::InputThreadId: + return DxbcRegisterPointer { + { DxbcScalarType::Uint32, 3 }, + m_cs.builtinGlobalInvocationId }; + + case DxbcOperandType::InputThreadGroupId: + return DxbcRegisterPointer { + { DxbcScalarType::Uint32, 3 }, + m_cs.builtinWorkgroupId }; + + case DxbcOperandType::InputThreadIdInGroup: + return DxbcRegisterPointer { + { DxbcScalarType::Uint32, 3 }, + m_cs.builtinLocalInvocationId }; + + case DxbcOperandType::InputThreadIndexInGroup: + return DxbcRegisterPointer { + { DxbcScalarType::Uint32, 1 }, + m_cs.builtinLocalInvocationIndex }; + + case DxbcOperandType::InputCoverageMask: { + const std::array indices + = {{ m_module.constu32(0) }}; + + DxbcRegisterPointer result; + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = 1; + result.id = m_module.opAccessChain( + m_module.defPointerType( + getVectorTypeId(result.type), + spv::StorageClassInput), + m_ps.builtinSampleMaskIn, + indices.size(), indices.data()); + return result; + } + + case DxbcOperandType::OutputCoverageMask: { + const std::array indices + = {{ m_module.constu32(0) }}; + + DxbcRegisterPointer result; + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = 1; + result.id = m_module.opAccessChain( + m_module.defPointerType( + getVectorTypeId(result.type), + spv::StorageClassOutput), + m_ps.builtinSampleMaskOut, + indices.size(), indices.data()); + return result; + } + + case DxbcOperandType::OutputDepth: + case DxbcOperandType::OutputDepthGe: + case DxbcOperandType::OutputDepthLe: + return DxbcRegisterPointer { + { DxbcScalarType::Float32, 1 }, + m_ps.builtinDepth }; + + case DxbcOperandType::OutputStencilRef: + return DxbcRegisterPointer { + { DxbcScalarType::Sint32, 1 }, + m_ps.builtinStencilRef }; + + case DxbcOperandType::InputPrimitiveId: + return DxbcRegisterPointer { + { DxbcScalarType::Uint32, 1 }, + m_primitiveIdIn }; + + case DxbcOperandType::InputDomainPoint: + return DxbcRegisterPointer { + { DxbcScalarType::Float32, 3 }, + m_ds.builtinTessCoord }; + + case DxbcOperandType::OutputControlPointId: + return DxbcRegisterPointer { + { DxbcScalarType::Uint32, 1 }, + m_hs.builtinInvocationId }; + + case DxbcOperandType::InputForkInstanceId: + case DxbcOperandType::InputJoinInstanceId: + return DxbcRegisterPointer { + { DxbcScalarType::Uint32, 1 }, + getCurrentHsForkJoinPhase()->instanceIdPtr }; + + case DxbcOperandType::InputGsInstanceId: + return DxbcRegisterPointer { + { DxbcScalarType::Uint32, 1 }, + m_gs.builtinInvocationId }; + + case DxbcOperandType::InputInnerCoverage: + return DxbcRegisterPointer { + { DxbcScalarType::Uint32, 1 }, + m_ps.builtinInnerCoverageId }; + + default: + throw DxvkError(str::format( + "DxbcCompiler: Unhandled operand type: ", + operand.type)); + } + } + + + DxbcRegisterPointer DxbcCompiler::emitGetAtomicPointer( + const DxbcRegister& operand, + const DxbcRegister& address) { + // Query information about the resource itself + const uint32_t registerId = operand.idx[0].offset; + const DxbcBufferInfo resourceInfo = getBufferInfo(operand); + + // For UAVs and shared memory, different methods + // of obtaining the final pointer are used. + bool isTgsm = operand.type == DxbcOperandType::ThreadGroupSharedMemory; + bool isSsbo = resourceInfo.isSsbo; + + // Compute the actual address into the resource + const DxbcRegisterValue addressValue = [&] { + switch (resourceInfo.type) { + case DxbcResourceType::Raw: + return emitCalcBufferIndexRaw(emitRegisterLoad( + address, DxbcRegMask(true, false, false, false))); + + case DxbcResourceType::Structured: { + const DxbcRegisterValue addressComponents = emitRegisterLoad( + address, DxbcRegMask(true, true, false, false)); + + return emitCalcBufferIndexStructured( + emitRegisterExtract(addressComponents, DxbcRegMask(true, false, false, false)), + emitRegisterExtract(addressComponents, DxbcRegMask(false, true, false, false)), + resourceInfo.stride); + }; + + case DxbcResourceType::Typed: { + if (isTgsm) + throw DxvkError("DxbcCompiler: TGSM cannot be typed"); + + return emitLoadTexCoord(address, + m_uavs.at(registerId).imageInfo); + } + + default: + throw DxvkError("DxbcCompiler: Unhandled resource type"); + } + }(); + + // Compute the actual pointer + DxbcRegisterPointer result; + result.type.ctype = resourceInfo.stype; + result.type.ccount = 1; + + if (isTgsm) { + result.id = m_module.opAccessChain(resourceInfo.typeId, + resourceInfo.varId, 1, &addressValue.id); + } else if (isSsbo) { + uint32_t indices[2] = { m_module.constu32(0), addressValue.id }; + result.id = m_module.opAccessChain(resourceInfo.typeId, + resourceInfo.varId, 2, indices); + } else { + result.id = m_module.opImageTexelPointer( + m_module.defPointerType(getVectorTypeId(result.type), spv::StorageClassImage), + resourceInfo.varId, addressValue.id, m_module.constu32(0)); + } + + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitQueryBufferSize( + const DxbcRegister& resource) { + const DxbcBufferInfo bufferInfo = getBufferInfo(resource); + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = 1; + result.id = m_module.opArrayLength( + getVectorTypeId(result.type), + bufferInfo.varId, 0); + + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitQueryTexelBufferSize( + const DxbcRegister& resource) { + // Load the texel buffer object. This cannot be used with + // constant buffers or any other type of resource. + const DxbcBufferInfo bufferInfo = getBufferInfo(resource); + + const uint32_t bufferId = m_module.opLoad( + bufferInfo.typeId, bufferInfo.varId); + + // We'll store this as a scalar unsigned integer + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = 1; + result.id = m_module.opImageQuerySize( + getVectorTypeId(result.type), bufferId); + + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitQueryTextureLods( + const DxbcRegister& resource) { + const DxbcBufferInfo info = getBufferInfo(resource); + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = 1; + + if (info.image.ms == 0 && info.image.sampled == 1) { + result.id = m_module.opImageQueryLevels( + getVectorTypeId(result.type), + m_module.opLoad(info.typeId, info.varId)); + } else { + // Report one LOD in case of UAVs or multisampled images + result.id = m_module.constu32(1); + } + + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitQueryTextureSamples( + const DxbcRegister& resource) { + if (resource.type == DxbcOperandType::Rasterizer) { + // SPIR-V has no gl_NumSamples equivalent, so we + // have to work around it using a push constant + if (!m_ps.pushConstantId) + m_ps.pushConstantId = emitPushConstants(); + + uint32_t uintTypeId = m_module.defIntType(32, 0); + uint32_t ptrTypeId = m_module.defPointerType(uintTypeId, spv::StorageClassPushConstant); + uint32_t index = m_module.constu32(0); + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = 1; + result.id = m_module.opLoad(uintTypeId, + m_module.opAccessChain(ptrTypeId, m_ps.pushConstantId, 1, &index)); + return result; + } else { + DxbcBufferInfo info = getBufferInfo(resource); + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = 1; + + if (info.image.ms) { + result.id = m_module.opImageQuerySamples( + getVectorTypeId(result.type), + m_module.opLoad(info.typeId, info.varId)); + } else { + // OpImageQuerySamples requires MSAA images + result.id = m_module.constu32(1); + } + + return result; + } + } + + + DxbcRegisterValue DxbcCompiler::emitQueryTextureSize( + const DxbcRegister& resource, + DxbcRegisterValue lod) { + const DxbcBufferInfo info = getBufferInfo(resource); + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = getTexSizeDim(info.image); + + if (info.image.ms == 0 && info.image.sampled == 1) { + result.id = m_module.opImageQuerySizeLod( + getVectorTypeId(result.type), + m_module.opLoad(info.typeId, info.varId), + lod.id); + } else { + result.id = m_module.opImageQuerySize( + getVectorTypeId(result.type), + m_module.opLoad(info.typeId, info.varId)); + } + + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitCalcBufferIndexStructured( + DxbcRegisterValue structId, + DxbcRegisterValue structOffset, + uint32_t structStride) { + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Sint32; + result.type.ccount = 1; + + uint32_t typeId = getVectorTypeId(result.type); + uint32_t offset = m_module.opShiftRightLogical(typeId, structOffset.id, m_module.consti32(2)); + + result.id = m_module.opIAdd(typeId, + m_module.opIMul(typeId, structId.id, m_module.consti32(structStride / 4)), + offset); + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitCalcBufferIndexRaw( + DxbcRegisterValue byteOffset) { + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Sint32; + result.type.ccount = 1; + + uint32_t typeId = getVectorTypeId(result.type); + result.id = m_module.opShiftRightLogical(typeId, byteOffset.id, m_module.consti32(2)); + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitCalcTexCoord( + DxbcRegisterValue coordVector, + const DxbcImageInfo& imageInfo) { + const uint32_t dim = getTexCoordDim(imageInfo); + + if (dim != coordVector.type.ccount) { + coordVector = emitRegisterExtract( + coordVector, DxbcRegMask::firstN(dim)); + } + + return coordVector; + } + + + DxbcRegisterValue DxbcCompiler::emitLoadTexCoord( + const DxbcRegister& coordReg, + const DxbcImageInfo& imageInfo) { + return emitCalcTexCoord(emitRegisterLoad(coordReg, + DxbcRegMask(true, true, true, true)), imageInfo); + } + + + DxbcRegisterValue DxbcCompiler::emitIndexLoad( + DxbcRegIndex index) { + if (index.relReg != nullptr) { + DxbcRegisterValue result = emitRegisterLoad( + *index.relReg, DxbcRegMask(true, false, false, false)); + + if (index.offset != 0) { + result.id = m_module.opIAdd( + getVectorTypeId(result.type), result.id, + m_module.consti32(index.offset)); + } + + return result; + } else { + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Sint32; + result.type.ccount = 1; + result.id = m_module.consti32(index.offset); + return result; + } + } + + + DxbcRegisterValue DxbcCompiler::emitValueLoad( + DxbcRegisterPointer ptr) { + DxbcRegisterValue result; + result.type = ptr.type; + result.id = m_module.opLoad( + getVectorTypeId(result.type), + ptr.id); + return result; + } + + + void DxbcCompiler::emitValueStore( + DxbcRegisterPointer ptr, + DxbcRegisterValue value, + DxbcRegMask writeMask) { + // If the component types are not compatible, + // we need to bit-cast the source variable. + if (value.type.ctype != ptr.type.ctype) + value = emitRegisterBitcast(value, ptr.type.ctype); + + // If the source value consists of only one component, + // it is stored in all components of the destination. + if (value.type.ccount == 1) + value = emitRegisterExtend(value, writeMask.popCount()); + + if (ptr.type.ccount == writeMask.popCount()) { + // Simple case: We write to the entire register + m_module.opStore(ptr.id, value.id); + } else { + // We only write to part of the destination + // register, so we need to load and modify it + DxbcRegisterValue tmp = emitValueLoad(ptr); + tmp = emitRegisterInsert(tmp, value, writeMask); + + m_module.opStore(ptr.id, tmp.id); + } + } + + + DxbcRegisterValue DxbcCompiler::emitRegisterLoadRaw( + const DxbcRegister& reg) { + // Try to find index range for the given register + const DxbcIndexRange* indexRange = nullptr; + + if (reg.idxDim && reg.idx[reg.idxDim - 1u].relReg) { + uint32_t offset = reg.idx[reg.idxDim - 1u].offset; + + for (const auto& range : m_indexRanges) { + if (reg.type == range.type && offset >= range.start && offset < range.start + range.length) + indexRange = ⦥ + } + } + + if (reg.type == DxbcOperandType::IndexableTemp || indexRange) { + bool doBoundsCheck = reg.idx[reg.idxDim - 1u].relReg != nullptr; + + if (doBoundsCheck) { + DxbcRegisterValue indexId = emitIndexLoad(reg.idx[reg.idxDim - 1u]); + uint32_t boundsCheck = 0u; + + if (reg.type == DxbcOperandType::IndexableTemp) { + boundsCheck = m_module.opULessThan( + m_module.defBoolType(), indexId.id, + m_module.constu32(m_xRegs.at(reg.idx[0].offset).alength)); + } else { + uint32_t adjustedId = m_module.opISub(getVectorTypeId(indexId.type), + indexId.id, m_module.consti32(indexRange->start)); + + boundsCheck = m_module.opULessThan( + m_module.defBoolType(), adjustedId, + m_module.constu32(indexRange->length)); + } + + // Kind of ugly to have an empty else block here but there's no + // way for us to know the current block ID for the phi below + DxbcConditional cond; + cond.labelIf = m_module.allocateId(); + cond.labelElse = m_module.allocateId(); + cond.labelEnd = m_module.allocateId(); + + m_module.opSelectionMerge(cond.labelEnd, spv::SelectionControlMaskNone); + m_module.opBranchConditional(boundsCheck, cond.labelIf, cond.labelElse); + + m_module.opLabel(cond.labelIf); + + DxbcRegisterValue returnValue = emitValueLoad(emitGetOperandPtr(reg)); + + m_module.opBranch(cond.labelEnd); + m_module.opLabel (cond.labelElse); + + DxbcRegisterValue zeroValue = emitBuildZeroVector(returnValue.type); + + m_module.opBranch(cond.labelEnd); + m_module.opLabel (cond.labelEnd); + + std::array phiLabels = {{ + { returnValue.id, cond.labelIf }, + { zeroValue.id, cond.labelElse }, + }}; + + returnValue.id = m_module.opPhi( + getVectorTypeId(returnValue.type), + phiLabels.size(), phiLabels.data()); + return returnValue; + } + } + + DxbcRegisterValue value = emitValueLoad(emitGetOperandPtr(reg)); + + // Pad icb values to a vec4 since the app may access components that are always 0 + if (reg.type == DxbcOperandType::ImmediateConstantBuffer && value.type.ccount < 4u) { + DxbcVectorType zeroType; + zeroType.ctype = value.type.ctype; + zeroType.ccount = 4u - value.type.ccount; + + uint32_t zeroVector = emitBuildZeroVector(zeroType).id; + + std::array constituents = { value.id, zeroVector }; + + value.type.ccount = 4u; + value.id = m_module.opCompositeConstruct(getVectorTypeId(value.type), + constituents.size(), constituents.data()); + } + + return value; + } + + + DxbcRegisterValue DxbcCompiler::emitConstantBufferLoad( + const DxbcRegister& reg, + DxbcRegMask writeMask) { + // Constant buffers take a two-dimensional index: + // (0) register index (immediate) + // (1) constant offset (relative) + DxbcRegisterInfo info; + info.type.ctype = DxbcScalarType::Float32; + info.type.ccount = 4; + info.type.alength = 0; + info.sclass = spv::StorageClassUniform; + + uint32_t regId = reg.idx[0].offset; + DxbcRegisterValue constId = emitIndexLoad(reg.idx[1]); + + uint32_t ptrTypeId = getPointerTypeId(info); + + const std::array indices = + {{ m_module.consti32(0), constId.id }}; + + DxbcRegisterPointer ptr; + ptr.type.ctype = info.type.ctype; + ptr.type.ccount = info.type.ccount; + ptr.id = m_module.opAccessChain(ptrTypeId, + m_constantBuffers.at(regId).varId, + indices.size(), indices.data()); + + // Load individual components from buffer + std::array ccomps = { 0, 0, 0, 0 }; + std::array scomps = { 0, 0, 0, 0 }; + uint32_t scount = 0; + + for (uint32_t i = 0; i < 4; i++) { + uint32_t sindex = reg.swizzle[i]; + + if (!writeMask[i] || ccomps[sindex]) + continue; + + uint32_t componentId = m_module.constu32(sindex); + uint32_t componentPtr = m_module.opAccessChain( + m_module.defPointerType( + getScalarTypeId(DxbcScalarType::Float32), + spv::StorageClassUniform), + ptr.id, 1, &componentId); + + ccomps[sindex] = m_module.opLoad( + getScalarTypeId(DxbcScalarType::Float32), + componentPtr); + } + + for (uint32_t i = 0; i < 4; i++) { + uint32_t sindex = reg.swizzle[i]; + + if (writeMask[i]) + scomps[scount++] = ccomps[sindex]; + } + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Float32; + result.type.ccount = scount; + result.id = scomps[0]; + + if (scount > 1) { + result.id = m_module.opCompositeConstruct( + getVectorTypeId(result.type), + scount, scomps.data()); + } + + // Apply any post-processing that might be necessary + result = emitRegisterBitcast(result, reg.dataType); + result = emitSrcOperandModifiers(result, reg.modifiers); + return result; + } + + + DxbcRegisterValue DxbcCompiler::emitRegisterLoad( + const DxbcRegister& reg, + DxbcRegMask writeMask) { + if (reg.type == DxbcOperandType::Imm32 + || reg.type == DxbcOperandType::Imm64) { + DxbcRegisterValue result; + + if (reg.componentCount == DxbcComponentCount::Component1) { + // Create one single u32 constant + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = 1; + result.id = m_module.constu32(reg.imm.u32_1); + + result = emitRegisterExtend(result, writeMask.popCount()); + } else if (reg.componentCount == DxbcComponentCount::Component4) { + // Create a u32 vector with as many components as needed + std::array indices = { }; + uint32_t indexId = 0; + + for (uint32_t i = 0; i < indices.size(); i++) { + if (writeMask[i]) { + indices.at(indexId++) = + m_module.constu32(reg.imm.u32_4[i]); + } + } + + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = writeMask.popCount(); + result.id = indices.at(0); + + if (indexId > 1) { + result.id = m_module.constComposite( + getVectorTypeId(result.type), + result.type.ccount, indices.data()); + } + + } else { + // Something went horribly wrong in the decoder or the shader is broken + throw DxvkError("DxbcCompiler: Invalid component count for immediate operand"); + } + + // Cast constants to the requested type + return emitRegisterBitcast(result, reg.dataType); + } else if (reg.type == DxbcOperandType::ConstantBuffer) { + return emitConstantBufferLoad(reg, writeMask); + } else { + // Load operand from the operand pointer + DxbcRegisterValue result = emitRegisterLoadRaw(reg); + + // Apply operand swizzle to the operand value + result = emitRegisterSwizzle(result, reg.swizzle, writeMask); + + // Cast it to the requested type. We need to do + // this after the swizzling for 64-bit types. + result = emitRegisterBitcast(result, reg.dataType); + + // Apply operand modifiers + result = emitSrcOperandModifiers(result, reg.modifiers); + return result; + } + } + + + void DxbcCompiler::emitRegisterStore( + const DxbcRegister& reg, + DxbcRegisterValue value) { + if (reg.type == DxbcOperandType::IndexableTemp) { + bool doBoundsCheck = reg.idx[1].relReg != nullptr; + DxbcRegisterValue vectorId = emitIndexLoad(reg.idx[1]); + + if (doBoundsCheck) { + uint32_t boundsCheck = m_module.opULessThan( + m_module.defBoolType(), vectorId.id, + m_module.constu32(m_xRegs.at(reg.idx[0].offset).alength)); + + DxbcConditional cond; + cond.labelIf = m_module.allocateId(); + cond.labelEnd = m_module.allocateId(); + + m_module.opSelectionMerge(cond.labelEnd, spv::SelectionControlMaskNone); + m_module.opBranchConditional(boundsCheck, cond.labelIf, cond.labelEnd); + + m_module.opLabel(cond.labelIf); + + emitValueStore(getIndexableTempPtr(reg, vectorId), value, reg.mask); + + m_module.opBranch(cond.labelEnd); + m_module.opLabel (cond.labelEnd); + } else { + emitValueStore(getIndexableTempPtr(reg, vectorId), value, reg.mask); + } + } else { + emitValueStore(emitGetOperandPtr(reg), value, reg.mask); + } + } + + + void DxbcCompiler::emitInputSetup() { + m_module.setLateConst(m_vArrayLengthId, &m_vArrayLength); + + // Copy all defined v# registers into the input array + const uint32_t vecTypeId = m_module.defVectorType(m_module.defFloatType(32), 4); + const uint32_t ptrTypeId = m_module.defPointerType(vecTypeId, spv::StorageClassPrivate); + + for (uint32_t i = 0; i < m_vRegs.size(); i++) { + if (m_vRegs.at(i).id != 0) { + const uint32_t registerId = m_module.consti32(i); + + DxbcRegisterPointer srcPtr = m_vRegs.at(i); + DxbcRegisterValue srcValue = emitRegisterBitcast( + emitValueLoad(srcPtr), DxbcScalarType::Float32); + + DxbcRegisterPointer dstPtr; + dstPtr.type = { DxbcScalarType::Float32, 4 }; + dstPtr.id = m_module.opAccessChain( + ptrTypeId, m_vArray, 1, ®isterId); + + emitValueStore(dstPtr, srcValue, DxbcRegMask::firstN(srcValue.type.ccount)); + } + } + + // Copy all system value registers into the array, + // preserving any previously written contents. + for (const DxbcSvMapping& map : m_vMappings) { + const uint32_t registerId = m_module.consti32(map.regId); + + const DxbcRegisterValue value = [&] { + switch (m_programInfo.type()) { + case DxbcProgramType::VertexShader: return emitVsSystemValueLoad(map.sv, map.regMask); + case DxbcProgramType::PixelShader: return emitPsSystemValueLoad(map.sv, map.regMask); + default: throw DxvkError(str::format("DxbcCompiler: Unexpected stage: ", m_programInfo.type())); + } + }(); + + DxbcRegisterPointer inputReg; + inputReg.type.ctype = DxbcScalarType::Float32; + inputReg.type.ccount = 4; + inputReg.id = m_module.opAccessChain( + ptrTypeId, m_vArray, 1, ®isterId); + emitValueStore(inputReg, value, map.regMask); + } + } + + + void DxbcCompiler::emitInputSetup(uint32_t vertexCount) { + m_module.setLateConst(m_vArrayLengthId, &m_vArrayLength); + + // Copy all defined v# registers into the input array. Note + // that the outer index of the array is the vertex index. + const uint32_t vecTypeId = m_module.defVectorType(m_module.defFloatType(32), 4); + const uint32_t dstPtrTypeId = m_module.defPointerType(vecTypeId, spv::StorageClassPrivate); + + for (uint32_t i = 0; i < m_vRegs.size(); i++) { + if (m_vRegs.at(i).id != 0) { + const uint32_t registerId = m_module.consti32(i); + + for (uint32_t v = 0; v < vertexCount; v++) { + std::array indices + = {{ m_module.consti32(v), registerId }}; + + DxbcRegisterPointer srcPtr; + srcPtr.type = m_vRegs.at(i).type; + srcPtr.id = m_module.opAccessChain( + m_module.defPointerType(getVectorTypeId(srcPtr.type), spv::StorageClassInput), + m_vRegs.at(i).id, 1, indices.data()); + + DxbcRegisterValue srcValue = emitRegisterBitcast( + emitValueLoad(srcPtr), DxbcScalarType::Float32); + + DxbcRegisterPointer dstPtr; + dstPtr.type = { DxbcScalarType::Float32, 4 }; + dstPtr.id = m_module.opAccessChain( + dstPtrTypeId, m_vArray, 2, indices.data()); + + emitValueStore(dstPtr, srcValue, DxbcRegMask::firstN(srcValue.type.ccount)); + } + } + } + + // Copy all system value registers into the array, + // preserving any previously written contents. + for (const DxbcSvMapping& map : m_vMappings) { + const uint32_t registerId = m_module.consti32(map.regId); + + for (uint32_t v = 0; v < vertexCount; v++) { + const DxbcRegisterValue value = [&] { + switch (m_programInfo.type()) { + case DxbcProgramType::GeometryShader: return emitGsSystemValueLoad(map.sv, map.regMask, v); + default: throw DxvkError(str::format("DxbcCompiler: Unexpected stage: ", m_programInfo.type())); + } + }(); + + std::array indices = { + m_module.consti32(v), registerId, + }; + + DxbcRegisterPointer inputReg; + inputReg.type.ctype = DxbcScalarType::Float32; + inputReg.type.ccount = 4; + inputReg.id = m_module.opAccessChain(dstPtrTypeId, + m_vArray, indices.size(), indices.data()); + emitValueStore(inputReg, value, map.regMask); + } + } + } + + + void DxbcCompiler::emitOutputSetup() { + for (const DxbcSvMapping& svMapping : m_oMappings) { + DxbcRegisterPointer outputReg = m_oRegs.at(svMapping.regId); + + if (m_programInfo.type() == DxbcProgramType::HullShader) { + uint32_t registerIndex = m_module.constu32(svMapping.regId); + + outputReg.type = { DxbcScalarType::Float32, 4 }; + outputReg.id = m_module.opAccessChain( + m_module.defPointerType( + getVectorTypeId(outputReg.type), + spv::StorageClassPrivate), + m_hs.outputPerPatch, + 1, ®isterIndex); + } + + auto sv = svMapping.sv; + auto mask = svMapping.regMask; + auto value = emitValueLoad(outputReg); + + switch (m_programInfo.type()) { + case DxbcProgramType::VertexShader: emitVsSystemValueStore(sv, mask, value); break; + case DxbcProgramType::GeometryShader: emitGsSystemValueStore(sv, mask, value); break; + case DxbcProgramType::HullShader: emitHsSystemValueStore(sv, mask, value); break; + case DxbcProgramType::DomainShader: emitDsSystemValueStore(sv, mask, value); break; + case DxbcProgramType::PixelShader: emitPsSystemValueStore(sv, mask, value); break; + default: break; + } + } + } + + + void DxbcCompiler::emitOutputDepthClamp() { + // HACK: Some drivers do not clamp FragDepth to [minDepth..maxDepth] + // before writing to the depth attachment, but we do not have acccess + // to those. Clamp to [0..1] instead. + if (m_ps.builtinDepth) { + DxbcRegisterPointer ptr; + ptr.type = { DxbcScalarType::Float32, 1 }; + ptr.id = m_ps.builtinDepth; + + DxbcRegisterValue value = emitValueLoad(ptr); + + value.id = m_module.opNClamp( + getVectorTypeId(ptr.type), + value.id, + m_module.constf32(0.0f), + m_module.constf32(1.0f)); + + emitValueStore(ptr, value, + DxbcRegMask::firstN(1)); + } + } + + + void DxbcCompiler::emitInitWorkgroupMemory() { + bool hasTgsm = false; + + SpirvMemoryOperands memoryOperands; + memoryOperands.flags = spv::MemoryAccessNonPrivatePointerMask; + + for (uint32_t i = 0; i < m_gRegs.size(); i++) { + if (!m_gRegs[i].varId) + continue; + + if (!m_cs.builtinLocalInvocationIndex) { + m_cs.builtinLocalInvocationIndex = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassInput }, + spv::BuiltInLocalInvocationIndex, + "vThreadIndexInGroup"); + } + + uint32_t intTypeId = getScalarTypeId(DxbcScalarType::Uint32); + uint32_t ptrTypeId = m_module.defPointerType( + intTypeId, spv::StorageClassWorkgroup); + + uint32_t numElements = m_gRegs[i].type == DxbcResourceType::Structured + ? m_gRegs[i].elementCount * m_gRegs[i].elementStride / 4 + : m_gRegs[i].elementCount / 4; + + uint32_t numThreads = m_cs.workgroupSizeX * + m_cs.workgroupSizeY * m_cs.workgroupSizeZ; + + uint32_t numElementsPerThread = numElements / numThreads; + uint32_t numElementsRemaining = numElements % numThreads; + + uint32_t threadId = m_module.opLoad( + intTypeId, m_cs.builtinLocalInvocationIndex); + uint32_t zeroId = m_module.constu32(0); + + for (uint32_t e = 0; e < numElementsPerThread; e++) { + uint32_t ofsId = m_module.opIAdd(intTypeId, threadId, + m_module.constu32(numThreads * e)); + + uint32_t ptrId = m_module.opAccessChain( + ptrTypeId, m_gRegs[i].varId, 1, &ofsId); + + m_module.opStore(ptrId, zeroId, memoryOperands); + } + + if (numElementsRemaining) { + uint32_t condition = m_module.opULessThan( + m_module.defBoolType(), threadId, + m_module.constu32(numElementsRemaining)); + + DxbcConditional cond; + cond.labelIf = m_module.allocateId(); + cond.labelEnd = m_module.allocateId(); + + m_module.opSelectionMerge(cond.labelEnd, spv::SelectionControlMaskNone); + m_module.opBranchConditional(condition, cond.labelIf, cond.labelEnd); + + m_module.opLabel(cond.labelIf); + + uint32_t ofsId = m_module.opIAdd(intTypeId, threadId, + m_module.constu32(numThreads * numElementsPerThread)); + + uint32_t ptrId = m_module.opAccessChain( + ptrTypeId, m_gRegs[i].varId, 1, &ofsId); + + m_module.opStore(ptrId, zeroId, memoryOperands); + + m_module.opBranch(cond.labelEnd); + m_module.opLabel (cond.labelEnd); + } + + hasTgsm = true; + } + + if (hasTgsm) { + m_module.opControlBarrier( + m_module.constu32(spv::ScopeWorkgroup), + m_module.constu32(spv::ScopeWorkgroup), + m_module.constu32(spv::MemorySemanticsWorkgroupMemoryMask + | spv::MemorySemanticsAcquireReleaseMask + | spv::MemorySemanticsMakeAvailableMask + | spv::MemorySemanticsMakeVisibleMask)); + } + } + + + DxbcRegisterValue DxbcCompiler::emitVsSystemValueLoad( + DxbcSystemValue sv, + DxbcRegMask mask) { + switch (sv) { + case DxbcSystemValue::VertexId: { + const uint32_t typeId = getScalarTypeId(DxbcScalarType::Uint32); + + if (m_vs.builtinVertexId == 0) { + m_vs.builtinVertexId = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassInput }, + spv::BuiltInVertexIndex, + "vs_vertex_index"); + } + + if (m_vs.builtinBaseVertex == 0) { + m_vs.builtinBaseVertex = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassInput }, + spv::BuiltInBaseVertex, + "vs_base_vertex"); + } + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = 1; + result.id = m_module.opISub(typeId, + m_module.opLoad(typeId, m_vs.builtinVertexId), + m_module.opLoad(typeId, m_vs.builtinBaseVertex)); + return result; + } break; + + case DxbcSystemValue::InstanceId: { + const uint32_t typeId = getScalarTypeId(DxbcScalarType::Uint32); + + if (m_vs.builtinInstanceId == 0) { + m_vs.builtinInstanceId = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassInput }, + spv::BuiltInInstanceIndex, + "vs_instance_index"); + } + + if (m_vs.builtinBaseInstance == 0) { + m_vs.builtinBaseInstance = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassInput }, + spv::BuiltInBaseInstance, + "vs_base_instance"); + } + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = 1; + result.id = m_module.opISub(typeId, + m_module.opLoad(typeId, m_vs.builtinInstanceId), + m_module.opLoad(typeId, m_vs.builtinBaseInstance)); + return result; + } break; + + default: + throw DxvkError(str::format( + "DxbcCompiler: Unhandled VS SV input: ", sv)); + } + } + + + DxbcRegisterValue DxbcCompiler::emitGsSystemValueLoad( + DxbcSystemValue sv, + DxbcRegMask mask, + uint32_t vertexId) { + switch (sv) { + case DxbcSystemValue::Position: { + uint32_t arrayIndex = m_module.consti32(vertexId); + + if (!m_positionIn) { + m_positionIn = emitNewBuiltinVariable({ + { DxbcScalarType::Float32, 4, primitiveVertexCount(m_gs.inputPrimitive) }, + spv::StorageClassInput }, + spv::BuiltInPosition, + "in_position"); + } + + DxbcRegisterPointer ptrIn; + ptrIn.type.ctype = DxbcScalarType::Float32; + ptrIn.type.ccount = 4; + ptrIn.id = m_module.opAccessChain( + m_module.defPointerType(getVectorTypeId(ptrIn.type), spv::StorageClassInput), + m_positionIn, 1, &arrayIndex); + + return emitRegisterExtract(emitValueLoad(ptrIn), mask); + } break; + + default: + throw DxvkError(str::format( + "DxbcCompiler: Unhandled GS SV input: ", sv)); + } + } + + + DxbcRegisterValue DxbcCompiler::emitPsSystemValueLoad( + DxbcSystemValue sv, + DxbcRegMask mask) { + switch (sv) { + case DxbcSystemValue::Position: { + if (m_ps.builtinFragCoord == 0) { + m_ps.builtinFragCoord = emitNewBuiltinVariable({ + { DxbcScalarType::Float32, 4, 0 }, + spv::StorageClassInput }, + spv::BuiltInFragCoord, + "ps_frag_coord"); + } + + DxbcRegisterPointer ptrIn; + ptrIn.type = { DxbcScalarType::Float32, 4 }; + ptrIn.id = m_ps.builtinFragCoord; + + // The X, Y and Z components of the SV_POSITION semantic + // are identical to Vulkan's FragCoord builtin, but we + // need to compute the reciprocal of the W component. + DxbcRegisterValue fragCoord = emitValueLoad(ptrIn); + + uint32_t componentIndex = 3; + uint32_t t_f32 = m_module.defFloatType(32); + uint32_t v_wComp = m_module.opCompositeExtract(t_f32, fragCoord.id, 1, &componentIndex); + v_wComp = m_module.opFDiv(t_f32, m_module.constf32(1.0f), v_wComp); + + fragCoord.id = m_module.opCompositeInsert( + getVectorTypeId(fragCoord.type), + v_wComp, fragCoord.id, + 1, &componentIndex); + + return emitRegisterExtract(fragCoord, mask); + } break; + + case DxbcSystemValue::IsFrontFace: { + if (m_ps.builtinIsFrontFace == 0) { + m_ps.builtinIsFrontFace = emitNewBuiltinVariable({ + { DxbcScalarType::Bool, 1, 0 }, + spv::StorageClassInput }, + spv::BuiltInFrontFacing, + "ps_is_front_face"); + } + + DxbcRegisterValue result; + result.type.ctype = DxbcScalarType::Uint32; + result.type.ccount = 1; + result.id = m_module.opSelect( + getVectorTypeId(result.type), + m_module.opLoad( + m_module.defBoolType(), + m_ps.builtinIsFrontFace), + m_module.constu32(0xFFFFFFFF), + m_module.constu32(0x00000000)); + return result; + } break; + + case DxbcSystemValue::PrimitiveId: { + if (m_primitiveIdIn == 0) { + m_module.enableCapability(spv::CapabilityGeometry); + + m_primitiveIdIn = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassInput }, + spv::BuiltInPrimitiveId, + "ps_primitive_id"); + } + + DxbcRegisterPointer ptrIn; + ptrIn.type = { DxbcScalarType::Uint32, 1 }; + ptrIn.id = m_primitiveIdIn; + + return emitValueLoad(ptrIn); + } break; + + case DxbcSystemValue::SampleIndex: { + if (m_ps.builtinSampleId == 0) { + m_module.enableCapability(spv::CapabilitySampleRateShading); + + m_ps.builtinSampleId = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassInput }, + spv::BuiltInSampleId, + "ps_sample_id"); + } + + DxbcRegisterPointer ptrIn; + ptrIn.type.ctype = DxbcScalarType::Uint32; + ptrIn.type.ccount = 1; + ptrIn.id = m_ps.builtinSampleId; + + return emitValueLoad(ptrIn); + } break; + + case DxbcSystemValue::RenderTargetId: { + if (m_ps.builtinLayer == 0) { + m_module.enableCapability(spv::CapabilityGeometry); + + m_ps.builtinLayer = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassInput }, + spv::BuiltInLayer, + "v_layer"); + } + + DxbcRegisterPointer ptr; + ptr.type.ctype = DxbcScalarType::Uint32; + ptr.type.ccount = 1; + ptr.id = m_ps.builtinLayer; + + return emitValueLoad(ptr); + } break; + + case DxbcSystemValue::ViewportId: { + if (m_ps.builtinViewportId == 0) { + m_module.enableCapability(spv::CapabilityMultiViewport); + + m_ps.builtinViewportId = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassInput }, + spv::BuiltInViewportIndex, + "v_viewport"); + } + + DxbcRegisterPointer ptr; + ptr.type.ctype = DxbcScalarType::Uint32; + ptr.type.ccount = 1; + ptr.id = m_ps.builtinViewportId; + + return emitValueLoad(ptr); + } break; + + default: + throw DxvkError(str::format( + "DxbcCompiler: Unhandled PS SV input: ", sv)); + } + } + + + void DxbcCompiler::emitVsSystemValueStore( + DxbcSystemValue sv, + DxbcRegMask mask, + const DxbcRegisterValue& value) { + switch (sv) { + case DxbcSystemValue::Position: { + if (!m_positionOut) { + m_positionOut = emitNewBuiltinVariable({ + { DxbcScalarType::Float32, 4, 0 }, + spv::StorageClassOutput }, + spv::BuiltInPosition, + "out_position"); + } + + DxbcRegisterPointer ptr; + ptr.type.ctype = DxbcScalarType::Float32; + ptr.type.ccount = 4; + ptr.id = m_positionOut; + + emitValueStore(ptr, value, mask); + } break; + + case DxbcSystemValue::RenderTargetId: { + if (m_programInfo.type() != DxbcProgramType::GeometryShader) + m_module.enableCapability(spv::CapabilityShaderLayer); + + if (m_gs.builtinLayer == 0) { + m_module.enableCapability(spv::CapabilityGeometry); + + m_gs.builtinLayer = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassOutput }, + spv::BuiltInLayer, + "o_layer"); + } + + DxbcRegisterPointer ptr; + ptr.type = { DxbcScalarType::Uint32, 1 }; + ptr.id = m_gs.builtinLayer; + + emitValueStore( + ptr, emitRegisterExtract(value, mask), + DxbcRegMask(true, false, false, false)); + } break; + + case DxbcSystemValue::ViewportId: { + if (m_programInfo.type() != DxbcProgramType::GeometryShader) + m_module.enableCapability(spv::CapabilityShaderViewportIndex); + + if (m_gs.builtinViewportId == 0) { + m_module.enableCapability(spv::CapabilityMultiViewport); + + m_gs.builtinViewportId = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassOutput }, + spv::BuiltInViewportIndex, + "o_viewport"); + } + + DxbcRegisterPointer ptr; + ptr.type = { DxbcScalarType::Uint32, 1}; + ptr.id = m_gs.builtinViewportId; + + emitValueStore( + ptr, emitRegisterExtract(value, mask), + DxbcRegMask(true, false, false, false)); + } break; + + default: + Logger::warn(str::format( + "DxbcCompiler: Unhandled VS SV output: ", sv)); + } + } + + + void DxbcCompiler::emitHsSystemValueStore( + DxbcSystemValue sv, + DxbcRegMask mask, + const DxbcRegisterValue& value) { + if (sv >= DxbcSystemValue::FinalQuadUeq0EdgeTessFactor + && sv <= DxbcSystemValue::FinalLineDensityTessFactor) { + struct TessFactor { + uint32_t array = 0; + uint32_t index = 0; + }; + + static const std::array s_tessFactors = {{ + { m_hs.builtinTessLevelOuter, 0 }, // FinalQuadUeq0EdgeTessFactor + { m_hs.builtinTessLevelOuter, 1 }, // FinalQuadVeq0EdgeTessFactor + { m_hs.builtinTessLevelOuter, 2 }, // FinalQuadUeq1EdgeTessFactor + { m_hs.builtinTessLevelOuter, 3 }, // FinalQuadVeq1EdgeTessFactor + { m_hs.builtinTessLevelInner, 0 }, // FinalQuadUInsideTessFactor + { m_hs.builtinTessLevelInner, 1 }, // FinalQuadVInsideTessFactor + { m_hs.builtinTessLevelOuter, 0 }, // FinalTriUeq0EdgeTessFactor + { m_hs.builtinTessLevelOuter, 1 }, // FinalTriVeq0EdgeTessFactor + { m_hs.builtinTessLevelOuter, 2 }, // FinalTriWeq0EdgeTessFactor + { m_hs.builtinTessLevelInner, 0 }, // FinalTriInsideTessFactor + { m_hs.builtinTessLevelOuter, 0 }, // FinalLineDensityTessFactor + { m_hs.builtinTessLevelOuter, 1 }, // FinalLineDetailTessFactor + }}; + + const TessFactor tessFactor = s_tessFactors.at(uint32_t(sv) + - uint32_t(DxbcSystemValue::FinalQuadUeq0EdgeTessFactor)); + + const uint32_t tessFactorArrayIndex + = m_module.constu32(tessFactor.index); + + // Apply global tess factor limit + float maxTessFactor = m_hs.maxTessFactor; + + if (m_moduleInfo.tess != nullptr) { + if (m_moduleInfo.tess->maxTessFactor < maxTessFactor) + maxTessFactor = m_moduleInfo.tess->maxTessFactor; + } + + DxbcRegisterValue tessValue = emitRegisterExtract(value, mask); + tessValue.id = m_module.opNClamp(getVectorTypeId(tessValue.type), + tessValue.id, m_module.constf32(0.0f), + m_module.constf32(maxTessFactor)); + + DxbcRegisterPointer ptr; + ptr.type.ctype = DxbcScalarType::Float32; + ptr.type.ccount = 1; + ptr.id = m_module.opAccessChain( + m_module.defPointerType( + getVectorTypeId(ptr.type), + spv::StorageClassOutput), + tessFactor.array, 1, + &tessFactorArrayIndex); + + emitValueStore(ptr, tessValue, + DxbcRegMask(true, false, false, false)); + } else { + Logger::warn(str::format( + "DxbcCompiler: Unhandled HS SV output: ", sv)); + } + } + + + void DxbcCompiler::emitGsSystemValueStore( + DxbcSystemValue sv, + DxbcRegMask mask, + const DxbcRegisterValue& value) { + switch (sv) { + case DxbcSystemValue::Position: + case DxbcSystemValue::CullDistance: + case DxbcSystemValue::ClipDistance: + case DxbcSystemValue::RenderTargetId: + case DxbcSystemValue::ViewportId: + emitVsSystemValueStore(sv, mask, value); + break; + + case DxbcSystemValue::PrimitiveId: { + if (m_primitiveIdOut == 0) { + m_primitiveIdOut = emitNewBuiltinVariable({ + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassOutput }, + spv::BuiltInPrimitiveId, + "gs_primitive_id"); + } + + DxbcRegisterPointer ptr; + ptr.type = { DxbcScalarType::Uint32, 1}; + ptr.id = m_primitiveIdOut; + + emitValueStore( + ptr, emitRegisterExtract(value, mask), + DxbcRegMask(true, false, false, false)); + } break; + + default: + Logger::warn(str::format( + "DxbcCompiler: Unhandled GS SV output: ", sv)); + } + } + + + void DxbcCompiler::emitPsSystemValueStore( + DxbcSystemValue sv, + DxbcRegMask mask, + const DxbcRegisterValue& value) { + Logger::warn(str::format( + "DxbcCompiler: Unhandled PS SV output: ", sv)); + } + + + void DxbcCompiler::emitDsSystemValueStore( + DxbcSystemValue sv, + DxbcRegMask mask, + const DxbcRegisterValue& value) { + switch (sv) { + case DxbcSystemValue::Position: + case DxbcSystemValue::CullDistance: + case DxbcSystemValue::ClipDistance: + case DxbcSystemValue::RenderTargetId: + case DxbcSystemValue::ViewportId: + emitVsSystemValueStore(sv, mask, value); + break; + + default: + Logger::warn(str::format( + "DxbcCompiler: Unhandled DS SV output: ", sv)); + } + } + + + void DxbcCompiler::emitClipCullStore( + DxbcSystemValue sv, + uint32_t dstArray) { + uint32_t offset = 0; + + if (dstArray == 0) + return; + + for (auto e = m_osgn->begin(); e != m_osgn->end(); e++) { + if (e->systemValue == sv) { + DxbcRegisterPointer srcPtr = m_oRegs.at(e->registerId); + DxbcRegisterValue srcValue = emitValueLoad(srcPtr); + + for (uint32_t i = 0; i < 4; i++) { + if (e->componentMask[i]) { + uint32_t offsetId = m_module.consti32(offset++); + + DxbcRegisterValue component = emitRegisterExtract( + srcValue, DxbcRegMask::select(i)); + + DxbcRegisterPointer dstPtr; + dstPtr.type = { DxbcScalarType::Float32, 1 }; + dstPtr.id = m_module.opAccessChain( + m_module.defPointerType( + getVectorTypeId(dstPtr.type), + spv::StorageClassOutput), + dstArray, 1, &offsetId); + + emitValueStore(dstPtr, component, + DxbcRegMask(true, false, false, false)); + } + } + } + } + } + + + void DxbcCompiler::emitClipCullLoad( + DxbcSystemValue sv, + uint32_t srcArray) { + uint32_t offset = 0; + + if (srcArray == 0) + return; + + for (auto e = m_isgn->begin(); e != m_isgn->end(); e++) { + if (e->systemValue == sv) { + // Load individual components from the source array + uint32_t componentIndex = 0; + std::array componentIds = {{ 0, 0, 0, 0 }}; + + for (uint32_t i = 0; i < 4; i++) { + if (e->componentMask[i]) { + uint32_t offsetId = m_module.consti32(offset++); + + DxbcRegisterPointer srcPtr; + srcPtr.type = { DxbcScalarType::Float32, 1 }; + srcPtr.id = m_module.opAccessChain( + m_module.defPointerType( + getVectorTypeId(srcPtr.type), + spv::StorageClassInput), + srcArray, 1, &offsetId); + + componentIds[componentIndex++] + = emitValueLoad(srcPtr).id; + } + } + + // Put everything into one vector + DxbcRegisterValue dstValue; + dstValue.type = { DxbcScalarType::Float32, componentIndex }; + dstValue.id = componentIds[0]; + + if (componentIndex > 1) { + dstValue.id = m_module.opCompositeConstruct( + getVectorTypeId(dstValue.type), + componentIndex, componentIds.data()); + } + + // Store vector to the input array + uint32_t registerId = m_module.consti32(e->registerId); + + DxbcRegisterPointer dstInput; + dstInput.type = { DxbcScalarType::Float32, 4 }; + dstInput.id = m_module.opAccessChain( + m_module.defPointerType( + getVectorTypeId(dstInput.type), + spv::StorageClassPrivate), + m_vArray, 1, ®isterId); + + emitValueStore(dstInput, dstValue, e->componentMask); + } + } + } + + + void DxbcCompiler::emitPointSizeStore() { + if (m_moduleInfo.options.needsPointSizeExport) { + uint32_t pointSizeId = emitNewBuiltinVariable(DxbcRegisterInfo { + { DxbcScalarType::Float32, 1, 0 }, + spv::StorageClassOutput }, + spv::BuiltInPointSize, + "point_size"); + + m_module.opStore(pointSizeId, m_module.constf32(1.0f)); + } + } + + + void DxbcCompiler::emitInit() { + // Set up common capabilities for all shaders + m_module.enableCapability(spv::CapabilityShader); + m_module.enableCapability(spv::CapabilityImageQuery); + + // Initialize the shader module with capabilities + // etc. Each shader type has its own peculiarities. + switch (m_programInfo.type()) { + case DxbcProgramType::VertexShader: emitVsInit(); break; + case DxbcProgramType::HullShader: emitHsInit(); break; + case DxbcProgramType::DomainShader: emitDsInit(); break; + case DxbcProgramType::GeometryShader: emitGsInit(); break; + case DxbcProgramType::PixelShader: emitPsInit(); break; + case DxbcProgramType::ComputeShader: emitCsInit(); break; + default: throw DxvkError("Invalid shader stage"); + } + } + + + void DxbcCompiler::emitFunctionBegin( + uint32_t entryPoint, + uint32_t returnType, + uint32_t funcType) { + this->emitFunctionEnd(); + + m_module.functionBegin( + returnType, entryPoint, funcType, + spv::FunctionControlMaskNone); + + m_insideFunction = true; + } + + + void DxbcCompiler::emitFunctionEnd() { + if (m_insideFunction) { + m_module.opReturn(); + m_module.functionEnd(); + } + + m_insideFunction = false; + } + + + void DxbcCompiler::emitFunctionLabel() { + m_module.opLabel(m_module.allocateId()); + } + + + void DxbcCompiler::emitMainFunctionBegin() { + this->emitFunctionBegin( + m_entryPointId, + m_module.defVoidType(), + m_module.defFunctionType( + m_module.defVoidType(), 0, nullptr)); + this->emitFunctionLabel(); + } + + + void DxbcCompiler::emitVsInit() { + m_module.enableCapability(spv::CapabilityClipDistance); + m_module.enableCapability(spv::CapabilityCullDistance); + m_module.enableCapability(spv::CapabilityDrawParameters); + + // Standard input array + emitDclInputArray(0); + + // Cull/clip distances as outputs + m_clipDistances = emitDclClipCullDistanceArray( + m_analysis->clipCullOut.numClipPlanes, + spv::BuiltInClipDistance, + spv::StorageClassOutput); + + m_cullDistances = emitDclClipCullDistanceArray( + m_analysis->clipCullOut.numCullPlanes, + spv::BuiltInCullDistance, + spv::StorageClassOutput); + + // Main function of the vertex shader + m_vs.functionId = m_module.allocateId(); + m_module.setDebugName(m_vs.functionId, "vs_main"); + + this->emitFunctionBegin( + m_vs.functionId, + m_module.defVoidType(), + m_module.defFunctionType( + m_module.defVoidType(), 0, nullptr)); + this->emitFunctionLabel(); + } + + + void DxbcCompiler::emitHsInit() { + m_module.enableCapability(spv::CapabilityTessellation); + m_module.enableCapability(spv::CapabilityClipDistance); + m_module.enableCapability(spv::CapabilityCullDistance); + + m_hs.builtinInvocationId = emitNewBuiltinVariable( + DxbcRegisterInfo { + { DxbcScalarType::Uint32, 1, 0 }, + spv::StorageClassInput }, + spv::BuiltInInvocationId, + "vOutputControlPointId"); + + m_hs.builtinTessLevelOuter = emitBuiltinTessLevelOuter(spv::StorageClassOutput); + m_hs.builtinTessLevelInner = emitBuiltinTessLevelInner(spv::StorageClassOutput); + } + + + void DxbcCompiler::emitDsInit() { + m_module.enableCapability(spv::CapabilityTessellation); + m_module.enableCapability(spv::CapabilityClipDistance); + m_module.enableCapability(spv::CapabilityCullDistance); + + m_ds.builtinTessLevelOuter = emitBuiltinTessLevelOuter(spv::StorageClassInput); + m_ds.builtinTessLevelInner = emitBuiltinTessLevelInner(spv::StorageClassInput); + + // Cull/clip distances as outputs + m_clipDistances = emitDclClipCullDistanceArray( + m_analysis->clipCullOut.numClipPlanes, + spv::BuiltInClipDistance, + spv::StorageClassOutput); + + m_cullDistances = emitDclClipCullDistanceArray( + m_analysis->clipCullOut.numCullPlanes, + spv::BuiltInCullDistance, + spv::StorageClassOutput); + + // Main function of the domain shader + m_ds.functionId = m_module.allocateId(); + m_module.setDebugName(m_ds.functionId, "ds_main"); + + this->emitFunctionBegin( + m_ds.functionId, + m_module.defVoidType(), + m_module.defFunctionType( + m_module.defVoidType(), 0, nullptr)); + this->emitFunctionLabel(); + } + + + void DxbcCompiler::emitGsInit() { + m_module.enableCapability(spv::CapabilityGeometry); + m_module.enableCapability(spv::CapabilityClipDistance); + m_module.enableCapability(spv::CapabilityCullDistance); + + // Enable capabilities for xfb mode if necessary + if (m_moduleInfo.xfb) { + m_module.enableCapability(spv::CapabilityGeometryStreams); + m_module.enableCapability(spv::CapabilityTransformFeedback); + + m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeXfb); + } + + // We only need outputs if rasterization is enabled + m_gs.needsOutputSetup = !m_moduleInfo.xfb + || m_moduleInfo.xfb->rasterizedStream >= 0; + + // Cull/clip distances as outputs + m_clipDistances = emitDclClipCullDistanceArray( + m_analysis->clipCullOut.numClipPlanes, + spv::BuiltInClipDistance, + spv::StorageClassOutput); + + m_cullDistances = emitDclClipCullDistanceArray( + m_analysis->clipCullOut.numCullPlanes, + spv::BuiltInCullDistance, + spv::StorageClassOutput); + + // Emit Xfb variables if necessary + if (m_moduleInfo.xfb) + emitXfbOutputDeclarations(); + + // Main function of the vertex shader + m_gs.functionId = m_module.allocateId(); + m_module.setDebugName(m_gs.functionId, "gs_main"); + + this->emitFunctionBegin( + m_gs.functionId, + m_module.defVoidType(), + m_module.defFunctionType( + m_module.defVoidType(), 0, nullptr)); + this->emitFunctionLabel(); + } + + + void DxbcCompiler::emitPsInit() { + m_module.enableCapability(spv::CapabilityDerivativeControl); + + m_module.setExecutionMode(m_entryPointId, + spv::ExecutionModeOriginUpperLeft); + + // Standard input array + emitDclInputArray(0); + + // Cull/clip distances as inputs + m_clipDistances = emitDclClipCullDistanceArray( + m_analysis->clipCullIn.numClipPlanes, + spv::BuiltInClipDistance, + spv::StorageClassInput); + + m_cullDistances = emitDclClipCullDistanceArray( + m_analysis->clipCullIn.numCullPlanes, + spv::BuiltInCullDistance, + spv::StorageClassInput); + + // Main function of the pixel shader + m_ps.functionId = m_module.allocateId(); + m_module.setDebugName(m_ps.functionId, "ps_main"); + + this->emitFunctionBegin( + m_ps.functionId, + m_module.defVoidType(), + m_module.defFunctionType( + m_module.defVoidType(), 0, nullptr)); + this->emitFunctionLabel(); + } + + + void DxbcCompiler::emitCsInit() { + // Main function of the compute shader + m_cs.functionId = m_module.allocateId(); + m_module.setDebugName(m_cs.functionId, "cs_main"); + + this->emitFunctionBegin( + m_cs.functionId, + m_module.defVoidType(), + m_module.defFunctionType( + m_module.defVoidType(), 0, nullptr)); + this->emitFunctionLabel(); + } + + + void DxbcCompiler::emitVsFinalize() { + this->emitMainFunctionBegin(); + this->emitInputSetup(); + m_module.opFunctionCall( + m_module.defVoidType(), + m_vs.functionId, 0, nullptr); + this->emitOutputSetup(); + this->emitClipCullStore(DxbcSystemValue::ClipDistance, m_clipDistances); + this->emitClipCullStore(DxbcSystemValue::CullDistance, m_cullDistances); + this->emitPointSizeStore(); + this->emitFunctionEnd(); + } + + + void DxbcCompiler::emitHsFinalize() { + if (m_hs.cpPhase.functionId == 0) + m_hs.cpPhase = this->emitNewHullShaderPassthroughPhase(); + + // Control point phase + this->emitMainFunctionBegin(); + this->emitInputSetup(m_hs.vertexCountIn); + this->emitHsControlPointPhase(m_hs.cpPhase); + this->emitHsPhaseBarrier(); + + // Fork-join phases and output setup + this->emitHsInvocationBlockBegin(1); + + for (const auto& phase : m_hs.forkPhases) + this->emitHsForkJoinPhase(phase); + + for (const auto& phase : m_hs.joinPhases) + this->emitHsForkJoinPhase(phase); + + this->emitOutputSetup(); + this->emitHsOutputSetup(); + this->emitHsInvocationBlockEnd(); + this->emitFunctionEnd(); + } + + + void DxbcCompiler::emitDsFinalize() { + this->emitMainFunctionBegin(); + m_module.opFunctionCall( + m_module.defVoidType(), + m_ds.functionId, 0, nullptr); + this->emitOutputSetup(); + this->emitClipCullStore(DxbcSystemValue::ClipDistance, m_clipDistances); + this->emitClipCullStore(DxbcSystemValue::CullDistance, m_cullDistances); + this->emitFunctionEnd(); + } + + + void DxbcCompiler::emitGsFinalize() { + if (!m_gs.invocationCount) + m_module.setInvocations(m_entryPointId, 1); + + this->emitMainFunctionBegin(); + this->emitInputSetup( + primitiveVertexCount(m_gs.inputPrimitive)); + m_module.opFunctionCall( + m_module.defVoidType(), + m_gs.functionId, 0, nullptr); + // No output setup at this point as that was + // already done during the EmitVertex step + this->emitFunctionEnd(); + } + + + void DxbcCompiler::emitPsFinalize() { + this->emitMainFunctionBegin(); + this->emitInputSetup(); + this->emitClipCullLoad(DxbcSystemValue::ClipDistance, m_clipDistances); + this->emitClipCullLoad(DxbcSystemValue::CullDistance, m_cullDistances); + + if (m_hasRasterizerOrderedUav) { + // For simplicity, just lock the entire fragment shader + // if there are any rasterizer ordered views. + m_module.enableExtension("SPV_EXT_fragment_shader_interlock"); + + if (m_module.hasCapability(spv::CapabilitySampleRateShading) + && m_moduleInfo.options.enableSampleShadingInterlock) { + m_module.enableCapability(spv::CapabilityFragmentShaderSampleInterlockEXT); + m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeSampleInterlockOrderedEXT); + } else { + m_module.enableCapability(spv::CapabilityFragmentShaderPixelInterlockEXT); + m_module.setExecutionMode(m_entryPointId, spv::ExecutionModePixelInterlockOrderedEXT); + } + + m_module.opBeginInvocationInterlock(); + } + + m_module.opFunctionCall( + m_module.defVoidType(), + m_ps.functionId, 0, nullptr); + + if (m_hasRasterizerOrderedUav) + m_module.opEndInvocationInterlock(); + + this->emitOutputSetup(); + + if (m_moduleInfo.options.useDepthClipWorkaround) + this->emitOutputDepthClamp(); + + this->emitFunctionEnd(); + } + + + void DxbcCompiler::emitCsFinalize() { + this->emitMainFunctionBegin(); + + if (m_moduleInfo.options.zeroInitWorkgroupMemory) + this->emitInitWorkgroupMemory(); + + m_module.opFunctionCall( + m_module.defVoidType(), + m_cs.functionId, 0, nullptr); + + this->emitFunctionEnd(); + } + + + void DxbcCompiler::emitXfbOutputDeclarations() { + for (uint32_t i = 0; i < m_moduleInfo.xfb->entryCount; i++) { + const DxbcXfbEntry* xfbEntry = m_moduleInfo.xfb->entries + i; + const DxbcSgnEntry* sigEntry = m_osgn->find( + xfbEntry->semanticName, + xfbEntry->semanticIndex, + xfbEntry->streamId); + + if (sigEntry == nullptr) + continue; + + DxbcRegisterInfo varInfo; + varInfo.type.ctype = DxbcScalarType::Float32; + varInfo.type.ccount = xfbEntry->componentCount; + varInfo.type.alength = 0; + varInfo.sclass = spv::StorageClassOutput; + + uint32_t dstComponentMask = (1 << xfbEntry->componentCount) - 1; + uint32_t srcComponentMask = dstComponentMask + << sigEntry->componentMask.firstSet() + << xfbEntry->componentIndex; + + DxbcXfbVar xfbVar; + xfbVar.varId = emitNewVariable(varInfo); + xfbVar.streamId = xfbEntry->streamId; + xfbVar.outputId = sigEntry->registerId; + xfbVar.srcMask = DxbcRegMask(srcComponentMask); + xfbVar.dstMask = DxbcRegMask(dstComponentMask); + m_xfbVars.push_back(xfbVar); + + m_module.setDebugName(xfbVar.varId, + str::format("xfb", i).c_str()); + + m_module.decorateXfb(xfbVar.varId, + xfbEntry->streamId, xfbEntry->bufferId, xfbEntry->offset, + m_moduleInfo.xfb->strides[xfbEntry->bufferId]); + } + + // TODO Compact location/component assignment + for (uint32_t i = 0; i < m_xfbVars.size(); i++) { + m_xfbVars[i].location = i; + m_xfbVars[i].component = 0; + } + + for (uint32_t i = 0; i < m_xfbVars.size(); i++) { + const DxbcXfbVar* var = &m_xfbVars[i]; + + m_module.decorateLocation (var->varId, var->location); + m_module.decorateComponent(var->varId, var->component); + } + } + + + void DxbcCompiler::emitXfbOutputSetup( + uint32_t streamId, + bool passthrough) { + for (size_t i = 0; i < m_xfbVars.size(); i++) { + if (m_xfbVars[i].streamId == streamId) { + DxbcRegisterPointer srcPtr = passthrough + ? m_vRegs[m_xfbVars[i].outputId] + : m_oRegs[m_xfbVars[i].outputId]; + + if (passthrough) { + srcPtr = emitArrayAccess(srcPtr, + spv::StorageClassInput, + m_module.constu32(0)); + } + + DxbcRegisterPointer dstPtr; + dstPtr.type.ctype = DxbcScalarType::Float32; + dstPtr.type.ccount = m_xfbVars[i].dstMask.popCount(); + dstPtr.id = m_xfbVars[i].varId; + + DxbcRegisterValue value = emitRegisterExtract( + emitValueLoad(srcPtr), m_xfbVars[i].srcMask); + emitValueStore(dstPtr, value, m_xfbVars[i].dstMask); + } + } + } + + + void DxbcCompiler::emitHsControlPointPhase( + const DxbcCompilerHsControlPointPhase& phase) { + m_module.opFunctionCall( + m_module.defVoidType(), + phase.functionId, 0, nullptr); + } + + + void DxbcCompiler::emitHsForkJoinPhase( + const DxbcCompilerHsForkJoinPhase& phase) { + for (uint32_t i = 0; i < phase.instanceCount; i++) { + uint32_t invocationId = m_module.constu32(i); + + m_module.opFunctionCall( + m_module.defVoidType(), + phase.functionId, 1, + &invocationId); + } + } + + + void DxbcCompiler::emitDclInputArray(uint32_t vertexCount) { + DxbcVectorType info; + info.ctype = DxbcScalarType::Float32; + info.ccount = 4; + + // Define the array type. This will be two-dimensional + // in some shaders, with the outer index representing + // the vertex ID within an invocation. + m_vArrayLength = m_isgn != nullptr ? std::max(1u, m_isgn->maxRegisterCount()) : 1; + m_vArrayLengthId = m_module.lateConst32(getScalarTypeId(DxbcScalarType::Uint32)); + + uint32_t vectorTypeId = getVectorTypeId(info); + uint32_t arrayTypeId = m_module.defArrayType(vectorTypeId, m_vArrayLengthId); + + if (vertexCount != 0) { + arrayTypeId = m_module.defArrayType( + arrayTypeId, m_module.constu32(vertexCount)); + } + + // Define the actual variable. Note that this is private + // because we will copy input registers and some system + // variables to the array during the setup phase. + const uint32_t ptrTypeId = m_module.defPointerType( + arrayTypeId, spv::StorageClassPrivate); + + const uint32_t varId = m_module.newVar( + ptrTypeId, spv::StorageClassPrivate); + + m_module.setDebugName(varId, "shader_in"); + m_vArray = varId; + } + + + uint32_t DxbcCompiler::emitDclClipCullDistanceArray( + uint32_t length, + spv::BuiltIn builtIn, + spv::StorageClass storageClass) { + if (length == 0) + return 0; + + uint32_t t_f32 = m_module.defFloatType(32); + uint32_t t_arr = m_module.defArrayType(t_f32, m_module.constu32(length)); + uint32_t t_ptr = m_module.defPointerType(t_arr, storageClass); + uint32_t varId = m_module.newVar(t_ptr, storageClass); + + m_module.decorateBuiltIn(varId, builtIn); + m_module.setDebugName(varId, + builtIn == spv::BuiltInClipDistance + ? "clip_distances" + : "cull_distances"); + + return varId; + } + + + DxbcCompilerHsControlPointPhase DxbcCompiler::emitNewHullShaderControlPointPhase() { + uint32_t funTypeId = m_module.defFunctionType( + m_module.defVoidType(), 0, nullptr); + + uint32_t funId = m_module.allocateId(); + + this->emitFunctionBegin(funId, + m_module.defVoidType(), + funTypeId); + this->emitFunctionLabel(); + + DxbcCompilerHsControlPointPhase result; + result.functionId = funId; + return result; + } + + + DxbcCompilerHsControlPointPhase DxbcCompiler::emitNewHullShaderPassthroughPhase() { + uint32_t funTypeId = m_module.defFunctionType( + m_module.defVoidType(), 0, nullptr); + + // Begin passthrough function + uint32_t funId = m_module.allocateId(); + m_module.setDebugName(funId, "hs_passthrough"); + + this->emitFunctionBegin(funId, + m_module.defVoidType(), + funTypeId); + this->emitFunctionLabel(); + + // We'll basically copy each input variable to the corresponding + // output, using the shader's invocation ID as the array index. + const uint32_t invocationId = m_module.opLoad( + getScalarTypeId(DxbcScalarType::Uint32), + m_hs.builtinInvocationId); + + for (auto i = m_isgn->begin(); i != m_isgn->end(); i++) { + this->emitDclInput( + i->registerId, m_hs.vertexCountIn, + i->componentMask, + DxbcSystemValue::None, + DxbcInterpolationMode::Undefined); + + // Vector type index + const std::array dstIndices + = {{ invocationId, m_module.constu32(i->registerId) }}; + + DxbcRegisterPointer srcPtr; + srcPtr.type = m_vRegs.at(i->registerId).type; + srcPtr.id = m_module.opAccessChain( + m_module.defPointerType(getVectorTypeId(srcPtr.type), spv::StorageClassInput), + m_vRegs.at(i->registerId).id, 1, &invocationId); + + DxbcRegisterValue srcValue = emitRegisterBitcast( + emitValueLoad(srcPtr), DxbcScalarType::Float32); + + DxbcRegisterPointer dstPtr; + dstPtr.type = { DxbcScalarType::Float32, 4 }; + dstPtr.id = m_module.opAccessChain( + m_module.defPointerType(getVectorTypeId(dstPtr.type), spv::StorageClassOutput), + m_hs.outputPerVertex, dstIndices.size(), dstIndices.data()); + + emitValueStore(dstPtr, srcValue, DxbcRegMask::firstN(srcValue.type.ccount)); + } + + // End function + this->emitFunctionEnd(); + + DxbcCompilerHsControlPointPhase result; + result.functionId = funId; + return result; + } + + + DxbcCompilerHsForkJoinPhase DxbcCompiler::emitNewHullShaderForkJoinPhase() { + uint32_t argTypeId = m_module.defIntType(32, 0); + uint32_t funTypeId = m_module.defFunctionType( + m_module.defVoidType(), 1, &argTypeId); + + uint32_t funId = m_module.allocateId(); + + this->emitFunctionBegin(funId, + m_module.defVoidType(), + funTypeId); + + uint32_t argId = m_module.functionParameter(argTypeId); + this->emitFunctionLabel(); + + DxbcCompilerHsForkJoinPhase result; + result.functionId = funId; + result.instanceId = argId; + return result; + } + + + void DxbcCompiler::emitHsPhaseBarrier() { + uint32_t exeScopeId = m_module.constu32(spv::ScopeWorkgroup); + uint32_t memScopeId = m_module.constu32(spv::ScopeWorkgroup); + uint32_t semanticId = m_module.constu32( + spv::MemorySemanticsOutputMemoryMask | + spv::MemorySemanticsAcquireReleaseMask | + spv::MemorySemanticsMakeAvailableMask | + spv::MemorySemanticsMakeVisibleMask); + + m_module.opControlBarrier(exeScopeId, memScopeId, semanticId); + } + + + void DxbcCompiler::emitHsInvocationBlockBegin(uint32_t count) { + uint32_t invocationId = m_module.opLoad( + getScalarTypeId(DxbcScalarType::Uint32), + m_hs.builtinInvocationId); + + uint32_t condition = m_module.opULessThan( + m_module.defBoolType(), invocationId, + m_module.constu32(count)); + + m_hs.invocationBlockBegin = m_module.allocateId(); + m_hs.invocationBlockEnd = m_module.allocateId(); + + m_module.opSelectionMerge( + m_hs.invocationBlockEnd, + spv::SelectionControlMaskNone); + + m_module.opBranchConditional( + condition, + m_hs.invocationBlockBegin, + m_hs.invocationBlockEnd); + + m_module.opLabel( + m_hs.invocationBlockBegin); + } + + + void DxbcCompiler::emitHsInvocationBlockEnd() { + m_module.opBranch (m_hs.invocationBlockEnd); + m_module.opLabel (m_hs.invocationBlockEnd); + + m_hs.invocationBlockBegin = 0; + m_hs.invocationBlockEnd = 0; + } + + + void DxbcCompiler::emitHsOutputSetup() { + uint32_t outputPerPatch = emitTessInterfacePerPatch(spv::StorageClassOutput); + + if (!outputPerPatch) + return; + + uint32_t vecType = getVectorTypeId({ DxbcScalarType::Float32, 4 }); + + uint32_t srcPtrType = m_module.defPointerType(vecType, spv::StorageClassPrivate); + uint32_t dstPtrType = m_module.defPointerType(vecType, spv::StorageClassOutput); + + for (uint32_t i = 0; i < 32; i++) { + if (m_hs.outputPerPatchMask & (1 << i)) { + uint32_t index = m_module.constu32(i); + + uint32_t srcPtr = m_module.opAccessChain(srcPtrType, m_hs.outputPerPatch, 1, &index); + uint32_t dstPtr = m_module.opAccessChain(dstPtrType, outputPerPatch, 1, &index); + + m_module.opStore(dstPtr, m_module.opLoad(vecType, srcPtr)); + } + } + } + + + uint32_t DxbcCompiler::emitTessInterfacePerPatch(spv::StorageClass storageClass) { + const char* name = "vPatch"; + + if (storageClass == spv::StorageClassPrivate) + name = "rPatch"; + if (storageClass == spv::StorageClassOutput) + name = "oPatch"; + + uint32_t arrLen = m_psgn != nullptr ? m_psgn->maxRegisterCount() : 0; + + if (!arrLen) + return 0; + + uint32_t vecType = m_module.defVectorType (m_module.defFloatType(32), 4); + uint32_t arrType = m_module.defArrayType (vecType, m_module.constu32(arrLen)); + uint32_t ptrType = m_module.defPointerType(arrType, storageClass); + uint32_t varId = m_module.newVar (ptrType, storageClass); + + m_module.setDebugName (varId, name); + + if (storageClass != spv::StorageClassPrivate) { + m_module.decorate (varId, spv::DecorationPatch); + m_module.decorateLocation (varId, 0); + } + + return varId; + } + + + uint32_t DxbcCompiler::emitTessInterfacePerVertex(spv::StorageClass storageClass, uint32_t vertexCount) { + const bool isInput = storageClass == spv::StorageClassInput; + + uint32_t arrLen = isInput + ? (m_isgn != nullptr ? m_isgn->maxRegisterCount() : 0) + : (m_osgn != nullptr ? m_osgn->maxRegisterCount() : 0); + + if (!arrLen) + return 0; + + uint32_t locIdx = m_psgn != nullptr + ? m_psgn->maxRegisterCount() + : 0; + + uint32_t vecType = m_module.defVectorType (m_module.defFloatType(32), 4); + uint32_t arrTypeInner = m_module.defArrayType (vecType, m_module.constu32(arrLen)); + uint32_t arrTypeOuter = m_module.defArrayType (arrTypeInner, m_module.constu32(vertexCount)); + uint32_t ptrType = m_module.defPointerType(arrTypeOuter, storageClass); + uint32_t varId = m_module.newVar (ptrType, storageClass); + + m_module.setDebugName (varId, isInput ? "vVertex" : "oVertex"); + m_module.decorateLocation (varId, locIdx); + return varId; + } + + + uint32_t DxbcCompiler::emitSamplePosArray() { + const std::array samplePosVectors = {{ + // Invalid sample count / unbound resource + m_module.constvec2f32( 0.0f, 0.0f), + // VK_SAMPLE_COUNT_1_BIT + m_module.constvec2f32( 0.0f, 0.0f), + // VK_SAMPLE_COUNT_2_BIT + m_module.constvec2f32( 0.25f, 0.25f), + m_module.constvec2f32(-0.25f,-0.25f), + // VK_SAMPLE_COUNT_4_BIT + m_module.constvec2f32(-0.125f,-0.375f), + m_module.constvec2f32( 0.375f,-0.125f), + m_module.constvec2f32(-0.375f, 0.125f), + m_module.constvec2f32( 0.125f, 0.375f), + // VK_SAMPLE_COUNT_8_BIT + m_module.constvec2f32( 0.0625f,-0.1875f), + m_module.constvec2f32(-0.0625f, 0.1875f), + m_module.constvec2f32( 0.3125f, 0.0625f), + m_module.constvec2f32(-0.1875f,-0.3125f), + m_module.constvec2f32(-0.3125f, 0.3125f), + m_module.constvec2f32(-0.4375f,-0.0625f), + m_module.constvec2f32( 0.1875f, 0.4375f), + m_module.constvec2f32( 0.4375f,-0.4375f), + // VK_SAMPLE_COUNT_16_BIT + m_module.constvec2f32( 0.0625f, 0.0625f), + m_module.constvec2f32(-0.0625f,-0.1875f), + m_module.constvec2f32(-0.1875f, 0.1250f), + m_module.constvec2f32( 0.2500f,-0.0625f), + m_module.constvec2f32(-0.3125f,-0.1250f), + m_module.constvec2f32( 0.1250f, 0.3125f), + m_module.constvec2f32( 0.3125f, 0.1875f), + m_module.constvec2f32( 0.1875f,-0.3125f), + m_module.constvec2f32(-0.1250f, 0.3750f), + m_module.constvec2f32( 0.0000f,-0.4375f), + m_module.constvec2f32(-0.2500f,-0.3750f), + m_module.constvec2f32(-0.3750f, 0.2500f), + m_module.constvec2f32(-0.5000f, 0.0000f), + m_module.constvec2f32( 0.4375f,-0.2500f), + m_module.constvec2f32( 0.3750f, 0.4375f), + m_module.constvec2f32(-0.4375f,-0.5000f), + }}; + + uint32_t arrayTypeId = getArrayTypeId({ + DxbcScalarType::Float32, 2, + static_cast(samplePosVectors.size()) }); + + uint32_t samplePosArray = m_module.constComposite( + arrayTypeId, + samplePosVectors.size(), + samplePosVectors.data()); + + uint32_t varId = m_module.newVarInit( + m_module.defPointerType(arrayTypeId, spv::StorageClassPrivate), + spv::StorageClassPrivate, samplePosArray); + + m_module.setDebugName(varId, "g_sample_pos"); + m_module.decorate(varId, spv::DecorationNonWritable); + return varId; + } + + + void DxbcCompiler::emitFloatControl() { + DxbcFloatControlFlags flags = m_moduleInfo.options.floatControl; + + if (flags.isClear()) + return; + + const uint32_t width32 = 32; + const uint32_t width64 = 64; + + if (flags.test(DxbcFloatControlFlag::DenormFlushToZero32)) { + m_module.enableCapability(spv::CapabilityDenormFlushToZero); + m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeDenormFlushToZero, 1, &width32); + } + + if (flags.test(DxbcFloatControlFlag::PreserveNan32)) { + m_module.enableCapability(spv::CapabilitySignedZeroInfNanPreserve); + m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeSignedZeroInfNanPreserve, 1, &width32); + } + + if (m_module.hasCapability(spv::CapabilityFloat64)) { + if (flags.test(DxbcFloatControlFlag::DenormPreserve64)) { + m_module.enableCapability(spv::CapabilityDenormPreserve); + m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeDenormPreserve, 1, &width64); + } + + if (flags.test(DxbcFloatControlFlag::PreserveNan64)) { + m_module.enableCapability(spv::CapabilitySignedZeroInfNanPreserve); + m_module.setExecutionMode(m_entryPointId, spv::ExecutionModeSignedZeroInfNanPreserve, 1, &width64); + } + } + } + + + uint32_t DxbcCompiler::emitNewVariable(const DxbcRegisterInfo& info) { + const uint32_t ptrTypeId = this->getPointerTypeId(info); + return m_module.newVar(ptrTypeId, info.sclass); + } + + + uint32_t DxbcCompiler::emitNewBuiltinVariable( + const DxbcRegisterInfo& info, + spv::BuiltIn builtIn, + const char* name) { + const uint32_t varId = emitNewVariable(info); + + if (name) + m_module.setDebugName(varId, name); + + m_module.decorateBuiltIn(varId, builtIn); + + if (m_programInfo.type() == DxbcProgramType::PixelShader + && info.type.ctype != DxbcScalarType::Float32 + && info.type.ctype != DxbcScalarType::Bool + && info.sclass == spv::StorageClassInput) + m_module.decorate(varId, spv::DecorationFlat); + + return varId; + } + + + uint32_t DxbcCompiler::emitBuiltinTessLevelOuter(spv::StorageClass storageClass) { + uint32_t id = emitNewBuiltinVariable( + DxbcRegisterInfo { + { DxbcScalarType::Float32, 0, 4 }, + storageClass }, + spv::BuiltInTessLevelOuter, + "bTessLevelOuter"); + + m_module.decorate(id, spv::DecorationPatch); + return id; + } + + + uint32_t DxbcCompiler::emitBuiltinTessLevelInner(spv::StorageClass storageClass) { + uint32_t id = emitNewBuiltinVariable( + DxbcRegisterInfo { + { DxbcScalarType::Float32, 0, 2 }, + storageClass }, + spv::BuiltInTessLevelInner, + "bTessLevelInner"); + + m_module.decorate(id, spv::DecorationPatch); + return id; + } + + + uint32_t DxbcCompiler::emitPushConstants() { + uint32_t uintTypeId = m_module.defIntType(32, 0); + uint32_t structTypeId = m_module.defStructTypeUnique(1, &uintTypeId); + + m_module.setDebugName(structTypeId, "pc_t"); + m_module.setDebugMemberName(structTypeId, 0, "RasterizerSampleCount"); + m_module.memberDecorateOffset(structTypeId, 0, 0); + + uint32_t ptrTypeId = m_module.defPointerType(structTypeId, spv::StorageClassPushConstant); + uint32_t varId = m_module.newVar(ptrTypeId, spv::StorageClassPushConstant); + + m_module.setDebugName(varId, "pc"); + return varId; + } + + + DxbcCfgBlock* DxbcCompiler::cfgFindBlock( + const std::initializer_list& types) { + for (auto cur = m_controlFlowBlocks.rbegin(); + cur != m_controlFlowBlocks.rend(); cur++) { + for (auto type : types) { + if (cur->type == type) + return &(*cur); + } + } + + return nullptr; + } + + + DxbcBufferInfo DxbcCompiler::getBufferInfo(const DxbcRegister& reg) { + const uint32_t registerId = reg.idx[0].offset; + + switch (reg.type) { + case DxbcOperandType::Resource: { + const auto& texture = m_textures.at(registerId); + + DxbcBufferInfo result; + result.image = texture.imageInfo; + result.stype = texture.sampledType; + result.type = texture.type; + result.typeId = texture.imageTypeId; + result.varId = texture.varId; + result.stride = texture.structStride; + result.coherence = 0; + result.isSsbo = texture.isRawSsbo; + return result; + } break; + + case DxbcOperandType::UnorderedAccessView: { + const auto& uav = m_uavs.at(registerId); + + DxbcBufferInfo result; + result.image = uav.imageInfo; + result.stype = uav.sampledType; + result.type = uav.type; + result.typeId = uav.imageTypeId; + result.varId = uav.varId; + result.stride = uav.structStride; + result.coherence = uav.coherence; + result.isSsbo = uav.isRawSsbo; + return result; + } break; + + case DxbcOperandType::ThreadGroupSharedMemory: { + DxbcBufferInfo result; + result.image = { spv::DimBuffer, 0, 0, 0 }; + result.stype = DxbcScalarType::Uint32; + result.type = m_gRegs.at(registerId).type; + result.typeId = m_module.defPointerType( + getScalarTypeId(DxbcScalarType::Uint32), + spv::StorageClassWorkgroup); + result.varId = m_gRegs.at(registerId).varId; + result.stride = m_gRegs.at(registerId).elementStride; + result.coherence = spv::ScopeInvocation; + result.isSsbo = false; + return result; + } break; + + default: + throw DxvkError(str::format("DxbcCompiler: Invalid operand type for buffer: ", reg.type)); + } + } + + + uint32_t DxbcCompiler::getTexSizeDim(const DxbcImageInfo& imageType) const { + switch (imageType.dim) { + case spv::DimBuffer: return 1 + imageType.array; + case spv::Dim1D: return 1 + imageType.array; + case spv::Dim2D: return 2 + imageType.array; + case spv::Dim3D: return 3 + imageType.array; + case spv::DimCube: return 2 + imageType.array; + default: throw DxvkError("DxbcCompiler: getTexLayerDim: Unsupported image dimension"); + } + } + + + uint32_t DxbcCompiler::getTexLayerDim(const DxbcImageInfo& imageType) const { + switch (imageType.dim) { + case spv::DimBuffer: return 1; + case spv::Dim1D: return 1; + case spv::Dim2D: return 2; + case spv::Dim3D: return 3; + case spv::DimCube: return 3; + default: throw DxvkError("DxbcCompiler: getTexLayerDim: Unsupported image dimension"); + } + } + + + uint32_t DxbcCompiler::getTexCoordDim(const DxbcImageInfo& imageType) const { + return getTexLayerDim(imageType) + imageType.array; + } + + + DxbcRegMask DxbcCompiler::getTexCoordMask(const DxbcImageInfo& imageType) const { + return DxbcRegMask::firstN(getTexCoordDim(imageType)); + } + + + bool DxbcCompiler::ignoreInputSystemValue(DxbcSystemValue sv) const { + switch (sv) { + case DxbcSystemValue::Position: + case DxbcSystemValue::IsFrontFace: + case DxbcSystemValue::SampleIndex: + case DxbcSystemValue::PrimitiveId: + case DxbcSystemValue::Coverage: + return m_programInfo.type() == DxbcProgramType::PixelShader; + + default: + return false; + } + } + + + void DxbcCompiler::emitUavBarrier(uint64_t readMask, uint64_t writeMask) { + if (!m_moduleInfo.options.forceComputeUavBarriers + || m_programInfo.type() != DxbcProgramType::ComputeShader) + return; + + // If both masks are 0, emit a barrier in case at least one read-write UAV + // has a pending unsynchronized access. Only consider read-after-write and + // write-after-read hazards, assume that back-to-back stores are safe and + // do not overlap in memory. Atomics are also completely ignored here. + uint64_t rdMask = m_uavRdMask; + uint64_t wrMask = m_uavWrMask; + + bool insertBarrier = bool(rdMask & wrMask); + + if (readMask || writeMask) { + rdMask &= m_uavWrMask; + wrMask &= m_uavRdMask; + } + + for (auto uav : bit::BitMask(rdMask | wrMask)) { + constexpr VkAccessFlags rwAccess = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + insertBarrier |= (m_analysis->uavInfos[uav].accessFlags & rwAccess) == rwAccess; + } + + // Need to be in uniform top-level control flow, or otherwise + // it is not safe to insert control barriers. + if (insertBarrier && m_controlFlowBlocks.empty() && m_topLevelIsUniform) { + m_module.opControlBarrier( + m_module.constu32(spv::ScopeWorkgroup), + m_module.constu32(m_hasGloballyCoherentUav ? spv::ScopeQueueFamily : spv::ScopeWorkgroup), + m_module.constu32(spv::MemorySemanticsWorkgroupMemoryMask + | spv::MemorySemanticsImageMemoryMask + | spv::MemorySemanticsUniformMemoryMask + | spv::MemorySemanticsAcquireReleaseMask + | spv::MemorySemanticsMakeAvailableMask + | spv::MemorySemanticsMakeVisibleMask)); + + m_uavWrMask = 0u; + m_uavRdMask = 0u; + } + + // Mark pending accesses + m_uavWrMask |= writeMask; + m_uavRdMask |= readMask; + } + + + DxbcVectorType DxbcCompiler::getInputRegType(uint32_t regIdx) const { + switch (m_programInfo.type()) { + case DxbcProgramType::VertexShader: { + const DxbcSgnEntry* entry = m_isgn->findByRegister(regIdx); + + DxbcVectorType result; + result.ctype = DxbcScalarType::Float32; + result.ccount = 4; + + if (entry != nullptr) { + result.ctype = entry->componentType; + result.ccount = entry->componentMask.popCount(); + } + + return result; + } + + case DxbcProgramType::DomainShader: { + DxbcVectorType result; + result.ctype = DxbcScalarType::Float32; + result.ccount = 4; + return result; + } + + default: { + DxbcVectorType result; + result.ctype = DxbcScalarType::Float32; + result.ccount = 4; + + if (m_isgn == nullptr || !m_isgn->findByRegister(regIdx)) + return result; + + DxbcRegMask mask(0u); + DxbcRegMask used(0u); + + for (const auto& e : *m_isgn) { + if (e.registerId == regIdx && !ignoreInputSystemValue(e.systemValue)) { + mask |= e.componentMask; + used |= e.componentUsed; + } + } + + if (m_programInfo.type() == DxbcProgramType::PixelShader) { + if ((used.raw() & mask.raw()) == used.raw()) + mask = used; + } + + result.ccount = mask.minComponents(); + return result; + } + } + } + + + DxbcVectorType DxbcCompiler::getOutputRegType(uint32_t regIdx) const { + switch (m_programInfo.type()) { + case DxbcProgramType::PixelShader: { + const DxbcSgnEntry* entry = m_osgn->findByRegister(regIdx); + + DxbcVectorType result; + result.ctype = DxbcScalarType::Float32; + result.ccount = 4; + + if (entry != nullptr) { + result.ctype = entry->componentType; + result.ccount = entry->componentMask.popCount(); + } + + return result; + } + + case DxbcProgramType::HullShader: { + DxbcVectorType result; + result.ctype = DxbcScalarType::Float32; + result.ccount = 4; + return result; + } + + default: { + DxbcVectorType result; + result.ctype = DxbcScalarType::Float32; + result.ccount = 4; + + if (m_osgn->findByRegister(regIdx)) + result.ccount = m_osgn->regMask(regIdx).minComponents(); + return result; + } + } + } + + + DxbcImageInfo DxbcCompiler::getResourceType( + DxbcResourceDim resourceType, + bool isUav) const { + uint32_t ms = m_moduleInfo.options.disableMsaa ? 0 : 1; + + switch (resourceType) { + case DxbcResourceDim::Buffer: return { spv::DimBuffer, 0, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_MAX_ENUM }; + case DxbcResourceDim::Texture1D: return { spv::Dim1D, 0, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_1D }; + case DxbcResourceDim::Texture1DArr: return { spv::Dim1D, 1, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_1D_ARRAY }; + case DxbcResourceDim::Texture2D: return { spv::Dim2D, 0, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_2D }; + case DxbcResourceDim::Texture2DArr: return { spv::Dim2D, 1, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_2D_ARRAY }; + case DxbcResourceDim::Texture2DMs: return { spv::Dim2D, 0, ms,isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_2D }; + case DxbcResourceDim::Texture2DMsArr: return { spv::Dim2D, 1, ms,isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_2D_ARRAY }; + case DxbcResourceDim::Texture3D: return { spv::Dim3D, 0, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_3D }; + case DxbcResourceDim::TextureCube: return { spv::DimCube, 0, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_CUBE }; + case DxbcResourceDim::TextureCubeArr: return { spv::DimCube, 1, 0, isUav ? 2u : 1u, VK_IMAGE_VIEW_TYPE_CUBE_ARRAY }; + default: throw DxvkError(str::format("DxbcCompiler: Unsupported resource type: ", resourceType)); + } + } + + + spv::ImageFormat DxbcCompiler::getScalarImageFormat(DxbcScalarType type) const { + switch (type) { + case DxbcScalarType::Float32: return spv::ImageFormatR32f; + case DxbcScalarType::Sint32: return spv::ImageFormatR32i; + case DxbcScalarType::Uint32: return spv::ImageFormatR32ui; + default: throw DxvkError("DxbcCompiler: Unhandled scalar resource type"); + } + } + + + bool DxbcCompiler::isDoubleType(DxbcScalarType type) const { + return type == DxbcScalarType::Sint64 + || type == DxbcScalarType::Uint64 + || type == DxbcScalarType::Float64; + } + + DxbcRegisterPointer DxbcCompiler::getIndexableTempPtr( + const DxbcRegister& operand, + DxbcRegisterValue vectorId) { + // x# regs are indexed as follows: + // (0) register index (immediate) + // (1) element index (relative) + const uint32_t regId = operand.idx[0].offset; + + DxbcRegisterInfo info; + info.type.ctype = DxbcScalarType::Float32; + info.type.ccount = m_xRegs[regId].ccount; + info.type.alength = 0; + info.sclass = spv::StorageClassPrivate; + + DxbcRegisterPointer result; + result.type.ctype = info.type.ctype; + result.type.ccount = info.type.ccount; + result.id = m_module.opAccessChain( + getPointerTypeId(info), + m_xRegs.at(regId).varId, + 1, &vectorId.id); + + return result; + } + + bool DxbcCompiler::caseBlockIsFallthrough() const { + return m_lastOp != DxbcOpcode::Case + && m_lastOp != DxbcOpcode::Default + && m_lastOp != DxbcOpcode::Break + && m_lastOp != DxbcOpcode::Ret; + } + + + uint32_t DxbcCompiler::getUavCoherence(uint32_t registerId, DxbcUavFlags flags) { + // For any ROV with write access, we must ensure that + // availability operations happen within the locked scope. + if (flags.test(DxbcUavFlag::RasterizerOrdered) + && (m_analysis->uavInfos[registerId].accessFlags & VK_ACCESS_SHADER_WRITE_BIT)) { + m_hasGloballyCoherentUav = true; + m_hasRasterizerOrderedUav = true; + return spv::ScopeQueueFamily; + } + + // Ignore any resources that can't both be read and written in + // the current shader, explicit availability/visibility operands + // are not useful in that case. + if (m_analysis->uavInfos[registerId].accessFlags != (VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT)) + return 0; + + // If the globally coherent flag is set, the resource must be + // coherent across multiple workgroups of the same dispatch + if (flags.test(DxbcUavFlag::GloballyCoherent)) { + m_hasGloballyCoherentUav = true; + return spv::ScopeQueueFamily; + } + + // In compute shaders, UAVs are implicitly workgroup coherent, + // but we can rely on memory barrier instructions to make any + // access available and visible to the entire workgroup. + if (m_programInfo.type() == DxbcProgramType::ComputeShader) + return spv::ScopeInvocation; + + return 0; + } + + + uint32_t DxbcCompiler::getScalarTypeId(DxbcScalarType type) { + if (type == DxbcScalarType::Float64) + m_module.enableCapability(spv::CapabilityFloat64); + + if (type == DxbcScalarType::Sint64 || type == DxbcScalarType::Uint64) + m_module.enableCapability(spv::CapabilityInt64); + + switch (type) { + case DxbcScalarType::Uint32: return m_module.defIntType(32, 0); + case DxbcScalarType::Uint64: return m_module.defIntType(64, 0); + case DxbcScalarType::Sint32: return m_module.defIntType(32, 1); + case DxbcScalarType::Sint64: return m_module.defIntType(64, 1); + case DxbcScalarType::Float32: return m_module.defFloatType(32); + case DxbcScalarType::Float64: return m_module.defFloatType(64); + case DxbcScalarType::Bool: return m_module.defBoolType(); + } + + throw DxvkError("DxbcCompiler: Invalid scalar type"); + } + + + uint32_t DxbcCompiler::getVectorTypeId(const DxbcVectorType& type) { + uint32_t typeId = this->getScalarTypeId(type.ctype); + + if (type.ccount > 1) + typeId = m_module.defVectorType(typeId, type.ccount); + + return typeId; + } + + + uint32_t DxbcCompiler::getArrayTypeId(const DxbcArrayType& type) { + DxbcVectorType vtype; + vtype.ctype = type.ctype; + vtype.ccount = type.ccount; + + uint32_t typeId = this->getVectorTypeId(vtype); + + if (type.alength != 0) { + typeId = m_module.defArrayType(typeId, + m_module.constu32(type.alength)); + } + + return typeId; + } + + + uint32_t DxbcCompiler::getPointerTypeId(const DxbcRegisterInfo& type) { + return m_module.defPointerType( + this->getArrayTypeId(type.type), + type.sclass); + } + + + uint32_t DxbcCompiler::getSparseResultTypeId(uint32_t baseType) { + m_module.enableCapability(spv::CapabilitySparseResidency); + + uint32_t uintType = getScalarTypeId(DxbcScalarType::Uint32); + std::array typeIds = { uintType, baseType }; + return m_module.defStructType(typeIds.size(), typeIds.data()); + } + + + uint32_t DxbcCompiler::getFunctionId( + uint32_t functionNr) { + auto entry = m_subroutines.find(functionNr); + if (entry != m_subroutines.end()) + return entry->second; + + uint32_t functionId = m_module.allocateId(); + m_subroutines.insert({ functionNr, functionId }); + return functionId; + } + + + DxbcCompilerHsForkJoinPhase* DxbcCompiler::getCurrentHsForkJoinPhase() { + switch (m_hs.currPhaseType) { + case DxbcCompilerHsPhase::Fork: return &m_hs.forkPhases.at(m_hs.currPhaseId); + case DxbcCompilerHsPhase::Join: return &m_hs.joinPhases.at(m_hs.currPhaseId); + default: return nullptr; + } + } + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_decoder.cpp b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_decoder.cpp new file mode 100644 index 000000000..0de7ddad1 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_decoder.cpp @@ -0,0 +1,360 @@ +#include "dxbc_decoder.h" + +namespace dxvk { + + const uint32_t* DxbcCodeSlice::ptrAt(uint32_t id) const { + if (m_ptr + id >= m_end) + throw DxvkError("DxbcCodeSlice: End of stream"); + return m_ptr + id; + } + + + uint32_t DxbcCodeSlice::at(uint32_t id) const { + if (m_ptr + id >= m_end) + throw DxvkError("DxbcCodeSlice: End of stream"); + return m_ptr[id]; + } + + + uint32_t DxbcCodeSlice::read() { + if (m_ptr >= m_end) + throw DxvkError("DxbcCodeSlice: End of stream"); + return *(m_ptr++); + } + + + DxbcCodeSlice DxbcCodeSlice::take(uint32_t n) const { + if (m_ptr + n > m_end) + throw DxvkError("DxbcCodeSlice: End of stream"); + return DxbcCodeSlice(m_ptr, m_ptr + n); + } + + + DxbcCodeSlice DxbcCodeSlice::skip(uint32_t n) const { + if (m_ptr + n > m_end) + throw DxvkError("DxbcCodeSlice: End of stream"); + return DxbcCodeSlice(m_ptr + n, m_end); + } + + + + void DxbcDecodeContext::decodeInstruction(DxbcCodeSlice& code) { + const uint32_t token0 = code.at(0); + + // Initialize the instruction structure. Some of these values + // may not get written otherwise while decoding the instruction. + m_instruction.op = static_cast(bit::extract(token0, 0, 10)); + m_instruction.opClass = DxbcInstClass::Undefined; + m_instruction.sampleControls = { 0, 0, 0 }; + m_instruction.dstCount = 0; + m_instruction.srcCount = 0; + m_instruction.immCount = 0; + m_instruction.dst = m_dstOperands.data(); + m_instruction.src = m_srcOperands.data(); + m_instruction.imm = m_immOperands.data(); + m_instruction.customDataType = DxbcCustomDataClass::Comment; + m_instruction.customDataSize = 0; + m_instruction.customData = nullptr; + + // Reset the index pointer, which may still contain + // a non-zero value from the previous iteration + m_indexId = 0; + + // Instruction length, in DWORDs. This includes the token + // itself and any other prefix that an instruction may have. + uint32_t length = 0; + + if (m_instruction.op == DxbcOpcode::CustomData) { + length = code.at(1); + this->decodeCustomData(code.take(length)); + } else { + length = bit::extract(token0, 24, 30); + this->decodeOperation(code.take(length)); + } + + // Advance the caller's slice to the next token so that + // they can make consecutive calls to decodeInstruction() + code = code.skip(length); + } + + + void DxbcDecodeContext::decodeCustomData(DxbcCodeSlice code) { + const uint32_t blockLength = code.at(1); + + if (blockLength < 2) { + Logger::err("DxbcDecodeContext: Invalid custom data block"); + return; + } + + // Custom data blocks have their own instruction class + m_instruction.op = DxbcOpcode::CustomData; + m_instruction.opClass = DxbcInstClass::CustomData; + + // We'll point into the code buffer rather than making a copy + m_instruction.customDataType = static_cast( + bit::extract(code.at(0), 11, 31)); + m_instruction.customDataSize = blockLength - 2; + m_instruction.customData = code.ptrAt(2); + } + + + void DxbcDecodeContext::decodeOperation(DxbcCodeSlice code) { + uint32_t token = code.read(); + + // Result modifiers, which are applied to common ALU ops + m_instruction.modifiers.saturate = !!bit::extract(token, 13, 13); + m_instruction.modifiers.precise = !!bit::extract(token, 19, 22); + + // Opcode controls. It will depend on the + // opcode itself which ones are valid. + m_instruction.controls = DxbcShaderOpcodeControls(token); + + // Process extended opcode tokens + while (bit::extract(token, 31, 31)) { + token = code.read(); + + const DxbcExtOpcode extOpcode + = static_cast(bit::extract(token, 0, 5)); + + switch (extOpcode) { + case DxbcExtOpcode::SampleControls: { + struct { + int u : 4; + int v : 4; + int w : 4; + } aoffimmi; + + aoffimmi.u = bit::extract(token, 9, 12); + aoffimmi.v = bit::extract(token, 13, 16); + aoffimmi.w = bit::extract(token, 17, 20); + + // Four-bit signed numbers, sign-extend them + m_instruction.sampleControls.u = aoffimmi.u; + m_instruction.sampleControls.v = aoffimmi.v; + m_instruction.sampleControls.w = aoffimmi.w; + } break; + + case DxbcExtOpcode::ResourceDim: + case DxbcExtOpcode::ResourceReturnType: + break; // part of resource description + + default: + Logger::warn(str::format( + "DxbcDecodeContext: Unhandled extended opcode: ", + extOpcode)); + } + } + + // Retrieve the instruction format in order to parse the + // operands. Doing this mostly automatically means that + // the compiler can rely on the operands being valid. + const DxbcInstFormat format = dxbcInstructionFormat(m_instruction.op); + m_instruction.opClass = format.instructionClass; + + for (uint32_t i = 0; i < format.operandCount; i++) + this->decodeOperand(code, format.operands[i]); + } + + + void DxbcDecodeContext::decodeComponentSelection(DxbcRegister& reg, uint32_t token) { + // Pick the correct component selection mode based on the + // component count. We'll simplify this here so that the + // compiler can assume that everything is a 4D vector. + reg.componentCount = static_cast(bit::extract(token, 0, 1)); + + switch (reg.componentCount) { + // No components - used for samplers etc. + case DxbcComponentCount::Component0: + reg.mask = DxbcRegMask(false, false, false, false); + reg.swizzle = DxbcRegSwizzle(0, 0, 0, 0); + break; + + // One component - used for immediates + // and a few built-in registers. + case DxbcComponentCount::Component1: + reg.mask = DxbcRegMask(true, false, false, false); + reg.swizzle = DxbcRegSwizzle(0, 0, 0, 0); + break; + + // Four components - everything else. This requires us + // to actually parse the component selection mode. + case DxbcComponentCount::Component4: { + const DxbcRegMode componentMode = + static_cast(bit::extract(token, 2, 3)); + + switch (componentMode) { + // Write mask for destination operands + case DxbcRegMode::Mask: + reg.mask = bit::extract(token, 4, 7); + reg.swizzle = DxbcRegSwizzle(0, 1, 2, 3); + break; + + // Swizzle for source operands (including resources) + case DxbcRegMode::Swizzle: + reg.mask = DxbcRegMask(true, true, true, true); + reg.swizzle = DxbcRegSwizzle( + bit::extract(token, 4, 5), + bit::extract(token, 6, 7), + bit::extract(token, 8, 9), + bit::extract(token, 10, 11)); + break; + + // Selection of one component. We can generate both a + // mask and a swizzle for this so that the compiler + // won't have to deal with this case specifically. + case DxbcRegMode::Select1: { + const uint32_t n = bit::extract(token, 4, 5); + reg.mask = DxbcRegMask(n == 0, n == 1, n == 2, n == 3); + reg.swizzle = DxbcRegSwizzle(n, n, n, n); + } break; + + default: + Logger::warn("DxbcDecodeContext: Invalid component selection mode"); + } + } break; + + default: + Logger::warn("DxbcDecodeContext: Invalid component count"); + } + } + + + void DxbcDecodeContext::decodeOperandExtensions(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token) { + while (bit::extract(token, 31, 31)) { + token = code.read(); + + // Type of the extended operand token + const DxbcOperandExt extTokenType = + static_cast(bit::extract(token, 0, 5)); + + switch (extTokenType) { + // Operand modifiers, which are used to manipulate the + // value of a source operand during the load operation + case DxbcOperandExt::OperandModifier: + reg.modifiers = bit::extract(token, 6, 13); + break; + + default: + Logger::warn(str::format( + "DxbcDecodeContext: Unhandled extended operand token: ", + extTokenType)); + } + } + } + + + void DxbcDecodeContext::decodeOperandImmediates(DxbcCodeSlice& code, DxbcRegister& reg) { + if (reg.type == DxbcOperandType::Imm32 + || reg.type == DxbcOperandType::Imm64) { + switch (reg.componentCount) { + // This is commonly used if only one vector + // component is involved in an operation + case DxbcComponentCount::Component1: { + reg.imm.u32_1 = code.read(); + } break; + + // Typical four-component vector + case DxbcComponentCount::Component4: { + reg.imm.u32_4[0] = code.read(); + reg.imm.u32_4[1] = code.read(); + reg.imm.u32_4[2] = code.read(); + reg.imm.u32_4[3] = code.read(); + } break; + + default: + Logger::warn("DxbcDecodeContext: Invalid component count for immediate operand"); + } + } + } + + + void DxbcDecodeContext::decodeOperandIndex(DxbcCodeSlice& code, DxbcRegister& reg, uint32_t token) { + reg.idxDim = bit::extract(token, 20, 21); + + for (uint32_t i = 0; i < reg.idxDim; i++) { + // An index can be encoded in various different ways + const DxbcOperandIndexRepresentation repr = + static_cast( + bit::extract(token, 22 + 3 * i, 24 + 3 * i)); + + switch (repr) { + case DxbcOperandIndexRepresentation::Imm32: + reg.idx[i].offset = static_cast(code.read()); + reg.idx[i].relReg = nullptr; + break; + + case DxbcOperandIndexRepresentation::Relative: + reg.idx[i].offset = 0; + reg.idx[i].relReg = &m_indices.at(m_indexId); + + this->decodeRegister(code, + m_indices.at(m_indexId++), + DxbcScalarType::Sint32); + break; + + case DxbcOperandIndexRepresentation::Imm32Relative: + reg.idx[i].offset = static_cast(code.read()); + reg.idx[i].relReg = &m_indices.at(m_indexId); + + this->decodeRegister(code, + m_indices.at(m_indexId++), + DxbcScalarType::Sint32); + break; + + default: + Logger::warn(str::format( + "DxbcDecodeContext: Unhandled index representation: ", + repr)); + } + } + } + + + void DxbcDecodeContext::decodeRegister(DxbcCodeSlice& code, DxbcRegister& reg, DxbcScalarType type) { + const uint32_t token = code.read(); + + reg.type = static_cast(bit::extract(token, 12, 19)); + reg.dataType = type; + reg.modifiers = 0; + reg.idxDim = 0; + + for (uint32_t i = 0; i < DxbcMaxRegIndexDim; i++) { + reg.idx[i].relReg = nullptr; + reg.idx[i].offset = 0; + } + + this->decodeComponentSelection(reg, token); + this->decodeOperandExtensions(code, reg, token); + this->decodeOperandImmediates(code, reg); + this->decodeOperandIndex(code, reg, token); + } + + + void DxbcDecodeContext::decodeImm32(DxbcCodeSlice& code, DxbcImmediate& imm, DxbcScalarType type) { + imm.u32 = code.read(); + } + + + void DxbcDecodeContext::decodeOperand(DxbcCodeSlice& code, const DxbcInstOperandFormat& format) { + switch (format.kind) { + case DxbcOperandKind::DstReg: { + const uint32_t operandId = m_instruction.dstCount++; + this->decodeRegister(code, m_dstOperands.at(operandId), format.type); + } break; + + case DxbcOperandKind::SrcReg: { + const uint32_t operandId = m_instruction.srcCount++; + this->decodeRegister(code, m_srcOperands.at(operandId), format.type); + } break; + + case DxbcOperandKind::Imm32: { + const uint32_t operandId = m_instruction.immCount++; + this->decodeImm32(code, m_immOperands.at(operandId), format.type); + } break; + + default: + throw DxvkError("DxbcDecodeContext: Invalid operand format"); + } + } + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_defs.cpp b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_defs.cpp new file mode 100644 index 000000000..0d193ada9 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_defs.cpp @@ -0,0 +1,1255 @@ +#include "dxbc_defs.h" + +namespace dxvk { + + const std::array g_instructionFormats = {{ + /* Add */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* And */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* Break */ + { 0, DxbcInstClass::ControlFlow }, + /* Breakc */ + { 1, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* Call */ + { 1, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* Callc */ + { 2, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* Case */ + { 1, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* Continue */ + { 0, DxbcInstClass::ControlFlow }, + /* Continuec */ + { 1, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* Cut */ + { 0, DxbcInstClass::GeometryEmit }, + /* Default */ + { 0, DxbcInstClass::ControlFlow }, + /* DerivRtx */ + { 2, DxbcInstClass::VectorDeriv, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* DerivRty */ + { 2, DxbcInstClass::VectorDeriv, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Discard */ + { 1, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* Div */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Dp2 */ + { 3, DxbcInstClass::VectorDot, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Dp3 */ + { 3, DxbcInstClass::VectorDot, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Dp4 */ + { 3, DxbcInstClass::VectorDot, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Else */ + { 0, DxbcInstClass::ControlFlow }, + /* Emit */ + { 0, DxbcInstClass::GeometryEmit }, + /* EmitThenCut */ + { 0, DxbcInstClass::GeometryEmit }, + /* EndIf */ + { 0, DxbcInstClass::ControlFlow }, + /* EndLoop */ + { 0, DxbcInstClass::ControlFlow }, + /* EndSwitch */ + { 0, DxbcInstClass::ControlFlow }, + /* Eq */ + { 3, DxbcInstClass::VectorCmp, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Exp */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Frc */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* FtoI */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* FtoU */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Ge */ + { 3, DxbcInstClass::VectorCmp, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* IAdd */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* If */ + { 1, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* IEq */ + { 3, DxbcInstClass::VectorCmp, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* IGe */ + { 3, DxbcInstClass::VectorCmp, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* ILt */ + { 3, DxbcInstClass::VectorCmp, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* IMad */ + { 4, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* IMax */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* IMin */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* IMul */ + { 4, DxbcInstClass::VectorImul, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* INe */ + { 3, DxbcInstClass::VectorCmp, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* INeg */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* IShl */ + { 3, DxbcInstClass::VectorShift, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* IShr */ + { 3, DxbcInstClass::VectorShift, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* ItoF */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* Label */ + { 1, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + } }, + /* Ld */ + { 3, DxbcInstClass::TextureFetch, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* LdMs */ + { 4, DxbcInstClass::TextureFetch, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* Log */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Loop */ + { 0, DxbcInstClass::ControlFlow }, + /* Lt */ + { 3, DxbcInstClass::VectorCmp, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Mad */ + { 4, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Min */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Max */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* CustomData */ + { 0, DxbcInstClass::CustomData }, + /* Mov */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Movc */ + { 4, DxbcInstClass::VectorCmov, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Mul */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Ne */ + { 3, DxbcInstClass::VectorCmp, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Nop */ + { 0, DxbcInstClass::NoOperation }, + /* Not */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* Or */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* ResInfo */ + { 3, DxbcInstClass::TextureQuery, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Ret */ + { 0, DxbcInstClass::ControlFlow }, + /* Retc */ + { 1, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* RoundNe */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* RoundNi */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* RoundPi */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* RoundZ */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Rsq */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Sample */ + { 4, DxbcInstClass::TextureSample, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* SampleC */ + { 5, DxbcInstClass::TextureSample, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* SampleClz */ + { 5, DxbcInstClass::TextureSample, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* SampleL */ + { 5, DxbcInstClass::TextureSample, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* SampleD */ + { 6, DxbcInstClass::TextureSample, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* SampleB */ + { 5, DxbcInstClass::TextureSample, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Sqrt */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Switch */ + { 1, DxbcInstClass::ControlFlow, { + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* SinCos */ + { 3, DxbcInstClass::VectorSinCos, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* UDiv */ + { 4, DxbcInstClass::VectorIdiv, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* ULt */ + { 3, DxbcInstClass::VectorCmp, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* UGe */ + { 3, DxbcInstClass::VectorCmp, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* UMul */ + { 4, DxbcInstClass::VectorImul, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* UMad */ + { 4, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* UMax */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* UMin */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* UShr */ + { 3, DxbcInstClass::VectorShift, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* UtoF */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* Xor */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* DclResource */ + { 2, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclConstantBuffer */ + { 1, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + } }, + /* DclSampler */ + { 1, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + } }, + /* DclIndexRange */ + { 2, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclGsOutputPrimitiveTopology */ + { 0, DxbcInstClass::Declaration }, + /* DclGsInputPrimitive */ + { 0, DxbcInstClass::Declaration }, + /* DclMaxOutputVertexCount */ + { 1, DxbcInstClass::Declaration, { + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclInput */ + { 1, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + } }, + /* DclInputSgv */ + { 2, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclInputSiv */ + { 2, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclInputPs */ + { 1, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + } }, + /* DclInputPsSgv */ + { 2, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclInputPsSiv */ + { 2, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclOutput */ + { 1, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + } }, + /* DclOutputSgv */ + { 2, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclOutputSiv */ + { 2, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclTemps */ + { 1, DxbcInstClass::Declaration, { + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclIndexableTemp */ + { 3, DxbcInstClass::Declaration, { + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclGlobalFlags */ + { 0, DxbcInstClass::Declaration }, + /* Reserved0 */ + { 0, DxbcInstClass::Undefined }, + /* Lod */ + { 4, DxbcInstClass::TextureQueryLod, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Gather4 */ + { 4, DxbcInstClass::TextureGather, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* SamplePos */ + { 3, DxbcInstClass::TextureQueryMsPos, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* SampleInfo */ + { 2, DxbcInstClass::TextureQueryMs, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Reserved1 */ + { }, + /* HsDecls */ + { 0, DxbcInstClass::HullShaderPhase }, + /* HsControlPointPhase */ + { 0, DxbcInstClass::HullShaderPhase }, + /* HsForkPhase */ + { 0, DxbcInstClass::HullShaderPhase }, + /* HsJoinPhase */ + { 0, DxbcInstClass::HullShaderPhase }, + /* EmitStream */ + { 1, DxbcInstClass::GeometryEmit, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + } }, + /* CutStream */ + { 1, DxbcInstClass::GeometryEmit, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + } }, + /* EmitThenCutStream */ + { 1, DxbcInstClass::GeometryEmit, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + } }, + /* InterfaceCall */ + { }, + /* BufInfo */ + { 2, DxbcInstClass::BufferQuery, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* DerivRtxCoarse */ + { 2, DxbcInstClass::VectorDeriv, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* DerivRtxFine */ + { 2, DxbcInstClass::VectorDeriv, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* DerivRtyCoarse */ + { 2, DxbcInstClass::VectorDeriv, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* DerivRtyFine */ + { 2, DxbcInstClass::VectorDeriv, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Gather4C */ + { 5, DxbcInstClass::TextureGather, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Gather4Po */ + { 5, DxbcInstClass::TextureGather, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Gather4PoC */ + { 6, DxbcInstClass::TextureGather, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Rcp */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* F32toF16 */ + { 2, DxbcInstClass::ConvertFloat16, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* F16toF32 */ + { 2, DxbcInstClass::ConvertFloat16, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* UAddc */ + { }, + /* USubb */ + { }, + /* CountBits */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* FirstBitHi */ + { 2, DxbcInstClass::BitScan, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* FirstBitLo */ + { 2, DxbcInstClass::BitScan, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* FirstBitShi */ + { 2, DxbcInstClass::BitScan, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* UBfe */ + { 4, DxbcInstClass::BitExtract, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* IBfe */ + { 4, DxbcInstClass::BitExtract, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* Bfi */ + { 5, DxbcInstClass::BitInsert, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* BfRev */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* Swapc */ + { 5, DxbcInstClass::VectorCmov, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* DclStream */ + { 1, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + } }, + /* DclFunctionBody */ + { }, + /* DclFunctionTable */ + { }, + /* DclInterface */ + { }, + /* DclInputControlPointCount */ + { 0, DxbcInstClass::Declaration }, + /* DclOutputControlPointCount */ + { 0, DxbcInstClass::Declaration }, + /* DclTessDomain */ + { 0, DxbcInstClass::Declaration }, + /* DclTessPartitioning */ + { 0, DxbcInstClass::Declaration }, + /* DclTessOutputPrimitive */ + { 0, DxbcInstClass::Declaration }, + /* DclHsMaxTessFactor */ + { 1, DxbcInstClass::Declaration, { + { DxbcOperandKind::Imm32, DxbcScalarType::Float32 }, + } }, + /* DclHsForkPhaseInstanceCount */ + { 1, DxbcInstClass::HullShaderInstCnt, { + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclHsJoinPhaseInstanceCount */ + { 1, DxbcInstClass::HullShaderInstCnt, { + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclThreadGroup */ + { 3, DxbcInstClass::Declaration, { + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclUavTyped */ + { 2, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclUavRaw */ + { 1, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + } }, + /* DclUavStructured */ + { 2, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclThreadGroupSharedMemoryRaw */ + { 2, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclThreadGroupSharedMemoryStructured */ + { 3, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* DclResourceRaw */ + { 1, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + } }, + /* DclResourceStructured */ + { 2, DxbcInstClass::Declaration, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* LdUavTyped */ + { 3, DxbcInstClass::TypedUavLoad, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* StoreUavTyped */ + { 3, DxbcInstClass::TypedUavStore, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* LdRaw */ + { 3, DxbcInstClass::BufferLoad, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* StoreRaw */ + { 3, DxbcInstClass::BufferStore, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* LdStructured */ + { 4, DxbcInstClass::BufferLoad, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* StoreStructured */ + { 4, DxbcInstClass::BufferStore, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* AtomicAnd */ + { 3, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* AtomicOr */ + { 3, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* AtomicXor */ + { 3, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* AtomicCmpStore */ + { 4, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* AtomicIAdd */ + { 3, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* AtomicIMax */ + { 3, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* AtomicIMin */ + { 3, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* AtomicUMax */ + { 3, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* AtomicUMin */ + { 3, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* ImmAtomicAlloc */ + { 2, DxbcInstClass::AtomicCounter, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + } }, + /* ImmAtomicConsume */ + { 2, DxbcInstClass::AtomicCounter, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + } }, + /* ImmAtomicIAdd */ + { 4, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* ImmAtomicAnd */ + { 4, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* ImmAtomicOr */ + { 4, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* ImmAtomicXor */ + { 4, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* ImmAtomicExch */ + { 4, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* ImmAtomicCmpExch */ + { 5, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* ImmAtomicIMax */ + { 4, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* ImmAtomicIMin */ + { 4, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* ImmAtomicUMax */ + { 4, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* ImmAtomicUMin */ + { 4, DxbcInstClass::Atomic, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* Sync */ + { 0, DxbcInstClass::Barrier }, + /* DAdd */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* DMax */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* DMin */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* DMul */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* DEq */ + { 3, DxbcInstClass::VectorCmp, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* DGe */ + { 3, DxbcInstClass::VectorCmp, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* DLt */ + { 3, DxbcInstClass::VectorCmp, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* DNe */ + { 3, DxbcInstClass::VectorCmp, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* DMov */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* DMovc */ + { 4, DxbcInstClass::VectorCmov, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* DtoF */ + { 2, DxbcInstClass::ConvertFloat64, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* FtoD */ + { 2, DxbcInstClass::ConvertFloat64, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* EvalSnapped */ + { 3, DxbcInstClass::Interpolate, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* EvalSampleIndex */ + { 3, DxbcInstClass::Interpolate, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* EvalCentroid */ + { 2, DxbcInstClass::Interpolate, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* DclGsInstanceCount */ + { 1, DxbcInstClass::Declaration, { + { DxbcOperandKind::Imm32, DxbcScalarType::Uint32 }, + } }, + /* Abort */ + { }, + /* DebugBreak */ + { }, + /* ReservedBegin11_1 */ + { }, + /* DDiv */ + { 3, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* DFma */ + { 4, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* DRcp */ + { 2, DxbcInstClass::VectorAlu, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* Msad */ + { 4, DxbcInstClass::VectorMsad, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* DtoI */ + { 2, DxbcInstClass::ConvertFloat64, { + { DxbcOperandKind::DstReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* DtoU */ + { 2, DxbcInstClass::ConvertFloat64, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float64 }, + } }, + /* ItoD */ + { 2, DxbcInstClass::ConvertFloat64, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* UtoD */ + { 2, DxbcInstClass::ConvertFloat64, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float64 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* ReservedBegin11_2 */ + { }, + /* Gather4S */ + { 5, DxbcInstClass::TextureGather, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Gather4CS */ + { 6, DxbcInstClass::TextureGather, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Gather4PoS */ + { 6, DxbcInstClass::TextureGather, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* Gather4PoCS */ + { 7, DxbcInstClass::TextureGather, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* LdS */ + { 4, DxbcInstClass::TextureFetch, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* LdMsS */ + { 5, DxbcInstClass::TextureFetch, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + } }, + /* LdUavTypedS */ + { 4, DxbcInstClass::TypedUavLoad, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* LdRawS */ + { 4, DxbcInstClass::BufferLoad, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* LdStructuredS */ + { 5, DxbcInstClass::BufferLoad, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Sint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + /* SampleLS */ + { 6, DxbcInstClass::TextureSample, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* SampleClzS */ + { 6, DxbcInstClass::TextureSample, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* SampleClampS */ + { 6, DxbcInstClass::TextureSample, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* SampleBClampS */ + { 7, DxbcInstClass::TextureSample, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* SampleDClampS */ + { 8, DxbcInstClass::TextureSample, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* SampleCClampS */ + { 7, DxbcInstClass::TextureSample, { + { DxbcOperandKind::DstReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Float32 }, + } }, + /* CheckAccessFullyMapped */ + { 2, DxbcInstClass::SparseCheckAccess, { + { DxbcOperandKind::DstReg, DxbcScalarType::Uint32 }, + { DxbcOperandKind::SrcReg, DxbcScalarType::Uint32 }, + } }, + }}; + + + DxbcInstFormat dxbcInstructionFormat(DxbcOpcode opcode) { + const uint32_t idx = static_cast(opcode); + + return (idx < g_instructionFormats.size()) + ? g_instructionFormats.at(idx) + : DxbcInstFormat(); + } + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_header.cpp b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_header.cpp new file mode 100644 index 000000000..9b5f69895 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_header.cpp @@ -0,0 +1,30 @@ +#include "dxbc_header.h" + +namespace dxvk { + + DxbcHeader::DxbcHeader(DxbcReader& reader) { + // FourCC at the start of the file, must be 'DXBC' + DxbcTag fourcc = reader.readTag(); + + if (fourcc != "DXBC") + throw DxvkError("DxbcHeader::DxbcHeader: Invalid fourcc, expected 'DXBC'"); + + // Stuff we don't actually need to store + reader.skip(4 * sizeof(uint32_t)); // Check sum + reader.skip(1 * sizeof(uint32_t)); // Constant 1 + reader.skip(1 * sizeof(uint32_t)); // Bytecode length + + // Number of chunks in the file + uint32_t chunkCount = reader.readu32(); + + // Chunk offsets are stored immediately after + for (uint32_t i = 0; i < chunkCount; i++) + m_chunkOffsets.push_back(reader.readu32()); + } + + + DxbcHeader::~DxbcHeader() { + + } + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_module.cpp b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_module.cpp new file mode 100644 index 000000000..573d503b0 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_module.cpp @@ -0,0 +1,120 @@ +#include "dxbc_analysis.h" +#include "dxbc_compiler.h" +#include "dxbc_module.h" + +namespace dxvk { + + DxbcModule::DxbcModule(DxbcReader& reader) + : m_header(reader) { + for (uint32_t i = 0; i < m_header.numChunks(); i++) { + + // The chunk tag is stored at the beginning of each chunk + auto chunkReader = reader.clone(m_header.chunkOffset(i)); + auto tag = chunkReader.readTag(); + + // The chunk size follows right after the four-character + // code. This does not include the eight bytes that are + // consumed by the FourCC and chunk length entry. + auto chunkLength = chunkReader.readu32(); + + chunkReader = chunkReader.clone(8); + chunkReader = chunkReader.resize(chunkLength); + + if ((tag == "SHDR") || (tag == "SHEX")) + m_shexChunk = new DxbcShex(chunkReader); + + if ((tag == "ISGN") || (tag == "ISG1")) + m_isgnChunk = new DxbcIsgn(chunkReader, tag); + + if ((tag == "OSGN") || (tag == "OSG5") || (tag == "OSG1")) + m_osgnChunk = new DxbcIsgn(chunkReader, tag); + + if ((tag == "PCSG") || (tag == "PSG1")) + m_psgnChunk = new DxbcIsgn(chunkReader, tag); + } + } + + + DxbcModule::~DxbcModule() { + + } + + + SpirvCodeBuffer DxbcModule::compile( + const DxbcModuleInfo& moduleInfo, + const std::string& fileName) { + if (m_shexChunk == nullptr) + throw DxvkError("DxbcModule::compile: No SHDR/SHEX chunk"); + + DxbcAnalysisInfo analysisInfo; + + DxbcAnalyzer analyzer(moduleInfo, + m_shexChunk->programInfo(), + m_isgnChunk, m_osgnChunk, + m_psgnChunk, analysisInfo); + + this->runAnalyzer(analyzer, m_shexChunk->slice()); + + m_bindings = std::make_optional(analysisInfo.bindings); + + DxbcCompiler compiler( + fileName, moduleInfo, + m_shexChunk->programInfo(), + m_isgnChunk, m_osgnChunk, + m_psgnChunk, analysisInfo); + + this->runCompiler(compiler, m_shexChunk->slice()); + + m_icb = compiler.getIcbData(); + + return compiler.finalize(); + } + + + SpirvCodeBuffer DxbcModule::compilePassthroughShader( + const DxbcModuleInfo& moduleInfo, + const std::string& fileName) const { + if (m_shexChunk == nullptr) + throw DxvkError("DxbcModule::compile: No SHDR/SHEX chunk"); + + DxbcAnalysisInfo analysisInfo; + + DxbcCompiler compiler( + fileName, moduleInfo, + DxbcProgramType::GeometryShader, + m_osgnChunk, m_osgnChunk, + m_psgnChunk, analysisInfo); + + compiler.processXfbPassthrough(); + return compiler.finalize(); + } + + + void DxbcModule::runAnalyzer( + DxbcAnalyzer& analyzer, + DxbcCodeSlice slice) const { + DxbcDecodeContext decoder; + + while (!slice.atEnd()) { + decoder.decodeInstruction(slice); + + analyzer.processInstruction( + decoder.getInstruction()); + } + } + + + void DxbcModule::runCompiler( + DxbcCompiler& compiler, + DxbcCodeSlice slice) const { + DxbcDecodeContext decoder; + + while (!slice.atEnd()) { + decoder.decodeInstruction(slice); + + compiler.processInstruction( + decoder.getInstruction()); + } + } + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_names.cpp b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_names.cpp new file mode 100644 index 000000000..c6a00eccc --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_names.cpp @@ -0,0 +1,445 @@ +#include "dxbc_names.h" + +namespace dxvk { + + std::ostream& operator << (std::ostream& os, DxbcOpcode e) { + switch (e) { + ENUM_NAME(DxbcOpcode::Add); + ENUM_NAME(DxbcOpcode::And); + ENUM_NAME(DxbcOpcode::Break); + ENUM_NAME(DxbcOpcode::Breakc); + ENUM_NAME(DxbcOpcode::Call); + ENUM_NAME(DxbcOpcode::Callc); + ENUM_NAME(DxbcOpcode::Case); + ENUM_NAME(DxbcOpcode::Continue); + ENUM_NAME(DxbcOpcode::Continuec); + ENUM_NAME(DxbcOpcode::Cut); + ENUM_NAME(DxbcOpcode::Default); + ENUM_NAME(DxbcOpcode::DerivRtx); + ENUM_NAME(DxbcOpcode::DerivRty); + ENUM_NAME(DxbcOpcode::Discard); + ENUM_NAME(DxbcOpcode::Div); + ENUM_NAME(DxbcOpcode::Dp2); + ENUM_NAME(DxbcOpcode::Dp3); + ENUM_NAME(DxbcOpcode::Dp4); + ENUM_NAME(DxbcOpcode::Else); + ENUM_NAME(DxbcOpcode::Emit); + ENUM_NAME(DxbcOpcode::EmitThenCut); + ENUM_NAME(DxbcOpcode::EndIf); + ENUM_NAME(DxbcOpcode::EndLoop); + ENUM_NAME(DxbcOpcode::EndSwitch); + ENUM_NAME(DxbcOpcode::Eq); + ENUM_NAME(DxbcOpcode::Exp); + ENUM_NAME(DxbcOpcode::Frc); + ENUM_NAME(DxbcOpcode::FtoI); + ENUM_NAME(DxbcOpcode::FtoU); + ENUM_NAME(DxbcOpcode::Ge); + ENUM_NAME(DxbcOpcode::IAdd); + ENUM_NAME(DxbcOpcode::If); + ENUM_NAME(DxbcOpcode::IEq); + ENUM_NAME(DxbcOpcode::IGe); + ENUM_NAME(DxbcOpcode::ILt); + ENUM_NAME(DxbcOpcode::IMad); + ENUM_NAME(DxbcOpcode::IMax); + ENUM_NAME(DxbcOpcode::IMin); + ENUM_NAME(DxbcOpcode::IMul); + ENUM_NAME(DxbcOpcode::INe); + ENUM_NAME(DxbcOpcode::INeg); + ENUM_NAME(DxbcOpcode::IShl); + ENUM_NAME(DxbcOpcode::IShr); + ENUM_NAME(DxbcOpcode::ItoF); + ENUM_NAME(DxbcOpcode::Label); + ENUM_NAME(DxbcOpcode::Ld); + ENUM_NAME(DxbcOpcode::LdMs); + ENUM_NAME(DxbcOpcode::Log); + ENUM_NAME(DxbcOpcode::Loop); + ENUM_NAME(DxbcOpcode::Lt); + ENUM_NAME(DxbcOpcode::Mad); + ENUM_NAME(DxbcOpcode::Min); + ENUM_NAME(DxbcOpcode::Max); + ENUM_NAME(DxbcOpcode::CustomData); + ENUM_NAME(DxbcOpcode::Mov); + ENUM_NAME(DxbcOpcode::Movc); + ENUM_NAME(DxbcOpcode::Mul); + ENUM_NAME(DxbcOpcode::Ne); + ENUM_NAME(DxbcOpcode::Nop); + ENUM_NAME(DxbcOpcode::Not); + ENUM_NAME(DxbcOpcode::Or); + ENUM_NAME(DxbcOpcode::ResInfo); + ENUM_NAME(DxbcOpcode::Ret); + ENUM_NAME(DxbcOpcode::Retc); + ENUM_NAME(DxbcOpcode::RoundNe); + ENUM_NAME(DxbcOpcode::RoundNi); + ENUM_NAME(DxbcOpcode::RoundPi); + ENUM_NAME(DxbcOpcode::RoundZ); + ENUM_NAME(DxbcOpcode::Rsq); + ENUM_NAME(DxbcOpcode::Sample); + ENUM_NAME(DxbcOpcode::SampleC); + ENUM_NAME(DxbcOpcode::SampleClz); + ENUM_NAME(DxbcOpcode::SampleL); + ENUM_NAME(DxbcOpcode::SampleD); + ENUM_NAME(DxbcOpcode::SampleB); + ENUM_NAME(DxbcOpcode::Sqrt); + ENUM_NAME(DxbcOpcode::Switch); + ENUM_NAME(DxbcOpcode::SinCos); + ENUM_NAME(DxbcOpcode::UDiv); + ENUM_NAME(DxbcOpcode::ULt); + ENUM_NAME(DxbcOpcode::UGe); + ENUM_NAME(DxbcOpcode::UMul); + ENUM_NAME(DxbcOpcode::UMad); + ENUM_NAME(DxbcOpcode::UMax); + ENUM_NAME(DxbcOpcode::UMin); + ENUM_NAME(DxbcOpcode::UShr); + ENUM_NAME(DxbcOpcode::UtoF); + ENUM_NAME(DxbcOpcode::Xor); + ENUM_NAME(DxbcOpcode::DclResource); + ENUM_NAME(DxbcOpcode::DclConstantBuffer); + ENUM_NAME(DxbcOpcode::DclSampler); + ENUM_NAME(DxbcOpcode::DclIndexRange); + ENUM_NAME(DxbcOpcode::DclGsOutputPrimitiveTopology); + ENUM_NAME(DxbcOpcode::DclGsInputPrimitive); + ENUM_NAME(DxbcOpcode::DclMaxOutputVertexCount); + ENUM_NAME(DxbcOpcode::DclInput); + ENUM_NAME(DxbcOpcode::DclInputSgv); + ENUM_NAME(DxbcOpcode::DclInputSiv); + ENUM_NAME(DxbcOpcode::DclInputPs); + ENUM_NAME(DxbcOpcode::DclInputPsSgv); + ENUM_NAME(DxbcOpcode::DclInputPsSiv); + ENUM_NAME(DxbcOpcode::DclOutput); + ENUM_NAME(DxbcOpcode::DclOutputSgv); + ENUM_NAME(DxbcOpcode::DclOutputSiv); + ENUM_NAME(DxbcOpcode::DclTemps); + ENUM_NAME(DxbcOpcode::DclIndexableTemp); + ENUM_NAME(DxbcOpcode::DclGlobalFlags); + ENUM_NAME(DxbcOpcode::Reserved0); + ENUM_NAME(DxbcOpcode::Lod); + ENUM_NAME(DxbcOpcode::Gather4); + ENUM_NAME(DxbcOpcode::SamplePos); + ENUM_NAME(DxbcOpcode::SampleInfo); + ENUM_NAME(DxbcOpcode::Reserved1); + ENUM_NAME(DxbcOpcode::HsDecls); + ENUM_NAME(DxbcOpcode::HsControlPointPhase); + ENUM_NAME(DxbcOpcode::HsForkPhase); + ENUM_NAME(DxbcOpcode::HsJoinPhase); + ENUM_NAME(DxbcOpcode::EmitStream); + ENUM_NAME(DxbcOpcode::CutStream); + ENUM_NAME(DxbcOpcode::EmitThenCutStream); + ENUM_NAME(DxbcOpcode::InterfaceCall); + ENUM_NAME(DxbcOpcode::BufInfo); + ENUM_NAME(DxbcOpcode::DerivRtxCoarse); + ENUM_NAME(DxbcOpcode::DerivRtxFine); + ENUM_NAME(DxbcOpcode::DerivRtyCoarse); + ENUM_NAME(DxbcOpcode::DerivRtyFine); + ENUM_NAME(DxbcOpcode::Gather4C); + ENUM_NAME(DxbcOpcode::Gather4Po); + ENUM_NAME(DxbcOpcode::Gather4PoC); + ENUM_NAME(DxbcOpcode::Rcp); + ENUM_NAME(DxbcOpcode::F32toF16); + ENUM_NAME(DxbcOpcode::F16toF32); + ENUM_NAME(DxbcOpcode::UAddc); + ENUM_NAME(DxbcOpcode::USubb); + ENUM_NAME(DxbcOpcode::CountBits); + ENUM_NAME(DxbcOpcode::FirstBitHi); + ENUM_NAME(DxbcOpcode::FirstBitLo); + ENUM_NAME(DxbcOpcode::FirstBitShi); + ENUM_NAME(DxbcOpcode::UBfe); + ENUM_NAME(DxbcOpcode::IBfe); + ENUM_NAME(DxbcOpcode::Bfi); + ENUM_NAME(DxbcOpcode::BfRev); + ENUM_NAME(DxbcOpcode::Swapc); + ENUM_NAME(DxbcOpcode::DclStream); + ENUM_NAME(DxbcOpcode::DclFunctionBody); + ENUM_NAME(DxbcOpcode::DclFunctionTable); + ENUM_NAME(DxbcOpcode::DclInterface); + ENUM_NAME(DxbcOpcode::DclInputControlPointCount); + ENUM_NAME(DxbcOpcode::DclOutputControlPointCount); + ENUM_NAME(DxbcOpcode::DclTessDomain); + ENUM_NAME(DxbcOpcode::DclTessPartitioning); + ENUM_NAME(DxbcOpcode::DclTessOutputPrimitive); + ENUM_NAME(DxbcOpcode::DclHsMaxTessFactor); + ENUM_NAME(DxbcOpcode::DclHsForkPhaseInstanceCount); + ENUM_NAME(DxbcOpcode::DclHsJoinPhaseInstanceCount); + ENUM_NAME(DxbcOpcode::DclThreadGroup); + ENUM_NAME(DxbcOpcode::DclUavTyped); + ENUM_NAME(DxbcOpcode::DclUavRaw); + ENUM_NAME(DxbcOpcode::DclUavStructured); + ENUM_NAME(DxbcOpcode::DclThreadGroupSharedMemoryRaw); + ENUM_NAME(DxbcOpcode::DclThreadGroupSharedMemoryStructured); + ENUM_NAME(DxbcOpcode::DclResourceRaw); + ENUM_NAME(DxbcOpcode::DclResourceStructured); + ENUM_NAME(DxbcOpcode::LdUavTyped); + ENUM_NAME(DxbcOpcode::StoreUavTyped); + ENUM_NAME(DxbcOpcode::LdRaw); + ENUM_NAME(DxbcOpcode::StoreRaw); + ENUM_NAME(DxbcOpcode::LdStructured); + ENUM_NAME(DxbcOpcode::StoreStructured); + ENUM_NAME(DxbcOpcode::AtomicAnd); + ENUM_NAME(DxbcOpcode::AtomicOr); + ENUM_NAME(DxbcOpcode::AtomicXor); + ENUM_NAME(DxbcOpcode::AtomicCmpStore); + ENUM_NAME(DxbcOpcode::AtomicIAdd); + ENUM_NAME(DxbcOpcode::AtomicIMax); + ENUM_NAME(DxbcOpcode::AtomicIMin); + ENUM_NAME(DxbcOpcode::AtomicUMax); + ENUM_NAME(DxbcOpcode::AtomicUMin); + ENUM_NAME(DxbcOpcode::ImmAtomicAlloc); + ENUM_NAME(DxbcOpcode::ImmAtomicConsume); + ENUM_NAME(DxbcOpcode::ImmAtomicIAdd); + ENUM_NAME(DxbcOpcode::ImmAtomicAnd); + ENUM_NAME(DxbcOpcode::ImmAtomicOr); + ENUM_NAME(DxbcOpcode::ImmAtomicXor); + ENUM_NAME(DxbcOpcode::ImmAtomicExch); + ENUM_NAME(DxbcOpcode::ImmAtomicCmpExch); + ENUM_NAME(DxbcOpcode::ImmAtomicIMax); + ENUM_NAME(DxbcOpcode::ImmAtomicIMin); + ENUM_NAME(DxbcOpcode::ImmAtomicUMax); + ENUM_NAME(DxbcOpcode::ImmAtomicUMin); + ENUM_NAME(DxbcOpcode::Sync); + ENUM_NAME(DxbcOpcode::DAdd); + ENUM_NAME(DxbcOpcode::DMax); + ENUM_NAME(DxbcOpcode::DMin); + ENUM_NAME(DxbcOpcode::DMul); + ENUM_NAME(DxbcOpcode::DEq); + ENUM_NAME(DxbcOpcode::DGe); + ENUM_NAME(DxbcOpcode::DLt); + ENUM_NAME(DxbcOpcode::DNe); + ENUM_NAME(DxbcOpcode::DMov); + ENUM_NAME(DxbcOpcode::DMovc); + ENUM_NAME(DxbcOpcode::DtoF); + ENUM_NAME(DxbcOpcode::FtoD); + ENUM_NAME(DxbcOpcode::EvalSnapped); + ENUM_NAME(DxbcOpcode::EvalSampleIndex); + ENUM_NAME(DxbcOpcode::EvalCentroid); + ENUM_NAME(DxbcOpcode::DclGsInstanceCount); + ENUM_DEFAULT(e); + } + } + + + std::ostream& operator << (std::ostream& os, DxbcExtOpcode e) { + switch (e) { + ENUM_NAME(DxbcExtOpcode::Empty); + ENUM_NAME(DxbcExtOpcode::SampleControls); + ENUM_NAME(DxbcExtOpcode::ResourceDim); + ENUM_NAME(DxbcExtOpcode::ResourceReturnType); + ENUM_DEFAULT(e); + } + } + + + std::ostream& operator << (std::ostream& os, DxbcOperandType e) { + switch (e) { + ENUM_NAME(DxbcOperandType::Temp); + ENUM_NAME(DxbcOperandType::Input); + ENUM_NAME(DxbcOperandType::Output); + ENUM_NAME(DxbcOperandType::IndexableTemp); + ENUM_NAME(DxbcOperandType::Imm32); + ENUM_NAME(DxbcOperandType::Imm64); + ENUM_NAME(DxbcOperandType::Sampler); + ENUM_NAME(DxbcOperandType::Resource); + ENUM_NAME(DxbcOperandType::ConstantBuffer); + ENUM_NAME(DxbcOperandType::ImmediateConstantBuffer); + ENUM_NAME(DxbcOperandType::Label); + ENUM_NAME(DxbcOperandType::InputPrimitiveId); + ENUM_NAME(DxbcOperandType::OutputDepth); + ENUM_NAME(DxbcOperandType::Null); + ENUM_NAME(DxbcOperandType::Rasterizer); + ENUM_NAME(DxbcOperandType::OutputCoverageMask); + ENUM_NAME(DxbcOperandType::Stream); + ENUM_NAME(DxbcOperandType::FunctionBody); + ENUM_NAME(DxbcOperandType::FunctionTable); + ENUM_NAME(DxbcOperandType::Interface); + ENUM_NAME(DxbcOperandType::FunctionInput); + ENUM_NAME(DxbcOperandType::FunctionOutput); + ENUM_NAME(DxbcOperandType::OutputControlPointId); + ENUM_NAME(DxbcOperandType::InputForkInstanceId); + ENUM_NAME(DxbcOperandType::InputJoinInstanceId); + ENUM_NAME(DxbcOperandType::InputControlPoint); + ENUM_NAME(DxbcOperandType::OutputControlPoint); + ENUM_NAME(DxbcOperandType::InputPatchConstant); + ENUM_NAME(DxbcOperandType::InputDomainPoint); + ENUM_NAME(DxbcOperandType::ThisPointer); + ENUM_NAME(DxbcOperandType::UnorderedAccessView); + ENUM_NAME(DxbcOperandType::ThreadGroupSharedMemory); + ENUM_NAME(DxbcOperandType::InputThreadId); + ENUM_NAME(DxbcOperandType::InputThreadGroupId); + ENUM_NAME(DxbcOperandType::InputThreadIdInGroup); + ENUM_NAME(DxbcOperandType::InputCoverageMask); + ENUM_NAME(DxbcOperandType::InputThreadIndexInGroup); + ENUM_NAME(DxbcOperandType::InputGsInstanceId); + ENUM_NAME(DxbcOperandType::OutputDepthGe); + ENUM_NAME(DxbcOperandType::OutputDepthLe); + ENUM_NAME(DxbcOperandType::CycleCounter); + ENUM_DEFAULT(e); + } + } + + + std::ostream& operator << (std::ostream& os, dxvk::DxbcOperandExt e) { + switch (e) { + ENUM_NAME(DxbcOperandExt::OperandModifier); + ENUM_DEFAULT(e); + } + } + + + std::ostream& operator << (std::ostream& os, DxbcComponentCount e) { + switch (e) { + ENUM_NAME(DxbcComponentCount::Component0); + ENUM_NAME(DxbcComponentCount::Component1); + ENUM_NAME(DxbcComponentCount::Component4); + ENUM_DEFAULT(e); + } + } + + + std::ostream& operator << (std::ostream& os, DxbcRegMode e) { + switch (e) { + ENUM_NAME(DxbcRegMode::Mask); + ENUM_NAME(DxbcRegMode::Swizzle); + ENUM_NAME(DxbcRegMode::Select1); + ENUM_DEFAULT(e); + } + } + + + std::ostream& operator << (std::ostream& os, DxbcOperandIndexRepresentation e) { + switch (e) { + ENUM_NAME(DxbcOperandIndexRepresentation::Imm32); + ENUM_NAME(DxbcOperandIndexRepresentation::Imm64); + ENUM_NAME(DxbcOperandIndexRepresentation::Relative); + ENUM_NAME(DxbcOperandIndexRepresentation::Imm32Relative); + ENUM_NAME(DxbcOperandIndexRepresentation::Imm64Relative); + ENUM_DEFAULT(e); + } + } + + + std::ostream& operator << (std::ostream& os, DxbcResourceDim e) { + switch (e) { + ENUM_NAME(DxbcResourceDim::Unknown); + ENUM_NAME(DxbcResourceDim::Buffer); + ENUM_NAME(DxbcResourceDim::Texture1D); + ENUM_NAME(DxbcResourceDim::Texture2D); + ENUM_NAME(DxbcResourceDim::Texture2DMs); + ENUM_NAME(DxbcResourceDim::Texture3D); + ENUM_NAME(DxbcResourceDim::TextureCube); + ENUM_NAME(DxbcResourceDim::Texture1DArr); + ENUM_NAME(DxbcResourceDim::Texture2DArr); + ENUM_NAME(DxbcResourceDim::Texture2DMsArr); + ENUM_NAME(DxbcResourceDim::TextureCubeArr); + ENUM_NAME(DxbcResourceDim::RawBuffer); + ENUM_NAME(DxbcResourceDim::StructuredBuffer); + ENUM_DEFAULT(e); + } + } + + + std::ostream& operator << (std::ostream& os, DxbcResourceReturnType e) { + switch (e) { + ENUM_NAME(DxbcResourceReturnType::Unorm); + ENUM_NAME(DxbcResourceReturnType::Snorm); + ENUM_NAME(DxbcResourceReturnType::Sint); + ENUM_NAME(DxbcResourceReturnType::Uint); + ENUM_NAME(DxbcResourceReturnType::Float); + ENUM_NAME(DxbcResourceReturnType::Mixed); + ENUM_NAME(DxbcResourceReturnType::Double); + ENUM_NAME(DxbcResourceReturnType::Continued); + ENUM_NAME(DxbcResourceReturnType::Unused); + ENUM_DEFAULT(e); + } + } + + + std::ostream& operator << (std::ostream& os, DxbcRegisterComponentType e) { + switch (e) { + ENUM_NAME(DxbcRegisterComponentType::Unknown); + ENUM_NAME(DxbcRegisterComponentType::Uint32); + ENUM_NAME(DxbcRegisterComponentType::Sint32); + ENUM_NAME(DxbcRegisterComponentType::Float32); + ENUM_DEFAULT(e); + } + } + + + std::ostream& operator << (std::ostream& os, DxbcInstructionReturnType e) { + switch (e) { + ENUM_NAME(DxbcInstructionReturnType::Float); + ENUM_NAME(DxbcInstructionReturnType::Uint); + ENUM_DEFAULT(e); + } + } + + + std::ostream& operator << (std::ostream& os, DxbcSystemValue e) { + switch (e) { + ENUM_NAME(DxbcSystemValue::None); + ENUM_NAME(DxbcSystemValue::Position); + ENUM_NAME(DxbcSystemValue::ClipDistance); + ENUM_NAME(DxbcSystemValue::CullDistance); + ENUM_NAME(DxbcSystemValue::RenderTargetId); + ENUM_NAME(DxbcSystemValue::ViewportId); + ENUM_NAME(DxbcSystemValue::VertexId); + ENUM_NAME(DxbcSystemValue::PrimitiveId); + ENUM_NAME(DxbcSystemValue::InstanceId); + ENUM_NAME(DxbcSystemValue::IsFrontFace); + ENUM_NAME(DxbcSystemValue::SampleIndex); + ENUM_NAME(DxbcSystemValue::FinalQuadUeq0EdgeTessFactor); + ENUM_NAME(DxbcSystemValue::FinalQuadVeq0EdgeTessFactor); + ENUM_NAME(DxbcSystemValue::FinalQuadUeq1EdgeTessFactor); + ENUM_NAME(DxbcSystemValue::FinalQuadVeq1EdgeTessFactor); + ENUM_NAME(DxbcSystemValue::FinalQuadUInsideTessFactor); + ENUM_NAME(DxbcSystemValue::FinalQuadVInsideTessFactor); + ENUM_NAME(DxbcSystemValue::FinalTriUeq0EdgeTessFactor); + ENUM_NAME(DxbcSystemValue::FinalTriVeq0EdgeTessFactor); + ENUM_NAME(DxbcSystemValue::FinalTriWeq0EdgeTessFactor); + ENUM_NAME(DxbcSystemValue::FinalTriInsideTessFactor); + ENUM_NAME(DxbcSystemValue::FinalLineDetailTessFactor); + ENUM_NAME(DxbcSystemValue::FinalLineDensityTessFactor); + ENUM_NAME(DxbcSystemValue::Target); + ENUM_NAME(DxbcSystemValue::Depth); + ENUM_NAME(DxbcSystemValue::Coverage); + ENUM_NAME(DxbcSystemValue::DepthGe); + ENUM_NAME(DxbcSystemValue::DepthLe); + ENUM_DEFAULT(e); + } + } + + + std::ostream& operator << (std::ostream& os, dxvk::DxbcProgramType e) { + switch (e) { + ENUM_NAME(DxbcProgramType::PixelShader); + ENUM_NAME(DxbcProgramType::VertexShader); + ENUM_NAME(DxbcProgramType::GeometryShader); + ENUM_NAME(DxbcProgramType::HullShader); + ENUM_NAME(DxbcProgramType::DomainShader); + ENUM_NAME(DxbcProgramType::ComputeShader); + ENUM_DEFAULT(e); + } + } + + std::ostream& operator << (std::ostream& os, dxvk::DxbcCustomDataClass e) { + switch (e) { + ENUM_NAME(DxbcCustomDataClass::Comment); + ENUM_NAME(DxbcCustomDataClass::DebugInfo); + ENUM_NAME(DxbcCustomDataClass::Opaque); + ENUM_NAME(DxbcCustomDataClass::ImmConstBuf); + ENUM_DEFAULT(e); + } + } + + std::ostream& operator << (std::ostream& os, dxvk::DxbcScalarType e) { + switch (e) { + ENUM_NAME(DxbcScalarType::Uint32); + ENUM_NAME(DxbcScalarType::Uint64); + ENUM_NAME(DxbcScalarType::Sint32); + ENUM_NAME(DxbcScalarType::Sint64); + ENUM_NAME(DxbcScalarType::Float32); + ENUM_NAME(DxbcScalarType::Float64); + ENUM_NAME(DxbcScalarType::Bool); + ENUM_DEFAULT(e); + } + } + + +} //namespace dxvk diff --git a/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_reader.cpp b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_reader.cpp new file mode 100644 index 000000000..9b9a340a3 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_reader.cpp @@ -0,0 +1,58 @@ +#include + +#include "dxbc_reader.h" + +namespace dxvk { + + DxbcTag DxbcReader::readTag() { + DxbcTag tag; + this->read(&tag, 4); + return tag; + } + + + std::string DxbcReader::readString() { + std::string result; + + while (m_data[m_pos] != '\0') + result.push_back(m_data[m_pos++]); + + m_pos++; + return result; + } + + + void DxbcReader::read(void* dst, size_t n) { + if (m_pos + n > m_size) + throw DxvkError("DxbcReader::read: Unexpected end of file"); + std::memcpy(dst, m_data + m_pos, n); + m_pos += n; + } + + + void DxbcReader::skip(size_t n) { + if (m_pos + n > m_size) + throw DxvkError("DxbcReader::skip: Unexpected end of file"); + m_pos += n; + } + + + DxbcReader DxbcReader::clone(size_t pos) const { + if (pos > m_size) + throw DxvkError("DxbcReader::clone: Invalid offset"); + return DxbcReader(m_data + pos, m_size - pos); + } + + + DxbcReader DxbcReader::resize(size_t size) const { + if (size > m_size) + throw DxvkError("DxbcReader::resize: Invalid size"); + return DxbcReader(m_data, size, m_pos); + } + + + void DxbcReader::store(std::ostream&& stream) const { + stream.write(m_data, m_size); + } + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_util.cpp b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_util.cpp new file mode 100644 index 000000000..3d885fc9c --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/dxbc/dxbc_util.cpp @@ -0,0 +1,26 @@ +#include "dxbc_util.h" + +namespace dxvk { + + uint32_t primitiveVertexCount(DxbcPrimitive primitive) { + static const std::array s_vertexCounts = { + 0, // Undefined + 1, // Point + 2, // Line + 3, // Triangle + 0, // Undefined + 0, // Undefined + 4, // Line with adjacency + 6, // Triangle with adjacency + }; + + if (primitive >= DxbcPrimitive::Patch1) { + return uint32_t(primitive) + - uint32_t(DxbcPrimitive::Patch1) + + 1u; + } else { + return s_vertexCounts.at(uint32_t(primitive)); + } + } + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/src/spirv/spirv_code_buffer.cpp b/app/src/main/cpp/thirdparty/dxbc/src/spirv/spirv_code_buffer.cpp new file mode 100644 index 000000000..f8166df51 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/spirv/spirv_code_buffer.cpp @@ -0,0 +1,166 @@ +#include +#include + +#include "spirv_code_buffer.h" + +namespace dxvk { + + SpirvCodeBuffer:: SpirvCodeBuffer() { } + SpirvCodeBuffer::~SpirvCodeBuffer() { } + + + SpirvCodeBuffer::SpirvCodeBuffer(uint32_t size) + : m_ptr(size) { + m_code.resize(size); + } + + + SpirvCodeBuffer::SpirvCodeBuffer(uint32_t size, const uint32_t* data) + : m_ptr(size) { + m_code.resize(size); + std::memcpy(m_code.data(), data, size * sizeof(uint32_t)); + } + + + SpirvCodeBuffer::SpirvCodeBuffer(std::istream& stream) { + stream.ignore(std::numeric_limits::max()); + std::streamsize length = stream.gcount(); + stream.clear(); + stream.seekg(0, std::ios_base::beg); + + std::vector buffer(length); + stream.read(buffer.data(), length); + buffer.resize(stream.gcount()); + + m_code.resize(buffer.size() / sizeof(uint32_t)); + std::memcpy(reinterpret_cast(m_code.data()), + buffer.data(), m_code.size() * sizeof(uint32_t)); + + m_ptr = m_code.size(); + } + + + uint32_t SpirvCodeBuffer::allocId() { + constexpr size_t BoundIdsOffset = 3; + + if (m_code.size() <= BoundIdsOffset) + return 0; + + return m_code[BoundIdsOffset]++; + } + + + void SpirvCodeBuffer::append(const SpirvInstruction& ins) { + const size_t size = m_code.size(); + + m_code.resize(size + ins.length()); + + for (uint32_t i = 0; i < ins.length(); i++) + m_code[size + i] = ins.arg(i); + + m_ptr += ins.length(); + } + + + void SpirvCodeBuffer::append(const SpirvCodeBuffer& other) { + if (other.size() != 0) { + const size_t size = m_code.size(); + m_code.resize(size + other.m_code.size()); + + uint32_t* dst = this->m_code.data(); + const uint32_t* src = other.m_code.data(); + + std::memcpy(dst + size, src, other.size()); + m_ptr += other.m_code.size(); + } + } + + + void SpirvCodeBuffer::putWord(uint32_t word) { + m_code.insert(m_code.begin() + m_ptr, word); + m_ptr += 1; + } + + + void SpirvCodeBuffer::putIns(spv::Op opCode, uint16_t wordCount) { + this->putWord( + (static_cast(opCode) << 0) + | (static_cast(wordCount) << 16)); + } + + + void SpirvCodeBuffer::putInt32(uint32_t word) { + this->putWord(word); + } + + + void SpirvCodeBuffer::putInt64(uint64_t value) { + this->putWord(value >> 0); + this->putWord(value >> 32); + } + + + void SpirvCodeBuffer::putFloat32(float value) { + uint32_t tmp; + static_assert(sizeof(tmp) == sizeof(value)); + std::memcpy(&tmp, &value, sizeof(value)); + this->putInt32(tmp); + } + + + void SpirvCodeBuffer::putFloat64(double value) { + uint64_t tmp; + static_assert(sizeof(tmp) == sizeof(value)); + std::memcpy(&tmp, &value, sizeof(value)); + this->putInt64(tmp); + } + + + void SpirvCodeBuffer::putStr(const char* str) { + uint32_t word = 0; + uint32_t nbit = 0; + + for (uint32_t i = 0; str[i] != '\0'; str++) { + word |= (static_cast(str[i]) & 0xFF) << nbit; + + if ((nbit += 8) == 32) { + this->putWord(word); + word = 0; + nbit = 0; + } + } + + // Commit current word + this->putWord(word); + } + + + void SpirvCodeBuffer::putHeader(uint32_t version, uint32_t boundIds) { + this->putWord(spv::MagicNumber); + this->putWord(version); + this->putWord(0); // Generator + this->putWord(boundIds); + this->putWord(0); // Schema + } + + + void SpirvCodeBuffer::erase(size_t size) { + m_code.erase( + m_code.begin() + m_ptr, + m_code.begin() + m_ptr + size); + } + + + uint32_t SpirvCodeBuffer::strLen(const char* str) { + // Null-termination plus padding + return (std::strlen(str) + 4) / 4; + } + + + void SpirvCodeBuffer::store(std::ostream& stream) const { + stream.write( + reinterpret_cast(m_code.data()), + sizeof(uint32_t) * m_code.size()); + } + +} \ No newline at end of file diff --git a/app/src/main/cpp/thirdparty/dxbc/src/spirv/spirv_module.cpp b/app/src/main/cpp/thirdparty/dxbc/src/spirv/spirv_module.cpp new file mode 100644 index 000000000..aad1b39ec --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/spirv/spirv_module.cpp @@ -0,0 +1,4085 @@ +#include + +#include "spirv_module.h" + +namespace dxvk { + + SpirvModule::SpirvModule(uint32_t version) + : m_version(version) { + this->instImportGlsl450(); + } + + + SpirvModule::~SpirvModule() { + + } + + + SpirvCodeBuffer SpirvModule::compile() { + SpirvCodeBuffer result; + result.putHeader(m_version, m_id); + result.append(m_capabilities); + result.append(m_extensions); + result.append(m_instExt); + result.append(m_memoryModel); + result.append(m_entryPoints); + result.append(m_execModeInfo); + result.append(m_debugNames); + result.append(m_annotations); + result.append(m_typeConstDefs); + result.append(m_variables); + + // Perform some crude dead code elimination. In some cases, our compilers + // may emit invalid code, such as an unreachable block branching to a loop's + // continue block, but those cases cannot be reasonably detected up-front. + std::unordered_set reachableBlocks; + std::unordered_set mergeBlocks; + + classifyBlocks(reachableBlocks, mergeBlocks); + + bool reachable = true; + + for (auto ins : m_code) { + if (ins.opCode() == spv::OpFunctionEnd) { + reachable = true; + result.append(ins); + } else if (ins.opCode() == spv::OpLabel) { + uint32_t labelId = ins.arg(1); + + if ((reachable = reachableBlocks.find(labelId) != reachableBlocks.end())) { + result.append(ins); + } else if (mergeBlocks.find(labelId) != mergeBlocks.end()) { + result.append(ins); + result.putIns(spv::OpUnreachable, 1); + } + } else if (reachable) { + result.append(ins); + } + } + + return result; + } + + + uint32_t SpirvModule::allocateId() { + return m_id++; + } + + + bool SpirvModule::hasCapability( + spv::Capability capability) { + for (auto ins : m_capabilities) { + if (ins.opCode() == spv::OpCapability && ins.arg(1) == capability) + return true; + } + + return false; + } + + void SpirvModule::enableCapability( + spv::Capability capability) { + // Scan the generated instructions to check + // whether we already enabled the capability. + if (!hasCapability(capability)) { + m_capabilities.putIns (spv::OpCapability, 2); + m_capabilities.putWord(capability); + } + } + + + void SpirvModule::enableExtension( + const char* extensionName) { + m_extensions.putIns (spv::OpExtension, 1 + m_extensions.strLen(extensionName)); + m_extensions.putStr (extensionName); + } + + + void SpirvModule::addEntryPoint( + uint32_t entryPointId, + spv::ExecutionModel executionModel, + const char* name) { + m_entryPoints.putIns (spv::OpEntryPoint, 3 + m_entryPoints.strLen(name) + m_interfaceVars.size()); + m_entryPoints.putWord (executionModel); + m_entryPoints.putWord (entryPointId); + m_entryPoints.putStr (name); + + for (uint32_t varId : m_interfaceVars) + m_entryPoints.putWord(varId); + } + + + void SpirvModule::setMemoryModel( + spv::AddressingModel addressModel, + spv::MemoryModel memoryModel) { + m_memoryModel.putIns (spv::OpMemoryModel, 3); + m_memoryModel.putWord (addressModel); + m_memoryModel.putWord (memoryModel); + } + + + void SpirvModule::setExecutionMode( + uint32_t entryPointId, + spv::ExecutionMode executionMode) { + m_execModeInfo.putIns (spv::OpExecutionMode, 3); + m_execModeInfo.putWord(entryPointId); + m_execModeInfo.putWord(executionMode); + } + + + void SpirvModule::setExecutionMode( + uint32_t entryPointId, + spv::ExecutionMode executionMode, + uint32_t argCount, + const uint32_t* args) { + m_execModeInfo.putIns (spv::OpExecutionMode, 3 + argCount); + m_execModeInfo.putWord(entryPointId); + m_execModeInfo.putWord(executionMode); + + for (uint32_t i = 0; i < argCount; i++) + m_execModeInfo.putWord(args[i]); + } + + + void SpirvModule::setInvocations( + uint32_t entryPointId, + uint32_t invocations) { + m_execModeInfo.putIns (spv::OpExecutionMode, 4); + m_execModeInfo.putWord (entryPointId); + m_execModeInfo.putWord (spv::ExecutionModeInvocations); + m_execModeInfo.putInt32(invocations); + } + + + void SpirvModule::setLocalSize( + uint32_t entryPointId, + uint32_t x, + uint32_t y, + uint32_t z) { + m_execModeInfo.putIns (spv::OpExecutionMode, 6); + m_execModeInfo.putWord (entryPointId); + m_execModeInfo.putWord (spv::ExecutionModeLocalSize); + m_execModeInfo.putInt32(x); + m_execModeInfo.putInt32(y); + m_execModeInfo.putInt32(z); + } + + + void SpirvModule::setOutputVertices( + uint32_t entryPointId, + uint32_t vertexCount) { + m_execModeInfo.putIns (spv::OpExecutionMode, 4); + m_execModeInfo.putWord(entryPointId); + m_execModeInfo.putWord(spv::ExecutionModeOutputVertices); + m_execModeInfo.putWord(vertexCount); + } + + + uint32_t SpirvModule::addDebugString( + const char* string) { + uint32_t resultId = this->allocateId(); + + m_debugNames.putIns (spv::OpString, + 2 + m_debugNames.strLen(string)); + m_debugNames.putWord(resultId); + m_debugNames.putStr (string); + return resultId; + } + + + void SpirvModule::setDebugSource( + spv::SourceLanguage language, + uint32_t version, + uint32_t file, + const char* source) { + uint32_t strLen = source != nullptr + ? m_debugNames.strLen(source) : 0; + + m_debugNames.putIns (spv::OpSource, 4 + strLen); + m_debugNames.putWord(language); + m_debugNames.putWord(version); + m_debugNames.putWord(file); + + if (source != nullptr) + m_debugNames.putStr(source); + } + + void SpirvModule::setDebugName( + uint32_t expressionId, + const char* debugName) { + m_debugNames.putIns (spv::OpName, 2 + m_debugNames.strLen(debugName)); + m_debugNames.putWord(expressionId); + m_debugNames.putStr (debugName); + } + + + void SpirvModule::setDebugMemberName( + uint32_t structId, + uint32_t memberId, + const char* debugName) { + m_debugNames.putIns (spv::OpMemberName, 3 + m_debugNames.strLen(debugName)); + m_debugNames.putWord(structId); + m_debugNames.putWord(memberId); + m_debugNames.putStr (debugName); + } + + + uint32_t SpirvModule::constBool( + bool v) { + return this->defConst(v + ? spv::OpConstantTrue + : spv::OpConstantFalse, + this->defBoolType(), + 0, nullptr); + } + + + uint32_t SpirvModule::consti32( + int32_t v) { + std::array data; + std::memcpy(data.data(), &v, sizeof(v)); + + return this->defConst( + spv::OpConstant, + this->defIntType(32, 1), + data.size(), + data.data()); + } + + + uint32_t SpirvModule::consti64( + int64_t v) { + std::array data; + std::memcpy(data.data(), &v, sizeof(v)); + + return this->defConst( + spv::OpConstant, + this->defIntType(64, 1), + data.size(), + data.data()); + } + + + uint32_t SpirvModule::constu32( + uint32_t v) { + std::array data; + std::memcpy(data.data(), &v, sizeof(v)); + + return this->defConst( + spv::OpConstant, + this->defIntType(32, 0), + data.size(), + data.data()); + } + + + uint32_t SpirvModule::constu64( + uint64_t v) { + std::array data; + std::memcpy(data.data(), &v, sizeof(v)); + + return this->defConst( + spv::OpConstant, + this->defIntType(64, 0), + data.size(), + data.data()); + } + + + uint32_t SpirvModule::constf32( + float v) { + std::array data; + std::memcpy(data.data(), &v, sizeof(v)); + + return this->defConst( + spv::OpConstant, + this->defFloatType(32), + data.size(), + data.data()); + } + + + uint32_t SpirvModule::constf64( + double v) { + std::array data; + std::memcpy(data.data(), &v, sizeof(v)); + + return this->defConst( + spv::OpConstant, + this->defFloatType(64), + data.size(), + data.data()); + } + + + uint32_t SpirvModule::constvec4i32( + int32_t x, + int32_t y, + int32_t z, + int32_t w) { + std::array args = {{ + this->consti32(x), this->consti32(y), + this->consti32(z), this->consti32(w), + }}; + + uint32_t scalarTypeId = this->defIntType(32, 1); + uint32_t vectorTypeId = this->defVectorType(scalarTypeId, 4); + + return this->constComposite(vectorTypeId, args.size(), args.data()); + } + + + uint32_t SpirvModule::constvec4b32( + bool x, + bool y, + bool z, + bool w) { + std::array args = {{ + this->constBool(x), this->constBool(y), + this->constBool(z), this->constBool(w), + }}; + + uint32_t scalarTypeId = this->defBoolType(); + uint32_t vectorTypeId = this->defVectorType(scalarTypeId, 4); + + return this->constComposite(vectorTypeId, args.size(), args.data()); + } + + + uint32_t SpirvModule::constvec4u32( + uint32_t x, + uint32_t y, + uint32_t z, + uint32_t w) { + std::array args = {{ + this->constu32(x), this->constu32(y), + this->constu32(z), this->constu32(w), + }}; + + uint32_t scalarTypeId = this->defIntType(32, 0); + uint32_t vectorTypeId = this->defVectorType(scalarTypeId, 4); + + return this->constComposite(vectorTypeId, args.size(), args.data()); + } + + + uint32_t SpirvModule::constvec2f32( + float x, + float y) { + std::array args = {{ + this->constf32(x), this->constf32(y), + }}; + + uint32_t scalarTypeId = this->defFloatType(32); + uint32_t vectorTypeId = this->defVectorType(scalarTypeId, 2); + + return this->constComposite(vectorTypeId, args.size(), args.data()); + } + + + uint32_t SpirvModule::constvec3f32( + float x, + float y, + float z) { + std::array args = {{ + this->constf32(x), this->constf32(y), + this->constf32(z), + }}; + + uint32_t scalarTypeId = this->defFloatType(32); + uint32_t vectorTypeId = this->defVectorType(scalarTypeId, 3); + + return this->constComposite(vectorTypeId, args.size(), args.data()); + } + + + uint32_t SpirvModule::constvec4f32( + float x, + float y, + float z, + float w) { + std::array args = {{ + this->constf32(x), this->constf32(y), + this->constf32(z), this->constf32(w), + }}; + + uint32_t scalarTypeId = this->defFloatType(32); + uint32_t vectorTypeId = this->defVectorType(scalarTypeId, 4); + + return this->constComposite(vectorTypeId, args.size(), args.data()); + } + + + uint32_t SpirvModule::constfReplicant( + float replicant, + uint32_t count) { + uint32_t value = this->constf32(replicant); + + std::array args = { value, value, value, value }; + + // Can't make a scalar composite. + if (count == 1) + return args[0]; + + uint32_t scalarTypeId = this->defFloatType(32); + uint32_t vectorTypeId = this->defVectorType(scalarTypeId, count); + + return this->constComposite(vectorTypeId, count, args.data()); + } + + + uint32_t SpirvModule::constbReplicant( + bool replicant, + uint32_t count) { + uint32_t value = this->constBool(replicant); + + std::array args = { value, value, value, value }; + + // Can't make a scalar composite. + if (count == 1) + return args[0]; + + uint32_t scalarTypeId = this->defBoolType(); + uint32_t vectorTypeId = this->defVectorType(scalarTypeId, count); + + return this->constComposite(vectorTypeId, count, args.data()); + } + + + uint32_t SpirvModule::constiReplicant( + int32_t replicant, + uint32_t count) { + uint32_t value = this->consti32(replicant); + + std::array args = { value, value, value, value }; + + // Can't make a scalar composite. + if (count == 1) + return args[0]; + + uint32_t scalarTypeId = this->defIntType(32, 1); + uint32_t vectorTypeId = this->defVectorType(scalarTypeId, count); + + return this->constComposite(vectorTypeId, count, args.data()); + } + + + uint32_t SpirvModule::constuReplicant( + int32_t replicant, + uint32_t count) { + uint32_t value = this->constu32(replicant); + + std::array args = { value, value, value, value }; + + // Can't make a scalar composite. + if (count == 1) + return args[0]; + + uint32_t scalarTypeId = this->defIntType(32, 0); + uint32_t vectorTypeId = this->defVectorType(scalarTypeId, count); + + return this->constComposite(vectorTypeId, count, args.data()); + } + + + uint32_t SpirvModule::constComposite( + uint32_t typeId, + uint32_t constCount, + const uint32_t* constIds) { + return this->defConst( + spv::OpConstantComposite, + typeId, constCount, constIds); + } + + + uint32_t SpirvModule::constUndef( + uint32_t typeId) { + return this->defConst(spv::OpUndef, + typeId, 0, nullptr); + } + + + uint32_t SpirvModule::constNull( + uint32_t typeId) { + return this->defConst(spv::OpConstantNull, + typeId, 0, nullptr); + } + + + uint32_t SpirvModule::lateConst32( + uint32_t typeId) { + uint32_t resultId = this->allocateId(); + m_lateConsts.insert(resultId); + + m_typeConstDefs.putIns (spv::OpConstant, 4); + m_typeConstDefs.putWord(typeId); + m_typeConstDefs.putWord(resultId); + m_typeConstDefs.putWord(0); + return resultId; + } + + + void SpirvModule::setLateConst( + uint32_t constId, + const uint32_t* argIds) { + for (auto ins : m_typeConstDefs) { + if (ins.opCode() != spv::OpConstant + && ins.opCode() != spv::OpConstantComposite) + continue; + + if (ins.arg(2) != constId) + continue; + + for (uint32_t i = 3; i < ins.length(); i++) + ins.setArg(i, argIds[i - 3]); + + return; + } + } + + + uint32_t SpirvModule::specConstBool( + bool v) { + uint32_t typeId = this->defBoolType(); + uint32_t resultId = this->allocateId(); + + const spv::Op op = v + ? spv::OpSpecConstantTrue + : spv::OpSpecConstantFalse; + + m_typeConstDefs.putIns (op, 3); + m_typeConstDefs.putWord (typeId); + m_typeConstDefs.putWord (resultId); + return resultId; + } + + + uint32_t SpirvModule::specConst32( + uint32_t typeId, + uint32_t value) { + uint32_t resultId = this->allocateId(); + + m_typeConstDefs.putIns (spv::OpSpecConstant, 4); + m_typeConstDefs.putWord (typeId); + m_typeConstDefs.putWord (resultId); + m_typeConstDefs.putWord (value); + return resultId; + } + + + void SpirvModule::decorate( + uint32_t object, + spv::Decoration decoration) { + m_annotations.putIns (spv::OpDecorate, 3); + m_annotations.putWord (object); + m_annotations.putWord (decoration); + } + + + void SpirvModule::decorateArrayStride( + uint32_t object, + uint32_t stride) { + m_annotations.putIns (spv::OpDecorate, 4); + m_annotations.putWord (object); + m_annotations.putWord (spv::DecorationArrayStride); + m_annotations.putInt32(stride); + } + + + void SpirvModule::decorateBinding( + uint32_t object, + uint32_t binding) { + m_annotations.putIns (spv::OpDecorate, 4); + m_annotations.putWord (object); + m_annotations.putWord (spv::DecorationBinding); + m_annotations.putInt32(binding); + } + + + void SpirvModule::decorateBlock(uint32_t object) { + m_annotations.putIns (spv::OpDecorate, 3); + m_annotations.putWord (object); + m_annotations.putWord (spv::DecorationBlock); + } + + + void SpirvModule::decorateBuiltIn( + uint32_t object, + spv::BuiltIn builtIn) { + m_annotations.putIns (spv::OpDecorate, 4); + m_annotations.putWord (object); + m_annotations.putWord (spv::DecorationBuiltIn); + m_annotations.putWord (builtIn); + } + + + void SpirvModule::decorateComponent( + uint32_t object, + uint32_t location) { + m_annotations.putIns (spv::OpDecorate, 4); + m_annotations.putWord (object); + m_annotations.putWord (spv::DecorationComponent); + m_annotations.putInt32(location); + } + + + void SpirvModule::decorateDescriptorSet( + uint32_t object, + uint32_t set) { + m_annotations.putIns (spv::OpDecorate, 4); + m_annotations.putWord (object); + m_annotations.putWord (spv::DecorationDescriptorSet); + m_annotations.putInt32(set); + } + + + void SpirvModule::decorateIndex( + uint32_t object, + uint32_t index) { + m_annotations.putIns (spv::OpDecorate, 4); + m_annotations.putWord (object); + m_annotations.putWord (spv::DecorationIndex); + m_annotations.putInt32(index); + } + + + void SpirvModule::decorateLocation( + uint32_t object, + uint32_t location) { + m_annotations.putIns (spv::OpDecorate, 4); + m_annotations.putWord (object); + m_annotations.putWord (spv::DecorationLocation); + m_annotations.putInt32(location); + } + + + void SpirvModule::decorateSpecId( + uint32_t object, + uint32_t specId) { + m_annotations.putIns (spv::OpDecorate, 4); + m_annotations.putWord (object); + m_annotations.putWord (spv::DecorationSpecId); + m_annotations.putInt32(specId); + } + + + void SpirvModule::decorateXfb( + uint32_t object, + uint32_t streamId, + uint32_t bufferId, + uint32_t offset, + uint32_t stride) { + m_annotations.putIns (spv::OpDecorate, 4); + m_annotations.putWord (object); + m_annotations.putWord (spv::DecorationStream); + m_annotations.putInt32(streamId); + + m_annotations.putIns (spv::OpDecorate, 4); + m_annotations.putWord (object); + m_annotations.putWord (spv::DecorationXfbBuffer); + m_annotations.putInt32(bufferId); + + m_annotations.putIns (spv::OpDecorate, 4); + m_annotations.putWord (object); + m_annotations.putWord (spv::DecorationXfbStride); + m_annotations.putInt32(stride); + + m_annotations.putIns (spv::OpDecorate, 4); + m_annotations.putWord (object); + m_annotations.putWord (spv::DecorationOffset); + m_annotations.putInt32(offset); + } + + + void SpirvModule::memberDecorateBuiltIn( + uint32_t structId, + uint32_t memberId, + spv::BuiltIn builtIn) { + m_annotations.putIns (spv::OpMemberDecorate, 5); + m_annotations.putWord (structId); + m_annotations.putWord (memberId); + m_annotations.putWord (spv::DecorationBuiltIn); + m_annotations.putWord (builtIn); + } + + + void SpirvModule::memberDecorate( + uint32_t structId, + uint32_t memberId, + spv::Decoration decoration) { + m_annotations.putIns (spv::OpMemberDecorate, 4); + m_annotations.putWord (structId); + m_annotations.putWord (memberId); + m_annotations.putWord (decoration); + } + + + void SpirvModule::memberDecorateMatrixStride( + uint32_t structId, + uint32_t memberId, + uint32_t stride) { + m_annotations.putIns (spv::OpMemberDecorate, 5); + m_annotations.putWord (structId); + m_annotations.putWord (memberId); + m_annotations.putWord (spv::DecorationMatrixStride); + m_annotations.putWord (stride); + } + + + void SpirvModule::memberDecorateOffset( + uint32_t structId, + uint32_t memberId, + uint32_t offset) { + m_annotations.putIns (spv::OpMemberDecorate, 5); + m_annotations.putWord (structId); + m_annotations.putWord (memberId); + m_annotations.putWord (spv::DecorationOffset); + m_annotations.putWord (offset); + } + + + uint32_t SpirvModule::defVoidType() { + return this->defType(spv::OpTypeVoid, 0, nullptr); + } + + + uint32_t SpirvModule::defBoolType() { + return this->defType(spv::OpTypeBool, 0, nullptr); + } + + + uint32_t SpirvModule::defIntType( + uint32_t width, + uint32_t isSigned) { + std::array args = {{ width, isSigned }}; + return this->defType(spv::OpTypeInt, + args.size(), args.data()); + } + + + uint32_t SpirvModule::defFloatType( + uint32_t width) { + std::array args = {{ width }}; + return this->defType(spv::OpTypeFloat, + args.size(), args.data()); + } + + + uint32_t SpirvModule::defVectorType( + uint32_t elementType, + uint32_t elementCount) { + std::array args = + {{ elementType, elementCount }}; + + return this->defType(spv::OpTypeVector, + args.size(), args.data()); + } + + + uint32_t SpirvModule::defMatrixType( + uint32_t columnType, + uint32_t columnCount) { + std::array args = + {{ columnType, columnCount }}; + + return this->defType(spv::OpTypeMatrix, + args.size(), args.data()); + } + + + uint32_t SpirvModule::defArrayType( + uint32_t typeId, + uint32_t length) { + std::array args = {{ typeId, length }}; + + return this->defType(spv::OpTypeArray, + args.size(), args.data()); + } + + + uint32_t SpirvModule::defArrayTypeUnique( + uint32_t typeId, + uint32_t length) { + uint32_t resultId = this->allocateId(); + + m_typeConstDefs.putIns (spv::OpTypeArray, 4); + m_typeConstDefs.putWord(resultId); + m_typeConstDefs.putWord(typeId); + m_typeConstDefs.putWord(length); + return resultId; + } + + + uint32_t SpirvModule::defRuntimeArrayType( + uint32_t typeId) { + std::array args = { typeId }; + + return this->defType(spv::OpTypeRuntimeArray, + args.size(), args.data()); + } + + + uint32_t SpirvModule::defRuntimeArrayTypeUnique( + uint32_t typeId) { + uint32_t resultId = this->allocateId(); + + m_typeConstDefs.putIns (spv::OpTypeRuntimeArray, 3); + m_typeConstDefs.putWord(resultId); + m_typeConstDefs.putWord(typeId); + return resultId; + } + + + uint32_t SpirvModule::defFunctionType( + uint32_t returnType, + uint32_t argCount, + const uint32_t* argTypes) { + std::vector args; + args.push_back(returnType); + + for (uint32_t i = 0; i < argCount; i++) + args.push_back(argTypes[i]); + + return this->defType(spv::OpTypeFunction, + args.size(), args.data()); + } + + + uint32_t SpirvModule::defStructType( + uint32_t memberCount, + const uint32_t* memberTypes) { + return this->defType(spv::OpTypeStruct, + memberCount, memberTypes); + } + + + uint32_t SpirvModule::defStructTypeUnique( + uint32_t memberCount, + const uint32_t* memberTypes) { + uint32_t resultId = this->allocateId(); + + m_typeConstDefs.putIns (spv::OpTypeStruct, 2 + memberCount); + m_typeConstDefs.putWord(resultId); + + for (uint32_t i = 0; i < memberCount; i++) + m_typeConstDefs.putWord(memberTypes[i]); + return resultId; + } + + + uint32_t SpirvModule::defPointerType( + uint32_t variableType, + spv::StorageClass storageClass) { + std::array args = {{ + static_cast(storageClass), + variableType, + }}; + + return this->defType(spv::OpTypePointer, + args.size(), args.data()); + } + + + uint32_t SpirvModule::defSamplerType() { + return this->defType(spv::OpTypeSampler, 0, nullptr); + } + + + uint32_t SpirvModule::defImageType( + uint32_t sampledType, + spv::Dim dimensionality, + uint32_t depth, + uint32_t arrayed, + uint32_t multisample, + uint32_t sampled, + spv::ImageFormat format) { + std::array args = {{ + sampledType, + static_cast(dimensionality), + depth, arrayed, + multisample, + sampled, + static_cast(format) + }}; + + return this->defType(spv::OpTypeImage, + args.size(), args.data()); + } + + + uint32_t SpirvModule::defSampledImageType( + uint32_t imageType) { + return this->defType(spv::OpTypeSampledImage, 1, &imageType); + } + + + uint32_t SpirvModule::newVar( + uint32_t pointerType, + spv::StorageClass storageClass) { + uint32_t resultId = this->allocateId(); + + if (isInterfaceVar(storageClass)) + m_interfaceVars.push_back(resultId); + + auto& code = storageClass != spv::StorageClassFunction + ? m_variables : m_code; + + code.putIns (spv::OpVariable, 4); + code.putWord (pointerType); + code.putWord (resultId); + code.putWord (storageClass); + return resultId; + } + + + uint32_t SpirvModule::newVarInit( + uint32_t pointerType, + spv::StorageClass storageClass, + uint32_t initialValue) { + uint32_t resultId = this->allocateId(); + + if (isInterfaceVar(storageClass)) + m_interfaceVars.push_back(resultId); + + auto& code = storageClass != spv::StorageClassFunction + ? m_variables : m_code; + + code.putIns (spv::OpVariable, 5); + code.putWord (pointerType); + code.putWord (resultId); + code.putWord (storageClass); + code.putWord (initialValue); + return resultId; + } + + + void SpirvModule::functionBegin( + uint32_t returnType, + uint32_t functionId, + uint32_t functionType, + spv::FunctionControlMask functionControl) { + m_code.putIns (spv::OpFunction, 5); + m_code.putWord(returnType); + m_code.putWord(functionId); + m_code.putWord(functionControl); + m_code.putWord(functionType); + } + + + uint32_t SpirvModule::functionParameter( + uint32_t parameterType) { + uint32_t parameterId = this->allocateId(); + + m_code.putIns (spv::OpFunctionParameter, 3); + m_code.putWord(parameterType); + m_code.putWord(parameterId); + return parameterId; + } + + + void SpirvModule::functionEnd() { + m_code.putIns (spv::OpFunctionEnd, 1); + } + + + uint32_t SpirvModule::opAccessChain( + uint32_t resultType, + uint32_t composite, + uint32_t indexCount, + const uint32_t* indexArray) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAccessChain, 4 + indexCount); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(composite); + + for (uint32_t i = 0; i < indexCount; i++) + m_code.putInt32(indexArray[i]); + return resultId; + } + + + uint32_t SpirvModule::opArrayLength( + uint32_t resultType, + uint32_t structure, + uint32_t memberId) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpArrayLength, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(structure); + m_code.putWord(memberId); + return resultId; + } + + + uint32_t SpirvModule::opAny( + uint32_t resultType, + uint32_t vector) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAny, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector); + return resultId; + } + + + uint32_t SpirvModule::opAll( + uint32_t resultType, + uint32_t vector) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAll, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector); + return resultId; + } + + + uint32_t SpirvModule::opAtomicLoad( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAtomicLoad, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(pointer); + m_code.putWord(scope); + m_code.putWord(semantics); + return resultId; + } + + + void SpirvModule::opAtomicStore( + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value) { + m_code.putIns (spv::OpAtomicStore, 5); + m_code.putWord(pointer); + m_code.putWord(scope); + m_code.putWord(semantics); + m_code.putWord(value); + } + + + uint32_t SpirvModule::opAtomicExchange( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAtomicExchange, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(pointer); + m_code.putWord(scope); + m_code.putWord(semantics); + m_code.putWord(value); + return resultId; + } + + + uint32_t SpirvModule::opAtomicCompareExchange( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t equal, + uint32_t unequal, + uint32_t value, + uint32_t comparator) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAtomicCompareExchange, 9); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(pointer); + m_code.putWord(scope); + m_code.putWord(equal); + m_code.putWord(unequal); + m_code.putWord(value); + m_code.putWord(comparator); + return resultId; + } + + + uint32_t SpirvModule::opAtomicIIncrement( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAtomicIIncrement, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(pointer); + m_code.putWord(scope); + m_code.putWord(semantics); + return resultId; + } + + + uint32_t SpirvModule::opAtomicIDecrement( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAtomicIDecrement, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(pointer); + m_code.putWord(scope); + m_code.putWord(semantics); + return resultId; + } + + + uint32_t SpirvModule::opAtomicIAdd( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAtomicIAdd, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(pointer); + m_code.putWord(scope); + m_code.putWord(semantics); + m_code.putWord(value); + return resultId; + } + + + uint32_t SpirvModule::opAtomicISub( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAtomicISub, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(pointer); + m_code.putWord(scope); + m_code.putWord(semantics); + m_code.putWord(value); + return resultId; + } + + + uint32_t SpirvModule::opAtomicSMin( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAtomicSMin, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(pointer); + m_code.putWord(scope); + m_code.putWord(semantics); + m_code.putWord(value); + return resultId; + } + + + uint32_t SpirvModule::opAtomicSMax( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAtomicSMax, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(pointer); + m_code.putWord(scope); + m_code.putWord(semantics); + m_code.putWord(value); + return resultId; + } + + + uint32_t SpirvModule::opAtomicUMin( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAtomicUMin, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(pointer); + m_code.putWord(scope); + m_code.putWord(semantics); + m_code.putWord(value); + return resultId; + } + + + uint32_t SpirvModule::opAtomicUMax( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAtomicUMax, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(pointer); + m_code.putWord(scope); + m_code.putWord(semantics); + m_code.putWord(value); + return resultId; + } + + + uint32_t SpirvModule::opAtomicAnd( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAtomicAnd, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(pointer); + m_code.putWord(scope); + m_code.putWord(semantics); + m_code.putWord(value); + return resultId; + } + + + uint32_t SpirvModule::opAtomicOr( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAtomicOr, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(pointer); + m_code.putWord(scope); + m_code.putWord(semantics); + m_code.putWord(value); + return resultId; + } + + + uint32_t SpirvModule::opAtomicXor( + uint32_t resultType, + uint32_t pointer, + uint32_t scope, + uint32_t semantics, + uint32_t value) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpAtomicXor, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(pointer); + m_code.putWord(scope); + m_code.putWord(semantics); + m_code.putWord(value); + return resultId; + } + + + uint32_t SpirvModule::opBitcast( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpBitcast, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opBitCount( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpBitCount, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opBitReverse( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpBitReverse, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opFindILsb( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450FindILsb); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opFindUMsb( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450FindUMsb); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opFindSMsb( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450FindSMsb); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opBitFieldInsert( + uint32_t resultType, + uint32_t base, + uint32_t insert, + uint32_t offset, + uint32_t count) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpBitFieldInsert, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(base); + m_code.putWord(insert); + m_code.putWord(offset); + m_code.putWord(count); + return resultId; + } + + + uint32_t SpirvModule::opBitFieldSExtract( + uint32_t resultType, + uint32_t base, + uint32_t offset, + uint32_t count) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpBitFieldSExtract, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(base); + m_code.putWord(offset); + m_code.putWord(count); + return resultId; + } + + + uint32_t SpirvModule::opBitFieldUExtract( + uint32_t resultType, + uint32_t base, + uint32_t offset, + uint32_t count) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpBitFieldUExtract, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(base); + m_code.putWord(offset); + m_code.putWord(count); + return resultId; + } + + + uint32_t SpirvModule::opBitwiseAnd( + uint32_t resultType, + uint32_t operand1, + uint32_t operand2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpBitwiseAnd, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand1); + m_code.putWord(operand2); + return resultId; + } + + + uint32_t SpirvModule::opBitwiseOr( + uint32_t resultType, + uint32_t operand1, + uint32_t operand2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpBitwiseOr, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand1); + m_code.putWord(operand2); + return resultId; + } + + + uint32_t SpirvModule::opBitwiseXor( + uint32_t resultType, + uint32_t operand1, + uint32_t operand2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpBitwiseXor, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand1); + m_code.putWord(operand2); + return resultId; + } + + + uint32_t SpirvModule::opNot( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpNot, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opShiftLeftLogical( + uint32_t resultType, + uint32_t base, + uint32_t shift) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpShiftLeftLogical, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(base); + m_code.putWord(shift); + return resultId; + } + + + uint32_t SpirvModule::opShiftRightArithmetic( + uint32_t resultType, + uint32_t base, + uint32_t shift) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpShiftRightArithmetic, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(base); + m_code.putWord(shift); + return resultId; + } + + + uint32_t SpirvModule::opShiftRightLogical( + uint32_t resultType, + uint32_t base, + uint32_t shift) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpShiftRightLogical, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(base); + m_code.putWord(shift); + return resultId; + } + + + uint32_t SpirvModule::opConvertFtoS( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpConvertFToS, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opConvertFtoU( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpConvertFToU, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opConvertStoF( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpConvertSToF, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opConvertUtoF( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpConvertUToF, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opCompositeConstruct( + uint32_t resultType, + uint32_t valueCount, + const uint32_t* valueArray) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpCompositeConstruct, 3 + valueCount); + m_code.putWord(resultType); + m_code.putWord(resultId); + + for (uint32_t i = 0; i < valueCount; i++) + m_code.putWord(valueArray[i]); + return resultId; + } + + + uint32_t SpirvModule::opCompositeExtract( + uint32_t resultType, + uint32_t composite, + uint32_t indexCount, + const uint32_t* indexArray) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpCompositeExtract, 4 + indexCount); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(composite); + + for (uint32_t i = 0; i < indexCount; i++) + m_code.putInt32(indexArray[i]); + return resultId; + } + + + uint32_t SpirvModule::opCompositeInsert( + uint32_t resultType, + uint32_t object, + uint32_t composite, + uint32_t indexCount, + const uint32_t* indexArray) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpCompositeInsert, 5 + indexCount); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(object); + m_code.putWord(composite); + + for (uint32_t i = 0; i < indexCount; i++) + m_code.putInt32(indexArray[i]); + return resultId; + } + + + uint32_t SpirvModule::opDpdx( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpDPdx, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opDpdy( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpDPdy, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opDpdxCoarse( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpDPdxCoarse, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opDpdyCoarse( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpDPdyCoarse, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opDpdxFine( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpDPdxFine, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opDpdyFine( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpDPdyFine, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opVectorExtractDynamic( + uint32_t resultType, + uint32_t vector, + uint32_t index) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpVectorExtractDynamic, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector); + m_code.putWord(index); + return resultId; + } + + + uint32_t SpirvModule::opVectorShuffle( + uint32_t resultType, + uint32_t vectorLeft, + uint32_t vectorRight, + uint32_t indexCount, + const uint32_t* indexArray) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpVectorShuffle, 5 + indexCount); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vectorLeft); + m_code.putWord(vectorRight); + + for (uint32_t i = 0; i < indexCount; i++) + m_code.putInt32(indexArray[i]); + return resultId; + } + + + uint32_t SpirvModule::opSNegate( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpSNegate, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opFNegate( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpFNegate, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opSAbs( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450SAbs); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opFAbs( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450FAbs); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opFSign( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450FSign); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opFMix( + uint32_t resultType, + uint32_t x, + uint32_t y, + uint32_t a) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 8); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450FMix); + m_code.putWord(x); + m_code.putWord(y); + m_code.putWord(a); + return resultId; + } + + + uint32_t SpirvModule::opCross( + uint32_t resultType, + uint32_t x, + uint32_t y) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Cross); + m_code.putWord(x); + m_code.putWord(y); + return resultId; + } + + + uint32_t SpirvModule::opIAdd( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpIAdd, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opISub( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpISub, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opFAdd( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpFAdd, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opFSub( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpFSub, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opSDiv( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpSDiv, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opUDiv( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpUDiv, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opSRem( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpSRem, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opUMod( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpUMod, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opFDiv( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpFDiv, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opIMul( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpIMul, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opFMul( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpFMul, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opVectorTimesScalar( + uint32_t resultType, + uint32_t vector, + uint32_t scalar) { + uint32_t resultId = this->allocateId(); + + m_code.putIns(spv::OpVectorTimesScalar, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector); + m_code.putWord(scalar); + return resultId; + } + + + uint32_t SpirvModule::opMatrixTimesMatrix( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns(spv::OpMatrixTimesMatrix, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opMatrixTimesVector( + uint32_t resultType, + uint32_t matrix, + uint32_t vector) { + uint32_t resultId = this->allocateId(); + + m_code.putIns(spv::OpMatrixTimesVector, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(matrix); + m_code.putWord(vector); + return resultId; + } + + + uint32_t SpirvModule::opVectorTimesMatrix( + uint32_t resultType, + uint32_t vector, + uint32_t matrix) { + uint32_t resultId = this->allocateId(); + + m_code.putIns(spv::OpVectorTimesMatrix, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector); + m_code.putWord(matrix); + return resultId; + } + + + uint32_t SpirvModule::opTranspose( + uint32_t resultType, + uint32_t matrix) { + uint32_t resultId = this->allocateId(); + + m_code.putIns(spv::OpTranspose, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(matrix); + return resultId; + } + + + uint32_t SpirvModule::opInverse( + uint32_t resultType, + uint32_t matrix) { + uint32_t resultId = this->allocateId(); + + m_code.putIns(spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450MatrixInverse); + m_code.putWord(matrix); + return resultId; + } + + + uint32_t SpirvModule::opFFma( + uint32_t resultType, + uint32_t a, + uint32_t b, + uint32_t c) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 8); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Fma); + m_code.putWord(a); + m_code.putWord(b); + m_code.putWord(c); + return resultId; + } + + + uint32_t SpirvModule::opFMax( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450FMax); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opFMin( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450FMin); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opNMax( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450NMax); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opNMin( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450NMin); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opSMax( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450SMax); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opSMin( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450SMin); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opUMax( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450UMax); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opUMin( + uint32_t resultType, + uint32_t a, + uint32_t b) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450UMin); + m_code.putWord(a); + m_code.putWord(b); + return resultId; + } + + + uint32_t SpirvModule::opFClamp( + uint32_t resultType, + uint32_t x, + uint32_t minVal, + uint32_t maxVal) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 8); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450FClamp); + m_code.putWord(x); + m_code.putWord(minVal); + m_code.putWord(maxVal); + return resultId; + } + + + uint32_t SpirvModule::opNClamp( + uint32_t resultType, + uint32_t x, + uint32_t minVal, + uint32_t maxVal) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 8); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450NClamp); + m_code.putWord(x); + m_code.putWord(minVal); + m_code.putWord(maxVal); + return resultId; + } + + + uint32_t SpirvModule::opIEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpIEqual, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opINotEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpINotEqual, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opSLessThan( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpSLessThan, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opSLessThanEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpSLessThanEqual, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opSGreaterThan( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpSGreaterThan, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opSGreaterThanEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpSGreaterThanEqual, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opULessThan( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpULessThan, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opULessThanEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpULessThanEqual, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opUGreaterThan( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpUGreaterThan, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opUGreaterThanEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpUGreaterThanEqual, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opFOrdEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpFOrdEqual, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opFUnordNotEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpFUnordNotEqual, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opFOrdLessThan( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpFOrdLessThan, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opFOrdLessThanEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpFOrdLessThanEqual, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opFOrdGreaterThan( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpFOrdGreaterThan, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opFOrdGreaterThanEqual( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpFOrdGreaterThanEqual, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opLogicalEqual( + uint32_t resultType, + uint32_t operand1, + uint32_t operand2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpLogicalEqual, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand1); + m_code.putWord(operand2); + return resultId; + } + + + uint32_t SpirvModule::opLogicalNotEqual( + uint32_t resultType, + uint32_t operand1, + uint32_t operand2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpLogicalNotEqual, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand1); + m_code.putWord(operand2); + return resultId; + } + + + uint32_t SpirvModule::opLogicalAnd( + uint32_t resultType, + uint32_t operand1, + uint32_t operand2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpLogicalAnd, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand1); + m_code.putWord(operand2); + return resultId; + } + + + uint32_t SpirvModule::opLogicalOr( + uint32_t resultType, + uint32_t operand1, + uint32_t operand2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpLogicalOr, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand1); + m_code.putWord(operand2); + return resultId; + } + + + uint32_t SpirvModule::opLogicalNot( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpLogicalNot, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opDot( + uint32_t resultType, + uint32_t vector1, + uint32_t vector2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpDot, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(vector1); + m_code.putWord(vector2); + return resultId; + } + + + uint32_t SpirvModule::opSin( + uint32_t resultType, + uint32_t vector) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Sin); + m_code.putWord(vector); + return resultId; + } + + + uint32_t SpirvModule::opCos( + uint32_t resultType, + uint32_t vector) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Cos); + m_code.putWord(vector); + return resultId; + } + + + uint32_t SpirvModule::opSqrt( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Sqrt); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opInverseSqrt( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450InverseSqrt); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opNormalize( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Normalize); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opRawAccessChain( + uint32_t resultType, + uint32_t base, + uint32_t stride, + uint32_t index, + uint32_t offset, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpRawAccessChainNV, operand ? 8 : 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(base); + m_code.putWord(stride); + m_code.putWord(index); + m_code.putWord(offset); + + if (operand) + m_code.putWord(operand); + + return resultId; + } + + + uint32_t SpirvModule::opReflect( + uint32_t resultType, + uint32_t incident, + uint32_t normal) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Reflect); + m_code.putWord(incident); + m_code.putWord(normal); + return resultId; + } + + + uint32_t SpirvModule::opLength( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Length); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opExp2( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Exp2); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opExp( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Exp); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opLog2( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Log2); + m_code.putWord(operand); + return resultId; + } + + uint32_t SpirvModule::opPow( + uint32_t resultType, + uint32_t base, + uint32_t exponent) { + uint32_t resultId = this->allocateId(); + + m_code.putIns(spv::OpExtInst, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Pow); + m_code.putWord(base); + m_code.putWord(exponent); + return resultId; + } + + uint32_t SpirvModule::opFract( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Fract); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opCeil( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Ceil); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opFloor( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Floor); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opRound( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Round); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opRoundEven( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450RoundEven); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opTrunc( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450Trunc); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opFConvert( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpFConvert, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opPackHalf2x16( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450PackHalf2x16); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opUnpackHalf2x16( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450UnpackHalf2x16); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opSelect( + uint32_t resultType, + uint32_t condition, + uint32_t operand1, + uint32_t operand2) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpSelect, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(condition); + m_code.putWord(operand1); + m_code.putWord(operand2); + return resultId; + } + + + uint32_t SpirvModule::opIsNan( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpIsNan, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opIsInf( + uint32_t resultType, + uint32_t operand) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpIsInf, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(operand); + return resultId; + } + + + uint32_t SpirvModule::opFunctionCall( + uint32_t resultType, + uint32_t functionId, + uint32_t argCount, + const uint32_t* argIds) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpFunctionCall, 4 + argCount); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(functionId); + + for (uint32_t i = 0; i < argCount; i++) + m_code.putWord(argIds[i]); + return resultId; + } + + + void SpirvModule::opLabel(uint32_t labelId) { + m_code.putIns (spv::OpLabel, 2); + m_code.putWord(labelId); + + m_blockId = labelId; + } + + + uint32_t SpirvModule::opLoad( + uint32_t typeId, + uint32_t pointerId) { + return opLoad(typeId, pointerId, SpirvMemoryOperands()); + } + + + uint32_t SpirvModule::opLoad( + uint32_t typeId, + uint32_t pointerId, + const SpirvMemoryOperands& operands) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpLoad, 4 + getMemoryOperandWordCount(operands)); + m_code.putWord(typeId); + m_code.putWord(resultId); + m_code.putWord(pointerId); + + putMemoryOperands(operands); + return resultId; + } + + + void SpirvModule::opStore( + uint32_t pointerId, + uint32_t valueId) { + opStore(pointerId, valueId, SpirvMemoryOperands()); + } + + + void SpirvModule::opStore( + uint32_t pointerId, + uint32_t valueId, + const SpirvMemoryOperands& operands) { + m_code.putIns (spv::OpStore, 3 + getMemoryOperandWordCount(operands)); + m_code.putWord(pointerId); + m_code.putWord(valueId); + + putMemoryOperands(operands); + } + + + uint32_t SpirvModule::opInterpolateAtCentroid( + uint32_t resultType, + uint32_t interpolant) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450InterpolateAtCentroid); + m_code.putWord(interpolant); + return resultId; + } + + + uint32_t SpirvModule::opInterpolateAtSample( + uint32_t resultType, + uint32_t interpolant, + uint32_t sample) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450InterpolateAtSample); + m_code.putWord(interpolant); + m_code.putWord(sample); + return resultId; + } + + + uint32_t SpirvModule::opInterpolateAtOffset( + uint32_t resultType, + uint32_t interpolant, + uint32_t offset) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpExtInst, 7); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(m_instExtGlsl450); + m_code.putWord(GLSLstd450InterpolateAtOffset); + m_code.putWord(interpolant); + m_code.putWord(offset); + return resultId; + } + + + uint32_t SpirvModule::opImage( + uint32_t resultType, + uint32_t sampledImage) { + uint32_t resultId = this->allocateId(); + + m_code.putIns(spv::OpImage, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(sampledImage); + return resultId; + } + + + uint32_t SpirvModule::opImageRead( + uint32_t resultType, + uint32_t image, + uint32_t coordinates, + const SpirvImageOperands& operands) { + uint32_t resultId = this->allocateId(); + + spv::Op op = operands.sparse + ? spv::OpImageSparseRead + : spv::OpImageRead; + + m_code.putIns(op, 5 + getImageOperandWordCount(operands)); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(image); + m_code.putWord(coordinates); + + putImageOperands(operands); + return resultId; + } + + + void SpirvModule::opImageWrite( + uint32_t image, + uint32_t coordinates, + uint32_t texel, + const SpirvImageOperands& operands) { + m_code.putIns (spv::OpImageWrite, + 4 + getImageOperandWordCount(operands)); + m_code.putWord(image); + m_code.putWord(coordinates); + m_code.putWord(texel); + + putImageOperands(operands); + } + + + uint32_t SpirvModule::opImageSparseTexelsResident( + uint32_t resultType, + uint32_t residentCode) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpImageSparseTexelsResident, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(residentCode); + + return resultId; + } + + + uint32_t SpirvModule::opSampledImage( + uint32_t resultType, + uint32_t image, + uint32_t sampler) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpSampledImage, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(image); + m_code.putWord(sampler); + return resultId; + } + + + uint32_t SpirvModule::opImageTexelPointer( + uint32_t resultType, + uint32_t image, + uint32_t coordinates, + uint32_t sample) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpImageTexelPointer, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(image); + m_code.putWord(coordinates); + m_code.putWord(sample); + return resultId; + } + + + uint32_t SpirvModule::opImageQuerySizeLod( + uint32_t resultType, + uint32_t image, + uint32_t lod) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpImageQuerySizeLod, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(image); + m_code.putWord(lod); + return resultId; + } + + + uint32_t SpirvModule::opImageQuerySize( + uint32_t resultType, + uint32_t image) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpImageQuerySize, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(image); + return resultId; + } + + + uint32_t SpirvModule::opImageQueryLevels( + uint32_t resultType, + uint32_t image) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpImageQueryLevels, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(image); + return resultId; + } + + + uint32_t SpirvModule::opImageQueryLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpImageQueryLod, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(sampledImage); + m_code.putWord(coordinates); + return resultId; + } + + + uint32_t SpirvModule::opImageQuerySamples( + uint32_t resultType, + uint32_t image) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpImageQuerySamples, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(image); + return resultId; + } + + + uint32_t SpirvModule::opImageFetch( + uint32_t resultType, + uint32_t image, + uint32_t coordinates, + const SpirvImageOperands& operands) { + uint32_t resultId = this->allocateId(); + + spv::Op op = operands.sparse + ? spv::OpImageSparseFetch + : spv::OpImageFetch; + + m_code.putIns(op, 5 + getImageOperandWordCount(operands)); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(image); + m_code.putWord(coordinates); + + putImageOperands(operands); + return resultId; + } + + + uint32_t SpirvModule::opImageGather( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + uint32_t component, + const SpirvImageOperands& operands) { + uint32_t resultId = this->allocateId(); + + spv::Op op = operands.sparse + ? spv::OpImageSparseGather + : spv::OpImageGather; + + m_code.putIns(op, 6 + getImageOperandWordCount(operands)); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(sampledImage); + m_code.putWord(coordinates); + m_code.putWord(component); + + putImageOperands(operands); + return resultId; + } + + + uint32_t SpirvModule::opImageDrefGather( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + uint32_t reference, + const SpirvImageOperands& operands) { + uint32_t resultId = this->allocateId(); + + spv::Op op = operands.sparse + ? spv::OpImageSparseDrefGather + : spv::OpImageDrefGather; + + m_code.putIns(op, 6 + getImageOperandWordCount(operands)); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(sampledImage); + m_code.putWord(coordinates); + m_code.putWord(reference); + + putImageOperands(operands); + return resultId; + } + + + uint32_t SpirvModule::opImageSampleImplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + const SpirvImageOperands& operands) { + uint32_t resultId = this->allocateId(); + + spv::Op op = operands.sparse + ? spv::OpImageSparseSampleImplicitLod + : spv::OpImageSampleImplicitLod; + + m_code.putIns(op, 5 + getImageOperandWordCount(operands)); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(sampledImage); + m_code.putWord(coordinates); + + putImageOperands(operands); + return resultId; + } + + + uint32_t SpirvModule::opImageSampleExplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + const SpirvImageOperands& operands) { + uint32_t resultId = this->allocateId(); + + spv::Op op = operands.sparse + ? spv::OpImageSparseSampleExplicitLod + : spv::OpImageSampleExplicitLod; + + m_code.putIns(op, 5 + getImageOperandWordCount(operands)); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(sampledImage); + m_code.putWord(coordinates); + + putImageOperands(operands); + return resultId; + } + + + uint32_t SpirvModule::opImageSampleProjImplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + const SpirvImageOperands& operands) { + uint32_t resultId = this->allocateId(); + + spv::Op op = operands.sparse + ? spv::OpImageSparseSampleProjImplicitLod + : spv::OpImageSampleProjImplicitLod; + + m_code.putIns(op, 5 + getImageOperandWordCount(operands)); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(sampledImage); + m_code.putWord(coordinates); + + putImageOperands(operands); + return resultId; + } + + + uint32_t SpirvModule::opImageSampleProjExplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + const SpirvImageOperands& operands) { + uint32_t resultId = this->allocateId(); + + spv::Op op = operands.sparse + ? spv::OpImageSparseSampleProjExplicitLod + : spv::OpImageSampleProjExplicitLod; + + m_code.putIns(op, 5 + getImageOperandWordCount(operands)); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(sampledImage); + m_code.putWord(coordinates); + + putImageOperands(operands); + return resultId; + } + + + uint32_t SpirvModule::opImageSampleDrefImplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + uint32_t reference, + const SpirvImageOperands& operands) { + uint32_t resultId = this->allocateId(); + + spv::Op op = operands.sparse + ? spv::OpImageSparseSampleDrefImplicitLod + : spv::OpImageSampleDrefImplicitLod; + + m_code.putIns(op, 6 + getImageOperandWordCount(operands)); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(sampledImage); + m_code.putWord(coordinates); + m_code.putWord(reference); + + putImageOperands(operands); + return resultId; + } + + + uint32_t SpirvModule::opImageSampleDrefExplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + uint32_t reference, + const SpirvImageOperands& operands) { + uint32_t resultId = this->allocateId(); + + spv::Op op = operands.sparse + ? spv::OpImageSparseSampleDrefExplicitLod + : spv::OpImageSampleDrefExplicitLod; + + m_code.putIns(op, 6 + getImageOperandWordCount(operands)); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(sampledImage); + m_code.putWord(coordinates); + m_code.putWord(reference); + + putImageOperands(operands); + return resultId; + } + + + uint32_t SpirvModule::opImageSampleProjDrefImplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + uint32_t reference, + const SpirvImageOperands& operands) { + uint32_t resultId = this->allocateId(); + + spv::Op op = operands.sparse + ? spv::OpImageSparseSampleProjDrefImplicitLod + : spv::OpImageSampleProjDrefImplicitLod; + + m_code.putIns(op, 6 + getImageOperandWordCount(operands)); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(sampledImage); + m_code.putWord(coordinates); + m_code.putWord(reference); + + putImageOperands(operands); + return resultId; + } + + + uint32_t SpirvModule::opImageSampleProjDrefExplicitLod( + uint32_t resultType, + uint32_t sampledImage, + uint32_t coordinates, + uint32_t reference, + const SpirvImageOperands& operands) { + uint32_t resultId = this->allocateId(); + + spv::Op op = operands.sparse + ? spv::OpImageSparseSampleProjDrefExplicitLod + : spv::OpImageSampleProjDrefExplicitLod; + + m_code.putIns(op, 6 + getImageOperandWordCount(operands)); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(sampledImage); + m_code.putWord(coordinates); + m_code.putWord(reference); + + putImageOperands(operands); + return resultId; + } + + + uint32_t SpirvModule::opGroupNonUniformBallot( + uint32_t resultType, + uint32_t execution, + uint32_t predicate) { + uint32_t resultId = this->allocateId(); + + m_code.putIns(spv::OpGroupNonUniformBallot, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(execution); + m_code.putWord(predicate); + return resultId; + } + + + uint32_t SpirvModule::opGroupNonUniformBallotBitCount( + uint32_t resultType, + uint32_t execution, + uint32_t operation, + uint32_t ballot) { + uint32_t resultId = this->allocateId(); + + m_code.putIns(spv::OpGroupNonUniformBallotBitCount, 6); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(execution); + m_code.putWord(operation); + m_code.putWord(ballot); + return resultId; + } + + + uint32_t SpirvModule::opGroupNonUniformElect( + uint32_t resultType, + uint32_t execution) { + uint32_t resultId = this->allocateId(); + + m_code.putIns(spv::OpGroupNonUniformElect, 4); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(execution); + return resultId; + } + + + uint32_t SpirvModule::opGroupNonUniformBroadcastFirst( + uint32_t resultType, + uint32_t execution, + uint32_t value) { + uint32_t resultId = this->allocateId(); + + m_code.putIns(spv::OpGroupNonUniformBroadcastFirst, 5); + m_code.putWord(resultType); + m_code.putWord(resultId); + m_code.putWord(execution); + m_code.putWord(value); + return resultId; + } + + + void SpirvModule::opControlBarrier( + uint32_t execution, + uint32_t memory, + uint32_t semantics) { + m_code.putIns (spv::OpControlBarrier, 4); + m_code.putWord(execution); + m_code.putWord(memory); + m_code.putWord(semantics); + } + + + void SpirvModule::opMemoryBarrier( + uint32_t memory, + uint32_t semantics) { + m_code.putIns (spv::OpMemoryBarrier, 3); + m_code.putWord(memory); + m_code.putWord(semantics); + } + + + void SpirvModule::opLoopMerge( + uint32_t mergeBlock, + uint32_t continueTarget, + uint32_t loopControl) { + m_code.putIns (spv::OpLoopMerge, 4); + m_code.putWord(mergeBlock); + m_code.putWord(continueTarget); + m_code.putWord(loopControl); + } + + + void SpirvModule::opSelectionMerge( + uint32_t mergeBlock, + uint32_t selectionControl) { + m_code.putIns (spv::OpSelectionMerge, 3); + m_code.putWord(mergeBlock); + m_code.putWord(selectionControl); + } + + + void SpirvModule::opBranch( + uint32_t label) { + m_code.putIns (spv::OpBranch, 2); + m_code.putWord(label); + + m_blockId = 0; + } + + + void SpirvModule::opBranchConditional( + uint32_t condition, + uint32_t trueLabel, + uint32_t falseLabel) { + m_code.putIns (spv::OpBranchConditional, 4); + m_code.putWord(condition); + m_code.putWord(trueLabel); + m_code.putWord(falseLabel); + + m_blockId = 0; + } + + + void SpirvModule::opSwitch( + uint32_t selector, + uint32_t jumpDefault, + uint32_t caseCount, + const SpirvSwitchCaseLabel* caseLabels) { + m_code.putIns (spv::OpSwitch, 3 + 2 * caseCount); + m_code.putWord(selector); + m_code.putWord(jumpDefault); + + for (uint32_t i = 0; i < caseCount; i++) { + m_code.putWord(caseLabels[i].literal); + m_code.putWord(caseLabels[i].labelId); + } + + m_blockId = 0; + } + + + uint32_t SpirvModule::opPhi( + uint32_t resultType, + uint32_t sourceCount, + const SpirvPhiLabel* sourceLabels) { + uint32_t resultId = this->allocateId(); + + m_code.putIns (spv::OpPhi, 3 + 2 * sourceCount); + m_code.putWord(resultType); + m_code.putWord(resultId); + + for (uint32_t i = 0; i < sourceCount; i++) { + m_code.putWord(sourceLabels[i].varId); + m_code.putWord(sourceLabels[i].labelId); + } + + return resultId; + } + + + void SpirvModule::opReturn() { + m_code.putIns (spv::OpReturn, 1); + m_blockId = 0; + } + + + void SpirvModule::opDemoteToHelperInvocation() { + m_code.putIns (spv::OpDemoteToHelperInvocation, 1); + } + + + void SpirvModule::opEmitVertex( + uint32_t streamId) { + if (streamId == 0) { + m_code.putIns (spv::OpEmitVertex, 1); + } else { + m_code.putIns (spv::OpEmitStreamVertex, 2); + m_code.putWord(streamId); + } + } + + + void SpirvModule::opEndPrimitive( + uint32_t streamId) { + if (streamId == 0) { + m_code.putIns (spv::OpEndPrimitive, 1); + } else { + m_code.putIns (spv::OpEndStreamPrimitive, 2); + m_code.putWord(streamId); + } + } + + + void SpirvModule::opBeginInvocationInterlock() { + m_code.putIns(spv::OpBeginInvocationInterlockEXT, 1); + } + + + void SpirvModule::opEndInvocationInterlock() { + m_code.putIns(spv::OpEndInvocationInterlockEXT, 1); + } + + + uint32_t SpirvModule::opSinCos( + uint32_t x, + bool useBuiltIn) { + // We only operate on 32-bit floats here + uint32_t floatType = defFloatType(32); + uint32_t resultType = defVectorType(floatType, 2u); + + if (useBuiltIn) { + std::array members = { opSin(floatType, x), opCos(floatType, x) }; + return opCompositeConstruct(resultType, members.size(), members.data()); + } else { + uint32_t uintType = defIntType(32, false); + uint32_t sintType = defIntType(32, true); + uint32_t boolType = defBoolType(); + + // Normalize input to multiple of pi/4 + uint32_t xNorm = opFMul(floatType, opFAbs(floatType, x), constf32(4.0 / pi)); + + uint32_t xTrunc = opTrunc(floatType, xNorm); + uint32_t xFract = opFSub(floatType, xNorm, xTrunc); + + uint32_t xInt = opConvertFtoU(uintType, xTrunc); + + // Mirror input along x axis as necessary + uint32_t mirror = opINotEqual(boolType, opBitwiseAnd(uintType, xInt, constu32(1u)), constu32(0u)); + xFract = opSelect(floatType, mirror, opFSub(floatType, constf32(1.0f), xFract), xFract); + + // Compute taylor series for fractional part + uint32_t xFract_2 = opFMul(floatType, xFract, xFract); + uint32_t xFract_4 = opFMul(floatType, xFract_2, xFract_2); + uint32_t xFract_6 = opFMul(floatType, xFract_4, xFract_2); + + uint32_t taylor = opFMul(floatType, xFract_6, constf32(-sincosTaylorFactor(7))); + decorate(taylor, spv::DecorationNoContraction); + + taylor = opFFma(floatType, xFract_4, constf32(sincosTaylorFactor(5)), taylor); + decorate(taylor, spv::DecorationNoContraction); + + taylor = opFFma(floatType, xFract_2, constf32(-sincosTaylorFactor(3)), taylor); + decorate(taylor, spv::DecorationNoContraction); + + taylor = opFAdd(floatType, constf32(sincosTaylorFactor(1)), taylor); + decorate(taylor, spv::DecorationNoContraction); + + taylor = opFMul(floatType, taylor, xFract); + decorate(taylor, spv::DecorationNoContraction); + + // Compute co-function based on sin^2 + cos^2 = 1 + uint32_t coFunc = opSqrt(floatType, opFSub(floatType, constf32(1.0f), opFMul(floatType, taylor, taylor))); + + // Determine whether the taylor series was used for sine or cosine and assign the correct result + uint32_t funcIsSin = opIEqual(boolType, opBitwiseAnd(uintType, opIAdd(uintType, xInt, constu32(1u)), constu32(2u)), constu32(0u)); + + uint32_t sin = opSelect(floatType, funcIsSin, taylor, coFunc); + uint32_t cos = opSelect(floatType, funcIsSin, coFunc, taylor); + + // Determine whether sine is negative. Interpret the input as a + // signed integer in order to propagate signed zeroes properly. + uint32_t inputNeg = opSLessThan(boolType, opBitcast(sintType, x), consti32(0)); + + uint32_t sinNeg = opINotEqual(boolType, opBitwiseAnd(uintType, xInt, constu32(4u)), constu32(0u)); + sinNeg = opLogicalNotEqual(boolType, sinNeg, inputNeg); + + // Determine whether cosine is negative + uint32_t cosNeg = opINotEqual(boolType, opBitwiseAnd(uintType, opIAdd(uintType, xInt, constu32(2u)), constu32(4u)), constu32(0u)); + + sin = opSelect(floatType, sinNeg, opFNegate(floatType, sin), sin); + cos = opSelect(floatType, cosNeg, opFNegate(floatType, cos), cos); + + std::array members = { sin, cos }; + return opCompositeConstruct(resultType, members.size(), members.data()); + } + } + + + uint32_t SpirvModule::defType( + spv::Op op, + uint32_t argCount, + const uint32_t* argIds) { + // Since the type info is stored in the code buffer, + // we can use the code buffer to look up type IDs as + // well. Result IDs are always stored as argument 1. + for (auto ins : m_typeConstDefs) { + bool match = ins.opCode() == op + && ins.length() == 2 + argCount; + + for (uint32_t i = 0; i < argCount && match; i++) + match &= ins.arg(2 + i) == argIds[i]; + + if (match) + return ins.arg(1); + } + + // Type not yet declared, create a new one. + uint32_t resultId = this->allocateId(); + m_typeConstDefs.putIns (op, 2 + argCount); + m_typeConstDefs.putWord(resultId); + + for (uint32_t i = 0; i < argCount; i++) + m_typeConstDefs.putWord(argIds[i]); + return resultId; + } + + + uint32_t SpirvModule::defConst( + spv::Op op, + uint32_t typeId, + uint32_t argCount, + const uint32_t* argIds) { + // Avoid declaring constants multiple times + for (auto ins : m_typeConstDefs) { + bool match = ins.opCode() == op + && ins.length() == 3 + argCount + && ins.arg(1) == typeId; + + for (uint32_t i = 0; i < argCount && match; i++) + match &= ins.arg(3 + i) == argIds[i]; + + if (!match) + continue; + + uint32_t id = ins.arg(2); + + if (m_lateConsts.find(id) == m_lateConsts.end()) + return id; + } + + // Constant not yet declared, make a new one + uint32_t resultId = this->allocateId(); + m_typeConstDefs.putIns (op, 3 + argCount); + m_typeConstDefs.putWord(typeId); + m_typeConstDefs.putWord(resultId); + + for (uint32_t i = 0; i < argCount; i++) + m_typeConstDefs.putWord(argIds[i]); + return resultId; + } + + + void SpirvModule::instImportGlsl450() { + m_instExtGlsl450 = this->allocateId(); + const char* name = "GLSL.std.450"; + + m_instExt.putIns (spv::OpExtInstImport, 2 + m_instExt.strLen(name)); + m_instExt.putWord(m_instExtGlsl450); + m_instExt.putStr (name); + } + + + uint32_t SpirvModule::getMemoryOperandWordCount( + const SpirvMemoryOperands& op) const { + const uint32_t result + = ((op.flags & spv::MemoryAccessAlignedMask) ? 1 : 0) + + ((op.flags & spv::MemoryAccessMakePointerAvailableMask) ? 1 : 0) + + ((op.flags & spv::MemoryAccessMakePointerVisibleMask) ? 1 : 0); + + return op.flags ? result + 1 : 0; + } + + + void SpirvModule::putMemoryOperands( + const SpirvMemoryOperands& op) { + if (op.flags) { + m_code.putWord(op.flags); + + if (op.flags & spv::MemoryAccessAlignedMask) + m_code.putWord(op.alignment); + + if (op.flags & spv::MemoryAccessMakePointerAvailableMask) + m_code.putWord(op.makeAvailable); + + if (op.flags & spv::MemoryAccessMakePointerVisibleMask) + m_code.putWord(op.makeVisible); + } + } + + + uint32_t SpirvModule::getImageOperandWordCount(const SpirvImageOperands& op) const { + // Each flag may add one or more operands + const uint32_t result + = ((op.flags & spv::ImageOperandsBiasMask) ? 1 : 0) + + ((op.flags & spv::ImageOperandsLodMask) ? 1 : 0) + + ((op.flags & spv::ImageOperandsConstOffsetMask) ? 1 : 0) + + ((op.flags & spv::ImageOperandsGradMask) ? 2 : 0) + + ((op.flags & spv::ImageOperandsOffsetMask) ? 1 : 0) + + ((op.flags & spv::ImageOperandsConstOffsetsMask) ? 1 : 0) + + ((op.flags & spv::ImageOperandsSampleMask) ? 1 : 0) + + ((op.flags & spv::ImageOperandsMinLodMask) ? 1 : 0) + + ((op.flags & spv::ImageOperandsMakeTexelAvailableMask) ? 1 : 0) + + ((op.flags & spv::ImageOperandsMakeTexelVisibleMask) ? 1 : 0); + + // Add a DWORD for the operand mask if it is non-zero + return op.flags ? result + 1 : 0; + } + + + void SpirvModule::putImageOperands(const SpirvImageOperands& op) { + if (op.flags) { + m_code.putWord(op.flags); + + if (op.flags & spv::ImageOperandsBiasMask) + m_code.putWord(op.sLodBias); + + if (op.flags & spv::ImageOperandsLodMask) + m_code.putWord(op.sLod); + + if (op.flags & spv::ImageOperandsGradMask) { + m_code.putWord(op.sGradX); + m_code.putWord(op.sGradY); + } + + if (op.flags & spv::ImageOperandsConstOffsetMask) + m_code.putWord(op.sConstOffset); + + if (op.flags & spv::ImageOperandsOffsetMask) + m_code.putWord(op.gOffset); + + if (op.flags & spv::ImageOperandsConstOffsetsMask) + m_code.putWord(op.gConstOffsets); + + if (op.flags & spv::ImageOperandsSampleMask) + m_code.putWord(op.sSampleId); + + if (op.flags & spv::ImageOperandsMinLodMask) + m_code.putWord(op.sMinLod); + + if (op.flags & spv::ImageOperandsMakeTexelAvailableMask) + m_code.putWord(op.makeAvailable); + + if (op.flags & spv::ImageOperandsMakeTexelVisibleMask) + m_code.putWord(op.makeVisible); + } + } + + + bool SpirvModule::isInterfaceVar( + spv::StorageClass sclass) const { + if (m_version < spvVersion(1, 4)) { + return sclass == spv::StorageClassInput + || sclass == spv::StorageClassOutput; + } else { + // All global variables need to be declared + return sclass != spv::StorageClassFunction; + } + } + + + void SpirvModule::classifyBlocks( + std::unordered_set& reachableBlocks, + std::unordered_set& mergeBlocks) { + std::unordered_multimap branches; + std::queue blockQueue; + + uint32_t blockId = 0; + + for (auto ins : m_code) { + switch (ins.opCode()) { + case spv::OpLabel: { + uint32_t id = ins.arg(1); + + if (!blockId) + branches.insert({ 0u, id }); + + blockId = id; + } break; + + case spv::OpFunction: { + blockId = 0u; + } break; + + case spv::OpBranch: { + branches.insert({ blockId, ins.arg(1) }); + } break; + + case spv::OpBranchConditional: { + branches.insert({ blockId, ins.arg(2) }); + branches.insert({ blockId, ins.arg(3) }); + } break; + + case spv::OpSwitch: { + branches.insert({ blockId, ins.arg(2) }); + + for (uint32_t i = 4; i < ins.length(); i += 2) + branches.insert({ blockId, ins.arg(i) }); + } break; + + case spv::OpSelectionMerge: { + mergeBlocks.insert(ins.arg(1)); + } break; + + case spv::OpLoopMerge: { + mergeBlocks.insert(ins.arg(1)); + + // It is possible for the continue block to be unreachable in + // practice, but we still need to emit it if we are not going + // to eliminate this loop. Since the current block dominates + // the loop, use it to keep the continue block intact. + branches.insert({ blockId, ins.arg(2) }); + } break; + + default:; + } + } + + blockQueue.push(0); + + while (!blockQueue.empty()) { + uint32_t id = blockQueue.front(); + + auto range = branches.equal_range(id); + + for (auto i = range.first; i != range.second; i++) { + if (reachableBlocks.insert(i->second).second) + blockQueue.push(i->second); + } + + blockQueue.pop(); + } + } + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/src/util/util_log.cpp b/app/src/main/cpp/thirdparty/dxbc/src/util/util_log.cpp new file mode 100644 index 000000000..e2dcfb803 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/util/util_log.cpp @@ -0,0 +1,11 @@ +#include "log/log_debug.h" + +namespace dxvk::debug { + + std::string methodName(const std::string& prettyName) { + size_t end = prettyName.find("("); + size_t begin = prettyName.substr(0, end).rfind(" ") + 1; + return prettyName.substr(begin,end - begin); + } + +} diff --git a/app/src/main/cpp/thirdparty/dxbc/src/util/util_string.cpp b/app/src/main/cpp/thirdparty/dxbc/src/util/util_string.cpp new file mode 100644 index 000000000..9bb4b6114 --- /dev/null +++ b/app/src/main/cpp/thirdparty/dxbc/src/util/util_string.cpp @@ -0,0 +1,234 @@ +#include "util_string.h" + +namespace dxvk::str { + + const uint8_t* decodeTypedChar( + const uint8_t* begin, + const uint8_t* end, + uint32_t& ch) { + uint32_t first = begin[0]; + + if (likely(first < 0x80)) { + // Basic ASCII character + ch = uint32_t(first); + return begin + 1; + } else if (unlikely(first < 0xC0)) { + // Character starts with a continuation byte, + // just skip until we find the next valid prefix + while ((begin < end) && (((*begin) & 0xC0) == 0x80)) + begin += 1; + + ch = uint32_t('?'); + return begin; + } else { + // The number of leading 1 bits in the first byte + // determines the length of this character + size_t length = bit::lzcnt((~first) << 24); + + if (unlikely(begin + length > end)) { + ch = uint32_t('?'); + return end; + } + + if (first < 0xE0) { + ch = ((uint32_t(begin[0]) & 0x1F) << 6) + | ((uint32_t(begin[1]) & 0x3F)); + } else if (first < 0xF0) { + ch = ((uint32_t(begin[0]) & 0x0F) << 12) + | ((uint32_t(begin[1]) & 0x3F) << 6) + | ((uint32_t(begin[2]) & 0x3F)); + } else if (first < 0xF8) { + ch = ((uint32_t(begin[0]) & 0x07) << 18) + | ((uint32_t(begin[1]) & 0x3F) << 12) + | ((uint32_t(begin[2]) & 0x3F) << 6) + | ((uint32_t(begin[3]) & 0x3F)); + } else { + // Invalid prefix + ch = uint32_t('?'); + } + + return begin + length; + } + } + + const uint16_t* decodeTypedChar( + const uint16_t* begin, + const uint16_t* end, + uint32_t& ch) { + uint32_t first = begin[0]; + + if (likely(first < 0xD800)) { + ch = first; + return begin + 1; + } else if (first < 0xDC00) { + if (unlikely(begin + 2 > end)) { + ch = uint32_t('?'); + return end; + } + + ch = 0x10000 + + ((uint32_t(begin[0]) & 0x3FF) << 10) + + ((uint32_t(begin[1]) & 0x3FF)); + return begin + 2; + } else if (unlikely(first < 0xE000)) { + // Stray low surrogate + ch = uint32_t('?'); + return begin + 1; + } else { + ch = first; + return begin + 1; + } + } + + + const uint32_t* decodeTypedChar( + const uint32_t* begin, + const uint32_t* end, + uint32_t& ch) { + ch = begin[0]; + return begin + 1; + } + + + size_t encodeTypedChar( + uint8_t* begin, + uint8_t* end, + uint32_t ch) { + if (likely(ch < 0x80)) { + if (begin) { + if (unlikely(begin + 1 > end)) + return 0; + + begin[0] = uint8_t(ch); + } + + return 1; + } else if (ch < 0x800) { + if (begin) { + if (unlikely(begin + 2 > end)) + return 0; + + begin[0] = uint8_t(0xC0 | (ch >> 6)); + begin[1] = uint8_t(0x80 | (ch & 0x3F)); + } + + return 2; + } else if (ch < 0x10000) { + if (begin) { + if (unlikely(begin + 3 > end)) + return 0; + + begin[0] = uint8_t(0xE0 | ((ch >> 12))); + begin[1] = uint8_t(0x80 | ((ch >> 6) & 0x3F)); + begin[2] = uint8_t(0x80 | ((ch >> 0) & 0x3F)); + } + + return 3; + } else if (ch < 0x200000) { + if (begin) { + if (unlikely(begin + 4 > end)) + return 0; + + begin[0] = uint8_t(0xF0 | ((ch >> 18))); + begin[1] = uint8_t(0x80 | ((ch >> 12) & 0x3F)); + begin[2] = uint8_t(0x80 | ((ch >> 6) & 0x3F)); + begin[3] = uint8_t(0x80 | ((ch >> 0) & 0x3F)); + } + + return 4; + } else { + // Invalid code point for UTF-8 + return 0; + } + } + + + size_t encodeTypedChar( + uint16_t* begin, + uint16_t* end, + uint32_t ch) { + if (likely(ch < 0xD800)) { + if (begin) { + if (unlikely(begin + 1 > end)) + return 0; + + begin[0] = ch; + } + + return 1; + } else if (ch < 0xE000) { + // Private use code points, + // we can't encode these + return 0; + } else if (ch < 0x10000) { + if (begin) { + if (unlikely(begin + 1 > end)) + return 0; + + begin[0] = ch; + } + + return 1; + } else if (ch < 0x110000) { + if (begin) { + if (unlikely(begin + 2 > end)) + return 0; + + ch -= 0x10000; + begin[0] = uint16_t(0xD800 + (ch >> 10)); + begin[1] = uint16_t(0xDC00 + (ch & 0x3FF)); + } + + return 2; + } else { + // Invalid code point + return 0; + } + } + + + size_t encodeTypedChar( + uint32_t* begin, + uint32_t* end, + uint32_t ch) { + if (begin) { + if (unlikely(begin + 1 > end)) + return 0; + + begin[0] = ch; + } + + return 1; + } + + + std::string fromws(const wchar_t* ws) { + size_t srcLen = length(ws); + size_t dstLen = transcodeString( + nullptr, 0, ws, srcLen); + + std::string result; + result.resize(dstLen); + + transcodeString(result.data(), + dstLen, ws, srcLen); + + return result; + } + + + std::wstring tows(const char* mbs) { + size_t srcLen = length(mbs); + size_t dstLen = transcodeString( + nullptr, 0, mbs, srcLen); + + std::wstring result; + result.resize(dstLen); + + transcodeString(result.data(), + dstLen, mbs, srcLen); + + return result; + } + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c index d67830b02..3033c71ae 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c @@ -1,4 +1,5 @@ #include "lsfg_dll.h" +#include "lsfg_dxbc.h" #include #include @@ -492,6 +493,12 @@ static LsfgVariant select_variant(const ResourceTable* table, bool prefer_fp16) return LSFG_VARIANT_NONE; } +static bool translate_base_shader(const ResourceTable* table, uint32_t id, uint32_t** out_words, + uint32_t* out_word_count) { + if (id >= MAX_RESOURCE_ID || table->data[id] == NULL) return false; + return lsfg_translate_dxbc(table->data[id], table->size[id], out_words, out_word_count); +} + static bool has_base_chain(const ResourceTable* table) { size_t count = 0; const uint32_t* ids = build_shader_ids(&count); @@ -501,11 +508,13 @@ static bool has_base_chain(const ResourceTable* table) { return true; } -static LsfgStatus classify_missing_variant(const ResourceTable* table) { - if (!has_base_chain(table)) return LSFG_MISSING_SHADERS; - LSFG_LOGW("Lossless.dll carries the DXBC shader chain but no precompiled SPIR-V variants; " - "a DXBC translator is required for this build"); - return LSFG_NO_SPIRV_VARIANTS; +static const char* variant_name(LsfgVariant variant) { + switch (variant) { + case LSFG_VARIANT_FP16: return "spirv-fp16"; + case LSFG_VARIANT_FP32: return "spirv-fp32"; + case LSFG_VARIANT_DXBC: return "dxbc-translated"; + default: return "none"; + } } static uint32_t variant_offset(LsfgVariant variant) { @@ -556,9 +565,7 @@ LsfgStatus lsfg_validate_dll(const char* dll_path) { } LsfgStatus status = parse_resources(&image, table); - if (status == LSFG_OK && select_variant(table, true) == LSFG_VARIANT_NONE) { - status = classify_missing_variant(table); - } + if (status == LSFG_OK && !has_base_chain(table)) status = LSFG_MISSING_SHADERS; free(table); pe_close(&image, fd, mapped_size); @@ -587,26 +594,30 @@ LsfgStatus lsfg_build_cache(const char* dll_path, const char* cache_path, bool p } const LsfgVariant variant = select_variant(table, prefer_fp16); - if (variant == LSFG_VARIANT_NONE) { - status = classify_missing_variant(table); + const bool translate = variant == LSFG_VARIANT_NONE; + if (translate && !has_base_chain(table)) { free(table); pe_close(&image, fd, mapped_size); - return status; + return LSFG_MISSING_SHADERS; } LsfgModuleSet set; memset(&set, 0, sizeof(set)); - set.variant = variant; + set.variant = translate ? LSFG_VARIANT_DXBC : variant; - const uint32_t offset = variant_offset(variant); + const uint32_t offset = translate ? 0u : variant_offset(variant); size_t id_count = 0; const uint32_t* ids = build_shader_ids(&id_count); for (size_t i = 0; i < id_count; i++) { const uint32_t resource_id = ids[i] + offset; uint32_t* words = NULL; uint32_t word_count = 0; - if (!adopt_spirv(table->data[resource_id], table->size[resource_id], &words, - &word_count)) { + const bool ok = translate + ? translate_base_shader(table, resource_id, &words, &word_count) + : adopt_spirv(table->data[resource_id], table->size[resource_id], &words, + &word_count); + if (!ok) { + LSFG_LOGE("Shader %u (%s) failed", ids[i], translate ? "dxbc" : "spirv"); status = LSFG_TRANSLATION_FAILED; break; } @@ -624,14 +635,14 @@ LsfgStatus lsfg_build_cache(const char* dll_path, const char* cache_path, bool p header.source_size = (uint64_t)image.size; header.source_hash = fnv1a64(image.data, image.size); header.module_count = set.count; - header.variant = (uint32_t)variant; + header.variant = (uint32_t)set.variant; if (!write_cache(cache_path, &header, &set)) status = LSFG_CACHE_UNUSABLE; } if (status == LSFG_OK) { - LSFG_LOGI("Cached %u LSFG shader modules (variant=%s)", set.count, - variant == LSFG_VARIANT_FP16 ? "fp16" : "fp32"); + LSFG_LOGI("Cached %u LSFG shader modules (source=%s)", set.count, + variant_name(set.variant)); } else { LSFG_LOGE("Shader cache build failed with status %d", (int)status); } diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h index 39dede14f..a062c89eb 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h @@ -15,16 +15,14 @@ typedef enum LsfgStatus { LSFG_NOT_PORTABLE_EXECUTABLE = 3, LSFG_MISSING_SHADERS = 4, LSFG_TRANSLATION_FAILED = 5, - LSFG_CACHE_UNUSABLE = 6, - LSFG_NO_SPIRV_VARIANTS = 7 + LSFG_CACHE_UNUSABLE = 6 } LsfgStatus; - - typedef enum LsfgVariant { LSFG_VARIANT_NONE = 0, LSFG_VARIANT_FP16 = 1, - LSFG_VARIANT_FP32 = 2 + LSFG_VARIANT_FP32 = 2, + LSFG_VARIANT_DXBC = 3 } LsfgVariant; #define LSFG_SHADER_MIPMAPS 255u diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dxbc.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dxbc.cpp new file mode 100644 index 000000000..9580a9b73 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dxbc.cpp @@ -0,0 +1,87 @@ +#include "lsfg_dxbc.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace { + +struct BindingOffsets { + uint32_t bindingIndex{}; + uint32_t bindingOffset{}; + uint32_t setIndex{}; + uint32_t setOffset{}; +}; + +} + +bool lsfg_translate_dxbc(const uint8_t* bytecode, uint32_t size, uint32_t** out_words, + uint32_t* out_word_count) { + if (!bytecode || size < 4 || !out_words || !out_word_count) return false; + + try { + dxvk::DxbcReader reader(reinterpret_cast(bytecode), size); + dxvk::DxbcModule module(reader); + const dxvk::DxbcModuleInfo info{}; + auto code = module.compile(info, "CS"); + + std::vector bindingOffsets; + std::vector varIds; + for (auto ins : code) { + if (ins.opCode() == spv::OpDecorate) { + if (ins.arg(2) == spv::DecorationBinding) { + const uint32_t varId = ins.arg(1); + bindingOffsets.resize(std::max(bindingOffsets.size(), + static_cast(varId + 1))); + bindingOffsets[varId].bindingIndex = ins.arg(3); + bindingOffsets[varId].bindingOffset = ins.offset() + 3; + varIds.push_back(varId); + } + + if (ins.arg(2) == spv::DecorationDescriptorSet) { + const uint32_t varId = ins.arg(1); + bindingOffsets.resize(std::max(bindingOffsets.size(), + static_cast(varId + 1))); + bindingOffsets[varId].setIndex = ins.arg(3); + bindingOffsets[varId].setOffset = ins.offset() + 3; + } + } + + if (ins.opCode() == spv::OpFunction) break; + } + + std::vector validBindings; + for (const auto varId : varIds) { + const auto slot = bindingOffsets[varId]; + if (slot.bindingOffset) validBindings.push_back(slot); + } + + for (size_t i = 0; i < validBindings.size(); i++) { + code.data()[validBindings.at(i).bindingOffset] = static_cast(i); + } + + const size_t byte_size = code.size(); + if (byte_size == 0 || byte_size % sizeof(uint32_t) != 0) return false; + + const size_t word_count = byte_size / sizeof(uint32_t); + auto* words = static_cast(std::malloc(byte_size)); + if (!words) return false; + std::memcpy(words, code.data(), byte_size); + + *out_words = words; + *out_word_count = static_cast(word_count); + return true; + } catch (...) { + return false; + } +} + +void lsfg_free_translated(uint32_t* words) { + std::free(words); +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dxbc.h b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dxbc.h new file mode 100644 index 000000000..7dbcbbde3 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dxbc.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool lsfg_translate_dxbc(const uint8_t* bytecode, uint32_t size, uint32_t** out_words, + uint32_t* out_word_count); + +void lsfg_free_translated(uint32_t* words); + +#ifdef __cplusplus +} +#endif diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_probe.c b/app/src/main/cpp/winlator/vk/lsfg/lsfg_probe.c index 9f11f2a5a..16a0534a2 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_probe.c +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_probe.c @@ -26,6 +26,7 @@ typedef struct ProbeApi { PFN_vkGetPhysicalDeviceProperties GetPhysicalDeviceProperties; PFN_vkGetPhysicalDeviceFormatProperties GetPhysicalDeviceFormatProperties; PFN_vkGetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceQueueFamilyProperties; + PFN_vkGetPhysicalDeviceFeatures2 GetPhysicalDeviceFeatures2; } ProbeApi; static const VkFormat kRequiredFormats[] = { @@ -58,10 +59,12 @@ static bool load_instance_api(ProbeApi* api, VkInstance instance) { api->GetPhysicalDeviceQueueFamilyProperties = (PFN_vkGetPhysicalDeviceQueueFamilyProperties)api->GetInstanceProcAddr( instance, "vkGetPhysicalDeviceQueueFamilyProperties"); + api->GetPhysicalDeviceFeatures2 = (PFN_vkGetPhysicalDeviceFeatures2)api->GetInstanceProcAddr( + instance, "vkGetPhysicalDeviceFeatures2"); return api->DestroyInstance && api->EnumeratePhysicalDevices && api->GetPhysicalDeviceProperties && api->GetPhysicalDeviceFormatProperties && - api->GetPhysicalDeviceQueueFamilyProperties; + api->GetPhysicalDeviceQueueFamilyProperties && api->GetPhysicalDeviceFeatures2; } static bool has_compute_queue(const ProbeApi* api, VkPhysicalDevice device) { @@ -95,6 +98,33 @@ static bool has_required_formats(const ProbeApi* api, VkPhysicalDevice device) { return true; } +static bool has_required_features(const ProbeApi* api, VkPhysicalDevice device) { + VkPhysicalDeviceVulkanMemoryModelFeatures memory_model; + memset(&memory_model, 0, sizeof(memory_model)); + memory_model.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES; + + VkPhysicalDeviceFeatures2 features; + memset(&features, 0, sizeof(features)); + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &memory_model; + + api->GetPhysicalDeviceFeatures2(device, &features); + + if (!memory_model.vulkanMemoryModel) { + PROBE_LOGW("vulkanMemoryModel unsupported; translated LSFG shaders require it"); + return false; + } + if (!features.features.shaderStorageImageWriteWithoutFormat) { + PROBE_LOGW("shaderStorageImageWriteWithoutFormat unsupported"); + return false; + } + if (!features.features.shaderStorageImageExtendedFormats) { + PROBE_LOGW("shaderStorageImageExtendedFormats unsupported"); + return false; + } + return true; +} + static bool probe_instance(ProbeApi* api, VkInstance instance) { uint32_t device_count = 0; if (api->EnumeratePhysicalDevices(instance, &device_count, NULL) != VK_SUCCESS || @@ -109,13 +139,23 @@ static bool probe_instance(ProbeApi* api, VkInstance instance) { } for (uint32_t i = 0; i < device_count; i++) { - if (!has_compute_queue(api, devices[i])) continue; - if (!has_required_formats(api, devices[i])) continue; - VkPhysicalDeviceProperties properties; memset(&properties, 0, sizeof(properties)); api->GetPhysicalDeviceProperties(devices[i], &properties); - PROBE_LOGI("Frame generation supported on %s", properties.deviceName); + + if (properties.apiVersion < VK_API_VERSION_1_3) { + PROBE_LOGW("%s reports Vulkan %u.%u; SPIR-V 1.6 modules need 1.3", + properties.deviceName, VK_API_VERSION_MAJOR(properties.apiVersion), + VK_API_VERSION_MINOR(properties.apiVersion)); + continue; + } + if (!has_compute_queue(api, devices[i])) continue; + if (!has_required_formats(api, devices[i])) continue; + if (!has_required_features(api, devices[i])) continue; + + PROBE_LOGI("Frame generation supported on %s (Vulkan %u.%u)", properties.deviceName, + VK_API_VERSION_MAJOR(properties.apiVersion), + VK_API_VERSION_MINOR(properties.apiVersion)); return true; } return false; @@ -139,7 +179,7 @@ bool lsfg_probe_support(JNIEnv* env, jobject context, const char* driver_name) { memset(&app_info, 0, sizeof(app_info)); app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.pApplicationName = "WinNative"; - app_info.apiVersion = VK_API_VERSION_1_1; + app_info.apiVersion = VK_API_VERSION_1_3; VkInstanceCreateInfo create_info; memset(&create_info, 0, sizeof(create_info)); @@ -148,7 +188,7 @@ bool lsfg_probe_support(JNIEnv* env, jobject context, const char* driver_name) { VkInstance instance = VK_NULL_HANDLE; if (api.CreateInstance(&create_info, NULL, &instance) != VK_SUCCESS) { - app_info.apiVersion = VK_API_VERSION_1_0; + app_info.apiVersion = VK_API_VERSION_1_1; if (api.CreateInstance(&create_info, NULL, &instance) != VK_SUCCESS) { PROBE_LOGW("vkCreateInstance failed during probe"); dlclose(library); diff --git a/app/src/main/runtime/display/lsfg/LosslessScaling.java b/app/src/main/runtime/display/lsfg/LosslessScaling.java index eaacfa998..23b65dd7c 100644 --- a/app/src/main/runtime/display/lsfg/LosslessScaling.java +++ b/app/src/main/runtime/display/lsfg/LosslessScaling.java @@ -25,11 +25,11 @@ public final class LosslessScaling { public static final int STATUS_MISSING_SHADERS = 4; public static final int STATUS_TRANSLATION_FAILED = 5; public static final int STATUS_CACHE_UNUSABLE = 6; - public static final int STATUS_NO_SPIRV_VARIANTS = 7; public static final int VARIANT_NONE = 0; public static final int VARIANT_FP16 = 1; public static final int VARIANT_FP32 = 2; + public static final int VARIANT_DXBC = 3; private static final String TAG = "LosslessScaling"; private static final String DLL_NAME = "Lossless.dll"; diff --git a/docs/lsfg-frame-generation.md b/docs/lsfg-frame-generation.md index 982423acc..674b123a4 100644 --- a/docs/lsfg-frame-generation.md +++ b/docs/lsfg-frame-generation.md @@ -14,8 +14,9 @@ the Wine boundary. | `eden-emu/eden` PR #4263 — *[vulkan, android] Initial implementation of LSFG-VK* | `469c9af` (base `dc95cd0`) | | WinNative | `acc130ee` | -The two clones live outside the repo at `~/Build/lsfg-research/`. Nothing from -either is vendored yet. +The two clones live outside the repo at `~/Build/lsfg-research/`. The only thing +vendored into WinNative is DXVK's `dxbc` subset (zlib licence), taken from +`lsfg-vk-android/thirdparty/dxbc`. ## 1. What LSFG actually is @@ -81,9 +82,38 @@ an opt-in FP16 toggle for Mali parts that lack `vulkanMemoryModel`. Eden's translator-free path assumes a Lossless build that carries the blobs; that assumption does not hold for anything currently downloadable. -So the port needs the resource walk **and** a DXBC→SPIR-V translator. The walk -still lands the SPIR-V path for free if a future build ships the variants — -`LSFG_NO_SPIRV_VARIANTS` marks exactly that case. +Eden's own UI strings confirm the shape of what it consumes: *"This GPU driver +does not support the Vulkan memory model, which the Lossless Scaling shaders +require."* `VulkanMemoryModel` is emitted by DXVK's translator and is absent +from the GLSL450 FP16 blobs — so eden is running DXVK-translated SPIR-V too, it +just gets it pre-translated when the DLL supplies it. + +**Implemented: both producers, one consumer.** Eden's design has a clean seam — +everything below `ShaderModules` (a map of shader id → SPIR-V words) is +source-agnostic. So the SPIR-V path is kept exactly as eden has it, and DXVK's +`dxbc` (zlib licence, 22.7k lines, vendored under `cpp/thirdparty/dxbc` from the +same subset `lsfg-vk` uses) fills the same map when the variants are absent. +`lsfg_dxbc.cpp` follows `lsfg-vk`'s `trans.cpp` exactly, including its +encounter-order binding renumber, which is what pairs with DXVK output; +eden's set/binding sort stays on the precompiled path where it belongs. + +Validation now matches eden's `ParseShaderSpans` semantics too: a DLL is valid +when the **base chain IDs** are present, with no requirement that the SPIR-V +variants exist. + +Measured end to end on an Adreno 750, translating the user's own 3.2.1 DLL: + +- **25/25 modules translated**, 352,889 SPIR-V words (1.38 MB), in ~40 ms. +- **25/25 pass `spirv-val --target-env vulkan1.3`**. +- Emitted modules are **SPIR-V 1.6**, `OpMemoryModel Logical Vulkan`, with + `OpCapability VulkanMemoryModel`, `StorageImageWriteWithoutFormat` and + `ImageQuery`; bindings renumber to a dense 0..n range as intended. + +That last point is a hard runtime requirement and the probe now enforces it: +Vulkan **1.3** (SPIR-V 1.6 will not load on a 1.1 device), plus +`vulkanMemoryModel`, `shaderStorageImageWriteWithoutFormat` and +`shaderStorageImageExtendedFormats`. A device failing any of them reports +unsupported up front instead of failing at `vkCreateShaderModule`. ## 2. How LSFG-Android bakes it in — and why WinNative must not copy it @@ -352,10 +382,9 @@ fallback. Extraction, SPIR-V adoption and caching happen once on the Android sid (cache keyed on file size + hash + variant, as eden does); the DLL is never loaded or executed, only parsed. -`LSFG_NO_SPIRV_VARIANTS` distinguishes "valid DLL, no precompiled SPIR-V" from a -genuinely broken one. It is the expected result for every build currently on -Steam, and it is the signal to take the DXBC path rather than an error to show -the user. +Which producer ran is recorded in the cache header and surfaced as the variant +(`spirv-fp16`, `spirv-fp32`, `dxbc-translated`), so the source is visible in +diagnostics without changing anything downstream. ### 5.7 Settings surface @@ -409,7 +438,7 @@ Choreographer coalescing in `requestRenderCoalesced`. | Phase | Work | Verifiable by | |---|---|---| | 1 | `Lossless.dll` PE resource walk, SPIR-V adoption, on-device cache, capability probe, container auto-detect + SAF fallback | 25 modules cached; status surfaces in settings | -| 1b | DXBC→SPIR-V translation for the base chain IDs (vendor DXVK's `dxbc`, as `lsfg-vk` does) | Translated module byte-compares against a known-good SPIR-V blob | +| 1b | DXBC→SPIR-V translation for the base chain IDs (vendor DXVK's `dxbc`, as `lsfg-vk` does) | **Done** — 25/25 translated and `spirv-val` clean on device | | 2 | Composite-target ring + swapchain blit, behind an off-by-default flag | Pixel-identical output, no measurable cost with the flag off | | 3 | Compute pipeline support + `mipmaps`/`alpha`/`beta`/`gamma`/`delta`/`generate` port | Flow-pyramid debug dump matches eden's on the same input pair | | 4 | Multi-present per composite; semaphore/fence rework; `VK_FRAMES_IN_FLIGHT` and swapchain depth; real-present-only back-pressure | 2× shows 2 presents per guest frame; no validation errors | From b420c6ac3c63c93cbbcde80fa63b14f9d17494a3 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sun, 23 Aug 2026 09:44:02 -0400 Subject: [PATCH 07/35] Port eden's LSFG foundation and prove the shaders build pipelines on device Phase 3a. The chain's building blocks are ported from eden and verified against real hardware before the passes that sit on top of them are written. Ported as C++ rather than hand-translated into C. vk_renderer.c stays C and will reach this through a C entry point, but eden's originals are C++ with RAII throughout, and rewriting 4700 lines of that into C by hand is how subtle lifetime and barrier bugs get introduced. The CMake target already compiles C++, so the faithful port is also the cheaper one. lsfg_common carries LsfgImage, LsfgResources, LsfgBarriers, LsfgDescriptorWriter and LsfgPass, plus the generation-slot and timestamp maths, following eden structure for structure. What changes is only what has to: eden's vk:: wrappers and MemoryAllocator become small move-only RAII types over raw handles with one allocation per image, which is what create_one_offscreen next door already does. lsfg_shaders turns the cached modules into VkShaderModules and refuses to report valid unless all 25 are present. Everything the rest of the port rests on is now checked on the tablet against the real Lossless.dll, not assumed: all 25 SPIR-V modules accepted by vkCreateShaderModule compute pipeline created for mipmaps, 10 descriptors compute pipeline created for generate, 9 descriptors storage images allocate in R8_UNORM and R16G16B16A16_SFLOAT sampler cache, uniform constants buffer and descriptor pool all build device creates with vulkanMemoryModel enabled The descriptor layouts were read out of the translated SPIR-V rather than guessed: mipmaps declares one uniform buffer, one sampler, one sampled image and seven storage images, which independently confirms the seven mip levels eden's LSFG_MIP_LEVELS asserts, and generate declares one uniform buffer, two samplers, five sampled images and one storage image. The GPL-3.0 and lsfg-vk SPDX headers are kept on every ported file, as the licence requires. They are the only comments in the new sources. --- app/src/main/cpp/CMakeLists.txt | 2 + .../main/cpp/winlator/vk/lsfg/lsfg_common.cpp | 564 ++++++++++++++++++ .../main/cpp/winlator/vk/lsfg/lsfg_common.hpp | 302 ++++++++++ .../cpp/winlator/vk/lsfg/lsfg_shaders.cpp | 72 +++ .../cpp/winlator/vk/lsfg/lsfg_shaders.hpp | 38 ++ 5 files changed, 978 insertions(+) create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.cpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.hpp diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index e6dc261ca..86fe2bb38 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -150,6 +150,8 @@ add_library(winlator SHARED winlator/vk/vk_renderer.c winlator/vk/lsfg/lsfg_dll.c winlator/vk/lsfg/lsfg_dxbc.cpp + winlator/vk/lsfg/lsfg_common.cpp + winlator/vk/lsfg/lsfg_shaders.cpp winlator/vk/lsfg/lsfg_probe.c winlator/vk/lsfg/lsfg_jni.c ) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp new file mode 100644 index 000000000..afcd020a9 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp @@ -0,0 +1,564 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "lsfg_common.hpp" +#include "lsfg_shaders.hpp" + +#include +#include + +namespace lsfg { + +namespace { + +constexpr uint32_t DESCRIPTORS_PER_TYPE = 4096; + +struct LsfgConstants { + std::array input_offset; + uint32_t first_iter; + uint32_t first_iter_s; + uint32_t advanced_color_kind; + uint32_t hdr_support; + float resolution_inv_scale; + float timestamp; + float ui_threshold; + std::array padding; +}; +static_assert(sizeof(LsfgConstants) == 48); + +VkImageMemoryBarrier MakeBarrier(const LsfgImage& image, VkAccessFlags src_access, + VkAccessFlags dst_access) { + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcAccessMask = src_access; + barrier.dstAccessMask = dst_access; + barrier.oldLayout = image.Layout(); + barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image.Handle(); + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.layerCount = 1; + return barrier; +} + +} + +Device::Device(VkDevice device_, VkPhysicalDevice physical_device_) + : device{device_}, physical_device{physical_device_} { + vkGetPhysicalDeviceMemoryProperties(physical_device, &memory_properties); +} + +uint32_t Device::FindMemoryType(uint32_t bits, VkMemoryPropertyFlags properties) const { + for (uint32_t i = 0; i < memory_properties.memoryTypeCount; i++) { + if ((bits & (1u << i)) == 0) continue; + if ((memory_properties.memoryTypes[i].propertyFlags & properties) == properties) return i; + } + return UINT32_MAX; +} + +Buffer::Buffer(const Device& device_, VkDeviceSize size) : device{device_.Handle()} { + VkBufferCreateInfo buffer_ci{}; + buffer_ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_ci.size = size; + buffer_ci.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + buffer_ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + if (vkCreateBuffer(device, &buffer_ci, nullptr, &buffer) != VK_SUCCESS) { + buffer = VK_NULL_HANDLE; + return; + } + + VkMemoryRequirements requirements; + vkGetBufferMemoryRequirements(device, buffer, &requirements); + + VkMemoryAllocateInfo allocate_info{}; + allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocate_info.allocationSize = requirements.size; + allocate_info.memoryTypeIndex = device_.FindMemoryType( + requirements.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + if (allocate_info.memoryTypeIndex == UINT32_MAX || + vkAllocateMemory(device, &allocate_info, nullptr, &memory) != VK_SUCCESS) { + Release(); + return; + } + + vkBindBufferMemory(device, buffer, memory, 0); + if (vkMapMemory(device, memory, 0, VK_WHOLE_SIZE, 0, &mapped) != VK_SUCCESS) { + mapped = nullptr; + Release(); + } +} + +Buffer::~Buffer() { + Release(); +} + +Buffer::Buffer(Buffer&& other) noexcept + : device{other.device}, buffer{other.buffer}, memory{other.memory}, mapped{other.mapped} { + other.device = VK_NULL_HANDLE; + other.buffer = VK_NULL_HANDLE; + other.memory = VK_NULL_HANDLE; + other.mapped = nullptr; +} + +Buffer& Buffer::operator=(Buffer&& other) noexcept { + if (this != &other) { + Release(); + device = other.device; + buffer = other.buffer; + memory = other.memory; + mapped = other.mapped; + other.device = VK_NULL_HANDLE; + other.buffer = VK_NULL_HANDLE; + other.memory = VK_NULL_HANDLE; + other.mapped = nullptr; + } + return *this; +} + +void Buffer::Upload(const void* data, size_t size) { + if (mapped) std::memcpy(mapped, data, size); +} + +void Buffer::Release() { + if (device == VK_NULL_HANDLE) return; + if (mapped) { + vkUnmapMemory(device, memory); + mapped = nullptr; + } + if (buffer) vkDestroyBuffer(device, buffer, nullptr); + if (memory) vkFreeMemory(device, memory, nullptr); + buffer = VK_NULL_HANDLE; + memory = VK_NULL_HANDLE; +} + +LsfgImage::LsfgImage(const Device& device_, VkExtent2D extent_, VkFormat format_) + : device{device_.Handle()}, + extent{std::max(1u, extent_.width), std::max(1u, extent_.height)}, format{format_} { + VkImageCreateInfo image_ci{}; + image_ci.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + image_ci.imageType = VK_IMAGE_TYPE_2D; + image_ci.format = format; + image_ci.extent = {extent.width, extent.height, 1}; + image_ci.mipLevels = 1; + image_ci.arrayLayers = 1; + image_ci.samples = VK_SAMPLE_COUNT_1_BIT; + image_ci.tiling = VK_IMAGE_TILING_OPTIMAL; + image_ci.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + image_ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + image_ci.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + if (vkCreateImage(device, &image_ci, nullptr, &image) != VK_SUCCESS) { + image = VK_NULL_HANDLE; + return; + } + + VkMemoryRequirements requirements; + vkGetImageMemoryRequirements(device, image, &requirements); + + VkMemoryAllocateInfo allocate_info{}; + allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocate_info.allocationSize = requirements.size; + allocate_info.memoryTypeIndex = + device_.FindMemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (allocate_info.memoryTypeIndex == UINT32_MAX || + vkAllocateMemory(device, &allocate_info, nullptr, &memory) != VK_SUCCESS) { + Release(); + return; + } + vkBindImageMemory(device, image, memory, 0); + + VkImageViewCreateInfo view_ci{}; + view_ci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + view_ci.image = image; + view_ci.viewType = VK_IMAGE_VIEW_TYPE_2D; + view_ci.format = format; + view_ci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + view_ci.subresourceRange.levelCount = 1; + view_ci.subresourceRange.layerCount = 1; + if (vkCreateImageView(device, &view_ci, nullptr, &view) != VK_SUCCESS) { + view = VK_NULL_HANDLE; + Release(); + } +} + +LsfgImage::~LsfgImage() { + Release(); +} + +LsfgImage::LsfgImage(LsfgImage&& other) noexcept + : device{other.device}, image{other.image}, view{other.view}, memory{other.memory}, + extent{other.extent}, format{other.format}, layout{other.layout} { + other.device = VK_NULL_HANDLE; + other.image = VK_NULL_HANDLE; + other.view = VK_NULL_HANDLE; + other.memory = VK_NULL_HANDLE; +} + +LsfgImage& LsfgImage::operator=(LsfgImage&& other) noexcept { + if (this != &other) { + Release(); + device = other.device; + image = other.image; + view = other.view; + memory = other.memory; + extent = other.extent; + format = other.format; + layout = other.layout; + other.device = VK_NULL_HANDLE; + other.image = VK_NULL_HANDLE; + other.view = VK_NULL_HANDLE; + other.memory = VK_NULL_HANDLE; + } + return *this; +} + +void LsfgImage::Release() { + if (device == VK_NULL_HANDLE) return; + if (view) vkDestroyImageView(device, view, nullptr); + if (image) vkDestroyImage(device, image, nullptr); + if (memory) vkFreeMemory(device, memory, nullptr); + view = VK_NULL_HANDLE; + image = VK_NULL_HANDLE; + memory = VK_NULL_HANDLE; +} + +LsfgResources::~LsfgResources() { + if (!device) return; + for (auto& [key, sampler] : samplers) { + vkDestroySampler(device->Handle(), sampler, nullptr); + } + samplers.clear(); +} + +VkDeviceSize LsfgResources::BufferSize() { + return sizeof(LsfgConstants); +} + +VkSampler LsfgResources::GetSampler(VkSamplerAddressMode address_mode, VkCompareOp compare_op, + bool white_border) { + const uint64_t key = static_cast(address_mode) | + (static_cast(compare_op) << 8) | + (static_cast(white_border) << 16); + + const auto it = samplers.find(key); + if (it != samplers.end()) return it->second; + + VkSampler sampler = CreateLsfgSampler(*device, address_mode, compare_op, white_border); + samplers.emplace(key, sampler); + return sampler; +} + +VkBuffer LsfgResources::GetBuffer(float timestamp, bool first_iter, bool first_iter_s) { + uint32_t timestamp_bits{}; + std::memcpy(×tamp_bits, ×tamp, sizeof(timestamp_bits)); + const uint64_t key = static_cast(timestamp_bits) | + (static_cast(first_iter) << 32) | + (static_cast(first_iter_s) << 33); + + const auto it = buffers.find(key); + if (it != buffers.end()) return it->second.Handle(); + + Buffer buffer{*device, sizeof(LsfgConstants)}; + if (!buffer.Valid()) return VK_NULL_HANDLE; + + LsfgConstants constants{}; + constants.first_iter = first_iter ? 1u : 0u; + constants.first_iter_s = first_iter_s ? 1u : 0u; + constants.resolution_inv_scale = 1.0f / flow_scale; + constants.timestamp = timestamp; + constants.ui_threshold = 0.5f; + buffer.Upload(&constants, sizeof(constants)); + + const auto [entry, inserted] = buffers.emplace(key, std::move(buffer)); + return entry->second.Handle(); +} + +LsfgBarriers& LsfgBarriers::Push(LsfgImage& image, VkAccessFlags src_access, + VkAccessFlags dst_access) { + barriers.push_back(MakeBarrier(image, src_access, dst_access)); + image.SetLayout(VK_IMAGE_LAYOUT_GENERAL); + return *this; +} + +LsfgBarriers& LsfgBarriers::WriteToRead(LsfgImage& image) { + return Push(image, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); +} + +LsfgBarriers& LsfgBarriers::ReadToWrite(LsfgImage& image) { + return Push(image, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT); +} + +LsfgBarriers& LsfgBarriers::WriteToRead(LsfgImage* image) { + return image == nullptr ? *this : WriteToRead(*image); +} + +LsfgBarriers& LsfgBarriers::ReadToWrite(LsfgImage* image) { + return image == nullptr ? *this : ReadToWrite(*image); +} + +LsfgBarriers& LsfgBarriers::DiscardToWrite(VkImage image) { + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.layerCount = 1; + barriers.push_back(barrier); + return *this; +} + +void LsfgBarriers::Build() { + if (barriers.empty()) return; + vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, + static_cast(barriers.size()), barriers.data()); + barriers.clear(); +} + +LsfgDescriptorWriter& LsfgDescriptorWriter::PushImage(VkDescriptorType type, VkSampler sampler, + VkImageView view) { + VkDescriptorImageInfo info{}; + info.sampler = sampler; + info.imageView = view; + info.imageLayout = + view == VK_NULL_HANDLE ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_GENERAL; + image_infos.push_back(info); + + VkWriteDescriptorSet write{}; + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = set; + write.dstBinding = binding++; + write.descriptorCount = 1; + write.descriptorType = type; + write.pImageInfo = &image_infos.back(); + writes.push_back(write); + return *this; +} + +LsfgDescriptorWriter& LsfgDescriptorWriter::AddSampler(VkSampler sampler) { + return PushImage(VK_DESCRIPTOR_TYPE_SAMPLER, sampler, VK_NULL_HANDLE); +} + +LsfgDescriptorWriter& LsfgDescriptorWriter::AddSampledImage(const LsfgImage& image) { + return PushImage(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_NULL_HANDLE, image.View()); +} + +LsfgDescriptorWriter& LsfgDescriptorWriter::AddSampledImage(const LsfgImage* image) { + return PushImage(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_NULL_HANDLE, + image == nullptr ? VK_NULL_HANDLE : image->View()); +} + +LsfgDescriptorWriter& LsfgDescriptorWriter::AddStorageImage(const LsfgImage& image) { + return PushImage(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_NULL_HANDLE, image.View()); +} + +LsfgDescriptorWriter& LsfgDescriptorWriter::AddStorageView(VkImageView view) { + return PushImage(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_NULL_HANDLE, view); +} + +LsfgDescriptorWriter& LsfgDescriptorWriter::AddUniformBuffer(VkBuffer buffer, VkDeviceSize size) { + VkDescriptorBufferInfo info{}; + info.buffer = buffer; + info.offset = 0; + info.range = size; + buffer_infos.push_back(info); + + VkWriteDescriptorSet write{}; + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = set; + write.dstBinding = binding++; + write.descriptorCount = 1; + write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + write.pBufferInfo = &buffer_infos.back(); + writes.push_back(write); + return *this; +} + +void LsfgDescriptorWriter::Build(const Device& device) { + if (writes.empty()) return; + vkUpdateDescriptorSets(device.Handle(), static_cast(writes.size()), writes.data(), 0, + nullptr); + writes.clear(); +} + +LsfgPass::LsfgPass(const Device& device_, const LsfgShaders& shaders, uint32_t shader_id, + LsfgBindings bindings) + : device{device_.Handle()} { + std::vector layout_bindings; + uint32_t index = 0; + for (const auto& [count, type] : bindings) { + for (uint32_t i = 0; i < count; i++) { + VkDescriptorSetLayoutBinding entry{}; + entry.binding = index++; + entry.descriptorType = type; + entry.descriptorCount = 1; + entry.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + layout_bindings.push_back(entry); + } + } + descriptor_count = index; + + VkDescriptorSetLayoutCreateInfo layout_ci{}; + layout_ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layout_ci.bindingCount = static_cast(layout_bindings.size()); + layout_ci.pBindings = layout_bindings.data(); + if (vkCreateDescriptorSetLayout(device, &layout_ci, nullptr, &descriptor_set_layout) != + VK_SUCCESS) { + Release(); + return; + } + + VkPipelineLayoutCreateInfo pipeline_layout_ci{}; + pipeline_layout_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipeline_layout_ci.setLayoutCount = 1; + pipeline_layout_ci.pSetLayouts = &descriptor_set_layout; + if (vkCreatePipelineLayout(device, &pipeline_layout_ci, nullptr, &pipeline_layout) != + VK_SUCCESS) { + Release(); + return; + } + + const VkShaderModule module = shaders.Get(shader_id); + if (module == VK_NULL_HANDLE) { + Release(); + return; + } + + VkPipelineShaderStageCreateInfo stage{}; + stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; + stage.module = module; + stage.pName = "main"; + + VkComputePipelineCreateInfo pipeline_ci{}; + pipeline_ci.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; + pipeline_ci.stage = stage; + pipeline_ci.layout = pipeline_layout; + if (vkCreateComputePipelines(device, VK_NULL_HANDLE, 1, &pipeline_ci, nullptr, &pipeline) != + VK_SUCCESS) { + pipeline = VK_NULL_HANDLE; + Release(); + } +} + +LsfgPass::~LsfgPass() { + Release(); +} + +LsfgPass::LsfgPass(LsfgPass&& other) noexcept + : device{other.device}, descriptor_set_layout{other.descriptor_set_layout}, + pipeline_layout{other.pipeline_layout}, pipeline{other.pipeline}, + descriptor_count{other.descriptor_count} { + other.device = VK_NULL_HANDLE; + other.descriptor_set_layout = VK_NULL_HANDLE; + other.pipeline_layout = VK_NULL_HANDLE; + other.pipeline = VK_NULL_HANDLE; +} + +LsfgPass& LsfgPass::operator=(LsfgPass&& other) noexcept { + if (this != &other) { + Release(); + device = other.device; + descriptor_set_layout = other.descriptor_set_layout; + pipeline_layout = other.pipeline_layout; + pipeline = other.pipeline; + descriptor_count = other.descriptor_count; + other.device = VK_NULL_HANDLE; + other.descriptor_set_layout = VK_NULL_HANDLE; + other.pipeline_layout = VK_NULL_HANDLE; + other.pipeline = VK_NULL_HANDLE; + } + return *this; +} + +void LsfgPass::Release() { + if (device == VK_NULL_HANDLE) return; + if (pipeline) vkDestroyPipeline(device, pipeline, nullptr); + if (pipeline_layout) vkDestroyPipelineLayout(device, pipeline_layout, nullptr); + if (descriptor_set_layout) vkDestroyDescriptorSetLayout(device, descriptor_set_layout, nullptr); + pipeline = VK_NULL_HANDLE; + pipeline_layout = VK_NULL_HANDLE; + descriptor_set_layout = VK_NULL_HANDLE; +} + +void LsfgPass::Bind(VkCommandBuffer cmdbuf, VkDescriptorSet set) const { + BindPipeline(cmdbuf); + BindSet(cmdbuf, set); +} + +void LsfgPass::BindPipeline(VkCommandBuffer cmdbuf) const { + vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); +} + +void LsfgPass::BindSet(VkCommandBuffer cmdbuf, VkDescriptorSet set) const { + vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0, 1, &set, 0, + nullptr); +} + +VkDescriptorPool CreateLsfgDescriptorPool(const Device& device, uint32_t max_sets) { + const VkDescriptorType types[] = { + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + VK_DESCRIPTOR_TYPE_SAMPLER, + VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + }; + + std::vector sizes; + for (const VkDescriptorType type : types) { + VkDescriptorPoolSize size{}; + size.type = type; + size.descriptorCount = DESCRIPTORS_PER_TYPE; + sizes.push_back(size); + } + + VkDescriptorPoolCreateInfo pool_ci{}; + pool_ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool_ci.maxSets = max_sets; + pool_ci.poolSizeCount = static_cast(sizes.size()); + pool_ci.pPoolSizes = sizes.data(); + + VkDescriptorPool pool = VK_NULL_HANDLE; + if (vkCreateDescriptorPool(device.Handle(), &pool_ci, nullptr, &pool) != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + return pool; +} + +VkSampler CreateLsfgSampler(const Device& device, VkSamplerAddressMode address_mode, + VkCompareOp compare_op, bool white_border) { + VkSamplerCreateInfo sampler_ci{}; + sampler_ci.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + sampler_ci.magFilter = VK_FILTER_LINEAR; + sampler_ci.minFilter = VK_FILTER_LINEAR; + sampler_ci.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + sampler_ci.addressModeU = address_mode; + sampler_ci.addressModeV = address_mode; + sampler_ci.addressModeW = address_mode; + sampler_ci.anisotropyEnable = VK_FALSE; + sampler_ci.compareEnable = VK_FALSE; + sampler_ci.compareOp = compare_op; + sampler_ci.maxLod = VK_LOD_CLAMP_NONE; + sampler_ci.borderColor = white_border ? VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE + : VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + sampler_ci.unnormalizedCoordinates = VK_FALSE; + + VkSampler sampler = VK_NULL_HANDLE; + if (vkCreateSampler(device.Handle(), &sampler_ci, nullptr, &sampler) != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + return sampler; +} + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp new file mode 100644 index 000000000..398c1a5ac --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp @@ -0,0 +1,302 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace lsfg { + +constexpr VkFormat LSFG_DEFAULT_FORMAT = VK_FORMAT_R8G8B8A8_UNORM; +constexpr VkFormat LSFG_FLOW_FORMAT = VK_FORMAT_R8_UNORM; +constexpr VkFormat LSFG_MOTION_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; + +constexpr size_t LSFG_HISTORY_SLOTS = 3; +constexpr size_t LSFG_MAX_TARGETS = 7; +constexpr size_t LSFG_MAX_GENERATIONS = 3; +constexpr size_t LSFG_MIP_LEVELS = 7; + +constexpr size_t LSFG_GENERATION_SLOTS = LSFG_MAX_GENERATIONS * (LSFG_MAX_GENERATIONS + 1) / 2; + +[[nodiscard]] constexpr size_t LsfgGenerationSlot(size_t generation_count, size_t generation) { + return (generation_count - 1) * generation_count / 2 + generation; +} + +[[nodiscard]] constexpr float LsfgTimestamp(size_t generation, size_t generation_count) { + return static_cast(generation + 1) / static_cast(generation_count + 1); +} + +[[nodiscard]] constexpr size_t LsfgSlotCount(size_t slot) { + size_t count = 1; + while (LsfgGenerationSlot(count + 1, 0) <= slot) { + ++count; + } + return count; +} + +[[nodiscard]] constexpr float LsfgSlotTimestamp(size_t slot) { + const size_t count = LsfgSlotCount(slot); + return LsfgTimestamp(slot - LsfgGenerationSlot(count, 0), count); +} + +class Device { +public: + Device() = default; + Device(VkDevice device_, VkPhysicalDevice physical_device_); + + [[nodiscard]] VkDevice Handle() const { + return device; + } + + [[nodiscard]] uint32_t FindMemoryType(uint32_t bits, VkMemoryPropertyFlags properties) const; + +private: + VkDevice device{VK_NULL_HANDLE}; + VkPhysicalDevice physical_device{VK_NULL_HANDLE}; + VkPhysicalDeviceMemoryProperties memory_properties{}; +}; + +class Buffer { +public: + Buffer() = default; + Buffer(const Device& device, VkDeviceSize size); + ~Buffer(); + + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + Buffer(Buffer&& other) noexcept; + Buffer& operator=(Buffer&& other) noexcept; + + [[nodiscard]] VkBuffer Handle() const { + return buffer; + } + + [[nodiscard]] bool Valid() const { + return buffer != VK_NULL_HANDLE; + } + + void Upload(const void* data, size_t size); + +private: + void Release(); + + VkDevice device{VK_NULL_HANDLE}; + VkBuffer buffer{VK_NULL_HANDLE}; + VkDeviceMemory memory{VK_NULL_HANDLE}; + void* mapped{nullptr}; +}; + +class LsfgImage { +public: + LsfgImage() = default; + LsfgImage(const Device& device, VkExtent2D extent, VkFormat format = LSFG_DEFAULT_FORMAT); + ~LsfgImage(); + + LsfgImage(const LsfgImage&) = delete; + LsfgImage& operator=(const LsfgImage&) = delete; + LsfgImage(LsfgImage&& other) noexcept; + LsfgImage& operator=(LsfgImage&& other) noexcept; + + [[nodiscard]] VkImage Handle() const { + return image; + } + + [[nodiscard]] VkImageView View() const { + return view; + } + + [[nodiscard]] VkExtent2D Extent() const { + return extent; + } + + [[nodiscard]] VkFormat Format() const { + return format; + } + + [[nodiscard]] VkImageLayout Layout() const { + return layout; + } + + [[nodiscard]] bool Valid() const { + return image != VK_NULL_HANDLE; + } + + void SetLayout(VkImageLayout new_layout) { + layout = new_layout; + } + +private: + void Release(); + + VkDevice device{VK_NULL_HANDLE}; + VkImage image{VK_NULL_HANDLE}; + VkImageView view{VK_NULL_HANDLE}; + VkDeviceMemory memory{VK_NULL_HANDLE}; + VkExtent2D extent{}; + VkFormat format{VK_FORMAT_UNDEFINED}; + VkImageLayout layout{VK_IMAGE_LAYOUT_UNDEFINED}; +}; + +using LsfgImagePair = std::array; +using LsfgImageHistory = std::array; + +class LsfgResources { +public: + LsfgResources() = default; + LsfgResources(const Device& device_, float flow_scale_) + : device{&device_}, flow_scale{flow_scale_} {} + + ~LsfgResources(); + + LsfgResources(const LsfgResources&) = delete; + LsfgResources& operator=(const LsfgResources&) = delete; + + [[nodiscard]] VkSampler GetSampler( + VkSamplerAddressMode address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, + VkCompareOp compare_op = VK_COMPARE_OP_NEVER, bool white_border = false); + + [[nodiscard]] VkBuffer GetBuffer(float timestamp = 0.0f, bool first_iter = false, + bool first_iter_s = false); + + [[nodiscard]] static VkDeviceSize BufferSize(); + +private: + const Device* device{}; + float flow_scale{1.0f}; + + std::map samplers; + std::map buffers; +}; + +class LsfgBarriers { +public: + explicit LsfgBarriers(VkCommandBuffer cmdbuf_) : cmdbuf{cmdbuf_} {} + + LsfgBarriers& WriteToRead(LsfgImage& image); + LsfgBarriers& ReadToWrite(LsfgImage& image); + LsfgBarriers& WriteToRead(LsfgImage* image); + LsfgBarriers& ReadToWrite(LsfgImage* image); + LsfgBarriers& DiscardToWrite(VkImage image); + + template + LsfgBarriers& WriteToReadAll(Range& images) { + for (auto& image : images) { + WriteToRead(image); + } + return *this; + } + + template + LsfgBarriers& ReadToWriteAll(Range& images) { + for (auto& image : images) { + ReadToWrite(image); + } + return *this; + } + + void Build(); + +private: + LsfgBarriers& Push(LsfgImage& image, VkAccessFlags src_access, VkAccessFlags dst_access); + + VkCommandBuffer cmdbuf; + std::vector barriers; +}; + +class LsfgDescriptorWriter { +public: + explicit LsfgDescriptorWriter(VkDescriptorSet set_) : set{set_} {} + + LsfgDescriptorWriter& AddSampler(VkSampler sampler); + LsfgDescriptorWriter& AddSampledImage(const LsfgImage& image); + LsfgDescriptorWriter& AddSampledImage(const LsfgImage* image); + LsfgDescriptorWriter& AddStorageImage(const LsfgImage& image); + LsfgDescriptorWriter& AddStorageView(VkImageView view); + LsfgDescriptorWriter& AddUniformBuffer(VkBuffer buffer, VkDeviceSize size); + + template + LsfgDescriptorWriter& AddSampledImages(const Range& images) { + for (const auto& image : images) { + AddSampledImage(image); + } + return *this; + } + + template + LsfgDescriptorWriter& AddStorageImages(const Range& images) { + for (const auto& image : images) { + AddStorageImage(image); + } + return *this; + } + + void Build(const Device& device); + +private: + LsfgDescriptorWriter& PushImage(VkDescriptorType type, VkSampler sampler, VkImageView view); + + VkDescriptorSet set; + uint32_t binding{}; + std::deque image_infos; + std::deque buffer_infos; + std::vector writes; +}; + +using LsfgBindings = std::initializer_list>; + +class LsfgShaders; + +class LsfgPass { +public: + LsfgPass() = default; + LsfgPass(const Device& device, const LsfgShaders& shaders, uint32_t shader_id, + LsfgBindings bindings); + ~LsfgPass(); + + LsfgPass(const LsfgPass&) = delete; + LsfgPass& operator=(const LsfgPass&) = delete; + LsfgPass(LsfgPass&& other) noexcept; + LsfgPass& operator=(LsfgPass&& other) noexcept; + + [[nodiscard]] VkDescriptorSetLayout SetLayout() const { + return descriptor_set_layout; + } + + [[nodiscard]] uint32_t DescriptorCount() const { + return descriptor_count; + } + + [[nodiscard]] bool Valid() const { + return pipeline != VK_NULL_HANDLE; + } + + void Bind(VkCommandBuffer cmdbuf, VkDescriptorSet set) const; + void BindPipeline(VkCommandBuffer cmdbuf) const; + void BindSet(VkCommandBuffer cmdbuf, VkDescriptorSet set) const; + +private: + void Release(); + + VkDevice device{VK_NULL_HANDLE}; + VkDescriptorSetLayout descriptor_set_layout{VK_NULL_HANDLE}; + VkPipelineLayout pipeline_layout{VK_NULL_HANDLE}; + VkPipeline pipeline{VK_NULL_HANDLE}; + uint32_t descriptor_count{}; +}; + +[[nodiscard]] VkDescriptorPool CreateLsfgDescriptorPool(const Device& device, uint32_t max_sets); + +[[nodiscard]] VkSampler CreateLsfgSampler(const Device& device, VkSamplerAddressMode address_mode, + VkCompareOp compare_op, bool white_border); + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.cpp new file mode 100644 index 000000000..88191259f --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.cpp @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "lsfg_shaders.hpp" +#include "lsfg_common.hpp" +#include "lsfg_dll.h" + +#include + +#define LOG_TAG "LsfgShaders" +#define SHADER_LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define SHADER_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) + +namespace lsfg { + +LsfgShaders::LsfgShaders(const Device& device_, const std::string& cache_path) + : device{device_.Handle()} { + LsfgModuleSet set{}; + const LsfgStatus status = lsfg_load_modules(cache_path.c_str(), &set); + if (status != LSFG_OK) { + SHADER_LOGE("Shader cache unusable (status %d)", static_cast(status)); + return; + } + + for (uint32_t i = 0; i < set.count; i++) { + const LsfgModule& module = set.modules[i]; + + VkShaderModuleCreateInfo module_ci{}; + module_ci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + module_ci.codeSize = static_cast(module.word_count) * sizeof(uint32_t); + module_ci.pCode = module.words; + + VkShaderModule handle = VK_NULL_HANDLE; + if (vkCreateShaderModule(device, &module_ci, nullptr, &handle) != VK_SUCCESS) { + SHADER_LOGE("vkCreateShaderModule failed for shader %u", module.id); + lsfg_release_modules(&set); + Release(); + return; + } + modules.emplace(module.id, handle); + } + + lsfg_release_modules(&set); + valid = modules.size() == LSFG_SHADER_COUNT; + if (valid) { + SHADER_LOGI("Created %zu LSFG shader modules", modules.size()); + } else { + SHADER_LOGE("Expected %u shader modules, got %zu", LSFG_SHADER_COUNT, modules.size()); + Release(); + } +} + +LsfgShaders::~LsfgShaders() { + Release(); +} + +void LsfgShaders::Release() { + if (device != VK_NULL_HANDLE) { + for (auto& [id, module] : modules) { + vkDestroyShaderModule(device, module, nullptr); + } + } + modules.clear(); + valid = false; +} + +VkShaderModule LsfgShaders::Get(uint32_t shader_id) const { + const auto it = modules.find(shader_id); + return it == modules.end() ? VK_NULL_HANDLE : it->second; +} + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.hpp new file mode 100644 index 000000000..a0a468172 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.hpp @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +namespace lsfg { + +class Device; + +class LsfgShaders { +public: + LsfgShaders() = default; + LsfgShaders(const Device& device, const std::string& cache_path); + ~LsfgShaders(); + + LsfgShaders(const LsfgShaders&) = delete; + LsfgShaders& operator=(const LsfgShaders&) = delete; + + [[nodiscard]] bool IsValid() const { + return valid; + } + + [[nodiscard]] VkShaderModule Get(uint32_t shader_id) const; + +private: + void Release(); + + VkDevice device{VK_NULL_HANDLE}; + std::map modules; + bool valid{}; +}; + +} From 994b9ac74e2aaddabab4949d373f8744ca2e546e Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sun, 23 Aug 2026 09:51:10 -0400 Subject: [PATCH 08/35] Run the first LSFG shaders on the GPU: mipmaps and generate Phase 3b. Two of the six chain passes are ported, and for the first time the Lossless shaders actually execute on the tablet rather than merely compiling. mipmaps builds the seven-level flow pyramid from the input frame pair and generate performs the final warp into a target view. Both follow eden pass for pass, including the descriptor layouts, the double-buffered sets indexed by frame parity, the barrier sequences, and the dispatch tile shifts, which differ between the two: 6 for mipmaps against the flow extent, 4 for generate against the output extent. generate keeps eden's full slot and target matrix, so all six generation slots by seven targets by two parities are allocated up front, 84 descriptor sets, with SetTarget rewriting only when the bound view actually changes. Verified on the Adreno 750 against the real Lossless.dll, recording both passes into one command buffer and submitting for execution: 25 shader modules created input pair allocated at 1920x1080 mipmaps built, pyramid from 1920x1080 down to 30x16 across seven levels generate built, 84 descriptor sets allocated command buffer recorded and submitted fence signalled, so the GPU ran both dispatches without device loss The pyramid dimensions are worth recording because they confirm the mip chain is being derived the way eden derives it rather than by accident: seven levels of halving from the flow extent lands exactly on 30x16. Descriptor set allocation moves into lsfg_common as a shared helper, since every remaining pass needs the same wrapped allocate. --- app/src/main/cpp/CMakeLists.txt | 2 + .../main/cpp/winlator/vk/lsfg/lsfg_common.cpp | 20 +++ .../main/cpp/winlator/vk/lsfg/lsfg_common.hpp | 3 + .../cpp/winlator/vk/lsfg/lsfg_generate.cpp | 124 ++++++++++++++++++ .../cpp/winlator/vk/lsfg/lsfg_generate.hpp | 56 ++++++++ .../cpp/winlator/vk/lsfg/lsfg_mipmaps.cpp | 80 +++++++++++ .../cpp/winlator/vk/lsfg/lsfg_mipmaps.hpp | 47 +++++++ 7 files changed, 332 insertions(+) create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.cpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.hpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_mipmaps.cpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_mipmaps.hpp diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 86fe2bb38..0a2a5cb35 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -152,6 +152,8 @@ add_library(winlator SHARED winlator/vk/lsfg/lsfg_dxbc.cpp winlator/vk/lsfg/lsfg_common.cpp winlator/vk/lsfg/lsfg_shaders.cpp + winlator/vk/lsfg/lsfg_mipmaps.cpp + winlator/vk/lsfg/lsfg_generate.cpp winlator/vk/lsfg/lsfg_probe.c winlator/vk/lsfg/lsfg_jni.c ) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp index afcd020a9..fe19f8283 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp @@ -536,6 +536,26 @@ VkDescriptorPool CreateLsfgDescriptorPool(const Device& device, uint32_t max_set return pool; } +std::vector AllocateLsfgDescriptorSets(const Device& device, + VkDescriptorPool pool, + VkDescriptorSetLayout layout, + uint32_t count) { + if (pool == VK_NULL_HANDLE || layout == VK_NULL_HANDLE || count == 0) return {}; + + const std::vector layouts(count, layout); + VkDescriptorSetAllocateInfo allocate_info{}; + allocate_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocate_info.descriptorPool = pool; + allocate_info.descriptorSetCount = count; + allocate_info.pSetLayouts = layouts.data(); + + std::vector sets(count, VK_NULL_HANDLE); + if (vkAllocateDescriptorSets(device.Handle(), &allocate_info, sets.data()) != VK_SUCCESS) { + return {}; + } + return sets; +} + VkSampler CreateLsfgSampler(const Device& device, VkSamplerAddressMode address_mode, VkCompareOp compare_op, bool white_border) { VkSamplerCreateInfo sampler_ci{}; diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp index 398c1a5ac..be3c145bc 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp @@ -296,6 +296,9 @@ class LsfgPass { [[nodiscard]] VkDescriptorPool CreateLsfgDescriptorPool(const Device& device, uint32_t max_sets); +[[nodiscard]] std::vector AllocateLsfgDescriptorSets( + const Device& device, VkDescriptorPool pool, VkDescriptorSetLayout layout, uint32_t count); + [[nodiscard]] VkSampler CreateLsfgSampler(const Device& device, VkSamplerAddressMode address_mode, VkCompareOp compare_op, bool white_border); diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.cpp new file mode 100644 index 000000000..254c04e96 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.cpp @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "lsfg_generate.hpp" +#include "lsfg_dll.h" +#include "lsfg_shaders.hpp" + +#include + +namespace lsfg { + +namespace { + +constexpr uint32_t DISPATCH_TILE_SHIFT = 4; + +[[nodiscard]] uint32_t GroupCount(uint32_t size) { + return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT; +} + +VkImageMemoryBarrier MakeTargetBarrier(VkImage image, VkAccessFlags src_access, + VkAccessFlags dst_access, VkImageLayout old_layout) { + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcAccessMask = src_access; + barrier.dstAccessMask = dst_access; + barrier.oldLayout = old_layout; + barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.layerCount = 1; + return barrier; +} + +} + +LsfgGenerate::LsfgGenerate(const Device& device, const LsfgShaders& shaders, + LsfgResources& resources, VkDescriptorPool descriptor_pool, + LsfgImagePair& frames_, LsfgImage& motion_, LsfgImage& detail1_, + LsfgImage& detail2_) + : frames{&frames_}, motion{&motion_}, detail1{&detail1_}, detail2{&detail2_} { + pass = LsfgPass(device, shaders, LSFG_SHADER_GENERATE, + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLER}, + {5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + if (!pass.Valid()) return; + + sampler = resources.GetSampler(); + edge_sampler = + resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false); + + const uint32_t total = + static_cast(LSFG_GENERATION_SLOTS * LSFG_MAX_TARGETS * 2); + const std::vector sets = + AllocateLsfgDescriptorSets(device, descriptor_pool, pass.SetLayout(), total); + if (sets.size() != total) return; + + size_t next = 0; + for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) { + Generation& target = generations[slot]; + target.buffer = resources.GetBuffer(LsfgSlotTimestamp(slot)); + + for (auto& entry : target.targets) { + for (auto& set : entry.descriptor_sets) { + set = sets[next++]; + } + } + } + allocated = true; +} + +void LsfgGenerate::SetTarget(const Device& device, size_t slot, uint32_t target, + VkImageView view) { + Target& entry = generations[slot].targets[target]; + if (entry.view == view) return; + entry.view = view; + + for (size_t i = 0; i < entry.descriptor_sets.size(); ++i) { + LsfgDescriptorWriter(entry.descriptor_sets[i]) + .AddUniformBuffer(generations[slot].buffer, LsfgResources::BufferSize()) + .AddSampler(sampler) + .AddSampler(edge_sampler) + .AddSampledImage((*frames)[1 - i]) + .AddSampledImage((*frames)[i]) + .AddSampledImage(*motion) + .AddSampledImage(*detail1) + .AddSampledImage(*detail2) + .AddStorageView(view) + .Build(device); + } +} + +void LsfgGenerate::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot, + uint32_t target, VkImage image, VkExtent2D extent) { + const Target& entry = generations[slot].targets[target]; + + LsfgBarriers(cmdbuf) + .WriteToReadAll(*frames) + .WriteToRead(*motion) + .WriteToRead(*detail1) + .WriteToRead(*detail2) + .DiscardToWrite(image) + .Build(); + + pass.Bind(cmdbuf, entry.descriptor_sets[frame_count % entry.descriptor_sets.size()]); + vkCmdDispatch(cmdbuf, GroupCount(extent.width), GroupCount(extent.height), 1); + + const VkImageMemoryBarrier after = MakeTargetBarrier( + image, VK_ACCESS_SHADER_WRITE_BIT, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT, + VK_IMAGE_LAYOUT_GENERAL); + vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, nullptr, 0, nullptr, 1, &after); +} + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.hpp new file mode 100644 index 000000000..b7fc6a8d7 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.hpp @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "lsfg_common.hpp" + +namespace lsfg { + +class LsfgShaders; + +class LsfgGenerate { +public: + LsfgGenerate() = default; + LsfgGenerate(const Device& device, const LsfgShaders& shaders, LsfgResources& resources, + VkDescriptorPool descriptor_pool, LsfgImagePair& frames, LsfgImage& motion, + LsfgImage& detail1, LsfgImage& detail2); + + void SetTarget(const Device& device, size_t slot, uint32_t target, VkImageView view); + + void Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot, uint32_t target, + VkImage image, VkExtent2D extent); + + [[nodiscard]] bool Valid() const { + return pass.Valid() && allocated; + } + +private: + struct Target { + std::array descriptor_sets{}; + VkImageView view{VK_NULL_HANDLE}; + }; + + struct Generation { + std::array targets{}; + VkBuffer buffer{VK_NULL_HANDLE}; + }; + + LsfgImagePair* frames{}; + LsfgImage* motion{}; + LsfgImage* detail1{}; + LsfgImage* detail2{}; + VkSampler sampler{VK_NULL_HANDLE}; + VkSampler edge_sampler{VK_NULL_HANDLE}; + + LsfgPass pass; + std::array generations{}; + bool allocated{}; +}; + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_mipmaps.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_mipmaps.cpp new file mode 100644 index 000000000..74d88850f --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_mipmaps.cpp @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "lsfg_mipmaps.hpp" +#include "lsfg_dll.h" +#include "lsfg_shaders.hpp" + +#include +#include + +namespace lsfg { + +namespace { + +constexpr uint32_t DISPATCH_TILE_SHIFT = 6; + +[[nodiscard]] uint32_t GroupCount(uint32_t size) { + return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT; +} + +} + +LsfgMipmaps::LsfgMipmaps(const Device& device, const LsfgShaders& shaders, + LsfgResources& resources, VkDescriptorPool descriptor_pool, + LsfgImagePair& frames_, float flow_scale) + : frames{&frames_} { + pass = LsfgPass(device, shaders, LSFG_SHADER_MIPMAPS, + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {LSFG_MIP_LEVELS, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + if (!pass.Valid()) return; + + const VkExtent2D input_extent = (*frames)[0].Extent(); + flow_extent = VkExtent2D{ + std::max(1u, static_cast(static_cast(input_extent.width) * flow_scale)), + std::max(1u, static_cast(static_cast(input_extent.height) * flow_scale)), + }; + + for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) { + const VkExtent2D level_extent{ + std::max(1u, flow_extent.width >> i), + std::max(1u, flow_extent.height >> i), + }; + out_images[i] = LsfgImage(device, level_extent, LSFG_FLOW_FORMAT); + if (!out_images[i].Valid()) return; + } + + const std::vector sets = AllocateLsfgDescriptorSets( + device, descriptor_pool, pass.SetLayout(), + static_cast(descriptor_sets.size())); + if (sets.size() != descriptor_sets.size()) return; + + const VkSampler sampler = resources.GetSampler(); + const VkBuffer buffer = resources.GetBuffer(); + + for (size_t i = 0; i < descriptor_sets.size(); ++i) { + descriptor_sets[i] = sets[i]; + LsfgDescriptorWriter(descriptor_sets[i]) + .AddUniformBuffer(buffer, LsfgResources::BufferSize()) + .AddSampler(sampler) + .AddSampledImage((*frames)[i]) + .AddStorageImages(out_images) + .Build(device); + } +} + +void LsfgMipmaps::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count) { + const size_t slot = frame_count % descriptor_sets.size(); + + LsfgBarriers(cmdbuf).WriteToRead((*frames)[slot]).ReadToWriteAll(out_images).Build(); + + pass.Bind(cmdbuf, descriptor_sets[slot]); + vkCmdDispatch(cmdbuf, GroupCount(flow_extent.width), GroupCount(flow_extent.height), 1); +} + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_mipmaps.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_mipmaps.hpp new file mode 100644 index 000000000..e863b7491 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_mipmaps.hpp @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "lsfg_common.hpp" + +namespace lsfg { + +class LsfgShaders; + +class LsfgMipmaps { +public: + LsfgMipmaps() = default; + LsfgMipmaps(const Device& device, const LsfgShaders& shaders, LsfgResources& resources, + VkDescriptorPool descriptor_pool, LsfgImagePair& frames, float flow_scale); + + void Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count); + + [[nodiscard]] LsfgImage& Output(size_t level) { + return out_images[level]; + } + + [[nodiscard]] VkExtent2D FlowExtent() const { + return flow_extent; + } + + [[nodiscard]] bool Valid() const { + return pass.Valid() && descriptor_sets[0] != VK_NULL_HANDLE; + } + +private: + LsfgImagePair* frames{}; + + LsfgPass pass; + std::array descriptor_sets{}; + + VkExtent2D flow_extent{}; + std::array out_images; +}; + +} From 446ed982605af316820a1ecfbe77d9601eed3c51 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sun, 23 Aug 2026 10:07:20 -0400 Subject: [PATCH 09/35] Complete the LSFG chain and prove it interpolates on the GPU Ports the four remaining passes from eden's PR 4263 onto the existing foundation: alpha (4 stages over 7 mip levels with 3-deep history), beta (5 stages, 6 flow outputs), gamma (5 stages per mip level) and delta (10 stages across 3 instances), then wires all six passes together in LsfgChain. Where eden binds VK_NULL_HANDLE for the first instance's absent history and relies on VK_EXT_robustness2 nullDescriptor, this binds a shared 1x1 dummy instead. Disassembling shader 280 shows the only load of binding 7 sits inside a block guarded by first_iter == 0, so the descriptor is never sampled when the history is absent and the two are equivalent - minus the extension requirement, which WinNative's device range cannot assume. Verified on the Adreno 750 against the real Lossless.dll: the full chain builds, records and executes all six generation slots over three frames, and for a known translation the output is the exact midpoint frame - mean absolute error 0.00 at k=shift/2 for shifts of 8, 12 and 20 px, against ~shift error versus either input. --- app/src/main/cpp/CMakeLists.txt | 5 + .../main/cpp/winlator/vk/lsfg/lsfg_alpha.cpp | 152 +++++++++ .../main/cpp/winlator/vk/lsfg/lsfg_alpha.hpp | 65 ++++ .../main/cpp/winlator/vk/lsfg/lsfg_beta.cpp | 150 +++++++++ .../main/cpp/winlator/vk/lsfg/lsfg_beta.hpp | 49 +++ .../main/cpp/winlator/vk/lsfg/lsfg_chain.cpp | 119 +++++++ .../main/cpp/winlator/vk/lsfg/lsfg_chain.hpp | 72 +++++ .../main/cpp/winlator/vk/lsfg/lsfg_common.cpp | 64 ++++ .../main/cpp/winlator/vk/lsfg/lsfg_common.hpp | 16 + .../main/cpp/winlator/vk/lsfg/lsfg_delta.cpp | 294 ++++++++++++++++++ .../main/cpp/winlator/vk/lsfg/lsfg_delta.hpp | 64 ++++ .../main/cpp/winlator/vk/lsfg/lsfg_gamma.cpp | 192 ++++++++++++ .../main/cpp/winlator/vk/lsfg/lsfg_gamma.hpp | 56 ++++ 13 files changed, 1298 insertions(+) create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_alpha.cpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_alpha.hpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_beta.cpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_beta.hpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.hpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.cpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.hpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.cpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.hpp diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 0a2a5cb35..a9cb0abd9 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -154,6 +154,11 @@ add_library(winlator SHARED winlator/vk/lsfg/lsfg_shaders.cpp winlator/vk/lsfg/lsfg_mipmaps.cpp winlator/vk/lsfg/lsfg_generate.cpp + winlator/vk/lsfg/lsfg_alpha.cpp + winlator/vk/lsfg/lsfg_beta.cpp + winlator/vk/lsfg/lsfg_gamma.cpp + winlator/vk/lsfg/lsfg_delta.cpp + winlator/vk/lsfg/lsfg_chain.cpp winlator/vk/lsfg/lsfg_probe.c winlator/vk/lsfg/lsfg_jni.c ) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_alpha.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_alpha.cpp new file mode 100644 index 000000000..d066b7f9e --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_alpha.cpp @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "lsfg_alpha.hpp" +#include "lsfg_shaders.hpp" + +#include + +namespace lsfg { + +namespace { + +constexpr uint32_t DISPATCH_TILE_SHIFT = 3; + +[[nodiscard]] uint32_t GroupCount(uint32_t size) { + return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT; +} + +[[nodiscard]] VkExtent2D HalveExtent(VkExtent2D extent) { + return VkExtent2D{ + (extent.width + 1) >> 1, + (extent.height + 1) >> 1, + }; +} + +} + +LsfgAlphaPasses::LsfgAlphaPasses(const Device& device, const LsfgShaders& shaders) { + passes[0] = LsfgPass(device, shaders, LSFG_ALPHA_SHADERS[0], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[1] = LsfgPass(device, shaders, LSFG_ALPHA_SHADERS[1], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[2] = LsfgPass(device, shaders, LSFG_ALPHA_SHADERS[2], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[3] = LsfgPass(device, shaders, LSFG_ALPHA_SHADERS[3], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); +} + +bool LsfgAlphaPasses::Valid() const { + for (const auto& pass : passes) { + if (!pass.Valid()) return false; + } + return true; +} + +LsfgAlpha::LsfgAlpha(const Device& device, const LsfgAlphaPasses& passes_, + LsfgResources& resources, VkDescriptorPool descriptor_pool, LsfgImage& input_) + : passes{&passes_}, input{&input_} { + if (!passes->Valid()) return; + + const VkExtent2D half_extent = HalveExtent(input->Extent()); + const VkExtent2D quarter_extent = HalveExtent(half_extent); + + temp1 = LsfgImage(device, half_extent); + temp2 = LsfgImage(device, half_extent); + if (!temp1.Valid() || !temp2.Valid()) return; + + for (size_t i = 0; i < temp3.size(); ++i) { + temp3[i] = LsfgImage(device, quarter_extent); + if (!temp3[i].Valid()) return; + + for (size_t j = 0; j < LSFG_HISTORY_SLOTS; ++j) { + out_images[j][i] = LsfgImage(device, quarter_extent); + if (!out_images[j][i].Valid()) return; + } + } + + std::vector layouts; + for (size_t i = 0; i < LSFG_ALPHA_STAGES - 1; ++i) { + layouts.push_back(passes->Get(i).SetLayout()); + } + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + layouts.push_back(passes->Get(3).SetLayout()); + } + + const std::vector sets = + AllocateLsfgDescriptorSets(device, descriptor_pool, layouts); + if (sets.size() != layouts.size()) return; + + for (size_t i = 0; i < LSFG_ALPHA_STAGES - 1; ++i) { + descriptor_sets[i] = sets[i]; + } + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + last_descriptor_sets[i] = sets[LSFG_ALPHA_STAGES - 1 + i]; + } + + const VkSampler sampler = resources.GetSampler(); + + LsfgDescriptorWriter(descriptor_sets[0]) + .AddSampler(sampler) + .AddSampledImage(*input) + .AddStorageImage(temp1) + .Build(device); + LsfgDescriptorWriter(descriptor_sets[1]) + .AddSampler(sampler) + .AddSampledImage(temp1) + .AddStorageImage(temp2) + .Build(device); + LsfgDescriptorWriter(descriptor_sets[2]) + .AddSampler(sampler) + .AddSampledImage(temp2) + .AddStorageImages(temp3) + .Build(device); + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + LsfgDescriptorWriter(last_descriptor_sets[i]) + .AddSampler(sampler) + .AddSampledImages(temp3) + .AddStorageImages(out_images[i]) + .Build(device); + } + allocated = true; +} + +void LsfgAlpha::PushBarriers(LsfgBarriers& barriers, uint64_t frame_count, size_t stage) { + switch (stage) { + case 0: + barriers.WriteToRead(*input).ReadToWrite(temp1); + break; + case 1: + barriers.WriteToRead(temp1).ReadToWrite(temp2); + break; + case 2: + barriers.WriteToRead(temp2).ReadToWriteAll(temp3); + break; + default: + barriers.WriteToReadAll(temp3).ReadToWriteAll(out_images[frame_count % LSFG_HISTORY_SLOTS]); + break; + } +} + +void LsfgAlpha::DispatchStage(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t stage) { + const VkExtent2D extent = stage < 2 ? temp1.Extent() : temp3[0].Extent(); + const VkDescriptorSet set = stage < LSFG_ALPHA_STAGES - 1 + ? descriptor_sets[stage] + : last_descriptor_sets[frame_count % LSFG_HISTORY_SLOTS]; + + passes->Get(stage).BindSet(cmdbuf, set); + vkCmdDispatch(cmdbuf, GroupCount(extent.width), GroupCount(extent.height), 1); +} + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_alpha.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_alpha.hpp new file mode 100644 index 000000000..55db1c754 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_alpha.hpp @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "lsfg_common.hpp" + +namespace lsfg { + +class LsfgShaders; + +constexpr size_t LSFG_ALPHA_STAGES = 4; + +class LsfgAlphaPasses { +public: + LsfgAlphaPasses() = default; + LsfgAlphaPasses(const Device& device, const LsfgShaders& shaders); + + [[nodiscard]] const LsfgPass& Get(size_t stage) const { + return passes[stage]; + } + + [[nodiscard]] bool Valid() const; + +private: + std::array passes; +}; + +class LsfgAlpha { +public: + LsfgAlpha() = default; + LsfgAlpha(const Device& device, const LsfgAlphaPasses& passes, LsfgResources& resources, + VkDescriptorPool descriptor_pool, LsfgImage& input); + + void PushBarriers(LsfgBarriers& barriers, uint64_t frame_count, size_t stage); + void DispatchStage(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t stage); + + [[nodiscard]] LsfgImageHistory& Outputs() { + return out_images; + } + + [[nodiscard]] bool Valid() const { + return allocated; + } + +private: + const LsfgAlphaPasses* passes{}; + LsfgImage* input{}; + + std::array descriptor_sets{}; + std::array last_descriptor_sets{}; + + LsfgImage temp1; + LsfgImage temp2; + LsfgImagePair temp3; + LsfgImageHistory out_images; + bool allocated{}; +}; + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_beta.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_beta.cpp new file mode 100644 index 000000000..dd933fc90 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_beta.cpp @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "lsfg_beta.hpp" +#include "lsfg_shaders.hpp" + +#include + +namespace lsfg { + +namespace { + +constexpr uint32_t DISPATCH_TILE_SHIFT = 3; +constexpr uint32_t OUTPUT_TILE_SHIFT = 5; + +[[nodiscard]] uint32_t GroupCount(uint32_t size, uint32_t shift) { + return (size + (1u << shift) - 1) >> shift; +} + +} + +LsfgBeta::LsfgBeta(const Device& device, const LsfgShaders& shaders, LsfgResources& resources, + VkDescriptorPool descriptor_pool, LsfgImageHistory& inputs_) + : inputs{&inputs_} { + passes[0] = LsfgPass(device, shaders, LSFG_BETA_SHADERS[0], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + for (size_t i = 1; i < LSFG_BETA_STAGES - 1; ++i) { + passes[i] = LsfgPass(device, shaders, LSFG_BETA_SHADERS[i], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + } + passes[4] = LsfgPass(device, shaders, LSFG_BETA_SHADERS[4], + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + for (const auto& pass : passes) { + if (!pass.Valid()) return; + } + + const VkExtent2D extent = (*inputs)[0][0].Extent(); + for (size_t i = 0; i < temp1.size(); ++i) { + temp1[i] = LsfgImage(device, extent); + temp2[i] = LsfgImage(device, extent); + if (!temp1[i].Valid() || !temp2[i].Valid()) return; + } + for (size_t i = 0; i < LSFG_BETA_OUTPUTS; ++i) { + const VkExtent2D level_extent{ + extent.width >> i, + extent.height >> i, + }; + out_images[i] = LsfgImage(device, level_extent, LSFG_FLOW_FORMAT); + if (!out_images[i].Valid()) return; + } + + std::vector layouts; + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + layouts.push_back(passes[0].SetLayout()); + } + for (size_t i = 1; i < LSFG_BETA_STAGES; ++i) { + layouts.push_back(passes[i].SetLayout()); + } + + const std::vector sets = + AllocateLsfgDescriptorSets(device, descriptor_pool, layouts); + if (sets.size() != layouts.size()) return; + + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + first_descriptor_sets[i] = sets[i]; + } + for (size_t i = 0; i < LSFG_BETA_STAGES - 1; ++i) { + descriptor_sets[i] = sets[LSFG_HISTORY_SLOTS + i]; + } + + const VkSampler sampler = resources.GetSampler(); + const VkSampler border_sampler = + resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + LsfgDescriptorWriter(first_descriptor_sets[i]) + .AddSampler(border_sampler) + .AddSampledImages((*inputs)[(i + 1) % LSFG_HISTORY_SLOTS]) + .AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS]) + .AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS]) + .AddStorageImages(temp1) + .Build(device); + } + LsfgDescriptorWriter(descriptor_sets[0]) + .AddSampler(sampler) + .AddSampledImages(temp1) + .AddStorageImages(temp2) + .Build(device); + LsfgDescriptorWriter(descriptor_sets[1]) + .AddSampler(sampler) + .AddSampledImages(temp2) + .AddStorageImages(temp1) + .Build(device); + LsfgDescriptorWriter(descriptor_sets[2]) + .AddSampler(sampler) + .AddSampledImages(temp1) + .AddStorageImages(temp2) + .Build(device); + LsfgDescriptorWriter(descriptor_sets[3]) + .AddUniformBuffer(resources.GetBuffer(0.5f), LsfgResources::BufferSize()) + .AddSampler(sampler) + .AddSampledImages(temp2) + .AddStorageImages(out_images) + .Build(device); + allocated = true; +} + +void LsfgBeta::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count) { + const VkExtent2D extent = temp1[0].Extent(); + const uint32_t groups_x = GroupCount(extent.width, DISPATCH_TILE_SHIFT); + const uint32_t groups_y = GroupCount(extent.height, DISPATCH_TILE_SHIFT); + + LsfgBarriers barriers(cmdbuf); + for (auto& slot : *inputs) { + barriers.WriteToReadAll(slot); + } + barriers.ReadToWriteAll(temp1).Build(); + + passes[0].Bind(cmdbuf, first_descriptor_sets[frame_count % LSFG_HISTORY_SLOTS]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); + passes[1].Bind(cmdbuf, descriptor_sets[0]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(temp1).Build(); + passes[2].Bind(cmdbuf, descriptor_sets[1]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); + passes[3].Bind(cmdbuf, descriptor_sets[2]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(out_images).Build(); + passes[4].Bind(cmdbuf, descriptor_sets[3]); + vkCmdDispatch(cmdbuf, GroupCount(extent.width, OUTPUT_TILE_SHIFT), + GroupCount(extent.height, OUTPUT_TILE_SHIFT), 1); +} + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_beta.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_beta.hpp new file mode 100644 index 000000000..a09efcb3b --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_beta.hpp @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "lsfg_common.hpp" + +namespace lsfg { + +class LsfgShaders; + +constexpr size_t LSFG_BETA_STAGES = 5; +constexpr size_t LSFG_BETA_OUTPUTS = 6; + +class LsfgBeta { +public: + LsfgBeta() = default; + LsfgBeta(const Device& device, const LsfgShaders& shaders, LsfgResources& resources, + VkDescriptorPool descriptor_pool, LsfgImageHistory& inputs); + + void Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count); + + [[nodiscard]] LsfgImage& Output(size_t level) { + return out_images[level]; + } + + [[nodiscard]] bool Valid() const { + return allocated; + } + +private: + LsfgImageHistory* inputs{}; + + std::array passes; + std::array first_descriptor_sets{}; + std::array descriptor_sets{}; + + LsfgImagePair temp1; + LsfgImagePair temp2; + std::array out_images; + bool allocated{}; +}; + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp new file mode 100644 index 000000000..f16912f4a --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "lsfg_chain.hpp" +#include "lsfg_shaders.hpp" + +#include + +namespace lsfg { + +namespace { + +constexpr uint32_t FIXED_DESCRIPTOR_SETS = 64; +constexpr uint32_t DESCRIPTOR_SETS_PER_SLOT = 112; +constexpr size_t FIRST_DELTA_LEVEL = 4; + +} + +LsfgChain::LsfgChain(const Device& device, const LsfgShaders& shaders, VkExtent2D extent, + VkFormat format, float flow_scale) + : resources{device, flow_scale}, owner{device.Handle()} { + descriptor_pool = CreateLsfgDescriptorPool( + device, FIXED_DESCRIPTOR_SETS + + DESCRIPTOR_SETS_PER_SLOT * static_cast(LSFG_GENERATION_SLOTS)); + if (descriptor_pool == VK_NULL_HANDLE) return; + + for (auto& image : frames) { + image = LsfgImage(device, extent, format); + if (!image.Valid()) return; + } + + mipmaps = LsfgMipmaps(device, shaders, resources, descriptor_pool, frames, flow_scale); + if (!mipmaps.Valid()) return; + + alpha_passes = LsfgAlphaPasses(device, shaders); + if (!alpha_passes.Valid()) return; + + for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) { + alpha[i] = LsfgAlpha(device, alpha_passes, resources, descriptor_pool, mipmaps.Output(i)); + if (!alpha[i].Valid()) return; + } + + beta = LsfgBeta(device, shaders, resources, descriptor_pool, alpha[0].Outputs()); + if (!beta.Valid()) return; + + for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) { + const size_t level = LSFG_MIP_LEVELS - 1 - i; + gamma[i] = LsfgGamma(device, shaders, resources, descriptor_pool, alpha[level].Outputs(), + beta.Output(std::min(level, LSFG_BETA_OUTPUTS - 1)), + i == 0 ? nullptr : &gamma[i - 1].Output()); + if (!gamma[i].Valid()) return; + + if (i < FIRST_DELTA_LEVEL) { + continue; + } + + const size_t index = i - FIRST_DELTA_LEVEL; + delta[index] = LsfgDelta(device, shaders, resources, descriptor_pool, + alpha[level].Outputs(), beta.Output(level), + i == FIRST_DELTA_LEVEL ? nullptr : &gamma[i - 1].Output(), + i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output1(), + i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output2()); + if (!delta[index].Valid()) return; + } + + generate = LsfgGenerate(device, shaders, resources, descriptor_pool, frames, + gamma[LSFG_MIP_LEVELS - 1].Output(), + delta[LSFG_DELTA_INSTANCES - 1].Output1(), + delta[LSFG_DELTA_INSTANCES - 1].Output2()); + if (!generate.Valid()) return; + + valid = true; +} + +LsfgChain::~LsfgChain() { + if (descriptor_pool != VK_NULL_HANDLE) { + vkDestroyDescriptorPool(owner, descriptor_pool, nullptr); + descriptor_pool = VK_NULL_HANDLE; + } +} + +void LsfgChain::DispatchShared(VkCommandBuffer cmdbuf, uint64_t frame_count) { + resources.PrepareDummies(cmdbuf); + + mipmaps.Dispatch(cmdbuf, frame_count); + + for (size_t stage = 0; stage < LSFG_ALPHA_STAGES; ++stage) { + LsfgBarriers barriers(cmdbuf); + for (auto& level : alpha) { + level.PushBarriers(barriers, frame_count, stage); + } + barriers.Build(); + + alpha_passes.Get(stage).BindPipeline(cmdbuf); + for (auto& level : alpha) { + level.DispatchStage(cmdbuf, frame_count, stage); + } + } + + beta.Dispatch(cmdbuf, frame_count); +} + +void LsfgChain::DispatchGeneration(VkCommandBuffer cmdbuf, uint64_t frame_count, + size_t generation_count, size_t generation, uint32_t target, + VkImage image, VkExtent2D extent) { + const size_t slot = LsfgGenerationSlot(generation_count, generation); + for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) { + gamma[i].Dispatch(cmdbuf, frame_count, slot); + if (i >= FIRST_DELTA_LEVEL) { + delta[i - FIRST_DELTA_LEVEL].Dispatch(cmdbuf, frame_count, slot); + } + } + generate.Dispatch(cmdbuf, frame_count, slot, target, image, extent); +} + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.hpp new file mode 100644 index 000000000..16c3724b1 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.hpp @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "lsfg_alpha.hpp" +#include "lsfg_beta.hpp" +#include "lsfg_common.hpp" +#include "lsfg_delta.hpp" +#include "lsfg_gamma.hpp" +#include "lsfg_generate.hpp" +#include "lsfg_mipmaps.hpp" + +namespace lsfg { + +class LsfgShaders; + +constexpr size_t LSFG_DELTA_INSTANCES = 3; + +class LsfgChain { +public: + LsfgChain(const Device& device, const LsfgShaders& shaders, VkExtent2D extent, VkFormat format, + float flow_scale); + ~LsfgChain(); + + LsfgChain(const LsfgChain&) = delete; + LsfgChain& operator=(const LsfgChain&) = delete; + + void DispatchShared(VkCommandBuffer cmdbuf, uint64_t frame_count); + + void DispatchGeneration(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t generation_count, + size_t generation, uint32_t target, VkImage image, VkExtent2D extent); + + void SetTarget(const Device& device, size_t generation_count, size_t generation, + uint32_t target, VkImageView view) { + generate.SetTarget(device, LsfgGenerationSlot(generation_count, generation), target, view); + } + + [[nodiscard]] LsfgImage& Input(uint64_t frame_count) { + return frames[frame_count % frames.size()]; + } + + [[nodiscard]] LsfgImage& FlowLevel(size_t level) { + return mipmaps.Output(level); + } + + [[nodiscard]] bool Valid() const { + return valid; + } + +private: + LsfgResources resources; + VkDevice owner{VK_NULL_HANDLE}; + VkDescriptorPool descriptor_pool{VK_NULL_HANDLE}; + + LsfgImagePair frames; + LsfgMipmaps mipmaps; + LsfgAlphaPasses alpha_passes; + std::array alpha; + LsfgBeta beta; + std::array gamma; + std::array delta; + LsfgGenerate generate; + bool valid{}; +}; + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp index fe19f8283..dc3e71782 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp @@ -279,6 +279,49 @@ VkBuffer LsfgResources::GetBuffer(float timestamp, bool first_iter, bool first_i return entry->second.Handle(); } +const LsfgImage& LsfgResources::GetDummy(VkFormat format) { + const uint32_t key = static_cast(format); + + const auto it = dummies.find(key); + if (it != dummies.end()) return it->second; + + LsfgImage image{*device, VkExtent2D{1, 1}, format}; + const auto [entry, inserted] = dummies.emplace(key, std::move(image)); + dummies_ready = false; + return entry->second; +} + +void LsfgResources::PrepareDummies(VkCommandBuffer cmdbuf) { + if (dummies_ready || dummies.empty()) return; + + std::vector barriers; + for (auto& [key, image] : dummies) { + if (!image.Valid()) continue; + + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image.Handle(); + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.layerCount = 1; + barriers.push_back(barrier); + image.SetLayout(VK_IMAGE_LAYOUT_GENERAL); + } + + if (!barriers.empty()) { + vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, + static_cast(barriers.size()), barriers.data()); + } + dummies_ready = true; +} + LsfgBarriers& LsfgBarriers::Push(LsfgImage& image, VkAccessFlags src_access, VkAccessFlags dst_access) { barriers.push_back(MakeBarrier(image, src_access, dst_access)); @@ -556,6 +599,27 @@ std::vector AllocateLsfgDescriptorSets(const Device& device, return sets; } +std::vector AllocateLsfgDescriptorSets( + const Device& device, VkDescriptorPool pool, + const std::vector& layouts) { + if (pool == VK_NULL_HANDLE || layouts.empty()) return {}; + for (const VkDescriptorSetLayout layout : layouts) { + if (layout == VK_NULL_HANDLE) return {}; + } + + VkDescriptorSetAllocateInfo allocate_info{}; + allocate_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocate_info.descriptorPool = pool; + allocate_info.descriptorSetCount = static_cast(layouts.size()); + allocate_info.pSetLayouts = layouts.data(); + + std::vector sets(layouts.size(), VK_NULL_HANDLE); + if (vkAllocateDescriptorSets(device.Handle(), &allocate_info, sets.data()) != VK_SUCCESS) { + return {}; + } + return sets; +} + VkSampler CreateLsfgSampler(const Device& device, VkSamplerAddressMode address_mode, VkCompareOp compare_op, bool white_border) { VkSamplerCreateInfo sampler_ci{}; diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp index be3c145bc..8d6937ac8 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp @@ -28,6 +28,12 @@ constexpr size_t LSFG_MIP_LEVELS = 7; constexpr size_t LSFG_GENERATION_SLOTS = LSFG_MAX_GENERATIONS * (LSFG_MAX_GENERATIONS + 1) / 2; +constexpr std::array LSFG_ALPHA_SHADERS{290, 291, 292, 293}; +constexpr std::array LSFG_BETA_SHADERS{298, 299, 300, 301, 302}; +constexpr std::array LSFG_GAMMA_SHADERS{280, 282, 283, 284, 285}; +constexpr std::array LSFG_DELTA_SHADERS{280, 286, 287, 288, 289, + 281, 294, 295, 296, 297}; + [[nodiscard]] constexpr size_t LsfgGenerationSlot(size_t generation_count, size_t generation) { return (generation_count - 1) * generation_count / 2 + generation; } @@ -168,6 +174,10 @@ class LsfgResources { [[nodiscard]] VkBuffer GetBuffer(float timestamp = 0.0f, bool first_iter = false, bool first_iter_s = false); + [[nodiscard]] const LsfgImage& GetDummy(VkFormat format = LSFG_MOTION_FORMAT); + + void PrepareDummies(VkCommandBuffer cmdbuf); + [[nodiscard]] static VkDeviceSize BufferSize(); private: @@ -176,6 +186,8 @@ class LsfgResources { std::map samplers; std::map buffers; + std::map dummies; + bool dummies_ready{}; }; class LsfgBarriers { @@ -299,6 +311,10 @@ class LsfgPass { [[nodiscard]] std::vector AllocateLsfgDescriptorSets( const Device& device, VkDescriptorPool pool, VkDescriptorSetLayout layout, uint32_t count); +[[nodiscard]] std::vector AllocateLsfgDescriptorSets( + const Device& device, VkDescriptorPool pool, + const std::vector& layouts); + [[nodiscard]] VkSampler CreateLsfgSampler(const Device& device, VkSamplerAddressMode address_mode, VkCompareOp compare_op, bool white_border); diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.cpp new file mode 100644 index 000000000..2b6f0f626 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.cpp @@ -0,0 +1,294 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "lsfg_delta.hpp" +#include "lsfg_shaders.hpp" + +#include + +namespace lsfg { + +namespace { + +constexpr uint32_t DISPATCH_TILE_SHIFT = 3; + +[[nodiscard]] uint32_t GroupCount(uint32_t size) { + return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT; +} + +} + +LsfgDelta::LsfgDelta(const Device& device, const LsfgShaders& shaders, LsfgResources& resources, + VkDescriptorPool descriptor_pool, LsfgImageHistory& inputs_, + LsfgImage& flow_input_, LsfgImage* previous_gamma_, LsfgImage* previous1_, + LsfgImage* previous2_) + : inputs{&inputs_}, flow_input{&flow_input_}, previous_gamma{previous_gamma_}, + previous1{previous1_}, previous2{previous2_} { + passes[0] = LsfgPass(device, shaders, LSFG_DELTA_SHADERS[0], + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLER}, + {5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[1] = LsfgPass(device, shaders, LSFG_DELTA_SHADERS[1], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[2] = LsfgPass(device, shaders, LSFG_DELTA_SHADERS[2], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[3] = LsfgPass(device, shaders, LSFG_DELTA_SHADERS[3], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[4] = LsfgPass(device, shaders, LSFG_DELTA_SHADERS[4], + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLER}, + {4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[5] = LsfgPass(device, shaders, LSFG_DELTA_SHADERS[5], + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLER}, + {6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + for (size_t i = 6; i < LSFG_DELTA_STAGES - 1; ++i) { + passes[i] = LsfgPass(device, shaders, LSFG_DELTA_SHADERS[i], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + } + passes[9] = LsfgPass(device, shaders, LSFG_DELTA_SHADERS[9], + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + for (const auto& pass : passes) { + if (!pass.Valid()) return; + } + + const VkExtent2D extent = (*inputs)[0][0].Extent(); + for (auto& image : temp1) { + image = LsfgImage(device, extent); + if (!image.Valid()) return; + } + for (auto& image : temp2) { + image = LsfgImage(device, extent); + if (!image.Valid()) return; + } + out_image1 = LsfgImage(device, extent, LSFG_MOTION_FORMAT); + out_image2 = LsfgImage(device, extent, LSFG_MOTION_FORMAT); + if (!out_image1.Valid() || !out_image2.Valid()) return; + + std::vector layouts; + for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) { + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + layouts.push_back(passes[0].SetLayout()); + } + for (size_t i = 1; i <= 4; ++i) { + layouts.push_back(passes[i].SetLayout()); + } + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + layouts.push_back(passes[5].SetLayout()); + } + for (size_t i = 6; i < LSFG_DELTA_STAGES; ++i) { + layouts.push_back(passes[i].SetLayout()); + } + } + + const std::vector sets = + AllocateLsfgDescriptorSets(device, descriptor_pool, layouts); + if (sets.size() != layouts.size()) return; + + const LsfgImage& dummy = resources.GetDummy(LSFG_MOTION_FORMAT); + const LsfgImage& previous_gamma_image = previous_gamma != nullptr ? *previous_gamma : dummy; + const LsfgImage& previous1_image = previous1 != nullptr ? *previous1 : dummy; + const LsfgImage& previous2_image = previous2 != nullptr ? *previous2 : dummy; + if (!dummy.Valid()) return; + + const VkSampler sampler = resources.GetSampler(); + const VkSampler border_sampler = + resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + const VkSampler edge_sampler = + resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false); + + size_t next = 0; + for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) { + Generation& pass = generations[slot]; + const VkBuffer buffer = + resources.GetBuffer(LsfgSlotTimestamp(slot), false, previous_gamma == nullptr); + + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + pass.first_descriptor_sets[i] = sets[next++]; + } + for (size_t i = 0; i < 4; ++i) { + pass.descriptor_sets[i] = sets[next++]; + } + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + pass.sixth_descriptor_sets[i] = sets[next++]; + } + for (size_t i = 4; i < LSFG_DELTA_STAGES - 2; ++i) { + pass.descriptor_sets[i] = sets[next++]; + } + + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + LsfgDescriptorWriter(pass.first_descriptor_sets[i]) + .AddUniformBuffer(buffer, LsfgResources::BufferSize()) + .AddSampler(border_sampler) + .AddSampler(edge_sampler) + .AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS]) + .AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS]) + .AddSampledImage(previous_gamma_image) + .AddStorageImages(temp1) + .Build(device); + LsfgDescriptorWriter(pass.sixth_descriptor_sets[i]) + .AddUniformBuffer(buffer, LsfgResources::BufferSize()) + .AddSampler(border_sampler) + .AddSampler(edge_sampler) + .AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS]) + .AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS]) + .AddSampledImage(previous_gamma_image) + .AddSampledImage(previous1_image) + .AddStorageImage(temp2[0]) + .Build(device); + } + LsfgDescriptorWriter(pass.descriptor_sets[0]) + .AddSampler(sampler) + .AddSampledImages(temp1) + .AddStorageImages(temp2) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[1]) + .AddSampler(sampler) + .AddSampledImages(temp2) + .AddStorageImage(temp1[0]) + .AddStorageImage(temp1[1]) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[2]) + .AddSampler(sampler) + .AddSampledImage(temp1[0]) + .AddSampledImage(temp1[1]) + .AddStorageImages(temp2) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[3]) + .AddUniformBuffer(buffer, LsfgResources::BufferSize()) + .AddSampler(sampler) + .AddSampler(edge_sampler) + .AddSampledImages(temp2) + .AddSampledImage(previous_gamma_image) + .AddSampledImage(*flow_input) + .AddStorageImage(out_image1) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[4]) + .AddSampler(sampler) + .AddSampledImage(temp2[0]) + .AddStorageImage(temp1[0]) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[5]) + .AddSampler(sampler) + .AddSampledImage(temp1[0]) + .AddStorageImage(temp2[0]) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[6]) + .AddSampler(sampler) + .AddSampledImage(temp2[0]) + .AddStorageImage(temp1[0]) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[7]) + .AddUniformBuffer(buffer, LsfgResources::BufferSize()) + .AddSampler(sampler) + .AddSampler(edge_sampler) + .AddSampledImage(temp1[0]) + .AddSampledImage(previous2_image) + .AddStorageImage(out_image2) + .Build(device); + } + allocated = true; +} + +void LsfgDelta::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot) { + const Generation& pass = generations[slot]; + + const VkExtent2D extent = temp1[0].Extent(); + const uint32_t groups_x = GroupCount(extent.width); + const uint32_t groups_y = GroupCount(extent.height); + + const size_t history = frame_count % LSFG_HISTORY_SLOTS; + const size_t previous_history = (frame_count + 2) % LSFG_HISTORY_SLOTS; + + LsfgBarriers(cmdbuf) + .WriteToReadAll((*inputs)[previous_history]) + .WriteToReadAll((*inputs)[history]) + .WriteToRead(previous_gamma) + .ReadToWriteAll(temp1) + .Build(); + passes[0].Bind(cmdbuf, pass.first_descriptor_sets[history]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); + passes[1].Bind(cmdbuf, pass.descriptor_sets[0]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(temp1).Build(); + passes[2].Bind(cmdbuf, pass.descriptor_sets[1]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); + passes[3].Bind(cmdbuf, pass.descriptor_sets[2]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToReadAll(temp2) + .WriteToRead(previous_gamma) + .WriteToRead(*flow_input) + .ReadToWrite(out_image1) + .Build(); + passes[4].Bind(cmdbuf, pass.descriptor_sets[3]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToReadAll((*inputs)[previous_history]) + .WriteToReadAll((*inputs)[history]) + .WriteToRead(previous_gamma) + .WriteToRead(previous1) + .ReadToWriteAll(temp2) + .Build(); + passes[5].Bind(cmdbuf, pass.sixth_descriptor_sets[history]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToReadAll(temp2) + .ReadToWrite(temp1[0]) + .ReadToWrite(temp1[1]) + .Build(); + passes[6].Bind(cmdbuf, pass.descriptor_sets[4]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToRead(temp1[0]) + .WriteToRead(temp1[1]) + .ReadToWriteAll(temp2) + .Build(); + passes[7].Bind(cmdbuf, pass.descriptor_sets[5]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToReadAll(temp2) + .ReadToWrite(temp1[0]) + .ReadToWrite(temp1[1]) + .Build(); + passes[8].Bind(cmdbuf, pass.descriptor_sets[6]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToRead(temp1[0]) + .WriteToRead(temp1[1]) + .WriteToRead(previous2) + .ReadToWrite(out_image2) + .Build(); + passes[9].Bind(cmdbuf, pass.descriptor_sets[7]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); +} + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.hpp new file mode 100644 index 000000000..9143c621d --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.hpp @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "lsfg_common.hpp" + +namespace lsfg { + +class LsfgShaders; + +constexpr size_t LSFG_DELTA_STAGES = 10; +constexpr size_t LSFG_DELTA_TEMPS = 3; + +class LsfgDelta { +public: + LsfgDelta() = default; + LsfgDelta(const Device& device, const LsfgShaders& shaders, LsfgResources& resources, + VkDescriptorPool descriptor_pool, LsfgImageHistory& inputs, LsfgImage& flow_input, + LsfgImage* previous_gamma, LsfgImage* previous1, LsfgImage* previous2); + + void Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot); + + [[nodiscard]] LsfgImage& Output1() { + return out_image1; + } + + [[nodiscard]] LsfgImage& Output2() { + return out_image2; + } + + [[nodiscard]] bool Valid() const { + return allocated; + } + +private: + struct Generation { + std::array first_descriptor_sets{}; + std::array sixth_descriptor_sets{}; + std::array descriptor_sets{}; + }; + + LsfgImageHistory* inputs{}; + LsfgImage* flow_input{}; + LsfgImage* previous_gamma{}; + LsfgImage* previous1{}; + LsfgImage* previous2{}; + + std::array passes; + std::array generations{}; + + std::array temp1; + LsfgImagePair temp2; + LsfgImage out_image1; + LsfgImage out_image2; + bool allocated{}; +}; + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.cpp new file mode 100644 index 000000000..40ae16a52 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.cpp @@ -0,0 +1,192 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "lsfg_gamma.hpp" +#include "lsfg_shaders.hpp" + +#include + +namespace lsfg { + +namespace { + +constexpr uint32_t DISPATCH_TILE_SHIFT = 3; + +[[nodiscard]] uint32_t GroupCount(uint32_t size) { + return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT; +} + +} + +LsfgGamma::LsfgGamma(const Device& device, const LsfgShaders& shaders, LsfgResources& resources, + VkDescriptorPool descriptor_pool, LsfgImageHistory& inputs_, + LsfgImage& flow_input_, LsfgImage* previous_) + : inputs{&inputs_}, flow_input{&flow_input_}, previous{previous_} { + passes[0] = LsfgPass(device, shaders, LSFG_GAMMA_SHADERS[0], + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLER}, + {5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[1] = LsfgPass(device, shaders, LSFG_GAMMA_SHADERS[1], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[2] = LsfgPass(device, shaders, LSFG_GAMMA_SHADERS[2], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[3] = LsfgPass(device, shaders, LSFG_GAMMA_SHADERS[3], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[4] = LsfgPass(device, shaders, LSFG_GAMMA_SHADERS[4], + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLER}, + {4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + for (const auto& pass : passes) { + if (!pass.Valid()) return; + } + + const VkExtent2D extent = (*inputs)[0][0].Extent(); + for (auto& image : temp1) { + image = LsfgImage(device, extent); + if (!image.Valid()) return; + } + for (auto& image : temp2) { + image = LsfgImage(device, extent); + if (!image.Valid()) return; + } + out_image = LsfgImage(device, extent, LSFG_MOTION_FORMAT); + if (!out_image.Valid()) return; + + std::vector layouts; + for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) { + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + layouts.push_back(passes[0].SetLayout()); + } + for (size_t i = 1; i < LSFG_GAMMA_STAGES; ++i) { + layouts.push_back(passes[i].SetLayout()); + } + } + + const std::vector sets = + AllocateLsfgDescriptorSets(device, descriptor_pool, layouts); + if (sets.size() != layouts.size()) return; + + const LsfgImage& previous_image = + previous != nullptr ? *previous : resources.GetDummy(LSFG_MOTION_FORMAT); + if (!previous_image.Valid()) return; + + const VkSampler sampler = resources.GetSampler(); + const VkSampler border_sampler = + resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + const VkSampler edge_sampler = + resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false); + + size_t next = 0; + for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) { + Generation& pass = generations[slot]; + const VkBuffer buffer = resources.GetBuffer(LsfgSlotTimestamp(slot), previous == nullptr); + + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + pass.first_descriptor_sets[i] = sets[next++]; + } + for (size_t i = 0; i < LSFG_GAMMA_STAGES - 1; ++i) { + pass.descriptor_sets[i] = sets[next++]; + } + + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + LsfgDescriptorWriter(pass.first_descriptor_sets[i]) + .AddUniformBuffer(buffer, LsfgResources::BufferSize()) + .AddSampler(border_sampler) + .AddSampler(edge_sampler) + .AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS]) + .AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS]) + .AddSampledImage(previous_image) + .AddStorageImages(temp1) + .Build(device); + } + LsfgDescriptorWriter(pass.descriptor_sets[0]) + .AddSampler(sampler) + .AddSampledImages(temp1) + .AddStorageImages(temp2) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[1]) + .AddSampler(sampler) + .AddSampledImages(temp2) + .AddStorageImage(temp1[0]) + .AddStorageImage(temp1[1]) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[2]) + .AddSampler(sampler) + .AddSampledImage(temp1[0]) + .AddSampledImage(temp1[1]) + .AddStorageImages(temp2) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[3]) + .AddUniformBuffer(buffer, LsfgResources::BufferSize()) + .AddSampler(sampler) + .AddSampler(edge_sampler) + .AddSampledImages(temp2) + .AddSampledImage(previous_image) + .AddSampledImage(*flow_input) + .AddStorageImage(out_image) + .Build(device); + } + allocated = true; +} + +void LsfgGamma::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot) { + const Generation& pass = generations[slot]; + + const VkExtent2D extent = temp1[0].Extent(); + const uint32_t groups_x = GroupCount(extent.width); + const uint32_t groups_y = GroupCount(extent.height); + + const size_t history = frame_count % LSFG_HISTORY_SLOTS; + const size_t previous_history = (frame_count + 2) % LSFG_HISTORY_SLOTS; + + LsfgBarriers(cmdbuf) + .WriteToReadAll((*inputs)[previous_history]) + .WriteToReadAll((*inputs)[history]) + .WriteToRead(previous) + .ReadToWriteAll(temp1) + .Build(); + passes[0].Bind(cmdbuf, pass.first_descriptor_sets[history]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); + passes[1].Bind(cmdbuf, pass.descriptor_sets[0]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToReadAll(temp2) + .ReadToWrite(temp1[0]) + .ReadToWrite(temp1[1]) + .Build(); + passes[2].Bind(cmdbuf, pass.descriptor_sets[1]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToRead(temp1[0]) + .WriteToRead(temp1[1]) + .ReadToWriteAll(temp2) + .Build(); + passes[3].Bind(cmdbuf, pass.descriptor_sets[2]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToReadAll(temp2) + .WriteToRead(previous) + .WriteToRead(*flow_input) + .ReadToWrite(out_image) + .Build(); + passes[4].Bind(cmdbuf, pass.descriptor_sets[3]); + vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); +} + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.hpp new file mode 100644 index 000000000..fd211e4d5 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.hpp @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "lsfg_common.hpp" + +namespace lsfg { + +class LsfgShaders; + +constexpr size_t LSFG_GAMMA_STAGES = 5; +constexpr size_t LSFG_GAMMA_TEMPS = 3; + +class LsfgGamma { +public: + LsfgGamma() = default; + LsfgGamma(const Device& device, const LsfgShaders& shaders, LsfgResources& resources, + VkDescriptorPool descriptor_pool, LsfgImageHistory& inputs, LsfgImage& flow_input, + LsfgImage* previous); + + void Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot); + + [[nodiscard]] LsfgImage& Output() { + return out_image; + } + + [[nodiscard]] bool Valid() const { + return allocated; + } + +private: + struct Generation { + std::array first_descriptor_sets{}; + std::array descriptor_sets{}; + }; + + LsfgImageHistory* inputs{}; + LsfgImage* flow_input{}; + LsfgImage* previous{}; + + std::array passes; + std::array generations{}; + + std::array temp1; + LsfgImagePair temp2; + LsfgImage out_image; + bool allocated{}; +}; + +} From 1f3a5cad988bab91c740b406907055503b5e695f Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sun, 23 Aug 2026 10:28:09 -0400 Subject: [PATCH 10/35] Drive the LSFG chain from the renderer and present generated frames Adds the FrameGen orchestrator and eden's pacer behind a C bridge (vkr_lsfg), then wires it into record_and_submit_frame: the composited real frame is copied into the chain, the shared flow passes run, and each generated frame is dispatched into its own composite target and blitted to its own acquired swapchain image. Generated frames are queued to present before the real frame, so FIFO paces them one vblank apart. Fixes a latent bug in the earlier port: WinNative resolves every Vulkan entry point through the vkd dispatch table because adrenotools drivers live in an isolated linker namespace with no shared global symbols. The ported LSFG code included directly and so called the system loader - correct on a stock driver, wrong on Turnip, where the chain would have allocated against a different driver than the renderer's device. All of it now routes through vkd, which gained CreateComputePipelines, CmdDispatch and CmdCopyImage. vk_dispatch.h also gained the extern "C" guard it needed once C++ began including it. The generate pass caches the last view bound per target slot, so recreating composite targets without rebuilding the chain would have left descriptors pointing at destroyed views; ForgetTargets now clears that cache whenever the targets are recreated. Verified on the Adreno 750 through the bridge exactly as the renderer drives it: 120 frames at a simulated 30 fps, 118 of them generating, and the generated frame is still the exact midpoint (0.00 mean abs error). --- app/src/main/cpp/CMakeLists.txt | 2 + .../main/cpp/winlator/vk/lsfg/lsfg_alpha.cpp | 2 +- .../main/cpp/winlator/vk/lsfg/lsfg_beta.cpp | 10 +- .../main/cpp/winlator/vk/lsfg/lsfg_chain.cpp | 2 +- .../main/cpp/winlator/vk/lsfg/lsfg_chain.hpp | 4 + .../main/cpp/winlator/vk/lsfg/lsfg_common.cpp | 66 ++--- .../main/cpp/winlator/vk/lsfg/lsfg_common.hpp | 3 +- .../main/cpp/winlator/vk/lsfg/lsfg_delta.cpp | 20 +- .../main/cpp/winlator/vk/lsfg/lsfg_gamma.cpp | 10 +- .../cpp/winlator/vk/lsfg/lsfg_generate.cpp | 12 +- .../cpp/winlator/vk/lsfg/lsfg_generate.hpp | 2 + .../cpp/winlator/vk/lsfg/lsfg_mipmaps.cpp | 2 +- .../main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp | 245 ++++++++++++++++ .../main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp | 61 ++++ .../cpp/winlator/vk/lsfg/lsfg_shaders.cpp | 4 +- .../cpp/winlator/vk/lsfg/lsfg_shaders.hpp | 2 +- .../main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp | 229 +++++++++++++++ app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h | 47 +++ app/src/main/cpp/winlator/vk/vk_dispatch.c | 3 + app/src/main/cpp/winlator/vk/vk_dispatch.h | 11 + app/src/main/cpp/winlator/vk/vk_renderer.c | 274 +++++++++++++++--- app/src/main/cpp/winlator/vk/vk_state.h | 9 + .../display/XServerDisplayActivity.java | 46 +++ .../display/renderer/VulkanRenderer.java | 33 +++ 24 files changed, 1004 insertions(+), 95 deletions(-) create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp create mode 100644 app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index a9cb0abd9..c23e420d7 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -159,6 +159,8 @@ add_library(winlator SHARED winlator/vk/lsfg/lsfg_gamma.cpp winlator/vk/lsfg/lsfg_delta.cpp winlator/vk/lsfg/lsfg_chain.cpp + winlator/vk/lsfg/lsfg_pacer.cpp + winlator/vk/lsfg/vkr_lsfg.cpp winlator/vk/lsfg/lsfg_probe.c winlator/vk/lsfg/lsfg_jni.c ) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_alpha.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_alpha.cpp index d066b7f9e..52b5c76c6 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_alpha.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_alpha.cpp @@ -146,7 +146,7 @@ void LsfgAlpha::DispatchStage(VkCommandBuffer cmdbuf, uint64_t frame_count, size : last_descriptor_sets[frame_count % LSFG_HISTORY_SLOTS]; passes->Get(stage).BindSet(cmdbuf, set); - vkCmdDispatch(cmdbuf, GroupCount(extent.width), GroupCount(extent.height), 1); + vkd.CmdDispatch(cmdbuf, GroupCount(extent.width), GroupCount(extent.height), 1); } } diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_beta.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_beta.cpp index dd933fc90..cc9746e11 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_beta.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_beta.cpp @@ -127,23 +127,23 @@ void LsfgBeta::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count) { barriers.ReadToWriteAll(temp1).Build(); passes[0].Bind(cmdbuf, first_descriptor_sets[frame_count % LSFG_HISTORY_SLOTS]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); passes[1].Bind(cmdbuf, descriptor_sets[0]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(temp1).Build(); passes[2].Bind(cmdbuf, descriptor_sets[1]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); passes[3].Bind(cmdbuf, descriptor_sets[2]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(out_images).Build(); passes[4].Bind(cmdbuf, descriptor_sets[3]); - vkCmdDispatch(cmdbuf, GroupCount(extent.width, OUTPUT_TILE_SHIFT), + vkd.CmdDispatch(cmdbuf, GroupCount(extent.width, OUTPUT_TILE_SHIFT), GroupCount(extent.height, OUTPUT_TILE_SHIFT), 1); } diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp index f16912f4a..cae436480 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp @@ -77,7 +77,7 @@ LsfgChain::LsfgChain(const Device& device, const LsfgShaders& shaders, VkExtent2 LsfgChain::~LsfgChain() { if (descriptor_pool != VK_NULL_HANDLE) { - vkDestroyDescriptorPool(owner, descriptor_pool, nullptr); + vkd.DestroyDescriptorPool(owner, descriptor_pool, nullptr); descriptor_pool = VK_NULL_HANDLE; } } diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.hpp index 16c3724b1..eea640df4 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.hpp @@ -41,6 +41,10 @@ class LsfgChain { generate.SetTarget(device, LsfgGenerationSlot(generation_count, generation), target, view); } + void ForgetTargets() { + generate.ForgetTargets(); + } + [[nodiscard]] LsfgImage& Input(uint64_t frame_count) { return frames[frame_count % frames.size()]; } diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp index dc3e71782..5b61acd4c 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.cpp @@ -50,7 +50,7 @@ VkImageMemoryBarrier MakeBarrier(const LsfgImage& image, VkAccessFlags src_acces Device::Device(VkDevice device_, VkPhysicalDevice physical_device_) : device{device_}, physical_device{physical_device_} { - vkGetPhysicalDeviceMemoryProperties(physical_device, &memory_properties); + vkd.GetPhysicalDeviceMemoryProperties(physical_device, &memory_properties); } uint32_t Device::FindMemoryType(uint32_t bits, VkMemoryPropertyFlags properties) const { @@ -67,13 +67,13 @@ Buffer::Buffer(const Device& device_, VkDeviceSize size) : device{device_.Handle buffer_ci.size = size; buffer_ci.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; buffer_ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(device, &buffer_ci, nullptr, &buffer) != VK_SUCCESS) { + if (vkd.CreateBuffer(device, &buffer_ci, nullptr, &buffer) != VK_SUCCESS) { buffer = VK_NULL_HANDLE; return; } VkMemoryRequirements requirements; - vkGetBufferMemoryRequirements(device, buffer, &requirements); + vkd.GetBufferMemoryRequirements(device, buffer, &requirements); VkMemoryAllocateInfo allocate_info{}; allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; @@ -82,13 +82,13 @@ Buffer::Buffer(const Device& device_, VkDeviceSize size) : device{device_.Handle requirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); if (allocate_info.memoryTypeIndex == UINT32_MAX || - vkAllocateMemory(device, &allocate_info, nullptr, &memory) != VK_SUCCESS) { + vkd.AllocateMemory(device, &allocate_info, nullptr, &memory) != VK_SUCCESS) { Release(); return; } - vkBindBufferMemory(device, buffer, memory, 0); - if (vkMapMemory(device, memory, 0, VK_WHOLE_SIZE, 0, &mapped) != VK_SUCCESS) { + vkd.BindBufferMemory(device, buffer, memory, 0); + if (vkd.MapMemory(device, memory, 0, VK_WHOLE_SIZE, 0, &mapped) != VK_SUCCESS) { mapped = nullptr; Release(); } @@ -128,11 +128,11 @@ void Buffer::Upload(const void* data, size_t size) { void Buffer::Release() { if (device == VK_NULL_HANDLE) return; if (mapped) { - vkUnmapMemory(device, memory); + vkd.UnmapMemory(device, memory); mapped = nullptr; } - if (buffer) vkDestroyBuffer(device, buffer, nullptr); - if (memory) vkFreeMemory(device, memory, nullptr); + if (buffer) vkd.DestroyBuffer(device, buffer, nullptr); + if (memory) vkd.FreeMemory(device, memory, nullptr); buffer = VK_NULL_HANDLE; memory = VK_NULL_HANDLE; } @@ -153,13 +153,13 @@ LsfgImage::LsfgImage(const Device& device_, VkExtent2D extent_, VkFormat format_ VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; image_ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; image_ci.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - if (vkCreateImage(device, &image_ci, nullptr, &image) != VK_SUCCESS) { + if (vkd.CreateImage(device, &image_ci, nullptr, &image) != VK_SUCCESS) { image = VK_NULL_HANDLE; return; } VkMemoryRequirements requirements; - vkGetImageMemoryRequirements(device, image, &requirements); + vkd.GetImageMemoryRequirements(device, image, &requirements); VkMemoryAllocateInfo allocate_info{}; allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; @@ -167,11 +167,11 @@ LsfgImage::LsfgImage(const Device& device_, VkExtent2D extent_, VkFormat format_ allocate_info.memoryTypeIndex = device_.FindMemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); if (allocate_info.memoryTypeIndex == UINT32_MAX || - vkAllocateMemory(device, &allocate_info, nullptr, &memory) != VK_SUCCESS) { + vkd.AllocateMemory(device, &allocate_info, nullptr, &memory) != VK_SUCCESS) { Release(); return; } - vkBindImageMemory(device, image, memory, 0); + vkd.BindImageMemory(device, image, memory, 0); VkImageViewCreateInfo view_ci{}; view_ci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; @@ -181,7 +181,7 @@ LsfgImage::LsfgImage(const Device& device_, VkExtent2D extent_, VkFormat format_ view_ci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; view_ci.subresourceRange.levelCount = 1; view_ci.subresourceRange.layerCount = 1; - if (vkCreateImageView(device, &view_ci, nullptr, &view) != VK_SUCCESS) { + if (vkd.CreateImageView(device, &view_ci, nullptr, &view) != VK_SUCCESS) { view = VK_NULL_HANDLE; Release(); } @@ -220,9 +220,9 @@ LsfgImage& LsfgImage::operator=(LsfgImage&& other) noexcept { void LsfgImage::Release() { if (device == VK_NULL_HANDLE) return; - if (view) vkDestroyImageView(device, view, nullptr); - if (image) vkDestroyImage(device, image, nullptr); - if (memory) vkFreeMemory(device, memory, nullptr); + if (view) vkd.DestroyImageView(device, view, nullptr); + if (image) vkd.DestroyImage(device, image, nullptr); + if (memory) vkd.FreeMemory(device, memory, nullptr); view = VK_NULL_HANDLE; image = VK_NULL_HANDLE; memory = VK_NULL_HANDLE; @@ -231,7 +231,7 @@ void LsfgImage::Release() { LsfgResources::~LsfgResources() { if (!device) return; for (auto& [key, sampler] : samplers) { - vkDestroySampler(device->Handle(), sampler, nullptr); + vkd.DestroySampler(device->Handle(), sampler, nullptr); } samplers.clear(); } @@ -315,7 +315,7 @@ void LsfgResources::PrepareDummies(VkCommandBuffer cmdbuf) { } if (!barriers.empty()) { - vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + vkd.CmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, static_cast(barriers.size()), barriers.data()); } @@ -364,7 +364,7 @@ LsfgBarriers& LsfgBarriers::DiscardToWrite(VkImage image) { void LsfgBarriers::Build() { if (barriers.empty()) return; - vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + vkd.CmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, static_cast(barriers.size()), barriers.data()); barriers.clear(); @@ -431,7 +431,7 @@ LsfgDescriptorWriter& LsfgDescriptorWriter::AddUniformBuffer(VkBuffer buffer, Vk void LsfgDescriptorWriter::Build(const Device& device) { if (writes.empty()) return; - vkUpdateDescriptorSets(device.Handle(), static_cast(writes.size()), writes.data(), 0, + vkd.UpdateDescriptorSets(device.Handle(), static_cast(writes.size()), writes.data(), 0, nullptr); writes.clear(); } @@ -457,7 +457,7 @@ LsfgPass::LsfgPass(const Device& device_, const LsfgShaders& shaders, uint32_t s layout_ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; layout_ci.bindingCount = static_cast(layout_bindings.size()); layout_ci.pBindings = layout_bindings.data(); - if (vkCreateDescriptorSetLayout(device, &layout_ci, nullptr, &descriptor_set_layout) != + if (vkd.CreateDescriptorSetLayout(device, &layout_ci, nullptr, &descriptor_set_layout) != VK_SUCCESS) { Release(); return; @@ -467,7 +467,7 @@ LsfgPass::LsfgPass(const Device& device_, const LsfgShaders& shaders, uint32_t s pipeline_layout_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipeline_layout_ci.setLayoutCount = 1; pipeline_layout_ci.pSetLayouts = &descriptor_set_layout; - if (vkCreatePipelineLayout(device, &pipeline_layout_ci, nullptr, &pipeline_layout) != + if (vkd.CreatePipelineLayout(device, &pipeline_layout_ci, nullptr, &pipeline_layout) != VK_SUCCESS) { Release(); return; @@ -489,7 +489,7 @@ LsfgPass::LsfgPass(const Device& device_, const LsfgShaders& shaders, uint32_t s pipeline_ci.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; pipeline_ci.stage = stage; pipeline_ci.layout = pipeline_layout; - if (vkCreateComputePipelines(device, VK_NULL_HANDLE, 1, &pipeline_ci, nullptr, &pipeline) != + if (vkd.CreateComputePipelines(device, VK_NULL_HANDLE, 1, &pipeline_ci, nullptr, &pipeline) != VK_SUCCESS) { pipeline = VK_NULL_HANDLE; Release(); @@ -528,9 +528,9 @@ LsfgPass& LsfgPass::operator=(LsfgPass&& other) noexcept { void LsfgPass::Release() { if (device == VK_NULL_HANDLE) return; - if (pipeline) vkDestroyPipeline(device, pipeline, nullptr); - if (pipeline_layout) vkDestroyPipelineLayout(device, pipeline_layout, nullptr); - if (descriptor_set_layout) vkDestroyDescriptorSetLayout(device, descriptor_set_layout, nullptr); + if (pipeline) vkd.DestroyPipeline(device, pipeline, nullptr); + if (pipeline_layout) vkd.DestroyPipelineLayout(device, pipeline_layout, nullptr); + if (descriptor_set_layout) vkd.DestroyDescriptorSetLayout(device, descriptor_set_layout, nullptr); pipeline = VK_NULL_HANDLE; pipeline_layout = VK_NULL_HANDLE; descriptor_set_layout = VK_NULL_HANDLE; @@ -542,11 +542,11 @@ void LsfgPass::Bind(VkCommandBuffer cmdbuf, VkDescriptorSet set) const { } void LsfgPass::BindPipeline(VkCommandBuffer cmdbuf) const { - vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); + vkd.CmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); } void LsfgPass::BindSet(VkCommandBuffer cmdbuf, VkDescriptorSet set) const { - vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0, 1, &set, 0, + vkd.CmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0, 1, &set, 0, nullptr); } @@ -573,7 +573,7 @@ VkDescriptorPool CreateLsfgDescriptorPool(const Device& device, uint32_t max_set pool_ci.pPoolSizes = sizes.data(); VkDescriptorPool pool = VK_NULL_HANDLE; - if (vkCreateDescriptorPool(device.Handle(), &pool_ci, nullptr, &pool) != VK_SUCCESS) { + if (vkd.CreateDescriptorPool(device.Handle(), &pool_ci, nullptr, &pool) != VK_SUCCESS) { return VK_NULL_HANDLE; } return pool; @@ -593,7 +593,7 @@ std::vector AllocateLsfgDescriptorSets(const Device& device, allocate_info.pSetLayouts = layouts.data(); std::vector sets(count, VK_NULL_HANDLE); - if (vkAllocateDescriptorSets(device.Handle(), &allocate_info, sets.data()) != VK_SUCCESS) { + if (vkd.AllocateDescriptorSets(device.Handle(), &allocate_info, sets.data()) != VK_SUCCESS) { return {}; } return sets; @@ -614,7 +614,7 @@ std::vector AllocateLsfgDescriptorSets( allocate_info.pSetLayouts = layouts.data(); std::vector sets(layouts.size(), VK_NULL_HANDLE); - if (vkAllocateDescriptorSets(device.Handle(), &allocate_info, sets.data()) != VK_SUCCESS) { + if (vkd.AllocateDescriptorSets(device.Handle(), &allocate_info, sets.data()) != VK_SUCCESS) { return {}; } return sets; @@ -639,7 +639,7 @@ VkSampler CreateLsfgSampler(const Device& device, VkSamplerAddressMode address_m sampler_ci.unnormalizedCoordinates = VK_FALSE; VkSampler sampler = VK_NULL_HANDLE; - if (vkCreateSampler(device.Handle(), &sampler_ci, nullptr, &sampler) != VK_SUCCESS) { + if (vkd.CreateSampler(device.Handle(), &sampler_ci, nullptr, &sampler) != VK_SUCCESS) { return VK_NULL_HANDLE; } return sampler; diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp index 8d6937ac8..469b21c99 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp @@ -13,7 +13,8 @@ #include #include #include -#include + +#include "../vk_dispatch.h" namespace lsfg { diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.cpp index 2b6f0f626..f120007a9 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.cpp @@ -224,19 +224,19 @@ void LsfgDelta::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t sl .ReadToWriteAll(temp1) .Build(); passes[0].Bind(cmdbuf, pass.first_descriptor_sets[history]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); passes[1].Bind(cmdbuf, pass.descriptor_sets[0]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(temp1).Build(); passes[2].Bind(cmdbuf, pass.descriptor_sets[1]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); passes[3].Bind(cmdbuf, pass.descriptor_sets[2]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf) .WriteToReadAll(temp2) @@ -245,7 +245,7 @@ void LsfgDelta::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t sl .ReadToWrite(out_image1) .Build(); passes[4].Bind(cmdbuf, pass.descriptor_sets[3]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf) .WriteToReadAll((*inputs)[previous_history]) @@ -255,7 +255,7 @@ void LsfgDelta::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t sl .ReadToWriteAll(temp2) .Build(); passes[5].Bind(cmdbuf, pass.sixth_descriptor_sets[history]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf) .WriteToReadAll(temp2) @@ -263,7 +263,7 @@ void LsfgDelta::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t sl .ReadToWrite(temp1[1]) .Build(); passes[6].Bind(cmdbuf, pass.descriptor_sets[4]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf) .WriteToRead(temp1[0]) @@ -271,7 +271,7 @@ void LsfgDelta::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t sl .ReadToWriteAll(temp2) .Build(); passes[7].Bind(cmdbuf, pass.descriptor_sets[5]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf) .WriteToReadAll(temp2) @@ -279,7 +279,7 @@ void LsfgDelta::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t sl .ReadToWrite(temp1[1]) .Build(); passes[8].Bind(cmdbuf, pass.descriptor_sets[6]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf) .WriteToRead(temp1[0]) @@ -288,7 +288,7 @@ void LsfgDelta::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t sl .ReadToWrite(out_image2) .Build(); passes[9].Bind(cmdbuf, pass.descriptor_sets[7]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); } } diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.cpp index 40ae16a52..600eef453 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.cpp @@ -157,11 +157,11 @@ void LsfgGamma::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t sl .ReadToWriteAll(temp1) .Build(); passes[0].Bind(cmdbuf, pass.first_descriptor_sets[history]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); passes[1].Bind(cmdbuf, pass.descriptor_sets[0]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf) .WriteToReadAll(temp2) @@ -169,7 +169,7 @@ void LsfgGamma::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t sl .ReadToWrite(temp1[1]) .Build(); passes[2].Bind(cmdbuf, pass.descriptor_sets[1]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf) .WriteToRead(temp1[0]) @@ -177,7 +177,7 @@ void LsfgGamma::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t sl .ReadToWriteAll(temp2) .Build(); passes[3].Bind(cmdbuf, pass.descriptor_sets[2]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); LsfgBarriers(cmdbuf) .WriteToReadAll(temp2) @@ -186,7 +186,7 @@ void LsfgGamma::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t sl .ReadToWrite(out_image) .Build(); passes[4].Bind(cmdbuf, pass.descriptor_sets[3]); - vkCmdDispatch(cmdbuf, groups_x, groups_y, 1); + vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); } } diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.cpp index 254c04e96..176208376 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.cpp @@ -96,6 +96,14 @@ void LsfgGenerate::SetTarget(const Device& device, size_t slot, uint32_t target, } } +void LsfgGenerate::ForgetTargets() { + for (auto& generation : generations) { + for (auto& entry : generation.targets) { + entry.view = VK_NULL_HANDLE; + } + } +} + void LsfgGenerate::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot, uint32_t target, VkImage image, VkExtent2D extent) { const Target& entry = generations[slot].targets[target]; @@ -109,13 +117,13 @@ void LsfgGenerate::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t .Build(); pass.Bind(cmdbuf, entry.descriptor_sets[frame_count % entry.descriptor_sets.size()]); - vkCmdDispatch(cmdbuf, GroupCount(extent.width), GroupCount(extent.height), 1); + vkd.CmdDispatch(cmdbuf, GroupCount(extent.width), GroupCount(extent.height), 1); const VkImageMemoryBarrier after = MakeTargetBarrier( image, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_LAYOUT_GENERAL); - vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + vkd.CmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &after); diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.hpp index b7fc6a8d7..716650da4 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_generate.hpp @@ -23,6 +23,8 @@ class LsfgGenerate { void SetTarget(const Device& device, size_t slot, uint32_t target, VkImageView view); + void ForgetTargets(); + void Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot, uint32_t target, VkImage image, VkExtent2D extent); diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_mipmaps.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_mipmaps.cpp index 74d88850f..2e42facd6 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_mipmaps.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_mipmaps.cpp @@ -74,7 +74,7 @@ void LsfgMipmaps::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count) { LsfgBarriers(cmdbuf).WriteToRead((*frames)[slot]).ReadToWriteAll(out_images).Build(); pass.Bind(cmdbuf, descriptor_sets[slot]); - vkCmdDispatch(cmdbuf, GroupCount(flow_extent.width), GroupCount(flow_extent.height), 1); + vkd.CmdDispatch(cmdbuf, GroupCount(flow_extent.width), GroupCount(flow_extent.height), 1); } } diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp new file mode 100644 index 000000000..a77ea8532 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp @@ -0,0 +1,245 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "lsfg_pacer.hpp" + +#include +#include +#include + +namespace lsfg { + +namespace { + +using Clock = std::chrono::steady_clock; + +constexpr float INTERVAL_SMOOTHING = 0.25f; +constexpr float MINIMUM_BASE_RATE = 10.0f; +constexpr float BURST_CADENCE_RATIO = 3.0f; +constexpr float BURST_TARGET_RATIO = 2.0f; +constexpr float PROBE_THROUGHPUT_TOLERANCE = 0.95f; +constexpr float PROBE_BASE_COLLAPSE_RATIO = 0.70f; +constexpr float PROBE_MARGINAL_GAIN = 1.15f; +constexpr float TARGET_SATISFIED_RATIO = 0.95f; +constexpr float UNLOADED_BASE_RETENTION = 0.75f; +constexpr float CREDIT_EPSILON = 1.0e-4f; +constexpr uint32_t MAX_PROBE_FAILURES = 4; + +constexpr auto STABILIZATION_DURATION = std::chrono::seconds(1); +constexpr auto PROBE_DURATION = std::chrono::seconds(1); +constexpr auto DEFICIT_DURATION = std::chrono::seconds(1); +constexpr auto PROBE_STEP_DELAY = std::chrono::milliseconds(250); + +[[nodiscard]] Clock::duration ProbeBackoff(uint32_t failures) { + switch (failures) { + case 1: + return std::chrono::seconds(5); + case 2: + return std::chrono::seconds(15); + case 3: + return std::chrono::seconds(30); + default: + return std::chrono::seconds(60); + } +} + +} + +size_t LsfgPacer::MaxGenerations() const { + if (config.multiplier < 2) return 0; + if (config.target_rate != 0) return LSFG_MAX_MULTIPLIER - 1; + return std::min(config.multiplier, LSFG_MAX_MULTIPLIER) - 1; +} + +LsfgPlan LsfgPacer::Plan(size_t capacity) { + const size_t ceiling = std::min(capacity, MaxGenerations()); + if (ceiling == 0) { + Reset(); + return {}; + } + + const Clock::time_point now = Clock::now(); + const size_t previous_generations = std::exchange(issued_generations, 0); + if (!last_frame) { + last_frame = now; + return {}; + } + + const Clock::duration interval = now - *last_frame; + const float interval_seconds = std::chrono::duration(interval).count(); + last_frame = now; + + if (interval_seconds <= 0.0f) { + Stabilize(now); + return {}; + } + + const float target_rate = static_cast(config.target_rate); + + if (smoothed_interval > 0.0f) { + float burst_threshold = BURST_CADENCE_RATIO / smoothed_interval; + if (target_rate > 0.0f) { + burst_threshold = std::max(burst_threshold, target_rate * BURST_TARGET_RATIO); + } + if (1.0f / interval_seconds > burst_threshold) { + DeferEvaluations(interval); + output_credit = 0.0f; + return {}; + } + } + + if (interval_seconds > 1.0f / MINIMUM_BASE_RATE) { + Stabilize(now); + return {}; + } + + smoothed_interval = smoothed_interval > 0.0f + ? smoothed_interval + + (interval_seconds - smoothed_interval) * INTERVAL_SMOOTHING + : interval_seconds; + + if (previous_generations == 0) { + const float measured = 1.0f / smoothed_interval; + unloaded_base_rate = + unloaded_base_rate > 0.0f + ? unloaded_base_rate + (measured - unloaded_base_rate) * INTERVAL_SMOOTHING + : measured; + } + + if (stable_until) { + if (now < *stable_until) { + return {}; + } + stable_until.reset(); + } + + if (target_rate == 0.0f) { + limit = std::min(MaxGenerations(), ceiling); + output_credit = 0.0f; + issued_generations = limit; + return LsfgPlan{limit, limit > 0}; + } + + UpdateLimit(now, 1.0f / smoothed_interval, target_rate, ceiling); + + const size_t allowed = std::min(limit, ceiling); + const float desired_outputs = smoothed_interval * target_rate; + if (allowed == 0 || desired_outputs <= 1.0f) { + output_credit = 0.0f; + return {}; + } + + output_credit += desired_outputs; + const size_t outputs = + std::max(1, static_cast(std::floor(output_credit + CREDIT_EPSILON))); + const size_t generations = std::min(outputs - 1, allowed); + + output_credit -= static_cast(generations + 1); + if (output_credit < 0.0f) { + output_credit = 0.0f; + } else if (generations == allowed && output_credit >= 1.0f) { + output_credit = std::fmod(output_credit, 1.0f); + } + + issued_generations = generations; + return LsfgPlan{generations, true}; +} + +void LsfgPacer::UpdateLimit(Clock::time_point now, float base_rate, float target_rate, + size_t ceiling) { + limit = std::min(limit, ceiling); + + if (probe_until) { + if (now < *probe_until) { + return; + } + probe_until.reset(); + output_credit = 0.0f; + + const float previous_output = + std::min(target_rate, probe_base_rate * static_cast(probe_previous_limit + 1)); + const float current_output = + std::min(target_rate, base_rate * static_cast(limit + 1)); + + const bool throughput_regressed = + current_output < previous_output * PROBE_THROUGHPUT_TOLERANCE; + const bool collapsed_for_marginal_gain = + base_rate < probe_base_rate * PROBE_BASE_COLLAPSE_RATIO && + current_output < previous_output * PROBE_MARGINAL_GAIN; + const bool emulation_slowed = unloaded_base_rate > 0.0f && + base_rate < unloaded_base_rate * UNLOADED_BASE_RETENTION; + + if (throughput_regressed || collapsed_for_marginal_gain || emulation_slowed) { + limit = probe_previous_limit; + probe_failures = std::min(probe_failures + 1, MAX_PROBE_FAILURES); + next_probe = now + ProbeBackoff(probe_failures); + deficit_since.reset(); + return; + } + + probe_failures = 0; + next_probe = now + PROBE_STEP_DELAY; + } + + if (base_rate * static_cast(limit + 1) >= target_rate * TARGET_SATISFIED_RATIO || + limit >= ceiling) { + deficit_since.reset(); + return; + } + + if (!deficit_since) { + deficit_since = now; + return; + } + if (now - *deficit_since < DEFICIT_DURATION) { + return; + } + if (next_probe && now < *next_probe) { + return; + } + + probe_previous_limit = limit; + probe_base_rate = base_rate; + ++limit; + probe_until = now + PROBE_DURATION; + deficit_since.reset(); + output_credit = 0.0f; +} + +void LsfgPacer::DeferEvaluations(Clock::duration amount) { + const auto defer = [amount](std::optional& deadline) { + if (deadline) { + *deadline += amount; + } + }; + defer(stable_until); + defer(probe_until); + defer(next_probe); + deficit_since.reset(); +} + +void LsfgPacer::Stabilize(Clock::time_point now) { + stable_until = now + STABILIZATION_DURATION; + probe_until.reset(); + deficit_since.reset(); + smoothed_interval = 0.0f; + output_credit = 0.0f; +} + +void LsfgPacer::Reset() { + last_frame.reset(); + stable_until.reset(); + probe_until.reset(); + next_probe.reset(); + deficit_since.reset(); + smoothed_interval = 0.0f; + output_credit = 0.0f; + probe_base_rate = 0.0f; + unloaded_base_rate = 0.0f; + issued_generations = 0; + probe_previous_limit = 0; + limit = 0; + probe_failures = 0; +} + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp new file mode 100644 index 000000000..0bc365735 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +namespace lsfg { + +constexpr size_t LSFG_MAX_MULTIPLIER = 4; + +struct LsfgPacerConfig { + uint32_t multiplier{2}; + uint32_t target_rate{}; +}; + +struct LsfgPlan { + size_t generations{}; + bool warm{}; +}; + +class LsfgPacer { +public: + void SetConfig(const LsfgPacerConfig& config_) { + config = config_; + } + + [[nodiscard]] size_t MaxGenerations() const; + + [[nodiscard]] LsfgPlan Plan(size_t capacity); + + void Reset(); + +private: + using Clock = std::chrono::steady_clock; + + void Stabilize(Clock::time_point now); + void DeferEvaluations(Clock::duration amount); + void UpdateLimit(Clock::time_point now, float base_rate, float target_rate, size_t ceiling); + + LsfgPacerConfig config; + + std::optional last_frame; + std::optional stable_until; + std::optional probe_until; + std::optional next_probe; + std::optional deficit_since; + float smoothed_interval{}; + float output_credit{}; + float probe_base_rate{}; + float unloaded_base_rate{}; + size_t issued_generations{}; + size_t probe_previous_limit{}; + size_t limit{}; + uint32_t probe_failures{}; +}; + +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.cpp index 88191259f..874df156f 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.cpp @@ -31,7 +31,7 @@ LsfgShaders::LsfgShaders(const Device& device_, const std::string& cache_path) module_ci.pCode = module.words; VkShaderModule handle = VK_NULL_HANDLE; - if (vkCreateShaderModule(device, &module_ci, nullptr, &handle) != VK_SUCCESS) { + if (vkd.CreateShaderModule(device, &module_ci, nullptr, &handle) != VK_SUCCESS) { SHADER_LOGE("vkCreateShaderModule failed for shader %u", module.id); lsfg_release_modules(&set); Release(); @@ -57,7 +57,7 @@ LsfgShaders::~LsfgShaders() { void LsfgShaders::Release() { if (device != VK_NULL_HANDLE) { for (auto& [id, module] : modules) { - vkDestroyShaderModule(device, module, nullptr); + vkd.DestroyShaderModule(device, module, nullptr); } } modules.clear(); diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.hpp index a0a468172..76a5d3c33 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.hpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include "../vk_dispatch.h" namespace lsfg { diff --git a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp new file mode 100644 index 000000000..1976b7cf7 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp @@ -0,0 +1,229 @@ +#include "vkr_lsfg.h" + +#include "lsfg_chain.hpp" +#include "lsfg_pacer.hpp" +#include "lsfg_shaders.hpp" + +#include +#include +#include + +#include + +#define LSFG_LOGI(...) __android_log_print(ANDROID_LOG_INFO, "VkrLsfg", __VA_ARGS__) +#define LSFG_LOGW(...) __android_log_print(ANDROID_LOG_WARN, "VkrLsfg", __VA_ARGS__) + +namespace { + +constexpr uint64_t LSFG_REQUIRED_FRAMES = 2; +constexpr uint32_t LSFG_RECURRENCE_FRAMES = 2; + +VkImageMemoryBarrier MakeTransitionBarrier(VkImage image, VkAccessFlags src_access, + VkAccessFlags dst_access, VkImageLayout old_layout, + VkImageLayout new_layout) { + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcAccessMask = src_access; + barrier.dstAccessMask = dst_access; + barrier.oldLayout = old_layout; + barrier.newLayout = new_layout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.layerCount = 1; + return barrier; +} + +void CopyPresentedFrame(VkCommandBuffer cmd, VkImage source, lsfg::LsfgImage& destination, + VkExtent2D extent) { + const VkImageMemoryBarrier before[] = { + MakeTransitionBarrier(source, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_LAYOUT_GENERAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL), + MakeTransitionBarrier(destination.Handle(), VK_ACCESS_SHADER_READ_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, destination.Layout(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL), + }; + vkd.CmdPipelineBarrier(cmd, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 2, before); + + VkImageCopy region{}; + region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.srcSubresource.layerCount = 1; + region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.dstSubresource.layerCount = 1; + region.extent = {extent.width, extent.height, 1}; + vkd.CmdCopyImage(cmd, source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, destination.Handle(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + const VkImageMemoryBarrier after[] = { + MakeTransitionBarrier(source, VK_ACCESS_TRANSFER_READ_BIT, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL), + MakeTransitionBarrier(destination.Handle(), VK_ACCESS_TRANSFER_WRITE_BIT, + VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_GENERAL), + }; + vkd.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + 0, 0, nullptr, 0, nullptr, 2, after); + + destination.SetLayout(VK_IMAGE_LAYOUT_GENERAL); +} + +} + +struct VkrLsfg { + lsfg::Device device; + std::string cache_path; + std::unique_ptr shaders; + std::unique_ptr chain; + lsfg::LsfgPacer pacer; + lsfg::LsfgPlan plan{}; + + VkExtent2D built_extent{}; + VkFormat built_format{VK_FORMAT_UNDEFINED}; + float built_flow_scale{}; + float flow_scale{1.0f}; + + uint64_t frame_count{}; + uint64_t last_count{}; + size_t last_generations{}; + uint32_t warm_streak{}; + bool generated{}; + bool unavailable{}; +}; + +VkrLsfg* vkr_lsfg_create(VkDevice device, VkPhysicalDevice physical_device, + const char* cache_path) { + if (device == VK_NULL_HANDLE || physical_device == VK_NULL_HANDLE || cache_path == nullptr) { + return nullptr; + } + + auto* lsfg = new VkrLsfg(); + lsfg->device = lsfg::Device(device, physical_device); + lsfg->cache_path = cache_path; + + lsfg->shaders = std::make_unique(lsfg->device, lsfg->cache_path.c_str()); + if (!lsfg->shaders->IsValid()) { + LSFG_LOGW("shader cache at %s did not yield all modules", cache_path); + delete lsfg; + return nullptr; + } + + LSFG_LOGI("frame generation shaders ready"); + return lsfg; +} + +void vkr_lsfg_destroy(VkrLsfg* lsfg) { + delete lsfg; +} + +void vkr_lsfg_configure(VkrLsfg* lsfg, uint32_t multiplier, uint32_t target_rate, + float flow_scale) { + if (!lsfg) return; + + lsfg::LsfgPacerConfig config; + config.multiplier = multiplier; + config.target_rate = target_rate; + lsfg->pacer.SetConfig(config); + lsfg->flow_scale = std::clamp(flow_scale, 0.25f, 1.0f); +} + +bool vkr_lsfg_needs_rebuild(const VkrLsfg* lsfg, uint32_t width, uint32_t height, + VkFormat format) { + if (!lsfg || lsfg->unavailable) return false; + return !lsfg->chain || lsfg->built_extent.width != width + || lsfg->built_extent.height != height || lsfg->built_format != format + || lsfg->built_flow_scale != lsfg->flow_scale; +} + +bool vkr_lsfg_prepare(VkrLsfg* lsfg, uint32_t width, uint32_t height, VkFormat format) { + if (!lsfg || lsfg->unavailable) return false; + if (width == 0 || height == 0 || format == VK_FORMAT_UNDEFINED) return false; + + if (!vkr_lsfg_needs_rebuild(lsfg, width, height, format)) { + return lsfg->chain && lsfg->chain->Valid(); + } + + lsfg->chain.reset(); + lsfg->chain = std::make_unique( + lsfg->device, *lsfg->shaders, VkExtent2D{width, height}, format, lsfg->flow_scale); + if (!lsfg->chain->Valid()) { + LSFG_LOGW("chain build failed at %ux%u; frame generation unavailable", width, height); + lsfg->chain.reset(); + lsfg->unavailable = true; + return false; + } + + lsfg->built_extent = VkExtent2D{width, height}; + lsfg->built_format = format; + lsfg->built_flow_scale = lsfg->flow_scale; + lsfg->frame_count = 0; + lsfg->warm_streak = 0; + lsfg->generated = false; + lsfg->pacer.Reset(); + LSFG_LOGI("chain built at %ux%u, flow scale %.2f", width, height, + (double)lsfg->built_flow_scale); + return true; +} + +uint32_t vkr_lsfg_plan(VkrLsfg* lsfg, uint32_t capacity) { + if (!lsfg || lsfg->unavailable) return 0; + lsfg->plan = lsfg->pacer.Plan(std::min(capacity, VKR_LSFG_MAX_GENERATIONS)); + return static_cast(lsfg->plan.generations); +} + +void vkr_lsfg_process(VkrLsfg* lsfg, VkCommandBuffer cmd, VkImage source, uint32_t width, + uint32_t height) { + if (!lsfg || !lsfg->chain || !lsfg->chain->Valid()) return; + + const uint64_t count = lsfg->frame_count++; + lsfg->last_count = count; + lsfg->last_generations = lsfg->plan.generations; + + const bool warm = lsfg->plan.warm && count + 1 >= LSFG_REQUIRED_FRAMES; + lsfg->warm_streak = warm ? lsfg->warm_streak + 1 : 0; + lsfg->generated = + warm && lsfg->warm_streak >= LSFG_RECURRENCE_FRAMES && lsfg->plan.generations > 0; + + CopyPresentedFrame(cmd, source, lsfg->chain->Input(count), VkExtent2D{width, height}); + if (warm) { + lsfg->chain->DispatchShared(cmd, count); + } +} + +uint32_t vkr_lsfg_generated_count(const VkrLsfg* lsfg) { + if (!lsfg || !lsfg->generated) return 0; + return static_cast(lsfg->last_generations); +} + +void vkr_lsfg_generate_into(VkrLsfg* lsfg, VkCommandBuffer cmd, uint32_t generation, + uint32_t target_index, VkImage target_image, VkImageView target_view, + uint32_t width, uint32_t height) { + if (!lsfg || !lsfg->chain || !lsfg->chain->Valid()) return; + if (target_index >= lsfg::LSFG_MAX_TARGETS) return; + + lsfg->chain->SetTarget(lsfg->device, lsfg->last_generations, generation, target_index, + target_view); + lsfg->chain->DispatchGeneration(cmd, lsfg->last_count, lsfg->last_generations, generation, + target_index, target_image, VkExtent2D{width, height}); +} + +void vkr_lsfg_forget_targets(VkrLsfg* lsfg) { + if (!lsfg || !lsfg->chain) return; + lsfg->chain->ForgetTargets(); +} + +void vkr_lsfg_reset(VkrLsfg* lsfg) { + if (!lsfg) return; + lsfg->pacer.Reset(); + lsfg->warm_streak = 0; + lsfg->generated = false; + lsfg->plan = {}; +} diff --git a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h new file mode 100644 index 000000000..40d6d2019 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include "../vk_dispatch.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VKR_LSFG_MAX_GENERATIONS 3u + +typedef struct VkrLsfg VkrLsfg; + +VkrLsfg* vkr_lsfg_create(VkDevice device, VkPhysicalDevice physical_device, + const char* cache_path); +void vkr_lsfg_destroy(VkrLsfg* lsfg); + +void vkr_lsfg_configure(VkrLsfg* lsfg, uint32_t multiplier, uint32_t target_rate, + float flow_scale); + +bool vkr_lsfg_needs_rebuild(const VkrLsfg* lsfg, uint32_t width, uint32_t height, + VkFormat format); + +bool vkr_lsfg_prepare(VkrLsfg* lsfg, uint32_t width, uint32_t height, VkFormat format); + +uint32_t vkr_lsfg_plan(VkrLsfg* lsfg, uint32_t capacity); + +void vkr_lsfg_process(VkrLsfg* lsfg, VkCommandBuffer cmd, VkImage source, + uint32_t width, uint32_t height); + +uint32_t vkr_lsfg_generated_count(const VkrLsfg* lsfg); + +void vkr_lsfg_generate_into(VkrLsfg* lsfg, VkCommandBuffer cmd, uint32_t generation, + uint32_t target_index, VkImage target_image, VkImageView target_view, + uint32_t width, uint32_t height); + +// Call whenever the composite targets are recreated: the generate pass caches the last view it +// bound per target slot and would otherwise keep descriptors pointing at destroyed views. +void vkr_lsfg_forget_targets(VkrLsfg* lsfg); + +void vkr_lsfg_reset(VkrLsfg* lsfg); + +#ifdef __cplusplus +} +#endif diff --git a/app/src/main/cpp/winlator/vk/vk_dispatch.c b/app/src/main/cpp/winlator/vk/vk_dispatch.c index 870c1456c..3ca0cfbea 100644 --- a/app/src/main/cpp/winlator/vk/vk_dispatch.c +++ b/app/src/main/cpp/winlator/vk/vk_dispatch.c @@ -112,6 +112,7 @@ bool vkd_load_instance(VkInstance instance) { LOAD(CreatePipelineLayout); LOAD(DestroyPipelineLayout); LOAD(CreateGraphicsPipelines); + LOAD(CreateComputePipelines); LOAD(DestroyPipeline); LOAD(CreateShaderModule); LOAD(DestroyShaderModule); @@ -151,6 +152,8 @@ bool vkd_load_instance(VkInstance instance) { LOAD(CmdPipelineBarrier); LOAD(CmdCopyBufferToImage); LOAD(CmdBlitImage); + LOAD(CmdCopyImage); + LOAD(CmdDispatch); // Queue LOAD(QueueSubmit); diff --git a/app/src/main/cpp/winlator/vk/vk_dispatch.h b/app/src/main/cpp/winlator/vk/vk_dispatch.h index 812b084b6..5bdcbdab9 100644 --- a/app/src/main/cpp/winlator/vk/vk_dispatch.h +++ b/app/src/main/cpp/winlator/vk/vk_dispatch.h @@ -14,6 +14,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct VkDispatch { // Loader-level (resolved via dlsym + vkGetInstanceProcAddr(NULL, ...)) PFN_vkGetInstanceProcAddr GetInstanceProcAddr; @@ -89,6 +93,7 @@ typedef struct VkDispatch { PFN_vkCreatePipelineLayout CreatePipelineLayout; PFN_vkDestroyPipelineLayout DestroyPipelineLayout; PFN_vkCreateGraphicsPipelines CreateGraphicsPipelines; + PFN_vkCreateComputePipelines CreateComputePipelines; PFN_vkDestroyPipeline DestroyPipeline; PFN_vkCreateShaderModule CreateShaderModule; PFN_vkDestroyShaderModule DestroyShaderModule; @@ -128,6 +133,8 @@ typedef struct VkDispatch { PFN_vkCmdPipelineBarrier CmdPipelineBarrier; PFN_vkCmdCopyBufferToImage CmdCopyBufferToImage; PFN_vkCmdBlitImage CmdBlitImage; + PFN_vkCmdCopyImage CmdCopyImage; + PFN_vkCmdDispatch CmdDispatch; // Queue PFN_vkQueueSubmit QueueSubmit; @@ -152,6 +159,10 @@ bool vkd_load_instance(VkInstance instance); // Must be called before dlclose so stale-pointer crashes fault on NULL. void vkd_unload(void); +#ifdef __cplusplus +} +#endif + // Redirect bare `vkFoo` names to the dispatch table. #define vkGetInstanceProcAddr vkd.GetInstanceProcAddr diff --git a/app/src/main/cpp/winlator/vk/vk_renderer.c b/app/src/main/cpp/winlator/vk/vk_renderer.c index 84bd0f230..807c91037 100644 --- a/app/src/main/cpp/winlator/vk/vk_renderer.c +++ b/app/src/main/cpp/winlator/vk/vk_renderer.c @@ -69,6 +69,10 @@ static void destroy_sgsr1_resources(VkRenderer* r); static void destroy_composite_targets(VkRenderer* r); static bool create_composite_targets(VkRenderer* r, uint32_t w, uint32_t h, uint32_t count); static bool composite_format_supported(VkRenderer* r); +static void blit_composite_to_swapchain(VkRenderer* r, VkCommandBuffer cmd, + VkCompositeTarget* src, VkImage dst); +static void create_lsfg(VkRenderer* r); +static void destroy_lsfg(VkRenderer* r); static bool create_quad_vbo(VkRenderer* r); static void destroy_quad_vbo(VkRenderer* r); static bool is_plain_rotation_transform(VkSurfaceTransformFlagBitsKHR transform); @@ -561,6 +565,11 @@ static bool create_command_pool(VkRenderer* r) { VkFrame* f = &r->frames[i]; if (vkAllocateCommandBuffers(r->device, &ai, &f->cmd) != VK_SUCCESS) return false; if (vkCreateSemaphore(r->device, &si, NULL, &f->image_available) != VK_SUCCESS) return false; + for (uint32_t g = 0; g < VKR_LSFG_MAX_GENERATIONS; g++) { + if (vkCreateSemaphore(r->device, &si, NULL, &f->image_available_gen[g]) != VK_SUCCESS) { + return false; + } + } if (vkCreateFence(r->device, &fi, NULL, &f->in_flight) != VK_SUCCESS) return false; } return true; @@ -1240,6 +1249,9 @@ static bool create_swapchain(VkRenderer* r, uint32_t fallback_width, uint32_t fa caps.currentTransform, pre_transform); uint32_t image_count = caps.minImageCount + 1; + // Every generated frame is held as an extra acquired image until it is presented, so ask for + // enough that the pacer is not immediately capped by the default triple buffering. + if (r->framegen_requested) image_count += VKR_LSFG_MAX_GENERATIONS; if (caps.maxImageCount > 0 && image_count > caps.maxImageCount) image_count = caps.maxImageCount; if (image_count > VK_MAX_SWAPCHAIN_IMAGES) image_count = VK_MAX_SWAPCHAIN_IMAGES; @@ -1822,6 +1834,54 @@ static bool create_composite_targets(VkRenderer* r, uint32_t w, uint32_t h, uint return true; } +static void destroy_lsfg(VkRenderer* r) { + if (!r->lsfg) return; + vkr_lsfg_destroy(r->lsfg); + r->lsfg = NULL; + r->framegen_real_frames = 0; + r->framegen_made_frames = 0; +} + +static void create_lsfg(VkRenderer* r) { + if (r->lsfg || !r->lsfg_cache_path || !r->device || !r->physical_device) return; + + r->lsfg = vkr_lsfg_create(r->device, r->physical_device, r->lsfg_cache_path); + if (!r->lsfg) { + VK_LOGW("LSFG shaders unavailable at %s; frame generation stays off", r->lsfg_cache_path); + return; + } + vkr_lsfg_configure(r->lsfg, r->framegen_multiplier ? r->framegen_multiplier : 2u, + r->framegen_target_rate, + r->framegen_flow_scale > 0.0f ? r->framegen_flow_scale : 1.0f); +} + +static void blit_composite_to_swapchain(VkRenderer* r, VkCommandBuffer cmd, + VkCompositeTarget* src, VkImage dst) { + vkr_image_barrier(cmd, dst, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, VK_ACCESS_TRANSFER_WRITE_BIT); + + VkImageBlit blit = {0}; + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.layerCount = 1; + blit.srcOffsets[1].x = (int32_t)src->width; + blit.srcOffsets[1].y = (int32_t)src->height; + blit.srcOffsets[1].z = 1; + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.layerCount = 1; + blit.dstOffsets[1].x = (int32_t)r->swapchain_extent.width; + blit.dstOffsets[1].y = (int32_t)r->swapchain_extent.height; + blit.dstOffsets[1].z = 1; + vkCmdBlitImage(cmd, src->image, VK_IMAGE_LAYOUT_GENERAL, + dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_NEAREST); + + vkr_image_barrier(cmd, dst, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, 0); +} + static bool composite_format_supported(VkRenderer* r) { if (r->swapchain_format == VK_FORMAT_UNDEFINED) return false; @@ -2340,17 +2400,46 @@ static bool record_and_submit_frame(VkRenderer* r) { bool via_composite = r->framegen_requested && r->framegen_supported && r->swapchain_transfer_dst; + + // A generated frame occupies a swapchain image between the previous real frame and this one, + // so the pacer may never claim more than the swapchain can spare while still leaving one + // image for the presentation engine. Each generated frame also needs its own composite target + // to be written into by compute before it is blitted out. + uint32_t framegen_capacity = 0; + if (via_composite && r->lsfg && r->swapchain_image_count > 2) { + framegen_capacity = r->swapchain_image_count - 2; + if (framegen_capacity > VKR_LSFG_MAX_GENERATIONS) { + framegen_capacity = VKR_LSFG_MAX_GENERATIONS; + } + if (VK_FRAMES_IN_FLIGHT + framegen_capacity > VK_MAX_COMPOSITE_TARGETS) { + framegen_capacity = VK_MAX_COMPOSITE_TARGETS - VK_FRAMES_IN_FLIGHT; + } + } + if (via_composite) { + uint32_t composite_needed = VK_FRAMES_IN_FLIGHT + framegen_capacity; bool composite_stale = !r->composite_built + || r->composite_count != composite_needed || r->composite[0].width != r->swapchain_extent.width || r->composite[0].height != r->swapchain_extent.height; - if (composite_stale) { + bool chain_stale = r->lsfg + && vkr_lsfg_needs_rebuild(r->lsfg, r->swapchain_extent.width, + r->swapchain_extent.height, r->swapchain_format); + if (composite_stale || chain_stale) { wait_inflight_frames(r); if (!create_composite_targets(r, r->swapchain_extent.width, - r->swapchain_extent.height, VK_FRAMES_IN_FLIGHT)) { + r->swapchain_extent.height, composite_needed)) { VK_LOGW("Composite targets unavailable; frame generation path disabled"); r->framegen_supported = false; via_composite = false; + framegen_capacity = 0; + } else if (r->lsfg) { + // Composite views are new even when the chain survives, so drop the cached ones. + vkr_lsfg_forget_targets(r->lsfg); + if (!vkr_lsfg_prepare(r->lsfg, r->swapchain_extent.width, + r->swapchain_extent.height, r->swapchain_format)) { + framegen_capacity = 0; + } } } } else if (r->composite_built) { @@ -2358,6 +2447,11 @@ static bool record_and_submit_frame(VkRenderer* r) { destroy_composite_targets(r); } + // Fed every frame even when it returns zero, so the pacer keeps a live interval model. + if (via_composite && r->lsfg) { + vkr_lsfg_plan(r->lsfg, framegen_capacity); + } + VkCompositeTarget* composite = via_composite ? &r->composite[r->frame_index] : NULL; uint32_t image_index = 0; @@ -2502,31 +2596,41 @@ static bool record_and_submit_frame(VkRenderer* r) { vkCmdEndRenderPass(f->cmd); } + uint32_t gen_count = 0; + uint32_t gen_image_index[VKR_LSFG_MAX_GENERATIONS] = {0}; + if (composite) { - VkImage disp_img = r->swapchain_images[image_index]; - vkr_image_barrier(f->cmd, disp_img, - VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, VK_ACCESS_TRANSFER_WRITE_BIT); + if (r->lsfg && framegen_capacity > 0) { + vkr_lsfg_process(r->lsfg, f->cmd, composite->image, + r->swapchain_extent.width, r->swapchain_extent.height); + + uint32_t want = vkr_lsfg_generated_count(r->lsfg); + if (want > framegen_capacity) want = framegen_capacity; + + // Bounded acquire: a busy presentation engine costs us the generated frames for this + // cycle rather than stalling the render thread holding render_mutex. + for (uint32_t g = 0; g < want; g++) { + uint32_t idx = 0; + VkResult ga = vkAcquireNextImageKHR(r->device, r->swapchain, 8000000ULL, + f->image_available_gen[g], VK_NULL_HANDLE, + &idx); + if (ga != VK_SUCCESS && ga != VK_SUBOPTIMAL_KHR) break; + gen_image_index[gen_count++] = idx; + } - VkImageBlit blit = {0}; - blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - blit.srcSubresource.layerCount = 1; - blit.srcOffsets[1].x = (int32_t)composite->width; - blit.srcOffsets[1].y = (int32_t)composite->height; - blit.srcOffsets[1].z = 1; - blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - blit.dstSubresource.layerCount = 1; - blit.dstOffsets[1].x = (int32_t)r->swapchain_extent.width; - blit.dstOffsets[1].y = (int32_t)r->swapchain_extent.height; - blit.dstOffsets[1].z = 1; - vkCmdBlitImage(f->cmd, composite->image, VK_IMAGE_LAYOUT_GENERAL, - disp_img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_NEAREST); + for (uint32_t g = 0; g < gen_count; g++) { + VkCompositeTarget* gt = &r->composite[VK_FRAMES_IN_FLIGHT + g]; + vkr_lsfg_generate_into(r->lsfg, f->cmd, g, VK_FRAMES_IN_FLIGHT + g, + gt->image, gt->view, + r->swapchain_extent.width, r->swapchain_extent.height); + blit_composite_to_swapchain(r, f->cmd, gt, + r->swapchain_images[gen_image_index[g]]); + } + r->framegen_real_frames++; + r->framegen_made_frames += gen_count; + } - vkr_image_barrier(f->cmd, disp_img, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT, 0); + blit_composite_to_swapchain(r, f->cmd, composite, r->swapchain_images[image_index]); } // Blit the final composited image (in PRESENT_SRC after the render pass) into the encoder image. @@ -2597,21 +2701,43 @@ static bool record_and_submit_frame(VkRenderer* r) { vkEndCommandBuffer(f->cmd); - // The mirror's acquire/present-ready semaphores are appended only when capturing this frame. - VkPipelineStageFlags wait_stages[2] = { + // One submit covers the real frame, every generated frame and the recording mirror, so each + // acquired image contributes a wait and each presented image a signal. + #define VK_MAX_FRAME_SEMAPHORES (2 + VKR_LSFG_MAX_GENERATIONS) + VkSemaphore wait_sems[VK_MAX_FRAME_SEMAPHORES]; + VkPipelineStageFlags wait_stages[VK_MAX_FRAME_SEMAPHORES]; + VkSemaphore signal_sems[VK_MAX_FRAME_SEMAPHORES]; + uint32_t wait_count = 0; + uint32_t signal_count = 0; + + wait_sems[wait_count] = f->image_available; + wait_stages[wait_count] = composite ? (VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT) - : VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT }; - VkSemaphore wait_sems[2] = { f->image_available, r->rec.acquire[r->frame_index] }; - VkSemaphore signal_sems[2] = { - render_finished, rec_this_frame ? r->rec.present_ready[rec_index] : VK_NULL_HANDLE }; + : VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + wait_count++; + signal_sems[signal_count++] = render_finished; + + for (uint32_t g = 0; g < gen_count; g++) { + wait_sems[wait_count] = f->image_available_gen[g]; + wait_stages[wait_count] = VK_PIPELINE_STAGE_TRANSFER_BIT; + wait_count++; + signal_sems[signal_count++] = r->swapchain_render_finished[gen_image_index[g]]; + } + + if (rec_this_frame) { + wait_sems[wait_count] = r->rec.acquire[r->frame_index]; + wait_stages[wait_count] = VK_PIPELINE_STAGE_TRANSFER_BIT; + wait_count++; + signal_sems[signal_count++] = r->rec.present_ready[rec_index]; + } + VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; - si.waitSemaphoreCount = rec_this_frame ? 2u : 1u; + si.waitSemaphoreCount = wait_count; si.pWaitSemaphores = wait_sems; si.pWaitDstStageMask = wait_stages; si.commandBufferCount = 1; si.pCommandBuffers = &f->cmd; - si.signalSemaphoreCount = rec_this_frame ? 2u : 1u; + si.signalSemaphoreCount = signal_count; si.pSignalSemaphores = signal_sems; pthread_mutex_lock(&r->queue_mutex); @@ -2640,6 +2766,20 @@ static bool record_and_submit_frame(VkRenderer* r) { pi.pImageIndices = &image_index; pthread_mutex_lock(&r->queue_mutex); + // Generated frames sit between the previous real frame and this one, so they are queued + // first; FIFO then paces them out one vblank apart ahead of the real frame below. + for (uint32_t g = 0; g < gen_count; g++) { + VkPresentInfoKHR gpi = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR}; + gpi.waitSemaphoreCount = 1; + gpi.pWaitSemaphores = &r->swapchain_render_finished[gen_image_index[g]]; + gpi.swapchainCount = 1; + gpi.pSwapchains = &r->swapchain; + gpi.pImageIndices = &gen_image_index[g]; + VkResult gpr = vkQueuePresentKHR(r->graphics_queue, &gpi); + if (gpr != VK_SUCCESS && gpr != VK_SUBOPTIMAL_KHR) { + VK_LOGW("generated frame present failed (%d)", gpr); + } + } VkResult pr = vkQueuePresentKHR(r->graphics_queue, &pi); // Present the mirror separately so its result doesn't disturb the display recreate logic below. if (rec_this_frame) { @@ -2802,6 +2942,10 @@ JNIEXPORT void JNICALL JNI_FN(nativeDestroy)(JNIEnv* env, jclass clazz, jlong ha destroy_record_swapchain(r); destroy_sgsr1_resources(r); destroy_offscreen(r); + destroy_lsfg(r); + free(r->lsfg_cache_path); + r->lsfg_cache_path = NULL; + destroy_swapchain(r); destroy_pipelines(r); destroy_quad_vbo(r); @@ -2809,6 +2953,11 @@ JNIEXPORT void JNICALL JNI_FN(nativeDestroy)(JNIEnv* env, jclass clazz, jlong ha for (uint32_t i = 0; i < VK_FRAMES_IN_FLIGHT; i++) { VkFrame* f = &r->frames[i]; if (f->image_available) vkDestroySemaphore(r->device, f->image_available, NULL); + for (uint32_t g = 0; g < VKR_LSFG_MAX_GENERATIONS; g++) { + if (f->image_available_gen[g]) { + vkDestroySemaphore(r->device, f->image_available_gen[g], NULL); + } + } if (f->in_flight) vkDestroyFence(r->device, f->in_flight, NULL); } @@ -3311,6 +3460,12 @@ JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationEnabled)(JNIEnv* env, jcla pthread_mutex_lock(&r->render_mutex); r->framegen_requested = want; + if (!want) { + wait_inflight_frames(r); + destroy_lsfg(r); + } else if (r->device && r->lsfg_cache_path) { + create_lsfg(r); + } if (r->surface && r->swapchain) { lifecycle_begin(r); if (r->device) vkDeviceWaitIdle(r->device); @@ -3340,6 +3495,59 @@ JNIEXPORT jboolean JNICALL JNI_FN(nativeIsFrameGenerationSupported)(JNIEnv* env, return r->framegen_supported ? JNI_TRUE : JNI_FALSE; } +// The cache is the SPIR-V set already extracted from Lossless.dll by LosslessScaling.java. +// Setting it does not enable frame generation; nativeSetFrameGenerationEnabled does. +JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationShaders)(JNIEnv* env, jclass clazz, + jlong handle, jstring cachePath) { + (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r) return; + + pthread_mutex_lock(&r->render_mutex); + wait_inflight_frames(r); + destroy_lsfg(r); + free(r->lsfg_cache_path); + r->lsfg_cache_path = NULL; + + if (cachePath != NULL) { + const char* path = (*env)->GetStringUTFChars(env, cachePath, NULL); + if (path != NULL) { + r->lsfg_cache_path = strdup(path); + (*env)->ReleaseStringUTFChars(env, cachePath, path); + } + } + if (r->framegen_requested && r->device && r->lsfg_cache_path) create_lsfg(r); + pthread_mutex_unlock(&r->render_mutex); +} + +// multiplier 2..4 fixes the output cadence; a non-zero targetRate hands pacing to the probe +// loop instead, which climbs toward that rate only while it measurably pays off. +JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationMode)(JNIEnv* env, jclass clazz, + jlong handle, jint multiplier, + jint targetRate, jint flowScalePct) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r) return; + + pthread_mutex_lock(&r->render_mutex); + r->framegen_multiplier = multiplier < 2 ? 2u : (uint32_t)multiplier; + r->framegen_target_rate = targetRate < 0 ? 0u : (uint32_t)targetRate; + r->framegen_flow_scale = flowScalePct <= 0 ? 1.0f : (float)flowScalePct / 100.0f; + if (r->lsfg) { + vkr_lsfg_configure(r->lsfg, r->framegen_multiplier, r->framegen_target_rate, + r->framegen_flow_scale); + } + pthread_mutex_unlock(&r->render_mutex); +} + +JNIEXPORT jlong JNICALL JNI_FN(nativeGetGeneratedFrameCount)(JNIEnv* env, jclass clazz, + jlong handle) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r) return 0; + return (jlong)r->framegen_made_frames; +} + // ============================================================ // JNI entry points for Java Texture / GPUImage // ============================================================ diff --git a/app/src/main/cpp/winlator/vk/vk_state.h b/app/src/main/cpp/winlator/vk/vk_state.h index a7f39a10e..182b99248 100644 --- a/app/src/main/cpp/winlator/vk/vk_state.h +++ b/app/src/main/cpp/winlator/vk/vk_state.h @@ -14,6 +14,7 @@ // All vk* calls route through the dispatch table — vk_dispatch.h is the Vulkan header for // this translation unit (do not include directly). #include "vk_dispatch.h" +#include "lsfg/vkr_lsfg.h" #define VK_LOG_TAG "VkRenderer" #define VK_LOGI(...) __android_log_print(ANDROID_LOG_INFO, VK_LOG_TAG, __VA_ARGS__) @@ -198,6 +199,7 @@ typedef struct VkPipelineSet { typedef struct VkFrame { VkSemaphore image_available; + VkSemaphore image_available_gen[VKR_LSFG_MAX_GENERATIONS]; VkFence in_flight; VkCommandBuffer cmd; } VkFrame; @@ -414,6 +416,13 @@ typedef struct VkRenderer { bool framegen_supported; bool framegen_requested; bool swapchain_transfer_dst; + struct VkrLsfg* lsfg; + char* lsfg_cache_path; + uint32_t framegen_multiplier; + uint32_t framegen_target_rate; + float framegen_flow_scale; + uint64_t framegen_real_frames; + uint64_t framegen_made_frames; // record_blit_src adds TRANSFER_SRC usage to the display swapchain (toggled by start/stop recording). bool record_blit_src; diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index 30611db0d..99b9ded7a 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -731,6 +731,50 @@ private String getShortcutSetting(String key, String containerValue) { return shortcut != null ? shortcut.getSettingExtra(key, containerValue) : containerValue; } + // Frame generation needs the extracted Lossless shader cache, a GPU that clears the probe, and + // an explicit opt-in. Any one missing leaves the renderer on its normal single-present path. + private void applyFrameGenerationSettings(VulkanRenderer renderer, Container container) { + if (renderer == null) return; + + String containerValue = container != null ? container.getExtra("frameGen", "0") : "0"; + boolean wanted = "1".equals(getShortcutSetting("frameGen", containerValue)); + if (!wanted) { + renderer.setFrameGenerationEnabled(false); + return; + } + + java.io.File cache = com.winlator.cmod.runtime.display.lsfg.LosslessScaling + .resolveCacheFile(this, true); + if (cache == null) { + Log.w("XServerDisplayActivity", "frameGen requested but no Lossless shader cache"); + renderer.setFrameGenerationEnabled(false); + return; + } + + String containerMultiplier = container != null ? container.getExtra("frameGenMultiplier", "2") : "2"; + String containerTargetRate = container != null ? container.getExtra("frameGenTargetRate", "0") : "0"; + String containerFlowScale = container != null ? container.getExtra("frameGenFlowScale", "100") : "100"; + + int multiplier = parseSettingInt(getShortcutSetting("frameGenMultiplier", containerMultiplier), 2); + int targetRate = parseSettingInt(getShortcutSetting("frameGenTargetRate", containerTargetRate), 0); + int flowScale = parseSettingInt(getShortcutSetting("frameGenFlowScale", containerFlowScale), 100); + + renderer.setFrameGenerationShaders(cache.getAbsolutePath()); + renderer.setFrameGenerationMode(multiplier, targetRate, flowScale); + renderer.setFrameGenerationEnabled(true); + Log.i("XServerDisplayActivity", "Frame generation on: multiplier=" + multiplier + + " targetRate=" + targetRate + " flowScale=" + flowScale); + } + + private static int parseSettingInt(String value, int fallback) { + if (value == null) return fallback; + try { + return Integer.parseInt(value.trim()); + } catch (NumberFormatException e) { + return fallback; + } + } + // paramsJson is nested {"":{uniform:value}} when nested, else the flat legacy map private static class ResolvedReshade { java.util.List loadout; @@ -7413,6 +7457,8 @@ private void setupUI() { String containerSwapRB = container != null ? container.getExtra("swapRB", "0") : "0"; renderer.setSwapRB("1".equals(getShortcutSetting("swapRB", containerSwapRB))); + applyFrameGenerationSettings(renderer, container); + if (shortcut != null || (bootExePath != null && !bootExePath.isEmpty())) { renderer.setUnviewableWMClasses("explorer.exe"); } diff --git a/app/src/main/runtime/display/renderer/VulkanRenderer.java b/app/src/main/runtime/display/renderer/VulkanRenderer.java index 5f3131a72..976bcbe5b 100644 --- a/app/src/main/runtime/display/renderer/VulkanRenderer.java +++ b/app/src/main/runtime/display/renderer/VulkanRenderer.java @@ -217,6 +217,12 @@ public void attachSurface(Surface surface) { if (requestedScaleFilter != SCALE_FILTER_OFF) { nativeSetScaleFilter(nativeHandle, requestedScaleFilter); } + // Shaders and cadence first: enabling is what actually builds the chain. + if (frameGenerationShaderCache != null) { + nativeSetFrameGenerationShaders(nativeHandle, frameGenerationShaderCache); + } + nativeSetFrameGenerationMode(nativeHandle, frameGenerationMultiplier, + frameGenerationTargetRate, frameGenerationFlowScale); if (frameGenerationRequested) { nativeSetFrameGenerationEnabled(nativeHandle, true); } @@ -888,12 +894,31 @@ public void setPresentMode(int mode) { } private boolean frameGenerationRequested = false; + private String frameGenerationShaderCache = null; + private int frameGenerationMultiplier = 2; + private int frameGenerationTargetRate = 0; + private int frameGenerationFlowScale = 100; public void setFrameGenerationEnabled(boolean enabled) { frameGenerationRequested = enabled; if (nativeHandle != 0) nativeSetFrameGenerationEnabled(nativeHandle, enabled); } + public void setFrameGenerationShaders(String cachePath) { + frameGenerationShaderCache = cachePath; + if (nativeHandle != 0) nativeSetFrameGenerationShaders(nativeHandle, cachePath); + } + + public void setFrameGenerationMode(int multiplier, int targetRate, int flowScalePercent) { + frameGenerationMultiplier = Math.max(2, multiplier); + frameGenerationTargetRate = Math.max(0, targetRate); + frameGenerationFlowScale = flowScalePercent <= 0 ? 100 : flowScalePercent; + if (nativeHandle != 0) { + nativeSetFrameGenerationMode(nativeHandle, frameGenerationMultiplier, + frameGenerationTargetRate, frameGenerationFlowScale); + } + } + public boolean isFrameGenerationRequested() { return frameGenerationRequested; } @@ -902,6 +927,10 @@ public boolean isFrameGenerationSupported() { return nativeHandle != 0 && nativeIsFrameGenerationSupported(nativeHandle); } + public long getGeneratedFrameCount() { + return nativeHandle != 0 ? nativeGetGeneratedFrameCount(nativeHandle) : 0L; + } + public static int parsePresentMode(String name) { if (name == null) return PRESENT_MODE_FIFO; switch (name.trim().toLowerCase()) { @@ -957,4 +986,8 @@ private static native long nativeCreate(boolean enableValidationLayers, private static native void nativeSetScaleFilter(long handle, int mode); private static native void nativeSetFrameGenerationEnabled(long handle, boolean enabled); private static native boolean nativeIsFrameGenerationSupported(long handle); + private static native void nativeSetFrameGenerationShaders(long handle, String cachePath); + private static native void nativeSetFrameGenerationMode(long handle, int multiplier, + int targetRate, int flowScalePercent); + private static native long nativeGetGeneratedFrameCount(long handle); } From 998fd4b6256e12713958a046ffab125c79788fa3 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sun, 23 Aug 2026 10:54:04 -0400 Subject: [PATCH 11/35] Stop over-deepening the swapchain for frame generation The swapchain asked for VKR_LSFG_MAX_GENERATIONS extra images whenever frame generation was requested, regardless of the configured multiplier. Under FIFO every surplus image is another frame queued between render and scanout, so at 2x this added two images of pure input lag for no benefit - which is what a game felt on the sticks. Sized to the multiplier instead: 2x asks for one extra image, and only adaptive mode (target rate set, so the pacer may climb to 3 generations) asks for the maximum. --- app/src/main/cpp/winlator/vk/vk_renderer.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/cpp/winlator/vk/vk_renderer.c b/app/src/main/cpp/winlator/vk/vk_renderer.c index 807c91037..0d08fb064 100644 --- a/app/src/main/cpp/winlator/vk/vk_renderer.c +++ b/app/src/main/cpp/winlator/vk/vk_renderer.c @@ -1249,9 +1249,15 @@ static bool create_swapchain(VkRenderer* r, uint32_t fallback_width, uint32_t fa caps.currentTransform, pre_transform); uint32_t image_count = caps.minImageCount + 1; - // Every generated frame is held as an extra acquired image until it is presented, so ask for - // enough that the pacer is not immediately capped by the default triple buffering. - if (r->framegen_requested) image_count += VKR_LSFG_MAX_GENERATIONS; + // One extra image per generated frame, because each is held acquired until it is presented. + // Sized to the configured multiplier rather than the maximum: under FIFO every surplus image + // is another queued frame between render and scanout, which is felt directly as input lag. + if (r->framegen_requested) { + uint32_t generations = r->framegen_multiplier > 1 ? r->framegen_multiplier - 1 : 1; + if (r->framegen_target_rate != 0) generations = VKR_LSFG_MAX_GENERATIONS; + if (generations > VKR_LSFG_MAX_GENERATIONS) generations = VKR_LSFG_MAX_GENERATIONS; + image_count += generations; + } if (caps.maxImageCount > 0 && image_count > caps.maxImageCount) image_count = caps.maxImageCount; if (image_count > VK_MAX_SWAPCHAIN_IMAGES) image_count = VK_MAX_SWAPCHAIN_IMAGES; From d47008465d0c467814931524c8ae8c49dac53369 Mon Sep 17 00:00:00 2001 From: Max Jividen Date: Sun, 23 Aug 2026 11:35:33 -0400 Subject: [PATCH 12/35] Add live frame generation controls to the X server drawer The Screen Effects (FX) pane gains a Frame Generation section: an enable toggle, Adaptive Target chips (Fixed plus every standard rate the panel supports), Multiplier chips shown only in fixed mode, and a Flow Scale slider. Changes apply to the running session immediately and persist to the shortcut when one owns the settings, otherwise to the container. Changing the multiplier now resizes the swapchain, because the number of extra images is derived from it -- without that, raising the multiplier mid-session silently capped generation at whatever the old swapchain could spare, and lowering it left surplus queued frames behind as input lag. Fixes a deadlock that the new toggle would have hit on first use: nativeSetFrameGenerationEnabled called lifecycle_begin while already holding render_mutex, which is not recursive. Nothing toggled frame generation with a live swapchain before, so it never fired. --- app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h | 2 - app/src/main/cpp/winlator/vk/vk_renderer.c | 81 +++++----- app/src/main/res/values/strings.xml | 11 ++ .../display/XServerDisplayActivity.java | 124 ++++++++++++--- .../display/XServerDrawerEffectsPane.kt | 143 ++++++++++++++++++ .../main/runtime/display/XServerDrawerMenu.kt | 42 +++++ .../display/renderer/VulkanRenderer.java | 16 +- 7 files changed, 351 insertions(+), 68 deletions(-) diff --git a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h index 40d6d2019..9231a0c6f 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h +++ b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h @@ -36,8 +36,6 @@ void vkr_lsfg_generate_into(VkrLsfg* lsfg, VkCommandBuffer cmd, uint32_t generat uint32_t target_index, VkImage target_image, VkImageView target_view, uint32_t width, uint32_t height); -// Call whenever the composite targets are recreated: the generate pass caches the last view it -// bound per target slot and would otherwise keep descriptors pointing at destroyed views. void vkr_lsfg_forget_targets(VkrLsfg* lsfg); void vkr_lsfg_reset(VkrLsfg* lsfg); diff --git a/app/src/main/cpp/winlator/vk/vk_renderer.c b/app/src/main/cpp/winlator/vk/vk_renderer.c index 0d08fb064..58a321e21 100644 --- a/app/src/main/cpp/winlator/vk/vk_renderer.c +++ b/app/src/main/cpp/winlator/vk/vk_renderer.c @@ -73,6 +73,8 @@ static void blit_composite_to_swapchain(VkRenderer* r, VkCommandBuffer cmd, VkCompositeTarget* src, VkImage dst); static void create_lsfg(VkRenderer* r); static void destroy_lsfg(VkRenderer* r); +static uint32_t framegen_extra_images(const VkRenderer* r); +static void framegen_rebuild_swapchain(VkRenderer* r); static bool create_quad_vbo(VkRenderer* r); static void destroy_quad_vbo(VkRenderer* r); static bool is_plain_rotation_transform(VkSurfaceTransformFlagBitsKHR transform); @@ -1248,16 +1250,7 @@ static bool create_swapchain(VkRenderer* r, uint32_t fallback_width, uint32_t fa surface_extent.width, surface_extent.height, extent.width, extent.height, caps.currentTransform, pre_transform); - uint32_t image_count = caps.minImageCount + 1; - // One extra image per generated frame, because each is held acquired until it is presented. - // Sized to the configured multiplier rather than the maximum: under FIFO every surplus image - // is another queued frame between render and scanout, which is felt directly as input lag. - if (r->framegen_requested) { - uint32_t generations = r->framegen_multiplier > 1 ? r->framegen_multiplier - 1 : 1; - if (r->framegen_target_rate != 0) generations = VKR_LSFG_MAX_GENERATIONS; - if (generations > VKR_LSFG_MAX_GENERATIONS) generations = VKR_LSFG_MAX_GENERATIONS; - image_count += generations; - } + uint32_t image_count = caps.minImageCount + 1 + framegen_extra_images(r); if (caps.maxImageCount > 0 && image_count > caps.maxImageCount) image_count = caps.maxImageCount; if (image_count > VK_MAX_SWAPCHAIN_IMAGES) image_count = VK_MAX_SWAPCHAIN_IMAGES; @@ -1861,6 +1854,36 @@ static void create_lsfg(VkRenderer* r) { r->framegen_flow_scale > 0.0f ? r->framegen_flow_scale : 1.0f); } +static uint32_t framegen_extra_images(const VkRenderer* r) { + if (!r->framegen_requested) return 0; + if (r->framegen_target_rate != 0) return VKR_LSFG_MAX_GENERATIONS; + + uint32_t generations = r->framegen_multiplier > 1 ? r->framegen_multiplier - 1 : 1; + return generations > VKR_LSFG_MAX_GENERATIONS ? VKR_LSFG_MAX_GENERATIONS : generations; +} + +static void framegen_rebuild_swapchain(VkRenderer* r) { + if (!r->surface || !r->swapchain) return; + + pthread_mutex_lock(&r->scene_mutex); + r->surface_ready = false; + pthread_mutex_unlock(&r->scene_mutex); + + if (r->device) vkDeviceWaitIdle(r->device); + uint32_t fw = r->surface_extent.width; + uint32_t fh = r->surface_extent.height; + destroy_sgsr1_resources(r); + destroy_offscreen(r); + destroy_swapchain(r); + if (!create_swapchain(r, fw, fh)) { + VK_LOGE("Swapchain re-create failed for frame generation"); + return; + } + pthread_mutex_lock(&r->scene_mutex); + r->surface_ready = true; + pthread_mutex_unlock(&r->scene_mutex); +} + static void blit_composite_to_swapchain(VkRenderer* r, VkCommandBuffer cmd, VkCompositeTarget* src, VkImage dst) { vkr_image_barrier(cmd, dst, @@ -2407,10 +2430,6 @@ static bool record_and_submit_frame(VkRenderer* r) { bool via_composite = r->framegen_requested && r->framegen_supported && r->swapchain_transfer_dst; - // A generated frame occupies a swapchain image between the previous real frame and this one, - // so the pacer may never claim more than the swapchain can spare while still leaving one - // image for the presentation engine. Each generated frame also needs its own composite target - // to be written into by compute before it is blitted out. uint32_t framegen_capacity = 0; if (via_composite && r->lsfg && r->swapchain_image_count > 2) { framegen_capacity = r->swapchain_image_count - 2; @@ -2440,7 +2459,6 @@ static bool record_and_submit_frame(VkRenderer* r) { via_composite = false; framegen_capacity = 0; } else if (r->lsfg) { - // Composite views are new even when the chain survives, so drop the cached ones. vkr_lsfg_forget_targets(r->lsfg); if (!vkr_lsfg_prepare(r->lsfg, r->swapchain_extent.width, r->swapchain_extent.height, r->swapchain_format)) { @@ -2453,7 +2471,6 @@ static bool record_and_submit_frame(VkRenderer* r) { destroy_composite_targets(r); } - // Fed every frame even when it returns zero, so the pacer keeps a live interval model. if (via_composite && r->lsfg) { vkr_lsfg_plan(r->lsfg, framegen_capacity); } @@ -2613,8 +2630,6 @@ static bool record_and_submit_frame(VkRenderer* r) { uint32_t want = vkr_lsfg_generated_count(r->lsfg); if (want > framegen_capacity) want = framegen_capacity; - // Bounded acquire: a busy presentation engine costs us the generated frames for this - // cycle rather than stalling the render thread holding render_mutex. for (uint32_t g = 0; g < want; g++) { uint32_t idx = 0; VkResult ga = vkAcquireNextImageKHR(r->device, r->swapchain, 8000000ULL, @@ -2707,8 +2722,6 @@ static bool record_and_submit_frame(VkRenderer* r) { vkEndCommandBuffer(f->cmd); - // One submit covers the real frame, every generated frame and the recording mirror, so each - // acquired image contributes a wait and each presented image a signal. #define VK_MAX_FRAME_SEMAPHORES (2 + VKR_LSFG_MAX_GENERATIONS) VkSemaphore wait_sems[VK_MAX_FRAME_SEMAPHORES]; VkPipelineStageFlags wait_stages[VK_MAX_FRAME_SEMAPHORES]; @@ -2772,8 +2785,6 @@ static bool record_and_submit_frame(VkRenderer* r) { pi.pImageIndices = &image_index; pthread_mutex_lock(&r->queue_mutex); - // Generated frames sit between the previous real frame and this one, so they are queued - // first; FIFO then paces them out one vblank apart ahead of the real frame below. for (uint32_t g = 0; g < gen_count; g++) { VkPresentInfoKHR gpi = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR}; gpi.waitSemaphoreCount = 1; @@ -3472,22 +3483,7 @@ JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationEnabled)(JNIEnv* env, jcla } else if (r->device && r->lsfg_cache_path) { create_lsfg(r); } - if (r->surface && r->swapchain) { - lifecycle_begin(r); - if (r->device) vkDeviceWaitIdle(r->device); - uint32_t fw = r->surface_extent.width; - uint32_t fh = r->surface_extent.height; - destroy_sgsr1_resources(r); - destroy_offscreen(r); - destroy_swapchain(r); - if (!create_swapchain(r, fw, fh)) { - VK_LOGE("Swapchain re-create failed in nativeSetFrameGenerationEnabled"); - } else { - pthread_mutex_lock(&r->scene_mutex); - r->surface_ready = true; - pthread_mutex_unlock(&r->scene_mutex); - } - } + framegen_rebuild_swapchain(r); pthread_mutex_unlock(&r->render_mutex); VK_LOGI("Frame generation composite path %s (supported=%d)", want ? "enabled" : "disabled", (int)r->framegen_supported); @@ -3501,8 +3497,6 @@ JNIEXPORT jboolean JNICALL JNI_FN(nativeIsFrameGenerationSupported)(JNIEnv* env, return r->framegen_supported ? JNI_TRUE : JNI_FALSE; } -// The cache is the SPIR-V set already extracted from Lossless.dll by LosslessScaling.java. -// Setting it does not enable frame generation; nativeSetFrameGenerationEnabled does. JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationShaders)(JNIEnv* env, jclass clazz, jlong handle, jstring cachePath) { (void)clazz; @@ -3526,8 +3520,6 @@ JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationShaders)(JNIEnv* env, jcla pthread_mutex_unlock(&r->render_mutex); } -// multiplier 2..4 fixes the output cadence; a non-zero targetRate hands pacing to the probe -// loop instead, which climbs toward that rate only while it measurably pays off. JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationMode)(JNIEnv* env, jclass clazz, jlong handle, jint multiplier, jint targetRate, jint flowScalePct) { @@ -3536,6 +3528,7 @@ JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationMode)(JNIEnv* env, jclass if (!r) return; pthread_mutex_lock(&r->render_mutex); + const uint32_t previous_images = framegen_extra_images(r); r->framegen_multiplier = multiplier < 2 ? 2u : (uint32_t)multiplier; r->framegen_target_rate = targetRate < 0 ? 0u : (uint32_t)targetRate; r->framegen_flow_scale = flowScalePct <= 0 ? 1.0f : (float)flowScalePct / 100.0f; @@ -3543,6 +3536,10 @@ JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationMode)(JNIEnv* env, jclass vkr_lsfg_configure(r->lsfg, r->framegen_multiplier, r->framegen_target_rate, r->framegen_flow_scale); } + if (framegen_extra_images(r) != previous_images) { + wait_inflight_frames(r); + framegen_rebuild_swapchain(r); + } pthread_mutex_unlock(&r->render_mutex); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f41842ce8..9be472b4e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -765,6 +765,17 @@ E.g. META for META key, \n Upscaler SGSR 1 Edge Sharpness + Frame Generation + Generate Frames + Multiplier + %1$dx + Adaptive Target + Fixed + %1$d fps + Flow Scale + Interpolates between rendered frames with Lossless Scaling. Smoother motion, but one extra frame of input latency. + Climbs toward the target only while it measurably helps, instead of holding a fixed multiplier. + Install Lossless Scaling in container settings to use frame generation. Vivid Vivid Strength Color Effect diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index 99b9ded7a..866ede5ef 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -447,6 +447,11 @@ private boolean isAnyControllerConnected() { private boolean frametimeNumericMode = false; private boolean hudCardExpanded = false; private boolean screenEffectsCardExpanded = false; + private boolean frameGenEnabled = false; + private int frameGenMultiplier = 2; + private int frameGenTargetRate = 0; + private int frameGenFlowScale = 100; + private String frameGenCachePath = null; private boolean sgsrEnabled = false; private boolean sgsrRuntimeEnabled = false; private int sgsrUpscaleMode = 1; @@ -731,39 +736,85 @@ private String getShortcutSetting(String key, String containerValue) { return shortcut != null ? shortcut.getSettingExtra(key, containerValue) : containerValue; } - // Frame generation needs the extracted Lossless shader cache, a GPU that clears the probe, and - // an explicit opt-in. Any one missing leaves the renderer on its normal single-present path. private void applyFrameGenerationSettings(VulkanRenderer renderer, Container container) { if (renderer == null) return; String containerValue = container != null ? container.getExtra("frameGen", "0") : "0"; - boolean wanted = "1".equals(getShortcutSetting("frameGen", containerValue)); - if (!wanted) { - renderer.setFrameGenerationEnabled(false); - return; - } + String containerMultiplier = container != null ? container.getExtra("frameGenMultiplier", "2") : "2"; + String containerTargetRate = container != null ? container.getExtra("frameGenTargetRate", "0") : "0"; + String containerFlowScale = container != null ? container.getExtra("frameGenFlowScale", "100") : "100"; + + frameGenEnabled = "1".equals(getShortcutSetting("frameGen", containerValue)); + frameGenMultiplier = clampFrameGenMultiplier( + parseSettingInt(getShortcutSetting("frameGenMultiplier", containerMultiplier), 2)); + frameGenTargetRate = Math.max(0, + parseSettingInt(getShortcutSetting("frameGenTargetRate", containerTargetRate), 0)); + frameGenFlowScale = clampFrameGenFlowScale( + parseSettingInt(getShortcutSetting("frameGenFlowScale", containerFlowScale), 100)); java.io.File cache = com.winlator.cmod.runtime.display.lsfg.LosslessScaling .resolveCacheFile(this, true); - if (cache == null) { - Log.w("XServerDisplayActivity", "frameGen requested but no Lossless shader cache"); + frameGenCachePath = cache != null ? cache.getAbsolutePath() : null; + if (frameGenCachePath == null) { + if (frameGenEnabled) { + Log.w("XServerDisplayActivity", "frameGen requested but no Lossless shader cache"); + } + frameGenEnabled = false; + } + + applyFrameGeneration(renderer); + } + + private void applyFrameGeneration(VulkanRenderer renderer) { + if (renderer == null) return; + + if (!frameGenEnabled || frameGenCachePath == null) { renderer.setFrameGenerationEnabled(false); return; } - String containerMultiplier = container != null ? container.getExtra("frameGenMultiplier", "2") : "2"; - String containerTargetRate = container != null ? container.getExtra("frameGenTargetRate", "0") : "0"; - String containerFlowScale = container != null ? container.getExtra("frameGenFlowScale", "100") : "100"; + renderer.setFrameGenerationShaders(frameGenCachePath); + renderer.setFrameGenerationMode(frameGenMultiplier, frameGenTargetRate, frameGenFlowScale); + renderer.setFrameGenerationEnabled(true); + Log.i("XServerDisplayActivity", "Frame generation on: multiplier=" + frameGenMultiplier + + " targetRate=" + frameGenTargetRate + " flowScale=" + frameGenFlowScale); + } - int multiplier = parseSettingInt(getShortcutSetting("frameGenMultiplier", containerMultiplier), 2); - int targetRate = parseSettingInt(getShortcutSetting("frameGenTargetRate", containerTargetRate), 0); - int flowScale = parseSettingInt(getShortcutSetting("frameGenFlowScale", containerFlowScale), 100); + private void applyFrameGenerationLive() { + applyFrameGeneration(xServerView != null ? xServerView.getRenderer() : null); + saveFrameGenerationSettings(); + renderDrawerMenu(); + } - renderer.setFrameGenerationShaders(cache.getAbsolutePath()); - renderer.setFrameGenerationMode(multiplier, targetRate, flowScale); - renderer.setFrameGenerationEnabled(true); - Log.i("XServerDisplayActivity", "Frame generation on: multiplier=" + multiplier - + " targetRate=" + targetRate + " flowScale=" + flowScale); + private void saveFrameGenerationSettings() { + if (shortcut != null) { + if (frameGenEnabled) { + shortcut.putExtra("frameGen", "1"); + shortcut.putExtra("frameGenMultiplier", String.valueOf(frameGenMultiplier)); + shortcut.putExtra("frameGenTargetRate", String.valueOf(frameGenTargetRate)); + shortcut.putExtra("frameGenFlowScale", String.valueOf(frameGenFlowScale)); + } else { + shortcut.putExtra("frameGen", null); + shortcut.putExtra("frameGenMultiplier", null); + shortcut.putExtra("frameGenTargetRate", null); + shortcut.putExtra("frameGenFlowScale", null); + } + shortcut.saveData(); + } else if (container != null) { + container.putExtra("frameGen", frameGenEnabled ? "1" : "0"); + container.putExtra("frameGenMultiplier", String.valueOf(frameGenMultiplier)); + container.putExtra("frameGenTargetRate", String.valueOf(frameGenTargetRate)); + container.putExtra("frameGenFlowScale", String.valueOf(frameGenFlowScale)); + container.saveData(); + } + } + + private static int clampFrameGenMultiplier(int value) { + return Math.max(2, Math.min(4, value)); + } + + private static int clampFrameGenFlowScale(int value) { + return Math.max(25, Math.min(100, value)); } private static int parseSettingInt(String value, int fallback) { @@ -4321,6 +4372,14 @@ private void renderDrawerMenu() { MangoHudView.lockedFromPrefs(preferences) ); + state = XServerDrawerMenuKt.withFrameGenState( + state, + frameGenCachePath != null, + frameGenEnabled, + frameGenMultiplier, + frameGenTargetRate, + frameGenFlowScale); + // Always-present "Output" tab (live controls while swapped, otherwise a Cast entry point). if (externalDisplayController != null) { boolean swapped = externalDisplayController.isSwapActive(); @@ -4692,6 +4751,31 @@ public void onOutputCastClick() { launchWirelessDisplayPicker(); } + @Override + public void onFrameGenEnabledChanged(boolean enabled) { + if (enabled && frameGenCachePath == null) return; + frameGenEnabled = enabled; + applyFrameGenerationLive(); + } + + @Override + public void onFrameGenMultiplierSelected(int multiplier) { + frameGenMultiplier = clampFrameGenMultiplier(multiplier); + applyFrameGenerationLive(); + } + + @Override + public void onFrameGenTargetRateSelected(int rate) { + frameGenTargetRate = Math.max(0, rate); + applyFrameGenerationLive(); + } + + @Override + public void onFrameGenFlowScaleChanged(int percent) { + frameGenFlowScale = clampFrameGenFlowScale(percent); + applyFrameGenerationLive(); + } + @Override public void onSGSREnabledChanged(boolean enabled) { boolean wasEnabled = sgsrEnabled; diff --git a/app/src/main/runtime/display/XServerDrawerEffectsPane.kt b/app/src/main/runtime/display/XServerDrawerEffectsPane.kt index fda9ebd6a..9e1ec16a0 100644 --- a/app/src/main/runtime/display/XServerDrawerEffectsPane.kt +++ b/app/src/main/runtime/display/XServerDrawerEffectsPane.kt @@ -202,6 +202,10 @@ internal fun ScreenEffectsPaneContent( .padding(horizontal = (12f * paneScale).dp, vertical = (12f * paneScale).dp), verticalArrangement = Arrangement.spacedBy((10f * paneScale).dp), ) { + FrameGenerationSection(state = state, listener = listener, paneScale = paneScale) + + ThinDivider() + Column(verticalArrangement = Arrangement.spacedBy((8f * paneScale).dp)) { PaneSectionLabel(stringResource(R.string.shortcuts_graphics_sgsr_full_title)) NavBooleanRow( @@ -464,3 +468,142 @@ internal fun ScreenEffectsPaneContent( } } } + +@Composable +private fun FrameGenerationSection( + state: XServerDrawerState, + listener: XServerDrawerActionListener, + paneScale: Float, +) { + Column(verticalArrangement = Arrangement.spacedBy((8f * paneScale).dp)) { + PaneSectionLabel(stringResource(R.string.session_drawer_frame_generation)) + + if (!state.frameGenAvailable) { + FrameGenNote(stringResource(R.string.session_drawer_frame_generation_missing), paneScale) + } else { + NavBooleanRow( + title = stringResource(R.string.session_drawer_frame_generation_enable), + checked = state.frameGenEnabled, + onCheckedChange = listener::onFrameGenEnabledChanged, + ) + + FrameGenNote(stringResource(R.string.session_drawer_frame_generation_note), paneScale) + + AnimatedVisibility( + visible = state.frameGenEnabled, + enter = + expandVertically( + animationSpec = tween(durationMillis = 220, easing = FastOutSlowInEasing), + expandFrom = Alignment.Top, + ) + fadeIn(animationSpec = tween(durationMillis = 160, easing = FastOutSlowInEasing)), + exit = + shrinkVertically( + animationSpec = tween(durationMillis = 180, easing = FastOutSlowInEasing), + shrinkTowards = Alignment.Top, + ) + fadeOut(animationSpec = tween(durationMillis = 120, easing = FastOutSlowInEasing)), + ) { + Column(verticalArrangement = Arrangement.spacedBy((8f * paneScale).dp)) { + FrameGenFieldLabel( + stringResource(R.string.session_drawer_frame_generation_target), + paneScale, + ) + + val rates = + remember(state.maxRefreshRate, state.frameGenTargetRate) { + (FrameGenTargetRates.filter { it <= state.maxRefreshRate } + + listOfNotNull(state.frameGenTargetRate.takeIf { it > 0 })) + .distinct() + .sorted() + } + + ChipFlow { + HUDToggleChip( + label = stringResource(R.string.session_drawer_frame_generation_target_off), + checked = state.frameGenTargetRate == 0, + onClick = { listener.onFrameGenTargetRateSelected(0) }, + modifier = Modifier.paneNavItem( + cornerRadius = (16f * paneScale).dp, + onActivate = { listener.onFrameGenTargetRateSelected(0) }, + ), + ) + rates.forEach { rate -> + HUDToggleChip( + label = stringResource( + R.string.session_drawer_frame_generation_target_value, + rate, + ), + checked = state.frameGenTargetRate == rate, + onClick = { listener.onFrameGenTargetRateSelected(rate) }, + modifier = Modifier.paneNavItem( + cornerRadius = (16f * paneScale).dp, + onActivate = { listener.onFrameGenTargetRateSelected(rate) }, + ), + ) + } + } + + if (state.frameGenTargetRate == 0) { + FrameGenFieldLabel( + stringResource(R.string.session_drawer_frame_generation_multiplier), + paneScale, + ) + ChipFlow { + FrameGenMultipliers.forEach { multiplier -> + HUDToggleChip( + label = stringResource( + R.string.session_drawer_frame_generation_multiplier_value, + multiplier, + ), + checked = state.frameGenMultiplier == multiplier, + onClick = { listener.onFrameGenMultiplierSelected(multiplier) }, + modifier = Modifier.paneNavItem( + cornerRadius = (16f * paneScale).dp, + onActivate = { listener.onFrameGenMultiplierSelected(multiplier) }, + ), + ) + } + } + } else { + FrameGenNote( + stringResource(R.string.session_drawer_frame_generation_target_note), + paneScale, + ) + } + + NavSliderRow( + label = stringResource(R.string.session_drawer_frame_generation_flow_scale), + valueText = "${state.frameGenFlowScale}%", + value = state.frameGenFlowScale.toFloat(), + valueRange = FrameGenFlowScaleMin.toFloat()..FrameGenFlowScaleMax.toFloat(), + steps = (FrameGenFlowScaleMax - FrameGenFlowScaleMin) / 5 - 1, + onValueChange = { + listener.onFrameGenFlowScaleChanged( + it.roundToInt().coerceIn(FrameGenFlowScaleMin, FrameGenFlowScaleMax), + ) + }, + ) + } + } + } + } +} + +@Composable +private fun FrameGenFieldLabel(text: String, paneScale: Float) { + Text( + text = text, + color = DrawerTextSecondary, + fontSize = (12f * paneScale).sp, + fontWeight = FontWeight.Medium, + ) +} + +@Composable +private fun FrameGenNote(text: String, paneScale: Float) { + Text( + text = text, + color = DrawerTextSecondary, + fontSize = (11f * paneScale).sp, + lineHeight = (15f * paneScale).sp, + ) +} diff --git a/app/src/main/runtime/display/XServerDrawerMenu.kt b/app/src/main/runtime/display/XServerDrawerMenu.kt index f6b4b68ba..7d19d85a9 100644 --- a/app/src/main/runtime/display/XServerDrawerMenu.kt +++ b/app/src/main/runtime/display/XServerDrawerMenu.kt @@ -563,6 +563,11 @@ private val ActionCardSpacing = 8.dp private const val ActionCardRevealStaggerMs = 28 private const val ActionCardRevealDurationMs = 220 +internal val FrameGenMultipliers = listOf(2, 3, 4) +internal val FrameGenTargetRates = listOf(60, 90, 120, 144, 165) +internal const val FrameGenFlowScaleMin = 25 +internal const val FrameGenFlowScaleMax = 100 + data class XServerDrawerItem( val itemId: Int, val title: String, @@ -608,6 +613,11 @@ data class XServerDrawerState( val gyroscopeCardExpanded: Boolean = false, val fpsLimit: Int = 0, val maxRefreshRate: Int = 60, + val frameGenAvailable: Boolean = false, + val frameGenEnabled: Boolean = false, + val frameGenMultiplier: Int = 2, + val frameGenTargetRate: Int = 0, + val frameGenFlowScale: Int = 100, val screenEffectsCardExpanded: Boolean = false, val sgsrEnabled: Boolean = false, val sgsrSharpness: Int = 100, @@ -1015,6 +1025,14 @@ interface XServerDrawerActionListener { fun onFPSLimitChanged(limit: Int) + fun onFrameGenEnabledChanged(enabled: Boolean) + + fun onFrameGenMultiplierSelected(multiplier: Int) + + fun onFrameGenTargetRateSelected(rate: Int) + + fun onFrameGenFlowScaleChanged(percent: Int) + fun onScreenEffectsCardExpandedChanged(expanded: Boolean) fun onOutputResolutionSelected(index: Int) @@ -1462,6 +1480,30 @@ fun setupXServerDrawerComposeView( } } +fun withFrameGenState( + state: XServerDrawerState, + available: Boolean, + enabled: Boolean, + multiplier: Int, + targetRate: Int, + flowScale: Int, +): XServerDrawerState = + state.copy( + items = + if (!enabled) { + state.items + } else { + state.items.map { + if (it.itemId == R.id.main_menu_screen_effects) it.copy(active = true) else it + } + }, + frameGenAvailable = available, + frameGenEnabled = enabled, + frameGenMultiplier = multiplier.coerceIn(2, FrameGenMultipliers.last()), + frameGenTargetRate = targetRate.coerceAtLeast(0), + frameGenFlowScale = flowScale.coerceIn(FrameGenFlowScaleMin, FrameGenFlowScaleMax), + ) + // Append the always-present "Output" tab item and its state to the drawer state. fun withOutputState( state: XServerDrawerState, diff --git a/app/src/main/runtime/display/renderer/VulkanRenderer.java b/app/src/main/runtime/display/renderer/VulkanRenderer.java index 976bcbe5b..209af6443 100644 --- a/app/src/main/runtime/display/renderer/VulkanRenderer.java +++ b/app/src/main/runtime/display/renderer/VulkanRenderer.java @@ -217,7 +217,6 @@ public void attachSurface(Surface surface) { if (requestedScaleFilter != SCALE_FILTER_OFF) { nativeSetScaleFilter(nativeHandle, requestedScaleFilter); } - // Shaders and cadence first: enabling is what actually builds the chain. if (frameGenerationShaderCache != null) { nativeSetFrameGenerationShaders(nativeHandle, frameGenerationShaderCache); } @@ -905,14 +904,23 @@ public void setFrameGenerationEnabled(boolean enabled) { } public void setFrameGenerationShaders(String cachePath) { + if (java.util.Objects.equals(frameGenerationShaderCache, cachePath)) return; frameGenerationShaderCache = cachePath; if (nativeHandle != 0) nativeSetFrameGenerationShaders(nativeHandle, cachePath); } public void setFrameGenerationMode(int multiplier, int targetRate, int flowScalePercent) { - frameGenerationMultiplier = Math.max(2, multiplier); - frameGenerationTargetRate = Math.max(0, targetRate); - frameGenerationFlowScale = flowScalePercent <= 0 ? 100 : flowScalePercent; + int wantMultiplier = Math.max(2, multiplier); + int wantTargetRate = Math.max(0, targetRate); + int wantFlowScale = flowScalePercent <= 0 ? 100 : flowScalePercent; + if (wantMultiplier == frameGenerationMultiplier + && wantTargetRate == frameGenerationTargetRate + && wantFlowScale == frameGenerationFlowScale) { + return; + } + frameGenerationMultiplier = wantMultiplier; + frameGenerationTargetRate = wantTargetRate; + frameGenerationFlowScale = wantFlowScale; if (nativeHandle != 0) { nativeSetFrameGenerationMode(nativeHandle, frameGenerationMultiplier, frameGenerationTargetRate, frameGenerationFlowScale); From 841278b7c9de806edd0ed5d8a61092ae1e42758a Mon Sep 17 00:00:00 2001 From: Max Jividen Date: Sun, 23 Aug 2026 11:58:57 -0400 Subject: [PATCH 13/35] Stop the on-screen sticks repainting the whole overlay, and auto-import Lossless Scaling Holding a stick called invalidate() on every motion sample, repainting the full-screen overlay and redrawing every element in the profile, not just the stick. gfxinfo showed 63% janky frames with Slow-issue-draw-commands accounting for nearly all of them and a 13ms median GPU time for the overlay layer alone. Frame generation is what made it obvious rather than merely wasteful: with the GPU near saturation the overlay kept winning and the game's real frames starved, so the stick glided while the world stuttered. The stick now uses the bounded invalidateControlElement() the class already had for this, and onDraw skips elements outside the damage rect. Per-game Graphics settings gain a Frame Generation card. Opening it detects a Steam-downloaded Lossless Scaling and imports its shaders with no prompting, looking at the recorded install path, the resolved app dir, every configured install root, and finally the container drives. --- app/src/main/feature/library/GameSettings.kt | 165 ++++++++++++++++++ .../feature/library/LosslessAutoImport.kt | 55 ++++++ .../ShortcutSettingsComposeDialog.kt | 30 ++++ app/src/main/res/values/strings.xml | 6 + .../input/controls/ControlElement.java | 2 +- .../runtime/input/ui/InputControlsView.java | 17 +- 6 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 app/src/main/feature/library/LosslessAutoImport.kt diff --git a/app/src/main/feature/library/GameSettings.kt b/app/src/main/feature/library/GameSettings.kt index 59da10135..ec5a7d2e6 100644 --- a/app/src/main/feature/library/GameSettings.kt +++ b/app/src/main/feature/library/GameSettings.kt @@ -58,6 +58,7 @@ import androidx.compose.material.icons.outlined.Code import androidx.compose.material.icons.outlined.Extension import androidx.compose.material.icons.outlined.AutoAwesome import androidx.compose.material.icons.outlined.Monitor +import androidx.compose.material.icons.outlined.Speed import androidx.compose.material.icons.outlined.Science import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.SportsEsports @@ -144,6 +145,7 @@ import com.winlator.cmod.runtime.reshade.ReshadeLoadout import com.winlator.cmod.runtime.reshade.ReshadeManager import com.winlator.cmod.shared.theme.GameSettingsStyle import com.winlator.cmod.runtime.wine.WineThemeManager +import com.winlator.cmod.runtime.display.lsfg.LosslessScaling import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -419,6 +421,13 @@ class GameSettingsStateHolder { val sgsrUpscaleMode = mutableIntStateOf(1) val sgsrSharpness = mutableIntStateOf(100) + val frameGenEnabled = mutableStateOf(false) + val frameGenMultiplier = mutableIntStateOf(2) + val frameGenTargetRate = mutableIntStateOf(0) + val frameGenFlowScale = mutableIntStateOf(100) + val frameGenShaderState = mutableIntStateOf(FRAMEGEN_SHADERS_CHECKING) + val frameGenSourceName = mutableStateOf("") + // scanned drop-in pool + ordered loadout; saved as a reshadeLoadout array plus nested reshadeParams object. val reshadeEffects = mutableStateOf>(emptyList()) val reshadeLoadout = ReshadeLoadoutState() @@ -649,6 +658,15 @@ private data class SidebarSection( val labelResId: Int ) +const val FRAMEGEN_SHADERS_CHECKING = 0 +const val FRAMEGEN_SHADERS_READY = 1 +const val FRAMEGEN_SHADERS_IMPORTING = 2 +const val FRAMEGEN_SHADERS_MISSING = 3 +const val FRAMEGEN_SHADERS_FAILED = 4 + +val FrameGenMultiplierOptions = listOf(2, 3, 4) +val FrameGenTargetOptions = listOf(0, 60, 90, 120, 144) + private const val SEC_GENERAL = 0 private const val SEC_STEAM = 1 private const val SEC_DISPLAY = 2 @@ -1707,6 +1725,10 @@ private fun DisplaySection( Spacer(Modifier.height(SettingItemGap)) + FrameGenerationCard(state) + + Spacer(Modifier.height(SettingItemGap)) + val dxWrapperEntries = state.dxWrapperEntries.value val dxWrapperIdx = state.selectedDxWrapper.intValue val selectedDxWrapper = if (dxWrapperIdx in dxWrapperEntries.indices) @@ -1721,6 +1743,149 @@ private fun DisplaySection( } +@Composable +private fun FrameGenerationCard(state: GameSettingsStateHolder) { + val context = LocalContext.current + + LaunchedEffect(Unit) { + if (state.frameGenShaderState.intValue != FRAMEGEN_SHADERS_CHECKING) return@LaunchedEffect + state.frameGenShaderState.intValue = FRAMEGEN_SHADERS_IMPORTING + val (status, source) = + withContext(Dispatchers.IO) { + if (LosslessScaling.isInstalled(context)) { + LosslessScaling.STATUS_OK to null + } else { + val dll = LosslessAutoImport.findDll(context) + if (dll == null) { + LosslessScaling.STATUS_NOT_INSTALLED to null + } else { + LosslessScaling.installFrom(context, dll) to dll + } + } + } + state.frameGenSourceName.value = source?.parentFile?.name.orEmpty() + state.frameGenShaderState.intValue = + when (status) { + LosslessScaling.STATUS_OK -> FRAMEGEN_SHADERS_READY + LosslessScaling.STATUS_NOT_INSTALLED -> FRAMEGEN_SHADERS_MISSING + else -> FRAMEGEN_SHADERS_FAILED + } + } + + val shaders = state.frameGenShaderState.intValue + val ready = shaders == FRAMEGEN_SHADERS_READY + val enabled = ready && state.frameGenEnabled.value + val targetRate = state.frameGenTargetRate.intValue + + SettingGroup { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + Icons.Outlined.Speed, + contentDescription = null, + tint = AccentBlue, + modifier = Modifier.size(SettingIconSize), + ) + Spacer(Modifier.width(8.dp)) + Text( + stringResource(R.string.settings_frame_generation_title), + color = TextPrimary, + fontSize = SettingValueSize, + fontWeight = FontWeight.Medium, + ) + } + + Spacer(Modifier.height(SettingItemGap)) + + SettingSwitch( + label = stringResource(R.string.session_drawer_frame_generation_enable), + checked = enabled, + enabled = ready, + onCheckedChange = { state.frameGenEnabled.value = it }, + ) + + Text( + text = + when (shaders) { + FRAMEGEN_SHADERS_IMPORTING, FRAMEGEN_SHADERS_CHECKING -> + stringResource(R.string.settings_frame_generation_importing) + FRAMEGEN_SHADERS_READY -> + if (state.frameGenSourceName.value.isEmpty()) { + stringResource(R.string.settings_frame_generation_ready) + } else { + stringResource( + R.string.settings_frame_generation_imported, + state.frameGenSourceName.value, + ) + } + FRAMEGEN_SHADERS_FAILED -> stringResource(R.string.settings_frame_generation_failed) + else -> stringResource(R.string.settings_frame_generation_not_found) + }, + color = TextSecondary, + fontSize = SettingLabelSize, + lineHeight = SettingLabelSize * 1.4f, + ) + + if (enabled) { + Spacer(Modifier.height(SettingItemGap)) + + SettingPairRow { + Box(Modifier.weight(1f)) { + SettingDropdown( + label = stringResource(R.string.session_drawer_frame_generation_target), + entries = + FrameGenTargetOptions.map { rate -> + if (rate == 0) { + stringResource(R.string.session_drawer_frame_generation_target_off) + } else { + stringResource(R.string.session_drawer_frame_generation_target_value, rate) + } + }, + selectedIndex = FrameGenTargetOptions.indexOf(targetRate).coerceAtLeast(0), + onSelected = { state.frameGenTargetRate.intValue = FrameGenTargetOptions[it] }, + ) + } + Box(Modifier.weight(1f)) { + SettingDropdown( + label = stringResource(R.string.session_drawer_frame_generation_multiplier), + entries = + FrameGenMultiplierOptions.map { multiplier -> + stringResource( + R.string.session_drawer_frame_generation_multiplier_value, + multiplier, + ) + }, + selectedIndex = + FrameGenMultiplierOptions + .indexOf(state.frameGenMultiplier.intValue) + .coerceAtLeast(0), + onSelected = { + state.frameGenMultiplier.intValue = FrameGenMultiplierOptions[it] + }, + enabled = targetRate == 0, + ) + } + } + + Spacer(Modifier.height(SettingItemGap)) + + SettingSlider( + label = stringResource(R.string.session_drawer_frame_generation_flow_scale), + value = state.frameGenFlowScale.intValue, + range = 25..100, + steps = 14, + onValueChange = { state.frameGenFlowScale.intValue = it }, + ) + + Text( + text = stringResource(R.string.session_drawer_frame_generation_note), + color = TextSecondary, + fontSize = SettingLabelSize, + lineHeight = SettingLabelSize * 1.4f, + ) + } + } +} + @Composable private fun GraphicsDriverConfigCard( state: GameSettingsStateHolder, diff --git a/app/src/main/feature/library/LosslessAutoImport.kt b/app/src/main/feature/library/LosslessAutoImport.kt new file mode 100644 index 000000000..4669b240a --- /dev/null +++ b/app/src/main/feature/library/LosslessAutoImport.kt @@ -0,0 +1,55 @@ +package com.winlator.cmod.feature.library + +import android.content.Context +import com.winlator.cmod.feature.stores.steam.service.SteamService +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.display.lsfg.LosslessScaling +import java.io.File + +object LosslessAutoImport { + const val STEAM_APP_ID = 993090 + + private const val DLL_NAME = "Lossless.dll" + private const val INSTALL_DIR_NAME = "Lossless Scaling" + + fun findDll(context: Context): File? { + for (dir in steamCandidateDirs()) { + val dll = File(dir, DLL_NAME) + if (dll.isFile && dll.canRead()) return dll + } + return runCatching { + LosslessScaling.findInContainers(ContainerManager(context).containers).firstOrNull() + }.getOrNull() + } + + fun importIfNeeded(context: Context): Int { + if (LosslessScaling.isInstalled(context)) return LosslessScaling.STATUS_OK + val dll = findDll(context) ?: return LosslessScaling.STATUS_NOT_INSTALLED + return LosslessScaling.installFrom(context, dll) + } + + private fun steamCandidateDirs(): List { + val dirs = LinkedHashSet() + + runCatching { SteamService.getInstalledApp(STEAM_APP_ID)?.installPath } + .getOrNull() + ?.takeIf { it.isNotBlank() } + ?.let { dirs += File(it) } + + runCatching { SteamService.getAppDirPath(STEAM_APP_ID) } + .getOrNull() + ?.takeIf { it.isNotBlank() } + ?.let { dirs += File(it) } + + runCatching { SteamService.allInstallPaths } + .getOrDefault(emptyList()) + .forEach { base -> if (base.isNotBlank()) dirs += File(base, INSTALL_DIR_NAME) } + + runCatching { SteamService.defaultAppInstallPath } + .getOrNull() + ?.takeIf { it.isNotBlank() } + ?.let { dirs += File(it, INSTALL_DIR_NAME) } + + return dirs.toList() + } +} diff --git a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt index 888144f6a..896b1819e 100644 --- a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt +++ b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt @@ -518,6 +518,24 @@ class ShortcutSettingsComposeDialog private constructor( ?.coerceIn(0, 100) ?: 100 + state.frameGenEnabled.value = + getShortcutSetting("frameGen", container.getExtra("frameGen", "0")) == "1" + state.frameGenMultiplier.intValue = + getShortcutSetting("frameGenMultiplier", container.getExtra("frameGenMultiplier", "2")) + .toIntOrNull() + ?.coerceIn(2, 4) + ?: 2 + state.frameGenTargetRate.intValue = + getShortcutSetting("frameGenTargetRate", container.getExtra("frameGenTargetRate", "0")) + .toIntOrNull() + ?.coerceAtLeast(0) + ?: 0 + state.frameGenFlowScale.intValue = + getShortcutSetting("frameGenFlowScale", container.getExtra("frameGenFlowScale", "100")) + .toIntOrNull() + ?.coerceIn(25, 100) + ?: 100 + // shortcut override else container value; legacy single reshadeEffect/flat params migrated in parse val reshadeEffects = com.winlator.cmod.runtime.reshade.ReshadeManager.scanEffects(context) state.reshadeEffects.value = reshadeEffects @@ -1297,6 +1315,18 @@ class ShortcutSettingsComposeDialog private constructor( shortcut.putExtra("sgsrSharpness", null) } + if (state.frameGenEnabled.value) { + shortcut.putExtra("frameGen", "1") + shortcut.putExtra("frameGenMultiplier", state.frameGenMultiplier.intValue.coerceIn(2, 4).toString()) + shortcut.putExtra("frameGenTargetRate", state.frameGenTargetRate.intValue.coerceAtLeast(0).toString()) + shortcut.putExtra("frameGenFlowScale", state.frameGenFlowScale.intValue.coerceIn(25, 100).toString()) + } else { + shortcut.putExtra("frameGen", null) + shortcut.putExtra("frameGenMultiplier", null) + shortcut.putExtra("frameGenTargetRate", null) + shortcut.putExtra("frameGenFlowScale", null) + } + // saveOverride not putExtra: putExtra leaves hasContainerOverride false, so a reshade-only shortcut gets use_container_defaults=1 and reads back the container's extras run { val loadoutJson = state.reshadeLoadout.loadoutJsonOrNull() ?: "" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9be472b4e..9c38297fa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -776,6 +776,12 @@ E.g. META for META key, \n Interpolates between rendered frames with Lossless Scaling. Smoother motion, but one extra frame of input latency. Climbs toward the target only while it measurably helps, instead of holding a fixed multiplier. Install Lossless Scaling in container settings to use frame generation. + Frame Generation + Looking for Lossless Scaling… + Lossless Scaling shaders ready. + Imported from %1$s. + Lossless Scaling was not found. Install it from Steam and reopen this screen to import it automatically. + Lossless.dll was found but its shaders could not be read. Verify the Steam files and try again. Vivid Vivid Strength Color Effect diff --git a/app/src/main/runtime/input/controls/ControlElement.java b/app/src/main/runtime/input/controls/ControlElement.java index 9dbb77b66..1632a23dc 100644 --- a/app/src/main/runtime/input/controls/ControlElement.java +++ b/app/src/main/runtime/input/controls/ControlElement.java @@ -3982,7 +3982,7 @@ public boolean handleTouchMove(int pointerId, float x, float y) { } } - inputControlsView.invalidate(); + inputControlsView.invalidateControlElement(this); } else if (type == Type.TRACKPAD) { Binding firstBinding = getBindingAt(0); if (firstBinding.isGamepad()) { diff --git a/app/src/main/runtime/input/ui/InputControlsView.java b/app/src/main/runtime/input/ui/InputControlsView.java index 74c5f8ad9..1753d0fc3 100644 --- a/app/src/main/runtime/input/ui/InputControlsView.java +++ b/app/src/main/runtime/input/ui/InputControlsView.java @@ -91,6 +91,8 @@ public class InputControlsView extends View { private boolean batchingUpdates = false; + private final Rect clipBounds = new Rect(); + public boolean isBatchingUpdates() { return batchingUpdates; } @@ -253,7 +255,9 @@ protected synchronized void onDraw(Canvas canvas) { if (profile != null && (showTouchscreenControls || editMode) && !isFocusedOnStick()) { if (!profile.isElementsLoaded()) profile.loadElements(this); + boolean clipped = !editMode && canvas.getClipBounds(clipBounds); for (ControlElement element : profile.getElements()) { + if (clipped && !intersectsDamage(element, clipBounds)) continue; element.draw(canvas); } } @@ -261,6 +265,17 @@ protected synchronized void onDraw(Canvas canvas) { super.onDraw(canvas); } + private boolean intersectsDamage(ControlElement element, Rect damage) { + Rect box = element.getBoundingBox(); + int padding = elementDamagePadding(); + return damage.intersects( + box.left - padding, box.top - padding, box.right + padding, box.bottom + padding); + } + + private int elementDamagePadding() { + return Math.max(getSnappingSize() * 4, 32); + } + public void resetStickPosition() { if (stickElement != null) { Rect boundingBox = stickElement.getBoundingBox(); @@ -965,7 +980,7 @@ public void invalidateControlElement(ControlElement element) { if (element == null) return; Rect dirtyRect = element.getBoundingBox(); - int padding = Math.max(getSnappingSize() * 4, 32); + int padding = elementDamagePadding(); postInvalidateOnAnimation( dirtyRect.left - padding, dirtyRect.top - padding, From d0bec9cdc822793ce20108853f7b5f5bf3499e03 Mon Sep 17 00:00:00 2001 From: Max Jividen Date: Sun, 23 Aug 2026 12:57:37 -0400 Subject: [PATCH 14/35] Gate Lossless import on ownership, follow Steam updates, and allow picking the DLL Import now requires a Steam license for 993090 or a recorded install of it, so a stray Lossless.dll left in a container no longer unlocks the shaders on its own. Without either the card says so and offers nothing to press. The cache header already carried the source size and an FNV-1a of the DLL, but nothing could read them back, so a cache once built was never rebuilt and a Steam update to Lossless left stale shaders in place forever. lsfg_cache_matches_source compares both against the DLL on disk and the card re-imports when they diverge, reporting it as an update rather than a first import. Verified on the tablet: a cache matches the DLL it came from and stops matching after a single byte of that DLL changes. Selecting the DLL by hand goes through the same ownership gate and the installFrom(Uri) path that has been unused since it was written. --- app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c | 27 +++++ app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h | 3 + app/src/main/cpp/winlator/vk/lsfg/lsfg_jni.c | 15 +++ app/src/main/feature/library/GameSettings.kt | 100 ++++++++++++++---- .../feature/library/LosslessAutoImport.kt | 44 +++++++- app/src/main/res/values/strings.xml | 3 + .../runtime/display/lsfg/LosslessScaling.java | 9 ++ 7 files changed, 176 insertions(+), 25 deletions(-) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c index 3033c71ae..c30d04575 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c @@ -653,6 +653,33 @@ LsfgStatus lsfg_build_cache(const char* dll_path, const char* cache_path, bool p return status; } +LsfgStatus lsfg_cache_matches_source(const char* cache_path, const char* dll_path, + bool* out_matches) { + if (!cache_path || !dll_path || !out_matches) return LSFG_CACHE_UNUSABLE; + *out_matches = false; + + FILE* file = fopen(cache_path, "rb"); + if (!file) return LSFG_NOT_INSTALLED; + + CacheHeader header; + const bool header_read = fread(&header, sizeof(header), 1, file) == 1; + fclose(file); + if (!header_read || header.magic != CACHE_MAGIC || header.version != CACHE_VERSION) { + return LSFG_CACHE_UNUSABLE; + } + + PeImage image; + int fd = -1; + size_t mapped_size = 0; + if (!pe_open(dll_path, &image, &fd, &mapped_size)) return LSFG_NOT_INSTALLED; + + *out_matches = header.source_size == (uint64_t)image.size + && header.source_hash == fnv1a64(image.data, image.size); + + pe_close(&image, fd, mapped_size); + return LSFG_OK; +} + LsfgStatus lsfg_load_modules(const char* cache_path, LsfgModuleSet* out_set) { if (!cache_path || !out_set) return LSFG_CACHE_UNUSABLE; memset(out_set, 0, sizeof(*out_set)); diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h index a062c89eb..4dfc75d07 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h @@ -51,6 +51,9 @@ LsfgStatus lsfg_build_cache(const char* dll_path, const char* cache_path, bool p LsfgStatus lsfg_load_modules(const char* cache_path, LsfgModuleSet* out_set); +LsfgStatus lsfg_cache_matches_source(const char* cache_path, const char* dll_path, + bool* out_matches); + void lsfg_release_modules(LsfgModuleSet* set); const uint32_t* lsfg_find_module(const LsfgModuleSet* set, uint32_t id, uint32_t* out_word_count); diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_jni.c b/app/src/main/cpp/winlator/vk/lsfg/lsfg_jni.c index f1355efa3..23119f549 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_jni.c +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_jni.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -37,6 +38,20 @@ JNIEXPORT jint JNICALL LSFG_FN(nativeBuildCache)(JNIEnv* env, jclass clazz, jstr return (jint)status; } +JNIEXPORT jboolean JNICALL LSFG_FN(nativeCacheMatchesSource)(JNIEnv* env, jclass clazz, + jstring cachePath, jstring dllPath) { + (void)clazz; + char* cache = copy_utf(env, cachePath); + char* dll = copy_utf(env, dllPath); + bool matches = false; + if (cache && dll) { + if (lsfg_cache_matches_source(cache, dll, &matches) != LSFG_OK) matches = false; + } + free(cache); + free(dll); + return matches ? JNI_TRUE : JNI_FALSE; +} + JNIEXPORT jint JNICALL LSFG_FN(nativeInspectCache)(JNIEnv* env, jclass clazz, jstring cachePath) { (void)clazz; char* cache = copy_utf(env, cachePath); diff --git a/app/src/main/feature/library/GameSettings.kt b/app/src/main/feature/library/GameSettings.kt index ec5a7d2e6..72f76c456 100644 --- a/app/src/main/feature/library/GameSettings.kt +++ b/app/src/main/feature/library/GameSettings.kt @@ -59,6 +59,8 @@ import androidx.compose.material.icons.outlined.Extension import androidx.compose.material.icons.outlined.AutoAwesome import androidx.compose.material.icons.outlined.Monitor import androidx.compose.material.icons.outlined.Speed +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.material.icons.outlined.Science import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.SportsEsports @@ -663,6 +665,8 @@ const val FRAMEGEN_SHADERS_READY = 1 const val FRAMEGEN_SHADERS_IMPORTING = 2 const val FRAMEGEN_SHADERS_MISSING = 3 const val FRAMEGEN_SHADERS_FAILED = 4 +const val FRAMEGEN_SHADERS_UPDATED = 5 +const val FRAMEGEN_SHADERS_NOT_OWNED = 6 val FrameGenMultiplierOptions = listOf(2, 3, 4) val FrameGenTargetOptions = listOf(0, 60, 90, 120, 144) @@ -1750,30 +1754,26 @@ private fun FrameGenerationCard(state: GameSettingsStateHolder) { LaunchedEffect(Unit) { if (state.frameGenShaderState.intValue != FRAMEGEN_SHADERS_CHECKING) return@LaunchedEffect state.frameGenShaderState.intValue = FRAMEGEN_SHADERS_IMPORTING - val (status, source) = - withContext(Dispatchers.IO) { - if (LosslessScaling.isInstalled(context)) { - LosslessScaling.STATUS_OK to null - } else { - val dll = LosslessAutoImport.findDll(context) - if (dll == null) { - LosslessScaling.STATUS_NOT_INSTALLED to null - } else { - LosslessScaling.installFrom(context, dll) to dll - } - } - } - state.frameGenSourceName.value = source?.parentFile?.name.orEmpty() - state.frameGenShaderState.intValue = - when (status) { - LosslessScaling.STATUS_OK -> FRAMEGEN_SHADERS_READY - LosslessScaling.STATUS_NOT_INSTALLED -> FRAMEGEN_SHADERS_MISSING - else -> FRAMEGEN_SHADERS_FAILED - } + val outcome = withContext(Dispatchers.IO) { LosslessAutoImport.sync(context) } + state.frameGenSourceName.value = outcome.sourceName + state.frameGenShaderState.intValue = frameGenStateFor(outcome.result) } + val scope = rememberCoroutineScope() + val picker = + rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> + if (uri == null) return@rememberLauncherForActivityResult + state.frameGenShaderState.intValue = FRAMEGEN_SHADERS_IMPORTING + scope.launch { + val outcome = withContext(Dispatchers.IO) { LosslessAutoImport.importFrom(context, uri) } + state.frameGenSourceName.value = outcome.sourceName + state.frameGenShaderState.intValue = frameGenStateFor(outcome.result) + } + } + val shaders = state.frameGenShaderState.intValue - val ready = shaders == FRAMEGEN_SHADERS_READY + val ready = shaders == FRAMEGEN_SHADERS_READY || shaders == FRAMEGEN_SHADERS_UPDATED + val busy = shaders == FRAMEGEN_SHADERS_IMPORTING || shaders == FRAMEGEN_SHADERS_CHECKING val enabled = ready && state.frameGenEnabled.value val targetRate = state.frameGenTargetRate.intValue @@ -1817,6 +1817,12 @@ private fun FrameGenerationCard(state: GameSettingsStateHolder) { state.frameGenSourceName.value, ) } + FRAMEGEN_SHADERS_UPDATED -> + stringResource( + R.string.settings_frame_generation_updated, + state.frameGenSourceName.value, + ) + FRAMEGEN_SHADERS_NOT_OWNED -> stringResource(R.string.settings_frame_generation_not_owned) FRAMEGEN_SHADERS_FAILED -> stringResource(R.string.settings_frame_generation_failed) else -> stringResource(R.string.settings_frame_generation_not_found) }, @@ -1825,6 +1831,15 @@ private fun FrameGenerationCard(state: GameSettingsStateHolder) { lineHeight = SettingLabelSize * 1.4f, ) + if (shaders != FRAMEGEN_SHADERS_NOT_OWNED) { + Spacer(Modifier.height(SettingItemGap)) + SettingActionButton( + label = stringResource(R.string.settings_frame_generation_locate), + enabled = !busy, + onClick = { picker.launch(arrayOf("*/*")) }, + ) + } + if (enabled) { Spacer(Modifier.height(SettingItemGap)) @@ -5503,6 +5518,49 @@ private fun EmulatorSectionHeader(title: String, usage: String?) { } } +@Composable +private fun SettingActionButton( + label: String, + enabled: Boolean = true, + onClick: () -> Unit, +) { + val alpha = if (enabled) 1f else 0.4f + Box( + modifier = Modifier + .alpha(alpha) + .clip(RoundedCornerShape(10.dp)) + .background(AccentBlue.copy(alpha = 0.08f)) + .border(1.dp, AccentBlue.copy(alpha = 0.2f), RoundedCornerShape(10.dp)) + .then( + if (enabled) { + Modifier + .paneNavItem(cornerRadius = 10.dp, onActivate = onClick, highlightColor = NavHighlight) + .clickable { onClick() } + } else { + Modifier + } + ) + .padding(horizontal = 12.dp, vertical = 7.dp) + ) { + Text( + text = label, + color = AccentBlue, + fontSize = SettingValueSize, + fontWeight = FontWeight.Medium, + maxLines = 1 + ) + } +} + +private fun frameGenStateFor(result: Int): Int = + when (result) { + LosslessAutoImport.RESULT_READY, LosslessAutoImport.RESULT_IMPORTED -> FRAMEGEN_SHADERS_READY + LosslessAutoImport.RESULT_UPDATED -> FRAMEGEN_SHADERS_UPDATED + LosslessAutoImport.RESULT_NOT_OWNED -> FRAMEGEN_SHADERS_NOT_OWNED + LosslessAutoImport.RESULT_NOT_FOUND -> FRAMEGEN_SHADERS_MISSING + else -> FRAMEGEN_SHADERS_FAILED + } + @Composable private fun SettingGroup( modifier: Modifier = Modifier, diff --git a/app/src/main/feature/library/LosslessAutoImport.kt b/app/src/main/feature/library/LosslessAutoImport.kt index 4669b240a..fdcf8bfd5 100644 --- a/app/src/main/feature/library/LosslessAutoImport.kt +++ b/app/src/main/feature/library/LosslessAutoImport.kt @@ -1,6 +1,7 @@ package com.winlator.cmod.feature.library import android.content.Context +import android.net.Uri import com.winlator.cmod.feature.stores.steam.service.SteamService import com.winlator.cmod.runtime.container.ContainerManager import com.winlator.cmod.runtime.display.lsfg.LosslessScaling @@ -9,9 +10,24 @@ import java.io.File object LosslessAutoImport { const val STEAM_APP_ID = 993090 + const val RESULT_READY = 0 + const val RESULT_IMPORTED = 1 + const val RESULT_UPDATED = 2 + const val RESULT_NOT_OWNED = 3 + const val RESULT_NOT_FOUND = 4 + const val RESULT_FAILED = 5 + private const val DLL_NAME = "Lossless.dll" private const val INSTALL_DIR_NAME = "Lossless Scaling" + class Outcome(val result: Int, val sourceName: String) + + fun isOwned(): Boolean { + val licensed = runCatching { SteamService.getPkgInfoOf(STEAM_APP_ID) != null }.getOrDefault(false) + if (licensed) return true + return runCatching { SteamService.getInstalledApp(STEAM_APP_ID) != null }.getOrDefault(false) + } + fun findDll(context: Context): File? { for (dir in steamCandidateDirs()) { val dll = File(dir, DLL_NAME) @@ -22,10 +38,30 @@ object LosslessAutoImport { }.getOrNull() } - fun importIfNeeded(context: Context): Int { - if (LosslessScaling.isInstalled(context)) return LosslessScaling.STATUS_OK - val dll = findDll(context) ?: return LosslessScaling.STATUS_NOT_INSTALLED - return LosslessScaling.installFrom(context, dll) + fun sync(context: Context): Outcome { + if (!isOwned()) { + return Outcome(if (LosslessScaling.isInstalled(context)) RESULT_READY else RESULT_NOT_OWNED, "") + } + + val dll = findDll(context) + if (dll == null) { + return Outcome(if (LosslessScaling.isInstalled(context)) RESULT_READY else RESULT_NOT_FOUND, "") + } + + val name = dll.parentFile?.name.orEmpty() + val installed = LosslessScaling.isInstalled(context) + if (installed && !LosslessScaling.isCacheStale(context, dll)) return Outcome(RESULT_READY, name) + + val status = LosslessScaling.installFrom(context, dll) + if (status != LosslessScaling.STATUS_OK) return Outcome(RESULT_FAILED, name) + return Outcome(if (installed) RESULT_UPDATED else RESULT_IMPORTED, name) + } + + fun importFrom(context: Context, uri: Uri): Outcome { + if (!isOwned()) return Outcome(RESULT_NOT_OWNED, "") + val status = LosslessScaling.installFrom(context, uri) + if (status != LosslessScaling.STATUS_OK) return Outcome(RESULT_FAILED, "") + return Outcome(RESULT_IMPORTED, uri.lastPathSegment?.substringAfterLast('/').orEmpty()) } private fun steamCandidateDirs(): List { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9c38297fa..53c1e01f3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -782,6 +782,9 @@ E.g. META for META key, \n Imported from %1$s. Lossless Scaling was not found. Install it from Steam and reopen this screen to import it automatically. Lossless.dll was found but its shaders could not be read. Verify the Steam files and try again. + Updated from %1$s. + Frame generation uses shaders from Lossless Scaling. Buy it on Steam with the signed-in account to enable this. + Select Lossless.dll… Vivid Vivid Strength Color Effect diff --git a/app/src/main/runtime/display/lsfg/LosslessScaling.java b/app/src/main/runtime/display/lsfg/LosslessScaling.java index 23b65dd7c..4b1676118 100644 --- a/app/src/main/runtime/display/lsfg/LosslessScaling.java +++ b/app/src/main/runtime/display/lsfg/LosslessScaling.java @@ -87,6 +87,13 @@ public static int getStatus(Context context, boolean preferFp16) { return nativeInspectCache(cache.getAbsolutePath()); } + public static boolean isCacheStale(Context context, File dll) { + if (dll == null || !dll.isFile()) return false; + File cache = resolveCacheFile(context, true); + if (cache == null) return true; + return !nativeCacheMatchesSource(cache.getAbsolutePath(), dll.getAbsolutePath()); + } + public static int getVariant(Context context, boolean preferFp16) { File cache = resolveCacheFile(context, preferFp16); if (cache == null) return VARIANT_NONE; @@ -240,5 +247,7 @@ private static native int nativeBuildCache(String dllPath, String cachePath, private static native int nativeCacheVariant(String cachePath); + private static native boolean nativeCacheMatchesSource(String cachePath, String dllPath); + private static native boolean nativeSupportsFrameGeneration(String driverName, Context context); } From 5e5b933ab75a3e2026c172c74621d7dc104d3756 Mon Sep 17 00:00:00 2001 From: Max Jividen Date: Sun, 23 Aug 2026 13:32:40 -0400 Subject: [PATCH 15/35] Deliver the requested multiplier at 3x and 4x, and re-import Lossless on update 3x and 4x never reached their multiplier because the fixed-cadence path ran through eden's adaptive gating on the way out. Any frame slower than 100ms hit MINIMUM_BASE_RATE and called Stabilize(), which blanks generation for a full second, and the bridge then needs two consecutive warm frames before it starts again. A game at 25-30fps with ordinary hitches spends most of its time inside that blackout, and raising the multiplier adds GPU load that produces more slow frames, so asking for more gave less. Measured on the tablet at 4x: game 25 -> compositor 40 before, game 19 -> compositor 77 after, settling at 103. Fixed mode now returns the configured cadence directly and only skips a frame when the gap exceeds 250ms, which is a real discontinuity worth re-priming the history for rather than a hitch. Adaptive mode keeps eden's probe logic, which is what it was written for. The swapchain was never the constraint here - the surface reports minImageCount 5, so capacity sat at the 3-generation ceiling throughout. Logged it, along with dropped generated-frame acquires, because guessing at those numbers cost a build. Lossless now re-imports when its Steam download finishes and again at game launch, so an update reaches the shaders without opening the settings card. --- .../main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp | 19 ++++++++++++------- app/src/main/cpp/winlator/vk/vk_renderer.c | 11 ++++++++++- app/src/main/cpp/winlator/vk/vk_state.h | 1 + .../service/SteamServiceDownloadFinalize.kt | 10 ++++++++++ .../display/XServerDisplayActivity.java | 7 +++++++ 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp index a77ea8532..ec1158956 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp @@ -15,6 +15,7 @@ using Clock = std::chrono::steady_clock; constexpr float INTERVAL_SMOOTHING = 0.25f; constexpr float MINIMUM_BASE_RATE = 10.0f; +constexpr float FIXED_DISCONTINUITY_SECONDS = 0.25f; constexpr float BURST_CADENCE_RATIO = 3.0f; constexpr float BURST_TARGET_RATIO = 2.0f; constexpr float PROBE_THROUGHPUT_TOLERANCE = 0.95f; @@ -76,6 +77,17 @@ LsfgPlan LsfgPacer::Plan(size_t capacity) { const float target_rate = static_cast(config.target_rate); + if (target_rate == 0.0f) { + output_credit = 0.0f; + if (interval_seconds > FIXED_DISCONTINUITY_SECONDS) { + issued_generations = 0; + return {}; + } + limit = ceiling; + issued_generations = limit; + return LsfgPlan{limit, limit > 0}; + } + if (smoothed_interval > 0.0f) { float burst_threshold = BURST_CADENCE_RATIO / smoothed_interval; if (target_rate > 0.0f) { @@ -113,13 +125,6 @@ LsfgPlan LsfgPacer::Plan(size_t capacity) { stable_until.reset(); } - if (target_rate == 0.0f) { - limit = std::min(MaxGenerations(), ceiling); - output_credit = 0.0f; - issued_generations = limit; - return LsfgPlan{limit, limit > 0}; - } - UpdateLimit(now, 1.0f / smoothed_interval, target_rate, ceiling); const size_t allowed = std::min(limit, ceiling); diff --git a/app/src/main/cpp/winlator/vk/vk_renderer.c b/app/src/main/cpp/winlator/vk/vk_renderer.c index 58a321e21..c09d22289 100644 --- a/app/src/main/cpp/winlator/vk/vk_renderer.c +++ b/app/src/main/cpp/winlator/vk/vk_renderer.c @@ -1310,6 +1310,8 @@ static bool create_swapchain(VkRenderer* r, uint32_t fallback_width, uint32_t fa } r->swapchain_image_count = got; r->framegen_supported = transfer_dst_capable && composite_format_supported(r); + VK_LOGI("Swapchain images requested=%u actual=%u caps.min=%u caps.max=%u framegen_extra=%u", + image_count, got, caps.minImageCount, caps.maxImageCount, framegen_extra_images(r)); if (!r->pipelines_built) { if (!create_pipelines(r)) goto fail; @@ -2635,7 +2637,14 @@ static bool record_and_submit_frame(VkRenderer* r) { VkResult ga = vkAcquireNextImageKHR(r->device, r->swapchain, 8000000ULL, f->image_available_gen[g], VK_NULL_HANDLE, &idx); - if (ga != VK_SUCCESS && ga != VK_SUBOPTIMAL_KHR) break; + if (ga != VK_SUCCESS && ga != VK_SUBOPTIMAL_KHR) { + if (r->framegen_acquire_misses++ % 120 == 0) { + VK_LOGW("Generated frame %u/%u dropped: acquire returned %d " + "(swapchain images=%u capacity=%u)", + g + 1, want, (int)ga, r->swapchain_image_count, framegen_capacity); + } + break; + } gen_image_index[gen_count++] = idx; } diff --git a/app/src/main/cpp/winlator/vk/vk_state.h b/app/src/main/cpp/winlator/vk/vk_state.h index 182b99248..6bc03ba42 100644 --- a/app/src/main/cpp/winlator/vk/vk_state.h +++ b/app/src/main/cpp/winlator/vk/vk_state.h @@ -423,6 +423,7 @@ typedef struct VkRenderer { float framegen_flow_scale; uint64_t framegen_real_frames; uint64_t framegen_made_frames; + uint64_t framegen_acquire_misses; // record_blit_src adds TRANSFER_SRC usage to the display swapchain (toggled by start/stop recording). bool record_blit_src; diff --git a/app/src/main/feature/stores/steam/service/SteamServiceDownloadFinalize.kt b/app/src/main/feature/stores/steam/service/SteamServiceDownloadFinalize.kt index 57437a064..d2362a648 100644 --- a/app/src/main/feature/stores/steam/service/SteamServiceDownloadFinalize.kt +++ b/app/src/main/feature/stores/steam/service/SteamServiceDownloadFinalize.kt @@ -142,6 +142,7 @@ import okhttp3.FormBody import okhttp3.Request import org.json.JSONArray import org.json.JSONObject +import com.winlator.cmod.feature.library.LosslessAutoImport import timber.log.Timber import java.io.File import java.io.IOException @@ -531,6 +532,15 @@ internal suspend fun SteamService.Companion.completeAppDownload( downloadInfo.updateStatus(DownloadPhase.COMPLETE) PluviaApp.events.emit(AndroidEvent.LibraryInstallStatusChanged(downloadInfo.gameId)) + if (downloadInfo.gameId == LosslessAutoImport.STEAM_APP_ID) { + instance?.let { context -> + Thread { + val outcome = runCatching { LosslessAutoImport.sync(context) }.getOrNull() + Timber.i("Lossless Scaling finished downloading; shader import result=${outcome?.result}") + }.start() + } + } + downloadInfo.clearPersistedBytesDownloaded(appDirPath, sync = true) // Notify the coordinator to advance the cross-store queue and persist COMPLETE. runBlocking { diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index 866ede5ef..930986bf9 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -752,6 +752,13 @@ private void applyFrameGenerationSettings(VulkanRenderer renderer, Container con frameGenFlowScale = clampFrameGenFlowScale( parseSettingInt(getShortcutSetting("frameGenFlowScale", containerFlowScale), 100)); + if (frameGenEnabled) { + int result = com.winlator.cmod.feature.library.LosslessAutoImport.INSTANCE.sync(this).getResult(); + if (result != com.winlator.cmod.feature.library.LosslessAutoImport.RESULT_READY) { + Log.i("XServerDisplayActivity", "Lossless shader sync at launch: result=" + result); + } + } + java.io.File cache = com.winlator.cmod.runtime.display.lsfg.LosslessScaling .resolveCacheFile(this, true); frameGenCachePath = cache != null ? cache.getAbsolutePath() : null; From 70e7856341c8f276b0d4e75a8509a228d5a9db87 Mon Sep 17 00:00:00 2001 From: Max Jividen Date: Sun, 23 Aug 2026 13:39:33 -0400 Subject: [PATCH 16/35] Translate the frame generation strings into every shipped locale All 22 locales were exactly 20 strings behind the base after the frame generation work; each now matches at 2349. Product and file names stay untranslated, as do the fps unit and the multiplier format. --- app/src/main/res/values-b+es+419/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-da/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-de/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-es/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-fi/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-fr/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-hi/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-it/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-ja/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-ko/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-no/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-pl/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-pt-rBR/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-pt/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-ro/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-ru/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-sv/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-th/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-tr/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-uk/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-zh-rCN/strings.xml | 20 ++++++++++++++++++++ app/src/main/res/values-zh-rTW/strings.xml | 20 ++++++++++++++++++++ 22 files changed, 440 insertions(+) diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 98f34dc3f..afa671cac 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -2493,4 +2493,24 @@ Ruta instalada: Verificando la descarga… Procesando… Sistema + Generación de fotogramas + Generar fotogramas + Multiplicador + %1$dx + Objetivo adaptativo + Fijo + %1$d fps + Escala de flujo + Interpola entre fotogramas renderizados con Lossless Scaling. Movimiento más fluido, pero un fotograma extra de latencia de entrada. + Sube hacia el objetivo solo mientras ayude de forma medible, en lugar de mantener un multiplicador fijo. + Instala Lossless Scaling en los ajustes del contenedor para usar la generación de fotogramas. + Generación de fotogramas + Buscando Lossless Scaling… + Sombreadores de Lossless Scaling listos. + Importado desde %1$s. + No se encontró Lossless Scaling. Instálalo desde Steam y vuelve a abrir esta pantalla para importarlo automáticamente. + Se encontró Lossless.dll, pero no se pudieron leer sus sombreadores. Verifica los archivos de Steam e inténtalo de nuevo. + Actualizado desde %1$s. + La generación de fotogramas usa sombreadores de Lossless Scaling. Cómpralo en Steam con la cuenta con la que has iniciado sesión para activarlo. + Seleccionar Lossless.dll… diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index a33c3be49..94a600487 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -2495,4 +2495,24 @@ Installeret sti: Bekræfter download… Arbejder… System + Billedgenerering + Generér billeder + Multiplikator + %1$dx + Adaptivt mål + Fast + %1$d fps + Flow-skala + Interpolerer mellem gengivne billeder med Lossless Scaling. Blødere bevægelse, men ét ekstra billede med inputforsinkelse. + Stiger mod målet, kun mens det målbart hjælper, i stedet for at fastholde en fast multiplikator. + Installér Lossless Scaling i containerindstillingerne for at bruge billedgenerering. + Billedgenerering + Leder efter Lossless Scaling… + Lossless Scaling-shaders er klar. + Importeret fra %1$s. + Lossless Scaling blev ikke fundet. Installér det fra Steam, og åbn denne skærm igen for at importere det automatisk. + Lossless.dll blev fundet, men dens shaders kunne ikke læses. Kontrollér Steam-filerne, og prøv igen. + Opdateret fra %1$s. + Billedgenerering bruger shaders fra Lossless Scaling. Køb det på Steam med den tilmeldte konto for at aktivere dette. + Vælg Lossless.dll… diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 445d285ff..2a490411e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -2495,4 +2495,24 @@ Installierter Pfad: Download wird geprüft… Wird ausgeführt… System + Frame-Generierung + Frames generieren + Multiplikator + %1$dx + Adaptives Ziel + Fest + %1$d fps + Flow-Skalierung + Interpoliert zwischen gerenderten Frames mit Lossless Scaling. Flüssigere Bewegung, aber ein zusätzlicher Frame Eingabeverzögerung. + Steigt nur dann in Richtung Ziel, wenn es messbar hilft, statt einen festen Multiplikator zu halten. + Installiere Lossless Scaling in den Container-Einstellungen, um Frame-Generierung zu nutzen. + Frame-Generierung + Suche nach Lossless Scaling… + Lossless-Scaling-Shader bereit. + Aus %1$s importiert. + Lossless Scaling wurde nicht gefunden. Installiere es über Steam und öffne diesen Bildschirm erneut, um es automatisch zu importieren. + Lossless.dll wurde gefunden, aber ihre Shader konnten nicht gelesen werden. Überprüfe die Steam-Dateien und versuche es erneut. + Aus %1$s aktualisiert. + Frame-Generierung nutzt Shader von Lossless Scaling. Kaufe es auf Steam mit dem angemeldeten Konto, um dies zu aktivieren. + Lossless.dll auswählen… diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index c7e2f3b7a..99a3b7866 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -2494,5 +2494,25 @@ Ruta instalada: Verificando la descarga… Procesando… Sistema + Generación de fotogramas + Generar fotogramas + Multiplicador + %1$dx + Objetivo adaptativo + Fijo + %1$d fps + Escala de flujo + Interpola entre fotogramas renderizados con Lossless Scaling. Movimiento más fluido, pero un fotograma extra de latencia de entrada. + Sube hacia el objetivo solo mientras ayude de forma medible, en lugar de mantener un multiplicador fijo. + Instala Lossless Scaling en los ajustes del contenedor para usar la generación de fotogramas. + Generación de fotogramas + Buscando Lossless Scaling… + Sombreadores de Lossless Scaling listos. + Importado desde %1$s. + No se encontró Lossless Scaling. Instálalo desde Steam y vuelve a abrir esta pantalla para importarlo automáticamente. + Se encontró Lossless.dll, pero no se pudieron leer sus sombreadores. Verifica los archivos de Steam e inténtalo de nuevo. + Actualizado desde %1$s. + La generación de fotogramas usa sombreadores de Lossless Scaling. Cómpralo en Steam con la cuenta con la que has iniciado sesión para activarlo. + Seleccionar Lossless.dll… diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 9e316d77f..e863cf682 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -2493,4 +2493,24 @@ Asennuspolku: Vahvistetaan latausta… Käsitellään… Järjestelmä + Ruudun generointi + Generoi ruutuja + Kerroin + %1$dx + Mukautuva tavoite + Kiinteä + %1$d fps + Virtauksen skaala + Interpoloi renderöityjen ruutujen välillä Lossless Scalingilla. Sulavampi liike, mutta yhden ruudun lisäviive syötteessä. + Nousee kohti tavoitetta vain, kun siitä on mitattavaa hyötyä, sen sijaan että pitäisi kiinteän kertoimen. + Asenna Lossless Scaling säilön asetuksista käyttääksesi ruudun generointia. + Ruudun generointi + Etsitään Lossless Scalingia… + Lossless Scalingin varjostimet valmiina. + Tuotu kohteesta %1$s. + Lossless Scalingia ei löytynyt. Asenna se Steamista ja avaa tämä näkymä uudelleen, niin se tuodaan automaattisesti. + Lossless.dll löytyi, mutta sen varjostimia ei voitu lukea. Tarkista Steam-tiedostot ja yritä uudelleen. + Päivitetty kohteesta %1$s. + Ruudun generointi käyttää Lossless Scalingin varjostimia. Osta se Steamista kirjautuneella tilillä ottaaksesi tämän käyttöön. + Valitse Lossless.dll… diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 2e1c76f75..f3556ce6c 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -2494,5 +2494,25 @@ Chemin installé : Vérification du téléchargement… Traitement… Système + Génération d\'images + Générer des images + Multiplicateur + %1$dx + Cible adaptative + Fixe + %1$d fps + Échelle de flux + Interpole entre les images rendues avec Lossless Scaling. Mouvement plus fluide, mais une image supplémentaire de latence d\'entrée. + Monte vers la cible uniquement tant que cela aide de façon mesurable, au lieu de conserver un multiplicateur fixe. + Installez Lossless Scaling dans les paramètres du conteneur pour utiliser la génération d\'images. + Génération d\'images + Recherche de Lossless Scaling… + Shaders Lossless Scaling prêts. + Importé depuis %1$s. + Lossless Scaling est introuvable. Installez-le depuis Steam et rouvrez cet écran pour l\'importer automatiquement. + Lossless.dll a été trouvé, mais ses shaders n\'ont pas pu être lus. Vérifiez les fichiers Steam et réessayez. + Mis à jour depuis %1$s. + La génération d\'images utilise les shaders de Lossless Scaling. Achetez-le sur Steam avec le compte connecté pour l\'activer. + Sélectionner Lossless.dll… diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 6fce2df9f..c1f1c291f 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -2430,4 +2430,24 @@ डाउनलोड सत्यापित हो रहा है… काम चल रहा है… सिस्टम + फ़्रेम जनरेशन + फ़्रेम जनरेट करें + गुणक + %1$dx + अनुकूली लक्ष्य + स्थिर + %1$d fps + फ़्लो स्केल + Lossless Scaling से रेंडर किए गए फ़्रेमों के बीच इंटरपोलेट करता है। गति अधिक सहज, लेकिन इनपुट में एक अतिरिक्त फ़्रेम की देरी। + स्थिर गुणक बनाए रखने के बजाय, केवल तभी लक्ष्य की ओर बढ़ता है जब इससे मापने योग्य लाभ हो। + फ़्रेम जनरेशन उपयोग करने के लिए कंटेनर सेटिंग्स में Lossless Scaling इंस्टॉल करें। + फ़्रेम जनरेशन + Lossless Scaling खोजा जा रहा है… + Lossless Scaling शेडर तैयार हैं। + %1$s से आयात किया गया। + Lossless Scaling नहीं मिला। इसे Steam से इंस्टॉल करें और इसे स्वतः आयात करने के लिए यह स्क्रीन दोबारा खोलें। + Lossless.dll मिली, लेकिन उसके शेडर पढ़े नहीं जा सके। Steam फ़ाइलें जाँचें और फिर कोशिश करें। + %1$s से अपडेट किया गया। + फ़्रेम जनरेशन Lossless Scaling के शेडर उपयोग करता है। इसे सक्षम करने के लिए साइन-इन खाते से Steam पर इसे खरीदें। + Lossless.dll चुनें… diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 17ef3b188..808a64fa2 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -2494,5 +2494,25 @@ Percorso installato: Verifica del download… Operazione in corso… Sistema + Generazione fotogrammi + Genera fotogrammi + Moltiplicatore + %1$dx + Obiettivo adattivo + Fisso + %1$d fps + Scala del flusso + Interpola tra i fotogrammi renderizzati con Lossless Scaling. Movimento più fluido, ma un fotogramma in più di latenza di input. + Sale verso l\'obiettivo solo finché aiuta in modo misurabile, invece di mantenere un moltiplicatore fisso. + Installa Lossless Scaling nelle impostazioni del contenitore per usare la generazione fotogrammi. + Generazione fotogrammi + Ricerca di Lossless Scaling… + Shader di Lossless Scaling pronti. + Importato da %1$s. + Lossless Scaling non è stato trovato. Installalo da Steam e riapri questa schermata per importarlo automaticamente. + Lossless.dll è stato trovato, ma non è stato possibile leggerne gli shader. Verifica i file di Steam e riprova. + Aggiornato da %1$s. + La generazione fotogrammi usa gli shader di Lossless Scaling. Acquistalo su Steam con l\'account connesso per abilitarla. + Seleziona Lossless.dll… diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index c4407016c..bfe7a62c2 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -2493,4 +2493,24 @@ ダウンロードを検証中… 処理中… システム + フレーム生成 + フレームを生成 + 倍率 + %1$dx + アダプティブ目標 + 固定 + %1$d fps + フロースケール + Lossless Scaling でレンダリング済みフレーム間を補間します。動きは滑らかになりますが、入力遅延が 1 フレーム増えます。 + 固定倍率を保つのではなく、効果が測定できる間だけ目標に向けて上げていきます。 + フレーム生成を使用するには、コンテナ設定で Lossless Scaling をインストールしてください。 + フレーム生成 + Lossless Scaling を検索中… + Lossless Scaling のシェーダーを準備しました。 + %1$s からインポートしました。 + Lossless Scaling が見つかりませんでした。Steam からインストールし、この画面を開き直すと自動的にインポートされます。 + Lossless.dll は見つかりましたが、シェーダーを読み取れませんでした。Steam のファイルを確認して再試行してください。 + %1$s から更新しました。 + フレーム生成は Lossless Scaling のシェーダーを使用します。有効にするには、サインイン中のアカウントで Steam から購入してください。 + Lossless.dll を選択… diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index bda2f784d..cd524db50 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -2495,5 +2495,25 @@ 다운로드 확인 중… 처리 중… 시스템 + 프레임 생성 + 프레임 생성 사용 + 배수 + %1$dx + 적응형 목표 + 고정 + %1$d fps + 플로우 스케일 + Lossless Scaling으로 렌더링된 프레임 사이를 보간합니다. 움직임은 부드러워지지만 입력 지연이 한 프레임 늘어납니다. + 고정 배수를 유지하는 대신, 측정 가능한 이득이 있을 때만 목표를 향해 올립니다. + 프레임 생성을 사용하려면 컨테이너 설정에서 Lossless Scaling을 설치하세요. + 프레임 생성 + Lossless Scaling 찾는 중… + Lossless Scaling 셰이더가 준비되었습니다. + %1$s에서 가져왔습니다. + Lossless Scaling을 찾을 수 없습니다. Steam에서 설치한 뒤 이 화면을 다시 열면 자동으로 가져옵니다. + Lossless.dll을 찾았지만 셰이더를 읽을 수 없습니다. Steam 파일을 확인하고 다시 시도하세요. + %1$s에서 업데이트했습니다. + 프레임 생성은 Lossless Scaling의 셰이더를 사용합니다. 사용하려면 로그인한 계정으로 Steam에서 구매하세요. + Lossless.dll 선택… diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index 527b24905..0f2d9a257 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -2493,4 +2493,24 @@ Installert bane: Verifiserer nedlastingen… Arbeider… System + Bildegenerering + Generer bilder + Multiplikator + %1$dx + Adaptivt mål + Fast + %1$d fps + Flytskala + Interpolerer mellom gjengitte bilder med Lossless Scaling. Jevnere bevegelse, men ett ekstra bilde med inndataforsinkelse. + Stiger mot målet bare så lenge det hjelper målbart, i stedet for å holde en fast multiplikator. + Installer Lossless Scaling i containerinnstillingene for å bruke bildegenerering. + Bildegenerering + Leter etter Lossless Scaling… + Lossless Scaling-shadere klare. + Importert fra %1$s. + Lossless Scaling ble ikke funnet. Installer det fra Steam og åpne denne skjermen på nytt for å importere det automatisk. + Lossless.dll ble funnet, men shaderne kunne ikke leses. Kontroller Steam-filene og prøv igjen. + Oppdatert fra %1$s. + Bildegenerering bruker shadere fra Lossless Scaling. Kjøp det på Steam med den påloggede kontoen for å aktivere dette. + Velg Lossless.dll… diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 1aad1741b..524aba57c 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -2500,5 +2500,25 @@ Zainstalowana ścieżka: Weryfikowanie pobranego pliku… Trwa praca… System + Generowanie klatek + Generuj klatki + Mnożnik + %1$dx + Cel adaptacyjny + Stały + %1$d fps + Skala przepływu + Interpoluje między wyrenderowanymi klatkami przy użyciu Lossless Scaling. Płynniejszy ruch, ale jedna dodatkowa klatka opóźnienia sterowania. + Zwiększa się w kierunku celu tylko wtedy, gdy przynosi to mierzalną korzyść, zamiast utrzymywać stały mnożnik. + Zainstaluj Lossless Scaling w ustawieniach kontenera, aby korzystać z generowania klatek. + Generowanie klatek + Szukanie Lossless Scaling… + Shadery Lossless Scaling gotowe. + Zaimportowano z %1$s. + Nie znaleziono Lossless Scaling. Zainstaluj je ze Steam i otwórz ten ekran ponownie, aby zaimportować je automatycznie. + Znaleziono Lossless.dll, ale nie udało się odczytać shaderów. Sprawdź pliki Steam i spróbuj ponownie. + Zaktualizowano z %1$s. + Generowanie klatek korzysta z shaderów Lossless Scaling. Kup je na Steam na zalogowanym koncie, aby to włączyć. + Wybierz Lossless.dll… diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 39135269b..95e5e0ecc 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -2494,5 +2494,25 @@ Caminho instalado: Verificando o download… Processando… Sistema + Geração de quadros + Gerar quadros + Multiplicador + %1$dx + Alvo adaptativo + Fixo + %1$d fps + Escala de fluxo + Interpola entre quadros renderizados com o Lossless Scaling. Movimento mais suave, mas um quadro extra de latência de entrada. + Sobe em direção ao alvo apenas enquanto ajudar de forma mensurável, em vez de manter um multiplicador fixo. + Instale o Lossless Scaling nas configurações do contêiner para usar a geração de quadros. + Geração de quadros + Procurando o Lossless Scaling… + Shaders do Lossless Scaling prontos. + Importado de %1$s. + O Lossless Scaling não foi encontrado. Instale-o pela Steam e reabra esta tela para importá-lo automaticamente. + O Lossless.dll foi encontrado, mas não foi possível ler seus shaders. Verifique os arquivos da Steam e tente novamente. + Atualizado de %1$s. + A geração de quadros usa shaders do Lossless Scaling. Compre-o na Steam com a conta conectada para ativar isso. + Selecionar Lossless.dll… diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 95b3ff6b1..5550fcc4f 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -2493,4 +2493,24 @@ Caminho instalado: A verificar a transferência… A processar… Sistema + Geração de fotogramas + Gerar fotogramas + Multiplicador + %1$dx + Alvo adaptativo + Fixo + %1$d fps + Escala de fluxo + Interpola entre fotogramas renderizados com o Lossless Scaling. Movimento mais suave, mas um fotograma extra de latência de entrada. + Sobe em direção ao alvo apenas enquanto ajudar de forma mensurável, em vez de manter um multiplicador fixo. + Instale o Lossless Scaling nas definições do contentor para usar a geração de fotogramas. + Geração de fotogramas + A procurar o Lossless Scaling… + Shaders do Lossless Scaling prontos. + Importado de %1$s. + O Lossless Scaling não foi encontrado. Instale-o a partir do Steam e reabra este ecrã para importá-lo automaticamente. + O Lossless.dll foi encontrado, mas não foi possível ler os seus shaders. Verifique os ficheiros do Steam e tente novamente. + Atualizado de %1$s. + A geração de fotogramas usa shaders do Lossless Scaling. Compre-o no Steam com a conta com sessão iniciada para ativar isto. + Selecionar Lossless.dll… diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 7a446ea7d..8dba3b72c 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -2493,5 +2493,25 @@ Cale instalata: Se verifică descărcarea… Se lucrează… Sistem + Generare de cadre + Generează cadre + Multiplicator + %1$dx + Țintă adaptivă + Fix + %1$d fps + Scară a fluxului + Interpolează între cadrele randate cu Lossless Scaling. Mișcare mai fluidă, dar un cadru suplimentar de latență la intrare. + Urcă spre țintă doar cât timp ajută în mod măsurabil, în loc să păstreze un multiplicator fix. + Instalează Lossless Scaling din setările containerului pentru a folosi generarea de cadre. + Generare de cadre + Se caută Lossless Scaling… + Shaderele Lossless Scaling sunt gata. + Importat din %1$s. + Lossless Scaling nu a fost găsit. Instalează-l din Steam și redeschide acest ecran pentru a-l importa automat. + Lossless.dll a fost găsit, dar shaderele sale nu au putut fi citite. Verifică fișierele Steam și încearcă din nou. + Actualizat din %1$s. + Generarea de cadre folosește shadere din Lossless Scaling. Cumpără-l pe Steam cu contul conectat pentru a activa acest lucru. + Selectează Lossless.dll… diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 78c721b50..5011bd819 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -2400,4 +2400,24 @@ Проверка загрузки… Выполняется… Система + Генерация кадров + Генерировать кадры + Множитель + %1$dx + Адаптивная цель + Фиксированный + %1$d fps + Масштаб потока + Интерполирует между отрисованными кадрами с помощью Lossless Scaling. Движение плавнее, но задержка ввода увеличивается на один кадр. + Повышается к цели только пока это даёт измеримый выигрыш, вместо удержания фиксированного множителя. + Установите Lossless Scaling в настройках контейнера, чтобы использовать генерацию кадров. + Генерация кадров + Поиск Lossless Scaling… + Шейдеры Lossless Scaling готовы. + Импортировано из %1$s. + Lossless Scaling не найден. Установите его из Steam и снова откройте этот экран, чтобы импортировать автоматически. + Lossless.dll найден, но его шейдеры не удалось прочитать. Проверьте файлы Steam и попробуйте снова. + Обновлено из %1$s. + Генерация кадров использует шейдеры Lossless Scaling. Купите его в Steam под текущей учётной записью, чтобы включить эту функцию. + Выбрать Lossless.dll… diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 81848eaec..6f6f67519 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -2493,4 +2493,24 @@ Installerad sökväg: Verifierar nedladdningen… Arbetar… System + Bildgenerering + Generera bilder + Multiplikator + %1$dx + Adaptivt mål + Fast + %1$d fps + Flödesskala + Interpolerar mellan renderade bildrutor med Lossless Scaling. Mjukare rörelse, men en extra bildruta med inmatningsfördröjning. + Stiger mot målet endast så länge det hjälper mätbart, i stället för att hålla en fast multiplikator. + Installera Lossless Scaling i containerinställningarna för att använda bildgenerering. + Bildgenerering + Söker efter Lossless Scaling… + Lossless Scaling-shaders klara. + Importerad från %1$s. + Lossless Scaling hittades inte. Installera det från Steam och öppna den här skärmen igen för att importera det automatiskt. + Lossless.dll hittades, men dess shaders kunde inte läsas. Kontrollera Steam-filerna och försök igen. + Uppdaterad från %1$s. + Bildgenerering använder shaders från Lossless Scaling. Köp det på Steam med det inloggade kontot för att aktivera detta. + Välj Lossless.dll… diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index db6bbdf91..cba9a1826 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -2493,4 +2493,24 @@ กำลังตรวจสอบไฟล์ที่ดาวน์โหลด… กำลังดำเนินการ… ระบบ + การสร้างเฟรม + สร้างเฟรม + ตัวคูณ + %1$dx + เป้าหมายแบบปรับอัตโนมัติ + คงที่ + %1$d fps + สเกลการไหล + แทรกเฟรมระหว่างเฟรมที่เรนเดอร์ด้วย Lossless Scaling การเคลื่อนไหวลื่นขึ้น แต่มีความหน่วงของอินพุตเพิ่มขึ้นหนึ่งเฟรม + จะไต่ขึ้นสู่เป้าหมายเฉพาะเมื่อวัดผลได้ว่าช่วยจริง แทนที่จะคงตัวคูณไว้คงที่ + ติดตั้ง Lossless Scaling ในการตั้งค่าคอนเทนเนอร์เพื่อใช้การสร้างเฟรม + การสร้างเฟรม + กำลังค้นหา Lossless Scaling… + เชเดอร์ของ Lossless Scaling พร้อมแล้ว + นำเข้าจาก %1$s แล้ว + ไม่พบ Lossless Scaling ติดตั้งจาก Steam แล้วเปิดหน้านี้อีกครั้งเพื่อนำเข้าโดยอัตโนมัติ + พบ Lossless.dll แต่ไม่สามารถอ่านเชเดอร์ได้ ตรวจสอบไฟล์ Steam แล้วลองอีกครั้ง + อัปเดตจาก %1$s แล้ว + การสร้างเฟรมใช้เชเดอร์จาก Lossless Scaling ซื้อบน Steam ด้วยบัญชีที่ลงชื่อเข้าใช้อยู่เพื่อเปิดใช้งาน + เลือก Lossless.dll… diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 1732a6284..a2cadb0ef 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -2493,4 +2493,24 @@ Yüklü konum: İndirme doğrulanıyor… Çalışıyor… Sistem + Kare üretimi + Kare üret + Çarpan + %1$dx + Uyarlanabilir hedef + Sabit + %1$d fps + Akış ölçeği + Lossless Scaling ile işlenmiş kareler arasında ara kare üretir. Daha akıcı hareket, ancak bir kare fazladan giriş gecikmesi. + Sabit bir çarpanı korumak yerine, yalnızca ölçülebilir fayda sağladığı sürece hedefe doğru yükselir. + Kare üretimini kullanmak için kapsayıcı ayarlarından Lossless Scaling\'i yükleyin. + Kare üretimi + Lossless Scaling aranıyor… + Lossless Scaling gölgelendiricileri hazır. + %1$s kaynağından içe aktarıldı. + Lossless Scaling bulunamadı. Steam üzerinden yükleyin ve otomatik olarak içe aktarmak için bu ekranı yeniden açın. + Lossless.dll bulundu ancak gölgelendiricileri okunamadı. Steam dosyalarını doğrulayıp tekrar deneyin. + %1$s kaynağından güncellendi. + Kare üretimi Lossless Scaling gölgelendiricilerini kullanır. Bunu etkinleştirmek için oturum açmış hesapla Steam\'den satın alın. + Lossless.dll seç… diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 8cce271d8..2039f222d 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -2500,5 +2500,25 @@ Перевірка завантаження… Виконується… Система + Генерація кадрів + Генерувати кадри + Множник + %1$dx + Адаптивна ціль + Фіксований + %1$d fps + Масштаб потоку + Інтерполює між відрендереними кадрами за допомогою Lossless Scaling. Рух плавніший, але затримка вводу зростає на один кадр. + Підвищується до цілі лише доки це дає вимірний виграш, замість утримання фіксованого множника. + Установіть Lossless Scaling у налаштуваннях контейнера, щоб використовувати генерацію кадрів. + Генерація кадрів + Пошук Lossless Scaling… + Шейдери Lossless Scaling готові. + Імпортовано з %1$s. + Lossless Scaling не знайдено. Встановіть його зі Steam і відкрийте цей екран знову, щоб імпортувати автоматично. + Lossless.dll знайдено, але не вдалося прочитати його шейдери. Перевірте файли Steam і спробуйте ще раз. + Оновлено з %1$s. + Генерація кадрів використовує шейдери Lossless Scaling. Придбайте його у Steam під поточним обліковим записом, щоб увімкнути це. + Вибрати Lossless.dll… diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 8f2706930..e1056561a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -2494,5 +2494,25 @@ 正在校验下载… 处理中… 系统 + 帧生成 + 生成帧 + 倍数 + %1$dx + 自适应目标 + 固定 + %1$d fps + 流场缩放 + 使用 Lossless Scaling 在已渲染的帧之间插帧。画面更流畅,但输入延迟会增加一帧。 + 仅在能带来可测量的提升时才向目标提升,而不是保持固定倍数。 + 请在容器设置中安装 Lossless Scaling 以使用帧生成。 + 帧生成 + 正在查找 Lossless Scaling… + Lossless Scaling 着色器已就绪。 + 已从 %1$s 导入。 + 未找到 Lossless Scaling。请从 Steam 安装,然后重新打开此页面以自动导入。 + 已找到 Lossless.dll,但无法读取其着色器。请校验 Steam 文件后重试。 + 已从 %1$s 更新。 + 帧生成使用 Lossless Scaling 的着色器。请使用当前登录的账号在 Steam 上购买以启用此功能。 + 选择 Lossless.dll… diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index ac5528bd2..09bb947a6 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -2493,5 +2493,25 @@ 正在驗證下載… 處理中… 系統 + 影格生成 + 生成影格 + 倍數 + %1$dx + 自適應目標 + 固定 + %1$d fps + 流場縮放 + 使用 Lossless Scaling 在已算繪的影格之間插補。畫面更流暢,但輸入延遲會增加一個影格。 + 僅在能帶來可測量的提升時才朝目標提升,而不是維持固定倍數。 + 請在容器設定中安裝 Lossless Scaling 以使用影格生成。 + 影格生成 + 正在尋找 Lossless Scaling… + Lossless Scaling 著色器已就緒。 + 已從 %1$s 匯入。 + 找不到 Lossless Scaling。請從 Steam 安裝,然後重新開啟此畫面以自動匯入。 + 已找到 Lossless.dll,但無法讀取其著色器。請驗證 Steam 檔案後再試一次。 + 已從 %1$s 更新。 + 影格生成使用 Lossless Scaling 的著色器。請使用目前登入的帳號在 Steam 上購買以啟用此功能。 + 選擇 Lossless.dll… From 11123717b5832700852a0433bd588b677117a898 Mon Sep 17 00:00:00 2001 From: Max Jividen Date: Sun, 23 Aug 2026 13:53:37 -0400 Subject: [PATCH 17/35] Present generated frames in motion order at 3x and 4x Every generated frame was a pixel-perfect interpolation, but within a frame they came out reversed: at 4x the first shown sat three quarters along the motion, then the midpoint, then a quarter. Motion stepped forward, jumped back, forward again. At 2x there is only one generated frame, so the reversal had nothing to show and 2x looked correct throughout - which is why this read as "3x and 4x do not work" rather than as an ordering fault. The shader timestamp runs from the newer frame backwards, so generation g needs (count - g) / (count + 1), not (g + 1) / (count + 1). At one generation both give one half, which is what hid it. Measured on the tablet GPU against a shifted multi-sine pattern, 24px of true motion: 2x lands at 12, 3x at 8 and 16, 4x at 6, 12 and 18, every one at 0.00 mean absolute error and strictly increasing. --- app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp index 469b21c99..f45d1cf70 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp @@ -40,7 +40,8 @@ constexpr std::array LSFG_DELTA_SHADERS{280, 286, 287, 288, 289, } [[nodiscard]] constexpr float LsfgTimestamp(size_t generation, size_t generation_count) { - return static_cast(generation + 1) / static_cast(generation_count + 1); + return static_cast(generation_count - generation) / + static_cast(generation_count + 1); } [[nodiscard]] constexpr size_t LsfgSlotCount(size_t slot) { From da62bba28a216be258b5f2dbd91a963e9bda68b7 Mon Sep 17 00:00:00 2001 From: Max Jividen Date: Sun, 23 Aug 2026 20:55:33 -0400 Subject: [PATCH 18/35] Raise the panel to carry generated frames, and stop stranding per-shortcut settings Two faults kept 3x and 4x from ever reaching the screen on the phone while 2x looked fine. The display stayed at 60Hz. Android picks a mode from the guest's own frame rate, so a 30fps game leaves the panel at 60 even though it supports 165, and under FIFO that caps total presents at 60 a second. 2x from 30fps needs exactly 60 and fits; 3x needs 90 and 4x needs 120, so beyond 2x the generated frames displaced real ones instead of adding to them and the game rate fell to compensate. Enabling frame generation now requests the lowest mode at the current resolution that can carry multiplier times the frame cap, and clears the request when it is switched off. Dinkum's shortcut carried frameGen=1 and frameGenMultiplier=4, but it also sets use_container_defaults, and getSettingExtra returns the container value outright for such shortcuts. Container 5 has no frame generation keys, so every launch silently resolved to off, and anything saved from the drawer went to the same ignored place. Frame generation now reads the shortcut's own extra first and falls back to the container, which is where the settings UI has been writing all along. --- .../display/XServerDisplayActivity.java | 73 ++++++++++++++++++- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index 930986bf9..35fb5d062 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -736,6 +736,12 @@ private String getShortcutSetting(String key, String containerValue) { return shortcut != null ? shortcut.getSettingExtra(key, containerValue) : containerValue; } + private String getFrameGenSetting(String key, String containerValue) { + if (shortcut == null) return containerValue; + String own = shortcut.getExtra(key, ""); + return own.isEmpty() ? containerValue : own; + } + private void applyFrameGenerationSettings(VulkanRenderer renderer, Container container) { if (renderer == null) return; @@ -744,13 +750,13 @@ private void applyFrameGenerationSettings(VulkanRenderer renderer, Container con String containerTargetRate = container != null ? container.getExtra("frameGenTargetRate", "0") : "0"; String containerFlowScale = container != null ? container.getExtra("frameGenFlowScale", "100") : "100"; - frameGenEnabled = "1".equals(getShortcutSetting("frameGen", containerValue)); + frameGenEnabled = "1".equals(getFrameGenSetting("frameGen", containerValue)); frameGenMultiplier = clampFrameGenMultiplier( - parseSettingInt(getShortcutSetting("frameGenMultiplier", containerMultiplier), 2)); + parseSettingInt(getFrameGenSetting("frameGenMultiplier", containerMultiplier), 2)); frameGenTargetRate = Math.max(0, - parseSettingInt(getShortcutSetting("frameGenTargetRate", containerTargetRate), 0)); + parseSettingInt(getFrameGenSetting("frameGenTargetRate", containerTargetRate), 0)); frameGenFlowScale = clampFrameGenFlowScale( - parseSettingInt(getShortcutSetting("frameGenFlowScale", containerFlowScale), 100)); + parseSettingInt(getFrameGenSetting("frameGenFlowScale", containerFlowScale), 100)); if (frameGenEnabled) { int result = com.winlator.cmod.feature.library.LosslessAutoImport.INSTANCE.sync(this).getResult(); @@ -783,12 +789,71 @@ private void applyFrameGeneration(VulkanRenderer renderer) { renderer.setFrameGenerationShaders(frameGenCachePath); renderer.setFrameGenerationMode(frameGenMultiplier, frameGenTargetRate, frameGenFlowScale); renderer.setFrameGenerationEnabled(true); + applyFrameGenerationDisplayMode(); Log.i("XServerDisplayActivity", "Frame generation on: multiplier=" + frameGenMultiplier + " targetRate=" + frameGenTargetRate + " flowScale=" + frameGenFlowScale); } + private void applyFrameGenerationDisplayMode() { + android.view.Window window = getWindow(); + if (window == null) return; + + android.view.WindowManager.LayoutParams params = window.getAttributes(); + if (!frameGenEnabled) { + if (params.preferredDisplayModeId != 0) { + params.preferredDisplayModeId = 0; + window.setAttributes(params); + } + return; + } + + int wanted = frameGenTargetRate > 0 + ? frameGenTargetRate + : frameGenMultiplier * Math.max(30, runtimeFpsLimit > 0 ? runtimeFpsLimit : 60); + + android.view.Display display = getDisplayCompat(); + if (display == null) return; + + android.view.Display.Mode active = display.getMode(); + android.view.Display.Mode best = null; + for (android.view.Display.Mode mode : display.getSupportedModes()) { + if (mode.getPhysicalWidth() != active.getPhysicalWidth() + || mode.getPhysicalHeight() != active.getPhysicalHeight()) { + continue; + } + if (best == null || betterFrameGenMode(mode, best, wanted)) best = mode; + } + if (best == null || best.getModeId() == params.preferredDisplayModeId) return; + + params.preferredDisplayModeId = best.getModeId(); + window.setAttributes(params); + Log.i("XServerDisplayActivity", "Frame generation display mode: wanted " + wanted + + "Hz, selected " + Math.round(best.getRefreshRate()) + "Hz (mode " + + best.getModeId() + ")"); + } + + private static boolean betterFrameGenMode(android.view.Display.Mode candidate, + android.view.Display.Mode current, int wanted) { + float a = candidate.getRefreshRate(); + float b = current.getRefreshRate(); + boolean aMeets = a + 0.5f >= wanted; + boolean bMeets = b + 0.5f >= wanted; + if (aMeets != bMeets) return aMeets; + return aMeets ? a < b : a > b; + } + + private android.view.Display getDisplayCompat() { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { + android.view.Display d = getDisplay(); + if (d != null) return d; + } + android.view.WindowManager wm = getWindowManager(); + return wm != null ? wm.getDefaultDisplay() : null; + } + private void applyFrameGenerationLive() { applyFrameGeneration(xServerView != null ? xServerView.getRenderer() : null); + applyFrameGenerationDisplayMode(); saveFrameGenerationSettings(); renderDrawerMenu(); } From d769d641256d4668b47d6384261f6bdd3211b30d Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sun, 23 Aug 2026 22:18:24 -0400 Subject: [PATCH 19/35] Show the generated frame rate next to the rendered one on the HUD The FPS readout counted guest presentations, so with frame generation on it reported the game's rate and said nothing about what actually reached the panel. The renderer now keeps a monotonic count of frames that survived vkQueuePresentKHR, real and interpolated alike, and the HUD samples it over the same one-second window it already uses for the input rate, rendering the pair as "30 -> 60". The split appears only while interpolated frames landed during the sampled window, so an unsupported or stalled generator falls back to the single number instead of asserting a ratio that never happened. --- app/src/main/cpp/winlator/vk/vk_renderer.c | 13 +++ app/src/main/cpp/winlator/vk/vk_state.h | 1 + .../main/runtime/display/PerformanceHud.kt | 10 +- .../display/XServerDisplayActivity.java | 25 +++++ .../display/renderer/VulkanRenderer.java | 5 + .../main/runtime/display/ui/FrameRating.java | 105 +++++++++++++++++- 6 files changed, 156 insertions(+), 3 deletions(-) diff --git a/app/src/main/cpp/winlator/vk/vk_renderer.c b/app/src/main/cpp/winlator/vk/vk_renderer.c index c09d22289..9a883ca29 100644 --- a/app/src/main/cpp/winlator/vk/vk_renderer.c +++ b/app/src/main/cpp/winlator/vk/vk_renderer.c @@ -2804,9 +2804,14 @@ static bool record_and_submit_frame(VkRenderer* r) { VkResult gpr = vkQueuePresentKHR(r->graphics_queue, &gpi); if (gpr != VK_SUCCESS && gpr != VK_SUBOPTIMAL_KHR) { VK_LOGW("generated frame present failed (%d)", gpr); + } else { + __atomic_fetch_add(&r->presented_frames, 1, __ATOMIC_RELAXED); } } VkResult pr = vkQueuePresentKHR(r->graphics_queue, &pi); + if (pr == VK_SUCCESS || pr == VK_SUBOPTIMAL_KHR) { + __atomic_fetch_add(&r->presented_frames, 1, __ATOMIC_RELAXED); + } // Present the mirror separately so its result doesn't disturb the display recreate logic below. if (rec_this_frame) { VkPresentInfoKHR rpi = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR}; @@ -3560,6 +3565,14 @@ JNIEXPORT jlong JNICALL JNI_FN(nativeGetGeneratedFrameCount)(JNIEnv* env, jclass return (jlong)r->framegen_made_frames; } +JNIEXPORT jlong JNICALL JNI_FN(nativeGetPresentedFrameCount)(JNIEnv* env, jclass clazz, + jlong handle) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r) return 0; + return (jlong)__atomic_load_n(&r->presented_frames, __ATOMIC_RELAXED); +} + // ============================================================ // JNI entry points for Java Texture / GPUImage // ============================================================ diff --git a/app/src/main/cpp/winlator/vk/vk_state.h b/app/src/main/cpp/winlator/vk/vk_state.h index 6bc03ba42..2a3a2c6cf 100644 --- a/app/src/main/cpp/winlator/vk/vk_state.h +++ b/app/src/main/cpp/winlator/vk/vk_state.h @@ -424,6 +424,7 @@ typedef struct VkRenderer { uint64_t framegen_real_frames; uint64_t framegen_made_frames; uint64_t framegen_acquire_misses; + uint64_t presented_frames; // record_blit_src adds TRANSFER_SRC usage to the display swapchain (toggled by start/stop recording). bool record_blit_src; diff --git a/app/src/main/runtime/display/PerformanceHud.kt b/app/src/main/runtime/display/PerformanceHud.kt index 257e03cfe..5e71f3585 100644 --- a/app/src/main/runtime/display/PerformanceHud.kt +++ b/app/src/main/runtime/display/PerformanceHud.kt @@ -42,6 +42,7 @@ object PerformanceHudState { val batteryWatts: Float = 0f, val tempC: Int = -1, val renderer: String = "", + val outputFps: Float = 0f, ) private val _state = MutableStateFlow(Snapshot()) @@ -61,10 +62,12 @@ object PerformanceHudState { fun updateValues( fps: Float, frametimeMs: Float, gpuLoad: Int, cpuPercent: Int, ramPercent: Int, batteryWatts: Float, tempC: Int, renderer: String, + outputFps: Float, ) { _state.value = _state.value.copy( fps = fps, frametimeMs = frametimeMs, gpuLoad = gpuLoad, cpuPercent = cpuPercent, ramPercent = ramPercent, batteryWatts = batteryWatts, tempC = tempC, renderer = renderer, + outputFps = outputFps, ) } } @@ -76,6 +79,7 @@ private val HudBad = Color(0xFFFF5A5A) private val HudText = Color(0xFFF0F4FF) private val HudSub = Color(0xFF7A8FA8) private val HudTrack = Color(0x33FFFFFF) +private val HudGen = Color(0xFF00E5FF) private data class GaugeSpec( val label: String, @@ -92,7 +96,11 @@ fun PerformanceHudOverlay(modifier: Modifier = Modifier) { // A gauge stays while its element is enabled; a momentarily-unavailable value shows N/A rather than dropping the gauge (which would make the row jump). val gauges = ArrayList(8) if (s.enabled.getOrElse(0) { false }) { - gauges.add(GaugeSpec("FPS", s.fps.toInt().toString(), s.fps / 120f, HudAccent)) + gauges.add(GaugeSpec( + "FPS", s.fps.toInt().toString(), s.fps / 120f, HudAccent, + sublabel = if (s.outputFps > 0f) "→ ${s.outputFps.toInt()}" else null, + sublabelColor = HudGen, + )) } if (s.enabled.getOrElse(2) { false }) { gauges.add(GaugeSpec("GPU", pctText(s.gpuLoad), pctFraction(s.gpuLoad), loadColor(maxOf(s.gpuLoad, 0)))) diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index 35fb5d062..55d1fc8d8 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -783,6 +783,7 @@ private void applyFrameGeneration(VulkanRenderer renderer) { if (!frameGenEnabled || frameGenCachePath == null) { renderer.setFrameGenerationEnabled(false); + syncFrameGenerationHud(); return; } @@ -790,10 +791,32 @@ private void applyFrameGeneration(VulkanRenderer renderer) { renderer.setFrameGenerationMode(frameGenMultiplier, frameGenTargetRate, frameGenFlowScale); renderer.setFrameGenerationEnabled(true); applyFrameGenerationDisplayMode(); + syncFrameGenerationHud(); Log.i("XServerDisplayActivity", "Frame generation on: multiplier=" + frameGenMultiplier + " targetRate=" + frameGenTargetRate + " flowScale=" + frameGenFlowScale); } + private void syncFrameGenerationHud() { + if (frameRating == null) return; + frameRating.setOutputFrameSource(frameGenEnabled ? frameGenOutputSource : null); + frameRating.setFrameGenerationActive(frameGenEnabled && frameGenCachePath != null); + } + + private final FrameRating.OutputFrameSource frameGenOutputSource = + new FrameRating.OutputFrameSource() { + @Override + public long getPresentedFrameCount() { + VulkanRenderer renderer = xServerView != null ? xServerView.getRenderer() : null; + return renderer != null ? renderer.getPresentedFrameCount() : 0L; + } + + @Override + public long getGeneratedFrameCount() { + VulkanRenderer renderer = xServerView != null ? xServerView.getRenderer() : null; + return renderer != null ? renderer.getGeneratedFrameCount() : 0L; + } + }; + private void applyFrameGenerationDisplayMode() { android.view.Window window = getWindow(); if (window == null) return; @@ -5903,6 +5926,7 @@ private boolean handleDrawerAction(int itemId) { if (lastGpuName != null) frameRating.setGpuName(lastGpuName); frameRating.setVisibility(View.GONE); applyHUDSettings(); + syncFrameGenerationHud(); rootView.addView(frameRating); if (perfController != null) perfController.attachToFrameRating(frameRating); } @@ -7663,6 +7687,7 @@ private void setupUI() { if (lastGpuName != null) frameRating.setGpuName(lastGpuName); frameRating.setVisibility(effectiveShowFPS ? View.VISIBLE : View.GONE); applyHUDSettings(); + syncFrameGenerationHud(); updateHUDRenderMode(); rootView.addView(frameRating); if (perfController != null) perfController.attachToFrameRating(frameRating); diff --git a/app/src/main/runtime/display/renderer/VulkanRenderer.java b/app/src/main/runtime/display/renderer/VulkanRenderer.java index 209af6443..0715d3783 100644 --- a/app/src/main/runtime/display/renderer/VulkanRenderer.java +++ b/app/src/main/runtime/display/renderer/VulkanRenderer.java @@ -939,6 +939,10 @@ public long getGeneratedFrameCount() { return nativeHandle != 0 ? nativeGetGeneratedFrameCount(nativeHandle) : 0L; } + public long getPresentedFrameCount() { + return nativeHandle != 0 ? nativeGetPresentedFrameCount(nativeHandle) : 0L; + } + public static int parsePresentMode(String name) { if (name == null) return PRESENT_MODE_FIFO; switch (name.trim().toLowerCase()) { @@ -998,4 +1002,5 @@ private static native long nativeCreate(boolean enableValidationLayers, private static native void nativeSetFrameGenerationMode(long handle, int multiplier, int targetRate, int flowScalePercent); private static native long nativeGetGeneratedFrameCount(long handle); + private static native long nativeGetPresentedFrameCount(long handle); } diff --git a/app/src/main/runtime/display/ui/FrameRating.java b/app/src/main/runtime/display/ui/FrameRating.java index 0e868cc3d..fb82ac6c2 100644 --- a/app/src/main/runtime/display/ui/FrameRating.java +++ b/app/src/main/runtime/display/ui/FrameRating.java @@ -84,6 +84,7 @@ public class FrameRating extends LinearLayout implements Runnable { private final int C_CPU; private final int C_DIVISOR; private final int C_FPS_OK; + private final int C_FPS_GEN; private final int C_WARM; private final int C_HOT; private final int C_GPU; @@ -128,6 +129,28 @@ public interface FrameObserver { public void setFrameObserver(FrameObserver observer) { this.frameObserver = observer; } + + public interface OutputFrameSource { + long getPresentedFrameCount(); + + long getGeneratedFrameCount(); + } + + public void setOutputFrameSource(OutputFrameSource source) { + this.outputFrameSource = source; + } + + public void setFrameGenerationActive(boolean active) { + if (this.frameGenActive == active) { + return; + } + this.frameGenActive = active; + synchronized (this) { + this.outputSamplesCount = 0; + this.outputFPS = 0.0f; + this.outputGenerating = false; + } + } private FrametimeGraphView graphView; private boolean isNativeActive; private boolean isStatsRunning; @@ -142,6 +165,15 @@ public void setFrameObserver(FrameObserver observer) { private final long[] frameTimesNano = new long[MAX_FRAME_SAMPLES]; private int frameTimesStart; private int frameTimesCount; + private volatile OutputFrameSource outputFrameSource; + private volatile boolean frameGenActive; + private volatile float outputFPS; + private volatile boolean outputGenerating; + private final long[] outputSampleNano = new long[MAX_OUTPUT_SAMPLES]; + private final long[] outputSampleTotal = new long[MAX_OUTPUT_SAMPLES]; + private final long[] outputSampleGenerated = new long[MAX_OUTPUT_SAMPLES]; + private int outputSamplesStart; + private int outputSamplesCount; private String rendererName; private String gpuName; private final View sep0, sep1, sep2, sep3, sep4, sep5, sep6; @@ -169,6 +201,9 @@ public void setFrameObserver(FrameObserver observer) { private static final long HUD_REFRESH_MS = 500L; private static final long CPU_WARMUP_POLL_MS = 500L; private static final int MAX_FRAME_SAMPLES = 1024; + private static final int MAX_OUTPUT_SAMPLES = 8; + private static final long OUTPUT_WINDOW_NS = 1000000000L; + private static final long OUTPUT_MIN_SPAN_NS = 250000000L; // ── Tap-cycle display modes ────────────────────────────────────── // 0/1 horizontal (no-backdrop/backdrop), 2/3 vertical (no-backdrop/backdrop) @@ -244,6 +279,7 @@ public FrameRating( this.C_CPU_TEMP = Color.parseColor("#9E9E9E"); this.C_GPU = Color.parseColor("#E040FB"); this.C_FPS_OK = Color.parseColor("#76FF03"); + this.C_FPS_GEN = Color.parseColor("#00E5FF"); this.C_WARM = Color.parseColor("#FFC107"); // TMP value when battery is warm (40-44C) this.C_HOT = Color.parseColor("#FF1744"); // TMP value when battery is hot (>=45C) this.C_DIVISOR = Color.parseColor("#616161"); @@ -1187,6 +1223,9 @@ public synchronized void reset() { this.frameTimesCount = 0; this.lastFPS = 0.0f; this.currentMs = 0.0f; + this.outputSamplesCount = 0; + this.outputFPS = 0.0f; + this.outputGenerating = false; post(this); } @@ -1426,6 +1465,55 @@ private void trimFrameTimesLocked(long oldestAllowedNano) { } } + private boolean showOutputFps() { + return this.frameGenActive && this.outputGenerating && this.outputFPS > 0.0f; + } + + private void sampleOutputFramesLocked(long nowNano) { + OutputFrameSource source = this.outputFrameSource; + if (!this.frameGenActive || source == null) { + this.outputSamplesCount = 0; + this.outputFPS = 0.0f; + this.outputGenerating = false; + return; + } + + long total = source.getPresentedFrameCount(); + long generated = source.getGeneratedFrameCount(); + int index = (this.outputSamplesStart + this.outputSamplesCount) % MAX_OUTPUT_SAMPLES; + if (this.outputSamplesCount == MAX_OUTPUT_SAMPLES) { + this.outputSamplesStart = (this.outputSamplesStart + 1) % MAX_OUTPUT_SAMPLES; + index = (this.outputSamplesStart + this.outputSamplesCount - 1) % MAX_OUTPUT_SAMPLES; + } else { + this.outputSamplesCount++; + } + this.outputSampleNano[index] = nowNano; + this.outputSampleTotal[index] = total; + this.outputSampleGenerated[index] = generated; + + if (this.outputSamplesCount < 2) { + return; + } + + int baseline = this.outputSamplesStart; + for (int i = 0; i < this.outputSamplesCount - 1; i++) { + int candidate = (this.outputSamplesStart + i) % MAX_OUTPUT_SAMPLES; + if (nowNano - this.outputSampleNano[candidate] >= OUTPUT_WINDOW_NS) { + baseline = candidate; + } else { + break; + } + } + + long elapsedNano = nowNano - this.outputSampleNano[baseline]; + long frames = total - this.outputSampleTotal[baseline]; + if (elapsedNano < OUTPUT_MIN_SPAN_NS || frames < 0) { + return; + } + this.outputFPS = (frames * 1000000000.0f) / elapsedNano; + this.outputGenerating = generated > this.outputSampleGenerated[baseline]; + } + private void updateRollingFpsLocked() { if (this.frameTimesCount <= 1) { this.lastFPS = 0.0f; @@ -1633,6 +1721,7 @@ public void run() { // Moved off the present path: maintain the 1s rolling window at display cadence. trimFrameTimesLocked(nowNano - FPS_CALC_INTERVAL_NS); updateRollingFpsLocked(); + sampleOutputFramesLocked(nowNano); } if (this.lastFrameNano > 0 && nowNano - this.lastFrameNano > 1500000000L) { synchronized (this) { @@ -1640,13 +1729,17 @@ public void run() { this.currentMs = 0.0f; this.frameTimesStart = 0; this.frameTimesCount = 0; + this.outputSamplesCount = 0; + this.outputFPS = 0.0f; + this.outputGenerating = false; } } // Feed the phone gauge HUD (single source of truth) even while the on-screen overlay is hidden. com.winlator.cmod.runtime.display.PerformanceHudState.updateValues( this.lastFPS, this.currentMs, this.gpuLoad, this.cpuPercent, ramPercentValue(), (this.dualSeriesBattery && this.batteryWatts >= 0.0f) ? this.batteryWatts * 2.0f : this.batteryWatts, - this.cpuTemp, this.rendererName != null ? this.rendererName : ""); + this.cpuTemp, this.rendererName != null ? this.rendererName : "", + showOutputFps() ? this.outputFPS : 0.0f); if (getVisibility() != View.VISIBLE) return; if (this.enableGpu && this.tvGpuLoad != null) { @@ -1736,8 +1829,16 @@ this.lastFPS, this.currentMs, this.gpuLoad, this.cpuPercent, ramPercentValue(), } if (this.enableFps && this.tvFpsBig != null) { - this.tvFpsBig.setText(String.format(Locale.US, "%.0f", this.lastFPS)); this.tvFpsBig.setTextColor(this.C_FPS_OK); + if (showOutputFps()) { + SpannableStringBuilder b = new SpannableStringBuilder(); + append(b, String.format(Locale.US, "%.0f", this.lastFPS), this.C_FPS_OK); + append(b, " → ", this.C_DIVISOR); + append(b, String.format(Locale.US, "%.0f", this.outputFPS), this.C_FPS_GEN); + this.tvFpsBig.setText(b); + } else { + this.tvFpsBig.setText(String.format(Locale.US, "%.0f", this.lastFPS)); + } this.tvFpsBig.setVisibility(View.VISIBLE); } else if (this.tvFpsBig != null) this.tvFpsBig.setVisibility(View.GONE); From d625fb3472a36a1451418a3ae10f2b22c16ccc8c Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sun, 23 Aug 2026 23:01:16 -0400 Subject: [PATCH 20/35] Present interpolated frames in rising time order at 3x and 4x The timestamp fell as the generation index rose, while the present loop emits generation 0 first and the real frame last. The newest interpolated position therefore went out ahead of the older ones, and motion ran forward two thirds of a game frame, snapped back to one third, then jumped to the real frame - a back-and-forth shimmer at the game's own rate. At 2x the two forms both evaluate to one half, which is why that multiplier alone was unaffected and why the fault read as "3x and 4x do nothing useful". This reverses 11123717. That commit trusted an offline harness which fed the two source frames to the interpolator in the opposite order from the renderer, mirroring the mapping and making the wrong direction look right. A 90 fps capture of the real pipeline disagrees: the per-frame temporal placement matches a reversed-order reference and not an in-order one. --- app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp index f45d1cf70..e2faf5434 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp @@ -40,7 +40,7 @@ constexpr std::array LSFG_DELTA_SHADERS{280, 286, 287, 288, 289, } [[nodiscard]] constexpr float LsfgTimestamp(size_t generation, size_t generation_count) { - return static_cast(generation_count - generation) / + return static_cast(generation + 1) / static_cast(generation_count + 1); } From a9335116c0a481c85eaa4432acb00b50c859bc86 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Mon, 24 Aug 2026 12:56:43 -0400 Subject: [PATCH 21/35] Give frame generation its own drawer tab and let the limiter reach 15 The frame generation controls move out of the FX tab and the FPS limiter out of the HUD tab into a new FG tab, sitting between HUD and Gyro on the rail. Frame pacing now lives in one place instead of being split across two panes that were each about something else. The limiter floor drops from 30 to 15, both in the session drawer and in the per-game settings slider, so a game saved at 15 is not clamped back to 30 the next time its settings are opened. --- app/src/main/feature/library/GameSettings.kt | 2 +- app/src/main/res/values-b+es+419/strings.xml | 1 + app/src/main/res/values-da/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-fi/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-hi/strings.xml | 1 + app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values-ja/strings.xml | 1 + app/src/main/res/values-ko/strings.xml | 1 + app/src/main/res/values-no/strings.xml | 1 + app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values-pt-rBR/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-ro/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values-sv/strings.xml | 1 + app/src/main/res/values-th/strings.xml | 1 + app/src/main/res/values-tr/strings.xml | 1 + app/src/main/res/values-uk/strings.xml | 1 + app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values-zh-rTW/strings.xml | 1 + app/src/main/res/values/refs.xml | 1 + app/src/main/res/values/strings.xml | 1 + .../display/XServerDisplayActivity.java | 3 +- .../display/XServerDrawerEffectsPane.kt | 143 ----------- .../display/XServerDrawerFrameGenPane.kt | 239 ++++++++++++++++++ .../runtime/display/XServerDrawerHudPane.kt | 28 +- .../main/runtime/display/XServerDrawerMenu.kt | 28 +- 30 files changed, 286 insertions(+), 181 deletions(-) create mode 100644 app/src/main/runtime/display/XServerDrawerFrameGenPane.kt diff --git a/app/src/main/feature/library/GameSettings.kt b/app/src/main/feature/library/GameSettings.kt index 72f76c456..b40040fea 100644 --- a/app/src/main/feature/library/GameSettings.kt +++ b/app/src/main/feature/library/GameSettings.kt @@ -1624,7 +1624,7 @@ private fun GeneralSection( if (!isContainer) { Spacer(Modifier.height(SettingSectionGap)) SettingGroup(verticalPadding = SettingTightGap) { - val fpsMin = 30 + val fpsMin = 15 // Cap the slider at the panel's highest supported refresh rate (parsed from entries like "120 Hz"); fall back to 60. val supportedMax = state.refreshRateEntries.value .mapNotNull { it.trim().substringBefore(" ").toIntOrNull() } diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index afa671cac..9ac5fc7f8 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -834,6 +834,7 @@ Por ejemplo, META para la tecla META, \n Restablecer efectos Calibración avanzada HUD + FG Giro FX Salida diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 94a600487..858c58aac 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -1138,6 +1138,7 @@ Installeret sti: CRT-effekt Avanceret kalibrering HUD + FG Gyro FX Mere diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 2a490411e..470e76ba9 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1138,6 +1138,7 @@ Installierter Pfad: CRT-Effekt Erweiterte Kalibrierung HUD + FG Gyro FX Mehr diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 99a3b7866..7dc25f828 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1138,6 +1138,7 @@ Ruta instalada: Efecto CRT Calibración avanzada HUD + FG Giro FX Más diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index e863cf682..50351bfa8 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -834,6 +834,7 @@ E.g. META for META-näppäin, \n Palauta tehosteet Lisäkalibrointi HUD + FG Gyro FX Ulostulo diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f3556ce6c..f5d098bbb 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1138,6 +1138,7 @@ Chemin installé : Effet CRT Étalonnage avancé HUD + FG Gyro FX Plus diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index c1f1c291f..81a6842ac 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -729,6 +729,7 @@ CRT प्रभाव उन्नत कैलिब्रेशन HUD + फ़्रेम Gyro FX और diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 808a64fa2..17a24cda1 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1138,6 +1138,7 @@ Percorso installato: Effetto CRT Calibrazione avanzata HUD + FG Giro FX Altro diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index bfe7a62c2..a2226fbac 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -834,6 +834,7 @@ エフェクトをリセット 詳細キャリブレーション HUD + フレーム生成 ジャイロ エフェクト 出力 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index cd524db50..f794e41c5 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -1138,6 +1138,7 @@ CRT 효과 고급 보정 HUD + 프레임 생성 자이로 FX 더 보기 diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index 0f2d9a257..c42975b9f 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -834,6 +834,7 @@ F.eks. META for META-tast, \n Nullstill effekter Avansert kalibrering HUD + FG Gyro FX Utgang diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 524aba57c..c0ea78041 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1144,6 +1144,7 @@ Zainstalowana ścieżka: Efekt CRT Zaawansowana kalibracja HUD + FG Gyro FX Więcej diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 95e5e0ecc..07a4eb39e 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1138,6 +1138,7 @@ Caminho instalado: Efeito CRT Calibração avançada HUD + FG Giro FX Mais diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 5550fcc4f..a6fd5ddc6 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -834,6 +834,7 @@ Por ex. META para tecla META, \n Repor efeitos Calibração avançada HUD + FG Giro FX Saída diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 8dba3b72c..80a8b4d4c 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -1138,6 +1138,7 @@ Cale instalata: Efect CRT Calibrare avansată HUD + FG Giro FX Mai multe diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 5011bd819..1177a6ae9 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -681,6 +681,7 @@ ЭЛТ-эффект Расширенная калибровка HUD + Кадры Гироскоп Эффекты Еще diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 6f6f67519..585b40c9d 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -834,6 +834,7 @@ T.ex. META för META-tangent, \n Återställ effekter Avancerad kalibrering HUD + FG Gyro FX Utgång diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index cba9a1826..2a1cf7e65 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -834,6 +834,7 @@ รีเซ็ตเอฟเฟกต์ การปรับเทียบขั้นสูง HUD + สร้างเฟรม ไจโร เอฟเฟกต์ เอาต์พุต diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index a2cadb0ef..e2e226746 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -834,6 +834,7 @@ E.g. META için META tuşu, \n Efektleri sıfırla Gelişmiş kalibrasyon HUD + FG Jiro FX Çıkış diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 2039f222d..14d190a6f 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1144,6 +1144,7 @@ Ефект CRT Розширене калібрування HUD + Кадри Гіро FX Більше diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index e1056561a..14fe743fc 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1138,6 +1138,7 @@ CRT 效果 高级校准 HUD + 帧生成 陀螺仪 FX 更多 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 09bb947a6..4cdcef5cd 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1138,6 +1138,7 @@ CRT 效果 進階校準 HUD + 影格生成 陀螺儀 FX 更多 diff --git a/app/src/main/res/values/refs.xml b/app/src/main/res/values/refs.xml index af4194ce9..b3ff36d64 100644 --- a/app/src/main/res/values/refs.xml +++ b/app/src/main/res/values/refs.xml @@ -23,6 +23,7 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 53c1e01f3..051bba017 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -893,6 +893,7 @@ E.g. META for META key, \n Reset effects Advanced calibration HUD + FG Gyro FX Output diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index 55d1fc8d8..d39209fac 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -4473,7 +4473,8 @@ private void renderDrawerMenu() { frameGenEnabled, frameGenMultiplier, frameGenTargetRate, - frameGenFlowScale); + frameGenFlowScale, + getString(R.string.session_drawer_frame_generation)); // Always-present "Output" tab (live controls while swapped, otherwise a Cast entry point). if (externalDisplayController != null) { diff --git a/app/src/main/runtime/display/XServerDrawerEffectsPane.kt b/app/src/main/runtime/display/XServerDrawerEffectsPane.kt index 9e1ec16a0..fda9ebd6a 100644 --- a/app/src/main/runtime/display/XServerDrawerEffectsPane.kt +++ b/app/src/main/runtime/display/XServerDrawerEffectsPane.kt @@ -202,10 +202,6 @@ internal fun ScreenEffectsPaneContent( .padding(horizontal = (12f * paneScale).dp, vertical = (12f * paneScale).dp), verticalArrangement = Arrangement.spacedBy((10f * paneScale).dp), ) { - FrameGenerationSection(state = state, listener = listener, paneScale = paneScale) - - ThinDivider() - Column(verticalArrangement = Arrangement.spacedBy((8f * paneScale).dp)) { PaneSectionLabel(stringResource(R.string.shortcuts_graphics_sgsr_full_title)) NavBooleanRow( @@ -468,142 +464,3 @@ internal fun ScreenEffectsPaneContent( } } } - -@Composable -private fun FrameGenerationSection( - state: XServerDrawerState, - listener: XServerDrawerActionListener, - paneScale: Float, -) { - Column(verticalArrangement = Arrangement.spacedBy((8f * paneScale).dp)) { - PaneSectionLabel(stringResource(R.string.session_drawer_frame_generation)) - - if (!state.frameGenAvailable) { - FrameGenNote(stringResource(R.string.session_drawer_frame_generation_missing), paneScale) - } else { - NavBooleanRow( - title = stringResource(R.string.session_drawer_frame_generation_enable), - checked = state.frameGenEnabled, - onCheckedChange = listener::onFrameGenEnabledChanged, - ) - - FrameGenNote(stringResource(R.string.session_drawer_frame_generation_note), paneScale) - - AnimatedVisibility( - visible = state.frameGenEnabled, - enter = - expandVertically( - animationSpec = tween(durationMillis = 220, easing = FastOutSlowInEasing), - expandFrom = Alignment.Top, - ) + fadeIn(animationSpec = tween(durationMillis = 160, easing = FastOutSlowInEasing)), - exit = - shrinkVertically( - animationSpec = tween(durationMillis = 180, easing = FastOutSlowInEasing), - shrinkTowards = Alignment.Top, - ) + fadeOut(animationSpec = tween(durationMillis = 120, easing = FastOutSlowInEasing)), - ) { - Column(verticalArrangement = Arrangement.spacedBy((8f * paneScale).dp)) { - FrameGenFieldLabel( - stringResource(R.string.session_drawer_frame_generation_target), - paneScale, - ) - - val rates = - remember(state.maxRefreshRate, state.frameGenTargetRate) { - (FrameGenTargetRates.filter { it <= state.maxRefreshRate } + - listOfNotNull(state.frameGenTargetRate.takeIf { it > 0 })) - .distinct() - .sorted() - } - - ChipFlow { - HUDToggleChip( - label = stringResource(R.string.session_drawer_frame_generation_target_off), - checked = state.frameGenTargetRate == 0, - onClick = { listener.onFrameGenTargetRateSelected(0) }, - modifier = Modifier.paneNavItem( - cornerRadius = (16f * paneScale).dp, - onActivate = { listener.onFrameGenTargetRateSelected(0) }, - ), - ) - rates.forEach { rate -> - HUDToggleChip( - label = stringResource( - R.string.session_drawer_frame_generation_target_value, - rate, - ), - checked = state.frameGenTargetRate == rate, - onClick = { listener.onFrameGenTargetRateSelected(rate) }, - modifier = Modifier.paneNavItem( - cornerRadius = (16f * paneScale).dp, - onActivate = { listener.onFrameGenTargetRateSelected(rate) }, - ), - ) - } - } - - if (state.frameGenTargetRate == 0) { - FrameGenFieldLabel( - stringResource(R.string.session_drawer_frame_generation_multiplier), - paneScale, - ) - ChipFlow { - FrameGenMultipliers.forEach { multiplier -> - HUDToggleChip( - label = stringResource( - R.string.session_drawer_frame_generation_multiplier_value, - multiplier, - ), - checked = state.frameGenMultiplier == multiplier, - onClick = { listener.onFrameGenMultiplierSelected(multiplier) }, - modifier = Modifier.paneNavItem( - cornerRadius = (16f * paneScale).dp, - onActivate = { listener.onFrameGenMultiplierSelected(multiplier) }, - ), - ) - } - } - } else { - FrameGenNote( - stringResource(R.string.session_drawer_frame_generation_target_note), - paneScale, - ) - } - - NavSliderRow( - label = stringResource(R.string.session_drawer_frame_generation_flow_scale), - valueText = "${state.frameGenFlowScale}%", - value = state.frameGenFlowScale.toFloat(), - valueRange = FrameGenFlowScaleMin.toFloat()..FrameGenFlowScaleMax.toFloat(), - steps = (FrameGenFlowScaleMax - FrameGenFlowScaleMin) / 5 - 1, - onValueChange = { - listener.onFrameGenFlowScaleChanged( - it.roundToInt().coerceIn(FrameGenFlowScaleMin, FrameGenFlowScaleMax), - ) - }, - ) - } - } - } - } -} - -@Composable -private fun FrameGenFieldLabel(text: String, paneScale: Float) { - Text( - text = text, - color = DrawerTextSecondary, - fontSize = (12f * paneScale).sp, - fontWeight = FontWeight.Medium, - ) -} - -@Composable -private fun FrameGenNote(text: String, paneScale: Float) { - Text( - text = text, - color = DrawerTextSecondary, - fontSize = (11f * paneScale).sp, - lineHeight = (15f * paneScale).sp, - ) -} diff --git a/app/src/main/runtime/display/XServerDrawerFrameGenPane.kt b/app/src/main/runtime/display/XServerDrawerFrameGenPane.kt new file mode 100644 index 000000000..cccd1d6a5 --- /dev/null +++ b/app/src/main/runtime/display/XServerDrawerFrameGenPane.kt @@ -0,0 +1,239 @@ +package com.winlator.cmod.runtime.display + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.winlator.cmod.R +import kotlin.math.roundToInt + +@Composable +internal fun FrameGenPaneContent( + state: XServerDrawerState, + listener: XServerDrawerActionListener, +) { + var fpsLimitMemory by remember { + mutableStateOf(if (state.fpsLimit > 0) state.fpsLimit else FPS_LIMITER_DEFAULT) + } + LaunchedEffect(state.fpsLimit) { + if (state.fpsLimit > 0) fpsLimitMemory = state.fpsLimit + } + + BoxWithConstraints(modifier = Modifier.fillMaxSize()) { + val paneScale = computePaneScale(maxHeight) + CompositionLocalProvider(LocalPaneScale provides paneScale) { + Column( + modifier = + Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + .padding(horizontal = (12f * paneScale).dp, vertical = (12f * paneScale).dp), + verticalArrangement = Arrangement.spacedBy((10f * paneScale).dp), + ) { + FrameGenerationSection(state = state, listener = listener, paneScale = paneScale) + + ThinDivider() + + Column(verticalArrangement = Arrangement.spacedBy((8f * paneScale).dp)) { + PaneSectionLabel(stringResource(R.string.session_drawer_fps_limiter)) + + Box( + Modifier.fillMaxWidth().paneNavItem( + cornerRadius = (12f * paneScale).dp, + onActivate = { + listener.onFPSLimitChanged( + if (state.fpsLimit > 0) { + 0 + } else { + fpsLimitMemory.coerceIn(FPS_LIMITER_MIN, state.maxRefreshRate) + }, + ) + }, + onAdjust = { dir -> + val base = if (state.fpsLimit > 0) state.fpsLimit else fpsLimitMemory + val q = base / 5.0 + val units = if (dir > 0) Math.floor(q + 1e-4) + 1 else Math.ceil(q - 1e-4) - 1 + listener.onFPSLimitChanged( + (units * 5).toInt().coerceIn(FPS_LIMITER_MIN, state.maxRefreshRate), + ) + }, + ), + ) { + FPSLimiterCard( + currentLimit = state.fpsLimit, + maxRefreshRate = state.maxRefreshRate, + onLimitChanged = listener::onFPSLimitChanged, + ) + } + } + } + } + } +} + +@Composable +private fun FrameGenerationSection( + state: XServerDrawerState, + listener: XServerDrawerActionListener, + paneScale: Float, +) { + Column(verticalArrangement = Arrangement.spacedBy((8f * paneScale).dp)) { + PaneSectionLabel(stringResource(R.string.session_drawer_frame_generation)) + + if (!state.frameGenAvailable) { + FrameGenNote(stringResource(R.string.session_drawer_frame_generation_missing), paneScale) + } else { + NavBooleanRow( + title = stringResource(R.string.session_drawer_frame_generation_enable), + checked = state.frameGenEnabled, + onCheckedChange = listener::onFrameGenEnabledChanged, + ) + + FrameGenNote(stringResource(R.string.session_drawer_frame_generation_note), paneScale) + + AnimatedVisibility( + visible = state.frameGenEnabled, + enter = + expandVertically( + animationSpec = tween(durationMillis = 220, easing = FastOutSlowInEasing), + expandFrom = Alignment.Top, + ) + fadeIn(animationSpec = tween(durationMillis = 160, easing = FastOutSlowInEasing)), + exit = + shrinkVertically( + animationSpec = tween(durationMillis = 180, easing = FastOutSlowInEasing), + shrinkTowards = Alignment.Top, + ) + fadeOut(animationSpec = tween(durationMillis = 120, easing = FastOutSlowInEasing)), + ) { + Column(verticalArrangement = Arrangement.spacedBy((8f * paneScale).dp)) { + FrameGenFieldLabel( + stringResource(R.string.session_drawer_frame_generation_target), + paneScale, + ) + + val rates = + remember(state.maxRefreshRate, state.frameGenTargetRate) { + ( + FrameGenTargetRates.filter { it <= state.maxRefreshRate } + + listOfNotNull(state.frameGenTargetRate.takeIf { it > 0 }) + ) + .distinct() + .sorted() + } + + ChipFlow { + HUDToggleChip( + label = stringResource(R.string.session_drawer_frame_generation_target_off), + checked = state.frameGenTargetRate == 0, + onClick = { listener.onFrameGenTargetRateSelected(0) }, + modifier = Modifier.paneNavItem( + cornerRadius = (16f * paneScale).dp, + onActivate = { listener.onFrameGenTargetRateSelected(0) }, + ), + ) + rates.forEach { rate -> + HUDToggleChip( + label = stringResource( + R.string.session_drawer_frame_generation_target_value, + rate, + ), + checked = state.frameGenTargetRate == rate, + onClick = { listener.onFrameGenTargetRateSelected(rate) }, + modifier = Modifier.paneNavItem( + cornerRadius = (16f * paneScale).dp, + onActivate = { listener.onFrameGenTargetRateSelected(rate) }, + ), + ) + } + } + + if (state.frameGenTargetRate == 0) { + FrameGenFieldLabel( + stringResource(R.string.session_drawer_frame_generation_multiplier), + paneScale, + ) + ChipFlow { + FrameGenMultipliers.forEach { multiplier -> + HUDToggleChip( + label = stringResource( + R.string.session_drawer_frame_generation_multiplier_value, + multiplier, + ), + checked = state.frameGenMultiplier == multiplier, + onClick = { listener.onFrameGenMultiplierSelected(multiplier) }, + modifier = Modifier.paneNavItem( + cornerRadius = (16f * paneScale).dp, + onActivate = { listener.onFrameGenMultiplierSelected(multiplier) }, + ), + ) + } + } + } else { + FrameGenNote( + stringResource(R.string.session_drawer_frame_generation_target_note), + paneScale, + ) + } + + NavSliderRow( + label = stringResource(R.string.session_drawer_frame_generation_flow_scale), + valueText = "${state.frameGenFlowScale}%", + value = state.frameGenFlowScale.toFloat(), + valueRange = FrameGenFlowScaleMin.toFloat()..FrameGenFlowScaleMax.toFloat(), + steps = (FrameGenFlowScaleMax - FrameGenFlowScaleMin) / 5 - 1, + onValueChange = { + listener.onFrameGenFlowScaleChanged( + it.roundToInt().coerceIn(FrameGenFlowScaleMin, FrameGenFlowScaleMax), + ) + }, + ) + } + } + } + } +} + +@Composable +private fun FrameGenFieldLabel(text: String, paneScale: Float) { + Text( + text = text, + color = DrawerTextSecondary, + fontSize = (12f * paneScale).sp, + fontWeight = FontWeight.Medium, + ) +} + +@Composable +private fun FrameGenNote(text: String, paneScale: Float) { + Text( + text = text, + color = DrawerTextSecondary, + fontSize = (11f * paneScale).sp, + lineHeight = (15f * paneScale).sp, + ) +} diff --git a/app/src/main/runtime/display/XServerDrawerHudPane.kt b/app/src/main/runtime/display/XServerDrawerHudPane.kt index 883712942..5b14bda61 100644 --- a/app/src/main/runtime/display/XServerDrawerHudPane.kt +++ b/app/src/main/runtime/display/XServerDrawerHudPane.kt @@ -192,12 +192,6 @@ internal fun HUDPaneContent( listener: XServerDrawerActionListener, ) { var activeEditor by remember { mutableStateOf(null) } - var fpsLimitMemory by remember { - mutableStateOf(if (state.fpsLimit > 0) state.fpsLimit else FPS_LIMITER_DEFAULT) - } - LaunchedEffect(state.fpsLimit) { - if (state.fpsLimit > 0) fpsLimitMemory = state.fpsLimit - } val elementNames = listOf( stringResource(R.string.session_drawer_hud_element_fps), @@ -258,27 +252,7 @@ internal fun HUDPaneContent( onCheckedChange = { listener.onActionSelected(R.id.main_menu_fps_monitor) }, ) - // FPS limiter sits directly under the HUD toggle, shown whether the HUD is on or off. - Box( - Modifier.fillMaxWidth().paneNavItem( - cornerRadius = (12f * paneScale).dp, - onActivate = { listener.onFPSLimitChanged(if (state.fpsLimit > 0) 0 else fpsLimitMemory.coerceIn(FPS_LIMITER_MIN, state.maxRefreshRate)) }, - onAdjust = { dir -> - val base = if (state.fpsLimit > 0) state.fpsLimit else fpsLimitMemory - val q = base / 5.0 - val units = if (dir > 0) Math.floor(q + 1e-4) + 1 else Math.ceil(q - 1e-4) - 1 - listener.onFPSLimitChanged((units * 5).toInt().coerceIn(FPS_LIMITER_MIN, state.maxRefreshRate)) - }, - ), - ) { - FPSLimiterCard( - currentLimit = state.fpsLimit, - maxRefreshRate = state.maxRefreshRate, - onLimitChanged = listener::onFPSLimitChanged, - ) - } - - // Mango-style HUD toggle + settings gear, shown like the limiter whether the HUD is on or off. + // Mango-style HUD toggle + settings gear, shown whether the HUD is on or off. var mangoSettingsOpen by remember { mutableStateOf(false) } Row( horizontalArrangement = Arrangement.spacedBy((8f * paneScale).dp), diff --git a/app/src/main/runtime/display/XServerDrawerMenu.kt b/app/src/main/runtime/display/XServerDrawerMenu.kt index 7d19d85a9..3002ba98b 100644 --- a/app/src/main/runtime/display/XServerDrawerMenu.kt +++ b/app/src/main/runtime/display/XServerDrawerMenu.kt @@ -88,6 +88,7 @@ import androidx.compose.material.icons.outlined.PlayArrow import androidx.compose.material.icons.outlined.ScreenRotation import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Share +import androidx.compose.material.icons.outlined.Speed import androidx.compose.material.icons.outlined.SportsEsports import androidx.compose.material.icons.outlined.Terminal import androidx.compose.material.icons.outlined.TouchApp @@ -467,7 +468,7 @@ internal enum class HUDMetricEditor( BACKGROUND_ALPHA(minPercent = 10, maxPercent = 100), } -internal enum class DrawerPane { INPUT_CONTROLS, HUD, GYROSCOPE, SCREEN_EFFECTS, RESHADE, OUTPUT, TASK_MANAGER, LOGS, TOUCH } +internal enum class DrawerPane { INPUT_CONTROLS, HUD, FRAME_GEN, GYROSCOPE, SCREEN_EFFECTS, RESHADE, OUTPUT, TASK_MANAGER, LOGS, TOUCH } internal const val LogsPaneMaxLines = 2000 internal const val LogsFlushIntervalMs = 200L @@ -520,6 +521,12 @@ private val RAIL_PANES = itemId = R.id.main_menu_fps_monitor, labelRes = R.string.session_drawer_rail_label_hud, ), + RailPaneSpec( + pane = DrawerPane.FRAME_GEN, + itemId = R.id.main_menu_frame_generation, + labelRes = R.string.session_drawer_rail_label_frame_gen, + iconOverride = Icons.Outlined.Speed, + ), RailPaneSpec( pane = DrawerPane.GYROSCOPE, itemId = R.id.main_menu_gyroscope, @@ -1487,16 +1494,18 @@ fun withFrameGenState( multiplier: Int, targetRate: Int, flowScale: Int, + frameGenTitle: String, ): XServerDrawerState = state.copy( items = - if (!enabled) { - state.items - } else { - state.items.map { - if (it.itemId == R.id.main_menu_screen_effects) it.copy(active = true) else it - } - }, + state.items + + XServerDrawerItem( + itemId = R.id.main_menu_frame_generation, + title = frameGenTitle, + subtitle = "", + icon = Icons.Outlined.Speed, + active = enabled || state.fpsLimit > 0, + ), frameGenAvailable = available, frameGenEnabled = enabled, frameGenMultiplier = multiplier.coerceIn(2, FrameGenMultipliers.last()), @@ -1715,6 +1724,7 @@ internal fun XServerDrawerContent( when (pane) { DrawerPane.INPUT_CONTROLS -> InputControlsPaneContent(state = state, listener = listener) DrawerPane.HUD -> HUDPaneContent(state = state, listener = listener) + DrawerPane.FRAME_GEN -> FrameGenPaneContent(state = state, listener = listener) DrawerPane.GYROSCOPE -> GyroscopePaneContent(state = state, listener = listener) DrawerPane.TOUCH -> TouchPaneContent(state = state, listener = listener, onClose = { onOpenPaneChange(null) }) DrawerPane.SCREEN_EFFECTS -> ScreenEffectsPaneContent(state = state, listener = listener) @@ -2841,7 +2851,7 @@ internal fun DrawerBooleanRow( internal val RECORD_QUALITY_LABELS = listOf("Performance", "Balance", "Quality") -internal const val FPS_LIMITER_MIN = 30 +internal const val FPS_LIMITER_MIN = 15 internal const val FPS_LIMITER_DEFAULT = 60 @OptIn(ExperimentalLayoutApi::class) From a4a5554d7118368f9193d1be37e93a199479d65e Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Mon, 24 Aug 2026 16:03:53 -0400 Subject: [PATCH 22/35] Stop frame generation from spending the game's own frames The pacer now sizes itself from the guest's present rate and the panel refresh, so it only ever fills vblanks the game was not going to use. Fixed multipliers take the integer headroom; target rates take the fractional one so the credit accumulator can still reach the rate asked for. Raising the count is verified against the rate it displaced and reverted with an escalating backoff when it costs more than it returns. Generated images are claimed before the command buffer is recorded, and the chain interpolates for the count actually acquired, so a dropped frame shortens the run instead of mistiming what is left. The acquire timeout follows the refresh period rather than a fixed 3ms. Frame generation owns the display mode while it is on, preferring a rate that is both above its target and a whole multiple of the FPS limiter. The refresh rate reaches the pacer through an atomic rather than the render mutex, keeping it off the UI thread. --- .../main/cpp/winlator/vk/lsfg/lsfg_common.hpp | 5 +- .../main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp | 250 +++++++++++++++++- .../main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp | 55 +++- .../main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp | 71 +++-- app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h | 10 +- app/src/main/cpp/winlator/vk/vk_renderer.c | 98 +++++-- app/src/main/cpp/winlator/vk/vk_state.h | 7 + .../display/XServerDisplayActivity.java | 55 ++-- .../display/renderer/VulkanRenderer.java | 21 ++ .../xserver/extensions/PresentExtension.java | 4 +- .../main/shared/android/RefreshRateUtils.java | 2 +- 11 files changed, 505 insertions(+), 73 deletions(-) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp index e2faf5434..31b5e5fb3 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_common.hpp @@ -28,6 +28,7 @@ constexpr size_t LSFG_MAX_GENERATIONS = 3; constexpr size_t LSFG_MIP_LEVELS = 7; constexpr size_t LSFG_GENERATION_SLOTS = LSFG_MAX_GENERATIONS * (LSFG_MAX_GENERATIONS + 1) / 2; +constexpr size_t LSFG_BARRIER_CAPACITY = 32; constexpr std::array LSFG_ALPHA_SHADERS{290, 291, 292, 293}; constexpr std::array LSFG_BETA_SHADERS{298, 299, 300, 301, 302}; @@ -194,7 +195,9 @@ class LsfgResources { class LsfgBarriers { public: - explicit LsfgBarriers(VkCommandBuffer cmdbuf_) : cmdbuf{cmdbuf_} {} + explicit LsfgBarriers(VkCommandBuffer cmdbuf_) : cmdbuf{cmdbuf_} { + barriers.reserve(LSFG_BARRIER_CAPACITY); + } LsfgBarriers& WriteToRead(LsfgImage& image); LsfgBarriers& ReadToWrite(LsfgImage& image); diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp index ec1158956..d4fc365d1 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp @@ -24,8 +24,35 @@ constexpr float PROBE_MARGINAL_GAIN = 1.15f; constexpr float TARGET_SATISFIED_RATIO = 0.95f; constexpr float UNLOADED_BASE_RETENTION = 0.75f; constexpr float CREDIT_EPSILON = 1.0e-4f; +constexpr float SOURCE_SMOOTHING = 0.15f; +constexpr float SOURCE_STALE_SECONDS = 0.5f; +constexpr float HEADROOM_EPSILON = 0.02f; +constexpr float HEADROOM_HYSTERESIS = 0.20f; +constexpr float REGRESSION_RATIO = 1.20f; +constexpr uint32_t MIN_RATE_SAMPLES = 20; constexpr uint32_t MAX_PROBE_FAILURES = 4; +constexpr float COST_PROBE_GAIN = 0.10f; + +constexpr auto RAISE_SETTLE_DURATION = std::chrono::milliseconds(600); +constexpr uint32_t MAX_COST_FAILURES = 4; + +[[nodiscard]] std::chrono::steady_clock::duration CostBackoff(uint32_t failures) { + switch (failures) { + case 0: + case 1: + return std::chrono::seconds(8); + case 2: + return std::chrono::seconds(20); + case 3: + return std::chrono::seconds(45); + default: + return std::chrono::seconds(90); + } +} +constexpr auto COST_PROBE_INTERVAL = std::chrono::seconds(15); +constexpr auto COST_PROBE_WINDOW = std::chrono::milliseconds(700); + constexpr auto STABILIZATION_DURATION = std::chrono::seconds(1); constexpr auto PROBE_DURATION = std::chrono::seconds(1); constexpr auto DEFICIT_DURATION = std::chrono::seconds(1); @@ -52,7 +79,159 @@ size_t LsfgPacer::MaxGenerations() const { return std::min(config.multiplier, LSFG_MAX_MULTIPLIER) - 1; } -LsfgPlan LsfgPacer::Plan(size_t capacity) { +void LsfgPacer::TrackSourceRate(Clock::time_point now, uint64_t source_frames) { + if (!last_source_sample) { + last_source_sample = now; + last_source_frames = source_frames; + return; + } + + const float elapsed = std::chrono::duration(now - *last_source_sample).count(); + if (source_frames <= last_source_frames) { + if (elapsed > SOURCE_STALE_SECONDS) { + source_interval = 0.0f; + last_source_sample = now; + last_source_frames = source_frames; + } + return; + } + + last_source_sample = now; + const uint64_t drawn = source_frames - last_source_frames; + last_source_frames = source_frames; + if (elapsed <= 0.0f || elapsed > SOURCE_STALE_SECONDS) { + source_interval = 0.0f; + return; + } + + last_drawn = drawn; + last_elapsed = elapsed; + const float measured = elapsed / static_cast(drawn); + source_interval = source_interval > 0.0f + ? source_interval + (measured - source_interval) * SOURCE_SMOOTHING + : measured; + if (source_samples < MIN_RATE_SAMPLES) ++source_samples; +} + +void LsfgPacer::TrackLoopRate(float interval_seconds) { + loop_interval = loop_interval > 0.0f + ? loop_interval + (interval_seconds - loop_interval) * INTERVAL_SMOOTHING + : interval_seconds; + if (loop_samples < MIN_RATE_SAMPLES) ++loop_samples; +} + +bool LsfgPacer::RatesSettled() const { + return source_samples >= MIN_RATE_SAMPLES && loop_samples >= MIN_RATE_SAMPLES; +} + +size_t LsfgPacer::CostLimit(Clock::time_point now, size_t current) { + if (cost_probe_until) { + const size_t probe_limit = cost_probe_from > 0 ? cost_probe_from - 1 : 0; + if (now < *cost_probe_until) { + cost_probe_active = true; + return probe_limit; + } + cost_probe_until.reset(); + cost_probe_active = true; + if (cost_probe_baseline > 0.0f && loop_interval > 0.0f && + loop_interval < cost_probe_baseline * (1.0f - COST_PROBE_GAIN)) { + cost_failures = std::min(cost_failures + 1, MAX_COST_FAILURES); + cost_ceiling = probe_limit; + cost_backoff_until = now + CostBackoff(cost_failures); + return cost_ceiling; + } + } else { + cost_probe_active = false; + } + + if (settle_until) { + if (now < *settle_until) { + return current; + } + settle_until.reset(); + + const bool source_regressed = + pre_raise_source_interval > 0.0f && source_interval > 0.0f && + source_interval > pre_raise_source_interval * REGRESSION_RATIO; + const bool loop_regressed = pre_raise_loop_interval > 0.0f && loop_interval > 0.0f && + loop_interval > pre_raise_loop_interval * REGRESSION_RATIO; + + if (source_regressed || loop_regressed) { + cost_failures = std::min(cost_failures + 1, MAX_COST_FAILURES); + cost_ceiling = pre_raise_limit; + cost_backoff_until = now + CostBackoff(cost_failures); + return cost_ceiling; + } + cost_failures = 0; + } + + if (cost_backoff_until) { + if (now < *cost_backoff_until) { + return cost_ceiling; + } + cost_backoff_until.reset(); + cost_ceiling = LSFG_MAX_MULTIPLIER - 1; + } + + if (current > 0 && !settle_until && RatesSettled()) { + if (!next_cost_probe) { + next_cost_probe = now + COST_PROBE_INTERVAL; + } else if (now >= *next_cost_probe) { + cost_probe_baseline = loop_interval; + cost_probe_from = current; + cost_probe_until = now + COST_PROBE_WINDOW; + next_cost_probe = now + COST_PROBE_INTERVAL; + cost_probe_active = true; + return current - 1; + } + } + + return cost_ceiling; +} + +void LsfgPacer::NoteLimitChange(Clock::time_point now, size_t previous_limit) { + if (cost_probe_active) { + settle_until.reset(); + return; + } + if (limit > previous_limit) { + if (!RatesSettled()) { + settle_until.reset(); + return; + } + pre_raise_source_interval = source_interval; + pre_raise_loop_interval = loop_interval; + pre_raise_limit = previous_limit; + settle_until = now + RAISE_SETTLE_DURATION; + } else if (limit < previous_limit) { + settle_until.reset(); + } +} + +size_t LsfgPacer::HeadroomLimit(size_t current, bool allow_fractional) const { + if (config.refresh_rate <= 0.0f || source_interval <= 0.0f || + source_samples < MIN_RATE_SAMPLES) { + return 0; + } + + const float slots = config.refresh_rate * source_interval; + + if (allow_fractional) { + const float budget = std::ceil(slots - HEADROOM_EPSILON); + return budget < 2.0f ? 0 : static_cast(budget) - 1; + } + + float budget = slots + HEADROOM_EPSILON; + if (current > 0 && slots + HEADROOM_HYSTERESIS >= static_cast(current + 1)) { + budget = std::max(budget, static_cast(current + 1)); + } + if (budget < 2.0f) { + return 0; + } + return static_cast(std::floor(budget)) - 1; +} + +LsfgPlan LsfgPacer::Plan(size_t capacity, uint64_t source_frames) { const size_t ceiling = std::min(capacity, MaxGenerations()); if (ceiling == 0) { Reset(); @@ -60,6 +239,7 @@ LsfgPlan LsfgPacer::Plan(size_t capacity) { } const Clock::time_point now = Clock::now(); + TrackSourceRate(now, source_frames); const size_t previous_generations = std::exchange(issued_generations, 0); if (!last_frame) { last_frame = now; @@ -75,7 +255,14 @@ LsfgPlan LsfgPacer::Plan(size_t capacity) { return {}; } - const float target_rate = static_cast(config.target_rate); + float target_rate = static_cast(config.target_rate); + if (target_rate > 0.0f && config.refresh_rate > 0.0f) { + target_rate = std::min(target_rate, config.refresh_rate); + } + + if (interval_seconds <= FIXED_DISCONTINUITY_SECONDS) { + TrackLoopRate(interval_seconds); + } if (target_rate == 0.0f) { output_credit = 0.0f; @@ -83,7 +270,9 @@ LsfgPlan LsfgPacer::Plan(size_t capacity) { issued_generations = 0; return {}; } - limit = ceiling; + const size_t previous_limit = limit; + limit = std::min({ceiling, HeadroomLimit(limit, false), CostLimit(now, limit)}); + NoteLimitChange(now, previous_limit); issued_generations = limit; return LsfgPlan{limit, limit > 0}; } @@ -127,7 +316,7 @@ LsfgPlan LsfgPacer::Plan(size_t capacity) { UpdateLimit(now, 1.0f / smoothed_interval, target_rate, ceiling); - const size_t allowed = std::min(limit, ceiling); + const size_t allowed = std::min({limit, ceiling, HeadroomLimit(limit, true)}); const float desired_outputs = smoothed_interval * target_rate; if (allowed == 0 || desired_outputs <= 1.0f) { output_credit = 0.0f; @@ -231,8 +420,61 @@ void LsfgPacer::Stabilize(Clock::time_point now) { output_credit = 0.0f; } +LsfgPacerStats LsfgPacer::Stats() const { + LsfgPacerStats stats; + stats.source_rate = source_interval > 0.0f ? 1.0f / source_interval : 0.0f; + stats.loop_rate = loop_interval > 0.0f ? 1.0f / loop_interval : 0.0f; + stats.refresh_rate = config.refresh_rate; + stats.slots = config.refresh_rate * source_interval; + stats.target_rate = static_cast(config.target_rate); + stats.limit = limit; + stats.cost_ceiling = cost_ceiling; + stats.settling = settle_until.has_value(); + stats.backing_off = cost_backoff_until.has_value(); + stats.rates_settled = RatesSettled(); + stats.probing = cost_probe_until.has_value(); + stats.cost_failures = cost_failures; + stats.last_drawn = last_drawn; + stats.last_elapsed = last_elapsed; + stats.source_frames = last_source_frames; + return stats; +} + +void LsfgPacer::ResetCostState() { + settle_until.reset(); + cost_backoff_until.reset(); + cost_probe_until.reset(); + next_cost_probe.reset(); + cost_probe_baseline = 0.0f; + cost_probe_from = 0; + pre_raise_limit = 0; + cost_probe_active = false; + cost_failures = 0; + pre_raise_source_interval = 0.0f; + pre_raise_loop_interval = 0.0f; + cost_ceiling = LSFG_MAX_MULTIPLIER - 1; +} + void LsfgPacer::Reset() { last_frame.reset(); + last_source_sample.reset(); + settle_until.reset(); + cost_backoff_until.reset(); + cost_probe_until.reset(); + next_cost_probe.reset(); + cost_probe_baseline = 0.0f; + cost_probe_from = 0; + pre_raise_limit = 0; + cost_probe_active = false; + cost_failures = 0; + last_source_frames = 0; + source_interval = 0.0f; + loop_interval = 0.0f; + pre_raise_source_interval = 0.0f; + pre_raise_loop_interval = 0.0f; + source_samples = 0; + loop_samples = 0; + cost_ceiling = LSFG_MAX_MULTIPLIER - 1; stable_until.reset(); probe_until.reset(); next_probe.reset(); diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp index 0bc365735..f932350d8 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp @@ -15,6 +15,7 @@ constexpr size_t LSFG_MAX_MULTIPLIER = 4; struct LsfgPacerConfig { uint32_t multiplier{2}; uint32_t target_rate{}; + float refresh_rate{}; }; struct LsfgPlan { @@ -22,15 +23,41 @@ struct LsfgPlan { bool warm{}; }; +struct LsfgPacerStats { + float source_rate{}; + float loop_rate{}; + float refresh_rate{}; + float target_rate{}; + float slots{}; + size_t limit{}; + size_t cost_ceiling{}; + bool settling{}; + bool backing_off{}; + bool rates_settled{}; + bool probing{}; + uint32_t cost_failures{}; + uint64_t last_drawn{}; + float last_elapsed{}; + uint64_t source_frames{}; +}; + class LsfgPacer { public: void SetConfig(const LsfgPacerConfig& config_) { config = config_; } + [[nodiscard]] const LsfgPacerConfig& Config() const { + return config; + } + [[nodiscard]] size_t MaxGenerations() const; - [[nodiscard]] LsfgPlan Plan(size_t capacity); + [[nodiscard]] LsfgPlan Plan(size_t capacity, uint64_t source_frames); + + [[nodiscard]] LsfgPacerStats Stats() const; + + void ResetCostState(); void Reset(); @@ -40,10 +67,36 @@ class LsfgPacer { void Stabilize(Clock::time_point now); void DeferEvaluations(Clock::duration amount); void UpdateLimit(Clock::time_point now, float base_rate, float target_rate, size_t ceiling); + void TrackSourceRate(Clock::time_point now, uint64_t source_frames); + void TrackLoopRate(float interval_seconds); + [[nodiscard]] bool RatesSettled() const; + [[nodiscard]] size_t HeadroomLimit(size_t current, bool allow_fractional) const; + [[nodiscard]] size_t CostLimit(Clock::time_point now, size_t current); + void NoteLimitChange(Clock::time_point now, size_t previous_limit); LsfgPacerConfig config; std::optional last_frame; + std::optional last_source_sample; + std::optional settle_until; + std::optional cost_backoff_until; + std::optional cost_probe_until; + std::optional next_cost_probe; + float cost_probe_baseline{}; + size_t cost_probe_from{}; + size_t pre_raise_limit{}; + uint32_t cost_failures{}; + bool cost_probe_active{}; + uint64_t last_source_frames{}; + float source_interval{}; + float loop_interval{}; + float pre_raise_source_interval{}; + float pre_raise_loop_interval{}; + uint32_t source_samples{}; + uint32_t loop_samples{}; + uint64_t last_drawn{}; + float last_elapsed{}; + size_t cost_ceiling{LSFG_MAX_MULTIPLIER - 1}; std::optional stable_until; std::optional probe_until; std::optional next_probe; diff --git a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp index 1976b7cf7..e8bc13065 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp @@ -17,6 +17,7 @@ namespace { constexpr uint64_t LSFG_REQUIRED_FRAMES = 2; constexpr uint32_t LSFG_RECURRENCE_FRAMES = 2; +constexpr uint64_t LSFG_TELEMETRY_INTERVAL = 120; VkImageMemoryBarrier MakeTransitionBarrier(VkImage image, VkAccessFlags src_access, VkAccessFlags dst_access, VkImageLayout old_layout, @@ -94,7 +95,9 @@ struct VkrLsfg { uint64_t frame_count{}; uint64_t last_count{}; size_t last_generations{}; + uint64_t plan_calls{}; uint32_t warm_streak{}; + bool warm{}; bool generated{}; bool unavailable{}; }; @@ -125,16 +128,30 @@ void vkr_lsfg_destroy(VkrLsfg* lsfg) { } void vkr_lsfg_configure(VkrLsfg* lsfg, uint32_t multiplier, uint32_t target_rate, - float flow_scale) { + float flow_scale, float refresh_rate) { if (!lsfg) return; - lsfg::LsfgPacerConfig config; + const lsfg::LsfgPacerConfig previous = lsfg->pacer.Config(); + lsfg::LsfgPacerConfig config = previous; config.multiplier = multiplier; config.target_rate = target_rate; + config.refresh_rate = refresh_rate; lsfg->pacer.SetConfig(config); + if (previous.multiplier != multiplier || previous.target_rate != target_rate) { + lsfg->pacer.ResetCostState(); + } lsfg->flow_scale = std::clamp(flow_scale, 0.25f, 1.0f); } +void vkr_lsfg_set_refresh_rate(VkrLsfg* lsfg, float refresh_rate) { + if (!lsfg) return; + + lsfg::LsfgPacerConfig config = lsfg->pacer.Config(); + if (config.refresh_rate == refresh_rate) return; + config.refresh_rate = refresh_rate; + lsfg->pacer.SetConfig(config); +} + bool vkr_lsfg_needs_rebuild(const VkrLsfg* lsfg, uint32_t width, uint32_t height, VkFormat format) { if (!lsfg || lsfg->unavailable) return false; @@ -165,7 +182,9 @@ bool vkr_lsfg_prepare(VkrLsfg* lsfg, uint32_t width, uint32_t height, VkFormat f lsfg->built_format = format; lsfg->built_flow_scale = lsfg->flow_scale; lsfg->frame_count = 0; + lsfg->plan_calls = 0; lsfg->warm_streak = 0; + lsfg->warm = false; lsfg->generated = false; lsfg->pacer.Reset(); LSFG_LOGI("chain built at %ux%u, flow scale %.2f", width, height, @@ -173,36 +192,51 @@ bool vkr_lsfg_prepare(VkrLsfg* lsfg, uint32_t width, uint32_t height, VkFormat f return true; } -uint32_t vkr_lsfg_plan(VkrLsfg* lsfg, uint32_t capacity) { +uint32_t vkr_lsfg_plan(VkrLsfg* lsfg, uint32_t capacity, uint64_t source_frames) { if (!lsfg || lsfg->unavailable) return 0; - lsfg->plan = lsfg->pacer.Plan(std::min(capacity, VKR_LSFG_MAX_GENERATIONS)); - return static_cast(lsfg->plan.generations); + + lsfg->plan = lsfg->pacer.Plan(std::min(capacity, VKR_LSFG_MAX_GENERATIONS), + source_frames); + + lsfg->warm = lsfg->plan.warm && lsfg->frame_count + 1 >= LSFG_REQUIRED_FRAMES; + lsfg->warm_streak = lsfg->warm ? lsfg->warm_streak + 1 : 0; + lsfg->generated = + lsfg->warm && lsfg->warm_streak >= LSFG_RECURRENCE_FRAMES && lsfg->plan.generations > 0; + + if ((lsfg->plan_calls++ % LSFG_TELEMETRY_INTERVAL) == 0) { + const lsfg::LsfgPacerStats stats = lsfg->pacer.Stats(); + LSFG_LOGI("pace gen=%zu max=%zu cap=%u guest=%.1f loop=%.1f out=%.1f refresh=%.1f " + "target=%.0f slots=%.2f ceil=%zu%s%s%s", + lsfg->plan.generations, lsfg->pacer.MaxGenerations(), capacity, + (double)stats.source_rate, (double)stats.loop_rate, + (double)(stats.loop_rate * (float)(lsfg->plan.generations + 1)), + (double)stats.refresh_rate, (double)stats.target_rate, + (double)stats.slots, stats.cost_ceiling, stats.settling ? " settling" : "", + stats.backing_off ? " backoff" : "", + stats.rates_settled ? (lsfg->warm ? "" : " cold") : " sampling"); + LSFG_LOGI("pace raw src=%llu drawn=%llu elapsed=%.4f fails=%u%s", + (unsigned long long)stats.source_frames, (unsigned long long)stats.last_drawn, + (double)stats.last_elapsed, stats.cost_failures, + stats.probing ? " probing" : ""); + } + + return lsfg->generated ? static_cast(lsfg->plan.generations) : 0; } void vkr_lsfg_process(VkrLsfg* lsfg, VkCommandBuffer cmd, VkImage source, uint32_t width, - uint32_t height) { + uint32_t height, uint32_t generations) { if (!lsfg || !lsfg->chain || !lsfg->chain->Valid()) return; const uint64_t count = lsfg->frame_count++; lsfg->last_count = count; - lsfg->last_generations = lsfg->plan.generations; - - const bool warm = lsfg->plan.warm && count + 1 >= LSFG_REQUIRED_FRAMES; - lsfg->warm_streak = warm ? lsfg->warm_streak + 1 : 0; - lsfg->generated = - warm && lsfg->warm_streak >= LSFG_RECURRENCE_FRAMES && lsfg->plan.generations > 0; + lsfg->last_generations = generations; CopyPresentedFrame(cmd, source, lsfg->chain->Input(count), VkExtent2D{width, height}); - if (warm) { + if (lsfg->warm) { lsfg->chain->DispatchShared(cmd, count); } } -uint32_t vkr_lsfg_generated_count(const VkrLsfg* lsfg) { - if (!lsfg || !lsfg->generated) return 0; - return static_cast(lsfg->last_generations); -} - void vkr_lsfg_generate_into(VkrLsfg* lsfg, VkCommandBuffer cmd, uint32_t generation, uint32_t target_index, VkImage target_image, VkImageView target_view, uint32_t width, uint32_t height) { @@ -224,6 +258,7 @@ void vkr_lsfg_reset(VkrLsfg* lsfg) { if (!lsfg) return; lsfg->pacer.Reset(); lsfg->warm_streak = 0; + lsfg->warm = false; lsfg->generated = false; lsfg->plan = {}; } diff --git a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h index 9231a0c6f..9eded59a3 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h +++ b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h @@ -18,19 +18,19 @@ VkrLsfg* vkr_lsfg_create(VkDevice device, VkPhysicalDevice physical_device, void vkr_lsfg_destroy(VkrLsfg* lsfg); void vkr_lsfg_configure(VkrLsfg* lsfg, uint32_t multiplier, uint32_t target_rate, - float flow_scale); + float flow_scale, float refresh_rate); + +void vkr_lsfg_set_refresh_rate(VkrLsfg* lsfg, float refresh_rate); bool vkr_lsfg_needs_rebuild(const VkrLsfg* lsfg, uint32_t width, uint32_t height, VkFormat format); bool vkr_lsfg_prepare(VkrLsfg* lsfg, uint32_t width, uint32_t height, VkFormat format); -uint32_t vkr_lsfg_plan(VkrLsfg* lsfg, uint32_t capacity); +uint32_t vkr_lsfg_plan(VkrLsfg* lsfg, uint32_t capacity, uint64_t source_frames); void vkr_lsfg_process(VkrLsfg* lsfg, VkCommandBuffer cmd, VkImage source, - uint32_t width, uint32_t height); - -uint32_t vkr_lsfg_generated_count(const VkrLsfg* lsfg); + uint32_t width, uint32_t height, uint32_t generations); void vkr_lsfg_generate_into(VkrLsfg* lsfg, VkCommandBuffer cmd, uint32_t generation, uint32_t target_index, VkImage target_image, VkImageView target_view, diff --git a/app/src/main/cpp/winlator/vk/vk_renderer.c b/app/src/main/cpp/winlator/vk/vk_renderer.c index 9a883ca29..aaff2ccf4 100644 --- a/app/src/main/cpp/winlator/vk/vk_renderer.c +++ b/app/src/main/cpp/winlator/vk/vk_renderer.c @@ -1853,7 +1853,8 @@ static void create_lsfg(VkRenderer* r) { } vkr_lsfg_configure(r->lsfg, r->framegen_multiplier ? r->framegen_multiplier : 2u, r->framegen_target_rate, - r->framegen_flow_scale > 0.0f ? r->framegen_flow_scale : 1.0f); + r->framegen_flow_scale > 0.0f ? r->framegen_flow_scale : 1.0f, + r->framegen_refresh_rate); } static uint32_t framegen_extra_images(const VkRenderer* r) { @@ -2473,8 +2474,14 @@ static bool record_and_submit_frame(VkRenderer* r) { destroy_composite_targets(r); } + uint32_t framegen_planned = 0; if (via_composite && r->lsfg) { - vkr_lsfg_plan(r->lsfg, framegen_capacity); + const int32_t pending_mhz = __atomic_load_n(&r->framegen_refresh_mhz, __ATOMIC_RELAXED); + r->framegen_refresh_rate = pending_mhz > 0 ? (float)pending_mhz / 1000.0f : 0.0f; + vkr_lsfg_set_refresh_rate(r->lsfg, r->framegen_refresh_rate); + framegen_planned = vkr_lsfg_plan(r->lsfg, framegen_capacity, + __atomic_load_n(&r->framegen_source_frames, + __ATOMIC_RELAXED)); } VkCompositeTarget* composite = via_composite ? &r->composite[r->frame_index] : NULL; @@ -2501,6 +2508,35 @@ static bool record_and_submit_frame(VkRenderer* r) { } VkSemaphore render_finished = r->swapchain_render_finished[image_index]; + uint64_t gen_acquire_timeout = VK_FRAMEGEN_ACQUIRE_TIMEOUT_NS; + if (r->framegen_refresh_rate > 1.0f) { + gen_acquire_timeout = (uint64_t)(1000000000.0f / r->framegen_refresh_rate); + if (gen_acquire_timeout < VK_FRAMEGEN_ACQUIRE_TIMEOUT_NS) { + gen_acquire_timeout = VK_FRAMEGEN_ACQUIRE_TIMEOUT_NS; + } + if (gen_acquire_timeout > VK_FRAMEGEN_ACQUIRE_TIMEOUT_MAX_NS) { + gen_acquire_timeout = VK_FRAMEGEN_ACQUIRE_TIMEOUT_MAX_NS; + } + } + + uint32_t gen_count = 0; + uint32_t gen_image_index[VKR_LSFG_MAX_GENERATIONS] = {0}; + for (uint32_t g = 0; g < framegen_planned; g++) { + uint32_t idx = 0; + VkResult ga = vkAcquireNextImageKHR(r->device, r->swapchain, gen_acquire_timeout, + f->image_available_gen[g], VK_NULL_HANDLE, &idx); + if (ga != VK_SUCCESS && ga != VK_SUBOPTIMAL_KHR) { + if (r->framegen_acquire_misses++ % 120 == 0) { + VK_LOGW("Generated frame %u/%u dropped: acquire returned %d " + "(swapchain images=%u capacity=%u)", + g + 1, framegen_planned, (int)ga, r->swapchain_image_count, + framegen_capacity); + } + break; + } + gen_image_index[gen_count++] = idx; + } + // Sample the render rate down to the requested fps on a fixed grid, then acquire an encoder // image to blit this frame into (bounded timeout so a busy encoder skips rather than stalls). bool rec_this_frame = false; @@ -2621,32 +2657,10 @@ static bool record_and_submit_frame(VkRenderer* r) { vkCmdEndRenderPass(f->cmd); } - uint32_t gen_count = 0; - uint32_t gen_image_index[VKR_LSFG_MAX_GENERATIONS] = {0}; - if (composite) { if (r->lsfg && framegen_capacity > 0) { vkr_lsfg_process(r->lsfg, f->cmd, composite->image, - r->swapchain_extent.width, r->swapchain_extent.height); - - uint32_t want = vkr_lsfg_generated_count(r->lsfg); - if (want > framegen_capacity) want = framegen_capacity; - - for (uint32_t g = 0; g < want; g++) { - uint32_t idx = 0; - VkResult ga = vkAcquireNextImageKHR(r->device, r->swapchain, 8000000ULL, - f->image_available_gen[g], VK_NULL_HANDLE, - &idx); - if (ga != VK_SUCCESS && ga != VK_SUBOPTIMAL_KHR) { - if (r->framegen_acquire_misses++ % 120 == 0) { - VK_LOGW("Generated frame %u/%u dropped: acquire returned %d " - "(swapchain images=%u capacity=%u)", - g + 1, want, (int)ga, r->swapchain_image_count, framegen_capacity); - } - break; - } - gen_image_index[gen_count++] = idx; - } + r->swapchain_extent.width, r->swapchain_extent.height, gen_count); for (uint32_t g = 0; g < gen_count; g++) { VkCompositeTarget* gt = &r->composite[VK_FRAMES_IN_FLIGHT + g]; @@ -2658,6 +2672,21 @@ static bool record_and_submit_frame(VkRenderer* r) { } r->framegen_real_frames++; r->framegen_made_frames += gen_count; + if ((r->framegen_real_frames % 120) == 0) { + const uint64_t d_real = r->framegen_real_frames - r->framegen_log_real; + const uint64_t d_made = r->framegen_made_frames - r->framegen_log_made; + r->framegen_log_real = r->framegen_real_frames; + r->framegen_log_made = r->framegen_made_frames; + VK_LOGI("framegen delivered real=%llu made=%llu ratio=%.2f planned=%u got=%u " + "misses=%llu timeout=%.1fms images=%u capacity=%u", + (unsigned long long)r->framegen_real_frames, + (unsigned long long)r->framegen_made_frames, + d_real ? (double)d_made / (double)d_real : 0.0, + framegen_planned, gen_count, + (unsigned long long)r->framegen_acquire_misses, + (double)gen_acquire_timeout / 1000000.0, + r->swapchain_image_count, framegen_capacity); + } } blit_composite_to_swapchain(r, f->cmd, composite, r->swapchain_images[image_index]); @@ -3534,6 +3563,23 @@ JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationShaders)(JNIEnv* env, jcla pthread_mutex_unlock(&r->render_mutex); } +JNIEXPORT void JNICALL JNI_FN(nativeSetSourceFrameCount)(JNIEnv* env, jclass clazz, jlong handle, + jlong count) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r) return; + __atomic_store_n(&r->framegen_source_frames, (uint64_t)count, __ATOMIC_RELAXED); +} + +JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationRefreshRate)(JNIEnv* env, jclass clazz, + jlong handle, jfloat hz) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r) return; + const int32_t mhz = hz > 0.0f ? (int32_t)((float)hz * 1000.0f + 0.5f) : 0; + __atomic_store_n(&r->framegen_refresh_mhz, mhz, __ATOMIC_RELAXED); +} + JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationMode)(JNIEnv* env, jclass clazz, jlong handle, jint multiplier, jint targetRate, jint flowScalePct) { @@ -3548,7 +3594,7 @@ JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationMode)(JNIEnv* env, jclass r->framegen_flow_scale = flowScalePct <= 0 ? 1.0f : (float)flowScalePct / 100.0f; if (r->lsfg) { vkr_lsfg_configure(r->lsfg, r->framegen_multiplier, r->framegen_target_rate, - r->framegen_flow_scale); + r->framegen_flow_scale, r->framegen_refresh_rate); } if (framegen_extra_images(r) != previous_images) { wait_inflight_frames(r); diff --git a/app/src/main/cpp/winlator/vk/vk_state.h b/app/src/main/cpp/winlator/vk/vk_state.h index 2a3a2c6cf..72079cd60 100644 --- a/app/src/main/cpp/winlator/vk/vk_state.h +++ b/app/src/main/cpp/winlator/vk/vk_state.h @@ -27,6 +27,8 @@ #define VK_MAX_RECORD_IMAGES 32 #define VK_MAX_EFFECTS 8 #define VK_MAX_COMPOSITE_TARGETS 8 +#define VK_FRAMEGEN_ACQUIRE_TIMEOUT_NS 3000000ULL +#define VK_FRAMEGEN_ACQUIRE_TIMEOUT_MAX_NS 12000000ULL #define VK_MAX_RENDERABLE_WINDOWS 64 // Number of in-flight upload slots. Each slot owns a persistently-mapped staging buffer, // fence, and command pool. An upload only blocks when this many uploads are still pending @@ -421,7 +423,12 @@ typedef struct VkRenderer { uint32_t framegen_multiplier; uint32_t framegen_target_rate; float framegen_flow_scale; + float framegen_refresh_rate; + int32_t framegen_refresh_mhz; + uint64_t framegen_source_frames; uint64_t framegen_real_frames; + uint64_t framegen_log_real; + uint64_t framegen_log_made; uint64_t framegen_made_frames; uint64_t framegen_acquire_misses; uint64_t presented_frames; diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index d39209fac..ee841a24c 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -788,12 +788,14 @@ private void applyFrameGeneration(VulkanRenderer renderer) { } renderer.setFrameGenerationShaders(frameGenCachePath); + float refreshRate = applyFrameGenerationDisplayMode(); renderer.setFrameGenerationMode(frameGenMultiplier, frameGenTargetRate, frameGenFlowScale); + renderer.setFrameGenerationRefreshRate(refreshRate); renderer.setFrameGenerationEnabled(true); - applyFrameGenerationDisplayMode(); syncFrameGenerationHud(); Log.i("XServerDisplayActivity", "Frame generation on: multiplier=" + frameGenMultiplier - + " targetRate=" + frameGenTargetRate + " flowScale=" + frameGenFlowScale); + + " targetRate=" + frameGenTargetRate + " flowScale=" + frameGenFlowScale + + " refreshRate=" + refreshRate); } private void syncFrameGenerationHud() { @@ -817,9 +819,9 @@ public long getGeneratedFrameCount() { } }; - private void applyFrameGenerationDisplayMode() { + private float applyFrameGenerationDisplayMode() { android.view.Window window = getWindow(); - if (window == null) return; + if (window == null) return 0f; android.view.WindowManager.LayoutParams params = window.getAttributes(); if (!frameGenEnabled) { @@ -827,42 +829,57 @@ private void applyFrameGenerationDisplayMode() { params.preferredDisplayModeId = 0; window.setAttributes(params); } - return; + return 0f; } - int wanted = frameGenTargetRate > 0 - ? frameGenTargetRate - : frameGenMultiplier * Math.max(30, runtimeFpsLimit > 0 ? runtimeFpsLimit : 60); - android.view.Display display = getDisplayCompat(); - if (display == null) return; + if (display == null) return 0f; android.view.Display.Mode active = display.getMode(); + int wanted = frameGenTargetRate > 0 + ? frameGenTargetRate + : frameGenMultiplier * (runtimeFpsLimit > 0 ? runtimeFpsLimit : 60); + android.view.Display.Mode best = null; for (android.view.Display.Mode mode : display.getSupportedModes()) { if (mode.getPhysicalWidth() != active.getPhysicalWidth() || mode.getPhysicalHeight() != active.getPhysicalHeight()) { continue; } - if (best == null || betterFrameGenMode(mode, best, wanted)) best = mode; + if (best == null || betterFrameGenMode(mode, best, wanted, runtimeFpsLimit)) best = mode; + } + if (best == null) return active.getRefreshRate(); + if (best.getModeId() == params.preferredDisplayModeId && params.preferredRefreshRate == 0f) { + return best.getRefreshRate(); } - if (best == null || best.getModeId() == params.preferredDisplayModeId) return; params.preferredDisplayModeId = best.getModeId(); + params.preferredRefreshRate = 0f; window.setAttributes(params); Log.i("XServerDisplayActivity", "Frame generation display mode: wanted " + wanted + "Hz, selected " + Math.round(best.getRefreshRate()) + "Hz (mode " - + best.getModeId() + ")"); + + best.getModeId() + ") fpsLimit=" + runtimeFpsLimit + " cadenceOk=" + + (runtimeFpsLimit <= 0 + || RefreshRateUtils.isFrameCadenceCompatible( + best.getRefreshRate(), runtimeFpsLimit))); + return best.getRefreshRate(); } private static boolean betterFrameGenMode(android.view.Display.Mode candidate, - android.view.Display.Mode current, int wanted) { + android.view.Display.Mode current, int wanted, + int fpsLimit) { float a = candidate.getRefreshRate(); float b = current.getRefreshRate(); boolean aMeets = a + 0.5f >= wanted; boolean bMeets = b + 0.5f >= wanted; if (aMeets != bMeets) return aMeets; - return aMeets ? a < b : a > b; + if (!aMeets) return a > b; + if (fpsLimit > 0) { + boolean aCadence = RefreshRateUtils.isFrameCadenceCompatible(a, fpsLimit); + boolean bCadence = RefreshRateUtils.isFrameCadenceCompatible(b, fpsLimit); + if (aCadence != bCadence) return aCadence; + } + return a < b; } private android.view.Display getDisplayCompat() { @@ -876,7 +893,7 @@ private android.view.Display getDisplayCompat() { private void applyFrameGenerationLive() { applyFrameGeneration(xServerView != null ? xServerView.getRenderer() : null); - applyFrameGenerationDisplayMode(); + if (!frameGenEnabled || frameGenCachePath == null) applyPreferredRefreshRate(); saveFrameGenerationSettings(); renderDrawerMenu(); } @@ -1100,6 +1117,12 @@ private void applyPreferredRefreshRate() { Runnable applyRefresh = () -> { if (isFinishing() || isDestroyed()) return; + if (frameGenEnabled && frameGenCachePath != null) { + float refreshRate = applyFrameGenerationDisplayMode(); + VulkanRenderer renderer = xServerView != null ? xServerView.getRenderer() : null; + if (renderer != null) renderer.setFrameGenerationRefreshRate(refreshRate); + return; + } RefreshRateUtils.applyPreferredRefreshRate(this, getRefreshRateOverride(), runtimeFpsLimit); }; diff --git a/app/src/main/runtime/display/renderer/VulkanRenderer.java b/app/src/main/runtime/display/renderer/VulkanRenderer.java index 0715d3783..d8b735996 100644 --- a/app/src/main/runtime/display/renderer/VulkanRenderer.java +++ b/app/src/main/runtime/display/renderer/VulkanRenderer.java @@ -29,6 +29,7 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; /** Native Vulkan compositor: owns the C-side renderer handle and pushes a scene snapshot per frame. */ public class VulkanRenderer @@ -117,6 +118,8 @@ public void setSwapRB(boolean v) { ByteBuffer.allocateDirect(SCENE_BUF_SIZE).order(ByteOrder.nativeOrder()); private final Handler mainHandler = new Handler(Looper.getMainLooper()); private final AtomicBoolean renderRequested = new AtomicBoolean(false); + private final AtomicLong sourceFrames = new AtomicLong(); + private final AtomicLong presentFrames = new AtomicLong(); // Reusable scratch — sized once, refilled per frame. private final float[] sceneXform = XForm.getInstance(); @@ -170,6 +173,10 @@ public void destroy() { private volatile Choreographer mainChoreographer; private final Choreographer.FrameCallback coalescedRenderCallback; + public void onGuestFramePresented() { + presentFrames.incrementAndGet(); + } + public void requestRenderCoalesced() { if (renderRequested.compareAndSet(false, true)) { // Post directly (thread-safe): a handler hop arms past the next doFrame and halves the visible cursor rate. @@ -222,6 +229,7 @@ public void attachSurface(Surface surface) { } nativeSetFrameGenerationMode(nativeHandle, frameGenerationMultiplier, frameGenerationTargetRate, frameGenerationFlowScale); + nativeSetFrameGenerationRefreshRate(nativeHandle, frameGenerationRefreshRate); if (frameGenerationRequested) { nativeSetFrameGenerationEnabled(nativeHandle, true); } @@ -574,6 +582,8 @@ private void buildAndSubmitFrame() { } nativeSetScene(nativeHandle, buf); + long presents = presentFrames.get(); + nativeSetSourceFrameCount(nativeHandle, presents > 0 ? presents : sourceFrames.get()); // nativeSetFpsLimit is a native no-op (pacing is done elsewhere); not called per frame. nativeRenderFrame(nativeHandle); } @@ -600,6 +610,7 @@ public void onChangeWindowZOrder(Window window) { @Override public void onUpdateWindowContent(Window window) { + sourceFrames.incrementAndGet(); requestRenderCoalesced(); } @@ -897,6 +908,7 @@ public void setPresentMode(int mode) { private int frameGenerationMultiplier = 2; private int frameGenerationTargetRate = 0; private int frameGenerationFlowScale = 100; + private float frameGenerationRefreshRate = 0f; public void setFrameGenerationEnabled(boolean enabled) { frameGenerationRequested = enabled; @@ -927,6 +939,13 @@ public void setFrameGenerationMode(int multiplier, int targetRate, int flowScale } } + public void setFrameGenerationRefreshRate(float refreshRate) { + frameGenerationRefreshRate = refreshRate > 0f ? refreshRate : 0f; + if (nativeHandle != 0) { + nativeSetFrameGenerationRefreshRate(nativeHandle, frameGenerationRefreshRate); + } + } + public boolean isFrameGenerationRequested() { return frameGenerationRequested; } @@ -999,6 +1018,8 @@ private static native long nativeCreate(boolean enableValidationLayers, private static native void nativeSetFrameGenerationEnabled(long handle, boolean enabled); private static native boolean nativeIsFrameGenerationSupported(long handle); private static native void nativeSetFrameGenerationShaders(long handle, String cachePath); + private static native void nativeSetSourceFrameCount(long handle, long count); + private static native void nativeSetFrameGenerationRefreshRate(long handle, float hz); private static native void nativeSetFrameGenerationMode(long handle, int multiplier, int targetRate, int flowScalePercent); private static native long nativeGetGeneratedFrameCount(long handle); diff --git a/app/src/main/runtime/display/xserver/extensions/PresentExtension.java b/app/src/main/runtime/display/xserver/extensions/PresentExtension.java index 40b14ae2d..c92e5c180 100644 --- a/app/src/main/runtime/display/xserver/extensions/PresentExtension.java +++ b/app/src/main/runtime/display/xserver/extensions/PresentExtension.java @@ -485,8 +485,10 @@ public void handleRequest(XClient client, XInputStream inputStream, XOutputStrea presentPixmap(client, p, outputStream); } - if (client.xServer.getRenderer() != null) + if (client.xServer.getRenderer() != null) { + client.xServer.getRenderer().onGuestFramePresented(); client.xServer.getRenderer().requestRenderCoalesced(); + } break; } case ClientOpcodes.SELECT_INPUT: diff --git a/app/src/main/shared/android/RefreshRateUtils.java b/app/src/main/shared/android/RefreshRateUtils.java index 6f25edb8f..bb7c929a8 100644 --- a/app/src/main/shared/android/RefreshRateUtils.java +++ b/app/src/main/shared/android/RefreshRateUtils.java @@ -226,7 +226,7 @@ public static int resolveFramePacedRefreshRate(Activity activity, int requestedH return fpsLimit; } - private static boolean isFrameCadenceCompatible(float refreshRate, int fpsLimit) { + public static boolean isFrameCadenceCompatible(float refreshRate, int fpsLimit) { if (refreshRate <= 0f || fpsLimit <= 0 || refreshRate < fpsLimit) { return false; } From 38ba89ae146c77b42ab0e82c0b488d24f1d8b68c Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Mon, 24 Aug 2026 16:21:46 -0400 Subject: [PATCH 23/35] Document frame generation and credit its upstreams Frame generation shipped without a word in the README, including the requirement to own Lossless Scaling, which makes the feature look broken rather than gated. Adds a usage section and the attribution the GPL-3.0 sources ask for: lsfg-vk, the Eden Emulator Project, and DXVK's dxbc translator under zlib. --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index 2f15049e8..bb5fd7070 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,43 @@ in-game **Online** tab). **How to use:** In the Library, tap **Add Custom Game** and select a ROM instead of an `.exe`. WinNative detects the console and adds the game to your Library. Tap **Play** to launch it with on-screen touch controls and physical gamepad support; the in-game menu (Back button or on-screen **MENU**) offers save/load state, reset, and fast-forward. PlayStation and PlayStation 2 BIOS files can be imported from **Settings → Retro**. +### Frame Generation + +WinNative can interpolate extra frames between the ones your game actually renders, using the +Lossless Scaling frame generation shaders. Interpolation runs **on the Android side**, inside +WinNative's own Vulkan compositor rather than inside the Wine container, so it works with any +graphics API Wine can drive — DXVK, WineD3D or native Vulkan alike. + +**You must own [Lossless Scaling](https://store.steampowered.com/) on Steam.** Its shaders are +not redistributable, so nothing ships with the APK. WinNative reads them out of your own copy of +`Lossless.dll`, translates them from DXBC to SPIR-V once, and caches the result in app storage. +The DLL is parsed as data and never executed. + +**Setup:** sign in to Steam, install Lossless Scaling, then open **Container Settings → Frame +Generation**. WinNative finds the DLL automatically from your Steam library; if it can't, use +**Select Lossless.dll…** to point at it. The in-game **FG** tab stays disabled until the shaders +import successfully. + +**In-game controls** live in the **FG** tab of the session drawer, between HUD and Gyro: + +| Control | What it does | +| --- | --- | +| Generate Frames | Master toggle | +| Adaptive Target | Aim for a specific output rate (60/90/120/144/165) instead of a fixed multiplier | +| Multiplier | 2× / 3× / 4× — generated frames per rendered frame | +| Flow Scale | 25–100%, resolution of the optical-flow pyramid; lower is cheaper and softer | +| FPS Limiter | Caps the game's own frame rate, from 15 fps upward | + +**What to expect.** Frame generation costs **one extra frame of input latency** — interpolating +between two frames means holding the newer one back. It also needs spare display refresh: +generated frames occupy vblanks, so WinNative sizes the multiplier against your panel's refresh +rate and the game's actual frame rate, and will hand back generated frames rather than take real +ones from the game. A game already running near your panel's refresh rate has nothing to gain. +Pairing a multiplier with an FPS limiter that divides the refresh rate evenly (120 Hz with a +60 fps cap at 2×, or 40 at 3×) gives the most even pacing. + +--- + ### Contributing We welcome community contributions! Feel free to open a pull request for bug fixes, driver updates, UI improvements, or anything else you'd like to add. @@ -96,3 +133,7 @@ Please match the existing code style and ensure any AI-assisted code is thorough - **LibretroDroid** by [Filippo Scognamiglio](https://github.com/Swordfish90/LibretroDroid) (GPL-3.0) — the embedded libretro host for retro console support - **libretro / RetroArch** and the individual core authors, built from source: [FCEUmm](https://github.com/libretro/libretro-fceumm), [Snes9x](https://github.com/libretro/snes9x), [Gambatte](https://github.com/libretro/gambatte-libretro), [mGBA](https://github.com/libretro/mgba), [Genesis Plus GX](https://github.com/libretro/Genesis-Plus-GX), [Mupen64Plus-Next](https://github.com/libretro/mupen64plus-libretro-nx), [Beetle PSX](https://github.com/libretro/beetle-psx-libretro) - **ARMSX2** by the [ARMSX2](https://github.com/ARMSX2/ARMSX2) team (GPL-3.0) — the PlayStation 2 core, a fork of **[PCSX2](https://github.com/pcsx2/pcsx2)** (GPL-3.0), built from source into `libemucore`. PS2 online play uses PCSX2's DEV9 network adapter +- **lsfg-vk** by [PancakeTAS](https://github.com/PancakeTAS/lsfg-vk) (GPL-3.0-or-later) — the original Vulkan reimplementation of the Lossless Scaling frame generation chain +- **Eden Emulator Project** by the [eden](https://github.com/eden-emu/eden) team (GPL-3.0-or-later) — the Android port of that chain, which WinNative's compute passes derive from +- **DXVK** by [Philip Rebohle and contributors](https://github.com/doitsujin/dxvk) (zlib/libpng) — the `dxbc` shader translator, vendored at `app/src/main/cpp/thirdparty/dxbc` to convert the frame generation shaders to SPIR-V +- **Lossless Scaling** (Steam) — the source of the frame generation shaders. They are read from the user's own installed copy at runtime; none are redistributed with WinNative From c13ae0ddcff47ab7b931ae75aecc7a8caf8b38a7 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Mon, 24 Aug 2026 18:13:12 -0400 Subject: [PATCH 24/35] Stop frame generation from compositing faster than the game renders Guest presents woke the compositor through the main thread's Choreographer, which from an X client thread costs a message hop, a vsync request and a doFrame dispatch before the render thread is told to draw. Only one request could be armed at a time, so frames arriving in between were dropped. Wake the render thread directly instead. The source rate was estimated by smoothing elapsed/drawn and inverting it, which biases the rate downward when the loop and the guest drift in phase. Smooth the frames and the seconds separately and divide. Four paths signalled the same guest frame into one flag, so the loop ran at the FIFO ceiling regardless of the game's rate and generated frames were interpolated between identical inputs. Suppress the redundant wakes while the guest is presenting; the guard lapses after 100ms so games that do not use Present and the desktop keep their own wakes. --- .../main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp | 367 ++---------------- .../main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp | 36 +- .../main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp | 17 +- app/src/main/cpp/winlator/vk/vk_renderer.c | 48 ++- app/src/main/cpp/winlator/vk/vk_state.h | 4 + .../display/renderer/VulkanRenderer.java | 94 ++++- .../display/ui/XServerSurfaceView.java | 29 ++ .../display/winhandler/WinHandler.java | 14 +- .../xserver/extensions/PresentExtension.java | 2 +- .../runtime/input/ui/InputControlsView.java | 10 +- 10 files changed, 210 insertions(+), 411 deletions(-) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp index d4fc365d1..8d5d08944 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp @@ -5,7 +5,6 @@ #include #include -#include namespace lsfg { @@ -14,62 +13,14 @@ namespace { using Clock = std::chrono::steady_clock; constexpr float INTERVAL_SMOOTHING = 0.25f; -constexpr float MINIMUM_BASE_RATE = 10.0f; -constexpr float FIXED_DISCONTINUITY_SECONDS = 0.25f; -constexpr float BURST_CADENCE_RATIO = 3.0f; -constexpr float BURST_TARGET_RATIO = 2.0f; -constexpr float PROBE_THROUGHPUT_TOLERANCE = 0.95f; -constexpr float PROBE_BASE_COLLAPSE_RATIO = 0.70f; -constexpr float PROBE_MARGINAL_GAIN = 1.15f; -constexpr float TARGET_SATISFIED_RATIO = 0.95f; -constexpr float UNLOADED_BASE_RETENTION = 0.75f; -constexpr float CREDIT_EPSILON = 1.0e-4f; constexpr float SOURCE_SMOOTHING = 0.15f; constexpr float SOURCE_STALE_SECONDS = 0.5f; +constexpr float DISCONTINUITY_SECONDS = 0.25f; constexpr float HEADROOM_EPSILON = 0.02f; constexpr float HEADROOM_HYSTERESIS = 0.20f; -constexpr float REGRESSION_RATIO = 1.20f; -constexpr uint32_t MIN_RATE_SAMPLES = 20; -constexpr uint32_t MAX_PROBE_FAILURES = 4; - -constexpr float COST_PROBE_GAIN = 0.10f; - -constexpr auto RAISE_SETTLE_DURATION = std::chrono::milliseconds(600); -constexpr uint32_t MAX_COST_FAILURES = 4; - -[[nodiscard]] std::chrono::steady_clock::duration CostBackoff(uint32_t failures) { - switch (failures) { - case 0: - case 1: - return std::chrono::seconds(8); - case 2: - return std::chrono::seconds(20); - case 3: - return std::chrono::seconds(45); - default: - return std::chrono::seconds(90); - } -} -constexpr auto COST_PROBE_INTERVAL = std::chrono::seconds(15); -constexpr auto COST_PROBE_WINDOW = std::chrono::milliseconds(700); - -constexpr auto STABILIZATION_DURATION = std::chrono::seconds(1); -constexpr auto PROBE_DURATION = std::chrono::seconds(1); -constexpr auto DEFICIT_DURATION = std::chrono::seconds(1); -constexpr auto PROBE_STEP_DELAY = std::chrono::milliseconds(250); - -[[nodiscard]] Clock::duration ProbeBackoff(uint32_t failures) { - switch (failures) { - case 1: - return std::chrono::seconds(5); - case 2: - return std::chrono::seconds(15); - case 3: - return std::chrono::seconds(30); - default: - return std::chrono::seconds(60); - } -} +constexpr float CREDIT_EPSILON = 1.0e-4f; +constexpr float SOURCE_ACCUM_FLOOR = 0.01f; +constexpr uint32_t MIN_RATE_SAMPLES = 12; } @@ -87,29 +38,29 @@ void LsfgPacer::TrackSourceRate(Clock::time_point now, uint64_t source_frames) { } const float elapsed = std::chrono::duration(now - *last_source_sample).count(); - if (source_frames <= last_source_frames) { - if (elapsed > SOURCE_STALE_SECONDS) { - source_interval = 0.0f; - last_source_sample = now; - last_source_frames = source_frames; - } + if (elapsed <= 0.0f) { return; } last_source_sample = now; - const uint64_t drawn = source_frames - last_source_frames; + const uint64_t drawn = + source_frames > last_source_frames ? source_frames - last_source_frames : 0; last_source_frames = source_frames; - if (elapsed <= 0.0f || elapsed > SOURCE_STALE_SECONDS) { + + if (elapsed > SOURCE_STALE_SECONDS) { + source_frame_accum = 0.0f; + source_time_accum = 0.0f; source_interval = 0.0f; + source_samples = 0; return; } last_drawn = drawn; last_elapsed = elapsed; - const float measured = elapsed / static_cast(drawn); - source_interval = source_interval > 0.0f - ? source_interval + (measured - source_interval) * SOURCE_SMOOTHING - : measured; + source_frame_accum += (static_cast(drawn) - source_frame_accum) * SOURCE_SMOOTHING; + source_time_accum += (elapsed - source_time_accum) * SOURCE_SMOOTHING; + source_interval = + source_frame_accum > SOURCE_ACCUM_FLOOR ? source_time_accum / source_frame_accum : 0.0f; if (source_samples < MIN_RATE_SAMPLES) ++source_samples; } @@ -124,94 +75,10 @@ bool LsfgPacer::RatesSettled() const { return source_samples >= MIN_RATE_SAMPLES && loop_samples >= MIN_RATE_SAMPLES; } -size_t LsfgPacer::CostLimit(Clock::time_point now, size_t current) { - if (cost_probe_until) { - const size_t probe_limit = cost_probe_from > 0 ? cost_probe_from - 1 : 0; - if (now < *cost_probe_until) { - cost_probe_active = true; - return probe_limit; - } - cost_probe_until.reset(); - cost_probe_active = true; - if (cost_probe_baseline > 0.0f && loop_interval > 0.0f && - loop_interval < cost_probe_baseline * (1.0f - COST_PROBE_GAIN)) { - cost_failures = std::min(cost_failures + 1, MAX_COST_FAILURES); - cost_ceiling = probe_limit; - cost_backoff_until = now + CostBackoff(cost_failures); - return cost_ceiling; - } - } else { - cost_probe_active = false; - } - - if (settle_until) { - if (now < *settle_until) { - return current; - } - settle_until.reset(); - - const bool source_regressed = - pre_raise_source_interval > 0.0f && source_interval > 0.0f && - source_interval > pre_raise_source_interval * REGRESSION_RATIO; - const bool loop_regressed = pre_raise_loop_interval > 0.0f && loop_interval > 0.0f && - loop_interval > pre_raise_loop_interval * REGRESSION_RATIO; - - if (source_regressed || loop_regressed) { - cost_failures = std::min(cost_failures + 1, MAX_COST_FAILURES); - cost_ceiling = pre_raise_limit; - cost_backoff_until = now + CostBackoff(cost_failures); - return cost_ceiling; - } - cost_failures = 0; - } - - if (cost_backoff_until) { - if (now < *cost_backoff_until) { - return cost_ceiling; - } - cost_backoff_until.reset(); - cost_ceiling = LSFG_MAX_MULTIPLIER - 1; - } - - if (current > 0 && !settle_until && RatesSettled()) { - if (!next_cost_probe) { - next_cost_probe = now + COST_PROBE_INTERVAL; - } else if (now >= *next_cost_probe) { - cost_probe_baseline = loop_interval; - cost_probe_from = current; - cost_probe_until = now + COST_PROBE_WINDOW; - next_cost_probe = now + COST_PROBE_INTERVAL; - cost_probe_active = true; - return current - 1; - } - } - - return cost_ceiling; -} - -void LsfgPacer::NoteLimitChange(Clock::time_point now, size_t previous_limit) { - if (cost_probe_active) { - settle_until.reset(); - return; - } - if (limit > previous_limit) { - if (!RatesSettled()) { - settle_until.reset(); - return; - } - pre_raise_source_interval = source_interval; - pre_raise_loop_interval = loop_interval; - pre_raise_limit = previous_limit; - settle_until = now + RAISE_SETTLE_DURATION; - } else if (limit < previous_limit) { - settle_until.reset(); - } -} - size_t LsfgPacer::HeadroomLimit(size_t current, bool allow_fractional) const { if (config.refresh_rate <= 0.0f || source_interval <= 0.0f || source_samples < MIN_RATE_SAMPLES) { - return 0; + return LSFG_MAX_MULTIPLIER - 1; } const float slots = config.refresh_rate * source_interval; @@ -240,86 +107,37 @@ LsfgPlan LsfgPacer::Plan(size_t capacity, uint64_t source_frames) { const Clock::time_point now = Clock::now(); TrackSourceRate(now, source_frames); - const size_t previous_generations = std::exchange(issued_generations, 0); if (!last_frame) { last_frame = now; return {}; } - const Clock::duration interval = now - *last_frame; - const float interval_seconds = std::chrono::duration(interval).count(); + const float interval_seconds = std::chrono::duration(now - *last_frame).count(); last_frame = now; - if (interval_seconds <= 0.0f) { - Stabilize(now); - return {}; + if (interval_seconds <= 0.0f || interval_seconds > DISCONTINUITY_SECONDS) { + output_credit = 0.0f; + return LsfgPlan{0, true}; } + TrackLoopRate(interval_seconds); + float target_rate = static_cast(config.target_rate); if (target_rate > 0.0f && config.refresh_rate > 0.0f) { target_rate = std::min(target_rate, config.refresh_rate); } - if (interval_seconds <= FIXED_DISCONTINUITY_SECONDS) { - TrackLoopRate(interval_seconds); - } - if (target_rate == 0.0f) { output_credit = 0.0f; - if (interval_seconds > FIXED_DISCONTINUITY_SECONDS) { - issued_generations = 0; - return {}; - } - const size_t previous_limit = limit; - limit = std::min({ceiling, HeadroomLimit(limit, false), CostLimit(now, limit)}); - NoteLimitChange(now, previous_limit); - issued_generations = limit; + limit = std::min(ceiling, HeadroomLimit(limit, false)); return LsfgPlan{limit, limit > 0}; } - if (smoothed_interval > 0.0f) { - float burst_threshold = BURST_CADENCE_RATIO / smoothed_interval; - if (target_rate > 0.0f) { - burst_threshold = std::max(burst_threshold, target_rate * BURST_TARGET_RATIO); - } - if (1.0f / interval_seconds > burst_threshold) { - DeferEvaluations(interval); - output_credit = 0.0f; - return {}; - } - } - - if (interval_seconds > 1.0f / MINIMUM_BASE_RATE) { - Stabilize(now); - return {}; - } - - smoothed_interval = smoothed_interval > 0.0f - ? smoothed_interval + - (interval_seconds - smoothed_interval) * INTERVAL_SMOOTHING - : interval_seconds; - - if (previous_generations == 0) { - const float measured = 1.0f / smoothed_interval; - unloaded_base_rate = - unloaded_base_rate > 0.0f - ? unloaded_base_rate + (measured - unloaded_base_rate) * INTERVAL_SMOOTHING - : measured; - } - - if (stable_until) { - if (now < *stable_until) { - return {}; - } - stable_until.reset(); - } - - UpdateLimit(now, 1.0f / smoothed_interval, target_rate, ceiling); - - const size_t allowed = std::min({limit, ceiling, HeadroomLimit(limit, true)}); - const float desired_outputs = smoothed_interval * target_rate; + const size_t allowed = std::min(ceiling, HeadroomLimit(limit, true)); + const float desired_outputs = loop_interval * target_rate; if (allowed == 0 || desired_outputs <= 1.0f) { output_credit = 0.0f; + limit = 0; return {}; } @@ -335,158 +153,39 @@ LsfgPlan LsfgPacer::Plan(size_t capacity, uint64_t source_frames) { output_credit = std::fmod(output_credit, 1.0f); } - issued_generations = generations; + limit = generations; return LsfgPlan{generations, true}; } -void LsfgPacer::UpdateLimit(Clock::time_point now, float base_rate, float target_rate, - size_t ceiling) { - limit = std::min(limit, ceiling); - - if (probe_until) { - if (now < *probe_until) { - return; - } - probe_until.reset(); - output_credit = 0.0f; - - const float previous_output = - std::min(target_rate, probe_base_rate * static_cast(probe_previous_limit + 1)); - const float current_output = - std::min(target_rate, base_rate * static_cast(limit + 1)); - - const bool throughput_regressed = - current_output < previous_output * PROBE_THROUGHPUT_TOLERANCE; - const bool collapsed_for_marginal_gain = - base_rate < probe_base_rate * PROBE_BASE_COLLAPSE_RATIO && - current_output < previous_output * PROBE_MARGINAL_GAIN; - const bool emulation_slowed = unloaded_base_rate > 0.0f && - base_rate < unloaded_base_rate * UNLOADED_BASE_RETENTION; - - if (throughput_regressed || collapsed_for_marginal_gain || emulation_slowed) { - limit = probe_previous_limit; - probe_failures = std::min(probe_failures + 1, MAX_PROBE_FAILURES); - next_probe = now + ProbeBackoff(probe_failures); - deficit_since.reset(); - return; - } - - probe_failures = 0; - next_probe = now + PROBE_STEP_DELAY; - } - - if (base_rate * static_cast(limit + 1) >= target_rate * TARGET_SATISFIED_RATIO || - limit >= ceiling) { - deficit_since.reset(); - return; - } - - if (!deficit_since) { - deficit_since = now; - return; - } - if (now - *deficit_since < DEFICIT_DURATION) { - return; - } - if (next_probe && now < *next_probe) { - return; - } - - probe_previous_limit = limit; - probe_base_rate = base_rate; - ++limit; - probe_until = now + PROBE_DURATION; - deficit_since.reset(); - output_credit = 0.0f; -} - -void LsfgPacer::DeferEvaluations(Clock::duration amount) { - const auto defer = [amount](std::optional& deadline) { - if (deadline) { - *deadline += amount; - } - }; - defer(stable_until); - defer(probe_until); - defer(next_probe); - deficit_since.reset(); -} - -void LsfgPacer::Stabilize(Clock::time_point now) { - stable_until = now + STABILIZATION_DURATION; - probe_until.reset(); - deficit_since.reset(); - smoothed_interval = 0.0f; - output_credit = 0.0f; -} - LsfgPacerStats LsfgPacer::Stats() const { LsfgPacerStats stats; stats.source_rate = source_interval > 0.0f ? 1.0f / source_interval : 0.0f; stats.loop_rate = loop_interval > 0.0f ? 1.0f / loop_interval : 0.0f; stats.refresh_rate = config.refresh_rate; - stats.slots = config.refresh_rate * source_interval; stats.target_rate = static_cast(config.target_rate); + stats.slots = config.refresh_rate * source_interval; stats.limit = limit; - stats.cost_ceiling = cost_ceiling; - stats.settling = settle_until.has_value(); - stats.backing_off = cost_backoff_until.has_value(); stats.rates_settled = RatesSettled(); - stats.probing = cost_probe_until.has_value(); - stats.cost_failures = cost_failures; stats.last_drawn = last_drawn; stats.last_elapsed = last_elapsed; stats.source_frames = last_source_frames; return stats; } -void LsfgPacer::ResetCostState() { - settle_until.reset(); - cost_backoff_until.reset(); - cost_probe_until.reset(); - next_cost_probe.reset(); - cost_probe_baseline = 0.0f; - cost_probe_from = 0; - pre_raise_limit = 0; - cost_probe_active = false; - cost_failures = 0; - pre_raise_source_interval = 0.0f; - pre_raise_loop_interval = 0.0f; - cost_ceiling = LSFG_MAX_MULTIPLIER - 1; -} - void LsfgPacer::Reset() { last_frame.reset(); last_source_sample.reset(); - settle_until.reset(); - cost_backoff_until.reset(); - cost_probe_until.reset(); - next_cost_probe.reset(); - cost_probe_baseline = 0.0f; - cost_probe_from = 0; - pre_raise_limit = 0; - cost_probe_active = false; - cost_failures = 0; last_source_frames = 0; source_interval = 0.0f; + source_frame_accum = 0.0f; + source_time_accum = 0.0f; loop_interval = 0.0f; - pre_raise_source_interval = 0.0f; - pre_raise_loop_interval = 0.0f; source_samples = 0; loop_samples = 0; - cost_ceiling = LSFG_MAX_MULTIPLIER - 1; - stable_until.reset(); - probe_until.reset(); - next_probe.reset(); - deficit_since.reset(); - smoothed_interval = 0.0f; + last_drawn = 0; + last_elapsed = 0.0f; output_credit = 0.0f; - probe_base_rate = 0.0f; - unloaded_base_rate = 0.0f; - issued_generations = 0; - probe_previous_limit = 0; limit = 0; - probe_failures = 0; } } diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp index f932350d8..6b7b954ce 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp @@ -30,12 +30,7 @@ struct LsfgPacerStats { float target_rate{}; float slots{}; size_t limit{}; - size_t cost_ceiling{}; - bool settling{}; - bool backing_off{}; bool rates_settled{}; - bool probing{}; - uint32_t cost_failures{}; uint64_t last_drawn{}; float last_elapsed{}; uint64_t source_frames{}; @@ -57,58 +52,31 @@ class LsfgPacer { [[nodiscard]] LsfgPacerStats Stats() const; - void ResetCostState(); - void Reset(); private: using Clock = std::chrono::steady_clock; - void Stabilize(Clock::time_point now); - void DeferEvaluations(Clock::duration amount); - void UpdateLimit(Clock::time_point now, float base_rate, float target_rate, size_t ceiling); void TrackSourceRate(Clock::time_point now, uint64_t source_frames); void TrackLoopRate(float interval_seconds); [[nodiscard]] bool RatesSettled() const; [[nodiscard]] size_t HeadroomLimit(size_t current, bool allow_fractional) const; - [[nodiscard]] size_t CostLimit(Clock::time_point now, size_t current); - void NoteLimitChange(Clock::time_point now, size_t previous_limit); LsfgPacerConfig config; std::optional last_frame; std::optional last_source_sample; - std::optional settle_until; - std::optional cost_backoff_until; - std::optional cost_probe_until; - std::optional next_cost_probe; - float cost_probe_baseline{}; - size_t cost_probe_from{}; - size_t pre_raise_limit{}; - uint32_t cost_failures{}; - bool cost_probe_active{}; uint64_t last_source_frames{}; float source_interval{}; + float source_frame_accum{}; + float source_time_accum{}; float loop_interval{}; - float pre_raise_source_interval{}; - float pre_raise_loop_interval{}; uint32_t source_samples{}; uint32_t loop_samples{}; uint64_t last_drawn{}; float last_elapsed{}; - size_t cost_ceiling{LSFG_MAX_MULTIPLIER - 1}; - std::optional stable_until; - std::optional probe_until; - std::optional next_probe; - std::optional deficit_since; - float smoothed_interval{}; float output_credit{}; - float probe_base_rate{}; - float unloaded_base_rate{}; - size_t issued_generations{}; - size_t probe_previous_limit{}; size_t limit{}; - uint32_t probe_failures{}; }; } diff --git a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp index e8bc13065..4edbfa50b 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp @@ -137,9 +137,6 @@ void vkr_lsfg_configure(VkrLsfg* lsfg, uint32_t multiplier, uint32_t target_rate config.target_rate = target_rate; config.refresh_rate = refresh_rate; lsfg->pacer.SetConfig(config); - if (previous.multiplier != multiplier || previous.target_rate != target_rate) { - lsfg->pacer.ResetCostState(); - } lsfg->flow_scale = std::clamp(flow_scale, 0.25f, 1.0f); } @@ -205,19 +202,13 @@ uint32_t vkr_lsfg_plan(VkrLsfg* lsfg, uint32_t capacity, uint64_t source_frames) if ((lsfg->plan_calls++ % LSFG_TELEMETRY_INTERVAL) == 0) { const lsfg::LsfgPacerStats stats = lsfg->pacer.Stats(); - LSFG_LOGI("pace gen=%zu max=%zu cap=%u guest=%.1f loop=%.1f out=%.1f refresh=%.1f " - "target=%.0f slots=%.2f ceil=%zu%s%s%s", + LSFG_LOGI("pace gen=%zu max=%zu cap=%u guest=%.1f loop=%.1f refresh=%.1f target=%.0f " + "slots=%.2f drawn=%llu%s", lsfg->plan.generations, lsfg->pacer.MaxGenerations(), capacity, (double)stats.source_rate, (double)stats.loop_rate, - (double)(stats.loop_rate * (float)(lsfg->plan.generations + 1)), - (double)stats.refresh_rate, (double)stats.target_rate, - (double)stats.slots, stats.cost_ceiling, stats.settling ? " settling" : "", - stats.backing_off ? " backoff" : "", + (double)stats.refresh_rate, (double)stats.target_rate, (double)stats.slots, + (unsigned long long)stats.last_drawn, stats.rates_settled ? (lsfg->warm ? "" : " cold") : " sampling"); - LSFG_LOGI("pace raw src=%llu drawn=%llu elapsed=%.4f fails=%u%s", - (unsigned long long)stats.source_frames, (unsigned long long)stats.last_drawn, - (double)stats.last_elapsed, stats.cost_failures, - stats.probing ? " probing" : ""); } return lsfg->generated ? static_cast(lsfg->plan.generations) : 0; diff --git a/app/src/main/cpp/winlator/vk/vk_renderer.c b/app/src/main/cpp/winlator/vk/vk_renderer.c index aaff2ccf4..994e25194 100644 --- a/app/src/main/cpp/winlator/vk/vk_renderer.c +++ b/app/src/main/cpp/winlator/vk/vk_renderer.c @@ -325,11 +325,24 @@ static bool pick_physical_device(VkRenderer* r) { r->graphics_queue_family = UINT32_MAX; for (uint32_t i = 0; i < qf_count; i++) { - if (qf[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + VK_LOGI("queue family %u: count=%u%s%s%s%s", i, qf[i].queueCount, + (qf[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) ? " graphics" : "", + (qf[i].queueFlags & VK_QUEUE_COMPUTE_BIT) ? " compute" : "", + (qf[i].queueFlags & VK_QUEUE_TRANSFER_BIT) ? " transfer" : "", + (qf[i].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) ? " sparse" : ""); + if ((qf[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) + && r->graphics_queue_family == UINT32_MAX) { r->graphics_queue_family = i; - break; } } + { + VkPhysicalDeviceProperties dp; + vkGetPhysicalDeviceProperties(r->physical_device, &dp); + VK_LOGI("device \"%s\" api=%u.%u.%u driver=0x%x families=%u chosen=%u", + dp.deviceName, VK_VERSION_MAJOR(dp.apiVersion), + VK_VERSION_MINOR(dp.apiVersion), VK_VERSION_PATCH(dp.apiVersion), + dp.driverVersion, qf_count, r->graphics_queue_family); + } free(qf); if (r->graphics_queue_family == UINT32_MAX) return false; return true; @@ -1841,6 +1854,10 @@ static void destroy_lsfg(VkRenderer* r) { r->lsfg = NULL; r->framegen_real_frames = 0; r->framegen_made_frames = 0; + r->framegen_draw_ns = 0; + r->framegen_gap_ns = 0; + r->framegen_last_end_ns = 0; + r->framegen_timed_frames = 0; } static void create_lsfg(VkRenderer* r) { @@ -2341,9 +2358,17 @@ static VkExtent2D compute_sgsr1_source_extent(VkRenderer* r, const VkScene* s) { return source; } +static uint64_t vkr_monotonic_ns(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; +} + static bool record_and_submit_frame(VkRenderer* r) { if (!r->surface_ready || !r->swapchain) return false; + const uint64_t draw_begin_ns = vkr_monotonic_ns(); + pthread_mutex_lock(&r->render_mutex); VkFrame* f = &r->frames[r->frame_index]; @@ -2677,15 +2702,22 @@ static bool record_and_submit_frame(VkRenderer* r) { const uint64_t d_made = r->framegen_made_frames - r->framegen_log_made; r->framegen_log_real = r->framegen_real_frames; r->framegen_log_made = r->framegen_made_frames; + const uint64_t timed = r->framegen_timed_frames; + const double draw_ms = timed ? (double)r->framegen_draw_ns / (double)timed / 1.0e6 : 0.0; + const double gap_ms = timed ? (double)r->framegen_gap_ns / (double)timed / 1.0e6 : 0.0; + r->framegen_draw_ns = 0; + r->framegen_gap_ns = 0; + r->framegen_timed_frames = 0; VK_LOGI("framegen delivered real=%llu made=%llu ratio=%.2f planned=%u got=%u " - "misses=%llu timeout=%.1fms images=%u capacity=%u", + "misses=%llu timeout=%.1fms images=%u capacity=%u draw=%.2fms gap=%.2fms", (unsigned long long)r->framegen_real_frames, (unsigned long long)r->framegen_made_frames, d_real ? (double)d_made / (double)d_real : 0.0, framegen_planned, gen_count, (unsigned long long)r->framegen_acquire_misses, (double)gen_acquire_timeout / 1000000.0, - r->swapchain_image_count, framegen_capacity); + r->swapchain_image_count, framegen_capacity, + draw_ms, gap_ms); } } @@ -2872,6 +2904,14 @@ static bool record_and_submit_frame(VkRenderer* r) { r->frame_index = (r->frame_index + 1) % VK_FRAMES_IN_FLIGHT; r->graveyard_index = (r->graveyard_index + 1) % (VK_FRAMES_IN_FLIGHT + 1); + const uint64_t draw_end_ns = vkr_monotonic_ns(); + if (r->framegen_last_end_ns != 0 && draw_begin_ns > r->framegen_last_end_ns) { + r->framegen_gap_ns += draw_begin_ns - r->framegen_last_end_ns; + } + if (draw_end_ns > draw_begin_ns) r->framegen_draw_ns += draw_end_ns - draw_begin_ns; + r->framegen_last_end_ns = draw_end_ns; + r->framegen_timed_frames++; + return true; } diff --git a/app/src/main/cpp/winlator/vk/vk_state.h b/app/src/main/cpp/winlator/vk/vk_state.h index 72079cd60..2155d69a1 100644 --- a/app/src/main/cpp/winlator/vk/vk_state.h +++ b/app/src/main/cpp/winlator/vk/vk_state.h @@ -431,6 +431,10 @@ typedef struct VkRenderer { uint64_t framegen_log_made; uint64_t framegen_made_frames; uint64_t framegen_acquire_misses; + uint64_t framegen_draw_ns; + uint64_t framegen_gap_ns; + uint64_t framegen_last_end_ns; + uint64_t framegen_timed_frames; uint64_t presented_frames; // record_blit_src adds TRANSFER_SRC usage to the display swapchain (toggled by start/stop recording). diff --git a/app/src/main/runtime/display/renderer/VulkanRenderer.java b/app/src/main/runtime/display/renderer/VulkanRenderer.java index d8b735996..ad31973f7 100644 --- a/app/src/main/runtime/display/renderer/VulkanRenderer.java +++ b/app/src/main/runtime/display/renderer/VulkanRenderer.java @@ -68,7 +68,7 @@ public class VulkanRenderer public void setSwapRB(boolean v) { this.swapRB = v; - requestRenderCoalesced(); + requestRenderCoalesced(WAKE_SETTING); } private boolean screenOffsetYRelativeToCursor = false; private String[] unviewableWMClasses = null; @@ -84,7 +84,10 @@ public void setSwapRB(boolean v) { public volatile int surfaceHeight; private boolean cpuSaverMode = false; private static final long CURSOR_ACTIVE_NS = 100_000_000L; + private static final long GUEST_ACTIVE_NS = 100_000_000L; private volatile long cursorActiveUntilNs = 0L; + private volatile long lastGuestPresentNs = 0L; + private long guestPresentMark = 0L; private static final int MAX_FPS_LIMIT = 1000; private volatile int currentFpsLimit = 0; @@ -175,9 +178,65 @@ public void destroy() { public void onGuestFramePresented() { presentFrames.incrementAndGet(); + lastGuestPresentNs = System.nanoTime(); + } + + public void requestRenderImmediate() { + xServerView.requestRender(); + } + + public long takeGuestPresentDelta() { + long now = presentFrames.get(); + long delta = now - guestPresentMark; + guestPresentMark = now; + return delta; + } + + private boolean guestIsDrivingFrames() { + long last = lastGuestPresentNs; + return last != 0L && System.nanoTime() - last < GUEST_ACTIVE_NS; + } + + public static final int WAKE_OTHER = 0; + public static final int WAKE_CONTENT = 1; + public static final int WAKE_GEOMETRY = 2; + public static final int WAKE_WINDOW = 3; + public static final int WAKE_CURSOR = 4; + public static final int WAKE_FRAME = 5; + public static final int WAKE_SUPPRESSED = 6; + public static final int WAKE_POINTER = 7; + public static final int WAKE_WINHANDLER = 8; + public static final int WAKE_INPUTVIEW = 9; + public static final int WAKE_SETTING = 10; + private final java.util.concurrent.atomic.AtomicLongArray wakeSources = + new java.util.concurrent.atomic.AtomicLongArray(11); + + public String takeWakeBreakdown() { + StringBuilder sb = new StringBuilder(); + String[] names = + {"other", "content", "geometry", "window", "cursor", "frame", "suppressed", + "pointer", "winhandler", "inputview", "setting"}; + for (int i = 0; i < names.length; i++) { + sb.append(' ').append(names[i]).append('=').append(wakeSources.getAndSet(i, 0)); + } + return sb.toString(); } public void requestRenderCoalesced() { + requestRenderCoalesced(WAKE_OTHER); + } + + private static boolean isGuestRedundantWake(int source) { + return source == WAKE_CONTENT || source == WAKE_FRAME || source == WAKE_WINHANDLER + || source == WAKE_INPUTVIEW; + } + + public void requestRenderCoalesced(int source) { + wakeSources.incrementAndGet(source); + if (isGuestRedundantWake(source) && guestIsDrivingFrames()) { + wakeSources.incrementAndGet(WAKE_SUPPRESSED); + return; + } if (renderRequested.compareAndSet(false, true)) { // Post directly (thread-safe): a handler hop arms past the next doFrame and halves the visible cursor rate. Choreographer choreographer = mainChoreographer; @@ -593,25 +652,25 @@ private void buildAndSubmitFrame() { @Override public void onMapWindow(Window window) { xServerView.queueEvent(this::updateScene); - requestRenderCoalesced(); + requestRenderCoalesced(WAKE_WINDOW); } @Override public void onUnmapWindow(Window window) { xServerView.queueEvent(this::updateScene); - requestRenderCoalesced(); + requestRenderCoalesced(WAKE_WINDOW); } @Override public void onChangeWindowZOrder(Window window) { xServerView.queueEvent(this::updateScene); - requestRenderCoalesced(); + requestRenderCoalesced(WAKE_WINDOW); } @Override public void onUpdateWindowContent(Window window) { sourceFrames.incrementAndGet(); - requestRenderCoalesced(); + requestRenderCoalesced(WAKE_CONTENT); } @Override @@ -622,16 +681,21 @@ public void onUpdateWindowGeometry(final Window window, boolean resized) { xServerView.queueEvent(() -> updateWindowPosition(window)); xServerView.queueEvent(this::updateScene); } - requestRenderCoalesced(); + requestRenderCoalesced(WAKE_GEOMETRY); } @Override public void onUpdateWindowAttributes(Window window, Bitmask mask) { - if (mask.isSet(WindowAttributes.FLAG_CURSOR)) requestRenderCoalesced(); + if (mask.isSet(WindowAttributes.FLAG_CURSOR)) requestRenderCoalesced(WAKE_CURSOR); } public void requestCursorRender() { cursorActiveUntilNs = System.nanoTime() + CURSOR_ACTIVE_NS; + wakeSources.incrementAndGet(WAKE_POINTER); + if (guestIsDrivingFrames()) { + wakeSources.incrementAndGet(WAKE_SUPPRESSED); + return; + } xServerView.requestTransientRender(100); } @@ -648,7 +712,7 @@ public void onPointerMove(short x, short y) { public void onFramePresented(Window window, WindowManager.FrameSource source, int serial) { // DRI3_BUFFER fires at pixmap allocation, not a visible change; the real present already wakes us. Skip it. if (source == WindowManager.FrameSource.DRI3_BUFFER) return; - requestRenderCoalesced(); + requestRenderCoalesced(WAKE_FRAME); } private void updateScene() { @@ -716,13 +780,13 @@ public void onXServerScreenChanged() { viewTransformation.viewWidth + "x" + viewTransformation.viewHeight + "@" + viewTransformation.viewOffsetX + "," + viewTransformation.viewOffsetY); } - requestRenderCoalesced(); + requestRenderCoalesced(WAKE_SETTING); } public void toggleFullscreen() { fullscreen = !fullscreen; viewportNeedsUpdate = true; - requestRenderCoalesced(); + requestRenderCoalesced(WAKE_SETTING); } public boolean isFullscreen() { return fullscreen; } @@ -730,7 +794,7 @@ public void toggleFullscreen() { public void setCursorVisible(boolean v) { if (this.cursorVisible == v) return; this.cursorVisible = v; - requestRenderCoalesced(); + requestRenderCoalesced(WAKE_SETTING); } public boolean isCursorVisible() { return cursorVisible; } @@ -739,7 +803,7 @@ public void setCursorVisible(boolean v) { public void setScreenOffsetYRelativeToCursor(boolean v) { this.screenOffsetYRelativeToCursor = v; - requestRenderCoalesced(); + requestRenderCoalesced(WAKE_SETTING); } public float getMagnifierZoom() { return magnifierZoom; } @@ -749,7 +813,7 @@ public void setMagnifierZoom(float v) { this.magnifierZoom = v; magnifierPanInitialized = false; } - requestRenderCoalesced(); + requestRenderCoalesced(WAKE_SETTING); } private void computeMagnifierPan(float[] outXForm) { @@ -867,7 +931,7 @@ public void setNativeMode(boolean enable) { cpuSaverMode = enable; viewportNeedsUpdate = true; xServerView.setRenderMode(XServerSurfaceView.RENDERMODE_WHEN_DIRTY); - requestRenderCoalesced(); + requestRenderCoalesced(WAKE_SETTING); } } @@ -879,7 +943,7 @@ public void setMagnifierUIActive(boolean active) { magnifierPanInitialized = false; viewportNeedsUpdate = true; xServerView.setRenderMode(XServerSurfaceView.RENDERMODE_WHEN_DIRTY); - requestRenderCoalesced(); + requestRenderCoalesced(WAKE_SETTING); } public boolean isMagnifierUIActive() { return magnifierUIActive; } diff --git a/app/src/main/runtime/display/ui/XServerSurfaceView.java b/app/src/main/runtime/display/ui/XServerSurfaceView.java index 2f8ae7688..db45d3393 100644 --- a/app/src/main/runtime/display/ui/XServerSurfaceView.java +++ b/app/src/main/runtime/display/ui/XServerSurfaceView.java @@ -35,6 +35,14 @@ public class XServerSurfaceView extends SurfaceView implements SurfaceHolder.Cal private long nextContinuousFrameNs; private int renderMode = RENDERMODE_WHEN_DIRTY; + private static final int REASON_REQUESTED = 0; + private static final int REASON_CONTINUOUS = 1; + private static final int REASON_TRANSIENT_REQ = 2; + private static final int REASON_TRANSIENT_ACTIVE = 3; + private final int[] drawReasonCounts = new int[4]; + private int drawReasonTotal; + private long drawReasonStartNs; + private volatile int width; private volatile int height; @@ -192,6 +200,7 @@ private void renderLoop() { while (true) { Runnable event = null; boolean draw = false; + int reason = 0; synchronized (renderLock) { while (true) { if (!running) break; @@ -211,6 +220,7 @@ private void renderLoop() { if (renderRequested) { draw = true; + reason = REASON_REQUESTED; renderRequested = false; transientRenderRequested = false; if (!transientActive) nextContinuousFrameNs = 0; @@ -219,6 +229,7 @@ private void renderLoop() { if (renderMode == RENDERMODE_CONTINUOUSLY) { draw = true; + reason = REASON_CONTINUOUS; transientRenderRequested = false; nextContinuousFrameNs = 0; break; @@ -226,6 +237,7 @@ private void renderLoop() { if (transientRenderRequested) { draw = true; + reason = REASON_TRANSIENT_REQ; transientRenderRequested = false; nextContinuousFrameNs = now + TRANSIENT_FRAME_INTERVAL_NS; break; @@ -234,6 +246,7 @@ private void renderLoop() { if (transientActive) { if (nextContinuousFrameNs == 0 || now >= nextContinuousFrameNs) { draw = true; + reason = REASON_TRANSIENT_ACTIVE; nextContinuousFrameNs = now + TRANSIENT_FRAME_INTERVAL_NS; break; } @@ -245,6 +258,7 @@ private void renderLoop() { try { renderLock.wait(); } catch (InterruptedException ignore) {} } } + if (draw) countDrawReason(reason); if (!running) break; if (event != null) { try { event.run(); } catch (Throwable ignore) {} @@ -255,6 +269,21 @@ private void renderLoop() { renderer.onSurfaceDestroyed(); } + private void countDrawReason(int reason) { + drawReasonCounts[reason]++; + if (++drawReasonTotal < 600) return; + long nowNs = System.nanoTime(); + float seconds = drawReasonStartNs == 0 ? 0f : (nowNs - drawReasonStartNs) / 1e9f; + drawReasonStartNs = nowNs; + android.util.Log.i("VkRenderer", String.format( + "draw reasons over %.1fs: requested=%d continuous=%d transientReq=%d transientActive=%d guestPresents=%d coalesced:%s", + seconds, drawReasonCounts[REASON_REQUESTED], drawReasonCounts[REASON_CONTINUOUS], + drawReasonCounts[REASON_TRANSIENT_REQ], drawReasonCounts[REASON_TRANSIENT_ACTIVE], + renderer.takeGuestPresentDelta(), renderer.takeWakeBreakdown())); + java.util.Arrays.fill(drawReasonCounts, 0); + drawReasonTotal = 0; + } + private void waitNanosLocked(long nanos) { if (nanos <= 0) return; long millis = nanos / 1_000_000L; diff --git a/app/src/main/runtime/display/winhandler/WinHandler.java b/app/src/main/runtime/display/winhandler/WinHandler.java index 7434bf41b..453c404c0 100644 --- a/app/src/main/runtime/display/winhandler/WinHandler.java +++ b/app/src/main/runtime/display/winhandler/WinHandler.java @@ -51,6 +51,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import com.winlator.cmod.runtime.display.renderer.VulkanRenderer; + public class WinHandler { private static final short CLIENT_PORT = 7946; public static final byte DEFAULT_INPUT_TYPE = 4; @@ -421,7 +423,7 @@ public void mouseEvent(final int flags, final int dx, final int dy, final int wh sendMouseEventPacket(flags, dx, dy, wheelDelta); XServer xServer = activity.getXServer(); if (xServer != null && xServer.getRenderer() != null) - xServer.getRenderer().requestRenderCoalesced(); + xServer.getRenderer().requestRenderCoalesced(VulkanRenderer.WAKE_WINHANDLER); }); } @@ -673,7 +675,7 @@ public void sendGamepadState() { maybeClearGyroTarget(GAMEPAD_SOURCE_VIRTUAL, null); writeVirtualGamepadState(shouldApplyGyroToTarget(GAMEPAD_SOURCE_VIRTUAL, null)); XServer xServer = activity.getXServer(); - if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(); + if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(VulkanRenderer.WAKE_WINHANDLER); } public boolean canUseScreenTouchStick() { @@ -713,7 +715,7 @@ private void writeScreenTouchStickFrame() { private void requestScreenTouchRender() { XServer xServer = activity.getXServer(); - if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(); + if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(VulkanRenderer.WAKE_WINHANDLER); } private void writeVirtualGamepadState(boolean applyGyroOverlay) { @@ -729,7 +731,7 @@ public void injectGestureGamepad(Binding binding, boolean pressed) { setLastGamepadSource(GAMEPAD_SOURCE_VIRTUAL, null); writeVirtualGamepadState(shouldApplyGyroToTarget(GAMEPAD_SOURCE_VIRTUAL, null), true); XServer xServer = activity.getXServer(); - if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(); + if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(VulkanRenderer.WAKE_WINHANDLER); } private void writeVirtualGamepadState(boolean applyGyroOverlay, boolean allowHiddenControls) { @@ -765,7 +767,7 @@ public void sendGamepadState(ExternalController controller) { writeControllerGamepadState( controller, shouldApplyGyroToTarget(GAMEPAD_SOURCE_CONTROLLER, controller)); XServer xServer = activity.getXServer(); - if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(); + if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(VulkanRenderer.WAKE_WINHANDLER); } // Menu owns the controller while open; zero tracked state and push it once so nothing stays held in the guest. @@ -1638,7 +1640,7 @@ private void applyGyroStickToTarget( } XServer xServer = activity.getXServer(); - if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(); + if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(VulkanRenderer.WAKE_WINHANDLER); this.lastGyroTargetSource = gyroActive ? targetSource : GAMEPAD_SOURCE_NONE; this.lastGyroTargetController = diff --git a/app/src/main/runtime/display/xserver/extensions/PresentExtension.java b/app/src/main/runtime/display/xserver/extensions/PresentExtension.java index c92e5c180..82ac6eaaa 100644 --- a/app/src/main/runtime/display/xserver/extensions/PresentExtension.java +++ b/app/src/main/runtime/display/xserver/extensions/PresentExtension.java @@ -487,7 +487,7 @@ public void handleRequest(XClient client, XInputStream inputStream, XOutputStrea if (client.xServer.getRenderer() != null) { client.xServer.getRenderer().onGuestFramePresented(); - client.xServer.getRenderer().requestRenderCoalesced(); + client.xServer.getRenderer().requestRenderImmediate(); } break; } diff --git a/app/src/main/runtime/input/ui/InputControlsView.java b/app/src/main/runtime/input/ui/InputControlsView.java index 1753d0fc3..eede270fb 100644 --- a/app/src/main/runtime/input/ui/InputControlsView.java +++ b/app/src/main/runtime/input/ui/InputControlsView.java @@ -51,6 +51,8 @@ import java.util.Timer; import java.util.TimerTask; +import com.winlator.cmod.runtime.display.renderer.VulkanRenderer; + public class InputControlsView extends View { public static final float DEFAULT_OVERLAY_OPACITY = 0.4f; private static final byte MOUSE_WHEEL_DELTA = 120; @@ -562,7 +564,7 @@ public void run() { } else { xServer.injectPointerMoveDelta(dx, dy); } - if (xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(); + if (xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(VulkanRenderer.WAKE_INPUTVIEW); } } }, @@ -664,7 +666,7 @@ private void processJoystickInput(ExternalController controller) { WinHandler winHandler = xServer != null ? xServer.getWinHandler() : null; if (winHandler != null) { winHandler.sendGamepadState(controller); - if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(); + if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(VulkanRenderer.WAKE_INPUTVIEW); } } @@ -1046,7 +1048,7 @@ public void handleStickInput( if (winHandler != null && sendUpdate) { winHandler.sendGamepadState(); - if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(); + if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(VulkanRenderer.WAKE_INPUTVIEW); } } @@ -1124,7 +1126,7 @@ public void handleInputEvent( if (winHandler != null && sendUpdate && stateChanged) { if (controller != null) winHandler.sendGamepadState(controller); else winHandler.sendGamepadState(); - if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(); + if (xServer != null && xServer.getRenderer() != null) xServer.getRenderer().requestRenderCoalesced(VulkanRenderer.WAKE_INPUTVIEW); } } else { if (binding == Binding.MOUSE_MOVE_LEFT || binding == Binding.MOUSE_MOVE_RIGHT) { From df46f868bb79c11c46fc37232ac891c5a92ab97c Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Mon, 24 Aug 2026 21:19:47 -0400 Subject: [PATCH 25/35] Estimate motion at the game's resolution and back frame generation off when it costs frames The flow pyramid ran at the swapchain extent, so a game rendering 720p into a 1080p surface paid for motion estimation at 1080p while the upscale added no motion detail to find. The chain now takes the guest's own render extent, the same one SGSR1 already computes, and derives the flow scale from its ratio to the output, stepped to five percent and clamped to the quarter-to-full range. Everything below generate scales with the square of that, so a 720p title at 2x drops from roughly 4.4 to 1.9 full-frame-equivalents of compute per real frame. Manual scale stays available and its default falls from full to seventy percent, which is the floor auto lands on for a native-resolution game. The pacer sized itself purely from refresh rate against the guest's present interval, which is a display-cadence budget rather than a cost one. Under GPU contention that reads backwards: generation slows the game, the source interval grows, more vblanks appear free, and the pacer asks for more. Nothing measured whether generating had helped. It now probes one generation down for seven hundred milliseconds every fifteen seconds and keeps the reduction when the render loop speeds up by more than a tenth without it, and it reverts a raised count within six hundred milliseconds when either the guest or the loop interval regresses by a fifth, with the ceiling held through an escalating eight, twenty, forty-five and ninety second backoff. The delta instance count was a literal three that had to be kept in step by hand with a first-level constant living in another file. Both bounds are named in the header now and the count derives from them, so changing where the warp field starts or stops is one edit rather than two that can disagree. --- .../main/cpp/winlator/vk/lsfg/lsfg_chain.cpp | 19 +-- .../main/cpp/winlator/vk/lsfg/lsfg_chain.hpp | 4 +- .../main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp | 136 +++++++++++++++++- .../main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp | 21 +++ .../main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp | 53 +++++-- app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h | 4 +- app/src/main/cpp/winlator/vk/vk_renderer.c | 18 ++- app/src/main/cpp/winlator/vk/vk_state.h | 1 + app/src/main/feature/library/GameSettings.kt | 31 +++- .../ShortcutSettingsComposeDialog.kt | 11 +- app/src/main/res/values/strings.xml | 2 + .../display/XServerDisplayActivity.java | 24 +++- .../display/XServerDrawerFrameGenPane.kt | 35 +++-- .../main/runtime/display/XServerDrawerMenu.kt | 7 +- .../display/renderer/VulkanRenderer.java | 21 ++- 15 files changed, 327 insertions(+), 60 deletions(-) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp index cae436480..62c388048 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp @@ -15,7 +15,9 @@ namespace { constexpr uint32_t FIXED_DESCRIPTOR_SETS = 64; constexpr uint32_t DESCRIPTOR_SETS_PER_SLOT = 112; -constexpr size_t FIRST_DELTA_LEVEL = 4; +[[nodiscard]] constexpr bool HasDelta(size_t i) { + return i >= LSFG_FIRST_DELTA_LEVEL && i <= LSFG_LAST_DELTA_LEVEL; +} } @@ -53,16 +55,17 @@ LsfgChain::LsfgChain(const Device& device, const LsfgShaders& shaders, VkExtent2 i == 0 ? nullptr : &gamma[i - 1].Output()); if (!gamma[i].Valid()) return; - if (i < FIRST_DELTA_LEVEL) { + if (!HasDelta(i)) { continue; } - const size_t index = i - FIRST_DELTA_LEVEL; + const size_t index = i - LSFG_FIRST_DELTA_LEVEL; + const bool first = i == LSFG_FIRST_DELTA_LEVEL; delta[index] = LsfgDelta(device, shaders, resources, descriptor_pool, alpha[level].Outputs(), beta.Output(level), - i == FIRST_DELTA_LEVEL ? nullptr : &gamma[i - 1].Output(), - i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output1(), - i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output2()); + first ? nullptr : &gamma[i - 1].Output(), + first ? nullptr : &delta[index - 1].Output1(), + first ? nullptr : &delta[index - 1].Output2()); if (!delta[index].Valid()) return; } @@ -109,8 +112,8 @@ void LsfgChain::DispatchGeneration(VkCommandBuffer cmdbuf, uint64_t frame_count, const size_t slot = LsfgGenerationSlot(generation_count, generation); for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) { gamma[i].Dispatch(cmdbuf, frame_count, slot); - if (i >= FIRST_DELTA_LEVEL) { - delta[i - FIRST_DELTA_LEVEL].Dispatch(cmdbuf, frame_count, slot); + if (HasDelta(i)) { + delta[i - LSFG_FIRST_DELTA_LEVEL].Dispatch(cmdbuf, frame_count, slot); } } generate.Dispatch(cmdbuf, frame_count, slot, target, image, extent); diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.hpp index eea640df4..f0611dac0 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.hpp @@ -20,7 +20,9 @@ namespace lsfg { class LsfgShaders; -constexpr size_t LSFG_DELTA_INSTANCES = 3; +constexpr size_t LSFG_FIRST_DELTA_LEVEL = 4; +constexpr size_t LSFG_LAST_DELTA_LEVEL = LSFG_MIP_LEVELS - 1; +constexpr size_t LSFG_DELTA_INSTANCES = LSFG_LAST_DELTA_LEVEL + 1 - LSFG_FIRST_DELTA_LEVEL; class LsfgChain { public: diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp index 8d5d08944..3e879f14d 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp @@ -22,6 +22,28 @@ constexpr float CREDIT_EPSILON = 1.0e-4f; constexpr float SOURCE_ACCUM_FLOOR = 0.01f; constexpr uint32_t MIN_RATE_SAMPLES = 12; +constexpr float REGRESSION_RATIO = 1.20f; +constexpr float COST_PROBE_GAIN = 0.10f; +constexpr uint32_t MAX_COST_FAILURES = 4; + +constexpr auto RAISE_SETTLE_DURATION = std::chrono::milliseconds(600); +constexpr auto COST_PROBE_INTERVAL = std::chrono::seconds(15); +constexpr auto COST_PROBE_WINDOW = std::chrono::milliseconds(700); + +[[nodiscard]] Clock::duration CostBackoff(uint32_t failures) { + switch (failures) { + case 0: + case 1: + return std::chrono::seconds(8); + case 2: + return std::chrono::seconds(20); + case 3: + return std::chrono::seconds(45); + default: + return std::chrono::seconds(90); + } +} + } size_t LsfgPacer::MaxGenerations() const { @@ -98,6 +120,98 @@ size_t LsfgPacer::HeadroomLimit(size_t current, bool allow_fractional) const { return static_cast(std::floor(budget)) - 1; } +size_t LsfgPacer::CostLimit(Clock::time_point now, size_t current) { + if (cost_probe_until) { + const size_t probe_limit = cost_probe_from > 0 ? cost_probe_from - 1 : 0; + if (now < *cost_probe_until) { + cost_probe_active = true; + return probe_limit; + } + cost_probe_until.reset(); + cost_probe_active = true; + if (cost_probe_baseline > 0.0f && loop_interval > 0.0f && + loop_interval < cost_probe_baseline * (1.0f - COST_PROBE_GAIN)) { + cost_failures = std::min(cost_failures + 1, MAX_COST_FAILURES); + cost_ceiling = probe_limit; + cost_backoff_until = now + CostBackoff(cost_failures); + return cost_ceiling; + } + } else { + cost_probe_active = false; + } + + if (settle_until) { + if (now < *settle_until) { + return current; + } + settle_until.reset(); + + const bool source_regressed = + pre_raise_source_interval > 0.0f && source_interval > 0.0f && + source_interval > pre_raise_source_interval * REGRESSION_RATIO; + const bool loop_regressed = pre_raise_loop_interval > 0.0f && loop_interval > 0.0f && + loop_interval > pre_raise_loop_interval * REGRESSION_RATIO; + + if (source_regressed || loop_regressed) { + cost_failures = std::min(cost_failures + 1, MAX_COST_FAILURES); + cost_ceiling = pre_raise_limit; + cost_backoff_until = now + CostBackoff(cost_failures); + return cost_ceiling; + } + cost_failures = 0; + } + + if (cost_backoff_until) { + if (now < *cost_backoff_until) { + return cost_ceiling; + } + cost_backoff_until.reset(); + cost_ceiling = LSFG_MAX_MULTIPLIER - 1; + } + + if (current > 0 && !settle_until && RatesSettled()) { + if (!next_cost_probe) { + next_cost_probe = now + COST_PROBE_INTERVAL; + } else if (now >= *next_cost_probe) { + cost_probe_baseline = loop_interval; + cost_probe_from = current; + cost_probe_until = now + COST_PROBE_WINDOW; + next_cost_probe = now + COST_PROBE_INTERVAL; + cost_probe_active = true; + return current - 1; + } + } + + return cost_ceiling; +} + +void LsfgPacer::NoteLimitChange(Clock::time_point now, size_t previous_limit) { + if (cost_probe_active) { + settle_until.reset(); + return; + } + if (governed_limit > previous_limit) { + if (!RatesSettled()) { + settle_until.reset(); + return; + } + pre_raise_source_interval = source_interval; + pre_raise_loop_interval = loop_interval; + pre_raise_limit = previous_limit; + settle_until = now + RAISE_SETTLE_DURATION; + } else if (governed_limit < previous_limit) { + settle_until.reset(); + } +} + +size_t LsfgPacer::Govern(Clock::time_point now, size_t ceiling, bool allow_fractional) { + const size_t previous = governed_limit; + governed_limit = std::min({ceiling, HeadroomLimit(limit, allow_fractional), + CostLimit(now, governed_limit)}); + NoteLimitChange(now, previous); + return governed_limit; +} + LsfgPlan LsfgPacer::Plan(size_t capacity, uint64_t source_frames) { const size_t ceiling = std::min(capacity, MaxGenerations()); if (ceiling == 0) { @@ -129,11 +243,11 @@ LsfgPlan LsfgPacer::Plan(size_t capacity, uint64_t source_frames) { if (target_rate == 0.0f) { output_credit = 0.0f; - limit = std::min(ceiling, HeadroomLimit(limit, false)); + limit = Govern(now, ceiling, false); return LsfgPlan{limit, limit > 0}; } - const size_t allowed = std::min(ceiling, HeadroomLimit(limit, true)); + const size_t allowed = Govern(now, ceiling, true); const float desired_outputs = loop_interval * target_rate; if (allowed == 0 || desired_outputs <= 1.0f) { output_credit = 0.0f; @@ -165,7 +279,12 @@ LsfgPacerStats LsfgPacer::Stats() const { stats.target_rate = static_cast(config.target_rate); stats.slots = config.refresh_rate * source_interval; stats.limit = limit; + stats.cost_ceiling = cost_ceiling; stats.rates_settled = RatesSettled(); + stats.settling = settle_until.has_value(); + stats.backing_off = cost_backoff_until.has_value(); + stats.probing = cost_probe_until.has_value(); + stats.cost_failures = cost_failures; stats.last_drawn = last_drawn; stats.last_elapsed = last_elapsed; stats.source_frames = last_source_frames; @@ -175,17 +294,30 @@ LsfgPacerStats LsfgPacer::Stats() const { void LsfgPacer::Reset() { last_frame.reset(); last_source_sample.reset(); + settle_until.reset(); + cost_backoff_until.reset(); + cost_probe_until.reset(); + next_cost_probe.reset(); last_source_frames = 0; source_interval = 0.0f; source_frame_accum = 0.0f; source_time_accum = 0.0f; loop_interval = 0.0f; + cost_probe_baseline = 0.0f; + pre_raise_source_interval = 0.0f; + pre_raise_loop_interval = 0.0f; source_samples = 0; loop_samples = 0; + cost_failures = 0; last_drawn = 0; last_elapsed = 0.0f; output_credit = 0.0f; limit = 0; + governed_limit = LSFG_MAX_MULTIPLIER - 1; + cost_probe_from = 0; + pre_raise_limit = 0; + cost_ceiling = LSFG_MAX_MULTIPLIER - 1; + cost_probe_active = false; } } diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp index 6b7b954ce..b58273fac 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp @@ -30,7 +30,12 @@ struct LsfgPacerStats { float target_rate{}; float slots{}; size_t limit{}; + size_t cost_ceiling{}; bool rates_settled{}; + bool settling{}; + bool backing_off{}; + bool probing{}; + uint32_t cost_failures{}; uint64_t last_drawn{}; float last_elapsed{}; uint64_t source_frames{}; @@ -61,22 +66,38 @@ class LsfgPacer { void TrackLoopRate(float interval_seconds); [[nodiscard]] bool RatesSettled() const; [[nodiscard]] size_t HeadroomLimit(size_t current, bool allow_fractional) const; + [[nodiscard]] size_t CostLimit(Clock::time_point now, size_t current); + void NoteLimitChange(Clock::time_point now, size_t previous_limit); + [[nodiscard]] size_t Govern(Clock::time_point now, size_t ceiling, bool allow_fractional); LsfgPacerConfig config; std::optional last_frame; std::optional last_source_sample; + std::optional settle_until; + std::optional cost_backoff_until; + std::optional cost_probe_until; + std::optional next_cost_probe; uint64_t last_source_frames{}; float source_interval{}; float source_frame_accum{}; float source_time_accum{}; float loop_interval{}; + float cost_probe_baseline{}; + float pre_raise_source_interval{}; + float pre_raise_loop_interval{}; uint32_t source_samples{}; uint32_t loop_samples{}; + uint32_t cost_failures{}; uint64_t last_drawn{}; float last_elapsed{}; float output_credit{}; size_t limit{}; + size_t governed_limit{LSFG_MAX_MULTIPLIER - 1}; + size_t cost_probe_from{}; + size_t pre_raise_limit{}; + size_t cost_ceiling{LSFG_MAX_MULTIPLIER - 1}; + bool cost_probe_active{}; }; } diff --git a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp index 4edbfa50b..590778ca6 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp @@ -5,6 +5,7 @@ #include "lsfg_shaders.hpp" #include +#include #include #include @@ -19,6 +20,10 @@ constexpr uint64_t LSFG_REQUIRED_FRAMES = 2; constexpr uint32_t LSFG_RECURRENCE_FRAMES = 2; constexpr uint64_t LSFG_TELEMETRY_INTERVAL = 120; +constexpr float LSFG_FLOW_SCALE_MIN = 0.25f; +constexpr float LSFG_FLOW_SCALE_MAX = 1.0f; +constexpr float LSFG_FLOW_SCALE_STEPS = 20.0f; + VkImageMemoryBarrier MakeTransitionBarrier(VkImage image, VkAccessFlags src_access, VkAccessFlags dst_access, VkImageLayout old_layout, VkImageLayout new_layout) { @@ -88,9 +93,11 @@ struct VkrLsfg { lsfg::LsfgPlan plan{}; VkExtent2D built_extent{}; + VkExtent2D peak_guest_extent{}; VkFormat built_format{VK_FORMAT_UNDEFINED}; float built_flow_scale{}; float flow_scale{1.0f}; + bool flow_scale_auto{true}; uint64_t frame_count{}; uint64_t last_count{}; @@ -102,6 +109,16 @@ struct VkrLsfg { bool unavailable{}; }; +static float lsfg_effective_flow_scale(const VkrLsfg* lsfg, uint32_t width) { + if (!lsfg->flow_scale_auto) return lsfg->flow_scale; + if (width == 0 || lsfg->peak_guest_extent.width == 0) return LSFG_FLOW_SCALE_MAX; + + const float ratio = + static_cast(lsfg->peak_guest_extent.width) / static_cast(width); + const float stepped = std::ceil(ratio * LSFG_FLOW_SCALE_STEPS) / LSFG_FLOW_SCALE_STEPS; + return std::clamp(stepped, LSFG_FLOW_SCALE_MIN, LSFG_FLOW_SCALE_MAX); +} + VkrLsfg* vkr_lsfg_create(VkDevice device, VkPhysicalDevice physical_device, const char* cache_path) { if (device == VK_NULL_HANDLE || physical_device == VK_NULL_HANDLE || cache_path == nullptr) { @@ -128,16 +145,22 @@ void vkr_lsfg_destroy(VkrLsfg* lsfg) { } void vkr_lsfg_configure(VkrLsfg* lsfg, uint32_t multiplier, uint32_t target_rate, - float flow_scale, float refresh_rate) { + float flow_scale, bool flow_scale_auto, float refresh_rate) { if (!lsfg) return; - const lsfg::LsfgPacerConfig previous = lsfg->pacer.Config(); - lsfg::LsfgPacerConfig config = previous; + lsfg::LsfgPacerConfig config = lsfg->pacer.Config(); config.multiplier = multiplier; config.target_rate = target_rate; config.refresh_rate = refresh_rate; lsfg->pacer.SetConfig(config); - lsfg->flow_scale = std::clamp(flow_scale, 0.25f, 1.0f); + lsfg->flow_scale = std::clamp(flow_scale, LSFG_FLOW_SCALE_MIN, LSFG_FLOW_SCALE_MAX); + lsfg->flow_scale_auto = flow_scale_auto; +} + +void vkr_lsfg_set_guest_extent(VkrLsfg* lsfg, uint32_t width, uint32_t height) { + if (!lsfg || width == 0 || height == 0) return; + lsfg->peak_guest_extent.width = std::max(lsfg->peak_guest_extent.width, width); + lsfg->peak_guest_extent.height = std::max(lsfg->peak_guest_extent.height, height); } void vkr_lsfg_set_refresh_rate(VkrLsfg* lsfg, float refresh_rate) { @@ -154,7 +177,7 @@ bool vkr_lsfg_needs_rebuild(const VkrLsfg* lsfg, uint32_t width, uint32_t height if (!lsfg || lsfg->unavailable) return false; return !lsfg->chain || lsfg->built_extent.width != width || lsfg->built_extent.height != height || lsfg->built_format != format - || lsfg->built_flow_scale != lsfg->flow_scale; + || lsfg->built_flow_scale != lsfg_effective_flow_scale(lsfg, width); } bool vkr_lsfg_prepare(VkrLsfg* lsfg, uint32_t width, uint32_t height, VkFormat format) { @@ -165,9 +188,11 @@ bool vkr_lsfg_prepare(VkrLsfg* lsfg, uint32_t width, uint32_t height, VkFormat f return lsfg->chain && lsfg->chain->Valid(); } + const float scale = lsfg_effective_flow_scale(lsfg, width); + lsfg->chain.reset(); lsfg->chain = std::make_unique( - lsfg->device, *lsfg->shaders, VkExtent2D{width, height}, format, lsfg->flow_scale); + lsfg->device, *lsfg->shaders, VkExtent2D{width, height}, format, scale); if (!lsfg->chain->Valid()) { LSFG_LOGW("chain build failed at %ux%u; frame generation unavailable", width, height); lsfg->chain.reset(); @@ -177,15 +202,18 @@ bool vkr_lsfg_prepare(VkrLsfg* lsfg, uint32_t width, uint32_t height, VkFormat f lsfg->built_extent = VkExtent2D{width, height}; lsfg->built_format = format; - lsfg->built_flow_scale = lsfg->flow_scale; + lsfg->built_flow_scale = scale; lsfg->frame_count = 0; lsfg->plan_calls = 0; lsfg->warm_streak = 0; lsfg->warm = false; lsfg->generated = false; lsfg->pacer.Reset(); - LSFG_LOGI("chain built at %ux%u, flow scale %.2f", width, height, - (double)lsfg->built_flow_scale); + LSFG_LOGI("chain built at %ux%u, flow %ux%u scale %.2f (%s, guest %ux%u)", width, height, + (unsigned)(width * lsfg->built_flow_scale), + (unsigned)(height * lsfg->built_flow_scale), (double)lsfg->built_flow_scale, + lsfg->flow_scale_auto ? "auto" : "manual", lsfg->peak_guest_extent.width, + lsfg->peak_guest_extent.height); return true; } @@ -203,11 +231,13 @@ uint32_t vkr_lsfg_plan(VkrLsfg* lsfg, uint32_t capacity, uint64_t source_frames) if ((lsfg->plan_calls++ % LSFG_TELEMETRY_INTERVAL) == 0) { const lsfg::LsfgPacerStats stats = lsfg->pacer.Stats(); LSFG_LOGI("pace gen=%zu max=%zu cap=%u guest=%.1f loop=%.1f refresh=%.1f target=%.0f " - "slots=%.2f drawn=%llu%s", + "slots=%.2f drawn=%llu cost=%zu fails=%u%s%s%s%s", lsfg->plan.generations, lsfg->pacer.MaxGenerations(), capacity, (double)stats.source_rate, (double)stats.loop_rate, (double)stats.refresh_rate, (double)stats.target_rate, (double)stats.slots, - (unsigned long long)stats.last_drawn, + (unsigned long long)stats.last_drawn, stats.cost_ceiling, stats.cost_failures, + stats.probing ? " probing" : "", stats.settling ? " settling" : "", + stats.backing_off ? " backoff" : "", stats.rates_settled ? (lsfg->warm ? "" : " cold") : " sampling"); } @@ -248,6 +278,7 @@ void vkr_lsfg_forget_targets(VkrLsfg* lsfg) { void vkr_lsfg_reset(VkrLsfg* lsfg) { if (!lsfg) return; lsfg->pacer.Reset(); + lsfg->peak_guest_extent = VkExtent2D{}; lsfg->warm_streak = 0; lsfg->warm = false; lsfg->generated = false; diff --git a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h index 9eded59a3..5900c6ecf 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h +++ b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h @@ -18,10 +18,12 @@ VkrLsfg* vkr_lsfg_create(VkDevice device, VkPhysicalDevice physical_device, void vkr_lsfg_destroy(VkrLsfg* lsfg); void vkr_lsfg_configure(VkrLsfg* lsfg, uint32_t multiplier, uint32_t target_rate, - float flow_scale, float refresh_rate); + float flow_scale, bool flow_scale_auto, float refresh_rate); void vkr_lsfg_set_refresh_rate(VkrLsfg* lsfg, float refresh_rate); +void vkr_lsfg_set_guest_extent(VkrLsfg* lsfg, uint32_t width, uint32_t height); + bool vkr_lsfg_needs_rebuild(const VkrLsfg* lsfg, uint32_t width, uint32_t height, VkFormat format); diff --git a/app/src/main/cpp/winlator/vk/vk_renderer.c b/app/src/main/cpp/winlator/vk/vk_renderer.c index 994e25194..ad2ef28a8 100644 --- a/app/src/main/cpp/winlator/vk/vk_renderer.c +++ b/app/src/main/cpp/winlator/vk/vk_renderer.c @@ -1870,8 +1870,8 @@ static void create_lsfg(VkRenderer* r) { } vkr_lsfg_configure(r->lsfg, r->framegen_multiplier ? r->framegen_multiplier : 2u, r->framegen_target_rate, - r->framegen_flow_scale > 0.0f ? r->framegen_flow_scale : 1.0f, - r->framegen_refresh_rate); + r->framegen_flow_scale > 0.0f ? r->framegen_flow_scale : 0.7f, + r->framegen_flow_scale_auto, r->framegen_refresh_rate); } static uint32_t framegen_extra_images(const VkRenderer* r) { @@ -2458,6 +2458,11 @@ static bool record_and_submit_frame(VkRenderer* r) { bool via_composite = r->framegen_requested && r->framegen_supported && r->swapchain_transfer_dst; + if (via_composite && r->lsfg) { + VkExtent2D guest = compute_sgsr1_source_extent(r, &snap); + vkr_lsfg_set_guest_extent(r->lsfg, guest.width, guest.height); + } + uint32_t framegen_capacity = 0; if (via_composite && r->lsfg && r->swapchain_image_count > 2) { framegen_capacity = r->swapchain_image_count - 2; @@ -3622,7 +3627,8 @@ JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationRefreshRate)(JNIEnv* env, JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationMode)(JNIEnv* env, jclass clazz, jlong handle, jint multiplier, - jint targetRate, jint flowScalePct) { + jint targetRate, jint flowScalePct, + jboolean flowScaleAuto) { (void)env; (void)clazz; VkRenderer* r = (VkRenderer*)(intptr_t)handle; if (!r) return; @@ -3631,10 +3637,12 @@ JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationMode)(JNIEnv* env, jclass const uint32_t previous_images = framegen_extra_images(r); r->framegen_multiplier = multiplier < 2 ? 2u : (uint32_t)multiplier; r->framegen_target_rate = targetRate < 0 ? 0u : (uint32_t)targetRate; - r->framegen_flow_scale = flowScalePct <= 0 ? 1.0f : (float)flowScalePct / 100.0f; + r->framegen_flow_scale = flowScalePct <= 0 ? 0.7f : (float)flowScalePct / 100.0f; + r->framegen_flow_scale_auto = flowScaleAuto == JNI_TRUE; if (r->lsfg) { vkr_lsfg_configure(r->lsfg, r->framegen_multiplier, r->framegen_target_rate, - r->framegen_flow_scale, r->framegen_refresh_rate); + r->framegen_flow_scale, r->framegen_flow_scale_auto, + r->framegen_refresh_rate); } if (framegen_extra_images(r) != previous_images) { wait_inflight_frames(r); diff --git a/app/src/main/cpp/winlator/vk/vk_state.h b/app/src/main/cpp/winlator/vk/vk_state.h index 2155d69a1..13792983c 100644 --- a/app/src/main/cpp/winlator/vk/vk_state.h +++ b/app/src/main/cpp/winlator/vk/vk_state.h @@ -423,6 +423,7 @@ typedef struct VkRenderer { uint32_t framegen_multiplier; uint32_t framegen_target_rate; float framegen_flow_scale; + bool framegen_flow_scale_auto; float framegen_refresh_rate; int32_t framegen_refresh_mhz; uint64_t framegen_source_frames; diff --git a/app/src/main/feature/library/GameSettings.kt b/app/src/main/feature/library/GameSettings.kt index b40040fea..f0e8db036 100644 --- a/app/src/main/feature/library/GameSettings.kt +++ b/app/src/main/feature/library/GameSettings.kt @@ -426,7 +426,8 @@ class GameSettingsStateHolder { val frameGenEnabled = mutableStateOf(false) val frameGenMultiplier = mutableIntStateOf(2) val frameGenTargetRate = mutableIntStateOf(0) - val frameGenFlowScale = mutableIntStateOf(100) + val frameGenFlowScale = mutableIntStateOf(70) + val frameGenFlowScaleAuto = mutableStateOf(true) val frameGenShaderState = mutableIntStateOf(FRAMEGEN_SHADERS_CHECKING) val frameGenSourceName = mutableStateOf("") @@ -1803,6 +1804,7 @@ private fun FrameGenerationCard(state: GameSettingsStateHolder) { onCheckedChange = { state.frameGenEnabled.value = it }, ) + Text( text = when (shaders) { @@ -1883,14 +1885,29 @@ private fun FrameGenerationCard(state: GameSettingsStateHolder) { Spacer(Modifier.height(SettingItemGap)) - SettingSlider( - label = stringResource(R.string.session_drawer_frame_generation_flow_scale), - value = state.frameGenFlowScale.intValue, - range = 25..100, - steps = 14, - onValueChange = { state.frameGenFlowScale.intValue = it }, + SettingSwitch( + label = stringResource(R.string.session_drawer_frame_generation_flow_scale_auto), + checked = state.frameGenFlowScaleAuto.value, + onCheckedChange = { state.frameGenFlowScaleAuto.value = it }, + ) + + Text( + text = stringResource(R.string.session_drawer_frame_generation_flow_scale_auto_note), + color = TextSecondary, + fontSize = SettingLabelSize, + lineHeight = SettingLabelSize * 1.4f, ) + if (!state.frameGenFlowScaleAuto.value) { + SettingSlider( + label = stringResource(R.string.session_drawer_frame_generation_flow_scale), + value = state.frameGenFlowScale.intValue, + range = 25..100, + steps = 14, + onValueChange = { state.frameGenFlowScale.intValue = it }, + ) + } + Text( text = stringResource(R.string.session_drawer_frame_generation_note), color = TextSecondary, diff --git a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt index 896b1819e..747c32bc4 100644 --- a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt +++ b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt @@ -531,10 +531,15 @@ class ShortcutSettingsComposeDialog private constructor( ?.coerceAtLeast(0) ?: 0 state.frameGenFlowScale.intValue = - getShortcutSetting("frameGenFlowScale", container.getExtra("frameGenFlowScale", "100")) + getShortcutSetting("frameGenFlowScale", container.getExtra("frameGenFlowScale", "70")) .toIntOrNull() ?.coerceIn(25, 100) - ?: 100 + ?: 70 + state.frameGenFlowScaleAuto.value = + getShortcutSetting( + "frameGenFlowScaleAuto", + container.getExtra("frameGenFlowScaleAuto", "1"), + ) != "0" // shortcut override else container value; legacy single reshadeEffect/flat params migrated in parse val reshadeEffects = com.winlator.cmod.runtime.reshade.ReshadeManager.scanEffects(context) @@ -1320,11 +1325,13 @@ class ShortcutSettingsComposeDialog private constructor( shortcut.putExtra("frameGenMultiplier", state.frameGenMultiplier.intValue.coerceIn(2, 4).toString()) shortcut.putExtra("frameGenTargetRate", state.frameGenTargetRate.intValue.coerceAtLeast(0).toString()) shortcut.putExtra("frameGenFlowScale", state.frameGenFlowScale.intValue.coerceIn(25, 100).toString()) + shortcut.putExtra("frameGenFlowScaleAuto", if (state.frameGenFlowScaleAuto.value) "1" else "0") } else { shortcut.putExtra("frameGen", null) shortcut.putExtra("frameGenMultiplier", null) shortcut.putExtra("frameGenTargetRate", null) shortcut.putExtra("frameGenFlowScale", null) + shortcut.putExtra("frameGenFlowScaleAuto", null) } // saveOverride not putExtra: putExtra leaves hasContainerOverride false, so a reshade-only shortcut gets use_container_defaults=1 and reads back the container's extras diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 051bba017..19263bcc1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -773,6 +773,8 @@ E.g. META for META key, \n Fixed %1$d fps Flow Scale + Match Motion To Game + Estimates motion at the resolution the game actually renders instead of the upscaled output. Costs nothing in accuracy, since upscaling adds no motion detail. Interpolates between rendered frames with Lossless Scaling. Smoother motion, but one extra frame of input latency. Climbs toward the target only while it measurably helps, instead of holding a fixed multiplier. Install Lossless Scaling in container settings to use frame generation. diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index ee841a24c..51ed93350 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -450,7 +450,8 @@ private boolean isAnyControllerConnected() { private boolean frameGenEnabled = false; private int frameGenMultiplier = 2; private int frameGenTargetRate = 0; - private int frameGenFlowScale = 100; + private int frameGenFlowScale = 70; + private boolean frameGenFlowScaleAuto = true; private String frameGenCachePath = null; private boolean sgsrEnabled = false; private boolean sgsrRuntimeEnabled = false; @@ -748,7 +749,8 @@ private void applyFrameGenerationSettings(VulkanRenderer renderer, Container con String containerValue = container != null ? container.getExtra("frameGen", "0") : "0"; String containerMultiplier = container != null ? container.getExtra("frameGenMultiplier", "2") : "2"; String containerTargetRate = container != null ? container.getExtra("frameGenTargetRate", "0") : "0"; - String containerFlowScale = container != null ? container.getExtra("frameGenFlowScale", "100") : "100"; + String containerFlowScale = container != null ? container.getExtra("frameGenFlowScale", "70") : "70"; + String containerFlowScaleAuto = container != null ? container.getExtra("frameGenFlowScaleAuto", "1") : "1"; frameGenEnabled = "1".equals(getFrameGenSetting("frameGen", containerValue)); frameGenMultiplier = clampFrameGenMultiplier( @@ -756,7 +758,9 @@ private void applyFrameGenerationSettings(VulkanRenderer renderer, Container con frameGenTargetRate = Math.max(0, parseSettingInt(getFrameGenSetting("frameGenTargetRate", containerTargetRate), 0)); frameGenFlowScale = clampFrameGenFlowScale( - parseSettingInt(getFrameGenSetting("frameGenFlowScale", containerFlowScale), 100)); + parseSettingInt(getFrameGenSetting("frameGenFlowScale", containerFlowScale), 70)); + frameGenFlowScaleAuto = !"0".equals( + getFrameGenSetting("frameGenFlowScaleAuto", containerFlowScaleAuto)); if (frameGenEnabled) { int result = com.winlator.cmod.feature.library.LosslessAutoImport.INSTANCE.sync(this).getResult(); @@ -789,12 +793,14 @@ private void applyFrameGeneration(VulkanRenderer renderer) { renderer.setFrameGenerationShaders(frameGenCachePath); float refreshRate = applyFrameGenerationDisplayMode(); - renderer.setFrameGenerationMode(frameGenMultiplier, frameGenTargetRate, frameGenFlowScale); + renderer.setFrameGenerationMode(frameGenMultiplier, frameGenTargetRate, frameGenFlowScale, + frameGenFlowScaleAuto); renderer.setFrameGenerationRefreshRate(refreshRate); renderer.setFrameGenerationEnabled(true); syncFrameGenerationHud(); Log.i("XServerDisplayActivity", "Frame generation on: multiplier=" + frameGenMultiplier + " targetRate=" + frameGenTargetRate + " flowScale=" + frameGenFlowScale + + " flowScaleAuto=" + frameGenFlowScaleAuto + " refreshRate=" + refreshRate); } @@ -905,11 +911,13 @@ private void saveFrameGenerationSettings() { shortcut.putExtra("frameGenMultiplier", String.valueOf(frameGenMultiplier)); shortcut.putExtra("frameGenTargetRate", String.valueOf(frameGenTargetRate)); shortcut.putExtra("frameGenFlowScale", String.valueOf(frameGenFlowScale)); + shortcut.putExtra("frameGenFlowScaleAuto", frameGenFlowScaleAuto ? "1" : "0"); } else { shortcut.putExtra("frameGen", null); shortcut.putExtra("frameGenMultiplier", null); shortcut.putExtra("frameGenTargetRate", null); shortcut.putExtra("frameGenFlowScale", null); + shortcut.putExtra("frameGenFlowScaleAuto", null); } shortcut.saveData(); } else if (container != null) { @@ -917,6 +925,7 @@ private void saveFrameGenerationSettings() { container.putExtra("frameGenMultiplier", String.valueOf(frameGenMultiplier)); container.putExtra("frameGenTargetRate", String.valueOf(frameGenTargetRate)); container.putExtra("frameGenFlowScale", String.valueOf(frameGenFlowScale)); + container.putExtra("frameGenFlowScaleAuto", frameGenFlowScaleAuto ? "1" : "0"); container.saveData(); } } @@ -4497,6 +4506,7 @@ private void renderDrawerMenu() { frameGenMultiplier, frameGenTargetRate, frameGenFlowScale, + frameGenFlowScaleAuto, getString(R.string.session_drawer_frame_generation)); // Always-present "Output" tab (live controls while swapped, otherwise a Cast entry point). @@ -4895,6 +4905,12 @@ public void onFrameGenFlowScaleChanged(int percent) { applyFrameGenerationLive(); } + @Override + public void onFrameGenFlowScaleAutoChanged(boolean auto) { + frameGenFlowScaleAuto = auto; + applyFrameGenerationLive(); + } + @Override public void onSGSREnabledChanged(boolean enabled) { boolean wasEnabled = sgsrEnabled; diff --git a/app/src/main/runtime/display/XServerDrawerFrameGenPane.kt b/app/src/main/runtime/display/XServerDrawerFrameGenPane.kt index cccd1d6a5..f747f123d 100644 --- a/app/src/main/runtime/display/XServerDrawerFrameGenPane.kt +++ b/app/src/main/runtime/display/XServerDrawerFrameGenPane.kt @@ -200,18 +200,31 @@ private fun FrameGenerationSection( ) } - NavSliderRow( - label = stringResource(R.string.session_drawer_frame_generation_flow_scale), - valueText = "${state.frameGenFlowScale}%", - value = state.frameGenFlowScale.toFloat(), - valueRange = FrameGenFlowScaleMin.toFloat()..FrameGenFlowScaleMax.toFloat(), - steps = (FrameGenFlowScaleMax - FrameGenFlowScaleMin) / 5 - 1, - onValueChange = { - listener.onFrameGenFlowScaleChanged( - it.roundToInt().coerceIn(FrameGenFlowScaleMin, FrameGenFlowScaleMax), - ) - }, + NavBooleanRow( + title = stringResource(R.string.session_drawer_frame_generation_flow_scale_auto), + checked = state.frameGenFlowScaleAuto, + onCheckedChange = listener::onFrameGenFlowScaleAutoChanged, ) + + FrameGenNote( + stringResource(R.string.session_drawer_frame_generation_flow_scale_auto_note), + paneScale, + ) + + if (!state.frameGenFlowScaleAuto) { + NavSliderRow( + label = stringResource(R.string.session_drawer_frame_generation_flow_scale), + valueText = "${state.frameGenFlowScale}%", + value = state.frameGenFlowScale.toFloat(), + valueRange = FrameGenFlowScaleMin.toFloat()..FrameGenFlowScaleMax.toFloat(), + steps = (FrameGenFlowScaleMax - FrameGenFlowScaleMin) / 5 - 1, + onValueChange = { + listener.onFrameGenFlowScaleChanged( + it.roundToInt().coerceIn(FrameGenFlowScaleMin, FrameGenFlowScaleMax), + ) + }, + ) + } } } } diff --git a/app/src/main/runtime/display/XServerDrawerMenu.kt b/app/src/main/runtime/display/XServerDrawerMenu.kt index 3002ba98b..7f0634e0e 100644 --- a/app/src/main/runtime/display/XServerDrawerMenu.kt +++ b/app/src/main/runtime/display/XServerDrawerMenu.kt @@ -624,7 +624,8 @@ data class XServerDrawerState( val frameGenEnabled: Boolean = false, val frameGenMultiplier: Int = 2, val frameGenTargetRate: Int = 0, - val frameGenFlowScale: Int = 100, + val frameGenFlowScale: Int = 70, + val frameGenFlowScaleAuto: Boolean = true, val screenEffectsCardExpanded: Boolean = false, val sgsrEnabled: Boolean = false, val sgsrSharpness: Int = 100, @@ -1040,6 +1041,8 @@ interface XServerDrawerActionListener { fun onFrameGenFlowScaleChanged(percent: Int) + fun onFrameGenFlowScaleAutoChanged(auto: Boolean) + fun onScreenEffectsCardExpandedChanged(expanded: Boolean) fun onOutputResolutionSelected(index: Int) @@ -1494,6 +1497,7 @@ fun withFrameGenState( multiplier: Int, targetRate: Int, flowScale: Int, + flowScaleAuto: Boolean, frameGenTitle: String, ): XServerDrawerState = state.copy( @@ -1511,6 +1515,7 @@ fun withFrameGenState( frameGenMultiplier = multiplier.coerceIn(2, FrameGenMultipliers.last()), frameGenTargetRate = targetRate.coerceAtLeast(0), frameGenFlowScale = flowScale.coerceIn(FrameGenFlowScaleMin, FrameGenFlowScaleMax), + frameGenFlowScaleAuto = flowScaleAuto, ) // Append the always-present "Output" tab item and its state to the drawer state. diff --git a/app/src/main/runtime/display/renderer/VulkanRenderer.java b/app/src/main/runtime/display/renderer/VulkanRenderer.java index ad31973f7..c2262abb4 100644 --- a/app/src/main/runtime/display/renderer/VulkanRenderer.java +++ b/app/src/main/runtime/display/renderer/VulkanRenderer.java @@ -287,7 +287,8 @@ public void attachSurface(Surface surface) { nativeSetFrameGenerationShaders(nativeHandle, frameGenerationShaderCache); } nativeSetFrameGenerationMode(nativeHandle, frameGenerationMultiplier, - frameGenerationTargetRate, frameGenerationFlowScale); + frameGenerationTargetRate, frameGenerationFlowScale, + frameGenerationFlowScaleAuto); nativeSetFrameGenerationRefreshRate(nativeHandle, frameGenerationRefreshRate); if (frameGenerationRequested) { nativeSetFrameGenerationEnabled(nativeHandle, true); @@ -971,7 +972,8 @@ public void setPresentMode(int mode) { private String frameGenerationShaderCache = null; private int frameGenerationMultiplier = 2; private int frameGenerationTargetRate = 0; - private int frameGenerationFlowScale = 100; + private int frameGenerationFlowScale = 70; + private boolean frameGenerationFlowScaleAuto = true; private float frameGenerationRefreshRate = 0f; public void setFrameGenerationEnabled(boolean enabled) { @@ -985,21 +987,25 @@ public void setFrameGenerationShaders(String cachePath) { if (nativeHandle != 0) nativeSetFrameGenerationShaders(nativeHandle, cachePath); } - public void setFrameGenerationMode(int multiplier, int targetRate, int flowScalePercent) { + public void setFrameGenerationMode(int multiplier, int targetRate, int flowScalePercent, + boolean flowScaleAuto) { int wantMultiplier = Math.max(2, multiplier); int wantTargetRate = Math.max(0, targetRate); - int wantFlowScale = flowScalePercent <= 0 ? 100 : flowScalePercent; + int wantFlowScale = flowScalePercent <= 0 ? 70 : flowScalePercent; if (wantMultiplier == frameGenerationMultiplier && wantTargetRate == frameGenerationTargetRate - && wantFlowScale == frameGenerationFlowScale) { + && wantFlowScale == frameGenerationFlowScale + && flowScaleAuto == frameGenerationFlowScaleAuto) { return; } frameGenerationMultiplier = wantMultiplier; frameGenerationTargetRate = wantTargetRate; frameGenerationFlowScale = wantFlowScale; + frameGenerationFlowScaleAuto = flowScaleAuto; if (nativeHandle != 0) { nativeSetFrameGenerationMode(nativeHandle, frameGenerationMultiplier, - frameGenerationTargetRate, frameGenerationFlowScale); + frameGenerationTargetRate, frameGenerationFlowScale, + frameGenerationFlowScaleAuto); } } @@ -1085,7 +1091,8 @@ private static native long nativeCreate(boolean enableValidationLayers, private static native void nativeSetSourceFrameCount(long handle, long count); private static native void nativeSetFrameGenerationRefreshRate(long handle, float hz); private static native void nativeSetFrameGenerationMode(long handle, int multiplier, - int targetRate, int flowScalePercent); + int targetRate, int flowScalePercent, + boolean flowScaleAuto); private static native long nativeGetGeneratedFrameCount(long handle); private static native long nativeGetPresentedFrameCount(long handle); } From 105144c68de03d097ea9101f546ea9b433b24f34 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Tue, 25 Aug 2026 08:06:13 -0400 Subject: [PATCH 26/35] Let shortcut frame generation settings override the container Both save paths wrote the extras with putExtra, so hasContainerOverride stayed false and use_container_defaults was set back to 1, discarding the edit. Disabling also nulled every key, which erased the shortcut's other values and made a per-shortcut "off" unexpressible. Use saveOverride and getSettingExtra like every other setting. --- .../ShortcutSettingsComposeDialog.kt | 38 ++++++++++++------ .../display/XServerDisplayActivity.java | 40 ++++++++++++------- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt index 747c32bc4..dc602545a 100644 --- a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt +++ b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt @@ -1320,19 +1320,31 @@ class ShortcutSettingsComposeDialog private constructor( shortcut.putExtra("sgsrSharpness", null) } - if (state.frameGenEnabled.value) { - shortcut.putExtra("frameGen", "1") - shortcut.putExtra("frameGenMultiplier", state.frameGenMultiplier.intValue.coerceIn(2, 4).toString()) - shortcut.putExtra("frameGenTargetRate", state.frameGenTargetRate.intValue.coerceAtLeast(0).toString()) - shortcut.putExtra("frameGenFlowScale", state.frameGenFlowScale.intValue.coerceIn(25, 100).toString()) - shortcut.putExtra("frameGenFlowScaleAuto", if (state.frameGenFlowScaleAuto.value) "1" else "0") - } else { - shortcut.putExtra("frameGen", null) - shortcut.putExtra("frameGenMultiplier", null) - shortcut.putExtra("frameGenTargetRate", null) - shortcut.putExtra("frameGenFlowScale", null) - shortcut.putExtra("frameGenFlowScaleAuto", null) - } + hasContainerOverride = hasContainerOverride or saveOverride( + "frameGen", + if (state.frameGenEnabled.value) "1" else "0", + container.getExtra("frameGen", "0"), + ) + hasContainerOverride = hasContainerOverride or saveOverride( + "frameGenMultiplier", + state.frameGenMultiplier.intValue.coerceIn(2, 4).toString(), + container.getExtra("frameGenMultiplier", "2"), + ) + hasContainerOverride = hasContainerOverride or saveOverride( + "frameGenTargetRate", + state.frameGenTargetRate.intValue.coerceAtLeast(0).toString(), + container.getExtra("frameGenTargetRate", "0"), + ) + hasContainerOverride = hasContainerOverride or saveOverride( + "frameGenFlowScale", + state.frameGenFlowScale.intValue.coerceIn(25, 100).toString(), + container.getExtra("frameGenFlowScale", "70"), + ) + hasContainerOverride = hasContainerOverride or saveOverride( + "frameGenFlowScaleAuto", + if (state.frameGenFlowScaleAuto.value) "1" else "0", + container.getExtra("frameGenFlowScaleAuto", "1"), + ) // saveOverride not putExtra: putExtra leaves hasContainerOverride false, so a reshade-only shortcut gets use_container_defaults=1 and reads back the container's extras run { diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index 51ed93350..1bdbf98f1 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -739,8 +739,21 @@ private String getShortcutSetting(String key, String containerValue) { private String getFrameGenSetting(String key, String containerValue) { if (shortcut == null) return containerValue; - String own = shortcut.getExtra(key, ""); - return own.isEmpty() ? containerValue : own; + return shortcut.getSettingExtra(key, containerValue); + } + + private String frameGenContainerValue(String key, String fallback) { + Container base = shortcut != null ? shortcut.container : container; + return base != null ? base.getExtra(key, fallback) : fallback; + } + + private boolean saveFrameGenOverride(String key, String value, String fallback) { + if (value.equals(frameGenContainerValue(key, fallback))) { + shortcut.putExtra(key, null); + return false; + } + shortcut.putExtra(key, value); + return true; } private void applyFrameGenerationSettings(VulkanRenderer renderer, Container container) { @@ -906,19 +919,16 @@ private void applyFrameGenerationLive() { private void saveFrameGenerationSettings() { if (shortcut != null) { - if (frameGenEnabled) { - shortcut.putExtra("frameGen", "1"); - shortcut.putExtra("frameGenMultiplier", String.valueOf(frameGenMultiplier)); - shortcut.putExtra("frameGenTargetRate", String.valueOf(frameGenTargetRate)); - shortcut.putExtra("frameGenFlowScale", String.valueOf(frameGenFlowScale)); - shortcut.putExtra("frameGenFlowScaleAuto", frameGenFlowScaleAuto ? "1" : "0"); - } else { - shortcut.putExtra("frameGen", null); - shortcut.putExtra("frameGenMultiplier", null); - shortcut.putExtra("frameGenTargetRate", null); - shortcut.putExtra("frameGenFlowScale", null); - shortcut.putExtra("frameGenFlowScaleAuto", null); - } + boolean overridden = saveFrameGenOverride("frameGen", frameGenEnabled ? "1" : "0", "0"); + overridden |= saveFrameGenOverride("frameGenMultiplier", + String.valueOf(frameGenMultiplier), "2"); + overridden |= saveFrameGenOverride("frameGenTargetRate", + String.valueOf(frameGenTargetRate), "0"); + overridden |= saveFrameGenOverride("frameGenFlowScale", + String.valueOf(frameGenFlowScale), "70"); + overridden |= saveFrameGenOverride("frameGenFlowScaleAuto", + frameGenFlowScaleAuto ? "1" : "0", "1"); + if (overridden) shortcut.putExtra("use_container_defaults", "0"); shortcut.saveData(); } else if (container != null) { container.putExtra("frameGen", frameGenEnabled ? "1" : "0"); From 267df3160cbc594ed8f0b2ec6fd75d0dafb6c200 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Tue, 25 Aug 2026 08:06:21 -0400 Subject: [PATCH 27/35] Download Lossless Scaling from the maintained depot App 993090 ships Lossless.dll in two depots: 993091, which gets the updates, and 993092, a stale twin. Both were selected, both wrote the same path, and the winner was whichever finished last, so 5.18 MiB from the stale depot usually landed instead of the current 7.17 MiB build. Drop the superseded depot when its replacement is present, and clear both from depot.config on an update check so the good one re-downloads. Nothing else could repair this: the depot bookkeeping was already current, so the update check correctly reported nothing to do. Pick the DLL by what it carries rather than by scan order, via a new PE probe that reports the shader variant, and log the source path and size on import so the wrong file is visible next time. The maintained build carries the precompiled SPIR-V, so frame generation runs native fp16 and skips DXBC translation entirely. --- app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c | 25 ++++++++ app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h | 2 + app/src/main/cpp/winlator/vk/lsfg/lsfg_jni.c | 9 +++ .../feature/library/LosslessAutoImport.kt | 14 +++-- .../stores/steam/service/SteamService.kt | 19 +++--- .../stores/steam/service/SteamServiceDepot.kt | 62 +++++++++++++++++++ .../runtime/display/lsfg/LosslessScaling.java | 37 +++++++++-- 7 files changed, 151 insertions(+), 17 deletions(-) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c index c30d04575..b00bd11ad 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.c @@ -572,6 +572,31 @@ LsfgStatus lsfg_validate_dll(const char* dll_path) { return status; } +LsfgVariant lsfg_dll_variant(const char* dll_path) { + if (!dll_path) return LSFG_VARIANT_NONE; + + PeImage image; + int fd = -1; + size_t mapped_size = 0; + if (!pe_open(dll_path, &image, &fd, &mapped_size)) return LSFG_VARIANT_NONE; + + ResourceTable* table = (ResourceTable*)calloc(1, sizeof(ResourceTable)); + if (!table) { + pe_close(&image, fd, mapped_size); + return LSFG_VARIANT_NONE; + } + + LsfgVariant variant = LSFG_VARIANT_NONE; + if (parse_resources(&image, table) == LSFG_OK) { + variant = select_variant(table, true); + if (variant == LSFG_VARIANT_NONE && has_base_chain(table)) variant = LSFG_VARIANT_DXBC; + } + + free(table); + pe_close(&image, fd, mapped_size); + return variant; +} + LsfgStatus lsfg_build_cache(const char* dll_path, const char* cache_path, bool prefer_fp16) { if (!dll_path || !cache_path) return LSFG_NOT_INSTALLED; diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h index 4dfc75d07..357d570eb 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_dll.h @@ -47,6 +47,8 @@ const uint32_t* lsfg_shader_ids(size_t* out_count); LsfgStatus lsfg_validate_dll(const char* dll_path); +LsfgVariant lsfg_dll_variant(const char* dll_path); + LsfgStatus lsfg_build_cache(const char* dll_path, const char* cache_path, bool prefer_fp16); LsfgStatus lsfg_load_modules(const char* cache_path, LsfgModuleSet* out_set); diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_jni.c b/app/src/main/cpp/winlator/vk/lsfg/lsfg_jni.c index 23119f549..868cf072a 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_jni.c +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_jni.c @@ -26,6 +26,15 @@ JNIEXPORT jint JNICALL LSFG_FN(nativeValidateDll)(JNIEnv* env, jclass clazz, jst return (jint)status; } +JNIEXPORT jint JNICALL LSFG_FN(nativeDllVariant)(JNIEnv* env, jclass clazz, jstring dllPath) { + (void)clazz; + char* path = copy_utf(env, dllPath); + if (!path) return (jint)LSFG_VARIANT_NONE; + const LsfgVariant variant = lsfg_dll_variant(path); + free(path); + return (jint)variant; +} + JNIEXPORT jint JNICALL LSFG_FN(nativeBuildCache)(JNIEnv* env, jclass clazz, jstring dllPath, jstring cachePath, jboolean preferFp16) { (void)clazz; diff --git a/app/src/main/feature/library/LosslessAutoImport.kt b/app/src/main/feature/library/LosslessAutoImport.kt index fdcf8bfd5..169c1f3b5 100644 --- a/app/src/main/feature/library/LosslessAutoImport.kt +++ b/app/src/main/feature/library/LosslessAutoImport.kt @@ -29,13 +29,19 @@ object LosslessAutoImport { } fun findDll(context: Context): File? { + val candidates = LinkedHashSet() for (dir in steamCandidateDirs()) { val dll = File(dir, DLL_NAME) - if (dll.isFile && dll.canRead()) return dll + if (dll.isFile && dll.canRead()) candidates += dll } - return runCatching { - LosslessScaling.findInContainers(ContainerManager(context).containers).firstOrNull() - }.getOrNull() + runCatching { LosslessScaling.findInContainers(ContainerManager(context).containers) } + .getOrDefault(emptyList()) + .forEach { candidates += it } + + return candidates.maxWithOrNull( + compareBy { LosslessScaling.variantRank(LosslessScaling.dllVariant(it)) } + .thenBy { it.length() }, + ) } fun sync(context: Context): Outcome { diff --git a/app/src/main/feature/stores/steam/service/SteamService.kt b/app/src/main/feature/stores/steam/service/SteamService.kt index 51d9325ac..5c93f754c 100644 --- a/app/src/main/feature/stores/steam/service/SteamService.kt +++ b/app/src/main/feature/stores/steam/service/SteamService.kt @@ -1250,12 +1250,15 @@ class SteamService : Service() { it.osArch == OSArch.Arch64 && (it.osList.contains(OS.windows) || (it.osList.isEmpty() || it.osList.contains(OS.none))) } - return appInfo.depots - .asSequence() - .filter { (depotId, depot) -> - return@filter isDepotEntitled(depotId, depot, entitledDepotIds) && - filterForDownloadableDepots(depot, has64Bit, preferredLanguage, ownedDlc) - }.associate { it.toPair() } + return dropSupersededDepots( + appId, + appInfo.depots + .asSequence() + .filter { (depotId, depot) -> + return@filter isDepotEntitled(depotId, depot, entitledDepotIds) && + filterForDownloadableDepots(depot, has64Bit, preferredLanguage, ownedDlc) + }.associate { it.toPair() }, + ) } /** Downloadable depots for an app, including all DLCs. */ @@ -1307,7 +1310,7 @@ class SteamService : Service() { } } - return map + return dropSupersededDepots(appId, map) } internal data class GroupedBaseAppDlcDepot( @@ -3826,6 +3829,8 @@ class SteamService : Service() { return@withContext SteamUpdateInfo(message = "No installed depots to update").logged() } + repairSupersededInstall(appId, appDirPath, selectedDepots.keys) + val installedManifestIds = readInstalledDepotManifestIds(appDirPath) val cachedManifestFiles: Set = File(appDirPath, ".DepotDownloader").list()?.toHashSet() ?: emptySet() diff --git a/app/src/main/feature/stores/steam/service/SteamServiceDepot.kt b/app/src/main/feature/stores/steam/service/SteamServiceDepot.kt index 1897f44e6..c922e782e 100644 --- a/app/src/main/feature/stores/steam/service/SteamServiceDepot.kt +++ b/app/src/main/feature/stores/steam/service/SteamServiceDepot.kt @@ -194,6 +194,68 @@ internal fun SteamService.Companion.isDepotEntitled( return depot.dlcAppId == INVALID_APP_ID } +private val SUPERSEDED_DEPOTS: Map> = + mapOf( + 993090 to mapOf(993092 to 993091), + ) + +internal fun SteamService.Companion.dropSupersededDepots( + appId: Int, + depots: Map, +): Map { + val rules = SUPERSEDED_DEPOTS[appId] ?: return depots + val dropped = depots.keys.filter { rules[it]?.let { preferred -> preferred in depots } == true } + if (dropped.isEmpty()) return depots + Timber.i("Dropping superseded depots $dropped for appId=$appId; preferred twin is present") + return depots.filterKeys { it !in dropped } +} + +internal fun SteamService.Companion.repairSupersededInstall( + appId: Int, + appDirPath: String, + selectedDepotIds: Set, +): Boolean { + val rules = SUPERSEDED_DEPOTS[appId] ?: return false + if (appDirPath.isBlank()) return false + + val installed = readInstalledDepotManifestIds(appDirPath) + val stale = rules.filterKeys { it in installed }.filterValues { it in selectedDepotIds } + if (stale.isEmpty()) return false + + val depotDir = File(appDirPath, ".DepotDownloader") + val configFile = File(depotDir, "depot.config") + val affected = stale.keys + stale.values + + val rewritten = + runCatching { + if (!configFile.isFile) return@runCatching false + val json = JSONObject(configFile.readText()) + val manifests = json.optJSONObject("installedManifestIDs") ?: return@runCatching false + affected.forEach { manifests.remove(it.toString()) } + configFile.writeText(json.toString()) + true + }.getOrElse { + Timber.w(it, "Could not rewrite depot.config repairing superseded depots for appId=$appId") + false + } + if (!rewritten) return false + + depotDir + .listFiles() + .orEmpty() + .forEach { file -> + if (!file.name.endsWith(".manifest")) return@forEach + val depotId = file.name.substringBefore('_').toIntOrNull() ?: return@forEach + if (depotId in affected) file.delete() + } + + Timber.i( + "Repaired superseded Steam install for appId=$appId: cleared depots $affected " + + "so ${stale.values} re-download and overwrite files the stale twin had won", + ) + return true +} + internal fun SteamService.Companion.getSelectedDownloadDepots( appId: Int, userSelectedDlcAppIds: Collection, diff --git a/app/src/main/runtime/display/lsfg/LosslessScaling.java b/app/src/main/runtime/display/lsfg/LosslessScaling.java index 4b1676118..da075bf03 100644 --- a/app/src/main/runtime/display/lsfg/LosslessScaling.java +++ b/app/src/main/runtime/display/lsfg/LosslessScaling.java @@ -100,6 +100,29 @@ public static int getVariant(Context context, boolean preferFp16) { return nativeCacheVariant(cache.getAbsolutePath()); } + public static int dllVariant(File dll) { + if (dll == null || !dll.isFile()) return VARIANT_NONE; + return nativeDllVariant(dll.getAbsolutePath()); + } + + public static int variantRank(int variant) { + switch (variant) { + case VARIANT_FP16: return 3; + case VARIANT_FP32: return 2; + case VARIANT_DXBC: return 1; + default: return 0; + } + } + + public static String variantName(int variant) { + switch (variant) { + case VARIANT_FP16: return "spirv-fp16"; + case VARIANT_FP32: return "spirv-fp32"; + case VARIANT_DXBC: return "dxbc-translated"; + default: return "none"; + } + } + public static int validate(File dll) { if (dll == null || !dll.isFile()) return STATUS_NOT_INSTALLED; return nativeValidateDll(dll.getAbsolutePath()); @@ -128,18 +151,18 @@ public static int installFrom(Context context, File dll) { deleteQuietly(fp32); return STATUS_CACHE_UNUSABLE; } - logInstalled(VARIANT_FP16, false); + logInstalled(dll, VARIANT_FP16, false); return STATUS_OK; } if (nativeBuildCache(source, fp16.getAbsolutePath(), true) != STATUS_OK || nativeCacheVariant(fp16.getAbsolutePath()) != VARIANT_FP16) { deleteQuietly(fp16); - logInstalled(VARIANT_FP32, false); + logInstalled(dll, nativeCacheVariant(fp32.getAbsolutePath()), false); return STATUS_OK; } - logInstalled(VARIANT_FP32, true); + logInstalled(dll, nativeCacheVariant(fp32.getAbsolutePath()), true); return STATUS_OK; } @@ -211,10 +234,10 @@ public static void invalidateGpuSupport() { gpuSupportedDriver = null; } - private static void logInstalled(int variant, boolean bothVariants) { + private static void logInstalled(File source, int variant, boolean bothVariants) { if (!ApplicationLogGate.isEnabled()) return; - Log.i(TAG, "Shader cache built: variant=" - + (variant == VARIANT_FP16 ? "fp16" : "fp32") + Log.i(TAG, "Shader cache built from " + source.getAbsolutePath() + + " (" + source.length() + " bytes): variant=" + variantName(variant) + (bothVariants ? " (fp16 alternate available)" : "")); } @@ -240,6 +263,8 @@ private static boolean deleteQuietly(File file) { private static native int nativeValidateDll(String dllPath); + private static native int nativeDllVariant(String dllPath); + private static native int nativeBuildCache(String dllPath, String cachePath, boolean preferFp16); From f3ec87d1a3e19eebd7d6c7e1a5200fe0331e416a Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Wed, 26 Aug 2026 09:16:33 -0400 Subject: [PATCH 28/35] Frame generation: quality presets, reliable pacing, automatic shader import Add a four-notch Quality Preset slider (Ultra Performance / Performance / Balanced / Quality) that drives flow scale directly, so there is no second stored key that can disagree with it. Legacy custom percentages snap to the nearest notch. Present in shortcut settings, container settings and the in-game drawer. Fold the "Match Motion To Game" toggle into the preset as an unconditional ceiling and remove the switch. It previously returned the guest/output ratio outright, which is 1.0 whenever a game renders at native resolution, so auto silently forced the most expensive setting and could raise a Performance preset back to full resolution. Flow scale is now min(guest ratio, preset), which can only ever lower cost and never buys less motion detail. Remove the pacer's self-throttles: the cost probe, the raise-regression watchdog and the headroom cliff. A requested multiplier now holds for the whole session instead of decaying from 4x to 2x under FIFO back-pressure. Import Lossless shaders without visiting container settings, and rebuild the cache on cold start so the in-game toggle can bootstrap itself. Track the active display mode rather than the maximum supported refresh rate, so a panel dropping to 60Hz is observed instead of being reported as 120. Persist frame generation settings from container settings, which previously loaded and saved none of the frameGen extras. --- app/src/main/app/PluviaApp.kt | 9 + .../main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp | 158 +----------------- .../main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp | 23 +-- .../main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp | 27 +-- app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h | 2 +- app/src/main/cpp/winlator/vk/vk_renderer.c | 10 +- app/src/main/cpp/winlator/vk/vk_state.h | 1 - app/src/main/feature/library/GameSettings.kt | 118 ++++++++++--- .../ContainerSettingsComposeDialog.kt | 17 ++ .../ShortcutSettingsComposeDialog.kt | 10 -- app/src/main/res/values-b+es+419/strings.xml | 1 - app/src/main/res/values-da/strings.xml | 1 - app/src/main/res/values-de/strings.xml | 1 - app/src/main/res/values-es/strings.xml | 1 - app/src/main/res/values-fi/strings.xml | 1 - app/src/main/res/values-fr/strings.xml | 1 - app/src/main/res/values-hi/strings.xml | 1 - app/src/main/res/values-it/strings.xml | 1 - app/src/main/res/values-ja/strings.xml | 1 - app/src/main/res/values-ko/strings.xml | 1 - app/src/main/res/values-no/strings.xml | 1 - app/src/main/res/values-pl/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values-pt/strings.xml | 1 - app/src/main/res/values-ro/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 1 - app/src/main/res/values-sv/strings.xml | 1 - app/src/main/res/values-th/strings.xml | 1 - app/src/main/res/values-tr/strings.xml | 1 - app/src/main/res/values-uk/strings.xml | 1 - app/src/main/res/values-zh-rCN/strings.xml | 1 - app/src/main/res/values-zh-rTW/strings.xml | 1 - app/src/main/res/values/strings.xml | 17 +- .../display/XServerDisplayActivity.java | 57 ++++--- .../display/XServerDrawerFrameGenPane.kt | 70 +++++--- .../main/runtime/display/XServerDrawerMenu.kt | 5 - .../display/renderer/VulkanRenderer.java | 18 +- .../main/shared/framegen/FrameGenPreset.kt | 57 +++++++ 38 files changed, 308 insertions(+), 313 deletions(-) create mode 100644 app/src/main/shared/framegen/FrameGenPreset.kt diff --git a/app/src/main/app/PluviaApp.kt b/app/src/main/app/PluviaApp.kt index 7db378cbe..d4e7cccef 100644 --- a/app/src/main/app/PluviaApp.kt +++ b/app/src/main/app/PluviaApp.kt @@ -257,6 +257,15 @@ class PluviaApp : Application() { com.winlator.cmod.runtime.system.LogManager .startAppLogging(this@PluviaApp) + runCatching { + val outcome = + com.winlator.cmod.feature.library.LosslessAutoImport + .sync(this@PluviaApp) + if (outcome.result != com.winlator.cmod.feature.library.LosslessAutoImport.RESULT_READY) { + Log.i("PluviaApp", "Lossless shader sync: result=${outcome.result}") + } + }.onFailure { Log.e("PluviaApp", "Lossless shader sync failed", it) } + if (steamLogsEnabled) { withContext(Dispatchers.Main.immediate) { if (timber.log.Timber.forest().none { it is timber.log.Timber.DebugTree }) { diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp index 3e879f14d..c067817b6 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.cpp @@ -17,33 +17,10 @@ constexpr float SOURCE_SMOOTHING = 0.15f; constexpr float SOURCE_STALE_SECONDS = 0.5f; constexpr float DISCONTINUITY_SECONDS = 0.25f; constexpr float HEADROOM_EPSILON = 0.02f; -constexpr float HEADROOM_HYSTERESIS = 0.20f; constexpr float CREDIT_EPSILON = 1.0e-4f; constexpr float SOURCE_ACCUM_FLOOR = 0.01f; constexpr uint32_t MIN_RATE_SAMPLES = 12; -constexpr float REGRESSION_RATIO = 1.20f; -constexpr float COST_PROBE_GAIN = 0.10f; -constexpr uint32_t MAX_COST_FAILURES = 4; - -constexpr auto RAISE_SETTLE_DURATION = std::chrono::milliseconds(600); -constexpr auto COST_PROBE_INTERVAL = std::chrono::seconds(15); -constexpr auto COST_PROBE_WINDOW = std::chrono::milliseconds(700); - -[[nodiscard]] Clock::duration CostBackoff(uint32_t failures) { - switch (failures) { - case 0: - case 1: - return std::chrono::seconds(8); - case 2: - return std::chrono::seconds(20); - case 3: - return std::chrono::seconds(45); - default: - return std::chrono::seconds(90); - } -} - } size_t LsfgPacer::MaxGenerations() const { @@ -97,119 +74,14 @@ bool LsfgPacer::RatesSettled() const { return source_samples >= MIN_RATE_SAMPLES && loop_samples >= MIN_RATE_SAMPLES; } -size_t LsfgPacer::HeadroomLimit(size_t current, bool allow_fractional) const { +size_t LsfgPacer::HeadroomLimit() const { if (config.refresh_rate <= 0.0f || source_interval <= 0.0f || source_samples < MIN_RATE_SAMPLES) { return LSFG_MAX_MULTIPLIER - 1; } - const float slots = config.refresh_rate * source_interval; - - if (allow_fractional) { - const float budget = std::ceil(slots - HEADROOM_EPSILON); - return budget < 2.0f ? 0 : static_cast(budget) - 1; - } - - float budget = slots + HEADROOM_EPSILON; - if (current > 0 && slots + HEADROOM_HYSTERESIS >= static_cast(current + 1)) { - budget = std::max(budget, static_cast(current + 1)); - } - if (budget < 2.0f) { - return 0; - } - return static_cast(std::floor(budget)) - 1; -} - -size_t LsfgPacer::CostLimit(Clock::time_point now, size_t current) { - if (cost_probe_until) { - const size_t probe_limit = cost_probe_from > 0 ? cost_probe_from - 1 : 0; - if (now < *cost_probe_until) { - cost_probe_active = true; - return probe_limit; - } - cost_probe_until.reset(); - cost_probe_active = true; - if (cost_probe_baseline > 0.0f && loop_interval > 0.0f && - loop_interval < cost_probe_baseline * (1.0f - COST_PROBE_GAIN)) { - cost_failures = std::min(cost_failures + 1, MAX_COST_FAILURES); - cost_ceiling = probe_limit; - cost_backoff_until = now + CostBackoff(cost_failures); - return cost_ceiling; - } - } else { - cost_probe_active = false; - } - - if (settle_until) { - if (now < *settle_until) { - return current; - } - settle_until.reset(); - - const bool source_regressed = - pre_raise_source_interval > 0.0f && source_interval > 0.0f && - source_interval > pre_raise_source_interval * REGRESSION_RATIO; - const bool loop_regressed = pre_raise_loop_interval > 0.0f && loop_interval > 0.0f && - loop_interval > pre_raise_loop_interval * REGRESSION_RATIO; - - if (source_regressed || loop_regressed) { - cost_failures = std::min(cost_failures + 1, MAX_COST_FAILURES); - cost_ceiling = pre_raise_limit; - cost_backoff_until = now + CostBackoff(cost_failures); - return cost_ceiling; - } - cost_failures = 0; - } - - if (cost_backoff_until) { - if (now < *cost_backoff_until) { - return cost_ceiling; - } - cost_backoff_until.reset(); - cost_ceiling = LSFG_MAX_MULTIPLIER - 1; - } - - if (current > 0 && !settle_until && RatesSettled()) { - if (!next_cost_probe) { - next_cost_probe = now + COST_PROBE_INTERVAL; - } else if (now >= *next_cost_probe) { - cost_probe_baseline = loop_interval; - cost_probe_from = current; - cost_probe_until = now + COST_PROBE_WINDOW; - next_cost_probe = now + COST_PROBE_INTERVAL; - cost_probe_active = true; - return current - 1; - } - } - - return cost_ceiling; -} - -void LsfgPacer::NoteLimitChange(Clock::time_point now, size_t previous_limit) { - if (cost_probe_active) { - settle_until.reset(); - return; - } - if (governed_limit > previous_limit) { - if (!RatesSettled()) { - settle_until.reset(); - return; - } - pre_raise_source_interval = source_interval; - pre_raise_loop_interval = loop_interval; - pre_raise_limit = previous_limit; - settle_until = now + RAISE_SETTLE_DURATION; - } else if (governed_limit < previous_limit) { - settle_until.reset(); - } -} - -size_t LsfgPacer::Govern(Clock::time_point now, size_t ceiling, bool allow_fractional) { - const size_t previous = governed_limit; - governed_limit = std::min({ceiling, HeadroomLimit(limit, allow_fractional), - CostLimit(now, governed_limit)}); - NoteLimitChange(now, previous); - return governed_limit; + const float budget = std::ceil(config.refresh_rate * source_interval - HEADROOM_EPSILON); + return budget < 2.0f ? 0 : static_cast(budget) - 1; } LsfgPlan LsfgPacer::Plan(size_t capacity, uint64_t source_frames) { @@ -243,11 +115,11 @@ LsfgPlan LsfgPacer::Plan(size_t capacity, uint64_t source_frames) { if (target_rate == 0.0f) { output_credit = 0.0f; - limit = Govern(now, ceiling, false); - return LsfgPlan{limit, limit > 0}; + limit = ceiling; + return LsfgPlan{limit, true}; } - const size_t allowed = Govern(now, ceiling, true); + const size_t allowed = std::min(ceiling, HeadroomLimit()); const float desired_outputs = loop_interval * target_rate; if (allowed == 0 || desired_outputs <= 1.0f) { output_credit = 0.0f; @@ -279,12 +151,7 @@ LsfgPacerStats LsfgPacer::Stats() const { stats.target_rate = static_cast(config.target_rate); stats.slots = config.refresh_rate * source_interval; stats.limit = limit; - stats.cost_ceiling = cost_ceiling; stats.rates_settled = RatesSettled(); - stats.settling = settle_until.has_value(); - stats.backing_off = cost_backoff_until.has_value(); - stats.probing = cost_probe_until.has_value(); - stats.cost_failures = cost_failures; stats.last_drawn = last_drawn; stats.last_elapsed = last_elapsed; stats.source_frames = last_source_frames; @@ -294,30 +161,17 @@ LsfgPacerStats LsfgPacer::Stats() const { void LsfgPacer::Reset() { last_frame.reset(); last_source_sample.reset(); - settle_until.reset(); - cost_backoff_until.reset(); - cost_probe_until.reset(); - next_cost_probe.reset(); last_source_frames = 0; source_interval = 0.0f; source_frame_accum = 0.0f; source_time_accum = 0.0f; loop_interval = 0.0f; - cost_probe_baseline = 0.0f; - pre_raise_source_interval = 0.0f; - pre_raise_loop_interval = 0.0f; source_samples = 0; loop_samples = 0; - cost_failures = 0; last_drawn = 0; last_elapsed = 0.0f; output_credit = 0.0f; limit = 0; - governed_limit = LSFG_MAX_MULTIPLIER - 1; - cost_probe_from = 0; - pre_raise_limit = 0; - cost_ceiling = LSFG_MAX_MULTIPLIER - 1; - cost_probe_active = false; } } diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp index b58273fac..5762ee47c 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_pacer.hpp @@ -30,12 +30,7 @@ struct LsfgPacerStats { float target_rate{}; float slots{}; size_t limit{}; - size_t cost_ceiling{}; bool rates_settled{}; - bool settling{}; - bool backing_off{}; - bool probing{}; - uint32_t cost_failures{}; uint64_t last_drawn{}; float last_elapsed{}; uint64_t source_frames{}; @@ -65,39 +60,23 @@ class LsfgPacer { void TrackSourceRate(Clock::time_point now, uint64_t source_frames); void TrackLoopRate(float interval_seconds); [[nodiscard]] bool RatesSettled() const; - [[nodiscard]] size_t HeadroomLimit(size_t current, bool allow_fractional) const; - [[nodiscard]] size_t CostLimit(Clock::time_point now, size_t current); - void NoteLimitChange(Clock::time_point now, size_t previous_limit); - [[nodiscard]] size_t Govern(Clock::time_point now, size_t ceiling, bool allow_fractional); + [[nodiscard]] size_t HeadroomLimit() const; LsfgPacerConfig config; std::optional last_frame; std::optional last_source_sample; - std::optional settle_until; - std::optional cost_backoff_until; - std::optional cost_probe_until; - std::optional next_cost_probe; uint64_t last_source_frames{}; float source_interval{}; float source_frame_accum{}; float source_time_accum{}; float loop_interval{}; - float cost_probe_baseline{}; - float pre_raise_source_interval{}; - float pre_raise_loop_interval{}; uint32_t source_samples{}; uint32_t loop_samples{}; - uint32_t cost_failures{}; uint64_t last_drawn{}; float last_elapsed{}; float output_credit{}; size_t limit{}; - size_t governed_limit{LSFG_MAX_MULTIPLIER - 1}; - size_t cost_probe_from{}; - size_t pre_raise_limit{}; - size_t cost_ceiling{LSFG_MAX_MULTIPLIER - 1}; - bool cost_probe_active{}; }; } diff --git a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp index 590778ca6..7e6bba35c 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.cpp @@ -97,7 +97,6 @@ struct VkrLsfg { VkFormat built_format{VK_FORMAT_UNDEFINED}; float built_flow_scale{}; float flow_scale{1.0f}; - bool flow_scale_auto{true}; uint64_t frame_count{}; uint64_t last_count{}; @@ -110,13 +109,13 @@ struct VkrLsfg { }; static float lsfg_effective_flow_scale(const VkrLsfg* lsfg, uint32_t width) { - if (!lsfg->flow_scale_auto) return lsfg->flow_scale; - if (width == 0 || lsfg->peak_guest_extent.width == 0) return LSFG_FLOW_SCALE_MAX; + if (width == 0 || lsfg->peak_guest_extent.width == 0) return lsfg->flow_scale; const float ratio = static_cast(lsfg->peak_guest_extent.width) / static_cast(width); const float stepped = std::ceil(ratio * LSFG_FLOW_SCALE_STEPS) / LSFG_FLOW_SCALE_STEPS; - return std::clamp(stepped, LSFG_FLOW_SCALE_MIN, LSFG_FLOW_SCALE_MAX); + return std::clamp(std::min(stepped, lsfg->flow_scale), LSFG_FLOW_SCALE_MIN, + LSFG_FLOW_SCALE_MAX); } VkrLsfg* vkr_lsfg_create(VkDevice device, VkPhysicalDevice physical_device, @@ -145,7 +144,7 @@ void vkr_lsfg_destroy(VkrLsfg* lsfg) { } void vkr_lsfg_configure(VkrLsfg* lsfg, uint32_t multiplier, uint32_t target_rate, - float flow_scale, bool flow_scale_auto, float refresh_rate) { + float flow_scale, float refresh_rate) { if (!lsfg) return; lsfg::LsfgPacerConfig config = lsfg->pacer.Config(); @@ -154,7 +153,6 @@ void vkr_lsfg_configure(VkrLsfg* lsfg, uint32_t multiplier, uint32_t target_rate config.refresh_rate = refresh_rate; lsfg->pacer.SetConfig(config); lsfg->flow_scale = std::clamp(flow_scale, LSFG_FLOW_SCALE_MIN, LSFG_FLOW_SCALE_MAX); - lsfg->flow_scale_auto = flow_scale_auto; } void vkr_lsfg_set_guest_extent(VkrLsfg* lsfg, uint32_t width, uint32_t height) { @@ -209,10 +207,10 @@ bool vkr_lsfg_prepare(VkrLsfg* lsfg, uint32_t width, uint32_t height, VkFormat f lsfg->warm = false; lsfg->generated = false; lsfg->pacer.Reset(); - LSFG_LOGI("chain built at %ux%u, flow %ux%u scale %.2f (%s, guest %ux%u)", width, height, - (unsigned)(width * lsfg->built_flow_scale), + LSFG_LOGI("chain built at %ux%u, flow %ux%u scale %.2f (preset %.2f, guest %ux%u)", width, + height, (unsigned)(width * lsfg->built_flow_scale), (unsigned)(height * lsfg->built_flow_scale), (double)lsfg->built_flow_scale, - lsfg->flow_scale_auto ? "auto" : "manual", lsfg->peak_guest_extent.width, + (double)lsfg->flow_scale, lsfg->peak_guest_extent.width, lsfg->peak_guest_extent.height); return true; } @@ -230,14 +228,17 @@ uint32_t vkr_lsfg_plan(VkrLsfg* lsfg, uint32_t capacity, uint64_t source_frames) if ((lsfg->plan_calls++ % LSFG_TELEMETRY_INTERVAL) == 0) { const lsfg::LsfgPacerStats stats = lsfg->pacer.Stats(); + const float wanted = + stats.source_rate * static_cast(lsfg->plan.generations + 1); LSFG_LOGI("pace gen=%zu max=%zu cap=%u guest=%.1f loop=%.1f refresh=%.1f target=%.0f " - "slots=%.2f drawn=%llu cost=%zu fails=%u%s%s%s%s", + "slots=%.2f drawn=%llu needs=%.1fHz%s%s", lsfg->plan.generations, lsfg->pacer.MaxGenerations(), capacity, (double)stats.source_rate, (double)stats.loop_rate, (double)stats.refresh_rate, (double)stats.target_rate, (double)stats.slots, - (unsigned long long)stats.last_drawn, stats.cost_ceiling, stats.cost_failures, - stats.probing ? " probing" : "", stats.settling ? " settling" : "", - stats.backing_off ? " backoff" : "", + (unsigned long long)stats.last_drawn, (double)wanted, + (stats.refresh_rate > 0.0f && wanted > stats.refresh_rate + 1.0f) + ? " PANEL-BOUND" + : "", stats.rates_settled ? (lsfg->warm ? "" : " cold") : " sampling"); } diff --git a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h index 5900c6ecf..0c51abb73 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h +++ b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h @@ -18,7 +18,7 @@ VkrLsfg* vkr_lsfg_create(VkDevice device, VkPhysicalDevice physical_device, void vkr_lsfg_destroy(VkrLsfg* lsfg); void vkr_lsfg_configure(VkrLsfg* lsfg, uint32_t multiplier, uint32_t target_rate, - float flow_scale, bool flow_scale_auto, float refresh_rate); + float flow_scale, float refresh_rate); void vkr_lsfg_set_refresh_rate(VkrLsfg* lsfg, float refresh_rate); diff --git a/app/src/main/cpp/winlator/vk/vk_renderer.c b/app/src/main/cpp/winlator/vk/vk_renderer.c index ad2ef28a8..501ec0390 100644 --- a/app/src/main/cpp/winlator/vk/vk_renderer.c +++ b/app/src/main/cpp/winlator/vk/vk_renderer.c @@ -1871,7 +1871,7 @@ static void create_lsfg(VkRenderer* r) { vkr_lsfg_configure(r->lsfg, r->framegen_multiplier ? r->framegen_multiplier : 2u, r->framegen_target_rate, r->framegen_flow_scale > 0.0f ? r->framegen_flow_scale : 0.7f, - r->framegen_flow_scale_auto, r->framegen_refresh_rate); + r->framegen_refresh_rate); } static uint32_t framegen_extra_images(const VkRenderer* r) { @@ -3627,8 +3627,8 @@ JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationRefreshRate)(JNIEnv* env, JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationMode)(JNIEnv* env, jclass clazz, jlong handle, jint multiplier, - jint targetRate, jint flowScalePct, - jboolean flowScaleAuto) { + jint targetRate, + jint flowScalePct) { (void)env; (void)clazz; VkRenderer* r = (VkRenderer*)(intptr_t)handle; if (!r) return; @@ -3638,11 +3638,9 @@ JNIEXPORT void JNICALL JNI_FN(nativeSetFrameGenerationMode)(JNIEnv* env, jclass r->framegen_multiplier = multiplier < 2 ? 2u : (uint32_t)multiplier; r->framegen_target_rate = targetRate < 0 ? 0u : (uint32_t)targetRate; r->framegen_flow_scale = flowScalePct <= 0 ? 0.7f : (float)flowScalePct / 100.0f; - r->framegen_flow_scale_auto = flowScaleAuto == JNI_TRUE; if (r->lsfg) { vkr_lsfg_configure(r->lsfg, r->framegen_multiplier, r->framegen_target_rate, - r->framegen_flow_scale, r->framegen_flow_scale_auto, - r->framegen_refresh_rate); + r->framegen_flow_scale, r->framegen_refresh_rate); } if (framegen_extra_images(r) != previous_images) { wait_inflight_frames(r); diff --git a/app/src/main/cpp/winlator/vk/vk_state.h b/app/src/main/cpp/winlator/vk/vk_state.h index 13792983c..2155d69a1 100644 --- a/app/src/main/cpp/winlator/vk/vk_state.h +++ b/app/src/main/cpp/winlator/vk/vk_state.h @@ -423,7 +423,6 @@ typedef struct VkRenderer { uint32_t framegen_multiplier; uint32_t framegen_target_rate; float framegen_flow_scale; - bool framegen_flow_scale_auto; float framegen_refresh_rate; int32_t framegen_refresh_mhz; uint64_t framegen_source_frames; diff --git a/app/src/main/feature/library/GameSettings.kt b/app/src/main/feature/library/GameSettings.kt index f0e8db036..1be6aaa1e 100644 --- a/app/src/main/feature/library/GameSettings.kt +++ b/app/src/main/feature/library/GameSettings.kt @@ -132,6 +132,7 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset @@ -145,6 +146,7 @@ import com.winlator.cmod.runtime.reshade.ReshadeCatalogEntry import com.winlator.cmod.runtime.reshade.ReshadeDownloader import com.winlator.cmod.runtime.reshade.ReshadeLoadout import com.winlator.cmod.runtime.reshade.ReshadeManager +import com.winlator.cmod.shared.framegen.FrameGenPreset import com.winlator.cmod.shared.theme.GameSettingsStyle import com.winlator.cmod.runtime.wine.WineThemeManager import com.winlator.cmod.runtime.display.lsfg.LosslessScaling @@ -427,7 +429,6 @@ class GameSettingsStateHolder { val frameGenMultiplier = mutableIntStateOf(2) val frameGenTargetRate = mutableIntStateOf(0) val frameGenFlowScale = mutableIntStateOf(70) - val frameGenFlowScaleAuto = mutableStateOf(true) val frameGenShaderState = mutableIntStateOf(FRAMEGEN_SHADERS_CHECKING) val frameGenSourceName = mutableStateOf("") @@ -1885,28 +1886,12 @@ private fun FrameGenerationCard(state: GameSettingsStateHolder) { Spacer(Modifier.height(SettingItemGap)) - SettingSwitch( - label = stringResource(R.string.session_drawer_frame_generation_flow_scale_auto), - checked = state.frameGenFlowScaleAuto.value, - onCheckedChange = { state.frameGenFlowScaleAuto.value = it }, + FrameGenPresetSlider( + selected = FrameGenPreset.fromFlowScale(state.frameGenFlowScale.intValue), + onSelected = { state.frameGenFlowScale.intValue = it.flowScale }, ) - Text( - text = stringResource(R.string.session_drawer_frame_generation_flow_scale_auto_note), - color = TextSecondary, - fontSize = SettingLabelSize, - lineHeight = SettingLabelSize * 1.4f, - ) - - if (!state.frameGenFlowScaleAuto.value) { - SettingSlider( - label = stringResource(R.string.session_drawer_frame_generation_flow_scale), - value = state.frameGenFlowScale.intValue, - range = 25..100, - steps = 14, - onValueChange = { state.frameGenFlowScale.intValue = it }, - ) - } + Spacer(Modifier.height(SettingItemGap)) Text( text = stringResource(R.string.session_drawer_frame_generation_note), @@ -5899,6 +5884,97 @@ private fun SettingSwitch( } } +@Composable +@OptIn(ExperimentalMaterial3Api::class) +private fun FrameGenPresetSlider( + selected: FrameGenPreset, + onSelected: (FrameGenPreset) -> Unit, +) { + val presets = FrameGenPreset.values() + val index = presets.indexOf(selected).coerceAtLeast(0) + + Column(modifier = Modifier.fillMaxWidth()) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + stringResource(R.string.frame_generation_preset), + color = TextSecondary, + fontSize = SettingLabelSize, + fontWeight = FontWeight.Medium, + letterSpacing = 0.3.sp, + ) + Spacer(Modifier.weight(1f)) + Box( + modifier = Modifier + .clip(RoundedCornerShape(6.dp)) + .background(AccentBlue.copy(alpha = 0.1f)) + .padding(horizontal = 7.dp, vertical = 2.dp) + ) { + Text( + stringResource(selected.labelRes), + color = AccentBlue, + fontSize = SettingLabelSize, + fontWeight = FontWeight.SemiBold, + ) + } + } + + Spacer(Modifier.height(SettingTightGap)) + + Slider( + value = index.toFloat(), + onValueChange = { onSelected(FrameGenPreset.atIndex(it.roundToInt())) }, + valueRange = 0f..(presets.size - 1).toFloat(), + steps = presets.size - 2, + modifier = Modifier + .fillMaxWidth() + .height(SettingSliderHeight) + .controllerSliderEscape() + .paneNavItem( + cornerRadius = 8.dp, + onAdjust = { d -> onSelected(FrameGenPreset.atIndex(index + d)) }, + highlightColor = NavHighlight, + ), + colors = settingSliderColors(), + track = { SettingSliderTrack(it) }, + thumb = { + Box( + modifier = Modifier + .size(SettingSliderThumbSize) + .clip(RoundedCornerShape(50)) + .background(AccentBlue) + .border(2.dp, CardSurface, RoundedCornerShape(50)) + ) + } + ) + + Row(modifier = Modifier.fillMaxWidth()) { + presets.forEachIndexed { i, preset -> + Text( + text = stringResource(preset.shortLabelRes), + color = if (i == index) AccentBlue else TextSecondary, + fontSize = SettingLabelSize * 0.9f, + fontWeight = if (i == index) FontWeight.SemiBold else FontWeight.Normal, + textAlign = when (i) { + 0 -> TextAlign.Start + presets.size - 1 -> TextAlign.End + else -> TextAlign.Center + }, + modifier = Modifier.weight(1f), + ) + } + } + + Spacer(Modifier.height(SettingTightGap)) + + Text( + text = stringResource(selected.descriptionRes), + color = TextSecondary, + fontSize = SettingLabelSize, + lineHeight = SettingLabelSize * 1.4f, + ) + } +} + @Composable @OptIn(ExperimentalMaterial3Api::class) private fun SettingSlider( diff --git a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt index 06f304d64..5e77140f2 100644 --- a/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt +++ b/app/src/main/feature/settings/containers/ContainerSettingsComposeDialog.kt @@ -560,6 +560,14 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( state.surfaceEffectEntries.value = surfaceEffectArr state.selectedSurfaceEffect.intValue = if (c?.getExtra("swapRB", "0") == "1") 1 else 0 + state.frameGenEnabled.value = c?.getExtra("frameGen", "0") == "1" + state.frameGenMultiplier.intValue = + c?.getExtra("frameGenMultiplier", "2")?.toIntOrNull()?.coerceIn(2, 4) ?: 2 + state.frameGenTargetRate.intValue = + c?.getExtra("frameGenTargetRate", "0")?.toIntOrNull()?.coerceAtLeast(0) ?: 0 + state.frameGenFlowScale.intValue = + c?.getExtra("frameGenFlowScale", "70")?.toIntOrNull()?.coerceIn(25, 100) ?: 70 + // init() migrates a legacy single reshadeEffect / flat reshadeParams into the loadout model val reshadeEffects = com.winlator.cmod.runtime.reshade.ReshadeManager.scanEffects(context) state.reshadeEffects.value = reshadeEffects @@ -841,6 +849,7 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( c.setDXWrapperConfig(dxwrapperConfig) c.putExtra("swapRB", if (state.selectedSurfaceEffect.intValue == 1) "1" else "0") c.putExtra("refreshRate", getRefreshRateFromState()) + writeFrameGenExtras(c) run { // reshadeEffect stays coherent (= first effect) for legacy readers; all null when empty val loadoutJson = state.reshadeLoadout.loadoutJsonOrNull() @@ -931,6 +940,7 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( if (state.selectedSurfaceEffect.intValue == 1) "1" else "0" ) getRefreshRateFromState()?.let { newContainer.putExtra("refreshRate", it) } + writeFrameGenExtras(newContainer) newContainer.setZinkMode(if (state.selectedZinkMode.intValue == 1) "windows" else "unix") newContainer.saveData() saveMouseWarpOverride(newContainer) @@ -971,6 +981,13 @@ class ContainerSettingsComposeDialog @JvmOverloads constructor( ?: WineInfo.MAIN_WINE_VERSION.identifier() } + private fun writeFrameGenExtras(c: Container) { + c.putExtra("frameGen", if (state.frameGenEnabled.value) "1" else "0") + c.putExtra("frameGenMultiplier", state.frameGenMultiplier.intValue.coerceIn(2, 4).toString()) + c.putExtra("frameGenTargetRate", state.frameGenTargetRate.intValue.coerceAtLeast(0).toString()) + c.putExtra("frameGenFlowScale", state.frameGenFlowScale.intValue.coerceIn(25, 100).toString()) + } + private fun saveMouseWarpOverride(c: Container) { val rootDir = c.getRootDir() ?: return val userRegFile = File(rootDir, ".wine/user.reg") diff --git a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt index dc602545a..7cd29d128 100644 --- a/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt +++ b/app/src/main/feature/shortcuts/ShortcutSettingsComposeDialog.kt @@ -535,11 +535,6 @@ class ShortcutSettingsComposeDialog private constructor( .toIntOrNull() ?.coerceIn(25, 100) ?: 70 - state.frameGenFlowScaleAuto.value = - getShortcutSetting( - "frameGenFlowScaleAuto", - container.getExtra("frameGenFlowScaleAuto", "1"), - ) != "0" // shortcut override else container value; legacy single reshadeEffect/flat params migrated in parse val reshadeEffects = com.winlator.cmod.runtime.reshade.ReshadeManager.scanEffects(context) @@ -1340,11 +1335,6 @@ class ShortcutSettingsComposeDialog private constructor( state.frameGenFlowScale.intValue.coerceIn(25, 100).toString(), container.getExtra("frameGenFlowScale", "70"), ) - hasContainerOverride = hasContainerOverride or saveOverride( - "frameGenFlowScaleAuto", - if (state.frameGenFlowScaleAuto.value) "1" else "0", - container.getExtra("frameGenFlowScaleAuto", "1"), - ) // saveOverride not putExtra: putExtra leaves hasContainerOverride false, so a reshade-only shortcut gets use_container_defaults=1 and reads back the container's extras run { diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 9ac5fc7f8..72bcf2e5e 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -2501,7 +2501,6 @@ Ruta instalada: Objetivo adaptativo Fijo %1$d fps - Escala de flujo Interpola entre fotogramas renderizados con Lossless Scaling. Movimiento más fluido, pero un fotograma extra de latencia de entrada. Sube hacia el objetivo solo mientras ayude de forma medible, en lugar de mantener un multiplicador fijo. Instala Lossless Scaling en los ajustes del contenedor para usar la generación de fotogramas. diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 858c58aac..69459bc2a 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -2503,7 +2503,6 @@ Installeret sti: Adaptivt mål Fast %1$d fps - Flow-skala Interpolerer mellem gengivne billeder med Lossless Scaling. Blødere bevægelse, men ét ekstra billede med inputforsinkelse. Stiger mod målet, kun mens det målbart hjælper, i stedet for at fastholde en fast multiplikator. Installér Lossless Scaling i containerindstillingerne for at bruge billedgenerering. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 470e76ba9..37f7c0fb6 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -2503,7 +2503,6 @@ Installierter Pfad: Adaptives Ziel Fest %1$d fps - Flow-Skalierung Interpoliert zwischen gerenderten Frames mit Lossless Scaling. Flüssigere Bewegung, aber ein zusätzlicher Frame Eingabeverzögerung. Steigt nur dann in Richtung Ziel, wenn es messbar hilft, statt einen festen Multiplikator zu halten. Installiere Lossless Scaling in den Container-Einstellungen, um Frame-Generierung zu nutzen. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 7dc25f828..22e0de191 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -2502,7 +2502,6 @@ Ruta instalada: Objetivo adaptativo Fijo %1$d fps - Escala de flujo Interpola entre fotogramas renderizados con Lossless Scaling. Movimiento más fluido, pero un fotograma extra de latencia de entrada. Sube hacia el objetivo solo mientras ayude de forma medible, en lugar de mantener un multiplicador fijo. Instala Lossless Scaling en los ajustes del contenedor para usar la generación de fotogramas. diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 50351bfa8..6a08a66ba 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -2501,7 +2501,6 @@ Asennuspolku: Mukautuva tavoite Kiinteä %1$d fps - Virtauksen skaala Interpoloi renderöityjen ruutujen välillä Lossless Scalingilla. Sulavampi liike, mutta yhden ruudun lisäviive syötteessä. Nousee kohti tavoitetta vain, kun siitä on mitattavaa hyötyä, sen sijaan että pitäisi kiinteän kertoimen. Asenna Lossless Scaling säilön asetuksista käyttääksesi ruudun generointia. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f5d098bbb..ab9935e22 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -2502,7 +2502,6 @@ Chemin installé : Cible adaptative Fixe %1$d fps - Échelle de flux Interpole entre les images rendues avec Lossless Scaling. Mouvement plus fluide, mais une image supplémentaire de latence d\'entrée. Monte vers la cible uniquement tant que cela aide de façon mesurable, au lieu de conserver un multiplicateur fixe. Installez Lossless Scaling dans les paramètres du conteneur pour utiliser la génération d\'images. diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 81a6842ac..8d1aef7d3 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -2438,7 +2438,6 @@ अनुकूली लक्ष्य स्थिर %1$d fps - फ़्लो स्केल Lossless Scaling से रेंडर किए गए फ़्रेमों के बीच इंटरपोलेट करता है। गति अधिक सहज, लेकिन इनपुट में एक अतिरिक्त फ़्रेम की देरी। स्थिर गुणक बनाए रखने के बजाय, केवल तभी लक्ष्य की ओर बढ़ता है जब इससे मापने योग्य लाभ हो। फ़्रेम जनरेशन उपयोग करने के लिए कंटेनर सेटिंग्स में Lossless Scaling इंस्टॉल करें। diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 17a24cda1..01065f1f0 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -2502,7 +2502,6 @@ Percorso installato: Obiettivo adattivo Fisso %1$d fps - Scala del flusso Interpola tra i fotogrammi renderizzati con Lossless Scaling. Movimento più fluido, ma un fotogramma in più di latenza di input. Sale verso l\'obiettivo solo finché aiuta in modo misurabile, invece di mantenere un moltiplicatore fisso. Installa Lossless Scaling nelle impostazioni del contenitore per usare la generazione fotogrammi. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index a2226fbac..68adaadde 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -2501,7 +2501,6 @@ アダプティブ目標 固定 %1$d fps - フロースケール Lossless Scaling でレンダリング済みフレーム間を補間します。動きは滑らかになりますが、入力遅延が 1 フレーム増えます。 固定倍率を保つのではなく、効果が測定できる間だけ目標に向けて上げていきます。 フレーム生成を使用するには、コンテナ設定で Lossless Scaling をインストールしてください。 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index f794e41c5..2f8ffdfcc 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -2503,7 +2503,6 @@ 적응형 목표 고정 %1$d fps - 플로우 스케일 Lossless Scaling으로 렌더링된 프레임 사이를 보간합니다. 움직임은 부드러워지지만 입력 지연이 한 프레임 늘어납니다. 고정 배수를 유지하는 대신, 측정 가능한 이득이 있을 때만 목표를 향해 올립니다. 프레임 생성을 사용하려면 컨테이너 설정에서 Lossless Scaling을 설치하세요. diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index c42975b9f..f45cca80b 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -2501,7 +2501,6 @@ Installert bane: Adaptivt mål Fast %1$d fps - Flytskala Interpolerer mellom gjengitte bilder med Lossless Scaling. Jevnere bevegelse, men ett ekstra bilde med inndataforsinkelse. Stiger mot målet bare så lenge det hjelper målbart, i stedet for å holde en fast multiplikator. Installer Lossless Scaling i containerinnstillingene for å bruke bildegenerering. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index c0ea78041..9450db0bc 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -2508,7 +2508,6 @@ Zainstalowana ścieżka: Cel adaptacyjny Stały %1$d fps - Skala przepływu Interpoluje między wyrenderowanymi klatkami przy użyciu Lossless Scaling. Płynniejszy ruch, ale jedna dodatkowa klatka opóźnienia sterowania. Zwiększa się w kierunku celu tylko wtedy, gdy przynosi to mierzalną korzyść, zamiast utrzymywać stały mnożnik. Zainstaluj Lossless Scaling w ustawieniach kontenera, aby korzystać z generowania klatek. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 07a4eb39e..e42b0460f 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -2502,7 +2502,6 @@ Caminho instalado: Alvo adaptativo Fixo %1$d fps - Escala de fluxo Interpola entre quadros renderizados com o Lossless Scaling. Movimento mais suave, mas um quadro extra de latência de entrada. Sobe em direção ao alvo apenas enquanto ajudar de forma mensurável, em vez de manter um multiplicador fixo. Instale o Lossless Scaling nas configurações do contêiner para usar a geração de quadros. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index a6fd5ddc6..e768ad75d 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -2501,7 +2501,6 @@ Caminho instalado: Alvo adaptativo Fixo %1$d fps - Escala de fluxo Interpola entre fotogramas renderizados com o Lossless Scaling. Movimento mais suave, mas um fotograma extra de latência de entrada. Sobe em direção ao alvo apenas enquanto ajudar de forma mensurável, em vez de manter um multiplicador fixo. Instale o Lossless Scaling nas definições do contentor para usar a geração de fotogramas. diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 80a8b4d4c..fee8beb66 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -2501,7 +2501,6 @@ Cale instalata: Țintă adaptivă Fix %1$d fps - Scară a fluxului Interpolează între cadrele randate cu Lossless Scaling. Mișcare mai fluidă, dar un cadru suplimentar de latență la intrare. Urcă spre țintă doar cât timp ajută în mod măsurabil, în loc să păstreze un multiplicator fix. Instalează Lossless Scaling din setările containerului pentru a folosi generarea de cadre. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1177a6ae9..0f6e9d725 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -2408,7 +2408,6 @@ Адаптивная цель Фиксированный %1$d fps - Масштаб потока Интерполирует между отрисованными кадрами с помощью Lossless Scaling. Движение плавнее, но задержка ввода увеличивается на один кадр. Повышается к цели только пока это даёт измеримый выигрыш, вместо удержания фиксированного множителя. Установите Lossless Scaling в настройках контейнера, чтобы использовать генерацию кадров. diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 585b40c9d..44cb4e995 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -2501,7 +2501,6 @@ Installerad sökväg: Adaptivt mål Fast %1$d fps - Flödesskala Interpolerar mellan renderade bildrutor med Lossless Scaling. Mjukare rörelse, men en extra bildruta med inmatningsfördröjning. Stiger mot målet endast så länge det hjälper mätbart, i stället för att hålla en fast multiplikator. Installera Lossless Scaling i containerinställningarna för att använda bildgenerering. diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 2a1cf7e65..b8e863c2b 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -2501,7 +2501,6 @@ เป้าหมายแบบปรับอัตโนมัติ คงที่ %1$d fps - สเกลการไหล แทรกเฟรมระหว่างเฟรมที่เรนเดอร์ด้วย Lossless Scaling การเคลื่อนไหวลื่นขึ้น แต่มีความหน่วงของอินพุตเพิ่มขึ้นหนึ่งเฟรม จะไต่ขึ้นสู่เป้าหมายเฉพาะเมื่อวัดผลได้ว่าช่วยจริง แทนที่จะคงตัวคูณไว้คงที่ ติดตั้ง Lossless Scaling ในการตั้งค่าคอนเทนเนอร์เพื่อใช้การสร้างเฟรม diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index e2e226746..da5cbad68 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -2501,7 +2501,6 @@ Yüklü konum: Uyarlanabilir hedef Sabit %1$d fps - Akış ölçeği Lossless Scaling ile işlenmiş kareler arasında ara kare üretir. Daha akıcı hareket, ancak bir kare fazladan giriş gecikmesi. Sabit bir çarpanı korumak yerine, yalnızca ölçülebilir fayda sağladığı sürece hedefe doğru yükselir. Kare üretimini kullanmak için kapsayıcı ayarlarından Lossless Scaling\'i yükleyin. diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 14d190a6f..b355258d7 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -2508,7 +2508,6 @@ Адаптивна ціль Фіксований %1$d fps - Масштаб потоку Інтерполює між відрендереними кадрами за допомогою Lossless Scaling. Рух плавніший, але затримка вводу зростає на один кадр. Підвищується до цілі лише доки це дає вимірний виграш, замість утримання фіксованого множника. Установіть Lossless Scaling у налаштуваннях контейнера, щоб використовувати генерацію кадрів. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 14fe743fc..ec8156a5c 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -2502,7 +2502,6 @@ 自适应目标 固定 %1$d fps - 流场缩放 使用 Lossless Scaling 在已渲染的帧之间插帧。画面更流畅,但输入延迟会增加一帧。 仅在能带来可测量的提升时才向目标提升,而不是保持固定倍数。 请在容器设置中安装 Lossless Scaling 以使用帧生成。 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 4cdcef5cd..3788d2236 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -2501,7 +2501,6 @@ 自適應目標 固定 %1$d fps - 流場縮放 使用 Lossless Scaling 在已算繪的影格之間插補。畫面更流暢,但輸入延遲會增加一個影格。 僅在能帶來可測量的提升時才朝目標提升,而不是維持固定倍數。 請在容器設定中安裝 Lossless Scaling 以使用影格生成。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 19263bcc1..7e4195d92 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -772,9 +772,20 @@ E.g. META for META key, \n Adaptive Target Fixed %1$d fps - Flow Scale - Match Motion To Game - Estimates motion at the resolution the game actually renders instead of the upscaled output. Costs nothing in accuracy, since upscaling adds no motion detail. + Quality Preset + Ultra Performance + Performance + Balanced + Quality + Ultra + Perf + Balanced + Quality + Motion estimated at 40% resolution. Around 40% of Quality\'s GPU cost. Fine detail softens during fast motion. + Motion estimated at 50% resolution. Around 46% of Quality\'s GPU cost, with almost no visible loss. + Motion estimated at 70% resolution. Around 61% of Quality\'s GPU cost. Recommended for handhelds. + Motion estimated at full resolution. Highest cost; use it only when the game has GPU headroom to spare. + Sets how much GPU work motion estimation does. It never changes the multiplier — 2x stays 2x, 4x stays 4x. Interpolates between rendered frames with Lossless Scaling. Smoother motion, but one extra frame of input latency. Climbs toward the target only while it measurably helps, instead of holding a fixed multiplier. Install Lossless Scaling in container settings to use frame generation. diff --git a/app/src/main/runtime/display/XServerDisplayActivity.java b/app/src/main/runtime/display/XServerDisplayActivity.java index 1bdbf98f1..fdeca3414 100644 --- a/app/src/main/runtime/display/XServerDisplayActivity.java +++ b/app/src/main/runtime/display/XServerDisplayActivity.java @@ -451,8 +451,8 @@ private boolean isAnyControllerConnected() { private int frameGenMultiplier = 2; private int frameGenTargetRate = 0; private int frameGenFlowScale = 70; - private boolean frameGenFlowScaleAuto = true; private String frameGenCachePath = null; + private float frameGenRefreshRate = 0f; private boolean sgsrEnabled = false; private boolean sgsrRuntimeEnabled = false; private int sgsrUpscaleMode = 1; @@ -763,7 +763,6 @@ private void applyFrameGenerationSettings(VulkanRenderer renderer, Container con String containerMultiplier = container != null ? container.getExtra("frameGenMultiplier", "2") : "2"; String containerTargetRate = container != null ? container.getExtra("frameGenTargetRate", "0") : "0"; String containerFlowScale = container != null ? container.getExtra("frameGenFlowScale", "70") : "70"; - String containerFlowScaleAuto = container != null ? container.getExtra("frameGenFlowScaleAuto", "1") : "1"; frameGenEnabled = "1".equals(getFrameGenSetting("frameGen", containerValue)); frameGenMultiplier = clampFrameGenMultiplier( @@ -772,10 +771,9 @@ private void applyFrameGenerationSettings(VulkanRenderer renderer, Container con parseSettingInt(getFrameGenSetting("frameGenTargetRate", containerTargetRate), 0)); frameGenFlowScale = clampFrameGenFlowScale( parseSettingInt(getFrameGenSetting("frameGenFlowScale", containerFlowScale), 70)); - frameGenFlowScaleAuto = !"0".equals( - getFrameGenSetting("frameGenFlowScaleAuto", containerFlowScaleAuto)); - if (frameGenEnabled) { + if (frameGenEnabled + || !com.winlator.cmod.runtime.display.lsfg.LosslessScaling.isInstalled(this)) { int result = com.winlator.cmod.feature.library.LosslessAutoImport.INSTANCE.sync(this).getResult(); if (result != com.winlator.cmod.feature.library.LosslessAutoImport.RESULT_READY) { Log.i("XServerDisplayActivity", "Lossless shader sync at launch: result=" + result); @@ -806,14 +804,13 @@ private void applyFrameGeneration(VulkanRenderer renderer) { renderer.setFrameGenerationShaders(frameGenCachePath); float refreshRate = applyFrameGenerationDisplayMode(); - renderer.setFrameGenerationMode(frameGenMultiplier, frameGenTargetRate, frameGenFlowScale, - frameGenFlowScaleAuto); + renderer.setFrameGenerationMode(frameGenMultiplier, frameGenTargetRate, frameGenFlowScale); + frameGenRefreshRate = refreshRate; renderer.setFrameGenerationRefreshRate(refreshRate); renderer.setFrameGenerationEnabled(true); syncFrameGenerationHud(); Log.i("XServerDisplayActivity", "Frame generation on: multiplier=" + frameGenMultiplier + " targetRate=" + frameGenTargetRate + " flowScale=" + frameGenFlowScale - + " flowScaleAuto=" + frameGenFlowScaleAuto + " refreshRate=" + refreshRate); } @@ -855,9 +852,14 @@ private float applyFrameGenerationDisplayMode() { if (display == null) return 0f; android.view.Display.Mode active = display.getMode(); - int wanted = frameGenTargetRate > 0 - ? frameGenTargetRate - : frameGenMultiplier * (runtimeFpsLimit > 0 ? runtimeFpsLimit : 60); + int wanted; + if (frameGenTargetRate > 0) { + wanted = frameGenTargetRate; + } else if (runtimeFpsLimit > 0) { + wanted = frameGenMultiplier * runtimeFpsLimit; + } else { + wanted = Integer.MAX_VALUE; + } android.view.Display.Mode best = null; for (android.view.Display.Mode mode : display.getSupportedModes()) { @@ -875,8 +877,9 @@ private float applyFrameGenerationDisplayMode() { params.preferredDisplayModeId = best.getModeId(); params.preferredRefreshRate = 0f; window.setAttributes(params); - Log.i("XServerDisplayActivity", "Frame generation display mode: wanted " + wanted - + "Hz, selected " + Math.round(best.getRefreshRate()) + "Hz (mode " + Log.i("XServerDisplayActivity", "Frame generation display mode: wanted " + + (wanted == Integer.MAX_VALUE ? "highest" : wanted + "Hz") + + ", selected " + Math.round(best.getRefreshRate()) + "Hz (mode " + best.getModeId() + ") fpsLimit=" + runtimeFpsLimit + " cadenceOk=" + (runtimeFpsLimit <= 0 || RefreshRateUtils.isFrameCadenceCompatible( @@ -926,8 +929,6 @@ private void saveFrameGenerationSettings() { String.valueOf(frameGenTargetRate), "0"); overridden |= saveFrameGenOverride("frameGenFlowScale", String.valueOf(frameGenFlowScale), "70"); - overridden |= saveFrameGenOverride("frameGenFlowScaleAuto", - frameGenFlowScaleAuto ? "1" : "0", "1"); if (overridden) shortcut.putExtra("use_container_defaults", "0"); shortcut.saveData(); } else if (container != null) { @@ -935,7 +936,6 @@ private void saveFrameGenerationSettings() { container.putExtra("frameGenMultiplier", String.valueOf(frameGenMultiplier)); container.putExtra("frameGenTargetRate", String.valueOf(frameGenTargetRate)); container.putExtra("frameGenFlowScale", String.valueOf(frameGenFlowScale)); - container.putExtra("frameGenFlowScaleAuto", frameGenFlowScaleAuto ? "1" : "0"); container.saveData(); } } @@ -1139,6 +1139,7 @@ private void applyPreferredRefreshRate() { if (frameGenEnabled && frameGenCachePath != null) { float refreshRate = applyFrameGenerationDisplayMode(); VulkanRenderer renderer = xServerView != null ? xServerView.getRenderer() : null; + frameGenRefreshRate = refreshRate; if (renderer != null) renderer.setFrameGenerationRefreshRate(refreshRate); return; } @@ -1207,12 +1208,30 @@ private void handleDisplayCapabilitiesChanged() { applyPreferredRefreshRate(); } + syncFrameGenerationRefreshRate(); + // Sync the in-drawer slider ceiling, but only if the drawer was opened (otherwise the next open rebuilds state fresh). if (maxChanged && drawerStateHolder != null) { renderDrawerMenu(); } } + private void syncFrameGenerationRefreshRate() { + if (!frameGenEnabled || frameGenCachePath == null) return; + + android.view.Display display = getDisplayCompat(); + if (display == null) return; + + float active = display.getMode().getRefreshRate(); + if (active <= 0f || Math.abs(active - frameGenRefreshRate) < 0.5f) return; + + Log.i("XServerDisplayActivity", "Frame generation panel changed: " + + Math.round(frameGenRefreshRate) + "Hz -> " + Math.round(active) + "Hz"); + frameGenRefreshRate = active; + VulkanRenderer renderer = xServerView != null ? xServerView.getRenderer() : null; + if (renderer != null) renderer.setFrameGenerationRefreshRate(active); + } + @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); @@ -4516,7 +4535,6 @@ private void renderDrawerMenu() { frameGenMultiplier, frameGenTargetRate, frameGenFlowScale, - frameGenFlowScaleAuto, getString(R.string.session_drawer_frame_generation)); // Always-present "Output" tab (live controls while swapped, otherwise a Cast entry point). @@ -4915,11 +4933,6 @@ public void onFrameGenFlowScaleChanged(int percent) { applyFrameGenerationLive(); } - @Override - public void onFrameGenFlowScaleAutoChanged(boolean auto) { - frameGenFlowScaleAuto = auto; - applyFrameGenerationLive(); - } @Override public void onSGSREnabledChanged(boolean enabled) { diff --git a/app/src/main/runtime/display/XServerDrawerFrameGenPane.kt b/app/src/main/runtime/display/XServerDrawerFrameGenPane.kt index f747f123d..7efec2770 100644 --- a/app/src/main/runtime/display/XServerDrawerFrameGenPane.kt +++ b/app/src/main/runtime/display/XServerDrawerFrameGenPane.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -28,9 +29,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.winlator.cmod.R +import com.winlator.cmod.shared.framegen.FrameGenPreset import kotlin.math.roundToInt @Composable @@ -200,34 +203,55 @@ private fun FrameGenerationSection( ) } - NavBooleanRow( - title = stringResource(R.string.session_drawer_frame_generation_flow_scale_auto), - checked = state.frameGenFlowScaleAuto, - onCheckedChange = listener::onFrameGenFlowScaleAutoChanged, + FrameGenPresetRow( + selected = FrameGenPreset.fromFlowScale(state.frameGenFlowScale), + onSelected = { listener.onFrameGenFlowScaleChanged(it.flowScale) }, + paneScale = paneScale, ) + } + } + } + } +} - FrameGenNote( - stringResource(R.string.session_drawer_frame_generation_flow_scale_auto_note), - paneScale, - ) +@Composable +private fun FrameGenPresetRow( + selected: FrameGenPreset, + onSelected: (FrameGenPreset) -> Unit, + paneScale: Float, +) { + val presets = FrameGenPreset.values() + val index = presets.indexOf(selected).coerceAtLeast(0) - if (!state.frameGenFlowScaleAuto) { - NavSliderRow( - label = stringResource(R.string.session_drawer_frame_generation_flow_scale), - valueText = "${state.frameGenFlowScale}%", - value = state.frameGenFlowScale.toFloat(), - valueRange = FrameGenFlowScaleMin.toFloat()..FrameGenFlowScaleMax.toFloat(), - steps = (FrameGenFlowScaleMax - FrameGenFlowScaleMin) / 5 - 1, - onValueChange = { - listener.onFrameGenFlowScaleChanged( - it.roundToInt().coerceIn(FrameGenFlowScaleMin, FrameGenFlowScaleMax), - ) - }, - ) - } - } + Column(verticalArrangement = Arrangement.spacedBy((4f * paneScale).dp)) { + NavSliderRow( + label = stringResource(R.string.frame_generation_preset), + valueText = stringResource(selected.labelRes), + value = index.toFloat(), + valueRange = 0f..(presets.size - 1).toFloat(), + steps = presets.size - 2, + adjustStep = 1f, + onValueChange = { onSelected(FrameGenPreset.atIndex(it.roundToInt())) }, + ) + + Row(modifier = Modifier.fillMaxWidth()) { + presets.forEachIndexed { i, preset -> + Text( + text = stringResource(preset.shortLabelRes), + color = if (i == index) DrawerAccent else DrawerTextSecondary, + fontSize = (10f * paneScale).sp, + fontWeight = if (i == index) FontWeight.SemiBold else FontWeight.Normal, + textAlign = when (i) { + 0 -> TextAlign.Start + presets.size - 1 -> TextAlign.End + else -> TextAlign.Center + }, + modifier = Modifier.weight(1f), + ) } } + + FrameGenNote(stringResource(selected.descriptionRes), paneScale) } } diff --git a/app/src/main/runtime/display/XServerDrawerMenu.kt b/app/src/main/runtime/display/XServerDrawerMenu.kt index 7f0634e0e..36a6fd7ed 100644 --- a/app/src/main/runtime/display/XServerDrawerMenu.kt +++ b/app/src/main/runtime/display/XServerDrawerMenu.kt @@ -625,7 +625,6 @@ data class XServerDrawerState( val frameGenMultiplier: Int = 2, val frameGenTargetRate: Int = 0, val frameGenFlowScale: Int = 70, - val frameGenFlowScaleAuto: Boolean = true, val screenEffectsCardExpanded: Boolean = false, val sgsrEnabled: Boolean = false, val sgsrSharpness: Int = 100, @@ -1041,8 +1040,6 @@ interface XServerDrawerActionListener { fun onFrameGenFlowScaleChanged(percent: Int) - fun onFrameGenFlowScaleAutoChanged(auto: Boolean) - fun onScreenEffectsCardExpandedChanged(expanded: Boolean) fun onOutputResolutionSelected(index: Int) @@ -1497,7 +1494,6 @@ fun withFrameGenState( multiplier: Int, targetRate: Int, flowScale: Int, - flowScaleAuto: Boolean, frameGenTitle: String, ): XServerDrawerState = state.copy( @@ -1515,7 +1511,6 @@ fun withFrameGenState( frameGenMultiplier = multiplier.coerceIn(2, FrameGenMultipliers.last()), frameGenTargetRate = targetRate.coerceAtLeast(0), frameGenFlowScale = flowScale.coerceIn(FrameGenFlowScaleMin, FrameGenFlowScaleMax), - frameGenFlowScaleAuto = flowScaleAuto, ) // Append the always-present "Output" tab item and its state to the drawer state. diff --git a/app/src/main/runtime/display/renderer/VulkanRenderer.java b/app/src/main/runtime/display/renderer/VulkanRenderer.java index c2262abb4..4982db0ca 100644 --- a/app/src/main/runtime/display/renderer/VulkanRenderer.java +++ b/app/src/main/runtime/display/renderer/VulkanRenderer.java @@ -287,8 +287,7 @@ public void attachSurface(Surface surface) { nativeSetFrameGenerationShaders(nativeHandle, frameGenerationShaderCache); } nativeSetFrameGenerationMode(nativeHandle, frameGenerationMultiplier, - frameGenerationTargetRate, frameGenerationFlowScale, - frameGenerationFlowScaleAuto); + frameGenerationTargetRate, frameGenerationFlowScale); nativeSetFrameGenerationRefreshRate(nativeHandle, frameGenerationRefreshRate); if (frameGenerationRequested) { nativeSetFrameGenerationEnabled(nativeHandle, true); @@ -973,7 +972,6 @@ public void setPresentMode(int mode) { private int frameGenerationMultiplier = 2; private int frameGenerationTargetRate = 0; private int frameGenerationFlowScale = 70; - private boolean frameGenerationFlowScaleAuto = true; private float frameGenerationRefreshRate = 0f; public void setFrameGenerationEnabled(boolean enabled) { @@ -987,25 +985,21 @@ public void setFrameGenerationShaders(String cachePath) { if (nativeHandle != 0) nativeSetFrameGenerationShaders(nativeHandle, cachePath); } - public void setFrameGenerationMode(int multiplier, int targetRate, int flowScalePercent, - boolean flowScaleAuto) { + public void setFrameGenerationMode(int multiplier, int targetRate, int flowScalePercent) { int wantMultiplier = Math.max(2, multiplier); int wantTargetRate = Math.max(0, targetRate); int wantFlowScale = flowScalePercent <= 0 ? 70 : flowScalePercent; if (wantMultiplier == frameGenerationMultiplier && wantTargetRate == frameGenerationTargetRate - && wantFlowScale == frameGenerationFlowScale - && flowScaleAuto == frameGenerationFlowScaleAuto) { + && wantFlowScale == frameGenerationFlowScale) { return; } frameGenerationMultiplier = wantMultiplier; frameGenerationTargetRate = wantTargetRate; frameGenerationFlowScale = wantFlowScale; - frameGenerationFlowScaleAuto = flowScaleAuto; if (nativeHandle != 0) { nativeSetFrameGenerationMode(nativeHandle, frameGenerationMultiplier, - frameGenerationTargetRate, frameGenerationFlowScale, - frameGenerationFlowScaleAuto); + frameGenerationTargetRate, frameGenerationFlowScale); } } @@ -1091,8 +1085,8 @@ private static native long nativeCreate(boolean enableValidationLayers, private static native void nativeSetSourceFrameCount(long handle, long count); private static native void nativeSetFrameGenerationRefreshRate(long handle, float hz); private static native void nativeSetFrameGenerationMode(long handle, int multiplier, - int targetRate, int flowScalePercent, - boolean flowScaleAuto); + int targetRate, + int flowScalePercent); private static native long nativeGetGeneratedFrameCount(long handle); private static native long nativeGetPresentedFrameCount(long handle); } diff --git a/app/src/main/shared/framegen/FrameGenPreset.kt b/app/src/main/shared/framegen/FrameGenPreset.kt new file mode 100644 index 000000000..e58a014fc --- /dev/null +++ b/app/src/main/shared/framegen/FrameGenPreset.kt @@ -0,0 +1,57 @@ +package com.winlator.cmod.shared.framegen + +import com.winlator.cmod.R +import kotlin.math.abs + +enum class FrameGenPreset( + val flowScale: Int, + val labelRes: Int, + val shortLabelRes: Int, + val descriptionRes: Int, +) { + ULTRA_PERFORMANCE( + flowScale = 40, + labelRes = R.string.frame_generation_preset_ultra_performance, + shortLabelRes = R.string.frame_generation_preset_ultra_performance_short, + descriptionRes = R.string.frame_generation_preset_ultra_performance_note, + ), + PERFORMANCE( + flowScale = 50, + labelRes = R.string.frame_generation_preset_performance, + shortLabelRes = R.string.frame_generation_preset_performance_short, + descriptionRes = R.string.frame_generation_preset_performance_note, + ), + BALANCED( + flowScale = 70, + labelRes = R.string.frame_generation_preset_balanced, + shortLabelRes = R.string.frame_generation_preset_balanced_short, + descriptionRes = R.string.frame_generation_preset_balanced_note, + ), + QUALITY( + flowScale = 100, + labelRes = R.string.frame_generation_preset_quality, + shortLabelRes = R.string.frame_generation_preset_quality_short, + descriptionRes = R.string.frame_generation_preset_quality_note, + ), + ; + + companion object { + val DEFAULT = BALANCED + + fun fromFlowScale(flowScale: Int): FrameGenPreset { + var best = DEFAULT + var bestDelta = Int.MAX_VALUE + for (preset in values()) { + val delta = abs(preset.flowScale - flowScale) + if (delta < bestDelta) { + bestDelta = delta + best = preset + } + } + return best + } + + fun atIndex(index: Int): FrameGenPreset = + values()[index.coerceIn(0, values().size - 1)] + } +} From f4fccd819c345ab5a4812c340fad8f86cbf78a60 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Wed, 26 Aug 2026 12:52:47 -0400 Subject: [PATCH 29/35] Frame generation: cut redundant copies, barriers and enable fp16 Three host-side changes, each validated bit-exact or quality-neutral on a desktop Vulkan harness before landing. The Lossless shaders themselves are untouched. Enable shaderFloat16. The cache path always asked for the fp16 shader blobs but the device never enabled VK_KHR_shader_float16_int8, so those pipelines ran outside spec and the driver was free to reject them or quietly demote to fp32. The feature is now probed and enabled, and pNext is a real chain rather than being overwritten by the YCbCr struct alone. vkGetPhysicalDeviceFeatures2 was missing from the dispatch table and is now loaded. Measured on a desktop harness, fp16 through the whole flow and interpolation pipeline is quality neutral: mean +0.22 dB PSNR across nine motion types, SSIM identical to four decimals, and sub-pixel precision better than an eighth of a pixel out to 128px of motion. Write generated frames straight into the swapchain image. The swapchain was created without STORAGE usage, so every generated frame was composited to an intermediate and then blitted, costing a full-resolution read plus write per generated frame. The swapchain now takes STORAGE when the surface reports it, and generate targets the acquired image directly. Where the surface does not support it the composite and blit path is kept unchanged. A readback comparison of both paths differed in zero of 262144 bytes. Interleave gamma with delta. Both read only the previous level's gamma output and each owns its temporaries, so they were already independent but ran back to back, spending 15 barriers where 10 suffice. Barrier commands per generated frame drop from 67 to 52. Both were refactored into the step-wise form LsfgAlpha already used; the outputs of the serial and interleaved orders are byte identical. Also log which shader variant the cache yielded, so the active precision can be confirmed on device. --- .../main/cpp/winlator/vk/lsfg/lsfg_chain.cpp | 28 +++- .../main/cpp/winlator/vk/lsfg/lsfg_delta.cpp | 146 +++++++++--------- .../main/cpp/winlator/vk/lsfg/lsfg_delta.hpp | 5 + .../main/cpp/winlator/vk/lsfg/lsfg_gamma.cpp | 89 +++++------ .../main/cpp/winlator/vk/lsfg/lsfg_gamma.hpp | 5 + .../cpp/winlator/vk/lsfg/lsfg_shaders.cpp | 8 +- app/src/main/cpp/winlator/vk/vk_dispatch.c | 1 + app/src/main/cpp/winlator/vk/vk_dispatch.h | 2 + app/src/main/cpp/winlator/vk/vk_renderer.c | 65 +++++++- app/src/main/cpp/winlator/vk/vk_state.h | 2 + 10 files changed, 221 insertions(+), 130 deletions(-) diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp index 62c388048..121b16a68 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_chain.cpp @@ -110,10 +110,32 @@ void LsfgChain::DispatchGeneration(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t generation_count, size_t generation, uint32_t target, VkImage image, VkExtent2D extent) { const size_t slot = LsfgGenerationSlot(generation_count, generation); + constexpr size_t PAIRED_STEPS = + LSFG_GAMMA_STAGES > LSFG_DELTA_STAGES ? LSFG_GAMMA_STAGES : LSFG_DELTA_STAGES; + for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) { - gamma[i].Dispatch(cmdbuf, frame_count, slot); - if (HasDelta(i)) { - delta[i - LSFG_FIRST_DELTA_LEVEL].Dispatch(cmdbuf, frame_count, slot); + if (!HasDelta(i)) { + gamma[i].Dispatch(cmdbuf, frame_count, slot); + continue; + } + + LsfgDelta& paired = delta[i - LSFG_FIRST_DELTA_LEVEL]; + for (size_t step = 0; step < PAIRED_STEPS; ++step) { + LsfgBarriers barriers(cmdbuf); + if (step < LSFG_GAMMA_STAGES) { + gamma[i].PushStepBarriers(barriers, frame_count, step); + } + if (step < LSFG_DELTA_STAGES) { + paired.PushStepBarriers(barriers, frame_count, step); + } + barriers.Build(); + + if (step < LSFG_GAMMA_STAGES) { + gamma[i].DispatchStep(cmdbuf, frame_count, slot, step); + } + if (step < LSFG_DELTA_STAGES) { + paired.DispatchStep(cmdbuf, frame_count, slot, step); + } } } generate.Dispatch(cmdbuf, frame_count, slot, target, image, extent); diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.cpp index f120007a9..d09f31d20 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.cpp @@ -207,88 +207,82 @@ LsfgDelta::LsfgDelta(const Device& device, const LsfgShaders& shaders, LsfgResou allocated = true; } -void LsfgDelta::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot) { - const Generation& pass = generations[slot]; - - const VkExtent2D extent = temp1[0].Extent(); - const uint32_t groups_x = GroupCount(extent.width); - const uint32_t groups_y = GroupCount(extent.height); - +void LsfgDelta::PushStepBarriers(LsfgBarriers& barriers, uint64_t frame_count, size_t step) { const size_t history = frame_count % LSFG_HISTORY_SLOTS; const size_t previous_history = (frame_count + 2) % LSFG_HISTORY_SLOTS; - LsfgBarriers(cmdbuf) - .WriteToReadAll((*inputs)[previous_history]) - .WriteToReadAll((*inputs)[history]) - .WriteToRead(previous_gamma) - .ReadToWriteAll(temp1) - .Build(); - passes[0].Bind(cmdbuf, pass.first_descriptor_sets[history]); - vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); - - LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); - passes[1].Bind(cmdbuf, pass.descriptor_sets[0]); - vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); - - LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(temp1).Build(); - passes[2].Bind(cmdbuf, pass.descriptor_sets[1]); - vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); - - LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); - passes[3].Bind(cmdbuf, pass.descriptor_sets[2]); - vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); - - LsfgBarriers(cmdbuf) - .WriteToReadAll(temp2) - .WriteToRead(previous_gamma) - .WriteToRead(*flow_input) - .ReadToWrite(out_image1) - .Build(); - passes[4].Bind(cmdbuf, pass.descriptor_sets[3]); - vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); - - LsfgBarriers(cmdbuf) - .WriteToReadAll((*inputs)[previous_history]) - .WriteToReadAll((*inputs)[history]) - .WriteToRead(previous_gamma) - .WriteToRead(previous1) - .ReadToWriteAll(temp2) - .Build(); - passes[5].Bind(cmdbuf, pass.sixth_descriptor_sets[history]); - vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); - - LsfgBarriers(cmdbuf) - .WriteToReadAll(temp2) - .ReadToWrite(temp1[0]) - .ReadToWrite(temp1[1]) - .Build(); - passes[6].Bind(cmdbuf, pass.descriptor_sets[4]); - vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); + switch (step) { + case 0: + barriers.WriteToReadAll((*inputs)[previous_history]) + .WriteToReadAll((*inputs)[history]) + .WriteToRead(previous_gamma) + .ReadToWriteAll(temp1); + break; + case 1: + barriers.WriteToReadAll(temp1).ReadToWriteAll(temp2); + break; + case 2: + barriers.WriteToReadAll(temp2).ReadToWriteAll(temp1); + break; + case 3: + barriers.WriteToReadAll(temp1).ReadToWriteAll(temp2); + break; + case 4: + barriers.WriteToReadAll(temp2) + .WriteToRead(previous_gamma) + .WriteToRead(*flow_input) + .ReadToWrite(out_image1); + break; + case 5: + barriers.WriteToReadAll((*inputs)[previous_history]) + .WriteToReadAll((*inputs)[history]) + .WriteToRead(previous_gamma) + .WriteToRead(previous1) + .ReadToWriteAll(temp2); + break; + case 6: + barriers.WriteToReadAll(temp2).ReadToWrite(temp1[0]).ReadToWrite(temp1[1]); + break; + case 7: + barriers.WriteToRead(temp1[0]).WriteToRead(temp1[1]).ReadToWriteAll(temp2); + break; + case 8: + barriers.WriteToReadAll(temp2).ReadToWrite(temp1[0]).ReadToWrite(temp1[1]); + break; + default: + barriers.WriteToRead(temp1[0]) + .WriteToRead(temp1[1]) + .WriteToRead(previous2) + .ReadToWrite(out_image2); + break; + } +} - LsfgBarriers(cmdbuf) - .WriteToRead(temp1[0]) - .WriteToRead(temp1[1]) - .ReadToWriteAll(temp2) - .Build(); - passes[7].Bind(cmdbuf, pass.descriptor_sets[5]); - vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); +void LsfgDelta::DispatchStep(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot, + size_t step) { + const Generation& pass = generations[slot]; + const VkExtent2D extent = temp1[0].Extent(); + const size_t history = frame_count % LSFG_HISTORY_SLOTS; - LsfgBarriers(cmdbuf) - .WriteToReadAll(temp2) - .ReadToWrite(temp1[0]) - .ReadToWrite(temp1[1]) - .Build(); - passes[8].Bind(cmdbuf, pass.descriptor_sets[6]); - vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); + if (step == 0) { + passes[0].Bind(cmdbuf, pass.first_descriptor_sets[history]); + } else if (step == 5) { + passes[5].Bind(cmdbuf, pass.sixth_descriptor_sets[history]); + } else if (step < 5) { + passes[step].Bind(cmdbuf, pass.descriptor_sets[step - 1]); + } else { + passes[step].Bind(cmdbuf, pass.descriptor_sets[step - 2]); + } + vkd.CmdDispatch(cmdbuf, GroupCount(extent.width), GroupCount(extent.height), 1); +} - LsfgBarriers(cmdbuf) - .WriteToRead(temp1[0]) - .WriteToRead(temp1[1]) - .WriteToRead(previous2) - .ReadToWrite(out_image2) - .Build(); - passes[9].Bind(cmdbuf, pass.descriptor_sets[7]); - vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); +void LsfgDelta::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot) { + for (size_t step = 0; step < LSFG_DELTA_STAGES; ++step) { + LsfgBarriers barriers(cmdbuf); + PushStepBarriers(barriers, frame_count, step); + barriers.Build(); + DispatchStep(cmdbuf, frame_count, slot, step); + } } } diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.hpp index 9143c621d..45e3b4adc 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_delta.hpp @@ -26,6 +26,11 @@ class LsfgDelta { void Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot); + void PushStepBarriers(LsfgBarriers& barriers, uint64_t frame_count, size_t step); + + void DispatchStep(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot, + size_t step); + [[nodiscard]] LsfgImage& Output1() { return out_image1; } diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.cpp index 600eef453..83d117cb3 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.cpp @@ -140,53 +140,56 @@ LsfgGamma::LsfgGamma(const Device& device, const LsfgShaders& shaders, LsfgResou allocated = true; } -void LsfgGamma::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot) { - const Generation& pass = generations[slot]; +void LsfgGamma::PushStepBarriers(LsfgBarriers& barriers, uint64_t frame_count, size_t step) { + const size_t history = frame_count % LSFG_HISTORY_SLOTS; + const size_t previous_history = (frame_count + 2) % LSFG_HISTORY_SLOTS; - const VkExtent2D extent = temp1[0].Extent(); - const uint32_t groups_x = GroupCount(extent.width); - const uint32_t groups_y = GroupCount(extent.height); + switch (step) { + case 0: + barriers.WriteToReadAll((*inputs)[previous_history]) + .WriteToReadAll((*inputs)[history]) + .WriteToRead(previous) + .ReadToWriteAll(temp1); + break; + case 1: + barriers.WriteToReadAll(temp1).ReadToWriteAll(temp2); + break; + case 2: + barriers.WriteToReadAll(temp2).ReadToWrite(temp1[0]).ReadToWrite(temp1[1]); + break; + case 3: + barriers.WriteToRead(temp1[0]).WriteToRead(temp1[1]).ReadToWriteAll(temp2); + break; + default: + barriers.WriteToReadAll(temp2) + .WriteToRead(previous) + .WriteToRead(*flow_input) + .ReadToWrite(out_image); + break; + } +} +void LsfgGamma::DispatchStep(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot, + size_t step) { + const Generation& pass = generations[slot]; + const VkExtent2D extent = temp1[0].Extent(); const size_t history = frame_count % LSFG_HISTORY_SLOTS; - const size_t previous_history = (frame_count + 2) % LSFG_HISTORY_SLOTS; - LsfgBarriers(cmdbuf) - .WriteToReadAll((*inputs)[previous_history]) - .WriteToReadAll((*inputs)[history]) - .WriteToRead(previous) - .ReadToWriteAll(temp1) - .Build(); - passes[0].Bind(cmdbuf, pass.first_descriptor_sets[history]); - vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); - - LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); - passes[1].Bind(cmdbuf, pass.descriptor_sets[0]); - vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); - - LsfgBarriers(cmdbuf) - .WriteToReadAll(temp2) - .ReadToWrite(temp1[0]) - .ReadToWrite(temp1[1]) - .Build(); - passes[2].Bind(cmdbuf, pass.descriptor_sets[1]); - vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); - - LsfgBarriers(cmdbuf) - .WriteToRead(temp1[0]) - .WriteToRead(temp1[1]) - .ReadToWriteAll(temp2) - .Build(); - passes[3].Bind(cmdbuf, pass.descriptor_sets[2]); - vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); - - LsfgBarriers(cmdbuf) - .WriteToReadAll(temp2) - .WriteToRead(previous) - .WriteToRead(*flow_input) - .ReadToWrite(out_image) - .Build(); - passes[4].Bind(cmdbuf, pass.descriptor_sets[3]); - vkd.CmdDispatch(cmdbuf, groups_x, groups_y, 1); + if (step == 0) { + passes[0].Bind(cmdbuf, pass.first_descriptor_sets[history]); + } else { + passes[step].Bind(cmdbuf, pass.descriptor_sets[step - 1]); + } + vkd.CmdDispatch(cmdbuf, GroupCount(extent.width), GroupCount(extent.height), 1); +} + +void LsfgGamma::Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot) { + for (size_t step = 0; step < LSFG_GAMMA_STAGES; ++step) { + LsfgBarriers barriers(cmdbuf); + PushStepBarriers(barriers, frame_count, step); + barriers.Build(); + DispatchStep(cmdbuf, frame_count, slot, step); + } } } diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.hpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.hpp index fd211e4d5..c3dcb3848 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.hpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_gamma.hpp @@ -26,6 +26,11 @@ class LsfgGamma { void Dispatch(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot); + void PushStepBarriers(LsfgBarriers& barriers, uint64_t frame_count, size_t step); + + void DispatchStep(VkCommandBuffer cmdbuf, uint64_t frame_count, size_t slot, + size_t step); + [[nodiscard]] LsfgImage& Output() { return out_image; } diff --git a/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.cpp b/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.cpp index 874df156f..33a66b1a4 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.cpp +++ b/app/src/main/cpp/winlator/vk/lsfg/lsfg_shaders.cpp @@ -40,10 +40,16 @@ LsfgShaders::LsfgShaders(const Device& device_, const std::string& cache_path) modules.emplace(module.id, handle); } + const LsfgVariant variant = set.variant; lsfg_release_modules(&set); valid = modules.size() == LSFG_SHADER_COUNT; if (valid) { - SHADER_LOGI("Created %zu LSFG shader modules", modules.size()); + const char* variant_name = variant == LSFG_VARIANT_FP16 ? "fp16" + : variant == LSFG_VARIANT_FP32 ? "fp32" + : variant == LSFG_VARIANT_DXBC ? "dxbc-translated" + : "unknown"; + SHADER_LOGI("Created %zu LSFG shader modules, variant=%s", modules.size(), + variant_name); } else { SHADER_LOGE("Expected %u shader modules, got %zu", LSFG_SHADER_COUNT, modules.size()); Release(); diff --git a/app/src/main/cpp/winlator/vk/vk_dispatch.c b/app/src/main/cpp/winlator/vk/vk_dispatch.c index 3ca0cfbea..92a5b541c 100644 --- a/app/src/main/cpp/winlator/vk/vk_dispatch.c +++ b/app/src/main/cpp/winlator/vk/vk_dispatch.c @@ -49,6 +49,7 @@ bool vkd_load_instance(VkInstance instance) { LOAD(EnumeratePhysicalDevices); LOAD(GetPhysicalDeviceProperties); LOAD(GetPhysicalDeviceMemoryProperties); + LOAD(GetPhysicalDeviceFeatures2); LOAD(GetPhysicalDeviceQueueFamilyProperties); LOAD(GetPhysicalDeviceFormatProperties); LOAD(GetPhysicalDeviceImageFormatProperties); diff --git a/app/src/main/cpp/winlator/vk/vk_dispatch.h b/app/src/main/cpp/winlator/vk/vk_dispatch.h index 5bdcbdab9..a9f49d05b 100644 --- a/app/src/main/cpp/winlator/vk/vk_dispatch.h +++ b/app/src/main/cpp/winlator/vk/vk_dispatch.h @@ -30,6 +30,7 @@ typedef struct VkDispatch { PFN_vkEnumeratePhysicalDevices EnumeratePhysicalDevices; PFN_vkGetPhysicalDeviceProperties GetPhysicalDeviceProperties; PFN_vkGetPhysicalDeviceMemoryProperties GetPhysicalDeviceMemoryProperties; + PFN_vkGetPhysicalDeviceFeatures2 GetPhysicalDeviceFeatures2; PFN_vkGetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceQueueFamilyProperties; PFN_vkGetPhysicalDeviceFormatProperties GetPhysicalDeviceFormatProperties; PFN_vkGetPhysicalDeviceImageFormatProperties GetPhysicalDeviceImageFormatProperties; @@ -174,6 +175,7 @@ void vkd_unload(void); #define vkEnumeratePhysicalDevices vkd.EnumeratePhysicalDevices #define vkGetPhysicalDeviceProperties vkd.GetPhysicalDeviceProperties #define vkGetPhysicalDeviceMemoryProperties vkd.GetPhysicalDeviceMemoryProperties +#define vkGetPhysicalDeviceFeatures2 vkd.GetPhysicalDeviceFeatures2 #define vkGetPhysicalDeviceQueueFamilyProperties vkd.GetPhysicalDeviceQueueFamilyProperties #define vkGetPhysicalDeviceFormatProperties vkd.GetPhysicalDeviceFormatProperties #define vkGetPhysicalDeviceImageFormatProperties vkd.GetPhysicalDeviceImageFormatProperties diff --git a/app/src/main/cpp/winlator/vk/vk_renderer.c b/app/src/main/cpp/winlator/vk/vk_renderer.c index 501ec0390..214cddb9a 100644 --- a/app/src/main/cpp/winlator/vk/vk_renderer.c +++ b/app/src/main/cpp/winlator/vk/vk_renderer.c @@ -373,6 +373,8 @@ static bool create_device(VkRenderer* r) { bool has_extmem_caps = has_extension(exts, ext_count, VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME); bool has_queue_fam = has_extension(exts, ext_count, VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME); bool has_cubic = has_extension(exts, ext_count, VK_EXT_FILTER_CUBIC_EXTENSION_NAME); + bool has_shader_f16 = has_extension(exts, ext_count, + VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME); free(exts); @@ -392,6 +394,26 @@ static bool create_device(VkRenderer* r) { if (has_cubic) enable[enable_n++] = VK_EXT_FILTER_CUBIC_EXTENSION_NAME; (void)has_extmem_caps; + VkPhysicalDeviceShaderFloat16Int8FeaturesKHR f16_feat = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES_KHR + }; + bool enable_f16 = false; + if (has_shader_f16) { + VkPhysicalDeviceShaderFloat16Int8FeaturesKHR probe = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES_KHR + }; + VkPhysicalDeviceFeatures2 probe2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2}; + probe2.pNext = &probe; + vkGetPhysicalDeviceFeatures2(r->physical_device, &probe2); + enable_f16 = probe.shaderFloat16 == VK_TRUE; + if (enable_f16) { + f16_feat.shaderFloat16 = VK_TRUE; + enable[enable_n++] = VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME; + } + } + r->ext_shader_float16 = enable_f16; + VK_LOGI("shaderFloat16: extension=%d enabled=%d", has_shader_f16, enable_f16); + r->ext_ahb = ahb_ok; r->ext_ycbcr = has_ycbcr; r->ext_filter_cubic = has_cubic; @@ -412,8 +434,18 @@ static bool create_device(VkRenderer* r) { }; ycbcr_feat.samplerYcbcrConversion = has_ycbcr ? VK_TRUE : VK_FALSE; + void* feature_chain = NULL; + if (has_ycbcr) { + ycbcr_feat.pNext = feature_chain; + feature_chain = &ycbcr_feat; + } + if (enable_f16) { + f16_feat.pNext = feature_chain; + feature_chain = &f16_feat; + } + VkDeviceCreateInfo dci = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO}; - if (has_ycbcr) dci.pNext = &ycbcr_feat; + dci.pNext = feature_chain; dci.queueCreateInfoCount = 1; dci.pQueueCreateInfos = &qci; dci.enabledExtensionCount = enable_n; @@ -1282,6 +1314,11 @@ static bool create_swapchain(VkRenderer* r, uint32_t fallback_width, uint32_t fa bool transfer_dst_capable = (caps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0; r->swapchain_transfer_dst = r->framegen_requested && transfer_dst_capable; if (r->swapchain_transfer_dst) sci.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + + r->swapchain_storage = r->framegen_requested + && (caps.supportedUsageFlags & VK_IMAGE_USAGE_STORAGE_BIT) != 0 + && composite_format_supported(r); + if (r->swapchain_storage) sci.imageUsage |= VK_IMAGE_USAGE_STORAGE_BIT; sci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; sci.preTransform = pre_transform; sci.compositeAlpha = (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) @@ -2693,12 +2730,26 @@ static bool record_and_submit_frame(VkRenderer* r) { r->swapchain_extent.width, r->swapchain_extent.height, gen_count); for (uint32_t g = 0; g < gen_count; g++) { - VkCompositeTarget* gt = &r->composite[VK_FRAMES_IN_FLIGHT + g]; - vkr_lsfg_generate_into(r->lsfg, f->cmd, g, VK_FRAMES_IN_FLIGHT + g, - gt->image, gt->view, - r->swapchain_extent.width, r->swapchain_extent.height); - blit_composite_to_swapchain(r, f->cmd, gt, - r->swapchain_images[gen_image_index[g]]); + const uint32_t idx = gen_image_index[g]; + if (r->swapchain_storage) { + vkr_lsfg_generate_into(r->lsfg, f->cmd, g, VK_FRAMES_IN_FLIGHT + g, + r->swapchain_images[idx], r->swapchain_views[idx], + r->swapchain_extent.width, + r->swapchain_extent.height); + vkr_image_barrier(f->cmd, r->swapchain_images[idx], + VK_IMAGE_LAYOUT_GENERAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + VK_ACCESS_SHADER_WRITE_BIT, 0); + } else { + VkCompositeTarget* gt = &r->composite[VK_FRAMES_IN_FLIGHT + g]; + vkr_lsfg_generate_into(r->lsfg, f->cmd, g, VK_FRAMES_IN_FLIGHT + g, + gt->image, gt->view, + r->swapchain_extent.width, + r->swapchain_extent.height); + blit_composite_to_swapchain(r, f->cmd, gt, r->swapchain_images[idx]); + } } r->framegen_real_frames++; r->framegen_made_frames += gen_count; diff --git a/app/src/main/cpp/winlator/vk/vk_state.h b/app/src/main/cpp/winlator/vk/vk_state.h index 2155d69a1..68255cf0d 100644 --- a/app/src/main/cpp/winlator/vk/vk_state.h +++ b/app/src/main/cpp/winlator/vk/vk_state.h @@ -418,6 +418,7 @@ typedef struct VkRenderer { bool framegen_supported; bool framegen_requested; bool swapchain_transfer_dst; + bool swapchain_storage; struct VkrLsfg* lsfg; char* lsfg_cache_path; uint32_t framegen_multiplier; @@ -480,6 +481,7 @@ typedef struct VkRenderer { // Extensions present bool ext_ahb; bool ext_ycbcr; + bool ext_shader_float16; // Cached device capabilities populated by query_device_caps(). VkDeviceCaps caps; From 614ac6a30c4f3a2e9977c92e7ea83897281f26ea Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Wed, 26 Aug 2026 12:52:55 -0400 Subject: [PATCH 30/35] Translate the frame generation quality preset strings The preset slider shipped English-only while every other string in the frame generation section is translated across all 22 locales. Adds the 13 preset strings to each of them. Drops frame_generation_preset_note, which was added with the slider but never referenced from any layout or composable. --- app/src/main/res/values-b+es+419/strings.xml | 13 +++++++++++++ app/src/main/res/values-da/strings.xml | 13 +++++++++++++ app/src/main/res/values-de/strings.xml | 13 +++++++++++++ app/src/main/res/values-es/strings.xml | 13 +++++++++++++ app/src/main/res/values-fi/strings.xml | 13 +++++++++++++ app/src/main/res/values-fr/strings.xml | 13 +++++++++++++ app/src/main/res/values-hi/strings.xml | 13 +++++++++++++ app/src/main/res/values-it/strings.xml | 13 +++++++++++++ app/src/main/res/values-ja/strings.xml | 13 +++++++++++++ app/src/main/res/values-ko/strings.xml | 13 +++++++++++++ app/src/main/res/values-no/strings.xml | 13 +++++++++++++ app/src/main/res/values-pl/strings.xml | 13 +++++++++++++ app/src/main/res/values-pt-rBR/strings.xml | 13 +++++++++++++ app/src/main/res/values-pt/strings.xml | 13 +++++++++++++ app/src/main/res/values-ro/strings.xml | 13 +++++++++++++ app/src/main/res/values-ru/strings.xml | 13 +++++++++++++ app/src/main/res/values-sv/strings.xml | 13 +++++++++++++ app/src/main/res/values-th/strings.xml | 13 +++++++++++++ app/src/main/res/values-tr/strings.xml | 13 +++++++++++++ app/src/main/res/values-uk/strings.xml | 13 +++++++++++++ app/src/main/res/values-zh-rCN/strings.xml | 13 +++++++++++++ app/src/main/res/values-zh-rTW/strings.xml | 13 +++++++++++++ app/src/main/res/values/strings.xml | 1 - 23 files changed, 286 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 72bcf2e5e..5c2dfbbe7 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -2504,6 +2504,19 @@ Ruta instalada: Interpola entre fotogramas renderizados con Lossless Scaling. Movimiento más fluido, pero un fotograma extra de latencia de entrada. Sube hacia el objetivo solo mientras ayude de forma medible, en lugar de mantener un multiplicador fijo. Instala Lossless Scaling en los ajustes del contenedor para usar la generación de fotogramas. + Preajuste de calidad + Ultra rendimiento + Rendimiento + Equilibrado + Calidad + Ultra + Rend. + Equilibrado + Calidad + El movimiento se calcula al 40 % de resolución. Alrededor del 40 % del costo de GPU de Calidad. El detalle fino se suaviza durante el movimiento rápido. + El movimiento se calcula al 50 % de resolución. Alrededor del 46 % del costo de GPU de Calidad, casi sin pérdida visible. + El movimiento se calcula al 70 % de resolución. Alrededor del 61 % del costo de GPU de Calidad. Recomendado para consolas portátiles. + El movimiento se calcula a resolución completa. Costo máximo; úsalo solo cuando el juego tenga GPU de sobra. Generación de fotogramas Buscando Lossless Scaling… Sombreadores de Lossless Scaling listos. diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 69459bc2a..afee73321 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -2506,6 +2506,19 @@ Installeret sti: Interpolerer mellem gengivne billeder med Lossless Scaling. Blødere bevægelse, men ét ekstra billede med inputforsinkelse. Stiger mod målet, kun mens det målbart hjælper, i stedet for at fastholde en fast multiplikator. Installér Lossless Scaling i containerindstillingerne for at bruge billedgenerering. + Kvalitetsforudindstilling + Ultra ydeevne + Ydeevne + Balanceret + Kvalitet + Ultra + Ydeevne + Balanceret + Kvalitet + Bevægelse beregnes ved 40 % opløsning. Omkring 40 % af Kvalitets GPU-forbrug. Fine detaljer bliver blødere under hurtig bevægelse. + Bevægelse beregnes ved 50 % opløsning. Omkring 46 % af Kvalitets GPU-forbrug, med næsten intet synligt tab. + Bevægelse beregnes ved 70 % opløsning. Omkring 61 % af Kvalitets GPU-forbrug. Anbefales til håndholdte. + Bevægelse beregnes ved fuld opløsning. Højeste forbrug; brug det kun, når spillet har GPU-kapacitet til overs. Billedgenerering Leder efter Lossless Scaling… Lossless Scaling-shaders er klar. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 37f7c0fb6..7c4d6195a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -2506,6 +2506,19 @@ Installierter Pfad: Interpoliert zwischen gerenderten Frames mit Lossless Scaling. Flüssigere Bewegung, aber ein zusätzlicher Frame Eingabeverzögerung. Steigt nur dann in Richtung Ziel, wenn es messbar hilft, statt einen festen Multiplikator zu halten. Installiere Lossless Scaling in den Container-Einstellungen, um Frame-Generierung zu nutzen. + Qualitätsvoreinstellung + Ultra-Leistung + Leistung + Ausgewogen + Qualität + Ultra + Leistung + Ausgewogen + Qualität + Bewegung wird mit 40 % Auflösung berechnet. Etwa 40 % der GPU-Last von Qualität. Feine Details werden bei schnellen Bewegungen weicher. + Bewegung wird mit 50 % Auflösung berechnet. Etwa 46 % der GPU-Last von Qualität, mit fast keinem sichtbaren Verlust. + Bewegung wird mit 70 % Auflösung berechnet. Etwa 61 % der GPU-Last von Qualität. Empfohlen für Handhelds. + Bewegung wird mit voller Auflösung berechnet. Höchste Last; nur verwenden, wenn das Spiel GPU-Reserven hat. Frame-Generierung Suche nach Lossless Scaling… Lossless-Scaling-Shader bereit. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 22e0de191..3cb44fa04 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -2505,6 +2505,19 @@ Ruta instalada: Interpola entre fotogramas renderizados con Lossless Scaling. Movimiento más fluido, pero un fotograma extra de latencia de entrada. Sube hacia el objetivo solo mientras ayude de forma medible, en lugar de mantener un multiplicador fijo. Instala Lossless Scaling en los ajustes del contenedor para usar la generación de fotogramas. + Preajuste de calidad + Ultra rendimiento + Rendimiento + Equilibrado + Calidad + Ultra + Rend. + Equilibrado + Calidad + El movimiento se calcula al 40 % de resolución. Alrededor del 40 % del coste de GPU de Calidad. El detalle fino se suaviza con el movimiento rápido. + El movimiento se calcula al 50 % de resolución. Alrededor del 46 % del coste de GPU de Calidad, casi sin pérdida visible. + El movimiento se calcula al 70 % de resolución. Alrededor del 61 % del coste de GPU de Calidad. Recomendado para portátiles. + El movimiento se calcula a resolución completa. Coste máximo; úsalo solo cuando el juego tenga GPU de sobra. Generación de fotogramas Buscando Lossless Scaling… Sombreadores de Lossless Scaling listos. diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 6a08a66ba..3241f5e8c 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -2504,6 +2504,19 @@ Asennuspolku: Interpoloi renderöityjen ruutujen välillä Lossless Scalingilla. Sulavampi liike, mutta yhden ruudun lisäviive syötteessä. Nousee kohti tavoitetta vain, kun siitä on mitattavaa hyötyä, sen sijaan että pitäisi kiinteän kertoimen. Asenna Lossless Scaling säilön asetuksista käyttääksesi ruudun generointia. + Laatuesiasetus + Ultra-suorituskyky + Suorituskyky + Tasapainoinen + Laatu + Ultra + Suoritus + Tasapaino + Laatu + Liike arvioidaan 40 %:n tarkkuudella. Noin 40 % Laatu-asetuksen GPU-kuormasta. Hienot yksityiskohdat pehmenevät nopeassa liikkeessä. + Liike arvioidaan 50 %:n tarkkuudella. Noin 46 % Laatu-asetuksen GPU-kuormasta, lähes ilman näkyvää heikkenemistä. + Liike arvioidaan 70 %:n tarkkuudella. Noin 61 % Laatu-asetuksen GPU-kuormasta. Suositellaan käsikonsoleille. + Liike arvioidaan täydellä tarkkuudella. Suurin kuorma; käytä vain, kun pelillä on GPU-tehoa varalla. Ruudun generointi Etsitään Lossless Scalingia… Lossless Scalingin varjostimet valmiina. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index ab9935e22..79bb816b7 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -2505,6 +2505,19 @@ Chemin installé : Interpole entre les images rendues avec Lossless Scaling. Mouvement plus fluide, mais une image supplémentaire de latence d\'entrée. Monte vers la cible uniquement tant que cela aide de façon mesurable, au lieu de conserver un multiplicateur fixe. Installez Lossless Scaling dans les paramètres du conteneur pour utiliser la génération d\'images. + Préréglage de qualité + Ultra performance + Performance + Équilibré + Qualité + Ultra + Perf. + Équilibré + Qualité + Le mouvement est estimé à 40 % de la résolution. Environ 40 % du coût GPU de Qualité. Les détails fins s\'adoucissent lors des mouvements rapides. + Le mouvement est estimé à 50 % de la résolution. Environ 46 % du coût GPU de Qualité, sans perte visible. + Le mouvement est estimé à 70 % de la résolution. Environ 61 % du coût GPU de Qualité. Recommandé pour les consoles portables. + Le mouvement est estimé en pleine résolution. Coût maximal ; à utiliser uniquement si le jeu dispose de marge GPU. Génération d\'images Recherche de Lossless Scaling… Shaders Lossless Scaling prêts. diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 8d1aef7d3..8bd5687d2 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -2441,6 +2441,19 @@ Lossless Scaling से रेंडर किए गए फ़्रेमों के बीच इंटरपोलेट करता है। गति अधिक सहज, लेकिन इनपुट में एक अतिरिक्त फ़्रेम की देरी। स्थिर गुणक बनाए रखने के बजाय, केवल तभी लक्ष्य की ओर बढ़ता है जब इससे मापने योग्य लाभ हो। फ़्रेम जनरेशन उपयोग करने के लिए कंटेनर सेटिंग्स में Lossless Scaling इंस्टॉल करें। + गुणवत्ता प्रीसेट + अल्ट्रा प्रदर्शन + प्रदर्शन + संतुलित + गुणवत्ता + अल्ट्रा + प्रदर्शन + संतुलित + गुणवत्ता + गति का अनुमान 40% रिज़ॉल्यूशन पर लगाया जाता है। गुणवत्ता की GPU लागत का लगभग 40%। तेज़ गति में महीन विवरण नरम हो जाते हैं। + गति का अनुमान 50% रिज़ॉल्यूशन पर लगाया जाता है। गुणवत्ता की GPU लागत का लगभग 46%, लगभग कोई दृश्य हानि नहीं। + गति का अनुमान 70% रिज़ॉल्यूशन पर लगाया जाता है। गुणवत्ता की GPU लागत का लगभग 61%। हैंडहेल्ड के लिए अनुशंसित। + गति का अनुमान पूर्ण रिज़ॉल्यूशन पर लगाया जाता है। सर्वाधिक लागत; इसे तभी उपयोग करें जब गेम के पास GPU क्षमता बची हो। फ़्रेम जनरेशन Lossless Scaling खोजा जा रहा है… Lossless Scaling शेडर तैयार हैं। diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 01065f1f0..4bed5dffc 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -2505,6 +2505,19 @@ Percorso installato: Interpola tra i fotogrammi renderizzati con Lossless Scaling. Movimento più fluido, ma un fotogramma in più di latenza di input. Sale verso l\'obiettivo solo finché aiuta in modo misurabile, invece di mantenere un moltiplicatore fisso. Installa Lossless Scaling nelle impostazioni del contenitore per usare la generazione fotogrammi. + Preset qualità + Ultra prestazioni + Prestazioni + Bilanciato + Qualità + Ultra + Prest. + Bilanciato + Qualità + Il movimento è stimato al 40% della risoluzione. Circa il 40% del costo GPU di Qualità. I dettagli fini si attenuano nei movimenti rapidi. + Il movimento è stimato al 50% della risoluzione. Circa il 46% del costo GPU di Qualità, quasi senza perdita visibile. + Il movimento è stimato al 70% della risoluzione. Circa il 61% del costo GPU di Qualità. Consigliato per i portatili. + Il movimento è stimato a piena risoluzione. Costo massimo; usalo solo se il gioco ha margine sulla GPU. Generazione fotogrammi Ricerca di Lossless Scaling… Shader di Lossless Scaling pronti. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 68adaadde..2582ea563 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -2504,6 +2504,19 @@ Lossless Scaling でレンダリング済みフレーム間を補間します。動きは滑らかになりますが、入力遅延が 1 フレーム増えます。 固定倍率を保つのではなく、効果が測定できる間だけ目標に向けて上げていきます。 フレーム生成を使用するには、コンテナ設定で Lossless Scaling をインストールしてください。 + 画質プリセット + ウルトラパフォーマンス + パフォーマンス + バランス + 画質 + ウルトラ + 性能 + バランス + 画質 + 動きを解像度の40%で推定します。「画質」のGPU負荷の約40%です。速い動きでは細部が甘くなります。 + 動きを解像度の50%で推定します。「画質」のGPU負荷の約46%で、見た目の劣化はほとんどありません。 + 動きを解像度の70%で推定します。「画質」のGPU負荷の約61%です。携帯機に推奨されます。 + 動きをフル解像度で推定します。負荷は最大です。GPUに余裕がある場合のみ使用してください。 フレーム生成 Lossless Scaling を検索中… Lossless Scaling のシェーダーを準備しました。 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 2f8ffdfcc..c5fa8c365 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -2506,6 +2506,19 @@ Lossless Scaling으로 렌더링된 프레임 사이를 보간합니다. 움직임은 부드러워지지만 입력 지연이 한 프레임 늘어납니다. 고정 배수를 유지하는 대신, 측정 가능한 이득이 있을 때만 목표를 향해 올립니다. 프레임 생성을 사용하려면 컨테이너 설정에서 Lossless Scaling을 설치하세요. + 품질 프리셋 + 울트라 성능 + 성능 + 균형 + 품질 + 울트라 + 성능 + 균형 + 품질 + 해상도의 40%에서 움직임을 추정합니다. 품질 대비 약 40%의 GPU 부하입니다. 빠른 움직임에서 미세한 디테일이 부드러워집니다. + 해상도의 50%에서 움직임을 추정합니다. 품질 대비 약 46%의 GPU 부하이며, 눈에 띄는 손실이 거의 없습니다. + 해상도의 70%에서 움직임을 추정합니다. 품질 대비 약 61%의 GPU 부하입니다. 휴대용 기기에 권장됩니다. + 전체 해상도에서 움직임을 추정합니다. 부하가 가장 큽니다. GPU 여유가 있을 때만 사용하세요. 프레임 생성 Lossless Scaling 찾는 중… Lossless Scaling 셰이더가 준비되었습니다. diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index f45cca80b..7a9e3d705 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -2504,6 +2504,19 @@ Installert bane: Interpolerer mellom gjengitte bilder med Lossless Scaling. Jevnere bevegelse, men ett ekstra bilde med inndataforsinkelse. Stiger mot målet bare så lenge det hjelper målbart, i stedet for å holde en fast multiplikator. Installer Lossless Scaling i containerinnstillingene for å bruke bildegenerering. + Kvalitetsforhåndsinnstilling + Ultra ytelse + Ytelse + Balansert + Kvalitet + Ultra + Ytelse + Balansert + Kvalitet + Bevegelse beregnes ved 40 % oppløsning. Omtrent 40 % av GPU-kostnaden til Kvalitet. Fine detaljer mykes opp ved rask bevegelse. + Bevegelse beregnes ved 50 % oppløsning. Omtrent 46 % av GPU-kostnaden til Kvalitet, med nesten ingen synlig forringelse. + Bevegelse beregnes ved 70 % oppløsning. Omtrent 61 % av GPU-kostnaden til Kvalitet. Anbefales for håndholdte. + Bevegelse beregnes ved full oppløsning. Høyest kostnad; bruk den bare når spillet har GPU-kapasitet til overs. Bildegenerering Leter etter Lossless Scaling… Lossless Scaling-shadere klare. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 9450db0bc..38887fdbf 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -2511,6 +2511,19 @@ Zainstalowana ścieżka: Interpoluje między wyrenderowanymi klatkami przy użyciu Lossless Scaling. Płynniejszy ruch, ale jedna dodatkowa klatka opóźnienia sterowania. Zwiększa się w kierunku celu tylko wtedy, gdy przynosi to mierzalną korzyść, zamiast utrzymywać stały mnożnik. Zainstaluj Lossless Scaling w ustawieniach kontenera, aby korzystać z generowania klatek. + Ustawienie jakości + Ultra wydajność + Wydajność + Zrównoważony + Jakość + Ultra + Wydajność + Zrówn. + Jakość + Ruch szacowany przy 40% rozdzielczości. Około 40% obciążenia GPU trybu Jakość. Drobne detale miękną przy szybkim ruchu. + Ruch szacowany przy 50% rozdzielczości. Około 46% obciążenia GPU trybu Jakość, praktycznie bez widocznej straty. + Ruch szacowany przy 70% rozdzielczości. Około 61% obciążenia GPU trybu Jakość. Zalecane dla urządzeń przenośnych. + Ruch szacowany w pełnej rozdzielczości. Najwyższe obciążenie; używaj tylko, gdy gra ma zapas mocy GPU. Generowanie klatek Szukanie Lossless Scaling… Shadery Lossless Scaling gotowe. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index e42b0460f..f75af85cb 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -2505,6 +2505,19 @@ Caminho instalado: Interpola entre quadros renderizados com o Lossless Scaling. Movimento mais suave, mas um quadro extra de latência de entrada. Sobe em direção ao alvo apenas enquanto ajudar de forma mensurável, em vez de manter um multiplicador fixo. Instale o Lossless Scaling nas configurações do contêiner para usar a geração de quadros. + Predefinição de qualidade + Ultra desempenho + Desempenho + Equilibrado + Qualidade + Ultra + Desemp. + Equilibrado + Qualidade + O movimento é estimado a 40% da resolução. Cerca de 40% do custo de GPU de Qualidade. Os detalhes finos ficam mais suaves em movimentos rápidos. + O movimento é estimado a 50% da resolução. Cerca de 46% do custo de GPU de Qualidade, quase sem perda visível. + O movimento é estimado a 70% da resolução. Cerca de 61% do custo de GPU de Qualidade. Recomendado para portáteis. + O movimento é estimado na resolução total. Custo máximo; use apenas quando o jogo tiver folga de GPU. Geração de quadros Procurando o Lossless Scaling… Shaders do Lossless Scaling prontos. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index e768ad75d..bab8c7c38 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -2504,6 +2504,19 @@ Caminho instalado: Interpola entre fotogramas renderizados com o Lossless Scaling. Movimento mais suave, mas um fotograma extra de latência de entrada. Sobe em direção ao alvo apenas enquanto ajudar de forma mensurável, em vez de manter um multiplicador fixo. Instale o Lossless Scaling nas definições do contentor para usar a geração de fotogramas. + Predefinição de qualidade + Ultra desempenho + Desempenho + Equilibrado + Qualidade + Ultra + Desemp. + Equilibrado + Qualidade + O movimento é estimado a 40 % da resolução. Cerca de 40 % do custo de GPU de Qualidade. Os detalhes finos suavizam-se em movimentos rápidos. + O movimento é estimado a 50 % da resolução. Cerca de 46 % do custo de GPU de Qualidade, quase sem perda visível. + O movimento é estimado a 70 % da resolução. Cerca de 61 % do custo de GPU de Qualidade. Recomendado para portáteis. + O movimento é estimado na resolução total. Custo máximo; use apenas quando o jogo tiver margem de GPU. Geração de fotogramas A procurar o Lossless Scaling… Shaders do Lossless Scaling prontos. diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index fee8beb66..7903cb3bf 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -2504,6 +2504,19 @@ Cale instalata: Interpolează între cadrele randate cu Lossless Scaling. Mișcare mai fluidă, dar un cadru suplimentar de latență la intrare. Urcă spre țintă doar cât timp ajută în mod măsurabil, în loc să păstreze un multiplicator fix. Instalează Lossless Scaling din setările containerului pentru a folosi generarea de cadre. + Presetare de calitate + Ultra performanță + Performanță + Echilibrat + Calitate + Ultra + Perf. + Echilibrat + Calitate + Mișcarea este estimată la 40% din rezoluție. Aproximativ 40% din costul GPU al modului Calitate. Detaliile fine se estompează la mișcări rapide. + Mișcarea este estimată la 50% din rezoluție. Aproximativ 46% din costul GPU al modului Calitate, aproape fără pierderi vizibile. + Mișcarea este estimată la 70% din rezoluție. Aproximativ 61% din costul GPU al modului Calitate. Recomandat pentru dispozitive portabile. + Mișcarea este estimată la rezoluție completă. Cost maxim; folosiți-l doar când jocul are resurse GPU disponibile. Generare de cadre Se caută Lossless Scaling… Shaderele Lossless Scaling sunt gata. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 0f6e9d725..43f10d438 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -2411,6 +2411,19 @@ Интерполирует между отрисованными кадрами с помощью Lossless Scaling. Движение плавнее, но задержка ввода увеличивается на один кадр. Повышается к цели только пока это даёт измеримый выигрыш, вместо удержания фиксированного множителя. Установите Lossless Scaling в настройках контейнера, чтобы использовать генерацию кадров. + Пресет качества + Ультрапроизводительность + Производительность + Сбалансированный + Качество + Ультра + Произв. + Баланс + Качество + Движение вычисляется при 40 % разрешения. Около 40 % нагрузки на GPU режима «Качество». Мелкие детали смазываются при быстром движении. + Движение вычисляется при 50 % разрешения. Около 46 % нагрузки на GPU режима «Качество», почти без заметных потерь. + Движение вычисляется при 70 % разрешения. Около 61 % нагрузки на GPU режима «Качество». Рекомендуется для портативных устройств. + Движение вычисляется при полном разрешении. Максимальная нагрузка; используйте, только когда у игры есть запас GPU. Генерация кадров Поиск Lossless Scaling… Шейдеры Lossless Scaling готовы. diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 44cb4e995..847c5880f 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -2504,6 +2504,19 @@ Installerad sökväg: Interpolerar mellan renderade bildrutor med Lossless Scaling. Mjukare rörelse, men en extra bildruta med inmatningsfördröjning. Stiger mot målet endast så länge det hjälper mätbart, i stället för att hålla en fast multiplikator. Installera Lossless Scaling i containerinställningarna för att använda bildgenerering. + Kvalitetsförval + Ultraprestanda + Prestanda + Balanserad + Kvalitet + Ultra + Prestanda + Balanserad + Kvalitet + Rörelse beräknas vid 40 % upplösning. Cirka 40 % av GPU-kostnaden för Kvalitet. Fina detaljer mjukas upp vid snabb rörelse. + Rörelse beräknas vid 50 % upplösning. Cirka 46 % av GPU-kostnaden för Kvalitet, med nästan ingen synlig förlust. + Rörelse beräknas vid 70 % upplösning. Cirka 61 % av GPU-kostnaden för Kvalitet. Rekommenderas för handhållna. + Rörelse beräknas vid full upplösning. Högst kostnad; använd endast när spelet har GPU-marginal. Bildgenerering Söker efter Lossless Scaling… Lossless Scaling-shaders klara. diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index b8e863c2b..e87128a62 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -2504,6 +2504,19 @@ แทรกเฟรมระหว่างเฟรมที่เรนเดอร์ด้วย Lossless Scaling การเคลื่อนไหวลื่นขึ้น แต่มีความหน่วงของอินพุตเพิ่มขึ้นหนึ่งเฟรม จะไต่ขึ้นสู่เป้าหมายเฉพาะเมื่อวัดผลได้ว่าช่วยจริง แทนที่จะคงตัวคูณไว้คงที่ ติดตั้ง Lossless Scaling ในการตั้งค่าคอนเทนเนอร์เพื่อใช้การสร้างเฟรม + ค่าที่ตั้งไว้ของคุณภาพ + ประสิทธิภาพสูงสุด + ประสิทธิภาพ + สมดุล + คุณภาพ + อัลตรา + ประสิทธิภาพ + สมดุล + คุณภาพ + ประมาณการเคลื่อนไหวที่ความละเอียด 40% ใช้ GPU ประมาณ 40% ของโหมดคุณภาพ รายละเอียดเล็ก ๆ จะนุ่มลงเมื่อเคลื่อนไหวเร็ว + ประมาณการเคลื่อนไหวที่ความละเอียด 50% ใช้ GPU ประมาณ 46% ของโหมดคุณภาพ แทบไม่เห็นความแตกต่าง + ประมาณการเคลื่อนไหวที่ความละเอียด 70% ใช้ GPU ประมาณ 61% ของโหมดคุณภาพ แนะนำสำหรับเครื่องพกพา + ประมาณการเคลื่อนไหวที่ความละเอียดเต็ม ใช้ GPU สูงสุด ใช้เมื่อเกมมี GPU เหลือเท่านั้น การสร้างเฟรม กำลังค้นหา Lossless Scaling… เชเดอร์ของ Lossless Scaling พร้อมแล้ว diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index da5cbad68..122f73c98 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -2504,6 +2504,19 @@ Yüklü konum: Lossless Scaling ile işlenmiş kareler arasında ara kare üretir. Daha akıcı hareket, ancak bir kare fazladan giriş gecikmesi. Sabit bir çarpanı korumak yerine, yalnızca ölçülebilir fayda sağladığı sürece hedefe doğru yükselir. Kare üretimini kullanmak için kapsayıcı ayarlarından Lossless Scaling\'i yükleyin. + Kalite ön ayarı + Ultra performans + Performans + Dengeli + Kalite + Ultra + Perf. + Dengeli + Kalite + Hareket %40 çözünürlükte hesaplanır. Kalite modunun GPU maliyetinin yaklaşık %40\'ı. Hızlı harekette ince ayrıntılar yumuşar. + Hareket %50 çözünürlükte hesaplanır. Kalite modunun GPU maliyetinin yaklaşık %46\'sı, neredeyse görünür kayıp olmadan. + Hareket %70 çözünürlükte hesaplanır. Kalite modunun GPU maliyetinin yaklaşık %61\'i. El konsolları için önerilir. + Hareket tam çözünürlükte hesaplanır. En yüksek maliyet; yalnızca oyunun GPU payı varken kullanın. Kare üretimi Lossless Scaling aranıyor… Lossless Scaling gölgelendiricileri hazır. diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index b355258d7..eecbaa1b5 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -2511,6 +2511,19 @@ Інтерполює між відрендереними кадрами за допомогою Lossless Scaling. Рух плавніший, але затримка вводу зростає на один кадр. Підвищується до цілі лише доки це дає вимірний виграш, замість утримання фіксованого множника. Установіть Lossless Scaling у налаштуваннях контейнера, щоб використовувати генерацію кадрів. + Пресет якості + Ультрапродуктивність + Продуктивність + Збалансований + Якість + Ультра + Продукт. + Баланс + Якість + Рух обчислюється за 40 % роздільної здатності. Близько 40 % навантаження на GPU режиму «Якість». Дрібні деталі розмиваються під час швидкого руху. + Рух обчислюється за 50 % роздільної здатності. Близько 46 % навантаження на GPU режиму «Якість», майже без помітних втрат. + Рух обчислюється за 70 % роздільної здатності. Близько 61 % навантаження на GPU режиму «Якість». Рекомендовано для портативних пристроїв. + Рух обчислюється за повної роздільної здатності. Найвище навантаження; використовуйте, лише коли гра має запас GPU. Генерація кадрів Пошук Lossless Scaling… Шейдери Lossless Scaling готові. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ec8156a5c..7d9635505 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -2505,6 +2505,19 @@ 使用 Lossless Scaling 在已渲染的帧之间插帧。画面更流畅,但输入延迟会增加一帧。 仅在能带来可测量的提升时才向目标提升,而不是保持固定倍数。 请在容器设置中安装 Lossless Scaling 以使用帧生成。 + 画质预设 + 极致性能 + 性能 + 均衡 + 画质 + 极致 + 性能 + 均衡 + 画质 + 以 40% 分辨率估算运动。约为“画质”GPU 开销的 40%。快速运动时细节会变柔和。 + 以 50% 分辨率估算运动。约为“画质”GPU 开销的 46%,几乎没有可见损失。 + 以 70% 分辨率估算运动。约为“画质”GPU 开销的 61%。推荐用于掌机。 + 以完整分辨率估算运动。开销最高;仅在游戏有 GPU 余量时使用。 帧生成 正在查找 Lossless Scaling… Lossless Scaling 着色器已就绪。 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 3788d2236..d3c2772ae 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -2504,6 +2504,19 @@ 使用 Lossless Scaling 在已算繪的影格之間插補。畫面更流暢,但輸入延遲會增加一個影格。 僅在能帶來可測量的提升時才朝目標提升,而不是維持固定倍數。 請在容器設定中安裝 Lossless Scaling 以使用影格生成。 + 畫質預設 + 極致效能 + 效能 + 均衡 + 畫質 + 極致 + 效能 + 均衡 + 畫質 + 以 40% 解析度估算移動。約為「畫質」GPU 負載的 40%。快速移動時細節會變柔和。 + 以 50% 解析度估算移動。約為「畫質」GPU 負載的 46%,幾乎沒有可見損失。 + 以 70% 解析度估算移動。約為「畫質」GPU 負載的 61%。建議用於掌機。 + 以完整解析度估算移動。負載最高;僅在遊戲有 GPU 餘裕時使用。 影格生成 正在尋找 Lossless Scaling… Lossless Scaling 著色器已就緒。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7e4195d92..d6d39597d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -785,7 +785,6 @@ E.g. META for META key, \n Motion estimated at 50% resolution. Around 46% of Quality\'s GPU cost, with almost no visible loss. Motion estimated at 70% resolution. Around 61% of Quality\'s GPU cost. Recommended for handhelds. Motion estimated at full resolution. Highest cost; use it only when the game has GPU headroom to spare. - Sets how much GPU work motion estimation does. It never changes the multiplier — 2x stays 2x, 4x stays 4x. Interpolates between rendered frames with Lossless Scaling. Smoother motion, but one extra frame of input latency. Climbs toward the target only while it measurably helps, instead of holding a fixed multiplier. Install Lossless Scaling in container settings to use frame generation. From d622725f098780225040a26cde0254b56b5e01a4 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Wed, 26 Aug 2026 15:50:00 -0400 Subject: [PATCH 31/35] Frame generation: fix swapchain writes degrading over a session Writing generated frames straight into the swapchain introduced two defects that were active on every frame. The generated image's acquire semaphore was still waited on at the transfer stage, which was correct while a blit produced the image but not once a compute dispatch does. A transfer-stage wait does not block compute work, so generate could begin writing a swapchain image before the presentation engine released it. It now waits at the compute stage on the storage path. The generate target was keyed by generation index, so its view changed every frame as swapchain images rotated. SetTarget only skips work when the view matches, so it rewrote both descriptor sets every frame, including the one the previous frame was still executing against; the frame fence only waits on its own slot and two frames are in flight. Updating an in-use descriptor set is undefined and some drivers absorb it with an implicit stall. Targets are now keyed by swapchain image index, giving each image a stable view so the write happens once per image. A swapchain can hand back more images than there are target slots, so that case keeps the composite path. Three robustness gaps around the same path are fixed as well. A failed present of a generated frame logged unconditionally from inside the per-generation loop, the only unbounded log in the frame path; at 4x and 120Hz a persistent failure meant several hundred writes per second for as long as it lasted. It is now rate limited like every neighbouring warning. Only the main present result drove swapchain recreation, so a generated present reporting out of date was logged and otherwise ignored and could never heal, which is what kept the log above running. It now feeds the recreate condition. A failed submit returned without presenting the images already acquired for this frame and left their acquire semaphores signalled, permanently dropping images from circulation and leaving the next acquire on those semaphores invalid. Frame semaphores outlive the swapchain, so recreation alone did not clear them; the path now drains the device, rebuilds the frame's acquire semaphores and recreates the swapchain. --- app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h | 1 + app/src/main/cpp/winlator/vk/vk_renderer.c | 38 +++++++++++++++++--- app/src/main/cpp/winlator/vk/vk_state.h | 1 + 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h index 0c51abb73..50a5a7e15 100644 --- a/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h +++ b/app/src/main/cpp/winlator/vk/lsfg/vkr_lsfg.h @@ -10,6 +10,7 @@ extern "C" { #endif #define VKR_LSFG_MAX_GENERATIONS 3u +#define VKR_LSFG_MAX_TARGETS 7u typedef struct VkrLsfg VkrLsfg; diff --git a/app/src/main/cpp/winlator/vk/vk_renderer.c b/app/src/main/cpp/winlator/vk/vk_renderer.c index 214cddb9a..f689bf614 100644 --- a/app/src/main/cpp/winlator/vk/vk_renderer.c +++ b/app/src/main/cpp/winlator/vk/vk_renderer.c @@ -1359,6 +1359,11 @@ static bool create_swapchain(VkRenderer* r, uint32_t fallback_width, uint32_t fa goto fail; } r->swapchain_image_count = got; + if (r->swapchain_storage && got > VKR_LSFG_MAX_TARGETS) { + r->swapchain_storage = false; + VK_LOGI("Swapchain has %u images, more than the %u frame generation targets; " + "keeping the composite path", got, VKR_LSFG_MAX_TARGETS); + } r->framegen_supported = transfer_dst_capable && composite_format_supported(r); VK_LOGI("Swapchain images requested=%u actual=%u caps.min=%u caps.max=%u framegen_extra=%u", image_count, got, caps.minImageCount, caps.maxImageCount, framegen_extra_images(r)); @@ -2732,7 +2737,7 @@ static bool record_and_submit_frame(VkRenderer* r) { for (uint32_t g = 0; g < gen_count; g++) { const uint32_t idx = gen_image_index[g]; if (r->swapchain_storage) { - vkr_lsfg_generate_into(r->lsfg, f->cmd, g, VK_FRAMES_IN_FLIGHT + g, + vkr_lsfg_generate_into(r->lsfg, f->cmd, g, idx, r->swapchain_images[idx], r->swapchain_views[idx], r->swapchain_extent.width, r->swapchain_extent.height); @@ -2864,7 +2869,9 @@ static bool record_and_submit_frame(VkRenderer* r) { for (uint32_t g = 0; g < gen_count; g++) { wait_sems[wait_count] = f->image_available_gen[g]; - wait_stages[wait_count] = VK_PIPELINE_STAGE_TRANSFER_BIT; + wait_stages[wait_count] = r->swapchain_storage + ? VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT + : VK_PIPELINE_STAGE_TRANSFER_BIT; wait_count++; signal_sems[signal_count++] = r->swapchain_render_finished[gen_image_index[g]]; } @@ -2899,6 +2906,23 @@ static bool record_and_submit_frame(VkRenderer* r) { f->in_flight = VK_NULL_HANDLE; VK_LOGE("Failed to recreate frame fence after submit failure"); } + vkDeviceWaitIdle(r->device); + VkSemaphoreCreateInfo asi = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; + if (f->image_available) { + vkDestroySemaphore(r->device, f->image_available, NULL); + f->image_available = VK_NULL_HANDLE; + vkCreateSemaphore(r->device, &asi, NULL, &f->image_available); + } + for (uint32_t g = 0; g < VKR_LSFG_MAX_GENERATIONS; g++) { + if (!f->image_available_gen[g]) continue; + vkDestroySemaphore(r->device, f->image_available_gen[g], NULL); + f->image_available_gen[g] = VK_NULL_HANDLE; + vkCreateSemaphore(r->device, &asi, NULL, &f->image_available_gen[g]); + } + r->surface_ready = false; + destroy_swapchain_resources(r); + r->surface_ready = create_swapchain(r, r->surface_extent.width, + r->surface_extent.height); pthread_mutex_unlock(&r->render_mutex); return false; } @@ -2910,6 +2934,7 @@ static bool record_and_submit_frame(VkRenderer* r) { pi.pSwapchains = &r->swapchain; pi.pImageIndices = &image_index; + bool gen_present_out_of_date = false; pthread_mutex_lock(&r->queue_mutex); for (uint32_t g = 0; g < gen_count; g++) { VkPresentInfoKHR gpi = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR}; @@ -2920,7 +2945,11 @@ static bool record_and_submit_frame(VkRenderer* r) { gpi.pImageIndices = &gen_image_index[g]; VkResult gpr = vkQueuePresentKHR(r->graphics_queue, &gpi); if (gpr != VK_SUCCESS && gpr != VK_SUBOPTIMAL_KHR) { - VK_LOGW("generated frame present failed (%d)", gpr); + if (gpr == VK_ERROR_OUT_OF_DATE_KHR) gen_present_out_of_date = true; + if (r->framegen_present_failures++ % 120 == 0) { + VK_LOGW("generated frame present failed (%d, failures=%llu)", gpr, + (unsigned long long)r->framegen_present_failures); + } } else { __atomic_fetch_add(&r->presented_frames, 1, __ATOMIC_RELAXED); } @@ -2946,7 +2975,8 @@ static bool record_and_submit_frame(VkRenderer* r) { pthread_mutex_unlock(&r->queue_mutex); bool present_suboptimal = (pr == VK_SUBOPTIMAL_KHR) && !r->ignore_suboptimal; - if (recreate_after_present || pr == VK_ERROR_OUT_OF_DATE_KHR || present_suboptimal) { + if (recreate_after_present || pr == VK_ERROR_OUT_OF_DATE_KHR || present_suboptimal + || gen_present_out_of_date) { r->surface_ready = false; pthread_mutex_lock(&r->queue_mutex); vkQueueWaitIdle(r->graphics_queue); diff --git a/app/src/main/cpp/winlator/vk/vk_state.h b/app/src/main/cpp/winlator/vk/vk_state.h index 68255cf0d..5ace43aaf 100644 --- a/app/src/main/cpp/winlator/vk/vk_state.h +++ b/app/src/main/cpp/winlator/vk/vk_state.h @@ -419,6 +419,7 @@ typedef struct VkRenderer { bool framegen_requested; bool swapchain_transfer_dst; bool swapchain_storage; + uint64_t framegen_present_failures; struct VkrLsfg* lsfg; char* lsfg_cache_path; uint32_t framegen_multiplier; From c5b67745ca7c67ba2d71005739702b603b8f55cd Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Wed, 26 Aug 2026 15:50:07 -0400 Subject: [PATCH 32/35] Rotate the captured logcat files Both captures ran logcat -f with no -r or -n, so each file grew unbounded for the whole session while being written continuously. The full capture takes everything at debug level, which is a lot of steady I/O on a long session. Caps the full capture at 16MB across 4 files and the app log at 8MB across 2. Both remain off unless one of the debug logging preferences is enabled. --- app/src/main/runtime/system/LogManager.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/runtime/system/LogManager.kt b/app/src/main/runtime/system/LogManager.kt index 4d65d8d7a..4b404187c 100644 --- a/app/src/main/runtime/system/LogManager.kt +++ b/app/src/main/runtime/system/LogManager.kt @@ -59,7 +59,7 @@ object LogManager { runBlockingLogcatCommand(arrayOf("logcat", "-c")) logcatProcess = Runtime.getRuntime().exec( - arrayOf("logcat", "-f", logFile.absolutePath, "*:D"), + arrayOf("logcat", "-f", logFile.absolutePath, "-r", "16384", "-n", "4", "*:D"), ) closeProcessStdin(logcatProcess) } catch (e: Exception) { @@ -104,7 +104,7 @@ object LogManager { val pid = android.os.Process.myPid() appLogProcess = Runtime.getRuntime().exec( - arrayOf("logcat", "-f", logFile.absolutePath, "--pid=$pid", "*:W"), + arrayOf("logcat", "-f", logFile.absolutePath, "-r", "8192", "-n", "2", "--pid=$pid", "*:W"), ) closeProcessStdin(appLogProcess) Log.i(TAG, "Application debug logging started (PID=$pid)") From f439adc84ec8574859218b016a19d6bdd1044330 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Thu, 27 Aug 2026 00:49:13 -0400 Subject: [PATCH 33/35] Label the frame generation tab FPS The tab read FG, which says nothing to anyone who has not already read the pane. FPS is what the pane is about from the user's side: frame generation sits above the FPS limiter, and both decide the frame rate. Set in every locale rather than only the default. Eight had localised the abbreviation into words, but this follows how the rail already treats HUD, which stays untranslated everywhere including the non-Latin locales. FPS is standard in gaming interfaces across all of them, and localising it would lose the frames-per-second reading the label exists for. The section heading inside the pane keeps its full name, as does the item title behind the tab's accessibility description, where the longer wording is worth more than an initialism. --- app/src/main/res/values-b+es+419/strings.xml | 2 +- app/src/main/res/values-da/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-fi/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-hi/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-ja/strings.xml | 2 +- app/src/main/res/values-ko/strings.xml | 2 +- app/src/main/res/values-no/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-pt/strings.xml | 2 +- app/src/main/res/values-ro/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sv/strings.xml | 2 +- app/src/main/res/values-th/strings.xml | 2 +- app/src/main/res/values-tr/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values-zh-rCN/strings.xml | 2 +- app/src/main/res/values-zh-rTW/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 5c2dfbbe7..c0818f754 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -834,7 +834,7 @@ Por ejemplo, META para la tecla META, \n Restablecer efectos Calibración avanzada HUD - FG + FPS Giro FX Salida diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index afee73321..3da6d50a4 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -1138,7 +1138,7 @@ Installeret sti: CRT-effekt Avanceret kalibrering HUD - FG + FPS Gyro FX Mere diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 7c4d6195a..970c586c9 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1138,7 +1138,7 @@ Installierter Pfad: CRT-Effekt Erweiterte Kalibrierung HUD - FG + FPS Gyro FX Mehr diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3cb44fa04..028f54045 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1138,7 +1138,7 @@ Ruta instalada: Efecto CRT Calibración avanzada HUD - FG + FPS Giro FX Más diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 3241f5e8c..f530e024b 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -834,7 +834,7 @@ E.g. META for META-näppäin, \n Palauta tehosteet Lisäkalibrointi HUD - FG + FPS Gyro FX Ulostulo diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 79bb816b7..69cc737aa 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1138,7 +1138,7 @@ Chemin installé : Effet CRT Étalonnage avancé HUD - FG + FPS Gyro FX Plus diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 8bd5687d2..d873c5a80 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -729,7 +729,7 @@ CRT प्रभाव उन्नत कैलिब्रेशन HUD - फ़्रेम + FPS Gyro FX और diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 4bed5dffc..7335f89e1 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1138,7 +1138,7 @@ Percorso installato: Effetto CRT Calibrazione avanzata HUD - FG + FPS Giro FX Altro diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 2582ea563..38b8f740c 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -834,7 +834,7 @@ エフェクトをリセット 詳細キャリブレーション HUD - フレーム生成 + FPS ジャイロ エフェクト 出力 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index c5fa8c365..df5f89d76 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -1138,7 +1138,7 @@ CRT 효과 고급 보정 HUD - 프레임 생성 + FPS 자이로 FX 더 보기 diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index 7a9e3d705..73d798d94 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -834,7 +834,7 @@ F.eks. META for META-tast, \n Nullstill effekter Avansert kalibrering HUD - FG + FPS Gyro FX Utgang diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 38887fdbf..eb2b56c59 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1144,7 +1144,7 @@ Zainstalowana ścieżka: Efekt CRT Zaawansowana kalibracja HUD - FG + FPS Gyro FX Więcej diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index f75af85cb..86eb171bb 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1138,7 +1138,7 @@ Caminho instalado: Efeito CRT Calibração avançada HUD - FG + FPS Giro FX Mais diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index bab8c7c38..33400329e 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -834,7 +834,7 @@ Por ex. META para tecla META, \n Repor efeitos Calibração avançada HUD - FG + FPS Giro FX Saída diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 7903cb3bf..dea00f05a 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -1138,7 +1138,7 @@ Cale instalata: Efect CRT Calibrare avansată HUD - FG + FPS Giro FX Mai multe diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 43f10d438..f58f27838 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -681,7 +681,7 @@ ЭЛТ-эффект Расширенная калибровка HUD - Кадры + FPS Гироскоп Эффекты Еще diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 847c5880f..8d577ff11 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -834,7 +834,7 @@ T.ex. META för META-tangent, \n Återställ effekter Avancerad kalibrering HUD - FG + FPS Gyro FX Utgång diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index e87128a62..e9844af57 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -834,7 +834,7 @@ รีเซ็ตเอฟเฟกต์ การปรับเทียบขั้นสูง HUD - สร้างเฟรม + FPS ไจโร เอฟเฟกต์ เอาต์พุต diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 122f73c98..e808023a1 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -834,7 +834,7 @@ E.g. META için META tuşu, \n Efektleri sıfırla Gelişmiş kalibrasyon HUD - FG + FPS Jiro FX Çıkış diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index eecbaa1b5..5a2bb008a 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1144,7 +1144,7 @@ Ефект CRT Розширене калібрування HUD - Кадри + FPS Гіро FX Більше diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 7d9635505..ad9b6efaf 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1138,7 +1138,7 @@ CRT 效果 高级校准 HUD - 帧生成 + FPS 陀螺仪 FX 更多 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index d3c2772ae..87edc1a40 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1138,7 +1138,7 @@ CRT 效果 進階校準 HUD - 影格生成 + FPS 陀螺儀 FX 更多 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d6d39597d..d3d53a37a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -905,7 +905,7 @@ E.g. META for META key, \n Reset effects Advanced calibration HUD - FG + FPS Gyro FX Output From d916523db7aefc8d97b537165bf1081569af5604 Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Thu, 27 Aug 2026 06:49:10 -0400 Subject: [PATCH 34/35] Credit Eden and lsfg-vk for the frame generation chain Frame generation is derived from the Eden Emulator Project's Vulkan port, which is itself derived from lsfg-vk. Both were only acknowledged in a single README line, and neither appeared in the in-app credits at all despite their copyright headers sitting in most of the frame generation sources. Adds both to the credits screen, and gives the README a section setting out what actually came from where, file by file: the chain layout and the pyramid stages, the Vulkan resource and barrier helpers, the pacer and shader module loading. Getting the descriptor layouts, barrier placement and dispatch geometry of a 25-shader chain right is the part that takes the debugging, and Eden had already done it. EMULATOR_CREDITS.md gains a frame generation section, which it was missing entirely. Uses Eden's own git host rather than the GitHub mirror the README had been pointing at. --- EMULATOR_CREDITS.md | 23 +++++++++++ README.md | 39 ++++++++++++++++++- .../main/feature/retro/RetroCreditsScreen.kt | 2 + 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/EMULATOR_CREDITS.md b/EMULATOR_CREDITS.md index 5327d1b5b..ed473708c 100644 --- a/EMULATOR_CREDITS.md +++ b/EMULATOR_CREDITS.md @@ -48,6 +48,29 @@ Each core is shipped as an unmodified `arm64-v8a` build and loaded through Libre | --- | --- | --- | | SwanStation | GPL-3.0 | https://github.com/libretro/swanstation | +## Frame generation + +Frame generation is a port of the Lossless Scaling compute chain to Vulkan. WinNative did not +port it from scratch: it derives from the **Eden Emulator Project**'s port, which in turn derives +from **lsfg-vk**. Both are GPL-3.0-or-later, and both copyright notices are preserved in the +header of every file that carries their work. + +| Component | Role | License | Source | +| --- | --- | --- | --- | +| Eden Emulator Project | The Vulkan frame generation chain WinNative's port is derived from | GPL-3.0-or-later | https://git.eden-emu.dev/eden-emu/eden | +| lsfg-vk | The original Vulkan reimplementation, which Eden's port derives from | GPL-3.0-or-later | https://github.com/PancakeTAS/lsfg-vk | +| DXVK (`dxbc`) | Shader translator, used when only DXBC shaders are available | zlib/libpng | https://github.com/doitsujin/dxvk | + +The chain layout, the pyramid stages (`lsfg_mipmaps`, `lsfg_alpha`, `lsfg_beta`, `lsfg_gamma`, +`lsfg_delta`, `lsfg_generate`), the Vulkan resource and barrier helpers (`lsfg_common`), the +generation pacer (`lsfg_pacer`) and shader module loading (`lsfg_shaders`) all come from that +lineage. WinNative's own additions are shader extraction from an installed copy of Lossless +Scaling (`lsfg_dll`), DXBC translation (`lsfg_dxbc`), the JNI surface (`lsfg_jni`), driver +probing (`lsfg_probe`) and compositor integration (`vkr_lsfg`). + +The frame generation shaders themselves are **not** redistributed. They are read at runtime from +the user's own Lossless Scaling installation, which they must own separately on Steam. + ## Frontend, achievements, and supporting libraries | Component | Role | License | Source | diff --git a/README.md b/README.md index bb5fd7070..899b85c1d 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,43 @@ Please match the existing code style and ensure any AI-assisted code is thorough - **libretro / RetroArch** and the individual core authors, built from source: [FCEUmm](https://github.com/libretro/libretro-fceumm), [Snes9x](https://github.com/libretro/snes9x), [Gambatte](https://github.com/libretro/gambatte-libretro), [mGBA](https://github.com/libretro/mgba), [Genesis Plus GX](https://github.com/libretro/Genesis-Plus-GX), [Mupen64Plus-Next](https://github.com/libretro/mupen64plus-libretro-nx), [Beetle PSX](https://github.com/libretro/beetle-psx-libretro) - **ARMSX2** by the [ARMSX2](https://github.com/ARMSX2/ARMSX2) team (GPL-3.0) — the PlayStation 2 core, a fork of **[PCSX2](https://github.com/pcsx2/pcsx2)** (GPL-3.0), built from source into `libemucore`. PS2 online play uses PCSX2's DEV9 network adapter - **lsfg-vk** by [PancakeTAS](https://github.com/PancakeTAS/lsfg-vk) (GPL-3.0-or-later) — the original Vulkan reimplementation of the Lossless Scaling frame generation chain -- **Eden Emulator Project** by the [eden](https://github.com/eden-emu/eden) team (GPL-3.0-or-later) — the Android port of that chain, which WinNative's compute passes derive from +- **Eden Emulator Project** by the [eden](https://git.eden-emu.dev/eden-emu/eden) team (GPL-3.0-or-later) — the Vulkan port of that chain that WinNative's frame generation is derived from. See [Frame generation — what came from Eden](#frame-generation--what-came-from-eden) below - **DXVK** by [Philip Rebohle and contributors](https://github.com/doitsujin/dxvk) (zlib/libpng) — the `dxbc` shader translator, vendored at `app/src/main/cpp/thirdparty/dxbc` to convert the frame generation shaders to SPIR-V - **Lossless Scaling** (Steam) — the source of the frame generation shaders. They are read from the user's own installed copy at runtime; none are redistributed with WinNative + +#### Frame generation — what came from Eden + +WinNative's frame generation exists because the [Eden Emulator Project](https://git.eden-emu.dev/eden-emu/eden) +had already solved the hard part: getting the Lossless Scaling compute chain running correctly +on Vulkan, on mobile GPUs. The port here started from Eden's work and still carries it. Their +copyright notices are preserved in every file that derives from them, under GPL-3.0-or-later. + +Derived from Eden (jointly with **[lsfg-vk](https://github.com/PancakeTAS/lsfg-vk)**, which Eden +themselves ported from): + +| Source file | What it provides | +| --- | --- | +| `lsfg_chain.*` | The shape of the whole chain — which of the 25 shaders run, in what order, and what each stage feeds the next | +| `lsfg_mipmaps.*` | The flow pyramid the rest of the chain is built on | +| `lsfg_alpha.*` | Per-level feature extraction, including the batched-barrier dispatch pattern the rest of the chain follows | +| `lsfg_beta.*` | The coarse flow estimate the refinement stages start from | +| `lsfg_gamma.*` | Coarse-to-fine flow refinement, one instance per pyramid level | +| `lsfg_delta.*` | The extra refinement and detail passes on the finest levels | +| `lsfg_generate.*` | The final warp that produces the interpolated frame | +| `lsfg_common.*` | The Vulkan plumbing all of the above sit on — image, sampler and buffer wrappers, the barrier builder, the descriptor writer, and the pass/pipeline helper | + +Derived from Eden specifically: + +| Source file | What it provides | +| --- | --- | +| `lsfg_pacer.*` | Deciding how many frames to generate per real frame | +| `lsfg_shaders.*` | Turning the extracted shader blobs into Vulkan shader modules | + +Getting the descriptor layouts, barrier placement and dispatch geometry of a 25-shader chain +right is not something you arrive at by reading the shaders; it is the part that takes the +debugging. Eden did that work, and this port would not have been possible without it. + +What WinNative added on top is the Windows and Android side of it: reading the shader blobs out +of a user's own Lossless Scaling install (`lsfg_dll.*`), translating them when only DXBC is +available (`lsfg_dxbc.*`), the JNI surface (`lsfg_jni.*`), driver probing (`lsfg_probe.*`), and +wiring the chain into WinNative's compositor and swapchain (`vkr_lsfg.*`). diff --git a/app/src/main/feature/retro/RetroCreditsScreen.kt b/app/src/main/feature/retro/RetroCreditsScreen.kt index d9ab0aeeb..7260eeacd 100644 --- a/app/src/main/feature/retro/RetroCreditsScreen.kt +++ b/app/src/main/feature/retro/RetroCreditsScreen.kt @@ -115,10 +115,12 @@ internal val RETRO_CREDITS = RetroCredit("ARMSX2", "PlayStation 2", "GPL-3.0", "https://github.com/ARMSX2/ARMSX2"), RetroCredit("Beetle PSX", "PlayStation", "GPL-2.0", "https://github.com/libretro/beetle-psx-libretro"), RetroCredit("Dolphin", "GameCube / Wii", "GPL-2.0", "https://github.com/dolphin-emu/dolphin"), + RetroCredit("Eden Emulator Project", "Frame generation Vulkan chain", "GPL-3.0", "https://git.eden-emu.dev/eden-emu/eden"), RetroCredit("FCEUmm", "NES", "GPL-2.0", "https://github.com/libretro/libretro-fceumm"), RetroCredit("Gambatte", "Game Boy / Color", "GPL-2.0", "https://github.com/libretro/gambatte-libretro"), RetroCredit("Genesis Plus GX", "Genesis / SMS / GG", "GPX", "https://github.com/libretro/Genesis-Plus-GX"), RetroCredit("LibretroDroid", "libretro frontend", "GPL-3.0", "https://github.com/Swordfish90/LibretroDroid"), + RetroCredit("lsfg-vk", "Frame generation, upstream of Eden's port", "GPL-3.0", "https://github.com/PancakeTAS/lsfg-vk"), RetroCredit("mGBA", "Game Boy Advance", "MPL-2.0", "https://github.com/libretro/mgba"), RetroCredit("ParaLLEl N64", "Nintendo 64", "GPL-2.0", "https://github.com/libretro/parallel-n64"), RetroCredit("PCSX2", "PS2 upstream of ARMSX2", "GPL-3.0", "https://github.com/pcsx2/pcsx2"), From d3997bb470d3705c8b1982e085aa0dfb447aa80e Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Thu, 27 Aug 2026 06:49:23 -0400 Subject: [PATCH 35/35] Add a Support screen under a new Help section There was nowhere in the app pointing at the Discord servers, the subreddit or the YouTube channel, so anyone stuck had to go looking outside the app. Adds a Support pane listing the two Discord invites, the subreddit and the YouTube channel, each opening in the browser. It sits under its own Help section in the settings sidebar rather than being tacked onto Credits, so it reads the same way Tools reads above Debug. The brand marks are vector drawables carrying the app accent, matching the sidebar icons, rather than each shipping its own brand colour. Their arc commands are written with the flags separated: Android's path parser mis-reads SVG's compact form, where the two arc flags are run together with the coordinate that follows, and Discord's mark is drawn almost entirely from arcs. Section headers render the enum name directly, as the existing ones do, so Help needs no string of its own. The eleven strings the pane does use are translated across all locales; handles and channel names stay verbatim. --- .../feature/settings/nav/SettingsNavGraph.kt | 3 + .../settings/nav/SettingsNavSidebar.kt | 3 + .../feature/settings/support/SupportScreen.kt | 187 ++++++++++++++++++ .../main/res/drawable/ic_brand_discord.xml | 10 + app/src/main/res/drawable/ic_brand_reddit.xml | 10 + .../main/res/drawable/ic_brand_youtube.xml | 10 + app/src/main/res/values-b+es+419/strings.xml | 11 ++ app/src/main/res/values-da/strings.xml | 11 ++ app/src/main/res/values-de/strings.xml | 11 ++ app/src/main/res/values-es/strings.xml | 11 ++ app/src/main/res/values-fi/strings.xml | 11 ++ app/src/main/res/values-fr/strings.xml | 11 ++ app/src/main/res/values-hi/strings.xml | 11 ++ app/src/main/res/values-it/strings.xml | 11 ++ app/src/main/res/values-ja/strings.xml | 11 ++ app/src/main/res/values-ko/strings.xml | 11 ++ app/src/main/res/values-no/strings.xml | 11 ++ app/src/main/res/values-pl/strings.xml | 11 ++ app/src/main/res/values-pt-rBR/strings.xml | 11 ++ app/src/main/res/values-pt/strings.xml | 11 ++ app/src/main/res/values-ro/strings.xml | 11 ++ app/src/main/res/values-ru/strings.xml | 11 ++ app/src/main/res/values-sv/strings.xml | 11 ++ app/src/main/res/values-th/strings.xml | 11 ++ app/src/main/res/values-tr/strings.xml | 11 ++ app/src/main/res/values-uk/strings.xml | 11 ++ app/src/main/res/values-zh-rCN/strings.xml | 11 ++ app/src/main/res/values-zh-rTW/strings.xml | 11 ++ app/src/main/res/values/refs.xml | 1 + app/src/main/res/values/strings.xml | 11 ++ 30 files changed, 477 insertions(+) create mode 100644 app/src/main/feature/settings/support/SupportScreen.kt create mode 100644 app/src/main/res/drawable/ic_brand_discord.xml create mode 100644 app/src/main/res/drawable/ic_brand_reddit.xml create mode 100644 app/src/main/res/drawable/ic_brand_youtube.xml diff --git a/app/src/main/feature/settings/nav/SettingsNavGraph.kt b/app/src/main/feature/settings/nav/SettingsNavGraph.kt index 494f9e7e4..c99f6f8d5 100644 --- a/app/src/main/feature/settings/nav/SettingsNavGraph.kt +++ b/app/src/main/feature/settings/nav/SettingsNavGraph.kt @@ -250,6 +250,9 @@ fun SettingsHost( composable(SettingsRoutes.fromNavItem(SettingsNavItem.CREDITS)) { com.winlator.cmod.feature.retro.RetroCreditsScreen(bridge = bridge) } + composable(SettingsRoutes.fromNavItem(SettingsNavItem.SUPPORT)) { + com.winlator.cmod.feature.settings.support.SupportScreen(bridge = bridge) + } } } } diff --git a/app/src/main/feature/settings/nav/SettingsNavSidebar.kt b/app/src/main/feature/settings/nav/SettingsNavSidebar.kt index 7e226a65b..8eaeeebaa 100644 --- a/app/src/main/feature/settings/nav/SettingsNavSidebar.kt +++ b/app/src/main/feature/settings/nav/SettingsNavSidebar.kt @@ -36,6 +36,7 @@ import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.outlined.AccountCircle import androidx.compose.material.icons.outlined.BugReport import androidx.compose.material.icons.outlined.Extension +import androidx.compose.material.icons.outlined.HelpOutline import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.Menu import androidx.compose.material.icons.outlined.Memory @@ -98,6 +99,7 @@ enum class NavSection { SYSTEM, TOOLS, CREDITS, + HELP, } enum class SettingsNavItem( @@ -117,6 +119,7 @@ enum class SettingsNavItem( OTHER(R.id.main_menu_other, Icons.Outlined.Widgets, R.string.common_ui_other, NavSection.SYSTEM), DEBUG(R.id.main_menu_advanced, Icons.Outlined.BugReport, R.string.settings_debug_title, NavSection.TOOLS), CREDITS(R.id.main_menu_credits, Icons.Outlined.Info, R.string.retro_scr_tab_credits, NavSection.CREDITS), + SUPPORT(R.id.main_menu_support, Icons.Outlined.HelpOutline, R.string.settings_support_title, NavSection.HELP), ; companion object { diff --git a/app/src/main/feature/settings/support/SupportScreen.kt b/app/src/main/feature/settings/support/SupportScreen.kt new file mode 100644 index 000000000..90387d1df --- /dev/null +++ b/app/src/main/feature/settings/support/SupportScreen.kt @@ -0,0 +1,187 @@ +package com.winlator.cmod.feature.settings.support + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.OpenInNew +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.winlator.cmod.R +import com.winlator.cmod.feature.settings.SettingsNavBridge +import com.winlator.cmod.shared.ui.focus.rememberSettingsContentNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.paneNavItem + +private val SupportBg = Color(0xFF101018) +private val SupportText = Color(0xFFF0F4FF) +private val SupportSub = Color(0xFF93A6BC) +private val SupportCard = Color(0xFF181822) +private val SupportAccent = Color(0xFF4FC3F7) + +private data class SupportLink( + val iconRes: Int, + val titleRes: Int, + val subtitleRes: Int, + val url: String, +) + +private val SUPPORT_LINKS = + listOf( + SupportLink( + iconRes = R.drawable.ic_brand_discord, + titleRes = R.string.support_winnative_discord, + subtitleRes = R.string.support_winnative_discord_desc, + url = "https://discord.gg/8Gzh5mmBJg", + ), + SupportLink( + iconRes = R.drawable.ic_brand_discord, + titleRes = R.string.support_maxstechreview_discord, + subtitleRes = R.string.support_maxstechreview_discord_desc, + url = "https://discord.gg/445xxnkCa2", + ), + SupportLink( + iconRes = R.drawable.ic_brand_reddit, + titleRes = R.string.support_reddit, + subtitleRes = R.string.support_reddit_desc, + url = "https://www.reddit.com/r/EmulatorsForAndroid/", + ), + SupportLink( + iconRes = R.drawable.ic_brand_youtube, + titleRes = R.string.support_youtube, + subtitleRes = R.string.support_youtube_desc, + url = "https://youtube.com/@maxstechreview", + ), + ) + +@Composable +fun SupportScreen(bridge: SettingsNavBridge? = null) { + val context = LocalContext.current + val contentNav = rememberSettingsContentNav(bridge) + + fun open(url: String) { + runCatching { + context.startActivity( + android.content.Intent( + android.content.Intent.ACTION_VIEW, + android.net.Uri.parse(url), + ), + ) + } + } + + CompositionLocalProvider(LocalPaneNav provides contentNav) { + Column( + modifier = + Modifier + .fillMaxSize() + .background(SupportBg) + .verticalScroll(rememberScrollState()) + .windowInsetsPadding(WindowInsets.navigationBars) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + Text( + stringResource(R.string.support_heading), + color = SupportSub, + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + letterSpacing = 1.sp, + modifier = Modifier.padding(top = 4.dp), + ) + Text( + stringResource(R.string.support_desc), + color = SupportSub, + style = MaterialTheme.typography.labelMedium, + ) + + Spacer(Modifier.size(2.dp)) + + SUPPORT_LINKS.forEach { link -> + Row( + modifier = + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(14.dp)) + .background(SupportCard) + .clickable { open(link.url) } + .paneNavItem( + cornerRadius = 14.dp, + onActivate = { open(link.url) }, + highlightColor = SupportAccent, + tapToSelect = true, + ) + .padding(horizontal = 14.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = + Modifier + .size(40.dp) + .clip(RoundedCornerShape(11.dp)) + .background(SupportAccent.copy(alpha = 0.12f)), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(link.iconRes), + contentDescription = null, + tint = SupportAccent, + modifier = Modifier.size(22.dp), + ) + } + + Spacer(Modifier.width(14.dp)) + + Column(Modifier.weight(1f)) { + Text( + stringResource(link.titleRes), + color = SupportText, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Medium, + ) + Text( + stringResource(link.subtitleRes), + color = SupportSub, + fontSize = 11.sp, + ) + } + + Icon( + Icons.Outlined.OpenInNew, + contentDescription = null, + tint = SupportSub, + modifier = Modifier.size(17.dp), + ) + } + } + } + } +} diff --git a/app/src/main/res/drawable/ic_brand_discord.xml b/app/src/main/res/drawable/ic_brand_discord.xml new file mode 100644 index 000000000..8da7f112e --- /dev/null +++ b/app/src/main/res/drawable/ic_brand_discord.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_brand_reddit.xml b/app/src/main/res/drawable/ic_brand_reddit.xml new file mode 100644 index 000000000..7dc71e7e5 --- /dev/null +++ b/app/src/main/res/drawable/ic_brand_reddit.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_brand_youtube.xml b/app/src/main/res/drawable/ic_brand_youtube.xml new file mode 100644 index 000000000..2c15bc09c --- /dev/null +++ b/app/src/main/res/drawable/ic_brand_youtube.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index c0818f754..2e6b306f6 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -2189,6 +2189,17 @@ Ruta instalada: Algunas funciones de WinNative se basan en estos proyectos de código abierto. Toca para ver cada fuente. Predeterminados Créditos + Soporte + OBTENER AYUDA + Haz preguntas, informa problemas o mira en qué se está trabajando. Cada enlace se abre en tu navegador. + Discord • soporte y desarrollo + Discord • comunidad + YouTube • guías y novedades + Reddit • comunidad + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid ¿Activar el modo Hardcore? El modo Hardcore reinicia el juego ahora y desactiva la carga de estados guardados, el avance rápido y los trucos. Se perderá cualquier progreso sin guardar. ¿Continuar? Cancelar diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 3da6d50a4..de349a7ce 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -2191,6 +2191,17 @@ Installeret sti: Nogle af WinNatives funktioner er bygget på disse open source-projekter. Tryk for at se hver kilde. Standarder Medvirkende + Support + FÅ HJÆLP + Stil spørgsmål, rapportér problemer, eller se hvad der arbejdes på. Hvert link åbner i din browser. + Discord • support og udvikling + Discord • fællesskab + YouTube • guides og opdateringer + Reddit • fællesskab + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid Aktivér Hardcore-tilstand? Hardcore-tilstand nulstiller spillet nu og deaktiverer indlæsning af gemte tilstande, spol frem og snydekoder. Ugemt fremskridt går tabt. Fortsæt? Annuller diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 970c586c9..8c9513b27 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -2191,6 +2191,17 @@ Installierter Pfad: Einige Funktionen von WinNative basieren auf diesen Open-Source-Projekten. Tippe, um die jeweilige Quelle anzuzeigen. Standards Mitwirkende + Support + HILFE + Stelle Fragen, melde Probleme oder sieh, woran gerade gearbeitet wird. Jeder Link öffnet sich im Browser. + Discord • Support und Entwicklung + Discord • Community + YouTube • Anleitungen und Neuigkeiten + Reddit • Community + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid Hardcore-Modus aktivieren? Der Hardcore-Modus setzt das Spiel jetzt zurück und deaktiviert das Laden von Spielständen, Schnellvorlauf und Cheats. Nicht gespeicherter Fortschritt geht verloren. Fortfahren? Abbrechen diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 028f54045..09efc29c3 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -2190,6 +2190,17 @@ Ruta instalada: Algunas de las funciones de WinNative se basan en estos proyectos de código abierto. Toca para ver cada fuente. Predeterminados Créditos + Soporte + OBTENER AYUDA + Haz preguntas, informa de problemas o mira en qué se está trabajando. Cada enlace se abre en tu navegador. + Discord • soporte y desarrollo + Discord • comunidad + YouTube • guías y novedades + Reddit • comunidad + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid ¿Activar el modo Hardcore? El modo Hardcore reinicia el juego ahora y desactiva la carga de estados guardados, el avance rápido y los trucos. Se perderá cualquier progreso sin guardar. ¿Continuar? Cancelar diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index f530e024b..955864d3a 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -2189,6 +2189,17 @@ Asennuspolku: Osa WinNativen ominaisuuksista perustuu näihin avoimen lähdekoodin projekteihin. Napauta nähdäksesi kunkin lähteen. Oletukset Tekijät + Tuki + HAE APUA + Kysy kysymyksiä, ilmoita ongelmista tai katso, mitä on työn alla. Jokainen linkki avautuu selaimessa. + Discord • tuki ja kehitys + Discord • yhteisö + YouTube • oppaat ja päivitykset + Reddit • yhteisö + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid Otetaanko Hardcore-tila käyttöön? Hardcore-tila nollaa pelin nyt ja poistaa käytöstä tallennustilojen latauksen, pikakelauksen ja huijaukset. Kaikki tallentamaton edistyminen menetetään. Jatketaanko? Peruuta diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 69cc737aa..bb595b6b8 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -2190,6 +2190,17 @@ Chemin installé : Certaines fonctionnalités de WinNative reposent sur ces projets open source. Appuyez pour voir chaque source. Par défaut Crédits + Assistance + OBTENIR DE L\'AIDE + Posez des questions, signalez des problèmes ou voyez ce qui est en cours. Chaque lien s\'ouvre dans votre navigateur. + Discord • assistance et développement + Discord • communauté + YouTube • guides et actualités + Reddit • communauté + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid Activer le mode Hardcore ? Le mode Hardcore réinitialise le jeu maintenant et désactive le chargement des sauvegardes rapides, l\'avance rapide et les codes de triche. Toute progression non enregistrée sera perdue. Continuer ? Annuler diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index d873c5a80..88d2abcf8 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -2126,6 +2126,17 @@ WinNative की कुछ सुविधाएँ इन ओपन-सोर्स परियोजनाओं पर आधारित हैं. प्रत्येक स्रोत देखने के लिए टैप करें. डिफ़ॉल्ट क्रेडिट + सहायता + मदद पाएँ + सवाल पूछें, समस्याएँ बताएँ, या देखें कि किस पर काम हो रहा है। हर लिंक आपके ब्राउज़र में खुलता है। + Discord • सहायता और विकास + Discord • समुदाय + YouTube • गाइड और अपडेट + Reddit • समुदाय + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid हार्डकोर मोड सक्षम करें? हार्डकोर मोड अभी गेम रीसेट करता है और सेव स्टेट लोड करना, फ़ास्ट फ़ॉरवर्ड, और चीट्स अक्षम कर देता है. कोई भी असहेजी प्रगति खो जाएगी. जारी रखें? रद्द करें diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 7335f89e1..493d35916 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -2190,6 +2190,17 @@ Percorso installato: Alcune funzioni di WinNative si basano su questi progetti open source. Tocca per visualizzare ogni fonte. Predefiniti Crediti + Supporto + ASSISTENZA + Fai domande, segnala problemi o guarda a cosa si sta lavorando. Ogni link si apre nel browser. + Discord • supporto e sviluppo + Discord • community + YouTube • guide e aggiornamenti + Reddit • community + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid Attivare la modalità Hardcore? La modalità Hardcore reimposta il gioco ora e disattiva il caricamento dei salvataggi rapidi, l\'avanzamento rapido e i trucchi. Ogni progresso non salvato andrà perso. Continuare? Annulla diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 38b8f740c..e7feeb180 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -2189,6 +2189,17 @@ WinNative の一部の機能はこれらのオープンソースプロジェクトを基盤としています。タップして各ソースを表示します。 デフォルト クレジット + サポート + ヘルプ + 質問、不具合の報告、開発状況の確認ができます。リンクはブラウザで開きます。 + Discord • サポートと開発 + Discord • コミュニティ + YouTube • ガイドと最新情報 + Reddit • コミュニティ + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid ハードコアモードを有効にしますか? ハードコアモードは今すぐゲームをリセットし、ステートセーブのロード、早送り、チートを無効にします。保存していない進行状況は失われます。続行しますか? キャンセル diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index df5f89d76..10cb99574 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -2191,6 +2191,17 @@ WinNative의 일부 기능은 이러한 오픈 소스 프로젝트를 기반으로 합니다. 탭하여 각 소스를 확인하세요. 기본값 크레딧 + 지원 + 도움말 + 질문하거나 문제를 보고하고, 진행 중인 작업을 확인하세요. 각 링크는 브라우저에서 열립니다. + Discord • 지원 및 개발 + Discord • 커뮤니티 + YouTube • 가이드 및 소식 + Reddit • 커뮤니티 + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid 하드코어 모드를 사용하시겠습니까? 하드코어 모드는 지금 게임을 재설정하고 상태 저장 불러오기, 빨리 감기, 치트를 비활성화합니다. 저장하지 않은 진행 상황은 손실됩니다. 계속하시겠습니까? 취소 diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index 73d798d94..59af6c788 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -2189,6 +2189,17 @@ Installert bane: Noen av WinNatives funksjoner er bygget på disse åpen kildekode-prosjektene. Trykk for å se hver kilde. Standarder Bidragsytere + Støtte + FÅ HJELP + Still spørsmål, meld fra om problemer, eller se hva det jobbes med. Hver lenke åpnes i nettleseren. + Discord • støtte og utvikling + Discord • fellesskap + YouTube • guider og oppdateringer + Reddit • fellesskap + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid Aktivere Hardcore-modus? Hardcore-modus tilbakestiller spillet nå og deaktiverer innlasting av lagrede tilstander, spoling fremover og juksekoder. Ulagret fremdrift går tapt. Fortsette? Avbryt diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index eb2b56c59..ae55206c0 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -2196,6 +2196,17 @@ Zainstalowana ścieżka: Niektóre funkcje WinNative są oparte na tych projektach open-source. Dotknij, aby zobaczyć każde źródło. Domyślne Autorzy + Wsparcie + UZYSKAJ POMOC + Zadawaj pytania, zgłaszaj problemy lub sprawdź, nad czym trwają prace. Każdy odnośnik otwiera się w przeglądarce. + Discord • wsparcie i rozwój + Discord • społeczność + YouTube • poradniki i nowości + Reddit • społeczność + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid Włączyć tryb Hardcore? Tryb Hardcore resetuje teraz grę i wyłącza wczytywanie stanów zapisu, przewijanie i kody. Wszelkie niezapisane postępy zostaną utracone. Kontynuować? Anuluj diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 86eb171bb..8158d027f 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -2190,6 +2190,17 @@ Caminho instalado: Alguns recursos do WinNative são baseados nestes projetos de código aberto. Toque para ver cada fonte. Predefinições Créditos + Suporte + OBTER AJUDA + Faça perguntas, relate problemas ou veja o que está sendo desenvolvido. Cada link abre no seu navegador. + Discord • suporte e desenvolvimento + Discord • comunidade + YouTube • guias e novidades + Reddit • comunidade + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid Ativar o modo Hardcore? O modo Hardcore reinicia o jogo agora e desativa o carregamento de estados salvos, o avanço rápido e as trapaças. Qualquer progresso não salvo será perdido. Continuar? Cancelar diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 33400329e..51b67f2c0 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -2189,6 +2189,17 @@ Caminho instalado: Algumas funcionalidades do WinNative baseiam-se nestes projetos de código aberto. Toque para ver cada fonte. Predefinições Créditos + Suporte + OBTER AJUDA + Faça perguntas, comunique problemas ou veja o que está a ser desenvolvido. Cada ligação abre no seu navegador. + Discord • suporte e desenvolvimento + Discord • comunidade + YouTube • guias e novidades + Reddit • comunidade + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid Ativar o modo Hardcore? O modo Hardcore reinicia o jogo agora e desativa o carregamento de estados guardados, o avanço rápido e as batotas. Qualquer progresso não guardado será perdido. Continuar? Cancelar diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index dea00f05a..6db427d23 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -2189,6 +2189,17 @@ Cale instalata: Unele funcții ale WinNative sunt construite pe aceste proiecte open-source. Atinge pentru a vedea fiecare sursă. Implicite Credite + Asistență + OBȚINE AJUTOR + Pune întrebări, raportează probleme sau vezi la ce se lucrează. Fiecare link se deschide în browser. + Discord • asistență și dezvoltare + Discord • comunitate + YouTube • ghiduri și noutăți + Reddit • comunitate + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid Activezi modul Hardcore? Modul Hardcore resetează jocul acum și dezactivează încărcarea salvărilor de stare, derularea rapidă și codurile. Orice progres nesalvat va fi pierdut. Continui? Anulează diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index f58f27838..77ce49974 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -2096,6 +2096,17 @@ Некоторые функции WinNative построены на этих проектах с открытым исходным кодом. Нажмите, чтобы посмотреть каждый источник. По умолчанию Благодарности + Поддержка + ПОМОЩЬ + Задавайте вопросы, сообщайте о проблемах или следите за разработкой. Каждая ссылка открывается в браузере. + Discord • поддержка и разработка + Discord • сообщество + YouTube • руководства и новости + Reddit • сообщество + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid Включить режим Hardcore? Режим Hardcore сбрасывает игру сейчас и отключает загрузку сохранений состояний, ускорение и читы. Любой несохранённый прогресс будет потерян. Продолжить? Отмена diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 8d577ff11..6a0bca7d2 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -2189,6 +2189,17 @@ Installerad sökväg: Vissa av WinNatives funktioner bygger på dessa projekt med öppen källkod. Tryck för att visa varje källa. Standarder Medverkande + Support + FÅ HJÄLP + Ställ frågor, rapportera problem eller se vad som arbetas på. Varje länk öppnas i webbläsaren. + Discord • support och utveckling + Discord • gemenskap + YouTube • guider och uppdateringar + Reddit • gemenskap + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid Aktivera Hardcore-läge? Hardcore-läge återställer spelet nu och inaktiverar inläsning av sparade tillstånd, snabbspolning och fusk. Osparade framsteg går förlorade. Fortsätta? Avbryt diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index e9844af57..4a082f994 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -2189,6 +2189,17 @@ ฟีเจอร์บางอย่างของ WinNative สร้างขึ้นบนโปรเจกต์โอเพนซอร์สเหล่านี้ แตะเพื่อดูแต่ละแหล่งที่มา ค่าเริ่มต้น เครดิต + ฝ่ายสนับสนุน + ขอความช่วยเหลือ + ถามคำถาม แจ้งปัญหา หรือดูว่ากำลังพัฒนาอะไรอยู่ ลิงก์ทั้งหมดจะเปิดในเบราว์เซอร์ + Discord • สนับสนุนและพัฒนา + Discord • ชุมชน + YouTube • คู่มือและอัปเดต + Reddit • ชุมชน + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid เปิดโหมดฮาร์ดคอร์? โหมดฮาร์ดคอร์จะรีเซ็ตเกมทันทีและปิดการโหลดสเตตบันทึก การกรอไปข้างหน้า และสูตรโกง ความคืบหน้าที่ยังไม่ได้บันทึกจะสูญหาย ดำเนินการต่อ? ยกเลิก diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index e808023a1..2648c967e 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -2189,6 +2189,17 @@ Yüklü konum: WinNative\'in bazı özellikleri bu açık kaynaklı projeler üzerine kuruludur. Her kaynağı görüntülemek için dokunun. Varsayılanlar Katkıda Bulunanlar + Destek + YARDIM AL + Soru sorun, sorun bildirin veya nelerin üzerinde çalışıldığını görün. Her bağlantı tarayıcınızda açılır. + Discord • destek ve geliştirme + Discord • topluluk + YouTube • rehberler ve güncellemeler + Reddit • topluluk + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid Hardcore modu etkinleştirilsin mi? Hardcore modu oyunu şimdi sıfırlar ve durum kaydı yükleme, ileri sarma ile hileleri devre dışı bırakır. Kaydedilmemiş tüm ilerleme kaybolur. Devam edilsin mi? İptal diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 5a2bb008a..b59a6d017 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -2196,6 +2196,17 @@ Деякі функції WinNative побудовані на цих проєктах з відкритим кодом. Торкніться, щоб переглянути кожне джерело. Стандартні Автори + Підтримка + ОТРИМАТИ ДОПОМОГУ + Ставте запитання, повідомляйте про проблеми або стежте за розробкою. Кожне посилання відкривається у браузері. + Discord • підтримка та розробка + Discord • спільнота + YouTube • посібники та новини + Reddit • спільнота + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid Увімкнути режим Hardcore? Режим Hardcore зараз скидає гру та вимикає завантаження станів, прискорення й чити. Будь-який незбережений прогрес буде втрачено. Продовжити? Скасувати diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ad9b6efaf..4267e92a5 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -2190,6 +2190,17 @@ WinNative 的部分功能基于这些开源项目构建。点按可查看各来源。 默认 鸣谢 + 支持 + 获取帮助 + 提问、反馈问题,或了解正在进行的开发。每个链接都会在浏览器中打开。 + Discord • 支持与开发 + Discord • 社区 + YouTube • 指南与更新 + Reddit • 社区 + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid 启用硬核模式? 硬核模式将立即重置游戏,并禁用加载即时存档、快进和金手指。任何未保存的进度都将丢失。是否继续? 取消 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 87edc1a40..830bdad0c 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -2189,6 +2189,17 @@ WinNative 的部分功能建置於這些開源專案之上。點按即可檢視各來源。 預設 鳴謝 + 支援 + 取得協助 + 提問、回報問題,或了解正在進行的開發。每個連結都會在瀏覽器中開啟。 + Discord • 支援與開發 + Discord • 社群 + YouTube • 指南與更新 + Reddit • 社群 + #WinNative + #MaxsTechReview + @MaxsTechReview + r/EmulatorsForAndroid 啟用硬派模式? 硬派模式將立即重置遊戲,並停用載入即時存檔、快轉和金手指。任何未儲存的進度都將遺失。是否繼續? 取消 diff --git a/app/src/main/res/values/refs.xml b/app/src/main/res/values/refs.xml index b3ff36d64..657944628 100644 --- a/app/src/main/res/values/refs.xml +++ b/app/src/main/res/values/refs.xml @@ -10,6 +10,7 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d3d53a37a..a0247678e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2362,6 +2362,17 @@ Installed path: Some of WinNative\'s features are built on these open-source projects. Tap to view each source. Defaults Credits + Support + GET HELP + Ask questions, report problems, or see what is being worked on. Each opens in your browser. + #WinNative + Discord • support and development + #MaxsTechReview + Discord • community + @MaxsTechReview + YouTube • guides and updates + r/EmulatorsForAndroid + Reddit • community Enable Hardcore mode? Hardcore mode resets the game now and disables loading save states, fast forward, and cheats. Any unsaved progress will be lost. Continue? Cancel