HelloTriangle

The first program that you typically write in any programming language is HelloWorld, where you simply print out the words: “Hello World!” to the console. When you graduate to using a windowing toolkit, the Hello World program displays a window with “Hello World!” in it. The post Creating wxWidgets Programs with Visual Studio 2015 – Part 2 gives an example using wxWidgets. In OpenGL, about the simplest program you can write is HelloTriangle which displays a white triangle on a black background; there is no text. I will develop HelloTriangle in this post.

HelloTriangle will be developed using Visual Studio 2015, wxWidgets, and OpenGL. Actually, Visual Studio is simply the development environment used by me. You could in fact use any environment you want provided it supports wxWidgets and GLEW (for OpenGL).

To begin, we need to create a bare-bones wxWidgets application. This is discussed in Creating wxWidgets Programs with Visual Studio 2015 – Part 1. Call the project HelloTriangle. Change MyProjectApp to HelloTriangleApp. Once you have that program working, you should install the NuGet package called nupengl.core, to include GLEW. Alternatively, you could simply install GLEW on your system and use that. Installing nupengl.core is discussed in Visual Studio, wxWidgets, and OpenGL. Once this is done, we have all of the parts needed to begin coding HelloTriangle.

This post will follow along with the tutorials at open.gl. In the tutorials on that website, the Context creation page shows how to set up an OpenGL program using SFML, SDL, and GLFW. We will use wxWidgets instead. The Drawing polygons page shows how to draw a triangle and then colour the triangle using vertex shaders and fragment shaders. We will not be using shaders; this post simply draws a white triangle on a black background. The code shown in this post can then be modified to use shaders.

If you have followed the steps above, you have a very simple program that simply displays an empty window. This will now be modified. We will add two new classes, called TriangleWindow and TriangleCanvas.

TriangleCanvas subclasses wxGLCanvas. wxGLCanvas is the wxWidgets class that displays OpenGL graphics. In the TriangleCanvas class, we will initialize OpenGL, create the wxGLContext required for OpenGL, and set up the graphics for drawing in the constructor. The paint event handler will paint the background and the triangle. This code is in the OnPaint method. Finally, the destructor does some clean up.

Here is the header file for TriangleCanvas:

#pragma once
#include <memory>
#include "wx/glcanvas.h"

class TriangleCanvas : public wxGLCanvas
{
public:
    TriangleCanvas(wxWindow* parent, wxWindowID id = wxID_ANY,
        const int* attribList = 0, const wxPoint& pos = wxDefaultPosition,
        const wxSize& size = wxDefaultSize, long style = 0L,
        const wxString& name = L"GLCanvas",
        const wxPalette& palette = wxNullPalette);

    virtual ~TriangleCanvas();
    TriangleCanvas(const TriangleCanvas& tc) = delete;
    TriangleCanvas(TriangleCanvas&& tc) = delete;
    TriangleCanvas& operator=(const TriangleCanvas& tc) = delete;
    TriangleCanvas& operator=(TriangleCanvas&& tc) = delete;

private:
    void InitializeGLEW();
    void SetupGraphics();
    void OnPaint(wxPaintEvent& event);

    std::unique_ptr<wxGLContext> m_context;
    GLuint m_vbo; // vertex buffer pointer
    GLuint m_vao; // vertex array pointer
};

The private methods InitializeGLEW and SetupGraphics are helper methods that are called in the constructor. m_context holds the OpenGL context and m_vbo and m_vao are properties used by OpenGL.

Here is the constructor:

TriangleCanvas::TriangleCanvas(wxWindow* parent, wxWindowID id, 
        const int* attribList, const wxPoint& pos, const wxSize& size,
        long style, const wxString& name, const wxPalette& palette)
	: wxGLCanvas(parent, id, attribList, pos, size, style, name, palette),
	m_vbo(0), m_vao(0)
{
	m_context = std::make_unique<wxGLContext>(this);
	Bind(wxEVT_PAINT, &TriangleCanvas::OnPaint, this);
	
	SetCurrent(*m_context);
	InitializeGLEW();
	SetupGraphics();
}

This code is straight-forward. It creates an OpenGL context for the TriangleCanvas, and then binds the OnPaint method to the paint event. Following this, the OpenGL context is set and the two helper methods are called to complete the initialization. The helper methods will only succeed if the context has been set.

InitializeGLEW simply initializes the GLEW so that the OpenGL methods can be accessed. The method is shown here:

