Opening a Window - Demo 01

Purpose

Learn how to open a window, make a black screen, and close the window.

Demo 01

Demo 01

How to Execute

On Linux or on MacOS, in a shell, type “python src/demo01/demo.py”. On Windows, in a command prompt, type “python src\demo01\demo.py”.

Terminology

The device attached to a computer which displays information to the user is called a monitor.

Picture of Monitor

Computer Monitor

The monitor is composed of a two-dimensional array of light-emitting elements, each called a pixel, typically composed of 3 components, red, green, and blue.

Picture of Pixel

Pixels

At a given time, each individual pixel is instructed by the computer to display one specific color, represented within the computer as a number. The aggregate of the colors at each pixel at one moment in time, called a frame, provides a picture that has some meaning to the human user. In OpenGL, the bottom left pixel of a window is pixel coordinate (0,0). The top right is pixel coordinate (window_width,window_height).

1024x768 monitor

1024x768 monitor

1920x1200 monitor

1920x1200 monitor

Frames are created within the computer and sent to the monitor at a rate over time, called the frame-rate, measured in Hertz. By updating frames quickly and at a constant rate, the computer provides the end-user with the illusion of motion.

Motion

14 Hertz Motion

Motion
Motion
Motion
Motion
Motion
Motion
Motion

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.

src/demo01/demo.py
23import sys
24
25# doc-region-begin import glfw
26import glfw
27# doc-region-begin import individual functions without needing module name
28from OpenGL.GL import (GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, GL_MODELVIEW,
29                       GL_PROJECTION, glClear, glClearColor, glLoadIdentity,
30                       glMatrixMode, glViewport)
31
  • The “sys” module is imported. To call functions from this module, the syntax is sys.function

src/demo01/demo.py
28from OpenGL.GL import (GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, GL_MODELVIEW,
29                       GL_PROJECTION, glClear, glClearColor, glLoadIdentity,
30                       glMatrixMode, glViewport)
31
32# doc-region-end import first module
33
34# doc-region-end import glfw
35
36
  • GL is a submodule of the OpenGL module, and we are directly importing a subset of the modules function into our current module. This is because we don’t want to have to write “OpenGL.GL.function” every time, as we use these functions frequently

src/demo01/demo.py
26import glfw
27# doc-region-begin import individual functions without needing module name
28from OpenGL.GL import (GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, GL_MODELVIEW,
29                       GL_PROJECTION, glClear, glClearColor, glLoadIdentity,
30                       glMatrixMode, glViewport)
31
32# doc-region-end import first module
33
  • GLFW is a library which allows us to create windows, get input both from a keyboard from mouse input, on Linux, Windows, and MacOS

On a Python prompt, you can use tab-complete to see which functions are defined on a module. you can also type help(modulename) (q is used to quit out of the pager). help works on any object, including modules.

Opening A Window

Desktop operating systems allow the user to run more than one program at a time, where each program draws into a subsection of the monitor called a window.

Window

Window

To create and to open a window in a cross-platform manner, this book will call procedures provided by the widely-ported GLFW library (supporting Windows, MacOS, Linux). GLFW also provides procedures for receiving input both from a keyboard input and from game controllers.

GLFW/OpenGL Initialization

Initialize GLFW.

src/demo01/demo.py
41if not glfw.init():
42    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.

src/demo01/demo.py
46glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 1)
47glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 4)

Create a Window

src/demo01/demo.py
51window = glfw.create_window(500, 500, "ModelViewProjection Demo 1", None, None)
src/demo01/demo.py
55if not window:
56    glfw.terminate()
57    sys.exit()
  • If GLFW cannot open the window, quit. Unlike MC Hammer, we are quite legit, yet still able to quit.

src/demo01/demo.py
61glfw.make_context_current(window)
  • Make the window’s context current. The details of this do not matter for this book.

src/demo01/demo.py
66def on_key(win, key, scancode, action, mods):
67    if key == glfw.KEY_ESCAPE and action == glfw.PRESS:
68        glfw.set_window_should_close(win, 1)
69
70
  • 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
src/demo01/demo.py
76glClearColor(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”).

src/demo01/demo.py
80glMatrixMode(GL_PROJECTION)
81glLoadIdentity()
82glMatrixMode(GL_MODELVIEW)
83glLoadIdentity()
  • 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.

src/demo01/demo.py
88while not glfw.window_should_close(window):
89    glfw.poll_events()
90
91    width, height = glfw.get_framebuffer_size(window)
92    glViewport(0, 0, width, height)
93    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
94    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.

  1. Sets the color at every pixel black. (A constant color is better than whatever color happened to be the previous time it was drawn.)

  2. If the user resized the window, reset OpenGL’s mappings from normalized-device-coordinates to screen-coordinates.

  3. 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