Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

There is (seemingly 100% consistent) vertical tearing when VBLANK deadline is met #7

Open
mcclure opened this issue Dec 9, 2023 · 1 comment

Comments

@mcclure
Copy link
Collaborator

mcclure commented Dec 9, 2023

Theoretically, the idea with openfpga-litex optimization is that you start writing to the framebuffer when video status (peripherals.APF_VIDEO.video.read()) .vblank_triggered().bit() goes high, and you finish before video status .vblank_status().bit() goes low. If you do this, the theory is the framebuffer only changes when the core is not reading it, and therefore no tearing will occur.

In my testing with minibreak and a custom test program, I find that there is 100% consistent tearing at the y=123 line exactly, if the VBLANK deadline is met (if the VBLANK deadline is not met— IE if we are still drawing when VBLANK goes high— then sometimes I do not see tearing).

I first noticed this in Minibreak. I noticed that the ball sometimes seemed to blink when it is around the midpoint of the screen. I assumed this was random, but then I caught this happening:

20231129_231420 20231129_231423

When only one brick is left in Minibreak, it starts moving side to side. On one run, I happened to have a brick on exactly the y=123 line and I saw it was tearing as it moved. This made me realize that the reason the ball blink seemed to be random was that it was only sometimes the ball happened to intersect the y=123 line, and it was only when it hit this precise pixel it blinked.

This evening I went back and revisited a test program I wrote:

Source: https://github.com/mcclure/pocket-riscv-rs/tree/strobe
Binaries: strobe.zip

The program is called "strobe" and I do not actually recommend running it. It slowly fades from green to black and back, inverting the screen once per cycle. By pressing Y you can make the screen invert on demand, and by holding X, A or B you can make the screen strobe at different rates. It is unpleasant to look at and I have a headache now, but it shows us some interesting things about the vertical tearing. The program comes in two flavors, a plain one, and one that tries to talk to the serial port and report debugging information about draws. If a frame fails to complete during vblank, it prints an error, if a frame is missed entirely (the counter goes up by more than 1 between frames) it prints an error, it counts the number of "failed frames". Like this

image

If you don't touch the D-Pad, the program tries to rewrite the entire screen on every cycle, and unsurprisingly it does not succeed. Running this way I see a vertical tear near the bottom of the screen:

20231207_234644

and also I see the "drawing finished outside vblank deadline" warning on every frame when running with strobe-speed-debug.bin. This is all entirely expected, I drew while the framebuffer was being read and I saw tearing. This is "correct". (Note, on like one of every 5 or so runs, I see no tearing. That's a little more surprising, but not totally.)

Tonight I added another feature to see what happens when we meet the vblank deadline: Pressing the d-pad causes it instead of drawing the entire screen to draw only to the limit of SCREEN_WIDTH / 2, 4, 8 or 16.

If I run with any of these four divisors, I see tearing on the y=123 line, but [i]not[/i] on the lower tear line:

20231207_234702

I see this failure even on runs when the SCREEN_WIDTH full does [i]not[/i] produce tearing. These are all screenshots, by the way, which means the tearing is visible to the scaler (or whatever Analogue thingy takes the screenshots).

With SCREEN_WIDTH full or SCREEN_WIDTH / 2, I get the "drawing finished outside vblank deadline" warning. with / 4, 8 or 16 I do not.

This was briefly discussed, with only the first bit of the information above, in Discord. Agg theorized that there is a "logical vblank" which is different from the advertised VBLANK, and that the litex display component's FIFOs could be to blame.

My "expected" behavior is that advertised VBLANK should be the same as "logical VBLANK" and that it should be possible to avoid tearing by drawing within advertised VBLANK (agg has already put a good bit of effort into trying to make this the case). However it may be Litex erects impassable barriers to us fixing this.

Things I can't explain:

  • Why does drawing [i]more[/i] pixels sometimes prevent the mid-screen tear? (Maybe when I'm drawing slower we wind up with the same color before and after the y=123 line?)
  • One of the minibreak screenshots shows a white line bisecting the brick. That's worrisome. The way minibreak draws is for each actor it needs to move, it first erases it at the old location then draws it at the new location. If at any point the DMA read of the screen saw a white pixel, that implies it somehow managed to read that line between the erasure and the new draw.
  • Why is the line of failure so consistent (y=123, at or nearly at the screen midpoint)? Imagine that the problem were vblank is advertised as being t=N to t=N+100, but it turns out vblank is actually t=N-50 to t=N+50. Then I would expect the vertical tearing line to occur at point t=N+50. But it doesn't occur at a time at all, since minibreak's draws have no correlation between the current time and the pixel currently being drawn. Rather across multiple apps the tear occurs at a y position.
  • Despite repeated efforts I cannot get a screenshot of the N/2 case which features vertical tearing. I have several times gotten the screen to freeze with a vertical tear in place and "screenshot saved" on the screen, but when I go to look at the screenshot, there's no tear. (This is probably not important as it's probably some internal timing thing, but it does raise a problem: When moving between N/2 and N/4 it might actually be the y=123 line moves down by a pixel or so, while keeping that weird h-pixel step within the tear at roughly the same point, but it's hard to tell because I'm staring into a strobelight and the screenshots are failing).
@mcclure
Copy link
Collaborator Author

mcclure commented Dec 9, 2023

agg in chat: "Y=123 is exactly where I would expect it to be. The FIFO is 32768 pixels, and 32768/266 = 123.19"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant