Skip to content

Instantly share code, notes, and snippets.

@clayote
Last active December 2, 2024 09:57
Show Gist options
  • Save clayote/0499721967a9d0c2249aba05e3a0a5d8 to your computer and use it in GitHub Desktop.
Save clayote/0499721967a9d0c2249aba05e3a0a5d8 to your computer and use it in GitHub Desktop.
Demonstration of a condition where a Fbo can draw outside the StencilView it's in
from kivy.core.image import Image
from kivy.graphics.fbo import Fbo
from kivy.properties import ObjectProperty, BooleanProperty, ListProperty
from kivy.graphics import (
InstructionGroup,
Translate,
Rectangle,
)
from kivy.clock import Clock, mainthread
from kivy.uix.widget import Widget
from kivy.logger import Logger
BACKGROUND = True # change this to False, and the bug doesn't happen
DEFAULT_IMAGE_PATH = "kivy/examples/canvas/mtexture2.png"
TEST_DATA = [{"name": (x, y), "x": x * 0.04083333333333333, "y": y * 0.04083333333333333, "width": 32, "height": 32, "textures": [DEFAULT_IMAGE_PATH]} for x in range(100) for y in range(100)]
class TextureStackPlane(Widget):
data = ListProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._trigger_redraw = Clock.create_trigger(self.redraw)
self._redraw_bind_uid = self.fbind("data", self._trigger_redraw)
def on_parent(self, *_):
if not self.canvas:
Clock.schedule_once(self.on_parent, 0)
return
with self.canvas:
self._fbo = Fbo(size=self.size)
self._translate = Translate(x=self.x, y=self.y)
self._rectangle = Rectangle(
size=self.size, texture=self._fbo.texture
)
self.bind(pos=self._trigger_redraw, size=self._trigger_redraw)
self._trigger_redraw()
def on_pos(self, *_):
if not hasattr(self, "_translate"):
return
self._translate.x, self._translate.y = self.pos
self.canvas.ask_update()
def on_size(self, *_):
if not hasattr(self, "_rectangle") or not hasattr(self, "_fbo"):
return
self._rectangle.size = self._fbo.size = self.size
self.redraw()
@mainthread
def _redraw_upd_fbo(self, changed_instructions):
# If I move this code into redraw(), the bug doesn't happen.
# Might be timing related?
# If I just remove the @mainthread decorator here, the bug happens *for an instant*
# and then goes away, I think on the next frame.
# These changes don't have any effect on the real app this is taken from, though.
# I think it's because the real app has a bunch of other graphics going on in other Fbos
# that are also in the same StencilView.
fbo = self._fbo
for insts in changed_instructions:
group = insts["group"]
group.clear()
for rect in insts["rectangles"]:
group.add(rect)
if "color0" in insts:
group.add(insts["color0"])
group.add(insts["line"])
group.add(insts["color1"])
if group not in fbo.children:
fbo.add(group)
self._rectangle.texture = fbo.texture
def redraw(self, *_):
def get_rects(datum):
width = datum.get("width", 0)
height = datum.get("height", 0)
x = datum["x"] * self_width
y = datum["y"] * self_height
rects = []
for source in datum["textures"]:
texture = Image.load(source).texture
rects.append(
Rectangle(pos=(x, y), size=(width, height), texture=texture)
)
return rects
if not hasattr(self, "_rectangle"):
self._trigger_redraw()
return
instructions = {}
self_width = self.width
self_height = self.height
todo = []
for datum in self.data:
name = datum["name"]
rects = get_rects(datum)
grp = InstructionGroup()
instructions[name] = insts = {
"rectangles": rects,
"group": grp,
}
todo.append(insts)
self._fbo.bind()
self._fbo.clear_buffer()
self._fbo.release()
self._redraw_upd_fbo(todo)
# The following is a demonstration of a graphical error involving TextureStackPlane
# and its interaction with StencilView.
# When BACKGROUND is True, TextureStackPlane overflows the StencilView it's in,
# even though the background image doesn't.
# When BACKGROUND is False, TextureStackPlane obeys the StencilView.
# The bug doesn't seem to come up when the graphics within TextureStackPlane
# have whole-number coordinates.
if __name__ == "__main__":
import os
import json
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image as ImageWidget
from kivy.uix.widget import Widget
from kivy.uix.stencilview import StencilView
from kivy.base import runTouchApp
root = BoxLayout()
root.add_widget(Widget())
texstac = TextureStackPlane(data=TEST_DATA, size_hint=(None, None), size=(1024,1024))
flot = FloatLayout()
if BACKGROUND:
texstacbg = ImageWidget(size_hint=(None, None), size=(1024,1024))
flot.add_widget(texstacbg)
flot.add_widget(texstac)
stenc = StencilView()
stenc.add_widget(flot)
root.add_widget(stenc)
root.add_widget(Widget())
runTouchApp(root)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment