mirror of
https://github.com/stevenrobertson/cuburn.git
synced 2025-03-14 07:21:29 -04:00
Finally, a working main.py again.
This commit is contained in:
parent
303accad16
commit
00890dd0d1
275
main.py
275
main.py
@ -1,9 +1,10 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
#
|
#
|
||||||
# flam3cuda, one of a surprisingly large number of ports of the fractal flame
|
# cuburn, one of a surprisingly large number of ports of the fractal flame
|
||||||
# algorithm to NVIDIA GPUs.
|
# algorithm to NVIDIA GPUs.
|
||||||
#
|
#
|
||||||
# This one is copyright 2010 Steven Robertson <steven@strobe.cc>
|
# This one is copyright 2010-2011, Steven Robertson <steven@strobe.cc>
|
||||||
|
# and Eric Reckase <e.reckase@gmail.com>.
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License version 2 or later
|
# it under the terms of the GNU General Public License version 2 or later
|
||||||
@ -12,6 +13,8 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import json
|
||||||
|
import warnings
|
||||||
import argparse
|
import argparse
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
from subprocess import Popen
|
from subprocess import Popen
|
||||||
@ -21,137 +24,57 @@ from itertools import ifilter
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import Image
|
import Image
|
||||||
import scipy
|
import scipy
|
||||||
import pycuda.autoinit
|
import pycuda.driver as cuda
|
||||||
|
|
||||||
import cuburn._pyflam3_hacks
|
from cuburn import genome, render
|
||||||
from fr0stlib import pyflam3
|
|
||||||
from cuburn import render
|
|
||||||
from cuburn.code.mwc import MWCTest
|
|
||||||
|
|
||||||
np.set_printoptions(precision=5, edgeitems=20)
|
profiles = {
|
||||||
|
'1080p': dict(fps=24, width=1920, height=1080, quality=3000, skip=0),
|
||||||
|
'720p': dict(fps=24, width=1280, height=720, quality=2500, skip=0),
|
||||||
|
'preview': dict(fps=24, width=640, height=360, quality=800, skip=1)
|
||||||
|
}
|
||||||
|
|
||||||
real_stdout = sys.stdout
|
def save(rframe):
|
||||||
|
noalpha = rframe.buf[:,:,:3]
|
||||||
def fmt_time(time):
|
|
||||||
# Format time in a lexically-ordered way that doesn't interfere with the
|
|
||||||
# typical case of ascending natural numbers
|
|
||||||
atime = abs(time)
|
|
||||||
dcml = ('-' if time < 0 else '') + ('%05d' % np.floor(atime))
|
|
||||||
frac = np.round((atime - np.floor(atime)) * 1000)
|
|
||||||
if frac:
|
|
||||||
return '%s_%03d' % (dcml, frac)
|
|
||||||
return dcml
|
|
||||||
|
|
||||||
def fmt_filename(args, time):
|
|
||||||
return os.path.join(args.dir, '%s_%s' % (args.name, fmt_time(time)))
|
|
||||||
|
|
||||||
def save(args, time, raw):
|
|
||||||
noalpha = raw[:,:,:3]
|
|
||||||
if args.raw:
|
|
||||||
real_stdout.write(buffer(np.uint8(noalpha * 255.0)))
|
|
||||||
sys.stderr.write('.')
|
|
||||||
return
|
|
||||||
|
|
||||||
name = fmt_filename(args, time)
|
|
||||||
img = scipy.misc.toimage(noalpha, cmin=0, cmax=1)
|
img = scipy.misc.toimage(noalpha, cmin=0, cmax=1)
|
||||||
img.save(name+'.png')
|
img.save(rframe.idx, quality=95)
|
||||||
|
print rframe.idx, rframe.gpu_time
|
||||||
|
|
||||||
if args.jpg is not None:
|
def main(args, prof):
|
||||||
img.save(name+'.jpg', quality=args.jpg)
|
import pycuda.autoinit
|
||||||
print 'saved', name
|
|
||||||
|
|
||||||
def error(msg):
|
gnm_str = args.flame.read()
|
||||||
print "Error:", msg
|
if '<' in gnm_str[:10]:
|
||||||
sys.exit(1)
|
flames = genome.XMLGenomeParser.parse(gnm_str)
|
||||||
|
if len(flames) != 1:
|
||||||
def main(args):
|
warnings.warn('%d flames in file, only using one.' % len(flames))
|
||||||
if args.test:
|
gnm = genome.convert_flame(flames[0])
|
||||||
MWCTest.test_mwc()
|
|
||||||
return
|
|
||||||
|
|
||||||
if args.raw:
|
|
||||||
sys.stdout = sys.stderr
|
|
||||||
|
|
||||||
genome_ptr, ngenomes = pyflam3.Genome.from_string(args.flame.read())
|
|
||||||
genomes = cast(genome_ptr, POINTER(pyflam3.Genome*ngenomes)).contents
|
|
||||||
|
|
||||||
if args.qs:
|
|
||||||
for g in genomes:
|
|
||||||
g.sample_density *= args.qs
|
|
||||||
g.ntemporal_samples = max(1, int(g.ntemporal_samples * args.qs))
|
|
||||||
|
|
||||||
if args.width:
|
|
||||||
if not args.scale:
|
|
||||||
args.scale = float(args.width) / genomes[0].width
|
|
||||||
for g in genomes:
|
|
||||||
g.width = args.width
|
|
||||||
elif not np.all([g.width == genomes[0].width for g in genomes]):
|
|
||||||
error("Inconsistent width. Force with --width.")
|
|
||||||
|
|
||||||
if args.height:
|
|
||||||
for g in genomes:
|
|
||||||
g.height = args.height
|
|
||||||
elif not np.all([g.height == genomes[0].height for g in genomes]):
|
|
||||||
error("Inconsistent height. Force with --height.")
|
|
||||||
|
|
||||||
if args.scale:
|
|
||||||
for g in genomes:
|
|
||||||
g.pixels_per_unit *= args.scale
|
|
||||||
|
|
||||||
if args.skip and not args.tempscale:
|
|
||||||
args.tempscale = float(args.skip)
|
|
||||||
if args.tempscale:
|
|
||||||
for g in genomes:
|
|
||||||
g.temporal_filter_width *= args.tempscale
|
|
||||||
|
|
||||||
if not args.name:
|
|
||||||
if args.flame.name == '<stdin>':
|
|
||||||
args.name = genomes[0].name
|
|
||||||
else:
|
|
||||||
args.name = os.path.splitext(os.path.basename(args.flame.name))[0]
|
|
||||||
|
|
||||||
cp_times = [cp.time for cp in genomes]
|
|
||||||
if args.renumber is not None:
|
|
||||||
for t, g in enumerate(genomes):
|
|
||||||
g.time = t - args.renumber
|
|
||||||
cp_times = [cp.time for cp in genomes]
|
|
||||||
elif np.any(np.diff(cp_times) <= 0):
|
|
||||||
error("Genome times are non-monotonic. Try using --renumber.")
|
|
||||||
|
|
||||||
if len(cp_times) > 2:
|
|
||||||
if args.start is None:
|
|
||||||
# [1], not [0]; want to be inclusive on --start but exclude
|
|
||||||
# the first genome when --start is not passed
|
|
||||||
args.start = cp_times[1]
|
|
||||||
if args.end is None:
|
|
||||||
args.end = cp_times[-1]
|
|
||||||
if args.skip:
|
|
||||||
times = np.arange(args.start, args.end, args.skip)
|
|
||||||
else:
|
|
||||||
times = [t for t in cp_times if args.start <= t < args.end]
|
|
||||||
else:
|
else:
|
||||||
times = cp_times
|
gnm = json.loads(gnm_str)
|
||||||
|
gnm = genome.Genome(gnm)
|
||||||
|
err, times = gnm.set_profile(prof)
|
||||||
|
|
||||||
|
anim = render.Renderer()
|
||||||
|
anim.compile(gnm, keep=args.keep)
|
||||||
|
anim.load(gnm)
|
||||||
|
|
||||||
|
basename = os.path.basename(args.flame.name).rsplit('.', 1)[0] + '_'
|
||||||
|
if args.flame.name == '-':
|
||||||
|
basename = ''
|
||||||
|
prefix = os.path.join(args.dir, args.name or basename)
|
||||||
|
frames = [(prefix + '%05d.jpg' % (i+1), t) for i, t in enumerate(times)]
|
||||||
|
if args.end:
|
||||||
|
frames = frames[:args.end]
|
||||||
|
frames = frames[args.start::prof['skip']+1]
|
||||||
if args.resume:
|
if args.resume:
|
||||||
times = [t for t in times
|
frames = ifilter(lambda r: not os.path.isfile(r[0]), frames)
|
||||||
if not os.path.isfile(fmt_filename(args, t)+'.png')]
|
w, h = prof['width'], prof['height']
|
||||||
|
gen = anim.render(gnm, frames, w, h)
|
||||||
if times == []:
|
|
||||||
print 'No genomes to be rendered.'
|
|
||||||
return
|
|
||||||
|
|
||||||
anim = render.Animation(genomes)
|
|
||||||
if args.debug:
|
|
||||||
anim.cmp_options.append('-G')
|
|
||||||
anim.keep = args.keep or args.debug
|
|
||||||
anim.compile()
|
|
||||||
anim.load()
|
|
||||||
|
|
||||||
if args.gfx:
|
if args.gfx:
|
||||||
import pyglet
|
import pyglet
|
||||||
window = pyglet.window.Window(anim.features.width, anim.features.height)
|
window = pyglet.window.Window(w, h)
|
||||||
image = pyglet.image.CheckerImagePattern().create_image(
|
image = pyglet.image.CheckerImagePattern().create_image(w, h)
|
||||||
anim.features.width, anim.features.height)
|
|
||||||
label = pyglet.text.Label('Rendering first frame', x=5, y=5,
|
label = pyglet.text.Label('Rendering first frame', x=5, y=5,
|
||||||
font_size=24, bold=True)
|
font_size=24, bold=True)
|
||||||
|
|
||||||
@ -172,9 +95,8 @@ def main(args):
|
|||||||
|
|
||||||
last_time = [time.time()]
|
last_time = [time.time()]
|
||||||
|
|
||||||
frames = anim.render_frames(times, sync=args.sync)
|
|
||||||
def poll(dt):
|
def poll(dt):
|
||||||
out = next(frames, False)
|
out = next(gen, False)
|
||||||
if out is False:
|
if out is False:
|
||||||
if args.nopause:
|
if args.nopause:
|
||||||
pyglet.app.exit()
|
pyglet.app.exit()
|
||||||
@ -184,25 +106,23 @@ def main(args):
|
|||||||
elif out is not None:
|
elif out is not None:
|
||||||
real_dt = time.time() - last_time[0]
|
real_dt = time.time() - last_time[0]
|
||||||
last_time[0] = time.time()
|
last_time[0] = time.time()
|
||||||
ftime, buf = out
|
save(out)
|
||||||
save(args, ftime, buf)
|
imgbuf = np.uint8(out.buf.flatten() * 255)
|
||||||
imgbuf = np.uint8(buf.flatten() * 255)
|
image.set_data('RGBA', -w*4, imgbuf.tostring())
|
||||||
image.set_data('RGBA', -anim.features.width*4, imgbuf.tostring())
|
label.text = '%s (%g fps)' % (out.idx, 1./real_dt)
|
||||||
label.text = '%s %4g (%g fps)' % (args.name, ftime, 1./real_dt)
|
|
||||||
else:
|
else:
|
||||||
label.text += '.'
|
label.text += '.'
|
||||||
if args.sleep:
|
if args.sync:
|
||||||
time.sleep(args.sleep / 1000.)
|
cuda.Context.synchronize()
|
||||||
|
|
||||||
pyglet.clock.set_fps_limit(30)
|
pyglet.clock.set_fps_limit(30)
|
||||||
pyglet.clock.schedule_interval(poll, 1/30.)
|
pyglet.clock.schedule_interval(poll, 1/30.)
|
||||||
pyglet.app.run()
|
pyglet.app.run()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for ftime, out in ifilter(None, anim.render_frames(times, sync=args.sync)):
|
for out in gen:
|
||||||
save(args, ftime, out)
|
save(out)
|
||||||
if args.sleep:
|
if args.sync:
|
||||||
time.sleep(args.sleep / 1000.)
|
cuda.Context.synchronize()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description='Render fractal flames.')
|
parser = argparse.ArgumentParser(description='Render fractal flames.')
|
||||||
@ -211,68 +131,45 @@ if __name__ == "__main__":
|
|||||||
help="Path to genome file ('-' for stdin)")
|
help="Path to genome file ('-' for stdin)")
|
||||||
parser.add_argument('-g', action='store_true', dest='gfx',
|
parser.add_argument('-g', action='store_true', dest='gfx',
|
||||||
help="Show output in OpenGL window")
|
help="Show output in OpenGL window")
|
||||||
parser.add_argument('-j', metavar='QUALITY', nargs='?',
|
|
||||||
action='store', type=int, dest='jpg', const=90,
|
|
||||||
help="Write .jpg in addition to .png (default quality 90)")
|
|
||||||
parser.add_argument('-n', metavar='NAME', type=str, dest='name',
|
parser.add_argument('-n', metavar='NAME', type=str, dest='name',
|
||||||
help="Prefix to use when saving files (default is basename of input)")
|
help="Prefix to use when saving files (default is basename of input)")
|
||||||
parser.add_argument('-o', metavar='DIR', type=str, dest='dir',
|
parser.add_argument('-o', metavar='DIR', type=str, dest='dir',
|
||||||
help="Output directory", default='.')
|
help="Output directory", default='.')
|
||||||
parser.add_argument('--resume', action='store_true', dest='resume',
|
parser.add_argument('--resume', action='store_true', dest='resume',
|
||||||
help="Do not render any frame for which a .png already exists.")
|
help="Do not render any frame for which a .jpg already exists.")
|
||||||
parser.add_argument('--raw', action='store_true', dest='raw',
|
|
||||||
help="Do not write files; instead, send raw RGBA data to stdout.")
|
|
||||||
parser.add_argument('--nopause', action='store_true',
|
parser.add_argument('--nopause', action='store_true',
|
||||||
help="Don't pause after rendering when preview is up")
|
help="Don't pause after rendering the last frame when previewing")
|
||||||
|
|
||||||
seq = parser.add_argument_group('Sequence options', description="""
|
parser.add_argument('--keep', action='store_true', dest='keep',
|
||||||
Control which frames are rendered from a genome sequence. If '-k' is
|
|
||||||
not given, '-s' and '-e' act as limits, and any control point with a
|
|
||||||
time in bounds is rendered at its central time. If '-k' is given,
|
|
||||||
a list of times to render is given according to the semantics of
|
|
||||||
Python's range operator, as in range(start, end, skip).
|
|
||||||
|
|
||||||
If no options are given, all control points except the first and last
|
|
||||||
are rendered. If only one or two control points are passed, everything
|
|
||||||
gets rendered.""")
|
|
||||||
seq.add_argument('-s', dest='start', metavar='TIME', type=float,
|
|
||||||
help="Start time of image sequence (inclusive)")
|
|
||||||
seq.add_argument('-e', dest='end', metavar='TIME', type=float,
|
|
||||||
help="End time of image sequence (exclusive)")
|
|
||||||
seq.add_argument('-k', dest='skip', metavar='TIME', type=float,
|
|
||||||
help="Skip time between frames in image sequence. Auto-sets "
|
|
||||||
"--tempscale, use '--tempscale 1' to override.")
|
|
||||||
seq.add_argument('--renumber', metavar="TIME", type=float,
|
|
||||||
dest='renumber', nargs='?', const=0,
|
|
||||||
help="Renumber frame times, counting up from the supplied start time "
|
|
||||||
"(default is 0).")
|
|
||||||
|
|
||||||
genome = parser.add_argument_group('Genome options')
|
|
||||||
genome.add_argument('--qs', type=float, metavar='SCALE',
|
|
||||||
help="Scale quality and number of temporal samples")
|
|
||||||
genome.add_argument('--scale', type=float, metavar='SCALE',
|
|
||||||
help="Scale pixels per unit (camera zoom)")
|
|
||||||
genome.add_argument('--tempscale', type=float, metavar='SCALE',
|
|
||||||
help="Scale temporal filter width")
|
|
||||||
genome.add_argument('--width', type=int, metavar='PIXELS',
|
|
||||||
help="Use this width. Auto-sets scale, use '--scale 1' to override.")
|
|
||||||
genome.add_argument('--height', type=int, metavar='PIXELS',
|
|
||||||
help="Use this height (does *not* auto-set scale)")
|
|
||||||
|
|
||||||
debug = parser.add_argument_group('Debug options')
|
|
||||||
debug.add_argument('--test', action='store_true', dest='test',
|
|
||||||
help='Run some internal tests')
|
|
||||||
debug.add_argument('--keep', action='store_true', dest='keep',
|
|
||||||
help='Keep compilation directory (disables kernel caching)')
|
help='Keep compilation directory (disables kernel caching)')
|
||||||
debug.add_argument('--debug', action='store_true', dest='debug',
|
parser.add_argument('--sync', action='store_true', dest='sync',
|
||||||
help='Compile kernel with debugging enabled (implies --keep)')
|
|
||||||
debug.add_argument('--sync', action='store_true', dest='sync',
|
|
||||||
help='Use synchronous launches whenever possible')
|
help='Use synchronous launches whenever possible')
|
||||||
debug.add_argument('--sleep', metavar='MSEC', type=int, dest='sleep',
|
|
||||||
nargs='?', const='5',
|
parser.add_argument('--duration', type=float, metavar='TIME',
|
||||||
help='Sleep between invocations. Keeps a single-card system '
|
help="Set base duration in seconds (30)", default=30)
|
||||||
'usable. Implies --sync.')
|
parser.add_argument('--start', metavar='FRAME_NO', type=int,
|
||||||
|
default=0, help="First frame to render (inclusive)")
|
||||||
|
parser.add_argument('--end', metavar='FRAME_NO', type=int,
|
||||||
|
help="Last frame to render (exclusive, negative OK)")
|
||||||
|
|
||||||
|
prof = parser.add_argument_group('Profile options')
|
||||||
|
prof.add_argument('-p', dest='prof', choices=profiles.keys(),
|
||||||
|
default='preview', help='Set profile, specifying defaults for all '
|
||||||
|
'options below. (default: "preview")')
|
||||||
|
prof.add_argument('--skip', dest='skip', metavar='N', type=int,
|
||||||
|
help="Skip N frames between each rendered frame")
|
||||||
|
prof.add_argument('--quality', type=int, metavar='SPP',
|
||||||
|
help="Set base samples per pixel")
|
||||||
|
prof.add_argument('--fps', type=float, dest='fps',
|
||||||
|
help="Set frames per second")
|
||||||
|
prof.add_argument('--width', type=int, metavar='PX')
|
||||||
|
prof.add_argument('--height', type=int, metavar='PX')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
prof = dict(profiles[args.prof])
|
||||||
|
for k in prof:
|
||||||
|
if getattr(args, k) is not None:
|
||||||
|
prof[k] = getattr(args, k)
|
||||||
|
prof['duration'] = args.duration
|
||||||
|
|
||||||
main(args)
|
main(args, prof)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user