Skip to content

Picture in Picture Port | True PiP [Multithreaded Branch] - #578

Closed
TheLostInPlace wants to merge 44 commits into
themrdemonized:all-in-one-vs2022-wpo-mtfrom
TheLostInPlace:final-pip-mt
Closed

TheLostInPlace wants to merge 44 commits into
themrdemonized:all-in-one-vs2022-wpo-mtfrom
TheLostInPlace:final-pip-mt

Conversation

@TheLostInPlace

@TheLostInPlace TheLostInPlace commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

True picture-in-picture scopes + per-scope optics + SVP DLSS-SR scaffolding (DX11)

Summary

Adds true picture-in-picture (PiP) scope rendering to the MT engine: the scoped image is a real
second render of the scene from the optic's camera, composited through the lens instead of the flat
FOV/zoom trick. On top of that it adds a physically-derived optics layer (real per-scope eye
relief / exit pupil, two-zone eye-box, lens cosmetics), a 3DSS cosmetic gate that removes the
flat-scope parallax-shadow crescent for optical scopes, SVP performance levers (frustum + small-
object culling, adaptive scope resolution, supersample), and all engine-side scaffolding for a future
DLSS-SR upscale of the scope render (reconstruction left as a documented stub).

The feature is DX11-only (R4), additive, and off by default. With r__svpscope 0 the render is
byte-identical to stock MT, and the DX10/9/8 renderers (R1/R2/R3) are fully insulated.

  • Base branch: all-in-one-vs2022-wpo-mt
  • Scope: 74 files, +4786 / -336, 21 commits

Requirements

PiP renders the scene into the in-world scope lens, so it relies on the 3D Shader Scopes (3DSS)
GAMMA
addon to supply that lens geometry and reticle. 3DSS is already bundled in GAMMA, so GAMMA
users have it by default and no extra setup is needed. Without 3DSS (or an equivalent scope-lens
setup) there is no lens surface for the PiP image to composite into, and r__svpscope has nothing
to draw. The real per-scope optics path (r__svp_optics_real) reads the eye-relief / exit-pupil
values from the 3DSS scope config; scopes without those entries fall back to geometry-derived values.

What's new (user-facing)

  • True PiP scope — a live second-viewport render of the scene sampled by the lens shader (real
    parallax, depth, and lighting inside the optic), with smooth and analog (variable) zoom, magnifier
    reticle, and an over-zoom fix
  • Real per-scope optics — eye relief and exit pupil taken from the 3DSS scope config drive a
    physical eye-box (the dark vignette tightens as you move off the exit pupil and as magnification
    rises), with an optional two-zone (exit-pupil + eye-relief) model
  • 3DSS cosmetic gate — for true-PiP optical scopes, suppresses the 3DSS flat-scope parallax-
    shadow crescent / blackout, chromatism, and NVG blur at the source (thermals and see-through optics
    keep their stock look)
  • Lens cosmetics — edge chromatic aberration, barrel distortion, exit-pupil dimming, radial
    chromatic fringing, radial vignette, procedural dirty-glass grunge + edge rim, and a field-curvature
    edge ring blur, all gated and tunable, with an in-game tuner page
  • SVP performance — frustum culling and magnification-aware small-object culling of the scope
    geometry, adaptive scope resolution (render the scope to the on-screen lens disc), and optional
    supersample
  • Per-viewport lighting, shadows, motion vectors, and skybox so the scope image is correctly lit
    and TAA-stable while panning (including a per-viewport sky-prev fix so the sky reprojects correctly
    under the scope camera)
  • Recoil gate — an optional runtime gate for script-mod per-shot camera kick so the scope stays
    on the eyeline; base engine/weapon recoil is untouched
  • Scope-pipeline debug visualization
  • Gated DLSS-SR plumbing for the scope render (reconstruction not yet implemented)

Console Variables (default collapsed)

Console variables

The r__ cvars are registered only under DX11, so they do not exist on DX10/9/8. The g_zoom_* cvars are engine-side and apply on every renderer.

Core

cvar type default purpose
r__svpscope int [0, 2] 0 enable true PiP scope rendering (0 off, 1 eyepiece, 2 objective)

Real optics & eye-box (the physical model, master-gated by r__svp_truepip)

cvar type default purpose
r__svp_truepip float [0, 1] 0 physical eye-box master strength + 3DSS cosmetic gate (0 off = stock 3DSS shadow)
r__svp_optics_real float [0, 1] 1.0 real per-scope eye-relief + exit-pupil from the 3DSS config (1) vs scope geometry (0)
r__svp_optics_soft float [0.02, 0.6] 0.15 eye-box falloff width (lens-local)
r__svp_optics_gain float [0, 5] 1.0 eye-offset sensitivity (crescent onset speed)
r__svp_optics_zoomvig int [0, 1] 1 magnification-scaled eye-box vignette toggle
r__svp_optics_zoomvig_blend float [0, 1] 1.0 zoom-vignette blend (0 static exit-pupil .. 1 mag-scaled)
r__svp_eyebox_relief float [0, 1] 0 two-zone eye-relief zone strength (0 = single-zone)
r__svp_eyebox_relief_size float [0.1, 1] 0.5 eye-relief clear-zone radius (lens-local)
r__svp_eyebox_relief_soft float [0.02, 0.6] 0.25 eye-relief falloff softness
r__svp_eyebox_aspect float [0.3, 3] 1.0 clear-zone ellipse stretch (1 = circular)

