Files
vt1200/bad-apple/pipeline/cost.py
T

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