mirror of https://github.com/kivy/kivy.git
Stencil: handle stencil state in a way it can be saved/restored.
Now the FBO can save/reset/restore the stencil state. This fixes many issues related to stencil not working withing FBO (FBO with StencilView/Graph inside ScreenManager, Carousel, etc). Closes #1578 Closes #4454 Ref #3674 Closes kivy-garden/garden.graph#7
This commit is contained in:
parent
4c6f7be666
commit
06af8eeddd
|
@ -15,6 +15,7 @@ cdef class Fbo(RenderContext):
|
||||||
cdef GLint _viewport[4]
|
cdef GLint _viewport[4]
|
||||||
cdef Texture _texture
|
cdef Texture _texture
|
||||||
cdef int _is_bound
|
cdef int _is_bound
|
||||||
|
cdef object _stencil_state
|
||||||
cdef list observers
|
cdef list observers
|
||||||
|
|
||||||
cpdef clear_buffer(self)
|
cpdef clear_buffer(self)
|
||||||
|
|
|
@ -82,6 +82,8 @@ from kivy.graphics.cgl cimport *
|
||||||
|
|
||||||
from kivy.graphics.instructions cimport RenderContext, Canvas
|
from kivy.graphics.instructions cimport RenderContext, Canvas
|
||||||
from kivy.graphics.opengl import glReadPixels as py_glReadPixels
|
from kivy.graphics.opengl import glReadPixels as py_glReadPixels
|
||||||
|
from kivy.graphics.stencil_instructions cimport (
|
||||||
|
get_stencil_state, restore_stencil_state, reset_stencil_state)
|
||||||
|
|
||||||
cdef list fbo_stack = []
|
cdef list fbo_stack = []
|
||||||
cdef list fbo_release_list = []
|
cdef list fbo_release_list = []
|
||||||
|
@ -312,6 +314,10 @@ cdef class Fbo(RenderContext):
|
||||||
cgl.glGetIntegerv(GL_VIEWPORT, <GLint *>self._viewport)
|
cgl.glGetIntegerv(GL_VIEWPORT, <GLint *>self._viewport)
|
||||||
cgl.glViewport(0, 0, self._width, self._height)
|
cgl.glViewport(0, 0, self._width, self._height)
|
||||||
|
|
||||||
|
# save stencil stack
|
||||||
|
self._stencil_state = get_stencil_state()
|
||||||
|
reset_stencil_state()
|
||||||
|
|
||||||
cpdef release(self):
|
cpdef release(self):
|
||||||
'''Release the Framebuffer (unbind).
|
'''Release the Framebuffer (unbind).
|
||||||
'''
|
'''
|
||||||
|
@ -329,6 +335,9 @@ cdef class Fbo(RenderContext):
|
||||||
cgl.glViewport(self._viewport[0], self._viewport[1],
|
cgl.glViewport(self._viewport[0], self._viewport[1],
|
||||||
self._viewport[2], self._viewport[3])
|
self._viewport[2], self._viewport[3])
|
||||||
|
|
||||||
|
# restore stencil stack
|
||||||
|
restore_stencil_state(self._stencil_state)
|
||||||
|
|
||||||
cpdef clear_buffer(self):
|
cpdef clear_buffer(self):
|
||||||
'''Clear the framebuffer with the :attr:`clear_color`.
|
'''Clear the framebuffer with the :attr:`clear_color`.
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
from kivy.graphics.instructions cimport Instruction
|
from kivy.graphics.instructions cimport Instruction
|
||||||
|
|
||||||
|
cdef get_stencil_state()
|
||||||
|
cdef void restore_stencil_state(dict state)
|
||||||
|
cdef void reset_stencil_state()
|
||||||
|
|
||||||
cdef class StencilPush(Instruction):
|
cdef class StencilPush(Instruction):
|
||||||
cdef int apply(self) except -1
|
cdef int apply(self) except -1
|
||||||
cdef class StencilPop(Instruction):
|
cdef class StencilPop(Instruction):
|
||||||
|
|
|
@ -103,9 +103,13 @@ from kivy.graphics.cgl cimport *
|
||||||
from kivy.compat import PY2
|
from kivy.compat import PY2
|
||||||
from kivy.graphics.instructions cimport Instruction
|
from kivy.graphics.instructions cimport Instruction
|
||||||
|
|
||||||
cdef int _stencil_level = 0
|
cdef dict DEFAULT_STATE = {
|
||||||
cdef int _stencil_in_push = 0
|
"level": 0,
|
||||||
|
"in_push": False,
|
||||||
|
"op": None,
|
||||||
|
"gl_stencil_func": None}
|
||||||
|
|
||||||
|
cdef dict _stencil_state = DEFAULT_STATE.copy()
|
||||||
|
|
||||||
cdef dict _gl_stencil_op = {
|
cdef dict _gl_stencil_op = {
|
||||||
'never': GL_NEVER, 'less': GL_LESS, 'equal': GL_EQUAL,
|
'never': GL_NEVER, 'less': GL_LESS, 'equal': GL_EQUAL,
|
||||||
|
@ -123,26 +127,42 @@ cdef inline int _stencil_op_to_gl(x):
|
||||||
raise Exception('Unknown <%s> stencil op' % x)
|
raise Exception('Unknown <%s> stencil op' % x)
|
||||||
|
|
||||||
|
|
||||||
cdef class StencilPush(Instruction):
|
cdef get_stencil_state():
|
||||||
'''Push the stencil stack. See the module documentation for more
|
global _stencil_state
|
||||||
information.
|
return _stencil_state.copy()
|
||||||
'''
|
|
||||||
cdef int apply(self) except -1:
|
|
||||||
global _stencil_level, _stencil_in_push
|
|
||||||
if _stencil_in_push:
|
|
||||||
raise Exception('Cannot use StencilPush inside another '
|
|
||||||
'StencilPush.\nUse StencilUse before.')
|
|
||||||
_stencil_in_push = 1
|
|
||||||
_stencil_level += 1
|
|
||||||
|
|
||||||
if _stencil_level == 1:
|
|
||||||
|
cdef void restore_stencil_state(dict state):
|
||||||
|
global _stencil_state
|
||||||
|
_stencil_state = state.copy()
|
||||||
|
stencil_apply_state(_stencil_state, True)
|
||||||
|
|
||||||
|
|
||||||
|
cdef void reset_stencil_state():
|
||||||
|
restore_stencil_state(DEFAULT_STATE)
|
||||||
|
|
||||||
|
|
||||||
|
cdef void stencil_apply_state(dict state, restore_only):
|
||||||
|
# apply state for stencil here. This allow to reapply a state
|
||||||
|
# easily when using FBO, or linking to other GL subprogram
|
||||||
|
if state["op"] is None:
|
||||||
|
cgl.glDisable(GL_STENCIL_TEST)
|
||||||
|
|
||||||
|
elif state["op"] == "push":
|
||||||
|
# Push the stencil stack, ready to draw a mask
|
||||||
|
|
||||||
|
if not restore_only:
|
||||||
|
state["level"] += 1
|
||||||
|
state["in_push"] = True
|
||||||
|
|
||||||
|
if state["level"] == 1:
|
||||||
cgl.glStencilMask(0xff)
|
cgl.glStencilMask(0xff)
|
||||||
log_gl_error('StencilPush.apply-glStencilMask')
|
log_gl_error('StencilPush.apply-glStencilMask')
|
||||||
cgl.glClearStencil(0)
|
cgl.glClearStencil(0)
|
||||||
log_gl_error('StencilPush.apply-glClearStencil')
|
log_gl_error('StencilPush.apply-glClearStencil')
|
||||||
cgl.glClear(GL_STENCIL_BUFFER_BIT)
|
cgl.glClear(GL_STENCIL_BUFFER_BIT)
|
||||||
log_gl_error('StencilPush.apply-glClear(GL_STENCIL_BUFFER_BIT)')
|
log_gl_error('StencilPush.apply-glClear(GL_STENCIL_BUFFER_BIT)')
|
||||||
if _stencil_level > 128:
|
elif state["level"] > 128:
|
||||||
raise Exception('Cannot push more than 128 level of stencil.'
|
raise Exception('Cannot push more than 128 level of stencil.'
|
||||||
' (stack overflow)')
|
' (stack overflow)')
|
||||||
|
|
||||||
|
@ -154,28 +174,68 @@ cdef class StencilPush(Instruction):
|
||||||
log_gl_error('StencilPush.apply-glStencilOp')
|
log_gl_error('StencilPush.apply-glStencilOp')
|
||||||
cgl.glColorMask(False, False, False, False)
|
cgl.glColorMask(False, False, False, False)
|
||||||
log_gl_error('StencilPush.apply-glColorMask')
|
log_gl_error('StencilPush.apply-glColorMask')
|
||||||
|
|
||||||
|
|
||||||
|
elif state["op"] == "pop":
|
||||||
|
# Pop the stencil stack
|
||||||
|
|
||||||
|
if not restore_only:
|
||||||
|
if state["level"] == 0:
|
||||||
|
raise Exception('Too much StencilPop (stack underflow)')
|
||||||
|
state["level"] -= 1
|
||||||
|
state["in_push"] = False
|
||||||
|
|
||||||
|
cgl.glColorMask(True, True, True, True)
|
||||||
|
log_gl_error('StencilPop.apply-glColorMask')
|
||||||
|
if state["level"] == 0:
|
||||||
|
cgl.glDisable(GL_STENCIL_TEST)
|
||||||
|
log_gl_error('StencilPop.apply-glDisable')
|
||||||
|
return
|
||||||
|
# reset for previous
|
||||||
|
cgl.glStencilFunc(GL_EQUAL, state["level"], 0xff)
|
||||||
|
log_gl_error('StencilPop.apply-glStencilFunc')
|
||||||
|
cgl.glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
|
||||||
|
log_gl_error('StencilPop.apply-glStencilOp')
|
||||||
|
|
||||||
|
|
||||||
|
elif state["op"] == "use":
|
||||||
|
# Use the current stencil buffer to cut the drawing
|
||||||
|
if not restore_only:
|
||||||
|
state["in_push"] = False
|
||||||
|
cgl.glColorMask(True, True, True, True)
|
||||||
|
log_gl_error('StencilUse.apply-glColorMask')
|
||||||
|
cgl.glStencilFunc(state["gl_stencil_func"], state["level"], 0xff)
|
||||||
|
log_gl_error('StencilUse.apply-glStencilFunc')
|
||||||
|
cgl.glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
|
||||||
|
cgl.glEnable(GL_STENCIL_TEST)
|
||||||
|
log_gl_error('StencilUse.apply-glStencilOp')
|
||||||
|
|
||||||
|
elif state["op"] == "unuse":
|
||||||
|
# Ready to undraw the mask
|
||||||
|
cgl.glStencilFunc(GL_GREATER, 0xff, 0xff)
|
||||||
|
log_gl_error('StencilUnUse.apply-glStencilFunc')
|
||||||
|
cgl.glStencilOp(GL_DECR, GL_DECR, GL_DECR)
|
||||||
|
log_gl_error('StencilUnUse.apply-glStencilOp')
|
||||||
|
cgl.glColorMask(False, False, False, False)
|
||||||
|
log_gl_error('StencilUnUse.apply-glColorMask')
|
||||||
|
|
||||||
|
|
||||||
|
cdef class StencilPush(Instruction):
|
||||||
|
'''Push the stencil stack. See the module documentation for more
|
||||||
|
information.
|
||||||
|
'''
|
||||||
|
cdef int apply(self) except -1:
|
||||||
|
_stencil_state["op"] = "push"
|
||||||
|
stencil_apply_state(_stencil_state, False)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
cdef class StencilPop(Instruction):
|
cdef class StencilPop(Instruction):
|
||||||
'''Pop the stencil stack. See the module documentation for more information.
|
'''Pop the stencil stack. See the module documentation for more information.
|
||||||
'''
|
'''
|
||||||
cdef int apply(self) except -1:
|
cdef int apply(self) except -1:
|
||||||
global _stencil_level, _stencil_in_push
|
_stencil_state["op"] = "pop"
|
||||||
if _stencil_level == 0:
|
stencil_apply_state(_stencil_state, False)
|
||||||
raise Exception('Too much StencilPop (stack underflow)')
|
|
||||||
_stencil_level -= 1
|
|
||||||
_stencil_in_push = 0
|
|
||||||
cgl.glColorMask(True, True, True, True)
|
|
||||||
log_gl_error('StencilPop.apply-glColorMask')
|
|
||||||
if _stencil_level == 0:
|
|
||||||
cgl.glDisable(GL_STENCIL_TEST)
|
|
||||||
log_gl_error('StencilPop.apply-glDisable')
|
|
||||||
return 0
|
|
||||||
# reset for previous
|
|
||||||
cgl.glStencilFunc(GL_EQUAL, _stencil_level, 0xff)
|
|
||||||
log_gl_error('StencilPop.apply-glStencilFunc')
|
|
||||||
cgl.glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
|
|
||||||
log_gl_error('StencilPop.apply-glStencilOp')
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -191,15 +251,9 @@ cdef class StencilUse(Instruction):
|
||||||
self._op = GL_EQUAL
|
self._op = GL_EQUAL
|
||||||
|
|
||||||
cdef int apply(self) except -1:
|
cdef int apply(self) except -1:
|
||||||
global _stencil_in_push
|
_stencil_state["gl_stencil_func"] = self._op
|
||||||
_stencil_in_push = 0
|
_stencil_state["op"] = "use"
|
||||||
cgl.glColorMask(True, True, True, True)
|
stencil_apply_state(_stencil_state, False)
|
||||||
log_gl_error('StencilUse.apply-glColorMask')
|
|
||||||
cgl.glStencilFunc(self._op, _stencil_level, 0xff)
|
|
||||||
log_gl_error('StencilUse.apply-glStencilFunc')
|
|
||||||
cgl.glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP)
|
|
||||||
cgl.glEnable(GL_STENCIL_TEST)
|
|
||||||
log_gl_error('StencilUse.apply-glStencilOp')
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
property func_op:
|
property func_op:
|
||||||
|
@ -230,10 +284,6 @@ cdef class StencilUnUse(Instruction):
|
||||||
'''Use current stencil buffer to unset the mask.
|
'''Use current stencil buffer to unset the mask.
|
||||||
'''
|
'''
|
||||||
cdef int apply(self) except -1:
|
cdef int apply(self) except -1:
|
||||||
cgl.glStencilFunc(GL_GREATER, 0xff, 0xff)
|
_stencil_state["op"] = "unuse"
|
||||||
log_gl_error('StencilUnUse.apply-glStencilFunc')
|
stencil_apply_state(_stencil_state, False)
|
||||||
cgl.glStencilOp(GL_DECR, GL_DECR, GL_DECR)
|
|
||||||
log_gl_error('StencilUnUse.apply-glStencilOp')
|
|
||||||
cgl.glColorMask(False, False, False, False)
|
|
||||||
log_gl_error('StencilUnUse.apply-glColorMask')
|
|
||||||
return 0
|
return 0
|
||||||
|
|
|
@ -585,6 +585,7 @@ class EffectFbo(Fbo):
|
||||||
attempts to set a new shader. See :meth:`set_fs`.
|
attempts to set a new shader. See :meth:`set_fs`.
|
||||||
'''
|
'''
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs.setdefaults("with_stencilbuffer", True)
|
||||||
super(EffectFbo, self).__init__(*args, **kwargs)
|
super(EffectFbo, self).__init__(*args, **kwargs)
|
||||||
self.texture_rectangle = None
|
self.texture_rectangle = None
|
||||||
|
|
||||||
|
|
|
@ -469,7 +469,7 @@ class ShaderTransition(TransitionBase):
|
||||||
and defaults to [0, 0, 0, 1].'''
|
and defaults to [0, 0, 0, 1].'''
|
||||||
|
|
||||||
def make_screen_fbo(self, screen):
|
def make_screen_fbo(self, screen):
|
||||||
fbo = Fbo(size=screen.size)
|
fbo = Fbo(size=screen.size, with_stencilbuffer=True)
|
||||||
with fbo:
|
with fbo:
|
||||||
ClearColor(*self.clearcolor)
|
ClearColor(*self.clearcolor)
|
||||||
ClearBuffers()
|
ClearBuffers()
|
||||||
|
|
Loading…
Reference in New Issue