diff --git a/doc/040_cvarlist.md b/doc/040_cvarlist.md index 1dc10f2c..4acba554 100644 --- a/doc/040_cvarlist.md +++ b/doc/040_cvarlist.md @@ -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 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), send a hint to discard framebuffers after finishing a frame. Useful for GPUs that attempt to reuse them, something Quake 2 doesn't do. diff --git a/src/client/refresh/gl1/gl1_buffer.c b/src/client/refresh/gl1/gl1_buffer.c index b09f3f8d..edbd2e44 100644 --- a/src/client/refresh/gl1/gl1_buffer.c +++ b/src/client/refresh/gl1/gl1_buffer.c @@ -32,6 +32,7 @@ #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 +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 @@ -181,7 +182,13 @@ R_ApplyGLBuffer(void) if (mtex) { // 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) { diff --git a/src/client/refresh/gl1/gl1_lightmap.c b/src/client/refresh/gl1/gl1_lightmap.c index 4857d6c3..477c327c 100644 --- a/src/client/refresh/gl1/gl1_lightmap.c +++ b/src/client/refresh/gl1/gl1_lightmap.c @@ -87,7 +87,7 @@ LM_UploadBlock(qboolean dynamic) { const int texture = (dynamic)? 0 : gl_lms.current_lightmap_texture; 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); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); @@ -95,8 +95,6 @@ LM_UploadBlock(qboolean dynamic) if (dynamic) { - int i; - for (i = 0; i < gl_state.block_width; i++) { if (gl_lms.allocated[i] > height) @@ -117,6 +115,21 @@ LM_UploadBlock(qboolean dynamic) 0, GL_LIGHTMAP_FORMAT, GL_UNSIGNED_BYTE, 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) { ri.Sys_Error(ERR_DROP, diff --git a/src/client/refresh/gl1/gl1_main.c b/src/client/refresh/gl1/gl1_main.c index 0c8203b8..75e62fed 100644 --- a/src/client/refresh/gl1/gl1_main.c +++ b/src/client/refresh/gl1/gl1_main.c @@ -91,6 +91,7 @@ cvar_t *gl1_particle_square; cvar_t *gl1_palettedtexture; cvar_t *gl1_pointparameters; cvar_t *gl1_multitexture; +cvar_t *gl1_lightmapcopies; cvar_t *gl1_discardfb; cvar_t *gl_drawbuffer; @@ -1171,6 +1172,12 @@ RI_RenderFrame(refdef_t *fd) R_SetGL2D(); } +#ifdef YQ2_GL1_GLES +#define DEFAULT_LMCOPIES "1" +#else +#define DEFAULT_LMCOPIES "0" +#endif + void R_Register(void) { @@ -1225,6 +1232,7 @@ R_Register(void) gl1_palettedtexture = ri.Cvar_Get("r_palettedtextures", "0", CVAR_ARCHIVE); gl1_pointparameters = ri.Cvar_Get("gl1_pointparameters", "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 gl1_discardfb = ri.Cvar_Get("gl1_discardfb", "1", CVAR_ARCHIVE); #endif @@ -1265,6 +1273,8 @@ R_Register(void) ri.Cmd_AddCommand("gl_strings", R_Strings); } +#undef DEFAULT_LMCOPIES + /* * 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" * 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 diff --git a/src/client/refresh/gl1/gl1_surf.c b/src/client/refresh/gl1/gl1_surf.c index 10e0f219..4eb5f23b 100644 --- a/src/client/refresh/gl1/gl1_surf.c +++ b/src/client/refresh/gl1/gl1_surf.c @@ -28,12 +28,18 @@ #include "header/local.h" +typedef struct +{ + int top, bottom, left, right; +} lmrect_t; + int c_visible_lightmaps; int c_visible_textures; static vec3_t modelorg; /* relative to viewpoint */ msurface_t *r_alpha_surfaces; gllightmapstate_t gl_lms; +extern int cur_lm_copy; void LM_InitBlock(void); void LM_UploadBlock(qboolean dynamic); @@ -519,52 +525,6 @@ R_DrawAlphaSurfaces(void) 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 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) */ static void 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; byte *base; qboolean affected_lightmap; @@ -611,11 +597,19 @@ R_RegenAllLightmaps() qboolean pixelstore_set = false; #endif - if ( !gl_config.multitexture ) + if ( !gl_config.multitexture || r_fullbright->value || !gl1_dynamic->value ) { 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++) { if (!gl_lms.lightmap_surfaces[i] || !gl_lms.lightmap_buffer[i]) @@ -624,57 +618,94 @@ R_RegenAllLightmaps() } affected_lightmap = false; - bt = gl_state.block_height; - bl = gl_state.block_width; - bb = br = 0; + best.top = gl_state.block_height; + best.left = gl_state.block_width; + best.bottom = best.right = 0; for (surf = gl_lms.lightmap_surfaces[i]; surf != 0; surf = surf->lightmapchain) { - if ( !R_HasDynamicLights(surf, &map) ) + if (surf->texinfo->flags & (SURF_SKY | SURF_TRANS33 | SURF_TRANS66 | SURF_WARP)) { 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; smax = (surf->extents[0] >> 4) + 1; tmax = (surf->extents[1] >> 4) + 1; - left = surf->light_s; - right = surf->light_s + smax; - top = surf->light_t; - bottom = surf->light_t + tmax; + current.left = surf->light_s; + current.right = surf->light_s + smax; + current.top = surf->light_t; + current.bottom = surf->light_t + tmax; 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_UpdateSurfCache(surf, map); - if (left < bl) + if ( ((surf->styles[map] >= 32) || (surf->styles[map] == 0)) + && (surf->dlightframe != r_framecount) ) { - bl = left; - } - if (right > br) - { - br = right; - } - if (top < bt) - { - bt = top; - } - if (bottom > bb) - { - bb = bottom; + R_SetCacheState(surf); } + + surf->dirty_lightmap = (surf->dlightframe == r_framecount); + R_JoinAreas(¤t, &best); } - if (!affected_lightmap) + if (!gl_config.lightmapcopies && !affected_lightmap) { 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 if (!pixelstore_set) { @@ -687,17 +718,21 @@ R_RegenAllLightmaps() base = gl_lms.lightmap_buffer[i]; #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); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, bt, gl_state.block_width, bb - bt, - GL_LIGHTMAP_FORMAT, GL_UNSIGNED_BYTE, base); + R_Bind(gl_state.lightmap_textures + i + lmtex); + + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, best.top, + gl_state.block_width, best.bottom - best.top, + GL_LIGHTMAP_FORMAT, GL_UNSIGNED_BYTE, base); #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); - glTexSubImage2D(GL_TEXTURE_2D, 0, bl, bt, br - bl, bb - bt, - GL_LIGHTMAP_FORMAT, GL_UNSIGNED_BYTE, base); + R_Bind(gl_state.lightmap_textures + i + lmtex); + + glTexSubImage2D(GL_TEXTURE_2D, 0, best.left, best.top, + best.right - best.left, best.bottom - best.top, + GL_LIGHTMAP_FORMAT, GL_UNSIGNED_BYTE, base); #endif } diff --git a/src/client/refresh/gl1/header/local.h b/src/client/refresh/gl1/header/local.h index 8514bffd..64394cbc 100644 --- a/src/client/refresh/gl1/header/local.h +++ b/src/client/refresh/gl1/header/local.h @@ -50,9 +50,10 @@ #endif #define MAX_LIGHTMAPS 128 +#define MAX_LIGHTMAP_COPIES 3 // Meant for tile / deferred rendering platforms #define MAX_SCRAPS 1 #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 MAX_GLTEXTURES 1024 #define BLOCK_WIDTH 128 // default values; now defined in glstate_t @@ -429,6 +430,7 @@ typedef struct qboolean palettedtexture; qboolean pointparameters; qboolean multitexture; + qboolean lightmapcopies; // many copies of same lightmap, for embedded qboolean discardfb; // ----