kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · his 2008 presentation...

23
Kandinsky Robert R. Snapp February 4, 2015 Contents 1 What is an org file? 2 1.1 Editing code blocks ................................................. 2 1.2 Weaving an org file ................................................. 2 1.2.1 Hints for weaving with L A T E X ....................................... 2 org_cs274.el .............................................. 3 1.3 Tangling ....................................................... 4 2 Installation 4 3 Project Outline 5 3.1 kandinsky.cpp ................................................. 6 3.1.1 File organization .............................................. 6 3.1.2 caution .................................................. 6 3.1.3 project_license ........................................... 6 3.1.4 headers .................................................. 7 3.1.5 structs .................................................. 7 3.1.6 globals .................................................. 8 3.1.7 tools ................................................... 9 loadAndCompileShader ....................................... 9 createVertexFragmentProgram .................................. 10 getImageFileName .......................................... 11 exportPNGImage ............................................ 12 figureAttributes .......................................... 14 3.1.8 initialization ............................................ 15 3.1.9 callbacks ................................................ 16 3.1.10 main .................................................... 18 3.2 Shader Files ..................................................... 20 3.2.1 The vertex shader: kandinsky.vert ................................. 20 3.2.2 The fragment shader: kandinsky.frag ................................ 21 3.3 CMakeLists.txt ................................................ 21 3.4 Building the project. ................................................ 22 4 The Bibliography 22 You mention the circle and I agree with your definition ...why does the circle fascinates me? It is (1) the most modern form, but asserts itself unconditionally, (2) a precise but inexhaustible variable, (3) simultaneously stable and unstable, (4) simultaneously loud and soft, (5) a single tension that caries countless tensions within it. The circle is the synthesis of the greatest oppositions. It combines the concentric and the eccentric in a single form, and in balance. Of the three primary forms (triangle, square, circle), it points most clearly to the fourth dimension. Wassily Kandinsky, letter to Will Grohmann (c. 1926) [Whitford, 1967] 1

Upload: others

Post on 17-Oct-2020

0 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

Kandinsky

Robert R. Snapp

February 4, 2015

Contents1 What is an org file? 2

1.1 Editing code blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.2 Weaving an org file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

1.2.1 Hints for weaving with LATEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2org_cs274.el . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

1.3 Tangling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

2 Installation 4

3 Project Outline 53.1 kandinsky.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

3.1.1 File organization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63.1.2 caution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63.1.3 project_license . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63.1.4 headers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73.1.5 structs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73.1.6 globals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83.1.7 tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

loadAndCompileShader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9createVertexFragmentProgram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10getImageFileName . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11exportPNGImage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12figureAttributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

3.1.8 initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153.1.9 callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163.1.10 main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

3.2 Shader Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203.2.1 The vertex shader: kandinsky.vert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203.2.2 The fragment shader: kandinsky.frag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

3.3 CMakeLists.txt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213.4 Building the project. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

4 The Bibliography 22

You mention the circle and I agree with your definition . . . why does the circle fascinates me? It is (1) the mostmodern form, but asserts itself unconditionally, (2) a precise but inexhaustible variable, (3) simultaneously stableand unstable, (4) simultaneously loud and soft, (5) a single tension that caries countless tensions within it. Thecircle is the synthesis of the greatest oppositions. It combines the concentric and the eccentric in a single form,and in balance. Of the three primary forms (triangle, square, circle), it points most clearly to the fourth dimension.

Wassily Kandinsky, letter to Will Grohmann (c. 1926) [Whitford, 1967]

1

Page 2: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

1 What is an org file?An org file is a text file (e.g., ASCII or UTF-8) that conforms to a versatile set of conventions that support hierarchical sections(outlines), time management, spreadsheet operations, and literate programming. “Org” is short for “organization”, and conse-quently org files can be used to organize your life if not your work flow. Operationally, org files are created and modified withthe GNU Emacs text editor under the orgmode extension. (N.B., highlighted words in this document represent clickable linksin PDF readers and web browsers.) Orgmode was developed by Carsten Dominik. His 2008 presentation at Google introducesthe main features of orgmode. If you are new to Emacs, it is highly recommended to step through the Emacs Tutorial beforeediting or processing an org file. The website https://www.gnu.org/software/emacs/tour/ is a good place tostart.

This file, kandinsky.org, is an example of an org file (hence the file’s extension). It is an ASCII file created andmaintained using the Emacs text editor. “Org” is short for organization, and org files can serve many functions: dynamicallyinteractive outlines, agendas, journals, and in the present context, literate programming. A literate program is a self-documentedcomputer program that explains in a literal style how the project is organized. Following Donald Knuth’s paradigm, two toolscan be applied to the source of a literate program: weave, which generates the documentation; and tangle, which generates thefiles containing code for compilation.

1.1 Editing code blocksAll the commands one uses to edit files in Emacs extend obviously to orgmode. An import new feature is the appearance ofcode blocks: lines delineated by #+begin_src . . . #+end_src delimiters. If the cursor falls within a code block, theEmacs command C-’ toggles in and out of a temporary window, where the syntax highlighting, and relevant language modeapply. (That’s Control-apostrophe, also known as Control-right-tick.)

1.2 Weaving an org fileThis file kandinsky.org can be “woven” into elegantly formatted documents (e.g., kandinsky.pdf or kandinsky.html)that describes the project. (Ideally, these documents are clearer than well-commented source code.) The generated documentsusually include every line of source contained in every project file. (With modest effort, documentation can also be generatedin Microsoft Word doc format.) An org file can be woven from within the Emacs text editor by executing the command,org-export-dispatch, which is by default bound to the key sequence C-c C-e. N.B., C denotes the Control key, soC-c C-e is entered by first pressing the Control key, then pressing and releasing the c key, then pressing and releasing thee key, then releasing the Control key. Phew! A menu is then presented for selecting the document format.

1.2.1 Hints for weaving with LATEX

Weaving kandinsky.org with LATEX (into a pdf) is tricky because of the use of the minted.sty package to print thesource code with syntax colorization. (N.B., the minted.sty package is part of the standard TEX/LATEX distribution: youshould not need to install it separately.) Here are some steps that I have found helpful.

1. I expect that you likely have a recent version of python installed on your computers. However, you might wish to ensureyou have a recent version. Even though Mac OS X includes python under /usr/bin/python, it is still in version 2.7.This should work fine for us, but you might wish to use a package manager to install the version 3+ branch.

2. Install the python package manager pip. (Again, it is easiest to let your OS package manager do the heavy lifting).

3. Once pip is installed, you can use it to install Pygments, a python tool that minted.sty uses to colorize the syntax for avariety of languages, providing yet another application for your cone cells. Execute sudo pip install Pygmentson the command line, and cross your fingers.

4. Ensure that when LATEX runs, the shell-escape option is enabled. I’ll show you below one way to do this.(For security reasons, it is by default not.) This option allows LATEX to execute external commands, namely pygments,when it is running. Beware however of using this option on LATEX source that you do not trust, as this could put yoursystem at risk. In the following, I will show you one way to do this using a customization file for Emacs.

2

Page 3: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

There are many different ways to setup emacs. I used an OS package manager to install the latest version of GNU Emacs24.x. And then I installed the Emacs Prelude distribution, following the instructions at the link. Prelude creates asubdirectory ~/.emacs.d/personal/ to help you customize Emacs to your habits and needs. For example, I created a filecalled org_cs274.el that defines some settings that I like to use for org-mode. Let’s focus on a few items in this file.

org_cs274.el The org_cs274.el emacs customization file for org-mode, contains the following emacs-lisp com-mands that enables the shell-escape option, among other things. This file first ensures that some required files are loaded.The files that begin with ob, an abbreviation for “org babel”, include syntax rules for the indicated language, as well as commonmacros. Files beginning with ox, an abbreviation for “org export”, are needed for weaving into each output file format.

