class LayerWidget(Widget): _T_RENDER_LAYER = 1 resolution = attributes.VectorAttribute(2) size = attributes.VectorAttribute(2) position = attributes.VectorAttribute(4) def __init__(self, position, size, resolution): super().__init__() self.resolution = resolution self.size = size self.position = position self._items = [] self._default_configuration = { 'color_channels': 4, 'resolution': self.resolution, 'type': 'static', 'rendered': False, } self._layers = [] self._tasks = [] def is_ready(self): pass def forward(self): pass def render(self): pass def append(self, configuration): self._item.append(self._layer(configuration)) self._task((self._T_RENDER_LAYER, len())) def insert(self, i, configuration): self._items.insert(i, self._layer(configuration)) self._task((self._T_RENDER_LAYER, i)) def _layer(self, configuration): config = copy(self._default_configuration) config.update(configuration) si = _LayerWidgetLayer(config) return si def _task(self, task): if task in self._tasks: self._tasks.remove(task) self._tasks.append(task)
class FragmentGraph(DomainGraph): DEFAULT_FRAGMENT_KERNEL = """ vec4 fragment_kernel(vec2 txcoord) { return tovec4($D.domain(txcoord)); } """ FRAGMENT_KERNELS = { 'expr': lambda e: "vec4 fragment_kernel(vec2 txcoord) { \n\treturn " + e + "; \n}", } cs = attributes.VectorAttribute(4) # main domain name fragment_kernel = attributes.CastedAttribute(str) def __init__(self, domain=None, cs=None, fragment_kernel=None): super().__init__(domain) self.cs = cs if 'domain' in self.domains: self.fragment_kernel = fragment_kernel or self.DEFAULT_FRAGMENT_KERNEL elif fragment_kernel is None: raise ValueError( 'argument fragment_kernel cannot be None without default domain' ) else: self.fragment_kernel = fragment_kernel self.mesh = None self.program = None self._fkernel_template = None def init(self): self._build_kernel() self.program = self._build_shader() self.mesh = StridedVertexMesh( mesh3d_rectangle(), GL_TRIANGLES, attribute_locations=self.program.attributes) self.on_tick.once(self.sync_gpu) @cs.on_change def _properties_changed(self, *e): self.on_tick.once(self.sync_gpu) def _build_kernel(self): context = self.get_domain_glsl_substitutions() kernel = Template(self.fragment_kernel, context) kernel.context.update({'size': 'gl_PointSize', 'color': 'v_col'}) self._fkernel_template = kernel def _build_shader(self): prg = DomainProgram(vrt_file='fragmentgraph.vrt.glsl', frg_file='fragmentgraph.frg.glsl') prg.declare_uniform('camera', components.camera.Camera2D.DTYPE, variable='camera') prg.declare_uniform('plot', plotter2d.Plotter2d.UBO_DTYPE, variable='plot') prg.prepare_domains(self.domains) prg.get_shader(GL_FRAGMENT_SHADER).substitutions.update({ 'fragment_kernel': self._fkernel_template.render(), }) prg.link() prg.uniform_block_binding( 'plot', G_.CONTEXT.buffer_base('gpupy.plot.plotter2d')) prg.uniform_block_binding('camera', G_.CONTEXT.buffer_base('gpupy.gl.camera')) self._src = prg.get_shader(GL_FRAGMENT_SHADER)._precompiled_source return prg def sync_gpu(self): cs = self.cs.values self.program.uniform('cs', cs) self.program.uniform('cs_size', (np.abs(cs[1] - cs[0]), np.abs(cs[3] - cs[2]))) def render(self): # enable all domains domain.enable_domains(self.program, self.domains.items()) # blending glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) # draw mesh self.program.use() self.mesh.draw() self.program.unuse() @fragment_kernel.transformation def set_color_kernel(self, ckrn): kernels = self.__class__.FRAGMENT_KERNELS ckrn_args, ckrn_kwargs = (), {} # we have ('kernel_name', ***) # i ('kernel_name', {kwargs}) --> kernel(**kwargs) # ii ('kernel_name', [args]) --> kernel(*args) # iii ('kernel_name', (args)) --> kernel(*args) # iv ('kernel_name', (args), {kwargs}) --> kernel(*args, **kwargs) # v ('kernel_name', x, y, z, ...) --> kernel(x, y, z, ...) if isinstance(ckrn, tuple) \ or isinstance(ckrn, list): if len(ckrn) == 2: # i - iii if isinstance(ckrn[1], dict): ckrn_kwargs = ckrn[1] elif isinstance(ckrn[1], tuple) \ or isinstance(ckrn[1], list): ckrn_args = ckrn[1] else: ckrn_args = (ckrn[1], ) elif len(ckrn) == 3 \ and isinstance(ckrn[2], dict) \ and (isinstance(ckrn[1], tuple) or isinstance(ckrn[1], list)): ckrn_args, ckrn_kwargs = ckrn[1:3] # iv elif len(ckrn) > 1: ckrn_args = ckrn[1:] # v ckrn = ckrn[0] # if ckrn is registred, use it. if ckrn in kernels: ckrn = kernels[ckrn] # is it callable? if hasattr(ckrn, '__call__'): ckrn = ckrn(*ckrn_args, **ckrn_kwargs) elif len(ckrn_args) or len(ckrn_kwargs): msg = 'kernel {} is not callable, but kernel args (), kwargs () are defined.' raise RuntimeError(msg.format(ckrn, ckrn_args, ckrn_kwargs)) return ckrn
class Container(Widget): """ like a div container, the Container widget has a position, size, margin, border and padding. position \ size x +---------------------------- ... + | margin s | i | +--------- z | | border e | | +---------- | | | padding y | | | +----------- | | | | content box | ... +---------------------------- ... + ```python container = Container((100, 100), margin=(10, 10), padding=(10, 10), border=(10, 10, 10, 10), border_color=(1, 0, 0, 1)) container.widget = SomeWidget(size=container.content_size, position=container_position) container.tick() container.render() ``` """ size = attributes.VectorAttribute(2) position = attributes.VectorAttribute(4) padding = attributes.VectorAttribute(4) margin = attributes.VectorAttribute(4) border = attributes.VectorAttribute(4) border_color = attributes.VectorAttribute(4) # transformed properties content_position = attributes.ComputedAttribute( position, border, margin, padding, transformation=content_position, descriptor=attributes.VectorAttribute(4)) content_size = attributes.ComputedAttribute( size, border, margin, padding, transformation=content_size, descriptor=attributes.VectorAttribute(2)) border_size = attributes.ComputedAttribute( size, border, margin, transformation=border_size, descriptor=attributes.VectorAttribute(2)) def __init__(self, widget=None, size=(0, 0), position=(0, 0, 0, 0), margin=(0, 0, 0, 0), padding=(0, 0, 0, 0), border=(1, 1, 1, 1), border_color=(0, 0, 0, 1)): super().__init__() self.size = size self.position = position self.padding = padding self.border = border self.border_color = border_color self.margin = margin self._widget = None self.set_widget(widget) self.to_gpu = True self._init_borders() def set_widget(self, widget): self._widget = widget @border_color.on_change @content_size.on_change @border_size.on_change def _flag_create_mesh(self, *a): self.on_tick.once(self.sync_gpu) def sync_gpu(self): self._create_mesh() def _init_borders(self): self.border_program = BorderProgram() self.border_mesh = StridedVertexMesh( np.empty(0, dtype=np.dtype([('vertex', np.float32, 4), ('color', np.float32, 4)])), GL_TRIANGLES, attribute_locations=self.border_program.attributes) def _create_mesh(self): b, c, s = self.border, self.border_color, self.border_size p = self.position + (b[3] + self.margin[3], b[0] + self.margin[0], 0, 0) crn = (p[1], p[0] + s[0], p[1] + s[1], p[0]) # corner coords vcnt = sum(6 * (int(l > 0) + int(l > 0 and b[(i + 1) % 4] > 0)) for i, l in enumerate(b)) v = np.zeros(vcnt, self.border_mesh.buffer.dtype) i = 0 if b[3] and b[0]: # left upper corner v[i:i + 6] = [((crn[3] - b[3], crn[0] - b[0], 0, 1), c), ((crn[3], crn[0] - b[0], 0, 1), c), ((crn[3], crn[0], 0, 1), c), ((crn[3], crn[0], 0, 1), c), ((crn[3] - b[3], crn[0], 0, 1), c), ((crn[3] - b[3], crn[0] - b[0], 0, 1), c)] i += 6 if b[0]: # top v[i:i + 6] = [((crn[3] - b[3], crn[0] - b[0], 0, 1), c), ((crn[1], crn[0] - b[0], 0, 1), c), ((crn[1], crn[0], 0, 1), c), ((crn[1], crn[0], 0, 1), c), ((crn[3] - b[3], crn[0] - b[0], 0, 1), c), ((crn[3] - b[3], crn[0], 0, 1), c)] i += 6 if b[0] and b[1]: # right upper corner v[i:i + 6] = [((crn[1] + b[1], crn[0] - b[0], 0, 1), c), ((crn[1], crn[0] - b[0], 0, 1), c), ((crn[1], crn[0], 0, 1), c), ((crn[1], crn[0], 0, 1), c), ((crn[1] + b[1], crn[0], 0, 1), c), ((crn[1] + b[1], crn[0] - b[0], 0, 1), c)] i += 6 if b[1]: # right v[i:i + 6] = [((crn[1] + b[1], crn[0], 0, 1), c), ((crn[1], crn[0], 0, 1), c), ((crn[1], crn[2], 0, 1), c), ((crn[1], crn[2], 0, 1), c), ((crn[1] + b[1], crn[2], 0, 1), c), ((crn[1] + b[1], crn[0], 0, 1), c)] i += 6 if b[1] and b[2]: # right lower corner v[i:i + 6] = [((crn[1] + b[1], crn[2] + b[2], 0, 1), c), ((crn[1], crn[2] + b[2], 0, 1), c), ((crn[1], crn[2], 0, 1), c), ((crn[1], crn[2], 0, 1), c), ((crn[1] + b[1], crn[2], 0, 1), c), ((crn[1] + b[1], crn[2] + b[2], 0, 1), c)] i += 6 if b[2]: # bottom v[i:i + 6] = [((crn[3] - b[3], crn[2] + b[2], 0, 1), c), ((crn[1], crn[2] + b[2], 0, 1), c), ((crn[1], crn[2], 0, 1), c), ((crn[1], crn[2], 0, 1), c), ((crn[3] - b[3], crn[2] + b[2], 0, 1), c), ((crn[3] - b[3], crn[2], 0, 1), c)] i += 6 if b[2] and b[3]: # left lower corner v[i:i + 6] = [((crn[3] - b[3], crn[2] + b[2], 0, 1), c), ((crn[3], crn[2] + b[2], 0, 1), c), ((crn[3], crn[2], 0, 1), c), ((crn[3], crn[2], 0, 1), c), ((crn[3] - b[3], crn[2], 0, 1), c), ((crn[3] - b[3], crn[2] + b[2], 0, 1), c)] i += 6 if b[3]: # left v[i:i + 6] = [((crn[3] - b[3], crn[0], 0, 1), c), ((crn[3], crn[0], 0, 1), c), ((crn[3], crn[2], 0, 1), c), ((crn[3], crn[2], 0, 1), c), ((crn[3] - b[3], crn[2], 0, 1), c), ((crn[3] - b[3], crn[0], 0, 1), c)] i += 6 self.border_mesh.buffer.set(v) def _render(self): self.border_program.use() self.border_mesh.draw() self.border_program.unuse()
class TextureDomain(_GlslDeclarationDomain): """ Allows to use gpupy.gl.texture's as plot domains. """ # -- GLSL templates _GLSL_TEMPLATE_DOMAIN = """ {ret_type:} {fname:}({arg_type:} x) {{ return texture(tx_{upref:}, x).{rgba:}; }} """ _GLSL_TEMPLATE_DECRL = "uniform sampler{d:}D tx_{upref:};" WHEELS = { 'complex': os.path.join(os.path.dirname(__file__), 'graph', 'res', 'cwheel_cmplx.jpg'), 'complex2': os.path.join(os.path.dirname(__file__), 'graph', 'res', 'cwheel_cmplx2.png'), 'complex3': os.path.join(os.path.dirname(__file__), 'graph', 'res', 'cwheel_cmplx3.png'), 'keksnicoh': os.path.join(os.path.dirname(__file__), 'graph', 'res', 'keksnicoh.png'), 'homer': os.path.join(os.path.dirname(__file__), 'graph', 'res', 'homer.png'), } DEFAULT_WHEEL = 'complex' # -- attributes texture = attributes.GlTexture() cs = attributes.VectorAttribute(4, (0, 1, 0, 1)) @classmethod def load_image(cls, path, smooth=True, periodic=False): txt = imread(path, mode="RGB") d = cls(Texture2D.to_device(txt)) d.periodic(periodic) d.smooth(smooth) return d @classmethod def colorwheel(cls, wheel=DEFAULT_WHEEL): if wheel not in cls.WHEELS: err = 'unkown wheel "{}". Available wheels: {}' raise ValueError(err.format(wheel, ','.join(cls.WHEELS))) d = cls.load_image(cls.WHEELS[wheel]) d.periodic(False) d.smooth(True) return d @classmethod def to_device_1d(cls, data, smooth=True, periodic=False): d = cls(Texture1D.to_device(data)) d.periodic(periodic) d.smooth(smooth) return d @classmethod def to_device_2d(cls, data, smooth=True, periodic=False): if data.dtype == np.float64: data = data.astype(np.float32) if data.dtype == np.complex128: data = data.astype(np.complex64) if data.dtype == np.complex64: data = np.stack((data.real, data.imag), -1) d = cls(Texture2D.to_device(data)) d.periodic(periodic) d.smooth(smooth) return d @classmethod def to_device_3d(cls, data, smooth=True, periodic=False): d = cls(Texture3D.to_device(data)) d.periodic(periodic) d.smooth(smooth) return d # -- def __init__(self, texture, cs=None): self.texture = texture if cs is not None: self.cs = cs def periodic(self, periodic=False): if periodic: self.texture.tex_parameterf(GL_TEXTURE_WRAP_S, GL_REPEAT) self.texture.tex_parameterf(GL_TEXTURE_WRAP_T, GL_REPEAT) else: self.texture.tex_parameterf(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) self.texture.tex_parameterf(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) def smooth(self, smooth=True): if smooth: self.texture.interpolate_linear() else: self.texture.interpolate_nearest() # -- domain API def enable(self, program, upref, texunit=0): self.texture.activate(texunit) program.uniform('tx_{}'.format(upref), self.texture) return texunit + 1 def glsl_identifier(self, pref): t = self.texture ret_type = 'float' if t.channels == 1 else 'vec{}'.format(t.channels) return [(None, 'txdmn_{}'.format(pref), 1, ret_type)] # -- function domain API def glsl_declr(self, upref, **kwargs): # XXX # - what about other vector types? t = self.texture _, fname, _, ret_type = self.glsl_identifier(upref)[0] #ret_type = 'float' if t.channels == 1 else 'vec{}'.format( t.channels) tmpl = self.__class__._GLSL_TEMPLATE_DOMAIN declr = tmpl.format(ret_type=ret_type, arg_type=['float', 'vec2', 'vec3'][t.dimension - 1], fname=fname, rgba='rgba'[0:t.channels], upref=upref) header = self.__class__._GLSL_TEMPLATE_DECRL.format(d=t.dimension, upref=upref) return header + '\n\n' + declr
class Context(): """ context API: events: ------- on_ready when the context is ready to let the OpenGL.GL functions do their job. on_cycle when the cycle logic should be executed on_resize when the context was resized. It is allowed to perform the tick and rendering logic like on_cycle to provide fluid window resizing. """ size = attributes.VectorAttribute(2) resolution = attributes.VectorAttribute(2) position = attributes.VectorAttribute(2) title = attributes.CastedAttribute(str, 'window') visible = attributes.CastedAttribute(bool) def __init__(self): self.gl_buffer_base_register = {} self._next_free_gl_buffer_base_index = 0 self.on_ready = Event() self.on_cycle = Event() self.on_resize = Event() self.on_close = Event() self.active_keys = set() def buffer_base(self, name, index=None): """ reserves a buffer base with a **name** at **index**. if no **index** was given, the next free buffer base will be reserved. The reserved buffer index is then returned. Example: ```python from gpupy.gl import VertexBuffer, GPUPY_GL ubo = VertexBuffer.to_device(ndarray, target=UNIFORM_BUFFER) ubo.bind_buffer_base(GPUPY_GL.CONTEXT.buffer_base('some_name')) ``` """ i_max = glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS) if not name in self.gl_buffer_base_register: if index is not None and index in self.gl_buffer_base_register: raise RuntimeError('allready registred.') index = index or self._next_free_gl_buffer_base_index if index > i_max: raise OverflowError() self.gl_buffer_base_register[ name] = index or self._next_free_gl_buffer_base_index # find lowest free index ui = sorted(self.gl_buffer_base_register.values()) for i in range(len(ui) - 1): if ui[i + 1] - ui[i] != 1: self._next_free_gl_buffer_base_index = ui[i] + 1 return self._next_free_gl_buffer_base_index = len(ui) return self.gl_buffer_base_register[name] def __gl_context_enable__(self): """ activates OpenGL context """ GPUPY_GL.CONTEXT = self
class Cartesian2D(Camera): """ 2d cartesian camera Attributes: screensize (float[2]): lengths of the space dimensions position (float[3]): camera position roll (float): rotation of the coordinate space Examples: XXX """ screensize = attributes.VectorAttribute(2, (1, 1)) roll = attributes.CastedAttribute(float, 0) position = attributes.VectorAttribute(3, (0, 0, 0)) DTYPE = np.dtype([ ('mat_view', np.float32, (4, 4)), ('mat_projection', np.float32, (4, 4)), ('position', np.float32, 3), ('roll', np.float32), ('direction', np.float32, 3), ('_b1', np.float32), ('direction_right', np.float32, 3), ('_b2', np.float32), ('direction_top', np.float32, 3), ]) def __init__(self, screensize, position=(0, 0, 0), roll=0, buffer_base=None): super().__init__( Cartesian2D.DTYPE, buffer_base or GPUPY_GL.CONTEXT.buffer_base('gpupy.gl.camera')) self._camera = np.zeros(1, dtype=Cartesian2D.DTYPE) self.screensize = screensize self.position = position self.roll = roll self.commit() # -- api -- def __buffer__(self): mat_projection = mat4_rot_z(self.roll) @ np.array( [ 1, 0, 0, -self.position.x, 0, 1, 0, -self.position.y, 0, 0, 1, 0, 0, 0, 0, 1, ], dtype=np.float32).reshape((4, 4)) mat_view = np.array([ 2.0 / self.screensize.x, 0, 0, 0, 0, -2.0 / self.screensize.y, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, ], dtype=np.float32).reshape((4, 4)) self._camera['mat_view'] = mat_view.T self._camera['mat_projection'] = mat_projection.T self._camera['roll'] = self.roll self._camera['position'] = self.position.xyz # XXX is a function of roll self._camera['direction'] = (0, 0, -1) self._camera['direction_right'] = (1, 0, 0) self._camera['direction_top'] = (0, 1, 0) return self._camera # -- matrix methods @roll.on_change @position.on_change @screensize.on_change def _attributes_changed(self, *e): self.commit()
class Perspective3D(Camera): """ 3d perspective camera Attributes: fov (float): field of view angle frustum (float[2]): near and far plane screensize (float[2]): coordinate space of near plane position (float[3]): camera position rotation (float[3]): roll, pitch, yaw Examples: XXX """ screensize = attributes.VectorAttribute(2, (1, 1)) rotation = attributes.VectorAttribute(3, (0, 0, 0)) position = attributes.VectorAttribute(3, (0, 0, 0)) frustum = attributes.VectorAttribute(2, (0.1, 10000)) fov = attributes.CastedAttribute(float, 0.5 * 65.0 * np.pi / 180.0) DTYPE = np.dtype([ ('mat_view', np.float32, (4, 4)), ('mat_projection', np.float32, (4, 4)), ('mat_viewprojection', np.float32, (4, 4)), ('position', np.float32, 3), ('yaw', np.float32), ('direction', np.float32, 3), ('pitch', np.float32), ('direction_right', np.float32, 3), ('roll', np.float32), ('direction_top', np.float32, 3), ]) def __init__(self, screensize, position=(0, 0, 0), rotation=(0, 0, 0), buffer_base=None): super().__init__( Perspective3D.DTYPE, buffer_base or GPUPY_GL.CONTEXT.buffer_base('gpupy.gl.camera')) self._camera = np.zeros(1, dtype=Perspective3D.DTYPE) self._mat_projection = None self._mat_view = None self.screensize = screensize self.position = position self.rotation = rotation self.direction = (0, 0, 0) self.right = (0, 0, 0) self.top = (0, 0, 0) self.commit() # -- api -- def __buffer__(self): # camera position and rotation matrix reflection_xy = mat4_reflection_xy() position_matrix = mat4_translation(*self.position) rot_roll = mat4_rot_z(self.rotation[2]) rot_yaw = mat4_rot_y(self.rotation[1]) rot_pitch = mat4_rot_x(self.rotation[0]) mat_view = ( rot_roll @ rot_pitch @ rot_yaw @ position_matrix @ reflection_xy) # perspective projection (n, f), ratio = self.frustum, self.screensize[0] / self.screensize[1] h = n * np.tan(self.fov) w = h * ratio mat_proj = np.array([ n / w, 0, 0, 0, 0, n / h, 0, 0, 0, 0, -(f + n) / (f - n), -2.0 * f * n / (f - n), 0, 0, -1, 0 ], dtype=np.float32).reshape((4, 4)) # ubo self._camera['mat_view'] = mat_view.T self._camera['mat_projection'] = mat_proj.T # remember.. (AB)^T = (B^T)(A^T) self._camera['mat_viewprojection'] = (mat_proj @ mat_view).T self._camera['roll'], \ self._camera['pitch'], \ self._camera['yaw'] = self.rotation self._camera['position'] = self.position self._camera['direction'] = self.direction self._camera['direction_right'] = self.right self._camera['direction_top'] = self.top return self._camera # -- camera control events -- @position.on_change @screensize.on_change @rotation.on_change def _attributes_changed(self, *e): self.commit()
class AbstractGrid(Widget): """ """ # widget configuration size = attributes.VectorAttribute(2) resolution = attributes.VectorAttribute(2) position = attributes.VectorAttribute(4) cs = attributes.VectorAttribute(4) # grid configuration major_grid = attributes.VectorAttribute(2, (.5, .5)) major_grid_width = attributes.CastedAttribute(float, 0.5) major_grid_color = attributes.VectorAttribute(4, (0, 0, 0, 1)) minor_grid_width = attributes.CastedAttribute(float, .5) minor_grid_color = attributes.VectorAttribute(4, (0, 0, 0, 1)) minor_grid_n = attributes.VectorAttribute(2, (5, 5)) antialiasing = attributes.CastedAttribute(float, .5) # style background_color = attributes.VectorAttribute(4, (1, 1, 1, 1)) def __init__(self, size, position=(0, 0, 0, 1), cs=(0, 0), major_grid=(1, 1), background_color=(1, 1, 1, 1), major_grid_color=(0, 0, 0, 1), resolution=None, minor_grid_color=(0, 0, 0, 1), minor_grid_n=(5, 5)): super().__init__() self.position = position self.size = size self.cs = cs self._t = time() self.major_grid = major_grid self.major_grid_color = major_grid_color self.minor_grid_color = minor_grid_color self.minor_grid_n = minor_grid_n self.background_color = background_color self._req_uniforms = True self.resolution = resolution or self.size self._dev = False self._mat_model = np.array([ self.resolution.x, 0, 0, 0, 0, self.resolution.y, 0, 0, 0, 0, 1, 0, self.position.x, self.position.y, 0, 1 ], dtype=np.float32).reshape((4, 4)).T self._init_plane() @size.on_change @cs.on_change @major_grid.on_change @minor_grid_n.on_change @minor_grid_width.on_change @major_grid_width.on_change @resolution.on_change @position.on_change def req_uniforms(self, *e): self._req_uniforms = True def upload_uniforms(self): # update model matrix self._mat_model[0][0] = self.resolution[0] self._mat_model[1][1] = self.resolution[1] self._mat_model[3] = self.position self.program.uniform('mat_model', self._mat_model) # -- polar # self.program.uniform('u_limits1',[-5.1,+5.1,-5.1,+5.1]) # self.program.uniform('u_limits2', [-5.0,+5.0, np.pi/6.0, 11.0*np.pi/6.0]) # self.program.uniform('u_major_grid_step', [1.00,np.pi/ 6.0]) # self.program.uniform('u_minor_grid_step', [0.25,np.pi/60.0]) # -- cartesian # add this extra bit and assure that u_limits1 != u_limit2 l1 = self.cs.values * 1.000 mg = self.major_grid.values cs = self.cs.values Mw = self.minor_grid_width * max(1.0, self.resolution[0] / self.size[0]) mw = self.major_grid_width * max(1.0, self.resolution[0] / self.size[0]) aa = self.antialiasing self.program.uniform('u_limits1', l1) self.program.uniform('u_limits2', cs) self.program.uniform('u_major_grid_step', mg) self.program.uniform('u_minor_grid_step', self.major_grid.values / self.minor_grid_n) self.program.uniform('u_major_grid_width', Mw) self.program.uniform('u_minor_grid_width', mw) self.program.uniform('u_major_grid_color', self.major_grid_color) self.program.uniform('u_minor_grid_color', self.minor_grid_color) self.program.uniform('u_resolution', self.resolution) self.program.uniform('u_antialias', aa) self.program.uniform('c_bg', self.background_color) self._req_uniforms = False # print('RES', self.resolution, 'size', self.size, 'CS', self.cs) # print('GRID', self.major_grid) def _init_plane(self): self.program = GridProgram() self.program.uniform_block_binding( 'camera', GPUPY_GL.CONTEXT.buffer_base('gpupy.gl.camera')) self.upload_uniforms() self.mesh = StridedVertexMesh( mesh3d_rectangle(), GL_TRIANGLES, attribute_locations=self.program.attributes) def dev(self): self.program = GridProgramDev() self.program.uniform_block_binding( 'camera', GPUPY_GL.CONTEXT.buffer_base('gpupy.gl.camera')) self.upload_uniforms() self._dev = True return self def tick(self): if True or self._req_uniforms: self.upload_uniforms() super().tick() def _render(self): glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) if self._dev: self.program.uniform('dev_color1', (1, 0, 0, 1)) self.program.uniform('dev_color2', (1, 0, 1, 1)) self.program.use() self.mesh.draw() self.program.unuse() if self._dev: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) self.program.uniform('dev_color1', (0, 1, 0, 1)) self.program.uniform('dev_color2', (0, 1, 1, 1)) self.program.use() self.mesh.draw() self.program.unuse() glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
class FrameWidget(Widget): """ Frame component uses framebuffer to render a scene on a plane. the state of a Frame is described by the following properties: Properties: - size: the size of the frame - resulution: the size of the texture which captures the scene. e.g. the capture size might be higher than the size of the plane to enable anti aliasing like down sampling. - viewport: gpupy.gl.ViewPort if not defined the viewport is set to ViewPort((0, 0), resulution) - camera: camera which will be enabled when the Framebuffer starts cawhichpturing - camera: camera for rendering the screen +----------------------------------------+ | | capture.x | c ##################################### | a # s | p # i | t # vp.pos vp.w z | u # x --------------------- e | r # | . | e # vp.h | y | . # + --------------------- | y #################################### | +----------------------------------------+ """ size = attributes.VectorAttribute(2) position = attributes.VectorAttribute(4) resulution = attributes.VectorAttribute(2) plane_size = attributes.ComputedAttribute( size, descriptor=attributes.VectorAttribute(2)) clear_color = attributes.VectorAttribute(4) def __init__(self, size, resulution=None, position=(0, 0, 0, 1), multisampling=None, post_effects=None, blit=None, clear_color=(0, 0, 0, 1), preload_factor=2): """ creates a framebuffer of *size* and *resulution* at *position*. if *resulution* is None, the resulution is linked to *size*. """ # XXX # - multisampling # - post effects # - blit/record mode super().__init__() self._res = None self.size = size self.position = position self.resulution = resulution if resulution is not None else self.size self.viewport = Viewport((0, 0), self.resulution) self.texture = None self.clear_color = clear_color self.preload_factor = preload_factor self._init_capturing() self._init_plane() self._require_resize = False # -- initialization -- def _init_capturing(self): self._res = self.preload_factor * self.resulution.values self.texture = Texture2D.empty((*self._res, 4), np.float32) self.texture.interpolation_linear() self.framebuffer = Framebuffer() self.framebuffer.color_attachment(self.texture) self.texture.parameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) self.texture.parameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) @resulution.on_change def resulution_changed(self, value): print('WFWEFWEFFF§§33') self._require_resize = True def _init_plane(self): self.program = _CameraProgram(_GLSL_VRT, _GLSL_FRG) self.texture.activate() self.program.uniform('frame_texture', self.texture) self.program.uniform_block_binding( 'camera', GPUPY_GL.CONTEXT.buffer_base('gpupy.gl.camera')) self.program.uniform('size', self.plane_size.xy) self.program.uniform('mat_model', np.identity(4, dtype=np.float32)) self.program.uniform('position', self.position.xyzw) self.program.uniform('rf', (self.resulution[0] / self._res[0], self.resulution[1] / self._res[1])) self.position.on_change.append( partial(self.program.uniform, 'position')) self.size.on_change.append(partial(self.program.uniform, 'size')) self.mesh = StridedVertexMesh( mesh3d_rectangle(), GL_TRIANGLES, attribute_locations=self.program.attributes) @plane_size.transformation @resulution.transformation def normalize(self, v): """ to avoid pixel rounding errors """ # XXX # - is this the best solution? v = np.ceil(v) return (max(1, v[0]), max(1, v[1])) def tick(self): if self._require_resize: if self._res[0] < self.resulution[0] \ or self._res[1] < self.resulution[1]: self._res = self.resulution.values * self.preload_factor self.texture.resize(self._res) self.program.uniform('rf', (self.resulution[0] / self._res[0], self.resulution[1] / self._res[1])) self._require_resize = False def render(self): self.draw() def draw(self, shader=None): shader = shader or self.program self.texture.activate() shader.use() self.mesh.draw() shader.unuse() def use(self): self.framebuffer.use() self.viewport.use((0, 0), np.array(self.resulution, dtype=np.int32)) glClearColor(*self.clear_color) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) def unuse(self): self.viewport.unuse(restore=True) self.framebuffer.unuse()
class Plotter2d(Widget): UBO_DTYPE = np.dtype([('mat_cs', np.float32, (4, 4)), ('cs', np.float32, 4), ('cs_size', np.float32, 2)]) # widget configuration size = attributes.VectorAttribute(2) position = attributes.VectorAttribute(4, (0, 0, 0, 1)) # configuration space determines [minx, maxx, miny, maxy] cs = attributes.VectorAttribute(4, (0, 1, 0, 1)) # axes configuration # size of one unit (sx, sy) in configuration space axes_unit = attributes.VectorAttribute(2, (0.25, 0.25)) # division of one axes_unit into sub units minor_axes_n = attributes.VectorAttribute(2, (5, 5)) # the plot plane is implemented via framebuffer. this factor # allows to adjust the resolution of the framebuffer viewport. plot_resolution_factor = attributes.CastedAttribute(float, 1) background_color = attributes.VectorAttribute(4, (0, 0, 0, 1)) plot_background_color = attributes.VectorAttribute(4, (0, 0, 0, 1)) plot_padding = attributes.VectorAttribute(4, (0, 0, 0, 0)) # common precomputed properties cs_size = attributes.ComputedAttribute( cs, descriptor=attributes.VectorAttribute(2), transformation=cs_size) def __init__(self, size, position=(0, 0, 0, 1), cs=(0, 1, 0, 1), style=DEFAULT_STYLE, axes_unit=None): super().__init__() # state self.cs = cs self.size = size self.position = position if axes_unit is not None: self.axes_unit = axes_unit #self.axes_unit = (0.1, 0.1) self._style = Style( { 'border': parse_4f1_1c4, 'background-color': parse_1c4, 'grid-color': parse_1c4, 'grid-sub-color': parse_1c4, 'plot-background-color': parse_1c4, 'plot-padding': parse_4f1, 'min-size': parse_2f1, 'plot-scaling': float, }, style) self.background_color = self._style['background-color'] self.plot_background_color = self._style['plot-background-color'] self.plot_padding = self._style['plot-padding'] self.ubo = None self.on_plot = Event() self._plot_margin = vec4((0, 0, 0, 0)) self.grid = None self.layer = None self._graphs = [] self._graphs_initialized = False self._init() self.a = False self.last_fr = False self.on_plot.once(self.init_graphs) self.cmc = [ [1, 0, 0, 1], [1, 1, 0, 1], [1, 0, 1, 1], [0, 1, 0, 1], [0, 0, 1, 1], [1, 1, 1, 1], ] # -- graph api def init_graphs(self): for graph in self._graphs: graph.init() self._graphs_initialized = True def append(self, graph): self._graphs.append(graph) if self._graphs_initialized: graph.init() graph.resolution = self.plotframe.resulution graph.viewport = self.plotframe.resulution def __iadd__(self, graph): self.append(graph) return self # -- init def _init(self): self._initlayer() self._initplotcam() self._init_grid() self._init_ubo() # -- plot ubo def _init_ubo(self): """ initializes plotting ubo """ self.ubo = BufferObject.to_device(np.zeros(1, dtype=Plotter2d.UBO_DTYPE), target=GL_UNIFORM_BUFFER) buffer_base = GPUPY_GL.CONTEXT.buffer_base('gpupy.plot.plotter2d') self.ubo.bind_buffer_base(buffer_base) self.update_ubo() @cs.on_change def update_ubo(self, *e): self.ubo.host['mat_cs'] = mat_cs(self.cs, self.layer.content_size) self.ubo.host['cs'] = self.cs.values self.ubo.host['cs_size'] = cs_size(self.cs) self.ubo.sync_gpu() # -- grid def _init_grid(self): """ initializes grid component """ major_grid = observables.transform_observables( transformation=grid, observables=(self.axes_unit, self.cs)) self.grid = CartesianGrid( size=self.layer.content_size, position=self.layer.content_position, cs=self.cs, major_grid=major_grid, major_grid_color=self._style['grid-color'], minor_grid_color=self._style['grid-sub-color'], background_color=self.plot_background_color, resolution=self.layer.content_size, minor_grid_n=self.minor_axes_n) #.dev() # -- camera def _initplotcam(self): """ creates a plot camera which is connected to the configuration space """ pos = observables.transform_observables( lambda s: (s[0] * 0.5, s[1] * 0.5, 1), vecn((0, 0, 0)), (self.layer.content_size, )) self.plotcam = Camera2D(self.layer.content_size, pos) # -- container def _initlayer(self): """ initializes main plotcontainer. the plotcontainer manages border, margin padding and contains the main plot framebuffer """ layer = Container(size=self.size, position=self.position, margin=self._plot_margin, padding=self.plot_padding, border=self._style['border'][0], border_color=self._style['border'][1]) self.plotframe = FrameWidget(position=layer.content_position, size=layer.content_size, resulution=layer.content_size, clear_color=(0, 0, 0, 0)) self.layer = layer self.layer.content_size.on_change.append(self.update_ubo) def tick(self): self.on_tick() # -- tick the components self.plotcam.enable() self.layer.tick() self.grid.tick() self.plotframe.tick() # -- graph rendering self.plotframe.use() self.plotcam.enable() self.on_plot() self.ubo.bind_buffer_base( GPUPY_GL.CONTEXT.buffer_base('gpupy.plot.plotter2d')) for graph in self._graphs: graph.tick() graph.render() self.plotframe.unuse() def draw(self): self.grid.render() self.layer.render() self.plotframe.render()
class FramestackWidget(Widget): SUBJECT = 0 IDX = 1 RENDERER = 2 CLICKMAP = 3 M_TOP_LAYER = 1 M_LAYERS = 0 resolution = attributes.VectorAttribute(2) size = attributes.VectorAttribute(2) position = attributes.VectorAttribute(4) def __init__(self, position, size, resolution, pf=1): super().__init__() print(glGetIntegerv(GL_MAX_DRAW_BUFFERS)) self._s = [] self.resolution = resolution self.size = size self.position = position self._pf = pf self._rs = [] self._hrl = 0 self._init_textures() self._init_fb() self._init_program() self._init_mesh() def _init_textures(self): def _texture(txres): txt = Texture2D.empty((self._ln(), *txres, 4), dtype=np.float32, array=True) txt.activate() txt.parameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) txt.parameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) return txt # LOW TEXTURE txres = self.resolution.xy self._rs.append(txres) self.tarray_rl = _texture(txres) if self._pf != 1 else None # MAIN TEXTURE txres = self.resolution.xy * self._pf self._rs.append(txres) self.tarray = _texture(txres) # HIGH TEXTURE txres = self.resolution.xy * self._pf**2 self._rs.append(txres) self.tarray_rh = _texture(txres) if self._pf != 1 else None def _init_fb(self): self._rb_fb = glGenFramebuffers(1) self._rb_fbs = glGenFramebuffers(100) def _init_program(self): self._rs_prg = _CameraProgram(_GLSL_VRT, _GLSL_STACK_FRG) self._rs_prg.uniform('mat_model', np.identity(4, dtype=np.float32)) self._rs_prg_layer = Program() self._rs_prg_layer.shaders.append( Shader(GL_VERTEX_SHADER, _GLSL_STACK_VRT_LAYER)) self._rs_prg_layer.shaders.append( Shader(GL_FRAGMENT_SHADER, _GLSL_STACK_FRG_LAYER)) self._rs_prg_layer.link() self._rs_prg_layer.uniform('frame_texture', self.tarray) self.upload_uniforms() def _init_mesh(self): self._rs_mesh = StridedVertexMesh( mesh3d_rectangle(), GL_TRIANGLES, attribute_locations=self._rs_prg.attributes) def upload_uniforms(self): self._rs_prg.uniform('size', self.size) self._rs_prg.uniform('position', self.position) self._rs_prg.uniform('frame_texture', self.tarray) self._rs_prg_layer.uniform('frame_texture', self.tarray) def _ln(self): return 35 @resolution.on_change def _resolution_changed(self, *e): return if self._pf != 1: if self._rs[1][0] < self.resolution[0] \ or self._rs[1][1] < self.resolution[1]: _G.debug('FramestackWidget swap textures HIGH') # swap textures old_tarray_rl = self.tarray_rl self.tarray_rl = self.tarray self.tarray = self.tarray_rh self.tarray_rh = old_tarray_rl self.tarray.activate() self._rs[0] = self._rs[1] self._rs[1] = self._rs[2] self._rs[2] = self._rs[2] * self._pf # resize the new small texture self.tarray_rh.resize((self._ln(), *self._rs[2])) self._rebind_active_fbo() elif self.resolution[0]/self._rs[1][0] <= .25 \ and self.resolution[1]/self._rs[1][1] <= .25: _G.debug('FramestackWidget swap textures LOW') # swap textures old_tarray_rh = self.tarray_rh self.tarray_rh = self.tarray self.tarray = self.tarray_rl self.tarray_rl = old_tarray_rh self.tarray.activate() self._rs[2] = self._rs[1] self._rs[1] = self._rs[0] self._rs[0] = self._rs[0] / self._pf # resize the new large texture self.tarray_rl.resize((self._ln(), *self._rs[0])) self._rebind_active_fbo() rf = (self.resolution[0] / self._rs[1][0], self.resolution[1] / self._rs[1][1]) self._rs_prg.uniform('rf', rf) self._rs_prg_layer.uniform('rf', rf) else: self.tarray.resize((self._ln(), *self.resolution.xy)) self._rs[1] = self.resolution.xy self._rs_prg.uniform('rf', (1, 1)) self._rs_prg_layer.uniform('rf', (1, 1)) self.upload_uniforms() @size.on_change @position.on_change def _attributes_changed(self, *e): self.upload_uniforms() def _rebind_active_fbo(self): tex = self.tarray.gl_texture_id for si in self._s: tidx = 2 * si[FramestackWidget.IDX] fb = self._rb_fbs[tidx] glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0, tidx) tidx = 2 * si[FramestackWidget.IDX] + 1 fb = self._rb_fbs[tidx] glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0, tidx) def render_stack(self, mode=0, layers=None): vp = glGetIntegerv(GL_VIEWPORT) glViewport(0, 0, *((self.resolution.xy).astype(np.int32))) glClearColor(0, 1, 0, 0) if layers is not None: layers = sorted(layers) print('REEE') if self._hrl < len(self._rs) - 1: print('RENDER LEVEL', self._hrl) self._hrl += 1 # render all layers # (slowest mode) if mode == self.M_LAYERS: glDisable(GL_BLEND) last_idx = None if layers is None or layers[0] == 0 else self._s[ layers[0] - 1][FramestackWidget.IDX] self._rs_prg_layer.uniform('idxl', -1) stack = self._s if layers is not None: stack = [(si, i in layers) for i, si in enumerate(self._s[layers[0]:], layers[0]) ] else: stack = [(si, True) for si in self._s] for si, rerender in stack: # render item if rerender: glBindFramebuffer( GL_DRAW_FRAMEBUFFER, self._rb_fbs[_tidx(si[FramestackWidget.IDX])]) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) si[self.RENDERER]() # render layer glBindFramebuffer( GL_DRAW_FRAMEBUFFER, self._rb_fbs[_tidxl(si[FramestackWidget.IDX])]) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) self._rs_prg_layer.use() self._rs_prg_layer.uniform('idx', _tidx(si[FramestackWidget.IDX])) self._rs_prg_layer.uniform('clickmap', si[FramestackWidget.CLICKMAP]) if last_idx is not None: self._rs_prg_layer.uniform('idxl', _tidxl(last_idx)) self._rs_prg_layer.uniform('idxc', _tidxc(last_idx)) self._rs_mesh.draw() self._rs_prg_layer.unuse() last_idx = si[FramestackWidget.IDX] glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0) # render all items to the top layer directly # (this is the fastest rendering method) elif mode == self.M_TOP_LAYER: print('TOP') #glEnable(GL_BLEND) #glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); fb = self._rb_fbs[_tidxl(self._s[len(self._s) - 1][FramestackWidget.IDX])] glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) stack = (self._s[i] for i in layers) if layers is not None else self._s for si in stack: si[self.RENDERER]() glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0) self._hrl = 0 glViewport(*vp) def render(self): self.tarray.activate() self._rs_prg.use() self._rs_prg.uniform( 'idx', _tidxl(self._s[len(self._s) - 1][FramestackWidget.IDX])) self._rs_mesh.draw() self._rs_prg.unuse() def _free_idx(self): sidx = sorted(self._s, key=lambda rsi: rsi[FramestackWidget.IDX]) for idx, rsi in enumerate(sidx): if rsi[FramestackWidget.IDX] != idx: return idx return len(sidx) def _caller(self, renderer): if hasattr(renderer, '__call__'): return renderer if hasattr(renderer, 'render'): return renderer.render raise ValueError() def get_layer(self, renderer): for i, si in enumerate(self._s): if si[0] is renderer: return i def append(self, subject, clickmap=(0, 0, 0, 0)): idx = self._free_idx() self._init_layer(idx) si = _StackItem() si.subject = subject si.idx = idx si.clickmap = clickmap si.renderer = self._caller(subject) si.init_gl(self.resolution.xy) self._s.append((subject, idx, self._caller(subject), clickmap)) glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0) def insert(self, i, renderer, clickmap=(0, 0, 0, 0)): idx = self._free_idx() self._init_layer(idx) self._s.insert(i, (renderer, idx, self._caller(renderer), clickmap)) glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0) def _init_layer(self, idx): # we perform at least one clear operation on each texture on init. # otherwise OpenGL will lag when switching to another texture for the first # time. if self._pf != 1: glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self._rb_fbs[_tidx(idx)]) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, self.tarray_rh.gl_texture_id, 0, _tidx(idx)) glClear(GL_COLOR_BUFFER_BIT) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, self.tarray_rl.gl_texture_id, 0, _tidx(idx)) glClear(GL_COLOR_BUFFER_BIT) glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self._rb_fbs[_tidxl(idx)]) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, self.tarray_rh.gl_texture_id, 0, _tidxl(idx)) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, self.tarray_rl.gl_texture_id, 0, _tidxc(idx)) glClear(GL_COLOR_BUFFER_BIT) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, self.tarray_rl.gl_texture_id, 0, _tidxl(idx)) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, self.tarray_rl.gl_texture_id, 0, _tidxc(idx)) glClear(GL_COLOR_BUFFER_BIT) glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self._rb_fbs[_tidx(idx)]) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, self.tarray.gl_texture_id, 0, _tidx(idx)) glClear(GL_COLOR_BUFFER_BIT) glBindFramebuffer(GL_DRAW_FRAMEBUFFER, self._rb_fbs[_tidxl(idx)]) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, self.tarray.gl_texture_id, 0, _tidxl(idx)) glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, self.tarray.gl_texture_id, 0, _tidxc(idx)) glDrawBuffers([GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1]) glClear(GL_COLOR_BUFFER_BIT) glBindFramebuffer(GL_FRAMEBUFFER, 0)
class RaycastingDemoWidget(): size = attributes.VectorAttribute(2) zoom = attributes.VectorAttribute(3, (1, 1, 1)) rotation = attributes.VectorAttribute(3, (0, 0, 0)) box_rotation = attributes.VectorAttribute(3, (0, 0, 0)) def __init__(self, size, volumedata): self.size = size self.force_viewbox_render = True self.volumedata = volumedata self.init() @zoom.on_change @rotation.on_change def upload_mat_volume(self, *e): self.program_ray.uniform( 'mat_volume', mat4_rot_x(self.rotation.x) @ mat4_rot_y(self.rotation.y) @ np.diag([self.zoom.x, self.zoom.x, self.zoom.x, 1])) @box_rotation.on_change def upload_mat_model(self, *e): self.force_viewbox_render = True self.program_box.uniform( 'mat_model', mat4_rot_x(self.box_rotation.x) @ mat4_rot_y(self.box_rotation.y)) def init(self): glEnable(GL_BLEND) glEnable(GL_DEPTH_TEST) glDepthFunc(GL_LESS) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glFrontFace(GL_CW) self._create_shader() rect_size = self.size rect_position = (-rect_size[0] / 2, -rect_size[1] / 2) self.cube_buffer = BufferObject.to_device( mesh3d_cube((200, 200, 200), center=True)) self.vao = create_vao_from_program_buffer_object( self.program_box, self.cube_buffer) # create texture for front and backside # # since we'll use the same camera inside the framebuffer we have # to put the framebuffer size to the size of the main window framebuffer. # w # otherwise we could scale the camera to compensate the difference in window # size and framebuffer size. self.texture_front = Texture2D.empty((*CUBE_FRAMEBUFFER, 4), np.float32) self.framebuffer_front = Framebuffer() self.framebuffer_front.color_attachment(self.texture_front) self.texture_back = Texture2D.empty((*CUBE_FRAMEBUFFER, 4), np.float32) self.framebuffer_back = Framebuffer() self.framebuffer_back.color_attachment(self.texture_back) # create framebuffer screen (this is the widget main screen) rect_size = [2, 2] rect_position = (-rect_size[0] / 2, -rect_size[1] / 2) self.screen_buffer = BufferObject.to_device( mesh3d_rectangle(center=rect_position, *rect_size)) self.screen_vao = create_vao_from_program_buffer_object( self.program_ray, self.screen_buffer) # create 3d texture self.texture = Texture3D.from_numpy(self.volumedata) self.texture.interpolation_nearest() self.texture.parameters.update({ GL_TEXTURE_WRAP_S: GL_CLAMP_TO_BORDER, GL_TEXTURE_WRAP_T: GL_CLAMP_TO_BORDER, GL_TEXTURE_WRAP_R: GL_CLAMP_TO_BORDER }) self.program_ray.uniform({ 'tex': self.texture_front.activate(0), 'back': self.texture_back.activate(1), 'vol': self.texture.activate(2), 'v_ray': V_RAY, 'v_iter': V_ITER, 'mat_volume': np.array([0.5, 0, 0, 0, 0, 0.5, 0, 0, 0, 0, 0.5, 0, 0, 0, 0, 1.0], dtype=np.float32) }) self.force_viewbox_render = True self.upload_mat_model() self.upload_mat_volume() def _create_shader(self): """ creates the ray casting shader and the raydirection box shader """ self.program_box = create_program(vertex=BOX_VRT_SHADER, fragment=BOX_FRG_SHADER, link=False) self.program_box.declare_uniform('camera', Perspective3D.DTYPE, variable='camera') self.program_box.link() self.program_box.uniform_blocks[ 'camera'] = GPUPY_GL.CONTEXT.buffer_base('gpupy.gl.camera') self.program_box.uniforms['mat_model'] = np.array( [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], dtype=np.float32) self.program_ray = create_program(vertex=RAYCASTING_VRT_SHADER, fragment=RAYCASTING_FRG_SHADER) def __call__(self): # check if we need to recalculate the culled # fron and back face frame buffers if self.force_viewbox_render: self._render_culled_box() self.force_viewbox_render = False # reset main frame glClearColor(0, 0, 0, 0) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # box for debugging purpose glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) self._render_box() glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) # volumetric raycasting self._render_raycast() return True def _render_box(self): self.program_box.use() glBindVertexArray(self.vao) glDrawArrays(GL_TRIANGLES, 0, len(self.cube_buffer)) glBindVertexArray(0) self.program_box.unuse() def _render_raycast(self): self.texture_front.reactivate() self.texture_back.reactivate() self.program_ray.use() glBindVertexArray(self.screen_vao) glDrawArrays(GL_TRIANGLES, 0, len(self.screen_buffer)) glBindVertexArray(0) self.program_ray.unuse() def _render_culled_box(self): # prepare old_viewport = glGetIntegerv(GL_VIEWPORT) glViewport(0, 0, *CUBE_FRAMEBUFFER) glDisable(GL_DEPTH_TEST) glEnable(GL_CULL_FACE) glClearColor(0, 0, 0, 0) self.program_box.use() # render front faces self.framebuffer_front.use() glCullFace(GL_FRONT) glClear(GL_COLOR_BUFFER_BIT) glBindVertexArray(self.vao) glDrawArrays(GL_TRIANGLES, 0, len(self.cube_buffer)) glBindVertexArray(0) self.framebuffer_front.unuse() # render back faces self.framebuffer_back.use() glClear(GL_COLOR_BUFFER_BIT) glCullFace(GL_BACK) glBindVertexArray(self.vao) glDrawArrays(GL_TRIANGLES, 0, len(self.cube_buffer)) glBindVertexArray(0) self.framebuffer_back.unuse() # restore self.program_box.unuse() glEnable(GL_DEPTH_TEST) glDisable(GL_CULL_FACE) glViewport(*old_viewport)
class TextWidget(): size = attributes.VectorAttribute(2) def __init__(self, size): self.size = size self.init() def init(self): # note that in Widget init the context should be active! glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) self.program = create_program(vertex=VERTEX_SHADER, fragment=FRAGMENT_SHADER, link=False) # the shader uses {% uniform block %} tags which we must # declare before linking the program self.program.declare_uniform('camera', Cartesian2D.DTYPE, variable='camera') self.program.link() # we need to tell the shader which is is buffer base. # the GPUPY_GL.CONTEXT.buffer_base returns a free buffer # base which is reserved for the active context. self.program.uniform_blocks['camera'] = GPUPY_GL.CONTEXT.buffer_base( 'gpupy.gl.camera') # the mesh represents VAO and VBO self.mesh = StridedVertexMesh( mesh3d_rectangle(center=(-self.size[0] / 2, -self.size[1] / 2), *self.size), GL_TRIANGLES, attribute_locations=self.program.attributes) # create texture and buffers self.texture = Texture2D.from_numpy( np.random.random((500, 500, 3)).astype(np.float32)) self.texture.interpolation_linear() self.texture.activate(0) self.step = 1 self.last_random = 1 def __call__(self): # a widget is something which is callable. # render me glClear(GL_COLOR_BUFFER_BIT) self.program.use() self.texture.reactivate() self.mesh.draw() self.texture.unbind() self.program.unuse() # texture update self._update_data() # to keep the widget alive return True return True def _update_data(self): # do some funny stuff if self.last_random > 0.1: channels, size = 4, 500 elif self.last_random > 0.04: channels, size = 3, 300 elif self.last_random > 0.02: channels, size = 2, 700 else: channels, size = 1, 50 new_data = np.random.random((size, size, channels)) new_data[:][self.step % size] = 1 new_data[:][(2 * self.step) % size] = 1 new_data[:][(3 * self.step) % size] = 1 new_data[:][(4 * self.step) % size] = 1 self.texture.load(new_data.astype(np.float32)) self.last_random = new_data[0][0][0] self.step += 1
class GLFW_Window(Context): size = attributes.VectorAttribute(2) resolution = attributes.VectorAttribute(2) position = attributes.VectorAttribute(2) title = attributes.CastedAttribute(str, 'window') visible = attributes.CastedAttribute(bool) def __init__(self, size=(400, 400), title='gpupy glfw window', bootstrap=True, widget=None): super().__init__() self._glfw_initialized = False self.size = size self.title = title self.visible = True self._active = False self.active_keys = set() self._in_cycle = False if bootstrap: self.bootstrap() self.make_context() self.widget = widget or (lambda *a: True) @visible.on_change def set_visible(self, visible): if visible: glfwShowWindow(self._handle) else: glfwHideWindow(self._handle) @size.on_change def set_size(self, size): glfwSetWindowSize(self._handle, int(size[0]), int(size[1])) def bootstrap(self): """ start GLFW window context """ if self._glfw_initialized: raise RuntimeError('allready initialized.') self._handle = glfwCreateWindow(int(self.size[0]), int(self.size[1]), self.title) if not self._handle: raise RuntimeError('glfw.CreateWindow() error') glfwWindowHint(GLFW_VISIBLE, int(self.visible)) def _resize_callback(window, width, height): self.size = (width, height) if len(self.on_resize): # at this point we only make a new context if we are not just # within GLFW_Application.cycle method. E.g. if GLFW_Window.set_size() # was performed within the GLFW_Window.cycle() method. if not self._in_cycle: self.make_context() self.on_cycle(self) self.on_resize(self) if not self._in_cycle: glfwSwapBuffers(self._handle) def _key_callback(window, keycode, scancode, action, option): """ put glfw keyboard event data into active and pressed keyboard buffer """ if action == GLFW_PRESS: self.active_keys.add(GLFW_Context.KEYBOARD_MAP[keycode]) elif action == GLFW_RELEASE: self.active_keys.remove(GLFW_Context.KEYBOARD_MAP[keycode]) def _v2_callback(attr, window, width, height): setattr(self, attr, (width, height)) def _close_callback(*e): self.on_close(self) glfwSetWindowSizeCallback(self._handle, _resize_callback) glfwSetFramebufferSizeCallback(self._handle, partial(_v2_callback, 'resolution')) glfwSetWindowCloseCallback(self._handle, _close_callback) glfwSetKeyCallback(self._handle, _key_callback) glfwSetWindowTitle(self._handle, self.title) self.resolution = glfwGetFramebufferSize(self._handle) self._glfw_initialized = True def make_context(self): """ make the glfw handle the current context and assigns itself to GPUPY_GL.CONTEXT. """ if not self._active: glfwMakeContextCurrent(self._handle) self._active = True GPUPY_GL.CONTEXT = self def __call__(self): """ runs the gl cycle and executed the widget. Returns: - Bool: whether the window should be closed or not """ self.make_context() self._in_cycle = True # GLFW close state if glfwWindowShouldClose(self._handle): self._active = False return False # run widget and close if return value is False self.on_cycle(self) success = self.widget() glfwSwapBuffers(self._handle) self._in_cycle = False if not success: self._active = False return False self._active = True return True
class GLFW_Context(Context): KEYBOARD_MAP = { GLFW_KEY_SPACE: KEY_SPACE, GLFW_KEY_APOSTROPHE: KEY_APOSTROPHE, GLFW_KEY_COMMA: KEY_COMMA, GLFW_KEY_MINUS: KEY_MINUS, GLFW_KEY_PERIOD: KEY_PERIOD, GLFW_KEY_SLASH: KEY_SLASH, GLFW_KEY_0: KEY_0, GLFW_KEY_1: KEY_1, GLFW_KEY_2: KEY_2, GLFW_KEY_3: KEY_3, GLFW_KEY_4: KEY_4, GLFW_KEY_5: KEY_5, GLFW_KEY_6: KEY_6, GLFW_KEY_7: KEY_7, GLFW_KEY_8: KEY_8, GLFW_KEY_9: KEY_9, GLFW_KEY_SEMICOLON: KEY_SEMICOLON, GLFW_KEY_EQUAL: KEY_EQUAL, GLFW_KEY_A: KEY_A, GLFW_KEY_B: KEY_B, GLFW_KEY_C: KEY_C, GLFW_KEY_D: KEY_D, GLFW_KEY_E: KEY_E, GLFW_KEY_F: KEY_F, GLFW_KEY_G: KEY_G, GLFW_KEY_H: KEY_H, GLFW_KEY_I: KEY_I, GLFW_KEY_J: KEY_J, GLFW_KEY_K: KEY_K, GLFW_KEY_L: KEY_L, GLFW_KEY_M: KEY_M, GLFW_KEY_N: KEY_N, GLFW_KEY_O: KEY_O, GLFW_KEY_P: KEY_P, GLFW_KEY_Q: KEY_Q, GLFW_KEY_R: KEY_R, GLFW_KEY_S: KEY_S, GLFW_KEY_T: KEY_T, GLFW_KEY_U: KEY_U, GLFW_KEY_V: KEY_V, GLFW_KEY_W: KEY_W, GLFW_KEY_X: KEY_X, GLFW_KEY_Y: KEY_Y, GLFW_KEY_Z: KEY_Z, GLFW_KEY_LEFT_BRACKET: KEY_LEFT_BRACKET, GLFW_KEY_BACKSLASH: KEY_BACKSLASH, GLFW_KEY_RIGHT_BRACKET: KEY_RIGHT_BRACKET, GLFW_KEY_GRAVE_ACCENT: KEY_GRAVE_ACCENT, GLFW_KEY_WORLD_1: KEY_WORLD_1, GLFW_KEY_WORLD_2: KEY_WORLD_2, GLFW_KEY_ESCAPE: KEY_ESCAPE, GLFW_KEY_ENTER: KEY_ENTER, GLFW_KEY_TAB: KEY_TAB, GLFW_KEY_BACKSPACE: KEY_BACKSPACE, GLFW_KEY_INSERT: KEY_INSERT, GLFW_KEY_DELETE: KEY_DELETE, GLFW_KEY_RIGHT: KEY_RIGHT, GLFW_KEY_LEFT: KEY_LEFT, GLFW_KEY_DOWN: KEY_DOWN, GLFW_KEY_UP: KEY_UP, GLFW_KEY_PAGE_UP: KEY_PAGE_UP, GLFW_KEY_PAGE_DOWN: KEY_PAGE_DOWN, GLFW_KEY_HOME: KEY_HOME, GLFW_KEY_END: KEY_END, GLFW_KEY_CAPS_LOCK: KEY_CAPS_LOCK, GLFW_KEY_SCROLL_LOCK: KEY_SCROLL_LOCK, GLFW_KEY_NUM_LOCK: KEY_NUM_LOCK, GLFW_KEY_PRINT_SCREEN: KEY_PRINT_SCREEN, GLFW_KEY_PAUSE: KEY_PAUSE, GLFW_KEY_F1: KEY_F1, GLFW_KEY_F2: KEY_F2, GLFW_KEY_F3: KEY_F3, GLFW_KEY_F4: KEY_F4, GLFW_KEY_F5: KEY_F5, GLFW_KEY_F6: KEY_F6, GLFW_KEY_F7: KEY_F7, GLFW_KEY_F8: KEY_F8, GLFW_KEY_F9: KEY_F9, GLFW_KEY_F10: KEY_F10, GLFW_KEY_F11: KEY_F11, GLFW_KEY_F12: KEY_F12, GLFW_KEY_F13: KEY_F13, GLFW_KEY_F14: KEY_F14, GLFW_KEY_F15: KEY_F15, GLFW_KEY_F16: KEY_F16, GLFW_KEY_F17: KEY_F17, GLFW_KEY_F18: KEY_F18, GLFW_KEY_F19: KEY_F19, GLFW_KEY_F20: KEY_F20, GLFW_KEY_F21: KEY_F21, GLFW_KEY_F22: KEY_F22, GLFW_KEY_F23: KEY_F23, GLFW_KEY_F24: KEY_F24, GLFW_KEY_F25: KEY_F25, GLFW_KEY_KP_0: KEY_KP_0, GLFW_KEY_KP_1: KEY_KP_1, GLFW_KEY_KP_2: KEY_KP_2, GLFW_KEY_KP_3: KEY_KP_3, GLFW_KEY_KP_4: KEY_KP_4, GLFW_KEY_KP_5: KEY_KP_5, GLFW_KEY_KP_6: KEY_KP_6, GLFW_KEY_KP_7: KEY_KP_7, GLFW_KEY_KP_8: KEY_KP_8, GLFW_KEY_KP_9: KEY_KP_9, GLFW_KEY_KP_DECIMAL: KEY_KP_DECIMAL, GLFW_KEY_KP_DIVIDE: KEY_KP_DIVIDE, GLFW_KEY_KP_MULTIPLY: KEY_KP_MULTIPLY, GLFW_KEY_KP_SUBTRACT: KEY_KP_SUBTRACT, GLFW_KEY_KP_ADD: KEY_KP_ADD, GLFW_KEY_KP_ENTER: KEY_KP_ENTER, GLFW_KEY_KP_EQUAL: KEY_KP_EQUAL, GLFW_KEY_LEFT_SHIFT: KEY_LEFT_SHIFT, GLFW_KEY_LEFT_CONTROL: KEY_LEFT_CONTROL, GLFW_KEY_LEFT_ALT: KEY_LEFT_ALT, GLFW_KEY_LEFT_SUPER: KEY_LEFT_SUPER, GLFW_KEY_RIGHT_SHIFT: KEY_RIGHT_SHIFT, GLFW_KEY_RIGHT_CONTROL: KEY_RIGHT_CONTROL, GLFW_KEY_RIGHT_ALT: KEY_RIGHT_ALT, GLFW_KEY_RIGHT_SUPER: KEY_RIGHT_SUPER, GLFW_KEY_MENU: KEY_MENU, GLFW_KEY_LAST: KEY_LAST, } size = attributes.VectorAttribute(2) resolution = attributes.VectorAttribute(2) position = attributes.VectorAttribute(2) title = attributes.CastedAttribute(str, 'window') visible = attributes.CastedAttribute(bool) """ a glfw context manages the window created by glfwCreateWindow. """ def __init__(self, size, title='no title'): super().__init__() self.size = size self.title = title self.visible = True self._handle = None self._glfw_initialized = False self._active = False def _close_callback(self, *e): self.on_close(self) @visible.on_change def set_visible(self, visible): if visible: glfwShowWindow(self._handle) else: glfwHideWindow(self._handle) @size.on_change def set_size(self, size): glfwSetWindowSize(self._handle, int(size[0]), int(size[1])) def bootstrap(self): if self._glfw_initialized: raise RuntimeError('allready initialized.') self._handle = glfwCreateWindow(int(self.size[0]), int(self.size[1]), self.title) if not self._handle: raise RuntimeError('glfw.CreateWindow() error') glfwWindowHint(GLFW_VISIBLE, int(self.visible)) def _v2_callback(attr, window, width, height): setattr(self, attr, (width, height)) glfwSetWindowSizeCallback(self._handle, self.resize_callback) glfwSetFramebufferSizeCallback(self._handle, partial(_v2_callback, 'resolution')) glfwSetWindowCloseCallback(self._handle, self._close_callback) glfwSetKeyCallback(self._handle, self.key_callback) glfwSetWindowTitle(self._handle, 'wewf') self.resolution = glfwGetFramebufferSize(self._handle) self._glfw_initialized = True def context(self): if glfwWindowShouldClose(self._handle): self.on_close(self) if not self._active: glfwMakeContextCurrent(self._handle) self._active = True super().__gl_context_enable__() def resize_callback(self, window, width, height): """ triggers on_resize event queue and swaps GLFW buffers. """ self.size = (width, height) if len(self.on_resize): # at this point we only make a new context if we are not just # within GLFW_Application.cycle method. E.g. if GLFW_Window.set_size() # was performed within the GLFW_Window.cycle() method. if not self._in_cycle: self.__gl_context_enable__() self.on_resize(self) if not self._in_cycle: glfwSwapBuffers(self._handle) def key_callback(self, window, keycode, scancode, action, option): """ put glfw keyboard event data into active and pressed keyboard buffer """ if action == GLFW_PRESS: self.active_keys.add(GLFW_Context.KEYBOARD_MAP[keycode]) elif action == GLFW_RELEASE: self.active_keys.remove(GLFW_Context.KEYBOARD_MAP[keycode]) def cycle(self): if glfwWindowShouldClose(self._handle): raise CloseContextException() self._in_cycle = True self.__gl_context_enable__() self.on_cycle(self) glfwSwapBuffers(self._handle) self._in_cycle = False def __del__(self): glfwDestroyWindow(self._handle)
class DomainGraph(components.widgets.Widget): """ abstract class for graphs which are using the domain concept for plotting data. """ DEFAULT_DOMAIN_NAME = 'domain' resolution = attributes.VectorAttribute(2, (1, 1)) viewport = attributes.VectorAttribute(2, (1, 1)) def __init__(self, domain=None): """ initializes the graph with one or many domains. argument domain is an dict, it is interpreted as (key, domain) pairs. """ super().__init__() self.domains = OrderedDict() if isinstance(domain, dict): for k, v in domain.items(): self[k] = v elif domain is not None: self[DomainGraph.DEFAULT_DOMAIN_NAME] = domain def __setitem__(self, key, domain): """ adds a domain to the graph """ safe_name(key) domain.requires(list(self.domains.keys())) self.domains[key] = _DomainInfo(domain, 'd_{}'.format(key)) def __getitem__(self, key): """ returns a domain by key """ return self.domains[key].domain def get_domain_glsl_substitutions(self): """ returns a list of tuples (glsl_substitution, glsl_identifier) where glsl_substitution is the name of the substitution e.g. ${name} """ domain_sfnames = {} for dname, domain in self.domains.items(): for field, glsl_id, glsl_meta, glsl_type in domain.glsl_identifier: substkey = 'D.'+dname if field is not None: substkey += '.{}'.format(field) domain_sfnames.update({substkey: glsl_id}) return domain_sfnames def _enable_domain_attrib_pointers(self): """ enable all vertex attribute pointers from domains """ for domain_info in self.domains.values(): domain = domain_info.domain if hasattr(domain, 'attrib_pointers'): domain.attrib_pointers(domain_info.prefix, self.program.attributes)