@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
package/dist/culling.js
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import { defineResource } from "@aethervtt/shard-core";
|
|
2
|
+
import { GpuBuffer } from "@aethervtt/shard-gpu";
|
|
3
|
+
import { readBuffer } from "./debug-views.js";
|
|
4
|
+
import {
|
|
5
|
+
InstanceFlags,
|
|
6
|
+
LOD_UNSET,
|
|
7
|
+
SLOT_MASK
|
|
8
|
+
} from "./instances.js";
|
|
9
|
+
import { Shaders } from "./plugin.js";
|
|
10
|
+
const ARGS_WORDS = 5;
|
|
11
|
+
const VIEW_FLOATS = 32;
|
|
12
|
+
const BATCH_WORDS = 8;
|
|
13
|
+
const LOD_WORDS = 20;
|
|
14
|
+
const VIEW_CASTERS = 1;
|
|
15
|
+
const VIEW_UPDATE_LOD = 2;
|
|
16
|
+
const VIEW_ORTHO = 4;
|
|
17
|
+
const VIEW_NO_PLANES = 8;
|
|
18
|
+
const BATCH_TRANSPARENT = 1;
|
|
19
|
+
const BATCH_NOT_READY = 2;
|
|
20
|
+
class GpuCuller {
|
|
21
|
+
supported;
|
|
22
|
+
/** Turn off to force CPU culling (tests, debugging). */
|
|
23
|
+
enabled = true;
|
|
24
|
+
views = [];
|
|
25
|
+
viewCount = 0;
|
|
26
|
+
cameraViews = 0;
|
|
27
|
+
/** LOD camera index per camera entity, stable while the camera lives. */
|
|
28
|
+
lodCameras = /* @__PURE__ */ new Map();
|
|
29
|
+
viewData = new Float32Array(VIEW_FLOATS * 16);
|
|
30
|
+
viewU32 = new Uint32Array(this.viewData.buffer);
|
|
31
|
+
batchData = new Uint32Array(BATCH_WORDS * 64);
|
|
32
|
+
batchF32 = new Float32Array(this.batchData.buffer);
|
|
33
|
+
lodData = new Uint32Array(LOD_WORDS * 16);
|
|
34
|
+
lodF32 = new Float32Array(this.lodData.buffer);
|
|
35
|
+
params = new Uint32Array(8);
|
|
36
|
+
viewBuffer;
|
|
37
|
+
batchBuffer;
|
|
38
|
+
lodBuffer;
|
|
39
|
+
args;
|
|
40
|
+
visible;
|
|
41
|
+
lodState;
|
|
42
|
+
paramBuffers = [];
|
|
43
|
+
/** Instances a view's region can hold: the sum of batch capacities. */
|
|
44
|
+
regionSize = 0;
|
|
45
|
+
batchCount = 0;
|
|
46
|
+
lodStateCapacity = 0;
|
|
47
|
+
gpu;
|
|
48
|
+
bindGroups;
|
|
49
|
+
readbacks = [];
|
|
50
|
+
/** Visible instances per DrawList, from the last readback (a frame or two late). */
|
|
51
|
+
counts = /* @__PURE__ */ new WeakMap();
|
|
52
|
+
/** Per-LOD-level instance counts per DrawList, from the last readback. */
|
|
53
|
+
lodCounts = /* @__PURE__ */ new WeakMap();
|
|
54
|
+
/** Batches that drew at least one instance, per DrawList, from the last readback. */
|
|
55
|
+
drawCounts = /* @__PURE__ */ new WeakMap();
|
|
56
|
+
/** Instances per batch index, per DrawList, from the last readback. */
|
|
57
|
+
batchCounts = /* @__PURE__ */ new WeakMap();
|
|
58
|
+
frameViews = [];
|
|
59
|
+
pending = /* @__PURE__ */ new Set();
|
|
60
|
+
constructor(gpu) {
|
|
61
|
+
this.gpu = gpu;
|
|
62
|
+
this.supported = gpu.features.has("indirect-first-instance");
|
|
63
|
+
const storage = GPUBufferUsage.STORAGE;
|
|
64
|
+
this.viewBuffer = new GpuBuffer(gpu, { label: "cull/views", usage: storage, size: 4096 });
|
|
65
|
+
this.batchBuffer = new GpuBuffer(gpu, { label: "cull/batches", usage: storage, size: 2048 });
|
|
66
|
+
this.lodBuffer = new GpuBuffer(gpu, { label: "cull/lod-sets", usage: storage, size: 1280 });
|
|
67
|
+
this.args = new GpuBuffer(gpu, {
|
|
68
|
+
label: "cull/args",
|
|
69
|
+
usage: storage | GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_SRC,
|
|
70
|
+
size: 4096
|
|
71
|
+
});
|
|
72
|
+
this.visible = new GpuBuffer(gpu, {
|
|
73
|
+
label: "cull/visible",
|
|
74
|
+
usage: storage | GPUBufferUsage.COPY_SRC,
|
|
75
|
+
size: 4096
|
|
76
|
+
});
|
|
77
|
+
this.lodState = new GpuBuffer(gpu, { label: "cull/lod-state", usage: storage, size: 4096 });
|
|
78
|
+
}
|
|
79
|
+
get active() {
|
|
80
|
+
return this.supported && this.enabled;
|
|
81
|
+
}
|
|
82
|
+
beginFrame() {
|
|
83
|
+
this.viewCount = 0;
|
|
84
|
+
this.cameraViews = 0;
|
|
85
|
+
}
|
|
86
|
+
/** The LOD state row of a camera (cameras choose; their shadow views reuse the choice). */
|
|
87
|
+
lodCamera(entity) {
|
|
88
|
+
let i = this.lodCameras.get(entity);
|
|
89
|
+
if (i === void 0) {
|
|
90
|
+
i = this.lodCameras.size;
|
|
91
|
+
this.lodCameras.set(entity, i);
|
|
92
|
+
}
|
|
93
|
+
return i;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Adds a view to this frame's cull. Camera views must come before shadow views (they choose LOD
|
|
97
|
+
* levels). The list gets one indirect draw per live batch; deferred views split non-deferrable
|
|
98
|
+
* batches into `forwardOnly`.
|
|
99
|
+
*/
|
|
100
|
+
add(store, list, params, lodCamera, forwardOnly) {
|
|
101
|
+
const v = this.viewCount++;
|
|
102
|
+
let view = this.views[v];
|
|
103
|
+
if (!view) {
|
|
104
|
+
view = {
|
|
105
|
+
list,
|
|
106
|
+
forwardOnly,
|
|
107
|
+
planes: new Float32Array(24),
|
|
108
|
+
hasPlanes: false,
|
|
109
|
+
eye: new Float32Array(3),
|
|
110
|
+
hasEye: false,
|
|
111
|
+
lodScale: 1,
|
|
112
|
+
flags: 0,
|
|
113
|
+
lodCamera: 0
|
|
114
|
+
};
|
|
115
|
+
this.views.push(view);
|
|
116
|
+
}
|
|
117
|
+
view.list = list;
|
|
118
|
+
view.forwardOnly = forwardOnly;
|
|
119
|
+
view.hasPlanes = params.planes !== null;
|
|
120
|
+
if (params.planes) view.planes.set(params.planes);
|
|
121
|
+
view.hasEye = params.eye !== void 0;
|
|
122
|
+
if (params.eye) view.eye.set(params.eye);
|
|
123
|
+
view.lodScale = params.lodScale;
|
|
124
|
+
view.lodCamera = lodCamera;
|
|
125
|
+
view.flags = (params.require & InstanceFlags.Caster ? VIEW_CASTERS : 0) | (params.updateLod ? VIEW_UPDATE_LOD : 0) | (params.orthographic ? VIEW_ORTHO : 0) | (params.planes ? 0 : VIEW_NO_PLANES);
|
|
126
|
+
if (params.updateLod) this.cameraViews = v + 1;
|
|
127
|
+
list.cullView = v;
|
|
128
|
+
list.culled = 0;
|
|
129
|
+
list.hidden = 0;
|
|
130
|
+
list.pending = store.pending.size;
|
|
131
|
+
list.visible = this.counts.get(list) ?? 0;
|
|
132
|
+
if (forwardOnly) {
|
|
133
|
+
forwardOnly.cullView = v;
|
|
134
|
+
forwardOnly.visible = this.counts.get(forwardOnly) ?? 0;
|
|
135
|
+
}
|
|
136
|
+
let n = 0;
|
|
137
|
+
let m = 0;
|
|
138
|
+
for (const batch of store.sorted) {
|
|
139
|
+
if (batch.count === 0 || batch.transparent || !store.anyVisible(batch)) continue;
|
|
140
|
+
if (!batch.ready) {
|
|
141
|
+
list.pending += batch.count;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const target = forwardOnly && !batch.deferrable ? forwardOnly : list;
|
|
145
|
+
const k = target === list ? n++ : m++;
|
|
146
|
+
let item = target.items[k];
|
|
147
|
+
if (!item) {
|
|
148
|
+
item = { batch, first: 0, count: 0, indirect: 0 };
|
|
149
|
+
target.items.push(item);
|
|
150
|
+
}
|
|
151
|
+
item.batch = batch;
|
|
152
|
+
item.first = 0;
|
|
153
|
+
item.count = 0;
|
|
154
|
+
item.indirect = -2 - batch.index;
|
|
155
|
+
}
|
|
156
|
+
list.length = n;
|
|
157
|
+
if (forwardOnly) forwardOnly.length = m;
|
|
158
|
+
return v;
|
|
159
|
+
}
|
|
160
|
+
/** Uploads the frame's view, batch, and LOD tables, and sizes the buffers. */
|
|
161
|
+
prepare(store) {
|
|
162
|
+
if (this.viewCount === 0) return;
|
|
163
|
+
const batches = store.batches;
|
|
164
|
+
this.batchCount = batches.length;
|
|
165
|
+
if (this.batchData.length < batches.length * BATCH_WORDS) {
|
|
166
|
+
this.batchData = new Uint32Array(batches.length * BATCH_WORDS * 2);
|
|
167
|
+
this.batchF32 = new Float32Array(this.batchData.buffer);
|
|
168
|
+
}
|
|
169
|
+
let region = 0;
|
|
170
|
+
for (const batch of batches) {
|
|
171
|
+
batch.region = region;
|
|
172
|
+
region += batch.count;
|
|
173
|
+
const o = batch.index * BATCH_WORDS;
|
|
174
|
+
const b = batch.mesh.bounds;
|
|
175
|
+
this.batchF32[o] = (b[0] + b[3]) * 0.5;
|
|
176
|
+
this.batchF32[o + 1] = (b[1] + b[4]) * 0.5;
|
|
177
|
+
this.batchF32[o + 2] = (b[2] + b[5]) * 0.5;
|
|
178
|
+
const ex = (b[3] - b[0]) * 0.5;
|
|
179
|
+
const ey = (b[4] - b[1]) * 0.5;
|
|
180
|
+
const ez = (b[5] - b[2]) * 0.5;
|
|
181
|
+
this.batchF32[o + 3] = Math.fround(Math.sqrt(ex * ex + ey * ey + ez * ez));
|
|
182
|
+
this.batchData[o + 4] = batch.mesh.drawCount;
|
|
183
|
+
this.batchData[o + 5] = batch.mesh.indexed ? 1 + batch.mesh.baseVertex : 0;
|
|
184
|
+
this.batchData[o + 6] = batch.region;
|
|
185
|
+
this.batchData[o + 7] = (batch.transparent ? BATCH_TRANSPARENT : 0) | (batch.ready ? 0 : BATCH_NOT_READY);
|
|
186
|
+
}
|
|
187
|
+
this.regionSize = Math.max(1, region);
|
|
188
|
+
this.batchBuffer.write(this.batchData, 0, 0, Math.max(1, batches.length) * BATCH_WORDS);
|
|
189
|
+
const sets = store.lodSets;
|
|
190
|
+
if (this.lodData.length < Math.max(1, sets.length) * LOD_WORDS) {
|
|
191
|
+
this.lodData = new Uint32Array(sets.length * LOD_WORDS * 2);
|
|
192
|
+
this.lodF32 = new Float32Array(this.lodData.buffer);
|
|
193
|
+
}
|
|
194
|
+
for (const set of sets) {
|
|
195
|
+
const o = set.index * LOD_WORDS;
|
|
196
|
+
this.lodData[o] = set.batches.length;
|
|
197
|
+
this.lodF32[o + 1] = set.hysteresis;
|
|
198
|
+
this.lodF32[o + 2] = set.bias;
|
|
199
|
+
this.lodData[o + 3] = 0;
|
|
200
|
+
for (let k = 0; k < 8; k++) {
|
|
201
|
+
this.lodF32[o + 4 + k] = k < set.thresholds.length ? set.thresholds[k] : 0;
|
|
202
|
+
this.lodData[o + 12 + k] = k < set.batches.length ? set.batches[k] : 0;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this.lodBuffer.write(this.lodData, 0, 0, Math.max(1, sets.length) * LOD_WORDS);
|
|
206
|
+
if (this.viewData.length < this.viewCount * VIEW_FLOATS) {
|
|
207
|
+
this.viewData = new Float32Array(this.viewCount * VIEW_FLOATS * 2);
|
|
208
|
+
this.viewU32 = new Uint32Array(this.viewData.buffer);
|
|
209
|
+
}
|
|
210
|
+
for (let v = 0; v < this.viewCount; v++) {
|
|
211
|
+
const view = this.views[v];
|
|
212
|
+
const o = v * VIEW_FLOATS;
|
|
213
|
+
if (view.hasPlanes) this.viewData.set(view.planes, o);
|
|
214
|
+
else this.viewData.fill(0, o, o + 24);
|
|
215
|
+
this.viewData[o + 24] = view.eye[0];
|
|
216
|
+
this.viewData[o + 25] = view.eye[1];
|
|
217
|
+
this.viewData[o + 26] = view.eye[2];
|
|
218
|
+
this.viewData[o + 27] = view.lodScale;
|
|
219
|
+
this.viewU32[o + 28] = view.flags | (view.hasEye ? 0 : 16);
|
|
220
|
+
this.viewU32[o + 29] = view.lodCamera;
|
|
221
|
+
this.viewU32[o + 30] = v * batches.length;
|
|
222
|
+
this.viewU32[o + 31] = v * this.regionSize;
|
|
223
|
+
for (const list of [view.list, view.forwardOnly]) {
|
|
224
|
+
if (!list) continue;
|
|
225
|
+
for (let d = 0; d < list.length; d++) {
|
|
226
|
+
const item = list.items[d];
|
|
227
|
+
item.indirect = (v * batches.length + item.batch.index) * ARGS_WORDS * 4;
|
|
228
|
+
item.first = v * this.regionSize + item.batch.region;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
this.viewBuffer.write(this.viewData, 0, 0, this.viewCount * VIEW_FLOATS);
|
|
233
|
+
this.args.ensureCapacity(Math.max(1, this.viewCount * batches.length) * ARGS_WORDS * 4);
|
|
234
|
+
this.visible.ensureCapacity(this.viewCount * this.regionSize * 4);
|
|
235
|
+
const lodCams = Math.max(1, this.lodCameras.size);
|
|
236
|
+
if (this.lodStateCapacity !== store.capacity * lodCams) {
|
|
237
|
+
this.lodStateCapacity = store.capacity * lodCams;
|
|
238
|
+
const fill = new Uint32Array(this.lodStateCapacity).fill(LOD_UNSET);
|
|
239
|
+
this.lodState.write(fill);
|
|
240
|
+
}
|
|
241
|
+
this.frameViews = this.views.slice(0, this.viewCount);
|
|
242
|
+
this.slotCount = store.high;
|
|
243
|
+
this.slotCapacity = store.capacity;
|
|
244
|
+
}
|
|
245
|
+
slotCount = 0;
|
|
246
|
+
slotCapacity = 0;
|
|
247
|
+
/** Encodes the cull: reset arguments, cull camera views, then shadow views. */
|
|
248
|
+
encode(ctx, store) {
|
|
249
|
+
if (this.viewCount === 0) return;
|
|
250
|
+
const gpu = ctx.gpu;
|
|
251
|
+
const world = ctx.world;
|
|
252
|
+
const module = world.resource(Shaders).module(gpu, { root: "shard::cull" });
|
|
253
|
+
if (!module) {
|
|
254
|
+
gpu.pipelines.skipped++;
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const layout = cullLayout(gpu);
|
|
258
|
+
const pipelineLayout = gpu.layouts.pipelineLayout({ label: "cull", bindGroupLayouts: [layout] });
|
|
259
|
+
const reset = gpu.pipelines.compute({
|
|
260
|
+
label: "cull/reset",
|
|
261
|
+
layout: pipelineLayout,
|
|
262
|
+
compute: { module, entryPoint: "reset" }
|
|
263
|
+
});
|
|
264
|
+
const cull = gpu.pipelines.compute({
|
|
265
|
+
label: "cull/instances",
|
|
266
|
+
layout: pipelineLayout,
|
|
267
|
+
compute: { module, entryPoint: "cull" }
|
|
268
|
+
});
|
|
269
|
+
if (!reset || !cull) return;
|
|
270
|
+
const passes = [
|
|
271
|
+
[0, this.viewCount],
|
|
272
|
+
[0, this.cameraViews],
|
|
273
|
+
[this.cameraViews, this.viewCount - this.cameraViews]
|
|
274
|
+
];
|
|
275
|
+
for (let i = 0; i < 3; i++) {
|
|
276
|
+
let buffer = this.paramBuffers[i];
|
|
277
|
+
if (!buffer) {
|
|
278
|
+
buffer = new GpuBuffer(gpu, {
|
|
279
|
+
label: `cull/params${i}`,
|
|
280
|
+
usage: GPUBufferUsage.UNIFORM,
|
|
281
|
+
size: 32
|
|
282
|
+
});
|
|
283
|
+
this.paramBuffers.push(buffer);
|
|
284
|
+
}
|
|
285
|
+
this.params[0] = this.slotCount;
|
|
286
|
+
this.params[1] = passes[i][0];
|
|
287
|
+
this.params[2] = passes[i][1];
|
|
288
|
+
this.params[3] = this.batchCount;
|
|
289
|
+
this.params[4] = this.slotCapacity;
|
|
290
|
+
buffer.write(this.params);
|
|
291
|
+
}
|
|
292
|
+
const key = [
|
|
293
|
+
gpu.generation,
|
|
294
|
+
store.instanceBuffer.version,
|
|
295
|
+
this.batchBuffer.version,
|
|
296
|
+
this.lodBuffer.version,
|
|
297
|
+
this.viewBuffer.version,
|
|
298
|
+
this.args.version,
|
|
299
|
+
this.visible.version,
|
|
300
|
+
this.lodState.version,
|
|
301
|
+
store.deform.recordBuffer.version,
|
|
302
|
+
...this.paramBuffers.map((b) => b.version)
|
|
303
|
+
].join("/");
|
|
304
|
+
if (!this.bindGroups || this.bindGroups.key !== key) {
|
|
305
|
+
this.bindGroups = {
|
|
306
|
+
key,
|
|
307
|
+
groups: this.paramBuffers.map(
|
|
308
|
+
(params) => gpu.device.createBindGroup({
|
|
309
|
+
label: "cull",
|
|
310
|
+
layout,
|
|
311
|
+
entries: [
|
|
312
|
+
{ binding: 0, resource: { buffer: store.instanceBuffer.buffer } },
|
|
313
|
+
{ binding: 1, resource: { buffer: this.batchBuffer.buffer } },
|
|
314
|
+
{ binding: 2, resource: { buffer: this.lodBuffer.buffer } },
|
|
315
|
+
{ binding: 3, resource: { buffer: this.viewBuffer.buffer } },
|
|
316
|
+
{ binding: 4, resource: { buffer: this.args.buffer } },
|
|
317
|
+
{ binding: 5, resource: { buffer: this.visible.buffer } },
|
|
318
|
+
{ binding: 6, resource: { buffer: this.lodState.buffer } },
|
|
319
|
+
{ binding: 7, resource: { buffer: params.buffer } },
|
|
320
|
+
{ binding: 8, resource: { buffer: store.deform.recordBuffer.buffer } }
|
|
321
|
+
]
|
|
322
|
+
})
|
|
323
|
+
)
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const pass = ctx.encoder.beginComputePass({
|
|
327
|
+
label: "instance-cull",
|
|
328
|
+
timestampWrites: ctx.timestamps("instance-cull")
|
|
329
|
+
});
|
|
330
|
+
const argGroups = Math.ceil(this.viewCount * this.batchCount / 64);
|
|
331
|
+
if (argGroups > 0) {
|
|
332
|
+
pass.setPipeline(reset);
|
|
333
|
+
pass.setBindGroup(0, this.bindGroups.groups[0]);
|
|
334
|
+
pass.dispatchWorkgroups(argGroups);
|
|
335
|
+
}
|
|
336
|
+
pass.setPipeline(cull);
|
|
337
|
+
const slotGroups = Math.ceil(this.slotCount / 64);
|
|
338
|
+
if (this.cameraViews > 0 && slotGroups > 0) {
|
|
339
|
+
pass.setBindGroup(0, this.bindGroups.groups[1]);
|
|
340
|
+
pass.dispatchWorkgroups(slotGroups, this.cameraViews);
|
|
341
|
+
}
|
|
342
|
+
if (this.viewCount > this.cameraViews && slotGroups > 0) {
|
|
343
|
+
pass.setBindGroup(0, this.bindGroups.groups[2]);
|
|
344
|
+
pass.dispatchWorkgroups(slotGroups, this.viewCount - this.cameraViews);
|
|
345
|
+
}
|
|
346
|
+
pass.end();
|
|
347
|
+
const map = this.readback(ctx.encoder, store);
|
|
348
|
+
if (map) ctx.afterSubmit(map);
|
|
349
|
+
}
|
|
350
|
+
/** Copies the arguments to a readback buffer; resolves visible counts when it maps. */
|
|
351
|
+
readback(encoder, store) {
|
|
352
|
+
const gen = this.gpu.generation;
|
|
353
|
+
const bytes = this.viewCount * this.batchCount * ARGS_WORDS * 4;
|
|
354
|
+
if (bytes === 0) return void 0;
|
|
355
|
+
for (let i = this.readbacks.length - 1; i >= 0; i--) {
|
|
356
|
+
const r = this.readbacks[i];
|
|
357
|
+
if (r.generation !== gen || !r.busy && r.buffer.size < bytes) {
|
|
358
|
+
if (!r.busy) r.buffer.destroy();
|
|
359
|
+
this.readbacks.splice(i, 1);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
let rb = this.readbacks.find((r) => !r.busy);
|
|
363
|
+
if (!rb && this.readbacks.length < 2) {
|
|
364
|
+
rb = {
|
|
365
|
+
buffer: this.gpu.device.createBuffer({
|
|
366
|
+
label: "cull/readback",
|
|
367
|
+
size: Math.max(256, bytes * 2),
|
|
368
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
369
|
+
}),
|
|
370
|
+
busy: false,
|
|
371
|
+
generation: gen
|
|
372
|
+
};
|
|
373
|
+
this.readbacks.push(rb);
|
|
374
|
+
}
|
|
375
|
+
if (!rb) return void 0;
|
|
376
|
+
encoder.copyBufferToBuffer(this.args.buffer, 0, rb.buffer, 0, bytes);
|
|
377
|
+
rb.busy = true;
|
|
378
|
+
const target = rb;
|
|
379
|
+
const views = this.frameViews;
|
|
380
|
+
const batchCount = this.batchCount;
|
|
381
|
+
const lodLevel = /* @__PURE__ */ new Map();
|
|
382
|
+
for (const set of store.lodSets) {
|
|
383
|
+
for (let k = 0; k < set.batches.length; k++) lodLevel.set(set.batches[k], k);
|
|
384
|
+
}
|
|
385
|
+
return () => {
|
|
386
|
+
const done = target.buffer.mapAsync(GPUMapMode.READ, 0, bytes).then(
|
|
387
|
+
() => {
|
|
388
|
+
const args = new Uint32Array(target.buffer.getMappedRange(0, bytes).slice(0));
|
|
389
|
+
target.buffer.unmap();
|
|
390
|
+
target.busy = false;
|
|
391
|
+
views.forEach((view, v) => {
|
|
392
|
+
let main = 0;
|
|
393
|
+
let forward = 0;
|
|
394
|
+
let mainDraws = 0;
|
|
395
|
+
let forwardDraws = 0;
|
|
396
|
+
const lods = [];
|
|
397
|
+
const perBatch = new Uint32Array(batchCount);
|
|
398
|
+
for (let b = 0; b < batchCount; b++) {
|
|
399
|
+
const n = args[(v * batchCount + b) * ARGS_WORDS + 1];
|
|
400
|
+
perBatch[b] = n;
|
|
401
|
+
const batch = store.batches[b];
|
|
402
|
+
if (!batch) continue;
|
|
403
|
+
if (view.forwardOnly && !batch.deferrable) {
|
|
404
|
+
forward += n;
|
|
405
|
+
if (n > 0) forwardDraws++;
|
|
406
|
+
} else {
|
|
407
|
+
main += n;
|
|
408
|
+
if (n > 0) mainDraws++;
|
|
409
|
+
}
|
|
410
|
+
const level = lodLevel.get(b);
|
|
411
|
+
if (level !== void 0) lods[level] = (lods[level] ?? 0) + n;
|
|
412
|
+
}
|
|
413
|
+
this.counts.set(view.list, main);
|
|
414
|
+
this.batchCounts.set(view.list, perBatch);
|
|
415
|
+
if (view.forwardOnly) this.batchCounts.set(view.forwardOnly, perBatch);
|
|
416
|
+
this.drawCounts.set(view.list, mainDraws);
|
|
417
|
+
this.lodCounts.set(view.list, lods);
|
|
418
|
+
if (view.forwardOnly) {
|
|
419
|
+
this.counts.set(view.forwardOnly, forward);
|
|
420
|
+
this.drawCounts.set(view.forwardOnly, forwardDraws);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
},
|
|
424
|
+
() => {
|
|
425
|
+
target.busy = false;
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
this.pending.add(done);
|
|
429
|
+
done.then(() => this.pending.delete(done));
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
/** Resolves once every readback in flight has landed (tests: stats that match the last frame). */
|
|
433
|
+
async whenIdle() {
|
|
434
|
+
while (this.pending.size > 0) await Promise.all(this.pending);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
function cullLayout(gpu) {
|
|
438
|
+
const C = GPUShaderStage.COMPUTE;
|
|
439
|
+
const read = (binding) => ({
|
|
440
|
+
binding,
|
|
441
|
+
visibility: C,
|
|
442
|
+
buffer: { type: "read-only-storage" }
|
|
443
|
+
});
|
|
444
|
+
const write = (binding) => ({
|
|
445
|
+
binding,
|
|
446
|
+
visibility: C,
|
|
447
|
+
buffer: { type: "storage" }
|
|
448
|
+
});
|
|
449
|
+
return gpu.layouts.bindGroupLayout({
|
|
450
|
+
label: "cull",
|
|
451
|
+
entries: [
|
|
452
|
+
read(0),
|
|
453
|
+
read(1),
|
|
454
|
+
read(2),
|
|
455
|
+
read(3),
|
|
456
|
+
write(4),
|
|
457
|
+
write(5),
|
|
458
|
+
write(6),
|
|
459
|
+
{ binding: 7, visibility: C, buffer: { type: "uniform" } },
|
|
460
|
+
read(8)
|
|
461
|
+
]
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
const Culler = defineResource("render/Culler", {
|
|
465
|
+
description: "GPU culling: indirect draws for every camera and shadow view."
|
|
466
|
+
});
|
|
467
|
+
function cullTransparent(store, list, params, forward) {
|
|
468
|
+
store.cullTransparentMembers(list, params, forward);
|
|
469
|
+
}
|
|
470
|
+
function visibleSlots(store, list, mask = SLOT_MASK) {
|
|
471
|
+
const out = /* @__PURE__ */ new Set();
|
|
472
|
+
for (let d = 0; d < list.length; d++) {
|
|
473
|
+
const item = list.items[d];
|
|
474
|
+
for (let i = 0; i < item.count; i++) out.add((store.visible[item.first + i] & mask) >>> 0);
|
|
475
|
+
}
|
|
476
|
+
return out;
|
|
477
|
+
}
|
|
478
|
+
async function readVisibleSlots(gpu, culler, list, mask = SLOT_MASK) {
|
|
479
|
+
const out = /* @__PURE__ */ new Set();
|
|
480
|
+
if (list.cullView < 0 || list.length === 0) return out;
|
|
481
|
+
const args = new Uint32Array(await readBuffer(gpu, culler.args.buffer, culler.args.byteLength));
|
|
482
|
+
const visible = new Uint32Array(
|
|
483
|
+
await readBuffer(gpu, culler.visible.buffer, culler.visible.byteLength)
|
|
484
|
+
);
|
|
485
|
+
for (let d = 0; d < list.length; d++) {
|
|
486
|
+
const item = list.items[d];
|
|
487
|
+
const n = args[item.indirect / 4 + 1];
|
|
488
|
+
for (let i = 0; i < n; i++) out.add((visible[item.first + i] & mask) >>> 0);
|
|
489
|
+
}
|
|
490
|
+
return out;
|
|
491
|
+
}
|
|
492
|
+
export {
|
|
493
|
+
Culler,
|
|
494
|
+
GpuCuller,
|
|
495
|
+
cullTransparent,
|
|
496
|
+
readVisibleSlots,
|
|
497
|
+
visibleSlots
|
|
498
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { LABEL_FONT } from "./gizmo-font.js";
|
|
2
|
+
const GIZMO_SHADERS = {
|
|
3
|
+
"shard::gizmos::common": `
|
|
4
|
+
import shard::view::view;
|
|
5
|
+
|
|
6
|
+
@group(1) @binding(2) var scene_depth: texture_depth_2d;
|
|
7
|
+
|
|
8
|
+
/** Occluded parts of depth-tested gizmos draw at this opacity. */
|
|
9
|
+
const OCCLUDED: f32 = 0.25;
|
|
10
|
+
|
|
11
|
+
/** Opacity from the depth test: 1 in front, OCCLUDED behind, 1 when the test is off. */
|
|
12
|
+
fn depth_fade(frag: vec4f, no_depth: bool) -> f32 {
|
|
13
|
+
if (no_depth) { return 1.0; }
|
|
14
|
+
let size = textureDimensions(scene_depth);
|
|
15
|
+
let p = min(vec2u(frag.xy), size - 1u);
|
|
16
|
+
let scene = textureLoad(scene_depth, p, 0);
|
|
17
|
+
// Reversed Z: nearer is larger.
|
|
18
|
+
return select(OCCLUDED, 1.0, frag.z >= scene - 1e-6);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fn to_pixels(ndc: vec2f) -> vec2f {
|
|
22
|
+
return (ndc * vec2f(0.5, -0.5) + 0.5) * view.viewport.xy;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fn to_ndc(px: vec2f) -> vec2f {
|
|
26
|
+
return (px * view.viewport.zw - 0.5) * vec2f(2.0, -2.0);
|
|
27
|
+
}`,
|
|
28
|
+
"shard::gizmos::lines": `
|
|
29
|
+
import shard::view::view;
|
|
30
|
+
import shard::gizmos::common::{ depth_fade, to_pixels, to_ndc };
|
|
31
|
+
|
|
32
|
+
struct Line {
|
|
33
|
+
a: vec3f,
|
|
34
|
+
b_x: f32,
|
|
35
|
+
b_yz: vec2f,
|
|
36
|
+
color: u32,
|
|
37
|
+
bits: u32,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@group(1) @binding(0) var<storage, read> lines: array<Line>;
|
|
41
|
+
|
|
42
|
+
struct LineOutput {
|
|
43
|
+
@builtin(position) clip: vec4f,
|
|
44
|
+
@location(0) color: vec4f,
|
|
45
|
+
/** Pixels across the line from its center (x), half width (y). */
|
|
46
|
+
@location(1) across: vec2f,
|
|
47
|
+
@location(2) @interpolate(flat) bits: u32,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Clip-space endpoints, the one behind the camera moved onto the near side. */
|
|
51
|
+
fn clip_segment(a: vec4f, b: vec4f) -> array<vec4f, 2> {
|
|
52
|
+
let eps = 1e-4;
|
|
53
|
+
var p = a;
|
|
54
|
+
var q = b;
|
|
55
|
+
if (p.w < eps) { p = mix(p, q, (eps - p.w) / (q.w - p.w)); }
|
|
56
|
+
if (q.w < eps) { q = mix(q, p, (eps - q.w) / (p.w - q.w)); }
|
|
57
|
+
return array<vec4f, 2>(p, q);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@vertex fn vs(@builtin(vertex_index) v: u32, @builtin(instance_index) i: u32) -> LineOutput {
|
|
61
|
+
let l = lines[i];
|
|
62
|
+
var out: LineOutput;
|
|
63
|
+
let ca = view.viewProj * vec4f(l.a, 1.0);
|
|
64
|
+
let cb = view.viewProj * vec4f(l.b_x, l.b_yz, 1.0);
|
|
65
|
+
if (ca.w < 1e-4 && cb.w < 1e-4) {
|
|
66
|
+
out.clip = vec4f(0.0, 0.0, 2.0, 1.0);
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
let seg = clip_segment(ca, cb);
|
|
70
|
+
let pa = to_pixels(seg[0].xy / seg[0].w);
|
|
71
|
+
let pb = to_pixels(seg[1].xy / seg[1].w);
|
|
72
|
+
var dir = pb - pa;
|
|
73
|
+
let len = length(dir);
|
|
74
|
+
dir = select(vec2f(1.0, 0.0), dir / len, len > 1e-6);
|
|
75
|
+
let normal = vec2f(-dir.y, dir.x);
|
|
76
|
+
let half_width = f32(l.bits >> 8u) / 16.0;
|
|
77
|
+
// One pixel of feathering on every side.
|
|
78
|
+
let extent = half_width + 1.0;
|
|
79
|
+
// Corners: (end, side) for two triangles.
|
|
80
|
+
let ends = array<f32, 6>(0.0, 0.0, 1.0, 1.0, 0.0, 1.0);
|
|
81
|
+
let sides = array<f32, 6>(-1.0, 1.0, -1.0, -1.0, 1.0, 1.0);
|
|
82
|
+
let end = ends[v];
|
|
83
|
+
let side = sides[v];
|
|
84
|
+
let base = select(seg[0], seg[1], end > 0.5);
|
|
85
|
+
let px = select(pa, pb, end > 0.5) + normal * side * extent + dir * (end * 2.0 - 1.0) * extent;
|
|
86
|
+
out.clip = vec4f(to_ndc(px) * base.w, base.z, base.w);
|
|
87
|
+
out.color = unpack4x8unorm(l.color);
|
|
88
|
+
out.across = vec2f(side * extent, half_width);
|
|
89
|
+
out.bits = l.bits;
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@fragment fn fs(in: LineOutput) -> @location(0) vec4f {
|
|
94
|
+
let coverage = clamp(in.across.y + 0.5 - abs(in.across.x), 0.0, 1.0);
|
|
95
|
+
let a = in.color.a * coverage * depth_fade(in.clip, (in.bits & 1u) != 0u);
|
|
96
|
+
if (a <= 0.0) { discard; }
|
|
97
|
+
return vec4f(in.color.rgb * a, a);
|
|
98
|
+
}`,
|
|
99
|
+
"shard::gizmos::labels": `
|
|
100
|
+
import shard::view::view;
|
|
101
|
+
import shard::gizmos::common::{ depth_fade, to_pixels, to_ndc };
|
|
102
|
+
|
|
103
|
+
struct Glyph {
|
|
104
|
+
anchor: vec3f,
|
|
105
|
+
offset_x: f32,
|
|
106
|
+
offset_y: f32,
|
|
107
|
+
_pad0: f32,
|
|
108
|
+
size: vec2f,
|
|
109
|
+
glyph: u32,
|
|
110
|
+
color: u32,
|
|
111
|
+
bits: u32,
|
|
112
|
+
_pad1: u32,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@group(1) @binding(1) var<storage, read> glyphs: array<Glyph>;
|
|
116
|
+
@group(1) @binding(3) var atlas: texture_2d<f32>;
|
|
117
|
+
|
|
118
|
+
struct GlyphOutput {
|
|
119
|
+
@builtin(position) clip: vec4f,
|
|
120
|
+
@location(0) color: vec4f,
|
|
121
|
+
/** Atlas texel (glyphs), or negative for a solid box. */
|
|
122
|
+
@location(1) texel: vec2f,
|
|
123
|
+
@location(2) @interpolate(flat) bits: u32,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@vertex fn vs(@builtin(vertex_index) v: u32, @builtin(instance_index) i: u32) -> GlyphOutput {
|
|
127
|
+
let g = glyphs[i];
|
|
128
|
+
var out: GlyphOutput;
|
|
129
|
+
let c = view.viewProj * vec4f(g.anchor, 1.0);
|
|
130
|
+
if (c.w < 1e-4) {
|
|
131
|
+
out.clip = vec4f(0.0, 0.0, 2.0, 1.0);
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
let corners = array<vec2f, 6>(vec2f(0.0, 0.0), vec2f(0.0, 1.0), vec2f(1.0, 0.0), vec2f(1.0, 0.0), vec2f(0.0, 1.0), vec2f(1.0, 1.0));
|
|
135
|
+
let k = corners[v];
|
|
136
|
+
// Whole pixels, so glyphs sample the atlas 1:1.
|
|
137
|
+
let origin = round(to_pixels(c.xy / c.w)) + vec2f(g.offset_x, g.offset_y);
|
|
138
|
+
let px = origin + k * g.size;
|
|
139
|
+
out.clip = vec4f(to_ndc(px) * c.w, c.z, c.w);
|
|
140
|
+
out.color = unpack4x8unorm(g.color);
|
|
141
|
+
if (g.glyph == 0xffffffffu) {
|
|
142
|
+
out.texel = vec2f(-1.0);
|
|
143
|
+
} else {
|
|
144
|
+
let cell = vec2f(f32(g.glyph % ${LABEL_FONT.columns}u) * ${LABEL_FONT.cellWidth}.0, f32(g.glyph / ${LABEL_FONT.columns}u) * ${LABEL_FONT.cellHeight}.0);
|
|
145
|
+
out.texel = cell + k * g.size;
|
|
146
|
+
}
|
|
147
|
+
out.bits = g.bits;
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@fragment fn fs(in: GlyphOutput) -> @location(0) vec4f {
|
|
152
|
+
var coverage = 1.0;
|
|
153
|
+
if (in.texel.x >= 0.0) { coverage = textureLoad(atlas, vec2u(in.texel), 0).r; }
|
|
154
|
+
let a = in.color.a * coverage * depth_fade(in.clip, (in.bits & 1u) != 0u);
|
|
155
|
+
if (a <= 0.0) { discard; }
|
|
156
|
+
return vec4f(in.color.rgb * a, a);
|
|
157
|
+
}`
|
|
158
|
+
};
|
|
159
|
+
const PICK_SHADERS = {
|
|
160
|
+
"shard::pick::types": `
|
|
161
|
+
struct PickOutput {
|
|
162
|
+
@location(0) id: u32,
|
|
163
|
+
/** World normal facing the camera, and the (reversed-Z) depth: 0 where nothing drew. */
|
|
164
|
+
@location(1) normal: vec4f,
|
|
165
|
+
}`,
|
|
166
|
+
"shard::pick": `
|
|
167
|
+
import shard::view::view;
|
|
168
|
+
import shard::mesh::{ instance_at, mesh_vertex_at };
|
|
169
|
+
import shard::pick::types::PickOutput;
|
|
170
|
+
|
|
171
|
+
struct PickVertex {
|
|
172
|
+
@builtin(position) clip: vec4f,
|
|
173
|
+
@location(0) normal: vec3f,
|
|
174
|
+
@location(1) @interpolate(flat) entity: u32,
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@vertex fn vs(
|
|
178
|
+
@builtin(instance_index) instance_index: u32,
|
|
179
|
+
@builtin(vertex_index) vertex_index: u32,
|
|
180
|
+
@location(0) position: vec3f,
|
|
181
|
+
@location(1) normal: vec3f,
|
|
182
|
+
@location(2) uv: vec2f,
|
|
183
|
+
@location(3) uv1: vec2f,
|
|
184
|
+
@location(4) tangent: vec4f,
|
|
185
|
+
) -> PickVertex {
|
|
186
|
+
let inst = instance_at(instance_index);
|
|
187
|
+
let m = mesh_vertex_at(instance_index, vertex_index, position, normal, uv, uv1, tangent);
|
|
188
|
+
var out: PickVertex;
|
|
189
|
+
// Unjittered, so positions from depth match the camera exactly.
|
|
190
|
+
out.clip = view.viewProjNoJitter * vec4f(m.world_position, 1.0);
|
|
191
|
+
out.normal = m.world_normal;
|
|
192
|
+
out.entity = inst.entity;
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@fragment fn fs(in: PickVertex, @builtin(front_facing) front: bool) -> PickOutput {
|
|
197
|
+
var out: PickOutput;
|
|
198
|
+
out.id = in.entity;
|
|
199
|
+
let n = normalize(in.normal);
|
|
200
|
+
out.normal = vec4f(select(-n, n, front), in.clip.z);
|
|
201
|
+
return out;
|
|
202
|
+
}`
|
|
203
|
+
};
|
|
204
|
+
export {
|
|
205
|
+
GIZMO_SHADERS,
|
|
206
|
+
PICK_SHADERS
|
|
207
|
+
};
|