«org_cs274.el»=;;; org_cs274.el;;;;;; Commentary:;;; Place this file into an appropriate location so that it automatically loads when;;; you execute the GNU Emacs text editor, e.g., within ~/.emacs.d/personal/.;;;;;; Code:(require ’ox-latex)

;; Use minted.sty for displaying code in LaTeX(setq org-latex-listings ’minted)

;; Let pdflatex call pygmantize by activating the shell-escape mode.(setq org-latex-pdf-process

’("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f""bibtex %b""pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f""pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f" ))

The above commands specify that the minted.sty latex package should be used for code listings. (This package requiresthat Pygments be installed on your machine.) The last expression redefines the command that orgmode uses to convert an orgfile into a pdf document using LATEX. Note that we enable the -shell-escape option, so that org can invoke pygments as aremote process. Also note that pdflatex is invoked a total of three times, around a call to bibtex. The extra calls ensurethat references and links are properly constructed.

«org_cs274.el»+=;; Add a touch of style(setq org-latex-minted-options

’(("frame" "lines")("fontsize" "\\scriptsize")("bgcolor" "WhiteSmoke")))

The above expression defines some font attributes for code blocks rendered with minted. Each code block will be delineatedwith horizontal lines, the font size will be “scriptsize” (smaller than normal), and the background color will be “WhiteSmoke”.Each language used needs to be explicitly activated. Here we do so for C (which includes C++), emacs-lisp, latex, andsh (Bourne shell).

«org_cs274.el»+=;; Enable code blocks written in C, C++, tex, latex, orgmode, and sh.

(require ’ob)(require ’ob-C)(require ’ob-latex)(require ’ob-org)(require ’ob-sh)

(org-babel-do-load-languages’org-babel-load-languages’((C . t)

(emacs-lisp .t)(latex . t)(org . t)))

The next expression enables one to enter an org template for inserting source simply by typing “S > [tab]” where “[tab]” denotes

3

Page 4: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

the tab key.

«org_cs274.el»+=;; This is a handy trick that generates the template for a code block by typing <S[TAB] where [TAB] stands;; for the TAB key.(add-to-list ’org-structure-template-alist

’("S" "#+name: ?\n#+begin_src \n\n#+end_src""<src id=\"?\" lang=\"\">\n\n</src>"))

The next statement ensures that the leading white space in every code block is preserved, when the org file is tangled.

«org_cs274.el»+=;; preserve the leading white space in every code block.(setq org-src-preserve-indentation t)

The next two emacs-lisp commands enable one to express unmatched quotation marks in a verbatim environment inorgmode.

«org_cs274.el»+=;; Allow one to display commas single, and double quotes in verbatim mode. Note the odd appearance of;; a left-tick (backquote) character in the second line, followed eventually by a comma. This peculiar;; line expands the second invocation of org-emphasis-alist to its current value, before invoking;; the first part of the command. See,;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Backquote.html#Backquote;; for more details.(setcar (nthcdr 2 org-emphasis-regexp-components) " \t\n")(custom-set-variables ‘(org-emphasis-alist ’,org-emphasis-alist))

(provide ’org_cs274.el)

;;; org_cs274.el ends here.

1.3 TanglingThis file can also be “tangled” into all of the project’s souce code. In this event a Bourne shell script, build_me should begenerated within the current directory, and the four files kandinsky.cpp, kandinsky.vert, kandinsky.frag, andCMakeLists.txt, should be generated within a subdirectory called build. An org file can be tangled from within theEmacs text editor by executing the command org-babel-tangle, which is by default bound to the key sequence C-cC-v t.

2 InstallationIf you would like to compile this org project, the following software tools should be installed on your system:

1. The GNU Emacs text editor, with a recent version of orgmode.

2. A C++ compiler with OpenGL version 3.2 or higher. This comes for free on Linux systems. Mac OSX users shouldinstall XCode, and activate the command line tools. I am told that Visual C++ might suffice for Windows. AnotherWindows system options are Cygwin and MinGW.

3. CMake: an open-source, cross-platform utility for generating makefiles for compiling projects in C, C++, and relatedlanguages.

4. GLM: the OpenGL Mathematics library implements many useful vector and matrix operations in C++ for OpenGLapplication and shader (GLSL) programs.

5. GLEW: the OpenGL Extension Wrangler is a cross-platform library that detects the presence of OpenGL extensions, andautomatically loads them.

6. GLFW: an open-source, cross-platform library that allows OpenGL applications to use operating-system resources of theresident operating system, including windows, keyboards, mice, and game pads.

4

Page 5: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

7. FreeImage: an open-source, cross-platform library for loading, manipulating, and saving digital images in a variety ofpopular formats, including png, tiff, and jpg.

8. An implementation of LATEX: e.g., TEXLive or MacTEX.

On Mac OS X you might choose to install Xcode, along with the command line tools. This will also install OpenGL 4.1 orhigher. The other components: CMake, GLM, GLEW, and GLFW can be obtained with a package installer, such as macports,fink, or homebrew.

3 Project OutlineThe Kandinsky project creates an OpenGL program that displays a random two-dimensional arrangement of filled geometricprimitive shapes (triangles, rectangles, and ellipses) on the display. The project illustrates some practical techniques for manag-ing a list of graphical objects with an STL vector container, how to upload different sets of model vertex arrays to the GPU,and how to define and modify the color, orientation, scale, and position of different objects using uniform shader variables.

This project consists of five files:

5

Page 6: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

1. kandinsky.cpp is a C++ souce file that defines a pair of functions for loading, compiling, and linking the GLSLshader files, an initialization routine, various other functions, four callback functions, and the main function.

2. kandinsky.vert defines a simple OpenGL vertex shader program.

3. kandinsky.frag defines a simple OpenGL fragement shader program.

4. CMakeLists.txt is a cmake input file, that when compiled with cmake, generates a makefile that can be used tocompile and link the project. It is recommended to build the project within a dedicated subdirectory (e.g., ./build/).

5. build_me is a unix shell script that creates a build subdirectory, runs CMake to generate a system specific makefile,applies make to the makefile to generate kandinsky (the executable program), and finally launches kandinsky.

Each component is now described in sequence.

3.1 kandinsky.cpp

3.1.1 File organization

This C++ file can be partitioned into seven sections. When tangled, the following source block generates kandinsky.cpp:each name surrounded by double angled brackets (<< label >>) expands to a labeled code block, called a “chunk”, definedelsewhere in this file. (The C++ comment characters to the left of <<project_licenses>> result in like comment char-acters appearing at the beginning of each line of this particular code block. Pretty neat!) You should view the content ofkandinsky.org to see how this magic is actually performed.

The first chunk defines the file kandinsky.cpp:

«kandinsky.cpp»=//<<caution>>//////////////////////////////////////////////////////////////////////////////////// kandinsky.cpp//////////////////////////////////////////////////////////////////////////////////////<<project_license>>//<<headers>><<structs>><<globals>><<tools>><<initialization>><<callbacks>><<main>>// end of kandinsky.cpp

We now describe each code block in sequence.

3.1.2 caution

The following cautionary note should be inserted as a comment at the beginning of each file that is automatically generated bythis org document.

«caution»=CAUTION: The content of this file is automatically generated by Emacs orgmodefrom the file kandinsky.org that should either be in this, or the parentdirectory. Consequently, any modifications made to this file will likely beephemeral. Please edit kandinsky.org instead.

3.1.3 project_license

«project_license»=

6

Page 7: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

================================================================================This file is part of project kandinsky, a simple demonstration of the OpenGLAPI, that draws 600 colorful translucent triangles, rectangles and ellipseson the display. Since the random number seed is initialized to Unix time,a different canvas is obtained with each execution.

