mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
243 lines
9.9 KiB
Lua
243 lines
9.9 KiB
Lua
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.harness")
|
|
local check, eq = T.check, T.eq
|
|
|
|
love = love or require("tests.love_stub")
|
|
|
|
package.loaded["src.core.Logger"] = {
|
|
info = function() end, warn = function() end, error = function() end,
|
|
}
|
|
|
|
love.filesystem.write = function() return true end
|
|
love.graphics.getRendererInfo = function() return "OpenGL", "4.1 stub", "stub", "stub" end
|
|
love.graphics.getSupported = function() return { glsl3 = true } end
|
|
love.graphics.flushBatch = function() end
|
|
love.graphics.validateShader = function() return true end
|
|
love.timer = love.timer or {}
|
|
love.timer.getDelta = love.timer.getDelta or function() return 1 / 60 end
|
|
|
|
local sends = {}
|
|
love.graphics.newShader = function(frag, vert)
|
|
local sh = { frag = frag, vert = vert }
|
|
function sh.send(self, name, value) sends[#sends + 1] = { shader = self, name = name, value = value } end
|
|
return sh
|
|
end
|
|
|
|
local quads, draws, scissors = {}, {}, {}
|
|
love.graphics.newQuad = function(x, y, w, h, sw, sh)
|
|
local q = { x = x, y = y, w = w, h = h, sw = sw, sh = sh }
|
|
quads[#quads + 1] = q
|
|
return q
|
|
end
|
|
love.graphics.draw = function(img, a, b, c, d, e, f)
|
|
if type(a) == "table" then
|
|
draws[#draws + 1] = { img = img, quad = a, x = b, y = c, sx = e, sy = f }
|
|
else
|
|
draws[#draws + 1] = { img = img, x = a, y = b, sx = d, sy = e }
|
|
end
|
|
end
|
|
love.graphics.setScissor = function(x, y, w, h)
|
|
scissors[#scissors + 1] = x and { x, y, w, h } or false
|
|
end
|
|
|
|
local ShaderFX = require("src.render.ShaderFX")
|
|
|
|
local function entry(name)
|
|
return { name = name .. ".slangp", fullPath = "tests/data/shaderfx/gl/" .. name .. ".slangp", converted = true }
|
|
end
|
|
|
|
local function windowCanvas(w, h)
|
|
return setmetatable({ w = w, h = h }, { __index = {
|
|
getWidth = function(self) return self.w end,
|
|
getHeight = function(self) return self.h end,
|
|
getPixelWidth = function(self) return self.w end,
|
|
getPixelHeight = function(self) return self.h end,
|
|
} })
|
|
end
|
|
|
|
local ok = ShaderFX.activate("main", entry("lcd3x"))
|
|
eq(ok, true, "lcd3x activates under the stub")
|
|
|
|
local realRunChain = ShaderFX.runChain
|
|
local captured
|
|
ShaderFX.runChain = function(state, img, luts, viewport, original, layer)
|
|
local out = realRunChain(state, img, luts, viewport, original, layer)
|
|
captured = { viewport = viewport, original = original, out = out, img = img }
|
|
return out
|
|
end
|
|
|
|
local function render(W, H, s, originX, originY, dpi, opts)
|
|
quads, draws, scissors, sends, captured = {}, {}, {}, {}, nil
|
|
dpi = dpi or 1
|
|
opts = opts or {}
|
|
opts.originX, opts.originY = originX, originY
|
|
local canvas = windowCanvas(W, H)
|
|
ShaderFX.render(canvas, { x = 0, y = 0, w = W, h = H, scale = s },
|
|
{ w = W / s, h = H / s }, dpi, dpi, opts)
|
|
return canvas
|
|
end
|
|
|
|
local function findDraw(img)
|
|
for _, d in ipairs(draws) do
|
|
if d.img == img then return d end
|
|
end
|
|
end
|
|
|
|
local function near(a, b) return math.abs(a - b) < 1e-9 end
|
|
|
|
local cases = {
|
|
{ label = "FIT", W = 1024, H = 768, s = 5, ox = 112, oy = 24 },
|
|
{ label = "OUT1", W = 1024, H = 768, s = 4, ox = 2, oy = 1 },
|
|
{ label = "OUT2", W = 1024, H = 768, s = 3, ox = 0, oy = 0 },
|
|
{ label = "OUT2 camera phase", W = 1024, H = 768, s = 3, ox = -7, oy = 5 },
|
|
{ label = "FIT 1280x720", W = 1280, H = 720, s = 5, ox = 240, oy = 0 },
|
|
}
|
|
|
|
for _, c in ipairs(cases) do
|
|
local canvas = render(c.W, c.H, c.s, c.ox, c.oy)
|
|
check(captured ~= nil, c.label .. ": the chain ran")
|
|
if captured then
|
|
local vp, orig = captured.viewport, captured.original
|
|
eq(vp.w, orig.w * c.s, c.label .. ": viewport width is exactly scale x source width")
|
|
eq(vp.h, orig.h * c.s, c.label .. ": viewport height is exactly scale x source height")
|
|
|
|
local q = quads[1]
|
|
check(q ~= nil, c.label .. ": the crop uses a quad")
|
|
local x0, y0 = math.floor(q.x), math.floor(q.y)
|
|
eq((c.ox - x0) % c.s, 0, c.label .. ": crop starts on a GB pixel column edge")
|
|
eq((c.oy - y0) % c.s, 0, c.label .. ": crop starts on a GB pixel row edge")
|
|
check(x0 <= 0 and x0 > -c.s and y0 <= 0 and y0 > -c.s,
|
|
c.label .. ": crop starts within one GB pixel above/left of the window")
|
|
eq(q.w, orig.w * c.s, c.label .. ": crop quad spans whole GB pixel columns")
|
|
eq(q.h, orig.h * c.s, c.label .. ": crop quad spans whole GB pixel rows")
|
|
check(x0 + q.w >= c.W and y0 + q.h >= c.H, c.label .. ": crop covers the whole window")
|
|
check(q.x - x0 > 0 and q.x - x0 < 0.5 and q.y - y0 > 0 and q.y - y0 < 0.5,
|
|
c.label .. ": crop samples inside a physical pixel, never on its edge")
|
|
|
|
local cropDraw = findDraw(canvas)
|
|
check(cropDraw and near(cropDraw.sx, 1 / c.s), c.label .. ": crop draws at exactly 1/scale")
|
|
|
|
local passDraw = findDraw(captured.img)
|
|
check(passDraw and near(passDraw.sx, c.s) and near(passDraw.sy, c.s),
|
|
c.label .. ": the viewport pass stretches the source by the integer scale")
|
|
check(passDraw and passDraw.x >= 1 / 16 and passDraw.x < 0.5 and passDraw.y >= 1 / 16 and passDraw.y < 0.5,
|
|
c.label .. ": the integer viewport pass bias is at least a rasterizer subpixel and under half a pixel")
|
|
if passDraw then
|
|
for j = 0, c.s * 4 do
|
|
eq(math.floor((j + 0.5 - passDraw.y) / c.s), math.floor((j + 0.5) / c.s),
|
|
c.label .. ": the bias never changes which texel output row " .. j .. " samples")
|
|
end
|
|
end
|
|
|
|
local final = findDraw(captured.out)
|
|
check(final ~= nil, c.label .. ": chain output reaches the screen")
|
|
if final then
|
|
eq(final.x, x0, c.label .. ": chain output lands where the crop was taken (x)")
|
|
eq(final.y, y0, c.label .. ": chain output lands where the crop was taken (y)")
|
|
local cw, ch = captured.out:getWidth(), captured.out:getHeight()
|
|
check(near(final.sx * cw, orig.w * c.s) and near(final.sy * ch, orig.h * c.s),
|
|
c.label .. ": chain output is exactly scale physical px per GB pixel")
|
|
end
|
|
local sc = scissors[#scissors - 1]
|
|
check(type(sc) == "table" and sc[1] == 0 and sc[2] == 0 and sc[3] == c.W and sc[4] == c.H,
|
|
c.label .. ": the overhanging output is clipped to the window")
|
|
end
|
|
end
|
|
|
|
do
|
|
local canvas = render(1024, 768, 3, 1, 2, 1, { layer = "ui", mask = true })
|
|
local maskRect
|
|
for _, s in ipairs(sends) do
|
|
if s.name == "maskRect" then maskRect = s.value end
|
|
end
|
|
check(maskRect ~= nil, "masked UI layer sends the mask rect")
|
|
if maskRect and captured then
|
|
local x0, y0 = -2, -1
|
|
check(near(maskRect[1], x0 / 1024) and near(maskRect[2], y0 / 768),
|
|
"mask rect starts at the crop origin")
|
|
check(near(maskRect[3], captured.original.w * 3 / 1024) and near(maskRect[4], captured.original.h * 3 / 768),
|
|
"mask rect spans the drawn chain output")
|
|
end
|
|
check(canvas ~= nil, "masked render ran")
|
|
end
|
|
|
|
do
|
|
render(2048, 1536, 6, 224, 48, 2)
|
|
local q = quads[1]
|
|
local final = captured and findDraw(captured.out)
|
|
check(q and final, "HiDPI render ran")
|
|
if q and final then
|
|
local x0 = math.floor(q.x)
|
|
eq((224 - x0) % 6, 0, "HiDPI: crop aligned to the physical UI origin")
|
|
eq(final.x, x0 / 2, "HiDPI: chain output placed in logical units")
|
|
eq(captured.viewport.w, captured.original.w * 6, "HiDPI: viewport is exactly scale x source")
|
|
end
|
|
end
|
|
|
|
do
|
|
render(1024, 768, 4.8, 12, 0)
|
|
local q = quads[1]
|
|
check(q and q.x == 0 and q.y == 0 and q.w == 1024 and q.h == 768,
|
|
"fractional scale keeps the whole-window crop")
|
|
eq(captured.viewport.w, 1024, "fractional scale keeps the window viewport width")
|
|
eq(captured.viewport.h, 768, "fractional scale keeps the window viewport height")
|
|
eq(captured.original.w, math.floor(1024 / 4.8 + 0.5), "fractional scale keeps the rounded source width")
|
|
local passDraw = findDraw(captured.img)
|
|
check(passDraw and passDraw.x == 0 and passDraw.y == 0,
|
|
"a non-integer viewport stretch draws unbiased")
|
|
local final = findDraw(captured.out)
|
|
check(final and final.x == 0 and final.y == 0
|
|
and near(final.sx * captured.out:getWidth(), 1024),
|
|
"fractional scale draws the chain output over the window as before")
|
|
for _, sc in ipairs(scissors) do
|
|
check(sc == false, "fractional scale adds no scissor")
|
|
end
|
|
end
|
|
|
|
ShaderFX.runChain = realRunChain
|
|
ShaderFX.deactivate("main")
|
|
|
|
do
|
|
draws = {}
|
|
local canvas = windowCanvas(1024, 768)
|
|
ShaderFX.render(canvas, { x = 0, y = 0, w = 1024, h = 768, scale = 3 },
|
|
{ w = 1024 / 3, h = 256 }, 1, 1, { originX = 1, originY = 1 })
|
|
eq(#draws, 1, "shader off: one draw")
|
|
check(draws[1] and draws[1].img == canvas and draws[1].x == 0 and draws[1].y == 0 and draws[1].sx == nil,
|
|
"shader off: the canvas is drawn untouched at the origin")
|
|
end
|
|
|
|
do
|
|
local Game2 = require("src.core.Game2")
|
|
local Chrome = require("src.ui.gen2.Chrome")
|
|
local function game(zoom, fit, cam)
|
|
return setmetatable({
|
|
world = {
|
|
map = {}, camera = cam or { x = 9, y = 27 },
|
|
zoomScale = function() return zoom end,
|
|
fitScale = function() return fit end,
|
|
},
|
|
}, { __index = Game2 })
|
|
end
|
|
eq(Chrome.fitScale(1024, 768), 5, "1024x768 fits at 5")
|
|
local ox, oy = Chrome.fitOrigin(1024, 768, 5)
|
|
eq(ox, 112, "1024x768 fit origin x")
|
|
eq(oy, 24, "1024x768 fit origin y")
|
|
local wx, wy = game(5, 5):fxWorldOrigin(1024, 768, 5)
|
|
eq(wx, -45, "world grid origin follows the camera x")
|
|
eq(wy, -135, "world grid origin follows the camera y")
|
|
eq(game(5, 5):fxSplitsUi(1024, 768), true,
|
|
"Gen 2 at FIT splits the UI layer when the menu grid phase differs from the world camera grid")
|
|
eq(game(5, 5, { x = 0, y = 0 }):fxSplitsUi(1024, 768), true,
|
|
"1024x768 never aligns at FIT: the fit origin is off the camera grid by 2,4 for every integer camera")
|
|
eq(game(5, 5, { x = 4, y = 11 }):fxSplitsUi(1280, 720), false,
|
|
"1280x720 (origin 240,0) shares phase with the camera grid, no split at FIT")
|
|
eq(game(3, 5):fxSplitsUi(1280, 720), true, "a zoomed world always splits the UI layer")
|
|
eq(setmetatable({ world = { map = nil } }, { __index = Game2 }):fxSplitsUi(1024, 768), false,
|
|
"no map, no split")
|
|
eq(setmetatable({}, { __index = Game2 }):fxSplitsUi(1024, 768), false, "no world, no split")
|
|
end
|
|
|
|
T.finish("shaderfx_grid_alignment_2235")
|