--- a/src/MacSrc/OpenGL.cc +++ b/src/MacSrc/OpenGL.cc @@ -391,7 +391,16 @@ } void opengl_resize(int width, int height) { +#ifndef __ANDROID__ + // Skipped on Android - see opengl_swap_and_restore()'s comment below for + // why re-asserting an already-current context/surface every call breaks + // gl4es's own "current framebuffer" bookkeeping, even though the real + // driver binding never actually changes. This turned out to be the + // actual source of the persistent per-frame GL_INVALID_OPERATION (1282) + // that plagued 3D rendering: this same unconditional call was present at + // the top of nearly every function in this file, not just here. SDL_GL_MakeCurrent(window, context); +#endif int logical_width, logical_height; SDL_RenderGetLogicalSize(renderer, &logical_width, &logical_height); @@ -443,7 +452,10 @@ } void opengl_end_frame() { +#ifndef __ANDROID__ + // See opengl_resize()'s comment above. SDL_GL_MakeCurrent(window, context); +#endif // Done rendering to the frame buffer, reset back to normal BindFrameBuffer(nullptr); @@ -470,7 +482,10 @@ static bool nightsight_active() { return WareActive(player_struct.hardwarez_status[HARDWARE_GOGGLE_INFRARED]); } void opengl_start_frame() { +#ifndef __ANDROID__ + // See opengl_resize()'s comment above. SDL_GL_MakeCurrent(window, context); +#endif // Start rendering to our frame buffer canvas BindFrameBuffer(&backupBuffer); @@ -500,6 +515,130 @@ } #ifdef __ANDROID__ +// Real per-vertex arrays, not glBegin/glVertexAttrib/glVertex3f immediate +// mode: glVertexAttrib*() is GLES2's *constant*-attribute API - without an +// enabled vertex array it sets one value for the whole draw call, not a +// fresh value per vertex. gl4es's immediate-mode emulation only reproduces +// desktop GL's per-vertex-capture behavior for attribute 0 (position, +// driven directly by glVertex3f), not for custom attributes like this +// shader's texcoords/light. That means every one of this file's +// glBegin(GL_TRIANGLE_STRIP)-based draws - not just the UI-overlay quad +// this was first found on, but also opengl_light_tmap() (every textured +// wall/floor polygon), opengl_bitmap() (sprites) and opengl_draw_poly() +// (flat/gouraud polygons) via draw_vertex() - draws every vertex with +// whatever texcoord/light was set for the *last* vertex instead of its +// own, which is what actually produced flat-colored/untextured world +// geometry after this patch's first revision only fixed the final +// screen-composite quad (the one bug this was originally found on). +// +// android_imm_begin()/android_imm_vertex()/android_imm_end() below emulate +// just enough of glBegin(GL_TRIANGLE_STRIP)/glVertexAttrib*()/glVertex3f() +// immediate mode to replace every such call site on Android, by buffering +// vertices and flushing them as one real glDrawArrays() call - the engine's +// other rendering still uses real immediate mode for attribute 0 elsewhere +// on desktop builds, which depends on it not having an enabled array bound, +// so android_imm_end() always disables the arrays it enables before +// returning. +struct AndroidImmVertex { + float x, y, z; + float u, v; + float light; +}; + +// Arbitrarily-sized, but generous: the engine's polygons (room walls/ +// floors, sprites, screen-filling quads) are all small, hand-authored +// shapes, never anywhere near this many vertices. +static AndroidImmVertex s_immVerts[64]; +static int s_immVertCount; +static GLint s_immTcAttrib; +static GLint s_immLightAttrib; + +// tcAttrib < 0 means this draw has no texcoord attribute (flat/gouraud +// polygons via opengl_draw_poly()) - matches draw_vertex()'s own +// `tcAttrib >= 0` check below. +static void android_imm_begin(GLint tcAttrib, GLint lightAttrib) { + s_immVertCount = 0; + s_immTcAttrib = tcAttrib; + s_immLightAttrib = lightAttrib; +} + +static void android_imm_vertex(float x, float y, float z, float u, float v, float light) { + if (s_immVertCount >= (int)(sizeof(s_immVerts) / sizeof(s_immVerts[0]))) { + WARN("android_imm_vertex: dropping vertex past the %d-vertex limit", s_immVertCount); + return; + } + AndroidImmVertex &vert = s_immVerts[s_immVertCount++]; + vert.x = x; + vert.y = y; + vert.z = z; + vert.u = u; + vert.v = v; + vert.light = light; +} + +static void android_imm_end() { + if (s_immVertCount < 3) + return; + glEnableVertexAttribArray(0); + if (s_immTcAttrib >= 0) + glEnableVertexAttribArray(s_immTcAttrib); + glEnableVertexAttribArray(s_immLightAttrib); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(AndroidImmVertex), &s_immVerts[0].x); + if (s_immTcAttrib >= 0) + glVertexAttribPointer(s_immTcAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(AndroidImmVertex), + &s_immVerts[0].u); + glVertexAttribPointer(s_immLightAttrib, 1, GL_FLOAT, GL_FALSE, sizeof(AndroidImmVertex), + &s_immVerts[0].light); + glDrawArrays(GL_TRIANGLE_STRIP, 0, s_immVertCount); + glDisableVertexAttribArray(0); + if (s_immTcAttrib >= 0) + glDisableVertexAttribArray(s_immTcAttrib); + glDisableVertexAttribArray(s_immLightAttrib); +} + +// Same underlying bug as android_imm_*() above, for opengl_draw_star(): +// opengl_begin_stars()/opengl_draw_star()/opengl_end_stars() draw up to +// NUM_STARS (rendtool.c) individual GL_POINTS between one begin/end pair, +// each with its own per-star brightness via glVertexAttrib1f(lightAttrib, +// ...)/glVertex3f() immediate mode - under gl4es that made every star on +// screen render at whatever brightness was set for the *last* one. Shaped +// differently enough from android_imm_*() above (GL_POINTS instead of +// GL_TRIANGLE_STRIP, far more vertices, no texcoord) to warrant its own +// small buffer rather than overloading that one. +static AndroidImmVertex s_starVerts[1000]; // NUM_STARS, GameSrc/rendtool.c +static int s_starVertCount; +static GLint s_starLightAttrib; + +static void android_stars_begin(GLint lightAttrib) { + s_starVertCount = 0; + s_starLightAttrib = lightAttrib; +} + +static void android_stars_vertex(float x, float y, float z, float light) { + if (s_starVertCount >= (int)(sizeof(s_starVerts) / sizeof(s_starVerts[0]))) { + WARN("android_stars_vertex: dropping star past the %d-star limit", s_starVertCount); + return; + } + AndroidImmVertex &vert = s_starVerts[s_starVertCount++]; + vert.x = x; + vert.y = y; + vert.z = z; + vert.light = light; +} + +static void android_stars_end() { + if (s_starVertCount == 0) + return; + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(s_starLightAttrib); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(AndroidImmVertex), &s_starVerts[0].x); + glVertexAttribPointer(s_starLightAttrib, 1, GL_FLOAT, GL_FALSE, sizeof(AndroidImmVertex), + &s_starVerts[0].light); + glDrawArrays(GL_POINTS, 0, s_starVertCount); + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(s_starLightAttrib); +} + // Shared by opengl_swap_and_restore()'s UI-overlay blit and // android_composite_software_frame() below - draws an SDL_Surface as a // fullscreen textured quad via gl4es. SDL's own renderer can't be used @@ -537,42 +676,15 @@ set_blend_mode(blend); - // Real per-vertex arrays, not glBegin/glVertexAttrib/glVertex3f - // immediate mode: glVertexAttrib*() is GLES2's *constant*-attribute - // API - without an enabled vertex array it sets one value for the - // whole draw call, not a fresh value per vertex. gl4es's immediate-mode - // emulation only reproduces desktop GL's per-vertex-capture behavior - // for attribute 0 (position, driven directly by glVertex3f), not for - // custom attributes like this shader's texcoords/light. - // // V is flipped (1 at the bottom, 0 at the top): GL texture v=0 // addresses the first row of data passed to glTexImage2D, but uiRgba // (an SDL surface) stores its rows top-down. - struct QuadVertex { - float x, y, z; - float u, v; - float light; - }; - static const QuadVertex quadVerts[4] = { - {1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f}, - {1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f}, - {-1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f}, - {-1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}, - }; - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(tcAttrib); - glEnableVertexAttribArray(lightAttrib); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(QuadVertex), &quadVerts[0].x); - glVertexAttribPointer(tcAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(QuadVertex), &quadVerts[0].u); - glVertexAttribPointer(lightAttrib, 1, GL_FLOAT, GL_FALSE, sizeof(QuadVertex), - &quadVerts[0].light); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - // The engine's other rendering uses glBegin/glVertexAttrib/glVertex3f - // immediate mode for attribute 0, which depends on it not having an - // enabled array bound. - glDisableVertexAttribArray(0); - glDisableVertexAttribArray(tcAttrib); - glDisableVertexAttribArray(lightAttrib); + android_imm_begin(tcAttrib, lightAttrib); + android_imm_vertex(1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f); + android_imm_vertex(1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f); + android_imm_vertex(-1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f); + android_imm_vertex(-1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); + android_imm_end(); // glFinish(), not glFlush(): xr_swapchain_release() (called by our // caller right after this returns) hands the image to the OpenXR // runtime for cross-process consumption, which requires the GPU write @@ -642,6 +754,16 @@ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); +#ifdef __ANDROID__ + // Real per-vertex arrays, not glBegin/glVertexAttrib/glVertex3f + // immediate mode - see android_imm_begin()'s comment above for why. + android_imm_begin(tcAttrib, lightAttrib); + android_imm_vertex(1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f); + android_imm_vertex(1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f); + android_imm_vertex(-1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f); + android_imm_vertex(-1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f); + android_imm_end(); +#else glBegin(GL_TRIANGLE_STRIP); glVertexAttrib2f(tcAttrib, 1.0f, 0.0f); glVertexAttrib1f(lightAttrib, 1.0f); @@ -656,6 +778,7 @@ glVertexAttrib1f(lightAttrib, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f); glEnd(); +#endif // Finish drawing the 3d view glFlush(); @@ -709,7 +832,10 @@ render_width = width; render_height = height; +#ifndef __ANDROID__ + // See opengl_resize()'s comment above. SDL_GL_MakeCurrent(window, context); +#endif int lw, lh; SDL_RenderGetLogicalSize(renderer, &lw, &lh); @@ -729,7 +855,10 @@ } static bool opengl_cache_texture(CachedTexture toCache, grs_bitmap *bm) { +#ifndef __ANDROID__ + // See opengl_resize()'s comment above. SDL_GL_MakeCurrent(window, context); +#endif // GL_UNPACK_ROW_LENGTH must describe the buffer actually passed to // glTexImage2D() below (toCache.converted->pixels, a separately @@ -771,7 +900,10 @@ DEBUG("Clearing OpenGL texture cache."); +#ifndef __ANDROID__ + // See opengl_resize()'s comment above. SDL_GL_MakeCurrent(window, context); +#endif for (auto iter = texturesByBitsPtr.begin(); iter != texturesByBitsPtr.end();) { // don't free locked surfaces @@ -896,10 +1028,16 @@ light = 1.0f - (clut - grd_screen->ltab) / 4096.0f; } +#ifdef __ANDROID__ + (void)tcAttrib; // captured by android_imm_begin() instead - see its comment above + android_imm_vertex(vertex.x / 65536.0f, vertex.y / 65536.0f, -vertex.z / 65536.0f, + vertex.uv.u / 256.0f, vertex.uv.v / 256.0f, light); +#else if (tcAttrib >= 0) glVertexAttrib2f(tcAttrib, vertex.uv.u / 256.0, vertex.uv.v / 256.0); glVertexAttrib1f(lightAttrib, light); glVertex3f(vertex.x / 65536.0f, vertex.y / 65536.0f, -vertex.z / 65536.0f); +#endif } int opengl_draw_tmap(int n, g3s_phandle *vp, grs_bitmap *bm) { return opengl_light_tmap(n, vp, bm); } @@ -910,7 +1048,10 @@ return CLIP_ALL; } +#ifndef __ANDROID__ + // See opengl_resize()'s comment above. SDL_GL_MakeCurrent(window, context); +#endif set_blend_mode(bm->flags & BMF_TRANS); glUseProgram(textureShaderProgram.shaderProgram); @@ -928,13 +1069,21 @@ glUniformMatrix4fv(textureShaderProgram.uniView, 1, false, ViewMatrix); glUniformMatrix4fv(textureShaderProgram.uniProj, 1, false, ProjectionMatrix); +#ifdef __ANDROID__ + android_imm_begin(tcAttrib, lightAttrib); +#else glBegin(GL_TRIANGLE_STRIP); +#endif draw_vertex(*(vp[1]), tcAttrib, lightAttrib); draw_vertex(*(vp[0]), tcAttrib, lightAttrib); draw_vertex(*(vp[2]), tcAttrib, lightAttrib); if (n > 3) draw_vertex(*(vp[3]), tcAttrib, lightAttrib); +#ifdef __ANDROID__ + android_imm_end(); +#else glEnd(); +#endif return CLIP_NONE; } @@ -949,7 +1098,10 @@ return CLIP_ALL; } +#ifndef __ANDROID__ + // See opengl_resize()'s comment above. SDL_GL_MakeCurrent(window, context); +#endif set_blend_mode(bm->flags & BMF_TRANS); glUseProgram(textureShaderProgram.shaderProgram); @@ -976,6 +1128,14 @@ glUniform1i(textureShaderProgram.uniMutant, (bm->type == BMT_TLUC8) || (bm->flags == BMF_TLUC8)); +#ifdef __ANDROID__ + android_imm_begin(tcAttrib, lightAttrib); + android_imm_vertex(convx(vpl[1]->x), convy(vpl[1]->y), 0.0f, 1.0f, 0.0f, light); + android_imm_vertex(convx(vpl[0]->x), convy(vpl[0]->y), 0.0f, 0.0f, 0.0f, light); + android_imm_vertex(convx(vpl[2]->x), convy(vpl[2]->y), 0.0f, 1.0f, 1.0f, light); + android_imm_vertex(convx(vpl[3]->x), convy(vpl[3]->y), 0.0f, 0.0f, 1.0f, light); + android_imm_end(); +#else glBegin(GL_TRIANGLE_STRIP); glVertexAttrib2f(tcAttrib, 1.0f, 0.0f); glVertexAttrib1f(lightAttrib, light); @@ -990,6 +1150,7 @@ glVertexAttrib1f(lightAttrib, light); glVertex3f(convx(vpl[3]->x), convy(vpl[3]->y), 0.0f); glEnd(); +#endif glUniform1i(textureShaderProgram.uniMutant, false); @@ -1007,7 +1168,10 @@ return CLIP_ALL; } +#ifndef __ANDROID__ + // See opengl_resize()'s comment above. SDL_GL_MakeCurrent(window, context); +#endif glUseProgram(colorShaderProgram.shaderProgram); glUniformMatrix4fv(colorShaderProgram.uniView, 1, false, ViewMatrix); @@ -1064,7 +1228,11 @@ GLint lightAttrib = colorShaderProgram.lightAttrib; long a = 0, b = n_verts - 1; +#ifdef __ANDROID__ + android_imm_begin(-1, lightAttrib); +#else glBegin(GL_TRIANGLE_STRIP); +#endif while (true) { draw_vertex(*(p[a]), -1, lightAttrib); if (a++ == b) @@ -1073,7 +1241,11 @@ if (b-- == a) break; } +#ifdef __ANDROID__ + android_imm_end(); +#else glEnd(); +#endif return CLIP_NONE; } @@ -1085,7 +1257,10 @@ } void opengl_begin_stars() { +#ifndef __ANDROID__ + // See opengl_resize()'s comment above. SDL_GL_MakeCurrent(window, context); +#endif // Bind the star shader before setting the point size: on Android, // questshock_glPointSize() (see the __ANDROID__ block above) needs @@ -1108,11 +1283,19 @@ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); +#ifdef __ANDROID__ + android_stars_begin(starShaderProgram.lightAttrib); +#else glBegin(GL_POINTS); +#endif } void opengl_end_stars() { +#ifdef __ANDROID__ + android_stars_end(); +#else glEnd(); +#endif // Turn off the stencil test and blending opengl_set_stencil(0x00); @@ -1120,9 +1303,11 @@ } int opengl_draw_star(fix star_x, fix star_y, int c, bool anti_alias) { +#ifndef __ANDROID__ + // See opengl_resize()'s comment above. SDL_GL_MakeCurrent(window, context); - GLint lightAttrib = starShaderProgram.lightAttrib; +#endif float x = fix_float(star_x); float y = fix_float(star_y); @@ -1142,8 +1327,12 @@ int color = (std_color_base + std_color_range - 1 - c); color = (255 * color) / (std_color_range + 1); +#ifdef __ANDROID__ + android_stars_vertex(x, y, -0.25f, color / 255.0f); +#else glVertexAttrib1f(lightAttrib, color / 255.0f); glVertex3f(x, y, -0.25f); +#endif return CLIP_NONE; }