orb: startup self-check — verify float framebuffers complete and the simulation produces a sane particle, else fall back to the static layout with a console warning
This commit is contained in:
@ -724,7 +724,8 @@ export function startOrb(canvas, { onReady } = {}) {
|
|||||||
if (!gl) return false;
|
if (!gl) return false;
|
||||||
|
|
||||||
const hasF32 = !!gl.getExtension('EXT_color_buffer_float');
|
const hasF32 = !!gl.getExtension('EXT_color_buffer_float');
|
||||||
const interactive = hasF32 || !!gl.getExtension('EXT_color_buffer_half_float');
|
// let: dropped to false if the simulation turns out not to work on this GPU/driver (see the checks below)
|
||||||
|
let interactive = hasF32 || !!gl.getExtension('EXT_color_buffer_half_float');
|
||||||
const stateFmt = hasF32
|
const stateFmt = hasF32
|
||||||
? { internal: gl.RGBA32F, type: gl.FLOAT }
|
? { internal: gl.RGBA32F, type: gl.FLOAT }
|
||||||
: { internal: gl.RGBA16F, type: gl.HALF_FLOAT };
|
: { internal: gl.RGBA16F, type: gl.HALF_FLOAT };
|
||||||
@ -770,6 +771,19 @@ export function startOrb(canvas, { onReady } = {}) {
|
|||||||
// and the spawn point of the current life, so the costly spawn maths runs once per life
|
// and the spawn point of the current life, so the costly spawn maths runs once per life
|
||||||
const state = interactive ? pingPong(gl, T, T, stateFmt.internal, stateFmt.type, gl.NEAREST, 3) : null;
|
const state = interactive ? pingPong(gl, T, T, stateFmt.internal, stateFmt.type, gl.NEAREST, 3) : null;
|
||||||
let needInit = true;
|
let needInit = true;
|
||||||
|
const fallBack = (why) => {
|
||||||
|
if (!interactive) return;
|
||||||
|
interactive = false;
|
||||||
|
console.warn('orb: particle simulation unavailable on this GPU/driver, using the static layout.', why);
|
||||||
|
};
|
||||||
|
// some drivers accept float render targets but cannot complete a three-attachment framebuffer
|
||||||
|
if (state) {
|
||||||
|
const incomplete = [state.read, state.write].some((t) => {
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, t.fbo);
|
||||||
|
return gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE;
|
||||||
|
});
|
||||||
|
if (incomplete) fallBack('particle framebuffer incomplete');
|
||||||
|
}
|
||||||
|
|
||||||
// fluid fields; rebuilt when the viewport shape changes so the grid stays square-celled
|
// fluid fields; rebuilt when the viewport shape changes so the grid stays square-celled
|
||||||
let velocity, dye, pressure, divergence, curl, simW = 1, simH = 1;
|
let velocity, dye, pressure, divergence, curl, simW = 1, simH = 1;
|
||||||
@ -924,6 +938,23 @@ export function startOrb(canvas, { onReady } = {}) {
|
|||||||
let simT = reduced ? 8 : 0;
|
let simT = reduced ? 8 : 0;
|
||||||
let last = performance.now();
|
let last = performance.now();
|
||||||
let ready = false;
|
let ready = false;
|
||||||
|
let frameCount = 0;
|
||||||
|
|
||||||
|
// A few frames in, read one particle back: if the simulation produced nothing sane (all zeros, NaN),
|
||||||
|
// draw the static layout instead of an orb with no particles.
|
||||||
|
function verifySimulation() {
|
||||||
|
if (!hasF32) return; // reading half-float targets back as float is not guaranteed
|
||||||
|
try {
|
||||||
|
const px = new Float32Array(4);
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, state.read.fbo);
|
||||||
|
gl.readBuffer(gl.COLOR_ATTACHMENT0);
|
||||||
|
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.FLOAT, px); // particle 0: an inner particle, always inside the ball
|
||||||
|
const r = Math.hypot(px[0], px[1], px[2]);
|
||||||
|
if (!(Number.isFinite(r) && r > 0.2 && r < 1.0)) fallBack(`particle 0 read back as (${Array.from(px).join(', ')})`);
|
||||||
|
} catch (e) {
|
||||||
|
fallBack(String(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function commonUniforms(prog, t) {
|
function commonUniforms(prog, t) {
|
||||||
gl.uniform1f(prog.u.uTime, t);
|
gl.uniform1f(prog.u.uTime, t);
|
||||||
@ -1004,6 +1035,7 @@ export function startOrb(canvas, { onReady } = {}) {
|
|||||||
gl.uniform1f(s.u.uDt, dt);
|
gl.uniform1f(s.u.uDt, dt);
|
||||||
gl.uniform1f(s.u.uGain, 0.5);
|
gl.uniform1f(s.u.uGain, 0.5);
|
||||||
stepState();
|
stepState();
|
||||||
|
if (++frameCount === 5) verifySimulation();
|
||||||
}
|
}
|
||||||
|
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||||
|
|||||||
Reference in New Issue
Block a user