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 Texture _texture
|
||||
cdef int _is_bound
|
||||
cdef object _stencil_state
|
||||
cdef list observers
|
||||
|
||||
cpdef clear_buffer(self)
|
||||
|
|
|
@ -82,6 +82,8 @@ from kivy.graphics.cgl cimport *
|
|||
|
||||
from kivy.graphics.instructions cimport RenderContext, Canvas
|
||||
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_release_list = []
|
||||
|
@ -312,6 +314,10 @@ cdef class Fbo(RenderContext):
|
|||
cgl.glGetIntegerv(GL_VIEWPORT, <GLint *>self._viewport)
|
||||
cgl.glViewport(0, 0, self._width, self._height)
|
||||
|
||||
# save stencil stack
|
||||
self._stencil_state = get_stencil_state()
|
||||
reset_stencil_state()
|
||||
|
||||
cpdef release(self):
|
||||
'''Release the Framebuffer (unbind).
|
||||
'''
|
||||
|
@ -329,6 +335,9 @@ cdef class Fbo(RenderContext):
|
|||
cgl.glViewport(self._viewport[0], self._viewport[1],
|
||||
self._viewport[2], self._viewport[3])
|
||||
|
||||
# restore stencil stack
|
||||
restore_stencil_state(self._stencil_state)
|
||||
|
||||
cpdef clear_buffer(self):
|
||||
'''Clear the framebuffer with the :attr:`clear_color`.
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
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 int apply(self) except -1
|
||||
cdef class StencilPop(Instruction):
|
||||
|
|
|
@ -103,9 +103,13 @@ from kivy.graphics.cgl cimport *
|
|||
from kivy.compat import PY2
|
||||
from kivy.graphics.instructions cimport Instruction
|
||||
|
||||
cdef int _stencil_level = 0
|
||||
cdef int _stencil_in_push = 0
|
||||
cdef dict DEFAULT_STATE = {
|
||||
"level": 0,
|
||||
"in_push": False,
|
||||
"op": None,
|
||||
"gl_stencil_func": None}
|
||||
|
||||
cdef dict _stencil_state = DEFAULT_STATE.copy()
|
||||
|
||||
cdef dict _gl_stencil_op = {
|
||||
'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)
|
||||
|
||||
|
||||
cdef class StencilPush(Instruction):
|
||||
'''Push the stencil stack. See the module documentation for more
|
||||
information.
|
||||
'''
|
||||
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
|
||||
cdef get_stencil_state():
|
||||
global _stencil_state
|
||||
return _stencil_state.copy()
|
||||
|
||||
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)
|
||||
log_gl_error('StencilPush.apply-glStencilMask')
|
||||
cgl.glClearStencil(0)
|
||||
log_gl_error('StencilPush.apply-glClearStencil')
|
||||
cgl.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.'
|
||||
' (stack overflow)')
|
||||
|
||||
|
@ -154,28 +174,68 @@ cdef class StencilPush(Instruction):
|
|||
log_gl_error('StencilPush.apply-glStencilOp')
|
||||
cgl.glColorMask(False, False, False, False)
|
||||
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
|
||||
|
||||
|
||||
cdef class StencilPop(Instruction):
|
||||
'''Pop the stencil stack. See the module documentation for more information.
|
||||
'''
|
||||
cdef int apply(self) except -1:
|
||||
global _stencil_level, _stencil_in_push
|
||||
if _stencil_level == 0:
|
||||
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')
|
||||
_stencil_state["op"] = "pop"
|
||||
stencil_apply_state(_stencil_state, False)
|
||||
return 0
|
||||
|
||||
|
||||
|
@ -191,15 +251,9 @@ cdef class StencilUse(Instruction):
|
|||
self._op = GL_EQUAL
|
||||
|
||||
cdef int apply(self) except -1:
|
||||
global _stencil_in_push
|
||||
_stencil_in_push = 0
|
||||
cgl.glColorMask(True, True, True, True)
|
||||
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')
|
||||
_stencil_state["gl_stencil_func"] = self._op
|
||||
_stencil_state["op"] = "use"
|
||||
stencil_apply_state(_stencil_state, False)
|
||||
return 0
|
||||
|
||||
property func_op:
|
||||
|
@ -230,10 +284,6 @@ cdef class StencilUnUse(Instruction):
|
|||
'''Use current stencil buffer to unset the mask.
|
||||
'''
|
||||
cdef int apply(self) except -1:
|
||||
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')
|
||||
_stencil_state["op"] = "unuse"
|
||||
stencil_apply_state(_stencil_state, False)
|
||||
return 0
|
||||
|
|
|
@ -585,6 +585,7 @@ class EffectFbo(Fbo):
|
|||
attempts to set a new shader. See :meth:`set_fs`.
|
||||
'''
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefaults("with_stencilbuffer", True)
|
||||
super(EffectFbo, self).__init__(*args, **kwargs)
|
||||
self.texture_rectangle = None
|
||||
|
||||
|
|
|
@ -469,7 +469,7 @@ class ShaderTransition(TransitionBase):
|
|||
and defaults to [0, 0, 0, 1].'''
|
||||
|
||||
def make_screen_fbo(self, screen):
|
||||
fbo = Fbo(size=screen.size)
|
||||
fbo = Fbo(size=screen.size, with_stencilbuffer=True)
|
||||
with fbo:
|
||||
ClearColor(*self.clearcolor)
|
||||
ClearBuffers()
|
||||
|
|
Loading…
Reference in New Issue