Epic: built-in effects stack (ReShade FX compatible) + cel-shading avatars #542

Closed
opened 2026-10-08 06:28:03 +00:00 by sky_line · 4 comments
Owner

Design note and plan for #186: a built-in, ReShade FX compatible effects stack that runs inside our VK post chain, with a native cel-shading effect, optionally limited to avatars / rigged mesh, as the first built-in.

Recommendation: GO. The cel-shading track can start now; it doesn't depend on the ReShade runtime. The ReShade FX track is GO contingent on Phase 0, a vendor-and-compile smoke test that is cheap and decisive.


1. Goals

  1. Load unmodified ReShade effect files: .fx/.fxh, the textures they reference (source = "..."), and ReShade preset .ini files. They come from a folder the user picks, which can be an existing ReShade reshade-shaders install. Our own format is used only for built-ins, and only where FX can't express something (in-pass hooks).
  2. Give effects better inputs than injected ReShade can get: the real scene colour, unjittered display depth, plus opt-in extensions for normals, motion vectors, the linear HDR scene and an avatar mask.
  3. Run effects on the world only: before selection outlines, 3D overlays, name tags, HUD attachments and UI. Include them in snapshots (custom sizes, supersampled).
  4. First built-in: cel shading. It has a toon lighting ramp, rim light and outlines, with a scope of whole scene / avatars only / self only / chosen avatars.
  5. Safe by default. A broken or hanging user effect must never brick the viewer. A device loss with FX on means the next launch starts with FX off and the culprit named.
  6. Same behaviour on Linux and Windows. No DLL or Vulkan-layer injection.

Non-goals for v1: ReShade's add-on API (the DLL plugin ABI), D3D-only intrinsics, depth from HUD space, and effects on 360 captures (see 5.6).


2. What exists today (code survey, vulkan-port-tb @ 9e512297ca)

Post chain order (indra/newview/vkgfx/llcleanrenderervk.cpp, recordWorld at :23547):

