mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 00:10:56 +02:00
Android SCALING fixes (#298)
* android fixes for mods and saves * perhaps this is the true scaling android issue fix
This commit is contained in:
@@ -3876,8 +3876,11 @@ function BattleState:colorMode()
|
||||
if g and g.newCanvas and g.setScissor and g.setShader and g.getCanvas
|
||||
and love.image and PaletteFX.pack(self.data)
|
||||
and PaletteFX.shader() then
|
||||
local ok1, bg = pcall(g.newCanvas, 160, 144)
|
||||
local ok2, wv = pcall(g.newCanvas, 160, 144)
|
||||
-- 160x144 real pixels, not DPI units, or the colored battle background
|
||||
-- resamples against the UI canvas on mobile (#208; PixelCanvas.lua)
|
||||
local PixelCanvas = require("src.render.PixelCanvas")
|
||||
local ok1, bg = pcall(PixelCanvas.new, 160, 144)
|
||||
local ok2, wv = pcall(PixelCanvas.new, 160, 144)
|
||||
if ok1 and ok2 and bg and wv then
|
||||
self.bgCanvas, self.waveCanvas = bg, wv
|
||||
ready = true
|
||||
|
||||
@@ -96,6 +96,20 @@ function Game:load()
|
||||
end
|
||||
|
||||
Logger.info("game loaded")
|
||||
-- Scaling bug reports (#87, #208) are unanswerable without these three
|
||||
-- numbers: LOVE units, drawable pixels, and the integer physical pixels
|
||||
-- per GB pixel the renderer settled on. Cheap, once, and it turns "it
|
||||
-- looks stretched" into something reproducible.
|
||||
if love.graphics and love.graphics.getDimensions then
|
||||
local ww, wh = love.graphics.getDimensions()
|
||||
local pw, ph = ww, wh
|
||||
if love.graphics.getPixelDimensions then
|
||||
pw, ph = love.graphics.getPixelDimensions()
|
||||
end
|
||||
Logger.info(string.format(
|
||||
"display: %dx%d units, %dx%d px, fit scale %d px/GB px",
|
||||
ww, wh, pw, ph, Renderer:fitScale()))
|
||||
end
|
||||
end
|
||||
|
||||
-- the merged field.boot: spawn, names, money and the naming presets a
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
-- Render targets measured in real framebuffer pixels.
|
||||
--
|
||||
-- love.graphics.newCanvas defaults its `dpiscale` to
|
||||
-- love.graphics.getDPIScale(), so on a highdpi surface the canvas texture is
|
||||
-- NOT the size it was asked for: newCanvas(160, 144) on a device reporting a
|
||||
-- DPI scale of 2.755 allocates a 441x397 texture, and the 160x144 GB scene
|
||||
-- then renders into it at 2.755 texels per GB pixel. conf.lua sets
|
||||
-- t.window.highdpi on Android/iOS (required for Retina), and Android's
|
||||
-- DisplayMetrics.density is routinely non-integer (1.5, 2.75, ...), so this
|
||||
-- is the normal mobile case, not an edge case.
|
||||
--
|
||||
-- That fractional render breaks the whole premise of Renderer's integer
|
||||
-- scale pipeline. Renderer:fitScale() picks a whole number of physical
|
||||
-- pixels per GB pixel (7 on 1080p) and the composite blit lands on it
|
||||
-- exactly -- but if the *source* already holds 2.755 texels per GB pixel,
|
||||
-- some GB pixels are 2 texels wide and some are 3, and the nearest-neighbour
|
||||
-- upscale turns them into 5 / 7 / 8 physical pixels instead of a uniform 7.
|
||||
-- Measured on a 1080p density-2.755 fixture, only 43 of 160 columns came out
|
||||
-- the right width. Fonts show it worst: their strokes are single pixels.
|
||||
-- That is issue #208 ("some are noticeably stretched, especially fonts").
|
||||
--
|
||||
-- Forcing dpiscale = 1 makes canvas texels the pixels the renderer already
|
||||
-- believes it is drawing. getWidth()/getHeight() are unaffected (they always
|
||||
-- reported the requested size), so no geometry anywhere changes -- only the
|
||||
-- resolution of the target. Desktop is unchanged; dpiscale is already 1
|
||||
-- there because conf.lua sets highdpi on mobile only.
|
||||
--
|
||||
-- Exception: a canvas that is deliberately sized in LOVE *units* and blitted
|
||||
-- back at unit scale 1 (Renderer's presentCanvas) must keep the screen's DPI
|
||||
-- scale so its texture still covers the framebuffer; it is not built with
|
||||
-- this helper.
|
||||
|
||||
local PixelCanvas = {}
|
||||
|
||||
-- One framebuffer pixel per w/h unit, always. `filter` is applied only when
|
||||
-- given, so callers that relied on LOVE's default ("linear") keep it.
|
||||
function PixelCanvas.new(w, h, filter)
|
||||
local canvas = love.graphics.newCanvas(w, h, { dpiscale = 1 })
|
||||
if filter and canvas and canvas.setFilter then
|
||||
canvas:setFilter(filter, filter)
|
||||
end
|
||||
return canvas
|
||||
end
|
||||
|
||||
return PixelCanvas
|
||||
+16
-8
@@ -10,6 +10,7 @@ local Zoom = require("src.render.Zoom")
|
||||
local Tilt = require("src.render.Tilt")
|
||||
local PaletteFX = require("src.render.PaletteFX")
|
||||
local Pipelines = require("src.render.Pipelines")
|
||||
local PixelCanvas = require("src.render.PixelCanvas")
|
||||
local Runtime = require("src.mods.Runtime")
|
||||
|
||||
local Renderer = {}
|
||||
@@ -79,8 +80,10 @@ local function displayMetrics()
|
||||
end
|
||||
|
||||
function Renderer:init()
|
||||
self.canvas = love.graphics.newCanvas(self.WIDTH, self.HEIGHT)
|
||||
self.canvas:setFilter("nearest", "nearest")
|
||||
-- 160x144 real pixels, never DPI-scaled: see src/render/PixelCanvas.lua
|
||||
-- (#208). Every canvas below is sized in framebuffer pixels for the same
|
||||
-- reason -- worldViewSize() already works in drawable pixels.
|
||||
self.canvas = PixelCanvas.new(self.WIDTH, self.HEIGHT, "nearest")
|
||||
self.worldCanvas = nil
|
||||
self.worldActive = false
|
||||
-- tilt mode only: a transparent overlay canvas the size of the world
|
||||
@@ -231,8 +234,7 @@ function Renderer:beginWorldPass()
|
||||
-- the view size every frame, so without this the superseded canvases
|
||||
-- pile up in VRAM until a GC finalizer happens to run
|
||||
if self.worldCanvas and self.worldCanvas.release then self.worldCanvas:release() end
|
||||
self.worldCanvas = love.graphics.newCanvas(vw, vh)
|
||||
self.worldCanvas:setFilter("nearest", "nearest")
|
||||
self.worldCanvas = PixelCanvas.new(vw, vh, "nearest")
|
||||
end
|
||||
self.worldActive = true
|
||||
PaletteFX.setPass("world")
|
||||
@@ -260,8 +262,7 @@ function Renderer:beginUprightPass()
|
||||
if not self.uprightCanvas or self.uprightCanvas:getWidth() ~= cw
|
||||
or self.uprightCanvas:getHeight() ~= ch then
|
||||
if self.uprightCanvas and self.uprightCanvas.release then self.uprightCanvas:release() end
|
||||
self.uprightCanvas = love.graphics.newCanvas(cw, ch)
|
||||
self.uprightCanvas:setFilter("nearest", "nearest")
|
||||
self.uprightCanvas = PixelCanvas.new(cw, ch, "nearest")
|
||||
end
|
||||
self.uprightActive = true
|
||||
PaletteFX.setPass(nil)
|
||||
@@ -355,8 +356,7 @@ function Renderer:drawTiltedWorld(zoneList, sx, sy, wox, woy, target)
|
||||
if not self.tiltCanvas or self.tiltCanvas:getWidth() ~= wvw
|
||||
or self.tiltCanvas:getHeight() ~= wvh then
|
||||
if self.tiltCanvas and self.tiltCanvas.release then self.tiltCanvas:release() end
|
||||
self.tiltCanvas = love.graphics.newCanvas(wvw, wvh)
|
||||
self.tiltCanvas:setFilter("linear", "linear")
|
||||
self.tiltCanvas = PixelCanvas.new(wvw, wvh, "linear")
|
||||
end
|
||||
|
||||
love.graphics.setCanvas(self.tiltCanvas)
|
||||
@@ -465,6 +465,14 @@ function Renderer:endFrame(zones, worldZones)
|
||||
if needPresent then
|
||||
if not self.presentCanvas or self.presentCanvas:getWidth() ~= ww
|
||||
or self.presentCanvas:getHeight() ~= wh then
|
||||
-- The one canvas NOT built through PixelCanvas: it is sized in LOVE
|
||||
-- units and blitted back at unit scale 1 (and handed to mod present
|
||||
-- passes as ww x wh), so it has to keep the screen's DPI scale for its
|
||||
-- texture to cover the framebuffer. Everything composited into it is
|
||||
-- already native-resolution now, so #208's fractional source is gone;
|
||||
-- what remains here is the dpiX vs dpiY truncation gap (well under 1%,
|
||||
-- one seam across the window) that a single scalar dpiscale cannot
|
||||
-- express.
|
||||
self.presentCanvas = love.graphics.newCanvas(ww, wh)
|
||||
self.presentCanvas:setFilter("linear", "linear")
|
||||
end
|
||||
|
||||
@@ -542,7 +542,10 @@ end
|
||||
local function bakeBorderFill(self, block)
|
||||
local border = self.map.tileset.blocks[block + 1]
|
||||
if not border then return end
|
||||
local canvas = love.graphics.newCanvas(32, 32)
|
||||
-- 32x32 real pixels: a DPI-scaled canvas would bake the border block at a
|
||||
-- fractional texel size and the repeat-wrapped image would then tile at
|
||||
-- non-square pixels (#208, see src/render/PixelCanvas.lua)
|
||||
local canvas = require("src.render.PixelCanvas").new(32, 32)
|
||||
love.graphics.push("all")
|
||||
love.graphics.setCanvas(canvas)
|
||||
love.graphics.clear(1, 1, 1, 1)
|
||||
|
||||
@@ -1917,6 +1917,40 @@ do
|
||||
check(math.abs(physX - 7) < 1e-9 and math.abs(physY - 7) < 1e-9,
|
||||
"#208 swapped-aspect still yields square 7x7 physical GB pixels")
|
||||
|
||||
-- #208 part two: getting the draw scale right is useless if the SOURCE is
|
||||
-- fractional. love.graphics.newCanvas defaults dpiscale to
|
||||
-- love.graphics.getDPIScale(), so on mobile (conf.lua sets highdpi) a
|
||||
-- newCanvas(160, 144) is really a 441x397 texture holding 2.755 texels per
|
||||
-- GB pixel; the integer blit then lands those on 5 / 7 / 8 physical pixels
|
||||
-- instead of a uniform 7. Every render target must be pixel-exact.
|
||||
Zoom.reset()
|
||||
g.getDimensions = function() return 698, 392 end
|
||||
g.getPixelDimensions = function() return 1920, 1080 end
|
||||
g.getDPIScale = function() return 1080 / 392 end
|
||||
-- one table, not a fistful of locals: this chunk is close to Lua's
|
||||
-- 200-local ceiling and a few more here overflow it
|
||||
local probe = { made = {}, newCanvas = g.newCanvas,
|
||||
ui = Renderer.canvas, world = Renderer.worldCanvas }
|
||||
g.newCanvas = function(w, h, settings)
|
||||
probe.made[#probe.made + 1] =
|
||||
{ w = w, h = h, dpiscale = settings and settings.dpiscale }
|
||||
return probe.newCanvas(w, h)
|
||||
end
|
||||
Renderer:init()
|
||||
Renderer:beginWorldPass()
|
||||
love.graphics.setCanvas()
|
||||
g.newCanvas = probe.newCanvas
|
||||
Renderer.canvas, Renderer.worldCanvas = probe.ui, probe.world
|
||||
Renderer.worldActive = false
|
||||
check(#probe.made >= 2, "#208 init + world pass allocated their canvases")
|
||||
for _, c in ipairs(probe.made) do
|
||||
eq(c.dpiscale, 1,
|
||||
("#208 canvas %dx%d is pixel-exact (dpiscale 1, not the screen's)")
|
||||
:format(c.w, c.h))
|
||||
if c.w == 160 and c.h == 144 then probe.sawUi = true end
|
||||
end
|
||||
check(probe.sawUi, "#208 the UI canvas is requested at exactly 160x144")
|
||||
|
||||
-- missing pixel API falls back to getDimensions (headless / old stub)
|
||||
g.getPixelDimensions = nil
|
||||
g.getDPIScale = nil
|
||||
|
||||
Reference in New Issue
Block a user