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.
This commit is contained in:
Zack Middleton 2024-06-05 21:33:08 -05:00
parent 1fc83e4845
commit 3b984d2b51
14 changed files with 658 additions and 167 deletions

View File

@ -160,6 +160,21 @@ Makefile.local:
The defaults for these variables differ depending on the target platform. 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 # Console
## New cvars ## New cvars

View File

@ -357,11 +357,12 @@ qboolean CL_OpenAVIForWriting( const char *fileName )
else else
afd.motionJpeg = qfalse; 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 // Allocate a bit more space for the capture buffer to account for possible
// padding at the end of pixel lines, and padding for alignment // padding at the end of pixel lines, and padding for alignment
#define MAX_PACK_LEN 16 #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 // raw avi files have pixel lines start on 4-byte boundaries
afd.eBuffer = Z_Malloc(PAD(afd.width * 3, AVI_LINE_PADDING) * afd.height); afd.eBuffer = Z_Malloc(PAD(afd.width * 3, AVI_LINE_PADDING) * afd.height);

View File

@ -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) { void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) {
byte *buffer;
GLuint texture; GLuint texture;
if (!tr.scratchImage[client]) 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 ) { if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) {
tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols; tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols;
tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows; 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_MIN_FILTER, GL_LINEAR);
qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_MAG_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); 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) { if (dirty) {
// otherwise, just subimage upload it so that drivers can tell we are going to be changing // 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 // 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) if (glRefConfig.occlusionQuery)
{ {
tr.sunFlareQueryActive[tr.sunFlareQueryIndex] = qtrue; 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); RB_DrawSun(0.3, tr.sunFlareShader);
if (glRefConfig.occlusionQuery) if (glRefConfig.occlusionQuery)
{ {
qglEndQuery(GL_SAMPLES_PASSED); qglEndQuery(glRefConfig.occlusionQueryTarget);
} }
FBO_Bind(oldFbo); FBO_Bind(oldFbo);

View File

@ -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 ); tr.deluxemaps = ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low );
textureInternalFormat = GL_RGBA8; 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. // Check for the first hdr lightmap, if it exists, use GL_RGBA16 for textures.
char filename[MAX_QPATH]; char filename[MAX_QPATH];

View File

@ -348,7 +348,13 @@ void RE_BeginFrame( stereoFrame_t stereoFrame ) {
// //
if ( r_measureOverdraw->integer ) 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.Printf( PRINT_ALL, "Warning: not enough stencil bits to measure overdraw: %d\n", glConfig.stencilBits );
ri.Cvar_Set( "r_measureOverdraw", "0" ); ri.Cvar_Set( "r_measureOverdraw", "0" );
@ -426,6 +432,13 @@ void RE_BeginFrame( stereoFrame_t stereoFrame ) {
} }
else 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->integer)
{ {
if(r_anaglyphMode->modified) if(r_anaglyphMode->modified)

View File

@ -53,8 +53,83 @@ void GLimp_InitExtraExtensions(void)
// GL function loader, based on https://gist.github.com/rygorous/16796a0c876cf8a5f542caddb55bce8a // GL function loader, based on https://gist.github.com/rygorous/16796a0c876cf8a5f542caddb55bce8a
#define GLE(ret, name, ...) qgl##name = (name##proc *) SDL_GL_GetProcAddress("gl" #name); #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 // OpenGL 1.5 - GL_ARB_occlusion_query
glRefConfig.occlusionQuery = qtrue; glRefConfig.occlusionQuery = qtrue;
glRefConfig.occlusionQueryTarget = GL_SAMPLES_PASSED;
QGL_ARB_occlusion_query_PROCS; QGL_ARB_occlusion_query_PROCS;
// OpenGL 3.0 - GL_ARB_framebuffer_object // OpenGL 3.0 - GL_ARB_framebuffer_object
@ -146,18 +221,6 @@ void GLimp_InitExtraExtensions(void)
ri.Printf(PRINT_ALL, result[2], extension); 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; glRefConfig.memInfo = MI_NONE;
// GL_NVX_gpu_memory_info // GL_NVX_gpu_memory_info
@ -249,5 +312,26 @@ void GLimp_InitExtraExtensions(void)
ri.Printf(PRINT_ALL, result[2], extension); 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 #undef GLE
} }

View File

