Merge branch 'core-gl-reload'

This commit is contained in:
Mathieu Virbel 2012-03-08 02:03:50 +01:00
commit 520c11874e
25 changed files with 862 additions and 289 deletions

View File

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

View File

@ -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 '<ImageData width=%d height=%d fmt=%s with %d images>' % (
self.width, self.height, self.fmt, len(self.mipmaps))
return '<ImageData width=%d height=%d fmt=%s source=%r with %d images>' % (
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.

View File

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

View File

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

View File

@ -404,12 +404,14 @@ class LabelBase(object):
else:
texture = Texture.create_from_data(data, mipmap=mipmap)
texture.flip_vertical()
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_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
@ -431,6 +433,9 @@ class LabelBase(object):
if data is not None and data.width > 1:
texture.blit_data(data)
def _texture_refresh(self, *l):
self.refresh()
def refresh(self):
'''Force re-rendering of the text
'''

View File

@ -12,13 +12,15 @@ 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
from kivy.utils import platform
# late import
VKeyboard = None
@ -331,13 +333,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 +367,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 +392,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 +436,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 +451,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 +461,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 +479,38 @@ 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)
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.
# XXX check how it's working on embed platform.
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.
from kivy.graphics.context import get_context
get_context().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 +564,7 @@ class WindowBase(EventDispatcher):
.. versionadded:: 1.0.5
'''
pass
self.title = title
def set_icon(self, filename):
'''Set the icon of the window

View File

@ -32,8 +32,10 @@ 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', [])
# force display to show (available only for fullscreen)
displayidx = Config.getint('graphics', 'display')
@ -47,7 +49,8 @@ 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') and \
Config.getint('graphics', 'resizable'):
self.flags |= pygame.RESIZABLE
try:
@ -64,18 +67,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
@ -84,7 +86,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
@ -110,10 +112,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()
@ -132,15 +130,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
@ -150,8 +149,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:
@ -219,6 +219,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
@ -238,6 +241,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,10 +269,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.update_viewport()
elif event.type == pygame.VIDEOEXPOSE:
self.canvas.ask_update()
@ -284,16 +289,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()
@ -311,22 +307,14 @@ 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):
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)

View File

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

View File

@ -10,6 +10,7 @@ cdef extern from "math.h":
double cos(double) nogil
double sin(double) nogil
double sqrt(double) nogil
double pow(double x, double y) nogil
cdef extern from "stdlib.h":
ctypedef unsigned long size_t
@ -20,6 +21,7 @@ cdef extern from "stdlib.h":
cdef extern from "string.h":
void *memcpy(void *dest, void *src, size_t n)
void *memset(void *dest, int c, size_t len)
cdef extern from "Python.h":
object PyString_FromStringAndSize(char *s, Py_ssize_t len)

37
kivy/graphics/context.pxd Normal file
View File

@ -0,0 +1,37 @@
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 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()

233
kivy/graphics/context.pyx Normal file
View File

