mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-18 11:44:42 +02:00
CLOSES #1396, CLOSES #1398, CLOSES #1400, CLOSES #1401, CLOSES #1406, CLOSES #1407, CLOSES #1411, CLOSES #1413, CLOSES #1415, CLOSES #1416, CLOSES #1417, CLOSES #1419, CLOSES #1421, CLOSES #1422, CLOSES #1423, CLOSES #1424, CLOSES #1425, CLOSES #1427, CLOSES #1428, CLOSES #1429, CLOSES #1431, CLOSES #1432, CLOSES #1433, CLOSES #1435, CLOSES #1437, CLOSES #1440, CLOSES #1441, CLOSES #1442, CLOSES #1443, CLOSES #1447, CLOSES #1449, CLOSES #1456, CLOSES #1464, CLOSES #1465, CLOSES #1468, CLOSES #1469, CLOSES #1470
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
-- 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
|
||||
Reference in New Issue
Block a user