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..8603794b 100644 --- a/code/renderergl2/tr_extensions.c +++ b/code/renderergl2/tr_extensions.c @@ -45,6 +45,17 @@ void GLimp_InitExtraExtensions(void) if (strstr((char *)qglGetString(GL_RENDERER), "Intel")) glRefConfig.intelGraphics = qtrue; + if (qglesMajorVersion) + { + glRefConfig.vaoCacheGlIndexType = GL_UNSIGNED_SHORT; + glRefConfig.vaoCacheGlIndexSize = sizeof(unsigned short); + } + else + { + glRefConfig.vaoCacheGlIndexType = GL_UNSIGNED_INT; + glRefConfig.vaoCacheGlIndexSize = sizeof(unsigned int); + } + // set DSA fallbacks #define GLE(ret, name, ...) qgl##name = GLDSA_##name; QGL_EXT_direct_state_access_PROCS; @@ -53,8 +64,96 @@ 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); + } + + // GL_OES_element_index_uint + extension = "GL_OES_element_index_uint"; + if (SDL_GL_ExtensionSupported(extension)) + { + glRefConfig.vaoCacheGlIndexType = GL_UNSIGNED_INT; + glRefConfig.vaoCacheGlIndexSize = sizeof(unsigned int); + ri.Printf(PRINT_ALL, result[1], 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 +245,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 +336,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..39223bf6 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"); @@ -933,6 +968,15 @@ void GLSL_InitGPUShaders(void) startTime = ri.Milliseconds(); + // OpenGL ES may not have enough attributes to fit ones used for vertex animation + if ( glRefConfig.maxVertexAttribs > ATTR_INDEX_NORMAL2 ) { + ri.Printf(PRINT_ALL, "Using GPU vertex animation\n"); + glRefConfig.gpuVertexAnimation = qtrue; + } else { + ri.Printf(PRINT_ALL, "Using CPU vertex animation\n"); + glRefConfig.gpuVertexAnimation = qfalse; + } + for (i = 0; i < GENERICDEF_COUNT; i++) { if ((i & GENERICDEF_USE_VERTEX_ANIMATION) && (i & GENERICDEF_USE_BONE_ANIMATION)) @@ -955,6 +999,9 @@ void GLSL_InitGPUShaders(void) if (i & GENERICDEF_USE_VERTEX_ANIMATION) { + if (!glRefConfig.gpuVertexAnimation) + continue; + Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n"); attribs |= ATTR_POSITION2 | ATTR_NORMAL2; } @@ -1006,6 +1053,9 @@ void GLSL_InitGPUShaders(void) if ((i & FOGDEF_USE_VERTEX_ANIMATION) && (i & FOGDEF_USE_BONE_ANIMATION)) continue; + if ((i & FOGDEF_USE_VERTEX_ANIMATION) && !glRefConfig.gpuVertexAnimation) + continue; + if ((i & FOGDEF_USE_BONE_ANIMATION) && !glRefConfig.glslMaxAnimatedBones) continue; @@ -1186,12 +1236,17 @@ void GLSL_InitGPUShaders(void) if (i & LIGHTDEF_ENTITY_VERTEX_ANIMATION) { - Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n#define USE_MODELMATRIX\n"); - attribs |= ATTR_POSITION2 | ATTR_NORMAL2; + Q_strcat(extradefines, 1024, "#define USE_MODELMATRIX\n"); - if (r_normalMapping->integer) + if (glRefConfig.gpuVertexAnimation) { - attribs |= ATTR_TANGENT2; + Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n"); + attribs |= ATTR_POSITION2 | ATTR_NORMAL2; + + if (r_normalMapping->integer) + { + attribs |= ATTR_TANGENT2; + } } } else if (i & LIGHTDEF_ENTITY_BONE_ANIMATION) @@ -1226,6 +1281,9 @@ void GLSL_InitGPUShaders(void) if ((i & SHADOWMAPDEF_USE_VERTEX_ANIMATION) && (i & SHADOWMAPDEF_USE_BONE_ANIMATION)) continue; + if ((i & SHADOWMAPDEF_USE_VERTEX_ANIMATION) && !glRefConfig.gpuVertexAnimation) + continue; + if ((i & SHADOWMAPDEF_USE_BONE_ANIMATION) && !glRefConfig.glslMaxAnimatedBones) continue; @@ -1350,86 +1408,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'; @@ -1462,7 +1544,7 @@ void GLSL_ShutdownGPUShaders(void) ri.Printf(PRINT_ALL, "------- GLSL_ShutdownGPUShaders -------\n"); - for (i = 0; i < ATTR_INDEX_COUNT; i++) + for (i = 0; i < ATTR_INDEX_COUNT && i < glRefConfig.maxVertexAttribs; i++) qglDisableVertexAttribArray(i); GL_BindNullProgram(); 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..aad662c5 100644 --- a/code/renderergl2/tr_init.c +++ b/code/renderergl2/tr_init.c @@ -279,6 +279,9 @@ static void InitOpenGL( void ) qglGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS, &temp ); glConfig.numTextureUnits = temp; + qglGetIntegerv( GL_MAX_VERTEX_ATTRIBS, &temp ); + glRefConfig.maxVertexAttribs = temp; + // reserve 160 components for other uniforms qglGetIntegerv( GL_MAX_VERTEX_UNIFORM_COMPONENTS, &temp ); glRefConfig.glslMaxAnimatedBones = Com_Clamp( 0, IQM_MAX_JOINTS, ( temp - 160 ) / 16 ); @@ -451,21 +454,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 +902,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 +913,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 +949,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 +986,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..9f46866f 100644 --- a/code/renderergl2/tr_local.h +++ b/code/renderergl2/tr_local.h @@ -49,8 +49,10 @@ QGL_ARB_vertex_array_object_PROCS; QGL_EXT_direct_state_access_PROCS; #undef GLE -#define GL_INDEX_TYPE GL_UNSIGNED_INT -typedef unsigned int glIndex_t; +#define GL_INDEX_TYPE GL_UNSIGNED_SHORT +typedef unsigned short glIndex_t; + +typedef unsigned int vaoCacheGlIndex_t; #define BUFFER_OFFSET(i) ((char *)NULL + (i)) @@ -1406,6 +1408,7 @@ typedef struct { qboolean intelGraphics; qboolean occlusionQuery; + GLenum occlusionQueryTarget; int glslMajorVersion; int glslMinorVersion; @@ -1429,6 +1432,18 @@ typedef struct { qboolean vertexArrayObject; qboolean directStateAccess; + + int maxVertexAttribs; + qboolean gpuVertexAnimation; + + GLenum vaoCacheGlIndexType; // GL_UNSIGNED_INT or GL_UNSIGNED_SHORT + size_t vaoCacheGlIndexSize; // must be <= sizeof( vaoCacheGlIndex_t ) + + // OpenGL ES extensions + qboolean readDepth; + qboolean readStencil; + qboolean shadowSamplers; + qboolean standardDerivatives; } glRefConfig_t; @@ -2213,6 +2228,7 @@ void R_VaoList_f(void); void RB_UpdateTessVao(unsigned int attribBits); void VaoCache_Commit(void); +void VaoCache_DrawElements(int numIndexes, int firstIndex); void VaoCache_Init(void); void VaoCache_BindVao(void); void VaoCache_CheckAdd(qboolean *endSurface, qboolean *recycleVertexBuffer, qboolean *recycleIndexBuffer, int numVerts, int numIndexes); @@ -2502,5 +2518,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_mesh.c b/code/renderergl2/tr_mesh.c index 4f2c6724..222b06d0 100644 --- a/code/renderergl2/tr_mesh.c +++ b/code/renderergl2/tr_mesh.c @@ -282,9 +282,10 @@ R_AddMD3Surfaces */ void R_AddMD3Surfaces( trRefEntity_t *ent ) { int i; - mdvModel_t *model = NULL; - mdvSurface_t *surface = NULL; - shader_t *shader = NULL; + mdvModel_t *model; + mdvSurface_t *surface; + void *drawSurf; + shader_t *shader; int cull; int lod; int fogNum; @@ -382,6 +383,12 @@ void R_AddMD3Surfaces( trRefEntity_t *ent ) { shader = tr.shaders[ surface->shaderIndexes[ ent->e.skinNum % surface->numShaderIndexes ] ]; } + if ( model->numVaoSurfaces > 0 ) { + drawSurf = &model->vaoSurfaces[i]; + } else { + drawSurf = surface; + } + // we will add shadows even if the main object isn't visible in the view // stencil shadows can't do personal models unless I polyhedron clip @@ -390,7 +397,7 @@ void R_AddMD3Surfaces( trRefEntity_t *ent ) { && fogNum == 0 && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) && shader->sort == SS_OPAQUE ) { - R_AddDrawSurf( (void *)&model->vaoSurfaces[i], tr.shadowShader, 0, qfalse, qfalse, 0 ); + R_AddDrawSurf( drawSurf, tr.shadowShader, 0, qfalse, qfalse, 0 ); } // projection shadows work fine with personal models @@ -398,12 +405,12 @@ void R_AddMD3Surfaces( trRefEntity_t *ent ) { && fogNum == 0 && (ent->e.renderfx & RF_SHADOW_PLANE ) && shader->sort == SS_OPAQUE ) { - R_AddDrawSurf( (void *)&model->vaoSurfaces[i], tr.projectionShadowShader, 0, qfalse, qfalse, 0 ); + R_AddDrawSurf( drawSurf, tr.projectionShadowShader, 0, qfalse, qfalse, 0 ); } // don't add third_person objects if not viewing through a portal if ( !personalModel ) { - R_AddDrawSurf((void *)&model->vaoSurfaces[i], shader, fogNum, qfalse, qfalse, cubemapIndex ); + R_AddDrawSurf( drawSurf, shader, fogNum, qfalse, qfalse, cubemapIndex ); } surface++; diff --git a/code/renderergl2/tr_model.c b/code/renderergl2/tr_model.c index 0fdf60d8..7201be1b 100644 --- a/code/renderergl2/tr_model.c +++ b/code/renderergl2/tr_model.c @@ -664,6 +664,12 @@ static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, int bufferSize, surf++; } + if (mdvModel->numFrames > 1 && !glRefConfig.gpuVertexAnimation) + { + mdvModel->numVaoSurfaces = 0; + mdvModel->vaoSurfaces = NULL; + } + else { srfVaoMdvMesh_t *vaoSurf; 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/renderergl2/tr_shade.c b/code/renderergl2/tr_shade.c index 21f9543a..dec35e52 100644 --- a/code/renderergl2/tr_shade.c +++ b/code/renderergl2/tr_shade.c @@ -40,7 +40,14 @@ R_DrawElements void R_DrawElements( int numIndexes, int firstIndex ) { - qglDrawElements(GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(glIndex_t))); + if (tess.useCacheVao) + { + VaoCache_DrawElements(numIndexes, firstIndex); + } + else + { + qglDrawElements(GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(glIndex_t))); + } } @@ -1720,6 +1727,8 @@ void RB_EndSurface( void ) { tess.numIndexes = 0; tess.numVertexes = 0; tess.firstIndex = 0; + tess.useCacheVao = qfalse; + tess.useInternalVao = qfalse; GLimp_LogComment( "----------\n" ); } diff --git a/code/renderergl2/tr_vbo.c b/code/renderergl2/tr_vbo.c index df64f8bc..5094df18 100644 --- a/code/renderergl2/tr_vbo.c +++ b/code/renderergl2/tr_vbo.c @@ -676,7 +676,7 @@ static struct srfVert_t vertexes[VAOCACHE_QUEUE_MAX_VERTEXES]; int vertexCommitSize; - glIndex_t indexes[VAOCACHE_QUEUE_MAX_INDEXES]; + vaoCacheGlIndex_t indexes[VAOCACHE_QUEUE_MAX_INDEXES]; int indexCommitSize; } vcq; @@ -687,18 +687,13 @@ vcq; // srfVert_t is 60 bytes // assuming each vert is referenced 4 times, need 16 bytes (4 glIndex_t) per vert // -> need about 4/15ths the space for indexes as vertexes -#if GL_INDEX_TYPE == GL_UNSIGNED_SHORT -#define VAOCACHE_VERTEX_BUFFER_SIZE (sizeof(srfVert_t) * USHRT_MAX) -#define VAOCACHE_INDEX_BUFFER_SIZE (sizeof(glIndex_t) * USHRT_MAX * 4) -#else // GL_UNSIGNED_INT #define VAOCACHE_VERTEX_BUFFER_SIZE (16 * 1024 * 1024) #define VAOCACHE_INDEX_BUFFER_SIZE (5 * 1024 * 1024) -#endif typedef struct buffered_s { - void *data; - int size; + glIndex_t *indexes; + int numIndexes; int bufferOffset; } buffered_t; @@ -736,7 +731,7 @@ void VaoCache_Commit(void) buffered_t *indexSet2 = indexSet; for (surf = vcq.surfaces; surf < end; surf++, indexSet2++) { - if (surf->indexes != indexSet2->data || (surf->numIndexes * sizeof(glIndex_t)) != indexSet2->size) + if (surf->indexes != indexSet2->indexes || surf->numIndexes != indexSet2->numIndexes) break; } @@ -750,7 +745,7 @@ void VaoCache_Commit(void) // If found, use it if (indexSet < vc.surfaceIndexSets + vc.numSurfaces) { - tess.firstIndex = indexSet->bufferOffset / sizeof(glIndex_t); + tess.firstIndex = indexSet->bufferOffset / glRefConfig.vaoCacheGlIndexSize; //ri.Printf(PRINT_ALL, "firstIndex %d numIndexes %d as %d\n", tess.firstIndex, tess.numIndexes, (int)(batchLength - vc.batchLengths)); //ri.Printf(PRINT_ALL, "vc.numSurfaces %d vc.numBatches %d\n", vc.numSurfaces, vc.numBatches); } @@ -759,20 +754,21 @@ void VaoCache_Commit(void) else { srfVert_t *dstVertex = vcq.vertexes; - glIndex_t *dstIndex = vcq.indexes; + vaoCacheGlIndex_t *dstIndex = vcq.indexes; + unsigned short *dstIndexUshort = (unsigned short *)vcq.indexes; batchLength = vc.batchLengths + vc.numBatches; *batchLength = vcq.numSurfaces; vc.numBatches++; - tess.firstIndex = vc.indexOffset / sizeof(glIndex_t); + tess.firstIndex = vc.indexOffset / glRefConfig.vaoCacheGlIndexSize; vcq.vertexCommitSize = 0; vcq.indexCommitSize = 0; for (surf = vcq.surfaces; surf < end; surf++) { glIndex_t *srcIndex = surf->indexes; int vertexesSize = surf->numVerts * sizeof(srfVert_t); - int indexesSize = surf->numIndexes * sizeof(glIndex_t); + int indexesSize = surf->numIndexes * glRefConfig.vaoCacheGlIndexSize; int i, indexOffset = (vc.vertexOffset + vcq.vertexCommitSize) / sizeof(srfVert_t); Com_Memcpy(dstVertex, surf->vertexes, vertexesSize); @@ -781,13 +777,21 @@ void VaoCache_Commit(void) vcq.vertexCommitSize += vertexesSize; indexSet = vc.surfaceIndexSets + vc.numSurfaces; - indexSet->data = surf->indexes; - indexSet->size = indexesSize; + indexSet->indexes = surf->indexes; + indexSet->numIndexes = surf->numIndexes; indexSet->bufferOffset = vc.indexOffset + vcq.indexCommitSize; vc.numSurfaces++; - for (i = 0; i < surf->numIndexes; i++) - *dstIndex++ = *srcIndex++ + indexOffset; + if (glRefConfig.vaoCacheGlIndexType == GL_UNSIGNED_SHORT) + { + for (i = 0; i < surf->numIndexes; i++) + *dstIndexUshort++ = *srcIndex++ + indexOffset; + } + else + { + for (i = 0; i < surf->numIndexes; i++) + *dstIndex++ = *srcIndex++ + indexOffset; + } vcq.indexCommitSize += indexesSize; } @@ -810,9 +814,30 @@ void VaoCache_Commit(void) } } +void VaoCache_DrawElements(int numIndexes, int firstIndex) +{ + assert( glState.currentVao == vc.vao ); + + qglDrawElements(GL_TRIANGLES, numIndexes, glRefConfig.vaoCacheGlIndexType, BUFFER_OFFSET(firstIndex * glRefConfig.vaoCacheGlIndexSize)); +} + void VaoCache_Init(void) { - vc.vao = R_CreateVao("VaoCache", NULL, VAOCACHE_VERTEX_BUFFER_SIZE, NULL, VAOCACHE_INDEX_BUFFER_SIZE, VAO_USAGE_DYNAMIC); + int vertexBufferSize; + int indexBufferSize; + + if (glRefConfig.vaoCacheGlIndexType == GL_UNSIGNED_SHORT) + { + vertexBufferSize = sizeof(srfVert_t) * USHRT_MAX; + indexBufferSize = sizeof(unsigned short) * USHRT_MAX * 4; + } + else + { + vertexBufferSize = VAOCACHE_VERTEX_BUFFER_SIZE; + indexBufferSize = VAOCACHE_INDEX_BUFFER_SIZE; + } + + vc.vao = R_CreateVao("VaoCache", NULL, vertexBufferSize, NULL, indexBufferSize, VAO_USAGE_DYNAMIC); vc.vao->attribs[ATTR_INDEX_POSITION].enabled = 1; vc.vao->attribs[ATTR_INDEX_TEXCOORD].enabled = 1; @@ -881,7 +906,7 @@ void VaoCache_BindVao(void) void VaoCache_CheckAdd(qboolean *endSurface, qboolean *recycleVertexBuffer, qboolean *recycleIndexBuffer, int numVerts, int numIndexes) { int vertexesSize = sizeof(srfVert_t) * numVerts; - int indexesSize = sizeof(glIndex_t) * numIndexes; + int indexesSize = glRefConfig.vaoCacheGlIndexSize * numIndexes; if (vc.vao->vertexesSize < vc.vertexOffset + vcq.vertexCommitSize + vertexesSize) { @@ -924,7 +949,7 @@ void VaoCache_CheckAdd(qboolean *endSurface, qboolean *recycleVertexBuffer, qboo *endSurface = qtrue; } - if (VAOCACHE_QUEUE_MAX_INDEXES * sizeof(glIndex_t) < vcq.indexCommitSize + indexesSize) + if (VAOCACHE_QUEUE_MAX_INDEXES * glRefConfig.vaoCacheGlIndexSize < vcq.indexCommitSize + indexesSize) { //ri.Printf(PRINT_ALL, "out of queued indexes\n"); *endSurface = qtrue; @@ -964,5 +989,5 @@ void VaoCache_AddSurface(srfVert_t *verts, int numVerts, glIndex_t *indexes, int vcq.numSurfaces++; vcq.vertexCommitSize += sizeof(srfVert_t) * numVerts; - vcq.indexCommitSize += sizeof(glIndex_t) * numIndexes; + vcq.indexCommitSize += glRefConfig.vaoCacheGlIndexSize * numIndexes; } 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.