Skip to content

Releases: dariulone/cyberpunk-vr-port

v0.1.5

Choose a tag to compare

@dariulone dariulone released this 25 Aug 11:45

0.1.5

  • Wrist readout: no longer goes black when a weapon is drawn. The draw event carries the surface into the weapon plane and the world widget loses its render target there, so the surface is now frozen where the draw left it and the widget re-armed once -- after which there are no transitions left to survive.
  • Wrist readout: the freeze belongs to the component it was done for, so a death or a save load owes it again instead of leaving the readout to die on every weapon switch. Spawning with a weapon already in hand counts as the transition, which also covers respawning armed.
  • Wrist readout on the female player, cut from her own forearm with her own bind pose.
  • Fixed the frame-rate collapse near a surveillance camera with a weapon drawn (60-70 down to 20). The ownership test on the hitscan ray is now free: no VirtualQuery anywhere on the ray path.
  • Fixed the view holding a frame while a car turns. 4-12% of rendered frames shared an aim epoch with the previous one and wrote its orientation back -- correct for the head, stale for the world.
  • Fixed VRIK jitter while seated in a vehicle: the solve now takes the full entity world quaternion instead of a yaw-only reconstruction, which was wrong by however far the car had pitched and rolled.
  • The second eye is submitted with the pose its OWN image was drawn with, from a read-back queue of its own, and when no image is paired to the current frame it reuses its own most recent one instead of borrowing MAIN's.
  • Surveillance cameras: the second eye now follows the camera instead of staying on the player, its FOV is matched (the camera renders at 60 where the port forces 103.982), VRIK is suspended as in a cutscene, and the head steers from the lens with the horizon level.
  • Computers and terminals no longer take the camera from the head. The screen still opens, takes input and closes; only the one Activate call that hands the camera over is skipped. The zoom is traded for leaning in, and is switchable.
  • Device screens can be scrolled again: the right stick's Y is handed back to the game while one is open.
  • The player is held in first person: the perspective toggle does nothing and a car entered in third person is put back, both through the game's own restriction and event.
  • The scanner is a TOGGLE -- squeeze at the ear to open, squeeze again to close, hand free in between.
  • The quickhack list moved off the face buttons onto the left stick, to the stop, with X as a plain apply. It presses the arrow keys rather than the D-pad, because the D-pad is also the scanner's zoom.
  • The scanner's HUD can be dragged and scaled, which HUDitor cannot do for it.
  • The D-pad chord fires at 0.90 like every other gesture in the port, not at 0.5.
  • In a vehicle, A confirms a dialogue line: confirming is X, and X is the exit there, so seated there was nothing able to confirm at all. The handbrake on A keeps working.
  • The shadow cascades are checked on every launch instead of installed once: they are shared between the two views, so a value drifting off the shipped one replaces UserSettings.json whole, keeping one backup.
  • Binding lists updated in the overlay and the README; the aim laser's label spelt right.

v0.1.4

Choose a tag to compare

@dariulone dariulone released this 23 Aug 10:54

v0.1.4

Fixed

  • Frame rate collapsed from 60-70 to about 10 near security cameras and turrets. PhysicalRay is not only the firearm — a camera sweeping for the player and a turret tracking one cast rays continuously
  • Two more of the same shape: a kernel call on every orientation-provider slot call for every entity in the world, and RenderDoc marker scaffolding that shipped switched on.
  • Constitutional Unity: the reload did nothing, on every skin. Its slots were declared in the part appearance file and the weapon is built from the root — which we did not ship at all. It is the only weapon that was missing one.
  • Arasaka Tamayura / Tsunami Nue: the reload worked bare and did nothing with a scope fitted. Reverse of the Unity — shipped the root and not the part file, and an attachment makes the part file decide.

Changed

  • Two-handed grip radius 12 cm → 6 cm. At 12 cm the hold was offered to a hand merely passing the weapon. It is an ini key now, xr_two_hand_radius, clamped to 0.02–0.30 — retune it ia rebuild.

v0.1.3

Choose a tag to compare

@dariulone dariulone released this 22 Aug 18:09

the hand target's origin goes back to the view origin

