mirror of https://github.com/kivy/kivy.git
Merge branch 'core-gl-reload'
This commit is contained in:
commit
520c11874e
|
@ -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
|
||||
#
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
'''
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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, \
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
1
setup.py
1
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),
|
||||
|
|
Loading…
Reference in New Issue