@aethervtt/shard-render 0.0.1
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/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/assets.d.ts +104 -0
- package/dist/assets.js +213 -0
- package/dist/atmosphere-model.d.ts +96 -0
- package/dist/atmosphere-model.js +370 -0
- package/dist/atmosphere-nodes.d.ts +55 -0
- package/dist/atmosphere-nodes.js +780 -0
- package/dist/atmosphere-plugin.d.ts +5 -0
- package/dist/atmosphere-plugin.js +42 -0
- package/dist/atmosphere-shaders.d.ts +7 -0
- package/dist/atmosphere-shaders.js +595 -0
- package/dist/atmosphere-state.d.ts +72 -0
- package/dist/atmosphere-state.js +18 -0
- package/dist/atmosphere.d.ts +191 -0
- package/dist/atmosphere.js +950 -0
- package/dist/camera.d.ts +83 -0
- package/dist/camera.js +163 -0
- package/dist/clusters.d.ts +68 -0
- package/dist/clusters.js +288 -0
- package/dist/culling-shaders.d.ts +2 -0
- package/dist/culling-shaders.js +203 -0
- package/dist/culling.d.ts +84 -0
- package/dist/culling.js +498 -0
- package/dist/debug-shaders.d.ts +3 -0
- package/dist/debug-shaders.js +207 -0
- package/dist/debug-views.d.ts +60 -0
- package/dist/debug-views.js +197 -0
- package/dist/deferred-plugin.d.ts +5 -0
- package/dist/deferred-plugin.js +20 -0
- package/dist/deferred-shaders.d.ts +2 -0
- package/dist/deferred-shaders.js +151 -0
- package/dist/deferred.d.ts +22 -0
- package/dist/deferred.js +296 -0
- package/dist/deform.d.ts +80 -0
- package/dist/deform.js +213 -0
- package/dist/describe-plugin.d.ts +5 -0
- package/dist/describe-plugin.js +19 -0
- package/dist/display-nodes.d.ts +5 -0
- package/dist/display-nodes.js +189 -0
- package/dist/dynamic-resolution.d.ts +47 -0
- package/dist/dynamic-resolution.js +191 -0
- package/dist/environment-plugin.d.ts +6 -0
- package/dist/environment-plugin.js +44 -0
- package/dist/environment-shaders.d.ts +2 -0
- package/dist/environment-shaders.js +260 -0
- package/dist/environment-state.d.ts +71 -0
- package/dist/environment-state.js +144 -0
- package/dist/environment.d.ts +70 -0
- package/dist/environment.js +529 -0
- package/dist/forward.d.ts +123 -0
- package/dist/forward.js +1216 -0
- package/dist/fxaa.d.ts +2 -0
- package/dist/fxaa.js +65 -0
- package/dist/gizmo-font.d.ts +21 -0
- package/dist/gizmo-font.js +132 -0
- package/dist/gizmos-plugin.d.ts +5 -0
- package/dist/gizmos-plugin.js +34 -0
- package/dist/gizmos.d.ts +156 -0
- package/dist/gizmos.js +772 -0
- package/dist/gpu-assets.d.ts +114 -0
- package/dist/gpu-assets.js +494 -0
- package/dist/graph.d.ts +204 -0
- package/dist/graph.js +417 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +540 -0
- package/dist/instances.d.ts +349 -0
- package/dist/instances.js +1335 -0
- package/dist/large-world.d.ts +13 -0
- package/dist/large-world.js +20 -0
- package/dist/lights.d.ts +160 -0
- package/dist/lights.js +468 -0
- package/dist/material-noise.d.ts +19 -0
- package/dist/material-noise.js +7 -0
- package/dist/material-pipelines.d.ts +46 -0
- package/dist/material-pipelines.js +166 -0
- package/dist/materials.d.ts +108 -0
- package/dist/materials.js +219 -0
- package/dist/noise.d.ts +76 -0
- package/dist/noise.js +286 -0
- package/dist/overlay-registry.d.ts +18 -0
- package/dist/overlay-registry.js +30 -0
- package/dist/overlays.d.ts +35 -0
- package/dist/overlays.js +344 -0
- package/dist/paths.d.ts +20 -0
- package/dist/paths.js +46 -0
- package/dist/picking-plugin.d.ts +2 -0
- package/dist/picking-plugin.js +20 -0
- package/dist/picking.d.ts +94 -0
- package/dist/picking.js +456 -0
- package/dist/pixel-perfect-plugin.d.ts +9 -0
- package/dist/pixel-perfect-plugin.js +135 -0
- package/dist/pixel-perfect.d.ts +27 -0
- package/dist/pixel-perfect.js +70 -0
- package/dist/plugin.d.ts +111 -0
- package/dist/plugin.js +182 -0
- package/dist/pool.d.ts +19 -0
- package/dist/pool.js +43 -0
- package/dist/post-common.d.ts +51 -0
- package/dist/post-common.js +148 -0
- package/dist/post-nodes.d.ts +39 -0
- package/dist/post-nodes.js +849 -0
- package/dist/post-plugin.d.ts +5 -0
- package/dist/post-plugin.js +26 -0
- package/dist/post-shaders.d.ts +8 -0
- package/dist/post-shaders.js +698 -0
- package/dist/post.d.ts +175 -0
- package/dist/post.js +453 -0
- package/dist/readback.d.ts +4 -0
- package/dist/readback.js +95 -0
- package/dist/render-scale.d.ts +53 -0
- package/dist/render-scale.js +63 -0
- package/dist/shaders.d.ts +122 -0
- package/dist/shaders.js +1168 -0
- package/dist/shadows.d.ts +119 -0
- package/dist/shadows.js +562 -0
- package/dist/skin-asset.d.ts +26 -0
- package/dist/skin-asset.js +25 -0
- package/dist/skinning-plugin.d.ts +5 -0
- package/dist/skinning-plugin.js +16 -0
- package/dist/skinning.d.ts +19 -0
- package/dist/skinning.js +343 -0
- package/dist/standard-fields.d.ts +67 -0
- package/dist/standard-fields.js +73 -0
- package/dist/standard.d.ts +7 -0
- package/dist/standard.js +37 -0
- package/dist/stats.d.ts +25 -0
- package/dist/stats.js +18 -0
- package/dist/target.d.ts +55 -0
- package/dist/target.js +70 -0
- package/dist/testing.d.ts +34 -0
- package/dist/testing.js +126 -0
- package/dist/timer.d.ts +28 -0
- package/dist/timer.js +136 -0
- package/dist/view.d.ts +117 -0
- package/dist/view.js +389 -0
- package/dist/visibility.d.ts +10 -0
- package/dist/visibility.js +84 -0
- package/package.json +49 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { type World } from '@aethervtt/shard-core';
|
|
2
|
+
import type { GpuContext } from '@aethervtt/shard-gpu';
|
|
3
|
+
import type { Mesh } from '@aethervtt/shard-mesh';
|
|
4
|
+
import { type Texture } from '@aethervtt/shard-texture';
|
|
5
|
+
import { type MaterialAsset } from './assets.js';
|
|
6
|
+
import type { MaterialType } from './materials.js';
|
|
7
|
+
import type { GpuMemoryData } from './stats.js';
|
|
8
|
+
export interface GpuMesh {
|
|
9
|
+
version: number;
|
|
10
|
+
generation: number;
|
|
11
|
+
positions: GPUBuffer;
|
|
12
|
+
normals: GPUBuffer;
|
|
13
|
+
uvs: GPUBuffer;
|
|
14
|
+
uvs1: GPUBuffer;
|
|
15
|
+
tangents: GPUBuffer;
|
|
16
|
+
indices: GPUBuffer | undefined;
|
|
17
|
+
indexFormat: GPUIndexFormat;
|
|
18
|
+
count: number;
|
|
19
|
+
/** The index buffer belongs to every GPU mesh sharing its array (`Mesh.gpu`): never destroyed here. */
|
|
20
|
+
sharedIndices: boolean;
|
|
21
|
+
/** The vertex buffers belong to another mesh (`Mesh.gpu` with `share`). */
|
|
22
|
+
sharedVertices: boolean;
|
|
23
|
+
/** Where this mesh's vertices start in its (shared) vertex buffers. */
|
|
24
|
+
baseVertex: number;
|
|
25
|
+
}
|
|
26
|
+
export interface GpuMaterial {
|
|
27
|
+
version: number;
|
|
28
|
+
generation: number;
|
|
29
|
+
/** The material type's version the GPU copy was built for. */
|
|
30
|
+
typeVersion: number;
|
|
31
|
+
type: MaterialType;
|
|
32
|
+
buffer: GPUBuffer;
|
|
33
|
+
textureBuffer: GPUBuffer;
|
|
34
|
+
/** The type's own uniform, when it has numeric fields. */
|
|
35
|
+
ownBuffer: GPUBuffer | undefined;
|
|
36
|
+
bindGroup: GPUBindGroup | undefined;
|
|
37
|
+
/** What the bind group was built from, to rebuild when a texture changes. */
|
|
38
|
+
bound: (GpuTexture | undefined)[];
|
|
39
|
+
ownBound: (GpuTexture | undefined)[];
|
|
40
|
+
}
|
|
41
|
+
export interface GpuTexture {
|
|
42
|
+
texture: GPUTexture;
|
|
43
|
+
linear: GPUTextureView;
|
|
44
|
+
srgb: GPUTextureView;
|
|
45
|
+
/** 2D-array views (the texture's own for arrays; one layer for plain 2D textures). */
|
|
46
|
+
arrayLinear: GPUTextureView | undefined;
|
|
47
|
+
arraySrgb: GPUTextureView | undefined;
|
|
48
|
+
version: number;
|
|
49
|
+
generation: number;
|
|
50
|
+
bytes: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* GPU copies of meshes, textures, and materials, shared by every pass that draws them (forward,
|
|
54
|
+
* shadows, G-buffer, previews). Each is uploaded on first use and again when its version changes,
|
|
55
|
+
* and rebuilt after a device loss.
|
|
56
|
+
*/
|
|
57
|
+
export declare class GpuAssets {
|
|
58
|
+
readonly meshes: Map<Mesh, GpuMesh>;
|
|
59
|
+
readonly materials: Map<MaterialAsset, GpuMaterial>;
|
|
60
|
+
readonly textures: Map<Texture, GpuTexture>;
|
|
61
|
+
private defaults;
|
|
62
|
+
private readonly samplers;
|
|
63
|
+
/** Textures scratch for the slot lookup (no per-frame allocation). */
|
|
64
|
+
private readonly slotTextures;
|
|
65
|
+
/** Guids reloading after device loss, so each is requested once. */
|
|
66
|
+
private readonly reloading;
|
|
67
|
+
/** Per frame: whether each material's textures are available. */
|
|
68
|
+
private readonly ready;
|
|
69
|
+
private readonly materialBytes;
|
|
70
|
+
private ownBytes;
|
|
71
|
+
/** The type's own textures for the material being resolved. */
|
|
72
|
+
private readonly ownTextures;
|
|
73
|
+
private readonly textureBytes;
|
|
74
|
+
private generation;
|
|
75
|
+
readonly gpu: GpuContext;
|
|
76
|
+
readonly memory: GpuMemoryData;
|
|
77
|
+
materialLayout: GPUBindGroupLayout;
|
|
78
|
+
constructor(gpu: GpuContext, memory: GpuMemoryData);
|
|
79
|
+
/** Call once per frame before drawing: rebuilds after device loss and resets readiness. */
|
|
80
|
+
beginFrame(): void;
|
|
81
|
+
mesh(mesh: Mesh): GpuMesh;
|
|
82
|
+
private destroyMesh;
|
|
83
|
+
/** Index buffers shared by GPU meshes, per index array. */
|
|
84
|
+
private readonly sharedIndexBuffers;
|
|
85
|
+
/**
|
|
86
|
+
* A GPU-written mesh (`Mesh.gpu`): storage + vertex buffers compute passes fill (positions,
|
|
87
|
+
* normals, uvs, uvs1, tangents; f32), and the index buffer every mesh with the same array shares.
|
|
88
|
+
*/
|
|
89
|
+
private gpuMesh;
|
|
90
|
+
/** Frees a mesh's GPU buffers now (they're made again if it's drawn later). */
|
|
91
|
+
releaseMesh(mesh: Mesh): void;
|
|
92
|
+
/** Uploads a texture (all levels) on first use and when its version changes. */
|
|
93
|
+
texture(texture: Texture): GpuTexture | undefined;
|
|
94
|
+
/** 1x1 white and flat-normal textures for empty slots. */
|
|
95
|
+
defaultTextures(): {
|
|
96
|
+
white: GpuTexture;
|
|
97
|
+
normal: GpuTexture;
|
|
98
|
+
};
|
|
99
|
+
sampler(wrap: string, filter: string): GPUSampler;
|
|
100
|
+
/**
|
|
101
|
+
* Whether a material's textures are available (once per material per frame). False when a
|
|
102
|
+
* referenced texture is loading or reloading after device loss: its draws wait.
|
|
103
|
+
*/
|
|
104
|
+
materialReady(world: World, material: MaterialAsset): boolean;
|
|
105
|
+
private resolveSlots;
|
|
106
|
+
/** Whether a texture can be uploaded; if its pixels are gone (device loss), reloads it. */
|
|
107
|
+
private available;
|
|
108
|
+
/** The group-1 layout of a material type on this device. */
|
|
109
|
+
layoutOf(type: MaterialType): GPUBindGroupLayout;
|
|
110
|
+
/** The material's uniforms and bind group, uploaded when its version or textures change. */
|
|
111
|
+
material(world: World, material: MaterialAsset): GpuMaterial | undefined;
|
|
112
|
+
}
|
|
113
|
+
export declare function createMaterialLayout(gpu: GpuContext): GPUBindGroupLayout;
|
|
114
|
+
export declare const GpuAssetsResource: import("@aethervtt/shard-core").ResourceDef<GpuAssets>;
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import { assetServer } from "@aethervtt/shard-assets";
|
|
2
|
+
import { defineResource } from "@aethervtt/shard-core";
|
|
3
|
+
import { FORMAT_INFO, Textures } from "@aethervtt/shard-texture";
|
|
4
|
+
import { STANDARD_TYPE, TEXTURE_SLOTS } from "./assets.js";
|
|
5
|
+
import { materialLayout } from "./shaders.js";
|
|
6
|
+
const SRGB_SLOT = [true, false, false, false, true];
|
|
7
|
+
class GpuAssets {
|
|
8
|
+
meshes = /* @__PURE__ */ new Map();
|
|
9
|
+
materials = /* @__PURE__ */ new Map();
|
|
10
|
+
textures = /* @__PURE__ */ new Map();
|
|
11
|
+
defaults;
|
|
12
|
+
samplers = /* @__PURE__ */ new Map();
|
|
13
|
+
/** Textures scratch for the slot lookup (no per-frame allocation). */
|
|
14
|
+
slotTextures = [
|
|
15
|
+
void 0,
|
|
16
|
+
void 0,
|
|
17
|
+
void 0,
|
|
18
|
+
void 0,
|
|
19
|
+
void 0
|
|
20
|
+
];
|
|
21
|
+
/** Guids reloading after device loss, so each is requested once. */
|
|
22
|
+
reloading = /* @__PURE__ */ new Set();
|
|
23
|
+
/** Per frame: whether each material's textures are available. */
|
|
24
|
+
ready = /* @__PURE__ */ new Map();
|
|
25
|
+
materialBytes = new DataView(new ArrayBuffer(materialLayout.size));
|
|
26
|
+
ownBytes = new DataView(new ArrayBuffer(256));
|
|
27
|
+
/** The type's own textures for the material being resolved. */
|
|
28
|
+
ownTextures = [];
|
|
29
|
+
textureBytes = new Float32Array(40);
|
|
30
|
+
generation;
|
|
31
|
+
gpu;
|
|
32
|
+
memory;
|
|
33
|
+
materialLayout;
|
|
34
|
+
constructor(gpu, memory) {
|
|
35
|
+
this.gpu = gpu;
|
|
36
|
+
this.memory = memory;
|
|
37
|
+
this.generation = gpu.generation;
|
|
38
|
+
this.materialLayout = createMaterialLayout(gpu);
|
|
39
|
+
}
|
|
40
|
+
/** Call once per frame before drawing: rebuilds after device loss and resets readiness. */
|
|
41
|
+
beginFrame() {
|
|
42
|
+
this.ready.clear();
|
|
43
|
+
if (this.generation === this.gpu.generation) return;
|
|
44
|
+
this.generation = this.gpu.generation;
|
|
45
|
+
this.materialLayout = createMaterialLayout(this.gpu);
|
|
46
|
+
this.textures.clear();
|
|
47
|
+
this.memory.textures = 0;
|
|
48
|
+
this.memory.textureBytes = 0;
|
|
49
|
+
this.defaults = void 0;
|
|
50
|
+
this.samplers.clear();
|
|
51
|
+
}
|
|
52
|
+
mesh(mesh) {
|
|
53
|
+
const gpu = this.gpu;
|
|
54
|
+
let gm = this.meshes.get(mesh);
|
|
55
|
+
if (gm && gm.version === mesh.version && gm.generation === gpu.generation) {
|
|
56
|
+
if (mesh.gpu) gm.count = mesh.drawCount;
|
|
57
|
+
return gm;
|
|
58
|
+
}
|
|
59
|
+
if (gm && gm.generation === gpu.generation) this.destroyMesh(gm);
|
|
60
|
+
if (mesh.gpu) return this.gpuMesh(mesh);
|
|
61
|
+
const device = gpu.device;
|
|
62
|
+
const n = mesh.vertexCount;
|
|
63
|
+
const upload = (data, usage, label) => {
|
|
64
|
+
const buffer = device.createBuffer({
|
|
65
|
+
label,
|
|
66
|
+
size: Math.max(16, data.byteLength + 3 & ~3),
|
|
67
|
+
usage: usage | GPUBufferUsage.COPY_DST
|
|
68
|
+
});
|
|
69
|
+
device.queue.writeBuffer(buffer, 0, data, 0, data.length);
|
|
70
|
+
return buffer;
|
|
71
|
+
};
|
|
72
|
+
const normals = mesh.normals ?? new Float32Array(n * 3).map((_, i) => i % 3 === 1 ? 1 : 0);
|
|
73
|
+
const uvs = mesh.uvs ?? new Float32Array(n * 2);
|
|
74
|
+
const uvs1 = mesh.uvs1 ?? uvs;
|
|
75
|
+
const tangents = mesh.tangents ?? new Float32Array(n * 4);
|
|
76
|
+
let indices = mesh.indices;
|
|
77
|
+
if (indices instanceof Uint16Array && indices.length % 2 === 1) {
|
|
78
|
+
const padded = new Uint16Array(indices.length + 1);
|
|
79
|
+
padded.set(indices);
|
|
80
|
+
indices = padded;
|
|
81
|
+
}
|
|
82
|
+
gm = {
|
|
83
|
+
version: mesh.version,
|
|
84
|
+
generation: gpu.generation,
|
|
85
|
+
positions: upload(mesh.positions, GPUBufferUsage.VERTEX, "mesh/positions"),
|
|
86
|
+
normals: upload(normals, GPUBufferUsage.VERTEX, "mesh/normals"),
|
|
87
|
+
uvs: upload(uvs, GPUBufferUsage.VERTEX, "mesh/uvs"),
|
|
88
|
+
uvs1: upload(uvs1, GPUBufferUsage.VERTEX, "mesh/uvs1"),
|
|
89
|
+
tangents: upload(tangents, GPUBufferUsage.VERTEX, "mesh/tangents"),
|
|
90
|
+
indices: indices ? upload(indices, GPUBufferUsage.INDEX, "mesh/indices") : void 0,
|
|
91
|
+
indexFormat: mesh.indices instanceof Uint32Array ? "uint32" : "uint16",
|
|
92
|
+
count: mesh.drawCount,
|
|
93
|
+
sharedIndices: false,
|
|
94
|
+
sharedVertices: false,
|
|
95
|
+
baseVertex: 0
|
|
96
|
+
};
|
|
97
|
+
this.meshes.set(mesh, gm);
|
|
98
|
+
return gm;
|
|
99
|
+
}
|
|
100
|
+
destroyMesh(gm) {
|
|
101
|
+
if (gm.sharedVertices) return;
|
|
102
|
+
gm.positions.destroy();
|
|
103
|
+
gm.normals.destroy();
|
|
104
|
+
gm.uvs.destroy();
|
|
105
|
+
gm.uvs1.destroy();
|
|
106
|
+
gm.tangents.destroy();
|
|
107
|
+
if (!gm.sharedIndices) gm.indices?.destroy();
|
|
108
|
+
}
|
|
109
|
+
/** Index buffers shared by GPU meshes, per index array. */
|
|
110
|
+
sharedIndexBuffers = /* @__PURE__ */ new WeakMap();
|
|
111
|
+
/**
|
|
112
|
+
* A GPU-written mesh (`Mesh.gpu`): storage + vertex buffers compute passes fill (positions,
|
|
113
|
+
* normals, uvs, uvs1, tangents; f32), and the index buffer every mesh with the same array shares.
|
|
114
|
+
*/
|
|
115
|
+
gpuMesh(mesh) {
|
|
116
|
+
const gpu = this.gpu;
|
|
117
|
+
const desc = mesh.gpu;
|
|
118
|
+
const n = desc.vertexCount;
|
|
119
|
+
const usage = GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC;
|
|
120
|
+
const make = (width, label) => gpu.device.createBuffer({ label, size: Math.max(16, n * width * 4), usage });
|
|
121
|
+
let shared = this.sharedIndexBuffers.get(desc.indices);
|
|
122
|
+
if (!shared || shared.generation !== gpu.generation) {
|
|
123
|
+
const data = desc.indices;
|
|
124
|
+
const bytes = data.byteLength + 3 & ~3;
|
|
125
|
+
const buffer = gpu.device.createBuffer({
|
|
126
|
+
label: "mesh/shared-indices",
|
|
127
|
+
size: Math.max(16, bytes),
|
|
128
|
+
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
|
|
129
|
+
});
|
|
130
|
+
if (data.byteLength % 4 === 0) gpu.device.queue.writeBuffer(buffer, 0, data, 0, data.length);
|
|
131
|
+
else {
|
|
132
|
+
const padded = new Uint16Array(data.length + 1);
|
|
133
|
+
padded.set(data);
|
|
134
|
+
gpu.device.queue.writeBuffer(buffer, 0, padded, 0, padded.length);
|
|
135
|
+
}
|
|
136
|
+
shared = { generation: gpu.generation, buffer };
|
|
137
|
+
this.sharedIndexBuffers.set(desc.indices, shared);
|
|
138
|
+
}
|
|
139
|
+
const source = desc.share ? this.mesh(desc.share) : void 0;
|
|
140
|
+
const gm = {
|
|
141
|
+
version: mesh.version,
|
|
142
|
+
generation: gpu.generation,
|
|
143
|
+
positions: source?.positions ?? make(3, "mesh/gpu-positions"),
|
|
144
|
+
normals: source?.normals ?? make(3, "mesh/gpu-normals"),
|
|
145
|
+
uvs: source?.uvs ?? make(2, "mesh/gpu-uvs"),
|
|
146
|
+
uvs1: source?.uvs1 ?? make(2, "mesh/gpu-uvs1"),
|
|
147
|
+
tangents: source?.tangents ?? make(4, "mesh/gpu-tangents"),
|
|
148
|
+
indices: shared.buffer,
|
|
149
|
+
indexFormat: desc.indices instanceof Uint32Array ? "uint32" : "uint16",
|
|
150
|
+
count: mesh.drawCount,
|
|
151
|
+
sharedIndices: true,
|
|
152
|
+
sharedVertices: source !== void 0,
|
|
153
|
+
baseVertex: mesh.baseVertex
|
|
154
|
+
};
|
|
155
|
+
this.meshes.set(mesh, gm);
|
|
156
|
+
return gm;
|
|
157
|
+
}
|
|
158
|
+
/** Frees a mesh's GPU buffers now (they're made again if it's drawn later). */
|
|
159
|
+
releaseMesh(mesh) {
|
|
160
|
+
const gm = this.meshes.get(mesh);
|
|
161
|
+
if (!gm) return;
|
|
162
|
+
if (gm.generation === this.gpu.generation) this.destroyMesh(gm);
|
|
163
|
+
this.meshes.delete(mesh);
|
|
164
|
+
}
|
|
165
|
+
/** Uploads a texture (all levels) on first use and when its version changes. */
|
|
166
|
+
texture(texture) {
|
|
167
|
+
const gpu = this.gpu;
|
|
168
|
+
const existing = this.textures.get(texture);
|
|
169
|
+
if (existing && existing.version === texture.version && existing.generation === gpu.generation) {
|
|
170
|
+
return existing;
|
|
171
|
+
}
|
|
172
|
+
if (!texture.levels && !texture.gpuOnly) return void 0;
|
|
173
|
+
if (existing) {
|
|
174
|
+
this.memory.textures--;
|
|
175
|
+
this.memory.textureBytes -= existing.bytes;
|
|
176
|
+
}
|
|
177
|
+
existing?.texture.destroy();
|
|
178
|
+
const info = FORMAT_INFO[texture.format];
|
|
179
|
+
const srgbView = texture.gpuOnly ? void 0 : info.srgbView;
|
|
180
|
+
const layers = texture.faces * texture.layers;
|
|
181
|
+
const handle = gpu.device.createTexture({
|
|
182
|
+
label: `texture/${texture.format}`,
|
|
183
|
+
size: { width: texture.width, height: texture.height, depthOrArrayLayers: layers },
|
|
184
|
+
format: texture.format,
|
|
185
|
+
mipLevelCount: texture.mipCount,
|
|
186
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | (texture.gpuOnly ? GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_SRC : 0),
|
|
187
|
+
viewFormats: srgbView ? [srgbView] : []
|
|
188
|
+
});
|
|
189
|
+
for (let level = 0; texture.levels && level < texture.mipCount; level++) {
|
|
190
|
+
const w = Math.max(1, texture.width >> level);
|
|
191
|
+
const h = Math.max(1, texture.height >> level);
|
|
192
|
+
const blocksWide = Math.ceil(w / info.block);
|
|
193
|
+
const blocksHigh = Math.ceil(h / info.block);
|
|
194
|
+
gpu.device.queue.writeTexture(
|
|
195
|
+
{ texture: handle, mipLevel: level },
|
|
196
|
+
texture.levels[level],
|
|
197
|
+
{ bytesPerRow: blocksWide * info.bytes, rowsPerImage: blocksHigh },
|
|
198
|
+
{
|
|
199
|
+
width: blocksWide * info.block,
|
|
200
|
+
height: blocksHigh * info.block,
|
|
201
|
+
depthOrArrayLayers: layers
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const dimension = texture.faces === 6 ? "cube" : texture.layers > 1 ? "2d-array" : "2d";
|
|
206
|
+
const linear = handle.createView({ dimension });
|
|
207
|
+
const srgb = srgbView ? handle.createView({ format: srgbView, dimension }) : linear;
|
|
208
|
+
const plain = texture.faces === 1;
|
|
209
|
+
const out = {
|
|
210
|
+
texture: handle,
|
|
211
|
+
linear,
|
|
212
|
+
srgb,
|
|
213
|
+
arrayLinear: !plain ? void 0 : dimension === "2d-array" ? linear : handle.createView({ dimension: "2d-array" }),
|
|
214
|
+
arraySrgb: !plain ? void 0 : dimension === "2d-array" ? srgb : srgbView ? handle.createView({
|
|
215
|
+
format: srgbView,
|
|
216
|
+
dimension: "2d-array"
|
|
217
|
+
}) : void 0,
|
|
218
|
+
version: texture.version,
|
|
219
|
+
generation: gpu.generation,
|
|
220
|
+
bytes: texture.byteSize
|
|
221
|
+
};
|
|
222
|
+
this.textures.set(texture, out);
|
|
223
|
+
this.memory.textures++;
|
|
224
|
+
this.memory.textureBytes += out.bytes;
|
|
225
|
+
if (!texture.keepCpu) texture.levels = void 0;
|
|
226
|
+
return out;
|
|
227
|
+
}
|
|
228
|
+
/** 1x1 white and flat-normal textures for empty slots. */
|
|
229
|
+
defaultTextures() {
|
|
230
|
+
const gpu = this.gpu;
|
|
231
|
+
if (this.defaults && this.defaults.white.generation === gpu.generation) return this.defaults;
|
|
232
|
+
const solid = (label, rgba) => {
|
|
233
|
+
const texture = gpu.device.createTexture({
|
|
234
|
+
label,
|
|
235
|
+
size: { width: 1, height: 1 },
|
|
236
|
+
format: "rgba8unorm",
|
|
237
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
|
|
238
|
+
viewFormats: ["rgba8unorm-srgb"]
|
|
239
|
+
});
|
|
240
|
+
gpu.device.queue.writeTexture({ texture }, new Uint8Array(rgba), { bytesPerRow: 4 }, [1, 1]);
|
|
241
|
+
const linear = texture.createView();
|
|
242
|
+
return {
|
|
243
|
+
texture,
|
|
244
|
+
linear,
|
|
245
|
+
srgb: texture.createView({ format: "rgba8unorm-srgb" }),
|
|
246
|
+
arrayLinear: texture.createView({ dimension: "2d-array" }),
|
|
247
|
+
arraySrgb: texture.createView({ format: "rgba8unorm-srgb", dimension: "2d-array" }),
|
|
248
|
+
version: 0,
|
|
249
|
+
generation: gpu.generation,
|
|
250
|
+
bytes: 4
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
this.defaults = {
|
|
254
|
+
white: solid("texture/white", [255, 255, 255, 255]),
|
|
255
|
+
normal: solid("texture/flat-normal", [128, 128, 255, 255])
|
|
256
|
+
};
|
|
257
|
+
this.samplers.clear();
|
|
258
|
+
return this.defaults;
|
|
259
|
+
}
|
|
260
|
+
sampler(wrap, filter) {
|
|
261
|
+
const key = `${wrap}|${filter}`;
|
|
262
|
+
let s = this.samplers.get(key);
|
|
263
|
+
if (!s) {
|
|
264
|
+
const address = wrap === "clamp" ? "clamp-to-edge" : wrap === "mirror" ? "mirror-repeat" : "repeat";
|
|
265
|
+
const linear = filter === "linear";
|
|
266
|
+
s = this.gpu.device.createSampler({
|
|
267
|
+
label: `sampler/${key}`,
|
|
268
|
+
addressModeU: address,
|
|
269
|
+
addressModeV: address,
|
|
270
|
+
magFilter: linear ? "linear" : "nearest",
|
|
271
|
+
minFilter: linear ? "linear" : "nearest",
|
|
272
|
+
mipmapFilter: linear ? "linear" : "nearest",
|
|
273
|
+
maxAnisotropy: linear ? 8 : 1
|
|
274
|
+
});
|
|
275
|
+
this.samplers.set(key, s);
|
|
276
|
+
}
|
|
277
|
+
return s;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Whether a material's textures are available (once per material per frame). False when a
|
|
281
|
+
* referenced texture is loading or reloading after device loss: its draws wait.
|
|
282
|
+
*/
|
|
283
|
+
materialReady(world, material) {
|
|
284
|
+
let ready = this.ready.get(material);
|
|
285
|
+
if (ready === void 0) {
|
|
286
|
+
ready = this.resolveSlots(world, material);
|
|
287
|
+
this.ready.set(material, ready);
|
|
288
|
+
}
|
|
289
|
+
return ready;
|
|
290
|
+
}
|
|
291
|
+
resolveSlots(world, material) {
|
|
292
|
+
const store = world.tryResource(Textures);
|
|
293
|
+
material.sync();
|
|
294
|
+
const type = material.type;
|
|
295
|
+
const own = type.textures;
|
|
296
|
+
this.ownTextures.length = own.length;
|
|
297
|
+
for (let i = 0; i < own.length; i++) {
|
|
298
|
+
const ref = material.value[own[i]];
|
|
299
|
+
if (!ref) {
|
|
300
|
+
this.ownTextures[i] = void 0;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const texture = store?.get(ref);
|
|
304
|
+
if (!texture || !this.available(world, texture, ref)) return false;
|
|
305
|
+
this.ownTextures[i] = texture;
|
|
306
|
+
}
|
|
307
|
+
if (!type.standard || !type.standardTextures) return true;
|
|
308
|
+
const value = material.value;
|
|
309
|
+
for (let i = 0; i < 5; i++) {
|
|
310
|
+
const ref = value[TEXTURE_SLOTS[i]].texture;
|
|
311
|
+
if (!ref) {
|
|
312
|
+
this.slotTextures[i] = void 0;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const texture = store?.get(ref);
|
|
316
|
+
if (!texture || !this.available(world, texture, ref)) return false;
|
|
317
|
+
this.slotTextures[i] = texture;
|
|
318
|
+
}
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
/** Whether a texture can be uploaded; if its pixels are gone (device loss), reloads it. */
|
|
322
|
+
available(world, texture, ref) {
|
|
323
|
+
if (texture.levels || this.textures.has(texture)) return true;
|
|
324
|
+
if (ref.guid && !this.reloading.has(ref.guid)) {
|
|
325
|
+
this.reloading.add(ref.guid);
|
|
326
|
+
const guid = ref.guid;
|
|
327
|
+
void assetServer(world).reload(guid).finally(() => this.reloading.delete(guid));
|
|
328
|
+
}
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
/** The group-1 layout of a material type on this device. */
|
|
332
|
+
layoutOf(type) {
|
|
333
|
+
return type.bindGroupLayout(this.gpu, standardEntries());
|
|
334
|
+
}
|
|
335
|
+
/** The material's uniforms and bind group, uploaded when its version or textures change. */
|
|
336
|
+
material(world, material) {
|
|
337
|
+
const gpu = this.gpu;
|
|
338
|
+
if (!this.resolveSlots(world, material)) return void 0;
|
|
339
|
+
const type = material.type;
|
|
340
|
+
let gm = this.materials.get(material);
|
|
341
|
+
if (!gm || gm.generation !== gpu.generation || gm.type !== type || gm.typeVersion !== type.version) {
|
|
342
|
+
gm = {
|
|
343
|
+
version: -1,
|
|
344
|
+
generation: gpu.generation,
|
|
345
|
+
typeVersion: type.version,
|
|
346
|
+
type,
|
|
347
|
+
buffer: gpu.device.createBuffer({
|
|
348
|
+
label: "material/standard",
|
|
349
|
+
size: materialLayout.size,
|
|
350
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
351
|
+
}),
|
|
352
|
+
textureBuffer: gpu.device.createBuffer({
|
|
353
|
+
label: "material/textures",
|
|
354
|
+
size: 160,
|
|
355
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
356
|
+
}),
|
|
357
|
+
ownBuffer: type.layout ? gpu.device.createBuffer({
|
|
358
|
+
label: `material/${type.name}`,
|
|
359
|
+
size: type.layout.size,
|
|
360
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
361
|
+
}) : void 0,
|
|
362
|
+
bindGroup: void 0,
|
|
363
|
+
bound: [void 0, void 0, void 0, void 0, void 0],
|
|
364
|
+
ownBound: []
|
|
365
|
+
};
|
|
366
|
+
this.materials.set(material, gm);
|
|
367
|
+
}
|
|
368
|
+
const defaults = this.defaultTextures();
|
|
369
|
+
let rebuild = gm.bindGroup === void 0;
|
|
370
|
+
if (type.standard && type.standardTextures) {
|
|
371
|
+
for (let i = 0; i < 5; i++) {
|
|
372
|
+
const texture = this.slotTextures[i];
|
|
373
|
+
const g = texture ? this.texture(texture) : void 0;
|
|
374
|
+
if (texture && !g) return void 0;
|
|
375
|
+
if (gm.bound[i] !== g) {
|
|
376
|
+
gm.bound[i] = g;
|
|
377
|
+
rebuild = true;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
for (let i = 0; i < this.ownTextures.length; i++) {
|
|
382
|
+
const texture = this.ownTextures[i];
|
|
383
|
+
const g = texture ? this.texture(texture) : void 0;
|
|
384
|
+
if (texture && !g) return void 0;
|
|
385
|
+
if (gm.ownBound[i] !== g) {
|
|
386
|
+
gm.ownBound[i] = g;
|
|
387
|
+
rebuild = true;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (gm.version !== material.version) {
|
|
391
|
+
const value = material.value;
|
|
392
|
+
if (type.standard) {
|
|
393
|
+
materialLayout.write(this.materialBytes, 0, material.value);
|
|
394
|
+
gpu.device.queue.writeBuffer(
|
|
395
|
+
gm.buffer,
|
|
396
|
+
0,
|
|
397
|
+
this.materialBytes.buffer,
|
|
398
|
+
0,
|
|
399
|
+
materialLayout.size
|
|
400
|
+
);
|
|
401
|
+
const t = this.textureBytes;
|
|
402
|
+
for (let i = 0; i < 5; i++) {
|
|
403
|
+
const slot = value[TEXTURE_SLOTS[i]];
|
|
404
|
+
t[i * 8] = slot.offset[0];
|
|
405
|
+
t[i * 8 + 1] = slot.offset[1];
|
|
406
|
+
t[i * 8 + 2] = slot.scale[0];
|
|
407
|
+
t[i * 8 + 3] = slot.scale[1];
|
|
408
|
+
t[i * 8 + 4] = slot.rotation;
|
|
409
|
+
t[i * 8 + 5] = slot.uv;
|
|
410
|
+
t[i * 8 + 6] = slot.texture ? 1 : 0;
|
|
411
|
+
t[i * 8 + 7] = 0;
|
|
412
|
+
}
|
|
413
|
+
gpu.device.queue.writeBuffer(gm.textureBuffer, 0, t.buffer, 0, 160);
|
|
414
|
+
}
|
|
415
|
+
if (type.layout && gm.ownBuffer) {
|
|
416
|
+
if (this.ownBytes.byteLength < type.layout.size)
|
|
417
|
+
this.ownBytes = new DataView(new ArrayBuffer(type.layout.size));
|
|
418
|
+
type.layout.write(this.ownBytes, 0, material.value);
|
|
419
|
+
gpu.device.queue.writeBuffer(gm.ownBuffer, 0, this.ownBytes.buffer, 0, type.layout.size);
|
|
420
|
+
}
|
|
421
|
+
gm.version = material.version;
|
|
422
|
+
rebuild = true;
|
|
423
|
+
}
|
|
424
|
+
if (rebuild) {
|
|
425
|
+
const value = material.value;
|
|
426
|
+
const entries = [];
|
|
427
|
+
if (type.standard) {
|
|
428
|
+
entries.push(
|
|
429
|
+
{ binding: 0, resource: { buffer: gm.buffer } },
|
|
430
|
+
{ binding: 1, resource: { buffer: gm.textureBuffer } }
|
|
431
|
+
);
|
|
432
|
+
for (let i = 0; type.standardTextures && i < 5; i++) {
|
|
433
|
+
const g = gm.bound[i] ?? (i === 2 ? defaults.normal : defaults.white);
|
|
434
|
+
entries.push({ binding: 2 + i, resource: SRGB_SLOT[i] ? g.srgb : g.linear });
|
|
435
|
+
const slot = value[TEXTURE_SLOTS[i]];
|
|
436
|
+
entries.push({ binding: 7 + i, resource: this.sampler(slot.wrap, slot.filter) });
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
let binding = type.bindingBase;
|
|
440
|
+
if (gm.ownBuffer) entries.push({ binding, resource: { buffer: gm.ownBuffer } });
|
|
441
|
+
binding++;
|
|
442
|
+
for (let i = 0; i < type.textures.length; i++) {
|
|
443
|
+
const name = type.textures[i];
|
|
444
|
+
if (type.arrays.has(name)) {
|
|
445
|
+
const texture = this.ownTextures[i];
|
|
446
|
+
const g = gm.ownBound[i] ?? (name.toLowerCase().includes("normal") ? defaults.normal : defaults.white);
|
|
447
|
+
const color = texture ? texture.usage === "color" : false;
|
|
448
|
+
entries.push({
|
|
449
|
+
binding: binding++,
|
|
450
|
+
resource: (color ? g.arraySrgb ?? g.arrayLinear : g.arrayLinear) ?? g.linear
|
|
451
|
+
});
|
|
452
|
+
} else {
|
|
453
|
+
const g = gm.ownBound[i] ?? defaults.white;
|
|
454
|
+
entries.push({ binding: binding++, resource: g.linear });
|
|
455
|
+
}
|
|
456
|
+
entries.push({ binding: binding++, resource: this.sampler("repeat", "linear") });
|
|
457
|
+
}
|
|
458
|
+
gm.bindGroup = gpu.device.createBindGroup({
|
|
459
|
+
label: `material/${type.name}`,
|
|
460
|
+
layout: this.layoutOf(type),
|
|
461
|
+
entries
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
return gm;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
function standardEntries() {
|
|
468
|
+
const visibility = GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT;
|
|
469
|
+
return [
|
|
470
|
+
{ binding: 0, visibility, buffer: { type: "uniform" } },
|
|
471
|
+
{ binding: 1, visibility, buffer: { type: "uniform" } },
|
|
472
|
+
...[2, 3, 4, 5, 6].map((binding) => ({
|
|
473
|
+
binding,
|
|
474
|
+
visibility,
|
|
475
|
+
texture: { sampleType: "float" }
|
|
476
|
+
})),
|
|
477
|
+
...[7, 8, 9, 10, 11].map((binding) => ({
|
|
478
|
+
binding,
|
|
479
|
+
visibility,
|
|
480
|
+
sampler: { type: "filtering" }
|
|
481
|
+
}))
|
|
482
|
+
];
|
|
483
|
+
}
|
|
484
|
+
function createMaterialLayout(gpu) {
|
|
485
|
+
return STANDARD_TYPE.bindGroupLayout(gpu, standardEntries());
|
|
486
|
+
}
|
|
487
|
+
const GpuAssetsResource = defineResource("render/GpuAssets", {
|
|
488
|
+
description: "GPU copies of meshes, textures, and materials."
|
|
489
|
+
});
|
|
490
|
+
export {
|
|
491
|
+
GpuAssets,
|
|
492
|
+
GpuAssetsResource,
|
|
493
|
+
createMaterialLayout
|
|
494
|
+
};
|