Lens FX (gated by r__svp_lensfx; cosmetics also active under r__svp_truepip)

cvar type default purpose
r__svp_lensfx int [0, 1] 0 enable lens FX (edge CA, barrel, exit-pupil dim, tunnel)
r__svp_lensfx_strength float [0, 2] 1.0 lens FX overall strength
r__svp_lens_ca float [0, 0.02] 0.0018 chromatic aberration in the ring
r__svp_lens_distort float [0, 0.05] 0.0040 barrel distortion in the ring
r__svp_lens_floor float [0, 1] 0.10 tunnel ring brightness floor
r__svp_lens_refmag float [1, 16] 6.0 tunnel reference magnification (open at or below)
r__svp_lens_vigk float [0, 4] 1.5 tunnel close rate vs magnification
r__svp_lens_blur float [0, 0.03] 0.0040 radial edge blur amount
r__svp_lens_fringe float [0, 1] 0 radial chromatic fringing toward the edge
r__svp_lens_vignette float [0, 1] 0 radial vignette darkening (independent of the tunnel)
r__svp_lens_vignette_r float [0, 1] 0.6 vignette onset radius (0 center .. 1 rim)
r__svp_glass_dirt float [0, 1] 0 dirty-glass procedural grunge on the ocular
r__svp_glass_rim float [0, 1] 0 soft highlight rim at the lens-glass edge
r__svp_dof float [0, 0.10] 0.040 field-curvature edge ring blur (rim softens, center sharp)
r__svp_dof_onset float [0, 1] 0.55 ring blur onset radius
r__svp_eyebox float [0, 1] 0 legacy dynamic eye-box crescent (3DSS override)
r__svp_eyebox_shift float [-25, 25] 0 legacy crescent drift gain (negative flips the side)
r__svp_lens_menu_key int [0, 9] 0 lens FX tuner open key (PiP options dropdown index)
r__svp_stabilize float [0, 1] 1.0 sway reduction, blends the lens toward aim (0 full sway, 1 tracks aim)

Performance

cvar type default purpose
r__svp_cull int [0, 1] 1 frustum-cull the scope geometry to the scope cone
r__svp_cull_ssa float [0, 8] 4.0 magnification-aware small-object cull strength (0 off)
r__svp_cull_grass int [0, 1] 1 cull grass instances to the scope cone
r__svp_lod float [0, 1] 0 scale scope LOD selection to its pixel coverage (0 off)
r__svp_adaptive_res float [0, 2] 1.2 size the scope render to the on-screen lens disc * margin (0 off)
r__svp_supersample float [1, 2] 1.0 SSAA the scope image (applies on vid_restart)
r__svp_skip_motionblur int [0, 1] 0 skip motion blur on the scope pass
r__svp_skip_ssr int [0, 2] 1 scope reflections (0 reflective+SSR, 1 flat+SSR, 2 flat no-SSR)
r__svp_skip_volumetric int [0, 1] 0 skip volumetric lights on the scope pass
r__svp_skip_grass int [0, 1] 0 skip grass/details on the scope pass
r__svp_sss_sun int [0, 1] 0 keep the SSS sun contact-shadow term on the scope (expensive)
r__svp_diag int [0, 1] 0 scope perf diagnostics log

Recoil

cvar type default purpose
r__truepip_recoil int [0, 2] 2 gate the script recoil mod's per-shot camera kick (0 normal, 1 off in true-PiP, 2 off always); base engine recoil untouched

Objective mode (r__svpscope 2)

cvar type default purpose
r__svp_obj_dist float [0, 3] 1.0 objective front-distance scale (auto geomscan, fixed fallback)
r__svp_obj_size float [0.1, 6] 0.65 objective radius = eyepiece radius * this

Reticle / DLSS scaffolding / debug / dynamic zoom

cvar type default purpose
r__svp_reticle_mag int [0, 1] 1 engine-drive the 3DSS reticle magnification so the FFP reticle scales
r__svp_dlss int [0, 1] 0 enable the DLSS-SR scope path (currently a passthrough stub)
r__svp_render_scale float [0.4, 1.0] 1.0 scope render-extent scale, inert until DLSS is enabled
r__scope_debug int [0, 4] 0 scope-pipeline debug visualization
g_zoom_smooth float [0, 60] 12 dynamic-scope zoom smoothing rate (lerp per second), 0 is instant stepped
g_zoom_analog float [0, 200] 0 dynamic-scope analog zoom, 0 is the discrete config steps, higher gives finer steps

