Files
gen1recomp/src/core/RefreshRate.lua
T

117 lines
3.3 KiB
Lua

local RefreshRate = {}
local DECLS = {
"typedef struct SDL_Window SDL_Window;",
"SDL_Window *SDL_GL_GetCurrentWindow(void);",
"typedef struct { uint32_t format; int w; int h; int refresh_rate;" ..
" void *driverdata; } SDL_DisplayMode;",
"int SDL_GetWindowDisplayIndex(SDL_Window *window);",
"int SDL_GetCurrentDisplayMode(int displayIndex, SDL_DisplayMode *mode);",
}
local ffiState = nil
local function sdlFfi()
if ffiState ~= nil then return ffiState or nil end
if not (love and love.window) then return nil end
local okFfi, ffi = pcall(require, "ffi")
if not okFfi then ffiState = false return nil end
for _, decl in ipairs(DECLS) do pcall(ffi.cdef, decl) end
local ok = pcall(ffi.new, "SDL_DisplayMode")
ffiState = ok and ffi or false
return ffiState or nil
end
local function sdlHz()
local ffi = sdlFfi()
if not ffi then return nil end
local ok, hz = pcall(function()
local index = 0
local window = ffi.C.SDL_GL_GetCurrentWindow()
if window ~= nil then
local got = ffi.C.SDL_GetWindowDisplayIndex(window)
if got >= 0 then index = got end
end
local mode = ffi.new("SDL_DisplayMode")
if ffi.C.SDL_GetCurrentDisplayMode(index, mode) ~= 0 then return nil end
return tonumber(mode.refresh_rate)
end)
if not ok or not hz or hz <= 0 then return nil end
return hz
end
local SAMPLES = 31
local ring, cursor, filled = {}, 0, 0
local measured = nil
local function quantize(hz)
if not hz or hz <= 0 then return nil end
local whole = math.floor(hz + 0.5)
local nearest = math.floor(whole / 60 + 0.5) * 60
if nearest > 0 and math.abs(whole - nearest) <= 1 then return nearest end
return whole
end
local function median(values)
local sorted = {}
for i = 1, #values do sorted[i] = values[i] end
table.sort(sorted)
local mid = sorted[math.floor(#sorted / 2) + 1]
local lo = sorted[math.floor(#sorted * 0.25) + 1]
local hi = sorted[math.floor(#sorted * 0.75) + 1]
if not mid or mid <= 0 or (hi - lo) > mid * 0.1 then return nil end
return mid
end
function RefreshRate.sample(dt)
if type(dt) ~= "number" or dt <= 0 or dt > 0.2 then return false end
cursor = cursor % SAMPLES + 1
ring[cursor] = dt
filled = math.min(filled + 1, SAMPLES)
if filled < SAMPLES or cursor ~= SAMPLES then return false end
local period = median(ring)
if not period then return false end
local hz = quantize(1 / period)
if not hz or measured == hz then return false end
measured = hz
return true
end
local queried, queriedAt = nil, nil
local function now()
if love and love.timer and love.timer.getTime then return love.timer.getTime() end
return nil
end
function RefreshRate.hz()
local time = now()
if queried == nil or not time or not queriedAt or time - queriedAt > 1 then
queried = sdlHz() or false
queriedAt = time or 0
end
local hz = quantize(queried or measured)
if not hz or hz <= 20 or hz >= 1000 then return nil end
return hz
end
function RefreshRate.period()
local hz = RefreshRate.hz()
return hz and 1 / hz or nil
end
function RefreshRate.mismatch()
local hz = RefreshRate.hz()
if not hz then return nil end
local nearest = math.floor(hz / 60 + 0.5) * 60
if math.abs(hz - nearest) <= 1 then return nil end
return hz
end
function RefreshRate.reset()
ffiState, queried, queriedAt, measured = nil, nil, nil, nil
ring, cursor, filled = {}, 0, 0
end
return RefreshRate