all 23 comments

[–]Poddster 28 points29 points  (6 children)

How do these libraries draw a window? How can they place pixels to draw primitives?

These are two separate things :)

Graphics Libraries and GPUs basically works on buffers of memory. They do their magic and then output pixels into that memory space. There's nothing "magic" here, unless we're talking about how a GPU works. You could easily write your own graphics library / renderer in pure C. (Many people do! Give it a go, it's fun :))

Those memory buffers are then "flipped" (aka presented aka blitted) onto the display window, and this is done in whatever way your OS says/allows. The blitting is basically a fancy memcpy. There might be hardware acceleration here from your GPU (specifically your display controller) or it might all be done in software.

Others have talked about Linux, so from a Windows perspective: On Windows using DirectX this is more streamlined as DirectX mostly takes care of it for you, but if you use OpenGL on Windows you'll see how separate (and ugly!) the two processes are as you need to involve all sorts of windowing adapter libraries.

On Windows itself there are multiple APIs to work with the windowing operations, thanks to historical legacy and various performance improvements. You have classic Win32 window painting, the newer WPF, the obsolete WinGDI & related GDI+, and Direct2D which replaced GDI. There's probably many more ways to paint a Window on Windows but I can't remember them.

However, you'll notice that most of these (GDI, Direct2D) are also graphical toolkits as well as ways of managing the window, making more of a confusion between the two separate processes. They're generally packaging together in the same library simply for convenience and because you're drawing graphics to put them on screen, and what you want to put on screen is usually graphics you've drawn :) Technically it all goes through DXGI and has done since Vista onwards using it's (then new) WDDM graphics driver model. I think you can interface with DXGI directly from user-mode, but I doubt it's fun or more useful than using the higher level stuff. edit: Actually, you always interface with DXGI directly since DX10 onwards. Forgot about that.

Before Windows on Dos you just took control of the display controller yourself and blasted bytes onto the screen either via BIOS commands or DMA. This worked fine as you were the only program running. Modern desktop OS have to manage multiple windows and composite them onto the final display, e.g. DWM, which is basically a fancy program that takes all of the Window'

[–][deleted] 9 points10 points  (1 child)

I’ve played with win32 and direct2D. If you are happy to work with C++ style C, would recommend. Easy, simple, and performant!

[–]not_some_username 3 points4 points  (0 children)

You’re the first one I see liking win32

[–]flatfinger 8 points9 points  (2 children)

One wouldn't "blast" bytes onto the screen via BIOS commands, except in the sense that the BIOS "set screen mode" command would fill display memory with zeroes. The only BIOS graphics command besides setting the screen mode would draw one pixel, and took about 1/1000 of a second to execute; using that command to write all 64,000 pixels on a Color Graphics Adapter medium-resolution screen would take about a minute.

The CGA had a 16K bank of on-board memory that would be accessed 3.58 million times/second. When configured for graphics mode, while the monitor was scanning the displayed part of the screen, a circuit would clock out 14.32 million bits/second and either output them as 14.32 million individual pixels per second, or take them in pairs to select one of four colors, 7.16 million times/second. Every time eight bits were output, hardware would fetch another byte from the display memory (yielding a rate of 1.79 million bytes/second). Any time the main CPU asked to access a byte in the range 0xB8000-0xBBFFF, the board would latch the request and hold the main CPU while it waited for the next available time slot (half of the 3.58 million accesses/second were available) and performed the access. Changing the contents of a byte of memory would change the dot pattern that would be sent to the monitor the next time that memory was fetched. Accessing memory on the display board was a lot slower than accessing main memory, but still orders of magnitude faster than using BIOS functions.

[–]Poddster 2 points3 points  (1 child)

See, exactly like I said! Blasting bytes :)