void TriangleCanvas::InitializeGLEW()
{
	glewExperimental = true;
	GLenum err = glewInit();
	if (err != GLEW_OK) {
		const GLubyte* msg = glewGetErrorString(err);
		throw std::exception(reinterpret_cast<const char*>(msg));
	}
}

glewInit() must be called to obtain information on the supported extensions from the graphics driver. Experimental and pre-release drivers may not report every available extension through the standard call to glewInit; in that case, GLEW will report that the extension is not supported. By setting glewExperimental to true (or GL_TRUE) before calling glewInit, all of the extensions are exposed.

Following the call to glewInit, the code checks the status returned from the glewInit call, and throws an exception if the call failed. I have not included any code in this program to catch the exception, so the program will simply terminate. Catching the exception is left as an exercise for the reader.

This is the code for the SetupGraphics method:

void TriangleCanvas::SetupGraphics()
{
	// define vertices
	float points[] = {
		0.0f, 0.5f,
		0.5f, -0.5f,
		-0.5f, -0.5f
	};
	// upload vertex data
	glGenBuffers(1, &m_vbo);
	glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
	// setup vertex array objects
	glGenVertexArrays(1, &m_vao);
	glBindVertexArray(m_vao);
	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
}

I will not provide an explanation of this code; it is available on the Drawing polygons page on open.gl.

The glGenBuffers and glGenVertexArrays calls in the code above allocate buffers on the graphics card. These buffers must be released before the program terminates or a memory leak will result. The code in the TriangleCanvas destructor performs this task:

TriangleCanvas::~TriangleCanvas()
{
	SetCurrent(*m_context);
	glDeleteVertexArrays(1, &m_vao);
	glDeleteBuffers(1, &m_vbo);
}

All of the drawing is performed in the OnPaint method:

void TriangleCanvas::OnPaint(wxPaintEvent& event)
{
	SetCurrent(*m_context);

	// set background to black
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	// draw the graphics
	glDrawArrays(GL_TRIANGLES, 0, 3);
	// and display
	glFlush();
	SwapBuffers();
}

This code is quite simple. The context is set. the glClearColor and glClear calls set a black background, and glDrawArrays draws the triangle. glFlush is called to force the drawing to the backup buffer, and SwapBuffers swaps the buffers to display the buffer that OnPaint just drew on.

That completes the TriangleCanvas.cpp file except for the preprocessor directives at the top of the file:

#include "wx/wxprec.h"
#include <GL/glew.h>
#include "TriangleCanvas.h"

#pragma comment(lib, "glew32.lib")

The TriangleWindow class is very simple. All it does is create a TriangleCanvas object and attach it to the window. Here is the content of TriangleWindow.h:

#pragma once

class TriangleWindow : public wxFrame
{
public:
	TriangleWindow(wxWindow* parent, const std::wstring& title, const wxPoint& pos = wxDefaultPosition,
		const wxSize& size = wxDefaultSize);
	virtual ~TriangleWindow();
	TriangleWindow(const TriangleWindow& tw) = delete;
	TriangleWindow(TriangleWindow&& tw) = delete;
	TriangleWindow& operator=(const TriangleWindow& tw) = delete;
	TriangleWindow& operator=(TriangleWindow&&) = delete;
};

and here is the content of TriangleWindow.cpp:

#include "wx/wxprec.h"
#include "TriangleCanvas.h"
#include "TriangleWindow.h"

const int triCanvasID = 2000;			// TriangleCanvas widget ID

TriangleWindow::TriangleWindow(wxWindow* parent, const std::wstring& title, const wxPoint& pos,
	const wxSize& size)
	: wxFrame(parent, wxID_ANY, title, pos, size, wxMINIMIZE_BOX | wxCLOSE_BOX | 
		wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN)
{
	TriangleCanvas* canvas = new TriangleCanvas(this, triCanvasID, nullptr, { 0, 0 },
	{ 800, 800 });
	Fit();
	Centre();
}

TriangleWindow::~TriangleWindow()
{
}

Displaying the window with the triangle requires only two changes to HelloTriangleApp.cpp. Add a #include directive for TriangleWindow.h, and replace the first line of HelloTriangleApp::OnInit with

	TriangleWindow* mainFrame = new TriangleWindow(nullptr, L"Hello Triangle!");

Now build and run the application. Here is the resulting window:

HelloTriangle

That’s all there is to it.

Advertisements

2 thoughts on “HelloTriangle

  1. Pingback: Points, Lines, Triangles, Quadrilaterals, and Polygons | Using C++

  2. Pingback: Drawing Circles With OpenGL | Using C++

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s