src/Hooks/AnimPose.cpp, both hands, in the view-frame branch. Reverts one assignment
made by 64e706fc:
- float vpM[3] = { handAnchor[0], handAnchor[1], handAnchor[2] };
+ float vpW[3] = { vpView[0] - g_VREntityPosX,
+ vpView[1] - g_VREntityPosY,
+ vpView[2] - g_VREntityPosZ };
+ float vpM[3]; VRIK_QuatRotateVec(ec, vpW, vpM);

v0.1.2

Choose a tag to compare

@dariulone dariulone released this 22 Aug 14:06

0.1.2

New

  • Physical reload: grab the magazine with your free hand, pull it out along the weapon's own well, carry it, push it home.
  • Rack the slide with your other palm; the right stick click is the slide release.
  • Ammo count and reload sounds follow the hands instead of a timeline.
  • Thirteen weapons tuned individually: Unity, Tamayura, Tsunami Nue, Constitutional Liberty, Arasaka Kenshin, Militech Lexington, Militech Omaha, Kang Tao Chao, Tsunami Kappa, Arasaka Yukimura, Militech Ticon, Silverhand's Malorian, Malorian Overture.
  • The Overture revolver rolls its cylinder, ejects its cases and reloads from a real speedloader item.
  • Recoil kicks the HAND, not the camera, on a damped spring, with a per-weapon angle.
  • The empty hand now rests instead of clawing at nothing while a weapon is out.
  • Two-handed hold: bring the off hand to the weapon, squeeze, and it steers the aim and drops the kick. Twelve weapons have a captured hold.
  • Physical body rotation (optional): the character turns to follow the headset through the engine's own heading, so mesh, capsule, aim and movement all follow.
  • VRIK suspends itself entirely during cutscenes, with a per-tier threshold.
  • VR driving: grab the wheel with your own hands, steer by the tilt of the line through the controllers, adjustable deadzone and lock angle.
  • A palm on the wheel hub sounds the horn; a hand that is grabbing never honks.
  • Draw a weapon while driving and the right trigger becomes the gun, with the throttle latched and trimmable from the left stick.
  • The F10 overlay now reaches the second eye, so opening a settings panel no longer costs you stereo.
  • 13 headsets and 60 resolutions, every ladder reaching 6000 px; PlayStation VR2 and Bigscreen Beyond 2/2e added.
  • The port's HUDitor setup ships as an opt-in config: the editor on F11 plus a VR-tuned layout for all 26 widgets.

Changed

  • Movement speed is discrete: past the deadzone only the direction comes from the stick, so a wrist tremor no longer changes your pace.
  • Sprint is the left stick held forward for 0.2 s, held rather than toggled, and it survives a dash.
  • No sprint out of a crouch, read from the game's own locomotion state machine.
  • Dash moved to the right stick pushed fully up, one dash per push.
  • Crouch stays on the right stick pushed fully down.
  • The right stick click is now the slide release; it no longer crouches.
  • B drops the magazine while a weapon is in hand, and reaches the game normally when holstered.
  • The scanner is a one-hand gesture: raise the left controller to your left ear and squeeze the left grip.
  • A snap turn needs a FULL stick push, so a resting thumb no longer turns you.
  • Leaving a vehicle is X HELD; B is never the exit in a car, so no stray press can eject you.
  • No hand smoothing while a hand is holding the steering wheel.
  • The HUD is pasted at the same pixel in both eyes by default, so markers land in the same place.
  • Virtual Desktop drives the XR loop inline again, behind an ini key.
  • The cascade shadow rows are removed from Graphics -> Advanced: raising them gives you artefacts the port cannot fix.
  • The persistent centre dot ships off.
  • The shipped UserSettings.json no longer forces the game's language.
  • The port's own HUD mod is deleted; HUDitor does this properly and the two fought over the same widgets.
  • Hand and weapon collision is off in this release: the body capsules came back out after a vehicle launched the player.
  • 61 .anims left the archive; they were export scaffolding, not files the game needs our copies of.
  • README and the overlay's binding list rewritten against the code.

