Last active
December 2, 2024 09:57
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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