diff --git a/kivy/core/text/__init__.py b/kivy/core/text/__init__.py index 4fb11b671..6179fb1b8 100644 --- a/kivy/core/text/__init__.py +++ b/kivy/core/text/__init__.py @@ -404,14 +404,14 @@ class LabelBase(object): else: texture = Texture.create_from_data(data, mipmap=mipmap) texture.flip_vertical() - texture.add_observer(self._texture_refresh) + texture.add_reload_observer(self._texture_refresh) elif self.width != texture.width or self.height != texture.height: if data is None: texture = Texture.create(size=self.size, mipmap=mipmap) else: texture = Texture.create_from_data(data, mipmap=mipmap) texture.flip_vertical() - texture.add_observer(self._texture_refresh) + texture.add_reload_observer(self._texture_refresh) ''' # Avoid that for the moment. # The thing is, as soon as we got a region, the blitting is not going in diff --git a/kivy/graphics/common.pxi b/kivy/graphics/common.pxi index f973da520..8f45fb089 100644 --- a/kivy/graphics/common.pxi +++ b/kivy/graphics/common.pxi @@ -10,6 +10,7 @@ cdef extern from "math.h": double cos(double) nogil double sin(double) nogil double sqrt(double) nogil + double pow(double x, double y) nogil cdef extern from "stdlib.h": ctypedef unsigned long size_t @@ -20,6 +21,7 @@ cdef extern from "stdlib.h": cdef extern from "string.h": void *memcpy(void *dest, void *src, size_t n) + void *memset(void *dest, int c, size_t len) cdef extern from "Python.h": object PyString_FromStringAndSize(char *s, Py_ssize_t len) diff --git a/kivy/graphics/context.pxd b/kivy/graphics/context.pxd index 84239b96d..cbc8a2e4f 100644 --- a/kivy/graphics/context.pxd +++ b/kivy/graphics/context.pxd @@ -2,28 +2,36 @@ from kivy.graphics.instructions cimport Instruction, Canvas from kivy.graphics.texture cimport Texture from kivy.graphics.vbo cimport VBO, VertexBatch from kivy.graphics.shader cimport Shader +from kivy.graphics.fbo cimport Fbo cdef class Context: + cdef list observers cdef list l_texture cdef list l_canvas cdef list l_vbo cdef list l_vertexbatch cdef list l_shader + cdef list l_fbo cdef list lr_texture cdef list lr_canvas cdef list lr_vbo + cdef list lr_fbo - cdef object trigger_gl_dealloc cdef void register_texture(self, Texture texture) cdef void register_canvas(self, Canvas canvas) cdef void register_vbo(self, VBO vbo) cdef void register_vertexbatch(self, VertexBatch vb) cdef void register_shader(self, Shader shader) + cdef void register_fbo(self, Fbo fbo) + cdef void dealloc_texture(self, Texture texture) cdef void dealloc_vbo(self, VBO vbo) cdef void dealloc_vertexbatch(self, VertexBatch vbo) cdef void dealloc_shader(self, Shader shader) + cdef void dealloc_fbo(self, Fbo fbo) + + cdef object trigger_gl_dealloc cdef void flush(self) cpdef Context get_context() diff --git a/kivy/graphics/context.pyx b/kivy/graphics/context.pyx index 978efa2fc..c4280b59b 100644 --- a/kivy/graphics/context.pyx +++ b/kivy/graphics/context.pyx @@ -18,6 +18,7 @@ from kivy.graphics.vbo cimport VBO, VertexBatch from kivy.logger import Logger from kivy.clock import Clock from kivy.graphics.c_opengl cimport * +from kivy.weakmethod import WeakMethod from time import time IF USE_OPENGL_DEBUG == 1: from kivy.graphics.c_opengl_debug cimport * @@ -28,15 +29,21 @@ cdef Context context = None cdef class Context: def __init__(self): + self.observers = [] self.l_texture = [] self.l_canvas = [] self.l_vbo = [] self.l_vertexbatch = [] self.l_shader = [] + self.l_fbo = [] + self.flush() + self.trigger_gl_dealloc = Clock.create_trigger(self.gl_dealloc, 0) + + cdef void flush(self): self.lr_texture = [] self.lr_canvas = [] self.lr_vbo = [] - self.trigger_gl_dealloc = Clock.create_trigger(self.gl_dealloc, 0) + self.lr_fbo = [] cdef void register_texture(self, Texture texture): self.l_texture.append(ref(texture)) @@ -53,6 +60,9 @@ cdef class Context: cdef void register_shader(self, Shader shader): self.l_shader.append(ref(shader)) + cdef void register_fbo(self, Fbo fbo): + self.l_fbo.append(ref(fbo)) + cdef void dealloc_texture(self, Texture texture): if texture._nofree or texture.__class__ is TextureRegion: return @@ -78,10 +88,29 @@ cdef class Context: glDetachShader(shader.program, shader.fragment_shader.shader) glDeleteProgram(shader.program) - cdef void flush(self): - self.lr_texture = [] - self.lr_canvas = [] - self.lr_vbo = [] + cdef void dealloc_fbo(self, Fbo fbo): + if fbo.buffer_id != -1: + self.lr_fbo.append((fbo.buffer_id, fbo.depthbuffer_id)) + self.trigger_gl_dealloc() + + def add_reload_observer(self, callback): + '''Add a callback to be called after the whole graphics context have + been reloaded. This is where you can reupload your custom data in GPU. + + :Parameters: + `callback`: func(context) -> return None + The first parameter will be the context itself + ''' + self.observers.append(WeakMethod(callback)) + + def remove_reload_observer(self, callback): + '''Remove a callback from the observer list, previously added by + :func:`add_reload_observer`. + ''' + for cb in self.observers[:]: + if cb.is_dead() or cb() is callback: + self.observers.remove(cb) + continue def reload(self): cdef VBO vbo @@ -90,21 +119,20 @@ cdef class Context: cdef Shader shader cdef Canvas canvas - start = time() - Logger.info('Context: Reloading graphics data...') - Logger.info('Context: Collect and flush all garbage') - self.gc() - self.flush() - Cache.remove('kv.image') Cache.remove('kv.texture') Cache.remove('kv.shader') + start = time() + Logger.info('Context: Reloading graphics data...') + Logger.debug('Context: Collect and flush all garbage') + self.gc() + self.flush() # First step, prevent double loading by setting everything to -1 # We do this because texture might be loaded in seperate texture at first, # then merged from the cache cause of the same source - Logger.info('Context: Reload textures') + Logger.debug('Context: Reload textures') cdef list l = self.l_texture[:] for item in l: texture = item() @@ -126,31 +154,39 @@ cdef class Context: continue texture.reload() - Logger.info('Context: Reload vbos') + Logger.debug('Context: Reload vbos') for item in self.l_vbo[:]: vbo = item() if vbo is not None: - Logger.info('Context: reloaded %r' % item()) + Logger.debug('Context: reloaded %r' % item()) vbo.reload() - Logger.info('Context: Reload vertex batchs') + Logger.debug('Context: Reload vertex batchs') for item in self.l_vertexbatch[:]: batch = item() if batch is not None: - Logger.info('Context: reloaded %r' % item()) + Logger.debug('Context: reloaded %r' % item()) batch.reload() - Logger.info('Context: Reload shaders') + Logger.debug('Context: Reload shaders') for item in self.l_shader[:]: shader = item() if shader is not None: - Logger.info('Context: reloaded %r' % item()) + Logger.debug('Context: reloaded %r' % item()) shader.reload() - Logger.info('Context: Reload canvas') + Logger.debug('Context: Reload canvas') for item in self.l_canvas[:]: canvas = item() if canvas is not None: - Logger.info('Context: reloaded %r' % item()) + Logger.debug('Context: reloaded %r' % item()) canvas.reload() + # call reload observers that want to do something after a whole gpu + # reloading. + for callback in self.observers[:]: + if callback.is_dead(): + self.observers.remove(callback) + continue + callback()(self) + glFinish() dt = time() - start Logger.info('Context: Reloading done in %2.4fs' % dt) @@ -164,17 +200,26 @@ cdef class Context: def gl_dealloc(self, *largs): # dealloc all gl resources asynchronously - cdef GLuint i + cdef GLuint i, j if len(self.lr_vbo): - Logger.info('Context: releasing %d vbos' % len(self.lr_vbo)) + Logger.debug('Context: releasing %d vbos' % len(self.lr_vbo)) while len(self.lr_vbo): i = self.lr_vbo.pop() glDeleteBuffers(1, &i) if len(self.lr_texture): - Logger.info('Context: releasing %d textures' % len(self.lr_texture)) + Logger.debug('Context: releasing %d textures: %r' % ( + len(self.lr_texture), self.lr_texture)) while len(self.lr_texture): i = self.lr_texture.pop() glDeleteTextures(1, &i) + if len(self.lr_fbo): + Logger.debug('Context: releasing %d fbos' % len(self.lr_fbo)) + while len(self.lr_fbo): + i, j = self.lr_fbo.pop() + if i != -1: + glDeleteFramebuffers(1, &i) + if j != -1: + glDeleteRenderbuffers(1, &j) cpdef Context get_context(): diff --git a/kivy/graphics/fbo.pxd b/kivy/graphics/fbo.pxd index 5080ee513..4e27ca9f3 100644 --- a/kivy/graphics/fbo.pxd +++ b/kivy/graphics/fbo.pxd @@ -13,6 +13,7 @@ cdef class Fbo(RenderContext): cdef GLint _viewport[4] cdef Texture _texture cdef int _is_bound + cdef list observers cpdef clear_buffer(self) cpdef bind(self) @@ -23,3 +24,4 @@ cdef class Fbo(RenderContext): cdef void apply(self) cdef void raise_exception(self, str message, int status=?) cdef str resolve_status(self, int status) + cdef void reload(self) diff --git a/kivy/graphics/fbo.pyx b/kivy/graphics/fbo.pyx index 493e383ae..a147ff69c 100644 --- a/kivy/graphics/fbo.pyx +++ b/kivy/graphics/fbo.pyx @@ -45,17 +45,18 @@ include "config.pxi" include "opcodes.pxi" from os import environ -from kivy import Logger +from kivy.logger import Logger +from kivy.weakmethod import WeakMethod from kivy.graphics.texture cimport Texture from kivy.graphics.transformation cimport Matrix +from kivy.graphics.context cimport get_context -from c_opengl cimport * +from kivy.graphics.c_opengl cimport * IF USE_OPENGL_DEBUG == 1: - from c_opengl_debug cimport * -from instructions cimport RenderContext, Canvas + from kivy.graphics.c_opengl_debug cimport * +from kivy.graphics.instructions cimport RenderContext, Canvas cdef list fbo_stack = [0] -cdef object fbo_release_trigger = None cdef list fbo_release_list = [] @@ -106,6 +107,8 @@ cdef class Fbo(RenderContext): raise Exception(message) def __init__(self, *args, **kwargs): + get_context().register_fbo(self) + RenderContext.__init__(self, *args, **kwargs) if 'clear_color' not in kwargs: @@ -119,32 +122,25 @@ cdef class Fbo(RenderContext): if 'texture' not in kwargs: kwargs['texture'] = None - self._buffer_id = -1 - self._depthbuffer_id = -1 - self._width, self._height = kwargs['size'] - self.clear_color = kwargs['clear_color'] - self._depthbuffer_attached = int(kwargs['with_depthbuffer']) - self._push_viewport = int(kwargs['push_viewport']) - self._is_bound = 0 - self._texture = kwargs['texture'] + self.buffer_id = -1 + self.depthbuffer_id = -1 + self._width, self._height = kwargs['size'] + self.clear_color = kwargs['clear_color'] + self._depthbuffer_attached = int(kwargs['with_depthbuffer']) + self._push_viewport = int(kwargs['push_viewport']) + self._is_bound = 0 + self._texture = kwargs['texture'] + self.observers = [] self.create_fbo() def __dealloc__(self): - # add fbo deletion outside gc call. - if fbo_release_list is not None: - fbo_release_list.append((self._buffer_id, self._depthbuffer_id)) - if fbo_release_trigger is not None: - fbo_release_trigger() + get_context().dealloc_fbo(self) cdef void delete_fbo(self): + print 'XXXD Delete fbo', self self._texture = None - self._depthbuffer_attached = 0 - # delete in asynchronous way the framebuffers - if fbo_release_list is not None: - fbo_release_list.append((self._buffer_id, self._depthbuffer_id)) - if fbo_release_trigger is not None: - fbo_release_trigger() + get_context().dealloc_fbo(self) self._buffer_id = -1 self._depthbuffer_id = -1 @@ -268,6 +264,43 @@ cdef class Fbo(RenderContext): self.release() self.flag_update_done() + cdef void reload(self): + # recreate the framebuffer, without deleting it. the deletion is not + # handled by us. + self.create_fbo() + self.flag_update() + # notify observers + for callback in self.observers: + if callback.is_dead(): + self.observers.remove(callback) + continue + callback()(self) + + def add_reload_observer(self, callback): + '''Add a callback to be called after the whole graphics context have + been reloaded. This is where you can reupload your custom data in GPU. + + .. versionadded:: 1.1.2 + + :Parameters: + `callback`: func(context) -> return None + The first parameter will be the context itself + ''' + self.observers.append(WeakMethod(callback)) + + def remove_reload_observer(self, callback): + '''Remove a callback from the observer list, previously added by + :func:`add_reload_observer`. + + .. versionadded:: 1.1.2 + + ''' + for cb in self.observers[:]: + if cb.is_dead() or cb() is callback: + self.observers.remove(cb) + continue + + property size: '''Size of the framebuffer, in (width, height) format. @@ -308,20 +341,3 @@ cdef class Fbo(RenderContext): def __get__(self): return self._texture -# Releasing fbo through GC is problematic. Same as any GL deletion. -def fbo_release(*largs): - cdef GLuint fbo_id, render_id - if not fbo_release_list: - return - Logger.trace('FBO: releasing %d fbos' % len(fbo_release_list)) - for l in fbo_release_list: - fbo_id, render_id = l - if fbo_id != -1: - glDeleteFramebuffers(1, &fbo_id) - if render_id != -1: - glDeleteRenderbuffers(1, &render_id) - del fbo_release_list[:] - -if 'KIVY_DOC_INCLUDE' not in environ: - from kivy.clock import Clock - fbo_release_trigger = Clock.create_trigger(fbo_release) diff --git a/kivy/graphics/texture.pyx b/kivy/graphics/texture.pyx index 441e19d71..4308fd937 100644 --- a/kivy/graphics/texture.pyx +++ b/kivy/graphics/texture.pyx @@ -154,14 +154,15 @@ include "opengl_utils_def.pxi" from os import environ from array import array +from kivy.weakmethod import WeakMethod from kivy.logger import Logger from kivy.cache import Cache from kivy.graphics.context cimport get_context -from c_opengl cimport * +from kivy.graphics.c_opengl cimport * IF USE_OPENGL_DEBUG == 1: - from c_opengl_debug cimport * -from opengl_utils cimport * + from kivy.graphics.c_opengl_debug cimport * +from kivy.graphics.opengl_utils cimport * # compatibility layer cdef GLuint GL_BGR = 0x80E0 @@ -549,9 +550,6 @@ cdef class Texture: self.update_tex_coords() def __dealloc__(self): - # Add texture deletion outside GC call. - # This case happen if some texture have been not deleted - # before application exit... get_context().dealloc_texture(self) cdef void update_tex_coords(self): @@ -564,15 +562,29 @@ cdef class Texture: self._tex_coords[6] = self._uvx self._tex_coords[7] = self._uvy + self._uvh - def add_observer(self, callback): - '''Add a callback to be called when the texture need to be recreated. - ''' - self.observers.append(callback) + def add_reload_observer(self, callback): + '''Add a callback to be called after the whole graphics context have + been reloaded. This is where you can reupload your custom data in GPU. - def remove_observer(self, callback): - '''Remove a callback from the observer list. + .. versionadded:: 1.1.2 + + :Parameters: + `callback`: func(context) -> return None + The first parameter will be the context itself ''' - self.observers.remove(callback) + self.observers.append(WeakMethod(callback)) + + def remove_reload_observer(self, callback): + '''Remove a callback from the observer list, previously added by + :func:`add_reload_observer`. + + .. versionadded:: 1.1.2 + + ''' + for cb in self.observers[:]: + if cb.is_dead() or cb() is callback: + self.observers.remove(cb) + continue cpdef flip_vertical(self): '''Flip tex_coords for vertical displaying''' @@ -701,10 +713,13 @@ cdef class Texture: self._id = texture.id texture._nofree = 1 # then update content again - for cb in self.observers: - cb(self) - + for callback in self.observers[:]: + if callback.is_dead(): + self.observers.remove(callback) + continue + callback()(self) return + from kivy.core.image import Image image = Image(self._source) self._id = image.texture.id diff --git a/kivy/graphics/vertex_instructions.pyx b/kivy/graphics/vertex_instructions.pyx index bcd963ebb..6b6cef99d 100644 --- a/kivy/graphics/vertex_instructions.pyx +++ b/kivy/graphics/vertex_instructions.pyx @@ -12,24 +12,15 @@ __all__ = ('Triangle', 'Quad', 'Rectangle', 'BorderImage', 'Ellipse', 'Line', include "config.pxi" include "common.pxi" -from vbo cimport * -from vertex cimport * -from instructions cimport * -from c_opengl cimport * +from kivy.graphics.vbo cimport * +from kivy.graphics.vertex cimport * +from kivy.graphics.instructions cimport * +from kivy.graphics.c_opengl cimport * IF USE_OPENGL_DEBUG == 1: - from c_opengl_debug cimport * + from kivy.graphics.c_opengl_debug cimport * from kivy.logger import Logger from kivy.graphics.texture import Texture -cdef extern from "string.h": - void *memset(void *s, int c, int n) - -cdef extern from "Python.h": - object PyString_FromStringAndSize(char *s, Py_ssize_t len) - -cdef extern from "math.h": - double sqrt(double x) nogil - double pow(double x, double y) nogil class GraphicException(Exception): '''Exception fired when a graphic error is fired.