diff options
-rw-r--r-- | player/lua/stats.lua | 207 |
1 files changed, 138 insertions, 69 deletions
diff --git a/player/lua/stats.lua b/player/lua/stats.lua index 000708c876..1b3a61d739 100644 --- a/player/lua/stats.lua +++ b/player/lua/stats.lua @@ -24,9 +24,12 @@ local o = { debug = false, -- Graph options and style - plot_graphs = true, + plot_perfdata = true, + plot_vsync_ratio = true, + plot_vsync_jitter = true, skip_frames = 5, global_max = true, + flush_graph_data = true, -- clear data buffers when toggling plot_bg_border_color = "0000FF", plot_bg_color = "262626", plot_color = "FFFFFF", @@ -65,12 +68,7 @@ local o = { options.read_options(o) local format = string.format --- Buffer for the "last" value of performance data for render/present/upload -local plast = {{0}, {0}, {0}} --- Position in buffer -local ppos = 1 --- Length of buffer -local plen = 50 +local max = math.max -- Function used to record performance data local recorder = nil -- Timer used for toggling @@ -79,6 +77,18 @@ local timer = nil local ass_start = mp.get_property_osd("osd-ass-cc/0") local ass_stop = mp.get_property_osd("osd-ass-cc/1") +-- Ring buffers for the values used to construct a graph. +-- .pos denotes the current position, .len the buffer length +-- .max is the max value in the corresponding buffer as computed in record_data(). +-- `plast_buf` is a table of buffers for the "last" value of performance data +-- for render/present/upload. +local plast_buf, vsratio_buf, vsjitter_buf +local function init_buffers() + plast_buf = {{0, max = 0}, {0, max = 0}, {0, max = 0}, pos = 1, len = 50} + vsratio_buf = {0, pos = 1, len = 50, max = 0} + vsjitter_buf = {0, pos = 1, len = 50, max = 0} +end + local function set_ASS(b) if not o.ass_formatting then @@ -138,6 +148,7 @@ local function has_ansi() return true end + -- Generate a graph from the given values. -- Returns an ASS formatted vector drawing as string. -- @@ -149,8 +160,8 @@ end -- values to a range of 0 to `v_max`. -- scale : A value that will be multiplied with all data values. local function generate_graph(values, i, len, v_max, scale) - -- check if at least one value was recorded yet (we assume lua-style 1-indexing) - if i < 1 then + -- Check if at least one value exists + if not values[i] then return "" end @@ -180,6 +191,43 @@ local function generate_graph(values, i, len, v_max, scale) end +-- Format and append a property. +-- A property whose value is either `nil` or empty (hereafter called "invalid") +-- is skipped and not appended. +-- Returns `false` in case nothing was appended, otherwise `true`. +-- +-- s : Table containing strings. +-- prop : The property to query and format (based on its OSD representation). +-- attr : Optional table to overwrite certain (formatting) attributes for +-- this property. +-- exclude: Optional table containing keys which are considered invalid values +-- for this property. Specifying this will replace empty string as +-- default invalid value (nil is always invalid). +local function append_property(s, prop, attr, excluded) + excluded = excluded or {[""] = true} + local ret = mp.get_property_osd(prop) + if not ret or excluded[ret] then + if o.debug then + print("No value for property: " .. prop) + end + return false + end + + attr.prefix_sep = attr.prefix_sep or o.prefix_sep + attr.indent = attr.indent or o.indent + attr.nl = attr.nl or o.nl + attr.suffix = attr.suffix or "" + attr.prefix = attr.prefix or "" + attr.no_prefix_markup = attr.no_prefix_markup or false + attr.prefix = attr.no_prefix_markup and attr.prefix or b(attr.prefix) + ret = attr.no_value and "" or ret + + s[#s+1] = format("%s%s%s%s%s%s", attr.nl, attr.indent, + attr.prefix, attr.prefix_sep, no_ASS(ret), attr.suffix) + return true +end + + local function append_perfdata(s) local vo_p = mp.get_property_native("vo-performance") if not vo_p then @@ -194,7 +242,7 @@ local function append_perfdata(s) local avg_s = vo_p["render-avg"] + vo_p["present-avg"] + vo_p["upload-avg"] local peak_s = vo_p["render-peak"] + vo_p["present-peak"] + vo_p["upload-peak"] - -- highlight i with a red border when t exceeds the time for one frame + -- Highlight i with a red border when t exceeds the time for one frame -- or yellow when it exceeds a given threshold local function hl(i, t) if o.timing_warning and target_fps > 0 then @@ -212,21 +260,17 @@ local function append_perfdata(s) local rsuffix, psuffix, usuffix - if o.plot_graphs and o.ass_formatting and timer:is_enabled() then - local max = {1, 1, 1} - for e = 1, plen do - if plast[1][e] and plast[1][e] > max[1] then max[1] = plast[1][e] end - if plast[2][e] and plast[2][e] > max[2] then max[2] = plast[2][e] end - if plast[3][e] and plast[3][e] > max[3] then max[3] = plast[3][e] end - end + -- Plot graphs when configured and we are toggled + if o.plot_perfdata and o.ass_formatting and timer:is_enabled() then + local pmax = {plast_buf[1].max, plast_buf[2].max, plast_buf[3].max} if o.global_max then - max[1] = math.max(max[1], max[2], max[3]) - max[2], max[3] = max[1], max[1] + pmax[1] = max(pmax[1], pmax[2], pmax[3]) + pmax[2], pmax[3] = pmax[1], pmax[1] end - rsuffix = generate_graph(plast[1], ppos, plen, max[1], 0.8) - psuffix = generate_graph(plast[2], ppos, plen, max[2], 0.8) - usuffix = generate_graph(plast[3], ppos, plen, max[3], 0.8) + rsuffix = generate_graph(plast_buf[1], plast_buf.pos, plast_buf.len, pmax[1], 0.8) + psuffix = generate_graph(plast_buf[2], plast_buf.pos, plast_buf.len, pmax[2], 0.8) + usuffix = generate_graph(plast_buf[3], plast_buf.pos, plast_buf.len, pmax[3], 0.8) s[#s+1] = format("%s%s%s%s{\\fs%s}%s%s%s{\\fs%s}", o.nl, o.indent, b("Frame Timings:"), o.prefix_sep, o.font_size * 0.66, @@ -260,40 +304,42 @@ local function append_perfdata(s) end --- Format and append a property. --- A property whose value is either `nil` or empty (hereafter called "invalid") --- is skipped and not appended. --- Returns `false` in case nothing was appended, otherwise `true`. --- --- s : Table containing strings. --- prop : The property to query and format (based on its OSD representation). --- attr : Optional table to overwrite certain (formatting) attributes for --- this property. --- exclude: Optional table containing keys which are considered invalid values --- for this property. Specifying this will replace empty string as --- default invalid value (nil is always invalid). -local function append_property(s, prop, attr, excluded) - excluded = excluded or {[""] = true} - local ret = mp.get_property_osd(prop) - if not ret or excluded[ret] then - if o.debug then - print("No value for property: " .. prop) - end - return false +function append_display_sync(s) + if not mp.get_property_bool("display-sync-active", false) then + return end - attr.prefix_sep = attr.prefix_sep or o.prefix_sep - attr.indent = attr.indent or o.indent - attr.nl = attr.nl or o.nl - attr.suffix = attr.suffix or "" - attr.prefix = attr.prefix or "" - attr.no_prefix_markup = attr.no_prefix_markup or false - attr.prefix = attr.no_prefix_markup and attr.prefix or b(attr.prefix) - ret = attr.no_value and "" or ret + local vspeed = append_property(s, "video-speed-correction", {prefix="DS:"}) + if vspeed then + append_property(s, "audio-speed-correction", + {prefix="/", nl="", indent=" ", prefix_sep=" ", no_prefix_markup=true}) + else + append_property(s, "audio-speed-correction", + {prefix="DS:" .. o.prefix_sep .. " - / ", prefix_sep=""}) + end - s[#s+1] = format("%s%s%s%s%s%s", attr.nl, attr.indent, - attr.prefix, attr.prefix_sep, no_ASS(ret), attr.suffix) - return true + -- Since no graph is needed we can print ratio/jitter on the same line and save some space + if not (o.plot_vsync_ratio or o.plot_vsync_jitter) then + local vratio = append_property(s, "vsync-ratio", {prefix="VSync Ratio:"}) + append_property(s, "vsync-jitter", {prefix="VSync Jitter:", nl=vratio and "" or o.nl}) + return + end + + -- As we potentially need to plot some graphs we print jitter and ratio on + -- their own lines so we have the same layout when toggled (= drawing graphs) + local ratio_graph = "" + local jitter_graph = "" + if o.ass_formatting and timer:is_enabled() then + if o.plot_vsync_ratio then + ratio_graph = generate_graph(vsratio_buf, vsratio_buf.pos, vsratio_buf.len, vsratio_buf.max, 0.8) + end + if o.plot_vsync_jitter then + jitter_graph = generate_graph(vsjitter_buf, vsjitter_buf.pos, vsjitter_buf.len, vsjitter_buf.max, 0.8) + end + end + + append_property(s, "vsync-ratio", {prefix="VSync Ratio:", suffix=o.prefix_sep .. ratio_graph}) + append_property(s, "vsync-jitter", {prefix="VSync Jitter:", suffix=o.prefix_sep .. jitter_graph}) end @@ -351,11 +397,8 @@ local function add_video(s) append_property(s, "estimated-vf-fps", {prefix="FPS:", suffix=" (estimated)"}) end - if append_property(s, "video-speed-correction", {prefix="DS:"}, {["+0.00000%"]=true}) then - append_property(s, "audio-speed-correction", - {prefix="/", nl="", indent=" ", prefix_sep=" ", no_prefix_markup=true}) - end + append_display_sync(s) append_perfdata(s) if append_property(s, "video-params/w", {prefix="Native Resolution:"}) then @@ -429,8 +472,9 @@ local function print_stats(duration) end -local function record_perfdata(skip) - skip = math.max(skip, 0) +local function record_data(skip) + init_buffers() + skip = max(skip, 0) local i = skip return function() if i < skip then @@ -440,28 +484,53 @@ local function record_perfdata(skip) i = 0 end - local vo_p = mp.get_property_native("vo-performance") - if not vo_p then - return + if o.plot_perfdata then + local vo_p = mp.get_property_native("vo-performance") + if vo_p then + plast_buf.pos = (plast_buf.pos % plast_buf.len) + 1 + plast_buf[1][plast_buf.pos] = vo_p["render-last"] + plast_buf[1].max = max(plast_buf[1].max, plast_buf[1][plast_buf.pos]) + plast_buf[2][plast_buf.pos] = vo_p["present-last"] + plast_buf[2].max = max(plast_buf[2].max, plast_buf[2][plast_buf.pos]) + plast_buf[3][plast_buf.pos] = vo_p["upload-last"] + plast_buf[3].max = max(plast_buf[3].max, plast_buf[3][plast_buf.pos]) + end + end + + if o.plot_vsync_jitter then + local r = mp.get_property_number("vsync-jitter", nil) + if r then + vsjitter_buf.pos = (vsjitter_buf.pos % vsjitter_buf.len) + 1 + vsjitter_buf[vsjitter_buf.pos] = r + vsjitter_buf.max = max(vsjitter_buf.max, r) + end + end + + if o.plot_vsync_ratio then + local r = mp.get_property_number("vsync-ratio", nil) + if r then + vsratio_buf.pos = (vsratio_buf.pos % vsratio_buf.len) + 1 + vsratio_buf[vsratio_buf.pos] = r + vsratio_buf.max = max(vsratio_buf.max, r) + end end - ppos = (ppos % plen) + 1 - plast[1][ppos] = vo_p["render-last"] - plast[2][ppos] = vo_p["present-last"] - plast[3][ppos] = vo_p["upload-last"] end end local function toggle_stats() + -- Disable if timer:is_enabled() then - if o.plot_graphs then + if recorder then mp.unregister_event(recorder) + recorder = nil end timer:kill() mp.osd_message("", 0) + -- Enable else - if o.plot_graphs then - recorder = record_perfdata(o.skip_frames) + if o.plot_perfdata or o.plot_vsync_jitter or o.plot_vsync_ratio then + recorder = record_data(o.skip_frames) mp.register_event("tick", recorder) end timer:resume() |