forked from arti/vt1200
71 lines
2.3 KiB
Python
71 lines
2.3 KiB
Python
"""Paint-cost model and build-time frame selection."""
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
from dataclasses import dataclass
|
|
from typing import Sequence
|
|
|
|
import numpy as np
|
|
|
|
from .movie import FrameRecord, TileRecord
|
|
from .tilepack import diff_tiles
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CostConstants:
|
|
word_us: float = 0.40
|
|
call_us: float = 3.0
|
|
rect_us: float = 1.0
|
|
|
|
|
|
def _words(width_px: int) -> int:
|
|
return max(1, int(math.ceil(width_px / 16.0)))
|
|
|
|
|
|
def estimate_record_us(record: FrameRecord, constants: CostConstants,
|
|
scale: int = 2, tile: int = 32) -> float:
|
|
"""Unit-time estimate for one record, including call and decode overhead."""
|
|
tile_words = _words(tile * scale) * tile * scale
|
|
total = 0.0
|
|
for rec in record.tiles:
|
|
total += tile_words * constants.word_us + constants.call_us
|
|
for rect in rec.rects:
|
|
total += (_words(rect.w * scale) * rect.h * scale
|
|
* constants.word_us)
|
|
total += constants.call_us + constants.rect_us
|
|
return total
|
|
|
|
|
|
def select_records(frames: Sequence[np.ndarray], constants: CostConstants,
|
|
budget_us: float = 13900.0,
|
|
max_skip: int = 2) -> list[FrameRecord]:
|
|
"""Build exact deltas, dropping source frames that cannot fit the gate.
|
|
|
|
The dropped frame is never emitted; the next kept frame is diffed from the
|
|
last *shown* frame and carries the accumulated ``ticks``. A run of heavy
|
|
frames is eventually emitted anyway (a cut), so the movie still advances.
|
|
"""
|
|
if not frames:
|
|
return []
|
|
prev = np.zeros_like(frames[0], dtype=bool)
|
|
out: list[FrameRecord] = []
|
|
pending = 0
|
|
skipped = 0
|
|
last = frames[0]
|
|
for current in frames:
|
|
pending += 1
|
|
last = current
|
|
rec_tiles = diff_tiles(prev, current, tile=32)
|
|
cost = estimate_record_us(FrameRecord(1, 0, rec_tiles), constants)
|
|
if cost <= budget_us or skipped >= max_skip:
|
|
out.append(FrameRecord(pending, 0, rec_tiles))
|
|
prev = current
|
|
pending = 0
|
|
skipped = 0
|
|
else:
|
|
skipped += 1
|
|
if pending:
|
|
rec_tiles = diff_tiles(prev, last, tile=32)
|
|
out.append(FrameRecord(pending, 0, rec_tiles))
|
|
return out
|