How it works

Double-pass / second viewport. When PiP is active the frame runs an extra scene pass for the
optic camera into a dedicated second-viewport target (TargetSVP). A hybrid frame selector routes
work between the scope pass and the main pass:

  • off: legacy throttled SVP behaviour (unchanged)
  • on (true_pip_on): per-pass selection driven by m_render_pass_is_svp

Lens composite. The scope target is sampled by the lens shader and composited on-screen with the
reticle, supporting smooth and analog zoom.

Real optics & cosmetic gate. With r__svp_truepip on, the eye-box is built from real geometry:
eye relief and exit pupil come from the 3DSS scope config (r__svp_optics_real), the clear zone grows
with the exit pupil and tightens with magnification, and the eye offset is the real bore-vs-aim drift.
For optical scopes it also clears the 3DSS flat-scope SETTINGS bits (parallax shadow, chromatism, NVG
blur) and strips the fisheye so the magnified image is clean, while thermals and see-through optics are
left untouched.

Per-viewport correctness. Lighting, shadows (via the shared-shadow accumulation hook), motion
vectors, and the skybox previous-frame transform are tracked per viewport. Previous-frame matrices,
foliage transforms, and the sky-prev matrix live in [2] arrays indexed by the active viewport, so the
scope image is correctly lit and stays motion-vector / TAA stable when panning.

Performance. The scope geometry is frustum-culled to the cone, and a magnification-aware small-
object cull (r__svp_cull_ssa) drops tiny distant clutter that the magnified cone can't resolve — the
SVP g-buffer is draw-call bound, so this is the main lever. Adaptive resolution sizes the scope render
to the on-screen lens disc, and supersample optionally over-renders for SSAA.

Recoil gate. r__truepip_recoil controls a small runtime gate (zzz_truepip_recoil_gate.script)
that neutralizes a script recoil mod's per-shot camera/PP/FOV kick while scoped, so the optic stays on
the eyeline. It is a runtime monkey-patch only — it edits no files, and the base Anomaly engine/weapon
recoil is untouched.

DX11-only insulation. true_pip_on is hard-forced false on DX10/9/8 and the cvars above are
not registered there, so every shared render path collapses to stock for R1/R2/R3.

DLSS-SR scaffolding (gated, eval stubbed)

All engine-side plumbing for a Streamline DLSS-SR upscale of the scope render is present behind
r__svp_dlss, with the reconstruction itself left as a documented stub for the Streamline
integration:

  • scope rendered at a render-extent (g-buffer, lighting, and motion vectors scale together)
  • sub-pixel jitter baked into the scope projection (placeholder Halton sequence)
  • engine AA skipped under the gate so the eval receives a raw aliased and jittered image
  • an atomic history-reset flag fired on the events that should reset temporal history (ADS-in, lens
    first appears, zoom-type change, target re-create)
  • a POD input contract (SvpDlss.h) carrying color, depth, and motion-vector SRVs, the output
    target, matrices, jitter, and camera params, with zero sl:: / NGX dependencies
  • EvalSVP_DLSS is a bilinear/copy passthrough, and replacing its body is the only remaining step

SvpDlss.h documents the open interface items (depth form, output format, exposure, colour space,
jitter convention, resource state) for whoever wires up the actual eval.

Invariants held:

  • at r__svp_dlss == 0 the output and GPU work are byte-identical to the non-DLSS path for any
    r__svp_render_scale
  • every touched cross-thread structure has a verified read/write thread map, and the single
    game-to-render flag is a std::atomic

Compatibility & safety

  • DX11 (R4): the feature
  • DX10/9/8 (R3/R2/R1): insulated, true_pip_on false, cvars unregistered, shared gates collapse to stock
  • Off by default: r__svpscope 0 is stock behaviour
  • Zero regression when off: verified byte-identical render at the gate

Testing

Tested on Anomaly 1.5.3 with GAMMA (3D Shader Scopes, SSS, Atmospherics):

  • gate-off: no visual or performance regressions
  • PiP on: scope renders the live scene with correct lighting and parallax; variable zoom, magnifier
    reticle, smooth zoom, real per-scope eye-box, and the 3DSS cosmetic gate all working
  • lens cosmetics (CA, fringing, vignette, dirty-glass, ring blur) and the in-game tuner verified
  • small-object cull verified clean (no popping) in open terrain and dense areas
  • skybox reprojects correctly under the scope camera (no sky smear/ghosting on pan)
  • DLSS gate on: scope upscales through the stub (passthrough) with no artifacts beyond expected aliasing
  • full engine build green across R1 to R4 (DX11-AVX)

Known limitations

  • The DLSS-SR reconstruction is not implemented, so r__svp_dlss 1 runs a passthrough stub
  • The recoil gate (r__truepip_recoil) targets a script recoil mod's camera kick; with no such mod
    present it is a no-op (base engine recoil is never touched)

