diff --git a/code/qcommon/q_shared.h b/code/qcommon/q_shared.h index b46ac1b6b..586cfdac5 100644 --- a/code/qcommon/q_shared.h +++ b/code/qcommon/q_shared.h @@ -613,6 +613,10 @@ typedef struct { #define VectorClear(a) ((a)[0] = (a)[1] = (a)[2] = 0) #define VectorNegate(a, b) ((b)[0] = -(a)[0], (b)[1] = -(a)[1], (b)[2] = -(a)[2]) #define VectorSet(v, x, y, z) ((v)[0] = (x), (v)[1] = (y), (v)[2] = (z)) +#define Vector2Set(v, x, y) ((v)[0] = (x), (v)[1] = (y)) +#define Vector2Copy(a, b) ((b)[0] = (a)[0], (b)[1] = (a)[1]) + +#define Vector4Set(v, x, y, z, w) ((v)[0] = (x), (v)[1] = (y), (v)[2] = (z), (v)[3] = (w)) #define Vector4Copy(a, b) ((b)[0] = (a)[0], (b)[1] = (a)[1], (b)[2] = (a)[2], (b)[3] = (a)[3]) #define Byte4Copy(a, b) ((b)[0] = (a)[0], (b)[1] = (a)[1], (b)[2] = (a)[2], (b)[3] = (a)[3]) diff --git a/code/renderercommon/qgl.h b/code/renderercommon/qgl.h index 96058f9bc..c5c4ae6ab 100644 --- a/code/renderercommon/qgl.h +++ b/code/renderercommon/qgl.h @@ -88,6 +88,7 @@ extern void(APIENTRYP qglUnlockArraysEXT)(void); #define QGL_1_1_FIXED_FUNCTION_PROCS \ GLE(void, AlphaFunc, GLenum func, GLclampf ref) \ GLE(void, Color4f, GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) \ + GLE(void, Color4ub, GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha) \ GLE(void, ColorPointer, GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) \ GLE(void, DisableClientState, GLenum cap) \ GLE(void, EnableClientState, GLenum cap) \ diff --git a/code/renderergl1/tr_backend.c b/code/renderergl1/tr_backend.c index 6a198f03e..b79c97285 100644 --- a/code/renderergl1/tr_backend.c +++ b/code/renderergl1/tr_backend.c @@ -645,6 +645,46 @@ static void RB_SetGL2D(void) { backEnd.refdef.floatTime = backEnd.refdef.time * 0.001; } +/* +================ +RB_InstantQuad2 +================ +*/ +void RB_InstantQuad2(vec4_t quadVerts[4], vec2_t texCoords[4]) { + glIndex_t indexes[6]; + + qglDisableClientState(GL_COLOR_ARRAY); + qglEnableClientState(GL_TEXTURE_COORD_ARRAY); + + qglTexCoordPointer(2, GL_FLOAT, 0, texCoords); + qglVertexPointer(3, GL_FLOAT, 16, quadVerts); + + indexes[0] = 0; + indexes[1] = 1; + indexes[2] = 2; + indexes[3] = 0; + indexes[4] = 2; + indexes[5] = 3; + + R_DrawElements(6, indexes); +} + +/* +================ +RB_InstantQuad +================ +*/ +void RB_InstantQuad(vec4_t quadVerts[4]) { + vec2_t texCoords[4] = { + {0, 0}, + {1, 0}, + {1, 1}, + {0, 1} + }; + + RB_InstantQuad2(quadVerts, texCoords); +} + /* ============= RE_StretchRaw @@ -696,19 +736,26 @@ void RE_StretchRaw(int x, int y, int w, int h, int cols, int rows, const byte *d qglColor3f(tr.identityLight, tr.identityLight, tr.identityLight); - qglBegin(GL_QUADS); - qglTexCoord2f(0.5f / cols, 0.5f / rows); - qglVertex2f(x, y); - qglTexCoord2f((cols - 0.5f) / cols, 0.5f / rows); - qglVertex2f(x + w, y); - qglTexCoord2f((cols - 0.5f) / cols, (rows - 0.5f) / rows); - qglVertex2f(x + w, y + h); - qglTexCoord2f(0.5f / cols, (rows - 0.5f) / rows); - qglVertex2f(x, y + h); - qglEnd(); + { + vec4_t quadVerts[4]; + vec2_t texCoords[4]; + + Vector4Set(quadVerts[0], x, y, 0.0f, 1.0f); + Vector4Set(quadVerts[1], x + w, y, 0.0f, 1.0f); + Vector4Set(quadVerts[2], x + w, y + h, 0.0f, 1.0f); + Vector4Set(quadVerts[3], x, y + h, 0.0f, 1.0f); + + Vector2Set(texCoords[0], 0.5f / cols, 0.5f / rows); + Vector2Set(texCoords[1], (cols - 0.5f) / cols, 0.5f / rows); + Vector2Set(texCoords[2], (cols - 0.5f) / cols, (rows - 0.5f) / rows); + Vector2Set(texCoords[3], 0.5f / cols, (rows - 0.5f) / rows); + + RB_InstantQuad2(quadVerts, texCoords); + } } void RE_UploadCinematic(int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) { + byte *buffer; GL_Bind(tr.scratchImage[client]); @@ -716,16 +763,35 @@ 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; - qglTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + + // manually convert RGBA to RGB for OpenGL ES + if (qglesMajorVersion >= 1) { + buffer = ri.Hunk_AllocateTempMemory(3 * cols * rows); + + R_ConvertTextureFormat(data, cols, rows, GL_RGB, GL_UNSIGNED_BYTE, buffer); + qglTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, cols, rows, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer); + + ri.Hunk_FreeTempMemory(buffer); + } else { + qglTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + } + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glConfig.haveClampToEdge ? GL_CLAMP_TO_EDGE : GL_CLAMP); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glConfig.haveClampToEdge ? GL_CLAMP_TO_EDGE : GL_CLAMP); } else { 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 - qglTexSubImage2D(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); + qglTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGB, GL_UNSIGNED_BYTE, buffer); + + ri.Hunk_FreeTempMemory(buffer); + } else { + qglTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data); + } } } } @@ -883,6 +949,7 @@ void RB_ShowImages(void) { image_t *image; float x, y, w, h; int start, end; + vec4_t quadVerts[4]; if (!backEnd.projection2D) { RB_SetGL2D(); @@ -909,16 +976,13 @@ void RB_ShowImages(void) { } GL_Bind(image); - qglBegin(GL_QUADS); - qglTexCoord2f(0, 0); - qglVertex2f(x, y); - qglTexCoord2f(1, 0); - qglVertex2f(x + w, y); - qglTexCoord2f(1, 1); - qglVertex2f(x + w, y + h); - qglTexCoord2f(0, 1); - qglVertex2f(x, y + h); - qglEnd(); + + Vector4Set(quadVerts[0], x, y, 0, 1); + Vector4Set(quadVerts[1], x + w, y, 0, 1); + Vector4Set(quadVerts[2], x + w, y + h, 0, 1); + Vector4Set(quadVerts[3], x, y + h, 0, 1); + + RB_InstantQuad(quadVerts); } qglFinish(); diff --git a/code/renderergl1/tr_cmds.c b/code/renderergl1/tr_cmds.c index 7d290b127..e178b0c9d 100644 --- a/code/renderergl1/tr_cmds.c +++ b/code/renderergl1/tr_cmds.c @@ -281,7 +281,12 @@ void RE_BeginFrame(stereoFrame_t stereoFrame) { // do overdraw measurement // if (r_measureOverdraw->integer) { - if (glConfig.stencilBits < 4) { + if (qglesMajorVersion >= 1) { + ri.Printf(PRINT_WARNING, + "OpenGL ES does not support reading 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"); r_measureOverdraw->modified = qfalse; @@ -349,6 +354,13 @@ void RE_BeginFrame(stereoFrame_t stereoFrame) { ri.Error(ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", 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) { // clear both, front and backbuffer. diff --git a/code/renderergl1/tr_flares.c b/code/renderergl1/tr_flares.c index fceb85414..3dbd9bb17 100644 --- a/code/renderergl1/tr_flares.c +++ b/code/renderergl1/tr_flares.c @@ -455,6 +455,19 @@ void RB_RenderFlares(void) { return; } + if (r_flares->modified) { + if (qglesMajorVersion >= 1) { + ri.Printf(PRINT_WARNING, + "OpenGL ES does not support reading depth to determine if flares are visible\n"); + ri.Cvar_Set("r_flares", "0"); + } + r_flares->modified = qfalse; + } + + if (!r_flares->integer) { + return; + } + if (r_flareCoeff->modified) { R_SetFlareCoeff(); r_flareCoeff->modified = qfalse; diff --git a/code/renderergl1/tr_image.c b/code/renderergl1/tr_image.c index abbe31216..ecd785adb 100644 --- a/code/renderergl1/tr_image.c +++ b/code/renderergl1/tr_image.c @@ -510,6 +510,84 @@ static const byte mipBlendColors[16][4] = { {0, 0, 255, 128}, {255, 0, 0, 128}, {0, 255, 0, 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); + } +} + /* =============== Upload32 @@ -517,14 +595,17 @@ Upload32 =============== */ static void Upload32(unsigned *data, int width, int height, qboolean mipmap, qboolean picmip, qboolean lightMap, - qboolean allowCompression, int *format, int *pUploadWidth, int *pUploadHeight) { + qboolean allowCompression, int *pInternalFormat, int *pUploadWidth, int *pUploadHeight) { int samples; unsigned *scaledBuffer = NULL; unsigned *resampledBuffer = NULL; + unsigned *formatBuffer = NULL; int scaled_width, scaled_height; int i, c; byte *scan; GLenum internalFormat = GL_RGB; + GLenum format = GL_RGBA; + GLenum type = GL_UNSIGNED_BYTE; float rMax = 0, gMax = 0, bMax = 0; // @@ -630,9 +711,9 @@ static void Upload32(unsigned *data, int width, int height, qboolean mipmap, qbo else internalFormat = GL_LUMINANCE; } else { - if (allowCompression && glConfig.textureCompression == TC_S3TC_ARB) { + if (!qglesMajorVersion && allowCompression && glConfig.textureCompression == TC_S3TC_ARB) { internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; - } else if (allowCompression && glConfig.textureCompression == TC_S3TC) { + } else if (!qglesMajorVersion && allowCompression && glConfig.textureCompression == TC_S3TC) { internalFormat = GL_RGB4_S3TC; } else if (r_texturebits->integer == 16) { internalFormat = GL_RGB5; @@ -660,14 +741,69 @@ static void Upload32(unsigned *data, int width, int height, qboolean mipmap, qbo } } + // Convert image data format for OpenGL ES + if (qglesMajorVersion >= 1) { + switch (internalFormat) { + case GL_LUMINANCE: + case GL_LUMINANCE8: + internalFormat = GL_LUMINANCE; + format = GL_LUMINANCE; + type = GL_UNSIGNED_BYTE; + break; + case GL_LUMINANCE_ALPHA: + case GL_LUMINANCE8_ALPHA8: + internalFormat = GL_LUMINANCE_ALPHA; + format = GL_LUMINANCE_ALPHA; + type = GL_UNSIGNED_BYTE; + break; + case GL_RGB: + case GL_RGB8: + internalFormat = GL_RGB; + format = GL_RGB; + type = GL_UNSIGNED_BYTE; + break; + case GL_RGB5: + internalFormat = GL_RGB; + format = GL_RGB; + type = GL_UNSIGNED_SHORT_5_6_5; + break; + case GL_RGBA: + case GL_RGBA8: + internalFormat = GL_RGBA; + format = GL_RGBA; + type = GL_UNSIGNED_BYTE; + break; + case GL_RGBA4: + internalFormat = GL_RGBA; + format = GL_RGBA; + type = GL_UNSIGNED_SHORT_4_4_4_4; + break; + default: + internalFormat = GL_RGBA; + format = GL_RGBA; + type = GL_UNSIGNED_BYTE; + break; + } + } + + if (format != GL_RGBA || type != GL_UNSIGNED_BYTE) { + formatBuffer = ri.Hunk_AllocateTempMemory(sizeof(unsigned) * scaled_width * scaled_height); + } + // copy or resample data as appropriate for first MIP level if ((scaled_width == width) && (scaled_height == height)) { if (!mipmap) { - qglTexImage2D(GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, - data); + if (formatBuffer) { + R_ConvertTextureFormat((byte *)data, scaled_width, scaled_height, format, type, (byte *)formatBuffer); + qglTexImage2D(GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, format, type, + formatBuffer); + } else { + qglTexImage2D(GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, + GL_UNSIGNED_BYTE, data); + } *pUploadWidth = scaled_width; *pUploadHeight = scaled_height; - *format = internalFormat; + *pInternalFormat = internalFormat; goto done; } @@ -692,10 +828,15 @@ static void Upload32(unsigned *data, int width, int height, qboolean mipmap, qbo *pUploadWidth = scaled_width; *pUploadHeight = scaled_height; - *format = internalFormat; + *pInternalFormat = internalFormat; - qglTexImage2D(GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, - scaledBuffer); + if (formatBuffer) { + R_ConvertTextureFormat((byte *)scaledBuffer, scaled_width, scaled_height, format, type, (byte *)formatBuffer); + qglTexImage2D(GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, format, type, formatBuffer); + } else { + qglTexImage2D(GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, + scaledBuffer); + } if (mipmap) { int miplevel; @@ -715,8 +856,15 @@ static void Upload32(unsigned *data, int width, int height, qboolean mipmap, qbo R_BlendOverTexture((byte *)scaledBuffer, scaled_width * scaled_height, mipBlendColors[miplevel]); } - qglTexImage2D(GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, - GL_UNSIGNED_BYTE, scaledBuffer); + if (formatBuffer) { + R_ConvertTextureFormat((byte *)scaledBuffer, scaled_width, scaled_height, format, type, + (byte *)formatBuffer); + qglTexImage2D(GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, format, type, + formatBuffer); + } else { + qglTexImage2D(GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, + GL_UNSIGNED_BYTE, scaledBuffer); + } } } done: @@ -738,6 +886,8 @@ static void Upload32(unsigned *data, int width, int height, qboolean mipmap, qbo GL_CheckErrors(); + if (formatBuffer != 0) + ri.Hunk_FreeTempMemory(formatBuffer); if (scaledBuffer != 0) ri.Hunk_FreeTempMemory(scaledBuffer); if (resampledBuffer != 0) diff --git a/code/renderergl1/tr_init.c b/code/renderergl1/tr_init.c index f61b828a1..cdc44d228 100644 --- a/code/renderergl1/tr_init.c +++ b/code/renderergl1/tr_init.c @@ -343,19 +343,42 @@ Return value must be freed with ri.Hunk_FreeTempMemory() */ static byte *RB_ReadPixels(int x, int y, int width, int height, size_t *offset, int *padlen) { byte *buffer, *bufstart; - int padwidth, linelen; - GLint packAlign = 0; + 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); + 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; @@ -645,26 +668,39 @@ RB_TakeVideoFrameCmd 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 = 0; + int yin, xin, xout; + GLint packAlign, format; 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, GL_UNSIGNED_BYTE, cBuf); + qglReadPixels(0, 0, cmd->width, cmd->height, format, GL_UNSIGNED_BYTE, cBuf); memcount = padwidth * cmd->height; @@ -673,7 +709,21 @@ const void *RB_TakeVideoFrameCmd(const void *data) { R_GammaCorrect(cBuf, memcount); if (cmd->motionJpeg) { - memcount = RE_SaveJPGToBuffer(cmd->encodeBuffer, linelen * cmd->height, r_aviMotionJpegQuality->integer, + // 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); } else { @@ -691,7 +741,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/renderergl1/tr_local.h b/code/renderergl1/tr_local.h index 813da9177..3d79bc852 100644 --- a/code/renderergl1/tr_local.h +++ b/code/renderergl1/tr_local.h @@ -1141,6 +1141,7 @@ void R_InitFogTable(void); float R_FogFactor(float s, float t); void R_InitImages(void); void R_DeleteTextures(void); +void R_ConvertTextureFormat(const byte *in, int width, int height, GLenum format, GLenum type, byte *out); int R_SumOfUsedImages(void); void R_InitSkins(void); skin_t *R_GetSkinByHandle(qhandle_t hSkin); @@ -1173,9 +1174,11 @@ typedef struct stageVars { vec2_t texcoords[NUM_TEXTURE_BUNDLES][SHADER_MAX_VERTEXES]; } stageVars_t; +// xyz index SHADER_MAX_VERTEXES-1 is used to check for overflow +// xyz index >= SHADER_MAX_VERTEXES are used for DrawNormals() and RB_ShadowTessEnd() typedef struct shaderCommands_s { glIndex_t indexes[SHADER_MAX_INDEXES] QALIGN(16); - vec4_t xyz[SHADER_MAX_VERTEXES] QALIGN(16); + vec4_t xyz[SHADER_MAX_VERTEXES * 2] QALIGN(16); vec4_t normal[SHADER_MAX_VERTEXES] QALIGN(16); vec2_t texCoords[SHADER_MAX_VERTEXES][2] QALIGN(16); color4ub_t vertexColors[SHADER_MAX_VERTEXES] QALIGN(16); @@ -1218,6 +1221,9 @@ void RB_StageIteratorLightmappedMultitexture(void); void RB_AddQuadStamp(vec3_t origin, vec3_t left, vec3_t up, byte *color); void RB_AddQuadStampExt(vec3_t origin, vec3_t left, vec3_t up, byte *color, float s1, float t1, float s2, float t2); +void RB_InstantQuad(vec4_t quadVerts[4]); +void RB_InstantQuad2(vec4_t quadVerts[4], vec2_t texCoords[4]); + void RB_ShowImages(void); /* diff --git a/code/renderergl1/tr_main.c b/code/renderergl1/tr_main.c index 7c18facb1..b82cb4fa5 100644 --- a/code/renderergl1/tr_main.c +++ b/code/renderergl1/tr_main.c @@ -1229,29 +1229,37 @@ R_DebugPolygon ================ */ static void R_DebugPolygon(int color, int numPoints, float *points) { - int i; + if (numPoints < 3) { + ri.Printf(PRINT_WARNING, "Debug polygon with color 0x%08X only has %d points (must have at least 3)\n", color, + numPoints); + return; + } GL_State(GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); + qglDisableClientState(GL_COLOR_ARRAY); + qglDisableClientState(GL_TEXTURE_COORD_ARRAY); - // draw solid shade + qglVertexPointer(3, GL_FLOAT, 0, points); - qglColor3f(color & 1, (color >> 1) & 1, (color >> 2) & 1); - qglBegin(GL_POLYGON); - for (i = 0; i < numPoints; i++) { - qglVertex3fv(points + i * 3); + if (qglLockArraysEXT) { + qglLockArraysEXT(0, numPoints); + GLimp_LogComment("glLockArraysEXT\n"); } - qglEnd(); + + // draw solid shade + qglColor3f(color & 1, (color >> 1) & 1, (color >> 2) & 1); + qglDrawArrays(GL_TRIANGLE_FAN, 0, numPoints); // draw wireframe outline - GL_State(GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); qglDepthRange(0, 0); qglColor3f(1, 1, 1); - qglBegin(GL_POLYGON); - for (i = 0; i < numPoints; i++) { - qglVertex3fv(points + i * 3); - } - qglEnd(); + qglDrawArrays(GL_LINE_LOOP, 0, numPoints); qglDepthRange(0, 1); + + if (qglUnlockArraysEXT) { + qglUnlockArraysEXT(); + GLimp_LogComment("glUnlockArraysEXT\n"); + } } /* diff --git a/code/renderergl1/tr_shade.c b/code/renderergl1/tr_shade.c index c4b784c83..feabf4843 100644 --- a/code/renderergl1/tr_shade.c +++ b/code/renderergl1/tr_shade.c @@ -164,6 +164,11 @@ void R_DrawElements(int numIndexes, const glIndex_t *indexes) { } } + // OpenGL ES doesn't support glBegin, force glDrawElements + if (qglesMajorVersion >= 1) { + primitives = 2; + } + if (primitives == 2) { qglDrawElements(GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, indexes); return; @@ -239,15 +244,31 @@ Draws triangle outlines for debugging ================ */ static void DrawTris(shaderCommands_t *input) { + glIndex_t lineIndexes[SHADER_MAX_INDEXES * 2]; + int i, numTris; + GL_Bind(tr.whiteImage); qglColor3f(1, 1, 1); - GL_State(GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE); + GL_State(GLS_DEPTHMASK_TRUE); qglDepthRange(0, 0); qglDisableClientState(GL_COLOR_ARRAY); qglDisableClientState(GL_TEXTURE_COORD_ARRAY); + // convert triangles to lines + numTris = tess.numIndexes / 3; + for (i = 0; i < numTris; i++) { + lineIndexes[i * 6 + 0] = input->indexes[i * 3 + 0]; + lineIndexes[i * 6 + 1] = input->indexes[i * 3 + 1]; + + lineIndexes[i * 6 + 2] = input->indexes[i * 3 + 1]; + lineIndexes[i * 6 + 3] = input->indexes[i * 3 + 2]; + + lineIndexes[i * 6 + 4] = input->indexes[i * 3 + 2]; + lineIndexes[i * 6 + 5] = input->indexes[i * 3 + 0]; + } + qglVertexPointer(3, GL_FLOAT, 16, input->xyz); // padded for SIMD if (qglLockArraysEXT) { @@ -255,7 +276,7 @@ static void DrawTris(shaderCommands_t *input) { GLimp_LogComment("glLockArraysEXT\n"); } - R_DrawElements(input->numIndexes, input->indexes); + qglDrawElements(GL_LINES, numTris * 6, GL_INDEX_TYPE, lineIndexes); if (qglUnlockArraysEXT) { qglUnlockArraysEXT(); @@ -272,23 +293,41 @@ Draws vertex normals for debugging ================ */ static void DrawNormals(shaderCommands_t *input) { + glIndex_t lineIndexes[SHADER_MAX_INDEXES * 2]; int i; - vec3_t temp; GL_Bind(tr.whiteImage); qglColor3f(1, 1, 1); qglDepthRange(0, 0); // never occluded - GL_State(GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE); + GL_State(GLS_DEPTHMASK_TRUE); + + qglDisableClientState(GL_COLOR_ARRAY); + qglDisableClientState(GL_TEXTURE_COORD_ARRAY); - qglBegin(GL_LINES); for (i = 0; i < input->numVertexes; i++) { - qglVertex3fv(input->xyz[i]); - VectorMA(input->xyz[i], 2, input->normal[i], temp); - qglVertex3fv(temp); + VectorMA(input->xyz[i], 2, input->normal[i], input->xyz[i + input->numVertexes]); + + lineIndexes[i * 2] = i; + lineIndexes[i * 2 + 1] = i + input->numVertexes; } - qglEnd(); + qglVertexPointer(3, GL_FLOAT, 16, input->xyz); // padded for SIMD + + if (qglLockArraysEXT) { + qglLockArraysEXT(0, input->numVertexes * 2); + GLimp_LogComment("glLockArraysEXT\n"); + } + + qglDrawElements(GL_LINES, input->numVertexes * 2, GL_INDEX_TYPE, lineIndexes); + + if (qglUnlockArraysEXT) { + qglUnlockArraysEXT(); + GLimp_LogComment("glUnlockArraysEXT\n"); + } qglDepthRange(0, 1); + + // FIXME: kind of ugly to have to clear this to avoid overflow detection. + memset(input->xyz[SHADER_MAX_VERTEXES - 1], 0, sizeof(input->xyz[0])); } /* diff --git a/code/renderergl1/tr_shadows.c b/code/renderergl1/tr_shadows.c index e560910b9..05c4b5bec 100644 --- a/code/renderergl1/tr_shadows.c +++ b/code/renderergl1/tr_shadows.c @@ -43,7 +43,8 @@ typedef struct { static edgeDef_t edgeDefs[SHADER_MAX_VERTEXES][MAX_EDGE_DEFS]; static int numEdgeDefs[SHADER_MAX_VERTEXES]; static int facing[SHADER_MAX_INDEXES / 3]; -static vec3_t shadowXyz[SHADER_MAX_VERTEXES]; +static glIndex_t shadowIndexes[SHADER_MAX_VERTEXES * MAX_EDGE_DEFS * 6]; +static int numShadowIndexes; static void R_AddEdgeDef(int i1, int i2, int facing) { int c; @@ -58,7 +59,7 @@ static void R_AddEdgeDef(int i1, int i2, int facing) { numEdgeDefs[i1]++; } -static void R_RenderShadowEdges(void) { +static void R_CalculateShadowEdges(void) { int i; #if 0 @@ -67,6 +68,8 @@ static void R_RenderShadowEdges(void) { // dumb way -- render every triangle's edges numTris = tess.numIndexes / 3; + numShadowIndexes = 0; + for ( i = 0 ; i < numTris ; i++ ) { int i1, i2, i3; @@ -78,15 +81,16 @@ static void R_RenderShadowEdges(void) { i2 = tess.indexes[ i*3 + 1 ]; i3 = tess.indexes[ i*3 + 2 ]; + // FIXME: Convert to using shadowIndexes qglBegin( GL_TRIANGLE_STRIP ); qglVertex3fv( tess.xyz[ i1 ] ); - qglVertex3fv( shadowXyz[ i1 ] ); + qglVertex3fv( tess.xyz[ i1 + tess.numVertexes ] ); qglVertex3fv( tess.xyz[ i2 ] ); - qglVertex3fv( shadowXyz[ i2 ] ); + qglVertex3fv( tess.xyz[ i2 + tess.numVertexes ] ); qglVertex3fv( tess.xyz[ i3 ] ); - qglVertex3fv( shadowXyz[ i3 ] ); + qglVertex3fv( tess.xyz[ i3 + tess.numVertexes ] ); qglVertex3fv( tess.xyz[ i1 ] ); - qglVertex3fv( shadowXyz[ i1 ] ); + qglVertex3fv( tess.xyz[ i1 + tess.numVertexes ] ); qglEnd(); } #else @@ -96,6 +100,8 @@ static void R_RenderShadowEdges(void) { int c_edges, c_rejected; int hit[2]; + numShadowIndexes = 0; + // an edge is NOT a silhouette edge if its face doesn't face the light, // or if it has a reverse paired edge that also faces the light. // A well behaved polyhedron would have exactly two faces for each edge, @@ -122,14 +128,15 @@ static void R_RenderShadowEdges(void) { } // if it doesn't share the edge with another front facing - // triangle, it is a sil edge + // triangle, it is a silhouette edge if (hit[1] == 0) { - qglBegin(GL_TRIANGLE_STRIP); - qglVertex3fv(tess.xyz[i]); - qglVertex3fv(shadowXyz[i]); - qglVertex3fv(tess.xyz[i2]); - qglVertex3fv(shadowXyz[i2]); - qglEnd(); + shadowIndexes[numShadowIndexes++] = i; + shadowIndexes[numShadowIndexes++] = i + tess.numVertexes; + shadowIndexes[numShadowIndexes++] = i2; + + shadowIndexes[numShadowIndexes++] = i2; + shadowIndexes[numShadowIndexes++] = i + tess.numVertexes; + shadowIndexes[numShadowIndexes++] = i2 + tess.numVertexes; c_edges++; } else { c_rejected++; @@ -165,7 +172,7 @@ void RB_ShadowTessEnd(void) { // project vertexes away from light direction for (i = 0; i < tess.numVertexes; i++) { - VectorMA(tess.xyz[i], -512, lightDir, shadowXyz[i]); + VectorMA(tess.xyz[i], -512, lightDir, tess.xyz[i + tess.numVertexes]); } // decide which triangles face the light @@ -205,10 +212,22 @@ void RB_ShadowTessEnd(void) { // draw the silhouette edges + R_CalculateShadowEdges(); + GL_Bind(tr.whiteImage); GL_State(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO); qglColor3f(0.2f, 0.2f, 0.2f); + qglDisableClientState(GL_TEXTURE_COORD_ARRAY); + qglDisableClientState(GL_COLOR_ARRAY); + + qglVertexPointer(3, GL_FLOAT, 16, tess.xyz); // padded for SIMD + + if (qglLockArraysEXT) { + qglLockArraysEXT(0, tess.numVertexes * 2); + GLimp_LogComment("glLockArraysEXT\n"); + } + // don't write to the color buffer qglGetBooleanv(GL_COLOR_WRITEMASK, rgba); qglColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); @@ -219,15 +238,23 @@ void RB_ShadowTessEnd(void) { GL_Cull(CT_BACK_SIDED); qglStencilOp(GL_KEEP, GL_KEEP, GL_INCR); - R_RenderShadowEdges(); + R_DrawElements(numShadowIndexes, shadowIndexes); GL_Cull(CT_FRONT_SIDED); qglStencilOp(GL_KEEP, GL_KEEP, GL_DECR); - R_RenderShadowEdges(); + R_DrawElements(numShadowIndexes, shadowIndexes); + + if (qglUnlockArraysEXT) { + qglUnlockArraysEXT(); + GLimp_LogComment("glUnlockArraysEXT\n"); + } // reenable writing to the color buffer qglColorMask(rgba[0], rgba[1], rgba[2], rgba[3]); + + // FIXME: kind of ugly to have to clear this to avoid overflow detection. + memset(tess.xyz[SHADER_MAX_VERTEXES - 1], 0, sizeof(tess.xyz[0])); } /* @@ -241,6 +268,8 @@ overlap and double darken. ================= */ void RB_ShadowFinish(void) { + vec4_t quadVerts[4]; + if (r_shadows->integer != 2) { return; } @@ -260,15 +289,12 @@ void RB_ShadowFinish(void) { qglColor3f(0.6f, 0.6f, 0.6f); GL_State(GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO); - // qglColor3f( 1, 0, 0 ); - // GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + Vector4Set(quadVerts[0], -100, 100, -10, 0); + Vector4Set(quadVerts[1], 100, 100, -10, 0); + Vector4Set(quadVerts[2], 100, -100, -10, 0); + Vector4Set(quadVerts[3], -100, -100, -10, 0); - qglBegin(GL_QUADS); - qglVertex3f(-100, 100, -10); - qglVertex3f(100, 100, -10); - qglVertex3f(100, -100, -10); - qglVertex3f(-100, -100, -10); - qglEnd(); + RB_InstantQuad(quadVerts); qglColor4f(1, 1, 1, 1); qglDisable(GL_STENCIL_TEST); diff --git a/code/renderergl1/tr_sky.c b/code/renderergl1/tr_sky.c index 623351c61..c4f4a44f4 100644 --- a/code/renderergl1/tr_sky.c +++ b/code/renderergl1/tr_sky.c @@ -312,24 +312,75 @@ static int sky_texorder[6] = {0, 2, 1, 3, 4, 5}; static vec3_t s_skyPoints[SKY_SUBDIVISIONS + 1][SKY_SUBDIVISIONS + 1]; static float s_skyTexCoords[SKY_SUBDIVISIONS + 1][SKY_SUBDIVISIONS + 1][2]; +// NOTE: This reuses the tess structure out of convenience but it doesn't use the shader system. static void DrawSkySide(struct image_s *image, const int mins[2], const int maxs[2]) { int s, t; + int vertexStart = tess.numVertexes; + int indexStart = tess.numIndexes; + int tHeight, sWidth; - GL_Bind(image); + tHeight = maxs[1] - mins[1] + 1; + sWidth = maxs[0] - mins[0] + 1; - for (t = mins[1] + HALF_SKY_SUBDIVISIONS; t < maxs[1] + HALF_SKY_SUBDIVISIONS; t++) { - qglBegin(GL_TRIANGLE_STRIP); + GL_Bind(image); + for (t = mins[1] + HALF_SKY_SUBDIVISIONS; t <= maxs[1] + HALF_SKY_SUBDIVISIONS; t++) { for (s = mins[0] + HALF_SKY_SUBDIVISIONS; s <= maxs[0] + HALF_SKY_SUBDIVISIONS; s++) { - qglTexCoord2fv(s_skyTexCoords[t][s]); - qglVertex3fv(s_skyPoints[t][s]); + VectorCopy(s_skyPoints[t][s], tess.xyz[tess.numVertexes]); + + tess.texCoords[tess.numVertexes][0][0] = s_skyTexCoords[t][s][0]; + tess.texCoords[tess.numVertexes][0][1] = s_skyTexCoords[t][s][1]; + + tess.numVertexes++; + + if (tess.numVertexes >= SHADER_MAX_VERTEXES) { + ri.Error(ERR_DROP, "SHADER_MAX_VERTEXES hit in DrawSkySide()"); + } + } + } - qglTexCoord2fv(s_skyTexCoords[t + 1][s]); - qglVertex3fv(s_skyPoints[t + 1][s]); + for (t = 0; t < tHeight - 1; t++) { + for (s = 0; s < sWidth - 1; s++) { + if (tess.numIndexes + 6 >= SHADER_MAX_INDEXES) { + ri.Error(ERR_DROP, "SHADER_MAX_INDEXES hit in DrawSkySide()"); + } + + tess.indexes[tess.numIndexes] = vertexStart + s + t * (sWidth); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + (t + 1) * (sWidth); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * (sWidth); + tess.numIndexes++; + + tess.indexes[tess.numIndexes] = vertexStart + s + (t + 1) * (sWidth); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + (t + 1) * (sWidth); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * (sWidth); + tess.numIndexes++; } + } - qglEnd(); + qglDisableClientState(GL_COLOR_ARRAY); + qglEnableClientState(GL_TEXTURE_COORD_ARRAY); + + qglTexCoordPointer(2, GL_FLOAT, 16, tess.texCoords[0][0]); + qglVertexPointer(3, GL_FLOAT, 16, tess.xyz); + + if (qglLockArraysEXT) { + qglLockArraysEXT(0, tess.numVertexes); + GLimp_LogComment("glLockArraysEXT\n"); + } + + R_DrawElements(tess.numIndexes - indexStart, tess.indexes + indexStart); + + if (qglUnlockArraysEXT) { + qglUnlockArraysEXT(); + GLimp_LogComment("glUnlockArraysEXT\n"); } + + tess.numVertexes = vertexStart; + tess.numIndexes = indexStart; } static void DrawSkyBox(shader_t *shader) { diff --git a/code/renderergl1/tr_surface.c b/code/renderergl1/tr_surface.c index 25ee98e56..9fd41a769 100644 --- a/code/renderergl1/tr_surface.c +++ b/code/renderergl1/tr_surface.c @@ -277,6 +277,8 @@ static void RB_SurfaceBeam(void) { vec3_t direction, normalized_direction; vec3_t start_points[NUM_BEAM_SEGS], end_points[NUM_BEAM_SEGS]; vec3_t oldorigin, origin; + vec3_t verts[(NUM_BEAM_SEGS + 1) * 2]; + glIndex_t indexes[(NUM_BEAM_SEGS) * 6]; e = &backEnd.currentEntity->e; @@ -301,7 +303,6 @@ static void RB_SurfaceBeam(void) { for (i = 0; i < NUM_BEAM_SEGS; i++) { RotatePointAroundVector(start_points[i], normalized_direction, perpvec, (360.0 / NUM_BEAM_SEGS) * i); - // VectorAdd( start_points[i], origin, start_points[i] ); VectorAdd(start_points[i], direction, end_points[i]); } @@ -311,12 +312,26 @@ static void RB_SurfaceBeam(void) { qglColor3f(1, 0, 0); - qglBegin(GL_TRIANGLE_STRIP); for (i = 0; i <= NUM_BEAM_SEGS; i++) { - qglVertex3fv(start_points[i % NUM_BEAM_SEGS]); - qglVertex3fv(end_points[i % NUM_BEAM_SEGS]); + VectorCopy(start_points[i % NUM_BEAM_SEGS], verts[i * 2]); + VectorCopy(end_points[i % NUM_BEAM_SEGS], verts[i * 2 + 1]); } - qglEnd(); + + for (i = 0; i < NUM_BEAM_SEGS; i++) { + indexes[i * 6 + 0] = i * 2; + indexes[i * 6 + 1] = i * 2 + 1; + indexes[i * 6 + 2] = (i + 1) * 2; + + indexes[i * 6 + 3] = i * 2 + 1; + indexes[i * 6 + 4] = (i + 1) * 2 + 1; + indexes[i * 6 + 5] = (i + 1) * 2; + } + + qglDisableClientState(GL_COLOR_ARRAY); + qglDisableClientState(GL_TEXTURE_COORD_ARRAY); + + qglVertexPointer(3, GL_FLOAT, 0, verts); + qglDrawElements(GL_TRIANGLES, NUM_BEAM_SEGS * 6, GL_INDEX_TYPE, indexes); } //================================================================================ @@ -985,20 +1000,28 @@ Draws x/y/z lines from the origin for orientation debugging =================== */ static void RB_SurfaceAxis(void) { + vec3_t verts[6] = { + {0, 0, 0}, {16, 0, 0}, + {0, 0, 0}, {0, 16, 0}, + {0, 0, 0}, {0, 0, 16} + }; + color4ub_t colors[6] = { + {255, 0, 0, 255}, {255, 0, 0, 255}, + {0, 255, 0, 255}, {0, 255, 0, 255}, + {0, 0, 255, 255}, {0, 0, 255, 255} + }; + GL_Bind(tr.whiteImage); GL_State(GLS_DEFAULT); qglLineWidth(3); - qglBegin(GL_LINES); - qglColor3f(1, 0, 0); - qglVertex3f(0, 0, 0); - qglVertex3f(16, 0, 0); - qglColor3f(0, 1, 0); - qglVertex3f(0, 0, 0); - qglVertex3f(0, 16, 0); - qglColor3f(0, 0, 1); - qglVertex3f(0, 0, 0); - qglVertex3f(0, 0, 16); - qglEnd(); + + qglEnableClientState(GL_COLOR_ARRAY); + qglDisableClientState(GL_TEXTURE_COORD_ARRAY); + + qglVertexPointer(3, GL_FLOAT, 0, verts); + qglColorPointer(4, GL_UNSIGNED_BYTE, 0, colors); + qglDrawArrays(GL_LINES, 0, 6); + qglLineWidth(1); } diff --git a/code/sdl/sdl_glimp.c b/code/sdl/sdl_glimp.c index fd40f1874..bc084534c 100644 --- a/code/sdl/sdl_glimp.c +++ b/code/sdl/sdl_glimp.c @@ -235,6 +235,29 @@ static void APIENTRY GLimp_GLES_PolygonMode(GLenum face, GLenum mode) { // unsupported } +static void APIENTRY GLimp_GLES_Color3f(GLfloat red, GLfloat green, GLfloat blue) { + qglColor4f(red, green, blue, 1.0f); +} + +static void APIENTRY GLimp_GLES_Color4ubv(const GLubyte *v) { + qglColor4ub(v[0], v[1], v[2], v[3]); +} + +static void APIENTRY GLimp_GLES_ClipPlane(GLenum plane, const GLdouble *equation) { + GLfloat f[4] = {(GLfloat)equation[0], (GLfloat)equation[1], (GLfloat)equation[2], (GLfloat)equation[3]}; + qglClipPlanef(plane, f); +} + +static void APIENTRY GLimp_GLES_Frustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, + GLdouble near_val, GLdouble far_val) { + qglFrustumf(left, right, bottom, top, near_val, far_val); +} + +static void APIENTRY GLimp_GLES_Ortho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, + GLdouble near_val, GLdouble far_val) { + qglOrthof(left, right, bottom, top, near_val, far_val); +} + /* =============== GLimp_GetProcAddresses @@ -294,8 +317,16 @@ static qboolean GLimp_GetProcAddresses(qboolean fixedFunction) { QGL_1_1_FIXED_FUNCTION_PROCS; QGL_ES_1_1_PROCS; QGL_ES_1_1_FIXED_FUNCTION_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; + qglClipPlane = GLimp_GLES_ClipPlane; + qglColor3f = GLimp_GLES_Color3f; + qglColor4ubv = GLimp_GLES_Color4ubv; + qglDepthRange = GLimp_GLES_DepthRange; + qglDrawBuffer = GLimp_GLES_DrawBuffer; + qglFrustum = GLimp_GLES_Frustum; + qglOrtho = GLimp_GLES_Ortho; + qglPolygonMode = GLimp_GLES_PolygonMode; } else { Com_Error(ERR_FATAL, "Unsupported OpenGL Version (%s), OpenGL 1.1 is required", version); } @@ -546,10 +577,35 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool numContexts++; } } else { + int profileMask; + qboolean preferOpenGLES; + + SDL_GL_ResetAttributes(); + SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profileMask); + + preferOpenGLES = (r_preferOpenGLES->integer == 1 || + (r_preferOpenGLES->integer == -1 && profileMask == SDL_GL_CONTEXT_PROFILE_ES)); + + if (preferOpenGLES) { + // Try OpenGL ES 1.1 first for fixed-function pipeline + contexts[numContexts].profileMask = SDL_GL_CONTEXT_PROFILE_ES; + contexts[numContexts].majorVersion = 1; + contexts[numContexts].minorVersion = 1; + numContexts++; + } + contexts[numContexts].profileMask = 0; contexts[numContexts].majorVersion = 1; contexts[numContexts].minorVersion = 1; numContexts++; + + if (!preferOpenGLES) { + // Try OpenGL ES 1.1 as fallback + contexts[numContexts].profileMask = SDL_GL_CONTEXT_PROFILE_ES; + contexts[numContexts].majorVersion = 1; + contexts[numContexts].minorVersion = 1; + numContexts++; + } } for (i = 0; i < 16; i++) { @@ -862,7 +918,7 @@ static void GLimp_InitExtensions(qboolean fixedFunction) { if (fixedFunction) { // GL_EXT_texture_env_add glConfig.textureEnvAddAvailable = qfalse; - if (SDL_GL_ExtensionSupported("GL_EXT_texture_env_add")) { + if (QGLES_VERSION_ATLEAST(1, 0) || SDL_GL_ExtensionSupported("GL_EXT_texture_env_add")) { if (r_ext_texture_env_add->integer) { glConfig.textureEnvAddAvailable = qtrue; ri.Printf(PRINT_ALL, "...using GL_EXT_texture_env_add\n"); @@ -878,11 +934,17 @@ static void GLimp_InitExtensions(qboolean fixedFunction) { qglMultiTexCoord2fARB = NULL; qglActiveTextureARB = NULL; qglClientActiveTextureARB = NULL; - if (SDL_GL_ExtensionSupported("GL_ARB_multitexture")) { + if (QGLES_VERSION_ATLEAST(1, 0) || SDL_GL_ExtensionSupported("GL_ARB_multitexture")) { if (r_ext_multitexture->value) { - *(void **)(&qglMultiTexCoord2fARB) = SDL_GL_GetProcAddress("glMultiTexCoord2fARB"); - *(void **)(&qglActiveTextureARB) = SDL_GL_GetProcAddress("glActiveTextureARB"); - *(void **)(&qglClientActiveTextureARB) = SDL_GL_GetProcAddress("glClientActiveTextureARB"); + if (QGLES_VERSION_ATLEAST(1, 0)) { + qglMultiTexCoord2fARB = NULL; + *(void **)(&qglActiveTextureARB) = SDL_GL_GetProcAddress("glActiveTexture"); + *(void **)(&qglClientActiveTextureARB) = SDL_GL_GetProcAddress("glClientActiveTexture"); + } else { + *(void **)(&qglMultiTexCoord2fARB) = SDL_GL_GetProcAddress("glMultiTexCoord2fARB"); + *(void **)(&qglActiveTextureARB) = SDL_GL_GetProcAddress("glActiveTextureARB"); + *(void **)(&qglClientActiveTextureARB) = SDL_GL_GetProcAddress("glClientActiveTextureARB"); + } if (qglActiveTextureARB) { GLint glint = 0; diff --git a/docs/ioq3-readme.md b/docs/ioq3-readme.md index 476152541..73bd7f645 100644 --- a/docs/ioq3-readme.md +++ b/docs/ioq3-readme.md @@ -159,6 +159,29 @@ set using command line arguments: ioquake3 +set cl_renderer opengl2 +set r_preferOpenGLES 1 +# OpenGL ES support + +The opengl2 renderer supports OpenGL ES 2.0/3.0. The opengl1 renderer supports +OpenGL 1.1+ and OpenGL ES 1.1. + +If you build on a platform that uses OpenGL ES (such as Raspberry Pi), run the +game using `+set cl_renderer opengl1` for the fixed-function renderer or +`+set cl_renderer opengl2` for the shader-based renderer. + +The `r_preferOpenGLES` 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, e.g. `+set r_preferOpenGLES 1`. + +When using OpenGL ES the opengl1 renderer does not support `r_flares`[1], +`r_measureOverdraw`[2], `r_anaglyphMode`[3], `r_stereoEnabled`[3], +`r_drawBuffer GL_FRONT`[3], and `r_primitives 1/3`[4]. + +* [1] Requires glReadPixels GL_DEPTH_COMPONENT +* [2] Requires glReadPixels GL_STENCIL_INDEX +* [3] Requires glDrawBuffer +* [4] Requires glBegin + + # Console ## New cvars @@ -324,6 +347,9 @@ set using command line arguments: cl_aviMotionJpeg is enabled r_mode -2 - This new video mode automatically uses the desktop resolution. + r_preferOpenGLES - Controls whether to use OpenGL API or + OpenGL ES API. Set to -1 for auto (default), + 0 for OpenGL, and 1 for OpenGL ES. ``` ## New commands