Skip to content

Commit 410697b

Browse files
authored
fix: simplify textures, update tsconfig, add better texture placeholders (#348)
1 parent f5062d2 commit 410697b

7 files changed

Lines changed: 215 additions & 170 deletions

File tree

integration-tests/index.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,9 @@ async function test() {
105105
initialProjectWidth: projectWidth,
106106
initialProjectHeight: projectHeight,
107107
canvas,
108-
uploadTexture: (url, setNewUrl) => {
108+
onExternalTextureCreation: (url, setNewUrl) => {
109109
setNewUrl(`${newTextures}-${url}`)
110110
newTextures++
111-
// if (url.startsWith('http://our-domain.com')) {
112-
// setNewUrl('new url')
113-
// }
114111
},
115112
onSnapshotUpdate: (snapshot, commit) => {
116113
setLastSnapshot(snapshot)

src/index.ts

Lines changed: 44 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,10 @@ export default async function initCreator({ canvas, ...props }: CreatorProps): P
135135

136136
const onAssetUpdate = (snapshot: ZigProjectSnapshot, commit: boolean) => {
137137
Snapshots.saveSnapshot(snapshot)
138-
newAssetsSnapshot(commit)
138+
props.onSnapshotUpdate(Snapshots.lastSnapshot, commit)
139+
if (commit) {
140+
triggerGeneratePreview()
141+
}
139142
}
140143

141144
Logic.glueJsGeneral(
@@ -148,15 +151,6 @@ export default async function initCreator({ canvas, ...props }: CreatorProps): P
148151
Fonts.getKerning
149152
)
150153

151-
function newAssetsSnapshot(commit: boolean) {
152-
// this function is not part of Logic.connect_on_asset_update_callback
153-
// only because once we update a texture url, we have to notify about the assets update
154-
props.onSnapshotUpdate(Snapshots.lastSnapshot, commit)
155-
if (commit) {
156-
triggerGeneratePreview()
157-
}
158-
}
159-
160154
Logic.connectTyping(
161155
(text: string) => Typing.enable(text, canvas),
162156
Typing.disable,
@@ -169,42 +163,47 @@ export default async function initCreator({ canvas, ...props }: CreatorProps): P
169163
urls.map<Promise<Asset | Asset[]>>(
170164
(url) =>
171165
new Promise((resolve, reject) => {
172-
const textureId = Textures.add(
173-
url,
174-
({ width, height, isNewTexture, shapeAssets, error }) => {
175-
// we wait to add image once points are known. The other option was to add image first
176-
// with "default" points and then update it once texture is loaded.
177-
// However, that would cause issues with undo/redo since we would have history
178-
// snapshot with "default" points and then update it to the real points.
179-
180-
if (error) {
181-
return reject(error)
182-
}
183-
184-
if (shapeAssets) {
185-
return resolve(shapeAssets)
186-
}
187-
188-
if (isNewTexture) {
189-
props.uploadTexture(url, (newUrl) => {
190-
Textures.updateTextureUrl(textureId, newUrl)
191-
newAssetsSnapshot(true)
192-
})
193-
}
194-
195-
return resolve({
196-
id: NO_ASSET_ID,
197-
bounds: getDefaultPoints(
198-
width,
199-
height,
200-
Snapshots.lastSnapshot.width,
201-
Snapshots.lastSnapshot.height
202-
),
203-
texture_id: textureId, // if there is no points, then for sure there is no asset.textureId
204-
url,
205-
})
166+
const textureId = Textures.add(url, ({ width, height, shapeAssets, error }) => {
167+
// we wait to add image once points are known. The other option was to add image first
168+
// with "default" points and then update it once texture is loaded.
169+
// However, that would cause issues with undo/redo since we would have history
170+
// snapshot with "default" points and then update it to the real points.
171+
172+
if (error) {
173+
return reject(error)
206174
}
207-
)
175+
176+
if (shapeAssets) {
177+
return resolve(shapeAssets)
178+
}
179+
180+
props.onExternalTextureCreation(url, (newUrl) => {
181+
Textures.updateTextureUrl(textureId, newUrl)
182+
183+
const newAssets = Snapshots.lastSnapshot.assets.map<Asset>((asset) => {
184+
if ('url' in asset && asset.url === url) {
185+
return {
186+
...asset,
187+
url: newUrl,
188+
}
189+
}
190+
return asset
191+
})
192+
Snapshots.lastSnapshot.assets = newAssets
193+
})
194+
195+
return resolve({
196+
id: NO_ASSET_ID,
197+
bounds: getDefaultPoints(
198+
width,
199+
height,
200+
Snapshots.lastSnapshot.width,
201+
Snapshots.lastSnapshot.height
202+
),
203+
texture_id: textureId, // if there is no points, then for sure there is no asset.textureId
204+
url,
205+
})
206+
})
208207
})
209208
)
210209
)

src/loadingTexture.ts

Lines changed: 0 additions & 61 deletions
This file was deleted.

src/placeholderTexture.ts

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { device, presentationFormat } from 'WebGPU/device'
2+
3+
function createTexture(
4+
textureData: Uint8Array<ArrayBuffer>,
5+
textureWidth: number,
6+
textureHeight: number
7+
): GPUTexture {
8+
const texture = device.createTexture({
9+
label: 'manually crafted placeholder texture',
10+
size: [textureWidth, textureHeight],
11+
format: presentationFormat,
12+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
13+
})
14+
device.queue.writeTexture(
15+
{ texture },
16+
textureData,
17+
{ bytesPerRow: textureWidth * 4 },
18+
{ width: textureWidth, height: textureHeight }
19+
)
20+
21+
return texture
22+
}
23+
24+
// lazy initialziation base on presentationFormat
25+
const INITIAL_RGB_COLOR = [255, 205, 58, 255]
26+
// if we keep just let color, we won't know if channels where already swapped or not
27+
// and init texture is called each time user visits creator
28+
let color = INITIAL_RGB_COLOR
29+
30+
// 5×5 pixel font glyphs
31+
// prettier-ignore
32+
const FONT: Record<string, number[][]> = {
33+
A: [[0,1,1,1,0],[1,0,0,0,1],[1,1,1,1,1],[1,0,0,0,1],[1,0,0,0,1]],
34+
D: [[1,1,1,1,0],[1,0,0,0,1],[1,0,0,0,1],[1,0,0,0,1],[1,1,1,1,0]],
35+
E: [[1,1,1,1,1],[1,0,0,0,0],[1,1,1,1,0],[1,0,0,0,0],[1,1,1,1,1]],
36+
F: [[1,1,1,1,1],[1,0,0,0,0],[1,1,1,1,0],[1,0,0,0,0],[1,0,0,0,0]],
37+
G: [[0,1,1,1,0],[1,0,0,0,0],[1,0,1,1,1],[1,0,0,0,1],[0,1,1,1,0]],
38+
I: [[1,1,1,1,1],[0,0,1,0,0],[0,0,1,0,0],[0,0,1,0,0],[1,1,1,1,1]],
39+
M: [[1,0,0,0,1],[1,1,0,1,1],[1,0,1,0,1],[1,0,0,0,1],[1,0,0,0,1]],
40+
N: [[1,0,0,0,1],[1,1,0,0,1],[1,0,1,0,1],[1,0,0,1,1],[1,0,0,0,1]],
41+
L: [[1,0,0,0,0],[1,0,0,0,0],[1,0,0,0,0],[1,0,0,0,0],[1,1,1,1,1]],
42+
O: [[0,1,1,1,0],[1,0,0,0,1],[1,0,0,0,1],[1,0,0,0,1],[0,1,1,1,0]],
43+
R: [[1,1,1,1,0],[1,0,0,0,1],[1,1,1,1,0],[1,0,0,1,0],[1,0,0,0,1]],
44+
T: [[1,1,1,1,1],[0,0,1,0,0],[0,0,1,0,0],[0,0,1,0,0],[0,0,1,0,0]],
45+
U: [[1,0,0,0,1],[1,0,0,0,1],[1,0,0,0,1],[1,0,0,0,1],[0,1,1,1,0]],
46+
X: [[1,0,0,0,1],[0,1,0,1,0],[0,0,1,0,0],[0,1,0,1,0],[1,0,0,0,1]],
47+
' ': [[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]],
48+
}
49+
50+
function drawText(pixels: Uint8Array, w: number, h: number, text: string, x0: number, y0: number) {
51+
let x = x0
52+
for (const ch of text) {
53+
const g = FONT[ch]
54+
if (g) {
55+
for (let row = 0; row < 5; row++) {
56+
for (let col = 0; col < 5; col++) {
57+
if (g[row][col]) {
58+
setPixel(pixels, w, h, x + col, y0 + 4 - row)
59+
}
60+
}
61+
}
62+
}
63+
x += 6 // 5px glyph + 1px gap
64+
}
65+
}
66+
67+
const getBorderPixels = (w: number, h: number) => {
68+
const pixels = new Uint8Array(w * h * 4)
69+
70+
for (let i = 0; i < w * h; i++) {
71+
if (i % w === 0 || i % w === w - 1 || i < w || i > w * h - w) {
72+
pixels[i * 4 + 0] = color[0]
73+
pixels[i * 4 + 1] = color[1]
74+
pixels[i * 4 + 2] = color[2]
75+
}
76+
pixels[i * 4 + 3] = 255 // opaque black by default
77+
}
78+
79+
return pixels
80+
}
81+
82+
function setPixel(pixels: Uint8Array, w: number, h: number, x: number, y: number) {
83+
if (x < 0 || x >= w || y < 0 || y >= h) return
84+
85+
const i = (y * w + x) * 4
86+
pixels[i] = color[0]
87+
pixels[i + 1] = color[1]
88+
pixels[i + 2] = color[2]
89+
pixels[i + 3] = color[3]
90+
}
91+
92+
export function getLoadingTexture(): GPUTexture {
93+
if (presentationFormat === 'bgra8unorm') {
94+
color = [INITIAL_RGB_COLOR[2], INITIAL_RGB_COLOR[1], INITIAL_RGB_COLOR[0], INITIAL_RGB_COLOR[3]]
95+
}
96+
97+
const W = 72
98+
const H = 40
99+
const pixels = getBorderPixels(W, H)
100+
101+
function drawText(text: string, x0: number, y0: number) {
102+
let x = x0
103+
for (const ch of text) {
104+
const g = FONT[ch]
105+
if (g) {
106+
for (let row = 0; row < 5; row++)
107+
for (let col = 0; col < 5; col++)
108+
if (g[row][col]) setPixel(pixels, W, H, x + col, y0 + 4 - row)
109+
}
110+
x += 6 // 5px glyph + 1px gap
111+
}
112+
}
113+
114+
// "LOADING": 7 chars × 6 - 1 = 41px; center in 72: start x=16
115+
drawText('LOADING', 16, 17)
116+
117+
return createTexture(pixels, W, H)
118+
}
119+
120+
export function getErrorTexture(): GPUTexture {
121+
const W = 72
122+
const H = 40
123+
const pixels = getBorderPixels(W, H)
124+
125+
// "IMAGE NOT": 9 chars × 6 - 1 = 53px; center in 72: start x=10
126+
drawText(pixels, W, H, 'IMAGE NOT', 10, 14)
127+
// "FOUND": 5 chars × 6 - 1 = 29px; center in 72: start x=21
128+
drawText(pixels, W, H, 'FOUND', 21, 6)
129+
130+
// Mario-style ? mark (20×16) centered horizontally (start col 26)
131+
// High y = top of screen; mark[0] is the arch top → placed at y = 35 (highest)
132+
// prettier-ignore
133+
const mark = [
134+
[0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0],
135+
[0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0], // wide arch top: cols 1–16
136+
[0,1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0], // left wall cols 1–4, right cols 13–16
137+
[0,1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0],
138+
[0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0], // right wall descends one more row
139+
[0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0], // diagonal: cols 11–14
140+
[0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0], // cols 9–12
141+
[0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0], // cols 7–10 (stem begins)
142+
[0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0], // stem
143+
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], // gap
144+
[0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0], // dot
145+
[0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0],
146+
]
147+
148+
for (let r = 0; r < mark.length; r++) {
149+
for (let c = 0; c < 20; c++) {
150+
if (mark[r][c]) {
151+
setPixel(pixels, W, H, 26 + c, 34 - r)
152+
}
153+
}
154+
}
155+
156+
return createTexture(pixels, W, H)
157+
}

0 commit comments

Comments
 (0)