Attribution

The true picture-in-picture scope code was written by m22spencer. This is a port of it from the
gc64 fork that adopted it (CnRJay/xray-monolith-gc64).
The DX11 second-viewport rendering, the lens composite (draw_scope), the per-viewport history, and
the DLSS input contract all follow that implementation. The per-scope optics, cosmetic gate, lens
cosmetics, and SVP performance work were added on top.

  • m22spencer: original author of the true PiP scope implementation
  • gc64 (CnRJay/xray-monolith-gc64): the fork that adopted it, the source this port is taken from
  • Redotix99, 3D Shader Based Scopes (3DSS): the scope lens and reticle content this builds on
  • themrdemonized: the base xray-monolith engine this targets

Files changed

74 files, grouped by area

Gamedata (new)

  • gamedata/scripts/zzz_extra_scope_features.script: magnification and zoom utility
  • gamedata/scripts/zzz_truepip_recoil_gate.script: optional script-recoil gate
  • gamedata/scripts/lens_fx_tune.script, gamedata/configs/ui/ui_lens_fx_tune.xml: in-game lens FX tuner page
  • gamedata/scripts/options_modded_exes_pip.script: PiP options page
  • gamedata/shaders/r3/scope_lensfx.{ps,s}: lens FX (CA, barrel, dim, eye-box, cosmetics)
  • gamedata/shaders/r3/scope_{debug,depth_write,color_write,vertex,custom_depth,defines}.*: scope debug and depth/colour-write shaders

Console / cvars

  • xrRender_console.cpp / .h, xrEngine/xr_ioc_cmd.cpp
  • xrGame/console_registrator_script.cpp: is_svp_active() Lua binding for the recoil gate

Shared render layer (xrRender, all renderers)

  • R_calculate.cpp: DX10/9/8 PiP gate
  • r__dsgraph_build.cpp, r__dsgraph_render.cpp, r__dsgraph_manager.h, r__dsgraph_types.h: scope-pass routing, shared shadows, SVP cull + objective capture
  • rendertarget_phase_nightvision.cpp: scope capture seam, lens composite, optics/eye-box, eval call
  • dxEnvironmentRender.cpp: per-viewport skybox previous-frame transform
  • R_sun.cpp, lights_render.cpp: per-viewport lighting and shadows
  • R_Backend.h, R_Backend_Runtime.h, R_Backend_xform.cpp / .h: per-viewport matrices
  • FTreeVisual.cpp, SkeletonCustom.h, SkeletonX.cpp, Shader.h, Blender_Recorder_StandartBinding.cpp, HW.h, dxRenderDeviceRender.cpp
  • Debug/renderdoc_app.h (new), Debug/dxPixEventWrapper.h

DX11 / R4 (PiP engine)

  • SvpDlss.h (new): DLSS input contract
  • r4.cpp / .h, r4_R_render.cpp, r4_rendertarget.cpp / .h, r4_rendertarget_phase_combine.cpp

DX10 layer

  • dx10EventWrapper.cpp / .h, dx10HW.cpp, dx10ResourceManager_Scripting.cpp, dx10SH_RT.cpp

Engine (xrEngine)

  • device.cpp / .h: second viewport, matrices, atomic reset flag
  • CameraManager.cpp, x_ray.cpp, bone.h
  • xrEngine.vcxproj: /LTCG:INCREMENTAL on the WPO configs

Core (xrCore)

  • _matrix.h, Xr_ini.cpp, xr_ini.h

Game (xrGame)

  • Actor.cpp / .h, ActorInput.cpp, Level.cpp, Weapon.cpp / .h, WeaponAK74.cpp

Build

  • .gitignore

- true picture-in-picture scope rendering (r__svpscope)
- on-screen lens composite, reticle, smooth and analog zoom, scope debug
- per-viewport lighting, shadows and motion vectors
- DLSS-SR engine scaffolding (r__svp_dlss), eval stubbed
The cvar is DX11-only now so the script gets nil on DX9/DX10; treat nil as not-SVP
@TheLostInPlace TheLostInPlace changed the title Picture in Picture Port | True PiP Picture in Picture Port | True PiP [Multithreaded Branch] Jun 19, 2026
@TheLostInPlace
TheLostInPlace marked this pull request as draft June 19, 2026 06:39
@TheLostInPlace

Copy link
Copy Markdown
Contributor Author

Temporarily putting the PR on draft while I investigate an edge case of scopes not rendering their PiP viewport onto the eyepiece properly on (1) weapon that I have found so far, I thought I fixed the error for all weapons ;-;

@TheLostInPlace
TheLostInPlace marked this pull request as ready for review June 19, 2026 06:45
@TheLostInPlace

TheLostInPlace commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

Alright that was a quick investigation, turns out that weapon had an improperly positioned eyepiece mesh resulting in the PiP drawing incorrectly