@ -478,6 +478,14 @@ void RB_RenderFlares (void) {
return; 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) if(r_flareCoeff->modified)
{ {
R_SetFlareCoeff(); R_SetFlareCoeff();

View File

@ -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 // 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 >= 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"); Q_strcat(dest, size, "#version 150\n");
else else
Q_strcat(dest, size, "#version 130\n"); 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) if(shaderType == GL_VERTEX_SHADER)
{ {
Q_strcat(dest, size, "#define attribute in\n"); Q_strcat(dest, size, "#define attribute in\n");
@ -272,8 +286,34 @@ static void GLSL_GetShaderHeader( GLenum shaderType, const GLchar *extra, char *
} }
else else
{ {
Q_strcat(dest, size, "#version 120\n"); if (qglesMajorVersion >= 2)
Q_strcat(dest, size, "#define shadow2D(a,b) shadow2D(a,b).r \n"); {
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 // 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)); 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 // 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 // so we have to reset the line counting
Q_strcat(dest, size, "#line 0\n"); Q_strcat(dest, size, "#line 0\n");
@ -1350,86 +1385,110 @@ void GLSL_InitGPUShaders(void)
} }
attribs = ATTR_POSITION | ATTR_TEXCOORD; // GLSL 1.10+ or GL_EXT_shadow_samplers extension are required for sampler2DShadow type
extradefines[0] = '\0'; if (glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 10)
|| glRefConfig.shadowSamplers)
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++)
{ {
attribs = ATTR_POSITION | ATTR_TEXCOORD; attribs = ATTR_POSITION | ATTR_TEXCOORD;
extradefines[0] = '\0'; extradefines[0] = '\0';
if (i & 1) if (qglesMajorVersion < 3 && glRefConfig.shadowSamplers)
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!"); Q_strcat(extradefines, 1024, "#extension GL_EXT_shadow_samplers : enable\n");
} }
GLSL_InitUniforms(&tr.depthBlurShader[i]); if (r_shadowFilter->integer >= 1)
Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER\n");
GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENIMAGEMAP, TB_COLORMAP); if (r_shadowFilter->integer >= 2)
GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENDEPTHMAP, TB_LIGHTMAP); Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER2\n");
GLSL_FinishGPUShader(&tr.depthBlurShader[i]); 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++; 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 #if 0
attribs = ATTR_POSITION | ATTR_TEXCOORD; attribs = ATTR_POSITION | ATTR_TEXCOORD;
extradefines[0] = '\0'; extradefines[0] = '\0';

View File

@ -1455,6 +1455,106 @@ byte mipBlendColors[16][4] = {
{0,0,255,128}, {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 ) static void RawImage_SwizzleRA( byte *data, int width, int height )
{ {
int i; 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 rgtc = internalFormat == GL_COMPRESSED_RG_RGTC2;
qboolean rgba8 = picFormat == GL_RGBA8 || picFormat == GL_SRGB8_ALPHA8_EXT; qboolean rgba8 = picFormat == GL_RGBA8 || picFormat == GL_SRGB8_ALPHA8_EXT;
qboolean rgba = rgba8 || picFormat == GL_RGBA16; qboolean rgba = rgba8 || picFormat == GL_RGBA16;
qboolean mipmap = !!(flags & IMGFLAG_MIPMAP); qboolean mipmap = !!(flags & IMGFLAG_MIPMAP);
int size, miplevel; int size, miplevel;
qboolean lastMip = qfalse; qboolean lastMip = qfalse;
byte *formatBuffer = NULL;
dataFormat = PixelDataFormatFromInternalFormat(internalFormat); if (qglesMajorVersion && rgba8 && (dataFormat != GL_RGBA || dataType != GL_UNSIGNED_BYTE))
dataType = picFormat == GL_RGBA16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE; {
formatBuffer = ri.Hunk_AllocateTempMemory(4 * width * height);
}
miplevel = 0; miplevel = 0;
do do
@ -1974,6 +2076,11 @@ static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y, int
if (rgba8 && rgtc) if (rgba8 && rgtc)
RawImage_UploadToRgtc2Texture(texture, miplevel, x, y, width, height, data); 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 else
qglTextureSubImage2DEXT(texture, target, miplevel, x, y, width, height, dataFormat, dataType, data); 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); 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; int i, c;
byte *scan; 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++) for (i = 0; i < 6; i++)
{ {
int w2 = width, h2 = height; 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--) for (c = numMips; c; c--)
{ {
data += CalculateMipSize(w2, h2, picFormat); 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 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(); 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 picmip = !!(flags & IMGFLAG_PICMIP);
qboolean lastMip; qboolean lastMip;
GLenum textureTarget = cubemap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D; GLenum textureTarget = cubemap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
GLenum dataFormat; GLenum dataFormat, dataType;
if (strlen(name) >= MAX_QPATH ) { if (strlen(name) >= MAX_QPATH ) {
ri.Error (ERR_DROP, "R_CreateImage: \"%s\" is too long", name); 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) if (!internalFormat)
internalFormat = RawImage_GetFormat(pic, width * height, picFormat, isLightmap, image->type, image->flags); 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; image->internalFormat = internalFormat;
// Possibly scale image before uploading. // 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; image->uploadHeight = height;
// Allocate texture storage so we don't have to worry about it later. // Allocate texture storage so we don't have to worry about it later.
dataFormat = PixelDataFormatFromInternalFormat(internalFormat);
mipWidth = width; mipWidth = width;
mipHeight = height; mipHeight = height;
miplevel = 0; miplevel = 0;
@ -2176,11 +2332,11 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe
int i; int i;
for (i = 0; i < 6; 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 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); 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. // Upload data.
if (pic) 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) if (resampledBuffer != NULL)
ri.Hunk_FreeTempMemory(resampledBuffer); 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 ) 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);
} }
//=================================================================== //===================================================================