kandinsky is free software: you can redistribute it and/or modifyit under the terms of the GNU General Public License as published bythe Free Software Foundation, either version 3 of the License, or(at your option) any later version.

kandinsky is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.

You should have received a copy of the GNU General Public Licensealong with union-find.org. If not, see <http://www.gnu.org/licenses/>.

Copyright (C) 2014, 2015 Robert R. Snapp.================================================================================This file was automatically generated using an org-babel tangle operation withthe file kandinsky.org. Thus, the latter file should be edited instead ofthis file.================================================================================

3.1.4 headers

As C++ is a strongly typed language, every function needs to be declared before it is invoked. It is convenient to declare thetypes of the standard C++ library functions by including the corresponing header files. Our program will also some functionsfor some commonly used libraries that extend the functionality of OpenGL. As noted above, you will need to install theselibraries, preferably by means of an automatic package manager. Doing so, will also install the corresponding header files. Allthree graphics libraries will be useful in our course.

«headers»=// Standard C++ libraries#include <iostream>#include <stdexcept>#include <cmath>#include <ctime>#include <sstream>#include <fstream>#include <string>#include <cstdlib> // for rand(), srand()#include <vector>

// Graphics libraries that extend OpenGL#include <GL/glew.h>#include <GLFW/glfw3.h>#include <glm/glm.hpp>#include <glm/gtc/matrix_transform.hpp>

// FreeImage headers, for digital image format support.#include <FreeImage.h>

3.1.5 structs

We will use a custom data structure, struct Figure for specifying the base model of each figure (triangle = 0, square =1, or circle = 2), along with its color and object transformation from model to world coordinates.

«structs»=struct Figure {

GLuint model;glm::vec4 color;glm::mat4 transformation;

};

7

Page 8: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

3.1.6 globals

The purpose of Kandinsky is to demonstrate how one can create a complex two-dimensional geometric sketch from a few basicgeometric templates, or models. Initially, we consider only two models: an isosceles right triangle, and a square. The globalvariable N_MODELS maintains the number of models. It is defined as a constant using the const qualifier, so its value isimmutable. The number of vertices in each model is maintained in the array N_Vertices. Each circle is modeled as a100-sided regular polygon, using a 102 element array. (A more sophisticated implementation might represent each model witha distinct C++ class, possessing member functions for initialization, and display.) The second array, PrimitiveModeTokenis initialized to the appropriate OpenGL primitive mode token for each model [Shreiner et al., 2013, p. 90]. The third array,VertexArrayObject will eventually contain the vertex array object handles for each base model. This array will beinitialized in the init() procedure.

«globals»=const GLuint N_MODELS = 3;const GLuint N_Vertices[N_MODELS] = {3, 4, 102}; // number of vertices per model;GLuint PrimitiveModeToken[N_MODELS] = {GL_TRIANGLES, // triangle primitive mode

GL_TRIANGLE_STRIP, // square primitive modeGL_TRIANGLE_FAN}; // circle primitive mode

GLuint VertexArrayObject[N_MODELS]; // One GPU object per model: initialized in init()

A C++ STL vector container called Figs will be used to maintain the set of figures that will be displayed. This listwill be initialized within the init() procedure, where N_FIGURES figures will be generated at random. The display()procedure will iterate through the entire array, rendering each figure in sequence onto the display.

«globals»+=const int N_FIGURES = 600;std::vector<Figure> Figs;

The next chunk defines some a few global variables that communicate information from the application to the display()callback function. Normally, adopting an object-oriented language allows one to forego the undesirable use of global variables.However, since OpenGL is designed to work with both C and C++, it is not possible to overload, or override the display callbackfunction. Consequently, we will often need to define a few global variables in each OpenGL application. Another option wouldbe to create an Application class, which would contain the global variables as member data, and then define the display()callback as a member function of the same class. This would have the desirable effect of hiding the variables from functionsoutside of the Application class. We might adopt this strategy in the future, but for now, we will keep things simple.

In the following GLuint maps to a 32-bit unsigned integer. Also glm defines the namespace of the glm library, a templatelibrary that defines many useful matrix and vector operations for OpenGL. Likewise, GLFWwindow is a type defined by theGLFW library that I believe is easier to use than the older GLUT library.

«globals»+=GLFWwindow* gWindow = NULL; // pointer to the graphics window goverened by the OSconst glm::vec2 CANVAS(800, 800); // Our window and viewport dimensions.

The next constant, MAX_FILE_MATCH_ATTEMPTS sets a limit on the number times the function getImageFilenameis allowed to test that an indexed string is not currently in use in the current directory.

«globals»+=const size_t MAX_FILE_MATCH_ATTEMPTS = 1000;

The vertex shader for kandinsky employs two variables having a uniform GLSL qualifier. From an examination ofthe OpenGL and GLSL documentation, one learns that a uniform variable allows an algorithm to apply the same value inevery shader process. The following two statements create handles (shader locations) for the GLSL variables color andtransformation, so that this C++ program (running on the CPU) can modify their values as needed. Here we simplydeclare them within a global scope. They are each initialized in the init() procedure, and are repeatedly evaluated in everyinvocation of display().

«globals»+=GLuint VSL_color; // vertex shader location for the color uniform variable;GLuint VSL_transformation; // vertex shader location for the transformation uniform matrix

8

Page 9: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

3.1.7 tools

One of the themes of computer graphics involves the accelerated development of powerful graphics hardware. In order to exploitthe computational power of graphics processing units (GPUs), which are now integrated into nearly every smartphone, laptop,and desktop computer, the OpenGL API has evolved to enable (or rather require) programmers to develop code that targetsGPUs. The programs, called graphics shaders, play different roles in the graphics pipeline. The current application defines twodifferent shaders: a vertex shader, and a fragment shader. These shader programs are written in the OpenGL Shading Language(GLSL) which shares syntactic elements with C. Each shader program must be compiled and linked by the application programduring runtime using the glCompileShader and glLinkShader OpenGL functions. Since these tasks will be performedfrequently (indeed, within every OpenGL program we will run this semester), a couple general purpose utility functions areprovided below as tools.

«tools»=<<loadAndCompileShader>><<createVertexFragmentProgram>><<getImageFileName>><<exportPNGImage>><<figureAttributes>>

loadAndCompileShader The first tool, loadAndCompileShader accepts two arguments. The first is constant oftype GLenum, which also represents an existing shader type. Legal values are GL_VERTEX_SHADER, GL_GEOMETRY_SHADER,GL_TESS_EVALUATION_SHADER, GL_TESS_CONTROL_SHADER, GL_FRAGMENT_SHADER, orGL_COMPUTE_SHADER. The second argument is a reference to a C++ string that defines the path of the shader file.

