Chapter 4. Platform independence

Visual Studio, C++, OpenGL ES, cross-platform


In the previous 2 chapters, we have successfully "married" Android and C++, Windows and OpenGL ES. Now, having 2 C++/GLES projects for 2 different platforms, let's see what is common and what is not.

The difference actually looks frightening, seems like nothing matches... Except C++ and GL syntax, which leaves us some hope.

Well, then let’s try to split the code to platform-specific part and to potentially reusable core.

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.

1. Start Visual Studio, open C:\CPP\a999hello\pw\pw.sln solution.


2. Under pw project add new filter.

Right-click on pw project -> Add -> New Filter. Name - xTheApp


3. Under xTheApp we'll add a new class. We won’t use “add Class” option 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 xTheApp -> Add -> New Item,

  • Header File (.h)
  • Name – TheApp.h
  • Change location to C:\CPP\a999hello\

Add.

  • Please note: we are saving it in a999hello root folder, accessible for both pw and pa projects.

TheApp class will consist of 5 methods/functions and 3 variables, summarized in TheApp.h.

Copy and paste following code to TheApp.h:

#pragma once

class TheApp
{
public:
	int screenSize[2];
	float screenRatio;

	bool bExitApp;
public:
	int run();
	int getReady();
	int drawFrame();
	int cleanUp();
	int onScreenResize(int width, int height);
};



4. Now - implementation:

Right-click on xTheApp -> Add -> New Item,

  • C++ File (.cpp)
  • Name – TheApp.cpp
  • Location - C:\CPP\a999hello\

Add.

Code:

#include "TheApp.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 TheApp::getReady() {
    bExitApp = 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 TheApp::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 TheApp::cleanUp() {
    return 1;
}
int TheApp::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 TheApp::run() {
    getReady();
    while (!bExitApp) {
        drawFrame();
    }
    cleanUp();
    return 1;
}

  • Please note, that this code doesn’t have platform-specific references (such as GLAD or 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 pw project where to look for TheApp.h.

Right-click on pw project -> Properties, All Configurations, x64, Configuration Properties -> C/C++ -> General, open Additional Include Directories -> Edit, add new line:

..

Yes, it’s just 2 dots, which means 1 folder level up (from pw application root folder).

Ok, Apply, Ok.


6. As planned, platform-specific code we'll place outside of TheApp class. Particularly, GLAD and GLFW declarations, events handling and swap screen buffers call. Also I added printf-alike "mylog" function, which can print debug messages in console window.

Under pw project add new filter xPlatform.

Under xPlatform add new item

Header File (.h)

Name: platform.h

Location: C:\CPP\p_windows\

Code:

#pragma once
#include <glad/glad.h>

void mylog(const char* _Format, ...);
void mySwapBuffers();
void myPollEvents();



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 "TheApp.h"

extern GLFWwindow* myMainWindow;
extern TheApp theApp;

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
    theApp.bExitApp = glfwWindowShouldClose(myMainWindow);
    //check screen size
    int width, height;
    glfwGetFramebufferSize(myMainWindow, &width, &height);
    theApp.onScreenResize(width, height);
}


8. Add new path to pw properties -> C/C++ -> General -> Additional Include Directories -> Edit

add new line

..\..\p_windows

9. main.cpp, accordingly, is much shorter now:

#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>

#include <stdlib.h>

#include "TheApp.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);
}

TheApp theApp;
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);

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

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


Leave a Reply

Your email address will not be published. Required fields are marked *