summaryrefslogtreecommitdiff
path: root/src/renderergl2
diff options
context:
space:
mode:
authorIronClawTrem <louie.nutman@gmail.com>2020-02-16 03:40:06 +0000
committerIronClawTrem <louie.nutman@gmail.com>2020-02-16 03:40:06 +0000
commit425decdf7e9284d15aa726e3ae96b9942fb0e3ea (patch)
tree6c0dd7edfefff1be7b9e75fe0b3a0a85fe1595f3 /src/renderergl2
parentccb0b2e4d6674a7a00c9bf491f08fc73b6898c54 (diff)
create tremded branch
Diffstat (limited to 'src/renderergl2')
-rw-r--r--src/renderergl2/CMakeLists.txt146
-rw-r--r--src/renderergl2/glsl/bokeh_fp.glsl70
-rw-r--r--src/renderergl2/glsl/bokeh_vp.glsl13
-rw-r--r--src/renderergl2/glsl/calclevels4x_fp.glsl60
-rw-r--r--src/renderergl2/glsl/calclevels4x_vp.glsl13
-rw-r--r--src/renderergl2/glsl/depthblur_fp.glsl82
-rw-r--r--src/renderergl2/glsl/depthblur_vp.glsl16
-rw-r--r--src/renderergl2/glsl/dlight_fp.glsl32
-rw-r--r--src/renderergl2/glsl/dlight_vp.glsl92
-rw-r--r--src/renderergl2/glsl/down4x_fp.glsl34
-rw-r--r--src/renderergl2/glsl/down4x_vp.glsl13
-rw-r--r--src/renderergl2/glsl/fogpass_fp.glsl9
-rw-r--r--src/renderergl2/glsl/fogpass_vp.glsl117
-rw-r--r--src/renderergl2/glsl/generic_fp.glsl33
-rw-r--r--src/renderergl2/glsl/generic_vp.glsl239
-rw-r--r--src/renderergl2/glsl/lightall_fp.glsl429
-rw-r--r--src/renderergl2/glsl/lightall_vp.glsl246
-rw-r--r--src/renderergl2/glsl/pshadow_fp.glsl78
-rw-r--r--src/renderergl2/glsl/pshadow_vp.glsl15
-rw-r--r--src/renderergl2/glsl/shadowfill_fp.glsl41
-rw-r--r--src/renderergl2/glsl/shadowfill_vp.glsl89
-rw-r--r--src/renderergl2/glsl/shadowmask_fp.glsl143
-rw-r--r--src/renderergl2/glsl/shadowmask_vp.glsl18
-rw-r--r--src/renderergl2/glsl/ssao_fp.glsl86
-rw-r--r--src/renderergl2/glsl/ssao_vp.glsl12
-rw-r--r--src/renderergl2/glsl/texturecolor_fp.glsl10
-rw-r--r--src/renderergl2/glsl/texturecolor_vp.glsl13
-rw-r--r--src/renderergl2/glsl/tonemap_fp.glsl57
-rw-r--r--src/renderergl2/glsl/tonemap_vp.glsl27
-rw-r--r--src/renderergl2/tr_animation.cpp525
-rw-r--r--src/renderergl2/tr_backend.cpp1817
-rw-r--r--src/renderergl2/tr_bsp.cpp3046
-rw-r--r--src/renderergl2/tr_cmds.cpp672
-rw-r--r--src/renderergl2/tr_curve.cpp741
-rw-r--r--src/renderergl2/tr_dsa.cpp287
-rw-r--r--src/renderergl2/tr_dsa.h80
-rw-r--r--src/renderergl2/tr_extensions.cpp279
-rw-r--r--src/renderergl2/tr_extramath.cpp248
-rw-r--r--src/renderergl2/tr_extramath.h104
-rw-r--r--src/renderergl2/tr_extratypes.h40
-rw-r--r--src/renderergl2/tr_fbo.cpp659
-rw-r--r--src/renderergl2/tr_fbo.h66
-rw-r--r--src/renderergl2/tr_flares.cpp554
-rw-r--r--src/renderergl2/tr_glsl.cpp1470
-rw-r--r--src/renderergl2/tr_image.cpp3235
-rw-r--r--src/renderergl2/tr_image_dds.cpp499
-rw-r--r--src/renderergl2/tr_init.cpp1534
-rw-r--r--src/renderergl2/tr_light.cpp513
-rw-r--r--src/renderergl2/tr_local.h2422
-rw-r--r--src/renderergl2/tr_main.cpp2669
-rw-r--r--src/renderergl2/tr_marks.cpp472
-rw-r--r--src/renderergl2/tr_mesh.cpp418
-rw-r--r--src/renderergl2/tr_model.cpp1419
-rw-r--r--src/renderergl2/tr_model_iqm.cpp1196
-rw-r--r--src/renderergl2/tr_postprocess.cpp484
-rw-r--r--src/renderergl2/tr_postprocess.h34
-rw-r--r--src/renderergl2/tr_scene.cpp575
-rw-r--r--src/renderergl2/tr_shade.cpp1634
-rw-r--r--src/renderergl2/tr_shade_calc.cpp843
-rw-r--r--src/renderergl2/tr_shader.cpp3891
-rw-r--r--src/renderergl2/tr_shadows.cpp327
-rw-r--r--src/renderergl2/tr_sky.cpp904
-rw-r--r--src/renderergl2/tr_subs.cpp49
-rw-r--r--src/renderergl2/tr_surface.cpp1320
-rw-r--r--src/renderergl2/tr_vbo.cpp945
-rw-r--r--src/renderergl2/tr_world.cpp811
66 files changed, 39015 insertions, 0 deletions
diff --git a/src/renderergl2/CMakeLists.txt b/src/renderergl2/CMakeLists.txt
new file mode 100644
index 0000000..b821436
--- /dev/null
+++ b/src/renderergl2/CMakeLists.txt
@@ -0,0 +1,146 @@
+include("${CMAKE_SOURCE_DIR}/cmake/SDL2.cmake")
+
+find_package(OpenGL)
+
+set(PARENT_DIR ${CMAKE_SOURCE_DIR}/src)
+include_directories(
+ ${PARENT_DIR}/jpeg-8c
+ ${PARENT_DIR}/renderercommon
+ ${PARENT_DIR}/nettle-3.3
+ ${SDL2_INCLUDE_DIRS}
+ )
+
+set(GLSL_SHADERS
+ glsl/bokeh_fp.glsl
+ glsl/bokeh_vp.glsl
+ glsl/calclevels4x_fp.glsl
+ glsl/calclevels4x_vp.glsl
+ glsl/depthblur_fp.glsl
+ glsl/depthblur_vp.glsl
+ glsl/dlight_fp.glsl
+ glsl/dlight_vp.glsl
+ glsl/down4x_fp.glsl
+ glsl/down4x_vp.glsl
+ glsl/fogpass_fp.glsl
+ glsl/fogpass_vp.glsl
+ glsl/generic_fp.glsl
+ glsl/generic_vp.glsl
+ glsl/lightall_fp.glsl
+ glsl/lightall_vp.glsl
+ glsl/pshadow_fp.glsl
+ glsl/pshadow_vp.glsl
+ glsl/shadowfill_fp.glsl
+ glsl/shadowfill_vp.glsl
+ glsl/shadowmask_fp.glsl
+ glsl/shadowmask_vp.glsl
+ glsl/ssao_fp.glsl
+ glsl/ssao_vp.glsl
+ glsl/texturecolor_fp.glsl
+ glsl/texturecolor_vp.glsl
+ glsl/tonemap_fp.glsl
+ glsl/tonemap_vp.glsl
+ )
+
+# Converts ^^^GSLS shaders^^^ into const char* C-strings.
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/glsl/)
+foreach(shader ${GLSL_SHADERS})
+ set(in ${CMAKE_CURRENT_SOURCE_DIR}/${shader})
+
+ get_filename_component(out ${in} NAME_WE)
+ set(out ${CMAKE_CURRENT_BINARY_DIR}/glsl/${out}.c)
+
+ get_filename_component(shader ${shader} NAME_WE)
+ message(STATUS "-- Generating embeded GLSL ${shader}")
+
+ file(READ ${in} contents HEX)
+ file(WRITE ${out} "const char *fallbackShader_${shader} = \"")
+
+ string(LENGTH "${contents}" contents_length)
+ math(EXPR contents_length "${contents_length} - 1")
+
+ foreach(iter RANGE 0 ${contents_length} 2)
+ string(SUBSTRING ${contents} ${iter} 2 line)
+ file(APPEND "${out}" "\\x${line}")
+ endforeach()
+ file(APPEND "${out}" "\";\n")
+
+ list(APPEND renderergl2_SHADERS "${out}")
+ set_source_files_properties(${out} PROPERTIES GENERATED TRUE)
+endforeach(shader)
+
+set(renderergl2_SRCS
+ ${renderergl2_SHADERS}
+ tr_local.h
+ tr_dsa.h
+ tr_extramath.h
+ tr_extratypes.h
+ tr_fbo.h
+ tr_postprocess.h
+ tr_animation.cpp
+ tr_backend.cpp
+ tr_bsp.cpp
+ tr_cmds.cpp
+ tr_curve.cpp
+ tr_dsa.cpp
+ tr_extensions.cpp
+ tr_extramath.cpp
+ tr_fbo.cpp
+ tr_flares.cpp
+ tr_glsl.cpp
+ tr_image.cpp
+ tr_image_dds.cpp
+ tr_init.cpp
+ tr_light.cpp
+ tr_main.cpp
+ tr_marks.cpp
+ tr_mesh.cpp
+ tr_model.cpp
+ tr_model_iqm.cpp
+ tr_postprocess.cpp
+ tr_scene.cpp
+ tr_shade.cpp
+ tr_shade_calc.cpp
+ tr_shader.cpp
+ tr_shadows.cpp
+ tr_sky.cpp
+ tr_subs.cpp
+ tr_surface.cpp
+ tr_vbo.cpp
+ tr_world.cpp
+ ${CMAKE_SOURCE_DIR}/src/common/puff.cpp
+ ${CMAKE_SOURCE_DIR}/src/common/q_shared.c
+ ${CMAKE_SOURCE_DIR}/src/common/q_math.c
+ )
+
+if(NOT USE_RENDERER_DLOPEN)
+
+ add_library(
+ renderergl2 STATIC
+ ${renderergl2_SRCS})
+
+ target_link_libraries(
+ renderergl2 renderercommon
+ ${SDL2_LIBRARIES})
+
+else(NOT USE_RENDERER_DLOPEN)
+
+ add_library(
+ renderergl2 SHARED
+ ${renderergl2_SRCS}
+ )
+
+ target_link_libraries(
+ renderergl2
+ renderercommon
+ ${FRAMEWORKS}
+ ${OPENGL_LIBRARIES}
+ ${SDL2_LIBRARIES}
+ )
+ add_custom_command(
+ TARGET renderergl2
+ POST_BUILD COMMAND ${CMAKE_COMMAND}
+ ARGS -E copy ${CMAKE_CURRENT_BINARY_DIR}/librenderergl2${CMAKE_SHARED_LIBRARY_SUFFIX} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/renderer_opengl2${CMAKE_SHARED_LIBRARY_SUFFIX}
+ )
+
+endif(NOT USE_RENDERER_DLOPEN)
+
diff --git a/src/renderergl2/glsl/bokeh_fp.glsl b/src/renderergl2/glsl/bokeh_fp.glsl
new file mode 100644
index 0000000..d08816a
--- /dev/null
+++ b/src/renderergl2/glsl/bokeh_fp.glsl
@@ -0,0 +1,70 @@
+uniform sampler2D u_TextureMap;
+
+uniform vec4 u_Color;
+
+uniform vec2 u_InvTexRes;
+varying vec2 var_TexCoords;
+
+void main()
+{
+ vec4 color;
+ vec2 tc;
+
+#if 0
+ float c[7] = float[7](1.0, 0.9659258263, 0.8660254038, 0.7071067812, 0.5, 0.2588190451, 0.0);
+
+ tc = var_TexCoords + u_InvTexRes * vec2( c[0], c[6]); color = texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[1], c[5]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[2], c[4]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[3], c[3]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[4], c[2]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[5], c[1]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[6], c[0]); color += texture2D(u_TextureMap, tc);
+
+ tc = var_TexCoords + u_InvTexRes * vec2( c[1], -c[5]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[2], -c[4]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[3], -c[3]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[4], -c[2]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[5], -c[1]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[6], -c[0]); color += texture2D(u_TextureMap, tc);
+
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[0], c[6]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[1], c[5]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[2], c[4]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[3], c[3]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[4], c[2]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[5], c[1]); color += texture2D(u_TextureMap, tc);
+
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[1], -c[5]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[2], -c[4]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[3], -c[3]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[4], -c[2]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[5], -c[1]); color += texture2D(u_TextureMap, tc);
+
+ gl_FragColor = color * 0.04166667 * u_Color;
+#endif
+
+ float c[5] = float[5](1.0, 0.9238795325, 0.7071067812, 0.3826834324, 0.0);
+
+ tc = var_TexCoords + u_InvTexRes * vec2( c[0], c[4]); color = texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[1], c[3]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[2], c[2]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[3], c[1]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[4], c[0]); color += texture2D(u_TextureMap, tc);
+
+ tc = var_TexCoords + u_InvTexRes * vec2( c[1], -c[3]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[2], -c[2]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[3], -c[1]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( c[4], -c[0]); color += texture2D(u_TextureMap, tc);
+
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[0], c[4]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[1], c[3]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[2], c[2]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[3], c[1]); color += texture2D(u_TextureMap, tc);
+
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[1], -c[3]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[2], -c[2]); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( -c[3], -c[1]); color += texture2D(u_TextureMap, tc);
+
+ gl_FragColor = color * 0.0625 * u_Color;
+}
diff --git a/src/renderergl2/glsl/bokeh_vp.glsl b/src/renderergl2/glsl/bokeh_vp.glsl
new file mode 100644
index 0000000..bdaa74a
--- /dev/null
+++ b/src/renderergl2/glsl/bokeh_vp.glsl
@@ -0,0 +1,13 @@
+attribute vec3 attr_Position;
+attribute vec4 attr_TexCoord0;
+
+uniform mat4 u_ModelViewProjectionMatrix;
+
+varying vec2 var_TexCoords;
+
+
+void main()
+{
+ gl_Position = u_ModelViewProjectionMatrix * vec4(attr_Position, 1.0);
+ var_TexCoords = attr_TexCoord0.st;
+}
diff --git a/src/renderergl2/glsl/calclevels4x_fp.glsl b/src/renderergl2/glsl/calclevels4x_fp.glsl
new file mode 100644
index 0000000..8246c4b
--- /dev/null
+++ b/src/renderergl2/glsl/calclevels4x_fp.glsl
@@ -0,0 +1,60 @@
+uniform sampler2D u_TextureMap;
+
+uniform vec4 u_Color;
+
+uniform vec2 u_InvTexRes;
+varying vec2 var_TexCoords;
+
+const vec3 LUMINANCE_VECTOR = vec3(0.2125, 0.7154, 0.0721); //vec3(0.299, 0.587, 0.114);
+
+vec3 GetValues(vec2 offset, vec3 current)
+{
+ vec2 tc = var_TexCoords + u_InvTexRes * offset;
+ vec3 minAvgMax = texture2D(u_TextureMap, tc).rgb;
+
+#ifdef FIRST_PASS
+
+ #if defined(USE_PBR)
+ minAvgMax *= minAvgMax;
+ #endif
+
+ float lumi = max(dot(LUMINANCE_VECTOR, minAvgMax), 0.000001);
+ float loglumi = clamp(log2(lumi), -10.0, 10.0);
+ minAvgMax = vec3(loglumi * 0.05 + 0.5);
+#endif
+
+ return vec3(min(current.x, minAvgMax.x), current.y + minAvgMax.y, max(current.z, minAvgMax.z));
+}
+
+void main()
+{
+ vec3 current = vec3(1.0, 0.0, 0.0);
+
+#ifdef FIRST_PASS
+ current = GetValues(vec2( 0.0, 0.0), current);
+#else
+ current = GetValues(vec2(-1.5, -1.5), current);
+ current = GetValues(vec2(-0.5, -1.5), current);
+ current = GetValues(vec2( 0.5, -1.5), current);
+ current = GetValues(vec2( 1.5, -1.5), current);
+
+ current = GetValues(vec2(-1.5, -0.5), current);
+ current = GetValues(vec2(-0.5, -0.5), current);
+ current = GetValues(vec2( 0.5, -0.5), current);
+ current = GetValues(vec2( 1.5, -0.5), current);
+
+ current = GetValues(vec2(-1.5, 0.5), current);
+ current = GetValues(vec2(-0.5, 0.5), current);
+ current = GetValues(vec2( 0.5, 0.5), current);
+ current = GetValues(vec2( 1.5, 0.5), current);
+
+ current = GetValues(vec2(-1.5, 1.5), current);
+ current = GetValues(vec2(-0.5, 1.5), current);
+ current = GetValues(vec2( 0.5, 1.5), current);
+ current = GetValues(vec2( 1.5, 1.5), current);
+
+ current.y *= 0.0625;
+#endif
+
+ gl_FragColor = vec4(current, 1.0f);
+}
diff --git a/src/renderergl2/glsl/calclevels4x_vp.glsl b/src/renderergl2/glsl/calclevels4x_vp.glsl
new file mode 100644
index 0000000..bdaa74a
--- /dev/null
+++ b/src/renderergl2/glsl/calclevels4x_vp.glsl
@@ -0,0 +1,13 @@
+attribute vec3 attr_Position;
+attribute vec4 attr_TexCoord0;
+
+uniform mat4 u_ModelViewProjectionMatrix;
+
+varying vec2 var_TexCoords;
+
+
+void main()
+{
+ gl_Position = u_ModelViewProjectionMatrix * vec4(attr_Position, 1.0);
+ var_TexCoords = attr_TexCoord0.st;
+}
diff --git a/src/renderergl2/glsl/depthblur_fp.glsl b/src/renderergl2/glsl/depthblur_fp.glsl
new file mode 100644
index 0000000..d71b348
--- /dev/null
+++ b/src/renderergl2/glsl/depthblur_fp.glsl
@@ -0,0 +1,82 @@
+uniform sampler2D u_ScreenImageMap;
+uniform sampler2D u_ScreenDepthMap;
+
+uniform vec4 u_ViewInfo; // zfar / znear, zfar, 1/width, 1/height
+varying vec2 var_ScreenTex;
+
+//float gauss[8] = float[8](0.17, 0.17, 0.16, 0.14, 0.12, 0.1, 0.08, 0.06);
+//float gauss[5] = float[5](0.30, 0.23, 0.097, 0.024, 0.0033);
+float gauss[4] = float[4](0.40, 0.24, 0.054, 0.0044);
+//float gauss[3] = float[3](0.60, 0.19, 0.0066);
+#define BLUR_SIZE 4
+
+#if !defined(USE_DEPTH)
+//#define USE_GAUSS
+#endif
+
+float getLinearDepth(sampler2D depthMap, const vec2 tex, const float zFarDivZNear)
+{
+ float sampleZDivW = texture2D(depthMap, tex).r;
+ return 1.0 / mix(zFarDivZNear, 1.0, sampleZDivW);
+}
+
+vec4 depthGaussian1D(sampler2D imageMap, sampler2D depthMap, vec2 tex, float zFarDivZNear, float zFar, vec2 scale)
+{
+
+#if defined(USE_DEPTH)
+ float depthCenter = getLinearDepth(depthMap, tex, zFarDivZNear);
+ vec2 slope = vec2(dFdx(depthCenter), dFdy(depthCenter)) / vec2(dFdx(tex.x), dFdy(tex.y));
+ scale /= clamp(zFarDivZNear * depthCenter / 32.0, 1.0, 2.0);
+#endif
+
+#if defined(USE_HORIZONTAL_BLUR)
+ vec2 direction = vec2(scale.x * 2.0, 0.0);
+ vec2 nudge = vec2(0.0, scale.y * 0.5);
+#else // if defined(USE_VERTICAL_BLUR)
+ vec2 direction = vec2(0.0, scale.y * 2.0);
+ vec2 nudge = vec2(-scale.x * 0.5, 0.0);
+#endif
+
+#if defined(USE_GAUSS)
+ vec4 result = texture2D(imageMap, tex) * gauss[0];
+ float total = gauss[0];
+#else
+ vec4 result = texture2D(imageMap, tex);
+ float total = 1.0;
+#endif
+
+ float zLimit = 5.0 / zFar;
+ int i, j;
+ for (i = 0; i < 2; i++)
+ {
+ for (j = 1; j < BLUR_SIZE; j++)
+ {
+ vec2 offset = direction * (float(j) - 0.25) + nudge;
+#if defined(USE_DEPTH)
+ float depthSample = getLinearDepth(depthMap, tex + offset, zFarDivZNear);
+ float depthExpected = depthCenter + dot(slope, offset);
+ float useSample = float(abs(depthSample - depthExpected) < zLimit);
+#else
+ float useSample = 1.0;
+#endif
+#if defined(USE_GAUSS)
+ result += texture2D(imageMap, tex + offset) * (gauss[j] * useSample);
+ total += gauss[j] * useSample;
+#else
+ result += texture2D(imageMap, tex + offset) * useSample;
+ total += useSample;
+#endif
+ nudge = -nudge;
+ }
+
+ direction = -direction;
+ nudge = -nudge;
+ }
+
+ return result / total;
+}
+
+void main()
+{
+ gl_FragColor = depthGaussian1D(u_ScreenImageMap, u_ScreenDepthMap, var_ScreenTex, u_ViewInfo.x, u_ViewInfo.y, u_ViewInfo.zw);
+}
diff --git a/src/renderergl2/glsl/depthblur_vp.glsl b/src/renderergl2/glsl/depthblur_vp.glsl
new file mode 100644
index 0000000..9c47660
--- /dev/null
+++ b/src/renderergl2/glsl/depthblur_vp.glsl
@@ -0,0 +1,16 @@
+attribute vec4 attr_Position;
+attribute vec4 attr_TexCoord0;
+
+uniform vec4 u_ViewInfo; // zfar / znear, zfar, 1/width, 1/height
+
+varying vec2 var_ScreenTex;
+
+void main()
+{
+ gl_Position = attr_Position;
+ vec2 wh = vec2(1.0) / u_ViewInfo.zw - vec2(1.0);
+ var_ScreenTex = (floor(attr_TexCoord0.xy * wh) + vec2(0.5)) * u_ViewInfo.zw;
+
+ //vec2 screenCoords = gl_Position.xy / gl_Position.w;
+ //var_ScreenTex = screenCoords * 0.5 + 0.5;
+}
diff --git a/src/renderergl2/glsl/dlight_fp.glsl b/src/renderergl2/glsl/dlight_fp.glsl
new file mode 100644
index 0000000..41be049
--- /dev/null
+++ b/src/renderergl2/glsl/dlight_fp.glsl
@@ -0,0 +1,32 @@
+uniform sampler2D u_DiffuseMap;
+
+uniform int u_AlphaTest;
+
+varying vec2 var_Tex1;
+varying vec4 var_Color;
+
+
+void main()
+{
+ vec4 color = texture2D(u_DiffuseMap, var_Tex1);
+
+ float alpha = color.a * var_Color.a;
+ if (u_AlphaTest == 1)
+ {
+ if (alpha == 0.0)
+ discard;
+ }
+ else if (u_AlphaTest == 2)
+ {
+ if (alpha >= 0.5)
+ discard;
+ }
+ else if (u_AlphaTest == 3)
+ {
+ if (alpha < 0.5)
+ discard;
+ }
+
+ gl_FragColor.rgb = color.rgb * var_Color.rgb;
+ gl_FragColor.a = alpha;
+}
diff --git a/src/renderergl2/glsl/dlight_vp.glsl b/src/renderergl2/glsl/dlight_vp.glsl
new file mode 100644
index 0000000..c326bd7
--- /dev/null
+++ b/src/renderergl2/glsl/dlight_vp.glsl
@@ -0,0 +1,92 @@
+attribute vec3 attr_Position;
+attribute vec4 attr_TexCoord0;
+attribute vec3 attr_Normal;
+
+uniform vec4 u_DlightInfo;
+
+#if defined(USE_DEFORM_VERTEXES)
+uniform int u_DeformGen;
+uniform float u_DeformParams[5];
+uniform float u_Time;
+#endif
+
+uniform vec4 u_Color;
+uniform mat4 u_ModelViewProjectionMatrix;
+
+varying vec2 var_Tex1;
+varying vec4 var_Color;
+
+#if defined(USE_DEFORM_VERTEXES)
+vec3 DeformPosition(const vec3 pos, const vec3 normal, const vec2 st)
+{
+ if (u_DeformGen == 0)
+ {
+ return pos;
+ }
+
+ float base = u_DeformParams[0];
+ float amplitude = u_DeformParams[1];
+ float phase = u_DeformParams[2];
+ float frequency = u_DeformParams[3];
+ float spread = u_DeformParams[4];
+
+ if (u_DeformGen == DGEN_BULGE)
+ {
+ phase *= st.x;
+ }
+ else // if (u_DeformGen <= DGEN_WAVE_INVERSE_SAWTOOTH)
+ {
+ phase += dot(pos.xyz, vec3(spread));
+ }
+
+ float value = phase + (u_Time * frequency);
+ float func;
+
+ if (u_DeformGen == DGEN_WAVE_SIN)
+ {
+ func = sin(value * 2.0 * M_PI);
+ }
+ else if (u_DeformGen == DGEN_WAVE_SQUARE)
+ {
+ func = sign(0.5 - fract(value));
+ }
+ else if (u_DeformGen == DGEN_WAVE_TRIANGLE)
+ {
+ func = abs(fract(value + 0.75) - 0.5) * 4.0 - 1.0;
+ }
+ else if (u_DeformGen == DGEN_WAVE_SAWTOOTH)
+ {
+ func = fract(value);
+ }
+ else if (u_DeformGen == DGEN_WAVE_INVERSE_SAWTOOTH)
+ {
+ func = (1.0 - fract(value));
+ }
+ else // if (u_DeformGen == DGEN_BULGE)
+ {
+ func = sin(value);
+ }
+
+ return pos + normal * (base + func * amplitude);
+}
+#endif
+
+void main()
+{
+ vec3 position = attr_Position;
+ vec3 normal = attr_Normal;
+
+#if defined(USE_DEFORM_VERTEXES)
+ position = DeformPosition(position, normal, attr_TexCoord0.st);
+#endif
+
+ gl_Position = u_ModelViewProjectionMatrix * vec4(position, 1.0);
+
+ vec3 dist = u_DlightInfo.xyz - position;
+
+ var_Tex1 = dist.xy * u_DlightInfo.a + vec2(0.5);
+ float dlightmod = step(0.0, dot(dist, normal));
+ dlightmod *= clamp(2.0 * (1.0 - abs(dist.z) * u_DlightInfo.a), 0.0, 1.0);
+
+ var_Color = u_Color * dlightmod;
+}
diff --git a/src/renderergl2/glsl/down4x_fp.glsl b/src/renderergl2/glsl/down4x_fp.glsl
new file mode 100644
index 0000000..0f88fb2
--- /dev/null
+++ b/src/renderergl2/glsl/down4x_fp.glsl
@@ -0,0 +1,34 @@
+uniform sampler2D u_TextureMap;
+
+uniform vec2 u_InvTexRes;
+varying vec2 var_TexCoords;
+
+void main()
+{
+ vec4 color;
+ vec2 tc;
+
+ tc = var_TexCoords + u_InvTexRes * vec2(-1.5, -1.5); color = texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2(-0.5, -1.5); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( 0.5, -1.5); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( 1.5, -1.5); color += texture2D(u_TextureMap, tc);
+
+ tc = var_TexCoords + u_InvTexRes * vec2(-1.5, -0.5); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2(-0.5, -0.5); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( 0.5, -0.5); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( 1.5, -0.5); color += texture2D(u_TextureMap, tc);
+
+ tc = var_TexCoords + u_InvTexRes * vec2(-1.5, 0.5); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2(-0.5, 0.5); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( 0.5, 0.5); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( 1.5, 0.5); color += texture2D(u_TextureMap, tc);
+
+ tc = var_TexCoords + u_InvTexRes * vec2(-1.5, 1.5); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2(-0.5, 1.5); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( 0.5, 1.5); color += texture2D(u_TextureMap, tc);
+ tc = var_TexCoords + u_InvTexRes * vec2( 1.5, 1.5); color += texture2D(u_TextureMap, tc);
+
+ color *= 0.0625;
+
+ gl_FragColor = color;
+}
diff --git a/src/renderergl2/glsl/down4x_vp.glsl b/src/renderergl2/glsl/down4x_vp.glsl
new file mode 100644
index 0000000..bdaa74a
--- /dev/null
+++ b/src/renderergl2/glsl/down4x_vp.glsl
@@ -0,0 +1,13 @@
+attribute vec3 attr_Position;
+attribute vec4 attr_TexCoord0;
+
+uniform mat4 u_ModelViewProjectionMatrix;
+
+varying vec2 var_TexCoords;
+
+
+void main()
+{
+ gl_Position = u_ModelViewProjectionMatrix * vec4(attr_Position, 1.0);
+ var_TexCoords = attr_TexCoord0.st;
+}
diff --git a/src/renderergl2/glsl/fogpass_fp.glsl b/src/renderergl2/glsl/fogpass_fp.glsl
new file mode 100644
index 0000000..e2ad465
--- /dev/null
+++ b/src/renderergl2/glsl/fogpass_fp.glsl
@@ -0,0 +1,9 @@
+uniform vec4 u_Color;
+
+varying float var_Scale;
+
+void main()
+{
+ gl_FragColor = u_Color;
+ gl_FragColor.a = sqrt(clamp(var_Scale, 0.0, 1.0));
+}
diff --git a/src/renderergl2/glsl/fogpass_vp.glsl b/src/renderergl2/glsl/fogpass_vp.glsl
new file mode 100644
index 0000000..c8ec9a9
--- /dev/null
+++ b/src/renderergl2/glsl/fogpass_vp.glsl
@@ -0,0 +1,117 @@
+attribute vec3 attr_Position;
+attribute vec3 attr_Normal;
+
+attribute vec4 attr_TexCoord0;
+
+#if defined(USE_VERTEX_ANIMATION)
+attribute vec3 attr_Position2;
+attribute vec3 attr_Normal2;
+#endif
+
+uniform vec4 u_FogDistance;
+uniform vec4 u_FogDepth;
+uniform float u_FogEyeT;
+
+#if defined(USE_DEFORM_VERTEXES)
+uniform int u_DeformGen;
+uniform float u_DeformParams[5];
+#endif
+
+uniform float u_Time;
+uniform mat4 u_ModelViewProjectionMatrix;
+
+#if defined(USE_VERTEX_ANIMATION)
+uniform float u_VertexLerp;
+#endif
+
+uniform vec4 u_Color;
+
+varying float var_Scale;
+
+#if defined(USE_DEFORM_VERTEXES)
+vec3 DeformPosition(const vec3 pos, const vec3 normal, const vec2 st)
+{
+ if (u_DeformGen == 0)
+ {
+ return pos;
+ }
+
+ float base = u_DeformParams[0];
+ float amplitude = u_DeformParams[1];
+ float phase = u_DeformParams[2];
+ float frequency = u_DeformParams[3];
+ float spread = u_DeformParams[4];
+
+ if (u_DeformGen == DGEN_BULGE)
+ {
+ phase *= st.x;
+ }
+ else // if (u_DeformGen <= DGEN_WAVE_INVERSE_SAWTOOTH)
+ {
+ phase += dot(pos.xyz, vec3(spread));
+ }
+
+ float value = phase + (u_Time * frequency);
+ float func;
+
+ if (u_DeformGen == DGEN_WAVE_SIN)
+ {
+ func = sin(value * 2.0 * M_PI);
+ }
+ else if (u_DeformGen == DGEN_WAVE_SQUARE)
+ {
+ func = sign(0.5 - fract(value));
+ }
+ else if (u_DeformGen == DGEN_WAVE_TRIANGLE)
+ {
+ func = abs(fract(value + 0.75) - 0.5) * 4.0 - 1.0;
+ }
+ else if (u_DeformGen == DGEN_WAVE_SAWTOOTH)
+ {
+ func = fract(value);
+ }
+ else if (u_DeformGen == DGEN_WAVE_INVERSE_SAWTOOTH)
+ {
+ func = (1.0 - fract(value));
+ }
+ else // if (u_DeformGen == DGEN_BULGE)
+ {
+ func = sin(value);
+ }
+
+ return pos + normal * (base + func * amplitude);
+}
+#endif
+
+float CalcFog(vec3 position)
+{
+ float s = dot(vec4(position, 1.0), u_FogDistance) * 8.0;
+ float t = dot(vec4(position, 1.0), u_FogDepth);
+
+ float eyeOutside = float(u_FogEyeT < 0.0);
+ float fogged = float(t >= eyeOutside);
+
+ t += 1e-6;
+ t *= fogged / (t - u_FogEyeT * eyeOutside);
+
+ return s * t;
+}
+
+void main()
+{
+#if defined(USE_VERTEX_ANIMATION)
+ vec3 position = mix(attr_Position, attr_Position2, u_VertexLerp);
+ vec3 normal = mix(attr_Normal, attr_Normal2, u_VertexLerp);
+#else
+ vec3 position = attr_Position;
+ vec3 normal = attr_Normal;
+#endif
+
+#if defined(USE_DEFORM_VERTEXES)
+ position.xyz = DeformPosition(position.xyz, normal, attr_TexCoord0.st);
+#endif
+
+ gl_Position = u_ModelViewProjectionMatrix * vec4(position, 1.0);
+
+ var_Scale = CalcFog(position) * u_Color.a * u_Color.a;
+}
diff --git a/src/renderergl2/glsl/generic_fp.glsl b/src/renderergl2/glsl/generic_fp.glsl
new file mode 100644
index 0000000..c0a4940
--- /dev/null
+++ b/src/renderergl2/glsl/generic_fp.glsl
@@ -0,0 +1,33 @@
+uniform sampler2D u_DiffuseMap;
+
+uniform int u_AlphaTest;
+
+varying vec2 var_DiffuseTex;
+
+varying vec4 var_Color;
+
+
+void main()
+{
+ vec4 color = texture2D(u_DiffuseMap, var_DiffuseTex);
+
+ float alpha = color.a * var_Color.a;
+ if (u_AlphaTest == 1)
+ {
+ if (alpha == 0.0)
+ discard;
+ }
+ else if (u_AlphaTest == 2)
+ {
+ if (alpha >= 0.5)
+ discard;
+ }
+ else if (u_AlphaTest == 3)
+ {
+ if (alpha < 0.5)
+ discard;
+ }
+
+ gl_FragColor.rgb = color.rgb * var_Color.rgb;
+ gl_FragColor.a = alpha;
+}
diff --git a/src/renderergl2/glsl/generic_vp.glsl b/src/renderergl2/glsl/generic_vp.glsl
new file mode 100644
index 0000000..bbe08e8
--- /dev/null
+++ b/src/renderergl2/glsl/generic_vp.glsl
@@ -0,0 +1,239 @@
+attribute vec3 attr_Position;
+attribute vec3 attr_Normal;
+
+#if defined(USE_VERTEX_ANIMATION)
+attribute vec3 attr_Position2;
+attribute vec3 attr_Normal2;
+#endif
+
+attribute vec4 attr_Color;
+attribute vec4 attr_TexCoord0;
+
+#if defined(USE_TCGEN)
+attribute vec4 attr_TexCoord1;
+#endif
+
+uniform vec4 u_DiffuseTexMatrix;
+uniform vec4 u_DiffuseTexOffTurb;
+
+#if defined(USE_TCGEN) || defined(USE_RGBAGEN)
+uniform vec3 u_LocalViewOrigin;
+#endif
+
+#if defined(USE_TCGEN)
+uniform int u_TCGen0;
+uniform vec3 u_TCGen0Vector0;
+uniform vec3 u_TCGen0Vector1;
+#endif
+
+#if defined(USE_FOG)
+uniform vec4 u_FogDistance;
+uniform vec4 u_FogDepth;
+uniform float u_FogEyeT;
+uniform vec4 u_FogColorMask;
+#endif
+
+#if defined(USE_DEFORM_VERTEXES)
+uniform int u_DeformGen;
+uniform float u_DeformParams[5];
+uniform float u_Time;
+#endif
+
+uniform mat4 u_ModelViewProjectionMatrix;
+uniform vec4 u_BaseColor;
+uniform vec4 u_VertColor;
+
+#if defined(USE_RGBAGEN)
+uniform int u_ColorGen;
+uniform int u_AlphaGen;
+uniform vec3 u_AmbientLight;
+uniform vec3 u_DirectedLight;
+uniform vec3 u_ModelLightDir;
+uniform float u_PortalRange;
+#endif
+
+#if defined(USE_VERTEX_ANIMATION)
+uniform float u_VertexLerp;
+#endif
+
+varying vec2 var_DiffuseTex;
+varying vec4 var_Color;
+
+#if defined(USE_DEFORM_VERTEXES)
+vec3 DeformPosition(const vec3 pos, const vec3 normal, const vec2 st)
+{
+ float base = u_DeformParams[0];
+ float amplitude = u_DeformParams[1];
+ float phase = u_DeformParams[2];
+ float frequency = u_DeformParams[3];
+ float spread = u_DeformParams[4];
+
+ if (u_DeformGen == DGEN_BULGE)
+ {
+ phase *= st.x;
+ }
+ else // if (u_DeformGen <= DGEN_WAVE_INVERSE_SAWTOOTH)
+ {
+ phase += dot(pos.xyz, vec3(spread));
+ }
+
+ float value = phase + (u_Time * frequency);
+ float func;
+
+ if (u_DeformGen == DGEN_WAVE_SIN)
+ {
+ func = sin(value * 2.0 * M_PI);
+ }
+ else if (u_DeformGen == DGEN_WAVE_SQUARE)
+ {
+ func = sign(fract(0.5 - value));
+ }
+ else if (u_DeformGen == DGEN_WAVE_TRIANGLE)
+ {
+ func = abs(fract(value + 0.75) - 0.5) * 4.0 - 1.0;
+ }
+ else if (u_DeformGen == DGEN_WAVE_SAWTOOTH)
+ {
+ func = fract(value);
+ }
+ else if (u_DeformGen == DGEN_WAVE_INVERSE_SAWTOOTH)
+ {
+ func = (1.0 - fract(value));
+ }
+ else // if (u_DeformGen == DGEN_BULGE)
+ {
+ func = sin(value);
+ }
+
+ return pos + normal * (base + func * amplitude);
+}
+#endif
+
+#if defined(USE_TCGEN)
+vec2 GenTexCoords(int TCGen, vec3 position, vec3 normal, vec3 TCGenVector0, vec3 TCGenVector1)
+{
+ vec2 tex = attr_TexCoord0.st;
+
+ if (TCGen == TCGEN_LIGHTMAP)
+ {
+ tex = attr_TexCoord1.st;
+ }
+ else if (TCGen == TCGEN_ENVIRONMENT_MAPPED)
+ {
+ vec3 viewer = normalize(u_LocalViewOrigin - position);
+ vec2 ref = reflect(viewer, normal).yz;
+ tex.s = ref.x * -0.5 + 0.5;
+ tex.t = ref.y * 0.5 + 0.5;
+ }
+ else if (TCGen == TCGEN_VECTOR)
+ {
+ tex = vec2(dot(position, TCGenVector0), dot(position, TCGenVector1));
+ }
+
+ return tex;
+}
+#endif
+
+#if defined(USE_TCMOD)
+vec2 ModTexCoords(vec2 st, vec3 position, vec4 texMatrix, vec4 offTurb)
+{
+ float amplitude = offTurb.z;
+ float phase = offTurb.w * 2.0 * M_PI;
+ vec2 st2;
+ st2.x = st.x * texMatrix.x + (st.y * texMatrix.z + offTurb.x);
+ st2.y = st.x * texMatrix.y + (st.y * texMatrix.w + offTurb.y);
+
+ vec2 offsetPos = vec2(position.x + position.z, position.y);
+
+ vec2 texOffset = sin(offsetPos * (2.0 * M_PI / 1024.0) + vec2(phase));
+
+ return st2 + texOffset * amplitude;
+}
+#endif
+
+#if defined(USE_RGBAGEN)
+vec4 CalcColor(vec3 position, vec3 normal)
+{
+ vec4 color = u_VertColor * attr_Color + u_BaseColor;
+
+ if (u_ColorGen == CGEN_LIGHTING_DIFFUSE)
+ {
+ float incoming = clamp(dot(normal, u_ModelLightDir), 0.0, 1.0);
+
+ color.rgb = clamp(u_DirectedLight * incoming + u_AmbientLight, 0.0, 1.0);
+ }
+
+ vec3 viewer = u_LocalViewOrigin - position;
+
+ if (u_AlphaGen == AGEN_LIGHTING_SPECULAR)
+ {
+ vec3 lightDir = normalize(vec3(-960.0, 1980.0, 96.0) - position);
+ vec3 reflected = -reflect(lightDir, normal);
+
+ color.a = clamp(dot(reflected, normalize(viewer)), 0.0, 1.0);
+ color.a *= color.a;
+ color.a *= color.a;
+ }
+ else if (u_AlphaGen == AGEN_PORTAL)
+ {
+ color.a = clamp(length(viewer) / u_PortalRange, 0.0, 1.0);
+ }
+
+ return color;
+}
+#endif
+
+#if defined(USE_FOG)
+float CalcFog(vec3 position)
+{
+ float s = dot(vec4(position, 1.0), u_FogDistance) * 8.0;
+ float t = dot(vec4(position, 1.0), u_FogDepth);
+
+ float eyeOutside = float(u_FogEyeT < 0.0);
+ float fogged = float(t >= eyeOutside);
+
+ t += 1e-6;
+ t *= fogged / (t - u_FogEyeT * eyeOutside);
+
+ return s * t;
+}
+#endif
+
+void main()
+{
+#if defined(USE_VERTEX_ANIMATION)
+ vec3 position = mix(attr_Position, attr_Position2, u_VertexLerp);
+ vec3 normal = mix(attr_Normal, attr_Normal2, u_VertexLerp);
+#else
+ vec3 position = attr_Position;
+ vec3 normal = attr_Normal;
+#endif
+
+#if defined(USE_DEFORM_VERTEXES)
+ position = DeformPosition(position, normal, attr_TexCoord0.st);
+#endif
+
+ gl_Position = u_ModelViewProjectionMatrix * vec4(position, 1.0);
+
+#if defined(USE_TCGEN)
+ vec2 tex = GenTexCoords(u_TCGen0, position, normal, u_TCGen0Vector0, u_TCGen0Vector1);
+#else
+ vec2 tex = attr_TexCoord0.st;
+#endif
+
+#if defined(USE_TCMOD)
+ var_DiffuseTex = ModTexCoords(tex, position, u_DiffuseTexMatrix, u_DiffuseTexOffTurb);
+#else
+ var_DiffuseTex = tex;
+#endif
+
+#if defined(USE_RGBAGEN)
+ var_Color = CalcColor(position, normal);
+#else
+ var_Color = u_VertColor * attr_Color + u_BaseColor;
+#endif
+
+#if defined(USE_FOG)
+ var_Color *= vec4(1.0) - u_FogColorMask * sqrt(clamp(CalcFog(position), 0.0, 1.0));
+#endif
+}
diff --git a/src/renderergl2/glsl/lightall_fp.glsl b/src/renderergl2/glsl/lightall_fp.glsl
new file mode 100644
index 0000000..8e7c9b4
--- /dev/null
+++ b/src/renderergl2/glsl/lightall_fp.glsl
@@ -0,0 +1,429 @@
+uniform sampler2D u_DiffuseMap;
+
+#if defined(USE_LIGHTMAP)
+uniform sampler2D u_LightMap;
+#endif
+
+#if defined(USE_NORMALMAP)
+uniform sampler2D u_NormalMap;
+#endif
+
+#if defined(USE_DELUXEMAP)
+uniform sampler2D u_DeluxeMap;
+#endif
+
+#if defined(USE_SPECULARMAP)
+uniform sampler2D u_SpecularMap;
+#endif
+
+#if defined(USE_SHADOWMAP)
+uniform sampler2D u_ShadowMap;
+#endif
+
+#if defined(USE_CUBEMAP)
+uniform samplerCube u_CubeMap;
+#endif
+
+#if defined(USE_NORMALMAP) || defined(USE_DELUXEMAP) || defined(USE_SPECULARMAP) || defined(USE_CUBEMAP)
+// y = deluxe, w = cube
+uniform vec4 u_EnableTextures;
+#endif
+
+#if defined(USE_PRIMARY_LIGHT) || defined(USE_SHADOWMAP)
+uniform vec3 u_PrimaryLightColor;
+uniform vec3 u_PrimaryLightAmbient;
+#endif
+
+#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)
+uniform vec4 u_NormalScale;
+uniform vec4 u_SpecularScale;
+#endif
+
+#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)
+#if defined(USE_CUBEMAP)
+uniform vec4 u_CubeMapInfo;
+#endif
+#endif
+
+uniform int u_AlphaTest;
+
+varying vec4 var_TexCoords;
+
+varying vec4 var_Color;
+#if (defined(USE_LIGHT) && !defined(USE_FAST_LIGHT))
+varying vec4 var_ColorAmbient;
+#endif
+
+#if (defined(USE_LIGHT) && !defined(USE_FAST_LIGHT))
+varying vec4 var_Normal;
+varying vec4 var_Tangent;
+varying vec4 var_Bitangent;
+#endif
+
+#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)
+varying vec4 var_LightDir;
+#endif
+
+#if defined(USE_PRIMARY_LIGHT) || defined(USE_SHADOWMAP)
+varying vec4 var_PrimaryLightDir;
+#endif
+
+
+#define EPSILON 0.00000001
+
+#if defined(USE_PARALLAXMAP)
+float SampleDepth(sampler2D normalMap, vec2 t)
+{
+ #if defined(SWIZZLE_NORMALMAP)
+ return 1.0 - texture2D(normalMap, t).r;
+ #else
+ return 1.0 - texture2D(normalMap, t).a;
+ #endif
+}
+
+float RayIntersectDisplaceMap(vec2 dp, vec2 ds, sampler2D normalMap)
+{
+ const int linearSearchSteps = 16;
+ const int binarySearchSteps = 6;
+
+ // current size of search window
+ float size = 1.0 / float(linearSearchSteps);
+
+ // current depth position
+ float depth = 0.0;
+
+ // best match found (starts with last position 1.0)
+ float bestDepth = 1.0;
+
+ // texture depth at best depth
+ float texDepth = 0.0;
+
+ float prevT = SampleDepth(normalMap, dp);
+ float prevTexDepth = prevT;
+
+ // search front to back for first point inside object
+ for(int i = 0; i < linearSearchSteps - 1; ++i)
+ {
+ depth += size;
+
+ float t = SampleDepth(normalMap, dp + ds * depth);
+
+ if(bestDepth > 0.996) // if no depth found yet
+ if(depth >= t)
+ {
+ bestDepth = depth; // store best depth
+ texDepth = t;
+ prevTexDepth = prevT;
+ }
+ prevT = t;
+ }
+
+ depth = bestDepth;
+
+#if !defined (USE_RELIEFMAP)
+ float div = 1.0 / (1.0 + (prevTexDepth - texDepth) * float(linearSearchSteps));
+ bestDepth -= (depth - size - prevTexDepth) * div;
+#else
+ // recurse around first point (depth) for closest match
+ for(int i = 0; i < binarySearchSteps; ++i)
+ {
+ size *= 0.5;
+
+ float t = SampleDepth(normalMap, dp + ds * depth);
+
+ if(depth >= t)
+ {
+ bestDepth = depth;
+ depth -= 2.0 * size;
+ }
+
+ depth += size;
+ }
+#endif
+
+ return bestDepth;
+}
+#endif
+
+vec3 CalcDiffuse(vec3 diffuseAlbedo, float NH, float EH, float roughness)
+{
+#if defined(USE_BURLEY)
+ // modified from https://disney-animation.s3.amazonaws.com/library/s2012_pbs_disney_brdf_notes_v2.pdf
+ float fd90 = -0.5 + EH * EH * roughness;
+ float burley = 1.0 + fd90 * 0.04 / NH;
+ burley *= burley;
+ return diffuseAlbedo * burley;
+#else
+ return diffuseAlbedo;
+#endif
+}
+
+vec3 EnvironmentBRDF(float roughness, float NE, vec3 specular)
+{
+ // from http://community.arm.com/servlet/JiveServlet/download/96891546-19496/siggraph2015-mmg-renaldas-slides.pdf
+ float v = 1.0 - max(roughness, NE);
+ v *= v * v;
+ return vec3(v) + specular;
+}
+
+vec3 CalcSpecular(vec3 specular, float NH, float EH, float roughness)
+{
+ // from http://community.arm.com/servlet/JiveServlet/download/96891546-19496/siggraph2015-mmg-renaldas-slides.pdf
+ float rr = roughness*roughness;
+ float rrrr = rr*rr;
+ float d = (NH * NH) * (rrrr - 1.0) + 1.0;
+ float v = (EH * EH) * (roughness + 0.5);
+ return specular * (rrrr / (4.0 * d * d * v));
+}
+
+
+float CalcLightAttenuation(float point, float normDist)
+{
+ // zero light at 1.0, approximating q3 style
+ // also don't attenuate directional light
+ float attenuation = (0.5 * normDist - 1.5) * point + 1.0;
+
+ // clamp attenuation
+ #if defined(NO_LIGHT_CLAMP)
+ attenuation = max(attenuation, 0.0);
+ #else
+ attenuation = clamp(attenuation, 0.0, 1.0);
+ #endif
+
+ return attenuation;
+}
+
+
+void main()
+{
+ vec3 viewDir, lightColor, ambientColor, reflectance;
+ vec3 L, N, E, H;
+ float NL, NH, NE, EH, attenuation;
+
+#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)
+ mat3 tangentToWorld = mat3(var_Tangent.xyz, var_Bitangent.xyz, var_Normal.xyz);
+ viewDir = vec3(var_Normal.w, var_Tangent.w, var_Bitangent.w);
+ E = normalize(viewDir);
+#endif
+
+ lightColor = var_Color.rgb;
+
+#if defined(USE_LIGHTMAP)
+ vec4 lightmapColor = texture2D(u_LightMap, var_TexCoords.zw);
+ #if defined(RGBM_LIGHTMAP)
+ lightmapColor.rgb *= lightmapColor.a;
+ #endif
+ #if defined(USE_PBR) && !defined(USE_FAST_LIGHT)
+ lightmapColor.rgb *= lightmapColor.rgb;
+ #endif
+ lightColor *= lightmapColor.rgb;
+#endif
+
+ vec2 texCoords = var_TexCoords.xy;
+
+#if defined(USE_PARALLAXMAP)
+ vec3 offsetDir = viewDir * tangentToWorld;
+
+ offsetDir.xy *= -u_NormalScale.a / offsetDir.z;
+
+ texCoords += offsetDir.xy * RayIntersectDisplaceMap(texCoords, offsetDir.xy, u_NormalMap);
+#endif
+
+ vec4 diffuse = texture2D(u_DiffuseMap, texCoords);
+
+ float alpha = diffuse.a * var_Color.a;
+ if (u_AlphaTest == 1)
+ {
+ if (alpha == 0.0)
+ discard;
+ }
+ else if (u_AlphaTest == 2)
+ {
+ if (alpha >= 0.5)
+ discard;
+ }
+ else if (u_AlphaTest == 3)
+ {
+ if (alpha < 0.5)
+ discard;
+ }
+
+#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)
+ L = var_LightDir.xyz;
+ #if defined(USE_DELUXEMAP)
+ L += (texture2D(u_DeluxeMap, var_TexCoords.zw).xyz - vec3(0.5)) * u_EnableTextures.y;
+ #endif
+ float sqrLightDist = dot(L, L);
+ L /= sqrt(sqrLightDist);
+
+ #if defined(USE_LIGHT_VECTOR)
+ attenuation = CalcLightAttenuation(float(var_LightDir.w > 0.0), var_LightDir.w / sqrLightDist);
+ #else
+ attenuation = 1.0;
+ #endif
+
+ #if defined(USE_NORMALMAP)
+ #if defined(SWIZZLE_NORMALMAP)
+ N.xy = texture2D(u_NormalMap, texCoords).ag - vec2(0.5);
+ #else
+ N.xy = texture2D(u_NormalMap, texCoords).rg - vec2(0.5);
+ #endif
+ N.xy *= u_NormalScale.xy;
+ N.z = sqrt(clamp((0.25 - N.x * N.x) - N.y * N.y, 0.0, 1.0));
+ N = tangentToWorld * N;
+ #else
+ N = var_Normal.xyz;
+ #endif
+
+ N = normalize(N);
+
+ #if defined(USE_SHADOWMAP)
+ vec2 shadowTex = gl_FragCoord.xy * r_FBufScale;
+ float shadowValue = texture2D(u_ShadowMap, shadowTex).r;
+
+ // surfaces not facing the light are always shadowed
+ shadowValue *= clamp(dot(N, var_PrimaryLightDir.xyz), 0.0, 1.0);
+
+ #if defined(SHADOWMAP_MODULATE)
+ lightColor *= shadowValue * (1.0 - u_PrimaryLightAmbient.r) + u_PrimaryLightAmbient.r;
+ #endif
+ #endif
+
+ #if !defined(USE_LIGHT_VECTOR)
+ ambientColor = lightColor;
+ float surfNL = clamp(dot(var_Normal.xyz, L), 0.0, 1.0);
+
+ // reserve 25% ambient to avoid black areas on normalmaps
+ lightColor *= 0.75;
+
+ // Scale the incoming light to compensate for the baked-in light angle
+ // attenuation.
+ lightColor /= max(surfNL, 0.25);
+
+ // Recover any unused light as ambient, in case attenuation is over 4x or
+ // light is below the surface
+ ambientColor = max(ambientColor - lightColor * surfNL, vec3(0.0));
+ #else
+ ambientColor = var_ColorAmbient.rgb;
+ #endif
+
+ NL = clamp(dot(N, L), 0.0, 1.0);
+ NE = clamp(dot(N, E), 0.0, 1.0);
+
+ #if defined(USE_SPECULARMAP)
+ vec4 specular = texture2D(u_SpecularMap, texCoords);
+ #else
+ vec4 specular = vec4(1.0);
+ #endif
+ specular *= u_SpecularScale;
+
+ #if defined(USE_PBR)
+ diffuse.rgb *= diffuse.rgb;
+ #endif
+
+ #if defined(USE_PBR)
+ // diffuse rgb is base color
+ // specular red is gloss
+ // specular green is metallicness
+ float gloss = specular.r;
+ float metal = specular.g;
+ specular.rgb = metal * diffuse.rgb + vec3(0.04 - 0.04 * metal);
+ diffuse.rgb *= 1.0 - metal;
+ #else
+ // diffuse rgb is diffuse
+ // specular rgb is specular reflectance at normal incidence
+ // specular alpha is gloss
+ float gloss = specular.a;
+
+ // adjust diffuse by specular reflectance, to maintain energy conservation
+ diffuse.rgb *= vec3(1.0) - specular.rgb;
+ #endif
+
+ #if defined(GLOSS_IS_GLOSS)
+ float roughness = exp2(-3.0 * gloss);
+ #elif defined(GLOSS_IS_SMOOTHNESS)
+ float roughness = 1.0 - gloss;
+ #elif defined(GLOSS_IS_ROUGHNESS)
+ float roughness = gloss;
+ #elif defined(GLOSS_IS_SHININESS)
+ float roughness = pow(2.0 / (8190.0 * gloss + 2.0), 0.25);
+ #endif
+
+ reflectance = CalcDiffuse(diffuse.rgb, NH, EH, roughness);
+
+ gl_FragColor.rgb = lightColor * reflectance * (attenuation * NL);
+ gl_FragColor.rgb += ambientColor * diffuse.rgb;
+
+ #if defined(USE_CUBEMAP)
+ reflectance = EnvironmentBRDF(roughness, NE, specular.rgb);
+
+ vec3 R = reflect(E, N);
+
+ // parallax corrected cubemap (cheaper trick)
+ // from http://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
+ vec3 parallax = u_CubeMapInfo.xyz + u_CubeMapInfo.w * viewDir;
+
+ vec3 cubeLightColor = textureCubeLod(u_CubeMap, R + parallax, ROUGHNESS_MIPS * roughness).rgb * u_EnableTextures.w;
+
+ // normalize cubemap based on last roughness mip (~diffuse)
+ // multiplying cubemap values by lighting below depends on either this or the cubemap being normalized at generation
+ //vec3 cubeLightDiffuse = max(textureCubeLod(u_CubeMap, N, ROUGHNESS_MIPS).rgb, 0.5 / 255.0);
+ //cubeLightColor /= dot(cubeLightDiffuse, vec3(0.2125, 0.7154, 0.0721));
+
+ #if defined(USE_PBR)
+ cubeLightColor *= cubeLightColor;
+ #endif
+
+ // multiply cubemap values by lighting
+ // not technically correct, but helps make reflections look less unnatural
+ //cubeLightColor *= lightColor * (attenuation * NL) + ambientColor;
+
+ gl_FragColor.rgb += cubeLightColor * reflectance;
+ #endif
+
+ #if defined(USE_PRIMARY_LIGHT) || defined(SHADOWMAP_MODULATE)
+ vec3 L2, H2;
+ float NL2, EH2, NH2;
+
+ L2 = var_PrimaryLightDir.xyz;
+
+ // enable when point lights are supported as primary lights
+ //sqrLightDist = dot(L2, L2);
+ //L2 /= sqrt(sqrLightDist);
+
+ NL2 = clamp(dot(N, L2), 0.0, 1.0);
+ H2 = normalize(L2 + E);
+ EH2 = clamp(dot(E, H2), 0.0, 1.0);
+ NH2 = clamp(dot(N, H2), 0.0, 1.0);
+
+ reflectance = CalcSpecular(specular.rgb, NH2, EH2, roughness);
+
+ // bit of a hack, with modulated shadowmaps, ignore diffuse
+ #if !defined(SHADOWMAP_MODULATE)
+ reflectance += CalcDiffuse(diffuse.rgb, NH2, EH2, roughness);
+ #endif
+
+ lightColor = u_PrimaryLightColor;
+
+ #if defined(USE_SHADOWMAP)
+ lightColor *= shadowValue;
+ #endif
+
+ // enable when point lights are supported as primary lights
+ //lightColor *= CalcLightAttenuation(float(u_PrimaryLightDir.w > 0.0), u_PrimaryLightDir.w / sqrLightDist);
+
+ gl_FragColor.rgb += lightColor * reflectance * NL2;
+ #endif
+
+ #if defined(USE_PBR)
+ gl_FragColor.rgb = sqrt(gl_FragColor.rgb);
+ #endif
+
+#else
+
+ gl_FragColor.rgb = diffuse.rgb * lightColor;
+
+#endif
+
+ gl_FragColor.a = alpha;
+}
diff --git a/src/renderergl2/glsl/lightall_vp.glsl b/src/renderergl2/glsl/lightall_vp.glsl
new file mode 100644
index 0000000..e5b3c4f
--- /dev/null
+++ b/src/renderergl2/glsl/lightall_vp.glsl
@@ -0,0 +1,246 @@
+attribute vec4 attr_TexCoord0;
+#if defined(USE_LIGHTMAP) || defined(USE_TCGEN)
+attribute vec4 attr_TexCoord1;
+#endif
+attribute vec4 attr_Color;
+
+attribute vec3 attr_Position;
+attribute vec3 attr_Normal;
+attribute vec4 attr_Tangent;
+
+#if defined(USE_VERTEX_ANIMATION)
+attribute vec3 attr_Position2;
+attribute vec3 attr_Normal2;
+attribute vec4 attr_Tangent2;
+#endif
+
+#if defined(USE_LIGHT) && !defined(USE_LIGHT_VECTOR)
+attribute vec3 attr_LightDirection;
+#endif
+
+#if defined(USE_DELUXEMAP)
+uniform vec4 u_EnableTextures; // x = normal, y = deluxe, z = specular, w = cube
+#endif
+
+#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)
+uniform vec3 u_ViewOrigin;
+#endif
+
+#if defined(USE_TCGEN)
+uniform int u_TCGen0;
+uniform vec3 u_TCGen0Vector0;
+uniform vec3 u_TCGen0Vector1;
+uniform vec3 u_LocalViewOrigin;
+#endif
+
+#if defined(USE_TCMOD)
+uniform vec4 u_DiffuseTexMatrix;
+uniform vec4 u_DiffuseTexOffTurb;
+#endif
+
+uniform mat4 u_ModelViewProjectionMatrix;
+uniform vec4 u_BaseColor;
+uniform vec4 u_VertColor;
+
+#if defined(USE_MODELMATRIX)
+uniform mat4 u_ModelMatrix;
+#endif
+
+#if defined(USE_VERTEX_ANIMATION)
+uniform float u_VertexLerp;
+#endif
+
+#if defined(USE_LIGHT_VECTOR)
+uniform vec4 u_LightOrigin;
+uniform float u_LightRadius;
+uniform vec3 u_DirectedLight;
+uniform vec3 u_AmbientLight;
+#endif
+
+#if defined(USE_PRIMARY_LIGHT) || defined(USE_SHADOWMAP)
+uniform vec4 u_PrimaryLightOrigin;
+uniform float u_PrimaryLightRadius;
+#endif
+
+varying vec4 var_TexCoords;
+
+varying vec4 var_Color;
+#if defined(USE_LIGHT_VECTOR) && !defined(USE_FAST_LIGHT)
+varying vec4 var_ColorAmbient;
+#endif
+
+#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)
+varying vec4 var_Normal;
+varying vec4 var_Tangent;
+varying vec4 var_Bitangent;
+#endif
+
+#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)
+varying vec4 var_LightDir;
+#endif
+
+#if defined(USE_PRIMARY_LIGHT) || defined(USE_SHADOWMAP)
+varying vec4 var_PrimaryLightDir;
+#endif
+
+#if defined(USE_TCGEN)
+vec2 GenTexCoords(int TCGen, vec3 position, vec3 normal, vec3 TCGenVector0, vec3 TCGenVector1)
+{
+ vec2 tex = attr_TexCoord0.st;
+
+ if (TCGen == TCGEN_LIGHTMAP)
+ {
+ tex = attr_TexCoord1.st;
+ }
+ else if (TCGen == TCGEN_ENVIRONMENT_MAPPED)
+ {
+ vec3 viewer = normalize(u_LocalViewOrigin - position);
+ vec2 ref = reflect(viewer, normal).yz;
+ tex.s = ref.x * -0.5 + 0.5;
+ tex.t = ref.y * 0.5 + 0.5;
+ }
+ else if (TCGen == TCGEN_VECTOR)
+ {
+ tex = vec2(dot(position, TCGenVector0), dot(position, TCGenVector1));
+ }
+
+ return tex;
+}
+#endif
+
+#if defined(USE_TCMOD)
+vec2 ModTexCoords(vec2 st, vec3 position, vec4 texMatrix, vec4 offTurb)
+{
+ float amplitude = offTurb.z;
+ float phase = offTurb.w * 2.0 * M_PI;
+ vec2 st2;
+ st2.x = st.x * texMatrix.x + (st.y * texMatrix.z + offTurb.x);
+ st2.y = st.x * texMatrix.y + (st.y * texMatrix.w + offTurb.y);
+
+ vec2 offsetPos = vec2(position.x + position.z, position.y);
+
+ vec2 texOffset = sin(offsetPos * (2.0 * M_PI / 1024.0) + vec2(phase));
+
+ return st2 + texOffset * amplitude;
+}
+#endif
+
+
+float CalcLightAttenuation(float point, float normDist)
+{
+ // zero light at 1.0, approximating q3 style
+ // also don't attenuate directional light
+ float attenuation = (0.5 * normDist - 1.5) * point + 1.0;
+
+ // clamp attenuation
+ #if defined(NO_LIGHT_CLAMP)
+ attenuation = max(attenuation, 0.0);
+ #else
+ attenuation = clamp(attenuation, 0.0, 1.0);
+ #endif
+
+ return attenuation;
+}
+
+
+void main()
+{
+#if defined(USE_VERTEX_ANIMATION)
+ vec3 position = mix(attr_Position, attr_Position2, u_VertexLerp);
+ vec3 normal = mix(attr_Normal, attr_Normal2, u_VertexLerp);
+ #if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)
+ vec3 tangent = mix(attr_Tangent.xyz, attr_Tangent2.xyz, u_VertexLerp);
+ #endif
+#else
+ vec3 position = attr_Position;
+ vec3 normal = attr_Normal;
+ #if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)
+ vec3 tangent = attr_Tangent.xyz;
+ #endif
+#endif
+
+#if defined(USE_TCGEN)
+ vec2 texCoords = GenTexCoords(u_TCGen0, position, normal, u_TCGen0Vector0, u_TCGen0Vector1);
+#else
+ vec2 texCoords = attr_TexCoord0.st;
+#endif
+
+#if defined(USE_TCMOD)
+ var_TexCoords.xy = ModTexCoords(texCoords, position, u_DiffuseTexMatrix, u_DiffuseTexOffTurb);
+#else
+ var_TexCoords.xy = texCoords;
+#endif
+
+ gl_Position = u_ModelViewProjectionMatrix * vec4(position, 1.0);
+
+#if defined(USE_MODELMATRIX)
+ position = (u_ModelMatrix * vec4(position, 1.0)).xyz;
+ normal = (u_ModelMatrix * vec4(normal, 0.0)).xyz;
+ #if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)
+ tangent = (u_ModelMatrix * vec4(tangent, 0.0)).xyz;
+ #endif
+#endif
+
+#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)
+ vec3 bitangent = cross(normal, tangent) * attr_Tangent.w;
+#endif
+
+#if defined(USE_LIGHT_VECTOR)
+ vec3 L = u_LightOrigin.xyz - (position * u_LightOrigin.w);
+#elif defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)
+ vec3 L = attr_LightDirection;
+ #if defined(USE_MODELMATRIX)
+ L = (u_ModelMatrix * vec4(L, 0.0)).xyz;
+ #endif
+#endif
+
+#if defined(USE_LIGHTMAP)
+ var_TexCoords.zw = attr_TexCoord1.st;
+#endif
+
+ var_Color = u_VertColor * attr_Color + u_BaseColor;
+
+#if defined(USE_LIGHT_VECTOR)
+ #if defined(USE_FAST_LIGHT)
+ float sqrLightDist = dot(L, L);
+ float NL = clamp(dot(normalize(normal), L) / sqrt(sqrLightDist), 0.0, 1.0);
+ float attenuation = CalcLightAttenuation(u_LightOrigin.w, u_LightRadius * u_LightRadius / sqrLightDist);
+
+ var_Color.rgb *= u_DirectedLight * (attenuation * NL) + u_AmbientLight;
+ #else
+ var_ColorAmbient.rgb = u_AmbientLight * var_Color.rgb;
+ var_Color.rgb *= u_DirectedLight;
+ #if defined(USE_PBR)
+ var_ColorAmbient.rgb *= var_ColorAmbient.rgb;
+ #endif
+ #endif
+#endif
+
+#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT) && defined(USE_PBR)
+ var_Color.rgb *= var_Color.rgb;
+#endif
+
+#if defined(USE_PRIMARY_LIGHT) || defined(USE_SHADOWMAP)
+ var_PrimaryLightDir.xyz = u_PrimaryLightOrigin.xyz - (position * u_PrimaryLightOrigin.w);
+ var_PrimaryLightDir.w = u_PrimaryLightRadius * u_PrimaryLightRadius;
+#endif
+
+#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)
+ #if defined(USE_LIGHT_VECTOR)
+ var_LightDir = vec4(L, u_LightRadius * u_LightRadius);
+ #else
+ var_LightDir = vec4(L, 0.0);
+ #endif
+ #if defined(USE_DELUXEMAP)
+ var_LightDir -= u_EnableTextures.y * var_LightDir;
+ #endif
+#endif
+
+#if defined(USE_LIGHT) && !defined(USE_FAST_LIGHT)
+ vec3 viewDir = u_ViewOrigin - position;
+ // store view direction in tangent space to save on varyings
+ var_Normal = vec4(normal, viewDir.x);
+ var_Tangent = vec4(tangent, viewDir.y);
+ var_Bitangent = vec4(bitangent, viewDir.z);
+#endif
+}
diff --git a/src/renderergl2/glsl/pshadow_fp.glsl b/src/renderergl2/glsl/pshadow_fp.glsl
new file mode 100644
index 0000000..c196f48
--- /dev/null
+++ b/src/renderergl2/glsl/pshadow_fp.glsl
@@ -0,0 +1,78 @@
+uniform sampler2D u_ShadowMap;
+
+uniform vec3 u_LightForward;
+uniform vec3 u_LightUp;
+uniform vec3 u_LightRight;
+uniform vec4 u_LightOrigin;
+uniform float u_LightRadius;
+varying vec3 var_Position;
+varying vec3 var_Normal;
+
+void main()
+{
+ vec3 lightToPos = var_Position - u_LightOrigin.xyz;
+ vec2 st = vec2(-dot(u_LightRight, lightToPos), dot(u_LightUp, lightToPos));
+
+ float fade = length(st);
+
+#if defined(USE_DISCARD)
+ if (fade >= 1.0)
+ {
+ discard;
+ }
+#endif
+
+ fade = clamp(8.0 - fade * 8.0, 0.0, 1.0);
+
+ st = st * 0.5 + vec2(0.5);
+
+#if defined(USE_SOLID_PSHADOWS)
+ float intensity = max(sign(u_LightRadius - length(lightToPos)), 0.0);
+#else
+ float intensity = clamp((1.0 - dot(lightToPos, lightToPos) / (u_LightRadius * u_LightRadius)) * 2.0, 0.0, 1.0);
+#endif
+
+ float lightDist = length(lightToPos);
+ float dist;
+
+#if defined(USE_DISCARD)
+ if (dot(u_LightForward, lightToPos) <= 0.0)
+ {
+ discard;
+ }
+
+ if (dot(var_Normal, lightToPos) > 0.0)
+ {
+ discard;
+ }
+#else
+ intensity *= max(sign(dot(u_LightForward, lightToPos)), 0.0);
+ intensity *= max(sign(-dot(var_Normal, lightToPos)), 0.0);
+#endif
+
+ intensity *= fade;
+
+ float part;
+#if defined(USE_PCF)
+ part = float(texture2D(u_ShadowMap, st + vec2(-1.0/512.0, -1.0/512.0)).r != 1.0);
+ part += float(texture2D(u_ShadowMap, st + vec2( 1.0/512.0, -1.0/512.0)).r != 1.0);
+ part += float(texture2D(u_ShadowMap, st + vec2(-1.0/512.0, 1.0/512.0)).r != 1.0);
+ part += float(texture2D(u_ShadowMap, st + vec2( 1.0/512.0, 1.0/512.0)).r != 1.0);
+#else
+ part = float(texture2D(u_ShadowMap, st).r != 1.0);
+#endif
+
+ if (part <= 0.0)
+ {
+ discard;
+ }
+
+#if defined(USE_PCF)
+ intensity *= part * 0.25;
+#else
+ intensity *= part;
+#endif
+
+ gl_FragColor.rgb = vec3(0);
+ gl_FragColor.a = clamp(intensity, 0.0, 0.75);
+}
diff --git a/src/renderergl2/glsl/pshadow_vp.glsl b/src/renderergl2/glsl/pshadow_vp.glsl
new file mode 100644
index 0000000..07a4985
--- /dev/null
+++ b/src/renderergl2/glsl/pshadow_vp.glsl
@@ -0,0 +1,15 @@
+attribute vec3 attr_Position;
+attribute vec3 attr_Normal;
+
+uniform mat4 u_ModelViewProjectionMatrix;
+varying vec3 var_Position;
+varying vec3 var_Normal;
+
+
+void main()
+{
+ gl_Position = u_ModelViewProjectionMatrix * vec4(attr_Position, 1.0);
+
+ var_Position = attr_Position;
+ var_Normal = attr_Normal;
+}
diff --git a/src/renderergl2/glsl/shadowfill_fp.glsl b/src/renderergl2/glsl/shadowfill_fp.glsl
new file mode 100644
index 0000000..150f3d1
--- /dev/null
+++ b/src/renderergl2/glsl/shadowfill_fp.glsl
@@ -0,0 +1,41 @@
+uniform vec4 u_LightOrigin;
+uniform float u_LightRadius;
+
+varying vec3 var_Position;
+
+void main()
+{
+#if defined(USE_DEPTH)
+ float depth = length(u_LightOrigin.xyz - var_Position) / u_LightRadius;
+ #if 0
+ // 32 bit precision
+ const vec4 bitSh = vec4( 256 * 256 * 256, 256 * 256, 256, 1);
+ const vec4 bitMsk = vec4( 0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);
+
+ vec4 comp;
+ comp = depth * bitSh;
+ comp.xyz = fract(comp.xyz);
+ comp -= comp.xxyz * bitMsk;
+ gl_FragColor = comp;
+ #endif
+
+ #if 1
+ // 24 bit precision
+ const vec3 bitSh = vec3( 256 * 256, 256, 1);
+ const vec3 bitMsk = vec3( 0, 1.0 / 256.0, 1.0 / 256.0);
+
+ vec3 comp;
+ comp = depth * bitSh;
+ comp.xy = fract(comp.xy);
+ comp -= comp.xxy * bitMsk;
+ gl_FragColor = vec4(comp, 1.0);
+ #endif
+
+ #if 0
+ // 8 bit precision
+ gl_FragColor = vec4(depth, depth, depth, 1);
+ #endif
+#else
+ gl_FragColor = vec4(0, 0, 0, 1);
+#endif
+}
diff --git a/src/renderergl2/glsl/shadowfill_vp.glsl b/src/renderergl2/glsl/shadowfill_vp.glsl
new file mode 100644
index 0000000..7de901b
--- /dev/null
+++ b/src/renderergl2/glsl/shadowfill_vp.glsl
@@ -0,0 +1,89 @@
+attribute vec3 attr_Position;
+attribute vec3 attr_Normal;
+attribute vec4 attr_TexCoord0;
+
+//#if defined(USE_VERTEX_ANIMATION)
+attribute vec3 attr_Position2;
+attribute vec3 attr_Normal2;
+//#endif
+
+//#if defined(USE_DEFORM_VERTEXES)
+uniform int u_DeformGen;
+uniform float u_DeformParams[5];
+//#endif
+
+uniform float u_Time;
+uniform mat4 u_ModelViewProjectionMatrix;
+
+uniform mat4 u_ModelMatrix;
+
+//#if defined(USE_VERTEX_ANIMATION)
+uniform float u_VertexLerp;
+//#endif
+
+varying vec3 var_Position;
+
+vec3 DeformPosition(const vec3 pos, const vec3 normal, const vec2 st)
+{
+ if (u_DeformGen == 0)
+ {
+ return pos;
+ }
+
+ float base = u_DeformParams[0];
+ float amplitude = u_DeformParams[1];
+ float phase = u_DeformParams[2];
+ float frequency = u_DeformParams[3];
+ float spread = u_DeformParams[4];
+
+ if (u_DeformGen == DGEN_BULGE)
+ {
+ phase *= st.x;
+ }
+ else // if (u_DeformGen <= DGEN_WAVE_INVERSE_SAWTOOTH)
+ {
+ phase += dot(pos.xyz, vec3(spread));
+ }
+
+ float value = phase + (u_Time * frequency);
+ float func;
+
+ if (u_DeformGen == DGEN_WAVE_SIN)
+ {
+ func = sin(value * 2.0 * M_PI);
+ }
+ else if (u_DeformGen == DGEN_WAVE_SQUARE)
+ {
+ func = sign(0.5 - fract(value));
+ }
+ else if (u_DeformGen == DGEN_WAVE_TRIANGLE)
+ {
+ func = abs(fract(value + 0.75) - 0.5) * 4.0 - 1.0;
+ }
+ else if (u_DeformGen == DGEN_WAVE_SAWTOOTH)
+ {
+ func = fract(value);
+ }
+ else if (u_DeformGen == DGEN_WAVE_INVERSE_SAWTOOTH)
+ {
+ func = (1.0 - fract(value));
+ }
+ else // if (u_DeformGen == DGEN_BULGE)
+ {
+ func = sin(value);
+ }
+
+ return pos + normal * (base + func * amplitude);
+}
+
+void main()
+{
+ vec3 position = mix(attr_Position, attr_Position2, u_VertexLerp);
+ vec3 normal = mix(attr_Normal, attr_Normal2, u_VertexLerp);
+
+ position = DeformPosition(position, normal, attr_TexCoord0.st);
+
+ gl_Position = u_ModelViewProjectionMatrix * vec4(position, 1.0);
+
+ var_Position = (u_ModelMatrix * vec4(position, 1.0)).xyz;
+}
diff --git a/src/renderergl2/glsl/shadowmask_fp.glsl b/src/renderergl2/glsl/shadowmask_fp.glsl
new file mode 100644
index 0000000..2b57e3b
--- /dev/null
+++ b/src/renderergl2/glsl/shadowmask_fp.glsl
@@ -0,0 +1,143 @@
+uniform sampler2D u_ScreenDepthMap;
+
+uniform sampler2DShadow u_ShadowMap;
+#if defined(USE_SHADOW_CASCADE)
+uniform sampler2DShadow u_ShadowMap2;
+uniform sampler2DShadow u_ShadowMap3;
+uniform sampler2DShadow u_ShadowMap4;
+#endif
+
+uniform mat4 u_ShadowMvp;
+#if defined(USE_SHADOW_CASCADE)
+uniform mat4 u_ShadowMvp2;
+uniform mat4 u_ShadowMvp3;
+uniform mat4 u_ShadowMvp4;
+#endif
+
+uniform vec3 u_ViewOrigin;
+uniform vec4 u_ViewInfo; // zfar / znear, zfar
+
+varying vec2 var_DepthTex;
+varying vec3 var_ViewDir;
+
+// depth is GL_DEPTH_COMPONENT24
+// so the maximum error is 1.0 / 2^24
+#define DEPTH_MAX_ERROR 0.000000059604644775390625
+
+// Input: It uses texture coords as the random number seed.
+// Output: Random number: [0,1), that is between 0.0 and 0.999999... inclusive.
+// Author: Michael Pohoreski
+// Copyright: Copyleft 2012 :-)
+// Source: http://stackoverflow.com/questions/5149544/can-i-generate-a-random-number-inside-a-pixel-shader
+
+float random( const vec2 p )
+{
+ // We need irrationals for pseudo randomness.
+ // Most (all?) known transcendental numbers will (generally) work.
+ const vec2 r = vec2(
+ 23.1406926327792690, // e^pi (Gelfond's constant)
+ 2.6651441426902251); // 2^sqrt(2) (Gelfond-Schneider constant)
+ //return fract( cos( mod( 123456789., 1e-7 + 256. * dot(p,r) ) ) );
+ return mod( 123456789., 1e-7 + 256. * dot(p,r) );
+}
+
+float PCF(const sampler2DShadow shadowmap, const vec2 st, const float dist)
+{
+ float mult;
+ float scale = 2.0 / r_shadowMapSize;
+
+#if 0
+ // from http://http.developer.nvidia.com/GPUGems/gpugems_ch11.html
+ vec2 offset = vec2(greaterThan(fract(var_DepthTex.xy * r_FBufScale * 0.5), vec2(0.25)));
+ offset.y += offset.x;
+ if (offset.y > 1.1) offset.y = 0.0;
+
+ mult = shadow2D(shadowmap, vec3(st + (offset + vec2(-1.5, 0.5)) * scale, dist))
+ + shadow2D(shadowmap, vec3(st + (offset + vec2( 0.5, 0.5)) * scale, dist))
+ + shadow2D(shadowmap, vec3(st + (offset + vec2(-1.5, -1.5)) * scale, dist))
+ + shadow2D(shadowmap, vec3(st + (offset + vec2( 0.5, -1.5)) * scale, dist));
+
+ mult *= 0.25;
+#endif
+
+#if defined(USE_SHADOW_FILTER)
+ float r = random(var_DepthTex.xy);
+ float sinr = sin(r) * scale;
+ float cosr = cos(r) * scale;
+ mat2 rmat = mat2(cosr, sinr, -sinr, cosr);
+
+ mult = shadow2D(shadowmap, vec3(st + rmat * vec2(-0.7055767, 0.196515), dist));
+ mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.3524343, -0.7791386), dist));
+ mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.2391056, 0.9189604), dist));
+ #if defined(USE_SHADOW_FILTER2)
+ mult += shadow2D(shadowmap, vec3(st + rmat * vec2(-0.07580382, -0.09224417), dist));
+ mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.5784913, -0.002528916), dist));
+ mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.192888, 0.4064181), dist));
+ mult += shadow2D(shadowmap, vec3(st + rmat * vec2(-0.6335801, -0.5247476), dist));
+ mult += shadow2D(shadowmap, vec3(st + rmat * vec2(-0.5579782, 0.7491854), dist));
+ mult += shadow2D(shadowmap, vec3(st + rmat * vec2(0.7320465, 0.6317794), dist));
+
+ mult *= 0.11111;
+ #else
+ mult *= 0.33333;
+ #endif
+#else
+ mult = shadow2D(shadowmap, vec3(st, dist));
+#endif
+
+ return mult;
+}
+
+float getLinearDepth(sampler2D depthMap, vec2 tex, float zFarDivZNear)
+{
+ float sampleZDivW = texture2D(depthMap, tex).r - DEPTH_MAX_ERROR;
+ return 1.0 / mix(zFarDivZNear, 1.0, sampleZDivW);
+}
+
+void main()
+{
+ float result;
+
+ float depth = getLinearDepth(u_ScreenDepthMap, var_DepthTex, u_ViewInfo.x);
+ vec4 biasPos = vec4(u_ViewOrigin + var_ViewDir * (depth - 0.5 / u_ViewInfo.x), 1.0);
+
+ vec4 shadowpos = u_ShadowMvp * biasPos;
+
+#if defined(USE_SHADOW_CASCADE)
+ if (all(lessThan(abs(shadowpos.xyz), vec3(abs(shadowpos.w)))))
+ {
+#endif
+ shadowpos.xyz = shadowpos.xyz * (0.5 / shadowpos.w) + vec3(0.5);
+ result = PCF(u_ShadowMap, shadowpos.xy, shadowpos.z);
+#if defined(USE_SHADOW_CASCADE)
+ }
+ else
+ {
+ shadowpos = u_ShadowMvp2 * biasPos;
+
+ if (all(lessThan(abs(shadowpos.xyz), vec3(abs(shadowpos.w)))))
+ {
+ shadowpos.xyz = shadowpos.xyz * (0.5 / shadowpos.w) + vec3(0.5);
+ result = PCF(u_ShadowMap2, shadowpos.xy, shadowpos.z);
+ }
+ else
+ {
+ shadowpos = u_ShadowMvp3 * biasPos;
+
+ if (all(lessThan(abs(shadowpos.xyz), vec3(abs(shadowpos.w)))))
+ {
+ shadowpos.xyz = shadowpos.xyz * (0.5 / shadowpos.w) + vec3(0.5);
+ result = PCF(u_ShadowMap3, shadowpos.xy, shadowpos.z);
+ }
+ else
+ {
+ shadowpos = u_ShadowMvp4 * biasPos;
+ shadowpos.xyz = shadowpos.xyz * (0.5 / shadowpos.w) + vec3(0.5);
+ result = PCF(u_ShadowMap4, shadowpos.xy, shadowpos.z);
+ }
+ }
+ }
+#endif
+
+ gl_FragColor = vec4(vec3(result), 1.0);
+}
diff --git a/src/renderergl2/glsl/shadowmask_vp.glsl b/src/renderergl2/glsl/shadowmask_vp.glsl
new file mode 100644
index 0000000..13166a2
--- /dev/null
+++ b/src/renderergl2/glsl/shadowmask_vp.glsl
@@ -0,0 +1,18 @@
+attribute vec4 attr_Position;
+attribute vec4 attr_TexCoord0;
+
+uniform vec3 u_ViewForward;
+uniform vec3 u_ViewLeft;
+uniform vec3 u_ViewUp;
+uniform vec4 u_ViewInfo; // zfar / znear
+
+varying vec2 var_DepthTex;
+varying vec3 var_ViewDir;
+
+void main()
+{
+ gl_Position = attr_Position;
+ vec2 screenCoords = gl_Position.xy / gl_Position.w;
+ var_DepthTex = attr_TexCoord0.xy;
+ var_ViewDir = u_ViewForward + u_ViewLeft * -screenCoords.x + u_ViewUp * screenCoords.y;
+}
diff --git a/src/renderergl2/glsl/ssao_fp.glsl b/src/renderergl2/glsl/ssao_fp.glsl
new file mode 100644
index 0000000..93f6185
--- /dev/null
+++ b/src/renderergl2/glsl/ssao_fp.glsl
@@ -0,0 +1,86 @@
+uniform sampler2D u_ScreenDepthMap;
+
+uniform vec4 u_ViewInfo; // zfar / znear, zfar, 1/width, 1/height
+
+varying vec2 var_ScreenTex;
+
+vec2 poissonDisc[9] = vec2[9](
+vec2(-0.7055767, 0.196515), vec2(0.3524343, -0.7791386),
+vec2(0.2391056, 0.9189604), vec2(-0.07580382, -0.09224417),
+vec2(0.5784913, -0.002528916), vec2(0.192888, 0.4064181),
+vec2(-0.6335801, -0.5247476), vec2(-0.5579782, 0.7491854),
+vec2(0.7320465, 0.6317794)
+);
+#define NUM_SAMPLES 3
+
+// Input: It uses texture coords as the random number seed.
+// Output: Random number: [0,1), that is between 0.0 and 0.999999... inclusive.
+// Author: Michael Pohoreski
+// Copyright: Copyleft 2012 :-)
+// Source: http://stackoverflow.com/questions/5149544/can-i-generate-a-random-number-inside-a-pixel-shader
+
+float random( const vec2 p )
+{
+ // We need irrationals for pseudo randomness.
+ // Most (all?) known transcendental numbers will (generally) work.
+ const vec2 r = vec2(
+ 23.1406926327792690, // e^pi (Gelfond's constant)
+ 2.6651441426902251); // 2^sqrt(2) (Gelfond-Schneider constant)
+ //return fract( cos( mod( 123456789., 1e-7 + 256. * dot(p,r) ) ) );
+ return mod( 123456789., 1e-7 + 256. * dot(p,r) );
+}
+
+mat2 randomRotation( const vec2 p )
+{
+ float r = random(p);
+ float sinr = sin(r);
+ float cosr = cos(r);
+ return mat2(cosr, sinr, -sinr, cosr);
+}
+
+float getLinearDepth(sampler2D depthMap, const vec2 tex, const float zFarDivZNear)
+{
+ float sampleZDivW = texture2D(depthMap, tex).r;
+ return 1.0 / mix(zFarDivZNear, 1.0, sampleZDivW);
+}
+
+float ambientOcclusion(sampler2D depthMap, const vec2 tex, const float zFarDivZNear, const float zFar, const vec2 scale)
+{
+ float result = 0;
+
+ float sampleZ = getLinearDepth(depthMap, tex, zFarDivZNear);
+ float scaleZ = zFarDivZNear * sampleZ;
+
+ vec2 slope = vec2(dFdx(sampleZ), dFdy(sampleZ)) / vec2(dFdx(tex.x), dFdy(tex.y));
+
+ if (length(slope) * zFar > 5000.0)
+ return 1.0;
+
+ vec2 offsetScale = vec2(scale * 1024.0 / scaleZ);
+
+ mat2 rmat = randomRotation(tex);
+
+ float invZFar = 1.0 / zFar;
+ float zLimit = 20.0 * invZFar;
+ int i;
+ for (i = 0; i < NUM_SAMPLES; i++)
+ {
+ vec2 offset = rmat * poissonDisc[i] * offsetScale;
+ float sampleDiff = getLinearDepth(depthMap, tex + offset, zFarDivZNear) - sampleZ;
+
+ bool s1 = abs(sampleDiff) > zLimit;
+ bool s2 = sampleDiff + invZFar > dot(slope, offset);
+ result += float(s1 || s2);
+ }
+
+ result *= 1.0 / float(NUM_SAMPLES);
+
+ return result;
+}
+
+void main()
+{
+ float result = ambientOcclusion(u_ScreenDepthMap, var_ScreenTex, u_ViewInfo.x, u_ViewInfo.y, u_ViewInfo.wz);
+
+ gl_FragColor = vec4(vec3(result), 1.0);
+}
diff --git a/src/renderergl2/glsl/ssao_vp.glsl b/src/renderergl2/glsl/ssao_vp.glsl
new file mode 100644
index 0000000..9c46a79
--- /dev/null
+++ b/src/renderergl2/glsl/ssao_vp.glsl
@@ -0,0 +1,12 @@
+attribute vec4 attr_Position;
+attribute vec4 attr_TexCoord0;
+
+varying vec2 var_ScreenTex;
+
+void main()
+{
+ gl_Position = attr_Position;
+ var_ScreenTex = attr_TexCoord0.xy;
+ //vec2 screenCoords = gl_Position.xy / gl_Position.w;
+ //var_ScreenTex = screenCoords * 0.5 + 0.5;
+}
diff --git a/src/renderergl2/glsl/texturecolor_fp.glsl b/src/renderergl2/glsl/texturecolor_fp.glsl
new file mode 100644
index 0000000..7c9046e
--- /dev/null
+++ b/src/renderergl2/glsl/texturecolor_fp.glsl
@@ -0,0 +1,10 @@
+uniform sampler2D u_DiffuseMap;
+uniform vec4 u_Color;
+
+varying vec2 var_Tex1;
+
+
+void main()
+{
+ gl_FragColor = texture2D(u_DiffuseMap, var_Tex1) * u_Color;
+}
diff --git a/src/renderergl2/glsl/texturecolor_vp.glsl b/src/renderergl2/glsl/texturecolor_vp.glsl
new file mode 100644
index 0000000..552cd93
--- /dev/null
+++ b/src/renderergl2/glsl/texturecolor_vp.glsl
@@ -0,0 +1,13 @@
+attribute vec3 attr_Position;
+attribute vec4 attr_TexCoord0;
+
+uniform mat4 u_ModelViewProjectionMatrix;
+
+varying vec2 var_Tex1;
+
+
+void main()
+{
+ gl_Position = u_ModelViewProjectionMatrix * vec4(attr_Position, 1.0);
+ var_Tex1 = attr_TexCoord0.st;
+}
diff --git a/src/renderergl2/glsl/tonemap_fp.glsl b/src/renderergl2/glsl/tonemap_fp.glsl
new file mode 100644
index 0000000..9e24e24
--- /dev/null
+++ b/src/renderergl2/glsl/tonemap_fp.glsl
@@ -0,0 +1,57 @@
+uniform sampler2D u_TextureMap;
+uniform sampler2D u_LevelsMap;
+
+uniform vec4 u_Color;
+
+
+uniform vec2 u_AutoExposureMinMax;
+uniform vec3 u_ToneMinAvgMaxLinear;
+
+varying vec2 var_TexCoords;
+varying float var_InvWhite;
+
+const vec3 LUMINANCE_VECTOR = vec3(0.2125, 0.7154, 0.0721); //vec3(0.299, 0.587, 0.114);
+
+float FilmicTonemap(float x)
+{
+ const float SS = 0.22; // Shoulder Strength
+ const float LS = 0.30; // Linear Strength
+ const float LA = 0.10; // Linear Angle
+ const float TS = 0.20; // Toe Strength
+ const float TAN = 0.01; // Toe Angle Numerator
+ const float TAD = 0.30; // Toe Angle Denominator
+
+ return ((x*(SS*x+LA*LS)+TS*TAN)/(x*(SS*x+LS)+TS*TAD)) - TAN/TAD;
+}
+
+void main()
+{
+ vec4 color = texture2D(u_TextureMap, var_TexCoords) * u_Color;
+
+#if defined(USE_PBR)
+ color.rgb *= color.rgb;
+#endif
+
+ vec3 minAvgMax = texture2D(u_LevelsMap, var_TexCoords).rgb;
+ vec3 logMinAvgMaxLum = clamp(minAvgMax * 20.0 - 10.0, -u_AutoExposureMinMax.y, -u_AutoExposureMinMax.x);
+
+ float invAvgLum = u_ToneMinAvgMaxLinear.y * exp2(-logMinAvgMaxLum.y);
+
+ color.rgb = color.rgb * invAvgLum - u_ToneMinAvgMaxLinear.xxx;
+ color.rgb = max(vec3(0.0), color.rgb);
+
+ color.r = FilmicTonemap(color.r);
+ color.g = FilmicTonemap(color.g);
+ color.b = FilmicTonemap(color.b);
+
+ color.rgb = clamp(color.rgb * var_InvWhite, 0.0, 1.0);
+
+#if defined(USE_PBR)
+ color.rgb = sqrt(color.rgb);
+#endif
+
+ // add a bit of dither to reduce banding
+ color.rgb += vec3(1.0/510.0 * mod(gl_FragCoord.x + gl_FragCoord.y, 2.0) - 1.0/1020.0);
+
+ gl_FragColor = color;
+}
diff --git a/src/renderergl2/glsl/tonemap_vp.glsl b/src/renderergl2/glsl/tonemap_vp.glsl
new file mode 100644
index 0000000..577c0a1
--- /dev/null
+++ b/src/renderergl2/glsl/tonemap_vp.glsl
@@ -0,0 +1,27 @@
+attribute vec3 attr_Position;
+attribute vec4 attr_TexCoord0;
+
+uniform mat4 u_ModelViewProjectionMatrix;
+uniform vec3 u_ToneMinAvgMaxLinear;
+
+varying vec2 var_TexCoords;
+varying float var_InvWhite;
+
+float FilmicTonemap(float x)
+{
+ const float SS = 0.22; // Shoulder Strength
+ const float LS = 0.30; // Linear Strength
+ const float LA = 0.10; // Linear Angle
+ const float TS = 0.20; // Toe Strength
+ const float TAN = 0.01; // Toe Angle Numerator
+ const float TAD = 0.30; // Toe Angle Denominator
+
+ return ((x*(SS*x+LA*LS)+TS*TAN)/(x*(SS*x+LS)+TS*TAD)) - TAN/TAD;
+}
+
+void main()
+{
+ gl_Position = u_ModelViewProjectionMatrix * vec4(attr_Position, 1.0);
+ var_TexCoords = attr_TexCoord0.st;
+ var_InvWhite = 1.0 / FilmicTonemap(u_ToneMinAvgMaxLinear.z - u_ToneMinAvgMaxLinear.x);
+}
diff --git a/src/renderergl2/tr_animation.cpp b/src/renderergl2/tr_animation.cpp
new file mode 100644
index 0000000..55dddd7
--- /dev/null
+++ b/src/renderergl2/tr_animation.cpp
@@ -0,0 +1,525 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#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.
+
+*/
+
+
+// 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 mdrs 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;
+ int cubemapIndex;
+ bool personalModel;
+
+ header = (mdrHeader_t *) tr.currentModel->modelData;
+
+ 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 %= 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 );
+
+ cubemapIndex = R_CubemapForPoint(ent->e.origin);
+
+ 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( (surfaceType_t*)surface, tr.shadowShader, 0, false, false, 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( (surfaceType_t*)surface, tr.projectionShadowShader, 0, false, false, 0 );
+ }
+
+ if (!personalModel)
+ R_AddDrawSurf( (surfaceType_t*)surface, shader, fogNum, false, false, cubemapIndex );
+
+ surface = (mdrSurface_t *)( (byte *)surface + surface->ofsEnd );
+ }
+}
+
+/*
+==============
+RB_MDRSurfaceAnim
+==============
+*/
+void RB_MDRSurfaceAnim( mdrSurface_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[MDR_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 * 3 );
+
+ 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];
+
+ R_VaoPackNormal(tess.normal[baseVertex + j], tempNormal);
+
+ tess.texCoords[baseVertex + j][0] = v->texCoords[0];
+ tess.texCoords[baseVertex + j][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;
+}
diff --git a/src/renderergl2/tr_backend.cpp b/src/renderergl2/tr_backend.cpp
new file mode 100644
index 0000000..3459f58
--- /dev/null
+++ b/src/renderergl2/tr_backend.cpp
@@ -0,0 +1,1817 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+#include "tr_local.h"
+#include "tr_fbo.h"
+#include "tr_dsa.h"
+
+backEndData_t *backEndData;
+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_BindToTMU
+*/
+void GL_BindToTMU( image_t *image, int tmu )
+{
+ GLuint texture = (tmu == TB_COLORMAP) ? tr.defaultImage->texnum : 0;
+ GLenum target = GL_TEXTURE_2D;
+
+ if (image)
+ {
+ if (image->flags & IMGFLAG_CUBEMAP)
+ target = GL_TEXTURE_CUBE_MAP;
+
+ image->frameUsed = tr.frameCount;
+ texture = image->texnum;
+ }
+ else
+ {
+ ri.Printf(PRINT_WARNING, "GL_BindToTMU: NULL image\n");
+ }
+
+ GL_BindMultiTexture(GL_TEXTURE0_ARB + tmu, target, texture);
+}
+
+
+/*
+** GL_Cull
+*/
+void GL_Cull( int cullType ) {
+ if ( glState.faceCulling == cullType ) {
+ return;
+ }
+
+ if ( cullType == CT_TWO_SIDED )
+ {
+ qglDisable( GL_CULL_FACE );
+ }
+ else
+ {
+ bool cullFront = (cullType == CT_FRONT_SIDED);
+
+ if ( glState.faceCulling == CT_TWO_SIDED )
+ qglEnable( GL_CULL_FACE );
+
+ if ( glState.faceCullFront != cullFront )
+ qglCullFace( cullFront ? GL_FRONT : GL_BACK );
+
+ glState.faceCullFront = cullFront;
+ }
+
+ glState.faceCulling = cullType;
+}
+
+/*
+** 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 ) )
+ {
+ uint32_t oldState = glState.glStateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS );
+ uint32_t newState = stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS );
+ uint32_t storedState = glState.storedGlState & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS );
+
+ if (oldState == 0)
+ {
+ qglEnable( GL_BLEND );
+ }
+ else if (newState == 0)
+ {
+ qglDisable( GL_BLEND );
+ }
+
+ if (newState != 0 && storedState != newState)
+ {
+ GLenum srcFactor = GL_ONE, dstFactor = GL_ONE;
+
+ glState.storedGlState &= ~( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS );
+ glState.storedGlState |= newState;
+
+ 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:
+ 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:
+ ri.Error( ERR_DROP, "GL_State: invalid dst blend state bits" );
+ break;
+ }
+
+ qglBlendFunc( srcFactor, dstFactor );
+ }
+ }
+
+ //
+ // 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 );
+ }
+ }
+
+ glState.glStateBits = stateBits;
+}
+
+
+void GL_SetProjectionMatrix(mat4_t matrix)
+{
+ Mat4Copy(matrix, glState.projection);
+ Mat4Multiply(glState.projection, glState.modelview, glState.modelviewProjection);
+}
+
+
+void GL_SetModelviewMatrix(mat4_t matrix)
+{
+ Mat4Copy(matrix, glState.modelview);
+ Mat4Multiply(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 );
+ qglClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+
+ backEnd.isHyperspace = true;
+}
+
+
+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
+
+
+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 = true;
+ }
+ if ( r_finish->integer == 0 ) {
+ glState.finishCalled = true;
+ }
+
+ // we will need to change the projection matrix before drawing
+ // 2D images again
+ backEnd.projection2D = false;
+
+ if (glRefConfig.framebufferObject)
+ {
+ FBO_t *fbo = backEnd.viewParms.targetFbo;
+
+ // FIXME: HUGE HACK: render to the screen fbo if we've already postprocessed the frame and aren't drawing more world
+ // drawing more world check is in case of double renders, such as skyportals
+ if (fbo == NULL && !(backEnd.framePostProcessed && (backEnd.refdef.rdflags & RDF_NOWORLDMODEL)))
+ fbo = tr.renderFbo;
+
+ if (tr.renderCubeFbo && fbo == tr.renderCubeFbo)
+ {
+ cubemap_t *cubemap = &tr.cubemaps[backEnd.viewParms.targetFboCubemapIndex];
+ FBO_AttachImage(fbo, cubemap->image, GL_COLOR_ATTACHMENT0_EXT, backEnd.viewParms.targetFboLayer);
+ }
+
+ FBO_Bind(fbo);
+ }
+
+ //
+ // 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
+ }
+
+ // clear to black for cube maps
+ if (tr.renderCubeFbo && backEnd.viewParms.targetFbo == tr.renderCubeFbo)
+ {
+ clearBits |= GL_COLOR_BUFFER_BIT;
+ }
+
+ qglClear( clearBits );
+
+ if ( ( backEnd.refdef.rdflags & RDF_HYPERSPACE ) )
+ {
+ RB_Hyperspace();
+ return;
+ }
+ else
+ {
+ backEnd.isHyperspace = false;
+ }
+
+ // we will only draw a sun if there was sky rendered in this view
+ backEnd.skyRenderedThisView = false;
+
+ // clip to the plane of the portal
+ if ( backEnd.viewParms.isPortal ) {
+#if 0
+ float plane[4];
+ GLdouble 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.orientation.axis[0], plane);
+ plane2[1] = DotProduct (backEnd.viewParms.orientation.axis[1], plane);
+ plane2[2] = DotProduct (backEnd.viewParms.orientation.axis[2], plane);
+ plane2[3] = DotProduct (plane, backEnd.viewParms.orientation.origin) - plane[3];
+#endif
+ GL_SetModelviewMatrix( s_flipMatrix );
+ }
+}
+
+
+/*
+==================
+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;
+ int cubemapIndex, oldCubemapIndex;
+ bool depthRange, oldDepthRange, isCrosshair, wasCrosshair;
+ int i;
+ drawSurf_t *drawSurf;
+ int oldSort;
+ FBO_t* fbo = NULL;
+ bool inQuery = false;
+
+ float depth[2];
+
+
+ // save original time for entity shader offsets
+ double originalTime = backEnd.refdef.floatTime;
+
+ fbo = glState.currentFBO;
+
+ // draw everything
+ oldEntityNum = -1;
+ backEnd.currentEntity = &tr.worldEntity;
+ oldShader = NULL;
+ oldFogNum = -1;
+ oldDepthRange = false;
+ wasCrosshair = false;
+ oldDlighted = false;
+ oldPshadowed = false;
+ oldCubemapIndex = -1;
+ oldSort = -1;
+
+ depth[0] = 0.f;
+ depth[1] = 1.f;
+
+ backEnd.pc.c_surfaces += numDrawSurfs;
+
+ for (i = 0, drawSurf = drawSurfs ; i < numDrawSurfs ; i++, drawSurf++) {
+ if ( drawSurf->sort == oldSort && drawSurf->cubemapIndex == oldCubemapIndex) {
+ 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 );
+ cubemapIndex = drawSurf->cubemapIndex;
+
+ //
+ // 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 != NULL && ( shader != oldShader || fogNum != oldFogNum || dlighted != oldDlighted || pshadowed != oldPshadowed || cubemapIndex != oldCubemapIndex
+ || ( entityNum != oldEntityNum && !shader->entityMergable ) ) ) {
+ if (oldShader != NULL) {
+ RB_EndSurface();
+ }
+ RB_BeginSurface( shader, fogNum, cubemapIndex );
+ backEnd.pc.c_surfBatches++;
+ oldShader = shader;
+ oldFogNum = fogNum;
+ oldDlighted = dlighted;
+ oldPshadowed = pshadowed;
+ oldCubemapIndex = cubemapIndex;
+ }
+
+ if (backEnd.depthFill && shader && shader->sort != SS_OPAQUE)
+ continue;
+
+ //
+ // change the modelview matrix if needed
+ //
+ if ( entityNum != oldEntityNum ) {
+ bool sunflare = false;
+ depthRange = isCrosshair = false;
+
+ if ( entityNum != REFENTITYNUM_WORLD ) {
+ backEnd.currentEntity = &backEnd.refdef.entities[entityNum];
+
+ // FIXME: e.shaderTime must be passed as int to avoid fp-precision loss issues
+ backEnd.refdef.floatTime = originalTime - (double)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.orientation );
+
+ // set up the dynamic lighting if needed
+ if ( backEnd.currentEntity->needDlights ) {
+ R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.orientation );
+ }
+
+ if(backEnd.currentEntity->e.renderfx & RF_DEPTHHACK)
+ {
+ // hack the depth range to prevent view model from poking into walls
+ depthRange = true;
+
+ if(backEnd.currentEntity->e.renderfx & RF_CROSSHAIR)
+ isCrosshair = true;
+ }
+ } else {
+ backEnd.currentEntity = &tr.worldEntity;
+ backEnd.refdef.floatTime = originalTime;
+ backEnd.orientation = 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.orientation );
+ }
+
+ GL_SetModelviewMatrix( backEnd.orientation.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, false);
+
+ GL_SetProjectionMatrix( temp.projectionMatrix );
+ }
+ }
+
+ if(!oldDepthRange)
+ {
+ depth[0] = 0;
+ depth[1] = 0.3f;
+ qglDepthRange (depth[0], depth[1]);
+ }
+ }
+ else
+ {
+ if(!wasCrosshair && backEnd.viewParms.stereoFrame != STEREO_CENTER)
+ {
+ GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix );
+ }
+
+ if (!sunflare)
+ qglDepthRange (0, 1);
+
+ depth[0] = 0;
+ depth[1] = 1;
+ }
+
+ 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) {
+ qglEndQuery(GL_SAMPLES_PASSED);
+ }
+
+ if (glRefConfig.framebufferObject)
+ FBO_Bind(fbo);
+
+ // go back to the world modelview matrix
+
+ GL_SetModelviewMatrix( backEnd.viewParms.world.modelMatrix );
+
+ qglDepthRange (0, 1);
+}
+
+
+/*
+============================================================================
+
+RENDER BACK END FUNCTIONS
+
+============================================================================
+*/
+
+/*
+================
+RB_SetGL2D
+
+================
+*/
+void RB_SetGL2D (void) {
+ mat4_t matrix;
+ int width, height;
+
+ if (backEnd.projection2D && backEnd.last2DFBO == glState.currentFBO)
+ return;
+
+ backEnd.projection2D = true;
+ 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 );
+
+ Mat4Ortho(0, width, height, 0, 0, 1, matrix);
+ GL_SetProjectionMatrix(matrix);
+ Mat4Identity(matrix);
+ GL_SetModelviewMatrix(matrix);
+
+ GL_State( GLS_DEPTHTEST_DISABLE |
+ GLS_SRCBLEND_SRC_ALPHA |
+ GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA );
+
+ GL_Cull( CT_TWO_SIDED );
+ qglDisable( GL_CLIP_PLANE0 );
+
+ // set time for 2D shaders
+ backEnd.refdef.time = ri.Milliseconds();
+ backEnd.refdef.floatTime = backEnd.refdef.time * 0.001;
+}
+
+
+/*
+=============
+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, bool dirty) {
+ int i, j;
+ int start, end;
+ vec4_t quadVerts[4];
+ vec2_t texCoords[4];
+
+ if ( !tr.registered ) {
+ return;
+ }
+ R_IssuePendingRenderCommands();
+
+ if ( tess.numIndexes ) {
+ RB_EndSurface();
+ }
+
+ // 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);
+ }
+
+ RE_UploadCinematic (w, h, cols, rows, data, client, dirty);
+ GL_BindToTMU(tr.scratchImage[client], TB_COLORMAP);
+
+ 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)
+ {
+ FBO_Bind(backEnd.framePostProcessed ? NULL : tr.renderFbo);
+ }
+
+ RB_SetGL2D();
+
+ VectorSet4(quadVerts[0], x, y, 0.0f, 1.0f);
+ VectorSet4(quadVerts[1], x + w, y, 0.0f, 1.0f);
+ VectorSet4(quadVerts[2], x + w, y + h, 0.0f, 1.0f);
+ VectorSet4(quadVerts[3], x, y + h, 0.0f, 1.0f);
+
+ VectorSet2(texCoords[0], 0.5f / cols, 0.5f / rows);
+ VectorSet2(texCoords[1], (cols - 0.5f) / cols, 0.5f / rows);
+ VectorSet2(texCoords[2], (cols - 0.5f) / cols, (rows - 0.5f) / rows);
+ VectorSet2(texCoords[3], 0.5f / cols, (rows - 0.5f) / rows);
+
+ GLSL_BindProgram(&tr.textureColorShader);
+
+ GLSL_SetUniformMat4(&tr.textureColorShader, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+ GLSL_SetUniformVec4(&tr.textureColorShader, UNIFORM_COLOR, colorWhite);
+
+ RB_InstantQuad2(quadVerts, texCoords);
+}
+
+void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, bool dirty) {
+ GLuint texture;
+
+ if (!tr.scratchImage[client])
+ {
+ ri.Printf(PRINT_WARNING, "RE_UploadCinematic: scratch images not initialized\n");
+ return;
+ }
+
+ texture = tr.scratchImage[client]->texnum;
+
+ // 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;
+ qglTextureImage2DEXT(texture, GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
+ qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ qglTextureParameterfEXT(texture, 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
+ qglTextureSubImage2DEXT(texture, 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)
+ FBO_Bind(backEnd.framePostProcessed ? NULL : tr.renderFbo);
+
+ RB_SetGL2D();
+
+ shader = cmd->shader;
+ if ( shader != tess.shader ) {
+ if ( tess.numIndexes ) {
+ RB_EndSurface();
+ }
+ backEnd.currentEntity = &backEnd.entity2D;
+ RB_BeginSurface( shader, 0, 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;
+
+ {
+ uint16_t color[4];
+
+ VectorScale4(backEnd.color2D, 257, color);
+
+ VectorCopy4(color, tess.color[ numVerts ]);
+ VectorCopy4(color, tess.color[ numVerts + 1]);
+ VectorCopy4(color, tess.color[ numVerts + 2]);
+ VectorCopy4(color, tess.color[ numVerts + 3 ]);
+ }
+
+ tess.xyz[ numVerts ][0] = cmd->x;
+ tess.xyz[ numVerts ][1] = cmd->y;
+ tess.xyz[ numVerts ][2] = 0;
+
+ tess.texCoords[ numVerts ][0] = cmd->s1;
+ tess.texCoords[ numVerts ][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] = cmd->s2;
+ tess.texCoords[ numVerts + 1 ][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] = cmd->s2;
+ tess.texCoords[ numVerts + 2 ][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] = cmd->s1;
+ tess.texCoords[ numVerts + 3 ][1] = cmd->t2;
+
+ return (const void *)(cmd + 1);
+}
+
+
+/*
+=============
+RB_DrawSurfs
+
+=============
+*/
+const void *RB_DrawSurfs( const void *data ) {
+ const drawSurfsCommand_t *cmd;
+ bool isShadowView;
+
+ // finish any 2D drawing if needed
+ if ( tess.numIndexes ) {
+ RB_EndSurface();
+ }
+
+ cmd = (const drawSurfsCommand_t *)data;
+
+ backEnd.refdef = cmd->refdef;
+ backEnd.viewParms = cmd->viewParms;
+
+ isShadowView = !!(backEnd.viewParms.flags & VPF_DEPTHSHADOW);
+
+ // clear the z buffer, set the modelview, etc
+ RB_BeginDrawingView ();
+
+ if (glRefConfig.framebufferObject && (backEnd.viewParms.flags & VPF_DEPTHCLAMP) && glRefConfig.depthClamp)
+ {
+ qglEnable(GL_DEPTH_CLAMP);
+ }
+
+ if (glRefConfig.framebufferObject && !(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) && (r_depthPrepass->integer || isShadowView))
+ {
+ FBO_t *oldFbo = glState.currentFBO;
+ vec4_t viewInfo;
+
+ VectorSet4(viewInfo, backEnd.viewParms.zFar / r_znear->value, backEnd.viewParms.zFar, 0.0, 0.0);
+
+ backEnd.depthFill = true;
+ 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 = false;
+
+ if (!isShadowView)
+ {
+ if (tr.msaaResolveFbo)
+ {
+ // If we're using multisampling, resolve the depth first
+ FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
+ }
+ else if (tr.renderFbo == NULL && tr.renderDepthImage)
+ {
+ // If we're rendering directly to the screen, copy the depth to a texture
+ // This is incredibly slow on Intel Graphics, so just skip it on there
+ if (!glRefConfig.intelGraphics)
+ qglCopyTextureSubImage2DEXT(tr.renderDepthImage->texnum, GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight);
+ }
+
+ if (tr.hdrDepthFbo)
+ {
+ // need the depth in a texture we can do GL_LINEAR sampling on, so copy it to an HDR image
+ vec4_t srcTexCoords;
+
+ VectorSet4(srcTexCoords, 0.0f, 0.0f, 1.0f, 1.0f);
+
+ FBO_BlitFromTexture(tr.renderDepthImage, srcTexCoords, NULL, tr.hdrDepthFbo, NULL, NULL, NULL, 0);
+ }
+
+ if (r_sunlightMode->integer && backEnd.viewParms.flags & VPF_USESUNLIGHT)
+ {
+ vec4_t quadVerts[4];
+ vec2_t texCoords[4];
+ vec4_t box;
+
+ FBO_Bind(tr.screenShadowFbo);
+
+ box[0] = backEnd.viewParms.viewportX * tr.screenShadowFbo->width / (float)glConfig.vidWidth;
+ box[1] = backEnd.viewParms.viewportY * tr.screenShadowFbo->height / (float)glConfig.vidHeight;
+ box[2] = backEnd.viewParms.viewportWidth * tr.screenShadowFbo->width / (float)glConfig.vidWidth;
+ box[3] = backEnd.viewParms.viewportHeight * tr.screenShadowFbo->height / (float)glConfig.vidHeight;
+
+ qglViewport(box[0], box[1], box[2], box[3]);
+ qglScissor(box[0], box[1], box[2], box[3]);
+
+ box[0] = backEnd.viewParms.viewportX / (float)glConfig.vidWidth;
+ box[1] = backEnd.viewParms.viewportY / (float)glConfig.vidHeight;
+ box[2] = box[0] + backEnd.viewParms.viewportWidth / (float)glConfig.vidWidth;
+ box[3] = box[1] + backEnd.viewParms.viewportHeight / (float)glConfig.vidHeight;
+
+ texCoords[0][0] = box[0]; texCoords[0][1] = box[3];
+ texCoords[1][0] = box[2]; texCoords[1][1] = box[3];
+ texCoords[2][0] = box[2]; texCoords[2][1] = box[1];
+ texCoords[3][0] = box[0]; texCoords[3][1] = box[1];
+
+ box[0] = -1.0f;
+ box[1] = -1.0f;
+ box[2] = 1.0f;
+ box[3] = 1.0f;
+
+ VectorSet4(quadVerts[0], box[0], box[3], 0, 1);
+ VectorSet4(quadVerts[1], box[2], box[3], 0, 1);
+ VectorSet4(quadVerts[2], box[2], box[1], 0, 1);
+ VectorSet4(quadVerts[3], box[0], box[1], 0, 1);
+
+ GL_State(GLS_DEPTHTEST_DISABLE);
+
+ GLSL_BindProgram(&tr.shadowmaskShader);
+
+ GL_BindToTMU(tr.renderDepthImage, TB_COLORMAP);
+
+ if (r_shadowCascadeZFar->integer != 0)
+ {
+ GL_BindToTMU(tr.sunShadowDepthImage[0], TB_SHADOWMAP);
+ GL_BindToTMU(tr.sunShadowDepthImage[1], TB_SHADOWMAP2);
+ GL_BindToTMU(tr.sunShadowDepthImage[2], TB_SHADOWMAP3);
+ GL_BindToTMU(tr.sunShadowDepthImage[3], TB_SHADOWMAP4);
+
+ GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP, backEnd.refdef.sunShadowMvp[0]);
+ GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP2, backEnd.refdef.sunShadowMvp[1]);
+ GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP3, backEnd.refdef.sunShadowMvp[2]);
+ GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP4, backEnd.refdef.sunShadowMvp[3]);
+ }
+ else
+ {
+ GL_BindToTMU(tr.sunShadowDepthImage[3], TB_SHADOWMAP);
+ GLSL_SetUniformMat4(&tr.shadowmaskShader, UNIFORM_SHADOWMVP, backEnd.refdef.sunShadowMvp[3]);
+ }
+
+ GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWORIGIN, backEnd.refdef.vieworg);
+ {
+ 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);
+
+ VectorScale(backEnd.refdef.viewaxis[0], zmax, viewVector);
+ GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWFORWARD, viewVector);
+ VectorScale(backEnd.refdef.viewaxis[1], xmax, viewVector);
+ GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWLEFT, viewVector);
+ VectorScale(backEnd.refdef.viewaxis[2], ymax, viewVector);
+ GLSL_SetUniformVec3(&tr.shadowmaskShader, UNIFORM_VIEWUP, viewVector);
+
+ GLSL_SetUniformVec4(&tr.shadowmaskShader, UNIFORM_VIEWINFO, viewInfo);
+ }
+
+ RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes);
+
+ if (r_shadowBlur->integer)
+ {
+ viewInfo[2] = 1.0f / (float)(tr.screenScratchFbo->width);
+ viewInfo[3] = 1.0f / (float)(tr.screenScratchFbo->height);
+
+ FBO_Bind(tr.screenScratchFbo);
+
+ GLSL_BindProgram(&tr.depthBlurShader[0]);
+
+ GL_BindToTMU(tr.screenShadowImage, TB_COLORMAP);
+ GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP);
+
+ GLSL_SetUniformVec4(&tr.depthBlurShader[0], UNIFORM_VIEWINFO, viewInfo);
+
+ RB_InstantQuad2(quadVerts, texCoords);
+
+ FBO_Bind(tr.screenShadowFbo);
+
+ GLSL_BindProgram(&tr.depthBlurShader[1]);
+
+ GL_BindToTMU(tr.screenScratchImage, TB_COLORMAP);
+ GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP);
+
+ GLSL_SetUniformVec4(&tr.depthBlurShader[1], UNIFORM_VIEWINFO, viewInfo);
+
+ RB_InstantQuad2(quadVerts, texCoords);
+ }
+ }
+
+ if (r_ssao->integer)
+ {
+ vec4_t quadVerts[4];
+ vec2_t texCoords[4];
+
+ viewInfo[2] = 1.0f / ((float)(tr.quarterImage[0]->width) * tan(backEnd.viewParms.fovX * M_PI / 360.0f) * 2.0f);
+ viewInfo[3] = 1.0f / ((float)(tr.quarterImage[0]->height) * tan(backEnd.viewParms.fovY * M_PI / 360.0f) * 2.0f);
+ viewInfo[3] *= (float)backEnd.viewParms.viewportHeight / (float)backEnd.viewParms.viewportWidth;
+
+ 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);
+
+ GLSL_SetUniformVec4(&tr.ssaoShader, UNIFORM_VIEWINFO, viewInfo);
+
+ RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes);
+
+
+ viewInfo[2] = 1.0f / (float)(tr.quarterImage[0]->width);
+ viewInfo[3] = 1.0f / (float)(tr.quarterImage[0]->height);
+
+ 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);
+
+ GLSL_SetUniformVec4(&tr.depthBlurShader[0], 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);
+
+ GLSL_SetUniformVec4(&tr.depthBlurShader[1], UNIFORM_VIEWINFO, viewInfo);
+
+
+ RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes);
+ }
+ }
+
+ // reset viewport and scissor
+ FBO_Bind(oldFbo);
+ SetViewportAndScissor();
+ }
+
+ if (glRefConfig.framebufferObject && (backEnd.viewParms.flags & VPF_DEPTHCLAMP) && glRefConfig.depthClamp)
+ {
+ qglDisable(GL_DEPTH_CLAMP);
+ }
+
+ if (!isShadowView)
+ {
+ RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs );
+
+ if (r_drawSun->integer)
+ {
+ RB_DrawSun(0.1, tr.sunShader);
+ }
+
+ if (glRefConfig.framebufferObject && r_drawSunRays->integer)
+ {
+ FBO_t *oldFbo = glState.currentFBO;
+ FBO_Bind(tr.sunRaysFbo);
+
+ qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
+ qglClear( GL_COLOR_BUFFER_BIT );
+
+ if (glRefConfig.occlusionQuery)
+ {
+ tr.sunFlareQueryActive[tr.sunFlareQueryIndex] = true;
+ qglBeginQuery(GL_SAMPLES_PASSED, tr.sunFlareQuery[tr.sunFlareQueryIndex]);
+ }
+
+ RB_DrawSun(0.3, tr.sunFlareShader);
+
+ if (glRefConfig.occlusionQuery)
+ {
+ qglEndQuery(GL_SAMPLES_PASSED);
+ }
+
+ FBO_Bind(oldFbo);
+ }
+
+ // darken down any stencil shadows
+ RB_ShadowFinish();
+
+ // add light flares on lights that aren't obscured
+ RB_RenderFlares();
+ }
+
+ if (glRefConfig.framebufferObject && tr.renderCubeFbo && backEnd.viewParms.targetFbo == tr.renderCubeFbo)
+ {
+ cubemap_t *cubemap = &tr.cubemaps[backEnd.viewParms.targetFboCubemapIndex];
+
+ FBO_Bind(NULL);
+ if (cubemap && cubemap->image)
+ qglGenerateTextureMipmapEXT(cubemap->image->texnum, GL_TEXTURE_CUBE_MAP);
+ }
+
+ return (const void *)(cmd + 1);
+}
+
+
+/*
+=============
+RB_DrawBuffer
+
+=============
+*/
+const void *RB_DrawBuffer( const void *data ) {
+ const drawBufferCommand_t *cmd;
+
+ cmd = (const drawBufferCommand_t *)data;
+
+ // finish any 2D drawing if needed
+ if(tess.numIndexes)
+ RB_EndSurface();
+
+ if (glRefConfig.framebufferObject)
+ FBO_Bind(NULL);
+
+ 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 ; i<tr.numImages ; i++ ) {
+ image = tr.images[i];
+
+ w = glConfig.vidWidth / 20;
+ h = glConfig.vidHeight / 15;
+ x = i % 20 * w;
+ y = i / 20 * h;
+
+ // show in proportional size in mode 2
+ if ( r_showImages->integer == 2 ) {
+ w *= image->uploadWidth / 512.0f;
+ h *= image->uploadHeight / 512.0f;
+ }
+
+ {
+ vec4_t quadVerts[4];
+
+ GL_BindToTMU(image, TB_COLORMAP);
+
+ 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 = (colorMaskCommand_t*)data;
+
+ // finish any 2D drawing if needed
+ if(tess.numIndexes)
+ RB_EndSurface();
+
+ 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 = (clearDepthCommand_t*)data;
+
+ // finish any 2D drawing if needed
+ if(tess.numIndexes)
+ RB_EndSurface();
+
+ // texture swapping test
+ if (r_showImages->integer)
+ RB_ShowImages();
+
+ if (glRefConfig.framebufferObject)
+ {
+ if (!tr.renderFbo || backEnd.framePostProcessed)
+ {
+ FBO_Bind(NULL);
+ }
+ 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.msaaResolveFbo);
+ 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 = (unsigned char*)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)
+ {
+ if (!backEnd.framePostProcessed)
+ {
+ if (tr.msaaResolveFbo && r_hdr->integer)
+ {
+ // Resolving an RGB16F MSAA FBO to the screen messes with the brightness, so resolve to an RGB16F FBO first
+ FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ FBO_FastBlit(tr.msaaResolveFbo, NULL, NULL, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ }
+ else if (tr.renderFbo)
+ {
+ FBO_FastBlit(tr.renderFbo, NULL, NULL, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ }
+ }
+ }
+
+ if ( !glState.finishCalled ) {
+ qglFinish();
+ }
+
+ GLimp_LogComment( "***************** RB_SwapBuffers *****************\n\n\n" );
+
+ GLimp_EndFrame();
+
+ backEnd.framePostProcessed = false;
+ backEnd.projection2D = false;
+
+ return (const void *)(cmd + 1);
+}
+
+/*
+=============
+RB_CapShadowMap
+
+=============
+*/
+const void *RB_CapShadowMap(const void *data)
+{
+ const capShadowmapCommand_t *cmd = (capShadowmapCommand_t*)data;
+
+ // finish any 2D drawing if needed
+ if(tess.numIndexes)
+ RB_EndSurface();
+
+ if (cmd->map != -1)
+ {
+ if (cmd->cubeSide != -1)
+ {
+ if (tr.shadowCubemaps[cmd->map])
+ {
+ qglCopyTextureSubImage2DEXT(tr.shadowCubemaps[cmd->map]->texnum, GL_TEXTURE_CUBE_MAP_POSITIVE_X + cmd->cubeSide, 0, 0, 0, backEnd.refdef.x, glConfig.vidHeight - ( backEnd.refdef.y + PSHADOW_MAP_SIZE ), PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE);
+ }
+ }
+ else
+ {
+ if (tr.pshadowMaps[cmd->map])
+ {
+ qglCopyTextureSubImage2DEXT(tr.pshadowMaps[cmd->map]->texnum, GL_TEXTURE_2D, 0, 0, 0, backEnd.refdef.x, glConfig.vidHeight - (backEnd.refdef.y + PSHADOW_MAP_SIZE), PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE);
+ }
+ }
+ }
+
+ return (const void *)(cmd + 1);
+}
+
+
+/*
+=============
+RB_PostProcess
+
+=============
+*/
+const void *RB_PostProcess(const void *data)
+{
+ const postProcessCommand_t *cmd = (const postProcessCommand_t*)data;
+ FBO_t *srcFbo;
+ ivec4_t srcBox, dstBox;
+ bool autoExposure;
+
+ // finish any 2D drawing if needed
+ if(tess.numIndexes)
+ RB_EndSurface();
+
+ if (!glRefConfig.framebufferObject || !r_postProcess->integer)
+ {
+ // do nothing
+ return (const void *)(cmd + 1);
+ }
+
+ if (cmd)
+ {
+ backEnd.refdef = cmd->refdef;
+ backEnd.viewParms = cmd->viewParms;
+ }
+
+ srcFbo = tr.renderFbo;
+ if (tr.msaaResolveFbo)
+ {
+ // Resolve the MSAA before anything else
+ // Can't resolve just part of the MSAA FBO, so multiple views will suffer a performance hit here
+ FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
+ srcFbo = tr.msaaResolveFbo;
+ }
+
+ dstBox[0] = backEnd.viewParms.viewportX;
+ dstBox[1] = backEnd.viewParms.viewportY;
+ dstBox[2] = backEnd.viewParms.viewportWidth;
+ dstBox[3] = backEnd.viewParms.viewportHeight;
+
+ if (r_ssao->integer)
+ {
+ srcBox[0] = backEnd.viewParms.viewportX * tr.screenSsaoImage->width / (float)glConfig.vidWidth;
+ srcBox[1] = backEnd.viewParms.viewportY * tr.screenSsaoImage->height / (float)glConfig.vidHeight;
+ srcBox[2] = backEnd.viewParms.viewportWidth * tr.screenSsaoImage->width / (float)glConfig.vidWidth;
+ srcBox[3] = backEnd.viewParms.viewportHeight * tr.screenSsaoImage->height / (float)glConfig.vidHeight;
+
+ FBO_Blit(tr.screenSsaoFbo, srcBox, NULL, srcFbo, dstBox, NULL, NULL, GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO);
+ }
+
+ srcBox[0] = backEnd.viewParms.viewportX;
+ srcBox[1] = backEnd.viewParms.viewportY;
+ srcBox[2] = backEnd.viewParms.viewportWidth;
+ srcBox[3] = backEnd.viewParms.viewportHeight;
+
+ if (srcFbo)
+ {
+ if (r_hdr->integer && (r_toneMap->integer || r_forceToneMap->integer))
+ {
+ autoExposure = r_autoExposure->integer || r_forceAutoExposure->integer;
+ RB_ToneMap(srcFbo, srcBox, NULL, dstBox, autoExposure);
+ }
+ else if (r_cameraExposure->value == 0.0f)
+ {
+ FBO_FastBlit(srcFbo, srcBox, NULL, dstBox, 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, srcBox, NULL, NULL, dstBox, NULL, color, 0);
+ }
+ }
+
+ if (r_drawSunRays->integer)
+ RB_SunRays(NULL, srcBox, NULL, dstBox);
+
+ if (1)
+ RB_BokehBlur(NULL, srcBox, NULL, dstBox, backEnd.refdef.blurFactor);
+ else
+ RB_GaussianBlur(backEnd.refdef.blurFactor);
+
+#if 0
+ if (0)
+ {
+ vec4_t quadVerts[4];
+ vec2_t texCoords[4];
+ ivec4_t iQtrBox;
+ vec4_t box;
+ vec4_t viewInfo;
+ static float scale = 5.0f;
+
+ scale -= 0.005f;
+ if (scale < 0.01f)
+ scale = 5.0f;
+
+ FBO_FastBlit(NULL, NULL, tr.quarterFbo[0], NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+
+ iQtrBox[0] = backEnd.viewParms.viewportX * tr.quarterImage[0]->width / (float)glConfig.vidWidth;
+ iQtrBox[1] = backEnd.viewParms.viewportY * tr.quarterImage[0]->height / (float)glConfig.vidHeight;
+ iQtrBox[2] = backEnd.viewParms.viewportWidth * tr.quarterImage[0]->width / (float)glConfig.vidWidth;
+ iQtrBox[3] = backEnd.viewParms.viewportHeight * tr.quarterImage[0]->height / (float)glConfig.vidHeight;
+
+ qglViewport(iQtrBox[0], iQtrBox[1], iQtrBox[2], iQtrBox[3]);
+ qglScissor(iQtrBox[0], iQtrBox[1], iQtrBox[2], iQtrBox[3]);
+
+ VectorSet4(box, 0.0f, 0.0f, 1.0f, 1.0f);
+
+ texCoords[0][0] = box[0]; texCoords[0][1] = box[3];
+ texCoords[1][0] = box[2]; texCoords[1][1] = box[3];
+ texCoords[2][0] = box[2]; texCoords[2][1] = box[1];
+ texCoords[3][0] = box[0]; texCoords[3][1] = box[1];
+
+ VectorSet4(box, -1.0f, -1.0f, 1.0f, 1.0f);
+
+ VectorSet4(quadVerts[0], box[0], box[3], 0, 1);
+ VectorSet4(quadVerts[1], box[2], box[3], 0, 1);
+ VectorSet4(quadVerts[2], box[2], box[1], 0, 1);
+ VectorSet4(quadVerts[3], box[0], box[1], 0, 1);
+
+ GL_State(GLS_DEPTHTEST_DISABLE);
+
+
+ VectorSet4(viewInfo, backEnd.viewParms.zFar / r_znear->value, backEnd.viewParms.zFar, 0.0, 0.0);
+
+ viewInfo[2] = scale / (float)(tr.quarterImage[0]->width);
+ viewInfo[3] = scale / (float)(tr.quarterImage[0]->height);
+
+ FBO_Bind(tr.quarterFbo[1]);
+ GLSL_BindProgram(&tr.depthBlurShader[2]);
+ GL_BindToTMU(tr.quarterImage[0], TB_COLORMAP);
+ GLSL_SetUniformVec4(&tr.depthBlurShader[2], UNIFORM_VIEWINFO, viewInfo);
+ RB_InstantQuad2(quadVerts, texCoords);
+
+ FBO_Bind(tr.quarterFbo[0]);
+ GLSL_BindProgram(&tr.depthBlurShader[3]);
+ GL_BindToTMU(tr.quarterImage[1], TB_COLORMAP);
+ GLSL_SetUniformVec4(&tr.depthBlurShader[3], UNIFORM_VIEWINFO, viewInfo);
+ RB_InstantQuad2(quadVerts, texCoords);
+
+ SetViewportAndScissor();
+
+ FBO_FastBlit(tr.quarterFbo[1], NULL, NULL, NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+ FBO_Bind(NULL);
+ }
+#endif
+
+ if (0 && r_sunlightMode->integer)
+ {
+ ivec4_t dstBox;
+ VectorSet4(dstBox, 0, glConfig.vidHeight - 128, 128, 128);
+ FBO_BlitFromTexture(tr.sunShadowDepthImage[0], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ VectorSet4(dstBox, 128, glConfig.vidHeight - 128, 128, 128);
+ FBO_BlitFromTexture(tr.sunShadowDepthImage[1], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ VectorSet4(dstBox, 256, glConfig.vidHeight - 128, 128, 128);
+ FBO_BlitFromTexture(tr.sunShadowDepthImage[2], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ VectorSet4(dstBox, 384, glConfig.vidHeight - 128, 128, 128);
+ FBO_BlitFromTexture(tr.sunShadowDepthImage[3], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ }
+
+ if (0 && r_shadows->integer == 4)
+ {
+ ivec4_t dstBox;
+ VectorSet4(dstBox, 512 + 0, glConfig.vidHeight - 128, 128, 128);
+ FBO_BlitFromTexture(tr.pshadowMaps[0], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ VectorSet4(dstBox, 512 + 128, glConfig.vidHeight - 128, 128, 128);
+ FBO_BlitFromTexture(tr.pshadowMaps[1], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ VectorSet4(dstBox, 512 + 256, glConfig.vidHeight - 128, 128, 128);
+ FBO_BlitFromTexture(tr.pshadowMaps[2], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ VectorSet4(dstBox, 512 + 384, glConfig.vidHeight - 128, 128, 128);
+ FBO_BlitFromTexture(tr.pshadowMaps[3], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ }
+
+ if (0)
+ {
+ ivec4_t dstBox;
+ VectorSet4(dstBox, 256, glConfig.vidHeight - 256, 256, 256);
+ FBO_BlitFromTexture(tr.renderDepthImage, NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ VectorSet4(dstBox, 512, glConfig.vidHeight - 256, 256, 256);
+ FBO_BlitFromTexture(tr.screenShadowImage, NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ }
+
+ if (0)
+ {
+ ivec4_t dstBox;
+ VectorSet4(dstBox, 256, glConfig.vidHeight - 256, 256, 256);
+ FBO_BlitFromTexture(tr.sunRaysImage, NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ }
+
+#if 0
+ if (r_cubeMapping->integer && tr.numCubemaps)
+ {
+ ivec4_t dstBox;
+ int cubemapIndex = R_CubemapForPoint( backEnd.viewParms.orientation.origin );
+
+ if (cubemapIndex)
+ {
+ VectorSet4(dstBox, 0, glConfig.vidHeight - 256, 256, 256);
+ //FBO_BlitFromTexture(tr.renderCubeImage, NULL, NULL, NULL, dstBox, &tr.testcubeShader, NULL, 0);
+ FBO_BlitFromTexture(tr.cubemaps[cubemapIndex - 1].image, NULL, NULL, NULL, dstBox, &tr.testcubeShader, NULL, 0);
+ }
+ }
+#endif
+
+ backEnd.framePostProcessed = true;
+
+ return (const void *)(cmd + 1);
+}
+
+// FIXME: put this function declaration elsewhere
+void R_SaveDDS(const char *filename, byte *pic, int width, int height, int depth);
+
+/*
+=============
+RB_ExportCubemaps
+
+=============
+*/
+const void *RB_ExportCubemaps(const void *data)
+{
+ const exportCubemapsCommand_t *cmd = (const exportCubemapsCommand_t*)data;
+
+ // finish any 2D drawing if needed
+ if (tess.numIndexes)
+ RB_EndSurface();
+
+ if (!glRefConfig.framebufferObject || !tr.world || tr.numCubemaps == 0)
+ {
+ // do nothing
+ ri.Printf(PRINT_ALL, "Nothing to export!\n");
+ return (const void *)(cmd + 1);
+ }
+
+ if (cmd)
+ {
+ FBO_t *oldFbo = glState.currentFBO;
+ int sideSize = r_cubemapSize->integer * r_cubemapSize->integer * 4;
+ byte *cubemapPixels = (byte*)ri.Malloc(sideSize * 6);
+ int i, j;
+
+ FBO_Bind(tr.renderCubeFbo);
+
+ for (i = 0; i < tr.numCubemaps; i++)
+ {
+ char filename[MAX_QPATH];
+ cubemap_t *cubemap = &tr.cubemaps[i];
+ byte *p = cubemapPixels;
+
+ for (j = 0; j < 6; j++)
+ {
+ FBO_AttachImage(tr.renderCubeFbo, cubemap->image, GL_COLOR_ATTACHMENT0_EXT, j);
+ qglReadPixels(0, 0, r_cubemapSize->integer, r_cubemapSize->integer, GL_RGBA, GL_UNSIGNED_BYTE, p);
+ p += sideSize;
+ }
+
+ if (cubemap->name[0])
+ {
+ COM_StripExtension(cubemap->name, filename, MAX_QPATH);
+ Q_strcat(filename, MAX_QPATH, ".dds");
+ }
+ else
+ {
+ Com_sprintf(filename, MAX_QPATH, "cubemaps/%s/%03d.dds", tr.world->baseName, i);
+ }
+
+ R_SaveDDS(filename, cubemapPixels, r_cubemapSize->integer, r_cubemapSize->integer, 6);
+ ri.Printf(PRINT_ALL, "Saved cubemap %d as %s\n", i, filename);
+ }
+
+ FBO_Bind(oldFbo);
+
+ ri.Free(cubemapPixels);
+ }
+
+ return (const void *)(cmd + 1);
+}
+
+
+/*
+====================
+RB_ExecuteRenderCommands
+====================
+*/
+void RB_ExecuteRenderCommands( const void *data ) {
+ int t1, t2;
+
+ t1 = ri.Milliseconds ();
+
+ 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_EXPORT_CUBEMAPS:
+ data = RB_ExportCubemaps(data);
+ break;
+ case RC_END_OF_LIST:
+ default:
+ // finish any 2D drawing if needed
+ if(tess.numIndexes)
+ RB_EndSurface();
+
+ // stop rendering
+ t2 = ri.Milliseconds ();
+ backEnd.pc.msec = t2 - t1;
+ return;
+ }
+ }
+
+}
diff --git a/src/renderergl2/tr_bsp.cpp b/src/renderergl2/tr_bsp.cpp
new file mode 100644
index 0000000..3bf74b5
--- /dev/null
+++ b/src/renderergl2/tr_bsp.cpp
@@ -0,0 +1,3046 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// tr_map.c
+
+#include "tr_local.h"
+
+#define JSON_IMPLEMENTATION
+#include "qcommon/json.h"
+#undef JSON_IMPLEMENTATION
+
+/*
+
+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;
+
+ // Minimum values
+ if(r < r_mapLightmapMin->integer){
+ r = r_mapLightmapMin->integer;
+ }
+ if(g < r_mapLightmapMin->integer){
+ g = r_mapLightmapMin->integer;
+ }
+ if(b < r_mapLightmapMin->integer){
+ b = r_mapLightmapMin->integer;
+ }
+
+ // 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_ColorShiftLightingFloats
+
+===============
+*/
+static void R_ColorShiftLightingFloats(float in[4], float out[4])
+{
+ float r, g, b;
+ float scale = (1 << (r_mapOverBrightBits->integer - tr.overbrightBits)) / 255.0f;
+
+ r = in[0] * scale;
+ g = in[1] * scale;
+ b = in[2] * scale;
+
+ // Minimum values
+ if(r < r_mapLightmapMin->value / 255.0f){
+ r = r_mapLightmapMin->value / 255.0f;
+ }
+ if(g < r_mapLightmapMin->value / 255.0f){
+ g = r_mapLightmapMin->value / 255.0f;
+ }
+ if(b < r_mapLightmapMin->value / 255.0f){
+ b = r_mapLightmapMin->value / 255.0f;
+ }
+
+ // normalize by color instead of saturating to white
+ if ( r > 1 || g > 1 || b > 1 ) {
+ float max;
+
+ max = r > g ? r : g;
+ max = max > b ? max : b;
+ r = r / max;
+ g = g / max;
+ b = b / max;
+ }
+
+ out[0] = r;
+ out[1] = g;
+ out[2] = b;
+ out[3] = in[3];
+}
+
+// Modified from http://graphicrants.blogspot.jp/2009/04/rgbm-color-encoding.html
+void ColorToRGBM(const vec3_t color, unsigned char rgbm[4])
+{
+ vec3_t sample;
+ float maxComponent;
+
+ VectorCopy(color, sample);
+
+ maxComponent = MAX(sample[0], sample[1]);
+ maxComponent = MAX(maxComponent, sample[2]);
+ maxComponent = CLAMP(maxComponent, 1.0f/255.0f, 1.0f);
+
+ rgbm[3] = (unsigned char) ceil(maxComponent * 255.0f);
+ maxComponent = 255.0f / rgbm[3];
+
+ VectorScale(sample, maxComponent, sample);
+
+ rgbm[0] = (unsigned char) (sample[0] * 255);
+ rgbm[1] = (unsigned char) (sample[1] * 255);
+ rgbm[2] = (unsigned char) (sample[2] * 255);
+}
+
+void ColorToRGB16(const vec3_t color, uint16_t rgb16[3])
+{
+ rgb16[0] = color[0] * 65535.0f + 0.5f;
+ rgb16[1] = color[1] * 65535.0f + 0.5f;
+ rgb16[2] = color[2] * 65535.0f + 0.5f;
+}
+
+
+/*
+===============
+R_LoadLightmaps
+
+===============
+*/
+#define DEFAULT_LIGHTMAP_SIZE 128
+static void R_LoadLightmaps( lump_t *l, lump_t *surfs ) {
+ int/*imgFlags_t*/ imgFlags = IMGFLAG_NOLIGHTSCALE | IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE;
+ byte *buf, *buf_p;
+ dsurface_t *surf;
+ int len;
+ byte *image;
+ int i, j, numLightmaps, textureInternalFormat = 0;
+ int numLightmapsPerPage = 16;
+ float maxIntensity = 0;
+ double sumIntensity = 0;
+
+ len = l->filelen;
+ if ( !len ) {
+ return;
+ }
+ buf = fileBase + l->fileofs;
+
+ // we are about to upload textures
+ R_IssuePendingRenderCommands();
+
+ tr.lightmapSize = DEFAULT_LIGHTMAP_SIZE;
+ numLightmaps = len / (tr.lightmapSize * tr.lightmapSize * 3);
+
+ // check for deluxe mapping
+ if (numLightmaps <= 1)
+ {
+ tr.worldDeluxeMapping = false;
+ }
+ else
+ {
+ tr.worldDeluxeMapping = true;
+ 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 = false;
+ break;
+ }
+ }
+ }
+
+ image = (byte*)ri.Malloc(tr.lightmapSize * tr.lightmapSize * 4 * 2);
+
+ if (tr.worldDeluxeMapping)
+ numLightmaps >>= 1;
+
+ // Use fat lightmaps of an appropriate size.
+ if (r_mergeLightmaps->integer)
+ {
+ int maxLightmapsPerAxis = glConfig.maxTextureSize / tr.lightmapSize;
+ int lightmapCols = 4, lightmapRows = 4;
+
+ // Increase width at first, then height.
+ while (lightmapCols * lightmapRows < numLightmaps && lightmapCols != maxLightmapsPerAxis)
+ lightmapCols <<= 1;
+
+ while (lightmapCols * lightmapRows < numLightmaps && lightmapRows != maxLightmapsPerAxis)
+ lightmapRows <<= 1;
+
+ tr.fatLightmapCols = lightmapCols;
+ tr.fatLightmapRows = lightmapRows;
+ numLightmapsPerPage = lightmapCols * lightmapRows;
+
+ tr.numLightmaps = (numLightmaps + (numLightmapsPerPage - 1)) / numLightmapsPerPage;
+ }
+ else
+ {
+ tr.numLightmaps = numLightmaps;
+ }
+
+ tr.lightmaps = (image_t**)ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low );
+
+ if (tr.worldDeluxeMapping)
+ tr.deluxemaps = (image_t**)ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low );
+
+ textureInternalFormat = GL_RGBA8;
+ if (r_hdr->integer)
+ {
+ // Check for the first hdr lightmap, if it exists, use GL_RGBA16 for textures.
+ char filename[MAX_QPATH];
+
+ Com_sprintf(filename, sizeof(filename), "maps/%s/lm_0000.hdr", s_worldData.baseName);
+ if (ri.FS_FileExists(filename))
+ textureInternalFormat = GL_RGBA16;
+ }
+
+ if (r_mergeLightmaps->integer)
+ {
+ int width = tr.fatLightmapCols * tr.lightmapSize;
+ int height = tr.fatLightmapRows * tr.lightmapSize;
+
+ for (i = 0; i < tr.numLightmaps; i++)
+ {
+ tr.lightmaps[i] = R_CreateImage(va("_fatlightmap%d", i), NULL, width, height, IMGTYPE_COLORALPHA, imgFlags, textureInternalFormat);
+
+ if (tr.worldDeluxeMapping)
+ tr.deluxemaps[i] = R_CreateImage(va("_fatdeluxemap%d", i), NULL, width, height, IMGTYPE_DELUXE, imgFlags, 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 % numLightmapsPerPage;
+ xoff = (lightmaponpage % tr.fatLightmapCols) * tr.lightmapSize;
+ yoff = (lightmaponpage / tr.fatLightmapCols) * tr.lightmapSize;
+
+ lightmapnum /= numLightmapsPerPage;
+ }
+
+ // if (tr.worldLightmapping)
+ {
+ char filename[MAX_QPATH];
+ byte *hdrLightmap = NULL;
+ int size = 0;
+
+ // look for hdr lightmaps
+ if (textureInternalFormat == GL_RGBA16)
+ {
+ 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, *end = hdrLightmap + size;
+ //ri.Printf(PRINT_ALL, "found!\n");
+
+ /* FIXME: don't just skip over this header and actually parse it */
+ while (p < end && !(*p == '\n' && *(p+1) == '\n'))
+ p++;
+
+ p += 2;
+
+ while (p < end && !(*p == '\n'))
+ p++;
+
+ p++;
+
+ if (p >= end)
+ ri.Error(ERR_DROP, "Bad header for %s!", filename);
+
+ buf_p = p;
+
+#if 0 // HDRFILE_RGBE
+ if ((int)(end - hdrLightmap) != tr.lightmapSize * tr.lightmapSize * 4)
+ ri.Error(ERR_DROP, "Bad size for %s (%i)!", filename, size);
+#else // HDRFILE_FLOAT
+ if ((int)(end - hdrLightmap) != tr.lightmapSize * tr.lightmapSize * 12)
+ ri.Error(ERR_DROP, "Bad size for %s (%i)!", filename, size);
+#endif
+ }
+ else
+ {
+ int imgOffset = tr.worldDeluxeMapping ? i * 2 : i;
+ buf_p = buf + imgOffset * tr.lightmapSize * tr.lightmapSize * 3;
+ }
+
+ for ( j = 0 ; j < tr.lightmapSize * tr.lightmapSize; j++ )
+ {
+ if (hdrLightmap)
+ {
+ vec4_t color;
+
+#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
+ color[3] = 1.0f;
+
+ R_ColorShiftLightingFloats(color, color);
+
+ ColorToRGB16(color, (uint16_t *)(&image[j * 8]));
+ ((uint16_t *)(&image[j * 8]))[3] = 65535;
+ }
+ else if (textureInternalFormat == GL_RGBA16)
+ {
+ vec4_t color;
+
+ //hack: convert LDR lightmap to HDR one
+ color[0] = MAX(buf_p[j*3+0], 0.499f);
+ color[1] = MAX(buf_p[j*3+1], 0.499f);
+ color[2] = MAX(buf_p[j*3+2], 0.499f);
+
+ // 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;
+ }
+ color[3] = 1.0f;
+
+ R_ColorShiftLightingFloats(color, color);
+
+ ColorToRGB16(color, (uint16_t *)(&image[j * 8]));
+ ((uint16_t *)(&image[j * 8]))[3] = 65535;
+ }
+ 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, textureInternalFormat);
+ else
+ tr.lightmaps[i] = R_CreateImage(va("*lightmap%d", i), image, tr.lightmapSize, tr.lightmapSize, IMGTYPE_COLORALPHA, imgFlags, 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+1] == 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, GL_RGBA8 );
+ else
+ tr.deluxemaps[i] = R_CreateImage(va("*deluxemap%d", i), image, tr.lightmapSize, tr.lightmapSize, IMGTYPE_DELUXE, imgFlags, 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;
+
+ if (tr.fatLightmapCols > 0)
+ {
+ lightmapnum %= (tr.fatLightmapCols * tr.fatLightmapRows);
+ return (input + (lightmapnum % tr.fatLightmapCols)) / (float)(tr.fatLightmapCols);
+ }
+
+ return input;
+}
+
+static float FatPackV(float input, int lightmapnum)
+{
+ if (lightmapnum < 0)
+ return input;
+
+ if (tr.worldDeluxeMapping)
+ lightmapnum >>= 1;
+
+ if (tr.fatLightmapCols > 0)
+ {
+ lightmapnum %= (tr.fatLightmapCols * tr.fatLightmapRows);
+ return (input + (lightmapnum / tr.fatLightmapCols)) / (float)(tr.fatLightmapRows);
+ }
+
+ return input;
+}
+
+
+static int FatLightmap(int lightmapnum)
+{
+ if (lightmapnum < 0)
+ return lightmapnum;
+
+ if (tr.worldDeluxeMapping)
+ lightmapnum >>= 1;
+
+ if (tr.fatLightmapCols > 0)
+ return lightmapnum / (tr.fatLightmapCols * tr.fatLightmapRows);
+
+ 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 = 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 = (byte*)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, true );
+
+ // if the shader had errors, just use default shader
+ if ( shader->defaultShader ) {
+ return tr.defaultShader;
+ }
+
+ return shader;
+}
+
+void LoadDrawVertToSrfVert(srfVert_t *s, drawVert_t *d, int realLightmapNum, float hdrVertColors[3], vec3_t *bounds)
+{
+ vec4_t v;
+
+ s->xyz[0] = LittleFloat(d->xyz[0]);
+ s->xyz[1] = LittleFloat(d->xyz[1]);
+ s->xyz[2] = LittleFloat(d->xyz[2]);
+
+ if (bounds)
+ AddPointToBounds(s->xyz, bounds[0], bounds[1]);
+
+ s->st[0] = LittleFloat(d->st[0]);
+ s->st[1] = LittleFloat(d->st[1]);
+
+ if (realLightmapNum >= 0)
+ {
+ s->lightmap[0] = FatPackU(LittleFloat(d->lightmap[0]), realLightmapNum);
+ s->lightmap[1] = FatPackV(LittleFloat(d->lightmap[1]), realLightmapNum);
+ }
+ else
+ {
+ s->lightmap[0] = LittleFloat(d->lightmap[0]);
+ s->lightmap[1] = LittleFloat(d->lightmap[1]);
+ }
+
+ v[0] = LittleFloat(d->normal[0]);
+ v[1] = LittleFloat(d->normal[1]);
+ v[2] = LittleFloat(d->normal[2]);
+
+ R_VaoPackNormal(s->normal, v);
+
+ if (hdrVertColors)
+ {
+ v[0] = hdrVertColors[0];
+ v[1] = hdrVertColors[1];
+ v[2] = hdrVertColors[2];
+ }
+ else
+ {
+ //hack: convert LDR vertex colors to HDR
+ if (r_hdr->integer)
+ {
+ v[0] = MAX(d->color[0], 0.499f);
+ v[1] = MAX(d->color[1], 0.499f);
+ v[2] = MAX(d->color[2], 0.499f);
+ }
+ else
+ {
+ v[0] = d->color[0];
+ v[1] = d->color[1];
+ v[2] = d->color[2];
+ }
+
+ }
+ v[3] = d->color[3] / 255.0f;
+
+ R_ColorShiftLightingFloats(v, v);
+ R_VaoPackColor(s->color, v);
+}
+
+
+/*
+===============
+ParseFace
+===============
+*/
+static void ParseFace( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors, msurface_t *surf, int *indexes ) {
+ int i, j;
+ srfBspSurface_t *cv;
+ glIndex_t *tri;
+ int numVerts, numIndexes, 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;
+ }
+
+ numIndexes = LittleLong(ds->numIndexes);
+
+ //cv = ri.Hunk_Alloc(sizeof(*cv), h_low);
+ cv = (srfBspSurface_t*)surf->data;
+ cv->surfaceType = SF_FACE;
+
+ cv->numIndexes = numIndexes;
+ cv->indexes = (glIndex_t*)ri.Hunk_Alloc(numIndexes * sizeof(cv->indexes[0]), h_low);
+
+ cv->numVerts = numVerts;
+ cv->verts = (srfVert_t*)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++)
+ LoadDrawVertToSrfVert(&cv->verts[i], &verts[i], realLightmapNum, hdrVertColors ? hdrVertColors + (ds->firstVert + i) * 3 : NULL, surf->cullinfo.bounds);
+
+ // copy triangles
+ badTriangles = 0;
+ indexes += LittleLong(ds->firstIndex);
+ for(i = 0, tri = cv->indexes; i < numIndexes; i += 3, tri += 3)
+ {
+ for(j = 0; j < 3; j++)
+ {
+ tri[j] = LittleLong(indexes[i + j]);
+
+ if(tri[j] >= numVerts)
+ {
+ ri.Error(ERR_DROP, "Bad index in face surface");
+ }
+ }
+
+ if ((tri[0] == tri[1]) || (tri[1] == tri[2]) || (tri[0] == tri[2]))
+ {
+ tri -= 3;
+ 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, numIndexes / 3, numVerts, numIndexes / 3 - badTriangles);
+ cv->numIndexes -= badTriangles * 3;
+ }
+
+ // take the plane information from the lightmap vector
+ for ( i = 0 ; i < 3 ; i++ ) {
+ cv->cullPlane.normal[i] = LittleFloat( ds->lightmapVecs[2][i] );
+ }
+ cv->cullPlane.dist = DotProduct( cv->verts[0].xyz, cv->cullPlane.normal );
+ SetPlaneSignbits( &cv->cullPlane );
+ cv->cullPlane.type = PlaneTypeForNormal( cv->cullPlane.normal );
+ surf->cullinfo.plane = cv->cullPlane;
+
+ surf->data = (surfaceType_t *)cv;
+
+ // Calculate tangent spaces
+ {
+ srfVert_t *dv[3];
+
+ for(i = 0, tri = cv->indexes; i < numIndexes; i += 3, tri += 3)
+ {
+ dv[0] = &cv->verts[tri[0]];
+ dv[1] = &cv->verts[tri[1]];
+ dv[2] = &cv->verts[tri[2]];
+
+ R_CalcTangentVectors(dv);
+ }
+ }
+}
+
+
+/*
+===============
+ParseMesh
+===============
+*/
+static void ParseMesh ( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors, msurface_t *surf ) {
+ srfBspSurface_t *grid = (srfBspSurface_t *)surf->data;
+ int i;
+ 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++)
+ LoadDrawVertToSrfVert(&points[i], &verts[i], realLightmapNum, hdrVertColors ? hdrVertColors + (ds->firstVert + i) * 3 : NULL, NULL);
+
+ // pre-tesseleate
+ R_SubdividePatchToGrid( grid, width, height, points );
+
+ // 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 );
+
+ surf->cullinfo.type = CULLINFO_BOX | CULLINFO_SPHERE;
+ VectorCopy(grid->cullBounds[0], surf->cullinfo.bounds[0]);
+ VectorCopy(grid->cullBounds[1], surf->cullinfo.bounds[1]);
+ VectorCopy(grid->cullOrigin, surf->cullinfo.localOrigin);
+ surf->cullinfo.radius = grid->cullRadius;
+}
+
+/*
+===============
+ParseTriSurf
+===============
+*/
+static void ParseTriSurf( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors, msurface_t *surf, int *indexes ) {
+ srfBspSurface_t *cv;
+ glIndex_t *tri;
+ int i, j;
+ int numVerts, numIndexes, 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);
+ numIndexes = LittleLong(ds->numIndexes);
+
+ //cv = ri.Hunk_Alloc(sizeof(*cv), h_low);
+ cv = (srfBspSurface_t*)surf->data;
+ cv->surfaceType = SF_TRIANGLES;
+
+ cv->numIndexes = numIndexes;
+ cv->indexes = (glIndex_t*)ri.Hunk_Alloc(numIndexes * sizeof(cv->indexes[0]), h_low);
+
+ cv->numVerts = numVerts;
+ cv->verts = (srfVert_t*)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++)
+ LoadDrawVertToSrfVert(&cv->verts[i], &verts[i], -1, hdrVertColors ? hdrVertColors + (ds->firstVert + i) * 3 : NULL, surf->cullinfo.bounds);
+
+ // copy triangles
+ badTriangles = 0;
+ indexes += LittleLong(ds->firstIndex);
+ for(i = 0, tri = cv->indexes; i < numIndexes; i += 3, tri += 3)
+ {
+ for(j = 0; j < 3; j++)
+ {
+ tri[j] = LittleLong(indexes[i + j]);
+
+ if(tri[j] >= numVerts)
+ {
+ ri.Error(ERR_DROP, "Bad index in face surface");
+ }
+ }
+
+ if ((tri[0] == tri[1]) || (tri[1] == tri[2]) || (tri[0] == tri[2]))
+ {
+ tri -= 3;
+ 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, numIndexes / 3, numVerts, numIndexes / 3 - badTriangles);
+ cv->numIndexes -= badTriangles * 3;
+ }
+
+ // Calculate tangent spaces
+ {
+ srfVert_t *dv[3];
+
+ for(i = 0, tri = cv->indexes; i < numIndexes; i += 3, tri += 3)
+ {
+ dv[0] = &cv->verts[tri[0]];
+ dv[1] = &cv->verts[tri[1]];
+ dv[2] = &cv->verts[tri[2]];
+
+ R_CalcTangentVectors(dv);
+ }
+ }
+}
+
+/*
+===============
+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 = (srfFlare_t*)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] );
+ }
+
+ surf->cullinfo.type = CULLINFO_NONE;
+}
+
+
+/*
+=================
+R_MergedWidthPoints
+
+returns true if there are grid points merged on a width edge
+=================
+*/
+int R_MergedWidthPoints(srfBspSurface_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 true;
+ }
+ }
+ return false;
+}
+
+/*
+=================
+R_MergedHeightPoints
+
+returns true if there are grid points merged on a height edge
+=================
+*/
+int R_MergedHeightPoints(srfBspSurface_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 true;
+ }
+ }
+ return false;
+}
+
+/*
+=================
+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, srfBspSurface_t *grid1 ) {
+ int j, k, l, m, n, offset1, offset2, touch;
+ srfBspSurface_t *grid2;
+
+ for ( j = start; j < s_worldData.numsurfaces; j++ ) {
+ //
+ grid2 = (srfBspSurface_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 = false;
+ 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 = true;
+ }
+ }
+ 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 = true;
+ }
+ }
+ }
+ }
+ 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 = true;
+ }
+ }
+ 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 = true;
+ }
+ }
+ }
+ }
+ 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;
+ srfBspSurface_t *grid1;
+
+ for ( i = 0; i < s_worldData.numsurfaces; i++ ) {
+ //
+ grid1 = (srfBspSurface_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;
+ srfBspSurface_t *grid1, *grid2;
+ int k, l, m, n, offset1, offset2, row, column;
+
+ grid1 = (srfBspSurface_t *) s_worldData.surfaces[grid1num].data;
+ grid2 = (srfBspSurface_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;
+ R_GridInsertColumn( grid2, l+1, row,
+ grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]);
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ 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;
+ R_GridInsertRow( grid2, l+1, column,
+ grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]);
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ }
+ }
+ 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;
+ R_GridInsertColumn( grid2, l+1, row,
+ grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]);
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ 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;
+ R_GridInsertRow( grid2, l+1, column,
+ grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]);
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ }
+ }
+ 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 || 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;
+ R_GridInsertColumn( grid2, l+1, row,
+ grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]);
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ for (m = 0; m < 2; m++) {
+
+ if (!grid2 || 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;
+ R_GridInsertRow( grid2, l+1, column,
+ grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]);
+ if (!grid2)
+ break;
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ }
+ }
+ 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 || 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;
+ R_GridInsertColumn( grid2, l+1, row,
+ grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]);
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ for (m = 0; m < 2; m++) {
+
+ if (!grid2 || 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;
+ R_GridInsertRow( grid2, l+1, column,
+ grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]);
+ grid2->lodStitched = false;
+ s_worldData.surfaces[grid2num].data = (surfaceType_t*) grid2;
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/*
+===============
+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;
+ srfBspSurface_t *grid1, *grid2;
+
+ numstitches = 0;
+ grid1 = (srfBspSurface_t *) s_worldData.surfaces[grid1num].data;
+ for ( j = 0; j < s_worldData.numsurfaces; j++ ) {
+ //
+ grid2 = (srfBspSurface_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;
+ srfBspSurface_t *grid1;
+
+ numstitches = 0;
+ do
+ {
+ stitched = false;
+ for ( i = 0; i < s_worldData.numsurfaces; i++ ) {
+ //
+ grid1 = (srfBspSurface_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 = true;
+ stitched = true;
+ //
+ numstitches += R_TryStitchingPatch( i );
+ }
+ }
+ while (stitched);
+ ri.Printf( PRINT_ALL, "stitched %d LoD cracks\n", numstitches );
+}
+
+/*
+===============
+R_MovePatchSurfacesToHunk
+===============
+*/
+void R_MovePatchSurfacesToHunk(void) {
+ int i;
+ srfBspSurface_t *grid;
+
+ for ( i = 0; i < s_worldData.numsurfaces; i++ ) {
+ void *copyFrom;
+ //
+ grid = (srfBspSurface_t *) s_worldData.surfaces[i].data;
+ // if this surface is not a grid
+ if ( grid->surfaceType != SF_GRID )
+ continue;
+ //
+
+ copyFrom = grid->widthLodError;
+ grid->widthLodError = (float*)ri.Hunk_Alloc( grid->width * 4, h_low );
+ Com_Memcpy(grid->widthLodError, copyFrom, grid->width * 4);
+ ri.Free(copyFrom);
+
+ copyFrom = grid->heightLodError;
+ grid->heightLodError = (float*)ri.Hunk_Alloc(grid->height * 4, h_low);
+ Com_Memcpy(grid->heightLodError, copyFrom, grid->height * 4);
+ ri.Free(copyFrom);
+
+ copyFrom = grid->indexes;
+ grid->indexes = (glIndex_t*)ri.Hunk_Alloc(grid->numIndexes * sizeof(glIndex_t), h_low);
+ Com_Memcpy(grid->indexes, copyFrom, grid->numIndexes * sizeof(glIndex_t));
+ ri.Free(copyFrom);
+
+ copyFrom = grid->verts;
+ grid->verts = (srfVert_t*)ri.Hunk_Alloc(grid->numVerts * sizeof(srfVert_t), h_low);
+ Com_Memcpy(grid->verts, copyFrom, grid->numVerts * sizeof(srfVert_t));
+ ri.Free(copyFrom);
+ }
+}
+
+
+/*
+===============
+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;
+
+ if (surfs->filelen % sizeof(*in))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+ count = surfs->filelen / sizeof(*in);
+
+ dv = (drawVert_t*)(fileBase + verts->fileofs);
+ if (verts->filelen % sizeof(*dv))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+
+ indexes = (int*)(fileBase + indexLump->fileofs);
+ if ( indexLump->filelen % sizeof(*indexes))
+ ri.Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name);
+
+ out = (msurface_t*)ri.Hunk_Alloc ( count * sizeof(*out), h_low );
+
+ s_worldData.surfaces = out;
+ s_worldData.numsurfaces = count;
+ s_worldData.surfacesViewCount = (int*)ri.Hunk_Alloc ( count * sizeof(*s_worldData.surfacesViewCount), h_low );
+ s_worldData.surfacesDlightBits = (int*)ri.Hunk_Alloc ( count * sizeof(*s_worldData.surfacesDlightBits), h_low );
+ s_worldData.surfacesPshadowBits = (int*)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)!", 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 VAOs,
+ // which don't actually use the verts and indexes
+ in = (dsurface_t*)(fileBase + surfs->fileofs);
+ out = s_worldData.surfaces;
+ for ( i = 0 ; i < count ; i++, in++, out++ ) {
+ switch ( LittleLong( in->surfaceType ) ) {
+ case MST_PATCH:
+ out->data = (surfaceType_t*)ri.Hunk_Alloc( sizeof(srfBspSurface_t), h_low);
+ break;
+ case MST_TRIANGLE_SOUP:
+ out->data = (surfaceType_t*)ri.Hunk_Alloc( sizeof(srfBspSurface_t), h_low);
+ break;
+ case MST_PLANAR:
+ out->data = (surfaceType_t*)ri.Hunk_Alloc( sizeof(srfBspSurface_t), h_low);
+ break;
+ case MST_FLARE:
+ out->data = (surfaceType_t*)ri.Hunk_Alloc( sizeof(srfFlare_t), h_low);
+ break;
+ default:
+ break;
+ }
+ }
+
+ in = (dsurface_t*)(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 );
+ 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 );
+ 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 = (dmodel_t*)(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 = (bmodel_t*)ri.Hunk_Alloc( count * sizeof(*out), h_low );
+
+ for ( i=0 ; i<count ; i++, in++, out++ ) {
+ model_t *model;
+
+ model = R_AllocModel();
+
+ assert( model != NULL ); // this should never happen
+ if ( model == NULL ) {
+ ri.Error(ERR_DROP, "R_LoadSubmodels: R_AllocModel() failed");
+ }
+
+ model->type = 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)
+ {
+ // Add this for limiting VAO 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 = (dnode_t*)(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 = (mnode_t*)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 ; i<numNodes; i++, in++, out++)
+ {
+ for (j=0 ; j<3 ; j++)
+ {
+ out->mins[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 = (dleaf_t*)(fileBase + leafLump->fileofs);
+ for ( i=0 ; i<numLeafs ; i++, inLeaf++, out++)
+ {
+ for (j=0 ; j<3 ; j++)
+ {
+ out->mins[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 = (dshader_t*)(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 = (dshader_t*)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 ; i<count ; i++ ) {
+ out[i].surfaceFlags = LittleLong( out[i].surfaceFlags );
+ out[i].contentFlags = LittleLong( out[i].contentFlags );
+ }
+}
+
+
+/*
+=================
+R_LoadMarksurfaces
+=================
+*/
+static void R_LoadMarksurfaces (lump_t *l)
+{
+ int i, j, count;
+ int *in;
+ int *out;
+
+ in = (int*)(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 = (int*)ri.Hunk_Alloc ( count*sizeof(*out), h_low);
+
+ s_worldData.marksurfaces = out;
+ s_worldData.nummarksurfaces = count;
+
+ for ( i=0 ; i<count ; i++)
+ {
+ j = LittleLong(in[i]);
+ out[i] = j;
+ }
+}
+
+
+/*
+=================
+R_LoadPlanes
+=================
+*/
+static void R_LoadPlanes( lump_t *l ) {
+ int i, j;
+ cplane_t *out;
+ dplane_t *in;
+ int count;
+ int bits;
+
+ in = (dplane_t*)(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 = (cplane_t*)ri.Hunk_Alloc ( count*2*sizeof(*out), h_low);
+
+ s_worldData.planes = out;
+ s_worldData.numplanes = count;
+
+ for ( i=0 ; i<count ; i++, in++, out++) {
+ bits = 0;
+ for (j=0 ; j<3 ; j++) {
+ out->normal[j] = LittleFloat (in->normal[j]);
+ if (out->normal[j] < 0) {
+ bits |= 1<<j;
+ }
+ }
+
+ out->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 = (dfog_t*)(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 = (fog_t*)ri.Hunk_Alloc ( s_worldData.numfogs*sizeof(*out), h_low);
+ out = s_worldData.fogs + 1;
+
+ if ( !count ) {
+ return;
+ }
+
+ brushes = (dbrush_t*)(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 = (dbrushside_t*)(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 ; i<count ; i++, fogs++) {
+ out->originalBrushNumber = 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, true );
+
+ out->parms = shader->fogParms;
+
+ out->colorInt = ColorBytes4 ( shader->fogParms.color[0],
+ shader->fogParms.color[1],
+ shader->fogParms.color[2], 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 = false;
+ } else {
+ out->hasSurface = true;
+ 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 = (byte*)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)
+ {
+ //ri.Printf(PRINT_ALL, "found!\n");
+
+ if (size != sizeof(float) * 6 * numGridPoints)
+ ri.Error(ERR_DROP, "Bad size for %s (%i, expected %i)!", filename, size, (int)(sizeof(float)) * 6 * numGridPoints);
+
+ w->lightGrid16 = (uint16_t*)ri.Hunk_Alloc(sizeof(w->lightGrid16) * 6 * numGridPoints, h_low);
+
+ for (i = 0; i < numGridPoints ; i++)
+ {
+ vec4_t c;
+
+ c[0] = hdrLightGrid[i * 6];
+ c[1] = hdrLightGrid[i * 6 + 1];
+ c[2] = hdrLightGrid[i * 6 + 2];
+ c[3] = 1.0f;
+
+ R_ColorShiftLightingFloats(c, c);
+ ColorToRGB16(c, &w->lightGrid16[i * 6]);
+
+ c[0] = hdrLightGrid[i * 6 + 3];
+ c[1] = hdrLightGrid[i * 6 + 4];
+ c[2] = hdrLightGrid[i * 6 + 5];
+ c[3] = 1.0f;
+
+ R_ColorShiftLightingFloats(c, c);
+ ColorToRGB16(c, &w->lightGrid16[i * 6 + 3]);
+ }
+ }
+ else if (0)
+ {
+ // promote 8-bit lightgrid to 16-bit
+ w->lightGrid16 = (uint16_t*)ri.Hunk_Alloc(sizeof(w->lightGrid16) * 6 * numGridPoints, h_low);
+
+ for (i = 0; i < numGridPoints; i++)
+ {
+ w->lightGrid16[i * 6] = w->lightGridData[i * 8] * 257;
+ w->lightGrid16[i * 6 + 1] = w->lightGridData[i * 8 + 1] * 257;
+ w->lightGrid16[i * 6 + 2] = w->lightGridData[i * 8 + 2] * 257;
+ w->lightGrid16[i * 6 + 3] = w->lightGridData[i * 8 + 3] * 257;
+ w->lightGrid16[i * 6 + 4] = w->lightGridData[i * 8 + 4] * 257;
+ w->lightGrid16[i * 6 + 5] = w->lightGridData[i * 8 + 5] * 257;
+ }
+ }
+
+ if (hdrLightGrid)
+ ri.FS_FreeFile(hdrLightGrid);
+ }
+}
+
+/*
+================
+R_LoadEntities
+================
+*/
+void R_LoadEntities( lump_t *l ) {
+ char *p, *token;
+ char *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 = (char*)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
+ if (!Q_strncmp(keyname, "vertexremapshader", strlen("vertexremapshader")) ) {
+ 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
+ if (!Q_strncmp(keyname, "remapshader", strlen("remapshader")) ) {
+ 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
+=================
+*/
+bool 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 false;
+ } else {
+ return true;
+ }
+}
+
+#ifndef MAX_SPAWN_VARS
+#define MAX_SPAWN_VARS 64
+#endif
+
+// derived from G_ParseSpawnVars() in g_spawn.c
+bool R_ParseSpawnVars( char *spawnVarChars, int maxSpawnVarChars, int *numSpawnVars, char *spawnVars[MAX_SPAWN_VARS][2] )
+{
+ char keyname[MAX_TOKEN_CHARS];
+ char com_token[MAX_TOKEN_CHARS];
+ int numSpawnVarChars = 0;
+
+ *numSpawnVars = 0;
+
+ // parse the opening brace
+ if ( !R_GetEntityToken( com_token, sizeof( com_token ) ) ) {
+ // end of spawn string
+ return false;
+ }
+ if ( com_token[0] != '{' ) {
+ ri.Printf( PRINT_ALL, "R_ParseSpawnVars: found %s when expecting {\n",com_token );
+ return false;
+ }
+
+ // go through all the key / value pairs
+ while ( 1 ) {
+ int keyLength, tokenLength;
+
+ // parse key
+ if ( !R_GetEntityToken( keyname, sizeof( keyname ) ) ) {
+ ri.Printf( PRINT_ALL, "R_ParseSpawnVars: EOF without closing brace\n" );
+ return false;
+ }
+
+ if ( keyname[0] == '}' ) {
+ break;
+ }
+
+ // parse value
+ if ( !R_GetEntityToken( com_token, sizeof( com_token ) ) ) {
+ ri.Printf( PRINT_ALL, "R_ParseSpawnVars: EOF without closing brace\n" );
+ return false;
+ }
+
+ if ( com_token[0] == '}' ) {
+ ri.Printf( PRINT_ALL, "R_ParseSpawnVars: closing brace without data\n" );
+ return false;
+ }
+
+ if ( *numSpawnVars == MAX_SPAWN_VARS ) {
+ ri.Printf( PRINT_ALL, "R_ParseSpawnVars: MAX_SPAWN_VARS\n" );
+ return false;
+ }
+
+ keyLength = strlen(keyname) + 1;
+ tokenLength = strlen(com_token) + 1;
+
+ if (numSpawnVarChars + keyLength + tokenLength > maxSpawnVarChars)
+ {
+ ri.Printf( PRINT_ALL, "R_ParseSpawnVars: MAX_SPAWN_VAR_CHARS\n" );
+ return false;
+ }
+
+ strcpy(spawnVarChars + numSpawnVarChars, keyname);
+ spawnVars[ *numSpawnVars ][0] = spawnVarChars + numSpawnVarChars;
+ numSpawnVarChars += keyLength;
+
+ strcpy(spawnVarChars + numSpawnVarChars, com_token);
+ spawnVars[ *numSpawnVars ][1] = spawnVarChars + numSpawnVarChars;
+ numSpawnVarChars += tokenLength;
+
+ (*numSpawnVars)++;
+ }
+
+ return true;
+}
+
+void R_LoadEnvironmentJson(const char *baseName)
+{
+ char filename[MAX_QPATH];
+
+ union {
+ char *c;
+ void *v;
+ } buffer;
+ char *bufferEnd;
+
+ const char *cubemapArrayJson;
+ int filelen, i;
+
+ Com_sprintf(filename, MAX_QPATH, "cubemaps/%s/env.json", baseName);
+
+ filelen = ri.FS_ReadFile(filename, &buffer.v);
+ if (!buffer.c)
+ return;
+ bufferEnd = buffer.c + filelen;
+
+ if (JSON_ValueGetType(buffer.c, bufferEnd) != JSONTYPE_OBJECT)
+ {
+ ri.Printf(PRINT_ALL, "Bad %s: does not start with a object\n", filename);
+ ri.FS_FreeFile(buffer.v);
+ return;
+ }
+
+ cubemapArrayJson = JSON_ObjectGetNamedValue(buffer.c, bufferEnd, "Cubemaps");
+ if (!cubemapArrayJson)
+ {
+ ri.Printf(PRINT_ALL, "Bad %s: no Cubemaps\n", filename);
+ ri.FS_FreeFile(buffer.v);
+ return;
+ }
+
+ if (JSON_ValueGetType(cubemapArrayJson, bufferEnd) != JSONTYPE_ARRAY)
+ {
+ ri.Printf(PRINT_ALL, "Bad %s: Cubemaps not an array\n", filename);
+ ri.FS_FreeFile(buffer.v);
+ return;
+ }
+
+ tr.numCubemaps = JSON_ArrayGetIndex(cubemapArrayJson, bufferEnd, NULL, 0);
+ tr.cubemaps = (cubemap_t*)ri.Hunk_Alloc(tr.numCubemaps * sizeof(*tr.cubemaps), h_low);
+ memset(tr.cubemaps, 0, tr.numCubemaps * sizeof(*tr.cubemaps));
+
+ for (i = 0; i < tr.numCubemaps; i++)
+ {
+ cubemap_t *cubemap = &tr.cubemaps[i];
+ const char *cubemapJson, *keyValueJson, *indexes[3];
+ int j;
+
+ cubemapJson = JSON_ArrayGetValue(cubemapArrayJson, bufferEnd, i);
+
+ keyValueJson = JSON_ObjectGetNamedValue(cubemapJson, bufferEnd, "Name");
+ if (!JSON_ValueGetString(keyValueJson, bufferEnd, cubemap->name, MAX_QPATH))
+ cubemap->name[0] = '\0';
+
+ keyValueJson = JSON_ObjectGetNamedValue(cubemapJson, bufferEnd, "Position");
+ JSON_ArrayGetIndex(keyValueJson, bufferEnd, indexes, 3);
+ for (j = 0; j < 3; j++)
+ cubemap->origin[j] = JSON_ValueGetFloat(indexes[j], bufferEnd);
+
+ cubemap->parallaxRadius = 1000.0f;
+ keyValueJson = JSON_ObjectGetNamedValue(cubemapJson, bufferEnd, "Radius");
+ if (keyValueJson)
+ cubemap->parallaxRadius = JSON_ValueGetFloat(keyValueJson, bufferEnd);
+ }
+
+ ri.FS_FreeFile(buffer.v);
+}
+
+void R_LoadCubemapEntities(const char *cubemapEntityName)
+{
+ char spawnVarChars[2048];
+ int numSpawnVars;
+ char *spawnVars[MAX_SPAWN_VARS][2];
+ int numCubemaps = 0;
+
+ // count cubemaps
+ numCubemaps = 0;
+ while(R_ParseSpawnVars(spawnVarChars, sizeof(spawnVarChars), &numSpawnVars, spawnVars))
+ {
+ int i;
+
+ for (i = 0; i < numSpawnVars; i++)
+ {
+ if (!Q_stricmp(spawnVars[i][0], "classname") && !Q_stricmp(spawnVars[i][1], cubemapEntityName))
+ numCubemaps++;
+ }
+ }
+
+ if (!numCubemaps)
+ return;
+
+ tr.numCubemaps = numCubemaps;
+ tr.cubemaps = (cubemap_t*)ri.Hunk_Alloc(tr.numCubemaps * sizeof(*tr.cubemaps), h_low);
+ memset(tr.cubemaps, 0, tr.numCubemaps * sizeof(*tr.cubemaps));
+
+ numCubemaps = 0;
+ while(R_ParseSpawnVars(spawnVarChars, sizeof(spawnVarChars), &numSpawnVars, spawnVars))
+ {
+ int i;
+ char name[MAX_QPATH];
+ bool isCubemap = false;
+ bool originSet = false;
+ vec3_t origin;
+ float parallaxRadius = 1000.0f;
+
+ name[0] = '\0';
+ for (i = 0; i < numSpawnVars; i++)
+ {
+ if (!Q_stricmp(spawnVars[i][0], "classname") && !Q_stricmp(spawnVars[i][1], cubemapEntityName))
+ isCubemap = true;
+
+ if (!Q_stricmp(spawnVars[i][0], "name"))
+ Q_strncpyz(name, spawnVars[i][1], MAX_QPATH);
+
+ if (!Q_stricmp(spawnVars[i][0], "origin"))
+ {
+ sscanf(spawnVars[i][1], "%f %f %f", &origin[0], &origin[1], &origin[2]);
+ originSet = true;
+ }
+ else if (!Q_stricmp(spawnVars[i][0], "radius"))
+ {
+ sscanf(spawnVars[i][1], "%f", &parallaxRadius);
+ }
+ }
+
+ if (isCubemap && originSet)
+ {
+ cubemap_t *cubemap = &tr.cubemaps[numCubemaps];
+ Q_strncpyz(cubemap->name, name, MAX_QPATH);
+ VectorCopy(origin, cubemap->origin);
+ cubemap->parallaxRadius = parallaxRadius;
+ numCubemaps++;
+ }
+ }
+}
+
+void R_AssignCubemapsToWorldSurfaces(void)
+{
+ world_t *w;
+ int i;
+
+ w = &s_worldData;
+
+ for (i = 0; i < w->numsurfaces; i++)
+ {
+ msurface_t *surf = &w->surfaces[i];
+ vec3_t surfOrigin;
+
+ if (surf->cullinfo.type & CULLINFO_SPHERE)
+ {
+ VectorCopy(surf->cullinfo.localOrigin, surfOrigin);
+ }
+ else if (surf->cullinfo.type & CULLINFO_BOX)
+ {
+ surfOrigin[0] = (surf->cullinfo.bounds[0][0] + surf->cullinfo.bounds[1][0]) * 0.5f;
+ surfOrigin[1] = (surf->cullinfo.bounds[0][1] + surf->cullinfo.bounds[1][1]) * 0.5f;
+ surfOrigin[2] = (surf->cullinfo.bounds[0][2] + surf->cullinfo.bounds[1][2]) * 0.5f;
+ }
+ else
+ {
+ //ri.Printf(PRINT_ALL, "surface %d has no cubemap\n", i);
+ continue;
+ }
+
+ surf->cubemapIndex = R_CubemapForPoint(surfOrigin);
+ //ri.Printf(PRINT_ALL, "surface %d has cubemap %d\n", i, surf->cubemapIndex);
+ }
+}
+
+
+void R_LoadCubemaps(void)
+{
+ int i;
+ int/*imgFlags_t*/ flags = IMGFLAG_CLAMPTOEDGE | IMGFLAG_MIPMAP | IMGFLAG_NOLIGHTSCALE | IMGFLAG_CUBEMAP;
+
+ for (i = 0; i < tr.numCubemaps; i++)
+ {
+ char filename[MAX_QPATH];
+ cubemap_t *cubemap = &tr.cubemaps[i];
+
+ Com_sprintf(filename, MAX_QPATH, "cubemaps/%s/%03d.dds", tr.world->baseName, i);
+
+ cubemap->image = R_FindImageFile(filename, IMGTYPE_COLORALPHA, flags);
+ }
+}
+
+
+void R_RenderMissingCubemaps(void)
+{
+ int i, j;
+ int/*imgFlags_t*/ flags = IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE | IMGFLAG_MIPMAP | IMGFLAG_NOLIGHTSCALE | IMGFLAG_CUBEMAP;
+
+ for (i = 0; i < tr.numCubemaps; i++)
+ {
+ if (!tr.cubemaps[i].image)
+ {
+ tr.cubemaps[i].image = R_CreateImage(va("*cubeMap%d", i), NULL,
+ r_cubemapSize->integer, r_cubemapSize->integer,
+ IMGTYPE_COLORALPHA, flags, GL_RGBA8);
+
+ for (j = 0; j < 6; j++)
+ {
+ RE_ClearScene();
+ R_RenderCubemapSide(i, j, false);
+ R_IssuePendingRenderCommands();
+ R_InitNextFrame();
+ }
+ }
+ }
+}
+
+
+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++)
+ {
+ srfBspSurface_t *bspSurf = (srfBspSurface_t *) surface->data;
+
+ switch(bspSurf->surfaceType)
+ {
+ case SF_FACE:
+ case SF_GRID:
+ case SF_TRIANGLES:
+ for(i = 0; i < bspSurf->numVerts; i++)
+ {
+ vec3_t lightDir;
+ vec3_t normal;
+
+ R_VaoUnpackNormal(normal, bspSurf->verts[i].normal);
+ R_LightDirForPoint( bspSurf->verts[i].xyz, lightDir, normal, &s_worldData );
+ R_VaoPackNormal(bspSurf->verts[i].lightdir, lightDir);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+
+/*
+=================
+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" );
+ }
+
+ // set default map light scale
+ tr.sunShadowScale = 0.5f;
+
+ // 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] = -8.0f;
+ tr.toneMinAvgMaxLevel[1] = -2.0f;
+ tr.toneMinAvgMaxLevel[2] = 0.0f;
+
+ // reset last cascade sun direction so last shadow cascade is rerendered
+ VectorClear(tr.lastCascadeSunDirection);
+
+ tr.worldMapLoaded = true;
+
+ // 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 = (byte*)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 ; i<sizeof(dheader_t)/4 ; i++) {
+ ((int *)header)[i] = LittleLong ( ((int *)header)[i]);
+ }
+
+ // load into heap
+ R_LoadEntities( &header->lumps[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();
+
+ // determine which parts of the map are in sunlight
+ if (0)
+ {
+ world_t *w;
+ uint8_t *primaryLightGrid, *data;
+ int lightGridSize;
+ int i;
+
+ w = &s_worldData;
+
+ lightGridSize = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2];
+ primaryLightGrid = (uint8_t*)ri.Malloc(lightGridSize * sizeof(*primaryLightGrid));
+
+ memset(primaryLightGrid, 0, lightGridSize * sizeof(*primaryLightGrid));
+
+ data = w->lightGridData;
+ for (i = 0; i < lightGridSize; i++, data += 8)
+ {
+ int lat, lng;
+ vec3_t gridLightDir, gridLightCol;
+
+ // skip samples in wall
+ if (!(data[0]+data[1]+data[2]+data[3]+data[4]+data[5]) )
+ continue;
+
+ gridLightCol[0] = ByteToFloat(data[3]);
+ gridLightCol[1] = ByteToFloat(data[4]);
+ gridLightCol[2] = ByteToFloat(data[5]);
+ (void)gridLightCol; // Suppress unused-but-set-variable warning
+
+ 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 )
+
+ gridLightDir[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+ gridLightDir[1] = tr.sinTable[lat] * tr.sinTable[lng];
+ gridLightDir[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+
+ // FIXME: magic number for determining if light direction is close enough to sunlight
+ if (DotProduct(gridLightDir, tr.sunDirection) > 0.75f)
+ {
+ primaryLightGrid[i] = 1;
+ }
+ else
+ {
+ primaryLightGrid[i] = 255;
+ }
+ }
+
+ if (0)
+ {
+ int i;
+ byte *buffer = (byte*)ri.Malloc(w->lightGridBounds[0] * w->lightGridBounds[1] * 3 + 18);
+ byte *out;
+ uint8_t *in;
+ char fileName[MAX_QPATH];
+
+ Com_Memset (buffer, 0, 18);
+ buffer[2] = 2; // uncompressed type
+ buffer[12] = w->lightGridBounds[0] & 255;
+ buffer[13] = w->lightGridBounds[0] >> 8;
+ buffer[14] = w->lightGridBounds[1] & 255;
+ buffer[15] = w->lightGridBounds[1] >> 8;
+ buffer[16] = 24; // pixel size
+
+ in = primaryLightGrid;
+ for (i = 0; i < w->lightGridBounds[2]; i++)
+ {
+ int j;
+
+ sprintf(fileName, "primarylg%d.tga", i);
+
+ out = buffer + 18;
+ for (j = 0; j < w->lightGridBounds[0] * w->lightGridBounds[1]; j++)
+ {
+ if (*in == 1)
+ {
+ *out++ = 255;
+ *out++ = 255;
+ *out++ = 255;
+ }
+ else if (*in == 255)
+ {
+ *out++ = 64;
+ *out++ = 64;
+ *out++ = 64;
+ }
+ else
+ {
+ *out++ = 0;
+ *out++ = 0;
+ *out++ = 0;
+ }
+ in++;
+ }
+
+ ri.FS_WriteFile(fileName, buffer, w->lightGridBounds[0] * w->lightGridBounds[1] * 3 + 18);
+ }
+
+ ri.Free(buffer);
+ }
+
+ for (i = 0; i < w->numWorldSurfaces; i++)
+ {
+ msurface_t *surf = w->surfaces + i;
+ cullinfo_t *ci = &surf->cullinfo;
+
+ if(ci->type & CULLINFO_PLANE)
+ {
+ if (DotProduct(ci->plane.normal, tr.sunDirection) <= 0.0f)
+ {
+ //ri.Printf(PRINT_ALL, "surface %d is not oriented towards sunlight\n", i);
+ continue;
+ }
+ }
+
+ if(ci->type & CULLINFO_BOX)
+ {
+ int ibounds[2][3], x, y, z, goodSamples, numSamples;
+ vec3_t lightOrigin;
+
+ VectorSubtract( ci->bounds[0], w->lightGridOrigin, lightOrigin );
+
+ ibounds[0][0] = floor(lightOrigin[0] * w->lightGridInverseSize[0]);
+ ibounds[0][1] = floor(lightOrigin[1] * w->lightGridInverseSize[1]);
+ ibounds[0][2] = floor(lightOrigin[2] * w->lightGridInverseSize[2]);
+
+ VectorSubtract( ci->bounds[1], w->lightGridOrigin, lightOrigin );
+
+ ibounds[1][0] = ceil(lightOrigin[0] * w->lightGridInverseSize[0]);
+ ibounds[1][1] = ceil(lightOrigin[1] * w->lightGridInverseSize[1]);
+ ibounds[1][2] = ceil(lightOrigin[2] * w->lightGridInverseSize[2]);
+
+ ibounds[0][0] = CLAMP(ibounds[0][0], 0, w->lightGridSize[0]);
+ ibounds[0][1] = CLAMP(ibounds[0][1], 0, w->lightGridSize[1]);
+ ibounds[0][2] = CLAMP(ibounds[0][2], 0, w->lightGridSize[2]);
+
+ ibounds[1][0] = CLAMP(ibounds[1][0], 0, w->lightGridSize[0]);
+ ibounds[1][1] = CLAMP(ibounds[1][1], 0, w->lightGridSize[1]);
+ ibounds[1][2] = CLAMP(ibounds[1][2], 0, w->lightGridSize[2]);
+
+ /*
+ ri.Printf(PRINT_ALL, "surf %d bounds (%f %f %f)-(%f %f %f) ibounds (%d %d %d)-(%d %d %d)\n", i,
+ ci->bounds[0][0], ci->bounds[0][1], ci->bounds[0][2],
+ ci->bounds[1][0], ci->bounds[1][1], ci->bounds[1][2],
+ ibounds[0][0], ibounds[0][1], ibounds[0][2],
+ ibounds[1][0], ibounds[1][1], ibounds[1][2]);
+ */
+
+ goodSamples = 0;
+ numSamples = 0;
+ for (x = ibounds[0][0]; x <= ibounds[1][0]; x++)
+ {
+ for (y = ibounds[0][1]; y <= ibounds[1][1]; y++)
+ {
+ for (z = ibounds[0][2]; z <= ibounds[1][2]; z++)
+ {
+ uint8_t primaryLight = primaryLightGrid[x * 8 + y * 8 * w->lightGridBounds[0] + z * 8 * w->lightGridBounds[0] * w->lightGridBounds[2]];
+
+ if (primaryLight == 0)
+ continue;
+
+ numSamples++;
+
+ if (primaryLight == 1)
+ goodSamples++;
+ }
+ }
+ }
+
+ // FIXME: magic number for determining whether object is mostly in sunlight
+ if (goodSamples > numSamples * 0.75f)
+ {
+ //ri.Printf(PRINT_ALL, "surface %d is in sunlight\n", i);
+ //surf->primaryLight = 1;
+ }
+ }
+ }
+
+ ri.Free(primaryLightGrid);
+ }
+
+ // load cubemaps
+ if (r_cubeMapping->integer)
+ {
+ // Try loading an env.json file first
+ R_LoadEnvironmentJson(s_worldData.baseName);
+
+ if (!tr.numCubemaps)
+ {
+ R_LoadCubemapEntities("misc_cubemap");
+ }
+
+ if (!tr.numCubemaps)
+ {
+ // location names are an assured way to get an even distribution
+ R_LoadCubemapEntities("target_location");
+ }
+
+ if (!tr.numCubemaps)
+ {
+ // try misc_models
+ R_LoadCubemapEntities("misc_model");
+ }
+
+ if (tr.numCubemaps)
+ {
+ R_AssignCubemapsToWorldSurfaces();
+ }
+ }
+
+ 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 VAO glState entry is safe
+ R_BindNullVao();
+
+ // Render or load all cubemaps
+ if (r_cubeMapping->integer && tr.numCubemaps && glRefConfig.framebufferObject)
+ {
+ R_LoadCubemaps();
+ R_RenderMissingCubemaps();
+ }
+
+ ri.FS_FreeFile( buffer.v );
+}
diff --git a/src/renderergl2/tr_cmds.cpp b/src/renderergl2/tr_cmds.cpp
new file mode 100644
index 0000000..d7eecd2
--- /dev/null
+++ b/src/renderergl2/tr_cmds.cpp
@@ -0,0 +1,672 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+#include "tr_local.h"
+
+/*
+=====================
+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, "VAO draws: static %i dynamic %i\n",
+ backEnd.pc.c_staticVaoDraws, backEnd.pc.c_dynamicVaoDraws);
+ 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_IssueRenderCommands
+====================
+*/
+void R_IssueRenderCommands( bool runPerformanceCounters ) {
+ renderCommandList_t *cmdList;
+
+ cmdList = &backEndData->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 ( runPerformanceCounters ) {
+ R_PerformanceCounters();
+ }
+
+ // actually start the commands going
+ if ( !r_skipBackEnd->integer ) {
+ // let it start on the new batch
+ RB_ExecuteRenderCommands( cmdList->cmds );
+ }
+}
+
+
+/*
+====================
+R_IssuePendingRenderCommands
+
+Issue any pending commands and wait for them to complete.
+====================
+*/
+void R_IssuePendingRenderCommands( void ) {
+ if ( !tr.registered ) {
+ return;
+ }
+ R_IssueRenderCommands( false );
+}
+
+/*
+============
+R_GetCommandBufferReserved
+
+make sure there is enough command space
+============
+*/
+void *R_GetCommandBufferReserved( int bytes, int reservedBytes ) {
+ renderCommandList_t *cmdList;
+
+ cmdList = &backEndData->commands;
+ bytes = PAD(bytes, sizeof(void *));
+
+ // always leave room for the end of list command
+ if ( cmdList->used + bytes + sizeof( int ) + reservedBytes > MAX_RENDER_COMMANDS ) {
+ if ( bytes > MAX_RENDER_COMMANDS - sizeof( int ) ) {
+ 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_GetCommandBuffer
+
+returns NULL if there is not enough space for important commands
+=============
+*/
+void *R_GetCommandBuffer( int bytes ) {
+ return R_GetCommandBufferReserved( bytes, PAD( sizeof( swapBuffersCommand_t ), sizeof(void *) ) );
+}
+
+
+/*
+=============
+R_AddDrawSurfCmd
+
+=============
+*/
+void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ) {
+ drawSurfsCommand_t *cmd;
+
+ cmd = (drawSurfsCommand_t*)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 = (capShadowmapCommand_t*)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 = (postProcessCommand_t*)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 = (setColorCommand_t*)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];
+}
+
+/*
+=============
+R_ClipRegion
+=============
+*/
+static bool R_ClipRegion ( float *x, float *y, float *w, float *h,
+ float *s1, float *t1, float *s2, float *t2 ) {
+ float left, top, right, bottom;
+ float _s1, _t1, _s2, _t2;
+ float clipLeft, clipTop, clipRight, clipBottom;
+
+ if (tr.clipRegion[2] <= tr.clipRegion[0] ||
+ tr.clipRegion[3] <= tr.clipRegion[1] ) {
+ return false;
+ }
+
+ left = *x;
+ top = *y;
+ right = *x + *w;
+ bottom = *y + *h;
+
+ _s1 = *s1;
+ _t1 = *t1;
+ _s2 = *s2;
+ _t2 = *t2;
+
+ clipLeft = tr.clipRegion[0];
+ clipTop = tr.clipRegion[1];
+ clipRight = tr.clipRegion[2];
+ clipBottom = tr.clipRegion[3];
+
+ // Completely clipped away
+ if ( right <= clipLeft || left >= clipRight ||
+ bottom <= clipTop || top >= clipBottom ) {
+ return true;
+ }
+
+ // Clip left edge
+ if ( left < clipLeft ) {
+ float f = ( clipLeft - left ) / ( right - left );
+ *s1 = ( f * ( _s2 - _s1 ) ) + _s1;
+ *x = clipLeft;
+ *w -= ( clipLeft - left );
+ }
+
+ // Clip right edge
+ if ( right > clipRight ) {
+ float f = ( clipRight - right ) / ( left - right );
+ *s2 = ( f * ( _s1 - _s2 ) ) + _s2;
+ *w = clipRight - *x;
+ }
+
+ // Clip top edge
+ if ( top < clipTop ) {
+ float f = ( clipTop - top ) / ( bottom - top );
+ *t1 = ( f * ( _t2 - _t1 ) ) + _t1;
+ *y = clipTop;
+ *h -= ( clipTop - top );
+ }
+
+ // Clip bottom edge
+ if ( bottom > clipBottom ) {
+ float f = ( clipBottom - bottom ) / ( top - bottom );
+ *t2 = ( f * ( _t1 - _t2 ) ) + _t2;
+ *h = clipBottom - *y;
+ }
+
+ return false;
+}
+
+/*
+=============
+RE_SetClipRegion
+=============
+*/
+void RE_SetClipRegion( const float *region ) {
+ if ( region == NULL ) {
+ Com_Memset( tr.clipRegion, 0, sizeof( vec4_t ) );
+ } else {
+ Vector4Copy( region, tr.clipRegion );
+ }
+}
+
+/*
+=============
+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;
+ }
+ if (R_ClipRegion(&x, &y, &w, &h, &s1, &t1, &s2, &t2)) {
+ return;
+ }
+ cmd = (stretchPicCommand_t*)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 = false;
+
+ 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 = false;
+ }
+ 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 = false;
+ }
+ else
+ {
+ R_IssuePendingRenderCommands();
+ qglEnable( GL_STENCIL_TEST );
+ qglStencilMask( ~0U );
+ qglClearStencil( 0U );
+ qglStencilFunc( GL_ALWAYS, 0U, ~0U );
+ qglStencilOp( GL_KEEP, GL_INCR, GL_INCR );
+ }
+ r_measureOverdraw->modified = false;
+ }
+ else
+ {
+ // this is only reached if it was on and is now off
+ if ( r_measureOverdraw->modified ) {
+ R_IssuePendingRenderCommands();
+ qglDisable( GL_STENCIL_TEST );
+ }
+ r_measureOverdraw->modified = false;
+ }
+
+ //
+ // texturemode stuff
+ //
+ if ( r_textureMode->modified ) {
+ R_IssuePendingRenderCommands();
+ GL_TextureMode( r_textureMode->string );
+ r_textureMode->modified = false;
+ }
+
+ //
+ // gamma stuff
+ //
+ if ( r_gamma->modified ) {
+ r_gamma->modified = false;
+
+ R_IssuePendingRenderCommands();
+ R_SetColorMappings();
+ }
+
+ // check for errors
+ if ( !r_ignoreGLErrors->integer )
+ {
+ int err;
+
+ R_IssuePendingRenderCommands();
+ if ((err = qglGetError()) != GL_NO_ERROR)
+ ri.Error(ERR_FATAL, "RE_BeginFrame() - glGetError() failed (0x%x)!", err);
+ }
+
+ if (glConfig.stereoEnabled) {
+ if( !(cmd = (drawBufferCommand_t*)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;
+
+ if (glRefConfig.framebufferObject)
+ {
+ // clear all framebuffers
+ if (tr.msaaResolveFbo)
+ {
+ FBO_Bind(tr.msaaResolveFbo);
+ qglClear(GL_COLOR_BUFFER_BIT);
+ }
+
+ if (tr.renderFbo)
+ {
+ FBO_Bind(tr.renderFbo);
+ qglClear(GL_COLOR_BUFFER_BIT);
+ }
+
+ FBO_Bind(NULL);
+ }
+
+ qglDrawBuffer(GL_FRONT);
+ qglClear(GL_COLOR_BUFFER_BIT);
+ qglDrawBuffer(GL_BACK);
+ qglClear(GL_COLOR_BUFFER_BIT);
+
+ r_anaglyphMode->modified = false;
+ }
+
+ if(stereoFrame == STEREO_LEFT)
+ {
+ if( !(cmd = (drawBufferCommand_t*)R_GetCommandBuffer(sizeof(*cmd))) )
+ return;
+
+ if( !(colcmd = (colorMaskCommand_t*)R_GetCommandBuffer(sizeof(*colcmd))) )
+ return;
+ }
+ else if(stereoFrame == STEREO_RIGHT)
+ {
+ clearDepthCommand_t *cldcmd;
+
+ if( !(cldcmd = (clearDepthCommand_t*)R_GetCommandBuffer(sizeof(*cldcmd))) )
+ return;
+
+ cldcmd->commandId = RC_CLEARDEPTH;
+
+ if( !(colcmd = (colorMaskCommand_t*)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 = (drawBufferCommand_t*)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 = false;
+ }
+
+ 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 = (swapBuffersCommand_t*)R_GetCommandBufferReserved( sizeof( *cmd ), 0 );
+ if ( !cmd ) {
+ return;
+ }
+ cmd->commandId = RC_SWAP_BUFFERS;
+
+ R_IssueRenderCommands( true );
+
+ R_InitNextFrame();
+
+ 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, bool motionJpeg )
+{
+ videoFrameCommand_t *cmd;
+
+ if( !tr.registered ) {
+ return;
+ }
+
+ cmd = (videoFrameCommand_t*)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/renderergl2/tr_curve.cpp b/src/renderergl2/tr_curve.cpp
new file mode 100644
index 0000000..20aef7f
--- /dev/null
+++ b/src/renderergl2/tr_curve.cpp
@@ -0,0 +1,741 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#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 srfBspSurface_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:
+
+srfBspSurface_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->color[0] = ((int)a->color[0] + (int)b->color[0]) >> 1;
+ out->color[1] = ((int)a->color[1] + (int)b->color[1]) >> 1;
+ out->color[2] = ((int)a->color[2] + (int)b->color[2]) >> 1;
+ out->color[3] = ((int)a->color[3] + (int)b->color[3]) >> 1;
+}
+
+/*
+============
+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;
+ bool good[8];
+ bool 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 = false;
+ 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 = true;
+ }
+
+ wrapHeight = false;
+ 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 = true;
+ }
+
+
+ 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] = false;
+
+ 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] = true;
+ 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");
+ //}
+ {
+ vec3_t fNormal;
+ VectorNormalize2(sum, fNormal);
+ R_VaoPackNormal(dv->normal, fNormal);
+ }
+ }
+ }
+}
+
+static void MakeMeshTangentVectors(int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], int numIndexes,
+ glIndex_t indexes[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2*3])
+{
+ int i, j;
+ srfVert_t *dv[3];
+ static srfVert_t ctrl2[MAX_GRID_SIZE * MAX_GRID_SIZE];
+ glIndex_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 = indexes; i < numIndexes; i += 3, tri += 3)
+ {
+ dv[0] = &ctrl2[tri[0]];
+ dv[1] = &ctrl2[tri[1]];
+ dv[2] = &ctrl2[tri[2]];
+
+ R_CalcTangentVectors(dv);
+ }
+
+ for(i = 0; i < width; i++)
+ {
+ for(j = 0; j < height; j++)
+ {
+ dv[0] = &ctrl2[j * width + i];
+ dv[1] = &ctrl[j][i];
+
+ VectorCopy4(dv[0]->tangent, dv[1]->tangent);
+ }
+ }
+}
+
+
+static int MakeMeshIndexes(int width, int height, glIndex_t indexes[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2*3])
+{
+ int i, j;
+ int numIndexes;
+ int w, h;
+
+ h = height - 1;
+ w = width - 1;
+ numIndexes = 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;
+
+ indexes[numIndexes++] = v2;
+ indexes[numIndexes++] = v3;
+ indexes[numIndexes++] = v1;
+
+ indexes[numIndexes++] = v1;
+ indexes[numIndexes++] = v3;
+ indexes[numIndexes++] = v4;
+ }
+ }
+
+ return numIndexes;
+}
+
+
+/*
+============
+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
+=================
+*/
+void R_CreateSurfaceGridMesh(srfBspSurface_t *grid, int width, int height,
+ srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], float errorTable[2][MAX_GRID_SIZE],
+ int numIndexes, glIndex_t indexes[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2*3]) {
+ int i, j;
+ srfVert_t *vert;
+ vec3_t tmpVec;
+
+ // copy the results out to a grid
+ Com_Memset(grid, 0, sizeof(*grid));
+
+#ifdef PATCH_STITCHING
+ grid->widthLodError = (float*)/*ri.Hunk_Alloc*/ ri.Malloc( width * 4 );
+ Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 );
+
+ grid->heightLodError = (float*)/*ri.Hunk_Alloc*/ ri.Malloc( height * 4 );
+ Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 );
+
+ grid->numIndexes = numIndexes;
+ grid->indexes = (glIndex_t*)ri.Malloc(grid->numIndexes * sizeof(glIndex_t));
+ Com_Memcpy(grid->indexes, indexes, numIndexes * sizeof(glIndex_t));
+
+ grid->numVerts = (width * height);
+ grid->verts = (srfVert_t*)ri.Malloc(grid->numVerts * sizeof(srfVert_t));
+#else
+ 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->numIndexes = numIndexes;
+ grid->indexes = ri.Hunk_Alloc(grid->numIndexes * sizeof(glIndex_t), h_low);
+ Com_Memcpy(grid->indexes, indexes, numIndexes * sizeof(glIndex_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->cullBounds[0], grid->cullBounds[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->cullBounds[0], grid->cullBounds[1] );
+ }
+ }
+
+ // compute local origin and bounds
+ VectorAdd( grid->cullBounds[0], grid->cullBounds[1], grid->cullOrigin );
+ VectorScale( grid->cullOrigin, 0.5f, grid->cullOrigin );
+ VectorSubtract( grid->cullBounds[0], grid->cullOrigin, tmpVec );
+ grid->cullRadius = VectorLength( tmpVec );
+
+ VectorCopy( grid->cullOrigin, grid->lodOrigin );
+ grid->lodRadius = grid->cullRadius;
+ //
+}
+
+/*
+=================
+R_FreeSurfaceGridMesh
+=================
+*/
+static void R_FreeSurfaceGridMeshData( srfBspSurface_t *grid ) {
+ ri.Free(grid->widthLodError);
+ ri.Free(grid->heightLodError);
+ ri.Free(grid->indexes);
+ ri.Free(grid->verts);
+}
+
+/*
+=================
+R_SubdividePatchToGrid
+=================
+*/
+void R_SubdividePatchToGrid( srfBspSurface_t *grid, 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 numIndexes;
+ static glIndex_t indexes[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2*3];
+ 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 indexes
+ numIndexes = MakeMeshIndexes(width, height, indexes);
+
+ // calculate normals
+ MakeMeshNormals( width, height, ctrl );
+ MakeMeshTangentVectors(width, height, ctrl, numIndexes, indexes);
+
+ R_CreateSurfaceGridMesh(grid, width, height, ctrl, errorTable, numIndexes, indexes);
+}
+
+/*
+===============
+R_GridInsertColumn
+===============
+*/
+void R_GridInsertColumn( srfBspSurface_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 numIndexes;
+ static glIndex_t indexes[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2*3];
+
+ oldwidth = 0;
+ width = grid->width + 1;
+ if (width > MAX_GRID_SIZE)
+ return;
+ 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 indexes
+ numIndexes = MakeMeshIndexes(width, height, indexes);
+
+ // calculate normals
+ MakeMeshNormals( width, height, ctrl );
+ MakeMeshTangentVectors(width, height, ctrl, numIndexes, indexes);
+
+ VectorCopy(grid->lodOrigin, lodOrigin);
+ lodRadius = grid->lodRadius;
+ // free the old grid
+ R_FreeSurfaceGridMeshData(grid);
+ // create a new grid
+ R_CreateSurfaceGridMesh(grid, width, height, ctrl, errorTable, numIndexes, indexes);
+ grid->lodRadius = lodRadius;
+ VectorCopy(lodOrigin, grid->lodOrigin);
+}
+
+/*
+===============
+R_GridInsertRow
+===============
+*/
+void R_GridInsertRow( srfBspSurface_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 numIndexes;
+ static glIndex_t indexes[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2*3];
+
+ oldheight = 0;
+ width = grid->width;
+ height = grid->height + 1;
+ if (height > MAX_GRID_SIZE)
+ return;
+ 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 indexes
+ numIndexes = MakeMeshIndexes(width, height, indexes);
+
+ // calculate normals
+ MakeMeshNormals( width, height, ctrl );
+ MakeMeshTangentVectors(width, height, ctrl, numIndexes, indexes);
+
+ VectorCopy(grid->lodOrigin, lodOrigin);
+ lodRadius = grid->lodRadius;
+ // free the old grid
+ R_FreeSurfaceGridMeshData(grid);
+ // create a new grid
+ R_CreateSurfaceGridMesh(grid, width, height, ctrl, errorTable, numIndexes, indexes);
+ grid->lodRadius = lodRadius;
+ VectorCopy(lodOrigin, grid->lodOrigin);
+}
diff --git a/src/renderergl2/tr_dsa.cpp b/src/renderergl2/tr_dsa.cpp
new file mode 100644
index 0000000..8ea42bc
--- /dev/null
+++ b/src/renderergl2/tr_dsa.cpp
@@ -0,0 +1,287 @@
+/*
+===========================================================================
+Copyright (C) 2016 James Canete
+Copyright (C) 2015-2019 GrangerHub
+
+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 3
+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, see <http://www.gnu.org/licenses/>.
+===========================================================================
+*/
+
+#include "tr_local.h"
+
+#include "tr_dsa.h"
+
+static struct
+{
+ GLuint textures[NUM_TEXTURE_BUNDLES];
+ GLenum texunit;
+
+ GLuint program;
+
+ GLuint drawFramebuffer;
+ GLuint readFramebuffer;
+ GLuint renderbuffer;
+}
+glDsaState;
+
+void GL_BindNullTextures()
+{
+ int i;
+
+ if (glRefConfig.directStateAccess)
+ {
+ for (i = 0; i < NUM_TEXTURE_BUNDLES; i++)
+ {
+ qglBindMultiTextureEXT(GL_TEXTURE0 + i, GL_TEXTURE_2D, 0);
+ glDsaState.textures[i] = 0;
+ }
+ }
+ else
+ {
+ for (i = 0; i < NUM_TEXTURE_BUNDLES; i++)
+ {
+ qglActiveTexture(GL_TEXTURE0 + i);
+ qglBindTexture(GL_TEXTURE_2D, 0);
+ glDsaState.textures[i] = 0;
+ }
+
+ qglActiveTexture(GL_TEXTURE0);
+ glDsaState.texunit = GL_TEXTURE0;
+ }
+}
+
+int GL_BindMultiTexture(GLenum texunit, GLenum target, GLuint texture)
+{
+ GLuint tmu = texunit - GL_TEXTURE0;
+
+ if (glDsaState.textures[tmu] == texture)
+ return 0;
+
+ if (target >= GL_TEXTURE_CUBE_MAP_POSITIVE_X && target <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z)
+ target = GL_TEXTURE_CUBE_MAP;
+
+ qglBindMultiTextureEXT(texunit, target, texture);
+ glDsaState.textures[tmu] = texture;
+ return 1;
+}
+
+GLvoid APIENTRY GLDSA_BindMultiTextureEXT(GLenum texunit, GLenum target, GLuint texture)
+{
+ if (glDsaState.texunit != texunit)
+ {
+ qglActiveTexture(texunit);
+ glDsaState.texunit = texunit;
+ }
+
+ qglBindTexture(target, texture);
+}
+
+GLvoid APIENTRY GLDSA_TextureParameterfEXT(GLuint texture, GLenum target, GLenum pname, GLfloat param)
+{
+ GL_BindMultiTexture(glDsaState.texunit, target, texture);
+ qglTexParameterf(target, pname, param);
+}
+
+GLvoid APIENTRY GLDSA_TextureParameteriEXT(GLuint texture, GLenum target, GLenum pname, GLint param)
+{
+ GL_BindMultiTexture(glDsaState.texunit, target, texture);
+ qglTexParameteri(target, pname, param);
+}
+
+GLvoid APIENTRY GLDSA_TextureImage2DEXT(GLuint texture, GLenum target, GLint level, GLint internalformat,
+ GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels)
+{
+ GL_BindMultiTexture(glDsaState.texunit, target, texture);
+ qglTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
+}
+
+GLvoid APIENTRY GLDSA_TextureSubImage2DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset,
+ GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels)
+{
+ GL_BindMultiTexture(glDsaState.texunit, target, texture);
+ qglTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
+}
+
+GLvoid APIENTRY GLDSA_CopyTextureSubImage2DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset,
+ GLint x, GLint y, GLsizei width, GLsizei height)
+{
+ GL_BindMultiTexture(glDsaState.texunit, target, texture);
+ qglCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
+}
+
+GLvoid APIENTRY GLDSA_CompressedTextureImage2DEXT(GLuint texture, GLenum target, GLint level, GLenum internalformat,
+ GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data)
+{
+ GL_BindMultiTexture(glDsaState.texunit, target, texture);
+ qglCompressedTexImage2D(target, level, internalformat, width, height, border, imageSize, data);
+}
+
+GLvoid APIENTRY GLDSA_CompressedTextureSubImage2DEXT(GLuint texture, GLenum target, GLint level,
+ GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format,
+ GLsizei imageSize, const GLvoid *data)
+{
+ GL_BindMultiTexture(glDsaState.texunit, target, texture);
+ qglCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, imageSize, data);
+}
+
+GLvoid APIENTRY GLDSA_GenerateTextureMipmapEXT(GLuint texture, GLenum target)
+{
+ GL_BindMultiTexture(glDsaState.texunit, target, texture);
+ qglGenerateMipmap(target);
+}
+
+void GL_BindNullProgram()
+{
+ qglUseProgram(0);
+ glDsaState.program = 0;
+}
+
+int GL_UseProgram(GLuint program)
+{
+ if (glDsaState.program == program)
+ return 0;
+
+ qglUseProgram(program);
+ glDsaState.program = program;
+ return 1;
+}
+
+GLvoid APIENTRY GLDSA_ProgramUniform1iEXT(GLuint program, GLint location, GLint v0)
+{
+ GL_UseProgram(program);
+ qglUniform1i(location, v0);
+}
+
+GLvoid APIENTRY GLDSA_ProgramUniform1fEXT(GLuint program, GLint location, GLfloat v0)
+{
+ GL_UseProgram(program);
+ qglUniform1f(location, v0);
+}
+
+GLvoid APIENTRY GLDSA_ProgramUniform2fEXT(GLuint program, GLint location,
+ GLfloat v0, GLfloat v1)
+{
+ GL_UseProgram(program);
+ qglUniform2f(location, v0, v1);
+}
+
+GLvoid APIENTRY GLDSA_ProgramUniform3fEXT(GLuint program, GLint location,
+ GLfloat v0, GLfloat v1, GLfloat v2)
+{
+ GL_UseProgram(program);
+ qglUniform3f(location, v0, v1, v2);
+}
+
+GLvoid APIENTRY GLDSA_ProgramUniform4fEXT(GLuint program, GLint location,
+ GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3)
+{
+ GL_UseProgram(program);
+ qglUniform4f(location, v0, v1, v2, v3);
+}
+
+GLvoid APIENTRY GLDSA_ProgramUniform1fvEXT(GLuint program, GLint location,
+ GLsizei count, const GLfloat *value)
+{
+ GL_UseProgram(program);
+ qglUniform1fv(location, count, value);
+}
+
+GLvoid APIENTRY GLDSA_ProgramUniformMatrix4fvEXT(GLuint program, GLint location,
+ GLsizei count, GLboolean transpose,
+ const GLfloat *value)
+{
+ GL_UseProgram(program);
+ qglUniformMatrix4fv(location, count, transpose, value);
+}
+
+void GL_BindNullFramebuffers()
+{
+ qglBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glDsaState.drawFramebuffer = glDsaState.readFramebuffer = 0;
+ qglBindRenderbuffer(GL_RENDERBUFFER, 0);
+ glDsaState.renderbuffer = 0;
+}
+
+void GL_BindFramebuffer(GLenum target, GLuint framebuffer)
+{
+ switch (target)
+ {
+ case GL_FRAMEBUFFER:
+ if (framebuffer != glDsaState.drawFramebuffer || framebuffer != glDsaState.readFramebuffer)
+ {
+ qglBindFramebuffer(target, framebuffer);
+ glDsaState.drawFramebuffer = glDsaState.readFramebuffer = framebuffer;
+ }
+ break;
+
+ case GL_DRAW_FRAMEBUFFER:
+ if (framebuffer != glDsaState.drawFramebuffer)
+ {
+ qglBindFramebuffer(target, framebuffer);
+ glDsaState.drawFramebuffer = framebuffer;
+ }
+ break;
+
+ case GL_READ_FRAMEBUFFER:
+ if (framebuffer != glDsaState.readFramebuffer)
+ {
+ qglBindFramebuffer(target, framebuffer);
+ glDsaState.readFramebuffer = framebuffer;
+ }
+ break;
+ }
+}
+
+void GL_BindRenderbuffer(GLuint renderbuffer)
+{
+ if (renderbuffer != glDsaState.renderbuffer)
+ {
+ qglBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
+ glDsaState.renderbuffer = renderbuffer;
+ }
+}
+
+GLvoid APIENTRY GLDSA_NamedRenderbufferStorageEXT(GLuint renderbuffer,
+ GLenum internalformat, GLsizei width, GLsizei height)
+{
+ GL_BindRenderbuffer(renderbuffer);
+ qglRenderbufferStorage(GL_RENDERBUFFER, internalformat, width, height);
+}
+
+GLvoid APIENTRY GLDSA_NamedRenderbufferStorageMultisampleEXT(GLuint renderbuffer,
+ GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height)
+{
+ GL_BindRenderbuffer(renderbuffer);
+ qglRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, internalformat, width, height);
+}
+
+GLenum APIENTRY GLDSA_CheckNamedFramebufferStatusEXT(GLuint framebuffer, GLenum target)
+{
+ GL_BindFramebuffer(target, framebuffer);
+ return qglCheckFramebufferStatus(target);
+}
+
+GLvoid APIENTRY GLDSA_NamedFramebufferTexture2DEXT(GLuint framebuffer,
+ GLenum attachment, GLenum textarget, GLuint texture, GLint level)
+{
+ GL_BindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+ qglFramebufferTexture2D(GL_FRAMEBUFFER, attachment, textarget, texture, level);
+}
+
+GLvoid APIENTRY GLDSA_NamedFramebufferRenderbufferEXT(GLuint framebuffer,
+ GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
+{
+ GL_BindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+ qglFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, renderbuffertarget, renderbuffer);
+}
diff --git a/src/renderergl2/tr_dsa.h b/src/renderergl2/tr_dsa.h
new file mode 100644
index 0000000..92c6563
--- /dev/null
+++ b/src/renderergl2/tr_dsa.h
@@ -0,0 +1,80 @@
+/*
+===========================================================================
+Copyright (C) 2016 James Canete
+Copyright (C) 2015-2019 GrangerHub
+
+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 3
+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, see <http://www.gnu.org/licenses/>.
+===========================================================================
+*/
+
+#ifndef __TR_DSA_H__
+#define __TR_DSA_H__
+
+#include "renderercommon/qgl.h"
+
+void GL_BindNullTextures(void);
+int GL_BindMultiTexture(GLenum texunit, GLenum target, GLuint texture);
+
+GLvoid APIENTRY GLDSA_BindMultiTextureEXT(GLenum texunit, GLenum target, GLuint texture);
+GLvoid APIENTRY GLDSA_TextureParameterfEXT(GLuint texture, GLenum target, GLenum pname, GLfloat param);
+GLvoid APIENTRY GLDSA_TextureParameteriEXT(GLuint texture, GLenum target, GLenum pname, GLint param);
+GLvoid APIENTRY GLDSA_TextureImage2DEXT(GLuint texture, GLenum target, GLint level, GLint internalformat,
+ GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
+GLvoid APIENTRY GLDSA_TextureSubImage2DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset,
+ GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels);
+GLvoid APIENTRY GLDSA_CopyTextureSubImage2DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset,
+ GLint x, GLint y, GLsizei width, GLsizei height);
+GLvoid APIENTRY GLDSA_CompressedTextureImage2DEXT(GLuint texture, GLenum target, GLint level, GLenum internalformat,
+ GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data);
+GLvoid APIENTRY GLDSA_CompressedTextureSubImage2DEXT(GLuint texture, GLenum target, GLint level,
+ GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format,
+ GLsizei imageSize, const GLvoid *data);
+
+GLvoid APIENTRY GLDSA_GenerateTextureMipmapEXT(GLuint texture, GLenum target);
+
+void GL_BindNullProgram(void);
+int GL_UseProgram(GLuint program);
+
+GLvoid APIENTRY GLDSA_ProgramUniform1iEXT(GLuint program, GLint location, GLint v0);
+GLvoid APIENTRY GLDSA_ProgramUniform1fEXT(GLuint program, GLint location, GLfloat v0);
+GLvoid APIENTRY GLDSA_ProgramUniform2fEXT(GLuint program, GLint location,
+ GLfloat v0, GLfloat v1);
+GLvoid APIENTRY GLDSA_ProgramUniform3fEXT(GLuint program, GLint location,
+ GLfloat v0, GLfloat v1, GLfloat v2);
+GLvoid APIENTRY GLDSA_ProgramUniform4fEXT(GLuint program, GLint location,
+ GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
+GLvoid APIENTRY GLDSA_ProgramUniform1fvEXT(GLuint program, GLint location,
+ GLsizei count, const GLfloat *value);
+GLvoid APIENTRY GLDSA_ProgramUniformMatrix4fvEXT(GLuint program, GLint location,
+ GLsizei count, GLboolean transpose,
+ const GLfloat *value);
+
+void GL_BindNullFramebuffers(void);
+void GL_BindFramebuffer(GLenum target, GLuint framebuffer);
+void GL_BindRenderbuffer(GLuint renderbuffer);
+
+GLvoid APIENTRY GLDSA_NamedRenderbufferStorageEXT(GLuint renderbuffer,
+ GLenum internalformat, GLsizei width, GLsizei height);
+
+GLvoid APIENTRY GLDSA_NamedRenderbufferStorageMultisampleEXT(GLuint renderbuffer,
+ GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
+
+GLenum APIENTRY GLDSA_CheckNamedFramebufferStatusEXT(GLuint framebuffer, GLenum target);
+GLvoid APIENTRY GLDSA_NamedFramebufferTexture2DEXT(GLuint framebuffer,
+ GLenum attachment, GLenum textarget, GLuint texture, GLint level);
+GLvoid APIENTRY GLDSA_NamedFramebufferRenderbufferEXT(GLuint framebuffer,
+ GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
+
+
+#endif
diff --git a/src/renderergl2/tr_extensions.cpp b/src/renderergl2/tr_extensions.cpp
new file mode 100644
index 0000000..6f73d29
--- /dev/null
+++ b/src/renderergl2/tr_extensions.cpp
@@ -0,0 +1,279 @@
+/*
+===========================================================================
+Copyright (C) 2011 James Canete (use.less01@gmail.com)
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous Arena source code.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// tr_extensions.c - extensions needed by the renderer not in sdl_glimp.c
+
+#ifdef USE_LOCAL_HEADERS
+#include "SDL.h"
+#else
+#include <SDL.h>
+#endif
+
+#include "tr_local.h"
+#include "tr_dsa.h"
+
+#define GLE(ret, name, ...) name##proc *qgl##name;
+QGL_1_3_PROCS;
+QGL_1_5_PROCS;
+QGL_2_0_PROCS;
+QGL_3_0_PROCS;
+QGL_ARB_framebuffer_object_PROCS;
+QGL_ARB_vertex_array_object_PROCS;
+QGL_EXT_direct_state_access_PROCS;
+#undef GLE
+
+void GLimp_InitExtraExtensions()
+{
+ const char *extension;
+ const char *result[3] = {"...ignoring %s\n", "...using %s\n", "...%s not found\n"};
+
+ // Check OpenGL version
+ sscanf(glConfig.version_string, "%d.%d", &glRefConfig.openglMajorVersion, &glRefConfig.openglMinorVersion);
+ if (glRefConfig.openglMajorVersion < 2) ri.Error(ERR_FATAL, "OpenGL 2.0 required!");
+ ri.Printf(PRINT_ALL, "...using OpenGL %s\n", glConfig.version_string);
+
+ bool q_gl_version_at_least_3_0 = (glRefConfig.openglMajorVersion >= 3);
+ bool q_gl_version_at_least_3_2 = (glRefConfig.openglMajorVersion > 3 ||
+ (glRefConfig.openglMajorVersion == 3 && glRefConfig.openglMinorVersion > 2));
+
+ // Check if we need Intel graphics specific fixes.
+ glRefConfig.intelGraphics = qfalse;
+ if (strstr((char *)qglGetString(GL_RENDERER), "Intel")) glRefConfig.intelGraphics = qtrue;
+
+ // set DSA fallbacks
+#define GLE(ret, name, ...) qgl##name = GLDSA_##name;
+ QGL_EXT_direct_state_access_PROCS;
+#undef GLE
+
+ // GL function loader, based on https://gist.github.com/rygorous/16796a0c876cf8a5f542caddb55bce8a
+#define GLE(ret, name, ...) qgl##name = (name##proc *)SDL_GL_GetProcAddress("gl" #name);
+
+ // OpenGL 1.3, was GL_ARB_texture_compression
+ QGL_1_3_PROCS;
+
+ // OpenGL 1.5, was GL_ARB_vertex_buffer_object and GL_ARB_occlusion_query
+ QGL_1_5_PROCS;
+ glRefConfig.occlusionQuery = qtrue;
+
+ // OpenGL 2.0, was GL_ARB_shading_language_100, GL_ARB_vertex_program, GL_ARB_shader_objects, and
+ // GL_ARB_vertex_shader
+ QGL_2_0_PROCS;
+
+ // OpenGL 3.0 - no matching extension
+ // QGL_*_PROCS becomes several functions, do not remove {}
+ if (q_gl_version_at_least_3_0)
+ {
+ QGL_3_0_PROCS;
+ }
+
+ // OpenGL 3.0 - GL_ARB_framebuffer_object
+ extension = "GL_ARB_framebuffer_object";
+ glRefConfig.framebufferObject = qfalse;
+ glRefConfig.framebufferBlit = qfalse;
+ glRefConfig.framebufferMultisample = qfalse;
+ if (q_gl_version_at_least_3_0 || SDL_GL_ExtensionSupported(extension))
+ {
+ glRefConfig.framebufferObject = !!r_ext_framebuffer_object->integer;
+ glRefConfig.framebufferBlit = qtrue;
+ glRefConfig.framebufferMultisample = qtrue;
+
+ qglGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &glRefConfig.maxRenderbufferSize);
+ qglGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &glRefConfig.maxColorAttachments);
+
+ QGL_ARB_framebuffer_object_PROCS;
+
+ ri.Printf(PRINT_ALL, result[glRefConfig.framebufferObject], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+ // OpenGL 3.0 - GL_ARB_vertex_array_object
+ extension = "GL_ARB_vertex_array_object";
+ glRefConfig.vertexArrayObject = qfalse;
+ if (q_gl_version_at_least_3_0 || SDL_GL_ExtensionSupported(extension))
+ {
+ if (q_gl_version_at_least_3_0)
+ {
+ // force VAO, core context requires it
+ glRefConfig.vertexArrayObject = qtrue;
+ }
+ else
+ {
+ glRefConfig.vertexArrayObject = !!r_arb_vertex_array_object->integer;
+ }
+
+ QGL_ARB_vertex_array_object_PROCS;
+
+ ri.Printf(PRINT_ALL, result[glRefConfig.vertexArrayObject], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+ // OpenGL 3.0 - GL_ARB_texture_float
+ extension = "GL_ARB_texture_float";
+ glRefConfig.textureFloat = qfalse;
+ if (q_gl_version_at_least_3_0 || SDL_GL_ExtensionSupported(extension))
+ {
+ glRefConfig.textureFloat = !!r_ext_texture_float->integer;
+
+ ri.Printf(PRINT_ALL, result[glRefConfig.textureFloat], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+ // OpenGL 3.2 - GL_ARB_depth_clamp
+ extension = "GL_ARB_depth_clamp";
+ glRefConfig.depthClamp = qfalse;
+ if (q_gl_version_at_least_3_2 || SDL_GL_ExtensionSupported(extension))
+ {
+ glRefConfig.depthClamp = qtrue;
+
+ ri.Printf(PRINT_ALL, result[glRefConfig.depthClamp], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+ // OpenGL 3.2 - GL_ARB_seamless_cube_map
+ extension = "GL_ARB_seamless_cube_map";
+ glRefConfig.seamlessCubeMap = qfalse;
+ if (q_gl_version_at_least_3_2 || SDL_GL_ExtensionSupported(extension))
+ {
+ glRefConfig.seamlessCubeMap = !!r_arb_seamless_cube_map->integer;
+
+ ri.Printf(PRINT_ALL, result[glRefConfig.seamlessCubeMap], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+ // Determine GLSL version
+ if (1)
+ {
+ char version[256];
+
+ Q_strncpyz(version, (char *)qglGetString(GL_SHADING_LANGUAGE_VERSION), sizeof(version));
+
+ sscanf(version, "%d.%d", &glRefConfig.glslMajorVersion, &glRefConfig.glslMinorVersion);
+
+ ri.Printf(PRINT_ALL, "...using GLSL version %s\n", version);
+ }
+
+ glRefConfig.memInfo = MI_NONE;
+
+ // GL_NVX_gpu_memory_info
+ extension = "GL_NVX_gpu_memory_info";
+ if (SDL_GL_ExtensionSupported(extension))
+ {
+ glRefConfig.memInfo = MI_NVX;
+
+ ri.Printf(PRINT_ALL, result[1], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+ // GL_ATI_meminfo
+ extension = "GL_ATI_meminfo";
+ if (SDL_GL_ExtensionSupported(extension))
+ {
+ if (glRefConfig.memInfo == MI_NONE)
+ {
+ glRefConfig.memInfo = MI_ATI;
+
+ ri.Printf(PRINT_ALL, result[1], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[0], extension);
+ }
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+ glRefConfig.textureCompression = TCR_NONE;
+
+ // GL_ARB_texture_compression_rgtc
+ extension = "GL_ARB_texture_compression_rgtc";
+ if (SDL_GL_ExtensionSupported(extension))
+ {
+ bool useRgtc = r_ext_compressed_textures->integer >= 1;
+
+ if (useRgtc) glRefConfig.textureCompression |= TCR_RGTC;
+
+ ri.Printf(PRINT_ALL, result[useRgtc], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+ glRefConfig.swizzleNormalmap = r_ext_compressed_textures->integer && !(glRefConfig.textureCompression & TCR_RGTC);
+
+ // GL_ARB_texture_compression_bptc
+ extension = "GL_ARB_texture_compression_bptc";
+ if (SDL_GL_ExtensionSupported(extension))
+ {
+ bool useBptc = r_ext_compressed_textures->integer >= 2;
+
+ if (useBptc) glRefConfig.textureCompression |= TCR_BPTC;
+
+ ri.Printf(PRINT_ALL, result[useBptc], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+ // GL_EXT_direct_state_access
+ extension = "GL_EXT_direct_state_access";
+ glRefConfig.directStateAccess = qfalse;
+ if (SDL_GL_ExtensionSupported(extension))
+ {
+ glRefConfig.directStateAccess = !!r_ext_direct_state_access->integer;
+
+ // QGL_*_PROCS becomes several functions, do not remove {}
+ if (glRefConfig.directStateAccess)
+ {
+ QGL_EXT_direct_state_access_PROCS;
+ }
+
+ ri.Printf(PRINT_ALL, result[glRefConfig.directStateAccess], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+#undef GLE
+}
diff --git a/src/renderergl2/tr_extramath.cpp b/src/renderergl2/tr_extramath.cpp
new file mode 100644
index 0000000..abe75fa
--- /dev/null
+++ b/src/renderergl2/tr_extramath.cpp
@@ -0,0 +1,248 @@
+/*
+===========================================================================
+Copyright (C) 2010 James Canete (use.less01@gmail.com)
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// 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 Mat4Zero( mat4_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 Mat4Identity( mat4_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 Mat4Copy( const mat4_t in, mat4_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 Mat4Multiply( const mat4_t in1, const mat4_t in2, mat4_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 Mat4Transform( const mat4_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];
+}
+
+bool Mat4Compare( const mat4_t a, const mat4_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 Mat4Dump( const mat4_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 Mat4Translation( vec3_t vec, mat4_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 Mat4Ortho( float left, float right, float bottom, float top, float znear, float zfar, mat4_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 Mat4View(vec3_t axes[3], vec3_t origin, mat4_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 Mat4SimpleInverse( const mat4_t in, mat4_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;
+}
+
+void VectorLerp( vec3_t a, vec3_t b, float lerp, vec3_t c)
+{
+ c[0] = a[0] * (1.0f - lerp) + b[0] * lerp;
+ c[1] = a[1] * (1.0f - lerp) + b[1] * lerp;
+ c[2] = a[2] * (1.0f - lerp) + b[2] * lerp;
+}
+
+bool 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 true;
+ }
+
+ return false;
+}
+
+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;
+}
+
+union f32_u {
+ float f;
+ uint32_t ui;
+ struct {
+ unsigned int fraction:23;
+ unsigned int exponent:8;
+ unsigned int sign:1;
+ } pack;
+};
+
+union f16_u {
+ uint16_t ui;
+ struct {
+ unsigned int fraction:10;
+ unsigned int exponent:5;
+ unsigned int sign:1;
+ } pack;
+};
+
+uint16_t FloatToHalf(float in)
+{
+ union f32_u f32;
+ union f16_u f16;
+
+ f32.f = in;
+
+ f16.pack.exponent = CLAMP((int)(f32.pack.exponent) - 112, 0, 31);
+ f16.pack.fraction = f32.pack.fraction >> 13;
+ f16.pack.sign = f32.pack.sign;
+
+ return f16.ui;
+}
+
+float HalfToFloat(uint16_t in)
+{
+ union f32_u f32;
+ union f16_u f16;
+
+ f16.ui = in;
+
+ f32.pack.exponent = (int)(f16.pack.exponent) + 112;
+ f32.pack.fraction = f16.pack.fraction << 13;
+ f32.pack.sign = f16.pack.sign;
+
+ return f32.f;
+}
diff --git a/src/renderergl2/tr_extramath.h b/src/renderergl2/tr_extramath.h
new file mode 100644
index 0000000..20c9278
--- /dev/null
+++ b/src/renderergl2/tr_extramath.h
@@ -0,0 +1,104 @@
+/*
+===========================================================================
+Copyright (C) 2010 James Canete (use.less01@gmail.com)
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// tr_extramath.h
+
+#ifndef __TR_EXTRAMATH_H__
+#define __TR_EXTRAMATH_H__
+
+typedef vec_t mat4_t[16];
+typedef int ivec2_t[2];
+typedef int ivec3_t[3];
+typedef int ivec4_t[4];
+
+void Mat4Zero( mat4_t out );
+void Mat4Identity( mat4_t out );
+void Mat4Copy( const mat4_t in, mat4_t out );
+void Mat4Multiply( const mat4_t in1, const mat4_t in2, mat4_t out );
+void Mat4Transform( const mat4_t in1, const vec4_t in2, vec4_t out );
+bool Mat4Compare(const mat4_t a, const mat4_t b);
+void Mat4Dump( const mat4_t in );
+void Mat4Translation( vec3_t vec, mat4_t out );
+void Mat4Ortho( float left, float right, float bottom, float top, float znear, float zfar, mat4_t out );
+void Mat4View(vec3_t axes[3], vec3_t origin, mat4_t out);
+void Mat4SimpleInverse( const mat4_t in, mat4_t out);
+
+#define VectorCopy2(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1])
+#define VectorSet2(v,x,y) ((v)[0]=(x),(v)[1]=(y));
+
+#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) * 127.5f + 128.0f)
+#define ByteToFloat(a) ((float)(a) * 1.0f/255.0f)
+#define FloatToByte(a) (byte)((a) * 255.0f)
+
+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;
+}
+
+void VectorLerp( vec3_t a, vec3_t b, float lerp, vec3_t c);
+
+
+bool 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);
+float HalfToFloat(unsigned short in);
+
+#endif
diff --git a/src/renderergl2/tr_extratypes.h b/src/renderergl2/tr_extratypes.h
new file mode 100644
index 0000000..c5c99c8
--- /dev/null
+++ b/src/renderergl2/tr_extratypes.h
@@ -0,0 +1,40 @@
+/*
+===========================================================================
+Copyright (C) 2009-2011 Andrei Drexler, Richard Allen, James Canete
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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.
+
+along with Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#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 refdef flags start at 0x0008
+#define RDF_NOFOG 0x0008 // don't apply fog to polys added using RE_AddPolyToScene
+#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
diff --git a/src/renderergl2/tr_fbo.cpp b/src/renderergl2/tr_fbo.cpp
new file mode 100644
index 0000000..5120540
--- /dev/null
+++ b/src/renderergl2/tr_fbo.cpp
@@ -0,0 +1,659 @@
+/*
+===========================================================================
+Copyright (C) 2006 Kirk Barnes
+Copyright (C) 2006-2008 Robert Beckebans <trebor_7@users.sourceforge.net>
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// tr_fbo.c
+#include "tr_local.h"
+
+#include "tr_dsa.h"
+
+/*
+=============
+R_CheckFBO
+=============
+*/
+bool R_CheckFBO(const FBO_t * fbo)
+{
+ GLenum code = qglCheckNamedFramebufferStatusEXT(fbo->frameBuffer, GL_FRAMEBUFFER);
+
+ if(code == GL_FRAMEBUFFER_COMPLETE)
+ return true;
+
+ // an error occured
+ switch (code)
+ {
+ case GL_FRAMEBUFFER_UNSUPPORTED:
+ ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Unsupported framebuffer format\n", fbo->name);
+ break;
+
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+ ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete attachment\n", fbo->name);
+ break;
+
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+ ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, missing attachment\n", fbo->name);
+ break;
+
+ case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
+ ri.Printf(PRINT_WARNING, "R_CheckFBO: (%s) Framebuffer incomplete, missing draw buffer\n", fbo->name);
+ break;
+
+ case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
+ 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);
+ break;
+ }
+
+ return false;
+}
+
+/*
+============
+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", 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] = (FBO_t*)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;
+
+ qglGenFramebuffers(1, &fbo->frameBuffer);
+
+ return fbo;
+}
+
+/*
+=================
+FBO_CreateBuffer
+=================
+*/
+void FBO_CreateBuffer(FBO_t *fbo, int format, int index, int multisample)
+{
+ uint32_t *pRenderBuffer;
+ GLenum attachment;
+ bool 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 + 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;
+ break;
+
+ case GL_STENCIL_INDEX:
+ case GL_STENCIL_INDEX1:
+ case GL_STENCIL_INDEX4:
+ case GL_STENCIL_INDEX8:
+ case GL_STENCIL_INDEX16:
+ fbo->stencilFormat = format;
+ pRenderBuffer = &fbo->stencilBuffer;
+ attachment = GL_STENCIL_ATTACHMENT;
+ break;
+
+ case GL_DEPTH_STENCIL:
+ case GL_DEPTH24_STENCIL8:
+ 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)
+ qglGenRenderbuffers(1, pRenderBuffer);
+
+ if (multisample && glRefConfig.framebufferMultisample)
+ qglNamedRenderbufferStorageMultisampleEXT(*pRenderBuffer, multisample, format, fbo->width, fbo->height);
+ else
+ qglNamedRenderbufferStorageEXT(*pRenderBuffer, format, fbo->width, fbo->height);
+
+ if(absent)
+ {
+ if (attachment == 0)
+ {
+ qglNamedFramebufferRenderbufferEXT(fbo->frameBuffer, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, *pRenderBuffer);
+ qglNamedFramebufferRenderbufferEXT(fbo->frameBuffer, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, *pRenderBuffer);
+ }
+ else
+ {
+ qglNamedFramebufferRenderbufferEXT(fbo->frameBuffer, attachment, GL_RENDERBUFFER, *pRenderBuffer);
+ }
+ }
+}
+
+
+/*
+=================
+FBO_AttachImage
+=================
+*/
+void FBO_AttachImage(FBO_t *fbo, image_t *image, GLenum attachment, GLuint cubemapside)
+{
+ GLenum target = GL_TEXTURE_2D;
+ int index;
+
+ if (image->flags & IMGFLAG_CUBEMAP)
+ target = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + cubemapside;
+
+ qglNamedFramebufferTexture2DEXT(fbo->frameBuffer, attachment, target, image->texnum, 0);
+ index = attachment - GL_COLOR_ATTACHMENT0;
+ if (index >= 0 && index <= 15)
+ fbo->colorImage[index] = image;
+}
+
+
+/*
+============
+FBO_Bind
+============
+*/
+void FBO_Bind(FBO_t * fbo)
+{
+ if (!glRefConfig.framebufferObject)
+ {
+ ri.Printf(PRINT_WARNING, "FBO_Bind() called without framebuffers enabled!\n");
+ return;
+ }
+
+ if (glState.currentFBO == fbo)
+ return;
+
+ if (r_logFile->integer)
+ {
+ // don't just call LogComment, or we will get a call to va() every frame!
+ GLimp_LogComment((char*)va("--- FBO_Bind( %s ) ---\n", fbo ? fbo->name : "NULL"));
+ }
+
+ GL_BindFramebuffer(GL_FRAMEBUFFER, fbo ? fbo->frameBuffer : 0);
+ glState.currentFBO = fbo;
+}
+
+/*
+============
+FBO_Init
+============
+*/
+void FBO_Init(void)
+{
+ int i;
+ int hdrFormat, multisample = 0;
+
+ ri.Printf(PRINT_ALL, "------- FBO_Init -------\n");
+
+ if(!glRefConfig.framebufferObject)
+ return;
+
+ tr.numFBOs = 0;
+
+ GL_CheckErrors();
+
+ R_IssuePendingRenderCommands();
+
+ hdrFormat = GL_RGBA8;
+ if (r_hdr->integer && glRefConfig.textureFloat)
+ hdrFormat = GL_RGBA16F_ARB;
+
+ if (glRefConfig.framebufferMultisample)
+ qglGetIntegerv(GL_MAX_SAMPLES, &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);
+
+ // only create a render FBO if we need to resolve MSAA or do HDR
+ // otherwise just render straight to the screen (tr.renderFbo = NULL)
+ if (multisample && glRefConfig.framebufferMultisample)
+ {
+ tr.renderFbo = FBO_Create("_render", tr.renderDepthImage->width, tr.renderDepthImage->height);
+ FBO_CreateBuffer(tr.renderFbo, hdrFormat, 0, multisample);
+ FBO_CreateBuffer(tr.renderFbo, GL_DEPTH_COMPONENT24, 0, multisample);
+ R_CheckFBO(tr.renderFbo);
+
+ tr.msaaResolveFbo = FBO_Create("_msaaResolve", tr.renderDepthImage->width, tr.renderDepthImage->height);
+ FBO_AttachImage(tr.msaaResolveFbo, tr.renderImage, GL_COLOR_ATTACHMENT0, 0);
+ FBO_AttachImage(tr.msaaResolveFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0);
+ R_CheckFBO(tr.msaaResolveFbo);
+ }
+ else if (r_hdr->integer)
+ {
+ tr.renderFbo = FBO_Create("_render", tr.renderDepthImage->width, tr.renderDepthImage->height);
+ FBO_AttachImage(tr.renderFbo, tr.renderImage, GL_COLOR_ATTACHMENT0, 0);
+ FBO_AttachImage(tr.renderFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0);
+ R_CheckFBO(tr.renderFbo);
+ }
+
+ // clear render buffer
+ // this fixes the corrupt screen bug with r_hdr 1 on older hardware
+ if (tr.renderFbo)
+ {
+ GL_BindFramebuffer(GL_FRAMEBUFFER, tr.renderFbo->frameBuffer);
+ qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
+ }
+
+ if (tr.screenScratchImage)
+ {
+ tr.screenScratchFbo = FBO_Create("screenScratch", tr.screenScratchImage->width, tr.screenScratchImage->height);
+ FBO_AttachImage(tr.screenScratchFbo, tr.screenScratchImage, GL_COLOR_ATTACHMENT0, 0);
+ FBO_AttachImage(tr.screenScratchFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0);
+ R_CheckFBO(tr.screenScratchFbo);
+ }
+
+ if (tr.sunRaysImage)
+ {
+ tr.sunRaysFbo = FBO_Create("_sunRays", tr.renderDepthImage->width, tr.renderDepthImage->height);
+ FBO_AttachImage(tr.sunRaysFbo, tr.sunRaysImage, GL_COLOR_ATTACHMENT0, 0);
+ FBO_AttachImage(tr.sunRaysFbo, tr.renderDepthImage, GL_DEPTH_ATTACHMENT, 0);
+ R_CheckFBO(tr.sunRaysFbo);
+ }
+
+ if (MAX_DRAWN_PSHADOWS && tr.pshadowMaps[0])
+ {
+ 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);
+ // FIXME: this next line wastes 16mb with 16x512x512 sun shadow maps, skip if OpenGL 4.3+ or ARB_framebuffer_no_attachments
+ FBO_CreateBuffer(tr.pshadowFbos[i], GL_RGBA8, 0, 0);
+ FBO_AttachImage(tr.pshadowFbos[i], tr.pshadowMaps[i], GL_DEPTH_ATTACHMENT, 0);
+ R_CheckFBO(tr.pshadowFbos[i]);
+ }
+ }
+
+ if (tr.sunShadowDepthImage[0])
+ {
+ for (i = 0; i < 4; i++)
+ {
+ tr.sunShadowFbo[i] = FBO_Create("_sunshadowmap", tr.sunShadowDepthImage[i]->width, tr.sunShadowDepthImage[i]->height);
+ // FIXME: this next line wastes 16mb with 4x1024x1024 sun shadow maps, skip if OpenGL 4.3+ or ARB_framebuffer_no_attachments
+ // This at least gets sun shadows working on older GPUs (Intel)
+ FBO_CreateBuffer(tr.sunShadowFbo[i], GL_RGBA8, 0, 0);
+ FBO_AttachImage(tr.sunShadowFbo[i], tr.sunShadowDepthImage[i], GL_DEPTH_ATTACHMENT, 0);
+ R_CheckFBO(tr.sunShadowFbo[i]);
+ }
+ }
+
+ if (tr.screenShadowImage)
+ {
+ tr.screenShadowFbo = FBO_Create("_screenshadow", tr.screenShadowImage->width, tr.screenShadowImage->height);
+ FBO_AttachImage(tr.screenShadowFbo, tr.screenShadowImage, GL_COLOR_ATTACHMENT0, 0);
+ R_CheckFBO(tr.screenShadowFbo);
+ }
+
+ if (tr.textureScratchImage[0])
+ {
+ for (i = 0; i < 2; i++)
+ {
+ tr.textureScratchFbo[i] = FBO_Create(va("_texturescratch%d", i), tr.textureScratchImage[i]->width, tr.textureScratchImage[i]->height);
+ FBO_AttachImage(tr.textureScratchFbo[i], tr.textureScratchImage[i], GL_COLOR_ATTACHMENT0, 0);
+ R_CheckFBO(tr.textureScratchFbo[i]);
+ }
+ }
+
+ if (tr.calcLevelsImage)
+ {
+ tr.calcLevelsFbo = FBO_Create("_calclevels", tr.calcLevelsImage->width, tr.calcLevelsImage->height);
+ FBO_AttachImage(tr.calcLevelsFbo, tr.calcLevelsImage, GL_COLOR_ATTACHMENT0, 0);
+ R_CheckFBO(tr.calcLevelsFbo);
+ }
+
+ if (tr.targetLevelsImage)
+ {
+ tr.targetLevelsFbo = FBO_Create("_targetlevels", tr.targetLevelsImage->width, tr.targetLevelsImage->height);
+ FBO_AttachImage(tr.targetLevelsFbo, tr.targetLevelsImage, GL_COLOR_ATTACHMENT0, 0);
+ R_CheckFBO(tr.targetLevelsFbo);
+ }
+
+ if (tr.quarterImage[0])
+ {
+ for (i = 0; i < 2; i++)
+ {
+ tr.quarterFbo[i] = FBO_Create(va("_quarter%d", i), tr.quarterImage[i]->width, tr.quarterImage[i]->height);
+ FBO_AttachImage(tr.quarterFbo[i], tr.quarterImage[i], GL_COLOR_ATTACHMENT0, 0);
+ R_CheckFBO(tr.quarterFbo[i]);
+ }
+ }
+
+ if (tr.hdrDepthImage)
+ {
+ tr.hdrDepthFbo = FBO_Create("_hdrDepth", tr.hdrDepthImage->width, tr.hdrDepthImage->height);
+ FBO_AttachImage(tr.hdrDepthFbo, tr.hdrDepthImage, GL_COLOR_ATTACHMENT0, 0);
+ R_CheckFBO(tr.hdrDepthFbo);
+ }
+
+ if (tr.screenSsaoImage)
+ {
+ tr.screenSsaoFbo = FBO_Create("_screenssao", tr.screenSsaoImage->width, tr.screenSsaoImage->height);
+ FBO_AttachImage(tr.screenSsaoFbo, tr.screenSsaoImage, GL_COLOR_ATTACHMENT0, 0);
+ R_CheckFBO(tr.screenSsaoFbo);
+ }
+
+ if (tr.renderCubeImage)
+ {
+ tr.renderCubeFbo = FBO_Create("_renderCubeFbo", tr.renderCubeImage->width, tr.renderCubeImage->height);
+ FBO_AttachImage(tr.renderCubeFbo, tr.renderCubeImage, GL_COLOR_ATTACHMENT0, 0);
+ FBO_CreateBuffer(tr.renderCubeFbo, GL_DEPTH_COMPONENT24_ARB, 0, 0);
+ R_CheckFBO(tr.renderCubeFbo);
+ }
+
+ GL_CheckErrors();
+
+ GL_BindFramebuffer(GL_FRAMEBUFFER, 0);
+ glState.currentFBO = 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])
+ qglDeleteRenderbuffers(1, &fbo->colorBuffers[j]);
+ }
+
+ if(fbo->depthBuffer)
+ qglDeleteRenderbuffers(1, &fbo->depthBuffer);
+
+ if(fbo->stencilBuffer)
+ qglDeleteRenderbuffers(1, &fbo->stencilBuffer);
+
+ if(fbo->frameBuffer)
+ qglDeleteFramebuffers(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);
+}
+
+void FBO_BlitFromTexture(struct image_s *src, vec4_t inSrcTexCorners, vec2_t inSrcTexScale, FBO_t *dst, ivec4_t inDstBox, struct shaderProgram_s *shaderProgram, vec4_t inColor, int blend)
+{
+ ivec4_t dstBox;
+ vec4_t color;
+ vec4_t quadVerts[4];
+ vec2_t texCoords[4];
+ vec2_t invTexRes;
+ FBO_t *oldFbo = glState.currentFBO;
+ mat4_t projection;
+ int width, height;
+
+ if (!src)
+ {
+ ri.Printf(PRINT_WARNING, "Tried to blit from a NULL texture!\n");
+ return;
+ }
+
+ width = dst ? dst->width : glConfig.vidWidth;
+ height = dst ? dst->height : glConfig.vidHeight;
+
+ if (inSrcTexCorners)
+ {
+ VectorSet2(texCoords[0], inSrcTexCorners[0], inSrcTexCorners[1]);
+ VectorSet2(texCoords[1], inSrcTexCorners[2], inSrcTexCorners[1]);
+ VectorSet2(texCoords[2], inSrcTexCorners[2], inSrcTexCorners[3]);
+ VectorSet2(texCoords[3], inSrcTexCorners[0], inSrcTexCorners[3]);
+ }
+ else
+ {
+ VectorSet2(texCoords[0], 0.0f, 1.0f);
+ VectorSet2(texCoords[1], 1.0f, 1.0f);
+ VectorSet2(texCoords[2], 1.0f, 0.0f);
+ VectorSet2(texCoords[3], 0.0f, 0.0f);
+ }
+
+ // framebuffers are 0 bottom, Y up.
+ if (inDstBox)
+ {
+ dstBox[0] = inDstBox[0];
+ dstBox[1] = height - inDstBox[1] - inDstBox[3];
+ dstBox[2] = inDstBox[0] + inDstBox[2];
+ dstBox[3] = height - inDstBox[1];
+ }
+ else
+ {
+ VectorSet4(dstBox, 0, height, width, 0);
+ }
+
+ if (inSrcTexScale)
+ {
+ VectorCopy2(inSrcTexScale, invTexRes);
+ }
+ else
+ {
+ VectorSet2(invTexRes, 1.0f, 1.0f);
+ }
+
+ if (inColor)
+ {
+ VectorCopy4(inColor, color);
+ }
+ else
+ {
+ VectorCopy4(colorWhite, color);
+ }
+
+ if (!shaderProgram)
+ {
+ shaderProgram = &tr.textureColorShader;
+ }
+
+ FBO_Bind(dst);
+
+ qglViewport( 0, 0, width, height );
+ qglScissor( 0, 0, width, height );
+
+ Mat4Ortho(0, width, height, 0, 0, 1, projection);
+
+ GL_Cull( CT_TWO_SIDED );
+
+ GL_BindToTMU(src, TB_COLORMAP);
+
+ VectorSet4(quadVerts[0], dstBox[0], dstBox[1], 0.0f, 1.0f);
+ VectorSet4(quadVerts[1], dstBox[2], dstBox[1], 0.0f, 1.0f);
+ VectorSet4(quadVerts[2], dstBox[2], dstBox[3], 0.0f, 1.0f);
+ VectorSet4(quadVerts[3], dstBox[0], dstBox[3], 0.0f, 1.0f);
+
+ invTexRes[0] /= src->width;
+ invTexRes[1] /= src->height;
+
+ GL_State( blend );
+
+ GLSL_BindProgram(shaderProgram);
+
+ GLSL_SetUniformMat4(shaderProgram, UNIFORM_MODELVIEWPROJECTIONMATRIX, projection);
+ GLSL_SetUniformVec4(shaderProgram, UNIFORM_COLOR, color);
+ GLSL_SetUniformVec2(shaderProgram, UNIFORM_INVTEXRES, invTexRes);
+ GLSL_SetUniformVec2(shaderProgram, UNIFORM_AUTOEXPOSUREMINMAX, tr.refdef.autoExposureMinMax);
+ GLSL_SetUniformVec3(shaderProgram, UNIFORM_TONEMINAVGMAXLINEAR, tr.refdef.toneMinAvgMaxLinear);
+
+ RB_InstantQuad2(quadVerts, texCoords);
+
+ FBO_Bind(oldFbo);
+}
+
+void FBO_Blit(FBO_t *src, ivec4_t inSrcBox, vec2_t srcTexScale, FBO_t *dst, ivec4_t dstBox, struct shaderProgram_s *shaderProgram, vec4_t color, int blend)
+{
+ vec4_t srcTexCorners;
+
+ if (!src)
+ {
+ ri.Printf(PRINT_WARNING, "Tried to blit from a NULL FBO!\n");
+ return;
+ }
+
+ if (inSrcBox)
+ {
+ srcTexCorners[0] = inSrcBox[0] / (float)src->width;
+ srcTexCorners[1] = (inSrcBox[1] + inSrcBox[3]) / (float)src->height;
+ srcTexCorners[2] = (inSrcBox[0] + inSrcBox[2]) / (float)src->width;
+ srcTexCorners[3] = inSrcBox[1] / (float)src->height;
+ }
+ else
+ {
+ VectorSet4(srcTexCorners, 0.0f, 0.0f, 1.0f, 1.0f);
+ }
+
+ FBO_BlitFromTexture(src->colorImage[0], srcTexCorners, srcTexScale, dst, dstBox, shaderProgram, color, blend | GLS_DEPTHTEST_DISABLE);
+}
+
+void FBO_FastBlit(FBO_t *src, ivec4_t srcBox, FBO_t *dst, ivec4_t dstBox, int buffers, int filter)
+{
+ ivec4_t srcBoxFinal, dstBoxFinal;
+ GLuint srcFb, dstFb;
+
+ if (!glRefConfig.framebufferBlit)
+ {
+ FBO_Blit(src, srcBox, NULL, dst, dstBox, NULL, NULL, 0);
+ return;
+ }
+
+ srcFb = src ? src->frameBuffer : 0;
+ dstFb = dst ? dst->frameBuffer : 0;
+
+ if (!srcBox)
+ {
+ int width = src ? src->width : glConfig.vidWidth;
+ int height = src ? src->height : glConfig.vidHeight;
+
+ VectorSet4(srcBoxFinal, 0, 0, width, height);
+ }
+ else
+ {
+ VectorSet4(srcBoxFinal, srcBox[0], srcBox[1], srcBox[0] + srcBox[2], srcBox[1] + srcBox[3]);
+ }
+
+ if (!dstBox)
+ {
+ int width = dst ? dst->width : glConfig.vidWidth;
+ int height = dst ? dst->height : glConfig.vidHeight;
+
+ VectorSet4(dstBoxFinal, 0, 0, width, height);
+ }
+ else
+ {
+ VectorSet4(dstBoxFinal, dstBox[0], dstBox[1], dstBox[0] + dstBox[2], dstBox[1] + dstBox[3]);
+ }
+
+ GL_BindFramebuffer(GL_READ_FRAMEBUFFER, srcFb);
+ GL_BindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFb);
+ qglBlitFramebuffer(srcBoxFinal[0], srcBoxFinal[1], srcBoxFinal[2], srcBoxFinal[3],
+ dstBoxFinal[0], dstBoxFinal[1], dstBoxFinal[2], dstBoxFinal[3],
+ buffers, filter);
+
+ GL_BindFramebuffer(GL_FRAMEBUFFER, 0);
+ glState.currentFBO = NULL;
+}
diff --git a/src/renderergl2/tr_fbo.h b/src/renderergl2/tr_fbo.h
new file mode 100644
index 0000000..efd317b
--- /dev/null
+++ b/src/renderergl2/tr_fbo.h
@@ -0,0 +1,66 @@
+/*
+===========================================================================
+Copyright (C) 2010 James Canete (use.less01@gmail.com)
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// 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_AttachImage(FBO_t *fbo, image_t *image, GLenum attachment, GLuint cubemapside);
+void FBO_Bind(FBO_t *fbo);
+void FBO_Init(void);
+void FBO_Shutdown(void);
+
+void FBO_BlitFromTexture(struct image_s *src, vec4_t inSrcTexCorners, vec2_t inSrcTexScale, FBO_t *dst, ivec4_t inDstBox, struct shaderProgram_s *shaderProgram, vec4_t inColor, int blend);
+void FBO_Blit(FBO_t *src, ivec4_t srcBox, vec2_t srcTexScale, FBO_t *dst, ivec4_t dstBox, struct shaderProgram_s *shaderProgram, vec4_t color, int blend);
+void FBO_FastBlit(FBO_t *src, ivec4_t srcBox, FBO_t *dst, ivec4_t dstBox, int buffers, int filter);
+
+
+#endif
diff --git a/src/renderergl2/tr_flares.cpp b/src/renderergl2/tr_flares.cpp
new file mode 100644
index 0000000..e2f38c8
--- /dev/null
+++ b/src/renderergl2/tr_flares.cpp
@@ -0,0 +1,554 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// 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;
+
+ bool inPortal; // true if in a portal view of the scene
+ int frameSceneNum;
+ void *surface;
+ int fogNum;
+
+ int fadeTime;
+
+ bool 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_SetFlareCoeff
+==================
+*/
+static void R_SetFlareCoeff( void ) {
+
+ if(r_flareCoeff->value == 0.0f)
+ flareCoeff = atof(FLARE_STDCOEFF);
+ else
+ flareCoeff = r_flareCoeff->value;
+}
+
+/*
+==================
+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];
+ }
+
+ R_SetFlareCoeff();
+}
+
+
+/*
+==================
+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.orientation.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.orientation.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 = false;
+ 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 ; i<backEnd.refdef.num_dlights ; i++, l++) {
+
+ if(fog)
+ {
+ // find which fog volume the light is in
+ for ( j = 1 ; j < tr.world->numfogs ; 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;
+ bool visible;
+ float fade;
+ float screenZ;
+ FBO_t *oldFbo;
+
+ backEnd.pc.c_flareTests++;
+
+ // doing a readpixels is as good as doing a glFinish(), so
+ // don't bother with another sync
+ glState.finishCalled = false;
+
+ // if we're doing multisample rendering, read from the correct FBO
+ oldFbo = glState.currentFBO;
+ if (tr.msaaResolveFbo)
+ {
+ FBO_Bind(tr.msaaResolveFbo);
+ }
+
+ // read back the z buffer contents
+ qglReadPixels( f->windowX, f->windowY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth );
+
+ // if we're doing multisample rendering, switch to the old FBO
+ if (tr.msaaResolveFbo)
+ {
+ FBO_Bind(oldFbo);
+ }
+
+ 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 = true;
+ f->fadeTime = backEnd.refdef.time - 1;
+ }
+ fade = ( ( backEnd.refdef.time - f->fadeTime ) /1000.0f ) * r_flareFade->value;
+ } else {
+ if ( f->visible ) {
+ f->visible = false;
+ 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 > 0 && 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] * 257;
+ iColor[1] = color[1] * fogFactors[1] * 257;
+ iColor[2] = color[2] * fogFactors[2] * 257;
+
+ RB_BeginSurface( tr.flareShader, f->fogNum, 0 );
+
+ // 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;
+ tess.texCoords[tess.numVertexes][1] = 0;
+ tess.color[tess.numVertexes][0] = iColor[0];
+ tess.color[tess.numVertexes][1] = iColor[1];
+ tess.color[tess.numVertexes][2] = iColor[2];
+ tess.color[tess.numVertexes][3] = 65535;
+ tess.numVertexes++;
+
+ tess.xyz[tess.numVertexes][0] = f->windowX - size;
+ tess.xyz[tess.numVertexes][1] = f->windowY + size;
+ tess.texCoords[tess.numVertexes][0] = 0;
+ tess.texCoords[tess.numVertexes][1] = 1;
+ tess.color[tess.numVertexes][0] = iColor[0];
+ tess.color[tess.numVertexes][1] = iColor[1];
+ tess.color[tess.numVertexes][2] = iColor[2];
+ tess.color[tess.numVertexes][3] = 65535;
+ tess.numVertexes++;
+
+ tess.xyz[tess.numVertexes][0] = f->windowX + size;
+ tess.xyz[tess.numVertexes][1] = f->windowY + size;
+ tess.texCoords[tess.numVertexes][0] = 1;
+ tess.texCoords[tess.numVertexes][1] = 1;
+ tess.color[tess.numVertexes][0] = iColor[0];
+ tess.color[tess.numVertexes][1] = iColor[1];
+ tess.color[tess.numVertexes][2] = iColor[2];
+ tess.color[tess.numVertexes][3] = 65535;
+ tess.numVertexes++;
+
+ tess.xyz[tess.numVertexes][0] = f->windowX + size;
+ tess.xyz[tess.numVertexes][1] = f->windowY - size;
+ tess.texCoords[tess.numVertexes][0] = 1;
+ tess.texCoords[tess.numVertexes][1] = 0;
+ tess.color[tess.numVertexes][0] = iColor[0];
+ tess.color[tess.numVertexes][1] = iColor[1];
+ tess.color[tess.numVertexes][2] = iColor[2];
+ tess.color[tess.numVertexes][3] = 65535;
+ 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;
+ bool draw;
+ mat4_t oldmodelview, oldprojection, matrix;
+
+ if ( !r_flares->integer ) {
+ return;
+ }
+
+ if(r_flareCoeff->modified)
+ {
+ R_SetFlareCoeff();
+ r_flareCoeff->modified = false;
+ }
+
+ // 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.orientation = backEnd.viewParms.world;
+
+// RB_AddDlightFlares();
+
+ // perform z buffer readback on each flare in this view
+ draw = false;
+ 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 = true;
+ } 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);
+ }
+
+ Mat4Copy(glState.projection, oldprojection);
+ Mat4Copy(glState.modelview, oldmodelview);
+ Mat4Identity(matrix);
+ GL_SetModelviewMatrix(matrix);
+ Mat4Ortho( 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/renderergl2/tr_glsl.cpp b/src/renderergl2/tr_glsl.cpp
new file mode 100644
index 0000000..469cb7c
--- /dev/null
+++ b/src/renderergl2/tr_glsl.cpp
@@ -0,0 +1,1470 @@
+/*
+===========================================================================
+Copyright (C) 2006-2009 Robert Beckebans <trebor_7@users.sourceforge.net>
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// tr_glsl.c
+#include "tr_local.h"
+
+#include "tr_dsa.h"
+
+extern const char *fallbackShader_bokeh_vp;
+extern const char *fallbackShader_bokeh_fp;
+extern const char *fallbackShader_calclevels4x_vp;
+extern const char *fallbackShader_calclevels4x_fp;
+extern const char *fallbackShader_depthblur_vp;
+extern const char *fallbackShader_depthblur_fp;
+extern const char *fallbackShader_dlight_vp;
+extern const char *fallbackShader_dlight_fp;
+extern const char *fallbackShader_down4x_vp;
+extern const char *fallbackShader_down4x_fp;
+extern const char *fallbackShader_fogpass_vp;
+extern const char *fallbackShader_fogpass_fp;
+extern const char *fallbackShader_generic_vp;
+extern const char *fallbackShader_generic_fp;
+extern const char *fallbackShader_lightall_vp;
+extern const char *fallbackShader_lightall_fp;
+extern const char *fallbackShader_pshadow_vp;
+extern const char *fallbackShader_pshadow_fp;
+extern const char *fallbackShader_shadowfill_vp;
+extern const char *fallbackShader_shadowfill_fp;
+extern const char *fallbackShader_shadowmask_vp;
+extern const char *fallbackShader_shadowmask_fp;
+extern const char *fallbackShader_ssao_vp;
+extern const char *fallbackShader_ssao_fp;
+extern const char *fallbackShader_texturecolor_vp;
+extern const char *fallbackShader_texturecolor_fp;
+extern const char *fallbackShader_tonemap_vp;
+extern const char *fallbackShader_tonemap_fp;
+
+struct uniformInfo_t
+{
+ const char *name;
+ int type;
+};
+
+// These must be in the same order as in uniform_t in tr_local.h.
+static uniformInfo_t uniformsInfo[] =
+{
+ { "u_DiffuseMap", GLSL_INT },
+ { "u_LightMap", GLSL_INT },
+ { "u_NormalMap", GLSL_INT },
+ { "u_DeluxeMap", GLSL_INT },
+ { "u_SpecularMap", GLSL_INT },
+
+ { "u_TextureMap", GLSL_INT },
+ { "u_LevelsMap", GLSL_INT },
+ { "u_CubeMap", GLSL_INT },
+
+ { "u_ScreenImageMap", GLSL_INT },
+ { "u_ScreenDepthMap", GLSL_INT },
+
+ { "u_ShadowMap", GLSL_INT },
+ { "u_ShadowMap2", GLSL_INT },
+ { "u_ShadowMap3", GLSL_INT },
+ { "u_ShadowMap4", GLSL_INT },
+
+ { "u_ShadowMvp", GLSL_MAT16 },
+ { "u_ShadowMvp2", GLSL_MAT16 },
+ { "u_ShadowMvp3", GLSL_MAT16 },
+ { "u_ShadowMvp4", GLSL_MAT16 },
+
+ { "u_EnableTextures", GLSL_VEC4 },
+
+ { "u_DiffuseTexMatrix", GLSL_VEC4 },
+ { "u_DiffuseTexOffTurb", GLSL_VEC4 },
+
+ { "u_TCGen0", GLSL_INT },
+ { "u_TCGen0Vector0", GLSL_VEC3 },
+ { "u_TCGen0Vector1", GLSL_VEC3 },
+
+ { "u_DeformGen", GLSL_INT },
+ { "u_DeformParams", GLSL_FLOAT5 },
+
+ { "u_ColorGen", GLSL_INT },
+ { "u_AlphaGen", GLSL_INT },
+ { "u_Color", GLSL_VEC4 },
+ { "u_BaseColor", GLSL_VEC4 },
+ { "u_VertColor", GLSL_VEC4 },
+
+ { "u_DlightInfo", GLSL_VEC4 },
+ { "u_LightForward", GLSL_VEC3 },
+ { "u_LightUp", GLSL_VEC3 },
+ { "u_LightRight", GLSL_VEC3 },
+ { "u_LightOrigin", GLSL_VEC4 },
+ { "u_ModelLightDir", GLSL_VEC3 },
+ { "u_LightRadius", GLSL_FLOAT },
+ { "u_AmbientLight", GLSL_VEC3 },
+ { "u_DirectedLight", GLSL_VEC3 },
+
+ { "u_PortalRange", GLSL_FLOAT },
+
+ { "u_FogDistance", GLSL_VEC4 },
+ { "u_FogDepth", GLSL_VEC4 },
+ { "u_FogEyeT", GLSL_FLOAT },
+ { "u_FogColorMask", GLSL_VEC4 },
+
+ { "u_ModelMatrix", GLSL_MAT16 },
+ { "u_ModelViewProjectionMatrix", GLSL_MAT16 },
+
+ { "u_Time", GLSL_FLOAT },
+ { "u_VertexLerp" , GLSL_FLOAT },
+ { "u_NormalScale", GLSL_VEC4 },
+ { "u_SpecularScale", GLSL_VEC4 },
+
+ { "u_ViewInfo", GLSL_VEC4 },
+ { "u_ViewOrigin", GLSL_VEC3 },
+ { "u_LocalViewOrigin", GLSL_VEC3 },
+ { "u_ViewForward", GLSL_VEC3 },
+ { "u_ViewLeft", GLSL_VEC3 },
+ { "u_ViewUp", GLSL_VEC3 },
+
+ { "u_InvTexRes", GLSL_VEC2 },
+ { "u_AutoExposureMinMax", GLSL_VEC2 },
+ { "u_ToneMinAvgMaxLinear", GLSL_VEC3 },
+
+ { "u_PrimaryLightOrigin", GLSL_VEC4 },
+ { "u_PrimaryLightColor", GLSL_VEC3 },
+ { "u_PrimaryLightAmbient", GLSL_VEC3 },
+ { "u_PrimaryLightRadius", GLSL_FLOAT },
+
+ { "u_CubeMapInfo", GLSL_VEC4 },
+
+ { "u_AlphaTest", GLSL_INT },
+};
+
+typedef enum
+{
+ GLSL_PRINTLOG_PROGRAM_INFO,
+ GLSL_PRINTLOG_SHADER_INFO,
+ GLSL_PRINTLOG_SHADER_SOURCE
+}
+glslPrintLog_t;
+
+static void GLSL_PrintLog(GLuint programOrShader, glslPrintLog_t type, bool developerOnly)
+{
+ char *msg;
+ static char msgPart[1024];
+ int maxLength = 0;
+ int i;
+ int printLevel = developerOnly ? PRINT_DEVELOPER : PRINT_ALL;
+
+ switch (type)
+ {
+ case GLSL_PRINTLOG_PROGRAM_INFO:
+ ri.Printf(printLevel, "Program info log:\n");
+ qglGetProgramiv(programOrShader, GL_INFO_LOG_LENGTH, &maxLength);
+ break;
+
+ case GLSL_PRINTLOG_SHADER_INFO:
+ ri.Printf(printLevel, "Shader info log:\n");
+ qglGetShaderiv(programOrShader, GL_INFO_LOG_LENGTH, &maxLength);
+ break;
+
+ case GLSL_PRINTLOG_SHADER_SOURCE:
+ ri.Printf(printLevel, "Shader source:\n");
+ qglGetShaderiv(programOrShader, GL_SHADER_SOURCE_LENGTH, &maxLength);
+ break;
+ }
+
+ if (maxLength <= 0)
+ {
+ ri.Printf(printLevel, "None.\n");
+ return;
+ }
+
+ if (maxLength < 1023)
+ msg = msgPart;
+ else
+ msg = (char*)ri.Malloc(maxLength);
+
+ switch (type)
+ {
+ case GLSL_PRINTLOG_PROGRAM_INFO:
+ qglGetProgramInfoLog(programOrShader, maxLength, &maxLength, msg);
+ break;
+
+ case GLSL_PRINTLOG_SHADER_INFO:
+ qglGetShaderInfoLog(programOrShader, maxLength, &maxLength, msg);
+ break;
+
+ case GLSL_PRINTLOG_SHADER_SOURCE:
+ qglGetShaderSource(programOrShader, maxLength, &maxLength, msg);
+ break;
+ }
+
+ if (maxLength < 1023)
+ {
+ msgPart[maxLength + 1] = '\0';
+
+ ri.Printf(printLevel, "%s\n", msgPart);
+ }
+ else
+ {
+ for(i = 0; i < maxLength; i += 1023)
+ {
+ Q_strncpyz(msgPart, msg + i, sizeof(msgPart));
+
+ ri.Printf(printLevel, "%s", msgPart);
+ }
+
+ ri.Printf(printLevel, "\n");
+
+ ri.Free(msg);
+ }
+
+}
+
+static void GLSL_GetShaderHeader( GLenum shaderType, const GLchar *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 150\n");
+
+ if(shaderType == GL_VERTEX_SHADER)
+ {
+ 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");
+ Q_strcat(dest, size, "#define texture2D texture\n");
+ Q_strcat(dest, size, "#define textureCubeLod textureLod\n");
+ Q_strcat(dest, size, "#define shadow2D texture\n");
+ }
+ }
+ else
+ {
+ Q_strcat(dest, size, "#version 120\n");
+ Q_strcat(dest, size, "#define shadow2D(a,b) shadow2D(a,b).r \n");
+ }
+
+ // HACK: add some macros to avoid extra uniforms and save speed and code maintenance
+ //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.14159265358979323846\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"
+ "#endif\n",
+ AGEN_LIGHTING_SPECULAR,
+ AGEN_PORTAL));
+
+ 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 (r_pbr->integer)
+ Q_strcat(dest, size, "#define USE_PBR\n");
+
+ if (r_cubeMapping->integer)
+ {
+ int cubeMipSize = r_cubemapSize->integer;
+ int numRoughnessMips = 0;
+
+ while (cubeMipSize)
+ {
+ cubeMipSize >>= 1;
+ numRoughnessMips++;
+ }
+ numRoughnessMips = MAX(1, numRoughnessMips - 2);
+ Q_strcat(dest, size, va("#define ROUGHNESS_MIPS float(%d)\n", numRoughnessMips));
+ }
+
+ if (extra)
+ {
+ Q_strcat(dest, size, extra);
+ }
+
+ // OK we added a lot of stuff but if we do something bad in the GLSL shaders then we want the proper line
+ // so we have to reset the line counting
+ Q_strcat(dest, size, "#line 0\n");
+}
+
+static int GLSL_CompileGPUShader(GLuint program, GLuint *prevShader, const GLchar *buffer, int size, GLenum shaderType)
+{
+ GLint compiled;
+ GLuint shader;
+
+ shader = qglCreateShader(shaderType);
+
+ qglShaderSource(shader, 1, (const GLchar **)&buffer, &size);
+
+ // compile shader
+ qglCompileShader(shader);
+
+ // check if shader compiled
+ qglGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+ if(!compiled)
+ {
+ GLSL_PrintLog(shader, GLSL_PRINTLOG_SHADER_SOURCE, false);
+ GLSL_PrintLog(shader, GLSL_PRINTLOG_SHADER_INFO, false);
+ ri.Error(ERR_DROP, "Couldn't compile shader");
+ return 0;
+ }
+
+ if (*prevShader)
+ {
+ qglDetachShader(program, *prevShader);
+ qglDeleteShader(*prevShader);
+ }
+
+ // attach shader to program
+ qglAttachShader(program, shader);
+
+ *prevShader = shader;
+
+ return 1;
+}
+
+static int GLSL_LoadGPUShaderText(const char *name, const char *fallback,
+ GLenum shaderType, char *dest, int destSize)
+{
+ char filename[MAX_QPATH];
+ GLchar *buffer = NULL;
+ const GLchar *shaderText = NULL;
+ int size;
+ int result;
+
+ if(shaderType == GL_VERTEX_SHADER)
+ {
+ Com_sprintf(filename, sizeof(filename), "glsl/%s_vp.glsl", name);
+ }
+ else
+ {
+ Com_sprintf(filename, sizeof(filename), "glsl/%s_fp.glsl", name);
+ }
+
+ if ( r_externalGLSL->integer ) {
+ size = ri.FS_ReadFile(filename, (void **)&buffer);
+ } else {
+ size = 0;
+ buffer = NULL;
+ }
+
+ if(!buffer)
+ {
+ if (fallback)
+ {
+ ri.Printf(PRINT_DEVELOPER, "...loading built-in '%s'\n", filename);
+ shaderText = fallback;
+ size = strlen(shaderText);
+ }
+ else
+ {
+ ri.Printf(PRINT_DEVELOPER, "couldn't load '%s'\n", filename);
+ return 0;
+ }
+ }
+ else
+ {
+ ri.Printf(PRINT_DEVELOPER, "...loading '%s'\n", filename);
+ shaderText = buffer;
+ }
+
+ 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(GLuint program)
+{
+ GLint linked;
+
+ qglLinkProgram(program);
+
+ qglGetProgramiv(program, GL_LINK_STATUS, &linked);
+ if(!linked)
+ {
+ GLSL_PrintLog(program, GLSL_PRINTLOG_PROGRAM_INFO, false);
+ ri.Error(ERR_DROP, "shaders failed to link");
+ }
+}
+
+static void GLSL_ValidateProgram(GLuint program)
+{
+ GLint validated;
+
+ qglValidateProgram(program);
+
+ qglGetProgramiv(program, GL_VALIDATE_STATUS, &validated);
+ if(!validated)
+ {
+ GLSL_PrintLog(program, GLSL_PRINTLOG_PROGRAM_INFO, false);
+ ri.Error(ERR_DROP, "shaders failed to validate");
+ }
+}
+
+static void GLSL_ShowProgramUniforms(GLuint program)
+{
+ int i, count, size;
+ GLenum type;
+ char uniformName[1000];
+
+ // query the number of active uniforms
+ qglGetProgramiv(program, GL_ACTIVE_UNIFORMS, &count);
+
+ // Loop over each of the active uniforms, and set their value
+ for(i = 0; i < count; i++)
+ {
+ qglGetActiveUniform(program, i, sizeof(uniformName), NULL, &size, &type, uniformName);
+
+ ri.Printf(PRINT_DEVELOPER, "active uniform: '%s'\n", uniformName);
+ }
+}
+
+static int GLSL_InitGPUShader2(shaderProgram_t * program, const char *name, int attribs, const char *vpCode, const char *fpCode)
+{
+ ri.Printf(PRINT_DEVELOPER, "------- GPU shader -------\n");
+
+ if(strlen(name) >= MAX_QPATH)
+ {
+ ri.Error(ERR_DROP, "GLSL_InitGPUShader2: \"%s\" is too long", name);
+ }
+
+ Q_strncpyz(program->name, name, sizeof(program->name));
+
+ program->program = qglCreateProgram();
+ program->attribs = attribs;
+
+ if (!(GLSL_CompileGPUShader(program->program, &program->vertexShader, vpCode, strlen(vpCode), GL_VERTEX_SHADER)))
+ {
+ ri.Printf(PRINT_ALL, "GLSL_InitGPUShader2: Unable to load \"%s\" as GL_VERTEX_SHADER\n", name);
+ qglDeleteProgram(program->program);
+ return 0;
+ }
+
+ if(fpCode)
+ {
+ if(!(GLSL_CompileGPUShader(program->program, &program->fragmentShader, fpCode, strlen(fpCode), GL_FRAGMENT_SHADER)))
+ {
+ ri.Printf(PRINT_ALL, "GLSL_InitGPUShader2: Unable to load \"%s\" as GL_FRAGMENT_SHADER\n", name);
+ qglDeleteProgram(program->program);
+ return 0;
+ }
+ }
+
+ if(attribs & ATTR_POSITION)
+ qglBindAttribLocation(program->program, ATTR_INDEX_POSITION, "attr_Position");
+
+ if(attribs & ATTR_TEXCOORD)
+ qglBindAttribLocation(program->program, ATTR_INDEX_TEXCOORD, "attr_TexCoord0");
+
+ if(attribs & ATTR_LIGHTCOORD)
+ qglBindAttribLocation(program->program, ATTR_INDEX_LIGHTCOORD, "attr_TexCoord1");
+
+// if(attribs & ATTR_TEXCOORD2)
+// qglBindAttribLocation(program->program, ATTR_INDEX_TEXCOORD2, "attr_TexCoord2");
+
+// if(attribs & ATTR_TEXCOORD3)
+// qglBindAttribLocation(program->program, ATTR_INDEX_TEXCOORD3, "attr_TexCoord3");
+
+ if(attribs & ATTR_TANGENT)
+ qglBindAttribLocation(program->program, ATTR_INDEX_TANGENT, "attr_Tangent");
+
+ if(attribs & ATTR_NORMAL)
+ qglBindAttribLocation(program->program, ATTR_INDEX_NORMAL, "attr_Normal");
+
+ if(attribs & ATTR_COLOR)
+ qglBindAttribLocation(program->program, ATTR_INDEX_COLOR, "attr_Color");
+
+ if(attribs & ATTR_PAINTCOLOR)
+ qglBindAttribLocation(program->program, ATTR_INDEX_PAINTCOLOR, "attr_PaintColor");
+
+ if(attribs & ATTR_LIGHTDIRECTION)
+ qglBindAttribLocation(program->program, ATTR_INDEX_LIGHTDIRECTION, "attr_LightDirection");
+
+ if(attribs & ATTR_POSITION2)
+ qglBindAttribLocation(program->program, ATTR_INDEX_POSITION2, "attr_Position2");
+
+ if(attribs & ATTR_NORMAL2)
+ qglBindAttribLocation(program->program, ATTR_INDEX_NORMAL2, "attr_Normal2");
+
+ if(attribs & ATTR_TANGENT2)
+ qglBindAttribLocation(program->program, ATTR_INDEX_TANGENT2, "attr_Tangent2");
+
+ GLSL_LinkProgram(program->program);
+
+ return 1;
+}
+
+static int GLSL_InitGPUShader(shaderProgram_t * program, const char *name,
+ int attribs, bool fragmentShader, const GLchar *extra, bool addHeader,
+ const char *fallback_vp, const char *fallback_fp)
+{
+ char vpCode[32000];
+ char fpCode[32000];
+ char *postHeader;
+ int size;
+ int result;
+
+ size = sizeof(vpCode);
+ if (addHeader)
+ {
+ GLSL_GetShaderHeader(GL_VERTEX_SHADER, extra, vpCode, size);
+ postHeader = &vpCode[strlen(vpCode)];
+ size -= strlen(vpCode);
+ }
+ else
+ {
+ postHeader = &vpCode[0];
+ }
+
+ if (!GLSL_LoadGPUShaderText(name, fallback_vp, GL_VERTEX_SHADER, postHeader, size))
+ {
+ return 0;
+ }
+
+ if (fragmentShader)
+ {
+ size = sizeof(fpCode);
+ if (addHeader)
+ {
+ GLSL_GetShaderHeader(GL_FRAGMENT_SHADER, extra, fpCode, size);
+ postHeader = &fpCode[strlen(fpCode)];
+ size -= strlen(fpCode);
+ }
+ else
+ {
+ postHeader = &fpCode[0];
+ }
+
+ if (!GLSL_LoadGPUShaderText(name, fallback_fp, GL_FRAGMENT_SHADER, postHeader, size))
+ {
+ return 0;
+ }
+ }
+
+ result = GLSL_InitGPUShader2(program, name, attribs, vpCode, fragmentShader ? fpCode : NULL);
+
+ return result;
+}
+
+void GLSL_InitUniforms(shaderProgram_t *program)
+{
+ int i, size;
+
+ GLint *uniforms = program->uniforms;
+
+ size = 0;
+ for (i = 0; i < UNIFORM_COUNT; i++)
+ {
+ uniforms[i] = qglGetUniformLocation(program->program, uniformsInfo[i].name);
+
+ if (uniforms[i] == -1)
+ continue;
+
+ program->uniformBufferOffsets[i] = size;
+
+ switch(uniformsInfo[i].type)
+ {
+ 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 = (char*)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 (uniformsInfo[uniformNum].type != 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;
+
+ qglProgramUniform1iEXT(program->program, 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 (uniformsInfo[uniformNum].type != 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;
+
+ qglProgramUniform1fEXT(program->program, 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 (uniformsInfo[uniformNum].type != 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];
+
+ qglProgramUniform2fEXT(program->program, 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 (uniformsInfo[uniformNum].type != 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);
+
+ qglProgramUniform3fEXT(program->program, 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 (uniformsInfo[uniformNum].type != 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);
+
+ qglProgramUniform4fEXT(program->program, 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)
+ return;
+
+ if (uniformsInfo[uniformNum].type != 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);
+
+ qglProgramUniform1fvEXT(program->program, uniforms[uniformNum], 5, v);
+}
+
+void GLSL_SetUniformMat4(shaderProgram_t *program, int uniformNum, const mat4_t matrix)
+{
+ GLint *uniforms = program->uniforms;
+ vec_t *compare = (float *)(program->uniformBuffer + program->uniformBufferOffsets[uniformNum]);
+
+ if (uniforms[uniformNum] == -1)
+ return;
+
+ if (uniformsInfo[uniformNum].type != GLSL_MAT16)
+ {
+ ri.Printf( PRINT_WARNING, "GLSL_SetUniformMat4: wrong type for uniform %i in program %s\n", uniformNum, program->name);
+ return;
+ }
+
+ if (Mat4Compare(matrix, compare))
+ {
+ return;
+ }
+
+ Mat4Copy(matrix, compare);
+
+ qglProgramUniformMatrix4fvEXT(program->program, uniforms[uniformNum], 1, GL_FALSE, matrix);
+}
+
+void GLSL_DeleteGPUShader(shaderProgram_t *program)
+{
+ if(program->program)
+ {
+ if (program->vertexShader)
+ {
+ qglDetachShader(program->program, program->vertexShader);
+ qglDeleteShader(program->vertexShader);
+ }
+
+ if (program->fragmentShader)
+ {
+ qglDetachShader(program->program, program->fragmentShader);
+ qglDeleteShader(program->fragmentShader);
+ }
+
+ qglDeleteProgram(program->program);
+
+ if (program->uniformBuffer)
+ {
+ ri.Free(program->uniformBuffer);
+ }
+
+ 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");
+
+ R_IssuePendingRenderCommands();
+
+ 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_AND_TCMOD)
+ {
+ Q_strcat(extradefines, 1024, "#define USE_TCGEN\n");
+ Q_strcat(extradefines, 1024, "#define USE_TCMOD\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 (!GLSL_InitGPUShader(&tr.genericShader[i], "generic", attribs, true, extradefines, true, fallbackShader_generic_vp, fallbackShader_generic_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load generic shader!");
+ }
+
+ GLSL_InitUniforms(&tr.genericShader[i]);
+
+ GLSL_SetUniformInt(&tr.genericShader[i], UNIFORM_DIFFUSEMAP, TB_DIFFUSEMAP);
+ GLSL_SetUniformInt(&tr.genericShader[i], UNIFORM_LIGHTMAP, TB_LIGHTMAP);
+
+ GLSL_FinishGPUShader(&tr.genericShader[i]);
+
+ numGenShaders++;
+ }
+
+
+ attribs = ATTR_POSITION | ATTR_TEXCOORD;
+
+ if (!GLSL_InitGPUShader(&tr.textureColorShader, "texturecolor", attribs, true, extradefines, true, fallbackShader_texturecolor_vp, fallbackShader_texturecolor_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load texturecolor shader!");
+ }
+
+ GLSL_InitUniforms(&tr.textureColorShader);
+
+ GLSL_SetUniformInt(&tr.textureColorShader, UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP);
+
+ GLSL_FinishGPUShader(&tr.textureColorShader);
+
+ numEtcShaders++;
+
+ for (i = 0; i < FOGDEF_COUNT; i++)
+ {
+ attribs = ATTR_POSITION | ATTR_POSITION2 | ATTR_NORMAL | ATTR_NORMAL2 | ATTR_TEXCOORD;
+ extradefines[0] = '\0';
+
+ if (i & FOGDEF_USE_DEFORM_VERTEXES)
+ Q_strcat(extradefines, 1024, "#define USE_DEFORM_VERTEXES\n");
+
+ if (i & FOGDEF_USE_VERTEX_ANIMATION)
+ Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n");
+
+ if (!GLSL_InitGPUShader(&tr.fogShader[i], "fogpass", attribs, true, extradefines, true, fallbackShader_fogpass_vp, fallbackShader_fogpass_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load fogpass shader!");
+ }
+
+ GLSL_InitUniforms(&tr.fogShader[i]);
+ GLSL_FinishGPUShader(&tr.fogShader[i]);
+
+ numEtcShaders++;
+ }
+
+
+ for (i = 0; i < DLIGHTDEF_COUNT; i++)
+ {
+ attribs = ATTR_POSITION | ATTR_NORMAL | ATTR_TEXCOORD;
+ extradefines[0] = '\0';
+
+ if (i & DLIGHTDEF_USE_DEFORM_VERTEXES)
+ {
+ Q_strcat(extradefines, 1024, "#define USE_DEFORM_VERTEXES\n");
+ }
+
+ if (!GLSL_InitGPUShader(&tr.dlightShader[i], "dlight", attribs, true, extradefines, true, fallbackShader_dlight_vp, fallbackShader_dlight_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load dlight shader!");
+ }
+
+ GLSL_InitUniforms(&tr.dlightShader[i]);
+
+ GLSL_SetUniformInt(&tr.dlightShader[i], UNIFORM_DIFFUSEMAP, TB_DIFFUSEMAP);
+
+ GLSL_FinishGPUShader(&tr.dlightShader[i]);
+
+ numEtcShaders++;
+ }
+
+
+ for (i = 0; i < LIGHTDEF_COUNT; i++)
+ {
+ int lightType = i & LIGHTDEF_LIGHTTYPE_MASK;
+ bool fastLight = !(r_normalMapping->integer || r_specularMapping->integer);
+
+ // skip impossible combos
+ if ((i & LIGHTDEF_USE_PARALLAXMAP) && !r_parallaxMapping->integer)
+ continue;
+
+ if ((i & LIGHTDEF_USE_SHADOWMAP) && (!lightType || !r_sunlightMode->integer))
+ continue;
+
+ attribs = ATTR_POSITION | ATTR_TEXCOORD | ATTR_COLOR | ATTR_NORMAL;
+
+ extradefines[0] = '\0';
+
+ if (r_dlightMode->integer >= 2)
+ Q_strcat(extradefines, 1024, "#define USE_SHADOWMAP\n");
+
+ if (glRefConfig.swizzleNormalmap)
+ Q_strcat(extradefines, 1024, "#define SWIZZLE_NORMALMAP\n");
+
+ if (lightType)
+ {
+ Q_strcat(extradefines, 1024, "#define USE_LIGHT\n");
+
+ if (fastLight)
+ Q_strcat(extradefines, 1024, "#define USE_FAST_LIGHT\n");
+
+ switch (lightType)
+ {
+ case LIGHTDEF_USE_LIGHTMAP:
+ Q_strcat(extradefines, 1024, "#define USE_LIGHTMAP\n");
+ if (r_deluxeMapping->integer && !fastLight)
+ Q_strcat(extradefines, 1024, "#define USE_DELUXEMAP\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 (r_normalMapping->integer)
+ {
+ Q_strcat(extradefines, 1024, "#define USE_NORMALMAP\n");
+
+ attribs |= ATTR_TANGENT;
+
+ if ((i & LIGHTDEF_USE_PARALLAXMAP) && !(i & LIGHTDEF_ENTITY) && r_parallaxMapping->integer)
+ {
+ Q_strcat(extradefines, 1024, "#define USE_PARALLAXMAP\n");
+ if (r_parallaxMapping->integer > 1)
+ Q_strcat(extradefines, 1024, "#define USE_RELIEFMAP\n");
+ }
+ }
+
+ if (r_specularMapping->integer)
+ Q_strcat(extradefines, 1024, "#define USE_SPECULARMAP\n");
+
+ if (r_cubeMapping->integer)
+ Q_strcat(extradefines, 1024, "#define USE_CUBEMAP\n");
+
+ switch (r_glossType->integer)
+ {
+ case 0:
+ default:
+ Q_strcat(extradefines, 1024, "#define GLOSS_IS_GLOSS\n");
+ break;
+ case 1:
+ Q_strcat(extradefines, 1024, "#define GLOSS_IS_SMOOTHNESS\n");
+ break;
+ case 2:
+ Q_strcat(extradefines, 1024, "#define GLOSS_IS_ROUGHNESS\n");
+ break;
+ case 3:
+ Q_strcat(extradefines, 1024, "#define GLOSS_IS_SHININESS\n");
+ break;
+ }
+ }
+
+ if (i & LIGHTDEF_USE_SHADOWMAP)
+ {
+ Q_strcat(extradefines, 1024, "#define USE_SHADOWMAP\n");
+
+ if (r_sunlightMode->integer == 1)
+ Q_strcat(extradefines, 1024, "#define SHADOWMAP_MODULATE\n");
+ else if (r_sunlightMode->integer == 2)
+ Q_strcat(extradefines, 1024, "#define USE_PRIMARY_LIGHT\n");
+ }
+
+ if (i & LIGHTDEF_USE_TCGEN_AND_TCMOD)
+ {
+ Q_strcat(extradefines, 1024, "#define USE_TCGEN\n");
+ Q_strcat(extradefines, 1024, "#define USE_TCMOD\n");
+ }
+
+ if (i & LIGHTDEF_ENTITY)
+ {
+ Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n#define USE_MODELMATRIX\n");
+ attribs |= ATTR_POSITION2 | ATTR_NORMAL2;
+
+ if (r_normalMapping->integer)
+ {
+ attribs |= ATTR_TANGENT2;
+ }
+ }
+
+ if (!GLSL_InitGPUShader(&tr.lightallShader[i], "lightall", attribs, true, extradefines, true, fallbackShader_lightall_vp, fallbackShader_lightall_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load lightall shader!");
+ }
+
+ GLSL_InitUniforms(&tr.lightallShader[i]);
+
+ GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_DIFFUSEMAP, TB_DIFFUSEMAP);
+ GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_LIGHTMAP, TB_LIGHTMAP);
+ GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_NORMALMAP, TB_NORMALMAP);
+ GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_DELUXEMAP, TB_DELUXEMAP);
+ GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_SPECULARMAP, TB_SPECULARMAP);
+ GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_SHADOWMAP, TB_SHADOWMAP);
+ GLSL_SetUniformInt(&tr.lightallShader[i], UNIFORM_CUBEMAP, TB_CUBEMAP);
+
+ 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, true, extradefines, true, fallbackShader_shadowfill_vp, fallbackShader_shadowfill_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load shadowfill shader!");
+ }
+
+ GLSL_InitUniforms(&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, true, extradefines, true, fallbackShader_pshadow_vp, fallbackShader_pshadow_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load pshadow shader!");
+ }
+
+ GLSL_InitUniforms(&tr.pshadowShader);
+
+ GLSL_SetUniformInt(&tr.pshadowShader, UNIFORM_SHADOWMAP, TB_DIFFUSEMAP);
+
+ GLSL_FinishGPUShader(&tr.pshadowShader);
+
+ numEtcShaders++;
+
+
+ attribs = ATTR_POSITION | ATTR_TEXCOORD;
+ extradefines[0] = '\0';
+
+ if (!GLSL_InitGPUShader(&tr.down4xShader, "down4x", attribs, true, extradefines, true, fallbackShader_down4x_vp, fallbackShader_down4x_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load down4x shader!");
+ }
+
+ GLSL_InitUniforms(&tr.down4xShader);
+
+ GLSL_SetUniformInt(&tr.down4xShader, UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP);
+
+ GLSL_FinishGPUShader(&tr.down4xShader);
+
+ numEtcShaders++;
+
+
+ attribs = ATTR_POSITION | ATTR_TEXCOORD;
+ extradefines[0] = '\0';
+
+ if (!GLSL_InitGPUShader(&tr.bokehShader, "bokeh", attribs, true, extradefines, true, fallbackShader_bokeh_vp, fallbackShader_bokeh_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load bokeh shader!");
+ }
+
+ GLSL_InitUniforms(&tr.bokehShader);
+
+ GLSL_SetUniformInt(&tr.bokehShader, UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP);
+
+ GLSL_FinishGPUShader(&tr.bokehShader);
+
+ numEtcShaders++;
+
+
+ attribs = ATTR_POSITION | ATTR_TEXCOORD;
+ extradefines[0] = '\0';
+
+ if (!GLSL_InitGPUShader(&tr.tonemapShader, "tonemap", attribs, true, extradefines, true, fallbackShader_tonemap_vp, fallbackShader_tonemap_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load tonemap shader!");
+ }
+
+ GLSL_InitUniforms(&tr.tonemapShader);
+
+ GLSL_SetUniformInt(&tr.tonemapShader, UNIFORM_TEXTUREMAP, TB_COLORMAP);
+ GLSL_SetUniformInt(&tr.tonemapShader, UNIFORM_LEVELSMAP, TB_LEVELSMAP);
+
+ 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, true, extradefines, true, fallbackShader_calclevels4x_vp, fallbackShader_calclevels4x_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load calclevels4x shader!");
+ }
+
+ GLSL_InitUniforms(&tr.calclevels4xShader[i]);
+
+ GLSL_SetUniformInt(&tr.calclevels4xShader[i], UNIFORM_TEXTUREMAP, TB_DIFFUSEMAP);
+
+ 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");
+
+ if (r_shadowCascadeZFar->integer != 0)
+ Q_strcat(extradefines, 1024, "#define USE_SHADOW_CASCADE\n");
+
+ Q_strcat(extradefines, 1024, va("#define r_shadowMapSize %f\n", r_shadowMapSize->value));
+ Q_strcat(extradefines, 1024, va("#define r_shadowCascadeZFar %f\n", r_shadowCascadeZFar->value));
+
+
+ if (!GLSL_InitGPUShader(&tr.shadowmaskShader, "shadowmask", attribs, true, extradefines, true, fallbackShader_shadowmask_vp, fallbackShader_shadowmask_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load shadowmask shader!");
+ }
+
+ GLSL_InitUniforms(&tr.shadowmaskShader);
+
+ GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP);
+ GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP, TB_SHADOWMAP);
+ GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP2, TB_SHADOWMAP2);
+ GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP3, TB_SHADOWMAP3);
+ GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP4, TB_SHADOWMAP4);
+
+ GLSL_FinishGPUShader(&tr.shadowmaskShader);
+
+ numEtcShaders++;
+
+
+ attribs = ATTR_POSITION | ATTR_TEXCOORD;
+ extradefines[0] = '\0';
+
+ if (!GLSL_InitGPUShader(&tr.ssaoShader, "ssao", attribs, true, extradefines, true, fallbackShader_ssao_vp, fallbackShader_ssao_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load ssao shader!");
+ }
+
+ GLSL_InitUniforms(&tr.ssaoShader);
+
+ GLSL_SetUniformInt(&tr.ssaoShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP);
+
+ GLSL_FinishGPUShader(&tr.ssaoShader);
+
+ numEtcShaders++;
+
+
+ for (i = 0; i < 4; i++)
+ {
+ attribs = ATTR_POSITION | ATTR_TEXCOORD;
+ extradefines[0] = '\0';
+
+ if (i & 1)
+ Q_strcat(extradefines, 1024, "#define USE_VERTICAL_BLUR\n");
+ else
+ Q_strcat(extradefines, 1024, "#define USE_HORIZONTAL_BLUR\n");
+
+ if (!(i & 2))
+ Q_strcat(extradefines, 1024, "#define USE_DEPTH\n");
+
+
+ if (!GLSL_InitGPUShader(&tr.depthBlurShader[i], "depthBlur", attribs, true, extradefines, true, fallbackShader_depthblur_vp, fallbackShader_depthblur_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load depthBlur shader!");
+ }
+
+ GLSL_InitUniforms(&tr.depthBlurShader[i]);
+
+ GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENIMAGEMAP, TB_COLORMAP);
+ GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENDEPTHMAP, TB_LIGHTMAP);
+
+ GLSL_FinishGPUShader(&tr.depthBlurShader[i]);
+
+ numEtcShaders++;
+ }
+
+#if 0
+ attribs = ATTR_POSITION | ATTR_TEXCOORD;
+ extradefines[0] = '\0';
+
+ if (!GLSL_InitGPUShader(&tr.testcubeShader, "testcube", attribs, true, extradefines, true, NULL, NULL))
+ {
+ ri.Error(ERR_FATAL, "Could not load testcube shader!");
+ }
+
+ GLSL_InitUniforms(&tr.testcubeShader);
+
+ GLSL_SetUniformInt(&tr.testcubeShader, UNIFORM_TEXTUREMAP, TB_COLORMAP);
+
+ GLSL_FinishGPUShader(&tr.testcubeShader);
+
+ numEtcShaders++;
+#endif
+
+
+ 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);
+}
+
+void GLSL_ShutdownGPUShaders(void)
+{
+ int i;
+
+ ri.Printf(PRINT_ALL, "------- GLSL_ShutdownGPUShaders -------\n");
+
+ for (i = 0; i < ATTR_INDEX_COUNT; i++)
+ qglDisableVertexAttribArray(i);
+
+ GL_BindNullProgram();
+
+ for ( i = 0; i < GENERICDEF_COUNT; i++)
+ GLSL_DeleteGPUShader(&tr.genericShader[i]);
+
+ GLSL_DeleteGPUShader(&tr.textureColorShader);
+
+ for ( i = 0; i < FOGDEF_COUNT; i++)
+ GLSL_DeleteGPUShader(&tr.fogShader[i]);
+
+ for ( i = 0; i < DLIGHTDEF_COUNT; i++)
+ GLSL_DeleteGPUShader(&tr.dlightShader[i]);
+
+ for ( i = 0; i < LIGHTDEF_COUNT; i++)
+ GLSL_DeleteGPUShader(&tr.lightallShader[i]);
+
+ GLSL_DeleteGPUShader(&tr.shadowmapShader);
+ GLSL_DeleteGPUShader(&tr.pshadowShader);
+ GLSL_DeleteGPUShader(&tr.down4xShader);
+ GLSL_DeleteGPUShader(&tr.bokehShader);
+ GLSL_DeleteGPUShader(&tr.tonemapShader);
+
+ for ( i = 0; i < 2; i++)
+ GLSL_DeleteGPUShader(&tr.calclevels4xShader[i]);
+
+ GLSL_DeleteGPUShader(&tr.shadowmaskShader);
+ GLSL_DeleteGPUShader(&tr.ssaoShader);
+
+ for ( i = 0; i < 4; i++)
+ GLSL_DeleteGPUShader(&tr.depthBlurShader[i]);
+}
+
+
+void GLSL_BindProgram(shaderProgram_t * program)
+{
+ GLuint programObject = program ? program->program : 0;
+ const char *name = program ? program->name : "NULL";
+
+ if(r_logFile->integer)
+ {
+ // don't just call LogComment, or we will get a call to va() every frame!
+ GLimp_LogComment((char*)va("--- GLSL_BindProgram( %s ) ---\n", name));
+ }
+
+ if (GL_UseProgram(programObject))
+ backEnd.pc.c_glslShaderBinds++;
+}
+
+
+shaderProgram_t *GLSL_GetGenericShaderProgram(int stage)
+{
+ shaderStage_t *pStage = tess.xstages[stage];
+ int shaderAttribs = 0;
+
+ if (tess.fogNum && pStage->adjustColorsForFog)
+ {
+ shaderAttribs |= GENERICDEF_USE_FOG;
+ }
+
+ switch (pStage->rgbGen)
+ {
+ case CGEN_LIGHTING_DIFFUSE:
+ shaderAttribs |= GENERICDEF_USE_RGBAGEN;
+ break;
+ default:
+ break;
+ }
+
+ switch (pStage->alphaGen)
+ {
+ case AGEN_LIGHTING_SPECULAR:
+ case AGEN_PORTAL:
+ shaderAttribs |= GENERICDEF_USE_RGBAGEN;
+ break;
+ default:
+ break;
+ }
+
+ if (pStage->bundle[0].tcGen != TCGEN_TEXTURE)
+ {
+ shaderAttribs |= GENERICDEF_USE_TCGEN_AND_TCMOD;
+ }
+
+ if (tess.shader->numDeforms && !ShaderRequiresCPUDeforms(tess.shader))
+ {
+ shaderAttribs |= GENERICDEF_USE_DEFORM_VERTEXES;
+ }
+
+ if (glState.vertexAnimation)
+ {
+ shaderAttribs |= GENERICDEF_USE_VERTEX_ANIMATION;
+ }
+
+ if (pStage->bundle[0].numTexMods)
+ {
+ shaderAttribs |= GENERICDEF_USE_TCGEN_AND_TCMOD;
+ }
+
+ return &tr.genericShader[shaderAttribs];
+}
diff --git a/src/renderergl2/tr_image.cpp b/src/renderergl2/tr_image.cpp
new file mode 100644
index 0000000..5e29ff7
--- /dev/null
+++ b/src/renderergl2/tr_image.cpp
@@ -0,0 +1,3235 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// tr_image.c
+#include "tr_local.h"
+
+#include "tr_dsa.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]];
+ }
+}
+
+struct textureMode_t {
+ const char *name;
+ int minimize, maximize;
+};
+
+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 && !(glt->flags & IMGFLAG_CUBEMAP)) {
+ qglTextureParameterfEXT(glt->texnum, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
+ qglTextureParameterfEXT(glt->texnum, 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 ) {
+ 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];
+ const char *format = "???? ";
+ const 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_RG_RGTC2:
+ format = "RGTC2 ";
+ // 128 bits per 16 pixels, so 1 byte per pixel
+ break;
+ case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
+ format = "DXT1 ";
+ // 64 bits per 16 pixels, so 4 bits per pixel
+ estSize /= 2;
+ break;
+ case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+ format = "DXT1a ";
+ // 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_RGBA16F:
+ format = "RGBA16F";
+ // 8 bytes per pixel
+ estSize *= 8;
+ break;
+ case GL_RGBA16:
+ format = "RGBA16 ";
+ // 8 bytes per pixel
+ estSize *= 8;
+ 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;
+ case GL_DEPTH_COMPONENT16:
+ format = "Depth16";
+ // 2 bytes per pixel
+ estSize *= 2;
+ break;
+ case GL_DEPTH_COMPONENT24:
+ format = "Depth24";
+ // 3 bytes per pixel
+ estSize *= 3;
+ break;
+ case GL_DEPTH_COMPONENT:
+ case GL_DEPTH_COMPONENT32:
+ format = "Depth32";
+ // 4 bytes per pixel
+ estSize *= 4;
+ 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 );
+}
+
+//=======================================================================
+
+/*
+================
+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<outwidth ; i++ ) {
+ p1[i] = 4*(frac>>16);
+ frac += fracstep;
+ }
+ frac = 3*(fracstep>>2);
+ for ( i=0 ; i<outwidth ; i++ ) {
+ p2[i] = 4*(frac>>16);
+ frac += fracstep;
+ }
+
+ for (i=0 ; i<outheight ; i++) {
+ inrow = in + 4*inwidth*(int)((i+0.25)*inheight/outheight);
+ inrow2 = in + 4*inwidth*(int)((i+0.75)*inheight/outheight);
+ for (j=0 ; j<outwidth ; j++) {
+ pix1 = inrow + p1[j];
+ pix2 = inrow + p2[j];
+ pix3 = inrow2 + p1[j];
+ pix4 = inrow2 + p2[j];
+ *out++ = (pix1[0] + pix2[0] + pix3[0] + pix4[0])>>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, bool 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++)
+ {
+ byte result = (inbyte[0] >> 2) + (inbyte[1] >> 1) + (inbyte[2] >> 2);
+ result = result * result / 255; // Make linear
+ *outbyte = result;
+ 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, width - 1);
+ }
+ else
+ {
+ src_x = (src_x + width) % width;
+ }
+
+ 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] = {0}, se[4] = {0}, sh[4] = {0}, si[4] = {0};
+ 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, bool clampToEdge, bool 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, bool only_gamma )
+{
+ if ( only_gamma )
+ {
+ if ( !glConfig.deviceSupportsGamma )
+ {
+ int i, c;
+ byte *p;
+
+ p = in;
+
+ c = inwidth*inheight;
+ for (i=0 ; i<c ; i++, p+=4)
+ {
+ p[0] = s_gammatable[p[0]];
+ p[1] = s_gammatable[p[1]];
+ p[2] = s_gammatable[p[2]];
+ }
+ }
+ }
+ else
+ {
+ int i, c;
+ byte *p;
+
+ p = in;
+
+ c = inwidth*inheight;
+
+ if ( glConfig.deviceSupportsGamma )
+ {
+ for (i=0 ; i<c ; i++, p+=4)
+ {
+ p[0] = s_intensitytable[p[0]];
+ p[1] = s_intensitytable[p[1]];
+ p[2] = s_intensitytable[p[2]];
+ }
+ }
+ else
+ {
+ for (i=0 ; i<c ; i++, p+=4)
+ {
+ p[0] = s_gammatable[s_intensitytable[p[0]]];
+ p[1] = s_gammatable[s_intensitytable[p[1]]];
+ p[2] = s_gammatable[s_intensitytable[p[2]]];
+ }
+ }
+ }
+}
+
+
+/*
+================
+R_MipMapsRGB
+
+Operates in place, quartering the size of the texture
+Colors are gamma correct
+================
+*/
+static void R_MipMapsRGB( byte *in, int inWidth, int inHeight)
+{
+ int x, y, c, stride;
+ const byte *in2;
+ float total;
+ static float downmipSrgbLookup[256];
+ static int downmipSrgbLookupSet = 0;
+ byte *out = in;
+
+ if (!downmipSrgbLookupSet) {
+ for (x = 0; x < 256; x++)
+ downmipSrgbLookup[x] = powf(x / 255.0f, 2.2f) * 0.25f;
+ downmipSrgbLookupSet = 1;
+ }
+
+ if (inWidth == 1 && inHeight == 1)
+ return;
+
+ if (inWidth == 1 || inHeight == 1) {
+ for (x = (inWidth * inHeight) >> 1; x; x--) {
+ for (c = 3; c; c--, in++) {
+ total = (downmipSrgbLookup[*(in)] + downmipSrgbLookup[*(in + 4)]) * 2.0f;
+
+ *out++ = (byte)(powf(total, 1.0f / 2.2f) * 255.0f);
+ }
+ *out++ = (*(in) + *(in + 4)) >> 1; in += 5;
+ }
+
+ return;
+ }
+
+ stride = inWidth * 4;
+ inWidth >>= 1; inHeight >>= 1;
+
+ in2 = in + stride;
+ for (y = inHeight; y; y--, in += stride, in2 += stride) {
+ for (x = inWidth; x; x--) {
+ for (c = 3; c; c--, in++, in2++) {
+ total = downmipSrgbLookup[*(in)] + downmipSrgbLookup[*(in + 4)]
+ + downmipSrgbLookup[*(in2)] + downmipSrgbLookup[*(in2 + 4)];
+
+ *out++ = (byte)(powf(total, 1.0f / 2.2f) * 255.0f);
+ }
+
+ *out++ = (*(in) + *(in + 4) + *(in2) + *(in2 + 4)) >> 2; in += 5, in2 += 5;
+ }
+ }
+}
+
+
+static void R_MipMapNormalHeight (const byte *in, byte *out, int width, int height, bool 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<height ; i++, in+=row) {
+ for (j=0 ; j<width ; j++, out+=4, in+=8) {
+ vec3_t v;
+
+ v[0] = OffsetByteToFloat(in[sx ]);
+ v[1] = OffsetByteToFloat(in[ 1]);
+ v[2] = OffsetByteToFloat(in[ 2]);
+
+ v[0] += OffsetByteToFloat(in[sx +4]);
+ v[1] += OffsetByteToFloat(in[ 5]);
+ v[2] += OffsetByteToFloat(in[ 6]);
+
+ v[0] += OffsetByteToFloat(in[sx+row ]);
+ v[1] += OffsetByteToFloat(in[ row+1]);
+ v[2] += OffsetByteToFloat(in[ row+2]);
+
+ v[0] += OffsetByteToFloat(in[sx+row+4]);
+ v[1] += OffsetByteToFloat(in[ row+5]);
+ v[2] += OffsetByteToFloat(in[ row+6]);
+
+ VectorNormalizeFast(v);
+
+ //v[0] *= 0.25f;
+ //v[1] *= 0.25f;
+ //v[2] = 1.0f - v[0] * v[0] - v[1] * v[1];
+ //v[2] = sqrt(MAX(v[2], 0.0f));
+
+ out[sx] = FloatToOffsetByte(v[0]);
+ out[1 ] = FloatToOffsetByte(v[1]);
+ out[2 ] = FloatToOffsetByte(v[2]);
+ out[sa] = MAX(MAX(in[sa], in[sa+4]), MAX(in[sa+row], in[sa+row+4]));
+ }
+ }
+}
+
+
+/*
+==================
+R_BlendOverTexture
+
+Apply a color blend over a set of pixels
+==================
+*/
+static void R_BlendOverTexture( byte *data, int pixelCount, byte blend[4] ) {
+ int i;
+ int inverseAlpha;
+ int premult[3];
+
+ inverseAlpha = 255 - blend[3];
+ premult[0] = blend[0] * blend[3];
+ premult[1] = blend[1] * blend[3];
+ premult[2] = blend[2] * blend[3];
+
+ for ( i = 0 ; i < pixelCount ; i++, data+=4 ) {
+ data[0] = ( data[0] * inverseAlpha + premult[0] ) >> 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; i<width*height; i++, ptr+=4)
+ {
+ // swap red and alpha
+ swap = ptr[0];
+ ptr[0] = ptr[3];
+ ptr[3] = swap;
+ }
+}
+
+
+/*
+===============
+RawImage_ScaleToPower2
+
+===============
+*/
+static bool RawImage_ScaleToPower2( byte **data, int *inout_width, int *inout_height, imgType_t type, int/*imgFlags_t*/ flags, byte **resampledBuffer)
+{
+ int width = *inout_width;
+ int height = *inout_height;
+ int scaled_width;
+ int scaled_height;
+ bool picmip = flags & IMGFLAG_PICMIP;
+ bool mipmap = flags & IMGFLAG_MIPMAP;
+ bool clampToEdge = flags & IMGFLAG_CLAMPTOEDGE;
+ bool scaled;
+
+ //
+ // convert to exact power of 2 sizes
+ //
+ if (!mipmap)
+ {
+ scaled_width = width;
+ scaled_height = height;
+ }
+ else
+ {
+ scaled_width = NextPowerOfTwo(width);
+ scaled_height = NextPowerOfTwo(height);
+ }
+
+ if ( r_roundImagesDown->integer && 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 = (byte*)ri.Hunk_AllocateTempMemory( finalwidth * finalheight * 4 );
+
+ if (scaled_width != width || scaled_height != height)
+ ResampleTexture (*data, width, height, *resampledBuffer, scaled_width, scaled_height);
+ else
+ Com_Memcpy(*resampledBuffer, *data, width * height * 4);
+
+ 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;
+ }
+ else if ( scaled_width != width || scaled_height != height )
+ {
+ if (data && resampledBuffer)
+ {
+ *resampledBuffer = (byte*)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 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;
+ }
+
+ //
+ // clamp to minimum size
+ //
+ scaled_width = MAX(1, scaled_width);
+ scaled_height = MAX(1, scaled_height);
+
+ scaled = (width != scaled_width) || (height != scaled_height);
+
+ //
+ // rescale texture to new size using existing mipmap functions
+ //
+ if (data)
+ {
+ while (width > scaled_width || height > scaled_height)
+ {
+ if (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT)
+ R_MipMapNormalHeight(*data, *data, width, height, false);
+ else
+ R_MipMapsRGB(*data, width, height);
+
+ width = MAX(1, width >> 1);
+ height = MAX(1, height >> 1);
+ }
+ }
+
+ *inout_width = width;
+ *inout_height = height;
+
+ return scaled;
+}
+
+
+static bool RawImage_HasAlpha(const byte *scan, int numPixels)
+{
+ int i;
+
+ if (!scan)
+ return true;
+
+ for ( i = 0; i < numPixels; i++ )
+ {
+ if ( scan[i*4 + 3] != 255 )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static GLenum RawImage_GetFormat(const byte *data, int numPixels, GLenum picFormat, bool lightMap, imgType_t type, int/*imgFlags_t*/ flags)
+{
+ int samples = 3;
+ GLenum internalFormat = GL_RGB;
+ bool forceNoCompression = (flags & IMGFLAG_NO_COMPRESSION);
+ bool normalmap = (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT);
+
+ if (picFormat != GL_RGBA8)
+ return picFormat;
+
+ if(normalmap)
+ {
+ if ((type == IMGTYPE_NORMALHEIGHT) && RawImage_HasAlpha(data, numPixels) && r_parallaxMapping->integer)
+ {
+ 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;
+ }
+ }
+ else
+ {
+ if (!forceNoCompression && glRefConfig.textureCompression & TCR_RGTC)
+ {
+ internalFormat = GL_COMPRESSED_RG_RGTC2;
+ }
+ 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_RGB5;
+ }
+ else if (r_texturebits->integer == 32)
+ {
+ internalFormat = GL_RGB8;
+ }
+ else
+ {
+ internalFormat = GL_RGB;
+ }
+ }
+ }
+ else if(lightMap)
+ {
+ 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;
+ }
+ }
+ }
+ }
+
+ return internalFormat;
+}
+
+static void CompressMonoBlock(byte outdata[8], const byte indata[16])
+{
+ int hi, lo, diff, bias, outbyte, shift, i;
+ byte *p = outdata;
+
+ hi = lo = indata[0];
+ for (i = 1; i < 16; i++)
+ {
+ hi = MAX(indata[i], hi);
+ lo = MIN(indata[i], lo);
+ }
+
+ *p++ = hi;
+ *p++ = lo;
+
+ diff = hi - lo;
+
+ if (diff == 0)
+ {
+ outbyte = (hi == 255) ? 255 : 0;
+
+ for (i = 0; i < 6; i++)
+ *p++ = outbyte;
+
+ return;
+ }
+
+ bias = diff / 2 - lo * 7;
+ outbyte = shift = 0;
+ for (i = 0; i < 16; i++)
+ {
+ const byte fixIndex[8] = { 1, 7, 6, 5, 4, 3, 2, 0 };
+ byte index = fixIndex[(indata[i] * 7 + bias) / diff];
+
+ outbyte |= index << shift;
+ shift += 3;
+ if (shift >= 8)
+ {
+ *p++ = outbyte & 0xff;
+ shift -= 8;
+ outbyte >>= 8;
+ }
+ }
+}
+
+static void RawImage_UploadToRgtc2Texture(GLuint texture, int miplevel, int x, int y, int width, int height, byte *data)
+{
+ int wBlocks, hBlocks, iy, ix, size;
+ byte *compressedData, *p;
+
+ wBlocks = (width + 3) / 4;
+ hBlocks = (height + 3) / 4;
+ size = wBlocks * hBlocks * 16;
+
+ p = compressedData = (byte*)ri.Hunk_AllocateTempMemory(size);
+ for (iy = 0; iy < height; iy += 4)
+ {
+ int oh = MIN(4, height - iy);
+
+ for (ix = 0; ix < width; ix += 4)
+ {
+ byte workingData[16];
+ int component;
+
+ int ow = MIN(4, width - ix);
+
+ for (component = 0; component < 2; component++)
+ {
+ int ox, oy;
+
+ for (oy = 0; oy < oh; oy++)
+ for (ox = 0; ox < ow; ox++)
+ workingData[oy * 4 + ox] = data[((iy + oy) * width + ix + ox) * 4 + component];
+
+ // dupe data to fill
+ for (oy = 0; oy < 4; oy++)
+ for (ox = (oy < oh) ? ow : 0; ox < 4; ox++)
+ workingData[oy * 4 + ox] = workingData[(oy % oh) * 4 + ox % ow];
+
+ CompressMonoBlock(p, workingData);
+ p += 8;
+ }
+ }
+ }
+
+ // FIXME: Won't work for x/y that aren't multiples of 4.
+ qglCompressedTextureSubImage2DEXT(texture, GL_TEXTURE_2D, miplevel, x, y, width, height, GL_COMPRESSED_RG_RGTC2, size, compressedData);
+
+ ri.Hunk_FreeTempMemory(compressedData);
+}
+
+static int CalculateMipSize(int width, int height, GLenum picFormat)
+{
+ int numBlocks = ((width + 3) / 4) * ((height + 3) / 4);
+ int numPixels = width * height;
+
+ switch (picFormat)
+ {
+ case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
+ case GL_COMPRESSED_RED_RGTC1:
+ case GL_COMPRESSED_SIGNED_RED_RGTC1:
+ return numBlocks * 8;
+
+ case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
+ case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+ case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
+ case GL_COMPRESSED_RG_RGTC2:
+ case GL_COMPRESSED_SIGNED_RG_RGTC2:
+ case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB:
+ case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB:
+ case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB:
+ case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB:
+ return numBlocks * 16;
+
+ case GL_RGBA8:
+ case GL_SRGB8_ALPHA8_EXT:
+ return numPixels * 4;
+
+ case GL_RGBA16:
+ return numPixels * 8;
+
+ default:
+ ri.Printf(PRINT_ALL, "Unsupported texture format %08x\n", picFormat);
+ return 0;
+ }
+
+ return 0;
+}
+
+
+static GLenum PixelDataFormatFromInternalFormat(GLenum internalFormat)
+{
+ switch (internalFormat)
+ {
+ case GL_DEPTH_COMPONENT:
+ case GL_DEPTH_COMPONENT16_ARB:
+ case GL_DEPTH_COMPONENT24_ARB:
+ case GL_DEPTH_COMPONENT32_ARB:
+ return GL_DEPTH_COMPONENT;
+ default:
+ return GL_RGBA;
+ break;
+ }
+}
+
+static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y,
+ int width, int height, GLenum target, GLenum picFormat,
+ int numMips, GLenum internalFormat, imgType_t type, int/*imgFlags_t*/ flags, bool subtexture )
+{
+ GLenum dataFormat, dataType;
+ bool rgtc = internalFormat == GL_COMPRESSED_RG_RGTC2;
+ bool rgba8 = picFormat == GL_RGBA8 || picFormat == GL_SRGB8_ALPHA8_EXT;
+ bool rgba = rgba8 || picFormat == GL_RGBA16;
+ bool mipmap = !!(flags & IMGFLAG_MIPMAP);
+ int size, miplevel;
+ bool lastMip = false;
+
+ dataFormat = PixelDataFormatFromInternalFormat(internalFormat);
+ dataType = picFormat == GL_RGBA16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE;
+
+ miplevel = 0;
+ do
+ {
+ lastMip = (width == 1 && height == 1) || !mipmap;
+ size = CalculateMipSize(width, height, picFormat);
+
+ if (!rgba)
+ {
+ qglCompressedTextureSubImage2DEXT(texture, target, miplevel, x, y, width, height, picFormat, size, data);
+ }
+ else
+ {
+ if (rgba8 && miplevel != 0 && r_colorMipLevels->integer)
+ R_BlendOverTexture((byte *)data, width * height, mipBlendColors[miplevel]);
+
+ if (rgba8 && rgtc)
+ RawImage_UploadToRgtc2Texture(texture, miplevel, x, y, width, height, data);
+ else
+ qglTextureSubImage2DEXT(texture, target, miplevel, x, y, width, height, dataFormat, dataType, data);
+ }
+
+ if (!lastMip && numMips < 2)
+ {
+ if (glRefConfig.framebufferObject)
+ {
+ qglGenerateTextureMipmapEXT(texture, target);
+ break;
+ }
+ else if (rgba8)
+ {
+ if (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT)
+ R_MipMapNormalHeight(data, data, width, height, glRefConfig.swizzleNormalmap);
+ else
+ R_MipMapsRGB(data, width, height);
+ }
+ }
+
+ x >>= 1;
+ y >>= 1;
+ width = MAX(1, width >> 1);
+ height = MAX(1, height >> 1);
+ miplevel++;
+
+ if (numMips > 1)
+ {
+ data += size;
+ numMips--;
+ }
+ }
+ while (!lastMip);
+}
+
+
+/*
+===============
+Upload32
+
+===============
+*/
+static void Upload32(byte *data, int x, int y, int width, int height, GLenum picFormat, int numMips, image_t *image, bool scaled)
+{
+ int i, c;
+ byte *scan;
+
+ imgType_t type = image->type;
+ int/*imgFlags_t*/ flags = image->flags;
+ GLenum internalFormat = image->internalFormat;
+ bool rgba8 = picFormat == GL_RGBA8 || picFormat == GL_SRGB8_ALPHA8_EXT;
+ bool mipmap = !!(flags & IMGFLAG_MIPMAP) && (rgba8 || numMips > 1);
+ bool cubemap = !!(flags & IMGFLAG_CUBEMAP);
+
+ // These operations cannot be performed on non-rgba8 images.
+ if (rgba8 && !cubemap)
+ {
+ c = width*height;
+ scan = data;
+
+ if (type == IMGTYPE_COLORALPHA)
+ {
+ 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);
+ }
+ }
+
+ // This corresponds to what the OpenGL1 renderer does.
+ if (!(flags & IMGFLAG_NOLIGHTSCALE) && (scaled || mipmap))
+ R_LightScaleTexture(data, width, height, !mipmap);
+ }
+
+ if (glRefConfig.swizzleNormalmap && (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT))
+ RawImage_SwizzleRA(data, width, height);
+ }
+
+ if (cubemap)
+ {
+ for (i = 0; i < 6; i++)
+ {
+ int w2 = width, h2 = height;
+ RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, picFormat, numMips, internalFormat, type, flags, false);
+ for (c = numMips; c; c--)
+ {
+ data += CalculateMipSize(w2, h2, picFormat);
+ w2 = MAX(1, w2 >> 1);
+ h2 = MAX(1, h2 >> 1);
+ }
+ }
+ }
+ else
+ {
+ RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_2D, picFormat, numMips, internalFormat, type, flags, false);
+ }
+
+ GL_CheckErrors();
+}
+
+
+/*
+================
+R_CreateImage2
+
+This is the only way any image_t are created
+================
+*/
+image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLenum picFormat, int numMips, imgType_t type, int/*imgFlags_t*/ flags, int internalFormat ) {
+ byte *resampledBuffer = NULL;
+ image_t *image;
+ bool isLightmap = false, scaled = false;
+ long hash;
+ int glWrapClampMode, mipWidth, mipHeight, miplevel;
+ bool rgba8 = picFormat == GL_RGBA8 || picFormat == GL_SRGB8_ALPHA8_EXT;
+ bool mipmap = !!(flags & IMGFLAG_MIPMAP);
+ bool cubemap = !!(flags & IMGFLAG_CUBEMAP);
+ bool picmip = !!(flags & IMGFLAG_PICMIP);
+ bool lastMip;
+ GLenum textureTarget = cubemap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
+ GLenum dataFormat;
+
+ if (strlen(name) >= MAX_QPATH ) {
+ ri.Error (ERR_DROP, "R_CreateImage: \"%s\" is too long", name);
+ }
+ if ( !strncmp( name, "*lightmap", 9 ) ) {
+ isLightmap = true;
+ }
+
+ if ( tr.numImages == MAX_DRAWIMAGES ) {
+ ri.Error( ERR_DROP, "R_CreateImage: MAX_DRAWIMAGES hit");
+ }
+
+ image = tr.images[tr.numImages] = (image_t*)ri.Hunk_Alloc( sizeof( image_t ), h_low );
+ qglGenTextures(1, &image->texnum);
+ 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)
+ internalFormat = RawImage_GetFormat(pic, width * height, picFormat, isLightmap, image->type, image->flags);
+
+ image->internalFormat = internalFormat;
+
+ // Possibly scale image before uploading.
+ // if not rgba8 and uploading an image, skip picmips.
+ if (!cubemap)
+ {
+ if (rgba8)
+ scaled = RawImage_ScaleToPower2(&pic, &width, &height, type, flags, &resampledBuffer);
+ else if (pic && picmip)
+ {
+ for (miplevel = r_picmip->integer; miplevel > 0 && numMips > 1; miplevel--, numMips--)
+ {
+ int size = CalculateMipSize(width, height, picFormat);
+ width = MAX(1, width >> 1);
+ height = MAX(1, height >> 1);
+ pic += size;
+ }
+ }
+ }
+
+ image->uploadWidth = width;
+ image->uploadHeight = height;
+
+ // Allocate texture storage so we don't have to worry about it later.
+ dataFormat = PixelDataFormatFromInternalFormat(internalFormat);
+ mipWidth = width;
+ mipHeight = height;
+ miplevel = 0;
+ do
+ {
+ lastMip = !mipmap || (mipWidth == 1 && mipHeight == 1);
+ if (cubemap)
+ {
+ int i;
+
+ for (i = 0; i < 6; i++)
+ qglTextureImage2DEXT(image->texnum, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, GL_UNSIGNED_BYTE, NULL);
+ }
+ else
+ {
+ qglTextureImage2DEXT(image->texnum, GL_TEXTURE_2D, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, GL_UNSIGNED_BYTE, NULL);
+ }
+
+ mipWidth = MAX(1, mipWidth >> 1);
+ mipHeight = MAX(1, mipHeight >> 1);
+ miplevel++;
+ }
+ while (!lastMip);
+
+ // Upload data.
+ if (pic)
+ Upload32(pic, 0, 0, width, height, picFormat, numMips, image, scaled);
+
+ if (resampledBuffer != NULL)
+ ri.Hunk_FreeTempMemory(resampledBuffer);
+
+ // Set all necessary texture parameters.
+ qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_WRAP_S, glWrapClampMode);
+ qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_WRAP_T, glWrapClampMode);
+
+ if (cubemap)
+ qglTextureParameteriEXT(image->texnum, textureTarget, GL_TEXTURE_WRAP_R, glWrapClampMode);
+
+ if (glConfig.textureFilterAnisotropic && !cubemap)
+ qglTextureParameteriEXT(image->texnum, textureTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT,
+ mipmap ? (GLint)Com_Clamp(1, glConfig.maxAnisotropy, r_ext_max_anisotropy->integer) : 1);
+
+ switch(internalFormat)
+ {
+ case GL_DEPTH_COMPONENT:
+ case GL_DEPTH_COMPONENT16_ARB:
+ case GL_DEPTH_COMPONENT24_ARB:
+ case GL_DEPTH_COMPONENT32_ARB:
+ // Fix for sampling depth buffer on old nVidia cards.
+ // from http://www.idevgames.com/forums/thread-4141-post-34844.html#pid34844
+ qglTextureParameterfEXT(image->texnum, textureTarget, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE);
+ qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ break;
+ default:
+ qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_MIN_FILTER, mipmap ? gl_filter_min : GL_LINEAR);
+ qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_MAG_FILTER, mipmap ? gl_filter_max : GL_LINEAR);
+ break;
+ }
+
+ GL_CheckErrors();
+
+ hash = generateHashValue(name);
+ image->next = hashTable[hash];
+ hashTable[hash] = image;
+
+ return image;
+}
+
+
+/*
+================
+R_CreateImage
+
+Wrapper for R_CreateImage2(), for the old parameters.
+================
+*/
+image_t *R_CreateImage(const char *name, byte *pic, int width, int height,
+ imgType_t type, int/*imgFlags_t*/ flags, int internalFormat)
+{
+ return R_CreateImage2(name, pic, width, height, GL_RGBA8, 0, type, flags, internalFormat);
+}
+
+
+void R_UpdateSubImage( image_t *image, byte *pic, int x, int y, int width, int height, GLenum picFormat )
+{
+ Upload32(pic, x, y, width, height, picFormat, 0, image, false);
+}
+
+//===================================================================
+
+// Prototype for dds loader function which isn't common to both renderers
+void R_LoadDDS(const char *filename, byte **pic, int *width, int *height, GLenum *picFormat, int *numMips);
+
+typedef struct
+{
+ const 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[ ] =
+{
+ { "png", R_LoadPNG },
+ { "tga", R_LoadTGA },
+ { "jpg", R_LoadJPG },
+ { "jpeg", R_LoadJPG },
+ { "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, GLenum *picFormat, int *numMips )
+{
+ bool orgNameFailed = false;
+ int orgLoader = -1;
+ int i;
+ char localName[ MAX_QPATH ];
+ const char *ext;
+ const char *altName;
+
+ *pic = NULL;
+ *width = 0;
+ *height = 0;
+ *picFormat = GL_RGBA8;
+ *numMips = 0;
+
+ Q_strncpyz( localName, name, MAX_QPATH );
+
+ ext = COM_GetExtension( localName );
+
+ // If compressed textures are enabled, try loading a DDS first, it'll load fastest
+ if (r_ext_compressed_textures->integer)
+ {
+ char ddsName[MAX_QPATH];
+
+ COM_StripExtension(name, ddsName, MAX_QPATH);
+ Q_strcat(ddsName, MAX_QPATH, ".dds");
+
+ R_LoadDDS(ddsName, pic, width, height, picFormat, numMips);
+
+ // If loaded, we're done.
+ if (*pic)
+ return;
+ }
+
+ 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 = true;
+ 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, int/*imgFlags_t*/ flags )
+{
+ image_t *image;
+ int width, height;
+ byte *pic;
+ GLenum picFormat;
+ int picNumMips;
+ long hash;
+ int/*imgFlags_t*/ checkFlagsTrue, checkFlagsFalse;
+
+ 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, &picFormat, &picNumMips );
+ if ( pic == NULL ) {
+ return NULL;
+ }
+
+ checkFlagsTrue = IMGFLAG_PICMIP | IMGFLAG_MIPMAP | IMGFLAG_GENNORMALMAP;
+ checkFlagsFalse = IMGFLAG_CUBEMAP;
+ if (r_normalMapping->integer && (picFormat == GL_RGBA8) && (type == IMGTYPE_COLORALPHA) &&
+ ((flags & checkFlagsTrue) == checkFlagsTrue) && !(flags & checkFlagsFalse))
+ {
+ char normalName[MAX_QPATH];
+ image_t *normalImage;
+ int normalWidth, normalHeight;
+ int/*imgFlags_t*/ normalFlags;
+
+ normalFlags = (flags & ~IMGFLAG_GENNORMALMAP) | 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 = (byte*)ri.Malloc(width * height * 4);
+ RGBAtoNormal(pic, normalPic, width, height, flags & IMGFLAG_CLAMPTOEDGE);
+
+#if 1
+ // 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);
+#else
+ // Blur original image's luma to work with the normal map
+ {
+ byte *blurPic;
+
+ RGBAtoYCoCgA(pic, pic, width, height);
+ blurPic = ri.Malloc(width * height);
+
+ for (y = 1; y < height - 1; y++)
+ {
+ byte *picbyte = pic + y * width * 4;
+ byte *blurbyte = blurPic + y * width;
+
+ picbyte += 4;
+ blurbyte += 1;
+
+ for (x = 1; x < width - 1; x++)
+ {
+ int result;
+
+ result = *(picbyte - (width + 1) * 4) + *(picbyte - width * 4) + *(picbyte - (width - 1) * 4) +
+ *(picbyte - 1 * 4) + *(picbyte ) + *(picbyte + 1 * 4) +
+ *(picbyte + (width - 1) * 4) + *(picbyte + width * 4) + *(picbyte + (width + 1) * 4);
+
+ result /= 9;
+
+ *blurbyte = result;
+ picbyte += 4;
+ blurbyte += 1;
+ }
+ }
+
+ // FIXME: do borders
+
+ for (y = 1; y < height - 1; y++)
+ {
+ byte *picbyte = pic + y * width * 4;
+ byte *blurbyte = blurPic + y * width;
+
+ picbyte += 4;
+ blurbyte += 1;
+
+ for (x = 1; x < width - 1; x++)
+ {
+ picbyte[0] = *blurbyte;
+ picbyte += 4;
+ blurbyte += 1;
+ }
+ }
+
+ ri.Free(blurPic);
+
+ YCoCgAtoRGBA(pic, pic, width, height);
+ }
+#endif
+
+ R_CreateImage( normalName, normalPic, normalWidth, normalHeight, IMGTYPE_NORMAL, normalFlags, 0 );
+ ri.Free( normalPic );
+ }
+ }
+
+ // force mipmaps off if image is compressed but doesn't have enough mips
+ if ((flags & IMGFLAG_MIPMAP) && picFormat != GL_RGBA8 && picFormat != GL_SRGB8_ALPHA8_EXT)
+ {
+ int wh = MAX(width, height);
+ int neededMips = 0;
+ while (wh)
+ {
+ neededMips++;
+ wh >>= 1;
+ }
+ if (neededMips > picNumMips)
+ flags &= ~IMGFLAG_MIPMAP;
+ }
+
+ image = R_CreateImage2( ( char * ) name, pic, width, height, picFormat, picNumMips, 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<DLIGHT_SIZE ; x++) {
+ for (y=0 ; y<DLIGHT_SIZE ; y++) {
+ float d;
+
+ d = ( DLIGHT_SIZE/2 - 0.5f - x ) * ( DLIGHT_SIZE/2 - 0.5f - x ) +
+ ( DLIGHT_SIZE/2 - 0.5f - y ) * ( DLIGHT_SIZE/2 - 0.5f - y );
+ b = 4000 / d;
+ if (b > 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;
+
+ data = (byte*)ri.Hunk_AllocateTempMemory( FOG_S * FOG_T * 4 );
+
+ // S is distance, T is depth
+ for (x=0 ; x<FOG_S ; x++) {
+ for (y=0 ; y<FOG_T ; y++) {
+ d = R_FogFactor( ( x + 0.5f ) / FOG_S, ( y + 0.5f ) / FOG_T );
+
+ data[(y*FOG_S+x)*4+0] =
+ data[(y*FOG_S+x)*4+1] =
+ data[(y*FOG_S+x)*4+2] = 255;
+ data[(y*FOG_S+x)*4+3] = 255*d;
+ }
+ }
+ tr.fogImage = R_CreateImage("*fog", (byte *)data, FOG_S, FOG_T, IMGTYPE_COLORALPHA, IMGFLAG_CLAMPTOEDGE, 0 );
+ ri.Hunk_FreeTempMemory( data );
+}
+
+/*
+==================
+R_CreateDefaultImage
+==================
+*/
+#define DEFAULT_SIZE 16
+static void R_CreateDefaultImage( void ) {
+ int x;
+ byte data[DEFAULT_SIZE][DEFAULT_SIZE][4];
+
+ // the default image will be a box, to allow you to see the mapping coordinates
+ Com_Memset( data, 32, sizeof( data ) );
+ for ( x = 0 ; x < DEFAULT_SIZE ; x++ ) {
+ data[0][x][0] =
+ data[0][x][1] =
+ data[0][x][2] =
+ data[0][x][3] = 255;
+
+ data[x][0][0] =
+ data[x][0][1] =
+ data[x][0][2] =
+ data[x][0][3] = 255;
+
+ data[DEFAULT_SIZE-1][x][0] =
+ data[DEFAULT_SIZE-1][x][1] =
+ data[DEFAULT_SIZE-1][x][2] =
+ data[DEFAULT_SIZE-1][x][3] = 255;
+
+ data[x][DEFAULT_SIZE-1][0] =
+ data[x][DEFAULT_SIZE-1][1] =
+ data[x][DEFAULT_SIZE-1][2] =
+ data[x][DEFAULT_SIZE-1][3] = 255;
+ }
+ tr.defaultImage = R_CreateImage("*default", (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_MIPMAP, 0);
+}
+
+/*
+==================
+R_CreateBuiltinImages
+==================
+*/
+void R_CreateBuiltinImages( void ) {
+ int x,y;
+ byte data[DEFAULT_SIZE][DEFAULT_SIZE][4];
+
+ R_CreateDefaultImage();
+
+ // we use a solid white image instead of disabling texturing
+ Com_Memset( data, 255, sizeof( data ) );
+ tr.whiteImage = R_CreateImage("*white", (byte *)data, 8, 8, IMGTYPE_COLORALPHA, IMGFLAG_NONE, 0);
+
+ if (r_dlightMode->integer >= 2)
+ {
+ for( x = 0; x < MAX_DLIGHTS; x++)
+ {
+ tr.shadowCubemaps[x] = R_CreateImage(va("*shadowcubemap%i", x), NULL, PSHADOW_MAP_SIZE, PSHADOW_MAP_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 ; x<DEFAULT_SIZE ; x++) {
+ for (y=0 ; y<DEFAULT_SIZE ; y++) {
+ data[y][x][0] =
+ data[y][x][1] =
+ data[y][x][2] = tr.identityLightByte;
+ data[y][x][3] = 255;
+ }
+ }
+
+ tr.identityLightImage = R_CreateImage("*identityLight", (byte *)data, 8, 8, IMGTYPE_COLORALPHA, IMGFLAG_NONE, 0);
+
+
+ for(x=0;x<32;x++) {
+ // scratchimage is usually used for cinematic drawing
+ tr.scratchImage[x] = R_CreateImage("*scratch", (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_PICMIP | IMGFLAG_CLAMPTOEDGE, 0);
+ }
+
+ R_CreateDlightImage();
+ R_CreateFogImage();
+
+ if (glRefConfig.framebufferObject)
+ {
+ int width, height, hdrFormat, rgbFormat;
+
+ width = glConfig.vidWidth;
+ height = glConfig.vidHeight;
+
+ hdrFormat = GL_RGBA8;
+ if (r_hdr->integer && glRefConfig.textureFloat)
+ hdrFormat = GL_RGBA16F_ARB;
+
+ rgbFormat = GL_RGBA8;
+
+ tr.renderImage = R_CreateImage("_render", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat);
+
+ if (r_shadowBlur->integer)
+ tr.screenScratchImage = R_CreateImage("screenScratch", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, rgbFormat);
+
+ //if (r_shadowBlur->integer || r_ssao->integer)
+ // tr.hdrDepthImage = R_CreateImage("*hdrDepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_INTENSITY32F_ARB);
+ if (r_shadowBlur->integer || r_ssao->integer)
+ tr.hdrDepthImage = R_CreateImage("*hdrDepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_R32F);
+
+ if (r_drawSunRays->integer)
+ tr.sunRaysImage = R_CreateImage("*sunRays", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, rgbFormat);
+
+ 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);
+
+ {
+ byte *p;
+
+ data[0][0][0] = 0;
+ data[0][0][1] = 0.45f * 255;
+ data[0][0][2] = 255;
+ data[0][0][3] = 255;
+ p = (byte*)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);
+ }
+
+ if (r_ssao->integer)
+ {
+ tr.screenSsaoImage = R_CreateImage("*screenSsao", NULL, width / 2, height / 2, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8);
+ }
+
+ 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_DEPTH_COMPONENT24);
+ //qglTextureParameterfEXT(tr.pshadowMaps[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
+ //qglTextureParameterfEXT(tr.pshadowMaps[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
+ }
+
+ if (r_sunlightMode->integer)
+ {
+ for ( x = 0; x < 4; 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);
+ qglTextureParameterfEXT(tr.sunShadowDepthImage[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
+ qglTextureParameterfEXT(tr.sunShadowDepthImage[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
+ }
+
+ tr.screenShadowImage = R_CreateImage("*screenShadow", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8);
+ }
+
+ if (r_cubeMapping->integer)
+ {
+ tr.renderCubeImage = R_CreateImage("*renderCube", NULL, r_cubemapSize->integer, r_cubemapSize->integer, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE | IMGFLAG_MIPMAP | IMGFLAG_CUBEMAP, rgbFormat);
+ }
+ }
+}
+
+
+/*
+===============
+R_SetColorMappings
+===============
+*/
+void R_SetColorMappings( void ) {
+ int i, j;
+ float g;
+ int inf;
+
+ // setup the overbright lighting
+ tr.overbrightBits = r_overBrightBits->integer;
+
+ // allow 2 overbright bits
+ if ( tr.overbrightBits > 2 ) {
+ tr.overbrightBits = 2;
+ } else if ( tr.overbrightBits < 0 ) {
+ tr.overbrightBits = 0;
+ }
+
+ // don't allow more overbright bits than map overbright bits
+ if ( tr.overbrightBits > r_mapOverBrightBits->integer ) {
+ tr.overbrightBits = r_mapOverBrightBits->integer;
+ }
+
+ 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;
+
+ for ( i = 0; i < 256; i++ ) {
+ if ( g == 1 ) {
+ inf = i;
+ } else {
+ inf = 255 * pow ( i/255.0f, 1.0f / g ) + 0.5f;
+ }
+
+ 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; i<tr.numImages ; i++ ) {
+ qglDeleteTextures( 1, &tr.images[i]->texnum );
+ }
+ Com_Memset( tr.images, 0, sizeof( tr.images ) );
+
+ tr.numImages = 0;
+
+ GL_BindNullTextures();
+}
+
+/*
+============================================================================
+
+SKINS
+
+============================================================================
+*/
+
+/*
+==================
+CommaParse
+
+This is unfortunate, but the skin files aren't
+compatable with our normal parsing rules.
+==================
+*/
+static const 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] == '/' )
+ {
+ data += 2;
+ while (*data && *data != '\n') {
+ data++;
+ }
+ }
+ // skip /* */ comments
+ else if ( c=='/' && data[1] == '*' )
+ {
+ data += 2;
+ 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 - 1)
+ {
+ com_token[len] = c;
+ len++;
+ }
+ }
+ }
+
+ // parse a regular word
+ do
+ {
+ if (len < MAX_TOKEN_CHARS - 1)
+ {
+ com_token[len] = c;
+ len++;
+ }
+ data++;
+ c = *data;
+ } while (c>32 && c != ',' );
+
+ com_token[len] = 0;
+
+ *data_p = ( char * ) data;
+ return com_token;
+}
+
+
+/*
+===============
+RE_RegisterSkin
+
+===============
+*/
+qhandle_t RE_RegisterSkin( const char *name ) {
+ skinSurface_t parseSurfaces[MAX_SKIN_SURFACES];
+ qhandle_t hSkin;
+ skin_t *skin;
+ skinSurface_t *surf;
+ union {
+ char *c;
+ void *v;
+ } text;
+ char *text_p;
+ const 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 = (skin_t*)ri.Hunk_Alloc( sizeof( skin_t ), h_low );
+ tr.skins[hSkin] = skin;
+ Q_strncpyz( skin->name, name, sizeof( skin->name ) );
+ skin->numSurfaces = 0;
+
+ R_IssuePendingRenderCommands();
+
+ // If not a .skin file, load as a single shader
+ if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) {
+ skin->numSurfaces = 1;
+ skin->surfaces = (skinSurface_t*)ri.Hunk_Alloc( sizeof( skinSurface_t ), h_low );
+ skin->surfaces[0].shader = R_FindShader( name, LIGHTMAP_NONE, true );
+ return hSkin;
+ }
+
+ // load and parse the skin file
+ ri.FS_ReadFile( name, &text.v );
+ if ( !text.c ) {
+ return 0;
+ }
+
+ int totalSurfaces = 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 );
+
+ if ( skin->numSurfaces < MAX_SKIN_SURFACES ) {
+ surf = &parseSurfaces[skin->numSurfaces];
+ Q_strncpyz( surf->name, surfName, sizeof( surf->name ) );
+ surf->shader = R_FindShader( token, LIGHTMAP_NONE, true );
+ skin->numSurfaces++;
+ }
+
+ totalSurfaces++;
+ }
+
+ ri.FS_FreeFile( text.v );
+
+ if ( totalSurfaces > MAX_SKIN_SURFACES ) {
+ ri.Printf( PRINT_WARNING, "WARNING: Ignoring excess surfaces (found %d, max is %d) in skin '%s'!\n",
+ totalSurfaces, MAX_SKIN_SURFACES, name );
+ }
+
+ // never let a skin have 0 shaders
+ if ( skin->numSurfaces == 0 ) {
+ return 0; // use default skin
+ }
+
+ // copy surfaces to skin
+ skin->surfaces = (skinSurface_t*)ri.Hunk_Alloc( skin->numSurfaces * sizeof( skinSurface_t ), h_low );
+ memcpy( skin->surfaces, parseSurfaces, skin->numSurfaces * sizeof( skinSurface_t ) );
+
+ 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] = (skin_t*)ri.Hunk_Alloc( sizeof( skin_t ), h_low );
+ Q_strncpyz( skin->name, "<default skin>", sizeof( skin->name ) );
+ skin->numSurfaces = 1;
+ skin->surfaces = (skinSurface_t*)ri.Hunk_Alloc( sizeof( skinSurface_t ), 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 (%d surfaces)\n", i, skin->name, skin->numSurfaces );
+ 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/renderergl2/tr_image_dds.cpp b/src/renderergl2/tr_image_dds.cpp
new file mode 100644
index 0000000..7c52ffb
--- /dev/null
+++ b/src/renderergl2/tr_image_dds.cpp
@@ -0,0 +1,499 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+ 2015 James Canete
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "renderercommon/tr_common.h"
+
+typedef unsigned int ui32_t;
+
+typedef struct ddsHeader_s
+{
+ ui32_t headerSize;
+ ui32_t flags;
+ ui32_t height;
+ ui32_t width;
+ ui32_t pitchOrFirstMipSize;
+ ui32_t volumeDepth;
+ ui32_t numMips;
+ ui32_t reserved1[11];
+ ui32_t always_0x00000020;
+ ui32_t pixelFormatFlags;
+ ui32_t fourCC;
+ ui32_t rgbBitCount;
+ ui32_t rBitMask;
+ ui32_t gBitMask;
+ ui32_t bBitMask;
+ ui32_t aBitMask;
+ ui32_t caps;
+ ui32_t caps2;
+ ui32_t caps3;
+ ui32_t caps4;
+ ui32_t reserved2;
+}
+ddsHeader_t;
+
+// flags:
+#define _DDSFLAGS_REQUIRED 0x001007
+#define _DDSFLAGS_PITCH 0x8
+#define _DDSFLAGS_MIPMAPCOUNT 0x20000
+#define _DDSFLAGS_FIRSTMIPSIZE 0x80000
+#define _DDSFLAGS_VOLUMEDEPTH 0x800000
+
+// pixelFormatFlags:
+#define DDSPF_ALPHAPIXELS 0x1
+#define DDSPF_ALPHA 0x2
+#define DDSPF_FOURCC 0x4
+#define DDSPF_RGB 0x40
+#define DDSPF_YUV 0x200
+#define DDSPF_LUMINANCE 0x20000
+
+// caps:
+#define DDSCAPS_COMPLEX 0x8
+#define DDSCAPS_MIPMAP 0x400000
+#define DDSCAPS_REQUIRED 0x1000
+
+// caps2:
+#define DDSCAPS2_CUBEMAP 0xFE00
+#define DDSCAPS2_VOLUME 0x200000
+
+typedef struct ddsHeaderDxt10_s
+{
+ ui32_t dxgiFormat;
+ ui32_t dimensions;
+ ui32_t miscFlags;
+ ui32_t arraySize;
+ ui32_t miscFlags2;
+}
+ddsHeaderDxt10_t;
+
+// dxgiFormat
+// from http://msdn.microsoft.com/en-us/library/windows/desktop/bb173059%28v=vs.85%29.aspx
+typedef enum DXGI_FORMAT {
+ DXGI_FORMAT_UNKNOWN = 0,
+ DXGI_FORMAT_R32G32B32A32_TYPELESS = 1,
+ DXGI_FORMAT_R32G32B32A32_FLOAT = 2,
+ DXGI_FORMAT_R32G32B32A32_UINT = 3,
+ DXGI_FORMAT_R32G32B32A32_SINT = 4,
+ DXGI_FORMAT_R32G32B32_TYPELESS = 5,
+ DXGI_FORMAT_R32G32B32_FLOAT = 6,
+ DXGI_FORMAT_R32G32B32_UINT = 7,
+ DXGI_FORMAT_R32G32B32_SINT = 8,
+ DXGI_FORMAT_R16G16B16A16_TYPELESS = 9,
+ DXGI_FORMAT_R16G16B16A16_FLOAT = 10,
+ DXGI_FORMAT_R16G16B16A16_UNORM = 11,
+ DXGI_FORMAT_R16G16B16A16_UINT = 12,
+ DXGI_FORMAT_R16G16B16A16_SNORM = 13,
+ DXGI_FORMAT_R16G16B16A16_SINT = 14,
+ DXGI_FORMAT_R32G32_TYPELESS = 15,
+ DXGI_FORMAT_R32G32_FLOAT = 16,
+ DXGI_FORMAT_R32G32_UINT = 17,
+ DXGI_FORMAT_R32G32_SINT = 18,
+ DXGI_FORMAT_R32G8X24_TYPELESS = 19,
+ DXGI_FORMAT_D32_FLOAT_S8X24_UINT = 20,
+ DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS = 21,
+ DXGI_FORMAT_X32_TYPELESS_G8X24_UINT = 22,
+ DXGI_FORMAT_R10G10B10A2_TYPELESS = 23,
+ DXGI_FORMAT_R10G10B10A2_UNORM = 24,
+ DXGI_FORMAT_R10G10B10A2_UINT = 25,
+ DXGI_FORMAT_R11G11B10_FLOAT = 26,
+ DXGI_FORMAT_R8G8B8A8_TYPELESS = 27,
+ DXGI_FORMAT_R8G8B8A8_UNORM = 28,
+ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29,
+ DXGI_FORMAT_R8G8B8A8_UINT = 30,
+ DXGI_FORMAT_R8G8B8A8_SNORM = 31,
+ DXGI_FORMAT_R8G8B8A8_SINT = 32,
+ DXGI_FORMAT_R16G16_TYPELESS = 33,
+ DXGI_FORMAT_R16G16_FLOAT = 34,
+ DXGI_FORMAT_R16G16_UNORM = 35,
+ DXGI_FORMAT_R16G16_UINT = 36,
+ DXGI_FORMAT_R16G16_SNORM = 37,
+ DXGI_FORMAT_R16G16_SINT = 38,
+ DXGI_FORMAT_R32_TYPELESS = 39,
+ DXGI_FORMAT_D32_FLOAT = 40,
+ DXGI_FORMAT_R32_FLOAT = 41,
+ DXGI_FORMAT_R32_UINT = 42,
+ DXGI_FORMAT_R32_SINT = 43,
+ DXGI_FORMAT_R24G8_TYPELESS = 44,
+ DXGI_FORMAT_D24_UNORM_S8_UINT = 45,
+ DXGI_FORMAT_R24_UNORM_X8_TYPELESS = 46,
+ DXGI_FORMAT_X24_TYPELESS_G8_UINT = 47,
+ DXGI_FORMAT_R8G8_TYPELESS = 48,
+ DXGI_FORMAT_R8G8_UNORM = 49,
+ DXGI_FORMAT_R8G8_UINT = 50,
+ DXGI_FORMAT_R8G8_SNORM = 51,
+ DXGI_FORMAT_R8G8_SINT = 52,
+ DXGI_FORMAT_R16_TYPELESS = 53,
+ DXGI_FORMAT_R16_FLOAT = 54,
+ DXGI_FORMAT_D16_UNORM = 55,
+ DXGI_FORMAT_R16_UNORM = 56,
+ DXGI_FORMAT_R16_UINT = 57,
+ DXGI_FORMAT_R16_SNORM = 58,
+ DXGI_FORMAT_R16_SINT = 59,
+ DXGI_FORMAT_R8_TYPELESS = 60,
+ DXGI_FORMAT_R8_UNORM = 61,
+ DXGI_FORMAT_R8_UINT = 62,
+ DXGI_FORMAT_R8_SNORM = 63,
+ DXGI_FORMAT_R8_SINT = 64,
+ DXGI_FORMAT_A8_UNORM = 65,
+ DXGI_FORMAT_R1_UNORM = 66,
+ DXGI_FORMAT_R9G9B9E5_SHAREDEXP = 67,
+ DXGI_FORMAT_R8G8_B8G8_UNORM = 68,
+ DXGI_FORMAT_G8R8_G8B8_UNORM = 69,
+ DXGI_FORMAT_BC1_TYPELESS = 70,
+ DXGI_FORMAT_BC1_UNORM = 71,
+ DXGI_FORMAT_BC1_UNORM_SRGB = 72,
+ DXGI_FORMAT_BC2_TYPELESS = 73,
+ DXGI_FORMAT_BC2_UNORM = 74,
+ DXGI_FORMAT_BC2_UNORM_SRGB = 75,
+ DXGI_FORMAT_BC3_TYPELESS = 76,
+ DXGI_FORMAT_BC3_UNORM = 77,
+ DXGI_FORMAT_BC3_UNORM_SRGB = 78,
+ DXGI_FORMAT_BC4_TYPELESS = 79,
+ DXGI_FORMAT_BC4_UNORM = 80,
+ DXGI_FORMAT_BC4_SNORM = 81,
+ DXGI_FORMAT_BC5_TYPELESS = 82,
+ DXGI_FORMAT_BC5_UNORM = 83,
+ DXGI_FORMAT_BC5_SNORM = 84,
+ DXGI_FORMAT_B5G6R5_UNORM = 85,
+ DXGI_FORMAT_B5G5R5A1_UNORM = 86,
+ DXGI_FORMAT_B8G8R8A8_UNORM = 87,
+ DXGI_FORMAT_B8G8R8X8_UNORM = 88,
+ DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM = 89,
+ DXGI_FORMAT_B8G8R8A8_TYPELESS = 90,
+ DXGI_FORMAT_B8G8R8A8_UNORM_SRGB = 91,
+ DXGI_FORMAT_B8G8R8X8_TYPELESS = 92,
+ DXGI_FORMAT_B8G8R8X8_UNORM_SRGB = 93,
+ DXGI_FORMAT_BC6H_TYPELESS = 94,
+ DXGI_FORMAT_BC6H_UF16 = 95,
+ DXGI_FORMAT_BC6H_SF16 = 96,
+ DXGI_FORMAT_BC7_TYPELESS = 97,
+ DXGI_FORMAT_BC7_UNORM = 98,
+ DXGI_FORMAT_BC7_UNORM_SRGB = 99,
+ DXGI_FORMAT_AYUV = 100,
+ DXGI_FORMAT_Y410 = 101,
+ DXGI_FORMAT_Y416 = 102,
+ DXGI_FORMAT_NV12 = 103,
+ DXGI_FORMAT_P010 = 104,
+ DXGI_FORMAT_P016 = 105,
+ DXGI_FORMAT_420_OPAQUE = 106,
+ DXGI_FORMAT_YUY2 = 107,
+ DXGI_FORMAT_Y210 = 108,
+ DXGI_FORMAT_Y216 = 109,
+ DXGI_FORMAT_NV11 = 110,
+ DXGI_FORMAT_AI44 = 111,
+ DXGI_FORMAT_IA44 = 112,
+ DXGI_FORMAT_P8 = 113,
+ DXGI_FORMAT_A8P8 = 114,
+ DXGI_FORMAT_B4G4R4A4_UNORM = 115,
+ DXGI_FORMAT_FORCE_UINT = 0xffffffffUL
+} DXGI_FORMAT;
+
+#define EncodeFourCC(x) ((((ui32_t)((x)[0])) ) | \
+ (((ui32_t)((x)[1])) << 8 ) | \
+ (((ui32_t)((x)[2])) << 16) | \
+ (((ui32_t)((x)[3])) << 24) )
+
+
+void R_LoadDDS ( const char *filename, byte **pic, int *width, int *height, GLenum *picFormat, int *numMips )
+{
+ union {
+ byte *b;
+ void *v;
+ } buffer;
+ int len;
+ ddsHeader_t *ddsHeader = NULL;
+ ddsHeaderDxt10_t *ddsHeaderDxt10 = NULL;
+ byte *data;
+
+ if (!picFormat)
+ {
+ ri.Printf(PRINT_ERROR, "R_LoadDDS() called without picFormat parameter!");
+ return;
+ }
+
+ if (width)
+ *width = 0;
+ if (height)
+ *height = 0;
+ if (picFormat)
+ *picFormat = GL_RGBA8;
+ if (numMips)
+ *numMips = 1;
+
+ *pic = NULL;
+
+ //
+ // load the file
+ //
+ len = ri.FS_ReadFile( ( char * ) filename, &buffer.v);
+ if (!buffer.b || len < 0) {
+ return;
+ }
+
+ //
+ // reject files that are too small to hold even a header
+ //
+ if (len < 4 + sizeof(*ddsHeader))
+ {
+ ri.Printf(PRINT_ALL, "File %s is too small to be a DDS file.\n", filename);
+ ri.FS_FreeFile(buffer.v);
+ return;
+ }
+
+ //
+ // reject files that don't start with "DDS "
+ //
+ if (*((ui32_t *)(buffer.b)) != EncodeFourCC("DDS "))
+ {
+ ri.Printf(PRINT_ALL, "File %s is not a DDS file.\n", filename);
+ ri.FS_FreeFile(buffer.v);
+ return;
+ }
+
+ //
+ // parse header and dx10 header if available
+ //
+ ddsHeader = (ddsHeader_t *)(buffer.b + 4);
+ if ((ddsHeader->pixelFormatFlags & DDSPF_FOURCC) && ddsHeader->fourCC == EncodeFourCC("DX10"))
+ {
+ if (len < 4 + sizeof(*ddsHeader) + sizeof(*ddsHeaderDxt10))
+ {
+ ri.Printf(PRINT_ALL, "File %s indicates a DX10 header it is too small to contain.\n", filename);
+ ri.FS_FreeFile(buffer.v);
+ return;
+ }
+
+ ddsHeaderDxt10 = (ddsHeaderDxt10_t *)(buffer.b + 4 + sizeof(ddsHeader_t));
+ data = buffer.b + 4 + sizeof(*ddsHeader) + sizeof(*ddsHeaderDxt10);
+ len -= 4 + sizeof(*ddsHeader) + sizeof(*ddsHeaderDxt10);
+ }
+ else
+ {
+ data = buffer.b + 4 + sizeof(*ddsHeader);
+ len -= 4 + sizeof(*ddsHeader);
+ }
+
+ if (width)
+ *width = ddsHeader->width;
+ if (height)
+ *height = ddsHeader->height;
+
+ if (numMips)
+ {
+ if (ddsHeader->flags & _DDSFLAGS_MIPMAPCOUNT)
+ *numMips = ddsHeader->numMips;
+ else
+ *numMips = 1;
+ }
+
+ // FIXME: handle cube map
+ //if ((ddsHeader->caps2 & DDSCAPS2_CUBEMAP) == DDSCAPS2_CUBEMAP)
+
+ //
+ // Convert DXGI format/FourCC into OpenGL format
+ //
+ if (ddsHeaderDxt10)
+ {
+ switch (ddsHeaderDxt10->dxgiFormat)
+ {
+ case DXGI_FORMAT_BC1_TYPELESS:
+ case DXGI_FORMAT_BC1_UNORM:
+ // FIXME: check for GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
+ *picFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
+ break;
+
+ case DXGI_FORMAT_BC1_UNORM_SRGB:
+ // FIXME: check for GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT
+ *picFormat = GL_COMPRESSED_SRGB_S3TC_DXT1_EXT;
+ break;
+
+ case DXGI_FORMAT_BC2_TYPELESS:
+ case DXGI_FORMAT_BC2_UNORM:
+ *picFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+ break;
+
+ case DXGI_FORMAT_BC2_UNORM_SRGB:
+ *picFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
+ break;
+
+ case DXGI_FORMAT_BC3_TYPELESS:
+ case DXGI_FORMAT_BC3_UNORM:
+ *picFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+ break;
+
+ case DXGI_FORMAT_BC3_UNORM_SRGB:
+ *picFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
+ break;
+
+ case DXGI_FORMAT_BC4_TYPELESS:
+ case DXGI_FORMAT_BC4_UNORM:
+ *picFormat = GL_COMPRESSED_RED_RGTC1;
+ break;
+
+ case DXGI_FORMAT_BC4_SNORM:
+ *picFormat = GL_COMPRESSED_SIGNED_RED_RGTC1;
+ break;
+
+ case DXGI_FORMAT_BC5_TYPELESS:
+ case DXGI_FORMAT_BC5_UNORM:
+ *picFormat = GL_COMPRESSED_RG_RGTC2;
+ break;
+
+ case DXGI_FORMAT_BC5_SNORM:
+ *picFormat = GL_COMPRESSED_SIGNED_RG_RGTC2;
+ break;
+
+ case DXGI_FORMAT_BC6H_TYPELESS:
+ case DXGI_FORMAT_BC6H_UF16:
+ *picFormat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB;
+ break;
+
+ case DXGI_FORMAT_BC6H_SF16:
+ *picFormat = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB;
+ break;
+
+ case DXGI_FORMAT_BC7_TYPELESS:
+ case DXGI_FORMAT_BC7_UNORM:
+ *picFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB;
+ break;
+
+ case DXGI_FORMAT_BC7_UNORM_SRGB:
+ *picFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB;
+ break;
+
+ case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
+ *picFormat = GL_SRGB8_ALPHA8_EXT;
+ break;
+
+ case DXGI_FORMAT_R8G8B8A8_UNORM:
+ case DXGI_FORMAT_R8G8B8A8_SNORM:
+ *picFormat = GL_RGBA8;
+ break;
+
+ default:
+ ri.Printf(PRINT_ALL, "DDS File %s has unsupported DXGI format %d.", filename, ddsHeaderDxt10->dxgiFormat);
+ ri.FS_FreeFile(buffer.v);
+ return;
+ break;
+ }
+ }
+ else
+ {
+ if (ddsHeader->pixelFormatFlags & DDSPF_FOURCC)
+ {
+ if (ddsHeader->fourCC == EncodeFourCC("DXT1"))
+ *picFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
+ else if (ddsHeader->fourCC == EncodeFourCC("DXT2"))
+ *picFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+ else if (ddsHeader->fourCC == EncodeFourCC("DXT3"))
+ *picFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
+ else if (ddsHeader->fourCC == EncodeFourCC("DXT4"))
+ *picFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+ else if (ddsHeader->fourCC == EncodeFourCC("DXT5"))
+ *picFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
+ else if (ddsHeader->fourCC == EncodeFourCC("ATI1"))
+ *picFormat = GL_COMPRESSED_RED_RGTC1;
+ else if (ddsHeader->fourCC == EncodeFourCC("BC4U"))
+ *picFormat = GL_COMPRESSED_RED_RGTC1;
+ else if (ddsHeader->fourCC == EncodeFourCC("BC4S"))
+ *picFormat = GL_COMPRESSED_SIGNED_RED_RGTC1;
+ else if (ddsHeader->fourCC == EncodeFourCC("ATI2"))
+ *picFormat = GL_COMPRESSED_RG_RGTC2;
+ else if (ddsHeader->fourCC == EncodeFourCC("BC5U"))
+ *picFormat = GL_COMPRESSED_RG_RGTC2;
+ else if (ddsHeader->fourCC == EncodeFourCC("BC5S"))
+ *picFormat = GL_COMPRESSED_SIGNED_RG_RGTC2;
+ else
+ {
+ ri.Printf(PRINT_ALL, "DDS File %s has unsupported FourCC.", filename);
+ ri.FS_FreeFile(buffer.v);
+ return;
+ }
+ }
+ else if (ddsHeader->pixelFormatFlags == (DDSPF_RGB | DDSPF_ALPHAPIXELS)
+ && ddsHeader->rgbBitCount == 32
+ && ddsHeader->rBitMask == 0x000000ff
+ && ddsHeader->gBitMask == 0x0000ff00
+ && ddsHeader->bBitMask == 0x00ff0000
+ && ddsHeader->aBitMask == 0xff000000)
+ {
+ *picFormat = GL_RGBA8;
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, "DDS File %s has unsupported RGBA format.", filename);
+ ri.FS_FreeFile(buffer.v);
+ return;
+ }
+ }
+
+ *pic = (byte*)ri.Malloc(len);
+ Com_Memcpy(*pic, data, len);
+
+ ri.FS_FreeFile(buffer.v);
+}
+
+void R_SaveDDS(const char *filename, byte *pic, int width, int height, int depth)
+{
+ byte *data;
+ ddsHeader_t *ddsHeader;
+ int picSize, size;
+
+ if (!depth)
+ depth = 1;
+
+ picSize = width * height * depth * 4;
+ size = 4 + sizeof(*ddsHeader) + picSize;
+ data = (byte*)ri.Malloc(size);
+
+ data[0] = 'D';
+ data[1] = 'D';
+ data[2] = 'S';
+ data[3] = ' ';
+
+ ddsHeader = (ddsHeader_t *)(data + 4);
+ memset(ddsHeader, 0, sizeof(ddsHeader_t));
+
+ ddsHeader->headerSize = 0x7c;
+ ddsHeader->flags = _DDSFLAGS_REQUIRED;
+ ddsHeader->height = height;
+ ddsHeader->width = width;
+ ddsHeader->always_0x00000020 = 0x00000020;
+ ddsHeader->caps = DDSCAPS_COMPLEX | DDSCAPS_REQUIRED;
+
+ if (depth == 6)
+ ddsHeader->caps2 = DDSCAPS2_CUBEMAP;
+
+ ddsHeader->pixelFormatFlags = DDSPF_RGB | DDSPF_ALPHAPIXELS;
+ ddsHeader->rgbBitCount = 32;
+ ddsHeader->rBitMask = 0x000000ff;
+ ddsHeader->gBitMask = 0x0000ff00;
+ ddsHeader->bBitMask = 0x00ff0000;
+ ddsHeader->aBitMask = 0xff000000;
+
+ Com_Memcpy(data + 4 + sizeof(*ddsHeader), pic, picSize);
+
+ ri.FS_WriteFile(filename, data, size);
+
+ ri.Free(data);
+}
diff --git a/src/renderergl2/tr_init.cpp b/src/renderergl2/tr_init.cpp
new file mode 100644
index 0000000..12e46a2
--- /dev/null
+++ b/src/renderergl2/tr_init.cpp
@@ -0,0 +1,1534 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// tr_init.c -- functions that are not called every frame
+
+#include "tr_local.h"
+
+#include "tr_dsa.h"
+
+glconfig_t glConfig;
+glRefConfig_t glRefConfig;
+bool textureFilterAnisotropic = false;
+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_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_framebuffer_object;
+cvar_t *r_ext_texture_float;
+cvar_t *r_ext_framebuffer_multisample;
+cvar_t *r_arb_seamless_cube_map;
+cvar_t *r_arb_vertex_array_object;
+cvar_t *r_ext_direct_state_access;
+
+cvar_t *r_cameraExposure;
+
+cvar_t *r_externalGLSL;
+
+cvar_t *r_hdr;
+cvar_t *r_floatLightmap;
+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_depthPrepass;
+cvar_t *r_ssao;
+
+cvar_t *r_normalMapping;
+cvar_t *r_specularMapping;
+cvar_t *r_deluxeMapping;
+cvar_t *r_parallaxMapping;
+cvar_t *r_cubeMapping;
+cvar_t *r_cubemapSize;
+cvar_t *r_pbr;
+cvar_t *r_baseNormalX;
+cvar_t *r_baseNormalY;
+cvar_t *r_baseParallax;
+cvar_t *r_baseSpecular;
+cvar_t *r_baseGloss;
+cvar_t *r_glossType;
+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_forceSunLightScale;
+cvar_t *r_forceSunAmbientScale;
+cvar_t *r_sunlightMode;
+cvar_t *r_drawSunRays;
+cvar_t *r_sunShadows;
+cvar_t *r_shadowFilter;
+cvar_t *r_shadowBlur;
+cvar_t *r_shadowMapSize;
+cvar_t *r_shadowCascadeZNear;
+cvar_t *r_shadowCascadeZFar;
+cvar_t *r_shadowCascadeZBias;
+cvar_t *r_ignoreDstAlpha;
+
+cvar_t *r_ignoreGLErrors;
+cvar_t *r_logFile;
+
+cvar_t *r_stencilbits;
+cvar_t *r_depthbits;
+cvar_t *r_colorbits;
+cvar_t *r_alphabits;
+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_width;
+cvar_t *r_height;
+cvar_t *r_pixelAspect;
+
+cvar_t *r_overBrightBits;
+cvar_t *r_mapOverBrightBits;
+
+cvar_t *r_mapLightmapMin;
+
+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( qtrue );
+ 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;
+ }
+ }
+
+ // set default state
+ GL_SetDefaultState();
+}
+
+/*
+==================
+GL_CheckErrors
+==================
+*/
+void GL_CheckErrs( const 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);
+}
+
+
+/*
+==============================================================================
+
+ 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 = (byte*)ri.Hunk_AllocateTempMemory(padwidth * height + *offset + packAlign - 1);
+
+ bufstart = (byte*)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;
+
+ // finish any 2D drawing if needed
+ if(tess.numIndexes)
+ RB_EndSurface();
+
+ 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, bool jpeg ) {
+ static char fileName[MAX_OSPATH]; // bad things if two screenshots per frame?
+ screenshotCommand_t *cmd;
+
+ cmd = (screenshotCommand_t*)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 = (byte*)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;
+ bool silent;
+
+ if ( !strcmp( ri.Cmd_Argv(1), "levelshot" ) ) {
+ R_LevelShot();
+ return;
+ }
+
+ if ( !strcmp( ri.Cmd_Argv(1), "silent" ) ) {
+ silent = true;
+ } else {
+ silent = false;
+ }
+
+ 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, false );
+
+ if ( !silent ) {
+ ri.Printf (PRINT_ALL, "Wrote %s\n", checkname);
+ }
+}
+
+void R_ScreenShotJPEG_f (void) {
+ char checkname[MAX_OSPATH];
+ static int lastNumber = -1;
+ bool silent;
+
+ if ( !strcmp( ri.Cmd_Argv(1), "levelshot" ) ) {
+ R_LevelShot();
+ return;
+ }
+
+ if ( !strcmp( ri.Cmd_Argv(1), "silent" ) ) {
+ silent = true;
+ } else {
+ silent = false;
+ }
+
+ 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, true );
+
+ if ( !silent ) {
+ ri.Printf (PRINT_ALL, "Wrote %s\n", checkname);
+ }
+}
+
+//============================================================================
+
+/*
+==================
+R_ExportCubemaps
+==================
+*/
+void R_ExportCubemaps(void)
+{
+ exportCubemapsCommand_t *cmd;
+
+ cmd = (exportCubemapsCommand_t*)R_GetCommandBuffer(sizeof(*cmd));
+ if (!cmd) {
+ return;
+ }
+ cmd->commandId = RC_EXPORT_CUBEMAPS;
+}
+
+
+/*
+==================
+R_ExportCubemaps_f
+==================
+*/
+void R_ExportCubemaps_f(void)
+{
+ R_ExportCubemaps();
+}
+
+//============================================================================
+
+/*
+==================
+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;
+
+ // finish any 2D drawing if needed
+ if(tess.numIndexes)
+ RB_EndSurface();
+
+ 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 = (byte*)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);
+
+ GL_BindNullTextures();
+
+ if (glRefConfig.framebufferObject)
+ GL_BindNullFramebuffers();
+
+ GL_TextureMode( r_textureMode->string );
+
+ //qglShadeModel( GL_SMOOTH );
+ qglDepthFunc( GL_LEQUAL );
+
+ //
+ // make sure our GL state vector is set correctly
+ //
+ glState.glStateBits = GLS_DEPTHTEST_DISABLE | GLS_DEPTHMASK_TRUE;
+ glState.storedGlState = 0;
+ glState.faceCulling = CT_TWO_SIDED;
+ glState.faceCullFront = true;
+
+ GL_BindNullProgram();
+
+ if (glRefConfig.vertexArrayObject)
+ qglBindVertexArray(0);
+
+ qglBindBuffer(GL_ARRAY_BUFFER, 0);
+ qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glState.currentVao = NULL;
+ glState.vertexAttribsEnabled = 0;
+
+ 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 );
+
+ if (glRefConfig.seamlessCubeMap)
+ qglEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
+
+ // GL_POLYGON_OFFSET_FILL will be glEnable()d when this is used
+ qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value );
+
+ qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); // FIXME: get color of sky
+}
+
+/*
+================
+R_PrintLongString
+
+Workaround for ri.Printf's 1024 characters buffer limit.
+================
+*/
+void R_PrintLongString(const char *string) {
+ char buffer[1024];
+ const char *p;
+ int size = strlen(string);
+
+ p = string;
+ while(size > 0)
+ {
+ Q_strncpyz(buffer, p, sizeof (buffer) );
+ ri.Printf( PRINT_ALL, "%s", buffer );
+ p += 1023;
+ size -= 1023;
+ }
+}
+
+/*
+================
+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 );
+ ri.Printf( PRINT_ALL, "GL_EXTENSIONS: " );
+ R_PrintLongString( glConfig.extensions_string );
+ ri.Printf( PRINT_ALL, "\n" );
+ 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, "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 ( 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_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_ext_framebuffer_multisample = ri.Cvar_Get( "r_ext_framebuffer_multisample", "0", CVAR_ARCHIVE | CVAR_LATCH);
+ r_arb_seamless_cube_map = ri.Cvar_Get( "r_arb_seamless_cube_map", "0", CVAR_ARCHIVE | CVAR_LATCH);
+ r_arb_vertex_array_object = ri.Cvar_Get( "r_arb_vertex_array_object", "1", CVAR_ARCHIVE | CVAR_LATCH);
+ r_ext_direct_state_access = ri.Cvar_Get("r_ext_direct_state_access", "1", 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, true );
+ 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_alphabits = ri.Cvar_Get( "r_alphabits", "1", 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, true );
+ 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 | CVAR_LATCH);
+ r_width = ri.Cvar_Get( "r_width", "0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_height = ri.Cvar_Get( "r_height", "0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_pixelAspect = ri.Cvar_Get( "r_pixelAspect", "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_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, false);
+
+ r_externalGLSL = ri.Cvar_Get( "r_externalGLSL", "0", CVAR_LATCH );
+
+ r_hdr = ri.Cvar_Get( "r_hdr", "1", CVAR_ARCHIVE | CVAR_LATCH );
+ r_floatLightmap = ri.Cvar_Get( "r_floatLightmap", "0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_postProcess = ri.Cvar_Get( "r_postProcess", "1", CVAR_ARCHIVE );
+
+ r_toneMap = ri.Cvar_Get( "r_toneMap", "1", CVAR_ARCHIVE );
+ r_forceToneMap = ri.Cvar_Get( "r_forceToneMap", "0", CVAR_CHEAT );
+ r_forceToneMapMin = ri.Cvar_Get( "r_forceToneMapMin", "-8.0", CVAR_CHEAT );
+ r_forceToneMapAvg = ri.Cvar_Get( "r_forceToneMapAvg", "-2.0", CVAR_CHEAT );
+ r_forceToneMapMax = ri.Cvar_Get( "r_forceToneMapMax", "0.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_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_cubeMapping = ri.Cvar_Get( "r_cubeMapping", "0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_cubemapSize = ri.Cvar_Get( "r_cubemapSize", "128", CVAR_ARCHIVE | CVAR_LATCH );
+ r_pbr = ri.Cvar_Get("r_pbr", "0", CVAR_ARCHIVE | CVAR_LATCH);
+ r_baseNormalX = ri.Cvar_Get( "r_baseNormalX", "1.0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_baseNormalY = ri.Cvar_Get( "r_baseNormalY", "1.0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_baseParallax = ri.Cvar_Get( "r_baseParallax", "0.05", CVAR_ARCHIVE | CVAR_LATCH );
+ r_baseSpecular = ri.Cvar_Get( "r_baseSpecular", "0.04", CVAR_ARCHIVE | CVAR_LATCH );
+ r_baseGloss = ri.Cvar_Get( "r_baseGloss", "0.3", CVAR_ARCHIVE | CVAR_LATCH );
+ r_glossType = ri.Cvar_Get("r_glossType", "1", 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_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_forceSunLightScale = ri.Cvar_Get( "r_forceSunLightScale", "1.0", CVAR_CHEAT );
+ r_forceSunAmbientScale = ri.Cvar_Get( "r_forceSunAmbientScale", "0.5", CVAR_CHEAT );
+ r_drawSunRays = ri.Cvar_Get( "r_drawSunRays", "0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_sunlightMode = ri.Cvar_Get( "r_sunlightMode", "1", CVAR_ARCHIVE | CVAR_LATCH );
+
+ 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_shadowBlur = ri.Cvar_Get("r_shadowBlur", "0", CVAR_ARCHIVE | CVAR_LATCH);
+ r_shadowMapSize = ri.Cvar_Get("r_shadowMapSize", "1024", CVAR_ARCHIVE | CVAR_LATCH);
+ r_shadowCascadeZNear = ri.Cvar_Get( "r_shadowCascadeZNear", "8", CVAR_ARCHIVE | CVAR_LATCH );
+ r_shadowCascadeZFar = ri.Cvar_Get( "r_shadowCascadeZFar", "1024", CVAR_ARCHIVE | CVAR_LATCH );
+ r_shadowCascadeZBias = ri.Cvar_Get( "r_shadowCascadeZBias", "0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_ignoreDstAlpha = ri.Cvar_Get( "r_ignoreDstAlpha", "1", 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_mapLightmapMin = ri.Cvar_Get ("r_mapLightmapMin", "0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_intensity = ri.Cvar_Get ("r_intensity", "1", CVAR_ARCHIVE | 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, false );
+ 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);
+
+ //
+ // temporary variables that can change at any time
+ //
+ r_showImages = ri.Cvar_Get( "r_showImages", "0", CVAR_CHEAT|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_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( "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 );
+ ri.Cmd_AddCommand( "exportCubemaps", R_ExportCubemaps_f );
+}
+
+void R_InitQueries(void)
+{
+ if (!glRefConfig.occlusionQuery)
+ return;
+
+ if (r_drawSunRays->integer)
+ qglGenQueries(ARRAY_LEN(tr.sunFlareQuery), tr.sunFlareQuery);
+}
+
+void R_ShutDownQueries(void)
+{
+ if (!glRefConfig.occlusionQuery)
+ return;
+
+ if (r_drawSunRays->integer)
+ qglDeleteQueries(ARRAY_LEN(tr.sunFlareQuery), tr.sunFlareQuery);
+}
+
+/*
+===============
+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 ) );
+
+// 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 = (byte*)ri.Hunk_Alloc( sizeof( *backEndData ) + sizeof(srfPoly_t) * max_polys + sizeof(polyVert_t) * max_polyverts, h_low);
+ backEndData = (backEndData_t *) ptr;
+ backEndData->polys = (srfPoly_t *) ((char *) ptr + sizeof( *backEndData ));
+ backEndData->polyVerts = (polyVert_t *) ((char *) ptr + sizeof( *backEndData ) + sizeof(srfPoly_t) * max_polys);
+ R_InitNextFrame();
+
+ InitOpenGL();
+
+ R_InitImages();
+
+ if (glRefConfig.framebufferObject)
+ FBO_Init();
+
+ GLSL_InitGPUShaders();
+
+ R_InitVaos();
+
+ 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( bool destroyWindow ) {
+
+ ri.Printf( PRINT_ALL, "RE_Shutdown( %i )\n", destroyWindow );
+
+ ri.Cmd_RemoveCommand("exportCubemaps");
+ ri.Cmd_RemoveCommand("gfxinfo");
+ ri.Cmd_RemoveCommand("gfxmeminfo");
+ ri.Cmd_RemoveCommand("imagelist");
+ ri.Cmd_RemoveCommand("minimize");
+ ri.Cmd_RemoveCommand("modellist");
+ ri.Cmd_RemoveCommand("screenshot");
+ ri.Cmd_RemoveCommand("screenshotJPEG");
+ ri.Cmd_RemoveCommand("shaderlist");
+ ri.Cmd_RemoveCommand("skinlist");
+
+ if ( tr.registered ) {
+ R_IssuePendingRenderCommands();
+ R_ShutDownQueries();
+ if (glRefConfig.framebufferObject)
+ FBO_Shutdown();
+ R_DeleteTextures();
+ R_ShutdownVaos();
+ GLSL_ShutdownGPUShaders();
+ }
+
+ R_DoneFreeType();
+
+ // shut down platform specific OpenGL stuff
+ if ( destroyWindow ) {
+ GLimp_Shutdown();
+
+ Com_Memset( &glConfig, 0, sizeof( glConfig ) );
+ Com_Memset( &glState, 0, sizeof( glState ) );
+ }
+
+ tr.registered = false;
+}
+
+
+/*
+=============
+RE_EndRegistration
+
+Touch all images to make sure they are resident
+=============
+*/
+void RE_EndRegistration( void ) {
+ R_IssuePendingRenderCommands();
+ if (!ri.Sys_LowPhysicalMemory()) {
+ RB_ShowImages();
+ }
+}
+
+
+/*
+@@@@@@@@@@@@@@@@@@@@@
+GetRefAPI
+
+@@@@@@@@@@@@@@@@@@@@@
+*/
+#ifdef USE_RENDERER_DLOPEN
+extern "C" 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.SetClipRegion = RE_SetClipRegion;
+ 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/renderergl2/tr_light.cpp b/src/renderergl2/tr_light.cpp
new file mode 100644
index 0000000..d14217f
--- /dev/null
+++ b/src/renderergl2/tr_light.cpp
@@ -0,0 +1,513 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// 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 *orientation) {
+ int i;
+ vec3_t temp;
+
+ for ( i = 0 ; i < count ; i++, dl++ ) {
+ VectorSubtract( dl->origin, orientation->origin, temp );
+ dl->transformed[0] = DotProduct( temp, orientation->axis[0] );
+ dl->transformed[1] = DotProduct( temp, orientation->axis[1] );
+ dl->transformed[2] = DotProduct( temp, orientation->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.orientation );
+
+ mask = 0;
+ for ( i=0 ; i<tr.refdef.num_dlights ; i++ ) {
+ dl = &tr.refdef.dlights[i];
+
+ // see if the point is close enough to the bounds to matter
+ for ( j = 0 ; j < 3 ; j++ ) {
+ if ( dl->transformed[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;
+
+ switch(*surf->data)
+ {
+ case SF_FACE:
+ case SF_GRID:
+ case SF_TRIANGLES:
+ ((srfBspSurface_t *)surf->data)->dlightBits = mask;
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+
+/*
+=============================================================================
+
+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;
+ #if idppc
+ float d0, d1, d2, d3, d4, d5;
+ #endif
+ factor = 1.0;
+ data = gridData;
+ for ( j = 0 ; j < 3 ; j++ ) {
+ if ( i & (1<<j) ) {
+ if ( pos[j] + 1 > world->lightGridBounds[j] - 1 ) {
+ break; // ignore values outside lightgrid
+ }
+ factor *= frac[j];
+ data += gridStep[j];
+ } else {
+ factor *= (1.0f - frac[j]);
+ }
+ }
+
+ if ( j != 3 ) {
+ continue;
+ }
+
+ if (world->lightGrid16)
+ {
+ uint16_t *data16 = world->lightGrid16 + (int)(data - world->lightGridData) / 8 * 6;
+ if (!(data16[0]+data16[1]+data16[2]+data16[3]+data16[4]+data16[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->lightGrid16)
+ {
+ // FIXME: this is hideous
+ uint16_t *data16 = world->lightGrid16 + (int)(data - world->lightGridData) / 8 * 6;
+
+ ent->ambientLight[0] += factor * data16[0] / 257.0f;
+ ent->ambientLight[1] += factor * data16[1] / 257.0f;
+ ent->ambientLight[2] += factor * data16[2] / 257.0f;
+
+ ent->directedLight[0] += factor * data16[3] / 257.0f;
+ ent->directedLight[1] += factor * data16[4] / 257.0f;
+ ent->directedLight[2] += factor * data16[5] / 257.0f;
+ }
+ 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 = true;
+
+ //
+ // 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 lights
+ // FIXME: old renderer clamps (ambient + NL * directed) per vertex
+ // check if that's worth implementing
+ {
+ float r, g, b, max;
+
+ r = ent->ambientLight[0];
+ g = ent->ambientLight[1];
+ b = ent->ambientLight[2];
+
+ max = MAX(MAX(r, g), b);
+
+ if (max > 255.0f)
+ {
+ max = 255.0f / max;
+ ent->ambientLight[0] *= max;
+ ent->ambientLight[1] *= max;
+ ent->ambientLight[2] *= max;
+ }
+
+ r = ent->directedLight[0];
+ g = ent->directedLight[1];
+ b = ent->directedLight[2];
+
+ max = MAX(MAX(r, g), b);
+
+ if (max > 255.0f)
+ {
+ max = 255.0f / max;
+ ent->directedLight[0] *= max;
+ ent->directedLight[1] *= max;
+ ent->directedLight[2] *= max;
+ }
+ }
+
+
+ if ( r_debugLight->integer ) {
+ LogLight( ent );
+ }
+
+ // save out the byte packet version
+ ((byte *)&ent->ambientLightInt)[0] = static_cast<int>(ent->ambientLight[0]);
+ ((byte *)&ent->ambientLightInt)[1] = static_cast<int>(ent->ambientLight[1]);
+ ((byte *)&ent->ambientLightInt)[2] = static_cast<int>(ent->ambientLight[2]);
+ ((byte *)&ent->ambientLightInt)[3] = 0xff;
+
+ // transform the direction to local space
+ VectorNormalize( lightDir );
+ ent->modelLightDir[0] = DotProduct( lightDir, ent->e.axis[0] );
+ ent->modelLightDir[1] = DotProduct( lightDir, ent->e.axis[1] );
+ ent->modelLightDir[2] = DotProduct( lightDir, ent->e.axis[2] );
+
+ VectorCopy(lightDir, ent->lightDir);
+}
+
+/*
+=================
+R_LightForPoint
+=================
+*/
+bool R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir )
+{
+ trRefEntity_t ent;
+
+ if ( tr.world->lightGridData == NULL )
+ return false;
+
+ 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 true;
+}
+
+
+bool R_LightDirForPoint( vec3_t point, vec3_t lightDir, vec3_t normal, world_t *world )
+{
+ trRefEntity_t ent;
+
+ if ( world->lightGridData == NULL )
+ return false;
+
+ Com_Memset(&ent, 0, sizeof(ent));
+ VectorCopy( point, ent.e.origin );
+ R_SetupEntityLightingGrid( &ent, world );
+
+ if (DotProduct(ent.lightDir, normal) > 0.2f)
+ VectorCopy(ent.lightDir, lightDir);
+ else
+ VectorCopy(normal, lightDir);
+
+ return true;
+}
+
+
+int R_CubemapForPoint( vec3_t point )
+{
+ int cubemapIndex = -1;
+
+ if (r_cubeMapping->integer && tr.numCubemaps)
+ {
+ int i;
+ vec_t shortest = (float)WORLD_SIZE * (float)WORLD_SIZE;
+
+ for (i = 0; i < tr.numCubemaps; i++)
+ {
+ vec3_t diff;
+ vec_t length;
+
+ VectorSubtract(point, tr.cubemaps[i].origin, diff);
+ length = DotProduct(diff, diff);
+
+ if (shortest > length)
+ {
+ shortest = length;
+ cubemapIndex = i;
+ }
+ }
+ }
+
+ return cubemapIndex + 1;
+}
diff --git a/src/renderergl2/tr_local.h b/src/renderergl2/tr_local.h
new file mode 100644
index 0000000..39df925
--- /dev/null
+++ b/src/renderergl2/tr_local.h
@@ -0,0 +1,2422 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+
+#ifndef TR_LOCAL_H
+#define TR_LOCAL_H
+
+#include <stdbool.h>
+
+#include "qcommon/cvar.h"
+#include "qcommon/q_shared.h"
+#include "qcommon/qfiles.h"
+#include "qcommon/qcommon.h"
+#include "renderercommon/tr_public.h"
+#include "renderercommon/tr_common.h"
+#include "tr_extratypes.h"
+#include "tr_extramath.h"
+#include "tr_fbo.h"
+#include "tr_postprocess.h"
+#include "renderercommon/iqm.h"
+
+#define GL_INDEX_TYPE GL_UNSIGNED_INT
+typedef unsigned int glIndex_t;
+
+#define BUFFER_OFFSET(i) ((char *)NULL + (i))
+
+// 14 bits
+// can't be increased without changing bit packing for drawsurfs
+// see QSORT_SHADERNUM_SHIFT
+#define SHADERNUM_BITS 14
+#define MAX_SHADERS (1<<SHADERNUM_BITS)
+
+#define MAX_FBOS 64
+#define MAX_VISCOUNTS 5
+#define MAX_VAOS 4096
+
+#define MAX_CALC_PSHADOWS 64
+#define MAX_DRAWN_PSHADOWS 16 // do not increase past 32, because bit flags are used on surfaces
+#define PSHADOW_MAP_SIZE 512
+
+typedef struct cubemap_s {
+ char name[MAX_QPATH];
+ vec3_t origin;
+ float parallaxRadius;
+ image_t *image;
+} cubemap_t;
+
+typedef struct dlight_s {
+ vec3_t origin;
+ vec3_t color; // range from 0.0 to 1.0, should be color normalized
+ float radius;
+
+ vec3_t transformed; // origin in local coordinate system
+ int additive; // texture detail is lost tho when the lightmap is dark
+} dlight_t;
+
+
+// a trRefEntity_t has all the information passed in by
+// the client game, as well as some locally derived info
+typedef struct {
+ refEntity_t e;
+
+ float axisLength; // compensate for non-normalized axis
+
+ bool needDlights; // true for bmodels that touch a dlight
+ bool lightingCalculated;
+ bool mirrored; // mirrored matrix, needs reversed culling
+ vec3_t lightDir; // normalized direction towards light, in world space
+ vec3_t modelLightDir; // normalized direction towards light, in model space
+ vec3_t ambientLight; // color normalized to 0-255
+ int ambientLightInt; // 32 bit rgba packed
+ vec3_t directedLight;
+} trRefEntity_t;
+
+
+typedef struct {
+ vec3_t origin; // in world coordinates
+ vec3_t axis[3]; // orientation in world
+ vec3_t viewOrigin; // viewParms->or.origin in local coordinates
+ float modelMatrix[16];
+ float transformMatrix[16];
+} orientationr_t;
+
+// Ensure this is >= the ATTR_INDEX_COUNT enum below
+#define VAO_MAX_ATTRIBS 16
+
+typedef enum
+{
+ VAO_USAGE_STATIC,
+ VAO_USAGE_DYNAMIC
+} vaoUsage_t;
+
+typedef struct vaoAttrib_s
+{
+ uint32_t enabled;
+ uint32_t count;
+ uint32_t type;
+ uint32_t normalized;
+ uint32_t stride;
+ uint32_t offset;
+}
+vaoAttrib_t;
+
+typedef struct vao_s
+{
+ char name[MAX_QPATH];
+
+ uint32_t vao;
+
+ uint32_t vertexesVBO;
+ int vertexesSize; // amount of memory data allocated for all vertices in bytes
+ vaoAttrib_t attribs[VAO_MAX_ATTRIBS];
+
+ uint32_t frameSize; // bytes to skip per frame when doing vertex animation
+
+ uint32_t indexesIBO;
+ int indexesSize; // amount of memory data allocated for all triangles in bytes
+} vao_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,
+} 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;
+ bool isLightmap;
+ bool isVideoMap;
+} textureBundle_t;
+
+enum
+{
+ TB_COLORMAP = 0,
+ TB_DIFFUSEMAP = 0,
+ TB_LIGHTMAP = 1,
+ TB_LEVELSMAP = 1,
+ TB_SHADOWMAP3 = 1,
+ TB_NORMALMAP = 2,
+ TB_DELUXEMAP = 3,
+ TB_SHADOWMAP2 = 3,
+ TB_SPECULARMAP = 4,
+ TB_SHADOWMAP = 5,
+ TB_CUBEMAP = 6,
+ TB_SHADOWMAP4 = 6,
+ NUM_TEXTURE_BUNDLES = 7
+};
+
+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 {
+ bool 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;
+
+ bool isDetail;
+
+ stageType_t type;
+ struct shaderProgram_s *glslShaderGroup;
+ int glslShaderIndex;
+
+ vec4_t normalScale;
+ vec4_t specularScale;
+
+} shaderStage_t;
+
+struct shaderCommands_s;
+
+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
+
+ bool 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
+
+ bool explicitlyDefined; // found in a .shader file
+
+ int surfaceFlags; // if explicitlyDefined, this will have SURF_* flags
+ int contentFlags;
+
+ bool entityMergable; // merge across entites optimizable (smoke, blood)
+
+ bool isSky;
+ skyParms_t sky;
+ fogParms_t fogParms;
+
+ float portalRange; // distance to fog out at
+ bool isPortal;
+
+ cullType_t cullType; // CT_FRONT_SIDED, CT_BACK_SIDED, or CT_TWO_SIDED
+ bool polygonOffset; // set for decals and other items that must be offset
+ bool noMipMaps; // for console fonts, 2D elements, etc.
+ bool 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 );
+
+ double clampTime; // time this shader is clamped to
+ double timeOffset; // current time offset for this shader
+
+ struct shader_s *remappedShader; // current shader this one is remapped too
+
+ struct shader_s *next;
+} shader_t;
+
+bool ShaderRequiresCPUDeforms(const shader_t*);
+
+enum
+{
+ ATTR_INDEX_POSITION = 0,
+ ATTR_INDEX_TEXCOORD = 1,
+ ATTR_INDEX_LIGHTCOORD = 2,
+ ATTR_INDEX_TANGENT = 3,
+ ATTR_INDEX_NORMAL = 4,
+ ATTR_INDEX_COLOR = 5,
+ ATTR_INDEX_PAINTCOLOR = 6,
+ ATTR_INDEX_LIGHTDIRECTION = 7,
+ ATTR_INDEX_BONE_INDEXES = 8,
+ ATTR_INDEX_BONE_WEIGHTS = 9,
+
+ // GPU vertex animations
+ ATTR_INDEX_POSITION2 = 10,
+ ATTR_INDEX_TANGENT2 = 11,
+ ATTR_INDEX_NORMAL2 = 12,
+
+ ATTR_INDEX_COUNT = 13
+};
+
+enum
+{
+ ATTR_POSITION = 1 << ATTR_INDEX_POSITION,
+ ATTR_TEXCOORD = 1 << ATTR_INDEX_TEXCOORD,
+ ATTR_LIGHTCOORD = 1 << ATTR_INDEX_LIGHTCOORD,
+ ATTR_TANGENT = 1 << ATTR_INDEX_TANGENT,
+ ATTR_NORMAL = 1 << ATTR_INDEX_NORMAL,
+ ATTR_COLOR = 1 << ATTR_INDEX_COLOR,
+ ATTR_PAINTCOLOR = 1 << ATTR_INDEX_PAINTCOLOR,
+ ATTR_LIGHTDIRECTION = 1 << ATTR_INDEX_LIGHTDIRECTION,
+ ATTR_BONE_INDEXES = 1 << ATTR_INDEX_BONE_INDEXES,
+ ATTR_BONE_WEIGHTS = 1 << ATTR_INDEX_BONE_WEIGHTS,
+
+ // for .md3 interpolation
+ ATTR_POSITION2 = 1 << ATTR_INDEX_POSITION2,
+ ATTR_TANGENT2 = 1 << ATTR_INDEX_TANGENT2,
+ ATTR_NORMAL2 = 1 << ATTR_INDEX_NORMAL2,
+
+ ATTR_DEFAULT = ATTR_POSITION,
+ ATTR_BITS = ATTR_POSITION |
+ ATTR_TEXCOORD |
+ ATTR_LIGHTCOORD |
+ ATTR_TANGENT |
+ ATTR_NORMAL |
+ ATTR_COLOR |
+ ATTR_PAINTCOLOR |
+ ATTR_LIGHTDIRECTION |
+ ATTR_BONE_INDEXES |
+ ATTR_BONE_WEIGHTS |
+ ATTR_POSITION2 |
+ ATTR_TANGENT2 |
+ ATTR_NORMAL2
+};
+
+enum
+{
+ GENERICDEF_USE_DEFORM_VERTEXES = 0x0001,
+ GENERICDEF_USE_TCGEN_AND_TCMOD = 0x0002,
+ GENERICDEF_USE_VERTEX_ANIMATION = 0x0004,
+ GENERICDEF_USE_FOG = 0x0008,
+ GENERICDEF_USE_RGBAGEN = 0x0010,
+ GENERICDEF_ALL = 0x001F,
+ GENERICDEF_COUNT = 0x0020,
+};
+
+enum
+{
+ FOGDEF_USE_DEFORM_VERTEXES = 0x0001,
+ FOGDEF_USE_VERTEX_ANIMATION = 0x0002,
+ FOGDEF_ALL = 0x0003,
+ FOGDEF_COUNT = 0x0004,
+};
+
+enum
+{
+ DLIGHTDEF_USE_DEFORM_VERTEXES = 0x0001,
+ DLIGHTDEF_ALL = 0x0001,
+ DLIGHTDEF_COUNT = 0x0002,
+};
+
+enum
+{
+ LIGHTDEF_USE_LIGHTMAP = 0x0001,
+ LIGHTDEF_USE_LIGHT_VECTOR = 0x0002,
+ LIGHTDEF_USE_LIGHT_VERTEX = 0x0003,
+ LIGHTDEF_LIGHTTYPE_MASK = 0x0003,
+ LIGHTDEF_ENTITY = 0x0004,
+ LIGHTDEF_USE_TCGEN_AND_TCMOD = 0x0008,
+ LIGHTDEF_USE_PARALLAXMAP = 0x0010,
+ LIGHTDEF_USE_SHADOWMAP = 0x0020,
+ LIGHTDEF_ALL = 0x003F,
+ LIGHTDEF_COUNT = 0x0040
+};
+
+enum
+{
+ GLSL_INT,
+ GLSL_FLOAT,
+ GLSL_FLOAT5,
+ GLSL_VEC2,
+ GLSL_VEC3,
+ GLSL_VEC4,
+ GLSL_MAT16
+};
+
+typedef enum
+{
+ UNIFORM_DIFFUSEMAP = 0,
+ UNIFORM_LIGHTMAP,
+ UNIFORM_NORMALMAP,
+ UNIFORM_DELUXEMAP,
+ UNIFORM_SPECULARMAP,
+
+ UNIFORM_TEXTUREMAP,
+ UNIFORM_LEVELSMAP,
+ UNIFORM_CUBEMAP,
+
+ UNIFORM_SCREENIMAGEMAP,
+ UNIFORM_SCREENDEPTHMAP,
+
+ UNIFORM_SHADOWMAP,
+ UNIFORM_SHADOWMAP2,
+ UNIFORM_SHADOWMAP3,
+ UNIFORM_SHADOWMAP4,
+
+ UNIFORM_SHADOWMVP,
+ UNIFORM_SHADOWMVP2,
+ UNIFORM_SHADOWMVP3,
+ UNIFORM_SHADOWMVP4,
+
+ UNIFORM_ENABLETEXTURES,
+
+ UNIFORM_DIFFUSETEXMATRIX,
+ UNIFORM_DIFFUSETEXOFFTURB,
+
+ UNIFORM_TCGEN0,
+ UNIFORM_TCGEN0VECTOR0,
+ UNIFORM_TCGEN0VECTOR1,
+
+ UNIFORM_DEFORMGEN,
+ UNIFORM_DEFORMPARAMS,
+
+ UNIFORM_COLORGEN,
+ UNIFORM_ALPHAGEN,
+ UNIFORM_COLOR,
+ UNIFORM_BASECOLOR,
+ UNIFORM_VERTCOLOR,
+
+ UNIFORM_DLIGHTINFO,
+ UNIFORM_LIGHTFORWARD,
+ UNIFORM_LIGHTUP,
+ UNIFORM_LIGHTRIGHT,
+ UNIFORM_LIGHTORIGIN,
+ UNIFORM_MODELLIGHTDIR,
+ UNIFORM_LIGHTRADIUS,
+ UNIFORM_AMBIENTLIGHT,
+ UNIFORM_DIRECTEDLIGHT,
+
+ UNIFORM_PORTALRANGE,
+
+ UNIFORM_FOGDISTANCE,
+ UNIFORM_FOGDEPTH,
+ UNIFORM_FOGEYET,
+ UNIFORM_FOGCOLORMASK,
+
+ UNIFORM_MODELMATRIX,
+ UNIFORM_MODELVIEWPROJECTIONMATRIX,
+
+ UNIFORM_TIME,
+ UNIFORM_VERTEXLERP,
+ UNIFORM_NORMALSCALE,
+ UNIFORM_SPECULARSCALE,
+
+ UNIFORM_VIEWINFO, // znear, zfar, width/2, height/2
+ UNIFORM_VIEWORIGIN,
+ UNIFORM_LOCALVIEWORIGIN,
+ UNIFORM_VIEWFORWARD,
+ UNIFORM_VIEWLEFT,
+ UNIFORM_VIEWUP,
+
+ UNIFORM_INVTEXRES,
+ UNIFORM_AUTOEXPOSUREMINMAX,
+ UNIFORM_TONEMINAVGMAXLINEAR,
+
+ UNIFORM_PRIMARYLIGHTORIGIN,
+ UNIFORM_PRIMARYLIGHTCOLOR,
+ UNIFORM_PRIMARYLIGHTAMBIENT,
+ UNIFORM_PRIMARYLIGHTRADIUS,
+
+ UNIFORM_CUBEMAPINFO,
+
+ UNIFORM_ALPHATEST,
+
+ UNIFORM_COUNT
+} uniform_t;
+
+// shaderProgram_t represents a pair of one
+// GLSL vertex and one GLSL fragment shader
+typedef struct shaderProgram_s
+{
+ char name[MAX_QPATH];
+
+ GLuint program;
+ GLuint vertexShader;
+ GLuint fragmentShader;
+ uint32_t attribs; // vertex array attributes
+
+ // uniform parameters
+ GLint uniforms[UNIFORM_COUNT];
+ short uniformBufferOffsets[UNIFORM_COUNT]; // max 32767/64=511 uniforms
+ char *uniformBuffer;
+} shaderProgram_t;
+
+// 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];
+ bool areamaskModified; // true if areamask changed since last scene
+
+ double 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[4][16];
+ float sunDir[4];
+ float sunCol[4];
+ float sunAmbCol[4];
+
+ float autoExposureMinMax[2];
+ float toneMinAvgMaxLinear[3];
+} trRefdef_t;
+
+
+//=================================================================================
+
+// max surfaces per-skin
+// This is an arbitry limit. Vanilla Q3 only supported 32 surfaces in skins but failed to
+// enforce the maximum limit when reading skin files. It was possile to use more than 32
+// surfaces which accessed out of bounds memory past end of skin->surfaces hunk block.
+#define MAX_SKIN_SURFACES 256
+
+// 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; // dynamically allocated array of 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
+ bool hasSurface;
+ float surface[4];
+} fog_t;
+
+typedef enum {
+ VPF_NONE = 0x00,
+ VPF_NOVIEWMODEL = 0x01,
+ VPF_SHADOWMAP = 0x02,
+ VPF_DEPTHSHADOW = 0x04,
+ VPF_DEPTHCLAMP = 0x08,
+ VPF_ORTHOGRAPHIC = 0x10,
+ VPF_USESUNLIGHT = 0x20,
+ VPF_FARPLANEFRUSTUM = 0x40,
+ VPF_NOCUBEMAPS = 0x80
+} viewParmFlags_t;
+
+typedef struct {
+ orientationr_t orientation;
+ orientationr_t world;
+ vec3_t pvsOrigin; // may be different than or.origin for portals
+ bool isPortal; // true if this view is through a portal
+ bool isMirror; // the portal is a mirror, invert the face culling
+ int/*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;
+ int targetFboLayer;
+ int targetFboCubemapIndex;
+ 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_MDR,
+ SF_IQM,
+ SF_FLARE,
+ SF_ENTITY, // beams, rails, lightning, etc that can be determined by entity
+ SF_VAO_MDVMESH,
+
+ SF_NUM_SURFACE_TYPES,
+ SF_MAX = 0x7fffffff // ensures that sizeof( surfaceType_t ) == sizeof( int )
+} surfaceType_t;
+
+typedef struct drawSurf_s {
+ unsigned int sort; // bit combination for fast compares
+ int cubemapIndex;
+ 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 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;
+ int16_t normal[4];
+ int16_t tangent[4];
+ int16_t lightdir[4];
+ uint16_t color[4];
+
+#if DEBUG_OPTIMIZEVERTICES
+ unsigned int id;
+#endif
+} srfVert_t;
+
+#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}}
+
+// srfBspSurface_t covers SF_GRID, SF_TRIANGLES and SF_POLY
+typedef struct srfBspSurface_s
+{
+ surfaceType_t surfaceType;
+
+ // dynamic lighting information
+ int dlightBits;
+ int pshadowBits;
+
+ // culling information
+ vec3_t cullBounds[2];
+ vec3_t cullOrigin;
+ float cullRadius;
+ cplane_t cullPlane;
+
+ // indexes
+ int numIndexes;
+ glIndex_t *indexes;
+
+ // vertexes
+ int numVerts;
+ srfVert_t *verts;
+
+ // SF_GRID specific variables after here
+
+ // 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;
+} srfBspSurface_t;
+
+// inter-quake-model
+typedef struct {
+ int num_vertexes;
+ int num_triangles;
+ int num_frames;
+ int num_surfaces;
+ int num_joints;
+ int num_poses;
+ struct srfIQModel_s *surfaces;
+
+ float *positions;
+ float *texcoords;
+ float *normals;
+ float *tangents;
+ byte *blendIndexes;
+ union {
+ float *f;
+ byte *b;
+ } blendWeights;
+ byte *colors;
+ int *triangles;
+
+ // depending upon the exporter, blend indices and weights might be int/float
+ // as opposed to the recommended byte/byte, for example Noesis exports
+ // int/float whereas the official IQM tool exports byte/byte
+ byte blendWeightsType; // IQM_UBYTE or IQM_FLOAT
+
+ int *jointParents;
+ float *jointMats;
+ 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 srfVaoMdvMesh_s
+{
+ surfaceType_t surfaceType;
+
+ struct mdvModel_s *mdvModel;
+ struct mdvSurface_s *mdvSurface;
+
+ // backEnd stats
+ int numIndexes;
+ int numVerts;
+ glIndex_t minIndex;
+ glIndex_t maxIndex;
+
+ // static render data
+ vao_t *vao;
+} srfVaoMdvMesh_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;
+ int cubemapIndex;
+ 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;
+
+ int numWorldSurfaces;
+
+ int numsurfaces;
+ msurface_t *surfaces;
+ int *surfacesViewCount;
+ int *surfacesDlightBits;
+ int *surfacesPshadowBits;
+
+ int nummarksurfaces;
+ int *marksurfaces;
+
+ int numfogs;
+ fog_t *fogs;
+
+ vec3_t lightGridOrigin;
+ vec3_t lightGridSize;
+ vec3_t lightGridInverseSize;
+ int lightGridBounds[3];
+ byte *lightGridData;
+ uint16_t *lightGrid16;
+
+
+ int numClusters;
+ int clusterBytes;
+ const byte *vis; // may be passed in by CM_LoadMap to save space
+
+ 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;
+ int16_t normal[4];
+ int16_t tangent[4];
+} 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 numIndexes;
+ glIndex_t *indexes;
+
+ 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 numVaoSurfaces;
+ srfVaoMdvMesh_t *vaoSurfaces;
+
+ int numSkins;
+} mdvModel_t;
+
+
+//======================================================================
+
+typedef enum {
+ MOD_BAD,
+ MOD_BRUSH,
+ MOD_MESH,
+ MOD_MDR,
+ 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_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);
+
+//====================================================
+
+#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 {
+ bool finishCalled;
+ int texEnv[2];
+ int faceCulling;
+ int faceCullFront;
+ uint32_t glStateBits;
+ uint32_t storedGlState;
+ float vertexAttribsInterpolation;
+ bool vertexAnimation;
+ uint32_t vertexAttribsEnabled; // global if no VAOs, tess only otherwise
+ FBO_t *currentFBO;
+ vao_t *currentVao;
+ mat4_t modelview;
+ mat4_t projection;
+ mat4_t modelviewProjection;
+} glstate_t;
+
+typedef enum {
+ MI_NONE,
+ MI_NVX,
+ MI_ATI
+} memInfo_t;
+
+typedef enum {
+ TCR_NONE = 0x0000,
+ TCR_RGTC = 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 {
+ int openglMajorVersion;
+ int openglMinorVersion;
+
+ bool intelGraphics;
+
+ bool occlusionQuery;
+
+ int glslMajorVersion;
+ int glslMinorVersion;
+
+ memInfo_t memInfo;
+
+ bool framebufferObject;
+ int maxRenderbufferSize;
+ int maxColorAttachments;
+
+ bool textureFloat;
+ int /*textureCompressionRef_t*/ textureCompression;
+ bool swizzleNormalmap;
+
+ bool framebufferMultisample;
+ bool framebufferBlit;
+
+ bool depthClamp;
+ bool seamlessCubeMap;
+
+ bool vertexArrayObject;
+ bool directStateAccess;
+} glRefConfig_t;
+
+
+typedef struct {
+ int c_surfaces, c_shaders, c_vertexes, c_indexes, c_totalIndexes;
+ int c_surfBatches;
+ float c_overDraw;
+
+ int c_vaoBinds;
+ int c_vaoVertexes;
+ int c_vaoIndexes;
+
+ int c_staticVaoDraws;
+ int c_dynamicVaoDraws;
+
+ 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 {
+ trRefdef_t refdef;
+ viewParms_t viewParms;
+ orientationr_t orientation;
+ backEndCounters_t pc;
+ bool isHyperspace;
+ trRefEntity_t *currentEntity;
+ bool skyRenderedThisView; // flag for drawing sun
+
+ bool projection2D; // if true, drawstretchpic doesn't need to change modes
+ byte color2D[4];
+ bool vertexes2D; // shader needs to be finished
+ trRefEntity_t entity2D; // currentEntity will point at this when doing 2D rendering
+
+ FBO_t *last2DFBO;
+ bool colorMask[4];
+ bool framePostProcessed;
+ bool 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 {
+ bool 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 frameSceneNum; // zeroed at RE_BeginFrame
+
+ bool worldMapLoaded;
+ bool 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 *sunRaysImage;
+ image_t *renderDepthImage;
+ image_t *pshadowMaps[MAX_DRAWN_PSHADOWS];
+ image_t *screenScratchImage;
+ image_t *textureScratchImage[2];
+ image_t *quarterImage[2];
+ image_t *calcLevelsImage;
+ image_t *targetLevelsImage;
+ image_t *fixedLevelsImage;
+ image_t *sunShadowDepthImage[4];
+ image_t *screenShadowImage;
+ image_t *screenSsaoImage;
+ image_t *hdrDepthImage;
+ image_t *renderCubeImage;
+
+ image_t *textureDepthImage;
+
+ FBO_t *renderFbo;
+ FBO_t *msaaResolveFbo;
+ FBO_t *sunRaysFbo;
+ FBO_t *depthFbo;
+ FBO_t *pshadowFbos[MAX_DRAWN_PSHADOWS];
+ FBO_t *screenScratchFbo;
+ FBO_t *textureScratchFbo[2];
+ FBO_t *quarterFbo[2];
+ FBO_t *calcLevelsFbo;
+ FBO_t *targetLevelsFbo;
+ FBO_t *sunShadowFbo[4];
+ FBO_t *screenShadowFbo;
+ FBO_t *screenSsaoFbo;
+ FBO_t *hdrDepthFbo;
+ FBO_t *renderCubeFbo;
+
+ shader_t *defaultShader;
+ shader_t *shadowShader;
+ shader_t *projectionShadowShader;
+
+ shader_t *flareShader;
+ shader_t *sunShader;
+ shader_t *sunFlareShader;
+
+ int numLightmaps;
+ int lightmapSize;
+ image_t **lightmaps;
+ image_t **deluxemaps;
+
+ int fatLightmapCols;
+ int fatLightmapRows;
+
+ int numCubemaps;
+ cubemap_t *cubemaps;
+
+ 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[FOGDEF_COUNT];
+ shaderProgram_t dlightShader[DLIGHTDEF_COUNT];
+ 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[4];
+ shaderProgram_t testcubeShader;
+
+
+ // -----------------------------------------
+
+ 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 orientation; // for current entity
+
+ trRefdef_t refdef;
+
+ int viewCluster;
+
+ float sunShadowScale;
+
+ bool sunShadows;
+ vec3_t sunLight; // from the sky shader for this level
+ vec3_t sunDirection;
+ vec3_t lastCascadeSunDirection;
+ float lastCascadeSunMvp[16];
+
+ frontEndCounters_t pc;
+ int frontEndMsec; // not in pc due to clearing issue
+
+ vec4_t clipRegion; // 2D clipping region
+
+ //
+ // 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 numVaos;
+ vao_t *vaos[MAX_VAOS];
+
+ // 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];
+
+ GLuint sunFlareQuery[2];
+ int sunFlareQueryIndex;
+ bool sunFlareQueryActive[2];
+
+ 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 glstate_t glState; // outside of TR since it shouldn't be cleared during ref re-init
+extern glRefConfig_t glRefConfig;
+
+//
+// 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_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_gamma;
+
+extern cvar_t *r_ext_framebuffer_object;
+extern cvar_t *r_ext_texture_float;
+extern cvar_t *r_ext_framebuffer_multisample;
+extern cvar_t *r_arb_seamless_cube_map;
+extern cvar_t *r_arb_vertex_array_object;
+extern cvar_t *r_ext_direct_state_access;
+
+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_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_skipBackEnd;
+
+extern cvar_t *r_anaglyphMode;
+
+extern cvar_t *r_externalGLSL;
+
+extern cvar_t *r_hdr;
+extern cvar_t *r_floatLightmap;
+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_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_cubeMapping;
+extern cvar_t *r_cubemapSize;
+extern cvar_t *r_pbr;
+extern cvar_t *r_baseNormalX;
+extern cvar_t *r_baseNormalY;
+extern cvar_t *r_baseParallax;
+extern cvar_t *r_baseSpecular;
+extern cvar_t *r_baseGloss;
+extern cvar_t *r_glossType;
+extern cvar_t *r_dlightMode;
+extern cvar_t *r_pshadowDist;
+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_forceSunLightScale;
+extern cvar_t *r_forceSunAmbientScale;
+extern cvar_t *r_sunlightMode;
+extern cvar_t *r_drawSunRays;
+extern cvar_t *r_sunShadows;
+extern cvar_t *r_shadowFilter;
+extern cvar_t *r_shadowBlur;
+extern cvar_t *r_shadowMapSize;
+extern cvar_t *r_shadowCascadeZNear;
+extern cvar_t *r_shadowCascadeZFar;
+extern cvar_t *r_shadowCascadeZBias;
+extern cvar_t *r_ignoreDstAlpha;
+
+extern cvar_t *r_greyscale;
+
+extern cvar_t *r_ignoreGLErrors;
+
+extern cvar_t *r_overBrightBits;
+extern cvar_t *r_mapOverBrightBits;
+
+extern cvar_t *r_mapLightmapMin;
+
+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_marksOnTriangleMeshes;
+
+//====================================================================
+
+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_RenderCubemapSide( int cubemapIndex, int cubemapSide, bool subscene );
+
+void R_AddMD3Surfaces( trRefEntity_t *e );
+void R_AddNullModelSurfaces( trRefEntity_t *e );
+void R_AddBeamSurfaces( trRefEntity_t *e );
+void R_AddRailSurfaces( trRefEntity_t *e, bool 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, int cubemap );
+
+void R_CalcTexDirs(vec3_t sdir, vec3_t tdir, const vec3_t v1, const vec3_t v2,
+ const vec3_t v3, const vec2_t w1, const vec2_t w2, const vec2_t w3);
+vec_t R_CalcTangentSpace(vec3_t tangent, vec3_t bitangent, const vec3_t normal, const vec3_t sdir, const vec3_t tdir);
+bool R_CalcTangentVectors(srfVert_t * dv[3]);
+
+#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, bool computeFrustum);
+void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, orientationr_t *orientation );
+
+/*
+** GL wrapper/helper functions
+*/
+void GL_BindToTMU( image_t *image, int tmu );
+void GL_SetDefaultState (void);
+void GL_TextureMode( const char *string );
+void GL_CheckErrs( const char *file, int line );
+#define GL_CheckErrors(...) GL_CheckErrs(__FILE__, __LINE__)
+void GL_State( unsigned long stateVector );
+void GL_SetProjectionMatrix(mat4_t matrix);
+void GL_SetModelviewMatrix(mat4_t matrix);
+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, bool dirty);
+void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, bool 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( bool destroyWindow );
+
+bool R_GetEntityToken( char *buffer, int size );
+
+model_t *R_AllocModel( void );
+
+void R_Init( void );
+void R_UpdateSubImage( image_t *image, byte *pic, int x, int y, int width, int height, GLenum picFormat );
+
+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
+//
+shader_t *R_FindShader( const char *name, int lightmapIndex, bool 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_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;
+
+typedef struct shaderCommands_s
+{
+ glIndex_t indexes[SHADER_MAX_INDEXES] QALIGN(16);
+ vec4_t xyz[SHADER_MAX_VERTEXES] QALIGN(16);
+ int16_t normal[SHADER_MAX_VERTEXES][4] QALIGN(16);
+ int16_t tangent[SHADER_MAX_VERTEXES][4] QALIGN(16);
+ vec2_t texCoords[SHADER_MAX_VERTEXES] QALIGN(16);
+ vec2_t lightCoords[SHADER_MAX_VERTEXES] QALIGN(16);
+ uint16_t color[SHADER_MAX_VERTEXES][4] QALIGN(16);
+ int16_t lightdir[SHADER_MAX_VERTEXES][4] QALIGN(16);
+ //int vertexDlightBits[SHADER_MAX_VERTEXES] QALIGN(16);
+
+ void *attribPointers[ATTR_INDEX_COUNT];
+ vao_t *vao;
+ bool useInternalVao;
+ bool useCacheVao;
+
+ stageVars_t svars QALIGN(16);
+
+ //color4ub_t constantColor255[SHADER_MAX_VERTEXES] QALIGN(16);
+
+ shader_t *shader;
+ double shaderTime;
+ int fogNum;
+ int cubemapIndex;
+
+ int dlightBits; // or together of all vertexDlightBits
+ int pshadowBits;
+
+ int firstIndex;
+ int numIndexes;
+ int numVertexes;
+
+ // 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, int cubemapIndex );
+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_DrawElements( int numIndexes, glIndex_t 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 );
+bool 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 *orientation );
+bool R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir );
+bool R_LightDirForPoint( vec3_t point, vec3_t lightDir, vec3_t normal, world_t *world );
+int R_CubemapForPoint( vec3_t point );
+
+
+/*
+============================================================
+
+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( float scale, shader_t *shader );
+void RB_ClipSkyPolygons( shaderCommands_t *shader );
+
+/*
+============================================================
+
+CURVE TESSELATION
+
+============================================================
+*/
+
+#define PATCH_STITCHING
+
+void R_SubdividePatchToGrid( srfBspSurface_t *grid, int width, int height,
+ srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] );
+void R_GridInsertColumn( srfBspSurface_t *grid, int column, int row, vec3_t point, float loderror );
+void R_GridInsertRow( srfBspSurface_t *grid, int row, int column, vec3_t point, float loderror );
+
+/*
+============================================================
+
+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
+
+============================================================
+*/
+
+void R_VaoPackTangent(int16_t *out, vec4_t v);
+void R_VaoPackNormal(int16_t *out, vec3_t v);
+void R_VaoPackColor(uint16_t *out, vec4_t c);
+void R_VaoUnpackTangent(vec4_t v, int16_t *pack);
+void R_VaoUnpackNormal(vec3_t v, int16_t *pack);
+
+vao_t *R_CreateVao(const char *name, byte *vertexes, int vertexesSize, byte *indexes, int indexesSize, vaoUsage_t usage);
+vao_t *R_CreateVao2(const char *name, int numVertexes, srfVert_t *verts, int numIndexes, glIndex_t *inIndexes);
+
+void R_BindVao(vao_t *vao);
+void R_BindNullVao(void);
+
+void Vao_SetVertexPointers(vao_t *vao);
+
+void R_InitVaos(void);
+void R_ShutdownVaos(void);
+void R_VaoList_f(void);
+
+void RB_UpdateTessVao(unsigned int attribBits);
+
+void VaoCache_Commit(void);
+void VaoCache_Init(void);
+void VaoCache_BindVao(void);
+void VaoCache_CheckAdd(bool *endSurface, bool *recycleVertexBuffer, bool *recycleIndexBuffer, int numVerts, int numIndexes);
+void VaoCache_RecycleVertexBuffer(void);
+void VaoCache_RecycleIndexBuffer(void);
+void VaoCache_InitNewSurfaceSet(void);
+void VaoCache_AddSurface(srfVert_t *verts, int numVerts, glIndex_t *indexes, int numIndexes);
+
+/*
+============================================================
+
+GLSL
+
+============================================================
+*/
+
+void GLSL_InitGPUShaders(void);
+void GLSL_ShutdownGPUShaders(void);
+void GLSL_VertexAttribPointers(uint32_t attribBits);
+void GLSL_BindProgram(shaderProgram_t * program);
+
+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_SetUniformMat4(shaderProgram_t *program, int uniformNum, const mat4_t matrix);
+
+shaderProgram_t *GLSL_GetGenericShaderProgram(int stage);
+
+/*
+============================================================
+
+SCENE GENERATION
+
+============================================================
+*/
+
+void R_InitNextFrame( 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_BeginScene( const refdef_t *fd );
+void RE_RenderScene( const refdef_t *fd );
+void RE_EndScene( void );
+
+/*
+=============================================================
+
+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);
+
+/*
+=============================================================
+
+ANIMATED MODELS
+
+=============================================================
+*/
+
+void R_MDRAddAnimSurfaces( trRefEntity_t *ent );
+void RB_MDRSurfaceAnim( mdrSurface_t *surface );
+bool 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 );
+
+/*
+=============================================================
+=============================================================
+*/
+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_CalcFogTexCoords( 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_CalcTurbulentFactors( const waveForm_t *wf, float *amplitude, float *now );
+void RB_CalcTransformTexMatrix( const texModInfo_t *tmi, float *matrix );
+void RB_CalcStretchTexMatrix( const waveForm_t *wf, float *matrix );
+
+void RB_CalcModulateColorsByFog( unsigned char *dstColors );
+float RB_CalcWaveAlphaSingle( const waveForm_t *wf );
+float RB_CalcWaveColorSingle( const waveForm_t *wf );
+
+/*
+=============================================================
+
+RENDERER BACK END FUNCTIONS
+
+=============================================================
+*/
+
+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;
+ bool jpeg;
+} screenshotCommand_t;
+
+typedef struct {
+ int commandId;
+ int width;
+ int height;
+ byte *captureBuffer;
+ byte *encodeBuffer;
+ bool 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 struct {
+ int commandId;
+} exportCubemapsCommand_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,
+ RC_EXPORT_CUBEMAPS
+} 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
+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; // the second one may not be allocated
+
+
+void *R_GetCommandBuffer( int bytes );
+void RB_ExecuteRenderCommands( const void *data );
+
+void R_IssuePendingRenderCommands( 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_SetClipRegion( const float *region );
+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, bool motionJpeg );
+
+
+#endif //TR_LOCAL_H
diff --git a/src/renderergl2/tr_main.cpp b/src/renderergl2/tr_main.cpp
new file mode 100644
index 0000000..d9eb3ee
--- /dev/null
+++ b/src/renderergl2/tr_main.cpp
@@ -0,0 +1,2669 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// tr_main.c -- main control flow for each frame
+
+#include "tr_local.h"
+
+#include <string.h> // 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
+================
+*/
+bool R_CompareVert(srfVert_t * v1, srfVert_t * v2, bool checkST)
+{
+ int i;
+
+ for(i = 0; i < 3; i++)
+ {
+ if(floor(v1->xyz[i] + 0.1) != floor(v2->xyz[i] + 0.1))
+ {
+ return false;
+ }
+
+ if(checkST && ((v1->st[0] != v2->st[0]) || (v1->st[1] != v2->st[1])))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*
+http://www.terathon.com/code/tangent.html
+*/
+void R_CalcTexDirs(vec3_t sdir, vec3_t tdir, const vec3_t v1, const vec3_t v2,
+ const vec3_t v3, const vec2_t w1, const vec2_t w2, const vec2_t w3)
+{
+ float x1, x2, y1, y2, z1, z2;
+ float s1, s2, t1, t2, r;
+
+ 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 = s1 * t2 - s2 * t1;
+ if (r) r = 1.0f / r;
+
+ VectorSet(sdir, (t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
+ VectorSet(tdir, (s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
+}
+
+/*
+=============
+R_CalcTangentSpace
+
+Lengyel, Eric. �Computing Tangent Space Basis Vectors for an Arbitrary Mesh�. Terathon Software 3D Graphics Library, 2001. http://www.terathon.com/src/tangent.html
+=============
+*/
+vec_t R_CalcTangentSpace(vec3_t tangent, vec3_t bitangent, const vec3_t normal, const vec3_t sdir, const vec3_t tdir)
+{
+ vec3_t n_cross_t;
+ vec_t n_dot_t, handedness;
+
+ // Gram-Schmidt orthogonalize
+ n_dot_t = DotProduct(normal, sdir);
+ VectorMA(sdir, -n_dot_t, normal, tangent);
+ VectorNormalize(tangent);
+
+ // Calculate handedness
+ CrossProduct(normal, sdir, n_cross_t);
+ handedness = (DotProduct(n_cross_t, tdir) < 0.0f) ? -1.0f : 1.0f;
+
+ // Calculate orthogonal bitangent, if necessary
+ if (bitangent)
+ CrossProduct(normal, tangent, bitangent);
+
+ return handedness;
+}
+
+bool 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 false;
+
+ /* do each vertex */
+ for(i = 0; i < 3; i++)
+ {
+ vec4_t tangent;
+ vec3_t normal, bitangent, nxt;
+
+ // 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;
+
+ tangent[0] = bary[0] * dv[0]->xyz[0] + bary[1] * dv[1]->xyz[0] + bary[2] * dv[2]->xyz[0];
+ tangent[1] = bary[0] * dv[0]->xyz[1] + bary[1] * dv[1]->xyz[1] + bary[2] * dv[2]->xyz[1];
+ tangent[2] = bary[0] * dv[0]->xyz[2] + bary[1] * dv[1]->xyz[2] + bary[2] * dv[2]->xyz[2];
+
+ VectorSubtract(tangent, dv[i]->xyz, tangent);
+ VectorNormalize(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;
+
+ bitangent[0] = bary[0] * dv[0]->xyz[0] + bary[1] * dv[1]->xyz[0] + bary[2] * dv[2]->xyz[0];
+ bitangent[1] = bary[0] * dv[0]->xyz[1] + bary[1] * dv[1]->xyz[1] + bary[2] * dv[2]->xyz[1];
+ bitangent[2] = bary[0] * dv[0]->xyz[2] + bary[1] * dv[1]->xyz[2] + bary[2] * dv[2]->xyz[2];
+
+ VectorSubtract(bitangent, dv[i]->xyz, bitangent);
+ VectorNormalize(bitangent);
+
+ // store bitangent handedness
+ R_VaoUnpackNormal(normal, dv[i]->normal);
+ CrossProduct(normal, tangent, nxt);
+ tangent[3] = (DotProduct(nxt, bitangent) < 0.0f) ? -1.0f : 1.0f;
+
+ R_VaoPackTangent(dv[i]->tangent, tangent);
+
+ // 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 true;
+}
+
+
+/*
+=================
+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.orientation.origin, transformed[i] );
+ VectorMA( transformed[i], v[0], tr.orientation.axis[0], transformed[i] );
+ VectorMA( transformed[i], v[1], tr.orientation.axis[1], transformed[i] );
+ VectorMA( transformed[i], v[2], tr.orientation.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;
+ bool anyClip;
+ int r, numPlanes;
+
+ numPlanes = (tr.viewParms.flags & VPF_FARPLANEFRUSTUM) ? 5 : 4;
+
+ // check against frustum planes
+ anyClip = false;
+ for(i = 0; i < numPlanes; 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 = true;
+ }
+ }
+
+ 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;
+ bool mightBeClipped = false;
+
+ 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 = true;
+ }
+ }
+
+ 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, (tr.viewParms.flags & VPF_FARPLANEFRUSTUM) ? 5 : 4);
+}
+
+/*
+=================
+R_LocalNormalToWorld
+
+=================
+*/
+void R_LocalNormalToWorld (const vec3_t local, vec3_t world) {
+ world[0] = local[0] * tr.orientation.axis[0][0] + local[1] * tr.orientation.axis[1][0] + local[2] * tr.orientation.axis[2][0];
+ world[1] = local[0] * tr.orientation.axis[0][1] + local[1] * tr.orientation.axis[1][1] + local[2] * tr.orientation.axis[2][1];
+ world[2] = local[0] * tr.orientation.axis[0][2] + local[1] * tr.orientation.axis[1][2] + local[2] * tr.orientation.axis[2][2];
+}
+
+/*
+=================
+R_LocalPointToWorld
+
+=================
+*/
+void R_LocalPointToWorld (const vec3_t local, vec3_t world) {
+ world[0] = local[0] * tr.orientation.axis[0][0] + local[1] * tr.orientation.axis[1][0] + local[2] * tr.orientation.axis[2][0] + tr.orientation.origin[0];
+ world[1] = local[0] * tr.orientation.axis[0][1] + local[1] * tr.orientation.axis[1][1] + local[2] * tr.orientation.axis[2][1] + tr.orientation.origin[1];
+ world[2] = local[0] * tr.orientation.axis[0][2] + local[1] * tr.orientation.axis[1][2] + local[2] * tr.orientation.axis[2][2] + tr.orientation.origin[2];
+}
+
+/*
+=================
+R_WorldToLocal
+
+=================
+*/
+void R_WorldToLocal (const vec3_t world, vec3_t local) {
+ local[0] = DotProduct(world, tr.orientation.axis[0]);
+ local[1] = DotProduct(world, tr.orientation.axis[1]);
+ local[2] = DotProduct(world, tr.orientation.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 *orientation ) {
+ float glMatrix[16];
+ vec3_t delta;
+ float axisLength;
+
+ if ( ent->e.reType != RT_MODEL ) {
+ *orientation = viewParms->world;
+ return;
+ }
+
+ VectorCopy( ent->e.origin, orientation->origin );
+
+ VectorCopy( ent->e.axis[0], orientation->axis[0] );
+ VectorCopy( ent->e.axis[1], orientation->axis[1] );
+ VectorCopy( ent->e.axis[2], orientation->axis[2] );
+
+ glMatrix[0] = orientation->axis[0][0];
+ glMatrix[4] = orientation->axis[1][0];
+ glMatrix[8] = orientation->axis[2][0];
+ glMatrix[12] = orientation->origin[0];
+
+ glMatrix[1] = orientation->axis[0][1];
+ glMatrix[5] = orientation->axis[1][1];
+ glMatrix[9] = orientation->axis[2][1];
+ glMatrix[13] = orientation->origin[1];
+
+ glMatrix[2] = orientation->axis[0][2];
+ glMatrix[6] = orientation->axis[1][2];
+ glMatrix[10] = orientation->axis[2][2];
+ glMatrix[14] = orientation->origin[2];
+
+ glMatrix[3] = 0;
+ glMatrix[7] = 0;
+ glMatrix[11] = 0;
+ glMatrix[15] = 1;
+
+ Mat4Copy(glMatrix, orientation->transformMatrix);
+ myGlMultMatrix( glMatrix, viewParms->world.modelMatrix, orientation->modelMatrix );
+
+ // calculate the viewer origin in the model's space
+ // needed for fog, specular, and environment mapping
+ VectorSubtract( viewParms->orientation.origin, orientation->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;
+ }
+
+ orientation->viewOrigin[0] = DotProduct( delta, orientation->axis[0] ) * axisLength;
+ orientation->viewOrigin[1] = DotProduct( delta, orientation->axis[1] ) * axisLength;
+ orientation->viewOrigin[2] = DotProduct( delta, orientation->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.orientation, 0, sizeof(tr.orientation));
+ tr.orientation.axis[0][0] = 1;
+ tr.orientation.axis[1][1] = 1;
+ tr.orientation.axis[2][2] = 1;
+ VectorCopy (tr.viewParms.orientation.origin, tr.orientation.viewOrigin);
+
+ // transform by the camera placement
+ VectorCopy( tr.viewParms.orientation.origin, origin );
+
+ viewerMatrix[0] = tr.viewParms.orientation.axis[0][0];
+ viewerMatrix[4] = tr.viewParms.orientation.axis[0][1];
+ viewerMatrix[8] = tr.viewParms.orientation.axis[0][2];
+ viewerMatrix[12] = -origin[0] * viewerMatrix[0] + -origin[1] * viewerMatrix[4] + -origin[2] * viewerMatrix[8];
+
+ viewerMatrix[1] = tr.viewParms.orientation.axis[1][0];
+ viewerMatrix[5] = tr.viewParms.orientation.axis[1][1];
+ viewerMatrix[9] = tr.viewParms.orientation.axis[1][2];
+ viewerMatrix[13] = -origin[0] * viewerMatrix[1] + -origin[1] * viewerMatrix[5] + -origin[2] * viewerMatrix[9];
+
+ viewerMatrix[2] = tr.viewParms.orientation.axis[2][0];
+ viewerMatrix[6] = tr.viewParms.orientation.axis[2][1];
+ viewerMatrix[10] = tr.viewParms.orientation.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.orientation.modelMatrix );
+
+ tr.viewParms.world = tr.orientation;
+
+}
+
+/*
+** 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.orientation.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->orientation.origin, ofsorigin);
+
+ length = sqrt(xmax * xmax + zProj * zProj);
+ oppleg = xmax / length;
+ adjleg = zProj / length;
+
+ VectorScale(dest->orientation.axis[0], oppleg, dest->frustum[0].normal);
+ VectorMA(dest->frustum[0].normal, adjleg, dest->orientation.axis[1], dest->frustum[0].normal);
+
+ VectorScale(dest->orientation.axis[0], oppleg, dest->frustum[1].normal);
+ VectorMA(dest->frustum[1].normal, -adjleg, dest->orientation.axis[1], dest->frustum[1].normal);
+ }
+ else
+ {
+ // In stereo rendering, due to the modification of the projection matrix, dest->orientation.origin is not the
+ // actual origin that we're rendering so offset the tip of the view pyramid.
+ VectorMA(dest->orientation.origin, stereoSep, dest->orientation.axis[1], ofsorigin);
+
+ oppleg = xmax + stereoSep;
+ length = sqrt(oppleg * oppleg + zProj * zProj);
+ VectorScale(dest->orientation.axis[0], oppleg / length, dest->frustum[0].normal);
+ VectorMA(dest->frustum[0].normal, zProj / length, dest->orientation.axis[1], dest->frustum[0].normal);
+
+ oppleg = xmin + stereoSep;
+ length = sqrt(oppleg * oppleg + zProj * zProj);
+ VectorScale(dest->orientation.axis[0], -oppleg / length, dest->frustum[1].normal);
+ VectorMA(dest->frustum[1].normal, -zProj / length, dest->orientation.axis[1], dest->frustum[1].normal);
+ }
+
+ length = sqrt(ymax * ymax + zProj * zProj);
+ oppleg = ymax / length;
+ adjleg = zProj / length;
+
+ VectorScale(dest->orientation.axis[0], oppleg, dest->frustum[2].normal);
+ VectorMA(dest->frustum[2].normal, adjleg, dest->orientation.axis[2], dest->frustum[2].normal);
+
+ VectorScale(dest->orientation.axis[0], oppleg, dest->frustum[3].normal);
+ VectorMA(dest->frustum[3].normal, -adjleg, dest->orientation.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->orientation.axis[0], farpoint);
+ VectorScale(dest->orientation.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] );
+ dest->flags |= VPF_FARPLANEFRUSTUM;
+ }
+}
+
+/*
+===============
+R_SetupProjection
+===============
+*/
+void R_SetupProjection(viewParms_t *dest, float zProj, float zFar, bool 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->orientation.axis[1], plane);
+ plane2[1] = DotProduct (dest->orientation.axis[2], plane);
+ plane2[2] = -DotProduct (dest->orientation.axis[0], plane);
+ plane2[3] = DotProduct (plane, dest->orientation.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->orientation.axis[1], 1.0f, dest->frustum[0].normal);
+ VectorMA(dest->orientation.origin, viewBounds[0][1], dest->frustum[0].normal, pop);
+ dest->frustum[0].dist = DotProduct(pop, dest->frustum[0].normal);
+
+ VectorScale(dest->orientation.axis[1], -1.0f, dest->frustum[1].normal);
+ VectorMA(dest->orientation.origin, -viewBounds[1][1], dest->frustum[1].normal, pop);
+ dest->frustum[1].dist = DotProduct(pop, dest->frustum[1].normal);
+
+ VectorScale(dest->orientation.axis[2], 1.0f, dest->frustum[2].normal);
+ VectorMA(dest->orientation.origin, viewBounds[0][2], dest->frustum[2].normal, pop);
+ dest->frustum[2].dist = DotProduct(pop, dest->frustum[2].normal);
+
+ VectorScale(dest->orientation.axis[2], -1.0f, dest->frustum[3].normal);
+ VectorMA(dest->orientation.origin, -viewBounds[1][2], dest->frustum[3].normal, pop);
+ dest->frustum[3].dist = DotProduct(pop, dest->frustum[3].normal);
+
+ VectorScale(dest->orientation.axis[0], -1.0f, dest->frustum[4].normal);
+ VectorMA(dest->orientation.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]);
+ }
+
+ dest->flags |= VPF_FARPLANEFRUSTUM;
+}
+
+/*
+=================
+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) {
+ srfBspSurface_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 = ((srfBspSurface_t *)surfType)->cullPlane;
+ return;
+ case SF_TRIANGLES:
+ tri = (srfBspSurface_t *)surfType;
+ v1 = tri->verts + tri->indexes[0];
+ v2 = tri->verts + tri->indexes[1];
+ v3 = tri->verts + tri->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 true if it should be mirrored
+=================
+*/
+bool R_GetPortalOrientations( drawSurf_t *drawSurf, int entityNum,
+ orientation_t *surface, orientation_t *camera,
+ vec3_t pvsOrigin, bool *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.orientation );
+
+ // 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.orientation.origin );
+
+ // translate the original plane
+ originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.orientation.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 = true;
+ return true;
+ }
+
+ // 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 = false;
+ return true;
+ }
+
+ // 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 false;
+}
+
+static bool 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.orientation );
+
+ // 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.orientation.origin );
+
+ // translate the original plane
+ originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.orientation.origin );
+ }
+
+ // 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 true;
+ }
+
+ return false;
+ }
+ return false;
+}
+
+/*
+** SurfIsOffscreen
+**
+** Determines if a surface is completely offscreen.
+*/
+static bool 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;
+
+ R_RotateForViewer();
+
+ R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted, &pshadowed );
+ RB_BeginSurface( shader, fogNum, drawSurf->cubemapIndex);
+ 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.orientation.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 true;
+ }
+
+ // 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, tNormal;
+
+ float len;
+
+ VectorSubtract( tess.xyz[tess.indexes[i]], tr.viewParms.orientation.origin, normal );
+
+ len = VectorLengthSquared( normal ); // lose the sqrt
+ if ( len < shortest )
+ {
+ shortest = len;
+ }
+
+ R_VaoUnpackNormal(tNormal, tess.normal[tess.indexes[i]]);
+
+ if ( DotProduct( normal, tNormal ) >= 0 )
+ {
+ numTriangles--;
+ }
+ }
+ if ( !numTriangles )
+ {
+ return true;
+ }
+
+ // 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 false;
+ }
+
+ if ( shortest > (tess.shader->portalRange*tess.shader->portalRange) )
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+========================
+R_MirrorViewBySurface
+
+Returns true if another view has been rendered
+========================
+*/
+bool 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 false;
+ }
+
+ if ( r_noportals->integer || (r_fastsky->integer == 1) ) {
+ return false;
+ }
+
+ // trivially reject portal/mirror
+ if ( SurfIsOffscreen( drawSurf, clipDest ) ) {
+ return false;
+ }
+
+ // save old viewParms so we can return to it after the mirror view
+ oldParms = tr.viewParms;
+
+ newParms = tr.viewParms;
+ newParms.isPortal = true;
+ newParms.zFar = 0.0f;
+ newParms.flags &= ~VPF_FARPLANEFRUSTUM;
+ if ( !R_GetPortalOrientations( drawSurf, entityNum, &surface, &camera,
+ newParms.pvsOrigin, &newParms.isMirror ) ) {
+ return false; // bad portal, no portalentity
+ }
+
+ // Never draw viewmodels in portal or mirror views.
+ newParms.flags |= VPF_NOVIEWMODEL;
+
+ R_MirrorPoint (oldParms.orientation.origin, &surface, &camera, newParms.orientation.origin );
+
+ VectorSubtract( vec3_origin, camera.axis[0], newParms.portalPlane.normal );
+ newParms.portalPlane.dist = DotProduct( camera.origin, newParms.portalPlane.normal );
+
+ R_MirrorVector (oldParms.orientation.axis[0], &surface, &camera, newParms.orientation.axis[0]);
+ R_MirrorVector (oldParms.orientation.axis[1], &surface, &camera, newParms.orientation.axis[1]);
+ R_MirrorVector (oldParms.orientation.axis[2], &surface, &camera, newParms.orientation.axis[2]);
+
+ // OPTIMIZE: restrict the viewport on the mirrored view
+
+ // render the mirror view
+ R_RenderView (&newParms);
+
+ tr.viewParms = oldParms;
+
+ return true;
+}
+
+/*
+=================
+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;
+ }
+
+ if ( ent->e.renderfx & RF_CROSSHAIR ) {
+ 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 cubemap ) {
+ 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].cubemapIndex = cubemap;
+ 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 >> QSORT_PSHADOW_SHIFT ) & 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;
+ }
+
+ // 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 = false;
+
+ // 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.flags & VPF_NOVIEWMODEL)) {
+ 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, 0 /*cubeMap*/ );
+ break;
+
+ case RT_MODEL:
+ // we must set up parts of tr.or for model culling
+ R_RotateForEntity( ent, &tr.viewParms, &tr.orientation );
+
+ tr.currentModel = R_GetModelByHandle( ent->e.hModel );
+ if (!tr.currentModel) {
+ R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0, 0, 0 /*cubeMap*/ );
+ } else {
+ switch ( tr.currentModel->type ) {
+ case MOD_MESH:
+ R_AddMD3Surfaces( ent );
+ break;
+ case MOD_MDR:
+ R_MDRAddAnimSurfaces( ent );
+ break;
+ 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, 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;
+ }
+
+ R_IssuePendingRenderCommands();
+
+ GL_BindToTMU(tr.whiteImage, TB_COLORMAP);
+ 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;
+ int numDrawSurfs;
+
+ 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, true);
+
+ R_GenerateDrawSurfs();
+
+ // if we overflowed MAX_DRAWSURFS, the drawsurfs
+ // wrapped around in the buffer and we will be missing
+ // the first surfaces, not the last ones
+ numDrawSurfs = tr.refdef.numDrawSurfs;
+ if ( numDrawSurfs > MAX_DRAWSURFS ) {
+ numDrawSurfs = MAX_DRAWSURFS;
+ }
+
+ R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, 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 = false;
+ shadowParms.isMirror = true; // because it is
+
+ shadowParms.fovX = 90;
+ shadowParms.fovY = 90;
+
+ shadowParms.flags = VPF_SHADOWMAP | VPF_DEPTHSHADOW | VPF_NOVIEWMODEL;
+ shadowParms.zFar = tr.refdef.dlights[i].radius;
+
+ VectorCopy( tr.refdef.dlights[i].origin, shadowParms.orientation.origin );
+
+ for (j = 0; j < 6; j++)
+ {
+ switch(j)
+ {
+ case 0:
+ // -X
+ VectorSet( shadowParms.orientation.axis[0], -1, 0, 0);
+ VectorSet( shadowParms.orientation.axis[1], 0, 0, -1);
+ VectorSet( shadowParms.orientation.axis[2], 0, 1, 0);
+ break;
+ case 1:
+ // +X
+ VectorSet( shadowParms.orientation.axis[0], 1, 0, 0);
+ VectorSet( shadowParms.orientation.axis[1], 0, 0, 1);
+ VectorSet( shadowParms.orientation.axis[2], 0, 1, 0);
+ break;
+ case 2:
+ // -Y
+ VectorSet( shadowParms.orientation.axis[0], 0, -1, 0);
+ VectorSet( shadowParms.orientation.axis[1], 1, 0, 0);
+ VectorSet( shadowParms.orientation.axis[2], 0, 0, -1);
+ break;
+ case 3:
+ // +Y
+ VectorSet( shadowParms.orientation.axis[0], 0, 1, 0);
+ VectorSet( shadowParms.orientation.axis[1], 1, 0, 0);
+ VectorSet( shadowParms.orientation.axis[2], 0, 0, 1);
+ break;
+ case 4:
+ // -Z
+ VectorSet( shadowParms.orientation.axis[0], 0, 0, -1);
+ VectorSet( shadowParms.orientation.axis[1], 1, 0, 0);
+ VectorSet( shadowParms.orientation.axis[2], 0, 1, 0);
+ break;
+ case 5:
+ // +Z
+ VectorSet( shadowParms.orientation.axis[0], 0, 0, 1);
+ VectorSet( shadowParms.orientation.axis[1], -1, 0, 0);
+ VectorSet( shadowParms.orientation.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_MDR:
+ {
+ // FIXME: never actually tested this
+ mdrHeader_t *header = (mdrHeader_t*)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;
+ case MOD_IQM:
+ {
+ // FIXME: never actually tested this
+ iqmData_t *data = (iqmData_t*)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;
+ bool touch;
+
+ if (ps1->numEntities == 8)
+ break;
+
+ touch = false;
+ 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 = true;
+ 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 ( fabsf(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 = false;
+ shadowParms.isMirror = false;
+
+ shadowParms.fovX = 90;
+ shadowParms.fovY = 90;
+
+ if (glRefConfig.framebufferObject)
+ shadowParms.targetFbo = tr.pshadowFbos[i];
+
+ shadowParms.flags = VPF_DEPTHSHADOW | VPF_NOVIEWMODEL;
+ shadowParms.zFar = shadow->lightRadius;
+
+ VectorCopy(shadow->lightOrigin, shadowParms.orientation.origin);
+
+ VectorCopy(shadow->lightViewAxis[0], shadowParms.orientation.axis[0]);
+ VectorCopy(shadow->lightViewAxis[1], shadowParms.orientation.axis[1]);
+ VectorCopy(shadow->lightViewAxis[2], shadowParms.orientation.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->orientation.axis[1], 1.0f, dest->frustum[0].normal);
+ VectorMA(dest->orientation.origin, -shadow->viewRadius, dest->frustum[0].normal, pop);
+ dest->frustum[0].dist = DotProduct(pop, dest->frustum[0].normal);
+
+ VectorScale(dest->orientation.axis[1], -1.0f, dest->frustum[1].normal);
+ VectorMA(dest->orientation.origin, -shadow->viewRadius, dest->frustum[1].normal, pop);
+ dest->frustum[1].dist = DotProduct(pop, dest->frustum[1].normal);
+
+ VectorScale(dest->orientation.axis[2], 1.0f, dest->frustum[2].normal);
+ VectorMA(dest->orientation.origin, -shadow->viewRadius, dest->frustum[2].normal, pop);
+ dest->frustum[2].dist = DotProduct(pop, dest->frustum[2].normal);
+
+ VectorScale(dest->orientation.axis[2], -1.0f, dest->frustum[3].normal);
+ VectorMA(dest->orientation.origin, -shadow->viewRadius, dest->frustum[3].normal, pop);
+ dest->frustum[3].dist = DotProduct(pop, dest->frustum[3].normal);
+
+ VectorScale(dest->orientation.axis[0], -1.0f, dest->frustum[4].normal);
+ VectorMA(dest->orientation.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]);
+ }
+
+ dest->flags |= VPF_FARPLANEFRUSTUM;
+ }
+
+ 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];
+ bool lightViewIndependentOfCameraView = false;
+
+ 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;
+ }
+
+ if (level != 3)
+ VectorCopy(fd->vieworg, lightOrigin);
+ else
+ VectorCopy(tr.world->lightGridOrigin, lightOrigin);
+
+ // Make up a projection
+ VectorScale(lightDir, -1.0f, lightViewAxis[0]);
+
+ if (level == 3 || 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 (fabsf(DotProduct(lightViewAxis[2], lightViewAxis[0])) > 0.9f)
+ {
+ if (level == 3 || 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
+ {
+ mat4_t lightViewMatrix;
+ vec4_t point, base, lightViewPoint;
+ float lx, ly;
+
+ base[3] = 1;
+ point[3] = 1;
+ lightViewPoint[3] = 1;
+
+ Mat4View(lightViewAxis, lightOrigin, lightViewMatrix);
+
+ ClearBounds(lightviewBounds[0], lightviewBounds[1]);
+
+ if (level != 3)
+ {
+ // 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);
+ Mat4Transform(lightViewMatrix, point, lightViewPoint);
+ AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+ VectorMA(base, -lx, fd->viewaxis[1], point);
+ VectorMA(point, ly, fd->viewaxis[2], point);
+ Mat4Transform(lightViewMatrix, point, lightViewPoint);
+ AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+ VectorMA(base, lx, fd->viewaxis[1], point);
+ VectorMA(point, -ly, fd->viewaxis[2], point);
+ Mat4Transform(lightViewMatrix, point, lightViewPoint);
+ AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+ VectorMA(base, -lx, fd->viewaxis[1], point);
+ VectorMA(point, -ly, fd->viewaxis[2], point);
+ Mat4Transform(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);
+ Mat4Transform(lightViewMatrix, point, lightViewPoint);
+ AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+ VectorMA(base, -lx, fd->viewaxis[1], point);
+ VectorMA(point, ly, fd->viewaxis[2], point);
+ Mat4Transform(lightViewMatrix, point, lightViewPoint);
+ AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+ VectorMA(base, lx, fd->viewaxis[1], point);
+ VectorMA(point, -ly, fd->viewaxis[2], point);
+ Mat4Transform(lightViewMatrix, point, lightViewPoint);
+ AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+ VectorMA(base, -lx, fd->viewaxis[1], point);
+ VectorMA(point, -ly, fd->viewaxis[2], point);
+ Mat4Transform(lightViewMatrix, point, lightViewPoint);
+ AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+ }
+ else
+ {
+ // use light grid size as level size
+ // FIXME: could be tighter
+ vec3_t bounds;
+
+ bounds[0] = tr.world->lightGridSize[0] * tr.world->lightGridBounds[0];
+ bounds[1] = tr.world->lightGridSize[1] * tr.world->lightGridBounds[1];
+ bounds[2] = tr.world->lightGridSize[2] * tr.world->lightGridBounds[2];
+
+ point[0] = tr.world->lightGridOrigin[0];
+ point[1] = tr.world->lightGridOrigin[1];
+ point[2] = tr.world->lightGridOrigin[2];
+ Mat4Transform(lightViewMatrix, point, lightViewPoint);
+ AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+ point[0] = tr.world->lightGridOrigin[0] + bounds[0];
+ point[1] = tr.world->lightGridOrigin[1];
+ point[2] = tr.world->lightGridOrigin[2];
+ Mat4Transform(lightViewMatrix, point, lightViewPoint);
+ AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+ point[0] = tr.world->lightGridOrigin[0];
+ point[1] = tr.world->lightGridOrigin[1] + bounds[1];
+ point[2] = tr.world->lightGridOrigin[2];
+ Mat4Transform(lightViewMatrix, point, lightViewPoint);
+ AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+ point[0] = tr.world->lightGridOrigin[0] + bounds[0];
+ point[1] = tr.world->lightGridOrigin[1] + bounds[1];
+ point[2] = tr.world->lightGridOrigin[2];
+ Mat4Transform(lightViewMatrix, point, lightViewPoint);
+ AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+ point[0] = tr.world->lightGridOrigin[0];
+ point[1] = tr.world->lightGridOrigin[1];
+ point[2] = tr.world->lightGridOrigin[2] + bounds[2];
+ Mat4Transform(lightViewMatrix, point, lightViewPoint);
+ AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+ point[0] = tr.world->lightGridOrigin[0] + bounds[0];
+ point[1] = tr.world->lightGridOrigin[1];
+ point[2] = tr.world->lightGridOrigin[2] + bounds[2];
+ Mat4Transform(lightViewMatrix, point, lightViewPoint);
+ AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+ point[0] = tr.world->lightGridOrigin[0];
+ point[1] = tr.world->lightGridOrigin[1] + bounds[1];
+ point[2] = tr.world->lightGridOrigin[2] + bounds[2];
+ Mat4Transform(lightViewMatrix, point, lightViewPoint);
+ AddPointToBounds(lightViewPoint, lightviewBounds[0], lightviewBounds[1]);
+
+ point[0] = tr.world->lightGridOrigin[0] + bounds[0];
+ point[1] = tr.world->lightGridOrigin[1] + bounds[1];
+ point[2] = tr.world->lightGridOrigin[2] + bounds[2];
+ Mat4Transform(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, "level %d znear %f zfar %f\n", level, lightviewBounds[0][0], lightviewBounds[1][0]);
+ //ri.Printf(PRINT_ALL, "xmin %f xmax %f ymin %f ymax %f\n", lightviewBounds[0][1], lightviewBounds[1][1], -lightviewBounds[1][2], -lightviewBounds[0][2]);
+ }
+
+ {
+ 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 = false;
+ shadowParms.isMirror = false;
+
+ shadowParms.fovX = 90;
+ shadowParms.fovY = 90;
+
+ if (glRefConfig.framebufferObject)
+ shadowParms.targetFbo = tr.sunShadowFbo[level];
+
+ shadowParms.flags = VPF_DEPTHSHADOW | VPF_DEPTHCLAMP | VPF_ORTHOGRAPHIC | VPF_NOVIEWMODEL;
+ shadowParms.zFar = lightviewBounds[1][0];
+
+ VectorCopy(lightOrigin, shadowParms.orientation.origin);
+
+ VectorCopy(lightViewAxis[0], shadowParms.orientation.axis[0]);
+ VectorCopy(lightViewAxis[1], shadowParms.orientation.axis[1]);
+ VectorCopy(lightViewAxis[2], shadowParms.orientation.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 );
+ }
+
+ Mat4Multiply(tr.viewParms.projectionMatrix, tr.viewParms.world.modelMatrix, tr.refdef.sunShadowMvp[level]);
+ }
+}
+
+void R_RenderCubemapSide( int cubemapIndex, int cubemapSide, bool subscene )
+{
+ refdef_t refdef;
+ viewParms_t parms;
+
+ memset( &refdef, 0, sizeof( refdef ) );
+ refdef.rdflags = 0;
+ VectorCopy(tr.cubemaps[cubemapIndex].origin, refdef.vieworg);
+
+ switch(cubemapSide)
+ {
+ case 0:
+ // -X
+ VectorSet( refdef.viewaxis[0], -1, 0, 0);
+ VectorSet( refdef.viewaxis[1], 0, 0, -1);
+ VectorSet( refdef.viewaxis[2], 0, 1, 0);
+ break;
+ case 1:
+ // +X
+ VectorSet( refdef.viewaxis[0], 1, 0, 0);
+ VectorSet( refdef.viewaxis[1], 0, 0, 1);
+ VectorSet( refdef.viewaxis[2], 0, 1, 0);
+ break;
+ case 2:
+ // -Y
+ VectorSet( refdef.viewaxis[0], 0, -1, 0);
+ VectorSet( refdef.viewaxis[1], 1, 0, 0);
+ VectorSet( refdef.viewaxis[2], 0, 0, -1);
+ break;
+ case 3:
+ // +Y
+ VectorSet( refdef.viewaxis[0], 0, 1, 0);
+ VectorSet( refdef.viewaxis[1], 1, 0, 0);
+ VectorSet( refdef.viewaxis[2], 0, 0, 1);
+ break;
+ case 4:
+ // -Z
+ VectorSet( refdef.viewaxis[0], 0, 0, -1);
+ VectorSet( refdef.viewaxis[1], 1, 0, 0);
+ VectorSet( refdef.viewaxis[2], 0, 1, 0);
+ break;
+ case 5:
+ // +Z
+ VectorSet( refdef.viewaxis[0], 0, 0, 1);
+ VectorSet( refdef.viewaxis[1], -1, 0, 0);
+ VectorSet( refdef.viewaxis[2], 0, 1, 0);
+ break;
+ }
+
+ refdef.fov_x = 90;
+ refdef.fov_y = 90;
+
+ refdef.x = 0;
+ refdef.y = 0;
+ refdef.width = tr.renderCubeFbo->width;
+ refdef.height = tr.renderCubeFbo->height;
+
+ refdef.time = 0;
+
+ if (!subscene)
+ {
+ RE_BeginScene(&refdef);
+
+ // FIXME: sun shadows aren't rendered correctly in cubemaps
+ // fix involves changing r_FBufScale to fit smaller cubemap image size, or rendering cubemap to framebuffer first
+ if(0) //(glRefConfig.framebufferObject && r_sunlightMode->integer && (r_forceSun->integer || tr.sunShadows))
+ {
+ R_RenderSunShadowMaps(&refdef, 0);
+ R_RenderSunShadowMaps(&refdef, 1);
+ R_RenderSunShadowMaps(&refdef, 2);
+ R_RenderSunShadowMaps(&refdef, 3);
+ }
+ }
+
+ {
+ vec3_t ambient, directed, lightDir;
+ float scale;
+
+ R_LightForPoint(tr.refdef.vieworg, ambient, directed, lightDir);
+ scale = directed[0] + directed[1] + directed[2] + ambient[0] + ambient[1] + ambient[2] + 1.0f;
+
+ // only print message for first side
+ if (scale < 1.0001f && cubemapSide == 0)
+ {
+ ri.Printf(PRINT_ALL, "cubemap %d %s (%f, %f, %f) is outside the lightgrid or inside a wall!\n", cubemapIndex, tr.cubemaps[cubemapIndex].name, tr.refdef.vieworg[0], tr.refdef.vieworg[1], tr.refdef.vieworg[2]);
+ }
+ }
+
+ Com_Memset( &parms, 0, sizeof( parms ) );
+
+ parms.viewportX = 0;
+ parms.viewportY = 0;
+ parms.viewportWidth = tr.renderCubeFbo->width;
+ parms.viewportHeight = tr.renderCubeFbo->height;
+ parms.isPortal = false;
+ parms.isMirror = true;
+ parms.flags = VPF_NOVIEWMODEL | VPF_NOCUBEMAPS;
+
+ parms.fovX = 90;
+ parms.fovY = 90;
+
+ VectorCopy( refdef.vieworg, parms.orientation.origin );
+ VectorCopy( refdef.viewaxis[0], parms.orientation.axis[0] );
+ VectorCopy( refdef.viewaxis[1], parms.orientation.axis[1] );
+ VectorCopy( refdef.viewaxis[2], parms.orientation.axis[2] );
+
+ VectorCopy( refdef.vieworg, parms.pvsOrigin );
+
+ // FIXME: sun shadows aren't rendered correctly in cubemaps
+ // fix involves changing r_FBufScale to fit smaller cubemap image size, or rendering cubemap to framebuffer first
+ if (0) //(r_depthPrepass->value && ((r_forceSun->integer) || tr.sunShadows))
+ {
+ parms.flags = VPF_USESUNLIGHT;
+ }
+
+ parms.targetFbo = tr.renderCubeFbo;
+ parms.targetFboLayer = cubemapSide;
+ parms.targetFboCubemapIndex = cubemapIndex;
+
+ R_RenderView(&parms);
+
+ if (!subscene)
+ RE_EndScene();
+}
diff --git a/src/renderergl2/tr_marks.cpp b/src/renderergl2/tr_marks.cpp
new file mode 100644
index 0000000..b56005a
--- /dev/null
+++ b/src/renderergl2/tr_marks.cpp
@@ -0,0 +1,472 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// 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] = { 0 };
+ int sides[MAX_VERTS_ON_POLY+4] = { 0 };
+ 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;
+ srfBspSurface_t *cv;
+ glIndex_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 = (srfBspSurface_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.
+ vec3_t fNormal;
+
+ numClipPoints = 3;
+
+ dv = cv->verts + m * cv->width + n;
+
+ VectorCopy(dv[0].xyz, clipPoints[0][0]);
+ R_VaoUnpackNormal(fNormal, dv[0].normal);
+ VectorMA(clipPoints[0][0], MARKER_OFFSET, fNormal, clipPoints[0][0]);
+ VectorCopy(dv[cv->width].xyz, clipPoints[0][1]);
+ R_VaoUnpackNormal(fNormal, dv[cv->width].normal);
+ VectorMA(clipPoints[0][1], MARKER_OFFSET, fNormal, clipPoints[0][1]);
+ VectorCopy(dv[1].xyz, clipPoints[0][2]);
+ R_VaoUnpackNormal(fNormal, dv[1].normal);
+ VectorMA(clipPoints[0][2], MARKER_OFFSET, fNormal, 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]);
+ R_VaoUnpackNormal(fNormal, dv[1].normal);
+ VectorMA(clipPoints[0][0], MARKER_OFFSET, fNormal, clipPoints[0][0]);
+ VectorCopy(dv[cv->width].xyz, clipPoints[0][1]);
+ R_VaoUnpackNormal(fNormal, dv[cv->width].normal);
+ VectorMA(clipPoints[0][1], MARKER_OFFSET, fNormal, clipPoints[0][1]);
+ VectorCopy(dv[cv->width+1].xyz, clipPoints[0][2]);
+ R_VaoUnpackNormal(fNormal, dv[cv->width + 1].normal);
+ VectorMA(clipPoints[0][2], MARKER_OFFSET, fNormal, 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) {
+
+ srfBspSurface_t *surf = ( srfBspSurface_t * ) surfaces[i];
+
+ // check the normal of this face
+ if (DotProduct(surf->cullPlane.normal, projectionDir) > -0.5) {
+ continue;
+ }
+
+ for(k = 0, tri = surf->indexes; k < surf->numIndexes; k += 3, tri += 3)
+ {
+ for(j = 0; j < 3; j++)
+ {
+ v = surf->verts[tri[j]].xyz;
+ VectorMA(v, MARKER_OFFSET, surf->cullPlane.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) {
+
+ srfBspSurface_t *surf = (srfBspSurface_t *) surfaces[i];
+
+ for(k = 0, tri = surf->indexes; k < surf->numIndexes; k += 3, tri += 3)
+ {
+ for(j = 0; j < 3; j++)
+ {
+ vec3_t fNormal;
+ v = surf->verts[tri[j]].xyz;
+ R_VaoUnpackNormal(fNormal, surf->verts[tri[j]].normal);
+ VectorMA(v, MARKER_OFFSET, fNormal, 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/renderergl2/tr_mesh.cpp b/src/renderergl2/tr_mesh.cpp
new file mode 100644
index 0000000..2761f83
--- /dev/null
+++ b/src/renderergl2/tr_mesh.cpp
@@ -0,0 +1,418 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// 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.orientation.axis[0], tr.viewParms.orientation.origin );
+ dist = DotProduct( tr.viewParms.orientation.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;
+ mdrHeader_t *mdr;
+ mdrFrame_t *mdrframe;
+ 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
+
+ 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
+ {
+ //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 = static_cast<int>(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;
+ int cubemapIndex;
+ bool 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 );
+
+ cubemapIndex = R_CubemapForPoint(ent->e.origin);
+
+ //
+ // 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 ] ];
+ }
+
+ // 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( (surfaceType_t*)&model->vaoSurfaces[i], tr.shadowShader, 0, false, false, 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( (surfaceType_t*)&model->vaoSurfaces[i], tr.projectionShadowShader, 0, false, false, 0 );
+ }
+
+ // don't add third_person objects if not viewing through a portal
+ if ( !personalModel )
+ R_AddDrawSurf((surfaceType_t*)&model->vaoSurfaces[i], shader, fogNum, false, false, cubemapIndex );
+
+ surface++;
+ }
+
+}
diff --git a/src/renderergl2/tr_model.cpp b/src/renderergl2/tr_model.cpp
new file mode 100644
index 0000000..6e3b984
--- /dev/null
+++ b/src/renderergl2/tr_model.cpp
@@ -0,0 +1,1419 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// tr_models.c -- model loading and caching
+
+#include "tr_local.h"
+
+#define LL(x) x=LittleLong(x)
+
+static bool R_LoadMD3(model_t *mod, int lod, void *buffer, int bufferSize, const char *modName);
+static bool R_LoadMDR(model_t *mod, void *buffer, int filesize, const char *name );
+
+/*
+====================
+R_RegisterMD3
+====================
+*/
+qhandle_t R_RegisterMD3(const char *name, model_t *mod)
+{
+ union {
+ unsigned *u;
+ void *v;
+ } buf;
+ int size;
+ int lod;
+ int ident;
+ bool loaded = false;
+ 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 == 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;
+ }
+
+ ri.Printf(PRINT_DEVELOPER, "R_RegisterMD3: couldn't load %s\n", name);
+
+ mod->type = MOD_BAD;
+ return 0;
+}
+
+/*
+====================
+R_RegisterMDR
+====================
+*/
+qhandle_t R_RegisterMDR(const char *name, model_t *mod)
+{
+ union {
+ unsigned *u;
+ void *v;
+ } buf;
+ int ident;
+ bool loaded = false;
+ 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;
+}
+
+/*
+====================
+R_RegisterIQM
+====================
+*/
+qhandle_t R_RegisterIQM(const char *name, model_t *mod)
+{
+ union {
+ unsigned *u;
+ void *v;
+ } buf;
+ bool loaded = false;
+ 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;
+}
+
+
+struct modelExtToLoaderMap_t
+{
+ const char *ext;
+ qhandle_t (*ModelLoader)( const char *, model_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 },
+ { "mdr", R_RegisterMDR },
+ { "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 = (model_t*)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;
+ bool orgNameFailed = false;
+ 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 ) );
+
+
+ R_IssuePendingRenderCommands();
+
+ 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 = true;
+ 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 bool R_LoadMD3(model_t * mod, int lod, void *buffer, int bufferSize, const char *modName)
+{
+ int f, i, j;
+
+ 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;
+ glIndex_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 false;
+ }
+
+ mod->type = MOD_MESH;
+ size = LittleLong(md3Model->ofsEnd);
+ mod->dataSize += size;
+ mdvModel = mod->mdv[lod] = (mdvModel_t*)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 false;
+ }
+
+ // swap all the frames
+ mdvModel->numFrames = md3Model->numFrames;
+ mdvModel->frames = frame = (mdvFrame_t*)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 = (mdvTag_t*)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 = (mdvTagName_t*)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 = (mdvSurface_t*)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 %s (%i).\n",
+ modName, SHADER_MAX_VERTEXES - 1, md3Surf->name[0] ? md3Surf->name : "a surface",
+ md3Surf->numVerts );
+ return false;
+ }
+ if(md3Surf->numTriangles * 3 >= SHADER_MAX_INDEXES)
+ {
+ ri.Printf(PRINT_WARNING, "R_LoadMD3: %s has more than %i triangles on %s (%i).\n",
+ modName, ( SHADER_MAX_INDEXES / 3 ) - 1, md3Surf->name[0] ? md3Surf->name : "a surface",
+ md3Surf->numTriangles );
+ return false;
+ }
+
+ // 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 = (int*)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, true);
+ if(sh->defaultShader)
+ {
+ *shaderIndex = 0;
+ }
+ else
+ {
+ *shaderIndex = sh->index;
+ }
+ }
+
+ // swap all the triangles
+ surf->numIndexes = md3Surf->numTriangles * 3;
+ surf->indexes = tri = (glIndex_t*)ri.Hunk_Alloc(sizeof(*tri) * 3 * md3Surf->numTriangles, h_low);
+
+ md3Tri = (md3Triangle_t *) ((byte *) md3Surf + md3Surf->ofsTriangles);
+ for(j = 0; j < md3Surf->numTriangles; j++, tri += 3, md3Tri++)
+ {
+ tri[0] = LittleLong(md3Tri->indexes[0]);
+ tri[1] = LittleLong(md3Tri->indexes[1]);
+ tri[2] = LittleLong(md3Tri->indexes[2]);
+ }
+
+ // swap all the XyzNormals
+ surf->numVerts = md3Surf->numVerts;
+ surf->verts = v = (mdvVertex_t*)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;
+ vec3_t fNormal;
+
+ 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 )
+
+ fNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng];
+ fNormal[1] = tr.sinTable[lat] * tr.sinTable[lng];
+ fNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK];
+
+ R_VaoPackNormal(v->normal, fNormal);
+ }
+
+ // swap all the ST
+ surf->st = st = (mdvSt_t*)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]);
+ }
+
+ // calc tangent spaces
+ {
+ vec3_t *sdirs = (vec3_t*)ri.Malloc(sizeof(*sdirs) * surf->numVerts * mdvModel->numFrames);
+ vec3_t *tdirs = (vec3_t*)ri.Malloc(sizeof(*tdirs) * surf->numVerts * mdvModel->numFrames);
+
+ for(j = 0, v = surf->verts; j < (surf->numVerts * mdvModel->numFrames); j++, v++)
+ {
+ VectorClear(sdirs[j]);
+ VectorClear(tdirs[j]);
+ }
+
+ for(f = 0; f < mdvModel->numFrames; f++)
+ {
+ for(j = 0, tri = surf->indexes; j < surf->numIndexes; j += 3, tri += 3)
+ {
+ vec3_t sdir, tdir;
+ const float *v0, *v1, *v2, *t0, *t1, *t2;
+ glIndex_t index0, index1, index2;
+
+ index0 = surf->numVerts * f + tri[0];
+ index1 = surf->numVerts * f + tri[1];
+ index2 = surf->numVerts * f + tri[2];
+
+ v0 = surf->verts[index0].xyz;
+ v1 = surf->verts[index1].xyz;
+ v2 = surf->verts[index2].xyz;
+
+ t0 = surf->st[tri[0]].st;
+ t1 = surf->st[tri[1]].st;
+ t2 = surf->st[tri[2]].st;
+
+ R_CalcTexDirs(sdir, tdir, v0, v1, v2, t0, t1, t2);
+
+ VectorAdd(sdir, sdirs[index0], sdirs[index0]);
+ VectorAdd(sdir, sdirs[index1], sdirs[index1]);
+ VectorAdd(sdir, sdirs[index2], sdirs[index2]);
+ VectorAdd(tdir, tdirs[index0], tdirs[index0]);
+ VectorAdd(tdir, tdirs[index1], tdirs[index1]);
+ VectorAdd(tdir, tdirs[index2], tdirs[index2]);
+ }
+ }
+
+ for(j = 0, v = surf->verts; j < (surf->numVerts * mdvModel->numFrames); j++, v++)
+ {
+ vec3_t normal;
+ vec4_t tangent;
+
+ VectorNormalize(sdirs[j]);
+ VectorNormalize(tdirs[j]);
+
+ R_VaoUnpackNormal(normal, v->normal);
+
+ tangent[3] = R_CalcTangentSpace(tangent, NULL, normal, sdirs[j], tdirs[j]);
+
+ R_VaoPackTangent(v->tangent, tangent);
+ }
+
+ ri.Free(sdirs);
+ ri.Free(tdirs);
+ }
+
+ // find the next surface
+ md3Surf = (md3Surface_t *) ((byte *) md3Surf + md3Surf->ofsEnd);
+ surf++;
+ }
+
+ {
+ srfVaoMdvMesh_t *vaoSurf;
+
+ mdvModel->numVaoSurfaces = mdvModel->numSurfaces;
+ mdvModel->vaoSurfaces = (srfVaoMdvMesh_t*)ri.Hunk_Alloc(sizeof(*mdvModel->vaoSurfaces) * mdvModel->numSurfaces, h_low);
+
+ vaoSurf = mdvModel->vaoSurfaces;
+ surf = mdvModel->surfaces;
+ for (i = 0; i < mdvModel->numSurfaces; i++, vaoSurf++, surf++)
+ {
+ uint32_t offset_xyz, offset_st, offset_normal, offset_tangent;
+ uint32_t stride_xyz, stride_st, stride_normal, stride_tangent;
+ uint32_t dataSize, dataOfs;
+ uint8_t *data;
+
+ if (mdvModel->numFrames > 1)
+ {
+ // vertex animation, store texcoords first, then position/normal/tangents
+ offset_st = 0;
+ offset_xyz = surf->numVerts * sizeof(vec2_t);
+ offset_normal = offset_xyz + sizeof(vec3_t);
+ offset_tangent = offset_normal + sizeof(int16_t) * 4;
+ stride_st = sizeof(vec2_t);
+ stride_xyz = sizeof(vec3_t) + sizeof(int16_t) * 4;
+ stride_xyz += sizeof(int16_t) * 4;
+ stride_normal = stride_tangent = stride_xyz;
+
+ dataSize = offset_xyz + surf->numVerts * mdvModel->numFrames * stride_xyz;
+ }
+ else
+ {
+ // no animation, interleave everything
+ offset_xyz = 0;
+ offset_st = offset_xyz + sizeof(vec3_t);
+ offset_normal = offset_st + sizeof(vec2_t);
+ offset_tangent = offset_normal + sizeof(int16_t) * 4;
+ stride_xyz = offset_tangent + sizeof(int16_t) * 4;
+ stride_st = stride_normal = stride_tangent = stride_xyz;
+
+ dataSize = surf->numVerts * stride_xyz;
+ }
+
+
+ data = (uint8_t*)ri.Malloc(dataSize);
+ dataOfs = 0;
+
+ if (mdvModel->numFrames > 1)
+ {
+ st = surf->st;
+ for ( j = 0 ; j < surf->numVerts ; j++, st++ ) {
+ memcpy(data + dataOfs, &st->st, sizeof(vec2_t));
+ dataOfs += sizeof(st->st);
+ }
+
+ v = surf->verts;
+ for ( j = 0; j < surf->numVerts * mdvModel->numFrames ; j++, v++ )
+ {
+ // xyz
+ memcpy(data + dataOfs, &v->xyz, sizeof(vec3_t));
+ dataOfs += sizeof(vec3_t);
+
+ // normal
+ memcpy(data + dataOfs, &v->normal, sizeof(int16_t) * 4);
+ dataOfs += sizeof(int16_t) * 4;
+
+ // tangent
+ memcpy(data + dataOfs, &v->tangent, sizeof(int16_t) * 4);
+ dataOfs += sizeof(int16_t) * 4;
+ }
+ }
+ else
+ {
+ v = surf->verts;
+ st = surf->st;
+ for ( j = 0; j < surf->numVerts; j++, v++, st++ )
+ {
+ // xyz
+ memcpy(data + dataOfs, &v->xyz, sizeof(vec3_t));
+ dataOfs += sizeof(v->xyz);
+
+ // st
+ memcpy(data + dataOfs, &st->st, sizeof(vec2_t));
+ dataOfs += sizeof(st->st);
+
+ // normal
+ memcpy(data + dataOfs, &v->normal, sizeof(int16_t) * 4);
+ dataOfs += sizeof(int16_t) * 4;
+
+ // tangent
+ memcpy(data + dataOfs, &v->tangent, sizeof(int16_t) * 4);
+ dataOfs += sizeof(int16_t) * 4;
+ }
+ }
+
+ vaoSurf->surfaceType = SF_VAO_MDVMESH;
+ vaoSurf->mdvModel = mdvModel;
+ vaoSurf->mdvSurface = surf;
+ vaoSurf->numIndexes = surf->numIndexes;
+ vaoSurf->numVerts = surf->numVerts;
+
+ vaoSurf->minIndex = 0;
+ vaoSurf->maxIndex = surf->numVerts - 1;
+
+ vaoSurf->vao = R_CreateVao(va("staticMD3Mesh_VAO '%s'", surf->name), data, dataSize, (byte *)surf->indexes, surf->numIndexes * sizeof(*surf->indexes), VAO_USAGE_STATIC);
+
+ vaoSurf->vao->attribs[ATTR_INDEX_POSITION].enabled = 1;
+ vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].enabled = 1;
+ vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].enabled = 1;
+ vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].enabled = 1;
+
+ vaoSurf->vao->attribs[ATTR_INDEX_POSITION].count = 3;
+ vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].count = 2;
+ vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].count = 4;
+ vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].count = 4;
+
+ vaoSurf->vao->attribs[ATTR_INDEX_POSITION].type = GL_FLOAT;
+ vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].type = GL_FLOAT;
+ vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].type = GL_SHORT;
+ vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].type = GL_SHORT;
+
+ vaoSurf->vao->attribs[ATTR_INDEX_POSITION].normalized = GL_FALSE;
+ vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].normalized = GL_FALSE;
+ vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].normalized = GL_TRUE;
+ vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].normalized = GL_TRUE;
+
+ vaoSurf->vao->attribs[ATTR_INDEX_POSITION].offset = offset_xyz;
+ vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].offset = offset_st;
+ vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].offset = offset_normal;
+ vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].offset = offset_tangent;
+
+ vaoSurf->vao->attribs[ATTR_INDEX_POSITION].stride = stride_xyz;
+ vaoSurf->vao->attribs[ATTR_INDEX_TEXCOORD].stride = stride_st;
+ vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ].stride = stride_normal;
+ vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ].stride = stride_tangent;
+
+ if (mdvModel->numFrames > 1)
+ {
+ vaoSurf->vao->attribs[ATTR_INDEX_POSITION2] = vaoSurf->vao->attribs[ATTR_INDEX_POSITION];
+ vaoSurf->vao->attribs[ATTR_INDEX_NORMAL2 ] = vaoSurf->vao->attribs[ATTR_INDEX_NORMAL ];
+ vaoSurf->vao->attribs[ATTR_INDEX_TANGENT2 ] = vaoSurf->vao->attribs[ATTR_INDEX_TANGENT ];
+
+ vaoSurf->vao->frameSize = stride_xyz * surf->numVerts;
+ }
+
+ Vao_SetVertexPointers(vaoSurf->vao);
+
+ ri.Free(data);
+ }
+ }
+
+ return true;
+}
+
+
+
+/*
+=================
+R_LoadMDR
+=================
+*/
+static bool 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 false;
+ }
+
+ 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 false;
+ }
+
+ 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 mdr.
+ 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 false;
+ }
+
+ mod->dataSize += size;
+ mod->modelData = mdr = (mdrHeader_t*)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 false;
+ }
+
+ /* 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 false;
+ }
+
+ 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 false;
+ }
+
+ // 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 %s (%i).\n",
+ mod_name, SHADER_MAX_VERTEXES - 1, surf->name[0] ? surf->name : "a surface",
+ surf->numVerts );
+ return false;
+ }
+ if ( surf->numTriangles*3 >= SHADER_MAX_INDEXES )
+ {
+ ri.Printf(PRINT_WARNING, "R_LoadMDR: %s has more than %i triangles on %s (%i).\n",
+ mod_name, ( SHADER_MAX_INDEXES / 3 ) - 1, surf->name[0] ? surf->name : "a surface",
+ surf->numTriangles );
+ return false;
+ }
+ // lowercase the surface name so skin compares are faster
+ Q_strlwr( surf->name );
+
+ // register the shaders
+ sh = R_FindShader(surf->shader, LIGHTMAP_NONE, true);
+ 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 false;
+ }
+
+ 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 false;
+ }
+
+ 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 false;
+ }
+
+ 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 true;
+}
+
+
+
+//=============================================================================
+
+/*
+** RE_BeginRegistration
+*/
+void RE_BeginRegistration( glconfig_t *glconfigOut ) {
+ int i;
+
+ R_Init();
+
+ *glconfigOut = glConfig;
+
+ R_IssuePendingRenderCommands();
+
+ tr.visIndex = 0;
+ // force markleafs to regenerate
+ for(i = 0; i < MAX_VISCOUNTS; i++) {
+ tr.visClusters[i] = -2;
+ }
+
+ R_ClearFlares();
+ RE_ClearScene();
+
+ tr.registered = true;
+}
+
+//=============================================================================
+
+/*
+===============
+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;
+}
+
+mdvTag_t *R_GetAnimTag( mdrHeader_t *mod, int framenum, const char *tagName, mdvTag_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 ) )
+ {
+ // 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 dest;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+================
+R_LerpTag
+================
+*/
+int R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame,
+ float frac, const char *tagName ) {
+ mdvTag_t *start, *end;
+ mdvTag_t start_space, end_space;
+ int i;
+ float frontLerp, backLerp;
+ model_t *model;
+
+ model = R_GetModelByHandle( handle );
+ if ( !model->mdv[0] )
+ {
+ if(model->type == MOD_MDR)
+ {
+ start = R_GetAnimTag((mdrHeader_t *) model->modelData, startFrame, tagName, &start_space);
+ end = R_GetAnimTag((mdrHeader_t *) model->modelData, endFrame, tagName, &end_space);
+ }
+ else if( model->type == MOD_IQM ) {
+ return R_IQMLerpTag( tag, (iqmData_t*)model->modelData, startFrame, endFrame, frac, tagName );
+ } else {
+ start = end = NULL;
+ }
+ }
+ 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 false;
+ }
+
+ 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 true;
+}
+
+
+/*
+====================
+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_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;
+ } else if(model->type == MOD_IQM) {
+ iqmData_t *iqmData;
+
+ iqmData = (iqmData_t*)model->modelData;
+
+ if(iqmData->bounds)
+ {
+ VectorCopy(iqmData->bounds, mins);
+ VectorCopy(iqmData->bounds + 3, maxs);
+ return;
+ }
+ }
+
+ VectorClear( mins );
+ VectorClear( maxs );
+}
diff --git a/src/renderergl2/tr_model_iqm.cpp b/src/renderergl2/tr_model_iqm.cpp
new file mode 100644
index 0000000..40ce9da
--- /dev/null
+++ b/src/renderergl2/tr_model_iqm.cpp
@@ -0,0 +1,1196 @@
+/*
+===========================================================================
+Copyright (C) 2011 Thilo Schulz <thilo@tjps.eu>
+Copyright (C) 2011 Matthias Bentrup <matthias.bentrup@googlemail.com>
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "tr_local.h"
+
+#define LL(x) x=LittleLong(x)
+
+// 3x4 identity matrix
+static float identityMatrix[12] = {
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0
+};
+
+static bool 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 Matrix34Multiply_OnlySetOrigin( float *a, float *b, float *out ) {
+ out[ 3] = a[0] * b[3] + a[1] * b[7] + a[ 2] * b[11] + a[ 3];
+ out[ 7] = a[4] * b[3] + a[5] * b[7] + a[ 6] * b[11] + a[ 7];
+ 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.
+=================
+*/
+bool 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 jointInvMats[IQM_MAX_JOINTS * 12] = {0.0f};
+ float *mat, *matInv;
+ size_t size, joint_names;
+ iqmData_t *iqmData;
+ srfIQModel_t *surface;
+ char meshName[MAX_QPATH];
+ byte blendIndexesType, blendWeightsType;
+
+ if( filesize < sizeof(iqmHeader_t) ) {
+ return false;
+ }
+
+ header = (iqmHeader_t *)buffer;
+ if( Q_strncmp( header->magic, IQM_MAGIC, sizeof(header->magic) ) ) {
+ return false;
+ }
+
+ 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 false;
+ }
+
+ LL( header->filesize );
+ if( header->filesize > filesize || header->filesize > 16<<20 ) {
+ return false;
+ }
+
+ 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 false;
+ }
+
+ blendIndexesType = blendWeightsType = IQM_UBYTE;
+
+ // check and swap vertex arrays
+ if( IQM_CheckRange( header, header->ofs_vertexarrays,
+ header->num_vertexarrays,
+ sizeof(iqmVertexArray_t) ) ) {
+ return false;
+ }
+ vertexarray = (iqmVertexArray_t *)((byte *)header + header->ofs_vertexarrays);
+ for( i = 0; i < header->num_vertexarrays; i++, vertexarray++ ) {
+ int n, *intPtr;
+
+ if( vertexarray->size <= 0 || vertexarray->size > 4 ) {
+ return false;
+ }
+
+ // 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 false;
+ }
+ break;
+ case IQM_INT:
+ case IQM_UINT:
+ case IQM_FLOAT:
+ // 4-byte swap
+ if( IQM_CheckRange( header, vertexarray->offset,
+ n, sizeof(float) ) ) {
+ return false;
+ }
+ intPtr = (int *)((byte *)header + vertexarray->offset);
+ for( j = 0; j < n; j++, intPtr++ ) {
+ LL( *intPtr );
+ }
+ break;
+ default:
+ // not supported
+ return false;
+ break;
+ }
+
+ switch( vertexarray->type ) {
+ case IQM_POSITION:
+ case IQM_NORMAL:
+ if( vertexarray->format != IQM_FLOAT ||
+ vertexarray->size != 3 ) {
+ return false;
+ }
+ break;
+ case IQM_TANGENT:
+ if( vertexarray->format != IQM_FLOAT ||
+ vertexarray->size != 4 ) {
+ return false;
+ }
+ break;
+ case IQM_TEXCOORD:
+ if( vertexarray->format != IQM_FLOAT ||
+ vertexarray->size != 2 ) {
+ return false;
+ }
+ break;
+ case IQM_BLENDINDEXES:
+ if( (vertexarray->format != IQM_INT &&
+ vertexarray->format != IQM_UBYTE) ||
+ vertexarray->size != 4 ) {
+ return false;
+ }
+ blendIndexesType = vertexarray->format;
+ break;
+ case IQM_BLENDWEIGHTS:
+ if( (vertexarray->format != IQM_FLOAT &&
+ vertexarray->format != IQM_UBYTE) ||
+ vertexarray->size != 4 ) {
+ return false;
+ }
+ blendWeightsType = vertexarray->format;
+ break;
+ case IQM_COLOR:
+ if( vertexarray->format != IQM_UBYTE ||
+ vertexarray->size != 4 ) {
+ return false;
+ }
+ break;
+ }
+ }
+
+ // check and swap triangles
+ if( IQM_CheckRange( header, header->ofs_triangles,
+ header->num_triangles, sizeof(iqmTriangle_t) ) ) {
+ return false;
+ }
+ 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 false;
+ }
+ }
+
+ // check and swap meshes
+ if( IQM_CheckRange( header, header->ofs_meshes,
+ header->num_meshes, sizeof(iqmMesh_t) ) ) {
+ return false;
+ }
+ 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 );
+
+ if ( mesh->name < header->num_text ) {
+ Q_strncpyz( meshName, (char*)header + header->ofs_text + mesh->name, sizeof (meshName) );
+ } else {
+ meshName[0] = '\0';
+ }
+
+ // check ioq3 limits
+ if ( mesh->num_vertexes >= SHADER_MAX_VERTEXES )
+ {
+ ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has more than %i verts on %s (%i).\n",
+ mod_name, SHADER_MAX_VERTEXES - 1, meshName[0] ? meshName : "a surface",
+ mesh->num_vertexes );
+ return false;
+ }
+ if ( mesh->num_triangles*3 >= SHADER_MAX_INDEXES )
+ {
+ ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has more than %i triangles on %s (%i).\n",
+ mod_name, ( SHADER_MAX_INDEXES / 3 ) - 1, meshName[0] ? meshName : "a surface",
+ mesh->num_triangles );
+ return false;
+ }
+
+ 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 false;
+ }
+ }
+
+ if( header->num_poses != header->num_joints && header->num_poses != 0 ) {
+ ri.Printf(PRINT_WARNING, "R_LoadIQM: %s has %d poses and %d joints, must have the same number or 0 poses\n",
+ mod_name, header->num_poses, header->num_joints );
+ return false;
+ }
+
+ joint_names = 0;
+
+ if ( header->num_joints )
+ {
+ // check and swap joints
+ if( IQM_CheckRange( header, header->ofs_joints,
+ header->num_joints, sizeof(iqmJoint_t) ) ) {
+ return false;
+ }
+ joint = (iqmJoint_t *)((byte *)header + header->ofs_joints);
+ 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 false;
+ }
+ joint_names += strlen( (char *)header + header->ofs_text +
+ joint->name ) + 1;
+ }
+ }
+
+ if ( header->num_poses )
+ {
+ // check and swap poses
+ if( IQM_CheckRange( header, header->ofs_poses,
+ header->num_poses, sizeof(iqmPose_t) ) ) {
+ return false;
+ }
+ 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 false;
+ }
+ 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 * 12 * sizeof( float ); // joint mats
+ size += header->num_poses * header->num_frames * 12 * sizeof( float ); // pose mats
+ 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); // colors
+ size += header->num_joints * sizeof(int); // parents
+ size += header->num_triangles * 3 * sizeof(int); // triangles
+ size += joint_names; // joint names
+
+ // blendWeights
+ if (blendWeightsType == IQM_FLOAT) {
+ size += header->num_vertexes * 4 * sizeof(float);
+ } else {
+ size += header->num_vertexes * 4 * sizeof(byte);
+ }
+
+ 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->num_poses = header->num_poses;
+ iqmData->blendWeightsType = blendWeightsType;
+ iqmData->surfaces = (srfIQModel_t *)(iqmData + 1);
+ iqmData->jointMats = (float *) (iqmData->surfaces + iqmData->num_surfaces);
+ iqmData->poseMats = iqmData->jointMats + 12 * header->num_joints;
+ if(header->ofs_bounds)
+ {
+ iqmData->bounds = iqmData->poseMats + 12 * header->num_poses * header->num_frames;
+ iqmData->positions = iqmData->bounds + 6 * header->num_frames;
+ }
+ else
+ iqmData->positions = iqmData->poseMats + 12 * header->num_poses * 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);
+
+ if(blendWeightsType == IQM_FLOAT) {
+ iqmData->blendWeights.f = (float *)(iqmData->blendIndexes + 4 * header->num_vertexes);
+ iqmData->colors = (byte *)(iqmData->blendWeights.f + 4 * header->num_vertexes);
+ } else {
+ iqmData->blendWeights.b = iqmData->blendIndexes + 4 * header->num_vertexes;
+ iqmData->colors = iqmData->blendWeights.b + 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);
+
+ if ( header->num_joints == 0 )
+ iqmData->jointMats = NULL;
+
+ if ( header->num_poses == 0 )
+ iqmData->poseMats = NULL;
+
+ // calculate joint matrices and their inverses
+ // joint inverses are needed only until the pose matrices are calculated
+ mat = iqmData->jointMats;
+ matInv = jointInvMats;
+ 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( iqmData->jointMats + 12 * joint->parent, baseFrame, mat );
+ mat += 12;
+ Matrix34Multiply( invBaseFrame, jointInvMats + 12 * joint->parent, matInv );
+ matInv += 12;
+ }
+ else
+ {
+ Com_Memcpy( mat, baseFrame, sizeof(baseFrame) );
+ mat += 12;
+ Com_Memcpy( matInv, invBaseFrame, sizeof(invBaseFrame) );
+ matInv += 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( iqmData->jointMats + 12 * pose->parent,
+ mat1, mat2 );
+ } else {
+ Com_Memcpy( mat2, mat1, sizeof(mat1) );
+ }
+
+ Matrix34Multiply( mat2, jointInvMats + 12 * j, 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, true );
+ 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:
+ if( blendIndexesType == IQM_INT ) {
+ int *data = (int*)((byte*)header + vertexarray->offset);
+ for ( j = 0; j < n; j++ ) {
+ iqmData->blendIndexes[j] = (byte)data[j];
+ }
+ } else {
+ Com_Memcpy( iqmData->blendIndexes,
+ (byte *)header + vertexarray->offset,
+ n * sizeof(byte) );
+ }
+ break;
+ case IQM_BLENDWEIGHTS:
+ if( blendWeightsType == IQM_FLOAT ) {
+ Com_Memcpy( iqmData->blendWeights.f,
+ (byte *)header + vertexarray->offset,
+ n * sizeof(float) );
+ } else {
+ Com_Memcpy( iqmData->blendWeights.b,
+ (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 true;
+}
+
+/*
+=============
+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;
+ bool personalModel;
+ int cull;
+ int fogNum;
+ int cubemapIndex;
+ shader_t *shader;
+ skin_t *skin;
+
+ data = (iqmData_t*)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
+ || (tr.viewParms.flags & (VPF_SHADOWMAP | VPF_DEPTHSHADOW)));
+
+
+ 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 );
+
+ cubemapIndex = R_CubemapForPoint(ent->e.origin);
+
+ 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( (surfaceType_t*)surface, tr.shadowShader, 0, 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( (surfaceType_t*)surface, tr.projectionShadowShader, 0, 0, 0, 0 );
+ }
+
+ if( !personalModel ) {
+ R_AddDrawSurf( (surfaceType_t*)surface, shader, fogNum, 0, 0, cubemapIndex );
+ }
+
+ surface++;
+ }
+}
+
+
+static void ComputePoseMats( iqmData_t *data, int frame, int oldframe,
+ float backlerp, float *mat ) {
+ float *mat1, *mat2;
+ int *joint = data->jointParents;
+ int i;
+
+ if ( data->num_poses == 0 ) {
+ for( i = 0; i < data->num_joints; i++, joint++ ) {
+ if( *joint >= 0 ) {
+ Matrix34Multiply( mat + 12 * *joint,
+ identityMatrix, mat + 12*i );
+ } else {
+ Com_Memcpy( mat + 12*i, identityMatrix, 12 * sizeof(float) );
+ }
+ }
+ return;
+ }
+
+ if ( oldframe == frame ) {
+ mat1 = data->poseMats + 12 * data->num_poses * frame;
+ for( i = 0; i < data->num_poses; 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_poses * frame;
+ mat2 = data->poseMats + 12 * data->num_poses * oldframe;
+
+ for( i = 0; i < data->num_poses; 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 );
+ }
+ }
+ }
+}
+
+static void ComputeJointMats( iqmData_t *data, int frame, int oldframe,
+ float backlerp, float *mat ) {
+ float *mat1;
+ int i;
+
+ ComputePoseMats( data, frame, oldframe, backlerp, mat );
+
+ for( i = 0; i < data->num_joints; i++ ) {
+ float outmat[12];
+ mat1 = mat + 12 * i;
+
+ Com_Memcpy(outmat, mat1, sizeof(outmat));
+
+ Matrix34Multiply_OnlySetOrigin( outmat, data->jointMats + 12 * i, mat1 );
+ }
+}
+
+
+/*
+=================
+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;
+ int16_t *outNormal;
+ int16_t *outTangent;
+ vec2_t *outTexCoord;
+ uint16_t *outColor;
+
+ int frame = data->num_frames ? backEnd.currentEntity->e.frame % data->num_frames : 0;
+ int oldframe = data->num_frames ? backEnd.currentEntity->e.oldframe % data->num_frames : 0;
+ float backlerp = backEnd.currentEntity->e.backlerp;
+
+ int *tri;
+ glIndex_t *ptr;
+ glIndex_t base;
+
+ RB_CHECKOVERFLOW( surf->num_vertexes, surf->num_triangles * 3 );
+
+ outXYZ = &tess.xyz[tess.numVertexes];
+ outNormal = tess.normal[tess.numVertexes];
+ outTangent = tess.tangent[tess.numVertexes];
+ outTexCoord = &tess.texCoords[tess.numVertexes];
+ outColor = tess.color[tess.numVertexes];
+
+ // compute interpolated joint matrices
+ if ( data->num_poses > 0 ) {
+ ComputePoseMats( data, frame, oldframe, backlerp, jointMats );
+ }
+
+ // transform vertexes and fill other data
+ for( i = 0; i < surf->num_vertexes;
+ i++, outXYZ++, outNormal+=4, outTexCoord++, outColor+=4 ) {
+ int j, k;
+ float vtxMat[12];
+ float nrmMat[9];
+ int vtx = i + surf->first_vertex;
+ float blendWeights[4];
+ int numWeights;
+
+ for ( numWeights = 0; numWeights < 4; numWeights++ ) {
+ if ( data->blendWeightsType == IQM_FLOAT )
+ blendWeights[numWeights] = data->blendWeights.f[4*vtx + numWeights];
+ else
+ blendWeights[numWeights] = (float)data->blendWeights.b[4*vtx + numWeights] / 255.0f;
+
+ if ( blendWeights[numWeights] <= 0 )
+ break;
+ }
+
+ if ( data->num_poses == 0 || numWeights == 0 ) {
+ // no blend joint, use identity matrix.
+ Com_Memcpy( vtxMat, identityMatrix, 12 * sizeof (float) );
+ } else {
+ // compute the vertex matrix by blending the up to
+ // four blend weights
+ Com_Memset( vtxMat, 0, 12 * sizeof (float) );
+ for( j = 0; j < numWeights; j++ ) {
+ for( k = 0; k < 12; k++ ) {
+ vtxMat[k] += blendWeights[j] * jointMats[12*data->blendIndexes[4*vtx + j] + k];
+ }
+ }
+ }
+
+ // 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] = data->texcoords[2*vtx + 0];
+ (*outTexCoord)[1] = data->texcoords[2*vtx + 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;
+
+ {
+ vec3_t normal;
+ vec4_t tangent;
+
+ normal[0] = DotProduct(&nrmMat[0], &data->normals[3*vtx]);
+ normal[1] = DotProduct(&nrmMat[3], &data->normals[3*vtx]);
+ normal[2] = DotProduct(&nrmMat[6], &data->normals[3*vtx]);
+
+ R_VaoPackNormal(outNormal, normal);
+
+ tangent[0] = DotProduct(&nrmMat[0], &data->tangents[4*vtx]);
+ tangent[1] = DotProduct(&nrmMat[3], &data->tangents[4*vtx]);
+ tangent[2] = DotProduct(&nrmMat[6], &data->tangents[4*vtx]);
+ tangent[3] = data->tangents[4*vtx+3];
+
+ R_VaoPackTangent(outTangent, tangent);
+ outTangent+=4;
+ }
+
+ outColor[0] = data->colors[4*vtx+0] * 257;
+ outColor[1] = data->colors[4*vtx+1] * 257;
+ outColor[2] = data->colors[4*vtx+2] * 257;
+ outColor[3] = data->colors[4*vtx+3] * 257;
+ }
+
+ 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 false;
+ }
+
+ 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 true;
+}
diff --git a/src/renderergl2/tr_postprocess.cpp b/src/renderergl2/tr_postprocess.cpp
new file mode 100644
index 0000000..7d00d01
--- /dev/null
+++ b/src/renderergl2/tr_postprocess.cpp
@@ -0,0 +1,484 @@
+/*
+===========================================================================
+Copyright (C) 2011 Andrei Drexler, Richard Allen, James Canete
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#include "tr_local.h"
+
+void RB_ToneMap(FBO_t *hdrFbo, ivec4_t hdrBox, FBO_t *ldrFbo, ivec4_t ldrBox, int autoExposure)
+{
+ ivec4_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);
+
+ FBO_Blit(hdrFbo, hdrBox, NULL, tr.textureScratchFbo[0], 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, hdrBox, NULL, ldrFbo, ldrBox, &tr.tonemapShader, color, 0);
+}
+
+/*
+=============
+RB_BokehBlur
+
+
+Blurs a part of one framebuffer to another.
+
+Framebuffers can be identical.
+=============
+*/
+void RB_BokehBlur(FBO_t *src, ivec4_t srcBox, FBO_t *dst, ivec4_t dstBox, float blur)
+{
+// ivec4_t srcBox, dstBox;
+ vec4_t color;
+
+ blur *= 10.0f;
+
+ if (blur < 0.004f)
+ return;
+
+ if (glRefConfig.framebufferObject)
+ {
+ // bokeh blur
+ if (blur > 0.0f)
+ {
+ ivec4_t quarterBox;
+
+ quarterBox[0] = 0;
+ quarterBox[1] = tr.quarterFbo[0]->height;
+ quarterBox[2] = tr.quarterFbo[0]->width;
+ quarterBox[3] = -tr.quarterFbo[0]->height;
+
+ // create a quarter texture
+ //FBO_Blit(NULL, NULL, NULL, tr.quarterFbo[0], NULL, NULL, NULL, 0);
+ FBO_FastBlit(src, srcBox, tr.quarterFbo[0], quarterBox, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+ }
+
+#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);
+ FBO_FastBlit(tr.quarterFbo[0], NULL, tr.textureScratchFbo[0], NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+ }
+#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, dst, dstBox, 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, dst, dstBox, NULL, NULL, 0);
+
+ VectorSet4(color, 1, 1, 1, blur - 1.0f);
+
+ FBO_Blit(tr.textureScratchFbo[0], NULL, NULL, dst, dstBox, 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, dst, dstBox, NULL, 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, dst, dstBox, NULL, NULL, 0);
+ }
+#endif
+ }
+}
+
+
+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)
+{
+ ivec4_t srcBox, dstBox;
+ int srcWidth, srcHeight;
+ vec4_t color;
+ const float inc = 1.f / passes;
+ const float mul = powf(stretch, inc);
+ float scale;
+
+ alpha *= inc;
+ VectorSet4(color, alpha, alpha, alpha, 1.0f);
+
+ srcWidth = srcFbo ? srcFbo->width : glConfig.vidWidth;
+ srcHeight = srcFbo ? srcFbo->height : glConfig.vidHeight;
+
+ VectorSet4(srcBox, 0, 0, srcWidth, srcHeight);
+
+ VectorSet4(dstBox, x, y, w, h);
+ FBO_Blit(srcFbo, srcBox, NULL, dstFbo, dstBox, NULL, 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);
+
+ srcBox[0] = s0 * srcWidth;
+ srcBox[1] = t0 * srcHeight;
+ srcBox[2] = iscale * srcWidth;
+ srcBox[3] = iscale * srcHeight;
+
+ FBO_Blit(srcFbo, srcBox, NULL, dstFbo, dstBox, NULL, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
+
+ scale *= mul;
+ --passes;
+ }
+}
+
+
+static bool RB_UpdateSunFlareVis(void)
+{
+ GLuint sampleCount = 0;
+ if (!glRefConfig.occlusionQuery)
+ return true;
+
+ tr.sunFlareQueryIndex ^= 1;
+ if (!tr.sunFlareQueryActive[tr.sunFlareQueryIndex])
+ return true;
+
+ /* debug code */
+ if (0)
+ {
+ int iter;
+ for (iter=0 ; ; ++iter)
+ {
+ GLint available = 0;
+ qglGetQueryObjectiv(tr.sunFlareQuery[tr.sunFlareQueryIndex], GL_QUERY_RESULT_AVAILABLE, &available);
+ if (available)
+ break;
+ }
+
+ ri.Printf(PRINT_DEVELOPER, "Waited %d iterations\n", iter);
+ }
+
+ qglGetQueryObjectuiv(tr.sunFlareQuery[tr.sunFlareQueryIndex], GL_QUERY_RESULT, &sampleCount);
+ return sampleCount > 0;
+}
+
+void RB_SunRays(FBO_t *srcFbo, ivec4_t srcBox, FBO_t *dstFbo, ivec4_t dstBox)
+{
+ vec4_t color;
+ float dot;
+ const float cutoff = 0.25f;
+ bool colorize = true;
+
+// float w, h, w2, h2;
+ mat4_t mvp;
+ vec4_t pos, hpos;
+
+ dot = DotProduct(tr.sunDirection, backEnd.viewParms.orientation.axis[0]);
+ if (dot < cutoff)
+ return;
+
+ if (!RB_UpdateSunFlareVis())
+ return;
+
+ // From RB_DrawSun()
+ {
+ float dist;
+ mat4_t trans, model;
+
+ Mat4Translation( backEnd.viewParms.orientation.origin, trans );
+ Mat4Multiply( backEnd.viewParms.world.modelMatrix, trans, model );
+ Mat4Multiply(backEnd.viewParms.projectionMatrix, model, mvp);
+
+ dist = backEnd.viewParms.zFar / 1.75; // div sqrt(3)
+
+ VectorScale( tr.sunDirection, dist, pos );
+ }
+
+ // project sun point
+ //Mat4Multiply(backEnd.viewParms.projectionMatrix, backEnd.viewParms.world.modelMatrix, mvp);
+ Mat4Transform(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];
+
+ // initialize quarter buffers
+ {
+ float mul = 1.f;
+ ivec4_t rayBox, quarterBox;
+ int srcWidth = srcFbo ? srcFbo->width : glConfig.vidWidth;
+ int srcHeight = srcFbo ? srcFbo->height : glConfig.vidHeight;
+
+ VectorSet4(color, mul, mul, mul, 1);
+
+ rayBox[0] = srcBox[0] * tr.sunRaysFbo->width / srcWidth;
+ rayBox[1] = srcBox[1] * tr.sunRaysFbo->height / srcHeight;
+ rayBox[2] = srcBox[2] * tr.sunRaysFbo->width / srcWidth;
+ rayBox[3] = srcBox[3] * tr.sunRaysFbo->height / srcHeight;
+
+ quarterBox[0] = 0;
+ quarterBox[1] = tr.quarterFbo[0]->height;
+ quarterBox[2] = tr.quarterFbo[0]->width;
+ quarterBox[3] = -tr.quarterFbo[0]->height;
+
+ // first, downsample the framebuffer
+ if (colorize)
+ {
+ FBO_FastBlit(srcFbo, srcBox, tr.quarterFbo[0], quarterBox, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+ FBO_Blit(tr.sunRaysFbo, rayBox, NULL, tr.quarterFbo[0], quarterBox, NULL, color, GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO);
+ }
+ else
+ {
+ FBO_FastBlit(tr.sunRaysFbo, rayBox, tr.quarterFbo[0], quarterBox, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+ }
+ }
+
+ // 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;
+
+ VectorSet4(color, mul, mul, mul, 1);
+
+ FBO_Blit(tr.quarterFbo[0], NULL, NULL, dstFbo, dstBox, NULL, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE);
+ }
+}
+
+static void RB_BlurAxis(FBO_t *srcFbo, FBO_t *dstFbo, float strength, bool 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;
+
+ {
+ ivec4_t srcBox, dstBox;
+ vec4_t color;
+
+ 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, NULL, dstFbo, dstBox, NULL, 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, NULL, dstFbo, dstBox, NULL, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE);
+ VectorSet4(srcBox, -dx, -dy, srcFbo->width, srcFbo->height);
+ FBO_Blit(srcFbo, srcBox, NULL, dstFbo, dstBox, NULL, 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, NULL, dstFbo, dstBox, NULL, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE);
+ VectorSet4(srcBox, -dx, -dy, srcFbo->width, srcFbo->height);
+ FBO_Blit(srcFbo, srcBox, NULL, dstFbo, dstBox, NULL, color, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE);
+ }
+}
+
+static void RB_HBlur(FBO_t *srcFbo, FBO_t *dstFbo, float strength)
+{
+ RB_BlurAxis(srcFbo, dstFbo, strength, true);
+}
+
+static void RB_VBlur(FBO_t *srcFbo, FBO_t *dstFbo, float strength)
+{
+ RB_BlurAxis(srcFbo, dstFbo, strength, false);
+}
+
+void RB_GaussianBlur(float blur)
+{
+ //float mul = 1.f;
+ float factor = Com_Clamp(0.f, 1.f, blur);
+
+ if (factor <= 0.f)
+ return;
+
+ {
+ ivec4_t srcBox, dstBox;
+ vec4_t color;
+
+ VectorSet4(color, 1, 1, 1, 1);
+
+ // first, downsample the framebuffer
+ FBO_FastBlit(NULL, NULL, tr.quarterFbo[0], NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+ FBO_FastBlit(tr.quarterFbo[0], NULL, tr.textureScratchFbo[0], NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+
+ // set the alpha channel
+ qglColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
+ FBO_BlitFromTexture(tr.whiteImage, NULL, NULL, tr.textureScratchFbo[0], NULL, NULL, 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, glConfig.vidWidth, glConfig.vidHeight);
+ color[3] = factor;
+ FBO_Blit(tr.textureScratchFbo[0], srcBox, NULL, NULL, dstBox, NULL, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA);
+ }
+}
diff --git a/src/renderergl2/tr_postprocess.h b/src/renderergl2/tr_postprocess.h
new file mode 100644
index 0000000..de00b88
--- /dev/null
+++ b/src/renderergl2/tr_postprocess.h
@@ -0,0 +1,34 @@
+/*
+===========================================================================
+Copyright (C) 2011 Andrei Drexler, Richard Allen, James Canete
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#ifndef TR_POSTPROCESS_H
+#define TR_POSTPROCESS_H
+
+#include "tr_fbo.h"
+
+void RB_ToneMap(FBO_t *hdrFbo, ivec4_t hdrBox, FBO_t *ldrFbo, ivec4_t ldrBox, int autoExposure);
+void RB_BokehBlur(FBO_t *src, ivec4_t srcBox, FBO_t *dst, ivec4_t dstBox, float blur);
+void RB_SunRays(FBO_t *srcFbo, ivec4_t srcBox, FBO_t *dstFbo, ivec4_t dstBox);
+void RB_GaussianBlur(float blur);
+
+#endif
diff --git a/src/renderergl2/tr_scene.cpp b/src/renderergl2/tr_scene.cpp
new file mode 100644
index 0000000..67ab0d3
--- /dev/null
+++ b/src/renderergl2/tr_scene.cpp
@@ -0,0 +1,575 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+
+#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_InitNextFrame
+
+====================
+*/
+void R_InitNextFrame( void ) {
+ backEndData->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;
+ 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( (surfaceType_t*)poly, sh, poly->fogIndex & fogMask, false, false, 0 /*cubeMap*/ );
+ }
+}
+
+/*
+=====================
+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->polys[r_numpolys];
+ poly->surfaceType = SF_POLY;
+ poly->hShader = hShader;
+ poly->numVerts = numVerts;
+ poly->verts = &backEndData->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 ) {
+ vec3_t cross;
+
+ 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 bool firstTime = true;
+ if (firstTime) {
+ firstTime = false;
+ 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->entities[r_numentities].e = *ent;
+ backEndData->entities[r_numentities].lightingCalculated = false;
+
+ CrossProduct(ent->axis[0], ent->axis[1], cross);
+ backEndData->entities[r_numentities].mirrored = (DotProduct(ent->axis[2], cross) < 0.f);
+
+ 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->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, false );
+}
+
+/*
+=====================
+RE_AddAdditiveLightToScene
+
+=====================
+*/
+void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) {
+ RE_AddDynamicLightToScene( org, intensity, r, g, b, true );
+}
+
+
+void RE_BeginScene(const refdef_t *fd)
+{
+ 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 = false;
+ 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 = true;
+ }
+ }
+
+ 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) ){
+ VectorSet(tr.refdef.sunCol, 0, 0, 0);
+ VectorSet(tr.refdef.sunAmbCol, 0, 0, 0);
+ }
+ else
+ {
+ float scale = (1 << r_mapOverBrightBits->integer) / 255.0f;
+
+ if (r_forceSun->integer)
+ VectorScale(tr.sunLight, scale * r_forceSunLightScale->value, tr.refdef.sunCol);
+ else
+ VectorScale(tr.sunLight, scale, tr.refdef.sunCol);
+
+ if (r_sunlightMode->integer == 1)
+ {
+ tr.refdef.sunAmbCol[0] =
+ tr.refdef.sunAmbCol[1] =
+ tr.refdef.sunAmbCol[2] = r_forceSun->integer ? r_forceSunAmbientScale->value : tr.sunShadowScale;
+ }
+ else
+ {
+ if (r_forceSun->integer)
+ VectorScale(tr.sunLight, scale * r_forceSunAmbientScale->value, tr.refdef.sunAmbCol);
+ else
+ VectorScale(tr.sunLight, scale * tr.sunShadowScale, 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]);
+ }
+
+ // 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;
+ }
+
+ // derived info
+
+ tr.refdef.floatTime = tr.refdef.time * 0.001;
+
+ tr.refdef.numDrawSurfs = r_firstSceneDrawSurf;
+ tr.refdef.drawSurfs = backEndData->drawSurfs;
+
+ tr.refdef.num_entities = r_numentities - r_firstSceneEntity;
+ tr.refdef.entities = &backEndData->entities[r_firstSceneEntity];
+
+ tr.refdef.num_dlights = r_numdlights - r_firstSceneDlight;
+ tr.refdef.dlights = &backEndData->dlights[r_firstSceneDlight];
+
+ tr.refdef.numPolys = r_numpolys - r_firstScenePoly;
+ tr.refdef.polys = &backEndData->polys[r_firstScenePoly];
+
+ tr.refdef.num_pshadows = 0;
+ tr.refdef.pshadows = &backEndData->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++;
+}
+
+
+void RE_EndScene()
+{
+ // 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;
+}
+
+/*
+@@@@@@@@@@@@@@@@@@@@@
+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");
+ }
+
+ RE_BeginScene(fd);
+
+ // 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(glRefConfig.framebufferObject && !( fd->rdflags & RDF_NOWORLDMODEL ) && r_shadows->integer == 4)
+ {
+ R_RenderPshadowMaps(fd);
+ }
+
+ // playing with even more shadows
+ if(glRefConfig.framebufferObject && r_sunlightMode->integer && !( fd->rdflags & RDF_NOWORLDMODEL ) && (r_forceSun->integer || tr.sunShadows))
+ {
+ if (r_shadowCascadeZFar->integer != 0)
+ {
+ R_RenderSunShadowMaps(fd, 0);
+ R_RenderSunShadowMaps(fd, 1);
+ R_RenderSunShadowMaps(fd, 2);
+ }
+ else
+ {
+ Mat4Zero(tr.refdef.sunShadowMvp[0]);
+ Mat4Zero(tr.refdef.sunShadowMvp[1]);
+ Mat4Zero(tr.refdef.sunShadowMvp[2]);
+ }
+
+ // only rerender last cascade if sun has changed position
+ if (r_forceSun->integer == 2 || !VectorCompare(tr.refdef.sunDir, tr.lastCascadeSunDirection))
+ {
+ VectorCopy(tr.refdef.sunDir, tr.lastCascadeSunDirection);
+ R_RenderSunShadowMaps(fd, 3);
+ Mat4Copy(tr.refdef.sunShadowMvp[3], tr.lastCascadeSunMvp);
+ }
+ else
+ {
+ Mat4Copy(tr.lastCascadeSunMvp, tr.refdef.sunShadowMvp[3]);
+ }
+ }
+
+ // playing with cube maps
+ // this is where dynamic cubemaps would be rendered
+ if (0) //(glRefConfig.framebufferObject && !( fd->rdflags & RDF_NOWORLDMODEL ))
+ {
+ int i, j;
+
+ for (i = 0; i < tr.numCubemaps; i++)
+ {
+ for (j = 0; j < 6; j++)
+ {
+ R_RenderCubemapSide(i, j, true);
+ }
+ }
+ }
+
+ // 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 = false;
+
+ parms.fovX = tr.refdef.fov_x;
+ parms.fovY = tr.refdef.fov_y;
+
+ parms.stereoFrame = tr.refdef.stereoFrame;
+
+ VectorCopy( fd->vieworg, parms.orientation.origin );
+ VectorCopy( fd->viewaxis[0], parms.orientation.axis[0] );
+ VectorCopy( fd->viewaxis[1], parms.orientation.axis[1] );
+ VectorCopy( fd->viewaxis[2], parms.orientation.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();
+
+ RE_EndScene();
+
+ tr.frontEndMsec += ri.Milliseconds() - startTime;
+}
diff --git a/src/renderergl2/tr_shade.cpp b/src/renderergl2/tr_shade.cpp
new file mode 100644
index 0000000..8f9e871
--- /dev/null
+++ b/src/renderergl2/tr_shade.cpp
@@ -0,0 +1,1634 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// tr_shade.c
+
+#include "tr_local.h"
+#if idppc_altivec && !defined(__APPLE__)
+#include <altivec.h>
+#endif
+
+/*
+
+ THIS ENTIRE FILE IS BACK END
+
+ This file deals with applying shaders to surface data in the tess struct.
+*/
+
+
+/*
+==================
+R_DrawElements
+
+==================
+*/
+
+void R_DrawElements( int numIndexes, glIndex_t firstIndex)
+{
+ qglDrawElements(GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(glIndex_t)));
+}
+
+
+/*
+=============================================================
+
+SURFACE SHADERS
+
+=============================================================
+*/
+
+shaderCommands_t tess;
+
+
+/*
+=================
+R_BindAnimatedImageToTMU
+
+=================
+*/
+static void R_BindAnimatedImageToTMU( textureBundle_t *bundle, int tmu ) {
+
+ if ( bundle->isVideoMap ) {
+ ri.CIN_RunCinematic(bundle->videoMapHandle);
+ ri.CIN_UploadCinematic(bundle->videoMapHandle);
+ GL_BindToTMU(tr.scratchImage[bundle->videoMapHandle], tmu);
+ 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
+ int i = static_cast<int>(tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE) >> FUNCTABLE_SIZE2;
+
+ if ( i < 0 )
+ {
+ i = 0; // may happen with shader time offsets
+ }
+ i %= bundle->numImageAnimations;
+
+ GL_BindToTMU( bundle->image[ i ], tmu );
+}
+
+
+/*
+================
+DrawTris
+
+Draws triangle outlines for debugging
+================
+*/
+static void DrawTris (shaderCommands_t *input) {
+ GL_BindToTMU( tr.whiteImage, TB_COLORMAP );
+
+ GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE );
+ qglDepthRange( 0, 0 );
+
+ {
+ shaderProgram_t *sp = &tr.textureColorShader;
+ vec4_t color;
+
+ GLSL_BindProgram(sp);
+
+ GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+ VectorSet4(color, 1, 1, 1, 1);
+ GLSL_SetUniformVec4(sp, UNIFORM_COLOR, color);
+ GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0);
+
+ R_DrawElements(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, int cubemapIndex ) {
+
+ shader_t *state = (shader->remappedShader) ? shader->remappedShader : shader;
+
+ tess.numIndexes = 0;
+ tess.firstIndex = 0;
+ tess.numVertexes = 0;
+ tess.shader = state;
+ tess.fogNum = fogNum;
+ tess.cubemapIndex = cubemapIndex;
+ 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.useInternalVao = true;
+ tess.useCacheVao = false;
+
+ 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 ComputeTexMods( shaderStage_t *pStage, int bundleNum, float *outMatrix, float *outOffTurb)
+{
+ int tm;
+ float matrix[6], currentmatrix[6];
+ textureBundle_t *bundle = &pStage->bundle[bundleNum];
+
+ matrix[0] = 1.0f; matrix[2] = 0.0f; matrix[4] = 0.0f;
+ matrix[1] = 0.0f; matrix[3] = 1.0f; matrix[5] = 0.0f;
+
+ currentmatrix[0] = 1.0f; currentmatrix[2] = 0.0f; currentmatrix[4] = 0.0f;
+ currentmatrix[1] = 0.0f; currentmatrix[3] = 1.0f; currentmatrix[5] = 0.0f;
+
+ outMatrix[0] = 1.0f; outMatrix[2] = 0.0f;
+ outMatrix[1] = 0.0f; outMatrix[3] = 1.0f;
+
+ outOffTurb[0] = 0.0f; outOffTurb[1] = 0.0f; outOffTurb[2] = 0.0f; outOffTurb[3] = 0.0f;
+
+ 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_CalcTurbulentFactors(&bundle->texMods[tm].wave, &outOffTurb[2], &outOffTurb[3]);
+ break;
+
+ case TMOD_ENTITY_TRANSLATE:
+ RB_CalcScrollTexMatrix( backEnd.currentEntity->e.shaderTexCoord, matrix );
+ break;
+
+ case TMOD_SCROLL:
+ RB_CalcScrollTexMatrix( bundle->texMods[tm].scroll,
+ matrix );
+ break;
+
+ case TMOD_SCALE:
+ RB_CalcScaleTexMatrix( bundle->texMods[tm].scale,
+ matrix );
+ break;
+
+ case TMOD_STRETCH:
+ RB_CalcStretchTexMatrix( &bundle->texMods[tm].wave,
+ matrix );
+ break;
+
+ case TMOD_TRANSFORM:
+ RB_CalcTransformTexMatrix( &bundle->texMods[tm],
+ matrix );
+ break;
+
+ case TMOD_ROTATE:
+ RB_CalcRotateTexMatrix( bundle->texMods[tm].rotateSpeed,
+ matrix );
+ break;
+
+ default:
+ ri.Error( ERR_DROP, "ERROR: unknown texmod '%d' in shader '%s'", bundle->texMods[tm].type, tess.shader->name );
+ break;
+ }
+
+ switch ( bundle->texMods[tm].type )
+ {
+ case TMOD_NONE:
+ case TMOD_TURBULENT:
+ default:
+ break;
+
+ case TMOD_ENTITY_TRANSLATE:
+ case TMOD_SCROLL:
+ case TMOD_SCALE:
+ case TMOD_STRETCH:
+ case TMOD_TRANSFORM:
+ case TMOD_ROTATE:
+ outMatrix[0] = matrix[0] * currentmatrix[0] + matrix[2] * currentmatrix[1];
+ outMatrix[1] = matrix[1] * currentmatrix[0] + matrix[3] * currentmatrix[1];
+
+ outMatrix[2] = matrix[0] * currentmatrix[2] + matrix[2] * currentmatrix[3];
+ outMatrix[3] = matrix[1] * currentmatrix[2] + matrix[3] * currentmatrix[3];
+
+ outOffTurb[0] = matrix[0] * currentmatrix[4] + matrix[2] * currentmatrix[5] + matrix[4];
+ outOffTurb[1] = matrix[1] * currentmatrix[4] + matrix[3] * currentmatrix[5] + matrix[5];
+
+ currentmatrix[0] = outMatrix[0];
+ currentmatrix[1] = outMatrix[1];
+ currentmatrix[2] = outMatrix[2];
+ currentmatrix[3] = outMatrix[3];
+ currentmatrix[4] = outOffTurb[0];
+ currentmatrix[5] = outOffTurb[1];
+ 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.dlightShader[deformGen == DGEN_NONE ? 0 : 1];
+
+ backEnd.pc.c_dlightDraws++;
+
+ GLSL_BindProgram(sp);
+
+ GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+
+ GLSL_SetUniformFloat(sp, UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation);
+
+ GLSL_SetUniformInt(sp, UNIFORM_DEFORMGEN, deformGen);
+ if (deformGen != DGEN_NONE)
+ {
+ GLSL_SetUniformFloat5(sp, UNIFORM_DEFORMPARAMS, deformParams);
+ GLSL_SetUniformFloat(sp, 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, UNIFORM_COLOR, vector);
+
+ vector[0] = origin[0];
+ vector[1] = origin[1];
+ vector[2] = origin[2];
+ vector[3] = scale;
+ GLSL_SetUniformVec4(sp, UNIFORM_DLIGHTINFO, vector);
+
+ GL_BindToTMU( tr.dlightImage, TB_COLORMAP );
+
+ // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light
+ // where they aren't rendered
+ if ( dl->additive ) {
+ GL_State( GLS_ATEST_GT_0 | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
+ }
+ else {
+ GL_State( GLS_ATEST_GT_0 | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
+ }
+
+ GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 1);
+
+ R_DrawElements(tess.numIndexes, tess.firstIndex);
+
+ backEnd.pc.c_totalIndexes += tess.numIndexes;
+ backEnd.pc.c_dlightIndexes += tess.numIndexes;
+ backEnd.pc.c_dlightVertexes += tess.numVertexes;
+ }
+}
+
+
+static void ComputeShaderColors( shaderStage_t *pStage, vec4_t baseColor, vec4_t vertColor, int blend )
+{
+ bool isBlend = ((blend & GLS_SRCBLEND_BITS) == GLS_SRCBLEND_DST_COLOR)
+ || ((blend & GLS_SRCBLEND_BITS) == GLS_SRCBLEND_ONE_MINUS_DST_COLOR)
+ || ((blend & GLS_DSTBLEND_BITS) == GLS_DSTBLEND_SRC_COLOR)
+ || ((blend & GLS_DSTBLEND_BITS) == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR);
+
+ bool is2DDraw = backEnd.currentEntity == &backEnd.entity2D;
+
+ float overbright = (isBlend || is2DDraw) ? 1.0f : (float)(1 << tr.overbrightBits);
+
+ fog_t *fog;
+
+ baseColor[0] =
+ baseColor[1] =
+ baseColor[2] =
+ baseColor[3] = 1.0f;
+
+ vertColor[0] =
+ vertColor[1] =
+ vertColor[2] =
+ vertColor[3] = 0.0f;
+
+ //
+ // rgbGen
+ //
+ switch ( pStage->rgbGen )
+ {
+ case CGEN_EXACT_VERTEX:
+ case CGEN_EXACT_VERTEX_LIT:
+ baseColor[0] =
+ baseColor[1] =
+ baseColor[2] =
+ baseColor[3] = 0.0f;
+
+ vertColor[0] =
+ vertColor[1] =
+ vertColor[2] = overbright;
+ 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;
+ break;
+ case CGEN_VERTEX:
+ case CGEN_VERTEX_LIT:
+ baseColor[0] =
+ baseColor[1] =
+ baseColor[2] =
+ baseColor[3] = 0.0f;
+
+ vertColor[0] =
+ vertColor[1] =
+ vertColor[2] =
+ vertColor[3] = 1.0f;
+ break;
+ case CGEN_ONE_MINUS_VERTEX:
+ baseColor[0] =
+ baseColor[1] =
+ baseColor[2] = 1.0f;
+
+ vertColor[0] =
+ vertColor[1] =
+ vertColor[2] = -1.0f;
+ break;
+ case CGEN_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;
+ break;
+ case CGEN_WAVEFORM:
+ baseColor[0] =
+ baseColor[1] =
+ baseColor[2] = RB_CalcWaveColorSingle( &pStage->rgbWave );
+ 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;
+ }
+ 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;
+ }
+ break;
+ case CGEN_IDENTITY:
+ case CGEN_LIGHTING_DIFFUSE:
+ baseColor[0] =
+ baseColor[1] =
+ baseColor[2] = overbright;
+ break;
+ case CGEN_IDENTITY_LIGHTING:
+ case CGEN_BAD:
+ break;
+ }
+
+ //
+ // alphaGen
+ //
+ switch ( pStage->alphaGen )
+ {
+ case AGEN_SKIP:
+ 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_IDENTITY:
+ case AGEN_LIGHTING_SPECULAR:
+ case AGEN_PORTAL:
+ // 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.orientation.origin, backEnd.viewParms.orientation.origin, local );
+ fogDistanceVector[0] = -backEnd.orientation.modelMatrix[2];
+ fogDistanceVector[1] = -backEnd.orientation.modelMatrix[6];
+ fogDistanceVector[2] = -backEnd.orientation.modelMatrix[10];
+ fogDistanceVector[3] = DotProduct( local, backEnd.viewParms.orientation.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.orientation.axis[0][0] +
+ fog->surface[1] * backEnd.orientation.axis[0][1] + fog->surface[2] * backEnd.orientation.axis[0][2];
+ fogDepthVector[1] = fog->surface[0] * backEnd.orientation.axis[1][0] +
+ fog->surface[1] * backEnd.orientation.axis[1][1] + fog->surface[2] * backEnd.orientation.axis[1][2];
+ fogDepthVector[2] = fog->surface[0] * backEnd.orientation.axis[2][0] +
+ fog->surface[1] * backEnd.orientation.axis[2][1] + fog->surface[2] * backEnd.orientation.axis[2][2];
+ fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.orientation.origin, fog->surface );
+
+ *eyeT = DotProduct( backEnd.orientation.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;
+ vec4_t texMatrix;
+ vec4_t texOffTurb;
+
+ 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;
+ index |= LIGHTDEF_USE_LIGHT_VECTOR;
+
+ sp = &tr.lightallShader[index];
+ }
+
+ backEnd.pc.c_lightallDraws++;
+
+ GLSL_BindProgram(sp);
+
+ GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+ GLSL_SetUniformVec3(sp, UNIFORM_VIEWORIGIN, backEnd.viewParms.orientation.origin);
+ GLSL_SetUniformVec3(sp, UNIFORM_LOCALVIEWORIGIN, backEnd.orientation.viewOrigin);
+
+ GLSL_SetUniformFloat(sp, UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation);
+
+ GLSL_SetUniformInt(sp, UNIFORM_DEFORMGEN, deformGen);
+ if (deformGen != DGEN_NONE)
+ {
+ GLSL_SetUniformFloat5(sp, UNIFORM_DEFORMPARAMS, deformParams);
+ GLSL_SetUniformFloat(sp, UNIFORM_TIME, tess.shaderTime);
+ }
+
+ if ( input->fogNum ) {
+ vec4_t fogColorMask;
+
+ GLSL_SetUniformVec4(sp, UNIFORM_FOGDISTANCE, fogDistanceVector);
+ GLSL_SetUniformVec4(sp, UNIFORM_FOGDEPTH, fogDepthVector);
+ GLSL_SetUniformFloat(sp, UNIFORM_FOGEYET, eyeT);
+
+ ComputeFogColorMask(pStage, fogColorMask);
+
+ GLSL_SetUniformVec4(sp, UNIFORM_FOGCOLORMASK, fogColorMask);
+ }
+
+ {
+ vec4_t baseColor;
+ vec4_t vertColor;
+
+ ComputeShaderColors(pStage, baseColor, vertColor, GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE);
+
+ GLSL_SetUniformVec4(sp, UNIFORM_BASECOLOR, baseColor);
+ GLSL_SetUniformVec4(sp, UNIFORM_VERTCOLOR, vertColor);
+ }
+
+ if (pStage->alphaGen == AGEN_PORTAL)
+ {
+ GLSL_SetUniformFloat(sp, UNIFORM_PORTALRANGE, tess.shader->portalRange);
+ }
+
+ GLSL_SetUniformInt(sp, UNIFORM_COLORGEN, pStage->rgbGen);
+ GLSL_SetUniformInt(sp, UNIFORM_ALPHAGEN, pStage->alphaGen);
+
+ GLSL_SetUniformVec3(sp, UNIFORM_DIRECTEDLIGHT, dl->color);
+
+ VectorSet(vector, 0, 0, 0);
+ GLSL_SetUniformVec3(sp, UNIFORM_AMBIENTLIGHT, vector);
+
+ VectorCopy(dl->origin, vector);
+ vector[3] = 1.0f;
+ GLSL_SetUniformVec4(sp, UNIFORM_LIGHTORIGIN, vector);
+
+ GLSL_SetUniformFloat(sp, UNIFORM_LIGHTRADIUS, radius);
+
+ GLSL_SetUniformVec4(sp, UNIFORM_NORMALSCALE, pStage->normalScale);
+ GLSL_SetUniformVec4(sp, UNIFORM_SPECULARSCALE, pStage->specularScale);
+
+ // 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_SetUniformInt(sp, UNIFORM_ALPHATEST, 0);
+
+ GLSL_SetUniformMat4(sp, UNIFORM_MODELMATRIX, backEnd.orientation.transformMatrix);
+
+ if (pStage->bundle[TB_DIFFUSEMAP].image[0])
+ R_BindAnimatedImageToTMU( &pStage->bundle[TB_DIFFUSEMAP], TB_DIFFUSEMAP);
+
+ // bind textures that are sampled and used in the glsl shader, and
+ // bind whiteImage to textures that are sampled but zeroed in the glsl shader
+ //
+ // alternatives:
+ // - use the last bound texture
+ // -> costs more to sample a higher res texture then throw out the result
+ // - disable texture sampling in glsl shader with #ifdefs, as before
+ // -> increases the number of shaders that must be compiled
+ //
+
+ if (pStage->bundle[TB_NORMALMAP].image[0])
+ {
+ R_BindAnimatedImageToTMU( &pStage->bundle[TB_NORMALMAP], TB_NORMALMAP);
+ }
+ else if (r_normalMapping->integer)
+ GL_BindToTMU( tr.whiteImage, TB_NORMALMAP );
+
+ if (pStage->bundle[TB_SPECULARMAP].image[0])
+ {
+ R_BindAnimatedImageToTMU( &pStage->bundle[TB_SPECULARMAP], TB_SPECULARMAP);
+ }
+ else if (r_specularMapping->integer)
+ GL_BindToTMU( tr.whiteImage, TB_SPECULARMAP );
+
+ {
+ vec4_t enableTextures;
+
+ VectorSet4(enableTextures, 0.0f, 0.0f, 0.0f, 0.0f);
+ GLSL_SetUniformVec4(sp, UNIFORM_ENABLETEXTURES, enableTextures);
+ }
+
+ if (r_dlightMode->integer >= 2)
+ GL_BindToTMU(tr.shadowCubemaps[l], TB_SHADOWMAP);
+
+ ComputeTexMods( pStage, TB_DIFFUSEMAP, texMatrix, texOffTurb );
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX, texMatrix);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, texOffTurb);
+
+ GLSL_SetUniformInt(sp, UNIFORM_TCGEN0, pStage->bundle[0].tcGen);
+
+ //
+ // draw
+ //
+
+ R_DrawElements(input->numIndexes, input->firstIndex);
+
+ backEnd.pc.c_totalIndexes += tess.numIndexes;
+ backEnd.pc.c_dlightIndexes += tess.numIndexes;
+ backEnd.pc.c_dlightVertexes += tess.numVertexes;
+ }
+}
+
+
+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_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+
+ VectorCopy(origin, vector);
+ vector[3] = 1.0f;
+ GLSL_SetUniformVec4(sp, UNIFORM_LIGHTORIGIN, vector);
+
+ VectorScale(ps->lightViewAxis[0], 1.0f / ps->viewRadius, vector);
+ GLSL_SetUniformVec3(sp, UNIFORM_LIGHTFORWARD, vector);
+
+ VectorScale(ps->lightViewAxis[1], 1.0f / ps->viewRadius, vector);
+ GLSL_SetUniformVec3(sp, UNIFORM_LIGHTRIGHT, vector);
+
+ VectorScale(ps->lightViewAxis[2], 1.0f / ps->viewRadius, vector);
+ GLSL_SetUniformVec3(sp, UNIFORM_LIGHTUP, vector);
+
+ GLSL_SetUniformFloat(sp, 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 );
+ GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0);
+
+ GL_BindToTMU( tr.pshadowMaps[l], TB_DIFFUSEMAP );
+
+ //
+ // draw
+ //
+
+ R_DrawElements(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;
+
+ int deformGen;
+ vec5_t deformParams;
+
+ ComputeDeformValues(&deformGen, deformParams);
+
+ {
+ int index = 0;
+
+ if (deformGen != DGEN_NONE)
+ index |= FOGDEF_USE_DEFORM_VERTEXES;
+
+ if (glState.vertexAnimation)
+ index |= FOGDEF_USE_VERTEX_ANIMATION;
+
+ sp = &tr.fogShader[index];
+ }
+
+ backEnd.pc.c_fogDraws++;
+
+ GLSL_BindProgram(sp);
+
+ fog = tr.world->fogs + tess.fogNum;
+
+ GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+
+ GLSL_SetUniformFloat(sp, UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation);
+
+ GLSL_SetUniformInt(sp, UNIFORM_DEFORMGEN, deformGen);
+ if (deformGen != DGEN_NONE)
+ {
+ GLSL_SetUniformFloat5(sp, UNIFORM_DEFORMPARAMS, deformParams);
+ GLSL_SetUniformFloat(sp, 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, UNIFORM_COLOR, color);
+
+ ComputeFogValues(fogDistanceVector, fogDepthVector, &eyeT);
+
+ GLSL_SetUniformVec4(sp, UNIFORM_FOGDISTANCE, fogDistanceVector);
+ GLSL_SetUniformVec4(sp, UNIFORM_FOGDEPTH, fogDepthVector);
+ GLSL_SetUniformFloat(sp, 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 );
+ }
+ GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0);
+
+ R_DrawElements(tess.numIndexes, tess.firstIndex);
+}
+
+
+static unsigned int RB_CalcShaderVertexAttribs( shaderCommands_t *input )
+{
+ unsigned int vertexAttribs = input->shader->vertexAttribs;
+
+ if(glState.vertexAnimation)
+ {
+ vertexAttribs |= ATTR_POSITION2;
+ if (vertexAttribs & ATTR_NORMAL)
+ {
+ vertexAttribs |= ATTR_NORMAL2;
+ vertexAttribs |= ATTR_TANGENT2;
+ }
+ }
+
+ return vertexAttribs;
+}
+
+static void RB_IterateStagesGeneric( shaderCommands_t *input )
+{
+ int stage;
+
+ vec4_t fogDistanceVector, fogDepthVector = {0, 0, 0, 0};
+ float eyeT = 0;
+
+ int deformGen;
+ vec5_t deformParams;
+
+ bool renderToCubemap = tr.renderCubeFbo && glState.currentFBO == tr.renderCubeFbo;
+
+ ComputeDeformValues(&deformGen, deformParams);
+
+ ComputeFogValues(fogDistanceVector, fogDepthVector, &eyeT);
+
+ for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ )
+ {
+ shaderStage_t *pStage = input->xstages[stage];
+ shaderProgram_t *sp;
+ vec4_t texMatrix;
+ vec4_t texOffTurb;
+
+ if ( !pStage )
+ {
+ break;
+ }
+
+ if (backEnd.depthFill)
+ {
+ if (pStage->glslShaderGroup == tr.lightallShader)
+ {
+ int index = 0;
+
+ if (backEnd.currentEntity && backEnd.currentEntity != &tr.worldEntity)
+ {
+ index |= LIGHTDEF_ENTITY;
+ }
+
+ if (pStage->stateBits & GLS_ATEST_BITS)
+ {
+ index |= LIGHTDEF_USE_TCGEN_AND_TCMOD;
+ }
+
+ sp = &pStage->glslShaderGroup[index];
+ }
+ else
+ {
+ int shaderAttribs = 0;
+
+ if (tess.shader->numDeforms && !ShaderRequiresCPUDeforms(tess.shader))
+ {
+ shaderAttribs |= GENERICDEF_USE_DEFORM_VERTEXES;
+ }
+
+ if (glState.vertexAnimation)
+ {
+ shaderAttribs |= GENERICDEF_USE_VERTEX_ANIMATION;
+ }
+
+ if (pStage->stateBits & GLS_ATEST_BITS)
+ {
+ shaderAttribs |= GENERICDEF_USE_TCGEN_AND_TCMOD;
+ }
+
+ sp = &tr.genericShader[shaderAttribs];
+ }
+ }
+ else if (pStage->glslShaderGroup == tr.lightallShader)
+ {
+ int index = pStage->glslShaderIndex;
+
+ if (backEnd.currentEntity && backEnd.currentEntity != &tr.worldEntity)
+ {
+ index |= LIGHTDEF_ENTITY;
+ }
+
+ if (r_sunlightMode->integer && (backEnd.viewParms.flags & VPF_USESUNLIGHT) && (index & LIGHTDEF_LIGHTTYPE_MASK))
+ {
+ index |= LIGHTDEF_USE_SHADOWMAP;
+ }
+
+ if (r_lightmap->integer && ((index & LIGHTDEF_LIGHTTYPE_MASK) == LIGHTDEF_USE_LIGHTMAP))
+ {
+ index = LIGHTDEF_USE_TCGEN_AND_TCMOD;
+ }
+
+ sp = &pStage->glslShaderGroup[index];
+
+ backEnd.pc.c_lightallDraws++;
+ }
+ else
+ {
+ sp = GLSL_GetGenericShaderProgram(stage);
+
+ backEnd.pc.c_genericDraws++;
+ }
+
+ GLSL_BindProgram(sp);
+
+ GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+ GLSL_SetUniformVec3(sp, UNIFORM_VIEWORIGIN, backEnd.viewParms.orientation.origin);
+ GLSL_SetUniformVec3(sp, UNIFORM_LOCALVIEWORIGIN, backEnd.orientation.viewOrigin);
+
+ GLSL_SetUniformFloat(sp, UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation);
+
+ GLSL_SetUniformInt(sp, UNIFORM_DEFORMGEN, deformGen);
+ if (deformGen != DGEN_NONE)
+ {
+ GLSL_SetUniformFloat5(sp, UNIFORM_DEFORMPARAMS, deformParams);
+ GLSL_SetUniformFloat(sp, UNIFORM_TIME, tess.shaderTime);
+ }
+
+ if ( input->fogNum ) {
+ GLSL_SetUniformVec4(sp, UNIFORM_FOGDISTANCE, fogDistanceVector);
+ GLSL_SetUniformVec4(sp, UNIFORM_FOGDEPTH, fogDepthVector);
+ GLSL_SetUniformFloat(sp, UNIFORM_FOGEYET, eyeT);
+ }
+
+ GL_State( pStage->stateBits );
+ if ((pStage->stateBits & GLS_ATEST_BITS) == GLS_ATEST_GT_0)
+ {
+ GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 1);
+ }
+ else if ((pStage->stateBits & GLS_ATEST_BITS) == GLS_ATEST_LT_80)
+ {
+ GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 2);
+ }
+ else if ((pStage->stateBits & GLS_ATEST_BITS) == GLS_ATEST_GE_80)
+ {
+ GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 3);
+ }
+ else
+ {
+ GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0);
+ }
+
+
+ {
+ vec4_t baseColor;
+ vec4_t vertColor;
+
+ ComputeShaderColors(pStage, baseColor, vertColor, pStage->stateBits);
+
+ GLSL_SetUniformVec4(sp, UNIFORM_BASECOLOR, baseColor);
+ GLSL_SetUniformVec4(sp, UNIFORM_VERTCOLOR, vertColor);
+ }
+
+ if (pStage->rgbGen == CGEN_LIGHTING_DIFFUSE)
+ {
+ vec4_t vec;
+
+ VectorScale(backEnd.currentEntity->ambientLight, 1.0f / 255.0f, vec);
+ GLSL_SetUniformVec3(sp, UNIFORM_AMBIENTLIGHT, vec);
+
+ VectorScale(backEnd.currentEntity->directedLight, 1.0f / 255.0f, vec);
+ GLSL_SetUniformVec3(sp, UNIFORM_DIRECTEDLIGHT, vec);
+
+ VectorCopy(backEnd.currentEntity->lightDir, vec);
+ vec[3] = 0.0f;
+ GLSL_SetUniformVec4(sp, UNIFORM_LIGHTORIGIN, vec);
+ GLSL_SetUniformVec3(sp, UNIFORM_MODELLIGHTDIR, backEnd.currentEntity->modelLightDir);
+
+ GLSL_SetUniformFloat(sp, UNIFORM_LIGHTRADIUS, 0.0f);
+ }
+
+ if (pStage->alphaGen == AGEN_PORTAL)
+ {
+ GLSL_SetUniformFloat(sp, UNIFORM_PORTALRANGE, tess.shader->portalRange);
+ }
+
+ GLSL_SetUniformInt(sp, UNIFORM_COLORGEN, pStage->rgbGen);
+ GLSL_SetUniformInt(sp, UNIFORM_ALPHAGEN, pStage->alphaGen);
+
+ if ( input->fogNum )
+ {
+ vec4_t fogColorMask;
+
+ ComputeFogColorMask(pStage, fogColorMask);
+
+ GLSL_SetUniformVec4(sp, UNIFORM_FOGCOLORMASK, fogColorMask);
+ }
+
+ if (r_lightmap->integer)
+ {
+ vec4_t v;
+ VectorSet4(v, 1.0f, 0.0f, 0.0f, 1.0f);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX, v);
+ VectorSet4(v, 0.0f, 0.0f, 0.0f, 0.0f);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, v);
+
+ GLSL_SetUniformInt(sp, UNIFORM_TCGEN0, TCGEN_LIGHTMAP);
+ }
+ else
+ {
+ ComputeTexMods(pStage, TB_DIFFUSEMAP, texMatrix, texOffTurb);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX, texMatrix);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, texOffTurb);
+
+ GLSL_SetUniformInt(sp, 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, UNIFORM_TCGEN0VECTOR0, vec);
+ VectorCopy(pStage->bundle[0].tcGenVectors[1], vec);
+ GLSL_SetUniformVec3(sp, UNIFORM_TCGEN0VECTOR1, vec);
+ }
+ }
+
+ GLSL_SetUniformMat4(sp, UNIFORM_MODELMATRIX, backEnd.orientation.transformMatrix);
+
+ GLSL_SetUniformVec4(sp, UNIFORM_NORMALSCALE, pStage->normalScale);
+
+ {
+ vec4_t specularScale;
+ Vector4Copy(pStage->specularScale, specularScale);
+
+ if (renderToCubemap)
+ {
+ // force specular to nonmetal if rendering cubemaps
+ if (r_pbr->integer)
+ specularScale[1] = 0.0f;
+ }
+
+ GLSL_SetUniformVec4(sp, UNIFORM_SPECULARSCALE, specularScale);
+ }
+
+ //GLSL_SetUniformFloat(sp, UNIFORM_MAPLIGHTSCALE, backEnd.refdef.mapLightScale);
+
+ //
+ // do multitexture
+ //
+ if ( backEnd.depthFill )
+ {
+ if (!(pStage->stateBits & GLS_ATEST_BITS))
+ GL_BindToTMU( tr.whiteImage, TB_COLORMAP );
+ else if ( pStage->bundle[TB_COLORMAP].image[0] != 0 )
+ R_BindAnimatedImageToTMU( &pStage->bundle[TB_COLORMAP], TB_COLORMAP );
+ }
+ else if ( pStage->glslShaderGroup == tr.lightallShader )
+ {
+ int i;
+ vec4_t enableTextures;
+
+ if (r_sunlightMode->integer && (backEnd.viewParms.flags & VPF_USESUNLIGHT) && (pStage->glslShaderIndex & LIGHTDEF_LIGHTTYPE_MASK))
+ {
+ // FIXME: screenShadowImage is NULL if no framebuffers
+ if (tr.screenShadowImage)
+ GL_BindToTMU(tr.screenShadowImage, TB_SHADOWMAP);
+ GLSL_SetUniformVec3(sp, UNIFORM_PRIMARYLIGHTAMBIENT, backEnd.refdef.sunAmbCol);
+ if (r_pbr->integer)
+ {
+ vec3_t color;
+
+ color[0] = backEnd.refdef.sunCol[0] * backEnd.refdef.sunCol[0];
+ color[1] = backEnd.refdef.sunCol[1] * backEnd.refdef.sunCol[1];
+ color[2] = backEnd.refdef.sunCol[2] * backEnd.refdef.sunCol[2];
+ GLSL_SetUniformVec3(sp, UNIFORM_PRIMARYLIGHTCOLOR, color);
+ }
+ else
+ {
+ GLSL_SetUniformVec3(sp, UNIFORM_PRIMARYLIGHTCOLOR, backEnd.refdef.sunCol);
+ }
+ GLSL_SetUniformVec4(sp, UNIFORM_PRIMARYLIGHTORIGIN, backEnd.refdef.sunDir);
+ }
+
+ VectorSet4(enableTextures, 0, 0, 0, 0);
+ 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_COLORMAP)
+ R_BindAnimatedImageToTMU( &pStage->bundle[TB_LIGHTMAP], i);
+ else
+ 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_COLORMAP)
+ R_BindAnimatedImageToTMU( &pStage->bundle[TB_DELUXEMAP], i);
+ else
+ GL_BindToTMU( tr.whiteImage, i );
+ }
+ }
+ else
+ {
+ bool light = (pStage->glslShaderIndex & LIGHTDEF_LIGHTTYPE_MASK) != 0;
+ bool fastLight = !(r_normalMapping->integer || r_specularMapping->integer);
+
+ if (pStage->bundle[TB_DIFFUSEMAP].image[0])
+ R_BindAnimatedImageToTMU( &pStage->bundle[TB_DIFFUSEMAP], TB_DIFFUSEMAP);
+
+ if (pStage->bundle[TB_LIGHTMAP].image[0])
+ R_BindAnimatedImageToTMU( &pStage->bundle[TB_LIGHTMAP], TB_LIGHTMAP);
+
+ // bind textures that are sampled and used in the glsl shader, and
+ // bind whiteImage to textures that are sampled but zeroed in the glsl shader
+ //
+ // alternatives:
+ // - use the last bound texture
+ // -> costs more to sample a higher res texture then throw out the result
+ // - disable texture sampling in glsl shader with #ifdefs, as before
+ // -> increases the number of shaders that must be compiled
+ //
+ if (light && !fastLight)
+ {
+ if (pStage->bundle[TB_NORMALMAP].image[0])
+ {
+ R_BindAnimatedImageToTMU( &pStage->bundle[TB_NORMALMAP], TB_NORMALMAP);
+ enableTextures[0] = 1.0f;
+ }
+ else if (r_normalMapping->integer)
+ GL_BindToTMU( tr.whiteImage, TB_NORMALMAP );
+
+ if (pStage->bundle[TB_DELUXEMAP].image[0])
+ {
+ R_BindAnimatedImageToTMU( &pStage->bundle[TB_DELUXEMAP], TB_DELUXEMAP);
+ enableTextures[1] = 1.0f;
+ }
+ else if (r_deluxeMapping->integer)
+ GL_BindToTMU( tr.whiteImage, TB_DELUXEMAP );
+
+ if (pStage->bundle[TB_SPECULARMAP].image[0])
+ {
+ R_BindAnimatedImageToTMU( &pStage->bundle[TB_SPECULARMAP], TB_SPECULARMAP);
+ enableTextures[2] = 1.0f;
+ }
+ else if (r_specularMapping->integer)
+ GL_BindToTMU( tr.whiteImage, TB_SPECULARMAP );
+ }
+
+ enableTextures[3] = (r_cubeMapping->integer && !(tr.viewParms.flags & VPF_NOCUBEMAPS) && input->cubemapIndex) ? 1.0f : 0.0f;
+ }
+
+ GLSL_SetUniformVec4(sp, UNIFORM_ENABLETEXTURES, enableTextures);
+ }
+ else if ( pStage->bundle[1].image[0] != 0 )
+ {
+ R_BindAnimatedImageToTMU( &pStage->bundle[0], 0 );
+ R_BindAnimatedImageToTMU( &pStage->bundle[1], 1 );
+ }
+ else
+ {
+ //
+ // set state
+ //
+ R_BindAnimatedImageToTMU( &pStage->bundle[0], 0 );
+ }
+
+ //
+ // testing cube map
+ //
+ if (!(tr.viewParms.flags & VPF_NOCUBEMAPS) && input->cubemapIndex && r_cubeMapping->integer)
+ {
+ vec4_t vec;
+ cubemap_t *cubemap = &tr.cubemaps[input->cubemapIndex - 1];
+
+ // FIXME: cubemap image could be NULL if cubemap isn't renderer or loaded
+ if (cubemap->image)
+ GL_BindToTMU( cubemap->image, TB_CUBEMAP);
+
+ VectorSubtract(cubemap->origin, backEnd.viewParms.orientation.origin, vec);
+ vec[3] = 1.0f;
+
+ VectorScale4(vec, 1.0f / cubemap->parallaxRadius, vec);
+
+ GLSL_SetUniformVec4(sp, UNIFORM_CUBEMAPINFO, vec);
+ }
+
+ //
+ // draw
+ //
+ R_DrawElements(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 ) )
+ {
+ 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_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+
+ GLSL_SetUniformMat4(sp, UNIFORM_MODELMATRIX, backEnd.orientation.transformMatrix);
+
+ GLSL_SetUniformFloat(sp, UNIFORM_VERTEXLERP, glState.vertexAttribsInterpolation);
+
+ GLSL_SetUniformInt(sp, UNIFORM_DEFORMGEN, deformGen);
+ if (deformGen != DGEN_NONE)
+ {
+ GLSL_SetUniformFloat5(sp, UNIFORM_DEFORMPARAMS, deformParams);
+ GLSL_SetUniformFloat(sp, UNIFORM_TIME, tess.shaderTime);
+ }
+
+ VectorCopy(backEnd.viewParms.orientation.origin, vector);
+ vector[3] = 1.0f;
+ GLSL_SetUniformVec4(sp, UNIFORM_LIGHTORIGIN, vector);
+ GLSL_SetUniformFloat(sp, UNIFORM_LIGHTRADIUS, backEnd.viewParms.zFar);
+
+ GL_State( 0 );
+ GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0);
+
+ //
+ // do multitexture
+ //
+ //if ( pStage->glslShaderGroup )
+ {
+ //
+ // draw
+ //
+
+ R_DrawElements(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.useInternalVao)
+ {
+ RB_DeformTessGeometry();
+ }
+
+ vertexAttribs = RB_CalcShaderVertexAttribs( input );
+
+ if (tess.useInternalVao)
+ {
+ RB_UpdateTessVao(vertexAttribs);
+ }
+ else
+ {
+ backEnd.pc.c_staticVaoDraws++;
+ }
+
+ //
+ // log this call
+ //
+ if ( r_logFile->integer )
+ {
+ // don't just call LogComment, or we will get
+ // a call to va() every frame!
+ GLimp_LogComment( (char*)va("--- RB_StageIteratorGeneric( %s ) ---\n", tess.shader->name) );
+ }
+
+ //
+ // set face culling appropriately
+ //
+ if (input->shader->cullType == CT_TWO_SIDED)
+ {
+ GL_Cull( CT_TWO_SIDED );
+ }
+ else
+ {
+ bool cullFront = (input->shader->cullType == CT_FRONT_SIDED);
+
+ if ( backEnd.viewParms.flags & VPF_DEPTHSHADOW )
+ cullFront = !cullFront;
+
+ if ( backEnd.viewParms.isMirror )
+ cullFront = !cullFront;
+
+ if ( backEnd.currentEntity && backEnd.currentEntity->mirrored )
+ cullFront = !cullFront;
+
+ if (cullFront)
+ GL_Cull( CT_FRONT_SIDED );
+ else
+ GL_Cull( CT_BACK_SIDED );
+ }
+
+ // set polygon offset if necessary
+ if ( input->shader->polygonOffset )
+ {
+ qglEnable( GL_POLYGON_OFFSET_FILL );
+ }
+
+ //
+ // 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 (glRefConfig.framebufferObject && r_shadows->integer == 4 && 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 && r_lightmap->integer == 0
+ && !(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();
+ }
+ }
+
+ //
+ // 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;
+ }
+
+ if (tess.useCacheVao)
+ {
+ // upload indexes now
+ VaoCache_Commit();
+ }
+
+ //
+ // 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;
+
+ GLimp_LogComment( "----------\n" );
+}
diff --git a/src/renderergl2/tr_shade_calc.cpp b/src/renderergl2/tr_shade_calc.cpp
new file mode 100644
index 0000000..182020b
--- /dev/null
+++ b/src/renderergl2/tr_shade_calc.cpp
@@ -0,0 +1,843 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// tr_shade_calc.c
+
+#include "tr_local.h"
+#if idppc_altivec && !defined(__APPLE__)
+#include <altivec.h>
+#endif
+
+
+#define WAVEVALUE( table, base, amplitude, phase, freq ) ((base) + table[ static_cast<int64_t>( ( ( (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_CalcStretchTexMatrix
+*/
+void RB_CalcStretchTexMatrix( const waveForm_t *wf, float *matrix )
+{
+ float p;
+
+ p = 1.0f / EvalWaveForm( wf );
+
+ matrix[0] = p; matrix[2] = 0; matrix[4] = 0.5f - 0.5f * p;
+ matrix[1] = 0; matrix[3] = p; matrix[5] = 0.5f - 0.5f * p;
+}
+
+/*
+====================================================================
+
+DEFORMATIONS
+
+====================================================================
+*/
+
+/*
+========================
+RB_CalcDeformVertexes
+
+========================
+*/
+void RB_CalcDeformVertexes( deformStage_t *ds )
+{
+ int i;
+ vec3_t offset;
+ float scale;
+ float *xyz = ( float * ) tess.xyz;
+ int16_t *normal = tess.normal[0];
+ float *table;
+
+ if ( ds->deformationWave.frequency == 0 )
+ {
+ scale = EvalWaveForm( &ds->deformationWave );
+
+ for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 )
+ {
+ R_VaoUnpackNormal(offset, normal);
+
+ xyz[0] += offset[0] * scale;
+ xyz[1] += offset[1] * scale;
+ xyz[2] += offset[2] * scale;
+ }
+ }
+ 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 );
+
+ R_VaoUnpackNormal(offset, normal);
+
+ xyz[0] += offset[0] * scale;
+ xyz[1] += offset[1] * scale;
+ xyz[2] += offset[2] * scale;
+ }
+ }
+}
+
+/*
+=========================
+RB_CalcDeformNormals
+
+Wiggle the normals for wavy environment mapping
+=========================
+*/
+void RB_CalcDeformNormals( deformStage_t *ds ) {
+ int i;
+ float scale;
+ float *xyz = ( float * ) tess.xyz;
+ int16_t *normal = tess.normal[0];
+
+ for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) {
+ vec3_t fNormal;
+
+ R_VaoUnpackNormal(fNormal, normal);
+
+ scale = 0.98f;
+ scale = R_NoiseGet4f( xyz[0] * scale, xyz[1] * scale, xyz[2] * scale,
+ tess.shaderTime * ds->deformationWave.frequency );
+ fNormal[ 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 );
+ fNormal[ 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 );
+ fNormal[ 2 ] += ds->deformationWave.amplitude * scale;
+
+ VectorNormalizeFast( fNormal );
+
+ R_VaoPackNormal(normal, fNormal);
+ }
+}
+
+/*
+========================
+RB_CalcBulgeVertexes
+
+========================
+*/
+void RB_CalcBulgeVertexes( deformStage_t *ds ) {
+ int i;
+ const float *st = ( const float * ) tess.texCoords[0];
+ float *xyz = ( float * ) tess.xyz;
+ int16_t *normal = tess.normal[0];
+
+ double now = backEnd.refdef.time * 0.001 * ds->bulgeSpeed;
+
+ for ( i = 0; i < tess.numVertexes; i++, xyz += 4, st += 2, normal += 4 ) {
+ int64_t off;
+ float scale;
+ vec3_t fNormal;
+
+ R_VaoUnpackNormal(fNormal, normal);
+
+ off = (float)( FUNCTABLE_SIZE / (M_PI*2) ) * ( st[0] * ds->bulgeWidth + now );
+
+ scale = tr.sinTable[ off & FUNCTABLE_MASK ] * ds->bulgeHeight;
+
+ xyz[0] += fNormal[0] * scale;
+ xyz[1] += fNormal[1] * scale;
+ xyz[2] += fNormal[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;
+ vec3_t fNormal;
+
+ height[0] = 0;
+ height[1] = 0;
+ height[2] = -1;
+
+ R_VaoUnpackNormal(fNormal, tess.normal[0]);
+ CrossProduct( fNormal, 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.orientation.axis[0] );
+ out[1] = DotProduct( in, backEnd.orientation.axis[1] );
+ out[2] = DotProduct( in, backEnd.orientation.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.orientation.axis[1], leftDir );
+ GlobalVectorToLocal( backEnd.viewParms.orientation.axis[2], upDir );
+ } else {
+ VectorCopy( backEnd.viewParms.orientation.axis[1], leftDir );
+ VectorCopy( backEnd.viewParms.orientation.axis[2], upDir );
+ }
+
+ for ( i = 0 ; i < oldVerts ; i+=4 ) {
+ vec4_t color;
+ // 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);
+ }
+
+ VectorScale4(tess.color[i], 1.0f / 65535.0f, color);
+ RB_AddQuadStamp( mid, left, up, color );
+ }
+}
+
+
+/*
+=====================
+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\n", tess.shader->name );
+ }
+ if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) {
+ ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd index count\n", tess.shader->name );
+ }
+
+ if ( backEnd.currentEntity != &tr.worldEntity ) {
+ GlobalVectorToLocal( backEnd.viewParms.orientation.axis[0], forward );
+ } else {
+ VectorCopy( backEnd.viewParms.orientation.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_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_CalcWaveAlphaSingle
+*/
+float RB_CalcWaveAlphaSingle( const waveForm_t *wf )
+{
+ return EvalWaveFormClamped( wf );
+}
+
+/*
+** RB_CalcModulateColorsByFog
+*/
+void RB_CalcModulateColorsByFog( unsigned char *colors ) {
+ int i;
+ float texCoords[SHADER_MAX_VERTEXES][2] = {{0.0f}};
+
+ // 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;
+ }
+}
+
+
+/*
+====================================================================
+
+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;
+ bool 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.orientation.origin, backEnd.viewParms.orientation.origin, local );
+ fogDistanceVector[0] = -backEnd.orientation.modelMatrix[2];
+ fogDistanceVector[1] = -backEnd.orientation.modelMatrix[6];
+ fogDistanceVector[2] = -backEnd.orientation.modelMatrix[10];
+ fogDistanceVector[3] = DotProduct( local, backEnd.viewParms.orientation.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.orientation.axis[0][0] +
+ fog->surface[1] * backEnd.orientation.axis[0][1] + fog->surface[2] * backEnd.orientation.axis[0][2];
+ fogDepthVector[1] = fog->surface[0] * backEnd.orientation.axis[1][0] +
+ fog->surface[1] * backEnd.orientation.axis[1][1] + fog->surface[2] * backEnd.orientation.axis[1][2];
+ fogDepthVector[2] = fog->surface[0] * backEnd.orientation.axis[2][0] +
+ fog->surface[1] * backEnd.orientation.axis[2][1] + fog->surface[2] * backEnd.orientation.axis[2][2];
+ fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.orientation.origin, fog->surface );
+
+ eyeT = DotProduct( backEnd.orientation.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 = true;
+ } else {
+ eyeOutside = false;
+ }
+
+ 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_CalcTurbulentFactors
+*/
+void RB_CalcTurbulentFactors( const waveForm_t *wf, float *amplitude, float *now )
+{
+ *now = wf->phase + tess.shaderTime * wf->frequency;
+ *amplitude = wf->amplitude;
+}
+
+/*
+** RB_CalcScaleTexMatrix
+*/
+void RB_CalcScaleTexMatrix( const float scale[2], float *matrix )
+{
+ matrix[0] = scale[0]; matrix[2] = 0.0f; matrix[4] = 0.0f;
+ matrix[1] = 0.0f; matrix[3] = scale[1]; matrix[5] = 0.0f;
+}
+
+/*
+** RB_CalcScrollTexMatrix
+*/
+void RB_CalcScrollTexMatrix( const float scrollSpeed[2], float *matrix )
+{
+ double timeScale = tess.shaderTime;
+ double adjustedScrollS = scrollSpeed[0] * timeScale;
+ double adjustedScrollT = scrollSpeed[1] * timeScale;
+
+ // clamp so coordinates don't continuously get larger, causing problems
+ // with hardware limits
+ adjustedScrollS -= floor( adjustedScrollS );
+ adjustedScrollT -= floor( adjustedScrollT );
+
+ matrix[0] = 1.0f; matrix[2] = 0.0f; matrix[4] = adjustedScrollS;
+ matrix[1] = 0.0f; matrix[3] = 1.0f; matrix[5] = adjustedScrollT;
+}
+
+/*
+** RB_CalcTransformTexMatrix
+*/
+void RB_CalcTransformTexMatrix( const texModInfo_t *tmi, float *matrix )
+{
+ matrix[0] = tmi->matrix[0][0]; matrix[2] = tmi->matrix[1][0]; matrix[4] = tmi->translate[0];
+ matrix[1] = tmi->matrix[0][1]; matrix[3] = tmi->matrix[1][1]; matrix[5] = tmi->translate[1];
+}
+
+/*
+** RB_CalcRotateTexMatrix
+*/
+void RB_CalcRotateTexMatrix( float degsPerSecond, float *matrix )
+{
+ double timeScale = tess.shaderTime;
+ double degs;
+ float sinValue, cosValue;
+
+ degs = -degsPerSecond * timeScale;
+ int i = degs * ( FUNCTABLE_SIZE / 360.0f );
+
+ sinValue = tr.sinTable[ i & FUNCTABLE_MASK ];
+ cosValue = tr.sinTable[ ( i + FUNCTABLE_SIZE / 4 ) & FUNCTABLE_MASK ];
+
+ matrix[0] = cosValue; matrix[2] = -sinValue; matrix[4] = 0.5 - 0.5 * cosValue + 0.5 * sinValue;
+ matrix[1] = sinValue; matrix[3] = cosValue; matrix[5] = 0.5 - 0.5 * sinValue - 0.5 * cosValue;
+}
+
+bool ShaderRequiresCPUDeforms(const shader_t * shader)
+{
+ if(shader->numDeforms)
+ {
+ const deformStage_t *ds = &shader->deforms[0];
+
+ if (shader->numDeforms > 1)
+ return true;
+
+ switch (ds->deformation)
+ {
+ case DEFORM_WAVE:
+ case DEFORM_BULGE:
+ // need CPU deforms at high level-times to avoid floating point percision loss
+ return ( backEnd.refdef.floatTime != (float)backEnd.refdef.floatTime );
+
+ default:
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/src/renderergl2/tr_shader.cpp b/src/renderergl2/tr_shader.cpp
new file mode 100644
index 0000000..b3ebacf
--- /dev/null
+++ b/src/renderergl2/tr_shader.cpp
@@ -0,0 +1,3891 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+#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 bool 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 false;
+ }
+
+ 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 false;
+ }
+ 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 false;
+ }
+
+ return true;
+}
+
+
+/*
+===============
+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" ) )
+ {
+ if (r_ignoreDstAlpha->integer)
+ return GLS_SRCBLEND_ONE;
+
+ return GLS_SRCBLEND_DST_ALPHA;
+ }
+ else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) )
+ {
+ if (r_ignoreDstAlpha->integer)
+ return GLS_SRCBLEND_ZERO;
+
+ 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" ) )
+ {
+ if (r_ignoreDstAlpha->integer)
+ return GLS_DSTBLEND_ONE;
+
+ return GLS_DSTBLEND_DST_ALPHA;
+ }
+ else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) )
+ {
+ if (r_ignoreDstAlpha->integer)
+ return GLS_DSTBLEND_ZERO;
+
+ 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 bool ParseStage( shaderStage_t *stage, char **text )
+{
+ char *token;
+ int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0;
+ bool depthMaskExplicit = false;
+
+ stage->active = true;
+
+ while ( 1 )
+ {
+ token = COM_ParseExt( text, qtrue );
+ if ( !token[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: no matching '}' found\n" );
+ return false;
+ }
+
+ if ( token[0] == '}' )
+ {
+ break;
+ }
+ //
+ // map <name>
+ //
+ 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 false;
+ }
+
+ if ( !Q_stricmp( token, "$whiteimage" ) )
+ {
+ stage->bundle[0].image[0] = tr.whiteImage;
+ continue;
+ }
+ else if ( !Q_stricmp( token, "$lightmap" ) )
+ {
+ stage->bundle[0].isLightmap = true;
+ if ( shader.lightmapIndex < 0 || !tr.lightmaps ) {
+ 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 false;
+ }
+
+ stage->bundle[0].isLightmap = true;
+ 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;
+ int/*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;
+ }
+
+ 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 false;
+ }
+ }
+ }
+ //
+ // clampmap <name>
+ //
+ else if ( !Q_stricmp( token, "clampmap" ) )
+ {
+ imgType_t type = IMGTYPE_COLORALPHA;
+ int/*imgFlags_t*/ flags = IMGFLAG_CLAMPTOEDGE;
+
+ 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 false;
+ }
+
+ 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;
+ }
+
+
+ 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 false;
+ }
+ }
+ //
+ // animMap <frequency> <image1> .... <imageN>
+ //
+ else if ( !Q_stricmp( token, "animMap" ) )
+ {
+ int totalImages = 0;
+
+ token = COM_ParseExt( text, qfalse );
+ if ( !token[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'animMap' keyword in shader '%s'\n", shader.name );
+ return false;
+ }
+ 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 ) {
+ int/*imgFlags_t*/ flags = IMGFLAG_NONE;
+
+ 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 false;
+ }
+ stage->bundle[0].numImageAnimations++;
+ }
+ totalImages++;
+ }
+
+ if ( totalImages > MAX_IMAGE_ANIMATIONS )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: ignoring excess images for 'animMap' (found %d, max is %d) in shader '%s'\n",
+ totalImages, MAX_IMAGE_ANIMATIONS, shader.name );
+ }
+ }
+ else if ( !Q_stricmp( token, "videoMap" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( !token[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'videoMap' keyword in shader '%s'\n", shader.name );
+ return false;
+ }
+ 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 = true;
+ stage->bundle[0].image[0] = tr.scratchImage[stage->bundle[0].videoMapHandle];
+ } else {
+ ri.Printf( PRINT_WARNING, "WARNING: could not load '%s' for 'videoMap' keyword in shader '%s'\n", token, shader.name );
+ }
+ }
+ //
+ // alphafunc <func>
+ //
+ 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 false;
+ }
+
+ atestBits = NameToAFunc( token );
+ }
+ //
+ // depthFunc <func>
+ //
+ 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 false;
+ }
+
+ 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 = true;
+ }
+ //
+ // blendfunc <srcFactor> <dstFactor>
+ // or blendfunc <add|filter|blend>
+ //
+ 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 <type>
+ //
+ 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;
+ VectorSet4(stage->normalScale, r_baseNormalX->value, r_baseNormalY->value, 1.0f, r_baseParallax->value);
+ }
+ else if(!Q_stricmp(token, "normalParallaxMap") || !Q_stricmp(token, "bumpParallaxMap"))
+ {
+ if (r_parallaxMapping->integer)
+ stage->type = ST_NORMALPARALLAXMAP;
+ else
+ stage->type = ST_NORMALMAP;
+ VectorSet4(stage->normalScale, r_baseNormalX->value, r_baseNormalY->value, 1.0f, r_baseParallax->value);
+ }
+ else if(!Q_stricmp(token, "specularMap"))
+ {
+ stage->type = ST_SPECULARMAP;
+ VectorSet4(stage->specularScale, 1.0f, 1.0f, 1.0f, 1.0f);
+ }
+ else
+ {
+ ri.Printf(PRINT_WARNING, "WARNING: unknown stage parameter '%s' in shader '%s'\n", token, shader.name);
+ continue;
+ }
+ }
+ //
+ // specularReflectance <value>
+ //
+ 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;
+ }
+
+ if (r_pbr->integer)
+ {
+ // interpret specularReflectance < 0.5 as nonmetal
+ stage->specularScale[1] = (atof(token) < 0.5f) ? 0.0f : 1.0f;
+ }
+ else
+ {
+ stage->specularScale[0] =
+ stage->specularScale[1] =
+ stage->specularScale[2] = atof( token );
+ }
+ }
+ //
+ // specularExponent <value>
+ //
+ else if (!Q_stricmp(token, "specularexponent"))
+ {
+ float exponent;
+
+ 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;
+ }
+
+ exponent = atof( token );
+
+ if (r_pbr->integer)
+ stage->specularScale[0] = 1.0f - powf(2.0f / (exponent + 2.0), 0.25);
+ else
+ {
+ // Change shininess to gloss
+ // Assumes max exponent of 8190 and min of 0, must change here if altered in lightall_fp.glsl
+ exponent = CLAMP(exponent, 0.0f, 8190.0f);
+ stage->specularScale[3] = (log2f(exponent + 2.0f) - 1.0f) / 12.0f;
+ }
+ }
+ //
+ // gloss <value>
+ //
+ else if (!Q_stricmp(token, "gloss"))
+ {
+ float gloss;
+
+ token = COM_ParseExt(text, qfalse);
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameter for gloss in shader '%s'\n", shader.name );
+ continue;
+ }
+
+ gloss = atof(token);
+
+ if (r_pbr->integer)
+ stage->specularScale[0] = 1.0f - exp2f(-3.0f * gloss);
+ else
+ stage->specularScale[3] = gloss;
+ }
+ //
+ // roughness <value>
+ //
+ else if (!Q_stricmp(token, "roughness"))
+ {
+ float roughness;
+
+ token = COM_ParseExt(text, qfalse);
+ if (token[0] == 0)
+ {
+ ri.Printf(PRINT_WARNING, "WARNING: missing parameter for roughness in shader '%s'\n", shader.name);
+ continue;
+ }
+
+ roughness = atof(token);
+
+ if (r_pbr->integer)
+ stage->specularScale[0] = 1.0 - roughness;
+ else
+ {
+ if (roughness >= 0.125)
+ stage->specularScale[3] = log2f(1.0f / roughness) / 3.0f;
+ else
+ stage->specularScale[3] = 1.0f;
+ }
+ }
+ //
+ // parallaxDepth <value>
+ //
+ else if (!Q_stricmp(token, "parallaxdepth"))
+ {
+ token = COM_ParseExt(text, qfalse);
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameter for parallaxDepth in shader '%s'\n", shader.name );
+ continue;
+ }
+
+ stage->normalScale[3] = atof( token );
+ }
+ //
+ // normalScale <xy>
+ // or normalScale <x> <y>
+ // or normalScale <x> <y> <height>
+ //
+ else if (!Q_stricmp(token, "normalscale"))
+ {
+ token = COM_ParseExt(text, qfalse);
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameter for normalScale in shader '%s'\n", shader.name );
+ continue;
+ }
+
+ stage->normalScale[0] = atof( token );
+
+ token = COM_ParseExt(text, qfalse);
+ if ( token[0] == 0 )
+ {
+ // one value, applies to X/Y
+ stage->normalScale[1] = stage->normalScale[0];
+ continue;
+ }
+
+ stage->normalScale[1] = atof( token );
+
+ token = COM_ParseExt(text, qfalse);
+ if ( token[0] == 0 )
+ {
+ // two values, no height
+ continue;
+ }
+
+ stage->normalScale[3] = atof( token );
+ }
+ //
+ // specularScale <rgb> <gloss>
+ // or specularScale <metallic> <smoothness> with r_pbr 1
+ // or specularScale <r> <g> <b>
+ // or specularScale <r> <g> <b> <gloss>
+ //
+ else if (!Q_stricmp(token, "specularscale"))
+ {
+ token = COM_ParseExt(text, qfalse);
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameter for specularScale in shader '%s'\n", shader.name );
+ continue;
+ }
+
+ stage->specularScale[0] = atof( token );
+
+ token = COM_ParseExt(text, qfalse);
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameter for specularScale in shader '%s'\n", shader.name );
+ continue;
+ }
+
+ stage->specularScale[1] = atof( token );
+
+ token = COM_ParseExt(text, qfalse);
+ if ( token[0] == 0 )
+ {
+ if (r_pbr->integer)
+ {
+ // two values, metallic then smoothness
+ float smoothness = stage->specularScale[1];
+ stage->specularScale[1] = (stage->specularScale[0] < 0.5f) ? 0.0f : 1.0f;
+ stage->specularScale[0] = smoothness;
+ }
+ {
+ // two values, rgb then gloss
+ stage->specularScale[3] = stage->specularScale[1];
+ stage->specularScale[1] =
+ stage->specularScale[2] = stage->specularScale[0];
+ }
+ continue;
+ }
+
+ stage->specularScale[2] = atof( token );
+
+ token = COM_ParseExt(text, qfalse);
+ if ( token[0] == 0 )
+ {
+ // three values, rgb
+ continue;
+ }
+
+ stage->specularScale[2] = 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;
+
+ VectorClear( 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
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: unknown alphaGen parameter '%s' in shader '%s'\n", token, shader.name );
+ continue;
+ }
+ }
+ //
+ // tcGen <function>
+ //
+ 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 <type> <...>
+ //
+ else if ( !Q_stricmp( token, "tcMod" ) )
+ {
+ char buffer[1024] = "";
+
+ while ( 1 )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ break;
+ Q_strcat( buffer, sizeof (buffer), token );
+ Q_strcat( buffer, sizeof (buffer), " " );
+ }
+
+ ParseTexMod( buffer, stage );
+
+ continue;
+ }
+ //
+ // depthmask
+ //
+ else if ( !Q_stricmp( token, "depthwrite" ) )
+ {
+ depthMaskBits = GLS_DEPTHMASK_TRUE;
+ depthMaskExplicit = true;
+
+ continue;
+ }
+ else
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: unknown parameter '%s' in shader '%s'\n", token, shader.name );
+ return false;
+ }
+ }
+
+ //
+ // 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 true;
+}
+
+/*
+===============
+ParseDeform
+
+deformVertexes wave <spread> <waveform> <base> <amplitude> <phase> <frequency>
+deformVertexes normal <frequency> <amplitude>
+deformVertexes move <vector> <waveform> <base> <amplitude> <phase> <frequency>
+deformVertexes bulge <bulgeWidth> <bulgeHeight> <bulgeSpeed>
+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_t)(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 <outerbox> <cloudheight> <innerbox>
+===============
+*/
+static void ParseSkyParms( char **text ) {
+ char *token;
+ static const char *suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};
+ char pathname[MAX_QPATH];
+ int i;
+ int/*imgFlags_t*/ imgFlags = IMGFLAG_MIPMAP | IMGFLAG_PICMIP;
+
+ // 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, imgFlags | 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, imgFlags );
+ if ( !shader.sky.innerbox[i] ) {
+ shader.sky.innerbox[i] = tr.defaultImage;
+ }
+ }
+ }
+
+ shader.isSky = true;
+}
+
+
+/*
+=================
+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 {
+ const char *name;
+ unsigned 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 <name>
+===============
+*/
+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 bool 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 false;
+ }
+
+ while ( 1 )
+ {
+ token = COM_ParseExt( text, qtrue );
+ if ( !token[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: no concluding '}' in shader %s\n", shader.name );
+ return false;
+ }
+
+ // 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 (max is %i)\n", shader.name, MAX_SHADER_STAGES );
+ return false;
+ }
+
+ if ( !ParseStage( &stages[s], text ) )
+ {
+ return false;
+ }
+ stages[s].active = true;
+ 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;
+ bool isGL2Sun = false;
+
+ if (!Q_stricmp( token, "q3gl2_sun" ) && r_sunShadows->integer )
+ {
+ isGL2Sun = true;
+ tr.sunShadows = true;
+ }
+
+ 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);
+
+ 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.sunShadowScale = atof(token);
+
+ // parse twice, since older shaders may include mapLightScale before sunShadowScale
+ token = COM_ParseExt( text, qfalse );
+ if (token[0])
+ tr.sunShadowScale = atof(token);
+ }
+
+ 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 = true;
+ shader.noPicMip = true;
+ continue;
+ }
+ // no picmip adjustment
+ else if ( !Q_stricmp( token, "nopicmip" ) )
+ {
+ shader.noPicMip = true;
+ continue;
+ }
+ // polygonOffset
+ else if ( !Q_stricmp( token, "polygonOffset" ) )
+ {
+ shader.polygonOffset = true;
+ 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 = true;
+ continue;
+ }
+ // fogParms
+ else if ( !Q_stricmp( token, "fogParms" ) )
+ {
+ if ( !ParseVector( text, 3, shader.fogParms.color ) ) {
+ return false;
+ }
+
+ if ( r_greyscale->integer )
+ {
+ float luminance;
+
+ luminance = LUMA( shader.fogParms.color[0], shader.fogParms.color[1], shader.fogParms.color[2] );
+ VectorSet( shader.fogParms.color, luminance, luminance, luminance );
+ }
+ else if ( r_greyscale->value )
+ {
+ float luminance;
+
+ luminance = LUMA( shader.fogParms.color[0], shader.fogParms.color[1], shader.fogParms.color[2] );
+ shader.fogParms.color[0] = LERP( shader.fogParms.color[0], luminance, r_greyscale->value );
+ shader.fogParms.color[1] = LERP( shader.fogParms.color[1], luminance, r_greyscale->value );
+ shader.fogParms.color[2] = LERP( shader.fogParms.color[2], luminance, r_greyscale->value );
+ }
+
+ 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 = true;
+ continue;
+ }
+ // skyparms <cloudheight> <outerbox> <innerbox>
+ else if ( !Q_stricmp( token, "skyparms" ) )
+ {
+ ParseSkyParms( text );
+ continue;
+ }
+ // light <value> determines flaring in q3map, not needed here
+ else if ( !Q_stricmp(token, "light") )
+ {
+ COM_ParseExt( text, qfalse );
+ continue;
+ }
+ // cull <face>
+ 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 false;
+ }
+ }
+
+ //
+ // 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 false;
+ }
+
+ shader.explicitlyDefined = true;
+
+ return true;
+}
+
+/*
+========================================================================================
+
+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;
+
+ if ((pStage->glslShaderIndex & LIGHTDEF_LIGHTTYPE_MASK) && !(r_normalMapping->integer == 0 && r_specularMapping->integer == 0))
+ {
+ shader.vertexAttribs |= ATTR_TANGENT;
+ }
+
+ 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:
+ shader.vertexAttribs |= ATTR_NORMAL;
+ break;
+
+ case AGEN_VERTEX:
+ case AGEN_ONE_MINUS_VERTEX:
+ shader.vertexAttribs |= ATTR_COLOR;
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+
+static void CollapseStagesToLightall(shaderStage_t *diffuse,
+ shaderStage_t *normal, shaderStage_t *specular, shaderStage_t *lightmap,
+ bool useLightVector, bool useLightVertex, bool parallax, bool tcgen)
+{
+ 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];
+ }
+
+ 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];
+ if (parallax && r_parallaxMapping->integer)
+ defs |= LIGHTDEF_USE_PARALLAXMAP;
+
+ VectorCopy4(normal->normalScale, diffuse->normalScale);
+ }
+ else if ((lightmap || useLightVector || useLightVertex) && (diffuseImg = diffuse->bundle[TB_DIFFUSEMAP].image[0]))
+ {
+ char normalName[MAX_QPATH];
+ image_t *normalImg;
+ int/*imgFlags_t*/ normalFlags = (diffuseImg->flags & ~IMGFLAG_GENNORMALMAP) | IMGFLAG_NOLIGHTSCALE;
+
+ // try a normalheight image first
+ COM_StripExtension(diffuseImg->imgName, normalName, MAX_QPATH);
+ Q_strcat(normalName, MAX_QPATH, "_nh");
+
+ normalImg = R_FindImageFile(normalName, IMGTYPE_NORMALHEIGHT, normalFlags);
+
+ if (normalImg)
+ {
+ parallax = true;
+ }
+ else
+ {
+ // try a normal image ("_n" suffix)
+ normalName[strlen(normalName) - 1] = '\0';
+ normalImg = R_FindImageFile(normalName, IMGTYPE_NORMAL, normalFlags);
+ }
+
+ if (normalImg)
+ {
+ diffuse->bundle[TB_NORMALMAP] = diffuse->bundle[0];
+ diffuse->bundle[TB_NORMALMAP].numImageAnimations = 0;
+ diffuse->bundle[TB_NORMALMAP].image[0] = normalImg;
+
+ if (parallax && r_parallaxMapping->integer)
+ defs |= LIGHTDEF_USE_PARALLAXMAP;
+
+ VectorSet4(diffuse->normalScale, r_baseNormalX->value, r_baseNormalY->value, 1.0f, r_baseParallax->value);
+ }
+ }
+ }
+
+ if (r_specularMapping->integer)
+ {
+ image_t *diffuseImg;
+ if (specular)
+ {
+ //ri.Printf(PRINT_ALL, ", specularmap %s", specular->bundle[0].image[0]->imgName);
+ diffuse->bundle[TB_SPECULARMAP] = specular->bundle[0];
+ VectorCopy4(specular->specularScale, diffuse->specularScale);
+ }
+ else if ((lightmap || useLightVector || useLightVertex) && (diffuseImg = diffuse->bundle[TB_DIFFUSEMAP].image[0]))
+ {
+ char specularName[MAX_QPATH];
+ image_t *specularImg;
+ int/*imgFlags_t*/ specularFlags = (diffuseImg->flags & ~IMGFLAG_GENNORMALMAP) | IMGFLAG_NOLIGHTSCALE;
+
+ COM_StripExtension(diffuseImg->imgName, specularName, MAX_QPATH);
+ Q_strcat(specularName, MAX_QPATH, "_s");
+
+ specularImg = R_FindImageFile(specularName, IMGTYPE_COLORALPHA, specularFlags);
+
+ if (specularImg)
+ {
+ diffuse->bundle[TB_SPECULARMAP] = diffuse->bundle[0];
+ diffuse->bundle[TB_SPECULARMAP].numImageAnimations = 0;
+ diffuse->bundle[TB_SPECULARMAP].image[0] = specularImg;
+
+ VectorSet4(diffuse->specularScale, 1.0f, 1.0f, 1.0f, 1.0f);
+ }
+ }
+ }
+
+ if (tcgen || diffuse->bundle[0].numTexMods)
+ {
+ defs |= LIGHTDEF_USE_TCGEN_AND_TCMOD;
+ }
+
+ //ri.Printf(PRINT_ALL, ".\n");
+
+ diffuse->glslShaderGroup = tr.lightallShader;
+ diffuse->glslShaderIndex = defs;
+}
+
+
+static int CollapseStagesToGLSL(void)
+{
+ int i, j, numStages;
+ bool skip = false;
+
+ // skip shaders with deforms
+ if (shader.numDeforms != 0)
+ {
+ skip = true;
+ }
+
+ 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].tcGen == TCGEN_LIGHTMAP && 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 = true;
+ break;
+ }
+
+ if (pStage->bundle[0].tcGen == TCGEN_LIGHTMAP)
+ {
+ 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 = true;
+ break;
+ }
+ }
+
+ switch(pStage->bundle[0].tcGen)
+ {
+ case TCGEN_TEXTURE:
+ case TCGEN_LIGHTMAP:
+ case TCGEN_ENVIRONMENT_MAPPED:
+ case TCGEN_VECTOR:
+ break;
+ default:
+ skip = true;
+ break;
+ }
+
+ switch(pStage->alphaGen)
+ {
+ case AGEN_LIGHTING_SPECULAR:
+ case AGEN_PORTAL:
+ skip = true;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (!skip)
+ {
+ for (i = 0; i < MAX_SHADER_STAGES; i++)
+ {
+ shaderStage_t *pStage = &stages[i];
+ shaderStage_t *diffuse, *normal, *specular, *lightmap;
+ bool parallax, tcgen, diffuselit, vertexlit;
+
+ if (!pStage->active)
+ continue;
+
+ // skip normal and specular maps
+ if (pStage->type != ST_COLORMAP)
+ continue;
+
+ // skip lightmaps
+ if (pStage->bundle[0].tcGen == TCGEN_LIGHTMAP)
+ continue;
+
+ diffuse = pStage;
+ normal = NULL;
+ parallax = false;
+ 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 = true;
+ }
+ break;
+
+ case ST_SPECULARMAP:
+ if (!specular)
+ {
+ specular = pStage2;
+ }
+ break;
+
+ case ST_COLORMAP:
+ if (pStage2->bundle[0].tcGen == TCGEN_LIGHTMAP)
+ {
+ lightmap = pStage2;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ tcgen = false;
+ if (diffuse->bundle[0].tcGen == TCGEN_ENVIRONMENT_MAPPED
+ || diffuse->bundle[0].tcGen == TCGEN_LIGHTMAP
+ || diffuse->bundle[0].tcGen == TCGEN_VECTOR)
+ {
+ tcgen = true;
+ }
+
+ diffuselit = false;
+ if (diffuse->rgbGen == CGEN_LIGHTING_DIFFUSE)
+ {
+ diffuselit = true;
+ }
+
+ vertexlit = false;
+ if (diffuse->rgbGen == CGEN_VERTEX_LIT || diffuse->rgbGen == CGEN_EXACT_VERTEX_LIT)
+ {
+ vertexlit = true;
+ }
+
+ CollapseStagesToLightall(diffuse, normal, specular, lightmap, diffuselit, vertexlit, parallax, tcgen);
+ }
+
+ // deactivate lightmap stages
+ for (i = 0; i < MAX_SHADER_STAGES; i++)
+ {
+ shaderStage_t *pStage = &stages[i];
+
+ if (!pStage->active)
+ continue;
+
+ if (pStage->bundle[0].tcGen == TCGEN_LIGHTMAP)
+ {
+ pStage->active = false;
+ }
+ }
+ }
+
+ // 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 = false;
+ }
+
+ if (pStage->type == ST_NORMALPARALLAXMAP)
+ {
+ pStage->active = false;
+ }
+
+ if (pStage->type == ST_SPECULARMAP)
+ {
+ pStage->active = false;
+ }
+ }
+
+ // 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 = false;
+ numStages++;
+ }
+
+ // convert any remaining lightmap stages to a lighting pass with a white texture
+ // only do this with r_sunlightMode non-zero, as it's only for correct shadows.
+ if (r_sunlightMode->integer && shader.numDeforms == 0)
+ {
+ for (i = 0; i < MAX_SHADER_STAGES; i++)
+ {
+ shaderStage_t *pStage = &stages[i];
+
+ if (!pStage->active)
+ continue;
+
+ if (pStage->adjustColorsForFog)
+ continue;
+
+ if (pStage->bundle[TB_DIFFUSEMAP].tcGen == TCGEN_LIGHTMAP)
+ {
+ pStage->glslShaderGroup = tr.lightallShader;
+ pStage->glslShaderIndex = LIGHTDEF_USE_LIGHTMAP;
+ pStage->bundle[TB_LIGHTMAP] = pStage->bundle[TB_DIFFUSEMAP];
+ pStage->bundle[TB_DIFFUSEMAP].image[0] = tr.whiteImage;
+ pStage->bundle[TB_DIFFUSEMAP].isLightmap = false;
+ pStage->bundle[TB_DIFFUSEMAP].tcGen = TCGEN_TEXTURE;
+ }
+ }
+ }
+
+ // convert any remaining lightingdiffuse stages to a lighting pass
+ if (shader.numDeforms == 0)
+ {
+ for (i = 0; i < MAX_SHADER_STAGES; i++)
+ {
+ shaderStage_t *pStage = &stages[i];
+
+ if (!pStage->active)
+ continue;
+
+ if (pStage->adjustColorsForFog)
+ continue;
+
+ if (pStage->rgbGen == CGEN_LIGHTING_DIFFUSE)
+ {
+ pStage->glslShaderGroup = tr.lightallShader;
+ pStage->glslShaderIndex = LIGHTDEF_USE_LIGHT_VECTOR;
+
+ if (pStage->bundle[0].tcGen != TCGEN_TEXTURE || pStage->bundle[0].numTexMods != 0)
+ pStage->glslShaderIndex |= LIGHTDEF_USE_TCGEN_AND_TCMOD;
+ }
+ }
+ }
+
+ 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->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 = (shader_t*)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] = (shaderStage_t*)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 = (texModInfo_t*)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 ) );
+ }
+}
+
+/*
+===============
+InitShader
+===============
+*/
+static void InitShader( const char *name, int lightmapIndex ) {
+ int i;
+
+ // 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];
+
+ // default normal/specular
+ VectorSet4(stages[i].normalScale, 0.0f, 0.0f, 0.0f, 0.0f);
+ if (r_pbr->integer)
+ {
+ stages[i].specularScale[0] = r_baseGloss->value;
+ }
+ else
+ {
+ stages[i].specularScale[0] =
+ stages[i].specularScale[1] =
+ stages[i].specularScale[2] = r_baseSpecular->value;
+ stages[i].specularScale[3] = r_baseGloss->value;
+ }
+ }
+}
+
+/*
+=========================
+FinishShader
+
+Returns a freshly allocated shader with all the needed info
+from the current global working shader
+=========================
+*/
+static shader_t *FinishShader( void ) {
+ int stage;
+ bool hasLightmapStage;
+ bool vertexLightmap;
+
+ hasLightmapStage = false;
+ vertexLightmap = false;
+
+ //
+ // 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 = false;
+ 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 = true;
+ } 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 = true;
+ //}
+
+
+
+ //
+ // 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();
+ hasLightmapStage = false;
+ }
+
+ //
+ // 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, 0 );
+ }
+ }
+
+ 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, bool mipRawImage ) {
+ char strippedName[MAX_QPATH];
+ int 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;
+ }
+ }
+
+ InitShader( strippedName, lightmapIndex );
+
+ //
+ // 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 = true;
+ }
+ sh = FinishShader();
+ return sh;
+ }
+
+
+ //
+ // if not defined in the in-memory shader descriptions,
+ // look for a single supported image file
+ //
+ {
+ int/*imgFlags_t*/ flags;
+
+ flags = IMGFLAG_NONE;
+
+ 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 = true;
+ 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 = true;
+ 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 = true;
+ 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 = true;
+ 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 = true;
+ stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+ stages[0].stateBits = GLS_DEFAULT;
+
+ stages[1].bundle[0].image[0] = image;
+ stages[1].active = true;
+ 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 = true;
+ stages[0].active = true;
+ 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 = true;
+ 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, bool mipRawImage) {
+ int 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;
+ }
+ }
+
+ InitShader( name, lightmapIndex );
+
+ //
+ // create the default shading commands
+ //
+ if ( shader.lightmapIndex == LIGHTMAP_NONE ) {
+ // dynamic colors at vertexes
+ stages[0].bundle[0].image[0] = image;
+ stages[0].active = true;
+ 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 = true;
+ 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 = true;
+ 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 = true;
+ stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+ stages[0].stateBits = GLS_DEFAULT;
+
+ stages[1].bundle[0].image[0] = image;
+ stages[1].active = true;
+ 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 = true;
+ stages[0].active = true;
+ 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 = true;
+ 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, true );
+
+ // 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, true );
+
+ // 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, false );
+
+ // 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->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] = {NULL};
+ char *p;
+ int numShaderFiles;
+ int i;
+ char *oldp, *token, *hashMem, *textEnd;
+ int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size;
+ char shaderName[MAX_QPATH];
+ int shaderLine;
+
+ 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];
+ COM_BeginParseSession(filename);
+ while(1)
+ {
+ token = COM_ParseExt(&p, qtrue);
+
+ if(!*token)
+ break;
+
+ Q_strncpyz(shaderName, token, sizeof(shaderName));
+ shaderLine = COM_GetCurrentParseLine();
+
+ token = COM_ParseExt(&p, qtrue);
+ if(token[0] != '{' || token[1] != '\0')
+ {
+ ri.Printf(PRINT_WARNING, "WARNING: Ignoring shader file %s. Shader \"%s\" on line %d missing opening brace",
+ filename, shaderName, shaderLine);
+ if (token[0])
+ {
+ ri.Printf(PRINT_WARNING, " (found \"%s\" on line %d)", token, COM_GetCurrentParseLine());
+ }
+ ri.Printf(PRINT_WARNING, ".\n");
+ ri.FS_FreeFile(buffers[i]);
+ buffers[i] = NULL;
+ break;
+ }
+
+ if(!SkipBracedSection(&p, 1))
+ {
+ ri.Printf(PRINT_WARNING, "WARNING: Ignoring shader file %s. Shader \"%s\" on line %d missing closing brace.\n",
+ filename, shaderName, shaderLine);
+ ri.FS_FreeFile(buffers[i]);
+ buffers[i] = NULL;
+ break;
+ }
+ }
+
+
+ if (buffers[i])
+ sum += summand;
+ }
+
+ // build single large buffer
+ s_shaderText = (char*)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, 0);
+ }
+
+ size += MAX_SHADERTEXT_HASH;
+
+ hashMem = (char*)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, 0);
+ }
+
+ return;
+
+}
+
+
+/*
+====================
+CreateInternalShaders
+====================
+*/
+static void CreateInternalShaders( void ) {
+ tr.numShaders = 0;
+
+ // init the default shader
+ InitShader( "<default>", LIGHTMAP_NONE );
+ stages[0].bundle[0].image[0] = tr.defaultImage;
+ stages[0].active = true;
+ stages[0].stateBits = GLS_DEFAULT;
+ tr.defaultShader = FinishShader();
+
+ // shadow shader is just a marker
+ Q_strncpyz( shader.name, "<stencil shadow>", sizeof( shader.name ) );
+ shader.sort = SS_STENCIL_SHADOW;
+ tr.shadowShader = FinishShader();
+}
+
+static void CreateExternalShaders( void ) {
+ tr.projectionShadowShader = R_FindShader( "projectionShadow", LIGHTMAP_NONE, true );
+ tr.flareShader = R_FindShader( "flareShader", LIGHTMAP_NONE, true );
+
+ // 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, true );
+
+ tr.sunFlareShader = R_FindShader( "gfx/2d/sunflare", LIGHTMAP_NONE, true);
+
+ // HACK: if sunflare is missing, make one using the flare image or dlight image
+ if (tr.sunFlareShader->defaultShader)
+ {
+ image_t *image;
+
+ if (!tr.flareShader->defaultShader && tr.flareShader->stages[0] && tr.flareShader->stages[0]->bundle[0].image[0])
+ image = tr.flareShader->stages[0]->bundle[0].image[0];
+ else
+ image = tr.dlightImage;
+
+ InitShader( "gfx/2d/sunflare", LIGHTMAP_NONE );
+ stages[0].bundle[0].image[0] = image;
+ stages[0].active = true;
+ stages[0].stateBits = GLS_DEFAULT;
+ tr.sunFlareShader = FinishShader();
+ }
+
+}
+
+/*
+==================
+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/renderergl2/tr_shadows.cpp b/src/renderergl2/tr_shadows.cpp
new file mode 100644
index 0000000..a602c31
--- /dev/null
+++ b/src/renderergl2/tr_shadows.cpp
@@ -0,0 +1,327 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+#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];
+static vec3_t shadowXyz[SHADER_MAX_VERTEXES];
+
+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( shadowXyz[ i1 ] );
+ qglVertex3fv( tess.xyz[ i2 ] );
+ qglVertex3fv( shadowXyz[ i2 ] );
+ qglVertex3fv( tess.xyz[ i3 ] );
+ qglVertex3fv( shadowXyz[ i3 ] );
+ qglVertex3fv( tess.xyz[ i1 ] );
+ qglVertex3fv( shadowXyz[ i1 ] );
+ 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( shadowXyz[ i ] );
+ qglVertex3fv( tess.xyz[ i2 ] );
+ qglVertex3fv( shadowXyz[ i2 ] );
+ 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];
+
+ if ( glConfig.stencilBits < 4 ) {
+ return;
+ }
+
+ VectorCopy( backEnd.currentEntity->modelLightDir, lightDir );
+
+ // project vertexes away from light direction
+ for ( i = 0 ; i < tess.numVertexes ; i++ ) {
+ VectorMA( tess.xyz[i], -512, lightDir, shadowXyz[i] );
+ }
+
+ // 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_BindToTMU( tr.whiteImage, TB_COLORMAP );
+ 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 );
+
+ GL_Cull( CT_BACK_SIDED );
+ qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR );
+
+ R_RenderShadowEdges();
+
+ GL_Cull( CT_FRONT_SIDED );
+ 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);
+ GL_Cull( CT_TWO_SIDED );
+
+ GL_BindToTMU( tr.whiteImage, TB_COLORMAP );
+
+ 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.orientation.axis[0][2];
+ ground[1] = backEnd.orientation.axis[1][2];
+ ground[2] = backEnd.orientation.axis[2][2];
+
+ groundDist = backEnd.orientation.origin[2] - backEnd.currentEntity->e.shadowPlane;
+
+ VectorCopy( backEnd.currentEntity->modelLightDir, 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/renderergl2/tr_sky.cpp b/src/renderergl2/tr_sky.cpp
new file mode 100644
index 0000000..98fb451
--- /dev/null
+++ b/src/renderergl2/tr_sky.cpp
@@ -0,0 +1,904 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// 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<nump ; i++, vp+=3)
+ {
+ VectorAdd (vp, v, v);
+ }
+ av[0] = fabs(v[0]);
+ av[1] = fabs(v[1]);
+ av[2] = fabs(v[2]);
+ if (av[0] > 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<nump ; i++, vecs+=3)
+ {
+ j = vec_to_st[axis][2];
+ if (j > 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;
+ bool 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 = false;
+ norm = sky_clip[stage];
+ for (i=0, v = vecs ; i<nump ; i++, v+=3)
+ {
+ d = DotProduct (v, norm);
+ if (d > ON_EPSILON)
+ {
+ front = true;
+ sides[i] = SIDE_FRONT;
+ }
+ else if (d < -ON_EPSILON)
+ {
+ back = true;
+ 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 ; i<nump ; i++, v+=3)
+ {
+ switch (sides[i])
+ {
+ case SIDE_FRONT:
+ VectorCopy (v, newv[0][newc[0]]);
+ newc[0]++;
+ break;
+ case SIDE_BACK:
+ VectorCopy (v, newv[1][newc[1]]);
+ newc[1]++;
+ break;
+ case SIDE_ON:
+ VectorCopy (v, newv[0][newc[0]]);
+ newc[0]++;
+ VectorCopy (v, newv[1][newc[1]]);
+ newc[1]++;
+ break;
+ }
+
+ if (sides[i] == SIDE_ON || sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
+ continue;
+
+ d = dists[i] / (dists[i] - dists[i+1]);
+ for (j=0 ; j<3 ; j++)
+ {
+ e = v[j] + d*(v[j+3] - v[j]);
+ newv[0][newc[0]][j] = e;
+ newv[1][newc[1]][j] = e;
+ }
+ newc[0]++;
+ newc[1]++;
+ }
+
+ // continue
+ ClipSkyPolygon (newc[0], newv[0][0], stage+1);
+ ClipSkyPolygon (newc[1], newv[1][0], stage+1);
+}
+
+/*
+==============
+ClearSkyBox
+==============
+*/
+static void ClearSkyBox (void) {
+ int i;
+
+ for (i=0 ; i<6 ; i++) {
+ sky_mins[0][i] = sky_mins[1][i] = 9999;
+ sky_maxs[0][i] = sky_maxs[1][i] = -9999;
+ }
+}
+
+/*
+================
+RB_ClipSkyPolygons
+================
+*/
+void RB_ClipSkyPolygons( shaderCommands_t *input )
+{
+ vec3_t p[5]; // need one extra point for clipping
+ int i, j;
+
+ ClearSkyBox();
+
+ for ( i = 0; i < input->numIndexes; i += 3 )
+ {
+ for (j = 0 ; j < 3 ; j++)
+ {
+ VectorSubtract( input->xyz[input->indexes[i+j]],
+ backEnd.viewParms.orientation.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_BindToTMU( image, TB_COLORMAP );
+ 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] = s_skyTexCoords[t][s][0];
+ tess.texCoords[tess.numVertexes][1] = s_skyTexCoords[t][s][1];
+
+ tess.numVertexes++;
+
+ if(tess.numVertexes >= SHADER_MAX_VERTEXES)
+ {
+ ri.Error(ERR_DROP, "SHADER_MAX_VERTEXES hit in DrawSkySideVBO()");
+ }
+ }
+ }
+
+ 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()");
+ }
+
+ 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_UpdateTessVao(ATTR_POSITION | ATTR_TEXCOORD);
+/*
+ {
+ shaderProgram_t *sp = &tr.textureColorShader;
+
+ GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
+ GLSL_BindProgram(sp);
+
+ GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+
+ color[0] =
+ color[1] =
+ color[2] = tr.identityLight;
+ color[3] = 1.0f;
+ GLSL_SetUniformVec4(sp, UNIFORM_COLOR, color);
+ }
+*/
+ {
+ shaderProgram_t *sp = &tr.lightallShader[0];
+ vec4_t vector;
+
+ GLSL_BindProgram(sp);
+
+ GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+
+ color[0] =
+ color[1] =
+ color[2] =
+ color[3] = 1.0f;
+ GLSL_SetUniformVec4(sp, UNIFORM_BASECOLOR, color);
+
+ color[0] =
+ color[1] =
+ color[2] =
+ color[3] = 0.0f;
+ GLSL_SetUniformVec4(sp, UNIFORM_VERTCOLOR, color);
+
+ VectorSet4(vector, 1.0, 0.0, 0.0, 1.0);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX, vector);
+
+ VectorSet4(vector, 0.0, 0.0, 0.0, 0.0);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, vector);
+
+ GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0);
+ }
+
+ R_DrawElements(tess.numIndexes - tess.firstIndex, tess.firstIndex);
+
+ //qglDrawElements(GL_TRIANGLES, tess.numIndexes - tess.firstIndex, GL_INDEX_TYPE, BUFFER_OFFSET(tess.firstIndex * sizeof(glIndex_t)));
+
+ //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], bool 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.orientation.origin, tess.xyz[tess.numVertexes] );
+ tess.texCoords[tess.numVertexes][0] = s_skyTexCoords[t][s][0];
+ tess.texCoords[tess.numVertexes][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] = static_cast<int>(sky_mins[0][i] * HALF_SKY_SUBDIVISIONS);
+ sky_mins_subd[1] = static_cast<int>(sky_mins[1][i] * HALF_SKY_SUBDIVISIONS);
+ sky_maxs_subd[0] = static_cast<int>(sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS);
+ sky_maxs_subd[1] = static_cast<int>(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( float scale, shader_t *shader ) {
+ float size;
+ float dist;
+ vec3_t origin, vec1, vec2;
+
+ if ( !backEnd.skyRenderedThisView ) {
+ return;
+ }
+
+ //qglLoadMatrixf( backEnd.viewParms.world.modelMatrix );
+ //qglTranslatef (backEnd.viewParms.orientation.origin[0], backEnd.viewParms.orientation.origin[1], backEnd.viewParms.orientation.origin[2]);
+ {
+ // FIXME: this could be a lot cleaner
+ mat4_t translation, modelview;
+
+ Mat4Translation( backEnd.viewParms.orientation.origin, translation );
+ Mat4Multiply( backEnd.viewParms.world.modelMatrix, translation, modelview );
+ GL_SetModelviewMatrix( modelview );
+ }
+
+ dist = backEnd.viewParms.zFar / 1.75; // div sqrt(3)
+ size = dist * scale;
+
+ 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 );
+
+ RB_BeginSurface( shader, 0, 0 );
+
+ RB_AddQuadStamp(origin, vec1, vec2, colorWhite);
+
+ 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 ) {
+ mat4_t oldmodelview;
+
+ GL_State( 0 );
+ GL_Cull( CT_FRONT_SIDED );
+ //qglTranslatef (backEnd.viewParms.orientation.origin[0], backEnd.viewParms.orientation.origin[1], backEnd.viewParms.orientation.origin[2]);
+
+ {
+ // FIXME: this could be a lot cleaner
+ mat4_t trans, product;
+
+ Mat4Copy( glState.modelview, oldmodelview );
+ Mat4Translation( backEnd.viewParms.orientation.origin, trans );
+ Mat4Multiply( 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 = true;
+}
diff --git a/src/renderergl2/tr_subs.cpp b/src/renderergl2/tr_subs.cpp
new file mode 100644
index 0000000..e7040e5
--- /dev/null
+++ b/src/renderergl2/tr_subs.cpp
@@ -0,0 +1,49 @@
+/*
+===========================================================================
+Copyright (C) 2010 James Canete (use.less01@gmail.com)
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// 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/renderergl2/tr_surface.cpp b/src/renderergl2/tr_surface.cpp
new file mode 100644
index 0000000..39f2c34
--- /dev/null
+++ b/src/renderergl2/tr_surface.cpp
@@ -0,0 +1,1320 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// tr_surf.c
+#include "tr_local.h"
+#if idppc_altivec && !defined(__APPLE__)
+#include <altivec.h>
+#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, tess.cubemapIndex );
+}
+
+void RB_CheckVao(vao_t *vao)
+{
+ if (vao != glState.currentVao)
+ {
+ RB_EndSurface();
+ RB_BeginSurface(tess.shader, tess.fogNum, tess.cubemapIndex);
+
+ R_BindVao(vao);
+ }
+
+ if (vao != tess.vao)
+ tess.useInternalVao = false;
+}
+
+
+/*
+==============
+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;
+ int16_t iNormal[4];
+ uint16_t iColor[4];
+ int ndx;
+
+ RB_CheckVao(tess.vao);
+
+ 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.orientation.axis[0], normal );
+
+ R_VaoPackNormal(iNormal, normal);
+
+ VectorCopy4(iNormal, tess.normal[ndx]);
+ VectorCopy4(iNormal, tess.normal[ndx + 1]);
+ VectorCopy4(iNormal, tess.normal[ndx + 2]);
+ VectorCopy4(iNormal, tess.normal[ndx + 3]);
+
+ // standard square texture coordinates
+ VectorSet2(tess.texCoords[ndx], s1, t1);
+ VectorSet2(tess.lightCoords[ndx], s1, t1);
+
+ VectorSet2(tess.texCoords[ndx+1], s2, t1);
+ VectorSet2(tess.lightCoords[ndx+1], s2, t1);
+
+ VectorSet2(tess.texCoords[ndx+2], s2, t2);
+ VectorSet2(tess.lightCoords[ndx+2], s2, t2);
+
+ VectorSet2(tess.texCoords[ndx+3], s1, t2);
+ VectorSet2(tess.lightCoords[ndx+3], s1, t2);
+
+ // constant color all the way around
+ // should this be identity and let the shader specify from entity?
+
+ R_VaoPackColor(iColor, color);
+
+ VectorCopy4(iColor, tess.color[ndx]);
+ VectorCopy4(iColor, tess.color[ndx + 1]);
+ VectorCopy4(iColor, tess.color[ndx + 2]);
+ VectorCopy4(iColor, tess.color[ndx + 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]);
+ tess.numVertexes++;
+
+ VectorCopy4(quadVerts[1], tess.xyz[tess.numVertexes]);
+ VectorCopy2(texCoords[1], tess.texCoords[tess.numVertexes]);
+ tess.numVertexes++;
+
+ VectorCopy4(quadVerts[2], tess.xyz[tess.numVertexes]);
+ VectorCopy2(texCoords[2], tess.texCoords[tess.numVertexes]);
+ tess.numVertexes++;
+
+ VectorCopy4(quadVerts[3], tess.xyz[tess.numVertexes]);
+ VectorCopy2(texCoords[3], tess.texCoords[tess.numVertexes]);
+ 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_UpdateTessVao(ATTR_POSITION | ATTR_TEXCOORD);
+
+ R_DrawElements(tess.numIndexes, tess.firstIndex);
+
+ tess.numIndexes = 0;
+ tess.numVertexes = 0;
+ tess.firstIndex = 0;
+}
+
+
+void RB_InstantQuad(vec4_t quadVerts[4])
+{
+ vec2_t texCoords[4];
+
+ VectorSet2(texCoords[0], 0.0f, 0.0f);
+ VectorSet2(texCoords[1], 1.0f, 0.0f);
+ VectorSet2(texCoords[2], 1.0f, 1.0f);
+ VectorSet2(texCoords[3], 0.0f, 1.0f);
+
+ GLSL_BindProgram(&tr.textureColorShader);
+
+ GLSL_SetUniformMat4(&tr.textureColorShader, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+ GLSL_SetUniformVec4(&tr.textureColorShader, UNIFORM_COLOR, colorWhite);
+
+ RB_InstantQuad2(quadVerts, texCoords);
+}
+
+
+/*
+==============
+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.orientation.axis[1], radius, left );
+ VectorScale( backEnd.viewParms.orientation.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.orientation.axis[1], c * radius, left );
+ VectorMA( left, -s * radius, backEnd.viewParms.orientation.axis[2], left );
+
+ VectorScale( backEnd.viewParms.orientation.axis[2], c * radius, up );
+ VectorMA( up, s * radius, backEnd.viewParms.orientation.axis[1], up );
+ }
+ if ( backEnd.viewParms.isMirror ) {
+ VectorSubtract( vec3_origin, left, left );
+ }
+
+ 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_CheckVao(tess.vao);
+
+ 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] = p->verts[i].st[0];
+ tess.texCoords[numv][1] = p->verts[i].st[1];
+ tess.color[numv][0] = (int)p->verts[i].modulate[0] * 257;
+ tess.color[numv][1] = (int)p->verts[i].modulate[1] * 257;
+ tess.color[numv][2] = (int)p->verts[i].modulate[2] * 257;
+ tess.color[numv][3] = (int)p->verts[i].modulate[3] * 257;
+
+ 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_SurfaceVertsAndIndexes( int numVerts, srfVert_t *verts, int numIndexes, glIndex_t *indexes, int dlightBits, int pshadowBits)
+{
+ int i;
+ glIndex_t *inIndex;
+ srfVert_t *dv;
+ float *xyz, *texCoords, *lightCoords;
+ int16_t *lightdir;
+ int16_t *normal;
+ int16_t *tangent;
+ glIndex_t *outIndex;
+ uint16_t *color;
+
+ RB_CheckVao(tess.vao);
+
+ RB_CHECKOVERFLOW( numVerts, numIndexes );
+
+ inIndex = indexes;
+ outIndex = &tess.indexes[ tess.numIndexes ];
+ for ( i = 0 ; i < numIndexes ; i++ ) {
+ *outIndex++ = tess.numVertexes + *inIndex++;
+ }
+ tess.numIndexes += numIndexes;
+
+ 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 )
+ VectorCopy4(dv->normal, normal);
+ }
+
+ if ( tess.shader->vertexAttribs & ATTR_TANGENT )
+ {
+ dv = verts;
+ tangent = tess.tangent[ tess.numVertexes ];
+ for ( i = 0 ; i < numVerts ; i++, dv++, tangent+=4 )
+ VectorCopy4(dv->tangent, tangent);
+ }
+
+ if ( tess.shader->vertexAttribs & ATTR_TEXCOORD )
+ {
+ dv = verts;
+ texCoords = tess.texCoords[tess.numVertexes];
+ for ( i = 0 ; i < numVerts ; i++, dv++, texCoords+=2 )
+ VectorCopy2(dv->st, texCoords);
+ }
+
+ if ( tess.shader->vertexAttribs & ATTR_LIGHTCOORD )
+ {
+ dv = verts;
+ lightCoords = tess.lightCoords[ tess.numVertexes ];
+ for ( i = 0 ; i < numVerts ; i++, dv++, lightCoords+=2 )
+ VectorCopy2(dv->lightmap, lightCoords);
+ }
+
+ if ( tess.shader->vertexAttribs & ATTR_COLOR )
+ {
+ dv = verts;
+ color = tess.color[ tess.numVertexes ];
+ for ( i = 0 ; i < numVerts ; i++, dv++, color+=4 )
+ VectorCopy4(dv->color, color);
+ }
+
+ if ( tess.shader->vertexAttribs & ATTR_LIGHTDIRECTION )
+ {
+ dv = verts;
+ lightdir = tess.lightdir[ tess.numVertexes ];
+ for ( i = 0 ; i < numVerts ; i++, dv++, lightdir+=4 )
+ VectorCopy4(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 bool RB_SurfaceVaoCached(int numVerts, srfVert_t *verts, int numIndexes, glIndex_t *indexes, int dlightBits, int pshadowBits)
+{
+ bool recycleVertexBuffer = false;
+ bool recycleIndexBuffer = false;
+ bool endSurface = false;
+
+ if (!(!ShaderRequiresCPUDeforms(tess.shader) && !tess.shader->isSky && !tess.shader->isPortal))
+ return false;
+
+ if (!numIndexes || !numVerts)
+ return false;
+
+ VaoCache_BindVao();
+
+ tess.dlightBits |= dlightBits;
+ tess.pshadowBits |= pshadowBits;
+
+ VaoCache_CheckAdd(&endSurface, &recycleVertexBuffer, &recycleIndexBuffer, numVerts, numIndexes);
+
+ if (endSurface)
+ {
+ RB_EndSurface();
+ RB_BeginSurface(tess.shader, tess.fogNum, tess.cubemapIndex);
+ }
+
+ if (recycleVertexBuffer)
+ VaoCache_RecycleVertexBuffer();
+
+ if (recycleIndexBuffer)
+ VaoCache_RecycleIndexBuffer();
+
+ if (!tess.numVertexes)
+ VaoCache_InitNewSurfaceSet();
+
+ VaoCache_AddSurface(verts, numVerts, indexes, numIndexes);
+
+ tess.numIndexes += numIndexes;
+ tess.numVertexes += numVerts;
+ tess.useInternalVao = false;
+ tess.useCacheVao = true;
+
+ return true;
+}
+
+/*
+=============
+RB_SurfaceTriangles
+=============
+*/
+static void RB_SurfaceTriangles( srfBspSurface_t *srf ) {
+ if (RB_SurfaceVaoCached(srf->numVerts, srf->verts, srf->numIndexes,
+ srf->indexes, srf->dlightBits, srf->pshadowBits))
+ {
+ return;
+ }
+
+ RB_SurfaceVertsAndIndexes(srf->numVerts, srf->verts, srf->numIndexes,
+ srf->indexes, srf->dlightBits, srf->pshadowBits);
+}
+
+
+
+/*
+==============
+RB_SurfaceBeam
+==============
+*/
+static void RB_SurfaceBeam( void )
+{
+#define NUM_BEAM_SEGS 6
+ refEntity_t *e;
+ shaderProgram_t *sp = &tr.textureColorShader;
+ 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_BindToTMU( tr.whiteImage, TB_COLORMAP );
+
+ 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_UpdateTessVao(ATTR_POSITION);
+
+ GLSL_BindProgram(sp);
+
+ GLSL_SetUniformMat4(sp, UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
+
+ GLSL_SetUniformVec4(sp, UNIFORM_COLOR, colorRed);
+
+ GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0);
+
+ R_DrawElements(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;
+
+ RB_CheckVao(tess.vao);
+
+ RB_CHECKOVERFLOW( 4, 6 );
+
+ vbase = tess.numVertexes;
+
+ spanWidth2 = -spanWidth;
+
+ // FIXME: use quad stamp?
+ VectorMA( start, spanWidth, up, tess.xyz[tess.numVertexes] );
+ tess.texCoords[tess.numVertexes][0] = 0;
+ tess.texCoords[tess.numVertexes][1] = 0;
+ tess.color[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 0.25f * 257.0f;
+ tess.color[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 0.25f * 257.0f;
+ tess.color[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 0.25f * 257.0f;
+ tess.numVertexes++;
+
+ VectorMA( start, spanWidth2, up, tess.xyz[tess.numVertexes] );
+ tess.texCoords[tess.numVertexes][0] = 0;
+ tess.texCoords[tess.numVertexes][1] = 1;
+ tess.color[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 257;
+ tess.color[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 257;
+ tess.color[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 257;
+ tess.numVertexes++;
+
+ VectorMA( end, spanWidth, up, tess.xyz[tess.numVertexes] );
+
+ tess.texCoords[tess.numVertexes][0] = t;
+ tess.texCoords[tess.numVertexes][1] = 0;
+ tess.color[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 257;
+ tess.color[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 257;
+ tess.color[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 257;
+ tess.numVertexes++;
+
+ VectorMA( end, spanWidth2, up, tess.xyz[tess.numVertexes] );
+ tess.texCoords[tess.numVertexes][0] = t;
+ tess.texCoords[tess.numVertexes][1] = 1;
+ tess.color[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 257;
+ tess.color[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 257;
+ tess.color[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 257;
+ 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] );
+ }
+ }
+
+ RB_CheckVao(tess.vao);
+
+ 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] = (j < 2);
+ tess.texCoords[tess.numVertexes][1] = (j && j != 3);
+ tess.color[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0] * 257;
+ tess.color[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1] * 257;
+ tess.color[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2] * 257;
+ 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.orientation.origin, v1 );
+ VectorNormalize( v1 );
+ VectorSubtract( end, backEnd.viewParms.orientation.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.orientation.origin, v1 );
+ VectorNormalize( v1 );
+ VectorSubtract( end, backEnd.viewParms.orientation.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 );
+ }
+}
+
+
+static void LerpMeshVertexes(mdvSurface_t *surf, float backlerp)
+{
+ float *outXyz;
+ int16_t *outNormal, *outTangent;
+ mdvVertex_t *newVerts;
+ int vertNum;
+
+ newVerts = surf->verts + backEnd.currentEntity->e.frame * surf->numVerts;
+
+ outXyz = tess.xyz[tess.numVertexes];
+ outNormal = tess.normal[tess.numVertexes];
+ outTangent = tess.tangent[tess.numVertexes];
+
+ if (backlerp == 0)
+ {
+ //
+ // just copy the vertexes
+ //
+
+ for (vertNum=0 ; vertNum < surf->numVerts ; vertNum++)
+ {
+ VectorCopy(newVerts->xyz, outXyz);
+ VectorCopy4(newVerts->normal, outNormal);
+ VectorCopy4(newVerts->tangent, outTangent);
+
+ newVerts++;
+ outXyz += 4;
+ outNormal += 4;
+ outTangent += 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(newVerts->xyz, oldVerts->xyz, backlerp, outXyz);
+
+ outNormal[0] = (int16_t)(newVerts->normal[0] * (1.0f - backlerp) + oldVerts->normal[0] * backlerp);
+ outNormal[1] = (int16_t)(newVerts->normal[1] * (1.0f - backlerp) + oldVerts->normal[1] * backlerp);
+ outNormal[2] = (int16_t)(newVerts->normal[2] * (1.0f - backlerp) + oldVerts->normal[2] * backlerp);
+ outNormal[3] = 0;
+
+ outTangent[0] = (int16_t)(newVerts->tangent[0] * (1.0f - backlerp) + oldVerts->tangent[0] * backlerp);
+ outTangent[1] = (int16_t)(newVerts->tangent[1] * (1.0f - backlerp) + oldVerts->tangent[1] * backlerp);
+ outTangent[2] = (int16_t)(newVerts->tangent[2] * (1.0f - backlerp) + oldVerts->tangent[2] * backlerp);
+ outTangent[3] = newVerts->tangent[3];
+
+ newVerts++;
+ oldVerts++;
+ outXyz += 4;
+ outNormal += 4;
+ outTangent += 4;
+ }
+ }
+
+}
+
+
+/*
+=============
+RB_SurfaceMesh
+=============
+*/
+static void RB_SurfaceMesh(mdvSurface_t *surface) {
+ int j;
+ float backlerp;
+ mdvSt_t *texCoords;
+ int Bob, Doug;
+ int numVerts;
+
+ if ( backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) {
+ backlerp = 0;
+ } else {
+ backlerp = backEnd.currentEntity->e.backlerp;
+ }
+
+ RB_CheckVao(tess.vao);
+
+ RB_CHECKOVERFLOW( surface->numVerts, surface->numIndexes );
+
+ LerpMeshVertexes (surface, backlerp);
+
+ Bob = tess.numIndexes;
+ Doug = tess.numVertexes;
+ for (j = 0 ; j < surface->numIndexes ; j++) {
+ tess.indexes[Bob + j] = Doug + surface->indexes[j];
+ }
+ tess.numIndexes += surface->numIndexes;
+
+ texCoords = surface->st;
+
+ numVerts = surface->numVerts;
+ for ( j = 0; j < numVerts; j++ ) {
+ tess.texCoords[Doug + j][0] = texCoords[j].st[0];
+ tess.texCoords[Doug + j][1] = texCoords[j].st[1];
+ // FIXME: fill in lightmapST for completeness?
+ }
+
+ tess.numVertexes += surface->numVerts;
+
+}
+
+
+/*
+==============
+RB_SurfaceFace
+==============
+*/
+static void RB_SurfaceFace( srfBspSurface_t *srf ) {
+ if (RB_SurfaceVaoCached(srf->numVerts, srf->verts, srf->numIndexes,
+ srf->indexes, srf->dlightBits, srf->pshadowBits))
+ {
+ return;
+ }
+
+ RB_SurfaceVertsAndIndexes(srf->numVerts, srf->verts, srf->numIndexes,
+ srf->indexes, srf->dlightBits, srf->pshadowBits);
+}
+
+
+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.orientation.axis[0][0] + local[1] * backEnd.orientation.axis[1][0] +
+ local[2] * backEnd.orientation.axis[2][0] + backEnd.orientation.origin[0];
+ world[1] = local[0] * backEnd.orientation.axis[0][1] + local[1] * backEnd.orientation.axis[1][1] +
+ local[2] * backEnd.orientation.axis[2][1] + backEnd.orientation.origin[1];
+ world[2] = local[0] * backEnd.orientation.axis[0][2] + local[1] * backEnd.orientation.axis[1][2] +
+ local[2] * backEnd.orientation.axis[2][2] + backEnd.orientation.origin[2];
+
+ VectorSubtract( world, backEnd.viewParms.orientation.origin, world );
+ d = DotProduct( world, backEnd.viewParms.orientation.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( srfBspSurface_t *srf ) {
+ int i, j;
+ float *xyz;
+ float *texCoords, *lightCoords;
+ int16_t *normal;
+ int16_t *tangent;
+ uint16_t *color;
+ int16_t *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_SurfaceVaoCached(srf->numVerts, srf->verts, srf->numIndexes,
+ srf->indexes, srf->dlightBits, srf->pshadowBits))
+ {
+ return;
+ }
+
+ RB_CheckVao(tess.vao);
+
+ dlightBits = srf->dlightBits;
+ tess.dlightBits |= dlightBits;
+
+ pshadowBits = srf->pshadowBits;
+ 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, tess.cubemapIndex );
+ } 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];
+ tangent = tess.tangent[numVertexes];
+ texCoords = tess.texCoords[numVertexes];
+ lightCoords = tess.lightCoords[numVertexes];
+ color = tess.color[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 )
+ {
+ VectorCopy4(dv->normal, normal);
+ normal += 4;
+ }
+
+ if ( tess.shader->vertexAttribs & ATTR_TANGENT )
+ {
+ VectorCopy4(dv->tangent, tangent);
+ tangent += 4;
+ }
+
+ if ( tess.shader->vertexAttribs & ATTR_TEXCOORD )
+ {
+ VectorCopy2(dv->st, texCoords);
+ texCoords += 2;
+ }
+
+ if ( tess.shader->vertexAttribs & ATTR_LIGHTCOORD )
+ {
+ VectorCopy2(dv->lightmap, lightCoords);
+ lightCoords += 2;
+ }
+
+ if ( tess.shader->vertexAttribs & ATTR_COLOR )
+ {
+ VectorCopy4(dv->color, color);
+ color += 4;
+ }
+
+ if ( tess.shader->vertexAttribs & ATTR_LIGHTDIRECTION )
+ {
+ VectorCopy4(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_BindToTMU( tr.whiteImage, TB_COLORMAP );
+ GL_State( GLS_DEFAULT );
+ qglLineWidth( 3 );
+ qglBegin( GL_LINES );
+ qglColor3f( 1,0,0 );
+ qglVertex3f( 0,0,0 );
+ qglVertex3f( 16,0,0 );
+ qglColor3f( 0,1,0 );
+ qglVertex3f( 0,0,0 );
+ qglVertex3f( 0,16,0 );
+ qglColor3f( 0,0,1 );
+ qglVertex3f( 0,0,0 );
+ qglVertex3f( 0,0,16 );
+ qglEnd();
+ 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;
+ }
+}
+
+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);
+}
+
+void RB_SurfaceVaoMdvMesh(srfVaoMdvMesh_t * surface)
+{
+ //mdvModel_t *mdvModel;
+ //mdvSurface_t *mdvSurface;
+ refEntity_t *refEnt;
+
+ GLimp_LogComment("--- RB_SurfaceVaoMdvMesh ---\n");
+
+ if (ShaderRequiresCPUDeforms(tess.shader))
+ {
+ RB_SurfaceMesh(surface->mdvSurface);
+ return;
+ }
+
+ if(!surface->vao)
+ return;
+
+ //RB_CheckVao(surface->vao);
+ RB_EndSurface();
+ RB_BeginSurface(tess.shader, tess.fogNum, tess.cubemapIndex);
+
+ R_BindVao(surface->vao);
+
+ tess.useInternalVao = false;
+
+ tess.numIndexes = surface->numIndexes;
+ tess.numVertexes = surface->numVerts;
+
+ //mdvModel = surface->mdvModel;
+ //mdvSurface = surface->mdvSurface;
+
+ refEnt = &backEnd.currentEntity->e;
+
+ glState.vertexAttribsInterpolation = (refEnt->oldframe == refEnt->frame) ? 0.0f : refEnt->backlerp;
+
+ if (surface->mdvModel->numFrames > 1)
+ {
+ int frameOffset, attribIndex;
+ vaoAttrib_t *vAtb;
+
+ glState.vertexAnimation = true;
+
+ if (glRefConfig.vertexArrayObject)
+ {
+ qglBindBuffer(GL_ARRAY_BUFFER, surface->vao->vertexesVBO);
+ }
+
+ frameOffset = refEnt->frame * surface->vao->frameSize;
+
+ attribIndex = ATTR_INDEX_POSITION;
+ vAtb = &surface->vao->attribs[attribIndex];
+ qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset + frameOffset));
+
+ attribIndex = ATTR_INDEX_NORMAL;
+ vAtb = &surface->vao->attribs[attribIndex];
+ qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset + frameOffset));
+
+ attribIndex = ATTR_INDEX_TANGENT;
+ vAtb = &surface->vao->attribs[attribIndex];
+ qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset + frameOffset));
+
+ frameOffset = refEnt->oldframe * surface->vao->frameSize;
+
+ attribIndex = ATTR_INDEX_POSITION2;
+ vAtb = &surface->vao->attribs[attribIndex];
+ qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset + frameOffset));
+
+ attribIndex = ATTR_INDEX_NORMAL2;
+ vAtb = &surface->vao->attribs[attribIndex];
+ qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset + frameOffset));
+
+ attribIndex = ATTR_INDEX_TANGENT2;
+ vAtb = &surface->vao->attribs[attribIndex];
+ qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset + frameOffset));
+
+
+ if (!glRefConfig.vertexArrayObject)
+ {
+ attribIndex = ATTR_INDEX_TEXCOORD;
+ vAtb = &surface->vao->attribs[attribIndex];
+ qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset));
+ }
+ }
+
+ RB_EndSurface();
+
+ // So we don't lerp surfaces that shouldn't be lerped
+ glState.vertexAnimation = false;
+}
+
+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_MDRSurfaceAnim, // SF_MDR,
+ (void(*)(void*))RB_IQMSurfaceAnim, // SF_IQM,
+ (void(*)(void*))RB_SurfaceFlare, // SF_FLARE,
+ (void(*)(void*))RB_SurfaceEntity, // SF_ENTITY
+ (void(*)(void*))RB_SurfaceVaoMdvMesh, // SF_VAO_MDVMESH
+};
diff --git a/src/renderergl2/tr_vbo.cpp b/src/renderergl2/tr_vbo.cpp
new file mode 100644
index 0000000..58836cd
--- /dev/null
+++ b/src/renderergl2/tr_vbo.cpp
@@ -0,0 +1,945 @@
+/*
+===========================================================================
+Copyright (C) 2007-2009 Robert Beckebans <trebor_7@users.sourceforge.net>
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+// tr_vbo.c
+#include "tr_local.h"
+
+
+void R_VaoPackTangent(int16_t *out, vec4_t v)
+{
+ out[0] = v[0] * 32767.0f + (v[0] > 0.0f ? 0.5f : -0.5f);
+ out[1] = v[1] * 32767.0f + (v[1] > 0.0f ? 0.5f : -0.5f);
+ out[2] = v[2] * 32767.0f + (v[2] > 0.0f ? 0.5f : -0.5f);
+ out[3] = v[3] * 32767.0f + (v[3] > 0.0f ? 0.5f : -0.5f);
+}
+
+void R_VaoPackNormal(int16_t *out, vec3_t v)
+{
+ out[0] = v[0] * 32767.0f + (v[0] > 0.0f ? 0.5f : -0.5f);
+ out[1] = v[1] * 32767.0f + (v[1] > 0.0f ? 0.5f : -0.5f);
+ out[2] = v[2] * 32767.0f + (v[2] > 0.0f ? 0.5f : -0.5f);
+ out[3] = 0;
+}
+
+void R_VaoPackColor(uint16_t *out, vec4_t c)
+{
+ out[0] = c[0] * 65535.0f + 0.5f;
+ out[1] = c[1] * 65535.0f + 0.5f;
+ out[2] = c[2] * 65535.0f + 0.5f;
+ out[3] = c[3] * 65535.0f + 0.5f;
+}
+
+void R_VaoUnpackTangent(vec4_t v, int16_t *pack)
+{
+ v[0] = pack[0] / 32767.0f;
+ v[1] = pack[1] / 32767.0f;
+ v[2] = pack[2] / 32767.0f;
+ v[3] = pack[3] / 32767.0f;
+}
+
+void R_VaoUnpackNormal(vec3_t v, int16_t *pack)
+{
+ v[0] = pack[0] / 32767.0f;
+ v[1] = pack[1] / 32767.0f;
+ v[2] = pack[2] / 32767.0f;
+}
+
+void Vao_SetVertexPointers(vao_t *vao)
+{
+ int attribIndex;
+
+ // set vertex pointers
+ for (attribIndex = 0; attribIndex < ATTR_INDEX_COUNT; attribIndex++)
+ {
+ uint32_t attribBit = 1 << attribIndex;
+ vaoAttrib_t *vAtb = &vao->attribs[attribIndex];
+
+ if (vAtb->enabled)
+ {
+ qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset));
+ if (glRefConfig.vertexArrayObject || !(glState.vertexAttribsEnabled & attribBit))
+ qglEnableVertexAttribArray(attribIndex);
+
+ if (!glRefConfig.vertexArrayObject || vao == tess.vao)
+ glState.vertexAttribsEnabled |= attribBit;
+ }
+ else
+ {
+ // don't disable vertex attribs when using vertex array objects
+ // Vao_SetVertexPointers is only called during init when using VAOs, and vertex attribs start disabled anyway
+ if (!glRefConfig.vertexArrayObject && (glState.vertexAttribsEnabled & attribBit))
+ qglDisableVertexAttribArray(attribIndex);
+
+ if (!glRefConfig.vertexArrayObject || vao == tess.vao)
+ glState.vertexAttribsEnabled &= ~attribBit;
+ }
+ }
+}
+
+/*
+============
+R_CreateVao
+============
+*/
+vao_t *R_CreateVao(const char *name, byte *vertexes, int vertexesSize, byte *indexes, int indexesSize, vaoUsage_t usage)
+{
+ vao_t *vao;
+ int glUsage;
+
+ switch (usage)
+ {
+ case VAO_USAGE_STATIC:
+ glUsage = GL_STATIC_DRAW;
+ break;
+
+ case VAO_USAGE_DYNAMIC:
+ glUsage = GL_DYNAMIC_DRAW;
+ break;
+
+ default:
+ Com_Error(ERR_FATAL, "bad vaoUsage_t given: %i", usage);
+ return NULL;
+ }
+
+ if(strlen(name) >= MAX_QPATH)
+ {
+ ri.Error(ERR_DROP, "R_CreateVao: \"%s\" is too long", name);
+ }
+
+ if ( tr.numVaos == MAX_VAOS ) {
+ ri.Error( ERR_DROP, "R_CreateVao: MAX_VAOS hit");
+ }
+
+ R_IssuePendingRenderCommands();
+
+ vao = tr.vaos[tr.numVaos] = (vao_t*)ri.Hunk_Alloc(sizeof(*vao), h_low);
+ tr.numVaos++;
+
+ memset(vao, 0, sizeof(*vao));
+
+ Q_strncpyz(vao->name, name, sizeof(vao->name));
+
+
+ if (glRefConfig.vertexArrayObject)
+ {
+ qglGenVertexArrays(1, &vao->vao);
+ qglBindVertexArray(vao->vao);
+ }
+
+
+ vao->vertexesSize = vertexesSize;
+
+ qglGenBuffers(1, &vao->vertexesVBO);
+
+ qglBindBuffer(GL_ARRAY_BUFFER, vao->vertexesVBO);
+ qglBufferData(GL_ARRAY_BUFFER, vertexesSize, vertexes, glUsage);
+
+
+ vao->indexesSize = indexesSize;
+
+ qglGenBuffers(1, &vao->indexesIBO);
+
+ qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao->indexesIBO);
+ qglBufferData(GL_ELEMENT_ARRAY_BUFFER, indexesSize, indexes, glUsage);
+
+
+ glState.currentVao = vao;
+
+ GL_CheckErrors();
+
+ return vao;
+}
+
+/*
+============
+R_CreateVao2
+============
+*/
+vao_t *R_CreateVao2(const char *name, int numVertexes, srfVert_t *verts, int numIndexes, glIndex_t *indexes)
+{
+ vao_t *vao;
+ int i;
+
+ byte *data;
+ int dataSize;
+ int dataOfs;
+
+ int glUsage = GL_STATIC_DRAW;
+
+ if(!numVertexes || !numIndexes)
+ return NULL;
+
+ if(strlen(name) >= MAX_QPATH)
+ {
+ ri.Error(ERR_DROP, "R_CreateVao2: \"%s\" is too long", name);
+ }
+
+ if ( tr.numVaos == MAX_VAOS ) {
+ ri.Error( ERR_DROP, "R_CreateVao2: MAX_VAOS hit");
+ }
+
+ R_IssuePendingRenderCommands();
+
+ vao = tr.vaos[tr.numVaos] = (vao_t*)ri.Hunk_Alloc(sizeof(*vao), h_low);
+ tr.numVaos++;
+
+ memset(vao, 0, sizeof(*vao));
+
+ Q_strncpyz(vao->name, name, sizeof(vao->name));
+
+ // since these vertex attributes are never altered, interleave them
+ vao->attribs[ATTR_INDEX_POSITION ].enabled = 1;
+ vao->attribs[ATTR_INDEX_NORMAL ].enabled = 1;
+ vao->attribs[ATTR_INDEX_TANGENT ].enabled = 1;
+ vao->attribs[ATTR_INDEX_TEXCOORD ].enabled = 1;
+ vao->attribs[ATTR_INDEX_LIGHTCOORD ].enabled = 1;
+ vao->attribs[ATTR_INDEX_COLOR ].enabled = 1;
+ vao->attribs[ATTR_INDEX_LIGHTDIRECTION].enabled = 1;
+
+ vao->attribs[ATTR_INDEX_POSITION ].count = 3;
+ vao->attribs[ATTR_INDEX_NORMAL ].count = 4;
+ vao->attribs[ATTR_INDEX_TANGENT ].count = 4;
+ vao->attribs[ATTR_INDEX_TEXCOORD ].count = 2;
+ vao->attribs[ATTR_INDEX_LIGHTCOORD ].count = 2;
+ vao->attribs[ATTR_INDEX_COLOR ].count = 4;
+ vao->attribs[ATTR_INDEX_LIGHTDIRECTION].count = 4;
+
+ vao->attribs[ATTR_INDEX_POSITION ].type = GL_FLOAT;
+ vao->attribs[ATTR_INDEX_NORMAL ].type = GL_SHORT;
+ vao->attribs[ATTR_INDEX_TANGENT ].type = GL_SHORT;
+ vao->attribs[ATTR_INDEX_TEXCOORD ].type = GL_FLOAT;
+ vao->attribs[ATTR_INDEX_LIGHTCOORD ].type = GL_FLOAT;
+ vao->attribs[ATTR_INDEX_COLOR ].type = GL_UNSIGNED_SHORT;
+ vao->attribs[ATTR_INDEX_LIGHTDIRECTION].type = GL_SHORT;
+
+ vao->attribs[ATTR_INDEX_POSITION ].normalized = GL_FALSE;
+ vao->attribs[ATTR_INDEX_NORMAL ].normalized = GL_TRUE;
+ vao->attribs[ATTR_INDEX_TANGENT ].normalized = GL_TRUE;
+ vao->attribs[ATTR_INDEX_TEXCOORD ].normalized = GL_FALSE;
+ vao->attribs[ATTR_INDEX_LIGHTCOORD ].normalized = GL_FALSE;
+ vao->attribs[ATTR_INDEX_COLOR ].normalized = GL_TRUE;
+ vao->attribs[ATTR_INDEX_LIGHTDIRECTION].normalized = GL_TRUE;
+
+ vao->attribs[ATTR_INDEX_POSITION ].offset = 0; dataSize = sizeof(verts[0].xyz);
+ vao->attribs[ATTR_INDEX_NORMAL ].offset = dataSize; dataSize += sizeof(verts[0].normal);
+ vao->attribs[ATTR_INDEX_TANGENT ].offset = dataSize; dataSize += sizeof(verts[0].tangent);
+ vao->attribs[ATTR_INDEX_TEXCOORD ].offset = dataSize; dataSize += sizeof(verts[0].st);
+ vao->attribs[ATTR_INDEX_LIGHTCOORD ].offset = dataSize; dataSize += sizeof(verts[0].lightmap);
+ vao->attribs[ATTR_INDEX_COLOR ].offset = dataSize; dataSize += sizeof(verts[0].color);
+ vao->attribs[ATTR_INDEX_LIGHTDIRECTION].offset = dataSize; dataSize += sizeof(verts[0].lightdir);
+
+ vao->attribs[ATTR_INDEX_POSITION ].stride = dataSize;
+ vao->attribs[ATTR_INDEX_NORMAL ].stride = dataSize;
+ vao->attribs[ATTR_INDEX_TANGENT ].stride = dataSize;
+ vao->attribs[ATTR_INDEX_TEXCOORD ].stride = dataSize;
+ vao->attribs[ATTR_INDEX_LIGHTCOORD ].stride = dataSize;
+ vao->attribs[ATTR_INDEX_COLOR ].stride = dataSize;
+ vao->attribs[ATTR_INDEX_LIGHTDIRECTION].stride = dataSize;
+
+
+ if (glRefConfig.vertexArrayObject)
+ {
+ qglGenVertexArrays(1, &vao->vao);
+ qglBindVertexArray(vao->vao);
+ }
+
+
+ // create VBO
+ dataSize *= numVertexes;
+ data = (byte*)ri.Hunk_AllocateTempMemory(dataSize);
+ dataOfs = 0;
+
+ for (i = 0; i < numVertexes; i++)
+ {
+ // xyz
+ memcpy(data + dataOfs, &verts[i].xyz, sizeof(verts[i].xyz));
+ dataOfs += sizeof(verts[i].xyz);
+
+ // normal
+ memcpy(data + dataOfs, &verts[i].normal, sizeof(verts[i].normal));
+ dataOfs += sizeof(verts[i].normal);
+
+ // tangent
+ memcpy(data + dataOfs, &verts[i].tangent, sizeof(verts[i].tangent));
+ dataOfs += sizeof(verts[i].tangent);
+
+ // texcoords
+ memcpy(data + dataOfs, &verts[i].st, sizeof(verts[i].st));
+ dataOfs += sizeof(verts[i].st);
+
+ // lightmap texcoords
+ memcpy(data + dataOfs, &verts[i].lightmap, sizeof(verts[i].lightmap));
+ dataOfs += sizeof(verts[i].lightmap);
+
+ // colors
+ memcpy(data + dataOfs, &verts[i].color, sizeof(verts[i].color));
+ dataOfs += sizeof(verts[i].color);
+
+ // light directions
+ memcpy(data + dataOfs, &verts[i].lightdir, sizeof(verts[i].lightdir));
+ dataOfs += sizeof(verts[i].lightdir);
+ }
+
+ vao->vertexesSize = dataSize;
+
+ qglGenBuffers(1, &vao->vertexesVBO);
+
+ qglBindBuffer(GL_ARRAY_BUFFER, vao->vertexesVBO);
+ qglBufferData(GL_ARRAY_BUFFER, vao->vertexesSize, data, glUsage);
+
+
+ // create IBO
+ vao->indexesSize = numIndexes * sizeof(glIndex_t);
+
+ qglGenBuffers(1, &vao->indexesIBO);
+
+ qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao->indexesIBO);
+ qglBufferData(GL_ELEMENT_ARRAY_BUFFER, vao->indexesSize, indexes, glUsage);
+
+
+ Vao_SetVertexPointers(vao);
+
+
+ glState.currentVao = vao;
+
+ GL_CheckErrors();
+
+ ri.Hunk_FreeTempMemory(data);
+
+ return vao;
+}
+
+
+/*
+============
+R_BindVao
+============
+*/
+void R_BindVao(vao_t * vao)
+{
+ if(!vao)
+ {
+ //R_BindNullVao();
+ ri.Error(ERR_DROP, "R_BindVao: NULL vao");
+ return;
+ }
+
+ if(r_logFile->integer)
+ {
+ // don't just call LogComment, or we will get a call to va() every frame!
+ GLimp_LogComment((char*)va("--- R_BindVao( %s ) ---\n", vao->name));
+ }
+
+ if(glState.currentVao != vao)
+ {
+ glState.currentVao = vao;
+
+ glState.vertexAttribsInterpolation = 0;
+ glState.vertexAnimation = false;
+ backEnd.pc.c_vaoBinds++;
+
+ if (glRefConfig.vertexArrayObject)
+ {
+ qglBindVertexArray(vao->vao);
+
+ // Intel Graphics doesn't save GL_ELEMENT_ARRAY_BUFFER binding with VAO binding.
+ if (glRefConfig.intelGraphics || vao == tess.vao)
+ qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao->indexesIBO);
+
+ // tess VAO always has buffers bound
+ if (vao == tess.vao)
+ qglBindBuffer(GL_ARRAY_BUFFER, vao->vertexesVBO);
+ }
+ else
+ {
+ qglBindBuffer(GL_ARRAY_BUFFER, vao->vertexesVBO);
+ qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao->indexesIBO);
+
+ // tess VAO doesn't have vertex pointers set until data is uploaded
+ if (vao != tess.vao)
+ Vao_SetVertexPointers(vao);
+ }
+ }
+}
+
+/*
+============
+R_BindNullVao
+============
+*/
+void R_BindNullVao(void)
+{
+ GLimp_LogComment("--- R_BindNullVao ---\n");
+
+ if(glState.currentVao)
+ {
+ if (glRefConfig.vertexArrayObject)
+ {
+ qglBindVertexArray(0);
+
+ // why you no save GL_ELEMENT_ARRAY_BUFFER binding, Intel?
+ if (1) qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ }
+ else
+ {
+ qglBindBuffer(GL_ARRAY_BUFFER, 0);
+ qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ }
+ glState.currentVao = NULL;
+ }
+
+ GL_CheckErrors();
+}
+
+
+/*
+============
+R_InitVaos
+============
+*/
+void R_InitVaos(void)
+{
+ int vertexesSize, indexesSize;
+ int offset;
+
+ ri.Printf(PRINT_ALL, "------- R_InitVaos -------\n");
+
+ tr.numVaos = 0;
+
+ vertexesSize = sizeof(tess.xyz[0]);
+ vertexesSize += sizeof(tess.normal[0]);
+ vertexesSize += sizeof(tess.tangent[0]);
+ vertexesSize += sizeof(tess.color[0]);
+ vertexesSize += sizeof(tess.texCoords[0]);
+ vertexesSize += sizeof(tess.lightCoords[0]);
+ vertexesSize += sizeof(tess.lightdir[0]);
+ vertexesSize *= SHADER_MAX_VERTEXES;
+
+ indexesSize = sizeof(tess.indexes[0]) * SHADER_MAX_INDEXES;
+
+ tess.vao = R_CreateVao("tessVertexArray_VAO", NULL, vertexesSize, NULL, indexesSize, VAO_USAGE_DYNAMIC);
+
+ offset = 0;
+
+ tess.vao->attribs[ATTR_INDEX_POSITION ].enabled = 1;
+ tess.vao->attribs[ATTR_INDEX_NORMAL ].enabled = 1;
+ tess.vao->attribs[ATTR_INDEX_TANGENT ].enabled = 1;
+ tess.vao->attribs[ATTR_INDEX_TEXCOORD ].enabled = 1;
+ tess.vao->attribs[ATTR_INDEX_LIGHTCOORD ].enabled = 1;
+ tess.vao->attribs[ATTR_INDEX_COLOR ].enabled = 1;
+ tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].enabled = 1;
+
+ tess.vao->attribs[ATTR_INDEX_POSITION ].count = 3;
+ tess.vao->attribs[ATTR_INDEX_NORMAL ].count = 4;
+ tess.vao->attribs[ATTR_INDEX_TANGENT ].count = 4;
+ tess.vao->attribs[ATTR_INDEX_TEXCOORD ].count = 2;
+ tess.vao->attribs[ATTR_INDEX_LIGHTCOORD ].count = 2;
+ tess.vao->attribs[ATTR_INDEX_COLOR ].count = 4;
+ tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].count = 4;
+
+ tess.vao->attribs[ATTR_INDEX_POSITION ].type = GL_FLOAT;
+ tess.vao->attribs[ATTR_INDEX_NORMAL ].type = GL_SHORT;
+ tess.vao->attribs[ATTR_INDEX_TANGENT ].type = GL_SHORT;
+ tess.vao->attribs[ATTR_INDEX_TEXCOORD ].type = GL_FLOAT;
+ tess.vao->attribs[ATTR_INDEX_LIGHTCOORD ].type = GL_FLOAT;
+ tess.vao->attribs[ATTR_INDEX_COLOR ].type = GL_UNSIGNED_SHORT;
+ tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].type = GL_SHORT;
+
+ tess.vao->attribs[ATTR_INDEX_POSITION ].normalized = GL_FALSE;
+ tess.vao->attribs[ATTR_INDEX_NORMAL ].normalized = GL_TRUE;
+ tess.vao->attribs[ATTR_INDEX_TANGENT ].normalized = GL_TRUE;
+ tess.vao->attribs[ATTR_INDEX_TEXCOORD ].normalized = GL_FALSE;
+ tess.vao->attribs[ATTR_INDEX_LIGHTCOORD ].normalized = GL_FALSE;
+ tess.vao->attribs[ATTR_INDEX_COLOR ].normalized = GL_TRUE;
+ tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].normalized = GL_TRUE;
+
+ tess.vao->attribs[ATTR_INDEX_POSITION ].offset = offset; offset += sizeof(tess.xyz[0]) * SHADER_MAX_VERTEXES;
+ tess.vao->attribs[ATTR_INDEX_NORMAL ].offset = offset; offset += sizeof(tess.normal[0]) * SHADER_MAX_VERTEXES;
+ tess.vao->attribs[ATTR_INDEX_TANGENT ].offset = offset; offset += sizeof(tess.tangent[0]) * SHADER_MAX_VERTEXES;
+ tess.vao->attribs[ATTR_INDEX_TEXCOORD ].offset = offset; offset += sizeof(tess.texCoords[0]) * SHADER_MAX_VERTEXES;
+ tess.vao->attribs[ATTR_INDEX_LIGHTCOORD ].offset = offset; offset += sizeof(tess.lightCoords[0]) * SHADER_MAX_VERTEXES;
+ tess.vao->attribs[ATTR_INDEX_COLOR ].offset = offset; offset += sizeof(tess.color[0]) * SHADER_MAX_VERTEXES;
+ tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].offset = offset;
+
+ tess.vao->attribs[ATTR_INDEX_POSITION ].stride = sizeof(tess.xyz[0]);
+ tess.vao->attribs[ATTR_INDEX_NORMAL ].stride = sizeof(tess.normal[0]);
+ tess.vao->attribs[ATTR_INDEX_TANGENT ].stride = sizeof(tess.tangent[0]);
+ tess.vao->attribs[ATTR_INDEX_TEXCOORD ].stride = sizeof(tess.texCoords[0]);
+ tess.vao->attribs[ATTR_INDEX_LIGHTCOORD ].stride = sizeof(tess.lightCoords[0]);
+ tess.vao->attribs[ATTR_INDEX_COLOR ].stride = sizeof(tess.color[0]);
+ tess.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].stride = sizeof(tess.lightdir[0]);
+
+ tess.attribPointers[ATTR_INDEX_POSITION] = tess.xyz;
+ tess.attribPointers[ATTR_INDEX_NORMAL] = tess.normal;
+ tess.attribPointers[ATTR_INDEX_TANGENT] = tess.tangent;
+ tess.attribPointers[ATTR_INDEX_TEXCOORD] = tess.texCoords;
+ tess.attribPointers[ATTR_INDEX_LIGHTCOORD] = tess.lightCoords;
+ tess.attribPointers[ATTR_INDEX_COLOR] = tess.color;
+ tess.attribPointers[ATTR_INDEX_LIGHTDIRECTION] = tess.lightdir;
+
+ Vao_SetVertexPointers(tess.vao);
+
+ R_BindNullVao();
+
+ VaoCache_Init();
+
+ GL_CheckErrors();
+}
+
+/*
+============
+R_ShutdownVaos
+============
+*/
+void R_ShutdownVaos(void)
+{
+ int i;
+ vao_t *vao;
+
+ ri.Printf(PRINT_ALL, "------- R_ShutdownVaos -------\n");
+
+ R_BindNullVao();
+
+ for(i = 0; i < tr.numVaos; i++)
+ {
+ vao = tr.vaos[i];
+
+ if(vao->vao)
+ qglDeleteVertexArrays(1, &vao->vao);
+
+ if(vao->vertexesVBO)
+ {
+ qglDeleteBuffers(1, &vao->vertexesVBO);
+ }
+
+ if(vao->indexesIBO)
+ {
+ qglDeleteBuffers(1, &vao->indexesIBO);
+ }
+ }
+
+ tr.numVaos = 0;
+}
+
+/*
+============
+R_VaoList_f
+============
+*/
+void R_VaoList_f(void)
+{
+ int i;
+ vao_t *vao;
+ int vertexesSize = 0;
+ int indexesSize = 0;
+
+ ri.Printf(PRINT_ALL, " size name\n");
+ ri.Printf(PRINT_ALL, "----------------------------------------------------------\n");
+
+ for(i = 0; i < tr.numVaos; i++)
+ {
+ vao = tr.vaos[i];
+
+ ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", vao->vertexesSize / (1024 * 1024),
+ (vao->vertexesSize % (1024 * 1024)) * 100 / (1024 * 1024), vao->name);
+
+ vertexesSize += vao->vertexesSize;
+ }
+
+ for(i = 0; i < tr.numVaos; i++)
+ {
+ vao = tr.vaos[i];
+
+ ri.Printf(PRINT_ALL, "%d.%02d MB %s\n", vao->indexesSize / (1024 * 1024),
+ (vao->indexesSize % (1024 * 1024)) * 100 / (1024 * 1024), vao->name);
+
+ indexesSize += vao->indexesSize;
+ }
+
+ ri.Printf(PRINT_ALL, " %i total VAOs\n", tr.numVaos);
+ ri.Printf(PRINT_ALL, " %d.%02d MB total vertices memory\n", vertexesSize / (1024 * 1024),
+ (vertexesSize % (1024 * 1024)) * 100 / (1024 * 1024));
+ ri.Printf(PRINT_ALL, " %d.%02d MB total triangle indices memory\n", indexesSize / (1024 * 1024),
+ (indexesSize % (1024 * 1024)) * 100 / (1024 * 1024));
+}
+
+
+/*
+==============
+RB_UpdateTessVao
+
+Adapted from Tess_UpdateVBOs from xreal
+
+Update the default VAO to replace the client side vertex arrays
+==============
+*/
+void RB_UpdateTessVao(unsigned int attribBits)
+{
+ GLimp_LogComment("--- RB_UpdateTessVao ---\n");
+
+ backEnd.pc.c_dynamicVaoDraws++;
+
+ // update the default VAO
+ if(tess.numVertexes > 0 && tess.numVertexes <= SHADER_MAX_VERTEXES && tess.numIndexes > 0 && tess.numIndexes <= SHADER_MAX_INDEXES)
+ {
+ int attribIndex;
+ int attribUpload;
+
+ R_BindVao(tess.vao);
+
+ // orphan old vertex buffer so we don't stall on it
+ qglBufferData(GL_ARRAY_BUFFER, tess.vao->vertexesSize, NULL, GL_DYNAMIC_DRAW);
+
+ // if nothing to set, set everything
+ if(!(attribBits & ATTR_BITS))
+ attribBits = ATTR_BITS;
+
+ attribUpload = attribBits;
+
+ for (attribIndex = 0; attribIndex < ATTR_INDEX_COUNT; attribIndex++)
+ {
+ uint32_t attribBit = 1 << attribIndex;
+ vaoAttrib_t *vAtb = &tess.vao->attribs[attribIndex];
+
+ if (attribUpload & attribBit)
+ {
+ // note: tess has a VBO where stride == size
+ qglBufferSubData(GL_ARRAY_BUFFER, vAtb->offset, tess.numVertexes * vAtb->stride, tess.attribPointers[attribIndex]);
+ }
+
+ if (attribBits & attribBit)
+ {
+ if (!glRefConfig.vertexArrayObject)
+ qglVertexAttribPointer(attribIndex, vAtb->count, vAtb->type, vAtb->normalized, vAtb->stride, BUFFER_OFFSET(vAtb->offset));
+
+ if (!(glState.vertexAttribsEnabled & attribBit))
+ {
+ qglEnableVertexAttribArray(attribIndex);
+ glState.vertexAttribsEnabled |= attribBit;
+ }
+ }
+ else
+ {
+ if ((glState.vertexAttribsEnabled & attribBit))
+ {
+ qglDisableVertexAttribArray(attribIndex);
+ glState.vertexAttribsEnabled &= ~attribBit;
+ }
+ }
+ }
+
+ // orphan old index buffer so we don't stall on it
+ qglBufferData(GL_ELEMENT_ARRAY_BUFFER, tess.vao->indexesSize, NULL, GL_DYNAMIC_DRAW);
+
+ qglBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, tess.numIndexes * sizeof(tess.indexes[0]), tess.indexes);
+ }
+}
+
+typedef struct bufferCacheEntry_s
+{
+ void *data;
+ int size;
+ int bufferOffset;
+}
+bufferCacheEntry_t;
+
+typedef struct queuedSurface_s
+{
+ srfVert_t *vertexes;
+ int numVerts;
+ glIndex_t *indexes;
+ int numIndexes;
+}
+queuedSurface_t;
+
+#define VAOCACHE_MAX_BUFFERED_SURFACES (1 << 16)
+#define VAOCACHE_VERTEX_BUFFER_SIZE 12 * 1024 * 1024
+#define VAOCACHE_INDEX_BUFFER_SIZE 12 * 1024 * 1024
+
+#define VAOCACHE_MAX_QUEUED_VERTEXES (1 << 16)
+#define VAOCACHE_MAX_QUEUED_INDEXES (VAOCACHE_MAX_QUEUED_VERTEXES * 6 / 4)
+
+#define VAOCACHE_MAX_QUEUED_SURFACES 4096
+
+static struct
+{
+ vao_t *vao;
+ bufferCacheEntry_t indexEntries[VAOCACHE_MAX_BUFFERED_SURFACES];
+ int indexChainLengths[VAOCACHE_MAX_BUFFERED_SURFACES];
+ int numIndexEntries;
+ int vertexOffset;
+ int indexOffset;
+
+ srfVert_t vertexes[VAOCACHE_MAX_QUEUED_VERTEXES];
+ int vertexCommitSize;
+
+ glIndex_t indexes[VAOCACHE_MAX_QUEUED_INDEXES];
+ int indexCommitSize;
+
+ queuedSurface_t surfaceQueue[VAOCACHE_MAX_QUEUED_SURFACES];
+ int numSurfacesQueued;
+}
+vc = { 0 };
+
+void VaoCache_Commit(void)
+{
+ bufferCacheEntry_t *entry1;
+ queuedSurface_t *surf, *end = vc.surfaceQueue + vc.numSurfacesQueued;
+
+ R_BindVao(vc.vao);
+
+ // search entire cache for exact chain of indexes
+ // FIXME: use faster search
+ for (entry1 = vc.indexEntries; entry1 < vc.indexEntries + vc.numIndexEntries;)
+ {
+ int chainLength = vc.indexChainLengths[entry1 - vc.indexEntries];
+
+ if (chainLength == vc.numSurfacesQueued)
+ {
+ bufferCacheEntry_t *entry2 = entry1;
+ for (surf = vc.surfaceQueue; surf < end; surf++, entry2++)
+ {
+ if (surf->indexes != entry2->data || (surf->numIndexes * sizeof(glIndex_t)) != entry2->size)
+ break;
+ }
+
+ if (surf == end)
+ break;
+ }
+
+ entry1 += chainLength;
+ }
+
+ // if found, use that
+ if (entry1 < vc.indexEntries + vc.numIndexEntries)
+ {
+ tess.firstIndex = entry1->bufferOffset / sizeof(glIndex_t);
+ //ri.Printf(PRINT_ALL, "firstIndex %d numIndexes %d\n", tess.firstIndex, tess.numIndexes);
+ //ri.Printf(PRINT_ALL, "vc.numIndexEntries %d\n", vc.numIndexEntries);
+ }
+ // if not, rebuffer all the indexes but reuse any existing vertexes
+ else
+ {
+ srfVert_t *dstVertex = vc.vertexes;
+ glIndex_t *dstIndex = vc.indexes;
+ int *indexChainLength = vc.indexChainLengths + vc.numIndexEntries;
+
+ tess.firstIndex = vc.indexOffset / sizeof(glIndex_t);
+ vc.vertexCommitSize = 0;
+ vc.indexCommitSize = 0;
+ for (surf = vc.surfaceQueue; surf < end; surf++)
+ {
+ srfVert_t *srcVertex = surf->vertexes;
+ glIndex_t *srcIndex = surf->indexes;
+ int vertexesSize = surf->numVerts * sizeof(srfVert_t);
+ int indexesSize = surf->numIndexes * sizeof(glIndex_t);
+ int i, indexOffset = (vc.vertexOffset + vc.vertexCommitSize) / sizeof(srfVert_t);
+
+ for (i = 0; i < surf->numVerts; i++)
+ *dstVertex++ = *srcVertex++;
+
+ vc.vertexCommitSize += vertexesSize;
+
+ entry1 = vc.indexEntries + vc.numIndexEntries;
+ entry1->data = surf->indexes;
+ entry1->size = indexesSize;
+ entry1->bufferOffset = vc.indexOffset + vc.indexCommitSize;
+ vc.numIndexEntries++;
+
+ *indexChainLength++ = ((surf == vc.surfaceQueue) ? vc.numSurfacesQueued : 0);
+
+ for (i = 0; i < surf->numIndexes; i++)
+ *dstIndex++ = *srcIndex++ + indexOffset;
+
+ vc.indexCommitSize += indexesSize;
+ }
+
+ //ri.Printf(PRINT_ALL, "committing %d to %d, %d to %d\n", vc.vertexCommitSize, vc.vertexOffset, vc.indexCommitSize, vc.indexOffset);
+
+ if (vc.vertexCommitSize)
+ {
+ qglBindBuffer(GL_ARRAY_BUFFER, vc.vao->vertexesVBO);
+ qglBufferSubData(GL_ARRAY_BUFFER, vc.vertexOffset, vc.vertexCommitSize, vc.vertexes);
+ vc.vertexOffset += vc.vertexCommitSize;
+ }
+
+ if (vc.indexCommitSize)
+ {
+ qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vc.vao->indexesIBO);
+ qglBufferSubData(GL_ELEMENT_ARRAY_BUFFER, vc.indexOffset, vc.indexCommitSize, vc.indexes);
+ vc.indexOffset += vc.indexCommitSize;
+ }
+ }
+}
+
+void VaoCache_Init(void)
+{
+ srfVert_t vert;
+ int dataSize;
+
+ vc.vao = R_CreateVao("VaoCache", NULL, VAOCACHE_VERTEX_BUFFER_SIZE, NULL, VAOCACHE_INDEX_BUFFER_SIZE, VAO_USAGE_DYNAMIC);
+
+ vc.vao->attribs[ATTR_INDEX_POSITION].enabled = 1;
+ vc.vao->attribs[ATTR_INDEX_TEXCOORD].enabled = 1;
+ vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].enabled = 1;
+ vc.vao->attribs[ATTR_INDEX_NORMAL].enabled = 1;
+ vc.vao->attribs[ATTR_INDEX_TANGENT].enabled = 1;
+ vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].enabled = 1;
+ vc.vao->attribs[ATTR_INDEX_COLOR].enabled = 1;
+
+ vc.vao->attribs[ATTR_INDEX_POSITION].count = 3;
+ vc.vao->attribs[ATTR_INDEX_TEXCOORD].count = 2;
+ vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].count = 2;
+ vc.vao->attribs[ATTR_INDEX_NORMAL].count = 4;
+ vc.vao->attribs[ATTR_INDEX_TANGENT].count = 4;
+ vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].count = 4;
+ vc.vao->attribs[ATTR_INDEX_COLOR].count = 4;
+
+ vc.vao->attribs[ATTR_INDEX_POSITION].type = GL_FLOAT;
+ vc.vao->attribs[ATTR_INDEX_TEXCOORD].type = GL_FLOAT;
+ vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].type = GL_FLOAT;
+ vc.vao->attribs[ATTR_INDEX_NORMAL].type = GL_SHORT;
+ vc.vao->attribs[ATTR_INDEX_TANGENT].type = GL_SHORT;
+ vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].type = GL_SHORT;
+ vc.vao->attribs[ATTR_INDEX_COLOR].type = GL_UNSIGNED_SHORT;
+
+ vc.vao->attribs[ATTR_INDEX_POSITION].normalized = GL_FALSE;
+ vc.vao->attribs[ATTR_INDEX_TEXCOORD].normalized = GL_FALSE;
+ vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].normalized = GL_FALSE;
+ vc.vao->attribs[ATTR_INDEX_NORMAL].normalized = GL_TRUE;
+ vc.vao->attribs[ATTR_INDEX_TANGENT].normalized = GL_TRUE;
+ vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].normalized = GL_TRUE;
+ vc.vao->attribs[ATTR_INDEX_COLOR].normalized = GL_TRUE;
+
+ vc.vao->attribs[ATTR_INDEX_POSITION].offset = 0; dataSize = sizeof(vert.xyz);
+ vc.vao->attribs[ATTR_INDEX_TEXCOORD].offset = dataSize; dataSize += sizeof(vert.st);
+ vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].offset = dataSize; dataSize += sizeof(vert.lightmap);
+ vc.vao->attribs[ATTR_INDEX_NORMAL].offset = dataSize; dataSize += sizeof(vert.normal);
+ vc.vao->attribs[ATTR_INDEX_TANGENT].offset = dataSize; dataSize += sizeof(vert.tangent);
+ vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].offset = dataSize; dataSize += sizeof(vert.lightdir);
+ vc.vao->attribs[ATTR_INDEX_COLOR].offset = dataSize; dataSize += sizeof(vert.color);
+
+ vc.vao->attribs[ATTR_INDEX_POSITION].stride = dataSize;
+ vc.vao->attribs[ATTR_INDEX_TEXCOORD].stride = dataSize;
+ vc.vao->attribs[ATTR_INDEX_LIGHTCOORD].stride = dataSize;
+ vc.vao->attribs[ATTR_INDEX_NORMAL].stride = dataSize;
+ vc.vao->attribs[ATTR_INDEX_TANGENT].stride = dataSize;
+ vc.vao->attribs[ATTR_INDEX_LIGHTDIRECTION].stride = dataSize;
+ vc.vao->attribs[ATTR_INDEX_COLOR].stride = dataSize;
+
+ Vao_SetVertexPointers(vc.vao);
+
+ vc.numIndexEntries = 0;
+ vc.vertexOffset = 0;
+ vc.indexOffset = 0;
+ vc.vertexCommitSize = 0;
+ vc.indexCommitSize = 0;
+ vc.numSurfacesQueued = 0;
+}
+
+void VaoCache_BindVao(void)
+{
+ R_BindVao(vc.vao);
+}
+
+void VaoCache_CheckAdd(bool *endSurface, bool *recycleVertexBuffer, bool *recycleIndexBuffer, int numVerts, int numIndexes)
+{
+ int vertexesSize = sizeof(srfVert_t) * numVerts;
+ int indexesSize = sizeof(glIndex_t) * numIndexes;
+
+ if (vc.vao->vertexesSize < vc.vertexOffset + vc.vertexCommitSize + vertexesSize)
+ {
+ //ri.Printf(PRINT_ALL, "out of space in vertex cache: %d < %d + %d + %d\n", vc.vao->vertexesSize, vc.vertexOffset, vc.vertexCommitSize, vertexesSize);
+ *recycleVertexBuffer = true;
+ *recycleIndexBuffer = true;
+ *endSurface = true;
+ }
+
+ if (vc.vao->indexesSize < vc.indexOffset + vc.indexCommitSize + indexesSize)
+ {
+ //ri.Printf(PRINT_ALL, "out of space in index cache\n");
+ *recycleIndexBuffer = true;
+ *endSurface = true;
+ }
+
+ if (vc.numIndexEntries + vc.numSurfacesQueued >= VAOCACHE_MAX_BUFFERED_SURFACES)
+ {
+ //ri.Printf(PRINT_ALL, "out of surfaces in index cache\n");
+ *recycleIndexBuffer = true;
+ *endSurface = true;
+ }
+
+ if (vc.numSurfacesQueued == VAOCACHE_MAX_QUEUED_SURFACES)
+ {
+ //ri.Printf(PRINT_ALL, "out of queued surfaces\n");
+ *endSurface = true;
+ }
+
+ if (VAOCACHE_MAX_QUEUED_VERTEXES * sizeof(srfVert_t) < vc.vertexCommitSize + vertexesSize)
+ {
+ //ri.Printf(PRINT_ALL, "out of queued vertexes\n");
+ *endSurface = true;
+ }
+
+ if (VAOCACHE_MAX_QUEUED_INDEXES * sizeof(glIndex_t) < vc.indexCommitSize + indexesSize)
+ {
+ //ri.Printf(PRINT_ALL, "out of queued indexes\n");
+ *endSurface = true;
+ }
+}
+
+void VaoCache_RecycleVertexBuffer(void)
+{
+ qglBindBuffer(GL_ARRAY_BUFFER, vc.vao->vertexesVBO);
+ qglBufferData(GL_ARRAY_BUFFER, vc.vao->vertexesSize, NULL, GL_DYNAMIC_DRAW);
+ vc.vertexOffset = 0;
+}
+
+void VaoCache_RecycleIndexBuffer(void)
+{
+ qglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vc.vao->indexesIBO);
+ qglBufferData(GL_ELEMENT_ARRAY_BUFFER, vc.vao->indexesSize, NULL, GL_DYNAMIC_DRAW);
+ vc.indexOffset = 0;
+ vc.numIndexEntries = 0;
+}
+
+void VaoCache_InitNewSurfaceSet(void)
+{
+ vc.vertexCommitSize = 0;
+ vc.indexCommitSize = 0;
+ vc.numSurfacesQueued = 0;
+}
+
+void VaoCache_AddSurface(srfVert_t *verts, int numVerts, glIndex_t *indexes, int numIndexes)
+{
+ int vertexesSize = sizeof(srfVert_t) * numVerts;
+ int indexesSize = sizeof(glIndex_t) * numIndexes;
+ queuedSurface_t *queueEntry = vc.surfaceQueue + vc.numSurfacesQueued;
+ queueEntry->vertexes = verts;
+ queueEntry->numVerts = numVerts;
+ queueEntry->indexes = indexes;
+ queueEntry->numIndexes = numIndexes;
+ vc.numSurfacesQueued++;
+
+ vc.vertexCommitSize += vertexesSize;
+ vc.indexCommitSize += indexesSize;
+}
diff --git a/src/renderergl2/tr_world.cpp b/src/renderergl2/tr_world.cpp
new file mode 100644
index 0000000..0361a88
--- /dev/null
+++ b/src/renderergl2/tr_world.cpp
@@ -0,0 +1,811 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2013 Darklegion Development
+Copyright (C) 2015-2019 GrangerHub
+
+This file is part of Tremulous.
+
+Tremulous 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 3 of the License,
+or (at your option) any later version.
+
+Tremulous 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 Tremulous; if not, see <https://www.gnu.org/licenses/>
+
+===========================================================================
+*/
+#include "tr_local.h"
+
+
+
+/*
+================
+R_CullSurface
+
+Tries to cull surfaces before they are lighted or
+added to the sorting list.
+================
+*/
+static bool R_CullSurface( msurface_t *surf ) {
+ if ( r_nocull->integer || surf->cullinfo.type == CULLINFO_NONE) {
+ return false;
+ }
+
+ if ( *surf->data == SF_GRID && r_nocurves->integer ) {
+ return true;
+ }
+
+ 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 false;
+ }
+
+ ct = surf->shader->cullType;
+
+ if (ct == CT_TWO_SIDED)
+ {
+ return false;
+ }
+
+ // don't cull for depth shadow
+ /*
+ if ( tr.viewParms.flags & VPF_DEPTHSHADOW )
+ {
+ return false;
+ }
+ */
+
+ // 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.orientation.axis[0], surf->cullinfo.plane.normal);
+ if ( ct == CT_FRONT_SIDED ) {
+ if (d > 0)
+ return true;
+ } else {
+ if (d < 0)
+ return true;
+ }
+ return false;
+ }
+
+ d = DotProduct (tr.orientation.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 true;
+ }
+ } else {
+ if ( d > surf->cullinfo.plane.dist + 8 ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ 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 true;
+ }
+ }
+
+ 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 true;
+ }
+ }
+
+ return false;
+}
+
+
+/*
+====================
+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 )
+ {
+ 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 );
+ }
+ }
+ }
+
+ switch(*surf->data)
+ {
+ case SF_FACE:
+ case SF_GRID:
+ case SF_TRIANGLES:
+ ((srfBspSurface_t *)surf->data)->dlightBits = dlightBits;
+ break;
+
+ default:
+ dlightBits = 0;
+ break;
+ }
+
+ if ( dlightBits ) {
+ tr.pc.c_dlightSurfaces++;
+ } else {
+ tr.pc.c_dlightSurfacesCulled++;
+ }
+
+ 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 )
+ {
+ 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 );
+ }
+ }
+ }
+
+ switch(*surf->data)
+ {
+ case SF_FACE:
+ case SF_GRID:
+ case SF_TRIANGLES:
+ ((srfBspSurface_t *)surf->data)->pshadowBits = pshadowBits;
+ break;
+
+ default:
+ pshadowBits = 0;
+ break;
+ }
+
+ 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, surf->cubemapIndex );
+}
+
+/*
+=============================================================
+
+ 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, uint32_t planeBits, uint32_t dlightBits, uint32_t pshadowBits ) {
+
+ do {
+ uint32_t newDlights[2];
+ uint32_t 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 surfaces
+ 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 (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->vis || cluster < 0 || cluster >= tr.world->numClusters ) {
+ return NULL;
+ }
+
+ return tr.world->vis + cluster * tr.world->clusterBytes;
+}
+
+/*
+=================
+R_inPVS
+=================
+*/
+bool 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 false;
+ }
+ return true;
+}
+
+/*
+===============
+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 the areamask or r_showcluster was modified, invalidate all visclusters
+ // this caused doors to open into undrawn areas
+ if (tr.refdef.areamaskModified || r_showcluster->modified)
+ {
+ tr.visClusters[i] = -2;
+ }
+ else if(tr.visClusters[i] == cluster)
+ {
+ 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;
+ }
+ }
+
+ 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 = false;
+ if ( r_showcluster->integer ) {
+ ri.Printf( PRINT_ALL, "cluster:%i area:%i\n", cluster, leaf->area );
+ }
+ }
+
+ vis = R_ClusterPVS(tr.visClusters[tr.visIndex]);
+
+ for (i=0,leaf=tr.world->nodes ; i<tr.world->numnodes ; i++, leaf++) {
+ cluster = leaf->cluster;
+ if ( cluster < 0 || cluster >= tr.world->numClusters ) {
+ continue;
+ }
+
+ // check general pvs
+ if ( vis && !(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) {
+ uint32_t planeBits, dlightBits, pshadowBits;
+
+ 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 > MAX_DLIGHTS ) {
+ tr.refdef.num_dlights = MAX_DLIGHTS ;
+ }
+
+ if ( tr.refdef.num_pshadows > MAX_DRAWN_PSHADOWS ) {
+ tr.refdef.num_pshadows = MAX_DRAWN_PSHADOWS;
+ }
+
+ planeBits = (tr.viewParms.flags & VPF_FARPLANEFRUSTUM) ? 31 : 15;
+
+ if ( tr.viewParms.flags & VPF_DEPTHSHADOW )
+ {
+ dlightBits = 0;
+ pshadowBits = 0;
+ }
+ else if ( !(tr.viewParms.flags & VPF_SHADOWMAP) )
+ {
+ dlightBits = ( 1ULL << tr.refdef.num_dlights ) - 1;
+ pshadowBits = ( 1ULL << tr.refdef.num_pshadows ) - 1;
+ }
+ else
+ {
+ dlightBits = ( 1ULL << tr.refdef.num_dlights ) - 1;
+ pshadowBits = 0;
+ }
+
+ R_RecursiveWorldNode( tr.world->nodes, planeBits, dlightBits, pshadowBits);
+
+ // 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];
+ }
+
+ tr.refdef.dlightMask = ~tr.refdef.dlightMask;
+ }
+}