«loadAndCompileShader»=GLuint loadAndCompileShader(GLenum shaderType, const std::string& path) {

std::ifstream f;f.open(path.c_str(), std::ios::in | std::ios::binary);if (!f.is_open()) {

throw std::runtime_error(std::string("Can’t open shader file ") + path);}

An input file stream f is declared. An attempt is them made to bind it to the path defined by the second argument. Anexception is thrown if the file cannot be opened.

«loadAndCompileShader»+=// read the shader program from the file into the bufferstd::stringstream buffer;buffer << f.rdbuf();

The contents of the file referenced by path are then read into a buffer using the standard C++ stream library.

«loadAndCompileShader»+=GLuint shader = glCreateShader(shaderType);if (! shader) {

throw std::runtime_error(std::string("Can’t create shader for file ") + path);}

We then “create” a shader of the kind specified by the first argument to loadAndCompileShader. In the event that theinput argument is out of range, (e.g., the type of shader might not be supported by the current version of OpenGL), an exceptionis thrown.

«loadAndCompileShader»+=// tricky conversion from a stringstream buffer to a C (null terminated) string.const std::string& bufferAsString = buffer.str();const GLchar* shaderCode = bufferAsString.c_str();const GLchar* codeArray[] = { shaderCode };GLint size = strlen(shaderCode);glShaderSource(shader, 1, codeArray, NULL);glCompileShader(shader);

Converting the std::stringstream buffer into a character array required by glCompileShader requires a smallmachination: buffer is first copied into bufferAsString as a C++ string, and then subsequently converted on the

9

Page 10: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

nonce into a C character array referenced by shaderCode. (Most C++ implementations perform this operation quicklywithout significant memory modification.) A single element codeArray is declared (N.B., this is an array of characterpointers: no significant memory allocation is required). The number of characters is shaderCode is then computed and storedin size. The OpenGL function glShaderSource binds the pointer codeArray to shader, which is then compiled byglCompileShader. Note that all of the steps occur within the application and CPU, not the GPU.

«loadAndCompileShader»+=GLint status;glGetShaderiv(shader, GL_COMPILE_STATUS, &status);if (! status) {

std::cerr << "Compilation error in shader file " << path << std::endl;GLint logLen;glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLen);if (logLen > 0) {

char *log = new char[logLen];GLsizei written;glGetShaderInfoLog(shader, logLen, &written, log);std::cerr << "Shader log: " << std::endl;std::cerr << log << std::endl;delete [] log;

}throw std::runtime_error(

std::string("Can’t compile the shader defined in file ") + path);}return shader;

}

It is always a good idea to check that the shader compiled without an error. If there was a problem, the most likely indicationwould be an incomprehensible blank window. A status variable is declared, and it is set to GL_FALSE (a.k.a., 0) in the eventof an error. In this event a log message, which is usually informative, is sent to the console, before the runtime exception isthrown. (These steps were inspired by a recipe in the OpenGL 4 Shading Language Cookbook, 2nd edition, by David Wolff,Packt Publishing Ltd., 2013.) If all went well, OpenGL has allocated an internal buffer containing a version of the shader codedfor the GPU, and the value of shader, the ID referencing that code, is returned.

createVertexFragmentProgram This second tool invokes loadAndCompileShader twice to compile and linka GLSL program that contains a vertex shader and fragment shader, each specified by corresponding paths as arguments. Ifsuccessful, an integer-valued reference to the linked program is returned. An example of the usage of this function appears atthe end of the initialization function init below.

First we load and compile the vertex and fragment shaders.

«createVertexFragmentProgram»=GLuint createVertexFragmentProgram(const std::string& vertex_shader_path, const std::string& fragment_shader_path) {

GLuint vertexShader = loadAndCompileShader(GL_VERTEX_SHADER, vertex_shader_path);GLuint fragmentShader = loadAndCompileShader(GL_FRAGMENT_SHADER, fragment_shader_path);

Then we ask OpenGL the resources required for defining a new shader program, and wisely test for success: glCreateProgramreturns a null value if the program could not be created.

«createVertexFragmentProgram»+=GLuint program = glCreateProgram();if (! program) {

throw std::runtime_error("Can’t create GLSL program.");}

After binding our shader programs, referenced by vertexShader and fragmentShader to our new program, we linkit with a call to glLinkProgram.

«createVertexFragmentProgram»+=glAttachShader(program, vertexShader);glAttachShader(program, fragmentShader);glLinkProgram(program);

Next, we repeat the recipe found in the OpenGL 4 Shading Language Cookbook, 2nd edition, to test for any unfortunatesurprises.

10

Page 11: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

«createVertexFragmentProgram»+=GLint status;glGetProgramiv(program, GL_LINK_STATUS, &status);if (! status) {

std::cerr << "Linking error in shader program!" << std::endl;GLint logLen;glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLen);if (logLen > 0) {

char *log = new char[logLen];GLsizei written;glGetProgramInfoLog(program, logLen, &written, log);std::cerr << "Shader log: " << std::endl;std::cerr << log << std::endl;delete [] log;

}throw std::runtime_error("Can’t link shader program.");

}return program;

}

getImageFileName We will now create a function that will attempt to construct an image file name that does not conflictwith any existing files in the current directory. The name will be constructed by concatenating a common prefix (e.g., theapplication name) to an integer time stamp, which will be followed by an integer index, followed by the specified extension.The index will be incremented until no conflict occurs. Just to be safe, a ceiling will be placed on the index, in the unlikelyevent that the current process does not have permission to read any files from the current directory. If no file name can be found,getImageFileName returns a null pointer. Otherwise, the file name is returned as a STL string.

«getImageFileName»=std::string getImageFileName(const std::string &prefix, const std::string &ext) {

std::string imageFileName(""); // initial value is returned in search fails.

// load the current date and time in theTime.time_t theTime;time(&theTime);

// convert the date time into the current locale.struct tm *theTimeInfo;theTimeInfo = localtime(&theTime);

The <ctime> header included near the top of kandinsky.cpp defines a numeric type, time_t, which can represent longtime intervals. (See Stroustrup, Section 43.6.) The header also defines the struct tm that has nine different fields, repre-senting hours, minutes, seconds, and other calendar measures. The function time computes the number of seconds that haveelapsed since 00:00:00, January 1, 1970 (UTC), and stores that value in the memory address indicated by its argument. (N.B.&theTime evaluates to the address of the variable theTime.) The localtime function converts the numeric time pointedto by its argument into the various calendar units in accordance with the local time zone. This information is passed into the tmstruct. The above four commands is pretty much the standard way to answer the question “What time is it?” in C or C++.

«getImageFileName»+=// represent the date and time in the format "150117184509" for 2015 January 17,// 18 hours, 45 minutes, 09 seconds.const int TIME_STRING_SIZE = 16; // must be at least 13.char timeString[TIME_STRING_SIZE];strftime(timeString, TIME_STRING_SIZE, "%y%m%d%H%M%S", theTimeInfo);

We then declare and define a C character array. The function strftime is originally part of the C time library. It acceptsfour arguments. First comes the address of the character buffer that will receive the formatted results. Second comes the sizeof the buffer. Third comes a string that dictates how the character string is formatted. We will write a four digit year, followedby a two digit month, followed by a two-digit date, followed by a 2 digit hour (with respect to a 24 hour clock), followed by atwo-digit minute, followed by a two digit second. If you want to try other options, study the table on page 1263 of Stroustrup[2013]. Finally, the fourth argument is a tm structure that has already broken the moment of interest into its salient components.

We will now use this information to compose a unique filename in the current directory. First we introduce three newvariables.

«getImageFileName»+=

11

Page 12: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

std::stringstream filename;std::ifstream fs;int index =0;

The C++ Standard Template Library defines a plethora of useful data structures with matching algorithms. A std::stringstreamobject is a string buffer that will automatically expand and contract as needed, and supports most of the functionality of a filedevice. In the following we will construct a name for our file by writing formatted values to filename. For each candidatefilename that we generate, we will probe the current directory in order to verify that filename does not already exist. We willuse std::ifstream fs as our probe. A std::ifstream is used to read an existing file, the leading i stands for input,the f for file. The third variable is an integer index initialized to 0. We will iteratively increment i until we obtain a uniquefile name.

«getImageFileName»+=// find a new filename of the form <filenameprefix>_<timeString>_<index>.<ext>, where// <index> is incremented as needed.do {

// reset the stringstream filenamefilename.str(std::string()); // clear the buffer contentfilename.clear(); // reset the state flags

// propose an available (unused) filename.filename << prefix << "_" << timeString << "_" << index++ << ext;

// Ensure the ifstream fs is closed.if (fs.is_open()) fs.close();

// If file does not exist, it can’t be opened in input mode.fs.open(filename.str(), std::ios_base::in);

} while (fs.is_open() && index < MAX_FILE_MATCH_ATTEMPTS);

