Frames: cut each frame into its own GPU texture, no more fill matrix (#47)
game-maker / suite (push) Successful in 8m18s

A frame drawn straight from the sheet showed a hairline of the neighbouring
cell along any edge that fell inside a screen pixel: with antialias on, a
partly covered pixel is still evaluated at its centre, past the edge. On top
of that Pixi rewrites a Graphics fill texture from clamp-to-edge to repeat at
draw time (generateTextureFillMatrix.mjs), so the top row of a sheet read its
bottom row.

TextureLibrary.get(ref) now cuts the rectangle out of the loaded sheet with an
OffscreenCanvas on first use and uploads the cut (key file|x,y,w,h, kept for
the session). Address mode is mirror-repeat: Pixi only rewrites clamp-to-edge,
and within a texel of the border mirror behaves like clamp, for linear as well
as nearest. Loaded sheets get it too, since whole pictures wrapped at their own
borders the same way. viewport/textureFill.ts and its test are gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-13 13:25:49 +02:00
1 parent 06e1494f94
commit 05f7a2745e
9 files changed
+150 -150

No files matched your search

+14 -3
View File
@@ -1,6 +1,7 @@
import { type Application, Color, type ColorSource, type Container, Rectangle, Text } from 'pixi.js';
import { resolveObjects } from '../core/model/instance';
import { findObjectHolder } from '../core/model/Project';
import type { FrameRect } from '../core/model/texture';
import { activeDrag, Button, ui, Widget } from '../ui';
import { textureLibrary } from '../viewport/TextureLibrary';
import type { CentreTab, Editor } from './Editor';
@@ -348,9 +349,9 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
const hex = (value: number | undefined) => (value ?? 0).toString(16).padStart(2, '0');
return `#${hex(pixels[0])}${hex(pixels[1])}${hex(pixels[2])}`;
},
/** The filter the GPU sampler actually got, next to the model's – only the driver knows the former. */
textureFilter: (file: string) => {
const texture = textureLibrary.peek(file);
/** The filter and wrap the GPU sampler actually got, next to the model's – only the driver knows the former. */
textureFilter: (file: string, rect: FrameRect | null = null) => {
const texture = textureLibrary.peek(file, rect);
if (!texture) return null;
const source = texture.source;
const renderer = app.renderer as unknown as {
@@ -363,14 +364,24 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
(source as unknown as { _gpuData: Record<number, unknown> })._gpuData[renderer.uid],
);
let magFilter = 0;
let wrapT = 0;
if (gl) {
// Through Pixi rather than a raw `bindTexture`, so its idea of what is bound stays true.
renderer.texture.bindSource(source, 0);
magFilter = gl.getTexParameter(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER) as number;
wrapT = gl.getTexParameter(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T) as number;
}
return {
scaleMode: source.scaleMode,
gpu: magFilter === gl?.NEAREST ? 'nearest' : magFilter === gl?.LINEAR ? 'linear' : 'none',
wrap:
wrapT === gl?.CLAMP_TO_EDGE
? 'clamp'
: wrapT === gl?.REPEAT
? 'repeat'
: wrapT === gl?.MIRRORED_REPEAT
? 'mirror'
: 'none',
uploaded,
};
},