@ -0,0 +1,233 @@
'''
Context management
==================
.. versionadded:: 1.1.2
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"
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 kivy.weakmethod import WeakMethod
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.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.lr_fbo = []
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 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
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 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
cdef VertexBatch batch
cdef Texture texture
cdef Shader shader
cdef Canvas canvas
Cache.remove('kv.atlas')
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.debug('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.debug('Context: Reload vbos')
for item in self.l_vbo[:]:
vbo = item()
if vbo is not None:
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.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.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.trace('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)
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, j
if 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.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.trace('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():
global context
if context is None:
context = Context()
return context

View File

@ -13,6 +13,7 @@ cdef class Fbo(RenderContext):
cdef GLint _viewport[4]
cdef Texture _texture
cdef int _is_bound
cdef list observers
cpdef clear_buffer(self)
cpdef bind(self)
@ -23,3 +24,4 @@ cdef class Fbo(RenderContext):
cdef void apply(self)
cdef void raise_exception(self, str message, int status=?)
cdef str resolve_status(self, int status)
cdef void reload(self)

View File

@ -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', )
@ -45,19 +71,19 @@ 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 = []
cdef list fbo_release_list = []
cdef class Fbo(RenderContext):
@ -107,6 +133,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:
@ -120,32 +148,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
@ -269,6 +290,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.
@ -309,20 +367,3 @@ cdef class Fbo(RenderContext):
def __get__(self):
return self._texture
# Releasing fbo through GC is problematic. Same as any GL deletion.
def _fbo_release(*largs):
cdef GLuint fbo_id, render_id
if not _fbo_release_list:
return
Logger.trace('FBO: releasing %d fbos' % len(_fbo_release_list))
for l in _fbo_release_list:
fbo_id, render_id = l
if fbo_id != -1:
glDeleteFramebuffers(1, &fbo_id)
if render_id != -1:
glDeleteRenderbuffers(1, &render_id)
del _fbo_release_list[:]
if 'KIVY_DOC_INCLUDE' not in environ:
from kivy.clock import Clock
_fbo_release_trigger = Clock.create_trigger(_fbo_release)

View File

@ -26,6 +26,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)
@ -36,6 +37,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)
@ -79,8 +81,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)
@ -106,4 +110,5 @@ cdef class RenderContext(Canvas):
cdef void leave(self)
cdef void apply(self)
cpdef draw(self)
cdef void reload(self)

View File

@ -19,10 +19,12 @@ from c_opengl cimport *
IF USE_OPENGL_DEBUG == 1:
from c_opengl_debug cimport *
from kivy.logger import Logger
from kivy.graphics.context cimport get_context
cdef int _need_reset_gl = 1
cdef int _active_texture = -1
cdef list canvas_list = []
cdef void reset_gl_context():
global _need_reset_gl, _active_texture
@ -77,6 +79,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:
@ -173,6 +180,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
@ -484,10 +497,22 @@ cdef class Canvas(CanvasBase):
'''
def __init__(self, **kwargs):
get_context().register_canvas(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[:]:
@ -687,6 +712,12 @@ cdef class RenderContext(Canvas):
popActiveContext()
self.flag_update_done()
cdef void reload(self):
pushActiveContext(self)
reset_gl_context()
Canvas.reload(self)
popActiveContext()
def __setitem__(self, key, val):
self.set_state(key, val)

View File

@ -186,3 +186,4 @@ cpdef int gl_has_texture_format(str fmt):
return 1
# otherwise, check if it can be converted
return gl_has_texture_conversion(fmt)

View File

@ -11,6 +11,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 +37,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)

View File

@ -43,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,6 +53,7 @@ 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 class ShaderSource:
def __cinit__(self, shadertype):
@ -129,22 +131,29 @@ cdef class Shader:
self.uniform_values = dict()
def __init__(self, str vs, str fs):
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
# 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
@ -152,15 +161,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

View File

@ -1,6 +1,9 @@
from c_opengl cimport GLuint
cdef class Texture:
cdef object __weakref__
cdef object _source
cdef float _tex_coords[8]
cdef int _width
cdef int _height
@ -12,18 +15,27 @@ 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 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)

View File

@ -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')
@ -154,12 +186,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
@ -168,9 +203,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 dict _gl_color_fmt = {
'rgba': GL_RGBA, 'bgra': GL_BGRA, 'rgb': GL_RGB, 'bgr': GL_BGR,
'luminance': GL_LUMINANCE, 'luminance_alpha': GL_LUMINANCE_ALPHA,
@ -178,17 +210,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,
@ -197,7 +232,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
@ -368,7 +402,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.
'''
@ -390,7 +424,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:
@ -406,7 +440,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 +527,10 @@ 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
'''
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 +545,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 +559,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):
get_context().register_texture(self)
self.observers = []
self._width = width
self._height = height
self._target = target
@ -539,18 +576,15 @@ 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()
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:
_texture_release_list.append(self._id)
if _texture_release_trigger is not None:
_texture_release_trigger()
get_context().dealloc_texture(self)
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
@ -560,6 +594,30 @@ cdef class Texture:
self._tex_coords[6] = self._uvx
self._tex_coords[7] = self._uvy + self._uvh
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
cpdef flip_vertical(self):
'''Flip tex_coords for vertical displaying'''
self._uvy += self._uvh
@ -575,17 +633,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)
@ -676,9 +734,42 @@ cdef class Texture:
if _mipmap_generation:
glGenerateMipmap(target)
def __str__(self):
return '<Texture id=%d size=(%d, %d)>' % (
self._id, self.width, self.height)
cdef void reload(self):
cdef Texture texture
if self._id != -1:
return
if self._source is None:
# manual texture recreation
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
# 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 callback in self.observers[:]:
if callback.is_dead():
self.observers.remove(callback)
continue
callback()(self)
def __repr__(self):
return '<Texture hash=%r id=%d size=%r colorfmt=%r bufferfmt=%r source=%r observers=%d>' % (
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)
@ -760,6 +851,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:
@ -821,10 +920,6 @@ 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):
Texture.__init__(self, width, height, origin.target, origin.id)
self._is_allocated = 1
@ -843,21 +938,20 @@ cdef class TextureRegion(Texture):
self._uvh = (height / <float>origin._height) * origin._uvh
self.update_tex_coords()
def __str__(self):
return '<TextureRegion id=%d size=(%d, %d)>' % (
self._id, self.width, self.height)
def __repr__(self):
return '<TextureRegion of %r hash=%r id=%d size=%r colorfmt=%r bufferfmt=%r source=%r observers=%d>' % (
self.owner, id(self), self._id, self.size, self.colorfmt,
self.bufferfmt, self._source, len(self.observers))
# 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[:]
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
if 'KIVY_DOC_INCLUDE' not in environ:
from kivy.clock import Clock
_texture_release_trigger = Clock.create_trigger(_texture_release)
# then update content again
for cb in self.observers:
cb(self)