For issues with PiP:

  1. Open the console and type r__scope_debug 4
  2. Take a screenshot of the weapon + scope while unaimed + aimed
  3. Post screenshot

In general, you want the blue line to go through the scope, with the upvector lines properly pointing up, and the eyepiece detection circle on the eyepiece

Screenshot of what it should look like:
image

- derive the eyepiece from the lens bone skinning matrix instead of the
  kinematics root, so a scope skinned to an offset bone sits on the glass
  and tracks ADS instead of landing at a fixed offset
- force the SVP camera up to world up so a canted scope renders upright
  while its reticle stays canted
- among multiple lens surfaces keep the eye-nearest in-front one (the ocular)
- take the eyepiece center from the lens AABB
@TheLostInPlace

TheLostInPlace commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

Here's a download link to the current MT build of the PiP exes with the db0 and PDB.

Keep in mind this was tested with the following primary stack on GAMMA:

  • 3DSS GAMMA 4.17
  • ARC Disabled
  • Mark Switch Disabled
  • Thick Russian Reticles Disabled

Installation is similar to how you would install the current exes, so re-pasting it here:

Installation

First, follow the start guide below
Drag and drop the folders in the zip (bin and db) into your anomaly folder

Start Guide

Delete everything in db/mods folders
Delete everything in gamedata/configs folder except localization.ltx, axr_options.ltx and cache_dbg.ltx files
Delete these folders if you have them:
    gamedata/materials
    gamedata/scripts
    gamedata/shaders

New options_modded_exes_pip.script page exposing the SVP scope cvars
as dropdowns/sliders: r__svpscope (OFF/Eyepiece/Objective), g_zoom_smooth,
g_zoom_analog, r__svp_render_scale, r__svp_dlss, r__scope_debug. Registered
in the Visual group next to 3D Scopes.

Adds the eng localization in ui_mm_modded_exes.xml: page title, menu entry,
control labels and hints, plus the ui_mm_lst_Eyepiece/ui_mm_lst_Objective
list-value strings the r__svpscope dropdown resolves through.
Engine scope_lensfx pass over the composited disc (works with any scope mod,
never touches 3DSS): magnification tunnel, radial blur, barrel distortion and
exit-pupil dimming, plus a dynamic eye-box crescent. Gated by r__svp_lensfx /
r__svp_eyebox, every value a cvar.

r__svp_recoil_comp: during fire, pull the SVP camera (orientation and position)
toward the main view in proportion to the live recoil so a PiP scope stays
usable like a shader scope instead of jumping with the weapon-bone kick.
SVP-only, the real recoil is untouched.

In-game tuner overlay (lens_fx_tune.script + ui_lens_fx_tune.xml) plus the lens
cvars and the tuner open key in the Modded Exes Picture in Picture page. Expose
CWeapon zoom methods to Lua so the tuner can drive the scope.

WIP checkpoint: scope image holds in full auto, but the weapon-model recoil can
still dip the scope body across the lens.
r__svp_recoil_hold (0-1): while a PiP/SVP scope is up, damp the weapon
shoot-shake and inertion in CWeapon::UpdateHudAdditional so the optic stays
on the eyeline during fire instead of riding the gun. SVP-only, default off;
iron sights / 3DSS / hipfire keep their full movement.

r__svp_recoil_comp is now orientation-only. Blending the eyepiece position
toward the camera fought svpCamera's FOV sizing (it projects the eyepiece
radius), so the magnified view zoomed out as recoil accumulated then snapped
back. Position steadiness is recoil_hold's job now.

Clean up comments to be more concise
The captured graph is culled to the main frustum and rendered twice; the SVP re-submitted the whole main-frustum world through a magnified cone that sees a fraction of it. Reject items whose world bounding sphere is fully outside the SVP frustum, built from the same matrices[1] SetActive renders the SVP with so it removes exactly what the GPU clips. Gated to the SVP gbuffer pass so the main view and non-PiP / non-MT renderers stay byte-identical. Static geometry carries no per-item matrix, so its world-space vis.sphere is tested directly. Measured: SVP gbuffer ~17ms to 2-8ms, ~13-18ms off the scoped frame, scaling with zoom (96% culled at max). Default on, r__svp_cull 0 restores the old path.
@TheLostInPlace

Copy link
Copy Markdown
Contributor Author

Updated Preview Build

Let me know if there are any issues with this (pop in, LOD issues, etc)

…ter + SSS fixes

Recoil
- Add r__svp_pupil_boost, enlarges the 3DSS scope exit pupil while a PiP
  scope is up so full-auto recoil no longer collapses the lens to black.
  The blackout is the 3DSS parallax exit-pupil shadow driven by V_tangent,
  a larger EXIT_PUPIL widens the clear disc and divides down the recoil shift.
- Remove r__svp_recoil_comp, recoil_hold and recoil_smooth plus the
  g_pip_recoil plumbing, they steered the SVP image not the shadow and
  never fixed the blackout.
