В этой главе мы попробуем “препарировать” наш GLFW пример с треугольником. Мы выведем “игровой” код (который готовит и рисует треугольник) в отдельный класс, который потом попробуем использовать и на Android-е. Чтобы код был platform-independent (независимым) мы отделим его от platform-specific вызовов. Весь environment-related код, типа создание окна, инициализация GL-а, и т.д., мы оставим в main.cpp как есть.
- Запускаем Visual Studio, открываем C:\CPP\a999hello\p_windows\p_windows.sln solution.
2. Под p_windows проеком добавим новый фильтр.
Right-click на p_windows project -> Add -> New Filter. Name – xTheGame
3. Под xTheGame создадим новый класс. Мы не будем использовать “add Class” поскольку это расположит файлы на свое усмотрение, не там где нам надо. Лучше создадим его по-файлово.
Right-click на xTheGame -> Add -> New Item,
- Header File (.h)
- Name – TheGame.h
- Поменяем location на C:\CPP\a999hello\
Add.
TheGame класс будет состоять из 5-и функций и 3-х переменных, собранных в TheGame.h.
Copy-paste следующий код в 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. Теперь – реализация:
Right-click на xTheGame -> Add -> New Item,
- C++ File (.cpp)
- Name – TheGame.cpp
- Location – C:\CPP\a999hello\
Add.
Код:
#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;
}
- Заметим, что в коде нет никаких platform-specific ссылок (как на GLAD или GLFW), что означает что его можно использовать и на других платформах, в частности – на Android-е, что мы непременно и попробуем в следующей главе.
5. Также (как обычно) надо оповестить p_windows проект, где искать TheGame.h.
Right-click на p_windows project -> Properties, All Configurations, Win32, Configuration Properties -> C/C++ -> General, открываем Additional Include Directories -> Edit, добавляем новую строку.
ВАЖНО: На этот раз вместо перехода на C:\CPP\a999hello (где TheGame и расположен), вручную добавим
..
Да, это просто 2 точки, что означает 1 уровень вверх (от корневого каталога p_windows проекта).
Ok, Apply, Ok.
6. Как и планировали, расположим platform-specific код вне TheGame класса. В частности, ссылки на GLFW и GLAD, обработку событий и вызов swap screen buffers.
Итак, откроем platform.h и заменим код следующим:
#pragma once
#include <glad/glad.h>
void mylog(const char* _Format, ...);
void mySwapBuffers();
void myPollEvents();
- Заодно убрем mylog(..) из .h в .cpp
7. Реализация:
Right-click на xPlatform -> Add -> New Item,
- C++ File (.cpp)
- Name – platform.cpp
- Location – C:\CPP\p_windows\
Add.
Код:
#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, соответственно, теперь намного короче:
#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, "Simple example", 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);
}
Заменим код в main.cpp на этот.
- Заметим, что этот код НЕ содержит никаких game-specific функций, что означает, что мы сможем использовать его как есть и в других наших будущих Windows проектах.
9. Запускаем (зеленая стрелка). Работает!
- Можно также запустить его в Release конфигурации, тогда будет без окна Консоли.
Наша следующая задача – запустить это на Android-е.