shader perf improvements

This commit is contained in:
Orion Reed 2024-12-01 22:00:13 -05:00
parent 1424fb6cac
commit bf1ca91e8b
3 changed files with 41 additions and 39 deletions

View File

@ -24,13 +24,9 @@
border: 2px solid rgba(0, 0, 0, 0.5);
}
distance-field canvas {
distance-field {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
inset: 0;
}
</style>
</head>

View File

@ -9,15 +9,16 @@ import { WebGLUtils } from './utils/webgl.ts';
export class DistanceField extends HTMLElement {
static tagName = 'distance-field';
private geometries: NodeListOf<Element>;
private textures: WebGLTexture[] = [];
private pingPongIndex: number = 0;
private shapes!: NodeListOf<Element>;
private canvas!: HTMLCanvasElement;
private glContext!: WebGL2RenderingContext;
private framebuffer!: WebGLFramebuffer;
private fullscreenQuadVAO!: WebGLVertexArrayObject;
private shapeVAO!: WebGLVertexArrayObject;
private offsetCache: Map<number, Float32Array> = new Map();
private jfaProgram!: WebGLProgram; // Shader program for the Jump Flooding Algorithm
private renderProgram!: WebGLProgram; // Shader program for final rendering
@ -25,11 +26,13 @@ export class DistanceField extends HTMLElement {
private static readonly MAX_DISTANCE = 99999.0;
constructor() {
super();
static define() {
customElements.define(this.tagName, this);
}
connectedCallback() {
// Collect all geometry elements to process
this.geometries = document.querySelectorAll('fc-geometry');
this.shapes = document.querySelectorAll('fc-geometry');
// Initialize WebGL context and canvas
const { gl, canvas } = WebGLUtils.createWebGLCanvas(this.clientWidth, this.clientHeight, this);
@ -53,15 +56,9 @@ export class DistanceField extends HTMLElement {
// Start the Jump Flooding Algorithm
this.runJFA();
}
static define() {
customElements.define(this.tagName, this);
}
connectedCallback() {
window.addEventListener('resize', this.handleResize);
this.geometries.forEach((geometry) => {
this.shapes.forEach((geometry) => {
geometry.addEventListener('move', this.handleGeometryUpdate);
geometry.addEventListener('resize', this.handleGeometryUpdate);
});
@ -69,7 +66,7 @@ export class DistanceField extends HTMLElement {
disconnectedCallback() {
window.removeEventListener('resize', this.handleResize);
this.geometries.forEach((geometry) => {
this.shapes.forEach((geometry) => {
geometry.removeEventListener('move', this.handleGeometryUpdate);
geometry.removeEventListener('resize', this.handleGeometryUpdate);
});
@ -146,6 +143,13 @@ export class DistanceField extends HTMLElement {
if (!this.framebuffer) {
this.framebuffer = gl.createFramebuffer()!;
}
// Check if framebuffer is complete
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (status !== gl.FRAMEBUFFER_COMPLETE) {
console.error('Framebuffer is not complete:', status);
return;
}
}
/**
@ -163,7 +167,7 @@ export class DistanceField extends HTMLElement {
// Collect positions and assign unique IDs to all shapes
const positions: number[] = [];
this.geometries.forEach((geometry, index) => {
this.shapes.forEach((geometry, index) => {
const rect = geometry.getBoundingClientRect();
// Convert DOM coordinates to Normalized Device Coordinates (NDC)
@ -239,7 +243,7 @@ export class DistanceField extends HTMLElement {
// Bind VAO and draw shapes
gl.bindVertexArray(this.shapeVAO);
gl.drawArrays(gl.TRIANGLES, 0, this.geometries.length * 6);
gl.drawArrays(gl.TRIANGLES, 0, this.shapes.length * 6);
// Unbind VAO and framebuffer
gl.bindVertexArray(null);
@ -251,15 +255,11 @@ export class DistanceField extends HTMLElement {
* It progressively reduces step sizes to refine the distance calculations.
*/
private runJFA() {
const maxDimension = Math.max(this.canvas.width, this.canvas.height);
let stepSize = Math.pow(2, Math.floor(Math.log2(maxDimension)));
const minStepSize = 1;
let stepSize = 1 << Math.floor(Math.log2(Math.max(this.canvas.width, this.canvas.height)));
// Perform passes with decreasing step sizes
while (stepSize >= minStepSize) {
for (; stepSize >= 1; stepSize >>= 1) {
this.renderPass(stepSize);
stepSize = Math.floor(stepSize / 2);
}
// Render the final result to the screen
@ -403,13 +403,19 @@ export class DistanceField extends HTMLElement {
* @returns A Float32Array of offsets.
*/
private computeOffsets(stepSize: number): Float32Array {
const offsets: number[] = [];
if (this.offsetCache.has(stepSize)) {
return this.offsetCache.get(stepSize)!;
}
const offsets = [];
for (let y = -1; y <= 1; y++) {
for (let x = -1; x <= 1; x++) {
offsets.push((x * stepSize) / this.canvas.width, (y * stepSize) / this.canvas.height);
}
}
return new Float32Array(offsets);
const offsetArray = new Float32Array(offsets);
this.offsetCache.set(stepSize, offsetArray);
return offsetArray;
}
/**
@ -448,7 +454,7 @@ export class DistanceField extends HTMLElement {
}
// Clear other references
this.geometries = null!;
this.shapes = null!;
}
}
@ -526,6 +532,12 @@ out vec4 outColor;
uniform sampler2D u_texture;
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main() {
vec4 texel = texture(u_texture, v_texCoord);
@ -533,12 +545,9 @@ void main() {
float shapeID = texel.z;
float distance = texel.a;
// Generate a hash-based color for each shape
vec3 shapeColor = vec3(
fract(sin(shapeID * 12.9898) * 43758.5453),
fract(sin(shapeID * 78.233) * 43758.5453),
fract(sin(shapeID * 93.433) * 43758.5453)
);
float hue = fract(shapeID * 0.61803398875); // Golden ratio conjugate
vec3 shapeColor = hsv2rgb(vec3(hue, 0.5, 0.95));
// Visualize distance as intensity
float intensity = exp(-distance * 10.0);

View File

@ -41,10 +41,7 @@ export class WebGLUtils {
// Set canvas styles
canvas.style.position = 'absolute';
canvas.style.top = '0';
canvas.style.left = '0';
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.inset = '0';
canvas.style.zIndex = '-1';
canvas.width = width;