1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
# -*- coding: utf-8 -*-
# Copyright (c) Vispy Development Team. All Rights Reserved.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
from __future__ import division
import logging
from weakref import WeakKeyDictionary
from ...gloo import Program
from ...gloo.preprocessor import preprocess
from ...util import logger
from ...util.event import EventEmitter
from .function import MainFunction
from .variable import Variable
from .compiler import Compiler
class ModularProgram(Program):
"""
Shader program using Function instances as basis for its shaders.
Automatically rebuilds program when functions have changed and uploads
program variables.
"""
def __init__(self, vcode='', fcode='', gcode=None):
Program.__init__(self)
self.changed = EventEmitter(source=self, type='program_change')
# Cache state of Variables so we know which ones require update
self._variable_cache = WeakKeyDictionary()
# List of settable variables to be checked for value changes
self._variables = []
self._vert = MainFunction('vertex', '')
self._frag = MainFunction('fragment', '')
self._vert._dependents[self] = None
self._frag._dependents[self] = None
self._geom = None
self.vert = vcode
self.frag = fcode
self.geom = gcode
@property
def vert(self):
return self._vert
@vert.setter
def vert(self, vcode):
vcode = preprocess(vcode)
self._vert.code = vcode
self._need_build = True
self.changed(code_changed=True, value_changed=False)
@property
def frag(self):
return self._frag
@frag.setter
def frag(self, fcode):
fcode = preprocess(fcode)
self._frag.code = fcode
self._need_build = True
self.changed(code_changed=True, value_changed=False)
@property
def geom(self):
return self._geom
@geom.setter
def geom(self, gcode):
if gcode is None:
self._geom = None
return
gcode = preprocess(gcode)
if self._geom is None:
self._geom = MainFunction('geometry', '')
self._geom._dependents[self] = None
self._geom.code = gcode
self._need_build = True
self.changed(code_changed=True, value_changed=False)
def _dep_changed(self, dep, code_changed=False, value_changed=False):
if code_changed and logger.level <= logging.DEBUG:
logger.debug("ModularProgram changed: %s source=%s, values=%s",
self, code_changed, value_changed)
import traceback
traceback.print_stack()
if code_changed:
self._need_build = True
self.changed(code_changed=code_changed,
value_changed=value_changed)
def draw(self, *args, **kwargs):
self.build_if_needed()
self.update_variables()
Program.draw(self, *args, **kwargs)
def build_if_needed(self):
""" Reset shader source if necesssary.
"""
if self._need_build:
self._build()
# after recompile, we need to upload all variables again
# (some variables may have changed name)
self._variable_cache.clear()
# Collect a list of all settable variables
settable_vars = 'attribute', 'uniform', 'in'
deps = [d for d in self.vert.dependencies() if (
isinstance(d, Variable) and d.vtype in settable_vars)]
deps += [d for d in self.frag.dependencies() if (
isinstance(d, Variable) and d.vtype == 'uniform')]
if self.geom is not None:
deps += [d for d in self.geom.dependencies() if (
isinstance(d, Variable) and d.vtype == 'uniform')]
self._variables = deps
self._need_build = False
def _build(self):
logger.debug("Rebuild ModularProgram: %s", self)
shaders = {'vert': self.vert, 'frag': self.frag}
if self.geom is not None:
shaders['geom'] = self.geom
self.compiler = Compiler(**shaders)
code = self.compiler.compile()
# Update shader code, but don't let the program update variables yet
code['update_variables'] = False
self.set_shaders(**code)
logger.debug('==== Vertex Shader ====\n\n%s\n', code['vert'])
if 'geom' in code:
logger.debug('==== Geometry shader ====\n\n%s\n', code['geom'])
logger.debug('==== Fragment shader ====\n\n%s\n', code['frag'])
def update_variables(self):
# Set any variables that have a new value
logger.debug("Apply variables:")
for dep in sorted(self._variables, key=lambda d: self.compiler[d]):
name = self.compiler[dep]
state_id = dep.state_id
if self._variable_cache.get(dep, None) != state_id:
self[name] = dep.value
self._variable_cache[dep] = state_id
logger.debug(" %s = %s **", name, dep.value)
else:
logger.debug(" %s = %s", name, dep.value)
# Process any pending variables and discard anything else that is
# not active in the program (otherwise we get lots of warnings).
self._process_pending_variables()
logger.debug("Discarding unused variables before draw: %s" %
self._pending_variables.keys())
self._pending_variables = {}
|