Tearing when drawing rectangles

General discussion about anything related to Ultibo.
Poi
Posts: 36
Joined: Mon Jan 07, 2019 11:57 pm

Tearing when drawing rectangles

Postby Poi » Sun Jul 07, 2019 11:54 pm

Some time ago I got a 4 inch HDMI display for my Pi Zero and did a short test updating the screen as quickly as possible while waiting for vsync, the crudest of game loops. And it worked quite well.

Basically the loop amounts to clearing the whole screen with FramebufferDeviceFillRect, drawing some rectangles again with FramebufferDeviceFillRect and finally waiting with FramebufferDeviceWaitSync. I was quite happy with that and moved on to other things... now all those other things are done and I am back doing some drawing code.

Today I finished implementing some input logic to handle button presses and did a quick test of moving a rectangle around the screen. I realised that the drawing of the rectangles isn't smooth in all sections of the screen, everything is fine in the left half, the right half seems to work fine at the top, and the bottom right of the screen looks like is not able to keep up with the drawing speed at all, the rectangle flickers a lot and some pixels are not even drawn, as it get's closer to the edges the drawing works well again, which is very odd.

I tried just clearing the area behind the rectangle instead of the whole screen, but it didn't make any difference.

Here is a snippet of what y main loop looks like

Code: Select all


while (True) do
begin
      // Clear the area behind the rectangle
      FramebufferDeviceFillRect(FramebufferDevice, x, y, 40, 40, $00000000, FRAMEBUFFER_TRANSFER_DMA);

      // The input logic to move the rectangle around
      if IsButtonDown(BUTTON_UP) then
         y := y - 1;

      if IsButtonDown(BUTTON_DOWN) then
         y := y + 1;

      if IsButtonDown(BUTTON_LEFT) then
         x := x - 1;

      if IsButtonDown(BUTTON_RIGTH) then
         x := x + 1;
      
      // Draw a red rectangle
      FramebufferDeviceFillRect(FramebufferDevice, x, y, 40, 40, $FFFF0000, FRAMEBUFFER_TRANSFER_DMA);
      
      FramebufferDeviceWaitSync(FramebufferDevice);
   end;


I also tried using FramebufferDeviceSetOffset to try an implement a back buffer, to see if that would help, but I am not sure I am doing that right. What I did was insert a call to set the frame buffer offset in between the wait for sync call and the fill rect call, and I added a little bit of code to change the offset in each iteration of the loop, much like the Bounding Boxes examples does.

Would I be able to implement a decent back buffer using Ultibo's built in frame buffer drawing methods? Or do I need to roll out an implementation of my own? I was thinking about that, because the Bouncing Boxes example doesn't use the built in methods and instead has custom functions doing the work.
pik33
Posts: 878
Joined: Fri Sep 30, 2016 6:30 pm
Location: Poland
Contact:

Re: Tearing when drawing rectangles

Postby pik33 » Mon Jul 08, 2019 4:16 pm

The bouncing boxes uses the best method to do the job. Declare a framebuffer with 2x height, draw on one of them while displaying another. My retromachine stuff uses this method too.

Code: Select all

repeat

  draw_a_screen(0);

  FramebufferDeviceSetOffset(fb,0,0,True);
  FramebufferDeviceWaitSync(fb);

  draw_a_screen(1);

  FramebufferDeviceSetOffset(fb,0,yres,True);
  FramebufferDeviceWaitSync(fb);

  end;
until terminated;


This I do in a separate thread.

Before this, you have to prepare the framebuffer:

Code: Select all

repeat fb:=FramebufferDevicegetdefault until fb<>nil;

// get native resolution

FramebufferDeviceGetProperties(fb,@FramebufferProperties);
nativex:=FramebufferProperties.PhysicalWidth;
nativey:=FramebufferProperties.PhysicalHeight;
FramebufferDeviceRelease(fb);

xres:=nativex;
yres:=nativey;

FramebufferProperties.Depth:=32;
FramebufferProperties.PhysicalWidth:=xres;
FramebufferProperties.PhysicalHeight:=yres;
FramebufferProperties.VirtualWidth:=xres;
FramebufferProperties.VirtualHeight:=yres*2;
FramebufferProperties.mode:=0;
FramebufferDeviceAllocate(fb,@FramebufferProperties);
threadsleep(300);

FramebufferDeviceGetProperties(fb,@FramebufferProperties);
p2:=Pointer(FramebufferProperties.Address);
Poi
Posts: 36
Joined: Mon Jan 07, 2019 11:57 pm

Re: Tearing when drawing rectangles

Postby Poi » Mon Jul 08, 2019 5:46 pm

