Skip to content

Commit

Permalink
🆕 feat: opt editor performance by merge all into one canvas
Browse files Browse the repository at this point in the history
  • Loading branch information
deepkolos committed Oct 15, 2023
1 parent e4e46fa commit 88747e9
Show file tree
Hide file tree
Showing 21 changed files with 154 additions and 67 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
- 链接稍微困难, 需要增大识别区域 ✅
- 右键未触发节点选择 导致删除可能误删, 先增加提示+顺序调整规避 ✅
- 右键菜单二级菜单无法显示 ✅
- 缺少SG示例库 节点开发不方便 资源上传需要代码手动替换
- SubGraph编译报错 还是有一些偶现的编译出错 ✅ 编译时序问题 编译1未完成再编译2
- 节点数量多时候编辑卡顿 预览canvas合并为一个 ✅

# 赞助

Expand Down
2 changes: 1 addition & 1 deletion src/compilers/ShaderGraphCompiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@ const stringifyFloat = (num: number | number[]): string => {
};

const stringifyVector = (value: number[], len: 2 | 3 | 4): string => {
return `vec${len}(${new Array(len)
return `vec${len}f(${new Array(len)
.fill(0)
.map((v, k) => stringifyFloat(value[k] || 0))
.join(', ')})`;
Expand Down
2 changes: 1 addition & 1 deletion src/components/utility/PreviewNumberRC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ fn ${varName}_PrintValue( vStringCoords: vec2f, fValue_: f32, fMaxDigits: f32, f
};

const print = (offset: string, suffix: string) =>
/* wgsl */ `vColour = mix( vColour, fontColor, ${fnBaseVar}_PrintValue( (fragCoord - vec2(${offset})) / vFontSize, value${suffix}, fDigits, fDecimalPlaces));`;
/* wgsl */ `vColour = mix( vColour, fontColor, ${fnBaseVar}_PrintValue( (fragCoord - vec2f(${offset})) / vFontSize, value${suffix}, fDigits, fDecimalPlaces));`;
// prettier-ignore
const printCodeMap = {
[ValueType.float]: print('-40, 45.0', ''),
Expand Down
2 changes: 1 addition & 1 deletion src/materials/OpaquePass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class OpaquePass {
this._camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1000);
this._camera.updateMatrixWorld(true);
}
let [w, h] = this._renderer.viewport;
let [, , w, h] = this._renderer.viewport;
if (!this.color || this.color.width !== w || this.color.height !== h) {
w = w < 1 ? 200 : w;
h = h < 1 ? 200 : h;
Expand Down
4 changes: 2 additions & 2 deletions src/materials/SGController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,8 @@ export class SGController {
} else {
this.set('Camera', 'orthographic', 0);
}
this.set('Screen', 'width', renderer.viewport[0]);
this.set('Screen', 'height', renderer.viewport[1]);
this.set('Screen', 'width', renderer.viewport[2]);
this.set('Screen', 'height', renderer.viewport[3]);
}

static loadTexture(asset: AssetValue) {
Expand Down
68 changes: 56 additions & 12 deletions src/materials/WebGPURenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,25 @@ export class WebGPURenderer {
depthTextures = new Map<string, GPUTexture>();
lastSubmit!: Promise<undefined>;
samplerCache: { [k: string]: GPUSampler } = {};
viewport: number[] = [0, 0];
viewport: [number, number, number, number] = [0, 0, 0, 0]; // [x,y,w,h]
scissor: [number, number, number, number] = [0, 0, 0, 0]; // [x,y,w,h]
clearColor = true;
clearDepth = true;
clearValue = { r: 0, g: 0, b: 0, a: 0 };
opaquePass = new OpaquePass(this);
defaultDepthTexture?: GPUTexture;
defaultColorTexture?: GPUTexture;
defaultDepthTextureView?: GPUTextureView;
defaultColorTextureView?: GPUTextureView;

setViewport(x: number, y: number, w: number, h: number) {
this.viewport = [x, y, w, h];
}

setScissor(x: number, y: number, w: number, h: number) {
this.scissor = [x, y, w, h];
}

async init() {
this.adapter = (await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' }))!;
this.device = await this.adapter!.requestDevice();
Expand Down Expand Up @@ -110,13 +122,12 @@ export class WebGPURenderer {
device: this.device,
format: this.canvasFormat,
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
alphaMode: 'opaque',
alphaMode: 'premultiplied',
});
targetCtx.configured = true;
}

const { width, height } = targetCtx.canvas;
this.viewport = [width, height];
const key = `${Math.ceil(width)}x${Math.ceil(height)}`;
let depthTexture = this.depthTextures.get(key);
if (!depthTexture) {
Expand Down Expand Up @@ -391,12 +402,17 @@ export class WebGPURenderer {

render(scene: Scene, camera: Camera, targetCtx: GPUCanvasContext) {
if (targetCtx.canvas.width === 0) return;
const [x, y, w, h] = this.viewport;
// check viewport https://www.w3.org/TR/webgpu/#dom-gpurenderpassencoder-setviewport
if (!(x >= 0 && y >= 0 && w >= 0 && h >= 0 && x + w <= targetCtx.canvas.width && y + h <= targetCtx.canvas.height)) return;

let { clearValue } = this;
const depthTexture = this.prepareCtx(targetCtx);
// 绘制背景 只支持纯色
const clearValue = { r: 1, g: 0, b: 0, a: 1 };
const bgColor = scene.background as Color | undefined;
if (bgColor && bgColor.isColor) Color.prototype.copy.call(clearValue, bgColor);
if (bgColor && bgColor.isColor) {
clearValue = { r: bgColor.r, g: bgColor.g, b: bgColor.b, a: 1 };
}

// 更新矩阵
scene.updateMatrixWorld();
Expand All @@ -423,21 +439,22 @@ export class WebGPURenderer {
{
view: useOpaquePass ? this.opaquePass.colorView! : canvasColorView,
clearValue,
loadOp: 'clear',
loadOp: this.clearColor ? 'clear' : 'load',
storeOp: 'store',
},
],
depthStencilAttachment: {
view: useOpaquePass ? this.opaquePass.depthView! : canvasDepthView,
depthClearValue: 1,
depthLoadOp: 'clear',
depthLoadOp: this.clearDepth ? 'clear' : 'load',
depthStoreOp: 'store',
},
});

const { width: w, height: h } = targetCtx.canvas;
passEncoder.setViewport(0, 0, w, h, 0, 1);
passEncoder.setScissorRect(0, 0, w, h);
if (!useOpaquePass) {
passEncoder.setViewport(...this.viewport, 0, 1);
passEncoder.setScissorRect(...this.scissor);
}

opaqueList.forEach(node => this.drawMesh(node, passEncoder));
if (useOpaquePass) {
Expand All @@ -447,18 +464,20 @@ export class WebGPURenderer {
{
view: canvasColorView,
clearValue,
loadOp: 'clear',
loadOp: this.clearColor ? 'clear' : 'load',
storeOp: 'store',
},
],
depthStencilAttachment: {
view: canvasDepthView,
depthClearValue: 1,
depthLoadOp: 'clear',
depthLoadOp: this.clearDepth ? 'clear' : 'load',
depthStoreOp: 'store',
},
});
// render opaque mesh
passEncoder.setViewport(...this.viewport, 0, 1);
passEncoder.setScissorRect(...this.scissor);
this.opaquePass.renderOpaque(passEncoder);
transparentList.forEach(node => this.drawMesh(node, passEncoder));
passEncoder.end();
Expand All @@ -471,6 +490,31 @@ export class WebGPURenderer {
this.lastSubmit = this.queue.onSubmittedWorkDone();
}

clear(targetCtx: GPUCanvasContext) {
const depthTexture = this.prepareCtx(targetCtx);
const canvasColorView = targetCtx.getCurrentTexture().createView();
const canvasDepthView = depthTexture.createView();
const commandEncoder = this.device.createCommandEncoder();
const passEncoder = commandEncoder.beginRenderPass({
colorAttachments: [
{
view: canvasColorView,
clearValue: this.clearValue,
loadOp: 'clear',
storeOp: 'store',
},
],
depthStencilAttachment: {
view: canvasDepthView,
depthClearValue: 1,
depthLoadOp: 'clear',
depthStoreOp: 'store',
},
});
passEncoder.end();
this.queue.submit([commandEncoder.finish()]);
}

async dispose() {
await this.lastSubmit;
this.depthTextures.forEach(textrue => textrue.destroy());
Expand Down
14 changes: 7 additions & 7 deletions src/plugins/PreviewPlugin/PreviewClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import { WebGPUMaterial, disposeMaterial } from '../../materials';
export class PreviewClient {
enable: boolean = false;
type: '2d' | '3d' = '2d';
ctx: GPUCanvasContext;
material = new WebGPUMaterial();

constructor(
public canvas: HTMLCanvasElement & { clientWidth_cache?: number; clientHeight_cache?: number },
public node?: ReteNode,
) {
this.ctx = canvas.getContext('webgpu')!;
constructor(public canvas: HTMLDivElement & { clientWidth_cache?: number; clientHeight_cache?: number }, public node?: ReteNode) {}

get weight() {
if (!this.node) return 2;
if (this.node.meta.hovering) return 1;
return 0;
}

set(enable?: boolean, type?: '2d' | '3d') {
Expand Down Expand Up @@ -47,7 +47,7 @@ export class PreviewClient {
}

export function getClientSize(
canvas: HTMLCanvasElement & {
canvas: HTMLElement & {
clientWidth_cache?: number;
clientHeight_cache?: number;
cacheNeedsUpdate?: boolean;
Expand Down
10 changes: 10 additions & 0 deletions src/plugins/PreviewPlugin/PreviewServer.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.sg-preview-server-canvas {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 2;
display: block;
}
59 changes: 39 additions & 20 deletions src/plugins/PreviewPlugin/PreviewServer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import './PreviewServer.less';
import {
AmbientLight,
BoxGeometry,
BufferGeometry,
CapsuleGeometry,
Clock,
Color,
CylinderGeometry,
DirectionalLight,
Matrix4,
Expand All @@ -16,7 +16,7 @@ import {
Scene,
SphereGeometry,
} from 'three';
import { getClientSize, PreviewClient } from './PreviewClient';
import { PreviewClient } from './PreviewClient';
import { ShaderGraphEditor } from '../../editors';
import { Rete } from '../../types';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
Expand All @@ -29,7 +29,7 @@ export class PreviewServer {
clock: Clock;
canvas: HTMLCanvasElement;
scene: Scene;
clients = new Map<HTMLCanvasElement, PreviewClient>();
clients = new Map<HTMLDivElement, PreviewClient>();
nodeClients = new Set<PreviewClient>();
mainClients = new Set<PreviewClient>();
geometries = {
Expand All @@ -53,6 +53,9 @@ export class PreviewServer {
floor: Mesh<BoxGeometry, WebGPUMaterial>;
updatingMaterial = false;
updatingMaterialNext = false;
resizeObserver: ResizeObserver;
ctx: GPUCanvasContext;
dpr = devicePixelRatio;

constructor(public editor: ShaderGraphEditor) {
this.canvas = document.createElement('canvas');
Expand All @@ -67,7 +70,6 @@ export class PreviewServer {

this.mesh = new Mesh();
this.scene.add(this.mesh);
this.scene.background = new Color('#2b2b2b');

const ambientLight = new AmbientLight('#ffffff', 0);
const dirLight = new DirectionalLight('#ffffff', 1);
Expand All @@ -78,10 +80,7 @@ export class PreviewServer {
this.scene.add(this.camera2D, this.camera3D);

const floorMaterial = new WebGPUMaterial();
this.floor = new Mesh(
new BoxGeometry(40 * scaleFactor, 2 * scaleFactor, 40 * scaleFactor),
floorMaterial,
);
this.floor = new Mesh(new BoxGeometry(40 * scaleFactor, 2 * scaleFactor, 40 * scaleFactor), floorMaterial);
floorMaterial.uniforms = {
modelViewMatrix: { value: new Matrix4(), type: 'mat4x4_f32' },
projectionMatrix: { value: new Matrix4(), type: 'mat4x4_f32' },
Expand All @@ -96,6 +95,19 @@ export class PreviewServer {
this.floor.updateMatrixWorld(true);
this.scene.add(this.floor);

this.ctx = this.canvas.getContext('webgpu')!;
this.canvas.classList.add('sg-preview-server-canvas');
this.editor.view.container.appendChild(this.canvas);

// resize
this.resizeObserver = new ResizeObserver(() => {
this.canvas.width = this.canvas.clientWidth * this.dpr;
this.canvas.height = this.canvas.clientHeight * this.dpr;
});
this.renderer.clearValue.a = 0;
this.renderer.clearColor = false;
this.resizeObserver.observe(this.canvas);

this.renderer.init().then(this.render);
}

Expand Down Expand Up @@ -124,7 +136,14 @@ export class PreviewServer {
if (this.disposed) return;
const deltaTime = this.clock.getDelta();
this.mainMaterial.sg.update(deltaTime);
if (!this.updatingMaterial) this.clients.forEach(this.renderClient);

// clear canvas with alpha 0
const { width, height } = this.canvas;
this.renderer.setViewport(0, 0, width, height);
this.renderer.setScissor(0, 0, width, height);
this.renderer.clear(this.ctx);

if (!this.updatingMaterial) [...this.clients.values()].sort((a, b) => a.weight - b.weight).forEach(this.renderClient);
requestAnimationFrame(this.render);
// setTimeout(this.render, 500);
};
Expand Down Expand Up @@ -204,28 +223,27 @@ export class PreviewServer {
client.updateCamera(this.camera3D);
if (!client.node) this.updateFloor();
this.updateMaterialUnifroms(camera);
this.syncCanvasSize(client.canvas);
this.renderer.render(this.scene, camera, client.ctx);

const { top: py, left: px } = this.editor.view.container.getBoundingClientRect();
const { width, height, top: cy, left: cx } = client.canvas.getBoundingClientRect();
const x = cx - px;
const y = cy - py;
this.renderer.setScissor(x * this.dpr, y * this.dpr, Math.round(width * this.dpr), Math.round(height * this.dpr));
this.renderer.setViewport(x * this.dpr, y * this.dpr, Math.round(width * this.dpr), Math.round(height * this.dpr));
this.renderer.render(this.scene, camera, this.ctx);
if (client.node) this.floor.visible = floorVisible;
}
};

syncCanvasSize(canvas: HTMLCanvasElement) {
const { width, height } = getClientSize(canvas);
canvas.width = width * devicePixelRatio;
canvas.height = height * devicePixelRatio;
}

add(client: PreviewClient) {
// console.log('add', client.node?.name);
if (this.clients.has(client.canvas)) this.remove(client.canvas);
this.clients.set(client.canvas, client);
(client.node ? this.nodeClients : this.mainClients).add(client);
if (!client.node)
this.control = this.control || new OrbitControls(this.camera3D, client.canvas);
if (!client.node) this.control = this.control || new OrbitControls(this.camera3D, client.canvas);
}

remove(canvas: HTMLCanvasElement) {
remove(canvas: HTMLDivElement) {
const old = this.clients.get(canvas);
if (old) {
(old.node ? this.nodeClients : this.mainClients).delete(old);
Expand All @@ -242,6 +260,7 @@ export class PreviewServer {

async dispose() {
this.disposed = true;
this.editor.view.container.removeChild(this.canvas);
await this.renderer.dispose();
Object.values(this.geometries).forEach(disposeGeometry);
disposeGeometry(this.mesh.geometry);
Expand Down
6 changes: 3 additions & 3 deletions src/plugins/PreviewPlugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ export * from './PreviewCustomMeshPlugin';

declare module '../../rete/core/events' {
interface EventsTypes {
previewclientcreate: { canvas: HTMLCanvasElement; node?: ReteNode; type?: '2d' | '3d'; enable?: boolean };
previewclientremove: { canvas: HTMLCanvasElement };
previewclientupdate: { canvas: HTMLCanvasElement; type?: '2d' | '3d'; enable?: boolean };
previewclientcreate: { canvas: HTMLDivElement; node?: ReteNode; type?: '2d' | '3d'; enable?: boolean };
previewclientremove: { canvas: HTMLDivElement };
previewclientupdate: { canvas: HTMLDivElement; type?: '2d' | '3d'; enable?: boolean };
previewsettingupdate: { geometry: string };
previewcopyshader: { node: ReteNode; callback: (data?: { fragCode: string; vertCode: string }) => void };
previewcustommesh: (geometry?: BufferGeometry) => void;
Expand Down
2 changes: 1 addition & 1 deletion src/rete/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class Node {
controls = new Map<string, Control>();
blocks: Array<Node> = [];
data: {[key: string]: unknown} = {};
meta: {[key: string]: unknown} = {};
meta: { hovering?: boolean } & {[key: string]: unknown} = {};
contextNode?: Node;

static latestId = 0;
Expand Down
Loading

0 comments on commit 88747e9

Please sign in to comment.