- Default stabilize and zoom_smooth to 1, pupil_boost to 6, eyebox_shift to 0.

Performance
- Add r__svp_cull_grass, per-instance frustum reject so the SVP grass is
  culled to the scope cone instead of replaying the whole main field, with
  the cull frustum arming decoupled so grass and geometry cull toggle apart.
- Add r__svp_skip_motionblur, skip_volumetric and skip_grass for the scope pass.

Reflections and water
- Add the r__svp_skip_ssr tri-state, 0 reflective water, 1 flat water plus
  SSR (default), 2 matte.
- Fix the scope water SSR temporal reprojection, build the per-viewport
  current and previous matrices for the SVP so it stops accumulating against
  the main view stale matrices (the matte vs reflective flicker).

SSS
- Add r__svp_sss_sun, compute the SVP SSS pass so the scope keeps the sun
  contact shadow term.
Resolve the r4.cpp shader_compile conflict by composing both shader-cache
changes: upstream's source-CRC cache key (440495c) keys the read/write,
and our r__shader_debug skips the cache entirely plus emits debug flags
for RenderDoc. The two are orthogonal so both are kept.
r__svp_pupil_track ramps pupil_boost with the live bore off-axis drift, so the
exit pupil stays tight when the aim is steady and only opens under recoil
(default 0 keeps the constant boost)

r__svp_reticle_mag drives shader_scope_params (curMag/min/max) from the engine
each SVP frame, so the FFP reticle scales with zoom regardless of the 3DSS zoom
script state (default 1)
@themrdemonized
themrdemonized marked this pull request as draft June 22, 2026 11:00
@TheLostInPlace

TheLostInPlace commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

I'm at a point where this isn't much more to port over/fix/adjust as far as I am aware with my testing, so the PR is "complete" for now so to speak. I know you mentioned that you won't merge anything render related till SSS24, so just wanted to let you know the state of the PR

Edit: Swallowed my words after doing some testing with Ascii on SSS24 + PiP

…tion

r__svp_cull dropped close skinned NPCs' face and hood because a dynamic visual's
vis.sphere is the rest-pose bound and does not track the animated bones, so it
now culls only the static world

the SVP and main share the captured sorted and wmark lists and whichever pass
runs first was clearing them, leaving the other empty. clear on the last consumer
instead: gbuffer is main-first so wmarks clear on the SVP pass, combine is
SVP-first so the translucent list clears on the main pass. fixes bullet holes and
decals missing in the scope, and translucent geometry vanishing from the main
…diagnostics

- objective auto-derived per scope from HUD geometry (geomscan): scan a snapshot of the HUD geometry (taken before render_hud clears the lists) for the forward-most on-axis node = the objective glass plane. no data table; r__svp_obj_dist is now a fine-tune scale
- real front-lens capture when a scope flags a distinct objective surface, geometric fallback otherwise
- recoil gate: r__truepip_recoil + zzz_truepip_recoil_gate.script monkey-patches GAMMA Enhanced Recoil shoot_effect (0 normal / 1 off-in-pip / 2 off)
- scope/weapon/mag bridge to the render side + svpcam offset log + magenta camera cube overlay
- temp scope_debug probes (lenssurf/lensbox/scopediag/bone scan) pending strip
…ip diagnostics

3DSS gate (s3ds_param_4) never fired. pip used IsSVPActive(), which is false at
3DSS shader-bind time, and the !see_through guard blocked nearly every scope
(bit 2 is the see-around-the-tube feature, on for almost all). Switch to
true_pip_on and drop the see_through guard (the gate preserves bit 2 anyway).
Parallax shadow / chromatism / nvg-blur now clear in true-pip optical mode
(verified firing settings 30 -> 12).

Objective radius: eyepiece-relative (eyepiece.radius * r__svp_obj_size, one
global knob). The front-node geomscan swung ~6x across scopes, conflating the
glass with the bell housing, so no single obj_size fit. Drop the geomscan radius
and the lateral-extent attempt; geom_front still drives the objective position.

Remove the now-dead r__svp_optics_er / r__svp_optics_obj cvars; the eyebox
derives eye-relief and objective from real geometry.

Strip the temp [truepip] log diagnostics (svpcam / header / objcap / lensbox /
geomscan / eyebox / lenssurf / 3dss-probe / 3dss-gate) plus the SVP_DiagScope
dead-end bone probe and the g_pip_scope_section/g_pip_weapon_section log bridge.
Keep debug_scope (the world camera cube + lens outlines) and SVP_LensBoneXform.
…cvars

Eye-box now reads real per-scope eye-relief (cm) and exit-pupil (ratio) straight
from the 3DSS config (s3ds_param_1.y/.z, already live on the engine global) instead
of deriving them from scope geometry. New cvar r__svp_optics_real (1 = real,
0 = geometry fallback). The geometry path is mathematically unchanged, so non-3DSS
scopes do not regress.

