GLES1 renderer: lightmap copies

Available in both GL1 and GLES1. Keep multiple copies of "the same"
lightmap on video memory; they are actually different, because they're
used in different frames. This is a workaround for the usage of
glTexSubImage2D() for dynamic lighting, since modifying textures used
recently causes slowdown in embedded/mobile devices.
Controlled by gl1_lightmapcopies cvar; default in GL1 is `0`, while
in GLES1 is `1`.
This commit is contained in:
Jaime Moreira 2024-07-27 23:22:41 -04:00
parent 0596d23e4c
commit b72c465214
6 changed files with 181 additions and 86 deletions

View file

@ -492,6 +492,12 @@ it's `+set busywait 0` (setting the `busywait` cvar) and `-portable`
* **gl1_stencilshadow**: If `gl_shadows` is set to `1`, this makes them * **gl1_stencilshadow**: If `gl_shadows` is set to `1`, this makes them
look a bit better (no flickering) by using the stencil buffer. look a bit better (no flickering) by using the stencil buffer.
* **gl1_lightmapcopies**: When enabled (`1`), keep multiple copies of the
same lightmap rotating, shifting to a different one when drawing a new
frame. Meant for mobile/embedded devices, where changing textures just
displayed (dynamic lighting) causes slowdown. By default in GL1 is
disabled, while in GLES1 is enabled. `vid_restart` needed.
* **gl1_discardfb**: Only available in ES1. If set to `1` (default), * **gl1_discardfb**: Only available in ES1. If set to `1` (default),
send a hint to discard framebuffers after finishing a frame. Useful send a hint to discard framebuffers after finishing a frame. Useful
for GPUs that attempt to reuse them, something Quake 2 doesn't do. for GPUs that attempt to reuse them, something Quake 2 doesn't do.

View file

