// //--------------------------------------------------------------------------- // // Copyright(C) 2000-2016 Christoph Oelckers // All rights reserved. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser 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 Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see http://www.gnu.org/licenses/ // //-------------------------------------------------------------------------- // /* ** gl_flat.cpp ** Flat processing ** */ #include "a_sharedglobal.h" #include "r_defs.h" #include "r_sky.h" #include "r_utility.h" #include "doomstat.h" #include "d_player.h" #include "g_levellocals.h" #include "actorinlines.h" #include "p_lnspec.h" #include "r_data/matrix.h" #include "hwrenderer/dynlights/hw_dynlightdata.h" #include "hwrenderer/utility/hw_cvars.h" #include "hwrenderer/utility/hw_clock.h" #include "hwrenderer/utility/hw_lighting.h" #include "hwrenderer/textures/hw_material.h" #include "hwrenderer/scene/hw_drawinfo.h" #include "hwrenderer/data/flatvertices.h" #include "hw_drawstructs.h" #include "hw_renderstate.h" #ifdef _DEBUG CVAR(Int, gl_breaksec, -1, 0) #endif //========================================================================== // // Sets the texture matrix according to the plane's texture positioning // information // //========================================================================== bool hw_SetPlaneTextureRotation(const GLSectorPlane * secplane, FMaterial * gltexture, VSMatrix &dest) { // only manipulate the texture matrix if needed. if (!secplane->Offs.isZero() || secplane->Scale.X != 1. || secplane->Scale.Y != 1 || secplane->Angle != 0 || gltexture->TextureWidth() != 64 || gltexture->TextureHeight() != 64) { float uoffs = secplane->Offs.X / gltexture->TextureWidth(); float voffs = secplane->Offs.Y / gltexture->TextureHeight(); float xscale1 = secplane->Scale.X; float yscale1 = secplane->Scale.Y; if (gltexture->tex->bHasCanvas) { yscale1 = 0 - yscale1; } float angle = -secplane->Angle; float xscale2 = 64.f / gltexture->TextureWidth(); float yscale2 = 64.f / gltexture->TextureHeight(); dest.loadIdentity(); dest.scale(xscale1, yscale1, 1.0f); dest.translate(uoffs, voffs, 0.0f); dest.scale(xscale2, yscale2, 1.0f); dest.rotate(angle, 0.0f, 0.0f, 1.0f); return true; } return false; } //========================================================================== // // special handling for skyboxes which need texture clamping. // This will find the bounding rectangle of the sector and just // draw one single polygon filling that rectangle with a clamped // texture. // //========================================================================== void GLFlat::CreateSkyboxVertices(FFlatVertex *vert) { float minx = FLT_MAX, miny = FLT_MAX; float maxx = -FLT_MAX, maxy = -FLT_MAX; for (auto ln : sector->Lines) { float x = ln->v1->fX(); float y = ln->v1->fY(); if (x < minx) minx = x; if (y < miny) miny = y; if (x > maxx) maxx = x; if (y > maxy) maxy = y; x = ln->v2->fX(); y = ln->v2->fY(); if (x < minx) minx = x; if (y < miny) miny = y; if (x > maxx) maxx = x; if (y > maxy) maxy = y; } static float uvals[] = { 0, 0, 1, 1 }; static float vvals[] = { 1, 0, 0, 1 }; int rot = -xs_FloorToInt(plane.Angle / 90.f); vert[0].Set(minx, z, miny, uvals[rot & 3], vvals[rot & 3]); vert[1].Set(minx, z, maxy, uvals[(rot + 1) & 3], vvals[(rot + 1) & 3]); vert[2].Set(maxx, z, maxy, uvals[(rot + 2) & 3], vvals[(rot + 2) & 3]); vert[3].Set(maxx, z, miny, uvals[(rot + 3) & 3], vvals[(rot + 3) & 3]); } //========================================================================== // // // //========================================================================== void GLFlat::SetupLights(HWDrawInfo *di, FLightNode * node, FDynLightData &lightdata, int portalgroup) { Plane p; lightdata.Clear(); if (renderstyle == STYLE_Add && !level.lightadditivesurfaces) { dynlightindex = -1; return; // no lights on additively blended surfaces. } while (node) { ADynamicLight * light = node->lightsource; if (light->flags2&MF2_DORMANT) { node = node->nextLight; continue; } iter_dlightf++; // we must do the side check here because gl_GetLight needs the correct plane orientation // which we don't have for Legacy-style 3D-floors double planeh = plane.plane.ZatPoint(light); if ((planehZ() && ceiling) || (planeh>light->Z() && !ceiling)) { node = node->nextLight; continue; } p.Set(plane.plane.Normal(), plane.plane.fD()); lightdata.GetLight(portalgroup, p, light, false); node = node->nextLight; } dynlightindex = di->UploadLights(lightdata); } //========================================================================== // // // //========================================================================== void GLFlat::DrawSubsectors(HWDrawInfo *di, FRenderState &state) { auto vcount = sector->ibocount; if (screen->BuffersArePersistent()) { SetupLights(di, sector->lighthead, lightdata, sector->PortalGroup); } state.SetLightIndex(dynlightindex); if (vcount > 0 && !di->ClipLineShouldBeActive()) { di->DrawIndexed(DT_Triangles, state, iboindex, vcount); flatvertices += vcount; flatprimitives++; } else { int index = iboindex; for (int i = 0; i < sector->subsectorcount; i++) { subsector_t * sub = sector->subsectors[i]; if (sub->numlines <= 2) continue; if (di->ss_renderflags[sub->Index()] & renderflags) { di->DrawIndexed(DT_Triangles, state, index, (sub->numlines - 2) * 3, false); flatvertices += sub->numlines; flatprimitives++; } index += (sub->numlines - 2) * 3; } } if (!(renderflags&SSRF_RENDER3DPLANES)) { // Draw the subsectors assigned to it due to missing textures gl_subsectorrendernode * node = (renderflags&SSRF_RENDERFLOOR) ? di->GetOtherFloorPlanes(sector->sectornum) : di->GetOtherCeilingPlanes(sector->sectornum); while (node) { state.SetLightIndex(node->lightindex); auto num = node->sub->numlines; flatvertices += num; flatprimitives++; di->Draw(DT_TriangleFan, state, node->vertexindex, num); node = node->next; } // Flood gaps with the back side's ceiling/floor texture // This requires a stencil because the projected plane interferes with // the depth buffer gl_floodrendernode * fnode = (renderflags&SSRF_RENDERFLOOR) ? di->GetFloodFloorSegs(sector->sectornum) : di->GetFloodCeilingSegs(sector->sectornum); state.SetLightIndex(dynlightindex); while (fnode) { flatvertices += 12; flatprimitives += 3; // Push bleeding floor/ceiling textures back a little in the z-buffer // so they don't interfere with overlapping mid textures. state.SetDepthBias(1, 128); // Create stencil state.SetEffect(EFF_STENCIL); state.EnableTexture(false); di->SetStencil(0, SOP_Increment, SF_ColorMaskOff); di->Draw(DT_TriangleFan, state, fnode->vertexindex, 4); // Draw projected plane into stencil state.EnableTexture(true); state.SetEffect(EFF_NONE); di->SetStencil(1, SOP_Keep, SF_DepthMaskOff | SF_DepthTestOff); di->Draw(DT_TriangleFan, state, fnode->vertexindex + 4, 4); // clear stencil state.SetEffect(EFF_STENCIL); state.EnableTexture(false); di->SetStencil(1, SOP_Decrement, SF_ColorMaskOff | SF_DepthMaskOff | SF_DepthTestOff); di->Draw(DT_TriangleFan, state, fnode->vertexindex, 4); // restore old stencil op. state.EnableTexture(true); state.SetEffect(EFF_NONE); state.SetDepthBias(0, 0); di->SetStencil(0, SOP_Keep, SF_AllOn); fnode = fnode->next; } } } //========================================================================== // // // //========================================================================== void GLFlat::DrawFlat(HWDrawInfo *di, FRenderState &state, bool translucent) { int rel = getExtraLight(); state.SetNormal(plane.plane.Normal().X, plane.plane.Normal().Z, plane.plane.Normal().Y); state.SetColor(lightlevel, rel, di->isFullbrightScene(), Colormap, alpha); state.SetFog(lightlevel, rel, di->isFullbrightScene(), &Colormap, false); if (!gltexture || !gltexture->tex->isFullbright()) state.SetObjectColor(FlatColor | 0xff000000); if (!translucent) { if (sector->special != GLSector_Skybox) { state.SetMaterial(gltexture, CLAMP_NONE, 0, -1); state.SetPlaneTextureRotation(&plane, gltexture); DrawSubsectors(di, state); state.EnableTextureMatrix(false); } else { state.SetMaterial(gltexture, CLAMP_XY, 0, -1); state.SetLightIndex(dynlightindex); di->Draw(DT_TriangleFan, state, iboindex, 4); flatvertices += 4; flatprimitives++; } state.SetObjectColor(0xffffffff); } else { state.SetRenderStyle(renderstyle); if (!gltexture) { state.AlphaFunc(Alpha_GEqual, 0.f); state.EnableTexture(false); DrawSubsectors(di, state); state.EnableTexture(true); } else { if (!gltexture->tex->GetTranslucency()) state.AlphaFunc(Alpha_GEqual, gl_mask_threshold); else state.AlphaFunc(Alpha_GEqual, 0.f); state.SetMaterial(gltexture, CLAMP_NONE, 0, -1); state.SetPlaneTextureRotation(&plane, gltexture); DrawSubsectors(di, state); state.EnableTextureMatrix(false); } state.SetRenderStyle(DefaultRenderStyle()); state.SetObjectColor(0xffffffff); } } //========================================================================== // // GLFlat::PutFlat // // submit to the renderer // //========================================================================== inline void GLFlat::PutFlat(HWDrawInfo *di, bool fog) { if (di->isFullbrightScene()) { Colormap.Clear(); } else if (!screen->BuffersArePersistent()) { if (level.HasDynamicLights && gltexture != nullptr) { SetupLights(di, sector->lighthead, lightdata, sector->PortalGroup); } } di->AddFlat(this, fog); } //========================================================================== // // This draws one flat // The passed sector does not indicate the area which is rendered. // It is only used as source for the plane data. // The whichplane boolean indicates if the flat is a floor(false) or a ceiling(true) // //========================================================================== void GLFlat::Process(HWDrawInfo *di, sector_t * model, int whichplane, bool fog) { plane.GetFromSector(model, whichplane); if (whichplane != int(ceiling)) { // Flip the normal if the source plane has a different orientation than what we are about to render. plane.plane.FlipVert(); } if (!fog) { gltexture=FMaterial::ValidateTexture(plane.texture, false, true); if (!gltexture) return; if (gltexture->tex->isFullbright()) { Colormap.MakeWhite(); lightlevel=255; } } else { gltexture = NULL; lightlevel = abs(lightlevel); } z = plane.plane.ZatPoint(0.f, 0.f); if (sector->special == GLSector_Skybox) { auto vert = screen->mVertexData->AllocVertices(4); CreateSkyboxVertices(vert.first); iboindex = vert.second; } PutFlat(di, fog); rendered_flats++; } //========================================================================== // // Sets 3D floor info. Common code for all 4 cases // //========================================================================== void GLFlat::SetFrom3DFloor(F3DFloor *rover, bool top, bool underside) { F3DFloor::planeref & plane = top? rover->top : rover->bottom; // FF_FOG requires an inverted logic where to get the light from lightlist_t *light = P_GetPlaneLight(sector, plane.plane, underside); lightlevel = hw_ClampLight(*light->p_lightlevel); if (rover->flags & FF_FOG) { Colormap.LightColor = light->extra_colormap.FadeColor; FlatColor = 0xffffffff; } else { Colormap.CopyFrom3DLight(light); FlatColor = *plane.flatcolor; } alpha = rover->alpha/255.0f; renderstyle = rover->flags&FF_ADDITIVETRANS? STYLE_Add : STYLE_Translucent; iboindex = plane.vindex; } //========================================================================== // // Process a sector's flats for rendering // This function is only called once per sector. // Subsequent subsectors are just quickly added to the ss_renderflags array // //========================================================================== void GLFlat::ProcessSector(HWDrawInfo *di, sector_t * frontsector) { lightlist_t * light; FSectorPortal *port; #ifdef _DEBUG if (frontsector->sectornum == gl_breaksec) { int a = 0; } #endif // Get the real sector for this one. sector = &level.sectors[frontsector->sectornum]; extsector_t::xfloor &x = sector->e->XFloor; dynlightindex = -1; uint8_t &srf = di->sectorrenderflags[sector->sectornum]; const auto &vp = di->Viewpoint; // // // // do floors // // // if (frontsector->floorplane.ZatPoint(vp.Pos) <= vp.Pos.Z) { // process the original floor first. srf |= SSRF_RENDERFLOOR; lightlevel = hw_ClampLight(frontsector->GetFloorLight()); Colormap = frontsector->Colormap; FlatColor = frontsector->SpecialColors[sector_t::floor]; port = frontsector->ValidatePortal(sector_t::floor); if ((stack = (port != NULL))) { if (port->mType == PORTS_STACKEDSECTORTHING) { di->AddFloorStack(sector); // stacked sector things require visplane merging. } alpha = frontsector->GetAlpha(sector_t::floor); } else { alpha = 1.0f - frontsector->GetReflect(sector_t::floor); } if (alpha != 0.f && frontsector->GetTexture(sector_t::floor) != skyflatnum) { iboindex = frontsector->iboindex[sector_t::floor]; ceiling = false; renderflags = SSRF_RENDERFLOOR; if (x.ffloors.Size()) { light = P_GetPlaneLight(sector, &frontsector->floorplane, false); if ((!(sector->GetFlags(sector_t::floor)&PLANEF_ABSLIGHTING) || light->lightsource == NULL) && (light->p_lightlevel != &frontsector->lightlevel)) { lightlevel = hw_ClampLight(*light->p_lightlevel); } Colormap.CopyFrom3DLight(light); } renderstyle = STYLE_Translucent; Process(di, frontsector, sector_t::floor, false); } } // // // // do ceilings // // // if (frontsector->ceilingplane.ZatPoint(vp.Pos) >= vp.Pos.Z) { // process the original ceiling first. srf |= SSRF_RENDERCEILING; lightlevel = hw_ClampLight(frontsector->GetCeilingLight()); Colormap = frontsector->Colormap; FlatColor = frontsector->SpecialColors[sector_t::ceiling]; port = frontsector->ValidatePortal(sector_t::ceiling); if ((stack = (port != NULL))) { if (port->mType == PORTS_STACKEDSECTORTHING) { di->AddCeilingStack(sector); } alpha = frontsector->GetAlpha(sector_t::ceiling); } else { alpha = 1.0f - frontsector->GetReflect(sector_t::ceiling); } if (alpha != 0.f && frontsector->GetTexture(sector_t::ceiling) != skyflatnum) { iboindex = frontsector->iboindex[sector_t::ceiling]; ceiling = true; renderflags = SSRF_RENDERCEILING; if (x.ffloors.Size()) { light = P_GetPlaneLight(sector, §or->ceilingplane, true); if ((!(sector->GetFlags(sector_t::ceiling)&PLANEF_ABSLIGHTING)) && (light->p_lightlevel != &frontsector->lightlevel)) { lightlevel = hw_ClampLight(*light->p_lightlevel); } Colormap.CopyFrom3DLight(light); } renderstyle = STYLE_Translucent; Process(di, frontsector, sector_t::ceiling, false); } } // // // // do 3D floors // // // stack = false; if (x.ffloors.Size()) { player_t * player = players[consoleplayer].camera->player; renderflags = SSRF_RENDER3DPLANES; srf |= SSRF_RENDER3DPLANES; // 3d-floors must not overlap! double lastceilingheight = sector->CenterCeiling(); // render only in the range of the double lastfloorheight = sector->CenterFloor(); // current sector part (if applicable) F3DFloor * rover; int k; // floors are ordered now top to bottom so scanning the list for the best match // is no longer necessary. ceiling = true; Colormap = frontsector->Colormap; for (k = 0; k < (int)x.ffloors.Size(); k++) { rover = x.ffloors[k]; if ((rover->flags&(FF_EXISTS | FF_RENDERPLANES | FF_THISINSIDE)) == (FF_EXISTS | FF_RENDERPLANES)) { if (rover->flags&FF_FOG && di->isFullbrightScene()) continue; if (!rover->top.copied && rover->flags&(FF_INVERTPLANES | FF_BOTHPLANES)) { double ff_top = rover->top.plane->ZatPoint(sector->centerspot); if (ff_top < lastceilingheight) { if (vp.Pos.Z <= rover->top.plane->ZatPoint(vp.Pos)) { SetFrom3DFloor(rover, true, !!(rover->flags&FF_FOG)); Colormap.FadeColor = frontsector->Colormap.FadeColor; Process(di, rover->top.model, rover->top.isceiling, !!(rover->flags&FF_FOG)); } lastceilingheight = ff_top; } } if (!rover->bottom.copied && !(rover->flags&FF_INVERTPLANES)) { double ff_bottom = rover->bottom.plane->ZatPoint(sector->centerspot); if (ff_bottom < lastceilingheight) { if (vp.Pos.Z <= rover->bottom.plane->ZatPoint(vp.Pos)) { SetFrom3DFloor(rover, false, !(rover->flags&FF_FOG)); Colormap.FadeColor = frontsector->Colormap.FadeColor; Process(di, rover->bottom.model, rover->bottom.isceiling, !!(rover->flags&FF_FOG)); } lastceilingheight = ff_bottom; if (rover->alpha < 255) lastceilingheight += EQUAL_EPSILON; } } } } ceiling = false; for (k = x.ffloors.Size() - 1; k >= 0; k--) { rover = x.ffloors[k]; if ((rover->flags&(FF_EXISTS | FF_RENDERPLANES | FF_THISINSIDE)) == (FF_EXISTS | FF_RENDERPLANES)) { if (rover->flags&FF_FOG && di->isFullbrightScene()) continue; if (!rover->bottom.copied && rover->flags&(FF_INVERTPLANES | FF_BOTHPLANES)) { double ff_bottom = rover->bottom.plane->ZatPoint(sector->centerspot); if (ff_bottom > lastfloorheight || (rover->flags&FF_FIX)) { if (vp.Pos.Z >= rover->bottom.plane->ZatPoint(vp.Pos)) { SetFrom3DFloor(rover, false, !(rover->flags&FF_FOG)); Colormap.FadeColor = frontsector->Colormap.FadeColor; if (rover->flags&FF_FIX) { lightlevel = hw_ClampLight(rover->model->lightlevel); Colormap = rover->GetColormap(); } Process(di, rover->bottom.model, rover->bottom.isceiling, !!(rover->flags&FF_FOG)); } lastfloorheight = ff_bottom; } } if (!rover->top.copied && !(rover->flags&FF_INVERTPLANES)) { double ff_top = rover->top.plane->ZatPoint(sector->centerspot); if (ff_top > lastfloorheight) { if (vp.Pos.Z >= rover->top.plane->ZatPoint(vp.Pos)) { SetFrom3DFloor(rover, true, !!(rover->flags&FF_FOG)); Colormap.FadeColor = frontsector->Colormap.FadeColor; Process(di, rover->top.model, rover->top.isceiling, !!(rover->flags&FF_FOG)); } lastfloorheight = ff_top; if (rover->alpha < 255) lastfloorheight -= EQUAL_EPSILON; } } } } } }