Magnification-scaled eye-box vignette: the static real exit-pupil does not tunnel
at high zoom, so r__svp_optics_zoomvig (on/off) + r__svp_optics_zoomvig_blend
(0 = static, 1 = exit-pupil / mag) tighten the clear zone as magnification rises,
restoring the per-scope "tube closes in as you zoom" darkening. The real-path
eye-offset shift uses a larger base (0.8 vs 0.10) because the real eye-relief K runs
~8x below the old camera->eyepiece HUD-distance proxy.

Drop the dead recoil cvars. r__svp_pupil_boost + r__svp_pupil_track never fired
(their gate used IsSVPActive(), which is false at 3DSS shader-bind time) and are moot
now that the s3ds_param_4 bit-gate clears the parallax shadow at its source.
r__svp_zoom_smooth was vestigial (base recoil does not punch the FOV). The
s3ds_param_1 binding collapses to a plain pass-through. r__truepip_recoil (the
script-recoil gate) is now the sole recoil cvar.

(A temporary scope_debug>=2 "optics:" log remains in the eye-box for first-test
verification, to strip once the optics defaults are locked.)
Zoom-vignette confirmed good in-game, so remove the scope_debug>=2 "optics:" Msg
that was left in for first-test verification. No behavior change.
…oke fix

SVP perf: r__svp_cull_ssa (default 4) magnification-aware small-object cull of the scope gbuffer, the draw-call-bound dominant cost (~-45% of the SVP geometry pass); r__svp_adaptive_res (default 1.2) sizes the SVP render to the on-screen eyepiece disc, latched once per ADS; r__svp_lod (default off) coverage-scaled LOD, a no-op on the single-LOD world but kept; r__svp_diag + RENDER_GBUFFER[SVP]/SVP_ACCUM/COMBINE_SVP markers for profiling.

Lens cosmetics (all default-off): two-zone eyebox, dirty-glass, radial fringe, vignette. Body-DoF and lens grain removed.

Fix: priority-1 forward geometry (anomaly smoke) was missing from the main view when scoped, the SVP combine consumed render_static/dynamic(1) before the main combine ran.

Build: LTCG:INCREMENTAL on the WPO configs.
@TheLostInPlace

Copy link
Copy Markdown
Contributor Author

RC-1 Build for testing: RC1

…+ non-PiP hardening

Reticle: solve the custom-reticle curMag so current_zoom = scope_mag (full FFP),
so the PiP reticle matches the 2D scope size instead of rendering at ~half. The
custom reticle sizes off current_zoom = (curMag-minMag)*0.4+1, and feeding
scope_mag raw only reaches 0.4*scope_mag+0.6. r__svp_reticle_curmag becomes an
FFP scale multiplier (default 1.0).

NPC cull: exempt dynamics (non-null pMatrix) from the SVP small-object SSA cull
at both render sites, so the perf cull only skips tiny static clutter and never
NPCs or items. The static-cull perf win is untouched.

Recoil gate: default r__truepip_recoil to 1 (PiP-only) instead of 2.

Mismatch hardening: gate the PiP option page and the lens-FX tuner behind a
runtime capability check so the gamedata does not crash on a non-PiP engine.
…draw-list cleanup

- reticle now sizes to mag/min so it matches the 2D/3DSS reticle on every scope (fixes both low-min like CQBSS and high-min like 1P59/MarchF)
- SpecterDR/PS-320 no longer black out at max zoom, curMag bounded so the exit-pupil shadow stays sane
- r__svp_clean_optics (default 1, PiP only) strips the 3DSS fake cosmetics: parallax shadow, chromatism, nvg blur, fisheye
- r__svp_roll_stabilize toggle for lean/cant (default 0 realistic tilt, 1 leveled)
- consolidated the SVP draw-list clear band-aids into one svp_clear_shared_list helper
- removed the eyebox/lens-FX engine pass and swept ~28 dead cvars
- removed the experimental lens-FX tuner gamedata to rebuild cleanly later
…, HUD-in-SVP barrel, grass defer-clear, SVPCOP/SVP-CFG diagnostics, minMag+NDC floors, pip-ship BuildBoth/RC
…/set idiom leaked one D3D ref per call, hundreds/sec while ADS, monotonic VRAM growth (3 sites: reticle alias, RT remap, debug overlay)
…e - the lens composite blends srcalpha with forced o.a, propagating source alpha would turn the lens transparent
… stock pass - CHK_DX is a no-op so VRAM-exhausted creation silently produced a half-built target with NULL surfaces bound every frame
…on capturing this and TargetSVP could fire after a device reset deleted the target (latent use-after-free)
…gs, 4 fixes, verification evidence, runtime test plan)
… image gets dof/lut in its own combine AND again over the composited lens, the gates land each exactly once (default 0 keeps the current look)
…lowing signs/lamps/anomalies were main-only and rendered dark through the scope (default 0 = stock)
… ms + draw-call/vert deltas via existing RCache.stat, gated r__svp_diag (no timer existed for the SVP pass)
…rity matrix, 3 Tier-1 implemented, 10 Tier-2 blueprints, coverage checklist)
… thermal/tube sights

