diorama dirty flag
This commit is contained in:
93
engine.js
93
engine.js
@@ -105,13 +105,14 @@ export class Engine {
|
||||
|
||||
this.dioramas = [];
|
||||
this.frameMeshes = new Map();
|
||||
|
||||
this.renderRequested = false;
|
||||
this.observedFrames = new Set();
|
||||
this.updateRequested = false;
|
||||
|
||||
window.addEventListener('resize', () => this.requestRender());
|
||||
this.observedFrames = new Set();
|
||||
this.dirtyFrames = new Set(); // Tracks specific timelines that need mesh updates
|
||||
|
||||
// to make sure offscreen dioramas are rendered as soon as they become visible
|
||||
window.addEventListener('resize', () => this.requestRender());
|
||||
window.addEventListener('scroll', () => this.requestRender());
|
||||
}
|
||||
|
||||
@@ -137,26 +138,41 @@ export class Engine {
|
||||
|
||||
addDiorama(diorama) {
|
||||
this.dioramas.push(diorama);
|
||||
diorama.dirty = true;
|
||||
|
||||
if (!this.observedFrames.has(diorama.frame)) {
|
||||
this.observedFrames.add(diorama.frame);
|
||||
diorama.frame.subscribe(() => this.requestUpdate());
|
||||
this.dirtyFrames.add(diorama.frame); // Force initial build
|
||||
|
||||
diorama.frame.subscribe(() => {
|
||||
this.dirtyFrames.add(diorama.frame);
|
||||
// Mark any diorama observing this shared timeline as dirty
|
||||
for (const d of this.dioramas) {
|
||||
if (d.frame === diorama.frame) d.dirty = true;
|
||||
}
|
||||
this.requestUpdate();
|
||||
});
|
||||
} else {
|
||||
this.requestRender();
|
||||
}
|
||||
}
|
||||
|
||||
async updateAll() {
|
||||
const uniqueFrames = new Set(this.dioramas.map(d => d.frame));
|
||||
if (uniqueFrames.size === 0) return;
|
||||
if (this.dirtyFrames.size === 0) return;
|
||||
|
||||
// Isolate only the frames that mutated
|
||||
const framesToUpdate = Array.from(this.dirtyFrames);
|
||||
this.dirtyFrames.clear();
|
||||
|
||||
const uniqueBlockIds = new Set();
|
||||
for (const frame of uniqueFrames) {
|
||||
for (const frame of framesToUpdate) {
|
||||
for (const block of frame.blocks.values()) uniqueBlockIds.add(block.id);
|
||||
}
|
||||
await Promise.all(Array.from(uniqueBlockIds).map(id => this.cache.get(id, 'blockstates')));
|
||||
|
||||
const uniqueModelIds = new Set();
|
||||
const parsedFrames = new Map();
|
||||
|
||||
for (const frame of uniqueFrames) {
|
||||
const uniqueModelIds = new Set();
|
||||
for (const frame of framesToUpdate) {
|
||||
const parsedBlocks = [];
|
||||
for (const block of frame.blocks.values()) {
|
||||
const state = await this.cache.get(block.id, 'blockstates');
|
||||
@@ -170,8 +186,7 @@ export class Engine {
|
||||
|
||||
const textureTasks = [];
|
||||
const framePools = new Map();
|
||||
|
||||
for (const frame of uniqueFrames) {
|
||||
for (const frame of framesToUpdate) {
|
||||
const instancePool = new Map();
|
||||
for (const {block, parts} of parsedFrames.get(frame)) {
|
||||
for (const part of parts) {
|
||||
@@ -211,7 +226,16 @@ export class Engine {
|
||||
await Promise.all(textureTasks);
|
||||
this.updateAtlasTexture();
|
||||
|
||||
for (const frame of uniqueFrames) {
|
||||
for (const frame of framesToUpdate) {
|
||||
// Memory Cleanup: Free old buffers from the GPU before creating new ones
|
||||
const oldMeshes = this.frameMeshes.get(frame);
|
||||
if (oldMeshes) {
|
||||
for (const mesh of oldMeshes) {
|
||||
this.gl.deleteVertexArray(mesh.vao);
|
||||
for (const buf of mesh.buffers) this.gl.deleteBuffer(buf);
|
||||
}
|
||||
}
|
||||
|
||||
const newMeshes = [];
|
||||
for (const pool of framePools.get(frame).values()) {
|
||||
const blockModel = await this.cache.get(pool.partDef.model, 'models');
|
||||
@@ -226,6 +250,11 @@ export class Engine {
|
||||
this.frameMeshes.set(frame, newMeshes);
|
||||
}
|
||||
|
||||
// Guarantee that dioramas relying on these newly built meshes are flagged for drawing
|
||||
for (const d of this.dioramas) {
|
||||
if (framesToUpdate.includes(d.frame)) d.dirty = true;
|
||||
}
|
||||
|
||||
this.requestRender();
|
||||
}
|
||||
|
||||
@@ -242,8 +271,11 @@ export class Engine {
|
||||
const vao = gl.createVertexArray();
|
||||
gl.bindVertexArray(vao);
|
||||
|
||||
const buffers = []; // Track buffers for GC
|
||||
|
||||
const bindGeomAttr = (loc, data, size) => {
|
||||
const buffer = gl.createBuffer();
|
||||
buffers.push(buffer);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
|
||||
gl.enableVertexAttribArray(loc);
|
||||
@@ -257,10 +289,12 @@ export class Engine {
|
||||
bindGeomAttr(4, geometry.shades, 1);
|
||||
|
||||
const ebo = gl.createBuffer();
|
||||
buffers.push(ebo);
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo);
|
||||
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, geometry.indices, gl.STATIC_DRAW);
|
||||
|
||||
const matrixBuffer = gl.createBuffer();
|
||||
buffers.push(matrixBuffer);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, matrices, gl.STATIC_DRAW);
|
||||
|
||||
@@ -272,6 +306,7 @@ export class Engine {
|
||||
}
|
||||
|
||||
const colorBuffer = gl.createBuffer();
|
||||
buffers.push(colorBuffer);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
|
||||
gl.enableVertexAttribArray(9);
|
||||
@@ -279,21 +314,20 @@ export class Engine {
|
||||
gl.vertexAttribDivisor(9, 1);
|
||||
|
||||
gl.bindVertexArray(null);
|
||||
return {vao, indexCount: geometry.indices.length, instanceCount};
|
||||
return {vao, buffers, indexCount: geometry.indices.length, instanceCount};
|
||||
}
|
||||
|
||||
render() {
|
||||
const gl = this.gl;
|
||||
|
||||
gl.useProgram(this.program);
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.atlasTexture);
|
||||
gl.uniform1i(this.uniforms.texture, 0);
|
||||
let setupProgram = false;
|
||||
|
||||
for (const diorama of this.dioramas) {
|
||||
const rect = diorama.canvas.getBoundingClientRect();
|
||||
|
||||
if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth) continue;
|
||||
// Visibility Culling
|
||||
if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const targetW = Math.floor(rect.width * dpr);
|
||||
@@ -302,11 +336,23 @@ export class Engine {
|
||||
if (diorama.canvas.width !== targetW || diorama.canvas.height !== targetH) {
|
||||
diorama.canvas.width = targetW;
|
||||
diorama.canvas.height = targetH;
|
||||
diorama.dirty = true;
|
||||
}
|
||||
|
||||
// Lazy Render: Only draw if the diorama actually changed
|
||||
if (!diorama.dirty) continue;
|
||||
|
||||
if (!setupProgram) {
|
||||
gl.useProgram(this.program);
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.atlasTexture);
|
||||
gl.uniform1i(this.uniforms.texture, 0);
|
||||
setupProgram = true;
|
||||
}
|
||||
|
||||
if (this.canvas.width < targetW || this.canvas.height < targetH) {
|
||||
this.canvas.width = targetW;
|
||||
this.canvas.height = targetH;
|
||||
this.canvas.width = Math.max(this.canvas.width, targetW);
|
||||
this.canvas.height = Math.max(this.canvas.height, targetH);
|
||||
}
|
||||
|
||||
gl.viewport(0, 0, targetW, targetH);
|
||||
@@ -333,6 +379,9 @@ export class Engine {
|
||||
0, 0, targetW, targetH,
|
||||
0, 0, targetW, targetH
|
||||
);
|
||||
|
||||
// Clean state!
|
||||
diorama.dirty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user