In the above loop, first the filename stringstream is reset. Then a formatted character string is written into thestringstream buffer as if it were a C++ output device. The content of the prefix is followed by an underscore, the formattedtimeString computed above, another underscore, the current index, and finally the requested extension ext. (N.B., the firstcharacter of ext should probably be a period.) Also note that here we increment the value of index by 1, in the event that weneed to try again. An attempt is then made to open a file with the constructed name for reading. We attempt to close fs, in theevent it was opened during the previous iteration, then we attempt to open the file. Here failure is a success. A failed attemptindicates that a file with that name does not exist. So we found our unused file name, and exit from the loop. However, if fs isopen, then a file with that name exists. If index is less than the value of MAX_FILE_MATCH_ATTEMPTS, defined as a globalconstant, then the iterations continue. Imposing an upper limit on index is probably pedantic and unnecessary: no directorycan have an infinite number of files. (You are free to modify it if you like.)

Note that a do { <<body>> } while (<<test>>); loop construct is employed whenever we must execute<<body>> at least once.

«getImageFileName»+=// Test to see if a unique name was actually found.if (fs.is_open() && index >= MAX_FILE_MATCH_ATTEMPTS) {

fs.close(); // failure: there are too many files with the same time stamp.} else { // N.B. fs must be closed on this branch, right?

imageFileName = filename.str(); // success!}return imageFileName;

}

If fs is open, and the value of index exceeds its predetermined limit, then fs is closed and the function returns an emptystring to the calling function. Otherwise, the function returns the successful filename as a string.

exportPNGImage This function exportPNGIMage uses the FreeImage library to save the contents of the specifiedgraphics window to a PNG file. exportPNGImage requires two arguments, a pointer to the active GLFWwindow, and astring prefix that will be the beginning of the file name.

«exportPNGImage»=

12

Page 13: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

void exportPNGImage(GLFWwindow* window, const std::string &prefix) {std::string imagefilename = getImageFileName(prefix, std::string(".png"));if (imagefilename.length() == 0) {

throw std::runtime_error("Can’t find an available png file name.");}

Next, the previously discussed getImageFileName is used to find an unused file name, that begins with the prefix,and ends with the extension .png. If the search succeeds, then the length of the returned string will be positive. If not, thenan exception is thrown. By now you should note the defensive nature of C++ programming: always test the integrity of theprevious function call before moving forward.

«exportPNGImage»+=// Recover window dimensions from glfwint width;int height;glfwGetFramebufferSize(window, &width, &height);

The function glfwGetFramebufferSize should return the dimension of the framebuffer of the active window inpixels. We will use this information to allocate a binary vector for storing the frame buffer, using 3 bytes per pixel.

«exportPNGImage»+=// allocate a buffer to store all of the pixels in the frame buffer.GLubyte* pixels = new GLubyte [width * height * sizeof(GLubyte)* 3];if (! pixels) {

throw std::runtime_error(std::string("Can’t allocate pixel array for image of size ")+ std::to_string(width) + std::string(" by ") + std::to_string(height)+ std::string(" pixels."));

}

Here we use the dreaded new operator, a common source of memory leaks. We must remember to delete pixels beforewe exit this function. GLubyte is a typedef for an unsigned standard byte. You can see we requesting a contiguousone-dimensional vector of three of these for every pixel in the frame buffer. In the event new fails (e.g., we have run out ofmemory), pixels is set to 0. In this case, ! 0 returns true and an exception is thrown. It is probably clearer to writepixels == NULL for this test, but I like the simplicity of !.

«exportPNGImage»+=glPixelStorei(GL_PACK_ALIGNMENT, 1); // Align data by bytes so the framebuffer data will fit.glReadBuffer(GL_FRONT); // The front color buffer is the visible one.glReadPixels(0, 0, width, height, GL_BGR, GL_UNSIGNED_BYTE, pixels);

You can read about the previous three commands in the Redbook Shreiner et al. [2013]. (Commands that generally beginwith a gl belong to OpenGL.) The workhorse is glReadPixels which will copy and tightly pack the entire framebuffer intovector pixels. Choosing GL_BGR over the other options, requires a modicum of thought.

«exportPNGImage»+=// The following variables are introduced to clarify the call signature of// FreeImage_ConvertFromRawBits, which will convert the GLubyte pixel buffer// into a FreeImage buffer.const unsigned int red_mask = 0xFF0000;const unsigned int green_mask = 0x00FF00;const unsigned int blue_mask = 0x0000FF;const unsigned int bits_per_pixel = 24;const bool top_left_pixel_appears_first = false;

unsigned int bytes_per_row = 3 * width;

FIBITMAP* image = FreeImage_ConvertFromRawBits(pixels, width, height, bytes_per_row,bits_per_pixel, red_mask, green_mask,blue_mask, top_left_pixel_appears_first);

The above piece of code allocates a FreeImage bit map, called image, and formats it using the raw pixel data stored invector pixels by glReadPixels. Once this is done, we can use a FreeImage routine to save the image to a variety offormats. For this function we have selected PNG.

«exportPNGImage»+=

13

Page 14: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

// Save the image as a png file.FreeImage_Save(FIF_PNG, image, imagefilename.c_str(), 0);

// Free allocated memoryFreeImage_Unload(image);delete [] pixels;return;

}

Before exiting, we explicitly free up the memory used by image and pixels. Note that STL data structures, likeimagefilename perform their own memory management.

figureAttributes The next three functions help us construct some random shapes on the display.getTransformation computes and returns the 4-by-4 transformation matrix that corresponds to the matrix computation

T (tx, ty, 0) ·Rz(θ) · S(sx, sy, 1) =

1 0 0 tx0 1 0 ty0 0 1 00 0 0 1

·

cos θ − sin θ 0 0sin θ cos θ 0 00 0 1 00 0 0 1

·

sx 0 0 00 sy 0 00 0 1 00 0 0 1

«figureAttributes»=glm::mat4 getTransformation(GLfloat sx, GLfloat sy, GLfloat theta, GLfloat tx, GLfloat ty) {

glm::mat4 identity_mtx = glm::mat4(1.0f);glm::mat4 translate_mtx = glm::translate(identity_mtx, glm::vec3(tx, ty, 0.0f));glm::mat4 translate_rotate_mtx = glm::rotate(translate_mtx, theta, glm::vec3(0.0f, 0.0f, 1.0f));glm::mat4 translate_rotate_scale_mtx = glm::scale(translate_rotate_mtx, glm::vec3(sx, sy, 1.0f));return translate_rotate_scale_mtx;

}

There seems to be some confusion about the second argument of glm::rotate on the internet: is the angle theta de-fined in degrees or radians? My empirical supposition is that its unit is indeed radians. (This also agrees with the documentationin the glm header file, glm/gtc/matrix_transform.hpp!)

A common error when using templates is to mix the types of the argument. During the precompilation phase, C++ uses thetypes of the arguments of template functions, like the constructor glm::vec3 to determine the value of the type parameter. Ibelieve that vec3 was built to assume that that the types its three arguments are the same. Consequently a compiler error mightresult if the third argument of glm::translate above is replaced by glm::vec3(tx, ty, 0) as no template functionmatches the situation where the first to arguments of vec3 are floats, and the third is an integer.

Using the above, we can now generate a random affine transformation by selecting random variables for the horizontal andvertical scale factors (sx and sy), the rotation angle (theta) in radians, and the horizontal and vertical translation shifts (txand ty).

