RT Camera fixes and improvements (#3019)

* Added FPS limiter to RT Cameras, refactored code a bit

* Changed RT Camera FPS limiter algorithm

* Actually made good RT Camera FPS limiter

* Changed ActiveCameras to be set-like array instead of set-like table.

* Fixed undefined behavior in RT Camera ENT:SetIsObserved

* Linter pass

* Removed debug print

* Fixed undefined behavior on `ObservedCameras` iteration

* Removed debug print *again*

* Removed unused `table.SeqCount` function
This commit is contained in:
stepa2 2024-03-26 20:18:20 +03:00 committed by GitHub
parent 1c5f5a0cb0
commit 60b23aa599
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 99 additions and 48 deletions

View File

@ -50,13 +50,17 @@ function ENT:TriggerInput( name, value )
end
if CLIENT then
local wire_rt_camera_resolution_h = CreateClientConVar("wire_rt_camera_resolution_h", "512", true, nil, nil, 128)
local wire_rt_camera_resolution_w = CreateClientConVar("wire_rt_camera_resolution_w", "512", true, nil, nil, 128)
local wire_rt_camera_filtering = CreateClientConVar("wire_rt_camera_filtering", "2", true, nil, nil, 0, 2)
local wire_rt_camera_hdr = CreateClientConVar("wire_rt_camera_hdr", "1", true, nil, nil, 0, 1)
local cvar_resolution_h = CreateClientConVar("wire_rt_camera_resolution_h", "512", true, nil, nil, 128)
local cvar_resolution_w = CreateClientConVar("wire_rt_camera_resolution_w", "512", true, nil, nil, 128)
local cvar_filtering = CreateClientConVar("wire_rt_camera_filtering", "2", true, nil, nil, 0, 2)
local cvar_hdr = CreateClientConVar("wire_rt_camera_hdr", "1", true, nil, nil, 0, 1)
local ActiveCameras = {}
local ObservedCameras = {}
-- array(Entity)
WireLib.__RTCameras_Active = WireLib.__RTCameras_Active or {}
local ActiveCameras = WireLib.__RTCameras_Active
-- table(Entity, true)
WireLib.__RTCameras_Observed = WireLib.__RTCameras_Observed or {}
local ObservedCameras = WireLib.__RTCameras_Observed
concommand.Add("wire_rt_camera_recreate", function()
for _, cam in ipairs(ObservedCameras) do
@ -66,12 +70,14 @@ if CLIENT then
local function SetCameraActive(camera, isActive)
if isActive then
ActiveCameras[camera] = true
if not table.HasValue(ActiveCameras, camera) then
table.insert(ActiveCameras, camera)
end
else
if camera.SetIsObserved then -- undefi
if camera.SetIsObserved then -- May be undefined (?)
camera:SetIsObserved(false)
end
ActiveCameras[camera] = nil
table.RemoveByValue(ActiveCameras, camera)
end
end
@ -97,22 +103,27 @@ if CLIENT then
self.IsObserved = isObserved
if isObserved then
local index = #ObservedCameras + 1
ObservedCameras[index] = self
self.ObservedCamerasIndex = index
self.ObservedCamerasIndex = table.insert(ObservedCameras, self)
self:InitRTTexture()
else
ObservedCameras[self.ObservedCamerasIndex] = nil
self.ObservedCamerasIndex = nil
self.RenderTarget = nil
local oldi = table.RemoveFastByValue(ObservedCameras, self)
if oldi == nil then return end
self.ObservedCamerasIndex = nil
local shifted_cam = ObservedCameras[oldi]
if IsValid(shifted_cam) then
shifted_cam.ObservedCamerasIndex = oldi
end
end
end
local function CreateRTName(index)
return "improvedrtcamera_rt_"..tostring(index).."_"..wire_rt_camera_filtering:GetString().."_"
..wire_rt_camera_resolution_h:GetString().."x"..wire_rt_camera_resolution_w:GetString()..
(wire_rt_camera_hdr:GetInt() and "_hdr" or "_ldr")
return "improvedrtcamera_rt_"..tostring(index).."_"..cvar_filtering:GetString().."_"
..cvar_resolution_h:GetString().."x"..cvar_resolution_w:GetString()..
(cvar_hdr:GetInt() and "_hdr" or "_ldr")
end
function ENT:InitRTTexture()
@ -120,17 +131,17 @@ if CLIENT then
local filteringFlag = 1 -- pointsample
if wire_rt_camera_filtering:GetInt() == 1 then
if cvar_filtering:GetInt() == 1 then
filteringFlag = 2 -- trilinear
elseif wire_rt_camera_filtering:GetInt() == 2 then
elseif cvar_filtering:GetInt() == 2 then
filteringFlag = 16 -- anisotropic
end
local isHDR = wire_rt_camera_hdr:GetInt() ~= 0
local isHDR = cvar_hdr:GetInt() ~= 0
local rt = GetRenderTargetEx(CreateRTName(index),
wire_rt_camera_resolution_w:GetInt(),
wire_rt_camera_resolution_h:GetInt(),
cvar_resolution_w:GetInt(),
cvar_resolution_h:GetInt(),
RT_SIZE_LITERAL,
MATERIAL_RT_DEPTH_SEPARATE,
filteringFlag + 256 + 32768,
@ -155,34 +166,54 @@ if CLIENT then
if CameraIsDrawn then return false end
end)
local function RenderCamerasImpl()
local isHDR = cvar_hdr:GetInt() ~= 0
local renderH = cvar_resolution_h:GetInt()
local renderW = cvar_resolution_w:GetInt()
local renderedCameras = 0
for _, ent in ipairs(ActiveCameras) do
if not IsValid(ent) or not ent.IsObserved then goto next_camera end
renderedCameras = renderedCameras + 1
render.PushRenderTarget(ent.RenderTarget)
local oldNoDraw = ent:GetNoDraw()
ent:SetNoDraw(true)
CameraIsDrawn = true
cam.Start2D()
render.OverrideAlphaWriteEnable(true, true)
render.RenderView({
origin = ent:GetPos(),
angles = ent:GetAngles(),
x = 0, y = 0, h = renderH, w = renderW,
drawmonitors = true,
drawviewmodel = false,
fov = ent:GetCamFOV(),
bloomtone = isHDR
})
cam.End2D()
CameraIsDrawn = false
ent:SetNoDraw(oldNoDraw)
render.PopRenderTarget()
::next_camera::
end
return renderedCameras
end
local cvar_skip_frame_per_cam = CreateClientConVar("wire_rt_camera_skip_frame_per_camera", 0.8, true, nil, nil, 0)
local SkippedFrames = 0
hook.Add("PreRender", "ImprovedRTCamera", function()
local isHDR = wire_rt_camera_hdr:GetInt() ~= 0
local renderH = wire_rt_camera_resolution_h:GetInt()
local renderW = wire_rt_camera_resolution_w:GetInt()
SkippedFrames = SkippedFrames - 1
for ent, _ in pairs(ActiveCameras) do
if IsValid(ent) and ent.IsObserved then
render.PushRenderTarget(ent.RenderTarget)
local oldNoDraw = ent:GetNoDraw()
ent:SetNoDraw(true)
CameraIsDrawn = true
cam.Start2D()
render.OverrideAlphaWriteEnable(true, true)
render.RenderView({
origin = ent:GetPos(),
angles = ent:GetAngles(),
x = 0, y = 0, h = renderH, w = renderW,
drawmonitors = true,
drawviewmodel = false,
fov = ent:GetCamFOV(),
bloomtone = isHDR
})
cam.End2D()
CameraIsDrawn = false
ent:SetNoDraw(oldNoDraw)
render.PopRenderTarget()
end
if SkippedFrames <= 0 then
local rendered_cams = RenderCamerasImpl()
SkippedFrames = math.ceil(rendered_cams * cvar_skip_frame_per_cam:GetFloat())
end
end)

View File

@ -20,6 +20,11 @@ if CLIENT then
language.Add("tool.wire_rt_camera.settings.cl_filtering_1", "Trilinear")
language.Add("tool.wire_rt_camera.settings.cl_filtering_2", "Anisotropic")
language.Add("tool.wire_rt_camera.settings.cl_apply", "Apply player-specific changes")
language.Add("tool.wire_rt_camera.settings.cl_skipframe", "Rendering slowdown")
language.Add("tool.wire_rt_camera.settings.cl_skipframe_hint",
"The greater this value, the greater your FPS is and the lesser FPS of the cameras is.\n"..
"Technically, it is amount of camera renders to skip per one rendered camera.\n"..
"Fractional values work too. Set to 0 to disable.")
WireToolSetup.setToolMenuIcon( "icon16/camera.png" )
end
@ -69,4 +74,6 @@ function TOOL.BuildCPanel(panel)
end
panel:Button("#tool.wire_rt_camera.settings.cl_apply", "wire_rt_camera_recreate")
panel:NumSlider("#tool.wire_rt_camera.settings.cl_skipframe", "wire_rt_camera_skip_frame_per_camera", 0, 3, 2)
panel:Help("#tool.wire_rt_camera.settings.cl_skipframe_hint")
end

View File

@ -41,6 +41,19 @@ function table.Compact(tbl, cb, n) -- luacheck: ignore
end
end
-- Removes `value` from `tbl` by shifting last element of `tbl` to its place.
-- Returns index of `value` if it was removed, nil otherwise.
function table.RemoveFastByValue(tbl, value)
for i, v in ipairs(tbl) do
if v == value then
tbl[i] = tbl[#tbl]
tbl[#tbl] = nil
return i
end
end
end
function string.GetNormalizedFilepath( path ) -- luacheck: ignore
local null = string.find(path, "\x00", 1, true)
if null then path = string.sub(path, 1, null-1) end