multi target renderer instead of background scissor

This commit is contained in:
2026-07-04 23:10:38 -04:00
parent 3ba219697e
commit f248f9ac0f
3 changed files with 154 additions and 52 deletions

View File

@@ -62,20 +62,15 @@ function compileShader(gl, type, src) {
export class Engine {
constructor() {
// One solitary hidden canvas to drive all WebGL multi-target logic
this.canvas = document.createElement('canvas');
this.canvas.style.position = 'fixed';
this.canvas.style.top = '0';
this.canvas.style.left = '0';
this.canvas.style.width = '100vw';
this.canvas.style.height = '100vh';
this.canvas.style.zIndex = '-1';
this.canvas.style.display = 'none';
document.body.appendChild(this.canvas);
this.gl = this.canvas.getContext('webgl2', {antialias: true, alpha: true});
const gl = this.gl;
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.enable(gl.SCISSOR_TEST);
const vs = compileShader(gl, gl.VERTEX_SHADER, VS_SRC);
const fs = compileShader(gl, gl.FRAGMENT_SHADER, FS_SRC);
@@ -93,7 +88,6 @@ export class Engine {
this.atlasTexture = gl.createTexture();
// --- Initialize the new Resource Pipeline ---
this.cache = new Cache('assets');
this.atlas = new TextureHandler(256);
this.cache.register('blockstates', new BlockstateHandler());
@@ -103,22 +97,18 @@ export class Engine {
this.dioramas = [];
this.worldMeshes = new Map();
this.renderRequested = false;
this.observedWorlds = new Set();
this.updateRequested = false;
window.addEventListener('resize', () => {
this.resize();
this.requestRender();
});
window.addEventListener('scroll', () => this.requestRender(), {passive: true});
this.resize();
window.addEventListener('resize', () => this.requestRender());
// to make sure offscreen dioramas are rendered as soon as they become visible
window.addEventListener('scroll', () => this.requestRender());
}
requestUpdate() {
if (!this.updateRequested) {
this.updateRequested = true;
// Add 'async' here and 'await' updateAll
Promise.resolve().then(async () => {
await this.updateAll();
this.updateRequested = false;
@@ -136,14 +126,9 @@ export class Engine {
}
}
resize() {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
}
addDiorama(diorama) {
this.dioramas.push(diorama);
if (!this.observedWorlds.has(diorama.worlds)) {
if (!this.observedWorlds.has(diorama.world)) {
this.observedWorlds.add(diorama.world);
diorama.world.subscribe(() => this.requestUpdate());
}
@@ -153,14 +138,12 @@ export class Engine {
const uniqueWorlds = new Set(this.dioramas.map(d => d.world));
if (uniqueWorlds.size === 0) return;
// --- SWEEP 1: Collect & Fetch Blockstates ---
const uniqueBlockIds = new Set();
for (const world of uniqueWorlds) {
for (const block of world.blocks.values()) uniqueBlockIds.add(block.id);
}
await Promise.all(Array.from(uniqueBlockIds).map(id => this.cache.get(id, 'blockstates')));
// --- SWEEP 2: Resolve Parts & Fetch Models ---
const uniqueModelIds = new Set();
const parsedWorlds = new Map();
@@ -176,7 +159,6 @@ export class Engine {
}
await Promise.all(Array.from(uniqueModelIds).map(id => this.cache.get(id, 'models')));
// --- SWEEP 3: Pool Instances & Fetch Textures ---
const textureTasks = [];
const worldPools = new Map();
@@ -209,7 +191,6 @@ export class Engine {
for (const face of Object.values(el.faces || {})) {
const texPath = blockModel.resolveTexture(face.texture);
if (texPath) {
// Synchronously allocates UV, asynchronously fetches image
textureTasks.push(this.cache.get(texPath, 'textures', 'png'));
}
}
@@ -221,7 +202,6 @@ export class Engine {
await Promise.all(textureTasks);
this.updateAtlasTexture();
// --- SWEEP 4: Bake Geometry ---
for (const world of uniqueWorlds) {
const newMeshes = [];
for (const pool of worldPools.get(world).values()) {
@@ -295,23 +275,39 @@ export class Engine {
render() {
const gl = this.gl;
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.scissor(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(this.program);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.atlasTexture);
gl.uniform1i(this.uniforms.texture, 0);
// High-Performance Multi-Target Loop
for (const diorama of this.dioramas) {
const rect = diorama.element.getBoundingClientRect();
if (rect.bottom < 0 || rect.top > gl.canvas.height || rect.right < 0 || rect.left > gl.canvas.width) continue;
const rect = diorama.canvas.getBoundingClientRect();
const bottom = gl.canvas.height - rect.bottom;
gl.viewport(rect.left, bottom, rect.width, rect.height);
gl.scissor(rect.left, bottom, rect.width, rect.height);
// Fast Frustum Culling via native DOM properties
if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth) continue;
// Sync layout device-pixel ratios explicitly to prevent jagged texturing
const dpr = window.devicePixelRatio || 1;
const targetW = Math.floor(rect.width * dpr);
const targetH = Math.floor(rect.height * dpr);
// Guard: Only update diorama canvas internal buffers if physical dimensions shifted
if (diorama.canvas.width !== targetW || diorama.canvas.height !== targetH) {
diorama.canvas.width = targetW;
diorama.canvas.height = targetH;
}
// Apply size changes to hidden canvas ONLY when growing
if (this.canvas.width < targetW || this.canvas.height < targetH) {
this.canvas.width = targetW;
this.canvas.height = targetH;
}
gl.viewport(0, 0, targetW, targetH);
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
const aspect = rect.width / rect.height;
const viewProj = diorama.updateMatrices(aspect);
@@ -325,6 +321,13 @@ export class Engine {
gl.bindVertexArray(mesh.vao);
gl.drawElementsInstanced(gl.TRIANGLES, mesh.indexCount, gl.UNSIGNED_SHORT, 0, mesh.instanceCount);
}
diorama.ctx2d.clearRect(0, 0, targetW, targetH);
diorama.ctx2d.drawImage(
this.canvas,
0, 0, targetW, targetH, // Source sub-rect coordinates from WebGL
0, 0, targetW, targetH // Destination coordinates on 2D surface
);
}
}
}