I came across retromachine while searching on this topic :) But does it mean I need to implement my own drawing methods? From what I understand FramebufferDeviceFillRect and the other similar methods don't allow you to draw to the memory which is not visible, they check the current offset to make sure they are always drawing to the visible memory.
pik33
Posts: 878
Joined: Fri Sep 30, 2016 6:30 pm
Location: Poland
Contact:

Re: Tearing when drawing rectangles

Postby pik33 » Mon Jul 08, 2019 6:33 pm

The Retromachine is somewhat strange as it uses its own 8-bit offscreen framebuffer which is then translated in real time to 32-bit native RPi framebuffer. All drawing procedures you can find there are my own and they are 8-bit oriented although they can be easy changed to 32 bit. Most of them uses putpixel to draw, except box, which is written in asm for speed, so if you change putpixel and box, all the rest will work. You can get these procedures from there. They use a global variable for a display start adress so you can draw everywhere in memory.

The window system is compositing (yes!) so every window has its own canvas and they are composited onto this 8-bit offscreen framebuffer :) (in most cases at 60 fps) so you can move a window and it is still tear free.
Poi
Posts: 36
Joined: Mon Jan 07, 2019 11:57 pm

Re: Tearing when drawing rectangles

Postby Poi » Mon Jul 08, 2019 10:29 pm

I did a quick test making copies of the FramebufferDeviceFillRect and FramebufferDevicePutRect so they would accept external offset values instead of using the offsets of the framebuffer device and it works well :) I'll have to clean up the solution a bit, but it looks very promising.

I think this was the last bit I needed to sort out before starting programming a simple game! I guess I am missing sound, but i'll worry about that later :P

Thanks for the help pik33.
User avatar
Ultibo
Site Admin
Posts: 2257
Joined: Sat Dec 19, 2015 3:49 am
Location: Australia

Re: Tearing when drawing rectangles

Postby Ultibo » Tue Jul 09, 2019 12:05 am

Poi wrote:Today I finished implementing some input logic to handle button presses and did a quick test of moving a rectangle around the screen. I realised that the drawing of the rectangles isn't smooth in all sections of the screen, everything is fine in the left half, the right half seems to work fine at the top, and the bottom right of the screen looks like is not able to keep up with the drawing speed at all, the rectangle flickers a lot and some pixels are not even drawn, as it get's closer to the edges the drawing works well again, which is very odd.

Just to add a little further explanation to this, the original issue of tearing and flickering when repeatedly clearing and redrawing the screen is mostly just because FramebufferDeviceWaitSync doesn't really help you unless you have a back buffer to draw on, otherwise you still end up drawing to the visible screen.

The reason for the more noticeable flicker in the bottom right is probably just because the screen is drawn from top to bottom, left to right so that is the last part to be cleared.

As pik33 has pointed out you need to use the technique shown in the Bouncing Boxes example to allocate a new framebuffer with a virtual height 2 times the height you want to appear on screen and use FramebufferDeviceSetOffset to switch between the two buffers.

Poi wrote:Would I be able to implement a decent back buffer using Ultibo's built in frame buffer drawing methods? Or do I need to roll out an implementation of my own? I was thinking about that, because the Bouncing Boxes example doesn't use the built in methods and instead has custom functions doing the work.

The actual intent of the design is that you should be able to use the Framebuffer API to draw on the off screen parts of the virtual buffer but looking at it I think there might be a couple of things that would prevent it working correctly.

Poi wrote:I did a quick test making copies of the FramebufferDeviceFillRect and FramebufferDevicePutRect so they would accept external offset values instead of using the offsets of the framebuffer device and it works well :) I'll have to clean up the solution a bit, but it looks very promising.

It looks to me like the issue is not with the offset but simply with the checks in FramebufferDeviceFillRect etc for PhysicalWidth and PhysicalHeight. If these were changed to VirtualWidth and VirtualHeight then it should be possible to draw on any part of the virtual buffer. Remembering also that the FramebufferDeviceSetOffset has a Pan parameter that allows the buffer to be moved without updating the OffsetX and OffsetY values so the X and Y passed to FramebufferDeviceFillRect etc could then be relative to the entire virtual buffer.

We'll do some testing to determine if this is correct and make sure it doesn't impact anything else if we make those changes.
Ultibo.org | Make something amazing
https://ultibo.org
pik33
Posts: 878
Joined: Fri Sep 30, 2016 6:30 pm
Location: Poland
Contact:

Re: Tearing when drawing rectangles

Postby pik33 » Tue Jul 09, 2019 8:50 am

I am missing sound


Then SimpleAudio can help.

https://github.com/pik33/SimpleAudio

Return to “Discussion”

Who is online

Users browsing this forum: No registered users and 2 guests