Chapter 13. Basic classes 2

Windows

1. Start VS. Open C:\CPP\a998engine\p_windows\p_windows.sln


MyColor class

The reason to have it as a separate class, is that we will need to keep colors in THREE different formats:

  • as an array of 4 single byte integers (from 0 to 255, as it is stored in files)
  • as an array of 4 floats (from 0.0 to 1.0, as required by OpenGL)
  • as a single 32-bit integer (4 bytes, 1 byte for each channel)

Any change to either of these formats must be reflected in the other two.

2. Under xEngine add new header (.h) file MyColor.h

Location - C:\CPP\engine

Code:

#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; };
};

  • Please note: we are using here 2 custom defined variable types: myUint32 and myUint8.
  • Can't use "normal" _int32 or _int8 because they are Windows-specific, Android has different definitions.

3. Open platform.h and replace code by following:

#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. Under xEngine add new C++ File (.cpp) file MyColor.cpp

Location - C:\CPP\engine

Code:

#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 class

"Material" in our case is an instruction HOW to render, which shader to use, which textures and colors, etc. For example, "Draw green surface with ucolor_flat shader" is one Material. "Draw red surface with same shader" is another one. WHAT to render - is a separate question. We don't have much to store in this class yet, but we will later.

5. Under xEngine add new header (.h) file Material.h

Location - C:\CPP\engine

Code:

#pragma once
#include "MyColor.h"

class Material
{
public:
	int shaderN = -1;
	int primitiveType = GL_TRIANGLES;
	MyColor uColor;
	int uTex0 = -1;
};


6. Under xEngine add new C++ (.cpp) file Material.cpp

Location - C:\CPP\engine

We can leave it empty so far.


Now we are ready for our engine's key class, which will represent WHAT to render. Also it will have Material info, so it will know WHAT to render and HOW. I will call it a DrawJob.

Each game subject can have 1 or a few DrawJobs. For example, let's say we want to draw a red cube with a picture on 1 side. So, the subject will have 2 DrawJobs:

  • 5-sided mesh with color reference and ucolor_flat shader
  • 1-sided mesh with texture reference and tex_flat shader

TheGame class will scan game subjects and their DrawJobs and will execute them.

Each DrawJob must have a reference to involved data sources, in our sample - a single vertex_buffer VBO (Vertex Buffer Object which was generated from vertices array). Each element of this VBO consists of 5 floats (alternatively it could be two arrays/VBOs: one keeping xyz info and another one - tUV).

Also, DrawJob must include instructions how to interpret these data: for each involved variable (such as aPos or aTuv) it must tell where it is located (in which VBO and a position/offset inside of this VBO). So, we'll need an extra structure, we'll call it AttribRef (for Attribute Reference).

VBOs themselves will reside separately (in GPU). In the DrawJob (in AttribRefs to be more accurate) we'll need only VBO ids.


DrawJob class

7. Under xEngine add new header (.h) file DrawJob.h

Location - C:\CPP\engine

Code:

#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. Under xEngine add new C++ (.cpp) file DrawJob.cpp

Location - C:\CPP\engine

Code:

#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;
}

Try to pay a bit closer attention to this class, especially to AttribRef structure and to it's usage. If it's too difficult now, don't worry, you can come back to this later.


9. Open TheGame.cpp and replace code by:

#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. Build and run - good.


Now let's try on

Android

11. Re-start VS. Open C:\CPP\a998engine\p_android\p_android.sln.


12. Under xEndine add Existing Item

Go to C:\CPP\engine

Pick

  • DrawJob.cpp
  • DrawJob.h
  • Material.cpp
  • Material.h
  • MyColor.cpp
  • MyColor.h

Add.


Add Android's myUint32/8 implementations.

13. Open platform.h and replace code by:

#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. Switch on, unlock and plug in Android, allow debugging.

Build and run - perfect.


Leave a Reply

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