- barrel through the scope: captured-HUD replay with the eye-ray anchor, window (1:1) and
  magnified mapping modes (r__svp_hud_fov_match), sway compensation, eff-mag gate, svpscope 2 only
- full-barrel: scope-body skip (aimed-lens cylinder + wrap test), near plane at the eye,
  near-field bokeh defocus at the capture seam (svp_nearblur, golden-angle gather)
- aimed-lens selection in deriveScopeLens, the last-wins loop latched wrong lenses on
  multi-lens weapons (ACE52+CQBSS tube rings)
- zoom-0 tube sights: captured-ocular SVP activation, 1x re-imaging, thermal objective clip
  and sharp capture, the fakescope overlay stands down under true PiP
- true-PiP shader sentinel (w = -2, legacy Lua -1 persists in user.ltx), svp_optics reticle
  field constant, ST_PIP_PRECISE bit (r__svp_reticle_precise, default 0), auto-boresight
  (r__svp_boresight, opt-in)
- svpscope mode split: 1 = classic eyepiece-camera PiP, 2 = realism with the near-eye camera
  owned by the mode (r__svp_near_eye removed), eyebox drift shadow + r__svp_stabilize removed
- diagnostics: [SVP-BARREL]/[SVP-AIM]/[SVP-HUD]/cant probes, cop_diag levels, aim-confirm overlay
… TBN switch dead weight

every view-coupled V_tangent use is zeroed or replaced under true PiP, so the mesh-tangent
vs cotangent frame can no longer affect anything visible
…e-lens pick

the hud skip test located pieces at their rest pose, addon scopes sit at their attach bone,
and hidden markswitch lenses could win the eyepiece pick. [SVP-LENS] candidate probe added
…05/007/009)

- r__svp_light_capture: the fog-distance light cull is a naked-eye heuristic, relax to the
  far plane while the SVP renders so distant lights reach the magnified image
- r__svp_local_exposure (+_bias): grade the scope image by its own measured luminance via
  s_tonemap_svp, dark-room-to-bright-outdoors no longer blows out
- r__svp_bloom: run ssfx bloom on the SVP pass so magnified bright sources flare
…stitution design, hazard register

scouted via 6-agent sweep + first-hand verification: the far accumulate samples via
fuckingsun->X.D.combine (xform param dead for FAR), the SVP target already owns an unused
rt_smap_depth, SetActive remaps make the texture swap symmetric. Main atlas never written.
…rch-grounded)

- r__svp_twilight: the exit pupil (ocular*ratio shrunk by zoom) vs the env-adapted eye pupil,
  image dims by the squared ratio at high mag in low light (Zeiss ~4mm rule), day unaffected,
  electronic sights exempt, floor 0.25, [SVP-TWL] diag
- r__svp_parallax: the bench-measured real reticle shift (~0.15 mrad at full eye deflection,
  sub-pixel) restored into the centered field via svp_optics.y, 0 = pinned, up to 10x
@TheLostInPlace

TheLostInPlace commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

There have been some pretty significant changes that require a 3DSS Patch I made to make certain things work properly, so I would not merge/review until I change the PR from draft to review.

Progress tracker: https://discord.com/channels/912320241713958912/1521006040550867044

The state of the non-mt port is also on hold for now, wrangling the MT branch is taxing enough as is for now.

- the barrel near plane now sits at the FRONT lens: max of the objective distance and the
  measured scope-body extent, so under-tube attachments (lasers) never render in the image
  and clip-on optics (skeet thermal) push the plane forward with them
- r__svp_hud_full 2 (new default) = front-plane clip, 1 = the old full-barrel-from-eye,
  0 = objective clip with the body rendered
- the piece scan measures every frame (thermals too) and drops coaxial clip-on bodies past
  the objective (t 1.4-2.6, tight radial keeps the barrel out)
…he eye

the front-plane near clip cut the barrel mesh mid-length (hard planar slice). the camera now
slides up the anchored aim ray TO the front plane with a small near, so everything behind the
front lens falls behind the camera and the barrel ahead keeps its full mesh
… eye

the entrance-pupil camera pushed the whole barrel out of the narrow magnified cone (closer
viewpoint = larger angular offset), no barrel rendered. mode 2 now keeps mode 1 geometry
(eye camera, near 0.08, no cut plane) and drops pieces WHOLLY behind the front plane in the
piece scan, spanning pieces (barrel, rail) render whole, discrete rear attachments (lasers)
never appear. g_svp_hud_skip_scope carries the mode
@TheLostInPlace

Copy link
Copy Markdown
Contributor Author

Closing in favor of #615

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant