Checkpoint! Renders again. Many fixes outstanding.

This commit is contained in:
Steven Robertson 2012-04-10 08:44:25 -07:00
parent 9aa1a94aa1
commit b53f703e6e
17 changed files with 1646 additions and 992 deletions

View File

@ -260,13 +260,13 @@ haloclip(float4 *pixbuf, const float *denbuf, float gamma) {
colorcliplib = devlib(deps=[yuvlib], defs=r''' colorcliplib = devlib(deps=[yuvlib], defs=r'''
__global__ void __global__ void
colorclip(float4 *pixbuf, float gamma, float vibrance, float highpow, colorclip(float4 *pixbuf, float gamma, float vibrance, float highpow,
float linrange, float lingam, float3 bkgd) float linrange, float lingam)
{ {
GET_IDX(i); GET_IDX(i);
float4 pix = pixbuf[i]; float4 pix = pixbuf[i];
if (pix.w <= 0) { if (pix.w <= 0) {
pixbuf[i] = make_float4(bkgd.x, bkgd.y, bkgd.z, 0.0f); pixbuf[i] = make_float4(0, 0, 0, 0);
return; return;
} }
pix.y -= 0.5f * pix.w; pix.y -= 0.5f * pix.w;
@ -321,10 +321,6 @@ colorclip(float4 *pixbuf, float gamma, float vibrance, float highpow,
pix.y += (1.0f - vibrance) * powf(opix.y, gamma); pix.y += (1.0f - vibrance) * powf(opix.y, gamma);
pix.z += (1.0f - vibrance) * powf(opix.z, gamma); pix.z += (1.0f - vibrance) * powf(opix.z, gamma);
pix.x += (1.0f - alpha) * bkgd.x;
pix.y += (1.0f - alpha) * bkgd.y;
pix.z += (1.0f - alpha) * bkgd.z;
pix.x = fminf(1.0f, pix.x); pix.x = fminf(1.0f, pix.x);
pix.y = fminf(1.0f, pix.y); pix.y = fminf(1.0f, pix.y);
pix.z = fminf(1.0f, pix.z); pix.z = fminf(1.0f, pix.z);

View File

@ -2,16 +2,14 @@ from collections import OrderedDict
from itertools import cycle from itertools import cycle
import numpy as np import numpy as np
from cuburn.genome.use import Wrapper, SplineEval
import util import util
from util import Template, assemble_code, devlib, binsearchlib, ringbuflib from util import Template, assemble_code, devlib, binsearchlib, ringbuflib
from color import yuvlib from color import yuvlib
from mwc import mwclib from mwc import mwclib
class GenomePackerName(str): class PackerWrapper(Wrapper):
"""Class to indicate that a property is precalculated on the device"""
pass
class GenomePackerView(object):
""" """
Obtain accessors in generated code. Obtain accessors in generated code.
@ -25,47 +23,46 @@ class GenomePackerView(object):
code and an interpolator for use in generating that code. This conversion code and an interpolator for use in generating that code. This conversion
is done when the property is coerced into a string by the templating is done when the property is coerced into a string by the templating
mechanism, so you can easily nest objects by saying, for instance, mechanism, so you can easily nest objects by saying, for instance,
{{pcp.camera.rotation}} from within templated code. The accessed property {{pcp.camera.rotation}} from within templated code.
must be a SplEval object, or a precalculated value (see
``GenomePackerPrecalc``).
Index operations are converted to property accesses as well, so that you
don't have to make a mess with 'getattr' in your code: {{pcp.xforms[x]}}
works just fine. This means, however, that no arrays can be packed
directly; they must be converted to have string-based keys first, and
any loops must be unrolled in your code.
""" """
def __init__(self, packer, ptr_name, wrapped, prefix=()): def __init__(self, packer, val, spec=None, path=()):
self.packer = packer super(PackerWrapper, self).__init__(val, spec)
self.ptr_name = ptr_name self.packer, self.path = packer, path
self.wrapped = wrapped
self.prefix = prefix def wrap_dict(self, path, spec, val):
return type(self)(self.packer, val, spec, path)
def wrap_spline(self, path, spec, val):
return PackerSpline(self.packer, path, spec)
def __getattr__(self, name): def __getattr__(self, name):
w = getattr(self.wrapped, name) path = self.path + (name,)
return type(self)(self.packer, self.ptr_name, w, self.prefix+(name,)) if path in self.packer.packed_precalc:
# As with the Genome class, we're all-dict, no-array here return self.packer.devname(path)
__getitem__ = lambda s, n: getattr(s, str(n)) return super(PackerWrapper, self).__getattr__(name)
def _precalc(self):
"""Create a GenomePackerPrecalc object. See that class for details."""
return PrecalcWrapper(self.packer, self._val, self.spec, self.path)
class PackerSpline(object):
def __init__(self, packer, path, spec):
self.packer, self.path, self.spec = packer, path, spec
def __str__(self): def __str__(self):
""" """
Returns the packed name in a format suitable for embedding directly Returns the packed name in a format suitable for embedding directly
into device code. into device code.
""" """
# So evil. When the template calls __str__ to format the output, we # When the template calls __str__ to format one of these splines, this
# allocate things. This makes for neater embedded code, which is where # allocates the corresponding spline.
# the real complexity lies, but it also means printf() debugging when return self.packer._require(self.spec, self.path)
# templating will screw with the allocation tables!
if not isinstance(self.wrapped, GenomePackerName):
self.packer._require(self.prefix)
# TODO: verify namespace stomping, etc
return '%s.%s' % (self.ptr_name, '_'.join(self.prefix))
def _precalc(self): class PrecalcSpline(PackerSpline):
"""Create a GenomePackerPrecalc object. See that class for details.""" def __str__(self):
return GenomePackerPrecalc(self.packer, self.ptr_name, return self.packer._require_pre(self.spec, self.path)
self.wrapped, self.prefix)
class GenomePackerPrecalc(GenomePackerView): class PrecalcWrapper(PackerWrapper):
""" """
Insert precalculated values into the packed genome. Insert precalculated values into the packed genome.
@ -91,35 +88,24 @@ class GenomePackerPrecalc(GenomePackerView):
Example: Example:
def do_precalc(px): def do_precalc(pcam):
pcam = px._precalc()
pcam._code(Template(''' pcam._code(Template('''
{{pcam._set('prop_sin')}} = sin({{pcam.prop}}); {{pcam._set('prop_sin')}} = sin({{pcam.prop}});
''').substitute(pcam=pcam)) ''').substitute(pcam=pcam))
def gen_code(px): def gen_code(px):
return Template(''' return Template('''
{{do_precalc(px)}} {{do_precalc(px._precalc())}}
printf("The sin of %g is %g.", {{px.prop}}, {{px.prop_sin}}); printf("The sin of %g is %g.", {{px.prop}}, {{px.prop_sin}});
''').substitute(px=px) ''').substitute(px=px)
""" """
def __init__(self, packer, ptr_name, wrapped, prefix): def wrap_spline(self, path, spec, val):
super(GenomePackerPrecalc, self).__init__(packer, 'out', wrapped, prefix) return PrecalcSpline(self.packer, path, spec)
def __str__(self):
return self.packer._require_pre(self.prefix)
def _magscale(self):
"""
This is a temporary hack which turns on magnitude scaling for the
value on which it is called. Takes the place of __str__ serialization.
"""
return self.packer._require_pre(self.prefix, True)
def _set(self, name): def _set(self, name):
fullname = self.prefix + (name,) path = self.path + (name,)
self.packer._pre_alloc(fullname) return self.packer._pre_alloc(path)
# This just modifies the underlying object, because I'm too lazy right
# now to ghost the namespace
self.wrapped[name] = GenomePackerName('_'.join(fullname))
return '%s->%s' % (self.ptr_name, self.wrapped[name])
def _code(self, code): def _code(self, code):
self.packer.precalc_code.append(code) self.packer.precalc_code.append(code)
@ -127,26 +113,27 @@ class GenomePacker(object):
""" """
Packs a genome for use in iteration. Packs a genome for use in iteration.
""" """
def __init__(self, tname): def __init__(self, tname, ptr_name, spec):
""" """
Create a new DataPacker. Create a new DataPacker.
``tname`` is the name of the structure typedef that will be emitted ``tname`` is the name of the structure typedef that will be emitted
via this object's ``decls`` property. via this object's ``decls`` property.
""" """
self.tname = tname self.tname, self.ptr_name, self.spec = tname, ptr_name, spec
# We could do this in the order that things are requested, but we want # We could do this in the order that things are requested, but we want
# to be able to treat the direct stuff as a list so this function # to be able to treat the direct stuff as a list so this function
# doesn't unroll any more than it has to. So we separate things into # doesn't unroll any more than it has to. So we separate things into
# direct requests, and those that need precalculation. # direct requests, and those that need precalculation.
# Values of OrderedDict are unused; basically, it's just OrderedSet. # Values of OrderedDict are unused; basically, it's just OrderedSet.
self.packed_direct = OrderedDict() self.packed_direct = OrderedDict()
# Feel kind of bad about this, but it's just under the threshold of
# being worth refactoring to be agnostic to interpolation types
self.packed_direct_mag = OrderedDict()
self.genome_precalc = OrderedDict() self.genome_precalc = OrderedDict()
self.packed_precalc = OrderedDict() self.packed_precalc = OrderedDict()
self.precalc_code = [] self.precalc_code = []
self.ns = {}
self._len = None self._len = None
self.decls = None self.decls = None
self.defs = None self.defs = None
@ -156,29 +143,37 @@ class GenomePacker(object):
self.search_rounds = util.DEFAULT_SEARCH_ROUNDS self.search_rounds = util.DEFAULT_SEARCH_ROUNDS
def __len__(self): def __len__(self):
"""Length in elements. (*4 for length in bytes.)"""
assert self._len is not None, 'len() called before finalize()' assert self._len is not None, 'len() called before finalize()'
return self._len return self._len
def view(self, ptr_name, wrapped_obj, prefix): def view(self, val={}):
"""Create a DataPacker view. See DataPackerView class for details.""" """Create a DataPacker view. See DataPackerView class for details."""
self.ns[prefix] = wrapped_obj return PackerWrapper(self, val, self.spec)
return GenomePackerView(self, ptr_name, wrapped_obj, (prefix,))
def _require(self, name): def _require(self, spec, path):
""" """
Called to indicate that the named parameter from the original genome Called to indicate that the named parameter from the original genome
must be available during interpolation. must be available during interpolation.
""" """
self.packed_direct[name] = None if spec.interp == 'mag':
self.packed_direct_mag[path] = None
else:
self.packed_direct[path] = None
return self.devname(path)
def _require_pre(self, name, mag_scaling=False): def _require_pre(self, spec, path):
i = len(self.genome_precalc) << self.search_rounds i = len(self.genome_precalc) << self.search_rounds
self.genome_precalc[name] = None self.genome_precalc[path] = None
name = 'catmull_rom_mag' if mag_scaling else 'catmull_rom' func = 'catmull_rom_mag' if spec.interp == 'mag' else 'catmull_rom'
return '%s(&times[%d], &knots[%d], time)' % (name, i, i) return '%s(&times[%d], &knots[%d], time)' % (func, i, i)
def _pre_alloc(self, name): def _pre_alloc(self, path):
self.packed_precalc[name] = None self.packed_precalc[path] = None
return '%s->%s' % (self.ptr_name, '_'.join(path))
def devname(self, path):
return '%s.%s' % (self.ptr_name, '_'.join(path))
def finalize(self): def finalize(self):
""" """
@ -187,20 +182,18 @@ class GenomePacker(object):
# At the risk of packing a few things more than once, we don't # At the risk of packing a few things more than once, we don't
# uniquify the overall precalc order, sparing us the need to implement # uniquify the overall precalc order, sparing us the need to implement
# recursive code generation # recursive code generation
self.packed = self.packed_direct.keys() + self.packed_precalc.keys() direct = self.packed_direct.keys() + self.packed_direct_mag.keys()
self.genome = self.packed_direct.keys() + self.genome_precalc.keys() self.packed = direct + self.packed_precalc.keys()
self.genome = direct + self.genome_precalc.keys()
self._len = len(self.packed) self._len = len(self.packed)
decls = self._decls.substitute(packed=self.packed, tname=self.tname) decls = self._decls.substitute(**self.__dict__)
defs = self._defs.substitute( defs = self._defs.substitute(**self.__dict__)
packed_direct=self.packed_direct, tname=self.tname,
precalc_code=self.precalc_code,
search_rounds=self.search_rounds)
return devlib(deps=[catmullromlib], decls=decls, defs=defs) return devlib(deps=[catmullromlib], decls=decls, defs=defs)
def pack(self, pool=None): def pack(self, gnm, pool=None):
""" """
Return a packed copy of the genome ready for uploading to the GPU, Return a packed copy of the genome ready for uploading to the GPU,
as two float32 NDArrays for the knot times and values. as two float32 NDArrays for the knot times and values.
@ -213,40 +206,49 @@ class GenomePacker(object):
times, knots = np.empty((2, len(self.genome), width), 'f4') times, knots = np.empty((2, len(self.genome), width), 'f4')
times.fill(1e9) times.fill(1e9)
for idx, gname in enumerate(self.genome): for idx, path in enumerate(self.genome):
attr = self.ns[gname[0]] attr = gnm
for g in gname[1:]: for name in path:
attr = getattr(attr, g) attr = attr[name]
times[idx,:len(attr.knots[0])] = attr.knots[0] attr = SplineEval.normalize(attr)
knots[idx,:len(attr.knots[1])] = attr.knots[1] times[idx,:len(attr[0])] = attr[0]
knots[idx,:len(attr[1])] = attr[1]
return times, knots return times, knots
_defs = Template(r""" _defs = Template(r"""
__global__ void interp_{{tname}}( __global__ void interp_{{tname}}(
{{tname}}* out, {{tname}}* {{ptr_name}},
const float *times, const float *knots, const float *times, const float *knots,
float tstart, float tstep, int maxid) float tstart, float tstep, int maxid)
{ {
int id = gtid(); int id = gtid();
if (id >= maxid) return; if (id >= maxid) return;
out = &out[id]; {{ptr_name}} = &{{ptr_name}}[id];
float time = tstart + id * tstep; float time = tstart + id * tstep;
float *outf = reinterpret_cast<float*>(out); float *outf = reinterpret_cast<float*>({{ptr_name}});
{{py:lpd = len(packed_direct)}}
{{py:lpdm = len(packed_direct_mag)}}
// TODO: unroll pragma? // TODO: unroll pragma?
for (int i = 0; i < {{len(packed_direct)}}; i++) { for (int i = 0; i < {{lpd}}; i++) {
int j = i << {{search_rounds}}; int j = i << {{search_rounds}};
outf[i] = catmull_rom(&times[j], &knots[j], time); outf[i] = catmull_rom(&times[j], &knots[j], time);
} }
for (int i = {{lpd}}; i < {{lpd+lpdm}}; i++) {
int j = i << {{search_rounds}};
outf[i] = catmull_rom_mag(&times[j], &knots[j], time);
}
// Advance 'times' and 'knots' to the purely generated sections, so that // Advance 'times' and 'knots' to the purely generated sections, so that
// the pregenerated statements emitted by _require_pre are correct. // the pregenerated statements emitted by _require_pre are correct.
times = &times[{{len(packed_direct)<<search_rounds}}]; times = &times[{{(lpd+lpdm)<<search_rounds}}];
knots = &knots[{{len(packed_direct)<<search_rounds}}]; knots = &knots[{{(lpd+lpdm)<<search_rounds}}];
{{for hunk in precalc_code}} {{for hunk in precalc_code}}
if (1) { {
{{hunk}} {{hunk}}
} }
{{endfor}} {{endfor}}
@ -255,8 +257,8 @@ __global__ void interp_{{tname}}(
_decls = Template(r""" _decls = Template(r"""
typedef struct { typedef struct {
{{for name in packed}} {{for path in packed}}
float {{'_'.join(name)}}; float {{'_'.join(path)}};
{{endfor}} {{endfor}}
} {{tname}}; } {{tname}};

View File

@ -7,54 +7,53 @@ import interp
from util import Template, devlib, ringbuflib from util import Template, devlib, ringbuflib
from mwc import mwclib from mwc import mwclib
def precalc_densities(pcp, std_xforms): import cuburn.genome.spec
def precalc_densities(cp):
# This pattern recurs a few times for precalc segments. Unfortunately, # This pattern recurs a few times for precalc segments. Unfortunately,
# namespace stuff means it's not easy to functionalize this boilerplate # namespace stuff means it's not easy to functionalize this boilerplate
pre_cp = pcp._precalc() cp._code(Template(r"""
pre_cp._code(Template(r"""
float sum = 0.0f; float sum = 0.0f;
{{for n in std_xforms}} {{for n in cp.xforms}}
float den_{{n}} = {{pre_cp.xforms[n].density}}; float den_{{n}} = {{cp.xforms[n].weight}};
sum += den_{{n}}; sum += den_{{n}};
{{endfor}} {{endfor}}
float rsum = 1.0f / sum; float rsum = 1.0f / sum;
sum = 0.0f; sum = 0.0f;
{{for n in std_xforms[:-1]}} {{for n in cp.xforms.keys()[:-1]}}
sum += den_{{n}} * rsum; sum += den_{{n}} * rsum;
{{pre_cp._set('den_' + n)}} = sum; {{cp._set('den_' + n)}} = sum;
{{endfor}} {{endfor}}
""", name='precalc_densities').substitute(locals())) """, name='precalc_densities').substitute(cp=cp))
def precalc_chaos(pcp, std_xforms): def precalc_chaos(cp):
pre_cp = pcp._precalc() cp._code(Template("""
pre_cp._code(Template("""
float sum, rsum; float sum, rsum;
{{for p in std_xforms}} {{for p in cp.xforms}}
sum = 0.0f; sum = 0.0f;
{{for n in std_xforms}} {{for n in cp.xforms}}
float den_{{p}}_{{n}} = {{pre_cp.xforms[p].chaos[n]}}; float den_{{p}}_{{n}} = {{cp.xforms[n].weight}}
* {{cp.xforms[p].chaos[n]}};
sum += den_{{p}}_{{n}}; sum += den_{{p}}_{{n}};
{{endfor}} {{endfor}}
rsum = 1.0f / sum; rsum = 1.0f / sum;
sum = 0.0f; sum = 0.0f;
{{for n in std_xforms[:-1]}} {{for n in cp.xforms.keys()[:-1]}}
sum += den_{{p}}_{{n}} * rsum; sum += den_{{p}}_{{n}} * rsum;
{{pre_cp._set('chaos_%s_%s' % (p, n))}} = sum; {{cp._set('chaos_%s_%s' % (p, n))}} = sum;
{{endfor}} {{endfor}}
{{endfor}} {{endfor}}
""", name='precalc_chaos').substitute(locals())) """, name='precalc_chaos').substitute(cp=cp))
def precalc_camera(pcam):
pre_cam = pcam._precalc()
def precalc_camera(cam):
# Maxima code to check my logic: # Maxima code to check my logic:
# matrix([1,0,0.5*width + g],[0,1,0.5*height+g],[0,0,1]) # matrix([1,0,0.5*width + g],[0,1,0.5*height+g],[0,0,1])
# . matrix([width * scale,0,0], [0,width * scale,0], [0,0,1]) # . matrix([width * scale,0,0], [0,width * scale,0], [0,0,1])
@ -62,41 +61,41 @@ def precalc_camera(pcam):
# . matrix([1,0,-cenx],[0,1,-ceny],[0,0,1]) # . matrix([1,0,-cenx],[0,1,-ceny],[0,0,1])
# . matrix([X],[Y],[1]); # . matrix([X],[Y],[1]);
pre_cam._code(Template(r""" cam._code(Template(r"""
float rot = {{pre_cam.rotation}} * M_PI / 180.0f; float rot = {{cam.rotation}} * M_PI / 180.0f;
float rotsin = sin(rot), rotcos = cos(rot); float rotsin = sin(rot), rotcos = cos(rot);
float cenx = {{pre_cam.center.x}}, ceny = {{pre_cam.center.y}}; float cenx = {{cam.center.x}}, ceny = {{cam.center.y}};
float scale = {{pre_cam.scale}} * acc_size.width; float scale = {{cam.scale}} * acc_size.width;
{{pre_cam._set('xx')}} = scale * rotcos; {{cam._set('xx')}} = scale * rotcos;
{{pre_cam._set('xy')}} = scale * -rotsin; {{cam._set('xy')}} = scale * -rotsin;
{{pre_cam._set('xo')}} = scale * (rotsin * ceny - rotcos * cenx) {{cam._set('xo')}} = scale * (rotsin * ceny - rotcos * cenx)
+ 0.5f * acc_size.awidth; + 0.5f * acc_size.awidth;
{{pre_cam._set('yx')}} = scale * rotsin; {{cam._set('yx')}} = scale * rotsin;
{{pre_cam._set('yy')}} = scale * rotcos; {{cam._set('yy')}} = scale * rotcos;
{{pre_cam._set('yo')}} = scale * -(rotsin * cenx + rotcos * ceny) {{cam._set('yo')}} = scale * -(rotsin * cenx + rotcos * ceny)
+ 0.5f * acc_size.aheight; + 0.5f * acc_size.aheight;
""", 'precalc_camera').substitute(locals())) """, 'precalc_camera').substitute(cam=cam))
def precalc_xf_affine(px): def precalc_xf_affine(px):
pre = px._precalc() px._code(Template(r"""
pre._code(Template(r""" float pri = {{px.angle}} * M_PI / 180.0f;
float pri = {{pre.angle}} * M_PI / 180.0f; float spr = {{px.spread}} * M_PI / 180.0f;
float spr = {{pre.spread}} * M_PI / 180.0f;
float magx = {{pre.magnitude.x._magscale()}}; float magx = {{px.magnitude.x}};
float magy = {{pre.magnitude.y._magscale()}}; float magy = {{px.magnitude.y}};
{{pre._set('xx')}} = magx * cos(pri-spr); {{px._set('xx')}} = magx * cos(pri-spr);
{{pre._set('yx')}} = -magx * sin(pri-spr); {{px._set('yx')}} = -magx * sin(pri-spr);
{{pre._set('xy')}} = -magy * cos(pri+spr); {{px._set('xy')}} = -magy * cos(pri+spr);
{{pre._set('yy')}} = magy * sin(pri+spr); {{px._set('yy')}} = magy * sin(pri+spr);
{{pre._set('xo')}} = {{pre.offset.x._magscale()}}; {{px._set('xo')}} = {{px.offset.x}};
{{pre._set('yo')}} = -{{pre.offset.y._magscale()}}; {{px._set('yo')}} = -{{px.offset.y}};
""", 'precalc_xf_affine').substitute(locals())) """, 'precalc_xf_affine').substitute(px=px))
def apply_affine(x, y, xo, yo, packer): def apply_affine(names, packer):
x, y, xo, yo = names.split()
return Template(""" return Template("""
{{xo}} = {{packer.xx}} * {{x}} + {{packer.xy}} * {{y}} + {{packer.xo}}; {{xo}} = {{packer.xx}} * {{x}} + {{packer.xy}} * {{y}} + {{packer.xo}};
{{yo}} = {{packer.yx}} * {{x}} + {{packer.yy}} * {{y}} + {{packer.yo}}; {{yo}} = {{packer.yx}} * {{x}} + {{packer.yy}} * {{y}} + {{packer.yo}};
@ -126,25 +125,24 @@ __device__
void apply_xf_{{xfid}}(float &ox, float &oy, float &color, mwc_st &rctx) { void apply_xf_{{xfid}}(float &ox, float &oy, float &color, mwc_st &rctx) {
float tx, ty; float tx, ty;
{{precalc_xf_affine(px.affine)}} {{precalc_xf_affine(px.pre_affine._precalc())}}
{{apply_affine('ox', 'oy', 'tx', 'ty', px.affine)}} {{apply_affine('ox oy tx ty', px.pre_affine)}}
ox = 0; ox = 0;
oy = 0; oy = 0;
{{for name in xform.variations}} {{for name, pv in px.variations.items()}}
if (1) { {
{{py:pv = px.variations[name]}}
float w = {{pv.weight}}; float w = {{pv.weight}};
{{variations.var_code[name].substitute(locals())}} {{variations.var_code[name].substitute(locals())}}
} }
{{endfor}} {{endfor}}
{{if 'post' in xform}} {{if 'post_affine' in px}}
tx = ox; tx = ox;
ty = oy; ty = oy;
{{precalc_xf_affine(px.post)}} {{precalc_xf_affine(px.post_affine._precalc())}}
{{apply_affine('tx', 'ty', 'ox', 'oy', px.post)}} {{apply_affine('tx ty ox oy', px.post_affine)}}
{{endif}} {{endif}}
float csp = {{px.color_speed}}; float csp = {{px.color_speed}};
@ -152,10 +150,8 @@ void apply_xf_{{xfid}}(float &ox, float &oy, float &color, mwc_st &rctx) {
}; };
""" """
def iter_xf_body(pcp, xfid, xform): def iter_xf_body(cp, xfid, px):
px = pcp.xforms[xfid]
tmpl = Template(iter_xf_body_code, 'apply_xf_'+xfid) tmpl = Template(iter_xf_body_code, 'apply_xf_'+xfid)
g = dict(globals()) g = dict(globals())
g.update(locals()) g.update(locals())
return tmpl.substitute(g) return tmpl.substitute(g)
@ -176,13 +172,16 @@ iter(uint64_t out_ptr, uint64_t atom_ptr,
int this_rb_idx = rb_incr(rb->head, blockDim.x * threadIdx.y + threadIdx.x); int this_rb_idx = rb_incr(rb->head, blockDim.x * threadIdx.y + threadIdx.x);
mwc_st rctx = msts[this_rb_idx]; mwc_st rctx = msts[this_rb_idx];
{{precalc_camera(pcp.camera)}} {{precalc_camera(cp.camera._precalc())}}
if (threadIdx.y == 5 && threadIdx.x == 4) { if (threadIdx.y == 5 && threadIdx.x == 4) {
float ditherwidth = {{pcp.camera.dither_width}} * 0.5f; if (blockIdx.x == 0)
{{pcp.camera.xo}} += ditherwidth * mwc_next_11(rctx); printf("Hiya %f\n", {{cp.camera.xx}});
{{pcp.camera.yo}} += ditherwidth * mwc_next_11(rctx); float ditherwidth = {{cp.camera.dither_width}} * 0.5f;
{{cp.camera.xo}} += ditherwidth * mwc_next_11(rctx);
{{cp.camera.yo}} += ditherwidth * mwc_next_11(rctx);
} }
// TODO: spare the register, reuse at call site? // TODO: spare the register, reuse at call site?
int time = blockIdx.x >> 4; int time = blockIdx.x >> 4;
float color_dither = 0.49f * mwc_next_11(rctx); float color_dither = 0.49f * mwc_next_11(rctx);
@ -229,24 +228,26 @@ iter(uint64_t out_ptr, uint64_t atom_ptr,
color = mwc_next_01(rctx); color = mwc_next_01(rctx);
} }
{{py:xk = cp.xforms.keys()}}
{{if chaos_used}} {{if chaos_used}}
{{precalc_chaos(pcp, std_xforms)}} {{precalc_chaos(cp)}}
// For now, we don't attempt to use the swap buffer when chaos is used // For now, we don't attempt to use the swap buffer when chaos is used
float xfsel = mwc_next_01(rctx); float xfsel = mwc_next_01(rctx);
{{for prior_xform_idx, prior_xform_name in enumerate(std_xforms)}} {{for prior_xform_idx, prior_xform_name in enumerate(xk)}}
if (last_xf_used == {{prior_xform_idx}}) { if (last_xf_used == {{prior_xform_idx}}) {
{{for xform_idx, xform_name in enumerate(std_xforms[:-1])}} {{for xform_idx, xform_name in enumerate(xk[:-1])}}
if (xfsel <= {{pcp['chaos_'+prior_xform_name+'_'+xform_name]}}) { if (xfsel <= {{cp['chaos_'+prior_xform_name+'_'+xform_name]}}) {
apply_xf_{{xform_name}}(x, y, color, rctx); apply_xf_{{xform_name}}(x, y, color, rctx);
last_xf_used = {{xform_idx}}; last_xf_used = {{xform_idx}};
} else } else
{{endfor}} {{endfor}}
{ {
apply_xf_{{std_xforms[-1]}}(x, y, color, rctx); apply_xf_{{xk[-1]}}(x, y, color, rctx);
last_xf_used = {{len(std_xforms)-1}}; last_xf_used = {{len(xk)-1}};
} }
} else } else
{{endfor}} {{endfor}}
@ -256,18 +257,18 @@ iter(uint64_t out_ptr, uint64_t atom_ptr,
} }
{{else}} {{else}}
{{precalc_densities(pcp, std_xforms)}} {{precalc_densities(cp._precalc())}}
float xfsel = cosel[threadIdx.y]; float xfsel = cosel[threadIdx.y];
{{for xform_idx, xform_name in enumerate(std_xforms[:-1])}} {{for xform_idx, xform_name in enumerate(xk[:-1])}}
if (xfsel <= {{pcp['den_'+xform_name]}}) { if (xfsel <= {{cp['den_'+xform_name]}}) {
apply_xf_{{xform_name}}(x, y, color, rctx); apply_xf_{{xform_name}}(x, y, color, rctx);
last_xf_used = {{xform_idx}}; last_xf_used = {{xform_idx}};
} else } else
{{endfor}} {{endfor}}
{ {
apply_xf_{{std_xforms[-1]}}(x, y, color, rctx); apply_xf_{{xk[-1]}}(x, y, color, rctx);
last_xf_used = {{len(std_xforms)-1}}; last_xf_used = {{len(xk)-1}};
} }
// Rotate points between threads. // Rotate points between threads.
@ -298,18 +299,14 @@ iter(uint64_t out_ptr, uint64_t atom_ptr,
continue; continue;
} }
{{if 'final' in cp.xforms}} float cx, cy, cc;
{{if 'final_xform' in cp}}
float fx = x, fy = y, fcolor = color; float fx = x, fy = y, fcolor = color;
apply_xf_final(fx, fy, fcolor, rctx); apply_xf_final(fx, fy, fcolor, rctx);
{{endif}} {{apply_affine('fx fy cx cy', cp.camera)}}
float cx, cy, cc;
{{if 'final' in cp.xforms}}
{{apply_affine('fx', 'fy', 'cx', 'cy', pcp.camera)}}
cc = fcolor; cc = fcolor;
{{else}} {{else}}
{{apply_affine('x', 'y', 'cx', 'cy', pcp.camera)}} {{apply_affine('x y cx cy', cp.camera)}}
cc = color; cc = color;
{{endif}} {{endif}}
@ -407,11 +404,9 @@ oflow_end:
} }
''' '''
def iter_body(cp, pcp): def iter_body(cp):
# For legacy reasons, 'cp' is used here instead of 'genome'.
tmpl = Template(iter_body_code, 'iter_body') tmpl = Template(iter_body_code, 'iter_body')
NWARPS = NTHREADS / 32 NWARPS = NTHREADS / 32
std_xforms = [n for n in sorted(cp.xforms) if n != 'final']
# TODO: detect this properly and use it # TODO: detect this properly and use it
chaos_used = False chaos_used = False
@ -420,12 +415,15 @@ def iter_body(cp, pcp):
vars.update(locals()) vars.update(locals())
return tmpl.substitute(vars) return tmpl.substitute(vars)
def mkiterlib(genome): def mkiterlib(gnm):
packer = interp.GenomePacker('iter_params') packer = interp.GenomePacker('iter_params', 'params',
pcp = packer.view('params', genome, 'cp') cuburn.genome.spec.anim)
cp = packer.view(gnm)
iterbody = iter_body(genome, pcp) iterbody = iter_body(cp)
bodies = [iter_xf_body(pcp, i, x) for i, x in sorted(genome.xforms.items())] bodies = [iter_xf_body(cp, i, x) for i, x in sorted(cp.xforms.items())]
if 'final_xform' in cp:
bodies.append(iter_xf_body(cp, 'final', cp.final_xform))
bodies.append(iterbody) bodies.append(iterbody)
packer_lib = packer.finalize() packer_lib = packer.finalize()

View File

@ -3,53 +3,39 @@ import numpy as np
from util import Template from util import Template
var_code = {} var_code = {}
var_params = {}
def var(num, name, code, params=None): def var(name, code, precalc=None):
var_code[name] = Template(code, name) precalc_fun = None
if params is not None: if precalc:
r = {} def precalc_fun(pv, px):
for p in params.split(): pv, px = pv._precalc(), px._precalc()
if '=' in p: tmpl = Template(precalc, name+'_precalc').substitute(pv=pv, px=px)
p, default = p.split('=') pv._code(tmpl)
if default == 'M_PI': code = "\n {{precalc_fun(pv, px)}}" + code
default = np.pi var_code[name] = Template(code, name,
else: namespace=dict(precalc_fun=precalc_fun))
default = float(default)
else:
default = 0.0
r[p] = default
var_params[name] = r
# TODO: This is a shitty hack
def precalc(name, code):
def precalc_fun(pv, px=None):
pre = pv._precalc()
prex = px._precalc() if px is not None else None
pre._code(Template(code, name+'_precalc').substitute(**locals()))
Template.default_namespace[name+'_precalc'] = precalc_fun
# Variables note: all functions will have their weights as 'w', # Variables note: all functions will have their weights as 'w',
# input variables 'tx' and 'ty', and output 'ox' and 'oy' available # input variables 'tx' and 'ty', and output 'ox' and 'oy' available
# from the calling context. Each statement will be placed inside brackets, # from the calling context. Each statement will be placed inside brackets,
# to avoid namespace pollution. # to avoid namespace pollution.
var(0, 'linear', """ var('linear', """
ox += tx * w; ox += tx * w;
oy += ty * w; oy += ty * w;
""") """)
var(1, 'sinusoidal', """ var('sinusoidal', """
ox += w * sinf(tx); ox += w * sinf(tx);
oy += w * sinf(ty); oy += w * sinf(ty);
""") """)
var(2, 'spherical', """ var('spherical', """
float r2 = w / (tx*tx + ty*ty); float r2 = w / (tx*tx + ty*ty);
ox += tx * r2; ox += tx * r2;
oy += ty * r2; oy += ty * r2;
""") """)
var(3, 'swirl', """ var('swirl', """
float r2 = tx*tx + ty*ty; float r2 = tx*tx + ty*ty;
float c1 = sinf(r2); float c1 = sinf(r2);
float c2 = cosf(r2); float c2 = cosf(r2);
@ -57,25 +43,25 @@ var(3, 'swirl', """
oy += w * (c2*tx + c1*ty); oy += w * (c2*tx + c1*ty);
""") """)
var(4, 'horseshoe', """ var('horseshoe', """
float r = w / sqrtf(tx*tx + ty*ty); float r = w / sqrtf(tx*tx + ty*ty);
ox += r * (tx - ty) * (tx + ty); ox += r * (tx - ty) * (tx + ty);
oy += 2.0f * tx * ty * r; oy += 2.0f * tx * ty * r;
""") """)
var(5, 'polar', """ var('polar', """
ox += w * atan2f(tx, ty) * M_1_PI; ox += w * atan2f(tx, ty) * M_1_PI;
oy += w * (sqrtf(tx * tx + ty * ty) - 1.0f); oy += w * (sqrtf(tx * tx + ty * ty) - 1.0f);
""") """)
var(6, 'handkerchief', """ var('handkerchief', """
float a = atan2f(tx, ty); float a = atan2f(tx, ty);
float r = sqrtf(tx*tx + ty*ty); float r = sqrtf(tx*tx + ty*ty);
ox += w * r * sinf(a+r); ox += w * r * sinf(a+r);
oy += w * r * cosf(a-r); oy += w * r * cosf(a-r);
""") """)
var(7, 'heart', """ var('heart', """
float sq = sqrtf(tx*tx + ty*ty); float sq = sqrtf(tx*tx + ty*ty);
float a = sq * atan2f(tx, ty); float a = sq * atan2f(tx, ty);
float r = w * sq; float r = w * sq;
@ -83,14 +69,14 @@ var(7, 'heart', """
oy -= r * cosf(a); oy -= r * cosf(a);
""") """)
var(8, 'disc', """ var('disc', """
float a = w * atan2f(tx, ty) * M_1_PI; float a = w * atan2f(tx, ty) * M_1_PI;
float r = M_PI * sqrtf(tx*tx + ty*ty); float r = M_PI * sqrtf(tx*tx + ty*ty);
ox += sinf(r) * a; ox += sinf(r) * a;
oy += cosf(r) * a; oy += cosf(r) * a;
""") """)
var(9, 'spiral', """ var('spiral', """
float a = atan2f(tx, ty); float a = atan2f(tx, ty);
float r = sqrtf(tx*tx + ty*ty); float r = sqrtf(tx*tx + ty*ty);
float r1 = w / r; float r1 = w / r;
@ -98,21 +84,21 @@ var(9, 'spiral', """
oy += r1 * (sinf(a) - cosf(r)); oy += r1 * (sinf(a) - cosf(r));
""") """)
var(10, 'hyperbolic', """ var('hyperbolic', """
float a = atan2f(tx, ty); float a = atan2f(tx, ty);
float r = sqrtf(tx*tx + ty*ty); float r = sqrtf(tx*tx + ty*ty);
ox += w * sinf(a) / r; ox += w * sinf(a) / r;
oy += w * cosf(a) * r; oy += w * cosf(a) * r;
""") """)
var(11, 'diamond', """ var('diamond', """
float a = atan2f(tx, ty); float a = atan2f(tx, ty);
float r = sqrtf(tx*tx + ty*ty); float r = sqrtf(tx*tx + ty*ty);
ox += w * sinf(a) * cosf(r); ox += w * sinf(a) * cosf(r);
oy += w * cosf(a) * sinf(r); oy += w * cosf(a) * sinf(r);
""") """)
var(12, 'ex', """ var('ex', """
float a = atan2f(tx, ty); float a = atan2f(tx, ty);
float r = sqrtf(tx*tx + ty*ty); float r = sqrtf(tx*tx + ty*ty);
float n0 = sinf(a+r); float n0 = sinf(a+r);
@ -123,7 +109,7 @@ var(12, 'ex', """
oy += w * (m0 - m1); oy += w * (m0 - m1);
""") """)
var(13, 'julia', """ var('julia', """
float a = 0.5f * atan2f(tx, ty); float a = 0.5f * atan2f(tx, ty);
if (mwc_next(rctx) & 1) a += M_PI; if (mwc_next(rctx) & 1) a += M_PI;
float r = w * sqrtf(sqrtf(tx*tx + ty*ty)); // TODO: fastest? float r = w * sqrtf(sqrtf(tx*tx + ty*ty)); // TODO: fastest?
@ -131,7 +117,7 @@ var(13, 'julia', """
oy += r * sinf(a); oy += r * sinf(a);
""") """)
var(14, 'bent', """ var('bent', """
float nx = 1.0f; float nx = 1.0f;
if (tx < 0.0f) nx = 2.0f; if (tx < 0.0f) nx = 2.0f;
float ny = 1.0f; float ny = 1.0f;
@ -140,37 +126,34 @@ var(14, 'bent', """
oy += w * ny * ty; oy += w * ny * ty;
""") """)
precalc('waves', """ var('waves', """
float dx = {{prex.affine.offset.x}}; float c10 = {{px.pre_affine.xy}};
float dy = {{prex.affine.offset.y}}; float c11 = {{px.pre_affine.yy}};
{{pre._set('dx2')}} = 1.0f / (dx * dx + 1.0e-20f);
{{pre._set('dy2')}} = 1.0f / (dy * dy + 1.0e-20f);
""")
var(15, 'waves', """
{{waves_precalc(pv, px)}}
float c10 = {{px.affine.xy}};
float c11 = {{px.affine.yy}};
ox += w * (tx + c10 * sinf(ty * {{pv.dx2}})); ox += w * (tx + c10 * sinf(ty * {{pv.dx2}}));
oy += w * (ty + c11 * sinf(tx * {{pv.dy2}})); oy += w * (ty + c11 * sinf(tx * {{pv.dy2}}));
""", """
float dx = {{px.pre_affine.offset.x}};
float dy = {{px.pre_affine.offset.y}};
{{pv._set('dx2')}} = 1.0f / (dx * dx + 1.0e-20f);
{{pv._set('dy2')}} = 1.0f / (dy * dy + 1.0e-20f);
""") """)
var(16, 'fisheye', """ var('fisheye', """
float r = sqrtf(tx*tx + ty*ty); float r = sqrtf(tx*tx + ty*ty);
r = 2.0f * w / (r + 1.0f); r = 2.0f * w / (r + 1.0f);
ox += r * ty; ox += r * ty;
oy += r * tx; oy += r * tx;
""") """)
var(17, 'popcorn', """ var('popcorn', """
float dx = tanf(3.0f*ty); float dx = tanf(3.0f*ty);
float dy = tanf(3.0f*tx); float dy = tanf(3.0f*tx);
ox += w * (tx + {{px.affine.xo}} * sinf(dx)); ox += w * (tx + {{px.pre_affine.xo}} * sinf(dx));
oy += w * (ty + {{px.affine.yo}} * sinf(dy)); oy += w * (ty + {{px.pre_affine.yo}} * sinf(dy));
""") """)
var(18, 'exponential', """ var('exponential', """
float dx = w * expf(tx - 1.0f); float dx = w * expf(tx - 1.0f);
if (isfinite(dx)) { if (isfinite(dx)) {
float dy = M_PI * ty; float dy = M_PI * ty;
@ -179,7 +162,7 @@ var(18, 'exponential', """
} }
""") """)
var(19, 'power', """ var('power', """
float a = atan2f(tx, ty); float a = atan2f(tx, ty);
float sa = sinf(a); float sa = sinf(a);
float r = w * powf(sqrtf(tx*tx + ty*ty),sa); float r = w * powf(sqrtf(tx*tx + ty*ty),sa);
@ -187,14 +170,15 @@ var(19, 'power', """
oy += r * sa; oy += r * sa;
""") """)
var(20, 'cosine', """ var('cosine', """
float a = M_PI * tx; float a = M_PI * tx;
ox += w * cosf(a) * coshf(ty); ox += w * cosf(a) * coshf(ty);
oy -= w * sinf(a) * sinhf(ty); oy -= w * sinf(a) * sinhf(ty);
""") """)
var(21, 'rings', """ var('rings', """
float dx = {{px.affine.xo}} * {{px.affine.xo}}; float dx = {{px.pre_affine.xo}};
dx *= dx;
float r = sqrtf(tx*tx + ty*ty); float r = sqrtf(tx*tx + ty*ty);
float a = atan2f(tx, ty); float a = atan2f(tx, ty);
r = w * (fmodf(r+dx, 2.0f*dx) - dx + r * (1.0f - dx)); r = w * (fmodf(r+dx, 2.0f*dx) - dx + r * (1.0f - dx));
@ -202,10 +186,11 @@ var(21, 'rings', """
oy += r * sinf(a); oy += r * sinf(a);
""") """)
var(22, 'fan', """ var('fan', """
float dx = M_PI * ({{px.affine.xo}} * {{px.affine.xo}}); float dx = {{px.pre_affine.xo}};
dx *= dx * M_PI;
float dx2 = 0.5f * dx; float dx2 = 0.5f * dx;
float dy = {{px.affine.yo}}; float dy = {{px.pre_affine.yo}};
float a = atan2f(tx, ty); float a = atan2f(tx, ty);
a += (fmodf(a+dy, dx) > dx2) ? -dx2 : dx2; a += (fmodf(a+dy, dx) > dx2) ? -dx2 : dx2;
float r = w * sqrtf(tx*tx + ty*ty); float r = w * sqrtf(tx*tx + ty*ty);
@ -213,27 +198,28 @@ var(22, 'fan', """
oy += r * sinf(a); oy += r * sinf(a);
""") """)
var(23, 'blob', """ var('blob', """
float r = sqrtf(tx*tx + ty*ty); float r = sqrtf(tx*tx + ty*ty);
float a = atan2f(tx, ty); float a = atan2f(tx, ty);
float bdiff = 0.5f * ({{pv.high}} - {{pv.low}}); float bdiff = 0.5f * ({{pv.high}} - {{pv.low}});
r *= w * ({{pv.low}} + bdiff * (1.0f + sinf({{pv.waves}} * a))); r *= w * ({{pv.low}} + bdiff * (1.0f + sinf({{pv.waves}} * a)));
ox += sinf(a) * r; ox += sinf(a) * r;
oy += cosf(a) * r; oy += cosf(a) * r;
""", 'low high=1 waves=1') """)
var(24, 'pdj', """ var('pdj', """
float nx1 = cosf({{pv.b}} * tx); float nx1 = cosf({{pv.b}} * tx);
float nx2 = sinf({{pv.c}} * tx); float nx2 = sinf({{pv.c}} * tx);
float ny1 = sinf({{pv.a}} * ty); float ny1 = sinf({{pv.a}} * ty);
float ny2 = cosf({{pv.d}} * ty); float ny2 = cosf({{pv.d}} * ty);
ox += w * (ny1 - nx1); ox += w * (ny1 - nx1);
oy += w * (nx2 - ny2); oy += w * (nx2 - ny2);
""", 'a b c d') """)
var(25, 'fan2', """ var('fan2', """
float dy = {{pv.y}}; float dy = {{pv.y}};
float dx = M_PI * {{pv.x}} * {{pv.x}}; float dx = {{pv.x}};
dx *= dx * M_PI;
float dx2 = 0.5f * dx; float dx2 = 0.5f * dx;
float a = atan2f(tx, ty); float a = atan2f(tx, ty);
float r = w * sqrtf(tx*tx + ty*ty); float r = w * sqrtf(tx*tx + ty*ty);
@ -245,62 +231,55 @@ var(25, 'fan2', """
ox += r * sinf(a); ox += r * sinf(a);
oy += r * cosf(a); oy += r * cosf(a);
""", 'x y') """)
var(26, 'rings2', """ var('rings2', """
float dx = {{pv.val}} * {{pv.val}}; float dx = {{pv.val}};
dx *= dx;
float r = sqrtf(tx*tx + ty*ty); float r = sqrtf(tx*tx + ty*ty);
float a = atan2f(tx, ty); float a = atan2f(tx, ty);
r += -2.0f * dx * (int)((r+dx)/(2.0f*dx)) + r * (1.0f - dx); r += -2.0f * dx * (int)((r+dx)/(2.0f*dx)) + r * (1.0f - dx);
ox += w * sinf(a) * r; ox += w * sinf(a) * r;
oy += w * cosf(a) * r; oy += w * cosf(a) * r;
""", 'val') """)
var(27, 'eyefish', """ var('eyefish', """
float r = 2.0f * w / (sqrtf(tx*tx + ty*ty) + 1.0f); float r = 2.0f * w / (sqrtf(tx*tx + ty*ty) + 1.0f);
ox += r * tx; ox += r * tx;
oy += r * ty; oy += r * ty;
""") """)
var(28, 'bubble', """ var('bubble', """
float r = w / (0.25f * (tx*tx + ty*ty) + 1.0f); float r = w / (0.25f * (tx*tx + ty*ty) + 1.0f);
ox += r * tx; ox += r * tx;
oy += r * ty; oy += r * ty;
""") """)
var(29, 'cylinder', """ var('cylinder', """
ox += w * sinf(tx); ox += w * sinf(tx);
oy += w * ty; oy += w * ty;
""") """)
precalc('perspective', """ var('perspective', """
float pang = {{pre.angle}} * M_PI_2;
float pdist = fmaxf(1e-9, {{pre.dist}});
{{pre._set('mdist')}} = pdist;
{{pre._set('sin')}} = sin(pang);
{{pre._set('cos')}} = pdist * cos(pang);
""")
var(30, 'perspective', """
{{perspective_precalc(pv)}}
float t = 1.0f / ({{pv.mdist}} - ty * {{pv.sin}}); float t = 1.0f / ({{pv.mdist}} - ty * {{pv.sin}});
ox += w * {{pv.mdist}} * tx * t; ox += w * {{pv.mdist}} * tx * t;
oy += w * {{pv.cos}} * ty * t; oy += w * {{pv.cos}} * ty * t;
""", 'angle dist') """, """
float pang = {{pv.angle}} * M_PI_2;
float pdist = fmaxf(1e-9, {{pv.dist}});
{{pv._set('mdist')}} = pdist;
{{pv._set('sin')}} = sin(pang);
{{pv._set('cos')}} = pdist * cos(pang);
""")
var(31, 'noise', """ var('noise', """
float tmpr = mwc_next_01(rctx) * 2.0f * M_PI; float tmpr = mwc_next_01(rctx) * 2.0f * M_PI;
float r = w * mwc_next_01(rctx); float r = w * mwc_next_01(rctx);
ox += tx * r * cosf(tmpr); ox += tx * r * cosf(tmpr);
oy += ty * r * sinf(tmpr); oy += ty * r * sinf(tmpr);
""") """)
precalc('julian', var('julian', """
"{{pre._set('cn')}} = {{pre.dist}} / (2.0f * {{pre.power}});\n")
var(32, 'julian', """
{{julian_precalc(pv)}}
float power = {{pv.power}}; float power = {{pv.power}};
float t_rnd = truncf(mwc_next_01(rctx) * fabsf(power)); float t_rnd = truncf(mwc_next_01(rctx) * fabsf(power));
float a = atan2f(ty, tx); float a = atan2f(ty, tx);
@ -310,14 +289,11 @@ var(32, 'julian', """
ox += r * cosf(tmpr); ox += r * cosf(tmpr);
oy += r * sinf(tmpr); oy += r * sinf(tmpr);
""", 'power=1 dist=1') """, """
{{pv._set('cn')}} = {{pv.dist}} / (2.0f * {{pv.power}});
precalc('juliascope', """)
"{{pre._set('cn')}} = {{pre.dist}} / (2.0f * {{pre.power}});\n")
var(33, 'juliascope', """
{{juliascope_precalc(pv)}}
var('juliascope', """
float ang = atan2f(ty, tx); float ang = atan2f(ty, tx);
float power = {{pv.power}}; float power = {{pv.power}};
float t_rnd = truncf(mwc_next_01(rctx) * fabsf(power)); float t_rnd = truncf(mwc_next_01(rctx) * fabsf(power));
@ -328,16 +304,18 @@ var(33, 'juliascope', """
ox += r * cosf(tmpr); ox += r * cosf(tmpr);
oy += r * sinf(tmpr); oy += r * sinf(tmpr);
""", 'power=1 dist=1') """, """
{{pv._set('cn')}} = {{pv.dist}} / (2.0f * {{pv.power}});
""")
var(34, 'blur', """ var('blur', """
float tmpr = mwc_next_01(rctx) * 2.0f * M_PI; float tmpr = mwc_next_01(rctx) * 2.0f * M_PI;
float r = w * mwc_next_01(rctx); float r = w * mwc_next_01(rctx);
ox += r * cosf(tmpr); ox += r * cosf(tmpr);
oy += r * sinf(tmpr); oy += r * sinf(tmpr);
""") """)
var(35, 'gaussian_blur', """ var('gaussian_blur', """
float ang = mwc_next_01(rctx) * 2.0f * M_PI; float ang = mwc_next_01(rctx) * 2.0f * M_PI;
// constant factor here is stdev correction for converting to Box-Muller; // constant factor here is stdev correction for converting to Box-Muller;
// np.std(np.sum(np.random.random((1<<30, 4)), axis=1) - 2) // np.std(np.sum(np.random.random((1<<30, 4)), axis=1) - 2)
@ -347,7 +325,7 @@ var(35, 'gaussian_blur', """
oy += r * sinf(ang); oy += r * sinf(ang);
""") """)
var(36, 'radial_blur', """ var('radial_blur', """
float blur_angle = {{pv.angle}} * M_PI * 0.5f; float blur_angle = {{pv.angle}} * M_PI * 0.5f;
float spinvar = sinf(blur_angle); float spinvar = sinf(blur_angle);
float zoomvar = cosf(blur_angle); float zoomvar = cosf(blur_angle);
@ -358,9 +336,9 @@ var(36, 'radial_blur', """
float rz = zoomvar * r - 1.0f; float rz = zoomvar * r - 1.0f;
ox += ra*cosf(tmpa) + rz*tx; ox += ra*cosf(tmpa) + rz*tx;
oy += ra*sinf(tmpa) + rz*ty; oy += ra*sinf(tmpa) + rz*ty;
""", 'angle') """)
var(37, 'pie', """ var('pie', """
float slices = {{pv.slices}}; float slices = {{pv.slices}};
float sl = truncf(mwc_next_01(rctx) * slices + 0.5f); float sl = truncf(mwc_next_01(rctx) * slices + 0.5f);
float a = {{pv.rotation}} + float a = {{pv.rotation}} +
@ -368,9 +346,9 @@ var(37, 'pie', """
float r = w * mwc_next_01(rctx); float r = w * mwc_next_01(rctx);
ox += r * cosf(a); ox += r * cosf(a);
oy += r * sinf(a); oy += r * sinf(a);
""", 'slices=6 rotation thickness=0.5') """)
var(38, 'ngon', """ var('ngon', """
float power = {{pv.power}} * 0.5f; float power = {{pv.power}} * 0.5f;
float b = 2.0f * M_PI / {{pv.sides}}; float b = 2.0f * M_PI / {{pv.sides}};
float corners = {{pv.corners}}; float corners = {{pv.corners}};
@ -384,9 +362,9 @@ var(38, 'ngon', """
ox += w * tx * amp; ox += w * tx * amp;
oy += w * ty * amp; oy += w * ty * amp;
""", 'sides=5 power=3 circle=1 corners=2') """)
var(39, 'curl', """ var('curl', """
float c1 = {{pv.c1}}; float c1 = {{pv.c1}};
float c2 = {{pv.c2}}; float c2 = {{pv.c2}};
@ -396,34 +374,34 @@ var(39, 'curl', """
ox += r * (tx*re + ty*im); ox += r * (tx*re + ty*im);
oy += r * (ty*re - tx*im); oy += r * (ty*re - tx*im);
""", 'c1=1 c2') """)
var(40, 'rectangles', """ var('rectangles', """
float rx = {{pv.x}}; float rx = {{pv.x}};
float ry = {{pv.y}}; float ry = {{pv.y}};
ox += w * ( (rx==0.0f) ? tx : rx * (2.0f * floorf(tx/rx) + 1.0f) - tx); ox += w * ( (rx==0.0f) ? tx : rx * (2.0f * floorf(tx/rx) + 1.0f) - tx);
oy += w * ( (ry==0.0f) ? ty : ry * (2.0f * floorf(ty/ry) + 1.0f) - ty); oy += w * ( (ry==0.0f) ? ty : ry * (2.0f * floorf(ty/ry) + 1.0f) - ty);
""", 'x y') """)
var(41, 'arch', """ var('arch', """
float ang = mwc_next_01(rctx) * w * M_PI; float ang = mwc_next_01(rctx) * w * M_PI;
ox += w * sinf(ang); ox += w * sinf(ang);
oy += w * sinf(ang) * sinf(ang) / cosf(ang); oy += w * sinf(ang) * sinf(ang) / cosf(ang);
""") """)
var(42, 'tangent', """ var('tangent', """
ox += w * sinf(tx) / cosf(ty); ox += w * sinf(tx) / cosf(ty);
oy += w * tanf(ty); oy += w * tanf(ty);
""") """)
var(43, 'square', """ var('square', """
ox += w * (mwc_next_01(rctx) - 0.5f); ox += w * (mwc_next_01(rctx) - 0.5f);
oy += w * (mwc_next_01(rctx) - 0.5f); oy += w * (mwc_next_01(rctx) - 0.5f);
""") """)
var(44, 'rays', """ var('rays', """
float ang = w * mwc_next_01(rctx) * M_PI; float ang = w * mwc_next_01(rctx) * M_PI;
float r = w / (tx*tx + ty*ty); float r = w / (tx*tx + ty*ty);
float tanr = w * tanf(ang) * r; float tanr = w * tanf(ang) * r;
@ -431,13 +409,13 @@ var(44, 'rays', """
oy += tanr * sinf(ty); oy += tanr * sinf(ty);
""") """)
var(45, 'blade', """ var('blade', """
float r = mwc_next_01(rctx) * w * sqrtf(tx*tx + ty*ty); float r = mwc_next_01(rctx) * w * sqrtf(tx*tx + ty*ty);
ox += w * tx * (cosf(r) + sinf(r)); ox += w * tx * (cosf(r) + sinf(r));
oy += w * tx * (cosf(r) - sinf(r)); oy += w * tx * (cosf(r) - sinf(r));
""") """)
var(46, 'secant2', """ var('secant2', """
float r = w * sqrtf(tx*tx + ty*ty); float r = w * sqrtf(tx*tx + ty*ty);
float cr = cosf(r); float cr = cosf(r);
float icr = 1.0f / cr; float icr = 1.0f / cr;
@ -449,7 +427,7 @@ var(46, 'secant2', """
# var 47 is twintrian, has a call to badvalue in it # var 47 is twintrian, has a call to badvalue in it
var(48, 'cross', """ var('cross', """
float s = tx*tx - ty*ty; float s = tx*tx - ty*ty;
float r = w * sqrtf(1.0f / (s*s)); float r = w * sqrtf(1.0f / (s*s));
@ -457,7 +435,7 @@ var(48, 'cross', """
oy += r * ty; oy += r * ty;
""") """)
var(49, 'disc2', """ var('disc2', """
float twist = {{pv.twist}}; float twist = {{pv.twist}};
float rotpi = {{pv.rot}} * M_PI; float rotpi = {{pv.rot}} * M_PI;
@ -481,9 +459,9 @@ var(49, 'disc2', """
ox += r * (sinf(t) + costwist); ox += r * (sinf(t) + costwist);
oy += r * (cosf(t) + sintwist); oy += r * (cosf(t) + sintwist);
""", 'rot twist') """)
var(50, 'super_shape', """ var('super_shape', """
float ang = atan2f(ty, tx); float ang = atan2f(ty, tx);
float theta = 0.25f * ({{pv.m}} * ang + M_PI); float theta = 0.25f * ({{pv.m}} * ang + M_PI);
float t1 = fabsf(cosf(theta)); float t1 = fabsf(cosf(theta));
@ -498,9 +476,9 @@ var(50, 'super_shape', """
ox += r * tx; ox += r * tx;
oy += r * ty; oy += r * ty;
""", 'rnd m n1=1 n2=1 n3=1 holes') """)
var(51, 'flower', """ var('flower', """
float holes = {{pv.holes}}; float holes = {{pv.holes}};
float petals = {{pv.petals}}; float petals = {{pv.petals}};
@ -509,9 +487,9 @@ var(51, 'flower', """
ox += r * tx; ox += r * tx;
oy += r * ty; oy += r * ty;
""", 'holes petals') """)
var(52, 'conic', """ var('conic', """
float d = sqrtf(tx*tx + ty*ty); float d = sqrtf(tx*tx + ty*ty);
float ct = tx / d; float ct = tx / d;
float holes = {{pv.holes}}; float holes = {{pv.holes}};
@ -521,27 +499,27 @@ var(52, 'conic', """
ox += r * tx; ox += r * tx;
oy += r * ty; oy += r * ty;
""", 'holes eccentricity=1') """)
var(53, 'parabola', """ var('parabola', """
float r = sqrtf(tx*tx + ty*ty); float r = sqrtf(tx*tx + ty*ty);
float sr = sinf(r); float sr = sinf(r);
float cr = cosf(r); float cr = cosf(r);
ox += {{pv.height}} * w * sr * sr * mwc_next_01(rctx); ox += {{pv.height}} * w * sr * sr * mwc_next_01(rctx);
oy += {{pv.width}} * w * cr * mwc_next_01(rctx); oy += {{pv.width}} * w * cr * mwc_next_01(rctx);
""", 'height width') """)
var(54, 'bent2', """ var('bent2', """
float nx = 1.0f; float nx = 1.0f;
if (tx < 0.0f) nx = {{pv.x}}; if (tx < 0.0f) nx = {{pv.x}};
float ny = 1.0f; float ny = 1.0f;
if (ty < 0.0f) ny = {{pv.y}}; if (ty < 0.0f) ny = {{pv.y}};
ox += w * nx * tx; ox += w * nx * tx;
oy += w * ny * ty; oy += w * ny * ty;
""", 'x=1 y=1') """)
var(55, 'bipolar', """ var('bipolar', """
float x2y2 = tx*tx + ty*ty; float x2y2 = tx*tx + ty*ty;
float t = x2y2 + 1.0f; float t = x2y2 + 1.0f;
float x2 = tx * 2.0f; float x2 = tx * 2.0f;
@ -555,9 +533,9 @@ var(55, 'bipolar', """
ox += w * 0.25f * M_2_PI * logf( (t+x2) / (t-x2) ); ox += w * 0.25f * M_2_PI * logf( (t+x2) / (t-x2) );
oy += w * M_2_PI * y; oy += w * M_2_PI * y;
""", 'shift') """)
var(56, 'boarders', """ var('boarders', """
float roundX = rintf(tx); float roundX = rintf(tx);
float roundY = rintf(ty); float roundY = rintf(ty);
float offsetX = tx - roundX; float offsetX = tx - roundX;
@ -587,7 +565,7 @@ var(56, 'boarders', """
} }
""") """)
var(57, 'butterfly', """ var('butterfly', """
/* wx is weight*4/sqrt(3*pi) */ /* wx is weight*4/sqrt(3*pi) */
float wx = w * 1.3029400317411197908970256609023f; float wx = w * 1.3029400317411197908970256609023f;
float y2 = ty * 2.0f; float y2 = ty * 2.0f;
@ -596,7 +574,7 @@ var(57, 'butterfly', """
oy += r * y2; oy += r * y2;
""") """)
var(58, 'cell', """ var('cell', """
float cell_size = {{pv.size}}; float cell_size = {{pv.size}};
float inv_cell_size = 1.0f/cell_size; float inv_cell_size = 1.0f/cell_size;
@ -629,37 +607,33 @@ var(58, 'cell', """
ox += w * (dx + x*cell_size); ox += w * (dx + x*cell_size);
oy -= w * (dy + y*cell_size); oy -= w * (dy + y*cell_size);
""", 'size=1') """)
var(59, 'cpow', """ var('cpow', """
float a = atan2f(ty, tx); float a = atan2f(ty, tx);
float lnr = 0.5f * logf(tx*tx+ty*ty); float lnr = 0.5f * logf(tx*tx+ty*ty);
float power = {{pv.power}}; float power = 1.0f / {{pv.power}};
float va = 2.0f * M_PI / power; float va = 2.0f * M_PI * power;
float vc = {{pv.cpow_r}} / power; float vc = {{pv.r}} * power;
float vd = {{pv.cpow_i}} / power; float vd = {{pv.i}} * power;
float ang = vc*a + vd*lnr + va*floorf(power*mwc_next_01(rctx)); float ang = vc*a + vd*lnr + va*floorf(power*mwc_next_01(rctx));
float m = w * expf(vc * lnr - vd * a); float m = w * expf(vc * lnr - vd * a);
ox += m * cosf(ang); ox += m * cosf(ang);
oy += m * sinf(ang); oy += m * sinf(ang);
""", 'r=1 i power=1') """)
var('curve', """
precalc('curve', '''
float xl = {{pv.xlength}}, yl = {{pv.ylength}};
{{pre._set('x2')}} = 1.0f / max(1e-20f, xl * xl);
{{pre._set('y2')}} = 1.0f / max(1e-20f, yl * yl);
''')
var(60, 'curve', """
{{curve_precalc()}}
float pc_xlen = {{pv.x2}}, pc_ylen = {{pv.y2}}; float pc_xlen = {{pv.x2}}, pc_ylen = {{pv.y2}};
ox += w * (tx + {{pv.xamp}} * expf(-ty*ty*pc_xlen)); ox += w * (tx + {{pv.xamp}} * expf(-ty*ty*pc_xlen));
oy += w * (ty + {{pv.yamp}} * expf(-tx*tx*pc_ylen)); oy += w * (ty + {{pv.yamp}} * expf(-tx*tx*pc_ylen));
""", 'xamp yamp xlength=1 ylength=1') """, """
float xl = {{pv.xlength}}, yl = {{pv.ylength}};
{{pv._set('x2')}} = 1.0f / max(1e-20f, xl * xl);
{{pv._set('y2')}} = 1.0f / max(1e-20f, yl * yl);
""")
var(61, 'edisc', """ var('edisc', """
float tmp = tx*tx + ty*ty + 1.0f; float tmp = tx*tx + ty*ty + 1.0f;
float tmp2 = 2.0f * tx; float tmp2 = 2.0f * tx;
float r1 = sqrtf(tmp+tmp2); float r1 = sqrtf(tmp+tmp2);
@ -677,7 +651,7 @@ var(61, 'edisc', """
oy += neww * sinhf(a2) * snv; oy += neww * sinhf(a2) * snv;
""") """)
var(62, 'elliptic', """ var('elliptic', """
float tmp = tx*tx + ty*ty + 1.0f; float tmp = tx*tx + ty*ty + 1.0f;
float x2 = 2.0f * tx; float x2 = 2.0f * tx;
float xmax = 0.5f * (sqrtf(tmp+x2) + sqrtf(tmp-x2)); float xmax = 0.5f * (sqrtf(tmp+x2) + sqrtf(tmp-x2));
@ -704,7 +678,7 @@ var(62, 'elliptic', """
oy -= neww * logf(xmax + ssx); oy -= neww * logf(xmax + ssx);
""") """)
var(63, 'escher', """ var('escher', """
float a = atan2f(ty,tx); float a = atan2f(ty,tx);
float lnr = 0.5f * logf(tx*tx + ty*ty); float lnr = 0.5f * logf(tx*tx + ty*ty);
float ebeta = {{pv.beta}}; float ebeta = {{pv.beta}};
@ -717,9 +691,9 @@ var(63, 'escher', """
ox += m * cosf(n); ox += m * cosf(n);
oy += m * sinf(n); oy += m * sinf(n);
""", 'beta') """)
var(64, 'foci', """ var('foci', """
float expx = expf(tx) * 0.5f; float expx = expf(tx) * 0.5f;
float expnx = 0.25f / expx; float expnx = 0.25f / expx;
float sn = sinf(ty); float sn = sinf(ty);
@ -729,7 +703,7 @@ var(64, 'foci', """
oy += tmp * sn; oy += tmp * sn;
""") """)
var(65, 'lazysusan', """ var('lazysusan', """
float lx = {{pv.x}}; float lx = {{pv.x}};
float ly = {{pv.y}}; float ly = {{pv.y}};
float x = tx - lx; float x = tx - lx;
@ -748,9 +722,9 @@ var(65, 'lazysusan', """
ox += w * (r * x + lx); ox += w * (r * x + lx);
oy += w * (r * y - ly); oy += w * (r * y - ly);
} }
""", 'x y twist space spin') """)
var(66, 'loonie', """ var('loonie', """
float r2 = tx*tx + ty*ty;; float r2 = tx*tx + ty*ty;;
float w2 = w*w; float w2 = w*w;
@ -764,7 +738,7 @@ var(66, 'loonie', """
} }
""") """)
var(67, 'pre_blur', """ var('pre_blur', """
float rndG = w * (mwc_next_01(rctx) + mwc_next_01(rctx) float rndG = w * (mwc_next_01(rctx) + mwc_next_01(rctx)
+ mwc_next_01(rctx) + mwc_next_01(rctx) - 2.0f); + mwc_next_01(rctx) + mwc_next_01(rctx) - 2.0f);
float rndA = mwc_next_01(rctx) * 2.0f * M_PI; float rndA = mwc_next_01(rctx) * 2.0f * M_PI;
@ -774,7 +748,7 @@ var(67, 'pre_blur', """
ty += rndG * sinf(rndA); ty += rndG * sinf(rndA);
""") """)
var(68, 'modulus', """ var('modulus', """
float mx = {{pv.x}}, my = {{pv.y}}; float mx = {{pv.x}}, my = {{pv.y}};
float xr = 2.0f*mx; float xr = 2.0f*mx;
float yr = 2.0f*my; float yr = 2.0f*my;
@ -792,9 +766,9 @@ var(68, 'modulus', """
oy += w * ( my - fmodf(my - ty, yr)); oy += w * ( my - fmodf(my - ty, yr));
else else
oy += w * ty; oy += w * ty;
""", 'x y') """)
var(69, 'oscope', """ var('oscope', """
float tpf = 2.0f * M_PI * {{pv.frequency}}; float tpf = 2.0f * M_PI * {{pv.frequency}};
float amp = {{pv.amplitude}}; float amp = {{pv.amplitude}};
float sep = {{pv.separation}}; float sep = {{pv.separation}};
@ -807,21 +781,21 @@ var(69, 'oscope', """
oy -= w*ty; oy -= w*ty;
else else
oy += w*ty; oy += w*ty;
""", 'separation=1 frequency=M_PI amplitude=1 damping') """)
var(70, 'polar2', """ var('polar2', """
float p2v = w / M_PI; float p2v = w / M_PI;
ox += p2v * atan2f(tx,ty); ox += p2v * atan2f(tx,ty);
oy += 0.5f * p2v * logf(tx*tx + ty*ty); oy += 0.5f * p2v * logf(tx*tx + ty*ty);
""") """)
var(71, 'popcorn2', """ var('popcorn2', """
float c = {{pv.c}}; float c = {{pv.c}};
ox += w * (tx + {{pv.x}} * sinf(tanf(ty*c))); ox += w * (tx + {{pv.x}} * sinf(tanf(ty*c)));
oy += w * (ty + {{pv.y}} * sinf(tanf(tx*c))); oy += w * (ty + {{pv.y}} * sinf(tanf(tx*c)));
""", 'x y c') """)
var(72, 'scry', """ var('scry', """
/* note that scry does not multiply by weight, but as the */ /* note that scry does not multiply by weight, but as the */
/* values still approach 0 as the weight approaches 0, it */ /* values still approach 0 as the weight approaches 0, it */
/* should be ok */ /* should be ok */
@ -831,7 +805,7 @@ var(72, 'scry', """
oy += ty*r; oy += ty*r;
""") """)
var(73, 'separation', """ var('separation', """
float sx2 = {{pv.x}} * {{pv.x}}; float sx2 = {{pv.x}} * {{pv.x}};
float sy2 = {{pv.y}} * {{pv.y}}; float sy2 = {{pv.y}} * {{pv.y}};
@ -844,9 +818,9 @@ var(73, 'separation', """
oy += w * (sqrtf(ty*ty + sy2) - ty*{{pv.yinside}}); oy += w * (sqrtf(ty*ty + sy2) - ty*{{pv.yinside}});
else else
oy -= w * (sqrtf(ty*ty + sy2) + ty*{{pv.yinside}}); oy -= w * (sqrtf(ty*ty + sy2) + ty*{{pv.yinside}});
""", 'x xinside y yinside') """)
var(74, 'split', """ var('split', """
if (cosf(tx*{{pv.xsize}}*M_PI) >= 0.0f) if (cosf(tx*{{pv.xsize}}*M_PI) >= 0.0f)
oy += w*ty; oy += w*ty;
else else
@ -856,21 +830,21 @@ var(74, 'split', """
ox += w*tx; ox += w*tx;
else else
ox -= w*tx; ox -= w*tx;
""", 'xsize ysize') """)
var(75, 'splits', """ var('splits', """
ox += w*(tx + copysignf({{pv.x}}, tx)); ox += w*(tx + copysignf({{pv.x}}, tx));
oy += w*(ty + copysignf({{pv.y}}, ty)); oy += w*(ty + copysignf({{pv.y}}, ty));
""", 'x y') """)
var(76, 'stripes', """ var('stripes', """
float roundx = floorf(tx + 0.5f); float roundx = floorf(tx + 0.5f);
float offsetx = tx - roundx; float offsetx = tx - roundx;
ox += w * (offsetx * (1.0f - {{pv.space}}) + roundx); ox += w * (offsetx * (1.0f - {{pv.space}}) + roundx);
oy += w * (ty + offsetx*offsetx*{{pv.warp}}); oy += w * (ty + offsetx*offsetx*{{pv.warp}});
""", 'space warp') """)
var(77, 'wedge', """ var('wedge', """
float r = sqrtf(tx*tx + ty*ty); float r = sqrtf(tx*tx + ty*ty);
float a = atan2f(ty, tx) + {{pv.swirl}} * r; float a = atan2f(ty, tx) + {{pv.swirl}} * r;
float wc = {{pv.count}}; float wc = {{pv.count}};
@ -881,9 +855,9 @@ var(77, 'wedge', """
r = w * (r + {{pv.hole}}); r = w * (r + {{pv.hole}});
ox += r * cosf(a); ox += r * cosf(a);
oy += r * sinf(a); oy += r * sinf(a);
""", 'angle hole count=1 swirl') """)
var(80, 'whorl', """ var('whorl', """
float r = sqrtf(tx*tx + ty*ty); float r = sqrtf(tx*tx + ty*ty);
float a = atan2f(ty,tx); float a = atan2f(ty,tx);
@ -894,93 +868,93 @@ var(80, 'whorl', """
ox += w * r * cosf(a); ox += w * r * cosf(a);
oy += w * r * sinf(a); oy += w * r * sinf(a);
""", 'inside outside') """)
var(81, 'waves2', """ var('waves2', """
ox += w*(tx + {{pv.scalex}}*sinf(ty * {{pv.freqx}})); ox += w*(tx + {{pv.scalex}}*sinf(ty * {{pv.freqx}}));
oy += w*(ty + {{pv.scaley}}*sinf(tx * {{pv.freqy}})); oy += w*(ty + {{pv.scaley}}*sinf(tx * {{pv.freqy}}));
""", 'scalex scaley freqx freqy') """)
var(82, 'exp', """ var('exp', """
float expe = expf(tx); float expe = expf(tx);
ox += w * expe * cosf(ty); ox += w * expe * cosf(ty);
oy += w * expe * sinf(ty); oy += w * expe * sinf(ty);
""") """)
var(83, 'log', """ var('log', """
ox += w * 0.5f * logf(tx*tx + ty*ty); ox += w * 0.5f * logf(tx*tx + ty*ty);
oy += w * atan2f(ty, tx); oy += w * atan2f(ty, tx);
""") """)
var(84, 'sin', """ var('sin', """
ox += w * sinf(tx) * coshf(ty); ox += w * sinf(tx) * coshf(ty);
oy += w * cosf(tx) * sinhf(ty); oy += w * cosf(tx) * sinhf(ty);
""") """)
var(85, 'cos', """ var('cos', """
ox += w * cosf(tx) * coshf(ty); ox += w * cosf(tx) * coshf(ty);
oy -= w * sinf(tx) * sinhf(ty); oy -= w * sinf(tx) * sinhf(ty);
""") """)
var(86, 'tan', """ var('tan', """
float tanden = 1.0f/(cosf(2.0f*tx) + coshf(2.0f*ty)); float tanden = 1.0f/(cosf(2.0f*tx) + coshf(2.0f*ty));
ox += w * tanden * sinf(2.0f*tx); ox += w * tanden * sinf(2.0f*tx);
oy += w * tanden * sinhf(2.0f*ty); oy += w * tanden * sinhf(2.0f*ty);
""") """)
var(87, 'sec', """ var('sec', """
float secden = 2.0f/(cosf(2.0f*tx) + coshf(2.0f*ty)); float secden = 2.0f/(cosf(2.0f*tx) + coshf(2.0f*ty));
ox += w * secden * cosf(tx) * coshf(ty); ox += w * secden * cosf(tx) * coshf(ty);
oy += w * secden * sinf(tx) * sinhf(ty); oy += w * secden * sinf(tx) * sinhf(ty);
""") """)
var(88, 'csc', """ var('csc', """
float cscden = 2.0f/(coshf(2.0f*ty) - cosf(2.0f*tx)); float cscden = 2.0f/(coshf(2.0f*ty) - cosf(2.0f*tx));
ox += w * cscden * sinf(tx) * coshf(ty); ox += w * cscden * sinf(tx) * coshf(ty);
oy -= w * cscden * cosf(tx) * sinhf(ty); oy -= w * cscden * cosf(tx) * sinhf(ty);
""") """)
var(89, 'cot', """ var('cot', """
float cotden = 1.0f/(coshf(2.0f*ty) - cosf(2.0f*tx)); float cotden = 1.0f/(coshf(2.0f*ty) - cosf(2.0f*tx));
ox += w * cotden * sinf(2.0f*tx); ox += w * cotden * sinf(2.0f*tx);
oy += w * cotden * -1.0f * sinhf(2.0f*ty); oy += w * cotden * -1.0f * sinhf(2.0f*ty);
""") """)
var(90, 'sinh', """ var('sinh', """
ox += w * sinhf(tx) * cosf(ty); ox += w * sinhf(tx) * cosf(ty);
oy += w * coshf(tx) * sinf(ty); oy += w * coshf(tx) * sinf(ty);
""") """)
var(91, 'cosh', """ var('cosh', """
ox += w * coshf(tx) * cosf(ty); ox += w * coshf(tx) * cosf(ty);
oy += w * sinhf(tx) * sinf(ty); oy += w * sinhf(tx) * sinf(ty);
""") """)
var(92, 'tanh', """ var('tanh', """
float tanhden = 1.0f/(cosf(2.0f*ty) + coshf(2.0f*tx)); float tanhden = 1.0f/(cosf(2.0f*ty) + coshf(2.0f*tx));
ox += w * tanhden * sinhf(2.0f*tx); ox += w * tanhden * sinhf(2.0f*tx);
oy += w * tanhden * sinf(2.0f*ty); oy += w * tanhden * sinf(2.0f*ty);
""") """)
var(93, 'sech', """ var('sech', """
float sechden = 2.0f/(cosf(2.0f*ty) + coshf(2.0f*tx)); float sechden = 2.0f/(cosf(2.0f*ty) + coshf(2.0f*tx));
ox += w * sechden * cosf(ty) * coshf(tx); ox += w * sechden * cosf(ty) * coshf(tx);
oy -= w * sechden * sinf(ty) * sinhf(tx); oy -= w * sechden * sinf(ty) * sinhf(tx);
""") """)
var(94, 'csch', """ var('csch', """
float cschden = 2.0f/(coshf(2.0f*tx) - cosf(2.0f*ty)); float cschden = 2.0f/(coshf(2.0f*tx) - cosf(2.0f*ty));
ox += w * cschden * sinhf(tx) * cosf(ty); ox += w * cschden * sinhf(tx) * cosf(ty);
oy -= w * cschden * coshf(tx) * sinf(ty); oy -= w * cschden * coshf(tx) * sinf(ty);
""") """)
var(95, 'coth', """ var('coth', """
float cothden = 1.0f/(coshf(2.0f*tx) - cosf(2.0f*ty)); float cothden = 1.0f/(coshf(2.0f*tx) - cosf(2.0f*ty));
ox += w * cothden * sinhf(2.0f*tx); ox += w * cothden * sinhf(2.0f*tx);
oy += w * cothden * sinf(2.0f*ty); oy += w * cothden * sinf(2.0f*ty);
""") """)
var(97, 'flux', """ var('flux', """
float xpw = tx + w; float xpw = tx + w;
float xmw = tx - w; float xmw = tx - w;
float avgr = w * (2.0f + {{pv.spread}}) float avgr = w * (2.0f + {{pv.spread}})
@ -988,9 +962,9 @@ var(97, 'flux', """
float avga = (atan2f(ty, xmw) - atan2f(ty,xpw))*0.5f; float avga = (atan2f(ty, xmw) - atan2f(ty,xpw))*0.5f;
ox += avgr * cosf(avga); ox += avgr * cosf(avga);
oy += avgr * sinf(avga); oy += avgr * sinf(avga);
""", 'spread') """)
var(98, 'mobius', """ var('mobius', """
float rea = {{pv.re_a}}; float rea = {{pv.re_a}};
float ima = {{pv.im_a}}; float ima = {{pv.im_a}};
float reb = {{pv.re_b}}; float reb = {{pv.re_b}};
@ -1011,5 +985,4 @@ var(98, 'mobius', """
ox += rad_v * (re_u*re_v + im_u*im_v); ox += rad_v * (re_u*re_v + im_u*im_v);
oy += rad_v * (im_u*re_v - re_u*im_v); oy += rad_v * (im_u*re_v - re_u*im_v);
""", 're_a im_a re_b im_b re_c im_c re_d im_d') """)

View File

@ -21,7 +21,7 @@ def mkdsc(dim, ch):
format=cuda.array_format.FLOAT) format=cuda.array_format.FLOAT)
class Filter(object): class Filter(object):
def apply(self, fb, gnm, dim, tc, stream=None): def apply(self, fb, gprof, params, dim, tc, stream=None):
""" """
Queue the application of this filter. When the live stream finishes Queue the application of this filter. When the live stream finishes
executing the last item enqueued by this method, the result must be executing the last item enqueued by this method, the result must be
@ -32,15 +32,10 @@ class Filter(object):
class Bilateral(Filter, ClsMod): class Bilateral(Filter, ClsMod):
lib = code.filters.bilaterallib lib = code.filters.bilaterallib
def __init__(self, directions=8, r=15, sstd=6, cstd=0.05, radius = 15
dstd=1.5, dpow=0.8, gspeed=4.0): directions = 8
# TODO: expose these parameters on the genome, or at least on the
# profile, and set them by a less ugly mechanism
for n in 'directions r sstd cstd dstd dpow gspeed'.split():
setattr(self, n, locals()[n])
super(Bilateral, self).__init__()
def apply(self, fb, gnm, dim, tc, stream=None): def apply(self, fb, gprof, params, dim, tc, stream=None):
# Helper variables and functions to keep it clean # Helper variables and functions to keep it clean
sb = 16 * dim.astride sb = 16 * dim.astride
bs = sb * dim.ah bs = sb * dim.ah
@ -53,7 +48,7 @@ class Bilateral(Filter, ClsMod):
for pattern in range(self.directions): for pattern in range(self.directions):
# Scale spatial parameter so that a "pixel" is equivalent to an # Scale spatial parameter so that a "pixel" is equivalent to an
# actual pixel at 1080p # actual pixel at 1080p
sstd = self.sstd * dim.w / 1920. sstd = params.spatial_std(tc) * dim.w / 1920.
tref.set_address_2d(fb.d_front, dsc, sb) tref.set_address_2d(fb.d_front, dsc, sb)
@ -67,38 +62,39 @@ class Bilateral(Filter, ClsMod):
grad_tref.set_address_2d(fb.d_side, grad_dsc, sb / 4) grad_tref.set_address_2d(fb.d_side, grad_dsc, sb / 4)
launch2('bilateral', self.mod, stream, dim, launch2('bilateral', self.mod, stream, dim,
fb.d_back, i32(pattern), i32(self.r), fb.d_back, i32(pattern), i32(self.radius),
f32(sstd), f32(self.cstd), f32(self.dstd), f32(sstd), f32(params.color_std(tc)),
f32(self.dpow), f32(self.gspeed), f32(params.density_std(tc)), f32(params.density_pow(tc)),
f32(params.gradient(tc)),
texrefs=[tref, grad_tref]) texrefs=[tref, grad_tref])
fb.flip() fb.flip()
class Logscale(Filter, ClsMod): class Logscale(Filter, ClsMod):
lib = code.filters.logscalelib lib = code.filters.logscalelib
def apply(self, fb, gnm, dim, tc, stream=None): def apply(self, fb, gprof, params, dim, tc, stream=None):
"""Log-scale in place.""" """Log-scale in place."""
k1 = f32(gnm.color.brightness(tc) * 268 / 256) k1 = f32(params.brightness(tc) * 268 / 256)
# Old definition of area is (w*h/(s*s)). Since new scale 'ns' is now # Old definition of area is (w*h/(s*s)). Since new scale 'ns' is now
# s/w, new definition is (w*h/(s*s*w*w)) = (h/(s*s*w)) # s/w, new definition is (w*h/(s*s*w*w)) = (h/(s*s*w))
area = dim.h / (gnm.camera.scale(tc) ** 2 * dim.w) area = dim.h / (params.scale(tc) ** 2 * dim.w)
k2 = f32(1.0 / (area * gnm.spp(tc))) k2 = f32(1.0 / (area * gprof.spp(tc)))
launch2('logscale', self.mod, stream, dim, launch2('logscale', self.mod, stream, dim,
fb.d_front, fb.d_front, k1, k2) fb.d_front, fb.d_front, k1, k2)
class HaloClip(Filter, ClsMod): class HaloClip(Filter, ClsMod):
lib = code.filters.halocliplib lib = code.filters.halocliplib
def apply(self, fb, gnm, dim, tc, stream=None): def apply(self, fb, gprof, params, dim, tc, stream=None):
gam = f32(1 / gnm.color.gamma(tc) - 1) gam = f32(1 / params.gamma(tc) - 1)
dsc = mkdsc(dim, 1) dsc = mkdsc(dim, 1)
tref = mktref(self.mod, 'chan1_src') tref = mktref(self.mod, 'chan1_src')
launch2('apply_gamma', self.mod, stream, dim, launch2('apply_gamma', self.mod, stream, dim,
fb.d_side, fb.d_front, gam) fb.d_side, fb.d_front, gam)
tref.set_address_2d(fb.d_side, dsc, 4 * dim.astride) tref.set_address_2d(fb.d_side, dsc, 4 * params.astride)
launch2('den_blur_1c', self.mod, stream, dim, launch2('den_blur_1c', self.mod, stream, dim,
fb.d_back, i32(0), i32(0), texrefs=[tref]) fb.d_back, i32(0), i32(0), texrefs=[tref])
tref.set_address_2d(fb.d_back, dsc, 4 * dim.astride) tref.set_address_2d(fb.d_back, dsc, 4 * params.astride)
launch2('den_blur_1c', self.mod, stream, dim, launch2('den_blur_1c', self.mod, stream, dim,
fb.d_side, i32(1), i32(0), texrefs=[tref]) fb.d_side, i32(1), i32(0), texrefs=[tref])
@ -107,17 +103,22 @@ class HaloClip(Filter, ClsMod):
class ColorClip(Filter, ClsMod): class ColorClip(Filter, ClsMod):
lib = code.filters.colorcliplib lib = code.filters.colorcliplib
def apply(self, fb, gnm, dim, tc, stream=None): def apply(self, fb, gprof, params, dim, tc, stream=None):
# TODO: implement integration over cubic splines? # TODO: implement integration over cubic splines?
gam = f32(1 / gnm.color.gamma(tc)) gam = f32(1 / params.gamma(tc))
vib = f32(gnm.color.vibrance(tc)) vib = f32(params.vibrance(tc))
hipow = f32(gnm.color.highlight_power(tc)) hipow = f32(params.highlight_power(tc))
lin = f32(gnm.color.gamma_threshold(tc)) lin = f32(params.gamma_threshold(tc))
lingam = f32(lin ** (gam-1.0) if lin > 0 else 0) lingam = f32(lin ** (gam-1.0) if lin > 0 else 0)
bkgd = vec.make_float3(
gnm.color.background.r(tc),
gnm.color.background.g(tc),
gnm.color.background.b(tc))
launch2('colorclip', self.mod, stream, dim, launch2('colorclip', self.mod, stream, dim,
fb.d_front, gam, vib, hipow, lin, lingam, bkgd) fb.d_front, gam, vib, hipow, lin, lingam)
# Ungainly but practical.
filter_map = dict(bilateral=Bilateral, logscale=Logscale, haloclip=HaloClip,
colorclip=ColorClip)
def create(gprof):
# TODO: redesign this (should not have to care about internals of
# use.Wrapper in order to find types from TypedList elements)
filts = gprof._val.get('filters') or gprof.spec['filters'].defaults
return [filter_map[f['type']]() for f in filts]

View File

@ -1,473 +0,0 @@
#!/usr/bin/env python2
import base64
import warnings
import xml.parsers.expat
import numpy as np
from code.variations import var_code, var_params
from code.util import crep
class SplEval(object):
_mat = np.matrix([[1.,-2, 1, 0], [2,-3, 0, 1],
[1,-1, 0, 0], [-2, 3, 0, 0]])
_deriv = np.matrix(np.diag([3,2,1], 1))
def __init__(self, knots, v0=None, v1=None):
self.knots = self.normalize(knots, v0, v1)
@staticmethod
def normalize(knots, v0=None, v1=None):
if isinstance(knots, (int, float)):
knots = [0.0, knots, 1.0, knots]
elif not np.all(np.diff(np.float32(np.asarray(knots))[::2]) > 0):
raise ValueError("Spline times are non-monotonic. (Use "
"nextafterf()-spaced times to anchor tangents.)")
# If stabilizing knots are missing before or after the edges of the
# [0,1] interval, add them.
if knots[0] >= 0:
if v0 is None:
v0 = (knots[3] - knots[1]) / float(knots[2] - knots[0])
knots = [-2, knots[3] - (knots[2] + 2) * v0] + knots
if knots[-2] <= 1:
if v1 is None:
v1 = (knots[-1] - knots[-3]) / float(knots[-2] - knots[-4])
knots.extend([3, knots[-3] + (3 - knots[-4]) * v1])
knotarray = np.zeros((2, len(knots)/2))
knotarray.T.flat[:] = knots
return knotarray
def find_knots(self, itime):
idx = np.searchsorted(self.knots[0], itime) - 2
idx = max(0, min(idx, len(self.knots[0]) - 4))
times = self.knots[0][idx:idx+4]
vals = self.knots[1][idx:idx+4]
# Normalize to [0,1]
t = itime - times[1]
times = times - times[1]
scale = 1 / times[2]
t = t * scale
times = times * scale
return times, vals, t, scale
def __call__(self, itime, deriv=0):
times, vals, t, scale = self.find_knots(itime)
m1 = (vals[2] - vals[0]) / (1.0 - times[0])
m2 = (vals[3] - vals[1]) / times[3]
mat = self._mat
if deriv:
mat = mat * (scale * self._deriv) ** deriv
val = [m1, vals[1], m2, vals[2]] * mat * np.array([[t**3, t**2, t, 1]]).T
return val[0,0]
def _plt(self, name='SplEval', fig=111, show=True):
import matplotlib.pyplot as plt
x = np.linspace(-0.0, 1.0, 500)
r = x[1] - x[0]
plt.figure(fig)
plt.title(name)
plt.plot(x,map(self,x),x,[self(i,1) for i in x],'--',
self.knots[0],self.knots[1],'x')
plt.xlim(0.0, 1.0)
if show:
plt.show()
def __str__(self):
return '[%g:%g]' % (self(0), self(1))
def __repr__(self):
return '<interp [%g:%g]>' % (self(0), self(1))
@property
def knotlist(self):
# TODO: scale error constants proportional to RMS?
# If everything is constant, return a constant
if np.std(self.knots[1]) < 1e-6:
return self.knots[1][0]
# If constant slope, omit the end knots
slopes = np.diff(self.knots[1]) / np.diff(self.knots[0])
if np.std(slopes) < 1e-6:
return list(self.knots.T.flat)[2:-2]
return list(self.knots.T.flat)
def update(self, knots, overwrite=True):
"""
Update this spline's knotlist with ``knots``, a list of two-tuples
(time, value) or a dictionary of the same, while preserving the zeroth
and first derivatives at t=0 and t=1.
If `overwrite` is True (the default), any knot values with precisely
the same float32 representation will be overwritten by the incoming
values. If not, a KeyError will be raised. Counting on this is not
recommended, due to the vagaries of floating-point representations,
but it works fine in a pinch.
Endpoint-preservation is not guaranteed (or conversely, is guaranteed
not to work) if any time is passed outside of the exclusive range
(0,1).
"""
old = dict(self.knots.T[1:-1])
new = dict(knots)
if not overwrite and set(old).intersection(set(new)):
raise KeyError("Conflicting spline times")
old.update(new)
knots = list(sum(sorted(old.items()), ()))
self.knots = self.normalize(knots, self(0, 1), self(1, 1))
def insert_knot(self, t, v):
self.update([(t, v)], True)
def palette_decode(datastrs):
"""
Decode a palette (stored as a list suitable for JSON packing) into a
palette. Internal palette format is simply as a (256,4) array of [0,1]
RGBA floats.
"""
if datastrs[0] != 'rgb8':
raise NotImplementedError
raw = base64.b64decode(''.join(datastrs[1:]))
pal = np.reshape(np.fromstring(raw, np.uint8), (256, 3))
data = np.ones((256, 4), np.float32)
data[:,:3] = pal / 255.0
return data
def palette_encode(data, format='rgb8'):
"""
Encode an internal-format palette to an external representation.
"""
if format != 'rgb8':
raise NotImplementedError
clamp = np.maximum(0, np.minimum(255, np.round(data[:,:3]*255.0)))
enc = base64.b64encode(np.uint8(clamp))
return ['rgb8'] + [enc[i:i+64] for i in range(0, len(enc), 64)]
class _AttrDict(dict):
def __getattr__(self, name):
if name in self:
return self[name]
raise AttributeError('%s not a dict key' % name)
@classmethod
def _wrap(cls, dct):
for k, v in dct.items():
if (isinstance(v, (float, int)) or
(isinstance(v, list) and isinstance(v[1], (float, int)))):
dct[k] = SplEval(v)
elif isinstance(v, dict):
dct[k] = cls._wrap(cls(v))
return dct
class Genome(_AttrDict):
"""
Load a genome description, wrapping all data structures in _AttrDicts,
converting lists of numbers to splines, and deriving some values. Derived
values are stored as instance properties, rather than replacing the
original values, such that JSON-encoding this structure should always
print a valid genome.
"""
# For now, we base the Genome class on an _AttrDict, letting its structure
# be defined implicitly by the way it is used in device code, except for
# these derived properties.
def __init__(self, gnm):
super(Genome, self).__init__(gnm)
for k, v in self.items():
if not isinstance(v, dict):
continue
v = _AttrDict(v)
# These two properties must be handled separately
if k not in ('info', 'time'):
_AttrDict._wrap(v)
self[k] = v
self.decoded_palettes = map(palette_decode, self.palettes)
pal = self.color.palette_times
if isinstance(pal, basestring):
self.palette_times = [(0.0, int(pal)), (1.0, int(pal))]
else:
self.palette_times = zip(pal[::2], map(int, pal[1::2]))
self.adj_frame_width, self.spp = None, None
def set_profile(self, prof, offset=0.0, err_spread=True):
"""
Sets the instance props which are dependent on a profile. Also
calculates timing information, which is returned instead of being
attached to the genome. May be called multiple times to set different
options.
``prof`` is a profile dictionary. ``offset`` is the time in seconds
that the first frame's effective presentation time should be offset
from the natural presentation time. ``err_spread`` will spread the
rounding error in this frame across all frames, such that PTS+(1/FPS)
is exactly equal to the requested duration.
Returns ``(err, times)``, where ``err`` is the rounding error in
seconds (taking ``offset`` into account), and ``times`` is a list of
the central time of each frame in the animation in relative-time
coordinates. Also sets the ``spp`` and ``adj_frame_width`` properties.
"""
self.spp = SplEval(self.camera.density.knotlist)
self.spp.knots[1] *= prof['quality']
fps, base_dur = prof['fps'], prof['duration']
# TODO: test!
dur = self.time.duration
if isinstance(dur, basestring):
clock = float(dur[:-1]) + offset
else:
clock = dur * base_dur + offset
if (not isinstance(self.get('link'), dict) or
not self.link.get('right')):
warnings.warn("Genomes with missing or string-valued 'link' "
"properties are deprecated, and will be axed shortly.")
nframes = int(np.ceil(clock * fps))
elif self.link.right == 'reference':
nframes = int(np.floor(clock * fps))
else:
nframes = int(np.ceil(clock * fps))
err = (clock - nframes / fps) / clock
fw = self.time.frame_width
if not isinstance(fw, list):
fw = [0, fw, 1, fw]
fw = [float(f[:-1]) * fps if isinstance(f, basestring)
else float(f) / (clock * fps) for f in fw]
self.adj_frame_width = SplEval(fw)
times = np.linspace(offset, 1 - err, nframes + 1)
# Move each time to a center time, and discard the last value
times = times[:-1] + 0.5 * (times[1] - times[0])
if err_spread:
epts = np.linspace(-2*np.pi, 2*np.pi, nframes)
times = times + 0.5 * err * (np.tanh(epts) + 1)
return err, times
def json_encode_genome(obj):
"""
Encode an object into JSON notation. This serializer only works on the
subset of JSON used in genomes.
"""
result = _js_enc_obj(obj).lstrip()
result = '\n'.join(l.rstrip() for l in result.split('\n'))
return result + '\n'
def _js_enc_obj(obj, indent=0):
isnum = lambda v: isinstance(v, (float, int, np.number))
def wrap(pairs, delims):
do, dc = delims
i = ' ' * indent
out = ''.join([do, ', '.join(pairs), dc])
if '\n' not in out and len(out) + indent < 70:
return out
return ''.join(['\n', i, do, ' ', ('\n'+i+', ').join(pairs),
'\n', i, dc])
if isinstance(obj, dict):
if not obj:
return '{}'
digsort = lambda kv: (int(kv[0]), kv[1]) if kv[0].isdigit() else kv
ks, vs = zip(*sorted(obj.items(), key=digsort))
if ks == ('b', 'g', 'r'):
ks, vs = reversed(ks), reversed(vs)
ks = [crep('%.6g' % k if isnum(k) else str(k)) for k in ks]
vs = [_js_enc_obj(v, indent+2) for v in vs]
return wrap(['%s: %s' % p for p in zip(ks, vs)], '{}')
elif isinstance(obj, list):
vs = [_js_enc_obj(v, indent+2) for v in obj]
if vs and len(vs) % 2 == 0 and isnum(obj[0]):
vs = map(', '.join, zip(vs[::2], vs[1::2]))
return wrap(vs, '[]')
elif isinstance(obj, SplEval):
return _js_enc_obj(obj.knotlist, indent)
elif isinstance(obj, basestring):
return crep(obj)
elif isnum(obj):
return '%.6g' % obj
raise TypeError("Don't know how to serialize %s of type %s" %
(obj, type(obj)))
class XMLGenomeParser(object):
"""
Parse an XML genome into a list of dictionaries.
"""
def __init__(self):
self.flames = []
self._flame = None
self.parser = xml.parsers.expat.ParserCreate()
self.parser.StartElementHandler = self.start_element
self.parser.EndElementHandler = self.end_element
def start_element(self, name, attrs):
if name == 'flame':
assert self._flame is None
self._flame = dict(attrs)
self._flame['xforms'] = []
self._flame['palette'] = np.ones((256, 4), dtype=np.float32)
elif name == 'xform':
self._flame['xforms'].append(dict(attrs))
elif name == 'finalxform':
self._flame['finalxform'] = dict(attrs)
elif name == 'color':
idx = int(attrs['index'])
self._flame['palette'][idx][:3] = [float(v) / 255.0
for v in attrs['rgb'].split()]
elif name == 'symmetry':
self._flame['symmetry'] = int(attrs['kind'])
def end_element(self, name):
if name == 'flame':
self.flames.append(self._flame)
self._flame = None
@classmethod
def parse(cls, src):
parser = cls()
parser.parser.Parse(src, True)
return parser.flames
def convert_flame(flame, arc=-360, offset=0):
"""
Convert an XML flame (as returned by XMLGenomeParser) into a plain dict
in cuburn's JSON genome format representing a loop edge. Caller is
responsible for correctly setting the 'link' dict.
"""
cvt = lambda ks: dict((k, float(flame[k])) for k in ks)
camera = {
'center': dict(zip('xy', map(float, flame['center'].split()))),
'scale': float(flame['scale']) / float(flame['size'].split()[0]),
'dither_width': float(flame['filter']),
'rotation': float(flame.get('rotate', 0)),
'density': 1.0
}
info = {}
if 'name' in flame:
info['name'] = flame['name']
if 'nick' in flame:
info['authors'] = [flame['nick']]
if flame.get('url'):
info['authors'][0] = info['authors'][0] + ', http://' + flame['url']
time = dict(frame_width=float(flame.get('temporal_filter_width', 1)),
duration=abs(arc)/360.)
color = cvt(['brightness', 'gamma'])
color.update((k, float(flame.get(k, d))) for k, d in
[('highlight_power', -1), ('gamma_threshold', 0.01)])
color['vibrance'] = float(flame.get('vibrancy', 1))
color['background'] = dict(zip('rgb',
map(float, flame['background'].split())))
color['palette_times'] = "0"
pal = palette_encode(flame['palette'])
de = dict((k, float(flame.get(f, d))) for f, k, d in
[('estimator', 'radius', 11),
('estimator_minimum', 'minimum', 0),
('estimator_curve', 'curve', 0.6)])
num_xf = len(flame['xforms'])
xfs = dict([(str(k), convert_xform(v, num_xf, arc, offset))
for k, v in enumerate(flame['xforms'])])
if 'symmetry' in flame:
xfs.update(make_symm_xforms(flame['symmetry'], len(xfs)))
if 'finalxform' in flame:
xfs['final'] = convert_xform(flame['finalxform'], num_xf,
arc, offset, True)
return dict(camera=camera, color=color, de=de, xforms=xfs,
info=info, time=time, palettes=[pal], link='self')
def convert_xform(xf, num_xf, arc, offset, isfinal=False):
# TODO: chaos
xf = dict(xf)
symm = float(xf.pop('symmetry', 0))
anim = xf.pop('animate', symm <= 0)
out = dict((k, float(xf.pop(k, v))) for k, v in
dict(color=0, color_speed=(1-symm)/2, opacity=1).items())
if not isfinal:
out['density'] = float(xf.pop('weight'))
out['affine'] = convert_affine(xf.pop('coefs'), arc, offset, anim)
if 'post' in xf and map(float, xf['post'].split()) != [1, 0, 0, 1, 0, 0]:
out['post'] = convert_affine(xf.pop('post'), arc, offset)
if 'chaos' in xf:
chaos = map(float, xf.pop('chaos').split())
out['chaos'] = dict()
for i in range(num_xf):
if i < len(chaos):
out['chaos'][str(i)] = chaos[i]
else:
out['chaos'][str(i)] = 1.0
out['variations'] = {}
for k in var_code:
if k in xf:
var = dict(weight=float(xf.pop(k)))
for param, default in var_params.get(k, {}).items():
var[param] = float(xf.pop('%s_%s' % (k, param), default))
out['variations'][k] = var
assert not xf, 'Unrecognized parameters remain: ' + str(xf)
return out
def convert_affine(aff, arc, offset, animate=False):
xx, yx, xy, yy, xo, yo = map(float, aff.split())
# Invert all instances of y (yy is inverted twice)
yx, xy, yo = -yx, -xy, -yo
xa = np.degrees(np.arctan2(yx, xx))
ya = np.degrees(np.arctan2(yy, xy))
xm = np.hypot(xx, yx)
ym = np.hypot(xy, yy)
angle_between = ya - xa
if angle_between < 0:
angle_between += 360
if angle_between < 180:
spread = angle_between / 2.0
else:
spread = -(360-angle_between) / 2.0
angle = xa + spread
if angle < 0:
angle += 360.0
if animate:
angle = [0, angle + offset, 1, angle + arc + offset]
return dict(spread=spread, magnitude={'x': xm, 'y': ym},
angle=angle, offset={'x': xo, 'y': yo})
def make_symm_xforms(kind, offset):
assert kind != 0, 'Pick your own damn symmetry.'
out = []
boring_xf = dict(color=1, color_speed=0, density=1, opacity=1,
variations={'linear': {'weight': 1}})
if kind < 0:
out.append(boring_xf.copy())
out[-1]['affine'] = dict(angle=135, magnitude={'x': 1, 'y': 1},
spread=-45, offset={'x': 0, 'y': 0})
kind = -kind
for i in range(1, kind):
out.append(boring_xf.copy())
if kind >= 3:
out[-1]['color'] = (i - 1) / (kind - 2.0)
ang = (45 + 360 * i / float(kind)) % 360
out[-1]['affine'] = dict(angle=ang, magnitude={'x': 1, 'y': 1},
spread=-45, offset={'x': 0, 'y': 0})
return dict((str(i+offset), v) for i, v in enumerate(out))
def convert_file(path):
"""Quick one-shot conversion for an XML genome."""
flames = XMLGenomeParser.parse(open(path).read())
if len(flames) > 10:
warnings.warn("Lot of flames in this file. Sure it's not a "
"frame-based animation?")
for flame in flames:
yield convert_flame(flame)
if __name__ == "__main__":
import sys
print '\n\n'.join(map(json_encode_genome, convert_file(sys.argv[1])))

View File

288
cuburn/genome/blend.py Normal file
View File

@ -0,0 +1,288 @@
#!/usr/bin/python
# Copyright 2011-2012 Erik Reckase <e.reckase@gmail.com>,
# Steven Robertson <steven@strobe.cc>.
import numpy as np
from copy import deepcopy
from itertools import izip_longest
from scipy.ndimage.filters import gaussian_filter1d
import spectypes
import spec
import util
from util import get
import variations
# TODO: move to better place before checkin
default_blend_opts = {'nloops': 2, 'duration': 2, 'xform_sort': 'weightflip'}
def blend(src, dst, edit={}):
"""
Blend two nodes to produce an animation.
``src`` and ``dst`` are the source and destination node specs for the
animation. These should be plain node dicts (hierarchical, pre-merged,
and adjusted for loop temporal offset).
``edit`` is an optional edit dict, also hierarchical and pre-merged.
Returns the animation spec as a plain dict.
"""
# By design, the blend element will contain only scalar values (no
# splines or hierarchy), so this can be done blindly
opts = dict(default_blend_opts)
for d in src, dst, edit:
opts.update(d.get('blend', {}))
blended = merge_nodes(spec.node, src, dst, edit, opts['nloops'])
name_map = sort_xforms(src['xforms'], dst['xforms'], opts['xform_sort'],
explicit=zip(*opts.get('xform_map', [])))
blended['xforms'] = {}
for (sxf_key, dxf_key) in name_map:
bxf_key = (sxf_key or 'pad') + '_' + (dxf_key or 'pad')
xf_edits = merge_edits(spec.xform,
get(edit, {}, 'xforms', 'src', sxf_key),
get(edit, {}, 'xforms', 'dst', dxf_key))
blended['xforms'][bxf_key] = blend_xform(
src['xforms'].get(sxf_key),
dst['xforms'].get(dxf_key),
xf_edits, opts['nloops'])
if 'final_xform' in src or 'final_xform' in dst:
blended['final_xform'] = blend_xform(src.get('final_xform'),
dst.get('final_xform'), edit.get('final_xform'), 0, True)
# TODO: write 'info' section
# TODO: palflip
blended['type'] = 'animation'
blended.setdefault('time', {})['duration'] = opts['duration']
return blended
def merge_edits(sv, av, bv):
"""
Merge the values of ``av`` and ``bv`` according to the spec ``sv``.
"""
if isinstance(spec, (dict, spectypes.Map)):
av, bv = av or {}, bv or {}
getsv = lambda k: sv.type if isinstance(sv, spectypes.Map) else sv[k]
return dict([(k, merge_edits(getsv(k), av.get(k), bv.get(k)))
for k in set(av.keys() + bv.keys())])
elif isinstance(sv, (spectypes.List, spectypes.Spline)):
return (av or []) + (bv or [])
else:
return bv if bv is not None else av
def tospline(spl, src, dst, edit, loops):
def split_node_val(val):
if val is None:
return spl.default, 0
if isinstance(val, (int, float)):
return val, 0
return val
sp, sv = split_node_val(src) # position, velocity
dp, dv = split_node_val(dst)
# For variation parameters, copy missing values instead of using defaults
if spl.var:
if src is None:
sp = dp
if dst is None:
dp = sp
edit = dict(zip(edit[::2], edit[1::2])) if edit else {}
e0, e1 = edit.pop(0, None), edit.pop(1, None)
edit = zip(*[(k, v) for k, v in edit.items() if v is not None])
if spl.period:
# Periodic extension: compute an appropriate number of loops based on
# the angular velocities at the endpoints, and extend the destination
# position by the appropriate number of periods.
avg_vel = round(float(sv + dv) * loops / spl.period)
dp = dp % spl.period + avg_vel * spl.period
# Endpoint override: allow adjusting the number of loops as calculated
# above by locking to the nearest value with the same mod (i.e. the
# nearest value which will still line up with the node)
if e0 is not None:
sp += round(float(e0 - sp) / spl.period) * spl.period
if e1 is not None:
dp += round(float(e1 - dp) / spl.period) * spl.period
if edit or sv or dv:
return [sp, sv, dp, dv] + edit
if sp != dp:
return [sp, dp]
return sp
def trace(k):
print k,
return k
def merge_nodes(sp, src, dst, edit, loops):
if isinstance(sp, dict):
src, dst, edit = [x or {} for x in src, dst, edit]
return dict([(k, merge_nodes(sp[k], src.get(k),
dst.get(k), edit.get(k), loops))
for k in set(src.keys() + dst.keys() + edit.keys()) if k in sp])
elif isinstance(sp, spectypes.Spline):
return tospline(sp, src, dst, edit, loops)
elif isinstance(sp, spectypes.List):
if isinstance(sp.type, spectypes.Palette):
if src is not None: src = [[0] + src]
if dst is not None: dst = [[1] + dst]
return (src or []) + (dst or []) + (edit or [])
else:
return edit if edit is not None else dst if dst is not None else src
def blend_xform(sxf, dxf, edits, loops, isfinal=False):
if sxf is None:
sxf = padding_xform(dxf, isfinal)
if dxf is None:
dxf = padding_xform(sxf, isfinal)
return merge_nodes(spec.xform, sxf, dxf, edits, loops)
# If xin contains any of these, use the inverse identity
hole_variations = ('spherical ngon julian juliascope polar '
'wedge_sph wedge_julia bipolar').split()
# These variations are identity functions at their default values
ident_variations = ('rectangles rings2 fan2 blob perspective curl '
'super_shape').split()
def padding_xform(xf, isfinal):
vars = {}
xout = {'variations': vars, 'pre_affine': {'angle': 45}}
if isfinal:
xout.update(weight=0, color_speed=0)
if get(xf, 45, 'pre_affine', 'spread') > 90:
xout['pre_affine'] = {'angle': 135, 'spread': 135}
if get(xf, 45, 'post_affine', 'spread') > 90:
xout['post_affine'] = {'angle': 135, 'spread': 135}
for k in xf['variations']:
if k in hole_variations:
# Attempt to correct for some known-ugly variations.
xout['pre_affine']['angle'] += 180
vars['linear'] = dict(weight=-1)
return xout
if k in ident_variations:
# Try to use non-linear variations whenever we can
vars[k] = dict([(vk, vv.default)
for vk, vv in variations.var_params[k].items()])
if vars:
n = float(len(vars))
for k in vars:
vars[k]['weight'] /= n
else:
vars['linear'] = dict(weight=1)
return xout
def blend_genomes(left, right, nloops=2, align='weightflip', seed=None,
stagger=False, blur=None, palflip=True):
align_xforms(left, right, align)
name = '%s=%s' % (left.info.get('name', ''), right.info.get('name', ''))
if seed is None:
seed = map(ord, name)
rng = np.random.RandomState(seed)
blend = blend_splines(left, right, nloops, rng, stagger)
# TODO: licenses; check license compatibility when merging
# TODO: add URL and flockutil revision to authors
blend['info'] = {
'name': name,
'authors': sum([g.info.get('authors', []) for g in left, right], [])
}
blend['info']['authors'].append('flockutil')
blend['palettes'] = [get_palette(left, False), get_palette(right, True)]
blend['color']['palette_times'] = [0, "0", 1, "1"]
if palflip:
checkpalflip(blend)
if blur:
blur_palettes(blend, blur)
return blend
def halfhearted_human_sort_key(key):
try:
return int(key)
except ValueError:
return key
def sort_xforms(sxfs, dxfs, sortmethod, explicit=[]):
# Walk through the explicit pairs, popping previous matches from the
# forward (src=>dst) and reverse (dst=>src) maps
fwd, rev = {}, {}
for sx, dx in explicit:
if sx in fwd:
rev.pop(fwd.pop(sx, None), None)
if dx in rev:
fwd.pop(rev.pop(dx, None), None)
fwd[sx] = dx
rev[dx] = sx
for sd in sorted(fwd.items()):
yield sd
# Classify the remaining xforms. Currently we classify based on whether
# the pre- and post-affine transforms are flipped
scl, dcl = {}, {}
for (cl, xfs, exp) in [(scl, sxfs, fwd), (dcl, dxfs, rev)]:
for k, v in xfs.items():
if k in exp: continue
xcl = (get(v, 45, 'pre_affine', 'spread') > 90,
get(v, 45, 'post_affine', 'spread') > 90)
cl.setdefault(xcl, []).append(k)
def sort(keys, dct, snd=False):
if sortmethod in ('weight', 'weightflip'):
sortf = lambda k: dct[k].get('weight', 0)
elif sortmethod == 'color':
sortf = lambda k: dct[k].get('color', 0)
else:
# 'natural' key-based sort
sortf = halfhearted_human_sort_key
return sorted(keys, key=sortf)
for cl in set(scl.keys() + dcl.keys()):
ssort = sort(scl.get(cl, []), sxfs)
dsort = sort(dcl.get(cl, []), dxfs)
if sortmethod == 'weightflip':
dsort = reversed(dsort)
for sd in izip_longest(ssort, dsort):
yield sd
def checkpalflip(gnm):
if 'final' in gnm['xforms']:
f = gnm['xforms']['final']
fcv, fcsp = f['color'], f['color_speed']
else:
fcv, fcsp = SplEval(0), SplEval(0)
sansfinal = [v for k, v in gnm['xforms'].items() if k != 'final']
lc, rc = [np.array([v['color'](t) * (1 - fcsp(t)) + fcv(t) * fcsp(t)
for v in sansfinal]) for t in (0, 1)]
rcrv = 1 - rc
# TODO: use spline integration instead of L2
dens = np.array([np.hypot(v['weight'](0), v['weight'](1))
for v in sansfinal])
return np.sum(np.abs(dens * (rc - lc))) > np.sum(np.abs(dens * (rcrv - lc)))
def palflip(gnm):
for v in gnm['xforms'].values():
c = v['color']
v['color'] = SplEval([0, c(0), 1, 1 - c(1)], c(0, 1), -c(1, 1))
pal = genome.palette_decode(gnm['palettes'][1])
gnm['palettes'][1] = genome.palette_encode(np.flipud(pal))
if __name__ == "__main__":
import sys, json
a, b, c = [json.load(open(f+'.json')) for f in 'abc']
print util.json_encode(blend(a, b, c))

182
cuburn/genome/convert.py Normal file
View File

@ -0,0 +1,182 @@
#!/usr/bin/env python2
import base64
import warnings
import xml.parsers.expat
import numpy as np
from variations import var_params
import util
class XMLGenomeParser(object):
"""
Parse an XML genome into a list of dictionaries.
"""
def __init__(self):
self.flames = []
self._flame = None
self.parser = xml.parsers.expat.ParserCreate()
self.parser.StartElementHandler = self.start_element
self.parser.EndElementHandler = self.end_element
def start_element(self, name, attrs):
if name == 'flame':
assert self._flame is None
self._flame = dict(attrs)
self._flame['xforms'] = []
self._flame['palette'] = np.ones((256, 4), dtype=np.float32)
elif name == 'xform':
self._flame['xforms'].append(dict(attrs))
elif name == 'finalxform':
self._flame['finalxform'] = dict(attrs)
elif name == 'color':
idx = int(attrs['index'])
self._flame['palette'][idx][:3] = [float(v) / 255.0
for v in attrs['rgb'].split()]
elif name == 'symmetry':
self._flame['symmetry'] = int(attrs['kind'])
def end_element(self, name):
if name == 'flame':
self.flames.append(self._flame)
self._flame = None
@classmethod
def parse(cls, src):
parser = cls()
parser.parser.Parse(src, True)
return parser.flames
def convert_affine(aff, animate=False):
xx, yx, xy, yy, xo, yo = vals = map(float, aff.split())
if vals == [1, 0, 0, 1, 0, 0]: return None
# Cuburn's IFS-space vertical direction is inverted with respect to flam3,
# so we invert all instances of y. (``yy`` is effectively inverted twice.)
yx, xy, yo = -yx, -xy, -yo
xa = np.degrees(np.arctan2(yx, xx))
ya = np.degrees(np.arctan2(yy, xy))
xm = np.hypot(xx, yx)
ym = np.hypot(xy, yy)
spread = ((ya - xa) % 360) / 2
angle = (xa + spread) % 360
return dict(spread=spread, magnitude={'x': xm, 'y': ym},
angle=angle, offset={'x': xo, 'y': yo})
def convert_vars(xf):
struct = lambda k, ps: ([('weight', k, float)] +
[(p, k+'_'+p, float) for p in ps])
return dict([(k, apply_structure(struct(k, ps), xf))
for k, ps in var_params.items() if k in xf])
def convert_xform(xf):
out = apply_structure(xform_structure, xf)
# Deprecated symmetry arg makes this too much of a bother to handle within
# the structure framework
symm = float(xf.get('symmetry', 0))
anim = xf.get('animate', symm <= 0)
if 'symmetry' in xf:
out.setdefault('color_speed', (1-symm)/2)
if anim and 'pre_affine' in out:
out['pre_affine']['angle'] = [out['pre_affine']['angle'], -360]
return out
def make_symm_xforms(kind, offset):
assert kind != 0, 'Pick your own damn symmetry.'
out = []
boring_xf = dict(color=1, color_speed=0, density=1,
variations={'linear': {'weight': 1}})
if kind < 0:
out.append(boring_xf.copy())
out[-1]['affine'] = dict(angle=135, spread=-45)
kind = -kind
for i in range(1, kind):
out.append(boring_xf.copy())
if kind >= 3:
out[-1]['color'] = (i - 1) / (kind - 2.0)
ang = (45 + 360 * i / float(kind)) % 360
out[-1]['affine'] = dict(angle=ang, spread=-45)
return dict(enumerate(out, offset))
def convert_xforms(flame):
xfs = dict(enumerate(map(convert_xform, flame['xforms'])))
if 'symmetry' in flame:
xfs.update(make_symm_xforms(float(flame['symmetry']), len(xfs)))
return xfs
split_to_dict = lambda keys: lambda v: dict(zip(keys, map(float, v.split())))
pair = split_to_dict('xy')
rgb_triple = split_to_dict('rgb')
xform_structure = (
('pre_affine', 'coefs', convert_affine),
('post_affine', 'post', convert_affine),
('color', 'color', float),
('color_speed', 'color_speed', float),
('opacity', 'opacity', float),
('weight', 'weight', float),
('chaos', 'chaos',
lambda s: dict(enumerate(map(float, s.split())))),
('variations', convert_vars)
)
# A list of either three-tuples (dst, src, cvt_val), or two-tuples
# (dst, cvt_dict) for properties that are built from multiple source keys.
# If a function returns 'None', its key is dropped from the result.
flame_structure = (
('info.author', 'nick', str),
('info.author_url', 'url', lambda s: 'http://' + str(s)),
('info.name', 'name', str),
('camera.center', 'center', pair),
('camera.rotation', 'rotate', float),
('camera.dither_width', 'filter', float),
('camera.scale',
lambda d: float(d['scale']) / float(d['size'].split()[0])),
('filters.colorclip.gamma', 'filter', float),
('filters.colorclip.gamma_threshold', 'gamma_threshold', float),
('filters.colorclip.highlight_power', 'highlight_power', float),
('filters.colorclip.vibrance', 'vibrancy', float),
# Not sure about putting this one on colorclip
('filters.colorclip.background', 'background', rgb_triple),
('filters.de.curve', 'estimator_curve', float),
('filters.de.radius', 'estimator_radius', float),
('filters.de.minimum',
lambda d: (float(d['estimator_minimum']) /
float(d.get('estimator_radius', 11)))
if 'estimator_minimum' in d else None),
('palette', 'palette', util.palette_encode),
('xforms', convert_xforms),
('final_xform', 'finalxform', convert_xform),
)
def apply_structure(struct, src):
out = {}
for l in struct:
if len(l) == 2:
v = l[1](src)
else:
v = l[2](src[l[1]]) if l[1] in src else None
if v is not None:
out[l[0]] = v
return out
def convert_flame(flame):
return util.unflatten(util.flatten(apply_structure(flame_structure, flame)))
def convert_file(path):
"""Quick one-shot conversion for an XML genome."""
flames = XMLGenomeParser.parse(open(path).read())
if len(flames) > 10:
warnings.warn("Lot of flames in this file. Sure it's not a "
"frame-based animation?")
for flame in flames:
yield convert_flame(flame)
if __name__ == "__main__":
import sys
print '\n\n'.join(map(util.json_encode, convert_file(sys.argv[1])))

82
cuburn/genome/schema.py Normal file
View File

@ -0,0 +1,82 @@
from schematypes import *
from variations import var_
affine = (
{ angle: spline(45, period=360)
, spread: spline(45, period=180)
# TODO: should these scale relative to magnitude?
, off_x: spline()
, off_y: spline()
# TODO: this is probably an inappropriate scaling domain? Should one be
# constructed specifically for magnitudes?
, mag_x: spline(1)
, mag_y: spline(1)
})
xform = (
{ affine: affine
, post: affine
, color: spline(0, 0, 1)
, color_speed: spline(0.5, 0, 1)
, density: spline()
, opacity: scalespline(max=1)
, variations: cuburn.code.variations.params
})
# Since the structure of the info element differs between anims, nodes and
# edges, we pull out some of the common elements here
author = String('Attribution in the form: "Name [<email>][, url]"')
name = String('A human-readable name for this entity')
src = String('The identifier of the source node')
dst = String('The identifier of the destination node')
filters = (
{ bilateral:
{ spatial_std: scalespline(d='Scale of profile spatial standard deviation')
, color_std: scalespline(d='Scale of profile color standard deviation')
, density_std: scalespline(d='Scale of profile density standard deviation')
, density_pow: scalespline(d='Scale of profile density pre-blur exponent')
, gradient: spline(1, d='Scale of profile gradient filter intensity '
'(can be negative)')
}
, colorclip:
{ bg_r: spline(0, 0, 1)
, bg_g: spline(0, 0, 1)
, bg_b: spline(0, 0, 1)
, gamma: scalespline()
, gamma_threshold: spline(0.01, 0, 1)
, highlight_power: spline(-1, -1, 1)
, vibrance: spline(1, 0, 1)
}
, de:
{ radius: scalespline(d='Scale of profile filter radius')
, minimum: scalespline(0, d='Scale against adjusted DE radius of '
'minimum radius')
, curve: scalespline(0.6, d='Absolute (unscaled) value of DE curve')
}
# TODO: absolute or relative?
, logscale: {brightness: scalespline(4, d='Absolute log brightness')}
})
anim = (
{ type: 'animation'
, info: dict(authors=List(author), name=name, src=src, dst=dst)
, camera:
# Should center_{xy} be scaled relative to the 'scale' parameter, or is
# that just too complicated for this representation?
{ center_x: spline()
, center_y: spline()
, density: scalespline()
, dither_width: scalespline()
, rotation: spline(period=360)
, scale: scalespline()
}
, filters: filters
, time:
{ duration:
, frame_width: scalespline(d='Scale of profile temporal width per frame.')
}
, palettes: list_(Palette())
, xforms: map(xform)
, final_xform: xform
})

112
cuburn/genome/spec.py Normal file
View File

@ -0,0 +1,112 @@
from spectypes import *
from variations import var_params
affine = (
{ 'angle': spline(45, period=360)
, 'spread': spline(45, period=180)
, 'magnitude': XYPair(scalespline())
, 'offset': XYPair(spline())
})
xform = (
{ 'pre_affine': affine
, 'post_affine': affine
, 'color': spline(0, 0, 1)
, 'color_speed': spline(0.5, 0, 1)
, 'weight': spline()
, 'opacity': scalespline(max=1)
, 'variations': var_params
})
# Since the structure of the info element differs between anims, nodes and
# edges, we pull out some of the common elements here
author = String('Attribution in the form: "Name [<email>][, url]"')
name = String('A human-readable name for this entity')
src = String('The identifier of the source node')
dst = String('The identifier of the destination node')
filters = (
{ 'bilateral':
{ 'spatial_std': scalespline(6,
d='Spatial filter radius, normalized to 1080p pixels')
, 'color_std': scalespline(0.05,
d='Color filter radius, in YUV space, normalized to [0,1]')
, 'density_std': scalespline(1.5, d='Density standard deviation')
, 'density_pow': scalespline(0.8, d='Density pre-filter power')
, 'gradient': scalespline(4.0, min=None,
d='Intensity of gradient amplification (can be negative)')
}
, 'colorclip':
{ 'gamma': scalespline(4)
, 'gamma_threshold': spline(0.01, 0, 1)
, 'highlight_power': spline(-1, -1, 1)
, 'vibrance': scalespline()
}
, 'de':
{ 'radius': scalespline(11, d='Spatial filter radius in flam3 units')
, 'minimum': scalespline(0, max=1, d='Proportional min radius')
, 'curve': scalespline(0.6, d='Power of filter radius with density')
}
, 'haloclip': {'gamma': scalespline(4)}
, 'logscale': {'brightness': scalespline(4, d='Log-scale brightness')}
})
camera = (
{ 'center': XYPair(spline())
, 'spp': scalespline(d='Samples per pixel multiplier')
, 'dither_width': scalespline()
, 'rotation': spline(period=360)
, 'scale': scalespline()
})
time = (
{ 'duration': scalar(1)
, 'frame_width': scalespline(d='Scale of profile temporal width per frame.')
})
base = (
{ 'camera': camera
, 'filters': filters
, 'palette': list_(Palette())
, 'xforms': map_(xform)
, 'final_xform': xform
})
anim = dict(base)
anim.update(type='animation', time=time,
info=dict(authors=list_(author), name=name, src=src, dst=dst,
origin=string_()))
# TODO
node = dict(base)
node.update(type='node', info=dict(author=author, author_url=string_(),
id=string_(), name=name))
# TODO
edge = dict(anim)
edge.update(type='edge',
info=dict(author=author, id=string_(), src=src, dst=dst),
xforms=dict(src=map_(xform), dst=map_(xform)))
# Yeah, now I'm just messing around.
prof_filters = dict([(fk, dict([(k, refscalar(1, '.'.join(['filters', fk, k])))
for k in fv])) for fk, fv in filters.items()])
# And here's a completely stupid hack to drag scale into the logscale filter
prof_filters['logscale']['scale'] = refscalar(1, 'camera.scale')
default_filters = [{'type': k} for k in ['bilateral', 'logscale', 'colorclip']]
profile = (
{ 'duration': RefScalar(30, 'time.duration', 'Base duration in seconds')
, 'fps': Scalar(24, 'Frames per second')
, 'height': Scalar(1920, 'Output height in pixels')
, 'width': Scalar(1080, 'Output width in pixels')
, 'frame_width': refscalar(1, 'time.frame_width')
, 'spp': RefScalar(2000, 'camera.spp', 'Base samples per pixel')
, 'skip': Scalar(0, 'Skip this many frames between each rendered frame')
, 'filters': TypedList(prof_filters, default_filters,
'Ordered list of filters to apply')
})
# Types recognized as independent units with a 'type' key
toplevels = dict(animation=anim, node=node, edge=edge, profile=profile)

View File

@ -0,0 +1,45 @@
from collections import namedtuple
Map = namedtuple('Map', 'type doc')
map_ = lambda type, d=None: Map(type, d)
List = namedtuple('List', 'type doc')
list_ = lambda type, d=None: List(type, d)
# A list as above, but where each element is a dict with a 'type' parameter
# corresponding to one of the specs listed in the 'types' dict on this spec.
TypedList = namedtuple('TypedList', 'types defaults doc')
typedlist = lambda types, defaults=[], d=None: TypedList(types, defaults, d)
Spline = namedtuple('Spline', 'default min max interp period doc var')
def spline(default=0, min=None, max=None, interp='linear', period=None, d=None):
return Spline(default, min, max, interp, period, d, False)
def scalespline(default=1, min=0, max=None, d=None):
"""Spline helper, with defaults appropriate for a scaling parameter."""
return Spline(default, min, None, 'mag', None, d, False)
class XYPair(dict):
"""
Specialization of spline over two dimensions. Separate type is a hint to
UIs and mutator, but this may be treated just like a normal dict.
"""
def __init__(self, type):
self['x'] = self['y'] = self.type = type
Scalar = namedtuple('Scalar', 'default doc')
scalar = lambda default, d=None: Scalar(default, d)
# These are scalars, as used in profiles, but which are scaled by some other
# parameter (in the genome) given by name as ``ref``.
RefScalar = namedtuple('RefScalar', 'default ref doc')
refscalar = lambda default, ref, d=None: RefScalar(default, ref, d)
String = namedtuple('String', 'doc')
def string_(d=None):
return String(d)
Enum = namedtuple('Enum', 'choices doc')
def enum(choices, d=None):
"""Enum helper. 'choices' is a space-separated string."""
return Enum(choices.split(), d)
Palette = namedtuple('Palette', '')

188
cuburn/genome/use.py Normal file
View File

@ -0,0 +1,188 @@
import numpy as np
from spectypes import Spline, Scalar, RefScalar, Map, List, TypedList
from spec import toplevels
class Wrapper(object):
def __init__(self, val, spec=None, path=()):
if spec is None:
spec = toplevels[val['type']]
# plain 'val' would conflict with some variation property names
self._val, self.spec, self.path = val, spec, path
def wrap(self, name, spec, val):
# Oh, a visitor. How... pedestrian.
path = self.path + (name,)
if isinstance(spec, Spline):
return self.wrap_spline(path, spec, val)
elif isinstance(spec, Scalar):
return self.wrap_scalar(path, spec, val)
elif isinstance(spec, RefScalar):
return self.wrap_refscalar(path, spec, val)
elif isinstance(spec, dict):
return self.wrap_dict(path, spec, val)
elif isinstance(spec, Map):
return self.wrap_Map(path, spec, val)
elif isinstance(spec, List):
return self.wrap_List(path, spec, val)
elif isinstance(spec, TypedList):
return self.wrap_TypedList(path, spec, val)
return self.wrap_default(path, spec, val)
def wrap_default(self, path, spec, val):
return val
def wrap_spline(self, path, spec, val):
return val
def wrap_scalar(self, path, spec, val):
return val if val is not None else spec.default
def wrap_dict(self, path, spec, val):
return type(self)(val or {}, spec, path)
def wrap_Map(self, path, spec, val):
return self.wrap_dict(path, spec, val)
def wrap_List(self, path, spec, val):
return [self.wrap(spec.type, v) for v in val]
def wrap_TypedList(self, path, spec, val):
val = val if val is not None else spec.defaults
return [self.wrap(path+(str(i),), spec.types[v['type']], v)
for i, v in enumerate(val)]
def get_spec(self, name):
if isinstance(self.spec, Map):
return self.spec.type
return self.spec[name]
def __getattr__(self, name):
return self.wrap(name, self.get_spec(name), self._val.get(name))
# Container emulation
def keys(self):
return sorted(self._val.keys())
def items(self):
return sorted((k, self[k]) for k in self)
def __contains__(self, name):
self.get_spec(name) # raise IndexError if name is not typed
return name in self._val
def __iter__(self):
return iter(sorted(self._val))
def __getitem__(self, name):
return getattr(self, str(name))
class RefWrapper(Wrapper):
"""
Wrapper that handles RefScalars, as with profile objects.
"""
# Turns out (somewhat intentionally) that every spline parameter used on
# the host has a matching parameter in the profile, so this
def __init__(self, val, other, spec=None, path=()):
super(RefWrapper, self).__init__(val, spec, path)
self.other = other
def wrap_dict(self, path, spec, val):
return type(self)(val or {}, self.other, spec, path)
def wrap_refscalar(self, path, spec, val):
spev = self.other
for part in spec.ref.split('.'):
spev = spev[part]
spev *= val if val is not None else spec.default
return spev
class SplineWrapper(Wrapper):
def wrap_spline(self, path, spec, val):
return SplineEval(val if val is not None else spec.default,
spec.interp)
class SplineEval(object):
_mat = np.matrix([[1.,-2, 1, 0], [2,-3, 0, 1],
[1,-1, 0, 0], [-2, 3, 0, 0]])
_deriv = np.matrix(np.diag([3,2,1], 1))
def __init__(self, knots, interp='linear'):
self.knots, self.interp = self.normalize(knots), interp
@staticmethod
def normalize(knots):
if isinstance(knots, (int, float)):
v0, v1 = 0, 0
knots = [(0, knots), (1, knots)]
elif len(knots) % 2 != 0:
raise ValueError("List with odd number of elements given")
elif len(knots) == 2:
v0, v1 = 0, 0
knots = [(0, knots[0]), (1, knots[1])]
else:
p0, v0, p1, v1 = knots[:4]
knots = [(0, p0), (1, p1)] + zip(knots[4::2], knots[5::2])
knots = sorted(knots)
# If stabilizing knots are missing before or after the edges of the
# [0,1] interval, add them. In almost all cases, the precise timing of
# the end knots has little affect on the shape of the curve.
td = 2
if knots[0][0] >= 0:
knots = [(-td, knots[1][1] - (knots[1][0] - (-td)) * v0)] + knots
if knots[-1][0] <= 1:
knots.extend([(1+td, knots[-2][1] + (1+td - knots[-2][0]) * v1)])
knotarray = np.zeros((2, len(knots)))
knotarray.T[:] = knots
return knotarray
def find_knots(self, itime):
idx = np.searchsorted(self.knots[0], itime) - 2
idx = max(0, min(idx, len(self.knots[0]) - 4))
times = self.knots[0][idx:idx+4]
vals = self.knots[1][idx:idx+4]
# Normalize to [0,1]
t = itime - times[1]
times = times - times[1]
scale = 1 / times[2]
t = t * scale
times = times * scale
return times, vals, t, scale
def __call__(self, itime, deriv=0):
times, vals, t, scale = self.find_knots(itime)
m1 = (vals[2] - vals[0]) / (1.0 - times[0])
m2 = (vals[3] - vals[1]) / times[3]
mat = self._mat
if deriv:
mat = mat * (scale * self._deriv) ** deriv
val = [m1, vals[1], m2, vals[2]] * mat * np.array([[t**3, t**2, t, 1]]).T
return val[0,0]
def __imul__(self, other):
self.knots[1] *= other
return self
def _plt(self, name='SplEval', fig=111, show=True):
import matplotlib.pyplot as plt
x = np.linspace(-0.0, 1.0, 500)
r = x[1] - x[0]
plt.figure(fig)
plt.title(name)
plt.plot(x,map(self,x),x,[self(i,1) for i in x],'--',
self.knots[0],self.knots[1],'x')
plt.xlim(0.0, 1.0)
if show:
plt.show()
def wrap_genome(prof, gnm):
# It's not obvious that this is what needs to happen, so we wrap. The
# timing is simplistic, and may get expanded or moved later.
gprof = RefWrapper(prof, SplineWrapper(gnm), toplevels['profile'])
nframes = round(gprof.fps * gprof.duration)
times = np.linspace(0, 1, nframes + 1)
times = times[:-1] + 0.5 * (times[1] - times[0])
return gprof, times

122
cuburn/genome/util.py Normal file
View File

@ -0,0 +1,122 @@
import base64
import numpy as np
from cuburn.code.util import crep
def get(dct, default, *keys):
if len(keys) == 1:
keys = keys[0].split('.')
for k in keys:
if k in dct:
dct = dct[k]
else:
return default
return dct
def flatten(dct, ctx=()):
"""
Given a nested dict, return a flattened dict with dot-separated string
keys. Keys that have dots in them already are treated the same.
>>> flatten({'ab': {'xy.zw': 1}, 4: 5}) == {'ab.xy.zw': 1, '4': 5}
True
"""
for k, v in dct.items():
k = str(k)
if isinstance(v, dict):
for sk, sv in flatten(v, ctx + (k,)):
yield sk, sv
else:
yield '.'.join(ctx + (k,)), v
def unflatten(kvlist):
"""
Given a flattened dict, return a nested dict, where every dot-separated
key is converted into a sub-dict.
>>> (unflatten([('ab.xy.zw', 1), ('4', 5)) ==
... {'ab': {'xy': {'zw': 1}}, '4': 5})
True
"""
def go(d, k, v):
if len(k) == 1:
d[k[0]] = v
else:
go(d.setdefault(k[0], {}), k[1:], v)
out = {}
for k, v in kvlist:
go(out, k.split('.'), v)
return out
def palette_decode(datastrs):
"""
Decode a palette (stored as a list suitable for JSON packing) into a
palette. Internal palette format is simply as a (256,4) array of [0,1]
RGBA floats.
"""
if datastrs[0] != 'rgb8':
raise NotImplementedError
raw = base64.b64decode(''.join(datastrs[1:]))
pal = np.reshape(np.fromstring(raw, np.uint8), (256, 3))
data = np.ones((256, 4), np.float32)
data[:,:3] = pal / 255.0
return data
def palette_encode(data, format='rgb8'):
"""
Encode an internal-format palette to an external representation.
"""
if format != 'rgb8':
raise NotImplementedError
clamp = np.maximum(0, np.minimum(255, np.round(data[:,:3]*255.0)))
enc = base64.b64encode(np.uint8(clamp))
return ['rgb8'] + [enc[i:i+64] for i in range(0, len(enc), 64)]
def json_encode(obj):
"""
Encode an object into JSON notation, formatted to be more readable than
the output of the standard 'json' package for genomes.
This serializer only works on the subset of JSON used in genomes.
"""
result = _js_enc_obj(obj).lstrip()
result = '\n'.join(l.rstrip() for l in result.split('\n'))
return result + '\n'
def _js_enc_obj(obj, indent=0):
isnum = lambda v: isinstance(v, (float, int, np.number))
def wrap(pairs, delims):
do, dc = delims
i = ' ' * indent
out = ''.join([do, ', '.join(pairs), dc])
if '\n' not in out and len(out) + indent < 70:
return out
return ''.join(['\n', i, do, ' ', ('\n'+i+', ').join(pairs),
'\n', i, dc])
if isinstance(obj, dict):
if not obj:
return '{}'
digsort = lambda kv: (int(kv[0]), kv[1]) if kv[0].isdigit() else kv
ks, vs = zip(*sorted(obj.items(), key=digsort))
if ks == ('b', 'g', 'r'):
ks, vs = reversed(ks), reversed(vs)
ks = [crep('%.6g' % k if isnum(k) else str(k)) for k in ks]
vs = [_js_enc_obj(v, indent+2) for v in vs]
return wrap(['%s: %s' % p for p in zip(ks, vs)], '{}')
elif isinstance(obj, list):
vs = [_js_enc_obj(v, indent+2) for v in obj]
if vs and len(vs) % 2 == 0 and isnum(obj[1]):
vs = map(', '.join, zip(vs[::2], vs[1::2]))
return wrap(vs, '[]')
#elif isinstance(obj, SplEval):
#return _js_enc_obj(obj.knotlist, indent)
elif isinstance(obj, basestring):
return crep(obj)
elif isnum(obj):
return '%.6g' % obj
raise TypeError("Don't know how to serialize %s of type %s" %
(obj, type(obj)))

127
cuburn/genome/variations.py Normal file
View File

@ -0,0 +1,127 @@
from spectypes import spline, scalespline
import numpy as np
# Pre-instantiated default splines. Used a *lot*.
s, ss, sz = spline(), scalespline(), scalespline(min=0)
__all__ = ["var_names", "var_params"]
# A map from flam3 variation numbers to variation names. Some variations may
# not be included in this list if they don't yet exist in flam3.
var_names = {}
# A map from variation names to a dict of parameter types, suitable for
# inclusion in the genome schema.
var_params = {}
def var(num, name, **params):
if num is not None:
var_names[num] = name
# Mark as a variation parameter spline. This can be handled in various
# ways by interpolation - usually by copying a value when missing, instead
# of reading the default value.
for k, v in params.items():
params[k] = v._replace(var=True)
params['weight'] = scalespline(0)
var_params[name] = params
# TODO: review all parameter splines, possibly programmatically
var(0, 'linear')
var(1, 'sinusoidal')
var(2, 'spherical')
var(3, 'swirl')
var(4, 'horseshoe')
var(5, 'polar')
var(6, 'handkerchief')
var(7, 'heart')
var(8, 'disc')
var(9, 'spiral')
var(10, 'hyperbolic')
var(11, 'diamond')
var(12, 'ex')
var(13, 'julia')
var(14, 'bent')
var(15, 'waves')
var(16, 'fisheye')
var(17, 'popcorn')
var(18, 'exponential')
var(19, 'power')
var(20, 'cosine')
var(21, 'rings')
var(22, 'fan')
var(23, 'blob', low=ss, high=ss, waves=ss)
var(24, 'pdj', a=s, b=s, c=s, d=s)
var(25, 'fan2', x=s, y=s)
var(26, 'rings2', val=s)
var(27, 'eyefish')
var(28, 'bubble')
var(29, 'cylinder')
var(30, 'perspective', angle=s, dist=s) # TODO: period
var(31, 'noise')
var(32, 'julian', power=ss, dist=ss)
var(33, 'juliascope', power=ss, dist=ss)
var(34, 'blur')
var(35, 'gaussian_blur')
var(36, 'radial_blur', angle=spline(period=4))
var(37, 'pie', slices=spline(6, 1), rotation=s, thickness=spline(0.5, 0, 1))
var(38, 'ngon', sides=spline(5), power=spline(3),
circle=spline(1), corners=spline(2))
var(39, 'curl', c1=spline(1), c2=s) # TODO: not identity?
var(40, 'rectangles', x=s, y=s)
var(41, 'arch')
var(42, 'tangent')
var(43, 'square')
var(44, 'rays')
var(45, 'blade')
var(46, 'secant2')
var(48, 'cross')
var(49, 'disc2', rot=s, twist=s)
var(50, 'super_shape', rnd=s, m=s, n1=ss, n2=spline(1), n3=spline(1), holes=s)
var(51, 'flower', holes=s, petals=s)
var(52, 'conic', holes=s, eccentricity=spline(1))
var(53, 'parabola', height=ss, width=ss)
var(54, 'bent2', x=ss, y=ss)
var(55, 'bipolar', shift=s)
var(56, 'boarders')
var(57, 'butterfly')
var(58, 'cell', size=ss)
var(59, 'cpow', r=ss, i=s, power=ss)
var(60, 'curve', xamp=s, yamp=s, xlength=ss, ylength=ss)
var(61, 'edisc')
var(62, 'elliptic')
var(63, 'escher', beta=spline(period=2*np.pi))
var(64, 'foci')
var(65, 'lazysusan', x=s, y=s, twist=s, space=s, spin=s)
var(66, 'loonie')
var(67, 'pre_blur')
var(68, 'modulus', x=s, y=s)
var(69, 'oscope', separation=spline(1), frequency=scalespline(np.pi),
amplitude=ss, damping=s)
var(70, 'polar2')
var(71, 'popcorn2', x=s, y=s, c=s)
var(72, 'scry')
var(73, 'separation', x=s, xinside=s, y=s, yinside=s)
var(74, 'split', xsize=s, ysize=s)
var(75, 'splits', x=s, y=s)
var(76, 'stripes', space=s, warp=s)
var(77, 'wedge', angle=s, hole=s, count=ss, swirl=s)
var(80, 'whorl', inside=s, outside=s)
var(81, 'waves2', scalex=ss, scaley=ss,
freqx=scalespline(np.pi), freqy=scalespline(np.pi))
var(82, 'exp')
var(83, 'log')
var(84, 'sin')
var(85, 'cos')
var(86, 'tan')
var(87, 'sec')
var(88, 'csc')
var(89, 'cot')
var(90, 'sinh')
var(91, 'cosh')
var(92, 'tanh')
var(93, 'sech')
var(94, 'csch')
var(95, 'coth')
var(97, 'flux', spread=s)
var(98, 'mobius', re_a=s, im_a=s, re_b=s, im_b=s,
re_c=s, im_c=s, re_d=s, im_d=s)

View File

@ -18,6 +18,7 @@ import filters
import output import output
from code import util, mwc, iter, interp, sort from code import util, mwc, iter, interp, sort
from code.util import ClsMod, devlib, filldptrlib, assemble_code, launch from code.util import ClsMod, devlib, filldptrlib, assemble_code, launch
from cuburn.genome.util import palette_decode
RenderedImage = namedtuple('RenderedImage', 'buf idx gpu_time') RenderedImage = namedtuple('RenderedImage', 'buf idx gpu_time')
Dimensions = namedtuple('Dimensions', 'w h aw ah astride') Dimensions = namedtuple('Dimensions', 'w h aw ah astride')
@ -200,7 +201,7 @@ class Renderer(object):
MAX_MODREFS = 20 MAX_MODREFS = 20
_modrefs = [] _modrefs = []
def __init__(self, gnm): def __init__(self, gnm, gprof):
self.packer, self.lib = iter.mkiterlib(gnm) self.packer, self.lib = iter.mkiterlib(gnm)
cubin = util.compile('iter', assemble_code(self.lib)) cubin = util.compile('iter', assemble_code(self.lib))
self.mod = cuda.module_from_buffer(cubin) self.mod = cuda.module_from_buffer(cubin)
@ -210,9 +211,7 @@ class Renderer(object):
self._modrefs.append(self.mod) self._modrefs.append(self.mod)
# TODO: make these customizable # TODO: make these customizable
self.filts = [ filters.Bilateral() self.filts = filters.create(gprof)
, filters.Logscale()
, filters.ColorClip() ]
self.out = output.PILOutput() self.out = output.PILOutput()
class RenderManager(ClsMod): class RenderManager(ClsMod):
@ -236,13 +235,14 @@ class RenderManager(ClsMod):
Note that for now, this is broken! It ignores ``gnm``, and only packs Note that for now, this is broken! It ignores ``gnm``, and only packs
the genome that was used when creating the renderer. the genome that was used when creating the renderer.
""" """
times, knots = rdr.packer.pack(self.pool) times, knots = rdr.packer.pack(gnm, self.pool)
cuda.memcpy_htod_async(self.src_a.d_times, times, self.stream_a) cuda.memcpy_htod_async(self.src_a.d_times, times, self.stream_a)
cuda.memcpy_htod_async(self.src_a.d_knots, knots, self.stream_a) cuda.memcpy_htod_async(self.src_a.d_knots, knots, self.stream_a)
ptimes, pidxs = zip(*gnm.palette_times) palsrc = dict([(v[0], palette_decode(v[1:])) for v in gnm['palette']])
palettes = self.pool.allocate((len(ptimes), 256, 4), f32) ptimes, pvals = zip(*sorted(palsrc.items()))
palettes[:] = [gnm.decoded_palettes[i] for i in pidxs] palettes = self.pool.allocate((len(palsrc), 256, 4), f32)
palettes[:] = pvals
palette_times = self.pool.allocate((self.src_a.max_knots,), f32) palette_times = self.pool.allocate((self.src_a.max_knots,), f32)
palette_times.fill(1e9) palette_times.fill(1e9)
palette_times[:len(ptimes)] = ptimes palette_times[:len(ptimes)] = ptimes
@ -271,7 +271,7 @@ class RenderManager(ClsMod):
256, np.ceil(nts / 256.), 256, np.ceil(nts / 256.),
self.info_a.d_params, self.src_a.d_times, self.src_a.d_knots, self.info_a.d_params, self.src_a.d_times, self.src_a.d_knots,
f32(ts), f32(td / nts), i32(nts)) f32(ts), f32(td / nts), i32(nts))
#self._print_interp_knots(rdr) self._print_interp_knots(rdr)
def _print_interp_knots(self, rdr, tsidx=5): def _print_interp_knots(self, rdr, tsidx=5):
infos = cuda.from_device(self.info_a.d_params, infos = cuda.from_device(self.info_a.d_params,
@ -279,7 +279,7 @@ class RenderManager(ClsMod):
for i, n in zip(infos[-1], rdr.packer.packed): for i, n in zip(infos[-1], rdr.packer.packed):
print '%60s %g' % ('_'.join(n), i) print '%60s %g' % ('_'.join(n), i)
def _iter(self, rdr, gnm, dim, tc): def _iter(self, rdr, gnm, gprof, dim, tc):
tref = rdr.mod.get_surfref('flatpal') tref = rdr.mod.get_surfref('flatpal')
tref.set_array(self.info_a.d_pal_array, 0) tref.set_array(self.info_a.d_pal_array, 0)
@ -291,19 +291,29 @@ class RenderManager(ClsMod):
fill(self.fb.d_points, self.fb._len_d_points / 4, f32(np.nan)) fill(self.fb.d_points, self.fb._len_d_points / 4, f32(np.nan))
nts = self.info_a.ntemporal_samples nts = self.info_a.ntemporal_samples
nsamps = (gnm.spp(tc) * dim.w * dim.h) nsamps = (gprof.spp(tc) * dim.w * dim.h)
nrounds = int(nsamps / (nts * 256. * 256)) + 1 nrounds = int(nsamps / (nts * 256. * 256)) + 1
launch('iter', rdr.mod, self.stream_a, (32, 8, 1), (nts, nrounds),
def launch_iter(n):
if n == 0: return
launch('iter', rdr.mod, self.stream_a, (32, 8, 1), (nts, n),
self.fb.d_front, self.fb.d_side, self.fb.d_front, self.fb.d_side,
self.fb.d_rb, self.fb.d_seeds, self.fb.d_points, self.fb.d_rb, self.fb.d_seeds, self.fb.d_points,
self.info_a.d_params) self.info_a.d_params)
# Split the launch into multiple rounds, possibly (slightly) reducing
# work overlap but avoiding stalls when working on a device with an
# active X session. TODO: characterize performance impact, autodetect
BLOCK_SIZE = 4
for i in range(BLOCK_SIZE-1, nrounds, BLOCK_SIZE):
launch_iter(BLOCK_SIZE)
launch_iter(nrounds%BLOCK_SIZE)
nblocks = int(np.ceil(np.sqrt(dim.ah*dim.astride/256.))) nblocks = int(np.ceil(np.sqrt(dim.ah*dim.astride/256.)))
launch('flush_atom', self.mod, self.stream_a, launch('flush_atom', self.mod, self.stream_a,
256, (nblocks, nblocks), 256, (nblocks, nblocks),
u64(self.fb.d_front), u64(self.fb.d_side), i32(nbins)) u64(self.fb.d_front), u64(self.fb.d_side), i32(nbins))
def queue_frame(self, rdr, gnm, tc, w, h, copy=True): def queue_frame(self, rdr, gnm, gprof, tc, copy=True):
""" """
Queue one frame for rendering. Queue one frame for rendering.
@ -332,9 +342,10 @@ class RenderManager(ClsMod):
""" """
# Note: we synchronize on the previous stream if buffers need to be # Note: we synchronize on the previous stream if buffers need to be
# reallocated, which implicitly also syncs the current stream. # reallocated, which implicitly also syncs the current stream.
dim = self.fb.set_dim(w, h, self.stream_b) dim = self.fb.set_dim(gprof.width, gprof.height, self.stream_b)
td = gnm.adj_frame_width(tc) # TODO: calculate this externally somewhere?
td = gprof.frame_width(tc) / round(gprof.fps * gprof.duration)
ts, te = tc - 0.5 * td, tc + 0.5 * td ts, te = tc - 0.5 * td, tc + 0.5 * td
# The stream interleaving here is nontrivial. # The stream interleaving here is nontrivial.
@ -345,12 +356,12 @@ class RenderManager(ClsMod):
self._interp(rdr, gnm, dim, ts, td) self._interp(rdr, gnm, dim, ts, td)
if self.filt_evt: if self.filt_evt:
self.stream_a.wait_for_event(self.filt_evt) self.stream_a.wait_for_event(self.filt_evt)
self._iter(rdr, gnm, dim, tc) self._iter(rdr, gnm, gprof, dim, tc)
if self.copy_evt: if self.copy_evt:
self.stream_a.wait_for_event(self.copy_evt) self.stream_a.wait_for_event(self.copy_evt)
for filt in rdr.filts: for filt, params in zip(rdr.filts, gprof.filters):
filt.apply(self.fb, gnm, dim, tc, self.stream_a) filt.apply(self.fb, gprof, params, dim, tc, self.stream_a)
rdr.out.convert(self.fb, gnm, dim, self.stream_a) rdr.out.convert(self.fb, gprof, dim, self.stream_a)
self.filt_evt = cuda.Event().record(self.stream_a) self.filt_evt = cuda.Event().record(self.stream_a)
h_out = rdr.out.copy(self.fb, dim, self.pool, self.stream_a) h_out = rdr.out.copy(self.fb, dim, self.pool, self.stream_a)
self.copy_evt = cuda.Event().record(self.stream_a) self.copy_evt = cuda.Event().record(self.stream_a)
@ -359,13 +370,13 @@ class RenderManager(ClsMod):
self.stream_a, self.stream_b = self.stream_b, self.stream_a self.stream_a, self.stream_b = self.stream_b, self.stream_a
return self.copy_evt, h_out return self.copy_evt, h_out
def render(self, gnm, times, w, h): def render(self, gnm, gprof, times):
""" """
A port of the old rendering function, retained for backwards A port of the old rendering function, retained for backwards
compatibility. Some of this will be pulled into as-yet-undecided compatibility. Some of this will be pulled into as-yet-undecided
methods for more DRY. methods for more DRY.
""" """
rdr = Renderer(gnm) rdr = Renderer(gnm, gprof)
last_evt = cuda.Event().record(self.stream_a) last_evt = cuda.Event().record(self.stream_a)
last_idx = None last_idx = None
def wait(): # Times like these where you wish for a macro def wait(): # Times like these where you wish for a macro
@ -374,7 +385,7 @@ class RenderManager(ClsMod):
gpu_time = last_evt.time_since(two_evts_ago) gpu_time = last_evt.time_since(two_evts_ago)
return RenderedImage(last_buf, last_idx, gpu_time) return RenderedImage(last_buf, last_idx, gpu_time)
for idx, tc in times: for idx, tc in times:
evt, h_buf = self.queue_frame(rdr, gnm, tc, w, h, last_idx is None) evt, h_buf = self.queue_frame(rdr, gnm, gprof, tc, last_idx is None)
if last_idx: if last_idx:
yield wait() yield wait()
two_evts_ago, last_evt = last_evt, evt two_evts_ago, last_evt = last_evt, evt

20
main.py
View File

@ -22,13 +22,14 @@ from itertools import ifilter
import numpy as np import numpy as np
import pycuda.driver as cuda import pycuda.driver as cuda
from cuburn import genome, render, filters, output from cuburn import render, filters, output
from cuburn.genome import convert, use
profiles = { profiles = {
'1080p': dict(fps=24, width=1920, height=1080, quality=3000, skip=0), '1080p': dict(width=1920, height=1080),
'720p': dict(fps=24, width=1280, height=720, quality=2500, skip=0), '720p': dict(width=1280, height=720),
'540p': dict(fps=24, width=960, height=540, quality=2500, skip=0), '540p': dict(width=960, height=540),
'preview': dict(fps=24, width=640, height=360, quality=800, skip=1) 'preview': dict(width=640, height=360, quality=800, skip=1)
} }
def save(out): def save(out):
@ -41,15 +42,14 @@ def main(args, prof):
gnm_str = args.flame.read() gnm_str = args.flame.read()
if '<' in gnm_str[:10]: if '<' in gnm_str[:10]:
flames = genome.XMLGenomeParser.parse(gnm_str) flames = convert.XMLGenomeParser.parse(gnm_str)
if len(flames) != 1: if len(flames) != 1:
warnings.warn('%d flames in file, only using one.' % len(flames)) warnings.warn('%d flames in file, only using one.' % len(flames))
gnm = genome.convert_flame(flames[0]) gnm = convert.convert_flame(flames[0])
else: else:
gnm = json.loads(gnm_str) gnm = json.loads(gnm_str)
gnm = genome.Genome(gnm)
err, times = gnm.set_profile(prof)
gprof, times = use.wrap_genome(prof, gnm)
rmgr = render.RenderManager() rmgr = render.RenderManager()
basename = os.path.basename(args.flame.name).rsplit('.', 1)[0] + '_' basename = os.path.basename(args.flame.name).rsplit('.', 1)[0] + '_'
@ -71,7 +71,7 @@ def main(args, prof):
if not os.path.isfile(f[0]) or m > os.path.getmtime(f[0])) if not os.path.isfile(f[0]) or m > os.path.getmtime(f[0]))
w, h = prof['width'], prof['height'] w, h = prof['width'], prof['height']
gen = rmgr.render(gnm, frames, w, h) gen = rmgr.render(gnm, gprof, frames)
if not args.gfx: if not args.gfx:
for out in gen: for out in gen: