mirror of
https://github.com/stevenrobertson/cuburn.git
synced 2025-02-05 11:40:04 -05:00
Add xforms and variations.
This commit is contained in:
parent
383c0f1f9a
commit
860d7b2fad
@ -1,4 +1,6 @@
|
||||
import sys
|
||||
import math
|
||||
import re
|
||||
from ctypes import *
|
||||
from cStringIO import StringIO
|
||||
import numpy as np
|
||||
@ -9,12 +11,45 @@ from fr0stlib.pyflam3.constants import *
|
||||
|
||||
from cuburn.cuda import LaunchContext
|
||||
from cuburn.device_code import *
|
||||
from cuburn.variations import Variations
|
||||
|
||||
Point = lambda x, y: np.array([x, y], dtype=np.double)
|
||||
|
||||
class Genome(pyflam3.Genome):
|
||||
pass
|
||||
|
||||
class XForm(object):
|
||||
"""
|
||||
A Python structure (*not* a ctypes wrapper) storing an xform. There are
|
||||
a few differences between the meaning of properties on this object and
|
||||
those of the C version; they are noted below.
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
read_affine = lambda c: [[c[0], c[1]], [c[2], c[3]], [c[4], c[5]]]
|
||||
self.coefs = read_affine(map(float, kwargs.pop('coefs').split()))
|
||||
if 'post' in kwargs:
|
||||
self.post = read_affine(map(float, kwargs.pop('post').split()))
|
||||
# TODO: more verification, detection of unknown variables, etc
|
||||
for k, v in kwargs.items():
|
||||
setattr(self, k, float(v))
|
||||
|
||||
# ctypes was being a pain. just parse the string.
|
||||
@classmethod
|
||||
def parse(cls, cp):
|
||||
flame_str = flam3_print_to_string(byref(cp))
|
||||
xforms = []
|
||||
for line in flame_str.split('\n'):
|
||||
if not line.strip().startswith('<xform'):
|
||||
continue
|
||||
props = dict(re.findall(r'(\w*)="([^"]*)"', line))
|
||||
xforms.append(cls(**props))
|
||||
# Set cumulative xform weight
|
||||
xforms[0].cweight = xforms[0].weight
|
||||
for i in range(1, len(xforms)):
|
||||
xforms[i].cweight = xforms[i].weight + xforms[i-1].cweight
|
||||
xforms[-1].cweight = 1.0
|
||||
return xforms
|
||||
|
||||
class _Frame(pyflam3.Frame):
|
||||
"""
|
||||
ctypes flam3_frame object used for genome interpolation and
|
||||
@ -56,7 +91,7 @@ class Frame(object):
|
||||
raise NotImplementedError(
|
||||
"Distribution of a CP across multiple CTAs not yet done")
|
||||
|
||||
# TODO: isn't this leaking ctypes xforms all over the place?
|
||||
# TODO: isn't this leaking xforms from C all over the place?
|
||||
stream = StringIO()
|
||||
cp_list = []
|
||||
|
||||
@ -70,6 +105,7 @@ class Frame(object):
|
||||
cp.camera = Camera(self._frame, cp, filters)
|
||||
cp.nsamples = (cp.camera.sample_density *
|
||||
center.width * center.height) / ncps
|
||||
cp.xforms = XForm.parse(cp)
|
||||
|
||||
print "Expected writes:", (
|
||||
cp.camera.sample_density * center.width * center.height)
|
||||
@ -170,6 +206,8 @@ class Filters(object):
|
||||
# TODO: density estimation
|
||||
self.gutter = (spa_width - self.oversample) / 2
|
||||
|
||||
|
||||
|
||||
class Features(object):
|
||||
"""
|
||||
Determine features and constants required to render a particular set of
|
||||
@ -186,6 +224,14 @@ class Features(object):
|
||||
self.non_box_temporal_filter = genomes[0].temporal_filter_type
|
||||
self.palette_mode = genomes[0].palette_mode and "linear" or "nearest"
|
||||
|
||||
xforms = [XForm.parse(cp) for cp in genomes]
|
||||
assert len(xforms[0]) == len(xforms[-1]), ("genomes must have same "
|
||||
"number of xforms! (try running through flam3-genome first)")
|
||||
self.xforms = [XFormFeatures([x[i] for x in xforms], i)
|
||||
for i in range(len(xforms[0]))]
|
||||
if any(lambda cp: cp.final_xform_enable):
|
||||
raise NotImplementedError("Final xform")
|
||||
|
||||
# Histogram (and log-density copy) width and height
|
||||
self.hist_width = flt.oversample * genomes[0].width + 2 * flt.gutter
|
||||
self.hist_height = flt.oversample * genomes[0].height + 2 * flt.gutter
|
||||
@ -195,6 +241,14 @@ class Features(object):
|
||||
# particularly the histogram bucket size, which may be split soon
|
||||
self.hist_stride = 8 * int(math.ceil(self.hist_width / 8.0))
|
||||
|
||||
class XFormFeatures(object):
|
||||
def __init__(self, xforms, xform_id):
|
||||
self.id = xform_id
|
||||
any = lambda l: bool(filter(None, map(l, xforms)))
|
||||
self.has_post = any(lambda xf: getattr(xf, 'post', None))
|
||||
self.vars = set([n for x in xforms for n in Variations.names
|
||||
if getattr(x, n, None)])
|
||||
|
||||
class Camera(object):
|
||||
"""Viewport and exposure."""
|
||||
def __init__(self, frame, cp, filters):
|
||||
|
133
cuburn/variations.py
Normal file
133
cuburn/variations.py
Normal file
@ -0,0 +1,133 @@
|
||||
from cuburn.ptx import PTXFragment, ptx_func
|
||||
|
||||
class Variations(PTXFragment):
|
||||
"""
|
||||
You know it.
|
||||
"""
|
||||
# TODO: precalc
|
||||
|
||||
shortname = "variations"
|
||||
|
||||
def __init__(self):
|
||||
self.xform_idx = None
|
||||
|
||||
names = [ "linear", "sinusoidal", "spherical", "swirl", "horseshoe",
|
||||
"polar", "handkerchief", "heart", "disc", "spiral", "hyperbolic",
|
||||
"diamond", "ex", "julia", "bent", "waves", "fisheye", "popcorn",
|
||||
"exponential", "power", "cosine", "rings", "fan", "blob", "pdj",
|
||||
"fan2", "rings2", "eyefish", "bubble", "cylinder", "perspective",
|
||||
"noise", "julian", "juliascope", "blur", "gaussian_blur",
|
||||
"radial_blur", "pie", "ngon", "curl", "rectangles", "arch", "tangent",
|
||||
"square", "rays", "blade", "secant2", "twintrian", "cross", "disc2",
|
||||
"super_shape", "flower", "conic", "parabola", "bent2", "bipolar",
|
||||
"boarders", "butterfly", "cell", "cpow", "curve", "edisc", "elliptic",
|
||||
"escher", "foci", "lazysusan", "loonie", "pre_blur", "modulus",
|
||||
"oscilloscope", "polar2", "popcorn2", "scry", "separation", "split",
|
||||
"splits", "stripes", "wedge", "wedge_julia", "wedge_sph", "whorl",
|
||||
"waves2", "exp", "log", "sin", "cos", "tan", "sec", "csc", "cot",
|
||||
"sinh", "cosh", "tanh", "sech", "csch", "coth", "auger", "flux", ]
|
||||
|
||||
@ptx_func
|
||||
def xfg(self, dst, expr):
|
||||
"""
|
||||
Convenience wrapper around cp.get which loads the given property from
|
||||
the current CP and XF.
|
||||
"""
|
||||
# xform_idx is set by apply_xform on the current instance, but the
|
||||
# expression will be evaluated using each CP in stream packing.
|
||||
cp.get(cpA, dst, 'cp.xforms[%d].%s' % (self.xform_idx, expr))
|
||||
|
||||
@ptx_func
|
||||
def xfg_v2(self, dst1, expr1, dst2, expr2):
|
||||
cp.get_v2(cpA, dst1, 'cp.xforms[%d].%s' % (self.xform_idx, expr1),
|
||||
dst2, 'cp.xforms[%d].%s' % (self.xform_idx, expr2))
|
||||
|
||||
@ptx_func
|
||||
def xfg_v4(self, d1, e1, d2, e2, d3, e3, d4, e4):
|
||||
cp.get_v4(cpA, d1, 'cp.xforms[%d].%s' % (self.xform_idx, e1),
|
||||
d2, 'cp.xforms[%d].%s' % (self.xform_idx, e2),
|
||||
d3, 'cp.xforms[%d].%s' % (self.xform_idx, e3),
|
||||
d4, 'cp.xforms[%d].%s' % (self.xform_idx, e4))
|
||||
|
||||
@ptx_func
|
||||
def apply_xform(self, xo, yo, co, xi, yi, ci, xform_idx):
|
||||
"""
|
||||
Apply a transform.
|
||||
|
||||
This function makes a copy of the input variables, so it's safe to use
|
||||
the same registers for input and output.
|
||||
"""
|
||||
with block("Apply xform %d" % xform_idx):
|
||||
self.xform_idx = xform_idx
|
||||
|
||||
with block('Modify color'):
|
||||
reg.f32('c_speed c_new')
|
||||
cp.get_v2(cpA,
|
||||
c_speed, '(1.0 - cp.xforms[%d].color_speed)' % xform_idx,
|
||||
c_new, 'cp.xforms[%d].color * cp.xforms[%d].color_speed' %
|
||||
(xform_idx, xform_idx))
|
||||
op.fma.rn.ftz.f32(co, ci, c_speed, c_new)
|
||||
|
||||
reg.f32('xt yt')
|
||||
with block("Do affine transformation"):
|
||||
# TODO: verify that this is the best performance (register
|
||||
# usage vs number of loads)
|
||||
reg.f32('c00 c10 c20 c01 c11 c21')
|
||||
self.xfg_v4(c00, 'coefs[0][0]', c01, 'coefs[0][1]',
|
||||
c20, 'coefs[2][0]', c21, 'coefs[2][1]')
|
||||
op.fma.rn.ftz.f32(xt, c00, xi, c20)
|
||||
op.fma.rn.ftz.f32(yt, c01, xi, c21)
|
||||
self.xfg_v2(c10, 'coefs[1][0]', c11, 'coefs[1][1]')
|
||||
op.fma.rn.ftz.f32(xt, c10, yi, xt)
|
||||
op.fma.rn.ftz.f32(yt, c11, yi, yt)
|
||||
|
||||
op.mov.f32(xo, '0.0')
|
||||
op.mov.f32(yo, '0.0')
|
||||
|
||||
for var_name in sorted(features.xforms[xform_idx].vars):
|
||||
func = getattr(self, var_name, None)
|
||||
if not func:
|
||||
raise NotImplementedError(
|
||||
"Haven't implemented %s yet" % var_name)
|
||||
with block('%s variation' % var_name):
|
||||
reg.f32('wgt')
|
||||
self.xfg(wgt, var_name)
|
||||
func(xo, yo, xt, yt, wgt)
|
||||
|
||||
if features.xforms[xform_idx].has_post:
|
||||
with block("Affine post-transformation"):
|
||||
op.mov.f32(xt, xo)
|
||||
op.mov.f32(yt, yo)
|
||||
reg.f32('c00 c10 c20 c01 c11 c21')
|
||||
self.xfg_v4(c00, 'post[0][0]', c01, 'post[0][1]',
|
||||
c20, 'post[2][0]', c21, 'post[2][1]')
|
||||
op.fma.rn.ftz.f32(xo, c00, xt, c20)
|
||||
op.fma.rn.ftz.f32(yo, c01, xt, c21)
|
||||
self.xfg_v2(c10, 'post[1][0]', c11, 'post[1][1]')
|
||||
op.fma.rn.ftz.f32(xo, c10, yt, xo)
|
||||
op.fma.rn.ftz.f32(yo, c11, yt, yo)
|
||||
|
||||
@ptx_func
|
||||
def linear(self, xo, yo, xi, yi, wgt):
|
||||
op.fma.rn.ftz.f32(xo, xi, wgt, xo)
|
||||
op.fma.rn.ftz.f32(yo, yi, wgt, xo)
|
||||
|
||||
@ptx_func
|
||||
def sinusoidal(self, xo, yo, xi, yi, wgt):
|
||||
reg.f32('sinval')
|
||||
op.sin.approx.ftz.f32(sinval, xi)
|
||||
op.fma.rn.ftz.f32(xo, sinval, wgt, xo)
|
||||
op.sin.approx.ftz.f32(sinval, yi)
|
||||
op.fma.rn.ftz.f32(yo, sinval, wgt, yo)
|
||||
|
||||
@ptx_func
|
||||
def spherical(self, xo, yo, xi, yi, wgt):
|
||||
reg.f32('r2')
|
||||
op.fma.rn.ftz.f32(r2, xi, xi, '1e-9')
|
||||
op.fma.rn.ftz.f32(r2, yi, yi, r2)
|
||||
op.rcp.approx.f32(r2, r2)
|
||||
op.mul.rn.ftz.f32(r2, r2, wgt)
|
||||
op.fma.rn.ftz.f32(xo, xi, r2, xo)
|
||||
op.fma.rn.ftz.f32(yo, yi, r2, yo)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user