From 3b984d2b510b491a315f455184265b6c6c896b17 Mon Sep 17 00:00:00 2001 From: Zack Middleton Date: Wed, 5 Jun 2024 21:33:08 -0500 Subject: [PATCH] OpenGL2: Add OpenGL ES 2.0+ support This mainly targets OpenGL ES 2.0 but it also supports compiling GLSL as ESSL 3.00. It's missing support for framebuffer objects which should be possible on ES 2. (Though using renderbuffers instead of textures.) opengl1 cvars that are not supported will display a message and disable the cvar. This has not been reviewed for new opengl2 cvars. Enabling cvars may cause rendering issues. Some of the broken cvars may be possible to support using OpenGL ES 3 features. The game displays okay with the default cvars. --- README.md | 15 +++ code/client/cl_avi.c | 5 +- code/renderergl2/tr_backend.c | 29 +++- code/renderergl2/tr_bsp.c | 2 +- code/renderergl2/tr_cmds.c | 15 ++- code/renderergl2/tr_extensions.c | 108 +++++++++++++-- code/renderergl2/tr_flares.c | 8 ++ code/renderergl2/tr_glsl.c | 211 +++++++++++++++++++----------- code/renderergl2/tr_image.c | 188 ++++++++++++++++++++++++-- code/renderergl2/tr_init.c | 85 +++++++++--- code/renderergl2/tr_local.h | 9 ++ code/renderergl2/tr_postprocess.c | 2 + code/sdl/sdl_glimp.c | 140 ++++++++++++++------ opengl2-readme.md | 8 ++ 14 files changed, 658 insertions(+), 167 deletions(-) diff --git a/README.md b/README.md index ce2999cb..f2031c73 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,21 @@ Makefile.local: The defaults for these variables differ depending on the target platform. +# OpenGL ES support + +The opengl2 renderer (the default) supports OpenGL ES 2+. Though there +are many missing features and the performance may not be sufficient for +embedded System-on-a-Chip and mobile platforms. + +The opengl1 renderer does not have OpenGL ES support. + +The `r_useOpenGLES` cvar controls whether to use OpenGL or OpenGL ES API. +Set to -1 for auto (default), 0 for OpenGL, and 1 for OpenGL ES. It should be +set using command line arguments: + + ioquake3 +set cl_renderer opengl2 +set r_useOpenGLES 1 + + # Console ## New cvars diff --git a/code/client/cl_avi.c b/code/client/cl_avi.c index 2f0dffd6..00cf7b1d 100644 --- a/code/client/cl_avi.c +++ b/code/client/cl_avi.c @@ -357,11 +357,12 @@ qboolean CL_OpenAVIForWriting( const char *fileName ) else afd.motionJpeg = qfalse; - // Buffers only need to store RGB pixels. + // Capture buffer stores RGB pixels but OpenGL ES reads RGBA and converts to RGB in-place. + // Encode buffer only needs to store RGB pixels. // Allocate a bit more space for the capture buffer to account for possible // padding at the end of pixel lines, and padding for alignment #define MAX_PACK_LEN 16 - afd.cBuffer = Z_Malloc((afd.width * 3 + MAX_PACK_LEN - 1) * afd.height + MAX_PACK_LEN - 1); + afd.cBuffer = Z_Malloc((afd.width * 4 + MAX_PACK_LEN - 1) * afd.height + MAX_PACK_LEN - 1); // raw avi files have pixel lines start on 4-byte boundaries afd.eBuffer = Z_Malloc(PAD(afd.width * 3, AVI_LINE_PADDING) * afd.height); diff --git a/code/renderergl2/tr_backend.c b/code/renderergl2/tr_backend.c index 0cffa4ee..71548779 100644 --- a/code/renderergl2/tr_backend.c +++ b/code/renderergl2/tr_backend.c @@ -732,6 +732,7 @@ void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte * } void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) { + byte *buffer; GLuint texture; if (!tr.scratchImage[client]) @@ -746,7 +747,18 @@ void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) { tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols; tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows; - qglTextureImage2DEXT(texture, GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + + if ( qglesMajorVersion >= 1 ) { + buffer = ri.Hunk_AllocateTempMemory( 3 * cols * rows ); + + R_ConvertTextureFormat( data, cols, rows, GL_RGB, GL_UNSIGNED_BYTE, buffer ); + qglTextureImage2DEXT(texture, GL_TEXTURE_2D, 0, GL_RGB, cols, rows, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer); + + ri.Hunk_FreeTempMemory( buffer ); + } else { + qglTextureImage2DEXT(texture, GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + } + qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); @@ -755,7 +767,16 @@ void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int if (dirty) { // otherwise, just subimage upload it so that drivers can tell we are going to be changing // it and don't try and do a texture compression - qglTextureSubImage2DEXT(texture, GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data); + if ( qglesMajorVersion >= 1 ) { + buffer = ri.Hunk_AllocateTempMemory( 3 * cols * rows ); + + R_ConvertTextureFormat( data, cols, rows, GL_RGB, GL_UNSIGNED_BYTE, buffer ); + qglTextureSubImage2DEXT(texture, GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGB, GL_UNSIGNED_BYTE, buffer); + + ri.Hunk_FreeTempMemory( buffer ); + } else { + qglTextureSubImage2DEXT(texture, GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data); + } } } } @@ -1140,14 +1161,14 @@ const void *RB_DrawSurfs( const void *data ) { if (glRefConfig.occlusionQuery) { tr.sunFlareQueryActive[tr.sunFlareQueryIndex] = qtrue; - qglBeginQuery(GL_SAMPLES_PASSED, tr.sunFlareQuery[tr.sunFlareQueryIndex]); + qglBeginQuery(glRefConfig.occlusionQueryTarget, tr.sunFlareQuery[tr.sunFlareQueryIndex]); } RB_DrawSun(0.3, tr.sunFlareShader); if (glRefConfig.occlusionQuery) { - qglEndQuery(GL_SAMPLES_PASSED); + qglEndQuery(glRefConfig.occlusionQueryTarget); } FBO_Bind(oldFbo); diff --git a/code/renderergl2/tr_bsp.c b/code/renderergl2/tr_bsp.c index 15f5f3cd..57a4efc3 100644 --- a/code/renderergl2/tr_bsp.c +++ b/code/renderergl2/tr_bsp.c @@ -276,7 +276,7 @@ static void R_LoadLightmaps( lump_t *l, lump_t *surfs ) { tr.deluxemaps = ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low ); textureInternalFormat = GL_RGBA8; - if (r_hdr->integer) + if (r_hdr->integer && !qglesMajorVersion) { // Check for the first hdr lightmap, if it exists, use GL_RGBA16 for textures. char filename[MAX_QPATH]; diff --git a/code/renderergl2/tr_cmds.c b/code/renderergl2/tr_cmds.c index 38ba45d2..2025c41d 100644 --- a/code/renderergl2/tr_cmds.c +++ b/code/renderergl2/tr_cmds.c @@ -348,7 +348,13 @@ void RE_BeginFrame( stereoFrame_t stereoFrame ) { // if ( r_measureOverdraw->integer ) { - if ( glConfig.stencilBits < 4 ) + if ( qglesMajorVersion >= 1 && !glRefConfig.readStencil ) + { + ri.Printf( PRINT_WARNING, "OpenGL ES needs GL_NV_read_stencil to read stencil bits to measure overdraw\n" ); + ri.Cvar_Set( "r_measureOverdraw", "0" ); + r_measureOverdraw->modified = qfalse; + } + else if ( glConfig.stencilBits < 4 ) { ri.Printf( PRINT_ALL, "Warning: not enough stencil bits to measure overdraw: %d\n", glConfig.stencilBits ); ri.Cvar_Set( "r_measureOverdraw", "0" ); @@ -426,6 +432,13 @@ void RE_BeginFrame( stereoFrame_t stereoFrame ) { } else { + if (qglesMajorVersion >= 1 && r_anaglyphMode->integer) + { + ri.Printf( PRINT_WARNING, "OpenGL ES does not support drawing to separate buffer for anaglyph mode\n" ); + ri.Cvar_Set( "r_anaglyphMode", "0" ); + r_anaglyphMode->modified = qfalse; + } + if(r_anaglyphMode->integer) { if(r_anaglyphMode->modified) diff --git a/code/renderergl2/tr_extensions.c b/code/renderergl2/tr_extensions.c index ebc985a9..f0ea7d0a 100644 --- a/code/renderergl2/tr_extensions.c +++ b/code/renderergl2/tr_extensions.c @@ -53,8 +53,83 @@ void GLimp_InitExtraExtensions(void) // GL function loader, based on https://gist.github.com/rygorous/16796a0c876cf8a5f542caddb55bce8a #define GLE(ret, name, ...) qgl##name = (name##proc *) SDL_GL_GetProcAddress("gl" #name); + // + // OpenGL ES extensions + // + if (qglesMajorVersion) + { + if (!r_allowExtensions->integer) + goto done; + + extension = "GL_EXT_occlusion_query_boolean"; + if (SDL_GL_ExtensionSupported(extension)) + { + glRefConfig.occlusionQuery = qtrue; + glRefConfig.occlusionQueryTarget = GL_ANY_SAMPLES_PASSED; + + QGL_ARB_occlusion_query_PROCS; + + ri.Printf(PRINT_ALL, result[glRefConfig.occlusionQuery], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_NV_read_depth + extension = "GL_NV_read_depth"; + if (SDL_GL_ExtensionSupported(extension)) + { + glRefConfig.readDepth = qtrue; + ri.Printf(PRINT_ALL, result[glRefConfig.readDepth], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_NV_read_stencil + extension = "GL_NV_read_stencil"; + if (SDL_GL_ExtensionSupported(extension)) + { + glRefConfig.readStencil = qtrue; + ri.Printf(PRINT_ALL, result[glRefConfig.readStencil], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_EXT_shadow_samplers + extension = "GL_EXT_shadow_samplers"; + if (qglesMajorVersion >= 3 || SDL_GL_ExtensionSupported(extension)) + { + glRefConfig.shadowSamplers = qtrue; + ri.Printf(PRINT_ALL, result[glRefConfig.shadowSamplers], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_OES_standard_derivatives + extension = "GL_OES_standard_derivatives"; + if (qglesMajorVersion >= 3 || SDL_GL_ExtensionSupported(extension)) + { + glRefConfig.standardDerivatives = qtrue; + ri.Printf(PRINT_ALL, result[glRefConfig.standardDerivatives], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + goto done; + } + // OpenGL 1.5 - GL_ARB_occlusion_query glRefConfig.occlusionQuery = qtrue; + glRefConfig.occlusionQueryTarget = GL_SAMPLES_PASSED; QGL_ARB_occlusion_query_PROCS; // OpenGL 3.0 - GL_ARB_framebuffer_object @@ -146,18 +221,6 @@ void GLimp_InitExtraExtensions(void) ri.Printf(PRINT_ALL, result[2], extension); } - // Determine GLSL version - if (1) - { - char version[256]; - - Q_strncpyz(version, (char *)qglGetString(GL_SHADING_LANGUAGE_VERSION), sizeof(version)); - - sscanf(version, "%d.%d", &glRefConfig.glslMajorVersion, &glRefConfig.glslMinorVersion); - - ri.Printf(PRINT_ALL, "...using GLSL version %s\n", version); - } - glRefConfig.memInfo = MI_NONE; // GL_NVX_gpu_memory_info @@ -249,5 +312,26 @@ void GLimp_InitExtraExtensions(void) ri.Printf(PRINT_ALL, result[2], extension); } +done: + + // Determine GLSL version + if (1) + { + char version[256], *version_p; + + Q_strncpyz(version, (char *)qglGetString(GL_SHADING_LANGUAGE_VERSION), sizeof(version)); + + // Skip leading text such as "OpenGL ES GLSL ES " + version_p = version; + while ( *version_p && !isdigit( *version_p ) ) + { + version_p++; + } + + sscanf(version_p, "%d.%d", &glRefConfig.glslMajorVersion, &glRefConfig.glslMinorVersion); + + ri.Printf(PRINT_ALL, "...using GLSL version %s\n", version); + } + #undef GLE } diff --git a/code/renderergl2/tr_flares.c b/code/renderergl2/tr_flares.c index fc83df7b..edf76822 100644 --- a/code/renderergl2/tr_flares.c +++ b/code/renderergl2/tr_flares.c @@ -478,6 +478,14 @@ void RB_RenderFlares (void) { return; } + if ( r_flares->modified ) { + if ( qglesMajorVersion >= 1 && !glRefConfig.readDepth ) { + ri.Printf( PRINT_WARNING, "OpenGL ES needs GL_NV_read_depth to read depth to determine if flares are visible\n" ); + ri.Cvar_Set( "r_flares", "0" ); + } + r_flares->modified = qfalse; + } + if(r_flareCoeff->modified) { R_SetFlareCoeff(); diff --git a/code/renderergl2/tr_glsl.c b/code/renderergl2/tr_glsl.c index 2c834f10..bf93a2f4 100644 --- a/code/renderergl2/tr_glsl.c +++ b/code/renderergl2/tr_glsl.c @@ -249,11 +249,25 @@ static void GLSL_GetShaderHeader( GLenum shaderType, const GLchar *extra, char * // HACK: abuse the GLSL preprocessor to turn GLSL 1.20 shaders into 1.30 ones if(glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 30)) { - if (glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 50)) + if (qglesMajorVersion >= 3 && glRefConfig.glslMajorVersion >= 3) + Q_strcat(dest, size, "#version 300 es\n"); + else if (glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 50)) Q_strcat(dest, size, "#version 150\n"); else Q_strcat(dest, size, "#version 130\n"); + // `extra' may contain #extension which must be directly after #version + if (extra) + { + Q_strcat(dest, size, extra); + } + + if (qglesMajorVersion >= 2) + { + Q_strcat(dest, size, "precision mediump float;\n"); + Q_strcat(dest, size, "precision mediump sampler2DShadow;\n"); + } + if(shaderType == GL_VERTEX_SHADER) { Q_strcat(dest, size, "#define attribute in\n"); @@ -272,8 +286,34 @@ static void GLSL_GetShaderHeader( GLenum shaderType, const GLchar *extra, char * } else { - Q_strcat(dest, size, "#version 120\n"); - Q_strcat(dest, size, "#define shadow2D(a,b) shadow2D(a,b).r \n"); + if (qglesMajorVersion >= 2) + { + Q_strcat(dest, size, "#version 100\n"); + + if (extra) + { + Q_strcat(dest, size, extra); + } + + Q_strcat(dest, size, "precision mediump float;\n"); + + if (glRefConfig.shadowSamplers) + { + Q_strcat(dest, size, "precision mediump sampler2DShadow;\n"); + Q_strcat(dest, size, "#define shadow2D(a,b) shadow2DEXT(a,b)\n"); + } + } + else + { + Q_strcat(dest, size, "#version 120\n"); + + if (extra) + { + Q_strcat(dest, size, extra); + } + + Q_strcat(dest, size, "#define shadow2D(a,b) shadow2D(a,b).r\n"); + } } // HACK: add some macros to avoid extra uniforms and save speed and code maintenance @@ -361,11 +401,6 @@ static void GLSL_GetShaderHeader( GLenum shaderType, const GLchar *extra, char * Q_strcat(dest, size, va("#define ROUGHNESS_MIPS float(%d)\n", numRoughnessMips)); } - if (extra) - { - Q_strcat(dest, size, extra); - } - // OK we added a lot of stuff but if we do something bad in the GLSL shaders then we want the proper line // so we have to reset the line counting Q_strcat(dest, size, "#line 0\n"); @@ -1350,86 +1385,110 @@ void GLSL_InitGPUShaders(void) } - attribs = ATTR_POSITION | ATTR_TEXCOORD; - extradefines[0] = '\0'; - - if (r_shadowFilter->integer >= 1) - Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER\n"); - - if (r_shadowFilter->integer >= 2) - Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER2\n"); - - if (r_shadowCascadeZFar->integer != 0) - Q_strcat(extradefines, 1024, "#define USE_SHADOW_CASCADE\n"); - - Q_strcat(extradefines, 1024, va("#define r_shadowMapSize %f\n", r_shadowMapSize->value)); - Q_strcat(extradefines, 1024, va("#define r_shadowCascadeZFar %f\n", r_shadowCascadeZFar->value)); - - - if (!GLSL_InitGPUShader(&tr.shadowmaskShader, "shadowmask", attribs, qtrue, extradefines, qtrue, fallbackShader_shadowmask_vp, fallbackShader_shadowmask_fp)) - { - ri.Error(ERR_FATAL, "Could not load shadowmask shader!"); - } - - GLSL_InitUniforms(&tr.shadowmaskShader); - - GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP); - GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP, TB_SHADOWMAP); - GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP2, TB_SHADOWMAP2); - GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP3, TB_SHADOWMAP3); - GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP4, TB_SHADOWMAP4); - - GLSL_FinishGPUShader(&tr.shadowmaskShader); - - numEtcShaders++; - - - attribs = ATTR_POSITION | ATTR_TEXCOORD; - extradefines[0] = '\0'; - - if (!GLSL_InitGPUShader(&tr.ssaoShader, "ssao", attribs, qtrue, extradefines, qtrue, fallbackShader_ssao_vp, fallbackShader_ssao_fp)) - { - ri.Error(ERR_FATAL, "Could not load ssao shader!"); - } - - GLSL_InitUniforms(&tr.ssaoShader); - - GLSL_SetUniformInt(&tr.ssaoShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP); - - GLSL_FinishGPUShader(&tr.ssaoShader); - - numEtcShaders++; - - - for (i = 0; i < 4; i++) + // GLSL 1.10+ or GL_EXT_shadow_samplers extension are required for sampler2DShadow type + if (glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 10) + || glRefConfig.shadowSamplers) { attribs = ATTR_POSITION | ATTR_TEXCOORD; extradefines[0] = '\0'; - if (i & 1) - Q_strcat(extradefines, 1024, "#define USE_VERTICAL_BLUR\n"); - else - Q_strcat(extradefines, 1024, "#define USE_HORIZONTAL_BLUR\n"); - - if (!(i & 2)) - Q_strcat(extradefines, 1024, "#define USE_DEPTH\n"); - - - if (!GLSL_InitGPUShader(&tr.depthBlurShader[i], "depthBlur", attribs, qtrue, extradefines, qtrue, fallbackShader_depthblur_vp, fallbackShader_depthblur_fp)) + if (qglesMajorVersion < 3 && glRefConfig.shadowSamplers) { - ri.Error(ERR_FATAL, "Could not load depthBlur shader!"); + Q_strcat(extradefines, 1024, "#extension GL_EXT_shadow_samplers : enable\n"); } - - GLSL_InitUniforms(&tr.depthBlurShader[i]); - GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENIMAGEMAP, TB_COLORMAP); - GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENDEPTHMAP, TB_LIGHTMAP); + if (r_shadowFilter->integer >= 1) + Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER\n"); - GLSL_FinishGPUShader(&tr.depthBlurShader[i]); + if (r_shadowFilter->integer >= 2) + Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER2\n"); + + if (r_shadowCascadeZFar->integer != 0) + Q_strcat(extradefines, 1024, "#define USE_SHADOW_CASCADE\n"); + + Q_strcat(extradefines, 1024, va("#define r_shadowMapSize %f\n", r_shadowMapSize->value)); + Q_strcat(extradefines, 1024, va("#define r_shadowCascadeZFar %f\n", r_shadowCascadeZFar->value)); + + if (!GLSL_InitGPUShader(&tr.shadowmaskShader, "shadowmask", attribs, qtrue, extradefines, qtrue, fallbackShader_shadowmask_vp, fallbackShader_shadowmask_fp)) + { + ri.Error(ERR_FATAL, "Could not load shadowmask shader!"); + } + + GLSL_InitUniforms(&tr.shadowmaskShader); + + GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP); + GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP, TB_SHADOWMAP); + GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP2, TB_SHADOWMAP2); + GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP3, TB_SHADOWMAP3); + GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP4, TB_SHADOWMAP4); + + GLSL_FinishGPUShader(&tr.shadowmaskShader); numEtcShaders++; } + + // GLSL 1.10+ or GL_OES_standard_derivatives extension are required for dFdx() and dFdy() GLSL functions + if (glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 10) + || glRefConfig.standardDerivatives) + { + attribs = ATTR_POSITION | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (qglesMajorVersion < 3 && glRefConfig.standardDerivatives) + { + Q_strcat(extradefines, 1024, "#extension GL_OES_standard_derivatives : enable\n"); + } + + if (!GLSL_InitGPUShader(&tr.ssaoShader, "ssao", attribs, qtrue, extradefines, qtrue, fallbackShader_ssao_vp, fallbackShader_ssao_fp)) + { + ri.Error(ERR_FATAL, "Could not load ssao shader!"); + } + + GLSL_InitUniforms(&tr.ssaoShader); + + GLSL_SetUniformInt(&tr.ssaoShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP); + + GLSL_FinishGPUShader(&tr.ssaoShader); + + numEtcShaders++; + + + for (i = 0; i < 4; i++) + { + attribs = ATTR_POSITION | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (qglesMajorVersion < 3 && glRefConfig.standardDerivatives) + { + Q_strcat(extradefines, 1024, "#extension GL_OES_standard_derivatives : enable\n"); + } + + if (i & 1) + Q_strcat(extradefines, 1024, "#define USE_VERTICAL_BLUR\n"); + else + Q_strcat(extradefines, 1024, "#define USE_HORIZONTAL_BLUR\n"); + + if (!(i & 2)) + Q_strcat(extradefines, 1024, "#define USE_DEPTH\n"); + + + if (!GLSL_InitGPUShader(&tr.depthBlurShader[i], "depthBlur", attribs, qtrue, extradefines, qtrue, fallbackShader_depthblur_vp, fallbackShader_depthblur_fp)) + { + ri.Error(ERR_FATAL, "Could not load depthBlur shader!"); + } + + GLSL_InitUniforms(&tr.depthBlurShader[i]); + + GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENIMAGEMAP, TB_COLORMAP); + GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENDEPTHMAP, TB_LIGHTMAP); + + GLSL_FinishGPUShader(&tr.depthBlurShader[i]); + + numEtcShaders++; + } + } + #if 0 attribs = ATTR_POSITION | ATTR_TEXCOORD; extradefines[0] = '\0'; diff --git a/code/renderergl2/tr_image.c b/code/renderergl2/tr_image.c index 101513b6..0e829d51 100644 --- a/code/renderergl2/tr_image.c +++ b/code/renderergl2/tr_image.c @@ -1455,6 +1455,106 @@ byte mipBlendColors[16][4] = { {0,0,255,128}, }; +/* +================== +R_ConvertTextureFormat + +Convert RGBA unsigned byte to specified format and type +================== +*/ +#define ROW_PADDING( width, bpp, alignment ) PAD( (width) * (bpp), (alignment) ) - (width) * (bpp) +void R_ConvertTextureFormat( const byte *in, int width, int height, GLenum format, GLenum type, byte *out ) +{ + int x, y, rowPadding; + int unpackAlign = 4; // matches GL_UNPACK_ALIGNMENT default + + if ( format == GL_RGB && type == GL_UNSIGNED_BYTE ) + { + rowPadding = ROW_PADDING( width, 3, unpackAlign ); + + for ( y = 0; y < height; y++ ) + { + for ( x = 0; x < width; x++ ) + { + *out++ = *in++; + *out++ = *in++; + *out++ = *in++; + in++; + } + + out += rowPadding; + } + } + else if ( format == GL_LUMINANCE && type == GL_UNSIGNED_BYTE ) + { + rowPadding = ROW_PADDING( width, 1, unpackAlign ); + + for ( y = 0; y < height; y++ ) + { + for ( x = 0; x < width; x++ ) + { + *out++ = *in++; // red + in += 3; + } + + out += rowPadding; + } + } + else if ( format == GL_LUMINANCE_ALPHA && type == GL_UNSIGNED_BYTE ) + { + rowPadding = ROW_PADDING( width, 2, unpackAlign ); + + for ( y = 0; y < height; y++ ) + { + for ( x = 0; x < width; x++ ) + { + *out++ = *in++; // red + in += 2; + *out++ = *in++; // alpha + } + + out += rowPadding; + } + } + else if ( format == GL_RGB && type == GL_UNSIGNED_SHORT_5_6_5 ) + { + rowPadding = ROW_PADDING( width, 2, unpackAlign ); + + for ( y = 0; y < height; y++ ) + { + for ( x = 0; x < width; x++, in += 4, out += 2 ) + { + *((unsigned short*)out) = ( (unsigned short)( in[0] >> 3 ) << 11 ) + | ( (unsigned short)( in[1] >> 2 ) << 5 ) + | ( (unsigned short)( in[2] >> 3 ) << 0 ); + } + + out += rowPadding; + } + } + else if ( format == GL_RGBA && type == GL_UNSIGNED_SHORT_4_4_4_4 ) + { + rowPadding = ROW_PADDING( width, 2, unpackAlign ); + + for ( y = 0; y < height; y++ ) + { + for ( x = 0; x < width; x++, in += 4, out += 2 ) + { + *((unsigned short*)out) = ( (unsigned short)( in[0] >> 4 ) << 12 ) + | ( (unsigned short)( in[1] >> 4 ) << 8 ) + | ( (unsigned short)( in[2] >> 4 ) << 4 ) + | ( (unsigned short)( in[3] >> 4 ) << 0 ); + } + + out += rowPadding; + } + } + else + { + ri.Error( ERR_DROP, "Unable to convert RGBA image to OpenGL format 0x%X and type 0x%X", format, type ); + } +} + static void RawImage_SwizzleRA( byte *data, int width, int height ) { int i; @@ -1944,18 +2044,20 @@ static GLenum PixelDataFormatFromInternalFormat(GLenum internalFormat) } } -static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y, int width, int height, GLenum target, GLenum picFormat, int numMips, GLenum internalFormat, imgType_t type, imgFlags_t flags, qboolean subtexture ) +static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y, int width, int height, GLenum target, GLenum picFormat, GLenum dataFormat, GLenum dataType, int numMips, GLenum internalFormat, imgType_t type, imgFlags_t flags, qboolean subtexture ) { - GLenum dataFormat, dataType; qboolean rgtc = internalFormat == GL_COMPRESSED_RG_RGTC2; qboolean rgba8 = picFormat == GL_RGBA8 || picFormat == GL_SRGB8_ALPHA8_EXT; qboolean rgba = rgba8 || picFormat == GL_RGBA16; qboolean mipmap = !!(flags & IMGFLAG_MIPMAP); int size, miplevel; qboolean lastMip = qfalse; + byte *formatBuffer = NULL; - dataFormat = PixelDataFormatFromInternalFormat(internalFormat); - dataType = picFormat == GL_RGBA16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE; + if (qglesMajorVersion && rgba8 && (dataFormat != GL_RGBA || dataType != GL_UNSIGNED_BYTE)) + { + formatBuffer = ri.Hunk_AllocateTempMemory(4 * width * height); + } miplevel = 0; do @@ -1974,6 +2076,11 @@ static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y, int if (rgba8 && rgtc) RawImage_UploadToRgtc2Texture(texture, miplevel, x, y, width, height, data); + else if (formatBuffer) + { + R_ConvertTextureFormat(data, width, height, dataFormat, dataType, formatBuffer); + qglTextureSubImage2DEXT(texture, target, miplevel, x, y, width, height, dataFormat, dataType, formatBuffer); + } else qglTextureSubImage2DEXT(texture, target, miplevel, x, y, width, height, dataFormat, dataType, data); } @@ -2007,6 +2114,9 @@ static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y, int } } while (!lastMip); + + if (formatBuffer != NULL) + ri.Hunk_FreeTempMemory(formatBuffer); } @@ -2016,7 +2126,7 @@ Upload32 =============== */ -static void Upload32(byte *data, int x, int y, int width, int height, GLenum picFormat, int numMips, image_t *image, qboolean scaled) +static void Upload32(byte *data, int x, int y, int width, int height, GLenum picFormat, GLenum dataFormat, GLenum dataType, int numMips, image_t *image, qboolean scaled) { int i, c; byte *scan; @@ -2071,7 +2181,7 @@ static void Upload32(byte *data, int x, int y, int width, int height, GLenum pic for (i = 0; i < 6; i++) { int w2 = width, h2 = height; - RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, picFormat, numMips, internalFormat, type, flags, qfalse); + RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, picFormat, dataFormat, dataType, numMips, internalFormat, type, flags, qfalse); for (c = numMips; c; c--) { data += CalculateMipSize(w2, h2, picFormat); @@ -2082,7 +2192,7 @@ static void Upload32(byte *data, int x, int y, int width, int height, GLenum pic } else { - RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_2D, picFormat, numMips, internalFormat, type, flags, qfalse); + RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_2D, picFormat, dataFormat, dataType, numMips, internalFormat, type, flags, qfalse); } GL_CheckErrors(); @@ -2108,7 +2218,7 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe qboolean picmip = !!(flags & IMGFLAG_PICMIP); qboolean lastMip; GLenum textureTarget = cubemap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D; - GLenum dataFormat; + GLenum dataFormat, dataType; if (strlen(name) >= MAX_QPATH ) { ri.Error (ERR_DROP, "R_CreateImage: \"%s\" is too long", name); @@ -2140,6 +2250,53 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe if (!internalFormat) internalFormat = RawImage_GetFormat(pic, width * height, picFormat, isLightmap, image->type, image->flags); + dataFormat = PixelDataFormatFromInternalFormat(internalFormat); + dataType = picFormat == GL_RGBA16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE; + + // Convert image data format for OpenGL ES, data is converted for each mip level + if (qglesMajorVersion) + { + switch (internalFormat) + { + case GL_LUMINANCE: + case GL_LUMINANCE8: + internalFormat = GL_LUMINANCE; + dataFormat = GL_LUMINANCE; + dataType = GL_UNSIGNED_BYTE; + break; + case GL_LUMINANCE_ALPHA: + case GL_LUMINANCE8_ALPHA8: + internalFormat = GL_LUMINANCE_ALPHA; + dataFormat = GL_LUMINANCE_ALPHA; + dataType = GL_UNSIGNED_BYTE; + break; + case GL_RGB: + case GL_RGB8: + internalFormat = GL_RGB; + dataFormat = GL_RGB; + dataType = GL_UNSIGNED_BYTE; + break; + case GL_RGB5: + internalFormat = GL_RGB; + dataFormat = GL_RGB; + dataType = GL_UNSIGNED_SHORT_5_6_5; + break; + case GL_RGBA: + case GL_RGBA8: + internalFormat = GL_RGBA; + dataFormat = GL_RGBA; + dataType = GL_UNSIGNED_BYTE; + break; + case GL_RGBA4: + internalFormat = GL_RGBA; + dataFormat = GL_RGBA; + dataType = GL_UNSIGNED_SHORT_4_4_4_4; + break; + default: + ri.Error( ERR_DROP, "Missing OpenGL ES support for image '%s' with internal format 0x%X\n", name, internalFormat ); + } + } + image->internalFormat = internalFormat; // Possibly scale image before uploading. @@ -2164,7 +2321,6 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe image->uploadHeight = height; // Allocate texture storage so we don't have to worry about it later. - dataFormat = PixelDataFormatFromInternalFormat(internalFormat); mipWidth = width; mipHeight = height; miplevel = 0; @@ -2176,11 +2332,11 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe int i; for (i = 0; i < 6; i++) - qglTextureImage2DEXT(image->texnum, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, GL_UNSIGNED_BYTE, NULL); + qglTextureImage2DEXT(image->texnum, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, dataType, NULL); } else { - qglTextureImage2DEXT(image->texnum, GL_TEXTURE_2D, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, GL_UNSIGNED_BYTE, NULL); + qglTextureImage2DEXT(image->texnum, GL_TEXTURE_2D, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, dataType, NULL); } mipWidth = MAX(1, mipWidth >> 1); @@ -2191,7 +2347,7 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe // Upload data. if (pic) - Upload32(pic, 0, 0, width, height, picFormat, numMips, image, scaled); + Upload32(pic, 0, 0, width, height, picFormat, dataFormat, dataType, numMips, image, scaled); if (resampledBuffer != NULL) ri.Hunk_FreeTempMemory(resampledBuffer); @@ -2252,7 +2408,13 @@ image_t *R_CreateImage(const char *name, byte *pic, int width, int height, imgTy void R_UpdateSubImage( image_t *image, byte *pic, int x, int y, int width, int height, GLenum picFormat ) { - Upload32(pic, x, y, width, height, picFormat, 0, image, qfalse); + GLenum dataFormat, dataType; + + // TODO: This is fine for lightmaps but (unused) general RGBA images need to store dataFormat / dataType in image_t for OpenGL ES? + dataFormat = PixelDataFormatFromInternalFormat(image->internalFormat); + dataType = picFormat == GL_RGBA16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE; + + Upload32(pic, x, y, width, height, picFormat, dataFormat, dataType, 0, image, qfalse); } //=================================================================== diff --git a/code/renderergl2/tr_init.c b/code/renderergl2/tr_init.c index 0edfe6be..12570392 100644 --- a/code/renderergl2/tr_init.c +++ b/code/renderergl2/tr_init.c @@ -451,21 +451,43 @@ Return value must be freed with ri.Hunk_FreeTempMemory() byte *RB_ReadPixels(int x, int y, int width, int height, size_t *offset, int *padlen) { byte *buffer, *bufstart; - int padwidth, linelen; - GLint packAlign; - + int padwidth, linelen, bytesPerPixel; + int yin, xin, xout; + GLint packAlign, format; + + // OpenGL ES is only required to support reading GL_RGBA + if (qglesMajorVersion >= 1) { + format = GL_RGBA; + bytesPerPixel = 4; + } else { + format = GL_RGB; + bytesPerPixel = 3; + } + qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign); - - linelen = width * 3; + + linelen = width * bytesPerPixel; padwidth = PAD(linelen, packAlign); - + // Allocate a few more bytes so that we can choose an alignment we like buffer = ri.Hunk_AllocateTempMemory(padwidth * height + *offset + packAlign - 1); - - bufstart = PADP((intptr_t) buffer + *offset, packAlign); - qglReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, bufstart); - + bufstart = PADP((intptr_t) buffer + *offset, packAlign); + qglReadPixels(x, y, width, height, format, GL_UNSIGNED_BYTE, bufstart); + + linelen = width * 3; + + // Convert RGBA to RGB, in place, line by line + if (format == GL_RGBA) { + for (yin = 0; yin < height; yin++) { + for (xin = 0, xout = 0; xout < linelen; xin += 4, xout += 3) { + bufstart[yin*padwidth + xout + 0] = bufstart[yin*padwidth + xin + 0]; + bufstart[yin*padwidth + xout + 1] = bufstart[yin*padwidth + xin + 1]; + bufstart[yin*padwidth + xout + 2] = bufstart[yin*padwidth + xin + 2]; + } + } + } + *offset = bufstart - buffer; *padlen = padwidth - linelen; @@ -877,9 +899,10 @@ const void *RB_TakeVideoFrameCmd( const void *data ) { const videoFrameCommand_t *cmd; byte *cBuf; - size_t memcount, linelen; + size_t memcount, bytesPerPixel, linelen, avilinelen; int padwidth, avipadwidth, padlen, avipadlen; - GLint packAlign; + int yin, xin, xout; + GLint packAlign, format; // finish any 2D drawing if needed if(tess.numIndexes) @@ -887,20 +910,32 @@ const void *RB_TakeVideoFrameCmd( const void *data ) cmd = (const videoFrameCommand_t *)data; + // OpenGL ES is only required to support reading GL_RGBA + if (qglesMajorVersion >= 1) { + format = GL_RGBA; + bytesPerPixel = 4; + } else { + format = GL_RGB; + bytesPerPixel = 3; + } + qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign); - linelen = cmd->width * 3; + linelen = cmd->width * bytesPerPixel; // Alignment stuff for glReadPixels padwidth = PAD(linelen, packAlign); padlen = padwidth - linelen; + + avilinelen = cmd->width * 3; + // AVI line padding - avipadwidth = PAD(linelen, AVI_LINE_PADDING); - avipadlen = avipadwidth - linelen; + avipadwidth = PAD(avilinelen, AVI_LINE_PADDING); + avipadlen = avipadwidth - avilinelen; cBuf = PADP(cmd->captureBuffer, packAlign); - qglReadPixels(0, 0, cmd->width, cmd->height, GL_RGB, + qglReadPixels(0, 0, cmd->width, cmd->height, format, GL_UNSIGNED_BYTE, cBuf); memcount = padwidth * cmd->height; @@ -911,7 +946,21 @@ const void *RB_TakeVideoFrameCmd( const void *data ) if(cmd->motionJpeg) { - memcount = RE_SaveJPGToBuffer(cmd->encodeBuffer, linelen * cmd->height, + // Convert RGBA to RGB, in place, line by line + if (format == GL_RGBA) { + linelen = cmd->width * 3; + padlen = padwidth - linelen; + + for (yin = 0; yin < cmd->height; yin++) { + for (xin = 0, xout = 0; xout < linelen; xin += 4, xout += 3) { + cBuf[yin*padwidth + xout + 0] = cBuf[yin*padwidth + xin + 0]; + cBuf[yin*padwidth + xout + 1] = cBuf[yin*padwidth + xin + 1]; + cBuf[yin*padwidth + xout + 2] = cBuf[yin*padwidth + xin + 2]; + } + } + } + + memcount = RE_SaveJPGToBuffer(cmd->encodeBuffer, avilinelen * cmd->height, r_aviMotionJpegQuality->integer, cmd->width, cmd->height, cBuf, padlen); ri.CL_WriteAVIVideoFrame(cmd->encodeBuffer, memcount); @@ -934,7 +983,7 @@ const void *RB_TakeVideoFrameCmd( const void *data ) *destptr++ = srcptr[2]; *destptr++ = srcptr[1]; *destptr++ = srcptr[0]; - srcptr += 3; + srcptr += bytesPerPixel; } Com_Memset(destptr, '\0', avipadlen); diff --git a/code/renderergl2/tr_local.h b/code/renderergl2/tr_local.h index 6a0aa36f..bdc6e477 100644 --- a/code/renderergl2/tr_local.h +++ b/code/renderergl2/tr_local.h @@ -1406,6 +1406,7 @@ typedef struct { qboolean intelGraphics; qboolean occlusionQuery; + GLenum occlusionQueryTarget; int glslMajorVersion; int glslMinorVersion; @@ -1429,6 +1430,12 @@ typedef struct { qboolean vertexArrayObject; qboolean directStateAccess; + + // OpenGL ES extensions + qboolean readDepth; + qboolean readStencil; + qboolean shadowSamplers; + qboolean standardDerivatives; } glRefConfig_t; @@ -2502,5 +2509,7 @@ size_t RE_SaveJPGToBuffer(byte *buffer, size_t bufSize, int quality, void RE_TakeVideoFrame( int width, int height, byte *captureBuffer, byte *encodeBuffer, qboolean motionJpeg ); +void R_ConvertTextureFormat( const byte *in, int width, int height, GLenum format, GLenum type, byte *out ); + #endif //TR_LOCAL_H diff --git a/code/renderergl2/tr_postprocess.c b/code/renderergl2/tr_postprocess.c index 3d71f304..ff9a25a1 100644 --- a/code/renderergl2/tr_postprocess.c +++ b/code/renderergl2/tr_postprocess.c @@ -290,6 +290,8 @@ static qboolean RB_UpdateSunFlareVis(void) ri.Printf(PRINT_DEVELOPER, "Waited %d iterations\n", iter); } + // Note: On desktop OpenGL this is a sample count (glRefConfig.occlusionQueryTarget == GL_SAMPLES_PASSED) + // but on OpenGL ES this is a boolean (glRefConfig.occlusionQueryTarget == GL_ANY_SAMPLES_PASSED) qglGetQueryObjectuiv(tr.sunFlareQuery[tr.sunFlareQueryIndex], GL_QUERY_RESULT, &sampleCount); return sampleCount > 0; } diff --git a/code/sdl/sdl_glimp.c b/code/sdl/sdl_glimp.c index 30986ab1..b24759b5 100644 --- a/code/sdl/sdl_glimp.c +++ b/code/sdl/sdl_glimp.c @@ -52,6 +52,7 @@ cvar_t *r_allowSoftwareGL; // Don't abort out if a hardware visual can't be obta cvar_t *r_allowResize; // make window resizable cvar_t *r_centerWindow; cvar_t *r_sdlDriver; +cvar_t *r_useOpenGLES; int qglMajorVersion, qglMinorVersion; int qglesMajorVersion, qglesMinorVersion; @@ -230,6 +231,27 @@ static void GLimp_DetectAvailableModes(void) SDL_free( modes ); } +/* +=============== +OpenGL ES compatibility +=============== +*/ +static void APIENTRY GLimp_GLES_ClearDepth( GLclampd depth ) { + qglClearDepthf( depth ); +} + +static void APIENTRY GLimp_GLES_DepthRange( GLclampd near_val, GLclampd far_val ) { + qglDepthRangef( near_val, far_val ); +} + +static void APIENTRY GLimp_GLES_DrawBuffer( GLenum mode ) { + // unsupported +} + +static void APIENTRY GLimp_GLES_PolygonMode( GLenum face, GLenum mode ) { + // unsupported +} + /* =============== GLimp_GetProcAddresses @@ -306,8 +328,11 @@ static qboolean GLimp_GetProcAddresses( qboolean fixedFunction ) { QGL_1_3_PROCS; QGL_1_5_PROCS; QGL_2_0_PROCS; - // error so this doesn't segfault due to NULL desktop GL functions being used - Com_Error( ERR_FATAL, "Unsupported OpenGL Version: %s", version ); + + qglClearDepth = GLimp_GLES_ClearDepth; + qglDepthRange = GLimp_GLES_DepthRange; + qglDrawBuffer = GLimp_GLES_DrawBuffer; + qglPolygonMode = GLimp_GLES_PolygonMode; } else { Com_Error( ERR_FATAL, "Unsupported OpenGL Version (%s), OpenGL 2.0 is required", version ); } @@ -633,57 +658,91 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool if (!fixedFunction) { - int profileMask, majorVersion, minorVersion; - SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profileMask); - SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &majorVersion); - SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minorVersion); + int profileMask; - ri.Printf(PRINT_ALL, "Trying to get an OpenGL 3.2 core context\n"); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); - if ((SDL_glContext = SDL_GL_CreateContext(SDL_window)) == NULL) + SDL_GL_GetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, &profileMask ); + + if ( r_useOpenGLES->integer == 1 || ( r_useOpenGLES->integer == -1 && profileMask == SDL_GL_CONTEXT_PROFILE_ES ) ) { - ri.Printf(PRINT_ALL, "SDL_GL_CreateContext failed: %s\n", SDL_GetError()); - ri.Printf(PRINT_ALL, "Reverting to default context\n"); + ri.Printf( PRINT_ALL, "Trying to get an OpenGL ES 2.0 context\n" ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 2 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 0 ); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profileMask); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorVersion); - } - else - { - const char *renderer; - - ri.Printf(PRINT_ALL, "SDL_GL_CreateContext succeeded.\n"); - - if ( GLimp_GetProcAddresses( fixedFunction ) ) + SDL_glContext = SDL_GL_CreateContext( SDL_window ); + if ( !SDL_glContext ) { - renderer = (const char *)qglGetString(GL_RENDERER); + ri.Printf( PRINT_ALL, "SDL_GL_CreateContext failed: %s\n", SDL_GetError() ); } else { - ri.Printf( PRINT_ALL, "GLimp_GetProcAddresses() failed for OpenGL 3.2 core context\n" ); - renderer = NULL; - } + ri.Printf( PRINT_ALL, "SDL_GL_CreateContext succeeded.\n" ); - if (!renderer || (strstr(renderer, "Software Renderer") || strstr(renderer, "Software Rasterizer"))) + if ( !GLimp_GetProcAddresses( fixedFunction ) ) + { + ri.Printf( PRINT_ALL, "GLimp_GetProcAddresses() failed for OpenGL ES 2.0 context\n" ); + GLimp_ClearProcAddresses(); + SDL_GL_DeleteContext( SDL_glContext ); + SDL_glContext = NULL; + } + } + } + + if ( !SDL_glContext ) + { + ri.Printf( PRINT_ALL, "Trying to get an OpenGL 3.2 core context\n" ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 2 ); + + SDL_glContext = SDL_GL_CreateContext( SDL_window ); + if ( !SDL_glContext ) { - if ( renderer ) - ri.Printf(PRINT_ALL, "GL_RENDERER is %s, rejecting context\n", renderer); - - GLimp_ClearProcAddresses(); - SDL_GL_DeleteContext(SDL_glContext); - SDL_glContext = NULL; - - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profileMask); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorVersion); + ri.Printf( PRINT_ALL, "SDL_GL_CreateContext failed: %s\n", SDL_GetError() ); } + else + { + const char *renderer; + + ri.Printf( PRINT_ALL, "SDL_GL_CreateContext succeeded.\n" ); + + if ( GLimp_GetProcAddresses( fixedFunction ) ) + { + renderer = (const char *)qglGetString( GL_RENDERER ); + } + else + { + ri.Printf( PRINT_ALL, "GLimp_GetProcAddresses() failed for OpenGL 3.2 core context\n" ); + renderer = NULL; + } + + if ( !renderer || strstr( renderer, "Software Renderer" ) || strstr( renderer, "Software Rasterizer" ) ) + { + if ( renderer ) + ri.Printf(PRINT_ALL, "GL_RENDERER is %s, rejecting context\n", renderer); + + GLimp_ClearProcAddresses(); + SDL_GL_DeleteContext( SDL_glContext ); + SDL_glContext = NULL; + } + } + } + + if ( !SDL_glContext ) + { + ri.Printf( PRINT_ALL, "Trying to get an OpenGL 2.0 context\n" ); + + SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, 0 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 2 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 0 ); } } else { + SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, 0 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 1 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 1 ); + SDL_glContext = NULL; } @@ -815,7 +874,7 @@ static void GLimp_InitExtensions( qboolean fixedFunction ) glConfig.textureCompression = TC_NONE; // GL_EXT_texture_compression_s3tc - if ( SDL_GL_ExtensionSupported( "GL_ARB_texture_compression" ) && + if ( ( QGLES_VERSION_ATLEAST( 2, 0 ) || SDL_GL_ExtensionSupported( "GL_ARB_texture_compression" ) ) && SDL_GL_ExtensionSupported( "GL_EXT_texture_compression_s3tc" ) ) { if ( r_ext_compressed_textures->value ) @@ -996,6 +1055,7 @@ void GLimp_Init( qboolean fixedFunction ) r_sdlDriver = ri.Cvar_Get( "r_sdlDriver", "", CVAR_ROM ); r_allowResize = ri.Cvar_Get( "r_allowResize", "0", CVAR_ARCHIVE | CVAR_LATCH ); r_centerWindow = ri.Cvar_Get( "r_centerWindow", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_useOpenGLES = ri.Cvar_Get( "r_useOpenGLES", "-1", CVAR_ARCHIVE | CVAR_LATCH ); if( ri.Cvar_VariableIntegerValue( "com_abnormalExit" ) ) { diff --git a/opengl2-readme.md b/opengl2-readme.md index ee85c02c..2e076cfa 100644 --- a/opengl2-readme.md +++ b/opengl2-readme.md @@ -63,6 +63,14 @@ For Win32: CVARS ------------------------------------------------------------------------------- +Cvars for API: + +* `r_useOpenGLES` - This enables using OpenGL ES 2+. + Many features are not supported such as sun shadows and HDR. + 1 - Use OpenGL ES. + 0 - Use desktop OpenGL. + -1 - Automatically pick (default). + Cvars for simple rendering features: * `r_ext_compressed_textures` - Automatically compress textures.