ScissorEffect flickering

I recently posted about an issue that I had with nested DirectScrolledFrames flickering. Well, giving it more thought, I decided to attempt re-implementing what I had in mind with simple DirectFrames instead. I did this, and it worked fairly well, I think–and the implementation feels less clunky, and perhaps has fewer unexpected side-effects, than doing this with DirectScrolledFrames.

However, I do still want the various regions of this new class to be clipped, as they were when they were the canvases DirectScrolledFrames.

Hunting around a little, it seems that DirectScrolledFrame may use a “ScissorEffect” to implement its clipping. I tried using one myself–and it works!

Except that the frames in question flicker horribly. :/

Based on some testing, it looks like the problem may have to do with the way that I’m going about updating the ScissorEffect, via overriding “DirectGuiWidget.setFrameSize”: If I simply create two nested frames and change their frame-sizes over time, updating their ScissorEffects as I do, I see no flickering. If instead I create a simple class that overrides “setFrameSize” and applies the ScissorEffect there, I see flickering.

What might be the problem here?

Here is a simple test-program that demonstrates the issue (on my machine, at least). You should see two nested squares. The outer one should grow–and flicker as it does.

from direct.gui.DirectGui import DirectFrame
from panda3d.core import ScissorEffect, Vec3
from direct.task import Task

from direct.showbase import ShowBase as showBase

class Mew(DirectFrame):
    def __init__(self, parent = None, **kwargs):
        optiondefs = (
             ('frameSize',      (-0.5, 0.5, -0.5, 0.5),         self.setFrameSize),
            )

        self.defineoptions(kwargs, optiondefs)

        DirectFrame.__init__(self, parent)

        self.initialiseoptions(Mew)

    def setFrameSize(self, fClearFrame = 0):
        frameSize = self["frameSize"]
        self.clearEffect(ScissorEffect)
        self.setScissor(Vec3(frameSize[0], 0, frameSize[2]),
                            Vec3(frameSize[1], 0, frameSize[2]),
                            Vec3(frameSize[0], 0, frameSize[3]),
                            Vec3(frameSize[1], 0, frameSize[3]),
                                 )

        DirectFrame.setFrameSize(self, fClearFrame)

class Game(showBase.ShowBase):

    def __init__(self):
        showBase.ShowBase.__init__(self)

        self.accept("escape", self.userExit)

        self.frame = Mew(frameColor = (0, 0.2, 1, 1))

        self.frame2 = Mew(parent = self.frame, frameColor = (0, 1, 0, 1), frameSize = (-0.1, 0.1, -0.1, 0.1))

        self.updateTask = taskMgr.add(self.update, "update")

    def update(self, task):

        dt = globalClock.getDt()

        frameSize = self.frame["frameSize"]
        frameSize = (frameSize[0] - 0.1*dt, frameSize[1] + 0.1*dt, frameSize[2] - 0.1*dt, frameSize[3] + 0.1*dt)

        self.frame["frameSize"] = frameSize

        return Task.cont

app = Game()
app.run()

I do not quite understand what you are trying to achieve. However, you can use a stencil, or try to sort. There is likely a Z buffer conflict.

ADD:
And yes, I understand that you have invested too small to block large. Therefore, the problem is not Z buffer.
ADD:
Although if comment out self.frame2 the problem disappears. It is interesting

In short, I’m working on a type of DirectFrame that one can split into sub-regions. (And adjust the size of.)

Indeed. I suspect that the problem is overlapping scissor regions.

After I stopped work last night I had the idea to remove the underlying scissor-effects in the class that I’m working on: that class functions in such a way that the overlaid scissor-effects should entirely cover the underlying ones, and thus the latter aren’t really important. I’ll likely try that tomorrow, and report back, I think.

Okay, an update:

Removing the underlying scissor-effects doesn’t seem to solve the problem.

In fact, further testing indicates that the ScissorEffects don’t even have to overlap to cause this effect! Nor do the nodes bearing ScissorEffects have to be parent and child.

It looks like this happens when I have:

  • Two or more ScissorEffects active (on different nodes)
  • The frame-size of at least one of them is changed
  • And the scissor-effect associated with the changed node is re-made to account for the new bounds.

And updated test-program follows. Note that the two nodes are no longer parent and child, and that they no longer visibly overlap. If you change self.frame2 to be a DirectFrame instead of the custom-class defined in the program (via the commented-out line), you should see the flickering disappear.

It is very possible that I’ve made some mistake with the points that I use to define my ScissorEffects.

from direct.gui.DirectGui import DirectFrame
from panda3d.core import ScissorEffect, Vec3
from direct.task import Task

from direct.showbase import ShowBase as showBase

class Mew(DirectFrame):
    def __init__(self, parent = None, **kwargs):
        optiondefs = (
             ('frameSize',      (-0.5, 0.5, -0.5, 0.5),         self.setFrameSize),
            )

        self.defineoptions(kwargs, optiondefs)

        DirectFrame.__init__(self, parent)

        self.initialiseoptions(Mew)


    def setFrameSize(self, fClearFrame = 0):
        frameSize = self["frameSize"]
        self.clearEffect(ScissorEffect)
        self.setScissor(Vec3(frameSize[0], 0, frameSize[2]),
                            Vec3(frameSize[1], 0, frameSize[2]),
                            Vec3(frameSize[0], 0, frameSize[3]),
                            Vec3(frameSize[1], 0, frameSize[3]),
                                 )

        DirectFrame.setFrameSize(self, fClearFrame)

class Game(showBase.ShowBase):

    def __init__(self):
        showBase.ShowBase.__init__(self)

        self.accept("escape", self.userExit)

        self.frame = Mew(frameColor = (0, 0.2, 1, 1), frameSize = (-0.1, -0.09, -0.1, 0.1))

        self.frame2 = Mew(frameColor = (0, 1, 0, 1), frameSize = (0.1, 0.2, -0.1, 0.1))
        #self.frame2 = DirectFrame(frameColor = (0, 1, 0, 1), frameSize = (0.1, 0.2, -0.1, 0.1))

        self.updateTask = taskMgr.add(self.update, "update")

    def update(self, task):

        dt = globalClock.getDt()

        frameSize = self.frame["frameSize"]
        frameSize = (frameSize[0] - 0.1*dt, frameSize[1], frameSize[2] - 0.1*dt, frameSize[3] + 0.1*dt)

        self.frame["frameSize"] = frameSize

        return Task.cont

app = Game()
app.run()

Any thoughts? :/

Thanks for the test case. I managed to track down the issue and implement a solution for 1.10.4. For the record, I’ll add a link to the issue being tracked on GitHub:

Ah, I’m glad that you did! (And that it helped with that GitHub issue!) Thank you for the work done in tracking down and fixing the issue! :slight_smile: