Files
gen1recomp/tests/engine/frame_cap_display.lua
T
1jamie 2f6c5527f6 Add PresentSync integration for improved VSync handling
- Introduced PresentSync module to manage display synchronization.
- Updated love.run() to handle display changes and resizing events with PresentSync.
- Enhanced FrameCap logic to accommodate PresentSync requirements.
- Modified VSync to report effective states and handle driver quirks.
- Updated options menus to reflect PresentSync availability and restrictions.
- Added tests to validate PresentSync functionality and its interaction with VSync settings.

should address issue #1910 and issue #1958
2026-08-29 19:43:25 -05:00

108 lines
3.9 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.modkit")
local FrameCap = require("src.core.FrameCap")
local RefreshRate = require("src.core.RefreshRate")
RefreshRate.reset()
T.eq(FrameCap.DISPLAY, 0, "DISPLAY is the zero cap")
T.eq(FrameCap.CYCLE[#FrameCap.CYCLE], FrameCap.DISPLAY,
"and it is the last stop on the cycle ring")
T.eq(#FrameCap.CYCLE, #FrameCap.STEPS + 1,
"the ring is the steps plus that one stop")
T.eq(FrameCap.normalize(0), FrameCap.DISPLAY, "a stored 0 is DISPLAY")
T.eq(FrameCap.normalize(-5), FrameCap.DISPLAY, "and so is a negative cap")
T.eq(FrameCap.normalize(1), 30, "a positive cap below the floor still clamps")
T.eq(FrameCap.normalize(9999), 160, "and one above the ceiling still clamps")
T.eq(FrameCap.normalize(nil), 60, "nil is still the 60 default")
T.eq(FrameCap.normalize("junk"), 60, "and so is garbage")
T.eq(FrameCap.label(0), "DISPLAY", "the row prints DISPLAY for the zero cap")
T.eq(FrameCap.label(60), "60", "and plain numbers for the rest")
T.eq(FrameCap.cycle(160, 1), FrameCap.DISPLAY, "the ceiling cycles to DISPLAY")
T.eq(FrameCap.cycle(FrameCap.DISPLAY, 1), 30, "which then wraps to the floor")
T.eq(FrameCap.cycle(30, -1), FrameCap.DISPLAY, "and back down again")
T.eq(FrameCap.cycle(FrameCap.DISPLAY, -1), 160, "DISPLAY steps down to 160")
local before = FrameCap.current
FrameCap.apply(FrameCap.DISPLAY)
T.eq(FrameCap.current, 0, "apply stores the uncapped value as-is")
FrameCap.apply(before)
local opts = { fpsCap = 60 }
FrameCap.applyOptions(opts)
T.eq(opts.fpsCap, 60, "an unknown refresh leaves the 60 default alone")
T.eq(FrameCap.current, 60, "and paces to it")
local function measure(hz)
RefreshRate.reset()
for _ = 1, 31 do RefreshRate.sample(1 / hz) end
end
measure(90)
T.eq(RefreshRate.mismatch(), 90, "90Hz is not a multiple of 60")
T.eq(FrameCap.label(0), "DISPLAY (90HZ)", "so the row names the panel rate")
T.eq(FrameCap.label(60), "60 (90HZ)", "beside whatever the cap reads")
FrameCap.migrated = false
opts = { fpsCap = 60 }
FrameCap.applyOptions(opts)
T.eq(opts.fpsCap, 60, "a mismatch panel no longer auto-migrates to DISPLAY")
T.eq(FrameCap.current, 60, "and keeps pacing at the stored cap")
T.eq(opts.fpsCapMigrated, nil, "without stamping a migration marker")
opts = { fpsCap = 144 }
FrameCap.applyOptions(opts)
T.eq(opts.fpsCap, 144, "a cap that is not the default is never rewritten")
measure(144)
T.eq(RefreshRate.mismatch(), 144, "144Hz is no multiple of 60 either")
FrameCap.migrated = false
opts = { fpsCap = 60 }
FrameCap.applyOptions(opts)
T.eq(opts.fpsCap, 60, "nor does a high-Hz panel rewrite a default 60 cap")
measure(75)
FrameCap.migrated = false
opts = { fpsCap = 60 }
FrameCap.applyOptions(opts)
T.eq(opts.fpsCap, 60, "75Hz mismatch also leaves the default alone")
measure(120)
T.eq(RefreshRate.mismatch(), nil, "120Hz shows 60Hz logic 1:1")
T.eq(FrameCap.label(60), "60", "so the row says nothing about it")
FrameCap.migrated = false
opts = { fpsCap = 60 }
FrameCap.applyOptions(opts)
T.eq(opts.fpsCap, 60, "and the 60 default stays put")
measure(59.94)
T.eq(RefreshRate.mismatch(), nil, "a 59.94Hz panel is within a hertz of 60")
measure(59)
T.eq(RefreshRate.hz(), 60, "the integer 59 a driver reports for it reads as 60")
T.eq(RefreshRate.period(), 1 / 60, "so the step period snapped to is the 60Hz one")
T.eq(RefreshRate.mismatch(), nil, "and it is no mismatch")
FrameCap.migrated = false
opts = { fpsCap = 60 }
FrameCap.applyOptions(opts)
T.eq(opts.fpsCap, 60, "nor does it move the cap")
measure(60.3)
T.eq(RefreshRate.hz(), 60, "a measured 60.3 quantizes to the panel's 60")
measure(59.7)
T.eq(RefreshRate.hz(), 60, "and so does a measured 59.7")
measure(89.8)
T.eq(RefreshRate.hz(), 90, "while a measured 89.8 is a 90Hz panel")
T.eq(RefreshRate.period(), 1 / 90, "with the 90Hz period, not the measurement's")
RefreshRate.reset()
FrameCap.migrated = false
FrameCap.apply(before)
T.finish("frame cap display")