if (pic->flags & CACHEPICFLAG_NEWPIC && pic->skinframe && pic->skinframe->base && pic->width == width && pic->height == height)
{
Con_DPrintf("Draw_NewPic(\"%s\"): frame %i: updating texture\n", picname, draw_frame);
- R_UpdateTexture(pic->skinframe->base, pixels_bgra, 0, 0, 0, width, height, 1);
+ R_UpdateTexture(pic->skinframe->base, pixels_bgra, 0, 0, 0, width, height, 1, 0);
R_SkinFrame_MarkUsed(pic->skinframe);
pic->lastusedframe = draw_frame;
return pic;
{CF_CLIENT | CF_ARCHIVE, "r_buffermegs_uniform", "0.25", "uniform buffer size for one frame"},
};
+cvar_t r_q1bsp_lightmap_updates_enabled = {CF_CLIENT | CF_ARCHIVE, "r_q1bsp_lightmap_updates_enabled", "1", "allow lightmaps to be updated on Q1BSP maps (don't turn this off except for debugging)"};
+cvar_t r_q1bsp_lightmap_updates_combine = {CF_CLIENT | CF_ARCHIVE, "r_q1bsp_lightmap_updates_combine", "2", "combine lightmap texture updates to make fewer glTexSubImage2D calls, modes: 0 = immediately upload lightmaps (may be thousands of small 3x3 updates), 1 = combine to one call, 2 = combine to one full texture update (glTexImage2D) which tells the driver it does not need to lock the resource (faster on most drivers)"};
+cvar_t r_q1bsp_lightmap_updates_hidden_surfaces = {CF_CLIENT | CF_ARCHIVE, "r_q1bsp_lightmap_updates_hidden_surfaces", "0", "update lightmaps on surfaces that are not visible, so that updates only occur on frames where lightstyles changed value (animation or light switches), only makes sense with combine = 2"};
+
extern cvar_t v_glslgamma_2d;
extern qbool v_flipped_state;
}
if (r_texture_fogattenuation)
{
- R_UpdateTexture(r_texture_fogattenuation, &data1[0][0], 0, 0, 0, FOGWIDTH, 1, 1);
- //R_UpdateTexture(r_texture_fogattenuation, &data2[0][0], 0, 0, 0, FOGWIDTH, 1, 1);
+ R_UpdateTexture(r_texture_fogattenuation, &data1[0][0], 0, 0, 0, FOGWIDTH, 1, 1, 0);
+ //R_UpdateTexture(r_texture_fogattenuation, &data2[0][0], 0, 0, 0, FOGWIDTH, 1, 1, 0);
}
else
{
for (i = 0;i < R_BUFFERDATA_COUNT;i++)
Cvar_RegisterVariable(&r_buffermegs[i]);
Cvar_RegisterVariable(&r_batch_dynamicbuffer);
+ Cvar_RegisterVariable(&r_q1bsp_lightmap_updates_enabled);
+ Cvar_RegisterVariable(&r_q1bsp_lightmap_updates_combine);
+ Cvar_RegisterVariable(&r_q1bsp_lightmap_updates_hidden_surfaces);
if (gamemode == GAME_NEHAHRA || gamemode == GAME_TENEBRAE)
Cvar_SetValue(&cvars_all, "r_fullbrights", 0);
#ifdef DP_MOBILETOUCH
}
if (r_texture_gammaramps)
{
- R_UpdateTexture(r_texture_gammaramps, &rampbgr[0][0], 0, 0, 0, RAMPWIDTH, 1, 1);
+ R_UpdateTexture(r_texture_gammaramps, &rampbgr[0][0], 0, 0, 0, RAMPWIDTH, 1, 1, 0);
}
else
{
const msurface_t **r_surfacelist = NULL;
void R_DrawModelSurfaces(entity_render_t *ent, qbool skysurfaces, qbool writedepth, qbool depthonly, qbool debug, qbool prepass, qbool ui)
{
- int i, j, endj, flagsmask;
+ int i, j, flagsmask;
model_t *model = ent->model;
msurface_t *surfaces;
unsigned char *update;
return;
}
+ // check if this is an empty model
+ if (model->nummodelsurfaces == 0)
+ return;
+
rsurface.lightmaptexture = NULL;
rsurface.deluxemaptexture = NULL;
rsurface.uselightmaptexture = false;
rsurface.texture = NULL;
rsurface.rtlight = NULL;
numsurfacelist = 0;
+
// add visible surfaces to draw list
if (ent == r_refdef.scene.worldentity)
{
- // update light styles
- if (!skysurfaces && !depthonly && !prepass && model->brushq1.num_lightstyles && r_refdef.scene.lightmapintensity > 0)
- {
- model_brush_lightstyleinfo_t *style;
- // Iterate over each active style
- for (i = 0, style = model->brushq1.data_lightstyleinfo;i < model->brushq1.num_lightstyles;i++, style++)
- {
- if (style->value != r_refdef.scene.lightstylevalue[style->style])
- {
- int *list = style->surfacelist;
- style->value = r_refdef.scene.lightstylevalue[style->style];
- // Iterate over every surface this style applies to
- for (j = 0;j < style->numsurfaces;j++)
- // Update brush entities even if not visible otherwise they'll render solid black.
- if(r_refdef.viewcache.world_surfacevisible[list[j]])
- update[list[j]] = true;
- }
- }
- }
// for the world entity, check surfacevisible
for (i = 0;i < model->nummodelsurfaces;i++)
{
if (r_refdef.viewcache.world_surfacevisible[j])
r_surfacelist[numsurfacelist++] = surfaces + j;
}
+
+ // don't do anything if there were no surfaces added (none of the world entity is visible)
+ if (!numsurfacelist)
+ {
+ rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveModelEntity
+ return;
+ }
}
else if (ui)
{
- // for ui we have to preserve the order of surfaces
+ // for ui we have to preserve the order of surfaces (not using sortedmodelsurfaces)
for (i = 0; i < model->nummodelsurfaces; i++)
r_surfacelist[numsurfacelist++] = surfaces + model->firstmodelsurface + i;
}
else
{
- // update light styles
- if (!skysurfaces && !depthonly && !prepass && model->brushq1.num_lightstyles && r_refdef.scene.lightmapintensity > 0)
- {
- model_brush_lightstyleinfo_t *style;
- // Iterate over each active style
- for (i = 0, style = model->brushq1.data_lightstyleinfo;i < model->brushq1.num_lightstyles;i++, style++)
- {
- if (style->value != r_refdef.scene.lightstylevalue[style->style])
- {
- int *list = style->surfacelist;
- style->value = r_refdef.scene.lightstylevalue[style->style];
- // Iterate over every surface this style applies to
- for (j = 0;j < style->numsurfaces;j++)
- update[list[j]] = true;
- }
- }
- }
// add all surfaces
for (i = 0; i < model->nummodelsurfaces; i++)
r_surfacelist[numsurfacelist++] = surfaces + model->sortedmodelsurfaces[i];
}
- // don't do anything if there were no surfaces
- if (!numsurfacelist)
- {
- rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveModelEntity
- return;
- }
- // update lightmaps if needed
- if (update)
+
+ // mark lightmaps as dirty if their lightstyle's value changed
+ // we do this by using style chains because most styles do not change on most frames,
+ // and most surfaces do not have styles on them
+ // map packs like Arcane Dimensions (e.g. ad_sepulcher) break this rule and animate most surfaces
+ if (update && !skysurfaces && !depthonly && !prepass && model->brushq1.num_lightstyles && r_refdef.scene.lightmapintensity > 0 && r_q1bsp_lightmap_updates_enabled.integer)
{
- for (j = model->firstmodelsurface, endj = model->firstmodelsurface + model->nummodelsurfaces;j < endj;j++)
+ model_brush_lightstyleinfo_t* style;
+ // for each lightstyle, check if its value changed and mark the lightmaps as dirty if so
+ for (i = 0, style = model->brushq1.data_lightstyleinfo; i < model->brushq1.num_lightstyles; i++, style++)
+ {
+ if (style->value != r_refdef.scene.lightstylevalue[style->style])
+ {
+ int* list = style->surfacelist;
+ style->value = r_refdef.scene.lightstylevalue[style->style];
+ // value changed - mark the surfaces belonging to this style chain as dirty
+ for (j = 0; j < style->numsurfaces; j++)
+ update[list[j]] = true;
+ }
+ }
+ // now check if update flags are set on any surfaces that are visible
+ if (r_q1bsp_lightmap_updates_hidden_surfaces.integer)
+ {
+ // we can do less frequent texture uploads (approximately 10hz for animated lightstyles) by rebuilding lightmaps on surfaces that are not currently visible
+ // for optimal efficiency, this includes the submodels of the worldmodel, so we use model->num_surfaces, not nummodelsurfaces
+ for (i = 0; i < model->num_surfaces;i++)
+ if (update[i])
+ R_BuildLightMap(ent, surfaces + i, r_q1bsp_lightmap_updates_combine.integer);
+ }
+ else
{
- if (update[j])
- R_BuildLightMap(ent, surfaces + j);
+ for (i = 0; i < numsurfacelist; i++)
+ if (update[r_surfacelist[i] - surfaces])
+ R_BuildLightMap(ent, (msurface_t *)r_surfacelist[i], r_q1bsp_lightmap_updates_combine.integer);
}
}
Combine and scale multiple lightmaps into the 8.8 format in blocklights
===============
*/
-void R_BuildLightMap (const entity_render_t *ent, msurface_t *surface)
+void R_BuildLightMap (const entity_render_t *ent, msurface_t *surface, int combine)
{
int smax, tmax, i, size, size3, maps, l;
int *bl, scale;
if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D)
Image_MakesRGBColorsFromLinear_Lightmap(templight, templight, size);
- R_UpdateTexture(surface->lightmaptexture, templight, surface->lightmapinfo->lightmaporigin[0], surface->lightmapinfo->lightmaporigin[1], 0, smax, tmax, 1);
+ R_UpdateTexture(surface->lightmaptexture, templight, surface->lightmapinfo->lightmaporigin[0], surface->lightmapinfo->lightmaporigin[1], 0, smax, tmax, 1, combine);
// update the surface's deluxemap if it has one
if (surface->deluxemaptexture != r_texture_blanknormalmap)
l = (int)(n[2] * 128 + 128);out[0] = bound(0, l, 255);
out[3] = 255;
}
- R_UpdateTexture(surface->deluxemaptexture, templight, surface->lightmapinfo->lightmaporigin[0], surface->lightmapinfo->lightmaporigin[1], 0, smax, tmax, 1);
+ R_UpdateTexture(surface->deluxemaptexture, templight, surface->lightmapinfo->lightmaporigin[0], surface->lightmapinfo->lightmaporigin[1], 0, smax, tmax, 1, r_q1bsp_lightmap_updates_combine.integer);
}
}
cvar_t gl_texturecompression_lightcubemaps = {CF_CLIENT | CF_ARCHIVE, "gl_texturecompression_lightcubemaps", "1", "whether to compress light cubemaps (spotlights and other light projection images)"};
cvar_t gl_texturecompression_reflectmask = {CF_CLIENT | CF_ARCHIVE, "gl_texturecompression_reflectmask", "1", "whether to compress reflection cubemap masks (mask of which areas of the texture should reflect the generic shiny cubemap)"};
cvar_t gl_texturecompression_sprites = {CF_CLIENT | CF_ARCHIVE, "gl_texturecompression_sprites", "1", "whether to compress sprites"};
-cvar_t gl_nopartialtextureupdates = {CF_CLIENT | CF_ARCHIVE, "gl_nopartialtextureupdates", "0", "use alternate path for dynamic lightmap updates that avoids a possibly slow code path in the driver"};
cvar_t r_texture_dds_load_alphamode = {CF_CLIENT, "r_texture_dds_load_alphamode", "1", "0: trust DDPF_ALPHAPIXELS flag, 1: texture format and brute force search if ambiguous, 2: texture format only"};
cvar_t r_texture_dds_load_logfailure = {CF_CLIENT, "r_texture_dds_load_logfailure", "0", "log missing DDS textures to ddstexturefailures.log, 0: done log, 1: log with no optional textures (_norm, glow etc.). 2: log all"};
cvar_t r_texture_dds_swdecode = {CF_CLIENT, "r_texture_dds_swdecode", "0", "0: don't software decode DDS, 1: software decode DDS if unsupported, 2: always software decode DDS"};
void *updatecallback_data;
// --- [11/22/2007 Black]
- // stores backup copy of texture for deferred texture updates (gl_nopartialtextureupdates cvar)
+ // stores backup copy of texture for deferred texture updates (R_UpdateTexture when combine = true)
unsigned char *bufferpixels;
- unsigned char *modifiedpixels;
- int modified_width, modified_height, modified_depth;
- int modified_offset_x, modified_offset_y, modified_offset_z;
+ int modified_mins[3], modified_maxs[3];
qbool buffermodified;
// pointer to texturepool (check this to see if the texture is allocated)
Cvar_RegisterVariable (&gl_texturecompression_lightcubemaps);
Cvar_RegisterVariable (&gl_texturecompression_reflectmask);
Cvar_RegisterVariable (&gl_texturecompression_sprites);
- Cvar_RegisterVariable (&gl_nopartialtextureupdates);
Cvar_RegisterVariable (&r_texture_dds_load_alphamode);
Cvar_RegisterVariable (&r_texture_dds_load_logfailure);
Cvar_RegisterVariable (&r_texture_dds_swdecode);
if (glt->flags & TEXF_ALLOWUPDATES)
glt->bufferpixels = (unsigned char *)Mem_Alloc(texturemempool, glt->tilewidth*glt->tileheight*glt->tiledepth*glt->sides*glt->bytesperpixel);
- glt->modifiedpixels = NULL;
- glt->modified_width = glt->modified_height = glt->modified_depth = glt->modified_offset_x = glt->modified_offset_y = glt->modified_offset_z = 0;
+ glt->buffermodified = false;
+ VectorClear(glt->modified_mins);
+ VectorClear(glt->modified_maxs);
// free any temporary processing buffer we allocated...
if (temppixels)
return rt ? ((gltexture_t *)rt)->flags : 0;
}
-void R_UpdateTexture(rtexture_t *rt, const unsigned char *data, int x, int y, int z, int width, int height, int depth)
+void R_UpdateTexture(rtexture_t *rt, const unsigned char *data, int x, int y, int z, int width, int height, int depth, int combine)
{
gltexture_t *glt = (gltexture_t *)rt;
if (data == NULL)
// update part of the texture
if (glt->bufferpixels || (glt->bufferpixels && (x || y || z || width != glt->inputwidth || height != glt->inputheight || depth != glt->inputdepth)))
{
- int j;
- int bpp = glt->bytesperpixel;
- int inputskip = width*bpp;
- int outputskip = glt->tilewidth*bpp;
- const unsigned char *input = data;
- unsigned char *output = glt->bufferpixels;
+ size_t j, bpp = glt->bytesperpixel;
+
+ // depth and sides are not fully implemented here - can still do full updates but not partial.
if (glt->inputdepth != 1 || glt->sides != 1)
- Sys_Error("R_UpdateTexture on buffered texture that is not 2D\n");
- if (x < 0)
- {
- width += x;
- input -= x*bpp;
- x = 0;
- }
- if (y < 0)
- {
- height += y;
- input -= y*inputskip;
- y = 0;
- }
- if (width > glt->tilewidth - x)
- width = glt->tilewidth - x;
- if (height > glt->tileheight - y)
- height = glt->tileheight - y;
- if (width < 1 || height < 1)
- return;
-
- output += y*outputskip + x*bpp;
- /* Cloudwalk FIXME: Broken shit, disabled for now.
- // TODO: Optimize this further.
- if(!gl_nopartialtextureupdates.integer)
+ Host_Error("R_UpdateTexture on buffered texture that is not 2D\n");
+ if (x < 0 || y < 0 || z < 0 || glt->tilewidth < x + width || glt->tileheight < y + height || glt->tiledepth < z + depth)
+ Host_Error("R_UpdateTexture on buffered texture with out of bounds coordinates (%i %i %i to %i %i %i is not within 0 0 0 to %i %i %i)", x, y, z, x + width, y + height, z + depth, glt->tilewidth, glt->tileheight, glt->tiledepth);
+
+ for (j = 0; j < height; j++)
+ memcpy(glt->bufferpixels + ((y + j) * glt->tilewidth + x) * bpp, data + j * width * bpp, width * bpp);
+
+ switch(combine)
{
- // Calculate the modified region, and resize it as it gets bigger.
- if(!glt->buffermodified)
+ case 0:
+ // immediately update the part of the texture, no combining
+ R_UploadPartialTexture(glt, data, x, y, z, width, height, depth);
+ break;
+ case 1:
+ // keep track of the region that is modified, decide later how big the partial update area is
+ if (glt->buffermodified)
{
- glt->modified_offset_x = x;
- glt->modified_offset_y = y;
- glt->modified_offset_z = z;
-
- glt->modified_width = width;
- glt->modified_height = height;
- glt->modified_depth = depth;
+ glt->modified_mins[0] = min(glt->modified_mins[0], x);
+ glt->modified_mins[1] = min(glt->modified_mins[1], y);
+ glt->modified_mins[2] = min(glt->modified_mins[2], z);
+ glt->modified_maxs[0] = max(glt->modified_maxs[0], x + width);
+ glt->modified_maxs[1] = max(glt->modified_maxs[1], y + height);
+ glt->modified_maxs[2] = max(glt->modified_maxs[2], z + depth);
}
else
{
- if(x < glt->modified_offset_x) glt->modified_offset_x = x;
- if(y < glt->modified_offset_y) glt->modified_offset_y = y;
- if(z < glt->modified_offset_z) glt->modified_offset_z = z;
-
- if(width + x > glt->modified_width) glt->modified_width = width + x;
- if(height + y > glt->modified_height) glt->modified_height = height + y;
- if(depth + z > glt->modified_depth) glt->modified_depth = depth + z;
+ glt->buffermodified = true;
+ glt->modified_mins[0] = x;
+ glt->modified_mins[1] = y;
+ glt->modified_mins[2] = z;
+ glt->modified_maxs[0] = x + width;
+ glt->modified_maxs[1] = y + height;
+ glt->modified_maxs[2] = z + depth;
}
-
- if(!&glt->modifiedpixels || &output < &glt->modifiedpixels)
- glt->modifiedpixels = output;
+ glt->dirty = true;
+ break;
+ default:
+ case 2:
+ // mark the entire texture as dirty, it will be uploaded later
+ glt->buffermodified = true;
+ glt->modified_mins[0] = 0;
+ glt->modified_mins[1] = 0;
+ glt->modified_mins[2] = 0;
+ glt->modified_maxs[0] = glt->tilewidth;
+ glt->modified_maxs[1] = glt->tileheight;
+ glt->modified_maxs[2] = glt->tiledepth;
+ glt->dirty = true;
+ break;
}
- */
-
- for (j = 0;j < height;j++, output += outputskip, input += inputskip)
- memcpy(output, input, width*bpp);
-
- glt->dirty = true;
- glt->buffermodified = true;
}
else
R_UploadFullTexture(glt, data);
if (glt->buffermodified && glt->bufferpixels)
{
glt->buffermodified = false;
- if(!glt->modifiedpixels)
+ // Because we currently don't set the relevant upload stride parameters, just make it full width.
+ glt->modified_mins[0] = 0;
+ glt->modified_maxs[0] = glt->tilewidth;
+ // Check also if it's updating at least half the height of the texture.
+ if (glt->modified_maxs[1] - glt->modified_mins[1] > glt->tileheight / 2)
R_UploadFullTexture(glt, glt->bufferpixels);
else
- R_UploadPartialTexture(glt, glt->modifiedpixels, glt->modified_offset_x, glt->modified_offset_y, glt->modified_offset_z, glt->modified_width, glt->modified_height, glt->modified_depth);
+ R_UploadPartialTexture(glt, glt->bufferpixels + (size_t)glt->modified_mins[1] * glt->tilewidth * glt->bytesperpixel, glt->modified_mins[0], glt->modified_mins[1], glt->modified_mins[2], glt->modified_maxs[0] - glt->modified_mins[0], glt->modified_maxs[1] - glt->modified_mins[1], glt->modified_maxs[2] - glt->modified_mins[2]);
}
- glt->modified_offset_x = glt->modified_offset_y = glt->modified_offset_z = glt->modified_width = glt->modified_height = glt->modified_depth = 0;
- glt->modifiedpixels = NULL;
+ VectorClear(glt->modified_mins);
+ VectorClear(glt->modified_maxs);
glt->dirty = false;
return glt->texnum;
}
}
if (!r_shadow_bouncegrid_state.createtexture)
- R_UpdateTexture(r_shadow_bouncegrid_state.texture, pixelsbgra8, 0, 0, 0, resolution[0], resolution[1], resolution[2]*pixelbands);
+ R_UpdateTexture(r_shadow_bouncegrid_state.texture, pixelsbgra8, 0, 0, 0, resolution[0], resolution[1], resolution[2]*pixelbands, 0);
else
r_shadow_bouncegrid_state.texture = R_LoadTexture3D(r_shadow_texturepool, "bouncegrid", resolution[0], resolution[1], resolution[2]*pixelbands, pixelsbgra8, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, 0, NULL);
break;
}
if (!r_shadow_bouncegrid_state.createtexture)
- R_UpdateTexture(r_shadow_bouncegrid_state.texture, (const unsigned char *)pixelsrgba16f, 0, 0, 0, resolution[0], resolution[1], resolution[2]*pixelbands);
+ R_UpdateTexture(r_shadow_bouncegrid_state.texture, (const unsigned char *)pixelsrgba16f, 0, 0, 0, resolution[0], resolution[1], resolution[2]*pixelbands, 0);
else
r_shadow_bouncegrid_state.texture = R_LoadTexture3D(r_shadow_texturepool, "bouncegrid", resolution[0], resolution[1], resolution[2]*pixelbands, (const unsigned char *)pixelsrgba16f, TEXTYPE_COLORBUFFER16F, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, 0, NULL);
break;
pixelsrgba32f = highpixels;
if (!r_shadow_bouncegrid_state.createtexture)
- R_UpdateTexture(r_shadow_bouncegrid_state.texture, (const unsigned char *)pixelsrgba32f, 0, 0, 0, resolution[0], resolution[1], resolution[2]*pixelbands);
+ R_UpdateTexture(r_shadow_bouncegrid_state.texture, (const unsigned char *)pixelsrgba32f, 0, 0, 0, resolution[0], resolution[1], resolution[2]*pixelbands, 0);
else
r_shadow_bouncegrid_state.texture = R_LoadTexture3D(r_shadow_texturepool, "bouncegrid", resolution[0], resolution[1], resolution[2]*pixelbands, (const unsigned char *)pixelsrgba32f, TEXTYPE_COLORBUFFER32F, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, 0, NULL);
break;
// update a portion of the image data of a texture, used by lightmap updates
// and procedural textures such as video playback, actual uploads may be
// delayed by gl_nopartialtextureupdates cvar until R_Mesh_TexBind uses it
-void R_UpdateTexture(rtexture_t *rt, const unsigned char *data, int x, int y, int z, int width, int height, int depth);
+// combine has 3 values: 0 = immediately upload (glTexSubImage2D), 1 = combine with other updates (glTexSubImage2D on next draw), 2 = combine with other updates and never upload partial images (glTexImage2D on next draw)
+void R_UpdateTexture(rtexture_t *rt, const unsigned char *data, int x, int y, int z, int width, int height, int depth, int combine);
// returns the renderer dependent texture slot number (call this before each
// use, as a texture might not have been precached)
extern cvar_t r_wateralpha;
extern cvar_t r_dynamic;
+extern cvar_t r_q1bsp_lightmap_updates_enabled;
+extern cvar_t r_q1bsp_lightmap_updates_combine;
+extern cvar_t r_q1bsp_lightmap_updates_combine_full_texture;
+extern cvar_t r_q1bsp_lightmap_updates_hidden_surfaces;
+
void R_NewExplosion(const vec3_t org);
void R_UpdateVariables(void); // must call after setting up most of r_refdef, but before calling R_RenderView
void R_RenderView(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, int x, int y, int width, int height); // must set r_refdef and call R_UpdateVariables and CL_UpdateEntityShading first
void R_DecalSystem_Reset(decalsystem_t *decalsystem);
void R_Shadow_UpdateBounceGridTexture(void);
void R_DrawPortals(void);
-void R_BuildLightMap(const entity_render_t *ent, msurface_t *surface);
+void R_BuildLightMap(const entity_render_t *ent, msurface_t *surface, int combine);
void R_Water_AddWaterPlane(msurface_t *surface, int entno);
int R_Shadow_GetRTLightInfo(unsigned int lightindex, float *origin, float *radius, float *color);
dp_font_t *FindFont(const char *title, qbool allocate_new);