From f725b9e7b7a36fe2fbd2293c8fd8232a7491e76d Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 27 Feb 2012 17:29:24 +0100 Subject: [PATCH 01/11] working on the opengl context reloading. first pass, partially working on osx. --- kivy/core/image/__init__.py | 11 ++- kivy/core/image/img_dds.py | 2 +- kivy/core/image/img_pygame.py | 2 +- kivy/core/text/__init__.py | 7 ++ kivy/core/window/__init__.py | 3 + kivy/core/window/window_pygame.py | 1 + kivy/graphics/instructions.pxd | 4 + kivy/graphics/instructions.pyx | 20 +++++ kivy/graphics/opengl_utils.pyx | 25 ++++++ kivy/graphics/shader.pxd | 6 +- kivy/graphics/shader.pyx | 36 ++++++++ kivy/graphics/texture.pxd | 9 ++ kivy/graphics/texture.pyx | 133 ++++++++++++++++++++++++++++-- kivy/graphics/vbo.pxd | 4 + kivy/graphics/vbo.pyx | 23 ++++++ 15 files changed, 274 insertions(+), 12 deletions(-) diff --git a/kivy/core/image/__init__.py b/kivy/core/image/__init__.py index fd8deb88f..cf4be83d2 100644 --- a/kivy/core/image/__init__.py +++ b/kivy/core/image/__init__.py @@ -41,11 +41,11 @@ class ImageData(object): The container will always have at least the mipmap level 0. ''' - __slots__ = ('fmt', 'mipmaps') + __slots__ = ('fmt', 'mipmaps', 'source') _supported_fmts = ('rgb', 'rgba', 'bgr', 'bgra', 's3tc_dxt1', 's3tc_dxt3', 's3tc_dxt5') - def __init__(self, width, height, fmt, data): + def __init__(self, width, height, fmt, data, source=None): assert fmt in ImageData._supported_fmts #: Decoded image format, one of a available texture format @@ -55,6 +55,9 @@ class ImageData(object): self.mipmaps = {} self.add_mipmap(0, width, height, data) + #: Image source, if available + self.source = source + def release_data(self): mm = self.mipmaps for item in mm.itervalues(): @@ -94,8 +97,8 @@ class ImageData(object): return len(self.mipmaps) > 1 def __repr__(self): - return '' % ( - self.width, self.height, self.fmt, len(self.mipmaps)) + return '' % ( + self.width, self.height, self.fmt, self.source, len(self.mipmaps)) def add_mipmap(self, level, width, height, data): '''Add a image for a specific mipmap level. diff --git a/kivy/core/image/img_dds.py b/kivy/core/image/img_dds.py index 4444685db..38034ad2c 100644 --- a/kivy/core/image/img_dds.py +++ b/kivy/core/image/img_dds.py @@ -24,7 +24,7 @@ class ImageLoaderDDS(ImageLoaderBase): self.filename = filename width, height = dds.size - im = ImageData(width, height, dds.dxt, dds.images[0]) + im = ImageData(width, height, dds.dxt, dds.images[0], source=filename) if len(dds.images) > 1: images = dds.images images_size = dds.images_size diff --git a/kivy/core/image/img_pygame.py b/kivy/core/image/img_pygame.py index 4e0723e3b..fa389449c 100644 --- a/kivy/core/image/img_pygame.py +++ b/kivy/core/image/img_pygame.py @@ -61,7 +61,7 @@ class ImageLoaderPygame(ImageLoaderBase): # update internals self.filename = filename data = pygame.image.tostring(im, fmt.upper(), True) - return [ImageData(im.get_width(), im.get_height(), fmt, data)] + return [ImageData(im.get_width(), im.get_height(), fmt, data, source=filename)] # register ImageLoader.register(ImageLoaderPygame) diff --git a/kivy/core/text/__init__.py b/kivy/core/text/__init__.py index 5534d59d7..2ac21a603 100644 --- a/kivy/core/text/__init__.py +++ b/kivy/core/text/__init__.py @@ -404,12 +404,14 @@ class LabelBase(object): else: texture = Texture.create_from_data(data, mipmap=mipmap) texture.flip_vertical() + texture.add_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) ''' # Avoid that for the moment. # The thing is, as soon as we got a region, the blitting is not going in @@ -429,8 +431,13 @@ class LabelBase(object): # If the text is 1px width, usually, the data is black. # Don't blit that kind of data, otherwise, you have a little black bar. if data is not None and data.width > 1: + print 'XXXXXXXXX BLIT DATA ON ', texture texture.blit_data(data) + def _texture_refresh(self, *l): + print 'context reloaded, reupload texture' + self.refresh() + def refresh(self): '''Force re-rendering of the text ''' diff --git a/kivy/core/window/__init__.py b/kivy/core/window/__init__.py index 811be299c..4e23d07b1 100755 --- a/kivy/core/window/__init__.py +++ b/kivy/core/window/__init__.py @@ -601,6 +601,9 @@ class WindowBase(EventDispatcher): def on_resize(self, width, height): '''Event called when the window is resized''' + print '-- on resize', width, height + from kivy.graphics.opengl_utils import gl_reload + gl_reload() self.update_viewport() def update_viewport(self): diff --git a/kivy/core/window/window_pygame.py b/kivy/core/window/window_pygame.py index cea54b3da..48038643f 100644 --- a/kivy/core/window/window_pygame.py +++ b/kivy/core/window/window_pygame.py @@ -49,6 +49,7 @@ class WindowPygame(WindowBase): # reconstruct everything. Check #168 for a state of the work. if platform() == 'linux': self.flags |= pygame.RESIZABLE + self.flags |= pygame.RESIZABLE try: pygame.display.init() diff --git a/kivy/graphics/instructions.pxd b/kivy/graphics/instructions.pxd index 10ae89685..31ef856ec 100644 --- a/kivy/graphics/instructions.pxd +++ b/kivy/graphics/instructions.pxd @@ -12,6 +12,7 @@ from compiler cimport * from shader cimport * from texture cimport Texture +cdef void gl_rcs_reload() cdef void reset_gl_context() cdef class Instruction @@ -88,6 +89,8 @@ cdef class Canvas(CanvasBase): cdef class RenderContext(Canvas): + cdef object __weakref__ + cdef Shader _shader cdef dict state_stacks #cdef TextureManager texture_manager @@ -106,4 +109,5 @@ cdef class RenderContext(Canvas): cdef void leave(self) cdef void apply(self) cpdef draw(self) + cdef void reload(self) diff --git a/kivy/graphics/instructions.pyx b/kivy/graphics/instructions.pyx index 77a09b1a8..44f6c4c81 100644 --- a/kivy/graphics/instructions.pyx +++ b/kivy/graphics/instructions.pyx @@ -19,10 +19,24 @@ from c_opengl cimport * IF USE_OPENGL_DEBUG == 1: from c_opengl_debug cimport * from kivy.logger import Logger +from weakref import ref cdef int _need_reset_gl = 1 cdef int _active_texture = -1 +cdef list rc_list = [] + +cdef void gl_rcs_gc(): + rc_list[:] = [x for x in rc_list if x() is not None] + +cdef void gl_rcs_reload(): + cdef RenderContext rc + gl_rcs_gc() + for item in rc_list: + rc = item() + if not rc: + continue + rc.reload() cdef void reset_gl_context(): global _need_reset_gl, _active_texture @@ -582,6 +596,7 @@ cdef class RenderContext(Canvas): - The state stack (color, texture, matrix...) ''' def __init__(self, *args, **kwargs): + rc_list.append(ref(self)) cdef str key self.bind_texture = dict() Canvas.__init__(self, **kwargs) @@ -684,6 +699,11 @@ cdef class RenderContext(Canvas): popActiveContext() self.flag_update_done() + cdef void reload(self): + pushActiveContext(self) + reset_gl_context() + popActiveContext() + def __setitem__(self, key, val): self.set_state(key, val) diff --git a/kivy/graphics/opengl_utils.pyx b/kivy/graphics/opengl_utils.pyx index cf15647a1..d0353c66a 100644 --- a/kivy/graphics/opengl_utils.pyx +++ b/kivy/graphics/opengl_utils.pyx @@ -9,6 +9,7 @@ __all__ = ('gl_get_extensions', 'gl_has_extension', 'gl_has_capability', 'gl_register_get_size', 'gl_has_texture_format', 'gl_has_texture_conversion', 'gl_has_texture_native_format', 'gl_get_texture_formats', + 'gl_reload', 'GLCAP_BGRA', 'GLCAP_NPOT', 'GLCAP_S3TC', 'GLCAP_DXT1') include "opengl_utils_def.pxi" @@ -186,3 +187,27 @@ cpdef int gl_has_texture_format(str fmt): return 1 # otherwise, check if it can be converted return gl_has_texture_conversion(fmt) + +cdef int gl_context = 1 +from kivy.graphics.vbo cimport gl_vbos_reload +from kivy.graphics.shader cimport gl_shaders_reload +from kivy.graphics.texture cimport gl_textures_reload +from kivy.graphics.instructions cimport gl_rcs_reload + +def gl_reload(): + '''Mark all the current opengl resources as invalid, and reupload + everything. + ''' + global gl_context + gl_context += 1 + + print '--> reload initiated' + print ' > vbos' + gl_vbos_reload() + print ' > shaders' + gl_shaders_reload() + print ' > textures' + gl_textures_reload() + print ' > render contexts' + gl_rcs_reload() + print '<-- reload done' diff --git a/kivy/graphics/shader.pxd b/kivy/graphics/shader.pxd index 718e02552..fc64dd9cf 100644 --- a/kivy/graphics/shader.pxd +++ b/kivy/graphics/shader.pxd @@ -2,6 +2,8 @@ from c_opengl cimport GLuint from transformation cimport Matrix +cdef void gl_shaders_reload() + cdef class ShaderSource: cdef int shader cdef int shadertype @@ -11,6 +13,8 @@ cdef class ShaderSource: cdef int is_compiled(self) cdef class Shader: + cdef object __weakref__ + cdef int _success cdef int program cdef ShaderSource vertex_shader @@ -35,4 +39,4 @@ cdef class Shader: cdef ShaderSource compile_shader(self, char* source, int shadertype) cdef str get_program_log(self, shader) cdef void process_message(self, str ctype, str message) - + cdef void reload(self) diff --git a/kivy/graphics/shader.pyx b/kivy/graphics/shader.pyx index 220c5c339..750d3e60e 100644 --- a/kivy/graphics/shader.pyx +++ b/kivy/graphics/shader.pyx @@ -36,6 +36,7 @@ __all__ = ('Shader', ) include "config.pxi" include "common.pxi" +from weakref import ref from os.path import join from kivy.graphics.c_opengl cimport * IF USE_OPENGL_DEBUG == 1: @@ -51,6 +52,25 @@ cdef str header_vs = open(join(kivy_shader_dir, 'header.vs')).read() cdef str header_fs = open(join(kivy_shader_dir, 'header.fs')).read() cdef str default_vs = open(join(kivy_shader_dir, 'default.vs')).read() cdef str default_fs = open(join(kivy_shader_dir, 'default.fs')).read() +cdef list shader_list = [] + + +cdef gl_shaders_gc(): + # Remove all the shaders not weakref. + shader_list[:] = [x for x in shader_list if x() is not None] + + +cdef void gl_shaders_reload(): + # Force reloading of shaders + cdef Shader shader + Cache.remove('kv.shader') + gl_shaders_gc() + for item in shader_list: + shader = item() + if not shader: + continue + shader.reload() + cdef class ShaderSource: @@ -129,6 +149,7 @@ cdef class Shader: self.uniform_values = dict() def __init__(self, str vs, str fs): + shader_list.append(ref(self)) self.program = glCreateProgram() self.bind_attrib_locations() self.fs = fs @@ -146,6 +167,21 @@ cdef class Shader: glDeleteProgram(self.program) self.program = -1 + cdef void reload(self): + # Note that we don't free previous created shaders. The current reload + # is called only when the gl context is reseted. If we do it, we might + # free newly created shaders (id collision) + glUseProgram(0) + self.vertex_shader = None + self.fragment_shader = None + self.uniform_values = dict() + self.uniform_locations = dict() + self._success = 0 + self.program = glCreateProgram() + self.bind_attrib_locations() + self.fs = self.fs + self.vs = self.vs + cdef void use(self): '''Use the shader ''' diff --git a/kivy/graphics/texture.pxd b/kivy/graphics/texture.pxd index 64daa7116..e28dc71c3 100644 --- a/kivy/graphics/texture.pxd +++ b/kivy/graphics/texture.pxd @@ -1,6 +1,11 @@ from c_opengl cimport GLuint +cdef void gl_textures_reload() + cdef class Texture: + cdef object __weakref__ + + cdef str _source cdef float _tex_coords[8] cdef int _width cdef int _height @@ -12,16 +17,20 @@ cdef class Texture: cdef str _mag_filter cdef int _rectangle cdef bytes _colorfmt + cdef bytes _bufferfmt cdef float _uvx cdef float _uvy cdef float _uvw cdef float _uvh cdef int _is_allocated + cdef int _nofree + cdef list observers cdef update_tex_coords(self) cdef set_min_filter(self, str x) cdef set_mag_filter(self, str x) cdef set_wrap(self, str x) + cdef reload(self) cpdef flip_vertical(self) cpdef get_region(self, x, y, width, height) diff --git a/kivy/graphics/texture.pyx b/kivy/graphics/texture.pyx index ab547ad63..2fe1a1eaa 100644 --- a/kivy/graphics/texture.pyx +++ b/kivy/graphics/texture.pyx @@ -153,8 +153,10 @@ include "common.pxi" include "opengl_utils_def.pxi" from os import environ +from weakref import ref from array import array from kivy.logger import Logger +from kivy.cache import Cache from c_opengl cimport * IF USE_OPENGL_DEBUG == 1: @@ -170,6 +172,54 @@ cdef GLuint GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3 cdef object _texture_release_trigger = None cdef list _texture_release_list = [] +cdef list texture_list = [] + +cdef void gl_textures_gc(): + # Remove all the vbos not weakref + texture_list[:] = [x for x in texture_list if x() is not None] + +cdef void gl_textures_reload(): + # Force reloading of textures + cdef Texture texture + cdef list l + Cache.remove('kv.image') + Cache.remove('kv.texture') + gl_textures_gc() + del _texture_release_list[:] + + # duplicate the current list, new texture might be created + l = texture_list[:] + + # 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 + print '------- texture reload phase 0' + for item in l: + texture = item() + if texture is None: + continue + print ' -', texture + texture._id = -1 + + # First time, only reload base texture + print '------- texture reload phase 1' + for item in l: + texture = item() + if texture is None or isinstance(texture, TextureRegion): + continue + texture.reload() + + # Second time, update texture region id + print '------- texture reload phase 2' + for item in l: + texture = item() + if texture is None or not isinstance(texture, TextureRegion): + continue + texture.reload() + + print '------- texture reload ended' + + cdef dict _gl_color_fmt = { 'rgba': GL_RGBA, 'bgra': GL_BGRA, 'rgb': GL_RGB, 'bgr': GL_BGR, @@ -406,7 +456,7 @@ cdef _texture_create(int width, int height, str colorfmt, str bufferfmt, colorfmt = _convert_gl_format(colorfmt) texture = Texture(texture_width, texture_height, target, texid, - colorfmt=colorfmt, mipmap=mipmap) + colorfmt=colorfmt, bufferfmt=bufferfmt, mipmap=mipmap) # set default parameter for this texture texture.bind() @@ -493,10 +543,12 @@ def texture_create(size=None, colorfmt=None, bufferfmt=None, mipmap=False): def texture_create_from_data(im, mipmap=False): '''Create a texture from an ImageData class ''' + print ' create texture from data', im cdef int width = im.width cdef int height = im.height cdef int allocate = 1 + cdef Texture texture # optimization, if the texture is power of 2, don't allocate in # _texture_create, but allocate in blit_data => only 1 upload @@ -511,6 +563,7 @@ def texture_create_from_data(im, mipmap=False): if texture is None: return None + texture._source = im.source texture.blit_data(im) return texture @@ -524,7 +577,9 @@ cdef class Texture: create_from_data = staticmethod(texture_create_from_data) def __init__(self, width, height, target, texid, colorfmt='rgb', - mipmap=False): + bufferfmt='ubyte', mipmap=False, source=None): + texture_list.append(ref(self)) + self.observers = [] self._width = width self._height = height self._target = target @@ -539,13 +594,17 @@ cdef class Texture: self._uvw = 1. self._uvh = 1. self._colorfmt = colorfmt + self._bufferfmt = bufferfmt + self._source = source + self._nofree = 0 self.update_tex_coords() + print 'create texture', self, texture_list def __dealloc__(self): # Add texture deletion outside GC call. # This case happen if some texture have been not deleted # before application exit... - if _texture_release_list is not None: + if _texture_release_list is not None and self._id != -1 and self._nofree == 0: _texture_release_list.append(self._id) if _texture_release_trigger is not None: _texture_release_trigger() @@ -560,6 +619,16 @@ 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 remove_observer(self, callback): + '''Remove a callback from the observer list. + ''' + self.observers.remove(callback) + cpdef flip_vertical(self): '''Flip tex_coords for vertical displaying''' self._uvy += self._uvh @@ -676,9 +745,38 @@ cdef class Texture: if _mipmap_generation: glGenerateMipmap(target) + cdef reload(self): + cdef Texture texture + print ' - want to reload texture', self.id, self._source + print ' ', isinstance(self, Texture), isinstance(self, TextureRegion) + if self._id != -1: + print ' -< abort, already reloaded.', self._id + return + if self._source is None: + print ' -< Unable to reload this texture automatically, call observers' + # manual texture recreation + texture = texture_create(self.size, self.colorfmt, self.bufferfmt, + self.mipmap) + self._id = texture.id + self._nofree = 1 + # then update content again + for cb in self.observers: + cb(self) + return + print ' reloading...' + from kivy.core.image import Image + image = Image(self._source) + print ' reloading give image', image + print ' reloading give texture', image.texture + self._id = image.texture.id + texture = image.texture + texture._nofree = 1 + print ' reloading give new id', self._id + + def __str__(self): - return '' % ( - self._id, self.width, self.height) + return '' % ( + self._id, self.width, self.height, self._source, self.observers) property size: '''Return the (width, height) of the texture (readonly) @@ -760,6 +858,14 @@ cdef class Texture: def __get__(self): return self._colorfmt + property bufferfmt: + '''Return the buffer format used in this texture. (readonly) + + .. versionadded:: 1.1.2 + ''' + def __get__(self): + return self._bufferfmt + property min_filter: '''Get/set the min filter texture. Available values: @@ -826,6 +932,23 @@ cdef class TextureRegion(Texture): cdef Texture owner def __init__(self, int x, int y, int width, int height, Texture origin): + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' + print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN', origin Texture.__init__(self, width, height, origin.target, origin.id) self._is_allocated = 1 self._mipmap = origin._mipmap diff --git a/kivy/graphics/vbo.pxd b/kivy/graphics/vbo.pxd index f5487f5a3..1a5d069c2 100644 --- a/kivy/graphics/vbo.pxd +++ b/kivy/graphics/vbo.pxd @@ -4,8 +4,11 @@ from vertex cimport vertex_t, vertex_attr_t cdef int vbo_vertex_attr_count() cdef vertex_attr_t *vbo_vertex_attr_list() +cdef void gl_vbos_reload() cdef class VBO: + cdef object __weakref__ + cdef GLuint id cdef int usage cdef int target @@ -22,6 +25,7 @@ cdef class VBO: cdef void add_vertex_data(self, void *v, unsigned short* indices, int count) cdef void update_vertex_data(self, int index, vertex_t* v, int count) cdef void remove_vertex_data(self, unsigned short* indices, int count) + cdef void reload(self) cdef class VertexBatch: diff --git a/kivy/graphics/vbo.pyx b/kivy/graphics/vbo.pyx index 1125e66bb..2fa5f3abe 100644 --- a/kivy/graphics/vbo.pyx +++ b/kivy/graphics/vbo.pyx @@ -11,6 +11,7 @@ __all__ = ('VBO', 'VertexBatch') include "config.pxi" include "common.pxi" +from weakref import ref from os import environ from buffer cimport Buffer from c_opengl cimport * @@ -29,6 +30,22 @@ vattr[1] = ['vTexCoords0', 1, 2, GL_FLOAT, sizeof(GLfloat) * 2, 1] cdef object _vbo_release_trigger = None cdef list _vbo_release_list = [] +cdef list vbo_list = [] + +cdef gl_vbos_gc(): + # Remove all the vbos not weakref. + vbo_list[:] = [x for x in vbo_list if x() is not None] + + +cdef void gl_vbos_reload(): + # Force reloading of vbos + cdef VBO vbo + gl_vbos_gc() + for item in vbo_list: + vbo = item() + if not vbo: + continue + vbo.reload() cdef int vbo_vertex_attr_count(): @@ -42,6 +59,7 @@ cdef vertex_attr_t *vbo_vertex_attr_list(): return vattr cdef class VBO: + def __cinit__(self, **kwargs): self.usage = GL_DYNAMIC_DRAW self.target = GL_ARRAY_BUFFER @@ -59,6 +77,7 @@ cdef class VBO: _vbo_release_trigger() def __init__(self, **kwargs): + vbo_list.append(ref(self)) self.data = Buffer(sizeof(vertex_t)) cdef void allocate_buffer(self): @@ -103,6 +122,10 @@ cdef class VBO: cdef void remove_vertex_data(self, unsigned short* indices, int count): self.data.remove(indices, count) + cdef void reload(self): + self.need_upload = 1 + self.vbo_size = 0 + glGenBuffers(1, &self.id) cdef class VertexBatch: def __init__(self, **kwargs): From d05777e9541de180fa1ee163c9b78148ec228f65 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Sat, 3 Mar 2012 18:45:23 +0100 Subject: [PATCH 02/11] core: got a first version of gl context reloading working. now need to clean and test more. --- kivy/core/text/__init__.py | 1 - kivy/graphics/instructions.pxd | 6 +- kivy/graphics/instructions.pyx | 33 ++++++++-- kivy/graphics/opengl_utils.pyx | 13 ++-- kivy/graphics/shader.pyx | 21 +++--- kivy/graphics/texture.pxd | 17 +++-- kivy/graphics/texture.pyx | 116 ++++++++++++++------------------- kivy/graphics/vbo.pxd | 4 ++ kivy/graphics/vbo.pyx | 21 ++++++ 9 files changed, 135 insertions(+), 97 deletions(-) diff --git a/kivy/core/text/__init__.py b/kivy/core/text/__init__.py index 2ac21a603..4fb11b671 100644 --- a/kivy/core/text/__init__.py +++ b/kivy/core/text/__init__.py @@ -431,7 +431,6 @@ class LabelBase(object): # If the text is 1px width, usually, the data is black. # Don't blit that kind of data, otherwise, you have a little black bar. if data is not None and data.width > 1: - print 'XXXXXXXXX BLIT DATA ON ', texture texture.blit_data(data) def _texture_refresh(self, *l): diff --git a/kivy/graphics/instructions.pxd b/kivy/graphics/instructions.pxd index 31ef856ec..236cd236d 100644 --- a/kivy/graphics/instructions.pxd +++ b/kivy/graphics/instructions.pxd @@ -27,6 +27,7 @@ cdef class Instruction: cdef void flag_update(self, int do_parent=?) cdef void flag_update_done(self) cdef void set_parent(self, Instruction parent) + cdef void reload(self) cdef void radd(self, InstructionGroup ig) cdef void rinsert(self, InstructionGroup ig, int index) @@ -37,6 +38,7 @@ cdef class InstructionGroup(Instruction): cdef InstructionGroup compiled_children cdef GraphicsCompiler compiler cdef void build(self) + cdef void reload(self) cpdef add(self, Instruction c) cpdef insert(self, int index, Instruction c) cpdef remove(self, Instruction c) @@ -80,8 +82,10 @@ cdef class CanvasBase(InstructionGroup): pass cdef class Canvas(CanvasBase): + cdef object __weakref__ cdef CanvasBase _before cdef CanvasBase _after + cdef void reload(self) cpdef clear(self) cpdef add(self, Instruction c) cpdef remove(self, Instruction c) @@ -89,8 +93,6 @@ cdef class Canvas(CanvasBase): cdef class RenderContext(Canvas): - cdef object __weakref__ - cdef Shader _shader cdef dict state_stacks #cdef TextureManager texture_manager diff --git a/kivy/graphics/instructions.pyx b/kivy/graphics/instructions.pyx index 9a7e8647c..59d49cffa 100644 --- a/kivy/graphics/instructions.pyx +++ b/kivy/graphics/instructions.pyx @@ -24,15 +24,15 @@ from weakref import ref cdef int _need_reset_gl = 1 cdef int _active_texture = -1 -cdef list rc_list = [] +cdef list canvas_list = [] cdef void gl_rcs_gc(): - rc_list[:] = [x for x in rc_list if x() is not None] + canvas_list[:] = [x for x in canvas_list if x() is not None] cdef void gl_rcs_reload(): - cdef RenderContext rc + cdef Canvas rc gl_rcs_gc() - for item in rc_list: + for item in canvas_list: rc = item() if not rc: continue @@ -91,6 +91,11 @@ cdef class Instruction: cdef void set_parent(self, Instruction parent): self.parent = parent + cdef void reload(self): + self.flags |= GI_NEEDS_UPDATE + self.flags &= ~GI_NO_APPLY_ONCE + self.flags &= ~GI_IGNORE + property needs_redraw: def __get__(self): if (self.flags & GI_NEEDS_UPDATE) > 0: @@ -187,6 +192,12 @@ cdef class InstructionGroup(Instruction): cdef Instruction c return [c for c in self.children if c.group == groupname] + cdef void reload(self): + Instruction.reload(self) + cdef Instruction c + for c in self.children: + c.reload() + cdef class ContextInstruction(Instruction): '''The ContextInstruction class is the base for the creation of instructions @@ -498,10 +509,22 @@ cdef class Canvas(CanvasBase): ''' def __init__(self, **kwargs): + canvas_list.append(ref(self)) CanvasBase.__init__(self, **kwargs) self._before = None self._after = None + cdef void reload(self): + return + cdef Canvas c + if self._before is not None: + c = self._before + c.reload() + CanvasBase.reload(self) + if self._after is not None: + c = self._after + c.reload() + cpdef clear(self): cdef Instruction c for c in self.children[:]: @@ -599,7 +622,6 @@ cdef class RenderContext(Canvas): - The state stack (color, texture, matrix...) ''' def __init__(self, *args, **kwargs): - rc_list.append(ref(self)) cdef str key self.bind_texture = dict() Canvas.__init__(self, **kwargs) @@ -705,6 +727,7 @@ cdef class RenderContext(Canvas): cdef void reload(self): pushActiveContext(self) reset_gl_context() + Canvas.reload(self) popActiveContext() def __setitem__(self, key, val): diff --git a/kivy/graphics/opengl_utils.pyx b/kivy/graphics/opengl_utils.pyx index d0353c66a..9188105d0 100644 --- a/kivy/graphics/opengl_utils.pyx +++ b/kivy/graphics/opengl_utils.pyx @@ -189,7 +189,7 @@ cpdef int gl_has_texture_format(str fmt): return gl_has_texture_conversion(fmt) cdef int gl_context = 1 -from kivy.graphics.vbo cimport gl_vbos_reload +from kivy.graphics.vbo cimport gl_vbos_reload, gl_batchs_reload from kivy.graphics.shader cimport gl_shaders_reload from kivy.graphics.texture cimport gl_textures_reload from kivy.graphics.instructions cimport gl_rcs_reload @@ -202,12 +202,15 @@ def gl_reload(): gl_context += 1 print '--> reload initiated' - print ' > vbos' - gl_vbos_reload() - print ' > shaders' - gl_shaders_reload() print ' > textures' gl_textures_reload() + print ' > vbos' + gl_vbos_reload() + print ' > vertex batches' + gl_batchs_reload() + print ' > shaders' + gl_shaders_reload() print ' > render contexts' gl_rcs_reload() + print ' > force recompilation.' print '<-- reload done' diff --git a/kivy/graphics/shader.pyx b/kivy/graphics/shader.pyx index 750d3e60e..e050c7208 100644 --- a/kivy/graphics/shader.pyx +++ b/kivy/graphics/shader.pyx @@ -174,7 +174,7 @@ cdef class Shader: glUseProgram(0) self.vertex_shader = None self.fragment_shader = None - self.uniform_values = dict() + #self.uniform_values = dict() self.uniform_locations = dict() self._success = 0 self.program = glCreateProgram() @@ -188,15 +188,16 @@ cdef class Shader: glUseProgram(self.program) for k,v in self.uniform_values.iteritems(): self.upload_uniform(k, v) - # XXX Very very weird bug. On virtualbox / win7 / glew, if we don't call - # glFlush or glFinish or glGetIntegerv(GL_CURRENT_PROGRAM, ...), it seem - # that the pipeline is broken, and we have glitch issue. In order to - # prevent that on possible other hardware, i've (mathieu) prefered to - # include a glFlush here. However, it could be nice to know exactly what - # is going on. Even the glGetIntegerv() is not working here. Broken - # driver on virtualbox / win7 ???? - # FIXME maybe include that instruction for glew usage only. - glFlush() + IF USE_GLEW == 1: + # XXX Very very weird bug. On virtualbox / win7 / glew, if we don't call + # glFlush or glFinish or glGetIntegerv(GL_CURRENT_PROGRAM, ...), it seem + # that the pipeline is broken, and we have glitch issue. In order to + # prevent that on possible other hardware, i've (mathieu) prefered to + # include a glFlush here. However, it could be nice to know exactly what + # is going on. Even the glGetIntegerv() is not working here. Broken + # driver on virtualbox / win7 ???? + # FIXME maybe include that instruction for glew usage only. + glFlush() cdef void stop(self): '''Stop using the shader diff --git a/kivy/graphics/texture.pxd b/kivy/graphics/texture.pxd index e28dc71c3..0d6dff12c 100644 --- a/kivy/graphics/texture.pxd +++ b/kivy/graphics/texture.pxd @@ -5,7 +5,7 @@ cdef void gl_textures_reload() cdef class Texture: cdef object __weakref__ - cdef str _source + cdef object _source cdef float _tex_coords[8] cdef int _width cdef int _height @@ -26,13 +26,18 @@ cdef class Texture: cdef int _nofree cdef list observers - cdef update_tex_coords(self) - cdef set_min_filter(self, str x) - cdef set_mag_filter(self, str x) - cdef set_wrap(self, str x) - cdef reload(self) + cdef void update_tex_coords(self) + cdef void set_min_filter(self, str x) + cdef void set_mag_filter(self, str x) + cdef void set_wrap(self, str x) + cdef void reload(self) cpdef flip_vertical(self) cpdef get_region(self, x, y, width, height) cpdef bind(self) +cdef class TextureRegion(Texture): + cdef int x + cdef int y + cdef Texture owner + cdef void reload(self) diff --git a/kivy/graphics/texture.pyx b/kivy/graphics/texture.pyx index 2fe1a1eaa..fc59deb74 100644 --- a/kivy/graphics/texture.pyx +++ b/kivy/graphics/texture.pyx @@ -170,13 +170,11 @@ cdef GLuint GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1 cdef GLuint GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2 cdef GLuint GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3 -cdef object _texture_release_trigger = None -cdef list _texture_release_list = [] +cdef object texture_release_trigger = None +cdef list texture_release_list = [] cdef list texture_list = [] -cdef void gl_textures_gc(): - # Remove all the vbos not weakref - texture_list[:] = [x for x in texture_list if x() is not None] + cdef void gl_textures_reload(): # Force reloading of textures @@ -184,8 +182,8 @@ cdef void gl_textures_reload(): cdef list l Cache.remove('kv.image') Cache.remove('kv.texture') - gl_textures_gc() - del _texture_release_list[:] + texture_list[:] = [x for x in texture_list if x() is not None] + del texture_release_list[:] # duplicate the current list, new texture might be created l = texture_list[:] @@ -193,16 +191,13 @@ cdef void gl_textures_reload(): # 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 - print '------- texture reload phase 0' for item in l: texture = item() if texture is None: continue - print ' -', texture texture._id = -1 # First time, only reload base texture - print '------- texture reload phase 1' for item in l: texture = item() if texture is None or isinstance(texture, TextureRegion): @@ -210,16 +205,12 @@ cdef void gl_textures_reload(): texture.reload() # Second time, update texture region id - print '------- texture reload phase 2' for item in l: texture = item() if texture is None or not isinstance(texture, TextureRegion): continue texture.reload() - print '------- texture reload ended' - - cdef dict _gl_color_fmt = { 'rgba': GL_RGBA, 'bgra': GL_BGRA, 'rgb': GL_RGB, 'bgr': GL_BGR, @@ -228,17 +219,20 @@ cdef dict _gl_color_fmt = { 's3tc_dxt3': GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 's3tc_dxt5': GL_COMPRESSED_RGBA_S3TC_DXT5_EXT } + cdef dict _gl_buffer_fmt = { 'ubyte': GL_UNSIGNED_BYTE, 'ushort': GL_UNSIGNED_SHORT, 'uint': GL_UNSIGNED_INT, 'byte': GL_BYTE, 'short': GL_SHORT, 'int': GL_INT, 'float': GL_FLOAT } + cdef dict _gl_buffer_size = { 'ubyte': sizeof(GLubyte), 'ushort': sizeof(GLushort), 'uint': sizeof(GLuint), 'byte': sizeof(GLbyte), 'short': sizeof(GLshort), 'int': sizeof(GLint), 'float': sizeof(GLfloat) } + cdef dict _gl_texture_min_filter = { 'nearest': GL_NEAREST, 'linear': GL_LINEAR, 'nearest_mipmap_nearest': GL_NEAREST_MIPMAP_NEAREST, @@ -247,7 +241,6 @@ cdef dict _gl_texture_min_filter = { 'linear_mipmap_linear': GL_LINEAR_MIPMAP_LINEAR } - cdef inline int _nearest_pow2(int v): # From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 # Credit: Sean Anderson @@ -440,7 +433,7 @@ cdef _texture_create(int width, int height, str colorfmt, str bufferfmt, make_npot = 0 # depending if npot is available, use the real size or pot size - if make_npot and gl_has_capability(GLCAP_NPOT): + if make_npot and gl_has_capability(c_GLCAP_NPOT): texture_width = width texture_height = height else: @@ -543,8 +536,6 @@ def texture_create(size=None, colorfmt=None, bufferfmt=None, mipmap=False): def texture_create_from_data(im, mipmap=False): '''Create a texture from an ImageData class ''' - print ' create texture from data', im - cdef int width = im.width cdef int height = im.height cdef int allocate = 1 @@ -598,18 +589,19 @@ cdef class Texture: self._source = source self._nofree = 0 self.update_tex_coords() - print 'create texture', self, texture_list def __dealloc__(self): # Add texture deletion outside GC call. # This case happen if some texture have been not deleted # before application exit... - if _texture_release_list is not None and self._id != -1 and self._nofree == 0: - _texture_release_list.append(self._id) - if _texture_release_trigger is not None: - _texture_release_trigger() + if self.__class__ is not Texture: + return + if texture_release_list is not None and self._id != -1 and self._nofree == 0: + texture_release_list.append(self._id) + if texture_release_trigger is not None: + texture_release_trigger() - cdef update_tex_coords(self): + cdef void update_tex_coords(self): self._tex_coords[0] = self._uvx self._tex_coords[1] = self._uvy self._tex_coords[2] = self._uvx + self._uvw @@ -644,17 +636,17 @@ cdef class Texture: '''Bind the texture to current opengl state''' glBindTexture(self._target, self._id) - cdef set_min_filter(self, str x): + cdef void set_min_filter(self, str x): cdef GLuint _value = _str_to_gl_texture_min_filter(x) glTexParameteri(self._target, GL_TEXTURE_MIN_FILTER, _value) self._min_filter = x - cdef set_mag_filter(self, str x): + cdef void set_mag_filter(self, str x): cdef GLuint _value = _str_to_gl_texture_mag_filter(x) glTexParameteri(self._target, GL_TEXTURE_MAG_FILTER, _value) self._mag_filter = x - cdef set_wrap(self, str x): + cdef void set_wrap(self, str x): cdef GLuint _value = _str_to_gl_texture_wrap(x) glTexParameteri(self._target, GL_TEXTURE_WRAP_S, _value) glTexParameteri(self._target, GL_TEXTURE_WRAP_T, _value) @@ -745,38 +737,34 @@ cdef class Texture: if _mipmap_generation: glGenerateMipmap(target) - cdef reload(self): + cdef void reload(self): cdef Texture texture - print ' - want to reload texture', self.id, self._source - print ' ', isinstance(self, Texture), isinstance(self, TextureRegion) if self._id != -1: - print ' -< abort, already reloaded.', self._id return if self._source is None: - print ' -< Unable to reload this texture automatically, call observers' # manual texture recreation texture = texture_create(self.size, self.colorfmt, self.bufferfmt, self.mipmap) self._id = texture.id - self._nofree = 1 + texture._nofree = 1 # then update content again for cb in self.observers: cb(self) + return - print ' reloading...' from kivy.core.image import Image image = Image(self._source) - print ' reloading give image', image - print ' reloading give texture', image.texture self._id = image.texture.id texture = image.texture texture._nofree = 1 - print ' reloading give new id', self._id - + # then update content again + for cb in self.observers: + cb(self) def __str__(self): - return '' % ( - self._id, self.width, self.height, self._source, self.observers) + return '' % ( + id(self), self._id, self.size, self.colorfmt, self.bufferfmt, + self._source, len(self.observers)) property size: '''Return the (width, height) of the texture (readonly) @@ -927,28 +915,7 @@ cdef class TextureRegion(Texture): '''Handle a region of a Texture class. Useful for non power-of-2 texture handling.''' - cdef int x - cdef int y - cdef Texture owner - def __init__(self, int x, int y, int width, int height, Texture origin): - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN' - print' TEXTURE REGIONNNNNNNNNNNNNNNNNNNNNNNNNNNN', origin Texture.__init__(self, width, height, origin.target, origin.id) self._is_allocated = 1 self._mipmap = origin._mipmap @@ -967,20 +934,33 @@ cdef class TextureRegion(Texture): self.update_tex_coords() def __str__(self): - return '' % ( - self._id, self.width, self.height) + return '' % ( + self.owner, id(self), self._id, self.size, self.colorfmt, + self.bufferfmt, self._source, len(self.observers)) + + cdef void reload(self): + # texture region are reloaded _after_ normal texture + # so that could work, except if it's a region of region + # it's safe to retrigger a reload, since the owner texture will be not + # really reloaded if its id is not -1. + self.owner.reload() + self._id = self.owner.id + + # then update content again + for cb in self.observers: + cb(self) # Releasing texture through GC is problematic def _texture_release(*largs): cdef GLuint texture_id - if not _texture_release_list: + if not texture_release_list: return - Logger.trace('Texture: releasing %d textures' % len(_texture_release_list)) - for texture_id in _texture_release_list: + Logger.trace('Texture: releasing %d textures' % len(texture_release_list)) + for texture_id in texture_release_list: glDeleteTextures(1, &texture_id) - del _texture_release_list[:] + del texture_release_list[:] if 'KIVY_DOC_INCLUDE' not in environ: from kivy.clock import Clock - _texture_release_trigger = Clock.create_trigger(_texture_release) + texture_release_trigger = Clock.create_trigger(_texture_release) diff --git a/kivy/graphics/vbo.pxd b/kivy/graphics/vbo.pxd index 7c19e93dc..631941601 100644 --- a/kivy/graphics/vbo.pxd +++ b/kivy/graphics/vbo.pxd @@ -5,6 +5,7 @@ from vertex cimport vertex_t, vertex_attr_t cdef int vbo_vertex_attr_count() cdef vertex_attr_t *vbo_vertex_attr_list() cdef void gl_vbos_reload() +cdef void gl_batchs_reload() cdef class VBO: cdef object __weakref__ @@ -29,6 +30,8 @@ cdef class VBO: cdef class VertexBatch: + cdef object __weakref__ + cdef VBO vbo cdef Buffer elements cdef Buffer vbo_index @@ -48,3 +51,4 @@ cdef class VertexBatch: cdef void set_mode(self, str mode) cdef str get_mode(self) cdef int count(self) + cdef void reload(self) diff --git a/kivy/graphics/vbo.pyx b/kivy/graphics/vbo.pyx index 8fb6043c1..306f8b1da 100644 --- a/kivy/graphics/vbo.pyx +++ b/kivy/graphics/vbo.pyx @@ -31,11 +31,16 @@ vattr[1] = ['vTexCoords0', 1, 2, GL_FLOAT, sizeof(GLfloat) * 2, 1] cdef object _vbo_release_trigger = None cdef list _vbo_release_list = [] cdef list vbo_list = [] +cdef list batch_list = [] cdef gl_vbos_gc(): # Remove all the vbos not weakref. vbo_list[:] = [x for x in vbo_list if x() is not None] +cdef gl_batchs_gc(): + # Remove all the vbos not weakref. + batch_list[:] = [x for x in batch_list if x() is not None] + cdef void gl_vbos_reload(): # Force reloading of vbos @@ -47,6 +52,16 @@ cdef void gl_vbos_reload(): continue vbo.reload() +cdef void gl_batchs_reload(): + # Force reloading of Batchs + cdef VertexBatch batch + gl_batchs_gc() + for item in batch_list: + batch = item() + if not batch: + continue + batch.reload() + cdef int vbo_vertex_attr_count(): '''Return the number of vertex attributes used in VBO @@ -129,6 +144,7 @@ cdef class VBO: cdef class VertexBatch: def __init__(self, **kwargs): + batch_list.append(ref(self)) self.usage = GL_DYNAMIC_DRAW cdef object lushort = sizeof(unsigned short) self.vbo = kwargs.get('vbo') @@ -150,6 +166,11 @@ cdef class VertexBatch: if _vbo_release_trigger is not None: _vbo_release_trigger() + cdef void reload(self): + self.need_upload = 1 + self.elements_size = 0 + glGenBuffers(1, &self.id) + cdef void clear_data(self): # clear old vertices from vbo and then reset index buffer self.vbo.remove_vertex_data(self.vbo_index.pointer(), From d45219d5b868518b9c72bb8d10c12e267fb45d11 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 5 Mar 2012 17:03:23 +0100 Subject: [PATCH 03/11] core/window: better handling of system size. window size can now be changed, opengl is automatically reloaded on the first resize (the first initialization doesn't count.) + fix issue with mouse_up never fired after a resize --- kivy/core/window/__init__.py | 147 ++++++++++++++++++------------ kivy/core/window/window_pygame.py | 73 +++++++-------- kivy/input/providers/mouse.py | 6 ++ 3 files changed, 124 insertions(+), 102 deletions(-) diff --git a/kivy/core/window/__init__.py b/kivy/core/window/__init__.py index c235deeea..7b4e7ec9f 100755 --- a/kivy/core/window/__init__.py +++ b/kivy/core/window/__init__.py @@ -12,13 +12,14 @@ from os.path import join, exists from os import getcwd from kivy.core import core_select_lib +from kivy.clock import Clock from kivy.config import Config from kivy.logger import Logger from kivy.base import EventLoop from kivy.modules import Modules from kivy.event import EventDispatcher from kivy.properties import ListProperty, ObjectProperty, AliasProperty, \ - NumericProperty + NumericProperty, OptionProperty, StringProperty # late import VKeyboard = None @@ -331,13 +332,32 @@ class WindowBase(EventDispatcher): degrees. ''' + def _set_system_size(self, size): + self._size = size + def _get_system_size(self): return self._size - system_size = AliasProperty(_get_system_size, None, bind=('_size', )) + system_size = AliasProperty(_get_system_size, _set_system_size, bind=('_size', )) '''Real size of the window, without taking care of the rotation. ''' + fullscreen = OptionProperty(False, options=(True, False, 'auto', 'fake')) + '''If true, the window will be put in fullscreen mode, "auto". That's mean + the screen size will not change, and use the current one to set the app + fullscreen + + .. versionadded:: 1.1.2 + ''' + + top = NumericProperty(None, allownone=True) + left = NumericProperty(None, allownone=True) + position = OptionProperty('auto', options=['auto', 'custom']) + render_context = ObjectProperty(None) + canvas = ObjectProperty(None) + title = StringProperty('Kivy') + + def __new__(cls, **kwargs): if cls.__instance is None: cls.__instance = EventDispatcher.__new__(cls) @@ -346,12 +366,12 @@ class WindowBase(EventDispatcher): def __init__(self, **kwargs): kwargs.setdefault('force', False) - kwargs.setdefault('config', None) # don't init window 2 times, # except if force is specified - if self.__initialized and not kwargs.get('force'): + if WindowBase.__instance is not None and not kwargs.get('force'): return + self.initialized = False # event subsystem self.register_event_type('on_draw') @@ -371,7 +391,41 @@ class WindowBase(EventDispatcher): self.register_event_type('on_key_up') self.register_event_type('on_dropfile') - super(WindowBase, self).__init__() + # create a trigger for update/create the window when one of window + # property changes + self.trigger_create_window = Clock.create_trigger(self.create_window, -1) + + # set the default window parameter according to the configuration + if 'fullscreen' not in kwargs: + fullscreen = Config.get('graphics', 'fullscreen') + if fullscreen not in ('auto', 'fake'): + fullscreen = fullscreen.lower() in ('true', '1', 'yes', 'yup') + kwargs['fullscreen'] = fullscreen + if 'width' not in kwargs: + kwargs['width'] = Config.getint('graphics', 'width') + if 'height' not in kwargs: + kwargs['height'] = Config.getint('graphics', 'height') + if 'rotation' not in kwargs: + kwargs['rotation'] = Config.getint('graphics', 'rotation') + if 'position' not in kwargs: + kwargs['position'] = Config.get('graphics', 'position', 'auto') + if 'top' in kwargs: + kwargs['position'] = 'custom' + kwargs['top'] = kwargs['top'] + else: + kwargs['top'] = Config.getint('graphics', 'top') + if 'left' in kwargs: + kwargs['position'] = 'custom' + kwargs['left'] = kwargs['left'] + else: + kwargs['left'] = Config.getint('graphics', 'left') + kwargs['_size'] = (kwargs.pop('width'), kwargs.pop('height')) + + super(WindowBase, self).__init__(**kwargs) + + # bind all the properties that need to recreate the window + for prop in ('fullscreen', 'position', 'top', 'left', '_size', 'system_size'): + self.bind(**{prop: self.trigger_create_window}) # init privates self._system_keyboard = Keyboard(window=self) @@ -381,55 +435,10 @@ class WindowBase(EventDispatcher): self.children = [] self.parent = self - # add view - if 'view' in kwargs: - self.add_widget(kwargs.get('view')) - - # get window params, user options before config option - params = {} - - if 'fullscreen' in kwargs: - params['fullscreen'] = kwargs.get('fullscreen') - else: - params['fullscreen'] = Config.get('graphics', 'fullscreen') - if params['fullscreen'] not in ('auto', 'fake'): - params['fullscreen'] = params['fullscreen'].lower() in \ - ('true', '1', 'yes', 'yup') - - if 'width' in kwargs: - params['width'] = kwargs.get('width') - else: - params['width'] = Config.getint('graphics', 'width') - - if 'height' in kwargs: - params['height'] = kwargs.get('height') - else: - params['height'] = Config.getint('graphics', 'height') - - if 'rotation' in kwargs: - params['rotation'] = kwargs.get('rotation') - else: - params['rotation'] = Config.getint('graphics', 'rotation') - - params['position'] = Config.get( - 'graphics', 'position', 'auto') - if 'top' in kwargs: - params['position'] = 'custom' - params['top'] = kwargs.get('top') - else: - params['top'] = Config.getint('graphics', 'top') - - if 'left' in kwargs: - params['position'] = 'custom' - params['left'] = kwargs.get('left') - else: - params['left'] = Config.getint('graphics', 'left') - # before creating the window __import__('kivy.core.gl') # configure the window - self.params = params self.create_window() # attach modules + listener event @@ -441,7 +450,7 @@ class WindowBase(EventDispatcher): self.configure_keyboards() # mark as initialized - self.__initialized = True + self.initialized = True def toggle_fullscreen(self): '''Toggle fullscreen on window''' @@ -451,7 +460,7 @@ class WindowBase(EventDispatcher): '''Close the window''' pass - def create_window(self): + def create_window(self, *largs): '''Will create the main window and configure it. .. warning:: @@ -469,14 +478,32 @@ class WindowBase(EventDispatcher): Again, don't use this method unless you know exactly what you are doing ! ''' - from kivy.core.gl import init_gl - init_gl() + # just to be sure, if the trigger is set, and if this method is manually + # called, unset the trigger + Clock.unschedule(self.create_window) - # create the render context and canvas - from kivy.graphics import RenderContext, Canvas - self.render_context = RenderContext() - self.canvas = Canvas() - self.render_context.add(self.canvas) + print 'create window !!' + if not self.initialized: + from kivy.core.gl import init_gl + init_gl() + + # create the render context and canvas, only the first time. + from kivy.graphics import RenderContext, Canvas + self.render_context = RenderContext() + self.canvas = Canvas() + self.render_context.add(self.canvas) + + else: + # if we get initialized more than once, then reload opengl state + # after the second time. + from kivy.graphics.opengl_utils import gl_reload + gl_reload() + def ask_update(dt): + self.canvas.ask_update() + Clock.schedule_once(ask_update, 0) + + # ensure the gl viewport is correct + self.update_viewport() def on_flip(self): '''Flip between buffers (event)''' @@ -530,7 +557,7 @@ class WindowBase(EventDispatcher): .. versionadded:: 1.0.5 ''' - pass + self.title = title def set_icon(self, filename): '''Set the icon of the window diff --git a/kivy/core/window/window_pygame.py b/kivy/core/window/window_pygame.py index ff3dd2bfa..3218a9119 100644 --- a/kivy/core/window/window_pygame.py +++ b/kivy/core/window/window_pygame.py @@ -32,9 +32,12 @@ glReadPixels = GL_RGBA = GL_UNSIGNED_BYTE = None class WindowPygame(WindowBase): - def create_window(self): - params = self.params + def create_window(self, *largs): + # ensure the mouse is still not up after window creation, otherwise, we + # have some weird bugs + self.dispatch('on_mouse_up', 0, 0, 'all', []) + print 'window create' # force display to show (available only for fullscreen) displayidx = Config.getint('graphics', 'display') if not 'SDL_VIDEO_FULLSCREEN_HEAD' in environ and displayidx != -1: @@ -47,11 +50,12 @@ class WindowPygame(WindowBase): # right now, activate resizable window only on linux. # on window / macosx, the opengl context is lost, and we need to # reconstruct everything. Check #168 for a state of the work. - if platform() == 'linux': + if platform() in ('linux', 'macosx', 'win'): self.flags |= pygame.RESIZABLE self.flags |= pygame.RESIZABLE try: + pygame.display.quit() pygame.display.init() except pygame.error, e: raise CoreCriticalException(e.message) @@ -65,18 +69,17 @@ class WindowPygame(WindowBase): pygame.display.gl_set_attribute(pygame.GL_DEPTH_SIZE, 16) pygame.display.gl_set_attribute(pygame.GL_STENCIL_SIZE, 1) pygame.display.gl_set_attribute(pygame.GL_ALPHA_SIZE, 8) - pygame.display.set_caption('kivy') + pygame.display.set_caption(self.title) - if params['position'] == 'auto': + if self.position == 'auto': self._pos = None - elif params['position'] == 'custom': - self._pos = params['left'], params['top'] + elif self.position == 'custom': + self._pos = self.left, self.top else: raise ValueError('position token in configuration accept only ' '"auto" or "custom"') - self._fullscreenmode = params['fullscreen'] - if self._fullscreenmode == 'fake': + if self.fullscreen == 'fake': Logger.debug('WinPygame: Set window to fake fullscreen mode') self.flags |= pygame.NOFRAME # if no position set, in fake mode, we always need to set the @@ -85,7 +88,7 @@ class WindowPygame(WindowBase): self._pos = (0, 0) environ['SDL_VIDEO_WINDOW_POS'] = '%d,%d' % self._pos - elif self._fullscreenmode: + elif self.fullscreen is True: Logger.debug('WinPygame: Set window to fullscreen mode') self.flags |= pygame.FULLSCREEN @@ -111,10 +114,6 @@ class WindowPygame(WindowBase): except: Logger.exception('Window: cannot set icon') - # init ourself size + setmode - # before calling on_resize - self._size = params['width'], params['height'] - # try to use mode with multisamples try: self._pygame_set_mode() @@ -133,15 +132,16 @@ class WindowPygame(WindowBase): else: raise CoreCriticalException(e.message) + info = pygame.display.Info() + self._size = (info.current_w, info.current_h) + #self.dispatch('on_resize', *self._size) + super(WindowPygame, self).create_window() # set mouse visibility pygame.mouse.set_visible( Config.getboolean('graphics', 'show_cursor')) - # set rotation - self.rotation = params['rotation'] - # if we are on android platform, automaticly create hooks if android: from kivy.support import install_android @@ -151,8 +151,9 @@ class WindowPygame(WindowBase): pygame.display.quit() self.dispatch('on_close') - def set_title(self, title): - pygame.display.set_caption(title) + def on_title(self, instance, value): + if self.initialized: + pygame.display.set_caption(self.title) def set_icon(self, filename): try: @@ -220,6 +221,9 @@ class WindowPygame(WindowBase): if event.buttons == (0, 0, 0): continue x, y = event.pos + self._mouse_x = x + self._mouse_y = y + self._mouse_meta = self.modifiers self.dispatch('on_mouse_move', x, y, self.modifiers) # mouse action @@ -239,6 +243,11 @@ class WindowPygame(WindowBase): eventname = 'on_mouse_down' if event.type == pygame.MOUSEBUTTONUP: eventname = 'on_mouse_up' + self._mouse_x = x + self._mouse_y = y + self._mouse_meta = self.modifiers + self._mouse_btn = btn + self._mouse_down = eventname == 'on_mouse_down' self.dispatch(eventname, x, y, btn, self.modifiers) # keyboard action @@ -261,11 +270,7 @@ class WindowPygame(WindowBase): # video resize elif event.type == pygame.VIDEORESIZE: - self._size = event.size - # don't use trigger here, we want to delay the resize event - cb = self._do_resize - Clock.unschedule(cb) - Clock.schedule_once(cb, .1) + self.system_size = event.size elif event.type == pygame.VIDEOEXPOSE: self.canvas.ask_update() @@ -285,16 +290,7 @@ class WindowPygame(WindowBase): Logger.debug('WinPygame: Unhandled event %s' % str(event)) ''' - def _do_resize(self, dt): - Logger.debug('Window: Resize window to %s' % str(self._size)) - self._pygame_set_mode(self._size) - self.dispatch('on_resize', *self._size) - def mainloop(self): - # don't known why, but pygame required a resize event - # for opengl, before mainloop... window reinit ? - self.dispatch('on_resize', *self.size) - while not EventLoop.quit and EventLoop.status == 'started': try: self._mainloop() @@ -312,22 +308,15 @@ class WindowPygame(WindowBase): # force deletion of window pygame.display.quit() - def _set_size(self, size): - if super(WindowPygame, self)._set_size(size): - self._pygame_set_mode() - return True - size = property(WindowBase._get_size, _set_size) - # # Pygame wrapper # def _pygame_set_mode(self, size=None): + print '>>> pygame set mode', self._size, self.fullscreen, self.flags if size is None: size = self.size - if self._fullscreenmode == 'auto': + if self.fullscreen == 'auto': pygame.display.set_mode((0, 0), self.flags) - info = pygame.display.Info() - self._size = (info.current_w, info.current_h) else: pygame.display.set_mode(size, self.flags) diff --git a/kivy/input/providers/mouse.py b/kivy/input/providers/mouse.py index f343c1cae..c2b362b00 100644 --- a/kivy/input/providers/mouse.py +++ b/kivy/input/providers/mouse.py @@ -181,6 +181,12 @@ class MouseMotionEventProvider(MotionEventProvider): return True def on_mouse_release(self, win, x, y, button, modifiers): + # special case, if button is all, then remove all the current mouses. + if button == 'all': + for cur in self.touches.values()[:]: + self.remove_touch(cur) + self.current_drag = None + width, height = EventLoop.window.system_size rx = x / float(width) ry = 1. - y / float(height) From edd1d2a5eeae2488304b972e957067271a71e1a5 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Mon, 5 Mar 2012 17:28:49 +0100 Subject: [PATCH 04/11] core/window: remove some debugs --- kivy/core/window/__init__.py | 20 ++++++++++++-------- kivy/core/window/window_pygame.py | 5 ++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/kivy/core/window/__init__.py b/kivy/core/window/__init__.py index 7b4e7ec9f..3936c9995 100755 --- a/kivy/core/window/__init__.py +++ b/kivy/core/window/__init__.py @@ -20,6 +20,7 @@ from kivy.modules import Modules from kivy.event import EventDispatcher from kivy.properties import ListProperty, ObjectProperty, AliasProperty, \ NumericProperty, OptionProperty, StringProperty +from kivy.utils import platform # late import VKeyboard = None @@ -482,7 +483,6 @@ class WindowBase(EventDispatcher): # called, unset the trigger Clock.unschedule(self.create_window) - print 'create window !!' if not self.initialized: from kivy.core.gl import init_gl init_gl() @@ -496,11 +496,18 @@ class WindowBase(EventDispatcher): else: # if we get initialized more than once, then reload opengl state # after the second time. + # XXX check how it's working on embed platform. from kivy.graphics.opengl_utils import gl_reload - gl_reload() - def ask_update(dt): - self.canvas.ask_update() - Clock.schedule_once(ask_update, 0) + if platform() == 'linux': + # on linux, it's safe for just sending a resize. + self.dispatch('on_resize', *self.system_size) + + else: + # on other platform, window are recreated, we need to reload. + gl_reload() + def ask_update(dt): + self.canvas.ask_update() + Clock.schedule_once(ask_update, 0) # ensure the gl viewport is correct self.update_viewport() @@ -631,9 +638,6 @@ class WindowBase(EventDispatcher): def on_resize(self, width, height): '''Event called when the window is resized''' - print '-- on resize', width, height - from kivy.graphics.opengl_utils import gl_reload - gl_reload() self.update_viewport() def update_viewport(self): diff --git a/kivy/core/window/window_pygame.py b/kivy/core/window/window_pygame.py index 3218a9119..fa6a3de30 100644 --- a/kivy/core/window/window_pygame.py +++ b/kivy/core/window/window_pygame.py @@ -37,7 +37,6 @@ class WindowPygame(WindowBase): # have some weird bugs self.dispatch('on_mouse_up', 0, 0, 'all', []) - print 'window create' # force display to show (available only for fullscreen) displayidx = Config.getint('graphics', 'display') if not 'SDL_VIDEO_FULLSCREEN_HEAD' in environ and displayidx != -1: @@ -270,7 +269,8 @@ class WindowPygame(WindowBase): # video resize elif event.type == pygame.VIDEORESIZE: - self.system_size = event.size + self._size = event.size + self.update_viewport() elif event.type == pygame.VIDEOEXPOSE: self.canvas.ask_update() @@ -312,7 +312,6 @@ class WindowPygame(WindowBase): # Pygame wrapper # def _pygame_set_mode(self, size=None): - print '>>> pygame set mode', self._size, self.fullscreen, self.flags if size is None: size = self.size if self.fullscreen == 'auto': From b85fe035993a8c2698232546e343645dda012d6e Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Tue, 6 Mar 2012 02:31:40 +0100 Subject: [PATCH 05/11] graphics/context: introduce a new Context singleton manager. It's responsible for managing resources for texture, vbo, shaders, canvas. Deallocation are going though manual delayed GC. Reloading are managed on Window needs. + It's mainly used to get the reloading code in one place, and reduce the complexity from each graphics class. + create vbo resources only when drawing, not at creation. --- kivy/core/window/__init__.py | 4 +- kivy/graphics/context.pxd | 29 ++++++ kivy/graphics/context.pyx | 185 +++++++++++++++++++++++++++++++++ kivy/graphics/fbo.pyx | 33 +++--- kivy/graphics/instructions.pxd | 1 - kivy/graphics/instructions.pyx | 16 +-- kivy/graphics/opengl_utils.pyx | 27 ----- kivy/graphics/shader.pxd | 2 - kivy/graphics/shader.pyx | 33 +----- kivy/graphics/texture.pxd | 2 - kivy/graphics/texture.pyx | 71 +------------ kivy/graphics/vbo.pxd | 9 +- kivy/graphics/vbo.pyx | 150 +++++++++++--------------- setup.py | 1 + 14 files changed, 308 insertions(+), 255 deletions(-) create mode 100644 kivy/graphics/context.pxd create mode 100644 kivy/graphics/context.pyx diff --git a/kivy/core/window/__init__.py b/kivy/core/window/__init__.py index 3936c9995..af6b6e707 100755 --- a/kivy/core/window/__init__.py +++ b/kivy/core/window/__init__.py @@ -497,14 +497,14 @@ class WindowBase(EventDispatcher): # if we get initialized more than once, then reload opengl state # after the second time. # XXX check how it's working on embed platform. - from kivy.graphics.opengl_utils import gl_reload if platform() == 'linux': # on linux, it's safe for just sending a resize. self.dispatch('on_resize', *self.system_size) else: # on other platform, window are recreated, we need to reload. - gl_reload() + from kivy.graphics.context import get_context + get_context().reload() def ask_update(dt): self.canvas.ask_update() Clock.schedule_once(ask_update, 0) diff --git a/kivy/graphics/context.pxd b/kivy/graphics/context.pxd new file mode 100644 index 000000000..84239b96d --- /dev/null +++ b/kivy/graphics/context.pxd @@ -0,0 +1,29 @@ +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 + +cdef class Context: + cdef list l_texture + cdef list l_canvas + cdef list l_vbo + cdef list l_vertexbatch + cdef list l_shader + + cdef list lr_texture + cdef list lr_canvas + cdef list lr_vbo + + 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 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 flush(self) + +cpdef Context get_context() diff --git a/kivy/graphics/context.pyx b/kivy/graphics/context.pyx new file mode 100644 index 000000000..978efa2fc --- /dev/null +++ b/kivy/graphics/context.pyx @@ -0,0 +1,185 @@ +''' +Context management +================== + +.. versionadded:: 1.1.2 + +This class handle a register of all graphics instructions created, and the +ability to flush and delete them. +''' + +include "config.pxi" + +from os import environ +from weakref import ref +from kivy.graphics.instructions cimport Canvas +from kivy.graphics.texture cimport Texture, TextureRegion +from kivy.graphics.vbo cimport VBO, VertexBatch +from kivy.logger import Logger +from kivy.clock import Clock +from kivy.graphics.c_opengl cimport * +from time import time +IF USE_OPENGL_DEBUG == 1: + from kivy.graphics.c_opengl_debug cimport * +from kivy.cache import Cache + +cdef Context context = None + +cdef class Context: + + def __init__(self): + self.l_texture = [] + self.l_canvas = [] + self.l_vbo = [] + self.l_vertexbatch = [] + self.l_shader = [] + self.lr_texture = [] + self.lr_canvas = [] + self.lr_vbo = [] + self.trigger_gl_dealloc = Clock.create_trigger(self.gl_dealloc, 0) + + cdef void register_texture(self, Texture texture): + self.l_texture.append(ref(texture)) + + cdef void register_canvas(self, Canvas canvas): + self.l_canvas.append(ref(canvas)) + + cdef void register_vbo(self, VBO vbo): + self.l_vbo.append(ref(vbo)) + + cdef void register_vertexbatch(self, VertexBatch vb): + self.l_vertexbatch.append(ref(vb)) + + cdef void register_shader(self, Shader shader): + self.l_shader.append(ref(shader)) + + cdef void dealloc_texture(self, Texture texture): + if texture._nofree or texture.__class__ is TextureRegion: + return + self.lr_texture.append(texture.id) + self.trigger_gl_dealloc() + + cdef void dealloc_vbo(self, VBO vbo): + if vbo.have_id(): + self.lr_vbo.append(vbo.id) + self.trigger_gl_dealloc() + + cdef void dealloc_vertexbatch(self, VertexBatch batch): + if batch.have_id(): + self.lr_vbo.append(batch.id) + self.trigger_gl_dealloc() + + cdef void dealloc_shader(self, Shader shader): + if shader.program == -1: + return + if shader.vertex_shader is not None: + glDetachShader(shader.program, shader.vertex_shader.shader) + if shader.fragment_shader is not None: + glDetachShader(shader.program, shader.fragment_shader.shader) + glDeleteProgram(shader.program) + + cdef void flush(self): + self.lr_texture = [] + self.lr_canvas = [] + self.lr_vbo = [] + + def reload(self): + cdef VBO vbo + cdef VertexBatch batch + cdef Texture texture + 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') + + + # 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') + cdef list l = self.l_texture[:] + for item in l: + texture = item() + if texture is None: + continue + texture._id = -1 + + # First time, only reload base texture + for item in l: + texture = item() + if texture is None or isinstance(texture, TextureRegion): + continue + texture.reload() + + # Second time, update texture region id + for item in l: + texture = item() + if texture is None or not isinstance(texture, TextureRegion): + continue + texture.reload() + + Logger.info('Context: Reload vbos') + for item in self.l_vbo[:]: + vbo = item() + if vbo is not None: + Logger.info('Context: reloaded %r' % item()) + vbo.reload() + Logger.info('Context: Reload vertex batchs') + for item in self.l_vertexbatch[:]: + batch = item() + if batch is not None: + Logger.info('Context: reloaded %r' % item()) + batch.reload() + Logger.info('Context: Reload shaders') + for item in self.l_shader[:]: + shader = item() + if shader is not None: + Logger.info('Context: reloaded %r' % item()) + shader.reload() + Logger.info('Context: Reload canvas') + for item in self.l_canvas[:]: + canvas = item() + if canvas is not None: + Logger.info('Context: reloaded %r' % item()) + canvas.reload() + + glFinish() + dt = time() - start + Logger.info('Context: Reloading done in %2.4fs' % dt) + + + def gc(self, *largs): + self.l_texture = [x for x in self.l_texture if x() is not None] + self.l_canvas = [x for x in self.l_canvas if x() is not None] + self.l_vbo = [x for x in self.l_vbo if x() is not None] + self.l_vertexbatch = [x for x in self.l_vertexbatch if x() is not None] + + def gl_dealloc(self, *largs): + # dealloc all gl resources asynchronously + cdef GLuint i + if len(self.lr_vbo): + Logger.info('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)) + while len(self.lr_texture): + i = self.lr_texture.pop() + glDeleteTextures(1, &i) + + +cpdef Context get_context(): + global context + if context is None: + context = Context() + return context + diff --git a/kivy/graphics/fbo.pyx b/kivy/graphics/fbo.pyx index 48479a580..493e383ae 100644 --- a/kivy/graphics/fbo.pyx +++ b/kivy/graphics/fbo.pyx @@ -55,9 +55,8 @@ IF USE_OPENGL_DEBUG == 1: from instructions cimport RenderContext, Canvas cdef list fbo_stack = [0] -cdef object _fbo_release_trigger = None -cdef list _fbo_release_list = [] - +cdef object fbo_release_trigger = None +cdef list fbo_release_list = [] cdef class Fbo(RenderContext): @@ -133,19 +132,19 @@ cdef class Fbo(RenderContext): 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() + 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() cdef void 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() + 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() self._buffer_id = -1 self._depthbuffer_id = -1 @@ -310,19 +309,19 @@ cdef class Fbo(RenderContext): return self._texture # Releasing fbo through GC is problematic. Same as any GL deletion. -def _fbo_release(*largs): +def fbo_release(*largs): cdef GLuint fbo_id, render_id - if not _fbo_release_list: + if not fbo_release_list: return - Logger.trace('FBO: releasing %d fbos' % len(_fbo_release_list)) - for l in _fbo_release_list: + 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[:] + del fbo_release_list[:] if 'KIVY_DOC_INCLUDE' not in environ: from kivy.clock import Clock - _fbo_release_trigger = Clock.create_trigger(_fbo_release) + fbo_release_trigger = Clock.create_trigger(fbo_release) diff --git a/kivy/graphics/instructions.pxd b/kivy/graphics/instructions.pxd index 236cd236d..1bda49dfe 100644 --- a/kivy/graphics/instructions.pxd +++ b/kivy/graphics/instructions.pxd @@ -12,7 +12,6 @@ from compiler cimport * from shader cimport * from texture cimport Texture -cdef void gl_rcs_reload() cdef void reset_gl_context() cdef class Instruction diff --git a/kivy/graphics/instructions.pyx b/kivy/graphics/instructions.pyx index 59d49cffa..e86891d9a 100644 --- a/kivy/graphics/instructions.pyx +++ b/kivy/graphics/instructions.pyx @@ -19,25 +19,13 @@ from c_opengl cimport * IF USE_OPENGL_DEBUG == 1: from c_opengl_debug cimport * from kivy.logger import Logger -from weakref import ref +from kivy.graphics.context cimport get_context cdef int _need_reset_gl = 1 cdef int _active_texture = -1 cdef list canvas_list = [] -cdef void gl_rcs_gc(): - canvas_list[:] = [x for x in canvas_list if x() is not None] - -cdef void gl_rcs_reload(): - cdef Canvas rc - gl_rcs_gc() - for item in canvas_list: - rc = item() - if not rc: - continue - rc.reload() - cdef void reset_gl_context(): global _need_reset_gl, _active_texture _need_reset_gl = 0 @@ -509,7 +497,7 @@ cdef class Canvas(CanvasBase): ''' def __init__(self, **kwargs): - canvas_list.append(ref(self)) + get_context().register_canvas(self) CanvasBase.__init__(self, **kwargs) self._before = None self._after = None diff --git a/kivy/graphics/opengl_utils.pyx b/kivy/graphics/opengl_utils.pyx index 9188105d0..ea2209fbc 100644 --- a/kivy/graphics/opengl_utils.pyx +++ b/kivy/graphics/opengl_utils.pyx @@ -9,7 +9,6 @@ __all__ = ('gl_get_extensions', 'gl_has_extension', 'gl_has_capability', 'gl_register_get_size', 'gl_has_texture_format', 'gl_has_texture_conversion', 'gl_has_texture_native_format', 'gl_get_texture_formats', - 'gl_reload', 'GLCAP_BGRA', 'GLCAP_NPOT', 'GLCAP_S3TC', 'GLCAP_DXT1') include "opengl_utils_def.pxi" @@ -188,29 +187,3 @@ cpdef int gl_has_texture_format(str fmt): # otherwise, check if it can be converted return gl_has_texture_conversion(fmt) -cdef int gl_context = 1 -from kivy.graphics.vbo cimport gl_vbos_reload, gl_batchs_reload -from kivy.graphics.shader cimport gl_shaders_reload -from kivy.graphics.texture cimport gl_textures_reload -from kivy.graphics.instructions cimport gl_rcs_reload - -def gl_reload(): - '''Mark all the current opengl resources as invalid, and reupload - everything. - ''' - global gl_context - gl_context += 1 - - print '--> reload initiated' - print ' > textures' - gl_textures_reload() - print ' > vbos' - gl_vbos_reload() - print ' > vertex batches' - gl_batchs_reload() - print ' > shaders' - gl_shaders_reload() - print ' > render contexts' - gl_rcs_reload() - print ' > force recompilation.' - print '<-- reload done' diff --git a/kivy/graphics/shader.pxd b/kivy/graphics/shader.pxd index fc64dd9cf..ec67c1496 100644 --- a/kivy/graphics/shader.pxd +++ b/kivy/graphics/shader.pxd @@ -2,8 +2,6 @@ from c_opengl cimport GLuint from transformation cimport Matrix -cdef void gl_shaders_reload() - cdef class ShaderSource: cdef int shader cdef int shadertype diff --git a/kivy/graphics/shader.pyx b/kivy/graphics/shader.pyx index e050c7208..7299e8b57 100644 --- a/kivy/graphics/shader.pyx +++ b/kivy/graphics/shader.pyx @@ -36,7 +36,6 @@ __all__ = ('Shader', ) include "config.pxi" include "common.pxi" -from weakref import ref from os.path import join from kivy.graphics.c_opengl cimport * IF USE_OPENGL_DEBUG == 1: @@ -44,6 +43,7 @@ IF USE_OPENGL_DEBUG == 1: from kivy.graphics.vertex cimport vertex_attr_t from kivy.graphics.vbo cimport vbo_vertex_attr_list, vbo_vertex_attr_count from kivy.graphics.transformation cimport Matrix +from kivy.graphics.context cimport get_context from kivy.logger import Logger from kivy.cache import Cache from kivy import kivy_shader_dir @@ -52,24 +52,6 @@ cdef str header_vs = open(join(kivy_shader_dir, 'header.vs')).read() cdef str header_fs = open(join(kivy_shader_dir, 'header.fs')).read() cdef str default_vs = open(join(kivy_shader_dir, 'default.vs')).read() cdef str default_fs = open(join(kivy_shader_dir, 'default.fs')).read() -cdef list shader_list = [] - - -cdef gl_shaders_gc(): - # Remove all the shaders not weakref. - shader_list[:] = [x for x in shader_list if x() is not None] - - -cdef void gl_shaders_reload(): - # Force reloading of shaders - cdef Shader shader - Cache.remove('kv.shader') - gl_shaders_gc() - for item in shader_list: - shader = item() - if not shader: - continue - shader.reload() cdef class ShaderSource: @@ -149,23 +131,14 @@ cdef class Shader: self.uniform_values = dict() def __init__(self, str vs, str fs): - shader_list.append(ref(self)) + get_context().register_shader(self) self.program = glCreateProgram() self.bind_attrib_locations() self.fs = fs self.vs = vs def __dealloc__(self): - if self.program == -1: - return - if self.vertex_shader is not None: - glDetachShader(self.program, self.vertex_shader.shader) - self.vertex_shader = None - if self.fragment_shader is not None: - glDetachShader(self.program, self.fragment_shader.shader) - self.fragment_shader = None - glDeleteProgram(self.program) - self.program = -1 + get_context().dealloc_shader(self) cdef void reload(self): # Note that we don't free previous created shaders. The current reload diff --git a/kivy/graphics/texture.pxd b/kivy/graphics/texture.pxd index 0d6dff12c..8afae6272 100644 --- a/kivy/graphics/texture.pxd +++ b/kivy/graphics/texture.pxd @@ -1,7 +1,5 @@ from c_opengl cimport GLuint -cdef void gl_textures_reload() - cdef class Texture: cdef object __weakref__ diff --git a/kivy/graphics/texture.pyx b/kivy/graphics/texture.pyx index fc59deb74..441e19d71 100644 --- a/kivy/graphics/texture.pyx +++ b/kivy/graphics/texture.pyx @@ -153,10 +153,10 @@ include "common.pxi" include "opengl_utils_def.pxi" from os import environ -from weakref import ref from array import array from kivy.logger import Logger from kivy.cache import Cache +from kivy.graphics.context cimport get_context from c_opengl cimport * IF USE_OPENGL_DEBUG == 1: @@ -170,48 +170,6 @@ cdef GLuint GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1 cdef GLuint GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2 cdef GLuint GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3 -cdef object texture_release_trigger = None -cdef list texture_release_list = [] -cdef list texture_list = [] - - - -cdef void gl_textures_reload(): - # Force reloading of textures - cdef Texture texture - cdef list l - Cache.remove('kv.image') - Cache.remove('kv.texture') - texture_list[:] = [x for x in texture_list if x() is not None] - del texture_release_list[:] - - # duplicate the current list, new texture might be created - l = texture_list[:] - - # 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 - for item in l: - texture = item() - if texture is None: - continue - texture._id = -1 - - # First time, only reload base texture - for item in l: - texture = item() - if texture is None or isinstance(texture, TextureRegion): - continue - texture.reload() - - # Second time, update texture region id - for item in l: - texture = item() - if texture is None or not isinstance(texture, TextureRegion): - continue - texture.reload() - - cdef dict _gl_color_fmt = { 'rgba': GL_RGBA, 'bgra': GL_BGRA, 'rgb': GL_RGB, 'bgr': GL_BGR, 'luminance': GL_LUMINANCE, 'luminance_alpha': GL_LUMINANCE_ALPHA, @@ -411,7 +369,7 @@ cdef inline void _gl_prepare_pixels_upload(int width) nogil: glPixelStorei(GL_UNPACK_ALIGNMENT, 1) -cdef _texture_create(int width, int height, str colorfmt, str bufferfmt, +cdef Texture _texture_create(int width, int height, str colorfmt, str bufferfmt, int mipmap, int allocate): '''Create the OpenGL texture. ''' @@ -569,7 +527,7 @@ cdef class Texture: def __init__(self, width, height, target, texid, colorfmt='rgb', bufferfmt='ubyte', mipmap=False, source=None): - texture_list.append(ref(self)) + get_context().register_texture(self) self.observers = [] self._width = width self._height = height @@ -594,12 +552,7 @@ cdef class Texture: # Add texture deletion outside GC call. # This case happen if some texture have been not deleted # before application exit... - if self.__class__ is not Texture: - return - if texture_release_list is not None and self._id != -1 and self._nofree == 0: - texture_release_list.append(self._id) - if texture_release_trigger is not None: - texture_release_trigger() + get_context().dealloc_texture(self) cdef void update_tex_coords(self): self._tex_coords[0] = self._uvx @@ -761,7 +714,7 @@ cdef class Texture: for cb in self.observers: cb(self) - def __str__(self): + def __repr__(self): return '' % ( id(self), self._id, self.size, self.colorfmt, self.bufferfmt, self._source, len(self.observers)) @@ -950,17 +903,3 @@ cdef class TextureRegion(Texture): for cb in self.observers: cb(self) -# Releasing texture through GC is problematic -def _texture_release(*largs): - cdef GLuint texture_id - if not texture_release_list: - return - Logger.trace('Texture: releasing %d textures' % len(texture_release_list)) - for texture_id in texture_release_list: - glDeleteTextures(1, &texture_id) - del texture_release_list[:] - -if 'KIVY_DOC_INCLUDE' not in environ: - from kivy.clock import Clock - texture_release_trigger = Clock.create_trigger(_texture_release) - diff --git a/kivy/graphics/vbo.pxd b/kivy/graphics/vbo.pxd index 631941601..995632a64 100644 --- a/kivy/graphics/vbo.pxd +++ b/kivy/graphics/vbo.pxd @@ -4,8 +4,6 @@ from vertex cimport vertex_t, vertex_attr_t cdef int vbo_vertex_attr_count() cdef vertex_attr_t *vbo_vertex_attr_list() -cdef void gl_vbos_reload() -cdef void gl_batchs_reload() cdef class VBO: cdef object __weakref__ @@ -16,10 +14,9 @@ cdef class VBO: cdef vertex_attr_t *format cdef int format_count cdef Buffer data - cdef int need_upload + cdef short flags cdef int vbo_size - cdef void allocate_buffer(self) cdef void update_buffer(self) cdef void bind(self) cdef void unbind(self) @@ -27,6 +24,7 @@ cdef class VBO: cdef void update_vertex_data(self, int index, vertex_t* v, int count) cdef void remove_vertex_data(self, unsigned short* indices, int count) cdef void reload(self) + cdef int have_id(self) cdef class VertexBatch: @@ -39,8 +37,8 @@ cdef class VertexBatch: cdef str mode_str cdef GLuint id cdef int usage + cdef short flags cdef int elements_size - cdef int need_upload cdef void clear_data(self) cdef void set_data(self, vertex_t *vertices, int vertices_count, @@ -52,3 +50,4 @@ cdef class VertexBatch: cdef str get_mode(self) cdef int count(self) cdef void reload(self) + cdef int have_id(self) diff --git a/kivy/graphics/vbo.pyx b/kivy/graphics/vbo.pyx index 306f8b1da..ec2834893 100644 --- a/kivy/graphics/vbo.pyx +++ b/kivy/graphics/vbo.pyx @@ -11,7 +11,6 @@ __all__ = ('VBO', 'VertexBatch') include "config.pxi" include "common.pxi" -from weakref import ref from os import environ from buffer cimport Buffer from c_opengl cimport * @@ -19,6 +18,7 @@ IF USE_OPENGL_DEBUG == 1: from c_opengl_debug cimport * from vertex cimport * from kivy.logger import Logger +from kivy.graphics.context cimport Context, get_context cdef int vattr_count = 2 cdef vertex_attr_t vattr[2] @@ -28,41 +28,6 @@ vattr[1] = ['vTexCoords0', 1, 2, GL_FLOAT, sizeof(GLfloat) * 2, 1] #vertex_attr_list[3] = ['vTexCoords2', 3, 2, GL_FLOAT, sizeof(GLfloat) * 2, 1] #vertex_attr_list[4] = ['vColor', 4, 2, GL_FLOAT, sizeof(GLfloat) * 4, 0] -cdef object _vbo_release_trigger = None -cdef list _vbo_release_list = [] -cdef list vbo_list = [] -cdef list batch_list = [] - -cdef gl_vbos_gc(): - # Remove all the vbos not weakref. - vbo_list[:] = [x for x in vbo_list if x() is not None] - -cdef gl_batchs_gc(): - # Remove all the vbos not weakref. - batch_list[:] = [x for x in batch_list if x() is not None] - - -cdef void gl_vbos_reload(): - # Force reloading of vbos - cdef VBO vbo - gl_vbos_gc() - for item in vbo_list: - vbo = item() - if not vbo: - continue - vbo.reload() - -cdef void gl_batchs_reload(): - # Force reloading of Batchs - cdef VertexBatch batch - gl_batchs_gc() - for item in batch_list: - batch = item() - if not batch: - continue - batch.reload() - - cdef int vbo_vertex_attr_count(): '''Return the number of vertex attributes used in VBO ''' @@ -73,6 +38,10 @@ cdef vertex_attr_t *vbo_vertex_attr_list(): ''' return vattr +cdef short V_NEEDGEN = 1 << 0 +cdef short V_NEEDUPLOAD = 1 << 1 +cdef short V_HAVEID = 1 << 2 + cdef class VBO: def __cinit__(self, **kwargs): @@ -80,35 +49,38 @@ cdef class VBO: self.target = GL_ARRAY_BUFFER self.format = vbo_vertex_attr_list() self.format_count = vbo_vertex_attr_count() - self.need_upload = 1 + self.flags = V_NEEDGEN | V_NEEDUPLOAD self.vbo_size = 0 - glGenBuffers(1, &self.id) def __dealloc__(self): - # Add texture deletion outside GC call. - if _vbo_release_list is not None: - _vbo_release_list.append(self.id) - if _vbo_release_trigger is not None: - _vbo_release_trigger() + get_context().dealloc_vbo(self) def __init__(self, **kwargs): - vbo_list.append(ref(self)) + get_context().register_vbo(self) self.data = Buffer(sizeof(vertex_t)) - cdef void allocate_buffer(self): - #Logger.trace("VBO:allocating VBO " + str(self.data.size())) - self.vbo_size = self.data.size() - glBindBuffer(GL_ARRAY_BUFFER, self.id) - glBufferData(GL_ARRAY_BUFFER, self.vbo_size, self.data.pointer(), self.usage) - self.need_upload = 0 + cdef int have_id(self): + return self.flags & V_HAVEID cdef void update_buffer(self): + # generate VBO if not done yet + if self.flags & V_NEEDGEN: + glGenBuffers(1, &self.id) + self.flags &= ~V_NEEDGEN + self.flags |= V_HAVEID + + # if the size doesn't match, we need to reupload the whole data if self.vbo_size < self.data.size(): - self.allocate_buffer() - elif self.need_upload: + self.vbo_size = self.data.size() + glBindBuffer(GL_ARRAY_BUFFER, self.id) + glBufferData(GL_ARRAY_BUFFER, self.vbo_size, self.data.pointer(), self.usage) + self.flags &= ~V_NEEDUPLOAD + + # if size match, update only what is needed + elif self.flags & V_NEEDUPLOAD: glBindBuffer(GL_ARRAY_BUFFER, self.id) glBufferSubData(GL_ARRAY_BUFFER, 0, self.data.size(), self.data.pointer()) - self.need_upload = 0 + self.flags &= ~V_NEEDUPLOAD cdef void bind(self): cdef vertex_attr_t *attr @@ -127,24 +99,28 @@ cdef class VBO: glBindBuffer(GL_ARRAY_BUFFER, 0) cdef void add_vertex_data(self, void *v, unsigned short* indices, int count): - self.need_upload = 1 + self.flags |= V_NEEDUPLOAD self.data.add(v, indices, count) cdef void update_vertex_data(self, int index, vertex_t* v, int count): - self.need_upload = 1 + self.flags |= V_NEEDUPLOAD self.data.update(index, v, count) cdef void remove_vertex_data(self, unsigned short* indices, int count): self.data.remove(indices, count) cdef void reload(self): - self.need_upload = 1 + self.flags = V_NEEDUPLOAD & V_NEEDGEN self.vbo_size = 0 - glGenBuffers(1, &self.id) + + def __repr__(self): + return '' % ( + id(self), self.id if self.flags & V_HAVEID else None, + self.data.count(), self.data.size()) cdef class VertexBatch: def __init__(self, **kwargs): - batch_list.append(ref(self)) + get_context().register_vertexbatch(self) self.usage = GL_DYNAMIC_DRAW cdef object lushort = sizeof(unsigned short) self.vbo = kwargs.get('vbo') @@ -153,23 +129,20 @@ cdef class VertexBatch: self.vbo_index = Buffer(lushort) #index of every vertex in the vbo self.elements = Buffer(lushort) #indices translated to vbo indices self.elements_size = 0 - self.need_upload = 1 - glGenBuffers(1, &self.id) + self.flags = V_NEEDGEN | V_NEEDUPLOAD self.set_data(NULL, 0, NULL, 0) self.set_mode(kwargs.get('mode')) def __dealloc__(self): - # Add texture deletion outside GC call. - if _vbo_release_list is not None: - _vbo_release_list.append(self.id) - if _vbo_release_trigger is not None: - _vbo_release_trigger() + get_context().dealloc_vertexbatch(self) + + cdef int have_id(self): + return self.flags & V_HAVEID cdef void reload(self): - self.need_upload = 1 + self.flags = V_NEEDGEN | V_NEEDUPLOAD self.elements_size = 0 - glGenBuffers(1, &self.id) cdef void clear_data(self): # clear old vertices from vbo and then reset index buffer @@ -186,7 +159,7 @@ cdef class VertexBatch: # now append the vertices and indices to vbo self.append_data(vertices, vertices_count, indices, indices_count) - self.need_upload = 1 + self.flags |= V_NEEDUPLOAD cdef void append_data(self, vertex_t *vertices, int vertices_count, unsigned short *indices, int indices_count): @@ -206,12 +179,21 @@ cdef class VertexBatch: for i in xrange(indices_count): local_index = indices[i] self.elements.add(&vbi[local_index], NULL, 1) - self.need_upload = 1 + self.flags |= V_NEEDUPLOAD cdef void draw(self): + # create when needed + if self.flags & V_NEEDGEN: + glGenBuffers(1, &self.id) + self.flags &= ~V_NEEDGEN + self.flags |= V_HAVEID + + + # bind to the current id glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.id) + # cache indices in a gpu buffer too - if self.need_upload: + if self.flags & V_NEEDUPLOAD: if self.elements_size == self.elements.size(): glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, self.elements_size, self.elements.pointer()) @@ -219,16 +201,14 @@ cdef class VertexBatch: glBufferData(GL_ELEMENT_ARRAY_BUFFER, self.elements.size(), self.elements.pointer(), self.usage) self.elements_size = self.elements.size() - self.need_upload = 0 + self.flags &= ~V_NEEDUPLOAD + self.vbo.bind() + + # draw the elements pointed by indices in ELEMENT ARRAY BUFFER. glDrawElements(self.mode, self.elements.count(), GL_UNSIGNED_SHORT, NULL) - # XXX if the user is doing its own things, Callback instruction will - # reset the context. - # self.vbo.unbind() - # glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0) - cdef void set_mode(self, str mode): # most common case in top; self.mode_str = mode @@ -255,16 +235,8 @@ cdef class VertexBatch: cdef int count(self): return self.elements.count() -# Releasing vbo through GC is problematic. Same as any GL deletion. -def _vbo_release(*largs): - cdef GLuint vbo_id - if not _vbo_release_list: - return - Logger.trace('VBO: releasing %d vbos' % len(_vbo_release_list)) - for vbo_id in _vbo_release_list: - glDeleteBuffers(1, &vbo_id) - del _vbo_release_list[:] - -if 'KIVY_DOC_INCLUDE' not in environ: - from kivy.clock import Clock - _vbo_release_trigger = Clock.create_trigger(_vbo_release) + def __repr__(self): + return '' % ( + id(self), self.id if self.flags & V_HAVEID else None, + self.elements.count(), self.elements.size(), self.get_mode(), + id(self.vbo)) diff --git a/setup.py b/setup.py index febca3e4d..a0f7e02ad 100644 --- a/setup.py +++ b/setup.py @@ -203,6 +203,7 @@ sources = { '_event.pyx': base_flags, 'properties.pyx': base_flags, 'graphics/buffer.pyx': base_flags, + 'graphics/context.pyx': merge(base_flags, gl_flags, graphics_flags), 'graphics/c_opengl_debug.pyx': merge(base_flags, gl_flags, graphics_flags), 'graphics/compiler.pyx': merge(base_flags, gl_flags, graphics_flags), 'graphics/context_instructions.pyx': merge(base_flags, gl_flags, graphics_flags), From 15087eba65fb81f7ed4225d523cb34b3e8f52931 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Tue, 6 Mar 2012 16:49:27 +0100 Subject: [PATCH 06/11] core/graphics: include fbo in the gl reloading process. standardize add/remove_reload_observers for texture, fbo and context --- kivy/core/text/__init__.py | 4 +- kivy/graphics/common.pxi | 2 + kivy/graphics/context.pxd | 10 ++- kivy/graphics/context.pyx | 91 ++++++++++++++++++------- kivy/graphics/fbo.pxd | 2 + kivy/graphics/fbo.pyx | 98 ++++++++++++++++----------- kivy/graphics/texture.pyx | 47 ++++++++----- kivy/graphics/vertex_instructions.pyx | 19 ++---- 8 files changed, 176 insertions(+), 97 deletions(-) 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. From 7fd74fa43145b703a25a8bf68d9eb9500e63cfd9 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Tue, 6 Mar 2012 19:49:15 +0100 Subject: [PATCH 07/11] core/window: fix maximize/restore size (clicking twice on the maximum button didnt restore the previous size. this fix works on windows, but not on osx) --- kivy/core/window/window_pygame.py | 1 - 1 file changed, 1 deletion(-) diff --git a/kivy/core/window/window_pygame.py b/kivy/core/window/window_pygame.py index fa6a3de30..4fd326380 100644 --- a/kivy/core/window/window_pygame.py +++ b/kivy/core/window/window_pygame.py @@ -54,7 +54,6 @@ class WindowPygame(WindowBase): self.flags |= pygame.RESIZABLE try: - pygame.display.quit() pygame.display.init() except pygame.error, e: raise CoreCriticalException(e.message) From ca7714281b304dcf7ea8ec50a2d1bd6719d25887 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 8 Mar 2012 01:10:41 +0100 Subject: [PATCH 08/11] core/graphics: fix missing attributes not copied when reloaded (min_filter, mag_filter and wrap) + fix vbo crash (invalid flags set while reloading.) --- kivy/core/text/__init__.py | 1 - kivy/graphics/context.pyx | 15 ++++++++------- kivy/graphics/texture.pyx | 33 +++++++++++++++++++-------------- kivy/graphics/vbo.pyx | 2 +- 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/kivy/core/text/__init__.py b/kivy/core/text/__init__.py index 6179fb1b8..3471cff63 100644 --- a/kivy/core/text/__init__.py +++ b/kivy/core/text/__init__.py @@ -434,7 +434,6 @@ class LabelBase(object): texture.blit_data(data) def _texture_refresh(self, *l): - print 'context reloaded, reupload texture' self.refresh() def refresh(self): diff --git a/kivy/graphics/context.pyx b/kivy/graphics/context.pyx index c4280b59b..8d80be7c7 100644 --- a/kivy/graphics/context.pyx +++ b/kivy/graphics/context.pyx @@ -119,6 +119,7 @@ cdef class Context: cdef Shader shader cdef Canvas canvas + Cache.remove('kv.atlas') Cache.remove('kv.image') Cache.remove('kv.texture') Cache.remove('kv.shader') @@ -158,25 +159,25 @@ cdef class Context: for item in self.l_vbo[:]: vbo = item() if vbo is not None: - Logger.debug('Context: reloaded %r' % item()) + Logger.trace('Context: reloaded %r' % item()) vbo.reload() Logger.debug('Context: Reload vertex batchs') for item in self.l_vertexbatch[:]: batch = item() if batch is not None: - Logger.debug('Context: reloaded %r' % item()) + Logger.trace('Context: reloaded %r' % item()) batch.reload() Logger.debug('Context: Reload shaders') for item in self.l_shader[:]: shader = item() if shader is not None: - Logger.debug('Context: reloaded %r' % item()) + Logger.trace('Context: reloaded %r' % item()) shader.reload() Logger.debug('Context: Reload canvas') for item in self.l_canvas[:]: canvas = item() if canvas is not None: - Logger.debug('Context: reloaded %r' % item()) + Logger.trace('Context: reloaded %r' % item()) canvas.reload() # call reload observers that want to do something after a whole gpu @@ -202,18 +203,18 @@ cdef class Context: # dealloc all gl resources asynchronously cdef GLuint i, j if len(self.lr_vbo): - Logger.debug('Context: releasing %d vbos' % len(self.lr_vbo)) + Logger.trace('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.debug('Context: releasing %d textures: %r' % ( + Logger.trace('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)) + Logger.trace('Context: releasing %d fbos' % len(self.lr_fbo)) while len(self.lr_fbo): i, j = self.lr_fbo.pop() if i != -1: diff --git a/kivy/graphics/texture.pyx b/kivy/graphics/texture.pyx index 4308fd937..9c1f0326c 100644 --- a/kivy/graphics/texture.pyx +++ b/kivy/graphics/texture.pyx @@ -711,23 +711,28 @@ cdef class Texture: texture = texture_create(self.size, self.colorfmt, self.bufferfmt, self.mipmap) self._id = texture.id + else: + from kivy.core.image import Image + image = Image(self._source) + self._id = image.texture.id + texture = image.texture texture._nofree = 1 - # then update content again - 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 - texture = image.texture + # ensure the new opengl ID will not get through GC texture._nofree = 1 + + # set the same parameters as our current texture + texture.bind() + texture.set_wrap(self.wrap) + texture.set_min_filter(self.min_filter) + texture.set_mag_filter(self.mag_filter) + # 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) def __repr__(self): return '' % ( @@ -901,7 +906,7 @@ cdef class TextureRegion(Texture): self._uvh = (height / origin._height) * origin._uvh self.update_tex_coords() - def __str__(self): + def __repr__(self): return '' % ( self.owner, id(self), self._id, self.size, self.colorfmt, self.bufferfmt, self._source, len(self.observers)) diff --git a/kivy/graphics/vbo.pyx b/kivy/graphics/vbo.pyx index ec2834893..d44bb8ccc 100644 --- a/kivy/graphics/vbo.pyx +++ b/kivy/graphics/vbo.pyx @@ -110,7 +110,7 @@ cdef class VBO: self.data.remove(indices, count) cdef void reload(self): - self.flags = V_NEEDUPLOAD & V_NEEDGEN + self.flags = V_NEEDUPLOAD | V_NEEDGEN self.vbo_size = 0 def __repr__(self): From b316ce3c5a33bc29ef62685a7ea2a27dbe1d082b Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 8 Mar 2012 01:20:08 +0100 Subject: [PATCH 09/11] kivy/config: add resizable token in graphics section, default to 1. If 0, the window will have a static size. --- kivy/config.py | 13 +++++++++++-- kivy/core/window/window_pygame.py | 4 ++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/kivy/config.py b/kivy/config.py index b5f94baff..0fb72b625 100644 --- a/kivy/config.py +++ b/kivy/config.py @@ -37,6 +37,10 @@ Available configuration tokens * windows icons are not copied to user directory anymore. You can still set a new window icon by using ``window_icon`` config setting. +.. versionchanged:: 1.1.2 + + * `resizable` has been added to graphics section + :kivy: `log_level`: (debug, info, warning, error, critical) @@ -114,7 +118,9 @@ Available configuration tokens pygame icon. `rotation`: (0, 90, 180, 270) Rotation of the :class:`~kivy.core.window.Window` - + `resizable`: (0, 1) + If 0, the window will have a fixed size. If 1, the window will be + resizable. :input: @@ -171,7 +177,7 @@ from kivy.logger import Logger from kivy.utils import OrderedDict # Version number of current configuration format -KIVY_CONFIG_VERSION = 5 +KIVY_CONFIG_VERSION = 6 #: Kivy configuration object Config = None @@ -369,6 +375,9 @@ if not 'KIVY_DOC_INCLUDE' in environ: Config.setdefault('kivy', 'keyboard_mode', '') Config.setdefault('kivy', 'keyboard_layout', 'qwerty') + elif version == 5: + Config.setdefault('graphics', 'resizable', '1') + #elif version == 1: # # add here the command for upgrading from configuration 0 to 1 # diff --git a/kivy/core/window/window_pygame.py b/kivy/core/window/window_pygame.py index 4fd326380..b651c6aa9 100644 --- a/kivy/core/window/window_pygame.py +++ b/kivy/core/window/window_pygame.py @@ -49,9 +49,9 @@ class WindowPygame(WindowBase): # right now, activate resizable window only on linux. # on window / macosx, the opengl context is lost, and we need to # reconstruct everything. Check #168 for a state of the work. - if platform() in ('linux', 'macosx', 'win'): + if platform() in ('linux', 'macosx', 'win') and \ + Config.getint('graphics', 'resizable'): self.flags |= pygame.RESIZABLE - self.flags |= pygame.RESIZABLE try: pygame.display.init() From c77224386e1bdcf75eabd79d7be01cd7a43fa7f6 Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 8 Mar 2012 01:59:52 +0100 Subject: [PATCH 10/11] core/graphics: add documentation about gl reloading, and examples about how to reload generated texture and fbo --- kivy/graphics/__init__.py | 43 +++++++++++++++++++++++++++++++++++++++ kivy/graphics/context.pyx | 2 ++ kivy/graphics/fbo.pyx | 26 +++++++++++++++++++++++ kivy/graphics/texture.pyx | 32 +++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) diff --git a/kivy/graphics/__init__.py b/kivy/graphics/__init__.py index b7314009f..6241b6bad 100644 --- a/kivy/graphics/__init__.py +++ b/kivy/graphics/__init__.py @@ -28,6 +28,49 @@ creating a widget, you can create all the instructions needed for drawing. If The instructions :class:`Color` and :class:`Rectangle` are automaticly added to the canvas object, and will be used when the window drawing will happen. +GL Reloading mechanism +---------------------- + +.. versionadded:: 1.1.2 + +During the lifetime of the application, the OpenGL context might be lost. This +is happening when: + + - Window is resized, on MacOSX and Windows platform, if you're using pygame + as window provider, cause of SDL 1.2. In the SDL 1.2 design, it need to + recreate a GL context everytime the window is resized. This is fixed in + SDL 1.3, but pygame is not available on it by default yet. + + - Android release the app resources: when your application goes to + background, android system might reclaim your opengl context to give the + resource to another app. When the user switch back to your application, a + newly gl context is given to you. + +Starting from 1.1.2, we introduced a mechanism for reloading all the graphics +resources using the GPU: Canvas, FBO, Shader, Texture, VBO, VertexBatch: + + - VBO and VertexBatch are constructed by our graphics instructions. We have + all the data to reconstruct when reloading. + + - Shader: same as VBO, we store the source and values used in the shader, we + are able to recreate the vertex/fragment/program. + + - Texture: if the texture have a source (an image file, an atlas...), the + image is reloaded from the source, and reuploaded to the GPU. + +You should cover theses cases yourself: + + - Texture without source: if you manually created a texture, and manually + blit data / buffer to it, you must handle the reloading yourself. Check + the :doc:`api-kivy.graphics.texture` to learn how to manage that case. + (The text rendering is generating the texture, and handle already the + reloading. You don't need to reload text yourself.) + + - FBO: if you added / removed / drawed things multiple times on the FBO, we + can't reload it. We don't keep a history of the instruction put on it. As + texture without source, Check the :doc:`api-kivy.graphics.fbo` to learn + how to manage that case. + ''' from kivy.graphics.instructions import Callback, Canvas, CanvasBase, \ diff --git a/kivy/graphics/context.pyx b/kivy/graphics/context.pyx index 8d80be7c7..e86a2c95a 100644 --- a/kivy/graphics/context.pyx +++ b/kivy/graphics/context.pyx @@ -6,6 +6,8 @@ Context management This class handle a register of all graphics instructions created, and the ability to flush and delete them. + +You can read more about it at :doc:`api-kivy.graphics` ''' include "config.pxi" diff --git a/kivy/graphics/fbo.pyx b/kivy/graphics/fbo.pyx index a147ff69c..f7e28e087 100644 --- a/kivy/graphics/fbo.pyx +++ b/kivy/graphics/fbo.pyx @@ -37,6 +37,32 @@ Exemple of using an fbo for some color rectangles :: If you change anything in the `self.fbo` object, it will be automaticly updated, and canvas where the fbo is putted will be automaticly updated too. + +Reloading the FBO content +------------------------- + +.. versionadded:: 1.1.2 + +If the OpenGL context is lost, then the FBO is lost too. You need to reupload +data on it yourself. Use the :func:`Fbo.add_reload_observer` to add a reloading +function that will be automatically called when needed:: + + def __init__(self, **kwargs): + super(...).__init__(**kwargs) + self.fbo = Fbo(size=(512, 512)) + self.fbo.add_reload_observer(self.populate_fbo) + + # and load the data now. + self.populate_fbo(self.fbo) + + + def populate_fbo(self, fbo): + with fbo: + # .. put your Color / Rectangle / ... here + +This way, you could use the same method for initialization and for reloading. +But it's up to you. + ''' __all__ = ('Fbo', ) diff --git a/kivy/graphics/texture.pyx b/kivy/graphics/texture.pyx index 9c1f0326c..ac3ac9e66 100644 --- a/kivy/graphics/texture.pyx +++ b/kivy/graphics/texture.pyx @@ -144,6 +144,38 @@ information. actually creating the nearest POT texture and generate mipmap on it. This might change in the future. +Reloading the Texture +--------------------- + +.. versionadded:: 1.1.2 + +If the OpenGL context is lost, the Texture must be reloaded. Texture having a +source are automatically reloaded without any help. But generated textures must +be reloaded by the user. + +Use the :func:`Texture.add_reload_observer` to add a reloading function that will be +automatically called when needed:: + + def __init__(self, **kwargs): + super(...).__init__(**kwargs) + self.texture = Texture.create(size=(512, 512), colorfmt='RGB', + bufferfmt='ubyte') + self.texture.add_reload_observer(self.populate_texture) + + # and load the data now. + self.cbuffer = '\x00\xf0\xff' * 512 * 512 + self.populate_texture(self.texture) + + def populate_texture(self, texture): + texture.blit_buffer(self.cbuffer) + +This way, you could use the same method for initialization and for reloading. + +.. note:: + + For all text rendering with our core text renderer, texture is generated, + but we are binding already a method to redo the text rendering and reupload + the text to the texture. You have nothing to do on that case. ''' __all__ = ('Texture', 'TextureRegion') From 610a604bb0427305f94ab4425a4dd00814c933de Mon Sep 17 00:00:00 2001 From: Mathieu Virbel Date: Thu, 8 Mar 2012 02:03:31 +0100 Subject: [PATCH 11/11] core/graphics: typo on graphics --- kivy/graphics/__init__.py | 48 +++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/kivy/graphics/__init__.py b/kivy/graphics/__init__.py index 6241b6bad..693427ebc 100644 --- a/kivy/graphics/__init__.py +++ b/kivy/graphics/__init__.py @@ -34,42 +34,42 @@ GL Reloading mechanism .. versionadded:: 1.1.2 During the lifetime of the application, the OpenGL context might be lost. This -is happening when: +is happening: - - Window is resized, on MacOSX and Windows platform, if you're using pygame - as window provider, cause of SDL 1.2. In the SDL 1.2 design, it need to - recreate a GL context everytime the window is resized. This is fixed in - SDL 1.3, but pygame is not available on it by default yet. +- When window is resized, on MacOSX and Windows platform, if you're using pygame + as window provider, cause of SDL 1.2. In the SDL 1.2 design, it need to + recreate a GL context everytime the window is resized. This is fixed in SDL + 1.3, but pygame is not available on it by default yet. - - Android release the app resources: when your application goes to - background, android system might reclaim your opengl context to give the - resource to another app. When the user switch back to your application, a - newly gl context is given to you. +- when Android release the app resources: when your application goes to + background, android system might reclaim your opengl context to give the + resource to another app. When the user switch back to your application, a + newly gl context is given to you. Starting from 1.1.2, we introduced a mechanism for reloading all the graphics resources using the GPU: Canvas, FBO, Shader, Texture, VBO, VertexBatch: - - VBO and VertexBatch are constructed by our graphics instructions. We have - all the data to reconstruct when reloading. +- VBO and VertexBatch are constructed by our graphics instructions. We have all + the data to reconstruct when reloading. - - Shader: same as VBO, we store the source and values used in the shader, we - are able to recreate the vertex/fragment/program. +- Shader: same as VBO, we store the source and values used in the shader, we are + able to recreate the vertex/fragment/program. - - Texture: if the texture have a source (an image file, an atlas...), the - image is reloaded from the source, and reuploaded to the GPU. +- Texture: if the texture have a source (an image file, an atlas...), the image + is reloaded from the source, and reuploaded to the GPU. You should cover theses cases yourself: - - Texture without source: if you manually created a texture, and manually - blit data / buffer to it, you must handle the reloading yourself. Check - the :doc:`api-kivy.graphics.texture` to learn how to manage that case. - (The text rendering is generating the texture, and handle already the - reloading. You don't need to reload text yourself.) +- Texture without source: if you manually created a texture, and manually blit + data / buffer to it, you must handle the reloading yourself. Check the + :doc:`api-kivy.graphics.texture` to learn how to manage that case. (The text + rendering is generating the texture, and handle already the reloading. You + don't need to reload text yourself.) - - FBO: if you added / removed / drawed things multiple times on the FBO, we - can't reload it. We don't keep a history of the instruction put on it. As - texture without source, Check the :doc:`api-kivy.graphics.fbo` to learn - how to manage that case. +- FBO: if you added / removed / drawed things multiple times on the FBO, we + can't reload it. We don't keep a history of the instruction put on it. As + texture without source, Check the :doc:`api-kivy.graphics.fbo` to learn how to + manage that case. '''