Skip to content

Commit b4668b6

Browse files
authored
fix: avoid shape.bounds non-invetrable matrix, fix too early preview generation (#356)
1 parent 4cd1084 commit b4668b6

8 files changed

Lines changed: 120 additions & 49 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import setCamera from 'utils/setCamera'
22
import { camera } from '../pointer'
33
import Logic from 'logic/index.zig'
44

5-
export default async function generatePreview(
5+
export async function captureCanvas(
66
device: GPUDevice,
77
presentationFormat: GPUTextureFormat,
88
creatorCanvas: HTMLCanvasElement,

src/customPrograms.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { device, presentationFormat } from 'WebGPU/setupDevice'
44
import { CustomProgramError } from 'types'
55
import drawShapeShaderBase from 'WebGPU/programs/drawShape/base.wgsl'
66
import * as Logic from './logic/index.zig'
7+
import * as PreviewTrigger from './previewTrigger'
78

89
interface CustomProgram {
910
code: string
@@ -84,6 +85,8 @@ function createProgram(code: string, newId: number): CustomProgram {
8485
execute: null,
8586
}
8687

88+
PreviewTrigger.updateResourcesFlag('program-load-start')
89+
8790
const executeCallback = getDrawShape(
8891
device,
8992
presentationFormat,
@@ -105,6 +108,8 @@ function createProgram(code: string, newId: number): CustomProgram {
105108
} else {
106109
program.execute = executeCallback
107110
}
111+
112+
PreviewTrigger.updateResourcesFlag('program-load-end')
108113
}
109114
)
110115

src/fonts.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Point } from 'types'
77
import decompressWoff2 from 'utils/decompressWoff2.mjs'
88
import { isStraightHandle } from 'svgToShapes/utils'
99
import { STRAIGHT_LINE_HANDLE } from 'svgToShapes/const'
10+
import * as PreviewTrigger from './previewTrigger'
1011

1112
const DEFAULT_SPACE = 250 // expressed in font units
1213
const ENTER = 10
@@ -28,6 +29,7 @@ export async function loadFont(fontId: number) {
2829
fonts.set(fontId, null)
2930
let fontBuffer: ArrayBuffer | null = null
3031
try {
32+
PreviewTrigger.updateResourcesFlag('font-load-start')
3133
const url = getFontUrl(fontId)
3234
const res = await fetch(url)
3335
fontBuffer = await res.arrayBuffer()
@@ -52,6 +54,8 @@ export async function loadFont(fontId: number) {
5254
} else {
5355
console.error('Failed to load font', err)
5456
}
57+
} finally {
58+
PreviewTrigger.updateResourcesFlag('font-load-end')
5559
}
5660
}
5761

src/index.ts

Lines changed: 18 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import * as Logic from './logic/index.zig'
66
import initMouseController, { camera } from 'pointer'
77
import getDefaultPoints from 'utils/getDefaultPoints'
88
import * as Textures from 'textures'
9-
import throttle from 'utils/throttle'
10-
import generatePreview from 'WebGPU/generatePreview'
9+
import { captureCanvas } from 'WebGPU/captureCanvas'
1110
import * as Typing from 'typing'
1211
import * as Fonts from 'fonts'
1312
import { Asset, CreatorAPI, CreatorProps, Id, ZigAsset, ZigProjectSnapshot } from './types'
@@ -19,19 +18,19 @@ import * as Snapshots from 'snapshots/snapshots'
1918
import toZigAsset from 'snapshots/toZigAsset'
2019
import { NO_ASSET_ID } from 'consts'
2120
import { downloadCanvas } from 'utils/downloadCanvas'
21+
import * as PreviewTrigger from './previewTrigger'
2222

2323
export default async function initCreator({ canvas, ...props }: CreatorProps): Promise<CreatorAPI> {
2424
const fakeMaxTexSize = 40
2525

26-
let texturesLoading = 0
2726
let isMouseEventProcessing = false
2827
const abortController = new AbortController()
2928

3029
function updateIsProcessingFlag() {
31-
props.onIsProcessingFlagUpdate(texturesLoading > 0 || isMouseEventProcessing)
30+
// TODO: add back resources loading status and trigger each time reosurces are loaded
31+
props.onIsProcessingFlagUpdate(isMouseEventProcessing)
3232
}
3333

34-
let isDestroyed = false
3534
await setupDevice(props.captureError)
3635
Snapshots.init(props.initialProjectWidth, props.initialProjectHeight)
3736

@@ -43,11 +42,7 @@ export default async function initCreator({ canvas, ...props }: CreatorProps): P
4342
props.isTest
4443
)
4544

46-
Textures.init((texLoadings) => {
47-
texturesLoading = texLoadings
48-
updateIsProcessingFlag()
49-
triggerGeneratePreview()
50-
})
45+
Textures.init()
5146

5247
CustomPrograms.init()
5348
Fonts.init(props.getFontUrl)
@@ -88,33 +83,11 @@ export default async function initCreator({ canvas, ...props }: CreatorProps): P
8883
abortController.signal
8984
)
9085

91-
const throttledPreviewGenerator = throttle(() => {
92-
if (isDestroyed || texturesLoading > 0 || props.isTest || props.disableMinaitures) return
93-
94-
generatePreview(
95-
device,
96-
presentationFormat,
97-
canvas,
98-
Snapshots.lastSnapshot.width,
99-
Snapshots.lastSnapshot.height,
100-
400,
101-
400,
102-
capturePreview,
103-
props.onPreviewUpdate
104-
)
105-
}, 1000 * 5)
106-
107-
const triggerGeneratePreview = () => {
108-
if (texturesLoading === 0) {
109-
throttledPreviewGenerator()
110-
}
111-
}
112-
11386
const onAssetUpdate = (snapshot: ZigProjectSnapshot, commit: boolean) => {
11487
Snapshots.saveSnapshot(snapshot)
11588
props.onSnapshotUpdate(Snapshots.lastSnapshot, commit)
11689
if (commit) {
117-
triggerGeneratePreview()
90+
PreviewTrigger.safeGeneratePreview()
11891
}
11992
}
12093

@@ -221,6 +194,15 @@ export default async function initCreator({ canvas, ...props }: CreatorProps): P
221194
props.captureError
222195
)
223196

