In this chapter we will dissect our GLFW spinning triangle sample. We will move “game” implementation into a separate class, which we will reuse later in Android version. It will be triangle rendering related code. In order to make it platform-independent we will separate it from platform-specific calls. All environment related code, such as window creation, GL initialization, and so on, we will leave in main.cpp as is.
- Start Visual Studio, open C:\CPP\a999hello\p_windows\p_windows.sln solution.
2. Under p_windows project add new filter.
Right-click on p_windows project -> Add -> New Filter. Name - xTheGame
3. Under xTheGame add new class. We won’t use “add Class” since it will place files at it’s own discretion, not where we want. We’ll better add it file-by-file.
Right-click on xTheGame -> Add -> New Item,
- Header File (.h)
- Name – TheGame.h
- Change location to C:\CPP\a999hello\
Add.
TheGame class will consist of 5 methods/functions and 3 variables, summarized in TheGame.h.
Copy and paste following code to TheGame.h:
#pragma once
class TheGame
{
public:
int screenSize[2];
float screenRatio;
bool bExitGame;
public:
int run();
int getReady();
int drawFrame();
int cleanUp();
int onScreenResize(int width, int height);
};
4. Now - implementation:
Right-click on xTheGame -> Add -> New Item,
- C++ File (.cpp)
- Name – TheGame.cpp
- Location - C:\CPP\a999hello\
Add.
Code:
#include "TheGame.h"
#include "platform.h"
#include "linmath.h"
static const struct
{
float x, y;
float r, g, b;
} vertices[3] =
{
{ -0.6f, -0.4f, 1.f, 0.f, 0.f },
{ 0.6f, -0.4f, 0.f, 1.f, 0.f },
{ 0.f, 0.6f, 0.f, 0.f, 1.f }
};
static const char* vertex_shader_text =
"#version 320 es\n"
"precision lowp float;\n"
"uniform mat4 MVP;\n"
"in vec3 vCol;\n"
"in vec2 vPos;\n"
"out vec3 color;\n"
"void main()\n"
"{\n"
" gl_Position = MVP * vec4(vPos, 0.0, 1.0);\n"
" color = vCol;\n"
"}\n";
static const char* fragment_shader_text =
"#version 320 es\n"
"precision lowp float;\n"
"in vec3 color;\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(color, 1.0);\n"
"}\n";
unsigned int vao_buffer, vertex_buffer, vertex_shader, fragment_shader, program;
int mvp_location, vpos_location, vcol_location;
float angle_z = 0;
int TheGame::getReady() {
bExitGame = false;
glGenVertexArrays(1, &vao_buffer);
glBindVertexArray(vao_buffer);
glGenBuffers(1, &vertex_buffer);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &vertex_shader_text, NULL);
glCompileShader(vertex_shader);
fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &fragment_shader_text, NULL);
glCompileShader(fragment_shader);
program = glCreateProgram();
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glLinkProgram(program);
mvp_location = glGetUniformLocation(program, "MVP");
vpos_location = glGetAttribLocation(program, "vPos");
vcol_location = glGetAttribLocation(program, "vCol");
glEnableVertexAttribArray(vpos_location);
glVertexAttribPointer(vpos_location, 2, GL_FLOAT, GL_FALSE,
sizeof(vertices[0]), (void*)0);
glEnableVertexAttribArray(vcol_location);
glVertexAttribPointer(vcol_location, 3, GL_FLOAT, GL_FALSE,
sizeof(vertices[0]), (void*)(sizeof(float) * 2));
return 1;
}
int TheGame::drawFrame() {
myPollEvents();
mat4x4 m, p, mvp;
glClear(GL_COLOR_BUFFER_BIT);
angle_z += 0.01f;
mat4x4_identity(m);
mat4x4_rotate_Z(m, m, angle_z);
mat4x4_ortho(p, -screenRatio, screenRatio, -1.f, 1.f, 1.f, -1.f);
mat4x4_mul(mvp, p, m);
glUseProgram(program);
glUniformMatrix4fv(mvp_location, 1, GL_FALSE, (const GLfloat*)mvp);
glDrawArrays(GL_TRIANGLES, 0, 3);
mySwapBuffers();
return 1;
}
int TheGame::cleanUp() {
return 1;
}
int TheGame::onScreenResize(int width, int height) {
if (screenSize[0] == width && screenSize[1] == height)
return 0;
screenSize[0] = width;
screenSize[1] = height;
screenRatio = (float)width / (float)height;
glViewport(0, 0, width, height);
mylog(" screen size %d x %d\n", width, height);
return 1;
}
int TheGame::run() {
getReady();
while (!bExitGame) {
drawFrame();
}
cleanUp();
return 1;
}
- Please note, that this code doesn’t have platform-specific references (such as GLAD and GLFW), which means that it can be re-used on another platform, particularly on Android, which we’ll definitely try in the next chapter.
5. Also (as usual) we need to notify p_windows project where to look for TheGame.h.
Right-click on p_windows project -> Properties, All Configurations, Win32, Configuration Properties -> C/C++ -> General, open Additional Include Directories -> Edit, add new line.
IMPORTANT: This time instead of navigating to C:\CPP\a999hello (where TheGame is actually located), manually add
..
Yes, it’s just 2 dots, which means 1 folder level up (from p_windows appication root folder).
Ok, Apply, Ok.
6. As planned, platform-specific code we'll place outside of TheGame class. Particularly, GLFW and GLAD declarations, events handling and swap screen buffers call.
So, open platform.h and replace code by:
#pragma once
#include <glad/glad.h>
void mylog(const char* _Format, ...);
void mySwapBuffers();
void myPollEvents();
- Also, it was a nice occasion to move mylog(..) from .h to .cpp
7. Implementation:
Right-click on xPlatform -> Add -> New Item,
- C++ File (.cpp)
- Name – platform.cpp
- Location - C:\CPP\p_windows\
Add.
Code:
#include <stdarg.h>
#include <stdio.h>
#include <GLFW/glfw3.h>
#include "platform.h"
#include "TheGame.h"
extern GLFWwindow* myMainWindow;
extern TheGame theGame;
void mylog(const char* _Format, ...) {
#ifdef _DEBUG
va_list _ArgList;
va_start(_ArgList, _Format);
vprintf(_Format, _ArgList);
va_end(_ArgList);
#endif
};
void mySwapBuffers() {
glfwSwapBuffers(myMainWindow);
}
void myPollEvents() {
glfwPollEvents();
//check if closing the window
theGame.bExitGame = glfwWindowShouldClose(myMainWindow);
//check screen size
int width, height;
glfwGetFramebufferSize(myMainWindow, &width, &height);
theGame.onScreenResize(width, height);
}
8. main.cpp, accordingly, is much shorter now:
#include <glad/glad.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <stdlib.h>
#include "TheGame.h"
#include "platform.h"
static void error_callback(int error, const char* description)
{
mylog("Error: %s\n", description);
}
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GLFW_TRUE);
}
TheGame theGame;
GLFWwindow* myMainWindow;
int main(void)
{
glfwSetErrorCallback(error_callback);
if (!glfwInit())
exit(EXIT_FAILURE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
myMainWindow = glfwCreateWindow(640, 480, "OurProject", NULL, NULL);
if (!myMainWindow)
{
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwSetKeyCallback(myMainWindow, key_callback);
glfwMakeContextCurrent(myMainWindow);
gladLoadGLES2Loader((GLADloadproc)glfwGetProcAddress); //gladLoadGL(glfwGetProcAddress);
glfwSwapInterval(1);
theGame.run();
glfwDestroyWindow(myMainWindow);
glfwTerminate();
exit(EXIT_SUCCESS);
}
Replace all code in main.cpp by this one.
- Please note, that this code does NOT contain any game-specific code, which means that we can re-use it “as is” in our ANY future Windows project.
9. Now build and run (green arrow). Works!
- We also can run it in Release configuration, then it will work without Console window.
Our next task – to make it work on Android.