<?phpnamespace JumPHP\Shorts;use JumPHP\Kernel\Platform;use JumPHP\Renderer\Renderer;/** * How much a scene may draw. A film (video:make, JUMPHP_VIDEO_OUT) is always at full quality, nothing here touches it. A game played * live is `live`: shapes come from Sprites, and on a phone the picture is drawn at most 3x the view, then, if the frame rate falls * under about 45 fps for a second, one step lighter (level 2 -> 1: drawn at 2x, fewer sparks and halos; 1 -> 0: no halos, a third of * the sparks). It only ever goes down, one step at a time with a pause between them, so it never flickers between two looks. * JUMPHP_QUALITY=0|1|2 (env or page) fixes the level and stops the adaptation. */final class Quality{ /** playing live: use the fast shapes (false while a film is rendered, and until begin() is called) */ public static bool $live = false; /** JUMPHP_PROBE: the scenes time their update / render / audio (Quality::$probe) */ public static bool $probing = false; /** 2 full, 1 lighter, 0 lightest */ public static int $level = 2; private static bool $fixed = false; private static bool $started = false; private static float $last = 0.0; private static float $ema = 0.0167; private static int $slow = 0; private static int $pause = 0; /** Call in the scene's enter(): $recording = Recorder::isRecording(). */ public static function begin(Renderer $r, bool $recording): void { self::$live = !$recording; self::$probing = (bool)Platform::setting('JUMPHP_PROBE'); self::$last = 0.0; self::$slow = 0; if (!self::$live) { self::$level = 2; return; } // a new scene starts one step better than the last one ended (a heavy scene must not leave the light ones dull), and its // first moments (textures and voices being made) are not measured if (!self::$fixed && self::$level < 2) { self::$level++; } self::$pause = 120; self::$ema = 0.0167; $forced = Platform::setting('JUMPHP_QUALITY'); if ($forced !== null && $forced !== '') { self::$level = max(0, min(2, (int)$forced)); self::$fixed = true; } if (!self::$started) { self::$started = true; if (Platform::isMobile() && !self::$fixed && $r->hd() > 3) { $r->setHd(3); } if (self::$fixed && self::$level < 2) { $r->setHd(2); } } } /** Call once per frame (the scene's update()): watches the real time between two calls. */ public static function frame(Renderer $r): void { if (!self::$live || self::$fixed) { return; } $now = microtime(true); $dt = $now - self::$last; self::$last = $now; if ($dt <= 0 || $dt > 0.25 || $dt === $now) { // the first frame, a pause, a scene change: not a measure return; } self::$ema += 0.08 * ($dt - self::$ema); if (self::$pause > 0) { self::$pause--; return; } self::$slow = self::$ema > 0.0225 ? self::$slow + 1 : max(0, self::$slow - 2); if (self::$slow >= 60 && self::$level > 0) { self::$level--; self::$slow = 0; self::$pause = 180; self::$ema = 0.0167; if (self::$level === 1 && $r->hd() > 2) { $r->setHd(2); } } } /** How many sparks to make instead of $n. */ public static function count(int $n): int { if (!self::$live || self::$level >= 2) { return $n; } return max(1, (int)ceil($n * (self::$level === 1 ? 0.6 : 0.3))); } /** Halos and trails drawn? */ public static function halos(): bool { return !self::$live || self::$level >= 1; } /** @var array<string, float> seconds spent in the parts of the frame, when JUMPHP_PROBE is set (measuring) */ public static array $probe = []; /** Adds the time since $since to the part $name (JUMPHP_PROBE only); returns now. */ public static function lap(string $name, float $since): float { $now = microtime(true); self::$probe[$name] = (self::$probe[$name] ?? 0.0) + $now - $since; return $now; } /** Smoothed seconds per frame, for measuring. */ public static function frameTime(): float { return self::$ema; }}