* Some dead config vars
* ShadowMapper work directly with SceneLight, we can't reliably create derived light types from native because the lightdesc is modified later
* Unused on light_environment
* InputRouter uses scan codes (make AZERTY not shit), UI related input remains key codes
* Input.GetGlyph and Input.GetButtonOrigin return the local key name e.g if forward is bound to "W" it will show as "Z" on azerty layouts
Rewrites Vulkan per-thread resource management to use a pooled PerThreadDataVulkan_t slot system with a TLS fast-path, replacing the previous thread-index/map/array approach and updating Vulkan call sites to pass PerThreadDataVulkan_t* instead of thread indices.
* Remove ThreadIndex, SetAssociatedThreadIndex & MaxDeviceThreads (see what breaks)
* Refactor per-thread vulkan data into a single pooled struct
Replace fixed-array per-thread Vulkan data with a dynamic slot pool.
Threads grab slots on demand, hot path is just a TLS read, completly lock-free
Two mechanisms manage slot lifetime:
- Ref counting: render contexts and the render thread AddRef to keep
their slot alive as long as they need it.
- Idle reclaim: slots nobody is holding onto get recycled after N frames
of inactivity. Keeps short-lived threads (async shader compiles, texture
uploads) from leaking slots, while still letting a thread that calls
GetPerThreadData() every frame hang onto the same slot and all its
resources (command pools, descriptor pools, pipeline cache, etc).
Moved VkPipelineCache into the per-thread struct with lazy creation.
* Add CRenderDeviceVulkan::GetPerThreadData() acquires from pool
* Use PerThreadData eveywhere instead of ThreadIndex
And do proper thread data shutdown cleanup
* Main render thread gets it's own permaned PerThreadData instance
* Rework pipelinecache to use PerThreadData instead of VkPipelineCache directly
* Shadows Rewrite: C#, bindless, flexible, quality options, less VRAM...
Introduces a new shadow mapper written entirely in C#, as well as
rewritten shader code for sampling shadow maps. This fully removes and
replaces Source 2's native shadow mapping giving us greater flexibility
and allowing us to open-source it all.
The main goal for the new shadow mapper is greater flexibility whilst
reducing complexity.
Older shaders are incompatible with the new lighting buffers, and will
need to be recompiled to receive lighting properly.
What's new:
- Bindless per-light shadow maps instead of a shared shadow atlas — this
means games can avoid the shadow atlas cost if not using many shadows,
but also allows games with many shadows to not be limited by atlas
space.
- Directional lights have developer configurable cascade count (1-4) and
control over split ratio (logarithmic/uniform blend via lambda
parameter), useful for games where you may not need multiple cascades.
User quality settings define a maximum cascade count which always
overrides developer settings, allowing low end hardware to use fewer.
- Directional lights have a dedicated cbuffer and uniform fast path in
rendering code, and are no longer binned and looped over with local
lights. Every pixel on screen is always affected by a directional
light.
- CSM cascade selection uses bounding spheres instead of depth
comparison, with per-cascade texel snapping to eliminate sub-texel
shimmer.
- Point lights use a TextureCube for cube shadows for much simpler
rendering and mapping, along with hardware PCF filtering.
- Local light shadow resolution is derived from each light's screen-space
size. Shadows below a configurable threshold are not rendered at all.
Lights are sorted by screen size, and r.shadows.max caps the total
count, culling least important lights first.
- User settings have been added for shadow quality (Low/Medium/High)
controlling max resolution, max cascades, and PCF filter quality.
- Local light shadow maps use D16 depth format, halving memory compared
to D32. CSMs remain D32 for precision at large distances.
(Although this could be a TODO, I bet we could make it work in D16)
- ShadowHardness: New per-light property controlling shadow sharpness.
Defaults to soft (0.0) and scales up to 4x sharper. For directional
lights, hardness is automatically scaled per cascade proportional to
texel density (wider cascades get softer shadows), and clamped so the
filter never exceeds a full texel — ensuring consistent softness
across cascade transitions.
- Shadow debug overlay showing all information about allocated shadow maps,
their textures, cascades and more.
- Many new convars to control
- r.shadows.max: Maximum number of shadow-casting local lights, sorted by screen size, least important culled first
- r.shadows.maxresolution: Max texture size for a projected light shadow map (128–4096)
- r.shadows.quality: Shadow filter quality (0=Off, 1=Low, 2=Med, 3=High, 4=Experimental Penumbra)
- r.shadows.csm.maxcascades: Maximum number of cascades for directional light shadows (1–4)
- r.shadows.csm.maxresolution: Maximum resolution for each cascade shadow map (512–8192)
- r.shadows.csm.distance: Maximum distance from camera that directional light shadows render (500–50000)
- r.shadows.debug: Show shadow debug overlay with CSM textures, cascade bounds, and memory budget
- r.shadows.csm.enabled: Enable or disable directional light (CSM) shadows
- r.shadows.local.enabled: Enable or disable local light (spot/point) shadows
- r.shadows.depthbias: Rasterizer constant depth bias during shadow map rendering
- r.shadows.slopescale: Rasterizer slope-scaled depth bias during shadow map rendering
- r.shadows.size_cull_threshold: Screen size percentage below which local light shadows are culled
- SceneLight refactored into a base class with ScenePointLight,
SceneSpotLight, SceneDirectionalLight. SceneOrthoLight removed.
- Simplified Light.hlsl: Light is now a class, DynamicLight merged into
Light, ProbeLight and LightmappedLight no longer inherit from
DynamicLight.
- GPULight/BinnedLight struct reorganized and trimmed: explicit typed
fields instead of packed uint4 Params, shadow data replaced with a
shadow index into a separate StructuredBuffer, removed embedded shadow
matrices and atlas bounds.
- ViewLightingConfig cleaned up: removed ViewLightingFlags,
Shadow3x3PCFConstants, EnvironmentMapSizeConstants,
LegacyAmbientLightColor.
- Baked light mode flags fixed: BAKED lights (lightmaps only) no longer
create shadow maps. MIXED_SHADOWS gated to Stationary lights only
(was unconditionally applied). RENDER_ALL_GEOMETRY flag removed.
DirectLightMode enum documented across Hammer entity definitions.
- Removed light cookie sheets; cookie UV derived from light transform
(LightToWorld transpose). Cookie sampling simplified to a single
bindless texture lookup.
When a Dresser component is set to `ClothingSource.OwnerConnection`, it will verify the owner connection actually owns the items they are attempting to wear using the Steam API. This means users can't just add items they never owned to their avatar forcefully.
Synthesizer never disposed its SpeechSynthesizer, causing background VoiceSynthesis threads to accumulate indefinitely (30+ seen in crash dumps).
This may have caused crashes in TTS heavy games like DXRP.
In addition we now properly terminate the SoundStream so SoundHandle.IsPlaying will become false after the stream has been played.
See: https://github.com/Facepunch/sbox-public/issues/4184
* Remove unnecessary static singletons in MainMenu code
* Empty SceneWorld delete queues during shutdown
* Dresser cancel async load operation on destroy
* Use reflection to null references to static native resources on shutdown
This way we don't have to remember doing this manually.
* Fix SoundOcclusionSystem using static lists to reference resources
* Sound System: Use weak references to refer to scenes
* Cleanup static logging listeners that kept strong refs to panels
* UISystem Cleanup, make sure all panel/stylesheet refs are released
* RenderTarget and RenderAttributes shutdown cleanup
* Rework AvatarLoader, ThumbLoader & HTTPImageLoader Cache
First try to go through ResourceLibrary.WeakIndex which might already hold the texture.
If there is no hit, go through a second cache that caches HTTP & Steam API response bytes instead of textures.
We want to cache the response bytes rather than the actual Texture, so stuff cached sits in RAM not VRAM.
Before avatars and thumbs would reside in VRAM.
* Fix rendertarget leak in CommandList.Attr.SetValue
GetDepthTarget() / GetColorTarget() return a new strong handle (ref count +1).
We need to DestroyStrongHandle() that ref. So handles don't leak.
* Call EngineLoop.DrainFrameEndDisposables before shutdown
* NativeResourceCache now report leaks on shutdown
* Override Resource.Destroy for native resources, kill stronghandles
* Deregister SceneWorld from SceneWorld.All during destruction
* Ensure async texture loading cancels on shutdown
* SkinnedModelRender bonemergetarget deregister from target OnDisabled
* Clear shaderMaterials cache during shutdown
* Refactor Shutdown code
Mostly renaming methods from Clear() -> Shutdown()
Adding separate GlobalContext.Shutdown function (more aggressive than GlobalContext.Reset).
Clear some static input state.
* Deregister surfaces from Surface.All in OnDestroy
* RunAllStaticConstructors when loading a mount
* Advanced managed resource leak tracker enabled via `resource_leak_tracking 1`
Works by never pruning the WeakTable in NativeResourceCache.
So we can check for all resources if they are still being held on to and log a callstack.
* Start moving SSR to bindless
* Classify pass stub
* Separable bilateral upscale, 0.3ms > 0.08ms, not happy with it still
* start classify and intersect indirect
* Commandlist support for UAV barriers and clearing a texture and gpubuffer
* Simplify this, move classify to it's own shader
* All indirect
* ResourceState.IndirectArgument missing from ResourceStateToVulkanFlags switch
* Fix indirect, all works
* classify doesnt get skybox
* Add Clear method to CommandList for clearing render target colors
* bilateral upscale indirect too
* Cleanup
* Simplify bilateral upscale, With RADIUS=1, separable saves only 3 taps (6 vs 9), but costs 3 barriers + shared memory + atomics — the synchronization overhead far exceeds 3 extra texture loads of doing it with a normal X Y loop
* Dotnet format, increase roughness threshold (we can afford it 😘 ) and dont need to discard these buffers on disable
* compile shaders
* this shouldnt be a property
* Add FillGPUBuffer method for efficient buffer filling using native GPU commands
* Defer sentry close to after native AppSystem shutdown
This ensures we still get proper error reporting during native appsystem shutdown.
* Add shutdown_crash tag so we can filter
### Problem
NavMesh baking needed to store raw binary data in external files (`.navdata`) that should be tracked and packaged, but aren't native compiled resources.
### Solution
Reused the existing `RegisterAdditionalRelatedFile_Game` native infrastructure to track raw files similar to how `RegisterAdditionalRelatedFile_Content` is used to track content sources (.fbx, .tga...). `RegisterAdditionalRelatedFile_Game` was not used anywhere in the codebase, so it's safe to repurpose. This avoids changing the asset file format.
### Changes
**C# Asset System:**
- Split `GetAdditionalRelatedFiles()` into:
- `GetAdditionalContentFiles()` - content-side files (.fbx, .lxo, .rect, etc.) filtered by `ASSET_LOCATION_CONTENT`
- `GetAdditionalGameFiles()` - game-side data files (.navdata, etc.) filtered by `ASSET_LOCATION_GAME`
**Resource Compilation:**
- Added `FileReference` struct for JSON serialization with `{ "$reference_type: "filereference", "path": "..." }` pattern
- Added `AddGameFileReference()` to `ResourceCompileContext` which calls `RegisterAdditionalRelatedFile_Game()`
**Packaging:**
- `PackageManifest` now additionally collects game/rawfiles files for publishing (content files are still only included when publishing source)
- Updated our native lz4 library to the latest version
- Exposed a few lz4 glue functions to managed
- Use native glue instead of K4os.Compression.LZ4
- Get rid of K4os.Compression.LZ4 dependency
- Should be a bit faster overall (2x in some tests)
- Gets rid of a dependency
- C version of LZ4 is maintained
- K4os.Compression.LZ4 is not maintained
- "Save as Model" in Mapping Tool now uses the model's origin instead of the worlds origin.
- The exported vmdl now supports Mesh, Hull, or None for collision types (based on the selected MeshComponent) instead of always including a mesh collider.
Environment.Exit triggers DLL unloading which causes native code to callback into managed after CLR shutdown.
Use Plat_ExitProcess instead which terminates immediately without running DLL destructors.
* DDGI stub with rasterized path
* Debug probes and fix stuff
* Simplify all of this, add system later
* Iterate, start integrate
* Integrate depth, allow copying depth to another format
* Iterate, fix depth, send ddgi volume data to gpu, start integrating on lighting compositing, almost there
* DDGIVolume gets bake button, doesnt bake automatically, correct transformation
* DDGI Visibility Tests wip
* DDGI Volumetric Fog
https://files.facepunch.com/sampavlovic/1b2911b1/sbox-dev_UCziuQjdTQ.mp4
* Fix ddgi depth integration, iterate
* Simplify this and correct octahedral coordinates seams
* Iterate
* Probe is 8x8, do normalization directly, cleanup
* Bias probe position, visibility adjustments, make eveyrthing smoother
* Unify integration in SampleProbeData, still need to clean it further
* Unify integrate passes
* Add view bias and use border as per papers, fixes most distortion when using visibility at the same time
https://files.facepunch.com/sampavlovic/1b0411b1/sbox-dev_laDCpwFxk5.png
* Cleanup and fixes for border, cleanup sampling code too
* Proper compositing for DDGI for maps that have baked lighting already, could still have more thought to avoid uncessesary branching
* DDGI System, allow for multiple volumes in scene, cleanup
https://files.facepunch.com/sampavlovic/1b0711b1/sbox-dev_xcJUeit2s4.pnghttps://files.facepunch.com/sampavlovic/1b0711b1/sbox-dev_c59O7Bea6p.png
* Rebase fixes, the way to do this sucks
* Caching tests, Texture.GetPixels3D seems a bit messed
* ddgi.texturecache texturegenerator, much simpler, caches texture nicely and doesnt lead residues on ram after uploaded
* Add LPVDebugGrid on managed, make DDGIVolume use it, dont save cache texture on disable, works way faster than gizmos
* Update volume only if it's enabled
* DDGI on AmbientLight.hlsl
* Simplify and fixes
* ExtendToSceneBounds, rebake envmaps when bake finished, normalbias
* RENDER_DEFAULT_STATE_RASTERIZER probably shouldnt remove override of rasterizer state
* Cleanup
* [Pick] Fix TextureCompiler not compiling 3D textures properly, make Texture.GetBitmap work with 3D textures
* Add NoPrepassCulling, avoids "rays" from probes that are inside geometry from leaking light
https://files.facepunch.com/sampavlovic/1b1611b1/sbox-dev_bQfsZlWwop.png
* Final adjustments and cleanupj, name this Indirect Light Volume
* Indirect Light Volume prefab
* Remove log infos, adjust sliders
* Whoops
* format
* Rebase fix
* Re-apply the currently tracked cull mode so the override state binds immediately, makes sure that SetLayerNoCull works as intended
* Enqueue probes that affect geometry first, this gives quicker iteration results
https://files.facepunch.com/sampavlovic/1b1911b1/Screen_Recording_2026-01-19_at_16.16.36.mov
* float16 guards for ddgi_integrate, avoid NaNs
* Texture.Save supports volume textures
* DDGIVolume saves using the super nice Texture.Save api instead of texutre generator
* Do the same color punchy look thing we do for envmap probes in ddgi probes
https://files.facepunch.com/sampavlovic/1b1911b1/ezgif-13a1dd53fc7688fe.gif
* Don't save ddgi async, just do it directly
* DDGI editor icon
https://files.facepunch.com/sampavlovic/1b2011b1/F1I0KaZt5j.png
* Shaders
* Format
* Add EditorSystem.Camera
* Move texture creation to DDGIProbeUpdaterCubemapper, build probes in order from the camera, use async instead of an update
* Dont multiply DDGI diffuse by AO, that's done on the shadingmodel, this gives accurate multibounce as well
* Update probes sequentially again, makes sure results are consistent, if we do near camera again, should do it with same logic to keep probes that affect geometry first too
* Move NoPrepassCulling to RenderToCubeTexture, can't figure out why behaviour is different between these but solves light leak issue
* Fix overriding array not working for depth textures, make this clearer
* Overriden rasterizer state actually overrides when doing SetCullMode
* Rework border logic, should be seamless now
* Experiment with shadow sharpness being soft, with borders being fine the variance shadow map stuff from DDGI paper looks correct
https://files.facepunch.com/sampavlovic/1b2211b1/sbox-dev_UNTiRmJ7Fw.png
* Make Depth Sharpness a Property instead
* Delete copy_border shader, do everything within the same shader with groupshared memory, actually sample borders correctly, this reduces octahedral border aliasing to a minimum
https://files.facepunch.com/sampavlovic/1b2311b1/sbox-dev_FqPPpRZ6MD.pnghttps://files.facepunch.com/sampavlovic/1b2311b1/Source2Viewer_TiqGxdYWwX.png
* Distance calculations use unbiased probe-space position so when we move ddgi transform it doesnt fuck visibility up
* Make DDGI Debug Grid use gizmo pooling so it follows gizmo visibility rules (hidden when gizmos disabled, not in cubemaps)
* DDGI Relocation proto, Moves probes out of geometry to unfuck artifacts
https://files.facepunch.com/sampavlovic/1b2311b1/ezgif-1afaed4e1c2ac9a5.gifhttps://files.facepunch.com/sampavlovic/1b2311b1/ezgif-11a3e64fd2952590.gif
* DDGI less contrasty
* Parallelize relocation
* More resilient tracing tests & give option to deactivate if probe is fully inside of geometry instead of relocating those fully occluded
* Simplify LPVDebugGrid, remove all the fucking mess from it
* Simplify DDGIVolume, hide all the bullshit
* VTexWriter allows to save as uncompressed, make Relocation texture uncompressed since we want alpha to be flags
* Add Graphics.FlushGPU
Forces the GPU to flush all pending commands and wait for completion.
Useful when you need to ensure GPU work is finished before proceeding.
Can be called outside of a render block.
* DDGIVolume uses Graphics.FlushGPU instead of dumb Task.Await, density goes up to 10, Debug Grid updates if we change probes
* Format
* Doubt we are going to use flags, just store alpha as active state directly, makes it more resilient to eventual compression
* Sprite was calculating offset incorrectly and causing ambient lighting to be fucked, fog was fucked too
https://files.facepunch.com/sampavlovic/1b2611b1/sbox-dev_i3h4RcWncI.png
* DDGI should really be under Light not Misc
* Keep LPVDebugGrid render bounds infinite, SceneCusotmObject shouldnt change it from there
* Move RelocationTexture to same Generated behaviour so we dont override the property it while we are rendering, make probes default visible
* Remove shitty hack from scenecamera and make DDGIVolumeUpdater take scene renderattributes, makes DDGI bounces retrofeed from itself
Should fix terrain rendering being fucked in DDGI too
Will see to make this more generic
https://files.facepunch.com/sampavlovic/1b2611b1/sbox-dev_uzXYN0Qzw6.png
* Make Energy Loss an attribute for DDGI, I prefer a more contrasty look but there are scenes where you'd want more accurate energy conservation, first picture is an example where energy loss contrast makes it look worse
https://files.facepunch.com/sampavlovic/1b2611b1/sbox-dev_kdTH1qhEwR.pnghttps://files.facepunch.com/sampavlovic/1b2611b1/sbox-dev_gMoRxorzV0.png
* Progress system can be toasts
* Clean up progress system, always available now via Application.Editor.
* Added Scene/Bake DDGI Volumes
* Changed DDGIVolume to IndirectLightVolume
* Refinement steps for probe relocation
* Kill existing bake if disabled or ticking bake again
* EnergyLoss is Contrast and increase max density, keep default contrast to the value I'm happy with
* Don't call it the implementation name to the user
* formet
* Build shaders
* Bind dummy DDGI_Volumes buffer as default on high level
---------
Co-authored-by: Garry Newman <garrynewman@users.noreply.github.com>
Speculative fix for the EnqueueResourceManifest crash reported on Sentry.
The crash occurs in a thread-interlocked compare-exchange operation:
```
ThreadInterlockedCompareExchange64
ThreadInterlockedCompareExchangePointer
CTSQueue<T>::InterlockedCompareExchangeNode
CTSQueue<T>::Push
CTSQueue<T>::PushItem
CResourceSystem::EnqueueResourceManifest
CResourceSystem::CreateResourceManifestInternal
Exports::g_pRsrcSystm_LoadResourceInManifest
```
The most likely cause of a ThreadInterlock failure is a null pointer. AFAIK the resource queue only becomes null during shutdown, so this crash is probably a race condition where managed code attempts to load a resource while the engine is shutting down.
- Auto-cleanup idle voice handles: Voice sound handles are now killed when player hasn't been talking for a while
- Audio Occlusion refactor: Moved occlusion calculation to a dedicated SoundOcclusionSystem GameObjectSystem for better organization
- This now performs really well, so we could look into improving our occlusion calculation: proper damping when sound is transferred via wall, path tracing for occlusion etc. (will open a separate issue)
- Fix mixer early return bug: Fixed issue where mixer could return early, potentially skipping sounds
- Voice Component lipsync toggle: Added option to enable/disable lipsync processing on VoiceComponent
- Cheaper HRTF for voice audio: Disabled expensive bilinear interpolation for voice and certain other sounds in Steam Audio
- Editor MixerDetail perf: Skip frame updates on MixerDetail widget when not visible
- Reduced allocations in audio systems: Optimized away List and LINQ allocations in SoundOcclusionSystem, Listener, and SoundHandle.Source
- MP3 decoder buffer optimization: Improved buffer handling in native CAudioMixerWaveMP3 to reduce overhead
Depending on scenario can reduces audio frame time by up to 30%.
This should also drastically improve performance for games that use VOIP.
* Clustered prototype
* Cleanup
* Cleanup, clustered culling uses switch case for readability, put generic math classes in math folder,
* Decals evaluate as a sphere, simpler, API for envmaps and lights dont take positionSs anymore
* Revert this shit on complex
* TiledCullingLayer > ClusteredCullingLayer
* Remove references to UseClusteredLighting/UseTiledLighting, just make it work everywhere
* Remove old tiled culling files
* Remove ClusterFrustum.hlsl since we have a generic one
* Remove OBB as we just test simple bounds for decal
* Final cleanups
* Remove references to tiled culling from native engine
* Adjust toolsvis for clustered
* Rename tiled culling references to clustered culling in render pipeline and tools visualization
* Build shaders
* Format
* Fix fog's near frustum cull plane and clustered warning from Mr. Copilot
* remove the last crumbs from tiled rendering
* Update shaders
* skybox rendering use ClusteredCullingLayer instead of TiledCullingLayer
* Volume fog still referencing MAX_LIGHTS_PER_TILE, should have it using clusters but slower for now
* RENDER_DEFAULT_STATE_RASTERIZER probably shouldnt remove override of rasterizer state
* Add CSceneLayer::SetLayerNoCull(), we need it to avoid light leaks when rendering DDGI probes
* Update src/rendersystem/rendercontextbase.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Recreating it works fine on Intel GPU, no issues on validation but didnt work on other cards, there is a better way to do this from native without dangling around
Fixes https://github.com/Facepunch/sbox-issues/issues/9675
* Make MSAA setting work on editor SceneRenderingWidgets
MSAA settings never worked with editor viewport, only on game tab, now with playmode it always takes highest MSAA value from hardware regardless of what
Two things from this PR:
* Pass current engine MSAA setting when creating SceneRenderingWidget swapchain
* Add callback when video settings are changed, recreate swapchain when so
Replicates fine for all SceneRenderingWidgets, applies instantly
https://files.facepunch.com/sampavlovic/1b0311b1/sbox-dev_bjEMrrYe71.mp4
* Push RenderMultisampleType_t nMSAAAmount on interop