Customizing profiler UI in Defold, can I show only the metrics I care about?

I’m using “@render:”, “draw_text” works fine. draw_line draws on world space had to check camera angle to place lines on world to show correctly like it is an ui

local function format_mem(kb)
    if kb > 1024 then
        return string.format("%.2f MB", kb / 1024)
    else
        return string.format("%.2f KB", kb)
    end
end

-- Helper to convert logical screen coordinates to world coordinates fixed to the camera
local function ScreenToWorld(x, y, d, cam_pos, cam_rot, width, height, aspect)
    local fov = math.rad(45) -- Match free_camera.script
    local tan_half_fov = math.tan(fov / 2)

    -- Normalize screen coordinates to [-1, 1]
    local nx = (x / width - 0.5) * 2
    local ny = (y / height - 0.5) * 2

    -- Calculate position in camera space
    local dx = nx * d * tan_half_fov * aspect
    local dy = ny * d * tan_half_fov
    local dz = -d

    local local_pos = vmath.vector3(dx, dy, dz)
    -- Rotate to world space and add camera position
    return cam_pos + vmath.rotate(cam_rot, local_pos)
end

function init(self)
    msg.post(".", "acquire_input_focus")
    self.mode = 0 -- 0: Off, 1: Text, 2: Text + Graph
    self.frame_times = {}
    self.max_samples = 100
    self.current_fps = 0
    self.low_1_fps = 0

    -- Logical size from game.project
    self.logical_width = 960
    self.logical_height = 640
end

function update(self, dt)
    if self.mode == 0 then return end

    -- FPS calculation
    self.current_fps = 1 / math.max(dt, 0.0001)

    -- Sample management
    table.insert(self.frame_times, dt)
    if #self.frame_times > self.max_samples then
        table.remove(self.frame_times, 1)
    end

    -- 1% Low FPS calculation
    if #self.frame_times >= 10 then -- Need some minimal samples
        local sorted_times = {}
        for i, v in ipairs(self.frame_times) do sorted_times[i] = v end
        table.sort(sorted_times)

        -- The 1% slowest frames (largest dt)
        local index = math.max(1, math.floor(#sorted_times * 0.99))
        self.low_1_fps = 1 / math.max(sorted_times[index], 0.0001)
    end

    -- Base position and style
    local x_start = 200
    local y_start = 200
    local color = vmath.vector4(0, 1, 0, 1)

    -- Display Text
    local ram = collectgarbage("count")
    local faces = _G.stats_faces or 0
    local vertices = _G.stats_vertices or 0
    local build_time = _G.stats_mesh_build_time or 0

    local lines = {
        string.format("FPS: %.1f", self.current_fps),
        string.format("1%% Low FPS: %.1f", self.low_1_fps),
        string.format("Faces: %d", faces),
        string.format("Vertices: %d", vertices),
        string.format("Mesh Build: %.2f ms", build_time),
        string.format("RAM: %s", format_mem(ram))
    }

    for i, line in ipairs(lines) do
        msg.post("@render:", "draw_debug_text", {
            text = line,
            position = vmath.vector3(x_start, y_start - (i * 20), 0),
            color = color
        })
    end

    -- Display Graph (Mode 2)
    if self.mode == 2 and #self.frame_times > 1 then
        local success, cam_pos = pcall(go.get_position, "camera")
        if not success then return end -- Camera might not be ready
        local cam_rot = go.get_rotation("camera")
        local width, height = window.get_size()
        local aspect = width / height
        local d = 0.5 -- Distance in front of camera

        local graph_width = 200
        local graph_height = 80
        local graph_x = x_start
        local graph_y = y_start + 10 -- Position above text

        local max_graph_fps = 144    -- Cap visual height
        local min_graph_fps = 0
        local step_x = graph_width / (self.max_samples - 1)

        for i = 1, #self.frame_times - 1 do
            local fps1 = 1 / math.max(self.frame_times[i], 0.0001)
            local fps2 = 1 / math.max(self.frame_times[i + 1], 0.0001)

            -- Normalize h1 and h2
            local h1 = math.min(1, (fps1 - min_graph_fps) / max_graph_fps) * graph_height
            local h2 = math.min(1, (fps2 - min_graph_fps) / max_graph_fps) * graph_height

            local sx1, sy1 = graph_x + (i - 1) * step_x, graph_y + h1
            local sx2, sy2 = graph_x + i * step_x, graph_y + h2

            msg.post("@render:", "draw_line", {
                start_point = ScreenToWorld(sx1, sy1, d, cam_pos, cam_rot, self.logical_width, self.logical_height,
                    aspect),
                end_point = ScreenToWorld(sx2, sy2, d, cam_pos, cam_rot, self.logical_width, self.logical_height, aspect),
                color = color
            })
        end

        -- Target line (60 FPS)
        local h60 = (60 / max_graph_fps) * graph_height
        msg.post("@render:", "draw_line", {
            start_point = ScreenToWorld(graph_x, graph_y + h60, d, cam_pos, cam_rot, self.logical_width,
                self.logical_height, aspect),
            end_point = ScreenToWorld(graph_x + graph_width, graph_y + h60, d, cam_pos, cam_rot, self.logical_width,
                self.logical_height, aspect),
            color = vmath.vector4(0, 1, 0, 0.2)
        })
    end
end

function on_input(self, action_id, action)
    if action_id == hash("toggle_performance") and action.pressed then
        self.mode = (self.mode + 1) % 3
        local modes = { "Off", "Text", "Text + Graph" }
        print("Performance overlay mode: " .. modes[self.mode + 1])
    end
end

1 Like