Skip to content

Commit

Permalink
RT Camera fixes and improvements (#3019)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
stepa2 authored Mar 26, 2024
1 parent 1c5f5a0 commit 60b23aa
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 50 deletions.
131 changes: 81 additions & 50 deletions lua/entities/gmod_wire_rt_camera.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 ActiveCameras = {}
local ObservedCameras = {}
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)

-- 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
Expand All @@ -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

Expand All @@ -97,40 +103,45 @@ 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()
local index = self.ObservedCamerasIndex

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,
Expand All @@ -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()

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
SkippedFrames = SkippedFrames - 1

if SkippedFrames <= 0 then
local rendered_cams = RenderCamerasImpl()
SkippedFrames = math.ceil(rendered_cams * cvar_skip_frame_per_cam:GetFloat())
end
end)

Expand Down
7 changes: 7 additions & 0 deletions lua/wire/stools/rt_camera.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
13 changes: 13 additions & 0 deletions lua/wire/wireshared.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 60b23aa

Please sign in to comment.