local fps = {} do cvars.RemoveChangeCallback("fps_graph", "fps_graph") cvars.RemoveChangeCallback("fps_graph_width", "fps_graph_width") cvars.RemoveChangeCallback("fps_graph_points", "fps_graph_points") fps.X, fps.Y = 20, 20 fps.W, fps.H = 0, 150 fps.PointDistance = 0 fps.max = GetConVar("fps_max") fps.graph = CreateClientConVar("fps_graph", "0", nil, nil, "Draw the FPS counter and/or graph, = 1 draws counter, = 2 draws bar graph and counter, = 3 draws line graph and counter.") fps.graph_points = CreateClientConVar("fps_graph_points", "60", nil, nil, "The number of points/bars the FPS graph will have.") fps.graph_width = CreateClientConVar("fps_graph_width", "500", nil, nil, "The FPS graph's target width. Will change to match the points.") fps.Gradient = Material("vgui/gradient-d") fps.Cache = setmetatable({}, {__index = function() return 0 end}) fps.NextCount = 0 fps.SinceLastUpdate = 0 fps.LastFrameNumber = FrameNumber() end local function CountFrames() if (SysTime() < fps.NextCount) then return end fps.NextCount = SysTime() + 1 local frames = FrameNumber() local diff = frames - fps.LastFrameNumber local limit = math.max(fps.max:GetInt(), 60) table.insert(fps.Cache, 1, math.Clamp(diff / limit, 0, 1)) if (#fps.Cache > fps.graph_points:GetInt() + 1) then table.remove(fps.Cache) end fps.SinceLastUpdate = diff fps.LastFrameNumber = frames end local function DrawCounter() local x, y = fps.X, fps.Y local w, h = fps.W, fps.H local FPS = string.format("%i FPS", fps.SinceLastUpdate) if (fps.graph:GetInt() == 1) then draw.SimpleText(FPS, "DebugOverlay", x, y) else local mult = fps.Cache[1] local tx = x + w + 2 local ty = y + (h - (h * mult)) draw.SimpleText(FPS, "DebugOverlay", tx, ty, _, _, TEXT_ALIGN_CENTER) end end local function DrawBarGraph() local x, y = fps.X, fps.Y local w, h = fps.W, fps.H local pd = fps.PointDistance local points = fps.graph_points:GetInt() render.SetScissorRect(x, y, x + w, y + h, true) surface.SetDrawColor(0, 0, 0, 200) surface.DrawRect(x, y, w, h) for i = 1, points do local mult = fps.Cache[i] local bh = math.floor(h * mult) local bx = (x + w) - (i * pd) local by = y + (h - bh) surface.SetDrawColor(Color(255 - 255 * mult, 255 * mult, 0)) surface.DrawRect(bx, by, pd, bh) end render.SetScissorRect(0, 0, 0, 0, false) end local function DrawLineGraph() local x, y = fps.X, fps.Y local w, h = fps.W, fps.H local pd = fps.PointDistance local points = fps.graph_points:GetInt() render.SetScissorRect(x, y, x + w, y + h, true) surface.SetDrawColor(0, 0, 0, 200) surface.DrawRect(x, y, w, h) render.ClearStencil() render.SetStencilEnable(true) render.SetStencilTestMask(0xFF) render.SetStencilWriteMask(0xFF) render.SetStencilReferenceValue(0x01) render.SetStencilCompareFunction(STENCIL_ALWAYS) render.SetStencilPassOperation(STENCIL_REPLACE) local first = fps.Cache[1] local pph = math.floor(h * first) local ppx = (x + w) - 1 local ppy = y + (h - pph) for i = 2, points + 1 do local mult = fps.Cache[i] local ph = math.floor(h * mult) local px = ((x + w) - ((i - 1) * pd)) - 1 local py = y + (h - ph) surface.SetDrawColor(0, 255, 0) surface.DrawLine(ppx, ppy, px, py) ppx, ppy = px, py end render.SetStencilCompareFunction(STENCIL_LESSEQUAL) render.SetStencilFailOperation(STENCIL_KEEP) surface.SetMaterial(fps.Gradient) surface.SetDrawColor(255, 0, 0) surface.DrawTexturedRect(x, y, w, h) render.SetStencilEnable(false) render.SetScissorRect(0, 0, 0, 0, false) end local function GraphChanged(_, old, new) old, new = tonumber(old) or 0, tonumber(new) or 0 if (new > 0) then if (old <= 0) then fps.SinceLastUpdate = 0 fps.LastFrameNumber = FrameNumber() end hook.Add("PreRender", "FPS Counter", CountFrames) if (new >= 1) then hook.Add("PostDrawHUD", "FPS Counter", DrawCounter) if (new == 2) then hook.Add("PostDrawHUD", "FPS Graph", DrawBarGraph) elseif (new >= 3) then hook.Add("PostDrawHUD", "FPS Graph", DrawLineGraph) else hook.Remove("PostDrawHUD", "FPS Graph") end end else table.Empty(fps.Cache) hook.Remove("PreRender", "FPS Counter") hook.Remove("PostDrawHUD", "FPS Counter") hook.Remove("PostDrawHUD", "FPS Graph") end end cvars.AddChangeCallback("fps_graph", GraphChanged, "fps_graph") local function FindClosest(width, points) local low = width % points local high = (width + low) % points return low <= high and width - low or width + high end local function WidthChanged(_, _, new) local points = fps.graph_points:GetInt() fps.W = FindClosest(new, points) fps.PointDistance = fps.W / points end cvars.AddChangeCallback("fps_graph_width", WidthChanged, "fps_graph_width") local function PointsChanged(_, old, new) old, new = tonumber(old) or 0, tonumber(new) or 60 local target = fps.graph_width:GetInt() fps.W = FindClosest(target, new) fps.PointDistance = fps.W / new if (old < new) then return end local diff = old - new if (#fps.Cache - diff > new) then for i = 1, diff do table.remove(fps.Cache) end end end cvars.AddChangeCallback("fps_graph_points", PointsChanged, "fps_graph_points") GraphChanged(_, _, fps.graph:GetInt()) WidthChanged(_, _, fps.graph_width:GetInt())