Step Where Image / space
Pass 1 solid (forward or deferred recordDeferredLighting) :25064, :25344 sceneHdr(), render-res, jittered
God rays :26081
Pass 2 alpha + water :26148
TAA / FSR 3 / snapshot accumulation / scaled blit :26544-26577 (recordTAA :15169, recordFSR :15798, recordSnapAccum :15634, recordSceneUpscaleBlit :16438) -> mHdr display-res, unjittered, RGBA16F linear (llcleanrenderervk.h:1043-1045)
SSR copy, eyedropper, DoF, bloom :26579, :26651-26661 linear HDR
recordTonemap :26718 (fn :17094) -> display-referred (sRGB-encoded)
LDR glow :26729
recordAA FXAA/SMAA :26734 (fn :17412) mAAColor RGBA16F -> chain out
RLVa @setsphere :26739 ping-pong mSphereTmp/mFinalColor
recordFinal (Photo Studio "Lightbox" lens/vignette/CVD/grain/dither/CAS, clean_final.frag) :26744 (fn :18341) mFinalColor -> swapchain
recordCaptureAt(1): user snapshot, world only :26774 (fn :16110)
recordDepthUpscale (#181), only when mScaled :26776 (fn :16482) displayDepth() (.h:1100)
Selection outline, mWorldOverlayHook (3D overlays, name tags #468) :26780-26806
Pass 3 HUD attachments :26814
UI (LLRender VK backend) after recordWorld
  • prepareFinal(..., force) (:18145, header .h:490-494) is how RLVa's sphere already forces the chain to end in mFinalColor. FX can reuse the same mechanism.
  • Render scale (#181): scene stages run at mRenderW x mRenderH, everything from the TAA resolve on runs at display size (.h:1053-1062). Target generation registry (.h:1063-1090): any new size-dependent target / descriptor set must follow its rules. External modules get the generation through their Frame, never a view-handle compare.
  • Depth: D32_SFLOAT, no stencil (.h:1026); reversed-Z, near = 1 / far = 0 (llcleanrenderervk.cpp:127-132). clean_depth_up.comp produces the unjittered display depth, but only on scaled frames. At scale 1 under TAA, displayDepth() is the jittered mDepth.
  • Normals: the pre-pass writes view-space oct normals to mAoNormal RG16F (.h:1414, clean_prepass_normal.frag), on AO frames only. The deferred G-buffer has G1 oct N/Ng and G2.w "model byte" with bits 3-7 free (vkclean/shaders/clean_gbuffer.glsl:8-13, :48). Deferred is not used for captures / offscreen renders (.h:1925-1926).
  • Motion vectors: mMotion RG16F, UV units, current minus previous, unjittered (.h:1673-1682, clean_prepass_normal.frag header). Written by the pre-pass when temporal is on.
  • Rigged / avatar identification: instance flags bit0 = rigged, bit9 = worn rigid attachment (skin_id = joint-table slot), bit1 alpha, bit2 mask, bit15 vegetation (indra/newview/llcleaninstancestore.h:61-67; clean_draw.vert:118). SkinMeta.avatar_idx (llcleanskinstore.h:78-80) and the per-avatar joint slot (ensureAvatarSlot, .h:104-115) give a per-pixel avatar index without any CPU per-face work. Rigged draws already have their own pre-pass pipeline twins (mPreMvRigPipe etc., llcleanrenderervk.h:1683-1688) and skinned verts in mSkinVbo (used by recordSelection :8082 to redraw rigged geometry).
  • Lighting: one lightSurface() (clean_surface_light.glsl:86) shared by forward and clean_deferred_light.comp.
  • Snapshots: offscreen recordings go through the same recordWorld (beginOffscreen :15439, mOffscreen). Supersampled snapshots (#417) accumulate N jittered recordings.
  • Device lost (#135): vk_device_lost_handler (llappviewer.cpp:4043) writes diagnostics and gpuLostWriteMarker() (:3823), which tells the next launch. vkBreadcrumb(cmd, name) (vk_common.h:38) uses static names (llcleanfsr3vk.cpp:545). gVkFatalHandler turns a failing VK_CHECK into LL_ERRS, so user content must never go through VK_CHECK.
  • GPU timers: gpuSubMark / gpuTimerMark (.h:1175).
  • Shaders: offline only. vkclean/regen_world_spv.py -> glslc -> embedded vk_world_shaders_spv.h. No runtime GLSL compiler (no glslang/shaderc in 3p). Vulkan headers come from the system SDK (indra/cmake/VulkanGfx.cmake), so SPIRV-Headers are not guaranteed on CI / Windows cross.
  • Swapchain: SDR sRGB only (vk_swapchain.cpp:63), BGRA (swapBytesToRGB, llcleanrenderervk.cpp:16119).
  • Photo Studio: pages cam / lens / look / scene / sky / scopes (skins/default/xui/en/panel_photo_studio_*.xml), slot model with section dropdowns + history (slfloaterphotostudio.h:67-68, 98-170).

3. Architecture

3.1 Compiler: vendor reshadefx (BSD-3) and run no GLSL compiler at runtime

  • Vendor crosire/reshade source/effect_* (lexer, preprocessor, parser, symbol table, expression, effect_codegen_spirv.cpp, headers). That's about 20 files. Also vendor the Khronos spirv.hpp + GLSL.std.450.h (MIT-style; system SDK headers aren't guaranteed). Location: indra/newview/vkgfx/third_party/reshadefx/ with LICENSE.md + NOTICE.
  • reshadefx emits SPIR-V directly, so no glslang/shaderc dependency. That is the main enabler. Codegen layout (effect_codegen_spirv.cpp ~:984-1094): set 0 = global uniform UBO, set 1 = combined samplers, set 2 = storage images. Options: vulkan_semantics, flip_vert_y, uniforms_to_spec_constants, enable_16bit_types.
  • Compile on a worker thread. Key (file hash, includes hash, defines, BUFFER size) -> SPIR-V cache on disk. Pipelines go through vk_pipecache.
  • Pin one ReShade release (track its __RESHADE__ value). Update deliberately.

3.2 Runtime (newview/vkgfx/llcleanfxvk.{h,cpp}, a module like FSR3 / god rays)

  • Resources per effect: textures / render targets (Width/Height/MipLevels/Format, pooled), created with VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT + UNORM/SRGB view pairs for SRGBTexture / SRGBWriteEnable. Samplers from sampler states. Storage images for compute passes. An own S8/D24S8 stencil attachment for passes with StencilEnable (our depth has no stencil). Image source files loaded via LL decoders (png/jpg/bmp/tga) + a small DDS and .cube LUT reader.
  • Passes: one graphics pipeline per pass (dynamic rendering, fullscreen VertexCount / PrimitiveTopology, blend / write-mask / stencil states, up to 8 RTs, ClearRenderTargets, GenerateMipMaps). Compute passes use DispatchSizeX/Y/Z. Techniques run in user order. Barriers are derived from each pass's read/write sets.
  • Uniforms: one UBO per effect per frame-ring slot; values from the UI / preset. source specials: timer, frametime, framecount, date, pingpong, random, key / mousebutton / mousepoint / mousedelta / mousewheel (from LL keyboard / window, only while the world has focus), bufready_depth (true), overlay_open (Photo Studio effects panel open), overlay_active / hovered (UI), screenshot (= mOffscreen).
  • Preprocessor: __RESHADE__, __RENDERER__ = 0x20000+ (Vulkan), __VENDOR__ / __DEVICE__, BUFFER_WIDTH/HEIGHT/RCP_*, BUFFER_COLOR_BIT_DEPTH (8 at the display point), BUFFER_COLOR_SPACE (1 sRGB; 2 scRGB at the HDR point), __SLIPSTREAM__ (version) for opt-in extensions.
  • Built-in headers: ship ReShade.fxh (CC0-1.0, SPDX in its header) and a clean-room ReShadeUI.fxh. The upstream one has no licence (the reshade-shaders repo has no LICENSE file), but it's only annotation macros.
  • Generation registry: the module's descriptor sets are gen-keyed via its Frame struct (rule 4, .h:1084).
  • Hot reload: a file watcher on the effect search paths recompiles changed effects on the worker and swaps them atomically on success. The old version keeps running on error.
  • GPU timer per technique (gpuSubMark with interned names) and breadcrumb per pass.

3.3 Injection points

Point Position Image Advertised as Use
Display (default) after recordAA (:26734), before RLVa sphere and recordFinal ping-pong LDR targets, mColorFormat-compatible, forced via prepareFinal(force) BUFFER_COLOR_SPACE 1, 8-bit all stock ReShade packs: they expect a post-AA, display-referred backbuffer, as in games
HDR scene (opt-in per effect) after bloom, before recordTonemap (~:26661 -> :26718) mHdr RGBA16F linear, exposure-scaled BUFFER_COLOR_SPACE 2, 16-bit HDR-aware effects; our own grading-like built-ins
In-scene pre-TAA (built-ins only) after pass-1 lighting, before pass 2 (~:26146) sceneHdr() render-res, jittered n/a cel outlines (TAA anti-aliases them)

Why this order:

  • RLVa vision restriction stays last (an effect can't be used to bypass @setsphere).
  • Lightbox lens/grain/dither run after user effects, like a camera's last stage.
  • Everything stays pre-snapshot-capture and pre-HUD/UI.

3.4 Input mapping

  • ReShade::BackBuffer / COLOR -> the chain's current image at the point.
  • ReShade::DepthBuffer / DEPTH -> unjittered display depth. Run clean_depth_up.comp's unjitter mapping at 1:1 too when FX wants depth, otherwise depth effects shimmer under TAA. Our reversed-Z matches ReShade.fxh's default RESHADE_DEPTH_INPUT_IS_REVERSED 1. Define RESHADE_DEPTH_LINEARIZATION_FAR_PLANE from the far clip (compile-time, recompile on draw-distance change). Alternative: hand effects linear depth / far and set FAR_PLANE 1. Pick in P1 after testing MXAO / DOF-style effects.
  • Slipstream extensions (custom texture semantics, bound like ReShade add-on update_texture_bindings): SLIPSTREAM_NORMAL (view-space normal, display res), SLIPSTREAM_MOTION (UV-unit MV), SLIPSTREAM_AVATAR_MASK (R8 / R16 id), SLIPSTREAM_SCENE_HDR. Effects that don't declare them are unaffected. Normals or motion force the corresponding pre-pass twin, as AO does.
  • Per-effect scope (host feature, works with any effect): whole frame / avatars only / everything except avatars. The host keeps the pre-effect image and lerps by the resolved avatar mask. Caveat: neighbourhood effects (bloom, blur) bleed at the mask edge; a feather slider helps.

3.5 UI (recommendation)

  • An "Effects" page in Photo Studio (new panel_photo_studio_effects.xml + section in the slot model). It sits next to grading, gets history / undo and Find for free, and holds:
    • the effect list (search, enable, drag-order, per-technique ms)
    • the annotation-generated controls: slider / drag / input / combo / radio / color / bool, ui_category collapsibles, ui_text, tooltips
    • per-effect preprocessor definitions (recompile) and scope
    • compile errors inline
  • Quick access outside Photo Studio: Preferences > Graphics "Effects: on/off + preset" dropdown, and a toggle keybind (ReShade users expect one).
  • Presets use ReShade's .ini: Techniques=Name@file.fx,..., TechniqueSorting=, PreprocessorDefinitions=, one [file.fx] section of uniform=values. Import and export as-is; store in ~/.slipstreamviewer/fx/presets/. Unknown keys are preserved on round-trip.
  • Search paths: our fx/ dir + user-added folders. The "Import ReShade install" button points at reshade-shaders/Shaders + Textures.

3.6 Safety

  • No VK_CHECK on any user-content path. Pipeline / shader-module failure disables that effect with a message.
  • Bounds:
    • robustBufferAccess2 / robustImageAccess2 on FX pipelines where available
    • caps on texture size (16384), MipLevels, pass count, dispatch size
    • an FX VRAM budget via llvrambudgetvk (soft cap, refuse load beyond it)
  • Device loss: if the stack was active, gpuLostWriteMarker also records the last FX breadcrumb.
    • The next launch starts with FX off and a notice naming the effect.
    • That effect is quarantined until the user re-enables it.
    • "Provisional" flag on newly loaded / edited effects: it clears after N good frames, and a loss while provisional quarantines without asking.
  • Safe-mode launch switch: --no-fx / a settings key.
  • Known injected ReShade / vkBasalt detected (Windows dxgi.dll / opengl32.dll next to the exe, VK_LAYER_reshade* / ENABLE_VKBASALT) -> one-time warning about double application.

3.7 Performance budget

  • Runtime overhead beyond the effects' own work: ≤ 0.05 ms. That covers the ping-pong + barriers; with zero effects, nothing is recorded and no targets are allocated.
  • Cel shading (lighting + mask + outlines): ≤ 0.3 ms at 1440p on a 3060-class GPU.
  • The UI shows per-technique GPU ms and warns when the stack exceeds a user budget (default 3 ms).
  • Effects compile off-thread. Any compile hitch on the main thread is a bug.

3.8 Snapshots

  • Effects run inside offscreen recordings automatically (same recordWorld). The screenshot source = true; enabled_in_screenshot = false techniques are skipped.
  • BUFFER_WIDTH/HEIGHT are compile-time, so a custom-size snapshot needs recompiling and re-creating effect targets at that size. Mitigations:
    • a per-size module cache
    • warming the compile when the snapshot floater's size changes
    • an extra VRAM check (abort to "effects off" for the shot rather than fail)
  • Supersampled shots (#417): effects run only on the last accumulation recording (on the converged mean), not N times.
  • 360: screen-space effects per cube face produce seams, so FX is off for 360 in v1 (cel lighting still applies; it's in-pass).

3.9 Linux / Windows

  • Pure C++17 + Vulkan, so the same code on both.
  • File watching uses the platform's API (inotify / ReadDirectoryChangesW) or polling mtimes once a second (simpler, recommended).
  • Paths with non-ASCII characters go through LL's UTF-8 file APIs.
  • The Windows cross-build (windows-cross preset) must compile the vendored reshadefx under clang-cl. It's plain C++17; watch for the /Yu PCH note (keep it out of PCH).

4. Cel-shading design (first built-in)

Pure post-processing can't do convincing toon shading. After lighting, albedo and light are baked together, so posterizing gives muddy bands. The design is therefore a hybrid:

4.1 Toon lighting (in-pass, material stage).

  • What it does: inside lightSurface(), a flagged surface uses
    • a quantized sun/moon N·L ramp (N bands, band softness, shadow tint)
    • banded local lights
    • a flattened ambient
    • optional rim light (view-dependent)
    • optional specular step
  • How the flag reaches the shader:
    • Forward: derived from the instance (bit0 / bit9 / avatar index) + a per-avatar toon bitset SSBO indexed by avatar slot. Toggling scope or avatars only rewrites a small buffer, with no re-ingest and no per-face CPU work.
    • Deferred: 1 bit in G2.w model byte (bit 3; bits 3-7 free, clean_gbuffer.glsl:48). It is written from that same lookup in the G-buffer FS.
  • Both back ends share lightSurface(), so forward-only paths (snapshots, mirror, probe captures) get the same look. Whole-scene mode just sets the global flag.

4.2 Avatar mask (also used by FX scope and SLIPSTREAM_AVATAR_MASK).

  • Format: a dedicated R16_UINT id attachment: avatar slot + 1, 0 = not avatar.
  • Writer: written by the depth pre-pass twins when any consumer wants it. The pre-pass is on by default (RenderVKDepthPrepass, :25051) and runs offscreen too. When needed, it is forced on.
  • Contents: includes rigged (bit0), worn rigid attachments (bit9) and the system avatar body. Without the attachments, seams appear at hair / jewellery / prim attachments.
  • Not the G-buffer, because that path is skipped for captures. Not mAoNormal, because both channels are used and it exists only on AO frames.
  • Gap: alpha-blended rigged surfaces (hair, sheer clothing) render in pass 2 and aren't in the pre-pass. v1 includes a tiny extra pass that re-issues pass 2's rigged indirect lists into the mask (depth-test, no depth write, alpha > threshold). Without it, outlines stop at the hairline.
  • Display-res, unjittered resolve: same mapping as clean_depth_up.comp (floor(pr + jitter)), for post-chain consumers.

4.3 Outlines.

  • Default: screen-space edge pass (native compute, "in-scene pre-TAA" point).
    • Runs at render res on sceneHdr() after pass-1 lighting, before pass 2. TAA anti-aliases the lines, and alpha / water composite correctly on top.
    • Edges come from (a) mask/id discontinuity, giving the silhouette against the world and between two avatars; (b) depth discontinuity (linear, slope-scaled), giving self-occlusion (arm over body); (c) normal crease (pre-pass normal) for inner lines.
    • Avatars-only mode restricts it to a dilated mask, so the outer silhouette is drawn on the world side too.
    • Width is in display pixels (scaled by render scale). The line colour is exposure-aware (reads mExposureImg). Distance fade.
    • Cost: one full-screen compute, independent of triangle count.
  • Optional "inverted hull" mode (later):
    • One extra multi-draw-indirect over the rigged buckets from mSkinVbo (world-space skinned normals already there), with front-face cull and clip-space extrusion along the normal (spec constant in clean_draw_rigged.vert).
    • Gives classic anime outlines that occlude correctly in depth and survive into mirrors / probes.
    • Costs triangle throughput (heavy mesh bodies), and gaps appear on hard-split normals.
    • Fully GPU-driven, so it is allowed, but it's not the default.

4.4 Controls:

  • scope (off / whole scene / all avatars / self / friends / chosen avatars; right-click avatar -> "Cel shade this avatar")
  • bands, softness, shadow tint, rim strength / width
  • outline width / colour / depth sensitivity / crease sensitivity
  • These live in Photo Studio "Effects" (built-in section) + render settings RenderVKCel*, and get saved in Photo Studio presets.

4.5 Not FX: cel lighting needs the in-pass hook, so it's a native render feature. The mask is still exposed to FX, so community outline / toon effects can be restricted to avatars too.


5. Compatibility strategy and known gaps

  • Target: effects that run on stock ReShade 5/6 with a depth buffer, using ReShade.fxh.
  • Gaps / risks:
    • effects needing ReShade add-ons (ADDON_* macros undefined) or add-on-provided resources
    • D3D-only codegen quirks (DXBC / DXIL-only intrinsics) — reshadefx abstracts these
    • effects that read the depth buffer of a different convention via hard-coded hacks
    • very large pooled texture sets (VRAM)
    • 360
    • effects relying on __RENDERER__ being D3D
  • Test matrix (P8): SweetFX (MIT), prod80 (MIT), qUINT (all rights reserved), iMMERSE free tier (restrictive), Lilium HDR shaders (GPL-3, HDR point), plus ReShade's own reshade-shaders sample effects. The last four are user-installed only.
  • vkBasalt (Zlib) is a reference for a host that embeds reshadefx on Vulkan, nothing more. It lacks multi-technique effects, stencil and reliable depth, and we expect to exceed it.

6. Licensing

  • reshadefx: BSD-3-Clause (crosire/reshade LICENSE.md), compatible with the LGPL-2.1 viewer. Keep the copyright notice in source + About / credits.
  • SPIRV-Headers: Khronos MIT-style. Same handling.
  • ReShade.fxh: CC0-1.0 (SPDX line in the file). OK to ship.
  • ReShadeUI.fxh: no licence found, so write a clean-room equivalent.
  • Do not bundle:
    • qUINT and iMMERSE (Pascal Gilcher: "All rights reserved"; iMMERSE §1 forbids redistribution, §4 needs permission to reuse parts)
    • Lilium HDR (GPL-3)
  • SweetFX / prod80 are MIT and could be bundled with notices. Recommendation: bundle none in v1; built-ins are ours. A curated MIT starter pack is a later decision.
  • vkBasalt: reference only, no code copied.

7. Phased plan (sizes = agent-days, rough)

Phase Ticket Size Depends on
P0 #543 FX P0: vendor reshadefx (BSD-3) + SPIR-V smoke test — go/no-go gate S (1-2 d) -
P1 #544 FX P1: effects runtime on the VK post chain (display injection point, BackBuffer/DepthBuffer) L (5-8 d) P0
P2 #545 FX P2: Effects page in Photo Studio + ReShade .ini presets M-L (4-6 d) P1
P3 #546 FX P3: safety — non-fatal user content, device-lost quarantine, safe start, VRAM cap M (2-4 d) P1 (before FX is user-visible)
P4 #547 FX P4: effects in snapshots (custom size, supersampled, 360 policy) M (2-3 d) P1, P3
P5 #548 FX P5: Slipstream extensions — HDR point, normals/motion/avatar-mask semantics, per-effect scope M (3-4 d) P1, P6
P6 #549 Cel P6: avatar mask — per-pixel avatar id from the GPU-driven pre-pass M (3-4 d) - (cel track, parallel)
P7 #550 Cel P7: built-in cel shading — toon lighting + outlines, whole scene or avatars only L (5-7 d) P6
P8 #551 FX P8: compatibility sweep (SweetFX, prod80, qUINT, iMMERSE free, Lilium HDR) + gap fixes M (3-5 d) P1-P5

Ordering:

  • P0 is the FX go/no-go gate.
  • The cel track (P6 -> P7) is independent of P0-P2 and can start in parallel.
  • P5 needs P1 + P6.
  • P3 must land before FX is user-visible.

8. Risks

  1. Compile-time BUFFER_WIDTH/HEIGHT -> recompiles + target re-creation on resize and every custom-size snapshot (VRAM spike, hitch). Mitigated by the per-size cache + worker compile.
  2. GPU hangs from user shaders can't be prevented, only contained (device loss -> marker -> safe start + quarantine). On Windows, a TDR can reset the whole display.
  3. reshadefx SPIR-V vs our driver set: codegen quirks (16-bit types, DemoteToHelperInvocation, cf. vk_device.cpp:1338), validation errors, upstream drift. P0 measures this.
  4. Avatar mask completeness: alpha-blended hair / clothing, system avatar body, attachments, own avatar in mouselook, impostors / jelly dolls. Each needs an explicit rule.
  5. Scope creep in UI: ReShade's ImGui overlay is rich, and annotation-driven LLUI generation is the largest single UI task.
  6. Temporal effects vs our TAA: effects with their own history (TAA-like, motion blur) at the display point see an already-resolved image. Expect them to be redundant or to fight TAA; document it.

9. Go / no-go

  • Cel shading (P6, P7): GO now. Native, fits the GPU-driven architecture (flag lookup via SSBO, MDI only, no per-face paths), low risk, and high user value.

  • ReShade FX runtime (P0-P5, P8): GO contingent on P0. Gate criteria:

    • reshadefx builds in-tree on Linux + wincross
    • ≥ 90% of the SweetFX + prod80 + reshade-shaders sample effects compile
    • output passes spirv-val for Vulkan 1.3
    • modules load on our device

    If P0 fails badly, fall back to "Slipstream GLSL effect format for built-ins only" and drop compatibility.

Design note and plan for #186: a **built-in, ReShade FX compatible effects stack** that runs inside our VK post chain, with a **native cel-shading effect, optionally limited to avatars / rigged mesh**, as the first built-in. **Recommendation: GO.** The cel-shading track can start now; it doesn't depend on the ReShade runtime. The ReShade FX track is **GO contingent on Phase 0**, a vendor-and-compile smoke test that is cheap and decisive. --- ## 1. Goals 1. Load **unmodified ReShade effect files**: `.fx`/`.fxh`, the textures they reference (`source = "..."`), and ReShade preset `.ini` files. They come from a folder the user picks, which can be an existing ReShade `reshade-shaders` install. Our own format is used only for built-ins, and only where FX can't express something (in-pass hooks). 2. Give effects **better inputs than injected ReShade can get**: the real scene colour, **unjittered display depth**, plus opt-in extensions for normals, motion vectors, the linear HDR scene and an **avatar mask**. 3. Run effects **on the world only**: before selection outlines, 3D overlays, name tags, HUD attachments and UI. Include them in **snapshots** (custom sizes, supersampled). 4. **First built-in: cel shading.** It has a toon lighting ramp, rim light and outlines, with a scope of *whole scene* / *avatars only* / *self only* / *chosen avatars*. 5. Safe by default. A broken or hanging user effect must never brick the viewer. A device loss with FX on means the next launch starts with FX off and the culprit named. 6. Same behaviour on Linux and Windows. No DLL or Vulkan-layer injection. Non-goals for v1: ReShade's add-on API (the DLL plugin ABI), D3D-only intrinsics, depth from HUD space, and effects on 360 captures (see 5.6). --- ## 2. What exists today (code survey, `vulkan-port-tb` @ 9e512297ca) **Post chain order** (`indra/newview/vkgfx/llcleanrenderervk.cpp`, `recordWorld` at :23547): | Step | Where | Image / space | |---|---|---| | Pass 1 solid (forward or deferred `recordDeferredLighting`) | :25064, :25344 | `sceneHdr()`, render-res, jittered | | God rays | :26081 | | | Pass 2 alpha + water | :26148 | | | TAA / FSR 3 / snapshot accumulation / scaled blit | :26544-26577 (`recordTAA` :15169, `recordFSR` :15798, `recordSnapAccum` :15634, `recordSceneUpscaleBlit` :16438) | -> `mHdr` display-res, **unjittered**, RGBA16F linear (`llcleanrenderervk.h:1043-1045`) | | SSR copy, eyedropper, DoF, bloom | :26579, :26651-26661 | linear HDR | | `recordTonemap` | :26718 (fn :17094) | -> display-referred (sRGB-encoded) | | LDR glow | :26729 | | | `recordAA` FXAA/SMAA | :26734 (fn :17412) | `mAAColor` RGBA16F -> chain out | | RLVa `@setsphere` | :26739 | ping-pong `mSphereTmp`/`mFinalColor` | | `recordFinal` (Photo Studio "Lightbox" lens/vignette/CVD/grain/dither/CAS, `clean_final.frag`) | :26744 (fn :18341) | `mFinalColor` -> swapchain | | **`recordCaptureAt(1)`: user snapshot, world only** | :26774 (fn :16110) | | | `recordDepthUpscale` (#181), **only when `mScaled`** | :26776 (fn :16482) | `displayDepth()` (`.h:1100`) | | Selection outline, `mWorldOverlayHook` (3D overlays, name tags #468) | :26780-26806 | | | Pass 3 HUD attachments | :26814 | | | UI (LLRender VK backend) | after `recordWorld` | | - `prepareFinal(..., force)` (:18145, header `.h:490-494`) is how RLVa's sphere already forces the chain to end in `mFinalColor`. FX can reuse the same mechanism. - **Render scale (#181):** scene stages run at `mRenderW x mRenderH`, everything from the TAA resolve on runs at display size (`.h:1053-1062`). **Target generation registry** (`.h:1063-1090`): any new size-dependent target / descriptor set must follow its rules. External modules get the generation through their Frame, never a view-handle compare. - **Depth:** `D32_SFLOAT`, **no stencil** (`.h:1026`); **reversed-Z**, near = 1 / far = 0 (`llcleanrenderervk.cpp:127-132`). `clean_depth_up.comp` produces the *unjittered* display depth, but only on scaled frames. **At scale 1 under TAA, `displayDepth()` is the jittered `mDepth`.** - **Normals:** the pre-pass writes view-space oct normals to `mAoNormal` RG16F (`.h:1414`, `clean_prepass_normal.frag`), on AO frames only. The deferred G-buffer has G1 oct N/Ng and G2.w "model byte" with **bits 3-7 free** (`vkclean/shaders/clean_gbuffer.glsl:8-13, :48`). Deferred is *not used for captures / offscreen renders* (`.h:1925-1926`). - **Motion vectors:** `mMotion` RG16F, UV units, current minus previous, unjittered (`.h:1673-1682`, `clean_prepass_normal.frag` header). Written by the pre-pass when temporal is on. - **Rigged / avatar identification:** instance `flags` bit0 = rigged, **bit9 = worn rigid attachment** (`skin_id` = joint-table slot), bit1 alpha, bit2 mask, bit15 vegetation (`indra/newview/llcleaninstancestore.h:61-67`; `clean_draw.vert:118`). `SkinMeta.avatar_idx` (`llcleanskinstore.h:78-80`) and the per-avatar joint slot (`ensureAvatarSlot`, `.h:104-115`) give a per-pixel **avatar index** without any CPU per-face work. Rigged draws already have their own pre-pass pipeline twins (`mPreMvRigPipe` etc., `llcleanrenderervk.h:1683-1688`) and skinned verts in `mSkinVbo` (used by `recordSelection` :8082 to redraw rigged geometry). - **Lighting:** one `lightSurface()` (`clean_surface_light.glsl:86`) shared by forward and `clean_deferred_light.comp`. - **Snapshots:** offscreen recordings go through the same `recordWorld` (`beginOffscreen` :15439, `mOffscreen`). Supersampled snapshots (#417) accumulate N jittered recordings. - **Device lost (#135):** `vk_device_lost_handler` (`llappviewer.cpp:4043`) writes diagnostics and `gpuLostWriteMarker()` (:3823), which tells the next launch. `vkBreadcrumb(cmd, name)` (`vk_common.h:38`) uses static names (`llcleanfsr3vk.cpp:545`). `gVkFatalHandler` turns a failing `VK_CHECK` into LL_ERRS, so **user content must never go through `VK_CHECK`**. - **GPU timers:** `gpuSubMark` / `gpuTimerMark` (`.h:1175`). - **Shaders:** offline only. `vkclean/regen_world_spv.py` -> `glslc` -> embedded `vk_world_shaders_spv.h`. **No runtime GLSL compiler** (no glslang/shaderc in 3p). Vulkan headers come from the system SDK (`indra/cmake/VulkanGfx.cmake`), so SPIRV-Headers are not guaranteed on CI / Windows cross. - **Swapchain:** SDR sRGB only (`vk_swapchain.cpp:63`), BGRA (`swapBytesToRGB`, `llcleanrenderervk.cpp:16119`). - **Photo Studio:** pages cam / lens / look / scene / sky / scopes (`skins/default/xui/en/panel_photo_studio_*.xml`), slot model with section dropdowns + history (`slfloaterphotostudio.h:67-68, 98-170`). --- ## 3. Architecture ### 3.1 Compiler: vendor `reshadefx` (BSD-3) and run no GLSL compiler at runtime - Vendor `crosire/reshade` `source/effect_*` (lexer, preprocessor, parser, symbol table, expression, `effect_codegen_spirv.cpp`, headers). That's about 20 files. Also vendor the Khronos `spirv.hpp` + `GLSL.std.450.h` (MIT-style; system SDK headers aren't guaranteed). Location: `indra/newview/vkgfx/third_party/reshadefx/` with `LICENSE.md` + `NOTICE`. - reshadefx emits SPIR-V directly, so **no glslang/shaderc dependency**. That is the main enabler. Codegen layout (`effect_codegen_spirv.cpp` ~:984-1094): **set 0 = global uniform UBO, set 1 = combined samplers, set 2 = storage images**. Options: `vulkan_semantics`, `flip_vert_y`, `uniforms_to_spec_constants`, `enable_16bit_types`. - Compile on a **worker thread**. Key `(file hash, includes hash, defines, BUFFER size)` -> SPIR-V cache on disk. Pipelines go through `vk_pipecache`. - Pin one ReShade release (track its `__RESHADE__` value). Update deliberately. ### 3.2 Runtime (`newview/vkgfx/llcleanfxvk.{h,cpp}`, a module like FSR3 / god rays) - **Resources per effect:** textures / render targets (`Width/Height/MipLevels/Format`, `pooled`), created with `VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT` + UNORM/SRGB view pairs for `SRGBTexture` / `SRGBWriteEnable`. Samplers from sampler states. Storage images for compute passes. An **own S8/D24S8 stencil** attachment for passes with `StencilEnable` (our depth has no stencil). Image `source` files loaded via LL decoders (png/jpg/bmp/tga) + a small DDS and `.cube` LUT reader. - **Passes:** one graphics pipeline per pass (dynamic rendering, fullscreen `VertexCount` / `PrimitiveTopology`, blend / write-mask / stencil states, up to 8 RTs, `ClearRenderTargets`, `GenerateMipMaps`). Compute passes use `DispatchSizeX/Y/Z`. Techniques run in user order. Barriers are derived from each pass's read/write sets. - **Uniforms:** one UBO per effect per frame-ring slot; values from the UI / preset. **`source` specials:** `timer`, `frametime`, `framecount`, `date`, `pingpong`, `random`, `key` / `mousebutton` / `mousepoint` / `mousedelta` / `mousewheel` (from LL keyboard / window, only while the world has focus), `bufready_depth` (true), `overlay_open` (Photo Studio effects panel open), `overlay_active` / `hovered` (UI), `screenshot` (= `mOffscreen`). - **Preprocessor:** `__RESHADE__`, `__RENDERER__` = 0x20000+ (Vulkan), `__VENDOR__` / `__DEVICE__`, `BUFFER_WIDTH/HEIGHT/RCP_*`, `BUFFER_COLOR_BIT_DEPTH` (8 at the display point), `BUFFER_COLOR_SPACE` (1 sRGB; 2 scRGB at the HDR point), `__SLIPSTREAM__` (version) for opt-in extensions. - **Built-in headers:** ship `ReShade.fxh` (**CC0-1.0**, `SPDX` in its header) and a **clean-room `ReShadeUI.fxh`**. The upstream one has no licence (the `reshade-shaders` repo has no LICENSE file), but it's only annotation macros. - **Generation registry:** the module's descriptor sets are gen-keyed via its Frame struct (rule 4, `.h:1084`). - **Hot reload:** a file watcher on the effect search paths recompiles changed effects on the worker and swaps them atomically on success. The old version keeps running on error. - **GPU timer per technique** (`gpuSubMark` with interned names) and **breadcrumb per pass**. ### 3.3 Injection points | Point | Position | Image | Advertised as | Use | |---|---|---|---|---| | **Display (default)** | after `recordAA` (:26734), **before** RLVa sphere and `recordFinal` | ping-pong LDR targets, `mColorFormat`-compatible, forced via `prepareFinal(force)` | `BUFFER_COLOR_SPACE 1`, 8-bit | all stock ReShade packs: they expect a post-AA, display-referred backbuffer, as in games | | **HDR scene** (opt-in per effect) | after bloom, before `recordTonemap` (~:26661 -> :26718) | `mHdr` RGBA16F linear, exposure-scaled | `BUFFER_COLOR_SPACE 2`, 16-bit | HDR-aware effects; our own grading-like built-ins | | **In-scene pre-TAA** (built-ins only) | after pass-1 lighting, before pass 2 (~:26146) | `sceneHdr()` render-res, jittered | n/a | cel outlines (TAA anti-aliases them) | Why this order: - **RLVa vision restriction stays last** (an effect can't be used to bypass `@setsphere`). - Lightbox lens/grain/dither run *after* user effects, like a camera's last stage. - Everything stays pre-snapshot-capture and pre-HUD/UI. ### 3.4 Input mapping - `ReShade::BackBuffer` / `COLOR` -> the chain's current image at the point. - `ReShade::DepthBuffer` / `DEPTH` -> **unjittered display depth**. Run `clean_depth_up.comp`'s unjitter mapping at 1:1 too when FX wants depth, otherwise depth effects shimmer under TAA. Our reversed-Z matches ReShade.fxh's default `RESHADE_DEPTH_INPUT_IS_REVERSED 1`. Define `RESHADE_DEPTH_LINEARIZATION_FAR_PLANE` from the far clip (compile-time, recompile on draw-distance change). Alternative: hand effects linear depth / far and set `FAR_PLANE 1`. Pick in P1 after testing MXAO / DOF-style effects. - **Slipstream extensions** (custom texture semantics, bound like ReShade add-on `update_texture_bindings`): `SLIPSTREAM_NORMAL` (view-space normal, display res), `SLIPSTREAM_MOTION` (UV-unit MV), `SLIPSTREAM_AVATAR_MASK` (R8 / R16 id), `SLIPSTREAM_SCENE_HDR`. Effects that don't declare them are unaffected. Normals or motion force the corresponding pre-pass twin, as AO does. - **Per-effect scope** (host feature, works with any effect): *whole frame* / *avatars only* / *everything except avatars*. The host keeps the pre-effect image and lerps by the resolved avatar mask. Caveat: neighbourhood effects (bloom, blur) bleed at the mask edge; a feather slider helps. ### 3.5 UI (recommendation) - **An "Effects" page in Photo Studio** (new `panel_photo_studio_effects.xml` + section in the slot model). It sits next to grading, gets history / undo and Find for free, and holds: - the effect list (search, enable, drag-order, per-technique ms) - the annotation-generated controls: slider / drag / input / combo / radio / color / bool, `ui_category` collapsibles, `ui_text`, tooltips - per-effect preprocessor definitions (recompile) and scope - compile errors inline - **Quick access outside Photo Studio:** Preferences > Graphics "Effects: on/off + preset" dropdown, and a toggle keybind (ReShade users expect one). - **Presets** use ReShade's **`.ini`**: `Techniques=Name@file.fx,...`, `TechniqueSorting=`, `PreprocessorDefinitions=`, one `[file.fx]` section of `uniform=values`. Import and export as-is; store in `~/.slipstreamviewer/fx/presets/`. Unknown keys are preserved on round-trip. - **Search paths:** our `fx/` dir + user-added folders. The "Import ReShade install" button points at `reshade-shaders/Shaders` + `Textures`. ### 3.6 Safety - No `VK_CHECK` on any user-content path. Pipeline / shader-module failure disables that effect with a message. - Bounds: - `robustBufferAccess2` / `robustImageAccess2` on FX pipelines where available - caps on texture size (16384), MipLevels, pass count, dispatch size - an **FX VRAM budget** via `llvrambudgetvk` (soft cap, refuse load beyond it) - **Device loss:** if the stack was active, `gpuLostWriteMarker` also records the last FX breadcrumb. - The next launch starts **with FX off** and a notice naming the effect. - That effect is quarantined until the user re-enables it. - "Provisional" flag on newly loaded / edited effects: it clears after N good frames, and a loss while provisional quarantines without asking. - Safe-mode launch switch: `--no-fx` / a settings key. - Known injected ReShade / vkBasalt detected (Windows `dxgi.dll` / `opengl32.dll` next to the exe, `VK_LAYER_reshade*` / `ENABLE_VKBASALT`) -> one-time warning about double application. ### 3.7 Performance budget - Runtime overhead beyond the effects' own work: **≤ 0.05 ms**. That covers the ping-pong + barriers; with zero effects, nothing is recorded and no targets are allocated. - Cel shading (lighting + mask + outlines): **≤ 0.3 ms at 1440p** on a 3060-class GPU. - The UI shows per-technique GPU ms and warns when the stack exceeds a user budget (default 3 ms). - Effects compile off-thread. Any compile hitch on the main thread is a bug. ### 3.8 Snapshots - Effects run inside offscreen recordings automatically (same `recordWorld`). The `screenshot` source = true; `enabled_in_screenshot = false` techniques are skipped. - `BUFFER_WIDTH/HEIGHT` are **compile-time**, so a custom-size snapshot needs recompiling and re-creating effect targets at that size. Mitigations: - a per-size module cache - warming the compile when the snapshot floater's size changes - an extra VRAM check (abort to "effects off" for the shot rather than fail) - Supersampled shots (#417): effects run **only on the last accumulation recording** (on the converged mean), not N times. - **360:** screen-space effects per cube face produce seams, so FX is off for 360 in v1 (cel *lighting* still applies; it's in-pass). ### 3.9 Linux / Windows - Pure C++17 + Vulkan, so the same code on both. - File watching uses the platform's API (inotify / ReadDirectoryChangesW) or polling mtimes once a second (simpler, recommended). - Paths with non-ASCII characters go through LL's UTF-8 file APIs. - The Windows cross-build (`windows-cross` preset) must compile the vendored reshadefx under clang-cl. It's plain C++17; watch for the `/Yu` PCH note (keep it out of PCH). --- ## 4. Cel-shading design (first built-in) Pure post-processing can't do convincing toon shading. After lighting, albedo and light are baked together, so posterizing gives muddy bands. The design is therefore a **hybrid**: **4.1 Toon lighting (in-pass, material stage).** - What it does: inside `lightSurface()`, a flagged surface uses - a quantized sun/moon N·L ramp (N bands, band softness, shadow tint) - banded local lights - a flattened ambient - optional rim light (view-dependent) - optional specular step - How the flag reaches the shader: - **Forward:** derived from the instance (bit0 / bit9 / avatar index) + a **per-avatar toon bitset SSBO** indexed by avatar slot. Toggling scope or avatars only rewrites a small buffer, with no re-ingest and no per-face CPU work. - **Deferred:** 1 bit in **G2.w model byte** (bit 3; bits 3-7 free, `clean_gbuffer.glsl:48`). It is written from that same lookup in the G-buffer FS. - Both back ends share `lightSurface()`, so forward-only paths (snapshots, mirror, probe captures) get the same look. Whole-scene mode just sets the global flag. **4.2 Avatar mask (also used by FX scope and `SLIPSTREAM_AVATAR_MASK`).** - **Format:** a dedicated **R16_UINT** id attachment: `avatar slot + 1`, 0 = not avatar. - **Writer:** written by the depth pre-pass twins when any consumer wants it. The pre-pass is on by default (`RenderVKDepthPrepass`, :25051) and runs offscreen too. When needed, it is forced on. - **Contents:** includes rigged (bit0), **worn rigid attachments (bit9)** and the system avatar body. Without the attachments, seams appear at hair / jewellery / prim attachments. - Not the G-buffer, because that path is skipped for captures. Not `mAoNormal`, because both channels are used and it exists only on AO frames. - **Gap:** alpha-*blended* rigged surfaces (hair, sheer clothing) render in pass 2 and aren't in the pre-pass. v1 includes a tiny extra pass that re-issues pass 2's rigged indirect lists into the mask (depth-test, no depth write, alpha > threshold). Without it, outlines stop at the hairline. - **Display-res, unjittered resolve:** same mapping as `clean_depth_up.comp` (`floor(pr + jitter)`), for post-chain consumers. **4.3 Outlines.** - **Default: screen-space edge pass (native compute, "in-scene pre-TAA" point).** - Runs at render res on `sceneHdr()` after pass-1 lighting, before pass 2. TAA anti-aliases the lines, and alpha / water composite correctly on top. - Edges come from (a) **mask/id discontinuity**, giving the silhouette against the world and between two avatars; (b) **depth discontinuity** (linear, slope-scaled), giving self-occlusion (arm over body); (c) **normal crease** (pre-pass normal) for inner lines. - Avatars-only mode restricts it to a dilated mask, so the outer silhouette is drawn on the world side too. - Width is in display pixels (scaled by render scale). The line colour is exposure-aware (reads `mExposureImg`). Distance fade. - Cost: one full-screen compute, independent of triangle count. - **Optional "inverted hull" mode (later):** - One extra multi-draw-indirect over the rigged buckets from `mSkinVbo` (world-space skinned normals already there), with front-face cull and clip-space extrusion along the normal (spec constant in `clean_draw_rigged.vert`). - Gives classic anime outlines that occlude correctly in depth and survive into mirrors / probes. - Costs triangle throughput (heavy mesh bodies), and gaps appear on hard-split normals. - Fully GPU-driven, so it is allowed, but it's not the default. **4.4 Controls:** - scope (off / whole scene / all avatars / self / friends / chosen avatars; right-click avatar -> "Cel shade this avatar") - bands, softness, shadow tint, rim strength / width - outline width / colour / depth sensitivity / crease sensitivity - These live in Photo Studio "Effects" (built-in section) + render settings `RenderVKCel*`, and get saved in Photo Studio presets. **4.5 Not FX:** cel lighting needs the in-pass hook, so it's a native render feature. The mask is still exposed to FX, so community outline / toon effects can be restricted to avatars too. --- ## 5. Compatibility strategy and known gaps - **Target:** effects that run on stock ReShade 5/6 with a depth buffer, using `ReShade.fxh`. - **Gaps / risks:** - effects needing ReShade **add-ons** (`ADDON_*` macros undefined) or add-on-provided resources - D3D-only codegen quirks (DXBC / DXIL-only intrinsics) — reshadefx abstracts these - effects that read the depth buffer of a *different* convention via hard-coded hacks - very large `pooled` texture sets (VRAM) - 360 - effects relying on `__RENDERER__` being D3D - **Test matrix (P8):** SweetFX (MIT), prod80 (MIT), qUINT (all rights reserved), iMMERSE free tier (restrictive), Lilium HDR shaders (GPL-3, HDR point), plus ReShade's own `reshade-shaders` sample effects. The last four are **user-installed only**. - vkBasalt (Zlib) is a reference for a host that embeds reshadefx on Vulkan, nothing more. It lacks multi-technique effects, stencil and reliable depth, and we expect to exceed it. ## 6. Licensing - **reshadefx:** BSD-3-Clause (crosire/reshade `LICENSE.md`), compatible with the LGPL-2.1 viewer. Keep the copyright notice in source + About / credits. - **SPIRV-Headers:** Khronos MIT-style. Same handling. - **`ReShade.fxh`:** CC0-1.0 (SPDX line in the file). OK to ship. - **`ReShadeUI.fxh`:** no licence found, so write a clean-room equivalent. - **Do not bundle:** - qUINT and iMMERSE (Pascal Gilcher: "All rights reserved"; iMMERSE §1 forbids redistribution, §4 needs permission to reuse parts) - Lilium HDR (GPL-3) - SweetFX / prod80 are MIT and *could* be bundled with notices. Recommendation: **bundle none in v1**; built-ins are ours. A curated MIT starter pack is a later decision. - vkBasalt: reference only, no code copied. ## 7. Phased plan (sizes = agent-days, rough) | Phase | Ticket | Size | Depends on | |---|---|---|---| | P0 | #543 FX P0: vendor reshadefx (BSD-3) + SPIR-V smoke test — go/no-go gate | S (1-2 d) | - | | P1 | #544 FX P1: effects runtime on the VK post chain (display injection point, BackBuffer/DepthBuffer) | L (5-8 d) | P0 | | P2 | #545 FX P2: Effects page in Photo Studio + ReShade .ini presets | M-L (4-6 d) | P1 | | P3 | #546 FX P3: safety — non-fatal user content, device-lost quarantine, safe start, VRAM cap | M (2-4 d) | P1 (before FX is user-visible) | | P4 | #547 FX P4: effects in snapshots (custom size, supersampled, 360 policy) | M (2-3 d) | P1, P3 | | P5 | #548 FX P5: Slipstream extensions — HDR point, normals/motion/avatar-mask semantics, per-effect scope | M (3-4 d) | P1, P6 | | P6 | #549 Cel P6: avatar mask — per-pixel avatar id from the GPU-driven pre-pass | M (3-4 d) | - (cel track, parallel) | | P7 | #550 Cel P7: built-in cel shading — toon lighting + outlines, whole scene or avatars only | L (5-7 d) | P6 | | P8 | #551 FX P8: compatibility sweep (SweetFX, prod80, qUINT, iMMERSE free, Lilium HDR) + gap fixes | M (3-5 d) | P1-P5 | Ordering: - **P0** is the FX go/no-go gate. - The **cel track (P6 -> P7)** is independent of P0-P2 and can start in parallel. - **P5** needs P1 + P6. - **P3** must land before FX is user-visible. ## 8. Risks 1. **Compile-time `BUFFER_WIDTH/HEIGHT`** -> recompiles + target re-creation on resize and every custom-size snapshot (VRAM spike, hitch). Mitigated by the per-size cache + worker compile. 2. **GPU hangs from user shaders** can't be prevented, only contained (device loss -> marker -> safe start + quarantine). On Windows, a TDR can reset the whole display. 3. **reshadefx SPIR-V vs our driver set:** codegen quirks (16-bit types, `DemoteToHelperInvocation`, cf. `vk_device.cpp:1338`), validation errors, upstream drift. P0 measures this. 4. **Avatar mask completeness:** alpha-blended hair / clothing, system avatar body, attachments, own avatar in mouselook, impostors / jelly dolls. Each needs an explicit rule. 5. **Scope creep in UI:** ReShade's ImGui overlay is rich, and annotation-driven LLUI generation is the largest single UI task. 6. **Temporal effects vs our TAA:** effects with their own history (TAA-like, motion blur) at the display point see an already-resolved image. Expect them to be redundant or to fight TAA; document it. ## 9. Go / no-go - **Cel shading (P6, P7): GO now.** Native, fits the GPU-driven architecture (flag lookup via SSBO, MDI only, no per-face paths), low risk, and high user value. - **ReShade FX runtime (P0-P5, P8): GO contingent on P0.** Gate criteria: - reshadefx builds in-tree on Linux + wincross - ≥ 90% of the SweetFX + prod80 + reshade-shaders sample effects compile - output passes `spirv-val` for Vulkan 1.3 - modules load on our device If P0 fails badly, fall back to "Slipstream GLSL effect format for built-ins only" and drop compatibility.
sky_line added this to the Slipstream project 2026-10-08 06:28:03 +00:00
Author
Owner

Status: P0-P7 shipped (Beta 4/5) and closed (#543-#550). Only the compatibility sweep, #551 (P8), remains.

Status: P0-P7 shipped (Beta 4/5) and closed (#543-#550). Only the compatibility sweep, #551 (P8), remains.
Author
Owner

P0-P7 (effects runtime, ReShade FX, cel shading) shipped and closed (#543-#550). Only the compatibility sweep #551 (P8, low priority) remains and nothing is being worked on, so this moves from In Progress to To Do.

P0-P7 (effects runtime, ReShade FX, cel shading) shipped and closed (#543-#550). Only the compatibility sweep #551 (P8, low priority) remains and nothing is being worked on, so this moves from In Progress to To Do.
Author
Owner

P0-P7 (effects runtime, ReShade FX, cel shading) have shipped and closed. Only the low-priority compatibility sweep #551 remains, so the epic goes back to the backlog.

P0-P7 (effects runtime, ReShade FX, cel shading) have shipped and closed. Only the low-priority compatibility sweep #551 remains, so the epic goes back to the backlog.
Author
Owner

Fixed in Slipstream 1.3.0 (stable): https://git.slipstreamviewer.org/sky_line/Slipstream/releases/tag/v1.3.0

If you still see this on 1.3.0, reply here and we'll reopen it.

Fixed in **Slipstream 1.3.0** (stable): https://git.slipstreamviewer.org/sky_line/Slipstream/releases/tag/v1.3.0 If you still see this on 1.3.0, reply here and we'll reopen it.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sky_line/Slipstream#542
No description provided.