Scale the Navara atmosphere luminance without writing into takram's shared vectors, so a rebuilt globe is not drawn nine times too bright

The sky, aerial perspective and clouds materials hold the vectors of takram's AtmosphereParameters.DEFAULT, one per page. The look's scaler multiplied them in place and took its base from the first value it saw, so a Navara globe built later on the same page (after the parked one lost its WebGL context) could record an already scaled value as its base and draw the atmosphere scale² bright: a washed-out, nearly uniform frame. The fourth case of qa-strict-context-loss failed this way under CPU load (3 of 3 with eight busy threads; 3 of 3 pass now). Scaled uniforms now point at the scaler's own vectors, which it puts back each frame if a material wrote into them; the page's vectors are never written.
This commit is contained in:
German Khokhlov committed 2026-11-04 19:04:48 +04:00
1 parent b3efcfb7a2
commit bc1b56b63a
2 files changed
+97 -16

No files matched your search

+38 -16
View File
@@ -256,46 +256,67 @@ const HIDDEN = Object.freeze({ visible: false });
/**
* The atmosphere scale (viewOptions.js ATMOSPHERE_LUMINANCE_SCALE): each
* luminance factor is set to `scale` times the base value recorded the
* first time it is seen, never multiplied in place, so a vector Navara
* shares between materials, or copies into a rebuilt one, is never scaled
* twice. The stars keep the base value: when they share the sky's vector,
* they get their own. Returns `apply()`, true when it changed a uniform.
* first time it is seen, so a vector Navara shares between materials, or
* copies into a rebuilt one, is never scaled twice. A scaled uniform points
* at this scaler's own vector; the vector Navara built it with is never
* written. Those are takram's AtmosphereParameters.DEFAULT vectors, one per
* page: written in place, the next globe on the page (a rebuild after a lost
* context) recorded a scaled value as its base and drew the atmosphere
* scale² bright. The stars keep the base value. Returns `apply()`, true
* when it changed what a uniform holds.
*/
export function createLuminanceScaler(scale, forEachUniform, handles) {
if (scale === 1 || !forEachUniform) return () => false;
const bases = new Map(); // key -> { base, target }
const scaledVectors = new WeakSet();
// key -> the vectors uniforms point at, and the values they must hold
const bases = new Map();
let changed = false;
const entry = (key, value) => {
let found = bases.get(key);
if (!found) {
const baseValue = value.clone();
const targetValue = value.clone().multiplyScalar(scale);
found = {
base: value.clone(),
target: value.clone().multiplyScalar(scale),
base: baseValue.clone(),
target: targetValue.clone(),
baseValue,
targetValue,
};
bases.set(key, found);
}
return found;
};
// A material may write into the vector its uniform holds.
const restore = () => {
for (const { base, target, baseValue, targetValue } of bases.values()) {
if (!base.equals(baseValue)) {
base.copy(baseValue);
changed = true;
}
if (!target.equals(targetValue)) {
target.copy(targetValue);
changed = true;
}
}
};
let applyScale = true;
const record = (key, uniform) => entry(key, uniform.value);
const scaleOne = (key, uniform, scaled) => {
if (!scaled) return;
const { base, target } = entry(key, uniform.value);
const desired = applyScale ? target : base;
scaledVectors.add(uniform.value);
if (uniform.value.equals(desired)) return;
uniform.value.copy(desired);
changed = true;
if (uniform.value === desired) return;
if (!uniform.value.equals(desired)) changed = true;
uniform.value = desired;
};
const keepBase = (key, uniform, scaled) => {
if (scaled) return;
const { base } = entry(key, uniform.value);
if (scaledVectors.has(uniform.value)) {
const { base, target } = entry(key, uniform.value);
// Never this scaler's own vectors: they change with the scale.
if (uniform.value === target || uniform.value === base) {
if (!uniform.value.equals(base)) changed = true;
uniform.value = base.clone();
changed = true;
} else if (!uniform.value.equals(base)) {
uniform.value.copy(base);
uniform.value = base.clone();
changed = true;
}
};
@@ -304,6 +325,7 @@ export function createLuminanceScaler(scale, forEachUniform, handles) {
changed = false;
// Bases first, from the values as Navara built them.
forEachUniform(handles, record);
restore();
forEachUniform(handles, scaleOne);
forEachUniform(handles, keepBase);
return changed;
+59
View File
@@ -539,3 +539,62 @@ test('the cockpit sets the cinematic clouds cover and null gives the look its ow
assert.equal(mobile.hasClouds, false);
assert.equal(mobile.setCloudCoverage(0.5), false);
});
test('a second look on the page scales the shared atmosphere vectors from their base, not twice', () => {
// takram's materials hold AtmosphereParameters.DEFAULT's vectors: one
// per page, shared by every material of every globe.
const pageDefault = factors();
const sharing = () => ({
[SUN]: { value: pageDefault[SUN].value },
[SKY]: { value: pageDefault[SKY].value },
});
const build = () => {
const materials = {
sky: sharing(),
stars: sharing(),
aerialPerspective: sharing(),
clouds: sharing(),
};
const look = buildNavaraLook(
fakeView(0),
plugin([]),
navaraScenePlan('cinematic'),
() => 0,
fakeInternals(materials),
);
look.frame(900);
look.frame(900);
return materials;
};
const first = build();
near(first.sky[SKY].value.x, 1.5 * k);
assert.equal(
pageDefault[SKY].value.x,
1.5,
'the shared vector is not written',
);
// The first globe's context is lost and a new one is built on the page.
const second = build();
near(second.sky[SKY].value.x, 1.5 * k);
near(second.clouds[SUN].value.x, 1.3 * k);
assert.equal(second.stars[SKY].value.x, 1.5);
near(first.sky[SKY].value.x, 1.5 * k);
});
test('a value a material writes into the scaled vector is put back on the next frame', () => {
const materials = { sky: factors(), aerialPerspective: factors() };
const look = buildNavaraLook(
fakeView(0),
plugin([]),
navaraScenePlan('cinematic'),
() => 0,
fakeInternals(materials),
);
look.frame(900);
look.frame(900);
// The shared scaled vector, rewritten in place to the base.
materials.sky[SKY].value.copy(vec(1.5, 0.9, 0.8));
assert.equal(look.frame(900), true);
near(materials.aerialPerspective[SKY].value.x, 1.5 * k);
assert.equal(look.frame(900), false);
});