2014-09-17 03:49:57 +00:00
|
|
|
/*
|
|
|
|
* Apple // emulator for *nix
|
|
|
|
*
|
|
|
|
* This software package is subject to the GNU General Public License
|
|
|
|
* version 2 or later (your choice) as published by the Free Software
|
|
|
|
* Foundation.
|
|
|
|
*
|
|
|
|
* THERE ARE NO WARRANTIES WHATSOEVER.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
// glvideo -- Created by Aaron Culliney
|
|
|
|
|
|
|
|
#include "common.h"
|
2014-09-21 22:58:27 +00:00
|
|
|
#include "video/glinput.h"
|
2014-09-27 18:03:51 +00:00
|
|
|
#include "video/renderer.h"
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
#include "video_util/modelUtil.h"
|
2014-10-25 15:44:21 +00:00
|
|
|
#include "video_util/matrixUtil.h"
|
2014-09-27 18:03:51 +00:00
|
|
|
#include "video_util/sourceUtil.h"
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
#ifdef __APPLE__
|
|
|
|
#import <CoreFoundation/CoreFoundation.h>
|
|
|
|
#endif
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// TODO: implement 3D CRT object, possibly with perspective drawing?
|
|
|
|
#define PERSPECTIVE 0
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
enum {
|
|
|
|
POS_ATTRIB_IDX,
|
|
|
|
NORMAL_ATTRIB_IDX,
|
|
|
|
TEXCOORD_ATTRIB_IDX
|
|
|
|
};
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-11-29 21:31:21 +00:00
|
|
|
bool safe_to_do_opengl_logging = false;
|
|
|
|
|
2015-01-11 20:27:39 +00:00
|
|
|
volatile bool _vid_dirty = true;
|
|
|
|
|
2014-09-17 03:49:57 +00:00
|
|
|
static int windowWidth = SCANWIDTH*1.5;
|
|
|
|
static int windowHeight = SCANHEIGHT*1.5;
|
|
|
|
|
|
|
|
static int viewportX = 0;
|
|
|
|
static int viewportY = 0;
|
|
|
|
static int viewportWidth = SCANWIDTH*1.5;
|
|
|
|
static int viewportHeight = SCANHEIGHT*1.5;
|
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
static GLint uniformMVPIdx;
|
|
|
|
static GLuint crtVAOName;
|
|
|
|
static GLenum crtElementType;
|
|
|
|
static GLuint crtNumElements;
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
static GLuint a2TextureName = 0;
|
|
|
|
static GLuint defaultFBO = 0;
|
2014-09-17 03:49:57 +00:00
|
|
|
static GLuint program = 0;
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
//
|
2014-09-27 18:03:51 +00:00
|
|
|
// OpenGL helper routines
|
2014-09-17 03:49:57 +00:00
|
|
|
//
|
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
static GLsizei _get_gl_type_size(GLenum type) {
|
|
|
|
switch (type) {
|
|
|
|
case GL_BYTE:
|
|
|
|
return sizeof(GLbyte);
|
|
|
|
case GL_UNSIGNED_BYTE:
|
|
|
|
return sizeof(GLubyte);
|
|
|
|
case GL_SHORT:
|
|
|
|
return sizeof(GLshort);
|
|
|
|
case GL_UNSIGNED_SHORT:
|
|
|
|
return sizeof(GLushort);
|
|
|
|
case GL_INT:
|
|
|
|
return sizeof(GLint);
|
|
|
|
case GL_UNSIGNED_INT:
|
|
|
|
return sizeof(GLuint);
|
|
|
|
case GL_FLOAT:
|
|
|
|
return sizeof(GLfloat);
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
2014-09-27 18:03:51 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
static demoModel *_create_CRT_model() {
|
|
|
|
#define STRIDE 9*sizeof(GLfloat)
|
|
|
|
#define TEST_COLOR_OFF (GLvoid *)(3*sizeof(GLfloat))
|
|
|
|
#define TEX_COORD_OFF (GLvoid *)(7*sizeof(GLfloat))
|
|
|
|
|
|
|
|
// NOTE: vertices in Normalized Device Coordinates
|
|
|
|
const GLfloat crt_positions[] = {
|
|
|
|
// CRT screen quad
|
|
|
|
-1.0, -1.0, 0.0, 1.0,
|
|
|
|
1.0, -1.0, 0.0, 1.0,
|
|
|
|
-1.0, 1.0, 0.0, 1.0,
|
|
|
|
1.0, 1.0, 0.0, 1.0,
|
|
|
|
#if PERSPECTIVE
|
|
|
|
// CRT back side point
|
|
|
|
0.0, 0.0, -1.0, 1.0,
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
const GLfloat crt_texcoords[] = {
|
|
|
|
0.0, 1.0,
|
|
|
|
1.0, 1.0,
|
|
|
|
0.0, 0.0,
|
|
|
|
1.0, 0.0,
|
|
|
|
};
|
|
|
|
const GLushort indices[] = {
|
|
|
|
// CRT screen quad
|
|
|
|
0, 1, 2, 2, 1, 3
|
|
|
|
#if PERSPECTIVE
|
|
|
|
// ...
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
|
|
|
demoModel *crt = calloc(1, sizeof(demoModel));
|
|
|
|
crt->numVertices = 4;
|
|
|
|
crt->numElements = 6;
|
|
|
|
|
|
|
|
crt->positions = malloc(sizeof(crt_positions));
|
|
|
|
memcpy(crt->positions, &crt_positions[0], sizeof(crt_positions));
|
|
|
|
crt->positionType = GL_FLOAT;
|
|
|
|
crt->positionSize = 4; // x,y,z coordinates
|
|
|
|
crt->positionArraySize = sizeof(crt_positions);
|
|
|
|
|
|
|
|
crt->texcoords = malloc(sizeof(crt_texcoords));
|
|
|
|
memcpy(crt->texcoords, &crt_texcoords[0], sizeof(crt_texcoords));
|
|
|
|
crt->texcoordType = GL_FLOAT;
|
|
|
|
crt->texcoordSize = 2; // s,t coordinates
|
|
|
|
crt->texcoordArraySize = sizeof(crt_texcoords);
|
|
|
|
|
|
|
|
crt->normals = NULL;
|
|
|
|
crt->normalType = GL_NONE;
|
|
|
|
crt->normalSize = GL_NONE;
|
|
|
|
crt->normalArraySize = 0;
|
|
|
|
|
|
|
|
crt->elements = malloc(sizeof(indices));
|
|
|
|
memcpy(crt->elements, &indices[0], sizeof(indices));
|
|
|
|
crt->elementType = GL_UNSIGNED_SHORT;
|
|
|
|
crt->elementArraySize = sizeof(indices);
|
|
|
|
|
|
|
|
return crt;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GLuint _create_VAO(demoModel *model) {
|
|
|
|
GLuint vaoName;
|
|
|
|
|
|
|
|
// Create a vertex array object (VAO) to cache model parameters
|
|
|
|
glGenVertexArrays(1, &vaoName);
|
|
|
|
glBindVertexArray(vaoName);
|
|
|
|
|
|
|
|
GLuint posBufferName;
|
|
|
|
|
|
|
|
// Create a vertex buffer object (VBO) to store positions and load data
|
|
|
|
glGenBuffers(1, &posBufferName);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, posBufferName);
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, model->positionArraySize, model->positions, GL_STATIC_DRAW);
|
|
|
|
|
|
|
|
// 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 = _get_gl_type_size(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?
|
|
|
|
|
|
|
|
if (model->normals) {
|
|
|
|
GLuint normalBufferName;
|
|
|
|
|
|
|
|
// Create a vertex buffer object (VBO) to store positions
|
|
|
|
glGenBuffers(1, &normalBufferName);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, normalBufferName);
|
|
|
|
|
|
|
|
// Allocate and load normal data into the VBO
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, model->normalArraySize, model->normals, GL_STATIC_DRAW);
|
|
|
|
|
|
|
|
// Enable the normal attribute for this VAO
|
|
|
|
glEnableVertexAttribArray(NORMAL_ATTRIB_IDX);
|
|
|
|
|
|
|
|
// Get the size of the normal type so we can set the stride properly
|
|
|
|
GLsizei normalTypeSize = _get_gl_type_size(model->normalType);
|
|
|
|
|
|
|
|
// 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(NORMAL_ATTRIB_IDX, // What attibute index will this array feed in the vertex shader (see buildProgram)
|
|
|
|
model->normalSize, // How many elements are there per normal?
|
|
|
|
model->normalType, // What is the type of this data?
|
|
|
|
GL_FALSE, // Do we want to normalize this data (0-1 range for fixed-pont types)
|
|
|
|
model->normalSize*normalTypeSize, // What is the stride (i.e. bytes between normals)?
|
|
|
|
0); // What is the offset in the VBO to the normal data?
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
if (model->texcoords) {
|
|
|
|
GLuint texcoordBufferName;
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// Create a VBO to store texcoords
|
|
|
|
glGenBuffers(1, &texcoordBufferName);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, texcoordBufferName);
|
|
|
|
|
|
|
|
// Allocate and load texcoord data into the VBO
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, model->texcoordArraySize, model->texcoords, GL_STATIC_DRAW);
|
|
|
|
|
|
|
|
// 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 = _get_gl_type_size(model->texcoordType);
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// 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?
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
GLuint elementBufferName;
|
|
|
|
|
|
|
|
// Create a VBO to vertex array elements
|
|
|
|
// This also attaches the element array buffer to the VAO
|
|
|
|
glGenBuffers(1, &elementBufferName);
|
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBufferName);
|
|
|
|
|
|
|
|
// Allocate and load vertex array element data into VBO
|
|
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, model->elementArraySize, model->elements, GL_STATIC_DRAW);
|
|
|
|
|
2014-10-25 18:38:28 +00:00
|
|
|
GL_ERRLOG("finished creating VAO/VBOs");
|
2014-09-27 18:03:51 +00:00
|
|
|
|
|
|
|
return vaoName;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void _destroy_VAO(GLuint vaoName) {
|
|
|
|
|
|
|
|
// Bind the VAO so we can get data from it
|
|
|
|
glBindVertexArray(vaoName);
|
|
|
|
|
|
|
|
// For every possible attribute set in the VAO
|
2014-12-21 21:59:04 +00:00
|
|
|
for (GLuint index = 0; index < 16; index++) {
|
2014-09-27 18:03:51 +00:00
|
|
|
// Get the VBO set for that attibute
|
2014-12-21 21:59:04 +00:00
|
|
|
GLuint bufName = 0;
|
2014-09-27 18:03:51 +00:00
|
|
|
glGetVertexAttribiv(index , GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, (GLint*)&bufName);
|
|
|
|
|
|
|
|
// If there was a VBO set...
|
|
|
|
if (bufName) {
|
|
|
|
//...delete the VBO
|
|
|
|
glDeleteBuffers(1, &bufName);
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// Get any element array VBO set in the VAO
|
2014-12-21 21:59:04 +00:00
|
|
|
{
|
|
|
|
GLuint bufName = 0;
|
|
|
|
glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, (GLint*)&bufName);
|
2014-09-27 18:03:51 +00:00
|
|
|
|
2014-12-21 21:59:04 +00:00
|
|
|
// If there was a element array VBO set in the VAO
|
|
|
|
if (bufName) {
|
|
|
|
//...delete the VBO
|
|
|
|
glDeleteBuffers(1, &bufName);
|
|
|
|
}
|
2014-09-27 18:03:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, delete the VAO
|
|
|
|
glDeleteVertexArrays(1, &vaoName);
|
|
|
|
|
|
|
|
GL_ERRLOG("destroying VAO/VBOs");
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
static GLuint _create_CRT_texture(void) {
|
|
|
|
GLuint texName;
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// 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);
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// Indicate that pixel rows are tightly packed
|
|
|
|
// (defaults to stride of 4 which is kind of only good for
|
|
|
|
// RGBA or FLOAT data types)
|
|
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// Allocate and load image data into texture
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, /*level*/0, /*internal format*/GL_RGBA, SCANWIDTH, SCANHEIGHT, /*border*/0, /*format*/GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
GL_ERRLOG("finished creating CRT texture");
|
|
|
|
|
|
|
|
return texName;
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
static GLuint _build_program(demoSource *vertexSource, demoSource *fragmentSource, bool hasNormal, bool hasTexcoord) {
|
|
|
|
GLuint prgName;
|
|
|
|
|
|
|
|
GLint logLength, status;
|
|
|
|
|
|
|
|
// String to pass to glShaderSource
|
|
|
|
GLchar *sourceString = NULL;
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// Determine if GLSL version 140 is supported by this context.
|
|
|
|
// We'll use this info to generate a GLSL shader source string
|
|
|
|
// with the proper version preprocessor string prepended
|
|
|
|
float glLanguageVersion;
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-10-08 04:59:21 +00:00
|
|
|
#if TARGET_OS_IPHONE
|
2014-09-27 18:03:51 +00:00
|
|
|
sscanf((char *)glGetString(GL_SHADING_LANGUAGE_VERSION), "OpenGL ES GLSL ES %f", &glLanguageVersion);
|
|
|
|
#else
|
|
|
|
sscanf((char *)glGetString(GL_SHADING_LANGUAGE_VERSION), "%f", &glLanguageVersion);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// GL_SHADING_LANGUAGE_VERSION returns the version standard version form
|
|
|
|
// with decimals, but the GLSL version preprocessor directive simply
|
|
|
|
// uses integers (thus 1.10 should 110 and 1.40 should be 140, etc.)
|
|
|
|
// We multiply the floating point number by 100 to get a proper
|
|
|
|
// number for the GLSL preprocessor directive
|
|
|
|
GLuint version = 100 * glLanguageVersion;
|
|
|
|
|
|
|
|
// Get the size of the version preprocessor string info so we know
|
|
|
|
// how much memory to allocate for our sourceString
|
|
|
|
const GLsizei versionStringSize = sizeof("#version 123\n");
|
|
|
|
|
|
|
|
// Create a program object
|
|
|
|
prgName = glCreateProgram();
|
|
|
|
|
|
|
|
// Indicate the attribute indicies on which vertex arrays will be
|
|
|
|
// set with glVertexAttribPointer
|
|
|
|
// See buildVAO to see where vertex arrays are actually set
|
|
|
|
glBindAttribLocation(prgName, POS_ATTRIB_IDX, "inPosition");
|
|
|
|
|
|
|
|
if (hasNormal) {
|
|
|
|
glBindAttribLocation(prgName, NORMAL_ATTRIB_IDX, "inNormal");
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
if (hasTexcoord) {
|
|
|
|
glBindAttribLocation(prgName, TEXCOORD_ATTRIB_IDX, "inTexcoord");
|
|
|
|
}
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
//////////////////////////////////////
|
|
|
|
// Specify and compile VertexShader //
|
|
|
|
//////////////////////////////////////
|
|
|
|
|
|
|
|
// Allocate memory for the source string including the version preprocessor information
|
|
|
|
sourceString = malloc(vertexSource->byteSize + versionStringSize);
|
|
|
|
|
|
|
|
// Prepend our vertex shader source string with the supported GLSL version so
|
|
|
|
// the shader will work on ES, Legacy, and OpenGL 3.2 Core Profile contexts
|
|
|
|
sprintf(sourceString, "#version %d\n%s", version, vertexSource->string);
|
|
|
|
|
|
|
|
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
|
|
|
glShaderSource(vertexShader, 1, (const GLchar **)&(sourceString), NULL);
|
|
|
|
glCompileShader(vertexShader);
|
|
|
|
glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &logLength);
|
|
|
|
|
|
|
|
if (logLength > 0) {
|
|
|
|
GLchar *log = (GLchar *)malloc(logLength);
|
|
|
|
glGetShaderInfoLog(vertexShader, logLength, &logLength, log);
|
|
|
|
LOG("Vtx Shader compile log:%s\n", log);
|
|
|
|
free(log);
|
|
|
|
}
|
|
|
|
|
|
|
|
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status);
|
|
|
|
if (status == 0) {
|
|
|
|
LOG("Failed to compile vtx shader:\n%s\n", sourceString);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(sourceString);
|
|
|
|
sourceString = NULL;
|
|
|
|
|
|
|
|
// Attach the vertex shader to our program
|
|
|
|
glAttachShader(prgName, vertexShader);
|
|
|
|
|
|
|
|
// Delete the vertex shader since it is now attached
|
|
|
|
// to the program, which will retain a reference to it
|
|
|
|
glDeleteShader(vertexShader);
|
|
|
|
|
|
|
|
/////////////////////////////////////////
|
|
|
|
// Specify and compile Fragment Shader //
|
|
|
|
/////////////////////////////////////////
|
|
|
|
|
|
|
|
// Allocate memory for the source string including the version preprocessor information
|
|
|
|
sourceString = malloc(fragmentSource->byteSize + versionStringSize);
|
|
|
|
|
|
|
|
// Prepend our fragment shader source string with the supported GLSL version so
|
|
|
|
// the shader will work on ES, Legacy, and OpenGL 3.2 Core Profile contexts
|
|
|
|
sprintf(sourceString, "#version %d\n%s", version, fragmentSource->string);
|
|
|
|
|
|
|
|
GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
|
|
glShaderSource(fragShader, 1, (const GLchar **)&(sourceString), NULL);
|
|
|
|
glCompileShader(fragShader);
|
|
|
|
glGetShaderiv(fragShader, GL_INFO_LOG_LENGTH, &logLength);
|
|
|
|
if (logLength > 0) {
|
|
|
|
GLchar *log = (GLchar *)malloc(logLength);
|
|
|
|
glGetShaderInfoLog(fragShader, logLength, &logLength, log);
|
|
|
|
LOG("Frag Shader compile log:\n%s\n", log);
|
|
|
|
free(log);
|
|
|
|
}
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
glGetShaderiv(fragShader, GL_COMPILE_STATUS, &status);
|
|
|
|
if (status == 0) {
|
|
|
|
LOG("Failed to compile frag shader:\n%s\n", sourceString);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(sourceString);
|
|
|
|
sourceString = NULL;
|
|
|
|
|
|
|
|
// Attach the fragment shader to our program
|
|
|
|
glAttachShader(prgName, fragShader);
|
|
|
|
|
|
|
|
// Delete the fragment shader since it is now attached
|
|
|
|
// to the program, which will retain a reference to it
|
|
|
|
glDeleteShader(fragShader);
|
|
|
|
|
|
|
|
//////////////////////
|
|
|
|
// Link the program //
|
|
|
|
//////////////////////
|
|
|
|
|
|
|
|
glLinkProgram(prgName);
|
|
|
|
glGetProgramiv(prgName, GL_INFO_LOG_LENGTH, &logLength);
|
|
|
|
if (logLength > 0) {
|
|
|
|
GLchar *log = (GLchar*)malloc(logLength);
|
|
|
|
glGetProgramInfoLog(prgName, logLength, &logLength, log);
|
|
|
|
LOG("Program link log:\n%s\n", log);
|
|
|
|
free(log);
|
|
|
|
}
|
|
|
|
|
|
|
|
glGetProgramiv(prgName, GL_LINK_STATUS, &status);
|
|
|
|
if (status == 0) {
|
|
|
|
LOG("Failed to link program");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
glValidateProgram(prgName);
|
|
|
|
glGetProgramiv(prgName, GL_INFO_LOG_LENGTH, &logLength);
|
|
|
|
if (logLength > 0) {
|
|
|
|
GLchar *log = (GLchar*)malloc(logLength);
|
|
|
|
glGetProgramInfoLog(prgName, logLength, &logLength, log);
|
|
|
|
LOG("Program validate log:\n%s\n", log);
|
|
|
|
free(log);
|
|
|
|
}
|
|
|
|
|
|
|
|
glGetProgramiv(prgName, GL_VALIDATE_STATUS, &status);
|
|
|
|
if (status == 0) {
|
|
|
|
LOG("Failed to validate program");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
glUseProgram(prgName);
|
|
|
|
|
|
|
|
///////////////////////////////////////
|
|
|
|
// Setup common program input points //
|
|
|
|
///////////////////////////////////////
|
|
|
|
|
|
|
|
GLint samplerLoc = glGetUniformLocation(prgName, "diffuseTexture");
|
|
|
|
|
|
|
|
// Indicate that the diffuse texture will be bound to texture unit 0
|
|
|
|
GLint unit = 0;
|
|
|
|
glUniform1i(samplerLoc, unit);
|
|
|
|
|
|
|
|
GL_ERRLOG("build program");
|
|
|
|
|
|
|
|
return prgName;
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
static demoSource *_create_shader_source(const char *fileName) {
|
|
|
|
demoSource *src = NULL;
|
2014-10-08 05:05:14 +00:00
|
|
|
#if defined(__APPLE__)
|
2014-09-27 18:03:51 +00:00
|
|
|
CFBundleRef mainBundle = CFBundleGetMainBundle();
|
|
|
|
CFStringRef fileString = CFStringCreateWithCString(/*allocator*/NULL, fileName, CFStringGetSystemEncoding());
|
|
|
|
CFURLRef fileURL = CFBundleCopyResourceURL(mainBundle, fileString, NULL, NULL);
|
|
|
|
CFRELEASE(fileString);
|
|
|
|
CFStringRef filePath = CFURLCopyFileSystemPath(fileURL, kCFURLPOSIXPathStyle);
|
|
|
|
CFRELEASE(fileURL);
|
|
|
|
src = srcLoadSource(CFStringGetCStringPtr(filePath, CFStringGetSystemEncoding()));
|
|
|
|
CFRELEASE(filePath);
|
2014-10-08 05:05:14 +00:00
|
|
|
#elif defined(CONFIG_DATADIR)
|
|
|
|
char *filePath = NULL;
|
|
|
|
asprintf(&filePath, "%s/%s/shaders/%s", CONFIG_DATADIR, PACKAGE_NAME, fileName);
|
|
|
|
if (filePath) {
|
|
|
|
src = srcLoadSource(filePath);
|
|
|
|
free(filePath);
|
|
|
|
} else {
|
|
|
|
ERRLOG("OOPS Could not load shader from %s (%s)", filePath, fileName);
|
|
|
|
}
|
2014-09-27 18:03:51 +00:00
|
|
|
#else
|
2014-10-08 05:05:14 +00:00
|
|
|
#error need to specify a DATADIR for shader files, etc
|
2014-09-27 18:03:51 +00:00
|
|
|
#endif
|
|
|
|
return src;
|
|
|
|
}
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-21 22:58:27 +00:00
|
|
|
static void gldriver_init_common(void) {
|
2014-09-27 18:03:51 +00:00
|
|
|
LOG("%s %s", glGetString(GL_RENDERER), glGetString(GL_VERSION));
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
viewportWidth = 400;
|
|
|
|
viewportHeight = 400;
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// ----------------------------
|
|
|
|
// Create CRT model VAO/VBOs
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// create CRT model
|
|
|
|
demoModel *crtModel = _create_CRT_model();
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// Build Vertex Buffer Objects (VBOs) and Vertex Array Object (VAOs) with our model data
|
|
|
|
crtVAOName = _create_VAO(crtModel);
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// Cache the number of element and primType to use later in our glDrawElements calls
|
|
|
|
crtNumElements = crtModel->numElements;
|
|
|
|
crtElementType = crtModel->elementType;
|
|
|
|
|
|
|
|
// We're using VBOs we can destroy all this memory since buffers are
|
|
|
|
// loaded into GL and we've saved anything else we need
|
|
|
|
mdlDestroyModel(crtModel);
|
|
|
|
crtModel = NULL;
|
|
|
|
|
|
|
|
// Build a default texture object with our image data
|
|
|
|
a2TextureName = _create_CRT_texture();
|
|
|
|
|
|
|
|
// ----------------------------
|
|
|
|
// Load/setup shaders
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-10-08 05:04:03 +00:00
|
|
|
demoSource *vtxSource = _create_shader_source("Basic.vsh");
|
|
|
|
demoSource *frgSource = _create_shader_source("Basic.fsh");
|
2014-09-27 18:03:51 +00:00
|
|
|
|
2014-11-22 22:08:53 +00:00
|
|
|
// Build/use Program
|
2014-09-27 18:03:51 +00:00
|
|
|
program = _build_program(vtxSource, frgSource, /*withNormal:*/false, /*withTexcoord:*/true);
|
|
|
|
|
|
|
|
srcDestroySource(vtxSource);
|
|
|
|
srcDestroySource(frgSource);
|
|
|
|
|
|
|
|
uniformMVPIdx = glGetUniformLocation(program, "modelViewProjectionMatrix");
|
|
|
|
if (uniformMVPIdx < 0) {
|
|
|
|
LOG("No modelViewProjectionMatrix in character shader");
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
2014-09-27 18:03:51 +00:00
|
|
|
|
|
|
|
// ----------------------------
|
|
|
|
// setup static OpenGL state
|
|
|
|
|
|
|
|
// Depth test will always be enabled
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
|
|
|
|
|
|
// We will always cull back faces for better performance
|
|
|
|
glEnable(GL_CULL_FACE);
|
|
|
|
|
|
|
|
// Always use this clear color
|
2014-10-08 05:05:14 +00:00
|
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
2014-09-27 18:03:51 +00:00
|
|
|
|
|
|
|
// Draw our scene once without presenting the rendered image.
|
|
|
|
// This is done in order to pre-warm OpenGL
|
|
|
|
// We don't need to present the buffer since we don't actually want the
|
|
|
|
// user to see this, we're only drawing as a pre-warm stage
|
|
|
|
video_driver_render();
|
|
|
|
|
|
|
|
// Check for errors to make sure all of our setup went ok
|
|
|
|
GL_ERRLOG("finished initialization");
|
2014-10-08 05:05:14 +00:00
|
|
|
|
|
|
|
#if !defined(__APPLE__)
|
2014-11-22 22:08:53 +00:00
|
|
|
//glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO);
|
2014-10-08 05:05:14 +00:00
|
|
|
#endif
|
|
|
|
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
|
|
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
|
|
|
ERRQUIT("framebuffer status: %04X", status);
|
|
|
|
}
|
2014-09-27 18:03:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void gldriver_shutdown(void) {
|
|
|
|
// Cleanup all OpenGL objects
|
|
|
|
glDeleteTextures(1, &a2TextureName);
|
|
|
|
_destroy_VAO(crtVAOName);
|
|
|
|
glDeleteProgram(program);
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
//
|
2014-09-27 18:03:51 +00:00
|
|
|
// update, render, reshape
|
2014-09-17 03:49:57 +00:00
|
|
|
//
|
2014-10-25 17:18:22 +00:00
|
|
|
#if USE_GLUT
|
2015-01-15 07:17:50 +00:00
|
|
|
static void gldriver_update(int unused) {
|
2015-01-18 19:10:16 +00:00
|
|
|
#if DEBUG_GL
|
|
|
|
static uint32_t prevCount = 0;
|
|
|
|
static uint32_t idleCount = 0;
|
|
|
|
|
|
|
|
idleCount++;
|
|
|
|
|
|
|
|
static struct timespec prev = { 0 };
|
|
|
|
struct timespec now;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
|
|
|
|
if (now.tv_sec != prev.tv_sec) {
|
|
|
|
LOG("gldriver_update() : %u", idleCount-prevCount);
|
|
|
|
prevCount = idleCount;
|
|
|
|
prev = now;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2014-09-22 00:26:37 +00:00
|
|
|
c_keys_handle_input(-1, 0, 0);
|
2014-09-17 03:49:57 +00:00
|
|
|
glutPostRedisplay();
|
2015-01-15 07:17:50 +00:00
|
|
|
glutTimerFunc(17, gldriver_update, 0);
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
2014-10-25 17:18:22 +00:00
|
|
|
#endif
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
static void gldriver_render(void) {
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
#if PERSPECTIVE
|
|
|
|
// Calculate modelview and projection matrices
|
|
|
|
GLfloat modelView[16];
|
|
|
|
GLfloat projection[16];
|
|
|
|
mtxLoadPerspective(projection, 90, (float)viewportWidth / (float)viewportHeight, 5.0, 10000);
|
|
|
|
#endif
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// Calculate the modelview matrix to render our character
|
|
|
|
// at the proper position and rotation
|
|
|
|
GLfloat mvp[16];
|
|
|
|
#if PERSPECTIVE
|
|
|
|
// Create model-view-projection matrix
|
|
|
|
//mtxLoadTranslate(modelView, 0, 150, -450);
|
|
|
|
//mtxRotateXApply(modelView, -90.0f);
|
|
|
|
//mtxRotateApply(modelView, -45.0f, 0.7, 0.3, 1);
|
|
|
|
mtxMultiply(mvp, projection, modelView);
|
|
|
|
#else
|
|
|
|
// Just load an identity matrix for a pure orthographic/non-perspective viewing
|
|
|
|
mtxLoadIdentity(mvp);
|
2014-09-17 03:49:57 +00:00
|
|
|
#endif
|
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// Have our shader use the modelview projection matrix
|
|
|
|
// that we calculated above
|
|
|
|
glUniformMatrix4fv(uniformMVPIdx, 1, GL_FALSE, mvp);
|
|
|
|
|
|
|
|
// Update texture from Apple //e internal framebuffer
|
2014-09-17 03:49:57 +00:00
|
|
|
const uint8_t * const fb = video_current_framebuffer();
|
|
|
|
uint8_t index;
|
2014-10-25 17:18:22 +00:00
|
|
|
#warning FIXME TODO use memcpy ... or don't use indexed color so that we don't need to do this copy?
|
2014-09-17 03:49:57 +00:00
|
|
|
unsigned int count = SCANWIDTH * SCANHEIGHT;
|
|
|
|
char pixels[SCANWIDTH * SCANHEIGHT * 4];
|
2014-09-27 18:03:51 +00:00
|
|
|
if (fb != NULL) {
|
|
|
|
for (unsigned int i=0, j=0; i<count; i++, j+=4) {
|
|
|
|
index = *(fb + i);
|
|
|
|
*( (uint32_t*)(pixels + j) ) = (uint32_t)(
|
|
|
|
((uint32_t)(colormap[index].red) << 0 ) |
|
|
|
|
((uint32_t)(colormap[index].green) << 8 ) |
|
|
|
|
((uint32_t)(colormap[index].blue) << 16) |
|
|
|
|
((uint32_t)0xff << 24)
|
|
|
|
);
|
|
|
|
}
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
glBindTexture(GL_TEXTURE_2D, a2TextureName);
|
2014-09-17 03:49:57 +00:00
|
|
|
glTexImage2D(GL_TEXTURE_2D, /*level*/0, /*internal format*/GL_RGBA, SCANWIDTH, SCANHEIGHT, /*border*/0, /*format*/GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid *)&pixels[0]);
|
2014-09-27 18:03:51 +00:00
|
|
|
glGetError();
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// Bind our vertex array object
|
|
|
|
glBindVertexArray(crtVAOName);
|
|
|
|
|
|
|
|
// Cull back faces now that we no longer render
|
|
|
|
// with an inverted matrix
|
|
|
|
glCullFace(GL_BACK);
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
// Draw the CRT object
|
|
|
|
glDrawElements(GL_TRIANGLES, crtNumElements, crtElementType, 0);
|
2014-10-08 05:05:14 +00:00
|
|
|
|
2015-01-11 20:27:39 +00:00
|
|
|
_vid_dirty = false;
|
|
|
|
|
2014-10-08 05:05:14 +00:00
|
|
|
#if USE_GLUT
|
|
|
|
glutSwapBuffers();
|
|
|
|
#endif
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
|
|
|
|
2014-09-21 22:58:27 +00:00
|
|
|
static void gldriver_reshape(int w, int h) {
|
2014-09-27 18:03:51 +00:00
|
|
|
//LOG("reshape to w:%d h:%d", w, h);
|
2014-09-17 03:49:57 +00:00
|
|
|
windowWidth = w;
|
|
|
|
windowHeight = h;
|
|
|
|
|
|
|
|
int w2 = ((float)h * (SCANWIDTH/(float)SCANHEIGHT));
|
|
|
|
int h2 = ((float)w / (SCANWIDTH/(float)SCANHEIGHT));
|
|
|
|
|
|
|
|
if (w2 <= w) {
|
|
|
|
// h is priority
|
|
|
|
viewportX = (w-w2)/2;
|
|
|
|
viewportY = 0;
|
|
|
|
viewportWidth = w2;
|
|
|
|
viewportHeight = h;
|
2014-09-27 18:03:51 +00:00
|
|
|
//LOG("OK1 : x:%d,y:%d w:%d,h:%d", viewportX, viewportY, viewportWidth, viewportHeight);
|
2014-09-17 03:49:57 +00:00
|
|
|
} else if (h2 <= h) {
|
|
|
|
viewportX = 0;
|
|
|
|
viewportY = (h-h2)/2;
|
|
|
|
viewportWidth = w;
|
|
|
|
viewportHeight = h2;
|
2014-09-27 18:03:51 +00:00
|
|
|
//LOG("OK2 : x:%d,y:%d w:%d,h:%d", viewportX, viewportY, viewportWidth, viewportHeight);
|
2014-09-17 03:49:57 +00:00
|
|
|
} else {
|
|
|
|
viewportX = 0;
|
|
|
|
viewportY = 0;
|
|
|
|
viewportWidth = w;
|
|
|
|
viewportHeight = h;
|
2014-09-27 18:03:51 +00:00
|
|
|
//LOG("small viewport : x:%d,y:%d w:%d,h:%d", viewportX, viewportY, viewportWidth, viewportHeight);
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
glViewport(viewportX, viewportY, viewportWidth, viewportHeight);
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
|
|
|
|
2014-09-27 18:03:51 +00:00
|
|
|
#if USE_GLUT
|
2014-12-21 21:51:45 +00:00
|
|
|
static int glutWindow = -1;
|
2014-10-08 05:05:14 +00:00
|
|
|
static void gldriver_init_glut(GLuint fbo) {
|
2014-09-17 03:49:57 +00:00
|
|
|
glutInit(&argc, argv);
|
2014-09-27 18:03:51 +00:00
|
|
|
glutInitDisplayMode(/*GLUT_DOUBLE|*/GLUT_RGBA|GLUT_DEPTH);
|
2014-09-17 03:49:57 +00:00
|
|
|
glutInitWindowSize(windowWidth, windowHeight);
|
|
|
|
//glutInitContextVersion(4, 0); -- Is this needed?
|
|
|
|
glutInitContextProfile(GLUT_CORE_PROFILE);
|
2014-12-21 21:51:45 +00:00
|
|
|
glutWindow = glutCreateWindow(PACKAGE_NAME);
|
2014-09-17 03:49:57 +00:00
|
|
|
GL_ERRQUIT("GLUT initialization");
|
|
|
|
|
|
|
|
if (glewInit()) {
|
|
|
|
ERRQUIT("Unable to initialize GLEW");
|
|
|
|
}
|
|
|
|
|
2014-09-21 22:58:27 +00:00
|
|
|
gldriver_init_common();
|
2014-09-17 03:49:57 +00:00
|
|
|
|
2015-01-15 07:17:50 +00:00
|
|
|
glutTimerFunc(16, gldriver_update, 0);
|
2014-09-27 18:03:51 +00:00
|
|
|
glutDisplayFunc(gldriver_render);
|
2014-09-21 22:58:27 +00:00
|
|
|
glutReshapeFunc(gldriver_reshape);
|
2014-09-17 03:49:57 +00:00
|
|
|
|
|
|
|
#if !defined(TESTING)
|
2014-09-21 22:58:27 +00:00
|
|
|
glutKeyboardFunc(gldriver_on_key_down);
|
|
|
|
glutKeyboardUpFunc(gldriver_on_key_up);
|
|
|
|
glutSpecialFunc(gldriver_on_key_special_down);
|
|
|
|
glutSpecialUpFunc(gldriver_on_key_special_up);
|
2014-11-22 22:08:53 +00:00
|
|
|
//glutMouseFunc(gldriver_mouse);
|
|
|
|
//glutMotionFunc(gldriver_mouse_drag);
|
2014-09-17 03:49:57 +00:00
|
|
|
#endif
|
2014-09-27 18:03:51 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
// renderer API
|
|
|
|
|
|
|
|
void video_driver_init(void *fbo) {
|
2014-11-29 21:31:21 +00:00
|
|
|
safe_to_do_opengl_logging = true;
|
2014-10-31 17:53:10 +00:00
|
|
|
#pragma GCC diagnostic push
|
|
|
|
#pragma GCC diagnostic ignored "-Wpointer-to-int-cast"
|
2014-09-27 18:03:51 +00:00
|
|
|
defaultFBO = (GLuint)fbo;
|
2014-10-31 17:53:10 +00:00
|
|
|
#pragma GCC diagnostic pop
|
2014-09-27 18:03:51 +00:00
|
|
|
#if defined(__APPLE__)
|
|
|
|
gldriver_init_common();
|
|
|
|
#elif USE_GLUT
|
2014-10-25 15:44:21 +00:00
|
|
|
gldriver_init_glut(defaultFBO);
|
2014-09-27 18:03:51 +00:00
|
|
|
#else
|
|
|
|
#error no working codepaths
|
|
|
|
#endif
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void video_driver_main_loop(void) {
|
2014-09-27 18:03:51 +00:00
|
|
|
#if USE_GLUT
|
2014-09-17 03:49:57 +00:00
|
|
|
glutMainLoop();
|
2014-09-27 18:03:51 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void video_driver_render(void) {
|
|
|
|
gldriver_render();
|
|
|
|
}
|
|
|
|
|
|
|
|
void video_driver_reshape(int w, int h) {
|
|
|
|
gldriver_reshape(w, h);
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void video_driver_shutdown(void) {
|
2014-12-21 21:51:45 +00:00
|
|
|
#if USE_GLUT
|
|
|
|
glutDestroyWindow(glutWindow);
|
|
|
|
#endif
|
2014-09-27 18:03:51 +00:00
|
|
|
gldriver_shutdown();
|
2014-09-17 03:49:57 +00:00
|
|
|
}
|
2014-11-22 22:08:53 +00:00
|
|
|
|