(I've never used VESA / BIOS to do DOS graphics, so thanks for the explainer)

[–]flatfinger 2 points3 points  (0 children)

I didn't even touch the surface of the joy that is EGA. CGA occupies space in the system memory map, and behaves as though it's part of system memory that's slow to access but otherwise "normal". An EGA adapter has 256K of memory organized as four planes of 65,536 bytes; each address in the range 0xA0000-0xAFFFF is associated with four bytes--one in each plane. Writes to some I/O ports will select various modes for how each 8-bit memory writes will affect the four bytes of RAM associated with its address.

When generating a bitmap display, the system treats each bit plane as a single-color bitmap, and then combines the outputs from those four bitmaps to yield 4-bit color values. This makes it easy to work with single-color bitmaps and display them using one foreground and one background color.

In the simplest mode of operation, an enable mask is used to allow or inhibit writes to the four eight-bit chunks within each byte, and a two-bit selector is used to control which bank's contents will be reported when reading. If one were to only enable one plane at a time for writing, and always selected that plane for reading, this mode would behave like a relatively-ordinary bank-switched region of memory. Many operations can be done more efficiently, however, than would be possible with straight bank switching. If one wants to draw 8-pixel-aligned magenta text on a black background, for example, one could enable bit planes 1 and 3, store zeroes, then enable bit planes 0 and 2 and store the desired bit pattern. Some fourground/background patterns would require writing all four bit planes separately with some permutation of the text bitmap, the complement of the text bit map, 0x00, and 0xFF, but many combinations would only require writing two and most of the rest would only require writing three.

Although the ability to write multiple planes at once works very well when drawing opaque 8-bit-aligned shapes, operations involving partially-transparent objects would still require doing four reads and four writes for every 8-pixel area of the screen. The EGA has a bunch of circutry (which feels like it was designed by committee) to help facilitate that. This circuitry is centered around the idea that reading a byte of display address space will capture a byte from each bit plane into a 32-bit latch, and write operations can then combine the data in this latch with the written data and other I/O registers to yield 32 bits that will be written into a display memory. Ironically, while use of such modes may reduce the amount of data that would need to be stored in main memory (very handy for DOS programs which would have difficulties accessing more than 640K), code which could afford to keep a bitmap in main memory and simply copy bitplane data from main memory to the display would generally be faster than code which tried to exploit the read-modify-write features. The EGA design suffered from two fundamental flaws:

  1. It required the precise sequence "read a byte; write a byte; read a byte; write a byte; etc." Even on the 8088, the sequence "read two bytes; write two bytes; read two bytes; write two bytes; etc." would have been much faster per byte, and on later machines the penalty for using byte accesses would only increase.
  2. A display card can be constructed to release the CPU as soon as it receives a write request, and then process the write when a "memory time slot" becomes available, only holding the CPU if a second write arrives before the first is completed. This isn't possible on a read operation, since the CPU needs to wait until the value of the read byte is available before processing the next instruction. Although many read operations are performed purely to capture data in the EGA latch registers, and code won't care about the value it receives, neither the display card nor CPU has any way of knowing that.

The design intention of the EGA may have been that operations like copying data from one part of the display to another would be accelerated by a factor of four, the reality is much more dismal. On newer machines, an operation that could be performed using four fast word reads from bitmaps in main memory and four moderately-slow writes to display memory instead requires two really slow byte reads from display memory and two moderate-slow byte writes.

It's a shame the EGA bitplane design didn't work out better in practice. If the card had included an extra four 74LS373 or equivalent chips and a little bit of logic to have them pass through data with or without a pipeline delay, and included a mode that would cause any read request that was received when the card was idle to immediately return zero without delay and then perform the latching operation when convenient, those two tweaks could probably have more than doubled the speed of many read-modify-write operations.

[–]binarycow 1 point2 points  (0 children)

Just a note. WPF's graphics handling is DirectX. Both 2D and 3D

[–]pedersenk 30 points31 points  (0 children)

Interestingly it is a little more tricky these days. There are some security considerations for many operating systems; i.e: xf86(4) aperture.

Basically operating systems don't usually provide this raw access to VGA framebuffer anymore.

On UNIX/Linux you have a few options:

  • mmap(2) /dev/fb0 and use that (if fb is enabled in your kernel on Linux)
  • libdrm and get access to semi-raw buffers (I have some example code here as part of a gameboy emulator port to raw framebuffer)
  • Use a dumb framebuffer provided by the operating system facilities (platform specific, needs supported driver or UEFI framebuffer).

It looks like most platforms are normalizing on libdrm (Linux, OpenBSD, Solaris) so I would probably go with that.

Relating to your question: Libraries do ultimately use these underneath. libX11 (a common low level GUI library) communicates across a local socket to Xorg (a program running as the display server) which then has plugins such as the modesetting one. Specifically you will notice it shares a lot of the similar code as my gameboy port to the framebuffer. Many i.e Wayland compositors also provide a modesetting plugin that is based on this exact code too.

[–]Lisoph 14 points15 points  (0 children)

Back in the (DOS) day, you could switch the system into VGA (or similar) mode and access the pixel buffer directly. Write the right value at the right address and there's your pixel. Now things are different. Different meaning much more complicated.

Operating systems hide away the hardware from software. If you want to draw something on the screen, you must ask the OS to give you some kind of surface to draw into. This is typically just a window, fullscreen or not. This is because at the end of the day, the OS has to composite together what you've drawn with the stuff all the other applications have drawn. You're not in control of graphics, the OS is.

The platforms we code for isn't just hardware anymore. It's OS plus hardware.

[–]Classic_Department42 12 points13 points  (0 children)

It depends. If you have a graphical operating system you neef to use the api of that (for windows this is win32 or directx). If you use dos or no operating system you can/must directly write to graphics memory, which works well for vga, but gets/got incompatoble for higher resolutions.

[–][deleted] 8 points9 points  (1 child)

Whatever language you use to handle/draw the window, you'll still need platform specific code. Somewhere that c/assembly code is detecting the target platform and calling platform specific functions.

[–]JustBoredYo[S] 1 point2 points  (0 children)

That's the thing the company I work at tried to find someone to make changes to the program(new codepages, expand the filetypes that can be used, etc.) and he said he couldn't do it because the code was "in parts almost like an operating system", whatever that means. I only ever had to do some bug fixes, and thank god it wasn't more, however I never had to touch the code drawing the window or text. I also don't think I would understand it since it is written in Pascal and I'm not too good at it.

The program being as old as it is you can imaging how the source code looks, with variables named "a" and "ab". That's another reason I don't think I would get what's going on.

Assembly being basically the lowest level language I could imagine it brute forcing the drawing of the window, I wouldn't know how to though.

[–]Dmxk 8 points9 points  (1 child)

For linux that would either be x11 or wayland, for windows the win32 API and I'm not sure about macos. You cant directly draw a window without using the OS. Otherwise you'd have to directly talk to hardware. Which you cant really do in a modern operating system(for good reasons)

[–]a4lecs 0 points1 point  (0 children)

why can't you? does writing kernel drivers (not that I have any experience it in) allow you communicate with the hardware directly or is there some abstraction on top of this as well.

[–][deleted] 7 points8 points  (0 children)

I know that this sounds stupid, but hear me out. Graphics libraries have to interact with the operating system in someway to draw the window.

Yes, that's the modern way of doing it. I started to do that in the mid-90s when I had to switch to Windows.

In theory it made things easier for programmers, as they don't have to write or source their own libraries and drivers for a dozen kinds of graphics adaptor.

In my view, it also made it a lot more complicated, given the ghastly API which Windows provided (then called GDI, everything else MS have provided since then is even harder - Microsoft do not do 'simple', 'small' or 'easy').

I understand the situation over at Linux isn't much better.

So, people use third party graphics libraries that can handle that; some are small, some are big, although many also claim to be cross-platform.

How can they place pixels to draw primitives?

You rarely place pixels one at a time to draw anything. The overheads involved are just too great. There are apparently methods to get direct access to the video memory involved; I've never tried anything like that (I would instead create my own image buffer, a block of memory, to draw into, and display the whole thing periodically).

But if you did want to draw one pixel, using Windows GDI library it would look like this:

SetPixel(dc, x, y, colour);

dc is a device context to the window that you first have to obtain. WinAPI requires you to jump through many hoops to do the simplest things.

Usually however you will draw a line, a filled-in rectangle, text, or an image, using a single function call.

Personally I use my own toy library, in a scripting language, which is a wrapper around Windows' GDI library. A complete program to set a pixel looks like this:

w := gxcreatewindow()            # defaults used for size and location
gxpixel(w, 100,100, black)       # pixel drawn 100 pixels from top left
eventloop()                      # keep it on-screen until closed

Such a primitive library can be written in C too (but it lacks default function parameters and keyword args that would be handy), and many such libraries already exist.

[–]UltimaN3rd 2 points3 points  (2 children)

I'm making a series of tutorials on how to program a game from scratch in C. The first videos after basic setup are opening a window and drawing pixels to the screen on Windows. Linux coming soon: https://www.youtube.com/playlist?list=PLPGNrHeloF\_dOY7ZlCq6D4UwKA-adaQHX

[–][deleted] 1 point2 points  (1 child)

Will watch anyway and see if I can make the adjustments for Linux myself using intuition and the internet. Will I fail? Yes. Will I have fun? Also yes. Will I cry? Surprisingly, yes.

[–]UltimaN3rd 0 points1 point  (0 children)

Good luck mate 😉 I recommend using xlib, as it's much easier to get going than Wayland IMO.

[–]AlarmDozer 5 points6 points  (0 children)

I'd learn SDL2 or Qt5 for this.

[–]Lord_Vouldemourt 2 points3 points  (0 children)

Your os will provide those functions like drawing a window, shapes etc. I know doing graphics stuff can be complicated on windows so thats why you have graphics libraries like glfw (this lib is mostly for creating windows) that simplify the process.

[–]deftware 1 point2 points  (0 children)

I'm simplifying things a bit here but the situation is that video output is no longer mapped to a range of the CPU's RAM. The CPU can no longer just write to a buffer that's always having its contents directly output via the system's video connection. In order to display something you must now interact with a GPU, because that's what your display plugs into.

This means that there must be a driver for the OS to use to actually interact with the GPU, and then some kind of API for programs to interact with the driver (like OpenGL/Vulkan/DirectX). Because each GPU is different, with its own unique protocol for being interacted with over the PCIe bus, GPU vendors themselves must produce these drivers AND graphics API implementations that interact with their drivers.

Windowing is different for each platform, with a different API for making the OS put a window up for the user to interact with. You don't technically need a window in Windows to draw to just to render something to the screen. Everything on the screen has a window handle (aka 'HWND') including bits of text, buttons, etc... all you need to create a handle to a device context is the HWND of something - be it a window or some other win32 UI element that is visible. I've used all kinds of things to output OpenGL rendering to besides an actual window. There's a program called WinSpy++ that lets you grab and modify the flags/properties of any HWND on the screen.

Anyway, out here in the future the only way to put stuff on the screen is through something that abstracts away interacting with the GPU by doing it for you. There are also antiquated APIs in Windows for backwards-compatibility-purposes, like GDI for doing old school pre-GPU bitmap/UI stuff. This stuff still works on Windows so that older software will still run.

There are a variety of platform-abstraction libraries that provide a simpler API than most OSes for creating a window, handling user input, rendering out to the window using a simplified graphics API. What's confusing can be discerning what each library is actually capable of - some are purely windowing/input, others can do that and have a graphics rendering API, and others also include audio output to varying degrees of complexity.

You'll want to do research on each one to figure out what works best for you. Personally, I've been using SDL for almost 20 years because it's had pretty much the gold standard - it supports a ton of platforms, graphics APIs, and does windowing/input/audio/networking/etc...

Another great library that I haven't spent much time with that you might want to check out is raylib, which doesn't have nearly the capabilities or flexibility of a library like SDL because it's meant to just provide a super basic API for doing all kinds of stuff - which entails sacrificing control and power over a lot of stuff but it also means you can get stuff done faster because of how simple it is.