-
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: lesson 8 use depth render target
- Loading branch information
Showing
56 changed files
with
1,678 additions
and
380 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<html> | ||
<script src="https://unpkg.com/[email protected]/touch-emulator.js"></script> | ||
<script type="module" src="./main.ts"></script> | ||
<style> | ||
html, | ||
body { | ||
margin: 0; | ||
padding: 0; | ||
overflow: hidden; | ||
} | ||
|
||
#canvas { | ||
touch-action: none; | ||
} | ||
</style> | ||
<body> | ||
<canvas id="canvas"></canvas> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { Canvas, Circle } from '../src'; | ||
|
||
const $canvas = document.getElementById('canvas') as HTMLCanvasElement; | ||
const resize = (width: number, height: number) => { | ||
$canvas.width = width * window.devicePixelRatio; | ||
$canvas.height = height * window.devicePixelRatio; | ||
$canvas.style.width = `${width}px`; | ||
$canvas.style.height = `${height}px`; | ||
$canvas.style.outline = 'none'; | ||
$canvas.style.padding = '0px'; | ||
$canvas.style.margin = '0px'; | ||
}; | ||
resize(window.innerWidth, window.innerHeight); | ||
|
||
const canvas = await new Canvas({ | ||
canvas: $canvas, | ||
// renderer: 'webgpu', | ||
shaderCompilerPath: | ||
'https://unpkg.com/@antv/[email protected]/dist/pkg/glsl_wgsl_compiler_bg.wasm', | ||
}).initialized; | ||
|
||
const circles: Circle[] = []; | ||
for (let i = 0; i < 100; i++) { | ||
const fill = `rgb(${Math.floor(Math.random() * 255)},${Math.floor( | ||
Math.random() * 255, | ||
)},${Math.floor(Math.random() * 255)})`; | ||
const circle = new Circle({ | ||
cx: Math.random() * 400, | ||
cy: Math.random() * 200, | ||
r: Math.random() * 20, | ||
// cx: i * 50, | ||
// cy: i * 50, | ||
// r: 100, | ||
fill, | ||
stroke: 'black', | ||
strokeWidth: 2, | ||
batchable: true, | ||
}); | ||
canvas.appendChild(circle); | ||
circles.push(circle); | ||
|
||
circle.addEventListener('pointerenter', () => { | ||
circle.fill = 'red'; | ||
}); | ||
circle.addEventListener('pointerleave', () => { | ||
circle.fill = fill; | ||
}); | ||
} | ||
|
||
const animate = () => { | ||
canvas.render(); | ||
requestAnimationFrame(animate); | ||
}; | ||
animate(); | ||
|
||
window.addEventListener('resize', () => { | ||
resize(window.innerWidth, window.innerHeight); | ||
canvas.resize(window.innerWidth, window.innerHeight); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import { Buffer, Device, RenderPass } from '@antv/g-device-api'; | ||
import { Drawcall, SDF } from '../drawcalls'; | ||
import { Circle, type Shape } from '../shapes'; | ||
|
||
/** | ||
* Since a shape may have multiple drawcalls, we need to cache them and maintain an 1-to-many relationship. | ||
* | ||
* e.g. we need 2 drawcalls for a Circle with dashed stroke: | ||
* - A SDF drawcall to draw the fill. | ||
* - A Path drawcall to draw the dashed stroke. | ||
*/ | ||
const SHAPE_DRAWCALL_CTORS = new WeakMap<typeof Shape, (typeof Drawcall)[]>(); | ||
SHAPE_DRAWCALL_CTORS.set(Circle, [SDF]); | ||
|
||
export class BatchManager { | ||
/** | ||
* Drawcalls to flush in the next frame. | ||
*/ | ||
#drawcallsToFlush: Drawcall[] = []; | ||
|
||
/** | ||
* Cache drawcalls for non batchable shape. | ||
*/ | ||
#nonBatchableDrawcallsCache: Record<number, Drawcall[]> = Object.create(null); | ||
|
||
#batchableDrawcallsCache: Record<number, Drawcall[]> = Object.create(null); | ||
|
||
#instancesCache = new WeakMap<typeof Shape, Drawcall[][]>(); | ||
|
||
constructor(private device: Device) {} | ||
|
||
private createDrawcalls(shape: Shape, instanced = false) { | ||
return SHAPE_DRAWCALL_CTORS.get(shape.constructor as typeof Shape)?.map( | ||
(DrawcallCtor) => { | ||
// @ts-ignore | ||
const drawcall = new DrawcallCtor(this.device, instanced); | ||
drawcall.add(shape); | ||
return drawcall; | ||
}, | ||
); | ||
} | ||
|
||
private getOrCreateNonBatchableDrawcalls(shape: Shape) { | ||
let existed = this.#nonBatchableDrawcallsCache[shape.uid]; | ||
if (!existed) { | ||
existed = this.createDrawcalls(shape) || []; | ||
this.#nonBatchableDrawcallsCache[shape.uid] = existed; | ||
} | ||
|
||
return existed; | ||
} | ||
|
||
private getOrCreateBatchableDrawcalls(shape: Shape) { | ||
let existed: Drawcall[] | undefined = | ||
this.#batchableDrawcallsCache[shape.uid]; | ||
if (!existed) { | ||
const shapeCtor = shape.constructor as typeof Shape; | ||
let instancedDrawcalls = this.#instancesCache.get(shapeCtor); | ||
if (!instancedDrawcalls) { | ||
instancedDrawcalls = []; | ||
} | ||
|
||
existed = instancedDrawcalls.find((drawcalls) => | ||
drawcalls.every((drawcall) => drawcall.validate()), | ||
); | ||
|
||
if (!existed) { | ||
existed = this.createDrawcalls(shape, true) || []; | ||
instancedDrawcalls.push(existed); | ||
this.#instancesCache.set(shapeCtor, instancedDrawcalls); | ||
} | ||
|
||
existed.forEach((drawcall) => { | ||
drawcall.add(shape); | ||
}); | ||
|
||
this.#batchableDrawcallsCache[shape.uid] = existed; | ||
} | ||
|
||
return existed; | ||
} | ||
|
||
add(shape: Shape) { | ||
if (shape.batchable) { | ||
const drawcalls = this.getOrCreateBatchableDrawcalls(shape); | ||
if (this.#drawcallsToFlush.indexOf(drawcalls[0]) === -1) { | ||
this.#drawcallsToFlush.push(...drawcalls); | ||
} | ||
} else { | ||
this.#drawcallsToFlush.push( | ||
...this.getOrCreateNonBatchableDrawcalls(shape), | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* Called when a shape is: | ||
* * removed from the scene graph. | ||
* * culled from viewport. | ||
* * invisible. | ||
*/ | ||
remove(shape: Shape) { | ||
if (shape.batchable) { | ||
this.getOrCreateBatchableDrawcalls(shape).forEach((drawcall) => { | ||
drawcall.remove(shape); | ||
}); | ||
delete this.#batchableDrawcallsCache[shape.uid]; | ||
} else { | ||
this.getOrCreateNonBatchableDrawcalls(shape).forEach((drawcall) => { | ||
drawcall.destroy(); | ||
}); | ||
delete this.#nonBatchableDrawcallsCache[shape.uid]; | ||
} | ||
} | ||
|
||
destroy() { | ||
for (const key in this.#nonBatchableDrawcallsCache) { | ||
this.#nonBatchableDrawcallsCache[key].forEach((drawcall) => { | ||
drawcall.destroy(); | ||
}); | ||
} | ||
for (const key in this.#batchableDrawcallsCache) { | ||
this.#batchableDrawcallsCache[key].forEach((drawcall) => { | ||
drawcall.destroy(); | ||
}); | ||
} | ||
} | ||
|
||
clear() { | ||
this.#drawcallsToFlush = []; | ||
} | ||
|
||
flush(renderPass: RenderPass, uniformBuffer: Buffer) { | ||
this.#drawcallsToFlush.forEach((drawcall) => { | ||
drawcall.submit(renderPass, uniformBuffer); | ||
}); | ||
} | ||
|
||
stats() { | ||
return { | ||
drawcallCount: this.#drawcallsToFlush.length, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,66 @@ | ||
import { Buffer, Device, Program, RenderPass } from '@antv/g-device-api'; | ||
import { Buffer, Device, RenderPass } from '@antv/g-device-api'; | ||
import { Shape } from '../shapes'; | ||
|
||
export class Drawcall { | ||
shapes: Shape[] = []; | ||
get instance() { | ||
return this.shapes[0]; | ||
} | ||
|
||
instanced: boolean; | ||
export const ZINDEX_FACTOR = 50000; | ||
|
||
protected program: Program; | ||
export abstract class Drawcall { | ||
protected shapes: Shape[] = []; | ||
|
||
/** | ||
* Create a new batch if the number of instances exceeds. | ||
*/ | ||
maxInstances = Infinity; | ||
protected maxInstances = Infinity; | ||
|
||
index = 0; | ||
protected geometryDirty = true; | ||
protected materialDirty = true; | ||
|
||
geometryDirty = true; | ||
constructor(protected device: Device, protected instanced: boolean) {} | ||
|
||
constructor(protected device: Device) {} | ||
abstract createGeometry(): void; | ||
abstract createMaterial(uniformBuffer: Buffer): void; | ||
abstract render(renderPass: RenderPass): void; | ||
abstract destroy(): void; | ||
|
||
validate() { | ||
return this.count() <= this.maxInstances - 1; | ||
} | ||
|
||
shouldMerge(shape: Shape, index: number) { | ||
if (!this.instance) { | ||
return true; | ||
submit(renderPass: RenderPass, uniformBuffer: Buffer) { | ||
if (this.geometryDirty) { | ||
this.createGeometry(); | ||
} | ||
|
||
if (this.instance.constructor !== shape.constructor) { | ||
return false; | ||
if (this.materialDirty) { | ||
this.createMaterial(uniformBuffer); | ||
} | ||
|
||
return true; | ||
} | ||
this.render(renderPass); | ||
|
||
protected init() {} | ||
protected render(renderPass: RenderPass, uniformBuffer: Buffer) {} | ||
if (this.geometryDirty) { | ||
this.geometryDirty = false; | ||
} | ||
|
||
submit(renderPass: RenderPass, uniformBuffer: Buffer) { | ||
if (!this.program) { | ||
this.init(); | ||
if (this.materialDirty) { | ||
this.materialDirty = false; | ||
} | ||
} | ||
|
||
add(shape: Shape) { | ||
if (!this.shapes.includes(shape)) { | ||
this.shapes.push(shape); | ||
this.geometryDirty = true; | ||
} | ||
} | ||
|
||
this.render(renderPass, uniformBuffer); | ||
remove(shape: Shape) { | ||
if (this.shapes.includes(shape)) { | ||
const index = this.shapes.indexOf(shape); | ||
this.shapes.splice(index, 1); | ||
this.geometryDirty = true; | ||
} | ||
} | ||
|
||
destroy() { | ||
this.program.destroy(); | ||
count() { | ||
return this.shapes.length; | ||
} | ||
} |
Oops, something went wrong.