View File

@ -451,20 +451,42 @@ 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 *RB_ReadPixels(int x, int y, int width, int height, size_t *offset, int *padlen)
{ {
byte *buffer, *bufstart; byte *buffer, *bufstart;
int padwidth, linelen; int padwidth, linelen, bytesPerPixel;
GLint packAlign; 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); qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign);
linelen = width * 3; linelen = width * bytesPerPixel;
padwidth = PAD(linelen, packAlign); padwidth = PAD(linelen, packAlign);
// Allocate a few more bytes so that we can choose an alignment we like // Allocate a few more bytes so that we can choose an alignment we like
buffer = ri.Hunk_AllocateTempMemory(padwidth * height + *offset + packAlign - 1); buffer = ri.Hunk_AllocateTempMemory(padwidth * height + *offset + packAlign - 1);
bufstart = PADP((intptr_t) buffer + *offset, packAlign); bufstart = PADP((intptr_t) buffer + *offset, packAlign);
qglReadPixels(x, y, width, height, format, GL_UNSIGNED_BYTE, bufstart);
qglReadPixels(x, y, width, height, GL_RGB, 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; *offset = bufstart - buffer;
*padlen = padwidth - linelen; *padlen = padwidth - linelen;
@ -877,9 +899,10 @@ const void *RB_TakeVideoFrameCmd( const void *data )
{ {
const videoFrameCommand_t *cmd; const videoFrameCommand_t *cmd;
byte *cBuf; byte *cBuf;
size_t memcount, linelen; size_t memcount, bytesPerPixel, linelen, avilinelen;
int padwidth, avipadwidth, padlen, avipadlen; int padwidth, avipadwidth, padlen, avipadlen;
GLint packAlign; int yin, xin, xout;
GLint packAlign, format;
// finish any 2D drawing if needed // finish any 2D drawing if needed
if(tess.numIndexes) if(tess.numIndexes)
@ -887,20 +910,32 @@ const void *RB_TakeVideoFrameCmd( const void *data )
cmd = (const videoFrameCommand_t *)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); qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign);
linelen = cmd->width * 3; linelen = cmd->width * bytesPerPixel;
// Alignment stuff for glReadPixels // Alignment stuff for glReadPixels
padwidth = PAD(linelen, packAlign); padwidth = PAD(linelen, packAlign);
padlen = padwidth - linelen; padlen = padwidth - linelen;
avilinelen = cmd->width * 3;
// AVI line padding // AVI line padding
avipadwidth = PAD(linelen, AVI_LINE_PADDING); avipadwidth = PAD(avilinelen, AVI_LINE_PADDING);
avipadlen = avipadwidth - linelen; avipadlen = avipadwidth - avilinelen;
cBuf = PADP(cmd->captureBuffer, packAlign); 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); GL_UNSIGNED_BYTE, cBuf);
memcount = padwidth * cmd->height; memcount = padwidth * cmd->height;
@ -911,7 +946,21 @@ const void *RB_TakeVideoFrameCmd( const void *data )
if(cmd->motionJpeg) 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, r_aviMotionJpegQuality->integer,
cmd->width, cmd->height, cBuf, padlen); cmd->width, cmd->height, cBuf, padlen);
ri.CL_WriteAVIVideoFrame(cmd->encodeBuffer, memcount); ri.CL_WriteAVIVideoFrame(cmd->encodeBuffer, memcount);
@ -934,7 +983,7 @@ const void *RB_TakeVideoFrameCmd( const void *data )
*destptr++ = srcptr[2]; *destptr++ = srcptr[2];
*destptr++ = srcptr[1]; *destptr++ = srcptr[1];
*destptr++ = srcptr[0]; *destptr++ = srcptr[0];
srcptr += 3; srcptr += bytesPerPixel;
} }
Com_Memset(destptr, '\0', avipadlen); Com_Memset(destptr, '\0', avipadlen);

View File

