raze/source/core/rendering/scene/hw_drawinfo.cpp
Christoph Oelckers be08a2f800 - clipper rework
* let the clipper work on relative angles to simplify the math.
* properly initialize the initial visible range and preserve it for multiple invocations.
* track the maximum visible angular range per sector. While possibly not sufficient to handle every edge case imaginable it has low overhead and is still useful to eliminate obvious cases that do not need more complex checks. It is enough to fix the blue door in Duke E3L4.
* removed unused elements of the clipper.
2021-04-24 12:08:38 +02:00

734 lines
21 KiB
C++

//
//---------------------------------------------------------------------------
//
// Copyright(C) 2000-2018 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 2 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_drawinfo.cpp
** Basic scene draw info management class
**
*/
#include "hw_portal.h"
#include "build.h"
#include "hw_renderstate.h"
#include "hw_drawinfo.h"
//#include "models.h"
#include "hw_clock.h"
#include "hw_cvars.h"
#include "hw_viewpointbuffer.h"
#include "flatvertices.h"
#include "hw_lightbuffer.h"
#include "hw_vrmodes.h"
#include "hw_clipper.h"
#include "v_draw.h"
#include "gamecvars.h"
#include "gamestruct.h"
#include "automap.h"
#include "hw_voxels.h"
EXTERN_CVAR(Float, r_visibility)
CVAR(Bool, gl_no_skyclear, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Bool, gl_texture, true, 0)
CVAR(Float, gl_mask_threshold, 0.5f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Float, gl_mask_sprite_threshold, 0.5f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
FixedBitArray<MAXSECTORS> gotsector;
//==========================================================================
//
//
//
//==========================================================================
class FDrawInfoList
{
public:
TDeletingArray<HWDrawInfo *> mList;
HWDrawInfo * GetNew();
void Release(HWDrawInfo *);
};
FDrawInfoList di_list;
//==========================================================================
//
// Try to reuse the lists as often as possible as they contain resources that
// are expensive to create and delete.
//
// Note: If multithreading gets used, this class needs synchronization.
//
//==========================================================================
HWDrawInfo *FDrawInfoList::GetNew()
{
if (mList.Size() > 0)
{
HWDrawInfo *di;
mList.Pop(di);
return di;
}
return new HWDrawInfo();
}
void FDrawInfoList::Release(HWDrawInfo * di)
{
di->ClearBuffers();
mList.Push(di);
}
//==========================================================================
//
// Sets up a new drawinfo struct
//
//==========================================================================
HWDrawInfo *HWDrawInfo::StartDrawInfo(HWDrawInfo *parent, FRenderViewpoint &parentvp, HWViewpointUniforms *uniforms)
{
HWDrawInfo *di = di_list.GetNew();
di->StartScene(parentvp, uniforms);
return di;
}
//==========================================================================
//
//
//
//==========================================================================
static Clipper staticClipper; // Since all scenes are processed sequentially we only need one clipper.
static HWDrawInfo * gl_drawinfo; // This is a linked list of all active DrawInfos and needed to free the memory arena after the last one goes out of scope.
void HWDrawInfo::StartScene(FRenderViewpoint& parentvp, HWViewpointUniforms* uniforms)
{
mClipper = &staticClipper;
Viewpoint = parentvp;
//lightmode = Level->lightMode;
if (uniforms)
{
VPUniforms = *uniforms;
// The clip planes will never be inherited from the parent drawinfo.
VPUniforms.mClipLine.X = -1000001.f;
VPUniforms.mClipHeight = 0;
}
else
{
VPUniforms.mProjectionMatrix.loadIdentity();
VPUniforms.mViewMatrix.loadIdentity();
VPUniforms.mNormalViewMatrix.loadIdentity();
//VPUniforms.mViewHeight = viewheight;
VPUniforms.mGlobVis = (2 / 65536.f) * g_visibility / r_ambientlight;
VPUniforms.mPalLightLevels = numshades | (static_cast<int>(gl_fogmode) << 8) | (5 << 16);
VPUniforms.mClipLine.X = -10000000.0f;
VPUniforms.mShadowmapFilter = gl_shadowmap_filter;
}
vec2_t view = { int(Viewpoint.Pos.X * 16), int(Viewpoint.Pos.Y * -16) };
mClipper->SetViewpoint(view);
ClearBuffers();
for (int i = 0; i < GLDL_TYPES; i++) drawlists[i].Reset();
vpIndex = 0;
// Fullbright information needs to be propagated from the main view.
if (outer != nullptr) FullbrightFlags = outer->FullbrightFlags;
else FullbrightFlags = 0;
outer = gl_drawinfo;
gl_drawinfo = this;
}
//==========================================================================
//
//
//
//==========================================================================
HWDrawInfo *HWDrawInfo::EndDrawInfo()
{
assert(this == gl_drawinfo);
for (int i = 0; i < GLDL_TYPES; i++) drawlists[i].Reset();
gl_drawinfo = outer;
di_list.Release(this);
if (gl_drawinfo == nullptr)
ResetRenderDataAllocator();
return gl_drawinfo;
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawInfo::ClearBuffers()
{
spriteindex = 0;
mClipPortal = nullptr;
mCurrentPortal = nullptr;
}
//-----------------------------------------------------------------------------
//
// R_FrustumAngle
//
//-----------------------------------------------------------------------------
angle_t HWDrawInfo::FrustumAngle()
{
float WidescreenRatio = 1.6666f; // fixme - this is a placeholder.
float tilt = fabs(Viewpoint.HWAngles.Pitch.Degrees);
// If the pitch is larger than this you can look all around at a FOV of 90°
if (tilt > 46.0f) return 0xffffffff;
// ok, this is a gross hack that barely works...
// but at least it doesn't overestimate too much...
double floatangle = 2.0 + (45.0 + ((tilt / 1.9)))*Viewpoint.FieldOfView.Degrees*48.0 / AspectMultiplier(WidescreenRatio) / 90.0;
angle_t a1 = DAngle(floatangle).BAMs();
if (a1 >= ANGLE_90) return 0xffffffff; // it's either below 90 or bust.
return a1;
}
//-----------------------------------------------------------------------------
//
// Setup the modelview matrix
//
//-----------------------------------------------------------------------------
void HWDrawInfo::SetViewMatrix(const FRotator &angles, float vx, float vy, float vz, bool mirror, bool planemirror)
{
float mult = mirror ? -1.f : 1.f;
float planemult = planemirror ? -1 : 1;// Level->info->pixelstretch : Level->info->pixelstretch;
VPUniforms.mViewMatrix.loadIdentity();
VPUniforms.mViewMatrix.rotate(angles.Roll.Degrees, 0.0f, 0.0f, 1.0f);
VPUniforms.mViewMatrix.rotate(angles.Pitch.Degrees, 1.0f, 0.0f, 0.0f);
VPUniforms.mViewMatrix.rotate(angles.Yaw.Degrees, 0.0f, mult, 0.0f);
VPUniforms.mViewMatrix.translate(vx * mult, -vz * planemult, -vy);
VPUniforms.mViewMatrix.scale(-mult, planemult, 1);
}
//-----------------------------------------------------------------------------
//
// SetupView
// Setup the view rotation matrix for the given viewpoint
//
//-----------------------------------------------------------------------------
void HWDrawInfo::SetupView(FRenderState &state, float vx, float vy, float vz, bool mirror, bool planemirror)
{
auto &vp = Viewpoint;
//vp.SetViewAngle(r_viewwindow); // todo: need to pass in.
SetViewMatrix(vp.HWAngles, vx, vy, vz, mirror, planemirror);
SetCameraPos(vp.Pos);
VPUniforms.CalcDependencies();
vpIndex = screen->mViewpoints->SetViewpoint(state, &VPUniforms);
}
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
HWPortal * HWDrawInfo::FindPortal(const void * src)
{
int i = Portals.Size() - 1;
while (i >= 0 && Portals[i] && Portals[i]->GetSource() != src) i--;
return i >= 0 ? Portals[i] : nullptr;
}
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
void HWDrawInfo::DispatchSprites()
{
for (int i = 0; i < spritesortcnt; i++)
{
auto tspr = &tsprite[i];
int tilenum = tspr->picnum;
int spritenum = tspr->owner;
if (spritenum < 0 || (unsigned)tilenum >= MAXTILES)
continue;
if (automapping == 1 && (unsigned)spritenum < MAXSPRITES)
show2dsprite.Set(spritenum);
setgotpic(tilenum);
if (!(spriteext[spritenum].flags & SPREXT_NOTMD))
{
int pt = Ptile2tile(tspr->picnum, tspr->pal);
if (hw_models && tile2model[pt].modelid >= 0 && tile2model[pt].framenum >= 0)
{
//HWSprite hwsprite;
//if (hwsprite.ProcessModel(pt, tspr)) continue;
}
if (r_voxels)
{
if ((tspr->cstat & CSTAT_SPRITE_ALIGNMENT) != CSTAT_SPRITE_ALIGNMENT_SLAB && tiletovox[tspr->picnum] >= 0 && voxmodels[tiletovox[tspr->picnum]])
{
HWSprite hwsprite;
int num = tiletovox[tspr->picnum];
if (hwsprite.ProcessVoxel(this, voxmodels[tiletovox[tspr->picnum]], tspr, &sector[tspr->sectnum], voxrotate[num]))
continue;
}
else if ((tspr->cstat & CSTAT_SPRITE_ALIGNMENT) == CSTAT_SPRITE_ALIGNMENT_SLAB && tspr->picnum < MAXVOXELS && voxmodels[tspr->picnum])
{
HWSprite hwsprite;
int num = tspr->picnum;
hwsprite.ProcessVoxel(this, voxmodels[tspr->picnum], tspr, &sector[tspr->sectnum], voxrotate[num]);
continue;
}
}
}
if (spriteext[spritenum].flags & SPREXT_AWAY1)
{
tspr->pos.x += bcos(tspr->ang, -13);
tspr->pos.y += bsin(tspr->ang, -13);
}
else if (spriteext[spritenum].flags & SPREXT_AWAY2)
{
tspr->pos.x -= bcos(tspr->ang, -13);
tspr->pos.y -= bsin(tspr->ang, -13);
}
tileUpdatePicnum(&tilenum, sprite->owner + 32768, 0);
tspr->picnum = tilenum;
switch (tspr->cstat & CSTAT_SPRITE_ALIGNMENT)
{
case CSTAT_SPRITE_ALIGNMENT_FACING:
{
HWSprite sprite;
sprite.Process(this, tspr, &sector[tspr->sectnum], false);
break;
}
case CSTAT_SPRITE_ALIGNMENT_WALL:
{
HWWall wall;
wall.ProcessWallSprite(this, tspr, &sector[tspr->sectnum]);
break;
}
case CSTAT_SPRITE_ALIGNMENT_FLOOR:
{
HWFlat flat;
flat.ProcessFlatSprite(this, tspr, &sector[tspr->sectnum]);
break;
}
default:
break;
}
}
}
//-----------------------------------------------------------------------------
//
// CreateScene
//
// creates the draw lists for the current scene
//
//-----------------------------------------------------------------------------
void HWDrawInfo::CreateScene(bool portal)
{
const auto& vp = Viewpoint;
angle_t a1 = FrustumAngle();
// reset the portal manager
portalState.StartFrame();
ProcessAll.Clock();
// clip the scene and fill the drawlists
screen->mVertexData->Map();
screen->mLights->Map();
spritesortcnt = 0;
ingeo = false;
geoofs = { 0,0 };
vec2_t view = { int(vp.Pos.X * 16), int(vp.Pos.Y * -16) };
if(!portal) mClipper->SetVisibleRange(vp.RotAngle, a1);
if (a1 != 0xffffffff) mDrawer.Init(this, mClipper, view, bamang(vp.RotAngle - a1), bamang(vp.RotAngle + a1));
else mDrawer.Init(this, mClipper, view, bamang(0), bamang(0));
if (vp.SectNums)
mDrawer.RenderScene(vp.SectNums, vp.SectCount, portal);
else
mDrawer.RenderScene(&vp.SectCount, 1, portal);
SetupSprite.Clock();
gi->processSprites(tsprite, spritesortcnt, view.x, view.y, vp.Pos.Z * -256, bamang(vp.RotAngle), vp.TicFrac * 65536);
DispatchSprites();
SetupSprite.Unclock();
GeoEffect eff;
int effsect = vp.SectNums ? vp.SectNums[0] : vp.SectCount;
int drawsect = effsect;
// RR geometry hack. Ugh...
// This just adds to the existing render list, so we must offset the effect areas to the same xy-space as the main one as we cannot change the view matrix.
if (gi->GetGeoEffect(&eff, effsect))
{
ingeo = true;
geoofs = { (float)eff.geox[0], (float)eff.geoy[0] };
// process the first layer.
for (int i = 0; i < eff.geocnt; i++)
{
auto sect = &sector[eff.geosectorwarp[i]];
for (auto w = 0; w < sect->wallnum; w++)
{
auto wal = &wall[sect->wallptr + w];
wal->x += eff.geox[i];
wal->y += eff.geoy[i];
}
sect->dirty = 255;
if (eff.geosector[i] == effsect) drawsect = eff.geosectorwarp[i];
}
if (a1 != 0xffffffff) mDrawer.Init(this, mClipper, view, bamang(vp.RotAngle - a1), bamang(vp.RotAngle + a1));
else mDrawer.Init(this, mClipper, view, bamang(0), bamang(0));
mDrawer.RenderScene(&drawsect, 1, false);
for (int i = 0; i < eff.geocnt; i++)
{
auto sect = &sector[eff.geosectorwarp[i]];
for (auto w = 0; w < sect->wallnum; w++)
{
auto wal = &wall[sect->wallptr + w];
wal->x -= eff.geox[i];
wal->y -= eff.geoy[i];
}
}
// Now the second layer. Same shit, different arrays.
geoofs = { (float)eff.geox2[0], (float)eff.geoy2[0] };
for (int i = 0; i < eff.geocnt; i++)
{
auto sect = &sector[eff.geosectorwarp2[i]];
for (auto w = 0; w < sect->wallnum; w++)
{
auto wal = &wall[sect->wallptr + w];
wal->x += eff.geox2[i];
wal->y += eff.geoy2[i];
}
sect->dirty = 255;
if (eff.geosector[i] == effsect) drawsect = eff.geosectorwarp2[i];
}
if (a1 != 0xffffffff) mDrawer.Init(this, mClipper, view, bamang(vp.RotAngle - a1), bamang(vp.RotAngle + a1));
else mDrawer.Init(this, mClipper, view, bamang(0), bamang(0));
mDrawer.RenderScene(&drawsect, 1, false);
for (int i = 0; i < eff.geocnt; i++)
{
auto sect = &sector[eff.geosectorwarp2[i]];
for (auto w = 0; w < sect->wallnum; w++)
{
auto wal = &wall[sect->wallptr + w];
wal->x -= eff.geox2[i];
wal->y -= eff.geoy2[i];
}
}
ingeo = false;
}
screen->mLights->Unmap();
screen->mVertexData->Unmap();
ProcessAll.Unclock();
}
//-----------------------------------------------------------------------------
//
// RenderScene
//
// Draws the current draw lists for the non GLSL renderer
//
//-----------------------------------------------------------------------------
void HWDrawInfo::RenderScene(FRenderState &state)
{
const auto &vp = Viewpoint;
RenderAll.Clock();
state.SetDepthMask(true);
state.EnableFog(true);
state.SetRenderStyle(STYLE_Source);
// Part 1: solid geometry. This is set up so that there are no transparent parts
state.SetDepthFunc(DF_Less);
state.AlphaFunc(Alpha_GEqual, 0.f);
state.ClearDepthBias();
state.EnableTexture(gl_texture);
state.EnableBrightmap(true);
drawlists[GLDL_PLAINWALLS].DrawWalls(this, state, false);
drawlists[GLDL_PLAINFLATS].DrawFlats(this, state, false);
// Part 2: masked geometry. This is set up so that only pixels with alpha>gl_mask_threshold will show
state.AlphaFunc(Alpha_GEqual, gl_mask_threshold);
// This list is masked, non-translucent walls.
drawlists[GLDL_MASKEDWALLS].DrawWalls(this, state, false);
// These lists must be drawn in two passes for color and depth to avoid depth fighting with overlapping entries
drawlists[GLDL_MASKEDFLATS].SortFlats(this);
drawlists[GLDL_MASKEDWALLSV].SortWallsHorz(this);
drawlists[GLDL_MASKEDWALLSH].SortWallsVert(this);
state.SetDepthBias(-1, -128);
// these lists are only wall and floor sprites - often attached to walls and floors - so they need to be offset from the plane they may be attached to.
drawlists[GLDL_MASKEDWALLSS].DrawWalls(this, state, false);
// Each list must draw both its passes before the next one to ensure proper depth buffer contents.
state.SetDepthMask(false);
drawlists[GLDL_MASKEDWALLSV].DrawWalls(this, state, false);
state.SetDepthMask(true);
state.SetColorMask(false);
drawlists[GLDL_MASKEDWALLSV].DrawWalls(this, state, false);
state.SetColorMask(true);
state.SetDepthMask(false);
drawlists[GLDL_MASKEDWALLSH].DrawWalls(this, state, false);
state.SetDepthMask(true);
state.SetColorMask(false);
drawlists[GLDL_MASKEDWALLSH].DrawWalls(this, state, false);
state.SetColorMask(true);
state.SetDepthMask(false);
drawlists[GLDL_MASKEDFLATS].DrawFlats(this, state, false);
state.SetDepthMask(true);
state.SetColorMask(false);
drawlists[GLDL_MASKEDFLATS].DrawFlats(this, state, false);
state.SetColorMask(true);
state.ClearDepthBias();
drawlists[GLDL_MODELS].Draw(this, state, false);
state.SetRenderStyle(STYLE_Translucent);
state.SetDepthFunc(DF_LEqual);
RenderAll.Unclock();
}
//-----------------------------------------------------------------------------
//
// RenderTranslucent
//
//-----------------------------------------------------------------------------
void HWDrawInfo::RenderTranslucent(FRenderState &state)
{
RenderAll.Clock();
state.SetDepthBias(-1, -128);
// final pass: translucent stuff
state.AlphaFunc(Alpha_GEqual, gl_mask_sprite_threshold);
state.SetRenderStyle(STYLE_Translucent);
state.EnableBrightmap(true);
drawlists[GLDL_TRANSLUCENTBORDER].Draw(this, state, true);
state.SetDepthMask(false);
drawlists[GLDL_TRANSLUCENT].DrawSorted(this, state);
state.EnableBrightmap(false);
state.ClearDepthBias();
state.AlphaFunc(Alpha_GEqual, 0.5f);
state.SetDepthMask(true);
RenderAll.Unclock();
}
//-----------------------------------------------------------------------------
//
// RenderTranslucent
//
//-----------------------------------------------------------------------------
void HWDrawInfo::RenderPortal(HWPortal *p, FRenderState &state, bool usestencil)
{
auto gp = static_cast<HWPortal *>(p);
gp->SetupStencil(this, state, usestencil);
auto new_di = StartDrawInfo(this, Viewpoint, &VPUniforms);
new_di->mCurrentPortal = gp;
state.SetLightIndex(-1);
gp->DrawContents(new_di, state);
new_di->EndDrawInfo();
state.SetVertexBuffer(screen->mVertexData);
screen->mViewpoints->Bind(state, vpIndex);
gp->RemoveStencil(this, state, usestencil);
}
//-----------------------------------------------------------------------------
//
// Draws player sprites and color blend
//
//-----------------------------------------------------------------------------
void HWDrawInfo::EndDrawScene(FRenderState &state)
{
state.EnableFog(false);
#if 0
// [BB] HUD models need to be rendered here.
const bool renderHUDModel = IsHUDModelForPlayerAvailable(players[consoleplayer].camera->player);
if (renderHUDModel)
{
// [BB] The HUD model should be drawn over everything else already drawn.
state.Clear(CT_Depth);
DrawPlayerSprites(true, state);
}
#endif
state.EnableStencil(false);
state.SetViewport(screen->mScreenViewport.left, screen->mScreenViewport.top, screen->mScreenViewport.width, screen->mScreenViewport.height);
// Restore standard rendering state
state.SetRenderStyle(STYLE_Translucent);
state.ResetColor();
state.EnableTexture(true);
state.SetScissor(0, 0, -1, -1);
}
//-----------------------------------------------------------------------------
//
// sets 3D viewport and initial state
//
//-----------------------------------------------------------------------------
void HWDrawInfo::Set3DViewport(FRenderState &state)
{
// Always clear all buffers with scissor test disabled.
// This is faster on newer hardware because it allows the GPU to skip
// reading from slower memory where the full buffers are stored.
state.SetScissor(0, 0, -1, -1);
state.Clear(CT_Color | CT_Depth | CT_Stencil);
const auto &bounds = screen->mSceneViewport;
state.SetViewport(bounds.left, bounds.top, bounds.width, bounds.height);
state.SetScissor(bounds.left, bounds.top, bounds.width, bounds.height);
state.EnableMultisampling(true);
state.EnableDepthTest(true);
state.EnableStencil(true);
state.SetStencil(0, SOP_Keep, SF_AllOn);
}
//-----------------------------------------------------------------------------
//
// gl_drawscene - this function renders the scene from the current
// viewpoint, including mirrors and skyboxes and other portals
// It is assumed that the HWPortal::EndFrame returns with the
// stencil, z-buffer and the projection matrix intact!
//
//-----------------------------------------------------------------------------
void HWDrawInfo::DrawScene(int drawmode, bool portal)
{
static int recursion = 0;
static int ssao_portals_available = 0;
const auto& vp = Viewpoint;
bool applySSAO = false;
if (drawmode == DM_MAINVIEW)
{
ssao_portals_available = gl_ssao_portals;
applySSAO = true;
}
else if (drawmode == DM_OFFSCREEN)
{
ssao_portals_available = 0;
}
else if (drawmode == DM_PORTAL && ssao_portals_available > 0)
{
applySSAO = (mCurrentPortal->AllowSSAO()/* || Level->flags3&LEVEL3_SKYBOXAO*/);
ssao_portals_available--;
}
CreateScene(portal);
auto& RenderState = *screen->RenderState();
RenderState.SetDepthMask(true);
if (!gl_no_skyclear) portalState.RenderFirstSkyPortal(recursion, this, RenderState);
RenderScene(RenderState);
if (applySSAO && RenderState.GetPassType() == GBUFFER_PASS)
{
screen->AmbientOccludeScene(VPUniforms.mProjectionMatrix.get()[5]);
screen->mViewpoints->Bind(RenderState, vpIndex);
}
// Handle all portals after rendering the opaque objects but before
// doing all translucent stuff
recursion++;
portalState.EndFrame(this, RenderState);
recursion--;
RenderTranslucent(RenderState);
}
//-----------------------------------------------------------------------------
//
// R_RenderView - renders one view - either the screen or a camera texture
//
//-----------------------------------------------------------------------------
void HWDrawInfo::ProcessScene(bool toscreen)
{
portalState.BeginScene();
DrawScene(toscreen ? DM_MAINVIEW : DM_OFFSCREEN, false);
if (toscreen && isBlood())
{
gotsector = mDrawer.GotSector(); // Blood needs this to implement some lighting effect hacks. Needs to be refactored to use better info.
}
}