@ -32,6 +32,7 @@
#define GLBUFFER_RESET vtx_ptr = idx_ptr = 0; gl_buf.vt = gl_buf.tx = gl_buf.cl = 0; #define GLBUFFER_RESET vtx_ptr = idx_ptr = 0; gl_buf.vt = gl_buf.tx = gl_buf.cl = 0;
glbuffer_t gl_buf; // our drawing buffer, used globally glbuffer_t gl_buf; // our drawing buffer, used globally
int cur_lm_copy; // which lightmap copy to use (when lightmapcopies=on)
static GLushort vtx_ptr, idx_ptr; // pointers for array positions in gl_buf static GLushort vtx_ptr, idx_ptr; // pointers for array positions in gl_buf
@ -181,7 +182,13 @@ R_ApplyGLBuffer(void)
if (mtex) if (mtex)
{ {
// TMU 1: Lightmap texture // TMU 1: Lightmap texture
R_MBind(GL_TEXTURE1, gl_state.lightmap_textures + gl_buf.texture[1]); int lmtexture = gl_state.lightmap_textures + gl_buf.texture[1];
if (gl_config.lightmapcopies)
{
// Bind appropiate lightmap copy for this frame
lmtexture += gl_state.max_lightmaps * cur_lm_copy;
}
R_MBind(GL_TEXTURE1, lmtexture);
if (gl1_overbrightbits->value) if (gl1_overbrightbits->value)
{ {

View file

@ -87,7 +87,7 @@ LM_UploadBlock(qboolean dynamic)
{ {
const int texture = (dynamic)? 0 : gl_lms.current_lightmap_texture; const int texture = (dynamic)? 0 : gl_lms.current_lightmap_texture;
const int buffer = (gl_config.multitexture)? gl_lms.current_lightmap_texture : 0; const int buffer = (gl_config.multitexture)? gl_lms.current_lightmap_texture : 0;
int height = 0; int height = 0, i;
R_Bind(gl_state.lightmap_textures + texture); R_Bind(gl_state.lightmap_textures + texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
@ -95,8 +95,6 @@ LM_UploadBlock(qboolean dynamic)
if (dynamic) if (dynamic)
{ {
int i;
for (i = 0; i < gl_state.block_width; i++) for (i = 0; i < gl_state.block_width; i++)
{ {
if (gl_lms.allocated[i] > height) if (gl_lms.allocated[i] > height)
@ -117,6 +115,21 @@ LM_UploadBlock(qboolean dynamic)
0, GL_LIGHTMAP_FORMAT, GL_UNSIGNED_BYTE, 0, GL_LIGHTMAP_FORMAT, GL_UNSIGNED_BYTE,
gl_lms.lightmap_buffer[buffer]); gl_lms.lightmap_buffer[buffer]);
if (gl_config.lightmapcopies && buffer != 0)
{
// Upload to all lightmap copies
for (i = 1; i < MAX_LIGHTMAP_COPIES; i++)
{
R_Bind(gl_state.lightmap_textures + (gl_state.max_lightmaps * i) + texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, gl_lms.internal_format,
gl_state.block_width, gl_state.block_height,
0, GL_LIGHTMAP_FORMAT, GL_UNSIGNED_BYTE,
gl_lms.lightmap_buffer[buffer]);
}
}
if (++gl_lms.current_lightmap_texture == gl_state.max_lightmaps) if (++gl_lms.current_lightmap_texture == gl_state.max_lightmaps)
{ {
ri.Sys_Error(ERR_DROP, ri.Sys_Error(ERR_DROP,

View file

@ -91,6 +91,7 @@ cvar_t *gl1_particle_square;
cvar_t *gl1_palettedtexture; cvar_t *gl1_palettedtexture;
cvar_t *gl1_pointparameters; cvar_t *gl1_pointparameters;
cvar_t *gl1_multitexture; cvar_t *gl1_multitexture;
cvar_t *gl1_lightmapcopies;
cvar_t *gl1_discardfb; cvar_t *gl1_discardfb;
cvar_t *gl_drawbuffer; cvar_t *gl_drawbuffer;
@ -1171,6 +1172,12 @@ RI_RenderFrame(refdef_t *fd)
R_SetGL2D(); R_SetGL2D();
} }
#ifdef YQ2_GL1_GLES
#define DEFAULT_LMCOPIES "1"
#else
#define DEFAULT_LMCOPIES "0"
#endif
void void
R_Register(void) R_Register(void)
{ {
@ -1225,6 +1232,7 @@ R_Register(void)
gl1_palettedtexture = ri.Cvar_Get("r_palettedtextures", "0", CVAR_ARCHIVE); gl1_palettedtexture = ri.Cvar_Get("r_palettedtextures", "0", CVAR_ARCHIVE);
gl1_pointparameters = ri.Cvar_Get("gl1_pointparameters", "1", CVAR_ARCHIVE); gl1_pointparameters = ri.Cvar_Get("gl1_pointparameters", "1", CVAR_ARCHIVE);
gl1_multitexture = ri.Cvar_Get("gl1_multitexture", "1", CVAR_ARCHIVE); gl1_multitexture = ri.Cvar_Get("gl1_multitexture", "1", CVAR_ARCHIVE);
gl1_lightmapcopies = ri.Cvar_Get("gl1_lightmapcopies", DEFAULT_LMCOPIES, CVAR_ARCHIVE);
#ifdef YQ2_GL1_GLES #ifdef YQ2_GL1_GLES
gl1_discardfb = ri.Cvar_Get("gl1_discardfb", "1", CVAR_ARCHIVE); gl1_discardfb = ri.Cvar_Get("gl1_discardfb", "1", CVAR_ARCHIVE);
#endif #endif
@ -1265,6 +1273,8 @@ R_Register(void)
ri.Cmd_AddCommand("gl_strings", R_Strings); ri.Cmd_AddCommand("gl_strings", R_Strings);
} }
#undef DEFAULT_LMCOPIES
/* /*
* Changes the video mode * Changes the video mode
*/ */
@ -1645,6 +1655,28 @@ RI_Init(void)
// ---- // ----
/* Lightmap copies: keep multiple copies of "the same" lightmap on video memory.
* All of them are actually different, because they are affected by different dynamic lighting,
* in different frames. This is not meant for Immediate-Mode Rendering systems (desktop),
* but for Tile-Based / Deferred Rendering ones (embedded / mobile), since active manipulation
* of textures already being used in the last few frames can cause slowdown on these systems.
* Needless to say, GPU memory usage is highly increased, so watch out in low memory situations.
*/
R_Printf(PRINT_ALL, " - Lightmap copies: ");
gl_config.lightmapcopies = false;
if (gl_config.multitexture && gl1_lightmapcopies->value)
{
gl_config.lightmapcopies = true;
R_Printf(PRINT_ALL, "Okay\n");
}
else
{
R_Printf(PRINT_ALL, "Disabled\n");
}
// ----
/* Discard framebuffer: Available only on GLES1, enables the use of a "performance hint" /* Discard framebuffer: Available only on GLES1, enables the use of a "performance hint"
* to the graphic driver, to get rid of the contents of the depth and stencil buffers. * to the graphic driver, to get rid of the contents of the depth and stencil buffers.
* Useful for some GPUs that may attempt to keep them and/or write them back to * Useful for some GPUs that may attempt to keep them and/or write them back to

View file

@ -28,12 +28,18 @@
#include "header/local.h" #include "header/local.h"
typedef struct
{
int top, bottom, left, right;
} lmrect_t;
int c_visible_lightmaps; int c_visible_lightmaps;
int c_visible_textures; int c_visible_textures;
static vec3_t modelorg; /* relative to viewpoint */ static vec3_t modelorg; /* relative to viewpoint */
msurface_t *r_alpha_surfaces; msurface_t *r_alpha_surfaces;
gllightmapstate_t gl_lms; gllightmapstate_t gl_lms;
extern int cur_lm_copy;
void LM_InitBlock(void); void LM_InitBlock(void);
void LM_UploadBlock(qboolean dynamic); void LM_UploadBlock(qboolean dynamic);
@ -519,52 +525,6 @@ R_DrawAlphaSurfaces(void)
r_alpha_surfaces = NULL; r_alpha_surfaces = NULL;
} }
static qboolean
R_HasDynamicLights(msurface_t *surf, int *mapNum)
{
int map;
qboolean is_dynamic = false;
if ( r_fullbright->value || !gl1_dynamic->value ||
(surf->texinfo->flags & (SURF_SKY | SURF_TRANS33 | SURF_TRANS66 | SURF_WARP)) )
{
return false;
}
// Any dynamic lights on this surface?
for (map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++)
{
if (r_newrefdef.lightstyles[surf->styles[map]].white != surf->cached_light[map])
{
is_dynamic = true;
break;
}
}
// No matter if it is this frame or was in the previous one: has dynamic lights
if ( !is_dynamic && (surf->dlightframe == r_framecount || surf->dirty_lightmap) )
{
is_dynamic = true;
}
if (mapNum)
{
*mapNum = map;
}
return is_dynamic;
}
static void
R_UpdateSurfCache(msurface_t *surf, int map)
{
if ( ((surf->styles[map] >= 32) || (surf->styles[map] == 0))
&& (surf->dlightframe != r_framecount) )
{
R_SetCacheState(surf);
}
surf->dirty_lightmap = (surf->dlightframe == r_framecount);
}
static void static void
R_RenderLightmappedPoly(entity_t *currententity, msurface_t *surf) R_RenderLightmappedPoly(entity_t *currententity, msurface_t *surf)
{ {
@ -599,11 +559,37 @@ R_RenderLightmappedPoly(entity_t *currententity, msurface_t *surf)
} }
} }
/* Add "adding" area to "obj" */
static void
R_JoinAreas(lmrect_t *adding, lmrect_t *obj)
{
if (adding->top < obj->top)
{
obj->top = adding->top;
}
if (adding->bottom > obj->bottom)
{
obj->bottom = adding->bottom;
}
if (adding->left < obj->left)
{
obj->left = adding->left;
}
if (adding->right > obj->right)
{
obj->right = adding->right;
}
}
/* Upload dynamic lights to each lightmap texture (multitexture path only) */ /* Upload dynamic lights to each lightmap texture (multitexture path only) */
static void static void
R_RegenAllLightmaps() R_RegenAllLightmaps()
{ {
int i, map, smax, tmax, top, bottom, left, right, bt, bb, bl, br; static lmrect_t lmchange[MAX_LIGHTMAP_COPIES][MAX_LIGHTMAPS];
static qboolean altered[MAX_LIGHTMAP_COPIES][MAX_LIGHTMAPS];
int i, map, smax, tmax, cc, lmtex;
lmrect_t current, best;
msurface_t *surf; msurface_t *surf;
byte *base; byte *base;
qboolean affected_lightmap; qboolean affected_lightmap;
@ -611,11 +597,19 @@ R_RegenAllLightmaps()
qboolean pixelstore_set = false; qboolean pixelstore_set = false;
#endif #endif
if ( !gl_config.multitexture ) if ( !gl_config.multitexture || r_fullbright->value || !gl1_dynamic->value )
{ {
return; return;
} }
cc = lmtex = 0;
if (gl_config.lightmapcopies)
{
cur_lm_copy = (cur_lm_copy + 1) % MAX_LIGHTMAP_COPIES; // alternate between calls
cc = cur_lm_copy;
lmtex = gl_state.max_lightmaps * cc;
}
for (i = 1; i < gl_state.max_lightmaps; i++) for (i = 1; i < gl_state.max_lightmaps; i++)
{ {
if (!gl_lms.lightmap_surfaces[i] || !gl_lms.lightmap_buffer[i]) if (!gl_lms.lightmap_surfaces[i] || !gl_lms.lightmap_buffer[i])
@ -624,57 +618,94 @@ R_RegenAllLightmaps()
} }
affected_lightmap = false; affected_lightmap = false;
bt = gl_state.block_height; best.top = gl_state.block_height;
bl = gl_state.block_width; best.left = gl_state.block_width;
bb = br = 0; best.bottom = best.right = 0;
for (surf = gl_lms.lightmap_surfaces[i]; for (surf = gl_lms.lightmap_surfaces[i];
surf != 0; surf != 0;
surf = surf->lightmapchain) surf = surf->lightmapchain)
{ {
if ( !R_HasDynamicLights(surf, &map) ) if (surf->texinfo->flags & (SURF_SKY | SURF_TRANS33 | SURF_TRANS66 | SURF_WARP))
{ {
continue; continue;
} }
// Any dynamic lights on this surface?
for (map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++)
{
if (r_newrefdef.lightstyles[surf->styles[map]].white != surf->cached_light[map])
{
goto dynamic_surf;
}
}
// Doesn't matter if it is this frame or was in the previous one: surface has dynamic lights
if ( surf->dlightframe == r_framecount || surf->dirty_lightmap )
{
goto dynamic_surf;
}
continue; // no dynamic lights affect this surface in this frame
dynamic_surf:
affected_lightmap = true; affected_lightmap = true;
smax = (surf->extents[0] >> 4) + 1; smax = (surf->extents[0] >> 4) + 1;
tmax = (surf->extents[1] >> 4) + 1; tmax = (surf->extents[1] >> 4) + 1;
left = surf->light_s; current.left = surf->light_s;
right = surf->light_s + smax; current.right = surf->light_s + smax;
top = surf->light_t; current.top = surf->light_t;
bottom = surf->light_t + tmax; current.bottom = surf->light_t + tmax;
base = gl_lms.lightmap_buffer[i]; base = gl_lms.lightmap_buffer[i];
base += (top * gl_state.block_width + left) * LIGHTMAP_BYTES; base += (current.top * gl_state.block_width + current.left) * LIGHTMAP_BYTES;
R_BuildLightMap(surf, base, gl_state.block_width * LIGHTMAP_BYTES); R_BuildLightMap(surf, base, gl_state.block_width * LIGHTMAP_BYTES);
R_UpdateSurfCache(surf, map);
if (left < bl) if ( ((surf->styles[map] >= 32) || (surf->styles[map] == 0))
&& (surf->dlightframe != r_framecount) )
{ {
bl = left; R_SetCacheState(surf);
}
if (right > br)
{
br = right;
}
if (top < bt)
{
bt = top;
}
if (bottom > bb)
{
bb = bottom;
}
} }
if (!affected_lightmap) surf->dirty_lightmap = (surf->dlightframe == r_framecount);
R_JoinAreas(&current, &best);
}
if (!gl_config.lightmapcopies && !affected_lightmap)
{ {
continue; continue;
} }
if (gl_config.lightmapcopies)
{
// add all the changes that have happened in the last few frames,
// at least just for consistency between them...
qboolean apply_changes = affected_lightmap;
current = best; // save state for next frames... +
for (int k = 0; k < MAX_LIGHTMAP_COPIES; k++)
{
if (altered[k][i])
{
apply_changes = true;
R_JoinAreas(&lmchange[k][i], &best);
}
}
altered[cc][i] = affected_lightmap;
if (affected_lightmap)
{
lmchange[cc][i] = current; // + ...here
}
if (!apply_changes)
{
continue;
}
}
#ifndef YQ2_GL1_GLES #ifndef YQ2_GL1_GLES
if (!pixelstore_set) if (!pixelstore_set)
{ {
@ -687,16 +718,20 @@ R_RegenAllLightmaps()
base = gl_lms.lightmap_buffer[i]; base = gl_lms.lightmap_buffer[i];
#ifdef YQ2_GL1_GLES #ifdef YQ2_GL1_GLES
base += (bt * gl_state.block_width) * LIGHTMAP_BYTES; base += (best.top * gl_state.block_width) * LIGHTMAP_BYTES;
R_Bind(gl_state.lightmap_textures + i); R_Bind(gl_state.lightmap_textures + i + lmtex);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, bt, gl_state.block_width, bb - bt,
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, best.top,
gl_state.block_width, best.bottom - best.top,
GL_LIGHTMAP_FORMAT, GL_UNSIGNED_BYTE, base); GL_LIGHTMAP_FORMAT, GL_UNSIGNED_BYTE, base);
#else #else
base += (bt * gl_state.block_width + bl) * LIGHTMAP_BYTES; base += (best.top * gl_state.block_width + best.left) * LIGHTMAP_BYTES;
R_Bind(gl_state.lightmap_textures + i); R_Bind(gl_state.lightmap_textures + i + lmtex);
glTexSubImage2D(GL_TEXTURE_2D, 0, bl, bt, br - bl, bb - bt,
glTexSubImage2D(GL_TEXTURE_2D, 0, best.left, best.top,
best.right - best.left, best.bottom - best.top,
GL_LIGHTMAP_FORMAT, GL_UNSIGNED_BYTE, base); GL_LIGHTMAP_FORMAT, GL_UNSIGNED_BYTE, base);
#endif #endif
} }

View file

@ -50,9 +50,10 @@
#endif #endif
#define MAX_LIGHTMAPS 128 #define MAX_LIGHTMAPS 128
#define MAX_LIGHTMAP_COPIES 3 // Meant for tile / deferred rendering platforms
#define MAX_SCRAPS 1 #define MAX_SCRAPS 1
#define TEXNUM_LIGHTMAPS 1024 #define TEXNUM_LIGHTMAPS 1024
#define TEXNUM_SCRAPS (TEXNUM_LIGHTMAPS + MAX_LIGHTMAPS) #define TEXNUM_SCRAPS (TEXNUM_LIGHTMAPS + MAX_LIGHTMAPS * MAX_LIGHTMAP_COPIES)
#define TEXNUM_IMAGES (TEXNUM_SCRAPS + MAX_SCRAPS) #define TEXNUM_IMAGES (TEXNUM_SCRAPS + MAX_SCRAPS)
#define MAX_GLTEXTURES 1024 #define MAX_GLTEXTURES 1024
#define BLOCK_WIDTH 128 // default values; now defined in glstate_t #define BLOCK_WIDTH 128 // default values; now defined in glstate_t
@ -429,6 +430,7 @@ typedef struct
qboolean palettedtexture; qboolean palettedtexture;
qboolean pointparameters; qboolean pointparameters;
qboolean multitexture; qboolean multitexture;
qboolean lightmapcopies; // many copies of same lightmap, for embedded
qboolean discardfb; qboolean discardfb;
// ---- // ----