diff --git a/CMakeLists.txt b/CMakeLists.txt index a2b59649..bc6adf2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,12 @@ endif() # Platform specific files builds file(GLOB PLATFORM_SOURCES src/${PLATFORM}/*.c) +# OpenGL ES 2.0 / WebGL2 compatibility +option(ENABLE_GLES2 "Build the gl_renderer against OpenGL ES 2.0 / WebGL 1.0 instead of desktop GL 4.1 Core" OFF) +if (ENABLE_GLES2 AND ENABLE_LEGACY_GL) + set(ENABLE_LEGACY_GL OFF) +endif() + # OpenGL ES 3.0 / WebGL2 compatibility option(ENABLE_GLES "Build the gl_renderer against OpenGL ES 3.0 / WebGL2 instead of desktop GL 4.1 Core" OFF) if (ENABLE_GLES AND ENABLE_LEGACY_GL) diff --git a/src/desktop/backends/glfw2.c b/src/desktop/backends/glfw2.c index 400047a8..f3c86a91 100644 --- a/src/desktop/backends/glfw2.c +++ b/src/desktop/backends/glfw2.c @@ -159,13 +159,15 @@ bool platformInit(int32_t reqW, int32_t reqH, const char *title, bool headless) glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 1); glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 1); } else { -#ifdef ENABLE_GLES +#if defined(ENABLE_GLES2) || defined(ENABLE_GLES) glfwOpenWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); - glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3); +#ifdef ENABLE_GLES2 + glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 2); glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 0); #else glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3); glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 2); +#endif glfwOpenWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwOpenWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE); diff --git a/src/desktop/backends/glfw3.c b/src/desktop/backends/glfw3.c index 99e9abe7..7a0afcec 100644 --- a/src/desktop/backends/glfw3.c +++ b/src/desktop/backends/glfw3.c @@ -183,9 +183,13 @@ bool platformInit(int32_t reqW, int32_t reqH, const char *title, bool headless) glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); } else { -#ifdef ENABLE_GLES +#if defined(ENABLE_GLES2) || defined(ENABLE_GLES) glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); +#ifdef ENABLE_GLES2 + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); +#else glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); +#endif glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); #else glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); diff --git a/src/desktop/backends/sdl2.c b/src/desktop/backends/sdl2.c index cbd6c5cc..13e26b2e 100644 --- a/src/desktop/backends/sdl2.c +++ b/src/desktop/backends/sdl2.c @@ -93,11 +93,15 @@ bool platformInit(int reqW, int reqH, const char *title, bool headless) { SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); } else if (gfx == MODERN_GL) { -#ifdef ENABLE_GLES +#if defined(ENABLE_GLES2) || defined(ENABLE_GLES) #ifdef SDL_GL_CONTEXT_PROFILE_MASK SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); #endif +#if defined(ENABLE_GLES2) + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); +#elif defined(ENABLE_GLES) SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); +#endif SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); #else SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index 3c8bab4f..7b7c65be 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -2,7 +2,11 @@ #include "matrix_math.h" #include "text_utils.h" -#ifdef __EMSCRIPTEN__ +#ifdef ENABLE_GLES2 + #ifdef __EMSCRIPTEN__ + #include + #endif +#elif defined(__EMSCRIPTEN__) #include #else #include @@ -26,25 +30,50 @@ #define INDICES_PER_QUAD 6 // ===[ Shader Sources ]=== -#ifdef ENABLE_GLES +#ifdef ENABLE_GLES2 + #define GLSL_VERSION_DIRECTIVE "#version 100\n" + #define GLSL_VERTEX_PRECISION "precision highp float;\n" + #define GLSL_FRAGMENT_PRECISION "precision mediump float;\n" + #define LAYOUT_LOC(x) "" + #define VTX_IN "attribute " + #define VTX_OUT "varying " + #define FRAG_IN "varying " + #define TEXTURE_SAMPLE "texture2D" + #define FRAG_COLOR_DECL "" + #define FRAG_COLOR "gl_FragColor" +#elif defined(ENABLE_GLES) #define GLSL_VERSION_DIRECTIVE "#version 300 es\n" #define GLSL_VERTEX_PRECISION "precision highp float;\n" #define GLSL_FRAGMENT_PRECISION "precision mediump float;\n" + #define LAYOUT_LOC(x) "layout(location = " #x ") " + #define VTX_IN "in " + #define VTX_OUT "out " + #define FRAG_IN "in " + #define TEXTURE_SAMPLE "texture" + #define FRAG_COLOR_DECL "out vec4 fragColor;\n" + #define FRAG_COLOR "fragColor" #else #define GLSL_VERSION_DIRECTIVE "#version 410 core\n" #define GLSL_VERTEX_PRECISION "" #define GLSL_FRAGMENT_PRECISION "" + #define LAYOUT_LOC(x) "layout(location = " #x ") " + #define VTX_IN "in " + #define VTX_OUT "out " + #define FRAG_IN "in " + #define TEXTURE_SAMPLE "texture" + #define FRAG_COLOR_DECL "out vec4 fragColor;\n" + #define FRAG_COLOR "fragColor" #endif static const char* vertexShaderSource = GLSL_VERSION_DIRECTIVE GLSL_VERTEX_PRECISION - "layout(location = 0) in vec2 aPos;\n" - "layout(location = 1) in vec4 aColor;\n" - "layout(location = 2) in vec2 aTexCoord;\n" + LAYOUT_LOC(0) VTX_IN "vec2 aPos;\n" + LAYOUT_LOC(1) VTX_IN "vec4 aColor;\n" + LAYOUT_LOC(2) VTX_IN "vec2 aTexCoord;\n" "uniform mat4 uProjection;\n" - "out vec2 vTexCoord;\n" - "out vec4 vColor;\n" + VTX_OUT "vec2 vTexCoord;\n" + VTX_OUT "vec4 vColor;\n" "void main() {\n" " gl_Position = uProjection * vec4(aPos, 0.0, 1.0);\n" " vTexCoord = aTexCoord;\n" @@ -54,21 +83,21 @@ static const char* vertexShaderSource = static const char* fragmentShaderSource = GLSL_VERSION_DIRECTIVE GLSL_FRAGMENT_PRECISION - "in vec2 vTexCoord;\n" - "in vec4 vColor;\n" + FRAG_IN "vec2 vTexCoord;\n" + FRAG_IN "vec4 vColor;\n" "uniform sampler2D uTexture;\n" "uniform float uAlphaTestRef;\n" "uniform bool uAlphaTestEnabled;\n" - "uniform vec4 uFogColor;\n" // rgb = fog color, a = enable flag (0 or 1) - "out vec4 fragColor;\n" + "uniform vec4 uFogColor;\n" + FRAG_COLOR_DECL "void main() {\n" - " vec4 c = texture(uTexture, vTexCoord) * vColor;\n" + " vec4 c = " TEXTURE_SAMPLE "(uTexture, vTexCoord) * vColor;\n" " if (uAlphaTestEnabled)" " {" " if (uAlphaTestRef >= c.a) discard;\n" " }" " c.rgb = mix(c.rgb, uFogColor.rgb, uFogColor.a);\n" - " fragColor = c;\n" + " " FRAG_COLOR " = c;\n" "}\n"; @@ -96,6 +125,13 @@ static GLuint linkProgram(GLuint vertShader, GLuint fragShader) { GLuint program = glCreateProgram(); glAttachShader(program, vertShader); glAttachShader(program, fragShader); + +#ifdef ENABLE_GLES2 + glBindAttribLocation(program, 0, "aPos"); + glBindAttribLocation(program, 1, "aColor"); + glBindAttribLocation(program, 2, "aTexCoord"); +#endif + glLinkProgram(program); GLint success; @@ -147,9 +183,19 @@ static void flushBatch(GLRenderer* gl) { glGetProgramiv(Shader, GL_ACTIVE_UNIFORMS, &UniformCount); const GLchar* name = "gm_BaseTexture"; - GLuint index; + GLuint index = GL_INVALID_INDEX; - glGetUniformIndices(Shader, 1, &name, &index); + if (gl->base.currentShader != -1) { + int32_t index = gl->gmBaseTextureIndex[gl->base.currentShader]; + if (index != -1) { + int32_t slot = gl->sampler2DLookUpTable[gl->base.currentShader][index]; + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_2D, gl->currentTextureId); + } + } else { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, gl->currentTextureId); + } if (index != GL_INVALID_INDEX) { @@ -175,17 +221,28 @@ static void flushBatch(GLRenderer* gl) { int32_t vertexCount = gl->batchCount * singleVertexCount; int32_t indexCount = gl->batchCount * INDICES_PER_QUAD; - // Bind the VAO so the EBO binding it carries is what glDrawElements uses. - // Without this, glDrawElements would treat the nullptr indices arg as a literal pointer to client memory and SEGV inside the driver during async upload. - glBindVertexArray(gl->vao); - - glBindBuffer(GL_ARRAY_BUFFER, gl->vbo); - glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * FLOATS_PER_VERTEX * sizeof(float), gl->vertexData); - - + if (gl->hasVAOs) { + // Bind the VAO so the EBO binding it carries is what glDrawElements uses. + // Without this, glDrawElements would treat the nullptr indices arg as a literal pointer to client memory and SEGV inside the driver during async upload. + glBindVertexArray(gl->vao); + glBindBuffer(GL_ARRAY_BUFFER, gl->vbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * FLOATS_PER_VERTEX * sizeof(float), gl->vertexData); + } else { + glBindBuffer(GL_ARRAY_BUFFER, gl->vbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount * FLOATS_PER_VERTEX * sizeof(float), gl->vertexData); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gl->ebo); + + int32_t stride = FLOATS_PER_VERTEX * sizeof(float); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, stride, (void*) 0); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, stride, (void*) (4 * sizeof(float))); + glEnableVertexAttribArray(1); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, stride, (void*) (2 * sizeof(float))); + glEnableVertexAttribArray(2); + } if (gl->batchType == BATCHTYPE_QUAD) { - glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, nullptr); + glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, nullptr); } else if (gl->batchType == BATCHTYPE_TRIANGLE) { glDrawArrays(GL_TRIANGLES, 0, gl->batchCount * VERTICES_PER_TRIANGLE); } else { @@ -213,6 +270,12 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { GLRenderer* gl = (GLRenderer*) renderer; renderer->dataWin = dataWin; +#ifdef __EMSCRIPTEN__ + gl->hasVAOs = true; +#else + gl->hasVAOs = (glBindVertexArray != NULL && glGenVertexArrays != NULL); +#endif + //compile shaders // If the default shaders fail we have bigger issues bool vertexShaderOK = false; @@ -239,13 +302,13 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { fprintf(stderr, "GL: Compiling %s Vertex Shader\n", shdr->name); bool vertexShaderOK = false; bool fragmentShaderOK = false; -#ifdef ENABLE_GLES +#if defined(ENABLE_GLES2) || defined(ENABLE_GLES) GLuint vertShaderT = compileShader(GL_VERTEX_SHADER, shdr->glslES_Vertex, &vertexShaderOK); #else GLuint vertShaderT = compileShader(GL_VERTEX_SHADER, shdr->glsl_Vertex, &vertexShaderOK); #endif fprintf(stderr, "GL: Compiling %s Fragment Shader\n", shdr->name); -#ifdef ENABLE_GLES +#if defined(ENABLE_GLES2) || defined(ENABLE_GLES) GLuint fragShaderT = compileShader(GL_FRAGMENT_SHADER, shdr->glslES_Fragment, &fragmentShaderOK); #else GLuint fragShaderT = compileShader(GL_FRAGMENT_SHADER, shdr->glsl_Fragment, &fragmentShaderOK); @@ -263,20 +326,23 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { int32_t SamplerIndex = 0; GLint UniformCount; glGetProgramiv(gl->gmlShaders[i], GL_ACTIVE_UNIFORMS, &UniformCount); - + //I know it looks baddd.... butttt it works gl->sampler2DLookUpTable[i] = safeMalloc(UniformCount * sizeof(int32_t)); + gl->gmBaseTextureIndex = safeRealloc(gl->gmBaseTextureIndex, gl->gmlShaderCount * sizeof(int32_t)); + gl->gmBaseTextureIndex[i] = -1; // Default state + GLint LongestUniformName = 0; glGetProgramiv(gl->gmlShaders[i], GL_ACTIVE_UNIFORM_MAX_LENGTH, &LongestUniformName); char *UniformName = safeMalloc(LongestUniformName+1); for (GLint b = 0; b < UniformCount; b++) { - + GLsizei length = 0; GLint size = 0; GLenum type = 0; glGetActiveUniform(gl->gmlShaders[i], b, LongestUniformName, &length, &size, &type, UniformName); - + gl->sampler2DLookUpTable[i][b] = -1; if (type == GL_SAMPLER_2D) { @@ -287,6 +353,9 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { SamplerIndex += 1; } + if (strcmp(UniformName, "gm_BaseTexture") == 0) { + gl->gmBaseTextureIndex[i] = b; + } } free(UniformName); @@ -310,23 +379,25 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { glUniform1f(gl->uAlphaTestRef, -1.0f); glUniform4f(gl->uFogColor, 0.0f, 0.0f, 0.0f, 0.0f); - // Create VAO/VBO/EBO - glGenVertexArrays(1, &gl->vao); +// Create VAO/VBO/EBO + if (gl->hasVAOs) { + glGenVertexArrays(1, &gl->vao); + glBindVertexArray(gl->vao); + } + glGenBuffers(1, &gl->vbo); glGenBuffers(1, &gl->ebo); - glBindVertexArray(gl->vao); - // VBO: sized for max quads int32_t vboSize = MAX_QUADS * VERTICES_PER_QUAD * FLOATS_PER_VERTEX * (int32_t) sizeof(float); glBindBuffer(GL_ARRAY_BUFFER, gl->vbo); glBufferData(GL_ARRAY_BUFFER, vboSize, nullptr, GL_DYNAMIC_DRAW); - // EBO: pre-fill with quad index pattern (0,1,2,2,3,0 repeated) - int32_t eboSize = MAX_QUADS * INDICES_PER_QUAD * (int32_t) sizeof(uint32_t); - uint32_t* indices = safeMalloc(eboSize); + // EBO: pre-fill with quad index pattern using 16-bit indices (globally optimized) + int32_t eboSize = MAX_QUADS * INDICES_PER_QUAD * (int32_t) sizeof(uint16_t); + uint16_t* indices = safeMalloc(eboSize); for (int32_t i = 0; MAX_QUADS > i; i++) { - uint32_t base = (uint32_t) i * 4; + uint16_t base = (uint16_t) i * 4; indices[i * 6 + 0] = base + 0; indices[i * 6 + 1] = base + 1; indices[i * 6 + 2] = base + 2; @@ -340,6 +411,7 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { // Vertex attributes: pos(2f), texcoord(2f), color(4f) int32_t stride = FLOATS_PER_VERTEX * (int32_t) sizeof(float); +#ifndef ENABLE_GLES2 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, stride, (void*) 0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, stride, (void*) (4 * sizeof(float))); @@ -348,6 +420,7 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { glEnableVertexAttribArray(2); glBindVertexArray(0); +#endif // Allocate CPU-side vertex buffer gl->vertexData = safeMalloc(MAX_QUADS * VERTICES_PER_QUAD * FLOATS_PER_VERTEX * sizeof(float)); @@ -405,9 +478,9 @@ static void glGpuSetShader(Renderer* renderer, int32_t ShaderIndex) { GLint gm_FogStart = glGetUniformLocation(Shader, "gm_FogStart"); - GLint gm_RcpFogRange = glGetUniformLocation(Shader, "gm_RcpFogRange"); + GLint gm_RcpFogRange = glGetUniformLocation(Shader, "gm_RcpFogRange"); GLint gm_PS_FogEnabled = glGetUniformLocation(Shader, "gm_PS_FogEnabled"); - GLint gm_FogColour = glGetUniformLocation(Shader, "gm_FogColour"); + GLint gm_FogColour = glGetUniformLocation(Shader, "gm_FogColour"); GLint gm_VS_FogEnabled = glGetUniformLocation(Shader, "gm_VS_FogEnabled"); //Lights are for another time @@ -463,7 +536,7 @@ static void glShaderSettingsRefresh(Renderer* renderer) { glUniformMatrix4fv(gl->uProjection, 1, GL_FALSE, renderer->gmlMatrices[MATRIX_WORLD_VIEW_PROJECTION].m); glUniform4f(gl->uFogColor, FogR, FoGG, FogB, gl->fogEnable ? 1.0f : 0.0f); glUniform1f(gl->uAlphaTestRef, gl->alphaTestRef); - glUniform1i(gl->uAlphaTestEnabled, gl->alphaTestEnable); + glUniform1i(gl->uAlphaTestEnabled, gl->alphaTestEnable); glUniform1i(gl->uTexture, 1); } } @@ -493,7 +566,9 @@ static void glDestroy(Renderer* renderer) { glDeleteTextures((GLsizei) gl->textureCount, gl->glTextures); glDeleteProgram(gl->shaderProgram); - glDeleteVertexArrays(1, &gl->vao); + if (gl->hasVAOs) { + glDeleteVertexArrays(1, &gl->vao); + } glDeleteBuffers(1, &gl->vbo); glDeleteBuffers(1, &gl->ebo); @@ -553,8 +628,9 @@ static void glBeginView(Renderer* renderer, int32_t viewX, int32_t viewY, int32_ renderer->gmlMatrices[MATRIX_WORLD_VIEW_PROJECTION] = projection; glShaderSettingsRefresh(renderer); glActiveTexture(GL_TEXTURE1); - - glBindVertexArray(gl->vao); + if (gl->hasVAOs) { + glBindVertexArray(gl->vao); + } renderer->previousViewMatrix = projection; } @@ -605,8 +681,9 @@ static void glBeginGUI(Renderer* renderer, int32_t guiW, int32_t guiH, int32_t p renderer->gmlMatrices[MATRIX_WORLD_VIEW_PROJECTION] = projection; glShaderSettingsRefresh(renderer); glActiveTexture(GL_TEXTURE1); - +#ifndef ENABLE_GLES2 glBindVertexArray(gl->vao); +#endif } static void glEndGUI(Renderer* renderer) { @@ -617,8 +694,9 @@ static void glEndGUI(Renderer* renderer) { static void glEndFrameInit(Renderer* renderer) { GLRenderer* gl = (GLRenderer*) renderer; +#ifndef ENABLE_GLES2 glBindVertexArray(0); - +#endif if (renderer->runner->usingAppSurface && !renderer->runner->appSurfaceAutoDraw) { glBindFramebuffer(GL_FRAMEBUFFER, gl->hostFramebuffer); return; @@ -1832,7 +1910,7 @@ static void glGpuSetAlphaTestRef(Renderer* renderer, uint8_t ref) { if (gl->alphaTestRef == refF) return; flushBatch(gl); gl->alphaTestRef = refF; - glShaderSettingsRefresh(renderer); + glShaderSettingsRefresh(renderer); } static void glGpuSetColorWriteEnable(Renderer* renderer, bool red, bool green, bool blue, bool alpha) { @@ -1867,7 +1945,7 @@ static int32_t glShaderGetUniform(Renderer* renderer, int32_t shaderIndex, char* flushBatch(gl); GLuint Shader = gl->gmlShaders[shaderIndex]; - return glGetUniformLocation(Shader, uniform); + return glGetUniformLocation(Shader, uniform); } static int32_t glShaderGetSamplerIndex(Renderer* renderer, int32_t shaderIndex, char* uniform) { diff --git a/src/gl/gl_renderer.h b/src/gl/gl_renderer.h index 9f5ec30e..061a677d 100644 --- a/src/gl/gl_renderer.h +++ b/src/gl/gl_renderer.h @@ -68,6 +68,9 @@ typedef struct { int32_t* surfaceWidth; int32_t* surfaceHeight; uint32_t surfaceCount; + + int32_t* gmBaseTextureIndex; + bool hasVAOs; // does the GL implementation support VAOs? } GLRenderer; bool GLRenderer_ensureTextureLoaded(GLRenderer* gl, uint32_t pageId);