Opening a Window - Demo 01¶
Purpose¶
Learn how to open a window, make a black screen, and close the window.
How to Execute¶
Load src/demo01/demo.py in Spyder and hit the play button
Terminology¶
The device connected to a computer that displays information to the user is called a monitor.
A monitor consists of a two-dimensional grid of tiny light-emitting elements, called *pixel*s. Each pixel typically has three components: red, green, and blue.
At any given moment, the computer instructs each pixel to display a specific color, which is represented as a number.
The combination of all these pixel colors at a single point in time is called a frame, and this frame forms a picture that conveys meaning to the user. In OpenGL, pixel coordinates start at (0, 0) in the bottom -left corner of the window. The top-right corner is at (window_width, window_height).
Frames are generated by the computer and sent to the monitor at a constant rate, called the frame rate, measured in Hertz (Hz). By updating these frames rapidly and consistently, the computer creates the illusion of motion for the user.
Code¶
That’s enough terms for now, let’s get on to a working program!
Importing Libraries¶
Import Python modules, which are Python programmer’s main form of libraries.
22import sys
23
24# doc-region-begin import glfw
25import glfw
26
27# doc-region-begin import individual functions without needing module name
28from OpenGL.GL import (
29 GL_COLOR_BUFFER_BIT,
30 GL_DEPTH_BUFFER_BIT,
31 GL_MODELVIEW,
32 GL_PROJECTION,
33 glClear,
34 glClearColor,
35 glLoadIdentity,
36 glMatrixMode,
37 glViewport,
38)
39
The “sys” module is imported. To call functions from this module, the syntax is sys.function
28from OpenGL.GL import (
29 GL_COLOR_BUFFER_BIT,
30 GL_DEPTH_BUFFER_BIT,
31 GL_MODELVIEW,
32 GL_PROJECTION,
33 glClear,
34 glClearColor,
35 glLoadIdentity,
36 glMatrixMode,
37 glViewport,
38)
39
40# doc-region-end import first module
41
42# doc-region-end import glfw
43
GL is a submodule of the OpenGL module. By directly importing specific functions from GL into our current module, we avoid having to write OpenGL.GL.function every time. This keeps our code cleaner and easier to read, especially since we’ll be using these functions frequently.
25import glfw
26
27# doc-region-begin import individual functions without needing module name
28from OpenGL.GL import (
29 GL_COLOR_BUFFER_BIT,
30 GL_DEPTH_BUFFER_BIT,
31 GL_MODELVIEW,
32 GL_PROJECTION,
33 glClear,
34 glClearColor,
35 glLoadIdentity,
36 glMatrixMode,
37 glViewport,
38)
39
40# doc-region-end import first module
41
GLFW is a library that lets us create windows and handle input from both the keyboard and mouse. It works seamlessly across Linux, Windows, and macOS, making it a versatile tool for cross-platform development.
In a Python prompt, you can use tab completion to see which functions are available in a module. You can also type help(modulename) to get detailed information about the module (press q to exit the help pager). The help() function works on any object in Python, including modules, classes, and functions, making it a handy tool for exploring and learning.
Opening A Window¶
Desktop operating systems allow users to run multiple programs simultaneously. Each program displays its output within a dedicated area of the monitor, called a window.
To create and open a window in a cross-platform way, this book uses functions provided by the widely supported GLFW library, which works on Windows, macOS, and Linux. In addition to window management, GLFW also provides functions for handling input from keyboards and game controllers.
GLFW/OpenGL Initialization¶
Initialize GLFW.¶
48if not glfw.init():
49 sys.exit()
Initialize GLFW. If initialization fails, the program should terminate. What does the initialization do? Doesn’t matter. Think of it like a constructor for a class; it initializes some state that it needs for later function calls.
Set the version of OpenGL¶
OpenGL has been around a long time, and has multiple, possibly incompatible versions. For this demo, we use OpenGL 1.4. By the end of this book, we will be using OpenGL 3.3.
53glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 1)
54glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 4)
Create a Window¶
58window = glfw.create_window(500, 500, "ModelViewProjection Demo 1", None, None)
62if not window:
63 glfw.terminate()
64 sys.exit()
If GLFW cannot open the window, quit. Unlike MC Hammer , we are quite legit, yet still able to quit.
68glfw.make_context_current(window)
Make the window’s context current. The details of this do not matter for this book.
73def on_key(win, key, scancode, action, mods):
74 if key == glfw.KEY_ESCAPE and action == glfw.PRESS:
75 glfw.set_window_should_close(win, 1)
76
77
Define an register a key handler.
If the user presses the escape key while the program is running, inform GLFW that the user wants to quit. We will handle this later in the event loop.
Functions are first class values in Python, and are objects just like anything else. The can be passed as arguments, stored in variables, and applied later zero, 1, or more times.
>>> def doubler(x):
... return x * 2
...
>>> def add_five_to_result_of(f, x):
... return 5 + f(x)
...
>>> add_five_to_result_of(doubler, 3)
11
83glClearColor(0.0289, 0.071875, 0.0972, 1.0)
Before a frame is drawn, it is first turned into a blank slate, where the color of each pixel is set to some value representing a color. We are not clearing the frame-buffer right now, but setting what color will be used for a later clear. Calling “glClearColor” “0,0,0,1”, means black “0,0,0”, without transparency (the “1”).
87glMatrixMode(GL_PROJECTION)
88glLoadIdentity()
89glMatrixMode(GL_MODELVIEW)
90glLoadIdentity()
Don’t worry about the 4 lines here. Although they are necessary, we will cover them in depth later. After all, this book is called ModelViewProjection. :-)
The Event Loop¶
When you pause a movie, motion stops and you see one picture. Movies are composed of sequence of pictures, when rendered in quick succession, provide the illusion of motion. Interactive computer graphics are rendered the same way, one “frame” at a time.
Render a frame, flush the complete frame-buffer to the monitor. Repeat indefinitely until the user closes the window, or the program needs to terminate.
95while not glfw.window_should_close(window):
96 glfw.poll_events()
97
98 width, height = glfw.get_framebuffer_size(window)
99 glViewport(0, 0, width, height)
100 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
101 glfw.swap_buffers(window)
Poll the operating system for any events, such as mouse movements, keyboard input, etc. This does not handle them, just registers them as having happened.
Get the size of the frame-buffer. The frame-buffer contains data representing all the pixels in a complete video frame. Python allows the returning of multiple values in the form of a tuple. Assigning to the variables this way is a form of “destructuring”
Tell OpenGL that we wish to draw in the entire frame-buffer, from the bottom left corner to the upper right corner.
Make the frame-buffer a blank slate by setting all of the pixels to have the same color. The color of each pixel will be the clear color. If we hadn’t cleared the frame-buffer, then frame number n+1 would be drawing on top of whatever was drawn on frame number n. Programming in OpenGL is a bit different than normal programming in a normal language, in that individual function calls do not complete self-contained tasks, as subroutines typically do. Instead, the procedure calls to OpenGL functions only make sense based off of the context in which they are evaluated, and the sequence of OpenGL calls to complete a task.
We have colored every pixel to be black, so flush the frame-buffer to the monitor, and swap the back and front buffers.
Exercise¶
Run the program, close it by hitting Escape.
Before the call to glClear, enter two new lines. On the first, type “import pdb”. On the second type “pdb.set_trace()”. Now run the program again and observe what is different. (pdb.set_trace() sets a breakpoint, meaning that the program pauses execution, although the GLFW window is still on screen over time)
One frame is created incrementally at a time on the CPU, but the frame is sent to the monitor only when frame is completely drawn, and each pixel has a color. The act of sending the frame to the monitor is called flushing the frame.
OpenGL has two frame-buffers (regions of memory which will eventually contain the full data for a frame), only one of which is “active”, or writable, at a given time. “glfwSwapBuffers” initiates the flushing the current buffer, and which switches the current writable frame-buffer to the other one.
Black Screen¶
Type “python src/demo01/demo.py”, or “python3 src/demo01/demo.py” to run.
The first demo is the least interesting graphical program possible.
Sets the color at every pixel black. (A constant color is better than whatever color happened to be the previous time it was drawn.)
If the user resized the window, reset OpenGL’s mappings from normalized-device-coordinates to screen-coordinates.
Cleared the color buffer and the depth buffer (don’t worry about this for now).
When this code returns, the event loop flushes (i.e) sends the frame to the monitor. Since no geometry was drawn, the color value for each pixel is still black.
Each color is represented by a number, so the frame is something like this, where ‘b’ represents black
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
The event loop then calls this code over and over again, and since we retain no state and we draw nothing, a black screen will be displayed every frame until the user closes the window, and says to himself, “why did I buy Doom 3”?
Questions¶
In the following ASCII-art diagram of the frame-buffer, where ‘b’ represents black, ‘Y’ represents yellow, and “G” represents green, what is width and height of the frame-buffer?
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
In the above ASCII-art diagram of the frame-buffer, where ‘b’ represents black, ‘Y’ represents yellow, and “G” represents green, what is the color at pixel (1,1)?
In the below ASCII-art diagram of the frame-buffer, where ‘b’ represents black, ‘Y’ represents yellow, and “G” represents green, what is the color at pixel (2,3)? At (36,2)?
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
GGGGGbbbbbbbbbbbbbbbbbbbbbbbbbYYYYYYY
GGGGGbbbbbbbbbbbbbbbbbbbbbbbbbYYYYYYY
GGGGGbbbbbbbbbbbbbbbbbbbbbbbbbYYYYYYY
GGGGGbbbbbbbbbbbbbbbbbbbbbbbbbYYYYYYY
GGGGGbbbbbbbbbbbbbbbbbbbbbbbbbYYYYYYY
GGGGGbbbbbbbbbbbbbbbbbbbbbbbbbYYYYYYY
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbYYYYYYY
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
Answers¶
width - 37, column 0-36. Height - 12, rows 0 - 11
‘b’, for the color black
‘G’ for Green, ‘Y’ for Yellow