Windows
1. Запускаем VS. Открываем C:\CPP\a998engine\p_windows\p_windows.sln
MyColor класс
Причина держать цвет в отдельном классе в том, что он будет нужен в ТРЕХ разных форматах:
- как массив 4-х byte integers (от 0 до 255, как это хранится в файлах)
- как массив 4-х floats (от 0.0 до 1.0, как требуется OpenGL-ом)
- и как 32-bit integer (4 байта, по одному на каждый канал)
Любое изменение в любом из форматов должно отражаться в двух остальных.
2. Под xEngine добавим новый header (.h) файл MyColor.h
Location – C:\CPP\engine
Код:
#pragma once
#include "platform.h"
class MyColor
{
private:
float rgba[4] = { 0,0,0,0 };
union {
myUint32 uint32value = 0;
myUint8 channel[4]; //4 channels
} RGBA;
public:
float* forGL() { return rgba; };
static void cloneIntToFloat(MyColor* pCl);
static void cloneFloatToInt(MyColor* pCl);
void setRGBA(float* rgba) { return setRGBA(this, rgba); };
static void setRGBA(MyColor* pCl, float* rgba);
void setRGBA(float r, float g, float b, float a) { return setRGBA(this, r, g, b, a); };
static void setRGBA(MyColor* pCl, float r, float g, float b, float a);
void setRGBA(int R, int G, int B, int A) { return setRGBA(this, R, G, B, A); };
static void setRGBA(MyColor* pCl, int R, int G, int B, int A);
void setRGBA(unsigned char* RGBA) { return setRGBA(this, RGBA); };
static void setRGBA(MyColor* pCl, unsigned char* RGBA);
void setUint32(unsigned int RGBA) { return setUint32(this, RGBA); };
static void setUint32(MyColor* pCl, unsigned int RGBA);
bool isZero() { return (RGBA.uint32value == 0); };
bool isSet() { return (RGBA.uint32value != 0); };
void clear() { setUint32(0); };
unsigned int getUint32() { return (unsigned int)RGBA.uint32value; };
};
- Заметим: у нас тут 2 собственных типа переменных: myUint32 and myUint8.
- Мы не можем использовать “нормальные” _int32 или _int8 потому что они Windows-specific, в Андроиде они представлены по-другому.
3. Откроем platform.h и заменим код на:
#pragma once
#include <glad/glad.h>
#include <stdio.h>
typedef unsigned _int64 myUint64;
typedef unsigned _int32 myUint32;
typedef unsigned _int16 myUint16;
typedef unsigned _int8 myUint8;
void mylog(const char* _Format, ...);
void mySwapBuffers();
void myPollEvents();
int myFopen_s(FILE** pFile, const char* filePath, const char* mode);
4. Под xEngine добавим новый C++ File (.cpp) MyColor.cpp
Location – C:\CPP\engine
Код:
#include "MyColor.h"
float chanIntToFloat = 1.0f / 255;
void MyColor::cloneIntToFloat(MyColor* pCl) {
for (int i = 0; i < 4; i++)
pCl->rgba[i] = chanIntToFloat * pCl->RGBA.channel[i];
}
void MyColor::cloneFloatToInt(MyColor* pCl) {
for (int i = 0; i < 4; i++)
pCl->RGBA.channel[i] = (int)(pCl->rgba[i] * 255);
}
void MyColor::setRGBA(MyColor* pCl, float* rgba) {
for (int i = 0; i < 4; i++)
pCl->rgba[i] = rgba[i];
cloneFloatToInt(pCl);
}
void MyColor::setRGBA(MyColor* pCl, float r, float g, float b, float a) {
pCl->rgba[0] = r;
pCl->rgba[1] = g;
pCl->rgba[2] = b;
pCl->rgba[3] = a;
cloneFloatToInt(pCl);
}
void MyColor::setRGBA(MyColor* pCl, int R, int G, int B, int A) {
pCl->RGBA.channel[0] = R;
pCl->RGBA.channel[1] = G;
pCl->RGBA.channel[2] = B;
pCl->RGBA.channel[3] = A;
cloneIntToFloat(pCl);
}
void MyColor::setRGBA(MyColor* pCl, unsigned char* RGBA) {
for (int i = 0; i < 4; i++)
pCl->RGBA.channel[i] = RGBA[i];
cloneIntToFloat(pCl);
}
void MyColor::setUint32(MyColor* pCl, unsigned int RGBA) {
pCl->RGBA.uint32value = RGBA;
cloneIntToFloat(pCl);
}
Material класс
“Material” в нашем случае – это инструкция КАК рендрить, каким шейдером, какую текстуру или цвет, и т.д. Например, “Рисовать зеленую поверхность ucolor_flat шейдером” – это один Material. “Рисовать красную поверхность тем же шейдером” – уже другой. ЧТО рендрить – это отдельный вопрос. Пока у нас не так много что держать в этом классе, но потом будет больше.
5. Под xEngine добавим новый header (.h) файл Material.h
Location – C:\CPP\engine
Код:
#pragma once
#include "MyColor.h"
class Material
{
public:
int shaderN = -1;
int primitiveType = GL_TRIANGLES;
MyColor uColor;
int uTex0 = -1;
};
6. Под xEngine добавим новый C++ (.cpp) файл Material.cpp
Location – C:\CPP\engine
Пока оставим его пустым.
И теперь мы готовы к ключевому классу нашего движка, который будет представлять ЧТО рендрить. Также в нем будет и Material, так что он будет знать ЧТО рендрить и КАК. Назовем его DrawJob.
Каждый game subject будет иметь 1 или несколько DrawJobs. Например, хотим мы нарисовать синий кубик с картинкой на одной стороне. Значит, у subject-а будет 2 DrawJobs:
- 5-сторонний меш со ссылкой на цвет и с шейдером ucolor_flat
- 1-сторонний меш со ссылкой на текстуру и с шейдером tex_flat
Класс TheGame будет сканировать массив game subject-ов и их DrawJobs и будет их исполнять.
Каждый DrawJob должен иметь ссылки на задействованные массивы данных, в нашем примере – на один vertex_buffer VBO (Vertex Buffer Object, который мы сгенерировали из массива вертексов). Каждый элемент этого VBO состоит из 5 floats (а могло быть и 2 VBO: в одном xyz координаты, а в другом текстурные – tUV).
Также DrawJob должен иметь информацию как эти данные интерпретировать и использовать: для каждой задействованной переменной (таких как aPos или aTuv) он должен указать где ее искать (в каком VBO и ее позицию/offset/смещение внутри этого VBO). Для этого понадобится новая структура, назовем ее AttribRef (for Attribute Reference).
Сами VBO будут храниться отдельно (в GPU). В DrawJob же (в AttribRefs, если быть точнее) нам будет нужен только номер – VBO id.
DrawJob class
7. Под xEngine добавим новый header (.h) файл DrawJob.h
Location – C:\CPP\engine
Код:
#pragma once
#include "Material.h"
#include <vector>
struct AttribRef //attribute reference/description
{
unsigned int glVBOid = 0; //buffer object id
int offset = 0; //variable's offset inside of VBO's element
int stride = 0; //Buffer's element size in bytes
};
class DrawJob
{
public:
Material mt;
int pointsN = 0; //N of points to draw
unsigned int glVAOid = 0; //will hold data stream attributes mapping/positions
//common attributes
AttribRef aPos;
AttribRef aTuv;
//static arrays (vectors) of all loaded DrawJobs, VBO ids
static std::vector<DrawJob*> drawJobs;
static std::vector<unsigned int> buffersIds;
public:
DrawJob();
virtual ~DrawJob(); //destructor
static int cleanUp();
static int newBufferId();
int buildVAO() { return buildVAOforShader(this, mt.shaderN); };
static int buildVAOforShader(DrawJob* pDJ, int shaderN);
static int attachAttribute(int varLocationInShader, int attributeSizeInFloats, AttribRef* pAttribRef);
virtual int setDesirableOffsets(int* pStride, int shaderN) { return setDesirableOffsetsBasic(this, pStride, shaderN); };
static int setDesirableOffsetsBasic(DrawJob* pDJ, int* pStride, int shaderN);
int execute(float* uMVP, Material* pMt) { return executeDrawJob(this, uMVP, pMt); };
static int executeDrawJob(DrawJob* pDJ, float* uMVP, Material* pMt);
};
8. Под xEngine добавим новый C++ (.cpp) файл DrawJob.cpp
Location – C:\CPP\engine
Код:
#include "DrawJob.h"
#include "platform.h"
#include "utils.h"
#include "Shader.h"
#include "Texture.h"
//static arrays (vectors) of all loaded DrawJobs, VBO ids
std::vector<DrawJob*> DrawJob::drawJobs;
std::vector<unsigned int> DrawJob::buffersIds;
DrawJob::DrawJob() {
drawJobs.push_back(this);
}
DrawJob::~DrawJob() {
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
if (glVAOid > 0)
glDeleteVertexArrays(1, &glVAOid);
}
int DrawJob::newBufferId() {
unsigned int bufferId;
glGenBuffers(1, &bufferId);
buffersIds.push_back(bufferId);
return (int)bufferId;
}
int DrawJob::setDesirableOffsetsBasic(DrawJob* pDJ, int* pStride, int shaderN) {
//sets desirable offsets and stride according to given shader needs
//assuming that we have 1 single VBO
Shader* pSh = Shader::shaders.at(shaderN);
int stride = 0;
pDJ->aPos.offset = 0; //attribute o_aPos, always 0
stride += sizeof(float) * 3; //aPos size - 3 floats (x,y,z)
if (pSh->l_aTuv >= 0) { //attribute TUV (texture coordinates)
pDJ->aTuv.offset = stride; //attribute TUV (texture coordinates)
stride += sizeof(float) * 2;
}
*pStride = stride;
return 1;
}
unsigned int activeVBOid;
int DrawJob::buildVAOforShader(DrawJob* pDJ, int shaderN) {
//delete VAO if exists already
if (pDJ->glVAOid > 0) {
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteVertexArrays(1, &(pDJ->glVAOid));
}
glGenVertexArrays(1, &pDJ->glVAOid);
glBindVertexArray(pDJ->glVAOid);
//open shader descriptor to access variables locations
Shader* pShader = Shader::shaders.at(pDJ->mt.shaderN);
activeVBOid = 0;
attachAttribute(pShader->l_aPos, 3, &pDJ->aPos);
attachAttribute(pShader->l_aTuv, 2, &pDJ->aTuv);
return 1;
}
int DrawJob::attachAttribute(int varLocationInShader, int attributeSizeInFloats, AttribRef* pAR) {
if (varLocationInShader < 0)
return 0; //not used in this shader
if (pAR->glVBOid == 0) {
mylog("ERROR in DrawJob::attachAttribute, nk such attribute/VBO\n");
return -1;
}
glEnableVertexAttribArray(varLocationInShader);
if (activeVBOid != pAR->glVBOid) {
activeVBOid = pAR->glVBOid;
//attach input stream data
glBindBuffer(GL_ARRAY_BUFFER, activeVBOid);
}
glVertexAttribPointer(varLocationInShader, attributeSizeInFloats, GL_FLOAT, GL_FALSE, pAR->stride, (void*)(long)pAR->offset);
return 1;
}
int DrawJob::executeDrawJob(DrawJob* pDJ, float* uMVP, Material* pMt) {
if (pMt == NULL)
pMt = &(pDJ->mt);
glBindVertexArray(pDJ->glVAOid);
Shader* pShader = Shader::shaders.at(pMt->shaderN);
glUseProgram(pShader->GLid);
glUniformMatrix4fv(pShader->l_uMVP, 1, GL_FALSE, (const GLfloat*)uMVP);
//attach textures
if (pShader->l_uTex0 >= 0) {
int textureId = Texture::getGLid(pMt->uTex0);
//pass textureId to shader program
glActiveTexture(GL_TEXTURE0); // activate the texture unit first before binding texture
glBindTexture(GL_TEXTURE_2D, textureId);
// Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.
glUniform1i(pShader->l_uTex0, 0);
}
//other uniforms
if (pShader->l_uColor >= 0) {
//float uColor[4] = { 1.f, 0.f, 1.f, 1.f }; //R,G,B, alpha
glUniform4fv(pShader->l_uColor, 1, pMt->uColor.forGL());
}
glDrawArrays(pMt->primitiveType, 0, pDJ->pointsN);
return 1;
}
int DrawJob::cleanUp() {
int itemsN = drawJobs.size();
//delete all drawJobs
for (int i = 0; i < itemsN; i++) {
DrawJob* pDJ = drawJobs.at(i);
delete pDJ;
}
drawJobs.clear();
//delete Buffers
itemsN = buffersIds.size();
//delete all buffers
for (int i = 0; i < itemsN; i++) {
unsigned int id = buffersIds.at(i);
glDeleteBuffers(1, &id);
}
buffersIds.clear();
return 1;
}
Постарайтесь присмотреться к этому классу повнимательнее, особенно к структуре AttribRef и к ее использованию. Если пока трудновато, не волнуйтесь, всегда будет можно к этому вернуться.
9. Откроем TheGame.cpp и заменим код на:
#include "TheGame.h"
#include "platform.h"
#include "linmath.h"
#include "Texture.h"
#include "Shader.h"
#include "DrawJob.h"
extern std::string filesRoot;
static const struct
{
float x, y, z, tu, tv;
} vertices[4] =
{
{ -0.5f, 0.5f, 0.f, 0.f, 0.f }, //top-left
{ -0.5f, -0.5f, 0.f, 0.f, 1.f }, //bottom-left
{ 0.5f, 0.5f, 0.f, 1.f, 0.f }, //top-right
{ 0.5f, -0.5f, 0.f, 1.f, 1.f } //bottom-right
};
DrawJob* pDJ;
float angle_z = 0;
int TheGame::getReady() {
bExitGame = false;
Shader::loadShaders();
pDJ = new DrawJob();
//define material
pDJ->mt.shaderN = Shader::spN_flat_tex;
pDJ->mt.primitiveType = GL_TRIANGLE_STRIP;
pDJ->mt.uTex0 = Texture::loadTexture(filesRoot + "/dt/sample_img.png");
//pDJ->mt.color0.setRGBA(1.f, 0.f, 1.f, 1.f);
//pDJ->mt.color0.setRGBA(255, 0, 255,255);
//build and attach data source(s)
pDJ->pointsN = 4; //number of vertices
unsigned int glVBOid = pDJ->newBufferId();
glBindBuffer(GL_ARRAY_BUFFER, glVBOid);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//describe data source
int stride = sizeof(float) * 5;
//attributes references
AttribRef* pAR;
pAR = &pDJ->aPos; pAR->offset = 0; pAR->glVBOid = glVBOid; pAR->stride = stride;
pAR = &pDJ->aTuv; pAR->offset = sizeof(float) * 3; pAR->glVBOid = glVBOid; pAR->stride = stride;
//create and fill vertex attributes array (VAO)
pDJ->buildVAO();
//end of data stream setting, now it is stored in VAO
return 1;
}
int TheGame::drawFrame() {
myPollEvents();
mat4x4 m, p, mvp;
//glClearColor(0.0, 0.0, 0.5, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
angle_z += 0.01f;
mat4x4_identity(m);
mat4x4_rotate_Z(m, m, angle_z);
mat4x4_scale_aniso(m, m, 2.0, 1.0, 1.0);
mat4x4_ortho(p, -screenRatio, screenRatio, -1.f, 1.f, 1.f, -1.f);
mat4x4_mul(mvp, p, m);
pDJ->execute((float*)mvp, NULL);
mySwapBuffers();
return 1;
}
int TheGame::cleanUp() {
Texture::cleanUp();
Shader::cleanUp();
DrawJob::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;
}
10. Компиляция и запуск – отлично.
Теперь попробуем на
Андроиде
11. Пере-запускаем VS. Открываем C:\CPP\a998engine\p_android\p_android.sln.
12. Под xEndine добавим Existing Item
Идем в C:\CPP\engine
Выбираем
- DrawJob.cpp
- DrawJob.h
- Material.cpp
- Material.h
- MyColor.cpp
- MyColor.h
Add.
Теперь надо добавить Android’s myUint32/8 implementations.
13. Откроем platform.h и заменим код на:
#pragma once
typedef uint32_t myUint64;
typedef uint32_t myUint32;
typedef uint8_t myUint16;
typedef uint8_t myUint8;
void mylog(const char* _Format, ...);
void mySwapBuffers();
void myPollEvents();
int myFopen_s(FILE** pFile, const char* filePath, const char* mode);
14. Включаем, разблокируем и подключаем Android, разрешаем debugging.
Компиляция и запуск – отлично.