apple2ix/src/video_util/modelUtil.c
2017-07-15 15:08:42 -10:00

640 lines
20 KiB
C

/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 2013-2015 Aaron Culliney
*
*/
// Modified sample code from https://developer.apple.com/library/mac/samplecode/GLEssentials/Introduction/Intro.html
#include "modelUtil.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if 0
#error untested codepaths after changing/iterating GLModel structure ...
typedef struct modelHeader {
char fileIdentifier[30];
unsigned int majorVersion;
unsigned int minorVersion;
} modelHeader;
typedef struct modelTOC {
unsigned int attribHeaderSize;
unsigned int byteElementOffset;
unsigned int bytePositionOffset;
unsigned int byteTexcoordOffset;
unsigned int byteNormalOffset;
} modelTOC;
typedef struct modelAttrib {
unsigned int byteSize;
GLenum datatype;
GLenum primType; //If index data
unsigned int sizePerElement;
unsigned int numElements;
} modelAttrib;
GLModel *mdlLoadModel(const char *filepathname) {
if (!filepathname) {
return NULL;
}
GLModel *model = (GLModel *)CALLOC(sizeof(GLModel), 1);
if (!model) {
return NULL;
}
size_t sizeRead = 0;
int error = 0;
FILE *curFile = fopen(filepathname, "r");
if (!curFile) {
mdlDestroyModel(&model);
return NULL;
}
modelHeader header = { { 0 } };
sizeRead = fread(&header, 1, sizeof(modelHeader), curFile);
if (sizeRead != sizeof(modelHeader)) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
if (strncmp(header.fileIdentifier, "AppleOpenGLDemoModelWWDC2010", sizeof(header.fileIdentifier))) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
if (header.majorVersion != 0 && header.minorVersion != 1) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
modelTOC toc = { 0 };
sizeRead = fread(&toc, 1, sizeof(modelTOC), curFile);
if (sizeRead != sizeof(modelTOC)) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
if (toc.attribHeaderSize > sizeof(modelAttrib)) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
modelAttrib attrib = { 0 };
error = fseek(curFile, toc.byteElementOffset, SEEK_SET);
if (error < 0) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
sizeRead = fread(&attrib, 1, toc.attribHeaderSize, curFile);
if (sizeRead != toc.attribHeaderSize) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
model->elementArraySize = attrib.byteSize;
model->elementType = attrib.datatype;
model->numElements = attrib.numElements;
// OpenGL ES cannot use UNSIGNED_INT elements
// so if the model has UI element...
if (GL_UNSIGNED_INT == model->elementType) {
// ...load the UI elements and convert to UNSIGNED_SHORT
GLubyte *uiElements = (GLubyte *)MALLOC(model->elementArraySize);
size_t ushortElementArraySize = model->numElements * sizeof(GLushort);
model->elements = (GLubyte *)MALLOC(ushortElementArraySize);
sizeRead = fread(uiElements, 1, model->elementArraySize, curFile);
if (sizeRead != model->elementArraySize) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
GLuint elemNum = 0;
for (elemNum = 0; elemNum < model->numElements; elemNum++) {
// can't handle this model if an element is out of the UNSIGNED_INT range
if (((GLuint *)uiElements)[elemNum] >= 0xFFFF) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
((GLushort *)model->elements)[elemNum] = ((GLuint *)uiElements)[elemNum];
}
FREE(uiElements);
model->elementType = GL_UNSIGNED_SHORT;
model->elementArraySize = model->numElements * sizeof(GLushort);
} else {
model->elements = (GLubyte*)MALLOC(model->elementArraySize);
sizeRead = fread(model->elements, 1, model->elementArraySize, curFile);
if (sizeRead != model->elementArraySize) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
}
fseek(curFile, toc.bytePositionOffset, SEEK_SET);
sizeRead = fread(&attrib, 1, toc.attribHeaderSize, curFile);
if (sizeRead != toc.attribHeaderSize) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
model->positionArraySize = attrib.byteSize;
model->positionType = attrib.datatype;
model->positionSize = attrib.sizePerElement;
model->numVertices = attrib.numElements;
model->positions = (GLubyte*)MALLOC(model->positionArraySize);
sizeRead = fread(model->positions, 1, model->positionArraySize, curFile);
if (sizeRead != model->positionArraySize) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
error = fseek(curFile, toc.byteTexcoordOffset, SEEK_SET);
if (error < 0) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
sizeRead = fread(&attrib, 1, toc.attribHeaderSize, curFile);
if (sizeRead != toc.attribHeaderSize) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
model->texcoordArraySize = attrib.byteSize;
model->texcoordType = attrib.datatype;
model->texcoordSize = attrib.sizePerElement;
// must have the same number of texcoords as positions
if (model->numVertices != attrib.numElements) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
model->texCoords = (GLubyte*)MALLOC(model->texcoordArraySize);
sizeRead = fread(model->texCoords, 1, model->texcoordArraySize, curFile);
if (sizeRead != model->texcoordArraySize) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
error = fseek(curFile, toc.byteNormalOffset, SEEK_SET);
if (error < 0) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
sizeRead = fread(&attrib, 1, toc.attribHeaderSize, curFile);
if (sizeRead != toc.attribHeaderSize) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
model->normalArraySize = attrib.byteSize;
model->normalType = attrib.datatype;
model->normalSize = attrib.sizePerElement;
// must have the same number of normals as positions
if (model->numVertices != attrib.numElements) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
model->normals = (GLubyte*)MALLOC(model->normalArraySize );
sizeRead = fread(model->normals, 1, model->normalArraySize , curFile);
if (sizeRead != model->normalArraySize) {
fclose(curFile);
mdlDestroyModel(&model);
return NULL;
}
fclose(curFile);
return model;
}
GLModel *mdlLoadQuadModel(void) {
GLfloat posArray[] = {
-200.0f, 0.0f, -200.0f,
200.0f, 0.0f, -200.0f,
200.0f, 0.0f, 200.0f,
-200.0f, 0.0f, 200.0f
};
GLfloat texcoordArray[] = {
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f
};
GLfloat normalArray[] = {
0.0f, 0.0f, 1.0,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
};
GLushort elementArray[] = {
0, 2, 1,
0, 3, 2
};
GLModel *model = (GLModel *)CALLOC(sizeof(GLModel), 1);
if (!model) {
return NULL;
}
model->positionType = GL_FLOAT;
model->positionSize = 3;
model->positionArraySize = sizeof(posArray);
model->positions = (GLubyte*)MALLOC(model->positionArraySize);
memcpy(model->positions, posArray, model->positionArraySize);
model->texcoordType = GL_FLOAT;
model->texcoordSize = 2;
model->texcoordArraySize = sizeof(texcoordArray);
model->texCoords = (GLubyte*)MALLOC(model->texcoordArraySize);
memcpy(model->texCoords, texcoordArray, model->texcoordArraySize );
model->normalType = GL_FLOAT;
model->normalSize = 3;
model->normalArraySize = sizeof(normalArray);
model->normals = (GLubyte*)MALLOC(model->normalArraySize);
memcpy(model->normals, normalArray, model->normalArraySize);
model->elementArraySize = sizeof(elementArray);
model->elements = (GLubyte*)MALLOC(model->elementArraySize);
memcpy(model->elements, elementArray, model->elementArraySize);
model->primType = GL_TRIANGLES;
model->numElements = sizeof(elementArray) / sizeof(GLushort);
model->elementType = GL_UNSIGNED_SHORT;
model->numVertices = model->positionArraySize / (model->positionSize * sizeof(GLfloat));
return model;
}
#endif // 0
static void _quadCreateVAOAndVBOs(GLModel *model) {
// Create a vertex array object (VAO) to cache model parameters
#if USE_VAO
glGenVertexArrays(1, &(model->vaoName));
glBindVertexArray(model->vaoName);
#endif
// Create a vertex buffer object (VBO) to store positions and load data
glGenBuffers(1, &(model->posBufferName));
glBindBuffer(GL_ARRAY_BUFFER, model->posBufferName);
glBufferData(GL_ARRAY_BUFFER, model->positionArraySize, model->positions, model->positionUsageHint);
#if USE_VAO
// Enable the position attribute for this VAO
glEnableVertexAttribArray(POS_ATTRIB_IDX);
// Get the size of the position type so we can set the stride properly
GLsizei posTypeSize = getGLTypeSize(model->positionType);
// Set up parmeters for position attribute in the VAO including,
// size, type, stride, and offset in the currenly bound VAO
// This also attaches the position VBO to the VAO
glVertexAttribPointer(POS_ATTRIB_IDX, // What attibute index will this array feed in the vertex shader (see buildProgram)
model->positionSize, // How many elements are there per position?
model->positionType, // What is the type of this data?
GL_FALSE, // Do we want to normalize this data (0-1 range for fixed-pont types)
model->positionSize*posTypeSize, // What is the stride (i.e. bytes between positions)?
0); // What is the offset in the VBO to the position data?
#endif
if (model->texCoords) {
// Create a VBO to store texcoords
glGenBuffers(1, &(model->texcoordBufferName));
glBindBuffer(GL_ARRAY_BUFFER, model->texcoordBufferName);
// Allocate and load texcoord data into the VBO
glBufferData(GL_ARRAY_BUFFER, model->texcoordArraySize, model->texCoords, model->texcoordUsageHint);
#if USE_VAO
// Enable the texcoord attribute for this VAO
glEnableVertexAttribArray(TEXCOORD_ATTRIB_IDX);
// Get the size of the texcoord type so we can set the stride properly
GLsizei texcoordTypeSize = getGLTypeSize(model->texcoordType);
// Set up parmeters for texcoord attribute in the VAO including,
// size, type, stride, and offset in the currenly bound VAO
// This also attaches the texcoord VBO to VAO
glVertexAttribPointer(TEXCOORD_ATTRIB_IDX, // What attibute index will this array feed in the vertex shader (see buildProgram)
model->texcoordSize, // How many elements are there per texture coord?
model->texcoordType, // What is the type of this data in the array?
GL_TRUE, // Do we want to normalize this data (0-1 range for fixed-point types)
model->texcoordSize*texcoordTypeSize, // What is the stride (i.e. bytes between texcoords)?
0); // What is the offset in the VBO to the texcoord data?
#endif
}
// Create a VBO to vertex array elements
// This also attaches the element array buffer to the VAO
glGenBuffers(1, &(model->elementBufferName));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model->elementBufferName);
// Allocate and load vertex array element data into VBO
glBufferData(GL_ELEMENT_ARRAY_BUFFER, model->elementArraySize, model->elements, GL_STATIC_DRAW/* HACK TODO FIXME: investigate*/);
#if USE_VAO
// We're using VAOs we can destroy certain buffers since they are already
// loaded into GL and we've saved anything else we need
FREE(model->elements);
//FREE(model->normals);
FREE(model->texCoords);
#endif
GL_MAYBELOG("quad creation of VAO/VBOs");
}
static GLuint _quadCreateTexture(GLModel *model) {
GLuint texName = UNINITIALIZED_GL;
// Create a texture object to apply to model
glGenTextures(1, &texName);
glBindTexture(GL_TEXTURE_2D, texName);
// Set up filter and wrap modes for this texture object
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// Indicate that pixel rows are tightly packed (defaults to a stride of sizeof(PIXEL_TYPE) which is good for RGBA or
// FLOAT data types)
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// register texture with OpenGL
glTexImage2D(GL_TEXTURE_2D, /*level*/0, /*internal format*/TEX_FORMAT_INTERNAL, model->texWidth, model->texHeight, /*border*/0, TEX_FORMAT, TEX_TYPE, NULL);
GL_MAYBELOG("quad texture creation");
return texName;
}
GLModel *mdlCreateQuad(GLModelParams_s parms, GLCustom clazz) {
const GLfloat skew_x = parms.skew_x;
const GLfloat skew_y = parms.skew_y;
const GLfloat obj_w = parms.obj_w;
const GLfloat obj_h = parms.obj_h;
const GLfloat z = parms.z;
const GLsizei tex_w = parms.tex_w;
const GLsizei tex_h = parms.tex_h;
/* 2...3
* .
* .
* .
* 0...1
*/
const GLfloat obj_positions[] = {
skew_x, skew_y, z, 1.0,
skew_x+obj_w, skew_y, z, 1.0,
skew_x, skew_y+obj_h, z, 1.0,
skew_x+obj_w, skew_y+obj_h, z, 1.0,
};
const GLfloat obj_texcoords[] = {
0.f, 1.f,
1.f, 1.f,
0.f, 0.f,
1.f, 0.f,
};
const GLushort indices[] = {
0, 1, 2, 2, 1, 3
};
GLModel *model = NULL;
do {
model = CALLOC(1, sizeof(GLModel));
if (!model) {
break;
}
model->numVertices = 4;
model->numElements = 6;
model->primType = GL_TRIANGLES;
model->positionUsageHint = parms.positionUsageHint;
model->texcoordUsageHint = parms.texcoordUsageHint;
model->positions = MALLOC(sizeof(obj_positions));
if (!(model->positions)) {
break;
}
memcpy(model->positions, &obj_positions[0], sizeof(obj_positions));
model->positionType = GL_FLOAT;
model->positionSize = 4; // x,y,z coordinates
model->positionArraySize = sizeof(obj_positions);
model->texcoordType = UNINITIALIZED_GL;
model->texcoordSize = 0;
model->texcoordArraySize = 0;
const bool useTexture = (tex_w > 0 && tex_h > 0);
if (useTexture) {
model->texCoords = MALLOC(sizeof(obj_texcoords));
if (!(model->texCoords)) {
break;
}
memcpy(model->texCoords, &obj_texcoords[0], sizeof(obj_texcoords));
model->texcoordType = GL_FLOAT;
model->texcoordSize = 2; // s,t coordinates
model->texcoordArraySize = sizeof(obj_texcoords);
}
#if 0
{
// NO NORMALS for now
model->normals = NULL;
model->normalType = GL_NONE;
model->normalSize = GL_NONE;
model->normalArraySize = 0;
}
#endif
model->elements = MALLOC(sizeof(indices));
if (!(model->elements)) {
break;
}
memcpy(model->elements, &indices[0], sizeof(indices));
model->elementType = GL_UNSIGNED_SHORT;
model->elementArraySize = sizeof(indices);
#if USE_VAO
model->vaoName = UNINITIALIZED_GL;
#endif
model->posBufferName = UNINITIALIZED_GL;
model->texcoordBufferName = UNINITIALIZED_GL;
model->elementBufferName = UNINITIALIZED_GL;
model->texFormat = UNINITIALIZED_GL;
_quadCreateVAOAndVBOs(model);
if (model->posBufferName == UNINITIALIZED_GL || (useTexture && model->texcoordBufferName == UNINITIALIZED_GL) || model->elementBufferName == UNINITIALIZED_GL) {
LOG("Error creating model buffers!");
break;
}
model->texDirty = true;
model->texWidth = tex_w;
model->texHeight = tex_h;
if (useTexture) {
model->texFormat = TEX_FORMAT;
model->texPixels = (GLvoid *)MALLOC(tex_w * tex_h * sizeof(PIXEL_TYPE));
if (model->texPixels == NULL) {
break;
}
model->textureName = _quadCreateTexture(model);
if (model->textureName == UNINITIALIZED_GL) {
LOG("Error creating model texture!");
break;
}
}
model->custom = NULL;
if (clazz.create) {
model->custom = clazz.create(model);
if (!model->custom) {
LOG("Error creating custom model!");
break;
}
model->custom->create = NULL;
model->custom->destroy = clazz.destroy;
}
GL_MAYBELOG("quad creation");
return model;
} while (0);
LOG("error in quad creation");
if (model) {
mdlDestroyModel(&model);
}
return NULL;
}
void mdlDestroyModel(INOUT GLModel **model) {
if (!model || (!*model)) {
return;
}
GLModel *m = *model;
FREE(m->elements);
FREE(m->positions);
//FREE(m->normals);
FREE(m->texCoords);
FREE(m->texPixels);
glBindTexture(GL_TEXTURE_2D, 0);
if (m->textureName != UNINITIALIZED_GL) {
glDeleteTextures(1, &(m->textureName));
m->textureName = UNINITIALIZED_GL;
}
#if USE_VAO
if (m->vaoName != UNINITIALIZED_GL) {
glBindVertexArray(m->vaoName);
// For every possible attribute set in the VAO
for (GLuint index = 0; index < 16; index++) {
#warning FIXME TODO ... why magick hardcoded 16? ...
// Get the VBO set for that attibute
GLuint bufName = 0;
glGetVertexAttribiv(index , GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, (GLint*)&bufName);
// If there was a VBO set...
if (bufName) {
//...delete the VBO
glDeleteBuffers(1, &bufName);
}
}
// Get any element array VBO set in the VAO
{
GLuint bufName = 0;
glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, (GLint*)&bufName);
// If there was a element array VBO set in the VAO
if (bufName) {
//...delete the VBO
glDeleteBuffers(1, &bufName);
}
}
// Finally, delete the VAO
glDeleteVertexArrays(1, &(m->vaoName));
}
#else
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
if (m->posBufferName != UNINITIALIZED_GL) {
glDeleteBuffers(1, &(m->posBufferName));
m->posBufferName = UNINITIALIZED_GL;
}
if (m->texcoordBufferName != UNINITIALIZED_GL) {
glDeleteBuffers(1, &(m->texcoordBufferName));
m->texcoordBufferName = UNINITIALIZED_GL;
}
if (m->elementBufferName != UNINITIALIZED_GL) {
glDeleteBuffers(1, &(m->elementBufferName));
m->elementBufferName = UNINITIALIZED_GL;
}
#endif
if (m->custom && m->custom->destroy) {
m->custom->destroy(m);
m->custom = NULL;
}
FREE(*model);
}