«figureAttributes»+=glm::mat4 getRandomTransformation() {

GLfloat sx = 0.10*static_cast<GLfloat>(rand())/RAND_MAX + 0.05f;GLfloat sy = 0.10*static_cast<GLfloat>(rand())/RAND_MAX + 0.05f;GLfloat theta = 2.0*M_PI*static_cast<GLfloat>(rand())/RAND_MAX;GLfloat tx = 2.0*static_cast<GLfloat>(rand())/RAND_MAX - 1.0;GLfloat ty = 2.0*static_cast<GLfloat>(rand())/RAND_MAX - 1.0;

return getTransformation(sx, sy, theta, tx, ty);}

In the above, the integer value returned by rand() is converted to a GLfloat using the static_cast<T>(arg)template function to ensure that floating-point division occurs.

And finally, getRandomColor computes and returns a random RGBA color as a four-vector:

«figureAttributes»+=glm::vec4 getRandomColor() {

GLfloat red = static_cast<GLfloat>(rand())/RAND_MAX;GLfloat green = static_cast<GLfloat>(rand())/RAND_MAX;GLfloat blue = static_cast<GLfloat>(rand())/RAND_MAX;GLfloat alpha = 0.75*static_cast<GLfloat>(rand())/RAND_MAX + 0.25; // not too transparent.return glm::vec4(red, green, blue, alpha);

}

Also note the application of static_cast<T>(arg) above.

14

Page 15: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

3.1.8 initialization

Within the initialization chunk the application constructs the graphical model (in this case a right, isosceles triangle, a unitsquare, and a circle (actually a 100-sided polygon).

«initialization»=void init(void) {

GLfloat triangle_positions[][2] = {{ 0.0f, 0.0f }, // vertices ordered for a TRIANGLE{ 1.0f, 0.0f },{ 0.0f, 1.0f },

};

GLfloat square_positions[][2] = {{ -0.5f, -0.5f }, // vertices ordered for a TRIANGLE_STRiP{ -0.5f, 0.5f },{ 0.5f, -0.5f },{ 0.5f, 0.5f },

};

GLfloat circle_positions[102][2];circle_positions[0][0] = 0.0f;circle_positions[0][1] = 0.0f;double delta = 2.0*M_PI/100;for (int i = 0; i < 101; i++) {

circle_positions[i+1][0] = cos(i*delta);circle_positions[i+1][1] = sin(i*delta);

}

After initializing the random number seed to the current time in seconds (the call to srand), we populate the Figs vectorwith N_FIGURES randomly constructed figures. Each figure consists of a random model (triangle, square, circle), a randomcolor, and a random affine transformation.

«initialization»=srand(time(NULL)); // initialize the random number generator rand(), using time as seed.for (int i=0; i < N_FIGURES; i++) {

Figure fig;fig.model = rand() % N_MODELS;fig.color = getRandomColor();fig.transformation = getRandomTransformation();Figs.push_back(fig);

}

Next, we define a vertex buffer object (vbo) for managing each of these position arrays. «intialization»+=

GLuint vboHandles[N_MODELS];glGenBuffers(N_MODELS, vboHandles);

GLuint triangle_position_buffer = vboHandles[0];GLuint square_position_buffer = vboHandles[1];GLuint circle_position_buffer = vboHandles[2];

Here we declare a two-element array vboHandes that will maintain references to two vertex buffer objects. The functionglGenBuffers asks OpenGL to allocate three buffers and return the handle to each in the provided array. The next few linessimply provide a more comprehensible name to each handle.

«intialization»+=The vertex positions are passed to OpenGL using the three globally defined vertex array objects (VertexArrayObject),

with one of the buffer object (vboHandles) assigned to each.glGenVertexArrays(N_MODELS, VertexArrayObject);

Here, glGenVertexArrays requests a handle to an OpenGL vertex array object, which we will use to representcollectively both buffers. The handle is stored in VertexArrayObject which is a global variable. The value ofVertexArrayObject can then be used by the callback function display, defined below, whenever these geometric ob-jects need to be redisplayed.

«intialization»+=

15

Page 16: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

glBindVertexArray(VertexArrayObject[0]);glEnableVertexAttribArray(0);glBindBuffer(GL_ARRAY_BUFFER, triangle_position_buffer);glBufferData(GL_ARRAY_BUFFER, sizeof(triangle_positions), triangle_positions, GL_STATIC_DRAW);glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);

The above loads the three vertices in triangle_position into the triangle_position_buffer. The constantGL_STATIC_DRAW is a hint to OpenGL indicating that we will not be changing the values in this buffer. In this case thedata will quite likely be stored in the GPU memory. The fourth line, glVertexAttribPointer, passes this informationto the vertex shader. The first argument, 0, indicates the location that the vertex shader will use to find the position of eachvertex. (This will be clearer after you have examined the code for the vertex shader that appears below.) The second argument 2indicates that each vertex position is described by 2 memory registers each of type GL_FLOAT. By setting the fourth argumentto GL_FALSE, we declare that we do not want the GPU to normalize the coordinates to fit within the display window.

The above segment of code is repeated for both the square and circle models.

«intialization»+=glBindVertexArray(VertexArrayObject[1]);glEnableVertexAttribArray(0);glBindBuffer(GL_ARRAY_BUFFER, square_position_buffer);glBufferData(GL_ARRAY_BUFFER, sizeof(square_positions), square_positions, GL_STATIC_DRAW);glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);

glBindVertexArray(VertexArrayObject[2]);glEnableVertexAttribArray(0);glBindBuffer(GL_ARRAY_BUFFER, circle_position_buffer);glBufferData(GL_ARRAY_BUFFER, sizeof(circle_positions), circle_positions, GL_STATIC_DRAW);glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);

The next step is to compile, link, and upload the shader program to the GPU. Most of the work is performed by our utilityfunction, createVertexFragmentProgram. The upload is performed by glUseProgram.

«intialization»+=GLuint shader_program = createVertexFragmentProgram(std::string("kandinsky.vert"),

std::string("kandinsky.frag"));glUseProgram(shader_program);

«intialization»+=Once the shader program has been defined, we can query OpenGL for the locations for the two uniform variables required

by the vertex shader (kandinsky.vert): color holds the color of the active vertices, and transformation retains theaffine transformation to be applied to each active vertex. These handles (VSL_color and VSL_transformation), whichare here treated as global variables, allow us to redefine the color and model transformation for each figure that we draw withinthe display() procedure. Note that the first argument of glGetUniformlocation identifies the handle to the shaderprogram, defined above. The second argument is a character string of the corresponding variable name in the shader program.Note that there is only one namespace in the shader programs, so avoid duplicating the same name for different purposes.

VSL_color = glGetUniformLocation(shader_program, "color");VSL_transformation = glGetUniformLocation(shader_program, "transformation");

}

3.1.9 callbacks

Four call back functions are used:

«callbacks»=<<display_callback>><<keyboard_callback>><<GLFW_error_callback>><<FreeImage_error_callback>>

Each will now be described in sequence.

1. display_callback

The display callback, display(), is technically not a callback function in this application because it is explicitlyinvoked main below.. However, the purpose of this function, to redraw the graphics content of the framebuffer is imple-

16

Page 17: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

mented as a callback function in many OpenGL applications, (e.g., those implemented using GLUT or freeGlut. Normallythis function is called whenever the content of the graphics window needs to be drawn, for example, following eventsthat correspond to the window being resized, or becoming unobstructed by another window that was just minimized.

«display_callbacks»=

//------------------------------------------------------------------------------//// display() should be called whenever the canvas needs to be refreshed. Here it// redraws the content of the Figs vector,

void display(void) {glClearColor(0.5,0.5,0.5,1); // grayglClear(GL_COLOR_BUFFER_BIT);glEnable(GL_BLEND);glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);for (int i = 0; i < N_FIGURES; i++) {

glUniform4fv(VSL_color, 1, &Figs[i].color[0]);glUniformMatrix4fv(VSL_transformation, 1, GL_FALSE, &Figs[i].transformation[0][0]);glBindVertexArray(VertexArrayObject[Figs[i].model]);glDrawArrays(PrimitiveModeToken[Figs[i].model], 0, N_Vertices[Figs[i].model]);

}

glfwSwapBuffers(gWindow);}

