diff --git a/lib/PixelCanvas.lua b/lib/PixelCanvas.lua new file mode 100644 index 0000000..d3f960d --- /dev/null +++ b/lib/PixelCanvas.lua @@ -0,0 +1,40 @@ +-- Render targets measured in real framebuffer PIXELS. +-- +-- love.graphics.newCanvas defaults its `dpiscale` to +-- love.graphics.getDPIScale(), so on a highdpi surface the texture it +-- allocates is NOT the size it was asked for -- newCanvas(2752, 2064) on a +-- panel reporting a DPI scale of 2 asks the driver for 5504x4128. +-- +-- Every target in this mod is already counted in framebuffer pixels: the +-- free-roam scene canvas is love.graphics.getPixelDimensions() (see +-- sceneSize in main.lua, and the 1/dpi blit the engine composites it with), +-- the shadow map is a resolution rung, the blur pairs are the size of the +-- image they were handed. Letting LOVE scale them a second time is the DPI +-- factor applied twice. +-- +-- On an iPad at DPI scale 2 that took the scene canvas past the driver's +-- maximum texture size. newCanvas threw, beginScene returned false, +-- drawWorld returned nil -- and returning nil IS the 2D fallback, so the +-- engine quietly kept drawing the flat world. Every switch said the mode was +-- on (the OPTIONS row, the mod manager, the hotkey) and nothing ever +-- appeared. Desktop never saw it: dpiscale is 1 there. +-- +-- getWidth()/getHeight() always report the REQUESTED size, so pinning the +-- scale changes only the resolution of the target -- no geometry, no UV, no +-- compositing scale anywhere moves, and the FX overlay's projected pixel +-- coordinates finally land on the pixels they name. The engine's own render +-- targets do this for the same reason (src/render/PixelCanvas.lua). + +-- the mod namespace (see main.lua) +local V = ... + +local PixelCanvas = {} + +-- One framebuffer pixel per w/h, always. Returns ok, canvas -- the shape the +-- pcall-guarded call sites already branch on, so a driver that refuses the +-- allocation still degrades instead of erroring. +function PixelCanvas.new(w, h) + return pcall(love.graphics.newCanvas, w, h, { dpiscale = 1 }) +end + +return PixelCanvas diff --git a/lib/ShadowMap.lua b/lib/ShadowMap.lua index 9798462..e3dd332 100644 --- a/lib/ShadowMap.lua +++ b/lib/ShadowMap.lua @@ -180,7 +180,9 @@ end local function getCanvas(res) if canvas == false then return nil end if canvas and canvasRes == res then return canvas end - local ok, c = pcall(love.graphics.newCanvas, res, res) + -- pixel-exact: `res` IS the texel count the filter's sunTexel is derived + -- from, so the texture must be exactly that big (see lib/PixelCanvas.lua) + local ok, c = V.require("PixelCanvas").new(res, res) if not (ok and c) then canvas = false return nil diff --git a/lib/TiltShift.lua b/lib/TiltShift.lua index d95c10e..a9cc66b 100644 --- a/lib/TiltShift.lua +++ b/lib/TiltShift.lua @@ -25,6 +25,9 @@ -- failure -- headless, no shader support) apply() hands the canvas back -- untouched, so every other path is byte-for-byte what it always was. +-- the mod namespace (see main.lua): V.require loads a sibling module +local V = ... + local TiltShift = {} TiltShift.level = 0 @@ -85,9 +88,12 @@ end local function getCanvases(w, h) if not ping or cw ~= w or ch ~= h then - local ok, a = pcall(love.graphics.newCanvas, w, h) + -- pixel-exact: w and h come from the scene canvas this pass was handed, + -- which is already counted in framebuffer pixels (see lib/PixelCanvas.lua) + local PixelCanvas = V.require("PixelCanvas") + local ok, a = PixelCanvas.new(w, h) if not ok then return nil end - local okB, b = pcall(love.graphics.newCanvas, w, h) + local okB, b = PixelCanvas.new(w, h) if not okB then return nil end -- the gaussian's fractional tap offsets need linear filtering a:setFilter("linear", "linear") diff --git a/lib/Voxel3D.lua b/lib/Voxel3D.lua index 62e98be..31e732f 100644 --- a/lib/Voxel3D.lua +++ b/lib/Voxel3D.lua @@ -33,6 +33,7 @@ local WorldCurve = V.require("WorldCurve") local Sky = V.require("Sky") local DayNight = V.require("DayNight") local GlassMask = V.require("GlassMask") +local PixelCanvas = V.require("PixelCanvas") local Voxel3D = {} @@ -540,7 +541,8 @@ function Voxel3D.beginScene(w, h, cx, cy, vw, vh, sky, slot) local name = slot or "world" local held = slots[name] if not (held and held.w == w and held.h == h) then - local ok, c = pcall(love.graphics.newCanvas, w, h) + -- pixel-exact: w and h are framebuffer pixels already (see PixelCanvas) + local ok, c = PixelCanvas.new(w, h) if not ok then return false end c:setFilter("nearest", "nearest") if held and held.canvas and held.canvas.release then