core/graphics: include fbo in the gl reloading process. standardize add/remove_reload_observers for texture, fbo and context

This commit is contained in:
Mathieu Virbel 2012-03-06 16:49:27 +01:00
parent b85fe03599
commit 15087eba65
8 changed files with 176 additions and 97 deletions

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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():

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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.