Facepunch / garrysmod-requests

Feature requests for Garry's Mod
83 stars 24 forks source link

Ability to change shadow depth texture computation rate for ProjectedTexture (FPS saver) #2137

Open maurits150 opened 1 year ago

maurits150 commented 1 year ago

Edited this issue to be more TLDR.

Feature 1 [must have]: controllable shadow computation rate:

I would love if a lua controllable continue could be added in the ComputeShadowDepthTextures loop loop.

A lua coder could call for example SetNextComputation(CurTime() + (1/ 25)) to set the shadow update rate to 25 FPS, avoiding rendering everything in the flashlight's frustrum (picture of r_flashlightdrawfrustrum 1) every client frame (which practically always includes at least worldspawn, often cutting your fps by 30% or more per light).

The new ShadowComputationRate variable could be stored in the FlashlightState_t. This state is already controlled via lua for e.g. depth bias changes so it should not be hard to add this.

Feature 2 [could have]: shadow vertex caching:

It looks like it's also possible to cache the shadow vertices that are projected onto the world to render the shadow texture (picture of r_flashlightdrawfrustrum 1 + r_shadowwireframe 1)

See code at https://github.com/ValveSoftware/source-sdk-2013/blob/master/mp/src/game/client/clientshadowmgr.cpp#L1856 This would save even more FPS because the shadow mesh won't be recalculated every frame.

This looks similar to the 'm_bAlwaysUpdate' (via inputs AlwaysUpdateOn/AlwaysUpdateOff) variable that's present on env_projectedtexture for Alien Swarm engine branch.

Feature 3 [could have], increase default -numshadowtextures:

Combining feature 1 and 2 should allow for dynamic shadows with almost 0 FPS impact on the game. This is a killer feature for mods that do global illumination such as RealCSM.

In that case it would be nice if -numshadowtextures could be increased to allow more shadows in general.

Feature 4 [could have], game wide convar:

The ability to throttle the shadow update rate in the entire game using a console command would allow game wide FPS savings at the cost of a bit more choppy shadows (at say 45 FPS it would still be playable but make e.g. car headlights or player flashlight have a lot less impact, especially if the game could otherwise run at 200 FPS or higher)

ZH-Hristov commented 1 year ago

IIRC Portal 2's branch has projected texture caching, but the code for that is probably not public. ASW branch might have it though.

maurits150 commented 1 year ago

Original OP contents.

I am trying to render a mesh based world that looks like this:

Rendering the mesh is a bit more expensive compared to a brush based world but I am able to make it work.

In order to light this world I want to use a ProjectedTexture (practically the only way to do this without it looking like garbage).

A ProjectedTexture with shadows enabled stores its depth information in a texture called _rtshadowdepthtexture<0-7>. This looks like this for my world: (+mat_texture_list -> Render Targets and Special Textures)

This texture seems to be rendered using the standard CViewSetup (kinda like render.RenderView).
This happens every frame, causing each dynamic shadow to render the entire world again but from the lamp's perspective. This is why dynamic shadows have a huge performance impact.

Engine code: https://github.com/ValveSoftware/source-sdk-2013/blob/master/mp/src/game/client/clientshadowmgr.cpp#L3949

In my case I am rendering a static world light that does not move much and the shadows only need to be computed every few seconds as the player moves around. Rendering the entire (mesh) world each frame just to generate a static shadowdepthtexture is a waste of CPU time.

What I am requesting is the ability to disable ComputeShadowDepthTextures for a specific ProjectedTexture object. The allocated shadowdepth render target will simply keep the last rendered value in memory. This way I am able to keep casting shadows while not updating them in real-time and saving lots of CPU time.

The function to change this could e.g. be called ProjectedTexture:SetNextShadowComputation (similar to some NextThink functions). It accepts the time at which the next shadow computation should take place. Setting it to CurTime() + 1 will make the flashlight update after 1 second. The default value will be 0 which is always below CurTime() and will cause shadows to be always computed by default.

The NextShadowComputation timestamp can be stored in the FlashlightState_t. This state that already contains the shadow depth biases which are already modifiable through lua and available in the loop. The ComputeShadowDepthTextures loop simply needs to continue a specific texture when the associated flashlight state computation timestamp is in the future.


Optionally, the ability to throttle this update rate in the entire game (excluding flashlight) using a console command would allow game wide FPS savings at the cost of a bit more choppy shadows (at say 20 FPS it would still be playable?)


It looks like it's also possible to cache the shadow vertices between frames.

https://github.com/ValveSoftware/source-sdk-2013/blob/master/mp/src/game/client/clientshadowmgr.cpp#L1856

If this implies that we can cache the actual vertex cache.. the one render.RenderFlashlights seems to build and render out:

Then we could make static shadows with close to zero resource usage?

Not sure if my assumptions are correct here, I am getting a bit in over my head.


The maximum number of projected textures could also be increased beyond 8 by default if the update rate was lowered for those slots because the performance impact would be way less.


I kinda managed to assault the engine into doing this by setting SetEnableShadows(true)+Update() the lamp before render.RenderFlashlights my world, and then setting it back to false after rendering flashlights.

Here I put two pallets next to each other then disabled updating the depth texture using this trick, and the shadow becomes frozen.

The only issue is that if there's any other other lamps the RT gets overwritten by those again, because I can't get the lamp to keep the texture reserved and when the next lamp gets rendering it overwrites the RT buffer again.. so it's kinda useless still.

Now if I could cache the RenderFlashlights vertices as well I could render this scene at ~20 fps on my GTX 970 (around 17k meshes, 16k hammer units in size, obviously would reduce the render distance in a real world map and use viewport culling)

maurits150 commented 11 months ago

IIRC Portal 2's branch has projected texture caching, but the code for that is probably not public. ASW branch might have it though.

Yea I guess it is this from the https://developer.valvesoftware.com/wiki/Env_projectedtexture wiki:

Lightstyle was backported so maybe this one can be as well? Although I am not sure if I can use the actual entity because I want to render this thing clientside only because people are in different positions in the mesh world.

ZH-Hristov commented 11 months ago

Yeah it'd need to be ported to GMod's projected textures, which are not "entities" per se

Jaffies commented 11 months ago

MUST HAVE

maurits150 commented 6 months ago

Having a crack at this again. Figured that merging meshes into a single one helps more than expected.. a few thousand draw calls is pretty heavy. So my individual world building blocks are now merged into a single mesh (per material and per max 64k tri's).

Still, this feature would be great to get my FPS back into the 300+ range. It would also allow me to add more 'quality' levels without sacrificing more FPS, by using more than a single projected textures, like in this example.

alt text

There are workshop mods that do this same trick to achieve dynamic world lighting. Having this feature would also make these mods much more efficient.

Real CSM - Modern Lighting & Realistic Dynamic Shadows https://steamcommunity.com/sharedfiles/filedetails/?id=2817072898

Just having the dynamic lights not update the render target (and render the whole world) every frame saves on rendering time. And being able to cache the mesh it projects into the world (like portal 2) would save even more FPS. I already mentioned where it's found in the engine code last year.