ES
games/doom-jumphp / src / Engine / Screen.php · commit 8e1cb9f
<?phpnamespace App\Engine;use JumPHP\Kernel\Engine;use JumPHP\Renderer\Renderer;/** * Puts the 8-bit frame on the screen: palette -> RGBA in one strtr() (C speed), uploaded as one OpenGL / WebGL * texture and stretched over the view. Without GL (rare: a software SDL renderer) a BMP goes through GD. */final class Screen{    private int $texture = 0;    private ?array $map = null;    private ?array $wideMap = null;    private ?array $tint = null;    private ?bool $gl = null;    private int $fallbackFrame = 0;    public function __construct(private Engine $engine)    {    }    /** Damage (red) / pickup (yellow) flash: [r, g, b, amount 0..1], or null. Rebuilds the colour map. */    public function setTint(?array $tint): void    {        if ($tint === $this->tint) {            return;        }        $this->tint = $tint;        $this->map = null;        $this->wideMap = null;    }    /**     * $view: palette frame of $vw x $vh ($vw = 320 or 160: low detail, each pixel doubled), $bar: 320 x n status bar.     */    public function present(Renderer $r, string $view, int $vw, int $vh, string $bar, int $barH): void    {        $this->map ??= Palette::rgbaMap($this->tint);        if ($vw === 160) {            if ($this->wideMap === null) {                $this->wideMap = [];                foreach ($this->map as $k => $v) {                    $this->wideMap[$k] = $v . $v;                }            }            $rgba = strtr($view, $this->wideMap);        } else {            $rgba = strtr($view, $this->map);        }        if ($barH > 0) {            $rgba .= strtr($bar, $this->map);        }        $h = $vh + $barH;        $gl = $this->engine->opengl();        $this->gl ??= $gl->isAvailable();        if ($this->gl) {            // GL textures start at the bottom row            $rgba = implode('', array_reverse(str_split($rgba, 320 * 4)));            $this->texture = $gl->textureFromRgba(320, $h, $rgba, false, false, $this->texture ?: null);            if ($gl->begin(null)) {                $gl->blit($this->texture);                $gl->end();            }            return;        }        $this->fallback($r, $rgba, 320, $h);    }    private function fallback(Renderer $r, string $rgba, int $w, int $h): void    {        // RGBA -> BGRA, a top-down 32-bit BMP, read by GD        $bgra = '';        $n = strlen($rgba);        $bgra = $rgba;        for ($i = 0; $i < $n; $i += 4) {            $bgra[$i] = $rgba[$i + 2];            $bgra[$i + 2] = $rgba[$i];        }        $bmp = 'BM' . pack('VvvV', 54 + $n, 0, 0, 54) . pack('VVVvvVVVVVV', 40, $w, (-$h) & 0xFFFFFFFF, 1, 32, 0, $n, 2835, 2835, 0, 0) . $bgra;        $img = @imagecreatefromstring($bmp);        if ($img === false) {            return;        }        $name = 'frame' . ($this->fallbackFrame++ % 2);        $sdl = $this->engine->sdl;        $sdl->createTextureFromGd($name, $img);        $r->copy($sdl->getTexture($name)['texture'], null, 0, 0, $r->getWidth(), $r->getHeight());    }}