diff --git a/doc/040_cvarlist.md b/doc/040_cvarlist.md index 9c1ef273..988eedda 100644 --- a/doc/040_cvarlist.md +++ b/doc/040_cvarlist.md @@ -488,6 +488,12 @@ Set `0` by default. value, at least `1.0` - default is `2.0`. Applied when textures are loaded, so it needs a `vid_restart`. +* **gl1_multitexture**: Enables (`1`) the blending of color and light + textures on a single drawing pass; disabling this (`0`) does one pass + for color and another for light. Default is `2`, which also enables + texture combine mode (`GL_ARB_texture_env_combine`) when supported. + Requires a `vid_restart` when changed. + * **gl1_overbrightbits**: Enables overbright bits, brightness scaling of lightmaps and models. Higher values make shadows less dark. Possible values are `0` (no overbright bits), `1` (more correct lighting for diff --git a/src/client/refresh/gl1/gl1_lightmap.c b/src/client/refresh/gl1/gl1_lightmap.c index 551eb9a3..f75a225d 100644 --- a/src/client/refresh/gl1/gl1_lightmap.c +++ b/src/client/refresh/gl1/gl1_lightmap.c @@ -28,25 +28,56 @@ extern gllightmapstate_t gl_lms; +void +LM_FreeLightmapBuffers(void) +{ + for (int i=0; iflags & (SURF_DRAWSKY | SURF_DRAWTURB)) @@ -256,8 +287,9 @@ LM_CreateSurfaceLightmap(msurface_t *surf) } surf->lightmaptexturenum = gl_lms.current_lightmap_texture; + buffer = (gl_config.multitexture)? surf->lightmaptexturenum : 0; - base = gl_lms.lightmap_buffer; + base = gl_lms.lightmap_buffer[buffer]; base += (surf->light_t * BLOCK_WIDTH + surf->light_s) * LIGHTMAP_BYTES; R_SetCacheState(surf, &r_newrefdef); @@ -284,13 +316,10 @@ void LM_BeginBuildingLightmaps(model_t *m) { static lightstyle_t lightstyles[MAX_LIGHTSTYLES]; - int i, size; - byte *dummy; - - size = BLOCK_WIDTH * BLOCK_HEIGHT * LIGHTMAP_BYTES; - dummy = R_GetTemporaryLMBuffer(size); + int i; memset(gl_lms.allocated, 0, sizeof(gl_lms.allocated)); + LM_FreeLightmapBuffers(); r_framecount = 1; /* no dlightcache */ @@ -314,13 +343,22 @@ LM_BeginBuildingLightmaps(model_t *m) gl_lms.current_lightmap_texture = 1; + if (gl_config.multitexture) + { + LM_AllocLightmapBuffer(gl_lms.current_lightmap_texture, false); + return; + } + + // dynamic lightmap for classic rendering path (no multitexture) + LM_AllocLightmapBuffer(0, true); + /* initialize the dynamic lightmap texture */ R_Bind(gl_state.lightmap_textures + 0); 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_LIGHTMAP_FORMAT, BLOCK_WIDTH, BLOCK_HEIGHT, 0, GL_LIGHTMAP_FORMAT, - GL_UNSIGNED_BYTE, dummy); + GL_UNSIGNED_BYTE, gl_lms.lightmap_buffer[0]); } void diff --git a/src/client/refresh/gl1/gl1_main.c b/src/client/refresh/gl1/gl1_main.c index d46c7e5b..0e5ad78c 100644 --- a/src/client/refresh/gl1/gl1_main.c +++ b/src/client/refresh/gl1/gl1_main.c @@ -145,6 +145,8 @@ cvar_t *gl1_stereo_convergence; refimport_t ri; +void LM_FreeLightmapBuffers(void); + void R_RotateForEntity(entity_t *e) { @@ -165,6 +167,7 @@ R_DrawSpriteModel(entity_t *currententity, const model_t *currentmodel) vec3_t point[4]; float *up, *right; + R_EnableMultitexture(false); /* don't even bother culling, because it's just a single polygon without a surface cache */ psprite = (dsprite_t *)currentmodel->extradata; @@ -263,6 +266,7 @@ R_DrawNullModel(entity_t *currententity) shadelight, r_modulate->value, lightspot); } + R_EnableMultitexture(false); glPushMatrix(); R_RotateForEntity(currententity); @@ -1669,6 +1673,7 @@ RI_Shutdown(void) ri.Cmd_RemoveCommand("imagelist"); ri.Cmd_RemoveCommand("gl_strings"); + LM_FreeLightmapBuffers(); Mod_FreeAll(); R_ShutdownImages(); @@ -1926,6 +1931,7 @@ R_DrawBeam(entity_t *e) VectorAdd(start_points[i], direction, end_points[i]); } + R_EnableMultitexture(false); glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); glDepthMask(GL_FALSE); diff --git a/src/client/refresh/gl1/gl1_mesh.c b/src/client/refresh/gl1/gl1_mesh.c index 6d1c43eb..1dc8476b 100644 --- a/src/client/refresh/gl1/gl1_mesh.c +++ b/src/client/refresh/gl1/gl1_mesh.c @@ -440,6 +440,7 @@ R_DrawAliasModel(entity_t *currententity, const model_t *currentmodel) } } + R_EnableMultitexture(false); paliashdr = (dmdx_t *)currentmodel->extradata; /* get lighting information */ diff --git a/src/client/refresh/gl1/gl1_surf.c b/src/client/refresh/gl1/gl1_surf.c index 94ce3633..72473f60 100644 --- a/src/client/refresh/gl1/gl1_surf.c +++ b/src/client/refresh/gl1/gl1_surf.c @@ -225,13 +225,8 @@ R_BlendLightmaps(const model_t *currentmodel) int i; msurface_t *surf; - /* don't bother if we're set to fullbright */ - if (r_fullbright->value) - { - return; - } - - if (!r_worldmodel->lightdata) + /* don't bother if we're set to fullbright or multitexture is enabled */ + if (gl_config.multitexture || r_fullbright->value || !r_worldmodel->lightdata) { return; } @@ -319,7 +314,7 @@ R_BlendLightmaps(const model_t *currentmodel) if (LM_AllocBlock(smax, tmax, &surf->dlight_s, &surf->dlight_t)) { - base = gl_lms.lightmap_buffer; + base = gl_lms.lightmap_buffer[0]; base += (surf->dlight_t * BLOCK_WIDTH + surf->dlight_s) * LIGHTMAP_BYTES; @@ -366,7 +361,7 @@ R_BlendLightmaps(const model_t *currentmodel) __func__, smax, tmax); } - base = gl_lms.lightmap_buffer; + base = gl_lms.lightmap_buffer[0]; base += (surf->dlight_t * BLOCK_WIDTH + surf->dlight_s) * LIGHTMAP_BYTES; @@ -457,6 +452,11 @@ R_RenderBrushPoly(const entity_t *currententity, msurface_t *fa) R_DrawGLPoly(fa->polys); } + if (gl_config.multitexture) + { + return; // lighting already done at this point for mtex + } + /* check for lightmap modification */ for (maps = 0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++) { @@ -583,6 +583,245 @@ 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 || !r_dynamic->value || + (surf->texinfo->flags & (SURF_SKY | SURF_TRANSPARENT | 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, &r_newrefdef); + } + surf->dirty_lightmap = (surf->dlightframe == r_framecount); +} + +static void +R_RenderLightmappedPoly(const entity_t *currententity, msurface_t *surf) +{ + int i; + int nv = surf->polys->numverts; + mvtx_t* vert; + + R_MBind(GL_TEXTURE1, gl_state.lightmap_textures + surf->lightmaptexturenum); + + // Apply overbrightbits to TMU 1 (lightmap) + if (gl1_overbrightbits->value) + { + R_TexEnv(GL_COMBINE); + glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE, gl1_overbrightbits->value); + } + + c_brush_polys++; + vert = surf->polys->verts; + + if (surf->texinfo->flags & SURF_FLOWING) + { + float sscroll, tscroll; + + R_FlowingScroll(&r_newrefdef, surf->texinfo->flags, &sscroll, &tscroll); + + YQ2_VLA(GLfloat, tex, 4 * nv); + unsigned int index_tex = 0; + + for (i = 0; i < nv; i++, vert++) + { + tex[index_tex++] = vert->texCoord[0] + sscroll; + tex[index_tex++] = vert->texCoord[1] + tscroll; + tex[index_tex++] = vert->lmTexCoord[0]; + tex[index_tex++] = vert->lmTexCoord[1]; + } + + // Polygon + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, sizeof(mvtx_t), surf->polys->verts->pos); + + // Texture + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + qglClientActiveTexture(GL_TEXTURE0); + glTexCoordPointer(2, GL_FLOAT, 4 * sizeof(GLfloat), tex); + + // Lightmap + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + qglClientActiveTexture(GL_TEXTURE1); + glTexCoordPointer(2, GL_FLOAT, 4 * sizeof(GLfloat), tex + 2); + + // Draw the thing + glDrawArrays(GL_TRIANGLE_FAN, 0, nv); + + YQ2_VLAFREE(tex); + } + else + { + // Polygon + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, sizeof(mvtx_t), vert->pos); + + // Texture + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + qglClientActiveTexture(GL_TEXTURE0); + glTexCoordPointer(2, GL_FLOAT, sizeof(mvtx_t), vert->texCoord); + + // Lightmap + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + qglClientActiveTexture(GL_TEXTURE1); + glTexCoordPointer(2, GL_FLOAT, sizeof(mvtx_t), vert->lmTexCoord); + + // Draw it + glDrawArrays(GL_TRIANGLE_FAN, 0, nv); + } + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); +} + +static void +R_UploadDynamicLights(msurface_t *surf) +{ + int map, smax, tmax, size; + byte *temp; + + if ( !gl_config.multitexture || !R_HasDynamicLights(surf, &map) ) + { + return; + } + + smax = (surf->extents[0] >> surf->lmshift) + 1; + tmax = (surf->extents[1] >> surf->lmshift) + 1; + + size = smax * tmax * LIGHTMAP_BYTES; + temp = R_GetTemporaryLMBuffer(size); + + R_BuildLightMap(surf, (void *) temp, smax * LIGHTMAP_BYTES, + &r_newrefdef, r_modulate->value, r_framecount); + R_UpdateSurfCache(surf, map); + + R_Bind(gl_state.lightmap_textures + surf->lightmaptexturenum); + glTexSubImage2D(GL_TEXTURE_2D, 0, surf->light_s, surf->light_t, smax, + tmax, GL_LIGHTMAP_FORMAT, GL_UNSIGNED_BYTE, temp); +} + +/* 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; + qboolean affected_lightmap; + msurface_t *surf; + byte *base; + + if ( !gl_config.multitexture ) + { + return; + } + + glPixelStorei(GL_UNPACK_ROW_LENGTH, BLOCK_WIDTH); + + for (i = 1; i < MAX_LIGHTMAPS; i++) + { + if (!gl_lms.lightmap_surfaces[i] || !gl_lms.lightmap_buffer[i]) + { + continue; + } + + affected_lightmap = false; + bt = BLOCK_HEIGHT; + bl = BLOCK_WIDTH; + bb = br = 0; + + for (surf = gl_lms.lightmap_surfaces[i]; + surf != 0; + surf = surf->lightmapchain) + { + if ( !R_HasDynamicLights(surf, &map) ) + { + continue; + } + + affected_lightmap = true; + smax = (surf->extents[0] >> surf->lmshift) + 1; + tmax = (surf->extents[1] >> surf->lmshift) + 1; + + left = surf->light_s; + right = surf->light_s + smax; + top = surf->light_t; + bottom = surf->light_t + tmax; + + base = gl_lms.lightmap_buffer[i]; + base += (top * BLOCK_WIDTH + left) * LIGHTMAP_BYTES; + + R_BuildLightMap(surf, base, BLOCK_WIDTH * LIGHTMAP_BYTES, + &r_newrefdef, r_modulate->value, r_framecount); + R_UpdateSurfCache(surf, map); + + if (left < bl) + { + bl = left; + } + if (right > br) + { + br = right; + } + if (top < bt) + { + bt = top; + } + if (bottom > bb) + { + bb = bottom; + } + } + + if (!affected_lightmap) + { + continue; + } + + base = gl_lms.lightmap_buffer[i]; + base += (bt * BLOCK_WIDTH + bl) * LIGHTMAP_BYTES; + + // upload changes + 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); + } + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); +} + static void R_DrawTextureChains(const entity_t *currententity) { @@ -592,32 +831,78 @@ R_DrawTextureChains(const entity_t *currententity) c_visible_textures = 0; - for (i = 0, image = gltextures; i < numgltextures; i++, image++) + if (!gl_config.multitexture) // classic path { - if (!image->registration_sequence) + for (i = 0, image = gltextures; i < numgltextures; i++, image++) { - continue; + if (!image->registration_sequence) + { + continue; + } + + s = image->texturechain; + + if (!s) + { + continue; + } + + c_visible_textures++; + + for ( ; s; s = s->texturechain) + { + R_Bind(image->texnum); // may reset because of dynamic lighting in R_RenderBrushPoly + R_RenderBrushPoly(currententity, s); + } + + image->texturechain = NULL; } - - s = image->texturechain; - - if (!s) - { - continue; - } - - c_visible_textures++; - - for ( ; s; s = s->texturechain) - { - R_Bind(image->texnum); // may reset because of dynamic lighting in R_RenderBrushPoly - R_RenderBrushPoly(currententity, s); - } - - image->texturechain = NULL; } + else // multitexture + { + R_EnableMultitexture(true); - R_TexEnv(GL_REPLACE); + for (i = 0, image = gltextures; i < numgltextures; i++, image++) + { + if (!image->registration_sequence || !image->texturechain) + { + continue; + } + + R_MBind(GL_TEXTURE0, image->texnum); // setting it only once + c_visible_textures++; + + for (s = image->texturechain; s; s = s->texturechain) + { + if (!(s->flags & SURF_DRAWTURB)) + { + R_RenderLightmappedPoly(currententity, s); + } + } + } + + R_EnableMultitexture(false); + + for (i = 0, image = gltextures; i < numgltextures; i++, image++) + { + if (!image->registration_sequence || !image->texturechain) + { + continue; + } + + R_Bind(image->texnum); // no danger of resetting in R_RenderBrushPoly + + for (s = image->texturechain; s; s = s->texturechain) + { + if (s->flags & SURF_DRAWTURB) + { + R_RenderBrushPoly(currententity, s); + } + } + + image->texturechain = NULL; + } + } } static void @@ -667,8 +952,20 @@ R_DrawInlineBModel(const entity_t *currententity, const model_t *currentmodel) else { image = R_TextureAnimation(currententity, psurf->texinfo); - R_Bind(image->texnum); - R_RenderBrushPoly(currententity, psurf); + + if (gl_config.multitexture && !(psurf->flags & SURF_DRAWTURB)) + { + R_UploadDynamicLights(psurf); + R_EnableMultitexture(true); + R_MBind(GL_TEXTURE0, image->texnum); + R_RenderLightmappedPoly(currententity, psurf); + } + else + { + R_EnableMultitexture(false); + R_Bind(image->texnum); + R_RenderBrushPoly(currententity, psurf); + } } } } @@ -895,6 +1192,12 @@ R_RecursiveWorldNode(entity_t *currententity, mnode_t *node) image = R_TextureAnimation(currententity, surf->texinfo); surf->texturechain = image->texturechain; image->texturechain = surf; + + if (gl_config.multitexture && !(surf->texinfo->flags & SURF_WARP)) // needed for R_RegenAllLightmaps() + { + surf->lightmapchain = gl_lms.lightmap_surfaces[surf->lightmaptexturenum]; + gl_lms.lightmap_surfaces[surf->lightmaptexturenum] = surf; + } } } @@ -930,6 +1233,7 @@ R_DrawWorld(void) RE_ClearSkyBox(); R_RecursiveWorldNode(&ent, r_worldmodel->nodes); + R_RegenAllLightmaps(); R_DrawTextureChains(&ent); R_BlendLightmaps(r_worldmodel); R_DrawSkyBox(); diff --git a/src/client/refresh/gl1/header/local.h b/src/client/refresh/gl1/header/local.h index 473e3e18..aad5130c 100644 --- a/src/client/refresh/gl1/header/local.h +++ b/src/client/refresh/gl1/header/local.h @@ -377,7 +377,7 @@ typedef struct /* the lightmap texture data needs to be kept in main memory so texsubimage can update properly */ - byte lightmap_buffer[LIGHTMAP_BYTES * BLOCK_WIDTH * BLOCK_HEIGHT]; + byte *lightmap_buffer[MAX_LIGHTMAPS]; } gllightmapstate_t; void LM_CreateLightmapsPoligon(model_t *currentmodel, msurface_t *fa); diff --git a/src/client/refresh/ref_shared.h b/src/client/refresh/ref_shared.h index 66f81e84..cc5f9a60 100644 --- a/src/client/refresh/ref_shared.h +++ b/src/client/refresh/ref_shared.h @@ -268,6 +268,7 @@ typedef struct msurface_s /* lighting info */ int dlightframe; int dlightbits; + qboolean dirty_lightmap; // lightmap has dynamic lights from previous frame (mtex only) int lightmaptexturenum; byte styles[MAXLIGHTMAPS];