Page 1 of 1
Managing direct access to the framebuffer
Posted: Tue Feb 02, 2021 7:18 am
by AndrewAPrice
I have a microkernel where the framebuffer is owned by a userland driver.
a) Should I give full screen applications (most of the time this will be the window compositor, but it could be a video game) direct access to the framebuffer? There's no messaging the driver to copy the buffer. Appplication libraries will be required to implement their own double buffering, and provide a user library for converting colors into 8-bit, 16-bit, 24-bit, etc.
b) Should I create a double buffer, and require applications to send a 'present' message to the graphics driver? This buffer could always be 24-bit, color conversion happens in the driver. I'll need a set of messages such as 'present the entire screen', 'present this sub rectangle'.
If I go with method (a), let's say my framebuffer lives at 0xFD00-0000-> 0xFD15-F900, I need to map this physical memory into the application that needs access. When an application leaves full-screen, I need to unmap this physical memory. But, I don't want to suddenly whip memory away from a process, because it could page fault if it unexpectingly leaves full-screen (e.g. you ALT-TAB out of the full screen program, or an alert pops up) in the middle of writing a frame. I'm thinking the graphics driver would turn the frame buffer into a shared memory object, and I need to build a syscall so that the owner of the shared memory can choose to 'fork' the shared memory (give everyone else their own unique instance, so the program mid-rendering buffer won't crash if it hasn't processed the message yet that it has lost the framebuffer), but let the graphics driver continue to hold onto the same physical memory?
If we assume just about every use case would need double buffering anyway, should I go with method (b)? No need to map the physical memory framebuffer into any application.
Re: Managing direct access to the framebuffer
Posted: Tue Feb 02, 2021 9:36 am
by AndrewAPrice
Preventing raw access to the framebuffer might be the route to go in the (unlikely) event if I ever supported hardware acceleration. Programs (including the window compositor) would draw to a texture, and then you'd ask graphics driver to blit this texture to the screen. The window manager might have special permission to be able to tell the graphics driver which texture becomes the full-screen one.
If I did go this route:
(1) Should the textures be in the same color space/pixel byte configuration as the framebuffer - because there could be benefits with a program being able to pre-transform the data (e.g. we could convert image assets to the display's pallete at load time), rather than every frame that it gets blitted?
(2) I wouldn't want to issue a IPC message per operation (e.g. 10 bitblit operations to update one frame would be a lot of syscalls and scheduling!), but I'd group them together into a
command buffer/list - bytecode of rendering instructions, that gets sent and executed on the final "Present" call. The windowing compositor wouldn't need access to the framebuffer, all it needs to know is that "Window A draws into texture B", so it could composite windows with a chain of commands such as "Draw texture B at {x,y}", "Draw texture C(mouse) at {x,y}".
Re: Managing direct access to the framebuffer
Posted: Tue Feb 02, 2021 8:06 pm
by neon
Hi,
I wanted to throw some ideas around as this brought up some interesting questions for us to address as well when we get to later on.
a. Primary surfaces -- First thought was akin to primary surfaces in direct draw. According to
MSDN, locking allows direct access to a region of video memory (or all of it) and maintains a critical section on it which prevents the GUI from being able to take control while it is locked. So in this case I would believe something like alt+tab simply wouldn't work as the GUI can never render while the video display is locked so the recommendation is to keep it a very short duration. This is probably the most realistic scenario as well if we allow access to video display to userland processes. In this scenario, video memory would be shared between the display server and whatever process that requests to lock a part or all of display memory. Perhaps a timeout should trigger if the lock exceeds a certain threshold as a separate operation to enforce the rule. This leaves it to the application developer to design their software such that locks on primary surfaces, if any, must not exceed the threshold. Perhaps the system can send a message to the application when they are about to lose focus: the applications event loop would catch it and know it is about to lose access to the primary surface so it can release the lock (or the system can after the message has been acknowledged.) Any "correctly written" software should then always work as expected as they can re-acquire the lock when they are notified that they are active again.
b. Bitblit from a Secondary surface or texture surface -- The application describes the surface of the display they want to use (mono, palleted, RGB#bits). Under this, we assume a message would call the display driver for the bitblit operation. If the surface is large, it can be shared temporarily with the process to allow the display driver direct access to it. This can either then be a software transformation from the secondary surface descriptor to primary surface to render it or through hardware as most devices support it. This is closer to option (b) but I wouldn't limit it to 24bpp but would translate from any surface description type instead. In theory, this idea can be applicable to quite literally any surface-to-surface operation.
Re: Managing direct access to the framebuffer
Posted: Fri Feb 05, 2021 9:43 pm
by Gigasoft
In my OS, this will be up to the application.
If an application has mapped a video memory surface (not implemented yet), and the driver needs to reclaim the memory, all of the mapped pages will be replaced with a dummy page. A solution where the pages become not present is also possible, requiring the application to handle an exception when it tries to access the mapping. A third solution involves saving the video memory contents into main memory, and loading it back when the application is in the foreground.
Otherwise, the application can perform the same operations in full screen mode as it can when displayed in a window. In windowed mode, the application can ask the UI about the pixel format for the surface that a given window is displayed on (a window is never cut in half across monitors). In full screen mode, the application controls the pixel format. By blitting from a surface that has the same pixel format, no color conversion is needed. If the pixel format differs, the conversion happens automatically.
As for how to manage the page mappings, you could have a system of hierarchically connected page lists where a page list can reference pages in other page lists that it is attached to. When a page list changes, all page lists that reference it are updated as well. Requests to map different logical surfaces (that is, requests from different full screen applications) would return distinct page lists. You would then need a system call to atomically change page entries in a page list and optionally transfer the page contents from the old pages to the new ones.
Re: Managing direct access to the framebuffer
Posted: Mon Mar 01, 2021 4:38 pm
by AndrewAPrice
I settled on an interface where only the driver (in userspace, being a microkernel) has access to the frame buffer. Applications can ask the graphics driver to create a texture, which is a shared memory block between the driver and application, and the texture has a unique ID. Only the owner of a texture can draw into it. (At some point I need to create a permissions system on who can read other programs' textures.)
There is a special texture - texture 0 - that represents the framebuffer. Only one process at a time can have permission to draw straight into the frame buffer (this is going to be the window manager, but at some point I could also support full screen games.)
I also have a batch API, where I can string together a list of draw commands (currently only BitBlt operations between textures, which are implemented in software), and send them all at once as a single message to the graphics driver. The idea here is that you could string together a bunch of draw calls and maybe one day I'll support VMWare/QEMU's
SVGA-II, and the driver would compose the scene together. If a developer wanted, they could create one big texture and treat it as a pixel buffer and avoid the graphics driver's operations completely until it was time to BitBlt it to the screen.
Now I'm thinking about how I'm going to deal with the window manager. It will be a compositing window manager. Each window will have a texture associated with it. Programs are able to draw whatever they want into the textures (either via the graphics driver, or via shared memory.) It's up to the individual program if they want to double buffer (in fact, we could implement a kind of page flipping: draw into texture 4, tell the WM "Window is now texture 4", draw into texture 3, tell the WM "Window is now texture 3", repeat - it would require making the "Window is now texture x" send a response so we now know it's safe to draw into the other texture.)
I'd like implement this with no shared memory needing to exist between programs and the window manager (only the programs and the graphics driver, and the WM and the graphics driver would share memory.) The WM would know the texture IDs for each window, and have a texture that it draws the window decorations to, a texture containing the mouse, and in a single IPC, send one batch of draw calls with what has changed to the graphics driver.
The disadvantage of this method is that I can't do cool effects such as blurs or shadows, unless I have an draw call to do that (and then I could go down the rabbit hole of pixel shaders, which is overkill for a hobby OS) or I do multiple IPCs - e.g.:
- Window Manager tells Graphics Driver to draw the windows' textures into the WM's texture. (IPC 1)
- Graphics Driver tells Window Manager it is done. (IPC 2)
- Window Manager samples the WM's texture to do fancy shadows and blurs.
- Window Manager tells Graphics Driver to draw the WM's texture to the frame buffer. (IPC 3)
What have other people done with this graphics drivers and window managers?
Re: Managing direct access to the framebuffer
Posted: Tue Mar 02, 2021 6:17 am
by Korona
Regarding the original question: the is no dichotomy here: you can simply give applications access to the true framebuffer at some points and present then with a RAM-backed copy at other times. You can even do it transparently via paging: in essence, this is not so different from memory mapped files: sometimes, the pages are backed by VRAM (in the swap analogy: like files that are currently cached) sometimes the pages are backed by system RAM (like files that need to be loaded on demand).
Re: Managing direct access to the framebuffer
Posted: Fri Mar 12, 2021 11:20 pm
by AndrewAPrice
It ended up being a lot of work with very frustrating debugging to get here.
I create individual buffers which I call "textures". The window manager gets a texture that's the size of the screen, and it uses this for drawing in the window decorations. For each window, it's up to its program to make a texture large enough to contain the window contents, and the program will tell the window manager the window's texture id.
Then, I create a list of draw commands (I only have two currently - copy texture and fill rect) to the graphics driver to compose the scene.
Now here's the fun part: I went a little overboard trying to reduce overdrawing. I built a quadtree and
rectangle slicing. I compose the invalidated part of the screen from back to front by putting the rectangles to copy or fill into the quadtree, and if the new rectangle overlaps an existing rectangle, I slice up the existing rectangle so only that part that peeks through stays in the quadtree.
Then, I go one step further and create a little rectangle for where the cursor is and I slice up whatever is under it so the little patch under the cursor gets copied try the window manager's texture, so I can draw the mouse cursor on top, before copying it to the framebuffer. Everything else outside of the little rectangle underneath the cursor can get copied directly into the framebuffer.
The idea is that if you have a video playing in the corner of the screen and the mouse cursor isn't over it, or you are playing a game that disables the mouse cursor, the entire contents of those windows can be copied directly into the framebuffer.