--- a/src/MacSrc/OpenGL.cc +++ b/src/MacSrc/OpenGL.cc @@ -7,6 +7,11 @@ #define GLEW_STATIC 1 #include #include +#elif defined(__ANDROID__) +// GLES 3.2 (see Shock.c's context creation) - none of the desktop GL +// headers below exist on Android; EGL only ever gives out GLES contexts. +#include +#include #else #define GL_GLEXT_PROTOTYPES #ifdef __APPLE__ @@ -20,6 +25,112 @@ #include #endif +#ifdef __ANDROID__ +// GLES has no immediate-mode rendering (glBegin/glVertex3f/glEnd, used +// throughout this file below) at all. These macros transparently replace +// it with an equivalent that accumulates vertex data into arrays and +// issues one glDrawArrays call at glEnd() time - every actual call site +// below (all unchanged) looks identical on both platforms. "position", +// bound to attribute location 0 in CreateShader() below, replaces +// gl_Vertex (a desktop-only builtin android/gles-shaders/main.vert can't +// use either). +#define glBegin questshock_glBegin +#define glVertex3f questshock_glVertex3f +#define glEnd questshock_glEnd +#define glVertexAttrib1f questshock_glVertexAttrib1f +#define glVertexAttrib2f questshock_glVertexAttrib2f +#define glPointSize questshock_glPointSize + +// Generous enough for both the largest opengl_draw_poly() polygon and a +// full frame's worth of opengl_draw_star() points accumulated between one +// opengl_begin_stars()/opengl_end_stars() pair. +static const int QUESTSHOCK_MAX_IM_VERTS = 4096; +static float questshock_im_pos[QUESTSHOCK_MAX_IM_VERTS * 3]; +static float questshock_im_tc[QUESTSHOCK_MAX_IM_VERTS * 2]; +static float questshock_im_light[QUESTSHOCK_MAX_IM_VERTS]; +static int questshock_im_count; +static GLenum questshock_im_mode; +static float questshock_im_cur_tc[2]; +static float questshock_im_cur_light; +static float questshock_point_size = 1.0f; + +// GLES has no glPointSize() at all - point size is instead read from +// gl_PointSize, set by a "pointSize" uniform in +// android/gles-shaders/main.vert. Stashed here and applied in +// questshock_glEnd() below, since opengl_begin_stars() calls this before +// glUseProgram(starShaderProgram...) - the star shader isn't the current +// program yet at this point. +static void questshock_glPointSize(GLfloat size) { questshock_point_size = size; } + +static void questshock_glBegin(GLenum mode) { + questshock_im_mode = mode; + questshock_im_count = 0; + questshock_im_cur_tc[0] = questshock_im_cur_tc[1] = 0.0f; + questshock_im_cur_light = 0.0f; +} + +static void questshock_glVertexAttrib1f(GLuint index, GLfloat v) { + // Only ever called for "light" at these call sites. + (void)index; + questshock_im_cur_light = v; +} + +static void questshock_glVertexAttrib2f(GLuint index, GLfloat a, GLfloat b) { + // Only ever called for "texcoords" at these call sites. + (void)index; + questshock_im_cur_tc[0] = a; + questshock_im_cur_tc[1] = b; +} + +static void questshock_glVertex3f(GLfloat x, GLfloat y, GLfloat z) { + if (questshock_im_count >= QUESTSHOCK_MAX_IM_VERTS) + return; + questshock_im_pos[questshock_im_count * 3 + 0] = x; + questshock_im_pos[questshock_im_count * 3 + 1] = y; + questshock_im_pos[questshock_im_count * 3 + 2] = z; + questshock_im_tc[questshock_im_count * 2 + 0] = questshock_im_cur_tc[0]; + questshock_im_tc[questshock_im_count * 2 + 1] = questshock_im_cur_tc[1]; + questshock_im_light[questshock_im_count] = questshock_im_cur_light; + questshock_im_count++; +} + +static void questshock_glEnd() { + GLint program = 0; + glGetIntegerv(GL_CURRENT_PROGRAM, &program); + GLint posAttrib = glGetAttribLocation(program, "position"); + GLint tcAttrib = glGetAttribLocation(program, "texcoords"); + GLint lightAttrib = glGetAttribLocation(program, "light"); + + if (questshock_im_mode == GL_POINTS) { + GLint pointSizeUniform = glGetUniformLocation(program, "pointSize"); + if (pointSizeUniform >= 0) + glUniform1f(pointSizeUniform, questshock_point_size); + } + + if (posAttrib >= 0) { + glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, 0, questshock_im_pos); + glEnableVertexAttribArray(posAttrib); + } + if (tcAttrib >= 0) { + glVertexAttribPointer(tcAttrib, 2, GL_FLOAT, GL_FALSE, 0, questshock_im_tc); + glEnableVertexAttribArray(tcAttrib); + } + if (lightAttrib >= 0) { + glVertexAttribPointer(lightAttrib, 1, GL_FLOAT, GL_FALSE, 0, questshock_im_light); + glEnableVertexAttribArray(lightAttrib); + } + + glDrawArrays(questshock_im_mode, 0, questshock_im_count); + + if (posAttrib >= 0) + glDisableVertexAttribArray(posAttrib); + if (tcAttrib >= 0) + glDisableVertexAttribArray(tcAttrib); + if (lightAttrib >= 0) + glDisableVertexAttribArray(lightAttrib); +} +#endif // __ANDROID__ + extern "C" { #include "mainloop.h" #include "map.h" @@ -229,6 +340,12 @@ GLuint shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertShader); glAttachShader(shaderProgram, fragShader); +#ifdef __ANDROID__ + // Must be bound before linking. Matches questshock_glEnd()'s + // glGetAttribLocation(program, "position") above and + // android/gles-shaders/main.vert's "position" attribute. + glBindAttribLocation(shaderProgram, 0, "position"); +#endif glLinkProgram(shaderProgram); glUseProgram(shaderProgram); @@ -313,10 +430,17 @@ glEnable(GL_CULL_FACE); glEnable(GL_BLEND); +#ifndef __ANDROID__ + // GLES has no fixed-function pipeline: no alpha test (emulated with a + // discard in android/gles-shaders/texture.frag instead), and point + // sprites are always implicitly on for GL_POINTS rendering (via + // gl_PointCoord in android/gles-shaders/star.frag) - no enum/enable + // for either exists in GLES at all. glEnable(GL_ALPHA_TEST); glEnable(GL_POINT_SPRITE); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glAlphaFunc(GL_GEQUAL, 0.05f); +#endif + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); CreateShader("main.vert", "texture.frag", &textureShaderProgram); CreateShader("main.vert", "color.frag", &colorShaderProgram);