@nodetool-ai/game-renderer 0.8.4 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.nodetool-build-stamp +1 -1
- package/dist/canvas2d.d.ts.map +1 -1
- package/dist/canvas2d.js +7 -4
- package/dist/canvas2d.js.map +1 -1
- package/dist/lighting.d.ts +1 -1
- package/dist/lighting.d.ts.map +1 -1
- package/dist/lighting.js +3 -3
- package/dist/lighting.js.map +1 -1
- package/dist/player-v1.js +219 -219
- package/dist/webgpu/sprites.d.ts +2 -2
- package/dist/webgpu/sprites.d.ts.map +1 -1
- package/dist/webgpu/sprites.js +4 -3
- package/dist/webgpu/sprites.js.map +1 -1
- package/dist/webgpu/types.d.ts +3 -1
- package/dist/webgpu/types.d.ts.map +1 -1
- package/dist/webgpu.d.ts +10 -1
- package/dist/webgpu.d.ts.map +1 -1
- package/dist/webgpu.js +140 -47
- package/dist/webgpu.js.map +1 -1
- package/package.json +5 -5
package/dist/webgpu/sprites.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Batch, TextureEntry, Blend } from "./types.js";
|
|
1
|
+
import type { Batch, TextureEntry, Blend, Sampling } from "./types.js";
|
|
2
2
|
export declare const INSTANCE_FLOATS = 14;
|
|
3
3
|
export declare const SHADER = "\nstruct Camera { center: vec2f, viewport: vec2f, zoom: f32, padding0: f32, padding1: f32, padding2: f32 };\noverride linearizeInput: bool = false;\nfn srgbToLinear(rgb: vec3f) -> vec3f {\n return select(rgb / 12.92, pow((rgb + 0.055) / 1.055, vec3f(2.4)), rgb > vec3f(0.04045));\n}\nfn linearToSrgb(rgb: vec3f) -> vec3f {\n return select(rgb * 12.92, 1.055 * pow(rgb, vec3f(1.0 / 2.4)) - 0.055, rgb > vec3f(0.0031308));\n}\n@group(0) @binding(0) var<uniform> camera: Camera;\n@group(0) @binding(1) var atlasSampler: sampler;\n@group(0) @binding(2) var atlas: texture_2d<f32>;\n@group(1) @binding(0) var lightSampler: sampler;\n@group(1) @binding(1) var lightTexture: texture_2d<f32>;\n\nstruct VertexInput {\n @builtin(vertex_index) corner: u32,\n @location(0) center: vec2f,\n @location(1) size: vec2f,\n @location(2) uv: vec4f,\n @location(3) color: vec4f,\n @location(4) rotation: f32,\n @location(5) unlit: f32,\n};\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n @location(1) color: vec4f,\n @location(3) @interpolate(flat) unlit: f32,\n @location(4) lightUv: vec2f,\n};\n@vertex fn vertexMain(input: VertexInput) -> VertexOutput {\n let corners = array<vec2f, 6>(\n vec2f(-0.5, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, 0.5),\n vec2f(0.5, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, -0.5));\n let local = corners[input.corner] * input.size;\n let sine = sin(input.rotation);\n let cosine = cos(input.rotation);\n let rotated = vec2f(local.x * cosine - local.y * sine, local.x * sine + local.y * cosine);\n let world = input.center + rotated;\n let clip = (world - camera.center) * camera.zoom * 2.0 / camera.viewport;\n var output: VertexOutput;\n output.position = vec4f(clip, 0.0, 1.0);\n let uvCorner = corners[input.corner] + vec2f(0.5, 0.5);\n output.uv = input.uv.xy + vec2f(uvCorner.x, 1.0 - uvCorner.y) * input.uv.zw;\n output.color = input.color;\n output.unlit = input.unlit;\n output.lightUv = clip * vec2f(0.5, -0.5) + vec2f(0.5, 0.5);\n return output;\n}\n@fragment fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {\n let sampled = textureSample(atlas, atlasSampler, input.uv) * input.color;\n var straight = select(sampled.rgb, srgbToLinear(sampled.rgb), linearizeInput);\n if (input.unlit < 0.5) {\n let irradiance = textureSampleLevel(lightTexture, lightSampler, input.lightUv, 0.0).rgb;\n let lit = clamp(srgbToLinear(sampled.rgb) * irradiance, vec3f(0.0), vec3f(1.0));\n straight = select(linearToSrgb(lit), lit, linearizeInput);\n }\n return vec4f(straight * sampled.a, sampled.a);\n}";
|
|
4
|
-
export declare function appendBatch(batches: Batch[], texture: TextureEntry, blend: Blend, index: number): void;
|
|
4
|
+
export declare function appendBatch(batches: Batch[], texture: TextureEntry, blend: Blend, index: number, sampling: Sampling): void;
|
|
5
5
|
export declare function writeInstance(target: Float32Array, index: number, ...values: number[]): void;
|
|
6
6
|
//# sourceMappingURL=sprites.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sprites.d.ts","sourceRoot":"","sources":["../../src/webgpu/sprites.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"sprites.d.ts","sourceRoot":"","sources":["../../src/webgpu/sprites.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACvE,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,MAAM,0iFA2DjB,CAAC;AAEH,wBAAgB,WAAW,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAQxH;AAEH,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAE1F"}
|
package/dist/webgpu/sprites.js
CHANGED
|
@@ -59,13 +59,14 @@ struct VertexOutput {
|
|
|
59
59
|
}
|
|
60
60
|
return vec4f(straight * sampled.a, sampled.a);
|
|
61
61
|
}`;
|
|
62
|
-
export function appendBatch(batches, texture, blend, index) {
|
|
62
|
+
export function appendBatch(batches, texture, blend, index, sampling) {
|
|
63
|
+
const bindGroup = texture.bindGroups[sampling];
|
|
63
64
|
const last = batches[batches.length - 1];
|
|
64
|
-
if (last?.texture === texture && last.blend === blend) {
|
|
65
|
+
if (last?.texture === texture && last.bindGroup === bindGroup && last.blend === blend) {
|
|
65
66
|
last.count++;
|
|
66
67
|
}
|
|
67
68
|
else {
|
|
68
|
-
batches.push({ texture, blend, first: index, count: 1 });
|
|
69
|
+
batches.push({ texture, bindGroup, blend, first: index, count: 1 });
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
export function writeInstance(target, index, ...values) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sprites.js","sourceRoot":"","sources":["../../src/webgpu/sprites.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC;AAClC,MAAM,CAAC,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2DpB,CAAC;AAEH,MAAM,UAAU,WAAW,CAAC,OAAgB,EAAE,OAAqB,EAAE,KAAY,EAAE,KAAa;
|
|
1
|
+
{"version":3,"file":"sprites.js","sourceRoot":"","sources":["../../src/webgpu/sprites.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC;AAClC,MAAM,CAAC,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2DpB,CAAC;AAEH,MAAM,UAAU,WAAW,CAAC,OAAgB,EAAE,OAAqB,EAAE,KAAY,EAAE,KAAa,EAAE,QAAkB;IAChH,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACzC,IAAI,IAAI,EAAE,OAAO,KAAK,OAAO,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QACtF,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACtE,CAAC;AACH,CAAC;AAEH,MAAM,UAAU,aAAa,CAAC,MAAoB,EAAE,KAAa,EAAE,GAAG,MAAgB;IAClF,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,GAAG,eAAe,CAAC,CAAC;AAC9C,CAAC"}
|
package/dist/webgpu/types.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
export type Sampling = "nearest" | "linear";
|
|
1
2
|
export interface TextureEntry {
|
|
2
3
|
readonly texture: GPUTexture;
|
|
3
|
-
readonly
|
|
4
|
+
readonly bindGroups: Readonly<Record<Sampling, GPUBindGroup>>;
|
|
4
5
|
readonly width: number;
|
|
5
6
|
readonly height: number;
|
|
6
7
|
}
|
|
7
8
|
export type Blend = "normal" | "additive";
|
|
8
9
|
export interface Batch {
|
|
9
10
|
readonly texture: TextureEntry;
|
|
11
|
+
readonly bindGroup: GPUBindGroup;
|
|
10
12
|
readonly blend: Blend;
|
|
11
13
|
readonly first: number;
|
|
12
14
|
count: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/webgpu/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/webgpu/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE5C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE1C,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf"}
|
package/dist/webgpu.d.ts
CHANGED
|
@@ -43,7 +43,8 @@ export declare class WebGPUGameRenderer implements GameRenderer {
|
|
|
43
43
|
private readonly lightFallback;
|
|
44
44
|
private lightTarget;
|
|
45
45
|
private readonly textures;
|
|
46
|
-
private readonly
|
|
46
|
+
private readonly assetGenerations;
|
|
47
|
+
private readonly liveAssetTextures;
|
|
47
48
|
private readonly fallback;
|
|
48
49
|
private readonly particleDot;
|
|
49
50
|
private readonly placeholders;
|
|
@@ -52,6 +53,7 @@ export declare class WebGPUGameRenderer implements GameRenderer {
|
|
|
52
53
|
private hudCanvas;
|
|
53
54
|
private hudTexture;
|
|
54
55
|
private hudKey;
|
|
56
|
+
private hudRevision;
|
|
55
57
|
private disposed;
|
|
56
58
|
private lost;
|
|
57
59
|
constructor(canvas: HTMLCanvasElement, device: GPUDevice, context: GPUCanvasContext, format: GPUTextureFormat, assets: AssetCache, adapterType?: "hardware" | "software" | "unknown");
|
|
@@ -62,11 +64,18 @@ export declare class WebGPUGameRenderer implements GameRenderer {
|
|
|
62
64
|
private convertColor;
|
|
63
65
|
private textureEntry;
|
|
64
66
|
private upload;
|
|
67
|
+
private assertUsable;
|
|
68
|
+
private assetGeneration;
|
|
69
|
+
private retireGeneration;
|
|
70
|
+
private releaseFrameAssets;
|
|
71
|
+
private uploadGeneration;
|
|
72
|
+
private generationTexture;
|
|
65
73
|
private getTexture;
|
|
66
74
|
private getLutTexture;
|
|
67
75
|
private placeholder;
|
|
68
76
|
render(frame: GameRenderFrame, interpolation: number, particles?: GameParticleField): Promise<GameRendererStats>;
|
|
69
77
|
private renderFrame;
|
|
78
|
+
private renderAssetFrame;
|
|
70
79
|
private updateHud;
|
|
71
80
|
private ensureInstanceCapacity;
|
|
72
81
|
resize(width: number, height: number): void;
|
package/dist/webgpu.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webgpu.d.ts","sourceRoot":"","sources":["../src/webgpu.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,EAAE,UAAU,EAA2C,MAAM,eAAe,CAAC;AAEpF,OAAO,EAA4D,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC3H,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"webgpu.d.ts","sourceRoot":"","sources":["../src/webgpu.ts"],"names":[],"mappings":"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"}
|
package/dist/webgpu.js
CHANGED
|
@@ -49,7 +49,8 @@ export class WebGPUGameRenderer {
|
|
|
49
49
|
lightFallback;
|
|
50
50
|
lightTarget;
|
|
51
51
|
textures = new Map();
|
|
52
|
-
|
|
52
|
+
assetGenerations = new Set();
|
|
53
|
+
liveAssetTextures = new Set();
|
|
53
54
|
fallback;
|
|
54
55
|
particleDot;
|
|
55
56
|
placeholders = new Map();
|
|
@@ -58,6 +59,7 @@ export class WebGPUGameRenderer {
|
|
|
58
59
|
hudCanvas;
|
|
59
60
|
hudTexture;
|
|
60
61
|
hudKey = "";
|
|
62
|
+
hudRevision = 0;
|
|
61
63
|
disposed = false;
|
|
62
64
|
lost = false;
|
|
63
65
|
constructor(canvas, device, context, format, assets, adapterType = "unknown") {
|
|
@@ -173,7 +175,7 @@ export class WebGPUGameRenderer {
|
|
|
173
175
|
const dotTexture = device.createTexture({ size: [PARTICLE_DOT_SIZE, PARTICLE_DOT_SIZE], format: "rgba8unorm",
|
|
174
176
|
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST });
|
|
175
177
|
device.queue.writeTexture({ texture: dotTexture }, particleDotPixels(), { bytesPerRow: PARTICLE_DOT_SIZE * 4 }, [PARTICLE_DOT_SIZE, PARTICLE_DOT_SIZE]);
|
|
176
|
-
this.particleDot = this.textureEntry(dotTexture, PARTICLE_DOT_SIZE, PARTICLE_DOT_SIZE
|
|
178
|
+
this.particleDot = this.textureEntry(dotTexture, PARTICLE_DOT_SIZE, PARTICLE_DOT_SIZE);
|
|
177
179
|
void device.lost.then(() => { this.lost = true; });
|
|
178
180
|
}
|
|
179
181
|
get capabilities() {
|
|
@@ -290,22 +292,24 @@ export class WebGPUGameRenderer {
|
|
|
290
292
|
pass.end();
|
|
291
293
|
output.markWritten();
|
|
292
294
|
}
|
|
293
|
-
textureEntry(texture, width, height
|
|
295
|
+
textureEntry(texture, width, height) {
|
|
296
|
+
const view = texture.createView();
|
|
297
|
+
const bindGroup = (sampler) => this.device.createBindGroup({
|
|
298
|
+
layout: this.pipeline.getBindGroupLayout(0),
|
|
299
|
+
entries: [
|
|
300
|
+
{ binding: 0, resource: { buffer: this.cameraBuffer } },
|
|
301
|
+
{ binding: 1, resource: sampler },
|
|
302
|
+
{ binding: 2, resource: view },
|
|
303
|
+
],
|
|
304
|
+
});
|
|
294
305
|
return {
|
|
295
306
|
texture,
|
|
296
307
|
width,
|
|
297
308
|
height,
|
|
298
|
-
bindGroup: this.
|
|
299
|
-
layout: this.pipeline.getBindGroupLayout(0),
|
|
300
|
-
entries: [
|
|
301
|
-
{ binding: 0, resource: { buffer: this.cameraBuffer } },
|
|
302
|
-
{ binding: 1, resource: sampling === "linear" ? this.linearSampler : this.sampler },
|
|
303
|
-
{ binding: 2, resource: texture.createView() },
|
|
304
|
-
],
|
|
305
|
-
}),
|
|
309
|
+
bindGroups: { nearest: bindGroup(this.sampler), linear: bindGroup(this.linearSampler) },
|
|
306
310
|
};
|
|
307
311
|
}
|
|
308
|
-
upload(image
|
|
312
|
+
upload(image) {
|
|
309
313
|
const width = image instanceof HTMLCanvasElement ? image.width : imageWidth(image);
|
|
310
314
|
const height = image instanceof HTMLCanvasElement ? image.height : imageHeight(image);
|
|
311
315
|
if (width <= 0 || height <= 0) {
|
|
@@ -314,31 +318,97 @@ export class WebGPUGameRenderer {
|
|
|
314
318
|
// copyExternalImageToTexture requires RENDER_ATTACHMENT on the destination as well as COPY_DST.
|
|
315
319
|
const texture = this.device.createTexture({ size: [width, height], format: "rgba8unorm",
|
|
316
320
|
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT });
|
|
317
|
-
|
|
318
|
-
|
|
321
|
+
try {
|
|
322
|
+
this.device.queue.copyExternalImageToTexture({ source: image }, { texture, premultipliedAlpha: false }, [width, height]);
|
|
323
|
+
return this.textureEntry(texture, width, height);
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
texture.destroy();
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
319
329
|
}
|
|
320
|
-
|
|
321
|
-
if (
|
|
322
|
-
|
|
330
|
+
assertUsable() {
|
|
331
|
+
if (this.disposed || this.lost) {
|
|
332
|
+
throw new Error(this.lost ? "WebGPU device was lost" : "Game renderer is disposed");
|
|
323
333
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
334
|
+
}
|
|
335
|
+
assetGeneration(assetId, frame) {
|
|
336
|
+
this.assertUsable();
|
|
337
|
+
let generation = this.textures.get(assetId);
|
|
338
|
+
if (!generation) {
|
|
339
|
+
generation = { image: this.assets.get(assetId), readers: 0, retired: false, opaque: false };
|
|
340
|
+
this.textures.set(assetId, generation);
|
|
341
|
+
this.assetGenerations.add(generation);
|
|
342
|
+
}
|
|
343
|
+
if (!frame.generations.has(generation)) {
|
|
344
|
+
frame.generations.add(generation);
|
|
345
|
+
generation.readers++;
|
|
346
|
+
}
|
|
347
|
+
return generation;
|
|
348
|
+
}
|
|
349
|
+
retireGeneration(generation) {
|
|
350
|
+
if (!generation.retired || generation.readers !== 0) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (generation.entry && this.liveAssetTextures.delete(generation.entry)) {
|
|
354
|
+
generation.entry.texture.destroy();
|
|
355
|
+
}
|
|
356
|
+
generation.entry = undefined;
|
|
357
|
+
this.assetGenerations.delete(generation);
|
|
358
|
+
}
|
|
359
|
+
releaseFrameAssets(frame) {
|
|
360
|
+
for (const generation of frame.generations) {
|
|
361
|
+
generation.readers--;
|
|
362
|
+
this.retireGeneration(generation);
|
|
363
|
+
}
|
|
364
|
+
frame.generations.clear();
|
|
365
|
+
}
|
|
366
|
+
async uploadGeneration(assetId, generation, frame) {
|
|
367
|
+
const image = await generation.image;
|
|
368
|
+
this.assertUsable();
|
|
369
|
+
if (generation.retired && generation.readers === 0) {
|
|
370
|
+
throw new Error(`Asset ${assetId} was invalidated before upload`);
|
|
327
371
|
}
|
|
328
|
-
const image = await this.assets.get(assetId);
|
|
329
372
|
if (!image) {
|
|
330
|
-
return
|
|
373
|
+
return this.placeholder(assetId);
|
|
374
|
+
}
|
|
375
|
+
const texture = this.upload(image);
|
|
376
|
+
if (texture !== this.fallback) {
|
|
377
|
+
generation.entry = texture;
|
|
378
|
+
this.liveAssetTextures.add(texture);
|
|
379
|
+
frame.uploadedBytes += texture.width * texture.height * 4;
|
|
380
|
+
}
|
|
381
|
+
return texture;
|
|
382
|
+
}
|
|
383
|
+
generationTexture(assetId, generation, frame) {
|
|
384
|
+
if (!generation.upload) {
|
|
385
|
+
const upload = this.uploadGeneration(assetId, generation, frame);
|
|
386
|
+
generation.upload = upload;
|
|
387
|
+
void upload.catch(() => {
|
|
388
|
+
if (generation.upload === upload && !generation.entry) {
|
|
389
|
+
generation.upload = undefined;
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
return generation.upload;
|
|
394
|
+
}
|
|
395
|
+
async getTexture(assetId, frame) {
|
|
396
|
+
if (assetId === PARTICLE_DOT_ASSET) {
|
|
397
|
+
return this.particleDot;
|
|
331
398
|
}
|
|
332
|
-
const
|
|
333
|
-
this.
|
|
334
|
-
|
|
399
|
+
const generation = this.assetGeneration(assetId, frame);
|
|
400
|
+
const texture = await this.generationTexture(assetId, generation, frame);
|
|
401
|
+
this.assertUsable();
|
|
402
|
+
return texture;
|
|
335
403
|
}
|
|
336
|
-
async getLutTexture(assetId, size) {
|
|
337
|
-
const
|
|
404
|
+
async getLutTexture(assetId, size, frame) {
|
|
405
|
+
const generation = this.assetGeneration(assetId, frame);
|
|
406
|
+
const image = await generation.image;
|
|
407
|
+
this.assertUsable();
|
|
338
408
|
if (!image || imageWidth(image) !== size * size || imageHeight(image) !== size) {
|
|
339
409
|
throw new Error(`LUT ${assetId} is missing or has invalid dimensions`);
|
|
340
410
|
}
|
|
341
|
-
if (!
|
|
411
|
+
if (!generation.opaque) {
|
|
342
412
|
const canvas = document.createElement("canvas");
|
|
343
413
|
canvas.width = imageWidth(image);
|
|
344
414
|
canvas.height = imageHeight(image);
|
|
@@ -353,9 +423,10 @@ export class WebGPUGameRenderer {
|
|
|
353
423
|
throw new Error(`LUT ${assetId} must be opaque`);
|
|
354
424
|
}
|
|
355
425
|
}
|
|
356
|
-
|
|
426
|
+
generation.opaque = true;
|
|
357
427
|
}
|
|
358
|
-
const
|
|
428
|
+
const texture = await this.generationTexture(assetId, generation, frame);
|
|
429
|
+
this.assertUsable();
|
|
359
430
|
return new LabeledTexture(texture.texture, { label: `game-lut-${assetId}`, format: "rgba8unorm",
|
|
360
431
|
width: texture.width, height: texture.height,
|
|
361
432
|
meta: { colorSpace: "srgb", alpha: "premultiplied", bindingKind: "texture_2d" } });
|
|
@@ -402,15 +473,26 @@ export class WebGPUGameRenderer {
|
|
|
402
473
|
}
|
|
403
474
|
}
|
|
404
475
|
async renderFrame(frame, interpolation, particles) {
|
|
476
|
+
const assets = { generations: new Set(), uploadedBytes: 0 };
|
|
477
|
+
try {
|
|
478
|
+
return await this.renderAssetFrame(frame, interpolation, assets, particles);
|
|
479
|
+
}
|
|
480
|
+
finally {
|
|
481
|
+
this.releaseFrameAssets(assets);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
async renderAssetFrame(frame, interpolation, assets, particles) {
|
|
405
485
|
const bloomRadius = this.effects.reduce((maximum, effect) => effect.kind === "bloom" ? Math.max(maximum, effect.radius) : maximum, 0);
|
|
406
486
|
const overscanPixels = Math.ceil(bloomRadius * 2);
|
|
407
487
|
const overscanWorld = overscanPixels * frame.width / (Math.max(1, this.canvas.width) * projectedCamera(frame, interpolation).zoom);
|
|
408
488
|
const items = visibleItems(frame, interpolation, overscanWorld, particles && { field: particles });
|
|
409
|
-
const
|
|
410
|
-
const
|
|
411
|
-
const textureResults = await Promise.all(assetIds.map((assetId) => this.getTexture(assetId, sampling.get(assetId) ?? "nearest")));
|
|
489
|
+
const assetIds = [...new Set(items.map((item) => item.assetId))];
|
|
490
|
+
const textureResults = await Promise.all(assetIds.map((assetId) => this.getTexture(assetId, assets)));
|
|
412
491
|
const textureById = new Map(assetIds.map((assetId, index) => [assetId, textureResults[index]]));
|
|
413
|
-
const
|
|
492
|
+
const hudRevision = this.hudRevision;
|
|
493
|
+
const hudImages = await loadGameUiImages(frame, (assetId) => this.assets.get(assetId));
|
|
494
|
+
this.assertUsable();
|
|
495
|
+
const hudTexture = this.updateHud(frame, hudImages, hudRevision);
|
|
414
496
|
const count = items.length + (hudTexture ? 1 : 0);
|
|
415
497
|
this.ensureInstanceCapacity(count);
|
|
416
498
|
const instances = new Float32Array(count * INSTANCE_FLOATS);
|
|
@@ -421,7 +503,7 @@ export class WebGPUGameRenderer {
|
|
|
421
503
|
if (!item || !result) {
|
|
422
504
|
continue;
|
|
423
505
|
}
|
|
424
|
-
const texture = result
|
|
506
|
+
const texture = result;
|
|
425
507
|
const rect = texture.width === 1 && texture.height === 1 ? { x: 0, y: 0, width: 1, height: 1 } : sourceRect(item, texture.width, texture.height);
|
|
426
508
|
const tint = parseTint(item.tint);
|
|
427
509
|
const subRect = item.frame !== undefined || item.cell !== undefined;
|
|
@@ -432,13 +514,13 @@ export class WebGPUGameRenderer {
|
|
|
432
514
|
const du = (rect.width - 2 * insetX) / texture.width;
|
|
433
515
|
const dv = (rect.height - 2 * insetY) / texture.height;
|
|
434
516
|
writeInstance(instances, index, item.x, item.y, item.width, item.height, item.flipX ? u + du : u, item.flipY ? v + dv : v, item.flipX ? -du : du, item.flipY ? -dv : dv, tint[0], tint[1], tint[2], item.opacity, item.rotation, item.unlit ? 1 : 0);
|
|
435
|
-
appendBatch(batches, texture, item.blend, index);
|
|
517
|
+
appendBatch(batches, texture, item.blend, index, item.sampling);
|
|
436
518
|
}
|
|
437
519
|
if (hudTexture) {
|
|
438
520
|
const scale = frame.camera.zoom;
|
|
439
521
|
const projected = projectedCamera(frame, interpolation);
|
|
440
522
|
writeInstance(instances, items.length, projected.x, projected.y, frame.width / scale, frame.height / scale, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1);
|
|
441
|
-
appendBatch(batches, hudTexture, "normal", items.length);
|
|
523
|
+
appendBatch(batches, hudTexture, "normal", items.length, "nearest");
|
|
442
524
|
}
|
|
443
525
|
const projected = projectedCamera(frame, interpolation);
|
|
444
526
|
const camera = new Float32Array([projected.x, projected.y,
|
|
@@ -504,7 +586,7 @@ export class WebGPUGameRenderer {
|
|
|
504
586
|
? hasEffects ? this.effectAdditivePipeline : this.additivePipeline
|
|
505
587
|
: hasEffects ? this.effectSpritePipeline : this.pipeline);
|
|
506
588
|
}
|
|
507
|
-
pass.setBindGroup(0, batch.
|
|
589
|
+
pass.setBindGroup(0, batch.bindGroup);
|
|
508
590
|
pass.draw(6, batch.count, 0, batch.first);
|
|
509
591
|
}
|
|
510
592
|
}
|
|
@@ -518,7 +600,7 @@ export class WebGPUGameRenderer {
|
|
|
518
600
|
effectContext: this.effectContext, effectExecutor: this.effectExecutor,
|
|
519
601
|
effectRecipeRunner: this.effectRecipeRunner, effectRegistry: this.effectRegistry,
|
|
520
602
|
srgbTarget: this.srgbTarget, lutTarget: this.lutTarget, toSrgbPipeline: this.toSrgbPipeline,
|
|
521
|
-
toLinearPipeline: this.toLinearPipeline, getLutTexture: (id, size) => this.getLutTexture(id, size),
|
|
603
|
+
toLinearPipeline: this.toLinearPipeline, getLutTexture: (id, size) => this.getLutTexture(id, size, assets),
|
|
522
604
|
convertColor: (command, input, target, pipeline) => this.convertColor(command, input, target, pipeline) });
|
|
523
605
|
const presentGroup = this.device.createBindGroup({ layout: this.presentLayout, entries: [
|
|
524
606
|
{ binding: 0, resource: this.sampler }, { binding: 1, resource: source.createView() },
|
|
@@ -548,14 +630,16 @@ export class WebGPUGameRenderer {
|
|
|
548
630
|
}
|
|
549
631
|
}
|
|
550
632
|
});
|
|
633
|
+
this.assertUsable();
|
|
551
634
|
this.device.queue.submit([encoder.finish()]);
|
|
552
|
-
|
|
635
|
+
this.releaseFrameAssets(assets);
|
|
636
|
+
const textureBytes = [...this.liveAssetTextures, ...this.placeholders.values(), this.fallback, this.particleDot,
|
|
553
637
|
...(this.hudTexture ? [this.hudTexture] : [])].reduce((sum, entry) => sum + entry.width * entry.height * 4, 0);
|
|
554
638
|
return {
|
|
555
639
|
backend: this.backend,
|
|
556
640
|
visibleSprites: items.length,
|
|
557
641
|
drawCalls: batches.length + this.effects.reduce((sum, effect) => sum + (effect.kind === "bloom" ? 4 : effect.kind === "lut" ? 3 : 1), 0) + (hasEffects ? 1 : 0) + (sceneLighting ? 1 : 0),
|
|
558
|
-
uploadedBytes:
|
|
642
|
+
uploadedBytes: assets.uploadedBytes,
|
|
559
643
|
textureBytes,
|
|
560
644
|
targetBytes: (this.sourceTarget && this.effectTarget ? (this.sourceTarget.width * this.sourceTarget.height +
|
|
561
645
|
this.effectTarget.width * this.effectTarget.height + (this.effects.some((effect) => effect.kind === "bloom") ?
|
|
@@ -565,11 +649,12 @@ export class WebGPUGameRenderer {
|
|
|
565
649
|
instanceBufferBytes: this.instanceCapacity * INSTANCE_FLOATS * 4,
|
|
566
650
|
};
|
|
567
651
|
}
|
|
568
|
-
updateHud(frame, images) {
|
|
652
|
+
updateHud(frame, images, revision) {
|
|
569
653
|
if (frame.hud.length === 0 && !frame.ui) {
|
|
570
654
|
return undefined;
|
|
571
655
|
}
|
|
572
|
-
const key = JSON.stringify([this.canvas.width, this.canvas.height, frame.
|
|
656
|
+
const key = JSON.stringify([this.canvas.width, this.canvas.height, frame.gameId, frame.fonts,
|
|
657
|
+
frame.hud, frame.ui, [...images.keys()], revision]);
|
|
573
658
|
if (key !== this.hudKey) {
|
|
574
659
|
const canvas = this.hudCanvas ?? document.createElement("canvas");
|
|
575
660
|
paintWebGPUHud(canvas, this.canvas.width, this.canvas.height, frame, images);
|
|
@@ -605,11 +690,14 @@ export class WebGPUGameRenderer {
|
|
|
605
690
|
this.lutTarget = undefined;
|
|
606
691
|
}
|
|
607
692
|
invalidateAsset(assetId) {
|
|
693
|
+
this.hudRevision++;
|
|
608
694
|
this.assets.invalidate(assetId);
|
|
609
|
-
const
|
|
610
|
-
texture?.texture.destroy();
|
|
695
|
+
const generation = this.textures.get(assetId);
|
|
611
696
|
this.textures.delete(assetId);
|
|
612
|
-
|
|
697
|
+
if (generation) {
|
|
698
|
+
generation.retired = true;
|
|
699
|
+
this.retireGeneration(generation);
|
|
700
|
+
}
|
|
613
701
|
}
|
|
614
702
|
dispose() {
|
|
615
703
|
if (this.disposed) {
|
|
@@ -635,9 +723,14 @@ export class WebGPUGameRenderer {
|
|
|
635
723
|
entry.texture.destroy();
|
|
636
724
|
}
|
|
637
725
|
this.placeholders.clear();
|
|
638
|
-
for (const
|
|
726
|
+
for (const generation of this.assetGenerations) {
|
|
727
|
+
generation.retired = true;
|
|
728
|
+
}
|
|
729
|
+
for (const entry of this.liveAssetTextures) {
|
|
639
730
|
entry.texture.destroy();
|
|
640
731
|
}
|
|
732
|
+
this.liveAssetTextures.clear();
|
|
733
|
+
this.assetGenerations.clear();
|
|
641
734
|
this.textures.clear();
|
|
642
735
|
this.assets.clear();
|
|
643
736
|
this.context.unconfigure();
|