Files
gen1recomp/tests/drivers/title_seam_bug1453_test.lua
T

177 lines
7.1 KiB
Lua

-- Manual check for #1453: the SGB zone scissors must stay contiguous in
-- framebuffer pixels on a fractional-DPI surface (Android), where LOVE 11
-- truncates the scissor to whole units before scaling it. Zones are pokered
-- data/sgb/sgb_packets.asm BlkPacket_Titlescreen (rows 0-7 / 8-9 / 10-17).
-- POKEPORT_DRIVER=tests/drivers/title_seam_bug1453_test.lua POKEPORT_TOUCH=0 SHOT_DIR=/tmp/shots love .
-- PROBE_DPI picks the density to emulate (default 2.625, the reporter's).
-- Do not set POKEPORT_SPEED: fast-forward desynchronizes the title music.
return function(game)
local U = dofile("tests/drivers/util.lua")
local PaletteFX = require("src.render.PaletteFX")
local Renderer = require("src.render.Renderer")
local SHOT_DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
local DPI = tonumber(os.getenv("PROBE_DPI") or "") or 2.625
local function check(label, ok)
U.log(ok and "PASS" or "FAIL", label)
return ok
end
-- past the copyright splash / attract movie (engine/movie/splash.asm)
local title
for _ = 1, 120 do
local top = game.stack:top()
if top and top.screenId == "TitleState" and top.sgbPalettes then
title = top
break
end
U.tap(game, "start")
U.wait(9)
end
check("the title screen is on top", title ~= nil)
if not (title and title.sgbPalettes) then
U.log("No title state to look at; nothing below can run.")
while true do coroutine.yield() end
end
local opts = game.save.options
local mode = opts and opts.colors or PaletteFX.mode
if PaletteFX.mode ~= "gbc" then
U.log("COLORS is", tostring(mode) .. "; switching the view to SGB for the check")
PaletteFX.setMode("gbc")
U.wait(20)
end
local zones = PaletteFX.ensureZones(title:sgbPalettes(game))
check("the title builds three SGB zones", zones ~= nil and #zones == 3)
-- A canvas carries its own dpiscale, so the emulated Android surface goes
-- through the same LOVE scissor path the phone does, on this desktop.
local probe = love.graphics.newCanvas(math.floor(1920 / DPI),
math.floor(1080 / DPI),
{ dpiscale = DPI })
local PW, PH = probe:getPixelWidth(), probe:getPixelHeight()
local Sp = math.max(1, math.floor(math.min(PW / 160, PH / 144)))
local Ux, Uy = Sp / DPI, Sp / DPI
local uox = math.floor((PW - 160 * Sp) / 2) / DPI
local uoy = math.floor((PH - 144 * Sp) / 2) / DPI
local uvpw, uvph = 160 * Ux, 144 * Uy
U.log(("emulating %dx%d px at dpi %.4f, fit scale %d"):format(PW, PH, DPI, Sp))
-- what the pre-fix Renderer handed LOVE: fractional units with a half
-- framebuffer pixel of bias, which LOVE 11 truncates away
local function scissorOld(x, y, w, h)
local x2, y2 = math.min(x + w, uox + uvpw), math.min(y + h, uoy + uvph)
x, y = math.max(x, uox), math.max(y, uoy)
if x2 <= x or y2 <= y then return false end
local px1, py1 = math.floor(x * DPI), math.floor(y * DPI)
local px2, py2 = math.ceil(x2 * DPI), math.ceil(y2 * DPI)
love.graphics.setScissor((px1 + 0.5) / DPI, (py1 + 0.5) / DPI,
(px2 - px1 + 0.5) / DPI, (py2 - py1 + 0.5) / DPI)
return true
end
local function renderProbe(old)
love.graphics.setCanvas(probe)
love.graphics.clear(0, 0, 0, 1)
love.graphics.setColor(1, 1, 1, 1)
if old then
local shader = PaletteFX.shader()
love.graphics.setShader(shader)
for _, z in ipairs(zones) do
PaletteFX.sendColors(shader, z.colors)
if scissorOld(uox + z.x * Ux, uoy + z.y * Uy, z.w * Ux, z.h * Uy) then
love.graphics.draw(Renderer.canvas, uox, uoy, 0, Ux, Uy)
end
end
love.graphics.setScissor()
love.graphics.setShader()
else
Renderer:blitCanvas(Renderer.canvas, Ux, Uy, zones, Ux, Uy,
uox, uoy, uox, uoy, uvpw, uvph, DPI, DPI)
end
love.graphics.setCanvas()
return probe:newImageData()
end
local top = math.floor(uoy * DPI)
local bottom = math.floor((uoy + uvph) * DPI)
local col = math.floor(uox * DPI) + 2
local function blackRows(data)
local rows = {}
for py = top, math.min(bottom - 1, data:getHeight() - 1) do
local r, g, b = data:getPixel(col, py)
if r < 0.02 and g < 0.02 and b < 0.02 then rows[#rows + 1] = py end
end
return rows
end
local dataNew = renderProbe(false)
local dataOld = renderProbe(true)
local rowsNew, rowsOld = blackRows(dataNew), blackRows(dataOld)
U.log(("picture rows %d-%d, sampling the background column at x=%d")
:format(top, bottom - 1, col))
U.log("pre-fix math left", #rowsOld, "black rows:",
#rowsOld > 0 and table.concat(rowsOld, ",") or "none")
check("no letterbox row shows through the zone boundaries", #rowsNew == 0)
check("the emulated surface reproduces the seam without the fix", #rowsOld > 0)
local ZOOM, CW, CH = 4, 220, 28
local cy = math.max(top, math.floor((uoy + 64 * Uy) * DPI) - math.floor(CH / 2))
local cx = math.floor(uox * DPI)
local function zoomOf(data)
local out = love.image.newImageData(CW * ZOOM, CH * ZOOM)
out:mapPixel(function(x, y)
return data:getPixel(cx + math.floor(x / ZOOM), cy + math.floor(y / ZOOM))
end)
return out
end
local function writePng(imageData, path)
local dir = path:match("^(.*)[/\\][^/\\]+$")
if dir and dir ~= "" then os.execute('mkdir -p "' .. dir .. '" 2>/dev/null') end
local f = io.open(path, "wb")
if not f then return false end
f:write(imageData:encode("png"):getString())
f:close()
return true
end
local zoomOld, zoomNew = zoomOf(dataOld), zoomOf(dataNew)
check("wrote " .. SHOT_DIR .. "/bug1453_before.png",
writePng(zoomOld, SHOT_DIR .. "/bug1453_before.png"))
check("wrote " .. SHOT_DIR .. "/bug1453_after.png",
writePng(zoomNew, SHOT_DIR .. "/bug1453_after.png"))
local imgOld = love.graphics.newImage(zoomOld)
local imgNew = love.graphics.newImage(zoomNew)
local baseDraw = love.draw
love.draw = function()
baseDraw()
local w, h = love.graphics.getDimensions()
local s = math.min((w - 24) / imgOld:getWidth(),
(h * 0.5 - 48) / (imgOld:getHeight() * 2))
local bw = imgOld:getWidth() * s + 16
local bh = imgOld:getHeight() * 2 * s + 56
local bx, by = (w - bw) / 2, h - bh - 8
love.graphics.setColor(0, 0, 0, 0.75)
love.graphics.rectangle("fill", bx, by, bw, bh)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.print("BEFORE (dpi " .. DPI .. ")", bx + 8, by + 4)
love.graphics.draw(imgOld, bx + 8, by + 20, 0, s, s)
love.graphics.print("AFTER", bx + 8, by + 24 + imgOld:getHeight() * s)
love.graphics.draw(imgNew, bx + 8, by + 40 + imgOld:getHeight() * s, 0, s, s)
end
PaletteFX.setMode(mode)
U.wait(10)
U.shot(game, SHOT_DIR .. "/bug1453_title.png")
U.log("captured", SHOT_DIR .. "/bug1453_title.png")
U.log("The title is live and the pad is yours; the strip along the bottom is")
U.log("the row-64 zone boundary of an emulated " .. DPI .. "x Android surface,")
U.log("magnified " .. ZOOM .. "x: BEFORE carries the black hairline across the")
U.log("picture, AFTER is unbroken off-white. PROBE_DPI=2.75 tries another one.")
while true do
coroutine.yield()
end
end