| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758 |
- import { clampResolution, motionResolution } from './renderResolution.js'
- import { frameIntervalP95, isContinuousGap, stepAdaptiveResolution } from './adaptiveResolution.js'
- /** One owner combines motion caps and adaptive decisions. Static views always use base quality.
- * applyScale checks the real renderer value; decisions commit even when no physical write is needed.
- * No viewer/Cesium dependency, and no wall-clock sleeps in tests.
- */
- export function createRenderResolutionController({ applyScale, now, options = {} }) {
- let base, movingCap, floor, enabled, moving = false, adaptiveTarget
- let intervals = [], lastSampleAt = null, lastEvaluationAt = -Infinity
- let hotSinceMs = null, coldSinceMs = null, p95Ms = null, writes = 0
- const clearSamples = () => {
- intervals = []; lastSampleAt = null; lastEvaluationAt = -Infinity
- hotSinceMs = coldSinceMs = p95Ms = null
- }
- const readOptions = values => {
- base = clampResolution(values.resolutionScale) ?? .75
- movingCap = motionResolution({ base, moving: values.movingResolutionScale ?? .6, isMoving: true })
- floor = Math.min(movingCap, clampResolution(values.adaptiveFloor) ?? .55)
- enabled = values.adaptiveResolution !== false
- adaptiveTarget = movingCap
- }
- const effectiveTarget = () => moving ? Math.min(movingCap, adaptiveTarget) : base
- const apply = () => {
- const changed = !!applyScale(effectiveTarget())
- if (changed) writes++
- return changed
- }
- readOptions(options)
- return {
- configure(values = {}) { readOptions(values); clearSamples(); return apply() },
- setMoving(value) {
- if (moving !== (value === true)) { moving = value === true; clearSamples() }
- return apply()
- },
- noteFrameInterval(value) {
- if (!moving || !enabled || !isContinuousGap(value)) return false
- intervals.push(Number(value)); if (intervals.length > 90) intervals.shift()
- lastSampleAt = now()
- return true
- },
- tick() {
- if (!moving || !enabled) return false
- const at = now()
- if (lastSampleAt === null || at - lastSampleAt > 1500) { clearSamples(); return false }
- if (at - lastEvaluationAt < 500) return false
- p95Ms = frameIntervalP95(intervals)
- if (p95Ms === null) return false
- lastEvaluationAt = at
- const step = stepAdaptiveResolution({ p95Ms, currentScale: Math.min(movingCap, adaptiveTarget), baseScale: movingCap, floorScale: floor, nowMs: at, hotSinceMs, coldSinceMs })
- hotSinceMs = step.hotSinceMs; coldSinceMs = step.coldSinceMs
- // State progresses even if motion already wrote the same target to the framebuffer.
- adaptiveTarget = step.scale
- return step.changed ? apply() : false
- },
- status() { return { scale: effectiveTarget(), adaptiveTarget, moving, baseScale: base, movingCap, floorScale: floor, enabled, samples: intervals.length, p95Ms, writes } },
- }
- }
|