Flipping appears to have > 2 buffers

I’m posting this in hopes there’s an obvious fix. My efforts so far to get a minimal reproducible example have been unsuccessful, though we can reproduce the error in its original context. We’re on Ubuntu 16.04 on a Dell Laptop using onboard Intel graphics.

My lab is running an experiment in which we draw a “ball” (red circle) onscreen that’s controlled by joystick. We also have a function that marks events for a photodiode by drawing a white circle in a corner of the screen to “spell out” a bit pattern.

Here’s the issue: I found that we were having flickering problems with the other onscreen stimuli while calling our function to mark events. Normally, this would be because we were flipping the screen without having a copy of the stimuli on both buffers (front and back), but we were also having to call flip one more time than necessary to clear buffers at the end of all this.

So I ended up simply running the following code at the same point in my script where the flicker problems arose:

for i in range(10):
    win.flip(clearBuffer=False)
    core.wait(1)

What I expected was that the screen would then toggle between the contents of the two buffers, exchanging front and back. Instead, I got a cycle through the last four positions of the red circle.

I realize this is short on lots of details, my confusion arises from the fact that simply flipping the screen seems to get me four distinct sets of visual information.

Is there something simple we’re missing?

1 Like

You generally want clearBuffer = True and redraw your stimuli prior to every win.flip() so you know exactly what is being sent to the screen.

Is VSync enabled?

That’s interesting to hear. To be honest the behaviour of clearBuffers=False is never known in advance (but I haven’t seen this version of events before). OpenGL doesn’t specify what is in the buffer that gets returned by a flip command; sometimes it is the previous front buffer (as in literally flipping 2 buffers) and sometimes it turns out to be a copy of the previous back buffer (more useful but not consistent with the concept of “flipping”).

I have no idea what would cause you to have quad buffers enabled, especially on a built-in intel card but, basically, they’re free to do what they like. I wouldn’t expect that to be consistent from one machine to the next, so don’t rely on it.

I once had a TextStim-only flicker problem on some poor hardware (integrated graphics). A textstim would show semi-transparent or not at all on every second draw. Drawing twice did the trick. Not pretty, but it worked.

stim.draw()
stim.draw()
win.flip()

Thanks! It’s useful to know that it’s essentially undefined behavior.

In reply to @m-macaskill’s suggestion: Our event marking code is supposed to be a drop-in function that simply adds a blip at a specific location onscreen (for the photodiode). Well, and then does a series of these, in which that circle may be on or off (link). It’s a bit cumbersome to break encapsulation by having to know how to redraw the screen. We simply assume the front buffer is correct and the back buffer loaded before the function is called.

Anyway, since that approach isn’t viable, is this a use case for BufferImageStim?

Also @m-macaskill: we have waitBlank=True and haven’t changed the VSync defaults on the system, so I suspect that yes, it’s enabled.

But with this approach, you’re losing control of the drawing loop cycle by doing a win.flip() within a function. The usual PsychoPy approach is to have a single event loop driven by the screen refresh cycle.

If encapsulation is your concern, then you should be defining a flicker class rather than a function. e.g. each time your current function is called, new objects (e.g. the timer and circle) are created. A more efficient model would be to create these objects as attributes of the class, and instantiate them just once when the parent object is init’ed. Your flicker object can accept new binary values, and also has a draw() method. Every second time time the draw() method is called, the binary value is trimmed by one digit. When there are no digits left, or if no binary value was ever set, no drawing occurs (at the init stage, a default value of None is applied to the binary value. That way, the flicker object maintains its own state, completely encapsulated. Hence, its draw() method can get called on every screen refresh, and you have much greater flexibility now.

i.e., pseudocode would be:

# create a flicker object and associate it with the active window
# it will internally assign a default null value for its binary digits.
flicker = Flicker(win)

while trial_continues:
    # draw whatever stimuli are needed at the moment:
    for stimulus in list_of_fixed_stimuli:
        stimulus.draw()
    if some_check:
        some_other_stimulus.draw()

    # determine if new binary value needed:
    if some_function():
        flicker.value = some_other_function()

    # regardless, always tell the flicker object to draw.
    # It is maintaining internal state, and so knows whether to draw or not on 
    # this particular screen refresh, depending on what binary digits it has remaining: 
    flicker.draw()

    win.flip()

This sort of structure has the advantage that you maintain control of drawing on every screen refresh. You don’t lose control for 16 consecutive frames by calling to a function that has no knowledge of how long the overall task has been running. That’s true encapsulation: the drawing loop knows where things are at in terms of the overall task, the flicker object knows whether it needs to draw anything on any given iteration, and win.flip() occurs at only one place.

Thanks! We don’t really have a problem with the flicker being a blocking call, since it’s at the beginning and end of trial, but we’ll try this approach.

Just for completeness and future reference, the solution I ended up implementing is here. Several of our tasks are poorly structured (not really conforming to the layout @m-macaskill suggested as best practice), so I included a method Flicker.flicker_block that blocks execution.

Thanks for all your help and for a great tool!

I was being a bit purist :wink: As you say, if this flashing is happening at the end of a trial, blocking doesn’t really matter.

But with your class in place now, you’ve got the foundation for more flexibility in the future if needed. Looks good, and great to see that you will have hardware validation in place via the photodiode.