Fixed

  • The second eye's random image jump every 2-4 seconds: it had ONE capture texture where MAIN keeps a pool of three, so a blit overwrote the pixels a submit was copying out.
  • Sun cascade shadows disagreed between the eyes and MAIN's blinked on its own: a view samples the atlas it rasterised itself, so the fit record, the cascade camera and the sampling matrix must all come from one view.
  • MAIN's cascade pass turned out to be a duplicate; 373 draws a frame stop being submitted.
  • Nothing animated in a shader moved in the second eye: its frame clock was zero forever, which is why cloth flags hung dead.
  • Vegetation jittered in MAIN on head turns: the wind volume is claimed by one view per frame, and MAIN was reading last frame's.
  • The second eye marched its reflections 4000 units in 5000 steps, mirroring a ceiling MAIN leaves matt.
  • The render mask table knew 13 of the engine's 27 categories, so a missing pass could not even be named.
  • A reflection-probe face was being compared against the second eye as though it were MAIN.
  • Black band down the outer edge of each eye on off-axis lenses (Quest 3): the frustum is sized to COVER the panel now, not to match its span.
  • Hands looked laggy and ghosted. Five separate causes, all measured: the solve was clocked by a Lua callback; the hand offset was measured against a different head than it is re-anchored on; a 72 Hz stream was resampled at 52 Hz; the per-frame locate aimed at the wrong clock; and Present is the wrong clock by phase, so 3-7% of displayed frames replayed the previous arm.
  • The hips jumped 22 cm in 2-4% of animation passes -- the same clock defect from another angle.
  • The elbows swung when you looked around: the shoulder girdle was anchored on the camera and now hangs off the body.
  • The camera heading and head displacement were each a frame old -- 4.2 deg of error at a 300 deg/s turn.
  • The avatar was thrown forward on a dash: the body anchor paired a current camera with a tick-old entity position, and used that same reading to decide which character it was.
  • Getting into a vehicle threw it into the air: the body capsules interpenetrate the seat, so they come off at the door.
  • A held magazine floated clear of the palm at a run -- 21 cm, because a script write is not in the race the hand is drawn in. It is carried by a component on the player's own template now.
  • The magazine started floating again in 0.1.1's archive: the carrier components had only ever existed inside the packed binary and were lost on the next repack. Their sources are tracked now.
  • A firearm's hit is redirected in the engine's own PhysicalRay evaluator, so the muzzle is the origin and shotgun spread stays spread.
  • An NPC firing at you no longer kicks YOUR hands or sends its projectiles down your barrel.
  • A crash on save load: the second eye replayed a node whose indirect argument buffer was NULL.
  • The overlay was draining the game's GPU queue every frame -- 43.8 fps against 70.3 with the fence guarded.
  • VRIK never armed at all under Proton: a pointer classifier assumed the Windows heap layout, and Wine's allocator sits two orders of magnitude below it.
  • The laser dot was being zoomed twice.
  • The driving view faced off to one side, differently per car: it was pinned to the body's yaw rather than the camera's.
  • Pushing the left stick to the stop in a car was handing it to the autopilot (Vehicle_Autodrive is the same L3).
  • Brushing X while driving ejected the player instantly; the exit is a hold now.
  • Seating a magazine and keeping the grip held made the weapon take the support hold by itself.
  • The Kenshin Spy lost its frame twice: once to cooked collision left behind in the mesh, once because twelve of our weapon overrides were built on the pre-Phantom-Liberty copies of base\ files.
  • The Overture's speedloader disappeared after a Windows reinstall; the source was in the repo all along.
  • The archive in the repo was three months behind the one the game runs, so a release built from it shipped almost none of this.
  • The tester package could not be built at all, and shipped 3 of 14 grip poses when it could.
  • Two loose r6\scripts files the port silently depended on are versioned with it now.
  • The reload's slot probe asked for slots the weapon does not have, which left it inert on any revolver.

v0.1.1

Choose a tag to compare

@dariulone dariulone released this 03 Aug 09:23

ResizeBuffers re-derived the swapchain height from the runtime's recommended aspect instead of
taking the launcher's pick -- a 2560x2848 selection came out as 2560x2682. Both it and
ResizeBuffers1 now use the launcher size verbatim.

xr_dlss_res_override and xr_dlss_matrix_hook are deleted, along with xr_dlss_slot_mode and
xr_dlss_log_stride which only fed them. 553 lines; both were AER-era and shipped off.

0.1.0

Choose a tag to compare

@dariulone dariulone released this 02 Aug 10:50

CyberpunkVRPort 0.1.0
The proxy is gone, the second eye is a real view, and the port is a RED4ext plugin

============================================================================

  1. ARCHITECTURE
    ============================================================================

1.1 Why the proxy had to go

As dxgi.dll we stood in front of DXGI and owned what we assumed was the process's only
swapchain. Every path grew around that assumption, and the moment a second swapchain
appeared - the VRCAM mirror window - it broke in a different way on each attempt: our own
IAT hooks recursing back through our module, the overlay drawing into the mirror's
backbuffer, a shared surface returning DXGI_ERROR_ACCESS_DENIED. None of those were mirror
bugs. They were consequences of being the proxy.

Almost nothing had to move to fix it. The engine hooks, the OpenXR submit, the capture, the
overlay and the stereo module never needed to be a proxy - they needed a device, a queue and
Present. Only the way those three are acquired changed:

device + queue   sync_stereo hooks d3d12!D3D12CreateDevice before the game creates its
                 device and keeps both; the plugin waits for them instead of being
                 handed them by CreateSwapChain.
Present          the swapchain vtable is shared process-wide, so a throwaway swapchain
                 exposes the same table the game presents through.
game window      learned from the first Present that is not one of ours.

Everything ships as two RED4ext plugins - CyberpunkVR_Stereo and CyberpunkVR_Hands - so the
game loads us through its own mod loader and uninstalling is deleting a folder. Running both
a dxgi.dll and the plugin means two copies of every hook fighting for the same addresses, so
the installer removes the proxy.

src/dxgi/ became src/vr/, because nothing in it proxies DXGI any more:
core/dxgi_proxy.cpp became core/vr_core.cpp (the ~7.5k-line hub: logger, live controls,
camera hooks, pose, menu detection), and core/dxgi_factory_wrapper.h/.cpp became
core/swapchain_hooks.h/.cpp (the swapchain vtable hook - Present, ResizeBuffers - which the
plugin needs and always did).

1.2 AER is deleted

Alternate-eye rendering with NVIDIA Optical Flow synthesis is gone in full: src/dxgi/aer_v2/,
openxr_aer_v2_worker.cpp, the optical_flow_d3d12 / stereo_reproject / mv_warp / warp_pass
passes, the CUDA Toolkit and Optical Flow SDK from CMake (48 vendored files),
EnsureAERCaptureResources, CapturePresentedFrame, DepthResolve::RecordResolveColor, the AER
submit arm of the frame loop, the 13 xr_aer ini keys plus xr_nvof_perf, and the overlay's
"Stereo / AER" combo. Roughly -9900 lines.

Things that were dead by construction went with it, and are worth naming because older notes
still mention them: syncSequential and the synced-pose machinery (AER-gated), the per-eye
display cant ComputeCantPoseDelta / ApplyCantToPose (mono renders one frame, so a per-eye
cant was never applied at all), and OnPatchBufferCallback's body, which sat behind an AER
gate and had therefore never executed once.

============================================================================
2. NATIVE STEREO - HOW IT ACTUALLY WORKS

The second eye is a real engine view: a render-to-texture camera component on the player
entity, running the frame graph for its own eye, from its own position, with its own
projection. Not a reprojection, not a copy of the first. This is the whole chain, in order.

2.1 The view exists because of an authored asset

tools/gen_vrcam_assets.py authors one entRenderToTextureCameraComponent per render resolution
into the player entity, packed into cyberpunkvrport.archive:

component        vrcam_<W>x<H>
virtualCamera    vrcam_feed_<W>x<H>          isEnabled = 0

All ship disabled. The launcher offers exactly the resolutions the archive carries a camera
for - offering one that does not exist used to fail silently, with every log line still
naming the component you expected.

2.2 Identity: one hash, never a heuristic

The entire stereo path recognises the second view by one value: the CName hash of the
component's virtualCameraName, which the engine stores at view-context + 0x28. MAIN is key 0.

