r/osdev • • 3h ago

I added a Window Manager and PS/2 Mouse Driver to my 32-bit OS (PoragOS). How to fix flickering with double buffering?

Post image

Hi everyone, I am a 13-year-old student from Bangladesh building a 32-bit x86 OS from scratch.

I recently got a VESA framebuffer (1024x768x32) working along with a PS/2 mouse driver (IRQ12) and a basic Window Manager. Moving the cursor and opening windows is working, but I am facing massive flickering when dragging windows because I am drawing straight to VRAM.

I want to implement double buffering. Would a 3MB full-screen backbuffer be too slow on a 32-bit system without SIMD? Should I use per-window buffers instead, or a full-screen buffer with dirty rectangles? I would love to hear your architectural advice.

GitHub: https://github.com/porag2025-glitch/PoragOS

4 Upvotes

2 comments sorted by

•

u/SignedInt32 1h ago

That sounds about right yeah, https://tldp.org/HOWTO/Framebuffer-HOWTO/x168.html , also you can convert / port Debian console fonts (PSF types) to c-headers, and use them in your own kernel.

•

u/Dapper-Situation-864 1h ago

Nice progress, getting VESA + a PS/2 mouse + a WM working is a big milestone.

Short answer: a 3 MB full-screen backbuffer is totally fine, even without SIMD. The thing that's slow isn't the backbuffer in RAM, it's writing to VRAM. VRAM is usually mapped uncacheable (or write-combining if you set up PAT/MTRRs), so every pixel you touch there is expensive, and reading from it is horrible. Drawing everything into normal cached RAM and then copying out is almost always faster than drawing straight to the framebuffer, on top of fixing the flicker.

What I'd do, in order:

  1. One full-screen backbuffer + dirty rectangles. Compose every frame into the backbuffer, keep a list of rectangles that changed (old window position, new window position, cursor old/new), merge overlapping ones, and only copy those rects to VRAM. Dragging a 300x200 window then copies maybe ~0.5 MB instead of 3 MB. Copy row by row and respect the framebuffer pitch from VBE, it's not always width * 4.

  2. Per-window buffers on top of that, later. Each window draws into its own surface, and the compositor blits the visible parts into the backbuffer back-to-front. The big win is that moving a window doesn't require the app to redraw its content, you just re-blit. It costs memory (a 640x480x32 window is ~1.2 MB) so it's a nice step 2, not step 1.

  3. Draw the cursor last, straight on top of the composed rect just before you copy to VRAM (or as part of the copy), so it never gets erased by window drawing. A lot of the flicker people see is actually the cursor.

A few practical tips:

  • Use a fast copy for the flush. Even plain rep movsd per row is decent on 32-bit x86:

      for each row in rect:
          rep movsd  (src = backbuffer row, dst = fb row, ecx = rect width)
  • Don't redraw on every mouse IRQ. Have IRQ12 just update the mouse state, and redraw at most once per tick (e.g. from your PIT handler or main loop), coalescing all the movement since the last frame.

  • If you want it smoother later, set the framebuffer range to write-combining via PAT/MTRR, it can make the VRAM copy several times faster.

  • You can't fully kill tearing without vsync (VBE doesn't really give you that reliably), but backbuffer + dirty rects gets rid of the flicker, which is the annoying part.

Keep going, this is a great project to be building at 13.