Usually, display will redraw the entire framebuffer. We begin by clearing the display: here setting every pixel to gray.(Using a background color other than white or black can sometimes help discover the common error that occurs whenthe color of the foreground and background colors match.) Since the call to glClearColor does not need to be calledrepeatedly, it could be moved into our init function. The function glClear performs the actual clearing.

The next two lines enable alpha blending to occur: see pages 166-169 of the OpenGL Redbook [Shreiner et al., 2013].Then for each figure in the Figs global vector, we bind the vertex shader’s uniform color variable to the figure color,its uniform transformation variable to the figure’s transformation. The call to glBindVertexArray select’s thevertex array that corresponds to the figure’s base model (triangle, square or circle).

The function glDrawArrays then renders the base model using the appropriate “primitive mode” (see page 90 of theRedbook). Here the globally defined array N_Vertices retains the number of vertices in the current base model.

All of the drawing takes place off-screen. The call to glfwSwapBuffers informs OpenGL to exchange the newversion of the framebuffer with the old one in the graphics window, gWindow, a global variable that is defined in themain function below.

2. keyboard_callback

The second callback function responds to keyboard events and is managed by GLFW. The first parameter of keyboard,window, identifies the window that was active when the keyboard event was detected. the integer keycode identifiesthe key that was pressed (see the GLFW keycode reference). The scanCode is a code that is platform dependent. Theaction reveals if the key was pressed, repeated, or released. The modifier integer represents a bit array that indicatesthe combination of modifier keys (shift, control, alt, or super) that were pressed, if any.

In the function below, the detection of a lowercase p triggers a message on the console, and the creation of a PNG imagefile that duplicates the contents of the active framebuffer. If the user presses a lowercase q, then GLFW will receive awindow close event. This will in turn result in the termination of the while loop in main; consequently, the programwill exit.

«keyboard_callback»=

17

Page 18: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

void keyboard(GLFWwindow* window, int keyCode, int scanCode, int action, int modifiers) {switch (keyCode) {

case GLFW_KEY_P:if (action == GLFW_PRESS && modifiers == 0x0000) {

std::cout << "Pressed a lower-case p, scanCode = " << scanCode << std::endl;exportPNGImage(window, std::string("kandinsky"));

}break;

case GLFW_KEY_Q:if (action == GLFW_PRESS && modifiers == 0x0000) {

std::cout << "Pressed a lower-case q, scanCode = " << scanCode << std::endl;glfwSetWindowShouldClose(window, GL_TRUE);

}break;

}return;

}

3. GLFW_error_callback

The next callback is used by GLFW, in the event that an error occurs. Hopefully, it will not need to be called.

«GLFW_error_callback»=

//------------------------------------------------------------------------------//// error_callback is used by GLFW.

void GLFW_error_callback(int errorCode, const char* msg) {throw std::runtime_error(msg);

}

GLFW_error_callback simply throws a C++ runtime exception, which will abort the application, since we are notattempting to catch it.

4. FreeImage_error_callback

Likewise, we define a callback function for the errors discovered by the FreeImage library:

«FreeImage_error_callback»=

//------------------------------------------------------------------------------//// free image error callbackvoid fi_error_callback(FREE_IMAGE_FORMAT fif, const char* msg) {

if (fif != FIF_UNKNOWN) {std:: cerr << FreeImage_GetFormatFromFIF(fif) << " format." << std:: endl;

}throw std::runtime_error(msg);

}

And now for the main function, which drives all of the above.

3.1.10 main

«main»=int main(int argc, char** argv) {

// initialize GLFWglfwSetErrorCallback(GLFW_error_callback);if (!glfwInit())

throw std::runtime_error("glfwInit failed!");

Here we are using the third party GLFW library to act as an interface between our application and the window managerof the host system. We will see below, and in subsequent examples, how GLFW can be used to create an OpenGL context,enable a stereo display, respond to keyboard and mouse events, and measure time increments. The above code simply registersGLFW_error_callback(), defined above, as the error callback function, and initializes the GLFW subsystem. In the eventthat the initialization fails, a runtime exception is thrown.

«main»+=

18

Page 19: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

// Create the main windowglfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

The above snippet informs GLFW that we desire our current application to be compatible with all future versions ofOpenGL, starting with OpenGL 3.2. Declaring the GLFW_OPENGL_CORE_PROFILE indicates that our application will useshaders, instead of the older direct draw paradigm. We finally tell GLFW that we prefer drawing windows with fixed dimension:it will not be possible for the user to change the size of the window by dragging the edges or corners; nor will it be possible tomaximize the window to cover the entire display.

«main»+=int glfw_major;int glfw_minor;int glfw_rev;glfwGetVersion(&glfw_major, &glfw_minor, &glfw_rev);std::cout << "GLFW version: " << glfw_major << "." << glfw_minor << "." << glfw_rev << std::endl;

The above prints the version of our GLFW library on the console.

«main»+=gWindow = glfwCreateWindow((int) CANVAS.x, (int) CANVAS.y, argv[0], NULL, NULL);if (! gWindow)

throw std::runtime_error("Can’t create a glfw window!");

glfwMakeContextCurrent(gWindow);

The glfwCreateWindow asks GLFW to obtain a GUI window (with appropriate decorations) from the window managerof the host operating system with the specified horizontal and vertical dimensions, and title. For the latter, we elect to useargv[0], the name of the application. By setting the fourth argument to NULL we indicate that we want to use an applicationwindow, instead of the full screen. By setting the final argument to NULL we indicate that we will not be sharing the contextthis window with a previously created one.

«main»+=glfwSetKeyCallback(gWindow, keyboard);

Here we bind keyboard as a key callback function for the graphics window, gWindow.

«main»+=glewExperimental = GL_TRUE; // Prevents a segmentation fault on Mac OSXif (glewInit() != GLEW_OK)

throw std::runtime_error("Can’t initialize glewInit!");

Our application also employs GLEW the OpenGL Extension Wrangler, a third-party toolkit that orchestrates miscellaneousOpenGL extensions: tools written by third parties that extend the capabilities of OpenGL. Normally, the host OpenGL systemkeeps track of the extensions that have been installed. By activating the glewExperimental mode in the line above, weinstruct the toolkit to access those OpenGL extensions that might be present, but are not reported by the host OpenGL system.This setting must be applied before GLEW is initialized.

«main»+=std::cout << "OpenGL version: " << glGetString(GL_VERSION) << std::endl;std::cout << "GLSL version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;std::cout << "Vendor: " << glGetString(GL_VENDOR) << std::endl;std::cout << "Graphics engine: " << glGetString(GL_RENDERER) << std::endl;

if (! GLEW_VERSION_3_2)throw std::runtime_error("OpenGL 3.2 is not supported!");

The above lines print important information about the version of OpenGL that is running on your system, and informationabout the active graphics hardware. Since we are using shader programs, we ensure that the version of OpenGL running is 3.2or greater.

We now initialize the FreeImage software package. This open source library (available for Linux, MacOS X and Windows),

19

Page 20: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

provides a simple interface for loading, modifying, and writing to digital image files in a variety of popular formats (e.g., png,tiff and jpg).

«main»+=// Initializing FreeImage:FreeImage_Initialise(TRUE); // only load local plugins.FreeImage_SetOutputMessage(fi_error_callback);

// Let’s see the version of FreeImage:std::cout << "FreeImage version = " << FreeImage_GetVersion() << std::endl;

«main»+=init(); // Initialize the model.

int update_count = 0;while(! glfwWindowShouldClose(gWindow)) {

update_count++;display();glfwWaitEvents(); // or, replace with glfwPollEvents();

}

The

call to init() of course initializes our vertex models, and compiles and links the vertex and fragment shader programs. Thesubsequent while loop performs the main display loop; glfwWindowShouldClose(gWindow) returns true if the indi-cated window has received a closed event, which occurs within the keyboard-callback function when the user presses a q.The number of times this loop is iterated is counted and ultimately printed on the console. The function glfwWaitEvents()allows the application to take a short nap between events (window exposure, keyboard taps, mouse movements, etc.) while theapplication has the focus of the window manager.

«main»+=FreeImage_DeInitialise();glfwTerminate();std::cout << argv[0] << " gracefully exits after " << update_count << " window updates." << std::endl;return 0; // Can be safely omitted, but the application will still return 0.

}

