apple2ix/src/video/glvideo.c

559 lines
19 KiB
C
Raw Normal View History

/*
* 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
*
*/
#include "common.h"
#include "video/glvideo.h"
#include "video/glinput.h"
#include "video/glnode.h"
#include <regex.h>
bool safe_to_do_opengl_logging = false;
bool renderer_shutting_down = false;
volatile unsigned long _backend_vid_dirty = 0;
static int viewportX = 0;
static int viewportY = 0;
static int viewportWidth = SCANWIDTH*1.5;
static int viewportHeight = SCANHEIGHT*1.5;
GLint texSamplerLoc = UNINITIALIZED_GL;
GLint alphaValue = UNINITIALIZED_GL;
2015-09-27 18:51:40 +00:00
GLuint mainShaderProgram = UNINITIALIZED_GL;
2015-09-27 18:51:40 +00:00
bool hackAroundBrokenAdreno200 = false;
bool hackAroundBrokenAdreno205 = false;
2015-09-27 18:51:40 +00:00
GLfloat mvpIdentity[16] = { 0 };
static GLint uniformMVPIdx = UNINITIALIZED_GL;
2015-04-10 05:49:53 +00:00
static GLModel *crtModel = NULL;
2015-11-07 06:18:16 +00:00
static GLuint vertexShader = UNINITIALIZED_GL;
static GLuint fragShader = UNINITIALIZED_GL;
static video_backend_s glvideo_backend = { 0 };
#if USE_GLUT
static int windowWidth = SCANWIDTH*1.5;
static int windowHeight = SCANHEIGHT*1.5;
static int glutWindow = -1;
#endif
//----------------------------------------------------------------------------
static void gldriver_render(void);
static void _gldriver_setup_hackarounds(void) {
2015-09-27 18:51:40 +00:00
const char *vendor = (const char *)glGetString(GL_VENDOR);
const char *renderer = (const char *)glGetString(GL_RENDERER);
const char *version = (const char *)glGetString(GL_VERSION);
2015-09-27 21:29:10 +00:00
if (vendor && renderer && version) {
LOG("GL_VENDOR:[%s] GL_RENDERER:[%s] GL_VERSION:[%s]", vendor, renderer, version);
2015-09-27 21:29:10 +00:00
} else {
RELEASE_LOG("One or more of GL_VENDOR, GL_RENDERER, and GL_VERSION is NULL ... this is bad ...");
return;
2015-09-27 21:29:10 +00:00
}
2015-09-27 18:51:40 +00:00
do {
// As if we didn't have enough problems with Android ... Bionic's POSIX Regex support for android-10 appears
// very basic ... we can't match the word-boundary atomics \> \< \b ... sigh ... hopefully by the time there is
// an Adreno 2000 we can remove these hackarounds ;-)
regex_t qualcommRegex = { 0 };
int err = regcomp(&qualcommRegex, "qualcomm", REG_ICASE|REG_NOSUB|REG_EXTENDED);
if (err) {
LOG("Cannot compile regex : %d", err);
break;
2015-09-27 18:51:40 +00:00
}
int nomatch = regexec(&qualcommRegex, vendor, /*nmatch:*/0, /*pmatch:*/NULL, /*eflags:*/0);
regfree(&qualcommRegex);
if (nomatch) {
LOG("NO MATCH QUALCOMM >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
break;
}
regex_t adrenoRegex = { 0 };
err = regcomp(&adrenoRegex, "adreno", REG_ICASE|REG_NOSUB|REG_EXTENDED);
if (err) {
LOG("Cannot compile regex : %d", err);
break;
}
nomatch = regexec(&adrenoRegex, renderer, /*nmatch:*/0, /*pmatch:*/NULL, /*eflags:*/0);
regfree(&adrenoRegex);
if (nomatch) {
LOG("NO MATCH ADRENO >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
break;
}
regex_t twoHundredRegex = { 0 };
err = regcomp(&twoHundredRegex, "200", REG_ICASE|REG_NOSUB|REG_EXTENDED);
if (err) {
LOG("Cannot compile regex : %d", err);
break;
}
regex_t twoHundredFiveRegex = { 0 };
err = regcomp(&twoHundredFiveRegex, "205", REG_ICASE|REG_NOSUB|REG_EXTENDED);
if (err) {
LOG("Cannot compile regex : %d", err);
break;
}
2015-11-03 04:46:16 +00:00
regex_t twoHundredXXRegex = { 0 };
err = regcomp(&twoHundredXXRegex, "2[2-9][0-9]", REG_ICASE|REG_NOSUB|REG_EXTENDED);
if (err) {
LOG("Cannot compile regex : %d", err);
break;
}
int found200 = !regexec(&twoHundredRegex, renderer, /*nmatch:*/0, /*pmatch:*/NULL, /*eflags:*/0);
int found205 = !regexec(&twoHundredFiveRegex, renderer, /*nmatch:*/0, /*pmatch:*/NULL, /*eflags:*/0);
2015-11-03 04:46:16 +00:00
int found2XX = !regexec(&twoHundredXXRegex, renderer, /*nmatch:*/0, /*pmatch:*/NULL, /*eflags:*/0);
regfree(&twoHundredRegex);
regfree(&twoHundredFiveRegex);
2015-11-03 04:46:16 +00:00
regfree(&twoHundredXXRegex);
if (found200) {
LOG("HACKING AROUND BROKEN ADRENO 200");
hackAroundBrokenAdreno200 = true;
break;
}
if (found205) {
LOG("HACKING AROUND BROKEN ADRENO 205");
hackAroundBrokenAdreno200 = true;
hackAroundBrokenAdreno205 = true;
break;
}
2015-11-03 04:46:16 +00:00
if (found2XX) {
LOG("HACKING AROUND BROKEN ADRENO 2XX");
hackAroundBrokenAdreno200 = true;
break;
}
} while (0);
}
static void gldriver_init_common(void) {
_gldriver_setup_hackarounds();
#if !PERSPECTIVE
mtxLoadIdentity(mvpIdentity);
#endif
GLint value = UNINITIALIZED_GL;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &value);
LOG("GL_MAX_TEXTURE_SIZE:%d", value);
renderer_shutting_down = false;
if (!viewportWidth) {
viewportWidth = 400;
}
if (!viewportHeight) {
viewportHeight = 400;
}
// ----------------------------
// Create Cathode Ray Tube (CRT) model ... which currently is just a simple texture quad model ...
mdlDestroyModel(&crtModel);
glActiveTexture(TEXTURE_ACTIVE_FRAMEBUFFER);
#warning HACK FIXME TODO ^^^^^^^ is glActiveTexture() call needed here?
crtModel = mdlCreateQuad((GLModelParams_s){
.skew_x = -1.0, // model space coords
.skew_y = -1.0,
.z = 0.0,
.obj_w = 2.0, // entire model space (-1.0 to 1.0)
.obj_h = 2.0,
.positionUsageHint = GL_STATIC_DRAW, // positions don't change
.tex_w = SCANWIDTH,
.tex_h = SCANHEIGHT,
.texcoordUsageHint = GL_DYNAMIC_DRAW, // but texture (Apple //e framebuffer) does
}, (GLCustom){ 0 });
// ----------------------------
// Load/setup shaders
demoSource *vtxSource = glshader_createSource("Basic.vsh");
demoSource *frgSource = glshader_createSource("Basic.fsh");
assert(vtxSource && "Catastrophic failure if vertex shader did not compile");
assert(frgSource && "Catastrophic failure if fragment shader did not compile");
// Build/use Program
mainShaderProgram = glshader_buildProgram(vtxSource, frgSource, /*withTexcoord:*/true, &vertexShader, &fragShader);
glshader_destroySource(vtxSource);
glshader_destroySource(frgSource);
///////////////////////////////////////
// Setup common program input points //
///////////////////////////////////////
glUseProgram(mainShaderProgram);
texSamplerLoc = glGetUniformLocation(mainShaderProgram, "aTexture");
if (texSamplerLoc < 0) {
LOG("OOPS, no framebufferTexture shader : %d", texSamplerLoc);
} else {
glUniform1i(texSamplerLoc, TEXTURE_ID_FRAMEBUFFER);
}
GLint maxTextureUnits = -1;
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
if (maxTextureUnits < TEXTURE_ID_MAX) {
#warning FIXME TODO ... gracefully handle devices with low max texture units?
ERRLOG("OOPS ... MAX TEXTURE UNITS : %d (<%d)", maxTextureUnits, TEXTURE_ID_MAX);
} else {
LOG("GL_MAX_TEXTURE_IMAGE_UNITS : %d", maxTextureUnits);
}
uniformMVPIdx = glGetUniformLocation(mainShaderProgram, "modelViewProjectionMatrix");
if (uniformMVPIdx < 0) {
LOG("OOPS, no modelViewProjectionMatrix in shader : %d", uniformMVPIdx);
}
alphaValue = glGetUniformLocation(mainShaderProgram, "aValue");
if (alphaValue < 0) {
LOG("OOPS, no texture selector in shader : %d", alphaValue);
}
GL_ERRLOG("build program");
// ----------------------------
// setup static OpenGL state
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
// Set up to do blending of texture quads. Disabling DEPTH/CULL appears to fix blended quad/texture rendering on
// finicky Tegra 2. This generally appears to be the correct way to do it accoring to NVIDIA forums and:
// http://www.learnopengles.com/android-lesson-five-an-introduction-to-blending/
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// 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
gldriver_render();
// Check for errors to make sure all of our setup went ok
GL_ERRLOG("finished initialization");
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
ERRQUIT("framebuffer status: %04X", status);
}
}
static void _gldriver_shutdown(void) {
LOG("Beginning GLDriver shutdown ...");
// Cleanup all OpenGL objects
mdlDestroyModel(&crtModel);
2015-11-07 06:18:16 +00:00
// detach and delete the main shaders
// 2015/11/06 NOTE : Tegra 2 for mobile has a bug whereby you cannot detach/delete shaders immediately after
// creating the program. So we delete them during the shutdown sequence instead.
// https://code.google.com/p/android/issues/detail?id=61832
glDetachShader(mainShaderProgram, vertexShader);
glDetachShader(mainShaderProgram, fragShader);
glDeleteShader(vertexShader);
glDeleteShader(fragShader);
vertexShader = UNINITIALIZED_GL;
fragShader = UNINITIALIZED_GL;
glUseProgram(0);
2015-09-27 18:51:40 +00:00
if (mainShaderProgram != UNINITIALIZED_GL) {
glDeleteProgram(mainShaderProgram);
mainShaderProgram = UNINITIALIZED_GL;
}
glnode_shutdownNodes();
LOG("Completed GLDriver shutdown ...");
}
static void gldriver_shutdown(void) {
if (renderer_shutting_down) {
return;
}
#if USE_GLUT
glutLeaveMainLoop();
#endif
renderer_shutting_down = true;
_gldriver_shutdown();
}
//----------------------------------------------------------------------------
//
// update, render, reshape
//
2014-10-25 17:18:22 +00:00
#if USE_GLUT
static void gldriver_update(int unused) {
#if FPS_LOG
2015-01-18 19:10:16 +00:00
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
c_keys_handle_input(-1, 0, 0);
2015-02-25 04:53:19 +00:00
glutPostRedisplay();
glutTimerFunc(17, gldriver_update, 0);
}
2014-10-25 17:18:22 +00:00
#endif
static void gldriver_render(void) {
SCOPE_TRACE_VIDEO("glvideo render");
2015-01-24 07:37:43 +00:00
const uint8_t * const fb = video_current_framebuffer();
if (UNLIKELY(!fb)) {
return;
}
if (UNLIKELY(renderer_shutting_down)) {
return;
}
glClear(GL_COLOR_BUFFER_BIT);
#if MOBILE_DEVICE
glViewport(viewportX, viewportY, viewportWidth, viewportHeight);
#endif
#if PERSPECTIVE
// Calculate modelview and projection matrices
GLfloat modelView[16];
GLfloat projection[16];
mtxLoadPerspective(projection, 90, (float)viewportWidth / (float)viewportHeight, 5.0, 10000);
#endif
// Calculate the modelview matrix to render our character
// at the proper position and rotation
#if PERSPECTIVE
GLfloat mvp[16];
// 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);
// Have our shader use the modelview projection matrix
// that we calculated above
glUniformMatrix4fv(uniformMVPIdx, 1, GL_FALSE, mvp);
#else
// Just load an identity matrix for a pure orthographic/non-perspective viewing
glUniformMatrix4fv(uniformMVPIdx, 1, GL_FALSE, mvpIdentity);
#endif
unsigned long wasDirty = video_clearDirty();
char *pixels = (char *)crtModel->texPixels;
if (wasDirty) {
SCOPE_TRACE_VIDEO("pixel convert");
2015-02-25 04:53:19 +00:00
// Update texture from indexed-color Apple //e internal framebuffer
unsigned int count = SCANWIDTH * SCANHEIGHT;
2015-05-30 21:50:51 +00:00
for (unsigned int i=0, j=0; i<count; i++, j+=sizeof(PIXEL_TYPE)) {
2015-02-25 04:53:19 +00:00
uint8_t index = *(fb + i);
2015-05-30 21:50:51 +00:00
*( (PIXEL_TYPE*)(pixels + j) ) = (PIXEL_TYPE)(
((PIXEL_TYPE)(colormap[index].red) << SHIFT_R) |
((PIXEL_TYPE)(colormap[index].green) << SHIFT_G) |
((PIXEL_TYPE)(colormap[index].blue) << SHIFT_B) |
((PIXEL_TYPE)MAX_SATURATION << SHIFT_A)
2015-02-25 04:53:19 +00:00
);
}
}
glActiveTexture(TEXTURE_ACTIVE_FRAMEBUFFER);
glBindTexture(GL_TEXTURE_2D, crtModel->textureName);
glUniform1i(texSamplerLoc, TEXTURE_ID_FRAMEBUFFER);
if (wasDirty) {
SCOPE_TRACE_VIDEO("glvideo texImage2D");
_HACKAROUND_GLTEXIMAGE2D_PRE(TEXTURE_ACTIVE_FRAMEBUFFER, crtModel->textureName);
glTexImage2D(GL_TEXTURE_2D, /*level*/0, TEX_FORMAT_INTERNAL, SCANWIDTH, SCANHEIGHT, /*border*/0, TEX_FORMAT, TEX_TYPE, (GLvoid *)&pixels[0]);
2015-02-25 04:53:19 +00:00
}
// Bind our vertex array object
#if USE_VAO
glBindVertexArray(crtModel->vaoName);
#else
glBindBuffer(GL_ARRAY_BUFFER, crtModel->posBufferName);
GLsizei posTypeSize = getGLTypeSize(crtModel->positionType);
GLsizei texcoordTypeSize = getGLTypeSize(crtModel->texcoordType);
// 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)
crtModel->positionSize, // How many elements are there per position?
crtModel->positionType, // What is the type of this data?
GL_FALSE, // Do we want to normalize this data (0-1 range for fixed-pont types)
crtModel->positionSize*posTypeSize, // What is the stride (i.e. bytes between positions)?
0); // What is the offset in the VBO to the position data?
glEnableVertexAttribArray(POS_ATTRIB_IDX);
// 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
glBindBuffer(GL_ARRAY_BUFFER, crtModel->texcoordBufferName);
glVertexAttribPointer(TEXCOORD_ATTRIB_IDX, // What attibute index will this array feed in the vertex shader (see buildProgram)
crtModel->texcoordSize, // How many elements are there per texture coord?
crtModel->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)
crtModel->texcoordSize*texcoordTypeSize, // What is the stride (i.e. bytes between texcoords)?
0); // What is the offset in the VBO to the texcoord data?
glEnableVertexAttribArray(TEXCOORD_ATTRIB_IDX);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, crtModel->elementBufferName);
#endif
glUniform1f(alphaValue, 1.0);
// Cull back faces now that we no longer render
// with an inverted matrix
//glCullFace(GL_BACK);
// Draw the CRT object and others
_HACKAROUND_GLDRAW_PRE();
glDrawElements(GL_TRIANGLES, crtModel->numElements, crtModel->elementType, 0);
// Render HUD nodes
glnode_renderNodes();
#if USE_GLUT
glutSwapBuffers();
#endif
GL_ERRLOG("gldriver_render");
}
static void gldriver_reshape(int w, int h) {
//LOG("reshape to w:%d h:%d", w, h);
#if USE_GLUT
windowWidth = w;
windowHeight = h;
#endif
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;
//LOG("OK1 : x:%d,y:%d w:%d,h:%d", viewportX, viewportY, viewportWidth, viewportHeight);
} else if (h2 <= h) {
viewportX = 0;
viewportY = (h-h2)/2;
viewportWidth = w;
viewportHeight = h2;
//LOG("OK2 : x:%d,y:%d w:%d,h:%d", viewportX, viewportY, viewportWidth, viewportHeight);
} else {
viewportX = 0;
viewportY = 0;
viewportWidth = w;
viewportHeight = h;
//LOG("small viewport : x:%d,y:%d w:%d,h:%d", viewportX, viewportY, viewportWidth, viewportHeight);
}
glViewport(viewportX, viewportY, viewportWidth, viewportHeight);
2015-04-02 02:59:38 +00:00
// Reshape HUD nodes
glnode_reshapeNodes(w, h);
}
#if USE_GLUT
static void gldriver_init_glut(void) {
glutInit(&argc, argv);
glutInitDisplayMode(/*GLUT_DOUBLE|*/GLUT_RGBA);
glutInitWindowSize(windowWidth, windowHeight);
//glutInitContextVersion(4, 0); -- Is this needed?
glutInitContextProfile(GLUT_CORE_PROFILE);
glutWindow = glutCreateWindow(PACKAGE_NAME);
GL_ERRQUIT("GLUT initialization");
if (glewInit()) {
ERRQUIT("Unable to initialize GLEW");
}
gldriver_init_common();
glutTimerFunc(16, gldriver_update, 0);
glutDisplayFunc(gldriver_render);
glutReshapeFunc(gldriver_reshape);
glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS);
2015-02-16 02:32:45 +00:00
#if !TESTING
glutKeyboardFunc(gldriver_on_key_down);
glutKeyboardUpFunc(gldriver_on_key_up);
glutSpecialFunc(gldriver_on_key_special_down);
glutSpecialUpFunc(gldriver_on_key_special_up);
//glutMouseFunc(gldriver_mouse);
//glutMotionFunc(gldriver_mouse_drag);
#endif
}
#endif
//----------------------------------------------------------------------------
// backend renderer API
static void gldriver_init(void *unused) {
safe_to_do_opengl_logging = true;
#if defined(__APPLE__) || defined(ANDROID)
gldriver_init_common();
#elif USE_GLUT
gldriver_init_glut();
#else
#error no working codepaths
#endif
glnode_setupNodes();
}
static void gldriver_main_loop(void) {
#if USE_GLUT
glutMainLoop();
LOG("GLUT main loop finished...");
#endif
// fall through if not GLUT
}
2015-04-18 05:12:13 +00:00
__attribute__((constructor(CTOR_PRIORITY_EARLY)))
static void _init_glvideo(void) {
LOG("Initializing OpenGL renderer");
assert((video_backend == NULL) && "there can only be one!");
glvideo_backend.init = &gldriver_init;
glvideo_backend.main_loop = &gldriver_main_loop;
glvideo_backend.reshape = &gldriver_reshape;
glvideo_backend.render = &gldriver_render;
glvideo_backend.shutdown = &gldriver_shutdown;
video_backend = &glvideo_backend;
}