mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
136 lines
4.8 KiB
Python
136 lines
4.8 KiB
Python
"""Render driver.
|
|
python render.py still T [T ...] [--scale S] [--samples N] [--only name] -> out/still_T.png
|
|
python render.py sheet T0 T1 N [--scale S] -> out/sheet_T0_T1.jpg
|
|
python render.py video T0 T1 [--scale S] [--samples N] [--out file] -> mp4 (with music)
|
|
python render.py final [--samples N] -> out/mobilegl_hype.mp4
|
|
MGL_LIBGL=<path to opengl32.dll> renders through that GL implementation."""
|
|
import os
|
|
import sys
|
|
import time
|
|
import subprocess
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
import score as S
|
|
from engine import Engine
|
|
from anim import Frame, frame_idx
|
|
import importlib
|
|
scenes = importlib.import_module(sys.argv[sys.argv.index('--module') + 1] if '--module' in sys.argv else 'scenes')
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
OUT = os.path.join(HERE, 'out')
|
|
FPS = int(sys.argv[sys.argv.index('--fps') + 1]) if '--fps' in sys.argv else 60
|
|
|
|
|
|
def opt(name, default, cast=float):
|
|
if name in sys.argv:
|
|
return cast(sys.argv[sys.argv.index(name) + 1])
|
|
return default
|
|
|
|
|
|
def draw_frame(E, t, fi):
|
|
n = E.samples
|
|
P = None
|
|
for s in range(n):
|
|
ts = t + ((s + 0.5) / n - 0.5) * E.shutter / FPS if n > 1 else t
|
|
p = scenes.draw(E, ts)
|
|
if s == n // 2 or P is None:
|
|
P = p
|
|
E.accumulate(1.0 / n, s == 0)
|
|
E.post(P or {}, fi)
|
|
|
|
|
|
def grab(E):
|
|
a = np.frombuffer(E.read(), np.uint8).reshape(E.H, E.W, 4)
|
|
return a
|
|
|
|
|
|
def make_engine():
|
|
E = Engine(scale=opt('--scale', 1.0), samples=opt('--samples', 1, int), shutter=opt('--shutter', 0.5))
|
|
if 'MobileGL' in E.renderer:
|
|
import sc_final
|
|
gpu = E.renderer.rsplit('(', 1)[1].split(',')[0] if '(' in E.renderer else 'GPU'
|
|
backend = 'Magma → Vulkan' if 'Vulkan' in E.renderer else 'Espryt → OpenGL ES'
|
|
sc_final.RENDERED_WITH['line'] = f'{backend} · {gpu} · Windows / WGL'
|
|
return E
|
|
|
|
|
|
def still(times):
|
|
E = make_engine()
|
|
print('renderer:', E.renderer)
|
|
paths = []
|
|
for t in times:
|
|
draw_frame(E, t, frame_idx(t))
|
|
p = os.path.join(OUT, f'still_{t:07.2f}.png')
|
|
Image.fromarray(grab(E)[:, :, :3]).save(p)
|
|
paths.append(p)
|
|
print('\n'.join(paths))
|
|
|
|
|
|
def sheet(t0, t1, n, cols=4):
|
|
E = make_engine()
|
|
imgs = []
|
|
for k in range(n):
|
|
t = t0 + (t1 - t0) * k / max(n - 1, 1)
|
|
draw_frame(E, t, frame_idx(t))
|
|
im = Image.fromarray(grab(E)[:, :, :3]).resize((480, 270), Image.LANCZOS)
|
|
imgs.append((t, im))
|
|
rows = (n + cols - 1) // cols
|
|
sh = Image.new('RGB', (cols * 484, rows * 290), (30, 30, 30))
|
|
from PIL import ImageDraw
|
|
d = ImageDraw.Draw(sh)
|
|
for k, (t, im) in enumerate(imgs):
|
|
x, y = (k % cols) * 484 + 2, (k // cols) * 290 + 2
|
|
sh.paste(im, (x, y))
|
|
d.text((x + 4, y + 272), f'{t:.2f}s bar {t / S.BAR:.2f}', fill=(220, 220, 220))
|
|
p = os.path.join(OUT, f'sheet_{t0:.1f}_{t1:.1f}.jpg')
|
|
sh.save(p, quality=88)
|
|
print(p)
|
|
|
|
|
|
def video(t0, t1, out, audio=True, crf=16, preset='slow'):
|
|
E = make_engine()
|
|
print('renderer:', E.renderer)
|
|
f0, f1 = int(round(t0 * FPS)), int(round(t1 * FPS))
|
|
args = ['ffmpeg', '-y', '-loglevel', 'error', '-f', 'rawvideo', '-pix_fmt', 'rgba', '-s', f'{E.W}x{E.H}',
|
|
'-r', str(FPS), '-i', '-']
|
|
wav = opt('--audio', os.path.join(OUT, 'music.wav'), str)
|
|
if audio and os.path.exists(wav):
|
|
args += ['-ss', f'{t0:.4f}', '-t', f'{t1 - t0:.4f}', '-i', wav, '-c:a', 'aac', '-b:a', '256k']
|
|
args += ['-c:v', 'libx264', '-preset', preset, '-crf', str(crf), '-pix_fmt', 'yuv420p',
|
|
'-movflags', '+faststart', '-shortest' if audio else '-an', out]
|
|
args = [a for a in args if a]
|
|
proc = subprocess.Popen(args, stdin=subprocess.PIPE)
|
|
start = time.time()
|
|
for fi in range(f0, f1):
|
|
t = fi / FPS
|
|
draw_frame(E, t, fi)
|
|
proc.stdin.write(E.read())
|
|
if (fi - f0) % 300 == 0:
|
|
el = time.time() - start
|
|
done = fi - f0 + 1
|
|
print(f'frame {fi} ({t:.1f}s) {done / max(el, 1e-6):.1f} fps', flush=True)
|
|
proc.stdin.close()
|
|
proc.wait()
|
|
print(out, f'{time.time() - start:.0f}s')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
os.makedirs(OUT, exist_ok=True)
|
|
mode = sys.argv[1]
|
|
nums = []
|
|
for a in sys.argv[2:]:
|
|
if a.startswith('--'):
|
|
break
|
|
nums.append(a)
|
|
if mode == 'still':
|
|
still([float(x) for x in nums])
|
|
elif mode == 'sheet':
|
|
sheet(float(nums[0]), float(nums[1]), int(nums[2]))
|
|
elif mode == 'video':
|
|
video(float(nums[0]), float(nums[1]), opt('--out', os.path.join(OUT, 'clip.mp4'), str),
|
|
crf=opt('--crf', 18, int), preset=opt('--preset', 'veryfast', str))
|
|
elif mode == 'final':
|
|
video(0.0, S.END, opt('--out', os.path.join(OUT, 'mobilegl_hype.mp4'), str), crf=opt('--crf', 16, int),
|
|
preset=opt('--preset', 'slow', str))
|