That hash differs per resolution, so a literal would only ever match one of them; it is
derived at init from the selection file instead. Nothing anywhere looks at aspect ratio or
resolution numbers to find the view - in VR MAIN renders square exactly like VRCAM, so the
old "key == 0 && aspect > 1.3" test either missed MAIN or latched some unrelated wide helper
view.

2.3 Selection plumbing, and why it goes through a file

launcher  --writes-->  bin\x64\plugins\cyber_engine_tweaks\mods\
                       CyberpunkVRPort_Stereo\vrcam.json
                             |
              CET Lua (modules/vrcam_select.lua) reads it and flips isEnabled
              on the matching component through the game's own RTTI
                             |
              the plugin reads the same file and hashes the camera name into
              g_vrcam_ctx_key

CET sandboxes Lua file IO to the mod folder, which is why the canonical copy lives there and
the plugin reaches out to it rather than the reverse. Four things describe this one choice -
component name, camera name, the key hashed from it, and the resolution the RTV filter matches
on - and vrcam_config.h moves all four together, shared with the launcher dialog. Updating a
subset is not a small bug: a key that names no live view means no second eye and no mirror
window, while every log line still prints the component you expected. Deleting vrcam.json
falls back to the legacy single component, which is the escape hatch when the expanded entity
has not been imported yet.

2.4 Making the second view render what MAIN renders

A fresh RTT view does not get the full frame graph. Three separate gates had to be told apart,
and only the third was the real one:

  • Frame-graph feature flags (f0/f1 at the builder). Forcing them changed nothing - VRCAM's
    pair was already a superset of MAIN's (3C00017F vs 3C00017D).

  • The view's draw-block list, which came back empty every frame (blocks null == vrcam
    dispatch count, exactly).

  • The per-view RenderMask, which is the answer. 32 qwords at view + 0x18A0, where
    view = the pointer at work_context + 0x18 - the same context whose key at +0x28
    identifies the view. The test sub_14021BE28(wc, desc) passes when
    (mask[i] & required[i]) == required[i] for i in 0..31, and the required words start at
    descriptor + 8, not descriptor + 0. Granting from +0 does nothing at all, and produced a
    confident, wrong "the mask is not what blocks
    it".

The descriptors are named by the engine - one registration function each,
sub_1400F76B0: "Rendering/RenderMask/DistantLights" -> word_143487D70 - 196 of them dumped to
engine_re/dumps/_render_mask.md by engine_re/scripts/re_render_mask.py. "Pass X does not run
for the second eye" became a table lookup instead of a hunt: GBuffer, GBufferLate,
DepthPrepass, GeometryStatic/Skinned/Proxies, WeaponPlane, Forward, ForwardNoTXAA, Unlit, HUD,
GameplayPostProcess, Particles, DistantLights and the rest.

The feature bitset at view + 0x17D0 and the mask at view + 0x18A0 are one bitset, 26 qwords
apart - which is why a diff over feature words 0..23 never saw the mask and came back clean,
and why the [fgflags-all] comparison was misleading.

For the HUD the shortfall was exactly one bit:

MAIN   w11 req=...0080  have=0000000000CFFFBF  missing=0
VRCAM  w11 req=...0080  have=0000000000CF6E3F  missing=0000000000000080

word 11, bit 7 - absolute feature 711. Granting the capability early and leaving it set is not
the same thing as overriding the test result deep inside the node: the override answered one
question at the moment it was asked, long after the work it guards had been skipped. The bit is
what the rest of the engine reads, so the view genuinely declares the capability and the engine
populates the state itself. The proof was the block list ceasing to be null.

