Skip to content

Commit 87b03bc

Browse files
committed
feat: export selected nodes and render in offscreen canvas #58
1 parent 4a98af9 commit 87b03bc

File tree

4 files changed

+112
-55
lines changed

4 files changed

+112
-55
lines changed

packages/ecs/src/systems/BatchManager.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
Renderable,
2424
Rough,
2525
Text,
26+
UI,
2627
VectorNetwork,
2728
} from '../components';
2829
import { TexturePool } from '../resources';
@@ -278,6 +279,26 @@ export class BatchManager {
278279
}
279280
}
280281

282+
#hidedUIs: Drawcall[] = [];
283+
hideUIs() {
284+
[...this.#drawcallsToFlush].forEach((drawcall) => {
285+
if (drawcall.shapes.some((shape) => shape.has(UI))) {
286+
this.#drawcallsToFlush.splice(
287+
this.#drawcallsToFlush.indexOf(drawcall),
288+
1,
289+
);
290+
this.#hidedUIs.push(drawcall);
291+
}
292+
});
293+
}
294+
295+
showUIs() {
296+
this.#hidedUIs.forEach((drawcall) => {
297+
this.#drawcallsToFlush.push(drawcall);
298+
});
299+
this.#hidedUIs = [];
300+
}
301+
281302
destroy() {
282303
for (const key in this.#nonBatchableDrawcallsCache) {
283304
this.#nonBatchableDrawcallsCache[key].forEach((drawcall) => {

packages/ecs/src/systems/MeshPipeline.ts

Lines changed: 72 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,23 @@ import {
5858
Marker,
5959
VectorNetwork,
6060
} from '../components';
61-
import { paddingMat3 } from '../utils';
61+
import { paddingMat3, SerializedNode } from '../utils';
6262
import { GridRenderer } from './GridRenderer';
6363
import { BatchManager } from './BatchManager';
6464
import { getSceneRoot } from './Transform';
6565
import { safeAddComponent } from '../history';
66+
import { SetupDevice } from './SetupDevice';
67+
68+
type GPURenderer = {
69+
uniformBuffer: Buffer;
70+
uniformLegacyObject: Record<string, unknown>;
71+
gridRenderer: GridRenderer;
72+
batchManager: BatchManager;
73+
};
6674

6775
export class MeshPipeline extends System {
76+
private setupDevice = this.attach(SetupDevice);
77+
6878
private canvases = this.query((q) => q.current.with(Canvas).read);
6979

7080
private cameras = this.query(
@@ -163,7 +173,7 @@ export class MeshPipeline extends System {
163173
(q) => q.addedChangedOrRemoved.with(Marker).trackWrites,
164174
);
165175

166-
gpuResources: Map<
176+
renderers: Map<
167177
Entity,
168178
{
169179
uniformBuffer: Buffer;
@@ -253,64 +263,73 @@ export class MeshPipeline extends System {
253263
canvas.remove(RasterScreenshotRequest);
254264
}
255265

266+
private createRenderer(gpuResource: GPUResource) {
267+
const { device, renderCache, texturePool } = gpuResource;
268+
return {
269+
uniformBuffer: device.createBuffer({
270+
viewOrSize: (16 * 3 + 4 * 5) * Float32Array.BYTES_PER_ELEMENT,
271+
usage: BufferUsage.UNIFORM,
272+
hint: BufferFrequencyHint.DYNAMIC,
273+
}),
274+
uniformLegacyObject: null,
275+
gridRenderer: new GridRenderer(),
276+
batchManager: new BatchManager(device, renderCache, texturePool),
277+
};
278+
}
279+
256280
private renderCamera(canvas: Entity, camera: Entity, sort = false) {
257281
if (!canvas.has(GPUResource)) {
258282
return;
259283
}
260284

261-
const {
262-
swapChain,
263-
device,
264-
renderTarget,
265-
depthRenderTarget,
266-
renderCache,
267-
texturePool,
268-
} = canvas.read(GPUResource);
269-
270285
const request = canvas.has(RasterScreenshotRequest)
271286
? canvas.read(RasterScreenshotRequest)
272287
: null;
273-
274-
const { type, encoderOptions, grid, download } = request ?? {
288+
const { type, encoderOptions, grid, download, nodes } = request ?? {
275289
type: 'image/png',
276290
encoderOptions: 1,
277291
grid: false,
292+
nodes: [],
278293
};
294+
const shouldRenderGrid = !request || grid;
295+
const shouldRenderPartially = nodes.length > 0;
296+
297+
let renderer: GPURenderer;
298+
let gpuResource: GPUResource;
299+
if (shouldRenderPartially) {
300+
// Render to offscreen canvas.
301+
gpuResource = this.setupDevice.getOffscreenGPUResource();
302+
renderer = this.createRenderer(gpuResource);
303+
} else {
304+
gpuResource = canvas.read(GPUResource);
305+
if (!this.renderers.get(camera)) {
306+
this.renderers.set(camera, this.createRenderer(gpuResource));
307+
}
308+
renderer = this.renderers.get(camera);
309+
const { batchManager } = renderer;
310+
if (request) {
311+
batchManager.hideUIs();
312+
}
313+
}
314+
315+
const { swapChain, device, renderTarget, depthRenderTarget } = gpuResource;
316+
const { uniformBuffer, uniformLegacyObject, gridRenderer, batchManager } =
317+
renderer;
279318

280319
const { width, height } = swapChain.getCanvas();
281320
const onscreenTexture = swapChain.getOnscreenTexture();
282321

283-
const shouldRenderGrid = !request || grid;
284-
285-
// console.log(request, grid);
286-
287-
if (!this.gpuResources.get(camera)) {
288-
this.gpuResources.set(camera, {
289-
uniformBuffer: device.createBuffer({
290-
viewOrSize: (16 * 3 + 4 * 5) * Float32Array.BYTES_PER_ELEMENT,
291-
usage: BufferUsage.UNIFORM,
292-
hint: BufferFrequencyHint.DYNAMIC,
293-
}),
294-
uniformLegacyObject: null,
295-
gridRenderer: new GridRenderer(),
296-
batchManager: new BatchManager(device, renderCache, texturePool),
297-
});
298-
}
299-
300322
const [buffer, legacyObject] = this.updateUniform(
301323
canvas,
302324
camera,
303325
shouldRenderGrid,
304326
swapChain,
305327
);
306-
this.gpuResources.set(camera, {
307-
...this.gpuResources.get(camera),
328+
this.renderers.set(camera, {
329+
...this.renderers.get(camera),
308330
uniformLegacyObject: legacyObject,
309331
});
310332

311-
const { uniformBuffer, uniformLegacyObject, gridRenderer, batchManager } =
312-
this.gpuResources.get(camera);
313-
314333
uniformBuffer.setSubData(0, new Uint8Array(buffer.buffer));
315334

316335
device.beginFrame();
@@ -326,15 +345,23 @@ export class MeshPipeline extends System {
326345

327346
gridRenderer.render(device, renderPass, uniformBuffer, uniformLegacyObject);
328347

329-
if (this.pendingRenderables.has(camera)) {
330-
this.pendingRenderables.get(camera).forEach(({ type, entity }) => {
331-
if (type === 'remove') {
332-
batchManager.remove(entity, !entity.has(Culled));
333-
} else {
334-
batchManager.add(entity);
335-
}
348+
if (shouldRenderPartially) {
349+
const { api } = canvas.read(Canvas);
350+
nodes.forEach((node: SerializedNode) => {
351+
const entity = api.getEntity(node);
352+
batchManager.add(entity);
336353
});
337-
this.pendingRenderables.delete(camera);
354+
} else {
355+
if (this.pendingRenderables.has(camera)) {
356+
this.pendingRenderables.get(camera).forEach(({ type, entity }) => {
357+
if (type === 'remove') {
358+
batchManager.remove(entity, !entity.has(Culled));
359+
} else {
360+
batchManager.add(entity);
361+
}
362+
});
363+
this.pendingRenderables.delete(camera);
364+
}
338365
}
339366

340367
if (sort) {
@@ -351,6 +378,7 @@ export class MeshPipeline extends System {
351378
encoderOptions,
352379
);
353380
this.setScreenshotTrigger(canvas, dataURL, download);
381+
batchManager.showUIs();
354382
}
355383
}
356384

@@ -476,7 +504,7 @@ export class MeshPipeline extends System {
476504
}
477505

478506
finalize() {
479-
this.gpuResources.forEach(({ gridRenderer, batchManager }) => {
507+
this.renderers.forEach(({ gridRenderer, batchManager }) => {
480508
gridRenderer.destroy();
481509
batchManager.clear();
482510
batchManager.destroy();

packages/ecs/src/systems/SetupDevice.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export class SetupDevice extends System {
2626
* Used for rendering and exporting the shapes in canvas to image(PNG, JPEG, etc.).
2727
*/
2828
#offscreenElement: HTMLCanvasElement | OffscreenCanvas;
29+
#offscreenGPUResource: GPUResource;
2930

3031
constructor() {
3132
super();
@@ -41,6 +42,10 @@ export class SetupDevice extends System {
4142
yield;
4243
}
4344

45+
getOffscreenGPUResource() {
46+
return this.#offscreenGPUResource;
47+
}
48+
4449
execute() {
4550
this.canvases.added.forEach(async (canvas) => {
4651
if (!canvas.has(Theme)) {
@@ -62,16 +67,19 @@ export class SetupDevice extends System {
6267

6368
if (!this.#offscreenElement && isBrowser) {
6469
this.#offscreenElement = document.createElement('canvas');
65-
this.#offscreenElement.width = width;
66-
this.#offscreenElement.height = height;
67-
await this.createGPUResource(
68-
renderer,
69-
shaderCompilerPath,
70-
this.#offscreenElement,
71-
width,
72-
height,
73-
devicePixelRatio,
74-
);
70+
this.#offscreenElement.width = width * devicePixelRatio;
71+
this.#offscreenElement.height = height * devicePixelRatio;
72+
this.#offscreenGPUResource = {
73+
...(await this.createGPUResource(
74+
renderer,
75+
shaderCompilerPath,
76+
this.#offscreenElement,
77+
width,
78+
height,
79+
devicePixelRatio,
80+
)),
81+
texturePool: this.#texturePool,
82+
};
7583
}
7684

7785
const holder = canvas.hold();

packages/webcomponents/examples/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ canvas.addEventListener(Event.READY, async (e) => {
283283
checkboardStyle: CheckboardStyle.GRID,
284284
snapToPixelGridEnabled: true,
285285
snapToPixelGridSize: 1,
286-
snapToObjectsEnabled: true,
286+
// snapToObjectsEnabled: true,
287287
// checkboardStyle: CheckboardStyle.NONE,
288288
// penbarSelected: Pen.SELECT,
289289
// topbarVisible: false,

0 commit comments

Comments
 (0)