197+
if (!props.isTest && !props.disableMinaitures) {
198+
PreviewTrigger.init({
199+
canvas,
200+
capturePreview,
201+
onPreviewUpdate: props.onPreviewUpdate,
202+
onResourcesLoad: updateIsProcessingFlag,
203+
})
204+
}
205+
224206
Fonts.loadFont(0)
225207

226208
const setSnapshot: CreatorAPI['setSnapshot'] = async (
@@ -230,7 +212,7 @@ export default async function initCreator({ canvas, ...props }: CreatorProps): P
230212
try {
231213
const assets = snapshot.assets.map<ZigAsset>((asset) => toZigAsset(asset, props.captureError))
232214
Logic.setSnapshot({ ...snapshot, assets }, produceSnapshot, addHistoryEntry)
233-
triggerGeneratePreview()
215+
PreviewTrigger.safeGeneratePreview()
234216
} catch (err) {
235217
props.captureError(err)
236218
}
@@ -242,7 +224,7 @@ export default async function initCreator({ canvas, ...props }: CreatorProps): P
242224
removeAsset: Logic.removeAsset,
243225
setSnapshot,
244226
destroy: () => {
245-
isDestroyed = true
227+
PreviewTrigger.markDeviceDestroyed()
246228
abortController.abort()
247229
stopRAF()
248230
Logic.deinitState()
@@ -266,7 +248,7 @@ export default async function initCreator({ canvas, ...props }: CreatorProps): P
266248
Logic.setSelectedAssetTypoProps(typoProps, commit)
267249
},
268250
download: () => {
269-
generatePreview(
251+
captureCanvas(
270252
device,
271253
presentationFormat,
272254
canvas,

src/logic/matrix.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ pub const Matrix3x3 = struct {
9494
const H = -(a * f - c * d);
9595
const I = a * e - b * d;
9696
const det = a * A + b * B + c * C;
97-
if (@abs(det) < 1e-6) @panic("Matrix not invertible");
97+
if (@abs(det) < 1e-6) @panic("Matrix not invertable");
9898
// for example when scale is set to zero, we are unable to "invert" it to know previous value
9999
const inv_det = 1.0 / det;
100100
return Matrix3x3{

src/logic/shapes/shapes.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,8 @@ pub const Shape = struct {
199199
const new_width = box.max_x - box.min_x;
200200
const new_height = box.max_y - box.min_y;
201201

202-
if (utils.equalF32(new_width, 0) or utils.equalF32(new_height, 0)) {
203-
return; // No valid bounding box
202+
if (new_width < 1e-6 or new_height < 1e-6) {
203+
return; // No valid bounding box, we gonna get not invertable matrix out of it
204204
}
205205

206206
// Normalize points to [0,1] range

src/previewTrigger.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import throttle from 'utils/throttle'
2+
import { captureCanvas } from 'WebGPU/captureCanvas'
3+
import * as Snapshots from 'snapshots/snapshots'
4+
import { device, presentationFormat } from 'WebGPU/setupDevice'
5+
6+
let isDeviceDestroyed = false
7+
export function markDeviceDestroyed() {
8+
isDeviceDestroyed = true
9+
}
10+
11+
let textures = 0
12+
let fonts = 0
13+
let programs = 0
14+
15+
export function updateResourcesFlag(
16+
type:
17+
| 'font-load-start'
18+
| 'font-load-end'
19+
| 'texture-load-start'
20+
| 'texture-load-end'
21+
| 'program-load-start'
22+
| 'program-load-end'
23+
) {
24+
switch (type) {
25+
case 'font-load-start': {
26+
fonts++
27+
break
28+
}
29+
case 'font-load-end': {
30+
fonts--
31+
break
32+
}
33+
case 'texture-load-start': {
34+
textures++
35+
break
36+
}
37+
case 'texture-load-end': {
38+
textures--
39+
break
40+
}
41+
case 'program-load-start': {
42+
programs++
43+
break
44+
}
45+
case 'program-load-end': {
46+
programs--
47+
break
48+
}
49+
}
50+
51+
safeGeneratePreview()
52+
}
53+
54+
let generatePreview: VoidFunction | null = null
55+
56+
export function safeGeneratePreview() {
57+
if (textures === 0 && fonts === 0 && programs === 0) {
58+
generatePreview?.()
59+
}
60+
}
61+
62+
type Params = {
63+
canvas: HTMLCanvasElement
64+
capturePreview: (previewCtx: GPUCanvasContext, collectAndCleanup: VoidFunction) => void
65+
onPreviewUpdate: (canvas: HTMLCanvasElement) => void
66+
onResourcesLoad: VoidFunction
67+
}
68+
69+
export function init({ canvas, capturePreview, onPreviewUpdate }: Params) {
70+
generatePreview = throttle(() => {
71+
if (isDeviceDestroyed) return
72+
73+
captureCanvas(
74+
device,
75+
presentationFormat,
76+
canvas,
77+
Snapshots.lastSnapshot.width,
78+
Snapshots.lastSnapshot.height,
79+
400,
80+
400,
81+
capturePreview,
82+
onPreviewUpdate
83+
)
84+
}, 1000 * 5)
85+
}

src/textures.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Asset } from 'types'
1010
import getShapesAssets from 'svgToShapes/getShapesAssets'
1111
import { device, storageFormat } from 'WebGPU/setupDevice'
1212
import { delayedDestroy, destroyGpuObjects } from 'WebGPU/programs/initPrograms'
13+
import * as PreviewTrigger from './previewTrigger'
1314

1415
function getSvgSize(svgRoot: ElementNode, img?: HTMLImageElement) {
1516
const props = svgRoot.properties
@@ -34,15 +35,11 @@ function extractSizeFromSvgViewbox(viewbox: string) {
3435
let textures: TextureSource[]
3536
let textureLoadingPlaceholder: GPUTexture
3637
let textureErrorPlaceholder: GPUTexture
37-
let updateProcessing: () => void
38-
let texturesLoading: number
3938

40-
export function init(_updateProcessing: (loadingTextures: number) => void): void {
39+
export function init(): void {
4140
textures = []
4241
textureLoadingPlaceholder = getLoadingTexture()
4342
textureErrorPlaceholder = getErrorTexture()
44-
texturesLoading = 0
45-
updateProcessing = () => _updateProcessing(texturesLoading)
4643

4744
addIcon(0, RotateIcon)
4845
}
@@ -85,8 +82,7 @@ export function add(
8582
return textures.indexOf(sameUrl)
8683
}
8784

88-
texturesLoading++
89-
updateProcessing()
85+
PreviewTrigger.updateResourcesFlag('texture-load-start')
9086

9187
const textureId = textures.length
9288
// we allow duplicates in textures array
@@ -148,8 +144,7 @@ async function resolveTexture(
148144
// onLoad has to be called, otherwise the Promise(waiting for onLoad)
149145
// will not resolve and the caller will keep waiting
150146
} finally {
151-
texturesLoading--
152-
updateProcessing()
147+
PreviewTrigger.updateResourcesFlag('texture-load-end')
153148
}
154149
}
155150

0 commit comments

Comments
 (0)