Shader graph: UV, Sample texture, Texture size, Time, Sin/Floor/Fract/Step; edge mode on Output (#78)
game-maker / suite (push) Successful in 8m38s
game-maker / suite (push) Successful in 8m38s
Fragment-side coordinate nodes instead of vertex nodes: the quad stays the quad, a UV read plus a sample at a computed coordinate covers flip, scroll, wave, pixelate and scanlines. Time is a uniform only when a Time node reaches the Output; a viewport that drew a Time shader redraws from the ticker so the Scene tab animates without a running world. Edge (Mirror/Repeat/Clamp) is a second texture variant in TextureLibrary because WebGL binds no TextureStyle resource of the shader. The compiler now emits #version 300 es first: Pixi only compiles as ES 3.00 when the fragment says so, and textureSize needs it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
0e5e5ef925
commit
f85606427b
12 files changed
+586
-38
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import { Assets, CanvasSource, Texture } from 'pixi.js';
|
||||
import type { FrameRect, TextureRef } from '../core/model/texture';
|
||||
import { DEFAULT_EDGE, type EdgeMode } from '../core/shader/nodes';
|
||||
|
||||
const DEFAULT_BASE = '/game-maker/data/textures';
|
||||
|
||||
@@ -10,6 +11,17 @@ const DEFAULT_BASE = '/game-maker/data/textures';
|
||||
*/
|
||||
const EDGE_MODE = 'mirror-repeat';
|
||||
|
||||
/**
|
||||
* A shader may want the picture to continue differently past its border (the Output's Edge). The
|
||||
* sampler state lives on the source – WebGL takes no style from the shader – so that is a cut of
|
||||
* its own with that address mode.
|
||||
*/
|
||||
const ADDRESS_MODE: Record<EdgeMode, 'repeat' | 'mirror-repeat' | 'clamp-to-edge'> = {
|
||||
mirror: EDGE_MODE,
|
||||
repeat: 'repeat',
|
||||
clamp: 'clamp-to-edge',
|
||||
};
|
||||
|
||||
/**
|
||||
* Keeps uploaded images on the GPU, keyed by file name. Nothing is evicted: file names are
|
||||
* content addressed, so a cached texture can never go stale. A miss is not an error – it starts
|
||||
@@ -35,9 +47,11 @@ export class TextureLibrary {
|
||||
* shared source, so two assets naming the same file with different `smooth` fight over it and
|
||||
* the last one drawn wins; upload avoids that by reusing the existing asset for a known file.
|
||||
*/
|
||||
get(ref: TextureRef): Texture | null {
|
||||
get(ref: TextureRef, edge: EdgeMode = DEFAULT_EDGE): Texture | null {
|
||||
const sheet = this.load(ref.file);
|
||||
const texture = sheet && (ref.rect ? this.crop(ref.file, sheet, ref.rect) : sheet);
|
||||
if (!sheet) return null;
|
||||
const rect = ref.rect ?? (edge === DEFAULT_EDGE ? null : wholeOf(sheet));
|
||||
const texture = rect ? this.crop(ref.file, sheet, rect, edge) : sheet;
|
||||
if (!texture) return null;
|
||||
const mode = ref.smooth ? 'linear' : 'nearest';
|
||||
if (texture.source.scaleMode !== mode) {
|
||||
@@ -80,15 +94,17 @@ export class TextureLibrary {
|
||||
* through a fill matrix it showed a row of the neighbouring cell along any edge that fell
|
||||
* inside a screen pixel; a cut has no neighbour to show.
|
||||
*/
|
||||
private crop(file: string, sheet: Texture, rect: FrameRect): Texture | null {
|
||||
private crop(file: string, sheet: Texture, rect: FrameRect, edge: EdgeMode): Texture | null {
|
||||
if (rect.w <= 0 || rect.h <= 0) return null;
|
||||
const key = cropKey(file, rect);
|
||||
const key = edge === DEFAULT_EDGE ? cropKey(file, rect) : `${cropKey(file, rect)}|${edge}`;
|
||||
let texture = this.crops.get(key);
|
||||
if (!texture) {
|
||||
const canvas = new OffscreenCanvas(rect.w, rect.h);
|
||||
const picture = sheet.source.resource as CanvasImageSource;
|
||||
canvas.getContext('2d')?.drawImage(picture, rect.x, rect.y, rect.w, rect.h, 0, 0, rect.w, rect.h);
|
||||
texture = new Texture({ source: new CanvasSource({ resource: canvas, addressMode: EDGE_MODE }) });
|
||||
texture = new Texture({
|
||||
source: new CanvasSource({ resource: canvas, addressMode: ADDRESS_MODE[edge] }),
|
||||
});
|
||||
this.crops.set(key, texture);
|
||||
}
|
||||
return texture;
|
||||
@@ -118,4 +134,8 @@ function cropKey(file: string, rect: FrameRect): string {
|
||||
return `${file}|${rect.x},${rect.y},${rect.w},${rect.h}`;
|
||||
}
|
||||
|
||||
function wholeOf(sheet: Texture): FrameRect {
|
||||
return { x: 0, y: 0, w: sheet.source.pixelWidth, h: sheet.source.pixelHeight };
|
||||
}
|
||||
|
||||
export const textureLibrary = new TextureLibrary();
|
||||
Reference in new issue
Block a user