View File

@ -6,25 +6,30 @@ cdef int vbo_vertex_attr_count()
cdef vertex_attr_t *vbo_vertex_attr_list()
cdef class VBO:
cdef object __weakref__
cdef GLuint id
cdef int usage
cdef int target
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)
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 int have_id(self)
cdef class VertexBatch:
cdef object __weakref__
cdef VBO vbo
cdef Buffer elements
cdef Buffer vbo_index
@ -32,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,
@ -44,3 +49,5 @@ cdef class VertexBatch:
cdef void set_mode(self, str mode)
cdef str get_mode(self)
cdef int count(self)
cdef void reload(self)
cdef int have_id(self)

View File

@ -18,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]
@ -27,10 +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 int vbo_vertex_attr_count():
'''Return the number of vertex attributes used in VBO
'''
@ -41,40 +38,49 @@ 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):
self.usage = GL_DYNAMIC_DRAW
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):
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
@ -93,19 +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.flags = V_NEEDUPLOAD | V_NEEDGEN
self.vbo_size = 0
def __repr__(self):
return '<VBO at %x id=%r count=%d size=%d>' % (
id(self), self.id if self.flags & V_HAVEID else None,
self.data.count(), self.data.size())
cdef class VertexBatch:
def __init__(self, **kwargs):
get_context().register_vertexbatch(self)
self.usage = GL_DYNAMIC_DRAW
cdef object lushort = sizeof(unsigned short)
self.vbo = kwargs.get('vbo')
@ -114,18 +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.flags = V_NEEDGEN | V_NEEDUPLOAD
self.elements_size = 0
cdef void clear_data(self):
# clear old vertices from vbo and then reset index buffer
@ -142,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):
@ -162,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())
@ -175,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
@ -211,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 '<VertexBatch at %x id=%r vertex=%d size=%d mode=%s vbo=%x>' % (
id(self), self.id if self.flags & V_HAVEID else None,
self.elements.count(), self.elements.size(), self.get_mode(),
id(self.vbo))

View File

@ -12,24 +12,15 @@ __all__ = ('Triangle', 'Quad', 'Rectangle', 'BorderImage', 'Ellipse', 'Line',
include "config.pxi"
include "common.pxi"
from vbo cimport *
from vertex cimport *
from instructions cimport *
from c_opengl cimport *
from kivy.graphics.vbo cimport *
from kivy.graphics.vertex cimport *
from kivy.graphics.instructions cimport *
from kivy.graphics.c_opengl cimport *
IF USE_OPENGL_DEBUG == 1:
from c_opengl_debug cimport *
from kivy.graphics.c_opengl_debug cimport *
from kivy.logger import Logger
from kivy.graphics.texture import Texture
cdef extern from "string.h":
void *memset(void *s, int c, int n)
cdef extern from "Python.h":
object PyString_FromStringAndSize(char *s, Py_ssize_t len)
cdef extern from "math.h":
double sqrt(double x) nogil
double pow(double x, double y) nogil
class GraphicException(Exception):
'''Exception fired when a graphic error is fired.

View File

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

View File

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