2.5 Giving it the right camera

  • Vertical FOV goes into the component at comp + 0x128, the only input the RTT projection
    has: the producer builds the projection from it and nothing else, and cot(68.238/2) =
    1.47593 reproduces the matrix exactly. The RTTI zoom field at +0x15C is never read on this
    path, and the zoom ratio at +0x424 is an output of the per-view setup, not an input. That
    producer runs every frame, standing still included, so writing the FOV is sufficient -
    forcing comp+0xA00 or calling sub_140AC316C drags view-create in, which hitched the game
    and hung the GPU.

  • View-context scalars - fov +0x90, zoom +0x9C, near +0xB0, far +0xB4 - are mirrored from
    MAIN so the second view's LOD and culling, which are screen-space-error driven, match
    MAIN's automatically instead of being tuned to match.

  • ADS magnification never touches the FOV field: measured, +0x90 reads 68.238 both at rest
    and while aiming. It goes through the projection matrix at +0x214, and the factor is
    recovered as projYY · tan(fov/2) - 1.0000 at rest, 1.4998 aiming. MAIN's projection already
    carries it, so it must not be applied a second time when the second view follows MAIN.

  • The eye separation is applied to the component's WORLD POSITION (component + 0xE0), above
    the view producer, so culling, shadows, the distant pass and motion vectors all see the eye
    they are being drawn for. The older write into +0x100/+0x110 ("posA/posB") is off by
    default: measured, it moved the rendered viewpoint by 23 micrometres - that is, not at all.

  • The ca...

Read more

v0.0.9

Choose a tag to compare

@dariulone dariulone released this 09 Jul 12:37

Stereo / AER / depth:

  • Restore mono-depth capture and AER stereo: dormant AER submit thread,
    per-eye toggle, late-IPD-shift stereo write.
  • Log cleanup ([AERDBG]/[DEPTHDBG]) and wait hardening (fence 1000ms,
    swapchain 200ms).
  • Keep the tested view-packet position scale explicit: posScaleView =
    50/131072 (bit-identical to the old 25/65536).
  • AER submit now defaults off.
  • SteamVR: run the mono submit on the dedicated thread (like AER) instead of
    inline on the game's Present thread, so SteamVR's xrWaitFrame / compositor
    pacing no longer stalls the game (fixes freezes / lag). VDXR and other
    runtimes keep the proven inline path (gated by UseThreadedSubmit()).

Gameplay / rig:

  • Melee: native effect hits + sound, stat guard, no idle unequip, tuned
    swing speed.
  • Camera shake: Aim_JNT swing-only freeze (rig-level shake kill, snap-safe),
    camera-shake weight kills, engine-true overlay projection.
  • Sprint: kill the sprint-dive / sprint-snap "double" at the anim-layer
    source; clean-pair XY slew limiter + one-tick lead predictor kept as
    dormant safety.
  • VRIK: snap-turn arm fixes, shared-slot registry, hot-path perf, dead-code
    purge; in-vehicle camera->head bake dropped (arms-only IK in vehicles).

Controls / HUD:

  • D-pad chord while the LEFT stick click is held, the RIGHT stick picks the D-Pad direction.
  • Holster zone margin.
  • VR HUD: "Phone" placement region relabeled "HP Bar".

v0.0.8

Choose a tag to compare

@dariulone dariulone released this 29 Jun 08:58

CyberpunkVR Port — v0.0.8

  • Physical body rotation is now an OPT-IN toggle (F10 -> VRIK -> "Physical body
    rotation"), OFF by default. Off = classic stick / snap-turn heading; the body no
    longer auto-turns.

Contributed by https://github.com/iPowerTech:

  • FIX: proper non-square canted-lens projection MATRIX for canted-display
    HMDs (Valve Index / Pimax). Supersedes the 0.0.7.5 square per-eye
    render-resolution workaround and corrects the 3D scene geometry.
    Tested on PICO 4, Quest 3 and Pimax Crystal OG.
  • Pre-launch device list: pick the connected HMD and choose native or scaled
    per-eye render resolution before the game starts.
  • New Crosshair mod: hides the flat 2D HUD crosshair

v0.0.7.5

Choose a tag to compare

@dariulone dariulone released this 26 Jun 10:44

Contributed by: https://github.com/iPowerTech):

  • Physical body rotation.
  • DPAD emulation: Right Grip + right thumbstick = D-pad Up/Down/Left/Right.
  • first-person hand / barrel point set to false by default.
  • FIX: the per-eye render resolution is forced square again.
  • The experimental d3d12.dll proxy is no longer built or shipped (dxgi.dll uses
    the system d3d12.dll the game already loads).

0.0.7.1

Choose a tag to compare

@dariulone dariulone released this 22 Jun 17:22
  • CUDA multi-arch support for every NVIDIA GPU
  • Fixed XInput hook
  • Some changes for canted displays