It is important to invoke both FreeImage_Deintialize() and glfwTerminate() before terminating the applica-tion, in order to release resources, avoiding memory leaks that the window manager might not catch. Since main is of typeint, we return 0 indicating successful termination.

3.2 Shader FilesEvery modern OpenGL application that adheres to the core profile, is required to provide a vertex and fragment shader program.These programs are written in a language that is called GLSL, (short for OpenGL Shading Language). GLSL looks a lot likeC. For this application, the shader programs are quite simple.

3.2.1 The vertex shader: kandinsky.vert

The vertex shader is invoked a the beginning of the graphics pipeline. Every shape that is rendered by OpenGL is delimited byvertices, which can possess numerous attributes. Here we restrict these to position and color.

«kandinsky.vert»=

20

Page 21: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

#version 330

//<<caution>>

layout (location=0) in vec2 vertexPosition;

uniform vec4 color;uniform mat4 transformation;

out vec4 vertex_color;

void main() {vertex_color = color;gl_Position = transformation * vec4(vertexPosition, 0.0f, 1.0f);

}

Every shader is required to specify its version in “preprocesser” syntax, i.e., with a hash symbol. Here #version 330specifies GLSL version 3.30 or higher. The next line defines the vertexPosition input channels for the vertex shader: the spatialcoordinates of each vertex as a two-dimensional floating point vector (in vec2), for the x and y components. For simplicity,we will render each shape with a uniform fill color (fillcolor), which is therefore passed to the vertex shader as a uniformfour-vector (red, green, blue, and alpha components). Likewise every vertex in the same shape is subject to the same affinetransformation.

The next statement declares color as a four-dimensional output channel to be read eventually by the fragment shader.The body of the shader appears as a short C program. It is important to adhere to this signature: main is a function of

zero arguments that returns void. This particular vertex shader is rather trivial; it is often called a pass-through shader: theoutput color is the same as the vertexColor (what comes in, goes out); and the four-dimensional position of the vertex(gl_Position) shares its first two components with the incoming vertexPosition. Note that we set the z coordinateexplicitly to 0, and the fourth component (which is used for normalization) to 1.

3.2.2 The fragment shader: kandinsky.frag

The fragment shader is employed at the end of the graphics pipeline. A fragment is an abstraction that in practical terms isapplied to each pixel in the framebuffer that belongs to a drawable region. Fragments have no form, just color.

«kandinsky.frag»=#version 330

//<<caution>>

in vec4 vertex_color;out vec4 fragmentColor;

void main() {fragmentColor = vertex_color;

}

The above shader program begins with the mandatory version information. The color input channel is then declared.Note that the name and type match the output color channel of vertexShader. We then declare fragmentColor as afour-dimensional output channel, which will be piped to the framebuffer. The procedure defined in main is again another pass-through shader, the only twist is that a vec4 type cast is used to add an alpha channel of 1 (full opacity) to the three-componentinput color vector.

3.3 CMakeLists.txt

For optimal portability, this application is designed to be compiled and linked using CMake. The following CMake file identifiesthe source code dependencies and library locations. Note that the reader will likely need to modify some items defined below,especially those specified by set.

«CMakeLists.txt»=

21

Page 22: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

#<<caution>>

cmake_minimum_required(VERSION 3.0.0)project (kandinsky)add_executable(kandinsky kandinsky.cpp)# target_compile_features(kandinsky PRIVATE cxx_generalized_initializers)

find_package(OpenGL REQUIRED)find_package(GLEW REQUIRED)set(CMAKE_CXX_FLAGS "-std=c++1y ${CMAKE_CXX_FLAGS} -g")

## Very likely you will need to adjust the following paths to match your installation.#

set(GLM_INCLUDE_DIR "/opt/local/include" CACHE PATH "" FORCE)set(GLFW_LIBRARIES "/opt/local/lib/libglfw.dylib" CACHE FILEPATH "" FORCE)set(GLFW_INCLUDE_DIR "/opt/local/include" CACHE PATH "" FORCE)set(FREEIMAGE_INCLUDE_DIR "/opt/local/include" CACHE PATH "" FORCE)set(FREEIMAGE_LIBRARIES "/opt/local/lib/libfreeimage.dylib" CACHE FILEPATH "" FORCE)

include_directories(${GLFW_INCLUDE_DIR})include_directories(${GLEW_INCLUDE_DIRS})include_directories(${GLM_INCLUDE_DIR})include_directories(${OPENGL_INCLUDE_DIR})include_directories(${FREEIMAGE_INCLUDE_DIR})target_link_libraries(kandinsky ${OPENGL_LIBRARIES})target_link_libraries(kandinsky ${GLFW_LIBRARIES})target_link_libraries(kandinsky ${GLEW_LIBRARIES})target_link_libraries(kandinsky ${FREEIMAGE_LIBRARIES})

The above reflects a little laziness on my part. Really, I should create a FindGLFW.cmake, FindFreeImage.cmake, etc.Instead, I am guilty of kludging.

3.4 Building the project.The following Bourne shell can be used to build the project from within a command-line shell. Simply, enter ./build_mefrom the command line. Good luck.

«build_me»=#!/bin/sh##<<caution>>#mkdir -p build/wscd build/wscmake ..make allcd ..ws/kandinsky

4 The BibliographyThe BibTeX references are exported to the file kandinsky.bib when the project is tangled. Consequently, it is best toreweave the project in LATEX after the tangling kandinsky.org.

22

Page 23: Kandinsky - cs.uvm.edursnapp/teaching/cs274/src/kandinsky/kandinsky.p… · His 2008 presentation at Google introduces the main features of orgmode. If you are new to Emacs, it is

% kandinsky.bib

@Book{Redbook2013,author={Dave Shreiner and Graham Sellers and John Kessenich and Bill Licea-Kane},title ={OpenGL Programming Guide},edition={8th},publisher={Addison-Wesley},address={Upper Saddle River, NJ},year = 2013,

}

@Book{Stroustrup2013,author={Bjarne Stroustrup},title ={The C++ Programming Language},edition={4thn},publisher={Addison-Wesley},address={Upper Saddle River, NJ},year=2013,

}

@Book{Whitford1967,author = {Frank Whitford},title = {Kandinsky},publisher = {Paul Hamlyn, Ltd.},address = {London},year = 1967

}

ReferencesDave Shreiner, Graham Sellers, John Kessenich, and Bill Licea-Kane. OpenGL Programming Guide. Addison-Wesley, Upper

Saddle River, NJ, 8th edition, 2013.

Bjarne Stroustrup. The C++ Programming Language. Addison-Wesley, Upper Saddle River, NJ, 4thn edition, 2013.

Frank Whitford. Kandinsky. Paul Hamlyn, Ltd., London, 1967.

23