@ -1406,6 +1406,7 @@ typedef struct {
qboolean intelGraphics; qboolean intelGraphics;
qboolean occlusionQuery; qboolean occlusionQuery;
GLenum occlusionQueryTarget;
int glslMajorVersion; int glslMajorVersion;
int glslMinorVersion; int glslMinorVersion;
@ -1429,6 +1430,12 @@ typedef struct {
qboolean vertexArrayObject; qboolean vertexArrayObject;
qboolean directStateAccess; qboolean directStateAccess;
// OpenGL ES extensions
qboolean readDepth;
qboolean readStencil;
qboolean shadowSamplers;
qboolean standardDerivatives;
} glRefConfig_t; } glRefConfig_t;
@ -2502,5 +2509,7 @@ size_t RE_SaveJPGToBuffer(byte *buffer, size_t bufSize, int quality,
void RE_TakeVideoFrame( int width, int height, void RE_TakeVideoFrame( int width, int height,
byte *captureBuffer, byte *encodeBuffer, qboolean motionJpeg ); 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 #endif //TR_LOCAL_H

View File

@ -290,6 +290,8 @@ static qboolean RB_UpdateSunFlareVis(void)
ri.Printf(PRINT_DEVELOPER, "Waited %d iterations\n", iter); 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); qglGetQueryObjectuiv(tr.sunFlareQuery[tr.sunFlareQueryIndex], GL_QUERY_RESULT, &sampleCount);
return sampleCount > 0; return sampleCount > 0;
} }

View File

@ -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_allowResize; // make window resizable
cvar_t *r_centerWindow; cvar_t *r_centerWindow;
cvar_t *r_sdlDriver; cvar_t *r_sdlDriver;
cvar_t *r_useOpenGLES;
int qglMajorVersion, qglMinorVersion; int qglMajorVersion, qglMinorVersion;
int qglesMajorVersion, qglesMinorVersion; int qglesMajorVersion, qglesMinorVersion;
@ -230,6 +231,27 @@ static void GLimp_DetectAvailableModes(void)
SDL_free( modes ); 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 GLimp_GetProcAddresses
@ -306,8 +328,11 @@ static qboolean GLimp_GetProcAddresses( qboolean fixedFunction ) {
QGL_1_3_PROCS; QGL_1_3_PROCS;
QGL_1_5_PROCS; QGL_1_5_PROCS;
QGL_2_0_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 { } else {
Com_Error( ERR_FATAL, "Unsupported OpenGL Version (%s), OpenGL 2.0 is required", version ); 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) if (!fixedFunction)
{ {
int profileMask, majorVersion, minorVersion; int profileMask;
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);
ri.Printf(PRINT_ALL, "Trying to get an OpenGL 3.2 core context\n"); SDL_GL_GetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, &profileMask );
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); if ( r_useOpenGLES->integer == 1 || ( r_useOpenGLES->integer == -1 && profileMask == SDL_GL_CONTEXT_PROFILE_ES ) )
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
if ((SDL_glContext = SDL_GL_CreateContext(SDL_window)) == NULL)
{ {
ri.Printf(PRINT_ALL, "SDL_GL_CreateContext failed: %s\n", SDL_GetError()); ri.Printf( PRINT_ALL, "Trying to get an OpenGL ES 2.0 context\n" );
ri.Printf(PRINT_ALL, "Reverting to default 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_glContext = SDL_GL_CreateContext( SDL_window );
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion); if ( !SDL_glContext )
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 ) )
{ {
renderer = (const char *)qglGetString(GL_RENDERER); ri.Printf( PRINT_ALL, "SDL_GL_CreateContext failed: %s\n", SDL_GetError() );
} }
else else
{ {
ri.Printf( PRINT_ALL, "GLimp_GetProcAddresses() failed for OpenGL 3.2 core context\n" ); ri.Printf( PRINT_ALL, "SDL_GL_CreateContext succeeded.\n" );
renderer = NULL;
}
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, "SDL_GL_CreateContext failed: %s\n", SDL_GetError() );
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);
} }
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 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; SDL_glContext = NULL;
} }
@ -815,7 +874,7 @@ static void GLimp_InitExtensions( qboolean fixedFunction )
glConfig.textureCompression = TC_NONE; glConfig.textureCompression = TC_NONE;
// GL_EXT_texture_compression_s3tc // 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" ) ) SDL_GL_ExtensionSupported( "GL_EXT_texture_compression_s3tc" ) )
{ {
if ( r_ext_compressed_textures->value ) 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_sdlDriver = ri.Cvar_Get( "r_sdlDriver", "", CVAR_ROM );
r_allowResize = ri.Cvar_Get( "r_allowResize", "0", CVAR_ARCHIVE | CVAR_LATCH ); 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_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" ) ) if( ri.Cvar_VariableIntegerValue( "com_abnormalExit" ) )
{ {

View File

@ -63,6 +63,14 @@ For Win32:
CVARS 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: Cvars for simple rendering features:
* `r_ext_compressed_textures` - Automatically compress textures. * `r_ext_compressed_textures` - Automatically compress textures.