qzdoom/src/gl/scene/gl_walls_draw.cpp
Christoph Oelckers 852c42bbd4 - fixed dynamic lights in legacy mode.
Since they mess around with the texture coordinates, these need to be backed up and restored afterward.
There was also an issue with the ValidNormal check that was suffering from imprecisions that cause walls to be skipped, so the check was removed because it was mostly pointless.
2018-04-29 19:00:17 +02:00

597 lines
18 KiB
C++

//
//---------------------------------------------------------------------------
//
// 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/
//
//--------------------------------------------------------------------------
//
#include "gl/system/gl_system.h"
#include "p_local.h"
#include "p_lnspec.h"
#include "a_sharedglobal.h"
#include "g_levellocals.h"
#include "actorinlines.h"
#include "hwrenderer/dynlights/hw_dynlightdata.h"
#include "gl/system/gl_interface.h"
#include "hwrenderer/utility/hw_cvars.h"
#include "gl/renderer/gl_lightdata.h"
#include "gl/renderer/gl_renderstate.h"
#include "gl/renderer/gl_renderer.h"
#include "gl/data/gl_vertexbuffer.h"
#include "gl/dynlights/gl_lightbuffer.h"
#include "gl/scene/gl_drawinfo.h"
#include "gl/scene/gl_portal.h"
#include "gl/scene/gl_scenedrawer.h"
#include "gl/renderer/gl_quaddrawer.h"
EXTERN_CVAR(Bool, gl_seamless)
// If we want to share the array to avoid constant allocations it needs to be thread local unless it'd be littered with expensive synchronization.
thread_local FDynLightData lightdata;
//==========================================================================
//
// General purpose wall rendering function
// everything goes through here
//
//==========================================================================
void FDrawInfo::RenderWall(GLWall *wall, int textured)
{
assert(wall->vertcount > 0);
gl_RenderState.Apply();
gl_RenderState.ApplyLightIndex(wall->dynlightindex);
GLRenderer->mVBO->RenderArray(GL_TRIANGLE_FAN, wall->vertindex, wall->vertcount);
vertexcount += wall->vertcount;
}
//==========================================================================
//
//
//
//==========================================================================
void FDrawInfo::RenderFogBoundary(GLWall *wall)
{
if (gl_fogmode && mDrawer->FixedColormap == 0)
{
if (!gl.legacyMode)
{
int rel = wall->rellight + getExtraLight();
mDrawer->SetFog(wall->lightlevel, rel, &wall->Colormap, false);
gl_RenderState.EnableDrawBuffers(1);
gl_RenderState.SetEffect(EFF_FOGBOUNDARY);
gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f);
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(-1.0f, -128.0f);
RenderWall(wall, GLWall::RWF_BLANK);
glPolygonOffset(0.0f, 0.0f);
glDisable(GL_POLYGON_OFFSET_FILL);
gl_RenderState.SetEffect(EFF_NONE);
gl_RenderState.EnableDrawBuffers(gl_RenderState.GetPassDrawBufferCount());
}
else
{
RenderFogBoundaryCompat(wall);
}
}
}
//==========================================================================
//
//
//
//==========================================================================
void FDrawInfo::RenderMirrorSurface(GLWall *wall)
{
if (!TexMan.mirrorTexture.isValid()) return;
if (!gl.legacyMode)
{
// we use texture coordinates and texture matrix to pass the normal stuff to the shader so that the default vertex buffer format can be used as is.
gl_RenderState.EnableTextureMatrix(true);
gl_RenderState.mTextureMatrix.computeNormalMatrix(gl_RenderState.mViewMatrix);
}
else
{
FVector3 v = wall->glseg.Normal();
glNormal3fv(&v[0]);
}
// Use sphere mapping for this
gl_RenderState.SetEffect(EFF_SPHEREMAP);
mDrawer->SetColor(wall->lightlevel, 0, wall->Colormap ,0.1f);
mDrawer->SetFog(wall->lightlevel, 0, &wall->Colormap, true);
gl_RenderState.BlendFunc(GL_SRC_ALPHA,GL_ONE);
gl_RenderState.AlphaFunc(GL_GREATER,0);
glDepthFunc(GL_LEQUAL);
FMaterial * pat=FMaterial::ValidateTexture(TexMan.mirrorTexture, false, false);
gl_RenderState.SetMaterial(pat, CLAMP_NONE, 0, -1, false);
wall->flags &= ~GLWall::GLWF_GLOW;
RenderWall(wall, GLWall::RWF_BLANK);
gl_RenderState.EnableTextureMatrix(false);
gl_RenderState.SetEffect(EFF_NONE);
// Restore the defaults for the translucent pass
gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_sprite_threshold);
glDepthFunc(GL_LESS);
// This is drawn in the translucent pass which is done after the decal pass
// As a result the decals have to be drawn here, right after the wall they are on,
// because the depth buffer won't get set by translucent items.
if (wall->seg->sidedef->AttachedDecals)
{
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(-1.0f, -128.0f);
glDepthMask(false);
gl_drawinfo->DrawDecalsForMirror(wall);
glDepthMask(true);
glPolygonOffset(0.0f, 0.0f);
glDisable(GL_POLYGON_OFFSET_FILL);
gl_RenderState.SetTextureMode(TM_MODULATE);
gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
}
//==========================================================================
//
//
//
//==========================================================================
void FDrawInfo::RenderTexturedWall(GLWall *wall, int rflags)
{
int tmode = gl_RenderState.GetTextureMode();
int rel = wall->rellight + getExtraLight();
if (wall->flags & GLWall::GLWF_GLOW)
{
gl_RenderState.EnableGlow(true);
gl_RenderState.SetGlowParams(wall->topglowcolor, wall->bottomglowcolor);
}
gl_RenderState.SetGlowPlanes(wall->topplane, wall->bottomplane);
gl_RenderState.SetMaterial(wall->gltexture, wall->flags & 3, 0, -1, false);
if (wall->type == RENDERWALL_M2SNF)
{
if (wall->flags & GLWall::GLWF_CLAMPY)
{
if (tmode == TM_MODULATE) gl_RenderState.SetTextureMode(TM_CLAMPY);
}
mDrawer->SetFog(255, 0, nullptr, false);
}
gl_RenderState.SetObjectColor(wall->seg->frontsector->SpecialColors[sector_t::walltop] | 0xff000000);
gl_RenderState.SetObjectColor2(wall->seg->frontsector->SpecialColors[sector_t::wallbottom] | 0xff000000);
float absalpha = fabsf(wall->alpha);
if (wall->lightlist == nullptr)
{
if (wall->type != RENDERWALL_M2SNF) mDrawer->SetFog(wall->lightlevel, rel, &wall->Colormap, wall->RenderStyle == STYLE_Add);
mDrawer->SetColor(wall->lightlevel, rel, wall->Colormap, absalpha);
RenderWall(wall, rflags);
}
else
{
gl_RenderState.EnableSplit(true);
for (unsigned i = 0; i < wall->lightlist->Size(); i++)
{
secplane_t &lowplane = i == (*wall->lightlist).Size() - 1 ? wall->bottomplane : (*wall->lightlist)[i + 1].plane;
// this must use the exact same calculation method as GLWall::Process etc.
float low1 = lowplane.ZatPoint(wall->vertexes[0]);
float low2 = lowplane.ZatPoint(wall->vertexes[1]);
if (low1 < wall->ztop[0] || low2 < wall->ztop[1])
{
int thisll = (*wall->lightlist)[i].caster != NULL ? hw_ClampLight(*(*wall->lightlist)[i].p_lightlevel) : wall->lightlevel;
FColormap thiscm;
thiscm.FadeColor = wall->Colormap.FadeColor;
thiscm.FogDensity = wall->Colormap.FogDensity;
thiscm.CopyFrom3DLight(&(*wall->lightlist)[i]);
mDrawer->SetColor(thisll, rel, thiscm, absalpha);
if (wall->type != RENDERWALL_M2SNF) mDrawer->SetFog(thisll, rel, &thiscm, wall->RenderStyle == STYLE_Add);
gl_RenderState.SetSplitPlanes((*wall->lightlist)[i].plane, lowplane);
RenderWall(wall, rflags);
}
if (low1 <= wall->zbottom[0] && low2 <= wall->zbottom[1]) break;
}
gl_RenderState.EnableSplit(false);
}
gl_RenderState.SetObjectColor(0xffffffff);
gl_RenderState.SetObjectColor2(0);
gl_RenderState.SetTextureMode(tmode);
gl_RenderState.EnableGlow(false);
}
//==========================================================================
//
//
//
//==========================================================================
void FDrawInfo::RenderTranslucentWall(GLWall *wall)
{
if (wall->gltexture)
{
if (!wall->gltexture->tex->GetTranslucency()) gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_threshold);
else gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f);
if (wall->RenderStyle == STYLE_Add) gl_RenderState.BlendFunc(GL_SRC_ALPHA,GL_ONE);
RenderTexturedWall(wall, GLWall::RWF_TEXTURED | GLWall::RWF_NOSPLIT);
if (wall->RenderStyle == STYLE_Add) gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
else
{
gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f);
mDrawer->SetColor(wall->lightlevel, 0, wall->Colormap, fabsf(wall->alpha));
mDrawer->SetFog(wall->lightlevel, 0, &wall->Colormap, wall->RenderStyle == STYLE_Add);
gl_RenderState.EnableTexture(false);
RenderWall(wall, GLWall::RWF_NOSPLIT);
gl_RenderState.EnableTexture(true);
}
}
//==========================================================================
//
//
//
//==========================================================================
void FDrawInfo::DrawWall(GLWall *wall, int pass)
{
gl_RenderState.SetNormal(wall->glseg.Normal());
switch (pass)
{
case GLPASS_ALL:
case GLPASS_PLAIN:
RenderTexturedWall(wall, GLWall::RWF_TEXTURED);
break;
case GLPASS_TRANSLUCENT:
switch (wall->type)
{
case RENDERWALL_MIRRORSURFACE:
RenderMirrorSurface(wall);
break;
case RENDERWALL_FOGBOUNDARY:
RenderFogBoundary(wall);
break;
default:
RenderTranslucentWall(wall);
break;
}
break;
case GLPASS_LIGHTTEX:
case GLPASS_LIGHTTEX_ADDITIVE:
case GLPASS_LIGHTTEX_FOGGY:
gl_drawinfo->RenderLightsCompat(wall, pass);
break;
case GLPASS_TEXONLY:
gl_RenderState.SetMaterial(wall->gltexture, wall->flags & 3, 0, -1, false);
RenderWall(wall, GLWall::RWF_TEXTURED);
break;
}
}
//==========================================================================
//
//
//
//==========================================================================
void FDrawInfo::AddWall(GLWall *wall)
{
bool translucent = !!(wall->flags & GLWall::GLWF_TRANSLUCENT);
int list;
wall->MakeVertices(this, translucent);
if (translucent) // translucent walls
{
if (!gl.legacyMode && mDrawer->FixedColormap == CM_DEFAULT && wall->gltexture != nullptr)
{
if (wall->SetupLights(lightdata))
wall->dynlightindex = GLRenderer->mLights->UploadLights(lightdata);
}
wall->ViewDistance = (r_viewpoint.Pos - (wall->seg->linedef->v1->fPos() + wall->seg->linedef->Delta() / 2)).XY().LengthSquared();
auto newwall = drawlists[GLDL_TRANSLUCENT].NewWall();
*newwall = *wall;
}
else
{
if (gl.legacyMode)
{
if (PutWallCompat(wall, GLWall::passflag[wall->type])) return;
}
else if (mDrawer->FixedColormap == CM_DEFAULT)
{
if (wall->SetupLights(lightdata))
wall->dynlightindex = GLRenderer->mLights->UploadLights(lightdata);
}
bool masked;
masked = GLWall::passflag[wall->type] == 1 ? false : (wall->gltexture && wall->gltexture->isMasked());
if ((wall->flags & GLWall::GLWF_SKYHACK && wall->type == RENDERWALL_M2S))
{
list = GLDL_MASKEDWALLSOFS;
}
else
{
list = masked ? GLDL_MASKEDWALLS : GLDL_PLAINWALLS;
}
auto newwall = drawlists[list].NewWall();
*newwall = *wall;
if (!masked) newwall->ProcessDecals(this);
}
wall->dynlightindex = -1;
wall->vertcount = 0;
}
//==========================================================================
//
//
//
//==========================================================================
void FDrawInfo::AddMirrorSurface(GLWall *w)
{
w->type = RENDERWALL_MIRRORSURFACE;
auto newwall = drawlists[GLDL_TRANSLUCENTBORDER].NewWall();
*newwall = *w;
FVector3 v = newwall->glseg.Normal();
auto tcs = newwall->tcs;
tcs[GLWall::LOLFT].u = tcs[GLWall::LORGT].u = tcs[GLWall::UPLFT].u = tcs[GLWall::UPRGT].u = v.X;
tcs[GLWall::LOLFT].v = tcs[GLWall::LORGT].v = tcs[GLWall::UPLFT].v = tcs[GLWall::UPRGT].v = v.Z;
newwall->MakeVertices(this, false);
newwall->ProcessDecals(this);
}
//==========================================================================
//
//
//
//==========================================================================
void FDrawInfo::AddPortal(GLWall *wall, int ptype)
{
GLPortal * portal;
wall->MakeVertices(this, false);
switch (ptype)
{
// portals don't go into the draw list.
// Instead they are added to the portal manager
case PORTALTYPE_HORIZON:
wall->horizon = UniqueHorizons.Get(wall->horizon);
portal = GLPortal::FindPortal(wall->horizon);
if (!portal) portal = new GLHorizonPortal(wall->horizon);
portal->AddLine(wall);
break;
case PORTALTYPE_SKYBOX:
portal = GLPortal::FindPortal(wall->secportal);
if (!portal)
{
// either a regular skybox or an Eternity-style horizon
if (wall->secportal->mType != PORTS_SKYVIEWPOINT) portal = new GLEEHorizonPortal(wall->secportal);
else portal = new GLSkyboxPortal(wall->secportal);
}
portal->AddLine(wall);
break;
case PORTALTYPE_SECTORSTACK:
portal = wall->portal->GetRenderState();
portal->AddLine(wall);
break;
case PORTALTYPE_PLANEMIRROR:
if (GLPortal::PlaneMirrorMode * wall->planemirror->fC() <= 0)
{
//@sync-portal
wall->planemirror = UniquePlaneMirrors.Get(wall->planemirror);
portal = GLPortal::FindPortal(wall->planemirror);
if (!portal) portal = new GLPlaneMirrorPortal(wall->planemirror);
portal->AddLine(wall);
}
break;
case PORTALTYPE_MIRROR:
portal = GLPortal::FindPortal(wall->seg->linedef);
if (!portal) portal = new GLMirrorPortal(wall->seg->linedef);
portal->AddLine(wall);
if (gl_mirror_envmap)
{
// draw a reflective layer over the mirror
AddMirrorSurface(wall);
}
break;
case PORTALTYPE_LINETOLINE:
portal = GLPortal::FindPortal(wall->lineportal);
if (!portal)
{
line_t *otherside = wall->lineportal->lines[0]->mDestination;
if (otherside != NULL && otherside->portalindex < level.linePortals.Size())
{
ProcessActorsInPortal(otherside->getPortal()->mGroup, mDrawer->in_area);
}
portal = new GLLineToLinePortal(wall->lineportal);
}
portal->AddLine(wall);
break;
case PORTALTYPE_SKY:
wall->sky = UniqueSkies.Get(wall->sky);
portal = GLPortal::FindPortal(wall->sky);
if (!portal) portal = new GLSkyPortal(wall->sky);
portal->AddLine(wall);
break;
}
wall->vertcount = 0;
}
//==========================================================================
//
//
//
//==========================================================================
void FDrawInfo::DrawDecal(GLDecal *gldecal)
{
auto wall = gldecal->wall;
auto decal = gldecal->decal;
auto tex = gldecal->gltexture;
auto &seg = wall->seg;
// calculate dynamic light effect.
if (gl_lights && GLRenderer->mLightCount && !mDrawer->FixedColormap && gl_light_sprites)
{
// Note: This should be replaced with proper shader based lighting.
double x, y;
float out[3];
decal->GetXY(seg->sidedef, x, y);
GetDynSpriteLight(nullptr, x, y, gldecal->zcenter, wall->sub, out);
gl_RenderState.SetDynLight(out[0], out[1], out[2]);
}
// alpha color only has an effect when using an alpha texture.
if (decal->RenderStyle.Flags & STYLEF_RedIsAlpha)
{
gl_RenderState.SetObjectColor(decal->AlphaColor | 0xff000000);
}
gl_SetRenderStyle(decal->RenderStyle, false, false);
gl_RenderState.SetMaterial(tex, CLAMP_XY, decal->Translation, 0, !!(decal->RenderStyle.Flags & STYLEF_RedIsAlpha));
// If srcalpha is one it looks better with a higher alpha threshold
if (decal->RenderStyle.SrcAlpha == STYLEALPHA_One) gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_sprite_threshold);
else gl_RenderState.AlphaFunc(GL_GREATER, 0.f);
mDrawer->SetColor(gldecal->light, gldecal->rel, gldecal->colormap, gldecal->a);
// for additively drawn decals we must temporarily set the fog color to black.
PalEntry fc = gl_RenderState.GetFogColor();
if (decal->RenderStyle.BlendOp == STYLEOP_Add && decal->RenderStyle.DestAlpha == STYLEALPHA_One)
{
gl_RenderState.SetFog(0, -1);
}
gl_RenderState.SetNormal(wall->glseg.Normal());
if (wall->lightlist == nullptr)
{
gl_RenderState.Apply();
GLRenderer->mVBO->RenderArray(GL_TRIANGLE_FAN, gldecal->vertindex, 4);
}
else
{
auto &lightlist = *wall->lightlist;
for (unsigned k = 0; k < lightlist.Size(); k++)
{
secplane_t &lowplane = k == lightlist.Size() - 1 ? wall->bottomplane : lightlist[k + 1].plane;
DecalVertex *dv = gldecal->dv;
float low1 = lowplane.ZatPoint(dv[1].x, dv[1].y);
float low2 = lowplane.ZatPoint(dv[2].x, dv[2].y);
if (low1 < dv[1].z || low2 < dv[2].z)
{
int thisll = lightlist[k].caster != NULL ? hw_ClampLight(*lightlist[k].p_lightlevel) : wall->lightlevel;
FColormap thiscm;
thiscm.FadeColor = wall->Colormap.FadeColor;
thiscm.CopyFrom3DLight(&lightlist[k]);
mDrawer->SetColor(thisll, gldecal->rel, thiscm, gldecal->a);
if (level.flags3 & LEVEL3_NOCOLOREDSPRITELIGHTING) thiscm.Decolorize();
mDrawer->SetFog(thisll, gldecal->rel, &thiscm, wall->RenderStyle == STYLE_Add);
gl_RenderState.SetSplitPlanes(lightlist[k].plane, lowplane);
gl_RenderState.Apply();
GLRenderer->mVBO->RenderArray(GL_TRIANGLE_FAN, gldecal->vertindex, 4);
}
if (low1 <= dv[0].z && low2 <= dv[3].z) break;
}
}
rendered_decals++;
gl_RenderState.SetTextureMode(TM_MODULATE);
gl_RenderState.SetObjectColor(0xffffffff);
gl_RenderState.SetFog(fc, -1);
gl_RenderState.SetDynLight(0, 0, 0);
}
//==========================================================================
//
//
//
//==========================================================================
void FDrawInfo::DrawDecals()
{
GLWall *wall = nullptr;
for (auto gldecal : decals[0])
{
if (gldecal->wall != wall)
{
wall = gldecal->wall;
if (wall->lightlist != nullptr)
{
gl_RenderState.EnableSplit(true);
}
else
{
gl_RenderState.EnableSplit(false);
mDrawer->SetFog(wall->lightlevel, wall->rellight + getExtraLight(), &wall->Colormap, false);
}
}
DrawDecal(gldecal);
}
if (wall && wall->lightlist != nullptr) gl_RenderState.EnableSplit(false);
}
//==========================================================================
//
// This list will never get long, so this code should be ok.
//
//==========================================================================
void FDrawInfo::DrawDecalsForMirror(GLWall *wall)
{
mDrawer->SetFog(wall->lightlevel, wall->rellight + getExtraLight(), &wall->Colormap, false);
for (auto gldecal : decals[1])
{
if (gldecal->wall == wall)
{
DrawDecal(gldecal);
}
}
}