From 9487654b9dd89200a7d28912effe717651387fa3 Mon Sep 17 00:00:00 2001 From: James Canete Date: Fri, 26 Oct 2012 01:23:06 +0000 Subject: Added Rend2, an alternate renderer. (Bug #4358) --- src/rend2/qgl.h | 755 +++++++++ src/rend2/tr_animation.c | 658 ++++++++ src/rend2/tr_backend.c | 1912 +++++++++++++++++++++++ src/rend2/tr_bsp.c | 3321 +++++++++++++++++++++++++++++++++++++++ src/rend2/tr_cmds.c | 666 ++++++++ src/rend2/tr_curve.c | 806 ++++++++++ src/rend2/tr_extensions.c | 682 ++++++++ src/rend2/tr_extramath.c | 233 +++ src/rend2/tr_extramath.h | 101 ++ src/rend2/tr_extratypes.h | 43 + src/rend2/tr_fbo.c | 843 ++++++++++ src/rend2/tr_fbo.h | 64 + src/rend2/tr_flares.c | 532 +++++++ src/rend2/tr_font.c | 555 +++++++ src/rend2/tr_glsl.c | 2825 +++++++++++++++++++++++++++++++++ src/rend2/tr_image.c | 3431 ++++++++++++++++++++++++++++++++++++++++ src/rend2/tr_image_bmp.c | 243 +++ src/rend2/tr_image_jpg.c | 441 ++++++ src/rend2/tr_image_pcx.c | 179 +++ src/rend2/tr_image_png.c | 2490 +++++++++++++++++++++++++++++ src/rend2/tr_image_tga.c | 324 ++++ src/rend2/tr_init.c | 1560 ++++++++++++++++++ src/rend2/tr_light.c | 455 ++++++ src/rend2/tr_local.h | 2856 +++++++++++++++++++++++++++++++++ src/rend2/tr_main.c | 2878 ++++++++++++++++++++++++++++++++++ src/rend2/tr_marks.c | 466 ++++++ src/rend2/tr_mesh.c | 405 +++++ src/rend2/tr_model.c | 1580 +++++++++++++++++++ src/rend2/tr_model_iqm.c | 1058 +++++++++++++ src/rend2/tr_noise.c | 93 ++ src/rend2/tr_postprocess.c | 490 ++++++ src/rend2/tr_postprocess.h | 33 + src/rend2/tr_scene.c | 539 +++++++ src/rend2/tr_shade.c | 1818 +++++++++++++++++++++ src/rend2/tr_shade_calc.c | 1339 ++++++++++++++++ src/rend2/tr_shader.c | 3728 ++++++++++++++++++++++++++++++++++++++++++++ src/rend2/tr_shadows.c | 343 ++++ src/rend2/tr_sky.c | 955 ++++++++++++ src/rend2/tr_subs.c | 48 + src/rend2/tr_surface.c | 1691 ++++++++++++++++++++ src/rend2/tr_vbo.c | 932 +++++++++++ src/rend2/tr_world.c | 851 ++++++++++ 42 files changed, 45222 insertions(+) create mode 100644 src/rend2/qgl.h create mode 100644 src/rend2/tr_animation.c create mode 100644 src/rend2/tr_backend.c create mode 100644 src/rend2/tr_bsp.c create mode 100644 src/rend2/tr_cmds.c create mode 100644 src/rend2/tr_curve.c create mode 100644 src/rend2/tr_extensions.c create mode 100644 src/rend2/tr_extramath.c create mode 100644 src/rend2/tr_extramath.h create mode 100644 src/rend2/tr_extratypes.h create mode 100644 src/rend2/tr_fbo.c create mode 100644 src/rend2/tr_fbo.h create mode 100644 src/rend2/tr_flares.c create mode 100644 src/rend2/tr_font.c create mode 100644 src/rend2/tr_glsl.c create mode 100644 src/rend2/tr_image.c create mode 100644 src/rend2/tr_image_bmp.c create mode 100644 src/rend2/tr_image_jpg.c create mode 100644 src/rend2/tr_image_pcx.c create mode 100644 src/rend2/tr_image_png.c create mode 100644 src/rend2/tr_image_tga.c create mode 100644 src/rend2/tr_init.c create mode 100644 src/rend2/tr_light.c create mode 100644 src/rend2/tr_local.h create mode 100644 src/rend2/tr_main.c create mode 100644 src/rend2/tr_marks.c create mode 100644 src/rend2/tr_mesh.c create mode 100644 src/rend2/tr_model.c create mode 100644 src/rend2/tr_model_iqm.c create mode 100644 src/rend2/tr_noise.c create mode 100644 src/rend2/tr_postprocess.c create mode 100644 src/rend2/tr_postprocess.h create mode 100644 src/rend2/tr_scene.c create mode 100644 src/rend2/tr_shade.c create mode 100644 src/rend2/tr_shade_calc.c create mode 100644 src/rend2/tr_shader.c create mode 100644 src/rend2/tr_shadows.c create mode 100644 src/rend2/tr_sky.c create mode 100644 src/rend2/tr_subs.c create mode 100644 src/rend2/tr_surface.c create mode 100644 src/rend2/tr_vbo.c create mode 100644 src/rend2/tr_world.c (limited to 'src/rend2') diff --git a/src/rend2/qgl.h b/src/rend2/qgl.h new file mode 100644 index 00000000..6013a87c --- /dev/null +++ b/src/rend2/qgl.h @@ -0,0 +1,755 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +/* +** QGL.H +*/ + +#ifndef __QGL_H__ +#define __QGL_H__ + +#ifdef USE_LOCAL_HEADERS +# include "SDL_opengl.h" +#else +# include +#endif + +extern void (APIENTRYP qglActiveTextureARB) (GLenum texture); +extern void (APIENTRYP qglClientActiveTextureARB) (GLenum texture); +extern void (APIENTRYP qglMultiTexCoord2fARB) (GLenum target, GLfloat s, GLfloat t); + +extern void (APIENTRYP qglLockArraysEXT) (GLint first, GLsizei count); +extern void (APIENTRYP qglUnlockArraysEXT) (void); + +// GL_EXT_draw_range_elements +extern void (APIENTRY * qglDrawRangeElementsEXT) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); + +// GL_EXT_multi_draw_arrays +extern void (APIENTRY * qglMultiDrawArraysEXT) (GLenum, GLint *, GLsizei *, GLsizei); +extern void (APIENTRY * qglMultiDrawElementsEXT) (GLenum, const GLsizei *, GLenum, const GLvoid **, GLsizei); + +// GL_ARB_shading_language_100 +#ifndef GL_ARB_shading_language_100 +#define GL_ARB_shading_language_100 +#define GL_SHADING_LANGUAGE_VERSION_ARB 0x8B8C +#endif + +// GL_ARB_vertex_program +extern void (APIENTRY * qglVertexAttrib4fARB) (GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +extern void (APIENTRY * qglVertexAttrib4fvARB) (GLuint, const GLfloat *); +extern void (APIENTRY * qglVertexAttribPointerARB) (GLuint index, GLint size, GLenum type, GLboolean normalized, + GLsizei stride, const GLvoid * pointer); +extern void (APIENTRY * qglEnableVertexAttribArrayARB) (GLuint index); +extern void (APIENTRY * qglDisableVertexAttribArrayARB) (GLuint index); + +// GL_ARB_vertex_buffer_object +extern void (APIENTRY * qglBindBufferARB) (GLenum target, GLuint buffer); +extern void (APIENTRY * qglDeleteBuffersARB) (GLsizei n, const GLuint * buffers); +extern void (APIENTRY * qglGenBuffersARB) (GLsizei n, GLuint * buffers); +extern GLboolean(APIENTRY * qglIsBufferARB) (GLuint buffer); +extern void (APIENTRY * qglBufferDataARB) (GLenum target, GLsizeiptrARB size, const GLvoid * data, GLenum usage); +extern void (APIENTRY * qglBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid * data); +extern void (APIENTRY * qglGetBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid * data); +extern void (APIENTRY * qglGetBufferParameterivARB) (GLenum target, GLenum pname, GLint * params); +extern void (APIENTRY * qglGetBufferPointervARB) (GLenum target, GLenum pname, GLvoid * *params); + +// GL_ARB_shader_objects +extern void (APIENTRY * qglDeleteObjectARB) (GLhandleARB obj); +extern GLhandleARB(APIENTRY * qglGetHandleARB) (GLenum pname); +extern void (APIENTRY * qglDetachObjectARB) (GLhandleARB containerObj, GLhandleARB attachedObj); +extern GLhandleARB(APIENTRY * qglCreateShaderObjectARB) (GLenum shaderType); +extern void (APIENTRY * qglShaderSourceARB) (GLhandleARB shaderObj, GLsizei count, const GLcharARB * *string, + const GLint * length); +extern void (APIENTRY * qglCompileShaderARB) (GLhandleARB shaderObj); +extern GLhandleARB(APIENTRY * qglCreateProgramObjectARB) (void); +extern void (APIENTRY * qglAttachObjectARB) (GLhandleARB containerObj, GLhandleARB obj); +extern void (APIENTRY * qglLinkProgramARB) (GLhandleARB programObj); +extern void (APIENTRY * qglUseProgramObjectARB) (GLhandleARB programObj); +extern void (APIENTRY * qglValidateProgramARB) (GLhandleARB programObj); +extern void (APIENTRY * qglUniform1fARB) (GLint location, GLfloat v0); +extern void (APIENTRY * qglUniform2fARB) (GLint location, GLfloat v0, GLfloat v1); +extern void (APIENTRY * qglUniform3fARB) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +extern void (APIENTRY * qglUniform4fARB) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +extern void (APIENTRY * qglUniform1iARB) (GLint location, GLint v0); +extern void (APIENTRY * qglUniform2iARB) (GLint location, GLint v0, GLint v1); +extern void (APIENTRY * qglUniform3iARB) (GLint location, GLint v0, GLint v1, GLint v2); +extern void (APIENTRY * qglUniform4iARB) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +extern void (APIENTRY * qglUniform1fvARB) (GLint location, GLsizei count, const GLfloat * value); +extern void (APIENTRY * qglUniform2fvARB) (GLint location, GLsizei count, const GLfloat * value); +extern void (APIENTRY * qglUniform3fvARB) (GLint location, GLsizei count, const GLfloat * value); +extern void (APIENTRY * qglUniform4fvARB) (GLint location, GLsizei count, const GLfloat * value); +extern void (APIENTRY * qglUniform2ivARB) (GLint location, GLsizei count, const GLint * value); +extern void (APIENTRY * qglUniform3ivARB) (GLint location, GLsizei count, const GLint * value); +extern void (APIENTRY * qglUniform4ivARB) (GLint location, GLsizei count, const GLint * value); +extern void (APIENTRY * qglUniformMatrix2fvARB) (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); +extern void (APIENTRY * qglUniformMatrix3fvARB) (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); +extern void (APIENTRY * qglUniformMatrix4fvARB) (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); +extern void (APIENTRY * qglGetObjectParameterfvARB) (GLhandleARB obj, GLenum pname, GLfloat * params); +extern void (APIENTRY * qglGetObjectParameterivARB) (GLhandleARB obj, GLenum pname, GLint * params); +extern void (APIENTRY * qglGetInfoLogARB) (GLhandleARB obj, GLsizei maxLength, GLsizei * length, GLcharARB * infoLog); +extern void (APIENTRY * qglGetAttachedObjectsARB) (GLhandleARB containerObj, GLsizei maxCount, GLsizei * count, + GLhandleARB * obj); +extern GLint(APIENTRY * qglGetUniformLocationARB) (GLhandleARB programObj, const GLcharARB * name); +extern void (APIENTRY * qglGetActiveUniformARB) (GLhandleARB programObj, GLuint index, GLsizei maxIndex, GLsizei * length, + GLint * size, GLenum * type, GLcharARB * name); +extern void (APIENTRY * qglGetUniformfvARB) (GLhandleARB programObj, GLint location, GLfloat * params); +extern void (APIENTRY * qglGetUniformivARB) (GLhandleARB programObj, GLint location, GLint * params); +extern void (APIENTRY * qglGetShaderSourceARB) (GLhandleARB obj, GLsizei maxLength, GLsizei * length, GLcharARB * source); + +// GL_ARB_vertex_shader +extern void (APIENTRY * qglBindAttribLocationARB) (GLhandleARB programObj, GLuint index, const GLcharARB * name); +extern void (APIENTRY * qglGetActiveAttribARB) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei * length, + GLint * size, GLenum * type, GLcharARB * name); +extern GLint(APIENTRY * qglGetAttribLocationARB) (GLhandleARB programObj, const GLcharARB * name); + +// GL_ARB_texture_compression +extern void (APIENTRY * qglCompressedTexImage3DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, + GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +extern void (APIENTRY * qglCompressedTexImage2DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, + GLint border, GLsizei imageSize, const GLvoid *data); +extern void (APIENTRY * qglCompressedTexImage1DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, + GLsizei imageSize, const GLvoid *data); +extern void (APIENTRY * qglCompressedTexSubImage3DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, + GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +extern void (APIENTRY * qglCompressedTexSubImage2DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, + GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +extern void (APIENTRY * qglCompressedTexSubImage1DARB)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, + GLsizei imageSize, const GLvoid *data); +extern void (APIENTRY * qglGetCompressedTexImageARB)(GLenum target, GLint lod, + GLvoid *img); + +// GL_NVX_gpu_memory_info +#ifndef GL_NVX_gpu_memory_info +#define GL_NVX_gpu_memory_info +#define GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX 0x9047 +#define GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX 0x9048 +#define GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX 0x9049 +#define GL_GPU_MEMORY_INFO_EVICTION_COUNT_NVX 0x904A +#define GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX 0x904B +#endif + +// GL_ATI_meminfo +#ifndef GL_ATI_meminfo +#define GL_ATI_meminfo +#define GL_VBO_FREE_MEMORY_ATI 0x87FB +#define GL_TEXTURE_FREE_MEMORY_ATI 0x87FC +#define GL_RENDERBUFFER_FREE_MEMORY_ATI 0x87FD +#endif + +// GL_ARB_texture_float +#ifndef GL_ARB_texture_float +#define GL_ARB_texture_float +#define GL_TEXTURE_RED_TYPE_ARB 0x8C10 +#define GL_TEXTURE_GREEN_TYPE_ARB 0x8C11 +#define GL_TEXTURE_BLUE_TYPE_ARB 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE_ARB 0x8C13 +#define GL_TEXTURE_LUMINANCE_TYPE_ARB 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE_ARB 0x8C15 +#define GL_TEXTURE_DEPTH_TYPE_ARB 0x8C16 +#define GL_UNSIGNED_NORMALIZED_ARB 0x8C17 +#define GL_RGBA32F_ARB 0x8814 +#define GL_RGB32F_ARB 0x8815 +#define GL_ALPHA32F_ARB 0x8816 +#define GL_INTENSITY32F_ARB 0x8817 +#define GL_LUMINANCE32F_ARB 0x8818 +#define GL_LUMINANCE_ALPHA32F_ARB 0x8819 +#define GL_RGBA16F_ARB 0x881A +#define GL_RGB16F_ARB 0x881B +#define GL_ALPHA16F_ARB 0x881C +#define GL_INTENSITY16F_ARB 0x881D +#define GL_LUMINANCE16F_ARB 0x881E +#define GL_LUMINANCE_ALPHA16F_ARB 0x881F +#endif + +#ifndef GL_ARB_half_float_pixel +#define GL_ARB_half_float_pixel +#define GL_HALF_FLOAT_ARB 0x140B +#endif + +// GL_EXT_framebuffer_object +extern GLboolean (APIENTRY * qglIsRenderbufferEXT)(GLuint renderbuffer); +extern void (APIENTRY * qglBindRenderbufferEXT)(GLenum target, GLuint renderbuffer); +extern void (APIENTRY * qglDeleteRenderbuffersEXT)(GLsizei n, const GLuint *renderbuffers); +extern void (APIENTRY * qglGenRenderbuffersEXT)(GLsizei n, GLuint *renderbuffers); +extern void (APIENTRY * qglRenderbufferStorageEXT)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +extern void (APIENTRY * qglGetRenderbufferParameterivEXT)(GLenum target, GLenum pname, GLint *params); +extern GLboolean (APIENTRY * qglIsFramebufferEXT)(GLuint framebuffer); +extern void (APIENTRY * qglBindFramebufferEXT)(GLenum target, GLuint framebuffer); +extern void (APIENTRY * qglDeleteFramebuffersEXT)(GLsizei n, const GLuint *framebuffers); +extern void (APIENTRY * qglGenFramebuffersEXT)(GLsizei n, GLuint *framebuffers); +extern GLenum (APIENTRY * qglCheckFramebufferStatusEXT)(GLenum target); +extern void (APIENTRY * qglFramebufferTexture1DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, + GLint level); +extern void (APIENTRY * qglFramebufferTexture2DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, + GLint level); +extern void (APIENTRY * qglFramebufferTexture3DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, + GLint level, GLint zoffset); +extern void (APIENTRY * qglFramebufferRenderbufferEXT)(GLenum target, GLenum attachment, GLenum renderbuffertarget, + GLuint renderbuffer); +extern void (APIENTRY * qglGetFramebufferAttachmentParameterivEXT)(GLenum target, GLenum attachment, GLenum pname, GLint *params); +extern void (APIENTRY * qglGenerateMipmapEXT)(GLenum target); + +#ifndef GL_EXT_framebuffer_object +#define GL_EXT_framebuffer_object +#define GL_FRAMEBUFFER_EXT 0x8D40 +#define GL_RENDERBUFFER_EXT 0x8D41 +#define GL_STENCIL_INDEX1_EXT 0x8D46 +#define GL_STENCIL_INDEX4_EXT 0x8D47 +#define GL_STENCIL_INDEX8_EXT 0x8D48 +#define GL_STENCIL_INDEX16_EXT 0x8D49 +#define GL_RENDERBUFFER_WIDTH_EXT 0x8D42 +#define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 +#define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE_EXT 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE_EXT 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE_EXT 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE_EXT 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT 0x8CD4 +#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0 +#define GL_COLOR_ATTACHMENT1_EXT 0x8CE1 +#define GL_COLOR_ATTACHMENT2_EXT 0x8CE2 +#define GL_COLOR_ATTACHMENT3_EXT 0x8CE3 +#define GL_COLOR_ATTACHMENT4_EXT 0x8CE4 +#define GL_COLOR_ATTACHMENT5_EXT 0x8CE5 +#define GL_COLOR_ATTACHMENT6_EXT 0x8CE6 +#define GL_COLOR_ATTACHMENT7_EXT 0x8CE7 +#define GL_COLOR_ATTACHMENT8_EXT 0x8CE8 +#define GL_COLOR_ATTACHMENT9_EXT 0x8CE9 +#define GL_COLOR_ATTACHMENT10_EXT 0x8CEA +#define GL_COLOR_ATTACHMENT11_EXT 0x8CEB +#define GL_COLOR_ATTACHMENT12_EXT 0x8CEC +#define GL_COLOR_ATTACHMENT13_EXT 0x8CED +#define GL_COLOR_ATTACHMENT14_EXT 0x8CEE +#define GL_COLOR_ATTACHMENT15_EXT 0x8CEF +#define GL_DEPTH_ATTACHMENT_EXT 0x8D00 +#define GL_STENCIL_ATTACHMENT_EXT 0x8D20 +#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT 0x8CD9 +#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT 0x8CDA +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED_EXT 0x8CDD +#define GL_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 +#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF +#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 +#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 +#endif + +// GL_EXT_packed_depth_stencil +#ifndef GL_EXT_packed_depth_stencil +#define GL_EXT_packed_depth_stencil +#define GL_DEPTH_STENCIL_EXT 0x84F9 +#define GL_UNSIGNED_INT_24_8_EXT 0x84FA +#define GL_DEPTH24_STENCIL8_EXT 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE_EXT 0x88F1 +#endif + +// GL_ARB_occlusion_query +extern void (APIENTRY * qglGenQueriesARB)(GLsizei n, GLuint *ids); +extern void (APIENTRY * qglDeleteQueriesARB)(GLsizei n, const GLuint *ids); +extern GLboolean (APIENTRY * qglIsQueryARB)(GLuint id); +extern void (APIENTRY * qglBeginQueryARB)(GLenum target, GLuint id); +extern void (APIENTRY * qglEndQueryARB)(GLenum target); +extern void (APIENTRY * qglGetQueryivARB)(GLenum target, GLenum pname, GLint *params); +extern void (APIENTRY * qglGetQueryObjectivARB)(GLuint id, GLenum pname, GLint *params); +extern void (APIENTRY * qglGetQueryObjectuivARB)(GLuint id, GLenum pname, GLuint *params); + +#ifndef GL_ARB_occlusion_query +#define GL_ARB_occlusion_query +#define GL_SAMPLES_PASSED_ARB 0x8914 +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#endif + +// GL_EXT_framebuffer_blit +extern void (APIENTRY * qglBlitFramebufferEXT)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, + GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, + GLbitfield mask, GLenum filter); + +#ifndef GL_EXT_framebuffer_blit +#define GL_EXT_framebuffer_blit +#define GL_READ_FRAMEBUFFER_EXT 0x8CA8 +#define GL_DRAW_FRAMEBUFFER_EXT 0x8CA9 +#define GL_DRAW_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_READ_FRAMEBUFFER_BINDING_EXT 0x8CAA +#endif + +// GL_EXT_framebuffer_multisample +extern void (APIENTRY * qglRenderbufferStorageMultisampleEXT)(GLenum target, GLsizei samples, + GLenum internalformat, GLsizei width, GLsizei height); + +#ifndef GL_EXT_framebuffer_multisample +#define GL_EXT_framebuffer_multisample +#define GL_RENDERBUFFER_SAMPLES_EXT 0x8CAB +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT 0x8D56 +#define GL_MAX_SAMPLES_EXT 0x8D57 +#endif + +#ifndef GL_EXT_texture_sRGB +#define GL_EXT_texture_sRGB +#define GL_SRGB_EXT 0x8C40 +#define GL_SRGB8_EXT 0x8C41 +#define GL_SRGB_ALPHA_EXT 0x8C42 +#define GL_SRGB8_ALPHA8_EXT 0x8C43 +#define GL_SLUMINANCE_ALPHA_EXT 0x8C44 +#define GL_SLUMINANCE8_ALPHA8_EXT 0x8C45 +#define GL_SLUMINANCE_EXT 0x8C46 +#define GL_SLUMINANCE8_EXT 0x8C47 +#define GL_COMPRESSED_SRGB_EXT 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA_EXT 0x8C49 +#define GL_COMPRESSED_SLUMINANCE_EXT 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA_EXT 0x8C4B +#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F +#endif + +#ifndef GL_EXT_framebuffer_sRGB +#define GL_EXT_framebuffer_sRGB +#define GL_FRAMEBUFFER_SRGB_EXT 0x8DB9 +#endif + +#ifndef GL_EXT_texture_compression_latc +#define GL_EXT_texture_compression_latc +#define GL_COMPRESSED_LUMINANCE_LATC1_EXT 0x8C70 +#define GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT 0x8C71 +#define GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT 0x8C72 +#define GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT 0x8C73 +#endif + +#ifndef GL_ARB_texture_compression_bptc +#define GL_ARB_texture_compression_bptc +#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C +#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D +#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E +#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F +#endif + +// GL_ARB_draw_buffers +extern void (APIENTRY * qglDrawBuffersARB)(GLsizei n, const GLenum *bufs); +#ifndef GL_ARB_draw_buffers +#define GL_ARB_draw_buffers +#define GL_MAX_DRAW_BUFFERS_ARB 0x8824 +#define GL_DRAW_BUFFER0_ARB 0x8825 +#define GL_DRAW_BUFFER1_ARB 0x8826 +#define GL_DRAW_BUFFER2_ARB 0x8827 +#define GL_DRAW_BUFFER3_ARB 0x8828 +#define GL_DRAW_BUFFER4_ARB 0x8829 +#define GL_DRAW_BUFFER5_ARB 0x882A +#define GL_DRAW_BUFFER6_ARB 0x882B +#define GL_DRAW_BUFFER7_ARB 0x882C +#define GL_DRAW_BUFFER8_ARB 0x882D +#define GL_DRAW_BUFFER9_ARB 0x882E +#define GL_DRAW_BUFFER10_ARB 0x882F +#define GL_DRAW_BUFFER11_ARB 0x8830 +#define GL_DRAW_BUFFER12_ARB 0x8831 +#define GL_DRAW_BUFFER13_ARB 0x8832 +#define GL_DRAW_BUFFER14_ARB 0x8833 +#define GL_DRAW_BUFFER15_ARB 0x8834 +#endif + +#ifndef GL_ARB_depth_clamp +#define GL_ARB_depth_clamp +#define GL_DEPTH_CLAMP 0x864F +#endif + +#if defined(WIN32) +// WGL_ARB_create_context +#ifndef WGL_ARB_create_context +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x0001 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#define ERROR_INVALID_VERSION_ARB 0x2095 +#define ERROR_INVALID_PROFILE_ARB 0x2096 +#endif + +extern HGLRC(APIENTRY * qwglCreateContextAttribsARB) (HDC hdC, HGLRC hShareContext, const int *attribList); +#endif + +#if 0 //defined(__linux__) +// GLX_ARB_create_context +#ifndef GLX_ARB_create_context +#define GLX_CONTEXT_DEBUG_BIT_ARB 0x00000001 +#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define GLX_CONTEXT_FLAGS_ARB 0x2094 +#endif + +extern GLXContext (APIENTRY * qglXCreateContextAttribsARB) (Display *dpy, GLXFBConfig config, GLXContext share_context, Bool direct, const int *attrib_list); +#endif + +//=========================================================================== + +#define qglAccum glAccum +#define qglAlphaFunc glAlphaFunc +#define qglAreTexturesResident glAreTexturesResident +#define qglArrayElement glArrayElement +#define qglBegin glBegin +#define qglBindTexture glBindTexture +#define qglBitmap glBitmap +#define qglBlendFunc glBlendFunc +#define qglCallList glCallList +#define qglCallLists glCallLists +#define qglClear glClear +#define qglClearAccum glClearAccum +#define qglClearColor glClearColor +#define qglClearDepth glClearDepth +#define qglClearIndex glClearIndex +#define qglClearStencil glClearStencil +#define qglClipPlane glClipPlane +#define qglColor3b glColor3b +#define qglColor3bv glColor3bv +#define qglColor3d glColor3d +#define qglColor3dv glColor3dv +#define qglColor3f glColor3f +#define qglColor3fv glColor3fv +#define qglColor3i glColor3i +#define qglColor3iv glColor3iv +#define qglColor3s glColor3s +#define qglColor3sv glColor3sv +#define qglColor3ub glColor3ub +#define qglColor3ubv glColor3ubv +#define qglColor3ui glColor3ui +#define qglColor3uiv glColor3uiv +#define qglColor3us glColor3us +#define qglColor3usv glColor3usv +#define qglColor4b glColor4b +#define qglColor4bv glColor4bv +#define qglColor4d glColor4d +#define qglColor4dv glColor4dv +#define qglColor4f glColor4f +#define qglColor4fv glColor4fv +#define qglColor4i glColor4i +#define qglColor4iv glColor4iv +#define qglColor4s glColor4s +#define qglColor4sv glColor4sv +#define qglColor4ub glColor4ub +#define qglColor4ubv glColor4ubv +#define qglColor4ui glColor4ui +#define qglColor4uiv glColor4uiv +#define qglColor4us glColor4us +#define qglColor4usv glColor4usv +#define qglColorMask glColorMask +#define qglColorMaterial glColorMaterial +#define qglColorPointer glColorPointer +#define qglCopyPixels glCopyPixels +#define qglCopyTexImage1D glCopyTexImage1D +#define qglCopyTexImage2D glCopyTexImage2D +#define qglCopyTexSubImage1D glCopyTexSubImage1D +#define qglCopyTexSubImage2D glCopyTexSubImage2D +#define qglCullFace glCullFace +#define qglDeleteLists glDeleteLists +#define qglDeleteTextures glDeleteTextures +#define qglDepthFunc glDepthFunc +#define qglDepthMask glDepthMask +#define qglDepthRange glDepthRange +#define qglDisable glDisable +#define qglDisableClientState glDisableClientState +#define qglDrawArrays glDrawArrays +#define qglDrawBuffer glDrawBuffer +#define qglDrawElements glDrawElements +#define qglDrawPixels glDrawPixels +#define qglEdgeFlag glEdgeFlag +#define qglEdgeFlagPointer glEdgeFlagPointer +#define qglEdgeFlagv glEdgeFlagv +#define qglEnable glEnable +#define qglEnableClientState glEnableClientState +#define qglEnd glEnd +#define qglEndList glEndList +#define qglEvalCoord1d glEvalCoord1d +#define qglEvalCoord1dv glEvalCoord1dv +#define qglEvalCoord1f glEvalCoord1f +#define qglEvalCoord1fv glEvalCoord1fv +#define qglEvalCoord2d glEvalCoord2d +#define qglEvalCoord2dv glEvalCoord2dv +#define qglEvalCoord2f glEvalCoord2f +#define qglEvalCoord2fv glEvalCoord2fv +#define qglEvalMesh1 glEvalMesh1 +#define qglEvalMesh2 glEvalMesh2 +#define qglEvalPoint1 glEvalPoint1 +#define qglEvalPoint2 glEvalPoint2 +#define qglFeedbackBuffer glFeedbackBuffer +#define qglFinish glFinish +#define qglFlush glFlush +#define qglFogf glFogf +#define qglFogfv glFogfv +#define qglFogi glFogi +#define qglFogiv glFogiv +#define qglFrontFace glFrontFace +#define qglFrustum glFrustum +#define qglGenLists glGenLists +#define qglGenTextures glGenTextures +#define qglGetBooleanv glGetBooleanv +#define qglGetClipPlane glGetClipPlane +#define qglGetDoublev glGetDoublev +#define qglGetError glGetError +#define qglGetFloatv glGetFloatv +#define qglGetIntegerv glGetIntegerv +#define qglGetLightfv glGetLightfv +#define qglGetLightiv glGetLightiv +#define qglGetMapdv glGetMapdv +#define qglGetMapfv glGetMapfv +#define qglGetMapiv glGetMapiv +#define qglGetMaterialfv glGetMaterialfv +#define qglGetMaterialiv glGetMaterialiv +#define qglGetPixelMapfv glGetPixelMapfv +#define qglGetPixelMapuiv glGetPixelMapuiv +#define qglGetPixelMapusv glGetPixelMapusv +#define qglGetPointerv glGetPointerv +#define qglGetPolygonStipple glGetPolygonStipple +#define qglGetString glGetString +#define qglGetTexGendv glGetTexGendv +#define qglGetTexGenfv glGetTexGenfv +#define qglGetTexGeniv glGetTexGeniv +#define qglGetTexImage glGetTexImage +#define qglGetTexLevelParameterfv glGetTexLevelParameterfv +#define qglGetTexLevelParameteriv glGetTexLevelParameteriv +#define qglGetTexParameterfv glGetTexParameterfv +#define qglGetTexParameteriv glGetTexParameteriv +#define qglHint glHint +#define qglIndexMask glIndexMask +#define qglIndexPointer glIndexPointer +#define qglIndexd glIndexd +#define qglIndexdv glIndexdv +#define qglIndexf glIndexf +#define qglIndexfv glIndexfv +#define qglIndexi glIndexi +#define qglIndexiv glIndexiv +#define qglIndexs glIndexs +#define qglIndexsv glIndexsv +#define qglIndexub glIndexub +#define qglIndexubv glIndexubv +#define qglInitNames glInitNames +#define qglInterleavedArrays glInterleavedArrays +#define qglIsEnabled glIsEnabled +#define qglIsList glIsList +#define qglIsTexture glIsTexture +#define qglLightModelf glLightModelf +#define qglLightModelfv glLightModelfv +#define qglLightModeli glLightModeli +#define qglLightModeliv glLightModeliv +#define qglLightf glLightf +#define qglLightfv glLightfv +#define qglLighti glLighti +#define qglLightiv glLightiv +#define qglLineStipple glLineStipple +#define qglLineWidth glLineWidth +#define qglListBase glListBase +#define qglLoadIdentity glLoadIdentity +#define qglLoadMatrixd glLoadMatrixd +#define qglLoadMatrixf glLoadMatrixf +#define qglLoadName glLoadName +#define qglLogicOp glLogicOp +#define qglMap1d glMap1d +#define qglMap1f glMap1f +#define qglMap2d glMap2d +#define qglMap2f glMap2f +#define qglMapGrid1d glMapGrid1d +#define qglMapGrid1f glMapGrid1f +#define qglMapGrid2d glMapGrid2d +#define qglMapGrid2f glMapGrid2f +#define qglMaterialf glMaterialf +#define qglMaterialfv glMaterialfv +#define qglMateriali glMateriali +#define qglMaterialiv glMaterialiv +#define qglMatrixMode glMatrixMode +#define qglMultMatrixd glMultMatrixd +#define qglMultMatrixf glMultMatrixf +#define qglNewList glNewList +#define qglNormal3b glNormal3b +#define qglNormal3bv glNormal3bv +#define qglNormal3d glNormal3d +#define qglNormal3dv glNormal3dv +#define qglNormal3f glNormal3f +#define qglNormal3fv glNormal3fv +#define qglNormal3i glNormal3i +#define qglNormal3iv glNormal3iv +#define qglNormal3s glNormal3s +#define qglNormal3sv glNormal3sv +#define qglNormalPointer glNormalPointer +#define qglOrtho glOrtho +#define qglPassThrough glPassThrough +#define qglPixelMapfv glPixelMapfv +#define qglPixelMapuiv glPixelMapuiv +#define qglPixelMapusv glPixelMapusv +#define qglPixelStoref glPixelStoref +#define qglPixelStorei glPixelStorei +#define qglPixelTransferf glPixelTransferf +#define qglPixelTransferi glPixelTransferi +#define qglPixelZoom glPixelZoom +#define qglPointSize glPointSize +#define qglPolygonMode glPolygonMode +#define qglPolygonOffset glPolygonOffset +#define qglPolygonStipple glPolygonStipple +#define qglPopAttrib glPopAttrib +#define qglPopClientAttrib glPopClientAttrib +#define qglPopMatrix glPopMatrix +#define qglPopName glPopName +#define qglPrioritizeTextures glPrioritizeTextures +#define qglPushAttrib glPushAttrib +#define qglPushClientAttrib glPushClientAttrib +#define qglPushMatrix glPushMatrix +#define qglPushName glPushName +#define qglRasterPos2d glRasterPos2d +#define qglRasterPos2dv glRasterPos2dv +#define qglRasterPos2f glRasterPos2f +#define qglRasterPos2fv glRasterPos2fv +#define qglRasterPos2i glRasterPos2i +#define qglRasterPos2iv glRasterPos2iv +#define qglRasterPos2s glRasterPos2s +#define qglRasterPos2sv glRasterPos2sv +#define qglRasterPos3d glRasterPos3d +#define qglRasterPos3dv glRasterPos3dv +#define qglRasterPos3f glRasterPos3f +#define qglRasterPos3fv glRasterPos3fv +#define qglRasterPos3i glRasterPos3i +#define qglRasterPos3iv glRasterPos3iv +#define qglRasterPos3s glRasterPos3s +#define qglRasterPos3sv glRasterPos3sv +#define qglRasterPos4d glRasterPos4d +#define qglRasterPos4dv glRasterPos4dv +#define qglRasterPos4f glRasterPos4f +#define qglRasterPos4fv glRasterPos4fv +#define qglRasterPos4i glRasterPos4i +#define qglRasterPos4iv glRasterPos4iv +#define qglRasterPos4s glRasterPos4s +#define qglRasterPos4sv glRasterPos4sv +#define qglReadBuffer glReadBuffer +#define qglReadPixels glReadPixels +#define qglRectd glRectd +#define qglRectdv glRectdv +#define qglRectf glRectf +#define qglRectfv glRectfv +#define qglRecti glRecti +#define qglRectiv glRectiv +#define qglRects glRects +#define qglRectsv glRectsv +#define qglRenderMode glRenderMode +#define qglRotated glRotated +#define qglRotatef glRotatef +#define qglScaled glScaled +#define qglScalef glScalef +#define qglScissor glScissor +#define qglSelectBuffer glSelectBuffer +#define qglShadeModel glShadeModel +#define qglStencilFunc glStencilFunc +#define qglStencilMask glStencilMask +#define qglStencilOp glStencilOp +#define qglTexCoord1d glTexCoord1d +#define qglTexCoord1dv glTexCoord1dv +#define qglTexCoord1f glTexCoord1f +#define qglTexCoord1fv glTexCoord1fv +#define qglTexCoord1i glTexCoord1i +#define qglTexCoord1iv glTexCoord1iv +#define qglTexCoord1s glTexCoord1s +#define qglTexCoord1sv glTexCoord1sv +#define qglTexCoord2d glTexCoord2d +#define qglTexCoord2dv glTexCoord2dv +#define qglTexCoord2f glTexCoord2f +#define qglTexCoord2fv glTexCoord2fv +#define qglTexCoord2i glTexCoord2i +#define qglTexCoord2iv glTexCoord2iv +#define qglTexCoord2s glTexCoord2s +#define qglTexCoord2sv glTexCoord2sv +#define qglTexCoord3d glTexCoord3d +#define qglTexCoord3dv glTexCoord3dv +#define qglTexCoord3f glTexCoord3f +#define qglTexCoord3fv glTexCoord3fv +#define qglTexCoord3i glTexCoord3i +#define qglTexCoord3iv glTexCoord3iv +#define qglTexCoord3s glTexCoord3s +#define qglTexCoord3sv glTexCoord3sv +#define qglTexCoord4d glTexCoord4d +#define qglTexCoord4dv glTexCoord4dv +#define qglTexCoord4f glTexCoord4f +#define qglTexCoord4fv glTexCoord4fv +#define qglTexCoord4i glTexCoord4i +#define qglTexCoord4iv glTexCoord4iv +#define qglTexCoord4s glTexCoord4s +#define qglTexCoord4sv glTexCoord4sv +#define qglTexCoordPointer glTexCoordPointer +#define qglTexEnvf glTexEnvf +#define qglTexEnvfv glTexEnvfv +#define qglTexEnvi glTexEnvi +#define qglTexEnviv glTexEnviv +#define qglTexGend glTexGend +#define qglTexGendv glTexGendv +#define qglTexGenf glTexGenf +#define qglTexGenfv glTexGenfv +#define qglTexGeni glTexGeni +#define qglTexGeniv glTexGeniv +#define qglTexImage1D glTexImage1D +#define qglTexImage2D glTexImage2D +#define qglTexParameterf glTexParameterf +#define qglTexParameterfv glTexParameterfv +#define qglTexParameteri glTexParameteri +#define qglTexParameteriv glTexParameteriv +#define qglTexSubImage1D glTexSubImage1D +#define qglTexSubImage2D glTexSubImage2D +#define qglTranslated glTranslated +#define qglTranslatef glTranslatef +#define qglVertex2d glVertex2d +#define qglVertex2dv glVertex2dv +#define qglVertex2f glVertex2f +#define qglVertex2fv glVertex2fv +#define qglVertex2i glVertex2i +#define qglVertex2iv glVertex2iv +#define qglVertex2s glVertex2s +#define qglVertex2sv glVertex2sv +#define qglVertex3d glVertex3d +#define qglVertex3dv glVertex3dv +#define qglVertex3f glVertex3f +#define qglVertex3fv glVertex3fv +#define qglVertex3i glVertex3i +#define qglVertex3iv glVertex3iv +#define qglVertex3s glVertex3s +#define qglVertex3sv glVertex3sv +#define qglVertex4d glVertex4d +#define qglVertex4dv glVertex4dv +#define qglVertex4f glVertex4f +#define qglVertex4fv glVertex4fv +#define qglVertex4i glVertex4i +#define qglVertex4iv glVertex4iv +#define qglVertex4s glVertex4s +#define qglVertex4sv glVertex4sv +#define qglVertexPointer glVertexPointer +#define qglViewport glViewport + +#endif diff --git a/src/rend2/tr_animation.c b/src/rend2/tr_animation.c new file mode 100644 index 00000000..794111c6 --- /dev/null +++ b/src/rend2/tr_animation.c @@ -0,0 +1,658 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "tr_local.h" + +/* + +All bones should be an identity orientation to display the mesh exactly +as it is specified. + +For all other frames, the bones represent the transformation from the +orientation of the bone in the base frame to the orientation in this +frame. + +*/ + +/* +============== +R_AddAnimSurfaces +============== +*/ +void R_AddAnimSurfaces( trRefEntity_t *ent ) { + md4Header_t *header; + md4Surface_t *surface; + md4LOD_t *lod; + shader_t *shader; + int i; + + header = (md4Header_t *) tr.currentModel->modelData; + lod = (md4LOD_t *)( (byte *)header + header->ofsLODs ); + + surface = (md4Surface_t *)( (byte *)lod + lod->ofsSurfaces ); + for ( i = 0 ; i < lod->numSurfaces ; i++ ) { + shader = R_GetShaderByHandle( surface->shaderIndex ); + R_AddDrawSurf( (void *)surface, shader, 0 /*fogNum*/, qfalse, qfalse ); + surface = (md4Surface_t *)( (byte *)surface + surface->ofsEnd ); + } +} + +/* +============== +RB_SurfaceAnim +============== +*/ +void RB_SurfaceAnim( md4Surface_t *surface ) { + int i, j, k; + float frontlerp, backlerp; + int *triangles; + int indexes; + int baseIndex, baseVertex; + int numVerts; + md4Vertex_t *v; + md4Bone_t bones[MD4_MAX_BONES]; + md4Bone_t *bonePtr, *bone; + md4Header_t *header; + md4Frame_t *frame; + md4Frame_t *oldFrame; + int frameSize; + + + if ( backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) { + backlerp = 0; + frontlerp = 1; + } else { + backlerp = backEnd.currentEntity->e.backlerp; + frontlerp = 1.0f - backlerp; + } + header = (md4Header_t *)((byte *)surface + surface->ofsHeader); + + frameSize = (size_t)( &((md4Frame_t *)0)->bones[ header->numBones ] ); + + frame = (md4Frame_t *)((byte *)header + header->ofsFrames + + backEnd.currentEntity->e.frame * frameSize ); + oldFrame = (md4Frame_t *)((byte *)header + header->ofsFrames + + backEnd.currentEntity->e.oldframe * frameSize ); + + RB_CheckOverflow( surface->numVerts, surface->numTriangles * 3 ); + + triangles = (int *) ((byte *)surface + surface->ofsTriangles); + indexes = surface->numTriangles * 3; + baseIndex = tess.numIndexes; + baseVertex = tess.numVertexes; + for (j = 0 ; j < indexes ; j++) { + tess.indexes[baseIndex + j] = baseIndex + triangles[j]; + } + tess.numIndexes += indexes; + + // + // lerp all the needed bones + // + if ( !backlerp ) { + // no lerping needed + bonePtr = frame->bones; + } else { + bonePtr = bones; + for ( i = 0 ; i < header->numBones*12 ; i++ ) { + ((float *)bonePtr)[i] = frontlerp * ((float *)frame->bones)[i] + + backlerp * ((float *)oldFrame->bones)[i]; + } + } + + // + // deform the vertexes by the lerped bones + // + numVerts = surface->numVerts; + // FIXME + // This makes TFC's skeletons work. Shouldn't be necessary anymore, but left + // in for reference. + //v = (md4Vertex_t *) ((byte *)surface + surface->ofsVerts + 12); + v = (md4Vertex_t *) ((byte *)surface + surface->ofsVerts); + for ( j = 0; j < numVerts; j++ ) { + vec3_t tempVert, tempNormal; + md4Weight_t *w; + + VectorClear( tempVert ); + VectorClear( tempNormal ); + w = v->weights; + for ( k = 0 ; k < v->numWeights ; k++, w++ ) { + bone = bonePtr + w->boneIndex; + + tempVert[0] += w->boneWeight * ( DotProduct( bone->matrix[0], w->offset ) + bone->matrix[0][3] ); + tempVert[1] += w->boneWeight * ( DotProduct( bone->matrix[1], w->offset ) + bone->matrix[1][3] ); + tempVert[2] += w->boneWeight * ( DotProduct( bone->matrix[2], w->offset ) + bone->matrix[2][3] ); + + tempNormal[0] += w->boneWeight * DotProduct( bone->matrix[0], v->normal ); + tempNormal[1] += w->boneWeight * DotProduct( bone->matrix[1], v->normal ); + tempNormal[2] += w->boneWeight * DotProduct( bone->matrix[2], v->normal ); + } + + tess.xyz[baseVertex + j][0] = tempVert[0]; + tess.xyz[baseVertex + j][1] = tempVert[1]; + tess.xyz[baseVertex + j][2] = tempVert[2]; + + tess.normal[baseVertex + j][0] = tempNormal[0]; + tess.normal[baseVertex + j][1] = tempNormal[1]; + tess.normal[baseVertex + j][2] = tempNormal[2]; + + tess.texCoords[baseVertex + j][0][0] = v->texCoords[0]; + tess.texCoords[baseVertex + j][0][1] = v->texCoords[1]; + + // FIXME + // This makes TFC's skeletons work. Shouldn't be necessary anymore, but left + // in for reference. + //v = (md4Vertex_t *)( ( byte * )&v->weights[v->numWeights] + 12 ); + v = (md4Vertex_t *)&v->weights[v->numWeights]; + } + + tess.numVertexes += surface->numVerts; +} + + +#ifdef RAVENMD4 + +// copied and adapted from tr_mesh.c + +/* +============= +R_MDRCullModel +============= +*/ + +static int R_MDRCullModel( mdrHeader_t *header, trRefEntity_t *ent ) { + vec3_t bounds[2]; + mdrFrame_t *oldFrame, *newFrame; + int i, frameSize; + + frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] ); + + // compute frame pointers + newFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.frame); + oldFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.oldframe); + + // cull bounding sphere ONLY if this is not an upscaled entity + if ( !ent->e.nonNormalizedAxes ) + { + if ( ent->e.frame == ent->e.oldframe ) + { + switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) ) + { + // Ummm... yeah yeah I know we don't really have an md3 here.. but we pretend + // we do. After all, the purpose of md4s are not that different, are they? + + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + break; + } + } + else + { + int sphereCull, sphereCullB; + + sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ); + if ( newFrame == oldFrame ) { + sphereCullB = sphereCull; + } else { + sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius ); + } + + if ( sphereCull == sphereCullB ) + { + if ( sphereCull == CULL_OUT ) + { + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + } + else if ( sphereCull == CULL_IN ) + { + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + } + else + { + tr.pc.c_sphere_cull_md3_clip++; + } + } + } + } + + // calculate a bounding box in the current coordinate system + for (i = 0 ; i < 3 ; i++) { + bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i]; + bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + +/* +================= +R_MDRComputeFogNum + +================= +*/ + +int R_MDRComputeFogNum( mdrHeader_t *header, trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + mdrFrame_t *mdrFrame; + vec3_t localOrigin; + int frameSize; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] ); + + // FIXME: non-normalized axis issues + mdrFrame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.frame); + VectorAdd( ent->e.origin, mdrFrame->localOrigin, localOrigin ); + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( localOrigin[j] - mdrFrame->radius >= fog->bounds[1][j] ) { + break; + } + if ( localOrigin[j] + mdrFrame->radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + + +/* +============== +R_MDRAddAnimSurfaces +============== +*/ + +// much stuff in there is just copied from R_AddMd3Surfaces in tr_mesh.c + +void R_MDRAddAnimSurfaces( trRefEntity_t *ent ) { + mdrHeader_t *header; + mdrSurface_t *surface; + mdrLOD_t *lod; + shader_t *shader; + skin_t *skin; + int i, j; + int lodnum = 0; + int fogNum = 0; + int cull; + qboolean personalModel; + + header = (mdrHeader_t *) tr.currentModel->modelData; + + personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; + + if ( ent->e.renderfx & RF_WRAP_FRAMES ) + { + ent->e.frame %= header->numFrames; + ent->e.oldframe %= header->numFrames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ((ent->e.frame >= header->numFrames) + || (ent->e.frame < 0) + || (ent->e.oldframe >= header->numFrames) + || (ent->e.oldframe < 0) ) + { + ri.Printf( PRINT_DEVELOPER, "R_MDRAddAnimSurfaces: no such frame %d to %d for '%s'\n", + ent->e.oldframe, ent->e.frame, tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_MDRCullModel (header, ent); + if ( cull == CULL_OUT ) { + return; + } + + // figure out the current LOD of the model we're rendering, and set the lod pointer respectively. + lodnum = R_ComputeLOD(ent); + // check whether this model has as that many LODs at all. If not, try the closest thing we got. + if(header->numLODs <= 0) + return; + if(header->numLODs <= lodnum) + lodnum = header->numLODs - 1; + + lod = (mdrLOD_t *)( (byte *)header + header->ofsLODs); + for(i = 0; i < lodnum; i++) + { + lod = (mdrLOD_t *) ((byte *) lod + lod->ofsEnd); + } + + // set up lighting + if ( !personalModel || r_shadows->integer > 1 ) + { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // fogNum? + fogNum = R_MDRComputeFogNum( header, ent ); + + surface = (mdrSurface_t *)( (byte *)lod + lod->ofsSurfaces ); + + for ( i = 0 ; i < lod->numSurfaces ; i++ ) + { + + if(ent->e.customShader) + shader = R_GetShaderByHandle(ent->e.customShader); + else if(ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins) + { + skin = R_GetSkinByHandle(ent->e.customSkin); + shader = tr.defaultShader; + + for(j = 0; j < skin->numSurfaces; j++) + { + if (!strcmp(skin->surfaces[j]->name, surface->name)) + { + shader = skin->surfaces[j]->shader; + break; + } + } + } + else if(surface->shaderIndex > 0) + shader = R_GetShaderByHandle( surface->shaderIndex ); + else + shader = tr.defaultShader; + + // 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 + if ( !personalModel + && r_shadows->integer == 2 + && fogNum == 0 + && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) + { + R_AddDrawSurf( (void *)surface, tr.shadowShader, 0, qfalse, qfalse ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) + { + R_AddDrawSurf( (void *)surface, tr.projectionShadowShader, 0, qfalse, qfalse ); + } + + if (!personalModel) + R_AddDrawSurf( (void *)surface, shader, fogNum, qfalse, qfalse ); + + surface = (mdrSurface_t *)( (byte *)surface + surface->ofsEnd ); + } +} + +/* +============== +RB_MDRSurfaceAnim +============== +*/ +void RB_MDRSurfaceAnim( md4Surface_t *surface ) +{ + int i, j, k; + float frontlerp, backlerp; + int *triangles; + int indexes; + int baseIndex, baseVertex; + int numVerts; + mdrVertex_t *v; + mdrHeader_t *header; + mdrFrame_t *frame; + mdrFrame_t *oldFrame; + mdrBone_t bones[MD4_MAX_BONES], *bonePtr, *bone; + + int frameSize; + + // don't lerp if lerping off, or this is the only frame, or the last frame... + // + if (backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame) + { + backlerp = 0; // if backlerp is 0, lerping is off and frontlerp is never used + frontlerp = 1; + } + else + { + backlerp = backEnd.currentEntity->e.backlerp; + frontlerp = 1.0f - backlerp; + } + + header = (mdrHeader_t *)((byte *)surface + surface->ofsHeader); + + frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] ); + + frame = (mdrFrame_t *)((byte *)header + header->ofsFrames + + backEnd.currentEntity->e.frame * frameSize ); + oldFrame = (mdrFrame_t *)((byte *)header + header->ofsFrames + + backEnd.currentEntity->e.oldframe * frameSize ); + + RB_CheckOverflow( surface->numVerts, surface->numTriangles ); + + triangles = (int *) ((byte *)surface + surface->ofsTriangles); + indexes = surface->numTriangles * 3; + baseIndex = tess.numIndexes; + baseVertex = tess.numVertexes; + + // Set up all triangles. + for (j = 0 ; j < indexes ; j++) + { + tess.indexes[baseIndex + j] = baseVertex + triangles[j]; + } + tess.numIndexes += indexes; + + // + // lerp all the needed bones + // + if ( !backlerp ) + { + // no lerping needed + bonePtr = frame->bones; + } + else + { + bonePtr = bones; + + for ( i = 0 ; i < header->numBones*12 ; i++ ) + { + ((float *)bonePtr)[i] = frontlerp * ((float *)frame->bones)[i] + backlerp * ((float *)oldFrame->bones)[i]; + } + } + + // + // deform the vertexes by the lerped bones + // + numVerts = surface->numVerts; + v = (mdrVertex_t *) ((byte *)surface + surface->ofsVerts); + for ( j = 0; j < numVerts; j++ ) + { + vec3_t tempVert, tempNormal; + mdrWeight_t *w; + + VectorClear( tempVert ); + VectorClear( tempNormal ); + w = v->weights; + for ( k = 0 ; k < v->numWeights ; k++, w++ ) + { + bone = bonePtr + w->boneIndex; + + tempVert[0] += w->boneWeight * ( DotProduct( bone->matrix[0], w->offset ) + bone->matrix[0][3] ); + tempVert[1] += w->boneWeight * ( DotProduct( bone->matrix[1], w->offset ) + bone->matrix[1][3] ); + tempVert[2] += w->boneWeight * ( DotProduct( bone->matrix[2], w->offset ) + bone->matrix[2][3] ); + + tempNormal[0] += w->boneWeight * DotProduct( bone->matrix[0], v->normal ); + tempNormal[1] += w->boneWeight * DotProduct( bone->matrix[1], v->normal ); + tempNormal[2] += w->boneWeight * DotProduct( bone->matrix[2], v->normal ); + } + + tess.xyz[baseVertex + j][0] = tempVert[0]; + tess.xyz[baseVertex + j][1] = tempVert[1]; + tess.xyz[baseVertex + j][2] = tempVert[2]; + + tess.normal[baseVertex + j][0] = tempNormal[0]; + tess.normal[baseVertex + j][1] = tempNormal[1]; + tess.normal[baseVertex + j][2] = tempNormal[2]; + + tess.texCoords[baseVertex + j][0][0] = v->texCoords[0]; + tess.texCoords[baseVertex + j][0][1] = v->texCoords[1]; + + v = (mdrVertex_t *)&v->weights[v->numWeights]; + } + + tess.numVertexes += surface->numVerts; +} + + +#define MC_MASK_X ((1<<(MC_BITS_X))-1) +#define MC_MASK_Y ((1<<(MC_BITS_Y))-1) +#define MC_MASK_Z ((1<<(MC_BITS_Z))-1) +#define MC_MASK_VECT ((1<<(MC_BITS_VECT))-1) + +#define MC_SCALE_VECT (1.0f/(float)((1<<(MC_BITS_VECT-1))-2)) + +#define MC_POS_X (0) +#define MC_SHIFT_X (0) + +#define MC_POS_Y ((((MC_BITS_X))/8)) +#define MC_SHIFT_Y ((((MC_BITS_X)%8))) + +#define MC_POS_Z ((((MC_BITS_X+MC_BITS_Y))/8)) +#define MC_SHIFT_Z ((((MC_BITS_X+MC_BITS_Y)%8))) + +#define MC_POS_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z))/8)) +#define MC_SHIFT_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z)%8))) + +#define MC_POS_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT))/8)) +#define MC_SHIFT_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT)%8))) + +#define MC_POS_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2))/8)) +#define MC_SHIFT_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2)%8))) + +#define MC_POS_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3))/8)) +#define MC_SHIFT_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3)%8))) + +#define MC_POS_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4))/8)) +#define MC_SHIFT_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4)%8))) + +#define MC_POS_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5))/8)) +#define MC_SHIFT_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5)%8))) + +#define MC_POS_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6))/8)) +#define MC_SHIFT_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6)%8))) + +#define MC_POS_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7))/8)) +#define MC_SHIFT_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7)%8))) + +#define MC_POS_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8))/8)) +#define MC_SHIFT_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8)%8))) + +void MC_UnCompress(float mat[3][4],const unsigned char * comp) +{ + int val; + + val=(int)((unsigned short *)(comp))[0]; + val-=1<<(MC_BITS_X-1); + mat[0][3]=((float)(val))*MC_SCALE_X; + + val=(int)((unsigned short *)(comp))[1]; + val-=1<<(MC_BITS_Y-1); + mat[1][3]=((float)(val))*MC_SCALE_Y; + + val=(int)((unsigned short *)(comp))[2]; + val-=1<<(MC_BITS_Z-1); + mat[2][3]=((float)(val))*MC_SCALE_Z; + + val=(int)((unsigned short *)(comp))[3]; + val-=1<<(MC_BITS_VECT-1); + mat[0][0]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[4]; + val-=1<<(MC_BITS_VECT-1); + mat[0][1]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[5]; + val-=1<<(MC_BITS_VECT-1); + mat[0][2]=((float)(val))*MC_SCALE_VECT; + + + val=(int)((unsigned short *)(comp))[6]; + val-=1<<(MC_BITS_VECT-1); + mat[1][0]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[7]; + val-=1<<(MC_BITS_VECT-1); + mat[1][1]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[8]; + val-=1<<(MC_BITS_VECT-1); + mat[1][2]=((float)(val))*MC_SCALE_VECT; + + + val=(int)((unsigned short *)(comp))[9]; + val-=1<<(MC_BITS_VECT-1); + mat[2][0]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[10]; + val-=1<<(MC_BITS_VECT-1); + mat[2][1]=((float)(val))*MC_SCALE_VECT; + + val=(int)((unsigned short *)(comp))[11]; + val-=1<<(MC_BITS_VECT-1); + mat[2][2]=((float)(val))*MC_SCALE_VECT; +} +#endif diff --git a/src/rend2/tr_backend.c b/src/rend2/tr_backend.c new file mode 100644 index 00000000..1a9bdccd --- /dev/null +++ b/src/rend2/tr_backend.c @@ -0,0 +1,1912 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "tr_local.h" + +backEndData_t *backEndData[SMP_FRAMES]; +backEndState_t backEnd; + + +static float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +}; + + +/* +** GL_Bind2 +*/ +void GL_Bind2( image_t *image, GLenum type ) { + int texnum; + + if ( !image ) { + ri.Printf( PRINT_WARNING, "GL_Bind2: NULL image\n" ); + texnum = tr.defaultImage->texnum; + } else { + texnum = image->texnum; + } + + if ( r_nobind->integer && tr.dlightImage ) { // performance evaluation option + texnum = tr.dlightImage->texnum; + } + + if ( glState.currenttextures[glState.currenttmu] != texnum ) { + image->frameUsed = tr.frameCount; + glState.currenttextures[glState.currenttmu] = texnum; + qglBindTexture (type, texnum); + } +} + +/* +** GL_Bind2 +*/ +void GL_Bind( image_t *image ) +{ + GL_Bind2( image, GL_TEXTURE_2D ); +} + +/* +** GL_BindCubemap +*/ +void GL_BindCubemap( image_t *image ) +{ + GL_Bind2( image, GL_TEXTURE_CUBE_MAP ); +} + +/* +** GL_SelectTexture +*/ +void GL_SelectTexture( int unit ) +{ + if ( glState.currenttmu == unit ) + { + return; + } + + if (!(unit >= 0 && unit <= 31)) + ri.Error( ERR_DROP, "GL_SelectTexture: unit = %i", unit ); + + qglActiveTextureARB( GL_TEXTURE0_ARB + unit ); + + glState.currenttmu = unit; +} + + +/* +** GL_BindMultitexture +*/ +void GL_BindMultitexture( image_t *image0, GLuint env0, image_t *image1, GLuint env1 ) { + int texnum0, texnum1; + + texnum0 = image0->texnum; + texnum1 = image1->texnum; + + if ( r_nobind->integer && tr.dlightImage ) { // performance evaluation option + texnum0 = texnum1 = tr.dlightImage->texnum; + } + + if ( glState.currenttextures[1] != texnum1 ) { + GL_SelectTexture( 1 ); + image1->frameUsed = tr.frameCount; + glState.currenttextures[1] = texnum1; + qglBindTexture( GL_TEXTURE_2D, texnum1 ); + } + if ( glState.currenttextures[0] != texnum0 ) { + GL_SelectTexture( 0 ); + image0->frameUsed = tr.frameCount; + glState.currenttextures[0] = texnum0; + qglBindTexture( GL_TEXTURE_2D, texnum0 ); + } +} + +/* +** GL_BindToTMU +*/ +void GL_BindToTMU( image_t *image, int tmu ) +{ + int texnum; + int oldtmu = glState.currenttmu; + + if (!image) + texnum = 0; + else + texnum = image->texnum; + + if ( glState.currenttextures[tmu] != texnum ) { + GL_SelectTexture( tmu ); + if (image) + image->frameUsed = tr.frameCount; + glState.currenttextures[tmu] = texnum; + qglBindTexture( GL_TEXTURE_2D, texnum ); + GL_SelectTexture( oldtmu ); + } +} + + +/* +** GL_Cull +*/ +void GL_Cull( int cullType ) { +#ifdef REACTION + // Makro - flip culling if needed + qboolean flip = (backEnd.currentEntity != NULL && backEnd.currentEntity->mirrored != qfalse && cullType != CT_TWO_SIDED); + cullType ^= flip; // this assumes CT_BACK_SIDED and CT_FRONT_SIDED are 0 or 1 +#endif + + if ( glState.faceCulling == cullType ) { + return; + } + + glState.faceCulling = cullType; + + if ( cullType == CT_TWO_SIDED ) + { + qglDisable( GL_CULL_FACE ); + } + else + { + qboolean cullFront; + qglEnable( GL_CULL_FACE ); + + cullFront = (cullType == CT_FRONT_SIDED); + if ( backEnd.viewParms.isMirror ) + { + cullFront = !cullFront; + } + + qglCullFace( cullFront ? GL_FRONT : GL_BACK ); + } +} + +/* +** GL_TexEnv +*/ +void GL_TexEnv( int env ) +{ + if ( env == glState.texEnv[glState.currenttmu] ) + { + return; + } + + glState.texEnv[glState.currenttmu] = env; + + + switch ( env ) + { + case GL_MODULATE: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + break; + case GL_REPLACE: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + break; + case GL_DECAL: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL ); + break; + case GL_ADD: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD ); + break; + default: + ri.Error( ERR_DROP, "GL_TexEnv: invalid env '%d' passed", env ); + break; + } +} + +/* +** GL_State +** +** This routine is responsible for setting the most commonly changed state +** in Q3. +*/ +void GL_State( unsigned long stateBits ) +{ + unsigned long diff = stateBits ^ glState.glStateBits; + + if ( !diff ) + { + return; + } + + // + // check depthFunc bits + // + if ( diff & GLS_DEPTHFUNC_BITS ) + { + if ( stateBits & GLS_DEPTHFUNC_EQUAL ) + { + qglDepthFunc( GL_EQUAL ); + } + else if ( stateBits & GLS_DEPTHFUNC_GREATER) + { + qglDepthFunc( GL_GREATER ); + } + else + { + qglDepthFunc( GL_LEQUAL ); + } + } + + // + // check blend bits + // + if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) + { + GLenum srcFactor, dstFactor; + + if ( stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) + { + switch ( stateBits & GLS_SRCBLEND_BITS ) + { + case GLS_SRCBLEND_ZERO: + srcFactor = GL_ZERO; + break; + case GLS_SRCBLEND_ONE: + srcFactor = GL_ONE; + break; + case GLS_SRCBLEND_DST_COLOR: + srcFactor = GL_DST_COLOR; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_COLOR: + srcFactor = GL_ONE_MINUS_DST_COLOR; + break; + case GLS_SRCBLEND_SRC_ALPHA: + srcFactor = GL_SRC_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA: + srcFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_SRCBLEND_DST_ALPHA: + srcFactor = GL_DST_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA: + srcFactor = GL_ONE_MINUS_DST_ALPHA; + break; + case GLS_SRCBLEND_ALPHA_SATURATE: + srcFactor = GL_SRC_ALPHA_SATURATE; + break; + default: + srcFactor = GL_ONE; // to get warning to shut up + ri.Error( ERR_DROP, "GL_State: invalid src blend state bits" ); + break; + } + + switch ( stateBits & GLS_DSTBLEND_BITS ) + { + case GLS_DSTBLEND_ZERO: + dstFactor = GL_ZERO; + break; + case GLS_DSTBLEND_ONE: + dstFactor = GL_ONE; + break; + case GLS_DSTBLEND_SRC_COLOR: + dstFactor = GL_SRC_COLOR; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR: + dstFactor = GL_ONE_MINUS_SRC_COLOR; + break; + case GLS_DSTBLEND_SRC_ALPHA: + dstFactor = GL_SRC_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA: + dstFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_DSTBLEND_DST_ALPHA: + dstFactor = GL_DST_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA: + dstFactor = GL_ONE_MINUS_DST_ALPHA; + break; + default: + dstFactor = GL_ONE; // to get warning to shut up + ri.Error( ERR_DROP, "GL_State: invalid dst blend state bits" ); + break; + } + + qglEnable( GL_BLEND ); + qglBlendFunc( srcFactor, dstFactor ); + } + else + { + qglDisable( GL_BLEND ); + } + } + + // + // check depthmask + // + if ( diff & GLS_DEPTHMASK_TRUE ) + { + if ( stateBits & GLS_DEPTHMASK_TRUE ) + { + qglDepthMask( GL_TRUE ); + } + else + { + qglDepthMask( GL_FALSE ); + } + } + + // + // fill/line mode + // + if ( diff & GLS_POLYMODE_LINE ) + { + if ( stateBits & GLS_POLYMODE_LINE ) + { + qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + } + else + { + qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + } + + // + // depthtest + // + if ( diff & GLS_DEPTHTEST_DISABLE ) + { + if ( stateBits & GLS_DEPTHTEST_DISABLE ) + { + qglDisable( GL_DEPTH_TEST ); + } + else + { + qglEnable( GL_DEPTH_TEST ); + } + } + + // + // alpha test + // + if ( diff & GLS_ATEST_BITS ) + { + switch ( stateBits & GLS_ATEST_BITS ) + { + case 0: + qglDisable( GL_ALPHA_TEST ); + break; + case GLS_ATEST_GT_0: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_GREATER, 0.0f ); + break; + case GLS_ATEST_LT_80: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_LESS, 0.5f ); + break; + case GLS_ATEST_GE_80: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_GEQUAL, 0.5f ); + break; + default: + assert( 0 ); + break; + } + } + + glState.glStateBits = stateBits; +} + + +void GL_SetProjectionMatrix(matrix_t matrix) +{ + Matrix16Copy(matrix, glState.projection); + Matrix16Multiply(glState.projection, glState.modelview, glState.modelviewProjection); +} + + +void GL_SetModelviewMatrix(matrix_t matrix) +{ + Matrix16Copy(matrix, glState.modelview); + Matrix16Multiply(glState.projection, glState.modelview, glState.modelviewProjection); +} + + +/* +================ +RB_Hyperspace + +A player has predicted a teleport, but hasn't arrived yet +================ +*/ +static void RB_Hyperspace( void ) { + float c; + + if ( !backEnd.isHyperspace ) { + // do initialization shit + } + + c = ( backEnd.refdef.time & 255 ) / 255.0f; + qglClearColor( c, c, c, 1 ); + qglClear( GL_COLOR_BUFFER_BIT ); + + backEnd.isHyperspace = qtrue; +} + + +static void SetViewportAndScissor( void ) { + GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix ); + + // set the window clipping + qglViewport( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + qglScissor( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); +} + +/* +================= +RB_BeginDrawingView + +Any mirrored or portaled views have already been drawn, so prepare +to actually render the visible surfaces for this view +================= +*/ +void RB_BeginDrawingView (void) { + int clearBits = 0; + + // sync with gl if needed + if ( r_finish->integer == 1 && !glState.finishCalled ) { + qglFinish (); + glState.finishCalled = qtrue; + } + if ( r_finish->integer == 0 ) { + glState.finishCalled = qtrue; + } + + // we will need to change the projection matrix before drawing + // 2D images again + backEnd.projection2D = qfalse; + + if (glRefConfig.framebufferObject) + { + // FIXME: HUGE HACK: render to the screen fbo if we've already postprocessed the frame and aren't drawing more world + if (backEnd.viewParms.targetFbo == tr.renderFbo && backEnd.framePostProcessed && (backEnd.refdef.rdflags & RDF_NOWORLDMODEL)) + { + FBO_Bind(tr.screenScratchFbo); + } + else + { + FBO_Bind(backEnd.viewParms.targetFbo); + } + } + + // + // set the modelview matrix for the viewer + // + SetViewportAndScissor(); + + // ensures that depth writes are enabled for the depth clear + GL_State( GLS_DEFAULT ); + // clear relevant buffers + clearBits = GL_DEPTH_BUFFER_BIT; + + if ( r_measureOverdraw->integer || r_shadows->integer == 2 ) + { + clearBits |= GL_STENCIL_BUFFER_BIT; + } + if ( r_fastsky->integer && !( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) ) + { + clearBits |= GL_COLOR_BUFFER_BIT; // FIXME: only if sky shaders have been used +#ifdef _DEBUG + qglClearColor( 0.8f, 0.7f, 0.4f, 1.0f ); // FIXME: get color of sky +#else + qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); // FIXME: get color of sky +#endif + } + + // clear to white for shadow maps + if (backEnd.viewParms.flags & VPF_SHADOWMAP) + { + clearBits |= GL_COLOR_BUFFER_BIT; + qglClearColor( 1.0f, 1.0f, 1.0f, 1.0f ); + } + + qglClear( clearBits ); + + if ( ( backEnd.refdef.rdflags & RDF_HYPERSPACE ) ) + { + RB_Hyperspace(); + return; + } + else + { + backEnd.isHyperspace = qfalse; + } + + glState.faceCulling = -1; // force face culling to set next time + + // we will only draw a sun if there was sky rendered in this view + backEnd.skyRenderedThisView = qfalse; + +#ifdef REACTION + backEnd.viewHasSunFlare = qfalse; +#endif + + // clip to the plane of the portal + if ( backEnd.viewParms.isPortal ) { +#if 0 + float plane[4]; + double plane2[4]; + + plane[0] = backEnd.viewParms.portalPlane.normal[0]; + plane[1] = backEnd.viewParms.portalPlane.normal[1]; + plane[2] = backEnd.viewParms.portalPlane.normal[2]; + plane[3] = backEnd.viewParms.portalPlane.dist; + + plane2[0] = DotProduct (backEnd.viewParms.or.axis[0], plane); + plane2[1] = DotProduct (backEnd.viewParms.or.axis[1], plane); + plane2[2] = DotProduct (backEnd.viewParms.or.axis[2], plane); + plane2[3] = DotProduct (plane, backEnd.viewParms.or.origin) - plane[3]; +#endif + GL_SetModelviewMatrix( s_flipMatrix ); + } +} + + +#define MAC_EVENT_PUMP_MSEC 5 + +/* +================== +RB_RenderDrawSurfList +================== +*/ +void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) { + shader_t *shader, *oldShader; + int fogNum, oldFogNum; + int entityNum, oldEntityNum; + int dlighted, oldDlighted; + int pshadowed, oldPshadowed; + qboolean depthRange, oldDepthRange, isCrosshair, wasCrosshair; + int i; + drawSurf_t *drawSurf; + int oldSort; + float originalTime; + FBO_t* fbo = NULL; + qboolean inQuery = qfalse; + +#if 1 //def REACTION + float depth[2]; +#endif + + + // save original time for entity shader offsets + originalTime = backEnd.refdef.floatTime; + + fbo = glState.currentFBO; + + // draw everything + oldEntityNum = -1; + backEnd.currentEntity = &tr.worldEntity; + oldShader = NULL; + oldFogNum = -1; + oldDepthRange = qfalse; + wasCrosshair = qfalse; + oldDlighted = qfalse; + oldPshadowed = qfalse; + oldSort = -1; + depthRange = qfalse; + +#if 1 //def REACTION + depth[0] = 0.f; + depth[1] = 1.f; +#endif + + backEnd.pc.c_surfaces += numDrawSurfs; + + for (i = 0, drawSurf = drawSurfs ; i < numDrawSurfs ; i++, drawSurf++) { + if ( drawSurf->sort == oldSort ) { + if (backEnd.depthFill && shader && shader->sort != SS_OPAQUE) + continue; + + // fast path, same as previous sort + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + continue; + } + oldSort = drawSurf->sort; + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted, &pshadowed ); + + // + // change the tess parameters if needed + // a "entityMergable" shader is a shader that can have surfaces from seperate + // entities merged into a single batch, like smoke and blood puff sprites + if (shader != oldShader || fogNum != oldFogNum || dlighted != oldDlighted || pshadowed != oldPshadowed + || ( entityNum != oldEntityNum && !shader->entityMergable ) ) { + if (oldShader != NULL) { + RB_EndSurface(); + } + RB_BeginSurface( shader, fogNum ); + backEnd.pc.c_surfBatches++; + oldShader = shader; + oldFogNum = fogNum; + oldDlighted = dlighted; + oldPshadowed = pshadowed; + } + + if (backEnd.depthFill && shader && shader->sort != SS_OPAQUE) + continue; + + // + // change the modelview matrix if needed + // + if ( entityNum != oldEntityNum ) { + qboolean sunflare = qfalse; + depthRange = isCrosshair = qfalse; + +#ifdef REACTION + // if we were rendering to a FBO and the previous entity was a sunflare + // and the current one isn't, switch back to the main fbo + if (oldEntityNum != -1 && fbo && !backEnd.depthFill && + RF_SUNFLARE == (backEnd.refdef.entities[oldEntityNum].e.renderfx & RF_SUNFLARE) && + 0 == (backEnd.refdef.entities[entityNum].e.renderfx & RF_SUNFLARE)) + { + if (inQuery) { + inQuery = qfalse; + qglEndQueryARB(GL_SAMPLES_PASSED_ARB); + } + FBO_Bind(fbo); + qglDepthRange(depth[0], depth[1]); + } +#endif + + if ( entityNum != REFENTITYNUM_WORLD ) { + backEnd.currentEntity = &backEnd.refdef.entities[entityNum]; + backEnd.refdef.floatTime = originalTime - backEnd.currentEntity->e.shaderTime; + // we have to reset the shaderTime as well otherwise image animations start + // from the wrong frame + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + + // set up the transformation matrix + R_RotateForEntity( backEnd.currentEntity, &backEnd.viewParms, &backEnd.or ); + + // set up the dynamic lighting if needed + if ( backEnd.currentEntity->needDlights ) { + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.or ); + } + +#ifdef REACTION + // if the current entity is a sunflare + if(backEnd.currentEntity->e.renderfx & RF_SUNFLARE && !backEnd.depthFill) { + // if we're rendering to a fbo + if (fbo) { + VectorCopy(backEnd.currentEntity->e.origin, backEnd.sunFlarePos); + // switch FBO + FBO_Bind(tr.godRaysFbo); + + qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); + qglClear( GL_COLOR_BUFFER_BIT ); + + qglDepthRange(1.f, 1.f); + if (glRefConfig.occlusionQuery && !inQuery && !backEnd.viewHasSunFlare) { + inQuery = qtrue; + tr.sunFlareQueryActive[tr.sunFlareQueryIndex] = qtrue; + qglBeginQueryARB(GL_SAMPLES_PASSED_ARB, tr.sunFlareQuery[tr.sunFlareQueryIndex]); + } + sunflare = qtrue; + } else { + depthRange = qtrue; + } + } +#endif + + if(backEnd.currentEntity->e.renderfx & RF_DEPTHHACK) + { + // hack the depth range to prevent view model from poking into walls + depthRange = qtrue; + + if(backEnd.currentEntity->e.renderfx & RF_CROSSHAIR) + isCrosshair = qtrue; + } + } else { + backEnd.currentEntity = &tr.worldEntity; + backEnd.refdef.floatTime = originalTime; + backEnd.or = backEnd.viewParms.world; + // we have to reset the shaderTime as well otherwise image animations on + // the world (like water) continue with the wrong frame + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.or ); + } + + GL_SetModelviewMatrix( backEnd.or.modelMatrix ); + + // + // change depthrange. Also change projection matrix so first person weapon does not look like coming + // out of the screen. + // + if (oldDepthRange != depthRange || wasCrosshair != isCrosshair) + { + if (depthRange) + { + if(backEnd.viewParms.stereoFrame != STEREO_CENTER) + { + if(isCrosshair) + { + if(oldDepthRange) + { + // was not a crosshair but now is, change back proj matrix + GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix ); + } + } + else + { + viewParms_t temp = backEnd.viewParms; + + R_SetupProjection(&temp, r_znear->value, 0, qfalse); + + GL_SetProjectionMatrix( temp.projectionMatrix ); + } + } + +#if 1 //def REACTION + if(!oldDepthRange) + { + depth[0] = 0; + depth[1] = 0.3f; + qglDepthRange (0, 0.3); + } +#endif + } + else + { + if(!wasCrosshair && backEnd.viewParms.stereoFrame != STEREO_CENTER) + { + GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix ); + } + + if (!sunflare) + qglDepthRange (0, 1); +#if 1 //def REACTION + depth[0] = 0; + depth[1] = 1; +#endif + } + + oldDepthRange = depthRange; + wasCrosshair = isCrosshair; + } + + oldEntityNum = entityNum; + } + + // add the triangles for this surface + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + } + + backEnd.refdef.floatTime = originalTime; + + // draw the contents of the last shader batch + if (oldShader != NULL) { + RB_EndSurface(); + } + + if (inQuery) { + inQuery = qfalse; + qglEndQueryARB(GL_SAMPLES_PASSED_ARB); + } +#ifdef REACTION + // HACK: flip Z and render black to god rays buffer + if (backEnd.frameHasSunFlare && !backEnd.depthFill) + { + vec4_t black; + VectorSet4(black, 0, 0, 0, 1); + qglDepthRange (1, 1); + FBO_BlitFromTexture(tr.whiteImage, NULL, NULL, tr.godRaysFbo, NULL, NULL, black, GLS_DEPTHFUNC_GREATER); + } +#endif + + FBO_Bind(fbo); + + // go back to the world modelview matrix + + GL_SetModelviewMatrix( backEnd.viewParms.world.modelMatrix ); + //if ( depthRange ) { + qglDepthRange (0, 1); + //} +} + + +/* +============================================================================ + +RENDER BACK END THREAD FUNCTIONS + +============================================================================ +*/ + +/* +================ +RB_SetGL2D + +================ +*/ +void RB_SetGL2D (void) { + matrix_t matrix; + int width, height; + + if (backEnd.projection2D && backEnd.last2DFBO == glState.currentFBO) + return; + + backEnd.projection2D = qtrue; + backEnd.last2DFBO = glState.currentFBO; + + if (glState.currentFBO) + { + width = glState.currentFBO->width; + height = glState.currentFBO->height; + } + else + { + width = glConfig.vidWidth; + height = glConfig.vidHeight; + } + + // set 2D virtual screen size + qglViewport( 0, 0, width, height ); + qglScissor( 0, 0, width, height ); + + Matrix16Ortho(0, width, height, 0, 0, 1, matrix); + GL_SetProjectionMatrix(matrix); + Matrix16Identity(matrix); + GL_SetModelviewMatrix(matrix); + + GL_State( GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + qglDisable( GL_CULL_FACE ); + qglDisable( GL_CLIP_PLANE0 ); + + // set time for 2D shaders + backEnd.refdef.time = ri.Milliseconds(); + backEnd.refdef.floatTime = backEnd.refdef.time * 0.001f; + + // reset color scaling + backEnd.refdef.colorScale = 1.0f; +} + + +/* +============= +RE_StretchRaw + +FIXME: not exactly backend +Stretches a raw 32 bit power of 2 bitmap image over the given screen rectangle. +Used for cinematics. +============= +*/ +void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) { + int i, j; + int start, end; + shaderProgram_t *sp = &tr.textureColorShader; + vec4_t color; + + if ( !tr.registered ) { + return; + } + R_SyncRenderThread(); + + // we definately want to sync every frame for the cinematics + qglFinish(); + + start = 0; + if ( r_speeds->integer ) { + start = ri.Milliseconds(); + } + + // make sure rows and cols are powers of 2 + for ( i = 0 ; ( 1 << i ) < cols ; i++ ) { + } + for ( j = 0 ; ( 1 << j ) < rows ; j++ ) { + } + if ( ( 1 << i ) != cols || ( 1 << j ) != rows) { + ri.Error (ERR_DROP, "Draw_StretchRaw: size not a power of 2: %i by %i", cols, rows); + } + + GL_Bind( tr.scratchImage[client] ); + + // if the scratchImage isn't in the format we want, specify it as a new texture + 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 ); + 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, GL_CLAMP_TO_EDGE ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + } 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 ( r_speeds->integer ) { + end = ri.Milliseconds(); + ri.Printf( PRINT_ALL, "qglTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start ); + } + + // FIXME: HUGE hack + if (glRefConfig.framebufferObject && !glState.currentFBO) + { + if (backEnd.framePostProcessed) + { + FBO_Bind(tr.screenScratchFbo); + } + else + { + FBO_Bind(tr.renderFbo); + } + } + + RB_SetGL2D(); + + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.firstIndex = 0; + + tess.xyz[tess.numVertexes][0] = x; + tess.xyz[tess.numVertexes][1] = y; + tess.xyz[tess.numVertexes][2] = 0; + tess.xyz[tess.numVertexes][3] = 1; + tess.texCoords[tess.numVertexes][0][0] = 0.5f / cols; + tess.texCoords[tess.numVertexes][0][1] = 0.5f / rows; + tess.texCoords[tess.numVertexes][1][0] = 0; + tess.texCoords[tess.numVertexes][1][1] = 1; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = x + w; + tess.xyz[tess.numVertexes][1] = y; + tess.xyz[tess.numVertexes][2] = 0; + tess.xyz[tess.numVertexes][3] = 1; + tess.texCoords[tess.numVertexes][0][0] = (cols - 0.5f) / cols; + tess.texCoords[tess.numVertexes][0][1] = 0.5f / rows; + tess.texCoords[tess.numVertexes][1][0] = 0; + tess.texCoords[tess.numVertexes][1][1] = 1; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = x + w; + tess.xyz[tess.numVertexes][1] = y + h; + tess.xyz[tess.numVertexes][2] = 0; + tess.xyz[tess.numVertexes][3] = 1; + tess.texCoords[tess.numVertexes][0][0] = (cols - 0.5f) / cols; + tess.texCoords[tess.numVertexes][0][1] = (rows - 0.5f) / rows; + tess.texCoords[tess.numVertexes][1][0] = 0; + tess.texCoords[tess.numVertexes][1][1] = 1; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = x; + tess.xyz[tess.numVertexes][1] = y + h; + tess.xyz[tess.numVertexes][2] = 0; + tess.xyz[tess.numVertexes][3] = 1; + tess.texCoords[tess.numVertexes][0][0] = 0.5f / cols; + tess.texCoords[tess.numVertexes][0][1] = (rows - 0.5f) / rows; + tess.texCoords[tess.numVertexes][1][0] = 0; + tess.texCoords[tess.numVertexes][1][1] = 1; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 1; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 3; + + // FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function + RB_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD); + + sp = &tr.textureColorShader; + + GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD); + + GLSL_BindProgram(sp); + + GLSL_SetUniformMatrix16(sp, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + VectorSet4(color, 1, 1, 1, 1); + GLSL_SetUniformVec4(sp, TEXTURECOLOR_UNIFORM_COLOR, color); + + R_DrawElementsVBO(tess.numIndexes, tess.firstIndex); + + //R_BindNullVBO(); + //R_BindNullIBO(); + + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.firstIndex = 0; +} + +void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) { + + GL_Bind( tr.scratchImage[client] ); + + // if the scratchImage isn't in the format we want, specify it as a new texture + 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 ); + 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, GL_CLAMP_TO_EDGE ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + } 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 ); + } + } +} + + +/* +============= +RB_SetColor + +============= +*/ +const void *RB_SetColor( const void *data ) { + const setColorCommand_t *cmd; + + cmd = (const setColorCommand_t *)data; + + backEnd.color2D[0] = cmd->color[0] * 255; + backEnd.color2D[1] = cmd->color[1] * 255; + backEnd.color2D[2] = cmd->color[2] * 255; + backEnd.color2D[3] = cmd->color[3] * 255; + + return (const void *)(cmd + 1); +} + +/* +============= +RB_StretchPic +============= +*/ +const void *RB_StretchPic ( const void *data ) { + const stretchPicCommand_t *cmd; + shader_t *shader; + int numVerts, numIndexes; + + cmd = (const stretchPicCommand_t *)data; + + // FIXME: HUGE hack + if (glRefConfig.framebufferObject && !glState.currentFBO) + { + if (backEnd.framePostProcessed) + { + FBO_Bind(tr.screenScratchFbo); + } + else + { + FBO_Bind(tr.renderFbo); + } + } + + RB_SetGL2D(); + + shader = cmd->shader; + if ( shader != tess.shader ) { + if ( tess.numIndexes ) { + RB_EndSurface(); + } + backEnd.currentEntity = &backEnd.entity2D; + RB_BeginSurface( shader, 0 ); + } + + RB_CHECKOVERFLOW( 4, 6 ); + numVerts = tess.numVertexes; + numIndexes = tess.numIndexes; + + tess.numVertexes += 4; + tess.numIndexes += 6; + + tess.indexes[ numIndexes ] = numVerts + 3; + tess.indexes[ numIndexes + 1 ] = numVerts + 0; + tess.indexes[ numIndexes + 2 ] = numVerts + 2; + tess.indexes[ numIndexes + 3 ] = numVerts + 2; + tess.indexes[ numIndexes + 4 ] = numVerts + 0; + tess.indexes[ numIndexes + 5 ] = numVerts + 1; + + { + vec4_t color; + + VectorScale4(backEnd.color2D, 1.0f / 255.0f, color); + + VectorCopy4(color, tess.vertexColors[ numVerts ]); + VectorCopy4(color, tess.vertexColors[ numVerts + 1]); + VectorCopy4(color, tess.vertexColors[ numVerts + 2]); + VectorCopy4(color, tess.vertexColors[ numVerts + 3 ]); + } + + tess.xyz[ numVerts ][0] = cmd->x; + tess.xyz[ numVerts ][1] = cmd->y; + tess.xyz[ numVerts ][2] = 0; + + tess.texCoords[ numVerts ][0][0] = cmd->s1; + tess.texCoords[ numVerts ][0][1] = cmd->t1; + + tess.xyz[ numVerts + 1 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 1 ][1] = cmd->y; + tess.xyz[ numVerts + 1 ][2] = 0; + + tess.texCoords[ numVerts + 1 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 1 ][0][1] = cmd->t1; + + tess.xyz[ numVerts + 2 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 2 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 2 ][2] = 0; + + tess.texCoords[ numVerts + 2 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 2 ][0][1] = cmd->t2; + + tess.xyz[ numVerts + 3 ][0] = cmd->x; + tess.xyz[ numVerts + 3 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 3 ][2] = 0; + + tess.texCoords[ numVerts + 3 ][0][0] = cmd->s1; + tess.texCoords[ numVerts + 3 ][0][1] = cmd->t2; + + return (const void *)(cmd + 1); +} + + +/* +============= +RB_DrawSurfs + +============= +*/ +const void *RB_DrawSurfs( const void *data ) { + const drawSurfsCommand_t *cmd; + + // finish any 2D drawing if needed + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + cmd = (const drawSurfsCommand_t *)data; + + backEnd.refdef = cmd->refdef; + backEnd.viewParms = cmd->viewParms; + + // clear the z buffer, set the modelview, etc + RB_BeginDrawingView (); + + if ((backEnd.viewParms.flags & VPF_DEPTHCLAMP) && glRefConfig.depthClamp) + { + qglEnable(GL_DEPTH_CLAMP); + } + + if (!(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) && (r_depthPrepass->integer || (backEnd.viewParms.flags & VPF_DEPTHSHADOW))) + { + FBO_t *oldFbo = glState.currentFBO; + + backEnd.depthFill = qtrue; + qglColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs ); + qglColorMask(!backEnd.colorMask[0], !backEnd.colorMask[1], !backEnd.colorMask[2], !backEnd.colorMask[3]); + backEnd.depthFill = qfalse; + + // If we're using multisampling, resolve the depth first + if (tr.msaaResolveFbo) + { + FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_DEPTH_BUFFER_BIT, GL_NEAREST); + } + + if (r_ssao->integer) + { + vec2_t srcTexScale; + vec4_t color; + vec4_t quadVerts[4]; + vec2_t texCoords[4]; + vec2_t invTexRes; + + matrix_t idmatrix; + + srcTexScale[0] = srcTexScale[1] = 1.0f; + color[0] = color[1] = color[2] = color[3] = 1.0f; + + FBO_Bind(tr.hdrDepthFbo); + + qglViewport(0, 0, tr.hdrDepthFbo->width, tr.hdrDepthFbo->height); + qglScissor(0, 0, tr.hdrDepthFbo->width, tr.hdrDepthFbo->height); + + Matrix16Identity(idmatrix); + + VectorSet4(quadVerts[0], -1, 1, 0, 1); + VectorSet4(quadVerts[1], 1, 1, 0, 1); + VectorSet4(quadVerts[2], 1, -1, 0, 1); + VectorSet4(quadVerts[3], -1, -1, 0, 1); + + texCoords[0][0] = 0; texCoords[0][1] = 1; + texCoords[1][0] = 1; texCoords[1][1] = 1; + texCoords[2][0] = 1; texCoords[2][1] = 0; + texCoords[3][0] = 0; texCoords[3][1] = 0; + + invTexRes[0] = 0.0f; + invTexRes[1] = 0.0f; + + GL_State( GLS_DEPTHTEST_DISABLE ); + + GLSL_BindProgram(&tr.textureColorShader); + + GL_BindToTMU(tr.renderDepthImage, TB_COLORMAP); + + GLSL_SetUniformMatrix16(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, idmatrix); + GLSL_SetUniformVec4(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_COLOR, color); + GLSL_SetUniformVec2(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_INVTEXRES, invTexRes); + GLSL_SetUniformVec2(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_AUTOEXPOSUREMINMAX, tr.refdef.autoExposureMinMax); + GLSL_SetUniformVec3(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_TONEMINAVGMAXLINEAR, tr.refdef.toneMinAvgMaxLinear); + + RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); + } + + if (backEnd.viewParms.flags & VPF_USESUNLIGHT) + { + vec4_t quadVerts[4]; + vec2_t texCoords[4]; + + FBO_Bind(tr.screenShadowFbo); + + qglViewport(0, 0, tr.screenShadowFbo->width, tr.screenShadowFbo->height); + qglScissor(0, 0, tr.screenShadowFbo->width, tr.screenShadowFbo->height); + + VectorSet4(quadVerts[0], -1, 1, 0, 1); + VectorSet4(quadVerts[1], 1, 1, 0, 1); + VectorSet4(quadVerts[2], 1, -1, 0, 1); + VectorSet4(quadVerts[3], -1, -1, 0, 1); + + texCoords[0][0] = 0; texCoords[0][1] = 1; + texCoords[1][0] = 1; texCoords[1][1] = 1; + texCoords[2][0] = 1; texCoords[2][1] = 0; + texCoords[3][0] = 0; texCoords[3][1] = 0; + + GL_State( GLS_DEPTHTEST_DISABLE ); + + GLSL_BindProgram(&tr.shadowmaskShader); + + GL_BindToTMU(tr.renderDepthImage, TB_COLORMAP); + GL_BindToTMU(tr.sunShadowDepthImage[0], TB_SHADOWMAP); + GL_BindToTMU(tr.sunShadowDepthImage[1], TB_SHADOWMAP2); + GL_BindToTMU(tr.sunShadowDepthImage[2], TB_SHADOWMAP3); + + GLSL_SetUniformMatrix16(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMVP, backEnd.refdef.sunShadowMvp[0]); + GLSL_SetUniformMatrix16(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMVP2, backEnd.refdef.sunShadowMvp[1]); + GLSL_SetUniformMatrix16(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMVP3, backEnd.refdef.sunShadowMvp[2]); + + GLSL_SetUniformVec3(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWORIGIN, backEnd.refdef.vieworg); + { + vec4_t viewInfo; + vec3_t viewVector; + + float zmax = backEnd.viewParms.zFar; + float ymax = zmax * tan(backEnd.viewParms.fovY * M_PI / 360.0f); + float xmax = zmax * tan(backEnd.viewParms.fovX * M_PI / 360.0f); + + float zmin = r_znear->value; + + VectorScale(backEnd.refdef.viewaxis[0], zmax, viewVector); + GLSL_SetUniformVec3(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWFORWARD, viewVector); + VectorScale(backEnd.refdef.viewaxis[1], xmax, viewVector); + GLSL_SetUniformVec3(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWLEFT, viewVector); + VectorScale(backEnd.refdef.viewaxis[2], ymax, viewVector); + GLSL_SetUniformVec3(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWUP, viewVector); + + VectorSet4(viewInfo, zmax / zmin, zmax, 0.0, 0.0); + + GLSL_SetUniformVec4(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWINFO, viewInfo); + } + + + RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); + } + + if (r_ssao->integer) + { + vec4_t quadVerts[4]; + vec2_t texCoords[4]; + + FBO_Bind(tr.quarterFbo[0]); + + qglViewport(0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height); + qglScissor(0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height); + + VectorSet4(quadVerts[0], -1, 1, 0, 1); + VectorSet4(quadVerts[1], 1, 1, 0, 1); + VectorSet4(quadVerts[2], 1, -1, 0, 1); + VectorSet4(quadVerts[3], -1, -1, 0, 1); + + texCoords[0][0] = 0; texCoords[0][1] = 1; + texCoords[1][0] = 1; texCoords[1][1] = 1; + texCoords[2][0] = 1; texCoords[2][1] = 0; + texCoords[3][0] = 0; texCoords[3][1] = 0; + + GL_State( GLS_DEPTHTEST_DISABLE ); + + GLSL_BindProgram(&tr.ssaoShader); + + GL_BindToTMU(tr.hdrDepthImage, TB_COLORMAP); + + { + vec4_t viewInfo; + + float zmax = backEnd.viewParms.zFar; + float zmin = r_znear->value; + + VectorSet4(viewInfo, zmax / zmin, zmax, 0.0, 0.0); + + GLSL_SetUniformVec4(&tr.ssaoShader, SSAO_UNIFORM_VIEWINFO, viewInfo); + } + + RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); + + + FBO_Bind(tr.quarterFbo[1]); + + qglViewport(0, 0, tr.quarterFbo[1]->width, tr.quarterFbo[1]->height); + qglScissor(0, 0, tr.quarterFbo[1]->width, tr.quarterFbo[1]->height); + + GLSL_BindProgram(&tr.depthBlurShader[0]); + + GL_BindToTMU(tr.quarterImage[0], TB_COLORMAP); + GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP); + + { + vec4_t viewInfo; + + float zmax = backEnd.viewParms.zFar; + float zmin = r_znear->value; + + VectorSet4(viewInfo, zmax / zmin, zmax, 0.0, 0.0); + + GLSL_SetUniformVec4(&tr.depthBlurShader[0], DEPTHBLUR_UNIFORM_VIEWINFO, viewInfo); + } + + RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); + + + FBO_Bind(tr.screenSsaoFbo); + + qglViewport(0, 0, tr.screenSsaoFbo->width, tr.screenSsaoFbo->height); + qglScissor(0, 0, tr.screenSsaoFbo->width, tr.screenSsaoFbo->height); + + GLSL_BindProgram(&tr.depthBlurShader[1]); + + GL_BindToTMU(tr.quarterImage[1], TB_COLORMAP); + GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP); + + { + vec4_t viewInfo; + + float zmax = backEnd.viewParms.zFar; + float zmin = r_znear->value; + + VectorSet4(viewInfo, zmax / zmin, zmax, 0.0, 0.0); + + GLSL_SetUniformVec4(&tr.depthBlurShader[1], DEPTHBLUR_UNIFORM_VIEWINFO, viewInfo); + } + + + RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); + } + + // reset viewport and scissor + FBO_Bind(oldFbo); + SetViewportAndScissor(); + } + + if ((backEnd.viewParms.flags & VPF_DEPTHCLAMP) && glRefConfig.depthClamp) + { + qglDisable(GL_DEPTH_CLAMP); + } + + if (!(backEnd.viewParms.flags & VPF_DEPTHSHADOW)) + { + RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs ); + +#if 0 + RB_DrawSun(); +#endif + // darken down any stencil shadows + RB_ShadowFinish(); + + // add light flares on lights that aren't obscured + RB_RenderFlares(); + } + + if (glRefConfig.framebufferObject) + FBO_Bind(NULL); + + return (const void *)(cmd + 1); +} + + +/* +============= +RB_DrawBuffer + +============= +*/ +const void *RB_DrawBuffer( const void *data ) { + const drawBufferCommand_t *cmd; + + cmd = (const drawBufferCommand_t *)data; + + qglDrawBuffer( cmd->buffer ); + + // clear screen for debugging + if ( r_clear->integer ) { + qglClearColor( 1, 0, 0.5, 1 ); + qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + } + + return (const void *)(cmd + 1); +} + +/* +=============== +RB_ShowImages + +Draw all the images to the screen, on top of whatever +was there. This is used to test for texture thrashing. + +Also called by RE_EndRegistration +=============== +*/ +void RB_ShowImages( void ) { + int i; + image_t *image; + float x, y, w, h; + int start, end; + + RB_SetGL2D(); + + qglClear( GL_COLOR_BUFFER_BIT ); + + qglFinish(); + + start = ri.Milliseconds(); + + for ( i=0 ; iinteger == 2 ) { + w *= image->uploadWidth / 512.0f; + h *= image->uploadHeight / 512.0f; + } + + { + vec4_t quadVerts[4]; + + GL_Bind(image); + + VectorSet4(quadVerts[0], x, y, 0, 1); + VectorSet4(quadVerts[1], x + w, y, 0, 1); + VectorSet4(quadVerts[2], x + w, y + h, 0, 1); + VectorSet4(quadVerts[3], x, y + h, 0, 1); + + RB_InstantQuad(quadVerts); + } + } + + qglFinish(); + + end = ri.Milliseconds(); + ri.Printf( PRINT_ALL, "%i msec to draw all images\n", end - start ); + +} + +/* +============= +RB_ColorMask + +============= +*/ +const void *RB_ColorMask(const void *data) +{ + const colorMaskCommand_t *cmd = data; + + if (glRefConfig.framebufferObject) + { + // reverse color mask, so 0 0 0 0 is the default + backEnd.colorMask[0] = !cmd->rgba[0]; + backEnd.colorMask[1] = !cmd->rgba[1]; + backEnd.colorMask[2] = !cmd->rgba[2]; + backEnd.colorMask[3] = !cmd->rgba[3]; + } + + qglColorMask(cmd->rgba[0], cmd->rgba[1], cmd->rgba[2], cmd->rgba[3]); + + return (const void *)(cmd + 1); +} + +/* +============= +RB_ClearDepth + +============= +*/ +const void *RB_ClearDepth(const void *data) +{ + const clearDepthCommand_t *cmd = data; + + if(tess.numIndexes) + RB_EndSurface(); + + // texture swapping test + if (r_showImages->integer) + RB_ShowImages(); + + if (backEnd.framePostProcessed && (backEnd.refdef.rdflags & RDF_NOWORLDMODEL)) + { + FBO_Bind(tr.screenScratchFbo); + } + else + { + FBO_Bind(tr.renderFbo); + } + qglClear(GL_DEPTH_BUFFER_BIT); + + // if we're doing MSAA, clear the depth texture for the resolve buffer + if (tr.msaaResolveFbo) + { + FBO_Bind(tr.screenScratchFbo); + qglClear(GL_DEPTH_BUFFER_BIT); + } + + + return (const void *)(cmd + 1); +} + +/* +============= +RB_SwapBuffers + +============= +*/ +const void *RB_SwapBuffers( const void *data ) { + const swapBuffersCommand_t *cmd; + + // finish any 2D drawing if needed + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + // texture swapping test + if ( r_showImages->integer ) { + RB_ShowImages(); + } + + cmd = (const swapBuffersCommand_t *)data; + + // we measure overdraw by reading back the stencil buffer and + // counting up the number of increments that have happened + if ( r_measureOverdraw->integer ) { + int i; + long sum = 0; + unsigned char *stencilReadback; + + stencilReadback = ri.Hunk_AllocateTempMemory( glConfig.vidWidth * glConfig.vidHeight ); + qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback ); + + for ( i = 0; i < glConfig.vidWidth * glConfig.vidHeight; i++ ) { + sum += stencilReadback[i]; + } + + backEnd.pc.c_overDraw += sum; + ri.Hunk_FreeTempMemory( stencilReadback ); + } + + if (glRefConfig.framebufferObject) + { + // copy final image to screen + vec4_t color; + + if (backEnd.framePostProcessed) + { + // frame was postprocessed into screen fbo, copy from there + } + else if (!glRefConfig.framebuffer_srgb) + { + // Copy render to screenscratch, possibly resolving MSAA + FBO_FastBlit(tr.renderFbo, NULL, tr.screenScratchFbo, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + else + { + FBO_t *srcFbo = tr.renderFbo; + + if (tr.msaaResolveFbo) + { + // Resolve the MSAA before copying + FBO_FastBlit(srcFbo, NULL, tr.msaaResolveFbo, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + srcFbo = tr.msaaResolveFbo; + } + + // need to copy from resolve to screenscratch to fix gamma + FBO_Blit(srcFbo, NULL, NULL, tr.screenScratchFbo, NULL, NULL, NULL, 0); + } + + color[0] = + color[1] = + color[2] = pow(2, tr.overbrightBits); //exp2(tr.overbrightBits); + color[3] = 1.0f; + + // turn off colormask when copying final image + if (backEnd.colorMask[0] || backEnd.colorMask[1] || backEnd.colorMask[2] || backEnd.colorMask[3]) + qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + FBO_Blit(tr.screenScratchFbo, NULL, NULL, NULL, NULL, NULL, color, 0); + + if (backEnd.colorMask[0] || backEnd.colorMask[1] || backEnd.colorMask[2] || backEnd.colorMask[3]) + qglColorMask(!backEnd.colorMask[0], !backEnd.colorMask[1], !backEnd.colorMask[2], !backEnd.colorMask[3]); + } + + if ( !glState.finishCalled ) { + qglFinish(); + } + + GLimp_LogComment( "***************** RB_SwapBuffers *****************\n\n\n" ); + + GLimp_EndFrame(); + + backEnd.framePostProcessed = qfalse; + backEnd.projection2D = qfalse; +#ifdef REACTION + backEnd.frameHasSunFlare = qfalse; +#endif + + return (const void *)(cmd + 1); +} + +/* +============= +RB_CapShadowMap + +============= +*/ +const void *RB_CapShadowMap(const void *data) +{ + const capShadowmapCommand_t *cmd = data; + + if (cmd->map != -1) + { + GL_SelectTexture(0); + if (cmd->cubeSide != -1) + { + GL_BindCubemap(tr.shadowCubemaps[cmd->map]); + qglCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cmd->cubeSide, 0, GL_RGBA8, backEnd.refdef.x, glConfig.vidHeight - ( backEnd.refdef.y + PSHADOW_MAP_SIZE ), PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, 0); + } + else + { + GL_Bind(tr.pshadowMaps[cmd->map]); + qglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, backEnd.refdef.x, glConfig.vidHeight - ( backEnd.refdef.y + PSHADOW_MAP_SIZE ), PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, 0); + } + } + + return (const void *)(cmd + 1); +} + + + +/* +============= +RB_PostProcess + +============= +*/ +const void *RB_PostProcess(const void *data) +{ + const postProcessCommand_t *cmd = data; + FBO_t *srcFbo; + qboolean autoExposure; + + if (!glRefConfig.framebufferObject) + { + // do nothing + backEnd.framePostProcessed = qtrue; + + return (const void *)(cmd + 1); + } + + srcFbo = tr.renderFbo; + if (tr.msaaResolveFbo) + { + // Resolve the MSAA before anything else + FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST); + srcFbo = tr.msaaResolveFbo; + } + + if (r_postProcess->integer && r_ssao->integer) + { + vec4i_t dstBox; + VectorSet4(dstBox, 0, 0, srcFbo->width, srcFbo->height); + FBO_BlitFromTexture(tr.screenSsaoImage, NULL, NULL, srcFbo, dstBox, NULL, NULL, GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO); + } + + if (r_postProcess->integer && (r_toneMap->integer || r_forceToneMap->integer)) + { + autoExposure = r_autoExposure->integer || r_forceAutoExposure; + RB_ToneMap(srcFbo, autoExposure); + } + else if (!glRefConfig.framebuffer_srgb && r_cameraExposure->value == 0.0f) + { + FBO_FastBlit(srcFbo, NULL, tr.screenScratchFbo, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + else + { + vec4_t color; + + color[0] = + color[1] = + color[2] = pow(2, r_cameraExposure->value); //exp2(r_cameraExposure->value); + color[3] = 1.0f; + + FBO_Blit(srcFbo, NULL, NULL, tr.screenScratchFbo, NULL, NULL, color, 0); + } + +#ifdef REACTION + if (r_postProcess->integer && glRefConfig.framebufferObject) + { + RB_GodRays(); + + if (1) + RB_BokehBlur(backEnd.refdef.blurFactor); + else + RB_GaussianBlur(backEnd.refdef.blurFactor); + } +#endif + + if (0) + { + vec4i_t dstBox; + VectorSet4(dstBox, 0, 0, 128, 128); + FBO_BlitFromTexture(tr.sunShadowDepthImage[0], NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0); + VectorSet4(dstBox, 128, 0, 128, 128); + FBO_BlitFromTexture(tr.sunShadowDepthImage[1], NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0); + VectorSet4(dstBox, 256, 0, 128, 128); + FBO_BlitFromTexture(tr.sunShadowDepthImage[2], NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0); + } + + if (0) + { + vec4i_t dstBox; + VectorSet4(dstBox, 256, tr.screenScratchFbo->height - 256, 256, 256); + FBO_BlitFromTexture(tr.renderDepthImage, NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0); + VectorSet4(dstBox, 512, tr.screenScratchFbo->height - 256, 256, 256); + FBO_BlitFromTexture(tr.screenShadowImage, NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0); + } + + if (0) + { + vec4i_t dstBox; + VectorSet4(dstBox, 256, tr.screenScratchFbo->height - 256, 256, 256); + FBO_BlitFromTexture(tr.renderImage, NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0); + } + + backEnd.framePostProcessed = qtrue; + + return (const void *)(cmd + 1); +} + +/* +==================== +RB_ExecuteRenderCommands + +This function will be called synchronously if running without +smp extensions, or asynchronously by another thread. +==================== +*/ +void RB_ExecuteRenderCommands( const void *data ) { + int t1, t2; + + t1 = ri.Milliseconds (); + + if ( !r_smp->integer || data == backEndData[0]->commands.cmds ) { + backEnd.smpFrame = 0; + } else { + backEnd.smpFrame = 1; + } + + while ( 1 ) { + data = PADP(data, sizeof(void *)); + + switch ( *(const int *)data ) { + case RC_SET_COLOR: + data = RB_SetColor( data ); + break; + case RC_STRETCH_PIC: + data = RB_StretchPic( data ); + break; + case RC_DRAW_SURFS: + data = RB_DrawSurfs( data ); + break; + case RC_DRAW_BUFFER: + data = RB_DrawBuffer( data ); + break; + case RC_SWAP_BUFFERS: + data = RB_SwapBuffers( data ); + break; + case RC_SCREENSHOT: + data = RB_TakeScreenshotCmd( data ); + break; + case RC_VIDEOFRAME: + data = RB_TakeVideoFrameCmd( data ); + break; + case RC_COLORMASK: + data = RB_ColorMask(data); + break; + case RC_CLEARDEPTH: + data = RB_ClearDepth(data); + break; + case RC_CAPSHADOWMAP: + data = RB_CapShadowMap(data); + break; + case RC_POSTPROCESS: + data = RB_PostProcess(data); + break; + case RC_END_OF_LIST: + default: + // stop rendering on this thread + t2 = ri.Milliseconds (); + backEnd.pc.msec = t2 - t1; + return; + } + } + +} + + +/* +================ +RB_RenderThread +================ +*/ +void RB_RenderThread( void ) { + const void *data; + + // wait for either a rendering command or a quit command + while ( 1 ) { + // sleep until we have work to do + data = GLimp_RendererSleep(); + + if ( !data ) { + return; // all done, renderer is shutting down + } + + renderThreadActive = qtrue; + + RB_ExecuteRenderCommands( data ); + + renderThreadActive = qfalse; + } +} + + diff --git a/src/rend2/tr_bsp.c b/src/rend2/tr_bsp.c new file mode 100644 index 00000000..de9a47af --- /dev/null +++ b/src/rend2/tr_bsp.c @@ -0,0 +1,3321 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_map.c + +#include "tr_local.h" + +/* + +Loads and prepares a map file for scene rendering. + +A single entry point: + +void RE_LoadWorldMap( const char *name ); + +*/ + +static world_t s_worldData; +static byte *fileBase; + +int c_subdivisions; +int c_gridVerts; + +//=============================================================================== + +static void HSVtoRGB( float h, float s, float v, float rgb[3] ) +{ + int i; + float f; + float p, q, t; + + h *= 5; + + i = floor( h ); + f = h - i; + + p = v * ( 1 - s ); + q = v * ( 1 - s * f ); + t = v * ( 1 - s * ( 1 - f ) ); + + switch ( i ) + { + case 0: + rgb[0] = v; + rgb[1] = t; + rgb[2] = p; + break; + case 1: + rgb[0] = q; + rgb[1] = v; + rgb[2] = p; + break; + case 2: + rgb[0] = p; + rgb[1] = v; + rgb[2] = t; + break; + case 3: + rgb[0] = p; + rgb[1] = q; + rgb[2] = v; + break; + case 4: + rgb[0] = t; + rgb[1] = p; + rgb[2] = v; + break; + case 5: + rgb[0] = v; + rgb[1] = p; + rgb[2] = q; + break; + } +} + +/* +=============== +R_ColorShiftLightingBytes + +=============== +*/ +static void R_ColorShiftLightingBytes( byte in[4], byte out[4] ) { + int shift, r, g, b; + + // shift the color data based on overbright range + shift = r_mapOverBrightBits->integer - tr.overbrightBits; + + // shift the data based on overbright range + r = in[0] << shift; + g = in[1] << shift; + b = in[2] << shift; + + // normalize by color instead of saturating to white + if ( ( r | g | b ) > 255 ) { + int max; + + max = r > g ? r : g; + max = max > b ? max : b; + r = r * 255 / max; + g = g * 255 / max; + b = b * 255 / max; + } + + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = in[3]; +} + + +/* +=============== +R_ColorShiftLightingBytes + +=============== +*/ +static void R_ColorShiftLightingFloats(float in[4], float out[4], float scale ) +{ + scale *= pow(2.0f, r_mapOverBrightBits->integer - tr.overbrightBits); + + out[0] = in[0] * scale; + out[1] = in[1] * scale; + out[2] = in[2] * scale; + out[3] = in[3]; +} + + +void ColorToRGBE(const vec3_t color, unsigned char rgbe[4]) +{ + vec3_t sample; + float maxComponent; + int e; + + VectorCopy(color, sample); + + maxComponent = sample[0]; + if(sample[1] > maxComponent) + maxComponent = sample[1]; + if(sample[2] > maxComponent) + maxComponent = sample[2]; + + if(maxComponent < 1e-32) + { + rgbe[0] = 0; + rgbe[1] = 0; + rgbe[2] = 0; + rgbe[3] = 0; + } + else + { +#if 0 + maxComponent = frexp(maxComponent, &e) * 255.0 / maxComponent; + rgbe[0] = (unsigned char) (sample[0] * maxComponent); + rgbe[1] = (unsigned char) (sample[1] * maxComponent); + rgbe[2] = (unsigned char) (sample[2] * maxComponent); + rgbe[3] = (unsigned char) (e + 128); +#else + e = ceil(log(maxComponent) / log(2.0f));//ceil(log2(maxComponent)); + VectorScale(sample, 1.0 / pow(2.0f, e)/*exp2(e)*/, sample); + + rgbe[0] = (unsigned char) (sample[0] * 255); + rgbe[1] = (unsigned char) (sample[1] * 255); + rgbe[2] = (unsigned char) (sample[2] * 255); + rgbe[3] = (unsigned char) (e + 128); +#endif + } +} + + +void ColorToRGBA16F(const vec3_t color, unsigned short rgba16f[4]) +{ + rgba16f[0] = FloatToHalf(color[0]); + rgba16f[1] = FloatToHalf(color[1]); + rgba16f[2] = FloatToHalf(color[2]); + rgba16f[3] = FloatToHalf(1.0f); +} + + +/* +=============== +R_LoadLightmaps + +=============== +*/ +#define DEFAULT_LIGHTMAP_SIZE 128 +#define MAX_LIGHTMAP_PAGES 2 +static void R_LoadLightmaps( lump_t *l, lump_t *surfs ) { + byte *buf, *buf_p; + dsurface_t *surf; + int len; + byte *image; + int i, j, numLightmaps, textureInternalFormat = 0; + float maxIntensity = 0; + double sumIntensity = 0; + + len = l->filelen; + if ( !len ) { + return; + } + buf = fileBase + l->fileofs; + + // we are about to upload textures + R_SyncRenderThread(); + + tr.lightmapSize = DEFAULT_LIGHTMAP_SIZE; + numLightmaps = len / (tr.lightmapSize * tr.lightmapSize * 3); + + // check for deluxe mapping + if (numLightmaps <= 1) + { + tr.worldDeluxeMapping = qfalse; + } + else + { + tr.worldDeluxeMapping = qtrue; + for( i = 0, surf = (dsurface_t *)(fileBase + surfs->fileofs); + i < surfs->filelen / sizeof(dsurface_t); i++, surf++ ) { + int lightmapNum = LittleLong( surf->lightmapNum ); + + if ( lightmapNum >= 0 && (lightmapNum & 1) != 0 ) { + tr.worldDeluxeMapping = qfalse; + break; + } + } + } + + image = ri.Malloc(tr.lightmapSize * tr.lightmapSize * 4 * 2); + + if (tr.worldDeluxeMapping) + numLightmaps >>= 1; + + if(numLightmaps == 1) + { + //FIXME: HACK: maps with only one lightmap turn up fullbright for some reason. + //this avoids this, but isn't the correct solution. + numLightmaps++; + } + else if (r_mergeLightmaps->integer && numLightmaps >= 1024 ) + { + // FIXME: fat light maps don't support more than 1024 light maps + ri.Printf(PRINT_WARNING, "WARNING: number of lightmaps > 1024\n"); + numLightmaps = 1024; + } + + // use fat lightmaps of an appropriate size + if (r_mergeLightmaps->integer) + { + tr.fatLightmapSize = 512; + tr.fatLightmapStep = tr.fatLightmapSize / tr.lightmapSize; + + // at most MAX_LIGHTMAP_PAGES + while (tr.fatLightmapStep * tr.fatLightmapStep * MAX_LIGHTMAP_PAGES < numLightmaps && tr.fatLightmapSize != glConfig.maxTextureSize ) + { + tr.fatLightmapSize <<= 1; + tr.fatLightmapStep = tr.fatLightmapSize / tr.lightmapSize; + } + + tr.numLightmaps = numLightmaps / (tr.fatLightmapStep * tr.fatLightmapStep); + + if (numLightmaps % (tr.fatLightmapStep * tr.fatLightmapStep) != 0) + tr.numLightmaps++; + } + else + { + tr.numLightmaps = numLightmaps; + } + + tr.lightmaps = ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low ); + + if (tr.worldDeluxeMapping) + { + tr.deluxemaps = ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low ); + } + + if (r_hdr->integer && glRefConfig.textureFloat && glRefConfig.halfFloatPixel) + textureInternalFormat = GL_RGBA16F_ARB; + + if (r_mergeLightmaps->integer) + { + for (i = 0; i < tr.numLightmaps; i++) + { + tr.lightmaps[i] = R_CreateImage(va("_fatlightmap%d", i), NULL, tr.fatLightmapSize, tr.fatLightmapSize, IMGTYPE_COLORALPHA, IMGFLAG_NOLIGHTSCALE | IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, textureInternalFormat ); + + if (tr.worldDeluxeMapping) + { + tr.deluxemaps[i] = R_CreateImage(va("_fatdeluxemap%d", i), NULL, tr.fatLightmapSize, tr.fatLightmapSize, IMGTYPE_DELUXE, IMGFLAG_NOLIGHTSCALE | IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, 0 ); + } + } + } + + for(i = 0; i < numLightmaps; i++) + { + int xoff = 0, yoff = 0; + int lightmapnum = i; + // expand the 24 bit on-disk to 32 bit + + if (r_mergeLightmaps->integer) + { + int lightmaponpage = i % (tr.fatLightmapStep * tr.fatLightmapStep); + xoff = (lightmaponpage % tr.fatLightmapStep) * tr.lightmapSize; + yoff = (lightmaponpage / tr.fatLightmapStep) * tr.lightmapSize; + + lightmapnum /= (tr.fatLightmapStep * tr.fatLightmapStep); + } + + // if (tr.worldLightmapping) + { + char filename[MAX_QPATH]; + byte *hdrLightmap = NULL; + float lightScale = 1.0f; + int size = 0; + + // look for hdr lightmaps + if (r_hdr->integer) + { + Com_sprintf( filename, sizeof( filename ), "maps/%s/lm_%04d.hdr", s_worldData.baseName, i * (tr.worldDeluxeMapping ? 2 : 1) ); + //ri.Printf(PRINT_ALL, "looking for %s\n", filename); + + size = ri.FS_ReadFile(filename, (void **)&hdrLightmap); + } + + if (hdrLightmap) + { + byte *p = hdrLightmap; + //ri.Printf(PRINT_ALL, "found!\n"); + + /* FIXME: don't just skip over this header and actually parse it */ + while (size && !(*p == '\n' && *(p+1) == '\n')) + { + size--; + p++; + } + + if (!size) + ri.Error(ERR_DROP, "Bad header for %s!\n", filename); + + size -= 2; + p += 2; + + while (size && !(*p == '\n')) + { + size--; + p++; + } + + size--; + p++; + + buf_p = (byte *)p; + +#if 0 // HDRFILE_RGBE + if (size != tr.lightmapSize * tr.lightmapSize * 4) + ri.Error(ERR_DROP, "Bad size for %s (%i)!\n", filename, size); +#else // HDRFILE_FLOAT + if (size != tr.lightmapSize * tr.lightmapSize * 12) + ri.Error(ERR_DROP, "Bad size for %s (%i)!\n", filename, size); +#endif + } + else + { + if (tr.worldDeluxeMapping) + buf_p = buf + (i * 2) * tr.lightmapSize * tr.lightmapSize * 3; + else + buf_p = buf + i * tr.lightmapSize * tr.lightmapSize * 3; + } + + lightScale = pow(2, r_mapOverBrightBits->integer - tr.overbrightBits - 8); //exp2(r_mapOverBrightBits->integer - tr.overbrightBits - 8); + + for ( j = 0 ; j < tr.lightmapSize * tr.lightmapSize; j++ ) + { + if (r_hdr->integer) + { + float color[3]; + + if (hdrLightmap) + { +#if 0 // HDRFILE_RGBE + float exponent = exp2(buf_p[j*4+3] - 128); + + color[0] = buf_p[j*4+0] * exponent; + color[1] = buf_p[j*4+1] * exponent; + color[2] = buf_p[j*4+2] * exponent; +#else // HDRFILE_FLOAT + memcpy(color, &buf_p[j*12], 12); + + color[0] = LittleFloat(color[0]); + color[1] = LittleFloat(color[1]); + color[2] = LittleFloat(color[2]); +#endif + } + else + { + //hack: convert LDR lightmap to HDR one + color[0] = (buf_p[j*3+0] + 1.0f); + color[1] = (buf_p[j*3+1] + 1.0f); + color[2] = (buf_p[j*3+2] + 1.0f); + + // if under an arbitrary value (say 12) grey it out + // this prevents weird splotches in dimly lit areas + if (color[0] + color[1] + color[2] < 12.0f) + { + float avg = (color[0] + color[1] + color[2]) * 0.3333f; + color[0] = avg; + color[1] = avg; + color[2] = avg; + } + } + + VectorScale(color, lightScale, color); + + if (glRefConfig.textureFloat && glRefConfig.halfFloatPixel) + ColorToRGBA16F(color, (unsigned short *)(&image[j*8])); + else + ColorToRGBE(color, &image[j*4]); + } + else + { + if ( r_lightmap->integer == 2 ) + { // color code by intensity as development tool (FIXME: check range) + float r = buf_p[j*3+0]; + float g = buf_p[j*3+1]; + float b = buf_p[j*3+2]; + float intensity; + float out[3] = {0.0, 0.0, 0.0}; + + intensity = 0.33f * r + 0.685f * g + 0.063f * b; + + if ( intensity > 255 ) + intensity = 1.0f; + else + intensity /= 255.0f; + + if ( intensity > maxIntensity ) + maxIntensity = intensity; + + HSVtoRGB( intensity, 1.00, 0.50, out ); + + image[j*4+0] = out[0] * 255; + image[j*4+1] = out[1] * 255; + image[j*4+2] = out[2] * 255; + image[j*4+3] = 255; + + sumIntensity += intensity; + } + else + { + R_ColorShiftLightingBytes( &buf_p[j*3], &image[j*4] ); + image[j*4+3] = 255; + } + } + } + + if (r_mergeLightmaps->integer) + R_UpdateSubImage(tr.lightmaps[lightmapnum], image, xoff, yoff, tr.lightmapSize, tr.lightmapSize); + else + tr.lightmaps[i] = R_CreateImage(va("*lightmap%d", i), image, tr.lightmapSize, tr.lightmapSize, IMGTYPE_COLORALPHA, IMGFLAG_NOLIGHTSCALE | IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, textureInternalFormat ); + + if (hdrLightmap) + ri.FS_FreeFile(hdrLightmap); + } + + if (tr.worldDeluxeMapping) + { + buf_p = buf + (i * 2 + 1) * tr.lightmapSize * tr.lightmapSize * 3; + + for ( j = 0 ; j < tr.lightmapSize * tr.lightmapSize; j++ ) { + image[j*4+0] = buf_p[j*3+0]; + image[j*4+1] = buf_p[j*3+1]; + image[j*4+2] = buf_p[j*3+2]; + + // make 0,0,0 into 127,127,127 + if ((image[j*4+0] == 0) && (image[j*4+0] == 0) && (image[j*4+2] == 0)) + { + image[j*4+0] = + image[j*4+1] = + image[j*4+2] = 127; + } + + image[j*4+3] = 255; + } + + if (r_mergeLightmaps->integer) + { + R_UpdateSubImage(tr.deluxemaps[lightmapnum], image, xoff, yoff, tr.lightmapSize, tr.lightmapSize ); + } + else + { + tr.deluxemaps[i] = R_CreateImage(va("*deluxemap%d", i), image, tr.lightmapSize, tr.lightmapSize, IMGTYPE_DELUXE, IMGFLAG_NOLIGHTSCALE | IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, 0 ); + } + } + } + + if ( r_lightmap->integer == 2 ) { + ri.Printf( PRINT_ALL, "Brightest lightmap value: %d\n", ( int ) ( maxIntensity * 255 ) ); + } + + ri.Free(image); +} + + +static float FatPackU(float input, int lightmapnum) +{ + if (lightmapnum < 0) + return input; + + if (tr.worldDeluxeMapping) + lightmapnum >>= 1; + + lightmapnum %= (tr.fatLightmapStep * tr.fatLightmapStep); + + if(tr.fatLightmapSize > 0) + { + int x = lightmapnum % tr.fatLightmapStep; + + return (input / ((float)tr.fatLightmapStep)) + ((1.0 / ((float)tr.fatLightmapStep)) * (float)x); + } + + return input; +} + +static float FatPackV(float input, int lightmapnum) +{ + if (lightmapnum < 0) + return input; + + if (tr.worldDeluxeMapping) + lightmapnum >>= 1; + + lightmapnum %= (tr.fatLightmapStep * tr.fatLightmapStep); + + if(tr.fatLightmapSize > 0) + { + int y = lightmapnum / tr.fatLightmapStep; + + return (input / ((float)tr.fatLightmapStep)) + ((1.0 / ((float)tr.fatLightmapStep)) * (float)y); + } + + return input; +} + + +static int FatLightmap(int lightmapnum) +{ + if (lightmapnum < 0) + return lightmapnum; + + if (tr.worldDeluxeMapping) + lightmapnum >>= 1; + + if (tr.fatLightmapSize > 0) + { + return lightmapnum / (tr.fatLightmapStep * tr.fatLightmapStep); + } + + return lightmapnum; +} + +/* +================= +RE_SetWorldVisData + +This is called by the clipmodel subsystem so we can share the 1.8 megs of +space in big maps... +================= +*/ +void RE_SetWorldVisData( const byte *vis ) { + tr.externalVisData = vis; +} + + +/* +================= +R_LoadVisibility +================= +*/ +static void R_LoadVisibility( lump_t *l ) { + int len; + byte *buf; + + len = ( s_worldData.numClusters + 63 ) & ~63; + s_worldData.novis = ri.Hunk_Alloc( len, h_low ); + Com_Memset( s_worldData.novis, 0xff, len ); + + len = l->filelen; + if ( !len ) { + return; + } + buf = fileBase + l->fileofs; + + s_worldData.numClusters = LittleLong( ((int *)buf)[0] ); + s_worldData.clusterBytes = LittleLong( ((int *)buf)[1] ); + + // CM_Load should have given us the vis data to share, so + // we don't need to allocate another copy + if ( tr.externalVisData ) { + s_worldData.vis = tr.externalVisData; + } else { + byte *dest; + + dest = ri.Hunk_Alloc( len - 8, h_low ); + Com_Memcpy( dest, buf + 8, len - 8 ); + s_worldData.vis = dest; + } +} + +//=============================================================================== + + +/* +=============== +ShaderForShaderNum +=============== +*/ +static shader_t *ShaderForShaderNum( int shaderNum, int lightmapNum ) { + shader_t *shader; + dshader_t *dsh; + + int _shaderNum = LittleLong( shaderNum ); + if ( _shaderNum < 0 || _shaderNum >= s_worldData.numShaders ) { + ri.Error( ERR_DROP, "ShaderForShaderNum: bad num %i", _shaderNum ); + } + dsh = &s_worldData.shaders[ _shaderNum ]; + + if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) { + lightmapNum = LIGHTMAP_BY_VERTEX; + } + + if ( r_fullbright->integer ) { + lightmapNum = LIGHTMAP_WHITEIMAGE; + } + + shader = R_FindShader( dsh->shader, lightmapNum, qtrue ); + + // if the shader had errors, just use default shader + if ( shader->defaultShader ) { + return tr.defaultShader; + } + + return shader; +} + +/* +=============== +ParseFace +=============== +*/ +static void ParseFace( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors, msurface_t *surf, int *indexes ) { + int i, j; + srfSurfaceFace_t *cv; + srfTriangle_t *tri; + int numVerts, numTriangles, badTriangles; + int realLightmapNum; + + realLightmapNum = LittleLong( ds->lightmapNum ); + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, FatLightmap(realLightmapNum) ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + numVerts = LittleLong(ds->numVerts); + if (numVerts > MAX_FACE_POINTS) { + ri.Printf( PRINT_WARNING, "WARNING: MAX_FACE_POINTS exceeded: %i\n", numVerts); + numVerts = MAX_FACE_POINTS; + surf->shader = tr.defaultShader; + } + + numTriangles = LittleLong(ds->numIndexes) / 3; + + //cv = ri.Hunk_Alloc(sizeof(*cv), h_low); + cv = (void *)surf->data; + cv->surfaceType = SF_FACE; + + cv->numTriangles = numTriangles; + cv->triangles = ri.Hunk_Alloc(numTriangles * sizeof(cv->triangles[0]), h_low); + + cv->numVerts = numVerts; + cv->verts = ri.Hunk_Alloc(numVerts * sizeof(cv->verts[0]), h_low); + + // copy vertexes + surf->cullinfo.type = CULLINFO_PLANE | CULLINFO_BOX; + ClearBounds(surf->cullinfo.bounds[0], surf->cullinfo.bounds[1]); + verts += LittleLong(ds->firstVert); + for(i = 0; i < numVerts; i++) + { + vec4_t color; + + for(j = 0; j < 3; j++) + { + cv->verts[i].xyz[j] = LittleFloat(verts[i].xyz[j]); + cv->verts[i].normal[j] = LittleFloat(verts[i].normal[j]); + } + AddPointToBounds(cv->verts[i].xyz, surf->cullinfo.bounds[0], surf->cullinfo.bounds[1]); + for(j = 0; j < 2; j++) + { + cv->verts[i].st[j] = LittleFloat(verts[i].st[j]); + //cv->verts[i].lightmap[j] = LittleFloat(verts[i].lightmap[j]); + } + cv->verts[i].lightmap[0] = FatPackU(LittleFloat(verts[i].lightmap[0]), realLightmapNum); + cv->verts[i].lightmap[1] = FatPackV(LittleFloat(verts[i].lightmap[1]), realLightmapNum); + + if (hdrVertColors) + { + color[0] = hdrVertColors[(ds->firstVert + i) * 3 ]; + color[1] = hdrVertColors[(ds->firstVert + i) * 3 + 1]; + color[2] = hdrVertColors[(ds->firstVert + i) * 3 + 2]; + } + else + { + //hack: convert LDR vertex colors to HDR + if (r_hdr->integer) + { + color[0] = verts[i].color[0] + 1.0f; + color[1] = verts[i].color[1] + 1.0f; + color[2] = verts[i].color[2] + 1.0f; + } + else + { + color[0] = verts[i].color[0]; + color[1] = verts[i].color[1]; + color[2] = verts[i].color[2]; + } + + } + color[3] = verts[i].color[3] / 255.0f; + + R_ColorShiftLightingFloats( color, cv->verts[i].vertexColors, 1.0f / 255.0f ); + } + + // copy triangles + badTriangles = 0; + indexes += LittleLong(ds->firstIndex); + for(i = 0, tri = cv->triangles; i < numTriangles; i++, tri++) + { + for(j = 0; j < 3; j++) + { + tri->indexes[j] = LittleLong(indexes[i * 3 + j]); + + if(tri->indexes[j] < 0 || tri->indexes[j] >= numVerts) + { + ri.Error(ERR_DROP, "Bad index in face surface"); + } + } + + if ((tri->indexes[0] == tri->indexes[1]) || (tri->indexes[1] == tri->indexes[2]) || (tri->indexes[0] == tri->indexes[2])) + { + tri--; + badTriangles++; + } + } + + if (badTriangles) + { + ri.Printf(PRINT_WARNING, "Face has bad triangles, originally shader %s %d tris %d verts, now %d tris\n", surf->shader->name, numTriangles, numVerts, numTriangles - badTriangles); + cv->numTriangles -= badTriangles; + } + + // take the plane information from the lightmap vector + for ( i = 0 ; i < 3 ; i++ ) { + cv->plane.normal[i] = LittleFloat( ds->lightmapVecs[2][i] ); + } + cv->plane.dist = DotProduct( cv->verts[0].xyz, cv->plane.normal ); + SetPlaneSignbits( &cv->plane ); + cv->plane.type = PlaneTypeForNormal( cv->plane.normal ); + surf->cullinfo.plane = cv->plane; + + surf->data = (surfaceType_t *)cv; + +#ifdef USE_VERT_TANGENT_SPACE + // Tr3B - calc tangent spaces + { + srfVert_t *dv[3]; + + for(i = 0, tri = cv->triangles; i < numTriangles; i++, tri++) + { + dv[0] = &cv->verts[tri->indexes[0]]; + dv[1] = &cv->verts[tri->indexes[1]]; + dv[2] = &cv->verts[tri->indexes[2]]; + + R_CalcTangentVectors(dv); + } + } +#endif +} + + +/* +=============== +ParseMesh +=============== +*/ +static void ParseMesh ( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors, msurface_t *surf ) { + srfGridMesh_t *grid; + int i, j; + int width, height, numPoints; + srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE]; + vec3_t bounds[2]; + vec3_t tmpVec; + static surfaceType_t skipData = SF_SKIP; + int realLightmapNum; + + realLightmapNum = LittleLong( ds->lightmapNum ); + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, FatLightmap(realLightmapNum) ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + // we may have a nodraw surface, because they might still need to + // be around for movement clipping + if ( s_worldData.shaders[ LittleLong( ds->shaderNum ) ].surfaceFlags & SURF_NODRAW ) { + surf->data = &skipData; + return; + } + + width = LittleLong( ds->patchWidth ); + height = LittleLong( ds->patchHeight ); + + if(width < 0 || width > MAX_PATCH_SIZE || height < 0 || height > MAX_PATCH_SIZE) + ri.Error(ERR_DROP, "ParseMesh: bad size"); + + verts += LittleLong( ds->firstVert ); + numPoints = width * height; + for(i = 0; i < numPoints; i++) + { + vec4_t color; + + for(j = 0; j < 3; j++) + { + points[i].xyz[j] = LittleFloat(verts[i].xyz[j]); + points[i].normal[j] = LittleFloat(verts[i].normal[j]); + } + + for(j = 0; j < 2; j++) + { + points[i].st[j] = LittleFloat(verts[i].st[j]); + //points[i].lightmap[j] = LittleFloat(verts[i].lightmap[j]); + } + points[i].lightmap[0] = FatPackU(LittleFloat(verts[i].lightmap[0]), realLightmapNum); + points[i].lightmap[1] = FatPackV(LittleFloat(verts[i].lightmap[1]), realLightmapNum); + + if (hdrVertColors) + { + color[0] = hdrVertColors[(ds->firstVert + i) * 3 ]; + color[1] = hdrVertColors[(ds->firstVert + i) * 3 + 1]; + color[2] = hdrVertColors[(ds->firstVert + i) * 3 + 2]; + } + else + { + //hack: convert LDR vertex colors to HDR + if (r_hdr->integer) + { + color[0] = verts[i].color[0] + 1.0f; + color[1] = verts[i].color[1] + 1.0f; + color[2] = verts[i].color[2] + 1.0f; + } + else + { + color[0] = verts[i].color[0]; + color[1] = verts[i].color[1]; + color[2] = verts[i].color[2]; + } + } + color[3] = verts[i].color[3] / 255.0f; + + R_ColorShiftLightingFloats( color, points[i].vertexColors, 1.0f / 255.0f ); + } + + // pre-tesseleate + grid = R_SubdividePatchToGrid( width, height, points ); + surf->data = (surfaceType_t *)grid; + + // copy the level of detail origin, which is the center + // of the group of all curves that must subdivide the same + // to avoid cracking + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = LittleFloat( ds->lightmapVecs[0][i] ); + bounds[1][i] = LittleFloat( ds->lightmapVecs[1][i] ); + } + VectorAdd( bounds[0], bounds[1], bounds[1] ); + VectorScale( bounds[1], 0.5f, grid->lodOrigin ); + VectorSubtract( bounds[0], grid->lodOrigin, tmpVec ); + grid->lodRadius = VectorLength( tmpVec ); +} + +/* +=============== +ParseTriSurf +=============== +*/ +static void ParseTriSurf( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors, msurface_t *surf, int *indexes ) { + srfTriangles_t *cv; + srfTriangle_t *tri; + int i, j; + int numVerts, numTriangles, badTriangles; + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + numVerts = LittleLong(ds->numVerts); + numTriangles = LittleLong(ds->numIndexes) / 3; + + //cv = ri.Hunk_Alloc(sizeof(*cv), h_low); + cv = (void *)surf->data; + cv->surfaceType = SF_TRIANGLES; + + cv->numTriangles = numTriangles; + cv->triangles = ri.Hunk_Alloc(numTriangles * sizeof(cv->triangles[0]), h_low); + + cv->numVerts = numVerts; + cv->verts = ri.Hunk_Alloc(numVerts * sizeof(cv->verts[0]), h_low); + + surf->data = (surfaceType_t *) cv; + + // copy vertexes + surf->cullinfo.type = CULLINFO_BOX; + ClearBounds(surf->cullinfo.bounds[0], surf->cullinfo.bounds[1]); + verts += LittleLong(ds->firstVert); + for(i = 0; i < numVerts; i++) + { + vec4_t color; + + for(j = 0; j < 3; j++) + { + cv->verts[i].xyz[j] = LittleFloat(verts[i].xyz[j]); + cv->verts[i].normal[j] = LittleFloat(verts[i].normal[j]); + } + + AddPointToBounds( cv->verts[i].xyz, surf->cullinfo.bounds[0], surf->cullinfo.bounds[1] ); + + for(j = 0; j < 2; j++) + { + cv->verts[i].st[j] = LittleFloat(verts[i].st[j]); + cv->verts[i].lightmap[j] = LittleFloat(verts[i].lightmap[j]); + } + + if (hdrVertColors) + { + color[0] = hdrVertColors[(ds->firstVert + i) * 3 ]; + color[1] = hdrVertColors[(ds->firstVert + i) * 3 + 1]; + color[2] = hdrVertColors[(ds->firstVert + i) * 3 + 2]; + } + else + { + //hack: convert LDR vertex colors to HDR + if (r_hdr->integer) + { + color[0] = verts[i].color[0] + 1.0f; + color[1] = verts[i].color[1] + 1.0f; + color[2] = verts[i].color[2] + 1.0f; + } + else + { + color[0] = verts[i].color[0]; + color[1] = verts[i].color[1]; + color[2] = verts[i].color[2]; + } + } + color[3] = verts[i].color[3] / 255.0f; + + R_ColorShiftLightingFloats( color, cv->verts[i].vertexColors, 1.0f / 255.0f ); + } + + // copy triangles + badTriangles = 0; + indexes += LittleLong(ds->firstIndex); + for(i = 0, tri = cv->triangles; i < numTriangles; i++, tri++) + { + for(j = 0; j < 3; j++) + { + tri->indexes[j] = LittleLong(indexes[i * 3 + j]); + + if(tri->indexes[j] < 0 || tri->indexes[j] >= numVerts) + { + ri.Error(ERR_DROP, "Bad index in face surface"); + } + } + + if ((tri->indexes[0] == tri->indexes[1]) || (tri->indexes[1] == tri->indexes[2]) || (tri->indexes[0] == tri->indexes[2])) + { + tri--; + badTriangles++; + } + } + + if (badTriangles) + { + ri.Printf(PRINT_WARNING, "Trisurf has bad triangles, originally shader %s %d tris %d verts, now %d tris\n", surf->shader->name, numTriangles, numVerts, numTriangles - badTriangles); + cv->numTriangles -= badTriangles; + } + +#ifdef USE_VERT_TANGENT_SPACE + // Tr3B - calc tangent spaces + { + srfVert_t *dv[3]; + + for(i = 0, tri = cv->triangles; i < numTriangles; i++, tri++) + { + dv[0] = &cv->verts[tri->indexes[0]]; + dv[1] = &cv->verts[tri->indexes[1]]; + dv[2] = &cv->verts[tri->indexes[2]]; + + R_CalcTangentVectors(dv); + } + } +#endif +} + +/* +=============== +ParseFlare +=============== +*/ +static void ParseFlare( dsurface_t *ds, drawVert_t *verts, msurface_t *surf, int *indexes ) { + srfFlare_t *flare; + int i; + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, LIGHTMAP_BY_VERTEX ); + if ( r_singleShader->integer && !surf->shader->isSky ) { + surf->shader = tr.defaultShader; + } + + //flare = ri.Hunk_Alloc( sizeof( *flare ), h_low ); + flare = (void *)surf->data; + flare->surfaceType = SF_FLARE; + + surf->data = (surfaceType_t *)flare; + + for ( i = 0 ; i < 3 ; i++ ) { + flare->origin[i] = LittleFloat( ds->lightmapOrigin[i] ); + flare->color[i] = LittleFloat( ds->lightmapVecs[0][i] ); + flare->normal[i] = LittleFloat( ds->lightmapVecs[2][i] ); + } +} + + +/* +================= +R_MergedWidthPoints + +returns true if there are grid points merged on a width edge +================= +*/ +int R_MergedWidthPoints(srfGridMesh_t *grid, int offset) { + int i, j; + + for (i = 1; i < grid->width-1; i++) { + for (j = i + 1; j < grid->width-1; j++) { + if ( fabs(grid->verts[i + offset].xyz[0] - grid->verts[j + offset].xyz[0]) > .1) continue; + if ( fabs(grid->verts[i + offset].xyz[1] - grid->verts[j + offset].xyz[1]) > .1) continue; + if ( fabs(grid->verts[i + offset].xyz[2] - grid->verts[j + offset].xyz[2]) > .1) continue; + return qtrue; + } + } + return qfalse; +} + +/* +================= +R_MergedHeightPoints + +returns true if there are grid points merged on a height edge +================= +*/ +int R_MergedHeightPoints(srfGridMesh_t *grid, int offset) { + int i, j; + + for (i = 1; i < grid->height-1; i++) { + for (j = i + 1; j < grid->height-1; j++) { + if ( fabs(grid->verts[grid->width * i + offset].xyz[0] - grid->verts[grid->width * j + offset].xyz[0]) > .1) continue; + if ( fabs(grid->verts[grid->width * i + offset].xyz[1] - grid->verts[grid->width * j + offset].xyz[1]) > .1) continue; + if ( fabs(grid->verts[grid->width * i + offset].xyz[2] - grid->verts[grid->width * j + offset].xyz[2]) > .1) continue; + return qtrue; + } + } + return qfalse; +} + +/* +================= +R_FixSharedVertexLodError_r + +NOTE: never sync LoD through grid edges with merged points! + +FIXME: write generalized version that also avoids cracks between a patch and one that meets half way? +================= +*/ +void R_FixSharedVertexLodError_r( int start, srfGridMesh_t *grid1 ) { + int j, k, l, m, n, offset1, offset2, touch; + srfGridMesh_t *grid2; + + for ( j = start; j < s_worldData.numsurfaces; j++ ) { + // + grid2 = (srfGridMesh_t *) s_worldData.surfaces[j].data; + // if this surface is not a grid + if ( grid2->surfaceType != SF_GRID ) continue; + // if the LOD errors are already fixed for this patch + if ( grid2->lodFixed == 2 ) continue; + // grids in the same LOD group should have the exact same lod radius + if ( grid1->lodRadius != grid2->lodRadius ) continue; + // grids in the same LOD group should have the exact same lod origin + if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue; + if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue; + if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue; + // + touch = qfalse; + for (n = 0; n < 2; n++) { + // + if (n) offset1 = (grid1->height-1) * grid1->width; + else offset1 = 0; + if (R_MergedWidthPoints(grid1, offset1)) continue; + for (k = 1; k < grid1->width-1; k++) { + for (m = 0; m < 2; m++) { + + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + if (R_MergedWidthPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->width-1; l++) { + // + if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->widthLodError[l] = grid1->widthLodError[k]; + touch = qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (m) offset2 = grid2->width-1; + else offset2 = 0; + if (R_MergedHeightPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->height-1; l++) { + // + if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->heightLodError[l] = grid1->widthLodError[k]; + touch = qtrue; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = grid1->width-1; + else offset1 = 0; + if (R_MergedHeightPoints(grid1, offset1)) continue; + for (k = 1; k < grid1->height-1; k++) { + for (m = 0; m < 2; m++) { + + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + if (R_MergedWidthPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->width-1; l++) { + // + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->widthLodError[l] = grid1->heightLodError[k]; + touch = qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (m) offset2 = grid2->width-1; + else offset2 = 0; + if (R_MergedHeightPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->height-1; l++) { + // + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->heightLodError[l] = grid1->heightLodError[k]; + touch = qtrue; + } + } + } + } + if (touch) { + grid2->lodFixed = 2; + R_FixSharedVertexLodError_r ( start, grid2 ); + //NOTE: this would be correct but makes things really slow + //grid2->lodFixed = 1; + } + } +} + +/* +================= +R_FixSharedVertexLodError + +This function assumes that all patches in one group are nicely stitched together for the highest LoD. +If this is not the case this function will still do its job but won't fix the highest LoD cracks. +================= +*/ +void R_FixSharedVertexLodError( void ) { + int i; + srfGridMesh_t *grid1; + + for ( i = 0; i < s_worldData.numsurfaces; i++ ) { + // + grid1 = (srfGridMesh_t *) s_worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid1->surfaceType != SF_GRID ) + continue; + // + if ( grid1->lodFixed ) + continue; + // + grid1->lodFixed = 2; + // recursively fix other patches in the same LOD group + R_FixSharedVertexLodError_r( i + 1, grid1); + } +} + + +/* +=============== +R_StitchPatches +=============== +*/ +int R_StitchPatches( int grid1num, int grid2num ) { + float *v1, *v2; + srfGridMesh_t *grid1, *grid2; + int k, l, m, n, offset1, offset2, row, column; + + grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data; + grid2 = (srfGridMesh_t *) s_worldData.surfaces[grid2num].data; + for (n = 0; n < 2; n++) { + // + if (n) offset1 = (grid1->height-1) * grid1->width; + else offset1 = 0; + if (R_MergedWidthPoints(grid1, offset1)) + continue; + for (k = 0; k < grid1->width-2; k += 2) { + + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k + 2 + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k + 2 + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = grid1->width-1; + else offset1 = 0; + if (R_MergedHeightPoints(grid1, offset1)) + continue; + for (k = 0; k < grid1->height-2; k += 2) { + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[(l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = (grid1->height-1) * grid1->width; + else offset1 = 0; + if (R_MergedWidthPoints(grid1, offset1)) + continue; + for (k = grid1->width-1; k > 1; k -= 2) { + + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k - 2 + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[(l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k - 2 + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]); + if (!grid2) + break; + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = grid1->width-1; + else offset1 = 0; + if (R_MergedHeightPoints(grid1, offset1)) + continue; + for (k = grid1->height-1; k > 1; k -= 2) { + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[(l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //ri.Printf( PRINT_ALL, "found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = qfalse; + s_worldData.surfaces[grid2num].data = (void *) grid2; + return qtrue; + } + } + } + } + return qfalse; +} + +/* +=============== +R_TryStitchPatch + +This function will try to stitch patches in the same LoD group together for the highest LoD. + +Only single missing vertice cracks will be fixed. + +Vertices will be joined at the patch side a crack is first found, at the other side +of the patch (on the same row or column) the vertices will not be joined and cracks +might still appear at that side. +=============== +*/ +int R_TryStitchingPatch( int grid1num ) { + int j, numstitches; + srfGridMesh_t *grid1, *grid2; + + numstitches = 0; + grid1 = (srfGridMesh_t *) s_worldData.surfaces[grid1num].data; + for ( j = 0; j < s_worldData.numsurfaces; j++ ) { + // + grid2 = (srfGridMesh_t *) s_worldData.surfaces[j].data; + // if this surface is not a grid + if ( grid2->surfaceType != SF_GRID ) continue; + // grids in the same LOD group should have the exact same lod radius + if ( grid1->lodRadius != grid2->lodRadius ) continue; + // grids in the same LOD group should have the exact same lod origin + if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue; + if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue; + if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue; + // + while (R_StitchPatches(grid1num, j)) + { + numstitches++; + } + } + return numstitches; +} + +/* +=============== +R_StitchAllPatches +=============== +*/ +void R_StitchAllPatches( void ) { + int i, stitched, numstitches; + srfGridMesh_t *grid1; + + numstitches = 0; + do + { + stitched = qfalse; + for ( i = 0; i < s_worldData.numsurfaces; i++ ) { + // + grid1 = (srfGridMesh_t *) s_worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid1->surfaceType != SF_GRID ) + continue; + // + if ( grid1->lodStitched ) + continue; + // + grid1->lodStitched = qtrue; + stitched = qtrue; + // + numstitches += R_TryStitchingPatch( i ); + } + } + while (stitched); + ri.Printf( PRINT_ALL, "stitched %d LoD cracks\n", numstitches ); +} + +/* +=============== +R_MovePatchSurfacesToHunk +=============== +*/ +void R_MovePatchSurfacesToHunk(void) { + int i, size; + srfGridMesh_t *grid, *hunkgrid; + + for ( i = 0; i < s_worldData.numsurfaces; i++ ) { + // + grid = (srfGridMesh_t *) s_worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid->surfaceType != SF_GRID ) + continue; + // + size = sizeof(*grid); + hunkgrid = ri.Hunk_Alloc(size, h_low); + Com_Memcpy(hunkgrid, grid, size); + + hunkgrid->widthLodError = ri.Hunk_Alloc( grid->width * 4, h_low ); + Com_Memcpy( hunkgrid->widthLodError, grid->widthLodError, grid->width * 4 ); + + hunkgrid->heightLodError = ri.Hunk_Alloc( grid->height * 4, h_low ); + Com_Memcpy( hunkgrid->heightLodError, grid->heightLodError, grid->height * 4 ); + + hunkgrid->numTriangles = grid->numTriangles; + hunkgrid->triangles = ri.Hunk_Alloc(grid->numTriangles * sizeof(srfTriangle_t), h_low); + Com_Memcpy(hunkgrid->triangles, grid->triangles, grid->numTriangles * sizeof(srfTriangle_t)); + + hunkgrid->numVerts = grid->numVerts; + hunkgrid->verts = ri.Hunk_Alloc(grid->numVerts * sizeof(srfVert_t), h_low); + Com_Memcpy(hunkgrid->verts, grid->verts, grid->numVerts * sizeof(srfVert_t)); + + R_FreeSurfaceGridMesh( grid ); + + s_worldData.surfaces[i].data = (void *) hunkgrid; + } +} + + +/* +================= +BSPSurfaceCompare +compare function for qsort() +================= +*/ +static int BSPSurfaceCompare(const void *a, const void *b) +{ + msurface_t *aa, *bb; + + aa = *(msurface_t **) a; + bb = *(msurface_t **) b; + + // shader first + if(aa->shader->sortedIndex < bb->shader->sortedIndex) + return -1; + + else if(aa->shader->sortedIndex > bb->shader->sortedIndex) + return 1; + + // by fogIndex + if(aa->fogIndex < bb->fogIndex) + return -1; + + else if(aa->fogIndex > bb->fogIndex) + return 1; + + return 0; +} + + +static void CopyVert(const srfVert_t * in, srfVert_t * out) +{ + int j; + + for(j = 0; j < 3; j++) + { + out->xyz[j] = in->xyz[j]; +#ifdef USE_VERT_TANGENT_SPACE + out->tangent[j] = in->tangent[j]; + out->bitangent[j] = in->bitangent[j]; +#endif + out->normal[j] = in->normal[j]; + out->lightdir[j] = in->lightdir[j]; + } + + for(j = 0; j < 2; j++) + { + out->st[j] = in->st[j]; + out->lightmap[j] = in->lightmap[j]; + } + + for(j = 0; j < 4; j++) + { + out->vertexColors[j] = in->vertexColors[j]; + } +} + + +/* +=============== +R_CreateWorldVBO +=============== +*/ +static void R_CreateWorldVBO(void) +{ + int i, j, k; + + int numVerts; + srfVert_t *verts; + + int numTriangles; + srfTriangle_t *triangles; + + int numSurfaces; + msurface_t *surface; + msurface_t **surfacesSorted; + + int startTime, endTime; + + startTime = ri.Milliseconds(); + + numVerts = 0; + numTriangles = 0; + numSurfaces = 0; + for(k = 0, surface = &s_worldData.surfaces[0]; k < s_worldData.numsurfaces /* s_worldData.numWorldSurfaces */; k++, surface++) + { + if(*surface->data == SF_FACE) + { + srfSurfaceFace_t *face = (srfSurfaceFace_t *) surface->data; + + if(face->numVerts) + numVerts += face->numVerts; + + if(face->numTriangles) + numTriangles += face->numTriangles; + + numSurfaces++; + } + else if(*surface->data == SF_GRID) + { + srfGridMesh_t *grid = (srfGridMesh_t *) surface->data; + + if(grid->numVerts) + numVerts += grid->numVerts; + + if(grid->numTriangles) + numTriangles += grid->numTriangles; + + numSurfaces++; + } + else if(*surface->data == SF_TRIANGLES) + { + srfTriangles_t *tri = (srfTriangles_t *) surface->data; + + if(tri->numVerts) + numVerts += tri->numVerts; + + if(tri->numTriangles) + numTriangles += tri->numTriangles; + + numSurfaces++; + } + } + + if(!numVerts || !numTriangles) + return; + + ri.Printf(PRINT_ALL, "...calculating world VBO ( %i verts %i tris )\n", numVerts, numTriangles); + + // create arrays + + verts = ri.Hunk_AllocateTempMemory(numVerts * sizeof(srfVert_t)); + + triangles = ri.Hunk_AllocateTempMemory(numTriangles * sizeof(srfTriangle_t)); + + // presort surfaces + surfacesSorted = ri.Malloc(numSurfaces * sizeof(*surfacesSorted)); + + j = 0; + for(k = 0, surface = &s_worldData.surfaces[0]; k < s_worldData.numsurfaces; k++, surface++) + { + if(*surface->data == SF_FACE || *surface->data == SF_GRID || *surface->data == SF_TRIANGLES) + { + surfacesSorted[j++] = surface; + } + } + + qsort(surfacesSorted, numSurfaces, sizeof(*surfacesSorted), BSPSurfaceCompare); + + // set up triangle indices + numVerts = 0; + numTriangles = 0; + for(k = 0, surface = surfacesSorted[k]; k < numSurfaces; k++, surface = surfacesSorted[k]) + { + if(*surface->data == SF_FACE) + { + srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data; + + srf->firstIndex = numTriangles * 3; + + if(srf->numTriangles) + { + srfTriangle_t *tri; + + for(i = 0, tri = srf->triangles; i < srf->numTriangles; i++, tri++) + { + for(j = 0; j < 3; j++) + { + triangles[numTriangles + i].indexes[j] = numVerts + tri->indexes[j]; + } + } + + numTriangles += srf->numTriangles; + } + + if(srf->numVerts) + numVerts += srf->numVerts; + } + else if(*surface->data == SF_GRID) + { + srfGridMesh_t *srf = (srfGridMesh_t *) surface->data; + + srf->firstIndex = numTriangles * 3; + + if(srf->numTriangles) + { + srfTriangle_t *tri; + + for(i = 0, tri = srf->triangles; i < srf->numTriangles; i++, tri++) + { + for(j = 0; j < 3; j++) + { + triangles[numTriangles + i].indexes[j] = numVerts + tri->indexes[j]; + } + } + + numTriangles += srf->numTriangles; + } + + if(srf->numVerts) + numVerts += srf->numVerts; + } + else if(*surface->data == SF_TRIANGLES) + { + srfTriangles_t *srf = (srfTriangles_t *) surface->data; + + srf->firstIndex = numTriangles * 3; + + if(srf->numTriangles) + { + srfTriangle_t *tri; + + for(i = 0, tri = srf->triangles; i < srf->numTriangles; i++, tri++) + { + for(j = 0; j < 3; j++) + { + triangles[numTriangles + i].indexes[j] = numVerts + tri->indexes[j]; + } + } + + numTriangles += srf->numTriangles; + } + + if(srf->numVerts) + numVerts += srf->numVerts; + } + } + + // build vertices + numVerts = 0; + for(k = 0, surface = surfacesSorted[k]; k < numSurfaces; k++, surface = surfacesSorted[k]) + { + if(*surface->data == SF_FACE) + { + srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data; + + srf->firstVert = numVerts; + + if(srf->numVerts) + { + for(i = 0; i < srf->numVerts; i++) + { + CopyVert(&srf->verts[i], &verts[numVerts + i]); + } + + numVerts += srf->numVerts; + } + } + else if(*surface->data == SF_GRID) + { + srfGridMesh_t *srf = (srfGridMesh_t *) surface->data; + + srf->firstVert = numVerts; + + if(srf->numVerts) + { + for(i = 0; i < srf->numVerts; i++) + { + CopyVert(&srf->verts[i], &verts[numVerts + i]); + } + + numVerts += srf->numVerts; + } + } + else if(*surface->data == SF_TRIANGLES) + { + srfTriangles_t *srf = (srfTriangles_t *) surface->data; + + srf->firstVert = numVerts; + + if(srf->numVerts) + { + for(i = 0; i < srf->numVerts; i++) + { + CopyVert(&srf->verts[i], &verts[numVerts + i]); + } + + numVerts += srf->numVerts; + } + } + } + +#ifdef USE_VERT_TANGENT_SPACE + s_worldData.vbo = R_CreateVBO2(va("staticBspModel0_VBO %i", 0), numVerts, verts, + ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD | ATTR_TANGENT | ATTR_BITANGENT | + ATTR_NORMAL | ATTR_COLOR | ATTR_LIGHTDIRECTION, VBO_USAGE_STATIC); +#else + s_worldData.vbo = R_CreateVBO2(va("staticBspModel0_VBO %i", 0), numVerts, verts, + ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD | + ATTR_NORMAL | ATTR_COLOR | ATTR_LIGHTDIRECTION, VBO_USAGE_STATIC); +#endif + + s_worldData.ibo = R_CreateIBO2(va("staticBspModel0_IBO %i", 0), numTriangles, triangles, VBO_USAGE_STATIC); + + endTime = ri.Milliseconds(); + ri.Printf(PRINT_ALL, "world VBO calculation time = %5.2f seconds\n", (endTime - startTime) / 1000.0); + + // point triangle surfaces to world VBO + for(k = 0, surface = surfacesSorted[k]; k < numSurfaces; k++, surface = surfacesSorted[k]) + { + if(*surface->data == SF_FACE) + { + srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data; + + if( srf->numVerts && srf->numTriangles) + { + srf->vbo = s_worldData.vbo; + srf->ibo = s_worldData.ibo; + } + } + else if(*surface->data == SF_GRID) + { + srfGridMesh_t *srf = (srfGridMesh_t *) surface->data; + + if( srf->numVerts && srf->numTriangles) + { + srf->vbo = s_worldData.vbo; + srf->ibo = s_worldData.ibo; + } + } + else if(*surface->data == SF_TRIANGLES) + { + srfTriangles_t *srf = (srfTriangles_t *) surface->data; + + if( srf->numVerts && srf->numTriangles) + { + srf->vbo = s_worldData.vbo; + srf->ibo = s_worldData.ibo; + } + } + } + + + startTime = ri.Milliseconds(); + + ri.Free(surfacesSorted); + + ri.Hunk_FreeTempMemory(triangles); + ri.Hunk_FreeTempMemory(verts); +} + +/* +=============== +R_LoadSurfaces +=============== +*/ +static void R_LoadSurfaces( lump_t *surfs, lump_t *verts, lump_t *indexLump ) { + dsurface_t *in; + msurface_t *out; + drawVert_t *dv; + int *indexes; + int count; + int numFaces, numMeshes, numTriSurfs, numFlares; + int i; + float *hdrVertColors = NULL; + + numFaces = 0; + numMeshes = 0; + numTriSurfs = 0; + numFlares = 0; + + in = (void *)(fileBase + surfs->fileofs); + if (surfs->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = surfs->filelen / sizeof(*in); + + dv = (void *)(fileBase + verts->fileofs); + if (verts->filelen % sizeof(*dv)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + indexes = (void *)(fileBase + indexLump->fileofs); + if ( indexLump->filelen % sizeof(*indexes)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + out = ri.Hunk_Alloc ( count * sizeof(*out), h_low ); + + s_worldData.surfaces = out; + s_worldData.numsurfaces = count; + s_worldData.surfacesViewCount = ri.Hunk_Alloc ( count * sizeof(*s_worldData.surfacesViewCount), h_low ); + s_worldData.surfacesDlightBits = ri.Hunk_Alloc ( count * sizeof(*s_worldData.surfacesDlightBits), h_low ); + s_worldData.surfacesPshadowBits = ri.Hunk_Alloc ( count * sizeof(*s_worldData.surfacesPshadowBits), h_low ); + + // load hdr vertex colors + if (r_hdr->integer) + { + char filename[MAX_QPATH]; + int size; + + Com_sprintf( filename, sizeof( filename ), "maps/%s/vertlight.raw", s_worldData.baseName); + //ri.Printf(PRINT_ALL, "looking for %s\n", filename); + + size = ri.FS_ReadFile(filename, (void **)&hdrVertColors); + + if (hdrVertColors) + { + //ri.Printf(PRINT_ALL, "Found!\n"); + if (size != sizeof(float) * 3 * (verts->filelen / sizeof(*dv))) + ri.Error(ERR_DROP, "Bad size for %s (%i, expected %i)!\n", filename, size, (int)(sizeof(float)) * 3 * (verts->filelen / sizeof(*dv))); + } + } + + + // Two passes, allocate surfaces first, then load them full of data + // This ensures surfaces are close together to reduce L2 cache misses when using VBOs, + // which don't actually use the verts and tris + in = (void *)(fileBase + surfs->fileofs); + out = s_worldData.surfaces; + for ( i = 0 ; i < count ; i++, in++, out++ ) { + switch ( LittleLong( in->surfaceType ) ) { + case MST_PATCH: + // FIXME: do this + break; + case MST_TRIANGLE_SOUP: + out->data = ri.Hunk_Alloc( sizeof(srfTriangles_t), h_low); + break; + case MST_PLANAR: + out->data = ri.Hunk_Alloc( sizeof(srfSurfaceFace_t), h_low); + break; + case MST_FLARE: + out->data = ri.Hunk_Alloc( sizeof(srfFlare_t), h_low); + break; + default: + break; + } + } + + in = (void *)(fileBase + surfs->fileofs); + out = s_worldData.surfaces; + for ( i = 0 ; i < count ; i++, in++, out++ ) { + switch ( LittleLong( in->surfaceType ) ) { + case MST_PATCH: + ParseMesh ( in, dv, hdrVertColors, out ); + { + srfGridMesh_t *surface = (srfGridMesh_t *)out->data; + + out->cullinfo.type = CULLINFO_BOX | CULLINFO_SPHERE; + VectorCopy(surface->meshBounds[0], out->cullinfo.bounds[0]); + VectorCopy(surface->meshBounds[1], out->cullinfo.bounds[1]); + VectorCopy(surface->localOrigin, out->cullinfo.localOrigin); + out->cullinfo.radius = surface->meshRadius; + } + numMeshes++; + break; + case MST_TRIANGLE_SOUP: + ParseTriSurf( in, dv, hdrVertColors, out, indexes ); + numTriSurfs++; + break; + case MST_PLANAR: + ParseFace( in, dv, hdrVertColors, out, indexes ); + numFaces++; + break; + case MST_FLARE: + ParseFlare( in, dv, out, indexes ); + { + out->cullinfo.type = CULLINFO_NONE; + } + numFlares++; + break; + default: + ri.Error( ERR_DROP, "Bad surfaceType" ); + } + } + + if (hdrVertColors) + { + ri.FS_FreeFile(hdrVertColors); + } + +#ifdef PATCH_STITCHING + R_StitchAllPatches(); +#endif + + R_FixSharedVertexLodError(); + +#ifdef PATCH_STITCHING + R_MovePatchSurfacesToHunk(); +#endif + + ri.Printf( PRINT_ALL, "...loaded %d faces, %i meshes, %i trisurfs, %i flares\n", + numFaces, numMeshes, numTriSurfs, numFlares ); +} + + + +/* +================= +R_LoadSubmodels +================= +*/ +static void R_LoadSubmodels( lump_t *l ) { + dmodel_t *in; + bmodel_t *out; + int i, j, count; + + in = (void *)(fileBase + l->fileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + + s_worldData.numBModels = count; + s_worldData.bmodels = out = ri.Hunk_Alloc( count * sizeof(*out), h_low ); + + for ( i=0 ; itype = MOD_BRUSH; + model->bmodel = out; + Com_sprintf( model->name, sizeof( model->name ), "*%d", i ); + + for (j=0 ; j<3 ; j++) { + out->bounds[0][j] = LittleFloat (in->mins[j]); + out->bounds[1][j] = LittleFloat (in->maxs[j]); + } + + out->firstSurface = LittleLong( in->firstSurface ); + out->numSurfaces = LittleLong( in->numSurfaces ); + + if(i == 0) + { + // Tr3B: add this for limiting VBO surface creation + s_worldData.numWorldSurfaces = out->numSurfaces; + } + } +} + + + +//================================================================== + +/* +================= +R_SetParent +================= +*/ +static void R_SetParent (mnode_t *node, mnode_t *parent) +{ + node->parent = parent; + if (node->contents != -1) + return; + R_SetParent (node->children[0], node); + R_SetParent (node->children[1], node); +} + +/* +================= +R_LoadNodesAndLeafs +================= +*/ +static void R_LoadNodesAndLeafs (lump_t *nodeLump, lump_t *leafLump) { + int i, j, p; + dnode_t *in; + dleaf_t *inLeaf; + mnode_t *out; + int numNodes, numLeafs; + + in = (void *)(fileBase + nodeLump->fileofs); + if (nodeLump->filelen % sizeof(dnode_t) || + leafLump->filelen % sizeof(dleaf_t) ) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + numNodes = nodeLump->filelen / sizeof(dnode_t); + numLeafs = leafLump->filelen / sizeof(dleaf_t); + + out = ri.Hunk_Alloc ( (numNodes + numLeafs) * sizeof(*out), h_low); + + s_worldData.nodes = out; + s_worldData.numnodes = numNodes + numLeafs; + s_worldData.numDecisionNodes = numNodes; + + // load nodes + for ( i=0 ; imins[j] = LittleLong (in->mins[j]); + out->maxs[j] = LittleLong (in->maxs[j]); + } + + p = LittleLong(in->planeNum); + out->plane = s_worldData.planes + p; + + out->contents = CONTENTS_NODE; // differentiate from leafs + + for (j=0 ; j<2 ; j++) + { + p = LittleLong (in->children[j]); + if (p >= 0) + out->children[j] = s_worldData.nodes + p; + else + out->children[j] = s_worldData.nodes + numNodes + (-1 - p); + } + } + + // load leafs + inLeaf = (void *)(fileBase + leafLump->fileofs); + for ( i=0 ; imins[j] = LittleLong (inLeaf->mins[j]); + out->maxs[j] = LittleLong (inLeaf->maxs[j]); + } + + out->cluster = LittleLong(inLeaf->cluster); + out->area = LittleLong(inLeaf->area); + + if ( out->cluster >= s_worldData.numClusters ) { + s_worldData.numClusters = out->cluster + 1; + } + + out->firstmarksurface = LittleLong(inLeaf->firstLeafSurface); + out->nummarksurfaces = LittleLong(inLeaf->numLeafSurfaces); + } + + // chain decendants + R_SetParent (s_worldData.nodes, NULL); +} + +//============================================================================= + +/* +================= +R_LoadShaders +================= +*/ +static void R_LoadShaders( lump_t *l ) { + int i, count; + dshader_t *in, *out; + + in = (void *)(fileBase + l->fileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + out = ri.Hunk_Alloc ( count*sizeof(*out), h_low ); + + s_worldData.shaders = out; + s_worldData.numShaders = count; + + Com_Memcpy( out, in, count*sizeof(*out) ); + + for ( i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + out = ri.Hunk_Alloc ( count*sizeof(*out), h_low); + + s_worldData.marksurfaces = out; + s_worldData.nummarksurfaces = count; + + for ( i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = l->filelen / sizeof(*in); + out = ri.Hunk_Alloc ( count*2*sizeof(*out), h_low); + + s_worldData.planes = out; + s_worldData.numplanes = count; + + for ( i=0 ; inormal[j] = LittleFloat (in->normal[j]); + if (out->normal[j] < 0) { + bits |= 1<dist = LittleFloat (in->dist); + out->type = PlaneTypeForNormal( out->normal ); + out->signbits = bits; + } +} + +/* +================= +R_LoadFogs + +================= +*/ +static void R_LoadFogs( lump_t *l, lump_t *brushesLump, lump_t *sidesLump ) { + int i; + fog_t *out; + dfog_t *fogs; + dbrush_t *brushes, *brush; + dbrushside_t *sides; + int count, brushesCount, sidesCount; + int sideNum; + int planeNum; + shader_t *shader; + float d; + int firstSide; + + fogs = (void *)(fileBase + l->fileofs); + if (l->filelen % sizeof(*fogs)) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + count = l->filelen / sizeof(*fogs); + + // create fog strucutres for them + s_worldData.numfogs = count + 1; + s_worldData.fogs = ri.Hunk_Alloc ( s_worldData.numfogs*sizeof(*out), h_low); + out = s_worldData.fogs + 1; + + if ( !count ) { + return; + } + + brushes = (void *)(fileBase + brushesLump->fileofs); + if (brushesLump->filelen % sizeof(*brushes)) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + brushesCount = brushesLump->filelen / sizeof(*brushes); + + sides = (void *)(fileBase + sidesLump->fileofs); + if (sidesLump->filelen % sizeof(*sides)) { + ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + sidesCount = sidesLump->filelen / sizeof(*sides); + + for ( i=0 ; ioriginalBrushNumber = LittleLong( fogs->brushNum ); + + if ( (unsigned)out->originalBrushNumber >= brushesCount ) { + ri.Error( ERR_DROP, "fog brushNumber out of range" ); + } + brush = brushes + out->originalBrushNumber; + + firstSide = LittleLong( brush->firstSide ); + + if ( (unsigned)firstSide > sidesCount - 6 ) { + ri.Error( ERR_DROP, "fog brush sideNumber out of range" ); + } + + // brushes are always sorted with the axial sides first + sideNum = firstSide + 0; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][0] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 1; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][0] = s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 2; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][1] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 3; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][1] = s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 4; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][2] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 5; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][2] = s_worldData.planes[ planeNum ].dist; + + // get information from the shader for fog parameters + shader = R_FindShader( fogs->shader, LIGHTMAP_NONE, qtrue ); + + out->parms = shader->fogParms; + + out->colorInt = ColorBytes4 ( shader->fogParms.color[0] * tr.identityLight, + shader->fogParms.color[1] * tr.identityLight, + shader->fogParms.color[2] * tr.identityLight, 1.0 ); + + d = shader->fogParms.depthForOpaque < 1 ? 1 : shader->fogParms.depthForOpaque; + out->tcScale = 1.0f / ( d * 8 ); + + // set the gradient vector + sideNum = LittleLong( fogs->visibleSide ); + + if ( sideNum == -1 ) { + out->hasSurface = qfalse; + } else { + out->hasSurface = qtrue; + planeNum = LittleLong( sides[ firstSide + sideNum ].planeNum ); + VectorSubtract( vec3_origin, s_worldData.planes[ planeNum ].normal, out->surface ); + out->surface[3] = -s_worldData.planes[ planeNum ].dist; + } + + out++; + } + +} + + +/* +================ +R_LoadLightGrid + +================ +*/ +void R_LoadLightGrid( lump_t *l ) { + int i; + vec3_t maxs; + int numGridPoints; + world_t *w; + float *wMins, *wMaxs; + + w = &s_worldData; + + w->lightGridInverseSize[0] = 1.0f / w->lightGridSize[0]; + w->lightGridInverseSize[1] = 1.0f / w->lightGridSize[1]; + w->lightGridInverseSize[2] = 1.0f / w->lightGridSize[2]; + + wMins = w->bmodels[0].bounds[0]; + wMaxs = w->bmodels[0].bounds[1]; + + for ( i = 0 ; i < 3 ; i++ ) { + w->lightGridOrigin[i] = w->lightGridSize[i] * ceil( wMins[i] / w->lightGridSize[i] ); + maxs[i] = w->lightGridSize[i] * floor( wMaxs[i] / w->lightGridSize[i] ); + w->lightGridBounds[i] = (maxs[i] - w->lightGridOrigin[i])/w->lightGridSize[i] + 1; + } + + numGridPoints = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2]; + + if ( l->filelen != numGridPoints * 8 ) { + ri.Printf( PRINT_WARNING, "WARNING: light grid mismatch\n" ); + w->lightGridData = NULL; + return; + } + + w->lightGridData = ri.Hunk_Alloc( l->filelen, h_low ); + Com_Memcpy( w->lightGridData, (void *)(fileBase + l->fileofs), l->filelen ); + + // deal with overbright bits + for ( i = 0 ; i < numGridPoints ; i++ ) { + R_ColorShiftLightingBytes( &w->lightGridData[i*8], &w->lightGridData[i*8] ); + R_ColorShiftLightingBytes( &w->lightGridData[i*8+3], &w->lightGridData[i*8+3] ); + } + + // load hdr lightgrid + if (r_hdr->integer) + { + char filename[MAX_QPATH]; + float *hdrLightGrid; + int size; + + Com_sprintf( filename, sizeof( filename ), "maps/%s/lightgrid.raw", s_worldData.baseName); + //ri.Printf(PRINT_ALL, "looking for %s\n", filename); + + size = ri.FS_ReadFile(filename, (void **)&hdrLightGrid); + + if (hdrLightGrid) + { + float lightScale = pow(2, r_mapOverBrightBits->integer - tr.overbrightBits); + + //ri.Printf(PRINT_ALL, "found!\n"); + + if (size != sizeof(float) * 6 * numGridPoints) + { + ri.Error(ERR_DROP, "Bad size for %s (%i, expected %i)!\n", filename, size, (int)(sizeof(float)) * 6 * numGridPoints); + } + + w->hdrLightGrid = ri.Hunk_Alloc(size, h_low); + + for (i = 0; i < numGridPoints ; i++) + { + w->hdrLightGrid[i * 6 ] = hdrLightGrid[i * 6 ] * lightScale; + w->hdrLightGrid[i * 6 + 1] = hdrLightGrid[i * 6 + 1] * lightScale; + w->hdrLightGrid[i * 6 + 2] = hdrLightGrid[i * 6 + 2] * lightScale; + w->hdrLightGrid[i * 6 + 3] = hdrLightGrid[i * 6 + 3] * lightScale; + w->hdrLightGrid[i * 6 + 4] = hdrLightGrid[i * 6 + 4] * lightScale; + w->hdrLightGrid[i * 6 + 5] = hdrLightGrid[i * 6 + 5] * lightScale; + } + } + + if (hdrLightGrid) + ri.FS_FreeFile(hdrLightGrid); + } +} + +/* +================ +R_LoadEntities +================ +*/ +void R_LoadEntities( lump_t *l ) { + char *p, *token, *s; + char keyname[MAX_TOKEN_CHARS]; + char value[MAX_TOKEN_CHARS]; + world_t *w; + + w = &s_worldData; + w->lightGridSize[0] = 64; + w->lightGridSize[1] = 64; + w->lightGridSize[2] = 128; + + p = (char *)(fileBase + l->fileofs); + + // store for reference by the cgame + w->entityString = ri.Hunk_Alloc( l->filelen + 1, h_low ); + strcpy( w->entityString, p ); + w->entityParsePoint = w->entityString; + + token = COM_ParseExt( &p, qtrue ); + if (!*token || *token != '{') { + return; + } + + // only parse the world spawn + while ( 1 ) { + // parse key + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(keyname, token, sizeof(keyname)); + + // parse value + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(value, token, sizeof(value)); + + // check for remapping of shaders for vertex lighting + s = "vertexremapshader"; + if (!Q_strncmp(keyname, s, strlen(s)) ) { + s = strchr(value, ';'); + if (!s) { + ri.Printf( PRINT_WARNING, "WARNING: no semi colon in vertexshaderremap '%s'\n", value ); + break; + } + *s++ = 0; + if (r_vertexLight->integer) { + R_RemapShader(value, s, "0"); + } + continue; + } + // check for remapping of shaders + s = "remapshader"; + if (!Q_strncmp(keyname, s, strlen(s)) ) { + s = strchr(value, ';'); + if (!s) { + ri.Printf( PRINT_WARNING, "WARNING: no semi colon in shaderremap '%s'\n", value ); + break; + } + *s++ = 0; + R_RemapShader(value, s, "0"); + continue; + } + // check for a different grid size + if (!Q_stricmp(keyname, "gridsize")) { + sscanf(value, "%f %f %f", &w->lightGridSize[0], &w->lightGridSize[1], &w->lightGridSize[2] ); + continue; + } + + // check for auto exposure + if (!Q_stricmp(keyname, "autoExposureMinMax")) { + sscanf(value, "%f %f", &tr.autoExposureMinMax[0], &tr.autoExposureMinMax[1]); + continue; + } + } +} + +/* +================= +R_GetEntityToken +================= +*/ +qboolean R_GetEntityToken( char *buffer, int size ) { + const char *s; + + s = COM_Parse( &s_worldData.entityParsePoint ); + Q_strncpyz( buffer, s, size ); + if ( !s_worldData.entityParsePoint || !s[0] ) { + s_worldData.entityParsePoint = s_worldData.entityString; + return qfalse; + } else { + return qtrue; + } +} + + +/* +================= +R_MergeLeafSurfaces + +Merges surfaces that share a common leaf +================= +*/ +void R_MergeLeafSurfaces(void) +{ + int i, j, k; + int numWorldSurfaces; + int mergedSurfIndex; + int numMergedSurfaces; + int numUnmergedSurfaces; + IBO_t *ibo; + + msurface_t *mergedSurf; + + glIndex_t *iboIndexes, *outIboIndexes; + int numIboIndexes; + + int startTime, endTime; + + startTime = ri.Milliseconds(); + + numWorldSurfaces = s_worldData.numWorldSurfaces; + + // use viewcount to keep track of mergers + for (i = 0; i < numWorldSurfaces; i++) + { + s_worldData.surfacesViewCount[i] = -1; + } + + // create ibo + ibo = tr.ibos[tr.numIBOs++] = ri.Hunk_Alloc(sizeof(*ibo), h_low); + memset(ibo, 0, sizeof(*ibo)); + Q_strncpyz(ibo->name, "staticWorldMesh_IBO_mergedSurfs", sizeof(ibo->name)); + + // allocate more than we need + iboIndexes = outIboIndexes = ri.Malloc(s_worldData.ibo->indexesSize); + + // mark matching surfaces + for (i = 0; i < s_worldData.numnodes - s_worldData.numDecisionNodes; i++) + { + mnode_t *leaf = s_worldData.nodes + s_worldData.numDecisionNodes + i; + + for (j = 0; j < leaf->nummarksurfaces; j++) + { + msurface_t *surf1; + shader_t *shader1; + int fogIndex1; + int surfNum1; + + surfNum1 = *(s_worldData.marksurfaces + leaf->firstmarksurface + j); + + if (s_worldData.surfacesViewCount[surfNum1] != -1) + continue; + + surf1 = s_worldData.surfaces + surfNum1; + + if ((*surf1->data != SF_GRID) && (*surf1->data != SF_TRIANGLES) && (*surf1->data != SF_FACE)) + continue; + + shader1 = surf1->shader; + + if(shader1->isSky) + continue; + + if(shader1->isPortal) + continue; + + if(ShaderRequiresCPUDeforms(shader1)) + continue; + + fogIndex1 = surf1->fogIndex; + + s_worldData.surfacesViewCount[surfNum1] = surfNum1; + + for (k = j + 1; k < leaf->nummarksurfaces; k++) + { + msurface_t *surf2; + shader_t *shader2; + int fogIndex2; + int surfNum2; + + surfNum2 = *(s_worldData.marksurfaces + leaf->firstmarksurface + k); + + if (s_worldData.surfacesViewCount[surfNum2] != -1) + continue; + + surf2 = s_worldData.surfaces + surfNum2; + + if ((*surf2->data != SF_GRID) && (*surf2->data != SF_TRIANGLES) && (*surf2->data != SF_FACE)) + continue; + + shader2 = surf2->shader; + + if (shader1 != shader2) + continue; + + fogIndex2 = surf2->fogIndex; + + if (fogIndex1 != fogIndex2) + continue; + + s_worldData.surfacesViewCount[surfNum2] = surfNum1; + } + } + } + + // count merged/unmerged surfaces + numMergedSurfaces = 0; + numUnmergedSurfaces = 0; + for (i = 0; i < numWorldSurfaces; i++) + { + if (s_worldData.surfacesViewCount[i] == i) + { + numMergedSurfaces++; + } + else if (s_worldData.surfacesViewCount[i] == -1) + { + numUnmergedSurfaces++; + } + } + + // Allocate merged surfaces + s_worldData.mergedSurfaces = ri.Hunk_Alloc(sizeof(*s_worldData.mergedSurfaces) * numMergedSurfaces, h_low); + s_worldData.mergedSurfacesViewCount = ri.Hunk_Alloc(sizeof(*s_worldData.mergedSurfacesViewCount) * numMergedSurfaces, h_low); + s_worldData.mergedSurfacesDlightBits = ri.Hunk_Alloc(sizeof(*s_worldData.mergedSurfacesDlightBits) * numMergedSurfaces, h_low); + s_worldData.numMergedSurfaces = numMergedSurfaces; + + // view surfaces are like mark surfaces, except negative ones represent merged surfaces + // -1 represents 0, -2 represents 1, and so on + s_worldData.viewSurfaces = ri.Hunk_Alloc(sizeof(*s_worldData.viewSurfaces) * s_worldData.nummarksurfaces, h_low); + + // copy view surfaces into mark surfaces + for (i = 0; i < s_worldData.nummarksurfaces; i++) + { + s_worldData.viewSurfaces[i] = s_worldData.marksurfaces[i]; + } + + // actually merge surfaces + numIboIndexes = 0; + mergedSurfIndex = 0; + mergedSurf = s_worldData.mergedSurfaces; + for (i = 0; i < numWorldSurfaces; i++) + { + msurface_t *surf1; + + vec3_t bounds[2]; + + int numSurfsToMerge; + int numTriangles; + int numVerts; + int firstIndex; + + srfVBOMesh_t *vboSurf; + + if (s_worldData.surfacesViewCount[i] != i) + continue; + + surf1 = s_worldData.surfaces + i; + + // count verts, indexes, and surfaces + numSurfsToMerge = 0; + numTriangles = 0; + numVerts = 0; + for (j = i; j < numWorldSurfaces; j++) + { + msurface_t *surf2; + + if (s_worldData.surfacesViewCount[j] != i) + continue; + + surf2 = s_worldData.surfaces + j; + + switch(*surf2->data) + { + case SF_FACE: + { + srfSurfaceFace_t *face; + + face = (srfSurfaceFace_t *) surf2->data; + numTriangles += face->numTriangles; + numVerts += face->numVerts; + } + break; + + case SF_GRID: + { + srfGridMesh_t *grid; + + grid = (srfGridMesh_t *) surf2->data; + numTriangles += grid->numTriangles; + numVerts += grid->numVerts; + } + break; + + case SF_TRIANGLES: + { + srfTriangles_t *tris; + + tris = (srfTriangles_t *) surf2->data; + numTriangles += tris->numTriangles; + numVerts += tris->numVerts; + } + break; + + default: + break; + } + + numSurfsToMerge++; + } + + if (numVerts == 0 || numTriangles == 0 || numSurfsToMerge < 2) + { + continue; + } + + // Merge surfaces (indexes) and calculate bounds + ClearBounds(bounds[0], bounds[1]); + firstIndex = numIboIndexes; + for (j = i; j < numWorldSurfaces; j++) + { + msurface_t *surf2; + + if (s_worldData.surfacesViewCount[j] != i) + continue; + + surf2 = s_worldData.surfaces + j; + + AddPointToBounds(surf2->cullinfo.bounds[0], bounds[0], bounds[1]); + AddPointToBounds(surf2->cullinfo.bounds[1], bounds[0], bounds[1]); + + switch(*surf2->data) + { + case SF_FACE: + { + srfSurfaceFace_t *face; + + face = (srfSurfaceFace_t *) surf2->data; + + for (k = 0; k < face->numTriangles; k++) + { + *outIboIndexes++ = face->triangles[k].indexes[0] + face->firstVert; + *outIboIndexes++ = face->triangles[k].indexes[1] + face->firstVert; + *outIboIndexes++ = face->triangles[k].indexes[2] + face->firstVert; + numIboIndexes += 3; + } + } + break; + + case SF_GRID: + { + srfGridMesh_t *grid; + + grid = (srfGridMesh_t *) surf2->data; + + for (k = 0; k < grid->numTriangles; k++) + { + *outIboIndexes++ = grid->triangles[k].indexes[0] + grid->firstVert; + *outIboIndexes++ = grid->triangles[k].indexes[1] + grid->firstVert; + *outIboIndexes++ = grid->triangles[k].indexes[2] + grid->firstVert; + numIboIndexes += 3; + } + } + break; + + case SF_TRIANGLES: + { + srfTriangles_t *tris; + + tris = (srfTriangles_t *) surf2->data; + + for (k = 0; k < tris->numTriangles; k++) + { + *outIboIndexes++ = tris->triangles[k].indexes[0] + tris->firstVert; + *outIboIndexes++ = tris->triangles[k].indexes[1] + tris->firstVert; + *outIboIndexes++ = tris->triangles[k].indexes[2] + tris->firstVert; + numIboIndexes += 3; + } + } + break; + + // never happens, but silences a compile warning + default: + break; + } + } + + vboSurf = ri.Hunk_Alloc(sizeof(*vboSurf), h_low); + memset(vboSurf, 0, sizeof(*vboSurf)); + vboSurf->surfaceType = SF_VBO_MESH; + + vboSurf->vbo = s_worldData.vbo; + vboSurf->ibo = ibo; + + vboSurf->numIndexes = numTriangles * 3; + vboSurf->numVerts = numVerts; + vboSurf->firstIndex = firstIndex; + + vboSurf->shader = surf1->shader; + vboSurf->fogIndex = surf1->fogIndex; + + VectorCopy(bounds[0], vboSurf->bounds[0]); + VectorCopy(bounds[1], vboSurf->bounds[1]); + + VectorCopy(bounds[0], mergedSurf->cullinfo.bounds[0]); + VectorCopy(bounds[1], mergedSurf->cullinfo.bounds[1]); + + mergedSurf->cullinfo.type = CULLINFO_BOX; + mergedSurf->data = (surfaceType_t *)vboSurf; + mergedSurf->fogIndex = surf1->fogIndex; + mergedSurf->shader = surf1->shader; + + // redirect view surfaces to this surf + for (j = i; j < numWorldSurfaces; j++) + { + if (s_worldData.surfacesViewCount[j] != i) + continue; + + for (k = 0; k < s_worldData.nummarksurfaces; k++) + { + int *mark = s_worldData.marksurfaces + k; + int *view = s_worldData.viewSurfaces + k; + + if (*mark == j) + *view = -(mergedSurfIndex + 1); + } + } + + mergedSurfIndex++; + mergedSurf++; + } + + // finish up the ibo + R_SyncRenderThread(); + + qglGenBuffersARB(1, &ibo->indexesVBO); + + R_BindIBO(ibo); + + qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, numIboIndexes * sizeof(*iboIndexes), iboIndexes, GL_STATIC_DRAW_ARB); + + R_BindNullIBO(); + + GL_CheckErrors(); + + ri.Free(iboIndexes); + + endTime = ri.Milliseconds(); + + ri.Printf(PRINT_ALL, "Processed %d surfaces into %d merged, %d unmerged in %5.2f seconds\n", + numWorldSurfaces, numMergedSurfaces, numUnmergedSurfaces, (endTime - startTime) / 1000.0f); + + // reset viewcounts + for (i = 0; i < numWorldSurfaces; i++) + { + s_worldData.surfacesViewCount[i] = -1; + } +} + + +void R_CalcVertexLightDirs( void ) +{ + int i, k; + msurface_t *surface; + + for(k = 0, surface = &s_worldData.surfaces[0]; k < s_worldData.numsurfaces /* s_worldData.numWorldSurfaces */; k++, surface++) + { + if(*surface->data == SF_FACE) + { + srfSurfaceFace_t *srf = (srfSurfaceFace_t *) surface->data; + + if(srf->numVerts) + { + for(i = 0; i < srf->numVerts; i++) + { + R_LightDirForPoint( srf->verts[i].xyz, srf->verts[i].lightdir, srf->verts[i].normal, &s_worldData ); + } + } + } + else if(*surface->data == SF_GRID) + { + srfGridMesh_t *srf = (srfGridMesh_t *) surface->data; + + if(srf->numVerts) + { + for(i = 0; i < srf->numVerts; i++) + { + R_LightDirForPoint( srf->verts[i].xyz, srf->verts[i].lightdir, srf->verts[i].normal, &s_worldData ); + } + } + } + else if(*surface->data == SF_TRIANGLES) + { + srfTriangles_t *srf = (srfTriangles_t *) surface->data; + + if(srf->numVerts) + { + for(i = 0; i < srf->numVerts; i++) + { + R_LightDirForPoint( srf->verts[i].xyz, srf->verts[i].lightdir, srf->verts[i].normal, &s_worldData ); + } + } + } + } +} + + +/* +================= +RE_LoadWorldMap + +Called directly from cgame +================= +*/ +void RE_LoadWorldMap( const char *name ) { + int i; + dheader_t *header; + union { + byte *b; + void *v; + } buffer; + byte *startMarker; + + if ( tr.worldMapLoaded ) { + ri.Error( ERR_DROP, "ERROR: attempted to redundantly load world map\n" ); + } + + // set default map light scale + tr.mapLightScale = 1.0f; + + // set default sun direction to be used if it isn't + // overridden by a shader + tr.sunDirection[0] = 0.45f; + tr.sunDirection[1] = 0.3f; + tr.sunDirection[2] = 0.9f; + + VectorNormalize( tr.sunDirection ); + + // set default autoexposure settings + tr.autoExposureMinMax[0] = -2.0f; + tr.autoExposureMinMax[1] = 2.0f; + + // set default tone mapping settings + tr.toneMinAvgMaxLevel[0] = -3.25f; + tr.toneMinAvgMaxLevel[1] = -1.0f; + tr.toneMinAvgMaxLevel[2] = 1.0f; + + tr.worldMapLoaded = qtrue; + + // load it + ri.FS_ReadFile( name, &buffer.v ); + if ( !buffer.b ) { + ri.Error (ERR_DROP, "RE_LoadWorldMap: %s not found", name); + } + + // clear tr.world so if the level fails to load, the next + // try will not look at the partially loaded version + tr.world = NULL; + + Com_Memset( &s_worldData, 0, sizeof( s_worldData ) ); + Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) ); + + Q_strncpyz( s_worldData.baseName, COM_SkipPath( s_worldData.name ), sizeof( s_worldData.name ) ); + COM_StripExtension(s_worldData.baseName, s_worldData.baseName, sizeof(s_worldData.baseName)); + + startMarker = ri.Hunk_Alloc(0, h_low); + c_gridVerts = 0; + + header = (dheader_t *)buffer.b; + fileBase = (byte *)header; + + i = LittleLong (header->version); + if ( i != BSP_VERSION ) { + ri.Error (ERR_DROP, "RE_LoadWorldMap: %s has wrong version number (%i should be %i)", + name, i, BSP_VERSION); + } + + // swap all the lumps + for (i=0 ; ilumps[LUMP_ENTITIES] ); + R_LoadShaders( &header->lumps[LUMP_SHADERS] ); + R_LoadLightmaps( &header->lumps[LUMP_LIGHTMAPS], &header->lumps[LUMP_SURFACES] ); + R_LoadPlanes (&header->lumps[LUMP_PLANES]); + R_LoadFogs( &header->lumps[LUMP_FOGS], &header->lumps[LUMP_BRUSHES], &header->lumps[LUMP_BRUSHSIDES] ); + R_LoadSurfaces( &header->lumps[LUMP_SURFACES], &header->lumps[LUMP_DRAWVERTS], &header->lumps[LUMP_DRAWINDEXES] ); + R_LoadMarksurfaces (&header->lumps[LUMP_LEAFSURFACES]); + R_LoadNodesAndLeafs (&header->lumps[LUMP_NODES], &header->lumps[LUMP_LEAFS]); + R_LoadSubmodels (&header->lumps[LUMP_MODELS]); + R_LoadVisibility( &header->lumps[LUMP_VISIBILITY] ); + R_LoadLightGrid( &header->lumps[LUMP_LIGHTGRID] ); + + // determine vertex light directions + R_CalcVertexLightDirs(); + + // create static VBOS from the world + R_CreateWorldVBO(); + if (r_mergeLeafSurfaces->integer) + { + R_MergeLeafSurfaces(); + } + + s_worldData.dataSize = (byte *)ri.Hunk_Alloc(0, h_low) - startMarker; + + // only set tr.world now that we know the entire level has loaded properly + tr.world = &s_worldData; + + // make sure the VBO glState entries are safe + R_BindNullVBO(); + R_BindNullIBO(); + + ri.FS_FreeFile( buffer.v ); +} diff --git a/src/rend2/tr_cmds.c b/src/rend2/tr_cmds.c new file mode 100644 index 00000000..b853f2fe --- /dev/null +++ b/src/rend2/tr_cmds.c @@ -0,0 +1,666 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "tr_local.h" + +volatile renderCommandList_t *renderCommandList; + +volatile qboolean renderThreadActive; + + +/* +===================== +R_PerformanceCounters +===================== +*/ +void R_PerformanceCounters( void ) { + if ( !r_speeds->integer ) { + // clear the counters even if we aren't printing + Com_Memset( &tr.pc, 0, sizeof( tr.pc ) ); + Com_Memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); + return; + } + + if (r_speeds->integer == 1) { + ri.Printf (PRINT_ALL, "%i/%i/%i shaders/batches/surfs %i leafs %i verts %i/%i tris %.2f mtex %.2f dc\n", + backEnd.pc.c_shaders, backEnd.pc.c_surfBatches, backEnd.pc.c_surfaces, tr.pc.c_leafs, backEnd.pc.c_vertexes, + backEnd.pc.c_indexes/3, backEnd.pc.c_totalIndexes/3, + R_SumOfUsedImages()/(1000000.0f), backEnd.pc.c_overDraw / (float)(glConfig.vidWidth * glConfig.vidHeight) ); + } else if (r_speeds->integer == 2) { + ri.Printf (PRINT_ALL, "(patch) %i sin %i sclip %i sout %i bin %i bclip %i bout\n", + tr.pc.c_sphere_cull_patch_in, tr.pc.c_sphere_cull_patch_clip, tr.pc.c_sphere_cull_patch_out, + tr.pc.c_box_cull_patch_in, tr.pc.c_box_cull_patch_clip, tr.pc.c_box_cull_patch_out ); + ri.Printf (PRINT_ALL, "(md3) %i sin %i sclip %i sout %i bin %i bclip %i bout\n", + tr.pc.c_sphere_cull_md3_in, tr.pc.c_sphere_cull_md3_clip, tr.pc.c_sphere_cull_md3_out, + tr.pc.c_box_cull_md3_in, tr.pc.c_box_cull_md3_clip, tr.pc.c_box_cull_md3_out ); + } else if (r_speeds->integer == 3) { + ri.Printf (PRINT_ALL, "viewcluster: %i\n", tr.viewCluster ); + } else if (r_speeds->integer == 4) { + if ( backEnd.pc.c_dlightVertexes ) { + ri.Printf (PRINT_ALL, "dlight srf:%i culled:%i verts:%i tris:%i\n", + tr.pc.c_dlightSurfaces, tr.pc.c_dlightSurfacesCulled, + backEnd.pc.c_dlightVertexes, backEnd.pc.c_dlightIndexes / 3 ); + } + } + else if (r_speeds->integer == 5 ) + { + ri.Printf( PRINT_ALL, "zFar: %.0f\n", tr.viewParms.zFar ); + } + else if (r_speeds->integer == 6 ) + { + ri.Printf( PRINT_ALL, "flare adds:%i tests:%i renders:%i\n", + backEnd.pc.c_flareAdds, backEnd.pc.c_flareTests, backEnd.pc.c_flareRenders ); + } + else if (r_speeds->integer == 7 ) + { + ri.Printf( PRINT_ALL, "VBO draws: static %i dynamic %i\nMultidraws: %i merged %i\n", + backEnd.pc.c_staticVboDraws, backEnd.pc.c_dynamicVboDraws, backEnd.pc.c_multidraws, backEnd.pc.c_multidrawsMerged ); + ri.Printf( PRINT_ALL, "GLSL binds: %i draws: gen %i light %i fog %i dlight %i\n", + backEnd.pc.c_glslShaderBinds, backEnd.pc.c_genericDraws, backEnd.pc.c_lightallDraws, backEnd.pc.c_fogDraws, backEnd.pc.c_dlightDraws); + } + + Com_Memset( &tr.pc, 0, sizeof( tr.pc ) ); + Com_Memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); +} + + +/* +==================== +R_InitCommandBuffers +==================== +*/ +void R_InitCommandBuffers( void ) { + glConfig.smpActive = qfalse; + if ( r_smp->integer ) { + ri.Printf( PRINT_ALL, "Trying SMP acceleration...\n" ); + if ( GLimp_SpawnRenderThread( RB_RenderThread ) ) { + ri.Printf( PRINT_ALL, "...succeeded.\n" ); + glConfig.smpActive = qtrue; + } else { + ri.Printf( PRINT_ALL, "...failed.\n" ); + } + } +} + +/* +==================== +R_ShutdownCommandBuffers +==================== +*/ +void R_ShutdownCommandBuffers( void ) { + // kill the rendering thread + if ( glConfig.smpActive ) { + GLimp_WakeRenderer( NULL ); + glConfig.smpActive = qfalse; + } +} + +/* +==================== +R_IssueRenderCommands +==================== +*/ +int c_blockedOnRender; +int c_blockedOnMain; + +void R_IssueRenderCommands( qboolean runPerformanceCounters ) { + renderCommandList_t *cmdList; + + cmdList = &backEndData[tr.smpFrame]->commands; + assert(cmdList); + // add an end-of-list command + *(int *)(cmdList->cmds + cmdList->used) = RC_END_OF_LIST; + + // clear it out, in case this is a sync and not a buffer flip + cmdList->used = 0; + + if ( glConfig.smpActive ) { + // if the render thread is not idle, wait for it + if ( renderThreadActive ) { + c_blockedOnRender++; + if ( r_showSmp->integer ) { + ri.Printf( PRINT_ALL, "R" ); + } + } else { + c_blockedOnMain++; + if ( r_showSmp->integer ) { + ri.Printf( PRINT_ALL, "." ); + } + } + + // sleep until the renderer has completed + GLimp_FrontEndSleep(); + } + + // at this point, the back end thread is idle, so it is ok + // to look at its performance counters + if ( runPerformanceCounters ) { + R_PerformanceCounters(); + } + + // actually start the commands going + if ( !r_skipBackEnd->integer ) { + // let it start on the new batch + if ( !glConfig.smpActive ) { + RB_ExecuteRenderCommands( cmdList->cmds ); + } else { + GLimp_WakeRenderer( cmdList ); + } + } +} + + +/* +==================== +R_SyncRenderThread + +Issue any pending commands and wait for them to complete. +After exiting, the render thread will have completed its work +and will remain idle and the main thread is free to issue +OpenGL calls until R_IssueRenderCommands is called. +==================== +*/ +void R_SyncRenderThread( void ) { + if ( !tr.registered ) { + return; + } + R_IssueRenderCommands( qfalse ); + + if ( !glConfig.smpActive ) { + return; + } + GLimp_FrontEndSleep(); +} + +/* +============ +R_GetCommandBuffer + +make sure there is enough command space, waiting on the +render thread if needed. +============ +*/ +void *R_GetCommandBuffer( int bytes ) { + renderCommandList_t *cmdList; + + cmdList = &backEndData[tr.smpFrame]->commands; + bytes = PAD(bytes, sizeof(void *)); + + // always leave room for the end of list command + if ( cmdList->used + bytes + 4 > MAX_RENDER_COMMANDS ) { + if ( bytes > MAX_RENDER_COMMANDS - 4 ) { + ri.Error( ERR_FATAL, "R_GetCommandBuffer: bad size %i", bytes ); + } + // if we run out of room, just start dropping commands + return NULL; + } + + cmdList->used += bytes; + + return cmdList->cmds + cmdList->used - bytes; +} + + +/* +============= +R_AddDrawSurfCmd + +============= +*/ +void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ) { + drawSurfsCommand_t *cmd; + + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_DRAW_SURFS; + + cmd->drawSurfs = drawSurfs; + cmd->numDrawSurfs = numDrawSurfs; + + cmd->refdef = tr.refdef; + cmd->viewParms = tr.viewParms; +} + + +/* +============= +R_AddCapShadowmapCmd + +============= +*/ +void R_AddCapShadowmapCmd( int map, int cubeSide ) { + capShadowmapCommand_t *cmd; + + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_CAPSHADOWMAP; + + cmd->map = map; + cmd->cubeSide = cubeSide; +} + + +/* +============= +R_PostProcessingCmd + +============= +*/ +void R_AddPostProcessCmd( ) { + postProcessCommand_t *cmd; + + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_POSTPROCESS; + + cmd->refdef = tr.refdef; + cmd->viewParms = tr.viewParms; +} + +/* +============= +RE_SetColor + +Passing NULL will set the color to white +============= +*/ +void RE_SetColor( const float *rgba ) { + setColorCommand_t *cmd; + + if ( !tr.registered ) { + return; + } + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SET_COLOR; + if ( !rgba ) { + static float colorWhite[4] = { 1, 1, 1, 1 }; + + rgba = colorWhite; + } + + cmd->color[0] = rgba[0]; + cmd->color[1] = rgba[1]; + cmd->color[2] = rgba[2]; + cmd->color[3] = rgba[3]; +} + + +/* +============= +RE_StretchPic +============= +*/ +void RE_StretchPic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ) { + stretchPicCommand_t *cmd; + + if (!tr.registered) { + return; + } + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_STRETCH_PIC; + cmd->shader = R_GetShaderByHandle( hShader ); + cmd->x = x; + cmd->y = y; + cmd->w = w; + cmd->h = h; + cmd->s1 = s1; + cmd->t1 = t1; + cmd->s2 = s2; + cmd->t2 = t2; +} + +#define MODE_RED_CYAN 1 +#define MODE_RED_BLUE 2 +#define MODE_RED_GREEN 3 +#define MODE_GREEN_MAGENTA 4 +#define MODE_MAX MODE_GREEN_MAGENTA + +void R_SetColorMode(GLboolean *rgba, stereoFrame_t stereoFrame, int colormode) +{ + rgba[0] = rgba[1] = rgba[2] = rgba[3] = GL_TRUE; + + if(colormode > MODE_MAX) + { + if(stereoFrame == STEREO_LEFT) + stereoFrame = STEREO_RIGHT; + else if(stereoFrame == STEREO_RIGHT) + stereoFrame = STEREO_LEFT; + + colormode -= MODE_MAX; + } + + if(colormode == MODE_GREEN_MAGENTA) + { + if(stereoFrame == STEREO_LEFT) + rgba[0] = rgba[2] = GL_FALSE; + else if(stereoFrame == STEREO_RIGHT) + rgba[1] = GL_FALSE; + } + else + { + if(stereoFrame == STEREO_LEFT) + rgba[1] = rgba[2] = GL_FALSE; + else if(stereoFrame == STEREO_RIGHT) + { + rgba[0] = GL_FALSE; + + if(colormode == MODE_RED_BLUE) + rgba[1] = GL_FALSE; + else if(colormode == MODE_RED_GREEN) + rgba[2] = GL_FALSE; + } + } +} + + +/* +==================== +RE_BeginFrame + +If running in stereo, RE_BeginFrame will be called twice +for each RE_EndFrame +==================== +*/ +void RE_BeginFrame( stereoFrame_t stereoFrame ) { + drawBufferCommand_t *cmd = NULL; + colorMaskCommand_t *colcmd = NULL; + + if ( !tr.registered ) { + return; + } + glState.finishCalled = qfalse; + + tr.frameCount++; + tr.frameSceneNum = 0; + + // + // do overdraw measurement + // + if ( r_measureOverdraw->integer ) + { + 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; + } + else if ( r_shadows->integer == 2 ) + { + ri.Printf( PRINT_ALL, "Warning: stencil shadows and overdraw measurement are mutually exclusive\n" ); + ri.Cvar_Set( "r_measureOverdraw", "0" ); + r_measureOverdraw->modified = qfalse; + } + else + { + R_SyncRenderThread(); + qglEnable( GL_STENCIL_TEST ); + qglStencilMask( ~0U ); + qglClearStencil( 0U ); + qglStencilFunc( GL_ALWAYS, 0U, ~0U ); + qglStencilOp( GL_KEEP, GL_INCR, GL_INCR ); + } + r_measureOverdraw->modified = qfalse; + } + else + { + // this is only reached if it was on and is now off + if ( r_measureOverdraw->modified ) { + R_SyncRenderThread(); + qglDisable( GL_STENCIL_TEST ); + } + r_measureOverdraw->modified = qfalse; + } + + // + // texturemode stuff + // + if ( r_textureMode->modified ) { + R_SyncRenderThread(); + GL_TextureMode( r_textureMode->string ); + r_textureMode->modified = qfalse; + } + + // + // gamma stuff + // + if ( r_gamma->modified ) { + r_gamma->modified = qfalse; + + R_SyncRenderThread(); + R_SetColorMappings(); + } + + // check for errors + if ( !r_ignoreGLErrors->integer ) + { + int err; + + R_SyncRenderThread(); + if ((err = qglGetError()) != GL_NO_ERROR) + ri.Error(ERR_FATAL, "RE_BeginFrame() - glGetError() failed (0x%x)!", err); + } + + if (glConfig.stereoEnabled) { + if( !(cmd = R_GetCommandBuffer(sizeof(*cmd))) ) + return; + + cmd->commandId = RC_DRAW_BUFFER; + + if ( stereoFrame == STEREO_LEFT ) { + cmd->buffer = (int)GL_BACK_LEFT; + } else if ( stereoFrame == STEREO_RIGHT ) { + cmd->buffer = (int)GL_BACK_RIGHT; + } else { + ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame ); + } + } + else + { + if(r_anaglyphMode->integer) + { + if(r_anaglyphMode->modified) + { + // clear both, front and backbuffer. + qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + backEnd.colorMask[0] = GL_FALSE; + backEnd.colorMask[1] = GL_FALSE; + backEnd.colorMask[2] = GL_FALSE; + backEnd.colorMask[3] = GL_FALSE; + qglClearColor(0.0f, 0.0f, 0.0f, 1.0f); + + qglDrawBuffer(GL_FRONT); + qglClear(GL_COLOR_BUFFER_BIT); + qglDrawBuffer(GL_BACK); + qglClear(GL_COLOR_BUFFER_BIT); + + if (glRefConfig.framebufferObject) + { + // clear all framebuffers + // FIXME: must be a better way to do this + int i; + + for (i = 0; i < 3; i++) + { + if (i == 1 && !tr.msaaResolveFbo) + continue; + + switch(i) + { + case 0: + FBO_Bind(tr.renderFbo); + break; + + case 1: + FBO_Bind(tr.msaaResolveFbo); + break; + + case 2: + FBO_Bind(tr.screenScratchFbo); + break; + } + + qglDrawBuffer(GL_FRONT); + qglClear(GL_COLOR_BUFFER_BIT); + qglDrawBuffer(GL_BACK); + qglClear(GL_COLOR_BUFFER_BIT); + } + + FBO_Bind(NULL); + } + + r_anaglyphMode->modified = qfalse; + } + + if(stereoFrame == STEREO_LEFT) + { + if( !(cmd = R_GetCommandBuffer(sizeof(*cmd))) ) + return; + + if( !(colcmd = R_GetCommandBuffer(sizeof(*colcmd))) ) + return; + } + else if(stereoFrame == STEREO_RIGHT) + { + clearDepthCommand_t *cldcmd; + + if( !(cldcmd = R_GetCommandBuffer(sizeof(*cldcmd))) ) + return; + + cldcmd->commandId = RC_CLEARDEPTH; + + if( !(colcmd = R_GetCommandBuffer(sizeof(*colcmd))) ) + return; + } + else + ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame ); + + R_SetColorMode(colcmd->rgba, stereoFrame, r_anaglyphMode->integer); + colcmd->commandId = RC_COLORMASK; + } + else + { + if(stereoFrame != STEREO_CENTER) + ri.Error( ERR_FATAL, "RE_BeginFrame: Stereo is disabled, but stereoFrame was %i", stereoFrame ); + + if( !(cmd = R_GetCommandBuffer(sizeof(*cmd))) ) + return; + } + + if(cmd) + { + cmd->commandId = RC_DRAW_BUFFER; + + if(r_anaglyphMode->modified) + { + qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + backEnd.colorMask[0] = 0; + backEnd.colorMask[1] = 0; + backEnd.colorMask[2] = 0; + backEnd.colorMask[3] = 0; + r_anaglyphMode->modified = qfalse; + } + + if (!Q_stricmp(r_drawBuffer->string, "GL_FRONT")) + cmd->buffer = (int)GL_FRONT; + else + cmd->buffer = (int)GL_BACK; + } + } + + tr.refdef.stereoFrame = stereoFrame; +} + + +/* +============= +RE_EndFrame + +Returns the number of msec spent in the back end +============= +*/ +void RE_EndFrame( int *frontEndMsec, int *backEndMsec ) { + swapBuffersCommand_t *cmd; + + if ( !tr.registered ) { + return; + } + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SWAP_BUFFERS; + + R_IssueRenderCommands( qtrue ); + + // use the other buffers next frame, because another CPU + // may still be rendering into the current ones + R_ToggleSmpFrame(); + + if ( frontEndMsec ) { + *frontEndMsec = tr.frontEndMsec; + } + tr.frontEndMsec = 0; + if ( backEndMsec ) { + *backEndMsec = backEnd.pc.msec; + } + backEnd.pc.msec = 0; +} + +/* +============= +RE_TakeVideoFrame +============= +*/ +void RE_TakeVideoFrame( int width, int height, + byte *captureBuffer, byte *encodeBuffer, qboolean motionJpeg ) +{ + videoFrameCommand_t *cmd; + + if( !tr.registered ) { + return; + } + + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if( !cmd ) { + return; + } + + cmd->commandId = RC_VIDEOFRAME; + + cmd->width = width; + cmd->height = height; + cmd->captureBuffer = captureBuffer; + cmd->encodeBuffer = encodeBuffer; + cmd->motionJpeg = motionJpeg; +} diff --git a/src/rend2/tr_curve.c b/src/rend2/tr_curve.c new file mode 100644 index 00000000..3d439257 --- /dev/null +++ b/src/rend2/tr_curve.c @@ -0,0 +1,806 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "tr_local.h" + +/* + +This file does all of the processing necessary to turn a raw grid of points +read from the map file into a srfGridMesh_t ready for rendering. + +The level of detail solution is direction independent, based only on subdivided +distance from the true curve. + +Only a single entry point: + +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { + +*/ + + +/* +============ +LerpDrawVert +============ +*/ +static void LerpDrawVert( srfVert_t *a, srfVert_t *b, srfVert_t *out ) { + out->xyz[0] = 0.5f * (a->xyz[0] + b->xyz[0]); + out->xyz[1] = 0.5f * (a->xyz[1] + b->xyz[1]); + out->xyz[2] = 0.5f * (a->xyz[2] + b->xyz[2]); + + out->st[0] = 0.5f * (a->st[0] + b->st[0]); + out->st[1] = 0.5f * (a->st[1] + b->st[1]); + + out->lightmap[0] = 0.5f * (a->lightmap[0] + b->lightmap[0]); + out->lightmap[1] = 0.5f * (a->lightmap[1] + b->lightmap[1]); + + out->vertexColors[0] = 0.5f * (a->vertexColors[0] + b->vertexColors[0]); + out->vertexColors[1] = 0.5f * (a->vertexColors[1] + b->vertexColors[1]); + out->vertexColors[2] = 0.5f * (a->vertexColors[2] + b->vertexColors[2]); + out->vertexColors[3] = 0.5f * (a->vertexColors[3] + b->vertexColors[3]); +} + +/* +============ +Transpose +============ +*/ +static void Transpose( int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j; + srfVert_t temp; + + if ( width > height ) { + for ( i = 0 ; i < height ; i++ ) { + for ( j = i + 1 ; j < width ; j++ ) { + if ( j < height ) { + // swap the value + temp = ctrl[j][i]; + ctrl[j][i] = ctrl[i][j]; + ctrl[i][j] = temp; + } else { + // just copy + ctrl[j][i] = ctrl[i][j]; + } + } + } + } else { + for ( i = 0 ; i < width ; i++ ) { + for ( j = i + 1 ; j < height ; j++ ) { + if ( j < width ) { + // swap the value + temp = ctrl[i][j]; + ctrl[i][j] = ctrl[j][i]; + ctrl[j][i] = temp; + } else { + // just copy + ctrl[i][j] = ctrl[j][i]; + } + } + } + } + +} + + +/* +================= +MakeMeshNormals + +Handles all the complicated wrapping and degenerate cases +================= +*/ +static void MakeMeshNormals( int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j, k, dist; + vec3_t normal; + vec3_t sum; + int count = 0; + vec3_t base; + vec3_t delta; + int x, y; + srfVert_t *dv; + vec3_t around[8], temp; + qboolean good[8]; + qboolean wrapWidth, wrapHeight; + float len; +static int neighbors[8][2] = { + {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1} + }; + + wrapWidth = qfalse; + for ( i = 0 ; i < height ; i++ ) { + VectorSubtract( ctrl[i][0].xyz, ctrl[i][width-1].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == height ) { + wrapWidth = qtrue; + } + + wrapHeight = qfalse; + for ( i = 0 ; i < width ; i++ ) { + VectorSubtract( ctrl[0][i].xyz, ctrl[height-1][i].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == width) { + wrapHeight = qtrue; + } + + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + count = 0; + dv = &ctrl[j][i]; + VectorCopy( dv->xyz, base ); + for ( k = 0 ; k < 8 ; k++ ) { + VectorClear( around[k] ); + good[k] = qfalse; + + for ( dist = 1 ; dist <= 3 ; dist++ ) { + x = i + neighbors[k][0] * dist; + y = j + neighbors[k][1] * dist; + if ( wrapWidth ) { + if ( x < 0 ) { + x = width - 1 + x; + } else if ( x >= width ) { + x = 1 + x - width; + } + } + if ( wrapHeight ) { + if ( y < 0 ) { + y = height - 1 + y; + } else if ( y >= height ) { + y = 1 + y - height; + } + } + + if ( x < 0 || x >= width || y < 0 || y >= height ) { + break; // edge of patch + } + VectorSubtract( ctrl[y][x].xyz, base, temp ); + if ( VectorNormalize2( temp, temp ) == 0 ) { + continue; // degenerate edge, get more dist + } else { + good[k] = qtrue; + VectorCopy( temp, around[k] ); + break; // good edge + } + } + } + + VectorClear( sum ); + for ( k = 0 ; k < 8 ; k++ ) { + if ( !good[k] || !good[(k+1)&7] ) { + continue; // didn't get two points + } + CrossProduct( around[(k+1)&7], around[k], normal ); + if ( VectorNormalize2( normal, normal ) == 0 ) { + continue; + } + VectorAdd( normal, sum, sum ); + count++; + } + //if ( count == 0 ) { + // printf("bad normal\n"); + //} + VectorNormalize2( sum, dv->normal ); + } + } +} + +#ifdef USE_VERT_TANGENT_SPACE +static void MakeMeshTangentVectors(int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], int numTriangles, + srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2]) +{ + int i, j; + srfVert_t *dv[3]; + static srfVert_t ctrl2[MAX_GRID_SIZE * MAX_GRID_SIZE]; + srfTriangle_t *tri; + + // FIXME: use more elegant way + for(i = 0; i < width; i++) + { + for(j = 0; j < height; j++) + { + dv[0] = &ctrl2[j * width + i]; + *dv[0] = ctrl[j][i]; + } + } + + for(i = 0, tri = triangles; i < numTriangles; i++, tri++) + { + dv[0] = &ctrl2[tri->indexes[0]]; + dv[1] = &ctrl2[tri->indexes[1]]; + dv[2] = &ctrl2[tri->indexes[2]]; + + R_CalcTangentVectors(dv); + } + +#if 0 + for(i = 0; i < (width * height); i++) + { + dv0 = &ctrl2[i]; + + VectorNormalize(dv0->normal); +#if 0 + VectorNormalize(dv0->tangent); + VectorNormalize(dv0->bitangent); +#else + d = DotProduct(dv0->tangent, dv0->normal); + VectorMA(dv0->tangent, -d, dv0->normal, dv0->tangent); + VectorNormalize(dv0->tangent); + + d = DotProduct(dv0->bitangent, dv0->normal); + VectorMA(dv0->bitangent, -d, dv0->normal, dv0->bitangent); + VectorNormalize(dv0->bitangent); +#endif + } +#endif + + +#if 0 + // do another extra smoothing for normals to avoid flat shading + for(i = 0; i < (width * height); i++) + { + for(j = 0; j < (width * height); j++) + { + if(R_CompareVert(&ctrl2[i], &ctrl2[j], qfalse)) + { + VectorAdd(ctrl2[i].normal, ctrl2[j].normal, ctrl2[i].normal); + } + } + + VectorNormalize(ctrl2[i].normal); + } +#endif + + for(i = 0; i < width; i++) + { + for(j = 0; j < height; j++) + { + dv[0] = &ctrl2[j * width + i]; + dv[1] = &ctrl[j][i]; + + VectorCopy(dv[0]->tangent, dv[1]->tangent); + VectorCopy(dv[0]->bitangent, dv[1]->bitangent); + } + } +} +#endif + + +static int MakeMeshTriangles(int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], + srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2]) +{ + int i, j; + int numTriangles; + int w, h; + srfVert_t *dv; + static srfVert_t ctrl2[MAX_GRID_SIZE * MAX_GRID_SIZE]; + + h = height - 1; + w = width - 1; + numTriangles = 0; + for(i = 0; i < h; i++) + { + for(j = 0; j < w; j++) + { + int v1, v2, v3, v4; + + // vertex order to be reckognized as tristrips + v1 = i * width + j + 1; + v2 = v1 - 1; + v3 = v2 + width; + v4 = v3 + 1; + + triangles[numTriangles].indexes[0] = v2; + triangles[numTriangles].indexes[1] = v3; + triangles[numTriangles].indexes[2] = v1; + numTriangles++; + + triangles[numTriangles].indexes[0] = v1; + triangles[numTriangles].indexes[1] = v3; + triangles[numTriangles].indexes[2] = v4; + numTriangles++; + } + } + + R_CalcSurfaceTriangleNeighbors(numTriangles, triangles); + + // FIXME: use more elegant way + for(i = 0; i < width; i++) + { + for(j = 0; j < height; j++) + { + dv = &ctrl2[j * width + i]; + *dv = ctrl[j][i]; + } + } + + R_CalcSurfaceTrianglePlanes(numTriangles, triangles, ctrl2); + + return numTriangles; +} + + +/* +============ +InvertCtrl +============ +*/ +static void InvertCtrl( int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j; + srfVert_t temp; + + for ( i = 0 ; i < height ; i++ ) { + for ( j = 0 ; j < width/2 ; j++ ) { + temp = ctrl[i][j]; + ctrl[i][j] = ctrl[i][width-1-j]; + ctrl[i][width-1-j] = temp; + } + } +} + + +/* +================= +InvertErrorTable +================= +*/ +static void InvertErrorTable( float errorTable[2][MAX_GRID_SIZE], int width, int height ) { + int i; + float copy[2][MAX_GRID_SIZE]; + + Com_Memcpy( copy, errorTable, sizeof( copy ) ); + + for ( i = 0 ; i < width ; i++ ) { + errorTable[1][i] = copy[0][i]; //[width-1-i]; + } + + for ( i = 0 ; i < height ; i++ ) { + errorTable[0][i] = copy[1][height-1-i]; + } + +} + +/* +================== +PutPointsOnCurve +================== +*/ +static void PutPointsOnCurve( srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], + int width, int height ) { + int i, j; + srfVert_t prev, next; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 1 ; j < height ; j += 2 ) { + LerpDrawVert( &ctrl[j][i], &ctrl[j+1][i], &prev ); + LerpDrawVert( &ctrl[j][i], &ctrl[j-1][i], &next ); + LerpDrawVert( &prev, &next, &ctrl[j][i] ); + } + } + + + for ( j = 0 ; j < height ; j++ ) { + for ( i = 1 ; i < width ; i += 2 ) { + LerpDrawVert( &ctrl[j][i], &ctrl[j][i+1], &prev ); + LerpDrawVert( &ctrl[j][i], &ctrl[j][i-1], &next ); + LerpDrawVert( &prev, &next, &ctrl[j][i] ); + } + } +} + +/* +================= +R_CreateSurfaceGridMesh +================= +*/ +srfGridMesh_t *R_CreateSurfaceGridMesh(int width, int height, + srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], float errorTable[2][MAX_GRID_SIZE], + int numTriangles, srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2]) { + int i, j, size; + srfVert_t *vert; + vec3_t tmpVec; + srfGridMesh_t *grid; + + // copy the results out to a grid + size = (width * height - 1) * sizeof( srfVert_t ) + sizeof( *grid ); + +#ifdef PATCH_STITCHING + grid = /*ri.Hunk_Alloc*/ ri.Malloc( size ); + Com_Memset(grid, 0, size); + + grid->widthLodError = /*ri.Hunk_Alloc*/ ri.Malloc( width * 4 ); + Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 ); + + grid->heightLodError = /*ri.Hunk_Alloc*/ ri.Malloc( height * 4 ); + Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 ); + + grid->numTriangles = numTriangles; + grid->triangles = ri.Malloc(grid->numTriangles * sizeof(srfTriangle_t)); + Com_Memcpy(grid->triangles, triangles, numTriangles * sizeof(srfTriangle_t)); + + grid->numVerts = (width * height); + grid->verts = ri.Malloc(grid->numVerts * sizeof(srfVert_t)); +#else + grid = ri.Hunk_Alloc( size ); + Com_Memset(grid, 0, size); + + grid->widthLodError = ri.Hunk_Alloc( width * 4 ); + Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 ); + + grid->heightLodError = ri.Hunk_Alloc( height * 4 ); + Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 ); + + grid->numTriangles = numTriangles; + grid->triangles = ri.Hunk_Alloc(grid->numTriangles * sizeof(srfTriangle_t), h_low); + Com_Memcpy(grid->triangles, triangles, numTriangles * sizeof(srfTriangle_t)); + + grid->numVerts = (width * height); + grid->verts = ri.Hunk_Alloc(grid->numVerts * sizeof(srfVert_t), h_low); +#endif + + grid->width = width; + grid->height = height; + grid->surfaceType = SF_GRID; + ClearBounds( grid->meshBounds[0], grid->meshBounds[1] ); + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + vert = &grid->verts[j*width+i]; + *vert = ctrl[j][i]; + AddPointToBounds( vert->xyz, grid->meshBounds[0], grid->meshBounds[1] ); + } + } + + // compute local origin and bounds + VectorAdd( grid->meshBounds[0], grid->meshBounds[1], grid->localOrigin ); + VectorScale( grid->localOrigin, 0.5f, grid->localOrigin ); + VectorSubtract( grid->meshBounds[0], grid->localOrigin, tmpVec ); + grid->meshRadius = VectorLength( tmpVec ); + + VectorCopy( grid->localOrigin, grid->lodOrigin ); + grid->lodRadius = grid->meshRadius; + // + return grid; +} + +/* +================= +R_FreeSurfaceGridMesh +================= +*/ +void R_FreeSurfaceGridMesh( srfGridMesh_t *grid ) { + ri.Free(grid->widthLodError); + ri.Free(grid->heightLodError); + ri.Free(grid->triangles); + ri.Free(grid->verts); + ri.Free(grid); +} + +/* +================= +R_SubdividePatchToGrid +================= +*/ +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { + int i, j, k, l; + srfVert_t_cleared( prev ); + srfVert_t_cleared( next ); + srfVert_t_cleared( mid ); + float len, maxLen; + int dir; + int t; + srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + int numTriangles; + static srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2]; + int consecutiveComplete; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + ctrl[j][i] = points[j*width+i]; + } + } + + for ( dir = 0 ; dir < 2 ; dir++ ) { + + for ( j = 0 ; j < MAX_GRID_SIZE ; j++ ) { + errorTable[dir][j] = 0; + } + + consecutiveComplete = 0; + + // horizontal subdivisions + for ( j = 0 ; ; j = (j + 2) % (width - 1) ) { + // check subdivided midpoints against control points + + // FIXME: also check midpoints of adjacent patches against the control points + // this would basically stitch all patches in the same LOD group together. + + maxLen = 0; + for ( i = 0 ; i < height ; i++ ) { + vec3_t midxyz; + vec3_t midxyz2; + vec3_t dir; + vec3_t projected; + float d; + + // calculate the point on the curve + for ( l = 0 ; l < 3 ; l++ ) { + midxyz[l] = (ctrl[i][j].xyz[l] + ctrl[i][j+1].xyz[l] * 2 + + ctrl[i][j+2].xyz[l] ) * 0.25f; + } + + // see how far off the line it is + // using dist-from-line will not account for internal + // texture warping, but it gives a lot less polygons than + // dist-from-midpoint + VectorSubtract( midxyz, ctrl[i][j].xyz, midxyz ); + VectorSubtract( ctrl[i][j+2].xyz, ctrl[i][j].xyz, dir ); + VectorNormalize( dir ); + + d = DotProduct( midxyz, dir ); + VectorScale( dir, d, projected ); + VectorSubtract( midxyz, projected, midxyz2); + len = VectorLengthSquared( midxyz2 ); // we will do the sqrt later + if ( len > maxLen ) { + maxLen = len; + } + } + + maxLen = sqrt(maxLen); + + // if all the points are on the lines, remove the entire columns + if ( maxLen < 0.1f ) { + errorTable[dir][j+1] = 999; + // if we go over the whole grid twice without adding any columns, stop + if (++consecutiveComplete >= width) + break; + continue; + } + + // see if we want to insert subdivided columns + if ( width + 2 > MAX_GRID_SIZE ) { + errorTable[dir][j+1] = 1.0f/maxLen; + break; // can't subdivide any more + } + + if ( maxLen <= r_subdivisions->value ) { + errorTable[dir][j+1] = 1.0f/maxLen; + // if we go over the whole grid twice without adding any columns, stop + if (++consecutiveComplete >= width) + break; + continue; // didn't need subdivision + } + + errorTable[dir][j+2] = 1.0f/maxLen; + + consecutiveComplete = 0; + + // insert two columns and replace the peak + width += 2; + for ( i = 0 ; i < height ; i++ ) { + LerpDrawVert( &ctrl[i][j], &ctrl[i][j+1], &prev ); + LerpDrawVert( &ctrl[i][j+1], &ctrl[i][j+2], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = width - 1 ; k > j + 3 ; k-- ) { + ctrl[i][k] = ctrl[i][k-2]; + } + ctrl[i][j + 1] = prev; + ctrl[i][j + 2] = mid; + ctrl[i][j + 3] = next; + } + + // skip the new one, we'll get it on the next pass + j += 2; + } + + Transpose( width, height, ctrl ); + t = width; + width = height; + height = t; + } + + + // put all the aproximating points on the curve + PutPointsOnCurve( ctrl, width, height ); + + // cull out any rows or columns that are colinear + for ( i = 1 ; i < width-1 ; i++ ) { + if ( errorTable[0][i] != 999 ) { + continue; + } + for ( j = i+1 ; j < width ; j++ ) { + for ( k = 0 ; k < height ; k++ ) { + ctrl[k][j-1] = ctrl[k][j]; + } + errorTable[0][j-1] = errorTable[0][j]; + } + width--; + } + + for ( i = 1 ; i < height-1 ; i++ ) { + if ( errorTable[1][i] != 999 ) { + continue; + } + for ( j = i+1 ; j < height ; j++ ) { + for ( k = 0 ; k < width ; k++ ) { + ctrl[j-1][k] = ctrl[j][k]; + } + errorTable[1][j-1] = errorTable[1][j]; + } + height--; + } + +#if 1 + // flip for longest tristrips as an optimization + // the results should be visually identical with or + // without this step + if ( height > width ) { + Transpose( width, height, ctrl ); + InvertErrorTable( errorTable, width, height ); + t = width; + width = height; + height = t; + InvertCtrl( width, height, ctrl ); + } +#endif + + // calculate triangles + numTriangles = MakeMeshTriangles(width, height, ctrl, triangles); + + // calculate normals + MakeMeshNormals( width, height, ctrl ); +#ifdef USE_VERT_TANGENT_SPACE + MakeMeshTangentVectors(width, height, ctrl, numTriangles, triangles); +#endif + + return R_CreateSurfaceGridMesh(width, height, ctrl, errorTable, numTriangles, triangles); +} + +/* +=============== +R_GridInsertColumn +=============== +*/ +srfGridMesh_t *R_GridInsertColumn( srfGridMesh_t *grid, int column, int row, vec3_t point, float loderror ) { + int i, j; + int width, height, oldwidth; + srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + float lodRadius; + vec3_t lodOrigin; + int numTriangles; + static srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2]; + + oldwidth = 0; + width = grid->width + 1; + if (width > MAX_GRID_SIZE) + return NULL; + height = grid->height; + for (i = 0; i < width; i++) { + if (i == column) { + //insert new column + for (j = 0; j < grid->height; j++) { + LerpDrawVert( &grid->verts[j * grid->width + i-1], &grid->verts[j * grid->width + i], &ctrl[j][i] ); + if (j == row) + VectorCopy(point, ctrl[j][i].xyz); + } + errorTable[0][i] = loderror; + continue; + } + errorTable[0][i] = grid->widthLodError[oldwidth]; + for (j = 0; j < grid->height; j++) { + ctrl[j][i] = grid->verts[j * grid->width + oldwidth]; + } + oldwidth++; + } + for (j = 0; j < grid->height; j++) { + errorTable[1][j] = grid->heightLodError[j]; + } + // put all the aproximating points on the curve + //PutPointsOnCurve( ctrl, width, height ); + + // calculate triangles + numTriangles = MakeMeshTriangles(width, height, ctrl, triangles); + + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + VectorCopy(grid->lodOrigin, lodOrigin); + lodRadius = grid->lodRadius; + // free the old grid + R_FreeSurfaceGridMesh(grid); + // create a new grid + grid = R_CreateSurfaceGridMesh(width, height, ctrl, errorTable, numTriangles, triangles); + grid->lodRadius = lodRadius; + VectorCopy(lodOrigin, grid->lodOrigin); + return grid; +} + +/* +=============== +R_GridInsertRow +=============== +*/ +srfGridMesh_t *R_GridInsertRow( srfGridMesh_t *grid, int row, int column, vec3_t point, float loderror ) { + int i, j; + int width, height, oldheight; + srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + float lodRadius; + vec3_t lodOrigin; + int numTriangles; + static srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2]; + + oldheight = 0; + width = grid->width; + height = grid->height + 1; + if (height > MAX_GRID_SIZE) + return NULL; + for (i = 0; i < height; i++) { + if (i == row) { + //insert new row + for (j = 0; j < grid->width; j++) { + LerpDrawVert( &grid->verts[(i-1) * grid->width + j], &grid->verts[i * grid->width + j], &ctrl[i][j] ); + if (j == column) + VectorCopy(point, ctrl[i][j].xyz); + } + errorTable[1][i] = loderror; + continue; + } + errorTable[1][i] = grid->heightLodError[oldheight]; + for (j = 0; j < grid->width; j++) { + ctrl[i][j] = grid->verts[oldheight * grid->width + j]; + } + oldheight++; + } + for (j = 0; j < grid->width; j++) { + errorTable[0][j] = grid->widthLodError[j]; + } + // put all the aproximating points on the curve + //PutPointsOnCurve( ctrl, width, height ); + + // calculate triangles + numTriangles = MakeMeshTriangles(width, height, ctrl, triangles); + + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + VectorCopy(grid->lodOrigin, lodOrigin); + lodRadius = grid->lodRadius; + // free the old grid + R_FreeSurfaceGridMesh(grid); + // create a new grid + grid = R_CreateSurfaceGridMesh(width, height, ctrl, errorTable, numTriangles, triangles); + grid->lodRadius = lodRadius; + VectorCopy(lodOrigin, grid->lodOrigin); + return grid; +} diff --git a/src/rend2/tr_extensions.c b/src/rend2/tr_extensions.c new file mode 100644 index 00000000..f888cbc8 --- /dev/null +++ b/src/rend2/tr_extensions.c @@ -0,0 +1,682 @@ +/* +=========================================================================== +Copyright (C) 2011 James Canete (use.less01@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_extensions.c - extensions needed by the renderer not in sdl_glimp.c + +#ifdef USE_LOCAL_HEADERS +# include "SDL.h" +#else +# include +#endif + +#include "tr_local.h" + +// GL_EXT_draw_range_elements +void (APIENTRY * qglDrawRangeElementsEXT) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); + +// GL_EXT_multi_draw_arrays +void (APIENTRY * qglMultiDrawArraysEXT) (GLenum mode, GLint *first, GLsizei *count, GLsizei primcount); +void (APIENTRY * qglMultiDrawElementsEXT) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid **indices, GLsizei primcount); + +// GL_ARB_vertex_shader +void (APIENTRY * qglBindAttribLocationARB) (GLhandleARB programObj, GLuint index, const GLcharARB * name); +void (APIENTRY * qglGetActiveAttribARB) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei * length, + GLint * size, GLenum * type, GLcharARB * name); +GLint(APIENTRY * qglGetAttribLocationARB) (GLhandleARB programObj, const GLcharARB * name); + +// GL_ARB_vertex_program +void (APIENTRY * qglVertexAttrib4fARB) (GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +void (APIENTRY * qglVertexAttrib4fvARB) (GLuint, const GLfloat *); +void (APIENTRY * qglVertexAttribPointerARB) (GLuint index, GLint size, GLenum type, GLboolean normalized, + GLsizei stride, const GLvoid * pointer); +void (APIENTRY * qglEnableVertexAttribArrayARB) (GLuint index); +void (APIENTRY * qglDisableVertexAttribArrayARB) (GLuint index); + +// GL_ARB_vertex_buffer_object +void (APIENTRY * qglBindBufferARB) (GLenum target, GLuint buffer); +void (APIENTRY * qglDeleteBuffersARB) (GLsizei n, const GLuint * buffers); +void (APIENTRY * qglGenBuffersARB) (GLsizei n, GLuint * buffers); + +GLboolean(APIENTRY * qglIsBufferARB) (GLuint buffer); +void (APIENTRY * qglBufferDataARB) (GLenum target, GLsizeiptrARB size, const GLvoid * data, GLenum usage); +void (APIENTRY * qglBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid * data); +void (APIENTRY * qglGetBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid * data); + +void (APIENTRY * qglGetBufferParameterivARB) (GLenum target, GLenum pname, GLint * params); +void (APIENTRY * qglGetBufferPointervARB) (GLenum target, GLenum pname, GLvoid * *params); + +// GL_ARB_shader_objects +void (APIENTRY * qglDeleteObjectARB) (GLhandleARB obj); + +GLhandleARB(APIENTRY * qglGetHandleARB) (GLenum pname); +void (APIENTRY * qglDetachObjectARB) (GLhandleARB containerObj, GLhandleARB attachedObj); + +GLhandleARB(APIENTRY * qglCreateShaderObjectARB) (GLenum shaderType); +void (APIENTRY * qglShaderSourceARB) (GLhandleARB shaderObj, GLsizei count, const GLcharARB * *string, + const GLint * length); +void (APIENTRY * qglCompileShaderARB) (GLhandleARB shaderObj); + +GLhandleARB(APIENTRY * qglCreateProgramObjectARB) (void); +void (APIENTRY * qglAttachObjectARB) (GLhandleARB containerObj, GLhandleARB obj); +void (APIENTRY * qglLinkProgramARB) (GLhandleARB programObj); +void (APIENTRY * qglUseProgramObjectARB) (GLhandleARB programObj); +void (APIENTRY * qglValidateProgramARB) (GLhandleARB programObj); +void (APIENTRY * qglUniform1fARB) (GLint location, GLfloat v0); +void (APIENTRY * qglUniform2fARB) (GLint location, GLfloat v0, GLfloat v1); +void (APIENTRY * qglUniform3fARB) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +void (APIENTRY * qglUniform4fARB) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +void (APIENTRY * qglUniform1iARB) (GLint location, GLint v0); +void (APIENTRY * qglUniform2iARB) (GLint location, GLint v0, GLint v1); +void (APIENTRY * qglUniform3iARB) (GLint location, GLint v0, GLint v1, GLint v2); +void (APIENTRY * qglUniform4iARB) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +void (APIENTRY * qglUniform1fvARB) (GLint location, GLsizei count, const GLfloat * value); +void (APIENTRY * qglUniform2fvARB) (GLint location, GLsizei count, const GLfloat * value); +void (APIENTRY * qglUniform3fvARB) (GLint location, GLsizei count, const GLfloat * value); +void (APIENTRY * qglUniform4fvARB) (GLint location, GLsizei count, const GLfloat * value); +void (APIENTRY * qglUniform2ivARB) (GLint location, GLsizei count, const GLint * value); +void (APIENTRY * qglUniform3ivARB) (GLint location, GLsizei count, const GLint * value); +void (APIENTRY * qglUniform4ivARB) (GLint location, GLsizei count, const GLint * value); +void (APIENTRY * qglUniformMatrix2fvARB) (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); +void (APIENTRY * qglUniformMatrix3fvARB) (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); +void (APIENTRY * qglUniformMatrix4fvARB) (GLint location, GLsizei count, GLboolean transpose, const GLfloat * value); +void (APIENTRY * qglGetObjectParameterfvARB) (GLhandleARB obj, GLenum pname, GLfloat * params); +void (APIENTRY * qglGetObjectParameterivARB) (GLhandleARB obj, GLenum pname, GLint * params); +void (APIENTRY * qglGetInfoLogARB) (GLhandleARB obj, GLsizei maxLength, GLsizei * length, GLcharARB * infoLog); +void (APIENTRY * qglGetAttachedObjectsARB) (GLhandleARB containerObj, GLsizei maxCount, GLsizei * count, + GLhandleARB * obj); +GLint(APIENTRY * qglGetUniformLocationARB) (GLhandleARB programObj, const GLcharARB * name); +void (APIENTRY * qglGetActiveUniformARB) (GLhandleARB programObj, GLuint index, GLsizei maxIndex, GLsizei * length, + GLint * size, GLenum * type, GLcharARB * name); +void (APIENTRY * qglGetUniformfvARB) (GLhandleARB programObj, GLint location, GLfloat * params); +void (APIENTRY * qglGetUniformivARB) (GLhandleARB programObj, GLint location, GLint * params); +void (APIENTRY * qglGetShaderSourceARB) (GLhandleARB obj, GLsizei maxLength, GLsizei * length, GLcharARB * source); + +// GL_ARB_texture_compression +void (APIENTRY * qglCompressedTexImage3DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, + GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +void (APIENTRY * qglCompressedTexImage2DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, + GLint border, GLsizei imageSize, const GLvoid *data); +void (APIENTRY * qglCompressedTexImage1DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, + GLsizei imageSize, const GLvoid *data); +void (APIENTRY * qglCompressedTexSubImage3DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, + GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +void (APIENTRY * qglCompressedTexSubImage2DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, + GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +void (APIENTRY * qglCompressedTexSubImage1DARB)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, + GLsizei imageSize, const GLvoid *data); +void (APIENTRY * qglGetCompressedTexImageARB)(GLenum target, GLint lod, + GLvoid *img); + +// GL_EXT_framebuffer_object +GLboolean (APIENTRY * qglIsRenderbufferEXT)(GLuint renderbuffer); +void (APIENTRY * qglBindRenderbufferEXT)(GLenum target, GLuint renderbuffer); +void (APIENTRY * qglDeleteRenderbuffersEXT)(GLsizei n, const GLuint *renderbuffers); +void (APIENTRY * qglGenRenderbuffersEXT)(GLsizei n, GLuint *renderbuffers); + +void (APIENTRY * qglRenderbufferStorageEXT)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); + +void (APIENTRY * qglGetRenderbufferParameterivEXT)(GLenum target, GLenum pname, GLint *params); + +GLboolean (APIENTRY * qglIsFramebufferEXT)(GLuint framebuffer); +void (APIENTRY * qglBindFramebufferEXT)(GLenum target, GLuint framebuffer); +void (APIENTRY * qglDeleteFramebuffersEXT)(GLsizei n, const GLuint *framebuffers); +void (APIENTRY * qglGenFramebuffersEXT)(GLsizei n, GLuint *framebuffers); + +GLenum (APIENTRY * qglCheckFramebufferStatusEXT)(GLenum target); + +void (APIENTRY * qglFramebufferTexture1DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, + GLint level); +void (APIENTRY * qglFramebufferTexture2DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, + GLint level); +void (APIENTRY * qglFramebufferTexture3DEXT)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, + GLint level, GLint zoffset); + +void (APIENTRY * qglFramebufferRenderbufferEXT)(GLenum target, GLenum attachment, GLenum renderbuffertarget, + GLuint renderbuffer); + +void (APIENTRY * qglGetFramebufferAttachmentParameterivEXT)(GLenum target, GLenum attachment, GLenum pname, GLint *params); + +void (APIENTRY * qglGenerateMipmapEXT)(GLenum target); + +// GL_ARB_occlusion_query +void (APIENTRY * qglGenQueriesARB)(GLsizei n, GLuint *ids); +void (APIENTRY * qglDeleteQueriesARB)(GLsizei n, const GLuint *ids); +GLboolean (APIENTRY * qglIsQueryARB)(GLuint id); +void (APIENTRY * qglBeginQueryARB)(GLenum target, GLuint id); +void (APIENTRY * qglEndQueryARB)(GLenum target); +void (APIENTRY * qglGetQueryivARB)(GLenum target, GLenum pname, GLint *params); +void (APIENTRY * qglGetQueryObjectivARB)(GLuint id, GLenum pname, GLint *params); +void (APIENTRY * qglGetQueryObjectuivARB)(GLuint id, GLenum pname, GLuint *params); + +// GL_EXT_framebuffer_blit +void (APIENTRY * qglBlitFramebufferEXT)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, + GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, + GLbitfield mask, GLenum filter); + +// GL_EXT_framebuffer_multisample +void (APIENTRY * qglRenderbufferStorageMultisampleEXT)(GLenum target, GLsizei samples, + GLenum internalformat, GLsizei width, GLsizei height); + +// GL_ARB_draw_buffers +void (APIENTRY * qglDrawBuffersARB)(GLsizei n, const GLenum *bufs); + +static qboolean GLimp_HaveExtension(const char *ext) +{ + const char *ptr = Q_stristr( glConfig.extensions_string, ext ); + if (ptr == NULL) + return qfalse; + ptr += strlen(ext); + return ((*ptr == ' ') || (*ptr == '\0')); // verify it's complete string. +} + +void GLimp_InitExtraExtensions() +{ + char *extension; + const char* result[3] = { "...ignoring %s\n", "...using %s\n", "...%s not found\n" }; + + // GL_EXT_draw_range_elements + extension = "GL_EXT_draw_range_elements"; + glRefConfig.drawRangeElements = qfalse; + qglMultiDrawArraysEXT = NULL; + qglMultiDrawElementsEXT = NULL; + if( GLimp_HaveExtension( extension ) ) + { + qglDrawRangeElementsEXT = (void *) SDL_GL_GetProcAddress("glDrawRangeElementsEXT"); + + if ( r_ext_draw_range_elements->integer) + glRefConfig.drawRangeElements = qtrue; + + ri.Printf(PRINT_ALL, result[glRefConfig.drawRangeElements], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_EXT_multi_draw_arrays + extension = "GL_EXT_multi_draw_arrays"; + glRefConfig.multiDrawArrays = qfalse; + qglMultiDrawArraysEXT = NULL; + qglMultiDrawElementsEXT = NULL; + if( GLimp_HaveExtension( extension ) ) + { + qglMultiDrawArraysEXT = (PFNGLMULTIDRAWARRAYSEXTPROC) SDL_GL_GetProcAddress("glMultiDrawArraysEXT"); + qglMultiDrawElementsEXT = (PFNGLMULTIDRAWELEMENTSEXTPROC) SDL_GL_GetProcAddress("glMultiDrawElementsEXT"); + + if ( r_ext_multi_draw_arrays->integer ) + glRefConfig.multiDrawArrays = qtrue; + + ri.Printf(PRINT_ALL, result[glRefConfig.multiDrawArrays], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_ARB_vertex_program + //glRefConfig.vertexProgram = qfalse; + extension = "GL_ARB_vertex_program"; + qglVertexAttrib4fARB = NULL; + qglVertexAttrib4fvARB = NULL; + qglVertexAttribPointerARB = NULL; + qglEnableVertexAttribArrayARB = NULL; + qglDisableVertexAttribArrayARB = NULL; + if( GLimp_HaveExtension( extension ) ) + { + qglVertexAttrib4fARB = (PFNGLVERTEXATTRIB4FARBPROC) SDL_GL_GetProcAddress("glVertexAttrib4fARB"); + qglVertexAttrib4fvARB = (PFNGLVERTEXATTRIB4FVARBPROC) SDL_GL_GetProcAddress("glVertexAttrib4fvARB"); + qglVertexAttribPointerARB = (PFNGLVERTEXATTRIBPOINTERARBPROC) SDL_GL_GetProcAddress("glVertexAttribPointerARB"); + qglEnableVertexAttribArrayARB = + (PFNGLENABLEVERTEXATTRIBARRAYARBPROC) SDL_GL_GetProcAddress("glEnableVertexAttribArrayARB"); + qglDisableVertexAttribArrayARB = + (PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) SDL_GL_GetProcAddress("glDisableVertexAttribArrayARB"); + + ri.Printf(PRINT_ALL, result[1], extension); + //glRefConfig.vertexProgram = qtrue; + } + else + { + ri.Error(ERR_FATAL, result[2], extension); + } + + // GL_ARB_vertex_buffer_object + //glRefConfig.vertexBufferObject = qfalse; + extension = "GL_ARB_vertex_buffer_object"; + qglBindBufferARB = NULL; + qglDeleteBuffersARB = NULL; + qglGenBuffersARB = NULL; + qglIsBufferARB = NULL; + qglBufferDataARB = NULL; + qglBufferSubDataARB = NULL; + qglGetBufferSubDataARB = NULL; + qglGetBufferParameterivARB = NULL; + qglGetBufferPointervARB = NULL; + if( GLimp_HaveExtension( extension ) ) + { + qglBindBufferARB = (PFNGLBINDBUFFERARBPROC) SDL_GL_GetProcAddress("glBindBufferARB"); + qglDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC) SDL_GL_GetProcAddress("glDeleteBuffersARB"); + qglGenBuffersARB = (PFNGLGENBUFFERSARBPROC) SDL_GL_GetProcAddress("glGenBuffersARB"); + qglIsBufferARB = (PFNGLISBUFFERARBPROC) SDL_GL_GetProcAddress("glIsBufferARB"); + qglBufferDataARB = (PFNGLBUFFERDATAARBPROC) SDL_GL_GetProcAddress("glBufferDataARB"); + qglBufferSubDataARB = (PFNGLBUFFERSUBDATAARBPROC) SDL_GL_GetProcAddress("glBufferSubDataARB"); + qglGetBufferSubDataARB = (PFNGLGETBUFFERSUBDATAARBPROC) SDL_GL_GetProcAddress("glGetBufferSubDataARB"); + qglGetBufferParameterivARB = (PFNGLGETBUFFERPARAMETERIVARBPROC) SDL_GL_GetProcAddress("glGetBufferParameterivARB"); + qglGetBufferPointervARB = (PFNGLGETBUFFERPOINTERVARBPROC) SDL_GL_GetProcAddress("glGetBufferPointervARB"); + ri.Printf(PRINT_ALL, result[1], extension); + //glRefConfig.vertexBufferObject = qtrue; + } + else + { + ri.Error(ERR_FATAL, result[2], extension); + } + + // GL_ARB_shader_objects + extension = "GL_ARB_shader_objects"; + //glRefConfig.shaderObjects = qfalse; + qglDeleteObjectARB = NULL; + qglGetHandleARB = NULL; + qglDetachObjectARB = NULL; + qglCreateShaderObjectARB = NULL; + qglShaderSourceARB = NULL; + qglCompileShaderARB = NULL; + qglCreateProgramObjectARB = NULL; + qglAttachObjectARB = NULL; + qglLinkProgramARB = NULL; + qglUseProgramObjectARB = NULL; + qglValidateProgramARB = NULL; + qglUniform1fARB = NULL; + qglUniform2fARB = NULL; + qglUniform3fARB = NULL; + qglUniform4fARB = NULL; + qglUniform1iARB = NULL; + qglUniform2iARB = NULL; + qglUniform3iARB = NULL; + qglUniform4iARB = NULL; + qglUniform1fvARB = NULL; + qglUniform2fvARB = NULL; + qglUniform3fvARB = NULL; + qglUniform4fvARB = NULL; + qglUniform2ivARB = NULL; + qglUniform3ivARB = NULL; + qglUniform4ivARB = NULL; + qglUniformMatrix2fvARB = NULL; + qglUniformMatrix3fvARB = NULL; + qglUniformMatrix4fvARB = NULL; + qglGetObjectParameterfvARB = NULL; + qglGetObjectParameterivARB = NULL; + qglGetInfoLogARB = NULL; + qglGetAttachedObjectsARB = NULL; + qglGetUniformLocationARB = NULL; + qglGetActiveUniformARB = NULL; + qglGetUniformfvARB = NULL; + qglGetUniformivARB = NULL; + qglGetShaderSourceARB = NULL; + if( GLimp_HaveExtension( extension ) ) + { + qglDeleteObjectARB = (PFNGLDELETEOBJECTARBPROC) SDL_GL_GetProcAddress("glDeleteObjectARB"); + qglGetHandleARB = (PFNGLGETHANDLEARBPROC) SDL_GL_GetProcAddress("glGetHandleARB"); + qglDetachObjectARB = (PFNGLDETACHOBJECTARBPROC) SDL_GL_GetProcAddress("glDetachObjectARB"); + qglCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC) SDL_GL_GetProcAddress("glCreateShaderObjectARB"); + qglShaderSourceARB = (PFNGLSHADERSOURCEARBPROC) SDL_GL_GetProcAddress("glShaderSourceARB"); + qglCompileShaderARB = (PFNGLCOMPILESHADERARBPROC) SDL_GL_GetProcAddress("glCompileShaderARB"); + qglCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC) SDL_GL_GetProcAddress("glCreateProgramObjectARB"); + qglAttachObjectARB = (PFNGLATTACHOBJECTARBPROC) SDL_GL_GetProcAddress("glAttachObjectARB"); + qglLinkProgramARB = (PFNGLLINKPROGRAMARBPROC) SDL_GL_GetProcAddress("glLinkProgramARB"); + qglUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC) SDL_GL_GetProcAddress("glUseProgramObjectARB"); + qglValidateProgramARB = (PFNGLVALIDATEPROGRAMARBPROC) SDL_GL_GetProcAddress("glValidateProgramARB"); + qglUniform1fARB = (PFNGLUNIFORM1FARBPROC) SDL_GL_GetProcAddress("glUniform1fARB"); + qglUniform2fARB = (PFNGLUNIFORM2FARBPROC) SDL_GL_GetProcAddress("glUniform2fARB"); + qglUniform3fARB = (PFNGLUNIFORM3FARBPROC) SDL_GL_GetProcAddress("glUniform3fARB"); + qglUniform4fARB = (PFNGLUNIFORM4FARBPROC) SDL_GL_GetProcAddress("glUniform4fARB"); + qglUniform1iARB = (PFNGLUNIFORM1IARBPROC) SDL_GL_GetProcAddress("glUniform1iARB"); + qglUniform2iARB = (PFNGLUNIFORM2IARBPROC) SDL_GL_GetProcAddress("glUniform2iARB"); + qglUniform3iARB = (PFNGLUNIFORM3IARBPROC) SDL_GL_GetProcAddress("glUniform3iARB"); + qglUniform4iARB = (PFNGLUNIFORM4IARBPROC) SDL_GL_GetProcAddress("glUniform4iARB"); + qglUniform1fvARB = (PFNGLUNIFORM1FVARBPROC) SDL_GL_GetProcAddress("glUniform1fvARB"); + qglUniform2fvARB = (PFNGLUNIFORM2FVARBPROC) SDL_GL_GetProcAddress("glUniform2fvARB"); + qglUniform3fvARB = (PFNGLUNIFORM3FVARBPROC) SDL_GL_GetProcAddress("glUniform3fvARB"); + qglUniform4fvARB = (PFNGLUNIFORM4FVARBPROC) SDL_GL_GetProcAddress("glUniform4fvARB"); + qglUniform2ivARB = (PFNGLUNIFORM2IVARBPROC) SDL_GL_GetProcAddress("glUniform2ivARB"); + qglUniform3ivARB = (PFNGLUNIFORM3IVARBPROC) SDL_GL_GetProcAddress("glUniform3ivARB"); + qglUniform4ivARB = (PFNGLUNIFORM4IVARBPROC) SDL_GL_GetProcAddress("glUniform4ivARB"); + qglUniformMatrix2fvARB = (PFNGLUNIFORMMATRIX2FVARBPROC) SDL_GL_GetProcAddress("glUniformMatrix2fvARB"); + qglUniformMatrix3fvARB = (PFNGLUNIFORMMATRIX3FVARBPROC) SDL_GL_GetProcAddress("glUniformMatrix3fvARB"); + qglUniformMatrix4fvARB = (PFNGLUNIFORMMATRIX4FVARBPROC) SDL_GL_GetProcAddress("glUniformMatrix4fvARB"); + qglGetObjectParameterfvARB = (PFNGLGETOBJECTPARAMETERFVARBPROC) SDL_GL_GetProcAddress("glGetObjectParameterfvARB"); + qglGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC) SDL_GL_GetProcAddress("glGetObjectParameterivARB"); + qglGetInfoLogARB = (PFNGLGETINFOLOGARBPROC) SDL_GL_GetProcAddress("glGetInfoLogARB"); + qglGetAttachedObjectsARB = (PFNGLGETATTACHEDOBJECTSARBPROC) SDL_GL_GetProcAddress("glGetAttachedObjectsARB"); + qglGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC) SDL_GL_GetProcAddress("glGetUniformLocationARB"); + qglGetActiveUniformARB = (PFNGLGETACTIVEUNIFORMARBPROC) SDL_GL_GetProcAddress("glGetActiveUniformARB"); + qglGetUniformfvARB = (PFNGLGETUNIFORMFVARBPROC) SDL_GL_GetProcAddress("glGetUniformfvARB"); + qglGetUniformivARB = (PFNGLGETUNIFORMIVARBPROC) SDL_GL_GetProcAddress("glGetUniformivARB"); + qglGetShaderSourceARB = (PFNGLGETSHADERSOURCEARBPROC) SDL_GL_GetProcAddress("glGetShaderSourceARB"); + ri.Printf(PRINT_ALL, result[1], extension); + //glRefConfig.shaderObjects = qtrue; + } + else + { + ri.Error(ERR_FATAL, result[2], extension); + } + + // GL_ARB_vertex_shader + //glRefConfig.vertexShader = qfalse; + extension = "GL_ARB_vertex_shader"; + qglBindAttribLocationARB = NULL; + qglGetActiveAttribARB = NULL; + qglGetAttribLocationARB = NULL; + if( GLimp_HaveExtension( extension ) ) + { + //int reservedComponents; + + //qglGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB, &glConfig.maxVertexUniforms); + //qglGetIntegerv(GL_MAX_VARYING_FLOATS_ARB, &glConfig.maxVaryingFloats); + //qglGetIntegerv(GL_MAX_VERTEX_ATTRIBS_ARB, &glConfig.maxVertexAttribs); + + //reservedComponents = 16 * 10; // approximation how many uniforms we have besides the bone matrices + +#if 0 + if(glConfig.driverType == GLDRV_MESA) + { + // HACK + // restrict to number of vertex uniforms to 512 because of: + // xreal.x86_64: nv50_program.c:4181: nv50_program_validate_data: Assertion `p->param_nr <= 512' failed + + glConfig.maxVertexUniforms = Q_bound(0, glConfig.maxVertexUniforms, 512); + } +#endif + + //glConfig.maxVertexSkinningBones = (int) Q_bound(0.0, (Q_max(glConfig.maxVertexUniforms - reservedComponents, 0) / 16), MAX_BONES); + //glConfig.vboVertexSkinningAvailable = r_vboVertexSkinning->integer && ((glConfig.maxVertexSkinningBones >= 12) ? qtrue : qfalse); + + qglBindAttribLocationARB = (PFNGLBINDATTRIBLOCATIONARBPROC) SDL_GL_GetProcAddress("glBindAttribLocationARB"); + qglGetActiveAttribARB = (PFNGLGETACTIVEATTRIBARBPROC) SDL_GL_GetProcAddress("glGetActiveAttribARB"); + qglGetAttribLocationARB = (PFNGLGETATTRIBLOCATIONARBPROC) SDL_GL_GetProcAddress("glGetAttribLocationARB"); + ri.Printf(PRINT_ALL, result[1], extension); + //glRefConfig.vertexShader = qtrue; + } + else + { + ri.Error(ERR_FATAL, result[2], extension); + } + + // GL_ARB_shading_language_100 + extension = "GL_ARB_shading_language_100"; + glRefConfig.textureFloat = qfalse; + if( GLimp_HaveExtension( extension ) ) + { + char version[256]; + + Q_strncpyz( version, (char *) qglGetString (GL_SHADING_LANGUAGE_VERSION_ARB), sizeof( version ) ); + + sscanf(version, "%d.%d", &glRefConfig.glslMajorVersion, &glRefConfig.glslMinorVersion); + + ri.Printf(PRINT_ALL, "...using GLSL version %s\n", version); + } + else + { + ri.Error(ERR_FATAL, result[2], extension); + } + + glRefConfig.memInfo = MI_NONE; + + if( GLimp_HaveExtension( "GL_NVX_gpu_memory_info" ) ) + { + glRefConfig.memInfo = MI_NVX; + } + else if( GLimp_HaveExtension( "GL_ATI_meminfo" ) ) + { + glRefConfig.memInfo = MI_ATI; + } + + extension = "GL_ARB_texture_non_power_of_two"; + glRefConfig.textureNonPowerOfTwo = qfalse; + if( GLimp_HaveExtension( extension ) ) + { + if(1) //(r_ext_texture_non_power_of_two->integer) + { + glRefConfig.textureNonPowerOfTwo = qtrue; + } + + ri.Printf(PRINT_ALL, result[glRefConfig.textureNonPowerOfTwo], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_ARB_texture_float + extension = "GL_ARB_texture_float"; + glRefConfig.textureFloat = qfalse; + if( GLimp_HaveExtension( extension ) ) + { + if( r_ext_texture_float->integer ) + { + glRefConfig.textureFloat = qtrue; + } + + ri.Printf(PRINT_ALL, result[glRefConfig.textureFloat], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_ARB_half_float_pixel + extension = "GL_ARB_half_float_pixel"; + glRefConfig.halfFloatPixel = qfalse; + if( GLimp_HaveExtension( extension ) ) + { + if( r_arb_half_float_pixel->integer ) + glRefConfig.halfFloatPixel = qtrue; + + ri.Printf(PRINT_ALL, result[glRefConfig.halfFloatPixel], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_EXT_framebuffer_object + extension = "GL_EXT_framebuffer_object"; + glRefConfig.framebufferObject = qfalse; + if( GLimp_HaveExtension( extension ) ) + { + glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE_EXT, &glRefConfig.maxRenderbufferSize); + glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, &glRefConfig.maxColorAttachments); + + qglIsRenderbufferEXT = (PFNGLISRENDERBUFFEREXTPROC) SDL_GL_GetProcAddress("glIsRenderbufferEXT"); + qglBindRenderbufferEXT = (PFNGLBINDRENDERBUFFEREXTPROC) SDL_GL_GetProcAddress("glBindRenderbufferEXT"); + qglDeleteRenderbuffersEXT = (PFNGLDELETERENDERBUFFERSEXTPROC) SDL_GL_GetProcAddress("glDeleteRenderbuffersEXT"); + qglGenRenderbuffersEXT = (PFNGLGENRENDERBUFFERSEXTPROC) SDL_GL_GetProcAddress("glGenRenderbuffersEXT"); + qglRenderbufferStorageEXT = (PFNGLRENDERBUFFERSTORAGEEXTPROC) SDL_GL_GetProcAddress("glRenderbufferStorageEXT"); + qglGetRenderbufferParameterivEXT = (PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC) SDL_GL_GetProcAddress("glGetRenderbufferParameterivEXT"); + qglIsFramebufferEXT = (PFNGLISFRAMEBUFFEREXTPROC) SDL_GL_GetProcAddress("glIsFramebufferEXT"); + qglBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC) SDL_GL_GetProcAddress("glBindFramebufferEXT"); + qglDeleteFramebuffersEXT = (PFNGLDELETEFRAMEBUFFERSEXTPROC) SDL_GL_GetProcAddress("glDeleteFramebuffersEXT"); + qglGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC) SDL_GL_GetProcAddress("glGenFramebuffersEXT"); + qglCheckFramebufferStatusEXT = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC) SDL_GL_GetProcAddress("glCheckFramebufferStatusEXT"); + qglFramebufferTexture1DEXT = (PFNGLFRAMEBUFFERTEXTURE1DEXTPROC) SDL_GL_GetProcAddress("glFramebufferTexture1DEXT"); + qglFramebufferTexture2DEXT = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC) SDL_GL_GetProcAddress("glFramebufferTexture2DEXT"); + qglFramebufferTexture3DEXT = (PFNGLFRAMEBUFFERTEXTURE3DEXTPROC) SDL_GL_GetProcAddress("glFramebufferTexture3DEXT"); + qglFramebufferRenderbufferEXT = (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC) SDL_GL_GetProcAddress("glFramebufferRenderbufferEXT"); + qglGetFramebufferAttachmentParameterivEXT = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) SDL_GL_GetProcAddress("glGetFramebufferAttachmentParameterivEXT"); + qglGenerateMipmapEXT = (PFNGLGENERATEMIPMAPEXTPROC) SDL_GL_GetProcAddress("glGenerateMipmapEXT"); + + if(r_ext_framebuffer_object->value) + glRefConfig.framebufferObject = qtrue; + + ri.Printf(PRINT_ALL, result[glRefConfig.framebufferObject], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_EXT_packed_depth_stencil + extension = "GL_EXT_packed_depth_stencil"; + glRefConfig.packedDepthStencil = qfalse; + if( GLimp_HaveExtension(extension)) + { + glRefConfig.packedDepthStencil = qtrue; + ri.Printf(PRINT_ALL, result[glRefConfig.packedDepthStencil], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_ARB_occlusion_query + extension = "GL_ARB_occlusion_query"; + glRefConfig.occlusionQuery = qfalse; + if (GLimp_HaveExtension(extension)) + { + qglGenQueriesARB = (PFNGLGENQUERIESARBPROC) SDL_GL_GetProcAddress("glGenQueriesARB"); + qglDeleteQueriesARB = (PFNGLDELETEQUERIESARBPROC) SDL_GL_GetProcAddress("glDeleteQueriesARB"); + qglIsQueryARB = (PFNGLISQUERYARBPROC) SDL_GL_GetProcAddress("glIsQueryARB"); + qglBeginQueryARB = (PFNGLBEGINQUERYARBPROC) SDL_GL_GetProcAddress("glBeginQueryARB"); + qglEndQueryARB = (PFNGLENDQUERYARBPROC) SDL_GL_GetProcAddress("glEndQueryARB"); + qglGetQueryivARB = (PFNGLGETQUERYIVARBPROC) SDL_GL_GetProcAddress("glGetQueryivARB"); + qglGetQueryObjectivARB = (PFNGLGETQUERYOBJECTIVARBPROC) SDL_GL_GetProcAddress("glGetQueryObjectivARB"); + qglGetQueryObjectuivARB = (PFNGLGETQUERYOBJECTUIVARBPROC) SDL_GL_GetProcAddress("glGetQueryObjectuivARB"); + glRefConfig.occlusionQuery = qtrue; + ri.Printf(PRINT_ALL, result[glRefConfig.occlusionQuery], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_EXT_framebuffer_blit + extension = "GL_EXT_framebuffer_blit"; + glRefConfig.framebufferBlit = qfalse; + if (GLimp_HaveExtension(extension)) + { + qglBlitFramebufferEXT = (void *)SDL_GL_GetProcAddress("glBlitFramebufferEXT"); + glRefConfig.framebufferBlit = qtrue; + ri.Printf(PRINT_ALL, result[glRefConfig.framebufferBlit], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_EXT_framebuffer_multisample + extension = "GL_EXT_framebuffer_multisample"; + glRefConfig.framebufferMultisample = qfalse; + if (GLimp_HaveExtension(extension)) + { + qglRenderbufferStorageMultisampleEXT = (void *)SDL_GL_GetProcAddress("glRenderbufferStorageMultisampleEXT"); + glRefConfig.framebufferMultisample = qtrue; + ri.Printf(PRINT_ALL, result[glRefConfig.framebufferMultisample], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_EXT_texture_sRGB + extension = "GL_EXT_texture_sRGB"; + glRefConfig.texture_srgb = qfalse; + if (GLimp_HaveExtension(extension)) + { + if (r_srgb->integer) + glRefConfig.texture_srgb = qtrue; + + ri.Printf(PRINT_ALL, result[glRefConfig.texture_srgb], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_EXT_framebuffer_sRGB + extension = "GL_EXT_framebuffer_sRGB"; + glRefConfig.framebuffer_srgb = qfalse; + if (GLimp_HaveExtension(extension)) + { + if (r_srgb->integer) + glRefConfig.framebuffer_srgb = qtrue; + + ri.Printf(PRINT_ALL, result[glRefConfig.framebuffer_srgb], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + glRefConfig.textureCompression = TCR_NONE; + + // GL_EXT_texture_compression_latc + extension = "GL_EXT_texture_compression_latc"; + if (GLimp_HaveExtension(extension)) + { + if (r_ext_compressed_textures->integer) + glRefConfig.textureCompression |= TCR_LATC; + + ri.Printf(PRINT_ALL, result[r_ext_compressed_textures->integer ? 1 : 0], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_ARB_texture_compression_bptc + extension = "GL_ARB_texture_compression_bptc"; + if (GLimp_HaveExtension(extension)) + { + if (r_ext_compressed_textures->integer >= 2) + glRefConfig.textureCompression |= TCR_BPTC; + + ri.Printf(PRINT_ALL, result[(r_ext_compressed_textures->integer >= 2) ? 1 : 0], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_ARB_draw_buffers + extension = "GL_ARB_draw_buffers"; + qglDrawBuffersARB = NULL; + if( GLimp_HaveExtension( extension ) ) + { + qglDrawBuffersARB = (void *) SDL_GL_GetProcAddress("glDrawBuffersARB"); + + ri.Printf(PRINT_ALL, result[1], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } + + // GL_ARB_depth_clamp + extension = "GL_ARB_depth_clamp"; + glRefConfig.depthClamp = qfalse; + if( GLimp_HaveExtension( extension ) ) + { + glRefConfig.depthClamp = qtrue; + ri.Printf(PRINT_ALL, result[1], extension); + } + else + { + ri.Printf(PRINT_ALL, result[2], extension); + } +} diff --git a/src/rend2/tr_extramath.c b/src/rend2/tr_extramath.c new file mode 100644 index 00000000..e989c7e9 --- /dev/null +++ b/src/rend2/tr_extramath.c @@ -0,0 +1,233 @@ +/* +=========================================================================== +Copyright (C) 2010 James Canete (use.less01@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_extramath.c - extra math needed by the renderer not in qmath.c + +#include "tr_local.h" + +// Some matrix helper functions +// FIXME: do these already exist in ioq3 and I don't know about them? + +void Matrix16Zero( matrix_t out ) +{ + out[ 0] = 0.0f; out[ 4] = 0.0f; out[ 8] = 0.0f; out[12] = 0.0f; + out[ 1] = 0.0f; out[ 5] = 0.0f; out[ 9] = 0.0f; out[13] = 0.0f; + out[ 2] = 0.0f; out[ 6] = 0.0f; out[10] = 0.0f; out[14] = 0.0f; + out[ 3] = 0.0f; out[ 7] = 0.0f; out[11] = 0.0f; out[15] = 0.0f; +} + +void Matrix16Identity( matrix_t out ) +{ + out[ 0] = 1.0f; out[ 4] = 0.0f; out[ 8] = 0.0f; out[12] = 0.0f; + out[ 1] = 0.0f; out[ 5] = 1.0f; out[ 9] = 0.0f; out[13] = 0.0f; + out[ 2] = 0.0f; out[ 6] = 0.0f; out[10] = 1.0f; out[14] = 0.0f; + out[ 3] = 0.0f; out[ 7] = 0.0f; out[11] = 0.0f; out[15] = 1.0f; +} + +void Matrix16Copy( const matrix_t in, matrix_t out ) +{ + out[ 0] = in[ 0]; out[ 4] = in[ 4]; out[ 8] = in[ 8]; out[12] = in[12]; + out[ 1] = in[ 1]; out[ 5] = in[ 5]; out[ 9] = in[ 9]; out[13] = in[13]; + out[ 2] = in[ 2]; out[ 6] = in[ 6]; out[10] = in[10]; out[14] = in[14]; + out[ 3] = in[ 3]; out[ 7] = in[ 7]; out[11] = in[11]; out[15] = in[15]; +} + +void Matrix16Multiply( const matrix_t in1, const matrix_t in2, matrix_t out ) +{ + out[ 0] = in1[ 0] * in2[ 0] + in1[ 4] * in2[ 1] + in1[ 8] * in2[ 2] + in1[12] * in2[ 3]; + out[ 1] = in1[ 1] * in2[ 0] + in1[ 5] * in2[ 1] + in1[ 9] * in2[ 2] + in1[13] * in2[ 3]; + out[ 2] = in1[ 2] * in2[ 0] + in1[ 6] * in2[ 1] + in1[10] * in2[ 2] + in1[14] * in2[ 3]; + out[ 3] = in1[ 3] * in2[ 0] + in1[ 7] * in2[ 1] + in1[11] * in2[ 2] + in1[15] * in2[ 3]; + + out[ 4] = in1[ 0] * in2[ 4] + in1[ 4] * in2[ 5] + in1[ 8] * in2[ 6] + in1[12] * in2[ 7]; + out[ 5] = in1[ 1] * in2[ 4] + in1[ 5] * in2[ 5] + in1[ 9] * in2[ 6] + in1[13] * in2[ 7]; + out[ 6] = in1[ 2] * in2[ 4] + in1[ 6] * in2[ 5] + in1[10] * in2[ 6] + in1[14] * in2[ 7]; + out[ 7] = in1[ 3] * in2[ 4] + in1[ 7] * in2[ 5] + in1[11] * in2[ 6] + in1[15] * in2[ 7]; + + out[ 8] = in1[ 0] * in2[ 8] + in1[ 4] * in2[ 9] + in1[ 8] * in2[10] + in1[12] * in2[11]; + out[ 9] = in1[ 1] * in2[ 8] + in1[ 5] * in2[ 9] + in1[ 9] * in2[10] + in1[13] * in2[11]; + out[10] = in1[ 2] * in2[ 8] + in1[ 6] * in2[ 9] + in1[10] * in2[10] + in1[14] * in2[11]; + out[11] = in1[ 3] * in2[ 8] + in1[ 7] * in2[ 9] + in1[11] * in2[10] + in1[15] * in2[11]; + + out[12] = in1[ 0] * in2[12] + in1[ 4] * in2[13] + in1[ 8] * in2[14] + in1[12] * in2[15]; + out[13] = in1[ 1] * in2[12] + in1[ 5] * in2[13] + in1[ 9] * in2[14] + in1[13] * in2[15]; + out[14] = in1[ 2] * in2[12] + in1[ 6] * in2[13] + in1[10] * in2[14] + in1[14] * in2[15]; + out[15] = in1[ 3] * in2[12] + in1[ 7] * in2[13] + in1[11] * in2[14] + in1[15] * in2[15]; +} + +void Matrix16Transform( const matrix_t in1, const vec4_t in2, vec4_t out ) +{ + out[ 0] = in1[ 0] * in2[ 0] + in1[ 4] * in2[ 1] + in1[ 8] * in2[ 2] + in1[12] * in2[ 3]; + out[ 1] = in1[ 1] * in2[ 0] + in1[ 5] * in2[ 1] + in1[ 9] * in2[ 2] + in1[13] * in2[ 3]; + out[ 2] = in1[ 2] * in2[ 0] + in1[ 6] * in2[ 1] + in1[10] * in2[ 2] + in1[14] * in2[ 3]; + out[ 3] = in1[ 3] * in2[ 0] + in1[ 7] * in2[ 1] + in1[11] * in2[ 2] + in1[15] * in2[ 3]; +} + +qboolean Matrix16Compare( const matrix_t a, const matrix_t b ) +{ + return !(a[ 0] != b[ 0] || a[ 4] != b[ 4] || a[ 8] != b[ 8] || a[12] != b[12] || + a[ 1] != b[ 1] || a[ 5] != b[ 5] || a[ 9] != b[ 9] || a[13] != b[13] || + a[ 2] != b[ 2] || a[ 6] != b[ 6] || a[10] != b[10] || a[14] != b[14] || + a[ 3] != b[ 3] || a[ 7] != b[ 7] || a[11] != b[11] || a[15] != b[15]); +} + +void Matrix16Dump( const matrix_t in ) +{ + ri.Printf(PRINT_ALL, "%3.5f %3.5f %3.5f %3.5f\n", in[ 0], in[ 4], in[ 8], in[12]); + ri.Printf(PRINT_ALL, "%3.5f %3.5f %3.5f %3.5f\n", in[ 1], in[ 5], in[ 9], in[13]); + ri.Printf(PRINT_ALL, "%3.5f %3.5f %3.5f %3.5f\n", in[ 2], in[ 6], in[10], in[14]); + ri.Printf(PRINT_ALL, "%3.5f %3.5f %3.5f %3.5f\n", in[ 3], in[ 7], in[11], in[15]); +} + +void Matrix16Translation( vec3_t vec, matrix_t out ) +{ + out[ 0] = 1.0f; out[ 4] = 0.0f; out[ 8] = 0.0f; out[12] = vec[0]; + out[ 1] = 0.0f; out[ 5] = 1.0f; out[ 9] = 0.0f; out[13] = vec[1]; + out[ 2] = 0.0f; out[ 6] = 0.0f; out[10] = 1.0f; out[14] = vec[2]; + out[ 3] = 0.0f; out[ 7] = 0.0f; out[11] = 0.0f; out[15] = 1.0f; +} + +void Matrix16Ortho( float left, float right, float bottom, float top, float znear, float zfar, matrix_t out ) +{ + out[ 0] = 2.0f / (right - left); out[ 4] = 0.0f; out[ 8] = 0.0f; out[12] = -(right + left) / (right - left); + out[ 1] = 0.0f; out[ 5] = 2.0f / (top - bottom); out[ 9] = 0.0f; out[13] = -(top + bottom) / (top - bottom); + out[ 2] = 0.0f; out[ 6] = 0.0f; out[10] = 2.0f / (zfar - znear); out[14] = -(zfar + znear) / (zfar - znear); + out[ 3] = 0.0f; out[ 7] = 0.0f; out[11] = 0.0f; out[15] = 1.0f; +} + +void Matrix16View(vec3_t axes[3], vec3_t origin, matrix_t out) +{ + out[0] = axes[0][0]; + out[1] = axes[1][0]; + out[2] = axes[2][0]; + out[3] = 0; + + out[4] = axes[0][1]; + out[5] = axes[1][1]; + out[6] = axes[2][1]; + out[7] = 0; + + out[8] = axes[0][2]; + out[9] = axes[1][2]; + out[10] = axes[2][2]; + out[11] = 0; + + out[12] = -DotProduct(origin, axes[0]); + out[13] = -DotProduct(origin, axes[1]); + out[14] = -DotProduct(origin, axes[2]); + out[15] = 1; +} + +void Matrix16SimpleInverse( const matrix_t in, matrix_t out) +{ + vec3_t v; + float invSqrLen; + + VectorCopy(in + 0, v); + invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v); + out[ 0] = v[0]; out[ 4] = v[1]; out[ 8] = v[2]; out[12] = -DotProduct(v, &in[12]); + + VectorCopy(in + 4, v); + invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v); + out[ 1] = v[0]; out[ 5] = v[1]; out[ 9] = v[2]; out[13] = -DotProduct(v, &in[12]); + + VectorCopy(in + 8, v); + invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v); + out[ 2] = v[0]; out[ 6] = v[1]; out[10] = v[2]; out[14] = -DotProduct(v, &in[12]); + + out[ 3] = 0.0f; out[ 7] = 0.0f; out[11] = 0.0f; out[15] = 1.0f; +} + +qboolean SpheresIntersect(vec3_t origin1, float radius1, vec3_t origin2, float radius2) +{ + float radiusSum = radius1 + radius2; + vec3_t diff; + + VectorSubtract(origin1, origin2, diff); + + if (DotProduct(diff, diff) <= radiusSum * radiusSum) + { + return qtrue; + } + + return qfalse; +} + +void BoundingSphereOfSpheres(vec3_t origin1, float radius1, vec3_t origin2, float radius2, vec3_t origin3, float *radius3) +{ + vec3_t diff; + + VectorScale(origin1, 0.5f, origin3); + VectorMA(origin3, 0.5f, origin2, origin3); + + VectorSubtract(origin1, origin2, diff); + *radius3 = VectorLength(diff) * 0.5f + MAX(radius1, radius2); +} + +int NextPowerOfTwo(int in) +{ + int out; + + for (out = 1; out < in; out <<= 1) + ; + + return out; +} + +unsigned short FloatToHalf(float in) +{ + unsigned short out; + + union + { + float f; + unsigned int i; + } f32; + + int sign, inExponent, inFraction; + int outExponent, outFraction; + + f32.f = in; + + sign = (f32.i & 0x80000000) >> 31; + inExponent = (f32.i & 0x7F800000) >> 23; + inFraction = f32.i & 0x007FFFFF; + + outExponent = CLAMP(inExponent - 127, -15, 16) + 15; + + outFraction = 0; + if (outExponent == 0x1F) + { + if (inExponent == 0xFF && inFraction != 0) + outFraction = 0x3FF; + } + else if (outExponent == 0x00) + { + if (inExponent == 0x00 && inFraction != 0) + outFraction = 0x3FF; + } + else + outFraction = inFraction >> 13; + + out = (sign << 15) | (outExponent << 10) | outFraction; + + return out; +} diff --git a/src/rend2/tr_extramath.h b/src/rend2/tr_extramath.h new file mode 100644 index 00000000..b4ca6f3c --- /dev/null +++ b/src/rend2/tr_extramath.h @@ -0,0 +1,101 @@ +/* +=========================================================================== +Copyright (C) 2010 James Canete (use.less01@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_extramath.h + +#ifndef __TR_EXTRAMATH_H__ +#define __TR_EXTRAMATH_H__ + +typedef vec_t matrix_t[16]; +typedef int vec2i_t[2]; +typedef int vec3i_t[3]; +typedef int vec4i_t[4]; + +void Matrix16Zero( matrix_t out ); +void Matrix16Identity( matrix_t out ); +void Matrix16Copy( const matrix_t in, matrix_t out ); +void Matrix16Multiply( const matrix_t in1, const matrix_t in2, matrix_t out ); +void Matrix16Transform( const matrix_t in1, const vec4_t in2, vec4_t out ); +qboolean Matrix16Compare(const matrix_t a, const matrix_t b); +void Matrix16Dump( const matrix_t in ); +void Matrix16Translation( vec3_t vec, matrix_t out ); +void Matrix16Ortho( float left, float right, float bottom, float top, float znear, float zfar, matrix_t out ); +void Matrix16View(vec3_t axes[3], vec3_t origin, matrix_t out); +void Matrix16SimpleInverse( const matrix_t in, matrix_t out); + +#define VectorCopy2(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1]) + +#define VectorCopy4(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) +#define VectorSet4(v,x,y,z,w) ((v)[0]=(x),(v)[1]=(y),(v)[2]=(z),(v)[3]=(w)) +#define DotProduct4(a,b) ((a)[0]*(b)[0] + (a)[1]*(b)[1] + (a)[2]*(b)[2] + (a)[3]*(b)[3]) +#define VectorScale4(a,b,c) ((c)[0]=(a)[0]*(b),(c)[1]=(a)[1]*(b),(c)[2]=(a)[2]*(b),(c)[3]=(a)[3]*(b)) + +#define VectorCopy5(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3],(b)[4]=(a)[4]) + +#define OffsetByteToFloat(a) ((float)(a) * 1.0f/127.5f - 1.0f) +#define FloatToOffsetByte(a) (byte)(((a) + 1.0f) * 127.5f) +#define ByteToFloat(a) ((float)(a) * 1.0f/255.0f) +#define FloatToByte(a) (byte)((a) * 255.0f) + +#define RGBtosRGB(a) (((a) < 0.0031308f) ? (12.92f * (a)) : (1.055f * pow((a), 0.41666f) - 0.055f)) +#define sRGBtoRGB(a) (((a) <= 0.04045f) ? ((a) / 12.92f) : (pow((((a) + 0.055f) / 1.055f), 2.4)) ) + +static ID_INLINE int VectorCompare4(const vec4_t v1, const vec4_t v2) +{ + if(v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2] || v1[3] != v2[3]) + { + return 0; + } + return 1; +} + +static ID_INLINE int VectorCompare5(const vec5_t v1, const vec5_t v2) +{ + if(v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2] || v1[3] != v2[3] || v1[4] != v2[4]) + { + return 0; + } + return 1; +} + +qboolean SpheresIntersect(vec3_t origin1, float radius1, vec3_t origin2, float radius2); +void BoundingSphereOfSpheres(vec3_t origin1, float radius1, vec3_t origin2, float radius2, vec3_t origin3, float *radius3); + +#ifndef SGN +#define SGN(x) (((x) >= 0) ? !!(x) : -1) +#endif + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef CLAMP +#define CLAMP(a,b,c) MIN(MAX((a),(b)),(c)) +#endif + +int NextPowerOfTwo(int in); +unsigned short FloatToHalf(float in); + +#endif diff --git a/src/rend2/tr_extratypes.h b/src/rend2/tr_extratypes.h new file mode 100644 index 00000000..3d185851 --- /dev/null +++ b/src/rend2/tr_extratypes.h @@ -0,0 +1,43 @@ +/* +=========================================================================== +Copyright (C) 2009-2011 Andrei Drexler, Richard Allen, James Canete + +This file is part of Reaction source code. + +Reaction source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Reaction source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Reaction source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef __TR_EXTRATYPES_H__ +#define __TR_EXTRATYPES_H__ + +// tr_extratypes.h, for mods that want to extend tr_types.h without losing compatibility with original VMs + +// extra renderfx flags start at 0x0400 +#define RF_SUNFLARE 0x0400 + +// extra refdef flags start at 0x0008 +#define RDF_NOFOG 0x0008 // don't apply fog +#define RDF_EXTRA 0x0010 // Makro - refdefex_t to follow after refdef_t +#define RDF_SUNLIGHT 0x0020 // SmileTheory - render sunlight and shadows + +typedef struct { + float blurFactor; + float sunDir[3]; + float sunCol[3]; + float sunAmbCol[3]; +} refdefex_t; + +#endif \ No newline at end of file diff --git a/src/rend2/tr_fbo.c b/src/rend2/tr_fbo.c new file mode 100644 index 00000000..0e4a2380 --- /dev/null +++ b/src/rend2/tr_fbo.c @@ -0,0 +1,843 @@ +/* +=========================================================================== +Copyright (C) 2006 Kirk Barnes +Copyright (C) 2006-2008 Robert Beckebans + +This file is part of XreaL source code. + +XreaL source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +XreaL source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with XreaL source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_fbo.c +#include "tr_local.h" + +/* +============= +R_CheckFBO +============= +*/ +qboolean R_CheckFBO(const FBO_t * fbo) +{ + int code; + int id; + + qglGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &id); + qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->frameBuffer); + + code = qglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); + + if(code == GL_FRAMEBUFFER_COMPLETE_EXT) + { + qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id); + return qtrue; + } + + // an error occured + switch (code) + { + case GL_FRAMEBUFFER_COMPLETE_EXT: + break; + + case GL_FRAMEBUFFER_UNSUPPORTED_EXT: + ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Unsupported framebuffer format\n", fbo->name); + break; + + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: + ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete attachment\n", fbo->name); + break; + + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: + ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, missing attachment\n", fbo->name); + break; + + //case GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT: + // ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, duplicate attachment\n", fbo->name); + // break; + + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: + ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, attached images must have same dimensions\n", + fbo->name); + break; + + case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: + ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, attached images must have same format\n", + fbo->name); + break; + + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: + ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, missing draw buffer\n", fbo->name); + break; + + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: + ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, missing read buffer\n", fbo->name); + break; + + default: + ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) unknown error 0x%X\n", fbo->name, code); + //ri.Error(ERR_FATAL, "R_CheckFBO: (%s) unknown error 0x%X", fbo->name, code); + //assert(0); + break; + } + + qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, id); + + return qfalse; +} + +/* +============ +FBO_Create +============ +*/ +FBO_t *FBO_Create(const char *name, int width, int height) +{ + FBO_t *fbo; + + if(strlen(name) >= MAX_QPATH) + { + ri.Error(ERR_DROP, "FBO_Create: \"%s\" is too long\n", name); + } + + if(width <= 0 || width > glRefConfig.maxRenderbufferSize) + { + ri.Error(ERR_DROP, "FBO_Create: bad width %i", width); + } + + if(height <= 0 || height > glRefConfig.maxRenderbufferSize) + { + ri.Error(ERR_DROP, "FBO_Create: bad height %i", height); + } + + if(tr.numFBOs == MAX_FBOS) + { + ri.Error(ERR_DROP, "FBO_Create: MAX_FBOS hit"); + } + + fbo = tr.fbos[tr.numFBOs] = ri.Hunk_Alloc(sizeof(*fbo), h_low); + Q_strncpyz(fbo->name, name, sizeof(fbo->name)); + fbo->index = tr.numFBOs++; + fbo->width = width; + fbo->height = height; + + qglGenFramebuffersEXT(1, &fbo->frameBuffer); + + return fbo; +} + +void FBO_CreateBuffer(FBO_t *fbo, int format, int index, int multisample) +{ + uint32_t *pRenderBuffer; + GLenum attachment; + qboolean absent; + + switch(format) + { + case GL_RGB: + case GL_RGBA: + case GL_RGB8: + case GL_RGBA8: + case GL_RGB16F_ARB: + case GL_RGBA16F_ARB: + case GL_RGB32F_ARB: + case GL_RGBA32F_ARB: + fbo->colorFormat = format; + pRenderBuffer = &fbo->colorBuffers[index]; + attachment = GL_COLOR_ATTACHMENT0_EXT + index; + break; + + case GL_DEPTH_COMPONENT: + case GL_DEPTH_COMPONENT16_ARB: + case GL_DEPTH_COMPONENT24_ARB: + case GL_DEPTH_COMPONENT32_ARB: + fbo->depthFormat = format; + pRenderBuffer = &fbo->depthBuffer; + attachment = GL_DEPTH_ATTACHMENT_EXT; + break; + + case GL_STENCIL_INDEX: + case GL_STENCIL_INDEX1_EXT: + case GL_STENCIL_INDEX4_EXT: + case GL_STENCIL_INDEX8_EXT: + case GL_STENCIL_INDEX16_EXT: + fbo->stencilFormat = format; + pRenderBuffer = &fbo->stencilBuffer; + attachment = GL_STENCIL_ATTACHMENT_EXT; + break; + + case GL_DEPTH_STENCIL_EXT: + case GL_DEPTH24_STENCIL8_EXT: + fbo->packedDepthStencilFormat = format; + pRenderBuffer = &fbo->packedDepthStencilBuffer; + attachment = 0; // special for stencil and depth + break; + + default: + ri.Printf(PRINT_WARNING, "FBO_CreateBuffer: invalid format %d\n", format); + return; + } + + absent = *pRenderBuffer == 0; + if (absent) + qglGenRenderbuffersEXT(1, pRenderBuffer); + + qglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, *pRenderBuffer); + if (multisample && glRefConfig.framebufferMultisample) + { + qglRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, multisample, format, fbo->width, fbo->height); + } + else + { + qglRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, format, fbo->width, fbo->height); + } + + if(absent) + { + if (attachment == 0) + { + qglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, *pRenderBuffer); + qglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, *pRenderBuffer); + } + else + qglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, attachment, GL_RENDERBUFFER_EXT, *pRenderBuffer); + } +} + + +/* +================= +R_AttachFBOTexture1D +================= +*/ +void R_AttachFBOTexture1D(int texId, int index) +{ + if(index < 0 || index >= glRefConfig.maxColorAttachments) + { + ri.Printf(PRINT_WARNING, "R_AttachFBOTexture1D: invalid attachment index %i\n", index); + return; + } + + qglFramebufferTexture1DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + index, GL_TEXTURE_1D, texId, 0); +} + +/* +================= +R_AttachFBOTexture2D +================= +*/ +void R_AttachFBOTexture2D(int target, int texId, int index) +{ + if(target != GL_TEXTURE_2D && (target < GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB || target > GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB)) + { + ri.Printf(PRINT_WARNING, "R_AttachFBOTexture2D: invalid target %i\n", target); + return; + } + + if(index < 0 || index >= glRefConfig.maxColorAttachments) + { + ri.Printf(PRINT_WARNING, "R_AttachFBOTexture2D: invalid attachment index %i\n", index); + return; + } + + qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + index, target, texId, 0); +} + +/* +================= +R_AttachFBOTexture3D +================= +*/ +void R_AttachFBOTexture3D(int texId, int index, int zOffset) +{ + if(index < 0 || index >= glRefConfig.maxColorAttachments) + { + ri.Printf(PRINT_WARNING, "R_AttachFBOTexture3D: invalid attachment index %i\n", index); + return; + } + + qglFramebufferTexture3DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + index, GL_TEXTURE_3D_EXT, texId, 0, zOffset); +} + +/* +================= +R_AttachFBOTextureDepth +================= +*/ +void R_AttachFBOTextureDepth(int texId) +{ + qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, texId, 0); +} + +/* +================= +R_AttachFBOTexturePackedDepthStencil +================= +*/ +void R_AttachFBOTexturePackedDepthStencil(int texId) +{ + qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, texId, 0); + qglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_2D, texId, 0); +} + +void FBO_AttachTextureImage(image_t *img, int index) +{ + if (!glState.currentFBO) + { + ri.Printf(PRINT_WARNING, "FBO: attempted to attach a texture image with no FBO bound!\n"); + return; + } + + R_AttachFBOTexture2D(GL_TEXTURE_2D, img->texnum, index); + glState.currentFBO->colorImage[index] = img; +} + +/* +============ +FBO_Bind +============ +*/ +void FBO_Bind(FBO_t * fbo) +{ + if (fbo && glState.currentFBO == fbo) + return; + + if (r_logFile->integer) + { + // don't just call LogComment, or we will get a call to va() every frame! + if (fbo) + GLimp_LogComment(va("--- FBO_Bind( %s ) ---\n", fbo->name)); + else + GLimp_LogComment("--- FBO_Bind ( NULL ) ---\n"); + } + + if (!fbo) + { + qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + //qglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); + glState.currentFBO = NULL; + + return; + } + + qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->frameBuffer); + + /* + if(fbo->colorBuffers[0]) + { + qglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo->colorBuffers[0]); + } + */ + + /* + if(fbo->depthBuffer) + { + qglBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo->depthBuffer); + qglFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->depthBuffer); + } + */ + + glState.currentFBO = fbo; +} + +/* +============ +FBO_Init +============ +*/ +void FBO_Init(void) +{ + int i; + // int width, height, hdrFormat, multisample; + int hdrFormat, multisample; + + ri.Printf(PRINT_ALL, "------- FBO_Init -------\n"); + + if(!glRefConfig.framebufferObject) + return; + + tr.numFBOs = 0; + + GL_CheckErrors(); + + // make sure the render thread is stopped + R_SyncRenderThread(); + +/* if(glRefConfig.textureNonPowerOfTwo) + { + width = glConfig.vidWidth; + height = glConfig.vidHeight; + } + else + { + width = NextPowerOfTwo(glConfig.vidWidth); + height = NextPowerOfTwo(glConfig.vidHeight); + } */ + + hdrFormat = GL_RGBA8; + if (r_hdr->integer && glRefConfig.framebufferObject && glRefConfig.textureFloat) + { + hdrFormat = GL_RGB16F_ARB; + } + + qglGetIntegerv(GL_MAX_SAMPLES_EXT, &multisample); + + if (r_ext_framebuffer_multisample->integer < multisample) + { + multisample = r_ext_framebuffer_multisample->integer; + } + + if (multisample < 2 || !glRefConfig.framebufferBlit) + multisample = 0; + + if (multisample != r_ext_framebuffer_multisample->integer) + { + ri.Cvar_SetValue("r_ext_framebuffer_multisample", (float)multisample); + } + + if (multisample && glRefConfig.framebufferMultisample) + { + tr.renderFbo = FBO_Create("_render", tr.renderDepthImage->width, tr.renderDepthImage->height); + FBO_Bind(tr.renderFbo); + + FBO_CreateBuffer(tr.renderFbo, hdrFormat, 0, multisample); + FBO_CreateBuffer(tr.renderFbo, GL_DEPTH_COMPONENT24_ARB, 0, multisample); + + R_CheckFBO(tr.renderFbo); + + + tr.msaaResolveFbo = FBO_Create("_msaaResolve", tr.renderDepthImage->width, tr.renderDepthImage->height); + FBO_Bind(tr.msaaResolveFbo); + + //FBO_CreateBuffer(tr.msaaResolveFbo, hdrFormat, 0, 0); + FBO_AttachTextureImage(tr.renderImage, 0); + + //FBO_CreateBuffer(tr.msaaResolveFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0); + R_AttachFBOTextureDepth(tr.renderDepthImage->texnum); + + R_CheckFBO(tr.msaaResolveFbo); + } + else + { + tr.renderFbo = FBO_Create("_render", tr.renderDepthImage->width, tr.renderDepthImage->height); + FBO_Bind(tr.renderFbo); + + //FBO_CreateBuffer(tr.renderFbo, hdrFormat, 0, 0); + FBO_AttachTextureImage(tr.renderImage, 0); + + //FBO_CreateBuffer(tr.renderFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0); + R_AttachFBOTextureDepth(tr.renderDepthImage->texnum); + + R_CheckFBO(tr.renderFbo); + } + + // clear render buffer + // this fixes the corrupt screen bug with r_hdr 1 on older hardware + FBO_Bind(tr.renderFbo); + qglClearColor( 1, 0, 0.5, 1 ); + qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + FBO_Bind(NULL); + +#ifdef REACTION + { + tr.godRaysFbo = FBO_Create("_godRays", tr.renderDepthImage->width, tr.renderDepthImage->height); + FBO_Bind(tr.godRaysFbo); + + //FBO_CreateBuffer(tr.godRaysFbo, GL_RGBA8, 0, multisample); + FBO_AttachTextureImage(tr.godRaysImage, 0); + + //FBO_CreateBuffer(tr.godRaysFbo, GL_DEPTH_COMPONENT24_ARB, 0, multisample); + R_AttachFBOTextureDepth(tr.renderDepthImage->texnum); + + R_CheckFBO(tr.godRaysFbo); + } +#endif + + // FIXME: Don't use separate color/depth buffers for a shadow buffer + for( i = 0; i < MAX_DRAWN_PSHADOWS; i++) + { + tr.pshadowFbos[i] = FBO_Create(va("_shadowmap%d", i), tr.pshadowMaps[i]->width, tr.pshadowMaps[i]->height); + FBO_Bind(tr.pshadowFbos[i]); + + //FBO_CreateBuffer(tr.pshadowFbos[i], GL_RGBA8, 0, 0); + FBO_AttachTextureImage(tr.pshadowMaps[i], 0); + + FBO_CreateBuffer(tr.pshadowFbos[i], GL_DEPTH_COMPONENT24_ARB, 0, 0); + //R_AttachFBOTextureDepth(tr.textureDepthImage->texnum); + + R_CheckFBO(tr.pshadowFbos[i]); + } + + for ( i = 0; i < 3; i++) + { + tr.sunShadowFbo[i] = FBO_Create("_sunshadowmap", tr.sunShadowDepthImage[i]->width, tr.sunShadowDepthImage[i]->height); + FBO_Bind(tr.sunShadowFbo[i]); + + //FBO_CreateBuffer(tr.sunShadowFbo[i], GL_RGBA8, 0, 0); + //FBO_AttachTextureImage(tr.sunShadowImage, 0); + qglDrawBuffer(GL_NONE); + qglReadBuffer(GL_NONE); + + //FBO_CreateBuffer(tr.sunShadowFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0); + R_AttachFBOTextureDepth(tr.sunShadowDepthImage[i]->texnum); + + R_CheckFBO(tr.sunShadowFbo[i]); + } + + for (i = 0; i < 2; i++) + { + tr.textureScratchFbo[i] = FBO_Create(va("_texturescratch%d", i), tr.textureScratchImage[i]->width, tr.textureScratchImage[i]->height); + FBO_Bind(tr.textureScratchFbo[i]); + + //FBO_CreateBuffer(tr.textureScratchFbo[i], GL_RGBA8, 0, 0); + FBO_AttachTextureImage(tr.textureScratchImage[i], 0); + + R_CheckFBO(tr.textureScratchFbo[i]); + } + + { + tr.calcLevelsFbo = FBO_Create("_calclevels", tr.calcLevelsImage->width, tr.calcLevelsImage->height); + FBO_Bind(tr.calcLevelsFbo); + + //FBO_CreateBuffer(tr.calcLevelsFbo, hdrFormat, 0, 0); + FBO_AttachTextureImage(tr.calcLevelsImage, 0); + + R_CheckFBO(tr.calcLevelsFbo); + } + + { + tr.targetLevelsFbo = FBO_Create("_targetlevels", tr.targetLevelsImage->width, tr.targetLevelsImage->height); + FBO_Bind(tr.targetLevelsFbo); + + //FBO_CreateBuffer(tr.targetLevelsFbo, hdrFormat, 0, 0); + FBO_AttachTextureImage(tr.targetLevelsImage, 0); + + R_CheckFBO(tr.targetLevelsFbo); + } + + { + //tr.screenScratchFbo = FBO_Create("_screenscratch", width, height); + tr.screenScratchFbo = FBO_Create("_screenscratch", tr.screenScratchImage->width, tr.screenScratchImage->height); + FBO_Bind(tr.screenScratchFbo); + + //FBO_CreateBuffer(tr.screenScratchFbo, format, 0, 0); + FBO_AttachTextureImage(tr.screenScratchImage, 0); + + // FIXME: hack: share zbuffer between render fbo and pre-screen fbo + //FBO_CreateBuffer(tr.screenScratchFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0); + R_AttachFBOTextureDepth(tr.renderDepthImage->texnum); + + R_CheckFBO(tr.screenScratchFbo); + } + + for (i = 0; i < 2; i++) + { + tr.quarterFbo[i] = FBO_Create(va("_quarter%d", i), tr.quarterImage[i]->width, tr.quarterImage[i]->height); + FBO_Bind(tr.quarterFbo[i]); + + //FBO_CreateBuffer(tr.quarterFbo[i], hdrFormat, 0, 0); + FBO_AttachTextureImage(tr.quarterImage[i], 0); + + R_CheckFBO(tr.quarterFbo[i]); + } + + { + tr.screenShadowFbo = FBO_Create("_screenshadow", tr.screenShadowImage->width, tr.screenShadowImage->height); + FBO_Bind(tr.screenShadowFbo); + + FBO_AttachTextureImage(tr.screenShadowImage, 0); + + R_CheckFBO(tr.screenShadowFbo); + } + + if (r_ssao->integer) + { + tr.hdrDepthFbo = FBO_Create("_hdrDepth", tr.hdrDepthImage->width, tr.hdrDepthImage->height); + FBO_Bind(tr.hdrDepthFbo); + + FBO_AttachTextureImage(tr.hdrDepthImage, 0); + + R_CheckFBO(tr.hdrDepthFbo); + + tr.screenSsaoFbo = FBO_Create("_screenssao", tr.screenSsaoImage->width, tr.screenSsaoImage->height); + FBO_Bind(tr.screenSsaoFbo); + + FBO_AttachTextureImage(tr.screenSsaoImage, 0); + + R_CheckFBO(tr.screenSsaoFbo); + } + + GL_CheckErrors(); + + FBO_Bind(NULL); +} + +/* +============ +FBO_Shutdown +============ +*/ +void FBO_Shutdown(void) +{ + int i, j; + FBO_t *fbo; + + ri.Printf(PRINT_ALL, "------- FBO_Shutdown -------\n"); + + if(!glRefConfig.framebufferObject) + return; + + FBO_Bind(NULL); + + for(i = 0; i < tr.numFBOs; i++) + { + fbo = tr.fbos[i]; + + for(j = 0; j < glRefConfig.maxColorAttachments; j++) + { + if(fbo->colorBuffers[j]) + qglDeleteRenderbuffersEXT(1, &fbo->colorBuffers[j]); + } + + if(fbo->depthBuffer) + qglDeleteRenderbuffersEXT(1, &fbo->depthBuffer); + + if(fbo->stencilBuffer) + qglDeleteRenderbuffersEXT(1, &fbo->stencilBuffer); + + if(fbo->frameBuffer) + qglDeleteFramebuffersEXT(1, &fbo->frameBuffer); + } +} + +/* +============ +R_FBOList_f +============ +*/ +void R_FBOList_f(void) +{ + int i; + FBO_t *fbo; + + if(!glRefConfig.framebufferObject) + { + ri.Printf(PRINT_ALL, "GL_EXT_framebuffer_object is not available.\n"); + return; + } + + ri.Printf(PRINT_ALL, " size name\n"); + ri.Printf(PRINT_ALL, "----------------------------------------------------------\n"); + + for(i = 0; i < tr.numFBOs; i++) + { + fbo = tr.fbos[i]; + + ri.Printf(PRINT_ALL, " %4i: %4i %4i %s\n", i, fbo->width, fbo->height, fbo->name); + } + + ri.Printf(PRINT_ALL, " %i FBOs\n", tr.numFBOs); +} + +// FIXME +extern void RB_SetGL2D (void); + +void FBO_BlitFromTexture(struct image_s *src, vec4i_t inSrcBox, vec2_t inSrcTexScale, FBO_t *dst, vec4i_t inDstBox, struct shaderProgram_s *shaderProgram, vec4_t inColor, int blend) +{ + vec4i_t dstBox, srcBox; + vec2_t srcTexScale; + vec4_t color; + vec4_t quadVerts[4]; + vec2_t texCoords[4]; + vec2_t invTexRes; + FBO_t *oldFbo = glState.currentFBO; + + if (!src) + return; + + if (inSrcBox) + { + VectorSet4(srcBox, inSrcBox[0], inSrcBox[1], inSrcBox[0] + inSrcBox[2], inSrcBox[1] + inSrcBox[3]); + } + else + { + VectorSet4(srcBox, 0, 0, src->width, src->height); + } + + // framebuffers are 0 bottom, Y up. + if (inDstBox) + { + if (dst) + { + dstBox[0] = inDstBox[0]; + dstBox[1] = dst->height - inDstBox[1] - inDstBox[3]; + dstBox[2] = inDstBox[0] + inDstBox[2]; + dstBox[3] = dst->height - inDstBox[1]; + } + else + { + dstBox[0] = inDstBox[0]; + dstBox[1] = glConfig.vidHeight - inDstBox[1] - inDstBox[3]; + dstBox[2] = inDstBox[0] + inDstBox[2]; + dstBox[3] = glConfig.vidHeight - inDstBox[1]; + } + } + else if (dst) + { + VectorSet4(dstBox, 0, dst->height, dst->width, 0); + } + else + { + VectorSet4(dstBox, 0, glConfig.vidHeight, glConfig.vidWidth, 0); + } + + if (inSrcTexScale) + { + VectorCopy2(inSrcTexScale, srcTexScale); + } + else + { + srcTexScale[0] = srcTexScale[1] = 1.0f; + } + + if (inColor) + { + VectorCopy4(inColor, color); + } + else + { + color[0] = color[1] = color[2] = color[3] = 1.0f; + } + + if (!shaderProgram) + { + shaderProgram = &tr.textureColorShader; + } + + FBO_Bind(dst); + + RB_SetGL2D(); + + GL_SelectTexture(TB_COLORMAP); + + GL_Bind(src); + + VectorSet4(quadVerts[0], dstBox[0], dstBox[1], 0, 1); + VectorSet4(quadVerts[1], dstBox[2], dstBox[1], 0, 1); + VectorSet4(quadVerts[2], dstBox[2], dstBox[3], 0, 1); + VectorSet4(quadVerts[3], dstBox[0], dstBox[3], 0, 1); + + texCoords[0][0] = srcBox[0] / (float)src->width; texCoords[0][1] = 1.0f - srcBox[1] / (float)src->height; + texCoords[1][0] = srcBox[2] / (float)src->width; texCoords[1][1] = 1.0f - srcBox[1] / (float)src->height; + texCoords[2][0] = srcBox[2] / (float)src->width; texCoords[2][1] = 1.0f - srcBox[3] / (float)src->height; + texCoords[3][0] = srcBox[0] / (float)src->width; texCoords[3][1] = 1.0f - srcBox[3] / (float)src->height; + + invTexRes[0] = 1.0f / src->width * srcTexScale[0]; + invTexRes[1] = 1.0f / src->height * srcTexScale[1]; + + GL_State( blend ); + + GLSL_BindProgram(shaderProgram); + + GLSL_SetUniformMatrix16(shaderProgram, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + GLSL_SetUniformVec4(shaderProgram, TEXTURECOLOR_UNIFORM_COLOR, color); + GLSL_SetUniformVec2(shaderProgram, TEXTURECOLOR_UNIFORM_INVTEXRES, invTexRes); + GLSL_SetUniformVec2(shaderProgram, TEXTURECOLOR_UNIFORM_AUTOEXPOSUREMINMAX, tr.refdef.autoExposureMinMax); + GLSL_SetUniformVec3(shaderProgram, TEXTURECOLOR_UNIFORM_TONEMINAVGMAXLINEAR, tr.refdef.toneMinAvgMaxLinear); + + RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes); + + FBO_Bind(oldFbo); +} + +void FBO_Blit(FBO_t *src, vec4i_t inSrcBox, vec2_t srcTexScale, FBO_t *dst, vec4i_t dstBox, struct shaderProgram_s *shaderProgram, vec4_t color, int blend) +{ + vec4i_t srcBox; + + if (!src) + return; + + // framebuffers are 0 bottom, Y up. + if (inSrcBox) + { + srcBox[0] = inSrcBox[0]; + srcBox[1] = src->height - inSrcBox[1] - inSrcBox[3]; + srcBox[2] = inSrcBox[2]; + srcBox[3] = inSrcBox[3]; + } + else + { + VectorSet4(srcBox, 0, src->height, src->width, -src->height); + } + + FBO_BlitFromTexture(src->colorImage[0], srcBox, srcTexScale, dst, dstBox, shaderProgram, color, blend | GLS_DEPTHTEST_DISABLE); +} + +void FBO_FastBlit(FBO_t *src, vec4i_t srcBox, FBO_t *dst, vec4i_t dstBox, int buffers, int filter) +{ + vec4i_t srcBoxFinal, dstBoxFinal; + GLuint srcFb, dstFb; + + if (!glRefConfig.framebufferBlit) + { + FBO_Blit(src, srcBox, NULL, dst, dstBox, NULL, NULL, 0); + return; + } + + // get to a neutral state first + FBO_Bind(NULL); + + srcFb = src ? src->frameBuffer : 0; + dstFb = dst ? dst->frameBuffer : 0; + + if (!srcBox) + { + if (src) + { + VectorSet4(srcBoxFinal, 0, 0, src->width, src->height); + } + else + { + VectorSet4(srcBoxFinal, 0, 0, glConfig.vidWidth, glConfig.vidHeight); + } + } + else + { + VectorSet4(srcBoxFinal, srcBox[0], srcBox[1], srcBox[0] + srcBox[2], srcBox[1] + srcBox[3]); + } + + if (!dstBox) + { + if (dst) + { + VectorSet4(dstBoxFinal, 0, 0, dst->width, dst->height); + } + else + { + VectorSet4(dstBoxFinal, 0, 0, glConfig.vidWidth, glConfig.vidHeight); + } + } + else + { + VectorSet4(dstBoxFinal, dstBox[0], dstBox[1], dstBox[0] + dstBox[2], dstBox[1] + dstBox[3]); + } + + qglBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, srcFb); + qglBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, dstFb); + qglBlitFramebufferEXT(srcBoxFinal[0], srcBoxFinal[1], srcBoxFinal[2], srcBoxFinal[3], + dstBoxFinal[0], dstBoxFinal[1], dstBoxFinal[2], dstBoxFinal[3], + buffers, filter); + + qglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + glState.currentFBO = NULL; +} \ No newline at end of file diff --git a/src/rend2/tr_fbo.h b/src/rend2/tr_fbo.h new file mode 100644 index 00000000..f0366251 --- /dev/null +++ b/src/rend2/tr_fbo.h @@ -0,0 +1,64 @@ +/* +=========================================================================== +Copyright (C) 2010 James Canete (use.less01@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_fbo.h + +#ifndef __TR_FBO_H__ +#define __TR_FBO_H__ + +struct image_s; +struct shaderProgram_s; + +typedef struct FBO_s +{ + char name[MAX_QPATH]; + + int index; + + uint32_t frameBuffer; + + uint32_t colorBuffers[16]; + int colorFormat; + struct image_s *colorImage[16]; + + uint32_t depthBuffer; + int depthFormat; + + uint32_t stencilBuffer; + int stencilFormat; + + uint32_t packedDepthStencilBuffer; + int packedDepthStencilFormat; + + int width; + int height; +} FBO_t; + +void FBO_Bind(FBO_t *fbo); +void FBO_Init(void); +void FBO_Shutdown(void); + +void FBO_BlitFromTexture(struct image_s *src, vec4i_t srcBox, vec2_t srcTexScale, FBO_t *dst, vec4i_t dstBox, struct shaderProgram_s *shaderProgram, vec4_t color, int blend); +void FBO_Blit(FBO_t *src, vec4i_t srcBox, vec2_t srcTexScale, FBO_t *dst, vec4i_t dstBox, struct shaderProgram_s *shaderProgram, vec4_t color, int blend); +void FBO_FastBlit(FBO_t *src, vec4i_t srcBox, FBO_t *dst, vec4i_t dstBox, int buffers, int filter); + + +#endif diff --git a/src/rend2/tr_flares.c b/src/rend2/tr_flares.c new file mode 100644 index 00000000..8e6c321f --- /dev/null +++ b/src/rend2/tr_flares.c @@ -0,0 +1,532 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_flares.c + +#include "tr_local.h" + +/* +============================================================================= + +LIGHT FLARES + +A light flare is an effect that takes place inside the eye when bright light +sources are visible. The size of the flare reletive to the screen is nearly +constant, irrespective of distance, but the intensity should be proportional to the +projected area of the light source. + +A surface that has been flagged as having a light flare will calculate the depth +buffer value that its midpoint should have when the surface is added. + +After all opaque surfaces have been rendered, the depth buffer is read back for +each flare in view. If the point has not been obscured by a closer surface, the +flare should be drawn. + +Surfaces that have a repeated texture should never be flagged as flaring, because +there will only be a single flare added at the midpoint of the polygon. + +To prevent abrupt popping, the intensity of the flare is interpolated up and +down as it changes visibility. This involves scene to scene state, unlike almost +all other aspects of the renderer, and is complicated by the fact that a single +frame may have multiple scenes. + +RB_RenderFlares() will be called once per view (twice in a mirrored scene, potentially +up to five or more times in a frame with 3D status bar icons). + +============================================================================= +*/ + + +// flare states maintain visibility over multiple frames for fading +// layers: view, mirror, menu +typedef struct flare_s { + struct flare_s *next; // for active chain + + int addedFrame; + + qboolean inPortal; // true if in a portal view of the scene + int frameSceneNum; + void *surface; + int fogNum; + + int fadeTime; + + qboolean visible; // state of last test + float drawIntensity; // may be non 0 even if !visible due to fading + + int windowX, windowY; + float eyeZ; + + vec3_t origin; + vec3_t color; +} flare_t; + +#define MAX_FLARES 128 + +flare_t r_flareStructs[MAX_FLARES]; +flare_t *r_activeFlares, *r_inactiveFlares; + +int flareCoeff; + +/* +================== +R_ClearFlares +================== +*/ +void R_ClearFlares( void ) { + int i; + + Com_Memset( r_flareStructs, 0, sizeof( r_flareStructs ) ); + r_activeFlares = NULL; + r_inactiveFlares = NULL; + + for ( i = 0 ; i < MAX_FLARES ; i++ ) { + r_flareStructs[i].next = r_inactiveFlares; + r_inactiveFlares = &r_flareStructs[i]; + } +} + + +/* +================== +RB_AddFlare + +This is called at surface tesselation time +================== +*/ +void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ) { + int i; + flare_t *f; + vec3_t local; + float d = 1; + vec4_t eye, clip, normalized, window; + + backEnd.pc.c_flareAdds++; + + if(normal && (normal[0] || normal[1] || normal[2])) + { + VectorSubtract( backEnd.viewParms.or.origin, point, local ); + VectorNormalizeFast(local); + d = DotProduct(local, normal); + + // If the viewer is behind the flare don't add it. + if(d < 0) + return; + } + + // if the point is off the screen, don't bother adding it + // calculate screen coordinates and depth + R_TransformModelToClip( point, backEnd.or.modelMatrix, + backEnd.viewParms.projectionMatrix, eye, clip ); + + // check to see if the point is completely off screen + for ( i = 0 ; i < 3 ; i++ ) { + if ( clip[i] >= clip[3] || clip[i] <= -clip[3] ) { + return; + } + } + + R_TransformClipToWindow( clip, &backEnd.viewParms, normalized, window ); + + if ( window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth + || window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight ) { + return; // shouldn't happen, since we check the clip[] above, except for FP rounding + } + + // see if a flare with a matching surface, scene, and view exists + for ( f = r_activeFlares ; f ; f = f->next ) { + if ( f->surface == surface && f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal ) { + break; + } + } + + // allocate a new one + if (!f ) { + if ( !r_inactiveFlares ) { + // the list is completely full + return; + } + f = r_inactiveFlares; + r_inactiveFlares = r_inactiveFlares->next; + f->next = r_activeFlares; + r_activeFlares = f; + + f->surface = surface; + f->frameSceneNum = backEnd.viewParms.frameSceneNum; + f->inPortal = backEnd.viewParms.isPortal; + f->addedFrame = -1; + } + + if ( f->addedFrame != backEnd.viewParms.frameCount - 1 ) { + f->visible = qfalse; + f->fadeTime = backEnd.refdef.time - 2000; + } + + f->addedFrame = backEnd.viewParms.frameCount; + f->fogNum = fogNum; + + VectorCopy(point, f->origin); + VectorCopy( color, f->color ); + + // fade the intensity of the flare down as the + // light surface turns away from the viewer + VectorScale( f->color, d, f->color ); + + // save info needed to test + f->windowX = backEnd.viewParms.viewportX + window[0]; + f->windowY = backEnd.viewParms.viewportY + window[1]; + + f->eyeZ = eye[2]; +} + +/* +================== +RB_AddDlightFlares +================== +*/ +void RB_AddDlightFlares( void ) { + dlight_t *l; + int i, j, k; + fog_t *fog = NULL; + + if ( !r_flares->integer ) { + return; + } + + l = backEnd.refdef.dlights; + + if(tr.world) + fog = tr.world->fogs; + + for (i=0 ; inumfogs ; j++ ) { + fog = &tr.world->fogs[j]; + for ( k = 0 ; k < 3 ; k++ ) { + if ( l->origin[k] < fog->bounds[0][k] || l->origin[k] > fog->bounds[1][k] ) { + break; + } + } + if ( k == 3 ) { + break; + } + } + if ( j == tr.world->numfogs ) { + j = 0; + } + } + else + j = 0; + + RB_AddFlare( (void *)l, j, l->origin, l->color, NULL ); + } +} + +/* +=============================================================================== + +FLARE BACK END + +=============================================================================== +*/ + +/* +================== +RB_TestFlare +================== +*/ +void RB_TestFlare( flare_t *f ) { + float depth; + qboolean visible; + float fade; + float screenZ; + + backEnd.pc.c_flareTests++; + + // doing a readpixels is as good as doing a glFinish(), so + // don't bother with another sync + glState.finishCalled = qfalse; + + // read back the z buffer contents + qglReadPixels( f->windowX, f->windowY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth ); + + screenZ = backEnd.viewParms.projectionMatrix[14] / + ( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] ); + + visible = ( -f->eyeZ - -screenZ ) < 24; + + if ( visible ) { + if ( !f->visible ) { + f->visible = qtrue; + f->fadeTime = backEnd.refdef.time - 1; + } + fade = ( ( backEnd.refdef.time - f->fadeTime ) /1000.0f ) * r_flareFade->value; + } else { + if ( f->visible ) { + f->visible = qfalse; + f->fadeTime = backEnd.refdef.time - 1; + } + fade = 1.0f - ( ( backEnd.refdef.time - f->fadeTime ) / 1000.0f ) * r_flareFade->value; + } + + if ( fade < 0 ) { + fade = 0; + } + if ( fade > 1 ) { + fade = 1; + } + + f->drawIntensity = fade; +} + + +/* +================== +RB_RenderFlare +================== +*/ +void RB_RenderFlare( flare_t *f ) { + float size; + vec3_t color; + int iColor[3]; + float distance, intensity, factor; + byte fogFactors[3] = {255, 255, 255}; + + backEnd.pc.c_flareRenders++; + + // We don't want too big values anyways when dividing by distance. + if(f->eyeZ > -1.0f) + distance = 1.0f; + else + distance = -f->eyeZ; + + // calculate the flare size.. + size = backEnd.viewParms.viewportWidth * ( r_flareSize->value/640.0f + 8 / distance ); + +/* + * This is an alternative to intensity scaling. It changes the size of the flare on screen instead + * with growing distance. See in the description at the top why this is not the way to go. + // size will change ~ 1/r. + size = backEnd.viewParms.viewportWidth * (r_flareSize->value / (distance * -2.0f)); +*/ + +/* + * As flare sizes stay nearly constant with increasing distance we must decrease the intensity + * to achieve a reasonable visual result. The intensity is ~ (size^2 / distance^2) which can be + * got by considering the ratio of + * (flaresurface on screen) : (Surface of sphere defined by flare origin and distance from flare) + * An important requirement is: + * intensity <= 1 for all distances. + * + * The formula used here to compute the intensity is as follows: + * intensity = flareCoeff * size^2 / (distance + size*sqrt(flareCoeff))^2 + * As you can see, the intensity will have a max. of 1 when the distance is 0. + * The coefficient flareCoeff will determine the falloff speed with increasing distance. + */ + + factor = distance + size * sqrt(flareCoeff); + + intensity = flareCoeff * size * size / (factor * factor); + + VectorScale(f->color, f->drawIntensity * intensity, color); + +// Calculations for fogging + if(tr.world && f->fogNum < tr.world->numfogs) + { + tess.numVertexes = 1; + VectorCopy(f->origin, tess.xyz[0]); + tess.fogNum = f->fogNum; + + RB_CalcModulateColorsByFog(fogFactors); + + // We don't need to render the flare if colors are 0 anyways. + if(!(fogFactors[0] || fogFactors[1] || fogFactors[2])) + return; + } + + iColor[0] = color[0] * fogFactors[0]; + iColor[1] = color[1] * fogFactors[1]; + iColor[2] = color[2] * fogFactors[2]; + + RB_BeginSurface( tr.flareShader, f->fogNum ); + + // FIXME: use quadstamp? + tess.xyz[tess.numVertexes][0] = f->windowX - size; + tess.xyz[tess.numVertexes][1] = f->windowY - size; + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = iColor[0] / 255.0f; + tess.vertexColors[tess.numVertexes][1] = iColor[1] / 255.0f; + tess.vertexColors[tess.numVertexes][2] = iColor[2] / 255.0f; + tess.vertexColors[tess.numVertexes][3] = 1.0f; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX - size; + tess.xyz[tess.numVertexes][1] = f->windowY + size; + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = iColor[0] / 255.0f; + tess.vertexColors[tess.numVertexes][1] = iColor[1] / 255.0f; + tess.vertexColors[tess.numVertexes][2] = iColor[2] / 255.0f; + tess.vertexColors[tess.numVertexes][3] = 1.0f; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX + size; + tess.xyz[tess.numVertexes][1] = f->windowY + size; + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = iColor[0] / 255.0f; + tess.vertexColors[tess.numVertexes][1] = iColor[1] / 255.0f; + tess.vertexColors[tess.numVertexes][2] = iColor[2] / 255.0f; + tess.vertexColors[tess.numVertexes][3] = 1.0f; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX + size; + tess.xyz[tess.numVertexes][1] = f->windowY - size; + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = iColor[0] / 255.0f; + tess.vertexColors[tess.numVertexes][1] = iColor[1] / 255.0f; + tess.vertexColors[tess.numVertexes][2] = iColor[2] / 255.0f; + tess.vertexColors[tess.numVertexes][3] = 1.0f; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 1; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 3; + + RB_EndSurface(); +} + +/* +================== +RB_RenderFlares + +Because flares are simulating an occular effect, they should be drawn after +everything (all views) in the entire frame has been drawn. + +Because of the way portals use the depth buffer to mark off areas, the +needed information would be lost after each view, so we are forced to draw +flares after each view. + +The resulting artifact is that flares in mirrors or portals don't dim properly +when occluded by something in the main view, and portal flares that should +extend past the portal edge will be overwritten. +================== +*/ +void RB_RenderFlares (void) { + flare_t *f; + flare_t **prev; + qboolean draw; + matrix_t oldmodelview, oldprojection, matrix; + + if ( !r_flares->integer ) { + return; + } + + if(r_flareCoeff->modified) + { + if(r_flareCoeff->value == 0.0f) + flareCoeff = atof(FLARE_STDCOEFF); + else + flareCoeff = r_flareCoeff->value; + + r_flareCoeff->modified = qfalse; + } + + // Reset currentEntity to world so that any previously referenced entities + // don't have influence on the rendering of these flares (i.e. RF_ renderer flags). + backEnd.currentEntity = &tr.worldEntity; + backEnd.or = backEnd.viewParms.world; + +// RB_AddDlightFlares(); + + // perform z buffer readback on each flare in this view + draw = qfalse; + prev = &r_activeFlares; + while ( ( f = *prev ) != NULL ) { + // throw out any flares that weren't added last frame + if ( f->addedFrame < backEnd.viewParms.frameCount - 1 ) { + *prev = f->next; + f->next = r_inactiveFlares; + r_inactiveFlares = f; + continue; + } + + // don't draw any here that aren't from this scene / portal + f->drawIntensity = 0; + if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal ) { + RB_TestFlare( f ); + if ( f->drawIntensity ) { + draw = qtrue; + } else { + // this flare has completely faded out, so remove it from the chain + *prev = f->next; + f->next = r_inactiveFlares; + r_inactiveFlares = f; + continue; + } + } + + prev = &f->next; + } + + if ( !draw ) { + return; // none visible + } + + if ( backEnd.viewParms.isPortal ) { + qglDisable (GL_CLIP_PLANE0); + } + + Matrix16Copy(glState.projection, oldprojection); + Matrix16Copy(glState.modelview, oldmodelview); + Matrix16Identity(matrix); + GL_SetModelviewMatrix(matrix); + Matrix16Ortho( backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, + backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, + -99999, 99999, matrix ); + GL_SetProjectionMatrix(matrix); + + for ( f = r_activeFlares ; f ; f = f->next ) { + if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal + && f->drawIntensity ) { + RB_RenderFlare( f ); + } + } + + GL_SetProjectionMatrix(oldprojection); + GL_SetModelviewMatrix(oldmodelview); +} + + + + + diff --git a/src/rend2/tr_font.c b/src/rend2/tr_font.c new file mode 100644 index 00000000..87465e52 --- /dev/null +++ b/src/rend2/tr_font.c @@ -0,0 +1,555 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_font.c +// +// +// The font system uses FreeType 2.x to render TrueType fonts for use within the game. +// As of this writing ( Nov, 2000 ) Team Arena uses these fonts for all of the ui and +// about 90% of the cgame presentation. A few areas of the CGAME were left uses the old +// fonts since the code is shared with standard Q3A. +// +// If you include this font rendering code in a commercial product you MUST include the +// following somewhere with your product, see www.freetype.org for specifics or changes. +// The Freetype code also uses some hinting techniques that MIGHT infringe on patents +// held by apple so be aware of that also. +// +// As of Q3A 1.25+ and Team Arena, we are shipping the game with the font rendering code +// disabled. This removes any potential patent issues and it keeps us from having to +// distribute an actual TrueTrype font which is 1. expensive to do and 2. seems to require +// an act of god to accomplish. +// +// What we did was pre-render the fonts using FreeType ( which is why we leave the FreeType +// credit in the credits ) and then saved off the glyph data and then hand touched up the +// font bitmaps so they scale a bit better in GL. +// +// There are limitations in the way fonts are saved and reloaded in that it is based on +// point size and not name. So if you pre-render Helvetica in 18 point and Impact in 18 point +// you will end up with a single 18 point data file and image set. Typically you will want to +// choose 3 sizes to best approximate the scaling you will be doing in the ui scripting system +// +// In the UI Scripting code, a scale of 1.0 is equal to a 48 point font. In Team Arena, we +// use three or four scales, most of them exactly equaling the specific rendered size. We +// rendered three sizes in Team Arena, 12, 16, and 20. +// +// To generate new font data you need to go through the following steps. +// 1. delete the fontImage_x_xx.tga files and fontImage_xx.dat files from the fonts path. +// 2. in a ui script, specificy a font, smallFont, and bigFont keyword with font name and +// point size. the original TrueType fonts must exist in fonts at this point. +// 3. run the game, you should see things normally. +// 4. Exit the game and there will be three dat files and at least three tga files. The +// tga's are in 256x256 pages so if it takes three images to render a 24 point font you +// will end up with fontImage_0_24.tga through fontImage_2_24.tga +// 5. In future runs of the game, the system looks for these images and data files when a s +// specific point sized font is rendered and loads them for use. +// 6. Because of the original beta nature of the FreeType code you will probably want to hand +// touch the font bitmaps. +// +// Currently a define in the project turns on or off the FreeType code which is currently +// defined out. To pre-render new fonts you need enable the define ( BUILD_FREETYPE ) and +// uncheck the exclude from build check box in the FreeType2 area of the Renderer project. + + +#include "tr_local.h" +#include "../qcommon/qcommon.h" + +#ifdef BUILD_FREETYPE +#include +#include FT_ERRORS_H +#include FT_SYSTEM_H +#include FT_IMAGE_H +#include FT_FREETYPE_H +#include FT_OUTLINE_H + +#define _FLOOR(x) ((x) & -64) +#define _CEIL(x) (((x)+63) & -64) +#define _TRUNC(x) ((x) >> 6) + +FT_Library ftLibrary = NULL; +#endif + +#define MAX_FONTS 6 +static int registeredFontCount = 0; +static fontInfo_t registeredFont[MAX_FONTS]; + +#ifdef BUILD_FREETYPE +void R_GetGlyphInfo(FT_GlyphSlot glyph, int *left, int *right, int *width, int *top, int *bottom, int *height, int *pitch) { + *left = _FLOOR( glyph->metrics.horiBearingX ); + *right = _CEIL( glyph->metrics.horiBearingX + glyph->metrics.width ); + *width = _TRUNC(*right - *left); + + *top = _CEIL( glyph->metrics.horiBearingY ); + *bottom = _FLOOR( glyph->metrics.horiBearingY - glyph->metrics.height ); + *height = _TRUNC( *top - *bottom ); + *pitch = ( qtrue ? (*width+3) & -4 : (*width+7) >> 3 ); +} + + +FT_Bitmap *R_RenderGlyph(FT_GlyphSlot glyph, glyphInfo_t* glyphOut) { + FT_Bitmap *bit2; + int left, right, width, top, bottom, height, pitch, size; + + R_GetGlyphInfo(glyph, &left, &right, &width, &top, &bottom, &height, &pitch); + + if ( glyph->format == ft_glyph_format_outline ) { + size = pitch*height; + + bit2 = ri.Malloc(sizeof(FT_Bitmap)); + + bit2->width = width; + bit2->rows = height; + bit2->pitch = pitch; + bit2->pixel_mode = ft_pixel_mode_grays; + //bit2->pixel_mode = ft_pixel_mode_mono; + bit2->buffer = ri.Malloc(pitch*height); + bit2->num_grays = 256; + + Com_Memset( bit2->buffer, 0, size ); + + FT_Outline_Translate( &glyph->outline, -left, -bottom ); + + FT_Outline_Get_Bitmap( ftLibrary, &glyph->outline, bit2 ); + + glyphOut->height = height; + glyphOut->pitch = pitch; + glyphOut->top = (glyph->metrics.horiBearingY >> 6) + 1; + glyphOut->bottom = bottom; + + return bit2; + } else { + ri.Printf(PRINT_ALL, "Non-outline fonts are not supported\n"); + } + return NULL; +} + +void WriteTGA (char *filename, byte *data, int width, int height) { + byte *buffer; + int i, c; + int row; + unsigned char *flip; + unsigned char *src, *dst; + + buffer = ri.Malloc(width*height*4 + 18); + Com_Memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = width&255; + buffer[13] = width>>8; + buffer[14] = height&255; + buffer[15] = height>>8; + buffer[16] = 32; // pixel size + + // swap rgb to bgr + c = 18 + width * height * 4; + for (i=18 ; iglyph, &glyph); + if (bitmap) { + glyph.xSkip = (face->glyph->metrics.horiAdvance >> 6) + 1; + } else { + return &glyph; + } + + if (glyph.height > *maxHeight) { + *maxHeight = glyph.height; + } + + if (calcHeight) { + ri.Free(bitmap->buffer); + ri.Free(bitmap); + return &glyph; + } + +/* + // need to convert to power of 2 sizes so we do not get + // any scaling from the gl upload + for (scaled_width = 1 ; scaled_width < glyph.pitch ; scaled_width<<=1) + ; + for (scaled_height = 1 ; scaled_height < glyph.height ; scaled_height<<=1) + ; +*/ + + scaled_width = glyph.pitch; + scaled_height = glyph.height; + + // we need to make sure we fit + if (*xOut + scaled_width + 1 >= 255) { + *xOut = 0; + *yOut += *maxHeight + 1; + } + + if (*yOut + *maxHeight + 1 >= 255) { + *yOut = -1; + *xOut = -1; + ri.Free(bitmap->buffer); + ri.Free(bitmap); + return &glyph; + } + + + src = bitmap->buffer; + dst = imageOut + (*yOut * 256) + *xOut; + + if (bitmap->pixel_mode == ft_pixel_mode_mono) { + for (i = 0; i < glyph.height; i++) { + int j; + unsigned char *_src = src; + unsigned char *_dst = dst; + unsigned char mask = 0x80; + unsigned char val = *_src; + for (j = 0; j < glyph.pitch; j++) { + if (mask == 0x80) { + val = *_src++; + } + if (val & mask) { + *_dst = 0xff; + } + mask >>= 1; + + if ( mask == 0 ) { + mask = 0x80; + } + _dst++; + } + + src += glyph.pitch; + dst += 256; + } + } else { + for (i = 0; i < glyph.height; i++) { + Com_Memcpy(dst, src, glyph.pitch); + src += glyph.pitch; + dst += 256; + } + } + + // we now have an 8 bit per pixel grey scale bitmap + // that is width wide and pf->ftSize->metrics.y_ppem tall + + glyph.imageHeight = scaled_height; + glyph.imageWidth = scaled_width; + glyph.s = (float)*xOut / 256; + glyph.t = (float)*yOut / 256; + glyph.s2 = glyph.s + (float)scaled_width / 256; + glyph.t2 = glyph.t + (float)scaled_height / 256; + + *xOut += scaled_width + 1; + + ri.Free(bitmap->buffer); + ri.Free(bitmap); + } + + return &glyph; +} +#endif + +static int fdOffset; +static byte *fdFile; + +int readInt( void ) { + int i = fdFile[fdOffset]+(fdFile[fdOffset+1]<<8)+(fdFile[fdOffset+2]<<16)+(fdFile[fdOffset+3]<<24); + fdOffset += 4; + return i; +} + +typedef union { + byte fred[4]; + float ffred; +} poor; + +float readFloat( void ) { + poor me; +#if defined Q3_BIG_ENDIAN + me.fred[0] = fdFile[fdOffset+3]; + me.fred[1] = fdFile[fdOffset+2]; + me.fred[2] = fdFile[fdOffset+1]; + me.fred[3] = fdFile[fdOffset+0]; +#elif defined Q3_LITTLE_ENDIAN + me.fred[0] = fdFile[fdOffset+0]; + me.fred[1] = fdFile[fdOffset+1]; + me.fred[2] = fdFile[fdOffset+2]; + me.fred[3] = fdFile[fdOffset+3]; +#endif + fdOffset += 4; + return me.ffred; +} + +void RE_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) { +#ifdef BUILD_FREETYPE + FT_Face face; + int j, k, xOut, yOut, lastStart, imageNumber; + int scaledSize, newSize, maxHeight, left; + unsigned char *out, *imageBuff; + glyphInfo_t *glyph; + image_t *image; + qhandle_t h; + float max; + float dpi = 72; + float glyphScale; +#endif + void *faceData; + int i, len; + char name[1024]; + + if (!fontName) { + ri.Printf(PRINT_ALL, "RE_RegisterFont: called with empty name\n"); + return; + } + + if (pointSize <= 0) { + pointSize = 12; + } + + // make sure the render thread is stopped + R_SyncRenderThread(); + + if (registeredFontCount >= MAX_FONTS) { + ri.Printf(PRINT_WARNING, "RE_RegisterFont: Too many fonts registered already.\n"); + return; + } + + Com_sprintf(name, sizeof(name), "fonts/fontImage_%i.dat",pointSize); + for (i = 0; i < registeredFontCount; i++) { + if (Q_stricmp(name, registeredFont[i].name) == 0) { + Com_Memcpy(font, ®isteredFont[i], sizeof(fontInfo_t)); + return; + } + } + + len = ri.FS_ReadFile(name, NULL); + if (len == sizeof(fontInfo_t)) { + ri.FS_ReadFile(name, &faceData); + fdOffset = 0; + fdFile = faceData; + for(i=0; iglyphs[i].height = readInt(); + font->glyphs[i].top = readInt(); + font->glyphs[i].bottom = readInt(); + font->glyphs[i].pitch = readInt(); + font->glyphs[i].xSkip = readInt(); + font->glyphs[i].imageWidth = readInt(); + font->glyphs[i].imageHeight = readInt(); + font->glyphs[i].s = readFloat(); + font->glyphs[i].t = readFloat(); + font->glyphs[i].s2 = readFloat(); + font->glyphs[i].t2 = readFloat(); + font->glyphs[i].glyph = readInt(); + Q_strncpyz(font->glyphs[i].shaderName, (const char *)&fdFile[fdOffset], sizeof(font->glyphs[i].shaderName)); + fdOffset += sizeof(font->glyphs[i].shaderName); + } + font->glyphScale = readFloat(); + Com_Memcpy(font->name, &fdFile[fdOffset], MAX_QPATH); + +// Com_Memcpy(font, faceData, sizeof(fontInfo_t)); + Q_strncpyz(font->name, name, sizeof(font->name)); + for (i = GLYPH_START; i < GLYPH_END; i++) { + font->glyphs[i].glyph = RE_RegisterShaderNoMip(font->glyphs[i].shaderName); + } + Com_Memcpy(®isteredFont[registeredFontCount++], font, sizeof(fontInfo_t)); + return; + } + +#ifndef BUILD_FREETYPE + ri.Printf(PRINT_WARNING, "RE_RegisterFont: FreeType code not available\n"); +#else + if (ftLibrary == NULL) { + ri.Printf(PRINT_WARNING, "RE_RegisterFont: FreeType not initialized.\n"); + return; + } + + len = ri.FS_ReadFile(fontName, &faceData); + if (len <= 0) { + ri.Printf(PRINT_WARNING, "RE_RegisterFont: Unable to read font file '%s'\n", fontName); + return; + } + + // allocate on the stack first in case we fail + if (FT_New_Memory_Face( ftLibrary, faceData, len, 0, &face )) { + ri.Printf(PRINT_WARNING, "RE_RegisterFont: FreeType, unable to allocate new face.\n"); + return; + } + + + if (FT_Set_Char_Size( face, pointSize << 6, pointSize << 6, dpi, dpi)) { + ri.Printf(PRINT_WARNING, "RE_RegisterFont: FreeType, unable to set face char size.\n"); + return; + } + + //*font = ®isteredFonts[registeredFontCount++]; + + // make a 256x256 image buffer, once it is full, register it, clean it and keep going + // until all glyphs are rendered + + out = ri.Malloc(1024*1024); + if (out == NULL) { + ri.Printf(PRINT_WARNING, "RE_RegisterFont: ri.Malloc failure during output image creation.\n"); + return; + } + Com_Memset(out, 0, 1024*1024); + + maxHeight = 0; + + for (i = GLYPH_START; i < GLYPH_END; i++) { + RE_ConstructGlyphInfo(out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qtrue); + } + + xOut = 0; + yOut = 0; + i = GLYPH_START; + lastStart = i; + imageNumber = 0; + + while ( i <= GLYPH_END ) { + + glyph = RE_ConstructGlyphInfo(out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qfalse); + + if (xOut == -1 || yOut == -1 || i == GLYPH_END) { + // ran out of room + // we need to create an image from the bitmap, set all the handles in the glyphs to this point + // + + scaledSize = 256*256; + newSize = scaledSize * 4; + imageBuff = ri.Malloc(newSize); + left = 0; + max = 0; + for ( k = 0; k < (scaledSize) ; k++ ) { + if (max < out[k]) { + max = out[k]; + } + } + + if (max > 0) { + max = 255/max; + } + + for ( k = 0; k < (scaledSize) ; k++ ) { + imageBuff[left++] = 255; + imageBuff[left++] = 255; + imageBuff[left++] = 255; + + imageBuff[left++] = ((float)out[k] * max); + } + + Com_sprintf (name, sizeof(name), "fonts/fontImage_%i_%i.tga", imageNumber++, pointSize); + if (r_saveFontData->integer) { + WriteTGA(name, imageBuff, 256, 256); + } + + //Com_sprintf (name, sizeof(name), "fonts/fontImage_%i_%i", imageNumber++, pointSize); + image = R_CreateImage(name, imageBuff, 256, 256, qfalse, qfalse, GL_CLAMP_TO_EDGE); + h = RE_RegisterShaderFromImage(name, LIGHTMAP_2D, image, qfalse); + for (j = lastStart; j < i; j++) { + font->glyphs[j].glyph = h; + Q_strncpyz(font->glyphs[j].shaderName, name, sizeof(font->glyphs[j].shaderName)); + } + lastStart = i; + Com_Memset(out, 0, 1024*1024); + xOut = 0; + yOut = 0; + ri.Free(imageBuff); + i++; + } else { + Com_Memcpy(&font->glyphs[i], glyph, sizeof(glyphInfo_t)); + i++; + } + } + + // change the scale to be relative to 1 based on 72 dpi ( so dpi of 144 means a scale of .5 ) + glyphScale = 72.0f / dpi; + + // we also need to adjust the scale based on point size relative to 48 points as the ui scaling is based on a 48 point font + glyphScale *= 48.0f / pointSize; + + registeredFont[registeredFontCount].glyphScale = glyphScale; + font->glyphScale = glyphScale; + Com_Memcpy(®isteredFont[registeredFontCount++], font, sizeof(fontInfo_t)); + + if (r_saveFontData->integer) { + ri.FS_WriteFile(va("fonts/fontImage_%i.dat", pointSize), font, sizeof(fontInfo_t)); + } + + ri.Free(out); + + ri.FS_FreeFile(faceData); +#endif +} + + + +void R_InitFreeType(void) { +#ifdef BUILD_FREETYPE + if (FT_Init_FreeType( &ftLibrary )) { + ri.Printf(PRINT_WARNING, "R_InitFreeType: Unable to initialize FreeType.\n"); + } +#endif + registeredFontCount = 0; +} + + +void R_DoneFreeType(void) { +#ifdef BUILD_FREETYPE + if (ftLibrary) { + FT_Done_FreeType( ftLibrary ); + ftLibrary = NULL; + } +#endif + registeredFontCount = 0; +} + diff --git a/src/rend2/tr_glsl.c b/src/rend2/tr_glsl.c new file mode 100644 index 00000000..823fce39 --- /dev/null +++ b/src/rend2/tr_glsl.c @@ -0,0 +1,2825 @@ +/* +=========================================================================== +Copyright (C) 2006-2009 Robert Beckebans + +This file is part of XreaL source code. + +XreaL source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +XreaL source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with XreaL source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_glsl.c +#include "tr_local.h" + +void GLSL_BindNullProgram(void); + +// FIXME: Do something that isn't this messy +static const char *fallbackGenericShader_vp = +"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\nattribut" +"e vec4 attr_TexCoord1;\r\nattribute vec3 attr_Normal;\r\nattribute vec4 att" +"r_Color;\r\n\r\n#if defined(USE_VERTEX_ANIMATION)\r\nattribute vec4 attr_Po" +"sition2;\r\nattribute vec3 attr_Normal2;\r\n#endif\r\n\r\nuniform mat4 u_" +"DiffuseTexMatrix;\r\nuniform vec3 u_ViewOrigin;\r\n\r\n#if defined(USE_TC" +"GEN)\r\nuniform int u_TCGen0;\r\nuniform vec3 u_TCGen0Vector0;\r\nunif" +"orm vec3 u_TCGen0Vector1;\r\n#endif\r\n\r\n#if defined(USE_FOG)\r\nunifor" +"m vec4 u_FogDistance;\r\nuniform vec4 u_FogDepth;\r\nuniform float u_F" +"ogEyeT;\r\nuniform vec4 u_FogColorMask;\r\n#endif\r\n\r\n#if defined(USE_" +"DEFORM_VERTEXES)\r\nuniform int u_DeformGen;\r\nuniform float u_DeformP" +"arams[5];\r\n#endif\r\n\r\nuniform float u_Time;\r\n\r\nuniform mat4 u_M" +"odelViewProjectionMatrix;\r\nuniform vec4 u_BaseColor;\r\nuniform vec4 " +"u_VertColor;\r\n\r\n#if defined(USE_RGBAGEN)\r\nuniform int u_ColorGen;" +"\r\nuniform int u_AlphaGen;\r\nuniform vec3 u_AmbientLight;\r\nuniform" +" vec3 u_DirectedLight;\r\nuniform vec4 u_LightOrigin;\r\nuniform float " +" u_PortalRange;\r\n#endif\r\n\r\n#if defined(USE_VERTEX_ANIMATION)\r\nunifo" +"rm float u_VertexLerp;\r\n#endif\r\n\r\nvarying vec2 var_DiffuseTex;\r\n" +"#if defined(USE_LIGHTMAP)\r\nvarying vec2 var_LightTex;\r\n#endif\r\nvary" +"ing vec4 var_Color;\r\n\r\nvec2 DoTexMatrix(vec2 st, vec3 position, mat4 " +"texMatrix)\r\n{\r\n\tfloat amplitude = texMatrix[3][0];\r\n\tfloat phase = " +"texMatrix[3][1];\r\n\tvec2 st2 = (texMatrix * vec4(st, 1.0, 0.0)).st;\r\n\r" +"\n\tvec3 offsetPos = position.xyz / 1024.0;\r\n\toffsetPos.x += offsetPos.z" +";\r\n\r\n\tvec2 texOffset = sin((offsetPos.xy + vec2(phase)) * 2.0 * M_PI);" +"\r\n\t\r\n\treturn st2 + texOffset * amplitude;\r\n}\r\n\r\n#if defined(USE" +"_DEFORM_VERTEXES)\r\nfloat triangle(float x)\r\n{\r\n\treturn max(1.0 - abs" +"(x), 0);\r\n}\r\n\r\nfloat sawtooth(float x)\r\n{\r\n\treturn x - floor(x);" +"\r\n}\r\n\r\nvec4 DeformPosition(const vec4 pos, const vec3 normal, const v" +"ec2 st)\r\n{\r\n\tfloat base = u_DeformParams[0];\r\n\tfloat amplitude" +" = u_DeformParams[1];\r\n\tfloat phase = u_DeformParams[2];\r\n\tfloat " +"frequency = u_DeformParams[3];\r\n\tfloat spread = u_DeformParams[4];\r" +"\n\t\r\n\tif (u_DeformGen == DGEN_BULGE)\r\n\t{\r\n\t\tphase *= M_PI * 0.25" +" * st.x;\r\n\t}\r\n\telse // if (u_DeformGen <= DGEN_WAVE_INVERSE_SAWTOOTH)" +"\r\n\t{\r\n\t\tphase += (pos.x + pos.y + pos.z) * spread;\r\n\t}\r\n\r\n\tf" +"loat value = phase + (u_Time * frequency);\r\n\tfloat func;\r\n\r\n\tif (u_" +"DeformGen == DGEN_WAVE_SIN)\r\n\t{\r\n\t\tfunc = sin(value * 2.0 * M_PI);\r" +"\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE_SQUARE)\r\n\t{\r\n\t\tfunc = s" +"ign(sin(value * 2.0 * M_PI));\r\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE" +"_TRIANGLE)\r\n\t{\r\n\t\tfunc = triangle(value);\r\n\t}\r\n\telse if (u_Def" +"ormGen == DGEN_WAVE_SAWTOOTH)\r\n\t{\r\n\t\tfunc = sawtooth(value);\r\n\t}" +"\r\n\telse if (u_DeformGen == DGEN_WAVE_INVERSE_SAWTOOTH)\r\n\t{\r\n\t\tfun" +"c = (1.0 - sawtooth(value));\r\n\t}\r\n\telse if (u_DeformGen == DGEN_BULGE" +")\r\n\t{\r\n\t\tfunc = sin(value);\r\n\t}\r\n\t\r\n\tvec4 deformed = pos;\r" +"\n\tdeformed.xyz += normal * (base + func * amplitude);\r\n\r\n\treturn def" +"ormed;\r\n}\r\n#endif\r\n\r\n#if defined(USE_TCGEN)\r\nvec2 GenTexCoords(in" +"t TCGen, vec4 position, vec3 normal, vec3 TCGenVector0, vec3 TCGenVector1)" +"\r\n{\r\n\tvec2 tex = attr_TexCoord0.st;\r\n\r\n\tif (TCGen == TCGEN_LIGHTM" +"AP)\r\n\t{\r\n\t\ttex = attr_TexCoord1.st;\r\n\t}\r\n\telse if (TCGen == TC" +"GEN_ENVIRONMENT_MAPPED)\r\n\t{\r\n\t\tvec3 viewer = normalize(u_ViewOrigin " +"- position.xyz);\r\n\t\tvec3 reflected = normal * 2.0 * dot(normal, viewer)" +" - viewer;\r\n\r\n\t\ttex = reflected.yz * vec2(0.5, -0.5) + 0.5;\r\n\t}\r" +"\n\telse if (TCGen == TCGEN_VECTOR)\r\n\t{\r\n\t\ttex = vec2(dot(position.x" +"yz, TCGenVector0), dot(position.xyz, TCGenVector1));\r\n\t}\r\n\t\r\n\tretu" +"rn tex;\r\n}\r\n#endif\r\n\r\nvoid main()\r\n{\r\n#if defined(USE_VERTEX_AN" +"IMATION)\r\n\tvec4 position = mix(attr_Position, attr_Position2, u_VertexLe" +"rp);\r\n\tvec3 normal = normalize(mix(attr_Normal, attr_Normal2, u_VertexLe" +"rp));\r\n#else\r\n\tvec4 position = attr_Position;\r\n\tvec3 normal = attr_" +"Normal;\r\n#endif\r\n\r\n#if defined(USE_DEFORM_VERTEXES)\r\n\tposition = D" +"eformPosition(position, normal, attr_TexCoord0.st);\r\n#endif\r\n\r\n\tgl_P" +"osition = u_ModelViewProjectionMatrix * position;\r\n\r\n#if defined(USE_TC" +"GEN)\r\n\tvec2 tex = GenTexCoords(u_TCGen0, position, normal, u_TCGen0Vecto" +"r0, u_TCGen0Vector1);\r\n#else\r\n\tvec2 tex = attr_TexCoord0.st;\r\n#endif" +"\r\n\tvar_DiffuseTex = DoTexMatrix(tex, position.xyz, u_DiffuseTexMatrix);" +"\r\n\r\n#if defined(USE_LIGHTMAP)\r\n\tvar_LightTex = attr_TexCoord1.st;\r" +"\n#endif\r\n\r\n\tvar_Color = u_VertColor * attr_Color + u_BaseColor;\r\n\r" +"\n#if defined(USE_RGBAGEN)\r\n\tif (u_ColorGen == CGEN_LIGHTING_DIFFUSE)\r" +"\n\t{\r\n\t\tfloat incoming = max(dot(normal, u_LightOrigin.xyz), 0.0);\r\n" +"\r\n\t\tvar_Color.rgb = min(u_DirectedLight * incoming + u_AmbientLight, 1." +"0);\r\n\t}\r\n\t\r\n\tvec3 toView = u_ViewOrigin - position.xyz;\r\n\r\n\ti" +"f (u_AlphaGen == AGEN_LIGHTING_SPECULAR)\r\n\t{\r\n\t\tvec3 lightDir = norm" +"alize(vec3(-960.0, -1980.0, 96.0) - position.xyz);\r\n\t\tvec3 viewer = nor" +"malize(toView);\r\n\t\tvec3 halfangle = normalize(lightDir + viewer);\r\n\t" +"\t\r\n\t\tvar_Color.a = pow(max(dot(normal, halfangle), 0.0), 8.0);\r\n\t}" +"\r\n\telse if (u_AlphaGen == AGEN_PORTAL)\r\n\t{\r\n\t\tfloat alpha = lengt" +"h(toView) / u_PortalRange;\r\n\r\n\t\tvar_Color.a = min(alpha, 1.0);\r\n\t}" +"\r\n\telse if (u_AlphaGen == AGEN_FRESNEL)\r\n\t{\r\n\t\tvec3 viewer = norm" +"alize(toView);\r\n\t\t\r\n\t\tvar_Color.a = 0.10 + 0.90 * pow(1.0 - dot(nor" +"mal, viewer), 5);\r\n\t}\r\n#endif\r\n\r\n#if defined (USE_FOG)\r\n\tfloat " +"s = dot(position, u_FogDistance);\r\n\tfloat t = dot(position, u_FogDepth);" +"\r\n\t\r\n\tif (t >= 1.0)\r\n\t{\r\n\t\ts *= t / (t - min(u_FogEyeT, 0.0));" +"\r\n\t}\r\n\telse\r\n\t{\r\n\t\ts *= max(t + sign(u_FogEyeT), 0.0);\r\n\t}" +"\r\n\t\r\n\ts = 1.0 - sqrt(clamp(s * 8.0, 0.0, 1.0));\r\n\t\r\n\tvar_Color " +"*= u_FogColorMask * s + (vec4(1.0) - u_FogColorMask);\r\n#endif\r\n}\r\n"; + +static const char *fallbackGenericShader_fp = +"uniform sampler2D u_DiffuseMap;\r\n\r\n#if defined(USE_LIGHTMAP)\r\nuniform" +" sampler2D u_LightMap;\r\n#endif\r\n\r\nuniform int u_Texture1Env;\r" +"\n\r\nvarying vec2 var_DiffuseTex;\r\n\r\n#if defined(USE_LIGHTMAP)\r" +"\nvarying vec2 var_LightTex;\r\n#endif\r\n\r\nvarying vec4 var_Co" +"lor;\r\n\r\n\r\nvoid main()\r\n{\r\n\tvec4 color = texture2D(u_DiffuseMap," +" var_DiffuseTex);\r\n#if defined(USE_LIGHTMAP)\r\n\tvec4 color2 = texture2D" +"(u_LightMap, var_LightTex);\r\n #if defined(RGBE_LIGHTMAP)\r\n\tcolor2.rgb" +" *= exp2(color2.a * 255.0 - 128.0);\r\n\tcolor2.a = 1.0;\r\n #endif\r\n\r" +"\n\tif (u_Texture1Env == TEXENV_MODULATE)\r\n\t{\r\n\t\tcolor *= color2;\r" +"\n\t}\r\n\telse if (u_Texture1Env == TEXENV_ADD)\r\n\t{\r\n\t\tcolor += col" +"or2;\r\n\t}\r\n\telse if (u_Texture1Env == TEXENV_REPLACE)\r\n\t{\r\n\t\tco" +"lor = color2;\r\n\t}\r\n#endif\r\n\r\n\tgl_FragColor = color * var_Color;\r" +"\n}\r\n"; + +static const char *fallbackTextureColorShader_vp = +"#version 120\r\n\r\nattribute vec4 attr_Position;\r\nattribute vec4 attr_Te" +"xCoord0;\r\n\r\nuniform mat4 u_ModelViewProjectionMatrix;\r\n\r\nvarying " +"vec2 var_Tex1;\r\n\r\n\r\nvoid main()\r\n{\r\n\tgl_Position = u_ModelView" +"ProjectionMatrix * attr_Position;\r\n\tvar_Tex1 = attr_TexCoord0.st;\r\n}\r" +"\n"; + +static const char *fallbackTextureColorShader_fp = +"#version 120\r\n\r\nuniform sampler2D u_DiffuseMap;\r\nuniform vec4 u_" +"Color;\r\n\r\nvarying vec2 var_Tex1;\r\n\r\n\r\nvoid main()\r\n{\r" +"\n\tgl_FragColor = texture2D(u_DiffuseMap, var_Tex1) * u_Color;\r\n}\r\n"; + +static const char *fallbackFogPassShader_vp = +"attribute vec4 attr_Position;\r\nattribute vec3 attr_Normal;\r\nattribute" +" vec4 attr_TexCoord0;\r\n\r\n//#if defined(USE_VERTEX_ANIMATION)\r\nattrib" +"ute vec4 attr_Position2;\r\nattribute vec3 attr_Normal2;\r\n//#endif\r\n" +"\r\nuniform vec4 u_FogDistance;\r\nuniform vec4 u_FogDepth;\r\nunifor" +"m float u_FogEyeT;\r\n\r\n//#if defined(USE_DEFORM_VERTEXES)\r\nuniform i" +"nt u_DeformGen;\r\nuniform float u_DeformParams[5];\r\n//#endif\r\n\r" +"\nuniform float u_Time;\r\nuniform mat4 u_ModelViewProjectionMatrix;\r" +"\n\r\n//#if defined(USE_VERTEX_ANIMATION)\r\nuniform float u_VertexLerp;" +"\r\n//#endif\r\n\r\nvarying float var_Scale;\r\n\r\n\r\nfloat triangle(fl" +"oat x)\r\n{\r\n\treturn max(1.0 - abs(x), 0);\r\n}\r\n\r\nfloat sawtooth(fl" +"oat x)\r\n{\r\n\treturn x - floor(x);\r\n}\r\n\r\nvec4 DeformPosition(const" +" vec4 pos, const vec3 normal, const vec2 st)\r\n{\r\n\tif (u_DeformGen == 0" +")\r\n\t{\r\n\t\treturn pos;\r\n\t}\r\n\r\n\tfloat base = u_DeformParam" +"s[0];\r\n\tfloat amplitude = u_DeformParams[1];\r\n\tfloat phase = u_De" +"formParams[2];\r\n\tfloat frequency = u_DeformParams[3];\r\n\tfloat spread " +"= u_DeformParams[4];\r\n\t\t\r\n\tif (u_DeformGen <= DGEN_WAVE_INVERSE_S" +"AWTOOTH)\r\n\t{\r\n\t\tphase += (pos.x + pos.y + pos.z) * spread;\r\n\t}\r" +"\n\telse if (u_DeformGen == DGEN_BULGE)\r\n\t{\r\n\t\tphase *= M_PI * 0.25 " +"* st.x;\r\n\t}\r\n\r\n\tfloat value = phase + (u_Time * frequency);\r\n\tfl" +"oat func;\r\n\r\n\tif (u_DeformGen == DGEN_WAVE_SIN)\r\n\t{\r\n\t\tfunc = s" +"in(value * 2.0 * M_PI);\r\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE_SQUAR" +"E)\r\n\t{\r\n\t\tfunc = sign(sin(value * 2.0 * M_PI));\r\n\t}\r\n\telse if " +"(u_DeformGen == DGEN_WAVE_TRIANGLE)\r\n\t{\r\n\t\tfunc = triangle(value);\r" +"\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE_SAWTOOTH)\r\n\t{\r\n\t\tfunc =" +" sawtooth(value);\r\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE_INVERSE_SAW" +"TOOTH)\r\n\t{\r\n\t\tfunc = (1.0 - sawtooth(value));\r\n\t}\r\n\telse if (u" +"_DeformGen == DGEN_BULGE)\r\n\t{\r\n\t\tfunc = sin(value);\r\n\t}\r\n\r\n\t" +"vec4 deformed = pos;\r\n\tdeformed.xyz += normal * (base + func * amplitude" +");\r\n\r\n\treturn deformed;\r\n\r\n}\r\n\r\nvoid main()\r\n{\r\n\tvec4 pos" +"ition = mix(attr_Position, attr_Position2, u_VertexLerp);\r\n\tvec3 normal " +"= normalize(mix(attr_Normal, attr_Normal2, u_VertexLerp));\r\n\r\n\tpositio" +"n = DeformPosition(position, normal, attr_TexCoord0.st);\r\n\r\n\tgl_Positi" +"on = u_ModelViewProjectionMatrix * position;\r\n\r\n\tfloat s = dot(positio" +"n, u_FogDistance);\r\n\tfloat t = dot(position, u_FogDepth);\r\n\r\n\tif (t" +" >= 1.0)\r\n\t{\r\n\t\ts *= t / (t - min(u_FogEyeT, 0.0));\r\n\t}\r\n\telse" +"\r\n\t{\r\n\t\ts *= max(t + sign(u_FogEyeT), 0.0);\r\n\t}\r\n\r\n\tvar_Scal" +"e = s * 8.0;\r\n}\r\n"; + +static const char *fallbackFogPassShader_fp = +"uniform vec4 u_Color;\r\n\r\nvarying float var_Scale;\r\n\r\nvoid main()\r" +"\n{\r\n\tgl_FragColor = u_Color;\r\n\tgl_FragColor.a *= sqrt(clamp(var_Scal" +"e, 0.0, 1.0));\r\n}\r\n"; + +static const char *fallbackDlightShader_vp = +"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\nattribut" +"e vec3 attr_Normal;\r\n\r\nuniform vec4 u_DlightInfo;\r\n\r\nuniform int " +" u_DeformGen;\r\nuniform float u_DeformParams[5];\r\n\r\nuniform float " +"u_Time;\r\nuniform vec4 u_Color;\r\nuniform mat4 u_ModelViewProjectionM" +"atrix;\r\n\r\nvarying vec2 var_Tex1;\r\nvarying vec4 var_Color;\r\n\r\n" +"float triangle(float x)\r\n{\r\n\treturn max(1.0 - abs(x), 0);\r\n}\r\n\r\n" +"float sawtooth(float x)\r\n{\r\n\treturn x - floor(x);\r\n}\r\n\r\nvec4 Def" +"ormPosition(const vec4 pos, const vec3 normal, const vec2 st)\r\n{\r\n\tif " +"(u_DeformGen == 0)\r\n\t{\r\n\t\treturn pos;\r\n\t}\r\n\r\n\tfloat base = " +" u_DeformParams[0];\r\n\tfloat amplitude = u_DeformParams[1];\r\n\tfloat" +" phase = u_DeformParams[2];\r\n\tfloat frequency = u_DeformParams[3];\r" +"\n\tfloat spread = u_DeformParams[4];\r\n\t\t\r\n\tif (u_DeformGen <= DG" +"EN_WAVE_INVERSE_SAWTOOTH)\r\n\t{\r\n\t\tphase += (pos.x + pos.y + pos.z) * " +"spread;\r\n\t}\r\n\telse if (u_DeformGen == DGEN_BULGE)\r\n\t{\r\n\t\tphase" +" *= M_PI * 0.25 * st.x;\r\n\t}\r\n\r\n\tfloat value = phase + (u_Time * fre" +"quency);\r\n\tfloat func;\r\n\r\n\tif (u_DeformGen == DGEN_WAVE_SIN)\r\n\t{" +"\r\n\t\tfunc = sin(value * 2.0 * M_PI);\r\n\t}\r\n\telse if (u_DeformGen ==" +" DGEN_WAVE_SQUARE)\r\n\t{\r\n\t\tfunc = sign(sin(value * 2.0 * M_PI));\r\n" +"\t}\r\n\telse if (u_DeformGen == DGEN_WAVE_TRIANGLE)\r\n\t{\r\n\t\tfunc = t" +"riangle(value);\r\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE_SAWTOOTH)\r\n" +"\t{\r\n\t\tfunc = sawtooth(value);\r\n\t}\r\n\telse if (u_DeformGen == DGEN" +"_WAVE_INVERSE_SAWTOOTH)\r\n\t{\r\n\t\tfunc = (1.0 - sawtooth(value));\r\n\t" +"}\r\n\telse if (u_DeformGen == DGEN_BULGE)\r\n\t{\r\n\t\tfunc = sin(value);" +"\r\n\t}\r\n\r\n\tvec4 deformed = pos;\r\n\tdeformed.xyz += normal * (base +" +" func * amplitude);\r\n\r\n\treturn deformed;\r\n\r\n}\r\n\r\nvoid main()\r" +"\n{\r\n\tvec4 position = attr_Position;\r\n\tvec3 normal = attr_Normal;\r\n" +"\r\n\tposition = DeformPosition(position, normal, attr_TexCoord0.st);\r\n\r" +"\n\tgl_Position = u_ModelViewProjectionMatrix * position;\r\n\t\t\r\n\tvec3" +" dist = u_DlightInfo.xyz - position.xyz;\t\r\n\r\n\tfloat diffz = abs(dist." +"z);\r\n\tfloat radius = 1.0 / u_DlightInfo.a;\r\n\r\n\tvec2 tex = vec2(0.5)" +" + dist.xy * u_DlightInfo.a;\r\n\tfloat dlightmod = max(sign(dot(dist, norm" +"al)), 0.0);\r\n\tdlightmod *= clamp(2.0 * (radius - diffz) * u_DlightInfo.a" +", 0.0, 1.0);\r\n\r\n\tvar_Tex1 = tex;\r\n\tvar_Color = u_Color;\r\n\tvar_Co" +"lor.rgb *= dlightmod;\r\n}\r\n"; + +static const char *fallbackDlightShader_fp = +"uniform sampler2D u_DiffuseMap;\r\n\r\nvarying vec2 var_Tex1;\r\nvaryi" +"ng vec4 var_Color;\r\n\r\n\r\nvoid main()\r\n{\r\n\tvec4 color = textu" +"re2D(u_DiffuseMap, var_Tex1);\r\n\r\n\tgl_FragColor = color * var_Color;\r" +"\n}\r\n"; + +static const char *fallbackLightallShader_vp = +"attribute vec4 attr_TexCoord0;\r\n#if defined(USE_LIGHTMAP)\r\nattribute ve" +"c4 attr_TexCoord1;\r\n#endif\r\nattribute vec4 attr_Color;\r\n\r\nattribute" +" vec4 attr_Position;\r\nattribute vec3 attr_Normal;\r\n\r\n#if defined(USE_" +"VERT_TANGENT_SPACE)\r\nattribute vec3 attr_Tangent;\r\nattribute vec3 attr_" +"Bitangent;\r\n#endif\r\n\r\n#if defined(USE_VERTEX_ANIMATION)\r\nattribute " +"vec4 attr_Position2;\r\nattribute vec3 attr_Normal2;\r\n #if defined(USE_V" +"ERT_TANGENT_SPACE)\r\nattribute vec3 attr_Tangent2;\r\nattribute vec3 attr_" +"Bitangent2;\r\n #endif\r\n#endif\r\n\r\n#if defined(USE_LIGHT) && !defined" +"(USE_LIGHT_VECTOR)\r\nattribute vec3 attr_LightDirection;\r\n#endif\r\n\r\n" +"#if defined(TCGEN_ENVIRONMENT) || defined(USE_NORMALMAP) || defined(USE_LIG" +"HT) && !defined(USE_FAST_LIGHT)\r\nuniform vec3 u_ViewOrigin;\r\n#endif\r" +"\n\r\nuniform mat4 u_DiffuseTexMatrix;\r\nuniform mat4 u_ModelViewProje" +"ctionMatrix;\r\nuniform vec4 u_BaseColor;\r\nuniform vec4 u_VertColor;" +"\r\n\r\n#if defined(USE_MODELMATRIX)\r\nuniform mat4 u_ModelMatrix;\r\n#e" +"ndif\r\n\r\n#if defined(USE_VERTEX_ANIMATION)\r\nuniform float u_VertexLer" +"p;\r\n#endif\r\n\r\n#if defined(USE_LIGHT_VECTOR)\r\nuniform vec4 u_Light" +"Origin;\r\n #if defined(USE_FAST_LIGHT)\r\nuniform vec3 u_DirectedLight;" +"\r\nuniform vec3 u_AmbientLight;\r\nuniform float u_LightRadius;\r\n #e" +"ndif\r\n#endif\r\n\r\nvarying vec2 var_DiffuseTex;\r\n\r\n#if defined(USE" +"_LIGHTMAP)\r\nvarying vec2 var_LightTex;\r\n#endif\r\n\r\n#if defined(USE" +"_NORMALMAP) || defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)\r\nvarying ve" +"c3 var_SampleToView;\r\n#endif\r\n\r\nvarying vec4 var_Color;\r\nvaryin" +"g vec3 var_Position;\r\nvarying vec3 var_Normal;\r\n\r\n#if defined(USE" +"_VERT_TANGENT_SPACE)\r\nvarying vec3 var_Tangent;\r\nvarying vec3 var_B" +"itangent;\r\n#endif\r\n\r\nvarying vec3 var_VertLight;\r\n\r\n#if defined" +"(USE_LIGHT) && !defined(USE_DELUXEMAP)\r\nvarying vec3 var_WorldLight;\r" +"\n#endif\r\n\r\nvarying vec4 var_ScreenPos;\r\n\r\nvec2 DoTexMatrix(vec2 " +"st, vec3 position, mat4 texMatrix)\r\n{\r\n\tvec2 st2 = (texMatrix * vec4(s" +"t, 1, 0)).st;\r\n\r\n\tvec3 offsetPos = position.xyz / 1024.0;\r\n\toffsetP" +"os.x += offsetPos.z;\r\n\r\n\tvec2 texOffset = sin((offsetPos.xy + vec2(tex" +"Matrix[3][1])) * 2.0 * M_PI);\r\n\t\r\n\treturn st2 + texOffset * texMatrix" +"[3][0];\r\n}\r\n\r\nvoid main()\r\n{\r\n#if defined(USE_VERTEX_ANIMATION)\r" +"\n\tvec4 position = mix(attr_Position, attr_Position2, u_VertexLerp);\r\n" +"\tvec3 normal = normalize(mix(attr_Normal, attr_Normal2, u_VertexL" +"erp));\r\n #if defined(USE_VERT_TANGENT_SPACE)\r\n\tvec3 tangent = norma" +"lize(mix(attr_Tangent, attr_Tangent2, u_VertexLerp));\r\n\tvec3 bitange" +"nt = normalize(mix(attr_Bitangent, attr_Bitangent2, u_VertexLerp));\r\n #e" +"ndif\r\n#else\r\n\tvec4 position = attr_Position;\r\n\tvec3 normal = at" +"tr_Normal;\r\n #if defined(USE_VERT_TANGENT_SPACE)\r\n\tvec3 tangent = a" +"ttr_Tangent;\r\n\tvec3 bitangent = attr_Bitangent;\r\n #endif\r\n#endif\r" +"\n\r\n\tgl_Position = u_ModelViewProjectionMatrix * position;\r\n\tvar_Scre" +"enPos = gl_Position;\r\n\r\n#if (defined(USE_LIGHTMAP) || defined(USE_LIGHT" +"_VERTEX)) && !defined(USE_DELUXEMAP)\r\n\tvec3 worldLight = attr_LightDirec" +"tion;\r\n#endif\r\n\t\r\n#if defined(USE_MODELMATRIX)\r\n\tposition = u_Mo" +"delMatrix * position;\r\n\tnormal = (u_ModelMatrix * vec4(normal, 0.0))." +"xyz;\r\n #if defined(USE_VERT_TANGENT_SPACE)\r\n\ttangent = (u_ModelMatr" +"ix * vec4(tangent, 0.0)).xyz;\r\n\tbitangent = (u_ModelMatrix * vec4(bitang" +"ent, 0.0)).xyz;\r\n #endif\r\n\r\n #if defined(USE_LIGHTMAP) && !defined(" +"USE_DELUXEMAP)\r\n\tworldLight = (u_ModelMatrix * vec4(worldLight, 0.0)).xy" +"z;\r\n #endif\r\n#endif\r\n\r\n\tvar_Position = position.xyz;\r\n\r\n#if d" +"efined(TCGEN_ENVIRONMENT) || defined(USE_NORMALMAP) || defined(USE_LIGHT) &" +"& !defined(USE_FAST_LIGHT)\r\n\tvec3 SampleToView = u_ViewOrigin - position" +".xyz;\r\n#endif\r\n\r\n#if defined(USE_NORMALMAP) || defined(USE_LIGHT) && " +"!defined(USE_FAST_LIGHT)\r\n\tvar_SampleToView = SampleToView;\r\n#endif\r" +"\n\r\n#if defined(TCGEN_ENVIRONMENT)\r\n\tvec3 viewer = normalize(SampleToV" +"iew);\r\n\tvec3 reflected = normal * 2.0 * dot(normal, viewer) - viewer;\r" +"\n\r\n\tvec2 tex = reflected.yz * vec2(0.5, -0.5) + 0.5;\r\n#else\r\n\tvec2" +" tex = attr_TexCoord0.st;\r\n#endif\r\n\r\n\tvar_DiffuseTex = DoTexMatrix(t" +"ex, position.xyz, u_DiffuseTexMatrix);\r\n\r\n#if defined(USE_LIGHTMAP)\r\n" +"\tvar_LightTex = attr_TexCoord1.st;\r\n#endif\r\n \r\n\tvar_Normal = norma" +"l;\r\n#if defined(USE_VERT_TANGENT_SPACE)\r\n\tvar_Tangent = tangent;\r\n\t" +"var_Bitangent = bitangent;\r\n#endif\r\n\r\n#if defined(USE_LIGHT) && !defi" +"ned(USE_DELUXEMAP)\r\n #if defined(USE_LIGHT_VECTOR)\r\n\tvec3 worldLight " +"= u_LightOrigin.xyz - (position.xyz * u_LightOrigin.w);\r\n #endif\r\n\r\n" +"\tworldLight += normal * 0.0001;\r\n\tvar_WorldLight = worldLight;\r\n#endi" +"f\r\n\t\r\n#if defined(USE_LIGHT_VERTEX)\r\n var_VertLight = attr_Color." +"rgb;\r\n #if !defined(USE_FAST_LIGHT)\r\n\tvar_VertLight /= max(dot(normal" +", normalize(worldLight)), 0.004);\r\n #endif\r\n\tvar_Color.rgb = u_BaseCo" +"lor.rgb;\r\n\tvar_Color.a = u_VertColor.a * attr_Color.a + u_BaseColor.a;\r" +"\n#else\r\n\tvar_Color = u_VertColor * attr_Color + u_BaseColor;\r\n#endif" +"\r\n\r\n#if defined(USE_LIGHT_VECTOR) && defined(USE_FAST_LIGHT)\r\n #if d" +"efined(USE_INVSQRLIGHT)\r\n\tfloat intensity = 1.0 / dot(worldLight, worldL" +"ight);\r\n #else\r\n\tfloat intensity = clamp((1.0 - dot(worldLight, world" +"Light) / (u_LightRadius * u_LightRadius)) * 1.07, 0.0, 1.0);\r\n #endif\r" +"\n\tfloat NL = clamp(dot(normal, normalize(worldLight)), 0.0, 1.0);\r\n\r\n" +"\tvar_VertLight = u_DirectedLight * intensity * NL + u_AmbientLight;\r\n#en" +"dif\r\n}\r\n"; + +static const char *fallbackLightallShader_fp = +"uniform sampler2D u_DiffuseMap;\r\n\r\n#if defined(USE_LIGHTMAP)\r\nuniform" +" sampler2D u_LightMap;\r\n#endif\r\n\r\n#if defined(USE_NORMALMAP)\r\nunifo" +"rm sampler2D u_NormalMap;\r\n#endif\r\n\r\n#if defined(USE_DELUXEMAP)\r\nun" +"iform sampler2D u_DeluxeMap;\r\n#endif\r\n\r\n#if defined(USE_SPECULARMAP)" +"\r\nuniform sampler2D u_SpecularMap;\r\n#endif\r\n\r\n#if defined(USE_SHADO" +"WMAP)\r\nuniform sampler2D u_ShadowMap;\r\n#endif\r\n\r\nuniform vec3 " +"u_ViewOrigin;\r\n\r\n#if defined(USE_LIGHT_VECTOR)\r\nuniform vec3 u_D" +"irectedLight;\r\nuniform vec3 u_AmbientLight;\r\nuniform float u_L" +"ightRadius;\r\n#endif\r\n\r\n#if defined(USE_LIGHT)\r\nuniform vec2 u_" +"MaterialInfo;\r\n#endif\r\n\r\nvarying vec2 var_DiffuseTex;\r\n#if def" +"ined(USE_LIGHTMAP)\r\nvarying vec2 var_LightTex;\r\n#endif\r\nvarying " +"vec4 var_Color;\r\nvarying vec3 var_Position;\r\n\r\nvarying vec3" +" var_SampleToView;\r\n\r\nvarying vec3 var_Normal;\r\n#if defined" +"(USE_VERT_TANGENT_SPACE)\r\nvarying vec3 var_Tangent;\r\nvarying vec3 " +" var_Bitangent;\r\n#endif\r\n\r\nvarying vec3 var_VertLight;\r\n\r" +"\n#if defined(USE_LIGHT) && !defined(USE_DELUXEMAP)\r\nvarying vec3 va" +"r_WorldLight;\r\n#endif\r\n\r\nvarying vec4 var_ScreenPos;\r\n\r\n#define" +" EPSILON 0.00000001\r\n\r\n#if defined(USE_PARALLAXMAP)\r\nfloat SampleHeig" +"ht(sampler2D normalMap, vec2 t)\r\n{\r\n #if defined(SWIZZLE_NORMALMAP)\r" +"\n\treturn texture2D(normalMap, t).r;\r\n #else\r\n\treturn texture2D(norm" +"alMap, t).a;\r\n #endif\r\n}\r\n\r\nfloat RayIntersectDisplaceMap(vec2 dp," +" vec2 ds, sampler2D normalMap)\r\n{\r\n\tconst int linearSearchSteps = 16;" +"\r\n\tconst int binarySearchSteps = 6;\r\n\r\n\tfloat depthStep = 1.0 / flo" +"at(linearSearchSteps);\r\n\r\n\t// current size of search window\r\n\tfloat" +" size = depthStep;\r\n\r\n\t// current depth position\r\n\tfloat depth = 0." +"0;\r\n\r\n\t// best match found (starts with last position 1.0)\r\n\tfloat " +"bestDepth = 1.0;\r\n\r\n\t// search front to back for first point inside ob" +"ject\r\n\tfor(int i = 0; i < linearSearchSteps - 1; ++i)\r\n\t{\r\n\t\tdept" +"h += size;\r\n\t\t\r\n\t\tfloat t = 1.0 - SampleHeight(normalMap, dp + ds *" +" depth);\r\n\t\t\r\n\t\tif(bestDepth > 0.996)\t\t// if no depth found yet\r" +"\n\t\t\tif(depth >= t)\r\n\t\t\t\tbestDepth = depth;\t// store best depth\r" +"\n\t}\r\n\r\n\tdepth = bestDepth;\r\n\t\r\n\t// recurse around first point " +"(depth) for closest match\r\n\tfor(int i = 0; i < binarySearchSteps; ++i)\r" +"\n\t{\r\n\t\tsize *= 0.5;\r\n\r\n\t\tfloat t = 1.0 - SampleHeight(normalMap" +", dp + ds * depth);\r\n\t\t\r\n\t\tif(depth >= t)\r\n\t\t{\r\n\t\t\tbestDep" +"th = depth;\r\n\t\t\tdepth -= 2.0 * size;\r\n\t\t}\r\n\r\n\t\tdepth += size" +";\r\n\t}\r\n\r\n\treturn bestDepth;\r\n}\r\n#endif\r\n\r\nfloat CalcDiffuse" +"(vec3 N, vec3 L, vec3 E, float NE, float NL, float fzero, float shininess)" +"\r\n{\r\n #if defined(USE_OREN_NAYAR) || defined(USE_TRIACE_OREN_NAYAR)\r" +"\n\tfloat gamma = dot(E, L) - NE * NL;\r\n\tfloat B = 2.22222 + 0.1 * shini" +"ness;\r\n\t\t\r\n\t#if defined(USE_OREN_NAYAR)\r\n\tfloat A = 1.0 - 1.0 / (" +"2.0 + 0.33 * shininess);\r\n\tgamma = clamp(gamma, 0.0, 1.0);\r\n\t#endif\r" +"\n\t\r\n\t#if defined(USE_TRIACE_OREN_NAYAR)\r\n\tfloat A = 1.0 - 1.0 / (2." +"0 + 0.65 * shininess);\r\n\r\n\tif (gamma >= 0.0)\r\n\t#endif\r\n\t{\r\n\t" +"\tB *= max(max(NL, NE), EPSILON);\r\n\t}\r\n\r\n\treturn A + gamma / B;\r\n" +" #else\r\n\treturn 1.0 - fzero;\r\n #endif\r\n}\r\n\r\n#if defined(USE_SP" +"ECULARMAP)\r\nfloat CalcSpecular(float NH, float NL, float NE, float EH, fl" +"oat fzero, float shininess)\r\n{\r\n #if defined(USE_BLINN) || defined(USE" +"_TRIACE) || defined(USE_TORRANCE_SPARROW)\r\n\tfloat blinn = pow(NH, shinin" +"ess);\r\n #endif\r\n\r\n #if defined(USE_BLINN)\r\n\treturn blinn;\r\n #" +"endif\r\n\r\n #if defined(USE_COOK_TORRANCE) || defined (USE_TRIACE) || de" +"fined (USE_TORRANCE_SPARROW)\r\n\tfloat fresnel = fzero + (1.0 - fzero) * p" +"ow(1.0 - EH, 5);\r\n #endif\r\n\r\n #if defined(USE_COOK_TORRANCE) || def" +"ined(USE_TORRANCE_SPARROW)\r\n\tfloat geo = 2.0 * NH * min(NE, NL);\r\n\tge" +"o /= max(EH, geo);\r\n #endif \r\n\r\n #if defined(USE_COOK_TORRANCE)\r" +"\n\tfloat m = sqrt(2.0 / max(shininess, EPSILON));\r\n\r\n\tfloat m_sq = m " +"* m;\r\n\tfloat NH_sq = NH * NH;\r\n\tfloat beckmann = exp((NH_sq - 1.0) / " +"max(m_sq * NH_sq, EPSILON)) / max(4.0 * m_sq * NH_sq * NH_sq, EPSILON);\r\n" +"\r\n\treturn fresnel * geo * beckmann / max(NE, EPSILON);\r\n #endif\r\n\r" +"\n #if defined(USE_TRIACE)\r\n\tfloat scale = 0.1248582 * shininess + 0.26" +"91817;\r\n\r\n\treturn fresnel * scale * blinn / max(max(NL, NE), EPSILON);" +"\r\n #endif\r\n \r\n #if defined(USE_TORRANCE_SPARROW)\r\n\tfloat scale " +"= 0.125 * shininess + 1.0;\r\n\r\n\treturn fresnel * geo * scale * blinn / " +"max(NE, EPSILON);\r\n #endif\r\n}\r\n#endif\r\n\r\nvoid main()\r\n{\r\n#if" +" defined(USE_LIGHT) || defined(USE_NORMALMAP)\r\n\tvec3 surfNormal = normal" +"ize(var_Normal);\r\n#endif\r\n\r\n#if defined(USE_DELUXEMAP)\r\n\tvec3 worl" +"dLight = 2.0 * texture2D(u_DeluxeMap, var_LightTex).xyz - vec3(1.0);\r\n\t/" +"/worldLight += var_WorldLight * 0.0001;\r\n#elif defined(USE_LIGHT)\r\n\tve" +"c3 worldLight = var_WorldLight;\r\n#endif\r\n\r\n#if defined(USE_LIGHTMAP)" +"\r\n\tvec4 lightSample = texture2D(u_LightMap, var_LightTex).rgba;\r\n #if" +" defined(RGBE_LIGHTMAP)\r\n\tlightSample.rgb *= exp2(lightSample.a * 255.0 " +"- 128.0);\r\n #endif\r\n\tvec3 directedLight = lightSample.rgb;\r\n#elif d" +"efined(USE_LIGHT_VECTOR)\r\n #if defined(USE_FAST_LIGHT)\r\n\tvec3 directe" +"dLight = var_VertLight;\r\n #else\r\n #if defined(USE_INVSQRLIGHT)\r\n" +"\tfloat intensity = 1.0 / dot(worldLight, worldLight);\r\n #else\r\n\tfl" +"oat intensity = clamp((1.0 - dot(worldLight, worldLight) / (u_LightRadius *" +" u_LightRadius)) * 1.07, 0.0, 1.0);\r\n #endif\r\n\r\n\tvec3 directedLig" +"ht = u_DirectedLight * intensity;\r\n\tvec3 ambientLight = u_AmbientLight;" +"\r\n #endif\r\n\r\n #if defined(USE_SHADOWMAP)\r\n\tvec2 shadowTex = var_" +"ScreenPos.xy / var_ScreenPos.w * 0.5 + 0.5;\r\n\tdirectedLight *= texture2D" +"(u_ShadowMap, shadowTex).r;\r\n #endif\r\n#elif defined(USE_LIGHT_VERTEX)" +"\r\n\tvec3 directedLight = var_VertLight;\r\n#endif\r\n\t\r\n#if defined(TC" +"GEN_ENVIRONMENT) || defined(USE_NORMALMAP) || (defined(USE_LIGHT) && !defin" +"ed(USE_FAST_LIGHT))\r\n\tvec3 SampleToView = normalize(var_SampleToView);\r" +"\n#endif\r\n\tvec2 tex = var_DiffuseTex;\r\n\r\n\tfloat ambientDiff = 1.0;" +"\r\n\r\n#if defined(USE_NORMALMAP)\r\n #if defined(USE_VERT_TANGENT_SPACE)" +"\r\n vec3 tangent = var_Tangent;\r\n\tvec3 bitangent = var_Bitangent;" +"\r\n #else\r\n\tvec3 q0 = dFdx(var_Position);\r\n\tvec3 q1 = dFdy(var_Po" +"sition);\r\n\tvec2 st0 = dFdx(tex);\r\n\tvec2 st1 = dFdy(tex);\r\n\tfloat d" +"ir = sign(st1.t * st0.s - st0.t * st1.s);\r\n\r\n\tvec3 tangent = normali" +"ze( q0 * st1.t - q1 * st0.t) * dir;\r\n\tvec3 bitangent = -normalize( q0 * " +"st1.s - q1 * st0.s) * dir;\r\n #endif\r\n\r\n\tmat3 tangentToWorld = mat3(" +"tangent, bitangent, var_Normal);\r\n\r\n #if defined(USE_PARALLAXMAP)\r\n" +"\tvec3 offsetDir = normalize(SampleToView * tangentToWorld);\r\n #if 0\r" +"\n float height = SampleHeight(u_NormalMap, tex);\r\n\tfloat pdist = 0.0" +"5 * height - (0.05 / 2.0);\r\n #else\r\n\toffsetDir.xy *= -0.05 / offset" +"Dir.z;\r\n\tfloat pdist = RayIntersectDisplaceMap(tex, offsetDir.xy, u_Norm" +"alMap);\r\n #endif\t\r\n\ttex += offsetDir.xy * pdist;\r\n #endif\r\n " +"#if defined(SWIZZLE_NORMALMAP)\r\n\tvec3 normal = 2.0 * texture2D(u_NormalM" +"ap, tex).agb - 1.0;\r\n #else\r\n\tvec3 normal = 2.0 * texture2D(u_NormalM" +"ap, tex).rgb - 1.0;\r\n #endif\r\n\tnormal.z = sqrt(clamp(1.0 - dot(normal" +".xy, normal.xy), 0.0, 1.0));\r\n\tvec3 worldNormal = tangentToWorld * norma" +"l;\r\n #if defined(r_normalAmbient)\r\n\tambientDiff = 0.781341 * normal.z" +" + 0.218659;\r\n #endif\r\n#elif defined(USE_LIGHT)\r\n\tvec3 worldNormal " +"= surfNormal;\r\n#endif\r\n\r\n#if (defined(USE_LIGHT) && !defined(USE_FAST" +"_LIGHT)) || (defined(TCGEN_ENVIRONMENT) && defined(USE_NORMALMAP))\r\n\twor" +"ldNormal = normalize(worldNormal);\r\n#endif\r\n\r\n#if defined(TCGEN_ENVIR" +"ONMENT) && defined(USE_NORMALMAP)\r\n\tvec3 reflected = worldNormal * 2.0 *" +" dot(worldNormal, SampleToView) - SampleToView;\r\n\r\n\ttex = reflected.yz" +" * vec2(0.5, -0.5) + 0.5;\r\n#endif\r\n\r\n\tvec4 diffuse = texture2D(u_Dif" +"fuseMap, tex);\r\n\r\n#if defined(USE_LIGHT) && defined(USE_FAST_LIGHT)\r\n" +"\tdiffuse.rgb *= directedLight;\r\n#elif defined(USE_LIGHT)\r\n\tworldLight" +" = normalize(worldLight);\r\n\r\n #if defined(USE_LIGHTMAP)\r\n\tdirectedL" +"ight /= max(dot(surfNormal, worldLight), 0.004);\r\n #endif\r\n\r\n #if d" +"efined(USE_LIGHTMAP) || defined(USE_LIGHT_VERTEX)\r\n\t#if defined(r_normal" +"Ambient)\r\n\tvec3 ambientLight = directedLight * r_normalAmbient;\r\n\tdir" +"ectedLight -= ambientLight;\r\n #else\r\n\tvec3 ambientLight = vec3(0);" +"\r\n #endif\r\n #endif\r\n\r\n\tfloat NL = clamp(dot(worldNormal, worl" +"dLight), 0.0, 1.0);\r\n\tfloat surfNL = clamp(dot(surfNormal, worldLight" +"), 0.0, 1.0);\r\n\tNL = min(NL, surfNL * 2.0);\r\n\tfloat NE = clamp(dot(" +"worldNormal, SampleToView), 0.0, 1.0);\r\n\t\r\n\tfloat fzero = u_Material" +"Info.x;\r\n\tfloat shininess = u_MaterialInfo.y;\r\n #if defined(USE_SPECU" +"LARMAP)\r\n\tvec4 specular = texture2D(u_SpecularMap, tex);\r\n\t//specular" +".rgb = clamp(specular.rgb - diffuse.rgb, 0.0, 1.0);\r\n\tshininess *= specu" +"lar.a;\r\n #endif\r\n\tfloat directedDiff = NL * CalcDiffuse(worldNormal, " +"worldLight, SampleToView, NE, NL, fzero, shininess);\r\n\tdiffuse.rgb *= di" +"rectedLight * directedDiff + ambientDiff * ambientLight;\r\n \r\n #if def" +"ined(USE_SPECULARMAP)\r\n\tvec3 halfAngle = normalize(worldLight + SampleTo" +"View);\r\n\r\n\tfloat EH = clamp(dot(SampleToView, halfAngle), 0.0, 1.0);\r" +"\n\tfloat NH = clamp(dot(worldNormal, halfAngle), 0.0, 1.0);\r\n\r\n\tfloa" +"t directedSpec = NL * CalcSpecular(NH, NL, NE, EH, fzero, shininess);\r\n " +"\r\n #if defined(r_normalAmbient)\r\n\tvec3 ambientHalf = normalize(surf" +"Normal + SampleToView);\r\n\tfloat ambientSpec = max(dot(ambientHalf, world" +"Normal) + 0.5, 0.0);\r\n\tambientSpec *= ambientSpec * 0.44;\r\n\tambientSp" +"ec = pow(ambientSpec, shininess) * fzero;\r\n\tspecular.rgb *= directedSpec" +" * directedLight + ambientSpec * ambientLight;\r\n #else\r\n\tspecular.r" +"gb *= directedSpec * directedLight;\r\n #endif\r\n #endif\r\n#endif\r\n" +"\r\n\tgl_FragColor = diffuse;\r\n\r\n#if defined(USE_SPECULARMAP) && define" +"d(USE_LIGHT) && !defined(USE_FAST_LIGHT)\r\n\tgl_FragColor.rgb += specular." +"rgb;\r\n#endif\r\n\r\n\tgl_FragColor *= var_Color;\r\n}\r\n"; + +static const char *fallbackShadowfillShader_vp = +"attribute vec4 attr_Position;\r\nattribute vec3 attr_Normal;\r\nattribute" +" vec4 attr_TexCoord0;\r\n\r\n//#if defined(USE_VERTEX_ANIMATION)\r\nattrib" +"ute vec4 attr_Position2;\r\nattribute vec3 attr_Normal2;\r\n//#endif\r\n" +"\r\n//#if defined(USE_DEFORM_VERTEXES)\r\nuniform int u_DeformGen;\r\nu" +"niform float u_DeformParams[5];\r\n//#endif\r\n\r\nuniform float u_Tim" +"e;\r\nuniform mat4 u_ModelViewProjectionMatrix;\r\n\r\nuniform mat4 u_" +"ModelMatrix;\r\n\r\n//#if defined(USE_VERTEX_ANIMATION)\r\nuniform float " +"u_VertexLerp;\r\n//#endif\r\n\r\nvarying vec3 var_Position;\r\n\r\nfloat" +" triangle(float x)\r\n{\r\n\treturn max(1.0 - abs(x), 0);\r\n}\r\n\r\nfloat" +" sawtooth(float x)\r\n{\r\n\treturn x - floor(x);\r\n}\r\n\r\nvec4 DeformPo" +"sition(const vec4 pos, const vec3 normal, const vec2 st)\r\n{\r\n\tif (u_De" +"formGen == 0)\r\n\t{\r\n\t\treturn pos;\r\n\t}\r\n\r\n\tfloat base = u" +"_DeformParams[0];\r\n\tfloat amplitude = u_DeformParams[1];\r\n\tfloat phas" +"e = u_DeformParams[2];\r\n\tfloat frequency = u_DeformParams[3];\r\n\tf" +"loat spread = u_DeformParams[4];\r\n\t\t\r\n\tif (u_DeformGen <= DGEN_WA" +"VE_INVERSE_SAWTOOTH)\r\n\t{\r\n\t\tphase += (pos.x + pos.y + pos.z) * sprea" +"d;\r\n\t}\r\n\telse if (u_DeformGen == DGEN_BULGE)\r\n\t{\r\n\t\tphase *= M" +"_PI * 0.25 * st.x;\r\n\t}\r\n\r\n\tfloat value = phase + (u_Time * frequenc" +"y);\r\n\tfloat func;\r\n\r\n\tif (u_DeformGen == DGEN_WAVE_SIN)\r\n\t{\r\n" +"\t\tfunc = sin(value * 2.0 * M_PI);\r\n\t}\r\n\telse if (u_DeformGen == DGE" +"N_WAVE_SQUARE)\r\n\t{\r\n\t\tfunc = sign(sin(value * 2.0 * M_PI));\r\n\t}\r" +"\n\telse if (u_DeformGen == DGEN_WAVE_TRIANGLE)\r\n\t{\r\n\t\tfunc = triang" +"le(value);\r\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE_SAWTOOTH)\r\n\t{\r" +"\n\t\tfunc = sawtooth(value);\r\n\t}\r\n\telse if (u_DeformGen == DGEN_WAVE" +"_INVERSE_SAWTOOTH)\r\n\t{\r\n\t\tfunc = (1.0 - sawtooth(value));\r\n\t}\r\n" +"\telse if (u_DeformGen == DGEN_BULGE)\r\n\t{\r\n\t\tfunc = sin(value);\r\n" +"\t}\r\n\r\n\tvec4 deformed = pos;\r\n\tdeformed.xyz += normal * (base + fun" +"c * amplitude);\r\n\r\n\treturn deformed;\r\n\r\n}\r\n\r\n\r\nvoid main()\r" +"\n{\r\n\tvec4 position = mix(attr_Position, attr_Position2, u_VertexLerp);" +"\r\n\tvec3 normal = normalize(mix(attr_Normal, attr_Normal2, u_VertexLerp))" +";\r\n\r\n\tposition = DeformPosition(position, normal, attr_TexCoord0.st);" +"\r\n\r\n\tgl_Position = u_ModelViewProjectionMatrix * position;\r\n\t\r\n\t" +"var_Position = (u_ModelMatrix * position).xyz;\r\n}\r\n"; + +static const char *fallbackShadowfillShader_fp = +"uniform vec4 u_LightOrigin;\r\nuniform float u_LightRadius;\r\n\r\nvarying" +" vec3 var_Position;\r\n\r\nvoid main()\r\n{\r\n#if defined(USE_DEPTH)\r\n" +"\tfloat depth = length(u_LightOrigin.xyz - var_Position) / u_LightRadius;\r" +"\n #if 0\r\n\t// 32 bit precision\r\n\tconst vec4 bitSh = vec4( 256 * 256 *" +" 256, 256 * 256, 256, 1);\r\n\tconst vec4 bitMsk = vec4" +"( 0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);\r\n\t\r\n\tvec4 c" +"omp;\r\n\tcomp = depth * bitSh;\r\n\tcomp.xyz = fract(comp.xyz);\r\n\tcomp " +"-= comp.xxyz * bitMsk;\r\n\tgl_FragColor = comp;\r\n #endif\r\n\r\n #if 1\r" +"\n\t// 24 bit precision\r\n\tconst vec3 bitSh = vec3( 256 * 256, 25" +"6, 1);\r\n\tconst vec3 bitMsk = vec3( 0, 1.0 / 256.0, 1.0 " +"/ 256.0);\r\n\t\r\n\tvec3 comp;\r\n\tcomp = depth * bitSh;\r\n\tcomp.xy = f" +"ract(comp.xy);\r\n\tcomp -= comp.xxy * bitMsk;\r\n\tgl_FragColor = vec4(com" +"p, 1.0);\r\n #endif\r\n\r\n #if 0\r\n\t// 8 bit precision\r\n\tgl_FragColor" +" = vec4(depth, depth, depth, 1);\r\n #endif\r\n#else\r\n\tgl_FragColor = ve" +"c4(0, 0, 0, 1);\r\n#endif\r\n}\r\n"; + +static const char *fallbackPshadowShader_vp = +"attribute vec4 attr_Position;\r\nattribute vec3 attr_Normal;\r\n\r\nuniform" +" mat4 u_ModelViewProjectionMatrix;\r\nvarying vec3 var_Position;\r\nvar" +"ying vec3 var_Normal;\r\n\r\n\r\nvoid main()\r\n{\r\n\tvec4 position = a" +"ttr_Position;\r\n\r\n\tgl_Position = u_ModelViewProjectionMatrix * position" +";\r\n\r\n\tvar_Position = position.xyz;\r\n\tvar_Normal = attr_Normal;" +"\r\n}\r\n"; + +static const char *fallbackPshadowShader_fp = +"uniform sampler2D u_ShadowMap;\r\n\r\nuniform vec3 u_LightForward;\r\n" +"uniform vec3 u_LightUp;\r\nuniform vec3 u_LightRight;\r\nuniform " +"vec4 u_LightOrigin;\r\nuniform float u_LightRadius;\r\nvarying vec" +"3 var_Position;\r\nvarying vec3 var_Normal;\r\n\r\nfloat sampleDi" +"stMap(sampler2D texMap, vec2 uv, float scale)\r\n{\r\n\tvec3 distv = textur" +"e2D(texMap, uv).xyz;\r\n\treturn dot(distv, vec3(1.0 / (256.0 * 256.0), 1.0" +" / 256.0, 1.0)) * scale;\r\n}\r\n\r\nvoid main()\r\n{\r\n\tvec3 lightToPos " +"= var_Position - u_LightOrigin.xyz;\r\n\tvec2 st = vec2(-dot(u_LightRight, " +"lightToPos), dot(u_LightUp, lightToPos));\r\n\t\r\n\tfloat fade = length(st" +");\r\n\t\r\n#if defined(USE_DISCARD)\r\n\tif (fade >= 1.0)\r\n\t{\r\n\t\tdi" +"scard;\r\n\t}\r\n#endif\r\n\r\n\tfade = clamp(8.0 - fade * 8.0, 0.0, 1.0);" +"\r\n\t\r\n\tst = st * 0.5 + vec2(0.5);\r\n\r\n#if defined(USE_SOLID_PSHADOW" +"S)\r\n\tfloat intensity = max(sign(u_LightRadius - length(lightToPos)), 0.0" +");\r\n#else\r\n\tfloat intensity = clamp((1.0 - dot(lightToPos, lightToPos)" +" / (u_LightRadius * u_LightRadius)) * 2.0, 0.0, 1.0);\r\n#endif\r\n\t\r\n\t" +"float lightDist = length(lightToPos);\r\n\tfloat dist;\r\n\r\n#if defined(U" +"SE_DISCARD)\r\n\tif (dot(u_LightForward, lightToPos) <= 0.0)\r\n\t{\r\n\t\t" +"discard;\r\n\t}\r\n\r\n\tif (dot(var_Normal, lightToPos) > 0.0)\r\n\t{\r\n" +"\t\tdiscard;\r\n\t}\r\n#else\r\n\tintensity *= max(sign(dot(u_LightForward," +" lightToPos)), 0.0);\r\n\tintensity *= max(sign(-dot(var_Normal, lightToPos" +")), 0.0);\r\n#endif\r\n\r\n\tintensity *= fade;\r\n#if defined(USE_PCF)\r\n" +"\tfloat part;\r\n\t\r\n\tdist = sampleDistMap(u_ShadowMap, st + vec2(-1.0/5" +"12.0, -1.0/512.0), u_LightRadius);\r\n\tpart = max(sign(lightDist - dist)," +" 0.0);\r\n\r\n\tdist = sampleDistMap(u_ShadowMap, st + vec2( 1.0/512.0, -1." +"0/512.0), u_LightRadius);\r\n\tpart += max(sign(lightDist - dist), 0.0);\r" +"\n\r\n\tdist = sampleDistMap(u_ShadowMap, st + vec2(-1.0/512.0, 1.0/512.0)" +", u_LightRadius);\r\n\tpart += max(sign(lightDist - dist), 0.0);\r\n\r\n\td" +"ist = sampleDistMap(u_ShadowMap, st + vec2( 1.0/512.0, 1.0/512.0), u_Light" +"Radius);\r\n\tpart += max(sign(lightDist - dist), 0.0);\r\n\r\n #if define" +"d(USE_DISCARD)\r\n\tif (part <= 0.0)\r\n\t{\r\n\t\tdiscard;\r\n\t}\r\n #en" +"dif\r\n\r\n\tintensity *= part * 0.25;\r\n#else\r\n\tdist = sampleDistMap(u" +"_ShadowMap, st, u_LightRadius);\r\n\r\n #if defined(USE_DISCARD)\r\n\tif (" +"lightDist - dist <= 0.0)\r\n\t{\r\n\t\tdiscard;\r\n\t}\r\n #endif\r\n\t\t" +"\t\r\n\tintensity *= max(sign(lightDist - dist), 0.0);\r\n#endif\r\n\t\t\r" +"\n\tgl_FragColor.rgb = vec3(0);\r\n\tgl_FragColor.a = clamp(intensity, 0.0," +" 0.75);\r\n}\r\n"; + +static const char *fallbackDown4xShader_vp = +"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\n\r\nunif" +"orm mat4 u_ModelViewProjectionMatrix;\r\n\r\nvarying vec2 var_TexCoords" +";\r\n\r\n\r\nvoid main()\r\n{\r\n\tgl_Position = u_ModelViewProjectionMatri" +"x * attr_Position;\r\n\tvar_TexCoords = attr_TexCoord0.st;\r\n}\r\n"; + +static const char *fallbackDown4xShader_fp = +"uniform sampler2D u_TextureMap;\r\n\r\nuniform vec2 u_InvTexRes;\r\nva" +"rying vec2 var_TexCoords;\r\n\r\nvoid main()\r\n{\r\n\tvec4 color;\r\n" +"\tvec2 tc;\r\n\t\r\n\ttc = var_TexCoords + u_InvTexRes * vec2(-1.5, -1.5); " +" color = texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRe" +"s * vec2(-0.5, -1.5); color += texture2D(u_TextureMap, tc);\r\n\ttc = var_" +"TexCoords + u_InvTexRes * vec2( 0.5, -1.5); color += texture2D(u_TextureMa" +"p, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( 1.5, -1.5); color +=" +" texture2D(u_TextureMap, tc);\r\n\r\n\ttc = var_TexCoords + u_InvTexRes * v" +"ec2(-1.5, -0.5); color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoo" +"rds + u_InvTexRes * vec2(-0.5, -0.5); color += texture2D(u_TextureMap, tc);" +"\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( 0.5, -0.5); color += texture" +"2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( 1.5, -0" +".5); color += texture2D(u_TextureMap, tc);\r\n\r\n\ttc = var_TexCoords + u_" +"InvTexRes * vec2(-1.5, 0.5); color += texture2D(u_TextureMap, tc);\r\n\ttc" +" = var_TexCoords + u_InvTexRes * vec2(-0.5, 0.5); color += texture2D(u_Tex" +"tureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( 0.5, 0.5); col" +"or += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * " +"vec2( 1.5, 0.5); color += texture2D(u_TextureMap, tc);\r\n\r\n\ttc = var_T" +"exCoords + u_InvTexRes * vec2(-1.5, 1.5); color += texture2D(u_TextureMap" +", tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2(-0.5, 1.5); color += " +"texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( " +"0.5, 1.5); color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords " +"+ u_InvTexRes * vec2( 1.5, 1.5); color += texture2D(u_TextureMap, tc);\r" +"\n\t\r\n\tcolor *= 0.0625;\r\n\t\r\n\tgl_FragColor = color;\r\n}\r\n"; + +static const char *fallbackBokehShader_vp = +"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\n\r\nunif" +"orm mat4 u_ModelViewProjectionMatrix;\r\n\r\nvarying vec2 var_TexCoords" +";\r\n\r\n\r\nvoid main()\r\n{\r\n\tgl_Position = u_ModelViewProjectionMatri" +"x * attr_Position;\r\n\tvar_TexCoords = attr_TexCoord0.st;\r\n}\r\n"; + +static const char *fallbackBokehShader_fp = +"uniform sampler2D u_TextureMap;\r\n\r\nuniform vec4 u_Color;\r\n\r\nun" +"iform vec2 u_InvTexRes;\r\nvarying vec2 var_TexCoords;\r\n\r\nvoi" +"d main()\r\n{\r\n\tvec4 color;\r\n\tvec2 tc;\r\n\r\n#if 0\r\n\tfloat c[7] =" +" float[7](1.0, 0.9659258263, 0.8660254038, 0.7071067812, 0.5, 0.2588190451," +" 0.0);\r\n\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( c[0], c[6]); co" +"lor = texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes *" +" vec2( c[1], c[5]); color += texture2D(u_TextureMap, tc);\r\n\ttc = var_" +"TexCoords + u_InvTexRes * vec2( c[2], c[4]); color += texture2D(u_Textur" +"eMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( c[3], c[3]); co" +"lor += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes *" +" vec2( c[4], c[2]); color += texture2D(u_TextureMap, tc);\r\n\ttc = var_" +"TexCoords + u_InvTexRes * vec2( c[5], c[1]); color += texture2D(u_Textur" +"eMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( c[6], c[0]); co" +"lor += texture2D(u_TextureMap, tc);\r\n\r\n\ttc = var_TexCoords + u_InvTexR" +"es * vec2( c[1], -c[5]); color += texture2D(u_TextureMap, tc);\r\n\ttc = " +"var_TexCoords + u_InvTexRes * vec2( c[2], -c[4]); color += texture2D(u_Te" +"xtureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( c[3], -c[3]);" +" color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexR" +"es * vec2( c[4], -c[2]); color += texture2D(u_TextureMap, tc);\r\n\ttc = " +"var_TexCoords + u_InvTexRes * vec2( c[5], -c[1]); color += texture2D(u_Te" +"xtureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( c[6], -c[0]);" +" color += texture2D(u_TextureMap, tc);\r\n\r\n\ttc = var_TexCoords + u_Inv" +"TexRes * vec2( -c[0], c[6]); color += texture2D(u_TextureMap, tc);\r\n\tt" +"c = var_TexCoords + u_InvTexRes * vec2( -c[1], c[5]); color += texture2D(" +"u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( -c[2], c[" +"4]); color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_Inv" +"TexRes * vec2( -c[3], c[3]); color += texture2D(u_TextureMap, tc);\r\n\tt" +"c = var_TexCoords + u_InvTexRes * vec2( -c[4], c[2]); color += texture2D(" +"u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( -c[5], c[" +"1]); color += texture2D(u_TextureMap, tc);\r\n\r\n\ttc = var_TexCoords + u" +"_InvTexRes * vec2( -c[1], -c[5]); color += texture2D(u_TextureMap, tc);\r" +"\n\ttc = var_TexCoords + u_InvTexRes * vec2( -c[2], -c[4]); color += textu" +"re2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( -c[3]" +", -c[3]); color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + " +"u_InvTexRes * vec2( -c[4], -c[2]); color += texture2D(u_TextureMap, tc);\r" +"\n\ttc = var_TexCoords + u_InvTexRes * vec2( -c[5], -c[1]); color += textu" +"re2D(u_TextureMap, tc);\r\n\t\r\n\tgl_FragColor = color * 0.04166667 * u_Co" +"lor;\r\n#endif\r\n\r\n\tfloat c[5] = float[5](1.0, 0.9238795325, 0.70710678" +"12, 0.3826834324, 0.0);\r\n\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( " +"c[0], c[4]); color = texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoord" +"s + u_InvTexRes * vec2( c[1], c[3]); color += texture2D(u_TextureMap, tc" +");\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( c[2], c[2]); color += t" +"exture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( " +"c[3], c[1]); color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoord" +"s + u_InvTexRes * vec2( c[4], c[0]); color += texture2D(u_TextureMap, tc" +");\r\n\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( c[1], -c[3]); color " +"+= texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec" +"2( c[2], -c[2]); color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexC" +"oords + u_InvTexRes * vec2( c[3], -c[1]); color += texture2D(u_TextureMap" +", tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( c[4], -c[0]); color " +"+= texture2D(u_TextureMap, tc);\r\n\r\n\ttc = var_TexCoords + u_InvTexRes *" +" vec2( -c[0], c[4]); color += texture2D(u_TextureMap, tc);\r\n\ttc = var_" +"TexCoords + u_InvTexRes * vec2( -c[1], c[3]); color += texture2D(u_Textur" +"eMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( -c[2], c[2]); co" +"lor += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes *" +" vec2( -c[3], c[1]); color += texture2D(u_TextureMap, tc);\r\n\r\n\ttc = " +"var_TexCoords + u_InvTexRes * vec2( -c[1], -c[3]); color += texture2D(u_Te" +"xtureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexRes * vec2( -c[2], -c[2]);" +" color += texture2D(u_TextureMap, tc);\r\n\ttc = var_TexCoords + u_InvTexR" +"es * vec2( -c[3], -c[1]); color += texture2D(u_TextureMap, tc);\r\n\t\r\n" +"\tgl_FragColor = color * 0.0625 * u_Color;\r\n}\r\n"; + +static const char *fallbackToneMapShader_vp = +"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\n\r\nunif" +"orm mat4 u_ModelViewProjectionMatrix;\r\n\r\nvarying vec2 var_TexCoords" +";\r\n\r\n\r\nvoid main()\r\n{\r\n\tgl_Position = u_ModelViewProjectionMatri" +"x * attr_Position;\r\n\tvar_TexCoords = attr_TexCoord0.st;\r\n}\r\n"; + +static const char *fallbackToneMapShader_fp = +"uniform sampler2D u_TextureMap;\r\nuniform sampler2D u_LevelsMap;\r\n\r\nun" +"iform vec4 u_Color;\r\n\r\nuniform vec2 u_AutoExposureMinMax;\r\n" +"uniform vec3 u_ToneMinAvgMaxLinear;\r\n\r\nvarying vec2 var_TexCo" +"ords;\r\n\r\nconst vec3 LUMINANCE_VECTOR = vec3(0.2125, 0.7154, 0.0721);" +" //vec3(0.299, 0.587, 0.114);\r\n\r\nvec3 FilmicTonemap(vec3 x)\r\n{\r\n\tc" +"onst float SS = 0.22; // Shoulder Strength\r\n\tconst float LS = 0.30; //" +" Linear Strength\r\n\tconst float LA = 0.10; // Linear Angle\r\n\tconst fl" +"oat TS = 0.20; // Toe Strength\r\n\tconst float TAN = 0.01; // Toe Angle N" +"umerator\r\n\tconst float TAD = 0.30; // Toe Angle Denominator\r\n\t\r\n\tv" +"ec3 SSxx = SS * x * x;\r\n\tvec3 LSx = LS * x;\r\n\tvec3 LALSx = LSx * LA;" +"\r\n\t\r\n\treturn ((SSxx + LALSx + TS * TAN) / (SSxx + LSx + TS * TAD)) - " +"TAN / TAD;\r\n\r\n\t//return ((x*(SS*x+LA*LS)+TS*TAN)/(x*(SS*x+LS)+TS*TAD))" +" - TAN/TAD;\r\n\r\n}\r\n\r\nvoid main()\r\n{\r\n\tvec4 color = texture2D(u_" +"TextureMap, var_TexCoords) * u_Color;\r\n\tvec3 minAvgMax = texture2D(u_Lev" +"elsMap, var_TexCoords).rgb;\r\n\tvec3 logMinAvgMaxLum = clamp(minAvgMax * 2" +"0.0 - 10.0, -u_AutoExposureMinMax.y, -u_AutoExposureMinMax.x);\r\n\t\t\r\n" +"\tfloat avgLum = exp2(logMinAvgMaxLum.y);\r\n\t//float maxLum = exp2(logMin" +"AvgMaxLum.z);\r\n\r\n\tcolor.rgb *= u_ToneMinAvgMaxLinear.y / avgLum;\r\n\t" +"color.rgb = max(vec3(0.0), color.rgb - vec3(u_ToneMinAvgMaxLinear.x));\r\n" +"\r\n\tvec3 fWhite = 1.0 / FilmicTonemap(vec3(u_ToneMinAvgMaxLinear.z - u_To" +"neMinAvgMaxLinear.x));\r\n\tcolor.rgb = FilmicTonemap(color.rgb) * fWhite;" +"\r\n\t\r\n\tgl_FragColor = clamp(color, 0.0, 1.0);\r\n}\r\n"; + +static const char *fallbackCalcLevels4xShader_vp = +"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\n\r\nunif" +"orm mat4 u_ModelViewProjectionMatrix;\r\n\r\nvarying vec2 var_TexCoords" +";\r\n\r\n\r\nvoid main()\r\n{\r\n\tgl_Position = u_ModelViewProjectionMatri" +"x * attr_Position;\r\n\tvar_TexCoords = attr_TexCoord0.st;\r\n}\r\n"; + +static const char *fallbackCalcLevels4xShader_fp = +"uniform sampler2D u_TextureMap;\r\n\r\nuniform vec4 u_Color;\r\n\r\nun" +"iform vec2 u_InvTexRes;\r\nvarying vec2 var_TexCoords;\r\n\r\ncon" +"st vec3 LUMINANCE_VECTOR = vec3(0.2125, 0.7154, 0.0721); //vec3(0.299, 0" +".587, 0.114);\r\n\r\nvec3 GetValues(vec2 offset, vec3 current)\r\n{\r\n\tve" +"c3 minAvgMax;\r\n\tvec2 tc = var_TexCoords + u_InvTexRes * offset; minAvgMa" +"x = texture2D(u_TextureMap, tc).rgb;\r\n\r\n#ifdef FIRST_PASS\r\n\tfloat lu" +"mi = max(dot(LUMINANCE_VECTOR, minAvgMax), 0.000001);\r\n\tfloat loglumi = " +"clamp(log2(lumi), -10.0, 10.0);\r\n\tminAvgMax = vec3(loglumi * 0.05 + 0.5)" +";\r\n#endif\r\n\r\n\treturn vec3(min(current.x, minAvgMax.x), current.y + m" +"inAvgMax.y, max(current.z, minAvgMax.z));\r\n}\r\n\r\nvoid main()\r\n{\r\n" +"\tvec3 current = vec3(1.0, 0.0, 0.0);\r\n\r\n#ifdef FIRST_PASS\r\n\tcurrent" +" = GetValues(vec2( 0.0, 0.0), current);\r\n#else\r\n\tcurrent = GetValues(" +"vec2(-1.5, -1.5), current);\r\n\tcurrent = GetValues(vec2(-0.5, -1.5), curr" +"ent);\r\n\tcurrent = GetValues(vec2( 0.5, -1.5), current);\r\n\tcurrent = G" +"etValues(vec2( 1.5, -1.5), current);\r\n\t\r\n\tcurrent = GetValues(vec2(-1" +".5, -0.5), current);\r\n\tcurrent = GetValues(vec2(-0.5, -0.5), current);\r" +"\n\tcurrent = GetValues(vec2( 0.5, -0.5), current);\r\n\tcurrent = GetValue" +"s(vec2( 1.5, -0.5), current);\r\n\t\r\n\tcurrent = GetValues(vec2(-1.5, 0." +"5), current);\r\n\tcurrent = GetValues(vec2(-0.5, 0.5), current);\r\n\tcur" +"rent = GetValues(vec2( 0.5, 0.5), current);\r\n\tcurrent = GetValues(vec2(" +" 1.5, 0.5), current);\r\n\r\n\tcurrent = GetValues(vec2(-1.5, 1.5), curre" +"nt);\r\n\tcurrent = GetValues(vec2(-0.5, 1.5), current);\r\n\tcurrent = Ge" +"tValues(vec2( 0.5, 1.5), current);\r\n\tcurrent = GetValues(vec2( 1.5, 1." +"5), current);\r\n\r\n\tcurrent.y *= 0.0625;\r\n#endif\r\n\r\n\tgl_FragColor" +" = vec4(current, 1.0f);\r\n}\r\n"; + +static const char *fallbackShadowmaskShader_vp = +"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\n\r\nunif" +"orm vec3 u_ViewForward;\r\nuniform vec3 u_ViewLeft;\r\nuniform vec3 u" +"_ViewUp;\r\nuniform vec4 u_ViewInfo; // zfar / znear\r\n\r\nvarying vec2 " +" var_ScreenTex;\r\nvarying vec3 var_ViewDir;\r\n\r\nvoid main()\r\n{\r\n" +"\tgl_Position = attr_Position;\r\n\t//vec2 screenCoords = gl_Position.xy / " +"gl_Position.w;\r\n\t//var_ScreenTex = screenCoords * 0.5 + 0.5;\r\n\tvar_Sc" +"reenTex = attr_TexCoord0.xy;\r\n\tvec2 screenCoords = attr_TexCoord0.xy * 2" +".0 - 1.0;\r\n\tvar_ViewDir = u_ViewForward + u_ViewLeft * -screenCoords.x +" +" u_ViewUp * screenCoords.y;\r\n}\r\n"; + +static const char *fallbackShadowmaskShader_fp = +"uniform sampler2D u_ScreenDepthMap;\r\n\r\nuniform sampler2D u_ShadowMap;\r" +"\n#if defined(USE_SHADOW_CASCADE)\r\nuniform sampler2D u_ShadowMap2;\r\nuni" +"form sampler2D u_ShadowMap3;\r\n#endif\r\n\r\nuniform mat4 u_ShadowMvp" +";\r\n#if defined(USE_SHADOW_CASCADE)\r\nuniform mat4 u_ShadowMvp2;\r\n" +"uniform mat4 u_ShadowMvp3;\r\n#endif\r\n\r\nuniform vec3 u_ViewOrigi" +"n;\r\nuniform vec4 u_ViewInfo; // zfar / znear, zfar\r\n\r\nvarying vec2 " +" var_ScreenTex;\r\nvarying vec3 var_ViewDir;\r\n\r\n// Input: It uses te" +"xture coords as the random number seed.\r\n// Output: Random number: [0,1)," +" that is between 0.0 and 0.999999... inclusive.\r\n// Author: Michael Pohor" +"eski\r\n// Copyright: Copyleft 2012 :-)\r\n// Source: http://stackoverflow." +"com/questions/5149544/can-i-generate-a-random-number-inside-a-pixel-shader" +"\r\n\r\nfloat random( const vec2 p )\r\n{\r\n // We need irrationals for p" +"seudo randomness.\r\n // Most (all?) known transcendental numbers will (ge" +"nerally) work.\r\n const vec2 r = vec2(\r\n 23.1406926327792690, // e^" +"pi (Gelfond's constant)\r\n 2.6651441426902251); // 2^sqrt(2) (Gelfond–" +"Schneider constant)\r\n //return fract( cos( mod( 123456789., 1e-7 + 256. " +"* dot(p,r) ) ) );\r\n return mod( 123456789., 1e-7 + 256. * dot(p,r) ); " +"\r\n}\r\n\r\nfloat PCF(const sampler2D shadowmap, const vec2 st, const floa" +"t dist)\r\n{\r\n\tfloat mult;\r\n\tfloat scale = 2.0 / r_shadowMapSize;\r\n" +"\t\t\r\n#if defined(USE_SHADOW_FILTER)\r\n\tfloat r = random(var_ScreenTex." +"xy);\r\n\tfloat sinr = sin(r) * scale;\r\n\tfloat cosr = cos(r) * scale;\r" +"\n\tmat2 rmat = mat2(cosr, sinr, -sinr, cosr);\r\n\r\n\tmult = step(dist, " +"texture2D(shadowmap, st + rmat * vec2(-0.7055767, 0.196515)).r);\r\n\tmult " +"+= step(dist, texture2D(shadowmap, st + rmat * vec2(0.3524343, -0.7791386))" +".r);\r\n\tmult += step(dist, texture2D(shadowmap, st + rmat * vec2(0.239105" +"6, 0.9189604)).r);\r\n #if defined(USE_SHADOW_FILTER2)\r\n\tmult += step(d" +"ist, texture2D(shadowmap, st + rmat * vec2(-0.07580382, -0.09224417)).r);\r" +"\n\tmult += step(dist, texture2D(shadowmap, st + rmat * vec2(0.5784913, -0." +"002528916)).r);\r\n\tmult += step(dist, texture2D(shadowmap, st + rmat * ve" +"c2(0.192888, 0.4064181)).r);\r\n\tmult += step(dist, texture2D(shadowmap, s" +"t + rmat * vec2(-0.6335801, -0.5247476)).r);\r\n\tmult += step(dist, textur" +"e2D(shadowmap, st + rmat * vec2(-0.5579782, 0.7491854)).r);\r\n\tmult += st" +"ep(dist, texture2D(shadowmap, st + rmat * vec2(0.7320465, 0.6317794)).r);\r" +"\n\r\n\tmult *= 0.11111;\r\n #else\r\n mult *= 0.33333;\r\n #endif\r\n" +"#else\r\n\tmult = step(dist, texture2D(shadowmap, st).r);\r\n#endif\r\n\t\t" +"\r\n\treturn mult;\r\n}\r\n\r\nfloat getLinearDepth(sampler2D depthMap, vec" +"2 tex, float zFarDivZNear)\r\n{\r\n\t\tfloat sampleZDivW = texture2D(depthM" +"ap, tex).r;\r\n\t\treturn 1.0 / mix(zFarDivZNear, 1.0, sampleZDivW);\r\n}\r" +"\n\r\nvoid main()\r\n{\r\n\tfloat result;\r\n\t\r\n\tfloat depth = getLinea" +"rDepth(u_ScreenDepthMap, var_ScreenTex, u_ViewInfo.x);\r\n\tfloat sampleZ =" +" u_ViewInfo.y * depth;\r\n\r\n\tvec4 biasPos = vec4(u_ViewOrigin + var_View" +"Dir * depth * 0.99, 1.0);\r\n\t\r\n\tvec4 shadowpos = u_ShadowMvp * biasPos" +";\r\n\t\r\n#if defined(USE_SHADOW_CASCADE)\r\n\tconst float fadeTo = 0.5;\r" +"\n\tresult = fadeTo;\r\n#else\r\n\tresult = 0.0;\r\n#endif\r\n\r\n\tif (all" +"(lessThanEqual(abs(shadowpos.xyz), vec3(abs(shadowpos.w)))))\r\n\t{\r\n\t\t" +"shadowpos.xyz = shadowpos.xyz / shadowpos.w * 0.5 + 0.5;\r\n\t\tresult = PC" +"F(u_ShadowMap, shadowpos.xy, shadowpos.z);\r\n\t}\r\n#if defined(USE_SHADOW" +"_CASCADE)\r\n\telse\r\n\t{\r\n\t\tshadowpos = u_ShadowMvp2 * biasPos;\r\n\r" +"\n\t\tif (all(lessThanEqual(abs(shadowpos.xyz), vec3(abs(shadowpos.w)))))\r" +"\n\t\t{\r\n\t\t\tshadowpos.xyz = shadowpos.xyz / shadowpos.w * 0.5 + 0.5;\r" +"\n\t\t\tresult = PCF(u_ShadowMap2, shadowpos.xy, shadowpos.z);\r\n\t\t}\r\n" +"\t\telse\r\n\t\t{\r\n\t\t\tshadowpos = u_ShadowMvp3 * biasPos;\r\n\r\n\t\t" +"\tif (all(lessThanEqual(abs(shadowpos.xyz), vec3(abs(shadowpos.w)))))\r\n\t" +"\t\t{\r\n\t\t\t\tshadowpos.xyz = shadowpos.xyz / shadowpos.w * 0.5 + 0.5;\r" +"\n\t\t\t\tresult = PCF(u_ShadowMap3, shadowpos.xy, shadowpos.z);\r\n\r\n\t" +"\t\t\tfloat fade = clamp(sampleZ / r_shadowCascadeZFar * 10.0 - 9.0, 0.0, 1" +".0);\r\n\t\t\t\tresult = mix(result, fadeTo, fade);\r\n\t\t\t}\r\n\t\t}\r\n" +"\t}\r\n#endif\r\n\t\t\r\n\tgl_FragColor = vec4(vec3(result), 1.0);\r\n}\r\n"; + +static const char *fallbackSsaoShader_vp = +"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\n\r\nvary" +"ing vec2 var_ScreenTex;\r\n\r\nvoid main()\r\n{\r\n\tgl_Position = attr_P" +"osition;\r\n\tvar_ScreenTex = attr_TexCoord0.xy;\r\n\t//vec2 screenCoords =" +" gl_Position.xy / gl_Position.w;\r\n\t//var_ScreenTex = screenCoords * 0.5 " +"+ 0.5;\r\n}\r\n"; + +static const char *fallbackSsaoShader_fp = +"uniform sampler2D u_ScreenDepthMap;\r\n\r\nuniform vec4 u_ViewInfo; // zf" +"ar / znear, zfar\r\n\r\nvarying vec2 var_ScreenTex;\r\n\r\nvec2 poissonDi" +"sc[9] = vec2[9](\r\nvec2(-0.7055767, 0.196515), vec2(0.3524343, -0.77913" +"86),\r\nvec2(0.2391056, 0.9189604), vec2(-0.07580382, -0.09224417),\r\nv" +"ec2(0.5784913, -0.002528916), vec2(0.192888, 0.4064181),\r\nvec2(-0.6335801" +", -0.5247476), vec2(-0.5579782, 0.7491854),\r\nvec2(0.7320465, 0.6317794)" +"\r\n);\r\n\r\n// Input: It uses texture coords as the random number seed.\r" +"\n// Output: Random number: [0,1), that is between 0.0 and 0.999999... incl" +"usive.\r\n// Author: Michael Pohoreski\r\n// Copyright: Copyleft 2012 :-)\r" +"\n// Source: http://stackoverflow.com/questions/5149544/can-i-generate-a-ra" +"ndom-number-inside-a-pixel-shader\r\n\r\nfloat random( const vec2 p )\r\n{" +"\r\n // We need irrationals for pseudo randomness.\r\n // Most (all?) kno" +"wn transcendental numbers will (generally) work.\r\n const vec2 r = vec2(" +"\r\n 23.1406926327792690, // e^pi (Gelfond's constant)\r\n 2.665144" +"1426902251); // 2^sqrt(2) (Gelfond–Schneider constant)\r\n //return fract(" +" cos( mod( 123456789., 1e-7 + 256. * dot(p,r) ) ) );\r\n return mod( 12345" +"6789., 1e-7 + 256. * dot(p,r) ); \r\n}\r\n\r\nmat2 randomRotation( const v" +"ec2 p )\r\n{\r\n\tfloat r = random(p);\r\n\tfloat sinr = sin(r);\r\n\tfloat" +" cosr = cos(r);\r\n\treturn mat2(cosr, sinr, -sinr, cosr);\r\n}\r\n\r\nfloa" +"t getLinearDepth(sampler2D depthMap, const vec2 tex, const float zFarDivZNe" +"ar)\r\n{\r\n\t\tfloat sampleZDivW = texture2D(depthMap, tex).r;\r\n\t\tretu" +"rn 1.0 / mix(zFarDivZNear, 1.0, sampleZDivW);\r\n}\r\n\r\nfloat ambientOccl" +"usion(sampler2D depthMap, const vec2 tex, const float zFarDivZNear, const f" +"loat zFar)\r\n{\r\n\tfloat result = 0;\r\n\r\n\tfloat sampleZ = zFar * getL" +"inearDepth(depthMap, tex, zFarDivZNear);\r\n\r\n\tvec2 expectedSlope = vec2" +"(dFdx(sampleZ), dFdy(sampleZ)) / vec2(dFdx(tex.x), dFdy(tex.y));\r\n\t\r\n" +"\tif (length(expectedSlope) > 5000.0)\r\n\t\treturn 1.0;\r\n\t\r\n\tvec2 of" +"fsetScale = vec2(3.0 / sampleZ);\r\n\t\r\n\tmat2 rmat = randomRotation(tex)" +";\r\n\t\t\r\n\tint i;\r\n\tfor (i = 0; i < 3; i++)\r\n\t{\r\n\t\tvec2 offse" +"t = rmat * poissonDisc[i] * offsetScale;\r\n\t\tfloat sampleZ2 = zFar * get" +"LinearDepth(depthMap, tex + offset, zFarDivZNear);\r\n\r\n\t\tif (abs(sampl" +"eZ - sampleZ2) > 20.0)\r\n\t\t\tresult += 1.0;\r\n\t\telse\r\n\t\t{\r\n\t\t" +"\tfloat expectedZ = sampleZ + dot(expectedSlope, offset);\r\n\t\t\tresult +" +"= step(expectedZ - 1.0, sampleZ2);\r\n\t\t}\r\n\t}\r\n\t\r\n\tresult *= 0.3" +"3333;\r\n\t\r\n\treturn result;\r\n}\r\n\r\nvoid main()\r\n{\r\n\tfloat res" +"ult = ambientOcclusion(u_ScreenDepthMap, var_ScreenTex, u_ViewInfo.x, u_Vie" +"wInfo.y);\r\n\t\t\t\r\n\tgl_FragColor = vec4(vec3(result), 1.0);\r\n}\r\n"; + +static const char *fallbackDepthBlurShader_vp = +"attribute vec4 attr_Position;\r\nattribute vec4 attr_TexCoord0;\r\n\r\nvary" +"ing vec2 var_ScreenTex;\r\n\r\nvoid main()\r\n{\r\n\tgl_Position = attr_P" +"osition;\r\n\tvar_ScreenTex = attr_TexCoord0.xy;\r\n\t//vec2 screenCoords =" +" gl_Position.xy / gl_Position.w;\r\n\t//var_ScreenTex = screenCoords * 0.5 " +"+ 0.5;\r\n}\r\n"; + +static const char *fallbackDepthBlurShader_fp = +"uniform sampler2D u_ScreenImageMap;\r\nuniform sampler2D u_ScreenDepthMap;" +"\r\n\r\nuniform vec4 u_ViewInfo; // zfar / znear, zfar\r\nvarying vec2 " +"var_ScreenTex;\r\n\r\n//float gauss[5] = float[5](0.30, 0.23, 0.097, 0.024," +" 0.0033);\r\nfloat gauss[4] = float[4](0.40, 0.24, 0.054, 0.0044);\r\n//flo" +"at gauss[3] = float[3](0.60, 0.19, 0.0066);\r\n#define GAUSS_SIZE 4\r\n\r\n" +"float getLinearDepth(sampler2D depthMap, const vec2 tex, const float zFarDi" +"vZNear)\r\n{\r\n\t\tfloat sampleZDivW = texture2D(depthMap, tex).r;\r\n\t\t" +"return 1.0 / mix(zFarDivZNear, 1.0, sampleZDivW);\r\n}\r\n\r\nvec4 depthGau" +"ssian1D(sampler2D imageMap, sampler2D depthMap, vec2 tex, float zFarDivZNea" +"r, float zFar)\r\n{\r\n\tfloat scale = 1.0 / 256.0;\r\n\r\n#if defined(USE_" +"HORIZONTAL_BLUR)\r\n vec2 direction = vec2(1.0, 0.0) * scale;\r\n#else /" +"/ if defined(USE_VERTICAL_BLUR)\r\n\tvec2 direction = vec2(0.0, 1.0) * scal" +"e;\r\n#endif\r\n\t\r\n\tfloat depthCenter = zFar * getLinearDepth(depthMap," +" tex, zFarDivZNear);\r\n\tvec2 centerSlope = vec2(dFdx(depthCenter), dFdy(d" +"epthCenter)) / vec2(dFdx(tex.x), dFdy(tex.y));\r\n\t\t\r\n\tvec4 result = t" +"exture2D(imageMap, tex) * gauss[0];\r\n\tfloat total = gauss[0];\r\n\r\n\ti" +"nt i, j;\r\n\tfor (i = 0; i < 2; i++)\r\n\t{\r\n\t\tfor (j = 1; j < GAUSS_S" +"IZE; j++)\r\n\t\t{\r\n\t\t\tvec2 offset = direction * j;\r\n\t\t\tfloat dep" +"thSample = zFar * getLinearDepth(depthMap, tex + offset, zFarDivZNear);\r\n" +"\t\t\tfloat depthExpected = depthCenter + dot(centerSlope, offset);\r\n\t\t" +"\tif(abs(depthSample - depthExpected) < 5.0)\r\n\t\t\t{\r\n\t\t\t\tresult +" +"= texture2D(imageMap, tex + offset) * gauss[j];\r\n\t\t\t\ttotal += gauss[j" +"];\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tdirection = -direction;\r\n\t}\t\r\n" +"\t\t\r\n\treturn result / total;\r\n}\r\n\r\nvoid main()\r\n{\t\t\r\n\tgl_F" +"ragColor = depthGaussian1D(u_ScreenImageMap, u_ScreenDepthMap, var_ScreenTe" +"x, u_ViewInfo.x, u_ViewInfo.y);\r\n}\r\n"; + + +static void GLSL_PrintInfoLog(GLhandleARB object, qboolean developerOnly) +{ + char *msg; + static char msgPart[1024]; + int maxLength = 0; + int i; + int printLevel = developerOnly ? PRINT_DEVELOPER : PRINT_ALL; + + qglGetObjectParameterivARB(object, GL_OBJECT_INFO_LOG_LENGTH_ARB, &maxLength); + + if (maxLength <= 0) + { + ri.Printf(printLevel, "No compile log.\n"); + return; + } + + ri.Printf(printLevel, "compile log:\n"); + + if (maxLength < 1023) + { + qglGetInfoLogARB(object, maxLength, &maxLength, msgPart); + + msgPart[maxLength + 1] = '\0'; + + ri.Printf(printLevel, "%s\n", msgPart); + } + else + { + msg = ri.Malloc(maxLength); + + qglGetInfoLogARB(object, maxLength, &maxLength, msg); + + for(i = 0; i < maxLength; i += 1024) + { + Q_strncpyz(msgPart, msg + i, sizeof(msgPart)); + + ri.Printf(printLevel, "%s\n", msgPart); + } + + ri.Free(msg); + } +} + +static void GLSL_PrintShaderSource(GLhandleARB object) +{ + char *msg; + static char msgPart[1024]; + int maxLength = 0; + int i; + + qglGetObjectParameterivARB(object, GL_OBJECT_SHADER_SOURCE_LENGTH_ARB, &maxLength); + + msg = ri.Malloc(maxLength); + + qglGetShaderSourceARB(object, maxLength, &maxLength, msg); + + for(i = 0; i < maxLength; i += 1024) + { + Q_strncpyz(msgPart, msg + i, sizeof(msgPart)); + ri.Printf(PRINT_ALL, "%s\n", msgPart); + } + + ri.Free(msg); +} + +static void GLSL_GetShaderHeader( GLenum shaderType, const GLcharARB *extra, char *dest, int size ) +{ + float fbufWidthScale, fbufHeightScale; + + dest[0] = '\0'; + + // 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)) + { + Q_strcat(dest, size, "#version 130\n"); + + if(shaderType == GL_VERTEX_SHADER_ARB) + { + Q_strcat(dest, size, "#define attribute in\n"); + Q_strcat(dest, size, "#define varying out\n"); + } + else + { + Q_strcat(dest, size, "#define varying in\n"); + + Q_strcat(dest, size, "out vec4 out_Color;\n"); + Q_strcat(dest, size, "#define gl_FragColor out_Color\n"); + } + } + else + { + Q_strcat(dest, size, "#version 120\n"); + } + + // HACK: add some macros to avoid extra uniforms and save speed and code maintenance + //Q_strcat(dest, size, + // va("#ifndef r_SpecularExponent\n#define r_SpecularExponent %f\n#endif\n", r_specularExponent->value)); + //Q_strcat(dest, size, + // va("#ifndef r_SpecularScale\n#define r_SpecularScale %f\n#endif\n", r_specularScale->value)); + //Q_strcat(dest, size, + // va("#ifndef r_NormalScale\n#define r_NormalScale %f\n#endif\n", r_normalScale->value)); + + + Q_strcat(dest, size, "#ifndef M_PI\n#define M_PI 3.14159265358979323846f\n#endif\n"); + + //Q_strcat(dest, size, va("#ifndef MAX_SHADOWMAPS\n#define MAX_SHADOWMAPS %i\n#endif\n", MAX_SHADOWMAPS)); + + Q_strcat(dest, size, + va("#ifndef deformGen_t\n" + "#define deformGen_t\n" + "#define DGEN_WAVE_SIN %i\n" + "#define DGEN_WAVE_SQUARE %i\n" + "#define DGEN_WAVE_TRIANGLE %i\n" + "#define DGEN_WAVE_SAWTOOTH %i\n" + "#define DGEN_WAVE_INVERSE_SAWTOOTH %i\n" + "#define DGEN_BULGE %i\n" + "#define DGEN_MOVE %i\n" + "#endif\n", + DGEN_WAVE_SIN, + DGEN_WAVE_SQUARE, + DGEN_WAVE_TRIANGLE, + DGEN_WAVE_SAWTOOTH, + DGEN_WAVE_INVERSE_SAWTOOTH, + DGEN_BULGE, + DGEN_MOVE)); + + Q_strcat(dest, size, + va("#ifndef tcGen_t\n" + "#define tcGen_t\n" + "#define TCGEN_LIGHTMAP %i\n" + "#define TCGEN_TEXTURE %i\n" + "#define TCGEN_ENVIRONMENT_MAPPED %i\n" + "#define TCGEN_FOG %i\n" + "#define TCGEN_VECTOR %i\n" + "#endif\n", + TCGEN_LIGHTMAP, + TCGEN_TEXTURE, + TCGEN_ENVIRONMENT_MAPPED, + TCGEN_FOG, + TCGEN_VECTOR)); + + Q_strcat(dest, size, + va("#ifndef colorGen_t\n" + "#define colorGen_t\n" + "#define CGEN_LIGHTING_DIFFUSE %i\n" + "#endif\n", + CGEN_LIGHTING_DIFFUSE)); + + Q_strcat(dest, size, + va("#ifndef alphaGen_t\n" + "#define alphaGen_t\n" + "#define AGEN_LIGHTING_SPECULAR %i\n" + "#define AGEN_PORTAL %i\n" + "#define AGEN_FRESNEL %i\n" + "#endif\n", + AGEN_LIGHTING_SPECULAR, + AGEN_PORTAL, + AGEN_FRESNEL)); + + Q_strcat(dest, size, + va("#ifndef texenv_t\n" + "#define texenv_t\n" + "#define TEXENV_MODULATE %i\n" + "#define TEXENV_ADD %i\n" + "#define TEXENV_REPLACE %i\n" + "#endif\n", + GL_MODULATE, + GL_ADD, + GL_REPLACE)); + + fbufWidthScale = 1.0f / ((float)glConfig.vidWidth); + fbufHeightScale = 1.0f / ((float)glConfig.vidHeight); + Q_strcat(dest, size, + va("#ifndef r_FBufScale\n#define r_FBufScale vec2(%f, %f)\n#endif\n", fbufWidthScale, fbufHeightScale)); + + 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"); +} + +static int GLSL_CompileGPUShader(GLhandleARB program, GLhandleARB *prevShader, const GLcharARB *buffer, int size, GLenum shaderType) +{ + GLint compiled; + GLhandleARB shader; + + shader = qglCreateShaderObjectARB(shaderType); + + qglShaderSourceARB(shader, 1, (const GLcharARB **)&buffer, &size); + + // compile shader + qglCompileShaderARB(shader); + + // check if shader compiled + qglGetObjectParameterivARB(shader, GL_OBJECT_COMPILE_STATUS_ARB, &compiled); + if(!compiled) + { + GLSL_PrintShaderSource(shader); + GLSL_PrintInfoLog(shader, qfalse); + ri.Error(ERR_DROP, "Couldn't compile shader"); + return 0; + } + + //GLSL_PrintInfoLog(shader, qtrue); + //GLSL_PrintShaderSource(shader); + + if (*prevShader) + { + qglDetachObjectARB(program, *prevShader); + qglDeleteObjectARB(*prevShader); + } + + // attach shader to program + qglAttachObjectARB(program, shader); + + *prevShader = shader; + + return 1; +} + + +static void GLSL_DumpText(const char *shaderText, int size, const char *name, GLenum shaderType) +{ + int i, l, inc; + ri.Printf(PRINT_ALL, "static const char *fallback%sShader_%s =\n\"", name, shaderType == GL_VERTEX_SHADER_ARB ? "vp" : "fp"); + l = 0; + + for (i = 0; i < size; i++) + { + switch (shaderText[i]) + { + case '\a': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + case '\v': + case '"': + case '\\': + inc = 2; + break; + default: + inc = 1; + break; + } + + l += inc; + + if (l >= 76) + { + ri.Printf(PRINT_ALL, "\"\n\""); + l = inc; + } + + switch (shaderText[i]) + { + case '\a': + ri.Printf(PRINT_ALL, "\\a"); + break; + case '\b': + ri.Printf(PRINT_ALL, "\\b"); + break; + case '\f': + ri.Printf(PRINT_ALL, "\\f"); + break; + case '\n': + ri.Printf(PRINT_ALL, "\\n"); + break; + case '\r': + ri.Printf(PRINT_ALL, "\\r"); + break; + case '\t': + ri.Printf(PRINT_ALL, "\\t"); + break; + case '\v': + ri.Printf(PRINT_ALL, "\\v"); + break; + case '"': + ri.Printf(PRINT_ALL, "\\\""); + break; + case '\\': + ri.Printf(PRINT_ALL, "\\\\"); + break; + default: + ri.Printf(PRINT_ALL, "%c", shaderText[i]); + break; + } + } + ri.Printf(PRINT_ALL, "\";\n\n"); +} + +static int GLSL_LoadGPUShaderText(const char *name, const char *fallback, + GLenum shaderType, char *dest, int destSize, qboolean dump) +{ + char filename[MAX_QPATH]; + GLcharARB *buffer = NULL; + const GLcharARB *shaderText = NULL; + int size; + int result; + + if(shaderType == GL_VERTEX_SHADER_ARB) + { + Com_sprintf(filename, sizeof(filename), "glsl/%s_vp.glsl", name); + } + else + { + Com_sprintf(filename, sizeof(filename), "glsl/%s_fp.glsl", name); + } + + ri.Printf(PRINT_DEVELOPER, "...loading '%s'\n", filename); + size = ri.FS_ReadFile(filename, (void **)&buffer); + if(!buffer) + { + if (fallback) + { + ri.Printf(PRINT_DEVELOPER, "couldn't load, using fallback\n"); + shaderText = fallback; + size = strlen(shaderText); + } + else + { + ri.Printf(PRINT_DEVELOPER, "couldn't load!\n"); + return 0; + } + } + else + { + shaderText = buffer; + } + + if (dump) + GLSL_DumpText(shaderText, size, name, shaderType); + + if (size > destSize) + { + result = 0; + } + else + { + Q_strncpyz(dest, shaderText, size + 1); + result = 1; + } + + if (buffer) + { + ri.FS_FreeFile(buffer); + } + + return result; +} + +static void GLSL_LinkProgram(GLhandleARB program) +{ + GLint linked; + + qglLinkProgramARB(program); + + qglGetObjectParameterivARB(program, GL_OBJECT_LINK_STATUS_ARB, &linked); + if(!linked) + { + GLSL_PrintInfoLog(program, qfalse); + ri.Error(ERR_DROP, "\nshaders failed to link"); + } +} + +static void GLSL_ValidateProgram(GLhandleARB program) +{ + GLint validated; + + qglValidateProgramARB(program); + + qglGetObjectParameterivARB(program, GL_OBJECT_VALIDATE_STATUS_ARB, &validated); + if(!validated) + { + GLSL_PrintInfoLog(program, qfalse); + ri.Error(ERR_DROP, "\nshaders failed to validate"); + } +} + +static void GLSL_ShowProgramUniforms(GLhandleARB program) +{ + int i, count, size; + GLenum type; + char uniformName[1000]; + + // install the executables in the program object as part of current state. + qglUseProgramObjectARB(program); + + // check for GL Errors + + // query the number of active uniforms + qglGetObjectParameterivARB(program, GL_OBJECT_ACTIVE_UNIFORMS_ARB, &count); + + // Loop over each of the active uniforms, and set their value + for(i = 0; i < count; i++) + { + qglGetActiveUniformARB(program, i, sizeof(uniformName), NULL, &size, &type, uniformName); + + ri.Printf(PRINT_DEVELOPER, "active uniform: '%s'\n", uniformName); + } + + qglUseProgramObjectARB(0); +} + +static int GLSL_InitGPUShader2(shaderProgram_t * program, const char *name, int attribs, const char *vpCode, const char *fpCode, int numUniforms) +{ + ri.Printf(PRINT_DEVELOPER, "------- GPU shader -------\n"); + + if(strlen(name) >= MAX_QPATH) + { + ri.Error(ERR_DROP, "GLSL_InitGPUShader2: \"%s\" is too long\n", name); + } + + Q_strncpyz(program->name, name, sizeof(program->name)); + + program->program = qglCreateProgramObjectARB(); + program->attribs = attribs; + + if (!(GLSL_CompileGPUShader(program->program, &program->vertexShader, vpCode, strlen(vpCode), GL_VERTEX_SHADER_ARB))) + { + ri.Printf(PRINT_ALL, "GLSL_InitGPUShader2: Unable to load \"%s\" as GL_VERTEX_SHADER_ARB\n", name); + qglDeleteObjectARB(program->program); + return 0; + } + + if(fpCode) + { + if(!(GLSL_CompileGPUShader(program->program, &program->fragmentShader, fpCode, strlen(fpCode), GL_FRAGMENT_SHADER_ARB))) + { + ri.Printf(PRINT_ALL, "GLSL_InitGPUShader2: Unable to load \"%s\" as GL_FRAGMENT_SHADER_ARB\n", name); + qglDeleteObjectARB(program->program); + return 0; + } + } + + if(attribs & ATTR_POSITION) + qglBindAttribLocationARB(program->program, ATTR_INDEX_POSITION, "attr_Position"); + + if(attribs & ATTR_TEXCOORD) + qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD0, "attr_TexCoord0"); + + if(attribs & ATTR_LIGHTCOORD) + qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD1, "attr_TexCoord1"); + +// if(attribs & ATTR_TEXCOORD2) +// qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD2, "attr_TexCoord2"); + +// if(attribs & ATTR_TEXCOORD3) +// qglBindAttribLocationARB(program->program, ATTR_INDEX_TEXCOORD3, "attr_TexCoord3"); + +#ifdef USE_VERT_TANGENT_SPACE + if(attribs & ATTR_TANGENT) + qglBindAttribLocationARB(program->program, ATTR_INDEX_TANGENT, "attr_Tangent"); + + if(attribs & ATTR_BITANGENT) + qglBindAttribLocationARB(program->program, ATTR_INDEX_BITANGENT, "attr_Bitangent"); +#endif + + if(attribs & ATTR_NORMAL) + qglBindAttribLocationARB(program->program, ATTR_INDEX_NORMAL, "attr_Normal"); + + if(attribs & ATTR_COLOR) + qglBindAttribLocationARB(program->program, ATTR_INDEX_COLOR, "attr_Color"); + + if(attribs & ATTR_PAINTCOLOR) + qglBindAttribLocationARB(program->program, ATTR_INDEX_PAINTCOLOR, "attr_PaintColor"); + + if(attribs & ATTR_LIGHTDIRECTION) + qglBindAttribLocationARB(program->program, ATTR_INDEX_LIGHTDIRECTION, "attr_LightDirection"); + + if(attribs & ATTR_POSITION2) + qglBindAttribLocationARB(program->program, ATTR_INDEX_POSITION2, "attr_Position2"); + + if(attribs & ATTR_NORMAL2) + qglBindAttribLocationARB(program->program, ATTR_INDEX_NORMAL2, "attr_Normal2"); + +#ifdef USE_VERT_TANGENT_SPACE + if(attribs & ATTR_TANGENT2) + qglBindAttribLocationARB(program->program, ATTR_INDEX_TANGENT2, "attr_Tangent2"); + + if(attribs & ATTR_BITANGENT2) + qglBindAttribLocationARB(program->program, ATTR_INDEX_BITANGENT2, "attr_Bitangent2"); +#endif + + GLSL_LinkProgram(program->program); + + program->numUniforms = numUniforms; + + { + int i, size; + + size = sizeof(*program->uniforms) * numUniforms; + program->uniforms = ri.Malloc(size); + for (i = 0; i < numUniforms; i++) + { + program->uniforms[i] = -1; + } + + size = sizeof(*program->uniformTypes) * numUniforms; + program->uniformTypes = ri.Malloc(size); + memset(program->uniformTypes, 0, size); + + size = sizeof(*program->uniformBufferOffsets) * numUniforms; + program->uniformBufferOffsets = ri.Malloc(size); + memset(program->uniformBufferOffsets, 0, size); + } + + return 1; +} + +static int GLSL_InitGPUShader(shaderProgram_t * program, const char *name, + int attribs, qboolean fragmentShader, const GLcharARB *extra, qboolean addHeader, + const char *fallback_vp, const char *fallback_fp, int numUniforms) +{ + char vpCode[32000]; + char fpCode[32000]; + char *postHeader; + int size; + int result; + + size = sizeof(vpCode); + if (addHeader) + { + GLSL_GetShaderHeader(GL_VERTEX_SHADER_ARB, extra, vpCode, size); + postHeader = &vpCode[strlen(vpCode)]; + size -= strlen(vpCode); + } + else + { + postHeader = &vpCode[0]; + } + + if (!GLSL_LoadGPUShaderText(name, fallback_vp, GL_VERTEX_SHADER_ARB, postHeader, size, qfalse)) + { + return 0; + } + + if (fragmentShader) + { + size = sizeof(fpCode); + if (addHeader) + { + GLSL_GetShaderHeader(GL_FRAGMENT_SHADER_ARB, extra, fpCode, size); + postHeader = &fpCode[strlen(fpCode)]; + size -= strlen(fpCode); + } + else + { + postHeader = &fpCode[0]; + } + + if (!GLSL_LoadGPUShaderText(name, fallback_fp, GL_FRAGMENT_SHADER_ARB, postHeader, size, qfalse)) + { + return 0; + } + } + + result = GLSL_InitGPUShader2(program, name, attribs, vpCode, fragmentShader ? fpCode : NULL, numUniforms); + + return result; +} + +// intentionally deceiving the user here, not actually setting the names but getting their indexes. +void GLSL_AddUniform(shaderProgram_t *program, int uniformNum, const char *name, int type) +{ + GLint *uniforms = program->uniforms; + + uniforms[uniformNum] = qglGetUniformLocationARB(program->program, name); + program->uniformTypes[uniformNum] = type; +} + +void GLSL_EndUniforms(shaderProgram_t *program) +{ + if (program->numUniforms) + { + int i, size; + + size = 0; + for (i = 0; i < program->numUniforms; i++) + { + if (program->uniforms[i] != -1) + { + program->uniformBufferOffsets[i] = size; + + switch(program->uniformTypes[i]) + { + case GLSL_INT: + size += sizeof(GLint); + break; + case GLSL_FLOAT: + size += sizeof(GLfloat); + break; + case GLSL_FLOAT5: + size += sizeof(vec_t) * 5; + break; + case GLSL_VEC2: + size += sizeof(vec_t) * 2; + break; + case GLSL_VEC3: + size += sizeof(vec_t) * 3; + break; + case GLSL_VEC4: + size += sizeof(vec_t) * 4; + break; + case GLSL_MAT16: + size += sizeof(vec_t) * 16; + break; + default: + break; + } + } + } + + program->uniformBuffer = ri.Malloc(size); + + } +} + +void GLSL_FinishGPUShader(shaderProgram_t *program) +{ + GLSL_ValidateProgram(program->program); + GLSL_ShowProgramUniforms(program->program); + GL_CheckErrors(); +} + +void GLSL_SetUniformInt(shaderProgram_t *program, int uniformNum, GLint value) +{ + GLint *uniforms = program->uniforms; + GLint *compare = (GLint *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]); + + if (uniforms[uniformNum] == -1) + return; + + if (program->uniformTypes[uniformNum] != GLSL_INT) + { + ri.Printf( PRINT_WARNING, "GLSL_SetUniformInt: wrong type for uniform %i in program %s\n", uniformNum, program->name); + return; + } + + if (value == *compare) + { + return; + } + + *compare = value; + + qglUniform1iARB(uniforms[uniformNum], value); +} + +void GLSL_SetUniformFloat(shaderProgram_t *program, int uniformNum, GLfloat value) +{ + GLint *uniforms = program->uniforms; + GLfloat *compare = (GLfloat *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]); + + if (uniforms[uniformNum] == -1) + return; + + if (program->uniformTypes[uniformNum] != GLSL_FLOAT) + { + ri.Printf( PRINT_WARNING, "GLSL_SetUniformFloat: wrong type for uniform %i in program %s\n", uniformNum, program->name); + return; + } + + if (value == *compare) + { + return; + } + + *compare = value; + + qglUniform1fARB(uniforms[uniformNum], value); +} + +void GLSL_SetUniformVec2(shaderProgram_t *program, int uniformNum, const vec2_t v) +{ + GLint *uniforms = program->uniforms; + vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]); + + if (uniforms[uniformNum] == -1) + return; + + if (program->uniformTypes[uniformNum] != GLSL_VEC2) + { + ri.Printf( PRINT_WARNING, "GLSL_SetUniformVec2: wrong type for uniform %i in program %s\n", uniformNum, program->name); + return; + } + + if (v[0] == compare[0] && v[1] == compare[1]) + { + return; + } + + compare[0] = v[0]; + compare[1] = v[1]; + + qglUniform2fARB(uniforms[uniformNum], v[0], v[1]); +} + +void GLSL_SetUniformVec3(shaderProgram_t *program, int uniformNum, const vec3_t v) +{ + GLint *uniforms = program->uniforms; + vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]); + + if (uniforms[uniformNum] == -1) + return; + + if (program->uniformTypes[uniformNum] != GLSL_VEC3) + { + ri.Printf( PRINT_WARNING, "GLSL_SetUniformVec3: wrong type for uniform %i in program %s\n", uniformNum, program->name); + return; + } + + if (VectorCompare(v, compare)) + { + return; + } + + VectorCopy(v, compare); + + qglUniform3fARB(uniforms[uniformNum], v[0], v[1], v[2]); +} + +void GLSL_SetUniformVec4(shaderProgram_t *program, int uniformNum, const vec4_t v) +{ + GLint *uniforms = program->uniforms; + vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]); + + if (uniforms[uniformNum] == -1) + return; + + if (program->uniformTypes[uniformNum] != GLSL_VEC4) + { + ri.Printf( PRINT_WARNING, "GLSL_SetUniformVec4: wrong type for uniform %i in program %s\n", uniformNum, program->name); + return; + } + + if (VectorCompare4(v, compare)) + { + return; + } + + VectorCopy4(v, compare); + + qglUniform4fARB(uniforms[uniformNum], v[0], v[1], v[2], v[3]); +} + +void GLSL_SetUniformFloat5(shaderProgram_t *program, int uniformNum, const vec5_t v) +{ + GLint *uniforms = program->uniforms; + vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]); + + if (uniforms[uniformNum] == -1) + { + ri.Printf( PRINT_ALL, "well shit.\n"); + return; + } + + if (program->uniformTypes[uniformNum] != GLSL_FLOAT5) + { + ri.Printf( PRINT_WARNING, "GLSL_SetUniformFloat5: wrong type for uniform %i in program %s\n", uniformNum, program->name); + return; + } + + if (VectorCompare5(v, compare)) + { + return; + } + + VectorCopy5(v, compare); + + qglUniform1fvARB(uniforms[uniformNum], 5, v); +} + +void GLSL_SetUniformMatrix16(shaderProgram_t *program, int uniformNum, const matrix_t matrix) +{ + GLint *uniforms = program->uniforms; + vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]); + + if (uniforms[uniformNum] == -1) + return; + + if (program->uniformTypes[uniformNum] != GLSL_MAT16) + { + ri.Printf( PRINT_WARNING, "GLSL_SetUniformMatrix16: wrong type for uniform %i in program %s\n", uniformNum, program->name); + return; + } + + if (Matrix16Compare(matrix, compare)) + { + return; + } + + Matrix16Copy(matrix, compare); + + qglUniformMatrix4fvARB(uniforms[uniformNum], 1, GL_FALSE, matrix); +} + +void GLSL_DeleteGPUShader(shaderProgram_t *program) +{ + if(program->program) + { + if (program->vertexShader) + { + qglDetachObjectARB(program->program, program->vertexShader); + qglDeleteObjectARB(program->vertexShader); + } + + if (program->fragmentShader) + { + qglDetachObjectARB(program->program, program->fragmentShader); + qglDeleteObjectARB(program->fragmentShader); + } + + qglDeleteObjectARB(program->program); + + if (program->uniforms) + { + ri.Free(program->uniforms); + } + + if (program->uniformTypes) + { + ri.Free(program->uniformTypes); + } + + if (program->uniformBuffer) + { + ri.Free(program->uniformBuffer); + } + + if (program->uniformBufferOffsets) + { + ri.Free(program->uniformBufferOffsets); + } + + Com_Memset(program, 0, sizeof(*program)); + } +} + +void GLSL_InitGPUShaders(void) +{ + int startTime, endTime; + int i; + char extradefines[1024]; + int attribs; + int numGenShaders = 0, numLightShaders = 0, numEtcShaders = 0; + + ri.Printf(PRINT_ALL, "------- GLSL_InitGPUShaders -------\n"); + + // make sure the render thread is stopped + R_SyncRenderThread(); + + startTime = ri.Milliseconds(); + + for (i = 0; i < GENERICDEF_COUNT; i++) + { + attribs = ATTR_POSITION | ATTR_TEXCOORD | ATTR_LIGHTCOORD | ATTR_NORMAL | ATTR_COLOR; + extradefines[0] = '\0'; + + if (i & GENERICDEF_USE_DEFORM_VERTEXES) + Q_strcat(extradefines, 1024, "#define USE_DEFORM_VERTEXES\n"); + + if (i & GENERICDEF_USE_TCGEN) + Q_strcat(extradefines, 1024, "#define USE_TCGEN\n"); + + if (i & GENERICDEF_USE_VERTEX_ANIMATION) + { + Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n"); + attribs |= ATTR_POSITION2 | ATTR_NORMAL2; + } + + if (i & GENERICDEF_USE_FOG) + Q_strcat(extradefines, 1024, "#define USE_FOG\n"); + + if (i & GENERICDEF_USE_RGBAGEN) + Q_strcat(extradefines, 1024, "#define USE_RGBAGEN\n"); + + if (i & GENERICDEF_USE_LIGHTMAP) + Q_strcat(extradefines, 1024, "#define USE_LIGHTMAP\n"); + + if (r_hdr->integer && !(glRefConfig.textureFloat && glRefConfig.halfFloatPixel)) + Q_strcat(extradefines, 1024, "#define RGBE_LIGHTMAP\n"); + + if (!GLSL_InitGPUShader(&tr.genericShader[i], "generic", attribs, qtrue, extradefines, qtrue, fallbackGenericShader_vp, fallbackGenericShader_fp, GENERIC_UNIFORM_COUNT)) + { + ri.Error(ERR_FATAL, "Could not load generic shader!\n"); + } + + // There's actually no need to filter these out, since they'll + // redirect to -1 if nonexistent, but it's more understandable this way. + + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16); + + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_BASECOLOR, "u_BaseColor", GLSL_VEC4); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_VERTCOLOR, "u_VertColor", GLSL_VEC4); + + if (i & GENERICDEF_USE_RGBAGEN) + { + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_COLORGEN, "u_ColorGen", GLSL_INT); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_ALPHAGEN, "u_AlphaGen", GLSL_INT); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_AMBIENTLIGHT, "u_AmbientLight", GLSL_VEC3); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_DIRECTEDLIGHT, "u_DirectedLight", GLSL_VEC3); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_LIGHTORIGIN, "u_LightOrigin", GLSL_VEC4); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_PORTALRANGE, "u_PortalRange", GLSL_FLOAT); + } + + if (i & GENERICDEF_USE_TCGEN) + { + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_TCGEN0, "u_TCGen0", GLSL_INT); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_TCGEN0VECTOR0, "u_TCGen0Vector0", GLSL_VEC3); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_TCGEN0VECTOR1, "u_TCGen0Vector1", GLSL_VEC3); + } + + if (i & GENERICDEF_USE_FOG) + { + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_FOGCOLORMASK, "u_FogColorMask", GLSL_VEC4); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_FOGDISTANCE, "u_FogDistance", GLSL_VEC4); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_FOGDEPTH, "u_FogDepth", GLSL_VEC4); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_FOGEYET, "u_FogEyeT", GLSL_FLOAT); + } + + if (i & GENERICDEF_USE_DEFORM_VERTEXES) + { + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_DEFORMGEN, "u_DeformGen", GLSL_INT); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_DEFORMPARAMS, "u_DeformParams", GLSL_FLOAT5); + } + + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_TIME, "u_Time", GLSL_FLOAT); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_VIEWORIGIN, "u_ViewOrigin", GLSL_VEC3); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_DIFFUSETEXMATRIX, "u_DiffuseTexMatrix", GLSL_MAT16); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_TEXTURE1ENV, "u_Texture1Env", GLSL_INT); + + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_DIFFUSEMAP, "u_DiffuseMap", GLSL_INT); + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_LIGHTMAP, "u_LightMap", GLSL_INT); + + + if (i & GENERICDEF_USE_VERTEX_ANIMATION) + { + GLSL_AddUniform(&tr.genericShader[i], GENERIC_UNIFORM_VERTEXLERP, "u_VertexLerp", GLSL_FLOAT); + } + + GLSL_EndUniforms(&tr.genericShader[i]); + + qglUseProgramObjectARB(tr.genericShader[i].program); + GLSL_SetUniformInt(&tr.genericShader[i], GENERIC_UNIFORM_DIFFUSEMAP, TB_DIFFUSEMAP); + GLSL_SetUniformInt(&tr.genericShader[i], GENERIC_UNIFORM_LIGHTMAP, TB_LIGHTMAP); + qglUseProgramObjectARB(0); + + GLSL_FinishGPUShader(&tr.genericShader[i]); + + numGenShaders++; + } + + + attribs = ATTR_POSITION | ATTR_TEXCOORD; + + if (!GLSL_InitGPUShader(&tr.textureColorShader, "texturecolor", attribs, qtrue, NULL, qfalse, fallbackTextureColorShader_vp, fallbackTextureColorShader_fp, TEXTURECOLOR_UNIFORM_COUNT)) + { + ri.Error(ERR_FATAL, "Could not load texturecolor shader!\n"); + } + + GLSL_AddUniform(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16); + GLSL_AddUniform(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_COLOR, "u_Color", GLSL_VEC4); + GLSL_AddUniform(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP, "u_DiffuseMap", GLSL_INT); + + GLSL_EndUniforms(&tr.textureColorShader); + + qglUseProgramObjectARB(tr.textureColorShader.program); + GLSL_SetUniformInt(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP); + qglUseProgramObjectARB(0); + + GLSL_FinishGPUShader(&tr.textureColorShader); + + numEtcShaders++; + + + attribs = ATTR_POSITION | ATTR_POSITION2 | ATTR_NORMAL | ATTR_NORMAL2 | ATTR_TEXCOORD; + + if (!GLSL_InitGPUShader(&tr.fogShader, "fogpass", attribs, qtrue, NULL, qtrue, fallbackFogPassShader_vp, fallbackFogPassShader_fp, FOGPASS_UNIFORM_COUNT)) + { + ri.Error(ERR_FATAL, "Could not load fogpass shader!\n"); + } + + GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_FOGDISTANCE, "u_FogDistance", GLSL_VEC4); + GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_FOGDEPTH, "u_FogDepth", GLSL_VEC4); + GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_FOGEYET, "u_FogEyeT", GLSL_FLOAT); + GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_DEFORMGEN, "u_DeformGen", GLSL_INT); + GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_DEFORMPARAMS, "u_DeformParams", GLSL_FLOAT5); + GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_TIME, "u_Time", GLSL_FLOAT); + GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_COLOR, "u_Color", GLSL_VEC4); + GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16); + GLSL_AddUniform(&tr.fogShader, FOGPASS_UNIFORM_VERTEXLERP, "u_VertexLerp", GLSL_FLOAT); + + GLSL_EndUniforms(&tr.fogShader); + GLSL_FinishGPUShader(&tr.fogShader); + + numEtcShaders++; + + + attribs = ATTR_POSITION | ATTR_NORMAL | ATTR_TEXCOORD; + + if (!GLSL_InitGPUShader(&tr.dlightallShader, "dlight", attribs, qtrue, NULL, qtrue, fallbackDlightShader_vp, fallbackDlightShader_fp, DLIGHT_UNIFORM_COUNT)) + { + ri.Error(ERR_FATAL, "Could not load dlight shader!\n"); + } + + GLSL_AddUniform(&tr.dlightallShader, DLIGHT_UNIFORM_DLIGHTINFO, "u_DlightInfo", GLSL_VEC4); + GLSL_AddUniform(&tr.dlightallShader, DLIGHT_UNIFORM_DEFORMGEN, "u_DeformGen", GLSL_INT); + GLSL_AddUniform(&tr.dlightallShader, DLIGHT_UNIFORM_DEFORMPARAMS, "u_DeformParams", GLSL_FLOAT5); + GLSL_AddUniform(&tr.dlightallShader, DLIGHT_UNIFORM_TIME, "u_Time", GLSL_FLOAT); + GLSL_AddUniform(&tr.dlightallShader, DLIGHT_UNIFORM_COLOR, "u_Color", GLSL_VEC4); + GLSL_AddUniform(&tr.dlightallShader, DLIGHT_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16); + + GLSL_EndUniforms(&tr.dlightallShader); + + qglUseProgramObjectARB(tr.dlightallShader.program); + GLSL_SetUniformInt(&tr.dlightallShader, DLIGHT_UNIFORM_DIFFUSEMAP, TB_DIFFUSEMAP); + qglUseProgramObjectARB(0); + + GLSL_FinishGPUShader(&tr.dlightallShader); + + numEtcShaders++; + + + for (i = 0; i < LIGHTDEF_COUNT; i++) + { + // skip impossible combos + if ((i & LIGHTDEF_USE_NORMALMAP) && !r_normalMapping->integer) + continue; + + if ((i & LIGHTDEF_USE_PARALLAXMAP) && !r_parallaxMapping->integer) + continue; + + if ((i & LIGHTDEF_USE_SPECULARMAP) && !r_specularMapping->integer) + continue; + + if ((i & LIGHTDEF_USE_DELUXEMAP) && !r_deluxeMapping->integer) + continue; + + if (!((i & LIGHTDEF_LIGHTTYPE_MASK) == LIGHTDEF_USE_LIGHTMAP) && (i & LIGHTDEF_USE_DELUXEMAP)) + continue; + + if (!(i & LIGHTDEF_USE_NORMALMAP) && (i & LIGHTDEF_USE_PARALLAXMAP)) + continue; + + if (!((i & LIGHTDEF_LIGHTTYPE_MASK) == LIGHTDEF_USE_LIGHT_VECTOR)) + { + if (i & LIGHTDEF_USE_SHADOWMAP) + continue; + } + + attribs = ATTR_POSITION | ATTR_TEXCOORD | ATTR_COLOR | ATTR_NORMAL; + + extradefines[0] = '\0'; + + if (r_normalAmbient->value > 0.003f) + Q_strcat(extradefines, 1024, va("#define r_normalAmbient %f\n", r_normalAmbient->value)); + + if (r_dlightMode->integer >= 2) + Q_strcat(extradefines, 1024, "#define USE_SHADOWMAP\n"); + + if (1) + { + Q_strcat(extradefines, 1024, "#define SWIZZLE_NORMALMAP\n"); + } + + if (r_hdr->integer && !(glRefConfig.textureFloat && glRefConfig.halfFloatPixel)) + Q_strcat(extradefines, 1024, "#define RGBE_LIGHTMAP\n"); + + if (i & LIGHTDEF_LIGHTTYPE_MASK) + { + Q_strcat(extradefines, 1024, "#define USE_LIGHT\n"); + + if (r_normalMapping->integer == 0 && r_specularMapping->integer == 0) + Q_strcat(extradefines, 1024, "#define USE_FAST_LIGHT\n"); + + switch (i & LIGHTDEF_LIGHTTYPE_MASK) + { + case LIGHTDEF_USE_LIGHTMAP: + Q_strcat(extradefines, 1024, "#define USE_LIGHTMAP\n"); + attribs |= ATTR_LIGHTCOORD | ATTR_LIGHTDIRECTION; + break; + case LIGHTDEF_USE_LIGHT_VECTOR: + Q_strcat(extradefines, 1024, "#define USE_LIGHT_VECTOR\n"); + break; + case LIGHTDEF_USE_LIGHT_VERTEX: + Q_strcat(extradefines, 1024, "#define USE_LIGHT_VERTEX\n"); + attribs |= ATTR_LIGHTDIRECTION; + break; + default: + break; + } + } + + if ((i & LIGHTDEF_USE_NORMALMAP) && r_normalMapping->integer) + { + Q_strcat(extradefines, 1024, "#define USE_NORMALMAP\n"); + + if (r_normalMapping->integer == 2) + Q_strcat(extradefines, 1024, "#define USE_OREN_NAYAR\n"); + + if (r_normalMapping->integer == 3) + Q_strcat(extradefines, 1024, "#define USE_TRIACE_OREN_NAYAR\n"); + +#ifdef USE_VERT_TANGENT_SPACE + Q_strcat(extradefines, 1024, "#define USE_VERT_TANGENT_SPACE\n"); + attribs |= ATTR_TANGENT | ATTR_BITANGENT; +#endif + } + + if ((i & LIGHTDEF_USE_SPECULARMAP) && r_specularMapping->integer) + { + Q_strcat(extradefines, 1024, "#define USE_SPECULARMAP\n"); + + switch (r_specularMapping->integer) + { + case 1: + default: + Q_strcat(extradefines, 1024, "#define USE_TRIACE\n"); + break; + + case 2: + Q_strcat(extradefines, 1024, "#define USE_BLINN\n"); + break; + + case 3: + Q_strcat(extradefines, 1024, "#define USE_COOK_TORRANCE\n"); + break; + + case 4: + Q_strcat(extradefines, 1024, "#define USE_TORRANCE_SPARROW\n"); + break; + } + } + + if ((i & LIGHTDEF_USE_DELUXEMAP) && r_deluxeMapping->integer) + Q_strcat(extradefines, 1024, "#define USE_DELUXEMAP\n"); + + if ((i & LIGHTDEF_USE_PARALLAXMAP) && !(i & LIGHTDEF_ENTITY) && r_parallaxMapping->integer) + Q_strcat(extradefines, 1024, "#define USE_PARALLAXMAP\n"); + + if (i & LIGHTDEF_USE_SHADOWMAP) + Q_strcat(extradefines, 1024, "#define USE_SHADOWMAP\n"); + + if (i & LIGHTDEF_TCGEN_ENVIRONMENT) + Q_strcat(extradefines, 1024, "#define TCGEN_ENVIRONMENT\n"); + + if (i & LIGHTDEF_ENTITY) + { + Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n#define USE_MODELMATRIX\n"); + attribs |= ATTR_POSITION2 | ATTR_NORMAL2; + +#ifdef USE_VERT_TANGENT_SPACE + if (i & LIGHTDEF_USE_NORMALMAP && r_normalMapping->integer) + { + attribs |= ATTR_TANGENT2 | ATTR_BITANGENT2; + } +#endif + } + + if (!GLSL_InitGPUShader(&tr.lightallShader[i], "lightall", attribs, qtrue, extradefines, qtrue, fallbackLightallShader_vp, fallbackLightallShader_fp, GENERIC_UNIFORM_COUNT)) + { + ri.Error(ERR_FATAL, "Could not load lightall shader!\n"); + } + + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16); + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_MODELMATRIX, "u_ModelMatrix", GLSL_MAT16); + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_DIFFUSETEXMATRIX, "u_DiffuseTexMatrix", GLSL_MAT16); + //GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_NORMALTEXMATRIX, "u_NormalTexMatrix", GLSL_MAT16); + //GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_SPECULARTEXMATRIX, "u_SpecularTexMatrix", GLSL_MAT16); + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_VIEWORIGIN, "u_ViewOrigin", GLSL_VEC3); + + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_DIFFUSEMAP, "u_DiffuseMap", GLSL_INT); + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_LIGHTMAP, "u_LightMap", GLSL_INT); + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_NORMALMAP, "u_NormalMap", GLSL_INT); + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_DELUXEMAP, "u_DeluxeMap", GLSL_INT); + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_SPECULARMAP, "u_SpecularMap", GLSL_INT); + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_SHADOWMAP, "u_ShadowMap", GLSL_INT); + + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_AMBIENTLIGHT, "u_AmbientLight", GLSL_VEC3); + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_DIRECTEDLIGHT, "u_DirectedLight", GLSL_VEC3); + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_LIGHTORIGIN, "u_LightOrigin", GLSL_VEC4); + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_LIGHTRADIUS, "u_LightRadius", GLSL_FLOAT); + + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_MATERIALINFO, "u_MaterialInfo", GLSL_VEC2); + + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_BASECOLOR, "u_BaseColor", GLSL_VEC4); + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_VERTCOLOR, "u_VertColor", GLSL_VEC4); + GLSL_AddUniform(&tr.lightallShader[i], GENERIC_UNIFORM_VERTEXLERP, "u_VertexLerp", GLSL_FLOAT); + + GLSL_EndUniforms(&tr.lightallShader[i]); + + qglUseProgramObjectARB(tr.lightallShader[i].program); + GLSL_SetUniformInt(&tr.lightallShader[i], GENERIC_UNIFORM_DIFFUSEMAP, TB_DIFFUSEMAP); + GLSL_SetUniformInt(&tr.lightallShader[i], GENERIC_UNIFORM_LIGHTMAP, TB_LIGHTMAP); + GLSL_SetUniformInt(&tr.lightallShader[i], GENERIC_UNIFORM_NORMALMAP, TB_NORMALMAP); + GLSL_SetUniformInt(&tr.lightallShader[i], GENERIC_UNIFORM_DELUXEMAP, TB_DELUXEMAP); + GLSL_SetUniformInt(&tr.lightallShader[i], GENERIC_UNIFORM_SPECULARMAP, TB_SPECULARMAP); + GLSL_SetUniformInt(&tr.lightallShader[i], GENERIC_UNIFORM_SHADOWMAP, TB_SHADOWMAP); + qglUseProgramObjectARB(0); + + GLSL_FinishGPUShader(&tr.lightallShader[i]); + + numLightShaders++; + } + + attribs = ATTR_POSITION | ATTR_POSITION2 | ATTR_NORMAL | ATTR_NORMAL2 | ATTR_TEXCOORD; + + extradefines[0] = '\0'; + + if (!GLSL_InitGPUShader(&tr.shadowmapShader, "shadowfill", attribs, qtrue, extradefines, qtrue, fallbackShadowfillShader_vp, fallbackShadowfillShader_fp, GENERIC_UNIFORM_COUNT)) + { + ri.Error(ERR_FATAL, "Could not load shadowfill shader!\n"); + } + + GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_DEFORMGEN, "u_DeformGen", GLSL_INT); + GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_DEFORMPARAMS, "u_DeformParams", GLSL_FLOAT5); + GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_TIME, "u_Time", GLSL_FLOAT); + GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16); + GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_MODELMATRIX, "u_ModelMatrix", GLSL_MAT16); + GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_VERTEXLERP, "u_VertexLerp", GLSL_FLOAT); + + GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_LIGHTORIGIN, "u_LightOrigin", GLSL_VEC4); + GLSL_AddUniform(&tr.shadowmapShader, GENERIC_UNIFORM_LIGHTRADIUS, "u_LightRadius", GLSL_FLOAT); + + GLSL_EndUniforms(&tr.shadowmapShader); + GLSL_FinishGPUShader(&tr.shadowmapShader); + + numEtcShaders++; + + attribs = ATTR_POSITION | ATTR_NORMAL; + extradefines[0] = '\0'; + + Q_strcat(extradefines, 1024, "#define USE_PCF\n#define USE_DISCARD\n"); + + if (!GLSL_InitGPUShader(&tr.pshadowShader, "pshadow", attribs, qtrue, extradefines, qtrue, fallbackPshadowShader_vp, fallbackPshadowShader_fp, PSHADOW_UNIFORM_COUNT)) + { + ri.Error(ERR_FATAL, "Could not load pshadow shader!\n"); + } + + GLSL_AddUniform(&tr.pshadowShader, PSHADOW_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16); + GLSL_AddUniform(&tr.pshadowShader, PSHADOW_UNIFORM_LIGHTFORWARD, "u_LightForward", GLSL_VEC3); + GLSL_AddUniform(&tr.pshadowShader, PSHADOW_UNIFORM_LIGHTUP, "u_LightUp", GLSL_VEC3); + GLSL_AddUniform(&tr.pshadowShader, PSHADOW_UNIFORM_LIGHTRIGHT, "u_LightRight", GLSL_VEC3); + GLSL_AddUniform(&tr.pshadowShader, PSHADOW_UNIFORM_LIGHTORIGIN, "u_LightOrigin", GLSL_VEC4); + GLSL_AddUniform(&tr.pshadowShader, PSHADOW_UNIFORM_LIGHTRADIUS, "u_LightRadius", GLSL_FLOAT); + + GLSL_EndUniforms(&tr.pshadowShader); + + qglUseProgramObjectARB(tr.pshadowShader.program); + GLSL_SetUniformInt(&tr.pshadowShader, PSHADOW_UNIFORM_SHADOWMAP, TB_DIFFUSEMAP); + qglUseProgramObjectARB(0); + + GLSL_FinishGPUShader(&tr.pshadowShader); + + numEtcShaders++; + + + attribs = ATTR_POSITION | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (!GLSL_InitGPUShader(&tr.down4xShader, "down4x", attribs, qtrue, extradefines, qtrue, fallbackDown4xShader_vp, fallbackDown4xShader_fp, TEXTURECOLOR_UNIFORM_COUNT)) + { + ri.Error(ERR_FATAL, "Could not load down4x shader!\n"); + } + + GLSL_AddUniform(&tr.down4xShader, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16); + GLSL_AddUniform(&tr.down4xShader, TEXTURECOLOR_UNIFORM_INVTEXRES, "u_InvTexRes", GLSL_VEC2); + + GLSL_AddUniform(&tr.down4xShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP, "u_TextureMap", GLSL_INT); + + GLSL_EndUniforms(&tr.down4xShader); + + qglUseProgramObjectARB(tr.down4xShader.program); + GLSL_SetUniformInt(&tr.down4xShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP); + qglUseProgramObjectARB(0); + + GLSL_FinishGPUShader(&tr.down4xShader); + + numEtcShaders++; + + + attribs = ATTR_POSITION | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (!GLSL_InitGPUShader(&tr.bokehShader, "bokeh", attribs, qtrue, extradefines, qtrue, fallbackBokehShader_vp, fallbackBokehShader_fp, TEXTURECOLOR_UNIFORM_COUNT)) + { + ri.Error(ERR_FATAL, "Could not load bokeh shader!\n"); + } + + GLSL_AddUniform(&tr.bokehShader, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16); + GLSL_AddUniform(&tr.bokehShader, TEXTURECOLOR_UNIFORM_INVTEXRES, "u_InvTexRes", GLSL_VEC2); + GLSL_AddUniform(&tr.bokehShader, TEXTURECOLOR_UNIFORM_COLOR, "u_Color", GLSL_VEC4); + + GLSL_AddUniform(&tr.bokehShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP, "u_TextureMap", GLSL_INT); + + GLSL_EndUniforms(&tr.bokehShader); + + qglUseProgramObjectARB(tr.bokehShader.program); + GLSL_SetUniformInt(&tr.bokehShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP); + qglUseProgramObjectARB(0); + + GLSL_FinishGPUShader(&tr.bokehShader); + + numEtcShaders++; + + + attribs = ATTR_POSITION | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (!GLSL_InitGPUShader(&tr.tonemapShader, "tonemap", attribs, qtrue, extradefines, qtrue, fallbackToneMapShader_vp, fallbackToneMapShader_fp, TEXTURECOLOR_UNIFORM_COUNT)) + { + ri.Error(ERR_FATAL, "Could not load tonemap shader!\n"); + } + + GLSL_AddUniform(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16); + GLSL_AddUniform(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_INVTEXRES, "u_InvTexRes", GLSL_VEC2); + GLSL_AddUniform(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_COLOR, "u_Color", GLSL_VEC4); + GLSL_AddUniform(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_AUTOEXPOSUREMINMAX, "u_AutoExposureMinMax", GLSL_VEC2); + GLSL_AddUniform(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_TONEMINAVGMAXLINEAR, "u_ToneMinAvgMaxLinear", GLSL_VEC3); + GLSL_AddUniform(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP, "u_TextureMap", GLSL_INT); + GLSL_AddUniform(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_LEVELSMAP, "u_LevelsMap", GLSL_INT); + + GLSL_EndUniforms(&tr.tonemapShader); + + qglUseProgramObjectARB(tr.tonemapShader.program); + GLSL_SetUniformInt(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_TEXTUREMAP, TB_COLORMAP); + GLSL_SetUniformInt(&tr.tonemapShader, TEXTURECOLOR_UNIFORM_LEVELSMAP, TB_LEVELSMAP); + qglUseProgramObjectARB(0); + + GLSL_FinishGPUShader(&tr.tonemapShader); + + numEtcShaders++; + + + for (i = 0; i < 2; i++) + { + attribs = ATTR_POSITION | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (!i) + Q_strcat(extradefines, 1024, "#define FIRST_PASS\n"); + + if (!GLSL_InitGPUShader(&tr.calclevels4xShader[i], "calclevels4x", attribs, qtrue, extradefines, qtrue, fallbackCalcLevels4xShader_vp, fallbackCalcLevels4xShader_fp, TEXTURECOLOR_UNIFORM_COUNT)) + { + ri.Error(ERR_FATAL, "Could not load calclevels4x shader!\n"); + } + + GLSL_AddUniform(&tr.calclevels4xShader[i], TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, "u_ModelViewProjectionMatrix", GLSL_MAT16); + GLSL_AddUniform(&tr.calclevels4xShader[i], TEXTURECOLOR_UNIFORM_INVTEXRES, "u_InvTexRes", GLSL_VEC2); + GLSL_AddUniform(&tr.calclevels4xShader[i], TEXTURECOLOR_UNIFORM_COLOR, "u_Color", GLSL_VEC4); + + GLSL_AddUniform(&tr.calclevels4xShader[i], TEXTURECOLOR_UNIFORM_TEXTUREMAP, "u_TextureMap", GLSL_INT); + + GLSL_EndUniforms(&tr.calclevels4xShader[i]); + + qglUseProgramObjectARB(tr.calclevels4xShader[i].program); + GLSL_SetUniformInt(&tr.calclevels4xShader[i], TEXTURECOLOR_UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP); + qglUseProgramObjectARB(0); + + GLSL_FinishGPUShader(&tr.calclevels4xShader[i]); + + numEtcShaders++; + } + + + 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"); + + Q_strcat(extradefines, 1024, "#define USE_SHADOW_CASCADE\n"); + + Q_strcat(extradefines, 1024, va("#define r_shadowMapSize %d\n", r_shadowMapSize->integer)); + Q_strcat(extradefines, 1024, va("#define r_shadowCascadeZFar %f\n", r_shadowCascadeZFar->value)); + + + if (!GLSL_InitGPUShader(&tr.shadowmaskShader, "shadowmask", attribs, qtrue, extradefines, qtrue, fallbackShadowmaskShader_vp, fallbackShadowmaskShader_fp, SHADOWMASK_UNIFORM_COUNT)) + { + ri.Error(ERR_FATAL, "Could not load shadowmask shader!\n"); + } + + GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMVP, "u_ShadowMvp", GLSL_MAT16); + GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMVP2, "u_ShadowMvp2", GLSL_MAT16); + GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMVP3, "u_ShadowMvp3", GLSL_MAT16); + GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWORIGIN, "u_ViewOrigin", GLSL_VEC3); + GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWINFO, "u_ViewInfo", GLSL_VEC4); + GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWFORWARD,"u_ViewForward", GLSL_VEC3); + GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWLEFT, "u_ViewLeft", GLSL_VEC3); + GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWUP, "u_ViewUp", GLSL_VEC3); + + GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SCREENDEPTHMAP, "u_ScreenDepthMap", GLSL_INT); + GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMAP, "u_ShadowMap", GLSL_INT); + GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMAP2, "u_ShadowMap2", GLSL_INT); + GLSL_AddUniform(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMAP3, "u_ShadowMap3", GLSL_INT); + + GLSL_EndUniforms(&tr.shadowmaskShader); + + qglUseProgramObjectARB(tr.shadowmaskShader.program); + GLSL_SetUniformInt(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SCREENDEPTHMAP, TB_COLORMAP); + GLSL_SetUniformInt(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMAP, TB_SHADOWMAP); + GLSL_SetUniformInt(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMAP2, TB_SHADOWMAP2); + GLSL_SetUniformInt(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMAP3, TB_SHADOWMAP3); + qglUseProgramObjectARB(0); + + GLSL_FinishGPUShader(&tr.shadowmaskShader); + + numEtcShaders++; + + + attribs = ATTR_POSITION | ATTR_TEXCOORD; + extradefines[0] = '\0'; + + if (!GLSL_InitGPUShader(&tr.ssaoShader, "ssao", attribs, qtrue, extradefines, qtrue, fallbackSsaoShader_vp, fallbackSsaoShader_fp, SSAO_UNIFORM_COUNT)) + { + ri.Error(ERR_FATAL, "Could not load ssao shader!\n"); + } + + GLSL_AddUniform(&tr.ssaoShader, SSAO_UNIFORM_VIEWINFO, "u_ViewInfo", GLSL_VEC4); + + GLSL_AddUniform(&tr.ssaoShader, SSAO_UNIFORM_SCREENDEPTHMAP, "u_ScreenDepthMap", GLSL_INT); + + GLSL_EndUniforms(&tr.ssaoShader); + + qglUseProgramObjectARB(tr.ssaoShader.program); + GLSL_SetUniformInt(&tr.ssaoShader, SSAO_UNIFORM_SCREENDEPTHMAP, TB_COLORMAP); + qglUseProgramObjectARB(0); + + GLSL_FinishGPUShader(&tr.ssaoShader); + + numEtcShaders++; + + + for (i = 0; i < 2; i++) + { + 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 (!GLSL_InitGPUShader(&tr.depthBlurShader[i], "depthBlur", attribs, qtrue, extradefines, qtrue, fallbackDepthBlurShader_vp, fallbackDepthBlurShader_fp, DEPTHBLUR_UNIFORM_COUNT)) + { + ri.Error(ERR_FATAL, "Could not load depthBlur shader!\n"); + } + + GLSL_AddUniform(&tr.depthBlurShader[i], DEPTHBLUR_UNIFORM_VIEWINFO, "u_ViewInfo", GLSL_VEC4); + + GLSL_AddUniform(&tr.depthBlurShader[i], DEPTHBLUR_UNIFORM_SCREENIMAGEMAP, "u_ScreenImageMap", GLSL_INT); + GLSL_AddUniform(&tr.depthBlurShader[i], DEPTHBLUR_UNIFORM_SCREENDEPTHMAP, "u_ScreenDepthMap", GLSL_INT); + + GLSL_EndUniforms(&tr.depthBlurShader[i]); + + qglUseProgramObjectARB(tr.depthBlurShader[i].program); + GLSL_SetUniformInt(&tr.depthBlurShader[i], DEPTHBLUR_UNIFORM_SCREENIMAGEMAP, TB_COLORMAP); + GLSL_SetUniformInt(&tr.depthBlurShader[i], DEPTHBLUR_UNIFORM_SCREENDEPTHMAP, TB_LIGHTMAP); + qglUseProgramObjectARB(0); + + GLSL_FinishGPUShader(&tr.depthBlurShader[i]); + + numEtcShaders++; + } + + + endTime = ri.Milliseconds(); + + ri.Printf(PRINT_ALL, "loaded %i GLSL shaders (%i gen %i light %i etc) in %5.2f seconds\n", + numGenShaders + numLightShaders + numEtcShaders, numGenShaders, numLightShaders, + numEtcShaders, (endTime - startTime) / 1000.0); + + if (0) + { + GLSL_LoadGPUShaderText("Generic", NULL, GL_VERTEX_SHADER_ARB, NULL, 0, qtrue); + GLSL_LoadGPUShaderText("Generic", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue); + + GLSL_LoadGPUShaderText("TextureColor", NULL, GL_VERTEX_SHADER_ARB, NULL, 0, qtrue); + GLSL_LoadGPUShaderText("TextureColor", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue); + + GLSL_LoadGPUShaderText("FogPass", NULL, GL_VERTEX_SHADER_ARB, NULL, 0, qtrue); + GLSL_LoadGPUShaderText("FogPass", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue); + + GLSL_LoadGPUShaderText("Dlight", NULL, GL_VERTEX_SHADER_ARB, NULL, 0, qtrue); + GLSL_LoadGPUShaderText("Dlight", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue); + + GLSL_LoadGPUShaderText("Lightall", NULL, GL_VERTEX_SHADER_ARB, NULL, 0, qtrue); + GLSL_LoadGPUShaderText("Lightall", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue); + + GLSL_LoadGPUShaderText("Shadowfill", NULL, GL_VERTEX_SHADER_ARB, NULL, 0, qtrue); + GLSL_LoadGPUShaderText("Shadowfill", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue); + + GLSL_LoadGPUShaderText("Pshadow", NULL, GL_VERTEX_SHADER_ARB, NULL, 0, qtrue); + GLSL_LoadGPUShaderText("Pshadow", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue); + + GLSL_LoadGPUShaderText("Down4x", NULL, GL_VERTEX_SHADER_ARB, NULL, 0, qtrue); + GLSL_LoadGPUShaderText("Down4x", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue); + + GLSL_LoadGPUShaderText("Bokeh", NULL, GL_VERTEX_SHADER_ARB, NULL, 0, qtrue); + GLSL_LoadGPUShaderText("Bokeh", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue); + + GLSL_LoadGPUShaderText("ToneMap", NULL, GL_VERTEX_SHADER_ARB, NULL, 0, qtrue); + GLSL_LoadGPUShaderText("ToneMap", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue); + + GLSL_LoadGPUShaderText("CalcLevels4x", NULL, GL_VERTEX_SHADER_ARB, NULL, 0, qtrue); + GLSL_LoadGPUShaderText("CalcLevels4x", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue); + + GLSL_LoadGPUShaderText("Shadowmask", NULL, GL_VERTEX_SHADER_ARB, NULL, 0, qtrue); + GLSL_LoadGPUShaderText("Shadowmask", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue); + + GLSL_LoadGPUShaderText("Ssao", NULL, GL_VERTEX_SHADER_ARB, NULL, 0, qtrue); + GLSL_LoadGPUShaderText("Ssao", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue); + + GLSL_LoadGPUShaderText("DepthBlur", NULL, GL_VERTEX_SHADER_ARB, NULL, 0, qtrue); + GLSL_LoadGPUShaderText("DepthBlur", NULL, GL_FRAGMENT_SHADER_ARB, NULL, 0, qtrue); + } + +} + +void GLSL_ShutdownGPUShaders(void) +{ + int i; + + ri.Printf(PRINT_ALL, "------- GLSL_ShutdownGPUShaders -------\n"); + + qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD0); + qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD1); + qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION); + qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION2); + qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL); +#ifdef USE_VERT_TANGENT_SPACE + qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT); + qglDisableVertexAttribArrayARB(ATTR_INDEX_BITANGENT); +#endif + qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL2); +#ifdef USE_VERT_TANGENT_SPACE + qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT2); + qglDisableVertexAttribArrayARB(ATTR_INDEX_BITANGENT2); +#endif + qglDisableVertexAttribArrayARB(ATTR_INDEX_COLOR); + qglDisableVertexAttribArrayARB(ATTR_INDEX_LIGHTDIRECTION); + GLSL_BindNullProgram(); + + for ( i = 0; i < GENERICDEF_COUNT; i++) + GLSL_DeleteGPUShader(&tr.genericShader[i]); + + GLSL_DeleteGPUShader(&tr.textureColorShader); + GLSL_DeleteGPUShader(&tr.fogShader); + GLSL_DeleteGPUShader(&tr.dlightallShader); + + for ( i = 0; i < LIGHTDEF_COUNT; i++) + GLSL_DeleteGPUShader(&tr.lightallShader[i]); + + GLSL_DeleteGPUShader(&tr.shadowmapShader); + GLSL_DeleteGPUShader(&tr.pshadowShader); + GLSL_DeleteGPUShader(&tr.down4xShader); + + for ( i = 0; i < 2; i++) + GLSL_DeleteGPUShader(&tr.calclevels4xShader[i]); + + glState.currentProgram = 0; + qglUseProgramObjectARB(0); +} + + +void GLSL_BindProgram(shaderProgram_t * program) +{ + if(!program) + { + GLSL_BindNullProgram(); + return; + } + + if(r_logFile->integer) + { + // don't just call LogComment, or we will get a call to va() every frame! + GLimp_LogComment(va("--- GL_BindProgram( %s ) ---\n", program->name)); + } + + if(glState.currentProgram != program) + { + qglUseProgramObjectARB(program->program); + glState.currentProgram = program; + backEnd.pc.c_glslShaderBinds++; + } +} + + +void GLSL_BindNullProgram(void) +{ + if(r_logFile->integer) + { + GLimp_LogComment("--- GL_BindNullProgram ---\n"); + } + + if(glState.currentProgram) + { + qglUseProgramObjectARB(0); + glState.currentProgram = NULL; + } +} + + +void GLSL_VertexAttribsState(uint32_t stateBits) +{ + uint32_t diff; + + GLSL_VertexAttribPointers(stateBits); + + diff = stateBits ^ glState.vertexAttribsState; + if(!diff) + { + return; + } + + if(diff & ATTR_POSITION) + { + if(stateBits & ATTR_POSITION) + { + GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION )\n"); + qglEnableVertexAttribArrayARB(ATTR_INDEX_POSITION); + } + else + { + GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_POSITION )\n"); + qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION); + } + } + + if(diff & ATTR_TEXCOORD) + { + if(stateBits & ATTR_TEXCOORD) + { + GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD )\n"); + qglEnableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD0); + } + else + { + GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD )\n"); + qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD0); + } + } + + if(diff & ATTR_LIGHTCOORD) + { + if(stateBits & ATTR_LIGHTCOORD) + { + GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHTCOORD )\n"); + qglEnableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD1); + } + else + { + GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_LIGHTCOORD )\n"); + qglDisableVertexAttribArrayARB(ATTR_INDEX_TEXCOORD1); + } + } + + if(diff & ATTR_NORMAL) + { + if(stateBits & ATTR_NORMAL) + { + GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL )\n"); + qglEnableVertexAttribArrayARB(ATTR_INDEX_NORMAL); + } + else + { + GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_NORMAL )\n"); + qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL); + } + } + +#ifdef USE_VERT_TANGENT_SPACE + if(diff & ATTR_TANGENT) + { + if(stateBits & ATTR_TANGENT) + { + GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT )\n"); + qglEnableVertexAttribArrayARB(ATTR_INDEX_TANGENT); + } + else + { + GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_TANGENT )\n"); + qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT); + } + } + + if(diff & ATTR_BITANGENT) + { + if(stateBits & ATTR_BITANGENT) + { + GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_BITANGENT )\n"); + qglEnableVertexAttribArrayARB(ATTR_INDEX_BITANGENT); + } + else + { + GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_BITANGENT )\n"); + qglDisableVertexAttribArrayARB(ATTR_INDEX_BITANGENT); + } + } +#endif + + if(diff & ATTR_COLOR) + { + if(stateBits & ATTR_COLOR) + { + GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_COLOR )\n"); + qglEnableVertexAttribArrayARB(ATTR_INDEX_COLOR); + } + else + { + GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_COLOR )\n"); + qglDisableVertexAttribArrayARB(ATTR_INDEX_COLOR); + } + } + + if(diff & ATTR_LIGHTDIRECTION) + { + if(stateBits & ATTR_LIGHTDIRECTION) + { + GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHTDIRECTION )\n"); + qglEnableVertexAttribArrayARB(ATTR_INDEX_LIGHTDIRECTION); + } + else + { + GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_LIGHTDIRECTION )\n"); + qglDisableVertexAttribArrayARB(ATTR_INDEX_LIGHTDIRECTION); + } + } + + if(diff & ATTR_POSITION2) + { + if(stateBits & ATTR_POSITION2) + { + GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION2 )\n"); + qglEnableVertexAttribArrayARB(ATTR_INDEX_POSITION2); + } + else + { + GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_POSITION2 )\n"); + qglDisableVertexAttribArrayARB(ATTR_INDEX_POSITION2); + } + } + + if(diff & ATTR_NORMAL2) + { + if(stateBits & ATTR_NORMAL2) + { + GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL2 )\n"); + qglEnableVertexAttribArrayARB(ATTR_INDEX_NORMAL2); + } + else + { + GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_NORMAL2 )\n"); + qglDisableVertexAttribArrayARB(ATTR_INDEX_NORMAL2); + } + } + +#ifdef USE_VERT_TANGENT_SPACE + if(diff & ATTR_TANGENT2) + { + if(stateBits & ATTR_TANGENT2) + { + GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT2 )\n"); + qglEnableVertexAttribArrayARB(ATTR_INDEX_TANGENT2); + } + else + { + GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_TANGENT2 )\n"); + qglDisableVertexAttribArrayARB(ATTR_INDEX_TANGENT2); + } + } + + if(diff & ATTR_BITANGENT2) + { + if(stateBits & ATTR_BITANGENT2) + { + GLimp_LogComment("qglEnableVertexAttribArrayARB( ATTR_INDEX_BITANGENT2 )\n"); + qglEnableVertexAttribArrayARB(ATTR_INDEX_BITANGENT2); + } + else + { + GLimp_LogComment("qglDisableVertexAttribArrayARB( ATTR_INDEX_BITANGENT2 )\n"); + qglDisableVertexAttribArrayARB(ATTR_INDEX_BITANGENT2); + } + } +#endif + + glState.vertexAttribsState = stateBits; +} + +void GLSL_VertexAttribPointers(uint32_t attribBits) +{ + if(!glState.currentVBO) + { + ri.Error(ERR_FATAL, "GL_VertexAttribPointers: no VBO bound"); + return; + } + + // don't just call LogComment, or we will get a call to va() every frame! + GLimp_LogComment(va("--- GL_VertexAttribPointers( %s ) ---\n", glState.currentVBO->name)); + + if((attribBits & ATTR_POSITION) && !(glState.vertexAttribPointersSet & ATTR_POSITION)) + { + GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_POSITION )\n"); + + qglVertexAttribPointerARB(ATTR_INDEX_POSITION, 3, GL_FLOAT, 0, glState.currentVBO->stride_xyz, BUFFER_OFFSET(glState.currentVBO->ofs_xyz + glState.vertexAttribsNewFrame * glState.currentVBO->size_xyz)); + glState.vertexAttribPointersSet |= ATTR_POSITION; + } + + if((attribBits & ATTR_TEXCOORD) && !(glState.vertexAttribPointersSet & ATTR_TEXCOORD)) + { + GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD )\n"); + + qglVertexAttribPointerARB(ATTR_INDEX_TEXCOORD0, 2, GL_FLOAT, 0, glState.currentVBO->stride_st, BUFFER_OFFSET(glState.currentVBO->ofs_st)); + glState.vertexAttribPointersSet |= ATTR_TEXCOORD; + } + + if((attribBits & ATTR_LIGHTCOORD) && !(glState.vertexAttribPointersSet & ATTR_LIGHTCOORD)) + { + GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_LIGHTCOORD )\n"); + + qglVertexAttribPointerARB(ATTR_INDEX_TEXCOORD1, 2, GL_FLOAT, 0, glState.currentVBO->stride_lightmap, BUFFER_OFFSET(glState.currentVBO->ofs_lightmap)); + glState.vertexAttribPointersSet |= ATTR_LIGHTCOORD; + } + + if((attribBits & ATTR_NORMAL) && !(glState.vertexAttribPointersSet & ATTR_NORMAL)) + { + GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_NORMAL )\n"); + + qglVertexAttribPointerARB(ATTR_INDEX_NORMAL, 3, GL_FLOAT, 0, glState.currentVBO->stride_normal, BUFFER_OFFSET(glState.currentVBO->ofs_normal + glState.vertexAttribsNewFrame * glState.currentVBO->size_normal)); + glState.vertexAttribPointersSet |= ATTR_NORMAL; + } + +#ifdef USE_VERT_TANGENT_SPACE + if((attribBits & ATTR_TANGENT) && !(glState.vertexAttribPointersSet & ATTR_TANGENT)) + { + GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_TANGENT )\n"); + + qglVertexAttribPointerARB(ATTR_INDEX_TANGENT, 3, GL_FLOAT, 0, glState.currentVBO->stride_tangent, BUFFER_OFFSET(glState.currentVBO->ofs_tangent + glState.vertexAttribsNewFrame * glState.currentVBO->size_normal)); // FIXME + glState.vertexAttribPointersSet |= ATTR_TANGENT; + } + + if((attribBits & ATTR_BITANGENT) && !(glState.vertexAttribPointersSet & ATTR_BITANGENT)) + { + GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_BITANGENT )\n"); + + qglVertexAttribPointerARB(ATTR_INDEX_BITANGENT, 3, GL_FLOAT, 0, glState.currentVBO->stride_bitangent, BUFFER_OFFSET(glState.currentVBO->ofs_bitangent + glState.vertexAttribsNewFrame * glState.currentVBO->size_normal)); // FIXME + glState.vertexAttribPointersSet |= ATTR_BITANGENT; + } +#endif + + if((attribBits & ATTR_COLOR) && !(glState.vertexAttribPointersSet & ATTR_COLOR)) + { + GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_COLOR )\n"); + + qglVertexAttribPointerARB(ATTR_INDEX_COLOR, 4, GL_FLOAT, 0, glState.currentVBO->stride_vertexcolor, BUFFER_OFFSET(glState.currentVBO->ofs_vertexcolor)); + glState.vertexAttribPointersSet |= ATTR_COLOR; + } + + if((attribBits & ATTR_LIGHTDIRECTION) && !(glState.vertexAttribPointersSet & ATTR_LIGHTDIRECTION)) + { + GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_LIGHTDIRECTION )\n"); + + qglVertexAttribPointerARB(ATTR_INDEX_LIGHTDIRECTION, 3, GL_FLOAT, 0, glState.currentVBO->stride_lightdir, BUFFER_OFFSET(glState.currentVBO->ofs_lightdir)); + glState.vertexAttribPointersSet |= ATTR_LIGHTDIRECTION; + } + + if((attribBits & ATTR_POSITION2) && !(glState.vertexAttribPointersSet & ATTR_POSITION2)) + { + GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_POSITION2 )\n"); + + qglVertexAttribPointerARB(ATTR_INDEX_POSITION2, 3, GL_FLOAT, 0, glState.currentVBO->stride_xyz, BUFFER_OFFSET(glState.currentVBO->ofs_xyz + glState.vertexAttribsOldFrame * glState.currentVBO->size_xyz)); + glState.vertexAttribPointersSet |= ATTR_POSITION2; + } + + if((attribBits & ATTR_NORMAL2) && !(glState.vertexAttribPointersSet & ATTR_NORMAL2)) + { + GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_NORMAL2 )\n"); + + qglVertexAttribPointerARB(ATTR_INDEX_NORMAL2, 3, GL_FLOAT, 0, glState.currentVBO->stride_normal, BUFFER_OFFSET(glState.currentVBO->ofs_normal + glState.vertexAttribsOldFrame * glState.currentVBO->size_normal)); + glState.vertexAttribPointersSet |= ATTR_NORMAL2; + } + +#ifdef USE_VERT_TANGENT_SPACE + if((attribBits & ATTR_TANGENT2) && !(glState.vertexAttribPointersSet & ATTR_TANGENT2)) + { + GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_TANGENT2 )\n"); + + qglVertexAttribPointerARB(ATTR_INDEX_TANGENT2, 3, GL_FLOAT, 0, glState.currentVBO->stride_tangent, BUFFER_OFFSET(glState.currentVBO->ofs_tangent + glState.vertexAttribsOldFrame * glState.currentVBO->size_normal)); // FIXME + glState.vertexAttribPointersSet |= ATTR_TANGENT2; + } + + if((attribBits & ATTR_BITANGENT2) && !(glState.vertexAttribPointersSet & ATTR_BITANGENT2)) + { + GLimp_LogComment("qglVertexAttribPointerARB( ATTR_INDEX_BITANGENT2 )\n"); + + qglVertexAttribPointerARB(ATTR_INDEX_BITANGENT2, 3, GL_FLOAT, 0, glState.currentVBO->stride_bitangent, BUFFER_OFFSET(glState.currentVBO->ofs_bitangent + glState.vertexAttribsOldFrame * glState.currentVBO->size_normal)); // FIXME + glState.vertexAttribPointersSet |= ATTR_BITANGENT2; + } +#endif + +} + +shaderProgram_t *GLSL_GetGenericShaderProgram(int stage) +{ + shaderStage_t *pStage = tess.xstages[stage]; + int shaderAttribs = 0; + + if (tess.fogNum && pStage->adjustColorsForFog) + { + shaderAttribs |= GENERICDEF_USE_FOG; + } + + if (pStage->bundle[1].image[0] && tess.shader->multitextureEnv) + { + shaderAttribs |= GENERICDEF_USE_LIGHTMAP; + } + + switch (pStage->rgbGen) + { + case CGEN_LIGHTING_DIFFUSE: + shaderAttribs |= GENERICDEF_USE_RGBAGEN; + break; + default: + break; + } + + switch (pStage->alphaGen) + { + case AGEN_LIGHTING_SPECULAR: + case AGEN_PORTAL: + case AGEN_FRESNEL: + shaderAttribs |= GENERICDEF_USE_RGBAGEN; + break; + default: + break; + } + + if (pStage->bundle[0].tcGen != TCGEN_TEXTURE) + { + shaderAttribs |= GENERICDEF_USE_TCGEN; + } + + if (tess.shader->numDeforms && !ShaderRequiresCPUDeforms(tess.shader)) + { + shaderAttribs |= GENERICDEF_USE_DEFORM_VERTEXES; + } + + if (glState.vertexAttribsInterpolation > 0.0f && backEnd.currentEntity && backEnd.currentEntity != &tr.worldEntity) + { + shaderAttribs |= GENERICDEF_USE_VERTEX_ANIMATION; + } + + return &tr.genericShader[shaderAttribs]; +} diff --git a/src/rend2/tr_image.c b/src/rend2/tr_image.c new file mode 100644 index 00000000..6b530c41 --- /dev/null +++ b/src/rend2/tr_image.c @@ -0,0 +1,3431 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_image.c +#include "tr_local.h" + +static byte s_intensitytable[256]; +static unsigned char s_gammatable[256]; + +int gl_filter_min = GL_LINEAR_MIPMAP_NEAREST; +int gl_filter_max = GL_LINEAR; + +#define FILE_HASH_SIZE 1024 +static image_t* hashTable[FILE_HASH_SIZE]; + +/* +** R_GammaCorrect +*/ +void R_GammaCorrect( byte *buffer, int bufSize ) { + int i; + + for ( i = 0; i < bufSize; i++ ) { + buffer[i] = s_gammatable[buffer[i]]; + } +} + +typedef struct { + char *name; + int minimize, maximize; +} textureMode_t; + +textureMode_t modes[] = { + {"GL_NEAREST", GL_NEAREST, GL_NEAREST}, + {"GL_LINEAR", GL_LINEAR, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR} +}; + +/* +================ +return a hash value for the filename +================ +*/ +static long generateHashValue( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower(fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (FILE_HASH_SIZE-1); + return hash; +} + +/* +=============== +GL_TextureMode +=============== +*/ +void GL_TextureMode( const char *string ) { + int i; + image_t *glt; + + for ( i=0 ; i< 6 ; i++ ) { + if ( !Q_stricmp( modes[i].name, string ) ) { + break; + } + } + + // hack to prevent trilinear from being set on voodoo, + // because their driver freaks... + if ( i == 5 && glConfig.hardwareType == GLHW_3DFX_2D3D ) { + ri.Printf( PRINT_ALL, "Refusing to set trilinear on a voodoo.\n" ); + i = 3; + } + + + if ( i == 6 ) { + ri.Printf (PRINT_ALL, "bad filter name\n"); + return; + } + + gl_filter_min = modes[i].minimize; + gl_filter_max = modes[i].maximize; + + // change all the existing mipmap texture objects + for ( i = 0 ; i < tr.numImages ; i++ ) { + glt = tr.images[ i ]; + if ( glt->flags & IMGFLAG_MIPMAP ) { + GL_Bind (glt); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); + } + } +} + +/* +=============== +R_SumOfUsedImages +=============== +*/ +int R_SumOfUsedImages( void ) { + int total; + int i; + + total = 0; + for ( i = 0; i < tr.numImages; i++ ) { + if ( tr.images[i]->frameUsed == tr.frameCount ) { + total += tr.images[i]->uploadWidth * tr.images[i]->uploadHeight; + } + } + + return total; +} + +/* +=============== +R_ImageList_f +=============== +*/ +void R_ImageList_f( void ) { +#if 1 + int i; + int estTotalSize = 0; + + ri.Printf(PRINT_ALL, "\n -w-- -h-- type -size- --name-------\n"); + + for ( i = 0 ; i < tr.numImages ; i++ ) + { + image_t *image = tr.images[i]; + char *format = "???? "; + char *sizeSuffix; + int estSize; + int displaySize; + + estSize = image->uploadHeight * image->uploadWidth; + + switch(image->internalFormat) + { + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: + format = "sDXT1"; + // 64 bits per 16 pixels, so 4 bits per pixel + estSize /= 2; + break; + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: + format = "sDXT5"; + // 128 bits per 16 pixels, so 1 byte per pixel + break; + case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB: + format = "sBPTC"; + // 128 bits per 16 pixels, so 1 byte per pixel + break; + case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT: + format = "LATC "; + // 128 bits per 16 pixels, so 1 byte per pixel + break; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + format = "DXT1 "; + // 64 bits per 16 pixels, so 4 bits per pixel + estSize /= 2; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + format = "DXT5 "; + // 128 bits per 16 pixels, so 1 byte per pixel + break; + case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB: + format = "BPTC "; + // 128 bits per 16 pixels, so 1 byte per pixel + break; + case GL_RGB4_S3TC: + format = "S3TC "; + // same as DXT1? + estSize /= 2; + break; + case GL_RGBA4: + case GL_RGBA8: + case GL_RGBA: + format = "RGBA "; + // 4 bytes per pixel + estSize *= 4; + break; + case GL_LUMINANCE8: + case GL_LUMINANCE16: + case GL_LUMINANCE: + format = "L "; + // 1 byte per pixel? + break; + case GL_RGB5: + case GL_RGB8: + case GL_RGB: + format = "RGB "; + // 3 bytes per pixel? + estSize *= 3; + break; + case GL_LUMINANCE8_ALPHA8: + case GL_LUMINANCE16_ALPHA16: + case GL_LUMINANCE_ALPHA: + format = "LA "; + // 2 bytes per pixel? + estSize *= 2; + break; + case GL_SRGB_EXT: + case GL_SRGB8_EXT: + format = "sRGB "; + // 3 bytes per pixel? + estSize *= 3; + break; + case GL_SRGB_ALPHA_EXT: + case GL_SRGB8_ALPHA8_EXT: + format = "sRGBA"; + // 4 bytes per pixel? + estSize *= 4; + break; + case GL_SLUMINANCE_EXT: + case GL_SLUMINANCE8_EXT: + format = "sL "; + // 1 byte per pixel? + break; + case GL_SLUMINANCE_ALPHA_EXT: + case GL_SLUMINANCE8_ALPHA8_EXT: + format = "sLA "; + // 2 byte per pixel? + estSize *= 2; + break; + } + + // mipmap adds about 50% + if (image->flags & IMGFLAG_MIPMAP) + estSize += estSize / 2; + + sizeSuffix = "b "; + displaySize = estSize; + + if (displaySize > 1024) + { + displaySize /= 1024; + sizeSuffix = "kb"; + } + + if (displaySize > 1024) + { + displaySize /= 1024; + sizeSuffix = "Mb"; + } + + if (displaySize > 1024) + { + displaySize /= 1024; + sizeSuffix = "Gb"; + } + + ri.Printf(PRINT_ALL, "%4i: %4ix%4i %s %4i%s %s\n", i, image->uploadWidth, image->uploadHeight, format, displaySize, sizeSuffix, image->imgName); + estTotalSize += estSize; + } + + ri.Printf (PRINT_ALL, " ---------\n"); + ri.Printf (PRINT_ALL, " approx %i bytes\n", estTotalSize); + ri.Printf (PRINT_ALL, " %i total images\n\n", tr.numImages ); +#else + int i; + image_t *image; + int texels; + const char *yesno[] = { + "no ", "yes" + }; + + ri.Printf (PRINT_ALL, "\n -w-- -h-- -mm- -TMU- -if-- wrap --name-------\n"); + texels = 0; + + for ( i = 0 ; i < tr.numImages ; i++ ) { + image = tr.images[ i ]; + + texels += image->uploadWidth*image->uploadHeight; + ri.Printf (PRINT_ALL, "%4i: %4i %4i %s %d ", + i, image->uploadWidth, image->uploadHeight, yesno[(image->flags & IMGFLAG_MIPMAP) ? 1 : 0], image->TMU ); + switch ( image->internalFormat ) { + case 1: + ri.Printf( PRINT_ALL, "I " ); + break; + case 2: + ri.Printf( PRINT_ALL, "IA " ); + break; + case 3: + ri.Printf( PRINT_ALL, "RGB " ); + break; + case 4: + ri.Printf( PRINT_ALL, "RGBA " ); + break; + case GL_RGBA8: + ri.Printf( PRINT_ALL, "RGBA8" ); + break; + case GL_RGB8: + ri.Printf( PRINT_ALL, "RGB8" ); + break; + case GL_RGB4_S3TC: + ri.Printf( PRINT_ALL, "S3TC " ); + break; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + ri.Printf( PRINT_ALL, "DXT1 " ); + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + ri.Printf( PRINT_ALL, "DXT5 " ); + break; + case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT: + ri.Printf( PRINT_ALL, "LATC " ); + break; + case GL_RGBA4: + ri.Printf( PRINT_ALL, "RGBA4" ); + break; + case GL_RGB5: + ri.Printf( PRINT_ALL, "RGB5 " ); + break; + case GL_SRGB_EXT: + ri.Printf( PRINT_ALL, "sRGB " ); + break; + case GL_SRGB8_EXT: + ri.Printf( PRINT_ALL, "sRGB8" ); + break; + case GL_SRGB_ALPHA_EXT: + case GL_SRGB8_ALPHA8_EXT: + ri.Printf( PRINT_ALL, "sRGBA" ); + break; + /* + case GL_SLUMINANCE_EXT: + break; + case GL_SLUMINANCE8_EXT: + break; + case GL_SLUMINANCE_ALPHA_EXT: + break; + case GL_SLUMINANCE8_ALPHA8_EXT: + break; + */ + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: + ri.Printf( PRINT_ALL, "sDXT1" ); + break; + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: + ri.Printf( PRINT_ALL, "sDXT5" ); + break; + case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB: + ri.Printf( PRINT_ALL, "BPTC " ); + break; + case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB: + ri.Printf( PRINT_ALL, "sBPTC" ); + break; + default: + ri.Printf( PRINT_ALL, "???? " ); + } + + if (image->flags & IMGFLAG_CLAMPTOEDGE) + ri.Printf( PRINT_ALL, "clmp " ); + else + ri.Printf( PRINT_ALL, "rept " ); + + ri.Printf( PRINT_ALL, " %s\n", image->imgName ); + } + ri.Printf (PRINT_ALL, " ---------\n"); + ri.Printf (PRINT_ALL, " %i total texels (not including mipmaps)\n", texels); + ri.Printf (PRINT_ALL, " %i total images\n\n", tr.numImages ); +#endif +} + +//======================================================================= + +/* +================ +ResampleTexture + +Used to resample images in a more general than quartering fashion. + +This will only be filtered properly if the resampled size +is greater than half the original size. + +If a larger shrinking is needed, use the mipmap function +before or after. +================ +*/ +static void ResampleTexture( byte *in, int inwidth, int inheight, byte *out, + int outwidth, int outheight ) { + int i, j; + byte *inrow, *inrow2; + int frac, fracstep; + int p1[2048], p2[2048]; + byte *pix1, *pix2, *pix3, *pix4; + + if (outwidth>2048) + ri.Error(ERR_DROP, "ResampleTexture: max width"); + + fracstep = inwidth*0x10000/outwidth; + + frac = fracstep>>2; + for ( i=0 ; i>16); + frac += fracstep; + } + frac = 3*(fracstep>>2); + for ( i=0 ; i>16); + frac += fracstep; + } + + for (i=0 ; i> 1; + for (j=0 ; j>2; + *out++ = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2; + *out++ = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2; + *out++ = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2; + } + } +} + +static void RGBAtoYCoCgA(const byte *in, byte *out, int width, int height) +{ + int x, y; + + for (y = 0; y < height; y++) + { + const byte *inbyte = in + y * width * 4; + byte *outbyte = out + y * width * 4; + + for (x = 0; x < width; x++) + { + byte r, g, b, a, rb2; + + r = *inbyte++; + g = *inbyte++; + b = *inbyte++; + a = *inbyte++; + rb2 = (r + b) >> 1; + + *outbyte++ = (g + rb2) >> 1; // Y = R/4 + G/2 + B/4 + *outbyte++ = (r - b + 256) >> 1; // Co = R/2 - B/2 + *outbyte++ = (g - rb2 + 256) >> 1; // Cg = -R/4 + G/2 - B/4 + *outbyte++ = a; + } + } +} + +static void YCoCgAtoRGBA(const byte *in, byte *out, int width, int height) +{ + int x, y; + + for (y = 0; y < height; y++) + { + const byte *inbyte = in + y * width * 4; + byte *outbyte = out + y * width * 4; + + for (x = 0; x < width; x++) + { + byte _Y, Co, Cg, a; + + _Y = *inbyte++; + Co = *inbyte++; + Cg = *inbyte++; + a = *inbyte++; + + *outbyte++ = CLAMP(_Y + Co - Cg, 0, 255); // R = Y + Co - Cg + *outbyte++ = CLAMP(_Y + Cg - 128, 0, 255); // G = Y + Cg + *outbyte++ = CLAMP(_Y - Co - Cg + 256, 0, 255); // B = Y - Co - Cg + *outbyte++ = a; + } + } +} + + +// uses a sobel filter to change a texture to a normal map +static void RGBAtoNormal(const byte *in, byte *out, int width, int height, qboolean clampToEdge) +{ + int x, y, max; + + // convert to heightmap, storing in alpha + // same as converting to Y in YCoCg + max = 1; + for (y = 0; y < height; y++) + { + const byte *inbyte = in + y * width * 4; + byte *outbyte = out + y * width * 4 + 3; + + for (x = 0; x < width; x++) + { + *outbyte = (inbyte[0] >> 2) + (inbyte[1] >> 1) + (inbyte[2] >> 2); + max = MAX(max, *outbyte); + outbyte += 4; + inbyte += 4; + } + } + + // level out heights + if (max < 255) + { + for (y = 0; y < height; y++) + { + byte *outbyte = out + y * width * 4 + 3; + + for (x = 0; x < width; x++) + { + *outbyte = *outbyte + (255 - max); + outbyte += 4; + } + } + } + + + // now run sobel filter over height values to generate X and Y + // then normalize + for (y = 0; y < height; y++) + { + byte *outbyte = out + y * width * 4; + + for (x = 0; x < width; x++) + { + // 0 1 2 + // 3 4 5 + // 6 7 8 + + byte s[9]; + int x2, y2, i; + vec3_t normal; + + i = 0; + for (y2 = -1; y2 <= 1; y2++) + { + int src_y = y + y2; + + if (clampToEdge) + { + src_y = CLAMP(src_y, 0, height - 1); + } + else + { + src_y = (src_y + height) % height; + } + + + for (x2 = -1; x2 <= 1; x2++) + { + int src_x = x + x2; + + if (clampToEdge) + { + src_x = CLAMP(src_x, 0, height - 1); + } + else + { + src_x = (src_x + height) % height; + } + + s[i++] = *(out + (src_y * width + src_x) * 4 + 3); + } + } + + normal[0] = s[0] - s[2] + + 2 * s[3] - 2 * s[5] + + s[6] - s[8]; + + normal[1] = s[0] + 2 * s[1] + s[2] + + - s[6] - 2 * s[7] - s[8]; + + normal[2] = s[4] * 4; + + if (!VectorNormalize2(normal, normal)) + { + VectorSet(normal, 0, 0, 1); + } + + *outbyte++ = FloatToOffsetByte(normal[0]); + *outbyte++ = FloatToOffsetByte(normal[1]); + *outbyte++ = FloatToOffsetByte(normal[2]); + outbyte++; + } + } +} + +#define COPYSAMPLE(a,b) *(unsigned int *)(a) = *(unsigned int *)(b) + +// based on Fast Curve Based Interpolation +// from Fast Artifacts-Free Image Interpolation (http://www.andreagiachetti.it/icbi/) +// assumes data has a 2 pixel thick border of clamped or wrapped data +// expects data to be a grid with even (0, 0), (2, 0), (0, 2), (2, 2) etc pixels filled +// only performs FCBI on specified component +static void DoFCBI(byte *in, byte *out, int width, int height, int component) +{ + int x, y; + byte *outbyte, *inbyte; + + // copy in to out + for (y = 2; y < height - 2; y += 2) + { + inbyte = in + (y * width + 2) * 4 + component; + outbyte = out + (y * width + 2) * 4 + component; + + for (x = 2; x < width - 2; x += 2) + { + *outbyte = *inbyte; + outbyte += 8; + inbyte += 8; + } + } + + for (y = 3; y < height - 3; y += 2) + { + // diagonals + // + // NWp - northwest interpolated pixel + // NEp - northeast interpolated pixel + // NWd - northwest first derivative + // NEd - northeast first derivative + // NWdd - northwest second derivative + // NEdd - northeast second derivative + // + // Uses these samples: + // + // 0 + // - - a - b - - + // - - - - - - - + // c - d - e - f + // 0 - - - - - - - + // g - h - i - j + // - - - - - - - + // - - k - l - - + // + // x+2 uses these samples: + // + // 0 + // - - - - a - b - - + // - - - - - - - - - + // - - c - d - e - f + // 0 - - - - - - - - - + // - - g - h - i - j + // - - - - - - - - - + // - - - - k - l - - + // + // so we can reuse 8 of them on next iteration + // + // a=b, c=d, d=e, e=f, g=h, h=i, i=j, k=l + // + // only b, f, j, and l need to be sampled on next iteration + + byte sa, sb, sc, sd, se, sf, sg, sh, si, sj, sk, sl; + byte *line1, *line2, *line3, *line4; + + x = 3; + + // optimization one + // SAMPLE2(sa, x-1, y-3); + //SAMPLE2(sc, x-3, y-1); SAMPLE2(sd, x-1, y-1); SAMPLE2(se, x+1, y-1); + //SAMPLE2(sg, x-3, y+1); SAMPLE2(sh, x-1, y+1); SAMPLE2(si, x+1, y+1); + // SAMPLE2(sk, x-1, y+3); + + // optimization two + line1 = in + ((y - 3) * width + (x - 1)) * 4 + component; + line2 = in + ((y - 1) * width + (x - 3)) * 4 + component; + line3 = in + ((y + 1) * width + (x - 3)) * 4 + component; + line4 = in + ((y + 3) * width + (x - 1)) * 4 + component; + + // COPYSAMPLE(sa, line1); line1 += 8; + //COPYSAMPLE(sc, line2); line2 += 8; COPYSAMPLE(sd, line2); line2 += 8; COPYSAMPLE(se, line2); line2 += 8; + //COPYSAMPLE(sg, line3); line3 += 8; COPYSAMPLE(sh, line3); line3 += 8; COPYSAMPLE(si, line3); line3 += 8; + // COPYSAMPLE(sk, line4); line4 += 8; + + sa = *line1; line1 += 8; + sc = *line2; line2 += 8; sd = *line2; line2 += 8; se = *line2; line2 += 8; + sg = *line3; line3 += 8; sh = *line3; line3 += 8; si = *line3; line3 += 8; + sk = *line4; line4 += 8; + + outbyte = out + (y * width + x) * 4 + component; + + for ( ; x < width - 3; x += 2) + { + int NWd, NEd, NWp, NEp; + + // original + // SAMPLE2(sa, x-1, y-3); SAMPLE2(sb, x+1, y-3); + //SAMPLE2(sc, x-3, y-1); SAMPLE2(sd, x-1, y-1); SAMPLE2(se, x+1, y-1); SAMPLE2(sf, x+3, y-1); + //SAMPLE2(sg, x-3, y+1); SAMPLE2(sh, x-1, y+1); SAMPLE2(si, x+1, y+1); SAMPLE2(sj, x+3, y+1); + // SAMPLE2(sk, x-1, y+3); SAMPLE2(sl, x+1, y+3); + + // optimization one + //SAMPLE2(sb, x+1, y-3); + //SAMPLE2(sf, x+3, y-1); + //SAMPLE2(sj, x+3, y+1); + //SAMPLE2(sl, x+1, y+3); + + // optimization two + //COPYSAMPLE(sb, line1); line1 += 8; + //COPYSAMPLE(sf, line2); line2 += 8; + //COPYSAMPLE(sj, line3); line3 += 8; + //COPYSAMPLE(sl, line4); line4 += 8; + + sb = *line1; line1 += 8; + sf = *line2; line2 += 8; + sj = *line3; line3 += 8; + sl = *line4; line4 += 8; + + NWp = sd + si; + NEp = se + sh; + NWd = abs(sd - si); + NEd = abs(se - sh); + + if (NWd > 100 || NEd > 100 || abs(NWp-NEp) > 200) + { + if (NWd < NEd) + *outbyte = NWp >> 1; + else + *outbyte = NEp >> 1; + } + else + { + int NWdd, NEdd; + + //NEdd = abs(sg + sd + sb - 3 * (se + sh) + sk + si + sf); + //NWdd = abs(sa + se + sj - 3 * (sd + si) + sc + sh + sl); + NEdd = abs(sg + sb - 3 * NEp + sk + sf + NWp); + NWdd = abs(sa + sj - 3 * NWp + sc + sl + NEp); + + if (NWdd > NEdd) + *outbyte = NWp >> 1; + else + *outbyte = NEp >> 1; + } + + outbyte += 8; + + // COPYSAMPLE(sa, sb); + //COPYSAMPLE(sc, sd); COPYSAMPLE(sd, se); COPYSAMPLE(se, sf); + //COPYSAMPLE(sg, sh); COPYSAMPLE(sh, si); COPYSAMPLE(si, sj); + // COPYSAMPLE(sk, sl); + + sa = sb; + sc = sd; sd = se; se = sf; + sg = sh; sh = si; si = sj; + sk = sl; + } + } + + // hack: copy out to in again + for (y = 3; y < height - 3; y += 2) + { + inbyte = out + (y * width + 3) * 4 + component; + outbyte = in + (y * width + 3) * 4 + component; + + for (x = 3; x < width - 3; x += 2) + { + *outbyte = *inbyte; + outbyte += 8; + inbyte += 8; + } + } + + for (y = 2; y < height - 3; y++) + { + // horizontal & vertical + // + // hp - horizontally interpolated pixel + // vp - vertically interpolated pixel + // hd - horizontal first derivative + // vd - vertical first derivative + // hdd - horizontal second derivative + // vdd - vertical second derivative + // Uses these samples: + // + // 0 + // - a - b - + // c - d - e + // 0 - f - g - + // h - i - j + // - k - l - + // + // x+2 uses these samples: + // + // 0 + // - - - a - b - + // - - c - d - e + // 0 - - - f - g - + // - - h - i - j + // - - - k - l - + // + // so we can reuse 7 of them on next iteration + // + // a=b, c=d, d=e, f=g, h=i, i=j, k=l + // + // only b, e, g, j, and l need to be sampled on next iteration + + byte sa, sb, sc, sd, se, sf, sg, sh, si, sj, sk, sl; + byte *line1, *line2, *line3, *line4, *line5; + + //x = (y + 1) % 2; + x = (y + 1) % 2 + 2; + + // optimization one + // SAMPLE2(sa, x-1, y-2); + //SAMPLE2(sc, x-2, y-1); SAMPLE2(sd, x, y-1); + // SAMPLE2(sf, x-1, y ); + //SAMPLE2(sh, x-2, y+1); SAMPLE2(si, x, y+1); + // SAMPLE2(sk, x-1, y+2); + + line1 = in + ((y - 2) * width + (x - 1)) * 4 + component; + line2 = in + ((y - 1) * width + (x - 2)) * 4 + component; + line3 = in + ((y ) * width + (x - 1)) * 4 + component; + line4 = in + ((y + 1) * width + (x - 2)) * 4 + component; + line5 = in + ((y + 2) * width + (x - 1)) * 4 + component; + + // COPYSAMPLE(sa, line1); line1 += 8; + //COPYSAMPLE(sc, line2); line2 += 8; COPYSAMPLE(sd, line2); line2 += 8; + // COPYSAMPLE(sf, line3); line3 += 8; + //COPYSAMPLE(sh, line4); line4 += 8; COPYSAMPLE(si, line4); line4 += 8; + // COPYSAMPLE(sk, line5); line5 += 8; + + sa = *line1; line1 += 8; + sc = *line2; line2 += 8; sd = *line2; line2 += 8; + sf = *line3; line3 += 8; + sh = *line4; line4 += 8; si = *line4; line4 += 8; + sk = *line5; line5 += 8; + + outbyte = out + (y * width + x) * 4 + component; + + for ( ; x < width - 3; x+=2) + { + int hd, vd, hp, vp; + + // SAMPLE2(sa, x-1, y-2); SAMPLE2(sb, x+1, y-2); + //SAMPLE2(sc, x-2, y-1); SAMPLE2(sd, x, y-1); SAMPLE2(se, x+2, y-1); + // SAMPLE2(sf, x-1, y ); SAMPLE2(sg, x+1, y ); + //SAMPLE2(sh, x-2, y+1); SAMPLE2(si, x, y+1); SAMPLE2(sj, x+2, y+1); + // SAMPLE2(sk, x-1, y+2); SAMPLE2(sl, x+1, y+2); + + // optimization one + //SAMPLE2(sb, x+1, y-2); + //SAMPLE2(se, x+2, y-1); + //SAMPLE2(sg, x+1, y ); + //SAMPLE2(sj, x+2, y+1); + //SAMPLE2(sl, x+1, y+2); + + //COPYSAMPLE(sb, line1); line1 += 8; + //COPYSAMPLE(se, line2); line2 += 8; + //COPYSAMPLE(sg, line3); line3 += 8; + //COPYSAMPLE(sj, line4); line4 += 8; + //COPYSAMPLE(sl, line5); line5 += 8; + + sb = *line1; line1 += 8; + se = *line2; line2 += 8; + sg = *line3; line3 += 8; + sj = *line4; line4 += 8; + sl = *line5; line5 += 8; + + hp = sf + sg; + vp = sd + si; + hd = abs(sf - sg); + vd = abs(sd - si); + + if (hd > 100 || vd > 100 || abs(hp-vp) > 200) + { + if (hd < vd) + *outbyte = hp >> 1; + else + *outbyte = vp >> 1; + } + else + { + int hdd, vdd; + + //hdd = abs(sc[i] + sd[i] + se[i] - 3 * (sf[i] + sg[i]) + sh[i] + si[i] + sj[i]); + //vdd = abs(sa[i] + sf[i] + sk[i] - 3 * (sd[i] + si[i]) + sb[i] + sg[i] + sl[i]); + + hdd = abs(sc + se - 3 * hp + sh + sj + vp); + vdd = abs(sa + sk - 3 * vp + sb + sl + hp); + + if (hdd > vdd) + *outbyte = hp >> 1; + else + *outbyte = vp >> 1; + } + + outbyte += 8; + + // COPYSAMPLE(sa, sb); + //COPYSAMPLE(sc, sd); COPYSAMPLE(sd, se); + // COPYSAMPLE(sf, sg); + //COPYSAMPLE(sh, si); COPYSAMPLE(si, sj); + // COPYSAMPLE(sk, sl); + sa = sb; + sc = sd; sd = se; + sf = sg; + sh = si; si = sj; + sk = sl; + } + } +} + +// Similar to FCBI, but throws out the second order derivatives for speed +static void DoFCBIQuick(byte *in, byte *out, int width, int height, int component) +{ + int x, y; + byte *outbyte, *inbyte; + + // copy in to out + for (y = 2; y < height - 2; y += 2) + { + inbyte = in + (y * width + 2) * 4 + component; + outbyte = out + (y * width + 2) * 4 + component; + + for (x = 2; x < width - 2; x += 2) + { + *outbyte = *inbyte; + outbyte += 8; + inbyte += 8; + } + } + + for (y = 3; y < height - 4; y += 2) + { + byte sd, se, sh, si; + byte *line2, *line3; + + x = 3; + + line2 = in + ((y - 1) * width + (x - 1)) * 4 + component; + line3 = in + ((y + 1) * width + (x - 1)) * 4 + component; + + sd = *line2; line2 += 8; + sh = *line3; line3 += 8; + + outbyte = out + (y * width + x) * 4 + component; + + for ( ; x < width - 4; x += 2) + { + int NWd, NEd, NWp, NEp; + + se = *line2; line2 += 8; + si = *line3; line3 += 8; + + NWp = sd + si; + NEp = se + sh; + NWd = abs(sd - si); + NEd = abs(se - sh); + + if (NWd < NEd) + *outbyte = NWp >> 1; + else + *outbyte = NEp >> 1; + + outbyte += 8; + + sd = se; + sh = si; + } + } + + // hack: copy out to in again + for (y = 3; y < height - 3; y += 2) + { + inbyte = out + (y * width + 3) * 4 + component; + outbyte = in + (y * width + 3) * 4 + component; + + for (x = 3; x < width - 3; x += 2) + { + *outbyte = *inbyte; + outbyte += 8; + inbyte += 8; + } + } + + for (y = 2; y < height - 3; y++) + { + byte sd, sf, sg, si; + byte *line2, *line3, *line4; + + x = (y + 1) % 2 + 2; + + line2 = in + ((y - 1) * width + (x )) * 4 + component; + line3 = in + ((y ) * width + (x - 1)) * 4 + component; + line4 = in + ((y + 1) * width + (x )) * 4 + component; + + outbyte = out + (y * width + x) * 4 + component; + + sf = *line3; line3 += 8; + + for ( ; x < width - 3; x+=2) + { + int hd, vd, hp, vp; + + sd = *line2; line2 += 8; + sg = *line3; line3 += 8; + si = *line4; line4 += 8; + + hp = sf + sg; + vp = sd + si; + hd = abs(sf - sg); + vd = abs(sd - si); + + if (hd < vd) + *outbyte = hp >> 1; + else + *outbyte = vp >> 1; + + outbyte += 8; + + sf = sg; + } + } +} + +// Similar to DoFCBIQuick, but just takes the average instead of checking derivatives +// as well, this operates on all four components +static void DoLinear(byte *in, byte *out, int width, int height) +{ + int x, y, i; + byte *outbyte, *inbyte; + + // copy in to out + for (y = 2; y < height - 2; y += 2) + { + x = 2; + + inbyte = in + (y * width + x) * 4; + outbyte = out + (y * width + x) * 4; + + for ( ; x < width - 2; x += 2) + { + COPYSAMPLE(outbyte, inbyte); + outbyte += 8; + inbyte += 8; + } + } + + for (y = 1; y < height - 1; y += 2) + { + byte sd[4], se[4], sh[4], si[4]; + byte *line2, *line3; + + x = 1; + + line2 = in + ((y - 1) * width + (x - 1)) * 4; + line3 = in + ((y + 1) * width + (x - 1)) * 4; + + COPYSAMPLE(sd, line2); line2 += 8; + COPYSAMPLE(sh, line3); line3 += 8; + + outbyte = out + (y * width + x) * 4; + + for ( ; x < width - 1; x += 2) + { + COPYSAMPLE(se, line2); line2 += 8; + COPYSAMPLE(si, line3); line3 += 8; + + for (i = 0; i < 4; i++) + { + *outbyte++ = (sd[i] + si[i] + se[i] + sh[i]) >> 2; + } + + outbyte += 4; + + COPYSAMPLE(sd, se); + COPYSAMPLE(sh, si); + } + } + + // hack: copy out to in again + for (y = 1; y < height - 1; y += 2) + { + x = 1; + + inbyte = out + (y * width + x) * 4; + outbyte = in + (y * width + x) * 4; + + for ( ; x < width - 1; x += 2) + { + COPYSAMPLE(outbyte, inbyte); + outbyte += 8; + inbyte += 8; + } + } + + for (y = 1; y < height - 1; y++) + { + byte sd[4], sf[4], sg[4], si[4]; + byte *line2, *line3, *line4; + + x = y % 2 + 1; + + line2 = in + ((y - 1) * width + (x )) * 4; + line3 = in + ((y ) * width + (x - 1)) * 4; + line4 = in + ((y + 1) * width + (x )) * 4; + + COPYSAMPLE(sf, line3); line3 += 8; + + outbyte = out + (y * width + x) * 4; + + for ( ; x < width - 1; x += 2) + { + COPYSAMPLE(sd, line2); line2 += 8; + COPYSAMPLE(sg, line3); line3 += 8; + COPYSAMPLE(si, line4); line4 += 8; + + for (i = 0; i < 4; i++) + { + *outbyte++ = (sf[i] + sg[i] + sd[i] + si[i]) >> 2; + } + + outbyte += 4; + + COPYSAMPLE(sf, sg); + } + } +} + + +static void ExpandHalfTextureToGrid( byte *data, int width, int height) +{ + int x, y; + + for (y = height / 2; y > 0; y--) + { + byte *outbyte = data + ((y * 2 - 1) * (width) - 2) * 4; + byte *inbyte = data + (y * (width / 2) - 1) * 4; + + for (x = width / 2; x > 0; x--) + { + COPYSAMPLE(outbyte, inbyte); + + outbyte -= 8; + inbyte -= 4; + } + } +} + +static void FillInNormalizedZ(const byte *in, byte *out, int width, int height) +{ + int x, y; + + for (y = 0; y < height; y++) + { + const byte *inbyte = in + y * width * 4; + byte *outbyte = out + y * width * 4; + + for (x = 0; x < width; x++) + { + byte nx, ny, nz, h; + float fnx, fny, fll, fnz; + + nx = *inbyte++; + ny = *inbyte++; + inbyte++; + h = *inbyte++; + + fnx = OffsetByteToFloat(nx); + fny = OffsetByteToFloat(ny); + fll = 1.0f - fnx * fnx - fny * fny; + if (fll >= 0.0f) + fnz = (float)sqrt(fll); + else + fnz = 0.0f; + + nz = FloatToOffsetByte(fnz); + + *outbyte++ = nx; + *outbyte++ = ny; + *outbyte++ = nz; + *outbyte++ = h; + } + } +} + + +// size must be even +#define WORKBLOCK_SIZE 128 +#define WORKBLOCK_BORDER 4 +#define WORKBLOCK_REALSIZE (WORKBLOCK_SIZE + WORKBLOCK_BORDER * 2) + +// assumes that data has already been expanded into a 2x2 grid +static void FCBIByBlock(byte *data, int width, int height, qboolean clampToEdge, qboolean normalized) +{ + byte workdata[WORKBLOCK_REALSIZE * WORKBLOCK_REALSIZE * 4]; + byte outdata[WORKBLOCK_REALSIZE * WORKBLOCK_REALSIZE * 4]; + byte *inbyte, *outbyte; + int x, y; + int srcx, srcy; + + ExpandHalfTextureToGrid(data, width, height); + + for (y = 0; y < height; y += WORKBLOCK_SIZE) + { + for (x = 0; x < width; x += WORKBLOCK_SIZE) + { + int x2, y2; + int workwidth, workheight, fullworkwidth, fullworkheight; + + workwidth = MIN(WORKBLOCK_SIZE, width - x); + workheight = MIN(WORKBLOCK_SIZE, height - y); + + fullworkwidth = workwidth + WORKBLOCK_BORDER * 2; + fullworkheight = workheight + WORKBLOCK_BORDER * 2; + + //memset(workdata, 0, WORKBLOCK_REALSIZE * WORKBLOCK_REALSIZE * 4); + + // fill in work block + for (y2 = 0; y2 < fullworkheight; y2 += 2) + { + srcy = y + y2 - WORKBLOCK_BORDER; + + if (clampToEdge) + { + srcy = CLAMP(srcy, 0, height - 2); + } + else + { + srcy = (srcy + height) % height; + } + + outbyte = workdata + y2 * fullworkwidth * 4; + inbyte = data + srcy * width * 4; + + for (x2 = 0; x2 < fullworkwidth; x2 += 2) + { + srcx = x + x2 - WORKBLOCK_BORDER; + + if (clampToEdge) + { + srcx = CLAMP(srcx, 0, width - 2); + } + else + { + srcx = (srcx + width) % width; + } + + COPYSAMPLE(outbyte, inbyte + srcx * 4); + outbyte += 8; + } + } + + // submit work block + DoLinear(workdata, outdata, fullworkwidth, fullworkheight); + + if (!normalized) + { + switch (r_imageUpsampleType->integer) + { + case 0: + break; + case 1: + DoFCBIQuick(workdata, outdata, fullworkwidth, fullworkheight, 0); + break; + case 2: + default: + DoFCBI(workdata, outdata, fullworkwidth, fullworkheight, 0); + break; + } + } + else + { + switch (r_imageUpsampleType->integer) + { + case 0: + break; + case 1: + DoFCBIQuick(workdata, outdata, fullworkwidth, fullworkheight, 0); + DoFCBIQuick(workdata, outdata, fullworkwidth, fullworkheight, 1); + break; + case 2: + default: + DoFCBI(workdata, outdata, fullworkwidth, fullworkheight, 0); + DoFCBI(workdata, outdata, fullworkwidth, fullworkheight, 1); + break; + } + } + + // copy back work block + for (y2 = 0; y2 < workheight; y2++) + { + inbyte = outdata + ((y2 + WORKBLOCK_BORDER) * fullworkwidth + WORKBLOCK_BORDER) * 4; + outbyte = data + ((y + y2) * width + x) * 4; + for (x2 = 0; x2 < workwidth; x2++) + { + COPYSAMPLE(outbyte, inbyte); + outbyte += 4; + inbyte += 4; + } + } + } + } +} +#undef COPYSAMPLE + +/* +================ +R_LightScaleTexture + +Scale up the pixel values in a texture to increase the +lighting range +================ +*/ +void R_LightScaleTexture (byte *in, int inwidth, int inheight, qboolean only_gamma ) +{ + if ( only_gamma ) + { + if ( !glConfig.deviceSupportsGamma ) + { + int i, c; + byte *p; + + p = in; + + c = inwidth*inheight; + for (i=0 ; i> 1; + outHeight = inHeight >> 1; + temp = ri.Hunk_AllocateTempMemory( outWidth * outHeight * 4 ); + + inWidthMask = inWidth - 1; + inHeightMask = inHeight - 1; + + for ( i = 0 ; i < outHeight ; i++ ) { + for ( j = 0 ; j < outWidth ; j++ ) { + outpix = (byte *) ( temp + i * outWidth + j ); + for ( k = 0 ; k < 4 ; k++ ) { + total = + 1 * (&in[ 4*(((i*2-1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask)) ])[k] + + 2 * (&in[ 4*(((i*2-1)&inHeightMask)*inWidth + ((j*2 )&inWidthMask)) ])[k] + + 2 * (&in[ 4*(((i*2-1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask)) ])[k] + + 1 * (&in[ 4*(((i*2-1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask)) ])[k] + + + 2 * (&in[ 4*(((i*2 )&inHeightMask)*inWidth + ((j*2-1)&inWidthMask)) ])[k] + + 4 * (&in[ 4*(((i*2 )&inHeightMask)*inWidth + ((j*2 )&inWidthMask)) ])[k] + + 4 * (&in[ 4*(((i*2 )&inHeightMask)*inWidth + ((j*2+1)&inWidthMask)) ])[k] + + 2 * (&in[ 4*(((i*2 )&inHeightMask)*inWidth + ((j*2+2)&inWidthMask)) ])[k] + + + 2 * (&in[ 4*(((i*2+1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask)) ])[k] + + 4 * (&in[ 4*(((i*2+1)&inHeightMask)*inWidth + ((j*2 )&inWidthMask)) ])[k] + + 4 * (&in[ 4*(((i*2+1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask)) ])[k] + + 2 * (&in[ 4*(((i*2+1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask)) ])[k] + + + 1 * (&in[ 4*(((i*2+2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask)) ])[k] + + 2 * (&in[ 4*(((i*2+2)&inHeightMask)*inWidth + ((j*2 )&inWidthMask)) ])[k] + + 2 * (&in[ 4*(((i*2+2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask)) ])[k] + + 1 * (&in[ 4*(((i*2+2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask)) ])[k]; + outpix[k] = total / 36; + } + } + } + + Com_Memcpy( in, temp, outWidth * outHeight * 4 ); + ri.Hunk_FreeTempMemory( temp ); +} + + +static void R_MipMapsRGB( byte *in, int inWidth, int inHeight) +{ + int i, j, k; + int outWidth, outHeight; + byte *temp; + + outWidth = inWidth >> 1; + outHeight = inHeight >> 1; + temp = ri.Hunk_AllocateTempMemory( outWidth * outHeight * 4 ); + + for ( i = 0 ; i < outHeight ; i++ ) { + byte *outbyte = temp + ( i * outWidth ) * 4; + byte *inbyte1 = in + ( i * 2 * inWidth ) * 4; + byte *inbyte2 = in + ( (i * 2 + 1) * inWidth ) * 4; + for ( j = 0 ; j < outWidth ; j++ ) { + for ( k = 0 ; k < 3 ; k++ ) { + float total, current; + + current = ByteToFloat(inbyte1[0]); total = sRGBtoRGB(current); + current = ByteToFloat(inbyte1[4]); total += sRGBtoRGB(current); + current = ByteToFloat(inbyte2[0]); total += sRGBtoRGB(current); + current = ByteToFloat(inbyte2[4]); total += sRGBtoRGB(current); + + total *= 0.25f; + + inbyte1++; + inbyte2++; + + current = RGBtosRGB(total); + *outbyte++ = FloatToByte(current); + } + *outbyte++ = (inbyte1[0] + inbyte1[4] + inbyte2[0] + inbyte2[4]) >> 2; + inbyte1 += 5; + inbyte2 += 5; + } + } + + Com_Memcpy( in, temp, outWidth * outHeight * 4 ); + ri.Hunk_FreeTempMemory( temp ); +} + +/* +================ +R_MipMap + +Operates in place, quartering the size of the texture +================ +*/ +static void R_MipMap (byte *in, int width, int height) { + int i, j; + byte *out; + int row; + + if ( !r_simpleMipMaps->integer ) { + R_MipMap2( in, width, height ); + return; + } + + if ( width == 1 && height == 1 ) { + return; + } + + row = width * 4; + out = in; + width >>= 1; + height >>= 1; + + if ( width == 0 || height == 0 ) { + width += height; // get largest + for (i=0 ; i>1; + out[1] = ( in[1] + in[5] )>>1; + out[2] = ( in[2] + in[6] )>>1; + out[3] = ( in[3] + in[7] )>>1; + } + return; + } + + for (i=0 ; i>2; + out[1] = (in[1] + in[5] + in[row+1] + in[row+5])>>2; + out[2] = (in[2] + in[6] + in[row+2] + in[row+6])>>2; + out[3] = (in[3] + in[7] + in[row+3] + in[row+7])>>2; + } + } +} + + +static void R_MipMapLuminanceAlpha (const byte *in, byte *out, int width, int height) +{ + int i, j, row; + + if ( width == 1 && height == 1 ) { + return; + } + + row = width * 4; + width >>= 1; + height >>= 1; + + if ( width == 0 || height == 0 ) { + width += height; // get largest + for (i=0 ; i> 1; + out[3] = (in[3] + in[7]) >> 1; + } + return; + } + + for (i=0 ; i> 2; + out[3] = (in[3] + in[7] + in[row+3] + in[row+7]) >> 2; + } + } + +} + + +static void R_MipMapNormalHeight (const byte *in, byte *out, int width, int height, qboolean swizzle) +{ + int i, j; + int row; + int sx = swizzle ? 3 : 0; + int sa = swizzle ? 0 : 3; + + if ( width == 1 && height == 1 ) { + return; + } + + row = width * 4; + width >>= 1; + height >>= 1; + + for (i=0 ; i> 9; + data[1] = ( data[1] * inverseAlpha + premult[1] ) >> 9; + data[2] = ( data[2] * inverseAlpha + premult[2] ) >> 9; + } +} + +byte mipBlendColors[16][4] = { + {0,0,0,0}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, +}; + +static void RawImage_SwizzleRA( byte *data, int width, int height ) +{ + int i; + byte *ptr = data, swap; + + for (i=0; iinteger && scaled_width > width ) + scaled_width >>= 1; + if ( r_roundImagesDown->integer && scaled_height > height ) + scaled_height >>= 1; + + if ( picmip && data && resampledBuffer && r_imageUpsample->integer && + scaled_width < r_imageUpsampleMaxSize->integer && scaled_height < r_imageUpsampleMaxSize->integer) + { + int finalwidth, finalheight; + //int startTime, endTime; + + //startTime = ri.Milliseconds(); + + finalwidth = scaled_width << r_imageUpsample->integer; + finalheight = scaled_height << r_imageUpsample->integer; + + while ( finalwidth > r_imageUpsampleMaxSize->integer + || finalheight > r_imageUpsampleMaxSize->integer ) { + finalwidth >>= 1; + finalheight >>= 1; + } + + while ( finalwidth > glConfig.maxTextureSize + || finalheight > glConfig.maxTextureSize ) { + finalwidth >>= 1; + finalheight >>= 1; + } + + *resampledBuffer = ri.Hunk_AllocateTempMemory( finalwidth * finalheight * 4 ); + + if (scaled_width != width || scaled_height != height) + { + ResampleTexture (*data, width, height, *resampledBuffer, scaled_width, scaled_height); + } + else + { + byte *inbyte, *outbyte; + int i; + + inbyte = *data; + outbyte = *resampledBuffer; + + for (i = width * height * 4; i > 0; i--) + { + *outbyte++ = *inbyte++; + } + } + + if (type == IMGTYPE_COLORALPHA) + RGBAtoYCoCgA(*resampledBuffer, *resampledBuffer, scaled_width, scaled_height); + + while (scaled_width < finalwidth || scaled_height < finalheight) + { + scaled_width <<= 1; + scaled_height <<= 1; + + FCBIByBlock(*resampledBuffer, scaled_width, scaled_height, clampToEdge, (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT)); + } + + if (type == IMGTYPE_COLORALPHA) + { + YCoCgAtoRGBA(*resampledBuffer, *resampledBuffer, scaled_width, scaled_height); + } + else if (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT) + { + FillInNormalizedZ(*resampledBuffer, *resampledBuffer, scaled_width, scaled_height); + } + + + //endTime = ri.Milliseconds(); + + //ri.Printf(PRINT_ALL, "upsampled %dx%d to %dx%d in %dms\n", width, height, scaled_width, scaled_height, endTime - startTime); + + *data = *resampledBuffer; + width = scaled_width; + height = scaled_height; + } + else if ( scaled_width != width || scaled_height != height ) { + if (data && resampledBuffer) + { + *resampledBuffer = ri.Hunk_AllocateTempMemory( scaled_width * scaled_height * 4 ); + ResampleTexture (*data, width, height, *resampledBuffer, scaled_width, scaled_height); + *data = *resampledBuffer; + } + width = scaled_width; + height = scaled_height; + } + + // + // perform optional picmip operation + // + if ( picmip ) { + scaled_width >>= r_picmip->integer; + scaled_height >>= r_picmip->integer; + } + + // + // clamp to minimum size + // + if (scaled_width < 1) { + scaled_width = 1; + } + if (scaled_height < 1) { + scaled_height = 1; + } + + // + // clamp to the current upper OpenGL limit + // scale both axis down equally so we don't have to + // deal with a half mip resampling + // + while ( scaled_width > glConfig.maxTextureSize + || scaled_height > glConfig.maxTextureSize ) { + scaled_width >>= 1; + scaled_height >>= 1; + } + + *inout_width = width; + *inout_height = height; + *inout_scaled_width = scaled_width; + *inout_scaled_height = scaled_height; +} + + +static qboolean RawImage_HasAlpha(const byte *scan, int numPixels) +{ + int i; + + if (!scan) + return qtrue; + + for ( i = 0; i < numPixels; i++ ) + { + if ( scan[i*4 + 3] != 255 ) + { + return qtrue; + } + } + + return qfalse; +} + +static GLenum RawImage_GetFormat(const byte *data, int numPixels, qboolean lightMap, imgType_t type, imgFlags_t flags) +{ + int samples = 3; + GLenum internalFormat = GL_RGB; + qboolean forceNoCompression = (flags & IMGFLAG_NO_COMPRESSION); + qboolean normalmap = (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT); + + if(normalmap) + { + if ((!RawImage_HasAlpha(data, numPixels) || (type == IMGTYPE_NORMAL)) && !forceNoCompression && (glRefConfig.textureCompression & TCR_LATC)) + { + internalFormat = GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT; + } + else + { + if ( !forceNoCompression && glConfig.textureCompression == TC_S3TC_ARB ) + { + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } + else if ( r_texturebits->integer == 16 ) + { + internalFormat = GL_RGBA4; + } + else if ( r_texturebits->integer == 32 ) + { + internalFormat = GL_RGBA8; + } + else + { + internalFormat = GL_RGBA; + } + } + } + else if(lightMap) + { + samples = 4; + if(r_greyscale->integer) + internalFormat = GL_LUMINANCE; + else + internalFormat = GL_RGBA; + } + else + { + if (RawImage_HasAlpha(data, numPixels)) + { + samples = 4; + } + + // select proper internal format + if ( samples == 3 ) + { + if(r_greyscale->integer) + { + if(r_texturebits->integer == 16) + internalFormat = GL_LUMINANCE8; + else if(r_texturebits->integer == 32) + internalFormat = GL_LUMINANCE16; + else + internalFormat = GL_LUMINANCE; + } + else + { + if ( !forceNoCompression && (glRefConfig.textureCompression & TCR_BPTC) ) + { + internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; + } + else if ( !forceNoCompression && glConfig.textureCompression == TC_S3TC_ARB ) + { + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + } + else if ( !forceNoCompression && glConfig.textureCompression == TC_S3TC ) + { + internalFormat = GL_RGB4_S3TC; + } + else if ( r_texturebits->integer == 16 ) + { + internalFormat = GL_RGB5; + } + else if ( r_texturebits->integer == 32 ) + { + internalFormat = GL_RGB8; + } + else + { + internalFormat = GL_RGB; + } + } + } + else if ( samples == 4 ) + { + if(r_greyscale->integer) + { + if(r_texturebits->integer == 16) + internalFormat = GL_LUMINANCE8_ALPHA8; + else if(r_texturebits->integer == 32) + internalFormat = GL_LUMINANCE16_ALPHA16; + else + internalFormat = GL_LUMINANCE_ALPHA; + } + else + { + if ( !forceNoCompression && (glRefConfig.textureCompression & TCR_BPTC) ) + { + internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; + } + else if ( !forceNoCompression && glConfig.textureCompression == TC_S3TC_ARB ) + { + internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } + else if ( r_texturebits->integer == 16 ) + { + internalFormat = GL_RGBA4; + } + else if ( r_texturebits->integer == 32 ) + { + internalFormat = GL_RGBA8; + } + else + { + internalFormat = GL_RGBA; + } + } + } + + if (glRefConfig.texture_srgb && (flags & IMGFLAG_SRGB)) + { + switch(internalFormat) + { + case GL_RGB: + internalFormat = GL_SRGB_EXT; + break; + + case GL_RGB4: + case GL_RGB5: + case GL_RGB8: + internalFormat = GL_SRGB8_EXT; + break; + + case GL_RGBA: + internalFormat = GL_SRGB_ALPHA_EXT; + break; + + case GL_RGBA4: + case GL_RGBA8: + internalFormat = GL_SRGB8_ALPHA8_EXT; + break; + + case GL_LUMINANCE: + internalFormat = GL_SLUMINANCE_EXT; + break; + + case GL_LUMINANCE8: + case GL_LUMINANCE16: + internalFormat = GL_SLUMINANCE8_EXT; + break; + + case GL_LUMINANCE_ALPHA: + internalFormat = GL_SLUMINANCE_ALPHA_EXT; + break; + + case GL_LUMINANCE8_ALPHA8: + case GL_LUMINANCE16_ALPHA16: + internalFormat = GL_SLUMINANCE8_ALPHA8_EXT; + break; + + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; + break; + + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + break; + + case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB: + internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB; + break; + } + } + } + + return internalFormat; +} + + +static void RawImage_UploadTexture( byte *data, int x, int y, int width, int height, GLenum internalFormat, imgType_t type, imgFlags_t flags, qboolean subtexture ) +{ + int dataFormat, dataType; + + switch(internalFormat) + { + case GL_DEPTH_COMPONENT: + case GL_DEPTH_COMPONENT16_ARB: + case GL_DEPTH_COMPONENT24_ARB: + case GL_DEPTH_COMPONENT32_ARB: + dataFormat = GL_DEPTH_COMPONENT; + dataType = GL_UNSIGNED_BYTE; + break; + case GL_RGBA16F_ARB: + dataFormat = GL_RGBA; + dataType = GL_HALF_FLOAT_ARB; + break; + default: + dataFormat = GL_RGBA; + dataType = GL_UNSIGNED_BYTE; + break; + } + + if ( subtexture ) + qglTexSubImage2D( GL_TEXTURE_2D, 0, x, y, width, height, dataFormat, dataType, data ); + else + qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, width, height, 0, dataFormat, dataType, data ); + + if (flags & IMGFLAG_MIPMAP) + { + int miplevel; + + miplevel = 0; + while (width > 1 || height > 1) + { + if (data) + { + if (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT) + { + if (internalFormat == GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT) + { + R_MipMapLuminanceAlpha( data, data, width, height ); + } + else + { + R_MipMapNormalHeight( data, data, width, height, qtrue); + } + } + else if (flags & IMGFLAG_SRGB) + { + R_MipMapsRGB( data, width, height ); + } + else + { + R_MipMap( data, width, height ); + } + } + + width >>= 1; + height >>= 1; + if (width < 1) + width = 1; + if (height < 1) + height = 1; + miplevel++; + + if ( data && r_colorMipLevels->integer ) + R_BlendOverTexture( (byte *)data, width * height, mipBlendColors[miplevel] ); + + if ( subtexture ) + { + x >>= 1; + y >>= 1; + qglTexSubImage2D( GL_TEXTURE_2D, miplevel, x, y, width, height, dataFormat, dataType, data ); + } + else + { + qglTexImage2D (GL_TEXTURE_2D, miplevel, internalFormat, width, height, 0, dataFormat, dataType, data ); + } + } + } +} + + +/* +=============== +Upload32 + +=============== +*/ +extern qboolean charSet; +static void Upload32( byte *data, int width, int height, imgType_t type, imgFlags_t flags, + qboolean lightMap, GLenum internalFormat, int *pUploadWidth, int *pUploadHeight) +{ + byte *scaledBuffer = NULL; + byte *resampledBuffer = NULL; + int scaled_width, scaled_height; + int i, c; + byte *scan; + + RawImage_ScaleToPower2(&data, &width, &height, &scaled_width, &scaled_height, type, flags, &resampledBuffer); + + scaledBuffer = ri.Hunk_AllocateTempMemory( sizeof( unsigned ) * scaled_width * scaled_height ); + + // + // scan the texture for each channel's max values + // and verify if the alpha channel is being used or not + // + c = width*height; + scan = data; + + if( r_greyscale->integer ) + { + for ( i = 0; i < c; i++ ) + { + byte luma = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]); + scan[i*4] = luma; + scan[i*4 + 1] = luma; + scan[i*4 + 2] = luma; + } + } + else if( r_greyscale->value ) + { + for ( i = 0; i < c; i++ ) + { + float luma = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]); + scan[i*4] = LERP(scan[i*4], luma, r_greyscale->value); + scan[i*4 + 1] = LERP(scan[i*4 + 1], luma, r_greyscale->value); + scan[i*4 + 2] = LERP(scan[i*4 + 2], luma, r_greyscale->value); + } + } + + // normals are always swizzled + if (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT) + { + RawImage_SwizzleRA(data, width, height); + } + + // LATC2 is only used for normals + if (internalFormat == GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT) + { + byte *in = data; + int c = width * height; + while (c--) + { + in[0] = in[1]; + in[2] = in[1]; + in += 4; + } + } + + // copy or resample data as appropriate for first MIP level + if ( ( scaled_width == width ) && + ( scaled_height == height ) ) { + if (!(flags & IMGFLAG_MIPMAP)) + { + RawImage_UploadTexture( data, 0, 0, scaled_width, scaled_height, internalFormat, type, flags, qfalse ); + //qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + *pUploadWidth = scaled_width; + *pUploadHeight = scaled_height; + + goto done; + } + Com_Memcpy (scaledBuffer, data, width*height*4); + } + else + { + // use the normal mip-mapping function to go down from here + while ( width > scaled_width || height > scaled_height ) { + + if (flags & IMGFLAG_SRGB) + { + R_MipMapsRGB( (byte *)data, width, height ); + } + else + { + R_MipMap( (byte *)data, width, height ); + } + + width >>= 1; + height >>= 1; + if ( width < 1 ) { + width = 1; + } + if ( height < 1 ) { + height = 1; + } + } + Com_Memcpy( scaledBuffer, data, width * height * 4 ); + } + + if (!(flags & IMGFLAG_NOLIGHTSCALE)) + R_LightScaleTexture (scaledBuffer, scaled_width, scaled_height, !(flags & IMGFLAG_MIPMAP) ); + + *pUploadWidth = scaled_width; + *pUploadHeight = scaled_height; + + RawImage_UploadTexture(scaledBuffer, 0, 0, scaled_width, scaled_height, internalFormat, type, flags, qfalse); + +done: + + if (flags & IMGFLAG_MIPMAP) + { + if ( textureFilterAnisotropic ) + qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, + (GLint)Com_Clamp( 1, maxAnisotropy, r_ext_max_anisotropy->integer ) ); + + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); + } + else + { + if ( textureFilterAnisotropic ) + qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1 ); + + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + + GL_CheckErrors(); + + if ( scaledBuffer != 0 ) + ri.Hunk_FreeTempMemory( scaledBuffer ); + if ( resampledBuffer != 0 ) + ri.Hunk_FreeTempMemory( resampledBuffer ); +} + + +static void EmptyTexture( int width, int height, imgType_t type, imgFlags_t flags, + qboolean lightMap, GLenum internalFormat, int *pUploadWidth, int *pUploadHeight ) +{ + int scaled_width, scaled_height; + + RawImage_ScaleToPower2(NULL, &width, &height, &scaled_width, &scaled_height, type, flags, NULL); + + *pUploadWidth = scaled_width; + *pUploadHeight = scaled_height; + + RawImage_UploadTexture(NULL, 0, 0, scaled_width, scaled_height, internalFormat, type, flags, qfalse); + + if (flags & IMGFLAG_MIPMAP) + { + if ( textureFilterAnisotropic ) + qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, + (GLint)Com_Clamp( 1, maxAnisotropy, r_ext_max_anisotropy->integer ) ); + + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); + } + else + { + if ( textureFilterAnisotropic ) + qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1 ); + + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + + // Fix for sampling depth buffer on old nVidia cards + // from http://www.idevgames.com/forums/thread-4141-post-34844.html#pid34844 + switch(internalFormat) + { + case GL_DEPTH_COMPONENT: + case GL_DEPTH_COMPONENT16_ARB: + case GL_DEPTH_COMPONENT24_ARB: + case GL_DEPTH_COMPONENT32_ARB: + qglTexParameterf(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE ); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + break; + default: + break; + } + + GL_CheckErrors(); +} + + +/* +================ +R_CreateImage + +This is the only way any image_t are created +================ +*/ +image_t *R_CreateImage( const char *name, byte *pic, int width, int height, imgType_t type, imgFlags_t flags, int internalFormat ) { + image_t *image; + qboolean isLightmap = qfalse; + long hash; + int glWrapClampMode; + + if (strlen(name) >= MAX_QPATH ) { + ri.Error (ERR_DROP, "R_CreateImage: \"%s\" is too long", name); + } + if ( !strncmp( name, "*lightmap", 9 ) ) { + isLightmap = qtrue; + } + + if ( tr.numImages == MAX_DRAWIMAGES ) { + ri.Error( ERR_DROP, "R_CreateImage: MAX_DRAWIMAGES hit"); + } + + image = tr.images[tr.numImages] = ri.Hunk_Alloc( sizeof( image_t ), h_low ); + image->texnum = 1024 + tr.numImages; + tr.numImages++; + + image->type = type; + image->flags = flags; + + strcpy (image->imgName, name); + + image->width = width; + image->height = height; + if (flags & IMGFLAG_CLAMPTOEDGE) + glWrapClampMode = GL_CLAMP_TO_EDGE; + else + glWrapClampMode = GL_REPEAT; + + if (!internalFormat) + { + if (image->flags & IMGFLAG_CUBEMAP) + internalFormat = GL_RGBA8; + else + internalFormat = RawImage_GetFormat(pic, width * height, isLightmap, image->type, image->flags); + } + + image->internalFormat = internalFormat; + + + // lightmaps are always allocated on TMU 1 + if ( qglActiveTextureARB && isLightmap ) { + image->TMU = 1; + } else { + image->TMU = 0; + } + + if ( qglActiveTextureARB ) { + GL_SelectTexture( image->TMU ); + } + + if (image->flags & IMGFLAG_CUBEMAP) + { + GL_BindCubemap(image); + qglTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + qglTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + qglTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + qglTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + qglTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + qglTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pic); + qglTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pic); + qglTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pic); + qglTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pic); + qglTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pic); + qglTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pic); + + image->uploadWidth = width; + image->uploadHeight = height; + } + else + { + GL_Bind(image); + + if (pic) + { + Upload32( pic, image->width, image->height, image->type, image->flags, + isLightmap, image->internalFormat, &image->uploadWidth, + &image->uploadHeight ); + } + else + { + EmptyTexture(image->width, image->height, image->type, image->flags, + isLightmap, image->internalFormat, &image->uploadWidth, + &image->uploadHeight ); + } + + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glWrapClampMode ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glWrapClampMode ); + } + + GL_SelectTexture( 0 ); + + hash = generateHashValue(name); + image->next = hashTable[hash]; + hashTable[hash] = image; + + return image; +} + +void R_UpdateSubImage( image_t *image, byte *pic, int x, int y, int width, int height ) +{ + byte *scaledBuffer = NULL; + byte *resampledBuffer = NULL; + int scaled_width, scaled_height, scaled_x, scaled_y; + byte *data = pic; + + // normals are always swizzled + if (image->type == IMGTYPE_NORMAL || image->type == IMGTYPE_NORMALHEIGHT) + { + RawImage_SwizzleRA(pic, width, height); + } + + // LATC2 is only used for normals + if (image->internalFormat == GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT) + { + byte *in = data; + int c = width * height; + while (c--) + { + in[0] = in[1]; + in[2] = in[1]; + in += 4; + } + } + + + RawImage_ScaleToPower2(&pic, &width, &height, &scaled_width, &scaled_height, image->type, image->flags, &resampledBuffer); + + scaledBuffer = ri.Hunk_AllocateTempMemory( sizeof( unsigned ) * scaled_width * scaled_height ); + + if ( qglActiveTextureARB ) { + GL_SelectTexture( image->TMU ); + } + + GL_Bind(image); + + // copy or resample data as appropriate for first MIP level + if ( ( scaled_width == width ) && + ( scaled_height == height ) ) { + if (!(image->flags & IMGFLAG_MIPMAP)) + { + scaled_x = x * scaled_width / width; + scaled_y = y * scaled_height / height; + RawImage_UploadTexture( data, scaled_x, scaled_y, scaled_width, scaled_height, image->internalFormat, image->type, image->flags, qtrue ); + //qglTexSubImage2D( GL_TEXTURE_2D, 0, scaled_x, scaled_y, scaled_width, scaled_height, GL_RGBA, GL_UNSIGNED_BYTE, data ); + + GL_CheckErrors(); + goto done; + } + Com_Memcpy (scaledBuffer, data, width*height*4); + } + else + { + // use the normal mip-mapping function to go down from here + while ( width > scaled_width || height > scaled_height ) { + + if (image->flags & IMGFLAG_SRGB) + { + R_MipMapsRGB( (byte *)data, width, height ); + } + else + { + R_MipMap( (byte *)data, width, height ); + } + + width >>= 1; + height >>= 1; + x >>= 1; + y >>= 1; + if ( width < 1 ) { + width = 1; + } + if ( height < 1 ) { + height = 1; + } + } + Com_Memcpy( scaledBuffer, data, width * height * 4 ); + } + + if (!(image->flags & IMGFLAG_NOLIGHTSCALE)) + R_LightScaleTexture (scaledBuffer, scaled_width, scaled_height, !(image->flags & IMGFLAG_MIPMAP) ); + + scaled_x = x * scaled_width / width; + scaled_y = y * scaled_height / height; + RawImage_UploadTexture( (byte *)data, scaled_x, scaled_y, scaled_width, scaled_height, image->internalFormat, image->type, image->flags, qtrue ); + +done: + + GL_SelectTexture( 0 ); + + GL_CheckErrors(); + + if ( scaledBuffer != 0 ) + ri.Hunk_FreeTempMemory( scaledBuffer ); + if ( resampledBuffer != 0 ) + ri.Hunk_FreeTempMemory( resampledBuffer ); +} + +//=================================================================== + +typedef struct +{ + char *ext; + void (*ImageLoader)( const char *, unsigned char **, int *, int * ); +} imageExtToLoaderMap_t; + +// Note that the ordering indicates the order of preference used +// when there are multiple images of different formats available +static imageExtToLoaderMap_t imageLoaders[ ] = +{ + { "tga", R_LoadTGA }, + { "jpg", R_LoadJPG }, + { "jpeg", R_LoadJPG }, + { "png", R_LoadPNG }, + { "pcx", R_LoadPCX }, + { "bmp", R_LoadBMP } +}; + +static int numImageLoaders = ARRAY_LEN( imageLoaders ); + +/* +================= +R_LoadImage + +Loads any of the supported image types into a cannonical +32 bit format. +================= +*/ +void R_LoadImage( const char *name, byte **pic, int *width, int *height ) +{ + qboolean orgNameFailed = qfalse; + int orgLoader = -1; + int i; + char localName[ MAX_QPATH ]; + const char *ext; + char *altName; + + *pic = NULL; + *width = 0; + *height = 0; + + Q_strncpyz( localName, name, MAX_QPATH ); + + ext = COM_GetExtension( localName ); + + if( *ext ) + { + // Look for the correct loader and use it + for( i = 0; i < numImageLoaders; i++ ) + { + if( !Q_stricmp( ext, imageLoaders[ i ].ext ) ) + { + // Load + imageLoaders[ i ].ImageLoader( localName, pic, width, height ); + break; + } + } + + // A loader was found + if( i < numImageLoaders ) + { + if( *pic == NULL ) + { + // Loader failed, most likely because the file isn't there; + // try again without the extension + orgNameFailed = qtrue; + orgLoader = i; + COM_StripExtension( name, localName, MAX_QPATH ); + } + else + { + // Something loaded + return; + } + } + } + + // Try and find a suitable match using all + // the image formats supported + for( i = 0; i < numImageLoaders; i++ ) + { + if (i == orgLoader) + continue; + + altName = va( "%s.%s", localName, imageLoaders[ i ].ext ); + + // Load + imageLoaders[ i ].ImageLoader( altName, pic, width, height ); + + if( *pic ) + { + if( orgNameFailed ) + { + ri.Printf( PRINT_DEVELOPER, "WARNING: %s not present, using %s instead\n", + name, altName ); + } + + break; + } + } +} + + +/* +=============== +R_FindImageFile + +Finds or loads the given image. +Returns NULL if it fails, not a default image. +============== +*/ +image_t *R_FindImageFile( const char *name, imgType_t type, imgFlags_t flags ) +{ + image_t *image; + int width, height; + byte *pic; + long hash; + + if (!name) { + return NULL; + } + + hash = generateHashValue(name); + + // + // see if the image is already loaded + // + for (image=hashTable[hash]; image; image=image->next) { + if ( !strcmp( name, image->imgName ) ) { + // the white image can be used with any set of parms, but other mismatches are errors + if ( strcmp( name, "*white" ) ) { + if ( image->flags != flags ) { + ri.Printf( PRINT_DEVELOPER, "WARNING: reused image %s with mixed flags (%i vs %i)\n", name, image->flags, flags ); + } + } + return image; + } + } + + // + // load the pic from disk + // + R_LoadImage( name, &pic, &width, &height ); + if ( pic == NULL ) { + return NULL; + } + + if (r_normalMapping->integer && !(type == IMGTYPE_NORMAL) && (flags & IMGFLAG_PICMIP) && (flags & IMGFLAG_MIPMAP) && (flags & IMGFLAG_GENNORMALMAP)) + { + char normalName[MAX_QPATH]; + image_t *normalImage; + int normalWidth, normalHeight; + imgFlags_t normalFlags; + + normalFlags = (flags & ~(IMGFLAG_GENNORMALMAP | IMGFLAG_SRGB)) | IMGFLAG_NOLIGHTSCALE; + + COM_StripExtension(name, normalName, MAX_QPATH); + Q_strcat(normalName, MAX_QPATH, "_n"); + + // find normalmap in case it's there + normalImage = R_FindImageFile(normalName, IMGTYPE_NORMAL, normalFlags); + + // if not, generate it + if (normalImage == NULL) + { + byte *normalPic; + int x, y; + + normalWidth = width; + normalHeight = height; + normalPic = ri.Malloc(width * height * 4); + RGBAtoNormal(pic, normalPic, width, height, flags & IMGFLAG_CLAMPTOEDGE); + + // Brighten up the original image to work with the normal map + RGBAtoYCoCgA(pic, pic, width, height); + for (y = 0; y < height; y++) + { + byte *picbyte = pic + y * width * 4; + byte *normbyte = normalPic + y * width * 4; + for (x = 0; x < width; x++) + { + int div = MAX(normbyte[2] - 127, 16); + picbyte[0] = CLAMP(picbyte[0] * 128 / div, 0, 255); + picbyte += 4; + normbyte += 4; + } + } + YCoCgAtoRGBA(pic, pic, width, height); + + R_CreateImage( normalName, normalPic, normalWidth, normalHeight, IMGTYPE_NORMAL, normalFlags, 0 ); + ri.Free( normalPic ); + } + } + + image = R_CreateImage( ( char * ) name, pic, width, height, type, flags, 0 ); + ri.Free( pic ); + return image; +} + + +/* +================ +R_CreateDlightImage +================ +*/ +#define DLIGHT_SIZE 16 +static void R_CreateDlightImage( void ) { + int x,y; + byte data[DLIGHT_SIZE][DLIGHT_SIZE][4]; + int b; + + // make a centered inverse-square falloff blob for dynamic lighting + for (x=0 ; x 255) { + b = 255; + } else if ( b < 75 ) { + b = 0; + } + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = b; + data[y][x][3] = 255; + } + } + tr.dlightImage = R_CreateImage("*dlight", (byte *)data, DLIGHT_SIZE, DLIGHT_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_CLAMPTOEDGE, 0 ); +} + + +/* +================= +R_InitFogTable +================= +*/ +void R_InitFogTable( void ) { + int i; + float d; + float exp; + + exp = 0.5; + + for ( i = 0 ; i < FOG_TABLE_SIZE ; i++ ) { + d = pow ( (float)i/(FOG_TABLE_SIZE-1), exp ); + + tr.fogTable[i] = d; + } +} + +/* +================ +R_FogFactor + +Returns a 0.0 to 1.0 fog density value +This is called for each texel of the fog texture on startup +and for each vertex of transparent shaders in fog dynamically +================ +*/ +float R_FogFactor( float s, float t ) { + float d; + + s -= 1.0/512; + if ( s < 0 ) { + return 0; + } + if ( t < 1.0/32 ) { + return 0; + } + if ( t < 31.0/32 ) { + s *= (t - 1.0f/32.0f) / (30.0f/32.0f); + } + + // we need to leave a lot of clamp range + s *= 8; + + if ( s > 1.0 ) { + s = 1.0; + } + + d = tr.fogTable[ (int)(s * (FOG_TABLE_SIZE-1)) ]; + + return d; +} + +/* +================ +R_CreateFogImage +================ +*/ +#define FOG_S 256 +#define FOG_T 32 +static void R_CreateFogImage( void ) { + int x,y; + byte *data; + float d; + float borderColor[4]; + + data = ri.Hunk_AllocateTempMemory( FOG_S * FOG_T * 4 ); + + // S is distance, T is depth + for (x=0 ; xinteger >= 2) + { + for( x = 0; x < MAX_DLIGHTS; x++) + { + tr.shadowCubemaps[x] = R_CreateImage(va("*shadowcubemap%i", x), (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_CLAMPTOEDGE | IMGFLAG_CUBEMAP, 0); + } + } + + // with overbright bits active, we need an image which is some fraction of full color, + // for default lightmaps, etc + for (x=0 ; xinteger && glRefConfig.framebufferObject && glRefConfig.textureFloat) + hdrFormat = GL_RGB16F_ARB; + + tr.renderImage = R_CreateImage("_render", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat); +#ifdef REACTION + tr.godRaysImage = R_CreateImage("*godRays", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); +#endif + + { + int format; + + if (glRefConfig.texture_srgb && glRefConfig.framebuffer_srgb) + format = GL_SRGB8_ALPHA8_EXT; + else + format = GL_RGBA8; + + tr.screenScratchImage = R_CreateImage("*screenScratch", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, format); + } + + if (glRefConfig.framebufferObject) + { + tr.renderDepthImage = R_CreateImage("*renderdepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24_ARB); + tr.textureDepthImage = R_CreateImage("*texturedepth", NULL, PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24_ARB); + } + + { + unsigned short sdata[4]; + void *p; + + if (hdrFormat == GL_RGB16F_ARB) + { + sdata[0] = FloatToHalf(0.0f); + sdata[1] = FloatToHalf(0.45f); + sdata[2] = FloatToHalf(1.0f); + sdata[3] = FloatToHalf(1.0f); + p = &sdata[0]; + } + else + { + data[0][0][0] = 0; + data[0][0][1] = 0.45f * 255; + data[0][0][2] = 255; + data[0][0][3] = 255; + p = data; + } + + tr.calcLevelsImage = R_CreateImage("*calcLevels", p, 1, 1, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat); + tr.targetLevelsImage = R_CreateImage("*targetLevels", p, 1, 1, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat); + tr.fixedLevelsImage = R_CreateImage("*fixedLevels", p, 1, 1, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat); + } + + for (x = 0; x < 2; x++) + { + tr.textureScratchImage[x] = R_CreateImage(va("*textureScratch%d", x), NULL, 256, 256, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); + } + for (x = 0; x < 2; x++) + { + tr.quarterImage[x] = R_CreateImage(va("*quarter%d", x), NULL, width / 2, height / 2, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); + } + + tr.screenShadowImage = R_CreateImage("*screenShadow", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); + + if (r_ssao->integer) + { + tr.screenSsaoImage = R_CreateImage("*screenSsao", NULL, width / 2, height / 2, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); + tr.hdrDepthImage = R_CreateImage("*hdrDepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_INTENSITY32F_ARB); + } + } + + for( x = 0; x < MAX_DRAWN_PSHADOWS; x++) + { + tr.pshadowMaps[x] = R_CreateImage(va("*shadowmap%i", x), NULL, PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); + } + + //tr.sunShadowImage = R_CreateImage("*sunshadowmap", NULL, SUNSHADOW_MAP_SIZE, SUNSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); + for ( x = 0; x < 3; x++) + { + tr.sunShadowDepthImage[x] = R_CreateImage(va("*sunshadowdepth%i", x), NULL, r_shadowMapSize->integer, r_shadowMapSize->integer, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24_ARB); + } +} + + +/* +=============== +R_SetColorMappings +=============== +*/ +void R_SetColorMappings( void ) { + int i, j; + float g; + int inf; + int shift; + + // setup the overbright lighting + tr.overbrightBits = r_overBrightBits->integer; + if ( !glConfig.deviceSupportsGamma ) { + tr.overbrightBits = 0; // need hardware gamma for overbright + } + + // never overbright in windowed mode + if ( 0 /* !glConfig.isFullscreen */ ) + { + tr.overbrightBits = 0; + } + + // never overbright with tonemapping + if ( r_toneMap->integer ) + { + tr.overbrightBits = 0; + } + + // allow 2 overbright bits in 24 bit, but only 1 in 16 bit + if ( glConfig.colorBits > 16 ) { + if ( tr.overbrightBits > 2 ) { + tr.overbrightBits = 2; + } + } else { + if ( tr.overbrightBits > 1 ) { + tr.overbrightBits = 1; + } + } + if ( tr.overbrightBits < 0 ) { + tr.overbrightBits = 0; + } + + tr.identityLight = 1.0f / ( 1 << tr.overbrightBits ); + tr.identityLightByte = 255 * tr.identityLight; + + + if ( r_intensity->value <= 1 ) { + ri.Cvar_Set( "r_intensity", "1" ); + } + + if ( r_gamma->value < 0.5f ) { + ri.Cvar_Set( "r_gamma", "0.5" ); + } else if ( r_gamma->value > 3.0f ) { + ri.Cvar_Set( "r_gamma", "3.0" ); + } + + g = r_gamma->value; + + shift = tr.overbrightBits; + + if (glRefConfig.framebufferObject) + shift = 0; + + for ( i = 0; i < 256; i++ ) { + if ( g == 1 ) { + inf = i; + } else { + inf = 255 * pow ( i/255.0f, 1.0f / g ) + 0.5f; + } + inf <<= shift; + if (inf < 0) { + inf = 0; + } + if (inf > 255) { + inf = 255; + } + s_gammatable[i] = inf; + } + + for (i=0 ; i<256 ; i++) { + j = i * r_intensity->value; + if (j > 255) { + j = 255; + } + s_intensitytable[i] = j; + } + + if ( glConfig.deviceSupportsGamma ) + { + GLimp_SetGamma( s_gammatable, s_gammatable, s_gammatable ); + } +} + +/* +=============== +R_InitImages +=============== +*/ +void R_InitImages( void ) { + Com_Memset(hashTable, 0, sizeof(hashTable)); + // build brightness translation tables + R_SetColorMappings(); + + // create default texture and white texture + R_CreateBuiltinImages(); +} + +/* +=============== +R_DeleteTextures +=============== +*/ +void R_DeleteTextures( void ) { + int i; + + for ( i=0; itexnum ); + } + Com_Memset( tr.images, 0, sizeof( tr.images ) ); + + tr.numImages = 0; + + Com_Memset( glState.currenttextures, 0, sizeof( glState.currenttextures ) ); + if ( qglActiveTextureARB ) { + GL_SelectTexture( 1 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + GL_SelectTexture( 0 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + } else { + qglBindTexture( GL_TEXTURE_2D, 0 ); + } +} + +/* +============================================================================ + +SKINS + +============================================================================ +*/ + +/* +================== +CommaParse + +This is unfortunate, but the skin files aren't +compatable with our normal parsing rules. +================== +*/ +static char *CommaParse( char **data_p ) { + int c = 0, len; + char *data; + static char com_token[MAX_TOKEN_CHARS]; + + data = *data_p; + len = 0; + com_token[0] = 0; + + // make sure incoming data is valid + if ( !data ) { + *data_p = NULL; + return com_token; + } + + while ( 1 ) { + // skip whitespace + while( (c = *data) <= ' ') { + if( !c ) { + break; + } + data++; + } + + + c = *data; + + // skip double slash comments + if ( c == '/' && data[1] == '/' ) + { + while (*data && *data != '\n') + data++; + } + // skip /* */ comments + else if ( c=='/' && data[1] == '*' ) + { + while ( *data && ( *data != '*' || data[1] != '/' ) ) + { + data++; + } + if ( *data ) + { + data += 2; + } + } + else + { + break; + } + } + + if ( c == 0 ) { + return ""; + } + + // handle quoted strings + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = ( char * ) data; + return com_token; + } + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + } + } + + // parse a regular word + do + { + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + data++; + c = *data; + } while (c>32 && c != ',' ); + + if (len == MAX_TOKEN_CHARS) + { +// ri.Printf (PRINT_DEVELOPER, "Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS); + len = 0; + } + com_token[len] = 0; + + *data_p = ( char * ) data; + return com_token; +} + + +/* +=============== +RE_RegisterSkin + +=============== +*/ +qhandle_t RE_RegisterSkin( const char *name ) { + qhandle_t hSkin; + skin_t *skin; + skinSurface_t *surf; + union { + char *c; + void *v; + } text; + char *text_p; + char *token; + char surfName[MAX_QPATH]; + + if ( !name || !name[0] ) { + ri.Printf( PRINT_DEVELOPER, "Empty name passed to RE_RegisterSkin\n" ); + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Printf( PRINT_DEVELOPER, "Skin name exceeds MAX_QPATH\n" ); + return 0; + } + + + // see if the skin is already loaded + for ( hSkin = 1; hSkin < tr.numSkins ; hSkin++ ) { + skin = tr.skins[hSkin]; + if ( !Q_stricmp( skin->name, name ) ) { + if( skin->numSurfaces == 0 ) { + return 0; // default skin + } + return hSkin; + } + } + + // allocate a new skin + if ( tr.numSkins == MAX_SKINS ) { + ri.Printf( PRINT_WARNING, "WARNING: RE_RegisterSkin( '%s' ) MAX_SKINS hit\n", name ); + return 0; + } + tr.numSkins++; + skin = ri.Hunk_Alloc( sizeof( skin_t ), h_low ); + tr.skins[hSkin] = skin; + Q_strncpyz( skin->name, name, sizeof( skin->name ) ); + skin->numSurfaces = 0; + + // make sure the render thread is stopped + R_SyncRenderThread(); + + // If not a .skin file, load as a single shader + if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) { + skin->numSurfaces = 1; + skin->surfaces[0] = ri.Hunk_Alloc( sizeof(skin->surfaces[0]), h_low ); + skin->surfaces[0]->shader = R_FindShader( name, LIGHTMAP_NONE, qtrue ); + return hSkin; + } + + // load and parse the skin file + ri.FS_ReadFile( name, &text.v ); + if ( !text.c ) { + return 0; + } + + text_p = text.c; + while ( text_p && *text_p ) { + // get surface name + token = CommaParse( &text_p ); + Q_strncpyz( surfName, token, sizeof( surfName ) ); + + if ( !token[0] ) { + break; + } + // lowercase the surface name so skin compares are faster + Q_strlwr( surfName ); + + if ( *text_p == ',' ) { + text_p++; + } + + if ( strstr( token, "tag_" ) ) { + continue; + } + + // parse the shader name + token = CommaParse( &text_p ); + + surf = skin->surfaces[ skin->numSurfaces ] = ri.Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low ); + Q_strncpyz( surf->name, surfName, sizeof( surf->name ) ); + surf->shader = R_FindShader( token, LIGHTMAP_NONE, qtrue ); + skin->numSurfaces++; + } + + ri.FS_FreeFile( text.v ); + + + // never let a skin have 0 shaders + if ( skin->numSurfaces == 0 ) { + return 0; // use default skin + } + + return hSkin; +} + + +/* +=============== +R_InitSkins +=============== +*/ +void R_InitSkins( void ) { + skin_t *skin; + + tr.numSkins = 1; + + // make the default skin have all default shaders + skin = tr.skins[0] = ri.Hunk_Alloc( sizeof( skin_t ), h_low ); + Q_strncpyz( skin->name, "", sizeof( skin->name ) ); + skin->numSurfaces = 1; + skin->surfaces[0] = ri.Hunk_Alloc( sizeof( *skin->surfaces ), h_low ); + skin->surfaces[0]->shader = tr.defaultShader; +} + +/* +=============== +R_GetSkinByHandle +=============== +*/ +skin_t *R_GetSkinByHandle( qhandle_t hSkin ) { + if ( hSkin < 1 || hSkin >= tr.numSkins ) { + return tr.skins[0]; + } + return tr.skins[ hSkin ]; +} + +/* +=============== +R_SkinList_f +=============== +*/ +void R_SkinList_f( void ) { + int i, j; + skin_t *skin; + + ri.Printf (PRINT_ALL, "------------------\n"); + + for ( i = 0 ; i < tr.numSkins ; i++ ) { + skin = tr.skins[i]; + + ri.Printf( PRINT_ALL, "%3i:%s\n", i, skin->name ); + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + ri.Printf( PRINT_ALL, " %s = %s\n", + skin->surfaces[j]->name, skin->surfaces[j]->shader->name ); + } + } + ri.Printf (PRINT_ALL, "------------------\n"); +} + + diff --git a/src/rend2/tr_image_bmp.c b/src/rend2/tr_image_bmp.c new file mode 100644 index 00000000..707bee3b --- /dev/null +++ b/src/rend2/tr_image_bmp.c @@ -0,0 +1,243 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qfiles.h" +#include "../qcommon/qcommon.h" +#include "../renderer/tr_public.h" +extern refimport_t ri; + +typedef struct +{ + char id[2]; + unsigned fileSize; + unsigned reserved0; + unsigned bitmapDataOffset; + unsigned bitmapHeaderSize; + unsigned width; + unsigned height; + unsigned short planes; + unsigned short bitsPerPixel; + unsigned compression; + unsigned bitmapDataSize; + unsigned hRes; + unsigned vRes; + unsigned colors; + unsigned importantColors; + unsigned char palette[256][4]; +} BMPHeader_t; + +void R_LoadBMP( const char *name, byte **pic, int *width, int *height ) +{ + int columns, rows; + unsigned numPixels; + byte *pixbuf; + int row, column; + byte *buf_p; + byte *end; + union { + byte *b; + void *v; + } buffer; + int length; + BMPHeader_t bmpHeader; + byte *bmpRGBA; + + *pic = NULL; + + if(width) + *width = 0; + + if(height) + *height = 0; + + // + // load the file + // + length = ri.FS_ReadFile( ( char * ) name, &buffer.v); + if (!buffer.b || length < 0) { + return; + } + + if (length < 54) + { + ri.Error( ERR_DROP, "LoadBMP: header too short (%s)", name ); + } + + buf_p = buffer.b; + end = buffer.b + length; + + bmpHeader.id[0] = *buf_p++; + bmpHeader.id[1] = *buf_p++; + bmpHeader.fileSize = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.reserved0 = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapDataOffset = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapHeaderSize = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.width = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.height = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.planes = LittleShort( * ( short * ) buf_p ); + buf_p += 2; + bmpHeader.bitsPerPixel = LittleShort( * ( short * ) buf_p ); + buf_p += 2; + bmpHeader.compression = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.bitmapDataSize = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.hRes = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.vRes = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.colors = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + bmpHeader.importantColors = LittleLong( * ( int * ) buf_p ); + buf_p += 4; + + if ( bmpHeader.bitsPerPixel == 8 ) + { + if (buf_p + sizeof(bmpHeader.palette) > end) + ri.Error( ERR_DROP, "LoadBMP: header too short (%s)", name ); + + Com_Memcpy( bmpHeader.palette, buf_p, sizeof( bmpHeader.palette ) ); + buf_p += sizeof(bmpHeader.palette); + } + + if (buffer.b + bmpHeader.bitmapDataOffset > end) + { + ri.Error( ERR_DROP, "LoadBMP: invalid offset value in header (%s)", name ); + } + + buf_p = buffer.b + bmpHeader.bitmapDataOffset; + + if ( bmpHeader.id[0] != 'B' && bmpHeader.id[1] != 'M' ) + { + ri.Error( ERR_DROP, "LoadBMP: only Windows-style BMP files supported (%s)", name ); + } + if ( bmpHeader.fileSize != length ) + { + ri.Error( ERR_DROP, "LoadBMP: header size does not match file size (%u vs. %u) (%s)", bmpHeader.fileSize, length, name ); + } + if ( bmpHeader.compression != 0 ) + { + ri.Error( ERR_DROP, "LoadBMP: only uncompressed BMP files supported (%s)", name ); + } + if ( bmpHeader.bitsPerPixel < 8 ) + { + ri.Error( ERR_DROP, "LoadBMP: monochrome and 4-bit BMP files not supported (%s)", name ); + } + + switch ( bmpHeader.bitsPerPixel ) + { + case 8: + case 16: + case 24: + case 32: + break; + default: + ri.Error( ERR_DROP, "LoadBMP: illegal pixel_size '%hu' in file '%s'", bmpHeader.bitsPerPixel, name ); + break; + } + + columns = bmpHeader.width; + rows = bmpHeader.height; + if ( rows < 0 ) + rows = -rows; + numPixels = columns * rows; + + if(columns <= 0 || !rows || numPixels > 0x1FFFFFFF // 4*1FFFFFFF == 0x7FFFFFFC < 0x7FFFFFFF + || ((numPixels * 4) / columns) / 4 != rows) + { + ri.Error (ERR_DROP, "LoadBMP: %s has an invalid image size", name); + } + if(buf_p + numPixels*bmpHeader.bitsPerPixel/8 > end) + { + ri.Error (ERR_DROP, "LoadBMP: file truncated (%s)", name); + } + + if ( width ) + *width = columns; + if ( height ) + *height = rows; + + bmpRGBA = ri.Malloc( numPixels * 4 ); + *pic = bmpRGBA; + + + for ( row = rows-1; row >= 0; row-- ) + { + pixbuf = bmpRGBA + row*columns*4; + + for ( column = 0; column < columns; column++ ) + { + unsigned char red, green, blue, alpha; + int palIndex; + unsigned short shortPixel; + + switch ( bmpHeader.bitsPerPixel ) + { + case 8: + palIndex = *buf_p++; + *pixbuf++ = bmpHeader.palette[palIndex][2]; + *pixbuf++ = bmpHeader.palette[palIndex][1]; + *pixbuf++ = bmpHeader.palette[palIndex][0]; + *pixbuf++ = 0xff; + break; + case 16: + shortPixel = * ( unsigned short * ) pixbuf; + pixbuf += 2; + *pixbuf++ = ( shortPixel & ( 31 << 10 ) ) >> 7; + *pixbuf++ = ( shortPixel & ( 31 << 5 ) ) >> 2; + *pixbuf++ = ( shortPixel & ( 31 ) ) << 3; + *pixbuf++ = 0xff; + break; + + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alpha = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + break; + } + } + } + + ri.FS_FreeFile( buffer.v ); + +} diff --git a/src/rend2/tr_image_jpg.c b/src/rend2/tr_image_jpg.c new file mode 100644 index 00000000..494b4a28 --- /dev/null +++ b/src/rend2/tr_image_jpg.c @@ -0,0 +1,441 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qfiles.h" +#include "../qcommon/qcommon.h" +#include "../renderer/tr_public.h" +extern refimport_t ri; + +/* + * Include file for users of JPEG library. + * You will need to have included system headers that define at least + * the typedefs FILE and size_t before you can include jpeglib.h. + * (stdio.h is sufficient on ANSI-conforming systems.) + * You may also wish to include "jerror.h". + */ + +#ifdef USE_INTERNAL_JPEG +# define JPEG_INTERNALS +#endif + +#include + +#ifndef USE_INTERNAL_JPEG +# if JPEG_LIB_VERSION < 80 +# error Need system libjpeg >= 80 +# endif +#endif + +static void R_JPGErrorExit(j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + (*cinfo->err->format_message) (cinfo, buffer); + + /* Let the memory manager delete any temp files before we die */ + jpeg_destroy(cinfo); + + ri.Error(ERR_FATAL, "%s", buffer); +} + +static void R_JPGOutputMessage(j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + + /* Send it to stderr, adding a newline */ + ri.Printf(PRINT_ALL, "%s\n", buffer); +} + +void R_LoadJPG(const char *filename, unsigned char **pic, int *width, int *height) +{ + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + struct jpeg_decompress_struct cinfo = {NULL}; + /* We use our private extension JPEG error handler. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + struct jpeg_error_mgr jerr; + /* More stuff */ + JSAMPARRAY buffer; /* Output row buffer */ + unsigned int row_stride; /* physical row width in output buffer */ + unsigned int pixelcount, memcount; + unsigned int sindex, dindex; + byte *out; + int len; + union { + byte *b; + void *v; + } fbuffer; + byte *buf; + + /* In this example we want to open the input file before doing anything else, + * so that the setjmp() error recovery below can assume the file is open. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to read binary files. + */ + + len = ri.FS_ReadFile ( ( char * ) filename, &fbuffer.v); + if (!fbuffer.b || len < 0) { + return; + } + + /* Step 1: allocate and initialize JPEG decompression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = R_JPGErrorExit; + cinfo.err->output_message = R_JPGOutputMessage; + + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress(&cinfo); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_mem_src(&cinfo, fbuffer.b, len); + + /* Step 3: read file parameters with jpeg_read_header() */ + + (void) jpeg_read_header(&cinfo, TRUE); + /* We can ignore the return value from jpeg_read_header since + * (a) suspension is not possible with the stdio data source, and + * (b) we passed TRUE to reject a tables-only JPEG file as an error. + * See libjpeg.doc for more info. + */ + + /* Step 4: set parameters for decompression */ + + /* + * Make sure it always converts images to RGB color space. This will + * automatically convert 8-bit greyscale images to RGB as well. + */ + cinfo.out_color_space = JCS_RGB; + + /* Step 5: Start decompressor */ + + (void) jpeg_start_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + * In this example, we need to make an output work buffer of the right size. + */ + /* JSAMPLEs per row in output buffer */ + + pixelcount = cinfo.output_width * cinfo.output_height; + + if(!cinfo.output_width || !cinfo.output_height + || ((pixelcount * 4) / cinfo.output_width) / 4 != cinfo.output_height + || pixelcount > 0x1FFFFFFF || cinfo.output_components != 3 + ) + { + // Free the memory to make sure we don't leak memory + ri.FS_FreeFile (fbuffer.v); + jpeg_destroy_decompress(&cinfo); + + ri.Error(ERR_DROP, "LoadJPG: %s has an invalid image format: %dx%d*4=%d, components: %d", filename, + cinfo.output_width, cinfo.output_height, pixelcount * 4, cinfo.output_components); + } + + memcount = pixelcount * 4; + row_stride = cinfo.output_width * cinfo.output_components; + + out = ri.Malloc(memcount); + + *width = cinfo.output_width; + *height = cinfo.output_height; + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + while (cinfo.output_scanline < cinfo.output_height) { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + buf = ((out+(row_stride*cinfo.output_scanline))); + buffer = &buf; + (void) jpeg_read_scanlines(&cinfo, buffer, 1); + } + + buf = out; + + // Expand from RGB to RGBA + sindex = pixelcount * cinfo.output_components; + dindex = memcount; + + do + { + buf[--dindex] = 255; + buf[--dindex] = buf[--sindex]; + buf[--dindex] = buf[--sindex]; + buf[--dindex] = buf[--sindex]; + } while(sindex); + + *pic = out; + + /* Step 7: Finish decompression */ + + jpeg_finish_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* Step 8: Release JPEG decompression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_decompress(&cinfo); + + /* After finish_decompress, we can close the input file. + * Here we postpone it until after no more JPEG errors are possible, + * so as to simplify the setjmp error logic above. (Actually, I don't + * think that jpeg_destroy can do an error exit, but why assume anything...) + */ + ri.FS_FreeFile (fbuffer.v); + + /* At this point you may want to check to see whether any corrupt-data + * warnings occurred (test whether jerr.pub.num_warnings is nonzero). + */ + + /* And we're done! */ +} + + +/* Expanded data destination object for stdio output */ + +typedef struct { + struct jpeg_destination_mgr pub; /* public fields */ + + byte* outfile; /* target stream */ + int size; +} my_destination_mgr; + +typedef my_destination_mgr * my_dest_ptr; + + +/* + * Initialize destination --- called by jpeg_start_compress + * before any data is actually written. + */ + +static void +init_destination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + dest->pub.next_output_byte = dest->outfile; + dest->pub.free_in_buffer = dest->size; +} + + +/* + * Empty the output buffer --- called whenever buffer fills up. + * + * In typical applications, this should write the entire output buffer + * (ignoring the current state of next_output_byte & free_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been dumped. + * + * In applications that need to be able to suspend compression due to output + * overrun, a FALSE return indicates that the buffer cannot be emptied now. + * In this situation, the compressor will return to its caller (possibly with + * an indication that it has not accepted all the supplied scanlines). The + * application should resume compression after it has made more room in the + * output buffer. Note that there are substantial restrictions on the use of + * suspension --- see the documentation. + * + * When suspending, the compressor will back up to a convenient restart point + * (typically the start of the current MCU). next_output_byte & free_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point will be regenerated after resumption, so do not + * write it out when emptying the buffer externally. + */ + +static boolean +empty_output_buffer (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + jpeg_destroy_compress(cinfo); + + // Make crash fatal or we would probably leak memory. + ri.Error(ERR_FATAL, "Output buffer for encoded JPEG image has insufficient size of %d bytes", + dest->size); + + return FALSE; +} + +/* + * Terminate destination --- called by jpeg_finish_compress + * after all data has been written. Usually needs to flush buffer. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +static void term_destination(j_compress_ptr cinfo) +{ +} + + +/* + * Prepare for output to a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing compression. + */ + +static void +jpegDest (j_compress_ptr cinfo, byte* outfile, int size) +{ + my_dest_ptr dest; + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same file without re-executing jpeg_stdio_dest. + * This makes it dangerous to use this manager and a different destination + * manager serially with the same JPEG object, because their private object + * sizes may be different. Caveat programmer. + */ + if (cinfo->dest == NULL) { /* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof(my_destination_mgr)); + } + + dest = (my_dest_ptr) cinfo->dest; + dest->pub.init_destination = init_destination; + dest->pub.empty_output_buffer = empty_output_buffer; + dest->pub.term_destination = term_destination; + dest->outfile = outfile; + dest->size = size; +} + +/* +================= +SaveJPGToBuffer + +Encodes JPEG from image in image_buffer and writes to buffer. +Expects RGB input data +================= +*/ +size_t RE_SaveJPGToBuffer(byte *buffer, size_t bufSize, int quality, + int image_width, int image_height, byte *image_buffer, int padding) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ + my_dest_ptr dest; + int row_stride; /* physical row width in image buffer */ + size_t outcount; + + /* Step 1: allocate and initialize JPEG compression object */ + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = R_JPGErrorExit; + cinfo.err->output_message = R_JPGOutputMessage; + + /* Now we can initialize the JPEG compression object. */ + jpeg_create_compress(&cinfo); + + /* Step 2: specify data destination (eg, a file) */ + /* Note: steps 2 and 3 can be done in either order. */ + jpegDest(&cinfo, buffer, bufSize); + + /* Step 3: set parameters for compression */ + cinfo.image_width = image_width; /* image width and height, in pixels */ + cinfo.image_height = image_height; + cinfo.input_components = 3; /* # of color components per pixel */ + cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); + /* If quality is set high, disable chroma subsampling */ + if (quality >= 85) { + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 1; + } + + /* Step 4: Start compressor */ + jpeg_start_compress(&cinfo, TRUE); + + /* Step 5: while (scan lines remain to be written) */ + /* jpeg_write_scanlines(...); */ + row_stride = image_width * cinfo.input_components + padding; /* JSAMPLEs per row in image_buffer */ + + while (cinfo.next_scanline < cinfo.image_height) { + /* jpeg_write_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could pass + * more than one scanline at a time if that's more convenient. + */ + row_pointer[0] = &image_buffer[((cinfo.image_height-1)*row_stride)-cinfo.next_scanline * row_stride]; + (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + /* Step 6: Finish compression */ + jpeg_finish_compress(&cinfo); + + dest = (my_dest_ptr) cinfo.dest; + outcount = dest->size - dest->pub.free_in_buffer; + + /* Step 7: release JPEG compression object */ + jpeg_destroy_compress(&cinfo); + + /* And we're done! */ + return outcount; +} + +void RE_SaveJPG(char * filename, int quality, int image_width, int image_height, byte *image_buffer, int padding) +{ + byte *out; + size_t bufSize; + + bufSize = image_width * image_height * 3; + out = ri.Hunk_AllocateTempMemory(bufSize); + + bufSize = RE_SaveJPGToBuffer(out, bufSize, quality, image_width, image_height, image_buffer, padding); + ri.FS_WriteFile(filename, out, bufSize); + + ri.Hunk_FreeTempMemory(out); +} diff --git a/src/rend2/tr_image_pcx.c b/src/rend2/tr_image_pcx.c new file mode 100644 index 00000000..d4dd575d --- /dev/null +++ b/src/rend2/tr_image_pcx.c @@ -0,0 +1,179 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + 2008 Ludwig Nussel + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qfiles.h" +#include "../qcommon/qcommon.h" +#include "../renderer/tr_public.h" +extern refimport_t ri; + +/* +======================================================================== + +PCX files are used for 8 bit images + +======================================================================== +*/ + +typedef struct { + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + unsigned short hscreensize, vscreensize; + char filler[54]; + unsigned char data[]; +} pcx_t; + +void R_LoadPCX ( const char *filename, byte **pic, int *width, int *height) +{ + union { + byte *b; + void *v; + } raw; + byte *end; + pcx_t *pcx; + int len; + unsigned char dataByte = 0, runLength = 0; + byte *out, *pix; + unsigned short w, h; + byte *pic8; + byte *palette; + int i; + unsigned size = 0; + + if (width) + *width = 0; + if (height) + *height = 0; + *pic = NULL; + + // + // load the file + // + len = ri.FS_ReadFile( ( char * ) filename, &raw.v); + if (!raw.b || len < 0) { + return; + } + + if((unsigned)len < sizeof(pcx_t)) + { + ri.Printf (PRINT_ALL, "PCX truncated: %s\n", filename); + ri.FS_FreeFile (raw.v); + return; + } + + // + // parse the PCX file + // + pcx = (pcx_t *)raw.b; + end = raw.b+len; + + w = LittleShort(pcx->xmax)+1; + h = LittleShort(pcx->ymax)+1; + size = w*h; + + if (pcx->manufacturer != 0x0a + || pcx->version != 5 + || pcx->encoding != 1 + || pcx->color_planes != 1 + || pcx->bits_per_pixel != 8 + || w >= 1024 + || h >= 1024) + { + ri.Printf (PRINT_ALL, "Bad or unsupported pcx file %s (%dx%d@%d)\n", filename, w, h, pcx->bits_per_pixel); + return; + } + + pix = pic8 = ri.Malloc ( size ); + + raw.b = pcx->data; + // FIXME: should use bytes_per_line but original q3 didn't do that either + while(pix < pic8+size) + { + if(runLength > 0) { + *pix++ = dataByte; + --runLength; + continue; + } + + if(raw.b+1 > end) + break; + dataByte = *raw.b++; + + if((dataByte & 0xC0) == 0xC0) + { + if(raw.b+1 > end) + break; + runLength = dataByte & 0x3F; + dataByte = *raw.b++; + } + else + runLength = 1; + } + + if(pix < pic8+size) + { + ri.Printf (PRINT_ALL, "PCX file truncated: %s\n", filename); + ri.FS_FreeFile (pcx); + ri.Free (pic8); + } + + if (raw.b-(byte*)pcx >= end - (byte*)769 || end[-769] != 0x0c) + { + ri.Printf (PRINT_ALL, "PCX missing palette: %s\n", filename); + ri.FS_FreeFile (pcx); + ri.Free (pic8); + return; + } + + palette = end-768; + + pix = out = ri.Malloc(4 * size ); + for (i = 0 ; i < size ; i++) + { + unsigned char p = pic8[i]; + pix[0] = palette[p*3]; + pix[1] = palette[p*3 + 1]; + pix[2] = palette[p*3 + 2]; + pix[3] = 255; + pix += 4; + } + + if (width) + *width = w; + if (height) + *height = h; + + *pic = out; + + ri.FS_FreeFile (pcx); + ri.Free (pic8); +} diff --git a/src/rend2/tr_image_png.c b/src/rend2/tr_image_png.c new file mode 100644 index 00000000..ee37aa75 --- /dev/null +++ b/src/rend2/tr_image_png.c @@ -0,0 +1,2490 @@ +/* +=========================================================================== +ioquake3 png decoder +Copyright (C) 2007,2008 Joerg Dietrich + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qfiles.h" +#include "../qcommon/qcommon.h" +#include "../renderer/tr_public.h" +extern refimport_t ri; + +#include "../qcommon/puff.h" + +// we could limit the png size to a lower value here +#ifndef INT_MAX +#define INT_MAX 0x1fffffff +#endif + +/* +================= +PNG LOADING +================= +*/ + +/* + * Quake 3 image format : RGBA + */ + +#define Q3IMAGE_BYTESPERPIXEL (4) + +/* + * PNG specifications + */ + +/* + * The first 8 Bytes of every PNG-File are a fixed signature + * to identify the file as a PNG. + */ + +#define PNG_Signature "\x89\x50\x4E\x47\xD\xA\x1A\xA" +#define PNG_Signature_Size (8) + +/* + * After the signature diverse chunks follow. + * A chunk consists of a header and if Length + * is bigger than 0 a body and a CRC of the body follow. + */ + +struct PNG_ChunkHeader +{ + uint32_t Length; + uint32_t Type; +}; + +#define PNG_ChunkHeader_Size (8) + +typedef uint32_t PNG_ChunkCRC; + +#define PNG_ChunkCRC_Size (4) + +/* + * We use the following ChunkTypes. + * All others are ignored. + */ + +#define MAKE_CHUNKTYPE(a,b,c,d) (((a) << 24) | ((b) << 16) | ((c) << 8) | ((d))) + +#define PNG_ChunkType_IHDR MAKE_CHUNKTYPE('I', 'H', 'D', 'R') +#define PNG_ChunkType_PLTE MAKE_CHUNKTYPE('P', 'L', 'T', 'E') +#define PNG_ChunkType_IDAT MAKE_CHUNKTYPE('I', 'D', 'A', 'T') +#define PNG_ChunkType_IEND MAKE_CHUNKTYPE('I', 'E', 'N', 'D') +#define PNG_ChunkType_tRNS MAKE_CHUNKTYPE('t', 'R', 'N', 'S') + +/* + * Per specification the first chunk after the signature SHALL be IHDR. + */ + +struct PNG_Chunk_IHDR +{ + uint32_t Width; + uint32_t Height; + uint8_t BitDepth; + uint8_t ColourType; + uint8_t CompressionMethod; + uint8_t FilterMethod; + uint8_t InterlaceMethod; +}; + +#define PNG_Chunk_IHDR_Size (13) + +/* + * ColourTypes + */ + +#define PNG_ColourType_Grey (0) +#define PNG_ColourType_True (2) +#define PNG_ColourType_Indexed (3) +#define PNG_ColourType_GreyAlpha (4) +#define PNG_ColourType_TrueAlpha (6) + +/* + * number of colour components + * + * Grey : 1 grey + * True : 1 R, 1 G, 1 B + * Indexed : 1 index + * GreyAlpha : 1 grey, 1 alpha + * TrueAlpha : 1 R, 1 G, 1 B, 1 alpha + */ + +#define PNG_NumColourComponents_Grey (1) +#define PNG_NumColourComponents_True (3) +#define PNG_NumColourComponents_Indexed (1) +#define PNG_NumColourComponents_GreyAlpha (2) +#define PNG_NumColourComponents_TrueAlpha (4) + +/* + * For the different ColourTypes + * different BitDepths are specified. + */ + +#define PNG_BitDepth_1 ( 1) +#define PNG_BitDepth_2 ( 2) +#define PNG_BitDepth_4 ( 4) +#define PNG_BitDepth_8 ( 8) +#define PNG_BitDepth_16 (16) + +/* + * Only one valid CompressionMethod is standardized. + */ + +#define PNG_CompressionMethod_0 (0) + +/* + * Only one valid FilterMethod is currently standardized. + */ + +#define PNG_FilterMethod_0 (0) + +/* + * This FilterMethod defines 5 FilterTypes + */ + +#define PNG_FilterType_None (0) +#define PNG_FilterType_Sub (1) +#define PNG_FilterType_Up (2) +#define PNG_FilterType_Average (3) +#define PNG_FilterType_Paeth (4) + +/* + * Two InterlaceMethods are standardized : + * 0 - NonInterlaced + * 1 - Interlaced + */ + +#define PNG_InterlaceMethod_NonInterlaced (0) +#define PNG_InterlaceMethod_Interlaced (1) + +/* + * The Adam7 interlace method uses 7 passes. + */ + +#define PNG_Adam7_NumPasses (7) + +/* + * The compressed data starts with a header ... + */ + +struct PNG_ZlibHeader +{ + uint8_t CompressionMethod; + uint8_t Flags; +}; + +#define PNG_ZlibHeader_Size (2) + +/* + * ... and is followed by a check value + */ + +#define PNG_ZlibCheckValue_Size (4) + +/* + * Some support functions for buffered files follow. + */ + +/* + * buffered file representation + */ + +struct BufferedFile +{ + byte *Buffer; + int Length; + byte *Ptr; + int BytesLeft; +}; + +/* + * Read a file into a buffer. + */ + +static struct BufferedFile *ReadBufferedFile(const char *name) +{ + struct BufferedFile *BF; + union { + byte *b; + void *v; + } buffer; + + /* + * input verification + */ + + if(!name) + { + return(NULL); + } + + /* + * Allocate control struct. + */ + + BF = ri.Malloc(sizeof(struct BufferedFile)); + if(!BF) + { + return(NULL); + } + + /* + * Initialize the structs components. + */ + + BF->Length = 0; + BF->Buffer = NULL; + BF->Ptr = NULL; + BF->BytesLeft = 0; + + /* + * Read the file. + */ + + BF->Length = ri.FS_ReadFile((char *) name, &buffer.v); + BF->Buffer = buffer.b; + + /* + * Did we get it? Is it big enough? + */ + + if(!(BF->Buffer && (BF->Length > 0))) + { + ri.Free(BF); + + return(NULL); + } + + /* + * Set the pointers and counters. + */ + + BF->Ptr = BF->Buffer; + BF->BytesLeft = BF->Length; + + return(BF); +} + +/* + * Close a buffered file. + */ + +static void CloseBufferedFile(struct BufferedFile *BF) +{ + if(BF) + { + if(BF->Buffer) + { + ri.FS_FreeFile(BF->Buffer); + } + + ri.Free(BF); + } +} + +/* + * Get a pointer to the requested bytes. + */ + +static void *BufferedFileRead(struct BufferedFile *BF, unsigned Length) +{ + void *RetVal; + + /* + * input verification + */ + + if(!(BF && Length)) + { + return(NULL); + } + + /* + * not enough bytes left + */ + + if(Length > BF->BytesLeft) + { + return(NULL); + } + + /* + * the pointer to the requested data + */ + + RetVal = BF->Ptr; + + /* + * Raise the pointer and counter. + */ + + BF->Ptr += Length; + BF->BytesLeft -= Length; + + return(RetVal); +} + +/* + * Rewind the buffer. + */ + +static qboolean BufferedFileRewind(struct BufferedFile *BF, unsigned Offset) +{ + unsigned BytesRead; + + /* + * input verification + */ + + if(!BF) + { + return(qfalse); + } + + /* + * special trick to rewind to the beginning of the buffer + */ + + if(Offset == (unsigned)-1) + { + BF->Ptr = BF->Buffer; + BF->BytesLeft = BF->Length; + + return(qtrue); + } + + /* + * How many bytes do we have already read? + */ + + BytesRead = BF->Ptr - BF->Buffer; + + /* + * We can only rewind to the beginning of the BufferedFile. + */ + + if(Offset > BytesRead) + { + return(qfalse); + } + + /* + * lower the pointer and counter. + */ + + BF->Ptr -= Offset; + BF->BytesLeft += Offset; + + return(qtrue); +} + +/* + * Skip some bytes. + */ + +static qboolean BufferedFileSkip(struct BufferedFile *BF, unsigned Offset) +{ + /* + * input verification + */ + + if(!BF) + { + return(qfalse); + } + + /* + * We can only skip to the end of the BufferedFile. + */ + + if(Offset > BF->BytesLeft) + { + return(qfalse); + } + + /* + * lower the pointer and counter. + */ + + BF->Ptr += Offset; + BF->BytesLeft -= Offset; + + return(qtrue); +} + +/* + * Find a chunk + */ + +static qboolean FindChunk(struct BufferedFile *BF, uint32_t ChunkType) +{ + struct PNG_ChunkHeader *CH; + + uint32_t Length; + uint32_t Type; + + /* + * input verification + */ + + if(!BF) + { + return(qfalse); + } + + /* + * cycle trough the chunks + */ + + while(qtrue) + { + /* + * Read the chunk-header. + */ + + CH = BufferedFileRead(BF, PNG_ChunkHeader_Size); + if(!CH) + { + return(qfalse); + } + + /* + * Do not swap the original types + * they might be needed later. + */ + + Length = BigLong(CH->Length); + Type = BigLong(CH->Type); + + /* + * We found it! + */ + + if(Type == ChunkType) + { + /* + * Rewind to the start of the chunk. + */ + + BufferedFileRewind(BF, PNG_ChunkHeader_Size); + + break; + } + else + { + /* + * Skip the rest of the chunk. + */ + + if(Length) + { + if(!BufferedFileSkip(BF, Length + PNG_ChunkCRC_Size)) + { + return(qfalse); + } + } + } + } + + return(qtrue); +} + +/* + * Decompress all IDATs + */ + +static uint32_t DecompressIDATs(struct BufferedFile *BF, uint8_t **Buffer) +{ + uint8_t *DecompressedData; + uint32_t DecompressedDataLength; + + uint8_t *CompressedData; + uint8_t *CompressedDataPtr; + uint32_t CompressedDataLength; + + struct PNG_ChunkHeader *CH; + + uint32_t Length; + uint32_t Type; + + int BytesToRewind; + + int32_t puffResult; + uint8_t *puffDest; + uint32_t puffDestLen; + uint8_t *puffSrc; + uint32_t puffSrcLen; + + /* + * input verification + */ + + if(!(BF && Buffer)) + { + return(-1); + } + + /* + * some zeroing + */ + + DecompressedData = NULL; + DecompressedDataLength = 0; + *Buffer = DecompressedData; + + CompressedData = NULL; + CompressedDataLength = 0; + + BytesToRewind = 0; + + /* + * Find the first IDAT chunk. + */ + + if(!FindChunk(BF, PNG_ChunkType_IDAT)) + { + return(-1); + } + + /* + * Count the size of the uncompressed data + */ + + while(qtrue) + { + /* + * Read chunk header + */ + + CH = BufferedFileRead(BF, PNG_ChunkHeader_Size); + if(!CH) + { + /* + * Rewind to the start of this adventure + * and return unsuccessfull + */ + + BufferedFileRewind(BF, BytesToRewind); + + return(-1); + } + + /* + * Length and Type of chunk + */ + + Length = BigLong(CH->Length); + Type = BigLong(CH->Type); + + /* + * We have reached the end of the IDAT chunks + */ + + if(!(Type == PNG_ChunkType_IDAT)) + { + BufferedFileRewind(BF, PNG_ChunkHeader_Size); + + break; + } + + /* + * Add chunk header to count. + */ + + BytesToRewind += PNG_ChunkHeader_Size; + + /* + * Skip to next chunk + */ + + if(Length) + { + if(!BufferedFileSkip(BF, Length + PNG_ChunkCRC_Size)) + { + BufferedFileRewind(BF, BytesToRewind); + + return(-1); + } + + BytesToRewind += Length + PNG_ChunkCRC_Size; + CompressedDataLength += Length; + } + } + + BufferedFileRewind(BF, BytesToRewind); + + CompressedData = ri.Malloc(CompressedDataLength); + if(!CompressedData) + { + return(-1); + } + + CompressedDataPtr = CompressedData; + + /* + * Collect the compressed Data + */ + + while(qtrue) + { + /* + * Read chunk header + */ + + CH = BufferedFileRead(BF, PNG_ChunkHeader_Size); + if(!CH) + { + ri.Free(CompressedData); + + return(-1); + } + + /* + * Length and Type of chunk + */ + + Length = BigLong(CH->Length); + Type = BigLong(CH->Type); + + /* + * We have reached the end of the IDAT chunks + */ + + if(!(Type == PNG_ChunkType_IDAT)) + { + BufferedFileRewind(BF, PNG_ChunkHeader_Size); + + break; + } + + /* + * Copy the Data + */ + + if(Length) + { + uint8_t *OrigCompressedData; + + OrigCompressedData = BufferedFileRead(BF, Length); + if(!OrigCompressedData) + { + ri.Free(CompressedData); + + return(-1); + } + + if(!BufferedFileSkip(BF, PNG_ChunkCRC_Size)) + { + ri.Free(CompressedData); + + return(-1); + } + + memcpy(CompressedDataPtr, OrigCompressedData, Length); + CompressedDataPtr += Length; + } + } + + /* + * Let puff() calculate the decompressed data length. + */ + + puffDest = NULL; + puffDestLen = 0; + + /* + * The zlib header and checkvalue don't belong to the compressed data. + */ + + puffSrc = CompressedData + PNG_ZlibHeader_Size; + puffSrcLen = CompressedDataLength - PNG_ZlibHeader_Size - PNG_ZlibCheckValue_Size; + + /* + * first puff() to calculate the size of the uncompressed data + */ + + puffResult = puff(puffDest, &puffDestLen, puffSrc, &puffSrcLen); + if(!((puffResult == 0) && (puffDestLen > 0))) + { + ri.Free(CompressedData); + + return(-1); + } + + /* + * Allocate the buffer for the uncompressed data. + */ + + DecompressedData = ri.Malloc(puffDestLen); + if(!DecompressedData) + { + ri.Free(CompressedData); + + return(-1); + } + + /* + * Set the input again in case something was changed by the last puff() . + */ + + puffDest = DecompressedData; + puffSrc = CompressedData + PNG_ZlibHeader_Size; + puffSrcLen = CompressedDataLength - PNG_ZlibHeader_Size - PNG_ZlibCheckValue_Size; + + /* + * decompression puff() + */ + + puffResult = puff(puffDest, &puffDestLen, puffSrc, &puffSrcLen); + + /* + * The compressed data is not needed anymore. + */ + + ri.Free(CompressedData); + + /* + * Check if the last puff() was successfull. + */ + + if(!((puffResult == 0) && (puffDestLen > 0))) + { + ri.Free(DecompressedData); + + return(-1); + } + + /* + * Set the output of this function. + */ + + DecompressedDataLength = puffDestLen; + *Buffer = DecompressedData; + + return(DecompressedDataLength); +} + +/* + * the Paeth predictor + */ + +static uint8_t PredictPaeth(uint8_t a, uint8_t b, uint8_t c) +{ + /* + * a == Left + * b == Up + * c == UpLeft + */ + + uint8_t Pr; + int p; + int pa, pb, pc; + + p = ((int) a) + ((int) b) - ((int) c); + pa = abs(p - ((int) a)); + pb = abs(p - ((int) b)); + pc = abs(p - ((int) c)); + + if((pa <= pb) && (pa <= pc)) + { + Pr = a; + } + else if(pb <= pc) + { + Pr = b; + } + else + { + Pr = c; + } + + return(Pr); + +} + +/* + * Reverse the filters. + */ + +static qboolean UnfilterImage(uint8_t *DecompressedData, + uint32_t ImageHeight, + uint32_t BytesPerScanline, + uint32_t BytesPerPixel) +{ + uint8_t *DecompPtr; + uint8_t FilterType; + uint8_t *PixelLeft, *PixelUp, *PixelUpLeft; + uint32_t w, h, p; + + /* + * some zeros for the filters + */ + + uint8_t Zeros[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + + /* + * input verification + */ + + if(!(DecompressedData && BytesPerPixel)) + { + return(qfalse); + } + + /* + * ImageHeight and BytesPerScanline can be zero in small interlaced images. + */ + + if((!ImageHeight) || (!BytesPerScanline)) + { + return(qtrue); + } + + /* + * Set the pointer to the start of the decompressed Data. + */ + + DecompPtr = DecompressedData; + + /* + * Un-filtering is done in place. + */ + + /* + * Go trough all scanlines. + */ + + for(h = 0; h < ImageHeight; h++) + { + /* + * Every scanline starts with a FilterType byte. + */ + + FilterType = *DecompPtr; + DecompPtr++; + + /* + * Left pixel of the first byte in a scanline is zero. + */ + + PixelLeft = Zeros; + + /* + * Set PixelUp to previous line only if we are on the second line or above. + * + * Plus one byte for the FilterType + */ + + if(h > 0) + { + PixelUp = DecompPtr - (BytesPerScanline + 1); + } + else + { + PixelUp = Zeros; + } + + /* + * The pixel left to the first pixel of the previous scanline is zero too. + */ + + PixelUpLeft = Zeros; + + /* + * Cycle trough all pixels of the scanline. + */ + + for(w = 0; w < (BytesPerScanline / BytesPerPixel); w++) + { + /* + * Cycle trough the bytes of the pixel. + */ + + for(p = 0; p < BytesPerPixel; p++) + { + switch(FilterType) + { + case PNG_FilterType_None : + { + /* + * The byte is unfiltered. + */ + + break; + } + + case PNG_FilterType_Sub : + { + DecompPtr[p] += PixelLeft[p]; + + break; + } + + case PNG_FilterType_Up : + { + DecompPtr[p] += PixelUp[p]; + + break; + } + + case PNG_FilterType_Average : + { + DecompPtr[p] += ((uint8_t) ((((uint16_t) PixelLeft[p]) + ((uint16_t) PixelUp[p])) / 2)); + + break; + } + + case PNG_FilterType_Paeth : + { + DecompPtr[p] += PredictPaeth(PixelLeft[p], PixelUp[p], PixelUpLeft[p]); + + break; + } + + default : + { + return(qfalse); + } + } + } + + PixelLeft = DecompPtr; + + /* + * We only have an upleft pixel if we are on the second line or above. + */ + + if(h > 0) + { + PixelUpLeft = DecompPtr - (BytesPerScanline + 1); + } + + /* + * Skip to the next pixel. + */ + + DecompPtr += BytesPerPixel; + + /* + * We only have a previous line if we are on the second line and above. + */ + + if(h > 0) + { + PixelUp = DecompPtr - (BytesPerScanline + 1); + } + } + } + + return(qtrue); +} + +/* + * Convert a raw input pixel to Quake 3 RGA format. + */ + +static qboolean ConvertPixel(struct PNG_Chunk_IHDR *IHDR, + byte *OutPtr, + uint8_t *DecompPtr, + qboolean HasTransparentColour, + uint8_t *TransparentColour, + uint8_t *OutPal) +{ + /* + * input verification + */ + + if(!(IHDR && OutPtr && DecompPtr && TransparentColour && OutPal)) + { + return(qfalse); + } + + switch(IHDR->ColourType) + { + case PNG_ColourType_Grey : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_1 : + case PNG_BitDepth_2 : + case PNG_BitDepth_4 : + { + uint8_t Step; + uint8_t GreyValue; + + Step = 0xFF / ((1 << IHDR->BitDepth) - 1); + + GreyValue = DecompPtr[0] * Step; + + OutPtr[0] = GreyValue; + OutPtr[1] = GreyValue; + OutPtr[2] = GreyValue; + OutPtr[3] = 0xFF; + + /* + * Grey supports full transparency for one specified colour + */ + + if(HasTransparentColour) + { + if(TransparentColour[1] == DecompPtr[0]) + { + OutPtr[3] = 0x00; + } + } + + + break; + } + + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[0]; + OutPtr[2] = DecompPtr[0]; + OutPtr[3] = 0xFF; + + /* + * Grey supports full transparency for one specified colour + */ + + if(HasTransparentColour) + { + if(IHDR->BitDepth == PNG_BitDepth_8) + { + if(TransparentColour[1] == DecompPtr[0]) + { + OutPtr[3] = 0x00; + } + } + else + { + if((TransparentColour[0] == DecompPtr[0]) && (TransparentColour[1] == DecompPtr[1])) + { + OutPtr[3] = 0x00; + } + } + } + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_True : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + { + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[1]; + OutPtr[2] = DecompPtr[2]; + OutPtr[3] = 0xFF; + + /* + * True supports full transparency for one specified colour + */ + + if(HasTransparentColour) + { + if((TransparentColour[1] == DecompPtr[0]) && + (TransparentColour[3] == DecompPtr[1]) && + (TransparentColour[5] == DecompPtr[2])) + { + OutPtr[3] = 0x00; + } + } + + break; + } + + case PNG_BitDepth_16 : + { + /* + * We use only the upper byte. + */ + + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[2]; + OutPtr[2] = DecompPtr[4]; + OutPtr[3] = 0xFF; + + /* + * True supports full transparency for one specified colour + */ + + if(HasTransparentColour) + { + if((TransparentColour[0] == DecompPtr[0]) && (TransparentColour[1] == DecompPtr[1]) && + (TransparentColour[2] == DecompPtr[2]) && (TransparentColour[3] == DecompPtr[3]) && + (TransparentColour[4] == DecompPtr[4]) && (TransparentColour[5] == DecompPtr[5])) + { + OutPtr[3] = 0x00; + } + } + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_Indexed : + { + OutPtr[0] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 0]; + OutPtr[1] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 1]; + OutPtr[2] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 2]; + OutPtr[3] = OutPal[DecompPtr[0] * Q3IMAGE_BYTESPERPIXEL + 3]; + + break; + } + + case PNG_ColourType_GreyAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + { + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[0]; + OutPtr[2] = DecompPtr[0]; + OutPtr[3] = DecompPtr[1]; + + break; + } + + case PNG_BitDepth_16 : + { + /* + * We use only the upper byte. + */ + + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[0]; + OutPtr[2] = DecompPtr[0]; + OutPtr[3] = DecompPtr[2]; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_TrueAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + { + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[1]; + OutPtr[2] = DecompPtr[2]; + OutPtr[3] = DecompPtr[3]; + + break; + } + + case PNG_BitDepth_16 : + { + /* + * We use only the upper byte. + */ + + OutPtr[0] = DecompPtr[0]; + OutPtr[1] = DecompPtr[2]; + OutPtr[2] = DecompPtr[4]; + OutPtr[3] = DecompPtr[6]; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + default : + { + return(qfalse); + } + } + + return(qtrue); +} + + +/* + * Decode a non-interlaced image. + */ + +static qboolean DecodeImageNonInterlaced(struct PNG_Chunk_IHDR *IHDR, + byte *OutBuffer, + uint8_t *DecompressedData, + uint32_t DecompressedDataLength, + qboolean HasTransparentColour, + uint8_t *TransparentColour, + uint8_t *OutPal) +{ + uint32_t IHDR_Width; + uint32_t IHDR_Height; + uint32_t BytesPerScanline, BytesPerPixel, PixelsPerByte; + uint32_t w, h, p; + byte *OutPtr; + uint8_t *DecompPtr; + + /* + * input verification + */ + + if(!(IHDR && OutBuffer && DecompressedData && DecompressedDataLength && TransparentColour && OutPal)) + { + return(qfalse); + } + + /* + * byte swapping + */ + + IHDR_Width = BigLong(IHDR->Width); + IHDR_Height = BigLong(IHDR->Height); + + /* + * information for un-filtering + */ + + switch(IHDR->ColourType) + { + case PNG_ColourType_Grey : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_1 : + case PNG_BitDepth_2 : + case PNG_BitDepth_4 : + { + BytesPerPixel = 1; + PixelsPerByte = 8 / IHDR->BitDepth; + + break; + } + + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_Grey; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_True : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_True; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_Indexed : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_1 : + case PNG_BitDepth_2 : + case PNG_BitDepth_4 : + { + BytesPerPixel = 1; + PixelsPerByte = 8 / IHDR->BitDepth; + + break; + } + + case PNG_BitDepth_8 : + { + BytesPerPixel = PNG_NumColourComponents_Indexed; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_GreyAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_GreyAlpha; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_TrueAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_TrueAlpha; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + default : + { + return(qfalse); + } + } + + /* + * Calculate the size of one scanline + */ + + BytesPerScanline = (IHDR_Width * BytesPerPixel + (PixelsPerByte - 1)) / PixelsPerByte; + + /* + * Check if we have enough data for the whole image. + */ + + if(!(DecompressedDataLength == ((BytesPerScanline + 1) * IHDR_Height))) + { + return(qfalse); + } + + /* + * Unfilter the image. + */ + + if(!UnfilterImage(DecompressedData, IHDR_Height, BytesPerScanline, BytesPerPixel)) + { + return(qfalse); + } + + /* + * Set the working pointers to the beginning of the buffers. + */ + + OutPtr = OutBuffer; + DecompPtr = DecompressedData; + + /* + * Create the output image. + */ + + for(h = 0; h < IHDR_Height; h++) + { + /* + * Count the pixels on the scanline for those multipixel bytes + */ + + uint32_t CurrPixel; + + /* + * skip FilterType + */ + + DecompPtr++; + + /* + * Reset the pixel count. + */ + + CurrPixel = 0; + + for(w = 0; w < (BytesPerScanline / BytesPerPixel); w++) + { + if(PixelsPerByte > 1) + { + uint8_t Mask; + uint32_t Shift; + uint8_t SinglePixel; + + for(p = 0; p < PixelsPerByte; p++) + { + if(CurrPixel < IHDR_Width) + { + Mask = (1 << IHDR->BitDepth) - 1; + Shift = (PixelsPerByte - 1 - p) * IHDR->BitDepth; + + SinglePixel = ((DecompPtr[0] & (Mask << Shift)) >> Shift); + + if(!ConvertPixel(IHDR, OutPtr, &SinglePixel, HasTransparentColour, TransparentColour, OutPal)) + { + return(qfalse); + } + + OutPtr += Q3IMAGE_BYTESPERPIXEL; + CurrPixel++; + } + } + + } + else + { + if(!ConvertPixel(IHDR, OutPtr, DecompPtr, HasTransparentColour, TransparentColour, OutPal)) + { + return(qfalse); + } + + + OutPtr += Q3IMAGE_BYTESPERPIXEL; + } + + DecompPtr += BytesPerPixel; + } + } + + return(qtrue); +} + +/* + * Decode an interlaced image. + */ + +static qboolean DecodeImageInterlaced(struct PNG_Chunk_IHDR *IHDR, + byte *OutBuffer, + uint8_t *DecompressedData, + uint32_t DecompressedDataLength, + qboolean HasTransparentColour, + uint8_t *TransparentColour, + uint8_t *OutPal) +{ + uint32_t IHDR_Width; + uint32_t IHDR_Height; + uint32_t BytesPerScanline[PNG_Adam7_NumPasses], BytesPerPixel, PixelsPerByte; + uint32_t PassWidth[PNG_Adam7_NumPasses], PassHeight[PNG_Adam7_NumPasses]; + uint32_t WSkip[PNG_Adam7_NumPasses], WOffset[PNG_Adam7_NumPasses], HSkip[PNG_Adam7_NumPasses], HOffset[PNG_Adam7_NumPasses]; + uint32_t w, h, p, a; + byte *OutPtr; + uint8_t *DecompPtr; + uint32_t TargetLength; + + /* + * input verification + */ + + if(!(IHDR && OutBuffer && DecompressedData && DecompressedDataLength && TransparentColour && OutPal)) + { + return(qfalse); + } + + /* + * byte swapping + */ + + IHDR_Width = BigLong(IHDR->Width); + IHDR_Height = BigLong(IHDR->Height); + + /* + * Skip and Offset for the passes. + */ + + WSkip[0] = 8; + WOffset[0] = 0; + HSkip[0] = 8; + HOffset[0] = 0; + + WSkip[1] = 8; + WOffset[1] = 4; + HSkip[1] = 8; + HOffset[1] = 0; + + WSkip[2] = 4; + WOffset[2] = 0; + HSkip[2] = 8; + HOffset[2] = 4; + + WSkip[3] = 4; + WOffset[3] = 2; + HSkip[3] = 4; + HOffset[3] = 0; + + WSkip[4] = 2; + WOffset[4] = 0; + HSkip[4] = 4; + HOffset[4] = 2; + + WSkip[5] = 2; + WOffset[5] = 1; + HSkip[5] = 2; + HOffset[5] = 0; + + WSkip[6] = 1; + WOffset[6] = 0; + HSkip[6] = 2; + HOffset[6] = 1; + + /* + * Calculate the sizes of the passes. + */ + + PassWidth[0] = (IHDR_Width + 7) / 8; + PassHeight[0] = (IHDR_Height + 7) / 8; + + PassWidth[1] = (IHDR_Width + 3) / 8; + PassHeight[1] = (IHDR_Height + 7) / 8; + + PassWidth[2] = (IHDR_Width + 3) / 4; + PassHeight[2] = (IHDR_Height + 3) / 8; + + PassWidth[3] = (IHDR_Width + 1) / 4; + PassHeight[3] = (IHDR_Height + 3) / 4; + + PassWidth[4] = (IHDR_Width + 1) / 2; + PassHeight[4] = (IHDR_Height + 1) / 4; + + PassWidth[5] = (IHDR_Width + 0) / 2; + PassHeight[5] = (IHDR_Height + 1) / 2; + + PassWidth[6] = (IHDR_Width + 0) / 1; + PassHeight[6] = (IHDR_Height + 0) / 2; + + /* + * information for un-filtering + */ + + switch(IHDR->ColourType) + { + case PNG_ColourType_Grey : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_1 : + case PNG_BitDepth_2 : + case PNG_BitDepth_4 : + { + BytesPerPixel = 1; + PixelsPerByte = 8 / IHDR->BitDepth; + + break; + } + + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_Grey; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_True : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_True; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_Indexed : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_1 : + case PNG_BitDepth_2 : + case PNG_BitDepth_4 : + { + BytesPerPixel = 1; + PixelsPerByte = 8 / IHDR->BitDepth; + + break; + } + + case PNG_BitDepth_8 : + { + BytesPerPixel = PNG_NumColourComponents_Indexed; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_GreyAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_GreyAlpha; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + case PNG_ColourType_TrueAlpha : + { + switch(IHDR->BitDepth) + { + case PNG_BitDepth_8 : + case PNG_BitDepth_16 : + { + BytesPerPixel = (IHDR->BitDepth / 8) * PNG_NumColourComponents_TrueAlpha; + PixelsPerByte = 1; + + break; + } + + default : + { + return(qfalse); + } + } + + break; + } + + default : + { + return(qfalse); + } + } + + /* + * Calculate the size of the scanlines per pass + */ + + for(a = 0; a < PNG_Adam7_NumPasses; a++) + { + BytesPerScanline[a] = (PassWidth[a] * BytesPerPixel + (PixelsPerByte - 1)) / PixelsPerByte; + } + + /* + * Calculate the size of all passes + */ + + TargetLength = 0; + + for(a = 0; a < PNG_Adam7_NumPasses; a++) + { + TargetLength += ((BytesPerScanline[a] + (BytesPerScanline[a] ? 1 : 0)) * PassHeight[a]); + } + + /* + * Check if we have enough data for the whole image. + */ + + if(!(DecompressedDataLength == TargetLength)) + { + return(qfalse); + } + + /* + * Unfilter the image. + */ + + DecompPtr = DecompressedData; + + for(a = 0; a < PNG_Adam7_NumPasses; a++) + { + if(!UnfilterImage(DecompPtr, PassHeight[a], BytesPerScanline[a], BytesPerPixel)) + { + return(qfalse); + } + + DecompPtr += ((BytesPerScanline[a] + (BytesPerScanline[a] ? 1 : 0)) * PassHeight[a]); + } + + /* + * Set the working pointers to the beginning of the buffers. + */ + + DecompPtr = DecompressedData; + + /* + * Create the output image. + */ + + for(a = 0; a < PNG_Adam7_NumPasses; a++) + { + for(h = 0; h < PassHeight[a]; h++) + { + /* + * Count the pixels on the scanline for those multipixel bytes + */ + + uint32_t CurrPixel; + + /* + * skip FilterType + * but only when the pass has a width bigger than zero + */ + + if(BytesPerScanline[a]) + { + DecompPtr++; + } + + /* + * Reset the pixel count. + */ + + CurrPixel = 0; + + for(w = 0; w < (BytesPerScanline[a] / BytesPerPixel); w++) + { + if(PixelsPerByte > 1) + { + uint8_t Mask; + uint32_t Shift; + uint8_t SinglePixel; + + for(p = 0; p < PixelsPerByte; p++) + { + if(CurrPixel < PassWidth[a]) + { + Mask = (1 << IHDR->BitDepth) - 1; + Shift = (PixelsPerByte - 1 - p) * IHDR->BitDepth; + + SinglePixel = ((DecompPtr[0] & (Mask << Shift)) >> Shift); + + OutPtr = OutBuffer + (((((h * HSkip[a]) + HOffset[a]) * IHDR_Width) + ((CurrPixel * WSkip[a]) + WOffset[a])) * Q3IMAGE_BYTESPERPIXEL); + + if(!ConvertPixel(IHDR, OutPtr, &SinglePixel, HasTransparentColour, TransparentColour, OutPal)) + { + return(qfalse); + } + + CurrPixel++; + } + } + + } + else + { + OutPtr = OutBuffer + (((((h * HSkip[a]) + HOffset[a]) * IHDR_Width) + ((w * WSkip[a]) + WOffset[a])) * Q3IMAGE_BYTESPERPIXEL); + + if(!ConvertPixel(IHDR, OutPtr, DecompPtr, HasTransparentColour, TransparentColour, OutPal)) + { + return(qfalse); + } + } + + DecompPtr += BytesPerPixel; + } + } + } + + return(qtrue); +} + +/* + * The PNG loader + */ + +void R_LoadPNG(const char *name, byte **pic, int *width, int *height) +{ + struct BufferedFile *ThePNG; + byte *OutBuffer; + uint8_t *Signature; + struct PNG_ChunkHeader *CH; + uint32_t ChunkHeaderLength; + uint32_t ChunkHeaderType; + struct PNG_Chunk_IHDR *IHDR; + uint32_t IHDR_Width; + uint32_t IHDR_Height; + PNG_ChunkCRC *CRC; + uint8_t *InPal; + uint8_t *DecompressedData; + uint32_t DecompressedDataLength; + uint32_t i; + + /* + * palette with 256 RGBA entries + */ + + uint8_t OutPal[1024]; + + /* + * transparent colour from the tRNS chunk + */ + + qboolean HasTransparentColour = qfalse; + uint8_t TransparentColour[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + /* + * input verification + */ + + if(!(name && pic)) + { + return; + } + + /* + * Zero out return values. + */ + + *pic = NULL; + + if(width) + { + *width = 0; + } + + if(height) + { + *height = 0; + } + + /* + * Read the file. + */ + + ThePNG = ReadBufferedFile(name); + if(!ThePNG) + { + return; + } + + /* + * Read the siganture of the file. + */ + + Signature = BufferedFileRead(ThePNG, PNG_Signature_Size); + if(!Signature) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Is it a PNG? + */ + + if(memcmp(Signature, PNG_Signature, PNG_Signature_Size)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the first chunk-header. + */ + + CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size); + if(!CH) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * PNG multi-byte types are in Big Endian + */ + + ChunkHeaderLength = BigLong(CH->Length); + ChunkHeaderType = BigLong(CH->Type); + + /* + * Check if the first chunk is an IHDR. + */ + + if(!((ChunkHeaderType == PNG_ChunkType_IHDR) && (ChunkHeaderLength == PNG_Chunk_IHDR_Size))) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the IHDR. + */ + + IHDR = BufferedFileRead(ThePNG, PNG_Chunk_IHDR_Size); + if(!IHDR) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the CRC for IHDR + */ + + CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size); + if(!CRC) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Here we could check the CRC if we wanted to. + */ + + /* + * multi-byte type swapping + */ + + IHDR_Width = BigLong(IHDR->Width); + IHDR_Height = BigLong(IHDR->Height); + + /* + * Check if Width and Height are valid. + */ + + if(!((IHDR_Width > 0) && (IHDR_Height > 0)) + || IHDR_Width > INT_MAX / Q3IMAGE_BYTESPERPIXEL / IHDR_Height) + { + CloseBufferedFile(ThePNG); + + ri.Printf( PRINT_WARNING, "%s: invalid image size\n", name ); + + return; + } + + /* + * Do we need to check if the dimensions of the image are valid for Quake3? + */ + + /* + * Check if CompressionMethod and FilterMethod are valid. + */ + + if(!((IHDR->CompressionMethod == PNG_CompressionMethod_0) && (IHDR->FilterMethod == PNG_FilterMethod_0))) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Check if InterlaceMethod is valid. + */ + + if(!((IHDR->InterlaceMethod == PNG_InterlaceMethod_NonInterlaced) || (IHDR->InterlaceMethod == PNG_InterlaceMethod_Interlaced))) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read palette for an indexed image. + */ + + if(IHDR->ColourType == PNG_ColourType_Indexed) + { + /* + * We need the palette first. + */ + + if(!FindChunk(ThePNG, PNG_ChunkType_PLTE)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the chunk-header. + */ + + CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size); + if(!CH) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * PNG multi-byte types are in Big Endian + */ + + ChunkHeaderLength = BigLong(CH->Length); + ChunkHeaderType = BigLong(CH->Type); + + /* + * Check if the chunk is a PLTE. + */ + + if(!(ChunkHeaderType == PNG_ChunkType_PLTE)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Check if Length is divisible by 3 + */ + + if(ChunkHeaderLength % 3) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the raw palette data + */ + + InPal = BufferedFileRead(ThePNG, ChunkHeaderLength); + if(!InPal) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the CRC for the palette + */ + + CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size); + if(!CRC) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Set some default values. + */ + + for(i = 0; i < 256; i++) + { + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 0] = 0x00; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 1] = 0x00; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 2] = 0x00; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = 0xFF; + } + + /* + * Convert to the Quake3 RGBA-format. + */ + + for(i = 0; i < (ChunkHeaderLength / 3); i++) + { + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 0] = InPal[i*3+0]; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 1] = InPal[i*3+1]; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 2] = InPal[i*3+2]; + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = 0xFF; + } + } + + /* + * transparency information is sometimes stored in a tRNS chunk + */ + + /* + * Let's see if there is a tRNS chunk + */ + + if(FindChunk(ThePNG, PNG_ChunkType_tRNS)) + { + uint8_t *Trans; + + /* + * Read the chunk-header. + */ + + CH = BufferedFileRead(ThePNG, PNG_ChunkHeader_Size); + if(!CH) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * PNG multi-byte types are in Big Endian + */ + + ChunkHeaderLength = BigLong(CH->Length); + ChunkHeaderType = BigLong(CH->Type); + + /* + * Check if the chunk is a tRNS. + */ + + if(!(ChunkHeaderType == PNG_ChunkType_tRNS)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the transparency information. + */ + + Trans = BufferedFileRead(ThePNG, ChunkHeaderLength); + if(!Trans) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Read the CRC. + */ + + CRC = BufferedFileRead(ThePNG, PNG_ChunkCRC_Size); + if(!CRC) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Only for Grey, True and Indexed ColourType should tRNS exist. + */ + + switch(IHDR->ColourType) + { + case PNG_ColourType_Grey : + { + if(!ChunkHeaderLength == 2) + { + CloseBufferedFile(ThePNG); + + return; + } + + HasTransparentColour = qtrue; + + /* + * Grey can have one colour which is completely transparent. + * This colour is always stored in 16 bits. + */ + + TransparentColour[0] = Trans[0]; + TransparentColour[1] = Trans[1]; + + break; + } + + case PNG_ColourType_True : + { + if(!ChunkHeaderLength == 6) + { + CloseBufferedFile(ThePNG); + + return; + } + + HasTransparentColour = qtrue; + + /* + * True can have one colour which is completely transparent. + * This colour is always stored in 16 bits. + */ + + TransparentColour[0] = Trans[0]; + TransparentColour[1] = Trans[1]; + TransparentColour[2] = Trans[2]; + TransparentColour[3] = Trans[3]; + TransparentColour[4] = Trans[4]; + TransparentColour[5] = Trans[5]; + + break; + } + + case PNG_ColourType_Indexed : + { + /* + * Maximum of 256 one byte transparency entries. + */ + + if(ChunkHeaderLength > 256) + { + CloseBufferedFile(ThePNG); + + return; + } + + HasTransparentColour = qtrue; + + /* + * alpha values for palette entries + */ + + for(i = 0; i < ChunkHeaderLength; i++) + { + OutPal[i * Q3IMAGE_BYTESPERPIXEL + 3] = Trans[i]; + } + + break; + } + + /* + * All other ColourTypes should not have tRNS chunks + */ + + default : + { + CloseBufferedFile(ThePNG); + + return; + } + } + } + + /* + * Rewind to the start of the file. + */ + + if(!BufferedFileRewind(ThePNG, -1)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Skip the signature + */ + + if(!BufferedFileSkip(ThePNG, PNG_Signature_Size)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Decompress all IDAT chunks + */ + + DecompressedDataLength = DecompressIDATs(ThePNG, &DecompressedData); + if(!(DecompressedDataLength && DecompressedData)) + { + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Allocate output buffer. + */ + + OutBuffer = ri.Malloc(IHDR_Width * IHDR_Height * Q3IMAGE_BYTESPERPIXEL); + if(!OutBuffer) + { + ri.Free(DecompressedData); + CloseBufferedFile(ThePNG); + + return; + } + + /* + * Interlaced and Non-interlaced images need to be handled differently. + */ + + switch(IHDR->InterlaceMethod) + { + case PNG_InterlaceMethod_NonInterlaced : + { + if(!DecodeImageNonInterlaced(IHDR, OutBuffer, DecompressedData, DecompressedDataLength, HasTransparentColour, TransparentColour, OutPal)) + { + ri.Free(OutBuffer); + ri.Free(DecompressedData); + CloseBufferedFile(ThePNG); + + return; + } + + break; + } + + case PNG_InterlaceMethod_Interlaced : + { + if(!DecodeImageInterlaced(IHDR, OutBuffer, DecompressedData, DecompressedDataLength, HasTransparentColour, TransparentColour, OutPal)) + { + ri.Free(OutBuffer); + ri.Free(DecompressedData); + CloseBufferedFile(ThePNG); + + return; + } + + break; + } + + default : + { + ri.Free(OutBuffer); + ri.Free(DecompressedData); + CloseBufferedFile(ThePNG); + + return; + } + } + + /* + * update the pointer to the image data + */ + + *pic = OutBuffer; + + /* + * Fill width and height. + */ + + if(width) + { + *width = IHDR_Width; + } + + if(height) + { + *height = IHDR_Height; + } + + /* + * DecompressedData is not needed anymore. + */ + + ri.Free(DecompressedData); + + /* + * We have all data, so close the file. + */ + + CloseBufferedFile(ThePNG); +} diff --git a/src/rend2/tr_image_tga.c b/src/rend2/tr_image_tga.c new file mode 100644 index 00000000..27ca0d7b --- /dev/null +++ b/src/rend2/tr_image_tga.c @@ -0,0 +1,324 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qfiles.h" +#include "../qcommon/qcommon.h" +#include "../renderer/tr_public.h" +extern refimport_t ri; + +/* +======================================================================== + +TGA files are used for 24/32 bit images + +======================================================================== +*/ + +typedef struct _TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} TargaHeader; + +void R_LoadTGA ( const char *name, byte **pic, int *width, int *height) +{ + unsigned columns, rows, numPixels; + byte *pixbuf; + int row, column; + byte *buf_p; + byte *end; + union { + byte *b; + void *v; + } buffer; + TargaHeader targa_header; + byte *targa_rgba; + int length; + + *pic = NULL; + + if(width) + *width = 0; + if(height) + *height = 0; + + // + // load the file + // + length = ri.FS_ReadFile ( ( char * ) name, &buffer.v); + if (!buffer.b || length < 0) { + return; + } + + if(length < 18) + { + ri.Error( ERR_DROP, "LoadTGA: header too short (%s)", name ); + } + + buf_p = buffer.b; + end = buffer.b + length; + + targa_header.id_length = buf_p[0]; + targa_header.colormap_type = buf_p[1]; + targa_header.image_type = buf_p[2]; + + memcpy(&targa_header.colormap_index, &buf_p[3], 2); + memcpy(&targa_header.colormap_length, &buf_p[5], 2); + targa_header.colormap_size = buf_p[7]; + memcpy(&targa_header.x_origin, &buf_p[8], 2); + memcpy(&targa_header.y_origin, &buf_p[10], 2); + memcpy(&targa_header.width, &buf_p[12], 2); + memcpy(&targa_header.height, &buf_p[14], 2); + targa_header.pixel_size = buf_p[16]; + targa_header.attributes = buf_p[17]; + + targa_header.colormap_index = LittleShort(targa_header.colormap_index); + targa_header.colormap_length = LittleShort(targa_header.colormap_length); + targa_header.x_origin = LittleShort(targa_header.x_origin); + targa_header.y_origin = LittleShort(targa_header.y_origin); + targa_header.width = LittleShort(targa_header.width); + targa_header.height = LittleShort(targa_header.height); + + buf_p += 18; + + if (targa_header.image_type!=2 + && targa_header.image_type!=10 + && targa_header.image_type != 3 ) + { + ri.Error (ERR_DROP, "LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported"); + } + + if ( targa_header.colormap_type != 0 ) + { + ri.Error( ERR_DROP, "LoadTGA: colormaps not supported" ); + } + + if ( ( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) && targa_header.image_type != 3 ) + { + ri.Error (ERR_DROP, "LoadTGA: Only 32 or 24 bit images supported (no colormaps)"); + } + + columns = targa_header.width; + rows = targa_header.height; + numPixels = columns * rows * 4; + + if(!columns || !rows || numPixels > 0x7FFFFFFF || numPixels / columns / 4 != rows) + { + ri.Error (ERR_DROP, "LoadTGA: %s has an invalid image size", name); + } + + + targa_rgba = ri.Malloc (numPixels); + + if (targa_header.id_length != 0) + { + if (buf_p + targa_header.id_length > end) + ri.Error( ERR_DROP, "LoadTGA: header too short (%s)", name ); + + buf_p += targa_header.id_length; // skip TARGA image comment + } + + if ( targa_header.image_type==2 || targa_header.image_type == 3 ) + { + if(buf_p + columns*rows*targa_header.pixel_size/8 > end) + { + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)", name); + } + + // Uncompressed RGB or gray scale image + for(row=rows-1; row>=0; row--) + { + pixbuf = targa_rgba + row*columns*4; + for(column=0; column=0; row--) { + pixbuf = targa_rgba + row*columns*4; + for(column=0; column end) + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)", name); + packetHeader= *buf_p++; + packetSize = 1 + (packetHeader & 0x7f); + if (packetHeader & 0x80) { // run-length packet + if(buf_p + targa_header.pixel_size/8 > end) + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)", name); + switch (targa_header.pixel_size) { + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = 255; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alphabyte = *buf_p++; + break; + default: + ri.Error( ERR_DROP, "LoadTGA: illegal pixel_size '%d' in file '%s'", targa_header.pixel_size, name ); + break; + } + + for(j=0;j0) + row--; + else + goto breakOut; + pixbuf = targa_rgba + row*columns*4; + } + } + } + else { // non run-length packet + + if(buf_p + targa_header.pixel_size/8*packetSize > end) + ri.Error (ERR_DROP, "LoadTGA: file truncated (%s)", name); + for(j=0;j0) + row--; + else + goto breakOut; + pixbuf = targa_rgba + row*columns*4; + } + } + } + } + breakOut:; + } + } + +#if 0 + // TTimo: this is the chunk of code to ensure a behavior that meets TGA specs + // bit 5 set => top-down + if (targa_header.attributes & 0x20) { + unsigned char *flip = (unsigned char*)malloc (columns*4); + unsigned char *src, *dst; + + for (row = 0; row < rows/2; row++) { + src = targa_rgba + row * 4 * columns; + dst = targa_rgba + (rows - row - 1) * 4 * columns; + + memcpy (flip, src, columns*4); + memcpy (src, dst, columns*4); + memcpy (dst, flip, columns*4); + } + free (flip); + } +#endif + // instead we just print a warning + if (targa_header.attributes & 0x20) { + ri.Printf( PRINT_WARNING, "WARNING: '%s' TGA file header declares top-down image, ignoring\n", name); + } + + if (width) + *width = columns; + if (height) + *height = rows; + + *pic = targa_rgba; + + ri.FS_FreeFile (buffer.v); +} diff --git a/src/rend2/tr_init.c b/src/rend2/tr_init.c new file mode 100644 index 00000000..f03de8b5 --- /dev/null +++ b/src/rend2/tr_init.c @@ -0,0 +1,1560 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_init.c -- functions that are not called every frame + +#include "tr_local.h" + +glconfig_t glConfig; +glRefConfig_t glRefConfig; +qboolean textureFilterAnisotropic = qfalse; +int maxAnisotropy = 0; +float displayAspect = 0.0f; + +glstate_t glState; + +static void GfxInfo_f( void ); +static void GfxMemInfo_f( void ); + +#ifdef USE_RENDERER_DLOPEN +cvar_t *com_altivec; +#endif + +cvar_t *r_flareSize; +cvar_t *r_flareFade; +cvar_t *r_flareCoeff; + +cvar_t *r_railWidth; +cvar_t *r_railCoreWidth; +cvar_t *r_railSegmentLength; + +cvar_t *r_verbose; +cvar_t *r_ignore; + +cvar_t *r_detailTextures; + +cvar_t *r_znear; +cvar_t *r_zproj; +cvar_t *r_stereoSeparation; + +cvar_t *r_smp; +cvar_t *r_showSmp; +cvar_t *r_skipBackEnd; + +cvar_t *r_stereoEnabled; +cvar_t *r_anaglyphMode; + +cvar_t *r_greyscale; + +cvar_t *r_ignorehwgamma; +cvar_t *r_measureOverdraw; + +cvar_t *r_inGameVideo; +cvar_t *r_fastsky; +cvar_t *r_drawSun; +cvar_t *r_dynamiclight; +cvar_t *r_dlightBacks; + +cvar_t *r_lodbias; +cvar_t *r_lodscale; + +cvar_t *r_norefresh; +cvar_t *r_drawentities; +cvar_t *r_drawworld; +cvar_t *r_speeds; +cvar_t *r_fullbright; +cvar_t *r_novis; +cvar_t *r_nocull; +cvar_t *r_facePlaneCull; +cvar_t *r_showcluster; +cvar_t *r_nocurves; + +cvar_t *r_allowExtensions; + +cvar_t *r_ext_compressed_textures; +cvar_t *r_ext_multitexture; +cvar_t *r_ext_compiled_vertex_array; +cvar_t *r_ext_texture_env_add; +cvar_t *r_ext_texture_filter_anisotropic; +cvar_t *r_ext_max_anisotropy; + +cvar_t *r_ext_draw_range_elements; +cvar_t *r_ext_multi_draw_arrays; +cvar_t *r_ext_framebuffer_object; +cvar_t *r_ext_texture_float; +cvar_t *r_arb_half_float_pixel; +cvar_t *r_ext_framebuffer_multisample; + +cvar_t *r_mergeMultidraws; +cvar_t *r_mergeLeafSurfaces; + +cvar_t *r_cameraExposure; +cvar_t *r_hdr; +cvar_t *r_postProcess; + +cvar_t *r_toneMap; +cvar_t *r_forceToneMap; +cvar_t *r_forceToneMapMin; +cvar_t *r_forceToneMapAvg; +cvar_t *r_forceToneMapMax; + +cvar_t *r_autoExposure; +cvar_t *r_forceAutoExposure; +cvar_t *r_forceAutoExposureMin; +cvar_t *r_forceAutoExposureMax; + +cvar_t *r_srgb; + +cvar_t *r_depthPrepass; +cvar_t *r_ssao; + +cvar_t *r_normalMapping; +cvar_t *r_specularMapping; +cvar_t *r_deluxeMapping; +cvar_t *r_parallaxMapping; +cvar_t *r_normalAmbient; +cvar_t *r_recalcMD3Normals; +cvar_t *r_mergeLightmaps; +cvar_t *r_dlightMode; +cvar_t *r_pshadowDist; +cvar_t *r_imageUpsample; +cvar_t *r_imageUpsampleMaxSize; +cvar_t *r_imageUpsampleType; +cvar_t *r_genNormalMaps; +cvar_t *r_forceSun; +cvar_t *r_forceSunMapLightScale; +cvar_t *r_forceSunLightScale; +cvar_t *r_forceSunAmbientScale; +cvar_t *r_sunShadows; +cvar_t *r_shadowFilter; +cvar_t *r_shadowMapSize; +cvar_t *r_shadowCascadeZNear; +cvar_t *r_shadowCascadeZFar; +cvar_t *r_shadowCascadeZBias; + +cvar_t *r_ignoreGLErrors; +cvar_t *r_logFile; + +cvar_t *r_stencilbits; +cvar_t *r_depthbits; +cvar_t *r_colorbits; +cvar_t *r_texturebits; +cvar_t *r_ext_multisample; + +cvar_t *r_drawBuffer; +cvar_t *r_lightmap; +cvar_t *r_vertexLight; +cvar_t *r_uiFullScreen; +cvar_t *r_shadows; +cvar_t *r_flares; +cvar_t *r_mode; +cvar_t *r_nobind; +cvar_t *r_singleShader; +cvar_t *r_roundImagesDown; +cvar_t *r_colorMipLevels; +cvar_t *r_picmip; +cvar_t *r_showtris; +cvar_t *r_showsky; +cvar_t *r_shownormals; +cvar_t *r_finish; +cvar_t *r_clear; +cvar_t *r_swapInterval; +cvar_t *r_textureMode; +cvar_t *r_offsetFactor; +cvar_t *r_offsetUnits; +cvar_t *r_gamma; +cvar_t *r_intensity; +cvar_t *r_lockpvs; +cvar_t *r_noportals; +cvar_t *r_portalOnly; + +cvar_t *r_subdivisions; +cvar_t *r_lodCurveError; + +cvar_t *r_fullscreen; +cvar_t *r_noborder; + +cvar_t *r_customwidth; +cvar_t *r_customheight; +cvar_t *r_customPixelAspect; + +cvar_t *r_overBrightBits; +cvar_t *r_mapOverBrightBits; + +cvar_t *r_debugSurface; +cvar_t *r_simpleMipMaps; + +cvar_t *r_showImages; + +cvar_t *r_ambientScale; +cvar_t *r_directedScale; +cvar_t *r_debugLight; +cvar_t *r_debugSort; +cvar_t *r_printShaders; +cvar_t *r_saveFontData; + +cvar_t *r_marksOnTriangleMeshes; + +cvar_t *r_aviMotionJpegQuality; +cvar_t *r_screenshotJpegQuality; + +cvar_t *r_maxpolys; +int max_polys; +cvar_t *r_maxpolyverts; +int max_polyverts; + +/* +** InitOpenGL +** +** This function is responsible for initializing a valid OpenGL subsystem. This +** is done by calling GLimp_Init (which gives us a working OGL subsystem) then +** setting variables, checking GL constants, and reporting the gfx system config +** to the user. +*/ +static void InitOpenGL( void ) +{ + char renderer_buffer[1024]; + + // + // initialize OS specific portions of the renderer + // + // GLimp_Init directly or indirectly references the following cvars: + // - r_fullscreen + // - r_mode + // - r_(color|depth|stencil)bits + // - r_ignorehwgamma + // - r_gamma + // + + if ( glConfig.vidWidth == 0 ) + { + GLint temp; + + GLimp_Init(); + GLimp_InitExtraExtensions(); + + strcpy( renderer_buffer, glConfig.renderer_string ); + Q_strlwr( renderer_buffer ); + + // OpenGL driver constants + qglGetIntegerv( GL_MAX_TEXTURE_SIZE, &temp ); + glConfig.maxTextureSize = temp; + + // stubbed or broken drivers may have reported 0... + if ( glConfig.maxTextureSize <= 0 ) + { + glConfig.maxTextureSize = 0; + } + } + + // init command buffers and SMP + R_InitCommandBuffers(); + + // set default state + GL_SetDefaultState(); +} + +/* +================== +GL_CheckErrors +================== +*/ +void GL_CheckErrs( char *file, int line ) { + int err; + char s[64]; + + err = qglGetError(); + if ( err == GL_NO_ERROR ) { + return; + } + if ( r_ignoreGLErrors->integer ) { + return; + } + switch( err ) { + case GL_INVALID_ENUM: + strcpy( s, "GL_INVALID_ENUM" ); + break; + case GL_INVALID_VALUE: + strcpy( s, "GL_INVALID_VALUE" ); + break; + case GL_INVALID_OPERATION: + strcpy( s, "GL_INVALID_OPERATION" ); + break; + case GL_STACK_OVERFLOW: + strcpy( s, "GL_STACK_OVERFLOW" ); + break; + case GL_STACK_UNDERFLOW: + strcpy( s, "GL_STACK_UNDERFLOW" ); + break; + case GL_OUT_OF_MEMORY: + strcpy( s, "GL_OUT_OF_MEMORY" ); + break; + default: + Com_sprintf( s, sizeof(s), "%i", err); + break; + } + + ri.Error( ERR_FATAL, "GL_CheckErrors: %s in %s at line %d", s , file, line); +} + + +/* +** R_GetModeInfo +*/ +typedef struct vidmode_s +{ + const char *description; + int width, height; + float pixelAspect; // pixel width / height +} vidmode_t; + +vidmode_t r_vidModes[] = +{ + { "Mode 0: 320x240", 320, 240, 1 }, + { "Mode 1: 400x300", 400, 300, 1 }, + { "Mode 2: 512x384", 512, 384, 1 }, + { "Mode 3: 640x480", 640, 480, 1 }, + { "Mode 4: 800x600", 800, 600, 1 }, + { "Mode 5: 960x720", 960, 720, 1 }, + { "Mode 6: 1024x768", 1024, 768, 1 }, + { "Mode 7: 1152x864", 1152, 864, 1 }, + { "Mode 8: 1280x1024", 1280, 1024, 1 }, + { "Mode 9: 1600x1200", 1600, 1200, 1 }, + { "Mode 10: 2048x1536", 2048, 1536, 1 }, + { "Mode 11: 856x480 (wide)",856, 480, 1 } +}; +static int s_numVidModes = ARRAY_LEN( r_vidModes ); + +qboolean R_GetModeInfo( int *width, int *height, float *windowAspect, int mode ) { + vidmode_t *vm; + float pixelAspect; + + if ( mode < -1 ) { + return qfalse; + } + if ( mode >= s_numVidModes ) { + return qfalse; + } + + if ( mode == -1 ) { + *width = r_customwidth->integer; + *height = r_customheight->integer; + pixelAspect = r_customPixelAspect->value; + } else { + vm = &r_vidModes[mode]; + + *width = vm->width; + *height = vm->height; + pixelAspect = vm->pixelAspect; + } + + *windowAspect = (float)*width / ( *height * pixelAspect ); + + return qtrue; +} + +/* +** R_ModeList_f +*/ +static void R_ModeList_f( void ) +{ + int i; + + ri.Printf( PRINT_ALL, "\n" ); + for ( i = 0; i < s_numVidModes; i++ ) + { + ri.Printf( PRINT_ALL, "%s\n", r_vidModes[i].description ); + } + ri.Printf( PRINT_ALL, "\n" ); +} + + +/* +============================================================================== + + SCREEN SHOTS + +NOTE TTimo +some thoughts about the screenshots system: +screenshots get written in fs_homepath + fs_gamedir +vanilla q3 .. baseq3/screenshots/ *.tga +team arena .. missionpack/screenshots/ *.tga + +two commands: "screenshot" and "screenshotJPEG" +we use statics to store a count and start writing the first screenshot/screenshot????.tga (.jpg) available +(with FS_FileExists / FS_FOpenFileWrite calls) +FIXME: the statics don't get a reinit between fs_game changes + +============================================================================== +*/ + +/* +================== +RB_ReadPixels + +Reads an image but takes care of alignment issues for reading RGB images. + +Reads a minimum offset for where the RGB data starts in the image from +integer stored at pointer offset. When the function has returned the actual +offset was written back to address offset. This address will always have an +alignment of packAlign to ensure efficient copying. + +Stores the length of padding after a line of pixels to address padlen + +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; + + qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign); + + linelen = width * 3; + 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); + + *offset = bufstart - buffer; + *padlen = padwidth - linelen; + + return buffer; +} + +/* +================== +RB_TakeScreenshot +================== +*/ +void RB_TakeScreenshot(int x, int y, int width, int height, char *fileName) +{ + byte *allbuf, *buffer; + byte *srcptr, *destptr; + byte *endline, *endmem; + byte temp; + + int linelen, padlen; + size_t offset = 18, memcount; + + allbuf = RB_ReadPixels(x, y, width, height, &offset, &padlen); + buffer = allbuf + offset - 18; + + Com_Memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 24; // pixel size + + // swap rgb to bgr and remove padding from line endings + linelen = width * 3; + + srcptr = destptr = allbuf + offset; + endmem = srcptr + (linelen + padlen) * height; + + while(srcptr < endmem) + { + endline = srcptr + linelen; + + while(srcptr < endline) + { + temp = srcptr[0]; + *destptr++ = srcptr[2]; + *destptr++ = srcptr[1]; + *destptr++ = temp; + + srcptr += 3; + } + + // Skip the pad + srcptr += padlen; + } + + memcount = linelen * height; + + // gamma correct + if(glConfig.deviceSupportsGamma) + R_GammaCorrect(allbuf + offset, memcount); + + ri.FS_WriteFile(fileName, buffer, memcount + 18); + + ri.Hunk_FreeTempMemory(allbuf); +} + +/* +================== +RB_TakeScreenshotJPEG +================== +*/ + +void RB_TakeScreenshotJPEG(int x, int y, int width, int height, char *fileName) +{ + byte *buffer; + size_t offset = 0, memcount; + int padlen; + + buffer = RB_ReadPixels(x, y, width, height, &offset, &padlen); + memcount = (width * 3 + padlen) * height; + + // gamma correct + if(glConfig.deviceSupportsGamma) + R_GammaCorrect(buffer + offset, memcount); + + RE_SaveJPG(fileName, r_screenshotJpegQuality->integer, width, height, buffer + offset, padlen); + ri.Hunk_FreeTempMemory(buffer); +} + +/* +================== +RB_TakeScreenshotCmd +================== +*/ +const void *RB_TakeScreenshotCmd( const void *data ) { + const screenshotCommand_t *cmd; + + cmd = (const screenshotCommand_t *)data; + + if (cmd->jpeg) + RB_TakeScreenshotJPEG( cmd->x, cmd->y, cmd->width, cmd->height, cmd->fileName); + else + RB_TakeScreenshot( cmd->x, cmd->y, cmd->width, cmd->height, cmd->fileName); + + return (const void *)(cmd + 1); +} + +/* +================== +R_TakeScreenshot +================== +*/ +void R_TakeScreenshot( int x, int y, int width, int height, char *name, qboolean jpeg ) { + static char fileName[MAX_OSPATH]; // bad things if two screenshots per frame? + screenshotCommand_t *cmd; + + cmd = R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SCREENSHOT; + + cmd->x = x; + cmd->y = y; + cmd->width = width; + cmd->height = height; + Q_strncpyz( fileName, name, sizeof(fileName) ); + cmd->fileName = fileName; + cmd->jpeg = jpeg; +} + +/* +================== +R_ScreenshotFilename +================== +*/ +void R_ScreenshotFilename( int lastNumber, char *fileName ) { + int a,b,c,d; + + if ( lastNumber < 0 || lastNumber > 9999 ) { + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999.tga" ); + return; + } + + a = lastNumber / 1000; + lastNumber -= a*1000; + b = lastNumber / 100; + lastNumber -= b*100; + c = lastNumber / 10; + lastNumber -= c*10; + d = lastNumber; + + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i.tga" + , a, b, c, d ); +} + +/* +================== +R_ScreenshotFilename +================== +*/ +void R_ScreenshotFilenameJPEG( int lastNumber, char *fileName ) { + int a,b,c,d; + + if ( lastNumber < 0 || lastNumber > 9999 ) { + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999.jpg" ); + return; + } + + a = lastNumber / 1000; + lastNumber -= a*1000; + b = lastNumber / 100; + lastNumber -= b*100; + c = lastNumber / 10; + lastNumber -= c*10; + d = lastNumber; + + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i.jpg" + , a, b, c, d ); +} + +/* +==================== +R_LevelShot + +levelshots are specialized 128*128 thumbnails for +the menu system, sampled down from full screen distorted images +==================== +*/ +void R_LevelShot( void ) { + char checkname[MAX_OSPATH]; + byte *buffer; + byte *source, *allsource; + byte *src, *dst; + size_t offset = 0; + int padlen; + int x, y; + int r, g, b; + float xScale, yScale; + int xx, yy; + + Com_sprintf(checkname, sizeof(checkname), "levelshots/%s.tga", tr.world->baseName); + + allsource = RB_ReadPixels(0, 0, glConfig.vidWidth, glConfig.vidHeight, &offset, &padlen); + source = allsource + offset; + + buffer = ri.Hunk_AllocateTempMemory(128 * 128*3 + 18); + Com_Memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = 128; + buffer[14] = 128; + buffer[16] = 24; // pixel size + + // resample from source + xScale = glConfig.vidWidth / 512.0f; + yScale = glConfig.vidHeight / 384.0f; + for ( y = 0 ; y < 128 ; y++ ) { + for ( x = 0 ; x < 128 ; x++ ) { + r = g = b = 0; + for ( yy = 0 ; yy < 3 ; yy++ ) { + for ( xx = 0 ; xx < 4 ; xx++ ) { + src = source + (3 * glConfig.vidWidth + padlen) * (int)((y*3 + yy) * yScale) + + 3 * (int) ((x*4 + xx) * xScale); + r += src[0]; + g += src[1]; + b += src[2]; + } + } + dst = buffer + 18 + 3 * ( y * 128 + x ); + dst[0] = b / 12; + dst[1] = g / 12; + dst[2] = r / 12; + } + } + + // gamma correct + if ( glConfig.deviceSupportsGamma ) { + R_GammaCorrect( buffer + 18, 128 * 128 * 3 ); + } + + ri.FS_WriteFile( checkname, buffer, 128 * 128*3 + 18 ); + + ri.Hunk_FreeTempMemory(buffer); + ri.Hunk_FreeTempMemory(allsource); + + ri.Printf( PRINT_ALL, "Wrote %s\n", checkname ); +} + +/* +================== +R_ScreenShot_f + +screenshot +screenshot [silent] +screenshot [levelshot] +screenshot [filename] + +Doesn't print the pacifier message if there is a second arg +================== +*/ +void R_ScreenShot_f (void) { + char checkname[MAX_OSPATH]; + static int lastNumber = -1; + qboolean silent; + + if ( !strcmp( ri.Cmd_Argv(1), "levelshot" ) ) { + R_LevelShot(); + return; + } + + if ( !strcmp( ri.Cmd_Argv(1), "silent" ) ) { + silent = qtrue; + } else { + silent = qfalse; + } + + if ( ri.Cmd_Argc() == 2 && !silent ) { + // explicit filename + Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.tga", ri.Cmd_Argv( 1 ) ); + } else { + // scan for a free filename + + // if we have saved a previous screenshot, don't scan + // again, because recording demo avis can involve + // thousands of shots + if ( lastNumber == -1 ) { + lastNumber = 0; + } + // scan for a free number + for ( ; lastNumber <= 9999 ; lastNumber++ ) { + R_ScreenshotFilename( lastNumber, checkname ); + + if (!ri.FS_FileExists( checkname )) + { + break; // file doesn't exist + } + } + + if ( lastNumber >= 9999 ) { + ri.Printf (PRINT_ALL, "ScreenShot: Couldn't create a file\n"); + return; + } + + lastNumber++; + } + + R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname, qfalse ); + + if ( !silent ) { + ri.Printf (PRINT_ALL, "Wrote %s\n", checkname); + } +} + +void R_ScreenShotJPEG_f (void) { + char checkname[MAX_OSPATH]; + static int lastNumber = -1; + qboolean silent; + + if ( !strcmp( ri.Cmd_Argv(1), "levelshot" ) ) { + R_LevelShot(); + return; + } + + if ( !strcmp( ri.Cmd_Argv(1), "silent" ) ) { + silent = qtrue; + } else { + silent = qfalse; + } + + if ( ri.Cmd_Argc() == 2 && !silent ) { + // explicit filename + Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.jpg", ri.Cmd_Argv( 1 ) ); + } else { + // scan for a free filename + + // if we have saved a previous screenshot, don't scan + // again, because recording demo avis can involve + // thousands of shots + if ( lastNumber == -1 ) { + lastNumber = 0; + } + // scan for a free number + for ( ; lastNumber <= 9999 ; lastNumber++ ) { + R_ScreenshotFilenameJPEG( lastNumber, checkname ); + + if (!ri.FS_FileExists( checkname )) + { + break; // file doesn't exist + } + } + + if ( lastNumber == 10000 ) { + ri.Printf (PRINT_ALL, "ScreenShot: Couldn't create a file\n"); + return; + } + + lastNumber++; + } + + R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname, qtrue ); + + if ( !silent ) { + ri.Printf (PRINT_ALL, "Wrote %s\n", checkname); + } +} + +//============================================================================ + +/* +================== +RB_TakeVideoFrameCmd +================== +*/ +const void *RB_TakeVideoFrameCmd( const void *data ) +{ + const videoFrameCommand_t *cmd; + byte *cBuf; + size_t memcount, linelen; + int padwidth, avipadwidth, padlen, avipadlen; + GLint packAlign; + + cmd = (const videoFrameCommand_t *)data; + + qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign); + + linelen = cmd->width * 3; + + // Alignment stuff for glReadPixels + padwidth = PAD(linelen, packAlign); + padlen = padwidth - linelen; + // AVI line padding + avipadwidth = PAD(linelen, AVI_LINE_PADDING); + avipadlen = avipadwidth - linelen; + + cBuf = PADP(cmd->captureBuffer, packAlign); + + qglReadPixels(0, 0, cmd->width, cmd->height, GL_RGB, + GL_UNSIGNED_BYTE, cBuf); + + memcount = padwidth * cmd->height; + + // gamma correct + if(glConfig.deviceSupportsGamma) + R_GammaCorrect(cBuf, memcount); + + if(cmd->motionJpeg) + { + memcount = RE_SaveJPGToBuffer(cmd->encodeBuffer, linelen * cmd->height, + r_aviMotionJpegQuality->integer, + cmd->width, cmd->height, cBuf, padlen); + ri.CL_WriteAVIVideoFrame(cmd->encodeBuffer, memcount); + } + else + { + byte *lineend, *memend; + byte *srcptr, *destptr; + + srcptr = cBuf; + destptr = cmd->encodeBuffer; + memend = srcptr + memcount; + + // swap R and B and remove line paddings + while(srcptr < memend) + { + lineend = srcptr + linelen; + while(srcptr < lineend) + { + *destptr++ = srcptr[2]; + *destptr++ = srcptr[1]; + *destptr++ = srcptr[0]; + srcptr += 3; + } + + Com_Memset(destptr, '\0', avipadlen); + destptr += avipadlen; + + srcptr += padlen; + } + + ri.CL_WriteAVIVideoFrame(cmd->encodeBuffer, avipadwidth * cmd->height); + } + + return (const void *)(cmd + 1); +} + +//============================================================================ + +/* +** GL_SetDefaultState +*/ +void GL_SetDefaultState( void ) +{ + qglClearDepth( 1.0f ); + + qglCullFace(GL_FRONT); + + qglColor4f (1,1,1,1); + + // initialize downstream texture unit if we're running + // in a multitexture environment + if ( qglActiveTextureARB ) { + GL_SelectTexture( 1 ); + GL_TextureMode( r_textureMode->string ); + GL_TexEnv( GL_MODULATE ); + qglDisable( GL_TEXTURE_2D ); + GL_SelectTexture( 0 ); + } + + qglEnable(GL_TEXTURE_2D); + GL_TextureMode( r_textureMode->string ); + GL_TexEnv( GL_MODULATE ); + + //qglShadeModel( GL_SMOOTH ); + qglDepthFunc( GL_LEQUAL ); + + // + // make sure our GL state vector is set correctly + // + glState.glStateBits = GLS_DEPTHTEST_DISABLE | GLS_DEPTHMASK_TRUE; + + glState.vertexAttribsState = 0; + glState.vertexAttribPointersSet = 0; + glState.currentProgram = 0; + qglUseProgramObjectARB(0); + + qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); + glState.currentVBO = NULL; + glState.currentIBO = NULL; + + if (glRefConfig.framebuffer_srgb) + { + qglEnable(GL_FRAMEBUFFER_SRGB_EXT); + } + + qglPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + qglDepthMask( GL_TRUE ); + qglDisable( GL_DEPTH_TEST ); + qglEnable( GL_SCISSOR_TEST ); + qglDisable( GL_CULL_FACE ); + qglDisable( GL_BLEND ); +} + + +/* +================ +GfxInfo_f +================ +*/ +void GfxInfo_f( void ) +{ + const char *enablestrings[] = + { + "disabled", + "enabled" + }; + const char *fsstrings[] = + { + "windowed", + "fullscreen" + }; + + ri.Printf( PRINT_ALL, "\nGL_VENDOR: %s\n", glConfig.vendor_string ); + ri.Printf( PRINT_ALL, "GL_RENDERER: %s\n", glConfig.renderer_string ); + ri.Printf( PRINT_ALL, "GL_VERSION: %s\n", glConfig.version_string ); + // this was really bugging me + if (strlen(glConfig.extensions_string) > 1008) + { + char buffer[1024]; + char *p; + int size = strlen(glConfig.extensions_string); + ri.Printf( PRINT_ALL, "GL_EXTENSIONS: "); + + p = glConfig.extensions_string; + while(size > 0) + { + Q_strncpyz(buffer, p, 1024); + ri.Printf( PRINT_ALL, "%s", buffer ); + p += 1023; + size -= 1023; + } + ri.Printf( PRINT_ALL, "\n" ); + } + else + { + ri.Printf( PRINT_ALL, "GL_EXTENSIONS: %s\n", glConfig.extensions_string ); + } + ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_SIZE: %d\n", glConfig.maxTextureSize ); + ri.Printf( PRINT_ALL, "GL_MAX_TEXTURE_UNITS_ARB: %d\n", glConfig.numTextureUnits ); + ri.Printf( PRINT_ALL, "\nPIXELFORMAT: color(%d-bits) Z(%d-bit) stencil(%d-bits)\n", glConfig.colorBits, glConfig.depthBits, glConfig.stencilBits ); + ri.Printf( PRINT_ALL, "MODE: %d, %d x %d %s hz:", r_mode->integer, glConfig.vidWidth, glConfig.vidHeight, fsstrings[r_fullscreen->integer == 1] ); + if ( glConfig.displayFrequency ) + { + ri.Printf( PRINT_ALL, "%d\n", glConfig.displayFrequency ); + } + else + { + ri.Printf( PRINT_ALL, "N/A\n" ); + } + if ( glConfig.deviceSupportsGamma ) + { + ri.Printf( PRINT_ALL, "GAMMA: hardware w/ %d overbright bits\n", tr.overbrightBits ); + } + else + { + ri.Printf( PRINT_ALL, "GAMMA: software w/ %d overbright bits\n", tr.overbrightBits ); + } + + ri.Printf( PRINT_ALL, "texturemode: %s\n", r_textureMode->string ); + ri.Printf( PRINT_ALL, "picmip: %d\n", r_picmip->integer ); + ri.Printf( PRINT_ALL, "texture bits: %d\n", r_texturebits->integer ); + ri.Printf( PRINT_ALL, "multitexture: %s\n", enablestrings[qglActiveTextureARB != 0] ); + ri.Printf( PRINT_ALL, "compiled vertex arrays: %s\n", enablestrings[qglLockArraysEXT != 0 ] ); + ri.Printf( PRINT_ALL, "texenv add: %s\n", enablestrings[glConfig.textureEnvAddAvailable != 0] ); + ri.Printf( PRINT_ALL, "compressed textures: %s\n", enablestrings[glConfig.textureCompression!=TC_NONE] ); + if ( r_vertexLight->integer || glConfig.hardwareType == GLHW_PERMEDIA2 ) + { + ri.Printf( PRINT_ALL, "HACK: using vertex lightmap approximation\n" ); + } + if ( glConfig.hardwareType == GLHW_RAGEPRO ) + { + ri.Printf( PRINT_ALL, "HACK: ragePro approximations\n" ); + } + if ( glConfig.hardwareType == GLHW_RIVA128 ) + { + ri.Printf( PRINT_ALL, "HACK: riva128 approximations\n" ); + } + if ( glConfig.smpActive ) { + ri.Printf( PRINT_ALL, "Using dual processor acceleration\n" ); + } + if ( r_finish->integer ) { + ri.Printf( PRINT_ALL, "Forcing glFinish\n" ); + } +} + +/* +================ +GfxMemInfo_f +================ +*/ +void GfxMemInfo_f( void ) +{ + switch (glRefConfig.memInfo) + { + case MI_NONE: + { + ri.Printf(PRINT_ALL, "No extension found for GPU memory info.\n"); + } + break; + case MI_NVX: + { + int value; + + qglGetIntegerv(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &value); + ri.Printf(PRINT_ALL, "GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX: %ikb\n", value); + + qglGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &value); + ri.Printf(PRINT_ALL, "GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX: %ikb\n", value); + + qglGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &value); + ri.Printf(PRINT_ALL, "GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX: %ikb\n", value); + + qglGetIntegerv(GL_GPU_MEMORY_INFO_EVICTION_COUNT_NVX, &value); + ri.Printf(PRINT_ALL, "GPU_MEMORY_INFO_EVICTION_COUNT_NVX: %i\n", value); + + qglGetIntegerv(GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX, &value); + ri.Printf(PRINT_ALL, "GPU_MEMORY_INFO_EVICTED_MEMORY_NVX: %ikb\n", value); + } + break; + case MI_ATI: + { + // GL_ATI_meminfo + int value[4]; + + qglGetIntegerv(GL_VBO_FREE_MEMORY_ATI, &value[0]); + ri.Printf(PRINT_ALL, "VBO_FREE_MEMORY_ATI: %ikb total %ikb largest aux: %ikb total %ikb largest\n", value[0], value[1], value[2], value[3]); + + qglGetIntegerv(GL_TEXTURE_FREE_MEMORY_ATI, &value[0]); + ri.Printf(PRINT_ALL, "TEXTURE_FREE_MEMORY_ATI: %ikb total %ikb largest aux: %ikb total %ikb largest\n", value[0], value[1], value[2], value[3]); + + qglGetIntegerv(GL_RENDERBUFFER_FREE_MEMORY_ATI, &value[0]); + ri.Printf(PRINT_ALL, "RENDERBUFFER_FREE_MEMORY_ATI: %ikb total %ikb largest aux: %ikb total %ikb largest\n", value[0], value[1], value[2], value[3]); + } + break; + } +} + +/* +=============== +R_Register +=============== +*/ +void R_Register( void ) +{ + #ifdef USE_RENDERER_DLOPEN + com_altivec = ri.Cvar_Get("com_altivec", "1", CVAR_ARCHIVE); + #endif + + // + // latched and archived variables + // + r_allowExtensions = ri.Cvar_Get( "r_allowExtensions", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compressed_textures = ri.Cvar_Get( "r_ext_compressed_textures", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_multitexture = ri.Cvar_Get( "r_ext_multitexture", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compiled_vertex_array = ri.Cvar_Get( "r_ext_compiled_vertex_array", "1", CVAR_ARCHIVE | CVAR_LATCH); + r_ext_texture_env_add = ri.Cvar_Get( "r_ext_texture_env_add", "1", CVAR_ARCHIVE | CVAR_LATCH); + + r_ext_draw_range_elements = ri.Cvar_Get( "r_ext_draw_range_elements", "1", CVAR_ARCHIVE | CVAR_LATCH); + r_ext_multi_draw_arrays = ri.Cvar_Get( "r_ext_multi_draw_arrays", "1", CVAR_ARCHIVE | CVAR_LATCH); + r_ext_framebuffer_object = ri.Cvar_Get( "r_ext_framebuffer_object", "1", CVAR_ARCHIVE | CVAR_LATCH); + r_ext_texture_float = ri.Cvar_Get( "r_ext_texture_float", "1", CVAR_ARCHIVE | CVAR_LATCH); + r_arb_half_float_pixel = ri.Cvar_Get( "r_arb_half_float_pixel", "1", CVAR_ARCHIVE | CVAR_LATCH); + r_ext_framebuffer_multisample = ri.Cvar_Get( "r_ext_framebuffer_multisample", "0", CVAR_ARCHIVE | CVAR_LATCH); + + r_ext_texture_filter_anisotropic = ri.Cvar_Get( "r_ext_texture_filter_anisotropic", + "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_max_anisotropy = ri.Cvar_Get( "r_ext_max_anisotropy", "2", CVAR_ARCHIVE | CVAR_LATCH ); + + r_picmip = ri.Cvar_Get ("r_picmip", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_roundImagesDown = ri.Cvar_Get ("r_roundImagesDown", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_colorMipLevels = ri.Cvar_Get ("r_colorMipLevels", "0", CVAR_LATCH ); + ri.Cvar_CheckRange( r_picmip, 0, 16, qtrue ); + r_detailTextures = ri.Cvar_Get( "r_detailtextures", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_texturebits = ri.Cvar_Get( "r_texturebits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_colorbits = ri.Cvar_Get( "r_colorbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_stencilbits = ri.Cvar_Get( "r_stencilbits", "8", CVAR_ARCHIVE | CVAR_LATCH ); + r_depthbits = ri.Cvar_Get( "r_depthbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_multisample = ri.Cvar_Get( "r_ext_multisample", "0", CVAR_ARCHIVE | CVAR_LATCH ); + ri.Cvar_CheckRange( r_ext_multisample, 0, 4, qtrue ); + r_overBrightBits = ri.Cvar_Get ("r_overBrightBits", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ignorehwgamma = ri.Cvar_Get( "r_ignorehwgamma", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_mode = ri.Cvar_Get( "r_mode", "-2", CVAR_ARCHIVE | CVAR_LATCH ); + r_fullscreen = ri.Cvar_Get( "r_fullscreen", "1", CVAR_ARCHIVE ); + r_noborder = ri.Cvar_Get("r_noborder", "0", CVAR_ARCHIVE); + r_customwidth = ri.Cvar_Get( "r_customwidth", "1600", CVAR_ARCHIVE | CVAR_LATCH ); + r_customheight = ri.Cvar_Get( "r_customheight", "1024", CVAR_ARCHIVE | CVAR_LATCH ); + r_customPixelAspect = ri.Cvar_Get( "r_customPixelAspect", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_simpleMipMaps = ri.Cvar_Get( "r_simpleMipMaps", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_vertexLight = ri.Cvar_Get( "r_vertexLight", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_uiFullScreen = ri.Cvar_Get( "r_uifullscreen", "0", 0); + r_subdivisions = ri.Cvar_Get ("r_subdivisions", "4", CVAR_ARCHIVE | CVAR_LATCH); + r_smp = ri.Cvar_Get( "r_smp", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_stereoEnabled = ri.Cvar_Get( "r_stereoEnabled", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_greyscale = ri.Cvar_Get("r_greyscale", "0", CVAR_ARCHIVE | CVAR_LATCH); + ri.Cvar_CheckRange(r_greyscale, 0, 1, qfalse); + + r_hdr = ri.Cvar_Get( "r_hdr", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_postProcess = ri.Cvar_Get( "r_postProcess", "1", CVAR_ARCHIVE ); + + r_toneMap = ri.Cvar_Get( "r_toneMap", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_forceToneMap = ri.Cvar_Get( "r_forceToneMap", "0", CVAR_CHEAT ); + r_forceToneMapMin = ri.Cvar_Get( "r_forceToneMapMin", "-3.25", CVAR_CHEAT ); + r_forceToneMapAvg = ri.Cvar_Get( "r_forceToneMapAvg", "-1.0", CVAR_CHEAT ); + r_forceToneMapMax = ri.Cvar_Get( "r_forceToneMapMax", "1.0", CVAR_CHEAT ); + + r_autoExposure = ri.Cvar_Get( "r_autoExposure", "1", CVAR_ARCHIVE ); + r_forceAutoExposure = ri.Cvar_Get( "r_forceAutoExposure", "0", CVAR_CHEAT ); + r_forceAutoExposureMin = ri.Cvar_Get( "r_forceAutoExposureMin", "-2.0", CVAR_CHEAT ); + r_forceAutoExposureMax = ri.Cvar_Get( "r_forceAutoExposureMax", "2.0", CVAR_CHEAT ); + + r_cameraExposure = ri.Cvar_Get( "r_cameraExposure", "0", CVAR_CHEAT ); + + r_srgb = ri.Cvar_Get( "r_srgb", "0", CVAR_ARCHIVE | CVAR_LATCH ); + + r_depthPrepass = ri.Cvar_Get( "r_depthPrepass", "1", CVAR_ARCHIVE ); + r_ssao = ri.Cvar_Get( "r_ssao", "0", CVAR_LATCH | CVAR_ARCHIVE ); + + r_normalMapping = ri.Cvar_Get( "r_normalMapping", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_specularMapping = ri.Cvar_Get( "r_specularMapping", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_deluxeMapping = ri.Cvar_Get( "r_deluxeMapping", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_parallaxMapping = ri.Cvar_Get( "r_parallaxMapping", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_normalAmbient = ri.Cvar_Get( "r_normalAmbient", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_dlightMode = ri.Cvar_Get( "r_dlightMode", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_pshadowDist = ri.Cvar_Get( "r_pshadowDist", "128", CVAR_ARCHIVE ); + r_recalcMD3Normals = ri.Cvar_Get( "r_recalcMD3Normals", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_mergeLightmaps = ri.Cvar_Get( "r_mergeLightmaps", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_imageUpsample = ri.Cvar_Get( "r_imageUpsample", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_imageUpsampleMaxSize = ri.Cvar_Get( "r_imageUpsampleMaxSize", "1024", CVAR_ARCHIVE | CVAR_LATCH ); + r_imageUpsampleType = ri.Cvar_Get( "r_imageUpsampleType", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_genNormalMaps = ri.Cvar_Get( "r_genNormalMaps", "0", CVAR_ARCHIVE | CVAR_LATCH ); + + r_forceSun = ri.Cvar_Get( "r_forceSun", "0", CVAR_CHEAT ); + r_forceSunMapLightScale = ri.Cvar_Get( "r_forceSunMapLightScale", "0.5", CVAR_CHEAT ); + r_forceSunLightScale = ri.Cvar_Get( "r_forceSunLightScale", "0.5", CVAR_CHEAT ); + r_forceSunAmbientScale = ri.Cvar_Get( "r_forceSunAmbientScale", "0.2", CVAR_CHEAT ); + r_sunShadows = ri.Cvar_Get( "r_sunShadows", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_shadowFilter = ri.Cvar_Get( "r_shadowFilter", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_shadowMapSize = ri.Cvar_Get( "r_shadowMapSize", "1024", CVAR_ARCHIVE | CVAR_LATCH ); + r_shadowCascadeZNear = ri.Cvar_Get( "r_shadowCascadeZNear", "4", CVAR_ARCHIVE | CVAR_LATCH ); + r_shadowCascadeZFar = ri.Cvar_Get( "r_shadowCascadeZFar", "3072", CVAR_ARCHIVE | CVAR_LATCH ); + r_shadowCascadeZBias = ri.Cvar_Get( "r_shadowCascadeZBias", "-320", CVAR_ARCHIVE | CVAR_LATCH ); + + // + // temporary latched variables that can only change over a restart + // + r_fullbright = ri.Cvar_Get ("r_fullbright", "0", CVAR_LATCH|CVAR_CHEAT ); + r_mapOverBrightBits = ri.Cvar_Get ("r_mapOverBrightBits", "2", CVAR_LATCH ); + r_intensity = ri.Cvar_Get ("r_intensity", "1", CVAR_LATCH ); + r_singleShader = ri.Cvar_Get ("r_singleShader", "0", CVAR_CHEAT | CVAR_LATCH ); + + // + // archived variables that can change at any time + // + r_lodCurveError = ri.Cvar_Get( "r_lodCurveError", "250", CVAR_ARCHIVE|CVAR_CHEAT ); + r_lodbias = ri.Cvar_Get( "r_lodbias", "0", CVAR_ARCHIVE ); + r_flares = ri.Cvar_Get ("r_flares", "0", CVAR_ARCHIVE ); + r_znear = ri.Cvar_Get( "r_znear", "4", CVAR_CHEAT ); + ri.Cvar_CheckRange( r_znear, 0.001f, 200, qfalse ); + r_zproj = ri.Cvar_Get( "r_zproj", "64", CVAR_ARCHIVE ); + r_stereoSeparation = ri.Cvar_Get( "r_stereoSeparation", "64", CVAR_ARCHIVE ); + r_ignoreGLErrors = ri.Cvar_Get( "r_ignoreGLErrors", "1", CVAR_ARCHIVE ); + r_fastsky = ri.Cvar_Get( "r_fastsky", "0", CVAR_ARCHIVE ); + r_inGameVideo = ri.Cvar_Get( "r_inGameVideo", "1", CVAR_ARCHIVE ); + r_drawSun = ri.Cvar_Get( "r_drawSun", "0", CVAR_ARCHIVE ); + r_dynamiclight = ri.Cvar_Get( "r_dynamiclight", "1", CVAR_ARCHIVE ); + r_dlightBacks = ri.Cvar_Get( "r_dlightBacks", "1", CVAR_ARCHIVE ); + r_finish = ri.Cvar_Get ("r_finish", "0", CVAR_ARCHIVE); + r_textureMode = ri.Cvar_Get( "r_textureMode", "GL_LINEAR_MIPMAP_NEAREST", CVAR_ARCHIVE ); + r_swapInterval = ri.Cvar_Get( "r_swapInterval", "0", + CVAR_ARCHIVE | CVAR_LATCH ); + r_gamma = ri.Cvar_Get( "r_gamma", "1", CVAR_ARCHIVE ); + r_facePlaneCull = ri.Cvar_Get ("r_facePlaneCull", "1", CVAR_ARCHIVE ); + + r_railWidth = ri.Cvar_Get( "r_railWidth", "16", CVAR_ARCHIVE ); + r_railCoreWidth = ri.Cvar_Get( "r_railCoreWidth", "6", CVAR_ARCHIVE ); + r_railSegmentLength = ri.Cvar_Get( "r_railSegmentLength", "32", CVAR_ARCHIVE ); + + r_ambientScale = ri.Cvar_Get( "r_ambientScale", "0.6", CVAR_CHEAT ); + r_directedScale = ri.Cvar_Get( "r_directedScale", "1", CVAR_CHEAT ); + + r_anaglyphMode = ri.Cvar_Get("r_anaglyphMode", "0", CVAR_ARCHIVE); + r_mergeMultidraws = ri.Cvar_Get("r_mergeMultidraws", "1", CVAR_ARCHIVE); + r_mergeLeafSurfaces = ri.Cvar_Get("r_mergeLeafSurfaces", "1", CVAR_ARCHIVE); + + // + // temporary variables that can change at any time + // + r_showImages = ri.Cvar_Get( "r_showImages", "0", CVAR_TEMP ); + + r_debugLight = ri.Cvar_Get( "r_debuglight", "0", CVAR_TEMP ); + r_debugSort = ri.Cvar_Get( "r_debugSort", "0", CVAR_CHEAT ); + r_printShaders = ri.Cvar_Get( "r_printShaders", "0", 0 ); + r_saveFontData = ri.Cvar_Get( "r_saveFontData", "0", 0 ); + + r_nocurves = ri.Cvar_Get ("r_nocurves", "0", CVAR_CHEAT ); + r_drawworld = ri.Cvar_Get ("r_drawworld", "1", CVAR_CHEAT ); + r_lightmap = ri.Cvar_Get ("r_lightmap", "0", 0 ); + r_portalOnly = ri.Cvar_Get ("r_portalOnly", "0", CVAR_CHEAT ); + + r_flareSize = ri.Cvar_Get ("r_flareSize", "40", CVAR_CHEAT); + r_flareFade = ri.Cvar_Get ("r_flareFade", "7", CVAR_CHEAT); + r_flareCoeff = ri.Cvar_Get ("r_flareCoeff", FLARE_STDCOEFF, CVAR_CHEAT); + + r_showSmp = ri.Cvar_Get ("r_showSmp", "0", CVAR_CHEAT); + r_skipBackEnd = ri.Cvar_Get ("r_skipBackEnd", "0", CVAR_CHEAT); + + r_measureOverdraw = ri.Cvar_Get( "r_measureOverdraw", "0", CVAR_CHEAT ); + r_lodscale = ri.Cvar_Get( "r_lodscale", "5", CVAR_CHEAT ); + r_norefresh = ri.Cvar_Get ("r_norefresh", "0", CVAR_CHEAT); + r_drawentities = ri.Cvar_Get ("r_drawentities", "1", CVAR_CHEAT ); + r_ignore = ri.Cvar_Get( "r_ignore", "1", CVAR_CHEAT ); + r_nocull = ri.Cvar_Get ("r_nocull", "0", CVAR_CHEAT); + r_novis = ri.Cvar_Get ("r_novis", "0", CVAR_CHEAT); + r_showcluster = ri.Cvar_Get ("r_showcluster", "0", CVAR_CHEAT); + r_speeds = ri.Cvar_Get ("r_speeds", "0", CVAR_CHEAT); + r_verbose = ri.Cvar_Get( "r_verbose", "0", CVAR_CHEAT ); + r_logFile = ri.Cvar_Get( "r_logFile", "0", CVAR_CHEAT ); + r_debugSurface = ri.Cvar_Get ("r_debugSurface", "0", CVAR_CHEAT); + r_nobind = ri.Cvar_Get ("r_nobind", "0", CVAR_CHEAT); + r_showtris = ri.Cvar_Get ("r_showtris", "0", CVAR_CHEAT); + r_showsky = ri.Cvar_Get ("r_showsky", "0", CVAR_CHEAT); + r_shownormals = ri.Cvar_Get ("r_shownormals", "0", CVAR_CHEAT); + r_clear = ri.Cvar_Get ("r_clear", "0", CVAR_CHEAT); + r_offsetFactor = ri.Cvar_Get( "r_offsetfactor", "-1", CVAR_CHEAT ); + r_offsetUnits = ri.Cvar_Get( "r_offsetunits", "-2", CVAR_CHEAT ); + r_drawBuffer = ri.Cvar_Get( "r_drawBuffer", "GL_BACK", CVAR_CHEAT ); + r_lockpvs = ri.Cvar_Get ("r_lockpvs", "0", CVAR_CHEAT); + r_noportals = ri.Cvar_Get ("r_noportals", "0", CVAR_CHEAT); + r_shadows = ri.Cvar_Get( "cg_shadows", "1", 0 ); + + r_marksOnTriangleMeshes = ri.Cvar_Get("r_marksOnTriangleMeshes", "0", CVAR_ARCHIVE); + + r_aviMotionJpegQuality = ri.Cvar_Get("r_aviMotionJpegQuality", "90", CVAR_ARCHIVE); + r_screenshotJpegQuality = ri.Cvar_Get("r_screenshotJpegQuality", "90", CVAR_ARCHIVE); + + r_maxpolys = ri.Cvar_Get( "r_maxpolys", va("%d", MAX_POLYS), 0); + r_maxpolyverts = ri.Cvar_Get( "r_maxpolyverts", va("%d", MAX_POLYVERTS), 0); + + // make sure all the commands added here are also + // removed in R_Shutdown + ri.Cmd_AddCommand( "imagelist", R_ImageList_f ); + ri.Cmd_AddCommand( "shaderlist", R_ShaderList_f ); + ri.Cmd_AddCommand( "skinlist", R_SkinList_f ); + ri.Cmd_AddCommand( "modellist", R_Modellist_f ); + ri.Cmd_AddCommand( "modelist", R_ModeList_f ); + ri.Cmd_AddCommand( "screenshot", R_ScreenShot_f ); + ri.Cmd_AddCommand( "screenshotJPEG", R_ScreenShotJPEG_f ); + ri.Cmd_AddCommand( "gfxinfo", GfxInfo_f ); + ri.Cmd_AddCommand( "minimize", GLimp_Minimize ); + ri.Cmd_AddCommand( "gfxmeminfo", GfxMemInfo_f ); +} + +void R_InitQueries(void) +{ + if (!glRefConfig.occlusionQuery) + return; + +#ifdef REACTION + qglGenQueriesARB(ARRAY_SIZE(tr.sunFlareQuery), tr.sunFlareQuery); +#endif +} + +void R_ShutDownQueries(void) +{ + if (!glRefConfig.occlusionQuery) + return; + +#ifdef REACTION + qglDeleteQueriesARB(ARRAY_SIZE(tr.sunFlareQuery), tr.sunFlareQuery); +#endif +} + +/* +=============== +R_Init +=============== +*/ +void R_Init( void ) { + int err; + int i; + byte *ptr; + + ri.Printf( PRINT_ALL, "----- R_Init -----\n" ); + + // clear all our internal state + Com_Memset( &tr, 0, sizeof( tr ) ); + Com_Memset( &backEnd, 0, sizeof( backEnd ) ); + Com_Memset( &tess, 0, sizeof( tess ) ); + + if(sizeof(glconfig_t) != 11332) + ri.Error( ERR_FATAL, "Mod ABI incompatible: sizeof(glconfig_t) == %u != 11332", (unsigned int) sizeof(glconfig_t)); + +// Swap_Init(); + + if ( (intptr_t)tess.xyz & 15 ) { + ri.Printf( PRINT_WARNING, "tess.xyz not 16 byte aligned\n" ); + } + //Com_Memset( tess.constantColor255, 255, sizeof( tess.constantColor255 ) ); + + // + // init function tables + // + for ( i = 0; i < FUNCTABLE_SIZE; i++ ) + { + tr.sinTable[i] = sin( DEG2RAD( i * 360.0f / ( ( float ) ( FUNCTABLE_SIZE - 1 ) ) ) ); + tr.squareTable[i] = ( i < FUNCTABLE_SIZE/2 ) ? 1.0f : -1.0f; + tr.sawToothTable[i] = (float)i / FUNCTABLE_SIZE; + tr.inverseSawToothTable[i] = 1.0f - tr.sawToothTable[i]; + + if ( i < FUNCTABLE_SIZE / 2 ) + { + if ( i < FUNCTABLE_SIZE / 4 ) + { + tr.triangleTable[i] = ( float ) i / ( FUNCTABLE_SIZE / 4 ); + } + else + { + tr.triangleTable[i] = 1.0f - tr.triangleTable[i-FUNCTABLE_SIZE / 4]; + } + } + else + { + tr.triangleTable[i] = -tr.triangleTable[i-FUNCTABLE_SIZE/2]; + } + } + + R_InitFogTable(); + + R_NoiseInit(); + + R_Register(); + + max_polys = r_maxpolys->integer; + if (max_polys < MAX_POLYS) + max_polys = MAX_POLYS; + + max_polyverts = r_maxpolyverts->integer; + if (max_polyverts < MAX_POLYVERTS) + max_polyverts = MAX_POLYVERTS; + + ptr = ri.Hunk_Alloc( sizeof( *backEndData[0] ) + sizeof(srfPoly_t) * max_polys + sizeof(polyVert_t) * max_polyverts, h_low); + backEndData[0] = (backEndData_t *) ptr; + backEndData[0]->polys = (srfPoly_t *) ((char *) ptr + sizeof( *backEndData[0] )); + backEndData[0]->polyVerts = (polyVert_t *) ((char *) ptr + sizeof( *backEndData[0] ) + sizeof(srfPoly_t) * max_polys); + if ( r_smp->integer ) { + ptr = ri.Hunk_Alloc( sizeof( *backEndData[1] ) + sizeof(srfPoly_t) * max_polys + sizeof(polyVert_t) * max_polyverts, h_low); + backEndData[1] = (backEndData_t *) ptr; + backEndData[1]->polys = (srfPoly_t *) ((char *) ptr + sizeof( *backEndData[1] )); + backEndData[1]->polyVerts = (polyVert_t *) ((char *) ptr + sizeof( *backEndData[1] ) + sizeof(srfPoly_t) * max_polys); + } else { + backEndData[1] = NULL; + } + R_ToggleSmpFrame(); + + InitOpenGL(); + + R_InitImages(); + + FBO_Init(); + + GLSL_InitGPUShaders(); + + R_InitVBOs(); + + R_InitShaders(); + + R_InitSkins(); + + R_ModelInit(); + + R_InitFreeType(); + + R_InitQueries(); + + + err = qglGetError(); + if ( err != GL_NO_ERROR ) + ri.Printf (PRINT_ALL, "glGetError() = 0x%x\n", err); + + // print info + GfxInfo_f(); + ri.Printf( PRINT_ALL, "----- finished R_Init -----\n" ); +} + +/* +=============== +RE_Shutdown +=============== +*/ +void RE_Shutdown( qboolean destroyWindow ) { + + ri.Printf( PRINT_ALL, "RE_Shutdown( %i )\n", destroyWindow ); + + ri.Cmd_RemoveCommand ("modellist"); + ri.Cmd_RemoveCommand ("screenshotJPEG"); + ri.Cmd_RemoveCommand ("screenshot"); + ri.Cmd_RemoveCommand ("imagelist"); + ri.Cmd_RemoveCommand ("shaderlist"); + ri.Cmd_RemoveCommand ("skinlist"); + ri.Cmd_RemoveCommand ("gfxinfo"); + ri.Cmd_RemoveCommand("minimize"); + ri.Cmd_RemoveCommand( "modelist" ); + ri.Cmd_RemoveCommand( "shaderstate" ); + + + if ( tr.registered ) { + R_SyncRenderThread(); + R_ShutdownCommandBuffers(); + R_ShutDownQueries(); + FBO_Shutdown(); + R_DeleteTextures(); + R_ShutdownVBOs(); + GLSL_ShutdownGPUShaders(); + } + + R_DoneFreeType(); + + // shut down platform specific OpenGL stuff + if ( destroyWindow ) { + GLimp_Shutdown(); + } + + tr.registered = qfalse; +} + + +/* +============= +RE_EndRegistration + +Touch all images to make sure they are resident +============= +*/ +void RE_EndRegistration( void ) { + R_SyncRenderThread(); + if (!ri.Sys_LowPhysicalMemory()) { + RB_ShowImages(); + } +} + + +/* +@@@@@@@@@@@@@@@@@@@@@ +GetRefAPI + +@@@@@@@@@@@@@@@@@@@@@ +*/ +#ifdef USE_RENDERER_DLOPEN +Q_EXPORT refexport_t QDECL *GetRefAPI ( int apiVersion, refimport_t *rimp ) { +#else +refexport_t *GetRefAPI ( int apiVersion, refimport_t *rimp ) { +#endif + + static refexport_t re; + + ri = *rimp; + + Com_Memset( &re, 0, sizeof( re ) ); + + if ( apiVersion != REF_API_VERSION ) { + ri.Printf(PRINT_ALL, "Mismatched REF_API_VERSION: expected %i, got %i\n", + REF_API_VERSION, apiVersion ); + return NULL; + } + + // the RE_ functions are Renderer Entry points + + re.Shutdown = RE_Shutdown; + + re.BeginRegistration = RE_BeginRegistration; + re.RegisterModel = RE_RegisterModel; + re.RegisterSkin = RE_RegisterSkin; + re.RegisterShader = RE_RegisterShader; + re.RegisterShaderNoMip = RE_RegisterShaderNoMip; + re.LoadWorld = RE_LoadWorldMap; + re.SetWorldVisData = RE_SetWorldVisData; + re.EndRegistration = RE_EndRegistration; + + re.BeginFrame = RE_BeginFrame; + re.EndFrame = RE_EndFrame; + + re.MarkFragments = R_MarkFragments; + re.LerpTag = R_LerpTag; + re.ModelBounds = R_ModelBounds; + + re.ClearScene = RE_ClearScene; + re.AddRefEntityToScene = RE_AddRefEntityToScene; + re.AddPolyToScene = RE_AddPolyToScene; + re.LightForPoint = R_LightForPoint; + re.AddLightToScene = RE_AddLightToScene; + re.AddAdditiveLightToScene = RE_AddAdditiveLightToScene; + re.RenderScene = RE_RenderScene; + + re.SetColor = RE_SetColor; + re.DrawStretchPic = RE_StretchPic; + re.DrawStretchRaw = RE_StretchRaw; + re.UploadCinematic = RE_UploadCinematic; + + re.RegisterFont = RE_RegisterFont; + re.RemapShader = R_RemapShader; + re.GetEntityToken = R_GetEntityToken; + re.inPVS = R_inPVS; + + re.TakeVideoFrame = RE_TakeVideoFrame; + + return &re; +} diff --git a/src/rend2/tr_light.c b/src/rend2/tr_light.c new file mode 100644 index 00000000..cec6b295 --- /dev/null +++ b/src/rend2/tr_light.c @@ -0,0 +1,455 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_light.c + +#include "tr_local.h" + +#define DLIGHT_AT_RADIUS 16 +// at the edge of a dlight's influence, this amount of light will be added + +#define DLIGHT_MINIMUM_RADIUS 16 +// never calculate a range less than this to prevent huge light numbers + + +/* +=============== +R_TransformDlights + +Transforms the origins of an array of dlights. +Used by both the front end (for DlightBmodel) and +the back end (before doing the lighting calculation) +=============== +*/ +void R_TransformDlights( int count, dlight_t *dl, orientationr_t *or) { + int i; + vec3_t temp; + + for ( i = 0 ; i < count ; i++, dl++ ) { + VectorSubtract( dl->origin, or->origin, temp ); + dl->transformed[0] = DotProduct( temp, or->axis[0] ); + dl->transformed[1] = DotProduct( temp, or->axis[1] ); + dl->transformed[2] = DotProduct( temp, or->axis[2] ); + } +} + +/* +============= +R_DlightBmodel + +Determine which dynamic lights may effect this bmodel +============= +*/ +void R_DlightBmodel( bmodel_t *bmodel ) { + int i, j; + dlight_t *dl; + int mask; + msurface_t *surf; + + // transform all the lights + R_TransformDlights( tr.refdef.num_dlights, tr.refdef.dlights, &tr.or ); + + mask = 0; + for ( i=0 ; itransformed[j] - bmodel->bounds[1][j] > dl->radius ) { + break; + } + if ( bmodel->bounds[0][j] - dl->transformed[j] > dl->radius ) { + break; + } + } + if ( j < 3 ) { + continue; + } + + // we need to check this light + mask |= 1 << i; + } + + tr.currentEntity->needDlights = (mask != 0); + + // set the dlight bits in all the surfaces + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + surf = tr.world->surfaces + bmodel->firstSurface + i; + + if ( *surf->data == SF_FACE ) { + ((srfSurfaceFace_t *)surf->data)->dlightBits[ tr.smpFrame ] = mask; + } else if ( *surf->data == SF_GRID ) { + ((srfGridMesh_t *)surf->data)->dlightBits[ tr.smpFrame ] = mask; + } else if ( *surf->data == SF_TRIANGLES ) { + ((srfTriangles_t *)surf->data)->dlightBits[ tr.smpFrame ] = mask; + } + } +} + + +/* +============================================================================= + +LIGHT SAMPLING + +============================================================================= +*/ + +extern cvar_t *r_ambientScale; +extern cvar_t *r_directedScale; +extern cvar_t *r_debugLight; + +/* +================= +R_SetupEntityLightingGrid + +================= +*/ +static void R_SetupEntityLightingGrid( trRefEntity_t *ent, world_t *world ) { + vec3_t lightOrigin; + int pos[3]; + int i, j; + byte *gridData; + float frac[3]; + int gridStep[3]; + vec3_t direction; + float totalFactor; + + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } + + VectorSubtract( lightOrigin, world->lightGridOrigin, lightOrigin ); + for ( i = 0 ; i < 3 ; i++ ) { + float v; + + v = lightOrigin[i]*world->lightGridInverseSize[i]; + pos[i] = floor( v ); + frac[i] = v - pos[i]; + if ( pos[i] < 0 ) { + pos[i] = 0; + } else if ( pos[i] >= world->lightGridBounds[i] - 1 ) { + pos[i] = world->lightGridBounds[i] - 1; + } + } + + VectorClear( ent->ambientLight ); + VectorClear( ent->directedLight ); + VectorClear( direction ); + + assert( world->lightGridData ); // NULL with -nolight maps + + // trilerp the light value + gridStep[0] = 8; + gridStep[1] = 8 * world->lightGridBounds[0]; + gridStep[2] = 8 * world->lightGridBounds[0] * world->lightGridBounds[1]; + gridData = world->lightGridData + pos[0] * gridStep[0] + + pos[1] * gridStep[1] + pos[2] * gridStep[2]; + + totalFactor = 0; + for ( i = 0 ; i < 8 ; i++ ) { + float factor; + byte *data; + int lat, lng; + vec3_t normal; + qboolean ignore; + #if idppc + float d0, d1, d2, d3, d4, d5; + #endif + factor = 1.0; + data = gridData; + ignore = qfalse; + for ( j = 0 ; j < 3 ; j++ ) { + if ( i & (1<= world->lightGridBounds[j] - 1) + { + ignore = qtrue; // ignore values outside lightgrid + } + factor *= frac[j]; + data += gridStep[j]; + } else { + factor *= (1.0f - frac[j]); + } + } + + if ( ignore ) + continue; + + if (world->hdrLightGrid) + { + float *hdrData = world->hdrLightGrid + (int)(data - world->lightGridData) / 8 * 6; + if (!(hdrData[0]+hdrData[1]+hdrData[2]+hdrData[3]+hdrData[4]+hdrData[5]) ) { + continue; // ignore samples in walls + } + } + else + { + if (!(data[0]+data[1]+data[2]+data[3]+data[4]+data[5]) ) { + continue; // ignore samples in walls + } + } + totalFactor += factor; + #if idppc + d0 = data[0]; d1 = data[1]; d2 = data[2]; + d3 = data[3]; d4 = data[4]; d5 = data[5]; + + ent->ambientLight[0] += factor * d0; + ent->ambientLight[1] += factor * d1; + ent->ambientLight[2] += factor * d2; + + ent->directedLight[0] += factor * d3; + ent->directedLight[1] += factor * d4; + ent->directedLight[2] += factor * d5; + #else + if (world->hdrLightGrid) + { + // FIXME: this is hideous + float *hdrData = world->hdrLightGrid + (int)(data - world->lightGridData) / 8 * 6; + + ent->ambientLight[0] += factor * hdrData[0]; + ent->ambientLight[1] += factor * hdrData[1]; + ent->ambientLight[2] += factor * hdrData[2]; + + ent->directedLight[0] += factor * hdrData[3]; + ent->directedLight[1] += factor * hdrData[4]; + ent->directedLight[2] += factor * hdrData[5]; + } + else + { + ent->ambientLight[0] += factor * data[0]; + ent->ambientLight[1] += factor * data[1]; + ent->ambientLight[2] += factor * data[2]; + + ent->directedLight[0] += factor * data[3]; + ent->directedLight[1] += factor * data[4]; + ent->directedLight[2] += factor * data[5]; + } + #endif + lat = data[7]; + lng = data[6]; + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + normal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + normal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + normal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + VectorMA( direction, factor, normal, direction ); + } + + if ( totalFactor > 0 && totalFactor < 0.99 ) { + totalFactor = 1.0f / totalFactor; + VectorScale( ent->ambientLight, totalFactor, ent->ambientLight ); + VectorScale( ent->directedLight, totalFactor, ent->directedLight ); + } + + VectorScale( ent->ambientLight, r_ambientScale->value, ent->ambientLight ); + VectorScale( ent->directedLight, r_directedScale->value, ent->directedLight ); + + VectorNormalize2( direction, ent->lightDir ); +} + + +/* +=============== +LogLight +=============== +*/ +static void LogLight( trRefEntity_t *ent ) { + int max1, max2; + + if ( !(ent->e.renderfx & RF_FIRST_PERSON ) ) { + return; + } + + max1 = ent->ambientLight[0]; + if ( ent->ambientLight[1] > max1 ) { + max1 = ent->ambientLight[1]; + } else if ( ent->ambientLight[2] > max1 ) { + max1 = ent->ambientLight[2]; + } + + max2 = ent->directedLight[0]; + if ( ent->directedLight[1] > max2 ) { + max2 = ent->directedLight[1]; + } else if ( ent->directedLight[2] > max2 ) { + max2 = ent->directedLight[2]; + } + + ri.Printf( PRINT_ALL, "amb:%i dir:%i\n", max1, max2 ); +} + +/* +================= +R_SetupEntityLighting + +Calculates all the lighting values that will be used +by the Calc_* functions +================= +*/ +void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ) { + int i; + dlight_t *dl; + float power; + vec3_t dir; + float d; + vec3_t lightDir; + vec3_t lightOrigin; + + // lighting calculations + if ( ent->lightingCalculated ) { + return; + } + ent->lightingCalculated = qtrue; + + // + // trace a sample point down to find ambient light + // + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } + + // if NOWORLDMODEL, only use dynamic lights (menu system, etc) + if ( !(refdef->rdflags & RDF_NOWORLDMODEL ) + && tr.world->lightGridData ) { + R_SetupEntityLightingGrid( ent, tr.world ); + } else { + ent->ambientLight[0] = ent->ambientLight[1] = + ent->ambientLight[2] = tr.identityLight * 150; + ent->directedLight[0] = ent->directedLight[1] = + ent->directedLight[2] = tr.identityLight * 150; + VectorCopy( tr.sunDirection, ent->lightDir ); + } + + // bonus items and view weapons have a fixed minimum add + if ( !r_hdr->integer /* ent->e.renderfx & RF_MINLIGHT */ ) { + // give everything a minimum light add + ent->ambientLight[0] += tr.identityLight * 32; + ent->ambientLight[1] += tr.identityLight * 32; + ent->ambientLight[2] += tr.identityLight * 32; + } + + // + // modify the light by dynamic lights + // + d = VectorLength( ent->directedLight ); + VectorScale( ent->lightDir, d, lightDir ); + + for ( i = 0 ; i < refdef->num_dlights ; i++ ) { + dl = &refdef->dlights[i]; + VectorSubtract( dl->origin, lightOrigin, dir ); + d = VectorNormalize( dir ); + + power = DLIGHT_AT_RADIUS * ( dl->radius * dl->radius ); + if ( d < DLIGHT_MINIMUM_RADIUS ) { + d = DLIGHT_MINIMUM_RADIUS; + } + d = power / ( d * d ); + + VectorMA( ent->directedLight, d, dl->color, ent->directedLight ); + VectorMA( lightDir, d, dir, lightDir ); + } + + // clamp ambient + if ( !r_hdr->integer ) + { + for ( i = 0 ; i < 3 ; i++ ) { + if ( ent->ambientLight[i] > tr.identityLightByte ) { + ent->ambientLight[i] = tr.identityLightByte; + } + } + } + + if ( r_debugLight->integer ) { + LogLight( ent ); + } + + // save out the byte packet version + ((byte *)&ent->ambientLightInt)[0] = ri.ftol(ent->ambientLight[0]); + ((byte *)&ent->ambientLightInt)[1] = ri.ftol(ent->ambientLight[1]); + ((byte *)&ent->ambientLightInt)[2] = ri.ftol(ent->ambientLight[2]); + ((byte *)&ent->ambientLightInt)[3] = 0xff; + + // transform the direction to local space + // no need to do this if using lightentity glsl shader + VectorNormalize( lightDir ); + VectorCopy(lightDir, ent->lightDir); +} + +/* +================= +R_LightForPoint +================= +*/ +int R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) +{ + trRefEntity_t ent; + + if ( tr.world->lightGridData == NULL ) + return qfalse; + + Com_Memset(&ent, 0, sizeof(ent)); + VectorCopy( point, ent.e.origin ); + R_SetupEntityLightingGrid( &ent, tr.world ); + VectorCopy(ent.ambientLight, ambientLight); + VectorCopy(ent.directedLight, directedLight); + VectorCopy(ent.lightDir, lightDir); + + return qtrue; +} + + +int R_LightDirForPoint( vec3_t point, vec3_t lightDir, vec3_t normal, world_t *world ) +{ + trRefEntity_t ent; + + if ( world->lightGridData == NULL ) + return qfalse; + + Com_Memset(&ent, 0, sizeof(ent)); + VectorCopy( point, ent.e.origin ); + R_SetupEntityLightingGrid( &ent, world ); + + if ((DotProduct(ent.lightDir, ent.lightDir) < 0.9f) || (DotProduct(ent.lightDir, normal) < 0.1f)) + { + VectorCopy(normal, lightDir); + } + else + { + VectorCopy(ent.lightDir, lightDir); + } + + return qtrue; +} \ No newline at end of file diff --git a/src/rend2/tr_local.h b/src/rend2/tr_local.h new file mode 100644 index 00000000..c86091d7 --- /dev/null +++ b/src/rend2/tr_local.h @@ -0,0 +1,2856 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +#ifndef TR_LOCAL_H +#define TR_LOCAL_H + +#include "../qcommon/q_shared.h" +#include "../qcommon/qfiles.h" +#include "../qcommon/qcommon.h" +#include "../renderer/tr_public.h" +#include "tr_extratypes.h" +#include "tr_extramath.h" +#include "tr_fbo.h" +#include "tr_postprocess.h" +#include "qgl.h" +#include "../renderer/iqm.h" + +#define GL_INDEX_TYPE GL_UNSIGNED_INT +typedef unsigned int glIndex_t; + +#define BUFFER_OFFSET(i) ((char *)NULL + (i)) + +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) +#endif + +// everything that is needed by the backend needs +// to be double buffered to allow it to run in +// parallel on a dual cpu machine +#define SMP_FRAMES 2 + +// 14 bits +// can't be increased without changing bit packing for drawsurfs +// see QSORT_SHADERNUM_SHIFT +#define SHADERNUM_BITS 14 +#define MAX_SHADERS (1<or.origin in local coordinates + float modelMatrix[16]; + float transformMatrix[16]; +} orientationr_t; + +typedef enum +{ + IMGTYPE_COLORALPHA, // for color, lightmap, diffuse, and specular + IMGTYPE_NORMAL, + IMGTYPE_NORMALHEIGHT, + IMGTYPE_DELUXE, // normals are swizzled, deluxe are not +} imgType_t; + +typedef enum +{ + IMGFLAG_NONE = 0x0000, + IMGFLAG_MIPMAP = 0x0001, + IMGFLAG_PICMIP = 0x0002, + IMGFLAG_CUBEMAP = 0x0004, + IMGFLAG_NO_COMPRESSION = 0x0010, + IMGFLAG_NOLIGHTSCALE = 0x0020, + IMGFLAG_CLAMPTOEDGE = 0x0040, + IMGFLAG_SRGB = 0x0080, + IMGFLAG_GENNORMALMAP = 0x0100, +} imgFlags_t; + +typedef struct image_s { + char imgName[MAX_QPATH]; // game path, including extension + int width, height; // source image + int uploadWidth, uploadHeight; // after power of two and picmip but not including clamp to MAX_TEXTURE_SIZE + GLuint texnum; // gl texture binding + + int frameUsed; // for texture usage in frame statistics + + int internalFormat; + int TMU; // only needed for voodoo2 + + imgType_t type; + imgFlags_t flags; + + struct image_s* next; +} image_t; + +typedef enum +{ + VBO_USAGE_STATIC, + VBO_USAGE_DYNAMIC +} vboUsage_t; + +typedef struct VBO_s +{ + char name[MAX_QPATH]; + + uint32_t vertexesVBO; + int vertexesSize; // amount of memory data allocated for all vertices in bytes + uint32_t ofs_xyz; + uint32_t ofs_normal; + uint32_t ofs_st; + uint32_t ofs_lightmap; + uint32_t ofs_vertexcolor; + uint32_t ofs_lightdir; +#ifdef USE_VERT_TANGENT_SPACE + uint32_t ofs_tangent; + uint32_t ofs_bitangent; +#endif + uint32_t stride_xyz; + uint32_t stride_normal; + uint32_t stride_st; + uint32_t stride_lightmap; + uint32_t stride_vertexcolor; + uint32_t stride_lightdir; +#ifdef USE_VERT_TANGENT_SPACE + uint32_t stride_tangent; + uint32_t stride_bitangent; +#endif + uint32_t size_xyz; + uint32_t size_normal; + + int attribs; +} VBO_t; + +typedef struct IBO_s +{ + char name[MAX_QPATH]; + + uint32_t indexesVBO; + int indexesSize; // amount of memory data allocated for all triangles in bytes +// uint32_t ofsIndexes; +} IBO_t; + +//=============================================================================== + +typedef enum { + SS_BAD, + SS_PORTAL, // mirrors, portals, viewscreens + SS_ENVIRONMENT, // sky box + SS_OPAQUE, // opaque + + SS_DECAL, // scorch marks, etc. + SS_SEE_THROUGH, // ladders, grates, grills that may have small blended edges + // in addition to alpha test + SS_BANNER, + + SS_FOG, + + SS_UNDERWATER, // for items that should be drawn in front of the water plane + + SS_BLEND0, // regular transparency and filters + SS_BLEND1, // generally only used for additive type effects + SS_BLEND2, + SS_BLEND3, + + SS_BLEND6, + SS_STENCIL_SHADOW, + SS_ALMOST_NEAREST, // gun smoke puffs + + SS_NEAREST // blood blobs +} shaderSort_t; + + +#define MAX_SHADER_STAGES 8 + +typedef enum { + GF_NONE, + + GF_SIN, + GF_SQUARE, + GF_TRIANGLE, + GF_SAWTOOTH, + GF_INVERSE_SAWTOOTH, + + GF_NOISE + +} genFunc_t; + + +typedef enum { + DEFORM_NONE, + DEFORM_WAVE, + DEFORM_NORMALS, + DEFORM_BULGE, + DEFORM_MOVE, + DEFORM_PROJECTION_SHADOW, + DEFORM_AUTOSPRITE, + DEFORM_AUTOSPRITE2, + DEFORM_TEXT0, + DEFORM_TEXT1, + DEFORM_TEXT2, + DEFORM_TEXT3, + DEFORM_TEXT4, + DEFORM_TEXT5, + DEFORM_TEXT6, + DEFORM_TEXT7 +} deform_t; + +// deformVertexes types that can be handled by the GPU +typedef enum +{ + // do not edit: same as genFunc_t + + DGEN_NONE, + DGEN_WAVE_SIN, + DGEN_WAVE_SQUARE, + DGEN_WAVE_TRIANGLE, + DGEN_WAVE_SAWTOOTH, + DGEN_WAVE_INVERSE_SAWTOOTH, + DGEN_WAVE_NOISE, + + // do not edit until this line + + DGEN_BULGE, + DGEN_MOVE +} deformGen_t; + +typedef enum { + AGEN_IDENTITY, + AGEN_SKIP, + AGEN_ENTITY, + AGEN_ONE_MINUS_ENTITY, + AGEN_VERTEX, + AGEN_ONE_MINUS_VERTEX, + AGEN_LIGHTING_SPECULAR, + AGEN_WAVEFORM, + AGEN_PORTAL, + AGEN_CONST, + AGEN_FRESNEL +} alphaGen_t; + +typedef enum { + CGEN_BAD, + CGEN_IDENTITY_LIGHTING, // tr.identityLight + CGEN_IDENTITY, // always (1,1,1,1) + CGEN_ENTITY, // grabbed from entity's modulate field + CGEN_ONE_MINUS_ENTITY, // grabbed from 1 - entity.modulate + CGEN_EXACT_VERTEX, // tess.vertexColors + CGEN_VERTEX, // tess.vertexColors * tr.identityLight + CGEN_EXACT_VERTEX_LIT, // like CGEN_EXACT_VERTEX but takes a light direction from the lightgrid + CGEN_VERTEX_LIT, // like CGEN_VERTEX but takes a light direction from the lightgrid + CGEN_ONE_MINUS_VERTEX, + CGEN_WAVEFORM, // programmatically generated + CGEN_LIGHTING_DIFFUSE, + CGEN_FOG, // standard fog + CGEN_CONST // fixed color +} colorGen_t; + +typedef enum { + TCGEN_BAD, + TCGEN_IDENTITY, // clear to 0,0 + TCGEN_LIGHTMAP, + TCGEN_TEXTURE, + TCGEN_ENVIRONMENT_MAPPED, + TCGEN_FOG, + TCGEN_VECTOR // S and T from world coordinates +} texCoordGen_t; + +typedef enum { + ACFF_NONE, + ACFF_MODULATE_RGB, + ACFF_MODULATE_RGBA, + ACFF_MODULATE_ALPHA +} acff_t; + +typedef struct { + genFunc_t func; + + float base; + float amplitude; + float phase; + float frequency; +} waveForm_t; + +#define TR_MAX_TEXMODS 4 + +typedef enum { + TMOD_NONE, + TMOD_TRANSFORM, + TMOD_TURBULENT, + TMOD_SCROLL, + TMOD_SCALE, + TMOD_STRETCH, + TMOD_ROTATE, + TMOD_ENTITY_TRANSLATE +} texMod_t; + +#define MAX_SHADER_DEFORMS 3 +typedef struct { + deform_t deformation; // vertex coordinate modification type + + vec3_t moveVector; + waveForm_t deformationWave; + float deformationSpread; + + float bulgeWidth; + float bulgeHeight; + float bulgeSpeed; +} deformStage_t; + + +typedef struct { + texMod_t type; + + // used for TMOD_TURBULENT and TMOD_STRETCH + waveForm_t wave; + + // used for TMOD_TRANSFORM + float matrix[2][2]; // s' = s * m[0][0] + t * m[1][0] + trans[0] + float translate[2]; // t' = s * m[0][1] + t * m[0][1] + trans[1] + + // used for TMOD_SCALE + float scale[2]; // s *= scale[0] + // t *= scale[1] + + // used for TMOD_SCROLL + float scroll[2]; // s' = s + scroll[0] * time + // t' = t + scroll[1] * time + + // + = clockwise + // - = counterclockwise + float rotateSpeed; + +} texModInfo_t; + + +#define MAX_IMAGE_ANIMATIONS 8 + +typedef struct { + image_t *image[MAX_IMAGE_ANIMATIONS]; + int numImageAnimations; + float imageAnimationSpeed; + + texCoordGen_t tcGen; + vec3_t tcGenVectors[2]; + + int numTexMods; + texModInfo_t *texMods; + + int videoMapHandle; + qboolean isLightmap; + qboolean vertexLightmap; + qboolean isVideoMap; +} textureBundle_t; + +enum +{ + TB_COLORMAP = 0, + TB_DIFFUSEMAP = 0, + TB_LIGHTMAP = 1, + TB_LEVELSMAP = 1, + TB_SHADOWMAP = 1, + TB_NORMALMAP = 2, + TB_DELUXEMAP = 3, + TB_SHADOWMAP2 = 3, + TB_SPECULARMAP = 4, + TB_SHADOWMAP3 = 5, + NUM_TEXTURE_BUNDLES = 6 +}; + +typedef enum +{ + // material shader stage types + ST_COLORMAP = 0, // vanilla Q3A style shader treatening + ST_DIFFUSEMAP = 0, // treat color and diffusemap the same + ST_NORMALMAP, + ST_NORMALPARALLAXMAP, + ST_SPECULARMAP, + ST_GLSL +} stageType_t; + +typedef struct { + qboolean active; + + textureBundle_t bundle[NUM_TEXTURE_BUNDLES]; + + waveForm_t rgbWave; + colorGen_t rgbGen; + + waveForm_t alphaWave; + alphaGen_t alphaGen; + + byte constantColor[4]; // for CGEN_CONST and AGEN_CONST + + unsigned stateBits; // GLS_xxxx mask + + acff_t adjustColorsForFog; + + qboolean isDetail; + + stageType_t type; + struct shaderProgram_s *glslShaderGroup; + int glslShaderIndex; + vec2_t materialInfo; +} shaderStage_t; + +struct shaderCommands_s; + +// any change in the LIGHTMAP_* defines here MUST be reflected in +// R_FindShader() in tr_bsp.c +#define LIGHTMAP_2D -4 // shader is for 2D rendering +#define LIGHTMAP_BY_VERTEX -3 // pre-lit triangle models +#define LIGHTMAP_WHITEIMAGE -2 +#define LIGHTMAP_NONE -1 + +typedef enum { + CT_FRONT_SIDED, + CT_BACK_SIDED, + CT_TWO_SIDED +} cullType_t; + +typedef enum { + FP_NONE, // surface is translucent and will just be adjusted properly + FP_EQUAL, // surface is opaque but possibly alpha tested + FP_LE // surface is trnaslucent, but still needs a fog pass (fog surface) +} fogPass_t; + +typedef struct { + float cloudHeight; + image_t *outerbox[6], *innerbox[6]; +} skyParms_t; + +typedef struct { + vec3_t color; + float depthForOpaque; +} fogParms_t; + + +typedef struct shader_s { + char name[MAX_QPATH]; // game path, including extension + int lightmapIndex; // for a shader to match, both name and lightmapIndex must match + + int index; // this shader == tr.shaders[index] + int sortedIndex; // this shader == tr.sortedShaders[sortedIndex] + + float sort; // lower numbered shaders draw before higher numbered + + qboolean defaultShader; // we want to return index 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + + qboolean explicitlyDefined; // found in a .shader file + + int surfaceFlags; // if explicitlyDefined, this will have SURF_* flags + int contentFlags; + + qboolean entityMergable; // merge across entites optimizable (smoke, blood) + + qboolean isSky; + skyParms_t sky; + fogParms_t fogParms; + + float portalRange; // distance to fog out at + qboolean isPortal; + + int multitextureEnv; // 0, GL_MODULATE, GL_ADD (FIXME: put in stage) + + cullType_t cullType; // CT_FRONT_SIDED, CT_BACK_SIDED, or CT_TWO_SIDED + qboolean polygonOffset; // set for decals and other items that must be offset + qboolean noMipMaps; // for console fonts, 2D elements, etc. + qboolean noPicMip; // for images that must always be full resolution + + fogPass_t fogPass; // draw a blended pass, possibly with depth test equals + + int vertexAttribs; // not all shaders will need all data to be gathered + + int numDeforms; + deformStage_t deforms[MAX_SHADER_DEFORMS]; + + int numUnfoggedPasses; + shaderStage_t *stages[MAX_SHADER_STAGES]; + + void (*optimalStageIteratorFunc)( void ); + + float clampTime; // time this shader is clamped to + float timeOffset; // current time offset for this shader + + int numStates; // if non-zero this is a state shader + struct shader_s *currentShader; // current state if this is a state shader + struct shader_s *parentShader; // current state if this is a state shader + int currentState; // current state index for cycle purposes + long expireTime; // time in milliseconds this expires + + struct shader_s *remappedShader; // current shader this one is remapped too + + int shaderStates[MAX_STATES_PER_SHADER]; // index to valid shader states + + struct shader_s *next; +} shader_t; + +static ID_INLINE qboolean ShaderRequiresCPUDeforms(const shader_t * shader) +{ + if(shader->numDeforms) + { + const deformStage_t *ds = &shader->deforms[0]; + + if (shader->numDeforms > 1) + return qtrue; + + switch (ds->deformation) + { + case DEFORM_WAVE: + case DEFORM_BULGE: + return qfalse; + + default: + return qtrue; + } + } + + return qfalse; +} + +typedef struct shaderState_s { + char shaderName[MAX_QPATH]; // name of shader this state belongs to + char name[MAX_STATE_NAME]; // name of this state + char stateShader[MAX_QPATH]; // shader this name invokes + int cycleTime; // time this cycle lasts, <= 0 is forever + shader_t *shader; +} shaderState_t; + +enum +{ + ATTR_INDEX_POSITION = 0, + ATTR_INDEX_TEXCOORD0 = 1, + ATTR_INDEX_TEXCOORD1 = 2, + ATTR_INDEX_TANGENT = 3, + ATTR_INDEX_BITANGENT = 4, + ATTR_INDEX_NORMAL = 5, + ATTR_INDEX_COLOR = 6, + ATTR_INDEX_PAINTCOLOR = 7, + ATTR_INDEX_LIGHTDIRECTION = 8, + ATTR_INDEX_BONE_INDEXES = 9, + ATTR_INDEX_BONE_WEIGHTS = 10, + + // GPU vertex animations + ATTR_INDEX_POSITION2 = 11, + ATTR_INDEX_TANGENT2 = 12, + ATTR_INDEX_BITANGENT2 = 13, + ATTR_INDEX_NORMAL2 = 14 +}; + +enum +{ + GLS_SRCBLEND_ZERO = (1 << 0), + GLS_SRCBLEND_ONE = (1 << 1), + GLS_SRCBLEND_DST_COLOR = (1 << 2), + GLS_SRCBLEND_ONE_MINUS_DST_COLOR = (1 << 3), + GLS_SRCBLEND_SRC_ALPHA = (1 << 4), + GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA = (1 << 5), + GLS_SRCBLEND_DST_ALPHA = (1 << 6), + GLS_SRCBLEND_ONE_MINUS_DST_ALPHA = (1 << 7), + GLS_SRCBLEND_ALPHA_SATURATE = (1 << 8), + + GLS_SRCBLEND_BITS = GLS_SRCBLEND_ZERO + | GLS_SRCBLEND_ONE + | GLS_SRCBLEND_DST_COLOR + | GLS_SRCBLEND_ONE_MINUS_DST_COLOR + | GLS_SRCBLEND_SRC_ALPHA + | GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA + | GLS_SRCBLEND_DST_ALPHA + | GLS_SRCBLEND_ONE_MINUS_DST_ALPHA + | GLS_SRCBLEND_ALPHA_SATURATE, + + GLS_DSTBLEND_ZERO = (1 << 9), + GLS_DSTBLEND_ONE = (1 << 10), + GLS_DSTBLEND_SRC_COLOR = (1 << 11), + GLS_DSTBLEND_ONE_MINUS_SRC_COLOR = (1 << 12), + GLS_DSTBLEND_SRC_ALPHA = (1 << 13), + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA = (1 << 14), + GLS_DSTBLEND_DST_ALPHA = (1 << 15), + GLS_DSTBLEND_ONE_MINUS_DST_ALPHA = (1 << 16), + + GLS_DSTBLEND_BITS = GLS_DSTBLEND_ZERO + | GLS_DSTBLEND_ONE + | GLS_DSTBLEND_SRC_COLOR + | GLS_DSTBLEND_ONE_MINUS_SRC_COLOR + | GLS_DSTBLEND_SRC_ALPHA + | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA + | GLS_DSTBLEND_DST_ALPHA + | GLS_DSTBLEND_ONE_MINUS_DST_ALPHA, + + GLS_DEPTHMASK_TRUE = (1 << 17), + + GLS_POLYMODE_LINE = (1 << 18), + + GLS_DEPTHTEST_DISABLE = (1 << 19), + + GLS_DEPTHFUNC_LESS = (1 << 20), + GLS_DEPTHFUNC_EQUAL = (1 << 21), + + GLS_DEPTHFUNC_BITS = GLS_DEPTHFUNC_LESS + | GLS_DEPTHFUNC_EQUAL, + + GLS_ATEST_GT_0 = (1 << 22), + GLS_ATEST_LT_128 = (1 << 23), + GLS_ATEST_GE_128 = (1 << 24), +// GLS_ATEST_GE_CUSTOM = (1 << 25), + + GLS_ATEST_BITS = GLS_ATEST_GT_0 + | GLS_ATEST_LT_128 + | GLS_ATEST_GE_128, +// | GLS_ATEST_GT_CUSTOM, + + GLS_REDMASK_FALSE = (1 << 26), + GLS_GREENMASK_FALSE = (1 << 27), + GLS_BLUEMASK_FALSE = (1 << 28), + GLS_ALPHAMASK_FALSE = (1 << 29), + + GLS_COLORMASK_BITS = GLS_REDMASK_FALSE + | GLS_GREENMASK_FALSE + | GLS_BLUEMASK_FALSE + | GLS_ALPHAMASK_FALSE, + + GLS_STENCILTEST_ENABLE = (1 << 30), + + GLS_DEFAULT = GLS_DEPTHMASK_TRUE +}; + +enum +{ + ATTR_POSITION = 0x0001, + ATTR_TEXCOORD = 0x0002, + ATTR_LIGHTCOORD = 0x0004, + ATTR_TANGENT = 0x0008, + ATTR_BITANGENT = 0x0010, + ATTR_NORMAL = 0x0020, + ATTR_COLOR = 0x0040, + ATTR_PAINTCOLOR = 0x0080, + ATTR_LIGHTDIRECTION = 0x0100, + ATTR_BONE_INDEXES = 0x0200, + ATTR_BONE_WEIGHTS = 0x0400, + + // for .md3 interpolation + ATTR_POSITION2 = 0x0800, + ATTR_TANGENT2 = 0x1000, + ATTR_BITANGENT2 = 0x2000, + ATTR_NORMAL2 = 0x4000, + + ATTR_DEFAULT = ATTR_POSITION, + ATTR_BITS = ATTR_POSITION | + ATTR_TEXCOORD | + ATTR_LIGHTCOORD | + ATTR_TANGENT | + ATTR_BITANGENT | + ATTR_NORMAL | + ATTR_COLOR | + ATTR_PAINTCOLOR | + ATTR_LIGHTDIRECTION | + ATTR_BONE_INDEXES | + ATTR_BONE_WEIGHTS | + ATTR_POSITION2 | + ATTR_TANGENT2 | + ATTR_BITANGENT2 | + ATTR_NORMAL2 +}; + +enum +{ + GENERICDEF_USE_DEFORM_VERTEXES = 0x0001, + GENERICDEF_USE_TCGEN = 0x0002, + GENERICDEF_USE_VERTEX_ANIMATION = 0x0004, + GENERICDEF_USE_FOG = 0x0008, + GENERICDEF_USE_RGBAGEN = 0x0010, + GENERICDEF_USE_LIGHTMAP = 0x0020, + GENERICDEF_ALL = 0x003F, + GENERICDEF_COUNT = 0x0040, +}; + +enum +{ + LIGHTDEF_USE_LIGHTMAP = 0x0001, + LIGHTDEF_USE_LIGHT_VECTOR = 0x0002, + LIGHTDEF_USE_LIGHT_VERTEX = 0x0003, + LIGHTDEF_LIGHTTYPE_MASK = 0x0003, + LIGHTDEF_USE_NORMALMAP = 0x0004, + LIGHTDEF_USE_SPECULARMAP = 0x0008, + LIGHTDEF_USE_DELUXEMAP = 0x0010, + LIGHTDEF_USE_PARALLAXMAP = 0x0020, + LIGHTDEF_USE_SHADOWMAP = 0x0040, + LIGHTDEF_TCGEN_ENVIRONMENT = 0x0080, + LIGHTDEF_ENTITY = 0x0100, + LIGHTDEF_ALL = 0x01FF, + LIGHTDEF_COUNT = 0x0200 +}; + +enum +{ + GLSL_INT, + GLSL_FLOAT, + GLSL_FLOAT5, + GLSL_VEC2, + GLSL_VEC3, + GLSL_VEC4, + GLSL_MAT16 +}; + +// Tr3B - shaderProgram_t represents a pair of one +// GLSL vertex and one GLSL fragment shader +typedef struct shaderProgram_s +{ + char name[MAX_QPATH]; + + GLhandleARB program; + GLhandleARB vertexShader; + GLhandleARB fragmentShader; + uint32_t attribs; // vertex array attributes + + // uniform parameters + int numUniforms; + GLint *uniforms; + GLint *uniformTypes; + int *uniformBufferOffsets; + char *uniformBuffer; +} shaderProgram_t; + + +enum +{ + TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX = 0, + TEXTURECOLOR_UNIFORM_INVTEXRES, + TEXTURECOLOR_UNIFORM_AUTOEXPOSUREMINMAX, + TEXTURECOLOR_UNIFORM_TONEMINAVGMAXLINEAR, + TEXTURECOLOR_UNIFORM_TEXTUREMAP, + TEXTURECOLOR_UNIFORM_LEVELSMAP, + TEXTURECOLOR_UNIFORM_COLOR, + TEXTURECOLOR_UNIFORM_COUNT +}; + + +enum +{ + FOGPASS_UNIFORM_FOGDISTANCE = 0, + FOGPASS_UNIFORM_FOGDEPTH, + FOGPASS_UNIFORM_FOGEYET, + FOGPASS_UNIFORM_DEFORMGEN, + FOGPASS_UNIFORM_DEFORMPARAMS, + FOGPASS_UNIFORM_TIME, + FOGPASS_UNIFORM_COLOR, + FOGPASS_UNIFORM_MODELVIEWPROJECTIONMATRIX, + FOGPASS_UNIFORM_VERTEXLERP, + FOGPASS_UNIFORM_COUNT +}; + + +enum +{ + DLIGHT_UNIFORM_DIFFUSEMAP = 0, + DLIGHT_UNIFORM_DLIGHTINFO, + DLIGHT_UNIFORM_DEFORMGEN, + DLIGHT_UNIFORM_DEFORMPARAMS, + DLIGHT_UNIFORM_TIME, + DLIGHT_UNIFORM_COLOR, + DLIGHT_UNIFORM_MODELVIEWPROJECTIONMATRIX, + DLIGHT_UNIFORM_VERTEXLERP, + DLIGHT_UNIFORM_COUNT +}; + + +enum +{ + PSHADOW_UNIFORM_SHADOWMAP = 0, + PSHADOW_UNIFORM_MODELVIEWPROJECTIONMATRIX, + PSHADOW_UNIFORM_LIGHTFORWARD, + PSHADOW_UNIFORM_LIGHTUP, + PSHADOW_UNIFORM_LIGHTRIGHT, + PSHADOW_UNIFORM_LIGHTORIGIN, + PSHADOW_UNIFORM_LIGHTRADIUS, + PSHADOW_UNIFORM_COUNT +}; + + +enum +{ + GENERIC_UNIFORM_DIFFUSEMAP = 0, + GENERIC_UNIFORM_LIGHTMAP, + GENERIC_UNIFORM_NORMALMAP, + GENERIC_UNIFORM_DELUXEMAP, + GENERIC_UNIFORM_SPECULARMAP, + GENERIC_UNIFORM_SHADOWMAP, + GENERIC_UNIFORM_DIFFUSETEXMATRIX, + //GENERIC_UNIFORM_NORMALTEXMATRIX, + //GENERIC_UNIFORM_SPECULARTEXMATRIX, + GENERIC_UNIFORM_TEXTURE1ENV, + GENERIC_UNIFORM_VIEWORIGIN, + GENERIC_UNIFORM_TCGEN0, + GENERIC_UNIFORM_TCGEN0VECTOR0, + GENERIC_UNIFORM_TCGEN0VECTOR1, + GENERIC_UNIFORM_DEFORMGEN, + GENERIC_UNIFORM_DEFORMPARAMS, + GENERIC_UNIFORM_COLORGEN, + GENERIC_UNIFORM_ALPHAGEN, + GENERIC_UNIFORM_BASECOLOR, + GENERIC_UNIFORM_VERTCOLOR, + GENERIC_UNIFORM_AMBIENTLIGHT, + GENERIC_UNIFORM_DIRECTEDLIGHT, + GENERIC_UNIFORM_LIGHTORIGIN, + GENERIC_UNIFORM_LIGHTRADIUS, + GENERIC_UNIFORM_PORTALRANGE, + GENERIC_UNIFORM_FOGDISTANCE, + GENERIC_UNIFORM_FOGDEPTH, + GENERIC_UNIFORM_FOGEYET, + GENERIC_UNIFORM_FOGCOLORMASK, + GENERIC_UNIFORM_MODELMATRIX, + GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, + GENERIC_UNIFORM_TIME, + GENERIC_UNIFORM_VERTEXLERP, + GENERIC_UNIFORM_MATERIALINFO, + GENERIC_UNIFORM_COUNT +}; + +enum +{ + SHADOWMASK_UNIFORM_SCREENDEPTHMAP = 0, + SHADOWMASK_UNIFORM_SHADOWMAP, + SHADOWMASK_UNIFORM_SHADOWMAP2, + SHADOWMASK_UNIFORM_SHADOWMAP3, + SHADOWMASK_UNIFORM_SHADOWMVP, + SHADOWMASK_UNIFORM_SHADOWMVP2, + SHADOWMASK_UNIFORM_SHADOWMVP3, + SHADOWMASK_UNIFORM_VIEWORIGIN, + SHADOWMASK_UNIFORM_VIEWINFO, // znear, zfar, width/2, height/2 + SHADOWMASK_UNIFORM_VIEWFORWARD, + SHADOWMASK_UNIFORM_VIEWLEFT, + SHADOWMASK_UNIFORM_VIEWUP, + SHADOWMASK_UNIFORM_COUNT +}; + +enum +{ + SSAO_UNIFORM_SCREENDEPTHMAP = 0, + SSAO_UNIFORM_VIEWINFO, // znear, zfar, width/2, height/2 + SSAO_UNIFORM_COUNT +}; + +enum +{ + DEPTHBLUR_UNIFORM_SCREENIMAGEMAP = 0, + DEPTHBLUR_UNIFORM_SCREENDEPTHMAP, + DEPTHBLUR_UNIFORM_VIEWINFO, // znear, zfar, width/2, height/2 + DEPTHBLUR_UNIFORM_COUNT +}; + +// +// Tr3B: these are fire wall functions to avoid expensive redundant glUniform* calls +//#define USE_UNIFORM_FIREWALL 1 +//#define LOG_GLSL_UNIFORMS 1 + +// trRefdef_t holds everything that comes in refdef_t, +// as well as the locally generated scene information +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewaxis[3]; // transformation matrix + + stereoFrame_t stereoFrame; + + int time; // time in milliseconds for shader effects and other time dependent rendering issues + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + qboolean areamaskModified; // qtrue if areamask changed since last scene + + float floatTime; // tr.refdef.time / 1000.0 + + float blurFactor; + + // text messages for deform text shaders + char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; + + int num_entities; + trRefEntity_t *entities; + + int num_dlights; + struct dlight_s *dlights; + + int numPolys; + struct srfPoly_s *polys; + + int numDrawSurfs; + struct drawSurf_s *drawSurfs; + + unsigned int dlightMask; + int num_pshadows; + struct pshadow_s *pshadows; + + float sunShadowMvp[3][16]; + float sunDir[4]; + float sunCol[4]; + float sunAmbCol[4]; + float colorScale; + + float autoExposureMinMax[2]; + float toneMinAvgMaxLinear[3]; +} trRefdef_t; + + +//================================================================================= + +// skins allow models to be retextured without modifying the model file +typedef struct { + char name[MAX_QPATH]; + shader_t *shader; +} skinSurface_t; + +typedef struct skin_s { + char name[MAX_QPATH]; // game path, including extension + int numSurfaces; + skinSurface_t *surfaces[MD3_MAX_SURFACES]; +} skin_t; + + +typedef struct { + int originalBrushNumber; + vec3_t bounds[2]; + + unsigned colorInt; // in packed byte format + float tcScale; // texture coordinate vector scales + fogParms_t parms; + + // for clipping distance in fog when outside + qboolean hasSurface; + float surface[4]; +} fog_t; + +typedef enum { + VPF_NONE = 0x00, + VPF_SHADOWMAP = 0x01, + VPF_DEPTHSHADOW = 0x02, + VPF_DEPTHCLAMP = 0x04, + VPF_ORTHOGRAPHIC = 0x08, + VPF_USESUNLIGHT = 0x10, +} viewParmFlags_t; + +typedef struct { + orientationr_t or; + orientationr_t world; + vec3_t pvsOrigin; // may be different than or.origin for portals + qboolean isPortal; // true if this view is through a portal + qboolean isMirror; // the portal is a mirror, invert the face culling + viewParmFlags_t flags; + int frameSceneNum; // copied from tr.frameSceneNum + int frameCount; // copied from tr.frameCount + cplane_t portalPlane; // clip anything behind this if mirroring + int viewportX, viewportY, viewportWidth, viewportHeight; + FBO_t *targetFbo; + float fovX, fovY; + float projectionMatrix[16]; + cplane_t frustum[5]; + vec3_t visBounds[2]; + float zFar; + float zNear; + stereoFrame_t stereoFrame; +} viewParms_t; + + +/* +============================================================================== + +SURFACES + +============================================================================== +*/ +typedef byte color4ub_t[4]; + +// any changes in surfaceType must be mirrored in rb_surfaceTable[] +typedef enum { + SF_BAD, + SF_SKIP, // ignore + SF_FACE, + SF_GRID, + SF_TRIANGLES, + SF_POLY, + SF_MDV, + SF_MD4, +#ifdef RAVENMD4 + SF_MDR, +#endif + SF_IQM, + SF_FLARE, + SF_ENTITY, // beams, rails, lightning, etc that can be determined by entity + SF_DISPLAY_LIST, + SF_VBO_MESH, + SF_VBO_MDVMESH, + + SF_NUM_SURFACE_TYPES, + SF_MAX = 0x7fffffff // ensures that sizeof( surfaceType_t ) == sizeof( int ) +} surfaceType_t; + +typedef struct drawSurf_s { + unsigned sort; // bit combination for fast compares + surfaceType_t *surface; // any of surface*_t +} drawSurf_t; + +#define MAX_FACE_POINTS 64 + +#define MAX_PATCH_SIZE 32 // max dimensions of a patch mesh in map file +#define MAX_GRID_SIZE 65 // max dimensions of a grid mesh in memory + +// when cgame directly specifies a polygon, it becomes a srfPoly_t +// as soon as it is called +typedef struct srfPoly_s { + surfaceType_t surfaceType; + qhandle_t hShader; + int fogIndex; + int numVerts; + polyVert_t *verts; +} srfPoly_t; + +typedef struct srfDisplayList_s { + surfaceType_t surfaceType; + int listNum; +} srfDisplayList_t; + + +typedef struct srfFlare_s { + surfaceType_t surfaceType; + vec3_t origin; + vec3_t normal; + vec3_t color; +} srfFlare_t; + +typedef struct +{ + vec3_t xyz; + vec2_t st; + vec2_t lightmap; + vec3_t normal; +#ifdef USE_VERT_TANGENT_SPACE + vec3_t tangent; + vec3_t bitangent; +#endif + vec3_t lightdir; + vec4_t vertexColors; + +#if DEBUG_OPTIMIZEVERTICES + unsigned int id; +#endif +} srfVert_t; + +#ifdef USE_VERT_TANGENT_SPACE +#define srfVert_t_cleared(x) srfVert_t (x) = {{0, 0, 0}, {0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0, 0}} +#else +#define srfVert_t_cleared(x) srfVert_t (x) = {{0, 0, 0}, {0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0, 0}} +#endif + +typedef struct +{ + int indexes[3]; + int neighbors[3]; + vec4_t plane; + qboolean facingLight; + qboolean degenerated; +} srfTriangle_t; + + +typedef struct srfGridMesh_s +{ + surfaceType_t surfaceType; + + // dynamic lighting information + int dlightBits[SMP_FRAMES]; + int pshadowBits[SMP_FRAMES]; + + // culling information + vec3_t meshBounds[2]; + vec3_t localOrigin; + float meshRadius; + + // lod information, which may be different + // than the culling information to allow for + // groups of curves that LOD as a unit + vec3_t lodOrigin; + float lodRadius; + int lodFixed; + int lodStitched; + + // vertexes + int width, height; + float *widthLodError; + float *heightLodError; + + int numTriangles; + srfTriangle_t *triangles; + + int numVerts; + srfVert_t *verts; + + // BSP VBO offsets + int firstVert; + int firstIndex; + + // static render data + VBO_t *vbo; // points to bsp model VBO + IBO_t *ibo; +} srfGridMesh_t; + + +typedef struct +{ + surfaceType_t surfaceType; + + // dynamic lighting information + int dlightBits[SMP_FRAMES]; + int pshadowBits[SMP_FRAMES]; + + // culling information + cplane_t plane; +// vec3_t bounds[2]; + + // triangle definitions + int numTriangles; + srfTriangle_t *triangles; + + int numVerts; + srfVert_t *verts; + + // BSP VBO offsets + int firstVert; + int firstIndex; + + // static render data + VBO_t *vbo; // points to bsp model VBO + IBO_t *ibo; +} srfSurfaceFace_t; + + +// misc_models in maps are turned into direct geometry by xmap +typedef struct +{ + surfaceType_t surfaceType; + + // dynamic lighting information + int dlightBits[SMP_FRAMES]; + int pshadowBits[SMP_FRAMES]; + + // culling information +// vec3_t bounds[2]; + + // triangle definitions + int numTriangles; + srfTriangle_t *triangles; + + int numVerts; + srfVert_t *verts; + + // BSP VBO offsets + int firstVert; + int firstIndex; + + // static render data + VBO_t *vbo; // points to bsp model VBO + IBO_t *ibo; +} srfTriangles_t; + +// inter-quake-model +typedef struct { + int num_vertexes; + int num_triangles; + int num_frames; + int num_surfaces; + int num_joints; + struct srfIQModel_s *surfaces; + + float *positions; + float *texcoords; + float *normals; + float *tangents; + byte *blendIndexes; + byte *blendWeights; + byte *colors; + int *triangles; + + int *jointParents; + float *poseMats; + float *bounds; + char *names; +} iqmData_t; + +// inter-quake-model surface +typedef struct srfIQModel_s { + surfaceType_t surfaceType; + char name[MAX_QPATH]; + shader_t *shader; + iqmData_t *data; + int first_vertex, num_vertexes; + int first_triangle, num_triangles; +} srfIQModel_t; + +typedef struct srfVBOMesh_s +{ + surfaceType_t surfaceType; + + struct shader_s *shader; // FIXME move this to somewhere else + int fogIndex; + + // dynamic lighting information + int dlightBits[SMP_FRAMES]; + int pshadowBits[SMP_FRAMES]; + + // culling information + vec3_t bounds[2]; + + // backEnd stats + int numIndexes; + int numVerts; + int firstIndex; + + // static render data + VBO_t *vbo; + IBO_t *ibo; +} srfVBOMesh_t; + +typedef struct srfVBOMDVMesh_s +{ + surfaceType_t surfaceType; + + struct mdvModel_s *mdvModel; + struct mdvSurface_s *mdvSurface; + + // backEnd stats + int numIndexes; + int numVerts; + + // static render data + VBO_t *vbo; + IBO_t *ibo; +} srfVBOMDVMesh_t; + +extern void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])(void *); + +/* +============================================================================== + +SHADOWS + +============================================================================== +*/ + +typedef struct pshadow_s +{ + float sort; + + int numEntities; + int entityNums[8]; + vec3_t entityOrigins[8]; + float entityRadiuses[8]; + + float viewRadius; + vec3_t viewOrigin; + + vec3_t lightViewAxis[3]; + vec3_t lightOrigin; + float lightRadius; + cplane_t cullPlane; +} pshadow_t; + + +/* +============================================================================== + +BRUSH MODELS + +============================================================================== +*/ + + +// +// in memory representation +// + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 + +#define CULLINFO_NONE 0 +#define CULLINFO_BOX 1 +#define CULLINFO_SPHERE 2 +#define CULLINFO_PLANE 4 + +typedef struct cullinfo_s { + int type; + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + cplane_t plane; +} cullinfo_t; + +typedef struct msurface_s { + //int viewCount; // if == tr.viewCount, already added + struct shader_s *shader; + int fogIndex; + cullinfo_t cullinfo; + + surfaceType_t *data; // any of srf*_t +} msurface_t; + + +#define CONTENTS_NODE -1 +typedef struct mnode_s { + // common with leaf and node + int contents; // -1 for nodes, to differentiate from leafs + int visCounts[MAX_VISCOUNTS]; // node needs to be traversed if current + vec3_t mins, maxs; // for bounding box culling + struct mnode_s *parent; + + // node specific + cplane_t *plane; + struct mnode_s *children[2]; + + // leaf specific + int cluster; + int area; + + int firstmarksurface; + int nummarksurfaces; +} mnode_t; + +typedef struct { + vec3_t bounds[2]; // for culling + int firstSurface; + int numSurfaces; +} bmodel_t; + +typedef struct { + char name[MAX_QPATH]; // ie: maps/tim_dm2.bsp + char baseName[MAX_QPATH]; // ie: tim_dm2 + + int dataSize; + + int numShaders; + dshader_t *shaders; + + int numBModels; + bmodel_t *bmodels; + + int numplanes; + cplane_t *planes; + + int numnodes; // includes leafs + int numDecisionNodes; + mnode_t *nodes; + + VBO_t *vbo; + IBO_t *ibo; + + int numWorldSurfaces; + + int numsurfaces; + msurface_t *surfaces; + int *surfacesViewCount; + int *surfacesDlightBits; + int *surfacesPshadowBits; + + int numMergedSurfaces; + msurface_t *mergedSurfaces; + int *mergedSurfacesViewCount; + int *mergedSurfacesDlightBits; + int *mergedSurfacesPshadowBits; + + int nummarksurfaces; + int *marksurfaces; + int *viewSurfaces; + + int numfogs; + fog_t *fogs; + + vec3_t lightGridOrigin; + vec3_t lightGridSize; + vec3_t lightGridInverseSize; + int lightGridBounds[3]; + byte *lightGridData; + float *hdrLightGrid; + + + int numClusters; + int clusterBytes; + const byte *vis; // may be passed in by CM_LoadMap to save space + + byte *novis; // clusterBytes of 0xff + + char *entityString; + char *entityParsePoint; +} world_t; + + +/* +============================================================================== +MDV MODELS - meta format for vertex animation models like .md2, .md3, .mdc +============================================================================== +*/ +typedef struct +{ + float bounds[2][3]; + float localOrigin[3]; + float radius; +} mdvFrame_t; + +typedef struct +{ + float origin[3]; + float axis[3][3]; +} mdvTag_t; + +typedef struct +{ + char name[MAX_QPATH]; // tag name +} mdvTagName_t; + +typedef struct +{ + vec3_t xyz; + vec3_t normal; +#ifdef USE_VERT_TANGENT_SPACE + vec3_t tangent; + vec3_t bitangent; +#endif +} mdvVertex_t; + +typedef struct +{ + float st[2]; +} mdvSt_t; + +typedef struct mdvSurface_s +{ + surfaceType_t surfaceType; + + char name[MAX_QPATH]; // polyset name + + int numShaderIndexes; + int *shaderIndexes; + + int numVerts; + mdvVertex_t *verts; + mdvSt_t *st; + + int numTriangles; + srfTriangle_t *triangles; + + struct mdvModel_s *model; +} mdvSurface_t; + +typedef struct mdvModel_s +{ + int numFrames; + mdvFrame_t *frames; + + int numTags; + mdvTag_t *tags; + mdvTagName_t *tagNames; + + int numSurfaces; + mdvSurface_t *surfaces; + + int numVBOSurfaces; + srfVBOMDVMesh_t *vboSurfaces; + + int numSkins; +} mdvModel_t; + + +//====================================================================== + +typedef enum { + MOD_BAD, + MOD_BRUSH, + MOD_MESH, + MOD_MD4, +#ifdef RAVENMD4 + MOD_MDR, +#endif + MOD_IQM +} modtype_t; + +typedef struct model_s { + char name[MAX_QPATH]; + modtype_t type; + int index; // model = tr.models[model->index] + + int dataSize; // just for listing purposes + bmodel_t *bmodel; // only if type == MOD_BRUSH + mdvModel_t *mdv[MD3_MAX_LODS]; // only if type == MOD_MESH + void *modelData; // only if type == (MOD_MD4 | MOD_MDR | MOD_IQM) + + int numLods; +} model_t; + + +#define MAX_MOD_KNOWN 1024 + +void R_ModelInit (void); +model_t *R_GetModelByHandle( qhandle_t hModel ); +int R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, + float frac, const char *tagName ); +void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ); + +void R_Modellist_f (void); + +//==================================================== +extern refimport_t ri; + +#define MAX_DRAWIMAGES 2048 +#define MAX_SKINS 1024 + + +#define MAX_DRAWSURFS 0x10000 +#define DRAWSURF_MASK (MAX_DRAWSURFS-1) + +/* + +the drawsurf sort data is packed into a single 32 bit value so it can be +compared quickly during the qsorting process + +the bits are allocated as follows: + +0 - 1 : dlightmap index +//2 : used to be clipped flag REMOVED - 03.21.00 rad +2 - 6 : fog index +11 - 20 : entity index +21 - 31 : sorted shader index + + TTimo - 1.32 +0-1 : dlightmap index +2-6 : fog index +7-16 : entity index +17-30 : sorted shader index + + SmileTheory - for pshadows +17-31 : sorted shader index +7-16 : entity index +2-6 : fog index +1 : pshadow flag +0 : dlight flag +*/ +#define QSORT_FOGNUM_SHIFT 2 +#define QSORT_REFENTITYNUM_SHIFT 7 +#define QSORT_SHADERNUM_SHIFT (QSORT_REFENTITYNUM_SHIFT+REFENTITYNUM_BITS) +#if (QSORT_SHADERNUM_SHIFT+SHADERNUM_BITS) > 32 + #error "Need to update sorting, too many bits." +#endif +#define QSORT_PSHADOW_SHIFT 1 + +extern int gl_filter_min, gl_filter_max; + +/* +** performanceCounters_t +*/ +typedef struct { + int c_sphere_cull_patch_in, c_sphere_cull_patch_clip, c_sphere_cull_patch_out; + int c_box_cull_patch_in, c_box_cull_patch_clip, c_box_cull_patch_out; + int c_sphere_cull_md3_in, c_sphere_cull_md3_clip, c_sphere_cull_md3_out; + int c_box_cull_md3_in, c_box_cull_md3_clip, c_box_cull_md3_out; + + int c_leafs; + int c_dlightSurfaces; + int c_dlightSurfacesCulled; +} frontEndCounters_t; + +#define FOG_TABLE_SIZE 256 +#define FUNCTABLE_SIZE 1024 +#define FUNCTABLE_SIZE2 10 +#define FUNCTABLE_MASK (FUNCTABLE_SIZE-1) + + +// the renderer front end should never modify glstate_t +typedef struct { + int currenttextures[NUM_TEXTURE_BUNDLES]; + int currenttmu; + qboolean finishCalled; + int texEnv[2]; + int faceCulling; + unsigned long glStateBits; + uint32_t vertexAttribsState; + uint32_t vertexAttribPointersSet; + uint32_t vertexAttribsNewFrame; + uint32_t vertexAttribsOldFrame; + float vertexAttribsInterpolation; + shaderProgram_t *currentProgram; + FBO_t *currentFBO; + VBO_t *currentVBO; + IBO_t *currentIBO; + matrix_t modelview; + matrix_t projection; + matrix_t modelviewProjection; +} glstate_t; + +typedef enum { + MI_NONE, + MI_NVX, + MI_ATI +} memInfo_t; + +typedef enum { + TCR_NONE = 0x0000, + TCR_LATC = 0x0001, + TCR_BPTC = 0x0002, +} textureCompressionRef_t; + +// We can't change glConfig_t without breaking DLL/vms compatibility, so +// store extensions we have here. +typedef struct { + qboolean drawRangeElements; + qboolean multiDrawArrays; + qboolean occlusionQuery; + + int glslMajorVersion; + int glslMinorVersion; + + memInfo_t memInfo; + + qboolean framebufferObject; + int maxRenderbufferSize; + int maxColorAttachments; + + qboolean textureNonPowerOfTwo; + qboolean textureFloat; + qboolean halfFloatPixel; + qboolean packedDepthStencil; + textureCompressionRef_t textureCompression; + + qboolean framebufferMultisample; + qboolean framebufferBlit; + + qboolean texture_srgb; + qboolean framebuffer_srgb; + + qboolean depthClamp; +} glRefConfig_t; + + +typedef struct { + int c_surfaces, c_shaders, c_vertexes, c_indexes, c_totalIndexes; + int c_surfBatches; + float c_overDraw; + + int c_vboVertexBuffers; + int c_vboIndexBuffers; + int c_vboVertexes; + int c_vboIndexes; + + int c_staticVboDraws; + int c_dynamicVboDraws; + + int c_multidraws; + int c_multidrawsMerged; + + int c_dlightVertexes; + int c_dlightIndexes; + + int c_flareAdds; + int c_flareTests; + int c_flareRenders; + + int c_glslShaderBinds; + int c_genericDraws; + int c_lightallDraws; + int c_fogDraws; + int c_dlightDraws; + + int msec; // total msec for backend run +} backEndCounters_t; + +// all state modified by the back end is seperated +// from the front end state +typedef struct { + int smpFrame; + trRefdef_t refdef; + viewParms_t viewParms; + orientationr_t or; + backEndCounters_t pc; + qboolean isHyperspace; + trRefEntity_t *currentEntity; + qboolean skyRenderedThisView; // flag for drawing sun + +#ifdef REACTION + vec3_t sunFlarePos; + qboolean viewHasSunFlare; + qboolean frameHasSunFlare; +#endif + + qboolean projection2D; // if qtrue, drawstretchpic doesn't need to change modes + byte color2D[4]; + qboolean vertexes2D; // shader needs to be finished + trRefEntity_t entity2D; // currentEntity will point at this when doing 2D rendering + + FBO_t *last2DFBO; + qboolean colorMask[4]; + qboolean framePostProcessed; + qboolean depthFill; +} backEndState_t; + +/* +** trGlobals_t +** +** Most renderer globals are defined here. +** backend functions should never modify any of these fields, +** but may read fields that aren't dynamically modified +** by the frontend. +*/ +typedef struct { + qboolean registered; // cleared at shutdown, set at beginRegistration + + int visIndex; + int visClusters[MAX_VISCOUNTS]; + int visCounts[MAX_VISCOUNTS]; // incremented every time a new vis cluster is entered + + int frameCount; // incremented every frame + int sceneCount; // incremented every scene + int viewCount; // incremented every view (twice a scene if portaled) + // and every R_MarkFragments call + + int smpFrame; // toggles from 0 to 1 every endFrame + + int frameSceneNum; // zeroed at RE_BeginFrame + + qboolean worldMapLoaded; + qboolean worldDeluxeMapping; + vec2_t autoExposureMinMax; + vec3_t toneMinAvgMaxLevel; + world_t *world; + + const byte *externalVisData; // from RE_SetWorldVisData, shared with CM_Load + + image_t *defaultImage; + image_t *scratchImage[32]; + image_t *fogImage; + image_t *dlightImage; // inverse-quare highlight for projective adding + image_t *flareImage; + image_t *whiteImage; // full of 0xff + image_t *identityLightImage; // full of tr.identityLightByte + + image_t *shadowCubemaps[MAX_DLIGHTS]; + + + image_t *renderImage; + image_t *godRaysImage; + image_t *renderDepthImage; + image_t *pshadowMaps[MAX_DRAWN_PSHADOWS]; + image_t *textureScratchImage[2]; + image_t *screenScratchImage; + image_t *quarterImage[2]; + image_t *calcLevelsImage; + image_t *targetLevelsImage; + image_t *fixedLevelsImage; + image_t *sunShadowDepthImage[3]; + image_t *screenShadowImage; + image_t *screenSsaoImage; + image_t *hdrDepthImage; + + image_t *textureDepthImage; + + FBO_t *renderFbo; + FBO_t *msaaResolveFbo; + FBO_t *godRaysFbo; + FBO_t *depthFbo; + FBO_t *pshadowFbos[MAX_DRAWN_PSHADOWS]; + FBO_t *textureScratchFbo[2]; + FBO_t *screenScratchFbo; + FBO_t *quarterFbo[2]; + FBO_t *calcLevelsFbo; + FBO_t *targetLevelsFbo; + FBO_t *sunShadowFbo[3]; + FBO_t *screenShadowFbo; + FBO_t *screenSsaoFbo; + FBO_t *hdrDepthFbo; + + shader_t *defaultShader; + shader_t *shadowShader; + shader_t *projectionShadowShader; + + shader_t *flareShader; + shader_t *sunShader; + + int numLightmaps; + int lightmapSize; + image_t **lightmaps; + image_t **deluxemaps; + + int fatLightmapSize; + int fatLightmapStep; + + trRefEntity_t *currentEntity; + trRefEntity_t worldEntity; // point currentEntity at this when rendering world + int currentEntityNum; + int shiftedEntityNum; // currentEntityNum << QSORT_REFENTITYNUM_SHIFT + model_t *currentModel; + + // + // GPU shader programs + // + shaderProgram_t genericShader[GENERICDEF_COUNT]; + shaderProgram_t textureColorShader; + shaderProgram_t fogShader; + shaderProgram_t dlightallShader; + shaderProgram_t lightallShader[LIGHTDEF_COUNT]; + shaderProgram_t shadowmapShader; + shaderProgram_t pshadowShader; + shaderProgram_t down4xShader; + shaderProgram_t bokehShader; + shaderProgram_t tonemapShader; + shaderProgram_t calclevels4xShader[2]; + shaderProgram_t shadowmaskShader; + shaderProgram_t ssaoShader; + shaderProgram_t depthBlurShader[2]; + + + // ----------------------------------------- + + viewParms_t viewParms; + + float identityLight; // 1.0 / ( 1 << overbrightBits ) + int identityLightByte; // identityLight * 255 + int overbrightBits; // r_overbrightBits->integer, but set to 0 if no hw gamma + + orientationr_t or; // for current entity + + trRefdef_t refdef; + + int viewCluster; + + float mapLightScale; + + qboolean sunShadows; + vec3_t sunLight; // from the sky shader for this level + vec3_t sunAmbient; + vec3_t sunDirection; + + frontEndCounters_t pc; + int frontEndMsec; // not in pc due to clearing issue + + // + // put large tables at the end, so most elements will be + // within the +/32K indexed range on risc processors + // + model_t *models[MAX_MOD_KNOWN]; + int numModels; + + int numImages; + image_t *images[MAX_DRAWIMAGES]; + + int numFBOs; + FBO_t *fbos[MAX_FBOS]; + + int numVBOs; + VBO_t *vbos[MAX_VBOS]; + + int numIBOs; + IBO_t *ibos[MAX_IBOS]; + + // shader indexes from other modules will be looked up in tr.shaders[] + // shader indexes from drawsurfs will be looked up in sortedShaders[] + // lower indexed sortedShaders must be rendered first (opaque surfaces before translucent) + int numShaders; + shader_t *shaders[MAX_SHADERS]; + shader_t *sortedShaders[MAX_SHADERS]; + + int numSkins; + skin_t *skins[MAX_SKINS]; + +#ifdef REACTION + GLuint sunFlareQuery[2]; + int sunFlareQueryIndex; + qboolean sunFlareQueryActive[2]; +#endif + + float sinTable[FUNCTABLE_SIZE]; + float squareTable[FUNCTABLE_SIZE]; + float triangleTable[FUNCTABLE_SIZE]; + float sawToothTable[FUNCTABLE_SIZE]; + float inverseSawToothTable[FUNCTABLE_SIZE]; + float fogTable[FOG_TABLE_SIZE]; +} trGlobals_t; + +extern backEndState_t backEnd; +extern trGlobals_t tr; +extern glconfig_t glConfig; // outside of TR since it shouldn't be cleared during ref re-init +extern glstate_t glState; // outside of TR since it shouldn't be cleared during ref re-init + +// These three variables should live inside glConfig but can't because of compatibility issues to the original ID vms. +// If you release a stand-alone game and your mod uses tr_types.h from this build you can safely move them to +// the glconfig_t struct. +extern qboolean textureFilterAnisotropic; +extern int maxAnisotropy; +extern glRefConfig_t glRefConfig; +extern float displayAspect; + + +// +// cvars +// +extern cvar_t *r_flareSize; +extern cvar_t *r_flareFade; +// coefficient for the flare intensity falloff function. +#define FLARE_STDCOEFF "150" +extern cvar_t *r_flareCoeff; + +extern cvar_t *r_railWidth; +extern cvar_t *r_railCoreWidth; +extern cvar_t *r_railSegmentLength; + +extern cvar_t *r_ignore; // used for debugging anything +extern cvar_t *r_verbose; // used for verbose debug spew + +extern cvar_t *r_znear; // near Z clip plane +extern cvar_t *r_zproj; // z distance of projection plane +extern cvar_t *r_stereoSeparation; // separation of cameras for stereo rendering + +extern cvar_t *r_stencilbits; // number of desired stencil bits +extern cvar_t *r_depthbits; // number of desired depth bits +extern cvar_t *r_colorbits; // number of desired color bits, only relevant for fullscreen +extern cvar_t *r_texturebits; // number of desired texture bits +extern cvar_t *r_ext_multisample; + // 0 = use framebuffer depth + // 16 = use 16-bit textures + // 32 = use 32-bit textures + // all else = error + +extern cvar_t *r_measureOverdraw; // enables stencil buffer overdraw measurement + +extern cvar_t *r_lodbias; // push/pull LOD transitions +extern cvar_t *r_lodscale; + +extern cvar_t *r_inGameVideo; // controls whether in game video should be draw +extern cvar_t *r_fastsky; // controls whether sky should be cleared or drawn +extern cvar_t *r_drawSun; // controls drawing of sun quad +extern cvar_t *r_dynamiclight; // dynamic lights enabled/disabled +extern cvar_t *r_dlightBacks; // dlight non-facing surfaces for continuity + +extern cvar_t *r_norefresh; // bypasses the ref rendering +extern cvar_t *r_drawentities; // disable/enable entity rendering +extern cvar_t *r_drawworld; // disable/enable world rendering +extern cvar_t *r_speeds; // various levels of information display +extern cvar_t *r_detailTextures; // enables/disables detail texturing stages +extern cvar_t *r_novis; // disable/enable usage of PVS +extern cvar_t *r_nocull; +extern cvar_t *r_facePlaneCull; // enables culling of planar surfaces with back side test +extern cvar_t *r_nocurves; +extern cvar_t *r_showcluster; + +extern cvar_t *r_mode; // video mode +extern cvar_t *r_fullscreen; +extern cvar_t *r_noborder; +extern cvar_t *r_gamma; +extern cvar_t *r_ignorehwgamma; // overrides hardware gamma capabilities + +extern cvar_t *r_allowExtensions; // global enable/disable of OpenGL extensions +extern cvar_t *r_ext_compressed_textures; // these control use of specific extensions +extern cvar_t *r_ext_multitexture; +extern cvar_t *r_ext_compiled_vertex_array; +extern cvar_t *r_ext_texture_env_add; + +extern cvar_t *r_ext_texture_filter_anisotropic; +extern cvar_t *r_ext_max_anisotropy; + +extern cvar_t *r_ext_draw_range_elements; +extern cvar_t *r_ext_multi_draw_arrays; +extern cvar_t *r_ext_framebuffer_object; +extern cvar_t *r_ext_texture_float; +extern cvar_t *r_arb_half_float_pixel; +extern cvar_t *r_ext_framebuffer_multisample; + +extern cvar_t *r_nobind; // turns off binding to appropriate textures +extern cvar_t *r_singleShader; // make most world faces use default shader +extern cvar_t *r_roundImagesDown; +extern cvar_t *r_colorMipLevels; // development aid to see texture mip usage +extern cvar_t *r_picmip; // controls picmip values +extern cvar_t *r_finish; +extern cvar_t *r_drawBuffer; +extern cvar_t *r_swapInterval; +extern cvar_t *r_textureMode; +extern cvar_t *r_offsetFactor; +extern cvar_t *r_offsetUnits; + +extern cvar_t *r_fullbright; // avoid lightmap pass +extern cvar_t *r_lightmap; // render lightmaps only +extern cvar_t *r_vertexLight; // vertex lighting mode for better performance +extern cvar_t *r_uiFullScreen; // ui is running fullscreen + +extern cvar_t *r_logFile; // number of frames to emit GL logs +extern cvar_t *r_showtris; // enables wireframe rendering of the world +extern cvar_t *r_showsky; // forces sky in front of all surfaces +extern cvar_t *r_shownormals; // draws wireframe normals +extern cvar_t *r_clear; // force screen clear every frame + +extern cvar_t *r_shadows; // controls shadows: 0 = none, 1 = blur, 2 = stencil, 3 = black planar projection +extern cvar_t *r_flares; // light flares + +extern cvar_t *r_intensity; + +extern cvar_t *r_lockpvs; +extern cvar_t *r_noportals; +extern cvar_t *r_portalOnly; + +extern cvar_t *r_subdivisions; +extern cvar_t *r_lodCurveError; +extern cvar_t *r_smp; +extern cvar_t *r_showSmp; +extern cvar_t *r_skipBackEnd; + +extern cvar_t *r_stereoEnabled; +extern cvar_t *r_anaglyphMode; + +extern cvar_t *r_mergeMultidraws; +extern cvar_t *r_mergeLeafSurfaces; + +extern cvar_t *r_hdr; +extern cvar_t *r_postProcess; + +extern cvar_t *r_toneMap; +extern cvar_t *r_forceToneMap; +extern cvar_t *r_forceToneMapMin; +extern cvar_t *r_forceToneMapAvg; +extern cvar_t *r_forceToneMapMax; + +extern cvar_t *r_autoExposure; +extern cvar_t *r_forceAutoExposure; +extern cvar_t *r_forceAutoExposureMin; +extern cvar_t *r_forceAutoExposureMax; + +extern cvar_t *r_cameraExposure; + +extern cvar_t *r_srgb; + +extern cvar_t *r_depthPrepass; +extern cvar_t *r_ssao; + +extern cvar_t *r_normalMapping; +extern cvar_t *r_specularMapping; +extern cvar_t *r_deluxeMapping; +extern cvar_t *r_parallaxMapping; +extern cvar_t *r_normalAmbient; +extern cvar_t *r_dlightMode; +extern cvar_t *r_pshadowDist; +extern cvar_t *r_recalcMD3Normals; +extern cvar_t *r_mergeLightmaps; +extern cvar_t *r_imageUpsample; +extern cvar_t *r_imageUpsampleMaxSize; +extern cvar_t *r_imageUpsampleType; +extern cvar_t *r_genNormalMaps; +extern cvar_t *r_forceSun; +extern cvar_t *r_forceSunMapLightScale; +extern cvar_t *r_forceSunLightScale; +extern cvar_t *r_forceSunAmbientScale; +extern cvar_t *r_sunShadows; +extern cvar_t *r_shadowFilter; +extern cvar_t *r_shadowMapSize; +extern cvar_t *r_shadowCascadeZNear; +extern cvar_t *r_shadowCascadeZFar; +extern cvar_t *r_shadowCascadeZBias; + +extern cvar_t *r_greyscale; + +extern cvar_t *r_ignoreGLErrors; + +extern cvar_t *r_overBrightBits; +extern cvar_t *r_mapOverBrightBits; + +extern cvar_t *r_debugSurface; +extern cvar_t *r_simpleMipMaps; + +extern cvar_t *r_showImages; +extern cvar_t *r_debugSort; + +extern cvar_t *r_printShaders; +extern cvar_t *r_saveFontData; + +extern cvar_t *r_marksOnTriangleMeshes; + +//==================================================================== + +float R_NoiseGet4f( float x, float y, float z, float t ); +void R_NoiseInit( void ); + +void R_SwapBuffers( int ); + +void R_RenderView( viewParms_t *parms ); +void R_RenderDlightCubemaps(const refdef_t *fd); +void R_RenderPshadowMaps(const refdef_t *fd); +void R_RenderSunShadowMaps(const refdef_t *fd, int level); + +void R_AddMD3Surfaces( trRefEntity_t *e ); +void R_AddNullModelSurfaces( trRefEntity_t *e ); +void R_AddBeamSurfaces( trRefEntity_t *e ); +void R_AddRailSurfaces( trRefEntity_t *e, qboolean isUnderwater ); +void R_AddLightningBoltSurfaces( trRefEntity_t *e ); + +void R_AddPolygonSurfaces( void ); + +void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, + int *fogNum, int *dlightMap, int *pshadowMap ); + +void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader, + int fogIndex, int dlightMap, int pshadowMap ); + +void R_CalcTangentSpace(vec3_t tangent, vec3_t bitangent, vec3_t normal, + const vec3_t v0, const vec3_t v1, const vec3_t v2, const vec2_t t0, const vec2_t t1, const vec2_t t2); +qboolean R_CalcTangentVectors(srfVert_t * dv[3]); +void R_CalcSurfaceTriangleNeighbors(int numTriangles, srfTriangle_t * triangles); +void R_CalcSurfaceTrianglePlanes(int numTriangles, srfTriangle_t * triangles, srfVert_t * verts); + +#define CULL_IN 0 // completely unclipped +#define CULL_CLIP 1 // clipped by one or more planes +#define CULL_OUT 2 // completely outside the clipping planes +void R_LocalNormalToWorld (const vec3_t local, vec3_t world); +void R_LocalPointToWorld (const vec3_t local, vec3_t world); +int R_CullBox (vec3_t bounds[2]); +int R_CullLocalBox (vec3_t bounds[2]); +int R_CullPointAndRadiusEx( const vec3_t origin, float radius, const cplane_t* frustum, int numPlanes ); +int R_CullPointAndRadius( const vec3_t origin, float radius ); +int R_CullLocalPointAndRadius( const vec3_t origin, float radius ); + +void R_SetupProjection(viewParms_t *dest, float zProj, float zFar, qboolean computeFrustum); +void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, orientationr_t *or ); + +/* +** GL wrapper/helper functions +*/ +void GL_Bind( image_t *image ); +void GL_BindCubemap( image_t *image ); +void GL_BindToTMU( image_t *image, int tmu ); +void GL_SetDefaultState (void); +void GL_SelectTexture( int unit ); +void GL_TextureMode( const char *string ); +void GL_CheckErrs( char *file, int line ); +#define GL_CheckErrors(...) GL_CheckErrs(__FILE__, __LINE__) +void GL_State( unsigned long stateVector ); +void GL_SetProjectionMatrix(matrix_t matrix); +void GL_SetModelviewMatrix(matrix_t matrix); +void GL_TexEnv( int env ); +void GL_Cull( int cullType ); + +#define GLS_SRCBLEND_ZERO 0x00000001 +#define GLS_SRCBLEND_ONE 0x00000002 +#define GLS_SRCBLEND_DST_COLOR 0x00000003 +#define GLS_SRCBLEND_ONE_MINUS_DST_COLOR 0x00000004 +#define GLS_SRCBLEND_SRC_ALPHA 0x00000005 +#define GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA 0x00000006 +#define GLS_SRCBLEND_DST_ALPHA 0x00000007 +#define GLS_SRCBLEND_ONE_MINUS_DST_ALPHA 0x00000008 +#define GLS_SRCBLEND_ALPHA_SATURATE 0x00000009 +#define GLS_SRCBLEND_BITS 0x0000000f + +#define GLS_DSTBLEND_ZERO 0x00000010 +#define GLS_DSTBLEND_ONE 0x00000020 +#define GLS_DSTBLEND_SRC_COLOR 0x00000030 +#define GLS_DSTBLEND_ONE_MINUS_SRC_COLOR 0x00000040 +#define GLS_DSTBLEND_SRC_ALPHA 0x00000050 +#define GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA 0x00000060 +#define GLS_DSTBLEND_DST_ALPHA 0x00000070 +#define GLS_DSTBLEND_ONE_MINUS_DST_ALPHA 0x00000080 +#define GLS_DSTBLEND_BITS 0x000000f0 + +#define GLS_DEPTHMASK_TRUE 0x00000100 + +#define GLS_POLYMODE_LINE 0x00001000 + +#define GLS_DEPTHTEST_DISABLE 0x00010000 +#define GLS_DEPTHFUNC_EQUAL 0x00020000 +#define GLS_DEPTHFUNC_GREATER 0x00040000 +#define GLS_DEPTHFUNC_BITS 0x00060000 + +#define GLS_ATEST_GT_0 0x10000000 +#define GLS_ATEST_LT_80 0x20000000 +#define GLS_ATEST_GE_80 0x40000000 +#define GLS_ATEST_BITS 0x70000000 + +#define GLS_DEFAULT GLS_DEPTHMASK_TRUE + +void RE_StretchRaw (int x, int y, 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); + +void RE_BeginFrame( stereoFrame_t stereoFrame ); +void RE_BeginRegistration( glconfig_t *glconfig ); +void RE_LoadWorldMap( const char *mapname ); +void RE_SetWorldVisData( const byte *vis ); +qhandle_t RE_RegisterModel( const char *name ); +qhandle_t RE_RegisterSkin( const char *name ); +void RE_Shutdown( qboolean destroyWindow ); + +qboolean R_GetEntityToken( char *buffer, int size ); + +model_t *R_AllocModel( void ); + +void R_Init( void ); +image_t *R_FindImageFile( const char *name, imgType_t type, imgFlags_t flags ); +image_t *R_CreateImage( const char *name, byte *pic, int width, int height, imgType_t type, imgFlags_t flags, int internalFormat ); +void R_UpdateSubImage( image_t *image, byte *pic, int x, int y, int width, int height ); +qboolean R_GetModeInfo( int *width, int *height, float *windowAspect, int mode ); + +void R_SetColorMappings( void ); +void R_GammaCorrect( byte *buffer, int bufSize ); + +void R_ImageList_f( void ); +void R_SkinList_f( void ); +// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=516 +const void *RB_TakeScreenshotCmd( const void *data ); +void R_ScreenShot_f( void ); + +void R_InitFogTable( void ); +float R_FogFactor( float s, float t ); +void R_InitImages( void ); +void R_DeleteTextures( void ); +int R_SumOfUsedImages( void ); +void R_InitSkins( void ); +skin_t *R_GetSkinByHandle( qhandle_t hSkin ); + +int R_ComputeLOD( trRefEntity_t *ent ); + +const void *RB_TakeVideoFrameCmd( const void *data ); + +// +// tr_shader.c +// +qhandle_t RE_RegisterShaderLightMap( const char *name, int lightmapIndex ); +qhandle_t RE_RegisterShader( const char *name ); +qhandle_t RE_RegisterShaderNoMip( const char *name ); +qhandle_t RE_RegisterShaderFromImage(const char *name, int lightmapIndex, image_t *image, qboolean mipRawImage); + +shader_t *R_FindShader( const char *name, int lightmapIndex, qboolean mipRawImage ); +shader_t *R_GetShaderByHandle( qhandle_t hShader ); +shader_t *R_GetShaderByState( int index, long *cycleTime ); +shader_t *R_FindShaderByName( const char *name ); +void R_InitShaders( void ); +void R_ShaderList_f( void ); +void R_RemapShader(const char *oldShader, const char *newShader, const char *timeOffset); + +/* +==================================================================== + +IMPLEMENTATION SPECIFIC FUNCTIONS + +==================================================================== +*/ + +void GLimp_Init( void ); +void GLimp_Shutdown( void ); +void GLimp_EndFrame( void ); + +qboolean GLimp_SpawnRenderThread( void (*function)( void ) ); +void *GLimp_RendererSleep( void ); +void GLimp_FrontEndSleep( void ); +void GLimp_WakeRenderer( void *data ); + +void GLimp_LogComment( char *comment ); +void GLimp_Minimize(void); + +// NOTE TTimo linux works with float gamma value, not the gamma table +// the params won't be used, getting the r_gamma cvar directly +void GLimp_SetGamma( unsigned char red[256], + unsigned char green[256], + unsigned char blue[256] ); + + +void GLimp_InitExtraExtensions( void ); +/* +==================================================================== + +TESSELATOR/SHADER DECLARATIONS + +==================================================================== +*/ + +typedef struct stageVars +{ + color4ub_t colors[SHADER_MAX_VERTEXES]; + vec2_t texcoords[NUM_TEXTURE_BUNDLES][SHADER_MAX_VERTEXES]; +} stageVars_t; + +#define MAX_MULTIDRAW_PRIMITIVES 16384 + +typedef struct shaderCommands_s +{ + glIndex_t indexes[SHADER_MAX_INDEXES] QALIGN(16); + vec4_t xyz[SHADER_MAX_VERTEXES] QALIGN(16); + vec4_t normal[SHADER_MAX_VERTEXES] QALIGN(16); +#ifdef USE_VERT_TANGENT_SPACE + vec4_t tangent[SHADER_MAX_VERTEXES] QALIGN(16); + vec4_t bitangent[SHADER_MAX_VERTEXES] QALIGN(16); +#endif + vec2_t texCoords[SHADER_MAX_VERTEXES][2] QALIGN(16); + vec4_t vertexColors[SHADER_MAX_VERTEXES] QALIGN(16); + vec4_t lightdir[SHADER_MAX_VERTEXES] QALIGN(16); + //int vertexDlightBits[SHADER_MAX_VERTEXES] QALIGN(16); + + VBO_t *vbo; + IBO_t *ibo; + qboolean useInternalVBO; + + stageVars_t svars QALIGN(16); + + //color4ub_t constantColor255[SHADER_MAX_VERTEXES] QALIGN(16); + + shader_t *shader; + float shaderTime; + int fogNum; + + int dlightBits; // or together of all vertexDlightBits + int pshadowBits; + + int firstIndex; + int numIndexes; + int numVertexes; + + int multiDrawPrimitives; + GLsizei multiDrawNumIndexes[MAX_MULTIDRAW_PRIMITIVES]; + GLvoid * multiDrawFirstIndex[MAX_MULTIDRAW_PRIMITIVES]; + GLvoid * multiDrawLastIndex[MAX_MULTIDRAW_PRIMITIVES]; + + // info extracted from current shader + int numPasses; + void (*currentStageIteratorFunc)( void ); + shaderStage_t **xstages; +} shaderCommands_t; + +extern shaderCommands_t tess; + +void RB_BeginSurface(shader_t *shader, int fogNum ); +void RB_EndSurface(void); +void RB_CheckOverflow( int verts, int indexes ); +#define RB_CHECKOVERFLOW(v,i) if (tess.numVertexes + (v) >= SHADER_MAX_VERTEXES || tess.numIndexes + (i) >= SHADER_MAX_INDEXES ) {RB_CheckOverflow(v,i);} + +void R_DrawElementsVBO( int numIndexes, int firstIndex ); +void RB_StageIteratorGeneric( void ); +void RB_StageIteratorSky( void ); +void RB_StageIteratorVertexLitTexture( void ); +void RB_StageIteratorLightmappedMultitexture( void ); + +void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, float color[4] ); +void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, float color[4], 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], vec4_t color, shaderProgram_t *sp, vec2_t invTexRes); +void RB_InstantQuad2(vec4_t quadVerts[4], vec2_t texCoords[4]); + +void RB_ShowImages( void ); + + +/* +============================================================ + +WORLD MAP + +============================================================ +*/ + +void R_AddBrushModelSurfaces( trRefEntity_t *e ); +void R_AddWorldSurfaces( void ); +qboolean R_inPVS( const vec3_t p1, const vec3_t p2 ); + + +/* +============================================================ + +FLARES + +============================================================ +*/ + +void R_ClearFlares( void ); + +void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ); +void RB_AddDlightFlares( void ); +void RB_RenderFlares (void); + +/* +============================================================ + +LIGHTS + +============================================================ +*/ + +void R_DlightBmodel( bmodel_t *bmodel ); +void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ); +void R_TransformDlights( int count, dlight_t *dl, orientationr_t *or ); +int R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); +int R_LightDirForPoint( vec3_t point, vec3_t lightDir, vec3_t normal, world_t *world ); + + +/* +============================================================ + +SHADOWS + +============================================================ +*/ + +void RB_ShadowTessEnd( void ); +void RB_ShadowFinish( void ); +void RB_ProjectionShadowDeform( void ); + +/* +============================================================ + +SKIES + +============================================================ +*/ + +void R_BuildCloudData( shaderCommands_t *shader ); +void R_InitSkyTexCoords( float cloudLayerHeight ); +void R_DrawSkyBox( shaderCommands_t *shader ); +void RB_DrawSun( void ); +void RB_ClipSkyPolygons( shaderCommands_t *shader ); + +/* +============================================================ + +CURVE TESSELATION + +============================================================ +*/ + +#define PATCH_STITCHING + +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ); +srfGridMesh_t *R_GridInsertColumn( srfGridMesh_t *grid, int column, int row, vec3_t point, float loderror ); +srfGridMesh_t *R_GridInsertRow( srfGridMesh_t *grid, int row, int column, vec3_t point, float loderror ); +void R_FreeSurfaceGridMesh( srfGridMesh_t *grid ); + +/* +============================================================ + +MARKERS, POLYGON PROJECTION ON WORLD POLYGONS + +============================================================ +*/ + +int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + + +/* +============================================================ + +VERTEX BUFFER OBJECTS + +============================================================ +*/ +VBO_t *R_CreateVBO(const char *name, byte * vertexes, int vertexesSize, vboUsage_t usage); +VBO_t *R_CreateVBO2(const char *name, int numVertexes, srfVert_t * vertexes, uint32_t stateBits, vboUsage_t usage); + +IBO_t *R_CreateIBO(const char *name, byte * indexes, int indexesSize, vboUsage_t usage); +IBO_t *R_CreateIBO2(const char *name, int numTriangles, srfTriangle_t * triangles, vboUsage_t usage); + +void R_BindVBO(VBO_t * vbo); +void R_BindNullVBO(void); + +void R_BindIBO(IBO_t * ibo); +void R_BindNullIBO(void); + +void R_InitVBOs(void); +void R_ShutdownVBOs(void); +void R_VBOList_f(void); + +void RB_UpdateVBOs(unsigned int attribBits); + + +/* +============================================================ + +GLSL + +============================================================ +*/ + +void GLSL_InitGPUShaders(void); +void GLSL_ShutdownGPUShaders(void); +void GLSL_VertexAttribsState(uint32_t stateBits); +void GLSL_VertexAttribPointers(uint32_t attribBits); +void GLSL_BindProgram(shaderProgram_t * program); +void GLSL_BindNullProgram(void); + +void GLSL_SetNumUniforms(shaderProgram_t *program, int numUniforms); +void GLSL_SetUniformName(shaderProgram_t *program, int uniformNum, const char *name); +void GLSL_SetUniformInt(shaderProgram_t *program, int uniformNum, GLint value); +void GLSL_SetUniformFloat(shaderProgram_t *program, int uniformNum, GLfloat value); +void GLSL_SetUniformFloat5(shaderProgram_t *program, int uniformNum, const vec5_t v); +void GLSL_SetUniformVec2(shaderProgram_t *program, int uniformNum, const vec2_t v); +void GLSL_SetUniformVec3(shaderProgram_t *program, int uniformNum, const vec3_t v); +void GLSL_SetUniformVec4(shaderProgram_t *program, int uniformNum, const vec4_t v); +void GLSL_SetUniformMatrix16(shaderProgram_t *program, int uniformNum, const matrix_t matrix); + +shaderProgram_t *GLSL_GetGenericShaderProgram(int stage); + +/* +============================================================ + +SCENE GENERATION + +============================================================ +*/ + +void R_ToggleSmpFrame( void ); + +void RE_ClearScene( void ); +void RE_AddRefEntityToScene( const refEntity_t *ent ); +void RE_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ); +void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void RE_RenderScene( const refdef_t *fd ); + +#ifdef RAVENMD4 +/* +============================================================= + +UNCOMPRESSING BONES + +============================================================= +*/ + +#define MC_BITS_X (16) +#define MC_BITS_Y (16) +#define MC_BITS_Z (16) +#define MC_BITS_VECT (16) + +#define MC_SCALE_X (1.0f/64) +#define MC_SCALE_Y (1.0f/64) +#define MC_SCALE_Z (1.0f/64) + +void MC_UnCompress(float mat[3][4],const unsigned char * comp); +#endif + +/* +============================================================= + +ANIMATED MODELS + +============================================================= +*/ + +// void R_MakeAnimModel( model_t *model ); haven't seen this one really, so not needed I guess. +void R_AddAnimSurfaces( trRefEntity_t *ent ); +void RB_SurfaceAnim( md4Surface_t *surfType ); +#ifdef RAVENMD4 +void R_MDRAddAnimSurfaces( trRefEntity_t *ent ); +void RB_MDRSurfaceAnim( md4Surface_t *surface ); +#endif +qboolean R_LoadIQM (model_t *mod, void *buffer, int filesize, const char *name ); +void R_AddIQMSurfaces( trRefEntity_t *ent ); +void RB_IQMSurfaceAnim( surfaceType_t *surface ); +int R_IQMLerpTag( orientation_t *tag, iqmData_t *data, + int startFrame, int endFrame, + float frac, const char *tagName ); + +/* +============================================================= + +IMAGE LOADERS + +============================================================= +*/ + +void R_LoadBMP( const char *name, byte **pic, int *width, int *height ); +void R_LoadJPG( const char *name, byte **pic, int *width, int *height ); +void R_LoadPCX( const char *name, byte **pic, int *width, int *height ); +void R_LoadPNG( const char *name, byte **pic, int *width, int *height ); +void R_LoadTGA( const char *name, byte **pic, int *width, int *height ); + +/* +============================================================= +============================================================= +*/ +void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix, + vec4_t eye, vec4_t dst ); +void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ); + +void RB_DeformTessGeometry( void ); + +void RB_CalcEnvironmentTexCoords( float *dstTexCoords ); +void RB_CalcFogTexCoords( float *dstTexCoords ); +void RB_CalcScrollTexCoords( const float scroll[2], float *dstTexCoords ); +void RB_CalcRotateTexCoords( float rotSpeed, float *dstTexCoords ); +void RB_CalcScaleTexCoords( const float scale[2], float *dstTexCoords ); +void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *dstTexCoords ); +void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *dstTexCoords ); + +void RB_CalcScaleTexMatrix( const float scale[2], float *matrix ); +void RB_CalcScrollTexMatrix( const float scrollSpeed[2], float *matrix ); +void RB_CalcRotateTexMatrix( float degsPerSecond, float *matrix ); +void RB_CalcTurbulentTexMatrix( const waveForm_t *wf, matrix_t matrix ); +void RB_CalcTransformTexMatrix( const texModInfo_t *tmi, float *matrix ); +void RB_CalcStretchTexMatrix( const waveForm_t *wf, float *matrix ); + +void RB_CalcModulateColorsByFog( unsigned char *dstColors ); +void RB_CalcModulateAlphasByFog( unsigned char *dstColors ); +void RB_CalcModulateRGBAsByFog( unsigned char *dstColors ); +void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors ); +float RB_CalcWaveAlphaSingle( const waveForm_t *wf ); +void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors ); +float RB_CalcWaveColorSingle( const waveForm_t *wf ); +void RB_CalcAlphaFromEntity( unsigned char *dstColors ); +void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors ); +void RB_CalcStretchTexCoords( const waveForm_t *wf, float *texCoords ); +void RB_CalcColorFromEntity( unsigned char *dstColors ); +void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors ); +void RB_CalcSpecularAlpha( unsigned char *alphas ); +void RB_CalcDiffuseColor( unsigned char *colors ); + +/* +============================================================= + +RENDERER BACK END FUNCTIONS + +============================================================= +*/ + +void RB_RenderThread( void ); +void RB_ExecuteRenderCommands( const void *data ); + +/* +============================================================= + +RENDERER BACK END COMMAND QUEUE + +============================================================= +*/ + +#define MAX_RENDER_COMMANDS 0x40000 + +typedef struct { + byte cmds[MAX_RENDER_COMMANDS]; + int used; +} renderCommandList_t; + +typedef struct { + int commandId; + float color[4]; +} setColorCommand_t; + +typedef struct { + int commandId; + int buffer; +} drawBufferCommand_t; + +typedef struct { + int commandId; + image_t *image; + int width; + int height; + void *data; +} subImageCommand_t; + +typedef struct { + int commandId; +} swapBuffersCommand_t; + +typedef struct { + int commandId; + int buffer; +} endFrameCommand_t; + +typedef struct { + int commandId; + shader_t *shader; + float x, y; + float w, h; + float s1, t1; + float s2, t2; +} stretchPicCommand_t; + +typedef struct { + int commandId; + trRefdef_t refdef; + viewParms_t viewParms; + drawSurf_t *drawSurfs; + int numDrawSurfs; +} drawSurfsCommand_t; + +typedef struct { + int commandId; + int x; + int y; + int width; + int height; + char *fileName; + qboolean jpeg; +} screenshotCommand_t; + +typedef struct { + int commandId; + int width; + int height; + byte *captureBuffer; + byte *encodeBuffer; + qboolean motionJpeg; +} videoFrameCommand_t; + +typedef struct +{ + int commandId; + + GLboolean rgba[4]; +} colorMaskCommand_t; + +typedef struct +{ + int commandId; +} clearDepthCommand_t; + +typedef struct { + int commandId; + int map; + int cubeSide; +} capShadowmapCommand_t; + +typedef struct { + int commandId; + trRefdef_t refdef; + viewParms_t viewParms; +} postProcessCommand_t; + +typedef enum { + RC_END_OF_LIST, + RC_SET_COLOR, + RC_STRETCH_PIC, + RC_DRAW_SURFS, + RC_DRAW_BUFFER, + RC_SWAP_BUFFERS, + RC_SCREENSHOT, + RC_VIDEOFRAME, + RC_COLORMASK, + RC_CLEARDEPTH, + RC_CAPSHADOWMAP, + RC_POSTPROCESS +} renderCommand_t; + + +// these are sort of arbitrary limits. +// the limits apply to the sum of all scenes in a frame -- +// the main view, all the 3D icons, etc +#define MAX_POLYS 600 +#define MAX_POLYVERTS 3000 + +// all of the information needed by the back end must be +// contained in a backEndData_t. This entire structure is +// duplicated so the front and back end can run in parallel +// on an SMP machine +typedef struct { + drawSurf_t drawSurfs[MAX_DRAWSURFS]; + dlight_t dlights[MAX_DLIGHTS]; + trRefEntity_t entities[MAX_REFENTITIES]; + srfPoly_t *polys;//[MAX_POLYS]; + polyVert_t *polyVerts;//[MAX_POLYVERTS]; + pshadow_t pshadows[MAX_CALC_PSHADOWS]; + renderCommandList_t commands; +} backEndData_t; + +extern int max_polys; +extern int max_polyverts; + +extern backEndData_t *backEndData[SMP_FRAMES]; // the second one may not be allocated + +extern volatile renderCommandList_t *renderCommandList; + +extern volatile qboolean renderThreadActive; + + +void *R_GetCommandBuffer( int bytes ); +void RB_ExecuteRenderCommands( const void *data ); + +void R_InitCommandBuffers( void ); +void R_ShutdownCommandBuffers( void ); + +void R_SyncRenderThread( void ); + +void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ); +void R_AddCapShadowmapCmd( int dlight, int cubeSide ); +void R_AddPostProcessCmd (void); + +void RE_SetColor( const float *rgba ); +void RE_StretchPic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); +void RE_BeginFrame( stereoFrame_t stereoFrame ); +void RE_EndFrame( int *frontEndMsec, int *backEndMsec ); +void RE_SaveJPG(char * filename, int quality, int image_width, int image_height, + unsigned char *image_buffer, int padding); +size_t RE_SaveJPGToBuffer(byte *buffer, size_t bufSize, int quality, + int image_width, int image_height, byte *image_buffer, int padding); +void RE_TakeVideoFrame( int width, int height, + byte *captureBuffer, byte *encodeBuffer, qboolean motionJpeg ); + +// font stuff +void R_InitFreeType( void ); +void R_DoneFreeType( void ); +void RE_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font); + + +#endif //TR_LOCAL_H diff --git a/src/rend2/tr_main.c b/src/rend2/tr_main.c new file mode 100644 index 00000000..c0ad54e5 --- /dev/null +++ b/src/rend2/tr_main.c @@ -0,0 +1,2878 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_main.c -- main control flow for each frame + +#include "tr_local.h" + +#include // memcpy + +trGlobals_t tr; + +static float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +}; + + +refimport_t ri; + +// entities that will have procedurally generated surfaces will just +// point at this for their sorting surface +surfaceType_t entitySurface = SF_ENTITY; + +/* +================ +R_CompareVert +================ +*/ +qboolean R_CompareVert(srfVert_t * v1, srfVert_t * v2, qboolean checkST) +{ + int i; + + for(i = 0; i < 3; i++) + { + if(floor(v1->xyz[i] + 0.1) != floor(v2->xyz[i] + 0.1)) + { + return qfalse; + } + + if(checkST && ((v1->st[0] != v2->st[0]) || (v1->st[1] != v2->st[1]))) + { + return qfalse; + } + } + + return qtrue; +} + +/* +============= +R_CalcNormalForTriangle +============= +*/ +void R_CalcNormalForTriangle(vec3_t normal, const vec3_t v0, const vec3_t v1, const vec3_t v2) +{ + vec3_t udir, vdir; + + // compute the face normal based on vertex points + VectorSubtract(v2, v0, udir); + VectorSubtract(v1, v0, vdir); + CrossProduct(udir, vdir, normal); + + VectorNormalize(normal); +} + +/* +============= +R_CalcTangentsForTriangle +http://members.rogers.com/deseric/tangentspace.htm +============= +*/ +void R_CalcTangentsForTriangle(vec3_t tangent, vec3_t bitangent, + const vec3_t v0, const vec3_t v1, const vec3_t v2, + const vec2_t t0, const vec2_t t1, const vec2_t t2) +{ + int i; + vec3_t planes[3]; + vec3_t u, v; + + for(i = 0; i < 3; i++) + { + VectorSet(u, v1[i] - v0[i], t1[0] - t0[0], t1[1] - t0[1]); + VectorSet(v, v2[i] - v0[i], t2[0] - t0[0], t2[1] - t0[1]); + + VectorNormalize(u); + VectorNormalize(v); + + CrossProduct(u, v, planes[i]); + } + + //So your tangent space will be defined by this : + //Normal = Normal of the triangle or Tangent X Bitangent (careful with the cross product, + // you have to make sure the normal points in the right direction) + //Tangent = ( dp(Fx(s,t)) / ds, dp(Fy(s,t)) / ds, dp(Fz(s,t)) / ds ) or ( -Bx/Ax, -By/Ay, - Bz/Az ) + //Bitangent = ( dp(Fx(s,t)) / dt, dp(Fy(s,t)) / dt, dp(Fz(s,t)) / dt ) or ( -Cx/Ax, -Cy/Ay, -Cz/Az ) + + // tangent... + tangent[0] = -planes[0][1] / planes[0][0]; + tangent[1] = -planes[1][1] / planes[1][0]; + tangent[2] = -planes[2][1] / planes[2][0]; + VectorNormalize(tangent); + + // bitangent... + bitangent[0] = -planes[0][2] / planes[0][0]; + bitangent[1] = -planes[1][2] / planes[1][0]; + bitangent[2] = -planes[2][2] / planes[2][0]; + VectorNormalize(bitangent); +} + + + + +/* +============= +R_CalcTangentSpace +============= +*/ +void R_CalcTangentSpace(vec3_t tangent, vec3_t bitangent, vec3_t normal, + const vec3_t v0, const vec3_t v1, const vec3_t v2, const vec2_t t0, const vec2_t t1, const vec2_t t2) +{ + vec3_t cp, u, v; + vec3_t faceNormal; + + VectorSet(u, v1[0] - v0[0], t1[0] - t0[0], t1[1] - t0[1]); + VectorSet(v, v2[0] - v0[0], t2[0] - t0[0], t2[1] - t0[1]); + + CrossProduct(u, v, cp); + if(fabs(cp[0]) > 10e-6) + { + tangent[0] = -cp[1] / cp[0]; + bitangent[0] = -cp[2] / cp[0]; + } + + u[0] = v1[1] - v0[1]; + v[0] = v2[1] - v0[1]; + + CrossProduct(u, v, cp); + if(fabs(cp[0]) > 10e-6) + { + tangent[1] = -cp[1] / cp[0]; + bitangent[1] = -cp[2] / cp[0]; + } + + u[0] = v1[2] - v0[2]; + v[0] = v2[2] - v0[2]; + + CrossProduct(u, v, cp); + if(fabs(cp[0]) > 10e-6) + { + tangent[2] = -cp[1] / cp[0]; + bitangent[2] = -cp[2] / cp[0]; + } + + VectorNormalize(tangent); + VectorNormalize(bitangent); + + // compute the face normal based on vertex points + if ( normal[0] == 0.0f && normal[1] == 0.0f && normal[2] == 0.0f ) + { + VectorSubtract(v2, v0, u); + VectorSubtract(v1, v0, v); + CrossProduct(u, v, faceNormal); + } + else + { + VectorCopy(normal, faceNormal); + } + + VectorNormalize(faceNormal); + +#if 1 + // Gram-Schmidt orthogonalize + //tangent[a] = (t - n * Dot(n, t)).Normalize(); + VectorMA(tangent, -DotProduct(faceNormal, tangent), faceNormal, tangent); + VectorNormalize(tangent); + + // compute the cross product B=NxT + //CrossProduct(normal, tangent, bitangent); +#else + // normal, compute the cross product N=TxB + CrossProduct(tangent, bitangent, normal); + VectorNormalize(normal); + + if(DotProduct(normal, faceNormal) < 0) + { + //VectorInverse(normal); + //VectorInverse(tangent); + //VectorInverse(bitangent); + + // compute the cross product T=BxN + CrossProduct(bitangent, faceNormal, tangent); + + // compute the cross product B=NxT + //CrossProduct(normal, tangent, bitangent); + } +#endif + + VectorCopy(faceNormal, normal); +} + +void R_CalcTangentSpaceFast(vec3_t tangent, vec3_t bitangent, vec3_t normal, + const vec3_t v0, const vec3_t v1, const vec3_t v2, const vec2_t t0, const vec2_t t1, const vec2_t t2) +{ + vec3_t cp, u, v; + vec3_t faceNormal; + + VectorSet(u, v1[0] - v0[0], t1[0] - t0[0], t1[1] - t0[1]); + VectorSet(v, v2[0] - v0[0], t2[0] - t0[0], t2[1] - t0[1]); + + CrossProduct(u, v, cp); + if(fabs(cp[0]) > 10e-6) + { + tangent[0] = -cp[1] / cp[0]; + bitangent[0] = -cp[2] / cp[0]; + } + + u[0] = v1[1] - v0[1]; + v[0] = v2[1] - v0[1]; + + CrossProduct(u, v, cp); + if(fabs(cp[0]) > 10e-6) + { + tangent[1] = -cp[1] / cp[0]; + bitangent[1] = -cp[2] / cp[0]; + } + + u[0] = v1[2] - v0[2]; + v[0] = v2[2] - v0[2]; + + CrossProduct(u, v, cp); + if(fabs(cp[0]) > 10e-6) + { + tangent[2] = -cp[1] / cp[0]; + bitangent[2] = -cp[2] / cp[0]; + } + + VectorNormalizeFast(tangent); + VectorNormalizeFast(bitangent); + + // compute the face normal based on vertex points + VectorSubtract(v2, v0, u); + VectorSubtract(v1, v0, v); + CrossProduct(u, v, faceNormal); + + VectorNormalizeFast(faceNormal); + +#if 0 + // normal, compute the cross product N=TxB + CrossProduct(tangent, bitangent, normal); + VectorNormalizeFast(normal); + + if(DotProduct(normal, faceNormal) < 0) + { + VectorInverse(normal); + //VectorInverse(tangent); + //VectorInverse(bitangent); + + CrossProduct(normal, tangent, bitangent); + } + + VectorCopy(faceNormal, normal); +#else + // Gram-Schmidt orthogonalize + //tangent[a] = (t - n * Dot(n, t)).Normalize(); + VectorMA(tangent, -DotProduct(faceNormal, tangent), faceNormal, tangent); + VectorNormalizeFast(tangent); +#endif + + VectorCopy(faceNormal, normal); +} + +/* +http://www.terathon.com/code/tangent.html +*/ +void R_CalcTBN(vec3_t tangent, vec3_t bitangent, vec3_t normal, + const vec3_t v1, const vec3_t v2, const vec3_t v3, const vec2_t w1, const vec2_t w2, const vec2_t w3) +{ + vec3_t u, v; + float x1, x2, y1, y2, z1, z2; + float s1, s2, t1, t2; + float r, dot; + + x1 = v2[0] - v1[0]; + x2 = v3[0] - v1[0]; + y1 = v2[1] - v1[1]; + y2 = v3[1] - v1[1]; + z1 = v2[2] - v1[2]; + z2 = v3[2] - v1[2]; + + s1 = w2[0] - w1[0]; + s2 = w3[0] - w1[0]; + t1 = w2[1] - w1[1]; + t2 = w3[1] - w1[1]; + + r = 1.0f / (s1 * t2 - s2 * t1); + + VectorSet(tangent, (t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r); + VectorSet(bitangent, (s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r); + + // compute the face normal based on vertex points + VectorSubtract(v3, v1, u); + VectorSubtract(v2, v1, v); + CrossProduct(u, v, normal); + + VectorNormalize(normal); + + // Gram-Schmidt orthogonalize + //tangent[a] = (t - n * Dot(n, t)).Normalize(); + dot = DotProduct(normal, tangent); + VectorMA(tangent, -dot, normal, tangent); + VectorNormalize(tangent); + + // B=NxT + //CrossProduct(normal, tangent, bitangent); +} + +void R_CalcTBN2(vec3_t tangent, vec3_t bitangent, vec3_t normal, + const vec3_t v1, const vec3_t v2, const vec3_t v3, const vec2_t t1, const vec2_t t2, const vec2_t t3) +{ + vec3_t v2v1; + vec3_t v3v1; + + float c2c1_T; + float c2c1_B; + + float c3c1_T; + float c3c1_B; + + float denominator; + float scale1, scale2; + + vec3_t T, B, N, C; + + + // Calculate the tangent basis for each vertex of the triangle + // UPDATE: In the 3rd edition of the accompanying article, the for-loop located here has + // been removed as it was redundant (the entire TBN matrix was calculated three times + // instead of just one). + // + // Please note, that this function relies on the fact that the input geometry are triangles + // and the tangent basis for each vertex thus is identical! + // + + // Calculate the vectors from the current vertex to the two other vertices in the triangle + VectorSubtract(v2, v1, v2v1); + VectorSubtract(v3, v1, v3v1); + + // The equation presented in the article states that: + // c2c1_T = V2.texcoord.x – V1.texcoord.x + // c2c1_B = V2.texcoord.y – V1.texcoord.y + // c3c1_T = V3.texcoord.x – V1.texcoord.x + // c3c1_B = V3.texcoord.y – V1.texcoord.y + + // Calculate c2c1_T and c2c1_B + c2c1_T = t2[0] - t1[0]; + c2c1_B = t2[1] - t2[1]; + + // Calculate c3c1_T and c3c1_B + c3c1_T = t3[0] - t1[0]; + c3c1_B = t3[1] - t1[1]; + + denominator = c2c1_T * c3c1_B - c3c1_T * c2c1_B; + //if(ROUNDOFF(fDenominator) == 0.0f) + if(denominator == 0.0f) + { + // We won't risk a divide by zero, so set the tangent matrix to the identity matrix + VectorSet(tangent, 1, 0, 0); + VectorSet(bitangent, 0, 1, 0); + VectorSet(normal, 0, 0, 1); + } + else + { + // Calculate the reciprocal value once and for all (to achieve speed) + scale1 = 1.0f / denominator; + + // T and B are calculated just as the equation in the article states + VectorSet(T, (c3c1_B * v2v1[0] - c2c1_B * v3v1[0]) * scale1, + (c3c1_B * v2v1[1] - c2c1_B * v3v1[1]) * scale1, + (c3c1_B * v2v1[2] - c2c1_B * v3v1[2]) * scale1); + + VectorSet(B, (-c3c1_T * v2v1[0] + c2c1_T * v3v1[0]) * scale1, + (-c3c1_T * v2v1[1] + c2c1_T * v3v1[1]) * scale1, + (-c3c1_T * v2v1[2] + c2c1_T * v3v1[2]) * scale1); + + // The normal N is calculated as the cross product between T and B + CrossProduct(T, B, N); + +#if 0 + VectorCopy(T, tangent); + VectorCopy(B, bitangent); + VectorCopy(N, normal); +#else + // Calculate the reciprocal value once and for all (to achieve speed) + scale2 = 1.0f / ((T[0] * B[1] * N[2] - T[2] * B[1] * N[0]) + + (B[0] * N[1] * T[2] - B[2] * N[1] * T[0]) + + (N[0] * T[1] * B[2] - N[2] * T[1] * B[0])); + + // Calculate the inverse if the TBN matrix using the formula described in the article. + // We store the basis vectors directly in the provided TBN matrix: pvTBNMatrix + CrossProduct(B, N, C); tangent[0] = C[0] * scale2; + CrossProduct(N, T, C); tangent[1] = -C[0] * scale2; + CrossProduct(T, B, C); tangent[2] = C[0] * scale2; + VectorNormalize(tangent); + + CrossProduct(B, N, C); bitangent[0] = -C[1] * scale2; + CrossProduct(N, T, C); bitangent[1] = C[1] * scale2; + CrossProduct(T, B, C); bitangent[2] = -C[1] * scale2; + VectorNormalize(bitangent); + + CrossProduct(B, N, C); normal[0] = C[2] * scale2; + CrossProduct(N, T, C); normal[1] = -C[2] * scale2; + CrossProduct(T, B, C); normal[2] = C[2] * scale2; + VectorNormalize(normal); +#endif + } +} + + +#ifdef USE_VERT_TANGENT_SPACE +qboolean R_CalcTangentVectors(srfVert_t * dv[3]) +{ + int i; + float bb, s, t; + vec3_t bary; + + + /* calculate barycentric basis for the triangle */ + bb = (dv[1]->st[0] - dv[0]->st[0]) * (dv[2]->st[1] - dv[0]->st[1]) - (dv[2]->st[0] - dv[0]->st[0]) * (dv[1]->st[1] - dv[0]->st[1]); + if(fabs(bb) < 0.00000001f) + return qfalse; + + /* do each vertex */ + for(i = 0; i < 3; i++) + { + // calculate s tangent vector + s = dv[i]->st[0] + 10.0f; + t = dv[i]->st[1]; + bary[0] = ((dv[1]->st[0] - s) * (dv[2]->st[1] - t) - (dv[2]->st[0] - s) * (dv[1]->st[1] - t)) / bb; + bary[1] = ((dv[2]->st[0] - s) * (dv[0]->st[1] - t) - (dv[0]->st[0] - s) * (dv[2]->st[1] - t)) / bb; + bary[2] = ((dv[0]->st[0] - s) * (dv[1]->st[1] - t) - (dv[1]->st[0] - s) * (dv[0]->st[1] - t)) / bb; + + dv[i]->tangent[0] = bary[0] * dv[0]->xyz[0] + bary[1] * dv[1]->xyz[0] + bary[2] * dv[2]->xyz[0]; + dv[i]->tangent[1] = bary[0] * dv[0]->xyz[1] + bary[1] * dv[1]->xyz[1] + bary[2] * dv[2]->xyz[1]; + dv[i]->tangent[2] = bary[0] * dv[0]->xyz[2] + bary[1] * dv[1]->xyz[2] + bary[2] * dv[2]->xyz[2]; + + VectorSubtract(dv[i]->tangent, dv[i]->xyz, dv[i]->tangent); + VectorNormalize(dv[i]->tangent); + + // calculate t tangent vector + s = dv[i]->st[0]; + t = dv[i]->st[1] + 10.0f; + bary[0] = ((dv[1]->st[0] - s) * (dv[2]->st[1] - t) - (dv[2]->st[0] - s) * (dv[1]->st[1] - t)) / bb; + bary[1] = ((dv[2]->st[0] - s) * (dv[0]->st[1] - t) - (dv[0]->st[0] - s) * (dv[2]->st[1] - t)) / bb; + bary[2] = ((dv[0]->st[0] - s) * (dv[1]->st[1] - t) - (dv[1]->st[0] - s) * (dv[0]->st[1] - t)) / bb; + + dv[i]->bitangent[0] = bary[0] * dv[0]->xyz[0] + bary[1] * dv[1]->xyz[0] + bary[2] * dv[2]->xyz[0]; + dv[i]->bitangent[1] = bary[0] * dv[0]->xyz[1] + bary[1] * dv[1]->xyz[1] + bary[2] * dv[2]->xyz[1]; + dv[i]->bitangent[2] = bary[0] * dv[0]->xyz[2] + bary[1] * dv[1]->xyz[2] + bary[2] * dv[2]->xyz[2]; + + VectorSubtract(dv[i]->bitangent, dv[i]->xyz, dv[i]->bitangent); + VectorNormalize(dv[i]->bitangent); + + // debug code + //% Sys_FPrintf( SYS_VRB, "%d S: (%f %f %f) T: (%f %f %f)\n", i, + //% stv[ i ][ 0 ], stv[ i ][ 1 ], stv[ i ][ 2 ], ttv[ i ][ 0 ], ttv[ i ][ 1 ], ttv[ i ][ 2 ] ); + } + + return qtrue; +} +#endif + + +/* +================= +R_FindSurfaceTriangleWithEdge +Tr3B - recoded from Q2E +================= +*/ +static int R_FindSurfaceTriangleWithEdge(int numTriangles, srfTriangle_t * triangles, int start, int end, int ignore) +{ + srfTriangle_t *tri; + int count, match; + int i; + + count = 0; + match = -1; + + for(i = 0, tri = triangles; i < numTriangles; i++, tri++) + { + if((tri->indexes[0] == start && tri->indexes[1] == end) || + (tri->indexes[1] == start && tri->indexes[2] == end) || (tri->indexes[2] == start && tri->indexes[0] == end)) + { + if(i != ignore) + { + match = i; + } + + count++; + } + else if((tri->indexes[1] == start && tri->indexes[0] == end) || + (tri->indexes[2] == start && tri->indexes[1] == end) || (tri->indexes[0] == start && tri->indexes[2] == end)) + { + count++; + } + } + + // detect edges shared by three triangles and make them seams + if(count > 2) + { + match = -1; + } + + return match; +} + + +/* +================= +R_CalcSurfaceTriangleNeighbors +Tr3B - recoded from Q2E +================= +*/ +void R_CalcSurfaceTriangleNeighbors(int numTriangles, srfTriangle_t * triangles) +{ + int i; + srfTriangle_t *tri; + + for(i = 0, tri = triangles; i < numTriangles; i++, tri++) + { + tri->neighbors[0] = R_FindSurfaceTriangleWithEdge(numTriangles, triangles, tri->indexes[1], tri->indexes[0], i); + tri->neighbors[1] = R_FindSurfaceTriangleWithEdge(numTriangles, triangles, tri->indexes[2], tri->indexes[1], i); + tri->neighbors[2] = R_FindSurfaceTriangleWithEdge(numTriangles, triangles, tri->indexes[0], tri->indexes[2], i); + } +} + +/* +================= +R_CalcSurfaceTrianglePlanes +================= +*/ +void R_CalcSurfaceTrianglePlanes(int numTriangles, srfTriangle_t * triangles, srfVert_t * verts) +{ + int i; + srfTriangle_t *tri; + + for(i = 0, tri = triangles; i < numTriangles; i++, tri++) + { + float *v1, *v2, *v3; + vec3_t d1, d2; + + v1 = verts[tri->indexes[0]].xyz; + v2 = verts[tri->indexes[1]].xyz; + v3 = verts[tri->indexes[2]].xyz; + + VectorSubtract(v2, v1, d1); + VectorSubtract(v3, v1, d2); + + CrossProduct(d2, d1, tri->plane); + tri->plane[3] = DotProduct(tri->plane, v1); + } +} + + +/* +================= +R_CullLocalBox + +Returns CULL_IN, CULL_CLIP, or CULL_OUT +================= +*/ +int R_CullLocalBox(vec3_t localBounds[2]) { +#if 0 + int i, j; + vec3_t transformed[8]; + float dists[8]; + vec3_t v; + cplane_t *frust; + int anyBack; + int front, back; + + if ( r_nocull->integer ) { + return CULL_CLIP; + } + + // transform into world space + for (i = 0 ; i < 8 ; i++) { + v[0] = bounds[i&1][0]; + v[1] = bounds[(i>>1)&1][1]; + v[2] = bounds[(i>>2)&1][2]; + + VectorCopy( tr.or.origin, transformed[i] ); + VectorMA( transformed[i], v[0], tr.or.axis[0], transformed[i] ); + VectorMA( transformed[i], v[1], tr.or.axis[1], transformed[i] ); + VectorMA( transformed[i], v[2], tr.or.axis[2], transformed[i] ); + } + + // check against frustum planes + anyBack = 0; + for (i = 0 ; i < 4 ; i++) { + frust = &tr.viewParms.frustum[i]; + + front = back = 0; + for (j = 0 ; j < 8 ; j++) { + dists[j] = DotProduct(transformed[j], frust->normal); + if ( dists[j] > frust->dist ) { + front = 1; + if ( back ) { + break; // a point is in front + } + } else { + back = 1; + } + } + if ( !front ) { + // all points were behind one of the planes + return CULL_OUT; + } + anyBack |= back; + } + + if ( !anyBack ) { + return CULL_IN; // completely inside frustum + } + + return CULL_CLIP; // partially clipped +#else + int j; + vec3_t transformed; + vec3_t v; + vec3_t worldBounds[2]; + + if(r_nocull->integer) + { + return CULL_CLIP; + } + + // transform into world space + ClearBounds(worldBounds[0], worldBounds[1]); + + for(j = 0; j < 8; j++) + { + v[0] = localBounds[j & 1][0]; + v[1] = localBounds[(j >> 1) & 1][1]; + v[2] = localBounds[(j >> 2) & 1][2]; + + R_LocalPointToWorld(v, transformed); + + AddPointToBounds(transformed, worldBounds[0], worldBounds[1]); + } + + return R_CullBox(worldBounds); +#endif +} + +/* +================= +R_CullBox + +Returns CULL_IN, CULL_CLIP, or CULL_OUT +================= +*/ +int R_CullBox(vec3_t worldBounds[2]) { + int i; + cplane_t *frust; + qboolean anyClip; + int r; + + // check against frustum planes + anyClip = qfalse; + for(i = 0; i < 4 /*FRUSTUM_PLANES*/; i++) + { + frust = &tr.viewParms.frustum[i]; + + r = BoxOnPlaneSide(worldBounds[0], worldBounds[1], frust); + + if(r == 2) + { + // completely outside frustum + return CULL_OUT; + } + if(r == 3) + { + anyClip = qtrue; + } + } + + if(!anyClip) + { + // completely inside frustum + return CULL_IN; + } + + // partially clipped + return CULL_CLIP; +} + +/* +** R_CullLocalPointAndRadius +*/ +int R_CullLocalPointAndRadius( const vec3_t pt, float radius ) +{ + vec3_t transformed; + + R_LocalPointToWorld( pt, transformed ); + + return R_CullPointAndRadius( transformed, radius ); +} + +/* +** R_CullPointAndRadius +*/ +int R_CullPointAndRadiusEx( const vec3_t pt, float radius, const cplane_t* frustum, int numPlanes ) +{ + int i; + float dist; + const cplane_t *frust; + qboolean mightBeClipped = qfalse; + + if ( r_nocull->integer ) { + return CULL_CLIP; + } + + // check against frustum planes + for (i = 0 ; i < numPlanes ; i++) + { + frust = &frustum[i]; + + dist = DotProduct( pt, frust->normal) - frust->dist; + if ( dist < -radius ) + { + return CULL_OUT; + } + else if ( dist <= radius ) + { + mightBeClipped = qtrue; + } + } + + if ( mightBeClipped ) + { + return CULL_CLIP; + } + + return CULL_IN; // completely inside frustum +} + +/* +** R_CullPointAndRadius +*/ +int R_CullPointAndRadius( const vec3_t pt, float radius ) +{ + return R_CullPointAndRadiusEx(pt, radius, tr.viewParms.frustum, ARRAY_LEN(tr.viewParms.frustum)); +} + +/* +================= +R_LocalNormalToWorld + +================= +*/ +void R_LocalNormalToWorld (const vec3_t local, vec3_t world) { + world[0] = local[0] * tr.or.axis[0][0] + local[1] * tr.or.axis[1][0] + local[2] * tr.or.axis[2][0]; + world[1] = local[0] * tr.or.axis[0][1] + local[1] * tr.or.axis[1][1] + local[2] * tr.or.axis[2][1]; + world[2] = local[0] * tr.or.axis[0][2] + local[1] * tr.or.axis[1][2] + local[2] * tr.or.axis[2][2]; +} + +/* +================= +R_LocalPointToWorld + +================= +*/ +void R_LocalPointToWorld (const vec3_t local, vec3_t world) { + world[0] = local[0] * tr.or.axis[0][0] + local[1] * tr.or.axis[1][0] + local[2] * tr.or.axis[2][0] + tr.or.origin[0]; + world[1] = local[0] * tr.or.axis[0][1] + local[1] * tr.or.axis[1][1] + local[2] * tr.or.axis[2][1] + tr.or.origin[1]; + world[2] = local[0] * tr.or.axis[0][2] + local[1] * tr.or.axis[1][2] + local[2] * tr.or.axis[2][2] + tr.or.origin[2]; +} + +/* +================= +R_WorldToLocal + +================= +*/ +void R_WorldToLocal (const vec3_t world, vec3_t local) { + local[0] = DotProduct(world, tr.or.axis[0]); + local[1] = DotProduct(world, tr.or.axis[1]); + local[2] = DotProduct(world, tr.or.axis[2]); +} + +/* +========================== +R_TransformModelToClip + +========================== +*/ +void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix, + vec4_t eye, vec4_t dst ) { + int i; + + for ( i = 0 ; i < 4 ; i++ ) { + eye[i] = + src[0] * modelMatrix[ i + 0 * 4 ] + + src[1] * modelMatrix[ i + 1 * 4 ] + + src[2] * modelMatrix[ i + 2 * 4 ] + + 1 * modelMatrix[ i + 3 * 4 ]; + } + + for ( i = 0 ; i < 4 ; i++ ) { + dst[i] = + eye[0] * projectionMatrix[ i + 0 * 4 ] + + eye[1] * projectionMatrix[ i + 1 * 4 ] + + eye[2] * projectionMatrix[ i + 2 * 4 ] + + eye[3] * projectionMatrix[ i + 3 * 4 ]; + } +} + +/* +========================== +R_TransformClipToWindow + +========================== +*/ +void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ) { + normalized[0] = clip[0] / clip[3]; + normalized[1] = clip[1] / clip[3]; + normalized[2] = ( clip[2] + clip[3] ) / ( 2 * clip[3] ); + + window[0] = 0.5f * ( 1.0f + normalized[0] ) * view->viewportWidth; + window[1] = 0.5f * ( 1.0f + normalized[1] ) * view->viewportHeight; + window[2] = normalized[2]; + + window[0] = (int) ( window[0] + 0.5 ); + window[1] = (int) ( window[1] + 0.5 ); +} + + +/* +========================== +myGlMultMatrix + +========================== +*/ +void myGlMultMatrix( const float *a, const float *b, float *out ) { + int i, j; + + for ( i = 0 ; i < 4 ; i++ ) { + for ( j = 0 ; j < 4 ; j++ ) { + out[ i * 4 + j ] = + a [ i * 4 + 0 ] * b [ 0 * 4 + j ] + + a [ i * 4 + 1 ] * b [ 1 * 4 + j ] + + a [ i * 4 + 2 ] * b [ 2 * 4 + j ] + + a [ i * 4 + 3 ] * b [ 3 * 4 + j ]; + } + } +} + +/* +================= +R_RotateForEntity + +Generates an orientation for an entity and viewParms +Does NOT produce any GL calls +Called by both the front end and the back end +================= +*/ +void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, + orientationr_t *or ) { + float glMatrix[16]; + vec3_t delta; + float axisLength; + + if ( ent->e.reType != RT_MODEL ) { + *or = viewParms->world; + return; + } + + VectorCopy( ent->e.origin, or->origin ); + + VectorCopy( ent->e.axis[0], or->axis[0] ); + VectorCopy( ent->e.axis[1], or->axis[1] ); + VectorCopy( ent->e.axis[2], or->axis[2] ); + + glMatrix[0] = or->axis[0][0]; + glMatrix[4] = or->axis[1][0]; + glMatrix[8] = or->axis[2][0]; + glMatrix[12] = or->origin[0]; + + glMatrix[1] = or->axis[0][1]; + glMatrix[5] = or->axis[1][1]; + glMatrix[9] = or->axis[2][1]; + glMatrix[13] = or->origin[1]; + + glMatrix[2] = or->axis[0][2]; + glMatrix[6] = or->axis[1][2]; + glMatrix[10] = or->axis[2][2]; + glMatrix[14] = or->origin[2]; + + glMatrix[3] = 0; + glMatrix[7] = 0; + glMatrix[11] = 0; + glMatrix[15] = 1; + + Matrix16Copy(glMatrix, or->transformMatrix); + myGlMultMatrix( glMatrix, viewParms->world.modelMatrix, or->modelMatrix ); + + // calculate the viewer origin in the model's space + // needed for fog, specular, and environment mapping + VectorSubtract( viewParms->or.origin, or->origin, delta ); + + // compensate for scale in the axes if necessary + if ( ent->e.nonNormalizedAxes ) { + axisLength = VectorLength( ent->e.axis[0] ); + if ( !axisLength ) { + axisLength = 0; + } else { + axisLength = 1.0f / axisLength; + } + } else { + axisLength = 1.0f; + } + + or->viewOrigin[0] = DotProduct( delta, or->axis[0] ) * axisLength; + or->viewOrigin[1] = DotProduct( delta, or->axis[1] ) * axisLength; + or->viewOrigin[2] = DotProduct( delta, or->axis[2] ) * axisLength; +} + +/* +================= +R_RotateForViewer + +Sets up the modelview matrix for a given viewParm +================= +*/ +void R_RotateForViewer (void) +{ + float viewerMatrix[16]; + vec3_t origin; + + Com_Memset (&tr.or, 0, sizeof(tr.or)); + tr.or.axis[0][0] = 1; + tr.or.axis[1][1] = 1; + tr.or.axis[2][2] = 1; + VectorCopy (tr.viewParms.or.origin, tr.or.viewOrigin); + + // transform by the camera placement + VectorCopy( tr.viewParms.or.origin, origin ); + + viewerMatrix[0] = tr.viewParms.or.axis[0][0]; + viewerMatrix[4] = tr.viewParms.or.axis[0][1]; + viewerMatrix[8] = tr.viewParms.or.axis[0][2]; + viewerMatrix[12] = -origin[0] * viewerMatrix[0] + -origin[1] * viewerMatrix[4] + -origin[2] * viewerMatrix[8]; + + viewerMatrix[1] = tr.viewParms.or.axis[1][0]; + viewerMatrix[5] = tr.viewParms.or.axis[1][1]; + viewerMatrix[9] = tr.viewParms.or.axis[1][2]; + viewerMatrix[13] = -origin[0] * viewerMatrix[1] + -origin[1] * viewerMatrix[5] + -origin[2] * viewerMatrix[9]; + + viewerMatrix[2] = tr.viewParms.or.axis[2][0]; + viewerMatrix[6] = tr.viewParms.or.axis[2][1]; + viewerMatrix[10] = tr.viewParms.or.axis[2][2]; + viewerMatrix[14] = -origin[0] * viewerMatrix[2] + -origin[1] * viewerMatrix[6] + -origin[2] * viewerMatrix[10]; + + viewerMatrix[3] = 0; + viewerMatrix[7] = 0; + viewerMatrix[11] = 0; + viewerMatrix[15] = 1; + + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + myGlMultMatrix( viewerMatrix, s_flipMatrix, tr.or.modelMatrix ); + + tr.viewParms.world = tr.or; + +} + +/* +** SetFarClip +*/ +static void R_SetFarClip( void ) +{ + float farthestCornerDistance = 0; + int i; + + // if not rendering the world (icons, menus, etc) + // set a 2k far clip plane + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + tr.viewParms.zFar = 2048; + return; + } + + // + // set far clipping planes dynamically + // + farthestCornerDistance = 0; + for ( i = 0; i < 8; i++ ) + { + vec3_t v; + vec3_t vecTo; + float distance; + + if ( i & 1 ) + { + v[0] = tr.viewParms.visBounds[0][0]; + } + else + { + v[0] = tr.viewParms.visBounds[1][0]; + } + + if ( i & 2 ) + { + v[1] = tr.viewParms.visBounds[0][1]; + } + else + { + v[1] = tr.viewParms.visBounds[1][1]; + } + + if ( i & 4 ) + { + v[2] = tr.viewParms.visBounds[0][2]; + } + else + { + v[2] = tr.viewParms.visBounds[1][2]; + } + + VectorSubtract( v, tr.viewParms.or.origin, vecTo ); + + distance = vecTo[0] * vecTo[0] + vecTo[1] * vecTo[1] + vecTo[2] * vecTo[2]; + + if ( distance > farthestCornerDistance ) + { + farthestCornerDistance = distance; + } + } + tr.viewParms.zFar = sqrt( farthestCornerDistance ); +} + +/* +================= +R_SetupFrustum + +Set up the culling frustum planes for the current view using the results we got from computing the first two rows of +the projection matrix. +================= +*/ +void R_SetupFrustum (viewParms_t *dest, float xmin, float xmax, float ymax, float zProj, float zFar, float stereoSep) +{ + vec3_t ofsorigin; + float oppleg, adjleg, length; + int i; + + if(stereoSep == 0 && xmin == -xmax) + { + // symmetric case can be simplified + VectorCopy(dest->or.origin, ofsorigin); + + length = sqrt(xmax * xmax + zProj * zProj); + oppleg = xmax / length; + adjleg = zProj / length; + + VectorScale(dest->or.axis[0], oppleg, dest->frustum[0].normal); + VectorMA(dest->frustum[0].normal, adjleg, dest->or.axis[1], dest->frustum[0].normal); + + VectorScale(dest->or.axis[0], oppleg, dest->frustum[1].normal); + VectorMA(dest->frustum[1].normal, -adjleg, dest->or.axis[1], dest->frustum[1].normal); + } + else + { + // In stereo rendering, due to the modification of the projection matrix, dest->or.origin is not the + // actual origin that we're rendering so offset the tip of the view pyramid. + VectorMA(dest->or.origin, stereoSep, dest->or.axis[1], ofsorigin); + + oppleg = xmax + stereoSep; + length = sqrt(oppleg * oppleg + zProj * zProj); + VectorScale(dest->or.axis[0], oppleg / length, dest->frustum[0].normal); + VectorMA(dest->frustum[0].normal, zProj / length, dest->or.axis[1], dest->frustum[0].normal); + + oppleg = xmin + stereoSep; + length = sqrt(oppleg * oppleg + zProj * zProj); + VectorScale(dest->or.axis[0], -oppleg / length, dest->frustum[1].normal); + VectorMA(dest->frustum[1].normal, -zProj / length, dest->or.axis[1], dest->frustum[1].normal); + } + + length = sqrt(ymax * ymax + zProj * zProj); + oppleg = ymax / length; + adjleg = zProj / length; + + VectorScale(dest->or.axis[0], oppleg, dest->frustum[2].normal); + VectorMA(dest->frustum[2].normal, adjleg, dest->or.axis[2], dest->frustum[2].normal); + + VectorScale(dest->or.axis[0], oppleg, dest->frustum[3].normal); + VectorMA(dest->frustum[3].normal, -adjleg, dest->or.axis[2], dest->frustum[3].normal); + + for (i=0 ; i<4 ; i++) { + dest->frustum[i].type = PLANE_NON_AXIAL; + dest->frustum[i].dist = DotProduct (ofsorigin, dest->frustum[i].normal); + SetPlaneSignbits( &dest->frustum[i] ); + } + + if (zFar != 0.0f) + { + vec3_t farpoint; + + VectorMA(ofsorigin, zFar, dest->or.axis[0], farpoint); + VectorScale(dest->or.axis[0], -1.0f, dest->frustum[4].normal); + + dest->frustum[4].type = PLANE_NON_AXIAL; + dest->frustum[4].dist = DotProduct (farpoint, dest->frustum[4].normal); + SetPlaneSignbits( &dest->frustum[4] ); + } +} + +/* +=============== +R_SetupProjection +=============== +*/ +void R_SetupProjection(viewParms_t *dest, float zProj, float zFar, qboolean computeFrustum) +{ + float xmin, xmax, ymin, ymax; + float width, height, stereoSep = r_stereoSeparation->value; + + /* + * offset the view origin of the viewer for stereo rendering + * by setting the projection matrix appropriately. + */ + + if(stereoSep != 0) + { + if(dest->stereoFrame == STEREO_LEFT) + stereoSep = zProj / stereoSep; + else if(dest->stereoFrame == STEREO_RIGHT) + stereoSep = zProj / -stereoSep; + else + stereoSep = 0; + } + + ymax = zProj * tan(dest->fovY * M_PI / 360.0f); + ymin = -ymax; + + xmax = zProj * tan(dest->fovX * M_PI / 360.0f); + xmin = -xmax; + + width = xmax - xmin; + height = ymax - ymin; + + dest->projectionMatrix[0] = 2 * zProj / width; + dest->projectionMatrix[4] = 0; + dest->projectionMatrix[8] = (xmax + xmin + 2 * stereoSep) / width; + dest->projectionMatrix[12] = 2 * zProj * stereoSep / width; + + dest->projectionMatrix[1] = 0; + dest->projectionMatrix[5] = 2 * zProj / height; + dest->projectionMatrix[9] = ( ymax + ymin ) / height; // normally 0 + dest->projectionMatrix[13] = 0; + + dest->projectionMatrix[3] = 0; + dest->projectionMatrix[7] = 0; + dest->projectionMatrix[11] = -1; + dest->projectionMatrix[15] = 0; + + // Now that we have all the data for the projection matrix we can also setup the view frustum. + if(computeFrustum) + R_SetupFrustum(dest, xmin, xmax, ymax, zProj, zFar, stereoSep); +} + +/* +=============== +R_SetupProjectionZ + +Sets the z-component transformation part in the projection matrix +=============== +*/ +void R_SetupProjectionZ(viewParms_t *dest) +{ + float zNear, zFar, depth; + + zNear = r_znear->value; + zFar = dest->zFar; + + depth = zFar - zNear; + + dest->projectionMatrix[2] = 0; + dest->projectionMatrix[6] = 0; + dest->projectionMatrix[10] = -( zFar + zNear ) / depth; + dest->projectionMatrix[14] = -2 * zFar * zNear / depth; + + if (dest->isPortal) + { + float plane[4]; + float plane2[4]; + vec4_t q, c; + + // transform portal plane into camera space + plane[0] = dest->portalPlane.normal[0]; + plane[1] = dest->portalPlane.normal[1]; + plane[2] = dest->portalPlane.normal[2]; + plane[3] = dest->portalPlane.dist; + + plane2[0] = -DotProduct (dest->or.axis[1], plane); + plane2[1] = DotProduct (dest->or.axis[2], plane); + plane2[2] = -DotProduct (dest->or.axis[0], plane); + plane2[3] = DotProduct (plane, dest->or.origin) - plane[3]; + + // Lengyel, Eric. “Modifying the Projection Matrix to Perform Oblique Near-plane Clipping”. + // Terathon Software 3D Graphics Library, 2004. http://www.terathon.com/code/oblique.html + q[0] = (SGN(plane2[0]) + dest->projectionMatrix[8]) / dest->projectionMatrix[0]; + q[1] = (SGN(plane2[1]) + dest->projectionMatrix[9]) / dest->projectionMatrix[5]; + q[2] = -1.0f; + q[3] = (1.0f + dest->projectionMatrix[10]) / dest->projectionMatrix[14]; + + VectorScale4(plane2, 2.0f / DotProduct4(plane2, q), c); + + dest->projectionMatrix[2] = c[0]; + dest->projectionMatrix[6] = c[1]; + dest->projectionMatrix[10] = c[2] + 1.0f; + dest->projectionMatrix[14] = c[3]; + + } + +} + +/* +=============== +R_SetupProjectionOrtho +=============== +*/ +void R_SetupProjectionOrtho(viewParms_t *dest, vec3_t viewBounds[2]) +{ + float xmin, xmax, ymin, ymax, znear, zfar; + //viewParms_t *dest = &tr.viewParms; + int i; + vec3_t pop; + + // Quake3: Projection: + // + // Z X Y Z + // | / | / + // |/ |/ + // Y--+ +--X + + xmin = viewBounds[0][1]; + xmax = viewBounds[1][1]; + ymin = -viewBounds[1][2]; + ymax = -viewBounds[0][2]; + znear = viewBounds[0][0]; + zfar = viewBounds[1][0]; + + dest->projectionMatrix[0] = 2 / (xmax - xmin); + dest->projectionMatrix[4] = 0; + dest->projectionMatrix[8] = 0; + dest->projectionMatrix[12] = (xmax + xmin) / (xmax - xmin); + + dest->projectionMatrix[1] = 0; + dest->projectionMatrix[5] = 2 / (ymax - ymin); + dest->projectionMatrix[9] = 0; + dest->projectionMatrix[13] = (ymax + ymin) / (ymax - ymin); + + dest->projectionMatrix[2] = 0; + dest->projectionMatrix[6] = 0; + dest->projectionMatrix[10] = -2 / (zfar - znear); + dest->projectionMatrix[14] = -(zfar + znear) / (zfar - znear); + + dest->projectionMatrix[3] = 0; + dest->projectionMatrix[7] = 0; + dest->projectionMatrix[11] = 0; + dest->projectionMatrix[15] = 1; + + VectorScale(dest->or.axis[1], 1.0f, dest->frustum[0].normal); + VectorMA(dest->or.origin, viewBounds[0][1], dest->frustum[0].normal, pop); + dest->frustum[0].dist = DotProduct(pop, dest->frustum[0].normal); + + VectorScale(dest->or.axis[1], -1.0f, dest->frustum[1].normal); + VectorMA(dest->or.origin, -viewBounds[1][1], dest->frustum[1].normal, pop); + dest->frustum[1].dist = DotProduct(pop, dest->frustum[1].normal); + + VectorScale(dest->or.axis[2], 1.0f, dest->frustum[2].normal); + VectorMA(dest->or.origin, viewBounds[0][2], dest->frustum[2].normal, pop); + dest->frustum[2].dist = DotProduct(pop, dest->frustum[2].normal); + + VectorScale(dest->or.axis[2], -1.0f, dest->frustum[3].normal); + VectorMA(dest->or.origin, -viewBounds[1][2], dest->frustum[3].normal, pop); + dest->frustum[3].dist = DotProduct(pop, dest->frustum[3].normal); + + VectorScale(dest->or.axis[0], -1.0f, dest->frustum[4].normal); + VectorMA(dest->or.origin, -viewBounds[1][0], dest->frustum[4].normal, pop); + dest->frustum[4].dist = DotProduct(pop, dest->frustum[4].normal); + + for (i = 0; i < 5; i++) + { + dest->frustum[i].type = PLANE_NON_AXIAL; + SetPlaneSignbits (&dest->frustum[i]); + } +} + +/* +================= +R_MirrorPoint +================= +*/ +void R_MirrorPoint (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) { + int i; + vec3_t local; + vec3_t transformed; + float d; + + VectorSubtract( in, surface->origin, local ); + + VectorClear( transformed ); + for ( i = 0 ; i < 3 ; i++ ) { + d = DotProduct(local, surface->axis[i]); + VectorMA( transformed, d, camera->axis[i], transformed ); + } + + VectorAdd( transformed, camera->origin, out ); +} + +void R_MirrorVector (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) { + int i; + float d; + + VectorClear( out ); + for ( i = 0 ; i < 3 ; i++ ) { + d = DotProduct(in, surface->axis[i]); + VectorMA( out, d, camera->axis[i], out ); + } +} + + +/* +============= +R_PlaneForSurface +============= +*/ +void R_PlaneForSurface (surfaceType_t *surfType, cplane_t *plane) { + srfTriangles_t *tri; + srfPoly_t *poly; + srfVert_t *v1, *v2, *v3; + vec4_t plane4; + + if (!surfType) { + Com_Memset (plane, 0, sizeof(*plane)); + plane->normal[0] = 1; + return; + } + switch (*surfType) { + case SF_FACE: + *plane = ((srfSurfaceFace_t *)surfType)->plane; + return; + case SF_TRIANGLES: + tri = (srfTriangles_t *)surfType; + v1 = tri->verts + tri->triangles[0].indexes[0]; + v2 = tri->verts + tri->triangles[0].indexes[1]; + v3 = tri->verts + tri->triangles[0].indexes[2]; + PlaneFromPoints( plane4, v1->xyz, v2->xyz, v3->xyz ); + VectorCopy( plane4, plane->normal ); + plane->dist = plane4[3]; + return; + case SF_POLY: + poly = (srfPoly_t *)surfType; + PlaneFromPoints( plane4, poly->verts[0].xyz, poly->verts[1].xyz, poly->verts[2].xyz ); + VectorCopy( plane4, plane->normal ); + plane->dist = plane4[3]; + return; + default: + Com_Memset (plane, 0, sizeof(*plane)); + plane->normal[0] = 1; + return; + } +} + +/* +================= +R_GetPortalOrientation + +entityNum is the entity that the portal surface is a part of, which may +be moving and rotating. + +Returns qtrue if it should be mirrored +================= +*/ +qboolean R_GetPortalOrientations( drawSurf_t *drawSurf, int entityNum, + orientation_t *surface, orientation_t *camera, + vec3_t pvsOrigin, qboolean *mirror ) { + int i; + cplane_t originalPlane, plane; + trRefEntity_t *e; + float d; + vec3_t transformed; + + // create plane axis for the portal we are seeing + R_PlaneForSurface( drawSurf->surface, &originalPlane ); + + // rotate the plane if necessary + if ( entityNum != REFENTITYNUM_WORLD ) { + tr.currentEntityNum = entityNum; + tr.currentEntity = &tr.refdef.entities[entityNum]; + + // get the orientation of the entity + R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.or ); + + // rotate the plane, but keep the non-rotated version for matching + // against the portalSurface entities + R_LocalNormalToWorld( originalPlane.normal, plane.normal ); + plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.or.origin ); + + // translate the original plane + originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.or.origin ); + } else { + plane = originalPlane; + } + + VectorCopy( plane.normal, surface->axis[0] ); + PerpendicularVector( surface->axis[1], surface->axis[0] ); + CrossProduct( surface->axis[0], surface->axis[1], surface->axis[2] ); + + // locate the portal entity closest to this plane. + // origin will be the origin of the portal, origin2 will be + // the origin of the camera + for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) { + e = &tr.refdef.entities[i]; + if ( e->e.reType != RT_PORTALSURFACE ) { + continue; + } + + d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist; + if ( d > 64 || d < -64) { + continue; + } + + // get the pvsOrigin from the entity + VectorCopy( e->e.oldorigin, pvsOrigin ); + + // if the entity is just a mirror, don't use as a camera point + if ( e->e.oldorigin[0] == e->e.origin[0] && + e->e.oldorigin[1] == e->e.origin[1] && + e->e.oldorigin[2] == e->e.origin[2] ) { + VectorScale( plane.normal, plane.dist, surface->origin ); + VectorCopy( surface->origin, camera->origin ); + VectorSubtract( vec3_origin, surface->axis[0], camera->axis[0] ); + VectorCopy( surface->axis[1], camera->axis[1] ); + VectorCopy( surface->axis[2], camera->axis[2] ); + + *mirror = qtrue; + return qtrue; + } + + // project the origin onto the surface plane to get + // an origin point we can rotate around + d = DotProduct( e->e.origin, plane.normal ) - plane.dist; + VectorMA( e->e.origin, -d, surface->axis[0], surface->origin ); + + // now get the camera origin and orientation + VectorCopy( e->e.oldorigin, camera->origin ); + AxisCopy( e->e.axis, camera->axis ); + VectorSubtract( vec3_origin, camera->axis[0], camera->axis[0] ); + VectorSubtract( vec3_origin, camera->axis[1], camera->axis[1] ); + + // optionally rotate + if ( e->e.oldframe ) { + // if a speed is specified + if ( e->e.frame ) { + // continuous rotate + d = (tr.refdef.time/1000.0f) * e->e.frame; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } else { + // bobbing rotate, with skinNum being the rotation offset + d = sin( tr.refdef.time * 0.003f ); + d = e->e.skinNum + d * 4; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } + } + else if ( e->e.skinNum ) { + d = e->e.skinNum; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } + *mirror = qfalse; + return qtrue; + } + + // if we didn't locate a portal entity, don't render anything. + // We don't want to just treat it as a mirror, because without a + // portal entity the server won't have communicated a proper entity set + // in the snapshot + + // unfortunately, with local movement prediction it is easily possible + // to see a surface before the server has communicated the matching + // portal surface entity, so we don't want to print anything here... + + //ri.Printf( PRINT_ALL, "Portal surface without a portal entity\n" ); + + return qfalse; +} + +static qboolean IsMirror( const drawSurf_t *drawSurf, int entityNum ) +{ + int i; + cplane_t originalPlane, plane; + trRefEntity_t *e; + float d; + + // create plane axis for the portal we are seeing + R_PlaneForSurface( drawSurf->surface, &originalPlane ); + + // rotate the plane if necessary + if ( entityNum != REFENTITYNUM_WORLD ) + { + tr.currentEntityNum = entityNum; + tr.currentEntity = &tr.refdef.entities[entityNum]; + + // get the orientation of the entity + R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.or ); + + // rotate the plane, but keep the non-rotated version for matching + // against the portalSurface entities + R_LocalNormalToWorld( originalPlane.normal, plane.normal ); + plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.or.origin ); + + // translate the original plane + originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.or.origin ); + } + else + { + plane = originalPlane; + } + + // locate the portal entity closest to this plane. + // origin will be the origin of the portal, origin2 will be + // the origin of the camera + for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) + { + e = &tr.refdef.entities[i]; + if ( e->e.reType != RT_PORTALSURFACE ) { + continue; + } + + d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist; + if ( d > 64 || d < -64) { + continue; + } + + // if the entity is just a mirror, don't use as a camera point + if ( e->e.oldorigin[0] == e->e.origin[0] && + e->e.oldorigin[1] == e->e.origin[1] && + e->e.oldorigin[2] == e->e.origin[2] ) + { + return qtrue; + } + + return qfalse; + } + return qfalse; +} + +/* +** SurfIsOffscreen +** +** Determines if a surface is completely offscreen. +*/ +static qboolean SurfIsOffscreen( const drawSurf_t *drawSurf, vec4_t clipDest[128] ) { + float shortest = 100000000; + int entityNum; + int numTriangles; + shader_t *shader; + int fogNum; + int dlighted; + int pshadowed; + vec4_t clip, eye; + int i; + unsigned int pointOr = 0; + unsigned int pointAnd = (unsigned int)~0; + + if ( glConfig.smpActive ) { // FIXME! we can't do RB_BeginSurface/RB_EndSurface stuff with smp! + return qfalse; + } + + R_RotateForViewer(); + + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted, &pshadowed ); + RB_BeginSurface( shader, fogNum ); + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + + assert( tess.numVertexes < 128 ); + + for ( i = 0; i < tess.numVertexes; i++ ) + { + int j; + unsigned int pointFlags = 0; + + R_TransformModelToClip( tess.xyz[i], tr.or.modelMatrix, tr.viewParms.projectionMatrix, eye, clip ); + + for ( j = 0; j < 3; j++ ) + { + if ( clip[j] >= clip[3] ) + { + pointFlags |= (1 << (j*2)); + } + else if ( clip[j] <= -clip[3] ) + { + pointFlags |= ( 1 << (j*2+1)); + } + } + pointAnd &= pointFlags; + pointOr |= pointFlags; + } + + // trivially reject + if ( pointAnd ) + { + return qtrue; + } + + // determine if this surface is backfaced and also determine the distance + // to the nearest vertex so we can cull based on portal range. Culling + // based on vertex distance isn't 100% correct (we should be checking for + // range to the surface), but it's good enough for the types of portals + // we have in the game right now. + numTriangles = tess.numIndexes / 3; + + for ( i = 0; i < tess.numIndexes; i += 3 ) + { + vec3_t normal; + float len; + + VectorSubtract( tess.xyz[tess.indexes[i]], tr.viewParms.or.origin, normal ); + + len = VectorLengthSquared( normal ); // lose the sqrt + if ( len < shortest ) + { + shortest = len; + } + + if ( DotProduct( normal, tess.normal[tess.indexes[i]] ) >= 0 ) + { + numTriangles--; + } + } + if ( !numTriangles ) + { + return qtrue; + } + + // mirrors can early out at this point, since we don't do a fade over distance + // with them (although we could) + if ( IsMirror( drawSurf, entityNum ) ) + { + return qfalse; + } + + if ( shortest > (tess.shader->portalRange*tess.shader->portalRange) ) + { + return qtrue; + } + + return qfalse; +} + +/* +======================== +R_MirrorViewBySurface + +Returns qtrue if another view has been rendered +======================== +*/ +qboolean R_MirrorViewBySurface (drawSurf_t *drawSurf, int entityNum) { + vec4_t clipDest[128]; + viewParms_t newParms; + viewParms_t oldParms; + orientation_t surface, camera; + + // don't recursively mirror + if (tr.viewParms.isPortal) { + ri.Printf( PRINT_DEVELOPER, "WARNING: recursive mirror/portal found\n" ); + return qfalse; + } + + if ( r_noportals->integer || (r_fastsky->integer == 1) ) { + return qfalse; + } + + // trivially reject portal/mirror + if ( SurfIsOffscreen( drawSurf, clipDest ) ) { + return qfalse; + } + + // save old viewParms so we can return to it after the mirror view + oldParms = tr.viewParms; + + newParms = tr.viewParms; + newParms.isPortal = qtrue; + if ( !R_GetPortalOrientations( drawSurf, entityNum, &surface, &camera, + newParms.pvsOrigin, &newParms.isMirror ) ) { + return qfalse; // bad portal, no portalentity + } + + R_MirrorPoint (oldParms.or.origin, &surface, &camera, newParms.or.origin ); + + VectorSubtract( vec3_origin, camera.axis[0], newParms.portalPlane.normal ); + newParms.portalPlane.dist = DotProduct( camera.origin, newParms.portalPlane.normal ); + + R_MirrorVector (oldParms.or.axis[0], &surface, &camera, newParms.or.axis[0]); + R_MirrorVector (oldParms.or.axis[1], &surface, &camera, newParms.or.axis[1]); + R_MirrorVector (oldParms.or.axis[2], &surface, &camera, newParms.or.axis[2]); + + // OPTIMIZE: restrict the viewport on the mirrored view + + // render the mirror view + R_RenderView (&newParms); + + tr.viewParms = oldParms; + + return qtrue; +} + +/* +================= +R_SpriteFogNum + +See if a sprite is inside a fog volume +================= +*/ +int R_SpriteFogNum( trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( ent->e.origin[j] - ent->e.radius >= fog->bounds[1][j] ) { + break; + } + if ( ent->e.origin[j] + ent->e.radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +========================================================================================== + +DRAWSURF SORTING + +========================================================================================== +*/ + +/* +=============== +R_Radix +=============== +*/ +static ID_INLINE void R_Radix( int byte, int size, drawSurf_t *source, drawSurf_t *dest ) +{ + int count[ 256 ] = { 0 }; + int index[ 256 ]; + int i; + unsigned char *sortKey = NULL; + unsigned char *end = NULL; + + sortKey = ( (unsigned char *)&source[ 0 ].sort ) + byte; + end = sortKey + ( size * sizeof( drawSurf_t ) ); + for( ; sortKey < end; sortKey += sizeof( drawSurf_t ) ) + ++count[ *sortKey ]; + + index[ 0 ] = 0; + + for( i = 1; i < 256; ++i ) + index[ i ] = index[ i - 1 ] + count[ i - 1 ]; + + sortKey = ( (unsigned char *)&source[ 0 ].sort ) + byte; + for( i = 0; i < size; ++i, sortKey += sizeof( drawSurf_t ) ) + dest[ index[ *sortKey ]++ ] = source[ i ]; +} + +/* +=============== +R_RadixSort + +Radix sort with 4 byte size buckets +=============== +*/ +static void R_RadixSort( drawSurf_t *source, int size ) +{ + static drawSurf_t scratch[ MAX_DRAWSURFS ]; +#ifdef Q3_LITTLE_ENDIAN + R_Radix( 0, size, source, scratch ); + R_Radix( 1, size, scratch, source ); + R_Radix( 2, size, source, scratch ); + R_Radix( 3, size, scratch, source ); +#else + R_Radix( 3, size, source, scratch ); + R_Radix( 2, size, scratch, source ); + R_Radix( 1, size, source, scratch ); + R_Radix( 0, size, scratch, source ); +#endif //Q3_LITTLE_ENDIAN +} + +//========================================================================================== + +/* +================= +R_AddDrawSurf +================= +*/ +void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader, + int fogIndex, int dlightMap, int pshadowMap ) { + int index; + + // instead of checking for overflow, we just mask the index + // so it wraps around + index = tr.refdef.numDrawSurfs & DRAWSURF_MASK; + // the sort data is packed into a single 32 bit value so it can be + // compared quickly during the qsorting process + tr.refdef.drawSurfs[index].sort = (shader->sortedIndex << QSORT_SHADERNUM_SHIFT) + | tr.shiftedEntityNum | ( fogIndex << QSORT_FOGNUM_SHIFT ) + | ((int)pshadowMap << QSORT_PSHADOW_SHIFT) | (int)dlightMap; + tr.refdef.drawSurfs[index].surface = surface; + tr.refdef.numDrawSurfs++; +} + +/* +================= +R_DecomposeSort +================= +*/ +void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, + int *fogNum, int *dlightMap, int *pshadowMap ) { + *fogNum = ( sort >> QSORT_FOGNUM_SHIFT ) & 31; + *shader = tr.sortedShaders[ ( sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1) ]; + *entityNum = ( sort >> QSORT_REFENTITYNUM_SHIFT ) & REFENTITYNUM_MASK; + *pshadowMap = (sort & 2) >> 1; + *dlightMap = sort & 1; +} + +/* +================= +R_SortDrawSurfs +================= +*/ +void R_SortDrawSurfs( drawSurf_t *drawSurfs, int numDrawSurfs ) { + shader_t *shader; + int fogNum; + int entityNum; + int dlighted; + int pshadowed; + int i; + + //ri.Printf(PRINT_ALL, "firstDrawSurf %d numDrawSurfs %d\n", (int)(drawSurfs - tr.refdef.drawSurfs), numDrawSurfs); + + // it is possible for some views to not have any surfaces + if ( numDrawSurfs < 1 ) { + // we still need to add it for hyperspace cases + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); + return; + } + + // if we overflowed MAX_DRAWSURFS, the drawsurfs + // wrapped around in the buffer and we will be missing + // the first surfaces, not the last ones + if ( numDrawSurfs > MAX_DRAWSURFS ) { + numDrawSurfs = MAX_DRAWSURFS; + } + + // sort the drawsurfs by sort type, then orientation, then shader + R_RadixSort( drawSurfs, numDrawSurfs ); + + // skip pass through drawing if rendering a shadow map + if (tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW)) + { + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); + return; + } + + // check for any pass through drawing, which + // may cause another view to be rendered first + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + R_DecomposeSort( (drawSurfs+i)->sort, &entityNum, &shader, &fogNum, &dlighted, &pshadowed ); + + if ( shader->sort > SS_PORTAL ) { + break; + } + + // no shader should ever have this sort type + if ( shader->sort == SS_BAD ) { + ri.Error (ERR_DROP, "Shader '%s'with sort == SS_BAD", shader->name ); + } + + // if the mirror was completely clipped away, we may need to check another surface + if ( R_MirrorViewBySurface( (drawSurfs+i), entityNum) ) { + // this is a debug option to see exactly what is being mirrored + if ( r_portalOnly->integer ) { + return; + } + break; // only one mirror view at a time + } + } + + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); +} + +static void R_AddEntitySurface (int entityNum) +{ + trRefEntity_t *ent; + shader_t *shader; + + tr.currentEntityNum = entityNum; + + ent = tr.currentEntity = &tr.refdef.entities[tr.currentEntityNum]; + + ent->needDlights = qfalse; + + // preshift the value we are going to OR into the drawsurf sort + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_REFENTITYNUM_SHIFT; + + // + // the weapon model must be handled special -- + // we don't want the hacked weapon position showing in + // mirrors, because the true body position will already be drawn + // + if ( (ent->e.renderfx & RF_FIRST_PERSON) && (tr.viewParms.isPortal + || (tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW))) ) { + return; + } + + // simple generated models, like sprites and beams, are not culled + switch ( ent->e.reType ) { + case RT_PORTALSURFACE: + break; // don't draw anything + case RT_SPRITE: + case RT_BEAM: + case RT_LIGHTNING: + case RT_RAIL_CORE: + case RT_RAIL_RINGS: + // self blood sprites, talk balloons, etc should not be drawn in the primary + // view. We can't just do this check for all entities, because md3 + // entities may still want to cast shadows from them + if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) { + return; + } + shader = R_GetShaderByHandle( ent->e.customShader ); + R_AddDrawSurf( &entitySurface, shader, R_SpriteFogNum( ent ), 0, 0 ); + break; + + case RT_MODEL: + // we must set up parts of tr.or for model culling + R_RotateForEntity( ent, &tr.viewParms, &tr.or ); + + tr.currentModel = R_GetModelByHandle( ent->e.hModel ); + if (!tr.currentModel) { + R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0, 0 ); + } else { + switch ( tr.currentModel->type ) { + case MOD_MESH: + R_AddMD3Surfaces( ent ); + break; + case MOD_MD4: + R_AddAnimSurfaces( ent ); + break; +#ifdef RAVENMD4 + case MOD_MDR: + R_MDRAddAnimSurfaces( ent ); + break; +#endif + case MOD_IQM: + R_AddIQMSurfaces( ent ); + break; + case MOD_BRUSH: + R_AddBrushModelSurfaces( ent ); + break; + case MOD_BAD: // null model axis + if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) { + break; + } + R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0, 0 ); + break; + default: + ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad modeltype" ); + break; + } + } + break; + default: + ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad reType" ); + } +} + +/* +============= +R_AddEntitySurfaces +============= +*/ +void R_AddEntitySurfaces (void) { + int i; + + if ( !r_drawentities->integer ) { + return; + } + + for ( i = 0; i < tr.refdef.num_entities; i++) + R_AddEntitySurface(i); +} + + +/* +==================== +R_GenerateDrawSurfs +==================== +*/ +void R_GenerateDrawSurfs( void ) { + R_AddWorldSurfaces (); + + R_AddPolygonSurfaces(); + + // set the projection matrix with the minimum zfar + // now that we have the world bounded + // this needs to be done before entities are + // added, because they use the projection + // matrix for lod calculation + + // dynamically compute far clip plane distance + if (!(tr.viewParms.flags & VPF_SHADOWMAP)) + { + R_SetFarClip(); + } + + // we know the size of the clipping volume. Now set the rest of the projection matrix. + R_SetupProjectionZ (&tr.viewParms); + + R_AddEntitySurfaces (); +} + +/* +================ +R_DebugPolygon +================ +*/ +void R_DebugPolygon( int color, int numPoints, float *points ) { + // FIXME: implement this +#if 0 + int i; + + GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + // draw solid shade + + qglColor3f( color&1, (color>>1)&1, (color>>2)&1 ); + qglBegin( GL_POLYGON ); + for ( i = 0 ; i < numPoints ; i++ ) { + qglVertex3fv( points + i * 3 ); + } + qglEnd(); + + // 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(); + qglDepthRange( 0, 1 ); +#endif +} + +/* +==================== +R_DebugGraphics + +Visualization aid for movement clipping debugging +==================== +*/ +void R_DebugGraphics( void ) { + if ( !r_debugSurface->integer ) { + return; + } + + // the render thread can't make callbacks to the main thread + R_SyncRenderThread(); + + GL_Bind( tr.whiteImage); + GL_Cull( CT_FRONT_SIDED ); + ri.CM_DrawDebugSurface( R_DebugPolygon ); +} + + +/* +================ +R_RenderView + +A view may be either the actual camera view, +or a mirror / remote location +================ +*/ +void R_RenderView (viewParms_t *parms) { + int firstDrawSurf; + + if ( parms->viewportWidth <= 0 || parms->viewportHeight <= 0 ) { + return; + } + + tr.viewCount++; + + tr.viewParms = *parms; + tr.viewParms.frameSceneNum = tr.frameSceneNum; + tr.viewParms.frameCount = tr.frameCount; + + firstDrawSurf = tr.refdef.numDrawSurfs; + + tr.viewCount++; + + // set viewParms.world + R_RotateForViewer (); + + R_SetupProjection(&tr.viewParms, r_zproj->value, tr.viewParms.zFar, qtrue); + + R_GenerateDrawSurfs(); + + R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, tr.refdef.numDrawSurfs - firstDrawSurf ); + + // draw main system development information (surface outlines, etc) + R_DebugGraphics(); +} + + +void R_RenderDlightCubemaps(const refdef_t *fd) +{ + int i; + + for (i = 0; i < tr.refdef.num_dlights; i++) + { + viewParms_t shadowParms; + int j; + + // use previous frame to determine visible dlights + if ((1 << i) & tr.refdef.dlightMask) + continue; + + Com_Memset( &shadowParms, 0, sizeof( shadowParms ) ); + + shadowParms.viewportX = tr.refdef.x; + shadowParms.viewportY = glConfig.vidHeight - ( tr.refdef.y + PSHADOW_MAP_SIZE ); + shadowParms.viewportWidth = PSHADOW_MAP_SIZE; + shadowParms.viewportHeight = PSHADOW_MAP_SIZE; + shadowParms.isPortal = qfalse; + shadowParms.isMirror = qtrue; // because it is + + shadowParms.fovX = 90; + shadowParms.fovY = 90; + + shadowParms.flags = VPF_SHADOWMAP | VPF_DEPTHSHADOW; + shadowParms.zFar = tr.refdef.dlights[i].radius; + + VectorCopy( tr.refdef.dlights[i].origin, shadowParms.or.origin ); + + for (j = 0; j < 6; j++) + { + switch(j) + { + case 0: + // -X + VectorSet( shadowParms.or.axis[0], -1, 0, 0); + VectorSet( shadowParms.or.axis[1], 0, 0, -1); + VectorSet( shadowParms.or.axis[2], 0, 1, 0); + break; + case 1: + // +X + VectorSet( shadowParms.or.axis[0], 1, 0, 0); + VectorSet( shadowParms.or.axis[1], 0, 0, 1); + VectorSet( shadowParms.or.axis[2], 0, 1, 0); + break; + case 2: + // -Y + VectorSet( shadowParms.or.axis[0], 0, -1, 0); + VectorSet( shadowParms.or.axis[1], 1, 0, 0); + VectorSet( shadowParms.or.axis[2], 0, 0, -1); + break; + case 3: + // +Y + VectorSet( shadowParms.or.axis[0], 0, 1, 0); + VectorSet( shadowParms.or.axis[1], 1, 0, 0); + VectorSet( shadowParms.or.axis[2], 0, 0, 1); + break; + case 4: + // -Z + VectorSet( shadowParms.or.axis[0], 0, 0, -1); + VectorSet( shadowParms.or.axis[1], 1, 0, 0); + VectorSet( shadowParms.or.axis[2], 0, 1, 0); + break; + case 5: + // +Z + VectorSet( shadowParms.or.axis[0], 0, 0, 1); + VectorSet( shadowParms.or.axis[1], -1, 0, 0); + VectorSet( shadowParms.or.axis[2], 0, 1, 0); + break; + } + + R_RenderView(&shadowParms); + R_AddCapShadowmapCmd( i, j ); + } + } +} + + +void R_RenderPshadowMaps(const refdef_t *fd) +{ + viewParms_t shadowParms; + int i; + + // first, make a list of shadows + for ( i = 0; i < tr.refdef.num_entities; i++) + { + trRefEntity_t *ent = &tr.refdef.entities[i]; + + if((ent->e.renderfx & (RF_FIRST_PERSON | RF_NOSHADOW))) + continue; + + //if((ent->e.renderfx & RF_THIRD_PERSON)) + //continue; + + if (ent->e.reType == RT_MODEL) + { + model_t *model = R_GetModelByHandle( ent->e.hModel ); + pshadow_t shadow; + float radius = 0.0f; + float scale = 1.0f; + vec3_t diff; + int j; + + if (!model) + continue; + + if (ent->e.nonNormalizedAxes) + { + scale = VectorLength( ent->e.axis[0] ); + } + + switch (model->type) + { + case MOD_MESH: + { + mdvFrame_t *frame = &model->mdv[0]->frames[ent->e.frame]; + + radius = frame->radius * scale; + } + break; + + case MOD_MD4: + { + // FIXME: actually calculate the radius and bounds, this is a horrible hack + radius = r_pshadowDist->value / 2.0f; + } + break; +#ifdef RAVENMD4 + case MOD_MDR: + { + // FIXME: never actually tested this + mdrHeader_t *header = model->modelData; + int frameSize = (size_t)( &((mdrFrame_t *)0)->bones[ header->numBones ] ); + mdrFrame_t *frame = ( mdrFrame_t * ) ( ( byte * ) header + header->ofsFrames + frameSize * ent->e.frame); + + radius = frame->radius; + } + break; +#endif + case MOD_IQM: + { + // FIXME: never actually tested this + iqmData_t *data = model->modelData; + vec3_t diag; + float *framebounds; + + framebounds = data->bounds + 6*ent->e.frame; + VectorSubtract( framebounds+3, framebounds, diag ); + radius = 0.5f * VectorLength( diag ); + } + break; + + default: + break; + } + + if (!radius) + continue; + + // Cull entities that are behind the viewer by more than lightRadius + VectorSubtract(ent->e.origin, fd->vieworg, diff); + if (DotProduct(diff, fd->viewaxis[0]) < -r_pshadowDist->value) + continue; + + memset(&shadow, 0, sizeof(shadow)); + + shadow.numEntities = 1; + shadow.entityNums[0] = i; + shadow.viewRadius = radius; + shadow.lightRadius = r_pshadowDist->value; + VectorCopy(ent->e.origin, shadow.viewOrigin); + shadow.sort = DotProduct(diff, diff) / (radius * radius); + VectorCopy(ent->e.origin, shadow.entityOrigins[0]); + shadow.entityRadiuses[0] = radius; + + for (j = 0; j < MAX_CALC_PSHADOWS; j++) + { + pshadow_t swap; + + if (j + 1 > tr.refdef.num_pshadows) + { + tr.refdef.num_pshadows = j + 1; + tr.refdef.pshadows[j] = shadow; + break; + } + + // sort shadows by distance from camera divided by radius + // FIXME: sort better + if (tr.refdef.pshadows[j].sort <= shadow.sort) + continue; + + swap = tr.refdef.pshadows[j]; + tr.refdef.pshadows[j] = shadow; + shadow = swap; + } + } + } + + // next, merge touching pshadows + for ( i = 0; i < tr.refdef.num_pshadows; i++) + { + pshadow_t *ps1 = &tr.refdef.pshadows[i]; + int j; + + for (j = i + 1; j < tr.refdef.num_pshadows; j++) + { + pshadow_t *ps2 = &tr.refdef.pshadows[j]; + int k; + qboolean touch; + + if (ps1->numEntities == 8) + break; + + touch = qfalse; + if (SpheresIntersect(ps1->viewOrigin, ps1->viewRadius, ps2->viewOrigin, ps2->viewRadius)) + { + for (k = 0; k < ps1->numEntities; k++) + { + if (SpheresIntersect(ps1->entityOrigins[k], ps1->entityRadiuses[k], ps2->viewOrigin, ps2->viewRadius)) + { + touch = qtrue; + break; + } + } + } + + if (touch) + { + vec3_t newOrigin; + float newRadius; + + BoundingSphereOfSpheres(ps1->viewOrigin, ps1->viewRadius, ps2->viewOrigin, ps2->viewRadius, newOrigin, &newRadius); + VectorCopy(newOrigin, ps1->viewOrigin); + ps1->viewRadius = newRadius; + + ps1->entityNums[ps1->numEntities] = ps2->entityNums[0]; + VectorCopy(ps2->viewOrigin, ps1->entityOrigins[ps1->numEntities]); + ps1->entityRadiuses[ps1->numEntities] = ps2->viewRadius; + + ps1->numEntities++; + + for (k = j; k < tr.refdef.num_pshadows - 1; k++) + { + tr.refdef.pshadows[k] = tr.refdef.pshadows[k + 1]; + } + + j--; + tr.refdef.num_pshadows--; + } + } + } + + // cap number of drawn pshadows + if (tr.refdef.num_pshadows > MAX_DRAWN_PSHADOWS) + { + tr.refdef.num_pshadows = MAX_DRAWN_PSHADOWS; + } + + // next, fill up the rest of the shadow info + for ( i = 0; i < tr.refdef.num_pshadows; i++) + { + pshadow_t *shadow = &tr.refdef.pshadows[i]; + vec3_t up; + vec3_t ambientLight, directedLight, lightDir; + + VectorSet(lightDir, 0.57735f, 0.57735f, 0.57735f); +#if 1 + R_LightForPoint(shadow->viewOrigin, ambientLight, directedLight, lightDir); + + // sometimes there's no light + if (DotProduct(lightDir, lightDir) < 0.9f) + VectorSet(lightDir, 0.0f, 0.0f, 1.0f); +#endif + + if (shadow->viewRadius * 3.0f > shadow->lightRadius) + { + shadow->lightRadius = shadow->viewRadius * 3.0f; + } + + VectorMA(shadow->viewOrigin, shadow->viewRadius, lightDir, shadow->lightOrigin); + + // make up a projection, up doesn't matter + VectorScale(lightDir, -1.0f, shadow->lightViewAxis[0]); + VectorSet(up, 0, 0, -1); + + if ( abs(DotProduct(up, shadow->lightViewAxis[0])) > 0.9f ) + { + VectorSet(up, -1, 0, 0); + } + + CrossProduct(shadow->lightViewAxis[0], up, shadow->lightViewAxis[1]); + VectorNormalize(shadow->lightViewAxis[1]); + CrossProduct(shadow->lightViewAxis[0], shadow->lightViewAxis[1], shadow->lightViewAxis[2]); + + VectorCopy(shadow->lightViewAxis[0], shadow->cullPlane.normal); + shadow->cullPlane.dist = DotProduct(shadow->cullPlane.normal, shadow->lightOrigin); + shadow->cullPlane.type = PLANE_NON_AXIAL; + SetPlaneSignbits(&shadow->cullPlane); + } + + // next, render shadowmaps + for ( i = 0; i < tr.refdef.num_pshadows; i++) + { + int firstDrawSurf; + pshadow_t *shadow = &tr.refdef.pshadows[i]; + int j; + + Com_Memset( &shadowParms, 0, sizeof( shadowParms ) ); + + if (glRefConfig.framebufferObject) + { + shadowParms.viewportX = 0; + shadowParms.viewportY = 0; + } + else + { + shadowParms.viewportX = tr.refdef.x; + shadowParms.viewportY = glConfig.vidHeight - ( tr.refdef.y + PSHADOW_MAP_SIZE ); + } + shadowParms.viewportWidth = PSHADOW_MAP_SIZE; + shadowParms.viewportHeight = PSHADOW_MAP_SIZE; + shadowParms.isPortal = qfalse; + shadowParms.isMirror = qfalse; + + shadowParms.fovX = 90; + shadowParms.fovY = 90; + + if (glRefConfig.framebufferObject) + shadowParms.targetFbo = tr.pshadowFbos[i]; + + shadowParms.flags = VPF_SHADOWMAP | VPF_DEPTHSHADOW; + shadowParms.zFar = shadow->lightRadius; + + VectorCopy(shadow->lightOrigin, shadowParms.or.origin); + + VectorCopy(shadow->lightViewAxis[0], shadowParms.or.axis[0]); + VectorCopy(shadow->lightViewAxis[1], shadowParms.or.axis[1]); + VectorCopy(shadow->lightViewAxis[2], shadowParms.or.axis[2]); + + { + tr.viewCount++; + + tr.viewParms = shadowParms; + tr.viewParms.frameSceneNum = tr.frameSceneNum; + tr.viewParms.frameCount = tr.frameCount; + + firstDrawSurf = tr.refdef.numDrawSurfs; + + tr.viewCount++; + + // set viewParms.world + R_RotateForViewer (); + + { + float xmin, xmax, ymin, ymax, znear, zfar; + viewParms_t *dest = &tr.viewParms; + vec3_t pop; + + xmin = ymin = -shadow->viewRadius; + xmax = ymax = shadow->viewRadius; + znear = 0; + zfar = shadow->lightRadius; + + dest->projectionMatrix[0] = 2 / (xmax - xmin); + dest->projectionMatrix[4] = 0; + dest->projectionMatrix[8] = (xmax + xmin) / (xmax - xmin); + dest->projectionMatrix[12] =0; + + dest->projectionMatrix[1] = 0; + dest->projectionMatrix[5] = 2 / (ymax - ymin); + dest->projectionMatrix[9] = ( ymax + ymin ) / (ymax - ymin); // normally 0 + dest->projectionMatrix[13] = 0; + + dest->projectionMatrix[2] = 0; + dest->projectionMatrix[6] = 0; + dest->projectionMatrix[10] = 2 / (zfar - znear); + dest->projectionMatrix[14] = 0; + + dest->projectionMatrix[3] = 0; + dest->projectionMatrix[7] = 0; + dest->projectionMatrix[11] = 0; + dest->projectionMatrix[15] = 1; + + VectorScale(dest->or.axis[1], 1.0f, dest->frustum[0].normal); + VectorMA(dest->or.origin, -shadow->viewRadius, dest->frustum[0].normal, pop); + dest->frustum[0].dist = DotProduct(pop, dest->frustum[0].normal); + + VectorScale(dest->or.axis[1], -1.0f, dest->frustum[1].normal); + VectorMA(dest->or.origin, -shadow->viewRadius, dest->frustum[1].normal, pop); + dest->frustum[1].dist = DotProduct(pop, dest->frustum[1].normal); + + VectorScale(dest->or.axis[2], 1.0f, dest->frustum[2].normal); + VectorMA(dest->or.origin, -shadow->viewRadius, dest->frustum[2].normal, pop); + dest->frustum[2].dist = DotProduct(pop, dest->frustum[2].normal); + + VectorScale(dest->or.axis[2], -1.0f, dest->frustum[3].normal); + VectorMA(dest->or.origin, -shadow->viewRadius, dest->frustum[3].normal, pop); + dest->frustum[3].dist = DotProduct(pop, dest->frustum[3].normal); + + VectorScale(dest->or.axis[0], -1.0f, dest->frustum[4].normal); + VectorMA(dest->or.origin, -shadow->lightRadius, dest->frustum[4].normal, pop); + dest->frustum[4].dist = DotProduct(pop, dest->frustum[4].normal); + + for (j = 0; j < 5; j++) + { + dest->frustum[j].type = PLANE_NON_AXIAL; + SetPlaneSignbits (&dest->frustum[j]); + } + } + + for (j = 0; j < shadow->numEntities; j++) + { + R_AddEntitySurface(shadow->entityNums[j]); + } + + R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, tr.refdef.numDrawSurfs - firstDrawSurf ); + + if (!glRefConfig.framebufferObject) + R_AddCapShadowmapCmd( i, -1 ); + } + } +} + +static float CalcSplit(float n, float f, float i, float m) +{ + return (n * pow(f / n, i / m) + (f - n) * i / m) / 2.0f; +} + + +void R_RenderSunShadowMaps(const refdef_t *fd, int level) +{ + viewParms_t shadowParms; + vec4_t lightDir, lightCol; + vec3_t lightViewAxis[3]; + vec3_t lightOrigin; + float splitZNear, splitZFar, splitBias; + float viewZNear, viewZFar; + vec3_t lightviewBounds[2]; + qboolean lightViewIndependentOfCameraView = qfalse; + + if (r_forceSun->integer == 2) + { + int scale = 32768; + float angle = (fd->time % scale) / (float)scale * M_PI; + lightDir[0] = cos(angle); + lightDir[1] = sin(35.0f * M_PI / 180.0f); + lightDir[2] = sin(angle) * cos(35.0f * M_PI / 180.0f); + lightDir[3] = 0.0f; + + if (1) //((fd->time % (scale * 2)) < scale) + { + lightCol[0] = + lightCol[1] = + lightCol[2] = CLAMP(sin(angle) * 2.0f, 0.0f, 1.0f) * 2.0f; + lightCol[3] = 1.0f; + } + else + { + lightCol[0] = + lightCol[1] = + lightCol[2] = CLAMP(sin(angle) * 2.0f * 0.1f, 0.0f, 0.1f); + lightCol[3] = 1.0f; + } + + VectorCopy4(lightDir, tr.refdef.sunDir); + VectorCopy4(lightCol, tr.refdef.sunCol); + VectorScale4(lightCol, 0.2f, tr.refdef.sunAmbCol); + } + else + { + VectorCopy4(tr.refdef.sunDir, lightDir); + } + + viewZNear = r_shadowCascadeZNear->value; + viewZFar = r_shadowCascadeZFar->value; + splitBias = r_shadowCascadeZBias->value; + + switch(level) + { + case 0: + default: + //splitZNear = r_znear->value; + //splitZFar = 256; + splitZNear = viewZNear; + splitZFar = CalcSplit(viewZNear, viewZFar, 1, 3) + splitBias; + break; + case 1: + splitZNear = CalcSplit(viewZNear, viewZFar, 1, 3) + splitBias; + splitZFar = CalcSplit(viewZNear, viewZFar, 2, 3) + splitBias; + //splitZNear = 256; + //splitZFar = 896; + break; + case 2: + splitZNear = CalcSplit(viewZNear, viewZFar, 2, 3) + splitBias; + splitZFar = viewZFar; + //splitZNear = 896; + //splitZFar = 3072; + break; + } + + VectorCopy(fd->vieworg, lightOrigin); + + + // Make up a projection + VectorScale(lightDir, -1.0f, lightViewAxis[0]); + + if (lightViewIndependentOfCameraView) + { + // Use world up as light view up + VectorSet(lightViewAxis[2], 0, 0, 1); + } + else if (level == 0) + { + // Level 0 tries to use a diamond texture orientation relative to camera view + // Use halfway between camera view forward and left for light view up + VectorAdd(fd->viewaxis[0], fd->viewaxis[1], lightViewAxis[2]); + } + else + { + // Use camera view up as light view up + VectorCopy(fd->viewaxis[2], lightViewAxis[2]); + } + + // Check if too close to parallel to light direction + if (abs(DotProduct(lightViewAxis[2], lightViewAxis[0])) > 0.9f) + { + if (lightViewIndependentOfCameraView) + { + // Use world left as light view up + VectorSet(lightViewAxis[2], 0, 1, 0); + } + else if (level == 0) + { + // Level 0 tries to use a diamond texture orientation relative to camera view + // Use halfway between camera view forward and up for light view up + VectorAdd(fd->viewaxis[0], fd->viewaxis[2], lightViewAxis[2]); + } + else + { + // Use camera view left as light view up + VectorCopy(fd->viewaxis[1], lightViewAxis[2]); + } + } + + // clean axes + CrossProduct(lightViewAxis[2], lightViewAxis[0], lightViewAxis[1]); + VectorNormalize(lightViewAxis[1]); + CrossProduct(lightViewAxis[0], lightViewAxis[1], lightViewAxis[2]); + + // Create bounds for light projection using slice of view projection + { + matrix_t lightViewMatrix; + vec4_t point, base, lightViewPoint; + float lx, ly; + + base[3] = 1; + point[3] = 1; + lightViewPoint[3] = 1; + + Matrix16View(lightViewAxis, lightOrigin, lightViewMatrix); + + ClearBounds(lightviewBounds[0], lightviewBounds[1]); + + // add view near plane + lx = splitZNear * tan(fd->fov_x * M_PI / 360.0f); + ly = splitZNear * tan(fd->fov_y * M_PI / 360.0f); + VectorMA(fd->vieworg, splitZNear, fd->viewaxis[0], base); + + VectorMA(base, lx, fd->viewaxis[1], point); + VectorMA(point, ly, fd->viewaxis[2], point); + Matrix16Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + VectorMA(base, -lx, fd->viewaxis[1], point); + VectorMA(point, ly, fd->viewaxis[2], point); + Matrix16Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + VectorMA(base, lx, fd->viewaxis[1], point); + VectorMA(point, -ly, fd->viewaxis[2], point); + Matrix16Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + VectorMA(base, -lx, fd->viewaxis[1], point); + VectorMA(point, -ly, fd->viewaxis[2], point); + Matrix16Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + + // add view far plane + lx = splitZFar * tan(fd->fov_x * M_PI / 360.0f); + ly = splitZFar * tan(fd->fov_y * M_PI / 360.0f); + VectorMA(fd->vieworg, splitZFar, fd->viewaxis[0], base); + + VectorMA(base, lx, fd->viewaxis[1], point); + VectorMA(point, ly, fd->viewaxis[2], point); + Matrix16Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + VectorMA(base, -lx, fd->viewaxis[1], point); + VectorMA(point, ly, fd->viewaxis[2], point); + Matrix16Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + VectorMA(base, lx, fd->viewaxis[1], point); + VectorMA(point, -ly, fd->viewaxis[2], point); + Matrix16Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + VectorMA(base, -lx, fd->viewaxis[1], point); + VectorMA(point, -ly, fd->viewaxis[2], point); + Matrix16Transform(lightViewMatrix, point, lightViewPoint); + AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]); + + if (!glRefConfig.depthClamp) + lightviewBounds[0][0] = lightviewBounds[1][0] - 8192; + + // Moving the Light in Texel-Sized Increments + // from http://msdn.microsoft.com/en-us/library/windows/desktop/ee416324%28v=vs.85%29.aspx + // + if (lightViewIndependentOfCameraView) + { + float cascadeBound, worldUnitsPerTexel, invWorldUnitsPerTexel; + + cascadeBound = MAX(lightviewBounds[1][0] - lightviewBounds[0][0], lightviewBounds[1][1] - lightviewBounds[0][1]); + cascadeBound = MAX(cascadeBound, lightviewBounds[1][2] - lightviewBounds[0][2]); + worldUnitsPerTexel = cascadeBound / tr.sunShadowFbo[level]->width; + invWorldUnitsPerTexel = 1.0f / worldUnitsPerTexel; + + VectorScale(lightviewBounds[0], invWorldUnitsPerTexel, lightviewBounds[0]); + lightviewBounds[0][0] = floor(lightviewBounds[0][0]); + lightviewBounds[0][1] = floor(lightviewBounds[0][1]); + lightviewBounds[0][2] = floor(lightviewBounds[0][2]); + VectorScale(lightviewBounds[0], worldUnitsPerTexel, lightviewBounds[0]); + + VectorScale(lightviewBounds[1], invWorldUnitsPerTexel, lightviewBounds[1]); + lightviewBounds[1][0] = floor(lightviewBounds[1][0]); + lightviewBounds[1][1] = floor(lightviewBounds[1][1]); + lightviewBounds[1][2] = floor(lightviewBounds[1][2]); + VectorScale(lightviewBounds[1], worldUnitsPerTexel, lightviewBounds[1]); + } + + //ri.Printf(PRINT_ALL, "znear %f zfar %f\n", lightviewBounds[0][0], lightviewBounds[1][0]); + //ri.Printf(PRINT_ALL, "fovx %f fovy %f xmin %f xmax %f ymin %f ymax %f\n", fd->fov_x, fd->fov_y, xmin, xmax, ymin, ymax); + } + + + { + int firstDrawSurf; + + Com_Memset( &shadowParms, 0, sizeof( shadowParms ) ); + + if (glRefConfig.framebufferObject) + { + shadowParms.viewportX = 0; + shadowParms.viewportY = 0; + } + else + { + shadowParms.viewportX = tr.refdef.x; + shadowParms.viewportY = glConfig.vidHeight - ( tr.refdef.y + tr.sunShadowFbo[level]->height ); + } + shadowParms.viewportWidth = tr.sunShadowFbo[level]->width; + shadowParms.viewportHeight = tr.sunShadowFbo[level]->height; + shadowParms.isPortal = qfalse; + shadowParms.isMirror = qfalse; + + shadowParms.fovX = 90; + shadowParms.fovY = 90; + + if (glRefConfig.framebufferObject) + shadowParms.targetFbo = tr.sunShadowFbo[level]; + + shadowParms.flags = VPF_DEPTHSHADOW | VPF_DEPTHCLAMP | VPF_ORTHOGRAPHIC; + shadowParms.zFar = lightviewBounds[1][0]; + + VectorCopy(lightOrigin, shadowParms.or.origin); + + VectorCopy(lightViewAxis[0], shadowParms.or.axis[0]); + VectorCopy(lightViewAxis[1], shadowParms.or.axis[1]); + VectorCopy(lightViewAxis[2], shadowParms.or.axis[2]); + + VectorCopy(lightOrigin, shadowParms.pvsOrigin ); + + { + tr.viewCount++; + + tr.viewParms = shadowParms; + tr.viewParms.frameSceneNum = tr.frameSceneNum; + tr.viewParms.frameCount = tr.frameCount; + + firstDrawSurf = tr.refdef.numDrawSurfs; + + tr.viewCount++; + + // set viewParms.world + R_RotateForViewer (); + + R_SetupProjectionOrtho(&tr.viewParms, lightviewBounds); + + R_AddWorldSurfaces (); + + R_AddPolygonSurfaces(); + + R_AddEntitySurfaces (); + + R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, tr.refdef.numDrawSurfs - firstDrawSurf ); + } + + Matrix16Multiply(tr.viewParms.projectionMatrix, tr.viewParms.world.modelMatrix, tr.refdef.sunShadowMvp[level]); + } +} diff --git a/src/rend2/tr_marks.c b/src/rend2/tr_marks.c new file mode 100644 index 00000000..f24459e2 --- /dev/null +++ b/src/rend2/tr_marks.c @@ -0,0 +1,466 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_marks.c -- polygon projection on the world polygons + +#include "tr_local.h" +//#include "assert.h" + +#define MAX_VERTS_ON_POLY 64 + +#define MARKER_OFFSET 0 // 1 + +/* +============= +R_ChopPolyBehindPlane + +Out must have space for two more vertexes than in +============= +*/ +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +static void R_ChopPolyBehindPlane( int numInPoints, vec3_t inPoints[MAX_VERTS_ON_POLY], + int *numOutPoints, vec3_t outPoints[MAX_VERTS_ON_POLY], + vec3_t normal, vec_t dist, vec_t epsilon) { + float dists[MAX_VERTS_ON_POLY+4]; + int sides[MAX_VERTS_ON_POLY+4]; + int counts[3]; + float dot; + int i, j; + float *p1, *p2, *clip; + float d; + + // don't clip if it might overflow + if ( numInPoints >= MAX_VERTS_ON_POLY - 2 ) { + *numOutPoints = 0; + return; + } + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0 ; i < numInPoints ; i++ ) { + dot = DotProduct( inPoints[i], normal ); + dot -= dist; + dists[i] = dot; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *numOutPoints = 0; + + if ( !counts[0] ) { + return; + } + if ( !counts[1] ) { + *numOutPoints = numInPoints; + Com_Memcpy( outPoints, inPoints, numInPoints * sizeof(vec3_t) ); + return; + } + + for ( i = 0 ; i < numInPoints ; i++ ) { + p1 = inPoints[i]; + clip = outPoints[ *numOutPoints ]; + + if ( sides[i] == SIDE_ON ) { + VectorCopy( p1, clip ); + (*numOutPoints)++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + VectorCopy( p1, clip ); + (*numOutPoints)++; + clip = outPoints[ *numOutPoints ]; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = inPoints[ (i+1) % numInPoints ]; + + d = dists[i] - dists[i+1]; + if ( d == 0 ) { + dot = 0; + } else { + dot = dists[i] / d; + } + + // clip xyz + + for (j=0 ; j<3 ; j++) { + clip[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + + (*numOutPoints)++; + } +} + +/* +================= +R_BoxSurfaces_r + +================= +*/ +void R_BoxSurfaces_r(mnode_t *node, vec3_t mins, vec3_t maxs, surfaceType_t **list, int listsize, int *listlength, vec3_t dir) { + + int s, c; + msurface_t *surf; + int *mark; + + // do the tail recursion in a loop + while ( node->contents == -1 ) { + s = BoxOnPlaneSide( mins, maxs, node->plane ); + if (s == 1) { + node = node->children[0]; + } else if (s == 2) { + node = node->children[1]; + } else { + R_BoxSurfaces_r(node->children[0], mins, maxs, list, listsize, listlength, dir); + node = node->children[1]; + } + } + + // add the individual surfaces + mark = tr.world->marksurfaces + node->firstmarksurface; + c = node->nummarksurfaces; + while (c--) { + int *surfViewCount; + // + if (*listlength >= listsize) break; + // + surfViewCount = &tr.world->surfacesViewCount[*mark]; + surf = tr.world->surfaces + *mark; + // check if the surface has NOIMPACT or NOMARKS set + if ( ( surf->shader->surfaceFlags & ( SURF_NOIMPACT | SURF_NOMARKS ) ) + || ( surf->shader->contentFlags & CONTENTS_FOG ) ) { + *surfViewCount = tr.viewCount; + } + // extra check for surfaces to avoid list overflows + else if (*(surf->data) == SF_FACE) { + // the face plane should go through the box + s = BoxOnPlaneSide( mins, maxs, &surf->cullinfo.plane ); + if (s == 1 || s == 2) { + *surfViewCount = tr.viewCount; + } else if (DotProduct(surf->cullinfo.plane.normal, dir) > -0.5) { + // don't add faces that make sharp angles with the projection direction + *surfViewCount = tr.viewCount; + } + } + else if (*(surf->data) != SF_GRID && + *(surf->data) != SF_TRIANGLES) + *surfViewCount = tr.viewCount; + // check the viewCount because the surface may have + // already been added if it spans multiple leafs + if (*surfViewCount != tr.viewCount) { + *surfViewCount = tr.viewCount; + list[*listlength] = surf->data; + (*listlength)++; + } + mark++; + } +} + +/* +================= +R_AddMarkFragments + +================= +*/ +void R_AddMarkFragments(int numClipPoints, vec3_t clipPoints[2][MAX_VERTS_ON_POLY], + int numPlanes, vec3_t *normals, float *dists, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer, + int *returnedPoints, int *returnedFragments, + vec3_t mins, vec3_t maxs) { + int pingPong, i; + markFragment_t *mf; + + // chop the surface by all the bounding planes of the to be projected polygon + pingPong = 0; + + for ( i = 0 ; i < numPlanes ; i++ ) { + + R_ChopPolyBehindPlane( numClipPoints, clipPoints[pingPong], + &numClipPoints, clipPoints[!pingPong], + normals[i], dists[i], 0.5 ); + pingPong ^= 1; + if ( numClipPoints == 0 ) { + break; + } + } + // completely clipped away? + if ( numClipPoints == 0 ) { + return; + } + + // add this fragment to the returned list + if ( numClipPoints + (*returnedPoints) > maxPoints ) { + return; // not enough space for this polygon + } + /* + // all the clip points should be within the bounding box + for ( i = 0 ; i < numClipPoints ; i++ ) { + int j; + for ( j = 0 ; j < 3 ; j++ ) { + if (clipPoints[pingPong][i][j] < mins[j] - 0.5) break; + if (clipPoints[pingPong][i][j] > maxs[j] + 0.5) break; + } + if (j < 3) break; + } + if (i < numClipPoints) return; + */ + + mf = fragmentBuffer + (*returnedFragments); + mf->firstPoint = (*returnedPoints); + mf->numPoints = numClipPoints; + Com_Memcpy( pointBuffer + (*returnedPoints) * 3, clipPoints[pingPong], numClipPoints * sizeof(vec3_t) ); + + (*returnedPoints) += numClipPoints; + (*returnedFragments)++; +} + +/* +================= +R_MarkFragments + +================= +*/ +int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ) { + int numsurfaces, numPlanes; + int i, j, k, m, n; + surfaceType_t *surfaces[64]; + vec3_t mins, maxs; + int returnedFragments; + int returnedPoints; + vec3_t normals[MAX_VERTS_ON_POLY+2]; + float dists[MAX_VERTS_ON_POLY+2]; + vec3_t clipPoints[2][MAX_VERTS_ON_POLY]; + int numClipPoints; + float *v; + srfGridMesh_t *cv; + srfTriangle_t *tri; + srfVert_t *dv; + vec3_t normal; + vec3_t projectionDir; + vec3_t v1, v2; + + if (numPoints <= 0) { + return 0; + } + + //increment view count for double check prevention + tr.viewCount++; + + // + VectorNormalize2( projection, projectionDir ); + // find all the brushes that are to be considered + ClearBounds( mins, maxs ); + for ( i = 0 ; i < numPoints ; i++ ) { + vec3_t temp; + + AddPointToBounds( points[i], mins, maxs ); + VectorAdd( points[i], projection, temp ); + AddPointToBounds( temp, mins, maxs ); + // make sure we get all the leafs (also the one(s) in front of the hit surface) + VectorMA( points[i], -20, projectionDir, temp ); + AddPointToBounds( temp, mins, maxs ); + } + + if (numPoints > MAX_VERTS_ON_POLY) numPoints = MAX_VERTS_ON_POLY; + // create the bounding planes for the to be projected polygon + for ( i = 0 ; i < numPoints ; i++ ) { + VectorSubtract(points[(i+1)%numPoints], points[i], v1); + VectorAdd(points[i], projection, v2); + VectorSubtract(points[i], v2, v2); + CrossProduct(v1, v2, normals[i]); + VectorNormalizeFast(normals[i]); + dists[i] = DotProduct(normals[i], points[i]); + } + // add near and far clipping planes for projection + VectorCopy(projectionDir, normals[numPoints]); + dists[numPoints] = DotProduct(normals[numPoints], points[0]) - 32; + VectorCopy(projectionDir, normals[numPoints+1]); + VectorInverse(normals[numPoints+1]); + dists[numPoints+1] = DotProduct(normals[numPoints+1], points[0]) - 20; + numPlanes = numPoints + 2; + + numsurfaces = 0; + R_BoxSurfaces_r(tr.world->nodes, mins, maxs, surfaces, 64, &numsurfaces, projectionDir); + //assert(numsurfaces <= 64); + //assert(numsurfaces != 64); + + returnedPoints = 0; + returnedFragments = 0; + + for ( i = 0 ; i < numsurfaces ; i++ ) { + + if (*surfaces[i] == SF_GRID) { + + cv = (srfGridMesh_t *) surfaces[i]; + for ( m = 0 ; m < cv->height - 1 ; m++ ) { + for ( n = 0 ; n < cv->width - 1 ; n++ ) { + // We triangulate the grid and chop all triangles within + // the bounding planes of the to be projected polygon. + // LOD is not taken into account, not such a big deal though. + // + // It's probably much nicer to chop the grid itself and deal + // with this grid as a normal SF_GRID surface so LOD will + // be applied. However the LOD of that chopped grid must + // be synced with the LOD of the original curve. + // One way to do this; the chopped grid shares vertices with + // the original curve. When LOD is applied to the original + // curve the unused vertices are flagged. Now the chopped curve + // should skip the flagged vertices. This still leaves the + // problems with the vertices at the chopped grid edges. + // + // To avoid issues when LOD applied to "hollow curves" (like + // the ones around many jump pads) we now just add a 2 unit + // offset to the triangle vertices. + // The offset is added in the vertex normal vector direction + // so all triangles will still fit together. + // The 2 unit offset should avoid pretty much all LOD problems. + + numClipPoints = 3; + + dv = cv->verts + m * cv->width + n; + + VectorCopy(dv[0].xyz, clipPoints[0][0]); + VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[0].normal, clipPoints[0][0]); + VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); + VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]); + VectorCopy(dv[1].xyz, clipPoints[0][2]); + VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[1].normal, clipPoints[0][2]); + // check the normal of this triangle + VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); + VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); + CrossProduct(v1, v2, normal); + VectorNormalizeFast(normal); + if (DotProduct(normal, projectionDir) < -0.1) { + // add the fragments of this triangle + R_AddMarkFragments(numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + + VectorCopy(dv[1].xyz, clipPoints[0][0]); + VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[1].normal, clipPoints[0][0]); + VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); + VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]); + VectorCopy(dv[cv->width+1].xyz, clipPoints[0][2]); + VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[cv->width+1].normal, clipPoints[0][2]); + // check the normal of this triangle + VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); + VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); + CrossProduct(v1, v2, normal); + VectorNormalizeFast(normal); + if (DotProduct(normal, projectionDir) < -0.05) { + // add the fragments of this triangle + R_AddMarkFragments(numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + } + } + } + else if (*surfaces[i] == SF_FACE) { + + srfSurfaceFace_t *surf = ( srfSurfaceFace_t * ) surfaces[i]; + + // check the normal of this face + if (DotProduct(surf->plane.normal, projectionDir) > -0.5) { + continue; + } + + for(k = 0, tri = surf->triangles; k < surf->numTriangles; k++, tri++) + { + for(j = 0; j < 3; j++) + { + v = surf->verts[tri->indexes[j]].xyz; + VectorMA(v, MARKER_OFFSET, surf->plane.normal, clipPoints[0][j]); + } + + // add the fragments of this face + R_AddMarkFragments( 3 , clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + } + else if(*surfaces[i] == SF_TRIANGLES && r_marksOnTriangleMeshes->integer) { + + srfTriangles_t *surf = (srfTriangles_t *) surfaces[i]; + + for(k = 0, tri = surf->triangles; k < surf->numTriangles; k++, tri++) + { + for(j = 0; j < 3; j++) + { + v = surf->verts[tri->indexes[j]].xyz; + VectorMA(v, MARKER_OFFSET, surf->verts[tri->indexes[j]].normal, clipPoints[0][j]); + } + + // add the fragments of this face + R_AddMarkFragments(3, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, &returnedPoints, &returnedFragments, mins, maxs); + if(returnedFragments == maxFragments) + { + return returnedFragments; // not enough space for more fragments + } + } + } + } + return returnedFragments; +} + + + + + diff --git a/src/rend2/tr_mesh.c b/src/rend2/tr_mesh.c new file mode 100644 index 00000000..342854bf --- /dev/null +++ b/src/rend2/tr_mesh.c @@ -0,0 +1,405 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_mesh.c: triangle model functions + +#include "tr_local.h" + +static float ProjectRadius( float r, vec3_t location ) +{ + float pr; + float dist; + float c; + vec3_t p; + float projected[4]; + + c = DotProduct( tr.viewParms.or.axis[0], tr.viewParms.or.origin ); + dist = DotProduct( tr.viewParms.or.axis[0], location ) - c; + + if ( dist <= 0 ) + return 0; + + p[0] = 0; + p[1] = fabs( r ); + p[2] = -dist; + + projected[0] = p[0] * tr.viewParms.projectionMatrix[0] + + p[1] * tr.viewParms.projectionMatrix[4] + + p[2] * tr.viewParms.projectionMatrix[8] + + tr.viewParms.projectionMatrix[12]; + + projected[1] = p[0] * tr.viewParms.projectionMatrix[1] + + p[1] * tr.viewParms.projectionMatrix[5] + + p[2] * tr.viewParms.projectionMatrix[9] + + tr.viewParms.projectionMatrix[13]; + + projected[2] = p[0] * tr.viewParms.projectionMatrix[2] + + p[1] * tr.viewParms.projectionMatrix[6] + + p[2] * tr.viewParms.projectionMatrix[10] + + tr.viewParms.projectionMatrix[14]; + + projected[3] = p[0] * tr.viewParms.projectionMatrix[3] + + p[1] * tr.viewParms.projectionMatrix[7] + + p[2] * tr.viewParms.projectionMatrix[11] + + tr.viewParms.projectionMatrix[15]; + + + pr = projected[1] / projected[3]; + + if ( pr > 1.0f ) + pr = 1.0f; + + return pr; +} + +/* +============= +R_CullModel +============= +*/ +static int R_CullModel( mdvModel_t *model, trRefEntity_t *ent ) { + vec3_t bounds[2]; + mdvFrame_t *oldFrame, *newFrame; + int i; + + // compute frame pointers + newFrame = model->frames + ent->e.frame; + oldFrame = model->frames + ent->e.oldframe; + + // cull bounding sphere ONLY if this is not an upscaled entity + if ( !ent->e.nonNormalizedAxes ) + { + if ( ent->e.frame == ent->e.oldframe ) + { + switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) ) + { + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + break; + } + } + else + { + int sphereCull, sphereCullB; + + sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ); + if ( newFrame == oldFrame ) { + sphereCullB = sphereCull; + } else { + sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius ); + } + + if ( sphereCull == sphereCullB ) + { + if ( sphereCull == CULL_OUT ) + { + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + } + else if ( sphereCull == CULL_IN ) + { + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + } + else + { + tr.pc.c_sphere_cull_md3_clip++; + } + } + } + } + + // calculate a bounding box in the current coordinate system + for (i = 0 ; i < 3 ; i++) { + bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i]; + bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + + +/* +================= +R_ComputeLOD + +================= +*/ +int R_ComputeLOD( trRefEntity_t *ent ) { + float radius; + float flod, lodscale; + float projectedRadius; + mdvFrame_t *frame; +#ifdef RAVENMD4 + mdrHeader_t *mdr; + mdrFrame_t *mdrframe; +#endif + int lod; + + if ( tr.currentModel->numLods < 2 ) + { + // model has only 1 LOD level, skip computations and bias + lod = 0; + } + else + { + // multiple LODs exist, so compute projected bounding sphere + // and use that as a criteria for selecting LOD + +#ifdef RAVENMD4 + if(tr.currentModel->type == MOD_MDR) + { + int frameSize; + mdr = (mdrHeader_t *) tr.currentModel->modelData; + frameSize = (size_t) (&((mdrFrame_t *)0)->bones[mdr->numBones]); + + mdrframe = (mdrFrame_t *) ((byte *) mdr + mdr->ofsFrames + frameSize * ent->e.frame); + + radius = RadiusFromBounds(mdrframe->bounds[0], mdrframe->bounds[1]); + } + else +#endif + { + //frame = ( md3Frame_t * ) ( ( ( unsigned char * ) tr.currentModel->md3[0] ) + tr.currentModel->md3[0]->ofsFrames ); + frame = tr.currentModel->mdv[0]->frames; + + frame += ent->e.frame; + + radius = RadiusFromBounds( frame->bounds[0], frame->bounds[1] ); + } + + if ( ( projectedRadius = ProjectRadius( radius, ent->e.origin ) ) != 0 ) + { + lodscale = r_lodscale->value; + if (lodscale > 20) lodscale = 20; + flod = 1.0f - projectedRadius * lodscale; + } + else + { + // object intersects near view plane, e.g. view weapon + flod = 0; + } + + flod *= tr.currentModel->numLods; + lod = ri.ftol(flod); + + if ( lod < 0 ) + { + lod = 0; + } + else if ( lod >= tr.currentModel->numLods ) + { + lod = tr.currentModel->numLods - 1; + } + } + + lod += r_lodbias->integer; + + if ( lod >= tr.currentModel->numLods ) + lod = tr.currentModel->numLods - 1; + if ( lod < 0 ) + lod = 0; + + return lod; +} + +/* +================= +R_ComputeFogNum + +================= +*/ +int R_ComputeFogNum( mdvModel_t *model, trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + mdvFrame_t *mdvFrame; + vec3_t localOrigin; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + // FIXME: non-normalized axis issues + mdvFrame = model->frames + ent->e.frame; + VectorAdd( ent->e.origin, mdvFrame->localOrigin, localOrigin ); + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( localOrigin[j] - mdvFrame->radius >= fog->bounds[1][j] ) { + break; + } + if ( localOrigin[j] + mdvFrame->radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +================= +R_AddMD3Surfaces + +================= +*/ +void R_AddMD3Surfaces( trRefEntity_t *ent ) { + int i; + mdvModel_t *model = NULL; + mdvSurface_t *surface = NULL; + shader_t *shader = NULL; + int cull; + int lod; + int fogNum; + qboolean personalModel; + + // don't add third_person objects if not in a portal + personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !(tr.viewParms.isPortal + || (tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW))); + + if ( ent->e.renderfx & RF_WRAP_FRAMES ) { + ent->e.frame %= tr.currentModel->mdv[0]->numFrames; + ent->e.oldframe %= tr.currentModel->mdv[0]->numFrames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ( (ent->e.frame >= tr.currentModel->mdv[0]->numFrames) + || (ent->e.frame < 0) + || (ent->e.oldframe >= tr.currentModel->mdv[0]->numFrames) + || (ent->e.oldframe < 0) ) { + ri.Printf( PRINT_DEVELOPER, "R_AddMD3Surfaces: no such frame %d to %d for '%s'\n", + ent->e.oldframe, ent->e.frame, + tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + // + // compute LOD + // + lod = R_ComputeLOD( ent ); + + model = tr.currentModel->mdv[lod]; + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_CullModel ( model, ent ); + if ( cull == CULL_OUT ) { + return; + } + + // + // set up lighting now that we know we aren't culled + // + if ( !personalModel || r_shadows->integer > 1 ) { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // + // see if we are in a fog volume + // + fogNum = R_ComputeFogNum( model, ent ); + + // + // draw all surfaces + // + surface = model->surfaces; + for ( i = 0 ; i < model->numSurfaces ; i++ ) { + + if ( ent->e.customShader ) { + shader = R_GetShaderByHandle( ent->e.customShader ); + } else if ( ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins ) { + skin_t *skin; + int j; + + skin = R_GetSkinByHandle( ent->e.customSkin ); + + // match the surface name to something in the skin file + shader = tr.defaultShader; + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + // the names have both been lowercased + if ( !strcmp( skin->surfaces[j]->name, surface->name ) ) { + shader = skin->surfaces[j]->shader; + break; + } + } + if (shader == tr.defaultShader) { + ri.Printf( PRINT_DEVELOPER, "WARNING: no shader for surface %s in skin %s\n", surface->name, skin->name); + } + else if (shader->defaultShader) { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader %s in skin %s not found\n", shader->name, skin->name); + } + //} else if ( surface->numShaders <= 0 ) { + //shader = tr.defaultShader; + } else { + //md3Shader = (md3Shader_t *) ( (byte *)surface + surface->ofsShaders ); + //md3Shader += ent->e.skinNum % surface->numShaders; + //shader = tr.shaders[ md3Shader->shaderIndex ]; + shader = tr.shaders[ surface->shaderIndexes[ ent->e.skinNum % surface->numShaderIndexes ] ]; + } + + // don't add third_person objects if not viewing through a portal + if(!personalModel) + { + srfVBOMDVMesh_t *vboSurface = &model->vboSurfaces[i]; + + R_AddDrawSurf((void *)vboSurface, shader, fogNum, qfalse, qfalse ); + } + + surface++; + } + +} + + + + + diff --git a/src/rend2/tr_model.c b/src/rend2/tr_model.c new file mode 100644 index 00000000..b269fdee --- /dev/null +++ b/src/rend2/tr_model.c @@ -0,0 +1,1580 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_models.c -- model loading and caching + +#include "tr_local.h" + +#define LL(x) x=LittleLong(x) + +static qboolean R_LoadMD3(model_t *mod, int lod, void *buffer, int bufferSize, const char *modName); +static qboolean R_LoadMD4(model_t *mod, void *buffer, const char *name ); +#ifdef RAVENMD4 +static qboolean R_LoadMDR(model_t *mod, void *buffer, int filesize, const char *name ); +#endif + +/* +==================== +R_RegisterMD3 +==================== +*/ +qhandle_t R_RegisterMD3(const char *name, model_t *mod) +{ + union { + unsigned *u; + void *v; + } buf; + int size; + int lod; + int ident; + qboolean loaded = qfalse; + int numLoaded; + char filename[MAX_QPATH], namebuf[MAX_QPATH+20]; + char *fext, defex[] = "md3"; + + numLoaded = 0; + + strcpy(filename, name); + + fext = strchr(filename, '.'); + if(!fext) + fext = defex; + else + { + *fext = '\0'; + fext++; + } + + for (lod = MD3_MAX_LODS - 1 ; lod >= 0 ; lod--) + { + if(lod) + Com_sprintf(namebuf, sizeof(namebuf), "%s_%d.%s", filename, lod, fext); + else + Com_sprintf(namebuf, sizeof(namebuf), "%s.%s", filename, fext); + + size = ri.FS_ReadFile( namebuf, &buf.v ); + if(!buf.u) + continue; + + ident = LittleLong(* (unsigned *) buf.u); + if (ident == MD4_IDENT) + loaded = R_LoadMD4(mod, buf.u, name); + else + { + if (ident == MD3_IDENT) + loaded = R_LoadMD3(mod, lod, buf.u, size, name); + else + ri.Printf(PRINT_WARNING,"R_RegisterMD3: unknown fileid for %s\n", name); + } + + ri.FS_FreeFile(buf.v); + + if(loaded) + { + mod->numLods++; + numLoaded++; + } + else + break; + } + + if(numLoaded) + { + // duplicate into higher lod spots that weren't + // loaded, in case the user changes r_lodbias on the fly + for(lod--; lod >= 0; lod--) + { + mod->numLods++; + mod->mdv[lod] = mod->mdv[lod + 1]; + } + + return mod->index; + } + +#ifdef _DEBUG + ri.Printf(PRINT_WARNING,"R_RegisterMD3: couldn't load %s\n", name); +#endif + + mod->type = MOD_BAD; + return 0; +} + +#ifdef RAVENMD4 +/* +==================== +R_RegisterMDR +==================== +*/ +qhandle_t R_RegisterMDR(const char *name, model_t *mod) +{ + union { + unsigned *u; + void *v; + } buf; + int ident; + qboolean loaded = qfalse; + int filesize; + + filesize = ri.FS_ReadFile(name, (void **) &buf.v); + if(!buf.u) + { + mod->type = MOD_BAD; + return 0; + } + + ident = LittleLong(*(unsigned *)buf.u); + if(ident == MDR_IDENT) + loaded = R_LoadMDR(mod, buf.u, filesize, name); + + ri.FS_FreeFile (buf.v); + + if(!loaded) + { + ri.Printf(PRINT_WARNING,"R_RegisterMDR: couldn't load mdr file %s\n", name); + mod->type = MOD_BAD; + return 0; + } + + return mod->index; +} +#endif + +/* +==================== +R_RegisterIQM +==================== +*/ +qhandle_t R_RegisterIQM(const char *name, model_t *mod) +{ + union { + unsigned *u; + void *v; + } buf; + qboolean loaded = qfalse; + int filesize; + + filesize = ri.FS_ReadFile(name, (void **) &buf.v); + if(!buf.u) + { + mod->type = MOD_BAD; + return 0; + } + + loaded = R_LoadIQM(mod, buf.u, filesize, name); + + ri.FS_FreeFile (buf.v); + + if(!loaded) + { + ri.Printf(PRINT_WARNING,"R_RegisterIQM: couldn't load iqm file %s\n", name); + mod->type = MOD_BAD; + return 0; + } + + return mod->index; +} + + +typedef struct +{ + char *ext; + qhandle_t (*ModelLoader)( const char *, model_t * ); +} modelExtToLoaderMap_t; + +// Note that the ordering indicates the order of preference used +// when there are multiple models of different formats available +static modelExtToLoaderMap_t modelLoaders[ ] = +{ + { "iqm", R_RegisterIQM }, +#ifdef RAVENMD4 + { "mdr", R_RegisterMDR }, +#endif + { "md4", R_RegisterMD3 }, + { "md3", R_RegisterMD3 } +}; + +static int numModelLoaders = ARRAY_LEN(modelLoaders); + +//=============================================================================== + +/* +** R_GetModelByHandle +*/ +model_t *R_GetModelByHandle( qhandle_t index ) { + model_t *mod; + + // out of range gets the defualt model + if ( index < 1 || index >= tr.numModels ) { + return tr.models[0]; + } + + mod = tr.models[index]; + + return mod; +} + +//=============================================================================== + +/* +** R_AllocModel +*/ +model_t *R_AllocModel( void ) { + model_t *mod; + + if ( tr.numModels == MAX_MOD_KNOWN ) { + return NULL; + } + + mod = ri.Hunk_Alloc( sizeof( *tr.models[tr.numModels] ), h_low ); + mod->index = tr.numModels; + tr.models[tr.numModels] = mod; + tr.numModels++; + + return mod; +} + +/* +==================== +RE_RegisterModel + +Loads in a model for the given name + +Zero will be returned if the model fails to load. +An entry will be retained for failed models as an +optimization to prevent disk rescanning if they are +asked for again. +==================== +*/ +qhandle_t RE_RegisterModel( const char *name ) { + model_t *mod; + qhandle_t hModel; + qboolean orgNameFailed = qfalse; + int orgLoader = -1; + int i; + char localName[ MAX_QPATH ]; + const char *ext; + char altName[ MAX_QPATH ]; + + if ( !name || !name[0] ) { + ri.Printf( PRINT_ALL, "RE_RegisterModel: NULL name\n" ); + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Printf( PRINT_ALL, "Model name exceeds MAX_QPATH\n" ); + return 0; + } + + // + // search the currently loaded models + // + for ( hModel = 1 ; hModel < tr.numModels; hModel++ ) { + mod = tr.models[hModel]; + if ( !strcmp( mod->name, name ) ) { + if( mod->type == MOD_BAD ) { + return 0; + } + return hModel; + } + } + + // allocate a new model_t + + if ( ( mod = R_AllocModel() ) == NULL ) { + ri.Printf( PRINT_WARNING, "RE_RegisterModel: R_AllocModel() failed for '%s'\n", name); + return 0; + } + + // only set the name after the model has been successfully loaded + Q_strncpyz( mod->name, name, sizeof( mod->name ) ); + + + // make sure the render thread is stopped + R_SyncRenderThread(); + + mod->type = MOD_BAD; + mod->numLods = 0; + + // + // load the files + // + Q_strncpyz( localName, name, MAX_QPATH ); + + ext = COM_GetExtension( localName ); + + if( *ext ) + { + // Look for the correct loader and use it + for( i = 0; i < numModelLoaders; i++ ) + { + if( !Q_stricmp( ext, modelLoaders[ i ].ext ) ) + { + // Load + hModel = modelLoaders[ i ].ModelLoader( localName, mod ); + break; + } + } + + // A loader was found + if( i < numModelLoaders ) + { + if( !hModel ) + { + // Loader failed, most likely because the file isn't there; + // try again without the extension + orgNameFailed = qtrue; + orgLoader = i; + COM_StripExtension( name, localName, MAX_QPATH ); + } + else + { + // Something loaded + return mod->index; + } + } + } + + // Try and find a suitable match using all + // the model formats supported + for( i = 0; i < numModelLoaders; i++ ) + { + if (i == orgLoader) + continue; + + Com_sprintf( altName, sizeof (altName), "%s.%s", localName, modelLoaders[ i ].ext ); + + // Load + hModel = modelLoaders[ i ].ModelLoader( altName, mod ); + + if( hModel ) + { + if( orgNameFailed ) + { + ri.Printf( PRINT_DEVELOPER, "WARNING: %s not present, using %s instead\n", + name, altName ); + } + + break; + } + } + + return hModel; +} + +/* +================= +R_LoadMD3 +================= +*/ +static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, int bufferSize, const char *modName) +{ + int f, i, j, k; + + md3Header_t *md3Model; + md3Frame_t *md3Frame; + md3Surface_t *md3Surf; + md3Shader_t *md3Shader; + md3Triangle_t *md3Tri; + md3St_t *md3st; + md3XyzNormal_t *md3xyz; + md3Tag_t *md3Tag; + + mdvModel_t *mdvModel; + mdvFrame_t *frame; + mdvSurface_t *surf;//, *surface; + int *shaderIndex; + srfTriangle_t *tri; + mdvVertex_t *v; + mdvSt_t *st; + mdvTag_t *tag; + mdvTagName_t *tagName; + + int version; + int size; + + md3Model = (md3Header_t *) buffer; + + version = LittleLong(md3Model->version); + if(version != MD3_VERSION) + { + ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has wrong version (%i should be %i)\n", modName, version, MD3_VERSION); + return qfalse; + } + + mod->type = MOD_MESH; + size = LittleLong(md3Model->ofsEnd); + mod->dataSize += size; + mdvModel = mod->mdv[lod] = ri.Hunk_Alloc(sizeof(mdvModel_t), h_low); + +// Com_Memcpy(mod->md3[lod], buffer, LittleLong(md3Model->ofsEnd)); + + LL(md3Model->ident); + LL(md3Model->version); + LL(md3Model->numFrames); + LL(md3Model->numTags); + LL(md3Model->numSurfaces); + LL(md3Model->ofsFrames); + LL(md3Model->ofsTags); + LL(md3Model->ofsSurfaces); + LL(md3Model->ofsEnd); + + if(md3Model->numFrames < 1) + { + ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has no frames\n", modName); + return qfalse; + } + + // swap all the frames + mdvModel->numFrames = md3Model->numFrames; + mdvModel->frames = frame = ri.Hunk_Alloc(sizeof(*frame) * md3Model->numFrames, h_low); + + md3Frame = (md3Frame_t *) ((byte *) md3Model + md3Model->ofsFrames); + for(i = 0; i < md3Model->numFrames; i++, frame++, md3Frame++) + { + frame->radius = LittleFloat(md3Frame->radius); + for(j = 0; j < 3; j++) + { + frame->bounds[0][j] = LittleFloat(md3Frame->bounds[0][j]); + frame->bounds[1][j] = LittleFloat(md3Frame->bounds[1][j]); + frame->localOrigin[j] = LittleFloat(md3Frame->localOrigin[j]); + } + } + + // swap all the tags + mdvModel->numTags = md3Model->numTags; + mdvModel->tags = tag = ri.Hunk_Alloc(sizeof(*tag) * (md3Model->numTags * md3Model->numFrames), h_low); + + md3Tag = (md3Tag_t *) ((byte *) md3Model + md3Model->ofsTags); + for(i = 0; i < md3Model->numTags * md3Model->numFrames; i++, tag++, md3Tag++) + { + for(j = 0; j < 3; j++) + { + tag->origin[j] = LittleFloat(md3Tag->origin[j]); + tag->axis[0][j] = LittleFloat(md3Tag->axis[0][j]); + tag->axis[1][j] = LittleFloat(md3Tag->axis[1][j]); + tag->axis[2][j] = LittleFloat(md3Tag->axis[2][j]); + } + } + + + mdvModel->tagNames = tagName = ri.Hunk_Alloc(sizeof(*tagName) * (md3Model->numTags), h_low); + + md3Tag = (md3Tag_t *) ((byte *) md3Model + md3Model->ofsTags); + for(i = 0; i < md3Model->numTags; i++, tagName++, md3Tag++) + { + Q_strncpyz(tagName->name, md3Tag->name, sizeof(tagName->name)); + } + + // swap all the surfaces + mdvModel->numSurfaces = md3Model->numSurfaces; + mdvModel->surfaces = surf = ri.Hunk_Alloc(sizeof(*surf) * md3Model->numSurfaces, h_low); + + md3Surf = (md3Surface_t *) ((byte *) md3Model + md3Model->ofsSurfaces); + for(i = 0; i < md3Model->numSurfaces; i++) + { + LL(md3Surf->ident); + LL(md3Surf->flags); + LL(md3Surf->numFrames); + LL(md3Surf->numShaders); + LL(md3Surf->numTriangles); + LL(md3Surf->ofsTriangles); + LL(md3Surf->numVerts); + LL(md3Surf->ofsShaders); + LL(md3Surf->ofsSt); + LL(md3Surf->ofsXyzNormals); + LL(md3Surf->ofsEnd); + + if(md3Surf->numVerts > SHADER_MAX_VERTEXES) + { + ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has more than %i verts on a surface (%i)", + modName, SHADER_MAX_VERTEXES, md3Surf->numVerts); + return qfalse; + } + if(md3Surf->numTriangles * 3 > SHADER_MAX_INDEXES) + { + ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has more than %i triangles on a surface (%i)", + modName, SHADER_MAX_INDEXES / 3, md3Surf->numTriangles); + return qfalse; + } + + // change to surface identifier + surf->surfaceType = SF_MDV; + + // give pointer to model for Tess_SurfaceMDX + surf->model = mdvModel; + + // copy surface name + Q_strncpyz(surf->name, md3Surf->name, sizeof(surf->name)); + + // lowercase the surface name so skin compares are faster + Q_strlwr(surf->name); + + // strip off a trailing _1 or _2 + // this is a crutch for q3data being a mess + j = strlen(surf->name); + if(j > 2 && surf->name[j - 2] == '_') + { + surf->name[j - 2] = 0; + } + + // register the shaders + surf->numShaderIndexes = md3Surf->numShaders; + surf->shaderIndexes = shaderIndex = ri.Hunk_Alloc(sizeof(*shaderIndex) * md3Surf->numShaders, h_low); + + md3Shader = (md3Shader_t *) ((byte *) md3Surf + md3Surf->ofsShaders); + for(j = 0; j < md3Surf->numShaders; j++, shaderIndex++, md3Shader++) + { + shader_t *sh; + + sh = R_FindShader(md3Shader->name, LIGHTMAP_NONE, qtrue); + if(sh->defaultShader) + { + *shaderIndex = 0; + } + else + { + *shaderIndex = sh->index; + } + } + + // swap all the triangles + surf->numTriangles = md3Surf->numTriangles; + surf->triangles = tri = ri.Hunk_Alloc(sizeof(*tri) * md3Surf->numTriangles, h_low); + + md3Tri = (md3Triangle_t *) ((byte *) md3Surf + md3Surf->ofsTriangles); + for(j = 0; j < md3Surf->numTriangles; j++, tri++, md3Tri++) + { + tri->indexes[0] = LittleLong(md3Tri->indexes[0]); + tri->indexes[1] = LittleLong(md3Tri->indexes[1]); + tri->indexes[2] = LittleLong(md3Tri->indexes[2]); + } + + R_CalcSurfaceTriangleNeighbors(surf->numTriangles, surf->triangles); + + // swap all the XyzNormals + surf->numVerts = md3Surf->numVerts; + surf->verts = v = ri.Hunk_Alloc(sizeof(*v) * (md3Surf->numVerts * md3Surf->numFrames), h_low); + + md3xyz = (md3XyzNormal_t *) ((byte *) md3Surf + md3Surf->ofsXyzNormals); + for(j = 0; j < md3Surf->numVerts * md3Surf->numFrames; j++, md3xyz++, v++) + { + unsigned lat, lng; + unsigned short normal; + + v->xyz[0] = LittleShort(md3xyz->xyz[0]) * MD3_XYZ_SCALE; + v->xyz[1] = LittleShort(md3xyz->xyz[1]) * MD3_XYZ_SCALE; + v->xyz[2] = LittleShort(md3xyz->xyz[2]) * MD3_XYZ_SCALE; + + normal = LittleShort(md3xyz->normal); + + lat = ( normal >> 8 ) & 0xff; + lng = ( normal & 0xff ); + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + v->normal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + v->normal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + v->normal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + } + + // swap all the ST + surf->st = st = ri.Hunk_Alloc(sizeof(*st) * md3Surf->numVerts, h_low); + + md3st = (md3St_t *) ((byte *) md3Surf + md3Surf->ofsSt); + for(j = 0; j < md3Surf->numVerts; j++, md3st++, st++) + { + st->st[0] = LittleFloat(md3st->st[0]); + st->st[1] = LittleFloat(md3st->st[1]); + } + +#ifdef USE_VERT_TANGENT_SPACE + // calc tangent spaces + { + // Valgrind complaints: Conditional jump or move depends on uninitialised value(s) + // So lets Initialize them. + const float *v0 = NULL, *v1 = NULL, *v2 = NULL; + const float *t0 = NULL, *t1 = NULL, *t2 = NULL; + vec3_t tangent = { 0, 0, 0 }; + vec3_t bitangent = { 0, 0, 0 }; + vec3_t normal = { 0, 0, 0 }; + + for(j = 0, v = surf->verts; j < (surf->numVerts * mdvModel->numFrames); j++, v++) + { + VectorClear(v->tangent); + VectorClear(v->bitangent); + if (r_recalcMD3Normals->integer) + VectorClear(v->normal); + } + + for(f = 0; f < mdvModel->numFrames; f++) + { + for(j = 0, tri = surf->triangles; j < surf->numTriangles; j++, tri++) + { + v0 = surf->verts[surf->numVerts * f + tri->indexes[0]].xyz; + v1 = surf->verts[surf->numVerts * f + tri->indexes[1]].xyz; + v2 = surf->verts[surf->numVerts * f + tri->indexes[2]].xyz; + + t0 = surf->st[tri->indexes[0]].st; + t1 = surf->st[tri->indexes[1]].st; + t2 = surf->st[tri->indexes[2]].st; + + if (!r_recalcMD3Normals->integer) + VectorCopy(v->normal, normal); + else + VectorClear(normal); + + #if 1 + R_CalcTangentSpace(tangent, bitangent, normal, v0, v1, v2, t0, t1, t2); + #else + R_CalcNormalForTriangle(normal, v0, v1, v2); + R_CalcTangentsForTriangle(tangent, bitangent, v0, v1, v2, t0, t1, t2); + #endif + + for(k = 0; k < 3; k++) + { + float *v; + + v = surf->verts[surf->numVerts * f + tri->indexes[k]].tangent; + VectorAdd(v, tangent, v); + + v = surf->verts[surf->numVerts * f + tri->indexes[k]].bitangent; + VectorAdd(v, bitangent, v); + + if (r_recalcMD3Normals->integer) + { + v = surf->verts[surf->numVerts * f + tri->indexes[k]].normal; + VectorAdd(v, normal, v); + } + } + } + } + + for(j = 0, v = surf->verts; j < (surf->numVerts * mdvModel->numFrames); j++, v++) + { + VectorNormalize(v->tangent); + VectorNormalize(v->bitangent); + VectorNormalize(v->normal); + } + } +#endif + + // find the next surface + md3Surf = (md3Surface_t *) ((byte *) md3Surf + md3Surf->ofsEnd); + surf++; + } + + { + srfVBOMDVMesh_t *vboSurf; + + mdvModel->numVBOSurfaces = mdvModel->numSurfaces; + mdvModel->vboSurfaces = ri.Hunk_Alloc(sizeof(*mdvModel->vboSurfaces) * mdvModel->numSurfaces, h_low); + + vboSurf = mdvModel->vboSurfaces; + surf = mdvModel->surfaces; + for (i = 0; i < mdvModel->numSurfaces; i++, vboSurf++, surf++) + { + vec3_t *verts; + vec3_t *normals; + vec2_t *texcoords; +#ifdef USE_VERT_TANGENT_SPACE + vec3_t *tangents; + vec3_t *bitangents; +#endif + + byte *data; + int dataSize; + + int ofs_xyz, ofs_normal, ofs_st; +#ifdef USE_VERT_TANGENT_SPACE + int ofs_tangent, ofs_bitangent; +#endif + + dataSize = 0; + + ofs_xyz = dataSize; + dataSize += surf->numVerts * mdvModel->numFrames * sizeof(*verts); + + ofs_normal = dataSize; + dataSize += surf->numVerts * mdvModel->numFrames * sizeof(*normals); + +#ifdef USE_VERT_TANGENT_SPACE + ofs_tangent = dataSize; + dataSize += surf->numVerts * mdvModel->numFrames * sizeof(*tangents); + + ofs_bitangent = dataSize; + dataSize += surf->numVerts * mdvModel->numFrames * sizeof(*bitangents); +#endif + + ofs_st = dataSize; + dataSize += surf->numVerts * sizeof(*texcoords); + + data = ri.Malloc(dataSize); + + verts = (void *)(data + ofs_xyz); + normals = (void *)(data + ofs_normal); +#ifdef USE_VERT_TANGENT_SPACE + tangents = (void *)(data + ofs_tangent); + bitangents = (void *)(data + ofs_bitangent); +#endif + texcoords = (void *)(data + ofs_st); + + v = surf->verts; + for ( j = 0; j < surf->numVerts * mdvModel->numFrames ; j++, v++ ) + { + VectorCopy(v->xyz, verts[j]); + VectorCopy(v->normal, normals[j]); +#ifdef USE_VERT_TANGENT_SPACE + VectorCopy(v->tangent, tangents[j]); + VectorCopy(v->bitangent, bitangents[j]); +#endif + } + + st = surf->st; + for ( j = 0 ; j < surf->numVerts ; j++, st++ ) { + texcoords[j][0] = st->st[0]; + texcoords[j][1] = st->st[1]; + } + + vboSurf->surfaceType = SF_VBO_MDVMESH; + vboSurf->mdvModel = mdvModel; + vboSurf->mdvSurface = surf; + vboSurf->numIndexes = surf->numTriangles * 3; + vboSurf->numVerts = surf->numVerts; + vboSurf->vbo = R_CreateVBO(va("staticMD3Mesh_VBO '%s'", surf->name), data, dataSize, VBO_USAGE_STATIC); + + vboSurf->vbo->ofs_xyz = ofs_xyz; + vboSurf->vbo->ofs_normal = ofs_normal; +#ifdef USE_VERT_TANGENT_SPACE + vboSurf->vbo->ofs_tangent = ofs_tangent; + vboSurf->vbo->ofs_bitangent = ofs_bitangent; +#endif + vboSurf->vbo->ofs_st = ofs_st; + + vboSurf->vbo->stride_xyz = sizeof(*verts); + vboSurf->vbo->stride_normal = sizeof(*normals); +#ifdef USE_VERT_TANGENT_SPACE + vboSurf->vbo->stride_tangent = sizeof(*tangents); + vboSurf->vbo->stride_bitangent = sizeof(*bitangents); +#endif + vboSurf->vbo->stride_st = sizeof(*st); + + vboSurf->vbo->size_xyz = sizeof(*verts) * surf->numVerts; + vboSurf->vbo->size_normal = sizeof(*normals) * surf->numVerts; + + ri.Free(data); + + vboSurf->ibo = R_CreateIBO2(va("staticMD3Mesh_IBO %s", surf->name), surf->numTriangles, surf->triangles, VBO_USAGE_STATIC); + } + } + + return qtrue; +} + + +#ifdef RAVENMD4 + +/* +================= +R_LoadMDR +================= +*/ +static qboolean R_LoadMDR( model_t *mod, void *buffer, int filesize, const char *mod_name ) +{ + int i, j, k, l; + mdrHeader_t *pinmodel, *mdr; + mdrFrame_t *frame; + mdrLOD_t *lod, *curlod; + mdrSurface_t *surf, *cursurf; + mdrTriangle_t *tri, *curtri; + mdrVertex_t *v, *curv; + mdrWeight_t *weight, *curweight; + mdrTag_t *tag, *curtag; + int size; + shader_t *sh; + + pinmodel = (mdrHeader_t *)buffer; + + pinmodel->version = LittleLong(pinmodel->version); + if (pinmodel->version != MDR_VERSION) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has wrong version (%i should be %i)\n", mod_name, pinmodel->version, MDR_VERSION); + return qfalse; + } + + size = LittleLong(pinmodel->ofsEnd); + + if(size > filesize) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: Header of %s is broken. Wrong filesize declared!\n", mod_name); + return qfalse; + } + + mod->type = MOD_MDR; + + LL(pinmodel->numFrames); + LL(pinmodel->numBones); + LL(pinmodel->ofsFrames); + + // This is a model that uses some type of compressed Bones. We don't want to uncompress every bone for each rendered frame + // over and over again, we'll uncompress it in this function already, so we must adjust the size of the target md4. + if(pinmodel->ofsFrames < 0) + { + // mdrFrame_t is larger than mdrCompFrame_t: + size += pinmodel->numFrames * sizeof(frame->name); + // now add enough space for the uncompressed bones. + size += pinmodel->numFrames * pinmodel->numBones * ((sizeof(mdrBone_t) - sizeof(mdrCompBone_t))); + } + + // simple bounds check + if(pinmodel->numBones < 0 || + sizeof(*mdr) + pinmodel->numFrames * (sizeof(*frame) + (pinmodel->numBones - 1) * sizeof(*frame->bones)) > size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return qfalse; + } + + mod->dataSize += size; + mod->modelData = mdr = ri.Hunk_Alloc( size, h_low ); + + // Copy all the values over from the file and fix endian issues in the process, if necessary. + + mdr->ident = LittleLong(pinmodel->ident); + mdr->version = pinmodel->version; // Don't need to swap byte order on this one, we already did above. + Q_strncpyz(mdr->name, pinmodel->name, sizeof(mdr->name)); + mdr->numFrames = pinmodel->numFrames; + mdr->numBones = pinmodel->numBones; + mdr->numLODs = LittleLong(pinmodel->numLODs); + mdr->numTags = LittleLong(pinmodel->numTags); + // We don't care about the other offset values, we'll generate them ourselves while loading. + + mod->numLods = mdr->numLODs; + + if ( mdr->numFrames < 1 ) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has no frames\n", mod_name); + return qfalse; + } + + /* The first frame will be put into the first free space after the header */ + frame = (mdrFrame_t *)(mdr + 1); + mdr->ofsFrames = (int)((byte *) frame - (byte *) mdr); + + if (pinmodel->ofsFrames < 0) + { + mdrCompFrame_t *cframe; + + // compressed model... + cframe = (mdrCompFrame_t *)((byte *) pinmodel - pinmodel->ofsFrames); + + for(i = 0; i < mdr->numFrames; i++) + { + for(j = 0; j < 3; j++) + { + frame->bounds[0][j] = LittleFloat(cframe->bounds[0][j]); + frame->bounds[1][j] = LittleFloat(cframe->bounds[1][j]); + frame->localOrigin[j] = LittleFloat(cframe->localOrigin[j]); + } + + frame->radius = LittleFloat(cframe->radius); + frame->name[0] = '\0'; // No name supplied in the compressed version. + + for(j = 0; j < mdr->numBones; j++) + { + for(k = 0; k < (sizeof(cframe->bones[j].Comp) / 2); k++) + { + // Do swapping for the uncompressing functions. They seem to use shorts + // values only, so I assume this will work. Never tested it on other + // platforms, though. + + ((unsigned short *)(cframe->bones[j].Comp))[k] = + LittleShort( ((unsigned short *)(cframe->bones[j].Comp))[k] ); + } + + /* Now do the actual uncompressing */ + MC_UnCompress(frame->bones[j].matrix, cframe->bones[j].Comp); + } + + // Next Frame... + cframe = (mdrCompFrame_t *) &cframe->bones[j]; + frame = (mdrFrame_t *) &frame->bones[j]; + } + } + else + { + mdrFrame_t *curframe; + + // uncompressed model... + // + + curframe = (mdrFrame_t *)((byte *) pinmodel + pinmodel->ofsFrames); + + // swap all the frames + for ( i = 0 ; i < mdr->numFrames ; i++) + { + for(j = 0; j < 3; j++) + { + frame->bounds[0][j] = LittleFloat(curframe->bounds[0][j]); + frame->bounds[1][j] = LittleFloat(curframe->bounds[1][j]); + frame->localOrigin[j] = LittleFloat(curframe->localOrigin[j]); + } + + frame->radius = LittleFloat(curframe->radius); + Q_strncpyz(frame->name, curframe->name, sizeof(frame->name)); + + for (j = 0; j < (int) (mdr->numBones * sizeof(mdrBone_t) / 4); j++) + { + ((float *)frame->bones)[j] = LittleFloat( ((float *)curframe->bones)[j] ); + } + + curframe = (mdrFrame_t *) &curframe->bones[mdr->numBones]; + frame = (mdrFrame_t *) &frame->bones[mdr->numBones]; + } + } + + // frame should now point to the first free address after all frames. + lod = (mdrLOD_t *) frame; + mdr->ofsLODs = (int) ((byte *) lod - (byte *)mdr); + + curlod = (mdrLOD_t *)((byte *) pinmodel + LittleLong(pinmodel->ofsLODs)); + + // swap all the LOD's + for ( l = 0 ; l < mdr->numLODs ; l++) + { + // simple bounds check + if((byte *) (lod + 1) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return qfalse; + } + + lod->numSurfaces = LittleLong(curlod->numSurfaces); + + // swap all the surfaces + surf = (mdrSurface_t *) (lod + 1); + lod->ofsSurfaces = (int)((byte *) surf - (byte *) lod); + cursurf = (mdrSurface_t *) ((byte *)curlod + LittleLong(curlod->ofsSurfaces)); + + for ( i = 0 ; i < lod->numSurfaces ; i++) + { + // simple bounds check + if((byte *) (surf + 1) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return qfalse; + } + + // first do some copying stuff + + surf->ident = SF_MDR; + Q_strncpyz(surf->name, cursurf->name, sizeof(surf->name)); + Q_strncpyz(surf->shader, cursurf->shader, sizeof(surf->shader)); + + surf->ofsHeader = (byte *) mdr - (byte *) surf; + + surf->numVerts = LittleLong(cursurf->numVerts); + surf->numTriangles = LittleLong(cursurf->numTriangles); + // numBoneReferences and BoneReferences generally seem to be unused + + // now do the checks that may fail. + if ( surf->numVerts > SHADER_MAX_VERTEXES ) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has more than %i verts on a surface (%i).\n", + mod_name, SHADER_MAX_VERTEXES, surf->numVerts ); + return qfalse; + } + if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has more than %i triangles on a surface (%i).\n", + mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + return qfalse; + } + // lowercase the surface name so skin compares are faster + Q_strlwr( surf->name ); + + // register the shaders + sh = R_FindShader(surf->shader, LIGHTMAP_NONE, qtrue); + if ( sh->defaultShader ) { + surf->shaderIndex = 0; + } else { + surf->shaderIndex = sh->index; + } + + // now copy the vertexes. + v = (mdrVertex_t *) (surf + 1); + surf->ofsVerts = (int)((byte *) v - (byte *) surf); + curv = (mdrVertex_t *) ((byte *)cursurf + LittleLong(cursurf->ofsVerts)); + + for(j = 0; j < surf->numVerts; j++) + { + LL(curv->numWeights); + + // simple bounds check + if(curv->numWeights < 0 || (byte *) (v + 1) + (curv->numWeights - 1) * sizeof(*weight) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return qfalse; + } + + v->normal[0] = LittleFloat(curv->normal[0]); + v->normal[1] = LittleFloat(curv->normal[1]); + v->normal[2] = LittleFloat(curv->normal[2]); + + v->texCoords[0] = LittleFloat(curv->texCoords[0]); + v->texCoords[1] = LittleFloat(curv->texCoords[1]); + + v->numWeights = curv->numWeights; + weight = &v->weights[0]; + curweight = &curv->weights[0]; + + // Now copy all the weights + for(k = 0; k < v->numWeights; k++) + { + weight->boneIndex = LittleLong(curweight->boneIndex); + weight->boneWeight = LittleFloat(curweight->boneWeight); + + weight->offset[0] = LittleFloat(curweight->offset[0]); + weight->offset[1] = LittleFloat(curweight->offset[1]); + weight->offset[2] = LittleFloat(curweight->offset[2]); + + weight++; + curweight++; + } + + v = (mdrVertex_t *) weight; + curv = (mdrVertex_t *) curweight; + } + + // we know the offset to the triangles now: + tri = (mdrTriangle_t *) v; + surf->ofsTriangles = (int)((byte *) tri - (byte *) surf); + curtri = (mdrTriangle_t *)((byte *) cursurf + LittleLong(cursurf->ofsTriangles)); + + // simple bounds check + if(surf->numTriangles < 0 || (byte *) (tri + surf->numTriangles) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return qfalse; + } + + for(j = 0; j < surf->numTriangles; j++) + { + tri->indexes[0] = LittleLong(curtri->indexes[0]); + tri->indexes[1] = LittleLong(curtri->indexes[1]); + tri->indexes[2] = LittleLong(curtri->indexes[2]); + + tri++; + curtri++; + } + + // tri now points to the end of the surface. + surf->ofsEnd = (byte *) tri - (byte *) surf; + surf = (mdrSurface_t *) tri; + + // find the next surface. + cursurf = (mdrSurface_t *) ((byte *) cursurf + LittleLong(cursurf->ofsEnd)); + } + + // surf points to the next lod now. + lod->ofsEnd = (int)((byte *) surf - (byte *) lod); + lod = (mdrLOD_t *) surf; + + // find the next LOD. + curlod = (mdrLOD_t *)((byte *) curlod + LittleLong(curlod->ofsEnd)); + } + + // lod points to the first tag now, so update the offset too. + tag = (mdrTag_t *) lod; + mdr->ofsTags = (int)((byte *) tag - (byte *) mdr); + curtag = (mdrTag_t *) ((byte *)pinmodel + LittleLong(pinmodel->ofsTags)); + + // simple bounds check + if(mdr->numTags < 0 || (byte *) (tag + mdr->numTags) > (byte *) mdr + size) + { + ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has broken structure.\n", mod_name); + return qfalse; + } + + for (i = 0 ; i < mdr->numTags ; i++) + { + tag->boneIndex = LittleLong(curtag->boneIndex); + Q_strncpyz(tag->name, curtag->name, sizeof(tag->name)); + + tag++; + curtag++; + } + + // And finally we know the real offset to the end. + mdr->ofsEnd = (int)((byte *) tag - (byte *) mdr); + + // phew! we're done. + + return qtrue; +} +#endif + +/* +================= +R_LoadMD4 +================= +*/ + +static qboolean R_LoadMD4( model_t *mod, void *buffer, const char *mod_name ) { + int i, j, k, lodindex; + md4Header_t *pinmodel, *md4; + md4Frame_t *frame; + md4LOD_t *lod; + md4Surface_t *surf; + md4Triangle_t *tri; + md4Vertex_t *v; + int version; + int size; + shader_t *sh; + int frameSize; + + pinmodel = (md4Header_t *)buffer; + + version = LittleLong (pinmodel->version); + if (version != MD4_VERSION) { + ri.Printf( PRINT_WARNING, "R_LoadMD4: %s has wrong version (%i should be %i)\n", + mod_name, version, MD4_VERSION); + return qfalse; + } + + mod->type = MOD_MD4; + size = LittleLong(pinmodel->ofsEnd); + mod->dataSize += size; + mod->modelData = md4 = ri.Hunk_Alloc( size, h_low ); + + Com_Memcpy(md4, buffer, size); + + LL(md4->ident); + LL(md4->version); + LL(md4->numFrames); + LL(md4->numBones); + LL(md4->numLODs); + LL(md4->ofsFrames); + LL(md4->ofsLODs); + md4->ofsEnd = size; + + if ( md4->numFrames < 1 ) { + ri.Printf( PRINT_WARNING, "R_LoadMD4: %s has no frames\n", mod_name ); + return qfalse; + } + + // we don't need to swap tags in the renderer, they aren't used + + // swap all the frames + frameSize = (size_t)( &((md4Frame_t *)0)->bones[ md4->numBones ] ); + for ( i = 0 ; i < md4->numFrames ; i++) { + frame = (md4Frame_t *) ( (byte *)md4 + md4->ofsFrames + i * frameSize ); + frame->radius = LittleFloat( frame->radius ); + for ( j = 0 ; j < 3 ; j++ ) { + frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] ); + frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] ); + frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); + } + for ( j = 0 ; j < md4->numBones * sizeof( md4Bone_t ) / 4 ; j++ ) { + ((float *)frame->bones)[j] = LittleFloat( ((float *)frame->bones)[j] ); + } + } + + // swap all the LOD's + lod = (md4LOD_t *) ( (byte *)md4 + md4->ofsLODs ); + for ( lodindex = 0 ; lodindex < md4->numLODs ; lodindex++ ) { + + // swap all the surfaces + surf = (md4Surface_t *) ( (byte *)lod + lod->ofsSurfaces ); + for ( i = 0 ; i < lod->numSurfaces ; i++) { + LL(surf->ident); + LL(surf->numTriangles); + LL(surf->ofsTriangles); + LL(surf->numVerts); + LL(surf->ofsVerts); + LL(surf->ofsEnd); + + if ( surf->numVerts > SHADER_MAX_VERTEXES ) { + ri.Printf(PRINT_WARNING, "R_LoadMD4: %s has more than %i verts on a surface (%i).\n", + mod_name, SHADER_MAX_VERTEXES, surf->numVerts ); + return qfalse; + } + if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) { + ri.Printf(PRINT_WARNING, "R_LoadMD4: %s has more than %i triangles on a surface (%i).\n", + mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + return qfalse; + } + + // change to surface identifier + surf->ident = SF_MD4; + + // lowercase the surface name so skin compares are faster + Q_strlwr( surf->name ); + + // register the shaders + sh = R_FindShader( surf->shader, LIGHTMAP_NONE, qtrue ); + if ( sh->defaultShader ) { + surf->shaderIndex = 0; + } else { + surf->shaderIndex = sh->index; + } + + // swap all the triangles + tri = (md4Triangle_t *) ( (byte *)surf + surf->ofsTriangles ); + for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) { + LL(tri->indexes[0]); + LL(tri->indexes[1]); + LL(tri->indexes[2]); + } + + // swap all the vertexes + // FIXME + // This makes TFC's skeletons work. Shouldn't be necessary anymore, but left + // in for reference. + //v = (md4Vertex_t *) ( (byte *)surf + surf->ofsVerts + 12); + v = (md4Vertex_t *) ( (byte *)surf + surf->ofsVerts); + for ( j = 0 ; j < surf->numVerts ; j++ ) { + v->normal[0] = LittleFloat( v->normal[0] ); + v->normal[1] = LittleFloat( v->normal[1] ); + v->normal[2] = LittleFloat( v->normal[2] ); + + v->texCoords[0] = LittleFloat( v->texCoords[0] ); + v->texCoords[1] = LittleFloat( v->texCoords[1] ); + + v->numWeights = LittleLong( v->numWeights ); + + for ( k = 0 ; k < v->numWeights ; k++ ) { + v->weights[k].boneIndex = LittleLong( v->weights[k].boneIndex ); + v->weights[k].boneWeight = LittleFloat( v->weights[k].boneWeight ); + v->weights[k].offset[0] = LittleFloat( v->weights[k].offset[0] ); + v->weights[k].offset[1] = LittleFloat( v->weights[k].offset[1] ); + v->weights[k].offset[2] = LittleFloat( v->weights[k].offset[2] ); + } + // FIXME + // This makes TFC's skeletons work. Shouldn't be necessary anymore, but left + // in for reference. + //v = (md4Vertex_t *)( ( byte * )&v->weights[v->numWeights] + 12 ); + v = (md4Vertex_t *)( ( byte * )&v->weights[v->numWeights]); + } + + // find the next surface + surf = (md4Surface_t *)( (byte *)surf + surf->ofsEnd ); + } + + // find the next LOD + lod = (md4LOD_t *)( (byte *)lod + lod->ofsEnd ); + } + + return qtrue; +} + + + +//============================================================================= + +/* +** RE_BeginRegistration +*/ +void RE_BeginRegistration( glconfig_t *glconfigOut ) { + + R_Init(); + + *glconfigOut = glConfig; + + R_SyncRenderThread(); + + tr.visIndex = 0; + memset(tr.visClusters, -2, sizeof(tr.visClusters)); // force markleafs to regenerate + + R_ClearFlares(); + RE_ClearScene(); + + tr.registered = qtrue; + + // NOTE: this sucks, for some reason the first stretch pic is never drawn + // without this we'd see a white flash on a level load because the very + // first time the level shot would not be drawn +// RE_StretchPic(0, 0, 0, 0, 0, 0, 1, 1, 0); +} + +//============================================================================= + +/* +=============== +R_ModelInit +=============== +*/ +void R_ModelInit( void ) { + model_t *mod; + + // leave a space for NULL model + tr.numModels = 0; + + mod = R_AllocModel(); + mod->type = MOD_BAD; +} + + +/* +================ +R_Modellist_f +================ +*/ +void R_Modellist_f( void ) { + int i, j; + model_t *mod; + int total; + int lods; + + total = 0; + for ( i = 1 ; i < tr.numModels; i++ ) { + mod = tr.models[i]; + lods = 1; + for ( j = 1 ; j < MD3_MAX_LODS ; j++ ) { + if ( mod->mdv[j] && mod->mdv[j] != mod->mdv[j-1] ) { + lods++; + } + } + ri.Printf( PRINT_ALL, "%8i : (%i) %s\n",mod->dataSize, lods, mod->name ); + total += mod->dataSize; + } + ri.Printf( PRINT_ALL, "%8i : Total models\n", total ); + +#if 0 // not working right with new hunk + if ( tr.world ) { + ri.Printf( PRINT_ALL, "\n%8i : %s\n", tr.world->dataSize, tr.world->name ); + } +#endif +} + + +//============================================================================= + + +/* +================ +R_GetTag +================ +*/ +static mdvTag_t *R_GetTag( mdvModel_t *mod, int frame, const char *_tagName ) { + int i; + mdvTag_t *tag; + mdvTagName_t *tagName; + + if ( frame >= mod->numFrames ) { + // it is possible to have a bad frame while changing models, so don't error + frame = mod->numFrames - 1; + } + + tag = mod->tags + frame * mod->numTags; + tagName = mod->tagNames; + for(i = 0; i < mod->numTags; i++, tag++, tagName++) + { + if(!strcmp(tagName->name, _tagName)) + { + return tag; + } + } + + return NULL; +} + +#ifdef RAVENMD4 +void R_GetAnimTag( mdrHeader_t *mod, int framenum, const char *tagName, md3Tag_t * dest) +{ + int i, j, k; + int frameSize; + mdrFrame_t *frame; + mdrTag_t *tag; + + if ( framenum >= mod->numFrames ) + { + // it is possible to have a bad frame while changing models, so don't error + framenum = mod->numFrames - 1; + } + + tag = (mdrTag_t *)((byte *)mod + mod->ofsTags); + for ( i = 0 ; i < mod->numTags ; i++, tag++ ) + { + if ( !strcmp( tag->name, tagName ) ) + { + Q_strncpyz(dest->name, tag->name, sizeof(dest->name)); + + // uncompressed model... + // + frameSize = (intptr_t)( &((mdrFrame_t *)0)->bones[ mod->numBones ] ); + frame = (mdrFrame_t *)((byte *)mod + mod->ofsFrames + framenum * frameSize ); + + for (j = 0; j < 3; j++) + { + for (k = 0; k < 3; k++) + dest->axis[j][k]=frame->bones[tag->boneIndex].matrix[k][j]; + } + + dest->origin[0]=frame->bones[tag->boneIndex].matrix[0][3]; + dest->origin[1]=frame->bones[tag->boneIndex].matrix[1][3]; + dest->origin[2]=frame->bones[tag->boneIndex].matrix[2][3]; + + return; + } + } + + AxisClear( dest->axis ); + VectorClear( dest->origin ); + strcpy(dest->name,""); +} +#endif + +/* +================ +R_LerpTag +================ +*/ +int R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, + float frac, const char *tagName ) { + mdvTag_t *start, *end; +#ifdef RAVENMD4 + md3Tag_t start_space, end_space; +#endif + int i; + float frontLerp, backLerp; + model_t *model; + + model = R_GetModelByHandle( handle ); + if ( !model->mdv[0] ) + { +#ifdef RAVENMD4 + if(model->type == MOD_MDR) + { + start = &start_space; + end = &end_space; + R_GetAnimTag((mdrHeader_t *) model->modelData, startFrame, tagName, start); + R_GetAnimTag((mdrHeader_t *) model->modelData, endFrame, tagName, end); + } + else +#endif + if( model->type == MOD_IQM ) { + return R_IQMLerpTag( tag, model->modelData, + startFrame, endFrame, + frac, tagName ); + } else { + + AxisClear( tag->axis ); + VectorClear( tag->origin ); + return qfalse; + + } + } + else + { + start = R_GetTag( model->mdv[0], startFrame, tagName ); + end = R_GetTag( model->mdv[0], endFrame, tagName ); + if ( !start || !end ) { + AxisClear( tag->axis ); + VectorClear( tag->origin ); + return qfalse; + } + } + + frontLerp = frac; + backLerp = 1.0f - frac; + + for ( i = 0 ; i < 3 ; i++ ) { + tag->origin[i] = start->origin[i] * backLerp + end->origin[i] * frontLerp; + tag->axis[0][i] = start->axis[0][i] * backLerp + end->axis[0][i] * frontLerp; + tag->axis[1][i] = start->axis[1][i] * backLerp + end->axis[1][i] * frontLerp; + tag->axis[2][i] = start->axis[2][i] * backLerp + end->axis[2][i] * frontLerp; + } + VectorNormalize( tag->axis[0] ); + VectorNormalize( tag->axis[1] ); + VectorNormalize( tag->axis[2] ); + return qtrue; +} + + +/* +==================== +R_ModelBounds +==================== +*/ +void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ) { + model_t *model; + + model = R_GetModelByHandle( handle ); + + if(model->type == MOD_BRUSH) { + VectorCopy( model->bmodel->bounds[0], mins ); + VectorCopy( model->bmodel->bounds[1], maxs ); + + return; + } else if (model->type == MOD_MESH) { + mdvModel_t *header; + mdvFrame_t *frame; + + header = model->mdv[0]; + frame = header->frames; + + VectorCopy( frame->bounds[0], mins ); + VectorCopy( frame->bounds[1], maxs ); + + return; + } else if (model->type == MOD_MD4) { + md4Header_t *header; + md4Frame_t *frame; + + header = (md4Header_t *)model->modelData; + frame = (md4Frame_t *) ((byte *)header + header->ofsFrames); + + VectorCopy( frame->bounds[0], mins ); + VectorCopy( frame->bounds[1], maxs ); + + return; +#ifdef RAVENMD4 + } else if (model->type == MOD_MDR) { + mdrHeader_t *header; + mdrFrame_t *frame; + + header = (mdrHeader_t *)model->modelData; + frame = (mdrFrame_t *) ((byte *)header + header->ofsFrames); + + VectorCopy( frame->bounds[0], mins ); + VectorCopy( frame->bounds[1], maxs ); + + return; +#endif + } else if(model->type == MOD_IQM) { + iqmData_t *iqmData; + + iqmData = model->modelData; + + if(iqmData->bounds) + { + VectorCopy(iqmData->bounds, mins); + VectorCopy(iqmData->bounds + 3, maxs); + return; + } + } + + VectorClear( mins ); + VectorClear( maxs ); +} diff --git a/src/rend2/tr_model_iqm.c b/src/rend2/tr_model_iqm.c new file mode 100644 index 00000000..1f1bf747 --- /dev/null +++ b/src/rend2/tr_model_iqm.c @@ -0,0 +1,1058 @@ +/* +=========================================================================== +Copyright (C) 2011 Thilo Schulz +Copyright (C) 2011 Matthias Bentrup + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "tr_local.h" + +#define LL(x) x=LittleLong(x) + +static qboolean IQM_CheckRange( iqmHeader_t *header, int offset, + int count,int size ) { + // return true if the range specified by offset, count and size + // doesn't fit into the file + return ( count <= 0 || + offset < 0 || + offset > header->filesize || + offset + count * size < 0 || + offset + count * size > header->filesize ); +} +// "multiply" 3x4 matrices, these are assumed to be the top 3 rows +// of a 4x4 matrix with the last row = (0 0 0 1) +static void Matrix34Multiply( float *a, float *b, float *out ) { + out[ 0] = a[0] * b[0] + a[1] * b[4] + a[ 2] * b[ 8]; + out[ 1] = a[0] * b[1] + a[1] * b[5] + a[ 2] * b[ 9]; + out[ 2] = a[0] * b[2] + a[1] * b[6] + a[ 2] * b[10]; + out[ 3] = a[0] * b[3] + a[1] * b[7] + a[ 2] * b[11] + a[ 3]; + out[ 4] = a[4] * b[0] + a[5] * b[4] + a[ 6] * b[ 8]; + out[ 5] = a[4] * b[1] + a[5] * b[5] + a[ 6] * b[ 9]; + out[ 6] = a[4] * b[2] + a[5] * b[6] + a[ 6] * b[10]; + out[ 7] = a[4] * b[3] + a[5] * b[7] + a[ 6] * b[11] + a[ 7]; + out[ 8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[ 8]; + out[ 9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[ 9]; + out[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10]; + out[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11]; +} +static void InterpolateMatrix( float *a, float *b, float lerp, float *mat ) { + float unLerp = 1.0f - lerp; + + mat[ 0] = a[ 0] * unLerp + b[ 0] * lerp; + mat[ 1] = a[ 1] * unLerp + b[ 1] * lerp; + mat[ 2] = a[ 2] * unLerp + b[ 2] * lerp; + mat[ 3] = a[ 3] * unLerp + b[ 3] * lerp; + mat[ 4] = a[ 4] * unLerp + b[ 4] * lerp; + mat[ 5] = a[ 5] * unLerp + b[ 5] * lerp; + mat[ 6] = a[ 6] * unLerp + b[ 6] * lerp; + mat[ 7] = a[ 7] * unLerp + b[ 7] * lerp; + mat[ 8] = a[ 8] * unLerp + b[ 8] * lerp; + mat[ 9] = a[ 9] * unLerp + b[ 9] * lerp; + mat[10] = a[10] * unLerp + b[10] * lerp; + mat[11] = a[11] * unLerp + b[11] * lerp; +} +static void JointToMatrix( vec4_t rot, vec3_t scale, vec3_t trans, + float *mat ) { + float xx = 2.0f * rot[0] * rot[0]; + float yy = 2.0f * rot[1] * rot[1]; + float zz = 2.0f * rot[2] * rot[2]; + float xy = 2.0f * rot[0] * rot[1]; + float xz = 2.0f * rot[0] * rot[2]; + float yz = 2.0f * rot[1] * rot[2]; + float wx = 2.0f * rot[3] * rot[0]; + float wy = 2.0f * rot[3] * rot[1]; + float wz = 2.0f * rot[3] * rot[2]; + + mat[ 0] = scale[0] * (1.0f - (yy + zz)); + mat[ 1] = scale[0] * (xy - wz); + mat[ 2] = scale[0] * (xz + wy); + mat[ 3] = trans[0]; + mat[ 4] = scale[1] * (xy + wz); + mat[ 5] = scale[1] * (1.0f - (xx + zz)); + mat[ 6] = scale[1] * (yz - wx); + mat[ 7] = trans[1]; + mat[ 8] = scale[2] * (xz - wy); + mat[ 9] = scale[2] * (yz + wx); + mat[10] = scale[2] * (1.0f - (xx + yy)); + mat[11] = trans[2]; +} +static void Matrix34Invert( float *inMat, float *outMat ) +{ + vec3_t trans; + float invSqrLen, *v; + + outMat[ 0] = inMat[ 0]; outMat[ 1] = inMat[ 4]; outMat[ 2] = inMat[ 8]; + outMat[ 4] = inMat[ 1]; outMat[ 5] = inMat[ 5]; outMat[ 6] = inMat[ 9]; + outMat[ 8] = inMat[ 2]; outMat[ 9] = inMat[ 6]; outMat[10] = inMat[10]; + + v = outMat + 0; invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v); + v = outMat + 4; invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v); + v = outMat + 8; invSqrLen = 1.0f / DotProduct(v, v); VectorScale(v, invSqrLen, v); + + trans[0] = inMat[ 3]; + trans[1] = inMat[ 7]; + trans[2] = inMat[11]; + + outMat[ 3] = -DotProduct(outMat + 0, trans); + outMat[ 7] = -DotProduct(outMat + 4, trans); + outMat[11] = -DotProduct(outMat + 8, trans); +} + +/* +================= +R_LoadIQM + +Load an IQM model and compute the joint matrices for every frame. +================= +*/ +qboolean R_LoadIQM( model_t *mod, void *buffer, int filesize, const char *mod_name ) { + iqmHeader_t *header; + iqmVertexArray_t *vertexarray; + iqmTriangle_t *triangle; + iqmMesh_t *mesh; + iqmJoint_t *joint; + iqmPose_t *pose; + iqmBounds_t *bounds; + unsigned short *framedata; + char *str; + int i, j; + float jointMats[IQM_MAX_JOINTS * 2 * 12]; + float *mat; + size_t size, joint_names; + iqmData_t *iqmData; + srfIQModel_t *surface; + + if( filesize < sizeof(iqmHeader_t) ) { + return qfalse; + } + + header = (iqmHeader_t *)buffer; + if( Q_strncmp( header->magic, IQM_MAGIC, sizeof(header->magic) ) ) { + return qfalse; + } + + LL( header->version ); + if( header->version != IQM_VERSION ) { + ri.Printf(PRINT_WARNING, "R_LoadIQM: %s is a unsupported IQM version (%d), only version %d is supported.\n", + mod_name, header->version, IQM_VERSION); + return qfalse; + } + + LL( header->filesize ); + if( header->filesize > filesize || header->filesize > 16<<20 ) { + return qfalse; + } + + LL( header->flags ); + LL( header->num_text ); + LL( header->ofs_text ); + LL( header->num_meshes ); + LL( header->ofs_meshes ); + LL( header->num_vertexarrays ); + LL( header->num_vertexes ); + LL( header->ofs_vertexarrays ); + LL( header->num_triangles ); + LL( header->ofs_triangles ); + LL( header->ofs_adjacency ); + LL( header->num_joints ); + LL( header->ofs_joints ); + LL( header->num_poses ); + LL( header->ofs_poses ); + LL( header->num_anims ); + LL( header->ofs_anims ); + LL( header->num_frames ); + LL( header->num_framechannels ); + LL( header->ofs_frames ); + LL( header->ofs_bounds ); + LL( header->num_comment ); + LL( header->ofs_comment ); + LL( header->num_extensions ); + LL( header->ofs_extensions ); + + // check ioq3 joint limit + if ( header->num_joints > IQM_MAX_JOINTS ) { + ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has more than %d joints (%d).\n", + mod_name, IQM_MAX_JOINTS, header->num_joints); + return qfalse; + } + + // check and swap vertex arrays + if( IQM_CheckRange( header, header->ofs_vertexarrays, + header->num_vertexarrays, + sizeof(iqmVertexArray_t) ) ) { + return qfalse; + } + vertexarray = (iqmVertexArray_t *)((byte *)header + header->ofs_vertexarrays); + for( i = 0; i < header->num_vertexarrays; i++, vertexarray++ ) { + int j, n, *intPtr; + + if( vertexarray->size <= 0 || vertexarray->size > 4 ) { + return qfalse; + } + + // total number of values + n = header->num_vertexes * vertexarray->size; + + switch( vertexarray->format ) { + case IQM_BYTE: + case IQM_UBYTE: + // 1 byte, no swapping necessary + if( IQM_CheckRange( header, vertexarray->offset, + n, sizeof(byte) ) ) { + return qfalse; + } + break; + case IQM_INT: + case IQM_UINT: + case IQM_FLOAT: + // 4-byte swap + if( IQM_CheckRange( header, vertexarray->offset, + n, sizeof(float) ) ) { + return qfalse; + } + intPtr = (int *)((byte *)header + vertexarray->offset); + for( j = 0; j < n; j++, intPtr++ ) { + LL( *intPtr ); + } + break; + default: + // not supported + return qfalse; + break; + } + + switch( vertexarray->type ) { + case IQM_POSITION: + case IQM_NORMAL: + if( vertexarray->format != IQM_FLOAT || + vertexarray->size != 3 ) { + return qfalse; + } + break; + case IQM_TANGENT: + if( vertexarray->format != IQM_FLOAT || + vertexarray->size != 4 ) { + return qfalse; + } + break; + case IQM_TEXCOORD: + if( vertexarray->format != IQM_FLOAT || + vertexarray->size != 2 ) { + return qfalse; + } + break; + case IQM_BLENDINDEXES: + case IQM_BLENDWEIGHTS: + if( vertexarray->format != IQM_UBYTE || + vertexarray->size != 4 ) { + return qfalse; + } + break; + case IQM_COLOR: + if( vertexarray->format != IQM_UBYTE || + vertexarray->size != 4 ) { + return qfalse; + } + break; + } + } + + // check and swap triangles + if( IQM_CheckRange( header, header->ofs_triangles, + header->num_triangles, sizeof(iqmTriangle_t) ) ) { + return qfalse; + } + triangle = (iqmTriangle_t *)((byte *)header + header->ofs_triangles); + for( i = 0; i < header->num_triangles; i++, triangle++ ) { + LL( triangle->vertex[0] ); + LL( triangle->vertex[1] ); + LL( triangle->vertex[2] ); + + if( triangle->vertex[0] > header->num_vertexes || + triangle->vertex[1] > header->num_vertexes || + triangle->vertex[2] > header->num_vertexes ) { + return qfalse; + } + } + + // check and swap meshes + if( IQM_CheckRange( header, header->ofs_meshes, + header->num_meshes, sizeof(iqmMesh_t) ) ) { + return qfalse; + } + mesh = (iqmMesh_t *)((byte *)header + header->ofs_meshes); + for( i = 0; i < header->num_meshes; i++, mesh++) { + LL( mesh->name ); + LL( mesh->material ); + LL( mesh->first_vertex ); + LL( mesh->num_vertexes ); + LL( mesh->first_triangle ); + LL( mesh->num_triangles ); + + // check ioq3 limits + if ( mesh->num_vertexes > SHADER_MAX_VERTEXES ) + { + ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has more than %i verts on a surface (%i).\n", + mod_name, SHADER_MAX_VERTEXES, mesh->num_vertexes ); + return qfalse; + } + if ( mesh->num_triangles*3 > SHADER_MAX_INDEXES ) + { + ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has more than %i triangles on a surface (%i).\n", + mod_name, SHADER_MAX_INDEXES / 3, mesh->num_triangles ); + return qfalse; + } + + if( mesh->first_vertex >= header->num_vertexes || + mesh->first_vertex + mesh->num_vertexes > header->num_vertexes || + mesh->first_triangle >= header->num_triangles || + mesh->first_triangle + mesh->num_triangles > header->num_triangles || + mesh->name >= header->num_text || + mesh->material >= header->num_text ) { + return qfalse; + } + } + + // check and swap joints + if( IQM_CheckRange( header, header->ofs_joints, + header->num_joints, sizeof(iqmJoint_t) ) ) { + return qfalse; + } + joint = (iqmJoint_t *)((byte *)header + header->ofs_joints); + joint_names = 0; + for( i = 0; i < header->num_joints; i++, joint++ ) { + LL( joint->name ); + LL( joint->parent ); + LL( joint->translate[0] ); + LL( joint->translate[1] ); + LL( joint->translate[2] ); + LL( joint->rotate[0] ); + LL( joint->rotate[1] ); + LL( joint->rotate[2] ); + LL( joint->rotate[3] ); + LL( joint->scale[0] ); + LL( joint->scale[1] ); + LL( joint->scale[2] ); + + if( joint->parent < -1 || + joint->parent >= (int)header->num_joints || + joint->name >= (int)header->num_text ) { + return qfalse; + } + joint_names += strlen( (char *)header + header->ofs_text + + joint->name ) + 1; + } + + // check and swap poses + if( header->num_poses != header->num_joints ) { + return qfalse; + } + if( IQM_CheckRange( header, header->ofs_poses, + header->num_poses, sizeof(iqmPose_t) ) ) { + return qfalse; + } + pose = (iqmPose_t *)((byte *)header + header->ofs_poses); + for( i = 0; i < header->num_poses; i++, pose++ ) { + LL( pose->parent ); + LL( pose->mask ); + LL( pose->channeloffset[0] ); + LL( pose->channeloffset[1] ); + LL( pose->channeloffset[2] ); + LL( pose->channeloffset[3] ); + LL( pose->channeloffset[4] ); + LL( pose->channeloffset[5] ); + LL( pose->channeloffset[6] ); + LL( pose->channeloffset[7] ); + LL( pose->channeloffset[8] ); + LL( pose->channeloffset[9] ); + LL( pose->channelscale[0] ); + LL( pose->channelscale[1] ); + LL( pose->channelscale[2] ); + LL( pose->channelscale[3] ); + LL( pose->channelscale[4] ); + LL( pose->channelscale[5] ); + LL( pose->channelscale[6] ); + LL( pose->channelscale[7] ); + LL( pose->channelscale[8] ); + LL( pose->channelscale[9] ); + } + + if (header->ofs_bounds) + { + // check and swap model bounds + if(IQM_CheckRange(header, header->ofs_bounds, + header->num_frames, sizeof(*bounds))) + { + return qfalse; + } + bounds = (iqmBounds_t *) ((byte *) header + header->ofs_bounds); + for(i = 0; i < header->num_frames; i++) + { + LL(bounds->bbmin[0]); + LL(bounds->bbmin[1]); + LL(bounds->bbmin[2]); + LL(bounds->bbmax[0]); + LL(bounds->bbmax[1]); + LL(bounds->bbmax[2]); + + bounds++; + } + } + + // allocate the model and copy the data + size = sizeof(iqmData_t); + size += header->num_meshes * sizeof( srfIQModel_t ); + size += header->num_joints * header->num_frames * 12 * sizeof( float ); + if(header->ofs_bounds) + size += header->num_frames * 6 * sizeof(float); // model bounds + size += header->num_vertexes * 3 * sizeof(float); // positions + size += header->num_vertexes * 2 * sizeof(float); // texcoords + size += header->num_vertexes * 3 * sizeof(float); // normals + size += header->num_vertexes * 4 * sizeof(float); // tangents + size += header->num_vertexes * 4 * sizeof(byte); // blendIndexes + size += header->num_vertexes * 4 * sizeof(byte); // blendWeights + size += header->num_vertexes * 4 * sizeof(byte); // colors + size += header->num_joints * sizeof(int); // parents + size += header->num_triangles * 3 * sizeof(int); // triangles + size += joint_names; // joint names + + mod->type = MOD_IQM; + iqmData = (iqmData_t *)ri.Hunk_Alloc( size, h_low ); + mod->modelData = iqmData; + + // fill header + iqmData->num_vertexes = header->num_vertexes; + iqmData->num_triangles = header->num_triangles; + iqmData->num_frames = header->num_frames; + iqmData->num_surfaces = header->num_meshes; + iqmData->num_joints = header->num_joints; + iqmData->surfaces = (srfIQModel_t *)(iqmData + 1); + iqmData->poseMats = (float *) (iqmData->surfaces + iqmData->num_surfaces); + if(header->ofs_bounds) + { + iqmData->bounds = iqmData->poseMats + 12 * header->num_joints * header->num_frames; + iqmData->positions = iqmData->bounds + 6 * header->num_frames; + } + else + iqmData->positions = iqmData->poseMats + 12 * header->num_joints * header->num_frames; + iqmData->texcoords = iqmData->positions + 3 * header->num_vertexes; + iqmData->normals = iqmData->texcoords + 2 * header->num_vertexes; + iqmData->tangents = iqmData->normals + 3 * header->num_vertexes; + iqmData->blendIndexes = (byte *)(iqmData->tangents + 4 * header->num_vertexes); + iqmData->blendWeights = iqmData->blendIndexes + 4 * header->num_vertexes; + iqmData->colors = iqmData->blendWeights + 4 * header->num_vertexes; + iqmData->jointParents = (int *)(iqmData->colors + 4 * header->num_vertexes); + iqmData->triangles = iqmData->jointParents + header->num_joints; + iqmData->names = (char *)(iqmData->triangles + 3 * header->num_triangles); + + // calculate joint matrices and their inverses + // they are needed only until the pose matrices are calculated + mat = jointMats; + joint = (iqmJoint_t *)((byte *)header + header->ofs_joints); + for( i = 0; i < header->num_joints; i++, joint++ ) { + float baseFrame[12], invBaseFrame[12]; + + JointToMatrix( joint->rotate, joint->scale, joint->translate, baseFrame ); + Matrix34Invert( baseFrame, invBaseFrame ); + + if ( joint->parent >= 0 ) + { + Matrix34Multiply( jointMats + 2 * 12 * joint->parent, baseFrame, mat ); + mat += 12; + Matrix34Multiply( invBaseFrame, jointMats + 2 * 12 * joint->parent + 12, mat ); + mat += 12; + } + else + { + Com_Memcpy( mat, baseFrame, sizeof(baseFrame) ); + mat += 12; + Com_Memcpy( mat, invBaseFrame, sizeof(invBaseFrame) ); + mat += 12; + } + } + + // calculate pose matrices + framedata = (unsigned short *)((byte *)header + header->ofs_frames); + mat = iqmData->poseMats; + for( i = 0; i < header->num_frames; i++ ) { + pose = (iqmPose_t *)((byte *)header + header->ofs_poses); + for( j = 0; j < header->num_poses; j++, pose++ ) { + vec3_t translate; + vec4_t rotate; + vec3_t scale; + float mat1[12], mat2[12]; + + translate[0] = pose->channeloffset[0]; + if( pose->mask & 0x001) + translate[0] += *framedata++ * pose->channelscale[0]; + translate[1] = pose->channeloffset[1]; + if( pose->mask & 0x002) + translate[1] += *framedata++ * pose->channelscale[1]; + translate[2] = pose->channeloffset[2]; + if( pose->mask & 0x004) + translate[2] += *framedata++ * pose->channelscale[2]; + + rotate[0] = pose->channeloffset[3]; + if( pose->mask & 0x008) + rotate[0] += *framedata++ * pose->channelscale[3]; + rotate[1] = pose->channeloffset[4]; + if( pose->mask & 0x010) + rotate[1] += *framedata++ * pose->channelscale[4]; + rotate[2] = pose->channeloffset[5]; + if( pose->mask & 0x020) + rotate[2] += *framedata++ * pose->channelscale[5]; + rotate[3] = pose->channeloffset[6]; + if( pose->mask & 0x040) + rotate[3] += *framedata++ * pose->channelscale[6]; + + scale[0] = pose->channeloffset[7]; + if( pose->mask & 0x080) + scale[0] += *framedata++ * pose->channelscale[7]; + scale[1] = pose->channeloffset[8]; + if( pose->mask & 0x100) + scale[1] += *framedata++ * pose->channelscale[8]; + scale[2] = pose->channeloffset[9]; + if( pose->mask & 0x200) + scale[2] += *framedata++ * pose->channelscale[9]; + + // construct transformation matrix + JointToMatrix( rotate, scale, translate, mat1 ); + + if( pose->parent >= 0 ) { + Matrix34Multiply( jointMats + 12 * 2 * pose->parent, + mat1, mat2 ); + } else { + Com_Memcpy( mat2, mat1, sizeof(mat1) ); + } + + Matrix34Multiply( mat2, jointMats + 12 * (2 * j + 1), mat ); + mat += 12; + } + } + + // register shaders + // overwrite the material offset with the shader index + mesh = (iqmMesh_t *)((byte *)header + header->ofs_meshes); + surface = iqmData->surfaces; + str = (char *)header + header->ofs_text; + for( i = 0; i < header->num_meshes; i++, mesh++, surface++ ) { + surface->surfaceType = SF_IQM; + Q_strncpyz(surface->name, str + mesh->name, sizeof (surface->name)); + Q_strlwr(surface->name); // lowercase the surface name so skin compares are faster + surface->shader = R_FindShader( str + mesh->material, LIGHTMAP_NONE, qtrue ); + if( surface->shader->defaultShader ) + surface->shader = tr.defaultShader; + surface->data = iqmData; + surface->first_vertex = mesh->first_vertex; + surface->num_vertexes = mesh->num_vertexes; + surface->first_triangle = mesh->first_triangle; + surface->num_triangles = mesh->num_triangles; + } + + // copy vertexarrays and indexes + vertexarray = (iqmVertexArray_t *)((byte *)header + header->ofs_vertexarrays); + for( i = 0; i < header->num_vertexarrays; i++, vertexarray++ ) { + int n; + + // total number of values + n = header->num_vertexes * vertexarray->size; + + switch( vertexarray->type ) { + case IQM_POSITION: + Com_Memcpy( iqmData->positions, + (byte *)header + vertexarray->offset, + n * sizeof(float) ); + break; + case IQM_NORMAL: + Com_Memcpy( iqmData->normals, + (byte *)header + vertexarray->offset, + n * sizeof(float) ); + break; + case IQM_TANGENT: + Com_Memcpy( iqmData->tangents, + (byte *)header + vertexarray->offset, + n * sizeof(float) ); + break; + case IQM_TEXCOORD: + Com_Memcpy( iqmData->texcoords, + (byte *)header + vertexarray->offset, + n * sizeof(float) ); + break; + case IQM_BLENDINDEXES: + Com_Memcpy( iqmData->blendIndexes, + (byte *)header + vertexarray->offset, + n * sizeof(byte) ); + break; + case IQM_BLENDWEIGHTS: + Com_Memcpy( iqmData->blendWeights, + (byte *)header + vertexarray->offset, + n * sizeof(byte) ); + break; + case IQM_COLOR: + Com_Memcpy( iqmData->colors, + (byte *)header + vertexarray->offset, + n * sizeof(byte) ); + break; + } + } + + // copy joint parents + joint = (iqmJoint_t *)((byte *)header + header->ofs_joints); + for( i = 0; i < header->num_joints; i++, joint++ ) { + iqmData->jointParents[i] = joint->parent; + } + + // copy triangles + triangle = (iqmTriangle_t *)((byte *)header + header->ofs_triangles); + for( i = 0; i < header->num_triangles; i++, triangle++ ) { + iqmData->triangles[3*i+0] = triangle->vertex[0]; + iqmData->triangles[3*i+1] = triangle->vertex[1]; + iqmData->triangles[3*i+2] = triangle->vertex[2]; + } + + // copy joint names + str = iqmData->names; + joint = (iqmJoint_t *)((byte *)header + header->ofs_joints); + for( i = 0; i < header->num_joints; i++, joint++ ) { + char *name = (char *)header + header->ofs_text + + joint->name; + int len = strlen( name ) + 1; + Com_Memcpy( str, name, len ); + str += len; + } + + // copy model bounds + if(header->ofs_bounds) + { + mat = iqmData->bounds; + bounds = (iqmBounds_t *) ((byte *) header + header->ofs_bounds); + for(i = 0; i < header->num_frames; i++) + { + mat[0] = bounds->bbmin[0]; + mat[1] = bounds->bbmin[1]; + mat[2] = bounds->bbmin[2]; + mat[3] = bounds->bbmax[0]; + mat[4] = bounds->bbmax[1]; + mat[5] = bounds->bbmax[2]; + + mat += 6; + bounds++; + } + } + + return qtrue; +} + +/* +============= +R_CullIQM +============= +*/ +static int R_CullIQM( iqmData_t *data, trRefEntity_t *ent ) { + vec3_t bounds[2]; + vec_t *oldBounds, *newBounds; + int i; + + if (!data->bounds) { + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + } + + // compute bounds pointers + oldBounds = data->bounds + 6*ent->e.oldframe; + newBounds = data->bounds + 6*ent->e.frame; + + // calculate a bounding box in the current coordinate system + for (i = 0 ; i < 3 ; i++) { + bounds[0][i] = oldBounds[i] < newBounds[i] ? oldBounds[i] : newBounds[i]; + bounds[1][i] = oldBounds[i+3] > newBounds[i+3] ? oldBounds[i+3] : newBounds[i+3]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + +/* +================= +R_ComputeIQMFogNum + +================= +*/ +int R_ComputeIQMFogNum( iqmData_t *data, trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + const vec_t *bounds; + const vec_t defaultBounds[6] = { -8, -8, -8, 8, 8, 8 }; + vec3_t diag, center; + vec3_t localOrigin; + vec_t radius; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + // FIXME: non-normalized axis issues + if (data->bounds) { + bounds = data->bounds + 6*ent->e.frame; + } else { + bounds = defaultBounds; + } + VectorSubtract( bounds+3, bounds, diag ); + VectorMA( bounds, 0.5f, diag, center ); + VectorAdd( ent->e.origin, center, localOrigin ); + radius = 0.5f * VectorLength( diag ); + + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( localOrigin[j] - radius >= fog->bounds[1][j] ) { + break; + } + if ( localOrigin[j] + radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +================= +R_AddIQMSurfaces + +Add all surfaces of this model +================= +*/ +void R_AddIQMSurfaces( trRefEntity_t *ent ) { + iqmData_t *data; + srfIQModel_t *surface; + int i, j; + qboolean personalModel; + int cull; + int fogNum; + shader_t *shader; + skin_t *skin; + + data = tr.currentModel->modelData; + surface = data->surfaces; + + // don't add third_person objects if not in a portal + personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; + + if ( ent->e.renderfx & RF_WRAP_FRAMES ) { + ent->e.frame %= data->num_frames; + ent->e.oldframe %= data->num_frames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ( (ent->e.frame >= data->num_frames) + || (ent->e.frame < 0) + || (ent->e.oldframe >= data->num_frames) + || (ent->e.oldframe < 0) ) { + ri.Printf( PRINT_DEVELOPER, "R_AddIQMSurfaces: no such frame %d to %d for '%s'\n", + ent->e.oldframe, ent->e.frame, + tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_CullIQM ( data, ent ); + if ( cull == CULL_OUT ) { + return; + } + + // + // set up lighting now that we know we aren't culled + // + if ( !personalModel || r_shadows->integer > 1 ) { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // + // see if we are in a fog volume + // + fogNum = R_ComputeIQMFogNum( data, ent ); + + for ( i = 0 ; i < data->num_surfaces ; i++ ) { + if(ent->e.customShader) + shader = R_GetShaderByHandle( ent->e.customShader ); + else if(ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins) + { + skin = R_GetSkinByHandle(ent->e.customSkin); + shader = tr.defaultShader; + + for(j = 0; j < skin->numSurfaces; j++) + { + if (!strcmp(skin->surfaces[j]->name, surface->name)) + { + shader = skin->surfaces[j]->shader; + break; + } + } + } else { + shader = surface->shader; + } + + // 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 + if ( !personalModel + && r_shadows->integer == 2 + && fogNum == 0 + && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (void *)surface, tr.shadowShader, 0, 0, 0 ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (void *)surface, tr.projectionShadowShader, 0, 0, 0 ); + } + + if( !personalModel ) { + R_AddDrawSurf( (void *)surface, shader, fogNum, 0, 0 ); + } + + surface++; + } +} + + +static void ComputeJointMats( iqmData_t *data, int frame, int oldframe, + float backlerp, float *mat ) { + float *mat1, *mat2; + int *joint = data->jointParents; + int i; + + if ( oldframe == frame ) { + mat1 = data->poseMats + 12 * data->num_joints * frame; + for( i = 0; i < data->num_joints; i++, joint++ ) { + if( *joint >= 0 ) { + Matrix34Multiply( mat + 12 * *joint, + mat1 + 12*i, mat + 12*i ); + } else { + Com_Memcpy( mat + 12*i, mat1 + 12*i, 12 * sizeof(float) ); + } + } + } else { + mat1 = data->poseMats + 12 * data->num_joints * frame; + mat2 = data->poseMats + 12 * data->num_joints * oldframe; + + for( i = 0; i < data->num_joints; i++, joint++ ) { + if( *joint >= 0 ) { + float tmpMat[12]; + InterpolateMatrix( mat1 + 12*i, mat2 + 12*i, + backlerp, tmpMat ); + Matrix34Multiply( mat + 12 * *joint, + tmpMat, mat + 12*i ); + + } else { + InterpolateMatrix( mat1 + 12*i, mat2 + 12*i, + backlerp, mat ); + } + } + } +} + + +/* +================= +RB_AddIQMSurfaces + +Compute vertices for this model surface +================= +*/ +void RB_IQMSurfaceAnim( surfaceType_t *surface ) { + srfIQModel_t *surf = (srfIQModel_t *)surface; + iqmData_t *data = surf->data; + float jointMats[IQM_MAX_JOINTS * 12]; + int i; + + vec4_t *outXYZ = &tess.xyz[tess.numVertexes]; + vec4_t *outNormal = &tess.normal[tess.numVertexes]; + vec2_t (*outTexCoord)[2] = &tess.texCoords[tess.numVertexes]; + vec4_t *outColor = &tess.vertexColors[tess.numVertexes]; + + int frame = backEnd.currentEntity->e.frame % data->num_frames; + int oldframe = backEnd.currentEntity->e.oldframe % data->num_frames; + float backlerp = backEnd.currentEntity->e.backlerp; + + int *tri; + glIndex_t *ptr; + glIndex_t base; + + RB_CHECKOVERFLOW( surf->num_vertexes, surf->num_triangles * 3 ); + + // compute interpolated joint matrices + ComputeJointMats( data, frame, oldframe, backlerp, jointMats ); + + // transform vertexes and fill other data + for( i = 0; i < surf->num_vertexes; + i++, outXYZ++, outNormal++, outTexCoord++, outColor++ ) { + int j, k; + float vtxMat[12]; + float nrmMat[9]; + int vtx = i + surf->first_vertex; + + // compute the vertex matrix by blending the up to + // four blend weights + for( k = 0; k < 12; k++ ) + vtxMat[k] = data->blendWeights[4*vtx] + * jointMats[12*data->blendIndexes[4*vtx] + k]; + for( j = 1; j < 4; j++ ) { + if( data->blendWeights[4*vtx + j] <= 0 ) + break; + for( k = 0; k < 12; k++ ) + vtxMat[k] += data->blendWeights[4*vtx + j] + * jointMats[12*data->blendIndexes[4*vtx + j] + k]; + } + for( k = 0; k < 12; k++ ) + vtxMat[k] *= 1.0f / 255.0f; + + // compute the normal matrix as transpose of the adjoint + // of the vertex matrix + nrmMat[ 0] = vtxMat[ 5]*vtxMat[10] - vtxMat[ 6]*vtxMat[ 9]; + nrmMat[ 1] = vtxMat[ 6]*vtxMat[ 8] - vtxMat[ 4]*vtxMat[10]; + nrmMat[ 2] = vtxMat[ 4]*vtxMat[ 9] - vtxMat[ 5]*vtxMat[ 8]; + nrmMat[ 3] = vtxMat[ 2]*vtxMat[ 9] - vtxMat[ 1]*vtxMat[10]; + nrmMat[ 4] = vtxMat[ 0]*vtxMat[10] - vtxMat[ 2]*vtxMat[ 8]; + nrmMat[ 5] = vtxMat[ 1]*vtxMat[ 8] - vtxMat[ 0]*vtxMat[ 9]; + nrmMat[ 6] = vtxMat[ 1]*vtxMat[ 6] - vtxMat[ 2]*vtxMat[ 5]; + nrmMat[ 7] = vtxMat[ 2]*vtxMat[ 4] - vtxMat[ 0]*vtxMat[ 6]; + nrmMat[ 8] = vtxMat[ 0]*vtxMat[ 5] - vtxMat[ 1]*vtxMat[ 4]; + + (*outTexCoord)[0][0] = data->texcoords[2*vtx + 0]; + (*outTexCoord)[0][1] = data->texcoords[2*vtx + 1]; + (*outTexCoord)[1][0] = (*outTexCoord)[0][0]; + (*outTexCoord)[1][1] = (*outTexCoord)[0][1]; + + (*outXYZ)[0] = + vtxMat[ 0] * data->positions[3*vtx+0] + + vtxMat[ 1] * data->positions[3*vtx+1] + + vtxMat[ 2] * data->positions[3*vtx+2] + + vtxMat[ 3]; + (*outXYZ)[1] = + vtxMat[ 4] * data->positions[3*vtx+0] + + vtxMat[ 5] * data->positions[3*vtx+1] + + vtxMat[ 6] * data->positions[3*vtx+2] + + vtxMat[ 7]; + (*outXYZ)[2] = + vtxMat[ 8] * data->positions[3*vtx+0] + + vtxMat[ 9] * data->positions[3*vtx+1] + + vtxMat[10] * data->positions[3*vtx+2] + + vtxMat[11]; + (*outXYZ)[3] = 1.0f; + + (*outNormal)[0] = + nrmMat[ 0] * data->normals[3*vtx+0] + + nrmMat[ 1] * data->normals[3*vtx+1] + + nrmMat[ 2] * data->normals[3*vtx+2]; + (*outNormal)[1] = + nrmMat[ 3] * data->normals[3*vtx+0] + + nrmMat[ 4] * data->normals[3*vtx+1] + + nrmMat[ 5] * data->normals[3*vtx+2]; + (*outNormal)[2] = + nrmMat[ 6] * data->normals[3*vtx+0] + + nrmMat[ 7] * data->normals[3*vtx+1] + + nrmMat[ 8] * data->normals[3*vtx+2]; + (*outNormal)[3] = 0.0f; + + (*outColor)[0] = data->colors[4*vtx+0] / 255.0f; + (*outColor)[1] = data->colors[4*vtx+1] / 255.0f; + (*outColor)[2] = data->colors[4*vtx+2] / 255.0f; + (*outColor)[3] = data->colors[4*vtx+3] / 255.0f; + } + + tri = data->triangles + 3 * surf->first_triangle; + ptr = &tess.indexes[tess.numIndexes]; + base = tess.numVertexes; + + for( i = 0; i < surf->num_triangles; i++ ) { + *ptr++ = base + (*tri++ - surf->first_vertex); + *ptr++ = base + (*tri++ - surf->first_vertex); + *ptr++ = base + (*tri++ - surf->first_vertex); + } + + tess.numIndexes += 3 * surf->num_triangles; + tess.numVertexes += surf->num_vertexes; +} + +int R_IQMLerpTag( orientation_t *tag, iqmData_t *data, + int startFrame, int endFrame, + float frac, const char *tagName ) { + float jointMats[IQM_MAX_JOINTS * 12]; + int joint; + char *names = data->names; + + // get joint number by reading the joint names + for( joint = 0; joint < data->num_joints; joint++ ) { + if( !strcmp( tagName, names ) ) + break; + names += strlen( names ) + 1; + } + if( joint >= data->num_joints ) { + AxisClear( tag->axis ); + VectorClear( tag->origin ); + return qfalse; + } + + ComputeJointMats( data, startFrame, endFrame, frac, jointMats ); + + tag->axis[0][0] = jointMats[12 * joint + 0]; + tag->axis[1][0] = jointMats[12 * joint + 1]; + tag->axis[2][0] = jointMats[12 * joint + 2]; + tag->origin[0] = jointMats[12 * joint + 3]; + tag->axis[0][1] = jointMats[12 * joint + 4]; + tag->axis[1][1] = jointMats[12 * joint + 5]; + tag->axis[2][1] = jointMats[12 * joint + 6]; + tag->origin[1] = jointMats[12 * joint + 7]; + tag->axis[0][2] = jointMats[12 * joint + 8]; + tag->axis[1][2] = jointMats[12 * joint + 9]; + tag->axis[2][2] = jointMats[12 * joint + 10]; + tag->origin[2] = jointMats[12 * joint + 11]; + + return qtrue; +} diff --git a/src/rend2/tr_noise.c b/src/rend2/tr_noise.c new file mode 100644 index 00000000..40eafc33 --- /dev/null +++ b/src/rend2/tr_noise.c @@ -0,0 +1,93 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_noise.c +#include "../qcommon/q_shared.h" +#include "../qcommon/qfiles.h" +#include "../qcommon/qcommon.h" + +#define NOISE_SIZE 256 +#define NOISE_MASK ( NOISE_SIZE - 1 ) + +#define VAL( a ) s_noise_perm[ ( a ) & ( NOISE_MASK )] +#define INDEX( x, y, z, t ) VAL( x + VAL( y + VAL( z + VAL( t ) ) ) ) + +static float s_noise_table[NOISE_SIZE]; +static int s_noise_perm[NOISE_SIZE]; + +static float GetNoiseValue( int x, int y, int z, int t ) +{ + int index = INDEX( ( int ) x, ( int ) y, ( int ) z, ( int ) t ); + + return s_noise_table[index]; +} + +void R_NoiseInit( void ) +{ + int i; + + for ( i = 0; i < NOISE_SIZE; i++ ) + { + s_noise_table[i] = ( float ) ( ( ( rand() / ( float ) RAND_MAX ) * 2.0 - 1.0 ) ); + s_noise_perm[i] = ( unsigned char ) ( rand() / ( float ) RAND_MAX * 255 ); + } +} + +float R_NoiseGet4f( float x, float y, float z, float t ) +{ + int i; + int ix, iy, iz, it; + float fx, fy, fz, ft; + float front[4]; + float back[4]; + float fvalue, bvalue, value[2], finalvalue; + + ix = ( int ) floor( x ); + fx = x - ix; + iy = ( int ) floor( y ); + fy = y - iy; + iz = ( int ) floor( z ); + fz = z - iz; + it = ( int ) floor( t ); + ft = t - it; + + for ( i = 0; i < 2; i++ ) + { + front[0] = GetNoiseValue( ix, iy, iz, it + i ); + front[1] = GetNoiseValue( ix+1, iy, iz, it + i ); + front[2] = GetNoiseValue( ix, iy+1, iz, it + i ); + front[3] = GetNoiseValue( ix+1, iy+1, iz, it + i ); + + back[0] = GetNoiseValue( ix, iy, iz + 1, it + i ); + back[1] = GetNoiseValue( ix+1, iy, iz + 1, it + i ); + back[2] = GetNoiseValue( ix, iy+1, iz + 1, it + i ); + back[3] = GetNoiseValue( ix+1, iy+1, iz + 1, it + i ); + + fvalue = LERP( LERP( front[0], front[1], fx ), LERP( front[2], front[3], fx ), fy ); + bvalue = LERP( LERP( back[0], back[1], fx ), LERP( back[2], back[3], fx ), fy ); + + value[i] = LERP( fvalue, bvalue, fz ); + } + + finalvalue = LERP( value[0], value[1], ft ); + + return finalvalue; +} diff --git a/src/rend2/tr_postprocess.c b/src/rend2/tr_postprocess.c new file mode 100644 index 00000000..0cb162b3 --- /dev/null +++ b/src/rend2/tr_postprocess.c @@ -0,0 +1,490 @@ +/* +=========================================================================== +Copyright (C) 2011 Andrei Drexler, Richard Allen, James Canete + +This file is part of Reaction source code. + +Reaction source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Reaction source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Reaction source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "tr_local.h" + +void RB_ToneMap(FBO_t *hdrFbo, int autoExposure) +{ + vec4i_t srcBox, dstBox; + vec4_t color; + static int lastFrameCount = 0; + + if (autoExposure) + { + if (lastFrameCount == 0 || tr.frameCount < lastFrameCount || tr.frameCount - lastFrameCount > 5) + { + // determine average log luminance + FBO_t *srcFbo, *dstFbo, *tmp; + int size = 256; + + lastFrameCount = tr.frameCount; + + VectorSet4(dstBox, 0, 0, size, size); + + srcFbo = hdrFbo; + dstFbo = tr.textureScratchFbo[0]; + FBO_Blit(srcFbo, NULL, NULL, dstFbo, dstBox, &tr.calclevels4xShader[0], NULL, 0); + + srcFbo = tr.textureScratchFbo[0]; + dstFbo = tr.textureScratchFbo[1]; + + // downscale to 1x1 texture + while (size > 1) + { + VectorSet4(srcBox, 0, 0, size, size); + //size >>= 2; + size >>= 1; + VectorSet4(dstBox, 0, 0, size, size); + + if (size == 1) + dstFbo = tr.targetLevelsFbo; + + //FBO_Blit(targetFbo, srcBox, NULL, tr.textureScratchFbo[nextScratch], dstBox, &tr.calclevels4xShader[1], NULL, 0); + FBO_FastBlit(srcFbo, srcBox, dstFbo, dstBox, GL_COLOR_BUFFER_BIT, GL_LINEAR); + + tmp = srcFbo; + srcFbo = dstFbo; + dstFbo = tmp; + } + } + + // blend with old log luminance for gradual change + VectorSet4(srcBox, 0, 0, 0, 0); + + color[0] = + color[1] = + color[2] = 1.0f; + if (glRefConfig.textureFloat) + color[3] = 0.03f; + else + color[3] = 0.1f; + + FBO_Blit(tr.targetLevelsFbo, srcBox, NULL, tr.calcLevelsFbo, NULL, NULL, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); + } + + // tonemap + color[0] = + color[1] = + color[2] = pow(2, r_cameraExposure->value); //exp2(r_cameraExposure->value); + color[3] = 1.0f; + + if (autoExposure) + GL_BindToTMU(tr.calcLevelsImage, TB_LEVELSMAP); + else + GL_BindToTMU(tr.fixedLevelsImage, TB_LEVELSMAP); + + FBO_Blit(hdrFbo, NULL, NULL, tr.screenScratchFbo, NULL, &tr.tonemapShader, color, 0); +} + + +void RB_BokehBlur(float blur) +{ +// vec4i_t srcBox, dstBox; + vec4_t color; + + blur *= 10.0f; + + if (blur < 0.004f) + return; + + if (glRefConfig.framebufferObject) + { + // bokeh blur + if (blur > 0.0f) + { + // create a quarter texture + FBO_Blit(tr.screenScratchFbo, NULL, NULL, tr.quarterFbo[0], NULL, NULL, NULL, 0); + } + +#ifndef HQ_BLUR + if (blur > 1.0f) + { + // create a 1/16th texture + FBO_Blit(tr.quarterFbo[0], NULL, NULL, tr.textureScratchFbo[0], NULL, NULL, NULL, 0); + } +#endif + + if (blur > 0.0f && blur <= 1.0f) + { + // Crossfade original with quarter texture + VectorSet4(color, 1, 1, 1, blur); + + FBO_Blit(tr.quarterFbo[0], NULL, NULL, tr.screenScratchFbo, NULL, NULL, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); + } +#ifndef HQ_BLUR + // ok blur, but can see some pixelization + else if (blur > 1.0f && blur <= 2.0f) + { + // crossfade quarter texture with 1/16th texture + FBO_Blit(tr.quarterFbo[0], NULL, NULL, tr.screenScratchFbo, NULL, NULL, NULL, 0); + + VectorSet4(color, 1, 1, 1, blur - 1.0f); + + FBO_Blit(tr.textureScratchFbo[0], NULL, NULL, tr.screenScratchFbo, NULL, NULL, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); + } + else if (blur > 2.0f) + { + // blur 1/16th texture then replace + int i; + + for (i = 0; i < 2; i++) + { + vec2_t blurTexScale; + float subblur; + + subblur = ((blur - 2.0f) / 2.0f) / 3.0f * (float)(i + 1); + + blurTexScale[0] = + blurTexScale[1] = subblur; + + color[0] = + color[1] = + color[2] = 0.5f; + color[3] = 1.0f; + + if (i != 0) + FBO_Blit(tr.textureScratchFbo[0], NULL, blurTexScale, tr.textureScratchFbo[1], NULL, &tr.bokehShader, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); + else + FBO_Blit(tr.textureScratchFbo[0], NULL, blurTexScale, tr.textureScratchFbo[1], NULL, &tr.bokehShader, color, 0); + } + + FBO_Blit(tr.textureScratchFbo[1], NULL, NULL, tr.screenScratchFbo, NULL, &tr.textureColorShader, NULL, 0); + } +#else // higher quality blur, but slower + else if (blur > 1.0f) + { + // blur quarter texture then replace + int i; + + src = tr.quarterFbo[0]; + dst = tr.quarterFbo[1]; + + VectorSet4(color, 0.5f, 0.5f, 0.5f, 1); + + for (i = 0; i < 2; i++) + { + vec2_t blurTexScale; + float subblur; + + subblur = (blur - 1.0f) / 2.0f * (float)(i + 1); + + blurTexScale[0] = + blurTexScale[1] = subblur; + + color[0] = + color[1] = + color[2] = 1.0f; + if (i != 0) + color[3] = 1.0f; + else + color[3] = 0.5f; + + FBO_Blit(tr.quarterFbo[0], NULL, blurTexScale, tr.quarterFbo[1], NULL, &tr.bokehShader, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); + } + + FBO_Blit(tr.quarterFbo[1], NULL, NULL, tr.screenScratchFbo, NULL, &tr.textureColorShader, NULL, 0); + } +#endif + } +} + + +#ifdef REACTION +static void RB_RadialBlur(FBO_t *srcFbo, FBO_t *dstFbo, int passes, float stretch, float x, float y, float w, float h, float xcenter, float ycenter, float alpha) +{ + vec4i_t srcBox, dstBox; + vec4_t color; + const float inc = 1.f / passes; + const float mul = powf(stretch, inc); + float scale; + + { + vec2_t texScale; + + texScale[0] = + texScale[1] = 1.0f; + + alpha *= inc; + VectorSet4(color, alpha, alpha, alpha, 1.0f); + + VectorSet4(srcBox, 0, 0, srcFbo->width, srcFbo->height); + VectorSet4(dstBox, x, y, w, h); + FBO_Blit(srcFbo, srcBox, texScale, dstFbo, dstBox, &tr.textureColorShader, color, 0); + + --passes; + scale = mul; + while (passes > 0) + { + float iscale = 1.f / scale; + float s0 = xcenter * (1.f - iscale); + float t0 = (1.0f - ycenter) * (1.f - iscale); + float s1 = iscale + s0; + float t1 = iscale + t0; + + srcBox[0] = s0 * srcFbo->width; + srcBox[1] = t0 * srcFbo->height; + srcBox[2] = (s1 - s0) * srcFbo->width; + srcBox[3] = (t1 - t0) * srcFbo->height; + + FBO_Blit(srcFbo, srcBox, texScale, dstFbo, dstBox, &tr.textureColorShader, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + scale *= mul; + --passes; + } + } +} + + +static qboolean RB_UpdateSunFlareVis(void) +{ + GLuint sampleCount = 0; + if (!glRefConfig.occlusionQuery) + return qtrue; + + tr.sunFlareQueryIndex ^= 1; + if (!tr.sunFlareQueryActive[tr.sunFlareQueryIndex]) + return qtrue; + + /* debug code */ + if (0) + { + int iter; + for (iter=0 ; ; ++iter) + { + GLint available = 0; + qglGetQueryObjectivARB(tr.sunFlareQuery[tr.sunFlareQueryIndex], GL_QUERY_RESULT_AVAILABLE_ARB, &available); + if (available) + break; + } + + ri.Printf(PRINT_DEVELOPER, "Waited %d iterations\n", iter); + } + + qglGetQueryObjectuivARB(tr.sunFlareQuery[tr.sunFlareQueryIndex], GL_QUERY_RESULT_ARB, &sampleCount); + return sampleCount > 0; +} + +void RB_GodRays(void) +{ + vec4i_t srcBox, dstBox; + vec4_t color; + vec3_t dir; + float dot; + const float cutoff = 0.25f; + qboolean colorize = qtrue; + +// float w, h, w2, h2; + matrix_t mvp; + vec4_t pos, hpos; + + if (!backEnd.viewHasSunFlare) + return; + + VectorSubtract(backEnd.sunFlarePos, backEnd.viewParms.or.origin, dir); + VectorNormalize(dir); + + dot = DotProduct(dir, backEnd.viewParms.or.axis[0]); + if (dot < cutoff) + return; + + if (!RB_UpdateSunFlareVis()) + return; + + VectorCopy(backEnd.sunFlarePos, pos); + pos[3] = 1.f; + + // project sun point + Matrix16Multiply(backEnd.viewParms.projectionMatrix, backEnd.viewParms.world.modelMatrix, mvp); + Matrix16Transform(mvp, pos, hpos); + + // transform to UV coords + hpos[3] = 0.5f / hpos[3]; + + pos[0] = 0.5f + hpos[0] * hpos[3]; + pos[1] = 0.5f - hpos[1] * hpos[3]; + + // viewport dimensions + // JBravo: Apparently not used +/* w = glConfig.vidWidth; + h = glConfig.vidHeight; + w2 = glConfig.vidWidth / 2; + h2 = glConfig.vidHeight / 2; */ + + // initialize quarter buffers + { + float mul = 1.f; + vec2_t texScale; + + texScale[0] = + texScale[1] = 1.0f; + + VectorSet4(color, mul, mul, mul, 1); + + // first, downsample the framebuffer + VectorSet4(srcBox, 0, 0, tr.godRaysFbo->width, tr.godRaysFbo->height); + VectorSet4(dstBox, 0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height); + FBO_Blit(tr.godRaysFbo, srcBox, texScale, tr.quarterFbo[0], dstBox, &tr.textureColorShader, color, 0); + + if (colorize) + { + VectorSet4(srcBox, 0, 0, tr.screenScratchFbo->width, tr.screenScratchFbo->height); + FBO_Blit(tr.screenScratchFbo, srcBox, texScale, tr.quarterFbo[0], dstBox, &tr.textureColorShader, color, + GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO); + } + } + + // radial blur passes, ping-ponging between the two quarter-size buffers + { + const float stretch_add = 2.f/3.f; + float stretch = 1.f + stretch_add; + int i; + for (i=0; i<2; ++i) + { + RB_RadialBlur(tr.quarterFbo[i&1], tr.quarterFbo[(~i) & 1], 5, stretch, 0.f, 0.f, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height, pos[0], pos[1], 1.125f); + stretch += stretch_add; + } + } + + // add result back on top of the main buffer + { + float mul = 1.f; + vec2_t texScale; + + texScale[0] = + texScale[1] = 1.0f; + + VectorSet4(color, mul, mul, mul, 1); + + VectorSet4(srcBox, 0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height); + VectorSet4(dstBox, 0, 0, tr.screenScratchFbo->width, tr.screenScratchFbo->height); + FBO_Blit(tr.quarterFbo[0], srcBox, texScale, tr.screenScratchFbo, dstBox, &tr.textureColorShader, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE); + } +} +#endif + +static void RB_BlurAxis(FBO_t *srcFbo, FBO_t *dstFbo, float strength, qboolean horizontal) +{ + float dx, dy; + float xmul, ymul; + float weights[3] = { + 0.227027027f, + 0.316216216f, + 0.070270270f, + }; + float offsets[3] = { + 0.f, + 1.3846153846f, + 3.2307692308f, + }; + + xmul = horizontal; + ymul = 1.f - xmul; + + xmul *= strength; + ymul *= strength; + + { + vec4i_t srcBox, dstBox; + vec4_t color; + vec2_t texScale; + + texScale[0] = + texScale[1] = 1.0f; + + VectorSet4(color, weights[0], weights[0], weights[0], 1.0f); + VectorSet4(srcBox, 0, 0, srcFbo->width, srcFbo->height); + VectorSet4(dstBox, 0, 0, dstFbo->width, dstFbo->height); + FBO_Blit(srcFbo, srcBox, texScale, dstFbo, dstBox, &tr.textureColorShader, color, 0 ); + + VectorSet4(color, weights[1], weights[1], weights[1], 1.0f); + dx = offsets[1] * xmul; + dy = offsets[1] * ymul; + VectorSet4(srcBox, dx, dy, srcFbo->width, srcFbo->height); + FBO_Blit(srcFbo, srcBox, texScale, dstFbo, dstBox, &tr.textureColorShader, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + VectorSet4(srcBox, -dx, -dy, srcFbo->width, srcFbo->height); + FBO_Blit(srcFbo, srcBox, texScale, dstFbo, dstBox, &tr.textureColorShader, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + VectorSet4(color, weights[2], weights[2], weights[2], 1.0f); + dx = offsets[2] * xmul; + dy = offsets[2] * ymul; + VectorSet4(srcBox, dx, dy, srcFbo->width, srcFbo->height); + FBO_Blit(srcFbo, srcBox, texScale, dstFbo, dstBox, &tr.textureColorShader, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + VectorSet4(srcBox, -dx, -dy, srcFbo->width, srcFbo->height); + FBO_Blit(srcFbo, srcBox, texScale, dstFbo, dstBox, &tr.textureColorShader, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + } +} + +static void RB_HBlur(FBO_t *srcFbo, FBO_t *dstFbo, float strength) +{ + RB_BlurAxis(srcFbo, dstFbo, strength, qtrue); +} + +static void RB_VBlur(FBO_t *srcFbo, FBO_t *dstFbo, float strength) +{ + RB_BlurAxis(srcFbo, dstFbo, strength, qfalse); +} + +void RB_GaussianBlur(float blur) +{ + //float mul = 1.f; + float factor = Com_Clamp(0.f, 1.f, blur); + + if (factor <= 0.f) + return; + + { + vec4i_t srcBox, dstBox; + vec4_t color; + vec2_t texScale; + + texScale[0] = + texScale[1] = 1.0f; + + VectorSet4(color, 1, 1, 1, 1); + + // first, downsample the framebuffer + VectorSet4(srcBox, 0, 0, tr.screenScratchFbo->width, tr.screenScratchFbo->height); + VectorSet4(dstBox, 0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height); + FBO_Blit(tr.screenScratchFbo, srcBox, texScale, tr.quarterFbo[0], dstBox, &tr.textureColorShader, color, 0); + + VectorSet4(srcBox, 0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height); + VectorSet4(dstBox, 0, 0, tr.textureScratchFbo[0]->width, tr.textureScratchFbo[0]->height); + FBO_Blit(tr.quarterFbo[0], srcBox, texScale, tr.textureScratchFbo[0], dstBox, &tr.textureColorShader, color, 0); + + // set the alpha channel + VectorSet4(srcBox, 0, 0, tr.whiteImage->width, tr.whiteImage->height); + VectorSet4(dstBox, 0, 0, tr.textureScratchFbo[0]->width, tr.textureScratchFbo[0]->height); + qglColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE); + FBO_BlitFromTexture(tr.whiteImage, srcBox, texScale, tr.textureScratchFbo[0], dstBox, &tr.textureColorShader, color, GLS_DEPTHTEST_DISABLE); + qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + // blur the tiny buffer horizontally and vertically + RB_HBlur(tr.textureScratchFbo[0], tr.textureScratchFbo[1], factor); + RB_VBlur(tr.textureScratchFbo[1], tr.textureScratchFbo[0], factor); + + // finally, merge back to framebuffer + VectorSet4(srcBox, 0, 0, tr.textureScratchFbo[0]->width, tr.textureScratchFbo[0]->height); + VectorSet4(dstBox, 0, 0, tr.screenScratchFbo->width, tr.screenScratchFbo->height); + color[3] = factor; + FBO_Blit(tr.textureScratchFbo[0], srcBox, texScale, tr.screenScratchFbo, dstBox, &tr.textureColorShader, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); + } +} diff --git a/src/rend2/tr_postprocess.h b/src/rend2/tr_postprocess.h new file mode 100644 index 00000000..5575bfd2 --- /dev/null +++ b/src/rend2/tr_postprocess.h @@ -0,0 +1,33 @@ +/* +=========================================================================== +Copyright (C) 2011 Andrei Drexler, Richard Allen, James Canete + +This file is part of Reaction source code. + +Reaction source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Reaction source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Reaction source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef TR_POSTPROCESS_H +#define TR_POSTPROCESS_H + +#include "tr_fbo.h" + +void RB_ToneMap(FBO_t *hdrFbo, int autoExposure); +void RB_BokehBlur(float blur); +void RB_GodRays(void); +void RB_GaussianBlur(float blur); + +#endif \ No newline at end of file diff --git a/src/rend2/tr_scene.c b/src/rend2/tr_scene.c new file mode 100644 index 00000000..20f90547 --- /dev/null +++ b/src/rend2/tr_scene.c @@ -0,0 +1,539 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "tr_local.h" + +int r_firstSceneDrawSurf; + +int r_numdlights; +int r_firstSceneDlight; + +int r_numentities; +int r_firstSceneEntity; + +int r_numpolys; +int r_firstScenePoly; + +int r_numpolyverts; + + +/* +==================== +R_ToggleSmpFrame + +==================== +*/ +void R_ToggleSmpFrame( void ) { + if ( r_smp->integer ) { + // use the other buffers next frame, because another CPU + // may still be rendering into the current ones + tr.smpFrame ^= 1; + } else { + tr.smpFrame = 0; + } + + backEndData[tr.smpFrame]->commands.used = 0; + + r_firstSceneDrawSurf = 0; + + r_numdlights = 0; + r_firstSceneDlight = 0; + + r_numentities = 0; + r_firstSceneEntity = 0; + + r_numpolys = 0; + r_firstScenePoly = 0; + + r_numpolyverts = 0; +} + + +/* +==================== +RE_ClearScene + +==================== +*/ +void RE_ClearScene( void ) { + r_firstSceneDlight = r_numdlights; + r_firstSceneEntity = r_numentities; + r_firstScenePoly = r_numpolys; +} + +/* +=========================================================================== + +DISCRETE POLYS + +=========================================================================== +*/ + +/* +===================== +R_AddPolygonSurfaces + +Adds all the scene's polys into this view's drawsurf list +===================== +*/ +void R_AddPolygonSurfaces( void ) { + int i; + shader_t *sh; + srfPoly_t *poly; +// JBravo: Fog fixes + int fogMask; + + tr.currentEntityNum = REFENTITYNUM_WORLD; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_REFENTITYNUM_SHIFT; + fogMask = -((tr.refdef.rdflags & RDF_NOFOG) == 0); + + for ( i = 0, poly = tr.refdef.polys; i < tr.refdef.numPolys ; i++, poly++ ) { + sh = R_GetShaderByHandle( poly->hShader ); + R_AddDrawSurf( ( void * )poly, sh, poly->fogIndex & fogMask, qfalse, qfalse ); + } +} + +/* +===================== +RE_AddPolyToScene + +===================== +*/ +void RE_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts, int numPolys ) { + srfPoly_t *poly; + int i, j; + int fogIndex; + fog_t *fog; + vec3_t bounds[2]; + + if ( !tr.registered ) { + return; + } + + if ( !hShader ) { + // This isn't a useful warning, and an hShader of zero isn't a null shader, it's + // the default shader. + //ri.Printf( PRINT_WARNING, "WARNING: RE_AddPolyToScene: NULL poly shader\n"); + //return; + } + + for ( j = 0; j < numPolys; j++ ) { + if ( r_numpolyverts + numVerts > max_polyverts || r_numpolys >= max_polys ) { + /* + NOTE TTimo this was initially a PRINT_WARNING + but it happens a lot with high fighting scenes and particles + since we don't plan on changing the const and making for room for those effects + simply cut this message to developer only + */ + ri.Printf( PRINT_DEVELOPER, "WARNING: RE_AddPolyToScene: r_max_polys or r_max_polyverts reached\n"); + return; + } + + poly = &backEndData[tr.smpFrame]->polys[r_numpolys]; + poly->surfaceType = SF_POLY; + poly->hShader = hShader; + poly->numVerts = numVerts; + poly->verts = &backEndData[tr.smpFrame]->polyVerts[r_numpolyverts]; + + Com_Memcpy( poly->verts, &verts[numVerts*j], numVerts * sizeof( *verts ) ); + + if ( glConfig.hardwareType == GLHW_RAGEPRO ) { + poly->verts->modulate[0] = 255; + poly->verts->modulate[1] = 255; + poly->verts->modulate[2] = 255; + poly->verts->modulate[3] = 255; + } + // done. + r_numpolys++; + r_numpolyverts += numVerts; + + // if no world is loaded + if ( tr.world == NULL ) { + fogIndex = 0; + } + // see if it is in a fog volume + else if ( tr.world->numfogs == 1 ) { + fogIndex = 0; + } else { + // find which fog volume the poly is in + VectorCopy( poly->verts[0].xyz, bounds[0] ); + VectorCopy( poly->verts[0].xyz, bounds[1] ); + for ( i = 1 ; i < poly->numVerts ; i++ ) { + AddPointToBounds( poly->verts[i].xyz, bounds[0], bounds[1] ); + } + for ( fogIndex = 1 ; fogIndex < tr.world->numfogs ; fogIndex++ ) { + fog = &tr.world->fogs[fogIndex]; + if ( bounds[1][0] >= fog->bounds[0][0] + && bounds[1][1] >= fog->bounds[0][1] + && bounds[1][2] >= fog->bounds[0][2] + && bounds[0][0] <= fog->bounds[1][0] + && bounds[0][1] <= fog->bounds[1][1] + && bounds[0][2] <= fog->bounds[1][2] ) { + break; + } + } + if ( fogIndex == tr.world->numfogs ) { + fogIndex = 0; + } + } + poly->fogIndex = fogIndex; + } +} + + +//================================================================================= + + +/* +===================== +RE_AddRefEntityToScene + +===================== +*/ +void RE_AddRefEntityToScene( const refEntity_t *ent ) { +#ifdef REACTION + // JBravo: Mirrored models + vec3_t cross; +#endif + + if ( !tr.registered ) { + return; + } + if ( r_numentities >= MAX_REFENTITIES ) { + ri.Printf(PRINT_DEVELOPER, "RE_AddRefEntityToScene: Dropping refEntity, reached MAX_REFENTITIES\n"); + return; + } + if ( Q_isnan(ent->origin[0]) || Q_isnan(ent->origin[1]) || Q_isnan(ent->origin[2]) ) { + static qboolean firstTime = qtrue; + if (firstTime) { + firstTime = qfalse; + ri.Printf( PRINT_WARNING, "RE_AddRefEntityToScene passed a refEntity which has an origin with a NaN component\n"); + } + return; + } + if ( (int)ent->reType < 0 || ent->reType >= RT_MAX_REF_ENTITY_TYPE ) { + ri.Error( ERR_DROP, "RE_AddRefEntityToScene: bad reType %i", ent->reType ); + } + + backEndData[tr.smpFrame]->entities[r_numentities].e = *ent; + backEndData[tr.smpFrame]->entities[r_numentities].lightingCalculated = qfalse; + +#ifdef REACTION + // JBravo: Mirrored models + CrossProduct(ent->axis[0], ent->axis[1], cross); + backEndData[tr.smpFrame]->entities[r_numentities].mirrored = (DotProduct(ent->axis[2], cross) < 0.f); +#endif + + r_numentities++; +} + + +/* +===================== +RE_AddDynamicLightToScene + +===================== +*/ +void RE_AddDynamicLightToScene( const vec3_t org, float intensity, float r, float g, float b, int additive ) { + dlight_t *dl; + + if ( !tr.registered ) { + return; + } + if ( r_numdlights >= MAX_DLIGHTS ) { + return; + } + if ( intensity <= 0 ) { + return; + } + // these cards don't have the correct blend mode + if ( glConfig.hardwareType == GLHW_RIVA128 || glConfig.hardwareType == GLHW_PERMEDIA2 ) { + return; + } + dl = &backEndData[tr.smpFrame]->dlights[r_numdlights++]; + VectorCopy (org, dl->origin); + dl->radius = intensity; + dl->color[0] = r; + dl->color[1] = g; + dl->color[2] = b; + dl->additive = additive; +} + +/* +===================== +RE_AddLightToScene + +===================== +*/ +void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + RE_AddDynamicLightToScene( org, intensity, r, g, b, qfalse ); +} + +/* +===================== +RE_AddAdditiveLightToScene + +===================== +*/ +void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + RE_AddDynamicLightToScene( org, intensity, r, g, b, qtrue ); +} + +/* +@@@@@@@@@@@@@@@@@@@@@ +RE_RenderScene + +Draw a 3D view into a part of the window, then return +to 2D drawing. + +Rendering a scene may require multiple views to be rendered +to handle mirrors, +@@@@@@@@@@@@@@@@@@@@@ +*/ +void RE_RenderScene( const refdef_t *fd ) { + viewParms_t parms; + int startTime; + + if ( !tr.registered ) { + return; + } + GLimp_LogComment( "====== RE_RenderScene =====\n" ); + + if ( r_norefresh->integer ) { + return; + } + + startTime = ri.Milliseconds(); + + if (!tr.world && !( fd->rdflags & RDF_NOWORLDMODEL ) ) { + ri.Error (ERR_DROP, "R_RenderScene: NULL worldmodel"); + } + + Com_Memcpy( tr.refdef.text, fd->text, sizeof( tr.refdef.text ) ); + + tr.refdef.x = fd->x; + tr.refdef.y = fd->y; + tr.refdef.width = fd->width; + tr.refdef.height = fd->height; + tr.refdef.fov_x = fd->fov_x; + tr.refdef.fov_y = fd->fov_y; + + VectorCopy( fd->vieworg, tr.refdef.vieworg ); + VectorCopy( fd->viewaxis[0], tr.refdef.viewaxis[0] ); + VectorCopy( fd->viewaxis[1], tr.refdef.viewaxis[1] ); + VectorCopy( fd->viewaxis[2], tr.refdef.viewaxis[2] ); + + tr.refdef.time = fd->time; + tr.refdef.rdflags = fd->rdflags; + + // copy the areamask data over and note if it has changed, which + // will force a reset of the visible leafs even if the view hasn't moved + tr.refdef.areamaskModified = qfalse; + if ( ! (tr.refdef.rdflags & RDF_NOWORLDMODEL) ) { + int areaDiff; + int i; + + // compare the area bits + areaDiff = 0; + for (i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++) { + areaDiff |= ((int *)tr.refdef.areamask)[i] ^ ((int *)fd->areamask)[i]; + ((int *)tr.refdef.areamask)[i] = ((int *)fd->areamask)[i]; + } + + if ( areaDiff ) { + // a door just opened or something + tr.refdef.areamaskModified = qtrue; + } + } + + tr.refdef.sunDir[3] = 0.0f; + tr.refdef.sunCol[3] = 1.0f; + tr.refdef.sunAmbCol[3] = 1.0f; + + VectorCopy(tr.sunDirection, tr.refdef.sunDir); + if ( (tr.refdef.rdflags & RDF_NOWORLDMODEL) || !(r_depthPrepass->value) ){ + tr.refdef.colorScale = 1.0f; + VectorSet(tr.refdef.sunCol, 0, 0, 0); + VectorSet(tr.refdef.sunAmbCol, 0, 0, 0); + } + else if (r_forceSun->integer == 1) + { + float scale = pow(2, r_mapOverBrightBits->integer - tr.overbrightBits - 8); + tr.refdef.colorScale = r_forceSunMapLightScale->value; + VectorScale(tr.sunLight, scale * r_forceSunLightScale->value, tr.refdef.sunCol); + VectorScale(tr.sunLight, scale * r_forceSunAmbientScale->value, tr.refdef.sunAmbCol); + } + else + { + float scale = pow(2, r_mapOverBrightBits->integer - tr.overbrightBits - 8); + tr.refdef.colorScale = tr.mapLightScale; + VectorScale(tr.sunLight, scale, tr.refdef.sunCol); + VectorScale(tr.sunAmbient, scale, tr.refdef.sunAmbCol); + } + + if (r_forceAutoExposure->integer) + { + tr.refdef.autoExposureMinMax[0] = r_forceAutoExposureMin->value; + tr.refdef.autoExposureMinMax[1] = r_forceAutoExposureMax->value; + } + else + { + tr.refdef.autoExposureMinMax[0] = tr.autoExposureMinMax[0]; + tr.refdef.autoExposureMinMax[1] = tr.autoExposureMinMax[1]; + } + + if (r_forceToneMap->integer) + { + tr.refdef.toneMinAvgMaxLinear[0] = pow(2, r_forceToneMapMin->value); + tr.refdef.toneMinAvgMaxLinear[1] = pow(2, r_forceToneMapAvg->value); + tr.refdef.toneMinAvgMaxLinear[2] = pow(2, r_forceToneMapMax->value); + } + else + { + tr.refdef.toneMinAvgMaxLinear[0] = pow(2, tr.toneMinAvgMaxLevel[0]); + tr.refdef.toneMinAvgMaxLinear[1] = pow(2, tr.toneMinAvgMaxLevel[1]); + tr.refdef.toneMinAvgMaxLinear[2] = pow(2, tr.toneMinAvgMaxLevel[2]); + } + +//#ifdef REACTION + // Makro - copy exta info if present + if (fd->rdflags & RDF_EXTRA) { + const refdefex_t* extra = (const refdefex_t*) (fd+1); + + tr.refdef.blurFactor = extra->blurFactor; + + if (fd->rdflags & RDF_SUNLIGHT) + { + VectorCopy(extra->sunDir, tr.refdef.sunDir); + VectorCopy(extra->sunCol, tr.refdef.sunCol); + VectorCopy(extra->sunAmbCol, tr.refdef.sunAmbCol); + } + } + else + { + tr.refdef.blurFactor = 0.0f; + } +//#endif + + // derived info + + tr.refdef.floatTime = tr.refdef.time * 0.001f; + + tr.refdef.numDrawSurfs = r_firstSceneDrawSurf; + tr.refdef.drawSurfs = backEndData[tr.smpFrame]->drawSurfs; + + tr.refdef.num_entities = r_numentities - r_firstSceneEntity; + tr.refdef.entities = &backEndData[tr.smpFrame]->entities[r_firstSceneEntity]; + + tr.refdef.num_dlights = r_numdlights - r_firstSceneDlight; + tr.refdef.dlights = &backEndData[tr.smpFrame]->dlights[r_firstSceneDlight]; + + tr.refdef.numPolys = r_numpolys - r_firstScenePoly; + tr.refdef.polys = &backEndData[tr.smpFrame]->polys[r_firstScenePoly]; + + tr.refdef.num_pshadows = 0; + tr.refdef.pshadows = &backEndData[tr.smpFrame]->pshadows[0]; + + // turn off dynamic lighting globally by clearing all the + // dlights if it needs to be disabled or if vertex lighting is enabled + if ( r_dynamiclight->integer == 0 || + r_vertexLight->integer == 1 || + glConfig.hardwareType == GLHW_PERMEDIA2 ) { + tr.refdef.num_dlights = 0; + } + + // a single frame may have multiple scenes draw inside it -- + // a 3D game view, 3D status bar renderings, 3D menus, etc. + // They need to be distinguished by the light flare code, because + // the visibility state for a given surface may be different in + // each scene / view. + tr.frameSceneNum++; + tr.sceneCount++; + + // SmileTheory: playing with shadow mapping + if (!( fd->rdflags & RDF_NOWORLDMODEL ) && tr.refdef.num_dlights && r_dlightMode->integer >= 2) + { + R_RenderDlightCubemaps(fd); + } + + /* playing with more shadows */ + if(!( fd->rdflags & RDF_NOWORLDMODEL ) && r_shadows->integer == 4) + { + R_RenderPshadowMaps(fd); + } + + // playing with even more shadows + if(!( fd->rdflags & RDF_NOWORLDMODEL ) && (r_forceSun->integer || tr.sunShadows)) + { + R_RenderSunShadowMaps(fd, 0); + R_RenderSunShadowMaps(fd, 1); + R_RenderSunShadowMaps(fd, 2); + } + + // setup view parms for the initial view + // + // set up viewport + // The refdef takes 0-at-the-top y coordinates, so + // convert to GL's 0-at-the-bottom space + // + Com_Memset( &parms, 0, sizeof( parms ) ); + parms.viewportX = tr.refdef.x; + parms.viewportY = glConfig.vidHeight - ( tr.refdef.y + tr.refdef.height ); + parms.viewportWidth = tr.refdef.width; + parms.viewportHeight = tr.refdef.height; + parms.isPortal = qfalse; + + parms.fovX = tr.refdef.fov_x; + parms.fovY = tr.refdef.fov_y; + + parms.stereoFrame = tr.refdef.stereoFrame; + + if (glRefConfig.framebufferObject) + { + parms.targetFbo = tr.renderFbo; + } + + VectorCopy( fd->vieworg, parms.or.origin ); + VectorCopy( fd->viewaxis[0], parms.or.axis[0] ); + VectorCopy( fd->viewaxis[1], parms.or.axis[1] ); + VectorCopy( fd->viewaxis[2], parms.or.axis[2] ); + + VectorCopy( fd->vieworg, parms.pvsOrigin ); + + if(!( fd->rdflags & RDF_NOWORLDMODEL ) && r_depthPrepass->value && ((r_forceSun->integer) || tr.sunShadows)) + { + parms.flags = VPF_USESUNLIGHT; + } + + R_RenderView( &parms ); + + if(!( fd->rdflags & RDF_NOWORLDMODEL )) + R_AddPostProcessCmd(); + + // the next scene rendered in this frame will tack on after this one + r_firstSceneDrawSurf = tr.refdef.numDrawSurfs; + r_firstSceneEntity = r_numentities; + r_firstSceneDlight = r_numdlights; + r_firstScenePoly = r_numpolys; + + tr.frontEndMsec += ri.Milliseconds() - startTime; +} diff --git a/src/rend2/tr_shade.c b/src/rend2/tr_shade.c new file mode 100644 index 00000000..4717c956 --- /dev/null +++ b/src/rend2/tr_shade.c @@ -0,0 +1,1818 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_shade.c + +#include "tr_local.h" +#if idppc_altivec && !defined(MACOS_X) +#include +#endif + +/* + + THIS ENTIRE FILE IS BACK END + + This file deals with applying shaders to surface data in the tess struct. +*/ + + +/* +================== +R_DrawElements + +================== +*/ + +void R_DrawElementsVBO( int numIndexes, int firstIndex ) +{ + if (glRefConfig.drawRangeElements) + qglDrawRangeElementsEXT(GL_TRIANGLES, 0, numIndexes, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(GL_INDEX_TYPE))); + else + qglDrawElements(GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(GL_INDEX_TYPE))); + +} + + +static void R_DrawMultiElementsVBO( int multiDrawPrimitives, const GLvoid **multiDrawFirstIndex, GLsizei *multiDrawNumIndexes ) +{ + if (glRefConfig.multiDrawArrays) + { + qglMultiDrawElementsEXT(GL_TRIANGLES, multiDrawNumIndexes, GL_INDEX_TYPE, multiDrawFirstIndex, multiDrawPrimitives); + } + else + { + int i; + + if (glRefConfig.drawRangeElements) + { + for (i = 0; i < multiDrawPrimitives; i++) + { + qglDrawRangeElementsEXT(GL_TRIANGLES, 0, multiDrawNumIndexes[i], multiDrawNumIndexes[i], GL_INDEX_TYPE, multiDrawFirstIndex[i]); + } + } + else + { + for (i = 0; i < multiDrawPrimitives; i++) + { + qglDrawElements(GL_TRIANGLES, multiDrawNumIndexes[i], GL_INDEX_TYPE, multiDrawFirstIndex[i]); + } + } + } +} + + +/* +============================================================= + +SURFACE SHADERS + +============================================================= +*/ + +shaderCommands_t tess; + + +/* +================= +R_BindAnimatedImageToTMU + +================= +*/ +static void R_BindAnimatedImageToTMU( textureBundle_t *bundle, int tmu ) { + int index; + + if ( bundle->isVideoMap ) { + int oldtmu = glState.currenttmu; + GL_SelectTexture(tmu); + ri.CIN_RunCinematic(bundle->videoMapHandle); + ri.CIN_UploadCinematic(bundle->videoMapHandle); + GL_SelectTexture(oldtmu); + return; + } + + if ( bundle->numImageAnimations <= 1 ) { + GL_BindToTMU( bundle->image[0], tmu); + return; + } + + // it is necessary to do this messy calc to make sure animations line up + // exactly with waveforms of the same frequency + index = ri.ftol(tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE); + index >>= FUNCTABLE_SIZE2; + + if ( index < 0 ) { + index = 0; // may happen with shader time offsets + } + index %= bundle->numImageAnimations; + + GL_BindToTMU( bundle->image[ index ], tmu ); +} + + +/* +================ +DrawTris + +Draws triangle outlines for debugging +================ +*/ +static void DrawTris (shaderCommands_t *input) { + GL_Bind( tr.whiteImage ); + + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE ); + qglDepthRange( 0, 0 ); + + { + shaderProgram_t *sp = &tr.textureColorShader; + vec4_t color; + + GLSL_VertexAttribsState(ATTR_POSITION); + GLSL_BindProgram(sp); + + GLSL_SetUniformMatrix16(sp, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + VectorSet4(color, 1, 1, 1, 1); + GLSL_SetUniformVec4(sp, TEXTURECOLOR_UNIFORM_COLOR, color); + + if (input->multiDrawPrimitives) + { + R_DrawMultiElementsVBO(input->multiDrawPrimitives, (const GLvoid **)input->multiDrawFirstIndex, input->multiDrawNumIndexes); + } + else + { + R_DrawElementsVBO(input->numIndexes, input->firstIndex); + } + } + + qglDepthRange( 0, 1 ); +} + + +/* +================ +DrawNormals + +Draws vertex normals for debugging +================ +*/ +static void DrawNormals (shaderCommands_t *input) { + //FIXME: implement this +} + +/* +============== +RB_BeginSurface + +We must set some things up before beginning any tesselation, +because a surface may be forced to perform a RB_End due +to overflow. +============== +*/ +void RB_BeginSurface( shader_t *shader, int fogNum ) { + + shader_t *state = (shader->remappedShader) ? shader->remappedShader : shader; + + tess.numIndexes = 0; + tess.firstIndex = 0; + tess.numVertexes = 0; + tess.multiDrawPrimitives = 0; + tess.shader = state; + tess.fogNum = fogNum; + tess.dlightBits = 0; // will be OR'd in by surface functions + tess.pshadowBits = 0; // will be OR'd in by surface functions + tess.xstages = state->stages; + tess.numPasses = state->numUnfoggedPasses; + tess.currentStageIteratorFunc = state->optimalStageIteratorFunc; + tess.useInternalVBO = qtrue; + + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + if (tess.shader->clampTime && tess.shaderTime >= tess.shader->clampTime) { + tess.shaderTime = tess.shader->clampTime; + } + + if (backEnd.viewParms.flags & VPF_SHADOWMAP) + { + tess.currentStageIteratorFunc = RB_StageIteratorGeneric; + } +} + + + +extern float EvalWaveForm( const waveForm_t *wf ); +extern float EvalWaveFormClamped( const waveForm_t *wf ); + + +static void ComputeTexMatrix( shaderStage_t *pStage, int bundleNum, float *outmatrix) +{ + int tm; + float matrix[16], currentmatrix[16]; + textureBundle_t *bundle = &pStage->bundle[bundleNum]; + + Matrix16Identity(outmatrix); + Matrix16Identity(currentmatrix); + + for ( tm = 0; tm < bundle->numTexMods ; tm++ ) { + switch ( bundle->texMods[tm].type ) + { + + case TMOD_NONE: + tm = TR_MAX_TEXMODS; // break out of for loop + break; + + case TMOD_TURBULENT: + RB_CalcTurbulentTexMatrix( &bundle->texMods[tm].wave, + matrix ); + outmatrix[12] = matrix[12]; + outmatrix[13] = matrix[13]; + Matrix16Copy(outmatrix, currentmatrix); + break; + + case TMOD_ENTITY_TRANSLATE: + RB_CalcScrollTexMatrix( backEnd.currentEntity->e.shaderTexCoord, + matrix ); + Matrix16Multiply(matrix, currentmatrix, outmatrix); + Matrix16Copy(outmatrix, currentmatrix); + break; + + case TMOD_SCROLL: + RB_CalcScrollTexMatrix( bundle->texMods[tm].scroll, + matrix ); + Matrix16Multiply(matrix, currentmatrix, outmatrix); + Matrix16Copy(outmatrix, currentmatrix); + break; + + case TMOD_SCALE: + RB_CalcScaleTexMatrix( bundle->texMods[tm].scale, + matrix ); + Matrix16Multiply(matrix, currentmatrix, outmatrix); + Matrix16Copy(outmatrix, currentmatrix); + break; + + case TMOD_STRETCH: + RB_CalcStretchTexMatrix( &bundle->texMods[tm].wave, + matrix ); + Matrix16Multiply(matrix, currentmatrix, outmatrix); + Matrix16Copy(outmatrix, currentmatrix); + break; + + case TMOD_TRANSFORM: + RB_CalcTransformTexMatrix( &bundle->texMods[tm], + matrix ); + Matrix16Multiply(matrix, currentmatrix, outmatrix); + Matrix16Copy(outmatrix, currentmatrix); + break; + + case TMOD_ROTATE: + RB_CalcRotateTexMatrix( bundle->texMods[tm].rotateSpeed, + matrix ); + Matrix16Multiply(matrix, currentmatrix, outmatrix); + Matrix16Copy(outmatrix, currentmatrix); + break; + + default: + ri.Error( ERR_DROP, "ERROR: unknown texmod '%d' in shader '%s'\n", bundle->texMods[tm].type, tess.shader->name ); + break; + } + } +} + + +static void ComputeDeformValues(int *deformGen, vec5_t deformParams) +{ + // u_DeformGen + *deformGen = DGEN_NONE; + if(!ShaderRequiresCPUDeforms(tess.shader)) + { + deformStage_t *ds; + + // only support the first one + ds = &tess.shader->deforms[0]; + + switch (ds->deformation) + { + case DEFORM_WAVE: + *deformGen = ds->deformationWave.func; + + deformParams[0] = ds->deformationWave.base; + deformParams[1] = ds->deformationWave.amplitude; + deformParams[2] = ds->deformationWave.phase; + deformParams[3] = ds->deformationWave.frequency; + deformParams[4] = ds->deformationSpread; + break; + + case DEFORM_BULGE: + *deformGen = DGEN_BULGE; + + deformParams[0] = 0; + deformParams[1] = ds->bulgeHeight; // amplitude + deformParams[2] = ds->bulgeWidth; // phase + deformParams[3] = ds->bulgeSpeed; // frequency + deformParams[4] = 0; + break; + + default: + break; + } + } +} + + +static void ProjectDlightTexture( void ) { + int l; + vec3_t origin; + float scale; + float radius; + int deformGen; + vec5_t deformParams; + + if ( !backEnd.refdef.num_dlights ) { + return; + } + + ComputeDeformValues(&deformGen, deformParams); + + for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) { + dlight_t *dl; + shaderProgram_t *sp; + vec4_t vector; + + if ( !( tess.dlightBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this light + } + + dl = &backEnd.refdef.dlights[l]; + VectorCopy( dl->transformed, origin ); + radius = dl->radius; + scale = 1.0f / radius; + + sp = &tr.dlightallShader; + + backEnd.pc.c_dlightDraws++; + + GLSL_BindProgram(sp); + + GLSL_SetUniformMatrix16(sp, DLIGHT_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + + GLSL_SetUniformFloat(sp, DLIGHT_UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation); + + GLSL_SetUniformInt(sp, DLIGHT_UNIFORM_DEFORMGEN, deformGen); + if (deformGen != DGEN_NONE) + { + GLSL_SetUniformFloat5(sp, DLIGHT_UNIFORM_DEFORMPARAMS, deformParams); + GLSL_SetUniformFloat(sp, DLIGHT_UNIFORM_TIME, tess.shaderTime); + } + + vector[0] = dl->color[0]; + vector[1] = dl->color[1]; + vector[2] = dl->color[2]; + vector[3] = 1.0f; + GLSL_SetUniformVec4(sp, DLIGHT_UNIFORM_COLOR, vector); + + vector[0] = origin[0]; + vector[1] = origin[1]; + vector[2] = origin[2]; + vector[3] = scale; + GLSL_SetUniformVec4(sp, DLIGHT_UNIFORM_DLIGHTINFO, vector); + + GL_Bind( tr.dlightImage ); + + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + if ( dl->additive ) { + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + else { + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + + if (tess.multiDrawPrimitives) + { + R_DrawMultiElementsVBO(tess.multiDrawPrimitives, (const GLvoid **)tess.multiDrawFirstIndex, tess.multiDrawNumIndexes); + } + else + { + R_DrawElementsVBO(tess.numIndexes, tess.firstIndex); + } + + backEnd.pc.c_totalIndexes += tess.numIndexes; + backEnd.pc.c_dlightIndexes += tess.numIndexes; + } +} + + +static void ComputeShaderColors( shaderStage_t *pStage, vec4_t baseColor, vec4_t vertColor ) +{ + // + // rgbGen + // + switch ( pStage->rgbGen ) + { + case CGEN_IDENTITY: + baseColor[0] = + baseColor[1] = + baseColor[2] = + baseColor[3] = 1.0f; + + vertColor[0] = + vertColor[1] = + vertColor[2] = + vertColor[3] = 0.0f; + break; + case CGEN_IDENTITY_LIGHTING: + baseColor[0] = + baseColor[1] = + baseColor[2] = tr.identityLight; + baseColor[3] = 1.0f; + + vertColor[0] = + vertColor[1] = + vertColor[2] = + vertColor[3] = 0.0f; + break; + case CGEN_EXACT_VERTEX: + baseColor[0] = + baseColor[1] = + baseColor[2] = + baseColor[3] = 0.0f; + + vertColor[0] = + vertColor[1] = + vertColor[2] = + vertColor[3] = 1.0f; + break; + case CGEN_EXACT_VERTEX_LIT: + baseColor[0] = + baseColor[1] = + baseColor[2] = 1.0f; + baseColor[3] = 0.0f; + + vertColor[0] = + vertColor[1] = + vertColor[2] = 0.0f; + vertColor[3] = 1.0f; + break; + case CGEN_CONST: + baseColor[0] = pStage->constantColor[0] / 255.0f; + baseColor[1] = pStage->constantColor[1] / 255.0f; + baseColor[2] = pStage->constantColor[2] / 255.0f; + baseColor[3] = pStage->constantColor[3] / 255.0f; + + vertColor[0] = + vertColor[1] = + vertColor[2] = + vertColor[3] = 0.0f; + break; + case CGEN_VERTEX: + baseColor[0] = + baseColor[1] = + baseColor[2] = + baseColor[3] = 0.0f; + + vertColor[0] = + vertColor[1] = + vertColor[2] = tr.identityLight; + vertColor[3] = 1.0f; + break; + case CGEN_VERTEX_LIT: + baseColor[0] = + baseColor[1] = + baseColor[2] = tr.identityLight; + baseColor[3] = 0.0f; + + vertColor[0] = + vertColor[1] = + vertColor[2] = 0.0f; + vertColor[3] = 1.0f; + break; + case CGEN_ONE_MINUS_VERTEX: + baseColor[0] = + baseColor[1] = + baseColor[2] = tr.identityLight; + baseColor[3] = 1.0f; + + vertColor[0] = + vertColor[1] = + vertColor[2] = -tr.identityLight; + vertColor[3] = 0.0f; + break; + case CGEN_FOG: + { + fog_t *fog; + + fog = tr.world->fogs + tess.fogNum; + + baseColor[0] = ((unsigned char *)(&fog->colorInt))[0] / 255.0f; + baseColor[1] = ((unsigned char *)(&fog->colorInt))[1] / 255.0f; + baseColor[2] = ((unsigned char *)(&fog->colorInt))[2] / 255.0f; + baseColor[3] = ((unsigned char *)(&fog->colorInt))[3] / 255.0f; + } + + vertColor[0] = + vertColor[1] = + vertColor[2] = + vertColor[3] = 0.0f; + break; + case CGEN_WAVEFORM: + baseColor[0] = + baseColor[1] = + baseColor[2] = RB_CalcWaveColorSingle( &pStage->rgbWave ); + baseColor[3] = 1.0f; + + vertColor[0] = + vertColor[1] = + vertColor[2] = + vertColor[3] = 0.0f; + break; + case CGEN_ENTITY: + if (backEnd.currentEntity) + { + baseColor[0] = ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[0] / 255.0f; + baseColor[1] = ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[1] / 255.0f; + baseColor[2] = ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[2] / 255.0f; + baseColor[3] = ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[3] / 255.0f; + } + + vertColor[0] = + vertColor[1] = + vertColor[2] = + vertColor[3] = 0.0f; + break; + case CGEN_ONE_MINUS_ENTITY: + if (backEnd.currentEntity) + { + baseColor[0] = 1.0f - ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[0] / 255.0f; + baseColor[1] = 1.0f - ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[1] / 255.0f; + baseColor[2] = 1.0f - ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[2] / 255.0f; + baseColor[3] = 1.0f - ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[3] / 255.0f; + } + + vertColor[0] = + vertColor[1] = + vertColor[2] = + vertColor[3] = 0.0f; + break; + case CGEN_LIGHTING_DIFFUSE: + case CGEN_BAD: + baseColor[0] = + baseColor[1] = + baseColor[2] = + baseColor[3] = 1.0f; + + vertColor[0] = + vertColor[1] = + vertColor[2] = + vertColor[3] = 0.0f; + break; + } + + // + // alphaGen + // + switch ( pStage->alphaGen ) + { + case AGEN_SKIP: + break; + case AGEN_IDENTITY: + baseColor[3] = 1.0f; + vertColor[3] = 0.0f; + break; + case AGEN_CONST: + baseColor[3] = pStage->constantColor[3] / 255.0f; + vertColor[3] = 0.0f; + break; + case AGEN_WAVEFORM: + baseColor[3] = RB_CalcWaveAlphaSingle( &pStage->alphaWave ); + vertColor[3] = 0.0f; + break; + case AGEN_ENTITY: + if (backEnd.currentEntity) + { + baseColor[3] = ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[3] / 255.0f; + } + vertColor[3] = 0.0f; + break; + case AGEN_ONE_MINUS_ENTITY: + if (backEnd.currentEntity) + { + baseColor[3] = 1.0f - ((unsigned char *)backEnd.currentEntity->e.shaderRGBA)[3] / 255.0f; + } + vertColor[3] = 0.0f; + break; + case AGEN_VERTEX: + baseColor[3] = 0.0f; + vertColor[3] = 1.0f; + break; + case AGEN_ONE_MINUS_VERTEX: + baseColor[3] = 1.0f; + vertColor[3] = -1.0f; + break; + case AGEN_LIGHTING_SPECULAR: + case AGEN_PORTAL: + case AGEN_FRESNEL: + // Done entirely in vertex program + baseColor[3] = 1.0f; + vertColor[3] = 0.0f; + break; + } + + // FIXME: find some way to implement this. +#if 0 + // if in greyscale rendering mode turn all color values into greyscale. + if(r_greyscale->integer) + { + int scale; + + for(i = 0; i < tess.numVertexes; i++) + { + scale = (tess.svars.colors[i][0] + tess.svars.colors[i][1] + tess.svars.colors[i][2]) / 3; + tess.svars.colors[i][0] = tess.svars.colors[i][1] = tess.svars.colors[i][2] = scale; + } + } +#endif +} + + +static void ComputeFogValues(vec4_t fogDistanceVector, vec4_t fogDepthVector, float *eyeT) +{ + // from RB_CalcFogTexCoords() + fog_t *fog; + vec3_t local; + + if (!tess.fogNum) + return; + + fog = tr.world->fogs + tess.fogNum; + + VectorSubtract( backEnd.or.origin, backEnd.viewParms.or.origin, local ); + fogDistanceVector[0] = -backEnd.or.modelMatrix[2]; + fogDistanceVector[1] = -backEnd.or.modelMatrix[6]; + fogDistanceVector[2] = -backEnd.or.modelMatrix[10]; + fogDistanceVector[3] = DotProduct( local, backEnd.viewParms.or.axis[0] ); + + // scale the fog vectors based on the fog's thickness + VectorScale4(fogDistanceVector, fog->tcScale, fogDistanceVector); + + // rotate the gradient vector for this orientation + if ( fog->hasSurface ) { + fogDepthVector[0] = fog->surface[0] * backEnd.or.axis[0][0] + + fog->surface[1] * backEnd.or.axis[0][1] + fog->surface[2] * backEnd.or.axis[0][2]; + fogDepthVector[1] = fog->surface[0] * backEnd.or.axis[1][0] + + fog->surface[1] * backEnd.or.axis[1][1] + fog->surface[2] * backEnd.or.axis[1][2]; + fogDepthVector[2] = fog->surface[0] * backEnd.or.axis[2][0] + + fog->surface[1] * backEnd.or.axis[2][1] + fog->surface[2] * backEnd.or.axis[2][2]; + fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.or.origin, fog->surface ); + + *eyeT = DotProduct( backEnd.or.viewOrigin, fogDepthVector ) + fogDepthVector[3]; + } else { + *eyeT = 1; // non-surface fog always has eye inside + } +} + + +static void ComputeFogColorMask( shaderStage_t *pStage, vec4_t fogColorMask ) +{ + switch(pStage->adjustColorsForFog) + { + case ACFF_MODULATE_RGB: + fogColorMask[0] = + fogColorMask[1] = + fogColorMask[2] = 1.0f; + fogColorMask[3] = 0.0f; + break; + case ACFF_MODULATE_ALPHA: + fogColorMask[0] = + fogColorMask[1] = + fogColorMask[2] = 0.0f; + fogColorMask[3] = 1.0f; + break; + case ACFF_MODULATE_RGBA: + fogColorMask[0] = + fogColorMask[1] = + fogColorMask[2] = + fogColorMask[3] = 1.0f; + break; + default: + fogColorMask[0] = + fogColorMask[1] = + fogColorMask[2] = + fogColorMask[3] = 0.0f; + break; + } +} + + +static void ForwardDlight( void ) { + int l; + //vec3_t origin; + //float scale; + float radius; + + int deformGen; + vec5_t deformParams; + + vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0}; + float eyeT = 0; + + shaderCommands_t *input = &tess; + shaderStage_t *pStage = tess.xstages[0]; + + if ( !backEnd.refdef.num_dlights ) { + return; + } + + ComputeDeformValues(&deformGen, deformParams); + + ComputeFogValues(fogDistanceVector, fogDepthVector, &eyeT); + + for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) { + dlight_t *dl; + shaderProgram_t *sp; + vec4_t vector; + matrix_t matrix; + + if ( !( tess.dlightBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this light + } + + dl = &backEnd.refdef.dlights[l]; + //VectorCopy( dl->transformed, origin ); + radius = dl->radius; + //scale = 1.0f / radius; + + //if (pStage->glslShaderGroup == tr.lightallShader) + { + int index = pStage->glslShaderIndex; + + index &= ~(LIGHTDEF_LIGHTTYPE_MASK | LIGHTDEF_USE_DELUXEMAP); + index |= LIGHTDEF_USE_LIGHT_VECTOR; + + sp = &tr.lightallShader[index]; + } + + backEnd.pc.c_lightallDraws++; + + GLSL_BindProgram(sp); + + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_VIEWORIGIN, backEnd.viewParms.or.origin); + + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation); + + GLSL_SetUniformInt(sp, GENERIC_UNIFORM_DEFORMGEN, deformGen); + if (deformGen != DGEN_NONE) + { + GLSL_SetUniformFloat5(sp, GENERIC_UNIFORM_DEFORMPARAMS, deformParams); + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_TIME, tess.shaderTime); + } + + if ( input->fogNum ) { + vec4_t fogColorMask; + + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGDISTANCE, fogDistanceVector); + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGDEPTH, fogDepthVector); + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_FOGEYET, eyeT); + + ComputeFogColorMask(pStage, fogColorMask); + + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGCOLORMASK, fogColorMask); + } + + { + vec4_t baseColor; + vec4_t vertColor; + + ComputeShaderColors(pStage, baseColor, vertColor); + + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_BASECOLOR, baseColor); + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_VERTCOLOR, vertColor); + } + + if (pStage->alphaGen == AGEN_PORTAL) + { + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_PORTALRANGE, tess.shader->portalRange); + } + + GLSL_SetUniformInt(sp, GENERIC_UNIFORM_COLORGEN, pStage->rgbGen); + GLSL_SetUniformInt(sp, GENERIC_UNIFORM_ALPHAGEN, pStage->alphaGen); + + GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_DIRECTEDLIGHT, dl->color); + + VectorSet(vector, 0, 0, 0); + GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_AMBIENTLIGHT, vector); + + VectorCopy(dl->origin, vector); + vector[3] = 1.0f; + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_LIGHTORIGIN, vector); + + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_LIGHTRADIUS, radius); + + GLSL_SetUniformVec2(sp, GENERIC_UNIFORM_MATERIALINFO, pStage->materialInfo); + + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELMATRIX, backEnd.or.transformMatrix); + + if (pStage->bundle[TB_DIFFUSEMAP].image[0]) + R_BindAnimatedImageToTMU( &pStage->bundle[TB_DIFFUSEMAP], TB_DIFFUSEMAP); + + if (pStage->bundle[TB_NORMALMAP].image[0]) + R_BindAnimatedImageToTMU( &pStage->bundle[TB_NORMALMAP], TB_NORMALMAP); + + if (pStage->bundle[TB_SPECULARMAP].image[0]) + R_BindAnimatedImageToTMU( &pStage->bundle[TB_SPECULARMAP], TB_SPECULARMAP); + + if (r_dlightMode->integer >= 2) + { + GL_SelectTexture(TB_SHADOWMAP); + GL_BindCubemap(tr.shadowCubemaps[l]); + GL_SelectTexture(0); + } + + ComputeTexMatrix( pStage, TB_DIFFUSEMAP, matrix ); + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_DIFFUSETEXMATRIX, matrix); + + // + // draw + // + + if (input->multiDrawPrimitives) + { + R_DrawMultiElementsVBO(input->multiDrawPrimitives, (const GLvoid **)input->multiDrawFirstIndex, input->multiDrawNumIndexes); + } + else + { + R_DrawElementsVBO(input->numIndexes, input->firstIndex); + } + + backEnd.pc.c_totalIndexes += tess.numIndexes; + backEnd.pc.c_dlightIndexes += tess.numIndexes; + } +} + + +static void ForwardSunlight( void ) { +// int l; + //vec3_t origin; + //float scale; + int stage; + int stageGlState[2]; + qboolean alphaOverride = qfalse; + + int deformGen; + vec5_t deformParams; + + vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0}; + float eyeT = 0; + + shaderCommands_t *input = &tess; + + ComputeDeformValues(&deformGen, deformParams); + + ComputeFogValues(fogDistanceVector, fogDepthVector, &eyeT); + + // deal with vertex alpha blended surfaces + if (input->xstages[0] && input->xstages[1] && + (input->xstages[1]->alphaGen == AGEN_VERTEX || input->xstages[1]->alphaGen == AGEN_ONE_MINUS_VERTEX)) + { + stageGlState[0] = input->xstages[0]->stateBits & (GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS); + + if (stageGlState[0] == 0 || stageGlState[0] == (GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO)) + { + stageGlState[1] = input->xstages[1]->stateBits & (GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS); + + if (stageGlState[1] == (GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA)) + { + alphaOverride = qtrue; + stageGlState[0] = GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL; + stageGlState[1] = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL; + } + else if (stageGlState[1] == (GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA | GLS_DSTBLEND_SRC_ALPHA)) + { + alphaOverride = qtrue; + stageGlState[0] = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL; + stageGlState[1] = GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL; + } + } + } + + if (!alphaOverride) + { + stageGlState[0] = + stageGlState[1] = GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL; + } + + for ( stage = 0; stage < 2 /*MAX_SHADER_STAGES */; stage++ ) + { + shaderStage_t *pStage = input->xstages[stage]; + shaderProgram_t *sp; + //vec4_t vector; + matrix_t matrix; + + if ( !pStage ) + { + break; + } + + //VectorCopy( dl->transformed, origin ); + + //if (pStage->glslShaderGroup == tr.lightallShader) + { + int index = pStage->glslShaderIndex; + + index &= ~(LIGHTDEF_LIGHTTYPE_MASK | LIGHTDEF_USE_DELUXEMAP); + index |= LIGHTDEF_USE_LIGHT_VECTOR | LIGHTDEF_USE_SHADOWMAP; + + if (backEnd.currentEntity && backEnd.currentEntity != &tr.worldEntity) + { + index |= LIGHTDEF_ENTITY; + } + + sp = &tr.lightallShader[index]; + } + + backEnd.pc.c_lightallDraws++; + + GLSL_BindProgram(sp); + + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_VIEWORIGIN, backEnd.viewParms.or.origin); + + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation); + + GLSL_SetUniformInt(sp, GENERIC_UNIFORM_DEFORMGEN, deformGen); + if (deformGen != DGEN_NONE) + { + GLSL_SetUniformFloat5(sp, GENERIC_UNIFORM_DEFORMPARAMS, deformParams); + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_TIME, tess.shaderTime); + } + + if ( input->fogNum ) { + vec4_t fogColorMask; + + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGDISTANCE, fogDistanceVector); + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGDEPTH, fogDepthVector); + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_FOGEYET, eyeT); + + ComputeFogColorMask(pStage, fogColorMask); + + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGCOLORMASK, fogColorMask); + } + + { + vec4_t baseColor; + vec4_t vertColor; + + ComputeShaderColors(pStage, baseColor, vertColor); + + if (alphaOverride) + { + if (input->xstages[1]->alphaGen == AGEN_VERTEX) + { + baseColor[3] = 0.0f; + vertColor[3] = 1.0f; + } + else if (input->xstages[1]->alphaGen == AGEN_ONE_MINUS_VERTEX) + { + baseColor[3] = 1.0f; + vertColor[3] = -1.0f; + } + } + + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_BASECOLOR, baseColor); + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_VERTCOLOR, vertColor); + } + + if (pStage->alphaGen == AGEN_PORTAL) + { + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_PORTALRANGE, tess.shader->portalRange); + } + + GLSL_SetUniformInt(sp, GENERIC_UNIFORM_COLORGEN, pStage->rgbGen); + GLSL_SetUniformInt(sp, GENERIC_UNIFORM_ALPHAGEN, pStage->alphaGen); + + GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_DIRECTEDLIGHT, backEnd.refdef.sunCol); + GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_AMBIENTLIGHT, backEnd.refdef.sunAmbCol); + + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_LIGHTORIGIN, backEnd.refdef.sunDir); + + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_LIGHTRADIUS, 9999999999.9f); + + GLSL_SetUniformVec2(sp, GENERIC_UNIFORM_MATERIALINFO, pStage->materialInfo); + + GL_State( stageGlState[stage] ); + + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELMATRIX, backEnd.or.transformMatrix); + + if (pStage->bundle[TB_DIFFUSEMAP].image[0]) + R_BindAnimatedImageToTMU( &pStage->bundle[TB_DIFFUSEMAP], TB_DIFFUSEMAP); + + if (pStage->bundle[TB_NORMALMAP].image[0]) + R_BindAnimatedImageToTMU( &pStage->bundle[TB_NORMALMAP], TB_NORMALMAP); + + if (pStage->bundle[TB_SPECULARMAP].image[0]) + R_BindAnimatedImageToTMU( &pStage->bundle[TB_SPECULARMAP], TB_SPECULARMAP); + + /* + { + GL_BindToTMU(tr.sunShadowDepthImage[0], TB_SHADOWMAP); + GL_BindToTMU(tr.sunShadowDepthImage[1], TB_SHADOWMAP2); + GL_BindToTMU(tr.sunShadowDepthImage[2], TB_SHADOWMAP3); + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_SHADOWMVP, backEnd.refdef.sunShadowMvp[0]); + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_SHADOWMVP2, backEnd.refdef.sunShadowMvp[1]); + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_SHADOWMVP3, backEnd.refdef.sunShadowMvp[2]); + } + */ + GL_BindToTMU(tr.screenShadowImage, TB_SHADOWMAP); + + ComputeTexMatrix( pStage, TB_DIFFUSEMAP, matrix ); + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_DIFFUSETEXMATRIX, matrix); + + // + // draw + // + + if (input->multiDrawPrimitives) + { + R_DrawMultiElementsVBO(input->multiDrawPrimitives, (const GLvoid **)input->multiDrawFirstIndex, input->multiDrawNumIndexes); + } + else + { + R_DrawElementsVBO(input->numIndexes, input->firstIndex); + } + + backEnd.pc.c_totalIndexes += tess.numIndexes; + backEnd.pc.c_dlightIndexes += tess.numIndexes; + } +} + + +static void ProjectPshadowVBOGLSL( void ) { + int l; + vec3_t origin; + float radius; + + int deformGen; + vec5_t deformParams; + + shaderCommands_t *input = &tess; + + if ( !backEnd.refdef.num_pshadows ) { + return; + } + + ComputeDeformValues(&deformGen, deformParams); + + for ( l = 0 ; l < backEnd.refdef.num_pshadows ; l++ ) { + pshadow_t *ps; + shaderProgram_t *sp; + vec4_t vector; + + if ( !( tess.pshadowBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this shadow + } + + ps = &backEnd.refdef.pshadows[l]; + VectorCopy( ps->lightOrigin, origin ); + radius = ps->lightRadius; + + sp = &tr.pshadowShader; + + GLSL_BindProgram(sp); + + GLSL_SetUniformMatrix16(sp, PSHADOW_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + + VectorCopy(origin, vector); + vector[3] = 1.0f; + GLSL_SetUniformVec4(sp, PSHADOW_UNIFORM_LIGHTORIGIN, vector); + + VectorScale(ps->lightViewAxis[0], 1.0f / ps->viewRadius, vector); + GLSL_SetUniformVec3(sp, PSHADOW_UNIFORM_LIGHTFORWARD, vector); + + VectorScale(ps->lightViewAxis[1], 1.0f / ps->viewRadius, vector); + GLSL_SetUniformVec3(sp, PSHADOW_UNIFORM_LIGHTRIGHT, vector); + + VectorScale(ps->lightViewAxis[2], 1.0f / ps->viewRadius, vector); + GLSL_SetUniformVec3(sp, PSHADOW_UNIFORM_LIGHTUP, vector); + + GLSL_SetUniformFloat(sp, PSHADOW_UNIFORM_LIGHTRADIUS, radius); + + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL ); + + GL_BindToTMU( tr.pshadowMaps[l], TB_DIFFUSEMAP ); + + // + // draw + // + + if (input->multiDrawPrimitives) + { + R_DrawMultiElementsVBO(input->multiDrawPrimitives, (const GLvoid **)input->multiDrawFirstIndex, input->multiDrawNumIndexes); + } + else + { + R_DrawElementsVBO(input->numIndexes, input->firstIndex); + } + + backEnd.pc.c_totalIndexes += tess.numIndexes; + //backEnd.pc.c_dlightIndexes += tess.numIndexes; + } +} + + + +/* +=================== +RB_FogPass + +Blends a fog texture on top of everything else +=================== +*/ +static void RB_FogPass( void ) { + fog_t *fog; + vec4_t color; + vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0}; + float eyeT = 0; + shaderProgram_t *sp = &tr.fogShader; + + int deformGen; + vec5_t deformParams; + + ComputeDeformValues(&deformGen, deformParams); + + backEnd.pc.c_fogDraws++; + + GLSL_BindProgram(sp); + + fog = tr.world->fogs + tess.fogNum; + + GLSL_SetUniformMatrix16(sp, FOGPASS_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + + GLSL_SetUniformFloat(sp, FOGPASS_UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation); + + GLSL_SetUniformInt(sp, FOGPASS_UNIFORM_DEFORMGEN, deformGen); + if (deformGen != DGEN_NONE) + { + GLSL_SetUniformFloat5(sp, FOGPASS_UNIFORM_DEFORMPARAMS, deformParams); + GLSL_SetUniformFloat(sp, FOGPASS_UNIFORM_TIME, tess.shaderTime); + } + + color[0] = ((unsigned char *)(&fog->colorInt))[0] / 255.0f; + color[1] = ((unsigned char *)(&fog->colorInt))[1] / 255.0f; + color[2] = ((unsigned char *)(&fog->colorInt))[2] / 255.0f; + color[3] = ((unsigned char *)(&fog->colorInt))[3] / 255.0f; + GLSL_SetUniformVec4(sp, FOGPASS_UNIFORM_COLOR, color); + + ComputeFogValues(fogDistanceVector, fogDepthVector, &eyeT); + + GLSL_SetUniformVec4(sp, FOGPASS_UNIFORM_FOGDISTANCE, fogDistanceVector); + GLSL_SetUniformVec4(sp, FOGPASS_UNIFORM_FOGDEPTH, fogDepthVector); + GLSL_SetUniformFloat(sp, FOGPASS_UNIFORM_FOGEYET, eyeT); + + if ( tess.shader->fogPass == FP_EQUAL ) { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL ); + } else { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + } + + if (tess.multiDrawPrimitives) + { + R_DrawMultiElementsVBO(tess.multiDrawPrimitives, (const GLvoid **)tess.multiDrawFirstIndex, tess.multiDrawNumIndexes); + } + else + { + R_DrawElementsVBO(tess.numIndexes, tess.firstIndex); + } +} + + +static unsigned int RB_CalcShaderVertexAttribs( shaderCommands_t *input ) +{ + unsigned int vertexAttribs = input->shader->vertexAttribs; + + if(glState.vertexAttribsInterpolation > 0.0f) + { + vertexAttribs |= ATTR_POSITION2; + if (vertexAttribs & ATTR_NORMAL) + { + vertexAttribs |= ATTR_NORMAL2; +#ifdef USE_VERT_TANGENT_SPACE + vertexAttribs |= ATTR_TANGENT2; + vertexAttribs |= ATTR_BITANGENT2; +#endif + } + } + + return vertexAttribs; +} + +static void RB_IterateStagesGeneric( shaderCommands_t *input ) +{ + int stage; + matrix_t matrix; + + vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0}; + float eyeT = 0; + + int deformGen; + vec5_t deformParams; + + ComputeDeformValues(&deformGen, deformParams); + + ComputeFogValues(fogDistanceVector, fogDepthVector, &eyeT); + + for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) + { + shaderStage_t *pStage = input->xstages[stage]; + shaderProgram_t *sp; + + if ( !pStage ) + { + break; + } + + if (backEnd.depthFill) + { + if (pStage->glslShaderGroup) + { + int index = 0; + + if (backEnd.currentEntity && backEnd.currentEntity != &tr.worldEntity) + { + index |= LIGHTDEF_ENTITY; + } + + sp = &pStage->glslShaderGroup[index]; + } + else + { + int shaderAttribs = 0; + + if (tess.shader->numDeforms && !ShaderRequiresCPUDeforms(tess.shader)) + { + shaderAttribs |= GENERICDEF_USE_DEFORM_VERTEXES; + } + + if (glState.vertexAttribsInterpolation > 0.0f && backEnd.currentEntity && backEnd.currentEntity != &tr.worldEntity) + { + shaderAttribs |= GENERICDEF_USE_VERTEX_ANIMATION; + } + + sp = &tr.genericShader[shaderAttribs]; + } + } + else if (pStage->glslShaderGroup) + { + int index = pStage->glslShaderIndex; + + if (backEnd.currentEntity && backEnd.currentEntity != &tr.worldEntity) + { + index |= LIGHTDEF_ENTITY; + } + + if (r_lightmap->integer && index & LIGHTDEF_USE_LIGHTMAP) + { + index = LIGHTDEF_USE_LIGHTMAP; + } + + sp = &pStage->glslShaderGroup[index]; + + if (pStage->glslShaderGroup == tr.lightallShader) + { + backEnd.pc.c_lightallDraws++; + } + } + else + { + sp = GLSL_GetGenericShaderProgram(stage); + + backEnd.pc.c_genericDraws++; + } + + GLSL_BindProgram(sp); + + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_VIEWORIGIN, backEnd.viewParms.or.origin); + + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation); + + GLSL_SetUniformInt(sp, GENERIC_UNIFORM_DEFORMGEN, deformGen); + if (deformGen != DGEN_NONE) + { + GLSL_SetUniformFloat5(sp, GENERIC_UNIFORM_DEFORMPARAMS, deformParams); + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_TIME, tess.shaderTime); + } + + if ( input->fogNum ) { + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGDISTANCE, fogDistanceVector); + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGDEPTH, fogDepthVector); + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_FOGEYET, eyeT); + } + + GL_State( pStage->stateBits ); + + { + vec4_t baseColor; + vec4_t vertColor; + qboolean tint = qtrue; + int stage2; + + ComputeShaderColors(pStage, baseColor, vertColor); + + for ( stage2 = stage + 1; stage2 < MAX_SHADER_STAGES; stage2++ ) + { + shaderStage_t *pStage2 = input->xstages[stage2]; + unsigned int srcBlendBits, dstBlendBits; + + if ( !pStage2 ) + { + break; + } + + srcBlendBits = pStage2->stateBits & GLS_SRCBLEND_BITS; + dstBlendBits = pStage2->stateBits & GLS_DSTBLEND_BITS; + + if (srcBlendBits == GLS_SRCBLEND_DST_COLOR) + { + tint = qfalse; + break; + } + } + + if (!((tr.sunShadows || r_forceSun->integer) && tess.shader->sort <= SS_OPAQUE + && !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) && tess.xstages[0]->glslShaderGroup == tr.lightallShader)) + { + tint = qfalse; + } + + if (tint) + { + // use VectorScale to only scale first three values, not alpha + VectorScale(baseColor, backEnd.refdef.colorScale, baseColor); + VectorScale(vertColor, backEnd.refdef.colorScale, vertColor); + } + + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_BASECOLOR, baseColor); + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_VERTCOLOR, vertColor); + } + + if (pStage->rgbGen == CGEN_LIGHTING_DIFFUSE) + { + vec4_t vec; + + VectorScale(backEnd.currentEntity->ambientLight, 1.0f / 255.0f, vec); + GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_AMBIENTLIGHT, vec); + + VectorScale(backEnd.currentEntity->directedLight, 1.0f / 255.0f, vec); + GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_DIRECTEDLIGHT, vec); + + VectorCopy(backEnd.currentEntity->lightDir, vec); + vec[3] = 0.0f; + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_LIGHTORIGIN, vec); + + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_LIGHTRADIUS, 999999.0f); + } + + if (pStage->alphaGen == AGEN_PORTAL) + { + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_PORTALRANGE, tess.shader->portalRange); + } + + GLSL_SetUniformInt(sp, GENERIC_UNIFORM_COLORGEN, pStage->rgbGen); + GLSL_SetUniformInt(sp, GENERIC_UNIFORM_ALPHAGEN, pStage->alphaGen); + + if ( input->fogNum ) + { + vec4_t fogColorMask; + + ComputeFogColorMask(pStage, fogColorMask); + + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_FOGCOLORMASK, fogColorMask); + } + + ComputeTexMatrix( pStage, TB_DIFFUSEMAP, matrix ); + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_DIFFUSETEXMATRIX, matrix); + + GLSL_SetUniformInt(sp, GENERIC_UNIFORM_TCGEN0, pStage->bundle[0].tcGen); + if (pStage->bundle[0].tcGen == TCGEN_VECTOR) + { + vec3_t vec; + + VectorCopy(pStage->bundle[0].tcGenVectors[0], vec); + GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_TCGEN0VECTOR0, vec); + VectorCopy(pStage->bundle[0].tcGenVectors[1], vec); + GLSL_SetUniformVec3(sp, GENERIC_UNIFORM_TCGEN0VECTOR1, vec); + } + + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELMATRIX, backEnd.or.transformMatrix); + + GLSL_SetUniformVec2(sp, GENERIC_UNIFORM_MATERIALINFO, pStage->materialInfo); + + //GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_MAPLIGHTSCALE, backEnd.refdef.mapLightScale); + + // + // do multitexture + // + if ( backEnd.depthFill ) + { + if (!(pStage->stateBits & GLS_ATEST_BITS)) + GL_BindToTMU( tr.whiteImage, 0 ); + else if ( pStage->bundle[TB_COLORMAP].image[0] != 0 ) + R_BindAnimatedImageToTMU( &pStage->bundle[TB_COLORMAP], TB_COLORMAP ); + } + else if ( pStage->glslShaderGroup ) + { + int i; + + if ((r_lightmap->integer == 1 || r_lightmap->integer == 2) && pStage->bundle[TB_LIGHTMAP].image[0]) + { + for (i = 0; i < NUM_TEXTURE_BUNDLES; i++) + { + if (i == TB_LIGHTMAP) + { + R_BindAnimatedImageToTMU( &pStage->bundle[i], i); + } + else if (pStage->bundle[i].image[0]) + { + GL_BindToTMU( tr.whiteImage, i); + } + } + } + else if (r_lightmap->integer == 3 && pStage->bundle[TB_DELUXEMAP].image[0]) + { + for (i = 0; i < NUM_TEXTURE_BUNDLES; i++) + { + if (i == TB_LIGHTMAP) + { + R_BindAnimatedImageToTMU( &pStage->bundle[TB_DELUXEMAP], i); + } + else if (pStage->bundle[i].image[0]) + { + GL_BindToTMU( tr.whiteImage, i); + } + } + } + else + { + for (i = 0; i < NUM_TEXTURE_BUNDLES; i++) + { + if (pStage->bundle[i].image[0]) + { + R_BindAnimatedImageToTMU( &pStage->bundle[i], i); + } + } + } + } + else if ( pStage->bundle[1].image[0] != 0 ) + { + R_BindAnimatedImageToTMU( &pStage->bundle[0], 0 ); + + // + // lightmap/secondary pass + // + if ( r_lightmap->integer ) { + GLSL_SetUniformInt(sp, GENERIC_UNIFORM_TEXTURE1ENV, GL_REPLACE); + } else { + GLSL_SetUniformInt(sp, GENERIC_UNIFORM_TEXTURE1ENV, tess.shader->multitextureEnv); + } + + R_BindAnimatedImageToTMU( &pStage->bundle[1], 1 ); + } + else + { + // + // set state + // + if ( pStage->bundle[0].vertexLightmap && ( (r_vertexLight->integer && !r_uiFullScreen->integer) || glConfig.hardwareType == GLHW_PERMEDIA2 ) && r_lightmap->integer ) + { + GL_BindToTMU( tr.whiteImage, 0 ); + } + else + R_BindAnimatedImageToTMU( &pStage->bundle[0], 0 ); + + GLSL_SetUniformInt(sp, GENERIC_UNIFORM_TEXTURE1ENV, 0); + } + + // + // draw + // + if (input->multiDrawPrimitives) + { + R_DrawMultiElementsVBO(input->multiDrawPrimitives, (const GLvoid **)input->multiDrawFirstIndex, input->multiDrawNumIndexes); + } + else + { + R_DrawElementsVBO(input->numIndexes, input->firstIndex); + } + + // allow skipping out to show just lightmaps during development + if ( r_lightmap->integer && ( pStage->bundle[0].isLightmap || pStage->bundle[1].isLightmap || pStage->bundle[0].vertexLightmap ) ) + { + break; + } + + if (backEnd.depthFill) + break; + } +} + + +static void RB_RenderShadowmap( shaderCommands_t *input ) +{ + int deformGen; + vec5_t deformParams; + + ComputeDeformValues(&deformGen, deformParams); + + { + shaderProgram_t *sp = &tr.shadowmapShader; + + vec4_t vector; + + GLSL_BindProgram(sp); + + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELMATRIX, backEnd.or.transformMatrix); + + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation); + + GLSL_SetUniformInt(sp, GENERIC_UNIFORM_DEFORMGEN, deformGen); + if (deformGen != DGEN_NONE) + { + GLSL_SetUniformFloat5(sp, GENERIC_UNIFORM_DEFORMPARAMS, deformParams); + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_TIME, tess.shaderTime); + } + + VectorCopy(backEnd.viewParms.or.origin, vector); + vector[3] = 1.0f; + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_LIGHTORIGIN, vector); + GLSL_SetUniformFloat(sp, GENERIC_UNIFORM_LIGHTRADIUS, backEnd.viewParms.zFar); + + GL_State( 0 ); + + // + // do multitexture + // + //if ( pStage->glslShaderGroup ) + { + // + // draw + // + + if (input->multiDrawPrimitives) + { + R_DrawMultiElementsVBO(input->multiDrawPrimitives, (const GLvoid **)input->multiDrawFirstIndex, input->multiDrawNumIndexes); + } + else + { + R_DrawElementsVBO(input->numIndexes, input->firstIndex); + } + } + } +} + + + +/* +** RB_StageIteratorGeneric +*/ +void RB_StageIteratorGeneric( void ) +{ + shaderCommands_t *input; + unsigned int vertexAttribs = 0; + + input = &tess; + + if (!input->numVertexes || !input->numIndexes) + { + return; + } + + if (tess.useInternalVBO) + { + RB_DeformTessGeometry(); + } + + vertexAttribs = RB_CalcShaderVertexAttribs( input ); + + if (tess.useInternalVBO) + { + RB_UpdateVBOs(vertexAttribs); + } + else + { + backEnd.pc.c_staticVboDraws++; + } + + // + // log this call + // + if ( r_logFile->integer ) + { + // don't just call LogComment, or we will get + // a call to va() every frame! + GLimp_LogComment( va("--- RB_StageIteratorGeneric( %s ) ---\n", tess.shader->name) ); + } + + // + // set face culling appropriately + // + if ((backEnd.viewParms.flags & VPF_DEPTHSHADOW)) + { + //GL_Cull( CT_TWO_SIDED ); + + if (input->shader->cullType == CT_TWO_SIDED) + GL_Cull( CT_TWO_SIDED ); + else if (input->shader->cullType == CT_FRONT_SIDED) + GL_Cull( CT_BACK_SIDED ); + else + GL_Cull( CT_FRONT_SIDED ); + + } + else + GL_Cull( input->shader->cullType ); + + // set polygon offset if necessary + if ( input->shader->polygonOffset ) + { + qglEnable( GL_POLYGON_OFFSET_FILL ); + qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value ); + } + + // + // Set vertex attribs and pointers + // + GLSL_VertexAttribsState(vertexAttribs); + + // + // render depth if in depthfill mode + // + if (backEnd.depthFill) + { + RB_IterateStagesGeneric( input ); + + // + // reset polygon offset + // + if ( input->shader->polygonOffset ) + { + qglDisable( GL_POLYGON_OFFSET_FILL ); + } + + return; + } + + // + // render shadowmap if in shadowmap mode + // + if (backEnd.viewParms.flags & VPF_SHADOWMAP) + { + if ( input->shader->sort == SS_OPAQUE ) + { + RB_RenderShadowmap( input ); + } + // + // reset polygon offset + // + if ( input->shader->polygonOffset ) + { + qglDisable( GL_POLYGON_OFFSET_FILL ); + } + + return; + } + + // + // + // call shader function + // + RB_IterateStagesGeneric( input ); + + // + // pshadows! + // + if ( tess.pshadowBits && tess.shader->sort <= SS_OPAQUE + && !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) ) { + ProjectPshadowVBOGLSL(); + } + + + // + // now do any dynamic lighting needed + // + if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE + && !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) ) { + if (tess.shader->numUnfoggedPasses == 1 && tess.xstages[0]->glslShaderGroup == tr.lightallShader + && (tess.xstages[0]->glslShaderIndex & LIGHTDEF_LIGHTTYPE_MASK) && r_dlightMode->integer) + { + ForwardDlight(); + } + else + { + ProjectDlightTexture(); + } + } + + if ((backEnd.viewParms.flags & VPF_USESUNLIGHT) && tess.shader->sort <= SS_OPAQUE + //if ((tr.sunShadows || r_forceSunlight->value > 0.0f) && tess.shader->sort <= SS_OPAQUE + && !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) && tess.xstages[0]->glslShaderGroup == tr.lightallShader) { + ForwardSunlight(); + } + + // + // now do fog + // + if ( tess.fogNum && tess.shader->fogPass ) { + RB_FogPass(); + } + + // + // reset polygon offset + // + if ( input->shader->polygonOffset ) + { + qglDisable( GL_POLYGON_OFFSET_FILL ); + } +} + + +/* +** RB_EndSurface +*/ +void RB_EndSurface( void ) { + shaderCommands_t *input; + + input = &tess; + + if (input->numIndexes == 0 || input->numVertexes == 0) { + return; + } + + if (input->indexes[SHADER_MAX_INDEXES-1] != 0) { + ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_INDEXES hit"); + } + if (input->xyz[SHADER_MAX_VERTEXES-1][0] != 0) { + ri.Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_VERTEXES hit"); + } + + if ( tess.shader == tr.shadowShader ) { + RB_ShadowTessEnd(); + return; + } + + // for debugging of sort order issues, stop rendering after a given sort value + if ( r_debugSort->integer && r_debugSort->integer < tess.shader->sort ) { + return; + } + + // + // update performance counters + // + backEnd.pc.c_shaders++; + backEnd.pc.c_vertexes += tess.numVertexes; + backEnd.pc.c_indexes += tess.numIndexes; + backEnd.pc.c_totalIndexes += tess.numIndexes * tess.numPasses; + + // + // call off to shader specific tess end function + // + tess.currentStageIteratorFunc(); + + // + // draw debugging stuff + // + if ( r_showtris->integer ) { + DrawTris (input); + } + if ( r_shownormals->integer ) { + DrawNormals (input); + } + // clear shader so we can tell we don't have any unclosed surfaces + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.firstIndex = 0; + tess.multiDrawPrimitives = 0; + + GLimp_LogComment( "----------\n" ); +} diff --git a/src/rend2/tr_shade_calc.c b/src/rend2/tr_shade_calc.c new file mode 100644 index 00000000..9421f646 --- /dev/null +++ b/src/rend2/tr_shade_calc.c @@ -0,0 +1,1339 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_shade_calc.c + +#include "tr_local.h" +#if idppc_altivec && !defined(MACOS_X) +#include +#endif + + +#define WAVEVALUE( table, base, amplitude, phase, freq ) ((base) + table[ ri.ftol( ( ( (phase) + tess.shaderTime * (freq) ) * FUNCTABLE_SIZE ) ) & FUNCTABLE_MASK ] * (amplitude)) + +static float *TableForFunc( genFunc_t func ) +{ + switch ( func ) + { + case GF_SIN: + return tr.sinTable; + case GF_TRIANGLE: + return tr.triangleTable; + case GF_SQUARE: + return tr.squareTable; + case GF_SAWTOOTH: + return tr.sawToothTable; + case GF_INVERSE_SAWTOOTH: + return tr.inverseSawToothTable; + case GF_NONE: + default: + break; + } + + ri.Error( ERR_DROP, "TableForFunc called with invalid function '%d' in shader '%s'", func, tess.shader->name ); + return NULL; +} + +/* +** EvalWaveForm +** +** Evaluates a given waveForm_t, referencing backEnd.refdef.time directly +*/ +static float EvalWaveForm( const waveForm_t *wf ) +{ + float *table; + + table = TableForFunc( wf->func ); + + return WAVEVALUE( table, wf->base, wf->amplitude, wf->phase, wf->frequency ); +} + +static float EvalWaveFormClamped( const waveForm_t *wf ) +{ + float glow = EvalWaveForm( wf ); + + if ( glow < 0 ) + { + return 0; + } + + if ( glow > 1 ) + { + return 1; + } + + return glow; +} + +/* +** RB_CalcStretchTexCoords +*/ +void RB_CalcStretchTexCoords( const waveForm_t *wf, float *st ) +{ + float p; + texModInfo_t tmi; + + p = 1.0f / EvalWaveForm( wf ); + + tmi.matrix[0][0] = p; + tmi.matrix[1][0] = 0; + tmi.translate[0] = 0.5f - 0.5f * p; + + tmi.matrix[0][1] = 0; + tmi.matrix[1][1] = p; + tmi.translate[1] = 0.5f - 0.5f * p; + + RB_CalcTransformTexCoords( &tmi, st ); +} + +void RB_CalcStretchTexMatrix( const waveForm_t *wf, float *matrix ) +{ + float p; + texModInfo_t tmi; + + p = 1.0f / EvalWaveForm( wf ); + + tmi.matrix[0][0] = p; + tmi.matrix[1][0] = 0; + tmi.translate[0] = 0.5f - 0.5f * p; + + tmi.matrix[0][1] = 0; + tmi.matrix[1][1] = p; + tmi.translate[1] = 0.5f - 0.5f * p; + + RB_CalcTransformTexMatrix( &tmi, matrix ); +} + +/* +==================================================================== + +DEFORMATIONS + +==================================================================== +*/ + +/* +======================== +RB_CalcDeformVertexes + +======================== +*/ +void RB_CalcDeformVertexes( deformStage_t *ds ) +{ + int i; + vec3_t offset; + float scale; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + float *table; + + if ( ds->deformationWave.frequency == 0 ) + { + scale = EvalWaveForm( &ds->deformationWave ); + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + VectorScale( normal, scale, offset ); + + xyz[0] += offset[0]; + xyz[1] += offset[1]; + xyz[2] += offset[2]; + } + } + else + { + table = TableForFunc( ds->deformationWave.func ); + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + float off = ( xyz[0] + xyz[1] + xyz[2] ) * ds->deformationSpread; + + scale = WAVEVALUE( table, ds->deformationWave.base, + ds->deformationWave.amplitude, + ds->deformationWave.phase + off, + ds->deformationWave.frequency ); + + VectorScale( normal, scale, offset ); + + xyz[0] += offset[0]; + xyz[1] += offset[1]; + xyz[2] += offset[2]; + } + } +} + +/* +========================= +RB_CalcDeformNormals + +Wiggle the normals for wavy environment mapping +========================= +*/ +void RB_CalcDeformNormals( deformStage_t *ds ) { + int i; + float scale; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) { + scale = 0.98f; + scale = R_NoiseGet4f( xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + normal[ 0 ] += ds->deformationWave.amplitude * scale; + + scale = 0.98f; + scale = R_NoiseGet4f( 100 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + normal[ 1 ] += ds->deformationWave.amplitude * scale; + + scale = 0.98f; + scale = R_NoiseGet4f( 200 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + normal[ 2 ] += ds->deformationWave.amplitude * scale; + + VectorNormalizeFast( normal ); + } +} + +/* +======================== +RB_CalcBulgeVertexes + +======================== +*/ +void RB_CalcBulgeVertexes( deformStage_t *ds ) { + int i; + const float *st = ( const float * ) tess.texCoords[0]; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + float now; + + now = backEnd.refdef.time * ds->bulgeSpeed * 0.001f; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, st += 4, normal += 4 ) { + int off; + float scale; + + off = (float)( FUNCTABLE_SIZE / (M_PI*2) ) * ( st[0] * ds->bulgeWidth + now ); + + scale = tr.sinTable[ off & FUNCTABLE_MASK ] * ds->bulgeHeight; + + xyz[0] += normal[0] * scale; + xyz[1] += normal[1] * scale; + xyz[2] += normal[2] * scale; + } +} + + +/* +====================== +RB_CalcMoveVertexes + +A deformation that can move an entire surface along a wave path +====================== +*/ +void RB_CalcMoveVertexes( deformStage_t *ds ) { + int i; + float *xyz; + float *table; + float scale; + vec3_t offset; + + table = TableForFunc( ds->deformationWave.func ); + + scale = WAVEVALUE( table, ds->deformationWave.base, + ds->deformationWave.amplitude, + ds->deformationWave.phase, + ds->deformationWave.frequency ); + + VectorScale( ds->moveVector, scale, offset ); + + xyz = ( float * ) tess.xyz; + for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { + VectorAdd( xyz, offset, xyz ); + } +} + + +/* +============= +DeformText + +Change a polygon into a bunch of text polygons +============= +*/ +void DeformText( const char *text ) { + int i; + vec3_t origin, width, height; + int len; + int ch; + float color[4]; + float bottom, top; + vec3_t mid; + + height[0] = 0; + height[1] = 0; + height[2] = -1; + CrossProduct( tess.normal[0], height, width ); + + // find the midpoint of the box + VectorClear( mid ); + bottom = 999999; + top = -999999; + for ( i = 0 ; i < 4 ; i++ ) { + VectorAdd( tess.xyz[i], mid, mid ); + if ( tess.xyz[i][2] < bottom ) { + bottom = tess.xyz[i][2]; + } + if ( tess.xyz[i][2] > top ) { + top = tess.xyz[i][2]; + } + } + VectorScale( mid, 0.25f, origin ); + + // determine the individual character size + height[0] = 0; + height[1] = 0; + height[2] = ( top - bottom ) * 0.5f; + + VectorScale( width, height[2] * -0.75f, width ); + + // determine the starting position + len = strlen( text ); + VectorMA( origin, (len-1), width, origin ); + + // clear the shader indexes + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.firstIndex = 0; + + color[0] = color[1] = color[2] = color[3] = 1.0f; + + // draw each character + for ( i = 0 ; i < len ; i++ ) { + ch = text[i]; + ch &= 255; + + if ( ch != ' ' ) { + int row, col; + float frow, fcol, size; + + row = ch>>4; + col = ch&15; + + frow = row*0.0625f; + fcol = col*0.0625f; + size = 0.0625f; + + RB_AddQuadStampExt( origin, width, height, color, fcol, frow, fcol + size, frow + size ); + } + VectorMA( origin, -2, width, origin ); + } +} + +/* +================== +GlobalVectorToLocal +================== +*/ +static void GlobalVectorToLocal( const vec3_t in, vec3_t out ) { + out[0] = DotProduct( in, backEnd.or.axis[0] ); + out[1] = DotProduct( in, backEnd.or.axis[1] ); + out[2] = DotProduct( in, backEnd.or.axis[2] ); +} + +/* +===================== +AutospriteDeform + +Assuming all the triangles for this shader are independant +quads, rebuild them as forward facing sprites +===================== +*/ +static void AutospriteDeform( void ) { + int i; + int oldVerts; + float *xyz; + vec3_t mid, delta; + float radius; + vec3_t left, up; + vec3_t leftDir, upDir; + + if ( tess.numVertexes & 3 ) { + ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd vertex count\n", tess.shader->name ); + } + if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { + ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd index count\n", tess.shader->name ); + } + + oldVerts = tess.numVertexes; + tess.numVertexes = 0; + tess.numIndexes = 0; + tess.firstIndex = 0; + + if ( backEnd.currentEntity != &tr.worldEntity ) { + GlobalVectorToLocal( backEnd.viewParms.or.axis[1], leftDir ); + GlobalVectorToLocal( backEnd.viewParms.or.axis[2], upDir ); + } else { + VectorCopy( backEnd.viewParms.or.axis[1], leftDir ); + VectorCopy( backEnd.viewParms.or.axis[2], upDir ); + } + + for ( i = 0 ; i < oldVerts ; i+=4 ) { + // find the midpoint + xyz = tess.xyz[i]; + + mid[0] = 0.25f * (xyz[0] + xyz[4] + xyz[8] + xyz[12]); + mid[1] = 0.25f * (xyz[1] + xyz[5] + xyz[9] + xyz[13]); + mid[2] = 0.25f * (xyz[2] + xyz[6] + xyz[10] + xyz[14]); + + VectorSubtract( xyz, mid, delta ); + radius = VectorLength( delta ) * 0.707f; // / sqrt(2) + + VectorScale( leftDir, radius, left ); + VectorScale( upDir, radius, up ); + + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + // compensate for scale in the axes if necessary + if ( backEnd.currentEntity->e.nonNormalizedAxes ) { + float axisLength; + axisLength = VectorLength( backEnd.currentEntity->e.axis[0] ); + if ( !axisLength ) { + axisLength = 0; + } else { + axisLength = 1.0f / axisLength; + } + VectorScale(left, axisLength, left); + VectorScale(up, axisLength, up); + } + + RB_AddQuadStamp( mid, left, up, tess.vertexColors[i] ); + } +} + + +/* +===================== +Autosprite2Deform + +Autosprite2 will pivot a rectangular quad along the center of its long axis +===================== +*/ +int edgeVerts[6][2] = { + { 0, 1 }, + { 0, 2 }, + { 0, 3 }, + { 1, 2 }, + { 1, 3 }, + { 2, 3 } +}; + +static void Autosprite2Deform( void ) { + int i, j, k; + int indexes; + float *xyz; + vec3_t forward; + + if ( tess.numVertexes & 3 ) { + ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd vertex count", tess.shader->name ); + } + if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { + ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd index count", tess.shader->name ); + } + + if ( backEnd.currentEntity != &tr.worldEntity ) { + GlobalVectorToLocal( backEnd.viewParms.or.axis[0], forward ); + } else { + VectorCopy( backEnd.viewParms.or.axis[0], forward ); + } + + // this is a lot of work for two triangles... + // we could precalculate a lot of it is an issue, but it would mess up + // the shader abstraction + for ( i = 0, indexes = 0 ; i < tess.numVertexes ; i+=4, indexes+=6 ) { + float lengths[2]; + int nums[2]; + vec3_t mid[2]; + vec3_t major, minor; + float *v1, *v2; + + // find the midpoint + xyz = tess.xyz[i]; + + // identify the two shortest edges + nums[0] = nums[1] = 0; + lengths[0] = lengths[1] = 999999; + + for ( j = 0 ; j < 6 ; j++ ) { + float l; + vec3_t temp; + + v1 = xyz + 4 * edgeVerts[j][0]; + v2 = xyz + 4 * edgeVerts[j][1]; + + VectorSubtract( v1, v2, temp ); + + l = DotProduct( temp, temp ); + if ( l < lengths[0] ) { + nums[1] = nums[0]; + lengths[1] = lengths[0]; + nums[0] = j; + lengths[0] = l; + } else if ( l < lengths[1] ) { + nums[1] = j; + lengths[1] = l; + } + } + + for ( j = 0 ; j < 2 ; j++ ) { + v1 = xyz + 4 * edgeVerts[nums[j]][0]; + v2 = xyz + 4 * edgeVerts[nums[j]][1]; + + mid[j][0] = 0.5f * (v1[0] + v2[0]); + mid[j][1] = 0.5f * (v1[1] + v2[1]); + mid[j][2] = 0.5f * (v1[2] + v2[2]); + } + + // find the vector of the major axis + VectorSubtract( mid[1], mid[0], major ); + + // cross this with the view direction to get minor axis + CrossProduct( major, forward, minor ); + VectorNormalize( minor ); + + // re-project the points + for ( j = 0 ; j < 2 ; j++ ) { + float l; + + v1 = xyz + 4 * edgeVerts[nums[j]][0]; + v2 = xyz + 4 * edgeVerts[nums[j]][1]; + + l = 0.5 * sqrt( lengths[j] ); + + // we need to see which direction this edge + // is used to determine direction of projection + for ( k = 0 ; k < 5 ; k++ ) { + if ( tess.indexes[ indexes + k ] == i + edgeVerts[nums[j]][0] + && tess.indexes[ indexes + k + 1 ] == i + edgeVerts[nums[j]][1] ) { + break; + } + } + + if ( k == 5 ) { + VectorMA( mid[j], l, minor, v1 ); + VectorMA( mid[j], -l, minor, v2 ); + } else { + VectorMA( mid[j], -l, minor, v1 ); + VectorMA( mid[j], l, minor, v2 ); + } + } + } +} + + +/* +===================== +RB_DeformTessGeometry + +===================== +*/ +void RB_DeformTessGeometry( void ) { + int i; + deformStage_t *ds; + + if(!ShaderRequiresCPUDeforms(tess.shader)) + { + // we don't need the following CPU deforms + return; + } + + for ( i = 0 ; i < tess.shader->numDeforms ; i++ ) { + ds = &tess.shader->deforms[ i ]; + + switch ( ds->deformation ) { + case DEFORM_NONE: + break; + case DEFORM_NORMALS: + RB_CalcDeformNormals( ds ); + break; + case DEFORM_WAVE: + RB_CalcDeformVertexes( ds ); + break; + case DEFORM_BULGE: + RB_CalcBulgeVertexes( ds ); + break; + case DEFORM_MOVE: + RB_CalcMoveVertexes( ds ); + break; + case DEFORM_PROJECTION_SHADOW: + RB_ProjectionShadowDeform(); + break; + case DEFORM_AUTOSPRITE: + AutospriteDeform(); + break; + case DEFORM_AUTOSPRITE2: + Autosprite2Deform(); + break; + case DEFORM_TEXT0: + case DEFORM_TEXT1: + case DEFORM_TEXT2: + case DEFORM_TEXT3: + case DEFORM_TEXT4: + case DEFORM_TEXT5: + case DEFORM_TEXT6: + case DEFORM_TEXT7: + DeformText( backEnd.refdef.text[ds->deformation - DEFORM_TEXT0] ); + break; + } + } +} + +/* +==================================================================== + +COLORS + +==================================================================== +*/ + + +/* +** RB_CalcColorFromEntity +*/ +void RB_CalcColorFromEntity( unsigned char *dstColors ) +{ + int i; + int *pColors = ( int * ) dstColors; + int c; + + if ( !backEnd.currentEntity ) + return; + + c = * ( int * ) backEnd.currentEntity->e.shaderRGBA; + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = c; + } +} + +/* +** RB_CalcColorFromOneMinusEntity +*/ +void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors ) +{ + int i; + int *pColors = ( int * ) dstColors; + unsigned char invModulate[4]; + int c; + + if ( !backEnd.currentEntity ) + return; + + invModulate[0] = 255 - backEnd.currentEntity->e.shaderRGBA[0]; + invModulate[1] = 255 - backEnd.currentEntity->e.shaderRGBA[1]; + invModulate[2] = 255 - backEnd.currentEntity->e.shaderRGBA[2]; + invModulate[3] = 255 - backEnd.currentEntity->e.shaderRGBA[3]; // this trashes alpha, but the AGEN block fixes it + + c = * ( int * ) invModulate; + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = c; + } +} + +/* +** RB_CalcAlphaFromEntity +*/ +void RB_CalcAlphaFromEntity( unsigned char *dstColors ) +{ + int i; + + if ( !backEnd.currentEntity ) + return; + + dstColors += 3; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + *dstColors = backEnd.currentEntity->e.shaderRGBA[3]; + } +} + +/* +** RB_CalcAlphaFromOneMinusEntity +*/ +void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors ) +{ + int i; + + if ( !backEnd.currentEntity ) + return; + + dstColors += 3; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + *dstColors = 0xff - backEnd.currentEntity->e.shaderRGBA[3]; + } +} + +/* +** RB_CalcWaveColorSingle +*/ +float RB_CalcWaveColorSingle( const waveForm_t *wf ) +{ + float glow; + + if ( wf->func == GF_NOISE ) { + glow = wf->base + R_NoiseGet4f( 0, 0, 0, ( tess.shaderTime + wf->phase ) * wf->frequency ) * wf->amplitude; + } else { + glow = EvalWaveForm( wf ) * tr.identityLight; + } + + if ( glow < 0 ) { + glow = 0; + } + else if ( glow > 1 ) { + glow = 1; + } + + return glow; +} + +/* +** RB_CalcWaveColor +*/ +void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors ) +{ + int i; + int v; + float glow; + int *colors = ( int * ) dstColors; + byte color[4]; + + glow = RB_CalcWaveColorSingle( wf ); + + v = ri.ftol(255 * glow); + color[0] = color[1] = color[2] = v; + color[3] = 255; + v = *(int *)color; + + for ( i = 0; i < tess.numVertexes; i++, colors++ ) { + *colors = v; + } +} + +/* +** RB_CalcWaveAlphaSingle +*/ +float RB_CalcWaveAlphaSingle( const waveForm_t *wf ) +{ + return EvalWaveFormClamped( wf ); +} + +/* +** RB_CalcWaveAlpha +*/ +void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors ) +{ + int i; + int v; + float glow; + + glow = EvalWaveFormClamped( wf ); + + v = 255 * glow; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + dstColors[3] = v; + } +} + +/* +** RB_CalcModulateColorsByFog +*/ +void RB_CalcModulateColorsByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[0] *= f; + colors[1] *= f; + colors[2] *= f; + } +} + +/* +** RB_CalcModulateAlphasByFog +*/ +void RB_CalcModulateAlphasByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[3] *= f; + } +} + +/* +** RB_CalcModulateRGBAsByFog +*/ +void RB_CalcModulateRGBAsByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[0] *= f; + colors[1] *= f; + colors[2] *= f; + colors[3] *= f; + } +} + + +/* +==================================================================== + +TEX COORDS + +==================================================================== +*/ + +/* +======================== +RB_CalcFogTexCoords + +To do the clipped fog plane really correctly, we should use +projected textures, but I don't trust the drivers and it +doesn't fit our shader data. +======================== +*/ +void RB_CalcFogTexCoords( float *st ) { + int i; + float *v; + float s, t; + float eyeT; + qboolean eyeOutside; + fog_t *fog; + vec3_t local; + vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0}; + + fog = tr.world->fogs + tess.fogNum; + + // all fogging distance is based on world Z units + VectorSubtract( backEnd.or.origin, backEnd.viewParms.or.origin, local ); + fogDistanceVector[0] = -backEnd.or.modelMatrix[2]; + fogDistanceVector[1] = -backEnd.or.modelMatrix[6]; + fogDistanceVector[2] = -backEnd.or.modelMatrix[10]; + fogDistanceVector[3] = DotProduct( local, backEnd.viewParms.or.axis[0] ); + + // scale the fog vectors based on the fog's thickness + fogDistanceVector[0] *= fog->tcScale; + fogDistanceVector[1] *= fog->tcScale; + fogDistanceVector[2] *= fog->tcScale; + fogDistanceVector[3] *= fog->tcScale; + + // rotate the gradient vector for this orientation + if ( fog->hasSurface ) { + fogDepthVector[0] = fog->surface[0] * backEnd.or.axis[0][0] + + fog->surface[1] * backEnd.or.axis[0][1] + fog->surface[2] * backEnd.or.axis[0][2]; + fogDepthVector[1] = fog->surface[0] * backEnd.or.axis[1][0] + + fog->surface[1] * backEnd.or.axis[1][1] + fog->surface[2] * backEnd.or.axis[1][2]; + fogDepthVector[2] = fog->surface[0] * backEnd.or.axis[2][0] + + fog->surface[1] * backEnd.or.axis[2][1] + fog->surface[2] * backEnd.or.axis[2][2]; + fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.or.origin, fog->surface ); + + eyeT = DotProduct( backEnd.or.viewOrigin, fogDepthVector ) + fogDepthVector[3]; + } else { + eyeT = 1; // non-surface fog always has eye inside + } + + // see if the viewpoint is outside + // this is needed for clipping distance even for constant fog + + if ( eyeT < 0 ) { + eyeOutside = qtrue; + } else { + eyeOutside = qfalse; + } + + fogDistanceVector[3] += 1.0/512; + + // calculate density for each point + for (i = 0, v = tess.xyz[0] ; i < tess.numVertexes ; i++, v += 4) { + // calculate the length in fog + s = DotProduct( v, fogDistanceVector ) + fogDistanceVector[3]; + t = DotProduct( v, fogDepthVector ) + fogDepthVector[3]; + + // partially clipped fogs use the T axis + if ( eyeOutside ) { + if ( t < 1.0 ) { + t = 1.0/32; // point is outside, so no fogging + } else { + t = 1.0/32 + 30.0/32 * t / ( t - eyeT ); // cut the distance at the fog plane + } + } else { + if ( t < 0 ) { + t = 1.0/32; // point is outside, so no fogging + } else { + t = 31.0/32; + } + } + + st[0] = s; + st[1] = t; + st += 2; + } +} + + + +/* +** RB_CalcEnvironmentTexCoords +*/ +void RB_CalcEnvironmentTexCoords( float *st ) +{ + int i; + float *v, *normal; + vec3_t viewer, reflected; + float d; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + for (i = 0 ; i < tess.numVertexes ; i++, v += 4, normal += 4, st += 2 ) + { + VectorSubtract (backEnd.or.viewOrigin, v, viewer); + VectorNormalizeFast (viewer); + + d = DotProduct (normal, viewer); + + reflected[0] = normal[0]*2*d - viewer[0]; + reflected[1] = normal[1]*2*d - viewer[1]; + reflected[2] = normal[2]*2*d - viewer[2]; + + st[0] = 0.5 + reflected[1] * 0.5; + st[1] = 0.5 - reflected[2] * 0.5; + } +} + +/* +** RB_CalcTurbulentTexCoords +*/ +void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *st ) +{ + int i; + float now; + + now = ( wf->phase + tess.shaderTime * wf->frequency ); + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + float s = st[0]; + float t = st[1]; + + st[0] = s + tr.sinTable[ ( ( int ) ( ( ( tess.xyz[i][0] + tess.xyz[i][2] )* 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; + st[1] = t + tr.sinTable[ ( ( int ) ( ( tess.xyz[i][1] * 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; + } +} + +void RB_CalcTurbulentTexMatrix( const waveForm_t *wf, matrix_t matrix ) +{ + float now; + + now = ( wf->phase + tess.shaderTime * wf->frequency ); + + // bit of a hack here, hide amplitude and now in the matrix + // the vertex program will extract them and perform a turbulent pass last if it's nonzero + + matrix[ 0] = 1.0f; matrix[ 4] = 0.0f; matrix[ 8] = 0.0f; matrix[12] = wf->amplitude; + matrix[ 1] = 0.0f; matrix[ 5] = 1.0f; matrix[ 9] = 0.0f; matrix[13] = now; + matrix[ 2] = 0.0f; matrix[ 6] = 0.0f; matrix[10] = 1.0f; matrix[14] = 0.0f; + matrix[ 3] = 0.0f; matrix[ 7] = 0.0f; matrix[11] = 0.0f; matrix[15] = 1.0f; +} + +/* +** RB_CalcScaleTexCoords +*/ +void RB_CalcScaleTexCoords( const float scale[2], float *st ) +{ + int i; + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + st[0] *= scale[0]; + st[1] *= scale[1]; + } +} + +void RB_CalcScaleTexMatrix( const float scale[2], float *matrix ) +{ + matrix[ 0] = scale[0]; matrix[ 4] = 0.0f; matrix[ 8] = 0.0f; matrix[12] = 0.0f; + matrix[ 1] = 0.0f; matrix[ 5] = scale[1]; matrix[ 9] = 0.0f; matrix[13] = 0.0f; + matrix[ 2] = 0.0f; matrix[ 6] = 0.0f; matrix[10] = 1.0f; matrix[14] = 0.0f; + matrix[ 3] = 0.0f; matrix[ 7] = 0.0f; matrix[11] = 0.0f; matrix[15] = 1.0f; +} + +/* +** RB_CalcScrollTexCoords +*/ +void RB_CalcScrollTexCoords( const float scrollSpeed[2], float *st ) +{ + int i; + float timeScale = tess.shaderTime; + float adjustedScrollS, adjustedScrollT; + + adjustedScrollS = scrollSpeed[0] * timeScale; + adjustedScrollT = scrollSpeed[1] * timeScale; + + // clamp so coordinates don't continuously get larger, causing problems + // with hardware limits + adjustedScrollS = adjustedScrollS - floor( adjustedScrollS ); + adjustedScrollT = adjustedScrollT - floor( adjustedScrollT ); + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + st[0] += adjustedScrollS; + st[1] += adjustedScrollT; + } +} + +void RB_CalcScrollTexMatrix( const float scrollSpeed[2], float *matrix ) +{ + float timeScale = tess.shaderTime; + float adjustedScrollS, adjustedScrollT; + + adjustedScrollS = scrollSpeed[0] * timeScale; + adjustedScrollT = scrollSpeed[1] * timeScale; + + // clamp so coordinates don't continuously get larger, causing problems + // with hardware limits + adjustedScrollS = adjustedScrollS - floor( adjustedScrollS ); + adjustedScrollT = adjustedScrollT - floor( adjustedScrollT ); + + + matrix[ 0] = 1.0f; matrix[ 4] = 0.0f; matrix[ 8] = adjustedScrollS; matrix[12] = 0.0f; + matrix[ 1] = 0.0f; matrix[ 5] = 1.0f; matrix[ 9] = adjustedScrollT; matrix[13] = 0.0f; + matrix[ 2] = 0.0f; matrix[ 6] = 0.0f; matrix[10] = 1.0f; matrix[14] = 0.0f; + matrix[ 3] = 0.0f; matrix[ 7] = 0.0f; matrix[11] = 0.0f; matrix[15] = 1.0f; +} + +/* +** RB_CalcTransformTexCoords +*/ +void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *st ) +{ + int i; + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + float s = st[0]; + float t = st[1]; + + st[0] = s * tmi->matrix[0][0] + t * tmi->matrix[1][0] + tmi->translate[0]; + st[1] = s * tmi->matrix[0][1] + t * tmi->matrix[1][1] + tmi->translate[1]; + } +} + +void RB_CalcTransformTexMatrix( const texModInfo_t *tmi, float *matrix ) +{ + matrix[ 0] = tmi->matrix[0][0]; matrix[ 4] = tmi->matrix[1][0]; matrix[ 8] = tmi->translate[0]; matrix[12] = 0.0f; + matrix[ 1] = tmi->matrix[0][1]; matrix[ 5] = tmi->matrix[1][1]; matrix[ 9] = tmi->translate[1]; matrix[13] = 0.0f; + matrix[ 2] = 0.0f; matrix[ 6] = 0.0f; matrix[10] = 1.0f; matrix[14] = 0.0f; + matrix[ 3] = 0.0f; matrix[ 7] = 0.0f; matrix[11] = 0.0f; matrix[15] = 1.0f; +} + +/* +** RB_CalcRotateTexCoords +*/ +void RB_CalcRotateTexCoords( float degsPerSecond, float *st ) +{ + float timeScale = tess.shaderTime; + float degs; + int index; + float sinValue, cosValue; + texModInfo_t tmi; + + degs = -degsPerSecond * timeScale; + index = degs * ( FUNCTABLE_SIZE / 360.0f ); + + sinValue = tr.sinTable[ index & FUNCTABLE_MASK ]; + cosValue = tr.sinTable[ ( index + FUNCTABLE_SIZE / 4 ) & FUNCTABLE_MASK ]; + + tmi.matrix[0][0] = cosValue; + tmi.matrix[1][0] = -sinValue; + tmi.translate[0] = 0.5 - 0.5 * cosValue + 0.5 * sinValue; + + tmi.matrix[0][1] = sinValue; + tmi.matrix[1][1] = cosValue; + tmi.translate[1] = 0.5 - 0.5 * sinValue - 0.5 * cosValue; + + RB_CalcTransformTexCoords( &tmi, st ); +} + +void RB_CalcRotateTexMatrix( float degsPerSecond, float *matrix ) +{ + float timeScale = tess.shaderTime; + float degs; + int index; + float sinValue, cosValue; + texModInfo_t tmi; + + degs = -degsPerSecond * timeScale; + index = degs * ( FUNCTABLE_SIZE / 360.0f ); + + sinValue = tr.sinTable[ index & FUNCTABLE_MASK ]; + cosValue = tr.sinTable[ ( index + FUNCTABLE_SIZE / 4 ) & FUNCTABLE_MASK ]; + + tmi.matrix[0][0] = cosValue; + tmi.matrix[1][0] = -sinValue; + tmi.translate[0] = 0.5 - 0.5 * cosValue + 0.5 * sinValue; + + tmi.matrix[0][1] = sinValue; + tmi.matrix[1][1] = cosValue; + tmi.translate[1] = 0.5 - 0.5 * sinValue - 0.5 * cosValue; + + RB_CalcTransformTexMatrix( &tmi, matrix ); +} +/* +** RB_CalcSpecularAlpha +** +** Calculates specular coefficient and places it in the alpha channel +*/ +vec3_t lightOrigin = { -960, 1980, 96 }; // FIXME: track dynamically + +void RB_CalcSpecularAlpha( unsigned char *alphas ) { + int i; + float *v, *normal; + vec3_t viewer, reflected; + float l, d; + int b; + vec3_t lightDir; + int numVertexes; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + alphas += 3; + + numVertexes = tess.numVertexes; + for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4, alphas += 4) { + float ilength; + + VectorSubtract( lightOrigin, v, lightDir ); +// ilength = Q_rsqrt( DotProduct( lightDir, lightDir ) ); + VectorNormalizeFast( lightDir ); + + // calculate the specular color + d = DotProduct (normal, lightDir); +// d *= ilength; + + // we don't optimize for the d < 0 case since this tends to + // cause visual artifacts such as faceted "snapping" + reflected[0] = normal[0]*2*d - lightDir[0]; + reflected[1] = normal[1]*2*d - lightDir[1]; + reflected[2] = normal[2]*2*d - lightDir[2]; + + VectorSubtract (backEnd.or.viewOrigin, v, viewer); + ilength = Q_rsqrt( DotProduct( viewer, viewer ) ); + l = DotProduct (reflected, viewer); + l *= ilength; + + if (l < 0) { + b = 0; + } else { + l = l*l; + l = l*l; + b = l * 255; + if (b > 255) { + b = 255; + } + } + + *alphas = b; + } +} + +/* +** RB_CalcDiffuseColor +** +** The basic vertex lighting calc +*/ +#if idppc_altivec +static void RB_CalcDiffuseColor_altivec( unsigned char *colors ) +{ + int i; + float *v, *normal; + trRefEntity_t *ent; + int ambientLightInt; + vec3_t lightDir; + int numVertexes; + vector unsigned char vSel = VECCONST_UINT8(0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff); + vector float ambientLightVec; + vector float directedLightVec; + vector float lightDirVec; + vector float normalVec0, normalVec1; + vector float incomingVec0, incomingVec1, incomingVec2; + vector float zero, jVec; + vector signed int jVecInt; + vector signed short jVecShort; + vector unsigned char jVecChar, normalPerm; + ent = backEnd.currentEntity; + ambientLightInt = ent->ambientLightInt; + // A lot of this could be simplified if we made sure + // entities light info was 16-byte aligned. + jVecChar = vec_lvsl(0, ent->ambientLight); + ambientLightVec = vec_ld(0, (vector float *)ent->ambientLight); + jVec = vec_ld(11, (vector float *)ent->ambientLight); + ambientLightVec = vec_perm(ambientLightVec,jVec,jVecChar); + + jVecChar = vec_lvsl(0, ent->directedLight); + directedLightVec = vec_ld(0,(vector float *)ent->directedLight); + jVec = vec_ld(11,(vector float *)ent->directedLight); + directedLightVec = vec_perm(directedLightVec,jVec,jVecChar); + + jVecChar = vec_lvsl(0, ent->lightDir); + lightDirVec = vec_ld(0,(vector float *)ent->lightDir); + jVec = vec_ld(11,(vector float *)ent->lightDir); + lightDirVec = vec_perm(lightDirVec,jVec,jVecChar); + + zero = (vector float)vec_splat_s8(0); + VectorCopy( ent->lightDir, lightDir ); + + v = tess.xyz[0]; + normal = tess.normal[0]; + + normalPerm = vec_lvsl(0,normal); + numVertexes = tess.numVertexes; + for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4) { + normalVec0 = vec_ld(0,(vector float *)normal); + normalVec1 = vec_ld(11,(vector float *)normal); + normalVec0 = vec_perm(normalVec0,normalVec1,normalPerm); + incomingVec0 = vec_madd(normalVec0, lightDirVec, zero); + incomingVec1 = vec_sld(incomingVec0,incomingVec0,4); + incomingVec2 = vec_add(incomingVec0,incomingVec1); + incomingVec1 = vec_sld(incomingVec1,incomingVec1,4); + incomingVec2 = vec_add(incomingVec2,incomingVec1); + incomingVec0 = vec_splat(incomingVec2,0); + incomingVec0 = vec_max(incomingVec0,zero); + normalPerm = vec_lvsl(12,normal); + jVec = vec_madd(incomingVec0, directedLightVec, ambientLightVec); + jVecInt = vec_cts(jVec,0); // RGBx + jVecShort = vec_pack(jVecInt,jVecInt); // RGBxRGBx + jVecChar = vec_packsu(jVecShort,jVecShort); // RGBxRGBxRGBxRGBx + jVecChar = vec_sel(jVecChar,vSel,vSel); // RGBARGBARGBARGBA replace alpha with 255 + vec_ste((vector unsigned int)jVecChar,0,(unsigned int *)&colors[i*4]); // store color + } +} +#endif + +static void RB_CalcDiffuseColor_scalar( unsigned char *colors ) +{ + int i, j; + float *v, *normal; + float incoming; + trRefEntity_t *ent; + int ambientLightInt; + vec3_t ambientLight; + vec3_t lightDir; + vec3_t directedLight; + int numVertexes; + ent = backEnd.currentEntity; + ambientLightInt = ent->ambientLightInt; + VectorCopy( ent->ambientLight, ambientLight ); + VectorCopy( ent->directedLight, directedLight ); + VectorCopy( ent->lightDir, lightDir ); + + v = tess.xyz[0]; + normal = tess.normal[0]; + + numVertexes = tess.numVertexes; + for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4) { + incoming = DotProduct (normal, lightDir); + if ( incoming <= 0 ) { + *(int *)&colors[i*4] = ambientLightInt; + continue; + } + j = ri.ftol(ambientLight[0] + incoming * directedLight[0]); + if ( j > 255 ) { + j = 255; + } + colors[i*4+0] = j; + + j = ri.ftol(ambientLight[1] + incoming * directedLight[1]); + if ( j > 255 ) { + j = 255; + } + colors[i*4+1] = j; + + j = ri.ftol(ambientLight[2] + incoming * directedLight[2]); + if ( j > 255 ) { + j = 255; + } + colors[i*4+2] = j; + + colors[i*4+3] = 255; + } +} + +void RB_CalcDiffuseColor( unsigned char *colors ) +{ +#if idppc_altivec + if (com_altivec->integer) { + // must be in a seperate function or G3 systems will crash. + RB_CalcDiffuseColor_altivec( colors ); + return; + } +#endif + RB_CalcDiffuseColor_scalar( colors ); +} + + + + + diff --git a/src/rend2/tr_shader.c b/src/rend2/tr_shader.c new file mode 100644 index 00000000..59cba8fa --- /dev/null +++ b/src/rend2/tr_shader.c @@ -0,0 +1,3728 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "tr_local.h" + +// tr_shader.c -- this file deals with the parsing and definition of shaders + +static char *s_shaderText; + +// the shader is parsed into these global variables, then copied into +// dynamically allocated memory if it is valid. +static shaderStage_t stages[MAX_SHADER_STAGES]; +static shader_t shader; +static texModInfo_t texMods[MAX_SHADER_STAGES][TR_MAX_TEXMODS]; + +#define FILE_HASH_SIZE 1024 +static shader_t* hashTable[FILE_HASH_SIZE]; + +#define MAX_SHADERTEXT_HASH 2048 +static char **shaderTextHashTable[MAX_SHADERTEXT_HASH]; + +/* +================ +return a hash value for the filename +================ +*/ +#ifdef __GNUCC__ + #warning TODO: check if long is ok here +#endif +static long generateHashValue( const char *fname, const int size ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower(fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + if (letter == PATH_SEP) letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)); + hash &= (size-1); + return hash; +} + +void R_RemapShader(const char *shaderName, const char *newShaderName, const char *timeOffset) { + char strippedName[MAX_QPATH]; + int hash; + shader_t *sh, *sh2; + qhandle_t h; + + sh = R_FindShaderByName( shaderName ); + if (sh == NULL || sh == tr.defaultShader) { + h = RE_RegisterShaderLightMap(shaderName, 0); + sh = R_GetShaderByHandle(h); + } + if (sh == NULL || sh == tr.defaultShader) { + ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: shader %s not found\n", shaderName ); + return; + } + + sh2 = R_FindShaderByName( newShaderName ); + if (sh2 == NULL || sh2 == tr.defaultShader) { + h = RE_RegisterShaderLightMap(newShaderName, 0); + sh2 = R_GetShaderByHandle(h); + } + + if (sh2 == NULL || sh2 == tr.defaultShader) { + ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: new shader %s not found\n", newShaderName ); + return; + } + + // remap all the shaders with the given name + // even tho they might have different lightmaps + COM_StripExtension(shaderName, strippedName, sizeof(strippedName)); + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + for (sh = hashTable[hash]; sh; sh = sh->next) { + if (Q_stricmp(sh->name, strippedName) == 0) { + if (sh != sh2) { + sh->remappedShader = sh2; + } else { + sh->remappedShader = NULL; + } + } + } + if (timeOffset) { + sh2->timeOffset = atof(timeOffset); + } +} + +/* +=============== +ParseVector +=============== +*/ +static qboolean ParseVector( char **text, int count, float *v ) { + char *token; + int i; + + // FIXME: spaces are currently required after parens, should change parseext... + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, "(" ) ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name ); + return qfalse; + } + + for ( i = 0 ; i < count ; i++ ) { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + ri.Printf( PRINT_WARNING, "WARNING: missing vector element in shader '%s'\n", shader.name ); + return qfalse; + } + v[i] = atof( token ); + } + + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, ")" ) ) { + ri.Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name ); + return qfalse; + } + + return qtrue; +} + + +/* +=============== +NameToAFunc +=============== +*/ +static unsigned NameToAFunc( const char *funcname ) +{ + if ( !Q_stricmp( funcname, "GT0" ) ) + { + return GLS_ATEST_GT_0; + } + else if ( !Q_stricmp( funcname, "LT128" ) ) + { + return GLS_ATEST_LT_80; + } + else if ( !Q_stricmp( funcname, "GE128" ) ) + { + return GLS_ATEST_GE_80; + } + + ri.Printf( PRINT_WARNING, "WARNING: invalid alphaFunc name '%s' in shader '%s'\n", funcname, shader.name ); + return 0; +} + + +/* +=============== +NameToSrcBlendMode +=============== +*/ +static int NameToSrcBlendMode( const char *name ) +{ + if ( !Q_stricmp( name, "GL_ONE" ) ) + { + return GLS_SRCBLEND_ONE; + } + else if ( !Q_stricmp( name, "GL_ZERO" ) ) + { + return GLS_SRCBLEND_ZERO; + } + else if ( !Q_stricmp( name, "GL_DST_COLOR" ) ) + { + return GLS_SRCBLEND_DST_COLOR; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_COLOR" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_DST_COLOR; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) ) + { + return GLS_SRCBLEND_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) ) + { + return GLS_SRCBLEND_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA_SATURATE" ) ) + { + return GLS_SRCBLEND_ALPHA_SATURATE; + } + + ri.Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name ); + return GLS_SRCBLEND_ONE; +} + +/* +=============== +NameToDstBlendMode +=============== +*/ +static int NameToDstBlendMode( const char *name ) +{ + if ( !Q_stricmp( name, "GL_ONE" ) ) + { + return GLS_DSTBLEND_ONE; + } + else if ( !Q_stricmp( name, "GL_ZERO" ) ) + { + return GLS_DSTBLEND_ZERO; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) ) + { + return GLS_DSTBLEND_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) ) + { + return GLS_DSTBLEND_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_SRC_COLOR" ) ) + { + return GLS_DSTBLEND_SRC_COLOR; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_COLOR" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_SRC_COLOR; + } + + ri.Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name ); + return GLS_DSTBLEND_ONE; +} + +/* +=============== +NameToGenFunc +=============== +*/ +static genFunc_t NameToGenFunc( const char *funcname ) +{ + if ( !Q_stricmp( funcname, "sin" ) ) + { + return GF_SIN; + } + else if ( !Q_stricmp( funcname, "square" ) ) + { + return GF_SQUARE; + } + else if ( !Q_stricmp( funcname, "triangle" ) ) + { + return GF_TRIANGLE; + } + else if ( !Q_stricmp( funcname, "sawtooth" ) ) + { + return GF_SAWTOOTH; + } + else if ( !Q_stricmp( funcname, "inversesawtooth" ) ) + { + return GF_INVERSE_SAWTOOTH; + } + else if ( !Q_stricmp( funcname, "noise" ) ) + { + return GF_NOISE; + } + + ri.Printf( PRINT_WARNING, "WARNING: invalid genfunc name '%s' in shader '%s'\n", funcname, shader.name ); + return GF_SIN; +} + + +/* +=================== +ParseWaveForm +=================== +*/ +static void ParseWaveForm( char **text, waveForm_t *wave ) +{ + char *token; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->func = NameToGenFunc( token ); + + // BASE, AMP, PHASE, FREQ + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->base = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->phase = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->frequency = atof( token ); +} + + +/* +=================== +ParseTexMod +=================== +*/ +static void ParseTexMod( char *_text, shaderStage_t *stage ) +{ + const char *token; + char **text = &_text; + texModInfo_t *tmi; + + if ( stage->bundle[0].numTexMods == TR_MAX_TEXMODS ) { + ri.Error( ERR_DROP, "ERROR: too many tcMod stages in shader '%s'", shader.name ); + return; + } + + tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods]; + stage->bundle[0].numTexMods++; + + token = COM_ParseExt( text, qfalse ); + + // + // turb + // + if ( !Q_stricmp( token, "turb" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.base = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.amplitude = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.phase = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.frequency = atof( token ); + + tmi->type = TMOD_TURBULENT; + } + // + // scale + // + else if ( !Q_stricmp( token, "scale" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name ); + return; + } + tmi->scale[0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name ); + return; + } + tmi->scale[1] = atof( token ); + tmi->type = TMOD_SCALE; + } + // + // scroll + // + else if ( !Q_stricmp( token, "scroll" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name ); + return; + } + tmi->scroll[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name ); + return; + } + tmi->scroll[1] = atof( token ); + tmi->type = TMOD_SCROLL; + } + // + // stretch + // + else if ( !Q_stricmp( token, "stretch" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.func = NameToGenFunc( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.base = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.phase = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.frequency = atof( token ); + + tmi->type = TMOD_STRETCH; + } + // + // transform + // + else if ( !Q_stricmp( token, "transform" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[0][0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[0][1] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[1][0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[1][1] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[1] = atof( token ); + + tmi->type = TMOD_TRANSFORM; + } + // + // rotate + // + else if ( !Q_stricmp( token, "rotate" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing tcMod rotate parms in shader '%s'\n", shader.name ); + return; + } + tmi->rotateSpeed = atof( token ); + tmi->type = TMOD_ROTATE; + } + // + // entityTranslate + // + else if ( !Q_stricmp( token, "entityTranslate" ) ) + { + tmi->type = TMOD_ENTITY_TRANSLATE; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown tcMod '%s' in shader '%s'\n", token, shader.name ); + } +} + + +/* +=================== +ParseStage +=================== +*/ +static qboolean ParseStage( shaderStage_t *stage, char **text ) +{ + char *token; + int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0; + qboolean depthMaskExplicit = qfalse; + + stage->active = qtrue; + + while ( 1 ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: no matching '}' found\n" ); + return qfalse; + } + + if ( token[0] == '}' ) + { + break; + } + // + // map + // + else if ( !Q_stricmp( token, "map" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'map' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + if ( !Q_stricmp( token, "$whiteimage" ) ) + { + stage->bundle[0].image[0] = tr.whiteImage; + continue; + } + else if ( !Q_stricmp( token, "$lightmap" ) ) + { + stage->bundle[0].isLightmap = qtrue; + if ( shader.lightmapIndex < 0 ) { + stage->bundle[0].image[0] = tr.whiteImage; + } else { + stage->bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex]; + } + continue; + } + else if ( !Q_stricmp( token, "$deluxemap" ) ) + { + if (!tr.worldDeluxeMapping) + { + ri.Printf( PRINT_WARNING, "WARNING: shader '%s' wants a deluxe map in a map compiled without them\n", shader.name ); + return qfalse; + } + + stage->bundle[0].isLightmap = qtrue; + if ( shader.lightmapIndex < 0 ) { + stage->bundle[0].image[0] = tr.whiteImage; + } else { + stage->bundle[0].image[0] = tr.deluxemaps[shader.lightmapIndex]; + } + continue; + } + else + { + imgType_t type = IMGTYPE_COLORALPHA; + imgFlags_t flags = IMGFLAG_NONE; + + if (!shader.noMipMaps) + flags |= IMGFLAG_MIPMAP; + + if (!shader.noPicMip) + flags |= IMGFLAG_PICMIP; + + if (stage->type == ST_NORMALMAP || stage->type == ST_NORMALPARALLAXMAP) + { + type = IMGTYPE_NORMAL; + flags |= IMGFLAG_NOLIGHTSCALE; + + if (stage->type == ST_NORMALPARALLAXMAP) + type = IMGTYPE_NORMALHEIGHT; + } + else + { + if (r_genNormalMaps->integer) + flags |= IMGFLAG_GENNORMALMAP; + + if (r_srgb->integer) + flags |= IMGFLAG_SRGB; + } + + stage->bundle[0].image[0] = R_FindImageFile( token, type, flags ); + + if ( !stage->bundle[0].image[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + } + } + // + // clampmap + // + else if ( !Q_stricmp( token, "clampmap" ) ) + { + imgType_t type = IMGTYPE_COLORALPHA; + imgFlags_t flags = IMGFLAG_CLAMPTOEDGE | IMGFLAG_SRGB; + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'clampmap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + if (!shader.noMipMaps) + flags |= IMGFLAG_MIPMAP; + + if (!shader.noPicMip) + flags |= IMGFLAG_PICMIP; + + if (stage->type == ST_NORMALMAP || stage->type == ST_NORMALPARALLAXMAP) + { + type = IMGTYPE_NORMAL; + flags |= IMGFLAG_NOLIGHTSCALE; + + if (stage->type == ST_NORMALPARALLAXMAP) + type = IMGTYPE_NORMALHEIGHT; + } + else + { + if (r_genNormalMaps->integer) + flags |= IMGFLAG_GENNORMALMAP; + + if (r_srgb->integer) + flags |= IMGFLAG_SRGB; + } + + + stage->bundle[0].image[0] = R_FindImageFile( token, type, flags ); + if ( !stage->bundle[0].image[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + } + // + // animMap .... + // + else if ( !Q_stricmp( token, "animMap" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'animMmap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + stage->bundle[0].imageAnimationSpeed = atof( token ); + + // parse up to MAX_IMAGE_ANIMATIONS animations + while ( 1 ) { + int num; + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + break; + } + num = stage->bundle[0].numImageAnimations; + if ( num < MAX_IMAGE_ANIMATIONS ) { + imgFlags_t flags = IMGFLAG_SRGB; + + if (!shader.noMipMaps) + flags |= IMGFLAG_MIPMAP; + + if (!shader.noPicMip) + flags |= IMGFLAG_PICMIP; + + stage->bundle[0].image[num] = R_FindImageFile( token, IMGTYPE_COLORALPHA, flags ); + if ( !stage->bundle[0].image[num] ) + { + ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + stage->bundle[0].numImageAnimations++; + } + } + } + else if ( !Q_stricmp( token, "videoMap" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'videoMmap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + stage->bundle[0].videoMapHandle = ri.CIN_PlayCinematic( token, 0, 0, 256, 256, (CIN_loop | CIN_silent | CIN_shader)); + if (stage->bundle[0].videoMapHandle != -1) { + stage->bundle[0].isVideoMap = qtrue; + stage->bundle[0].image[0] = tr.scratchImage[stage->bundle[0].videoMapHandle]; + } + } + // + // alphafunc + // + else if ( !Q_stricmp( token, "alphaFunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'alphaFunc' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + atestBits = NameToAFunc( token ); + } + // + // depthFunc + // + else if ( !Q_stricmp( token, "depthfunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'depthfunc' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + if ( !Q_stricmp( token, "lequal" ) ) + { + depthFuncBits = 0; + } + else if ( !Q_stricmp( token, "equal" ) ) + { + depthFuncBits = GLS_DEPTHFUNC_EQUAL; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown depthfunc '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // detail + // + else if ( !Q_stricmp( token, "detail" ) ) + { + stage->isDetail = qtrue; + } + // + // blendfunc + // or blendfunc + // + else if ( !Q_stricmp( token, "blendfunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name ); + continue; + } + // check for "simple" blends first + if ( !Q_stricmp( token, "add" ) ) { + blendSrcBits = GLS_SRCBLEND_ONE; + blendDstBits = GLS_DSTBLEND_ONE; + } else if ( !Q_stricmp( token, "filter" ) ) { + blendSrcBits = GLS_SRCBLEND_DST_COLOR; + blendDstBits = GLS_DSTBLEND_ZERO; + } else if ( !Q_stricmp( token, "blend" ) ) { + blendSrcBits = GLS_SRCBLEND_SRC_ALPHA; + blendDstBits = GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else { + // complex double blends + blendSrcBits = NameToSrcBlendMode( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name ); + continue; + } + blendDstBits = NameToDstBlendMode( token ); + } + + // clear depth mask for blended surfaces + if ( !depthMaskExplicit ) + { + depthMaskBits = 0; + } + } + // + // stage + // + else if(!Q_stricmp(token, "stage")) + { + token = COM_ParseExt(text, qfalse); + if(token[0] == 0) + { + ri.Printf(PRINT_WARNING, "WARNING: missing parameters for stage in shader '%s'\n", shader.name); + continue; + } + + if(!Q_stricmp(token, "diffuseMap")) + { + stage->type = ST_DIFFUSEMAP; + } + else if(!Q_stricmp(token, "normalMap") || !Q_stricmp(token, "bumpMap")) + { + stage->type = ST_NORMALMAP; + } + else if(!Q_stricmp(token, "normalParallaxMap") || !Q_stricmp(token, "bumpParallaxMap")) + { + if (r_parallaxMapping->integer) + stage->type = ST_NORMALPARALLAXMAP; + else + stage->type = ST_NORMALMAP; + } + else if(!Q_stricmp(token, "specularMap")) + { + stage->type = ST_SPECULARMAP; + stage->materialInfo[0] = 0.04f; + stage->materialInfo[1] = 256.0f; + } + else + { + ri.Printf(PRINT_WARNING, "WARNING: unknown stage parameter '%s' in shader '%s'\n", token, shader.name); + continue; + } + } + // + // specularReflectance + // + else if (!Q_stricmp(token, "specularreflectance")) + { + token = COM_ParseExt(text, qfalse); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for specular reflectance in shader '%s'\n", shader.name ); + continue; + } + stage->materialInfo[0] = atof( token ); + } + // + // specularExponent + // + else if (!Q_stricmp(token, "specularexponent")) + { + token = COM_ParseExt(text, qfalse); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameter for specular exponent in shader '%s'\n", shader.name ); + continue; + } + stage->materialInfo[1] = atof( token ); + } + // + // rgbGen + // + else if ( !Q_stricmp( token, "rgbGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameters for rgbGen in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + ParseWaveForm( text, &stage->rgbWave ); + stage->rgbGen = CGEN_WAVEFORM; + } + else if ( !Q_stricmp( token, "const" ) ) + { + vec3_t color; + + ParseVector( text, 3, color ); + stage->constantColor[0] = 255 * color[0]; + stage->constantColor[1] = 255 * color[1]; + stage->constantColor[2] = 255 * color[2]; + + stage->rgbGen = CGEN_CONST; + } + else if ( !Q_stricmp( token, "identity" ) ) + { + stage->rgbGen = CGEN_IDENTITY; + } + else if ( !Q_stricmp( token, "identityLighting" ) ) + { + stage->rgbGen = CGEN_IDENTITY_LIGHTING; + } + else if ( !Q_stricmp( token, "entity" ) ) + { + stage->rgbGen = CGEN_ENTITY; + } + else if ( !Q_stricmp( token, "oneMinusEntity" ) ) + { + stage->rgbGen = CGEN_ONE_MINUS_ENTITY; + } + else if ( !Q_stricmp( token, "vertex" ) ) + { + stage->rgbGen = CGEN_VERTEX; + if ( stage->alphaGen == 0 ) { + stage->alphaGen = AGEN_VERTEX; + } + } + else if ( !Q_stricmp( token, "exactVertex" ) ) + { + stage->rgbGen = CGEN_EXACT_VERTEX; + } + else if ( !Q_stricmp( token, "vertexLit" ) ) + { + stage->rgbGen = CGEN_VERTEX_LIT; + if ( stage->alphaGen == 0 ) { + stage->alphaGen = AGEN_VERTEX; + } + } + else if ( !Q_stricmp( token, "exactVertexLit" ) ) + { + stage->rgbGen = CGEN_EXACT_VERTEX_LIT; + } + else if ( !Q_stricmp( token, "lightingDiffuse" ) ) + { + stage->rgbGen = CGEN_LIGHTING_DIFFUSE; + } + else if ( !Q_stricmp( token, "oneMinusVertex" ) ) + { + stage->rgbGen = CGEN_ONE_MINUS_VERTEX; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown rgbGen parameter '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // alphaGen + // + else if ( !Q_stricmp( token, "alphaGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parameters for alphaGen in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + ParseWaveForm( text, &stage->alphaWave ); + stage->alphaGen = AGEN_WAVEFORM; + } + else if ( !Q_stricmp( token, "const" ) ) + { + token = COM_ParseExt( text, qfalse ); + stage->constantColor[3] = 255 * atof( token ); + stage->alphaGen = AGEN_CONST; + } + else if ( !Q_stricmp( token, "identity" ) ) + { + stage->alphaGen = AGEN_IDENTITY; + } + else if ( !Q_stricmp( token, "entity" ) ) + { + stage->alphaGen = AGEN_ENTITY; + } + else if ( !Q_stricmp( token, "oneMinusEntity" ) ) + { + stage->alphaGen = AGEN_ONE_MINUS_ENTITY; + } + else if ( !Q_stricmp( token, "vertex" ) ) + { + stage->alphaGen = AGEN_VERTEX; + } + else if ( !Q_stricmp( token, "lightingSpecular" ) ) + { + stage->alphaGen = AGEN_LIGHTING_SPECULAR; + } + else if ( !Q_stricmp( token, "oneMinusVertex" ) ) + { + stage->alphaGen = AGEN_ONE_MINUS_VERTEX; + } + else if ( !Q_stricmp( token, "portal" ) ) + { + stage->alphaGen = AGEN_PORTAL; + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + shader.portalRange = 256; + ri.Printf( PRINT_WARNING, "WARNING: missing range parameter for alphaGen portal in shader '%s', defaulting to 256\n", shader.name ); + } + else + { + shader.portalRange = atof( token ); + } + } + else if ( !Q_stricmp( token, "fresnel" ) ) + { + stage->alphaGen = AGEN_FRESNEL; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown alphaGen parameter '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // tcGen + // + else if ( !Q_stricmp(token, "texgen") || !Q_stricmp( token, "tcGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing texgen parm in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "environment" ) ) + { + stage->bundle[0].tcGen = TCGEN_ENVIRONMENT_MAPPED; + } + else if ( !Q_stricmp( token, "lightmap" ) ) + { + stage->bundle[0].tcGen = TCGEN_LIGHTMAP; + } + else if ( !Q_stricmp( token, "texture" ) || !Q_stricmp( token, "base" ) ) + { + stage->bundle[0].tcGen = TCGEN_TEXTURE; + } + else if ( !Q_stricmp( token, "vector" ) ) + { + ParseVector( text, 3, stage->bundle[0].tcGenVectors[0] ); + ParseVector( text, 3, stage->bundle[0].tcGenVectors[1] ); + + stage->bundle[0].tcGen = TCGEN_VECTOR; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown texgen parm in shader '%s'\n", shader.name ); + } + } + // + // tcMod <...> + // + else if ( !Q_stricmp( token, "tcMod" ) ) + { + char buffer[1024] = ""; + + while ( 1 ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + break; + strcat( buffer, token ); + strcat( buffer, " " ); + } + + ParseTexMod( buffer, stage ); + + continue; + } + // + // depthmask + // + else if ( !Q_stricmp( token, "depthwrite" ) ) + { + depthMaskBits = GLS_DEPTHMASK_TRUE; + depthMaskExplicit = qtrue; + + continue; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown parameter '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + } + + // + // if cgen isn't explicitly specified, use either identity or identitylighting + // + if ( stage->rgbGen == CGEN_BAD ) { + if ( blendSrcBits == 0 || + blendSrcBits == GLS_SRCBLEND_ONE || + blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) { + stage->rgbGen = CGEN_IDENTITY_LIGHTING; + } else { + stage->rgbGen = CGEN_IDENTITY; + } + } + + + // + // implicitly assume that a GL_ONE GL_ZERO blend mask disables blending + // + if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && + ( blendDstBits == GLS_DSTBLEND_ZERO ) ) + { + blendDstBits = blendSrcBits = 0; + depthMaskBits = GLS_DEPTHMASK_TRUE; + } + + // decide which agens we can skip + if ( stage->alphaGen == AGEN_IDENTITY ) { + if ( stage->rgbGen == CGEN_IDENTITY + || stage->rgbGen == CGEN_LIGHTING_DIFFUSE ) { + stage->alphaGen = AGEN_SKIP; + } + } + + // + // compute state bits + // + stage->stateBits = depthMaskBits | + blendSrcBits | blendDstBits | + atestBits | + depthFuncBits; + + return qtrue; +} + +/* +=============== +ParseDeform + +deformVertexes wave +deformVertexes normal +deformVertexes move +deformVertexes bulge +deformVertexes projectionShadow +deformVertexes autoSprite +deformVertexes autoSprite2 +deformVertexes text[0-7] +=============== +*/ +static void ParseDeform( char **text ) { + char *token; + deformStage_t *ds; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deform parm in shader '%s'\n", shader.name ); + return; + } + + if ( shader.numDeforms == MAX_SHADER_DEFORMS ) { + ri.Printf( PRINT_WARNING, "WARNING: MAX_SHADER_DEFORMS in '%s'\n", shader.name ); + return; + } + + ds = &shader.deforms[ shader.numDeforms ]; + shader.numDeforms++; + + if ( !Q_stricmp( token, "projectionShadow" ) ) { + ds->deformation = DEFORM_PROJECTION_SHADOW; + return; + } + + if ( !Q_stricmp( token, "autosprite" ) ) { + ds->deformation = DEFORM_AUTOSPRITE; + return; + } + + if ( !Q_stricmp( token, "autosprite2" ) ) { + ds->deformation = DEFORM_AUTOSPRITE2; + return; + } + + if ( !Q_stricmpn( token, "text", 4 ) ) { + int n; + + n = token[4] - '0'; + if ( n < 0 || n > 7 ) { + n = 0; + } + ds->deformation = DEFORM_TEXT0 + n; + return; + } + + if ( !Q_stricmp( token, "bulge" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeWidth = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeHeight = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeSpeed = atof( token ); + + ds->deformation = DEFORM_BULGE; + return; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + + if ( atof( token ) != 0 ) + { + ds->deformationSpread = 1.0f / atof( token ); + } + else + { + ds->deformationSpread = 100.0f; + ri.Printf( PRINT_WARNING, "WARNING: illegal div value of 0 in deformVertexes command for shader '%s'\n", shader.name ); + } + + ParseWaveForm( text, &ds->deformationWave ); + ds->deformation = DEFORM_WAVE; + return; + } + + if ( !Q_stricmp( token, "normal" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->deformationWave.amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->deformationWave.frequency = atof( token ); + + ds->deformation = DEFORM_NORMALS; + return; + } + + if ( !Q_stricmp( token, "move" ) ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->moveVector[i] = atof( token ); + } + + ParseWaveForm( text, &ds->deformationWave ); + ds->deformation = DEFORM_MOVE; + return; + } + + ri.Printf( PRINT_WARNING, "WARNING: unknown deformVertexes subtype '%s' found in shader '%s'\n", token, shader.name ); +} + + +/* +=============== +ParseSkyParms + +skyParms +=============== +*/ +static void ParseSkyParms( char **text ) { + char *token; + static char *suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"}; + char pathname[MAX_QPATH]; + int i; + + // outerbox + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + if ( strcmp( token, "-" ) ) { + for (i=0 ; i<6 ; i++) { + Com_sprintf( pathname, sizeof(pathname), "%s_%s.tga" + , token, suf[i] ); + shader.sky.outerbox[i] = R_FindImageFile( ( char * ) pathname, IMGTYPE_COLORALPHA, IMGFLAG_SRGB | IMGFLAG_MIPMAP | IMGFLAG_PICMIP | IMGFLAG_CLAMPTOEDGE ); + + if ( !shader.sky.outerbox[i] ) { + shader.sky.outerbox[i] = tr.defaultImage; + } + } + } + + // cloudheight + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + shader.sky.cloudHeight = atof( token ); + if ( !shader.sky.cloudHeight ) { + shader.sky.cloudHeight = 512; + } + R_InitSkyTexCoords( shader.sky.cloudHeight ); + + + // innerbox + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + if ( strcmp( token, "-" ) ) { + for (i=0 ; i<6 ; i++) { + Com_sprintf( pathname, sizeof(pathname), "%s_%s.tga" + , token, suf[i] ); + shader.sky.innerbox[i] = R_FindImageFile( ( char * ) pathname, IMGTYPE_COLORALPHA, IMGFLAG_SRGB | IMGFLAG_MIPMAP | IMGFLAG_PICMIP ); + if ( !shader.sky.innerbox[i] ) { + shader.sky.innerbox[i] = tr.defaultImage; + } + } + } + + shader.isSky = qtrue; +} + + +/* +================= +ParseSort +================= +*/ +void ParseSort( char **text ) { + char *token; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + ri.Printf( PRINT_WARNING, "WARNING: missing sort parameter in shader '%s'\n", shader.name ); + return; + } + + if ( !Q_stricmp( token, "portal" ) ) { + shader.sort = SS_PORTAL; + } else if ( !Q_stricmp( token, "sky" ) ) { + shader.sort = SS_ENVIRONMENT; + } else if ( !Q_stricmp( token, "opaque" ) ) { + shader.sort = SS_OPAQUE; + }else if ( !Q_stricmp( token, "decal" ) ) { + shader.sort = SS_DECAL; + } else if ( !Q_stricmp( token, "seeThrough" ) ) { + shader.sort = SS_SEE_THROUGH; + } else if ( !Q_stricmp( token, "banner" ) ) { + shader.sort = SS_BANNER; + } else if ( !Q_stricmp( token, "additive" ) ) { + shader.sort = SS_BLEND1; + } else if ( !Q_stricmp( token, "nearest" ) ) { + shader.sort = SS_NEAREST; + } else if ( !Q_stricmp( token, "underwater" ) ) { + shader.sort = SS_UNDERWATER; + } else { + shader.sort = atof( token ); + } +} + + + +// this table is also present in q3map + +typedef struct { + char *name; + int clearSolid, surfaceFlags, contents; +} infoParm_t; + +infoParm_t infoParms[] = { + // server relevant contents + {"water", 1, 0, CONTENTS_WATER }, + {"slime", 1, 0, CONTENTS_SLIME }, // mildly damaging + {"lava", 1, 0, CONTENTS_LAVA }, // very damaging + {"playerclip", 1, 0, CONTENTS_PLAYERCLIP }, + {"monsterclip", 1, 0, CONTENTS_MONSTERCLIP }, + {"nodrop", 1, 0, CONTENTS_NODROP }, // don't drop items or leave bodies (death fog, lava, etc) + {"nonsolid", 1, SURF_NONSOLID, 0}, // clears the solid flag + + // utility relevant attributes + {"origin", 1, 0, CONTENTS_ORIGIN }, // center of rotating brushes + {"trans", 0, 0, CONTENTS_TRANSLUCENT }, // don't eat contained surfaces + {"detail", 0, 0, CONTENTS_DETAIL }, // don't include in structural bsp + {"structural", 0, 0, CONTENTS_STRUCTURAL }, // force into structural bsp even if trnas + {"areaportal", 1, 0, CONTENTS_AREAPORTAL }, // divides areas + {"clusterportal", 1,0, CONTENTS_CLUSTERPORTAL }, // for bots + {"donotenter", 1, 0, CONTENTS_DONOTENTER }, // for bots + + {"fog", 1, 0, CONTENTS_FOG}, // carves surfaces entering + {"sky", 0, SURF_SKY, 0 }, // emit light from an environment map + {"lightfilter", 0, SURF_LIGHTFILTER, 0 }, // filter light going through it + {"alphashadow", 0, SURF_ALPHASHADOW, 0 }, // test light on a per-pixel basis + {"hint", 0, SURF_HINT, 0 }, // use as a primary splitter + + // server attributes + {"slick", 0, SURF_SLICK, 0 }, + {"noimpact", 0, SURF_NOIMPACT, 0 }, // don't make impact explosions or marks + {"nomarks", 0, SURF_NOMARKS, 0 }, // don't make impact marks, but still explode + {"ladder", 0, SURF_LADDER, 0 }, + {"nodamage", 0, SURF_NODAMAGE, 0 }, + {"metalsteps", 0, SURF_METALSTEPS,0 }, + {"flesh", 0, SURF_FLESH, 0 }, + {"nosteps", 0, SURF_NOSTEPS, 0 }, + + // drawsurf attributes + {"nodraw", 0, SURF_NODRAW, 0 }, // don't generate a drawsurface (or a lightmap) + {"pointlight", 0, SURF_POINTLIGHT, 0 }, // sample lighting at vertexes + {"nolightmap", 0, SURF_NOLIGHTMAP,0 }, // don't generate a lightmap + {"nodlight", 0, SURF_NODLIGHT, 0 }, // don't ever add dynamic lights + {"dust", 0, SURF_DUST, 0} // leave a dust trail when walking on this surface +}; + + +/* +=============== +ParseSurfaceParm + +surfaceparm +=============== +*/ +static void ParseSurfaceParm( char **text ) { + char *token; + int numInfoParms = ARRAY_LEN( infoParms ); + int i; + + token = COM_ParseExt( text, qfalse ); + for ( i = 0 ; i < numInfoParms ; i++ ) { + if ( !Q_stricmp( token, infoParms[i].name ) ) { + shader.surfaceFlags |= infoParms[i].surfaceFlags; + shader.contentFlags |= infoParms[i].contents; +#if 0 + if ( infoParms[i].clearSolid ) { + si->contents &= ~CONTENTS_SOLID; + } +#endif + break; + } + } +} + +/* +================= +ParseShader + +The current text pointer is at the explicit text definition of the +shader. Parse it into the global shader variable. Later functions +will optimize it. +================= +*/ +static qboolean ParseShader( char **text ) +{ + char *token; + int s; + + s = 0; + + token = COM_ParseExt( text, qtrue ); + if ( token[0] != '{' ) + { + ri.Printf( PRINT_WARNING, "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader.name ); + return qfalse; + } + + while ( 1 ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: no concluding '}' in shader %s\n", shader.name ); + return qfalse; + } + + // end of shader definition + if ( token[0] == '}' ) + { + break; + } + // stage definition + else if ( token[0] == '{' ) + { + if ( s >= MAX_SHADER_STAGES ) { + ri.Printf( PRINT_WARNING, "WARNING: too many stages in shader %s\n", shader.name ); + return qfalse; + } + + if ( !ParseStage( &stages[s], text ) ) + { + return qfalse; + } + stages[s].active = qtrue; + s++; + + continue; + } + // skip stuff that only the QuakeEdRadient needs + else if ( !Q_stricmpn( token, "qer", 3 ) ) { + SkipRestOfLine( text ); + continue; + } + // sun parms + else if ( !Q_stricmp( token, "q3map_sun" ) || !Q_stricmp( token, "q3map_sunExt" ) || !Q_stricmp( token, "q3gl2_sun" ) ) { + float a, b; + qboolean isGL2Sun = qfalse; + + if (!Q_stricmp( token, "q3gl2_sun" ) && r_sunShadows->integer ) + { + isGL2Sun = qtrue; + tr.sunShadows = qtrue; + } + + token = COM_ParseExt( text, qfalse ); + tr.sunLight[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.sunLight[1] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.sunLight[2] = atof( token ); + + VectorNormalize( tr.sunLight ); + + token = COM_ParseExt( text, qfalse ); + a = atof( token ); + VectorScale( tr.sunLight, a, tr.sunLight); + + VectorSet( tr.sunAmbient, 0.0f, 0.0f, 0.0f); + + token = COM_ParseExt( text, qfalse ); + a = atof( token ); + a = a / 180 * M_PI; + + token = COM_ParseExt( text, qfalse ); + b = atof( token ); + b = b / 180 * M_PI; + + tr.sunDirection[0] = cos( a ) * cos( b ); + tr.sunDirection[1] = sin( a ) * cos( b ); + tr.sunDirection[2] = sin( b ); + + if (isGL2Sun) + { + token = COM_ParseExt( text, qfalse ); + tr.mapLightScale = atof(token); + + token = COM_ParseExt( text, qfalse ); + VectorScale( tr.sunLight, atof(token), tr.sunAmbient ); + } + + SkipRestOfLine( text ); + continue; + } + // tonemap parms + else if ( !Q_stricmp( token, "q3gl2_tonemap" ) ) { + token = COM_ParseExt( text, qfalse ); + tr.toneMinAvgMaxLevel[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.toneMinAvgMaxLevel[1] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.toneMinAvgMaxLevel[2] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + tr.autoExposureMinMax[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.autoExposureMinMax[1] = atof( token ); + + SkipRestOfLine( text ); + continue; + } + else if ( !Q_stricmp( token, "deformVertexes" ) ) { + ParseDeform( text ); + continue; + } + else if ( !Q_stricmp( token, "tesssize" ) ) { + SkipRestOfLine( text ); + continue; + } + else if ( !Q_stricmp( token, "clampTime" ) ) { + token = COM_ParseExt( text, qfalse ); + if (token[0]) { + shader.clampTime = atof(token); + } + } + // skip stuff that only the q3map needs + else if ( !Q_stricmpn( token, "q3map", 5 ) ) { + SkipRestOfLine( text ); + continue; + } + // skip stuff that only q3map or the server needs + else if ( !Q_stricmp( token, "surfaceParm" ) ) { + ParseSurfaceParm( text ); + continue; + } + // no mip maps + else if ( !Q_stricmp( token, "nomipmaps" ) ) + { + shader.noMipMaps = qtrue; + shader.noPicMip = qtrue; + continue; + } + // no picmip adjustment + else if ( !Q_stricmp( token, "nopicmip" ) ) + { + shader.noPicMip = qtrue; + continue; + } + // polygonOffset + else if ( !Q_stricmp( token, "polygonOffset" ) ) + { + shader.polygonOffset = qtrue; + continue; + } + // entityMergable, allowing sprite surfaces from multiple entities + // to be merged into one batch. This is a savings for smoke + // puffs and blood, but can't be used for anything where the + // shader calcs (not the surface function) reference the entity color or scroll + else if ( !Q_stricmp( token, "entityMergable" ) ) + { + shader.entityMergable = qtrue; + continue; + } + // fogParms + else if ( !Q_stricmp( token, "fogParms" ) ) + { + if ( !ParseVector( text, 3, shader.fogParms.color ) ) { + return qfalse; + } + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing parm for 'fogParms' keyword in shader '%s'\n", shader.name ); + continue; + } + shader.fogParms.depthForOpaque = atof( token ); + + // skip any old gradient directions + SkipRestOfLine( text ); + continue; + } + // portal + else if ( !Q_stricmp(token, "portal") ) + { + shader.sort = SS_PORTAL; + shader.isPortal = qtrue; + continue; + } + // skyparms + else if ( !Q_stricmp( token, "skyparms" ) ) + { + ParseSkyParms( text ); + continue; + } + // light determines flaring in q3map, not needed here + else if ( !Q_stricmp(token, "light") ) + { + token = COM_ParseExt( text, qfalse ); + continue; + } + // cull + else if ( !Q_stricmp( token, "cull") ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + ri.Printf( PRINT_WARNING, "WARNING: missing cull parms in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "none" ) || !Q_stricmp( token, "twosided" ) || !Q_stricmp( token, "disable" ) ) + { + shader.cullType = CT_TWO_SIDED; + } + else if ( !Q_stricmp( token, "back" ) || !Q_stricmp( token, "backside" ) || !Q_stricmp( token, "backsided" ) ) + { + shader.cullType = CT_BACK_SIDED; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: invalid cull parm '%s' in shader '%s'\n", token, shader.name ); + } + continue; + } + // sort + else if ( !Q_stricmp( token, "sort" ) ) + { + ParseSort( text ); + continue; + } + else + { + ri.Printf( PRINT_WARNING, "WARNING: unknown general shader parameter '%s' in '%s'\n", token, shader.name ); + return qfalse; + } + } + + // + // ignore shaders that don't have any stages, unless it is a sky or fog + // + if ( s == 0 && !shader.isSky && !(shader.contentFlags & CONTENTS_FOG ) ) { + return qfalse; + } + + shader.explicitlyDefined = qtrue; + + return qtrue; +} + +/* +======================================================================================== + +SHADER OPTIMIZATION AND FOGGING + +======================================================================================== +*/ + +/* +=================== +ComputeStageIteratorFunc + +See if we can use on of the simple fastpath stage functions, +otherwise set to the generic stage function +=================== +*/ +static void ComputeStageIteratorFunc( void ) +{ + shader.optimalStageIteratorFunc = RB_StageIteratorGeneric; + + // + // see if this should go into the sky path + // + if ( shader.isSky ) + { + shader.optimalStageIteratorFunc = RB_StageIteratorSky; + return; + } +} + +/* +=================== +ComputeVertexAttribs + +Check which vertex attributes we only need, so we +don't need to submit/copy all of them. +=================== +*/ +static void ComputeVertexAttribs(void) +{ + int i, stage; + + // dlights always need ATTR_NORMAL + shader.vertexAttribs = ATTR_POSITION | ATTR_NORMAL; + + // portals always need normals, for SurfIsOffscreen() + if (shader.isPortal) + { + shader.vertexAttribs |= ATTR_NORMAL; + } + + if (shader.defaultShader) + { + shader.vertexAttribs |= ATTR_TEXCOORD; + return; + } + + if(shader.numDeforms) + { + for ( i = 0; i < shader.numDeforms; i++) + { + deformStage_t *ds = &shader.deforms[i]; + + switch (ds->deformation) + { + case DEFORM_BULGE: + shader.vertexAttribs |= ATTR_NORMAL | ATTR_TEXCOORD; + break; + + case DEFORM_AUTOSPRITE: + shader.vertexAttribs |= ATTR_NORMAL | ATTR_COLOR; + break; + + case DEFORM_WAVE: + case DEFORM_NORMALS: + case DEFORM_TEXT0: + case DEFORM_TEXT1: + case DEFORM_TEXT2: + case DEFORM_TEXT3: + case DEFORM_TEXT4: + case DEFORM_TEXT5: + case DEFORM_TEXT6: + case DEFORM_TEXT7: + shader.vertexAttribs |= ATTR_NORMAL; + break; + + default: + case DEFORM_NONE: + case DEFORM_MOVE: + case DEFORM_PROJECTION_SHADOW: + case DEFORM_AUTOSPRITE2: + break; + } + } + } + + for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) + { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) + { + break; + } + + if (pStage->glslShaderGroup == tr.lightallShader) + { + shader.vertexAttribs |= ATTR_NORMAL; + +#ifdef USE_VERT_TANGENT_SPACE + if (pStage->glslShaderIndex & LIGHTDEF_USE_NORMALMAP) + { + shader.vertexAttribs |= ATTR_BITANGENT | ATTR_TANGENT; + } +#endif + + switch (pStage->glslShaderIndex & LIGHTDEF_LIGHTTYPE_MASK) + { + case LIGHTDEF_USE_LIGHTMAP: + case LIGHTDEF_USE_LIGHT_VERTEX: + shader.vertexAttribs |= ATTR_LIGHTDIRECTION; + break; + default: + break; + } + } + + for (i = 0; i < NUM_TEXTURE_BUNDLES; i++) + { + if ( pStage->bundle[i].image[0] == 0 ) + { + continue; + } + + switch(pStage->bundle[i].tcGen) + { + case TCGEN_TEXTURE: + shader.vertexAttribs |= ATTR_TEXCOORD; + break; + case TCGEN_LIGHTMAP: + shader.vertexAttribs |= ATTR_LIGHTCOORD; + break; + case TCGEN_ENVIRONMENT_MAPPED: + shader.vertexAttribs |= ATTR_NORMAL; + break; + + default: + break; + } + } + + switch(pStage->rgbGen) + { + case CGEN_EXACT_VERTEX: + case CGEN_VERTEX: + case CGEN_EXACT_VERTEX_LIT: + case CGEN_VERTEX_LIT: + case CGEN_ONE_MINUS_VERTEX: + shader.vertexAttribs |= ATTR_COLOR; + break; + + case CGEN_LIGHTING_DIFFUSE: + shader.vertexAttribs |= ATTR_NORMAL; + break; + + default: + break; + } + + switch(pStage->alphaGen) + { + case AGEN_LIGHTING_SPECULAR: + case AGEN_FRESNEL: + shader.vertexAttribs |= ATTR_NORMAL; + break; + + case AGEN_VERTEX: + case AGEN_ONE_MINUS_VERTEX: + shader.vertexAttribs |= ATTR_COLOR; + break; + + default: + break; + } + } +} + +typedef struct { + int blendA; + int blendB; + + int multitextureEnv; + int multitextureBlend; +} collapse_t; + +static collapse_t collapse[] = { + { 0, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, 0 }, + + { 0, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, 0 }, + + { GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { 0, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, + GL_ADD, 0 }, + + { GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, + GL_ADD, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE }, +#if 0 + { 0, GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_SRCBLEND_SRC_ALPHA, + GL_DECAL, 0 }, +#endif + { -1 } +}; + +/* +================ +CollapseMultitexture + +Attempt to combine two stages into a single multitexture stage +FIXME: I think modulated add + modulated add collapses incorrectly +================= +*/ +static qboolean CollapseMultitexture( void ) { + int abits, bbits; + int i; + textureBundle_t tmpBundle; + + if ( !qglActiveTextureARB ) { + return qfalse; + } + + // make sure both stages are active + if ( !stages[0].active || !stages[1].active ) { + return qfalse; + } + + // on voodoo2, don't combine different tmus + if ( glConfig.driverType == GLDRV_VOODOO ) { + if ( stages[0].bundle[0].image[0]->TMU == + stages[1].bundle[0].image[0]->TMU ) { + return qfalse; + } + } + + abits = stages[0].stateBits; + bbits = stages[1].stateBits; + + // make sure that both stages have identical state other than blend modes + if ( ( abits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) != + ( bbits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) ) { + return qfalse; + } + + abits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + bbits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + + // search for a valid multitexture blend function + for ( i = 0; collapse[i].blendA != -1 ; i++ ) { + if ( abits == collapse[i].blendA + && bbits == collapse[i].blendB ) { + break; + } + } + + // nothing found + if ( collapse[i].blendA == -1 ) { + return qfalse; + } + + // GL_ADD is a separate extension + if ( collapse[i].multitextureEnv == GL_ADD && !glConfig.textureEnvAddAvailable ) { + return qfalse; + } + + // make sure waveforms have identical parameters + if ( ( stages[0].rgbGen != stages[1].rgbGen ) || + ( stages[0].alphaGen != stages[1].alphaGen ) ) { + return qfalse; + } + + // an add collapse can only have identity colors + if ( collapse[i].multitextureEnv == GL_ADD && stages[0].rgbGen != CGEN_IDENTITY ) { + return qfalse; + } + + if ( stages[0].rgbGen == CGEN_WAVEFORM ) + { + if ( memcmp( &stages[0].rgbWave, + &stages[1].rgbWave, + sizeof( stages[0].rgbWave ) ) ) + { + return qfalse; + } + } + if ( stages[0].alphaGen == AGEN_WAVEFORM ) + { + if ( memcmp( &stages[0].alphaWave, + &stages[1].alphaWave, + sizeof( stages[0].alphaWave ) ) ) + { + return qfalse; + } + } + + + // make sure that lightmaps are in bundle 1 for 3dfx + if ( stages[0].bundle[0].isLightmap ) + { + tmpBundle = stages[0].bundle[0]; + stages[0].bundle[0] = stages[1].bundle[0]; + stages[0].bundle[1] = tmpBundle; + } + else + { + stages[0].bundle[1] = stages[1].bundle[0]; + } + + // set the new blend state bits + shader.multitextureEnv = collapse[i].multitextureEnv; + stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + stages[0].stateBits |= collapse[i].multitextureBlend; + + // + // move down subsequent shaders + // + memmove( &stages[1], &stages[2], sizeof( stages[0] ) * ( MAX_SHADER_STAGES - 2 ) ); + Com_Memset( &stages[MAX_SHADER_STAGES-1], 0, sizeof( stages[0] ) ); + + return qtrue; +} + +static void CollapseStagesToLightall(shaderStage_t *diffuse, + shaderStage_t *normal, shaderStage_t *specular, shaderStage_t *lightmap, + qboolean useLightVector, qboolean useLightVertex, qboolean parallax, qboolean environment) +{ + int defs = 0; + + //ri.Printf(PRINT_ALL, "shader %s has diffuse %s", shader.name, diffuse->bundle[0].image[0]->imgName); + + // reuse diffuse, mark others inactive + diffuse->type = ST_GLSL; + + if (lightmap) + { + //ri.Printf(PRINT_ALL, ", lightmap"); + diffuse->bundle[TB_LIGHTMAP] = lightmap->bundle[0]; + defs |= LIGHTDEF_USE_LIGHTMAP; + } + else if (useLightVector) + { + defs |= LIGHTDEF_USE_LIGHT_VECTOR; + } + else if (useLightVertex) + { + defs |= LIGHTDEF_USE_LIGHT_VERTEX; + } + + if (r_deluxeMapping->integer && tr.worldDeluxeMapping && lightmap) + { + //ri.Printf(PRINT_ALL, ", deluxemap"); + diffuse->bundle[TB_DELUXEMAP] = lightmap->bundle[0]; + diffuse->bundle[TB_DELUXEMAP].image[0] = tr.deluxemaps[shader.lightmapIndex]; + defs |= LIGHTDEF_USE_DELUXEMAP; + } + + if (r_normalMapping->integer) + { + image_t *diffuseImg; + if (normal) + { + //ri.Printf(PRINT_ALL, ", normalmap %s", normal->bundle[0].image[0]->imgName); + diffuse->bundle[TB_NORMALMAP] = normal->bundle[0]; + defs |= LIGHTDEF_USE_NORMALMAP; + if (parallax && r_parallaxMapping->integer) + defs |= LIGHTDEF_USE_PARALLAXMAP; + } + else if ((lightmap || useLightVector || useLightVertex) && (diffuseImg = diffuse->bundle[TB_DIFFUSEMAP].image[0])) + { + char normalName[MAX_QPATH]; + image_t *normalImg; + imgFlags_t normalFlags = (diffuseImg->flags & ~(IMGFLAG_GENNORMALMAP | IMGFLAG_SRGB)) | IMGFLAG_NOLIGHTSCALE; + + COM_StripExtension(diffuseImg->imgName, normalName, MAX_QPATH); + Q_strcat(normalName, MAX_QPATH, "_n"); + + normalImg = R_FindImageFile(normalName, IMGTYPE_NORMAL, normalFlags); + + if (normalImg) + { + diffuse->bundle[TB_NORMALMAP] = diffuse->bundle[0]; + diffuse->bundle[TB_NORMALMAP].image[0] = normalImg; + + defs |= LIGHTDEF_USE_NORMALMAP; + if (parallax && r_parallaxMapping->integer) + defs |= LIGHTDEF_USE_PARALLAXMAP; + } + } + } + + if (r_specularMapping->integer) + { + if (specular) + { + //ri.Printf(PRINT_ALL, ", specularmap %s", specular->bundle[0].image[0]->imgName); + diffuse->bundle[TB_SPECULARMAP] = specular->bundle[0]; + diffuse->materialInfo[0] = specular->materialInfo[0]; + diffuse->materialInfo[1] = specular->materialInfo[1]; + defs |= LIGHTDEF_USE_SPECULARMAP; + } + } + + if (environment) + { + defs |= LIGHTDEF_TCGEN_ENVIRONMENT; + } + + //ri.Printf(PRINT_ALL, ".\n"); + + diffuse->glslShaderGroup = tr.lightallShader; + diffuse->glslShaderIndex = defs; +} + + +static qboolean CollapseStagesToGLSL(void) +{ + int i, j, numStages; + qboolean skip = qfalse; + + // skip shaders with deforms + if (shader.numDeforms != 0) + { + skip = qtrue; + } + + if (!skip) + { + // if 2+ stages and first stage is lightmap, switch them + // this makes it easier for the later bits to process + if (stages[0].active && stages[0].bundle[0].isLightmap && stages[1].active) + { + int blendBits = stages[1].stateBits & ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + + if (blendBits == (GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO) + || blendBits == (GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR)) + { + int stateBits0 = stages[0].stateBits; + int stateBits1 = stages[1].stateBits; + shaderStage_t swapStage; + + swapStage = stages[0]; + stages[0] = stages[1]; + stages[1] = swapStage; + + stages[0].stateBits = stateBits0; + stages[1].stateBits = stateBits1; + } + } + } + + if (!skip) + { + // scan for shaders that aren't supported + for (i = 0; i < MAX_SHADER_STAGES; i++) + { + shaderStage_t *pStage = &stages[i]; + + if (!pStage->active) + continue; + + if (pStage->adjustColorsForFog) + { + skip = qtrue; + break; + } + + if (pStage->bundle[0].isLightmap) + { + int blendBits = pStage->stateBits & ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + + if (blendBits != (GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO) + && blendBits != (GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR)) + { + skip = qtrue; + break; + } + } + + switch(pStage->bundle[0].tcGen) + { + case TCGEN_TEXTURE: + case TCGEN_LIGHTMAP: + case TCGEN_ENVIRONMENT_MAPPED: + break; + default: + skip = qtrue; + break; + } + + switch(pStage->alphaGen) + { + case AGEN_LIGHTING_SPECULAR: + case AGEN_PORTAL: + case AGEN_FRESNEL: + skip = qtrue; + break; + default: + break; + } + } + } + + if (!skip) + { + for (i = 0; i < MAX_SHADER_STAGES; i++) + { + shaderStage_t *pStage = &stages[i]; + shaderStage_t *diffuse, *normal, *specular, *lightmap; + qboolean parallax, environment, diffuselit, vertexlit; + + if (!pStage->active) + continue; + + // skip normal and specular maps + if (pStage->type != ST_COLORMAP) + continue; + + // skip lightmaps + if (pStage->bundle[0].isLightmap) + continue; + + diffuse = pStage; + normal = NULL; + parallax = qfalse; + specular = NULL; + lightmap = NULL; + + // we have a diffuse map, find matching normal, specular, and lightmap + for (j = i + 1; j < MAX_SHADER_STAGES; j++) + { + shaderStage_t *pStage2 = &stages[j]; + + if (!pStage2->active) + continue; + + switch(pStage2->type) + { + case ST_NORMALMAP: + if (!normal) + { + normal = pStage2; + } + break; + + case ST_NORMALPARALLAXMAP: + if (!normal) + { + normal = pStage2; + parallax = qtrue; + } + break; + + case ST_SPECULARMAP: + if (!specular) + { + specular = pStage2; + } + break; + + case ST_COLORMAP: + if (pStage2->bundle[0].isLightmap) + { + lightmap = pStage2; + } + break; + + default: + break; + } + } + + environment = qfalse; + if (diffuse->bundle[0].tcGen == TCGEN_ENVIRONMENT_MAPPED) + { + environment = qtrue; + } + + diffuselit = qfalse; + if (diffuse->rgbGen == CGEN_LIGHTING_DIFFUSE) + { + diffuselit = qtrue; + } + + vertexlit = qfalse; + if (diffuse->rgbGen == CGEN_VERTEX_LIT || diffuse->rgbGen == CGEN_EXACT_VERTEX_LIT) + { + vertexlit = qtrue; + } + + CollapseStagesToLightall(diffuse, normal, specular, lightmap, diffuselit, vertexlit, parallax, environment); + } + + // deactivate lightmap stages + for (i = 0; i < MAX_SHADER_STAGES; i++) + { + shaderStage_t *pStage = &stages[i]; + + if (!pStage->active) + continue; + + if (pStage->bundle[0].isLightmap) + { + pStage->active = qfalse; + } + } + } + + // deactivate normal and specular stages + for (i = 0; i < MAX_SHADER_STAGES; i++) + { + shaderStage_t *pStage = &stages[i]; + + if (!pStage->active) + continue; + + if (pStage->type == ST_NORMALMAP) + { + pStage->active = qfalse; + } + + if (pStage->type == ST_NORMALPARALLAXMAP) + { + pStage->active = qfalse; + } + + if (pStage->type == ST_SPECULARMAP) + { + pStage->active = qfalse; + } + } + + // remove inactive stages + numStages = 0; + for (i = 0; i < MAX_SHADER_STAGES; i++) + { + if (!stages[i].active) + continue; + + if (i == numStages) + { + numStages++; + continue; + } + + stages[numStages] = stages[i]; + stages[i].active = qfalse; + numStages++; + } + + if (numStages == i && i >= 2 && CollapseMultitexture()) + numStages--; + + return numStages; +} + +/* +============= + +FixRenderCommandList +https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493 +Arnout: this is a nasty issue. Shaders can be registered after drawsurfaces are generated +but before the frame is rendered. This will, for the duration of one frame, cause drawsurfaces +to be rendered with bad shaders. To fix this, need to go through all render commands and fix +sortedIndex. +============== +*/ +static void FixRenderCommandList( int newShader ) { + renderCommandList_t *cmdList = &backEndData[tr.smpFrame]->commands; + + if( cmdList ) { + const void *curCmd = cmdList->cmds; + + while ( 1 ) { + curCmd = PADP(curCmd, sizeof(void *)); + + switch ( *(const int *)curCmd ) { + case RC_SET_COLOR: + { + const setColorCommand_t *sc_cmd = (const setColorCommand_t *)curCmd; + curCmd = (const void *)(sc_cmd + 1); + break; + } + case RC_STRETCH_PIC: + { + const stretchPicCommand_t *sp_cmd = (const stretchPicCommand_t *)curCmd; + curCmd = (const void *)(sp_cmd + 1); + break; + } + case RC_DRAW_SURFS: + { + int i; + drawSurf_t *drawSurf; + shader_t *shader; + int fogNum; + int entityNum; + int dlightMap; + int pshadowMap; + int sortedIndex; + const drawSurfsCommand_t *ds_cmd = (const drawSurfsCommand_t *)curCmd; + + for( i = 0, drawSurf = ds_cmd->drawSurfs; i < ds_cmd->numDrawSurfs; i++, drawSurf++ ) { + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlightMap, &pshadowMap ); + sortedIndex = (( drawSurf->sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1)); + if( sortedIndex >= newShader ) { + sortedIndex++; + drawSurf->sort = (sortedIndex << QSORT_SHADERNUM_SHIFT) | entityNum | ( fogNum << QSORT_FOGNUM_SHIFT ) | ( (int)pshadowMap << QSORT_PSHADOW_SHIFT) | (int)dlightMap; + } + } + curCmd = (const void *)(ds_cmd + 1); + break; + } + case RC_DRAW_BUFFER: + { + const drawBufferCommand_t *db_cmd = (const drawBufferCommand_t *)curCmd; + curCmd = (const void *)(db_cmd + 1); + break; + } + case RC_SWAP_BUFFERS: + { + const swapBuffersCommand_t *sb_cmd = (const swapBuffersCommand_t *)curCmd; + curCmd = (const void *)(sb_cmd + 1); + break; + } + case RC_END_OF_LIST: + default: + return; + } + } + } +} + +/* +============== +SortNewShader + +Positions the most recently created shader in the tr.sortedShaders[] +array so that the shader->sort key is sorted reletive to the other +shaders. + +Sets shader->sortedIndex +============== +*/ +static void SortNewShader( void ) { + int i; + float sort; + shader_t *newShader; + + newShader = tr.shaders[ tr.numShaders - 1 ]; + sort = newShader->sort; + + for ( i = tr.numShaders - 2 ; i >= 0 ; i-- ) { + if ( tr.sortedShaders[ i ]->sort <= sort ) { + break; + } + tr.sortedShaders[i+1] = tr.sortedShaders[i]; + tr.sortedShaders[i+1]->sortedIndex++; + } + + // Arnout: fix rendercommandlist + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493 + FixRenderCommandList( i+1 ); + + newShader->sortedIndex = i+1; + tr.sortedShaders[i+1] = newShader; +} + + +/* +==================== +GeneratePermanentShader +==================== +*/ +static shader_t *GeneratePermanentShader( void ) { + shader_t *newShader; + int i, b; + int size, hash; + + if ( tr.numShaders == MAX_SHADERS ) { + ri.Printf( PRINT_WARNING, "WARNING: GeneratePermanentShader - MAX_SHADERS hit\n"); + return tr.defaultShader; + } + + newShader = ri.Hunk_Alloc( sizeof( shader_t ), h_low ); + + *newShader = shader; + + if ( shader.sort <= SS_OPAQUE ) { + newShader->fogPass = FP_EQUAL; + } else if ( shader.contentFlags & CONTENTS_FOG ) { + newShader->fogPass = FP_LE; + } + + tr.shaders[ tr.numShaders ] = newShader; + newShader->index = tr.numShaders; + + tr.sortedShaders[ tr.numShaders ] = newShader; + newShader->sortedIndex = tr.numShaders; + + tr.numShaders++; + + for ( i = 0 ; i < newShader->numUnfoggedPasses ; i++ ) { + if ( !stages[i].active ) { + break; + } + newShader->stages[i] = ri.Hunk_Alloc( sizeof( stages[i] ), h_low ); + *newShader->stages[i] = stages[i]; + + for ( b = 0 ; b < NUM_TEXTURE_BUNDLES ; b++ ) { + size = newShader->stages[i]->bundle[b].numTexMods * sizeof( texModInfo_t ); + newShader->stages[i]->bundle[b].texMods = ri.Hunk_Alloc( size, h_low ); + Com_Memcpy( newShader->stages[i]->bundle[b].texMods, stages[i].bundle[b].texMods, size ); + } + } + + SortNewShader(); + + hash = generateHashValue(newShader->name, FILE_HASH_SIZE); + newShader->next = hashTable[hash]; + hashTable[hash] = newShader; + + return newShader; +} + +/* +================= +VertexLightingCollapse + +If vertex lighting is enabled, only render a single +pass, trying to guess which is the correct one to best aproximate +what it is supposed to look like. +================= +*/ +static void VertexLightingCollapse( void ) { + int stage; + shaderStage_t *bestStage; + int bestImageRank; + int rank; + + // if we aren't opaque, just use the first pass + if ( shader.sort == SS_OPAQUE ) { + + // pick the best texture for the single pass + bestStage = &stages[0]; + bestImageRank = -999999; + + for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + rank = 0; + + if ( pStage->bundle[0].isLightmap ) { + rank -= 100; + } + if ( pStage->bundle[0].tcGen != TCGEN_TEXTURE ) { + rank -= 5; + } + if ( pStage->bundle[0].numTexMods ) { + rank -= 5; + } + if ( pStage->rgbGen != CGEN_IDENTITY && pStage->rgbGen != CGEN_IDENTITY_LIGHTING ) { + rank -= 3; + } + + if ( rank > bestImageRank ) { + bestImageRank = rank; + bestStage = pStage; + } + } + + stages[0].bundle[0] = bestStage->bundle[0]; + stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + stages[0].stateBits |= GLS_DEPTHMASK_TRUE; + if ( shader.lightmapIndex == LIGHTMAP_NONE ) { + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + } else { + stages[0].rgbGen = CGEN_EXACT_VERTEX; + } + stages[0].alphaGen = AGEN_SKIP; + } else { + // don't use a lightmap (tesla coils) + if ( stages[0].bundle[0].isLightmap ) { + stages[0] = stages[1]; + } + + // if we were in a cross-fade cgen, hack it to normal + if ( stages[0].rgbGen == CGEN_ONE_MINUS_ENTITY || stages[1].rgbGen == CGEN_ONE_MINUS_ENTITY ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_SAWTOOTH ) + && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_INVERSE_SAWTOOTH ) ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_INVERSE_SAWTOOTH ) + && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_SAWTOOTH ) ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + } + + for ( stage = 1; stage < MAX_SHADER_STAGES; stage++ ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + + Com_Memset( pStage, 0, sizeof( *pStage ) ); + } +} + +/* +========================= +FinishShader + +Returns a freshly allocated shader with all the needed info +from the current global working shader +========================= +*/ +static shader_t *FinishShader( void ) { + int stage; + qboolean hasLightmapStage; + qboolean vertexLightmap; + + hasLightmapStage = qfalse; + vertexLightmap = qfalse; + + // + // set sky stuff appropriate + // + if ( shader.isSky ) { + shader.sort = SS_ENVIRONMENT; + } + + // + // set polygon offset + // + if ( shader.polygonOffset && !shader.sort ) { + shader.sort = SS_DECAL; + } + + // + // set appropriate stage information + // + for ( stage = 0; stage < MAX_SHADER_STAGES; ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + + // check for a missing texture + if ( !pStage->bundle[0].image[0] ) { + ri.Printf( PRINT_WARNING, "Shader %s has a stage with no image\n", shader.name ); + pStage->active = qfalse; + stage++; + continue; + } + + // + // ditch this stage if it's detail and detail textures are disabled + // + if ( pStage->isDetail && !r_detailTextures->integer ) + { + int index; + + for(index = stage + 1; index < MAX_SHADER_STAGES; index++) + { + if(!stages[index].active) + break; + } + + if(index < MAX_SHADER_STAGES) + memmove(pStage, pStage + 1, sizeof(*pStage) * (index - stage)); + else + { + if(stage + 1 < MAX_SHADER_STAGES) + memmove(pStage, pStage + 1, sizeof(*pStage) * (index - stage - 1)); + + Com_Memset(&stages[index - 1], 0, sizeof(*stages)); + } + + continue; + } + + // + // default texture coordinate generation + // + if ( pStage->bundle[0].isLightmap ) { + if ( pStage->bundle[0].tcGen == TCGEN_BAD ) { + pStage->bundle[0].tcGen = TCGEN_LIGHTMAP; + } + hasLightmapStage = qtrue; + } else { + if ( pStage->bundle[0].tcGen == TCGEN_BAD ) { + pStage->bundle[0].tcGen = TCGEN_TEXTURE; + } + } + + + // not a true lightmap but we want to leave existing + // behaviour in place and not print out a warning + //if (pStage->rgbGen == CGEN_VERTEX) { + // vertexLightmap = qtrue; + //} + + + + // + // determine sort order and fog color adjustment + // + if ( ( pStage->stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) && + ( stages[0].stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) ) { + int blendSrcBits = pStage->stateBits & GLS_SRCBLEND_BITS; + int blendDstBits = pStage->stateBits & GLS_DSTBLEND_BITS; + + // fog color adjustment only works for blend modes that have a contribution + // that aproaches 0 as the modulate values aproach 0 -- + // GL_ONE, GL_ONE + // GL_ZERO, GL_ONE_MINUS_SRC_COLOR + // GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA + + // modulate, additive + if ( ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE ) ) || + ( ( blendSrcBits == GLS_SRCBLEND_ZERO ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR ) ) ) { + pStage->adjustColorsForFog = ACFF_MODULATE_RGB; + } + // strict blend + else if ( ( blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) ) + { + pStage->adjustColorsForFog = ACFF_MODULATE_ALPHA; + } + // premultiplied alpha + else if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) ) + { + pStage->adjustColorsForFog = ACFF_MODULATE_RGBA; + } else { + // we can't adjust this one correctly, so it won't be exactly correct in fog + } + + // don't screw with sort order if this is a portal or environment + if ( !shader.sort ) { + // see through item, like a grill or grate + if ( pStage->stateBits & GLS_DEPTHMASK_TRUE ) { + shader.sort = SS_SEE_THROUGH; + } else { + shader.sort = SS_BLEND0; + } + } + } + + stage++; + } + + // there are times when you will need to manually apply a sort to + // opaque alpha tested shaders that have later blend passes + if ( !shader.sort ) { + shader.sort = SS_OPAQUE; + } + + // + // if we are in r_vertexLight mode, never use a lightmap texture + // + if ( stage > 1 && ( (r_vertexLight->integer && !r_uiFullScreen->integer) || glConfig.hardwareType == GLHW_PERMEDIA2 ) ) { + VertexLightingCollapse(); + stage = 1; + hasLightmapStage = qfalse; + } + + // + // look for multitexture potential + // + stage = CollapseStagesToGLSL(); + + if ( shader.lightmapIndex >= 0 && !hasLightmapStage ) { + if (vertexLightmap) { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has VERTEX forced lightmap!\n", shader.name ); + } else { + ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has lightmap but no lightmap stage!\n", shader.name ); + // Don't set this, it will just add duplicate shaders to the hash + //shader.lightmapIndex = LIGHTMAP_NONE; + } + } + + + // + // compute number of passes + // + shader.numUnfoggedPasses = stage; + + // fogonly shaders don't have any normal passes + if (stage == 0 && !shader.isSky) + shader.sort = SS_FOG; + + // determine which stage iterator function is appropriate + ComputeStageIteratorFunc(); + + // determine which vertex attributes this shader needs + ComputeVertexAttribs(); + + return GeneratePermanentShader(); +} + +//======================================================================================== + +/* +==================== +FindShaderInShaderText + +Scans the combined text description of all the shader files for +the given shader name. + +return NULL if not found + +If found, it will return a valid shader +===================== +*/ +static char *FindShaderInShaderText( const char *shadername ) { + + char *token, *p; + + int i, hash; + + hash = generateHashValue(shadername, MAX_SHADERTEXT_HASH); + + if(shaderTextHashTable[hash]) + { + for (i = 0; shaderTextHashTable[hash][i]; i++) + { + p = shaderTextHashTable[hash][i]; + token = COM_ParseExt(&p, qtrue); + + if(!Q_stricmp(token, shadername)) + return p; + } + } + + p = s_shaderText; + + if ( !p ) { + return NULL; + } + + // look for label + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + if ( !Q_stricmp( token, shadername ) ) { + return p; + } + else { + // skip the definition + SkipBracedSection( &p ); + } + } + + return NULL; +} + + +/* +================== +R_FindShaderByName + +Will always return a valid shader, but it might be the +default shader if the real one can't be found. +================== +*/ +shader_t *R_FindShaderByName( const char *name ) { + char strippedName[MAX_QPATH]; + int hash; + shader_t *sh; + + if ( (name==NULL) || (name[0] == 0) ) { + return tr.defaultShader; + } + + COM_StripExtension(name, strippedName, sizeof(strippedName)); + + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + + // + // see if the shader is already loaded + // + for (sh=hashTable[hash]; sh; sh=sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if (Q_stricmp(sh->name, strippedName) == 0) { + // match found + return sh; + } + } + + return tr.defaultShader; +} + + +/* +=============== +R_FindShader + +Will always return a valid shader, but it might be the +default shader if the real one can't be found. + +In the interest of not requiring an explicit shader text entry to +be defined for every single image used in the game, three default +shader behaviors can be auto-created for any image: + +If lightmapIndex == LIGHTMAP_NONE, then the image will have +dynamic diffuse lighting applied to it, as apropriate for most +entity skin surfaces. + +If lightmapIndex == LIGHTMAP_2D, then the image will be used +for 2D rendering unless an explicit shader is found + +If lightmapIndex == LIGHTMAP_BY_VERTEX, then the image will use +the vertex rgba modulate values, as apropriate for misc_model +pre-lit surfaces. + +Other lightmapIndex values will have a lightmap stage created +and src*dest blending applied with the texture, as apropriate for +most world construction surfaces. + +=============== +*/ +shader_t *R_FindShader( const char *name, int lightmapIndex, qboolean mipRawImage ) { + char strippedName[MAX_QPATH]; + int i, hash; + char *shaderText; + image_t *image; + shader_t *sh; + + if ( name[0] == 0 ) { + return tr.defaultShader; + } + + // use (fullbright) vertex lighting if the bsp file doesn't have + // lightmaps + if ( lightmapIndex >= 0 && lightmapIndex >= tr.numLightmaps ) { + lightmapIndex = LIGHTMAP_BY_VERTEX; + } else if ( lightmapIndex < LIGHTMAP_2D ) { + // negative lightmap indexes cause stray pointers (think tr.lightmaps[lightmapIndex]) + ri.Printf( PRINT_WARNING, "WARNING: shader '%s' has invalid lightmap index of %d\n", name, lightmapIndex ); + lightmapIndex = LIGHTMAP_BY_VERTEX; + } + + COM_StripExtension(name, strippedName, sizeof(strippedName)); + + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + + // + // see if the shader is already loaded + // + for (sh = hashTable[hash]; sh; sh = sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if ( (sh->lightmapIndex == lightmapIndex || sh->defaultShader) && + !Q_stricmp(sh->name, strippedName)) { + // match found + return sh; + } + } + + // make sure the render thread is stopped, because we are probably + // going to have to upload an image + if (r_smp->integer) { + R_SyncRenderThread(); + } + + // clear the global shader + Com_Memset( &shader, 0, sizeof( shader ) ); + Com_Memset( &stages, 0, sizeof( stages ) ); + Q_strncpyz(shader.name, strippedName, sizeof(shader.name)); + shader.lightmapIndex = lightmapIndex; + for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) { + stages[i].bundle[0].texMods = texMods[i]; + } + + // + // attempt to define shader from an explicit parameter file + // + shaderText = FindShaderInShaderText( strippedName ); + if ( shaderText ) { + // enable this when building a pak file to get a global list + // of all explicit shaders + if ( r_printShaders->integer ) { + ri.Printf( PRINT_ALL, "*SHADER* %s\n", name ); + } + + if ( !ParseShader( &shaderText ) ) { + // had errors, so use default shader + shader.defaultShader = qtrue; + } + sh = FinishShader(); + return sh; + } + + + // + // if not defined in the in-memory shader descriptions, + // look for a single supported image file + // + { + imgFlags_t flags; + + flags = IMGFLAG_NONE; + + if (r_srgb->integer) + flags |= IMGFLAG_SRGB; + + if (mipRawImage) + { + flags |= IMGFLAG_MIPMAP | IMGFLAG_PICMIP; + + if (r_genNormalMaps->integer) + flags |= IMGFLAG_GENNORMALMAP; + } + else + { + flags |= IMGFLAG_CLAMPTOEDGE; + } + + image = R_FindImageFile( name, IMGTYPE_COLORALPHA, flags ); + if ( !image ) { + ri.Printf( PRINT_DEVELOPER, "Couldn't find image file for shader %s\n", name ); + shader.defaultShader = qtrue; + return FinishShader(); + } + } + + // + // create the default shading commands + // + if ( shader.lightmapIndex == LIGHTMAP_NONE ) { + // dynamic colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) { + // explicit colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_EXACT_VERTEX; + stages[0].alphaGen = AGEN_SKIP; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_2D ) { + // GUI elements + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_VERTEX; + stages[0].alphaGen = AGEN_VERTEX; + stages[0].stateBits = GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) { + // fullbright level + stages[0].bundle[0].image[0] = tr.whiteImage; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } else { + // two pass lightmap + stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex]; + stages[0].bundle[0].isLightmap = qtrue; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation + // for identitylight + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } + + return FinishShader(); +} + + +qhandle_t RE_RegisterShaderFromImage(const char *name, int lightmapIndex, image_t *image, qboolean mipRawImage) { + int i, hash; + shader_t *sh; + + hash = generateHashValue(name, FILE_HASH_SIZE); + + // probably not necessary since this function + // only gets called from tr_font.c with lightmapIndex == LIGHTMAP_2D + // but better safe than sorry. + if ( lightmapIndex >= tr.numLightmaps ) { + lightmapIndex = LIGHTMAP_WHITEIMAGE; + } + + // + // see if the shader is already loaded + // + for (sh=hashTable[hash]; sh; sh=sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if ( (sh->lightmapIndex == lightmapIndex || sh->defaultShader) && + // index by name + !Q_stricmp(sh->name, name)) { + // match found + return sh->index; + } + } + + // make sure the render thread is stopped, because we are probably + // going to have to upload an image + if (r_smp->integer) { + R_SyncRenderThread(); + } + + // clear the global shader + Com_Memset( &shader, 0, sizeof( shader ) ); + Com_Memset( &stages, 0, sizeof( stages ) ); + Q_strncpyz(shader.name, name, sizeof(shader.name)); + shader.lightmapIndex = lightmapIndex; + for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) { + stages[i].bundle[0].texMods = texMods[i]; + } + + // + // create the default shading commands + // + if ( shader.lightmapIndex == LIGHTMAP_NONE ) { + // dynamic colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) { + // explicit colors at vertexes + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_EXACT_VERTEX; + stages[0].alphaGen = AGEN_SKIP; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex == LIGHTMAP_2D ) { + // GUI elements + stages[0].bundle[0].image[0] = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_VERTEX; + stages[0].alphaGen = AGEN_VERTEX; + stages[0].stateBits = GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) { + // fullbright level + stages[0].bundle[0].image[0] = tr.whiteImage; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } else { + // two pass lightmap + stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex]; + stages[0].bundle[0].isLightmap = qtrue; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation + // for identitylight + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image[0] = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } + + sh = FinishShader(); + return sh->index; +} + + +/* +==================== +RE_RegisterShader + +This is the exported shader entry point for the rest of the system +It will always return an index that will be valid. + +This should really only be used for explicit shaders, because there is no +way to ask for different implicit lighting modes (vertex, lightmap, etc) +==================== +*/ +qhandle_t RE_RegisterShaderLightMap( const char *name, int lightmapIndex ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Printf( PRINT_ALL, "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, lightmapIndex, qtrue ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +/* +==================== +RE_RegisterShader + +This is the exported shader entry point for the rest of the system +It will always return an index that will be valid. + +This should really only be used for explicit shaders, because there is no +way to ask for different implicit lighting modes (vertex, lightmap, etc) +==================== +*/ +qhandle_t RE_RegisterShader( const char *name ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Printf( PRINT_ALL, "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, LIGHTMAP_2D, qtrue ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +/* +==================== +RE_RegisterShaderNoMip + +For menu graphics that should never be picmiped +==================== +*/ +qhandle_t RE_RegisterShaderNoMip( const char *name ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + ri.Printf( PRINT_ALL, "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, LIGHTMAP_2D, qfalse ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + +/* +==================== +R_GetShaderByHandle + +When a handle is passed in by another module, this range checks +it and returns a valid (possibly default) shader_t to be used internally. +==================== +*/ +shader_t *R_GetShaderByHandle( qhandle_t hShader ) { + if ( hShader < 0 ) { + ri.Printf( PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader ); + return tr.defaultShader; + } + if ( hShader >= tr.numShaders ) { + ri.Printf( PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader ); + return tr.defaultShader; + } + return tr.shaders[hShader]; +} + +/* +=============== +R_ShaderList_f + +Dump information on all valid shaders to the console +A second parameter will cause it to print in sorted order +=============== +*/ +void R_ShaderList_f (void) { + int i; + int count; + shader_t *shader; + + ri.Printf (PRINT_ALL, "-----------------------\n"); + + count = 0; + for ( i = 0 ; i < tr.numShaders ; i++ ) { + if ( ri.Cmd_Argc() > 1 ) { + shader = tr.sortedShaders[i]; + } else { + shader = tr.shaders[i]; + } + + ri.Printf( PRINT_ALL, "%i ", shader->numUnfoggedPasses ); + + if (shader->lightmapIndex >= 0 ) { + ri.Printf (PRINT_ALL, "L "); + } else { + ri.Printf (PRINT_ALL, " "); + } + if ( shader->multitextureEnv == GL_ADD ) { + ri.Printf( PRINT_ALL, "MT(a) " ); + } else if ( shader->multitextureEnv == GL_MODULATE ) { + ri.Printf( PRINT_ALL, "MT(m) " ); + } else if ( shader->multitextureEnv == GL_DECAL ) { + ri.Printf( PRINT_ALL, "MT(d) " ); + } else { + ri.Printf( PRINT_ALL, " " ); + } + if ( shader->explicitlyDefined ) { + ri.Printf( PRINT_ALL, "E " ); + } else { + ri.Printf( PRINT_ALL, " " ); + } + + if ( shader->optimalStageIteratorFunc == RB_StageIteratorGeneric ) { + ri.Printf( PRINT_ALL, "gen " ); + } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorSky ) { + ri.Printf( PRINT_ALL, "sky " ); + } else { + ri.Printf( PRINT_ALL, " " ); + } + + if ( shader->defaultShader ) { + ri.Printf (PRINT_ALL, ": %s (DEFAULTED)\n", shader->name); + } else { + ri.Printf (PRINT_ALL, ": %s\n", shader->name); + } + count++; + } + ri.Printf (PRINT_ALL, "%i total shaders\n", count); + ri.Printf (PRINT_ALL, "------------------\n"); +} + +/* +==================== +ScanAndLoadShaderFiles + +Finds and loads all .shader files, combining them into +a single large text block that can be scanned for shader names +===================== +*/ +#define MAX_SHADER_FILES 4096 +static void ScanAndLoadShaderFiles( void ) +{ + char **shaderFiles; + char *buffers[MAX_SHADER_FILES]; + char *p; + int numShaderFiles; + int i; + char *oldp, *token, *hashMem, *textEnd; + int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size; + + long sum = 0, summand; + // scan for shader files + shaderFiles = ri.FS_ListFiles( "scripts", ".shader", &numShaderFiles ); + + if ( !shaderFiles || !numShaderFiles ) + { + ri.Printf( PRINT_WARNING, "WARNING: no shader files found\n" ); + return; + } + + if ( numShaderFiles > MAX_SHADER_FILES ) { + numShaderFiles = MAX_SHADER_FILES; + } + + // load and parse shader files + for ( i = 0; i < numShaderFiles; i++ ) + { + char filename[MAX_QPATH]; + + // look for a .mtr file first + { + char *ext; + Com_sprintf( filename, sizeof( filename ), "scripts/%s", shaderFiles[i] ); + if ( (ext = strrchr(filename, '.')) ) + { + strcpy(ext, ".mtr"); + } + + if ( ri.FS_ReadFile( filename, NULL ) <= 0 ) + { + Com_sprintf( filename, sizeof( filename ), "scripts/%s", shaderFiles[i] ); + } + } + + ri.Printf( PRINT_DEVELOPER, "...loading '%s'\n", filename ); + summand = ri.FS_ReadFile( filename, (void **)&buffers[i] ); + + if ( !buffers[i] ) + ri.Error( ERR_DROP, "Couldn't load %s", filename ); + + // Do a simple check on the shader structure in that file to make sure one bad shader file cannot fuck up all other shaders. + p = buffers[i]; + while(1) + { + token = COM_ParseExt(&p, qtrue); + + if(!*token) + break; + + oldp = p; + + token = COM_ParseExt(&p, qtrue); + if(token[0] != '{' && token[1] != '\0') + { + ri.Printf(PRINT_WARNING, "WARNING: Bad shader file %s has incorrect syntax.\n", filename); + ri.FS_FreeFile(buffers[i]); + buffers[i] = NULL; + break; + } + + SkipBracedSection(&oldp); + p = oldp; + } + + + if (buffers[i]) + sum += summand; + } + + // build single large buffer + s_shaderText = ri.Hunk_Alloc( sum + numShaderFiles*2, h_low ); + s_shaderText[ 0 ] = '\0'; + textEnd = s_shaderText; + + // free in reverse order, so the temp files are all dumped + for ( i = numShaderFiles - 1; i >= 0 ; i-- ) + { + if ( !buffers[i] ) + continue; + + strcat( textEnd, buffers[i] ); + strcat( textEnd, "\n" ); + textEnd += strlen( textEnd ); + ri.FS_FreeFile( buffers[i] ); + } + + COM_Compress( s_shaderText ); + + // free up memory + ri.FS_FreeFileList( shaderFiles ); + + Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); + size = 0; + + p = s_shaderText; + // look for shader names + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + hash = generateHashValue(token, MAX_SHADERTEXT_HASH); + shaderTextHashTableSizes[hash]++; + size++; + SkipBracedSection(&p); + } + + size += MAX_SHADERTEXT_HASH; + + hashMem = ri.Hunk_Alloc( size * sizeof(char *), h_low ); + + for (i = 0; i < MAX_SHADERTEXT_HASH; i++) { + shaderTextHashTable[i] = (char **) hashMem; + hashMem = ((char *) hashMem) + ((shaderTextHashTableSizes[i] + 1) * sizeof(char *)); + } + + Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); + + p = s_shaderText; + // look for shader names + while ( 1 ) { + oldp = p; + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + hash = generateHashValue(token, MAX_SHADERTEXT_HASH); + shaderTextHashTable[hash][shaderTextHashTableSizes[hash]++] = oldp; + + SkipBracedSection(&p); + } + + return; + +} + + +/* +==================== +CreateInternalShaders +==================== +*/ +static void CreateInternalShaders( void ) { + tr.numShaders = 0; + + // init the default shader + Com_Memset( &shader, 0, sizeof( shader ) ); + Com_Memset( &stages, 0, sizeof( stages ) ); + + Q_strncpyz( shader.name, "", sizeof( shader.name ) ); + + shader.lightmapIndex = LIGHTMAP_NONE; + stages[0].bundle[0].image[0] = tr.defaultImage; + stages[0].active = qtrue; + stages[0].stateBits = GLS_DEFAULT; + tr.defaultShader = FinishShader(); + + // shadow shader is just a marker + Q_strncpyz( shader.name, "", sizeof( shader.name ) ); + shader.sort = SS_STENCIL_SHADOW; + tr.shadowShader = FinishShader(); +} + +static void CreateExternalShaders( void ) { + tr.projectionShadowShader = R_FindShader( "projectionShadow", LIGHTMAP_NONE, qtrue ); + tr.flareShader = R_FindShader( "flareShader", LIGHTMAP_NONE, qtrue ); + + // Hack to make fogging work correctly on flares. Fog colors are calculated + // in tr_flare.c already. + if(!tr.flareShader->defaultShader) + { + int index; + + for(index = 0; index < tr.flareShader->numUnfoggedPasses; index++) + { + tr.flareShader->stages[index]->adjustColorsForFog = ACFF_NONE; + tr.flareShader->stages[index]->stateBits |= GLS_DEPTHTEST_DISABLE; + } + } + + tr.sunShader = R_FindShader( "sun", LIGHTMAP_NONE, qtrue ); +} + +/* +================== +R_InitShaders +================== +*/ +void R_InitShaders( void ) { + ri.Printf( PRINT_ALL, "Initializing Shaders\n" ); + + Com_Memset(hashTable, 0, sizeof(hashTable)); + + CreateInternalShaders(); + + ScanAndLoadShaderFiles(); + + CreateExternalShaders(); +} diff --git a/src/rend2/tr_shadows.c b/src/rend2/tr_shadows.c new file mode 100644 index 00000000..f412b00d --- /dev/null +++ b/src/rend2/tr_shadows.c @@ -0,0 +1,343 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "tr_local.h" + + +/* + + for a projection shadow: + + point[x] += light vector * ( z - shadow plane ) + point[y] += + point[z] = shadow plane + + 1 0 light[x] / light[z] + +*/ + +typedef struct { + int i2; + int facing; +} edgeDef_t; + +#define MAX_EDGE_DEFS 32 + +static edgeDef_t edgeDefs[SHADER_MAX_VERTEXES][MAX_EDGE_DEFS]; +static int numEdgeDefs[SHADER_MAX_VERTEXES]; +static int facing[SHADER_MAX_INDEXES/3]; + +void R_AddEdgeDef( int i1, int i2, int facing ) { + int c; + + c = numEdgeDefs[ i1 ]; + if ( c == MAX_EDGE_DEFS ) { + return; // overflow + } + edgeDefs[ i1 ][ c ].i2 = i2; + edgeDefs[ i1 ][ c ].facing = facing; + + numEdgeDefs[ i1 ]++; +} + +void R_RenderShadowEdges( void ) { + int i; + +#if 0 + int numTris; + + // dumb way -- render every triangle's edges + numTris = tess.numIndexes / 3; + + for ( i = 0 ; i < numTris ; i++ ) { + int i1, i2, i3; + + if ( !facing[i] ) { + continue; + } + + i1 = tess.indexes[ i*3 + 0 ]; + i2 = tess.indexes[ i*3 + 1 ]; + i3 = tess.indexes[ i*3 + 2 ]; + + qglBegin( GL_TRIANGLE_STRIP ); + qglVertex3fv( tess.xyz[ i1 ] ); + qglVertex3fv( tess.xyz[ i1 + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i2 ] ); + qglVertex3fv( tess.xyz[ i2 + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i3 ] ); + qglVertex3fv( tess.xyz[ i3 + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i1 ] ); + qglVertex3fv( tess.xyz[ i1 + tess.numVertexes ] ); + qglEnd(); + } +#else + int c, c2; + int j, k; + int i2; + int c_edges, c_rejected; + int hit[2]; + + // 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, + // but lots of models have dangling edges or overfanned edges + c_edges = 0; + c_rejected = 0; + + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + c = numEdgeDefs[ i ]; + for ( j = 0 ; j < c ; j++ ) { + if ( !edgeDefs[ i ][ j ].facing ) { + continue; + } + + hit[0] = 0; + hit[1] = 0; + + i2 = edgeDefs[ i ][ j ].i2; + c2 = numEdgeDefs[ i2 ]; + for ( k = 0 ; k < c2 ; k++ ) { + if ( edgeDefs[ i2 ][ k ].i2 == i ) { + hit[ edgeDefs[ i2 ][ k ].facing ]++; + } + } + + // if it doesn't share the edge with another front facing + // triangle, it is a sil edge + if ( hit[ 1 ] == 0 ) { + qglBegin( GL_TRIANGLE_STRIP ); + qglVertex3fv( tess.xyz[ i ] ); + qglVertex3fv( tess.xyz[ i + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i2 ] ); + qglVertex3fv( tess.xyz[ i2 + tess.numVertexes ] ); + qglEnd(); + c_edges++; + } else { + c_rejected++; + } + } + } +#endif +} + +/* +================= +RB_ShadowTessEnd + +triangleFromEdge[ v1 ][ v2 ] + + + set triangle from edge( v1, v2, tri ) + if ( facing[ triangleFromEdge[ v1 ][ v2 ] ] && !facing[ triangleFromEdge[ v2 ][ v1 ] ) { + } +================= +*/ +void RB_ShadowTessEnd( void ) { + int i; + int numTris; + vec3_t lightDir; + GLboolean rgba[4]; + + // we can only do this if we have enough space in the vertex buffers + if ( tess.numVertexes >= SHADER_MAX_VERTEXES / 2 ) { + return; + } + + if ( glConfig.stencilBits < 4 ) { + return; + } + + VectorCopy( backEnd.currentEntity->lightDir, lightDir ); + + // project vertexes away from light direction + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + VectorMA( tess.xyz[i], -512, lightDir, tess.xyz[i+tess.numVertexes] ); + } + + // decide which triangles face the light + Com_Memset( numEdgeDefs, 0, 4 * tess.numVertexes ); + + numTris = tess.numIndexes / 3; + for ( i = 0 ; i < numTris ; i++ ) { + int i1, i2, i3; + vec3_t d1, d2, normal; + float *v1, *v2, *v3; + float d; + + i1 = tess.indexes[ i*3 + 0 ]; + i2 = tess.indexes[ i*3 + 1 ]; + i3 = tess.indexes[ i*3 + 2 ]; + + v1 = tess.xyz[ i1 ]; + v2 = tess.xyz[ i2 ]; + v3 = tess.xyz[ i3 ]; + + VectorSubtract( v2, v1, d1 ); + VectorSubtract( v3, v1, d2 ); + CrossProduct( d1, d2, normal ); + + d = DotProduct( normal, lightDir ); + if ( d > 0 ) { + facing[ i ] = 1; + } else { + facing[ i ] = 0; + } + + // create the edges + R_AddEdgeDef( i1, i2, facing[ i ] ); + R_AddEdgeDef( i2, i3, facing[ i ] ); + R_AddEdgeDef( i3, i1, facing[ i ] ); + } + + // draw the silhouette edges + + GL_Bind( tr.whiteImage ); + qglEnable( GL_CULL_FACE ); + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + qglColor3f( 0.2f, 0.2f, 0.2f ); + + // don't write to the color buffer + qglGetBooleanv(GL_COLOR_WRITEMASK, rgba); + qglColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); + + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_ALWAYS, 1, 255 ); + + // mirrors have the culling order reversed + if ( backEnd.viewParms.isMirror ) { + qglCullFace( GL_FRONT ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); + + R_RenderShadowEdges(); + + qglCullFace( GL_BACK ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR ); + + R_RenderShadowEdges(); + } else { + qglCullFace( GL_BACK ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); + + R_RenderShadowEdges(); + + qglCullFace( GL_FRONT ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR ); + + R_RenderShadowEdges(); + } + + + // reenable writing to the color buffer + qglColorMask(rgba[0], rgba[1], rgba[2], rgba[3]); +} + + +/* +================= +RB_ShadowFinish + +Darken everything that is is a shadow volume. +We have to delay this until everything has been shadowed, +because otherwise shadows from different body parts would +overlap and double darken. +================= +*/ +void RB_ShadowFinish( void ) { + if ( r_shadows->integer != 2 ) { + return; + } + if ( glConfig.stencilBits < 4 ) { + return; + } + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_NOTEQUAL, 0, 255 ); + + qglDisable (GL_CLIP_PLANE0); + qglDisable (GL_CULL_FACE); + + GL_Bind( tr.whiteImage ); + + qglLoadIdentity (); + + 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 ); + + qglBegin( GL_QUADS ); + qglVertex3f( -100, 100, -10 ); + qglVertex3f( 100, 100, -10 ); + qglVertex3f( 100, -100, -10 ); + qglVertex3f( -100, -100, -10 ); + qglEnd (); + + qglColor4f(1,1,1,1); + qglDisable( GL_STENCIL_TEST ); +} + + +/* +================= +RB_ProjectionShadowDeform + +================= +*/ +void RB_ProjectionShadowDeform( void ) { + float *xyz; + int i; + float h; + vec3_t ground; + vec3_t light; + float groundDist; + float d; + vec3_t lightDir; + + xyz = ( float * ) tess.xyz; + + ground[0] = backEnd.or.axis[0][2]; + ground[1] = backEnd.or.axis[1][2]; + ground[2] = backEnd.or.axis[2][2]; + + groundDist = backEnd.or.origin[2] - backEnd.currentEntity->e.shadowPlane; + + VectorCopy( backEnd.currentEntity->lightDir, lightDir ); + d = DotProduct( lightDir, ground ); + // don't let the shadows get too long or go negative + if ( d < 0.5 ) { + VectorMA( lightDir, (0.5 - d), ground, lightDir ); + d = DotProduct( lightDir, ground ); + } + d = 1.0 / d; + + light[0] = lightDir[0] * d; + light[1] = lightDir[1] * d; + light[2] = lightDir[2] * d; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { + h = DotProduct( xyz, ground ) + groundDist; + + xyz[0] -= light[0] * h; + xyz[1] -= light[1] * h; + xyz[2] -= light[2] * h; + } +} diff --git a/src/rend2/tr_sky.c b/src/rend2/tr_sky.c new file mode 100644 index 00000000..25022703 --- /dev/null +++ b/src/rend2/tr_sky.c @@ -0,0 +1,955 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_sky.c +#include "tr_local.h" + +#define SKY_SUBDIVISIONS 8 +#define HALF_SKY_SUBDIVISIONS (SKY_SUBDIVISIONS/2) + +static float s_cloudTexCoords[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2]; +static float s_cloudTexP[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1]; + +/* +=================================================================================== + +POLYGON TO BOX SIDE PROJECTION + +=================================================================================== +*/ + +static vec3_t sky_clip[6] = +{ + {1,1,0}, + {1,-1,0}, + {0,-1,1}, + {0,1,1}, + {1,0,1}, + {-1,0,1} +}; + +static float sky_mins[2][6], sky_maxs[2][6]; +static float sky_min, sky_max; + +/* +================ +AddSkyPolygon +================ +*/ +static void AddSkyPolygon (int nump, vec3_t vecs) +{ + int i,j; + vec3_t v, av; + float s, t, dv; + int axis; + float *vp; + // s = [0]/[2], t = [1]/[2] + static int vec_to_st[6][3] = + { + {-2,3,1}, + {2,3,-1}, + + {1,3,2}, + {-1,3,-2}, + + {-2,-1,3}, + {-2,1,-3} + + // {-1,2,3}, + // {1,2,-3} + }; + + // decide which face it maps to + VectorCopy (vec3_origin, v); + for (i=0, vp=vecs ; i av[1] && av[0] > av[2]) + { + if (v[0] < 0) + axis = 1; + else + axis = 0; + } + else if (av[1] > av[2] && av[1] > av[0]) + { + if (v[1] < 0) + axis = 3; + else + axis = 2; + } + else + { + if (v[2] < 0) + axis = 5; + else + axis = 4; + } + + // project new texture coords + for (i=0 ; i 0) + dv = vecs[j - 1]; + else + dv = -vecs[-j - 1]; + if (dv < 0.001) + continue; // don't divide by zero + j = vec_to_st[axis][0]; + if (j < 0) + s = -vecs[-j -1] / dv; + else + s = vecs[j-1] / dv; + j = vec_to_st[axis][1]; + if (j < 0) + t = -vecs[-j -1] / dv; + else + t = vecs[j-1] / dv; + + if (s < sky_mins[0][axis]) + sky_mins[0][axis] = s; + if (t < sky_mins[1][axis]) + sky_mins[1][axis] = t; + if (s > sky_maxs[0][axis]) + sky_maxs[0][axis] = s; + if (t > sky_maxs[1][axis]) + sky_maxs[1][axis] = t; + } +} + +#define ON_EPSILON 0.1f // point on plane side epsilon +#define MAX_CLIP_VERTS 64 +/* +================ +ClipSkyPolygon +================ +*/ +static void ClipSkyPolygon (int nump, vec3_t vecs, int stage) +{ + float *norm; + float *v; + qboolean front, back; + float d, e; + float dists[MAX_CLIP_VERTS]; + int sides[MAX_CLIP_VERTS]; + vec3_t newv[2][MAX_CLIP_VERTS]; + int newc[2]; + int i, j; + + if (nump > MAX_CLIP_VERTS-2) + ri.Error (ERR_DROP, "ClipSkyPolygon: MAX_CLIP_VERTS"); + if (stage == 6) + { // fully clipped, so draw it + AddSkyPolygon (nump, vecs); + return; + } + + front = back = qfalse; + norm = sky_clip[stage]; + for (i=0, v = vecs ; i ON_EPSILON) + { + front = qtrue; + sides[i] = SIDE_FRONT; + } + else if (d < -ON_EPSILON) + { + back = qtrue; + sides[i] = SIDE_BACK; + } + else + sides[i] = SIDE_ON; + dists[i] = d; + } + + if (!front || !back) + { // not clipped + ClipSkyPolygon (nump, vecs, stage+1); + return; + } + + // clip it + sides[i] = sides[0]; + dists[i] = dists[0]; + VectorCopy (vecs, (vecs+(i*3)) ); + newc[0] = newc[1] = 0; + + for (i=0, v = vecs ; inumIndexes; i += 3 ) + { + for (j = 0 ; j < 3 ; j++) + { + VectorSubtract( input->xyz[input->indexes[i+j]], + backEnd.viewParms.or.origin, + p[j] ); + } + ClipSkyPolygon( 3, p[0], 0 ); + } +} + +/* +=================================================================================== + +CLOUD VERTEX GENERATION + +=================================================================================== +*/ + +/* +** MakeSkyVec +** +** Parms: s, t range from -1 to 1 +*/ +static void MakeSkyVec( float s, float t, int axis, float outSt[2], vec3_t outXYZ ) +{ + // 1 = s, 2 = t, 3 = 2048 + static int st_to_vec[6][3] = + { + {3,-1,2}, + {-3,1,2}, + + {1,3,2}, + {-1,-3,2}, + + {-2,-1,3}, // 0 degrees yaw, look straight up + {2,-1,-3} // look straight down + }; + + vec3_t b; + int j, k; + float boxSize; + + boxSize = backEnd.viewParms.zFar / 1.75; // div sqrt(3) + b[0] = s*boxSize; + b[1] = t*boxSize; + b[2] = boxSize; + + for (j=0 ; j<3 ; j++) + { + k = st_to_vec[axis][j]; + if (k < 0) + { + outXYZ[j] = -b[-k - 1]; + } + else + { + outXYZ[j] = b[k - 1]; + } + } + + // avoid bilerp seam + s = (s+1)*0.5; + t = (t+1)*0.5; + if (s < sky_min) + { + s = sky_min; + } + else if (s > sky_max) + { + s = sky_max; + } + + if (t < sky_min) + { + t = sky_min; + } + else if (t > sky_max) + { + t = sky_max; + } + + t = 1.0 - t; + + + if ( outSt ) + { + outSt[0] = s; + outSt[1] = t; + } +} + +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]; + +static void DrawSkySide( struct image_s *image, const int mins[2], const int maxs[2] ) +{ + int s, t; + int firstVertex = tess.numVertexes; + //int firstIndex = tess.numIndexes; + vec4_t color; + + //tess.numVertexes = 0; + //tess.numIndexes = 0; + tess.firstIndex = tess.numIndexes; + + GL_Bind( image ); + GL_Cull( CT_TWO_SIDED ); + + 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++ ) + { + tess.xyz[tess.numVertexes][0] = s_skyPoints[t][s][0]; + tess.xyz[tess.numVertexes][1] = s_skyPoints[t][s][1]; + tess.xyz[tess.numVertexes][2] = s_skyPoints[t][s][2]; + tess.xyz[tess.numVertexes][3] = 1.0; + + 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 DrawSkySideVBO()\n"); + } + } + } + + for ( t = 0; t < maxs[1] - mins[1]; t++ ) + { + for ( s = 0; s < maxs[0] - mins[0]; s++ ) + { + if (tess.numIndexes + 6 >= SHADER_MAX_INDEXES) + { + ri.Error(ERR_DROP, "SHADER_MAX_INDEXES hit in DrawSkySideVBO()\n"); + } + + tess.indexes[tess.numIndexes++] = s + t * (maxs[0] - mins[0] + 1) + firstVertex; + tess.indexes[tess.numIndexes++] = s + (t + 1) * (maxs[0] - mins[0] + 1) + firstVertex; + tess.indexes[tess.numIndexes++] = (s + 1) + t * (maxs[0] - mins[0] + 1) + firstVertex; + + tess.indexes[tess.numIndexes++] = (s + 1) + t * (maxs[0] - mins[0] + 1) + firstVertex; + tess.indexes[tess.numIndexes++] = s + (t + 1) * (maxs[0] - mins[0] + 1) + firstVertex; + tess.indexes[tess.numIndexes++] = (s + 1) + (t + 1) * (maxs[0] - mins[0] + 1) + firstVertex; + } + } + + // FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function + RB_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD); +/* + { + shaderProgram_t *sp = &tr.textureColorShader; + + GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD); + GLSL_BindProgram(sp); + + GLSL_SetUniformMatrix16(sp, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + + color[0] = + color[1] = + color[2] = tr.identityLight; + color[3] = 1.0f; + GLSL_SetUniformVec4(sp, TEXTURECOLOR_UNIFORM_COLOR, color); + } +*/ + { + shaderProgram_t *sp = &tr.lightallShader[0]; + matrix_t matrix; + + GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD); + GLSL_BindProgram(sp); + + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + + color[0] = + color[1] = + color[2] = tr.identityLight; + color[3] = 1.0f; + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_BASECOLOR, color); + + color[0] = + color[1] = + color[2] = + color[3] = 0.0f; + GLSL_SetUniformVec4(sp, GENERIC_UNIFORM_VERTCOLOR, color); + + Matrix16Identity(matrix); + GLSL_SetUniformMatrix16(sp, GENERIC_UNIFORM_DIFFUSETEXMATRIX, matrix); + } + + R_DrawElementsVBO(tess.numIndexes - tess.firstIndex, tess.firstIndex); + + //qglDrawElements(GL_TRIANGLES, tess.numIndexes - tess.firstIndex, GL_INDEX_TYPE, BUFFER_OFFSET(tess.firstIndex * sizeof(GL_INDEX_TYPE))); + + //R_BindNullVBO(); + //R_BindNullIBO(); + + tess.numIndexes = tess.firstIndex; + tess.numVertexes = firstVertex; + tess.firstIndex = 0; +} + +static void DrawSkyBox( shader_t *shader ) +{ + int i; + + sky_min = 0; + sky_max = 1; + + Com_Memset( s_skyTexCoords, 0, sizeof( s_skyTexCoords ) ); + + for (i=0 ; i<6 ; i++) + { + int sky_mins_subd[2], sky_maxs_subd[2]; + int s, t; + + sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + + if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || + ( sky_mins[1][i] >= sky_maxs[1][i] ) ) + { + continue; + } + + sky_mins_subd[0] = sky_mins[0][i] * HALF_SKY_SUBDIVISIONS; + sky_mins_subd[1] = sky_mins[1][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[0] = sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[1] = sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS; + + if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_mins_subd[1] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; + + if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_maxs_subd[1] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; + + // + // iterate through the subdivisions + // + for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + s_skyTexCoords[t][s], + s_skyPoints[t][s] ); + } + } + + DrawSkySide( shader->sky.outerbox[sky_texorder[i]], + sky_mins_subd, + sky_maxs_subd ); + } + +} + +static void FillCloudySkySide( const int mins[2], const int maxs[2], qboolean addIndexes ) +{ + int s, t; + int vertexStart = tess.numVertexes; + int tHeight, sWidth; + + 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++ ) + { + for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + VectorAdd( s_skyPoints[t][s], backEnd.viewParms.or.origin, 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 FillCloudySkySide()" ); + } + } + } + + // only add indexes for one pass, otherwise it would draw multiple times for each pass + if ( addIndexes ) { + for ( t = 0; t < tHeight-1; t++ ) + { + for ( s = 0; s < sWidth-1; s++ ) + { + 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++; + } + } + } +} + +static void FillCloudBox( const shader_t *shader, int stage ) +{ + int i; + + for ( i =0; i < 6; i++ ) + { + int sky_mins_subd[2], sky_maxs_subd[2]; + int s, t; + float MIN_T; + + if ( 1 ) // FIXME? shader->sky.fullClouds ) + { + MIN_T = -HALF_SKY_SUBDIVISIONS; + + // still don't want to draw the bottom, even if fullClouds + if ( i == 5 ) + continue; + } + else + { + switch( i ) + { + case 0: + case 1: + case 2: + case 3: + MIN_T = -1; + break; + case 5: + // don't draw clouds beneath you + continue; + case 4: // top + default: + MIN_T = -HALF_SKY_SUBDIVISIONS; + break; + } + } + + sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + + if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || + ( sky_mins[1][i] >= sky_maxs[1][i] ) ) + { + continue; + } + + sky_mins_subd[0] = ri.ftol(sky_mins[0][i] * HALF_SKY_SUBDIVISIONS); + sky_mins_subd[1] = ri.ftol(sky_mins[1][i] * HALF_SKY_SUBDIVISIONS); + sky_maxs_subd[0] = ri.ftol(sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS); + sky_maxs_subd[1] = ri.ftol(sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS); + + if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_mins_subd[1] < MIN_T ) + sky_mins_subd[1] = MIN_T; + else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; + + if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_maxs_subd[1] < MIN_T ) + sky_maxs_subd[1] = MIN_T; + else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; + + // + // iterate through the subdivisions + // + for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + NULL, + s_skyPoints[t][s] ); + + s_skyTexCoords[t][s][0] = s_cloudTexCoords[i][t][s][0]; + s_skyTexCoords[t][s][1] = s_cloudTexCoords[i][t][s][1]; + } + } + + // only add indexes for first stage + FillCloudySkySide( sky_mins_subd, sky_maxs_subd, ( stage == 0 ) ); + } +} + +/* +** R_BuildCloudData +*/ +void R_BuildCloudData( shaderCommands_t *input ) +{ + int i; + shader_t *shader; + + shader = input->shader; + + assert( shader->isSky ); + + sky_min = 1.0 / 256.0f; // FIXME: not correct? + sky_max = 255.0 / 256.0f; + + // set up for drawing + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.firstIndex = 0; + + if ( shader->sky.cloudHeight ) + { + for ( i = 0; i < MAX_SHADER_STAGES; i++ ) + { + if ( !tess.xstages[i] ) { + break; + } + FillCloudBox( shader, i ); + } + } +} + +/* +** R_InitSkyTexCoords +** Called when a sky shader is parsed +*/ +#define SQR( a ) ((a)*(a)) +void R_InitSkyTexCoords( float heightCloud ) +{ + int i, s, t; + float radiusWorld = 4096; + float p; + float sRad, tRad; + vec3_t skyVec; + vec3_t v; + + // init zfar so MakeSkyVec works even though + // a world hasn't been bounded + backEnd.viewParms.zFar = 1024; + + for ( i = 0; i < 6; i++ ) + { + for ( t = 0; t <= SKY_SUBDIVISIONS; t++ ) + { + for ( s = 0; s <= SKY_SUBDIVISIONS; s++ ) + { + // compute vector from view origin to sky side integral point + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + NULL, + skyVec ); + + // compute parametric value 'p' that intersects with cloud layer + p = ( 1.0f / ( 2 * DotProduct( skyVec, skyVec ) ) ) * + ( -2 * skyVec[2] * radiusWorld + + 2 * sqrt( SQR( skyVec[2] ) * SQR( radiusWorld ) + + 2 * SQR( skyVec[0] ) * radiusWorld * heightCloud + + SQR( skyVec[0] ) * SQR( heightCloud ) + + 2 * SQR( skyVec[1] ) * radiusWorld * heightCloud + + SQR( skyVec[1] ) * SQR( heightCloud ) + + 2 * SQR( skyVec[2] ) * radiusWorld * heightCloud + + SQR( skyVec[2] ) * SQR( heightCloud ) ) ); + + s_cloudTexP[i][t][s] = p; + + // compute intersection point based on p + VectorScale( skyVec, p, v ); + v[2] += radiusWorld; + + // compute vector from world origin to intersection point 'v' + VectorNormalize( v ); + + sRad = Q_acos( v[0] ); + tRad = Q_acos( v[1] ); + + s_cloudTexCoords[i][t][s][0] = sRad; + s_cloudTexCoords[i][t][s][1] = tRad; + } + } + } +} + +//====================================================================================== + +/* +** RB_DrawSun +*/ +void RB_DrawSun( void ) { + float size; + float dist; + vec3_t origin, vec1, vec2; + vec3_t temp; + + if ( !backEnd.skyRenderedThisView ) { + return; + } + if ( !r_drawSun->integer ) { + return; + } + + //qglLoadMatrixf( backEnd.viewParms.world.modelMatrix ); + //qglTranslatef (backEnd.viewParms.or.origin[0], backEnd.viewParms.or.origin[1], backEnd.viewParms.or.origin[2]); + { + // FIXME: this could be a lot cleaner + matrix_t trans, product; + + Matrix16Translation( backEnd.viewParms.or.origin, trans ); + Matrix16Multiply( backEnd.viewParms.world.modelMatrix, trans, product ); + GL_SetModelviewMatrix( product ); + } + + dist = backEnd.viewParms.zFar / 1.75; // div sqrt(3) + size = dist * 0.4; + + VectorScale( tr.sunDirection, dist, origin ); + PerpendicularVector( vec1, tr.sunDirection ); + CrossProduct( tr.sunDirection, vec1, vec2 ); + + VectorScale( vec1, size, vec1 ); + VectorScale( vec2, size, vec2 ); + + // farthest depth range + qglDepthRange( 1.0, 1.0 ); + + // FIXME: use quad stamp + RB_BeginSurface( tr.sunShader, tess.fogNum ); + VectorCopy( origin, temp ); + VectorSubtract( temp, vec1, temp ); + VectorSubtract( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = 1.0f; + tess.vertexColors[tess.numVertexes][1] = 1.0f; + tess.vertexColors[tess.numVertexes][2] = 1.0f; + tess.numVertexes++; + + VectorCopy( origin, temp ); + VectorAdd( temp, vec1, temp ); + VectorSubtract( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = 1.0f; + tess.vertexColors[tess.numVertexes][1] = 1.0f; + tess.vertexColors[tess.numVertexes][2] = 1.0f; + tess.numVertexes++; + + VectorCopy( origin, temp ); + VectorAdd( temp, vec1, temp ); + VectorAdd( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = 1.0f; + tess.vertexColors[tess.numVertexes][1] = 1.0f; + tess.vertexColors[tess.numVertexes][2] = 1.0f; + tess.numVertexes++; + + VectorCopy( origin, temp ); + VectorSubtract( temp, vec1, temp ); + VectorAdd( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = 1.0f; + tess.vertexColors[tess.numVertexes][1] = 1.0f; + tess.vertexColors[tess.numVertexes][2] = 1.0f; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 1; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 3; + + RB_EndSurface(); + + // back to normal depth range + qglDepthRange( 0.0, 1.0 ); +} + + + + +/* +================ +RB_StageIteratorSky + +All of the visible sky triangles are in tess + +Other things could be stuck in here, like birds in the sky, etc +================ +*/ +void RB_StageIteratorSky( void ) { + if ( r_fastsky->integer ) { + return; + } + + // go through all the polygons and project them onto + // the sky box to see which blocks on each side need + // to be drawn + RB_ClipSkyPolygons( &tess ); + + // r_showsky will let all the sky blocks be drawn in + // front of everything to allow developers to see how + // much sky is getting sucked in + if ( r_showsky->integer ) { + qglDepthRange( 0.0, 0.0 ); + } else { + qglDepthRange( 1.0, 1.0 ); + } + + // draw the outer skybox + if ( tess.shader->sky.outerbox[0] && tess.shader->sky.outerbox[0] != tr.defaultImage ) { + matrix_t oldmodelview; + + GL_State( 0 ); + //qglTranslatef (backEnd.viewParms.or.origin[0], backEnd.viewParms.or.origin[1], backEnd.viewParms.or.origin[2]); + + { + // FIXME: this could be a lot cleaner + matrix_t trans, product; + + Matrix16Copy( glState.modelview, oldmodelview ); + Matrix16Translation( backEnd.viewParms.or.origin, trans ); + Matrix16Multiply( glState.modelview, trans, product ); + GL_SetModelviewMatrix( product ); + + } + + DrawSkyBox( tess.shader ); + + GL_SetModelviewMatrix( oldmodelview ); + } + + // generate the vertexes for all the clouds, which will be drawn + // by the generic shader routine + R_BuildCloudData( &tess ); + + RB_StageIteratorGeneric(); + + // draw the inner skybox + + + // back to normal depth range + qglDepthRange( 0.0, 1.0 ); + + // note that sky was drawn so we will draw a sun later + backEnd.skyRenderedThisView = qtrue; +} + + + + + diff --git a/src/rend2/tr_subs.c b/src/rend2/tr_subs.c new file mode 100644 index 00000000..6f490128 --- /dev/null +++ b/src/rend2/tr_subs.c @@ -0,0 +1,48 @@ +/* +=========================================================================== +Copyright (C) 2010 James Canete (use.less01@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_subs.c - common function replacements for modular renderer + +#include "tr_local.h" + +void QDECL Com_Printf( const char *msg, ... ) +{ + va_list argptr; + char text[1024]; + + va_start(argptr, msg); + Q_vsnprintf(text, sizeof(text), msg, argptr); + va_end(argptr); + + ri.Printf(PRINT_ALL, "%s", text); +} + +void QDECL Com_Error( int level, const char *error, ... ) +{ + va_list argptr; + char text[1024]; + + va_start(argptr, error); + Q_vsnprintf(text, sizeof(text), error, argptr); + va_end(argptr); + + ri.Error(level, "%s", text); +} diff --git a/src/rend2/tr_surface.c b/src/rend2/tr_surface.c new file mode 100644 index 00000000..061ad5bd --- /dev/null +++ b/src/rend2/tr_surface.c @@ -0,0 +1,1691 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_surf.c +#include "tr_local.h" +#if idppc_altivec && !defined(MACOS_X) +#include +#endif + +/* + + THIS ENTIRE FILE IS BACK END + +backEnd.currentEntity will be valid. + +Tess_Begin has already been called for the surface's shader. + +The modelview matrix will be set. + +It is safe to actually issue drawing commands here if you don't want to +use the shader system. +*/ + + +//============================================================================ + + +/* +============== +RB_CheckOverflow +============== +*/ +void RB_CheckOverflow( int verts, int indexes ) { + if (tess.numVertexes + verts < SHADER_MAX_VERTEXES + && tess.numIndexes + indexes < SHADER_MAX_INDEXES) { + return; + } + + RB_EndSurface(); + + if ( verts >= SHADER_MAX_VERTEXES ) { + ri.Error(ERR_DROP, "RB_CheckOverflow: verts > MAX (%d > %d)", verts, SHADER_MAX_VERTEXES ); + } + if ( indexes >= SHADER_MAX_INDEXES ) { + ri.Error(ERR_DROP, "RB_CheckOverflow: indices > MAX (%d > %d)", indexes, SHADER_MAX_INDEXES ); + } + + RB_BeginSurface(tess.shader, tess.fogNum ); +} + +void RB_CheckVBOandIBO(VBO_t *vbo, IBO_t *ibo) +{ + if (!(vbo == glState.currentVBO && ibo == glState.currentIBO) || tess.multiDrawPrimitives >= MAX_MULTIDRAW_PRIMITIVES) + { + RB_EndSurface(); + RB_BeginSurface(tess.shader, tess.fogNum); + + R_BindVBO(vbo); + R_BindIBO(ibo); + } + + if (vbo != tess.vbo && ibo != tess.ibo) + tess.useInternalVBO = qfalse; +} + + +/* +============== +RB_AddQuadStampExt +============== +*/ +void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, float color[4], float s1, float t1, float s2, float t2 ) { + vec3_t normal; + int ndx; + + RB_CHECKOVERFLOW( 4, 6 ); + + ndx = tess.numVertexes; + + // triangle indexes for a simple quad + tess.indexes[ tess.numIndexes ] = ndx; + tess.indexes[ tess.numIndexes + 1 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 2 ] = ndx + 3; + + tess.indexes[ tess.numIndexes + 3 ] = ndx + 3; + tess.indexes[ tess.numIndexes + 4 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 5 ] = ndx + 2; + + tess.xyz[ndx][0] = origin[0] + left[0] + up[0]; + tess.xyz[ndx][1] = origin[1] + left[1] + up[1]; + tess.xyz[ndx][2] = origin[2] + left[2] + up[2]; + + tess.xyz[ndx+1][0] = origin[0] - left[0] + up[0]; + tess.xyz[ndx+1][1] = origin[1] - left[1] + up[1]; + tess.xyz[ndx+1][2] = origin[2] - left[2] + up[2]; + + tess.xyz[ndx+2][0] = origin[0] - left[0] - up[0]; + tess.xyz[ndx+2][1] = origin[1] - left[1] - up[1]; + tess.xyz[ndx+2][2] = origin[2] - left[2] - up[2]; + + tess.xyz[ndx+3][0] = origin[0] + left[0] - up[0]; + tess.xyz[ndx+3][1] = origin[1] + left[1] - up[1]; + tess.xyz[ndx+3][2] = origin[2] + left[2] - up[2]; + + + // constant normal all the way around + VectorSubtract( vec3_origin, backEnd.viewParms.or.axis[0], normal ); + + tess.normal[ndx][0] = tess.normal[ndx+1][0] = tess.normal[ndx+2][0] = tess.normal[ndx+3][0] = normal[0]; + tess.normal[ndx][1] = tess.normal[ndx+1][1] = tess.normal[ndx+2][1] = tess.normal[ndx+3][1] = normal[1]; + tess.normal[ndx][2] = tess.normal[ndx+1][2] = tess.normal[ndx+2][2] = tess.normal[ndx+3][2] = normal[2]; + + // standard square texture coordinates + tess.texCoords[ndx][0][0] = tess.texCoords[ndx][1][0] = s1; + tess.texCoords[ndx][0][1] = tess.texCoords[ndx][1][1] = t1; + + tess.texCoords[ndx+1][0][0] = tess.texCoords[ndx+1][1][0] = s2; + tess.texCoords[ndx+1][0][1] = tess.texCoords[ndx+1][1][1] = t1; + + tess.texCoords[ndx+2][0][0] = tess.texCoords[ndx+2][1][0] = s2; + tess.texCoords[ndx+2][0][1] = tess.texCoords[ndx+2][1][1] = t2; + + tess.texCoords[ndx+3][0][0] = tess.texCoords[ndx+3][1][0] = s1; + tess.texCoords[ndx+3][0][1] = tess.texCoords[ndx+3][1][1] = t2; + + // constant color all the way around + // should this be identity and let the shader specify from entity? + tess.vertexColors[ndx][0] = color[0]; + tess.vertexColors[ndx][1] = color[1]; + tess.vertexColors[ndx][2] = color[2]; + tess.vertexColors[ndx][3] = color[3]; + + tess.vertexColors[ndx+1][0] = color[0]; + tess.vertexColors[ndx+1][1] = color[1]; + tess.vertexColors[ndx+1][2] = color[2]; + tess.vertexColors[ndx+1][3] = color[3]; + + tess.vertexColors[ndx+2][0] = color[0]; + tess.vertexColors[ndx+2][1] = color[1]; + tess.vertexColors[ndx+2][2] = color[2]; + tess.vertexColors[ndx+2][3] = color[3]; + + tess.vertexColors[ndx+3][0] = color[0]; + tess.vertexColors[ndx+3][1] = color[1]; + tess.vertexColors[ndx+3][2] = color[2]; + tess.vertexColors[ndx+3][3] = color[3]; + + tess.numVertexes += 4; + tess.numIndexes += 6; +} + +/* +============== +RB_AddQuadStamp +============== +*/ +void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, float color[4] ) { + RB_AddQuadStampExt( origin, left, up, color, 0, 0, 1, 1 ); +} + + +/* +============== +RB_InstantQuad + +based on Tess_InstantQuad from xreal +============== +*/ +void RB_InstantQuad2(vec4_t quadVerts[4], vec2_t texCoords[4]) +{ + GLimp_LogComment("--- RB_InstantQuad2 ---\n"); + + tess.numVertexes = 0; + tess.numIndexes = 0; + tess.firstIndex = 0; + + VectorCopy4(quadVerts[0], tess.xyz[tess.numVertexes]); + VectorCopy2(texCoords[0], tess.texCoords[tess.numVertexes][0]); + tess.numVertexes++; + + VectorCopy4(quadVerts[1], tess.xyz[tess.numVertexes]); + VectorCopy2(texCoords[1], tess.texCoords[tess.numVertexes][0]); + tess.numVertexes++; + + VectorCopy4(quadVerts[2], tess.xyz[tess.numVertexes]); + VectorCopy2(texCoords[2], tess.texCoords[tess.numVertexes][0]); + tess.numVertexes++; + + VectorCopy4(quadVerts[3], tess.xyz[tess.numVertexes]); + VectorCopy2(texCoords[3], tess.texCoords[tess.numVertexes][0]); + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 1; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 3; + + RB_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD); + + GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD); + + R_DrawElementsVBO(tess.numIndexes, tess.firstIndex); + + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.firstIndex = 0; +} + + +void RB_InstantQuad(vec4_t quadVerts[4]) +{ + vec4_t color; + vec2_t texCoords[4]; + vec2_t invTexRes; + + VectorSet4(color, 1, 1, 1, 1); + + texCoords[0][0] = 0; + texCoords[0][1] = 0; + + texCoords[1][0] = 1; + texCoords[1][1] = 0; + + texCoords[2][0] = 1; + texCoords[2][1] = 1; + + texCoords[3][0] = 0; + texCoords[3][1] = 1; + + invTexRes[0] = 1.0f / 256.0f; + invTexRes[1] = 1.0f / 256.0f; + + GLSL_BindProgram(&tr.textureColorShader); + + GLSL_SetUniformMatrix16(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + GLSL_SetUniformVec4(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_COLOR, color); + GLSL_SetUniformVec2(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_INVTEXRES, invTexRes); + GLSL_SetUniformVec2(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_AUTOEXPOSUREMINMAX, tr.refdef.autoExposureMinMax); + GLSL_SetUniformVec3(&tr.textureColorShader, TEXTURECOLOR_UNIFORM_TONEMINAVGMAXLINEAR, tr.refdef.toneMinAvgMaxLinear); + + RB_InstantQuad2(quadVerts, texCoords); //, color, &tr.textureColorShader, invTexRes); +} + + +/* +============== +RB_SurfaceSprite +============== +*/ +static void RB_SurfaceSprite( void ) { + vec3_t left, up; + float radius; + float colors[4]; + trRefEntity_t *ent = backEnd.currentEntity; + + // calculate the xyz locations for the four corners + radius = ent->e.radius; + if ( ent->e.rotation == 0 ) { + VectorScale( backEnd.viewParms.or.axis[1], radius, left ); + VectorScale( backEnd.viewParms.or.axis[2], radius, up ); + } else { + float s, c; + float ang; + + ang = M_PI * ent->e.rotation / 180; + s = sin( ang ); + c = cos( ang ); + + VectorScale( backEnd.viewParms.or.axis[1], c * radius, left ); + VectorMA( left, -s * radius, backEnd.viewParms.or.axis[2], left ); + + VectorScale( backEnd.viewParms.or.axis[2], c * radius, up ); + VectorMA( up, s * radius, backEnd.viewParms.or.axis[1], up ); + } + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + +#ifdef REACTION + if (ent->e.renderfx & RF_SUNFLARE) + { + if (backEnd.viewHasSunFlare) + { + ri.Printf(PRINT_WARNING, "Multiple sun flares not supported\n"); + return; + } + if (R_CullPointAndRadiusEx(ent->e.origin, ent->e.radius, backEnd.viewParms.frustum, ARRAY_LEN(backEnd.viewParms.frustum)) == CULL_OUT) + return; + colors[0] = colors[1] = colors[2] = colors[3] = ent->e.shaderRGBA[glRefConfig.framebufferObject] / 255.0f; + if (colors[0] == 0) + return; + backEnd.viewHasSunFlare = qtrue; + backEnd.frameHasSunFlare = qtrue; + } + else +#endif + { + VectorScale4(ent->e.shaderRGBA, 1.0f / 255.0f, colors); + } + + RB_AddQuadStamp( ent->e.origin, left, up, colors ); +} + + +/* +============= +RB_SurfacePolychain +============= +*/ +static void RB_SurfacePolychain( srfPoly_t *p ) { + int i; + int numv; + + RB_CHECKOVERFLOW( p->numVerts, 3*(p->numVerts - 2) ); + + // fan triangles into the tess array + numv = tess.numVertexes; + for ( i = 0; i < p->numVerts; i++ ) { + VectorCopy( p->verts[i].xyz, tess.xyz[numv] ); + tess.texCoords[numv][0][0] = p->verts[i].st[0]; + tess.texCoords[numv][0][1] = p->verts[i].st[1]; + tess.vertexColors[numv][0] = p->verts[ i ].modulate[0] / 255.0f; + tess.vertexColors[numv][1] = p->verts[ i ].modulate[1] / 255.0f; + tess.vertexColors[numv][2] = p->verts[ i ].modulate[2] / 255.0f; + tess.vertexColors[numv][3] = p->verts[ i ].modulate[3] / 255.0f; + + numv++; + } + + // generate fan indexes into the tess array + for ( i = 0; i < p->numVerts-2; i++ ) { + tess.indexes[tess.numIndexes + 0] = tess.numVertexes; + tess.indexes[tess.numIndexes + 1] = tess.numVertexes + i + 1; + tess.indexes[tess.numIndexes + 2] = tess.numVertexes + i + 2; + tess.numIndexes += 3; + } + + tess.numVertexes = numv; +} + +static void RB_SurfaceHelper( int numVerts, srfVert_t *verts, int numTriangles, srfTriangle_t *triangles, int dlightBits, int pshadowBits) +{ + int i; + srfTriangle_t *tri; + srfVert_t *dv; + float *xyz, *normal, *texCoords, *lightCoords, *lightdir; +#ifdef USE_VERT_TANGENT_SPACE + float *tangent, *bitangent; +#endif + glIndex_t *index; + float *color; + + RB_CheckVBOandIBO(tess.vbo, tess.ibo); + + RB_CHECKOVERFLOW( numVerts, numTriangles * 3 ); + + tri = triangles; + index = &tess.indexes[ tess.numIndexes ]; + for ( i = 0 ; i < numTriangles ; i++, tri++ ) { + *index++ = tess.numVertexes + tri->indexes[0]; + *index++ = tess.numVertexes + tri->indexes[1]; + *index++ = tess.numVertexes + tri->indexes[2]; + } + tess.numIndexes += numTriangles * 3; + + if ( tess.shader->vertexAttribs & ATTR_POSITION ) + { + dv = verts; + xyz = tess.xyz[ tess.numVertexes ]; + for ( i = 0 ; i < numVerts ; i++, dv++, xyz+=4 ) + VectorCopy(dv->xyz, xyz); + } + + if ( tess.shader->vertexAttribs & ATTR_NORMAL ) + { + dv = verts; + normal = tess.normal[ tess.numVertexes ]; + for ( i = 0 ; i < numVerts ; i++, dv++, normal+=4 ) + VectorCopy(dv->normal, normal); + } + +#ifdef USE_VERT_TANGENT_SPACE + if ( tess.shader->vertexAttribs & ATTR_TANGENT ) + { + dv = verts; + tangent = tess.tangent[ tess.numVertexes ]; + for ( i = 0 ; i < numVerts ; i++, dv++, tangent+=4 ) + VectorCopy(dv->tangent, tangent); + } + + if ( tess.shader->vertexAttribs & ATTR_BITANGENT ) + { + dv = verts; + bitangent = tess.bitangent[ tess.numVertexes ]; + for ( i = 0 ; i < numVerts ; i++, dv++, bitangent+=4 ) + VectorCopy(dv->bitangent, bitangent); + } +#endif + + if ( tess.shader->vertexAttribs & ATTR_TEXCOORD ) + { + dv = verts; + texCoords = tess.texCoords[ tess.numVertexes ][0]; + for ( i = 0 ; i < numVerts ; i++, dv++, texCoords+=4 ) + VectorCopy2(dv->st, texCoords); + } + + if ( tess.shader->vertexAttribs & ATTR_LIGHTCOORD ) + { + dv = verts; + lightCoords = tess.texCoords[ tess.numVertexes ][1]; + for ( i = 0 ; i < numVerts ; i++, dv++, lightCoords+=4 ) + VectorCopy2(dv->lightmap, lightCoords); + } + + if ( tess.shader->vertexAttribs & ATTR_COLOR ) + { + dv = verts; + color = tess.vertexColors[ tess.numVertexes ]; + for ( i = 0 ; i < numVerts ; i++, dv++, color+=4 ) + VectorCopy4(dv->vertexColors, color); + } + + if ( tess.shader->vertexAttribs & ATTR_LIGHTDIRECTION ) + { + dv = verts; + lightdir = tess.lightdir[ tess.numVertexes ]; + for ( i = 0 ; i < numVerts ; i++, dv++, lightdir+=4 ) + VectorCopy(dv->lightdir, lightdir); + } + +#if 0 // nothing even uses vertex dlightbits + for ( i = 0 ; i < numVerts ; i++ ) { + tess.vertexDlightBits[ tess.numVertexes + i ] = dlightBits; + } +#endif + + tess.dlightBits |= dlightBits; + tess.pshadowBits |= pshadowBits; + + tess.numVertexes += numVerts; +} + +static qboolean RB_SurfaceHelperVBO(VBO_t *vbo, IBO_t *ibo, int numVerts, int numIndexes, int firstIndex, int dlightBits, int pshadowBits, qboolean shaderCheck) +{ + int i, mergeForward, mergeBack; + GLvoid *firstIndexOffset, *lastIndexOffset; + + if (!vbo || !ibo) + { + return qfalse; + } + + if (shaderCheck && !(!ShaderRequiresCPUDeforms(tess.shader) && !tess.shader->isSky && !tess.shader->isPortal)) + { + return qfalse; + } + + RB_CheckVBOandIBO(vbo, ibo); + + tess.dlightBits |= dlightBits; + tess.pshadowBits |= pshadowBits; + + // merge this into any existing multidraw primitives + mergeForward = -1; + mergeBack = -1; + firstIndexOffset = BUFFER_OFFSET(firstIndex * sizeof(GL_INDEX_TYPE)); + lastIndexOffset = BUFFER_OFFSET((firstIndex + numIndexes) * sizeof(GL_INDEX_TYPE)); + + if (r_mergeMultidraws->integer) + { + i = 0; + + if (r_mergeMultidraws->integer == 1) + { + // lazy merge, only check the last primitive + if (tess.multiDrawPrimitives) + { + i = tess.multiDrawPrimitives - 1; + } + } + + for (; i < tess.multiDrawPrimitives; i++) + { + if (tess.multiDrawLastIndex[i] == firstIndexOffset) + { + mergeBack = i; + } + + if (lastIndexOffset == tess.multiDrawFirstIndex[i]) + { + mergeForward = i; + } + } + } + + if (mergeBack != -1 && mergeForward == -1) + { + tess.multiDrawNumIndexes[mergeBack] += numIndexes; + tess.multiDrawLastIndex[mergeBack] = (byte *)tess.multiDrawFirstIndex[mergeBack] + tess.multiDrawNumIndexes[mergeBack] * sizeof(GL_INDEX_TYPE); + backEnd.pc.c_multidrawsMerged++; + } + else if (mergeBack == -1 && mergeForward != -1) + { + tess.multiDrawNumIndexes[mergeForward] += numIndexes; + tess.multiDrawFirstIndex[mergeForward] = firstIndexOffset; + tess.multiDrawLastIndex[mergeForward] = (byte *)tess.multiDrawFirstIndex[mergeForward] + tess.multiDrawNumIndexes[mergeForward] * sizeof(GL_INDEX_TYPE); + backEnd.pc.c_multidrawsMerged++; + } + else if (mergeBack != -1 && mergeForward != -1) + { + tess.multiDrawNumIndexes[mergeBack] += numIndexes + tess.multiDrawNumIndexes[mergeForward]; + tess.multiDrawLastIndex[mergeBack] = (byte *)tess.multiDrawFirstIndex[mergeBack] + tess.multiDrawNumIndexes[mergeBack] * sizeof(GL_INDEX_TYPE); + tess.multiDrawPrimitives--; + + if (mergeForward != tess.multiDrawPrimitives) + { + tess.multiDrawNumIndexes[mergeForward] = tess.multiDrawNumIndexes[tess.multiDrawPrimitives]; + tess.multiDrawFirstIndex[mergeForward] = tess.multiDrawFirstIndex[tess.multiDrawPrimitives]; + } + backEnd.pc.c_multidrawsMerged += 2; + } + else if (mergeBack == -1 && mergeForward == -1) + { + tess.multiDrawNumIndexes[tess.multiDrawPrimitives] = numIndexes; + tess.multiDrawFirstIndex[tess.multiDrawPrimitives] = firstIndexOffset; + tess.multiDrawLastIndex[tess.multiDrawPrimitives] = lastIndexOffset; + tess.multiDrawPrimitives++; + } + + backEnd.pc.c_multidraws++; + + tess.numIndexes += numIndexes; + tess.numVertexes += numVerts; + + return qtrue; +} + +/* +============= +RB_SurfaceTriangles +============= +*/ +static void RB_SurfaceTriangles( srfTriangles_t *srf ) { + if( RB_SurfaceHelperVBO (srf->vbo, srf->ibo, srf->numVerts, srf->numTriangles * 3, srf->firstIndex, srf->dlightBits[backEnd.smpFrame], srf->pshadowBits[backEnd.smpFrame], qtrue ) ) + { + return; + } + + RB_SurfaceHelper(srf->numVerts, srf->verts, srf->numTriangles, srf->triangles, srf->dlightBits[backEnd.smpFrame], srf->pshadowBits[backEnd.smpFrame]); +} + + + +/* +============== +RB_SurfaceBeam +============== +*/ +static void RB_SurfaceBeam( void ) +{ +#define NUM_BEAM_SEGS 6 + refEntity_t *e; + int i; + vec3_t perpvec; + vec3_t direction, normalized_direction; + vec3_t start_points[NUM_BEAM_SEGS], end_points[NUM_BEAM_SEGS]; + vec3_t oldorigin, origin; + + e = &backEnd.currentEntity->e; + + oldorigin[0] = e->oldorigin[0]; + oldorigin[1] = e->oldorigin[1]; + oldorigin[2] = e->oldorigin[2]; + + origin[0] = e->origin[0]; + origin[1] = e->origin[1]; + origin[2] = e->origin[2]; + + normalized_direction[0] = direction[0] = oldorigin[0] - origin[0]; + normalized_direction[1] = direction[1] = oldorigin[1] - origin[1]; + normalized_direction[2] = direction[2] = oldorigin[2] - origin[2]; + + if ( VectorNormalize( normalized_direction ) == 0 ) + return; + + PerpendicularVector( perpvec, normalized_direction ); + + VectorScale( perpvec, 4, perpvec ); + + 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] ); + } + + GL_Bind( tr.whiteImage ); + + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + // FIXME: Quake3 doesn't use this, so I never tested it + tess.numVertexes = 0; + tess.numIndexes = 0; + tess.firstIndex = 0; + + for ( i = 0; i <= NUM_BEAM_SEGS; i++ ) { + VectorCopy(start_points[ i % NUM_BEAM_SEGS ], tess.xyz[tess.numVertexes++]); + VectorCopy(end_points [ i % NUM_BEAM_SEGS ], tess.xyz[tess.numVertexes++]); + } + + for ( i = 0; i < NUM_BEAM_SEGS; i++ ) { + tess.indexes[tess.numIndexes++] = i * 2; + tess.indexes[tess.numIndexes++] = (i + 1) * 2; + tess.indexes[tess.numIndexes++] = 1 + i * 2; + + tess.indexes[tess.numIndexes++] = 1 + i * 2; + tess.indexes[tess.numIndexes++] = (i + 1) * 2; + tess.indexes[tess.numIndexes++] = 1 + (i + 1) * 2; + } + + // FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function + RB_UpdateVBOs(ATTR_POSITION); + + { + shaderProgram_t *sp = &tr.textureColorShader; + vec4_t color; + + GLSL_VertexAttribsState(ATTR_POSITION); + GLSL_BindProgram(sp); + + GLSL_SetUniformMatrix16(sp, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection); + + color[0] = 1.0f; + color[1] = 0.0f; + color[2] = 0.0f; + color[3] = 1.0f; + GLSL_SetUniformVec4(sp, TEXTURECOLOR_UNIFORM_COLOR, color); + } + + R_DrawElementsVBO(tess.numIndexes, tess.firstIndex); + + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.firstIndex = 0; +} + +//================================================================================ + +static void DoRailCore( const vec3_t start, const vec3_t end, const vec3_t up, float len, float spanWidth ) +{ + float spanWidth2; + int vbase; + float t = len / 256.0f; + + vbase = tess.numVertexes; + + spanWidth2 = -spanWidth; + + // FIXME: use quad stamp? + VectorMA( start, spanWidth, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 0.25 / 255.0f; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 0.25 / 255.0f; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 0.25 / 255.0f; + tess.numVertexes++; + + VectorMA( start, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] / 255.0f; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] / 255.0f; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] / 255.0f; + tess.numVertexes++; + + VectorMA( end, spanWidth, up, tess.xyz[tess.numVertexes] ); + + tess.texCoords[tess.numVertexes][0][0] = t; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] / 255.0f; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] / 255.0f; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] / 255.0f; + tess.numVertexes++; + + VectorMA( end, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = t; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] / 255.0f; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] / 255.0f; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] / 255.0f; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = vbase; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 2; + + tess.indexes[tess.numIndexes++] = vbase + 2; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 3; +} + +static void DoRailDiscs( int numSegs, const vec3_t start, const vec3_t dir, const vec3_t right, const vec3_t up ) +{ + int i; + vec3_t pos[4]; + vec3_t v; + int spanWidth = r_railWidth->integer; + float c, s; + float scale; + + if ( numSegs > 1 ) + numSegs--; + if ( !numSegs ) + return; + + scale = 0.25; + + for ( i = 0; i < 4; i++ ) + { + c = cos( DEG2RAD( 45 + i * 90 ) ); + s = sin( DEG2RAD( 45 + i * 90 ) ); + v[0] = ( right[0] * c + up[0] * s ) * scale * spanWidth; + v[1] = ( right[1] * c + up[1] * s ) * scale * spanWidth; + v[2] = ( right[2] * c + up[2] * s ) * scale * spanWidth; + VectorAdd( start, v, pos[i] ); + + if ( numSegs > 1 ) + { + // offset by 1 segment if we're doing a long distance shot + VectorAdd( pos[i], dir, pos[i] ); + } + } + + for ( i = 0; i < numSegs; i++ ) + { + int j; + + RB_CHECKOVERFLOW( 4, 6 ); + + for ( j = 0; j < 4; j++ ) + { + VectorCopy( pos[j], tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = ( j < 2 ); + tess.texCoords[tess.numVertexes][0][1] = ( j && j != 3 ); + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] / 255.0f; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] / 255.0f; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] / 255.0f; + tess.numVertexes++; + + VectorAdd( pos[j], dir, pos[j] ); + } + + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 0; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 1; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 3; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 3; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 1; + tess.indexes[tess.numIndexes++] = tess.numVertexes - 4 + 2; + } +} + +/* +** RB_SurfaceRailRinges +*/ +static void RB_SurfaceRailRings( void ) { + refEntity_t *e; + int numSegs; + int len; + vec3_t vec; + vec3_t right, up; + vec3_t start, end; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, start ); + VectorCopy( e->origin, end ); + + // compute variables + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + MakeNormalVectors( vec, right, up ); + numSegs = ( len ) / r_railSegmentLength->value; + if ( numSegs <= 0 ) { + numSegs = 1; + } + + VectorScale( vec, r_railSegmentLength->value, vec ); + + DoRailDiscs( numSegs, start, vec, right, up ); +} + +/* +** RB_SurfaceRailCore +*/ +static void RB_SurfaceRailCore( void ) { + refEntity_t *e; + int len; + vec3_t right; + vec3_t vec; + vec3_t start, end; + vec3_t v1, v2; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, start ); + VectorCopy( e->origin, end ); + + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + + // compute side vector + VectorSubtract( start, backEnd.viewParms.or.origin, v1 ); + VectorNormalize( v1 ); + VectorSubtract( end, backEnd.viewParms.or.origin, v2 ); + VectorNormalize( v2 ); + CrossProduct( v1, v2, right ); + VectorNormalize( right ); + + DoRailCore( start, end, right, len, r_railCoreWidth->integer ); +} + +/* +** RB_SurfaceLightningBolt +*/ +static void RB_SurfaceLightningBolt( void ) { + refEntity_t *e; + int len; + vec3_t right; + vec3_t vec; + vec3_t start, end; + vec3_t v1, v2; + int i; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, end ); + VectorCopy( e->origin, start ); + + // compute variables + VectorSubtract( end, start, vec ); + len = VectorNormalize( vec ); + + // compute side vector + VectorSubtract( start, backEnd.viewParms.or.origin, v1 ); + VectorNormalize( v1 ); + VectorSubtract( end, backEnd.viewParms.or.origin, v2 ); + VectorNormalize( v2 ); + CrossProduct( v1, v2, right ); + VectorNormalize( right ); + + for ( i = 0 ; i < 4 ; i++ ) { + vec3_t temp; + + DoRailCore( start, end, right, len, 8 ); + RotatePointAroundVector( temp, vec, right, 45 ); + VectorCopy( temp, right ); + } +} + +/* +** VectorArrayNormalize +* +* The inputs to this routing seem to always be close to length = 1.0 (about 0.6 to 2.0) +* This means that we don't have to worry about zero length or enormously long vectors. +*/ +static void VectorArrayNormalize(vec4_t *normals, unsigned int count) +{ +// assert(count); + +#if idppc + { + register float half = 0.5; + register float one = 1.0; + float *components = (float *)normals; + + // Vanilla PPC code, but since PPC has a reciprocal square root estimate instruction, + // runs *much* faster than calling sqrt(). We'll use a single Newton-Raphson + // refinement step to get a little more precision. This seems to yeild results + // that are correct to 3 decimal places and usually correct to at least 4 (sometimes 5). + // (That is, for the given input range of about 0.6 to 2.0). + do { + float x, y, z; + float B, y0, y1; + + x = components[0]; + y = components[1]; + z = components[2]; + components += 4; + B = x*x + y*y + z*z; + +#ifdef __GNUC__ + asm("frsqrte %0,%1" : "=f" (y0) : "f" (B)); +#else + y0 = __frsqrte(B); +#endif + y1 = y0 + half*y0*(one - B*y0*y0); + + x = x * y1; + y = y * y1; + components[-4] = x; + z = z * y1; + components[-3] = y; + components[-2] = z; + } while(count--); + } +#else // No assembly version for this architecture, or C_ONLY defined + // given the input, it's safe to call VectorNormalizeFast + while (count--) { + VectorNormalizeFast(normals[0]); + normals++; + } +#endif + +} + + + +/* +** LerpMeshVertexes +*/ +#if idppc_altivec +static void LerpMeshVertexes_altivec(md3Surface_t *surf, float backlerp) +{ + short *oldXyz, *newXyz, *oldNormals, *newNormals; + float *outXyz, *outNormal; + float oldXyzScale QALIGN(16); + float newXyzScale QALIGN(16); + float oldNormalScale QALIGN(16); + float newNormalScale QALIGN(16); + int vertNum; + unsigned lat, lng; + int numVerts; + + outXyz = tess.xyz[tess.numVertexes]; + outNormal = tess.normal[tess.numVertexes]; + + newXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.frame * surf->numVerts * 4); + newNormals = newXyz + 3; + + newXyzScale = MD3_XYZ_SCALE * (1.0 - backlerp); + newNormalScale = 1.0 - backlerp; + + numVerts = surf->numVerts; + + if ( backlerp == 0 ) { + vector signed short newNormalsVec0; + vector signed short newNormalsVec1; + vector signed int newNormalsIntVec; + vector float newNormalsFloatVec; + vector float newXyzScaleVec; + vector unsigned char newNormalsLoadPermute; + vector unsigned char newNormalsStorePermute; + vector float zero; + + newNormalsStorePermute = vec_lvsl(0,(float *)&newXyzScaleVec); + newXyzScaleVec = *(vector float *)&newXyzScale; + newXyzScaleVec = vec_perm(newXyzScaleVec,newXyzScaleVec,newNormalsStorePermute); + newXyzScaleVec = vec_splat(newXyzScaleVec,0); + newNormalsLoadPermute = vec_lvsl(0,newXyz); + newNormalsStorePermute = vec_lvsr(0,outXyz); + zero = (vector float)vec_splat_s8(0); + // + // just copy the vertexes + // + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + newXyz += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + newNormalsLoadPermute = vec_lvsl(0,newXyz); + newNormalsStorePermute = vec_lvsr(0,outXyz); + newNormalsVec0 = vec_ld(0,newXyz); + newNormalsVec1 = vec_ld(16,newXyz); + newNormalsVec0 = vec_perm(newNormalsVec0,newNormalsVec1,newNormalsLoadPermute); + newNormalsIntVec = vec_unpackh(newNormalsVec0); + newNormalsFloatVec = vec_ctf(newNormalsIntVec,0); + newNormalsFloatVec = vec_madd(newNormalsFloatVec,newXyzScaleVec,zero); + newNormalsFloatVec = vec_perm(newNormalsFloatVec,newNormalsFloatVec,newNormalsStorePermute); + //outXyz[0] = newXyz[0] * newXyzScale; + //outXyz[1] = newXyz[1] * newXyzScale; + //outXyz[2] = newXyz[2] * newXyzScale; + + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + outNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + outNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + vec_ste(newNormalsFloatVec,0,outXyz); + vec_ste(newNormalsFloatVec,4,outXyz); + vec_ste(newNormalsFloatVec,8,outXyz); + } + } else { + // + // interpolate and copy the vertex and normal + // + oldXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.oldframe * surf->numVerts * 4); + oldNormals = oldXyz + 3; + + oldXyzScale = MD3_XYZ_SCALE * backlerp; + oldNormalScale = backlerp; + + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + oldXyz += 4, newXyz += 4, oldNormals += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + vec3_t uncompressedOldNormal, uncompressedNewNormal; + + // interpolate the xyz + outXyz[0] = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale; + outXyz[1] = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale; + outXyz[2] = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale; + + // FIXME: interpolate lat/long instead? + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + uncompressedNewNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedNewNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedNewNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + lat = ( oldNormals[0] >> 8 ) & 0xff; + lng = ( oldNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + + uncompressedOldNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedOldNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedOldNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + outNormal[0] = uncompressedOldNormal[0] * oldNormalScale + uncompressedNewNormal[0] * newNormalScale; + outNormal[1] = uncompressedOldNormal[1] * oldNormalScale + uncompressedNewNormal[1] * newNormalScale; + outNormal[2] = uncompressedOldNormal[2] * oldNormalScale + uncompressedNewNormal[2] * newNormalScale; + +// VectorNormalize (outNormal); + } + VectorArrayNormalize((vec4_t *)tess.normal[tess.numVertexes], numVerts); + } +} +#endif + +static void LerpMeshVertexes_scalar(mdvSurface_t *surf, float backlerp) +{ +#if 0 + short *oldXyz, *newXyz, *oldNormals, *newNormals; + float *outXyz, *outNormal; + float oldXyzScale, newXyzScale; + float oldNormalScale, newNormalScale; + int vertNum; + unsigned lat, lng; + int numVerts; + + outXyz = tess.xyz[tess.numVertexes]; + outNormal = tess.normal[tess.numVertexes]; + + newXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.frame * surf->numVerts * 4); + newNormals = newXyz + 3; + + newXyzScale = MD3_XYZ_SCALE * (1.0 - backlerp); + newNormalScale = 1.0 - backlerp; + + numVerts = surf->numVerts; + + if ( backlerp == 0 ) { + // + // just copy the vertexes + // + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + newXyz += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + + outXyz[0] = newXyz[0] * newXyzScale; + outXyz[1] = newXyz[1] * newXyzScale; + outXyz[2] = newXyz[2] * newXyzScale; + + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + outNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + outNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + } + } else { + // + // interpolate and copy the vertex and normal + // + oldXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.oldframe * surf->numVerts * 4); + oldNormals = oldXyz + 3; + + oldXyzScale = MD3_XYZ_SCALE * backlerp; + oldNormalScale = backlerp; + + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + oldXyz += 4, newXyz += 4, oldNormals += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + vec3_t uncompressedOldNormal, uncompressedNewNormal; + + // interpolate the xyz + outXyz[0] = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale; + outXyz[1] = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale; + outXyz[2] = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale; + + // FIXME: interpolate lat/long instead? + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + uncompressedNewNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedNewNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedNewNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + lat = ( oldNormals[0] >> 8 ) & 0xff; + lng = ( oldNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + + uncompressedOldNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedOldNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedOldNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + outNormal[0] = uncompressedOldNormal[0] * oldNormalScale + uncompressedNewNormal[0] * newNormalScale; + outNormal[1] = uncompressedOldNormal[1] * oldNormalScale + uncompressedNewNormal[1] * newNormalScale; + outNormal[2] = uncompressedOldNormal[2] * oldNormalScale + uncompressedNewNormal[2] * newNormalScale; + +// VectorNormalize (outNormal); + } + VectorArrayNormalize((vec4_t *)tess.normal[tess.numVertexes], numVerts); + } +#endif + float *outXyz, *outNormal; + mdvVertex_t *newVerts; + int vertNum; + + newVerts = surf->verts + backEnd.currentEntity->e.frame * surf->numVerts; + + outXyz = tess.xyz[tess.numVertexes]; + outNormal = tess.normal[tess.numVertexes]; + + if (backlerp == 0) + { + // + // just copy the vertexes + // + + for (vertNum=0 ; vertNum < surf->numVerts ; vertNum++) + { + VectorCopy(newVerts->xyz, outXyz); + VectorCopy(newVerts->normal, outNormal); + newVerts++; + outXyz += 4; + outNormal += 4; + } + } + else + { + // + // interpolate and copy the vertex and normal + // + + mdvVertex_t *oldVerts; + + oldVerts = surf->verts + backEnd.currentEntity->e.oldframe * surf->numVerts; + + for (vertNum=0 ; vertNum < surf->numVerts ; vertNum++) + { + VectorLerp(backlerp, newVerts->xyz, oldVerts->xyz, outXyz); + VectorLerp(backlerp, newVerts->normal, oldVerts->normal, outNormal); + //VectorNormalize(outNormal); + newVerts++; + oldVerts++; + outXyz += 4; + outNormal += 4; + } + VectorArrayNormalize((vec4_t *)tess.normal[tess.numVertexes], surf->numVerts); + } + +} + +static void LerpMeshVertexes(mdvSurface_t *surf, float backlerp) +{ +#if 0 +#if idppc_altivec + if (com_altivec->integer) { + // must be in a seperate function or G3 systems will crash. + LerpMeshVertexes_altivec( surf, backlerp ); + return; + } +#endif // idppc_altivec +#endif + LerpMeshVertexes_scalar( surf, backlerp ); +} + + +/* +============= +RB_SurfaceMesh +============= +*/ +static void RB_SurfaceMesh(mdvSurface_t *surface) { + int j; + float backlerp; + srfTriangle_t *triangles; + mdvSt_t *texCoords; + int indexes; + int Bob, Doug; + int numVerts; + + if ( backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) { + backlerp = 0; + } else { + backlerp = backEnd.currentEntity->e.backlerp; + } + + RB_CHECKOVERFLOW( surface->numVerts, surface->numTriangles*3 ); + + LerpMeshVertexes (surface, backlerp); + + triangles = surface->triangles; + indexes = surface->numTriangles * 3; + Bob = tess.numIndexes; + Doug = tess.numVertexes; + for (j = 0 ; j < surface->numTriangles ; j++) { + tess.indexes[Bob + j*3 + 0] = Doug + triangles[j].indexes[0]; + tess.indexes[Bob + j*3 + 1] = Doug + triangles[j].indexes[1]; + tess.indexes[Bob + j*3 + 2] = Doug + triangles[j].indexes[2]; + } + tess.numIndexes += indexes; + + texCoords = surface->st; + + numVerts = surface->numVerts; + for ( j = 0; j < numVerts; j++ ) { + tess.texCoords[Doug + j][0][0] = texCoords[j].st[0]; + tess.texCoords[Doug + j][0][1] = texCoords[j].st[1]; + // FIXME: fill in lightmapST for completeness? + } + + tess.numVertexes += surface->numVerts; + +} + + +/* +============== +RB_SurfaceFace +============== +*/ +static void RB_SurfaceFace( srfSurfaceFace_t *srf ) { + if( RB_SurfaceHelperVBO (srf->vbo, srf->ibo, srf->numVerts, srf->numTriangles * 3, srf->firstIndex, srf->dlightBits[backEnd.smpFrame], srf->pshadowBits[backEnd.smpFrame], qtrue ) ) + { + return; + } + + RB_SurfaceHelper(srf->numVerts, srf->verts, srf->numTriangles, srf->triangles, srf->dlightBits[backEnd.smpFrame], srf->pshadowBits[backEnd.smpFrame]); +} + + +static float LodErrorForVolume( vec3_t local, float radius ) { + vec3_t world; + float d; + + // never let it go negative + if ( r_lodCurveError->value < 0 ) { + return 0; + } + + world[0] = local[0] * backEnd.or.axis[0][0] + local[1] * backEnd.or.axis[1][0] + + local[2] * backEnd.or.axis[2][0] + backEnd.or.origin[0]; + world[1] = local[0] * backEnd.or.axis[0][1] + local[1] * backEnd.or.axis[1][1] + + local[2] * backEnd.or.axis[2][1] + backEnd.or.origin[1]; + world[2] = local[0] * backEnd.or.axis[0][2] + local[1] * backEnd.or.axis[1][2] + + local[2] * backEnd.or.axis[2][2] + backEnd.or.origin[2]; + + VectorSubtract( world, backEnd.viewParms.or.origin, world ); + d = DotProduct( world, backEnd.viewParms.or.axis[0] ); + + if ( d < 0 ) { + d = -d; + } + d -= radius; + if ( d < 1 ) { + d = 1; + } + + return r_lodCurveError->value / d; +} + +/* +============= +RB_SurfaceGrid + +Just copy the grid of points and triangulate +============= +*/ +static void RB_SurfaceGrid( srfGridMesh_t *srf ) { + int i, j; + float *xyz; + float *texCoords, *lightCoords; + float *normal; +#ifdef USE_VERT_TANGENT_SPACE + float *tangent, *bitangent; +#endif + float *color, *lightdir; + srfVert_t *dv; + int rows, irows, vrows; + int used; + int widthTable[MAX_GRID_SIZE]; + int heightTable[MAX_GRID_SIZE]; + float lodError; + int lodWidth, lodHeight; + int numVertexes; + int dlightBits; + int pshadowBits; + //int *vDlightBits; + + if( RB_SurfaceHelperVBO (srf->vbo, srf->ibo, srf->numVerts, srf->numTriangles * 3, srf->firstIndex, srf->dlightBits[backEnd.smpFrame], srf->pshadowBits[backEnd.smpFrame], qtrue ) ) + { + return; + } + + dlightBits = srf->dlightBits[backEnd.smpFrame]; + tess.dlightBits |= dlightBits; + + pshadowBits = srf->pshadowBits[backEnd.smpFrame]; + tess.pshadowBits |= pshadowBits; + + // determine the allowable discrepance + lodError = LodErrorForVolume( srf->lodOrigin, srf->lodRadius ); + + // determine which rows and columns of the subdivision + // we are actually going to use + widthTable[0] = 0; + lodWidth = 1; + for ( i = 1 ; i < srf->width-1 ; i++ ) { + if ( srf->widthLodError[i] <= lodError ) { + widthTable[lodWidth] = i; + lodWidth++; + } + } + widthTable[lodWidth] = srf->width-1; + lodWidth++; + + heightTable[0] = 0; + lodHeight = 1; + for ( i = 1 ; i < srf->height-1 ; i++ ) { + if ( srf->heightLodError[i] <= lodError ) { + heightTable[lodHeight] = i; + lodHeight++; + } + } + heightTable[lodHeight] = srf->height-1; + lodHeight++; + + + // very large grids may have more points or indexes than can be fit + // in the tess structure, so we may have to issue it in multiple passes + + used = 0; + while ( used < lodHeight - 1 ) { + // see how many rows of both verts and indexes we can add without overflowing + do { + vrows = ( SHADER_MAX_VERTEXES - tess.numVertexes ) / lodWidth; + irows = ( SHADER_MAX_INDEXES - tess.numIndexes ) / ( lodWidth * 6 ); + + // if we don't have enough space for at least one strip, flush the buffer + if ( vrows < 2 || irows < 1 ) { + RB_EndSurface(); + RB_BeginSurface(tess.shader, tess.fogNum ); + } else { + break; + } + } while ( 1 ); + + rows = irows; + if ( vrows < irows + 1 ) { + rows = vrows - 1; + } + if ( used + rows > lodHeight ) { + rows = lodHeight - used; + } + + numVertexes = tess.numVertexes; + + xyz = tess.xyz[numVertexes]; + normal = tess.normal[numVertexes]; +#ifdef USE_VERT_TANGENT_SPACE + tangent = tess.tangent[numVertexes]; + bitangent = tess.bitangent[numVertexes]; +#endif + texCoords = tess.texCoords[numVertexes][0]; + lightCoords = tess.texCoords[numVertexes][1]; + color = tess.vertexColors[numVertexes]; + lightdir = tess.lightdir[numVertexes]; + //vDlightBits = &tess.vertexDlightBits[numVertexes]; + + for ( i = 0 ; i < rows ; i++ ) { + for ( j = 0 ; j < lodWidth ; j++ ) { + dv = srf->verts + heightTable[ used + i ] * srf->width + + widthTable[ j ]; + + if ( tess.shader->vertexAttribs & ATTR_POSITION ) + { + VectorCopy(dv->xyz, xyz); + xyz += 4; + } + + if ( tess.shader->vertexAttribs & ATTR_NORMAL ) + { + VectorCopy(dv->normal, normal); + normal += 4; + } + +#ifdef USE_VERT_TANGENT_SPACE + if ( tess.shader->vertexAttribs & ATTR_TANGENT ) + { + VectorCopy(dv->tangent, tangent); + tangent += 4; + } + + if ( tess.shader->vertexAttribs & ATTR_BITANGENT ) + { + VectorCopy(dv->bitangent, bitangent); + bitangent += 4; + } +#endif + if ( tess.shader->vertexAttribs & ATTR_TEXCOORD ) + { + VectorCopy2(dv->st, texCoords); + texCoords += 4; + } + + if ( tess.shader->vertexAttribs & ATTR_LIGHTCOORD ) + { + VectorCopy2(dv->lightmap, lightCoords); + lightCoords += 4; + } + + if ( tess.shader->vertexAttribs & ATTR_COLOR ) + { + VectorCopy4(dv->vertexColors, color); + color += 4; + } + + if ( tess.shader->vertexAttribs & ATTR_LIGHTDIRECTION ) + { + VectorCopy(dv->lightdir, lightdir); + lightdir += 4; + } + + //*vDlightBits++ = dlightBits; + } + } + + + // add the indexes + { + int numIndexes; + int w, h; + + h = rows - 1; + w = lodWidth - 1; + numIndexes = tess.numIndexes; + for (i = 0 ; i < h ; i++) { + for (j = 0 ; j < w ; j++) { + int v1, v2, v3, v4; + + // vertex order to be reckognized as tristrips + v1 = numVertexes + i*lodWidth + j + 1; + v2 = v1 - 1; + v3 = v2 + lodWidth; + v4 = v3 + 1; + + tess.indexes[numIndexes] = v2; + tess.indexes[numIndexes+1] = v3; + tess.indexes[numIndexes+2] = v1; + + tess.indexes[numIndexes+3] = v1; + tess.indexes[numIndexes+4] = v3; + tess.indexes[numIndexes+5] = v4; + numIndexes += 6; + } + } + + tess.numIndexes = numIndexes; + } + + tess.numVertexes += rows * lodWidth; + + used += rows - 1; + } +} + + +/* +=========================================================================== + +NULL MODEL + +=========================================================================== +*/ + +/* +=================== +RB_SurfaceAxis + +Draws x/y/z lines from the origin for orientation debugging +=================== +*/ +static void RB_SurfaceAxis( void ) { + // FIXME: implement this +#if 0 + GL_Bind( tr.whiteImage ); + 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(); + qglLineWidth( 1 ); +#endif +} + +//=========================================================================== + +/* +==================== +RB_SurfaceEntity + +Entities that have a single procedurally generated surface +==================== +*/ +static void RB_SurfaceEntity( surfaceType_t *surfType ) { + switch( backEnd.currentEntity->e.reType ) { + case RT_SPRITE: + RB_SurfaceSprite(); + break; + case RT_BEAM: + RB_SurfaceBeam(); + break; + case RT_RAIL_CORE: + RB_SurfaceRailCore(); + break; + case RT_RAIL_RINGS: + RB_SurfaceRailRings(); + break; + case RT_LIGHTNING: + RB_SurfaceLightningBolt(); + break; + default: + RB_SurfaceAxis(); + break; + } + return; +} + +static void RB_SurfaceBad( surfaceType_t *surfType ) { + ri.Printf( PRINT_ALL, "Bad surface tesselated.\n" ); +} + +static void RB_SurfaceFlare(srfFlare_t *surf) +{ + if (r_flares->integer) + RB_AddFlare(surf, tess.fogNum, surf->origin, surf->color, surf->normal); +} + +static void RB_SurfaceVBOMesh(srfVBOMesh_t * srf) +{ + RB_SurfaceHelperVBO (srf->vbo, srf->ibo, srf->numVerts, srf->numIndexes, srf->firstIndex, srf->dlightBits[backEnd.smpFrame], srf->pshadowBits[backEnd.smpFrame], qfalse ); +} + +void RB_SurfaceVBOMDVMesh(srfVBOMDVMesh_t * surface) +{ + //mdvModel_t *mdvModel; + //mdvSurface_t *mdvSurface; + refEntity_t *refEnt; + + GLimp_LogComment("--- RB_SurfaceVBOMDVMesh ---\n"); + + if(!surface->vbo || !surface->ibo) + return; + + //RB_CheckVBOandIBO(surface->vbo, surface->ibo); + RB_EndSurface(); + RB_BeginSurface(tess.shader, tess.fogNum); + + R_BindVBO(surface->vbo); + R_BindIBO(surface->ibo); + + tess.useInternalVBO = qfalse; + + tess.numIndexes += surface->numIndexes; + tess.numVertexes += surface->numVerts; + + //mdvModel = surface->mdvModel; + //mdvSurface = surface->mdvSurface; + + refEnt = &backEnd.currentEntity->e; + + if(refEnt->oldframe == refEnt->frame) + { + glState.vertexAttribsInterpolation = 0; + } + else + { + glState.vertexAttribsInterpolation = refEnt->backlerp; + } + + glState.vertexAttribsOldFrame = refEnt->oldframe; + glState.vertexAttribsNewFrame = refEnt->frame; + + RB_EndSurface(); + + // So we don't lerp surfaces that shouldn't be lerped + glState.vertexAttribsInterpolation = 0; +} + +static void RB_SurfaceDisplayList( srfDisplayList_t *surf ) { + // all apropriate state must be set in RB_BeginSurface + // this isn't implemented yet... + qglCallList( surf->listNum ); +} + +static void RB_SurfaceSkip( void *surf ) { +} + + +void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])( void *) = { + (void(*)(void*))RB_SurfaceBad, // SF_BAD, + (void(*)(void*))RB_SurfaceSkip, // SF_SKIP, + (void(*)(void*))RB_SurfaceFace, // SF_FACE, + (void(*)(void*))RB_SurfaceGrid, // SF_GRID, + (void(*)(void*))RB_SurfaceTriangles, // SF_TRIANGLES, + (void(*)(void*))RB_SurfacePolychain, // SF_POLY, + (void(*)(void*))RB_SurfaceMesh, // SF_MDV, + (void(*)(void*))RB_SurfaceAnim, // SF_MD4, +#ifdef RAVENMD4 + (void(*)(void*))RB_MDRSurfaceAnim, // SF_MDR, +#endif + (void(*)(void*))RB_IQMSurfaceAnim, // SF_IQM, + (void(*)(void*))RB_SurfaceFlare, // SF_FLARE, + (void(*)(void*))RB_SurfaceEntity, // SF_ENTITY + (void(*)(void*))RB_SurfaceDisplayList, // SF_DISPLAY_LIST + (void(*)(void*))RB_SurfaceVBOMesh, // SF_VBO_MESH, + (void(*)(void*))RB_SurfaceVBOMDVMesh, // SF_VBO_MDVMESH +}; diff --git a/src/rend2/tr_vbo.c b/src/rend2/tr_vbo.c new file mode 100644 index 00000000..41af9375 --- /dev/null +++ b/src/rend2/tr_vbo.c @@ -0,0 +1,932 @@ +/* +=========================================================================== +Copyright (C) 2007-2009 Robert Beckebans + +This file is part of XreaL source code. + +XreaL source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +XreaL source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with XreaL source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// tr_vbo.c +#include "tr_local.h" + +/* +============ +R_CreateVBO +============ +*/ +VBO_t *R_CreateVBO(const char *name, byte * vertexes, int vertexesSize, vboUsage_t usage) +{ + VBO_t *vbo; + int glUsage; + + switch (usage) + { + case VBO_USAGE_STATIC: + glUsage = GL_STATIC_DRAW_ARB; + break; + + case VBO_USAGE_DYNAMIC: + glUsage = GL_DYNAMIC_DRAW_ARB; + break; + + default: + Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage); + return NULL; + } + + if(strlen(name) >= MAX_QPATH) + { + ri.Error(ERR_DROP, "R_CreateVBO: \"%s\" is too long\n", name); + } + + if ( tr.numVBOs == MAX_VBOS ) { + ri.Error( ERR_DROP, "R_CreateVBO: MAX_VBOS hit\n"); + } + + // make sure the render thread is stopped + R_SyncRenderThread(); + + vbo = tr.vbos[tr.numVBOs] = ri.Hunk_Alloc(sizeof(*vbo), h_low); + tr.numVBOs++; + + memset(vbo, 0, sizeof(*vbo)); + + Q_strncpyz(vbo->name, name, sizeof(vbo->name)); + + vbo->vertexesSize = vertexesSize; + + qglGenBuffersARB(1, &vbo->vertexesVBO); + + qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo->vertexesVBO); + qglBufferDataARB(GL_ARRAY_BUFFER_ARB, vertexesSize, vertexes, glUsage); + + qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + + glState.currentVBO = NULL; + + GL_CheckErrors(); + + return vbo; +} + +/* +============ +R_CreateVBO2 +============ +*/ +VBO_t *R_CreateVBO2(const char *name, int numVertexes, srfVert_t * verts, unsigned int stateBits, vboUsage_t usage) +{ + VBO_t *vbo; + int i; + + byte *data; + int dataSize; + int dataOfs; + + int glUsage; + + switch (usage) + { + case VBO_USAGE_STATIC: + glUsage = GL_STATIC_DRAW_ARB; + break; + + case VBO_USAGE_DYNAMIC: + glUsage = GL_DYNAMIC_DRAW_ARB; + break; + + default: + Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage); + return NULL; + } + + if(!numVertexes) + return NULL; + + if(strlen(name) >= MAX_QPATH) + { + ri.Error(ERR_DROP, "R_CreateVBO2: \"%s\" is too long\n", name); + } + + if ( tr.numVBOs == MAX_VBOS ) { + ri.Error( ERR_DROP, "R_CreateVBO2: MAX_VBOS hit\n"); + } + + // make sure the render thread is stopped + R_SyncRenderThread(); + + vbo = tr.vbos[tr.numVBOs] = ri.Hunk_Alloc(sizeof(*vbo), h_low); + tr.numVBOs++; + + memset(vbo, 0, sizeof(*vbo)); + + Q_strncpyz(vbo->name, name, sizeof(vbo->name)); + + if (usage == VBO_USAGE_STATIC) + { + // since these vertex attributes are never altered, interleave them + vbo->ofs_xyz = 0; + dataSize = sizeof(verts[0].xyz); + + if(stateBits & ATTR_NORMAL) + { + vbo->ofs_normal = dataSize; + dataSize += sizeof(verts[0].normal); + } + +#ifdef USE_VERT_TANGENT_SPACE + if(stateBits & ATTR_TANGENT) + { + vbo->ofs_tangent = dataSize; + dataSize += sizeof(verts[0].tangent); + } + + if(stateBits & ATTR_BITANGENT) + { + vbo->ofs_bitangent = dataSize; + dataSize += sizeof(verts[0].bitangent); + } +#endif + + if(stateBits & ATTR_TEXCOORD) + { + vbo->ofs_st = dataSize; + dataSize += sizeof(verts[0].st); + } + + if(stateBits & ATTR_LIGHTCOORD) + { + vbo->ofs_lightmap = dataSize; + dataSize += sizeof(verts[0].lightmap); + } + + if(stateBits & ATTR_COLOR) + { + vbo->ofs_vertexcolor = dataSize; + dataSize += sizeof(verts[0].vertexColors); + } + + if(stateBits & ATTR_LIGHTDIRECTION) + { + vbo->ofs_lightdir = dataSize; + dataSize += sizeof(verts[0].lightdir); + } + + vbo->stride_xyz = dataSize; + vbo->stride_normal = dataSize; +#ifdef USE_VERT_TANGENT_SPACE + vbo->stride_tangent = dataSize; + vbo->stride_bitangent = dataSize; +#endif + vbo->stride_st = dataSize; + vbo->stride_lightmap = dataSize; + vbo->stride_vertexcolor = dataSize; + vbo->stride_lightdir = dataSize; + + // create VBO + dataSize *= numVertexes; + data = ri.Hunk_AllocateTempMemory(dataSize); + dataOfs = 0; + + //ri.Printf(PRINT_ALL, "CreateVBO: %d, %d %d %d %d %d, %d %d %d %d %d\n", dataSize, vbo->ofs_xyz, vbo->ofs_normal, vbo->ofs_st, vbo->ofs_lightmap, vbo->ofs_vertexcolor, + //vbo->stride_xyz, vbo->stride_normal, vbo->stride_st, vbo->stride_lightmap, vbo->stride_vertexcolor); + + for (i = 0; i < numVertexes; i++) + { + // xyz + memcpy(data + dataOfs, &verts[i].xyz, sizeof(verts[i].xyz)); + dataOfs += sizeof(verts[i].xyz); + + // normal + if(stateBits & ATTR_NORMAL) + { + memcpy(data + dataOfs, &verts[i].normal, sizeof(verts[i].normal)); + dataOfs += sizeof(verts[i].normal); + } + +#ifdef USE_VERT_TANGENT_SPACE + // tangent + if(stateBits & ATTR_TANGENT) + { + memcpy(data + dataOfs, &verts[i].tangent, sizeof(verts[i].tangent)); + dataOfs += sizeof(verts[i].tangent); + } + + // bitangent + if(stateBits & ATTR_BITANGENT) + { + memcpy(data + dataOfs, &verts[i].bitangent, sizeof(verts[i].bitangent)); + dataOfs += sizeof(verts[i].bitangent); + } +#endif + + // vertex texcoords + if(stateBits & ATTR_TEXCOORD) + { + memcpy(data + dataOfs, &verts[i].st, sizeof(verts[i].st)); + dataOfs += sizeof(verts[i].st); + } + + // feed vertex lightmap texcoords + if(stateBits & ATTR_LIGHTCOORD) + { + memcpy(data + dataOfs, &verts[i].lightmap, sizeof(verts[i].lightmap)); + dataOfs += sizeof(verts[i].lightmap); + } + + // feed vertex colors + if(stateBits & ATTR_COLOR) + { + memcpy(data + dataOfs, &verts[i].vertexColors, sizeof(verts[i].vertexColors)); + dataOfs += sizeof(verts[i].vertexColors); + } + + // feed vertex light directions + if(stateBits & ATTR_LIGHTDIRECTION) + { + memcpy(data + dataOfs, &verts[i].lightdir, sizeof(verts[i].lightdir)); + dataOfs += sizeof(verts[i].lightdir); + } + } + } + else + { + // since these vertex attributes may be changed, put them in flat arrays + dataSize = sizeof(verts[0].xyz); + + if(stateBits & ATTR_NORMAL) + { + dataSize += sizeof(verts[0].normal); + } + +#ifdef USE_VERT_TANGENT_SPACE + if(stateBits & ATTR_TANGENT) + { + dataSize += sizeof(verts[0].tangent); + } + + if(stateBits & ATTR_BITANGENT) + { + dataSize += sizeof(verts[0].bitangent); + } +#endif + + if(stateBits & ATTR_TEXCOORD) + { + dataSize += sizeof(verts[0].st); + } + + if(stateBits & ATTR_LIGHTCOORD) + { + dataSize += sizeof(verts[0].lightmap); + } + + if(stateBits & ATTR_COLOR) + { + dataSize += sizeof(verts[0].vertexColors); + } + + if(stateBits & ATTR_LIGHTDIRECTION) + { + dataSize += sizeof(verts[0].lightdir); + } + + // create VBO + dataSize *= numVertexes; + data = ri.Hunk_AllocateTempMemory(dataSize); + dataOfs = 0; + + vbo->ofs_xyz = 0; + vbo->ofs_normal = 0; +#ifdef USE_VERT_TANGENT_SPACE + vbo->ofs_tangent = 0; + vbo->ofs_bitangent = 0; +#endif + vbo->ofs_st = 0; + vbo->ofs_lightmap = 0; + vbo->ofs_vertexcolor = 0; + vbo->ofs_lightdir = 0; + + vbo->stride_xyz = sizeof(verts[0].xyz); + vbo->stride_normal = sizeof(verts[0].normal); +#ifdef USE_VERT_TANGENT_SPACE + vbo->stride_tangent = sizeof(verts[0].tangent); + vbo->stride_bitangent = sizeof(verts[0].bitangent); +#endif + vbo->stride_vertexcolor = sizeof(verts[0].vertexColors); + vbo->stride_st = sizeof(verts[0].st); + vbo->stride_lightmap = sizeof(verts[0].lightmap); + vbo->stride_lightdir = sizeof(verts[0].lightdir); + + //ri.Printf(PRINT_ALL, "2CreateVBO: %d, %d %d %d %d %d, %d %d %d %d %d\n", dataSize, vbo->ofs_xyz, vbo->ofs_normal, vbo->ofs_st, vbo->ofs_lightmap, vbo->ofs_vertexcolor, + //vbo->stride_xyz, vbo->stride_normal, vbo->stride_st, vbo->stride_lightmap, vbo->stride_vertexcolor); + + // xyz + for (i = 0; i < numVertexes; i++) + { + memcpy(data + dataOfs, &verts[i].xyz, sizeof(verts[i].xyz)); + dataOfs += sizeof(verts[i].xyz); + } + + // normal + if(stateBits & ATTR_NORMAL) + { + vbo->ofs_normal = dataOfs; + for (i = 0; i < numVertexes; i++) + { + memcpy(data + dataOfs, &verts[i].normal, sizeof(verts[i].normal)); + dataOfs += sizeof(verts[i].normal); + } + } + +#ifdef USE_VERT_TANGENT_SPACE + // tangent + if(stateBits & ATTR_TANGENT) + { + vbo->ofs_tangent = dataOfs; + for (i = 0; i < numVertexes; i++) + { + memcpy(data + dataOfs, &verts[i].tangent, sizeof(verts[i].tangent)); + dataOfs += sizeof(verts[i].tangent); + } + } + + // bitangent + if(stateBits & ATTR_BITANGENT) + { + vbo->ofs_bitangent = dataOfs; + for (i = 0; i < numVertexes; i++) + { + memcpy(data + dataOfs, &verts[i].bitangent, sizeof(verts[i].bitangent)); + dataOfs += sizeof(verts[i].bitangent); + } + } +#endif + + // vertex texcoords + if(stateBits & ATTR_TEXCOORD) + { + vbo->ofs_st = dataOfs; + for (i = 0; i < numVertexes; i++) + { + memcpy(data + dataOfs, &verts[i].st, sizeof(verts[i].st)); + dataOfs += sizeof(verts[i].st); + } + } + + // feed vertex lightmap texcoords + if(stateBits & ATTR_LIGHTCOORD) + { + vbo->ofs_lightmap = dataOfs; + for (i = 0; i < numVertexes; i++) + { + memcpy(data + dataOfs, &verts[i].lightmap, sizeof(verts[i].lightmap)); + dataOfs += sizeof(verts[i].lightmap); + } + } + + // feed vertex colors + if(stateBits & ATTR_COLOR) + { + vbo->ofs_vertexcolor = dataOfs; + for (i = 0; i < numVertexes; i++) + { + memcpy(data + dataOfs, &verts[i].vertexColors, sizeof(verts[i].vertexColors)); + dataOfs += sizeof(verts[i].vertexColors); + } + } + + // feed vertex lightdirs + if(stateBits & ATTR_LIGHTDIRECTION) + { + vbo->ofs_lightdir = dataOfs; + for (i = 0; i < numVertexes; i++) + { + memcpy(data + dataOfs, &verts[i].lightdir, sizeof(verts[i].lightdir)); + dataOfs += sizeof(verts[i].lightdir); + } + } + } + + + vbo->vertexesSize = dataSize; + + qglGenBuffersARB(1, &vbo->vertexesVBO); + + qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo->vertexesVBO); + qglBufferDataARB(GL_ARRAY_BUFFER_ARB, dataSize, data, glUsage); + + qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + + glState.currentVBO = NULL; + + GL_CheckErrors(); + + ri.Hunk_FreeTempMemory(data); + + return vbo; +} + + +/* +============ +R_CreateIBO +============ +*/ +IBO_t *R_CreateIBO(const char *name, byte * indexes, int indexesSize, vboUsage_t usage) +{ + IBO_t *ibo; + int glUsage; + + switch (usage) + { + case VBO_USAGE_STATIC: + glUsage = GL_STATIC_DRAW_ARB; + break; + + case VBO_USAGE_DYNAMIC: + glUsage = GL_DYNAMIC_DRAW_ARB; + break; + + default: + Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage); + return NULL; + } + + if(strlen(name) >= MAX_QPATH) + { + ri.Error(ERR_DROP, "R_CreateIBO: \"%s\" is too long\n", name); + } + + if ( tr.numIBOs == MAX_IBOS ) { + ri.Error( ERR_DROP, "R_CreateIBO: MAX_IBOS hit\n"); + } + + // make sure the render thread is stopped + R_SyncRenderThread(); + + ibo = tr.ibos[tr.numIBOs] = ri.Hunk_Alloc(sizeof(*ibo), h_low); + tr.numIBOs++; + + Q_strncpyz(ibo->name, name, sizeof(ibo->name)); + + ibo->indexesSize = indexesSize; + + qglGenBuffersARB(1, &ibo->indexesVBO); + + qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, ibo->indexesVBO); + qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexesSize, indexes, glUsage); + + qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); + + glState.currentIBO = NULL; + + GL_CheckErrors(); + + return ibo; +} + +/* +============ +R_CreateIBO2 +============ +*/ +IBO_t *R_CreateIBO2(const char *name, int numTriangles, srfTriangle_t * triangles, vboUsage_t usage) +{ + IBO_t *ibo; + int i, j; + + byte *indexes; + int indexesSize; + int indexesOfs; + + srfTriangle_t *tri; + glIndex_t index; + int glUsage; + + switch (usage) + { + case VBO_USAGE_STATIC: + glUsage = GL_STATIC_DRAW_ARB; + break; + + case VBO_USAGE_DYNAMIC: + glUsage = GL_DYNAMIC_DRAW_ARB; + break; + + default: + Com_Error(ERR_FATAL, "bad vboUsage_t given: %i", usage); + return NULL; + } + + if(!numTriangles) + return NULL; + + if(strlen(name) >= MAX_QPATH) + { + ri.Error(ERR_DROP, "R_CreateIBO2: \"%s\" is too long\n", name); + } + + if ( tr.numIBOs == MAX_IBOS ) { + ri.Error( ERR_DROP, "R_CreateIBO2: MAX_IBOS hit\n"); + } + + // make sure the render thread is stopped + R_SyncRenderThread(); + + ibo = tr.ibos[tr.numIBOs] = ri.Hunk_Alloc(sizeof(*ibo), h_low); + tr.numIBOs++; + + Q_strncpyz(ibo->name, name, sizeof(ibo->name)); + + indexesSize = numTriangles * 3 * sizeof(int); + indexes = ri.Hunk_AllocateTempMemory(indexesSize); + indexesOfs = 0; + + for(i = 0, tri = triangles; i < numTriangles; i++, tri++) + { + for(j = 0; j < 3; j++) + { + index = tri->indexes[j]; + memcpy(indexes + indexesOfs, &index, sizeof(glIndex_t)); + indexesOfs += sizeof(glIndex_t); + } + } + + ibo->indexesSize = indexesSize; + + qglGenBuffersARB(1, &ibo->indexesVBO); + + qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, ibo->indexesVBO); + qglBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexesSize, indexes, glUsage); + + qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); + + glState.currentIBO = NULL; + + GL_CheckErrors(); + + ri.Hunk_FreeTempMemory(indexes); + + return ibo; +} + +/* +============ +R_BindVBO +============ +*/ +void R_BindVBO(VBO_t * vbo) +{ + if(!vbo) + { + //R_BindNullVBO(); + ri.Error(ERR_DROP, "R_BindNullVBO: NULL vbo"); + return; + } + + if(r_logFile->integer) + { + // don't just call LogComment, or we will get a call to va() every frame! + GLimp_LogComment(va("--- R_BindVBO( %s ) ---\n", vbo->name)); + } + + if(glState.currentVBO != vbo) + { + glState.currentVBO = vbo; + glState.vertexAttribPointersSet = 0; + + glState.vertexAttribsInterpolation = 0; + glState.vertexAttribsOldFrame = 0; + glState.vertexAttribsNewFrame = 0; + + qglBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo->vertexesVBO); + + backEnd.pc.c_vboVertexBuffers++; + } +} + +/* +============ +R_BindNullVBO +============ +*/ +void R_BindNullVBO(void) +{ + GLimp_LogComment("--- R_BindNullVBO ---\n"); + + if(glState.currentVBO) + { + qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); + glState.currentVBO = NULL; + } + + GL_CheckErrors(); +} + +/* +============ +R_BindIBO +============ +*/ +void R_BindIBO(IBO_t * ibo) +{ + if(!ibo) + { + //R_BindNullIBO(); + ri.Error(ERR_DROP, "R_BindIBO: NULL ibo"); + return; + } + + if(r_logFile->integer) + { + // don't just call LogComment, or we will get a call to va() every frame! + GLimp_LogComment(va("--- R_BindIBO( %s ) ---\n", ibo->name)); + } + + if(glState.currentIBO != ibo) + { + qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, ibo->indexesVBO); + + glState.currentIBO = ibo; + + backEnd.pc.c_vboIndexBuffers++; + } +} + +/* +============ +R_BindNullIBO +============ +*/ +void R_BindNullIBO(void) +{ + GLimp_LogComment("--- R_BindNullIBO ---\n"); + + if(glState.currentIBO) + { + qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); + glState.currentIBO = NULL; + glState.vertexAttribPointersSet = 0; + } +} + +/* +============ +R_InitVBOs +============ +*/ +void R_InitVBOs(void) +{ + int dataSize; + int offset; + + ri.Printf(PRINT_ALL, "------- R_InitVBOs -------\n"); + + tr.numVBOs = 0; + tr.numIBOs = 0; + + dataSize = sizeof(tess.xyz[0]); + dataSize += sizeof(tess.normal[0]); +#ifdef USE_VERT_TANGENT_SPACE + dataSize += sizeof(tess.tangent[0]); + dataSize += sizeof(tess.bitangent[0]); +#endif + dataSize += sizeof(tess.vertexColors[0]); + dataSize += sizeof(tess.texCoords[0][0]) * 2; + dataSize += sizeof(tess.lightdir[0]); + dataSize *= SHADER_MAX_VERTEXES; + + tess.vbo = R_CreateVBO("tessVertexArray_VBO", NULL, dataSize, VBO_USAGE_DYNAMIC); + + offset = 0; + + tess.vbo->ofs_xyz = offset; offset += sizeof(tess.xyz[0]) * SHADER_MAX_VERTEXES; + tess.vbo->ofs_normal = offset; offset += sizeof(tess.normal[0]) * SHADER_MAX_VERTEXES; +#ifdef USE_VERT_TANGENT_SPACE + tess.vbo->ofs_tangent = offset; offset += sizeof(tess.tangent[0]) * SHADER_MAX_VERTEXES; + tess.vbo->ofs_bitangent = offset; offset += sizeof(tess.bitangent[0]) * SHADER_MAX_VERTEXES; +#endif + // these next two are actually interleaved + tess.vbo->ofs_st = offset; + tess.vbo->ofs_lightmap = offset + sizeof(tess.texCoords[0][0]); + offset += sizeof(tess.texCoords[0][0]) * 2 * SHADER_MAX_VERTEXES; + + tess.vbo->ofs_vertexcolor = offset; offset += sizeof(tess.vertexColors[0]) * SHADER_MAX_VERTEXES; + tess.vbo->ofs_lightdir = offset; + + tess.vbo->stride_xyz = sizeof(tess.xyz[0]); + tess.vbo->stride_normal = sizeof(tess.normal[0]); +#ifdef USE_VERT_TANGENT_SPACE + tess.vbo->stride_tangent = sizeof(tess.tangent[0]); + tess.vbo->stride_bitangent = sizeof(tess.bitangent[0]); +#endif + tess.vbo->stride_vertexcolor = sizeof(tess.vertexColors[0]); + tess.vbo->stride_st = sizeof(tess.texCoords[0][0]) * 2; + tess.vbo->stride_lightmap = sizeof(tess.texCoords[0][0]) * 2; + tess.vbo->stride_lightdir = sizeof(tess.lightdir[0]); + + dataSize = sizeof(tess.indexes[0]) * SHADER_MAX_INDEXES; + + tess.ibo = R_CreateIBO("tessVertexArray_IBO", NULL, dataSize, VBO_USAGE_DYNAMIC); + + R_BindNullVBO(); + R_BindNullIBO(); + + GL_CheckErrors(); +} + +/* +============ +R_ShutdownVBOs +============ +*/ +void R_ShutdownVBOs(void) +{ + int i; + VBO_t *vbo; + IBO_t *ibo; + + ri.Printf(PRINT_ALL, "------- R_ShutdownVBOs -------\n"); + + R_BindNullVBO(); + R_BindNullIBO(); + + + for(i = 0; i < tr.numVBOs; i++) + { + vbo = tr.vbos[i]; + + if(vbo->vertexesVBO) + { + qglDeleteBuffersARB(1, &vbo->vertexesVBO); + } + + //ri.Free(vbo); + } + + for(i = 0; i < tr.numIBOs; i++) + { + ibo = tr.ibos[i]; + + if(ibo->indexesVBO) + { + qglDeleteBuffersARB(1, &ibo->indexesVBO); + } + + //ri.Free(ibo); + } + + tr.numVBOs = 0; + tr.numIBOs = 0; +} + +/* +============ +R_VBOList_f +============ +*/ +void R_VBOList_f(void) +{ + int i; + VBO_t *vbo; + IBO_t *ibo; + int vertexesSize = 0; + int indexesSize = 0; + + ri.Printf(PRINT_ALL, " size name\n"); + ri.Printf(PRINT_ALL, "----------------------------------------------------------\n"); + + for(i = 0; i < tr.numVBOs; i++) + { + vbo = tr.vbos[i]; + + ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", vbo->vertexesSize / (1024 * 1024), + (vbo->vertexesSize % (1024 * 1024)) * 100 / (1024 * 1024), vbo->name); + + vertexesSize += vbo->vertexesSize; + } + + for(i = 0; i < tr.numIBOs; i++) + { + ibo = tr.ibos[i]; + + ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", ibo->indexesSize / (1024 * 1024), + (ibo->indexesSize % (1024 * 1024)) * 100 / (1024 * 1024), ibo->name); + + indexesSize += ibo->indexesSize; + } + + ri.Printf(PRINT_ALL, " %i total VBOs\n", tr.numVBOs); + ri.Printf(PRINT_ALL, " %d.%02d MB total vertices memory\n", vertexesSize / (1024 * 1024), + (vertexesSize % (1024 * 1024)) * 100 / (1024 * 1024)); + + ri.Printf(PRINT_ALL, " %i total IBOs\n", tr.numIBOs); + ri.Printf(PRINT_ALL, " %d.%02d MB total triangle indices memory\n", indexesSize / (1024 * 1024), + (indexesSize % (1024 * 1024)) * 100 / (1024 * 1024)); +} + + +/* +============== +RB_UpdateVBOs + +Adapted from Tess_UpdateVBOs from xreal + +Tr3B: update the default VBO to replace the client side vertex arrays +============== +*/ +void RB_UpdateVBOs(unsigned int attribBits) +{ + GLimp_LogComment("--- RB_UpdateVBOs ---\n"); + + backEnd.pc.c_dynamicVboDraws++; + + // update the default VBO + if(tess.numVertexes > 0 && tess.numVertexes <= SHADER_MAX_VERTEXES) + { + R_BindVBO(tess.vbo); + + if(attribBits & ATTR_BITS) + { + if(attribBits & ATTR_POSITION) + { + //ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_xyz, tess.numVertexes * sizeof(tess.xyz[0])); + qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_xyz, tess.numVertexes * sizeof(tess.xyz[0]), tess.xyz); + } + + if(attribBits & ATTR_TEXCOORD || attribBits & ATTR_LIGHTCOORD) + { + // these are interleaved, so we update both if either need it + //ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_st, tess.numVertexes * sizeof(tess.texCoords[0][0]) * 2); + qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_st, tess.numVertexes * sizeof(tess.texCoords[0][0]) * 2, tess.texCoords); + } + + if(attribBits & ATTR_NORMAL) + { + //ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_normal, tess.numVertexes * sizeof(tess.normal[0])); + qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_normal, tess.numVertexes * sizeof(tess.normal[0]), tess.normal); + } + +#ifdef USE_VERT_TANGENT_SPACE + if(attribBits & ATTR_TANGENT) + { + //ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_tangent, tess.numVertexes * sizeof(tess.tangent[0])); + qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_tangent, tess.numVertexes * sizeof(tess.tangent[0]), tess.tangent); + } + + if(attribBits & ATTR_BITANGENT) + { + //ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_bitangent, tess.numVertexes * sizeof(tess.bitangent[0])); + qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_bitangent, tess.numVertexes * sizeof(tess.bitangent[0]), tess.bitangent); + } +#endif + + if(attribBits & ATTR_COLOR) + { + //ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_vertexcolor, tess.numVertexes * sizeof(tess.vertexColors[0])); + qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_vertexcolor, tess.numVertexes * sizeof(tess.vertexColors[0]), tess.vertexColors); + } + + if(attribBits & ATTR_LIGHTDIRECTION) + { + //ri.Printf(PRINT_ALL, "offset %d, size %d\n", tess.vbo->ofs_lightdir, tess.numVertexes * sizeof(tess.lightdir[0])); + qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_lightdir, tess.numVertexes * sizeof(tess.lightdir[0]), tess.lightdir); + } + } + else + { + qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_xyz, tess.numVertexes * sizeof(tess.xyz[0]), tess.xyz); + qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_st, tess.numVertexes * sizeof(tess.texCoords[0][0]) * 2, tess.texCoords); + qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_normal, tess.numVertexes * sizeof(tess.normal[0]), tess.normal); +#ifdef USE_VERT_TANGENT_SPACE + qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_tangent, tess.numVertexes * sizeof(tess.tangent[0]), tess.tangent); + qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_bitangent, tess.numVertexes * sizeof(tess.bitangent[0]), tess.bitangent); +#endif + qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_vertexcolor, tess.numVertexes * sizeof(tess.vertexColors[0]), tess.vertexColors); + qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, tess.vbo->ofs_lightdir, tess.numVertexes * sizeof(tess.lightdir[0]), tess.lightdir); + } + + } + + // update the default IBO + if(tess.numIndexes > 0 && tess.numIndexes <= SHADER_MAX_INDEXES) + { + R_BindIBO(tess.ibo); + + qglBufferSubDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0, tess.numIndexes * sizeof(tess.indexes[0]), tess.indexes); + } +} diff --git a/src/rend2/tr_world.c b/src/rend2/tr_world.c new file mode 100644 index 00000000..54287be4 --- /dev/null +++ b/src/rend2/tr_world.c @@ -0,0 +1,851 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "tr_local.h" + + + +/* +================ +R_CullSurface + +Tries to cull surfaces before they are lighted or +added to the sorting list. +================ +*/ +static qboolean R_CullSurface( msurface_t *surf ) { + if ( r_nocull->integer || surf->cullinfo.type == CULLINFO_NONE) { + return qfalse; + } + + if (surf->cullinfo.type & CULLINFO_PLANE) + { + // Only true for SF_FACE, so treat like its own function + float d; + cullType_t ct; + + if ( !r_facePlaneCull->integer ) { + return qfalse; + } + + ct = surf->shader->cullType; + + if (ct == CT_TWO_SIDED) + { + return qfalse; + } + + // don't cull for depth shadow + /* + if ( tr.viewParms.flags & VPF_DEPTHSHADOW ) + { + return qfalse; + } + */ + + // shadowmaps draw back surfaces + if ( tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW) ) + { + if (ct == CT_FRONT_SIDED) + { + ct = CT_BACK_SIDED; + } + else + { + ct = CT_FRONT_SIDED; + } + } + + // do proper cull for orthographic projection + if (tr.viewParms.flags & VPF_ORTHOGRAPHIC) { + d = DotProduct(tr.viewParms.or.axis[0], surf->cullinfo.plane.normal); + if ( ct == CT_FRONT_SIDED ) { + if (d > 0) + return qtrue; + } else { + if (d < 0) + return qtrue; + } + return qfalse; + } + + d = DotProduct (tr.or.viewOrigin, surf->cullinfo.plane.normal); + + // don't cull exactly on the plane, because there are levels of rounding + // through the BSP, ICD, and hardware that may cause pixel gaps if an + // epsilon isn't allowed here + if ( ct == CT_FRONT_SIDED ) { + if ( d < surf->cullinfo.plane.dist - 8 ) { + return qtrue; + } + } else { + if ( d > surf->cullinfo.plane.dist + 8 ) { + return qtrue; + } + } + + return qfalse; + } + + if (surf->cullinfo.type & CULLINFO_SPHERE) + { + int sphereCull; + + if ( tr.currentEntityNum != REFENTITYNUM_WORLD ) { + sphereCull = R_CullLocalPointAndRadius( surf->cullinfo.localOrigin, surf->cullinfo.radius ); + } else { + sphereCull = R_CullPointAndRadius( surf->cullinfo.localOrigin, surf->cullinfo.radius ); + } + + if ( sphereCull == CULL_OUT ) + { + return qtrue; + } + + if ( sphereCull == CULL_IN ) + { + return qfalse; + } + } + + if (surf->cullinfo.type & CULLINFO_BOX) + { + int boxCull; + + if ( tr.currentEntityNum != REFENTITYNUM_WORLD ) { + boxCull = R_CullLocalBox( surf->cullinfo.bounds ); + } else { + boxCull = R_CullBox( surf->cullinfo.bounds ); + } + + if ( boxCull == CULL_OUT ) + { + return qtrue; + } + + if ( boxCull == CULL_IN ) + { + return qfalse; + } + } + + return qfalse; +} + + +/* +==================== +R_DlightSurface + +The given surface is going to be drawn, and it touches a leaf +that is touched by one or more dlights, so try to throw out +more dlights if possible. +==================== +*/ +static int R_DlightSurface( msurface_t *surf, int dlightBits ) { + float d; + int i; + dlight_t *dl; + + if ( surf->cullinfo.type & CULLINFO_PLANE ) + { + int i; + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + d = DotProduct( dl->origin, surf->cullinfo.plane.normal ) - surf->cullinfo.plane.dist; + if ( d < -dl->radius || d > dl->radius ) { + // dlight doesn't reach the plane + dlightBits &= ~( 1 << i ); + } + } + } + + if ( surf->cullinfo.type & CULLINFO_BOX ) + { + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + if ( dl->origin[0] - dl->radius > surf->cullinfo.bounds[1][0] + || dl->origin[0] + dl->radius < surf->cullinfo.bounds[0][0] + || dl->origin[1] - dl->radius > surf->cullinfo.bounds[1][1] + || dl->origin[1] + dl->radius < surf->cullinfo.bounds[0][1] + || dl->origin[2] - dl->radius > surf->cullinfo.bounds[1][2] + || dl->origin[2] + dl->radius < surf->cullinfo.bounds[0][2] ) { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + } + } + + if ( surf->cullinfo.type & CULLINFO_SPHERE ) + { + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + if (!SpheresIntersect(dl->origin, dl->radius, surf->cullinfo.localOrigin, surf->cullinfo.radius)) + { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + } + } + + if ( *surf->data == SF_FACE ) { + ((srfSurfaceFace_t *)surf->data)->dlightBits[ tr.smpFrame ] = dlightBits; + } else if ( *surf->data == SF_GRID ) { + ((srfGridMesh_t *)surf->data)->dlightBits[ tr.smpFrame ] = dlightBits; + } else if ( *surf->data == SF_TRIANGLES ) { + ((srfTriangles_t *)surf->data)->dlightBits[ tr.smpFrame ] = dlightBits; + } else if ( *surf->data == SF_VBO_MESH ) { + ((srfVBOMesh_t *)surf->data)->dlightBits[ tr.smpFrame ] = dlightBits; + } else { + dlightBits = 0; + } + + if ( dlightBits ) { + tr.pc.c_dlightSurfaces++; + } + + return dlightBits; +} + +/* +==================== +R_PshadowSurface + +Just like R_DlightSurface, cull any we can +==================== +*/ +static int R_PshadowSurface( msurface_t *surf, int pshadowBits ) { + float d; + int i; + pshadow_t *ps; + + if ( surf->cullinfo.type & CULLINFO_PLANE ) + { + int i; + for ( i = 0 ; i < tr.refdef.num_pshadows ; i++ ) { + if ( ! ( pshadowBits & ( 1 << i ) ) ) { + continue; + } + ps = &tr.refdef.pshadows[i]; + d = DotProduct( ps->lightOrigin, surf->cullinfo.plane.normal ) - surf->cullinfo.plane.dist; + if ( d < -ps->lightRadius || d > ps->lightRadius ) { + // pshadow doesn't reach the plane + pshadowBits &= ~( 1 << i ); + } + } + } + + if ( surf->cullinfo.type & CULLINFO_BOX ) + { + for ( i = 0 ; i < tr.refdef.num_pshadows ; i++ ) { + if ( ! ( pshadowBits & ( 1 << i ) ) ) { + continue; + } + ps = &tr.refdef.pshadows[i]; + if ( ps->lightOrigin[0] - ps->lightRadius > surf->cullinfo.bounds[1][0] + || ps->lightOrigin[0] + ps->lightRadius < surf->cullinfo.bounds[0][0] + || ps->lightOrigin[1] - ps->lightRadius > surf->cullinfo.bounds[1][1] + || ps->lightOrigin[1] + ps->lightRadius < surf->cullinfo.bounds[0][1] + || ps->lightOrigin[2] - ps->lightRadius > surf->cullinfo.bounds[1][2] + || ps->lightOrigin[2] + ps->lightRadius < surf->cullinfo.bounds[0][2] + || BoxOnPlaneSide(surf->cullinfo.bounds[0], surf->cullinfo.bounds[1], &ps->cullPlane) == 2 ) { + // pshadow doesn't reach the bounds + pshadowBits &= ~( 1 << i ); + } + } + } + + if ( surf->cullinfo.type & CULLINFO_SPHERE ) + { + for ( i = 0 ; i < tr.refdef.num_pshadows ; i++ ) { + if ( ! ( pshadowBits & ( 1 << i ) ) ) { + continue; + } + ps = &tr.refdef.pshadows[i]; + if (!SpheresIntersect(ps->viewOrigin, ps->viewRadius, surf->cullinfo.localOrigin, surf->cullinfo.radius) + || DotProduct( surf->cullinfo.localOrigin, ps->cullPlane.normal ) - ps->cullPlane.dist < -surf->cullinfo.radius) + { + // pshadow doesn't reach the bounds + pshadowBits &= ~( 1 << i ); + } + } + } + + if ( *surf->data == SF_FACE ) { + ((srfSurfaceFace_t *)surf->data)->pshadowBits[ tr.smpFrame ] = pshadowBits; + } else if ( *surf->data == SF_GRID ) { + ((srfGridMesh_t *)surf->data)->pshadowBits[ tr.smpFrame ] = pshadowBits; + } else if ( *surf->data == SF_TRIANGLES ) { + ((srfTriangles_t *)surf->data)->pshadowBits[ tr.smpFrame ] = pshadowBits; + } else if ( *surf->data == SF_VBO_MESH ) { + ((srfVBOMesh_t *)surf->data)->pshadowBits[ tr.smpFrame ] = pshadowBits; + } else { + pshadowBits = 0; + } + + if ( pshadowBits ) { + //tr.pc.c_dlightSurfaces++; + } + + return pshadowBits; +} + + +/* +====================== +R_AddWorldSurface +====================== +*/ +static void R_AddWorldSurface( msurface_t *surf, int dlightBits, int pshadowBits ) { + // FIXME: bmodel fog? + + // try to cull before dlighting or adding + if ( R_CullSurface( surf ) ) { + return; + } + + // check for dlighting + if ( dlightBits ) { + dlightBits = R_DlightSurface( surf, dlightBits ); + dlightBits = ( dlightBits != 0 ); + } + + // check for pshadows + /*if ( pshadowBits ) */{ + pshadowBits = R_PshadowSurface( surf, pshadowBits); + pshadowBits = ( pshadowBits != 0 ); + } + + R_AddDrawSurf( surf->data, surf->shader, surf->fogIndex, dlightBits, pshadowBits ); +} + +/* +============================================================= + + BRUSH MODELS + +============================================================= +*/ + +/* +================= +R_AddBrushModelSurfaces +================= +*/ +void R_AddBrushModelSurfaces ( trRefEntity_t *ent ) { + bmodel_t *bmodel; + int clip; + model_t *pModel; + int i; + + pModel = R_GetModelByHandle( ent->e.hModel ); + + bmodel = pModel->bmodel; + + clip = R_CullLocalBox( bmodel->bounds ); + if ( clip == CULL_OUT ) { + return; + } + + R_SetupEntityLighting( &tr.refdef, ent ); + R_DlightBmodel( bmodel ); + + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + int surf = bmodel->firstSurface + i; + + if (tr.world->surfacesViewCount[surf] != tr.viewCount) + { + tr.world->surfacesViewCount[surf] = tr.viewCount; + R_AddWorldSurface( tr.world->surfaces + surf, tr.currentEntity->needDlights, 0 ); + } + } +} + + +/* +============================================================= + + WORLD MODEL + +============================================================= +*/ + + +/* +================ +R_RecursiveWorldNode +================ +*/ +static void R_RecursiveWorldNode( mnode_t *node, int planeBits, int dlightBits, int pshadowBits ) { + + do { + int newDlights[2]; + unsigned int newPShadows[2]; + + // if the node wasn't marked as potentially visible, exit + // pvs is skipped for depth shadows + if (!(tr.viewParms.flags & VPF_DEPTHSHADOW) && node->visCounts[tr.visIndex] != tr.visCounts[tr.visIndex]) { + return; + } + + // if the bounding volume is outside the frustum, nothing + // inside can be visible OPTIMIZE: don't do this all the way to leafs? + + if ( !r_nocull->integer ) { + int r; + + if ( planeBits & 1 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[0]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~1; // all descendants will also be in front + } + } + + if ( planeBits & 2 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[1]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~2; // all descendants will also be in front + } + } + + if ( planeBits & 4 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[2]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~4; // all descendants will also be in front + } + } + + if ( planeBits & 8 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[3]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~8; // all descendants will also be in front + } + } + + if ( planeBits & 16 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[4]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~16; // all descendants will also be in front + } + } + } + + if ( node->contents != -1 ) { + break; + } + + // node is just a decision point, so go down both sides + // since we don't care about sort orders, just go positive to negative + + // determine which dlights are needed + newDlights[0] = 0; + newDlights[1] = 0; + if ( dlightBits ) { + int i; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + dlight_t *dl; + float dist; + + if ( dlightBits & ( 1 << i ) ) { + dl = &tr.refdef.dlights[i]; + dist = DotProduct( dl->origin, node->plane->normal ) - node->plane->dist; + + if ( dist > -dl->radius ) { + newDlights[0] |= ( 1 << i ); + } + if ( dist < dl->radius ) { + newDlights[1] |= ( 1 << i ); + } + } + } + } + + newPShadows[0] = 0; + newPShadows[1] = 0; + if ( pshadowBits ) { + int i; + + for ( i = 0 ; i < tr.refdef.num_pshadows ; i++ ) { + pshadow_t *shadow; + float dist; + + if ( pshadowBits & ( 1 << i ) ) { + shadow = &tr.refdef.pshadows[i]; + dist = DotProduct( shadow->lightOrigin, node->plane->normal ) - node->plane->dist; + + if ( dist > -shadow->lightRadius ) { + newPShadows[0] |= ( 1 << i ); + } + if ( dist < shadow->lightRadius ) { + newPShadows[1] |= ( 1 << i ); + } + } + } + } + + // recurse down the children, front side first + R_RecursiveWorldNode (node->children[0], planeBits, newDlights[0], newPShadows[0] ); + + // tail recurse + node = node->children[1]; + dlightBits = newDlights[1]; + pshadowBits = newPShadows[1]; + } while ( 1 ); + + { + // leaf node, so add mark surfaces + int c; + int surf, *view; + + tr.pc.c_leafs++; + + // add to z buffer bounds + if ( node->mins[0] < tr.viewParms.visBounds[0][0] ) { + tr.viewParms.visBounds[0][0] = node->mins[0]; + } + if ( node->mins[1] < tr.viewParms.visBounds[0][1] ) { + tr.viewParms.visBounds[0][1] = node->mins[1]; + } + if ( node->mins[2] < tr.viewParms.visBounds[0][2] ) { + tr.viewParms.visBounds[0][2] = node->mins[2]; + } + + if ( node->maxs[0] > tr.viewParms.visBounds[1][0] ) { + tr.viewParms.visBounds[1][0] = node->maxs[0]; + } + if ( node->maxs[1] > tr.viewParms.visBounds[1][1] ) { + tr.viewParms.visBounds[1][1] = node->maxs[1]; + } + if ( node->maxs[2] > tr.viewParms.visBounds[1][2] ) { + tr.viewParms.visBounds[1][2] = node->maxs[2]; + } + + // add merged and unmerged surfaces + if (tr.world->viewSurfaces) + view = tr.world->viewSurfaces + node->firstmarksurface; + else + view = tr.world->marksurfaces + node->firstmarksurface; + + c = node->nummarksurfaces; + while (c--) { + // just mark it as visible, so we don't jump out of the cache derefencing the surface + surf = *view; + if (surf < 0) + { + if (tr.world->mergedSurfacesViewCount[-surf - 1] != tr.viewCount) + { + tr.world->mergedSurfacesViewCount[-surf - 1] = tr.viewCount; + tr.world->mergedSurfacesDlightBits[-surf - 1] = dlightBits; + tr.world->mergedSurfacesPshadowBits[-surf - 1] = pshadowBits; + } + else + { + tr.world->mergedSurfacesDlightBits[-surf - 1] |= dlightBits; + tr.world->mergedSurfacesPshadowBits[-surf - 1] |= pshadowBits; + } + } + else + { + if (tr.world->surfacesViewCount[surf] != tr.viewCount) + { + tr.world->surfacesViewCount[surf] = tr.viewCount; + tr.world->surfacesDlightBits[surf] = dlightBits; + tr.world->surfacesPshadowBits[surf] = pshadowBits; + } + else + { + tr.world->surfacesDlightBits[surf] |= dlightBits; + tr.world->surfacesPshadowBits[surf] |= pshadowBits; + } + } + view++; + } + } + +} + + +/* +=============== +R_PointInLeaf +=============== +*/ +static mnode_t *R_PointInLeaf( const vec3_t p ) { + mnode_t *node; + float d; + cplane_t *plane; + + if ( !tr.world ) { + ri.Error (ERR_DROP, "R_PointInLeaf: bad model"); + } + + node = tr.world->nodes; + while( 1 ) { + if (node->contents != -1) { + break; + } + plane = node->plane; + d = DotProduct (p,plane->normal) - plane->dist; + if (d > 0) { + node = node->children[0]; + } else { + node = node->children[1]; + } + } + + return node; +} + +/* +============== +R_ClusterPVS +============== +*/ +static const byte *R_ClusterPVS (int cluster) { + if (!tr.world || !tr.world->vis || cluster < 0 || cluster >= tr.world->numClusters ) { + return tr.world->novis; + } + + return tr.world->vis + cluster * tr.world->clusterBytes; +} + +/* +================= +R_inPVS +================= +*/ +qboolean R_inPVS( const vec3_t p1, const vec3_t p2 ) { + mnode_t *leaf; + byte *vis; + + leaf = R_PointInLeaf( p1 ); + vis = ri.CM_ClusterPVS( leaf->cluster ); // why not R_ClusterPVS ?? + leaf = R_PointInLeaf( p2 ); + + if ( !(vis[leaf->cluster>>3] & (1<<(leaf->cluster&7))) ) { + return qfalse; + } + return qtrue; +} + +/* +=============== +R_MarkLeaves + +Mark the leaves and nodes that are in the PVS for the current +cluster +=============== +*/ +static void R_MarkLeaves (void) { + const byte *vis; + mnode_t *leaf, *parent; + int i; + int cluster; + + // lockpvs lets designers walk around to determine the + // extent of the current pvs + if ( r_lockpvs->integer ) { + return; + } + + // current viewcluster + leaf = R_PointInLeaf( tr.viewParms.pvsOrigin ); + cluster = leaf->cluster; + + // if the cluster is the same and the area visibility matrix + // hasn't changed, we don't need to mark everything again + + for(i = 0; i < MAX_VISCOUNTS; i++) + { + if(tr.visClusters[i] == cluster) + { + //tr.visIndex = i; + break; + } + } + + // if r_showcluster was just turned on, remark everything + if(i != MAX_VISCOUNTS && !tr.refdef.areamaskModified && !r_showcluster->modified)// && !r_dynamicBspOcclusionCulling->modified) + { + if(tr.visClusters[i] != tr.visClusters[tr.visIndex] && r_showcluster->integer) + { + ri.Printf(PRINT_ALL, "found cluster:%i area:%i index:%i\n", cluster, leaf->area, i); + } + tr.visIndex = i; + return; + } + + // if the areamask was modified, invalidate all visclusters + // this caused doors to open into undrawn areas + if (tr.refdef.areamaskModified) + { + memset(tr.visClusters, -2, sizeof(tr.visClusters)); + } + + tr.visIndex = (tr.visIndex + 1) % MAX_VISCOUNTS; + tr.visCounts[tr.visIndex]++; + tr.visClusters[tr.visIndex] = cluster; + + if ( r_showcluster->modified || r_showcluster->integer ) { + r_showcluster->modified = qfalse; + if ( r_showcluster->integer ) { + ri.Printf( PRINT_ALL, "cluster:%i area:%i\n", cluster, leaf->area ); + } + } + + // set all nodes to visible if there is no vis + // this caused some levels to simply not render + if (r_novis->integer || !tr.world->vis || tr.visClusters[tr.visIndex] == -1) { + for (i=0 ; inumnodes ; i++) { + if (tr.world->nodes[i].contents != CONTENTS_SOLID) { + tr.world->nodes[i].visCounts[tr.visIndex] = tr.visCounts[tr.visIndex]; + } + } + return; + } + + vis = R_ClusterPVS(tr.visClusters[tr.visIndex]); + + for (i=0,leaf=tr.world->nodes ; inumnodes ; i++, leaf++) { + cluster = leaf->cluster; + if ( cluster < 0 || cluster >= tr.world->numClusters ) { + continue; + } + + // check general pvs + if ( !(vis[cluster>>3] & (1<<(cluster&7))) ) { + continue; + } + + // check for door connection + if ( (tr.refdef.areamask[leaf->area>>3] & (1<<(leaf->area&7)) ) ) { + continue; // not visible + } + + parent = leaf; + do { + if(parent->visCounts[tr.visIndex] == tr.visCounts[tr.visIndex]) + break; + parent->visCounts[tr.visIndex] = tr.visCounts[tr.visIndex]; + parent = parent->parent; + } while (parent); + } +} + + +/* +============= +R_AddWorldSurfaces +============= +*/ +void R_AddWorldSurfaces (void) { + if ( !r_drawworld->integer ) { + return; + } + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return; + } + + tr.currentEntityNum = REFENTITYNUM_WORLD; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_REFENTITYNUM_SHIFT; + + // determine which leaves are in the PVS / areamask + if (!(tr.viewParms.flags & VPF_DEPTHSHADOW)) + R_MarkLeaves (); + + // clear out the visible min/max + ClearBounds( tr.viewParms.visBounds[0], tr.viewParms.visBounds[1] ); + + // perform frustum culling and flag all the potentially visible surfaces + if ( tr.refdef.num_dlights > 32 ) { + tr.refdef.num_dlights = 32 ; + } + + if ( tr.refdef.num_pshadows > 32 ) { + tr.refdef.num_pshadows = 32 ; + } + + if ( tr.viewParms.flags & VPF_DEPTHSHADOW ) + { + R_RecursiveWorldNode( tr.world->nodes, 31, 0, 0); + } + else if ( !(tr.viewParms.flags & VPF_SHADOWMAP) ) + { + R_RecursiveWorldNode( tr.world->nodes, 15, ( 1 << tr.refdef.num_dlights ) - 1, ( 1 << tr.refdef.num_pshadows ) - 1 ); + } + else + { + R_RecursiveWorldNode( tr.world->nodes, 31, ( 1 << tr.refdef.num_dlights ) - 1, 0 ); + } + + // now add all the potentially visible surfaces + // also mask invisible dlights for next frame + { + int i; + + tr.refdef.dlightMask = 0; + + for (i = 0; i < tr.world->numWorldSurfaces; i++) + { + if (tr.world->surfacesViewCount[i] != tr.viewCount) + continue; + + R_AddWorldSurface( tr.world->surfaces + i, tr.world->surfacesDlightBits[i], tr.world->surfacesPshadowBits[i] ); + tr.refdef.dlightMask |= tr.world->surfacesDlightBits[i]; + } + for (i = 0; i < tr.world->numMergedSurfaces; i++) + { + if (tr.world->mergedSurfacesViewCount[i] != tr.viewCount) + continue; + + R_AddWorldSurface( tr.world->mergedSurfaces + i, tr.world->mergedSurfacesDlightBits[i], tr.world->mergedSurfacesPshadowBits[i] ); + tr.refdef.dlightMask |= tr.world->mergedSurfacesDlightBits[i]; + } + + tr.refdef.dlightMask = ~tr.refdef.dlightMask; + } +} -- cgit