Home

Windowing

Distributed under the terms of the CC BY-NC-ND 4.0 License.

  1. Universal Aspects of Window Systems
  2. Creating a Window on Windows OS
  3. Source Code (external link GitHub)

Universal Aspects of Window Systems

Reading time: 8 mins.

A World Without Windows?

Showing images on a screen is fundamentally important. This importance stems from the fact that since the 1980s, it has become evident that interacting with programs through a graphical user interface (GUI) is typically more efficient than using command-line interfaces. The frustration of generating images without the capability to see them is considerable. Many of you, belonging to the Millennial and Gen Z generations, may not have experienced this directly, but pioneers in computer-generated imagery (CGI) often had to work on images without the ability to interact with them on a screen in real time. Much of the early image processing software was operated via command lines without any graphical interface. Understanding how to display images on your screen is very beneficial when working with images. As we delve deeper into CGI techniques, being able to see and interact with what our programs are creating becomes increasingly valuable. However, there are a couple of points to note:

The scarcity of educational resources on this topic can be attributed to a few factors, often not discussed or overlooked by educators:

This situation has a couple of implications:

1. To meet everyone's needs, we would have to teach how to create and display images on Windows, Mac, Linux, etc., which triples the workload compared to using a unified API. Given our limited resources, we'll initially focus on Windows, where a significant majority of you work. However, rest assured, we'll explore Mac and Linux basics later on.

2. Many might suggest (often professionals who may seem not to want you to succeed) using existing libraries like Qt. While Qt itself isn't inherently problematic, it does have significant drawbacks. It's a vast library with peculiarities, such as a special pre-compiling step that converts unique syntax in your code into C++ code that the compiler can use, centered around a concept of "slot" that Qt heavily relies on, among others. Though learning Qt is an option, we will not cover it here. The main advantage of using a library like Qt is its cross-OS compatibility, but we'll demonstrate how to manage without it—and yes, it's entirely possible to do so.

3. Alternatives to Qt, such as GLFW, offer a simpler solution for addressing cross-OS windowing issues. Despite GLFW's popularity and ease of integration, it has its limitations, which might lead you back to native windowing systems. Hence, learning the native approach from the outset is beneficial. As you'll discover, understanding and compiling the code for windowing is not as daunting as it seems, especially given its brevity.

4. For illustrating how to build 3D apps with windows, a real-time graphics API might seem suitable. However, with the current state of 3D graphics APIs, where OpenGL is becoming obsolete and alternatives like DirectX and Vulkan (for Windows - and Metal for Mac) are complex, fitting this into a brief format becomes challenging. Instead, our lessons will start with displaying a ray-traced triangle in a window, avoiding the need for a real-time graphics API. This unconventional approach will bypass the complexity of extensive preliminary lessons on graphics APIs.

5. Did I say it was fun. Well for a nerd it is.

With that in mind, let's dive in.

Universal Aspects of Window Systems Across Different Operating Systems

Before diving into the practical aspects, it's essential to grasp that all window systems operate on similar principles. Beyond the mere appearance of the window, what we're addressing here is the interaction mechanism between your program and its users. This encompasses not just the window itself but also input devices like the mouse and keyboard.

Here's a simplified breakdown of how it works:

In pseudocode, this interaction process might look something like this:

void MyWindowProcFunc(WindowHandle handle, Message msg, SomeParams params) {
    switch (msg) {
        case MOUSEMOVE:
            int x = ConvertParamsToInt(params[0]);
            int y = ConvertParamsToInt(params[1]);
            // Handle mouse movement
            break;
        case LEFTBUTTON:
            // Handle left mouse button press/release
            break;
        ...
    }
}

WindowHandle CreateWindow(...) {
	WindowsProperties prop;
	prop.title = "This is My First Window";
	prop.size.width = 640;
	prop.size.height = 480;
	// more props like color, etc.
	...
	prop.identifier = MY_WINDOW_ID;
	prop.proc = MyWindowProcFunc;

	WindowHandle handle = CreateWindow(&prop);

	ShowWindow(handle);

	return handle;
}

void DoSomeWork() {
	Render();
	ForceRedraw();
}

int main() {
	CreateWindow(...);
	Message msg;
	while (1) {
		while (MessagesInQueue()) {
			msg = GetMessage(POP_FROM_QUEUE);
			if (msg.value == QUIT)
				break;
			DispatchMessage(msg);
		}
		// Additional tasks, like rendering an image...
		DoSomeWork();
	}

	CleanWindow(handle);
    ...
}

The pseudocode provided is heavily inspired by the Windows operating system's approach to handling window events, but the underlying concepts apply broadly across various systems. Here's an essential aspect to understand: the application runs indefinitely within an infinite loop located in the main() portion of the app. During each iteration of this loop, the application checks for incoming messages. Operating systems typically queue these messages, meaning your application might need to process multiple messages sequentially within a single cycle.

If a message indicating that the application should quit is encountered, the loop is exited, and the program proceeds to clean up any window-related resources. It's also worth noting that when messages are received, they are dispatched, triggering the call to another function, MyWindowProcFunc, which was specified during the window's creation. This function is where you define how to respond to various possible messages, such as mouse movements or clicks, as well as a multitude of other events like draw messages initiated by the OS for window redraws or keyboard actions.

In our example, we focused on mouse movement and the pressing or releasing of the left mouse button. However, numerous other messages could be handled, including those related to redrawing the window after it's been obscured by another window or processing keyboard inputs, among others. This structure allows for a responsive and interactive application, capable of handling a wide array of user inputs and system notifications.

The mention of MY_WINDOW_ID has been omitted here, but it will be discussed in further detail in the subsequent section. This identifier plays a crucial role in the management and interaction with window resources within our application.

Note that after processing events, we have the opportunity to perform additional tasks before looping back to event processing. This cycle eventually continues for event until your decide to quite the application. In our code, these tasks are encapsulated within the DoSomeWork() function. This is where activities such as rendering a 3D scene image or image processing take place. It's also important to signal to the window that we wish to refresh its content with the updated image. This approach is fundamental to creating animations. By generating new frames at regular intervals and requesting the window API to refresh the display, we can animate content. A practical demonstration of this process will be provided in the next chapter.

-next