- moved all rendering code into a common subdirectory.

No changes to the files themselves was made.
This commit is contained in:
Christoph Oelckers 2019-01-31 19:58:17 +01:00
parent c77487dab8
commit 89d607c9a6
256 changed files with 193 additions and 191 deletions

View file

@ -0,0 +1,82 @@
#pragma once
#include <stddef.h>
#include <assert.h>
// The low level code needs to know which attributes exist.
// OpenGL needs to change the state of all of them per buffer binding.
// VAOs are mostly useless for this because they lump buffer and binding state together which the model code does not want.
enum
{
VATTR_VERTEX,
VATTR_TEXCOORD,
VATTR_COLOR,
VATTR_VERTEX2,
VATTR_NORMAL,
VATTR_NORMAL2,
VATTR_MAX
};
enum EVertexAttributeFormat
{
VFmt_Float4,
VFmt_Float3,
VFmt_Float2,
VFmt_Float,
VFmt_Byte4,
VFmt_Packed_A2R10G10B10,
};
struct FVertexBufferAttribute
{
int binding;
int location;
int format;
int offset;
};
class IBuffer
{
protected:
size_t buffersize = 0;
void *map = nullptr;
public:
IBuffer() = default;
IBuffer(const IBuffer &) = delete;
IBuffer &operator=(const IBuffer &) = delete;
virtual ~IBuffer() = default;
virtual void SetData(size_t size, const void *data, bool staticdata = true) = 0;
virtual void SetSubData(size_t offset, size_t size, const void *data) = 0;
virtual void *Lock(unsigned int size) = 0;
virtual void Unlock() = 0;
virtual void Resize(size_t newsize) = 0;
virtual void Map() {} // Only needed by old OpenGL but this needs to be in the interface.
virtual void Unmap() {}
void *Memory() { assert(map); return map; }
size_t Size() { return buffersize; }
};
class IVertexBuffer : virtual public IBuffer
{
public:
virtual void SetFormat(int numBindingPoints, int numAttributes, size_t stride, const FVertexBufferAttribute *attrs) = 0;
};
// This merely exists to have a dedicated type for index buffers to inherit from.
class IIndexBuffer : virtual public IBuffer
{
// Element size is fixed to 4, thanks to OpenGL requiring this info to be coded into the glDrawElements call.
// This mostly prohibits a more flexible buffer setup but GZDoom doesn't use any other format anyway.
// Ob Vulkam, element size is a buffer property and of no concern to the drawing functions (as it should be.)
};
class IDataBuffer : virtual public IBuffer
{
// Can be either uniform or shader storage buffer, depending on its needs.
public:
virtual void BindRange(size_t start, size_t length) = 0;
virtual void BindBase() = 0;
};

View file

@ -0,0 +1,400 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2005-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/
//
//--------------------------------------------------------------------------
//
/*
** hw_flatvertices.cpp
** Creates flat vertex data for hardware rendering.
**
**/
#include "doomtype.h"
#include "p_local.h"
#include "r_state.h"
#include "c_cvars.h"
#include "g_levellocals.h"
#include "flatvertices.h"
#include "cmdlib.h"
#include "hwrenderer/data/buffers.h"
#include "hwrenderer/scene/hw_renderstate.h"
//==========================================================================
//
//
//
//==========================================================================
FFlatVertexBuffer::FFlatVertexBuffer(int width, int height)
{
vbo_shadowdata.Resize(NUM_RESERVED);
// the first quad is reserved for handling coordinates through uniforms.
vbo_shadowdata[0].Set(0, 0, 0, 0, 0);
vbo_shadowdata[1].Set(1, 0, 0, 0, 0);
vbo_shadowdata[2].Set(2, 0, 0, 0, 0);
vbo_shadowdata[3].Set(3, 0, 0, 0, 0);
// and the second one for the fullscreen quad used for blend overlays.
vbo_shadowdata[4].Set(0, 0, 0, 0, 0);
vbo_shadowdata[5].Set(0, (float)height, 0, 0, 1);
vbo_shadowdata[6].Set((float)width, 0, 0, 1, 0);
vbo_shadowdata[7].Set((float)width, (float)height, 0, 1, 1);
// and this is for the postprocessing copy operation
vbo_shadowdata[8].Set(-1.0f, -1.0f, 0, 0.0f, 0.0f);
vbo_shadowdata[9].Set(-1.0f, 1.0f, 0, 0.0f, 1.f);
vbo_shadowdata[10].Set(1.0f, -1.0f, 0, 1.f, 0.0f);
vbo_shadowdata[11].Set(1.0f, 1.0f, 0, 1.f, 1.f);
// The next two are the stencil caps.
vbo_shadowdata[12].Set(-32767.0f, 32767.0f, -32767.0f, 0, 0);
vbo_shadowdata[13].Set(-32767.0f, 32767.0f, 32767.0f, 0, 0);
vbo_shadowdata[14].Set(32767.0f, 32767.0f, 32767.0f, 0, 0);
vbo_shadowdata[15].Set(32767.0f, 32767.0f, -32767.0f, 0, 0);
vbo_shadowdata[16].Set(-32767.0f, -32767.0f, -32767.0f, 0, 0);
vbo_shadowdata[17].Set(-32767.0f, -32767.0f, 32767.0f, 0, 0);
vbo_shadowdata[18].Set(32767.0f, -32767.0f, 32767.0f, 0, 0);
vbo_shadowdata[19].Set(32767.0f, -32767.0f, -32767.0f, 0, 0);
mVertexBuffer = screen->CreateVertexBuffer();
mIndexBuffer = screen->CreateIndexBuffer();
unsigned int bytesize = BUFFER_SIZE * sizeof(FFlatVertex);
mVertexBuffer->SetData(bytesize, nullptr, false);
static const FVertexBufferAttribute format[] = {
{ 0, VATTR_VERTEX, VFmt_Float3, (int)myoffsetof(FFlatVertex, x) },
{ 0, VATTR_TEXCOORD, VFmt_Float2, (int)myoffsetof(FFlatVertex, u) }
};
mVertexBuffer->SetFormat(1, 2, sizeof(FFlatVertex), format);
mIndex = mCurIndex = 0;
mNumReserved = NUM_RESERVED;
Copy(0, NUM_RESERVED);
}
//==========================================================================
//
//
//
//==========================================================================
FFlatVertexBuffer::~FFlatVertexBuffer()
{
delete mIndexBuffer;
delete mVertexBuffer;
mIndexBuffer = nullptr;
mVertexBuffer = nullptr;
}
//==========================================================================
//
//
//
//==========================================================================
void FFlatVertexBuffer::OutputResized(int width, int height)
{
vbo_shadowdata[4].Set(0, 0, 0, 0, 0);
vbo_shadowdata[5].Set(0, (float)height, 0, 0, 1);
vbo_shadowdata[6].Set((float)width, 0, 0, 1, 0);
vbo_shadowdata[7].Set((float)width, (float)height, 0, 1, 1);
Copy(4, 4);
}
//==========================================================================
//
// Initialize a single vertex
//
//==========================================================================
void FFlatVertex::SetFlatVertex(vertex_t *vt, const secplane_t & plane)
{
x = (float)vt->fX();
y = (float)vt->fY();
z = (float)plane.ZatPoint(vt);
u = (float)vt->fX()/64.f;
v = -(float)vt->fY()/64.f;
}
//==========================================================================
//
// Find a 3D floor
//
//==========================================================================
static F3DFloor *Find3DFloor(sector_t *target, sector_t *model)
{
for(unsigned i=0; i<target->e->XFloor.ffloors.Size(); i++)
{
F3DFloor *ffloor = target->e->XFloor.ffloors[i];
if (ffloor->model == model && !(ffloor->flags & FF_THISINSIDE)) return ffloor;
}
return NULL;
}
//==========================================================================
//
// Creates the vertices for one plane in one subsector
//
//==========================================================================
int FFlatVertexBuffer::CreateIndexedSectorVertices(sector_t *sec, const secplane_t &plane, int floor, VertexContainer &verts)
{
unsigned vi = vbo_shadowdata.Reserve(verts.vertices.Size());
float diff;
// Create the actual vertices.
if (sec->transdoor && floor) diff = -1.f;
else diff = 0.f;
for (unsigned i = 0; i < verts.vertices.Size(); i++)
{
vbo_shadowdata[vi + i].SetFlatVertex(verts.vertices[i].vertex, plane);
vbo_shadowdata[vi + i].z += diff;
}
unsigned rt = ibo_data.Reserve(verts.indices.Size());
for (unsigned i = 0; i < verts.indices.Size(); i++)
{
ibo_data[rt + i] = vi + verts.indices[i];
}
return (int)rt;
}
//==========================================================================
//
//
//
//==========================================================================
int FFlatVertexBuffer::CreateIndexedVertices(int h, sector_t *sec, const secplane_t &plane, int floor, VertexContainers &verts)
{
sec->vboindex[h] = vbo_shadowdata.Size();
// First calculate the vertices for the sector itself
sec->vboheight[h] = sec->GetPlaneTexZ(h);
sec->ibocount = verts[sec->Index()].indices.Size();
sec->iboindex[h] = CreateIndexedSectorVertices(sec, plane, floor, verts[sec->Index()]);
// Next are all sectors using this one as heightsec
TArray<sector_t *> &fakes = sec->e->FakeFloor.Sectors;
for (unsigned g = 0; g < fakes.Size(); g++)
{
sector_t *fsec = fakes[g];
fsec->iboindex[2 + h] = CreateIndexedSectorVertices(fsec, plane, false, verts[fsec->Index()]);
}
// and finally all attached 3D floors
TArray<sector_t *> &xf = sec->e->XFloor.attached;
for (unsigned g = 0; g < xf.Size(); g++)
{
sector_t *fsec = xf[g];
F3DFloor *ffloor = Find3DFloor(fsec, sec);
if (ffloor != NULL && ffloor->flags & FF_RENDERPLANES)
{
bool dotop = (ffloor->top.model == sec) && (ffloor->top.isceiling == h);
bool dobottom = (ffloor->bottom.model == sec) && (ffloor->bottom.isceiling == h);
if (dotop || dobottom)
{
auto ndx = CreateIndexedSectorVertices(fsec, plane, false, verts[fsec->Index()]);
if (dotop) ffloor->top.vindex = ndx;
if (dobottom) ffloor->bottom.vindex = ndx;
}
}
}
sec->vbocount[h] = vbo_shadowdata.Size() - sec->vboindex[h];
return sec->iboindex[h];
}
//==========================================================================
//
//
//
//==========================================================================
void FFlatVertexBuffer::CreateIndexedFlatVertices(TArray<sector_t> &sectors)
{
auto verts = BuildVertices(sectors);
int i = 0;
/*
for (auto &vert : verts)
{
Printf(PRINT_LOG, "Sector %d\n", i);
Printf(PRINT_LOG, "%d vertices, %d indices\n", vert.vertices.Size(), vert.indices.Size());
int j = 0;
for (auto &v : vert.vertices)
{
Printf(PRINT_LOG, " %d: (%2.3f, %2.3f)\n", j++, v.vertex->fX(), v.vertex->fY());
}
for (unsigned i=0;i<vert.indices.Size();i+=3)
{
Printf(PRINT_LOG, " %d, %d, %d\n", vert.indices[i], vert.indices[i + 1], vert.indices[i + 2]);
}
i++;
}
*/
for (int h = sector_t::floor; h <= sector_t::ceiling; h++)
{
for (auto &sec : sectors)
{
CreateIndexedVertices(h, &sec, sec.GetSecPlane(h), h == sector_t::floor, verts);
}
}
// We need to do a final check for Vavoom water and FF_FIX sectors.
// No new vertices are needed here. The planes come from the actual sector
for (auto &sec : sectors)
{
for (auto ff : sec.e->XFloor.ffloors)
{
if (ff->top.model == &sec)
{
ff->top.vindex = sec.iboindex[ff->top.isceiling];
}
if (ff->bottom.model == &sec)
{
ff->bottom.vindex = sec.iboindex[ff->top.isceiling];
}
}
}
}
//==========================================================================
//
//
//
//==========================================================================
void FFlatVertexBuffer::UpdatePlaneVertices(sector_t *sec, int plane)
{
int startvt = sec->vboindex[plane];
int countvt = sec->vbocount[plane];
secplane_t &splane = sec->GetSecPlane(plane);
FFlatVertex *vt = &vbo_shadowdata[startvt];
FFlatVertex *mapvt = GetBuffer(startvt);
for(int i=0; i<countvt; i++, vt++, mapvt++)
{
vt->z = (float)splane.ZatPoint(vt->x, vt->y);
if (plane == sector_t::floor && sec->transdoor) vt->z -= 1;
mapvt->z = vt->z;
}
}
//==========================================================================
//
//
//
//==========================================================================
void FFlatVertexBuffer::CreateVertices(TArray<sector_t> &sectors)
{
vbo_shadowdata.Resize(NUM_RESERVED);
CreateIndexedFlatVertices(sectors);
}
//==========================================================================
//
//
//
//==========================================================================
void FFlatVertexBuffer::CheckPlanes(sector_t *sector)
{
if (sector->GetPlaneTexZ(sector_t::ceiling) != sector->vboheight[sector_t::ceiling])
{
UpdatePlaneVertices(sector, sector_t::ceiling);
sector->vboheight[sector_t::ceiling] = sector->GetPlaneTexZ(sector_t::ceiling);
}
if (sector->GetPlaneTexZ(sector_t::floor) != sector->vboheight[sector_t::floor])
{
UpdatePlaneVertices(sector, sector_t::floor);
sector->vboheight[sector_t::floor] = sector->GetPlaneTexZ(sector_t::floor);
}
}
//==========================================================================
//
// checks the validity of all planes attached to this sector
// and updates them if possible.
//
//==========================================================================
void FFlatVertexBuffer::CheckUpdate(sector_t *sector)
{
CheckPlanes(sector);
sector_t *hs = sector->GetHeightSec();
if (hs != NULL) CheckPlanes(hs);
for (unsigned i = 0; i < sector->e->XFloor.ffloors.Size(); i++)
CheckPlanes(sector->e->XFloor.ffloors[i]->model);
}
//==========================================================================
//
//
//
//==========================================================================
std::pair<FFlatVertex *, unsigned int> FFlatVertexBuffer::AllocVertices(unsigned int count)
{
FFlatVertex *p = GetBuffer();
auto index = mCurIndex.fetch_add(count);
auto offset = index;
if (index + count >= BUFFER_SIZE_TO_USE)
{
// If a single scene needs 2'000'000 vertices there must be something very wrong.
I_FatalError("Out of vertex memory. Tried to allocate more than %u vertices for a single frame", index + count);
}
return std::make_pair(p, index);
}
//==========================================================================
//
//
//
//==========================================================================
void FFlatVertexBuffer::Copy(int start, int count)
{
Map();
memcpy(GetBuffer(start), &vbo_shadowdata[0], count * sizeof(FFlatVertex));
Unmap();
}
//==========================================================================
//
//
//
//==========================================================================
void FFlatVertexBuffer::CreateVBO(TArray<sector_t> &sectors)
{
vbo_shadowdata.Resize(mNumReserved);
FFlatVertexBuffer::CreateVertices(sectors);
mCurIndex = mIndex = vbo_shadowdata.Size();
Copy(0, mIndex);
mIndexBuffer->SetData(ibo_data.Size() * sizeof(uint32_t), &ibo_data[0]);
}

View file

@ -0,0 +1,133 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2005-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/
//
//--------------------------------------------------------------------------
//
#ifndef _HW__VERTEXBUFFER_H
#define _HW__VERTEXBUFFER_H
#include "tarray.h"
#include "hwrenderer/data/buffers.h"
#include "hw_vertexbuilder.h"
#include <atomic>
#include <mutex>
class FRenderState;
struct FFlatVertex
{
float x, z, y; // world position
float u, v; // texture coordinates
void SetFlatVertex(vertex_t *vt, const secplane_t &plane);
void Set(float xx, float zz, float yy, float uu, float vv)
{
x = xx;
z = zz;
y = yy;
u = uu;
v = vv;
}
};
class FFlatVertexBuffer
{
TArray<FFlatVertex> vbo_shadowdata;
TArray<uint32_t> ibo_data;
IVertexBuffer *mVertexBuffer;
IIndexBuffer *mIndexBuffer;
unsigned int mIndex;
std::atomic<unsigned int> mCurIndex;
unsigned int mNumReserved;
static const unsigned int BUFFER_SIZE = 2000000;
static const unsigned int BUFFER_SIZE_TO_USE = 1999500;
public:
enum
{
QUAD_INDEX = 0,
FULLSCREEN_INDEX = 4,
PRESENT_INDEX = 8,
STENCILTOP_INDEX = 12,
STENCILBOTTOM_INDEX = 16,
NUM_RESERVED = 20
};
FFlatVertexBuffer(int width, int height);
~FFlatVertexBuffer();
void OutputResized(int width, int height);
std::pair<IVertexBuffer *, IIndexBuffer *> GetBufferObjects() const
{
return std::make_pair(mVertexBuffer, mIndexBuffer);
}
void CreateVBO(TArray<sector_t> &sectors);
void Copy(int start, int count);
FFlatVertex *GetBuffer(int index) const
{
FFlatVertex *ff = (FFlatVertex*)mVertexBuffer->Memory();
return &ff[index];
}
FFlatVertex *GetBuffer() const
{
return GetBuffer(mCurIndex);
}
std::pair<FFlatVertex *, unsigned int> AllocVertices(unsigned int count);
void Reset()
{
mCurIndex = mIndex;
}
void Map()
{
mVertexBuffer->Map();
}
void Unmap()
{
mVertexBuffer->Unmap();
}
private:
int CreateIndexedSectionVertices(subsector_t *sub, const secplane_t &plane, int floor, VertexContainer &cont);
int CreateIndexedSectorVertices(sector_t *sec, const secplane_t &plane, int floor, VertexContainer &cont);
int CreateIndexedVertices(int h, sector_t *sec, const secplane_t &plane, int floor, VertexContainers &cont);
void CreateIndexedFlatVertices(TArray<sector_t> &sectors);
void UpdatePlaneVertices(sector_t *sec, int plane);
protected:
void CreateVertices(TArray<sector_t> &sectors);
void CheckPlanes(sector_t *sector);
public:
void CheckUpdate(sector_t *sector);
};
#endif

View file

@ -0,0 +1,148 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2015-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 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 "g_levellocals.h"
#include "hw_vertexbuilder.h"
#include "earcut.hpp"
//=============================================================================
//
// Creates vertex meshes for sector planes
//
//=============================================================================
//=============================================================================
//
//
//
//=============================================================================
static void CreateVerticesForSubsector(subsector_t *sub, VertexContainer &gen, int qualifier)
{
if (sub->numlines < 3) return;
uint32_t startindex = gen.indices.Size();
if ((sub->flags & SSECF_HOLE) && sub->numlines > 3)
{
// Hole filling "subsectors" are not necessarily convex so they require real triangulation.
// These things are extremely rare so performance is secondary here.
using Point = std::pair<double, double>;
std::vector<std::vector<Point>> polygon;
std::vector<Point> *curPoly;
polygon.resize(1);
curPoly = &polygon.back();
curPoly->resize(sub->numlines);
for (unsigned i = 0; i < sub->numlines; i++)
{
(*curPoly)[i] = { sub->firstline[i].v1->fX(), sub->firstline[i].v1->fY() };
}
auto indices = mapbox::earcut(polygon);
for (auto vti : indices)
{
gen.AddIndexForVertex(sub->firstline[vti].v1, qualifier);
}
}
else
{
int firstndx = gen.AddVertex(sub->firstline[0].v1, qualifier);
int secondndx = gen.AddVertex(sub->firstline[1].v1, qualifier);
for (unsigned int k = 2; k < sub->numlines; k++)
{
gen.AddIndex(firstndx);
gen.AddIndex(secondndx);
auto ndx = gen.AddVertex(sub->firstline[k].v1, qualifier);
gen.AddIndex(ndx);
secondndx = ndx;
}
}
}
//=============================================================================
//
//
//
//=============================================================================
static void TriangulateSection(FSection &sect, VertexContainer &gen, int qualifier)
{
if (sect.segments.Size() < 3) return;
// todo
}
//=============================================================================
//
//
//
//=============================================================================
static void CreateVerticesForSection(FSection &section, VertexContainer &gen, bool useSubsectors)
{
section.vertexindex = gen.indices.Size();
if (useSubsectors)
{
for (auto sub : section.subsectors)
{
CreateVerticesForSubsector(sub, gen, -1);
}
}
else
{
TriangulateSection(section, gen, -1);
}
section.vertexcount = gen.indices.Size() - section.vertexindex;
}
//==========================================================================
//
// Creates the vertices for one plane in one subsector
//
//==========================================================================
static void CreateVerticesForSector(sector_t *sec, VertexContainer &gen)
{
auto sections = sec->Level->sections.SectionsForSector(sec);
for (auto &section :sections)
{
CreateVerticesForSection( section, gen, true);
}
}
TArray<VertexContainer> BuildVertices(TArray<sector_t> &sectors)
{
TArray<VertexContainer> verticesPerSector(sectors.Size(), true);
for (unsigned i=0; i< sectors.Size(); i++)
{
CreateVerticesForSector(&sectors[i], verticesPerSector[i]);
}
return verticesPerSector;
}

View file

@ -0,0 +1,69 @@
#include "tarray.h"
struct vertex_t;
struct FQualifiedVertex
{
vertex_t *vertex; // index into vertex array
int qualifier; // some index to prevent vertices in different groups from being merged together.
};
template<> struct THashTraits<FQualifiedVertex>
{
hash_t Hash(const FQualifiedVertex &key)
{
return (int)(((intptr_t)key.vertex) >> 4) ^ (key.qualifier << 16);
}
int Compare(const FQualifiedVertex &left, const FQualifiedVertex &right) { return left.vertex != right.vertex || left.qualifier != right.qualifier; }
};
//=============================================================================
//
// One sector's vertex data.
//
//=============================================================================
struct VertexContainer
{
TArray<FQualifiedVertex> vertices;
TMap<FQualifiedVertex, uint32_t> vertexmap;
bool perSubsector = false;
TArray<uint32_t> indices;
uint32_t AddVertex(FQualifiedVertex *vert)
{
auto check = vertexmap.CheckKey(*vert);
if (check != nullptr) return *check;
auto index = vertices.Push(*vert);
vertexmap[*vert] = index;
return index;
}
uint32_t AddVertex(vertex_t *vert, int qualifier)
{
FQualifiedVertex vertx = { vert, qualifier};
return AddVertex(&vertx);
}
uint32_t AddIndexForVertex(FQualifiedVertex *vert)
{
return indices.Push(AddVertex(vert));
}
uint32_t AddIndexForVertex(vertex_t *vert, int qualifier)
{
return indices.Push(AddVertex(vert, qualifier));
}
uint32_t AddIndex(uint32_t indx)
{
return indices.Push(indx);
}
};
using VertexContainers = TArray<VertexContainer>;
VertexContainers BuildVertices(TArray<sector_t> &sectors);

View file

@ -0,0 +1,110 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 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 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_viewpointbuffer.cpp
** Buffer data maintenance for per viewpoint uniform data
**
**/
#include "hwrenderer/data/shaderuniforms.h"
#include "hwrenderer/scene/hw_viewpointuniforms.h"
#include "hwrenderer/scene/hw_drawinfo.h"
#include "hwrenderer/scene/hw_renderstate.h"
#include "hw_viewpointbuffer.h"
static const int INITIAL_BUFFER_SIZE = 100; // 100 viewpoints per frame should nearly always be enough
GLViewpointBuffer::GLViewpointBuffer()
{
mBufferSize = INITIAL_BUFFER_SIZE;
mBlockAlign = ((sizeof(HWViewpointUniforms) / screen->uniformblockalignment) + 1) * screen->uniformblockalignment;
mByteSize = mBufferSize * mBlockAlign;
mBuffer = screen->CreateDataBuffer(VIEWPOINT_BINDINGPOINT, false);
mBuffer->SetData(mByteSize, nullptr, false);
Clear();
mLastMappedIndex = UINT_MAX;
mClipPlaneInfo.Push(0);
}
GLViewpointBuffer::~GLViewpointBuffer()
{
delete mBuffer;
}
void GLViewpointBuffer::CheckSize()
{
if (mUploadIndex >= mBufferSize)
{
mBufferSize *= 2;
mByteSize *= 2;
mBuffer->Resize(mByteSize);
}
}
int GLViewpointBuffer::Bind(FRenderState &di, unsigned int index)
{
if (index != mLastMappedIndex)
{
mLastMappedIndex = index;
mBuffer->BindRange(index * mBlockAlign, mBlockAlign);
di.EnableClipDistance(0, mClipPlaneInfo[index]);
}
return index;
}
void GLViewpointBuffer::Set2D(FRenderState &di, int width, int height)
{
if (width != m2DWidth || height != m2DHeight)
{
HWViewpointUniforms matrices;
matrices.SetDefaults();
matrices.mProjectionMatrix.ortho(0, (float)width, (float)height, 0, -1.0f, 1.0f);
matrices.CalcDependencies();
mBuffer->Map();
memcpy(mBuffer->Memory(), &matrices, sizeof(matrices));
mBuffer->Unmap();
m2DWidth = width;
m2DHeight = height;
mLastMappedIndex = -1;
}
Bind(di, 0);
}
int GLViewpointBuffer::SetViewpoint(FRenderState &di, HWViewpointUniforms *vp)
{
CheckSize();
mBuffer->Map();
memcpy(((char*)mBuffer->Memory()) + mUploadIndex * mBlockAlign, vp, sizeof(*vp));
mBuffer->Unmap();
mClipPlaneInfo.Push(vp->mClipHeightDirection != 0.f || vp->mClipLine.X > -10000000.0f);
return Bind(di, mUploadIndex++);
}
void GLViewpointBuffer::Clear()
{
// Index 0 is reserved for the 2D projection.
mUploadIndex = 1;
mClipPlaneInfo.Resize(1);
}

View file

@ -0,0 +1,35 @@
#include "tarray.h"
#include "hwrenderer/data/buffers.h"
struct HWViewpointUniforms;
class FRenderState;
class GLViewpointBuffer
{
IDataBuffer *mBuffer;
unsigned int mBufferSize;
unsigned int mBlockAlign;
unsigned int mUploadIndex;
unsigned int mLastMappedIndex;
unsigned int mByteSize;
TArray<bool> mClipPlaneInfo;
int m2DWidth = -1, m2DHeight = -1;
unsigned int mBlockSize;
void CheckSize();
public:
GLViewpointBuffer();
~GLViewpointBuffer();
void Clear();
int Bind(FRenderState &di, unsigned int index);
void Set2D(FRenderState &di, int width, int height);
int SetViewpoint(FRenderState &di, HWViewpointUniforms *vp);
unsigned int GetBlockSize() const { return mBlockSize; }
};

View file

@ -0,0 +1,33 @@
#pragma once
#include <stdint.h>
#include "tflags.h"
// A render queue is what contains all render commands.
// On Vulkan there can be several of them so this interface is needed to allow for the needed parallelism.
// On OpenGL the render state is global so all this will do is to translate the system independent calls into OpenGL API calls.
enum class ColormaskBits
{
RED = 1,
GREEN = 2,
BLUE = 4,
ALPHA = 8
};
typedef TFlags<ColormaskBits, uint8_t> Colormask;
class IRenderQueue
{
Colormask mColorMask;
Colormask GetColorMask() const
{
return mColorMask;
}
virtual void SetColorMask(Colormask mask) = 0;
};

View file

@ -0,0 +1,145 @@
#pragma once
#include <vector>
#include "hwrenderer/data/buffers.h"
#include "v_video.h"
enum
{
LIGHTBUF_BINDINGPOINT = 1,
POSTPROCESS_BINDINGPOINT = 2,
VIEWPOINT_BINDINGPOINT = 3
};
enum class UniformType
{
Int,
UInt,
Float,
Vec2,
Vec3,
Vec4,
IVec2,
IVec3,
IVec4,
UVec2,
UVec3,
UVec4,
Mat4
};
class UniformFieldDesc
{
public:
UniformFieldDesc() { }
UniformFieldDesc(const char *name, UniformType type, std::size_t offset) : Name(name), Type(type), Offset(offset) { }
const char *Name;
UniformType Type;
std::size_t Offset;
};
class UniformBlockDecl
{
public:
static FString Create(const char *name, const std::vector<UniformFieldDesc> &fields, int bindingpoint)
{
FString decl;
FString layout;
if (screen->glslversion < 4.20)
{
layout = "std140";
}
else
{
layout.Format("std140, binding = %d", bindingpoint);
}
decl.Format("layout(%s) uniform %s\n{\n", layout.GetChars(), name);
for (size_t i = 0; i < fields.size(); i++)
{
decl.AppendFormat("\t%s %s;\n", GetTypeStr(fields[i].Type), fields[i].Name);
}
decl += "};\n";
return decl;
}
private:
static const char *GetTypeStr(UniformType type)
{
switch (type)
{
default:
case UniformType::Int: return "int";
case UniformType::UInt: return "uint";
case UniformType::Float: return "float";
case UniformType::Vec2: return "vec2";
case UniformType::Vec3: return "vec3";
case UniformType::Vec4: return "vec4";
case UniformType::IVec2: return "ivec2";
case UniformType::IVec3: return "ivec3";
case UniformType::IVec4: return "ivec4";
case UniformType::UVec2: return "uvec2";
case UniformType::UVec3: return "uvec3";
case UniformType::UVec4: return "uvec4";
case UniformType::Mat4: return "mat4";
}
}
};
template<typename T, int bindingpoint>
class ShaderUniforms
{
public:
ShaderUniforms()
{
memset(&Values, 0, sizeof(Values));
}
~ShaderUniforms()
{
if (mBuffer != nullptr)
delete mBuffer;
}
int BindingPoint() const
{
return bindingpoint;
}
FString CreateDeclaration(const char *name, const std::vector<UniformFieldDesc> &fields)
{
mFields = fields;
return UniformBlockDecl::Create(name, fields, bindingpoint);
}
void Init()
{
if (mBuffer == nullptr)
mBuffer = screen->CreateDataBuffer(bindingpoint, false);
}
void Set(bool bind = true)
{
if (mBuffer != nullptr)
mBuffer->SetData(sizeof(T), &Values);
// Let's hope this can be done better when things have moved further ahead.
// This really is not the best place to add something that depends on API behavior.
if (bind) mBuffer->BindBase();
}
T *operator->() { return &Values; }
const T *operator->() const { return &Values; }
T Values;
private:
ShaderUniforms(const ShaderUniforms &) = delete;
ShaderUniforms &operator=(const ShaderUniforms &) = delete;
IDataBuffer *mBuffer = nullptr;
std::vector<UniformFieldDesc> mFields;
};

View file

@ -0,0 +1,410 @@
//
//---------------------------------------------------------------------------
// AABB-tree used for ray testing
// Copyright(C) 2017 Magnus Norddahl
// 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 "r_state.h"
#include "g_levellocals.h"
#include "hw_aabbtree.h"
namespace hwrenderer
{
LevelAABBTree::LevelAABBTree(FLevelLocals *lev)
{
Level = lev;
// Calculate the center of all lines
TArray<FVector2> centroids;
for (unsigned int i = 0; i < Level->lines.Size(); i++)
{
FVector2 v1 = { (float)Level->lines[i].v1->fX(), (float)Level->lines[i].v1->fY() };
FVector2 v2 = { (float)Level->lines[i].v2->fX(), (float)Level->lines[i].v2->fY() };
centroids.Push((v1 + v2) * 0.5f);
}
// Create the static subtree
if (!GenerateTree(&centroids[0], false))
return;
int staticroot = nodes.Size() - 1;
dynamicStartNode = nodes.Size();
dynamicStartLine = treelines.Size();
// Create the dynamic subtree
if (GenerateTree(&centroids[0], true))
{
int dynamicroot = nodes.Size() - 1;
// Create a shared root node
FVector2 aabb_min, aabb_max;
const auto &left = nodes[staticroot];
const auto &right = nodes[dynamicroot];
aabb_min.X = MIN(left.aabb_left, right.aabb_left);
aabb_min.Y = MIN(left.aabb_top, right.aabb_top);
aabb_max.X = MAX(left.aabb_right, right.aabb_right);
aabb_max.Y = MAX(left.aabb_bottom, right.aabb_bottom);
nodes.Push({ aabb_min, aabb_max, staticroot, dynamicroot });
}
// Add the lines referenced by the leaf nodes
treelines.Resize(mapLines.Size());
for (unsigned int i = 0; i < mapLines.Size(); i++)
{
const auto &line = Level->lines[mapLines[i]];
auto &treeline = treelines[i];
treeline.x = (float)line.v1->fX();
treeline.y = (float)line.v1->fY();
treeline.dx = (float)line.v2->fX() - treeline.x;
treeline.dy = (float)line.v2->fY() - treeline.y;
}
}
bool LevelAABBTree::GenerateTree(const FVector2 *centroids, bool dynamicsubtree)
{
// Create a list of level lines we want to add:
TArray<int> line_elements;
auto &maplines = Level->lines;
for (unsigned int i = 0; i < maplines.Size(); i++)
{
if (!maplines[i].backsector)
{
bool isPolyLine = maplines[i].sidedef[0] && (maplines[i].sidedef[0]->Flags & WALLF_POLYOBJ);
if (isPolyLine && dynamicsubtree)
{
line_elements.Push(mapLines.Size());
mapLines.Push(i);
}
else if (!isPolyLine && !dynamicsubtree)
{
line_elements.Push(mapLines.Size());
mapLines.Push(i);
}
}
}
if (line_elements.Size() == 0)
return false;
// GenerateTreeNode needs a buffer where it can store line indices temporarily when sorting lines into the left and right child AABB buckets
TArray<int> work_buffer;
work_buffer.Resize(line_elements.Size() * 2);
// Generate the AABB tree
GenerateTreeNode(&line_elements[0], (int)line_elements.Size(), centroids, &work_buffer[0]);
return true;
}
bool LevelAABBTree::Update()
{
bool modified = false;
for (unsigned int i = dynamicStartLine; i < mapLines.Size(); i++)
{
const auto &line = Level->lines[mapLines[i]];
AABBTreeLine treeline;
treeline.x = (float)line.v1->fX();
treeline.y = (float)line.v1->fY();
treeline.dx = (float)line.v2->fX() - treeline.x;
treeline.dy = (float)line.v2->fY() - treeline.y;
if (memcmp(&treelines[i], &treeline, sizeof(AABBTreeLine)))
{
TArray<int> path = FindNodePath(i, nodes.Size() - 1);
if (path.Size())
{
float x1 = (float)line.v1->fX();
float y1 = (float)line.v1->fY();
float x2 = (float)line.v2->fX();
float y2 = (float)line.v2->fY();
int nodeIndex = path[0];
nodes[nodeIndex].aabb_left = MIN(x1, x2);
nodes[nodeIndex].aabb_right = MAX(x1, x2);
nodes[nodeIndex].aabb_top = MIN(y1, y2);
nodes[nodeIndex].aabb_bottom = MAX(y1, y2);
for (unsigned int j = 1; j < path.Size(); j++)
{
auto &cur = nodes[path[j]];
const auto &left = nodes[cur.left_node];
const auto &right = nodes[cur.right_node];
cur.aabb_left = MIN(left.aabb_left, right.aabb_left);
cur.aabb_top = MIN(left.aabb_top, right.aabb_top);
cur.aabb_right = MAX(left.aabb_right, right.aabb_right);
cur.aabb_bottom = MAX(left.aabb_bottom, right.aabb_bottom);
}
treelines[i] = treeline;
modified = true;
}
}
}
return modified;
}
TArray<int> LevelAABBTree::FindNodePath(unsigned int line, unsigned int node)
{
const AABBTreeNode &n = nodes[node];
if (n.aabb_left > treelines[line].x || n.aabb_right < treelines[line].x ||
n.aabb_top > treelines[line].y || n.aabb_bottom < treelines[line].y)
{
return {};
}
TArray<int> path;
if (n.line_index == -1)
{
path = FindNodePath(line, n.left_node);
if (path.Size() == 0)
path = FindNodePath(line, n.right_node);
if (path.Size())
path.Push(node);
}
else if (n.line_index == (int)line)
{
path.Push(node);
}
return path;
}
double LevelAABBTree::RayTest(const DVector3 &ray_start, const DVector3 &ray_end)
{
// Precalculate some of the variables used by the ray/line intersection test
DVector2 raydelta = ray_end - ray_start;
double raydist2 = raydelta | raydelta;
DVector2 raynormal = DVector2(raydelta.Y, -raydelta.X);
double rayd = raynormal | ray_start;
if (raydist2 < 1.0)
return 1.0f;
double hit_fraction = 1.0;
// Walk the tree nodes
int stack[32];
int stack_pos = 1;
stack[0] = nodes.Size() - 1; // root node is the last node in the list
while (stack_pos > 0)
{
int node_index = stack[stack_pos - 1];
if (!OverlapRayAABB(ray_start, ray_end, nodes[node_index]))
{
// If the ray doesn't overlap this node's AABB we're done for this subtree
stack_pos--;
}
else if (nodes[node_index].line_index != -1) // isLeaf(node_index)
{
// We reached a leaf node. Do a ray/line intersection test to see if we hit the line.
hit_fraction = MIN(IntersectRayLine(ray_start, ray_end, nodes[node_index].line_index, raydelta, rayd, raydist2), hit_fraction);
stack_pos--;
}
else if (stack_pos == 32)
{
stack_pos--; // stack overflow - tree is too deep!
}
else
{
// The ray overlaps the node's AABB. Examine its child nodes.
stack[stack_pos - 1] = nodes[node_index].left_node;
stack[stack_pos] = nodes[node_index].right_node;
stack_pos++;
}
}
return hit_fraction;
}
bool LevelAABBTree::OverlapRayAABB(const DVector2 &ray_start2d, const DVector2 &ray_end2d, const AABBTreeNode &node)
{
// To do: simplify test to use a 2D test
DVector3 ray_start = DVector3(ray_start2d, 0.0);
DVector3 ray_end = DVector3(ray_end2d, 0.0);
DVector3 aabb_min = DVector3(node.aabb_left, node.aabb_top, -1.0);
DVector3 aabb_max = DVector3(node.aabb_right, node.aabb_bottom, 1.0);
// Standard 3D ray/AABB overlapping test.
// The details for the math here can be found in Real-Time Rendering, 3rd Edition.
// We could use a 2D test here instead, which would probably simplify the math.
DVector3 c = (ray_start + ray_end) * 0.5f;
DVector3 w = ray_end - c;
DVector3 h = (aabb_max - aabb_min) * 0.5f; // aabb.extents();
c -= (aabb_max + aabb_min) * 0.5f; // aabb.center();
DVector3 v = DVector3(fabs(w.X), fabs(w.Y), fabs(w.Z));
if (fabs(c.X) > v.X + h.X || fabs(c.Y) > v.Y + h.Y || fabs(c.Z) > v.Z + h.Z)
return false; // disjoint;
if (fabs(c.Y * w.Z - c.Z * w.Y) > h.Y * v.Z + h.Z * v.Y ||
fabs(c.X * w.Z - c.Z * w.X) > h.X * v.Z + h.Z * v.X ||
fabs(c.X * w.Y - c.Y * w.X) > h.X * v.Y + h.Y * v.X)
return false; // disjoint;
return true; // overlap;
}
double LevelAABBTree::IntersectRayLine(const DVector2 &ray_start, const DVector2 &ray_end, int line_index, const DVector2 &raydelta, double rayd, double raydist2)
{
// Check if two line segments intersects (the ray and the line).
// The math below does this by first finding the fractional hit for an infinitely long ray line.
// If that hit is within the line segment (0 to 1 range) then it calculates the fractional hit for where the ray would hit.
//
// This algorithm is homemade - I would not be surprised if there's a much faster method out there.
const double epsilon = 0.0000001;
const AABBTreeLine &line = treelines[line_index];
DVector2 raynormal = DVector2(raydelta.Y, -raydelta.X);
DVector2 line_pos(line.x, line.y);
DVector2 line_delta(line.dx, line.dy);
double den = raynormal | line_delta;
if (fabs(den) > epsilon)
{
double t_line = (rayd - (raynormal | line_pos)) / den;
if (t_line >= 0.0 && t_line <= 1.0)
{
DVector2 linehitdelta = line_pos + line_delta * t_line - ray_start;
double t = (raydelta | linehitdelta) / raydist2;
return t > 0.0 ? t : 1.0;
}
}
return 1.0;
}
int LevelAABBTree::GenerateTreeNode(int *lines, int num_lines, const FVector2 *centroids, int *work_buffer)
{
if (num_lines == 0)
return -1;
// Find bounding box and median of the lines
FVector2 median = FVector2(0.0f, 0.0f);
FVector2 aabb_min, aabb_max;
auto &maplines = Level->lines;
aabb_min.X = (float)maplines[mapLines[lines[0]]].v1->fX();
aabb_min.Y = (float)maplines[mapLines[lines[0]]].v1->fY();
aabb_max = aabb_min;
for (int i = 0; i < num_lines; i++)
{
float x1 = (float)maplines[mapLines[lines[i]]].v1->fX();
float y1 = (float)maplines[mapLines[lines[i]]].v1->fY();
float x2 = (float)maplines[mapLines[lines[i]]].v2->fX();
float y2 = (float)maplines[mapLines[lines[i]]].v2->fY();
aabb_min.X = MIN(aabb_min.X, x1);
aabb_min.X = MIN(aabb_min.X, x2);
aabb_min.Y = MIN(aabb_min.Y, y1);
aabb_min.Y = MIN(aabb_min.Y, y2);
aabb_max.X = MAX(aabb_max.X, x1);
aabb_max.X = MAX(aabb_max.X, x2);
aabb_max.Y = MAX(aabb_max.Y, y1);
aabb_max.Y = MAX(aabb_max.Y, y2);
median += centroids[mapLines[lines[i]]];
}
median /= (float)num_lines;
if (num_lines == 1) // Leaf node
{
nodes.Push(AABBTreeNode(aabb_min, aabb_max, lines[0]));
return (int)nodes.Size() - 1;
}
// Find the longest axis
float axis_lengths[2] =
{
aabb_max.X - aabb_min.X,
aabb_max.Y - aabb_min.Y
};
int axis_order[2] = { 0, 1 };
FVector2 axis_plane[2] = { FVector2(1.0f, 0.0f), FVector2(0.0f, 1.0f) };
std::sort(axis_order, axis_order + 2, [&](int a, int b) { return axis_lengths[a] > axis_lengths[b]; });
// Try sort at longest axis, then if that fails then the other one.
// We place the sorted lines into work_buffer and then move the result back to the lines list when done.
int left_count, right_count;
for (int attempt = 0; attempt < 2; attempt++)
{
// Find the sort plane for axis
FVector2 axis = axis_plane[axis_order[attempt]];
FVector3 plane(axis, -(median | axis));
// Sort lines into two based ib whether the line center is on the front or back side of a plane
left_count = 0;
right_count = 0;
for (int i = 0; i < num_lines; i++)
{
int line_index = lines[i];
float side = FVector3(centroids[mapLines[lines[i]]], 1.0f) | plane;
if (side >= 0.0f)
{
work_buffer[left_count] = line_index;
left_count++;
}
else
{
work_buffer[num_lines + right_count] = line_index;
right_count++;
}
}
if (left_count != 0 && right_count != 0)
break;
}
// Check if something went wrong when sorting and do a random sort instead
if (left_count == 0 || right_count == 0)
{
left_count = num_lines / 2;
right_count = num_lines - left_count;
}
else
{
// Move result back into lines list:
for (int i = 0; i < left_count; i++)
lines[i] = work_buffer[i];
for (int i = 0; i < right_count; i++)
lines[i + left_count] = work_buffer[num_lines + i];
}
// Create child nodes:
int left_index = -1;
int right_index = -1;
if (left_count > 0)
left_index = GenerateTreeNode(lines, left_count, centroids, work_buffer);
if (right_count > 0)
right_index = GenerateTreeNode(lines + left_count, right_count, centroids, work_buffer);
// Store resulting node and return its index
nodes.Push(AABBTreeNode(aabb_min, aabb_max, left_index, right_index));
return (int)nodes.Size() - 1;
}
}

View file

@ -0,0 +1,91 @@
#pragma once
#include "tarray.h"
#include "vectors.h"
struct FLevelLocals;
namespace hwrenderer
{
// Node in a binary AABB tree
struct AABBTreeNode
{
AABBTreeNode(const FVector2 &aabb_min, const FVector2 &aabb_max, int line_index) : aabb_left(aabb_min.X), aabb_top(aabb_min.Y), aabb_right(aabb_max.X), aabb_bottom(aabb_max.Y), left_node(-1), right_node(-1), line_index(line_index) { }
AABBTreeNode(const FVector2 &aabb_min, const FVector2 &aabb_max, int left, int right) : aabb_left(aabb_min.X), aabb_top(aabb_min.Y), aabb_right(aabb_max.X), aabb_bottom(aabb_max.Y), left_node(left), right_node(right), line_index(-1) { }
// Axis aligned bounding box for the node
float aabb_left, aabb_top;
float aabb_right, aabb_bottom;
// Child node indices
int left_node;
int right_node;
// AABBTreeLine index if it is a leaf node. Index is -1 if it is not.
int line_index;
// Padding to keep 16-byte length (this structure is uploaded to the GPU)
int padding;
};
// Line segment for leaf nodes in an AABB tree
struct AABBTreeLine
{
float x, y;
float dx, dy;
};
// Axis aligned bounding box tree used for ray testing treelines.
class LevelAABBTree
{
public:
// Constructs a tree for the current level
LevelAABBTree(FLevelLocals *lev);
// Shoot a ray from ray_start to ray_end and return the closest hit as a fractional value between 0 and 1. Returns 1 if no line was hit.
double RayTest(const DVector3 &ray_start, const DVector3 &ray_end);
bool Update();
const void *Nodes() const { return nodes.Data(); }
const void *Lines() const { return treelines.Data(); }
size_t NodesSize() const { return nodes.Size() * sizeof(AABBTreeNode); }
size_t LinesSize() const { return treelines.Size() * sizeof(AABBTreeLine); }
const void *DynamicNodes() const { return nodes.Data() + dynamicStartNode; }
const void *DynamicLines() const { return treelines.Data() + dynamicStartLine; }
size_t DynamicNodesSize() const { return (nodes.Size() - dynamicStartNode) * sizeof(AABBTreeNode); }
size_t DynamicLinesSize() const { return (treelines.Size() - dynamicStartLine) * sizeof(AABBTreeLine); }
size_t DynamicNodesOffset() const { return dynamicStartNode * sizeof(AABBTreeNode); }
size_t DynamicLinesOffset() const { return dynamicStartLine * sizeof(AABBTreeLine); }
private:
bool GenerateTree(const FVector2 *centroids, bool dynamicsubtree);
// Test if a ray overlaps an AABB node or not
bool OverlapRayAABB(const DVector2 &ray_start2d, const DVector2 &ray_end2d, const AABBTreeNode &node);
// Intersection test between a ray and a line segment
double IntersectRayLine(const DVector2 &ray_start, const DVector2 &ray_end, int line_index, const DVector2 &raydelta, double rayd, double raydist2);
// Generate a tree node and its children recursively
int GenerateTreeNode(int *treelines, int num_lines, const FVector2 *centroids, int *work_buffer);
TArray<int> FindNodePath(unsigned int line, unsigned int node);
// Nodes in the AABB tree. Last node is the root node.
TArray<AABBTreeNode> nodes;
// Line segments for the leaf nodes in the tree.
TArray<AABBTreeLine> treelines;
int dynamicStartNode = 0;
int dynamicStartLine = 0;
TArray<int> mapLines;
FLevelLocals *Level;
};
} // namespace

View file

@ -0,0 +1,150 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2002-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 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_dynlight1.cpp
** dynamic light application
**
**/
#include "actorinlines.h"
#include "hw_dynlightdata.h"
// 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;
//==========================================================================
//
// Light related CVARs
//
//==========================================================================
// These shouldn't be called 'gl...' anymore...
CVAR (Bool, gl_light_sprites, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG);
CVAR (Bool, gl_light_particles, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG);
//==========================================================================
//
// Sets up the parameters to render one dynamic light onto one plane
//
//==========================================================================
bool FDynLightData::GetLight(int group, Plane & p, FDynamicLight * light, bool checkside)
{
DVector3 pos = light->PosRelative(group);
float radius = (light->GetRadius());
auto dist = fabs(p.DistToPoint((float)pos.X, (float)pos.Z, (float)pos.Y));
if (radius <= 0.f) return false;
if (dist > radius) return false;
if (checkside && p.PointOnSide((float)pos.X, (float)pos.Z, (float)pos.Y))
{
return false;
}
AddLightToList(group, light, false);
return true;
}
//==========================================================================
//
// Add one dynamic light to the light data list
//
//==========================================================================
void FDynLightData::AddLightToList(int group, FDynamicLight * light, bool forceAttenuate)
{
int i = 0;
DVector3 pos = light->PosRelative(group);
float radius = light->GetRadius();
float cs;
if (light->IsAdditive())
{
cs = 0.2f;
i = 2;
}
else
{
cs = 1.0f;
}
float r = light->GetRed() / 255.0f * cs;
float g = light->GetGreen() / 255.0f * cs;
float b = light->GetBlue() / 255.0f * cs;
if (light->IsSubtractive())
{
DVector3 v(r, g, b);
float length = (float)v.Length();
r = length - r;
g = length - g;
b = length - b;
i = 1;
}
float shadowIndex = light->mShadowmapIndex + 1.0f;
// Store attenuate flag in the sign bit of the float.
if (light->IsAttenuated() || forceAttenuate) shadowIndex = -shadowIndex;
float lightType = 0.0f;
float spotInnerAngle = 0.0f;
float spotOuterAngle = 0.0f;
float spotDirX = 0.0f;
float spotDirY = 0.0f;
float spotDirZ = 0.0f;
if (light->IsSpot())
{
lightType = 1.0f;
spotInnerAngle = (float)light->pSpotInnerAngle->Cos();
spotOuterAngle = (float)light->pSpotOuterAngle->Cos();
DAngle negPitch = -*light->pPitch;
DAngle Angle = light->target->Angles.Yaw;
double xzLen = negPitch.Cos();
spotDirX = float(-Angle.Cos() * xzLen);
spotDirY = float(-negPitch.Sin());
spotDirZ = float(-Angle.Sin() * xzLen);
}
float *data = &arrays[i][arrays[i].Reserve(16)];
data[0] = float(pos.X);
data[1] = float(pos.Z);
data[2] = float(pos.Y);
data[3] = radius;
data[4] = r;
data[5] = g;
data[6] = b;
data[7] = shadowIndex;
data[8] = spotDirX;
data[9] = spotDirY;
data[10] = spotDirZ;
data[11] = lightType;
data[12] = spotInnerAngle;
data[13] = spotOuterAngle;
data[14] = 0.0f; // unused
data[15] = 0.0f; // unused
}

View file

@ -0,0 +1,64 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2005-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/
//
//--------------------------------------------------------------------------
//
#ifndef __GLC_DYNLIGHT_H
#define __GLC_DYNLIGHT_H
#include "a_dynlight.h"
struct FDynLightData
{
TArray<float> arrays[3];
void Clear()
{
arrays[0].Clear();
arrays[1].Clear();
arrays[2].Clear();
}
void Combine(int *siz, int max)
{
siz[0] = arrays[0].Size();
siz[1] = siz[0] + arrays[1].Size();
siz[2] = siz[1] + arrays[2].Size();
arrays[0].Resize(arrays[0].Size() + arrays[1].Size() + arrays[2].Size());
memcpy(&arrays[0][siz[0]], &arrays[1][0], arrays[1].Size() * sizeof(float));
memcpy(&arrays[0][siz[1]], &arrays[2][0], arrays[2].Size() * sizeof(float));
siz[0]>>=2;
siz[1]>>=2;
siz[2]>>=2;
if (siz[0] > max) siz[0] = max;
if (siz[1] > max) siz[1] = max;
if (siz[2] > max) siz[2] = max;
}
bool GetLight(int group, Plane & p, FDynamicLight * light, bool checkside);
void AddLightToList(int group, FDynamicLight * light, bool forceAttenuate);
};
extern thread_local FDynLightData lightdata;
#endif

View file

@ -0,0 +1,144 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2014-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_lightbuffer.cpp
** Buffer data maintenance for dynamic lights
**
**/
#include "hw_lightbuffer.h"
#include "hwrenderer/utility/hw_clock.h"
#include "hwrenderer/dynlights/hw_dynlightdata.h"
#include "hwrenderer/data/shaderuniforms.h"
static const int ELEMENTS_PER_LIGHT = 4; // each light needs 4 vec4's.
static const int ELEMENT_SIZE = (4*sizeof(float));
FLightBuffer::FLightBuffer()
{
int maxNumberOfLights = 80000;
mBufferSize = maxNumberOfLights * ELEMENTS_PER_LIGHT;
mByteSize = mBufferSize * ELEMENT_SIZE;
// Hack alert: On Intel's GL driver SSBO's perform quite worse than UBOs.
// We only want to disable using SSBOs for lights but not disable the feature entirely.
// Note that using an uniform buffer here will limit the number of lights per surface so it isn't done for NVidia and AMD.
if (screen->hwcaps & RFL_SHADER_STORAGE_BUFFER && !strstr(screen->gl_vendorstring, "Intel"))
{
mBufferType = true;
mBlockAlign = 0;
mBlockSize = mBufferSize;
mMaxUploadSize = mBlockSize;
}
else
{
mBufferType = false;
mBlockSize = screen->maxuniformblock / ELEMENT_SIZE;
mBlockAlign = screen->uniformblockalignment / ELEMENT_SIZE;
mMaxUploadSize = (mBlockSize - mBlockAlign);
mByteSize += screen->maxuniformblock; // to avoid mapping beyond the end of the buffer.
}
mBuffer = screen->CreateDataBuffer(LIGHTBUF_BINDINGPOINT, mBufferType);
mBuffer->SetData(mByteSize, nullptr, false);
Clear();
}
FLightBuffer::~FLightBuffer()
{
delete mBuffer;
}
void FLightBuffer::Clear()
{
mIndex = 0;
mLastMappedIndex = UINT_MAX;
}
int FLightBuffer::UploadLights(FDynLightData &data)
{
// All meaasurements here are in vec4's.
int size0 = data.arrays[0].Size()/4;
int size1 = data.arrays[1].Size()/4;
int size2 = data.arrays[2].Size()/4;
int totalsize = size0 + size1 + size2 + 1;
if (totalsize > (int)mMaxUploadSize)
{
int diff = totalsize - (int)mMaxUploadSize;
size2 -= diff;
if (size2 < 0)
{
size1 += size2;
size2 = 0;
}
if (size1 < 0)
{
size0 += size1;
size1 = 0;
}
totalsize = size0 + size1 + size2 + 1;
}
float *mBufferPointer = (float*)mBuffer->Memory();
assert(mBufferPointer != nullptr);
if (mBufferPointer == nullptr) return -1;
if (totalsize <= 1) return -1; // there are no lights
unsigned thisindex = mIndex.fetch_add(totalsize);
float parmcnt[] = { 0, float(size0), float(size0 + size1), float(size0 + size1 + size2) };
if (thisindex + totalsize <= mBufferSize)
{
float *copyptr = mBufferPointer + thisindex*4;
memcpy(&copyptr[0], parmcnt, ELEMENT_SIZE);
memcpy(&copyptr[4], &data.arrays[0][0], size0 * ELEMENT_SIZE);
memcpy(&copyptr[4 + 4*size0], &data.arrays[1][0], size1 * ELEMENT_SIZE);
memcpy(&copyptr[4 + 4*(size0 + size1)], &data.arrays[2][0], size2 * ELEMENT_SIZE);
return thisindex;
}
else
{
return -1; // Buffer is full. Since it is being used live at the point of the upload we cannot do much here but to abort.
}
}
int FLightBuffer::DoBindUBO(unsigned int index)
{
// this function will only get called if a uniform buffer is used. For a shader storage buffer we only need to bind the buffer once at the start.
unsigned int offset = (index / mBlockAlign) * mBlockAlign;
if (offset != mLastMappedIndex)
{
mLastMappedIndex = offset;
mBuffer->BindRange(offset * ELEMENT_SIZE, mBlockSize * ELEMENT_SIZE);
}
return (index - offset);
}

View file

@ -0,0 +1,69 @@
#ifndef __GL_LIGHTBUFFER_H
#define __GL_LIGHTBUFFER_H
#include "tarray.h"
#include "hwrenderer/dynlights/hw_dynlightdata.h"
#include "hwrenderer/data/buffers.h"
#include <atomic>
#include <mutex>
class FRenderState;
class FLightBuffer
{
IDataBuffer *mBuffer;
bool mBufferType;
std::atomic<unsigned int> mIndex;
unsigned int mLastMappedIndex;
unsigned int mBlockAlign;
unsigned int mBlockSize;
unsigned int mBufferSize;
unsigned int mByteSize;
unsigned int mMaxUploadSize;
void CheckSize();
public:
FLightBuffer();
~FLightBuffer();
void Clear();
int UploadLights(FDynLightData &data);
void Map() { mBuffer->Map(); }
void Unmap() { mBuffer->Unmap(); }
unsigned int GetBlockSize() const { return mBlockSize; }
bool GetBufferType() const { return mBufferType; }
int DoBindUBO(unsigned int index);
int ShaderIndex(unsigned int index) const
{
if (mBlockAlign == 0) return index;
// This must match the math in BindUBO.
unsigned int offset = (index / mBlockAlign) * mBlockAlign;
return int(index-offset);
}
// Only relevant for OpenGL, so this does not need access to the render state.
int BindUBO(int index)
{
if (!mBufferType && index > -1)
{
index = DoBindUBO(index);
}
return index;
}
// The parameter is a reminder for Vulkan.
void BindBase()
{
mBuffer->BindBase();
}
};
int gl_SetDynModelLight(AActor *self, int dynlightindex);
#endif

View file

@ -0,0 +1,220 @@
//
//---------------------------------------------------------------------------
// 1D dynamic shadow maps (API independent part)
// Copyright(C) 2017 Magnus Norddahl
// 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 "hwrenderer/dynlights/hw_shadowmap.h"
#include "hwrenderer/utility/hw_cvars.h"
#include "hwrenderer/dynlights/hw_dynlightdata.h"
#include "hwrenderer/data/buffers.h"
#include "stats.h"
#include "g_levellocals.h"
/*
The 1D shadow maps are stored in a 1024x1024 texture as float depth values (R32F).
Each line in the texture is assigned to a single light. For example, to grab depth values for light 20
the fragment shader (main.fp) needs to sample from row 20. That is, the V texture coordinate needs
to be 20.5/1024.
The texel row for each light is split into four parts. One for each direction, like a cube texture,
but then only in 2D where this reduces itself to a square. When main.fp samples from the shadow map
it first decides in which direction the fragment is (relative to the light), like cubemap sampling does
for 3D, but once again just for the 2D case.
Texels 0-255 is Y positive, 256-511 is X positive, 512-767 is Y negative and 768-1023 is X negative.
Generating the shadow map itself is done by FShadowMap::Update(). The shadow map texture's FBO is
bound and then a screen quad is drawn to make a fragment shader cover all texels. For each fragment
it shoots a ray and collects the distance to what it hit.
The shadowmap.fp shader knows which light and texel it is processing by mapping gl_FragCoord.y back
to the light index, and it knows which direction to ray trace by looking at gl_FragCoord.x. For
example, if gl_FragCoord.y is 20.5, then it knows its processing light 20, and if gl_FragCoord.x is
127.5, then it knows we are shooting straight ahead for the Y positive direction.
Ray testing is done by uploading two GPU storage buffers - one holding AABB tree nodes, and one with
the line segments at the leaf nodes of the tree. The fragment shader then performs a test same way
as on the CPU, except everything uses indexes as pointers are not allowed in GLSL.
*/
cycle_t IShadowMap::UpdateCycles;
int IShadowMap::LightsProcessed;
int IShadowMap::LightsShadowmapped;
ADD_STAT(shadowmap)
{
FString out;
out.Format("upload=%04.2f ms lights=%d shadowmapped=%d", IShadowMap::UpdateCycles.TimeMS(), IShadowMap::LightsProcessed, IShadowMap::LightsShadowmapped);
return out;
}
CUSTOM_CVAR(Int, gl_shadowmap_quality, 512, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
switch (self)
{
case 128:
case 256:
case 512:
case 1024:
break;
default:
self = 128;
break;
}
}
CUSTOM_CVAR (Bool, gl_light_shadowmap, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
if (!self) for (auto Level : AllLevels())
{
auto light = Level->lights;
while (light)
{
light->mShadowmapIndex = 1024;
light = light->next;
}
}
}
bool IShadowMap::ShadowTest(FDynamicLight *light, const DVector3 &pos)
{
if (light->shadowmapped && light->GetRadius() > 0.0 && IsEnabled() && mAABBTree)
return mAABBTree->RayTest(light->Pos, pos) >= 1.0f;
else
return true;
}
bool IShadowMap::IsEnabled() const
{
return gl_light_shadowmap && (screen->hwcaps & RFL_SHADER_STORAGE_BUFFER);
}
void IShadowMap::CollectLights()
{
if (mLights.Size() != 1024 * 4) mLights.Resize(1024 * 4);
int lightindex = 0;
// Todo: this should go through the blockmap in a spiral pattern around the player so that closer lights are preferred.
for (auto light = level.lights; light; light = light->next)
{
LightsProcessed++;
if (light->shadowmapped && light->IsActive() && lightindex < 1024 * 4)
{
LightsShadowmapped++;
light->mShadowmapIndex = lightindex >> 2;
mLights[lightindex] = (float)light->X();
mLights[lightindex+1] = (float)light->Y();
mLights[lightindex+2] = (float)light->Z();
mLights[lightindex+3] = light->GetRadius();
lightindex += 4;
}
else
{
light->mShadowmapIndex = 1024;
}
}
for (; lightindex < 1024 * 4; lightindex++)
{
mLights[lightindex] = 0;
}
}
bool IShadowMap::ValidateAABBTree(FLevelLocals *Level)
{
// Just comparing the level info is not enough. If two MAPINFO-less levels get played after each other,
// they can both refer to the same default level info.
if (Level->info != mLastLevel && (Level->nodes.Size() != mLastNumNodes || Level->segs.Size() != mLastNumSegs))
{
mAABBTree.reset();
mLastLevel = Level->info;
mLastNumNodes = Level->nodes.Size();
mLastNumSegs = Level->segs.Size();
}
if (mAABBTree)
return true;
mAABBTree.reset(new hwrenderer::LevelAABBTree(Level));
return false;
}
bool IShadowMap::PerformUpdate()
{
UpdateCycles.Reset();
LightsProcessed = 0;
LightsShadowmapped = 0;
if (IsEnabled())
{
UpdateCycles.Clock();
UploadAABBTree();
UploadLights();
mLightList->BindBase();
mNodesBuffer->BindBase();
mLinesBuffer->BindBase();
return true;
}
return false;
}
void IShadowMap::UploadLights()
{
CollectLights();
if (mLightList == nullptr)
mLightList = screen->CreateDataBuffer(4, true);
mLightList->SetData(sizeof(float) * mLights.Size(), &mLights[0]);
}
void IShadowMap::UploadAABBTree()
{
if (!ValidateAABBTree(&level))
{
if (!mNodesBuffer)
mNodesBuffer = screen->CreateDataBuffer(2, true);
mNodesBuffer->SetData(mAABBTree->NodesSize(), mAABBTree->Nodes());
if (!mLinesBuffer)
mLinesBuffer = screen->CreateDataBuffer(3, true);
mLinesBuffer->SetData(mAABBTree->LinesSize(), mAABBTree->Lines());
}
else if (mAABBTree->Update())
{
mNodesBuffer->SetSubData(mAABBTree->DynamicNodesOffset(), mAABBTree->DynamicNodesSize(), mAABBTree->DynamicNodes());
mLinesBuffer->SetSubData(mAABBTree->DynamicLinesOffset(), mAABBTree->DynamicLinesSize(), mAABBTree->DynamicLines());
}
}
IShadowMap::~IShadowMap()
{
if (mLightList) delete mLightList;
if (mNodesBuffer) delete mNodesBuffer;
if (mLinesBuffer) delete mLinesBuffer;
}

View file

@ -0,0 +1,66 @@
#pragma once
#include "hw_aabbtree.h"
#include "stats.h"
#include <memory>
struct FDynamicLight;
struct level_info_t;
class IDataBuffer;
struct FLevelLocals;
class IShadowMap
{
public:
IShadowMap() { }
virtual ~IShadowMap();
// Test if a world position is in shadow relative to the specified light and returns false if it is
bool ShadowTest(FDynamicLight *light, const DVector3 &pos);
// Returns true if gl_light_shadowmap is enabled and supported by the hardware
bool IsEnabled() const;
static cycle_t UpdateCycles;
static int LightsProcessed;
static int LightsShadowmapped;
bool PerformUpdate();
void FinishUpdate()
{
UpdateCycles.Clock();
}
protected:
void CollectLights();
bool ValidateAABBTree(FLevelLocals *lev);
// Upload the AABB-tree to the GPU
void UploadAABBTree();
// Upload light list to the GPU
void UploadLights();
// Working buffer for creating the list of lights. Stored here to avoid allocating memory each frame
TArray<float> mLights;
// Used to detect when a level change requires the AABB tree to be regenerated
level_info_t *mLastLevel = nullptr;
unsigned mLastNumNodes = 0;
unsigned mLastNumSegs = 0;
// AABB-tree of the level, used for ray tests
std::unique_ptr<hwrenderer::LevelAABBTree> mAABBTree;
IShadowMap(const IShadowMap &) = delete;
IShadowMap &operator=(IShadowMap &) = delete;
// OpenGL storage buffer with the list of lights in the shadow map texture
IDataBuffer *mLightList = nullptr;
// OpenGL storage buffers for the AABB tree
IDataBuffer *mNodesBuffer = nullptr;
IDataBuffer *mLinesBuffer = nullptr;
};

View file

@ -0,0 +1,222 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2005-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_models.cpp
**
** hardware renderer model handling code
**
**/
#include "w_wad.h"
#include "g_game.h"
#include "doomstat.h"
#include "g_level.h"
#include "r_state.h"
#include "d_player.h"
#include "g_levellocals.h"
#include "i_time.h"
#include "cmdlib.h"
#include "hwrenderer/textures/hw_material.h"
#include "hwrenderer/data/buffers.h"
#include "hwrenderer/data/flatvertices.h"
#include "hwrenderer/scene/hw_drawinfo.h"
#include "hwrenderer/scene/hw_renderstate.h"
#include "hwrenderer/scene/hw_portal.h"
#include "hw_models.h"
CVAR(Bool, gl_light_models, true, CVAR_ARCHIVE)
VSMatrix FGLModelRenderer::GetViewToWorldMatrix()
{
VSMatrix objectToWorldMatrix;
di->VPUniforms.mViewMatrix.inverseMatrix(objectToWorldMatrix);
return objectToWorldMatrix;
}
void FGLModelRenderer::BeginDrawModel(AActor *actor, FSpriteModelFrame *smf, const VSMatrix &objectToWorldMatrix, bool mirrored)
{
state.SetDepthFunc(DF_LEqual);
state.EnableTexture(true);
// [BB] In case the model should be rendered translucent, do back face culling.
// This solves a few of the problems caused by the lack of depth sorting.
// [Nash] Don't do back face culling if explicitly specified in MODELDEF
// TO-DO: Implement proper depth sorting.
if (!(actor->RenderStyle == DefaultRenderStyle()) && !(smf->flags & MDL_DONTCULLBACKFACES))
{
state.SetCulling((mirrored ^ screen->mPortalState->isMirrored()) ? Cull_CCW : Cull_CW);
}
state.mModelMatrix = objectToWorldMatrix;
state.EnableModelMatrix(true);
}
void FGLModelRenderer::EndDrawModel(AActor *actor, FSpriteModelFrame *smf)
{
state.EnableModelMatrix(false);
state.SetDepthFunc(DF_Less);
if (!(actor->RenderStyle == DefaultRenderStyle()) && !(smf->flags & MDL_DONTCULLBACKFACES))
state.SetCulling(Cull_None);
}
void FGLModelRenderer::BeginDrawHUDModel(AActor *actor, const VSMatrix &objectToWorldMatrix, bool mirrored)
{
state.SetDepthFunc(DF_LEqual);
// [BB] In case the model should be rendered translucent, do back face culling.
// This solves a few of the problems caused by the lack of depth sorting.
// TO-DO: Implement proper depth sorting.
if (!(actor->RenderStyle == DefaultRenderStyle()))
{
state.SetCulling((mirrored ^ screen->mPortalState->isMirrored()) ? Cull_CW : Cull_CCW);
}
state.mModelMatrix = objectToWorldMatrix;
state.EnableModelMatrix(true);
}
void FGLModelRenderer::EndDrawHUDModel(AActor *actor)
{
state.EnableModelMatrix(false);
state.SetDepthFunc(DF_Less);
if (!(actor->RenderStyle == DefaultRenderStyle()))
state.SetCulling(Cull_None);
}
IModelVertexBuffer *FGLModelRenderer::CreateVertexBuffer(bool needindex, bool singleframe)
{
return new FModelVertexBuffer(needindex, singleframe);
}
void FGLModelRenderer::SetInterpolation(double inter)
{
state.SetInterpolationFactor((float)inter);
}
void FGLModelRenderer::SetMaterial(FTexture *skin, bool clampNoFilter, int translation)
{
FMaterial * tex = FMaterial::ValidateTexture(skin, false);
state.SetMaterial(tex, clampNoFilter ? CLAMP_NOFILTER : CLAMP_NONE, translation, -1);
state.SetLightIndex(modellightindex);
}
void FGLModelRenderer::DrawArrays(int start, int count)
{
state.Draw(DT_Triangles, start, count);
}
void FGLModelRenderer::DrawElements(int numIndices, size_t offset)
{
state.DrawIndexed(DT_Triangles, int(offset / sizeof(unsigned int)), numIndices);
}
//===========================================================================
//
//
//
//===========================================================================
FModelVertexBuffer::FModelVertexBuffer(bool needindex, bool singleframe)
{
mVertexBuffer = screen->CreateVertexBuffer();
mIndexBuffer = needindex ? screen->CreateIndexBuffer() : nullptr;
static const FVertexBufferAttribute format[] = {
{ 0, VATTR_VERTEX, VFmt_Float3, (int)myoffsetof(FModelVertex, x) },
{ 0, VATTR_TEXCOORD, VFmt_Float2, (int)myoffsetof(FModelVertex, u) },
{ 0, VATTR_NORMAL, VFmt_Packed_A2R10G10B10, (int)myoffsetof(FModelVertex, packedNormal) },
{ 1, VATTR_VERTEX2, VFmt_Float3, (int)myoffsetof(FModelVertex, x) },
{ 1, VATTR_NORMAL2, VFmt_Packed_A2R10G10B10, (int)myoffsetof(FModelVertex, packedNormal) }
};
mVertexBuffer->SetFormat(2, 5, sizeof(FModelVertex), format);
}
//===========================================================================
//
//
//
//===========================================================================
FModelVertexBuffer::~FModelVertexBuffer()
{
if (mIndexBuffer) delete mIndexBuffer;
delete mVertexBuffer;
}
//===========================================================================
//
//
//
//===========================================================================
FModelVertex *FModelVertexBuffer::LockVertexBuffer(unsigned int size)
{
return static_cast<FModelVertex*>(mVertexBuffer->Lock(size * sizeof(FModelVertex)));
}
//===========================================================================
//
//
//
//===========================================================================
void FModelVertexBuffer::UnlockVertexBuffer()
{
mVertexBuffer->Unlock();
}
//===========================================================================
//
//
//
//===========================================================================
unsigned int *FModelVertexBuffer::LockIndexBuffer(unsigned int size)
{
if (mIndexBuffer) return static_cast<unsigned int*>(mIndexBuffer->Lock(size * sizeof(unsigned int)));
else return nullptr;
}
//===========================================================================
//
//
//
//===========================================================================
void FModelVertexBuffer::UnlockIndexBuffer()
{
if (mIndexBuffer) mIndexBuffer->Unlock();
}
//===========================================================================
//
//
//
//===========================================================================
void FModelVertexBuffer::SetupFrame(FModelRenderer *renderer, unsigned int frame1, unsigned int frame2, unsigned int size)
{
auto &state = static_cast<FGLModelRenderer*>(renderer)->state;
state.SetVertexBuffer(mVertexBuffer, frame1, frame2);
if (mIndexBuffer) state.SetIndexBuffer(mIndexBuffer);
}

View file

@ -0,0 +1,74 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2005-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/
//
//--------------------------------------------------------------------------
//
#pragma once
#include "tarray.h"
#include "p_pspr.h"
#include "r_data/voxels.h"
#include "r_data/models/models.h"
class GLSprite;
struct HWDrawInfo;
class FRenderState;
class FModelVertexBuffer : public IModelVertexBuffer
{
IVertexBuffer *mVertexBuffer;
IIndexBuffer *mIndexBuffer;
public:
FModelVertexBuffer(bool needindex, bool singleframe);
~FModelVertexBuffer();
FModelVertex *LockVertexBuffer(unsigned int size) override;
void UnlockVertexBuffer() override;
unsigned int *LockIndexBuffer(unsigned int size) override;
void UnlockIndexBuffer() override;
void SetupFrame(FModelRenderer *renderer, unsigned int frame1, unsigned int frame2, unsigned int size) override;
};
class FGLModelRenderer : public FModelRenderer
{
friend class FModelVertexBuffer;
int modellightindex = -1;
HWDrawInfo *di;
FRenderState &state;
public:
FGLModelRenderer(HWDrawInfo *d, FRenderState &st, int mli) : modellightindex(mli), di(d), state(st)
{}
ModelRendererType GetType() const override { return GLModelRendererType; }
void BeginDrawModel(AActor *actor, FSpriteModelFrame *smf, const VSMatrix &objectToWorldMatrix, bool mirrored) override;
void EndDrawModel(AActor *actor, FSpriteModelFrame *smf) override;
IModelVertexBuffer *CreateVertexBuffer(bool needindex, bool singleframe) override;
VSMatrix GetViewToWorldMatrix() override;
void BeginDrawHUDModel(AActor *actor, const VSMatrix &objectToWorldMatrix, bool mirrored) override;
void EndDrawHUDModel(AActor *actor) override;
void SetInterpolation(double interpolation) override;
void SetMaterial(FTexture *skin, bool clampNoFilter, int translation) override;
void DrawArrays(int start, int count) override;
void DrawElements(int numIndices, size_t offset) override;
};

View file

@ -0,0 +1,856 @@
#include "v_video.h"
#include "hw_postprocess.h"
#include "hwrenderer/utility/hw_cvars.h"
#include "hwrenderer/postprocessing/hw_postprocess_cvars.h"
#include <random>
Postprocess hw_postprocess;
Postprocess::Postprocess()
{
Managers.Push(new PPBloom());
Managers.Push(new PPLensDistort());
Managers.Push(new PPFXAA());
Managers.Push(new PPCameraExposure());
Managers.Push(new PPColormap());
Managers.Push(new PPTonemap());
Managers.Push(new PPAmbientOcclusion());
}
Postprocess::~Postprocess()
{
for (unsigned int i = 0; i < Managers.Size(); i++)
delete Managers[i];
}
/////////////////////////////////////////////////////////////////////////////
void PPBloom::DeclareShaders()
{
hw_postprocess.Shaders["BloomCombine"] = { "shaders/glsl/bloomcombine.fp", "", {} };
hw_postprocess.Shaders["BloomExtract"] = { "shaders/glsl/bloomextract.fp", "", ExtractUniforms::Desc() };
hw_postprocess.Shaders["BlurVertical"] = { "shaders/glsl/blur.fp", "#define BLUR_VERTICAL\n", BlurUniforms::Desc() };
hw_postprocess.Shaders["BlurHorizontal"] = { "shaders/glsl/blur.fp", "#define BLUR_HORIZONTAL\n", BlurUniforms::Desc() };
}
void PPBloom::UpdateTextures()
{
int width = hw_postprocess.SceneWidth;
int height = hw_postprocess.SceneHeight;
// No scene, no bloom!
if (width <= 0 || height <= 0)
return;
int bloomWidth = (width + 1) / 2;
int bloomHeight = (height + 1) / 2;
for (int i = 0; i < NumBloomLevels; i++)
{
auto &blevel = levels[i];
blevel.VTexture.Format("Bloom.VTexture.%d", i);
blevel.HTexture.Format("Bloom.HTexture.%d", i);
blevel.Viewport.left = 0;
blevel.Viewport.top = 0;
blevel.Viewport.width = (bloomWidth + 1) / 2;
blevel.Viewport.height = (bloomHeight + 1) / 2;
PPTextureDesc texture = { blevel.Viewport.width, blevel.Viewport.height, PixelFormat::Rgba16f };
hw_postprocess.Textures[blevel.VTexture] = texture;
hw_postprocess.Textures[blevel.HTexture] = texture;
bloomWidth = blevel.Viewport.width;
bloomHeight = blevel.Viewport.height;
}
}
void PPBloom::UpdateSteps()
{
UpdateBlurSteps();
// Only bloom things if enabled and no special fixed light mode is active
if (!gl_bloom || hw_postprocess.fixedcm != CM_DEFAULT || gl_ssao_debug || hw_postprocess.SceneWidth <= 0 || hw_postprocess.SceneHeight <= 0)
{
hw_postprocess.Effects["BloomScene"] = {};
return;
}
TArray<PPStep> steps;
PPStep step;
ExtractUniforms extractUniforms;
extractUniforms.Scale = screen->SceneScale();
extractUniforms.Offset = screen->SceneOffset();
auto &level0 = levels[0];
// Extract blooming pixels from scene texture:
step.ShaderName = "BloomExtract";
step.Uniforms.Set(extractUniforms);
step.Viewport = level0.Viewport;
step.SetInputCurrent(0, PPFilterMode::Linear);
step.SetInputTexture(1, "Exposure.CameraTexture");
step.SetOutputTexture(level0.VTexture);
step.SetNoBlend();
steps.Push(step);
const float blurAmount = gl_bloom_amount;
BlurUniforms blurUniforms;
ComputeBlurSamples(7, blurAmount, blurUniforms.SampleWeights);
// Blur and downscale:
for (int i = 0; i < NumBloomLevels - 1; i++)
{
auto &blevel = levels[i];
auto &next = levels[i + 1];
steps.Push(BlurStep(blurUniforms, blevel.VTexture, blevel.HTexture, blevel.Viewport, false));
steps.Push(BlurStep(blurUniforms, blevel.HTexture, blevel.VTexture, blevel.Viewport, true));
// Linear downscale:
step.ShaderName = "BloomCombine";
step.Uniforms.Clear();
step.Viewport = next.Viewport;
step.SetInputTexture(0, blevel.VTexture, PPFilterMode::Linear);
step.SetOutputTexture(next.VTexture);
step.SetNoBlend();
steps.Push(step);
}
// Blur and upscale:
for (int i = NumBloomLevels - 1; i > 0; i--)
{
auto &blevel = levels[i];
auto &next = levels[i - 1];
steps.Push(BlurStep(blurUniforms, blevel.VTexture, blevel.HTexture, blevel.Viewport, false));
steps.Push(BlurStep(blurUniforms, blevel.HTexture, blevel.VTexture, blevel.Viewport, true));
// Linear upscale:
step.ShaderName = "BloomCombine";
step.Uniforms.Clear();
step.Viewport = next.Viewport;
step.SetInputTexture(0, blevel.VTexture, PPFilterMode::Linear);
step.SetOutputTexture(next.VTexture);
step.SetNoBlend();
steps.Push(step);
}
steps.Push(BlurStep(blurUniforms, level0.VTexture, level0.HTexture, level0.Viewport, false));
steps.Push(BlurStep(blurUniforms, level0.HTexture, level0.VTexture, level0.Viewport, true));
// Add bloom back to scene texture:
step.ShaderName = "BloomCombine";
step.Uniforms.Clear();
step.Viewport = screen->mSceneViewport;
step.SetInputTexture(0, level0.VTexture, PPFilterMode::Linear);
step.SetOutputCurrent();
step.SetAdditiveBlend();
steps.Push(step);
hw_postprocess.Effects["BloomScene"] = steps;
}
void PPBloom::UpdateBlurSteps()
{
// first, respect the CVar
float blurAmount = gl_menu_blur;
// if CVar is negative, use the gameinfo entry
if (gl_menu_blur < 0)
blurAmount = hw_postprocess.gameinfobluramount;
// if blurAmount == 0 or somehow still returns negative, exit to prevent a crash, clearly we don't want this
if (blurAmount <= 0.0)
{
hw_postprocess.Effects["BlurScene"] = {};
return;
}
TArray<PPStep> steps;
PPStep step;
int numLevels = 3;
assert(numLevels <= NumBloomLevels);
const auto &level0 = levels[0];
// Grab the area we want to bloom:
step.ShaderName = "BloomCombine";
step.Uniforms.Clear();
step.Viewport = level0.Viewport;
step.SetInputCurrent(0, PPFilterMode::Linear);
step.SetOutputTexture(level0.VTexture);
step.SetNoBlend();
steps.Push(step);
BlurUniforms blurUniforms;
ComputeBlurSamples(7, blurAmount, blurUniforms.SampleWeights);
// Blur and downscale:
for (int i = 0; i < numLevels - 1; i++)
{
auto &blevel = levels[i];
auto &next = levels[i + 1];
steps.Push(BlurStep(blurUniforms, blevel.VTexture, blevel.HTexture, blevel.Viewport, false));
steps.Push(BlurStep(blurUniforms, blevel.HTexture, blevel.VTexture, blevel.Viewport, true));
// Linear downscale:
step.ShaderName = "BloomCombine";
step.Uniforms.Clear();
step.Viewport = next.Viewport;
step.SetInputTexture(0, blevel.VTexture, PPFilterMode::Linear);
step.SetOutputTexture(next.VTexture);
step.SetNoBlend();
steps.Push(step);
}
// Blur and upscale:
for (int i = numLevels - 1; i > 0; i--)
{
auto &blevel = levels[i];
auto &next = levels[i - 1];
steps.Push(BlurStep(blurUniforms, blevel.VTexture, blevel.HTexture, blevel.Viewport, false));
steps.Push(BlurStep(blurUniforms, blevel.HTexture, blevel.VTexture, blevel.Viewport, true));
// Linear upscale:
step.ShaderName = "BloomCombine";
step.Uniforms.Clear();
step.Viewport = next.Viewport;
step.SetInputTexture(0, blevel.VTexture, PPFilterMode::Linear);
step.SetOutputTexture(next.VTexture);
step.SetNoBlend();
steps.Push(step);
}
steps.Push(BlurStep(blurUniforms, level0.VTexture, level0.HTexture, level0.Viewport, false));
steps.Push(BlurStep(blurUniforms, level0.HTexture, level0.VTexture, level0.Viewport, true));
// Copy blur back to scene texture:
step.ShaderName = "BloomCombine";
step.Uniforms.Clear();
step.Viewport = screen->mScreenViewport;
step.SetInputTexture(0, level0.VTexture, PPFilterMode::Linear);
step.SetOutputCurrent();
step.SetNoBlend();
steps.Push(step);
hw_postprocess.Effects["BlurScene"] = steps;
}
PPStep PPBloom::BlurStep(const BlurUniforms &blurUniforms, PPTextureName input, PPTextureName output, PPViewport viewport, bool vertical)
{
PPStep step;
step.ShaderName = vertical ? "BlurVertical" : "BlurHorizontal";
step.Uniforms.Set(blurUniforms);
step.Viewport = viewport;
step.SetInputTexture(0, input);
step.SetOutputTexture(output);
step.SetNoBlend();
return step;
}
float PPBloom::ComputeBlurGaussian(float n, float theta) // theta = Blur Amount
{
return (float)((1.0f / sqrtf(2 * (float)M_PI * theta)) * expf(-(n * n) / (2.0f * theta * theta)));
}
void PPBloom::ComputeBlurSamples(int sampleCount, float blurAmount, float *sampleWeights)
{
sampleWeights[0] = ComputeBlurGaussian(0, blurAmount);
float totalWeights = sampleWeights[0];
for (int i = 0; i < sampleCount / 2; i++)
{
float weight = ComputeBlurGaussian(i + 1.0f, blurAmount);
sampleWeights[i * 2 + 1] = weight;
sampleWeights[i * 2 + 2] = weight;
totalWeights += weight * 2;
}
for (int i = 0; i < sampleCount; i++)
{
sampleWeights[i] /= totalWeights;
}
}
/////////////////////////////////////////////////////////////////////////////
void PPLensDistort::DeclareShaders()
{
hw_postprocess.Shaders["Lens"] = { "shaders/glsl/lensdistortion.fp", "", LensUniforms::Desc() };
}
void PPLensDistort::UpdateSteps()
{
if (gl_lens == 0)
{
hw_postprocess.Effects["LensDistortScene"] = {};
return;
}
float k[4] =
{
gl_lens_k,
gl_lens_k * gl_lens_chromatic,
gl_lens_k * gl_lens_chromatic * gl_lens_chromatic,
0.0f
};
float kcube[4] =
{
gl_lens_kcube,
gl_lens_kcube * gl_lens_chromatic,
gl_lens_kcube * gl_lens_chromatic * gl_lens_chromatic,
0.0f
};
float aspect = screen->mSceneViewport.width / (float)screen->mSceneViewport.height;
// Scale factor to keep sampling within the input texture
float r2 = aspect * aspect * 0.25f + 0.25f;
float sqrt_r2 = sqrt(r2);
float f0 = 1.0f + MAX(r2 * (k[0] + kcube[0] * sqrt_r2), 0.0f);
float f2 = 1.0f + MAX(r2 * (k[2] + kcube[2] * sqrt_r2), 0.0f);
float f = MAX(f0, f2);
float scale = 1.0f / f;
LensUniforms uniforms;
uniforms.AspectRatio = aspect;
uniforms.Scale = scale;
uniforms.LensDistortionCoefficient = k;
uniforms.CubicDistortionValue = kcube;
TArray<PPStep> steps;
PPStep step;
step.ShaderName = "Lens";
step.Uniforms.Set(uniforms);
step.Viewport = screen->mScreenViewport;
step.SetInputCurrent(0, PPFilterMode::Linear);
step.SetOutputNext();
step.SetNoBlend();
steps.Push(step);
hw_postprocess.Effects["LensDistortScene"] = steps;
}
/////////////////////////////////////////////////////////////////////////////
void PPFXAA::DeclareShaders()
{
hw_postprocess.Shaders["FXAALuma"] = { "shaders/glsl/fxaa.fp", "#define FXAA_LUMA_PASS\n", {} };
hw_postprocess.Shaders["FXAA"] = { "shaders/glsl/fxaa.fp", GetDefines(), FXAAUniforms::Desc(), GetMaxVersion() };
}
void PPFXAA::UpdateSteps()
{
if (0 == gl_fxaa)
{
hw_postprocess.Effects["ApplyFXAA"] = {};
return;
}
FXAAUniforms uniforms;
uniforms.ReciprocalResolution = { 1.0f / screen->mScreenViewport.width, 1.0f / screen->mScreenViewport.height };
TArray<PPStep> steps;
PPStep step;
step.ShaderName = "FXAALuma";
step.Uniforms.Clear();
step.Viewport = screen->mScreenViewport;
step.SetInputCurrent(0, PPFilterMode::Nearest);
step.SetOutputNext();
step.SetNoBlend();
steps.Push(step);
step.ShaderName = "FXAA";
step.Uniforms.Set(uniforms);
step.Viewport = screen->mScreenViewport;
step.SetInputCurrent(0, PPFilterMode::Linear);
step.SetOutputNext();
step.SetNoBlend();
steps.Push(step);
hw_postprocess.Effects["ApplyFXAA"] = steps;
}
int PPFXAA::GetMaxVersion()
{
return screen->glslversion >= 4.f ? 400 : 330;
}
FString PPFXAA::GetDefines()
{
int quality;
switch (gl_fxaa)
{
default:
case IFXAAShader::Low: quality = 10; break;
case IFXAAShader::Medium: quality = 12; break;
case IFXAAShader::High: quality = 29; break;
case IFXAAShader::Extreme: quality = 39; break;
}
const int gatherAlpha = GetMaxVersion() >= 400 ? 1 : 0;
// TODO: enable FXAA_GATHER4_ALPHA on OpenGL earlier than 4.0
// when GL_ARB_gpu_shader5/GL_NV_gpu_shader5 extensions are supported
FString result;
result.Format(
"#define FXAA_QUALITY__PRESET %i\n"
"#define FXAA_GATHER4_ALPHA %i\n",
quality, gatherAlpha);
return result;
}
/////////////////////////////////////////////////////////////////////////////
void PPCameraExposure::DeclareShaders()
{
hw_postprocess.Shaders["ExposureExtract"] = { "shaders/glsl/exposureextract.fp", "", ExposureExtractUniforms::Desc() };
hw_postprocess.Shaders["ExposureAverage"] = { "shaders/glsl/exposureaverage.fp", "", {}, 400 };
hw_postprocess.Shaders["ExposureCombine"] = { "shaders/glsl/exposurecombine.fp", "", ExposureCombineUniforms::Desc() };
}
void PPCameraExposure::UpdateTextures()
{
int width = hw_postprocess.SceneWidth;
int height = hw_postprocess.SceneHeight;
if (ExposureLevels.Size() > 0 && ExposureLevels[0].Viewport.width == width && ExposureLevels[0].Viewport.height == height)
{
return;
}
ExposureLevels.Clear();
int i = 0;
do
{
width = MAX(width / 2, 1);
height = MAX(height / 2, 1);
PPExposureLevel blevel;
blevel.Viewport.left = 0;
blevel.Viewport.top = 0;
blevel.Viewport.width = width;
blevel.Viewport.height = height;
blevel.Texture.Format("Exposure.Level.%d", i);
ExposureLevels.Push(blevel);
PPTextureDesc texture = { blevel.Viewport.width, blevel.Viewport.height, PixelFormat::R32f };
hw_postprocess.Textures[blevel.Texture] = texture;
i++;
} while (width > 1 || height > 1);
hw_postprocess.Textures["Exposure.CameraTexture"] = { 1, 1, PixelFormat::R32f };
FirstExposureFrame = true;
}
void PPCameraExposure::UpdateSteps()
{
if (!gl_bloom)
{
hw_postprocess.Effects["UpdateCameraExposure"] = {};
return;
}
TArray<PPStep> steps;
PPStep step;
ExposureExtractUniforms extractUniforms;
extractUniforms.Scale = screen->SceneScale();
extractUniforms.Offset = screen->SceneOffset();
ExposureCombineUniforms combineUniforms;
combineUniforms.ExposureBase = gl_exposure_base;
combineUniforms.ExposureMin = gl_exposure_min;
combineUniforms.ExposureScale = gl_exposure_scale;
combineUniforms.ExposureSpeed = gl_exposure_speed;
auto &level0 = ExposureLevels[0];
// Extract light blevel from scene texture:
step.ShaderName = "ExposureExtract";
step.Uniforms.Set(extractUniforms);
step.Viewport = level0.Viewport;
step.SetInputCurrent(0, PPFilterMode::Linear);
step.SetOutputTexture(level0.Texture);
step.SetNoBlend();
steps.Push(step);
// Find the average value:
for (unsigned int i = 0; i + 1 < ExposureLevels.Size(); i++)
{
auto &blevel = ExposureLevels[i];
auto &next = ExposureLevels[i + 1];
step.ShaderName = "ExposureAverage";
step.Uniforms.Clear();
step.Viewport = next.Viewport;
step.SetInputTexture(0, blevel.Texture, PPFilterMode::Linear);
step.SetOutputTexture(next.Texture);
step.SetNoBlend();
steps.Push(step);
}
// Combine average value with current camera exposure:
step.ShaderName = "ExposureCombine";
step.Uniforms.Set(combineUniforms);
step.Viewport.left = 0;
step.Viewport.top = 0;
step.Viewport.width = 1;
step.Viewport.height = 1;
step.SetInputTexture(0, ExposureLevels.Last().Texture, PPFilterMode::Linear);
step.SetOutputTexture("Exposure.CameraTexture");
if (!FirstExposureFrame)
step.SetAlphaBlend();
else
step.SetNoBlend();
steps.Push(step);
FirstExposureFrame = false;
hw_postprocess.Effects["UpdateCameraExposure"] = steps;
}
/////////////////////////////////////////////////////////////////////////////
void PPColormap::DeclareShaders()
{
hw_postprocess.Shaders["Colormap"] = { "shaders/glsl/colormap.fp", "", ColormapUniforms::Desc() };
}
void PPColormap::UpdateSteps()
{
int fixedcm = hw_postprocess.fixedcm;
if (fixedcm < CM_FIRSTSPECIALCOLORMAP || fixedcm >= CM_MAXCOLORMAP)
{
hw_postprocess.Effects["ColormapScene"] = {};
return;
}
FSpecialColormap *scm = &SpecialColormaps[fixedcm - CM_FIRSTSPECIALCOLORMAP];
float m[] = { scm->ColorizeEnd[0] - scm->ColorizeStart[0],
scm->ColorizeEnd[1] - scm->ColorizeStart[1], scm->ColorizeEnd[2] - scm->ColorizeStart[2], 0.f };
ColormapUniforms uniforms;
uniforms.MapStart = { scm->ColorizeStart[0], scm->ColorizeStart[1], scm->ColorizeStart[2], 0.f };
uniforms.MapRange = m;
PPStep step;
step.ShaderName = "Colormap";
step.Uniforms.Set(uniforms);
step.Viewport = screen->mScreenViewport;
step.SetInputCurrent(0);
step.SetOutputNext();
step.SetNoBlend();
TArray<PPStep> steps;
steps.Push(step);
hw_postprocess.Effects["ColormapScene"] = steps;
}
/////////////////////////////////////////////////////////////////////////////
void PPTonemap::DeclareShaders()
{
hw_postprocess.Shaders["Tonemap.Linear"] = { "shaders/glsl/tonemap.fp", "#define LINEAR\n", {} };
hw_postprocess.Shaders["Tonemap.Reinhard"] = { "shaders/glsl/tonemap.fp", "#define REINHARD\n", {} };
hw_postprocess.Shaders["Tonemap.HejlDawson"] = { "shaders/glsl/tonemap.fp", "#define HEJLDAWSON\n", {} };
hw_postprocess.Shaders["Tonemap.Uncharted2"] = { "shaders/glsl/tonemap.fp", "#define UNCHARTED2\n", {} };
hw_postprocess.Shaders["Tonemap.Palette"] = { "shaders/glsl/tonemap.fp", "#define PALETTE\n", {} };
}
void PPTonemap::UpdateTextures()
{
if (gl_tonemap == Palette)
{
if (!hw_postprocess.Textures.CheckKey("Tonemap.Palette"))
{
std::shared_ptr<void> data(new uint32_t[512 * 512], [](void *p) { delete[](uint32_t*)p; });
uint8_t *lut = (uint8_t *)data.get();
for (int r = 0; r < 64; r++)
{
for (int g = 0; g < 64; g++)
{
for (int b = 0; b < 64; b++)
{
PalEntry color = GPalette.BaseColors[(uint8_t)PTM_BestColor((uint32_t *)GPalette.BaseColors, (r << 2) | (r >> 4), (g << 2) | (g >> 4), (b << 2) | (b >> 4),
gl_paltonemap_reverselookup, gl_paltonemap_powtable, 0, 256)];
int index = ((r * 64 + g) * 64 + b) * 4;
lut[index] = color.r;
lut[index + 1] = color.g;
lut[index + 2] = color.b;
lut[index + 3] = 255;
}
}
}
hw_postprocess.Textures["Tonemap.Palette"] = { 512, 512, PixelFormat::Rgba8, data };
}
}
}
void PPTonemap::UpdateSteps()
{
if (gl_tonemap == 0)
{
hw_postprocess.Effects["TonemapScene"] = {};
return;
}
PPShaderName shader;
switch (gl_tonemap)
{
default:
case Linear: shader = "Tonemap.Linear"; break;
case Reinhard: shader = "Tonemap.Reinhard"; break;
case HejlDawson: shader = "Tonemap.HejlDawson"; break;
case Uncharted2: shader = "Tonemap.Uncharted2"; break;
case Palette: shader = "Tonemap.Palette"; break;
}
PPStep step;
step.ShaderName = shader;
step.Viewport = screen->mScreenViewport;
step.SetInputCurrent(0);
if (gl_tonemap == Palette)
step.SetInputTexture(1, "Tonemap.Palette");
step.SetOutputNext();
step.SetNoBlend();
TArray<PPStep> steps;
steps.Push(step);
hw_postprocess.Effects["TonemapScene"] = steps;
}
/////////////////////////////////////////////////////////////////////////////
void PPAmbientOcclusion::DeclareShaders()
{
// Must match quality values in PPAmbientOcclusion::UpdateTextures
int numDirections, numSteps;
switch (gl_ssao)
{
default:
case LowQuality: numDirections = 2; numSteps = 4; break;
case MediumQuality: numDirections = 4; numSteps = 4; break;
case HighQuality: numDirections = 8; numSteps = 4; break;
}
FString defines;
defines.Format(R"(
#define USE_RANDOM_TEXTURE
#define RANDOM_TEXTURE_WIDTH 4.0
#define NUM_DIRECTIONS %d.0
#define NUM_STEPS %d.0
)", numDirections, numSteps);
hw_postprocess.Shaders["SSAO.LinearDepth"] = { "shaders/glsl/lineardepth.fp", "", LinearDepthUniforms::Desc() };
hw_postprocess.Shaders["SSAO.LinearDepthMS"] = { "shaders/glsl/lineardepth.fp", "#define MULTISAMPLE\n", LinearDepthUniforms::Desc() };
hw_postprocess.Shaders["SSAO.AmbientOcclude"] = { "shaders/glsl/ssao.fp", defines, SSAOUniforms::Desc() };
hw_postprocess.Shaders["SSAO.AmbientOccludeMS"] = { "shaders/glsl/ssao.fp", defines + "\n#define MULTISAMPLE\n", SSAOUniforms::Desc() };
hw_postprocess.Shaders["SSAO.BlurVertical"] = { "shaders/glsl/depthblur.fp", "#define BLUR_VERTICAL\n", DepthBlurUniforms::Desc() };
hw_postprocess.Shaders["SSAO.BlurHorizontal"] = { "shaders/glsl/depthblur.fp", "#define BLUR_HORIZONTAL\n", DepthBlurUniforms::Desc() };
hw_postprocess.Shaders["SSAO.Combine"] = { "shaders/glsl/ssaocombine.fp", "", AmbientCombineUniforms::Desc() };
hw_postprocess.Shaders["SSAO.CombineMS"] = { "shaders/glsl/ssaocombine.fp", "#define MULTISAMPLE\n", AmbientCombineUniforms::Desc() };
}
void PPAmbientOcclusion::UpdateTextures()
{
int width = hw_postprocess.SceneWidth;
int height = hw_postprocess.SceneHeight;
if (width <= 0 || height <= 0)
return;
AmbientWidth = (width + 1) / 2;
AmbientHeight = (height + 1) / 2;
hw_postprocess.Textures["SSAO.LinearDepth"] = { AmbientWidth, AmbientHeight, PixelFormat::R32f };
hw_postprocess.Textures["SSAO.Ambient0"] = { AmbientWidth, AmbientHeight, PixelFormat::Rg16f };
hw_postprocess.Textures["SSAO.Ambient1"] = { AmbientWidth, AmbientHeight, PixelFormat::Rg16f };
// We only need to create the random texture once
if (!hw_postprocess.Textures.CheckKey("SSAO.Random0"))
{
// Must match quality enum in PPAmbientOcclusion::DeclareShaders
double numDirections[NumAmbientRandomTextures] = { 2.0, 4.0, 8.0 };
std::mt19937 generator(1337);
std::uniform_real_distribution<double> distribution(0.0, 1.0);
for (int quality = 0; quality < NumAmbientRandomTextures; quality++)
{
std::shared_ptr<void> data(new int16_t[16 * 4], [](void *p) { delete[](int16_t*)p; });
int16_t *randomValues = (int16_t *)data.get();
for (int i = 0; i < 16; i++)
{
double angle = 2.0 * M_PI * distribution(generator) / numDirections[quality];
double x = cos(angle);
double y = sin(angle);
double z = distribution(generator);
double w = distribution(generator);
randomValues[i * 4 + 0] = (int16_t)clamp(x * 32767.0, -32768.0, 32767.0);
randomValues[i * 4 + 1] = (int16_t)clamp(y * 32767.0, -32768.0, 32767.0);
randomValues[i * 4 + 2] = (int16_t)clamp(z * 32767.0, -32768.0, 32767.0);
randomValues[i * 4 + 3] = (int16_t)clamp(w * 32767.0, -32768.0, 32767.0);
}
FString name;
name.Format("SSAO.Random%d", quality);
hw_postprocess.Textures[name] = { 4, 4, PixelFormat::Rgba16_snorm, data };
AmbientRandomTexture[quality] = name;
}
}
}
void PPAmbientOcclusion::UpdateSteps()
{
if (gl_ssao == 0 || hw_postprocess.SceneWidth == 0 || hw_postprocess.SceneHeight == 0)
{
hw_postprocess.Effects["AmbientOccludeScene"] = {};
return;
}
float bias = gl_ssao_bias;
float aoRadius = gl_ssao_radius;
const float blurAmount = gl_ssao_blur;
float aoStrength = gl_ssao_strength;
//float tanHalfFovy = tan(fovy * (M_PI / 360.0f));
float tanHalfFovy = 1.0f / hw_postprocess.m5;
float invFocalLenX = tanHalfFovy * (hw_postprocess.SceneWidth / (float)hw_postprocess.SceneHeight);
float invFocalLenY = tanHalfFovy;
float nDotVBias = clamp(bias, 0.0f, 1.0f);
float r2 = aoRadius * aoRadius;
float blurSharpness = 1.0f / blurAmount;
auto sceneScale = screen->SceneScale();
auto sceneOffset = screen->SceneOffset();
int randomTexture = clamp(gl_ssao - 1, 0, NumAmbientRandomTextures - 1);
LinearDepthUniforms linearUniforms;
linearUniforms.SampleIndex = 0;
linearUniforms.LinearizeDepthA = 1.0f / screen->GetZFar() - 1.0f / screen->GetZNear();
linearUniforms.LinearizeDepthB = MAX(1.0f / screen->GetZNear(), 1.e-8f);
linearUniforms.InverseDepthRangeA = 1.0f;
linearUniforms.InverseDepthRangeB = 0.0f;
linearUniforms.Scale = sceneScale;
linearUniforms.Offset = sceneOffset;
SSAOUniforms ssaoUniforms;
ssaoUniforms.SampleIndex = 0;
ssaoUniforms.UVToViewA = { 2.0f * invFocalLenX, 2.0f * invFocalLenY };
ssaoUniforms.UVToViewB = { -invFocalLenX, -invFocalLenY };
ssaoUniforms.InvFullResolution = { 1.0f / AmbientWidth, 1.0f / AmbientHeight };
ssaoUniforms.NDotVBias = nDotVBias;
ssaoUniforms.NegInvR2 = -1.0f / r2;
ssaoUniforms.RadiusToScreen = aoRadius * 0.5f / tanHalfFovy * AmbientHeight;
ssaoUniforms.AOMultiplier = 1.0f / (1.0f - nDotVBias);
ssaoUniforms.AOStrength = aoStrength;
ssaoUniforms.Scale = sceneScale;
ssaoUniforms.Offset = sceneOffset;
DepthBlurUniforms blurUniforms;
blurUniforms.BlurSharpness = blurSharpness;
blurUniforms.InvFullResolution = { 1.0f / AmbientWidth, 1.0f / AmbientHeight };
blurUniforms.PowExponent = gl_ssao_exponent;
AmbientCombineUniforms combineUniforms;
combineUniforms.SampleCount = gl_multisample;
combineUniforms.Scale = screen->SceneScale();
combineUniforms.Offset = screen->SceneOffset();
IntRect ambientViewport;
ambientViewport.left = 0;
ambientViewport.top = 0;
ambientViewport.width = AmbientWidth;
ambientViewport.height = AmbientHeight;
TArray<PPStep> steps;
// Calculate linear depth values
{
PPStep step;
step.ShaderName = gl_multisample > 1 ? "SSAO.LinearDepthMS" : "SSAO.LinearDepth";
step.Uniforms.Set(linearUniforms);
step.Viewport = ambientViewport;
step.SetInputSceneDepth(0);
step.SetInputSceneColor(1);
step.SetOutputTexture("SSAO.LinearDepth");
step.SetNoBlend();
steps.Push(step);
}
// Apply ambient occlusion
{
PPStep step;
step.ShaderName = gl_multisample > 1 ? "SSAO.AmbientOccludeMS" : "SSAO.AmbientOcclude";
step.Uniforms.Set(ssaoUniforms);
step.Viewport = ambientViewport;
step.SetInputTexture(0, "SSAO.LinearDepth");
step.SetInputSceneNormal(1);
step.SetInputTexture(2, AmbientRandomTexture[randomTexture], PPFilterMode::Nearest, PPWrapMode::Repeat);
step.SetOutputTexture("SSAO.Ambient0");
step.SetNoBlend();
steps.Push(step);
}
// Blur SSAO texture
if (gl_ssao_debug < 2)
{
PPStep step;
step.ShaderName = "SSAO.BlurHorizontal";
step.Uniforms.Set(blurUniforms);
step.Viewport = ambientViewport;
step.SetInputTexture(0, "SSAO.Ambient0");
step.SetOutputTexture("SSAO.Ambient1");
step.SetNoBlend();
steps.Push(step);
step.ShaderName = "SSAO.BlurVertical";
step.SetInputTexture(0, "SSAO.Ambient1");
step.SetOutputTexture("SSAO.Ambient0");
steps.Push(step);
}
// Add SSAO back to scene texture:
{
PPStep step;
step.ShaderName = gl_multisample > 1 ? "SSAO.CombineMS" : "SSAO.Combine";
step.Uniforms.Set(combineUniforms);
step.Viewport = screen->mSceneViewport;
step.SetInputTexture(0, "SSAO.Ambient0", PPFilterMode::Linear);
step.SetInputSceneFog(1);
step.SetOutputSceneColor();
if (gl_ssao_debug != 0)
step.SetNoBlend();
else
step.SetAlphaBlend();
steps.Push(step);
}
hw_postprocess.Effects["AmbientOccludeScene"] = steps;
}

View file

@ -0,0 +1,599 @@
#pragma once
#include "hwrenderer/data/shaderuniforms.h"
#include <memory>
typedef FString PPTextureName;
typedef FString PPShaderName;
typedef FRenderStyle PPBlendMode;
typedef IntRect PPViewport;
enum class PPFilterMode { Nearest, Linear };
enum class PPWrapMode { Clamp, Repeat };
enum class PPTextureType { CurrentPipelineTexture, NextPipelineTexture, PPTexture, SceneColor, SceneFog, SceneNormal, SceneDepth };
class PPTextureInput
{
public:
PPFilterMode Filter;
PPWrapMode Wrap;
PPTextureType Type;
PPTextureName Texture;
};
class PPOutput
{
public:
PPTextureType Type;
PPTextureName Texture;
};
class PPUniforms
{
public:
PPUniforms()
{
}
PPUniforms(const PPUniforms &src)
{
Data = src.Data;
}
~PPUniforms()
{
Clear();
}
PPUniforms &operator=(const PPUniforms &src)
{
Data = src.Data;
return *this;
}
void Clear()
{
Data.Clear();
}
template<typename T>
void Set(const T &v)
{
if (Data.Size() != (int)sizeof(T))
{
Data.Resize(sizeof(T));
memcpy(Data.Data(), &v, Data.Size());
}
}
TArray<uint8_t> Data;
};
class PPStep
{
public:
void SetInputTexture(int index, PPTextureName texture, PPFilterMode filter = PPFilterMode::Nearest, PPWrapMode wrap = PPWrapMode::Clamp)
{
if ((int)Textures.Size() < index + 1)
Textures.Resize(index + 1);
auto &tex = Textures[index];
tex.Filter = filter;
tex.Wrap = wrap;
tex.Type = PPTextureType::PPTexture;
tex.Texture = texture;
}
void SetInputCurrent(int index, PPFilterMode filter = PPFilterMode::Nearest, PPWrapMode wrap = PPWrapMode::Clamp)
{
SetInputSpecialType(index, PPTextureType::CurrentPipelineTexture, filter, wrap);
}
void SetInputSceneColor(int index, PPFilterMode filter = PPFilterMode::Nearest, PPWrapMode wrap = PPWrapMode::Clamp)
{
SetInputSpecialType(index, PPTextureType::SceneColor, filter, wrap);
}
void SetInputSceneFog(int index, PPFilterMode filter = PPFilterMode::Nearest, PPWrapMode wrap = PPWrapMode::Clamp)
{
SetInputSpecialType(index, PPTextureType::SceneFog, filter, wrap);
}
void SetInputSceneNormal(int index, PPFilterMode filter = PPFilterMode::Nearest, PPWrapMode wrap = PPWrapMode::Clamp)
{
SetInputSpecialType(index, PPTextureType::SceneNormal, filter, wrap);
}
void SetInputSceneDepth(int index, PPFilterMode filter = PPFilterMode::Nearest, PPWrapMode wrap = PPWrapMode::Clamp)
{
SetInputSpecialType(index, PPTextureType::SceneDepth, filter, wrap);
}
void SetInputSpecialType(int index, PPTextureType type, PPFilterMode filter = PPFilterMode::Nearest, PPWrapMode wrap = PPWrapMode::Clamp)
{
if ((int)Textures.Size() < index + 1)
Textures.Resize(index + 1);
auto &tex = Textures[index];
tex.Filter = filter;
tex.Wrap = wrap;
tex.Type = type;
tex.Texture = {};
}
void SetOutputTexture(PPTextureName texture)
{
Output.Type = PPTextureType::PPTexture;
Output.Texture = texture;
}
void SetOutputCurrent()
{
Output.Type = PPTextureType::CurrentPipelineTexture;
Output.Texture = {};
}
void SetOutputNext()
{
Output.Type = PPTextureType::NextPipelineTexture;
Output.Texture = {};
}
void SetOutputSceneColor()
{
Output.Type = PPTextureType::SceneColor;
Output.Texture = {};
}
void SetNoBlend()
{
BlendMode.BlendOp = STYLEOP_Add;
BlendMode.SrcAlpha = STYLEALPHA_One;
BlendMode.DestAlpha = STYLEALPHA_Zero;
BlendMode.Flags = 0;
}
void SetAdditiveBlend()
{
BlendMode.BlendOp = STYLEOP_Add;
BlendMode.SrcAlpha = STYLEALPHA_One;
BlendMode.DestAlpha = STYLEALPHA_One;
BlendMode.Flags = 0;
}
void SetAlphaBlend()
{
BlendMode.BlendOp = STYLEOP_Add;
BlendMode.SrcAlpha = STYLEALPHA_Src;
BlendMode.DestAlpha = STYLEALPHA_InvSrc;
BlendMode.Flags = 0;
}
PPShaderName ShaderName;
TArray<PPTextureInput> Textures;
PPUniforms Uniforms;
PPViewport Viewport;
PPBlendMode BlendMode;
PPOutput Output;
};
enum class PixelFormat
{
Rgba8,
Rgba16f,
R32f,
Rg16f,
Rgba16_snorm
};
class PPTextureDesc
{
public:
PPTextureDesc() { }
PPTextureDesc(int width, int height, PixelFormat format, std::shared_ptr<void> data = {}) : Width(width), Height(height), Format(format), Data(data) { }
int Width;
int Height;
PixelFormat Format;
std::shared_ptr<void> Data;
};
class PPShader
{
public:
PPShader() { }
PPShader(const FString &fragment, const FString &defines, const std::vector<UniformFieldDesc> &uniforms, int version = 330) : FragmentShader(fragment), Defines(defines), Uniforms(uniforms), Version(version) { }
FString VertexShader = "shaders/glsl/screenquad.vp";
FString FragmentShader;
FString Defines;
std::vector<UniformFieldDesc> Uniforms;
int Version = 330;
};
class PPEffectManager
{
public:
virtual ~PPEffectManager() { }
virtual void DeclareShaders() { }
virtual void UpdateTextures() { }
virtual void UpdateSteps() { }
};
class Postprocess
{
public:
Postprocess();
~Postprocess();
void DeclareShaders() { for (unsigned int i = 0; i < Managers.Size(); i++) Managers[i]->DeclareShaders(); }
void UpdateTextures() { for (unsigned int i = 0; i < Managers.Size(); i++) Managers[i]->UpdateTextures(); }
void UpdateSteps() { for (unsigned int i = 0; i < Managers.Size(); i++) Managers[i]->UpdateSteps(); }
int SceneWidth = 0;
int SceneHeight = 0;
int fixedcm = 0;
float gameinfobluramount = 0.0f;
float m5 = 0.0f;
TMap<FString, TArray<PPStep>> Effects;
TMap<FString, PPTextureDesc> Textures;
TMap<FString, PPShader> Shaders;
TArray<PPEffectManager*> Managers;
};
extern Postprocess hw_postprocess;
/////////////////////////////////////////////////////////////////////////////
struct ExtractUniforms
{
FVector2 Scale;
FVector2 Offset;
static std::vector<UniformFieldDesc> Desc()
{
return
{
{ "Scale", UniformType::Vec2, offsetof(ExtractUniforms, Scale) },
{ "Offset", UniformType::Vec2, offsetof(ExtractUniforms, Offset) }
};
}
};
struct BlurUniforms
{
float SampleWeights[8];
static std::vector<UniformFieldDesc> Desc()
{
return
{
{ "SampleWeights0", UniformType::Float, offsetof(BlurUniforms, SampleWeights[0]) },
{ "SampleWeights1", UniformType::Float, offsetof(BlurUniforms, SampleWeights[1]) },
{ "SampleWeights2", UniformType::Float, offsetof(BlurUniforms, SampleWeights[2]) },
{ "SampleWeights3", UniformType::Float, offsetof(BlurUniforms, SampleWeights[3]) },
{ "SampleWeights4", UniformType::Float, offsetof(BlurUniforms, SampleWeights[4]) },
{ "SampleWeights5", UniformType::Float, offsetof(BlurUniforms, SampleWeights[5]) },
{ "SampleWeights6", UniformType::Float, offsetof(BlurUniforms, SampleWeights[6]) },
{ "SampleWeights7", UniformType::Float, offsetof(BlurUniforms, SampleWeights[7]) },
};
}
};
enum { NumBloomLevels = 4 };
class PPBlurLevel
{
public:
PPViewport Viewport;
PPTextureName VTexture;
PPTextureName HTexture;
};
class PPBloom : public PPEffectManager
{
public:
void DeclareShaders() override;
void UpdateTextures() override;
void UpdateSteps() override;
private:
void UpdateBlurSteps();
PPStep BlurStep(const BlurUniforms &blurUniforms, PPTextureName input, PPTextureName output, PPViewport viewport, bool vertical);
static float ComputeBlurGaussian(float n, float theta);
static void ComputeBlurSamples(int sampleCount, float blurAmount, float *sampleWeights);
PPBlurLevel levels[NumBloomLevels];
};
/////////////////////////////////////////////////////////////////////////////
struct LensUniforms
{
float AspectRatio;
float Scale;
float Padding0, Padding1;
FVector4 LensDistortionCoefficient;
FVector4 CubicDistortionValue;
static std::vector<UniformFieldDesc> Desc()
{
return
{
{ "Aspect", UniformType::Float, offsetof(LensUniforms, AspectRatio) },
{ "Scale", UniformType::Float, offsetof(LensUniforms, Scale) },
{ "Padding0", UniformType::Float, offsetof(LensUniforms, Padding0) },
{ "Padding1", UniformType::Float, offsetof(LensUniforms, Padding1) },
{ "k", UniformType::Vec4, offsetof(LensUniforms, LensDistortionCoefficient) },
{ "kcube", UniformType::Vec4, offsetof(LensUniforms, CubicDistortionValue) }
};
}
};
class PPLensDistort : public PPEffectManager
{
public:
void DeclareShaders() override;
void UpdateSteps() override;
};
/////////////////////////////////////////////////////////////////////////////
struct FXAAUniforms
{
FVector2 ReciprocalResolution;
float Padding0, Padding1;
static std::vector<UniformFieldDesc> Desc()
{
return
{
{ "ReciprocalResolution", UniformType::Vec2, offsetof(FXAAUniforms, ReciprocalResolution) },
{ "Padding0", UniformType::Float, offsetof(FXAAUniforms, Padding0) },
{ "Padding1", UniformType::Float, offsetof(FXAAUniforms, Padding1) }
};
}
};
class PPFXAA : public PPEffectManager
{
public:
void DeclareShaders() override;
void UpdateSteps() override;
private:
int GetMaxVersion();
FString GetDefines();
};
/////////////////////////////////////////////////////////////////////////////
struct ExposureExtractUniforms
{
FVector2 Scale;
FVector2 Offset;
static std::vector<UniformFieldDesc> Desc()
{
return
{
{ "Scale", UniformType::Vec2, offsetof(ExposureExtractUniforms, Scale) },
{ "Offset", UniformType::Vec2, offsetof(ExposureExtractUniforms, Offset) }
};
}
};
struct ExposureCombineUniforms
{
float ExposureBase;
float ExposureMin;
float ExposureScale;
float ExposureSpeed;
static std::vector<UniformFieldDesc> Desc()
{
return
{
{ "ExposureBase", UniformType::Float, offsetof(ExposureCombineUniforms, ExposureBase) },
{ "ExposureMin", UniformType::Float, offsetof(ExposureCombineUniforms, ExposureMin) },
{ "ExposureScale", UniformType::Float, offsetof(ExposureCombineUniforms, ExposureScale) },
{ "ExposureSpeed", UniformType::Float, offsetof(ExposureCombineUniforms, ExposureSpeed) }
};
}
};
class PPExposureLevel
{
public:
PPViewport Viewport;
PPTextureName Texture;
};
class PPCameraExposure : public PPEffectManager
{
public:
void DeclareShaders() override;
void UpdateTextures() override;
void UpdateSteps() override;
private:
TArray<PPExposureLevel> ExposureLevels;
bool FirstExposureFrame = true;
};
/////////////////////////////////////////////////////////////////////////////
struct ColormapUniforms
{
FVector4 MapStart;
FVector4 MapRange;
static std::vector<UniformFieldDesc> Desc()
{
return
{
{ "uFixedColormapStart", UniformType::Vec4, offsetof(ColormapUniforms, MapStart) },
{ "uFixedColormapRange", UniformType::Vec4, offsetof(ColormapUniforms, MapRange) },
};
}
};
class PPColormap : public PPEffectManager
{
public:
void DeclareShaders() override;
void UpdateSteps() override;
};
/////////////////////////////////////////////////////////////////////////////
class PPTonemap : public PPEffectManager
{
public:
void DeclareShaders() override;
void UpdateTextures() override;
void UpdateSteps() override;
enum TonemapMode
{
None,
Uncharted2,
HejlDawson,
Reinhard,
Linear,
Palette,
NumTonemapModes
};
};
/////////////////////////////////////////////////////////////////////////////
struct LinearDepthUniforms
{
int SampleIndex;
float LinearizeDepthA;
float LinearizeDepthB;
float InverseDepthRangeA;
float InverseDepthRangeB;
float Padding0, Padding1, Padding2;
FVector2 Scale;
FVector2 Offset;
static std::vector<UniformFieldDesc> Desc()
{
return
{
{ "SampleIndex", UniformType::Int, offsetof(LinearDepthUniforms, SampleIndex) },
{ "LinearizeDepthA", UniformType::Float, offsetof(LinearDepthUniforms, LinearizeDepthA) },
{ "LinearizeDepthB", UniformType::Float, offsetof(LinearDepthUniforms, LinearizeDepthB) },
{ "InverseDepthRangeA", UniformType::Float, offsetof(LinearDepthUniforms, InverseDepthRangeA) },
{ "InverseDepthRangeB", UniformType::Float, offsetof(LinearDepthUniforms, InverseDepthRangeB) },
{ "Padding0", UniformType::Float, offsetof(LinearDepthUniforms, Padding0) },
{ "Padding1", UniformType::Float, offsetof(LinearDepthUniforms, Padding1) },
{ "Padding2", UniformType::Float, offsetof(LinearDepthUniforms, Padding2) },
{ "Scale", UniformType::Vec2, offsetof(LinearDepthUniforms, Scale) },
{ "Offset", UniformType::Vec2, offsetof(LinearDepthUniforms, Offset) }
};
}
};
struct SSAOUniforms
{
FVector2 UVToViewA;
FVector2 UVToViewB;
FVector2 InvFullResolution;
float NDotVBias;
float NegInvR2;
float RadiusToScreen;
float AOMultiplier;
float AOStrength;
int SampleIndex;
float Padding0, Padding1;
FVector2 Scale;
FVector2 Offset;
static std::vector<UniformFieldDesc> Desc()
{
return
{
{ "UVToViewA", UniformType::Vec2, offsetof(SSAOUniforms, UVToViewA) },
{ "UVToViewB", UniformType::Vec2, offsetof(SSAOUniforms, UVToViewB) },
{ "InvFullResolution", UniformType::Vec2, offsetof(SSAOUniforms, InvFullResolution) },
{ "NDotVBias", UniformType::Float, offsetof(SSAOUniforms, NDotVBias) },
{ "NegInvR2", UniformType::Float, offsetof(SSAOUniforms, NegInvR2) },
{ "RadiusToScreen", UniformType::Float, offsetof(SSAOUniforms, RadiusToScreen) },
{ "AOMultiplier", UniformType::Float, offsetof(SSAOUniforms, AOMultiplier) },
{ "AOStrength", UniformType::Float, offsetof(SSAOUniforms, AOStrength) },
{ "SampleIndex", UniformType::Int, offsetof(SSAOUniforms, SampleIndex) },
{ "Padding0", UniformType::Float, offsetof(SSAOUniforms, Padding0) },
{ "Padding1", UniformType::Float, offsetof(SSAOUniforms, Padding1) },
{ "Scale", UniformType::Vec2, offsetof(SSAOUniforms, Scale) },
{ "Offset", UniformType::Vec2, offsetof(SSAOUniforms, Offset) },
};
}
};
struct DepthBlurUniforms
{
float BlurSharpness;
float PowExponent;
FVector2 InvFullResolution;
static std::vector<UniformFieldDesc> Desc()
{
return
{
{ "BlurSharpness", UniformType::Float, offsetof(DepthBlurUniforms, BlurSharpness) },
{ "PowExponent", UniformType::Float, offsetof(DepthBlurUniforms, PowExponent) },
{ "InvFullResolution", UniformType::Vec2, offsetof(DepthBlurUniforms, InvFullResolution) }
};
}
};
struct AmbientCombineUniforms
{
int SampleCount;
int Padding0, Padding1, Padding2;
FVector2 Scale;
FVector2 Offset;
static std::vector<UniformFieldDesc> Desc()
{
return
{
{ "SampleCount", UniformType::Int, offsetof(AmbientCombineUniforms, SampleCount) },
{ "Padding0", UniformType::Int, offsetof(AmbientCombineUniforms, Padding0) },
{ "Padding1", UniformType::Int, offsetof(AmbientCombineUniforms, Padding1) },
{ "Padding2", UniformType::Int, offsetof(AmbientCombineUniforms, Padding2) },
{ "Scale", UniformType::Vec2, offsetof(AmbientCombineUniforms, Scale) },
{ "Offset", UniformType::Vec2, offsetof(AmbientCombineUniforms, Offset) }
};
}
};
class PPAmbientOcclusion : public PPEffectManager
{
public:
void DeclareShaders() override;
void UpdateTextures() override;
void UpdateSteps() override;
private:
enum Quality
{
Off,
LowQuality,
MediumQuality,
HighQuality,
NumQualityModes
};
int AmbientWidth = 0;
int AmbientHeight = 0;
enum { NumAmbientRandomTextures = 3 };
PPTextureName AmbientRandomTexture[NumAmbientRandomTextures];
};

View file

@ -0,0 +1,104 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2016 Magnus Norddahl
// 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_postprocess.cpp
** Post processing effects in the render pipeline
**
*/
#include "hw_postprocess_cvars.h"
#include "v_video.h"
//==========================================================================
//
// CVARs
//
//==========================================================================
CVAR(Bool, gl_bloom, false, CVAR_ARCHIVE);
CUSTOM_CVAR(Float, gl_bloom_amount, 1.4f, CVAR_ARCHIVE)
{
if (self < 0.1f) self = 0.1f;
}
CVAR(Float, gl_exposure_scale, 1.3f, CVAR_ARCHIVE)
CVAR(Float, gl_exposure_min, 0.35f, CVAR_ARCHIVE)
CVAR(Float, gl_exposure_base, 0.35f, CVAR_ARCHIVE)
CVAR(Float, gl_exposure_speed, 0.05f, CVAR_ARCHIVE)
CUSTOM_CVAR(Int, gl_tonemap, 0, CVAR_ARCHIVE)
{
if (self < 0 || self > 5)
self = 0;
}
CVAR(Bool, gl_lens, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Float, gl_lens_k, -0.12f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Float, gl_lens_kcube, 0.1f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Float, gl_lens_chromatic, 1.12f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CUSTOM_CVAR(Int, gl_fxaa, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
if (self < 0 || self >= IFXAAShader::Count)
{
self = 0;
}
}
CUSTOM_CVAR(Int, gl_ssao, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
if (self < 0 || self > 3)
self = 0;
}
CUSTOM_CVAR(Int, gl_ssao_portals, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
if (self < 0)
self = 0;
}
CVAR(Float, gl_ssao_strength, 0.7f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Int, gl_ssao_debug, 0, 0)
CVAR(Float, gl_ssao_bias, 0.2f, 0)
CVAR(Float, gl_ssao_radius, 80.0f, 0)
CUSTOM_CVAR(Float, gl_ssao_blur, 16.0f, 0)
{
if (self < 0.1f) self = 0.1f;
}
CUSTOM_CVAR(Float, gl_ssao_exponent, 1.8f, 0)
{
if (self < 0.1f) self = 0.1f;
}
CUSTOM_CVAR(Float, gl_paltonemap_powtable, 2.0f, CVAR_ARCHIVE | CVAR_NOINITCALL)
{
screen->UpdatePalette();
}
CUSTOM_CVAR(Bool, gl_paltonemap_reverselookup, true, CVAR_ARCHIVE | CVAR_NOINITCALL)
{
screen->UpdatePalette();
}
CVAR(Float, gl_menu_blur, -1.0f, CVAR_ARCHIVE)

View file

@ -0,0 +1,54 @@
#pragma once
#include "c_cvars.h"
class IFXAAShader
{
public:
enum Quality
{
None,
Low,
Medium,
High,
Extreme,
Count
};
};
//==========================================================================
//
// CVARs
//
//==========================================================================
EXTERN_CVAR(Bool, gl_bloom)
EXTERN_CVAR(Float, gl_bloom_amount)
EXTERN_CVAR(Float, gl_exposure_scale)
EXTERN_CVAR(Float, gl_exposure_min)
EXTERN_CVAR(Float, gl_exposure_base)
EXTERN_CVAR(Float, gl_exposure_speed)
EXTERN_CVAR(Int, gl_tonemap)
EXTERN_CVAR(Int, gl_bloom_kernel_size)
EXTERN_CVAR(Bool, gl_lens)
EXTERN_CVAR(Float, gl_lens_k)
EXTERN_CVAR(Float, gl_lens_kcube)
EXTERN_CVAR(Float, gl_lens_chromatic)
EXTERN_CVAR(Int, gl_fxaa)
EXTERN_CVAR(Int, gl_ssao)
EXTERN_CVAR(Int, gl_ssao_portals)
EXTERN_CVAR(Float, gl_ssao_strength)
EXTERN_CVAR(Int, gl_ssao_debug)
EXTERN_CVAR(Float, gl_ssao_bias)
EXTERN_CVAR(Float, gl_ssao_radius)
EXTERN_CVAR(Float, gl_ssao_blur)
EXTERN_CVAR(Float, gl_ssao_exponent)
EXTERN_CVAR(Float, gl_paltonemap_powtable)
EXTERN_CVAR(Bool, gl_paltonemap_reverselookup)
EXTERN_CVAR(Float, gl_menu_blur)
EXTERN_CVAR(Float, vid_brightness)
EXTERN_CVAR(Float, vid_contrast)
EXTERN_CVAR(Float, vid_saturation)
EXTERN_CVAR(Int, gl_satformula)

View file

@ -0,0 +1,171 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2017 Magnus Norddahl
// 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 "vm.h"
#include "d_player.h"
#include "hw_postprocessshader.h"
TArray<PostProcessShader> PostProcessShaders;
static bool IsConsolePlayer(player_t *player)
{
AActor *activator = player ? player->mo : nullptr;
if (activator == nullptr || activator->player == nullptr)
return false;
return int(activator->player - players) == consoleplayer;
}
static void ShaderSetEnabled(player_t *player, const FString &shaderName, bool value)
{
if (IsConsolePlayer(player))
{
for (unsigned int i = 0; i < PostProcessShaders.Size(); i++)
{
PostProcessShader &shader = PostProcessShaders[i];
if (shader.Name == shaderName)
shader.Enabled = value;
}
}
}
DEFINE_ACTION_FUNCTION_NATIVE(_Shader, SetEnabled, ShaderSetEnabled)
{
PARAM_PROLOGUE;
PARAM_POINTER(player, player_t);
PARAM_STRING(shaderName);
PARAM_BOOL(value);
ShaderSetEnabled(player, shaderName, value);
return 0;
}
static void ShaderSetUniform1f(player_t *player, const FString &shaderName, const FString &uniformName, double value)
{
if (IsConsolePlayer(player))
{
for (unsigned int i = 0; i < PostProcessShaders.Size(); i++)
{
PostProcessShader &shader = PostProcessShaders[i];
if (shader.Name == shaderName)
{
double *vec4 = shader.Uniforms[uniformName].Values;
vec4[0] = value;
vec4[1] = 0.0;
vec4[2] = 0.0;
vec4[3] = 1.0;
}
}
}
}
DEFINE_ACTION_FUNCTION_NATIVE(_Shader, SetUniform1f, ShaderSetUniform1f)
{
PARAM_PROLOGUE;
PARAM_POINTER(player, player_t);
PARAM_STRING(shaderName);
PARAM_STRING(uniformName);
PARAM_FLOAT(value);
ShaderSetUniform1f(player, shaderName, uniformName, value);
return 0;
}
DEFINE_ACTION_FUNCTION(_Shader, SetUniform2f)
{
PARAM_PROLOGUE;
PARAM_POINTER(player, player_t);
PARAM_STRING(shaderName);
PARAM_STRING(uniformName);
PARAM_FLOAT(x);
PARAM_FLOAT(y);
if (IsConsolePlayer(player))
{
for (unsigned int i = 0; i < PostProcessShaders.Size(); i++)
{
PostProcessShader &shader = PostProcessShaders[i];
if (shader.Name == shaderName)
{
double *vec4 = shader.Uniforms[uniformName].Values;
vec4[0] = x;
vec4[1] = y;
vec4[2] = 0.0;
vec4[3] = 1.0;
}
}
}
return 0;
}
DEFINE_ACTION_FUNCTION(_Shader, SetUniform3f)
{
PARAM_PROLOGUE;
PARAM_POINTER(player, player_t);
PARAM_STRING(shaderName);
PARAM_STRING(uniformName);
PARAM_FLOAT(x);
PARAM_FLOAT(y);
PARAM_FLOAT(z);
if (IsConsolePlayer(player))
{
for (unsigned int i = 0; i < PostProcessShaders.Size(); i++)
{
PostProcessShader &shader = PostProcessShaders[i];
if (shader.Name == shaderName)
{
double *vec4 = shader.Uniforms[uniformName].Values;
vec4[0] = x;
vec4[1] = y;
vec4[2] = z;
vec4[3] = 1.0;
}
}
}
return 0;
}
DEFINE_ACTION_FUNCTION(_Shader, SetUniform1i)
{
PARAM_PROLOGUE;
PARAM_POINTER(player, player_t);
PARAM_STRING(shaderName);
PARAM_STRING(uniformName);
PARAM_INT(value);
if (IsConsolePlayer(player))
{
for (unsigned int i = 0; i < PostProcessShaders.Size(); i++)
{
PostProcessShader &shader = PostProcessShaders[i];
if (shader.Name == shaderName)
{
double *vec4 = shader.Uniforms[uniformName].Values;
vec4[0] = (double)value;
vec4[1] = 0.0;
vec4[2] = 0.0;
vec4[3] = 1.0;
}
}
}
return 0;
}

View file

@ -0,0 +1,32 @@
#pragma once
enum class PostProcessUniformType
{
Undefined,
Int,
Float,
Vec2,
Vec3
};
struct PostProcessUniformValue
{
PostProcessUniformType Type = PostProcessUniformType::Undefined;
double Values[4] = { 0.0, 0.0, 0.0, 0.0 };
};
struct PostProcessShader
{
FString Target;
FString ShaderLumpName;
int ShaderVersion = 0;
FString Name;
bool Enabled = false;
TMap<FString, PostProcessUniformValue> Uniforms;
TMap<FString, FString> Textures;
};
extern TArray<PostProcessShader> PostProcessShaders;

View file

@ -0,0 +1,57 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2016 Christopher Bruns
// 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_3dRowshader.cpp
** Copy rendered texture to back buffer, possibly with gamma correction
** while interleaving rows from two independent viewpoint textures,
** representing the left-eye and right-eye views.
**
*/
#include "hw_present3dRowshader.h"
void FPresent3DCheckerShader::Bind(IRenderQueue *q)
{
if (!mShader)
{
Init("shaders/glsl/present_checker3d.fp", "shaders/glsl/presentChecker3d");
}
mShader->Bind(q);
}
void FPresent3DColumnShader::Bind(IRenderQueue *q)
{
if (!mShader)
{
Init("shaders/glsl/present_column3d.fp", "shaders/glsl/presentColumn3d");
}
mShader->Bind(q);
}
void FPresent3DRowShader::Bind(IRenderQueue *q)
{
if (!mShader)
{
Init("shaders/glsl/present_row3d.fp", "shaders/glsl/presentRow3d");
}
mShader->Bind(q);
}

View file

@ -0,0 +1,53 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2015 Christopher Bruns
// 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_present3dRowshader.h
** Final composition and present shader for row-interleaved stereoscopic 3D mode
**
*/
#ifndef GL_PRESENT3DROWSHADER_H_
#define GL_PRESENT3DROWSHADER_H_
#include "hw_shaderprogram.h"
#include "hw_presentshader.h"
class FPresent3DCheckerShader : public FPresentShaderBase
{
public:
void Bind(IRenderQueue *q) override;
};
class FPresent3DColumnShader : public FPresentShaderBase
{
public:
void Bind(IRenderQueue *q) override;
};
class FPresent3DRowShader : public FPresentShaderBase
{
public:
void Bind(IRenderQueue *q) override;
};
// GL_PRESENT3DROWSHADER_H_
#endif

View file

@ -0,0 +1,50 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2016 Magnus Norddahl
// 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_presentshader.cpp
** Copy rendered texture to back buffer, possibly with gamma correction
**
*/
#include "v_video.h"
#include "hw_presentshader.h"
void FPresentShaderBase::Init(const char * vtx_shader_name, const char * program_name)
{
FString prolog = Uniforms.CreateDeclaration("Uniforms", UniformBlock::Desc());
mShader.reset(screen->CreateShaderProgram());
mShader->Compile(IShaderProgram::Vertex, "shaders/glsl/screenquadscale.vp", prolog, 330);
mShader->Compile(IShaderProgram::Fragment, vtx_shader_name, prolog, 330);
mShader->Link(program_name);
mShader->SetUniformBufferLocation(Uniforms.BindingPoint(), "Uniforms");
Uniforms.Init();
}
void FPresentShader::Bind(IRenderQueue *q)
{
if (!mShader)
{
Init("shaders/glsl/present.fp", "shaders/glsl/present");
}
mShader->Bind(q);
}

View file

@ -0,0 +1,54 @@
#ifndef __GL_PRESENTSHADER_H
#define __GL_PRESENTSHADER_H
#include "hwrenderer/postprocessing/hw_shaderprogram.h"
class FPresentShaderBase
{
public:
virtual ~FPresentShaderBase() {}
virtual void Bind(IRenderQueue *q) = 0;
struct UniformBlock
{
float InvGamma;
float Contrast;
float Brightness;
float Saturation;
int GrayFormula;
int WindowPositionParity; // top-of-window might not be top-of-screen
FVector2 Scale;
float ColorScale;
float Padding1, Padding2, Padding3;
static std::vector<UniformFieldDesc> Desc()
{
return
{
{ "InvGamma", UniformType::Float, offsetof(UniformBlock, InvGamma) },
{ "Contrast", UniformType::Float, offsetof(UniformBlock, Contrast) },
{ "Brightness", UniformType::Float, offsetof(UniformBlock, Brightness) },
{ "Saturation", UniformType::Float, offsetof(UniformBlock, Saturation) },
{ "GrayFormula", UniformType::Int, offsetof(UniformBlock, GrayFormula) },
{ "WindowPositionParity", UniformType::Int, offsetof(UniformBlock, WindowPositionParity) },
{ "UVScale", UniformType::Vec2, offsetof(UniformBlock, Scale) },
{ "ColorScale", UniformType::Float, offsetof(UniformBlock, ColorScale) },
};
}
};
ShaderUniforms<UniformBlock, POSTPROCESS_BINDINGPOINT> Uniforms;
protected:
virtual void Init(const char * vtx_shader_name, const char * program_name);
std::unique_ptr<IShaderProgram> mShader;
};
class FPresentShader : public FPresentShaderBase
{
public:
void Bind(IRenderQueue *q) override;
};
#endif

View file

@ -0,0 +1,33 @@
#pragma once
#include <memory>
#include "hwrenderer/data/shaderuniforms.h"
class IRenderQueue;
class IShaderProgram
{
public:
IShaderProgram() {}
virtual ~IShaderProgram() {}
enum ShaderType
{
Vertex,
Fragment,
NumShaderTypes
};
virtual void Compile(ShaderType type, const char *lumpName, const char *defines, int maxGlslVersion) = 0;
virtual void Compile(ShaderType type, const char *name, const FString &code, const char *defines, int maxGlslVersion) = 0;
virtual void Link(const char *name) = 0;
virtual void SetUniformBufferLocation(int index, const char *name) = 0;
virtual void Bind(IRenderQueue *q) = 0; // the parameter here is just a preparation for Vulkan
private:
IShaderProgram(const IShaderProgram &) = delete;
IShaderProgram &operator=(const IShaderProgram &) = delete;
};

View file

@ -0,0 +1,40 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2016 Magnus Norddahl
// 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 "files.h"
#include "hw_shadowmapshader.h"
void FShadowMapShader::Bind(IRenderQueue *q)
{
if (!mShader)
{
FString prolog = Uniforms.CreateDeclaration("Uniforms", UniformBlock::Desc());
mShader.reset(screen->CreateShaderProgram());
mShader->Compile(IShaderProgram::Vertex, "shaders/glsl/screenquad.vp", "", 430);
mShader->Compile(IShaderProgram::Fragment, "shaders/glsl/shadowmap.fp", prolog, 430);
mShader->Link("shaders/glsl/shadowmap");
mShader->SetUniformBufferLocation(Uniforms.BindingPoint(), "Uniforms");
Uniforms.Init();
}
mShader->Bind(q);
}

View file

@ -0,0 +1,34 @@
#ifndef __GL_SHADOWMAPSHADER_H
#define __GL_SHADOWMAPSHADER_H
#include "hwrenderer/postprocessing/hw_shaderprogram.h"
class FShadowMapShader
{
public:
void Bind(IRenderQueue *q);
struct UniformBlock
{
float ShadowmapQuality;
float Padding0, Padding1, Padding2;
static std::vector<UniformFieldDesc> Desc()
{
return
{
{ "ShadowmapQuality", UniformType::Float, offsetof(UniformBlock, ShadowmapQuality) },
{ "Padding0", UniformType::Float, offsetof(UniformBlock, Padding0) },
{ "Padding1", UniformType::Float, offsetof(UniformBlock, Padding1) },
{ "Padding2", UniformType::Float, offsetof(UniformBlock, Padding2) },
};
}
};
ShaderUniforms<UniformBlock, POSTPROCESS_BINDINGPOINT> Uniforms;
private:
std::unique_ptr<IShaderProgram> mShader;
};
#endif

View file

@ -0,0 +1,829 @@
//
//---------------------------------------------------------------------------
//
// 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_bsp.cpp
** Main rendering loop / BSP traversal / visibility clipping
**
**/
#include "p_lnspec.h"
#include "p_local.h"
#include "a_sharedglobal.h"
#include "g_levellocals.h"
#include "p_effect.h"
#include "po_man.h"
#include "m_fixed.h"
#include "ctpl.h"
#include "hwrenderer/scene/hw_fakeflat.h"
#include "hwrenderer/scene/hw_clipper.h"
#include "hwrenderer/scene/hw_drawstructs.h"
#include "hwrenderer/scene/hw_drawinfo.h"
#include "hwrenderer/scene/hw_portal.h"
#include "hwrenderer/utility/hw_clock.h"
#include "hwrenderer/data/flatvertices.h"
#include <immintrin.h>
CVAR(Bool, gl_multithread, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
thread_local bool isWorkerThread;
ctpl::thread_pool renderPool(1);
bool inited = false;
struct RenderJob
{
enum
{
FlatJob,
WallJob,
SpriteJob,
ParticleJob,
PortalJob,
TerminateJob // inserted when all work is done so that the worker can return.
};
int type;
subsector_t *sub;
seg_t *seg;
};
class RenderJobQueue
{
RenderJob pool[300000]; // Way more than ever needed. The largest ever seen on a single viewpoint is around 40000.
std::atomic<int> readindex{};
std::atomic<int> writeindex{};
public:
void AddJob(int type, subsector_t *sub, seg_t *seg = nullptr)
{
// This does not check for array overflows. The pool should be large enough that it never hits the limit.
pool[writeindex] = { type, sub, seg };
writeindex++; // update index only after the value has been written.
}
RenderJob *GetJob()
{
if (readindex < writeindex) return &pool[readindex++];
return nullptr;
}
void ReleaseAll()
{
readindex = 0;
writeindex = 0;
}
};
static RenderJobQueue jobQueue; // One static queue is sufficient here. This code will never be called recursively.
void HWDrawInfo::WorkerThread()
{
sector_t *front, *back;
WTTotal.Clock();
isWorkerThread = true; // for adding asserts in GL API code. The worker thread may never call any GL API.
while (true)
{
auto job = jobQueue.GetJob();
if (job == nullptr)
{
// The queue is empty. But yielding would be too costly here and possibly cause further delays down the line if the thread is halted.
// So instead add a few pause instructions and retry immediately.
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
_mm_pause();
}
// Note that the main thread MUST have prepared the fake sectors that get used below!
// This worker thread cannot prepare them itself without costly synchronization.
else switch (job->type)
{
case RenderJob::TerminateJob:
WTTotal.Unclock();
return;
case RenderJob::WallJob:
{
GLWall wall;
SetupWall.Clock();
wall.sub = job->sub;
front = hw_FakeFlat(job->sub->sector, in_area, false);
auto seg = job->seg;
if (seg->backsector)
{
if (front->sectornum == seg->backsector->sectornum || (seg->sidedef->Flags & WALLF_POLYOBJ))
{
back = front;
}
else
{
back = hw_FakeFlat(seg->backsector, in_area, true);
}
}
else back = nullptr;
wall.Process(this, job->seg, front, back);
rendered_lines++;
SetupWall.Unclock();
break;
}
case RenderJob::FlatJob:
{
GLFlat flat;
SetupFlat.Clock();
flat.section = job->sub->section;
front = hw_FakeFlat(job->sub->render_sector, in_area, false);
flat.ProcessSector(this, front);
SetupFlat.Unclock();
break;
}
case RenderJob::SpriteJob:
SetupSprite.Clock();
front = hw_FakeFlat(job->sub->sector, in_area, false);
RenderThings(job->sub, front);
SetupSprite.Unclock();
break;
case RenderJob::ParticleJob:
SetupSprite.Clock();
front = hw_FakeFlat(job->sub->sector, in_area, false);
RenderParticles(job->sub, front);
SetupSprite.Unclock();
break;
case RenderJob::PortalJob:
AddSubsectorToPortal((FSectorPortalGroup *)job->seg, job->sub);
break;
}
}
}
EXTERN_CVAR(Bool, gl_render_segs)
CVAR(Bool, gl_render_things, true, 0)
CVAR(Bool, gl_render_walls, true, 0)
CVAR(Bool, gl_render_flats, true, 0)
void HWDrawInfo::UnclipSubsector(subsector_t *sub)
{
int count = sub->numlines;
seg_t * seg = sub->firstline;
auto &clipper = *mClipper;
while (count--)
{
angle_t startAngle = clipper.GetClipAngle(seg->v2);
angle_t endAngle = clipper.GetClipAngle(seg->v1);
// Back side, i.e. backface culling - read: endAngle >= startAngle!
if (startAngle-endAngle >= ANGLE_180)
{
clipper.SafeRemoveClipRange(startAngle, endAngle);
clipper.SetBlocked(false);
}
seg++;
}
}
//==========================================================================
//
// R_AddLine
// Clips the given segment
// and adds any visible pieces to the line list.
//
//==========================================================================
void HWDrawInfo::AddLine (seg_t *seg, bool portalclip)
{
#ifdef _DEBUG
if (seg->linedef && seg->linedef->Index() == 38)
{
int a = 0;
}
#endif
sector_t * backsector = nullptr;
if (portalclip)
{
int clipres = mClipPortal->ClipSeg(seg, Viewpoint.Pos);
if (clipres == PClip_InFront) return;
}
auto &clipper = *mClipper;
angle_t startAngle = clipper.GetClipAngle(seg->v2);
angle_t endAngle = clipper.GetClipAngle(seg->v1);
// Back side, i.e. backface culling - read: endAngle >= startAngle!
if (startAngle-endAngle<ANGLE_180)
{
return;
}
if (seg->sidedef == nullptr)
{
if (!(currentsubsector->flags & SSECMF_DRAWN))
{
if (clipper.SafeCheckRange(startAngle, endAngle))
{
currentsubsector->flags |= SSECMF_DRAWN;
}
}
return;
}
if (!clipper.SafeCheckRange(startAngle, endAngle))
{
return;
}
currentsubsector->flags |= SSECMF_DRAWN;
uint8_t ispoly = uint8_t(seg->sidedef->Flags & WALLF_POLYOBJ);
if (!seg->backsector)
{
clipper.SafeAddClipRange(startAngle, endAngle);
}
else if (!ispoly) // Two-sided polyobjects never obstruct the view
{
if (currentsector->sectornum == seg->backsector->sectornum)
{
if (!seg->linedef->isVisualPortal())
{
FTexture * tex = TexMan.GetTexture(seg->sidedef->GetTexture(side_t::mid), true);
if (!tex || !tex->isValid())
{
// nothing to do here!
seg->linedef->validcount=validcount;
return;
}
}
backsector=currentsector;
}
else
{
// clipping checks are only needed when the backsector is not the same as the front sector
if (in_area == area_default) in_area = hw_CheckViewArea(seg->v1, seg->v2, seg->frontsector, seg->backsector);
backsector = hw_FakeFlat(seg->backsector, in_area, true);
if (hw_CheckClip(seg->sidedef, currentsector, backsector))
{
clipper.SafeAddClipRange(startAngle, endAngle);
}
}
}
else
{
// Backsector for polyobj segs is always the containing sector itself
backsector = currentsector;
}
seg->linedef->flags |= ML_MAPPED;
if (ispoly || seg->linedef->validcount!=validcount)
{
if (!ispoly) seg->linedef->validcount=validcount;
if (gl_render_walls)
{
if (multithread)
{
jobQueue.AddJob(RenderJob::WallJob, seg->Subsector, seg);
}
else
{
GLWall wall;
SetupWall.Clock();
wall.sub = seg->Subsector;
wall.Process(this, seg, currentsector, backsector);
rendered_lines++;
SetupWall.Unclock();
}
}
}
}
//==========================================================================
//
// R_Subsector
// Determine floor/ceiling planes.
// Add sprites of things in sector.
// Draw one or more line segments.
//
//==========================================================================
void HWDrawInfo::PolySubsector(subsector_t * sub)
{
int count = sub->numlines;
seg_t * line = sub->firstline;
while (count--)
{
if (line->linedef)
{
AddLine (line, mClipPortal != nullptr);
}
line++;
}
}
//==========================================================================
//
// RenderBSPNode
// Renders all subsectors below a given node,
// traversing subtree recursively.
// Just call with BSP root.
//
//==========================================================================
void HWDrawInfo::RenderPolyBSPNode (void *node)
{
while (!((size_t)node & 1)) // Keep going until found a subsector
{
node_t *bsp = (node_t *)node;
// Decide which side the view point is on.
int side = R_PointOnSide(viewx, viewy, bsp);
// Recursively divide front space (toward the viewer).
RenderPolyBSPNode (bsp->children[side]);
// Possibly divide back space (away from the viewer).
side ^= 1;
// It is not necessary to use the slower precise version here
if (!mClipper->CheckBox(bsp->bbox[side]))
{
return;
}
node = bsp->children[side];
}
PolySubsector ((subsector_t *)((uint8_t *)node - 1));
}
//==========================================================================
//
// Unlilke the software renderer this function will only draw the walls,
// not the flats. Those are handled as a whole by the parent subsector.
//
//==========================================================================
void HWDrawInfo::AddPolyobjs(subsector_t *sub)
{
if (sub->BSP == nullptr || sub->BSP->bDirty)
{
sub->BuildPolyBSP();
for (unsigned i = 0; i < sub->BSP->Segs.Size(); i++)
{
sub->BSP->Segs[i].Subsector = sub;
sub->BSP->Segs[i].PartnerSeg = nullptr;
}
}
if (sub->BSP->Nodes.Size() == 0)
{
PolySubsector(&sub->BSP->Subsectors[0]);
}
else
{
RenderPolyBSPNode(&sub->BSP->Nodes.Last());
}
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawInfo::AddLines(subsector_t * sub, sector_t * sector)
{
currentsector = sector;
currentsubsector = sub;
ClipWall.Clock();
if (sub->polys != nullptr)
{
AddPolyobjs(sub);
}
else
{
int count = sub->numlines;
seg_t * seg = sub->firstline;
while (count--)
{
if (seg->linedef == nullptr)
{
if (!(sub->flags & SSECMF_DRAWN)) AddLine (seg, mClipPortal != nullptr);
}
else if (!(seg->sidedef->Flags & WALLF_POLYOBJ))
{
AddLine (seg, mClipPortal != nullptr);
}
seg++;
}
}
ClipWall.Unclock();
}
//==========================================================================
//
// Adds lines that lie directly on the portal boundary.
// Only two-sided lines will be handled here, and no polyobjects
//
//==========================================================================
inline bool PointOnLine(const DVector2 &pos, const line_t *line)
{
double v = (pos.Y - line->v1->fY()) * line->Delta().X + (line->v1->fX() - pos.X) * line->Delta().Y;
return fabs(v) <= EQUAL_EPSILON;
}
void HWDrawInfo::AddSpecialPortalLines(subsector_t * sub, sector_t * sector, line_t *line)
{
currentsector = sector;
currentsubsector = sub;
ClipWall.Clock();
int count = sub->numlines;
seg_t * seg = sub->firstline;
while (count--)
{
if (seg->linedef != nullptr && seg->PartnerSeg != nullptr)
{
if (PointOnLine(seg->v1->fPos(), line) && PointOnLine(seg->v2->fPos(), line))
AddLine(seg, false);
}
seg++;
}
ClipWall.Unclock();
}
//==========================================================================
//
// R_RenderThings
//
//==========================================================================
void HWDrawInfo::RenderThings(subsector_t * sub, sector_t * sector)
{
sector_t * sec=sub->sector;
// Handle all things in sector.
const auto &vp = Viewpoint;
for (auto p = sec->touching_renderthings; p != nullptr; p = p->m_snext)
{
auto thing = p->m_thing;
if (thing->validcount == validcount) continue;
thing->validcount = validcount;
FIntCVar *cvar = thing->GetInfo()->distancecheck;
if (cvar != nullptr && *cvar >= 0)
{
double dist = (thing->Pos() - vp.Pos).LengthSquared();
double check = (double)**cvar;
if (dist >= check * check)
{
continue;
}
}
// If this thing is in a map section that's not in view it can't possibly be visible
if (CurrentMapSections[thing->subsector->mapsection])
{
GLSprite sprite;
sprite.Process(this, thing, sector, in_area, false);
}
}
for (msecnode_t *node = sec->sectorportal_thinglist; node; node = node->m_snext)
{
AActor *thing = node->m_thing;
FIntCVar *cvar = thing->GetInfo()->distancecheck;
if (cvar != nullptr && *cvar >= 0)
{
double dist = (thing->Pos() - vp.Pos).LengthSquared();
double check = (double)**cvar;
if (dist >= check * check)
{
continue;
}
}
GLSprite sprite;
sprite.Process(this, thing, sector, in_area, true);
}
}
void HWDrawInfo::RenderParticles(subsector_t *sub, sector_t *front)
{
SetupSprite.Clock();
for (int i = Level->ParticlesInSubsec[sub->Index()]; i != NO_PARTICLE; i = Level->Particles[i].snext)
{
if (mClipPortal)
{
int clipres = mClipPortal->ClipPoint(Level->Particles[i].Pos);
if (clipres == PClip_InFront) continue;
}
GLSprite sprite;
sprite.ProcessParticle(this, &Level->Particles[i], front);
}
SetupSprite.Unclock();
}
//==========================================================================
//
// R_Subsector
// Determine floor/ceiling planes.
// Add sprites of things in sector.
// Draw one or more line segments.
//
//==========================================================================
void HWDrawInfo::DoSubsector(subsector_t * sub)
{
sector_t * sector;
sector_t * fakesector;
#ifdef _DEBUG
if (sub->sector->sectornum==931)
{
int a = 0;
}
#endif
sector=sub->sector;
if (!sector) return;
// If the mapsections differ this subsector can't possibly be visible from the current view point
if (!CurrentMapSections[sub->mapsection]) return;
if (sub->flags & SSECF_POLYORG) return; // never render polyobject origin subsectors because their vertices no longer are where one may expect.
if (ss_renderflags[sub->Index()] & SSRF_SEEN)
{
// This means that we have reached a subsector in a portal that has been marked 'seen'
// from the other side of the portal. This means we must clear the clipper for the
// range this subsector spans before going on.
UnclipSubsector(sub);
}
if (mClipper->IsBlocked()) return; // if we are inside a stacked sector portal which hasn't unclipped anything yet.
fakesector=hw_FakeFlat(sector, in_area, false);
if (mClipPortal)
{
int clipres = mClipPortal->ClipSubsector(sub);
if (clipres == PClip_InFront)
{
line_t *line = mClipPortal->ClipLine();
// The subsector is out of range, but we still have to check lines that lie directly on the boundary and may expose their upper or lower parts.
if (line) AddSpecialPortalLines(sub, fakesector, line);
return;
}
}
if (sector->validcount != validcount)
{
screen->mVertexData->CheckUpdate(sector);
}
// [RH] Add particles
if (gl_render_things && Level->ParticlesInSubsec[sub->Index()] != NO_PARTICLE)
{
if (multithread)
{
jobQueue.AddJob(RenderJob::ParticleJob, sub, nullptr);
}
else
{
SetupSprite.Clock();
RenderParticles(sub, fakesector);
SetupSprite.Unclock();
}
}
AddLines(sub, fakesector);
// BSP is traversed by subsector.
// A sector might have been split into several
// subsectors during BSP building.
// Thus we check whether it was already added.
if (sector->validcount != validcount)
{
// Well, now it will be done.
sector->validcount = validcount;
sector->MoreFlags |= SECMF_DRAWN;
if (gl_render_things && (sector->touching_renderthings || sector->sectorportal_thinglist))
{
if (multithread)
{
jobQueue.AddJob(RenderJob::SpriteJob, sub, nullptr);
}
else
{
SetupSprite.Clock();
RenderThings(sub, fakesector);
SetupSprite.Unclock();
}
}
}
if (gl_render_flats)
{
// Subsectors with only 2 lines cannot have any area
if (sub->numlines>2 || (sub->hacked&1))
{
// Exclude the case when it tries to render a sector with a heightsec
// but undetermined heightsec state. This can only happen if the
// subsector is obstructed but not excluded due to a large bounding box.
// Due to the way a BSP works such a subsector can never be visible
if (!sector->GetHeightSec() || in_area!=area_default)
{
if (sector != sub->render_sector)
{
sector = sub->render_sector;
// the planes of this subsector are faked to belong to another sector
// This means we need the heightsec parts and light info of the render sector, not the actual one.
fakesector = hw_FakeFlat(sector, in_area, false);
}
uint8_t &srf = section_renderflags[Level->sections.SectionIndex(sub->section)];
if (!(srf & SSRF_PROCESSED))
{
srf |= SSRF_PROCESSED;
if (multithread)
{
jobQueue.AddJob(RenderJob::FlatJob, sub);
}
else
{
GLFlat flat;
flat.section = sub->section;
SetupFlat.Clock();
flat.ProcessSector(this, fakesector);
SetupFlat.Unclock();
}
}
// mark subsector as processed - but mark for rendering only if it has an actual area.
ss_renderflags[sub->Index()] =
(sub->numlines > 2) ? SSRF_PROCESSED|SSRF_RENDERALL : SSRF_PROCESSED;
if (sub->hacked & 1) AddHackedSubsector(sub);
// This is for portal coverage.
FSectorPortalGroup *portal;
// AddSubsectorToPortal cannot be called here when using multithreaded processing,
// because the wall processing code in the worker can also modify the portal state.
// To avoid costly synchronization for every access to the portal list,
// the call to AddSubsectorToPortal will be deferred to the worker.
// (GetPortalGruop only accesses static sector data so this check can be done here, restricting the new job to the minimum possible extent.)
portal = fakesector->GetPortalGroup(sector_t::ceiling);
if (portal != nullptr)
{
if (multithread)
{
jobQueue.AddJob(RenderJob::PortalJob, sub, (seg_t *)portal);
}
else
{
AddSubsectorToPortal(portal, sub);
}
}
portal = fakesector->GetPortalGroup(sector_t::floor);
if (portal != nullptr)
{
if (multithread)
{
jobQueue.AddJob(RenderJob::PortalJob, sub, (seg_t *)portal);
}
else
{
AddSubsectorToPortal(portal, sub);
}
}
}
}
}
}
//==========================================================================
//
// RenderBSPNode
// Renders all subsectors below a given node,
// traversing subtree recursively.
// Just call with BSP root.
//
//==========================================================================
void HWDrawInfo::RenderBSPNode (void *node)
{
if (Level->nodes.Size() == 0)
{
DoSubsector (&Level->subsectors[0]);
return;
}
while (!((size_t)node & 1)) // Keep going until found a subsector
{
node_t *bsp = (node_t *)node;
// Decide which side the view point is on.
int side = R_PointOnSide(viewx, viewy, bsp);
// Recursively divide front space (toward the viewer).
RenderBSPNode (bsp->children[side]);
// Possibly divide back space (away from the viewer).
side ^= 1;
// It is not necessary to use the slower precise version here
if (!mClipper->CheckBox(bsp->bbox[side]))
{
if (!(no_renderflags[bsp->Index()] & SSRF_SEEN))
return;
}
node = bsp->children[side];
}
DoSubsector ((subsector_t *)((uint8_t *)node - 1));
}
void HWDrawInfo::RenderBSP(void *node)
{
Bsp.Clock();
// Give the DrawInfo the viewpoint in fixed point because that's what the nodes are.
viewx = FLOAT2FIXED(Viewpoint.Pos.X);
viewy = FLOAT2FIXED(Viewpoint.Pos.Y);
validcount++; // used for processing sidedefs only once by the renderer.
multithread = gl_multithread;
if (multithread)
{
jobQueue.ReleaseAll();
auto future = renderPool.push([&](int id) {
WorkerThread();
});
RenderBSPNode(node);
jobQueue.AddJob(RenderJob::TerminateJob, nullptr, nullptr);
Bsp.Unclock();
MTWait.Clock();
future.wait();
MTWait.Unclock();
}
else
{
RenderBSPNode(node);
Bsp.Unclock();
}
// Process all the sprites on the current portal's back side which touch the portal.
if (mCurrentPortal != nullptr) mCurrentPortal->RenderAttached(this);
PreparePlayerSprites(Viewpoint.sector, in_area);
}

View file

@ -0,0 +1,442 @@
/*
*
** gl_clipper.cpp
**
** Handles visibility checks.
** Loosely based on the JDoom clipper.
**
**---------------------------------------------------------------------------
** Copyright 2003 Tim Stump
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "hw_clipper.h"
#include "g_levellocals.h"
unsigned Clipper::starttime;
Clipper::Clipper()
{
starttime++;
}
//-----------------------------------------------------------------------------
//
// RemoveRange
//
//-----------------------------------------------------------------------------
void Clipper::RemoveRange(ClipNode * range)
{
if (range == cliphead)
{
cliphead = cliphead->next;
}
else
{
if (range->prev) range->prev->next = range->next;
if (range->next) range->next->prev = range->prev;
}
Free(range);
}
//-----------------------------------------------------------------------------
//
// Clear
//
//-----------------------------------------------------------------------------
void Clipper::Clear()
{
ClipNode *node = cliphead;
ClipNode *temp;
blocked = false;
while (node != NULL)
{
temp = node;
node = node->next;
Free(temp);
}
node = silhouette;
while (node != NULL)
{
temp = node;
node = node->next;
Free(temp);
}
cliphead = NULL;
silhouette = NULL;
starttime++;
}
//-----------------------------------------------------------------------------
//
// SetSilhouette
//
//-----------------------------------------------------------------------------
void Clipper::SetSilhouette()
{
ClipNode *node = cliphead;
ClipNode *last = NULL;
while (node != NULL)
{
ClipNode *snode = NewRange(node->start, node->end);
if (silhouette == NULL) silhouette = snode;
snode->prev = last;
if (last != NULL) last->next = snode;
last = snode;
node = node->next;
}
}
//-----------------------------------------------------------------------------
//
// IsRangeVisible
//
//-----------------------------------------------------------------------------
bool Clipper::IsRangeVisible(angle_t startAngle, angle_t endAngle)
{
ClipNode *ci;
ci = cliphead;
if (endAngle==0 && ci && ci->start==0) return false;
while (ci != NULL && ci->start < endAngle)
{
if (startAngle >= ci->start && endAngle <= ci->end)
{
return false;
}
ci = ci->next;
}
return true;
}
//-----------------------------------------------------------------------------
//
// AddClipRange
//
//-----------------------------------------------------------------------------
void Clipper::AddClipRange(angle_t start, angle_t end)
{
ClipNode *node, *temp, *prevNode;
if (cliphead)
{
//check to see if range contains any old ranges
node = cliphead;
while (node != NULL && node->start < end)
{
if (node->start >= start && node->end <= end)
{
temp = node;
node = node->next;
RemoveRange(temp);
}
else if (node->start<=start && node->end>=end)
{
return;
}
else
{
node = node->next;
}
}
//check to see if range overlaps a range (or possibly 2)
node = cliphead;
while (node != NULL && node->start <= end)
{
if (node->end >= start)
{
// we found the first overlapping node
if (node->start > start)
{
// the new range overlaps with this node's start point
node->start = start;
}
if (node->end < end)
{
node->end = end;
}
ClipNode *node2 = node->next;
while (node2 && node2->start <= node->end)
{
if (node2->end > node->end) node->end = node2->end;
ClipNode *delnode = node2;
node2 = node2->next;
RemoveRange(delnode);
}
return;
}
node = node->next;
}
//just add range
node = cliphead;
prevNode = NULL;
temp = NewRange(start, end);
while (node != NULL && node->start < end)
{
prevNode = node;
node = node->next;
}
temp->next = node;
if (node == NULL)
{
temp->prev = prevNode;
if (prevNode) prevNode->next = temp;
if (!cliphead) cliphead = temp;
}
else
{
if (node == cliphead)
{
cliphead->prev = temp;
cliphead = temp;
}
else
{
temp->prev = prevNode;
prevNode->next = temp;
node->prev = temp;
}
}
}
else
{
temp = NewRange(start, end);
cliphead = temp;
return;
}
}
//-----------------------------------------------------------------------------
//
// RemoveClipRange
//
//-----------------------------------------------------------------------------
void Clipper::RemoveClipRange(angle_t start, angle_t end)
{
ClipNode *node;
if (silhouette)
{
node = silhouette;
while (node != NULL && node->end <= start)
{
node = node->next;
}
if (node != NULL && node->start <= start)
{
if (node->end >= end) return;
start = node->end;
node = node->next;
}
while (node != NULL && node->start < end)
{
DoRemoveClipRange(start, node->start);
start = node->end;
node = node->next;
}
if (start >= end) return;
}
DoRemoveClipRange(start, end);
}
//-----------------------------------------------------------------------------
//
// RemoveClipRange worker function
//
//-----------------------------------------------------------------------------
void Clipper::DoRemoveClipRange(angle_t start, angle_t end)
{
ClipNode *node, *temp;
if (cliphead)
{
//check to see if range contains any old ranges
node = cliphead;
while (node != NULL && node->start < end)
{
if (node->start >= start && node->end <= end)
{
temp = node;
node = node->next;
RemoveRange(temp);
}
else
{
node = node->next;
}
}
//check to see if range overlaps a range (or possibly 2)
node = cliphead;
while (node != NULL)
{
if (node->start >= start && node->start <= end)
{
node->start = end;
break;
}
else if (node->end >= start && node->end <= end)
{
node->end=start;
}
else if (node->start < start && node->end > end)
{
temp = NewRange(end, node->end);
node->end=start;
temp->next=node->next;
temp->prev=node;
node->next=temp;
if (temp->next) temp->next->prev=temp;
break;
}
node = node->next;
}
}
}
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
angle_t Clipper::AngleToPseudo(angle_t ang)
{
double vecx = cos(ang * M_PI / ANGLE_180);
double vecy = sin(ang * M_PI / ANGLE_180);
double result = vecy / (fabs(vecx) + fabs(vecy));
if (vecx < 0)
{
result = 2.f - result;
}
return xs_Fix<30>::ToFix(result);
}
//-----------------------------------------------------------------------------
//
// ! Returns the pseudoangle between the line p1 to (infinity, p1.y) and the
// line from p1 to p2. The pseudoangle has the property that the ordering of
// points by true angle around p1 and ordering of points by pseudoangle are the
// same.
//
// For clipping exact angles are not needed. Only the ordering matters.
// This is about as fast as the fixed point R_PointToAngle2 but without
// the precision issues associated with that function.
//
//-----------------------------------------------------------------------------
angle_t Clipper::PointToPseudoAngle(double x, double y)
{
double vecx = x - viewpoint->Pos.X;
double vecy = y - viewpoint->Pos.Y;
if (vecx == 0 && vecy == 0)
{
return 0;
}
else
{
double result = vecy / (fabs(vecx) + fabs(vecy));
if (vecx < 0)
{
result = 2. - result;
}
return xs_Fix<30>::ToFix(result);
}
}
//-----------------------------------------------------------------------------
//
// R_CheckBBox
// Checks BSP node/subtree bounding box.
// Returns true
// if some part of the bbox might be visible.
//
//-----------------------------------------------------------------------------
static const uint8_t checkcoord[12][4] = // killough -- static const
{
{3,0,2,1},
{3,0,2,0},
{3,1,2,0},
{0},
{2,0,2,1},
{0,0,0,0},
{3,1,3,0},
{0},
{2,0,3,1},
{2,1,3,1},
{2,1,3,0}
};
bool Clipper::CheckBox(const float *bspcoord)
{
angle_t angle1, angle2;
int boxpos;
const uint8_t* check;
// Find the corners of the box
// that define the edges from current viewpoint.
auto &vp = viewpoint;
boxpos = (vp->Pos.X <= bspcoord[BOXLEFT] ? 0 : vp->Pos.X < bspcoord[BOXRIGHT ] ? 1 : 2) +
(vp->Pos.Y >= bspcoord[BOXTOP ] ? 0 : vp->Pos.Y > bspcoord[BOXBOTTOM] ? 4 : 8);
if (boxpos == 5) return true;
check = checkcoord[boxpos];
angle1 = PointToPseudoAngle (bspcoord[check[0]], bspcoord[check[1]]);
angle2 = PointToPseudoAngle (bspcoord[check[2]], bspcoord[check[3]]);
return SafeCheckRange(angle2, angle1);
}

View file

@ -0,0 +1,162 @@
#ifndef __GL_CLIPPER
#define __GL_CLIPPER
#include "doomtype.h"
#include "xs_Float.h"
#include "r_utility.h"
#include "memarena.h"
class ClipNode
{
friend class Clipper;
ClipNode *prev, *next;
angle_t start, end;
bool operator== (const ClipNode &other)
{
return other.start == start && other.end == end;
}
};
class Clipper
{
static unsigned starttime;
FMemArena nodearena;
ClipNode * freelist = nullptr;
ClipNode * clipnodes = nullptr;
ClipNode * cliphead = nullptr;
ClipNode * silhouette = nullptr; // will be preserved even when RemoveClipRange is called
const FRenderViewpoint *viewpoint = nullptr;
bool blocked = false;
static angle_t AngleToPseudo(angle_t ang);
bool IsRangeVisible(angle_t startangle, angle_t endangle);
void RemoveRange(ClipNode * cn);
void AddClipRange(angle_t startangle, angle_t endangle);
void RemoveClipRange(angle_t startangle, angle_t endangle);
void DoRemoveClipRange(angle_t start, angle_t end);
public:
Clipper();
void Clear();
void Free(ClipNode *node)
{
node->next = freelist;
freelist = node;
}
ClipNode * GetNew()
{
if (freelist)
{
ClipNode * p = freelist;
freelist = p->next;
return p;
}
else return (ClipNode*)nodearena.Alloc(sizeof(ClipNode));
}
ClipNode * NewRange(angle_t start, angle_t end)
{
ClipNode * c = GetNew();
c->start = start;
c->end = end;
c->next = c->prev = NULL;
return c;
}
void SetViewpoint(const FRenderViewpoint &vp)
{
viewpoint = &vp;
}
void SetSilhouette();
bool SafeCheckRange(angle_t startAngle, angle_t endAngle)
{
if(startAngle > endAngle)
{
return (IsRangeVisible(startAngle, ANGLE_MAX) || IsRangeVisible(0, endAngle));
}
return IsRangeVisible(startAngle, endAngle);
}
void SafeAddClipRange(angle_t startangle, angle_t endangle)
{
if(startangle > endangle)
{
// The range has to added in two parts.
AddClipRange(startangle, ANGLE_MAX);
AddClipRange(0, endangle);
}
else
{
// Add the range as usual.
AddClipRange(startangle, endangle);
}
}
void SafeAddClipRange(const vertex_t *v1, const vertex_t *v2)
{
angle_t a2 = PointToPseudoAngle(v1->p.X, v1->p.Y);
angle_t a1 = PointToPseudoAngle(v2->p.X, v2->p.Y);
SafeAddClipRange(a1,a2);
}
void SafeAddClipRangeRealAngles(angle_t startangle, angle_t endangle)
{
SafeAddClipRange(AngleToPseudo(startangle), AngleToPseudo(endangle));
}
void SafeRemoveClipRange(angle_t startangle, angle_t endangle)
{
if(startangle > endangle)
{
// The range has to added in two parts.
RemoveClipRange(startangle, ANGLE_MAX);
RemoveClipRange(0, endangle);
}
else
{
// Add the range as usual.
RemoveClipRange(startangle, endangle);
}
}
void SafeRemoveClipRangeRealAngles(angle_t startangle, angle_t endangle)
{
SafeRemoveClipRange(AngleToPseudo(startangle), AngleToPseudo(endangle));
}
void SetBlocked(bool on)
{
blocked = on;
}
bool IsBlocked() const
{
return blocked;
}
angle_t PointToPseudoAngle(double x, double y);
bool CheckBox(const float *bspcoord);
// Used to speed up angle calculations during clipping
inline angle_t GetClipAngle(vertex_t *v)
{
return unsigned(v->angletime) == starttime ? v->viewangle : (v->angletime = starttime, v->viewangle = PointToPseudoAngle(v->p.X, v->p.Y));
}
};
#endif

View file

@ -0,0 +1,443 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2003-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 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_decal.cpp
** OpenGL decal processing code
**
*/
#include "doomdata.h"
#include "a_sharedglobal.h"
#include "r_utility.h"
#include "g_levellocals.h"
#include "hwrenderer/textures/hw_material.h"
#include "hwrenderer/utility/hw_cvars.h"
#include "hwrenderer/scene/hw_drawstructs.h"
#include "hwrenderer/scene/hw_drawinfo.h"
#include "hwrenderer/utility/hw_lighting.h"
#include "hwrenderer/utility/hw_clock.h"
#include "hwrenderer/data/flatvertices.h"
#include "hw_renderstate.h"
//==========================================================================
//
//
//
//==========================================================================
void GLDecal::DrawDecal(HWDrawInfo *di, FRenderState &state)
{
auto tex = gltexture;
// calculate dynamic light effect.
if (di->Level->HasDynamicLights && !di->isFullbrightScene() && gl_light_sprites)
{
// Note: This should be replaced with proper shader based lighting.
double x, y;
float out[3];
decal->GetXY(decal->Side, x, y);
di->GetDynSpriteLight(nullptr, x, y, zcenter, decal->Side->lighthead, decal->Side->sector->PortalGroup, out);
state.SetDynLight(out[0], out[1], out[2]);
}
// alpha color only has an effect when using an alpha texture.
if (decal->RenderStyle.Flags & (STYLEF_RedIsAlpha | STYLEF_ColorIsFixed))
{
state.SetObjectColor(decal->AlphaColor | 0xff000000);
}
state.SetTextureMode(decal->RenderStyle);
state.SetRenderStyle(decal->RenderStyle);
state.SetMaterial(tex, CLAMP_XY, decal->Translation, -1);
// If srcalpha is one it looks better with a higher alpha threshold
if (decal->RenderStyle.SrcAlpha == STYLEALPHA_One) state.AlphaFunc(Alpha_GEqual, gl_mask_sprite_threshold);
else state.AlphaFunc(Alpha_Greater, 0.f);
di->SetColor(state, lightlevel, rellight, di->isFullbrightScene(), Colormap, alpha);
// for additively drawn decals we must temporarily set the fog color to black.
PalEntry fc = state.GetFogColor();
if (decal->RenderStyle.BlendOp == STYLEOP_Add && decal->RenderStyle.DestAlpha == STYLEALPHA_One)
{
state.SetFog(0, -1);
}
state.SetNormal(Normal);
if (lightlist == nullptr)
{
state.Draw(DT_TriangleFan, vertindex, 4);
}
else
{
auto &lightlist = *this->lightlist;
for (unsigned k = 0; k < lightlist.Size(); k++)
{
secplane_t &lowplane = k == lightlist.Size() - 1 ? frontsector->floorplane : lightlist[k + 1].plane;
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 != nullptr ? hw_ClampLight(*lightlist[k].p_lightlevel) : lightlevel;
FColormap thiscm;
thiscm.FadeColor = Colormap.FadeColor;
thiscm.CopyFrom3DLight(&lightlist[k]);
di->SetColor(state, thisll, rellight, di->isFullbrightScene(), thiscm, alpha);
if (di->Level->flags3 & LEVEL3_NOCOLOREDSPRITELIGHTING) thiscm.Decolorize();
di->SetFog(state, thisll, rellight, di->isFullbrightScene(), &thiscm, false);
state.SetSplitPlanes(lightlist[k].plane, lowplane);
state.Draw(DT_TriangleFan, vertindex, 4);
}
if (low1 <= dv[0].z && low2 <= dv[3].z) break;
}
}
rendered_decals++;
state.SetTextureMode(TM_NORMAL);
state.SetObjectColor(0xffffffff);
state.SetFog(fc, -1);
state.SetDynLight(0, 0, 0);
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawInfo::DrawDecals(FRenderState &state, TArray<GLDecal *> &decals)
{
side_t *wall = nullptr;
state.SetDepthMask(false);
state.SetDepthBias(-1, -128);
state.SetLightIndex(-1);
for (auto gldecal : decals)
{
if (gldecal->decal->Side != wall)
{
wall = gldecal->decal->Side;
if (gldecal->lightlist != nullptr)
{
state.EnableSplit(true);
}
else
{
state.EnableSplit(false);
SetFog(state, gldecal->lightlevel, gldecal->rellight, isFullbrightScene(), &gldecal->Colormap, false);
}
}
gldecal->DrawDecal(this, state);
}
state.EnableSplit(false);
state.ClearDepthBias();
state.SetTextureMode(TM_NORMAL);
state.SetDepthMask(true);
}
//==========================================================================
//
// This list will never get long, so this code should be ok.
//
//==========================================================================
void GLWall::DrawDecalsForMirror(HWDrawInfo *di, FRenderState &state, TArray<GLDecal *> &decals)
{
state.SetDepthMask(false);
state.SetDepthBias(-1, -128);
state.SetLightIndex(-1);
di->SetFog(state, lightlevel, rellight + getExtraLight(), di->isFullbrightScene(), &Colormap, false);
for (auto gldecal : decals)
{
if (gldecal->decal->Side == seg->sidedef)
{
gldecal->DrawDecal(di, state);
}
}
state.ClearDepthBias();
state.SetTextureMode(TM_NORMAL);
state.SetDepthMask(true);
}
//==========================================================================
//
//
//
//==========================================================================
void GLWall::ProcessDecal(HWDrawInfo *di, DBaseDecal *decal, const FVector3 &normal)
{
line_t * line = seg->linedef;
side_t * side = seg->sidedef;
int i;
float zpos;
bool flipx, flipy;
FTextureID decalTile;
if (decal->RenderFlags & RF_INVISIBLE) return;
if (type == RENDERWALL_FFBLOCK && gltexture->isMasked()) return; // No decals on 3D floors with transparent textures.
if (seg == nullptr) return;
decalTile = decal->PicNum;
flipx = !!(decal->RenderFlags & RF_XFLIP);
flipy = !!(decal->RenderFlags & RF_YFLIP);
FTexture *texture = TexMan.GetTexture(decalTile);
if (texture == NULL) return;
// the sectors are only used for their texture origin coordinates
// so we don't need the fake sectors for deep water etc.
// As this is a completely split wall fragment no further splits are
// necessary for the decal.
sector_t *frontsector;
// for 3d-floor segments use the model sector as reference
if ((decal->RenderFlags&RF_CLIPMASK) == RF_CLIPMID) frontsector = decal->Sector;
else frontsector = seg->frontsector;
switch (decal->RenderFlags & RF_RELMASK)
{
default:
// No valid decal can have this type. If one is encountered anyway
// it is in some way invalid so skip it.
return;
//zpos = decal->z;
//break;
case RF_RELUPPER:
if (type != RENDERWALL_TOP) return;
if (line->flags & ML_DONTPEGTOP)
{
zpos = decal->Z + frontsector->GetPlaneTexZ(sector_t::ceiling);
}
else
{
zpos = decal->Z + seg->backsector->GetPlaneTexZ(sector_t::ceiling);
}
break;
case RF_RELLOWER:
if (type != RENDERWALL_BOTTOM) return;
if (line->flags & ML_DONTPEGBOTTOM)
{
zpos = decal->Z + frontsector->GetPlaneTexZ(sector_t::ceiling);
}
else
{
zpos = decal->Z + seg->backsector->GetPlaneTexZ(sector_t::floor);
}
break;
case RF_RELMID:
if (type == RENDERWALL_TOP || type == RENDERWALL_BOTTOM) return;
if (line->flags & ML_DONTPEGBOTTOM)
{
zpos = decal->Z + frontsector->GetPlaneTexZ(sector_t::floor);
}
else
{
zpos = decal->Z + frontsector->GetPlaneTexZ(sector_t::ceiling);
}
}
FMaterial *tex = FMaterial::ValidateTexture(texture, false);
// now clip the decal to the actual polygon
float decalwidth = tex->TextureWidth() * decal->ScaleX;
float decalheight = tex->TextureHeight() * decal->ScaleY;
float decallefto = tex->GetLeftOffset() * decal->ScaleX;
float decaltopo = tex->GetTopOffset() * decal->ScaleY;
float leftedge = glseg.fracleft * side->TexelLength;
float linelength = glseg.fracright * side->TexelLength - leftedge;
// texel index of the decal's left edge
float decalpixpos = (float)side->TexelLength * decal->LeftDistance - (flipx ? decalwidth - decallefto : decallefto) - leftedge;
float left, right;
float lefttex, righttex;
// decal is off the left edge
if (decalpixpos < 0)
{
left = 0;
lefttex = -decalpixpos;
}
else
{
left = decalpixpos;
lefttex = 0;
}
// decal is off the right edge
if (decalpixpos + decalwidth > linelength)
{
right = linelength;
righttex = right - decalpixpos;
}
else
{
right = decalpixpos + decalwidth;
righttex = decalwidth;
}
if (right <= left)
return; // nothing to draw
// one texture unit on the wall as vector
float vx = (glseg.x2 - glseg.x1) / linelength;
float vy = (glseg.y2 - glseg.y1) / linelength;
DecalVertex dv[4];
dv[1].x = dv[0].x = glseg.x1 + vx * left;
dv[1].y = dv[0].y = glseg.y1 + vy * left;
dv[3].x = dv[2].x = glseg.x1 + vx * right;
dv[3].y = dv[2].y = glseg.y1 + vy * right;
zpos += (flipy ? decalheight - decaltopo : decaltopo);
dv[1].z = dv[2].z = zpos;
dv[0].z = dv[3].z = dv[1].z - decalheight;
dv[1].v = dv[2].v = tex->GetVT();
dv[1].u = dv[0].u = tex->GetU(lefttex / decal->ScaleX);
dv[3].u = dv[2].u = tex->GetU(righttex / decal->ScaleX);
dv[0].v = dv[3].v = tex->GetVB();
// now clip to the top plane
float vzt = (ztop[1] - ztop[0]) / linelength;
float topleft = ztop[0] + vzt * left;
float topright = ztop[0] + vzt * right;
// completely below the wall
if (topleft < dv[0].z && topright < dv[3].z)
return;
if (topleft < dv[1].z || topright < dv[2].z)
{
// decal has to be clipped at the top
// let texture clamping handle all extreme cases
dv[1].v = (dv[1].z - topleft) / (dv[1].z - dv[0].z)*dv[0].v;
dv[2].v = (dv[2].z - topright) / (dv[2].z - dv[3].z)*dv[3].v;
dv[1].z = topleft;
dv[2].z = topright;
}
// now clip to the bottom plane
float vzb = (zbottom[1] - zbottom[0]) / linelength;
float bottomleft = zbottom[0] + vzb * left;
float bottomright = zbottom[0] + vzb * right;
// completely above the wall
if (bottomleft > dv[1].z && bottomright > dv[2].z)
return;
if (bottomleft > dv[0].z || bottomright > dv[3].z)
{
// decal has to be clipped at the bottom
// let texture clamping handle all extreme cases
dv[0].v = (dv[1].z - bottomleft) / (dv[1].z - dv[0].z)*(dv[0].v - dv[1].v) + dv[1].v;
dv[3].v = (dv[2].z - bottomright) / (dv[2].z - dv[3].z)*(dv[3].v - dv[2].v) + dv[2].v;
dv[0].z = bottomleft;
dv[3].z = bottomright;
}
if (flipx)
{
float ur = tex->GetUR();
for (i = 0; i < 4; i++) dv[i].u = ur - dv[i].u;
}
if (flipy)
{
float vb = tex->GetVB();
for (i = 0; i < 4; i++) dv[i].v = vb - dv[i].v;
}
GLDecal *gldecal = di->AddDecal(type == RENDERWALL_MIRRORSURFACE);
gldecal->gltexture = tex;
gldecal->decal = decal;
if (decal->RenderFlags & RF_FULLBRIGHT)
{
gldecal->lightlevel = 255;
gldecal->rellight = 0;
}
else
{
gldecal->lightlevel = lightlevel;
gldecal->rellight = rellight + getExtraLight();
}
gldecal->Colormap = Colormap;
if (di->Level->flags3 & LEVEL3_NOCOLOREDSPRITELIGHTING)
{
gldecal->Colormap.Decolorize();
}
gldecal->alpha = decal->Alpha;
gldecal->zcenter = zpos - decalheight * 0.5f;
gldecal->frontsector = frontsector;
gldecal->Normal = normal;
gldecal->lightlist = lightlist;
memcpy(gldecal->dv, dv, sizeof(dv));
auto verts = screen->mVertexData->AllocVertices(4);
gldecal->vertindex = verts.second;
for (i = 0; i < 4; i++)
{
verts.first[i].Set(dv[i].x, dv[i].z, dv[i].y, dv[i].u, dv[i].v);
}
}
//==========================================================================
//
//
//
//==========================================================================
void GLWall::ProcessDecals(HWDrawInfo *di)
{
if (seg->sidedef != nullptr)
{
DBaseDecal *decal = seg->sidedef->AttachedDecals;
if (decal)
{
auto normal = glseg.Normal(); // calculate the normal only once per wall because it requires a square root.
while (decal)
{
ProcessDecal(di, decal, normal);
decal = decal->WallNext;
}
}
}
}

View file

@ -0,0 +1,679 @@
//
//---------------------------------------------------------------------------
//
// 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 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_drawinfo.cpp
** Basic scene draw info management class
**
*/
#include "a_sharedglobal.h"
#include "r_utility.h"
#include "r_sky.h"
#include "d_player.h"
#include "g_levellocals.h"
#include "hw_fakeflat.h"
#include "hw_portal.h"
#include "hw_renderstate.h"
#include "hw_drawinfo.h"
#include "po_man.h"
#include "r_data/models/models.h"
#include "hwrenderer/utility/hw_clock.h"
#include "hwrenderer/utility/hw_cvars.h"
#include "hwrenderer/data/hw_viewpointbuffer.h"
#include "hwrenderer/data/flatvertices.h"
#include "hwrenderer/dynlights/hw_lightbuffer.h"
#include "hwrenderer/utility/hw_vrmodes.h"
#include "hw_clipper.h"
EXTERN_CVAR(Float, r_visibility)
CVAR(Bool, gl_bandedswlight, false, CVAR_ARCHIVE)
CVAR(Bool, gl_sort_textures, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
sector_t * hw_FakeFlat(sector_t * sec, sector_t * dest, area_t in_area, bool back);
//==========================================================================
//
//
//
//==========================================================================
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->DrawScene = nullptr;
di->ClearBuffers();
di->Level = nullptr;
mList.Push(di);
}
//==========================================================================
//
// Sets up a new drawinfo struct
//
//==========================================================================
HWDrawInfo *HWDrawInfo::StartDrawInfo(FLevelLocals *lev, HWDrawInfo *parent, FRenderViewpoint &parentvp, HWViewpointUniforms *uniforms)
{
HWDrawInfo *di = di_list.GetNew();
if (parent) di->DrawScene = parent->DrawScene;
di->Level = lev;
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)
{
staticClipper.Clear();
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.SetDefaults();
mClipper->SetViewpoint(Viewpoint);
ClearBuffers();
for (int i = 0; i < GLDL_TYPES; i++) drawlists[i].Reset();
hudsprites.Clear();
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()
{
otherFloorPlanes.Clear();
otherCeilingPlanes.Clear();
floodFloorSegs.Clear();
floodCeilingSegs.Clear();
// clear all the lists that might not have been cleared already
MissingUpperTextures.Clear();
MissingLowerTextures.Clear();
MissingUpperSegs.Clear();
MissingLowerSegs.Clear();
SubsectorHacks.Clear();
//CeilingStacks.Clear();
//FloorStacks.Clear();
HandledSubsectors.Clear();
spriteindex = 0;
if (Level)
{
CurrentMapSections.Resize(Level->NumMapSections);
CurrentMapSections.Zero();
section_renderflags.Resize(Level->sections.allSections.Size());
ss_renderflags.Resize(Level->subsectors.Size());
no_renderflags.Resize(Level->subsectors.Size());
memset(&section_renderflags[0], 0, Level->sections.allSections.Size() * sizeof(section_renderflags[0]));
memset(&ss_renderflags[0], 0, Level->subsectors.Size() * sizeof(ss_renderflags[0]));
memset(&no_renderflags[0], 0, Level->nodes.Size() * sizeof(no_renderflags[0]));
}
Decals[0].Clear();
Decals[1].Clear();
mClipPortal = nullptr;
mCurrentPortal = nullptr;
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawInfo::UpdateCurrentMapSection()
{
const int mapsection = Level->PointInRenderSubsector(Viewpoint.Pos)->mapsection;
CurrentMapSections.Set(mapsection);
}
//-----------------------------------------------------------------------------
//
// Sets the area the camera is in
//
//-----------------------------------------------------------------------------
void HWDrawInfo::SetViewArea()
{
auto &vp = Viewpoint;
// The render_sector is better suited to represent the current position in GL
vp.sector = Level->PointInRenderSubsector(vp.Pos)->render_sector;
// Get the heightsec state from the render sector, not the current one!
if (vp.sector->GetHeightSec())
{
in_area = vp.Pos.Z <= vp.sector->heightsec->floorplane.ZatPoint(vp.Pos) ? area_below :
(vp.Pos.Z > vp.sector->heightsec->ceilingplane.ZatPoint(vp.Pos) &&
!(vp.sector->heightsec->MoreFlags&SECMF_FAKEFLOORONLY)) ? area_above : area_normal;
}
else
{
in_area = Level->HasHeightSecs ? area_default : area_normal; // depends on exposed lower sectors, if map contains heightsecs.
}
}
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
int HWDrawInfo::SetFullbrightFlags(player_t *player)
{
FullbrightFlags = 0;
// check for special colormaps
player_t * cplayer = player? player->camera->player : nullptr;
if (cplayer)
{
int cm = CM_DEFAULT;
if (cplayer->extralight == INT_MIN)
{
cm = CM_FIRSTSPECIALCOLORMAP + INVERSECOLORMAP;
Viewpoint.extralight = 0;
FullbrightFlags = Fullbright;
// This does never set stealth vision.
}
else if (cplayer->fixedcolormap != NOFIXEDCOLORMAP)
{
cm = CM_FIRSTSPECIALCOLORMAP + cplayer->fixedcolormap;
FullbrightFlags = Fullbright;
if (gl_enhanced_nv_stealth > 2) FullbrightFlags |= StealthVision;
}
else if (cplayer->fixedlightlevel != -1)
{
auto torchtype = PClass::FindActor(NAME_PowerTorch);
auto litetype = PClass::FindActor(NAME_PowerLightAmp);
for (AActor *in = cplayer->mo->Inventory; in; in = in->Inventory)
{
// Need special handling for light amplifiers
if (in->IsKindOf(torchtype))
{
FullbrightFlags = Fullbright;
if (gl_enhanced_nv_stealth > 1) FullbrightFlags |= StealthVision;
}
else if (in->IsKindOf(litetype))
{
FullbrightFlags = Fullbright;
if (gl_enhanced_nightvision) FullbrightFlags |= Nightvision;
if (gl_enhanced_nv_stealth > 0) FullbrightFlags |= StealthVision;
}
}
}
return cm;
}
else
{
return CM_DEFAULT;
}
}
//-----------------------------------------------------------------------------
//
// R_FrustumAngle
//
//-----------------------------------------------------------------------------
angle_t HWDrawInfo::FrustumAngle()
{
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(r_viewwindow.WidescreenRatio) / 90.0;
angle_t a1 = DAngle(floatangle).BAMs();
if (a1 >= ANGLE_180) return 0xffffffff;
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 ? -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);
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 HWViewpointUniforms::SetDefaults()
{
mProjectionMatrix.loadIdentity();
mViewMatrix.loadIdentity();
mNormalViewMatrix.loadIdentity();
mViewHeight = viewheight;
mGlobVis = (float)R_GetGlobVis(r_viewwindow, r_visibility) / 32.f;
mPalLightLevels = static_cast<int>(gl_bandedswlight) | (static_cast<int>(gl_fogmode) << 8) | (static_cast<int>(gl_lightmode) << 16);
mClipLine.X = -10000000.0f;
mShadowmapFilter = gl_shadowmap_filter;
}
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
GLDecal *HWDrawInfo::AddDecal(bool onmirror)
{
auto decal = (GLDecal*)RenderDataAllocator.Alloc(sizeof(GLDecal));
Decals[onmirror ? 1 : 0].Push(decal);
return decal;
}
//-----------------------------------------------------------------------------
//
// CreateScene
//
// creates the draw lists for the current scene
//
//-----------------------------------------------------------------------------
void HWDrawInfo::CreateScene()
{
const auto &vp = Viewpoint;
angle_t a1 = FrustumAngle();
mClipper->SafeAddClipRangeRealAngles(vp.Angles.Yaw.BAMs() + a1, vp.Angles.Yaw.BAMs() - a1);
// reset the portal manager
screen->mPortalState->StartFrame();
ProcessAll.Clock();
// clip the scene and fill the drawlists
screen->mVertexData->Map();
screen->mLights->Map();
RenderBSP(Level->HeadNode());
// And now the crappy hacks that have to be done to avoid rendering anomalies.
// These cannot be multithreaded when the time comes because all these depend
// on the global 'validcount' variable.
HandleMissingTextures(in_area); // Missing upper/lower textures
HandleHackedSubsectors(); // open sector hacks for deep water
PrepareUnhandledMissingTextures();
DispatchRenderHacks();
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);
screen->mLights->BindBase();
state.EnableFog(true);
state.SetRenderStyle(STYLE_Source);
if (gl_sort_textures)
{
drawlists[GLDL_PLAINWALLS].SortWalls();
drawlists[GLDL_PLAINFLATS].SortFlats();
drawlists[GLDL_MASKEDWALLS].SortWalls();
drawlists[GLDL_MASKEDFLATS].SortFlats();
drawlists[GLDL_MASKEDWALLSOFS].SortWalls();
}
// 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);
drawlists[GLDL_MASKEDWALLS].DrawWalls(this, state, false);
drawlists[GLDL_MASKEDFLATS].DrawFlats(this, state, false);
// Part 3: masked geometry with polygon offset. This list is empty most of the time so only waste time on it when in use.
if (drawlists[GLDL_MASKEDWALLSOFS].Size() > 0)
{
state.SetDepthBias(-1, -128);
drawlists[GLDL_MASKEDWALLSOFS].DrawWalls(this, state, false);
state.ClearDepthBias();
}
drawlists[GLDL_MODELS].Draw(this, state, false);
state.SetRenderStyle(STYLE_Translucent);
// Part 4: Draw decals (not a real pass)
state.SetDepthFunc(DF_LEqual);
DrawDecals(state, Decals[0]);
RenderAll.Unclock();
}
//-----------------------------------------------------------------------------
//
// RenderTranslucent
//
//-----------------------------------------------------------------------------
void HWDrawInfo::RenderTranslucent(FRenderState &state)
{
RenderAll.Clock();
// 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.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->Level, 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(sector_t * viewsector, FRenderState &state)
{
state.EnableFog(false);
// [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);
}
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);
}
void HWDrawInfo::DrawEndScene2D(sector_t * viewsector, FRenderState &state)
{
const bool renderHUDModel = IsHUDModelForPlayerAvailable(players[consoleplayer].camera->player);
auto vrmode = VRMode::GetVRMode(true);
HWViewpointUniforms vp = VPUniforms;
vp.mViewMatrix.loadIdentity();
vp.mProjectionMatrix = vrmode->GetHUDSpriteProjection();
screen->mViewpoints->SetViewpoint(state, &vp);
state.EnableDepthTest(false);
state.EnableMultisampling(false);
DrawPlayerSprites(false, state);
state.SetNoSoftLightLevel();
// 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);
}
//-----------------------------------------------------------------------------
//
// R_RenderView - renders one view - either the screen or a camera texture
//
//-----------------------------------------------------------------------------
void HWDrawInfo::ProcessScene(bool toscreen, const std::function<void(HWDrawInfo *,int)> &drawScene)
{
screen->mPortalState->BeginScene();
int mapsection = Level->PointInRenderSubsector(Viewpoint.Pos)->mapsection;
CurrentMapSections.Set(mapsection);
DrawScene = drawScene;
DrawScene(this, toscreen ? DM_MAINVIEW : DM_OFFSCREEN);
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawInfo::AddSubsectorToPortal(FSectorPortalGroup *ptg, subsector_t *sub)
{
auto portal = FindPortal(ptg);
if (!portal)
{
portal = new HWSectorStackPortal(screen->mPortalState, ptg);
Portals.Push(portal);
}
auto ptl = static_cast<HWSectorStackPortal*>(portal);
ptl->AddSubsector(sub);
}

View file

@ -0,0 +1,332 @@
#pragma once
#include <atomic>
#include <functional>
#include "vectors.h"
#include "r_defs.h"
#include "r_utility.h"
#include "hw_viewpointuniforms.h"
#include "v_video.h"
#include "hw_weapon.h"
#include "hw_drawlist.h"
enum EDrawMode
{
DM_MAINVIEW,
DM_OFFSCREEN,
DM_PORTAL,
DM_SKYPORTAL
};
struct FSectorPortalGroup;
struct FLinePortalSpan;
struct FFlatVertex;
class GLWall;
class GLFlat;
class GLSprite;
struct GLDecal;
class IShadowMap;
struct particle_t;
struct FDynLightData;
struct HUDSprite;
class Clipper;
class HWPortal;
class FFlatVertexBuffer;
class IRenderQueue;
class HWScenePortalBase;
class FRenderState;
//==========================================================================
//
// these are used to link faked planes due to missing textures to a sector
//
//==========================================================================
struct gl_subsectorrendernode
{
gl_subsectorrendernode * next;
subsector_t * sub;
int lightindex;
int vertexindex;
};
struct gl_floodrendernode
{
gl_floodrendernode * next;
seg_t *seg;
int vertexindex;
// This will use the light list of the originating sector.
};
enum area_t : int;
enum SectorRenderFlags
{
// This is used to merge several subsectors into a single draw item
SSRF_RENDERFLOOR = 1,
SSRF_RENDERCEILING = 2,
SSRF_RENDER3DPLANES = 4,
SSRF_RENDERALL = 7,
SSRF_PROCESSED = 8,
SSRF_SEEN = 16,
SSRF_PLANEHACK = 32,
SSRF_FLOODHACK = 64
};
enum EPortalClip
{
PClip_InFront,
PClip_Inside,
PClip_Behind,
};
enum DrawListType
{
GLDL_PLAINWALLS,
GLDL_PLAINFLATS,
GLDL_MASKEDWALLS,
GLDL_MASKEDFLATS,
GLDL_MASKEDWALLSOFS,
GLDL_MODELS,
GLDL_TRANSLUCENT,
GLDL_TRANSLUCENTBORDER,
GLDL_TYPES,
};
struct HWDrawInfo
{
struct wallseg
{
float x1, y1, z1, x2, y2, z2;
};
struct MissingTextureInfo
{
seg_t * seg;
subsector_t * sub;
float Planez;
float Planezfront;
};
struct MissingSegInfo
{
seg_t * seg;
int MTI_Index; // tells us which MissingTextureInfo represents this seg.
};
struct SubsectorHackInfo
{
subsector_t * sub;
uint8_t flags;
};
enum EFullbrightFlags
{
Fullbright = 1,
Nightvision = 2,
StealthVision = 4
};
bool isFullbrightScene() const { return !!(FullbrightFlags & Fullbright); }
bool isNightvision() const { return !!(FullbrightFlags & Nightvision); }
bool isStealthVision() const { return !!(FullbrightFlags & StealthVision); }
HWDrawList drawlists[GLDL_TYPES];
int vpIndex;
ELightMode lightmode;
FLevelLocals *Level;
HWDrawInfo * outer = nullptr;
int FullbrightFlags;
std::atomic<int> spriteindex;
HWPortal *mClipPortal;
HWPortal *mCurrentPortal;
//FRotator mAngles;
Clipper *mClipper;
FRenderViewpoint Viewpoint;
HWViewpointUniforms VPUniforms; // per-viewpoint uniform state
TArray<HWPortal *> Portals;
TArray<GLDecal *> Decals[2]; // the second slot is for mirrors which get rendered in a separate pass.
TArray<HUDSprite> hudsprites; // These may just be stored by value.
TArray<MissingTextureInfo> MissingUpperTextures;
TArray<MissingTextureInfo> MissingLowerTextures;
TArray<MissingSegInfo> MissingUpperSegs;
TArray<MissingSegInfo> MissingLowerSegs;
TArray<SubsectorHackInfo> SubsectorHacks;
TMap<int, gl_subsectorrendernode*> otherFloorPlanes;
TMap<int, gl_subsectorrendernode*> otherCeilingPlanes;
TMap<int, gl_floodrendernode*> floodFloorSegs;
TMap<int, gl_floodrendernode*> floodCeilingSegs;
//TArray<sector_t *> CeilingStacks;
//TArray<sector_t *> FloorStacks;
TArray<subsector_t *> HandledSubsectors;
TArray<uint8_t> section_renderflags;
TArray<uint8_t> ss_renderflags;
TArray<uint8_t> no_renderflags;
// This is needed by the BSP traverser.
BitArray CurrentMapSections; // this cannot be a single number, because a group of portals with the same displacement may link different sections.
area_t in_area;
fixed_t viewx, viewy; // since the nodes are still fixed point, keeping the view position also fixed point for node traversal is faster.
bool multithread;
std::function<void(HWDrawInfo *, int)> DrawScene = nullptr;
private:
// For ProcessLowerMiniseg
bool inview;
subsector_t * viewsubsector;
TArray<seg_t *> lowersegs;
subsector_t *currentsubsector; // used by the line processing code.
sector_t *currentsector;
void WorkerThread();
void UnclipSubsector(subsector_t *sub);
void AddLine(seg_t *seg, bool portalclip);
void PolySubsector(subsector_t * sub);
void RenderPolyBSPNode(void *node);
void AddPolyobjs(subsector_t *sub);
void AddLines(subsector_t * sub, sector_t * sector);
void AddSpecialPortalLines(subsector_t * sub, sector_t * sector, line_t *line);
public:
void RenderThings(subsector_t * sub, sector_t * sector);
void RenderParticles(subsector_t *sub, sector_t *front);
void DoSubsector(subsector_t * sub);
int SetupLightsForOtherPlane(subsector_t * sub, FDynLightData &lightdata, const secplane_t *plane);
int CreateOtherPlaneVertices(subsector_t *sub, const secplane_t *plane);
void DrawPSprite(HUDSprite *huds, FRenderState &state);
void SetColor(FRenderState &state, int sectorlightlevel, int rellight, bool fullbright, const FColormap &cm, float alpha, bool weapon = false);
void SetFog(FRenderState &state, int lightlevel, int rellight, bool fullbright, const FColormap *cmap, bool isadditive);
void SetShaderLight(FRenderState &state, float level, float olight);
int CalcLightLevel(int lightlevel, int rellight, bool weapon, int blendfactor);
PalEntry CalcLightColor(int light, PalEntry pe, int blendfactor);
float GetFogDensity(int lightlevel, PalEntry fogcolor, int sectorfogdensity, int blendfactor);
bool CheckFog(sector_t *frontsector, sector_t *backsector);
WeaponLighting GetWeaponLighting(sector_t *viewsector, const DVector3 &pos, int cm, area_t in_area, const DVector3 &playerpos);
public:
void SetCameraPos(const DVector3 &pos)
{
VPUniforms.mCameraPos = { (float)pos.X, (float)pos.Z, (float)pos.Y, 0.f };
}
void SetClipHeight(float h, float d)
{
VPUniforms.mClipHeight = h;
VPUniforms.mClipHeightDirection = d;
VPUniforms.mClipLine.X = -1000001.f;
}
void SetClipLine(line_t *line)
{
VPUniforms.mClipLine = { (float)line->v1->fX(), (float)line->v1->fY(), (float)line->Delta().X, (float)line->Delta().Y };
VPUniforms.mClipHeight = 0;
}
HWPortal * FindPortal(const void * src);
void RenderBSPNode(void *node);
void RenderBSP(void *node);
static HWDrawInfo *StartDrawInfo(FLevelLocals *lev, HWDrawInfo *parent, FRenderViewpoint &parentvp, HWViewpointUniforms *uniforms);
void StartScene(FRenderViewpoint &parentvp, HWViewpointUniforms *uniforms);
void ClearBuffers();
HWDrawInfo *EndDrawInfo();
void SetViewArea();
int SetFullbrightFlags(player_t *player);
void CreateScene();
void RenderScene(FRenderState &state);
void RenderTranslucent(FRenderState &state);
void RenderPortal(HWPortal *p, FRenderState &state, bool usestencil);
void EndDrawScene(sector_t * viewsector, FRenderState &state);
void DrawEndScene2D(sector_t * viewsector, FRenderState &state);
void Set3DViewport(FRenderState &state);
void ProcessScene(bool toscreen, const std::function<void(HWDrawInfo *, int)> &drawScene);
bool DoOneSectorUpper(subsector_t * subsec, float planez, area_t in_area);
bool DoOneSectorLower(subsector_t * subsec, float planez, area_t in_area);
bool DoFakeBridge(subsector_t * subsec, float planez, area_t in_area);
bool DoFakeCeilingBridge(subsector_t * subsec, float planez, area_t in_area);
bool CheckAnchorFloor(subsector_t * sub);
bool CollectSubsectorsFloor(subsector_t * sub, sector_t * anchor);
bool CheckAnchorCeiling(subsector_t * sub);
bool CollectSubsectorsCeiling(subsector_t * sub, sector_t * anchor);
void CollectSectorStacksCeiling(subsector_t * sub, sector_t * anchor, area_t in_area);
void CollectSectorStacksFloor(subsector_t * sub, sector_t * anchor, area_t in_area);
void DispatchRenderHacks();
void AddUpperMissingTexture(side_t * side, subsector_t *sub, float backheight);
void AddLowerMissingTexture(side_t * side, subsector_t *sub, float backheight);
void HandleMissingTextures(area_t in_area);
void PrepareUnhandledMissingTextures();
void PrepareUpperGap(seg_t * seg);
void PrepareLowerGap(seg_t * seg);
void CreateFloodPoly(wallseg * ws, FFlatVertex *vertices, float planez, sector_t * sec, bool ceiling);
void CreateFloodStencilPoly(wallseg * ws, FFlatVertex *vertices);
void AddHackedSubsector(subsector_t * sub);
void HandleHackedSubsectors();
void AddFloorStack(sector_t * sec);
void AddCeilingStack(sector_t * sec);
void ProcessSectorStacks(area_t in_area);
void ProcessActorsInPortal(FLinePortalSpan *glport, area_t in_area);
void AddOtherFloorPlane(int sector, gl_subsectorrendernode * node);
void AddOtherCeilingPlane(int sector, gl_subsectorrendernode * node);
void GetDynSpriteLight(AActor *self, float x, float y, float z, FLightNode *node, int portalgroup, float *out);
void GetDynSpriteLight(AActor *thing, particle_t *particle, float *out);
void PreparePlayerSprites(sector_t * viewsector, area_t in_area);
void PrepareTargeterSprites();
void UpdateCurrentMapSection();
void SetViewMatrix(const FRotator &angles, float vx, float vy, float vz, bool mirror, bool planemirror);
void SetupView(FRenderState &state, float vx, float vy, float vz, bool mirror, bool planemirror);
angle_t FrustumAngle();
void DrawDecals(FRenderState &state, TArray<GLDecal *> &decals);
void DrawPlayerSprites(bool hudModelStep, FRenderState &state);
void ProcessLowerMinisegs(TArray<seg_t *> &lowersegs);
void AddSubsectorToPortal(FSectorPortalGroup *portal, subsector_t *sub);
void AddWall(GLWall *w);
void AddMirrorSurface(GLWall *w);
void AddFlat(GLFlat *flat, bool fog);
void AddSprite(GLSprite *sprite, bool translucent);
GLDecal *AddDecal(bool onmirror);
bool isSoftwareLighting() const
{
return lightmode >= ELightMode::ZDoomSoftware;
}
bool isDarkLightMode() const
{
return !!((int)lightmode & (int)ELightMode::Doom);
}
void SetFallbackLightMode()
{
lightmode = ELightMode::Doom;
}
};

View file

@ -0,0 +1,948 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2002-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/
//
//--------------------------------------------------------------------------
//
/*
** hw_drawlist.cpp
** The main container type for draw items.
**
*/
#include "r_sky.h"
#include "r_utility.h"
#include "doomstat.h"
#include "actor.h"
#include "g_levellocals.h"
#include "hwrenderer/scene/hw_drawstructs.h"
#include "hwrenderer/scene/hw_drawlist.h"
#include "hwrenderer/data/flatvertices.h"
#include "hwrenderer/utility/hw_clock.h"
#include "hw_renderstate.h"
#include "hw_drawinfo.h"
#include "hw_fakeflat.h"
FMemArena RenderDataAllocator(1024*1024); // Use large blocks to reduce allocation time.
void ResetRenderDataAllocator()
{
RenderDataAllocator.FreeAll();
}
//==========================================================================
//
//
//
//==========================================================================
class StaticSortNodeArray : public TDeletingArray<SortNode*>
{
unsigned usecount;
public:
unsigned Size() { return usecount; }
void Clear() { usecount=0; }
void Release(int start) { usecount=start; }
SortNode * GetNew();
};
SortNode * StaticSortNodeArray::GetNew()
{
if (usecount==TArray<SortNode*>::Size())
{
Push(new SortNode);
}
return operator[](usecount++);
}
static StaticSortNodeArray SortNodes;
//==========================================================================
//
//
//
//==========================================================================
void HWDrawList::Reset()
{
if (sorted) SortNodes.Release(SortNodeStart);
sorted=NULL;
walls.Clear();
flats.Clear();
sprites.Clear();
drawitems.Clear();
}
//==========================================================================
//
//
//
//==========================================================================
inline void SortNode::UnlinkFromChain()
{
if (parent) parent->next=next;
if (next) next->parent=parent;
parent=next=NULL;
}
//==========================================================================
//
//
//
//==========================================================================
inline void SortNode::Link(SortNode * hook)
{
if (hook)
{
parent=hook->parent;
hook->parent=this;
}
next=hook;
if (parent) parent->next=this;
}
//==========================================================================
//
//
//
//==========================================================================
inline void SortNode::AddToEqual(SortNode *child)
{
child->UnlinkFromChain();
child->equal=equal;
equal=child;
}
//==========================================================================
//
//
//
//==========================================================================
inline void SortNode::AddToLeft(SortNode * child)
{
child->UnlinkFromChain();
child->Link(left);
left=child;
}
//==========================================================================
//
//
//
//==========================================================================
inline void SortNode::AddToRight(SortNode * child)
{
child->UnlinkFromChain();
child->Link(right);
right=child;
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawList::MakeSortList()
{
SortNode * p, * n, * c;
unsigned i;
SortNodeStart=SortNodes.Size();
p=NULL;
n=SortNodes.GetNew();
for(i=0;i<drawitems.Size();i++)
{
n->itemindex=(int)i;
n->left=n->equal=n->right=NULL;
n->parent=p;
p=n;
if (i!=drawitems.Size()-1)
{
c=SortNodes.GetNew();
n->next=c;
n=c;
}
else
{
n->next=NULL;
}
}
}
//==========================================================================
//
//
//
//==========================================================================
SortNode * HWDrawList::FindSortPlane(SortNode * head)
{
while (head->next && drawitems[head->itemindex].rendertype!=GLDIT_FLAT)
head=head->next;
if (drawitems[head->itemindex].rendertype==GLDIT_FLAT) return head;
return NULL;
}
//==========================================================================
//
//
//
//==========================================================================
SortNode * HWDrawList::FindSortWall(SortNode * head)
{
float farthest = -FLT_MAX;
float nearest = FLT_MAX;
SortNode * best = NULL;
SortNode * node = head;
float bestdist = FLT_MAX;
while (node)
{
GLDrawItem * it = &drawitems[node->itemindex];
if (it->rendertype == GLDIT_WALL)
{
float d = walls[it->index]->ViewDistance;
if (d > farthest) farthest = d;
if (d < nearest) nearest = d;
}
node = node->next;
}
if (farthest == INT_MIN) return NULL;
node = head;
farthest = (farthest + nearest) / 2;
while (node)
{
GLDrawItem * it = &drawitems[node->itemindex];
if (it->rendertype == GLDIT_WALL)
{
float di = fabsf(walls[it->index]->ViewDistance - farthest);
if (!best || di < bestdist)
{
best = node;
bestdist = di;
}
}
node = node->next;
}
return best;
}
//==========================================================================
//
// Note: sloped planes are a huge problem...
//
//==========================================================================
void HWDrawList::SortPlaneIntoPlane(SortNode * head,SortNode * sort)
{
GLFlat * fh= flats[drawitems[head->itemindex].index];
GLFlat * fs= flats[drawitems[sort->itemindex].index];
if (fh->z==fs->z)
head->AddToEqual(sort);
else if ( (fh->z<fs->z && fh->ceiling) || (fh->z>fs->z && !fh->ceiling))
head->AddToLeft(sort);
else
head->AddToRight(sort);
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawList::SortWallIntoPlane(SortNode * head, SortNode * sort)
{
GLFlat * fh = flats[drawitems[head->itemindex].index];
GLWall * ws = walls[drawitems[sort->itemindex].index];
bool ceiling = fh->z > SortZ;
if ((ws->ztop[0] > fh->z || ws->ztop[1] > fh->z) && (ws->zbottom[0] < fh->z || ws->zbottom[1] < fh->z))
{
// We have to split this wall!
GLWall *w = NewWall();
*w = *ws;
// Splitting is done in the shader with clip planes, if available
if (screen->hwcaps & RFL_NO_CLIP_PLANES)
{
ws->vertcount = 0; // invalidate current vertices.
float newtexv = ws->tcs[GLWall::UPLFT].v + ((ws->tcs[GLWall::LOLFT].v - ws->tcs[GLWall::UPLFT].v) / (ws->zbottom[0] - ws->ztop[0])) * (fh->z - ws->ztop[0]);
// I make the very big assumption here that translucent walls in sloped sectors
// and 3D-floors never coexist in the same level - If that were the case this
// code would become extremely more complicated.
if (!ceiling)
{
ws->ztop[1] = w->zbottom[1] = ws->ztop[0] = w->zbottom[0] = fh->z;
ws->tcs[GLWall::UPRGT].v = w->tcs[GLWall::LORGT].v = ws->tcs[GLWall::UPLFT].v = w->tcs[GLWall::LOLFT].v = newtexv;
}
else
{
w->ztop[1] = ws->zbottom[1] = w->ztop[0] = ws->zbottom[0] = fh->z;
w->tcs[GLWall::UPLFT].v = ws->tcs[GLWall::LOLFT].v = w->tcs[GLWall::UPRGT].v = ws->tcs[GLWall::LORGT].v = newtexv;
}
}
SortNode * sort2 = SortNodes.GetNew();
memset(sort2, 0, sizeof(SortNode));
sort2->itemindex = drawitems.Size() - 1;
head->AddToLeft(sort);
head->AddToRight(sort2);
}
else if ((ws->zbottom[0] < fh->z && !ceiling) || (ws->ztop[0] > fh->z && ceiling)) // completely on the left side
{
head->AddToLeft(sort);
}
else
{
head->AddToRight(sort);
}
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawList::SortSpriteIntoPlane(SortNode * head, SortNode * sort)
{
GLFlat * fh = flats[drawitems[head->itemindex].index];
GLSprite * ss = sprites[drawitems[sort->itemindex].index];
bool ceiling = fh->z > SortZ;
auto hiz = ss->z1 > ss->z2 ? ss->z1 : ss->z2;
auto loz = ss->z1 < ss->z2 ? ss->z1 : ss->z2;
if ((hiz > fh->z && loz < fh->z) || ss->modelframe)
{
// We have to split this sprite
GLSprite *s = NewSprite();
*s = *ss;
// Splitting is done in the shader with clip planes, if available.
// The fallback here only really works for non-y-billboarded sprites.
if (screen->hwcaps & RFL_NO_CLIP_PLANES)
{
float newtexv = ss->vt + ((ss->vb - ss->vt) / (ss->z2 - ss->z1))*(fh->z - ss->z1);
if (!ceiling)
{
ss->z1 = s->z2 = fh->z;
ss->vt = s->vb = newtexv;
}
else
{
s->z1 = ss->z2 = fh->z;
s->vt = ss->vb = newtexv;
}
}
SortNode * sort2 = SortNodes.GetNew();
memset(sort2, 0, sizeof(SortNode));
sort2->itemindex = drawitems.Size() - 1;
head->AddToLeft(sort);
head->AddToRight(sort2);
}
else if ((ss->z2<fh->z && !ceiling) || (ss->z1>fh->z && ceiling)) // completely on the left side
{
head->AddToLeft(sort);
}
else
{
head->AddToRight(sort);
}
}
//==========================================================================
//
//
//
//==========================================================================
#define MIN_EQ (0.0005f)
// Lines start-end and fdiv must intersect.
inline double CalcIntersectionVertex(GLWall *w1, GLWall * w2)
{
float ax = w1->glseg.x1, ay = w1->glseg.y1;
float bx = w1->glseg.x2, by = w1->glseg.y2;
float cx = w2->glseg.x1, cy = w2->glseg.y1;
float dx = w2->glseg.x2, dy = w2->glseg.y2;
return ((ay - cy)*(dx - cx) - (ax - cx)*(dy - cy)) / ((bx - ax)*(dy - cy) - (by - ay)*(dx - cx));
}
void HWDrawList::SortWallIntoWall(HWDrawInfo *di, SortNode * head,SortNode * sort)
{
GLWall * wh= walls[drawitems[head->itemindex].index];
GLWall * ws= walls[drawitems[sort->itemindex].index];
float v1=wh->PointOnSide(ws->glseg.x1,ws->glseg.y1);
float v2=wh->PointOnSide(ws->glseg.x2,ws->glseg.y2);
if (fabs(v1)<MIN_EQ && fabs(v2)<MIN_EQ)
{
if (ws->type==RENDERWALL_FOGBOUNDARY && wh->type!=RENDERWALL_FOGBOUNDARY)
{
head->AddToRight(sort);
}
else if (ws->type!=RENDERWALL_FOGBOUNDARY && wh->type==RENDERWALL_FOGBOUNDARY)
{
head->AddToLeft(sort);
}
else
{
head->AddToEqual(sort);
}
}
else if (v1<MIN_EQ && v2<MIN_EQ)
{
head->AddToLeft(sort);
}
else if (v1>-MIN_EQ && v2>-MIN_EQ)
{
head->AddToRight(sort);
}
else
{
double r = CalcIntersectionVertex(ws, wh);
float ix=(float)(ws->glseg.x1+r*(ws->glseg.x2-ws->glseg.x1));
float iy=(float)(ws->glseg.y1+r*(ws->glseg.y2-ws->glseg.y1));
float iu=(float)(ws->tcs[GLWall::UPLFT].u + r * (ws->tcs[GLWall::UPRGT].u - ws->tcs[GLWall::UPLFT].u));
float izt=(float)(ws->ztop[0]+r*(ws->ztop[1]-ws->ztop[0]));
float izb=(float)(ws->zbottom[0]+r*(ws->zbottom[1]-ws->zbottom[0]));
ws->vertcount = 0; // invalidate current vertices.
GLWall *w= NewWall();
*w = *ws;
w->glseg.x1=ws->glseg.x2=ix;
w->glseg.y1=ws->glseg.y2=iy;
w->glseg.fracleft = ws->glseg.fracright = ws->glseg.fracleft + r*(ws->glseg.fracright - ws->glseg.fracleft);
w->ztop[0]=ws->ztop[1]=izt;
w->zbottom[0]=ws->zbottom[1]=izb;
w->tcs[GLWall::LOLFT].u = w->tcs[GLWall::UPLFT].u = ws->tcs[GLWall::LORGT].u = ws->tcs[GLWall::UPRGT].u = iu;
ws->MakeVertices(di, false);
w->MakeVertices(di, false);
SortNode * sort2=SortNodes.GetNew();
memset(sort2,0,sizeof(SortNode));
sort2->itemindex=drawitems.Size()-1;
if (v1>0)
{
head->AddToLeft(sort2);
head->AddToRight(sort);
}
else
{
head->AddToLeft(sort);
head->AddToRight(sort2);
}
}
}
//==========================================================================
//
//
//
//==========================================================================
EXTERN_CVAR(Int, gl_billboard_mode)
EXTERN_CVAR(Bool, gl_billboard_faces_camera)
EXTERN_CVAR(Bool, gl_billboard_particles)
inline double CalcIntersectionVertex(GLSprite *s, GLWall * w2)
{
float ax = s->x1, ay = s->y1;
float bx = s->x2, by = s->y2;
float cx = w2->glseg.x1, cy = w2->glseg.y1;
float dx = w2->glseg.x2, dy = w2->glseg.y2;
return ((ay - cy)*(dx - cx) - (ax - cx)*(dy - cy)) / ((bx - ax)*(dy - cy) - (by - ay)*(dx - cx));
}
void HWDrawList::SortSpriteIntoWall(HWDrawInfo *di, SortNode * head,SortNode * sort)
{
GLWall *wh= walls[drawitems[head->itemindex].index];
GLSprite * ss= sprites[drawitems[sort->itemindex].index];
float v1 = wh->PointOnSide(ss->x1, ss->y1);
float v2 = wh->PointOnSide(ss->x2, ss->y2);
if (fabs(v1)<MIN_EQ && fabs(v2)<MIN_EQ)
{
if (wh->type==RENDERWALL_FOGBOUNDARY)
{
head->AddToLeft(sort);
}
else
{
head->AddToEqual(sort);
}
}
else if (v1<MIN_EQ && v2<MIN_EQ)
{
head->AddToLeft(sort);
}
else if (v1>-MIN_EQ && v2>-MIN_EQ)
{
head->AddToRight(sort);
}
else
{
const bool drawWithXYBillboard = ((ss->particle && gl_billboard_particles) || (!(ss->actor && ss->actor->renderflags & RF_FORCEYBILLBOARD)
&& (gl_billboard_mode == 1 || (ss->actor && ss->actor->renderflags & RF_FORCEXYBILLBOARD))));
const bool drawBillboardFacingCamera = gl_billboard_faces_camera;
// [Nash] has +ROLLSPRITE
const bool rotated = (ss->actor != nullptr && ss->actor->renderflags & (RF_ROLLSPRITE | RF_WALLSPRITE | RF_FLATSPRITE));
// cannot sort them at the moment. This requires more complex splitting.
if (drawWithXYBillboard || drawBillboardFacingCamera || rotated)
{
float v1 = wh->PointOnSide(ss->x, ss->y);
if (v1 < 0)
{
head->AddToLeft(sort);
}
else
{
head->AddToRight(sort);
}
return;
}
double r=CalcIntersectionVertex(ss, wh);
float ix=(float)(ss->x1 + r * (ss->x2-ss->x1));
float iy=(float)(ss->y1 + r * (ss->y2-ss->y1));
float iu=(float)(ss->ul + r * (ss->ur-ss->ul));
GLSprite *s = NewSprite();
*s = *ss;
s->x1=ss->x2=ix;
s->y1=ss->y2=iy;
s->ul=ss->ur=iu;
SortNode * sort2=SortNodes.GetNew();
memset(sort2,0,sizeof(SortNode));
sort2->itemindex=drawitems.Size()-1;
if (v1>0)
{
head->AddToLeft(sort2);
head->AddToRight(sort);
}
else
{
head->AddToLeft(sort);
head->AddToRight(sort2);
}
if (screen->BuffersArePersistent())
{
s->vertexindex = ss->vertexindex = -1;
}
else
{
s->CreateVertices(di);
ss->CreateVertices(di);
}
}
}
//==========================================================================
//
//
//
//==========================================================================
inline int HWDrawList::CompareSprites(SortNode * a,SortNode * b)
{
GLSprite * s1= sprites[drawitems[a->itemindex].index];
GLSprite * s2= sprites[drawitems[b->itemindex].index];
int res = s1->depth - s2->depth;
if (res != 0) return -res;
else return reverseSort? s2->index-s1->index : s1->index-s2->index;
}
//==========================================================================
//
//
//
//==========================================================================
SortNode * HWDrawList::SortSpriteList(SortNode * head)
{
SortNode * n;
int count;
unsigned i;
static TArray<SortNode*> sortspritelist;
SortNode * parent=head->parent;
sortspritelist.Clear();
for(count=0,n=head;n;n=n->next) sortspritelist.Push(n);
std::stable_sort(sortspritelist.begin(), sortspritelist.end(), [=](SortNode *a, SortNode *b)
{
return CompareSprites(a, b) < 0;
});
for(i=0;i<sortspritelist.Size();i++)
{
sortspritelist[i]->next=NULL;
if (parent) parent->equal=sortspritelist[i];
parent=sortspritelist[i];
}
return sortspritelist[0];
}
//==========================================================================
//
//
//
//==========================================================================
SortNode * HWDrawList::DoSort(HWDrawInfo *di, SortNode * head)
{
SortNode * node, * sn, * next;
sn=FindSortPlane(head);
if (sn)
{
if (sn==head) head=head->next;
sn->UnlinkFromChain();
node=head;
head=sn;
while (node)
{
next=node->next;
switch(drawitems[node->itemindex].rendertype)
{
case GLDIT_FLAT:
SortPlaneIntoPlane(head,node);
break;
case GLDIT_WALL:
SortWallIntoPlane(head,node);
break;
case GLDIT_SPRITE:
SortSpriteIntoPlane(head,node);
break;
}
node=next;
}
}
else
{
sn=FindSortWall(head);
if (sn)
{
if (sn==head) head=head->next;
sn->UnlinkFromChain();
node=head;
head=sn;
while (node)
{
next=node->next;
switch(drawitems[node->itemindex].rendertype)
{
case GLDIT_WALL:
SortWallIntoWall(di, head,node);
break;
case GLDIT_SPRITE:
SortSpriteIntoWall(di, head, node);
break;
case GLDIT_FLAT: break;
}
node=next;
}
}
else
{
return SortSpriteList(head);
}
}
if (head->left) head->left=DoSort(di, head->left);
if (head->right) head->right=DoSort(di, head->right);
return sn;
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawList::Sort(HWDrawInfo *di)
{
reverseSort = !!(di->Level->i_compatflags & COMPATF_SPRITESORT);
SortZ = di->Viewpoint.Pos.Z;
MakeSortList();
sorted = DoSort(di, SortNodes[SortNodeStart]);
}
//==========================================================================
//
// Sorting the drawitems first by texture and then by light level
//
//==========================================================================
void HWDrawList::SortWalls()
{
if (drawitems.Size() > 1)
{
std::sort(drawitems.begin(), drawitems.end(), [=](const GLDrawItem &a, const GLDrawItem &b) -> bool
{
GLWall * w1 = walls[a.index];
GLWall * w2 = walls[b.index];
if (w1->gltexture != w2->gltexture) return w1->gltexture < w2->gltexture;
return (w1->flags & 3) < (w2->flags & 3);
});
}
}
void HWDrawList::SortFlats()
{
if (drawitems.Size() > 1)
{
std::sort(drawitems.begin(), drawitems.end(), [=](const GLDrawItem &a, const GLDrawItem &b)
{
GLFlat * w1 = flats[a.index];
GLFlat* w2 = flats[b.index];
return w1->gltexture < w2->gltexture;
});
}
}
//==========================================================================
//
//
//
//==========================================================================
GLWall *HWDrawList::NewWall()
{
auto wall = (GLWall*)RenderDataAllocator.Alloc(sizeof(GLWall));
drawitems.Push(GLDrawItem(GLDIT_WALL, walls.Push(wall)));
return wall;
}
//==========================================================================
//
//
//
//==========================================================================
GLFlat *HWDrawList::NewFlat()
{
auto flat = (GLFlat*)RenderDataAllocator.Alloc(sizeof(GLFlat));
drawitems.Push(GLDrawItem(GLDIT_FLAT,flats.Push(flat)));
return flat;
}
//==========================================================================
//
//
//
//==========================================================================
GLSprite *HWDrawList::NewSprite()
{
auto sprite = (GLSprite*)RenderDataAllocator.Alloc(sizeof(GLSprite));
drawitems.Push(GLDrawItem(GLDIT_SPRITE, sprites.Push(sprite)));
return sprite;
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawList::DoDraw(HWDrawInfo *di, FRenderState &state, bool translucent, int i)
{
switch(drawitems[i].rendertype)
{
case GLDIT_FLAT:
{
GLFlat * f= flats[drawitems[i].index];
RenderFlat.Clock();
f->DrawFlat(di, state, translucent);
RenderFlat.Unclock();
}
break;
case GLDIT_WALL:
{
GLWall * w= walls[drawitems[i].index];
RenderWall.Clock();
w->DrawWall(di, state, translucent);
RenderWall.Unclock();
}
break;
case GLDIT_SPRITE:
{
GLSprite * s= sprites[drawitems[i].index];
RenderSprite.Clock();
s->DrawSprite(di, state, translucent);
RenderSprite.Unclock();
}
break;
}
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawList::Draw(HWDrawInfo *di, FRenderState &state, bool translucent)
{
for (unsigned i = 0; i < drawitems.Size(); i++)
{
DoDraw(di, state, translucent, i);
}
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawList::DrawWalls(HWDrawInfo *di, FRenderState &state, bool translucent)
{
RenderWall.Clock();
for (auto &item : drawitems)
{
walls[item.index]->DrawWall(di, state, translucent);
}
RenderWall.Unclock();
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawList::DrawFlats(HWDrawInfo *di, FRenderState &state, bool translucent)
{
RenderFlat.Clock();
for (unsigned i = 0; i<drawitems.Size(); i++)
{
flats[drawitems[i].index]->DrawFlat(di, state, translucent);
}
RenderFlat.Unclock();
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawList::DrawSorted(HWDrawInfo *di, FRenderState &state, SortNode * head)
{
float clipsplit[2];
int relation = 0;
float z = 0.f;
state.GetClipSplit(clipsplit);
if (drawitems[head->itemindex].rendertype == GLDIT_FLAT)
{
z = flats[drawitems[head->itemindex].index]->z;
relation = z > di->Viewpoint.Pos.Z ? 1 : -1;
}
// left is further away, i.e. for stuff above viewz its z coordinate higher, for stuff below viewz its z coordinate is lower
if (head->left)
{
if (relation == -1)
{
state.SetClipSplit(clipsplit[0], z); // render below: set flat as top clip plane
}
else if (relation == 1)
{
state.SetClipSplit(z, clipsplit[1]); // render above: set flat as bottom clip plane
}
DrawSorted(di, state, head->left);
state.SetClipSplit(clipsplit);
}
DoDraw(di, state, true, head->itemindex);
if (head->equal)
{
SortNode * ehead = head->equal;
while (ehead)
{
DoDraw(di, state, true, ehead->itemindex);
ehead = ehead->equal;
}
}
// right is closer, i.e. for stuff above viewz its z coordinate is lower, for stuff below viewz its z coordinate is higher
if (head->right)
{
if (relation == 1)
{
state.SetClipSplit(clipsplit[0], z); // render below: set flat as top clip plane
}
else if (relation == -1)
{
state.SetClipSplit(z, clipsplit[1]); // render above: set flat as bottom clip plane
}
DrawSorted(di, state, head->right);
state.SetClipSplit(clipsplit);
}
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawList::DrawSorted(HWDrawInfo *di, FRenderState &state)
{
if (drawitems.Size() == 0) return;
if (!sorted)
{
screen->mVertexData->Map();
Sort(di);
screen->mVertexData->Unmap();
}
state.ClearClipSplit();
state.EnableClipDistance(1, true);
state.EnableClipDistance(2, true);
DrawSorted(di, state, sorted);
state.EnableClipDistance(1, false);
state.EnableClipDistance(2, false);
state.ClearClipSplit();
}

View file

@ -0,0 +1,122 @@
#pragma once
#include "memarena.h"
extern FMemArena RenderDataAllocator;
void ResetRenderDataAllocator();
struct HWDrawInfo;
class GLWall;
class GLFlat;
class GLSprite;
class FRenderState;
//==========================================================================
//
// Intermediate struct to link one draw item into a draw list
//
// unfortunately this struct must not contain pointers because
// the arrays may be reallocated!
//
//==========================================================================
enum GLDrawItemType
{
GLDIT_WALL,
GLDIT_FLAT,
GLDIT_SPRITE,
};
struct GLDrawItem
{
GLDrawItemType rendertype;
int index;
GLDrawItem(GLDrawItemType _rendertype,int _index) : rendertype(_rendertype),index(_index) {}
};
struct SortNode
{
int itemindex;
SortNode * parent;
SortNode * next; // unsorted successor
SortNode * left; // left side of this node
SortNode * equal; // equal to this node
SortNode * right; // right side of this node
void UnlinkFromChain();
void Link(SortNode * hook);
void AddToEqual(SortNode * newnode);
void AddToLeft (SortNode * newnode);
void AddToRight(SortNode * newnode);
};
//==========================================================================
//
// One draw list. This contains all info for one type of rendering data
//
//==========================================================================
struct HWDrawList
{
//private:
TArray<GLWall*> walls;
TArray<GLFlat*> flats;
TArray<GLSprite*> sprites;
TArray<GLDrawItem> drawitems;
int SortNodeStart;
float SortZ;
SortNode * sorted;
bool reverseSort;
public:
HWDrawList()
{
next=NULL;
SortNodeStart=-1;
sorted=NULL;
}
~HWDrawList()
{
Reset();
}
unsigned int Size()
{
return drawitems.Size();
}
GLWall *NewWall();
GLFlat *NewFlat();
GLSprite *NewSprite();
void Reset();
void SortWalls();
void SortFlats();
void MakeSortList();
SortNode * FindSortPlane(SortNode * head);
SortNode * FindSortWall(SortNode * head);
void SortPlaneIntoPlane(SortNode * head,SortNode * sort);
void SortWallIntoPlane(SortNode * head,SortNode * sort);
void SortSpriteIntoPlane(SortNode * head,SortNode * sort);
void SortWallIntoWall(HWDrawInfo *di, SortNode * head,SortNode * sort);
void SortSpriteIntoWall(HWDrawInfo *di, SortNode * head,SortNode * sort);
int CompareSprites(SortNode * a,SortNode * b);
SortNode * SortSpriteList(SortNode * head);
SortNode * DoSort(HWDrawInfo *di, SortNode * head);
void Sort(HWDrawInfo *di);
void DoDraw(HWDrawInfo *di, FRenderState &state, bool translucent, int i);
void Draw(HWDrawInfo *di, FRenderState &state, bool translucent);
void DrawWalls(HWDrawInfo *di, FRenderState &state, bool translucent);
void DrawFlats(HWDrawInfo *di, FRenderState &state, bool translucent);
void DrawSorted(HWDrawInfo *di, FRenderState &state, SortNode * head);
void DrawSorted(HWDrawInfo *di, FRenderState &state);
HWDrawList * next;
} ;

View file

@ -0,0 +1,150 @@
//
//---------------------------------------------------------------------------
//
// 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 "hwrenderer/dynlights/hw_dynlightdata.h"
#include "hwrenderer/utility/hw_cvars.h"
#include "hwrenderer/dynlights/hw_lightbuffer.h"
#include "hwrenderer/scene/hw_drawstructs.h"
#include "hwrenderer/scene/hw_drawinfo.h"
#include "hwrenderer/textures/hw_material.h"
EXTERN_CVAR(Bool, gl_seamless)
//==========================================================================
//
//
//
//==========================================================================
void HWDrawInfo::AddWall(GLWall *wall)
{
if (wall->flags & GLWall::GLWF_TRANSLUCENT)
{
auto newwall = drawlists[GLDL_TRANSLUCENT].NewWall();
*newwall = *wall;
}
else
{
bool masked = GLWall::passflag[wall->type] == 1 ? false : (wall->gltexture && wall->gltexture->isMasked());
int list;
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;
}
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawInfo::AddMirrorSurface(GLWall *w)
{
w->type = RENDERWALL_MIRRORSURFACE;
auto newwall = drawlists[GLDL_TRANSLUCENTBORDER].NewWall();
*newwall = *w;
// Invalidate vertices to allow setting of texture coordinates
newwall->vertcount = 0;
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);
}
//==========================================================================
//
// FDrawInfo::AddFlat
//
// Checks texture, lighting and translucency settings and puts this
// plane in the appropriate render list.
//
//==========================================================================
void HWDrawInfo::AddFlat(GLFlat *flat, bool fog)
{
int list;
if (flat->renderstyle != STYLE_Translucent || flat->alpha < 1.f - FLT_EPSILON || fog || flat->gltexture == nullptr)
{
// translucent 3D floors go into the regular translucent list, translucent portals go into the translucent border list.
list = (flat->renderflags&SSRF_RENDER3DPLANES) ? GLDL_TRANSLUCENT : GLDL_TRANSLUCENTBORDER;
}
else if (flat->gltexture->tex->GetTranslucency())
{
if (flat->stack)
{
list = GLDL_TRANSLUCENTBORDER;
}
else if ((flat->renderflags&SSRF_RENDER3DPLANES) && !flat->plane.plane.isSlope())
{
list = GLDL_TRANSLUCENT;
}
else
{
list = GLDL_PLAINFLATS;
}
}
else //if (flat->hacktype != SSRF_FLOODHACK) // The flood hack may later need different treatment but with the current setup can go into the existing render list.
{
bool masked = flat->gltexture->isMasked() && ((flat->renderflags&SSRF_RENDER3DPLANES) || flat->stack);
list = masked ? GLDL_MASKEDFLATS : GLDL_PLAINFLATS;
}
auto newflat = drawlists[list].NewFlat();
*newflat = *flat;
}
//==========================================================================
//
//
//
//==========================================================================
void HWDrawInfo::AddSprite(GLSprite *sprite, bool translucent)
{
int list;
// [BB] Allow models to be drawn in the GLDL_TRANSLUCENT pass.
if (translucent || sprite->actor == nullptr || (!sprite->modelframe && (sprite->actor->renderflags & RF_SPRITETYPEMASK) != RF_WALLSPRITE))
{
list = GLDL_TRANSLUCENT;
}
else
{
list = GLDL_MODELS;
}
auto newsprt = drawlists[list].NewSprite();
*newsprt = *sprite;
}

View file

@ -0,0 +1,431 @@
#pragma once
//==========================================================================
//
// One wall segment in the draw list
//
//==========================================================================
#include "r_defs.h"
#include "r_data/renderstyle.h"
#include "textures/textures.h"
#include "r_data/colormaps.h"
#ifdef _MSC_VER
#pragma warning(disable:4244)
#endif
struct GLHorizonInfo;
struct GLSkyInfo;
struct F3DFloor;
class FMaterial;
struct FTexCoordInfo;
struct FSectorPortalGroup;
struct FFlatVertex;
struct FLinePortalSpan;
struct FDynLightData;
class VSMatrix;
struct FSpriteModelFrame;
struct particle_t;
class FRenderState;
struct GLDecal;
struct FSection;
enum area_t : int;
enum HWRenderStyle
{
STYLEHW_Normal, // default
STYLEHW_Solid, // drawn solid (needs special treatment for sprites)
STYLEHW_NoAlphaTest, // disable alpha test
};
enum WallTypes
{
RENDERWALL_NONE,
RENDERWALL_TOP,
RENDERWALL_M1S,
RENDERWALL_M2S,
RENDERWALL_BOTTOM,
RENDERWALL_FOGBOUNDARY,
RENDERWALL_MIRRORSURFACE,
RENDERWALL_M2SNF,
RENDERWALL_COLOR,
RENDERWALL_FFBLOCK,
// Insert new types at the end!
};
enum PortalTypes
{
PORTALTYPE_SKY,
PORTALTYPE_HORIZON,
PORTALTYPE_SKYBOX,
PORTALTYPE_SECTORSTACK,
PORTALTYPE_PLANEMIRROR,
PORTALTYPE_MIRROR,
PORTALTYPE_LINETOLINE,
};
//==========================================================================
//
// One sector plane, still in fixed point
//
//==========================================================================
struct GLSectorPlane
{
FTextureID texture;
secplane_t plane;
float Texheight;
float Angle;
FVector2 Offs;
FVector2 Scale;
void GetFromSector(sector_t * sec, int ceiling)
{
Offs.X = (float)sec->GetXOffset(ceiling);
Offs.Y = (float)sec->GetYOffset(ceiling);
Scale.X = (float)sec->GetXScale(ceiling);
Scale.Y = (float)sec->GetYScale(ceiling);
Angle = (float)sec->GetAngle(ceiling).Degrees;
texture = sec->GetTexture(ceiling);
plane = sec->GetSecPlane(ceiling);
Texheight = (float)((ceiling == sector_t::ceiling)? plane.fD() : -plane.fD());
}
};
struct GLSeg
{
float x1,x2;
float y1,y2;
float fracleft, fracright; // fractional offset of the 2 vertices on the linedef
FVector3 Normal() const
{
// we do not use the vector math inlines here because they are not optimized for speed but accuracy in the playsim and this is called quite frequently.
float x = y2 - y1;
float y = x1 - x2;
float ilength = 1.f / sqrtf(x*x + y*y);
return FVector3(x * ilength, 0, y * ilength);
}
};
struct texcoord
{
float u,v;
};
struct HWDrawInfo;
class GLWall
{
public:
static const char passflag[];
enum
{
GLWF_CLAMPX=1,
GLWF_CLAMPY=2,
GLWF_SKYHACK=4,
GLWF_GLOW=8, // illuminated by glowing flats
GLWF_NOSPLITUPPER=16,
GLWF_NOSPLITLOWER=32,
GLWF_NOSPLIT=64,
GLWF_TRANSLUCENT = 128
};
enum
{
RWF_BLANK = 0,
RWF_TEXTURED = 1, // actually not being used anymore because with buffers it's even less efficient not writing the texture coordinates - but leave it here
RWF_NOSPLIT = 4,
RWF_NORENDER = 8,
};
enum
{
LOLFT,
UPLFT,
UPRGT,
LORGT,
};
friend struct HWDrawList;
friend class HWPortal;
vertex_t * vertexes[2]; // required for polygon splitting
FMaterial *gltexture;
TArray<lightlist_t> *lightlist;
GLSeg glseg;
float ztop[2],zbottom[2];
texcoord tcs[4];
float alpha;
FColormap Colormap;
ERenderStyle RenderStyle;
float ViewDistance;
int lightlevel;
uint8_t type;
uint8_t flags;
short rellight;
float topglowcolor[4];
float bottomglowcolor[4];
int dynlightindex;
union
{
// it's either one of them but never more!
FSectorPortal *secportal; // sector portal (formerly skybox)
GLSkyInfo * sky; // for normal sky
GLHorizonInfo * horizon; // for horizon information
FSectorPortalGroup * portal; // stacked sector portals
secplane_t * planemirror; // for plane mirrors
FLinePortalSpan *lineportal; // line-to-line portals
};
// these are not the same as ytop and ybottom!!!
float zceil[2];
float zfloor[2];
unsigned int vertindex;
unsigned int vertcount;
public:
seg_t * seg; // this gives the easiest access to all other structs involved
subsector_t * sub; // For polyobjects
sector_t *frontsector, *backsector;
//private:
void PutWall(HWDrawInfo *di, bool translucent);
void PutPortal(HWDrawInfo *di, int ptype, int plane);
void CheckTexturePosition(FTexCoordInfo *tci);
void Put3DWall(HWDrawInfo *di, lightlist_t * lightlist, bool translucent);
bool SplitWallComplex(HWDrawInfo *di, sector_t * frontsector, bool translucent, float& maplightbottomleft, float& maplightbottomright);
void SplitWall(HWDrawInfo *di, sector_t * frontsector, bool translucent);
void SetupLights(HWDrawInfo *di, FDynLightData &lightdata);
void MakeVertices(HWDrawInfo *di, bool nosplit);
void SkyPlane(HWDrawInfo *di, sector_t *sector, int plane, bool allowmirror);
void SkyLine(HWDrawInfo *di, sector_t *sec, line_t *line);
void SkyNormal(HWDrawInfo *di, sector_t * fs,vertex_t * v1,vertex_t * v2);
void SkyTop(HWDrawInfo *di, seg_t * seg,sector_t * fs,sector_t * bs,vertex_t * v1,vertex_t * v2);
void SkyBottom(HWDrawInfo *di, seg_t * seg,sector_t * fs,sector_t * bs,vertex_t * v1,vertex_t * v2);
bool DoHorizon(HWDrawInfo *di, seg_t * seg,sector_t * fs, vertex_t * v1,vertex_t * v2);
bool SetWallCoordinates(seg_t * seg, FTexCoordInfo *tci, float ceilingrefheight,
float topleft, float topright, float bottomleft, float bottomright, float t_ofs);
void DoTexture(HWDrawInfo *di, int type,seg_t * seg,int peg,
float ceilingrefheight, float floorrefheight,
float CeilingHeightstart,float CeilingHeightend,
float FloorHeightstart,float FloorHeightend,
float v_offset);
void DoMidTexture(HWDrawInfo *di, seg_t * seg, bool drawfogboundary,
sector_t * front, sector_t * back,
sector_t * realfront, sector_t * realback,
float fch1, float fch2, float ffh1, float ffh2,
float bch1, float bch2, float bfh1, float bfh2);
void GetPlanePos(F3DFloor::planeref * planeref, float & left, float & right);
void BuildFFBlock(HWDrawInfo *di, seg_t * seg, F3DFloor * rover,
float ff_topleft, float ff_topright,
float ff_bottomleft, float ff_bottomright);
void InverseFloors(HWDrawInfo *di, seg_t * seg, sector_t * frontsector,
float topleft, float topright,
float bottomleft, float bottomright);
void ClipFFloors(HWDrawInfo *di, seg_t * seg, F3DFloor * ffloor, sector_t * frontsector,
float topleft, float topright,
float bottomleft, float bottomright);
void DoFFloorBlocks(HWDrawInfo *di, seg_t * seg, sector_t * frontsector, sector_t * backsector,
float fch1, float fch2, float ffh1, float ffh2,
float bch1, float bch2, float bfh1, float bfh2);
void ProcessDecal(HWDrawInfo *di, DBaseDecal *decal, const FVector3 &normal);
void ProcessDecals(HWDrawInfo *di);
int CreateVertices(FFlatVertex *&ptr, bool nosplit);
void SplitLeftEdge (FFlatVertex *&ptr);
void SplitRightEdge(FFlatVertex *&ptr);
void SplitUpperEdge(FFlatVertex *&ptr);
void SplitLowerEdge(FFlatVertex *&ptr);
void CountLeftEdge (unsigned &ptr);
void CountRightEdge(unsigned &ptr);
int CountVertices();
void RenderWall(HWDrawInfo *di, FRenderState &state, int textured);
void RenderFogBoundary(HWDrawInfo *di, FRenderState &state);
void RenderMirrorSurface(HWDrawInfo *di, FRenderState &state);
void RenderTexturedWall(HWDrawInfo *di, FRenderState &state, int rflags);
void RenderTranslucentWall(HWDrawInfo *di, FRenderState &state);
void DrawDecalsForMirror(HWDrawInfo *di, FRenderState &state, TArray<GLDecal *> &decals);
public:
void Process(HWDrawInfo *di, seg_t *seg, sector_t *frontsector, sector_t *backsector);
void ProcessLowerMiniseg(HWDrawInfo *di, seg_t *seg, sector_t *frontsector, sector_t *backsector);
float PointOnSide(float x,float y)
{
return -((y-glseg.y1)*(glseg.x2-glseg.x1)-(x-glseg.x1)*(glseg.y2-glseg.y1));
}
void DrawWall(HWDrawInfo *di, FRenderState &state, bool translucent);
};
//==========================================================================
//
// One flat plane in the draw list
//
//==========================================================================
class GLFlat
{
public:
sector_t * sector;
FSection *section;
float z; // the z position of the flat (only valid for non-sloped planes)
FMaterial *gltexture;
FColormap Colormap; // light and fog
PalEntry FlatColor;
PalEntry AddColor;
ERenderStyle renderstyle;
float alpha;
GLSectorPlane plane;
int lightlevel;
bool stack;
bool ceiling;
uint8_t renderflags;
uint8_t hacktype;
int iboindex;
//int vboheight;
int dynlightindex;
void CreateSkyboxVertices(FFlatVertex *buffer);
void SetupLights(HWDrawInfo *di, FLightNode *head, FDynLightData &lightdata, int portalgroup);
void PutFlat(HWDrawInfo *di, bool fog = false);
void Process(HWDrawInfo *di, sector_t * model, int whichplane, bool notexture);
void SetFrom3DFloor(F3DFloor *rover, bool top, bool underside);
void ProcessSector(HWDrawInfo *di, sector_t * frontsector, int which = 7 /*SSRF_RENDERALL*/); // cannot use constant due to circular dependencies.
void DrawSubsectors(HWDrawInfo *di, FRenderState &state);
void DrawFlat(HWDrawInfo *di, FRenderState &state, bool translucent);
void DrawOtherPlanes(HWDrawInfo *di, FRenderState &state);
void DrawFloodPlanes(HWDrawInfo *di, FRenderState &state);
};
//==========================================================================
//
// One sprite in the draw list
//
//==========================================================================
class GLSprite
{
public:
int lightlevel;
uint8_t foglevel;
uint8_t hw_styleflags;
bool fullbright;
bool polyoffset;
PalEntry ThingColor; // thing's own color
FColormap Colormap;
FSpriteModelFrame * modelframe;
FRenderStyle RenderStyle;
int OverrideShader;
int translation;
int index;
int depth;
int vertexindex;
float topclip;
float bottomclip;
float x,y,z; // needed for sorting!
float ul,ur;
float vt,vb;
float x1,y1,z1;
float x2,y2,z2;
FMaterial *gltexture;
float trans;
AActor * actor;
particle_t * particle;
TArray<lightlist_t> *lightlist;
DRotator Angles;
int dynlightindex;
void SplitSprite(HWDrawInfo *di, sector_t * frontsector, bool translucent);
void PerformSpriteClipAdjustment(AActor *thing, const DVector2 &thingpos, float spriteheight);
bool CalculateVertices(HWDrawInfo *di, FVector3 *v, DVector3 *vp);
public:
void CreateVertices(HWDrawInfo *di);
void PutSprite(HWDrawInfo *di, bool translucent);
void Process(HWDrawInfo *di, AActor* thing,sector_t * sector, area_t in_area, int thruportal = false);
void ProcessParticle (HWDrawInfo *di, particle_t *particle, sector_t *sector);//, int shade, int fakeside)
void DrawSprite(HWDrawInfo *di, FRenderState &state, bool translucent);
};
struct DecalVertex
{
float x, y, z;
float u, v;
};
struct GLDecal
{
FMaterial *gltexture;
TArray<lightlist_t> *lightlist;
DBaseDecal *decal;
DecalVertex dv[4];
float zcenter;
unsigned int vertindex;
FRenderStyle renderstyle;
int lightlevel;
int rellight;
float alpha;
FColormap Colormap;
sector_t *frontsector;
FVector3 Normal;
void DrawDecal(HWDrawInfo *di, FRenderState &state);
};
inline float Dist2(float x1,float y1,float x2,float y2)
{
return sqrtf((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
bool hw_SetPlaneTextureRotation(const GLSectorPlane * secplane, FMaterial * gltexture, VSMatrix &mat);
void hw_GetDynModelLight(AActor *self, FDynLightData &modellightdata);
extern const float LARGE_VALUE;

View file

@ -0,0 +1,423 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2001-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_fakeflat.cpp
** Fake flat functions to render stacked sectors
**
*/
#include "p_lnspec.h"
#include "p_local.h"
#include "g_levellocals.h"
#include "a_sharedglobal.h"
#include "d_player.h"
#include "r_sky.h"
#include "hw_fakeflat.h"
#include "hw_drawinfo.h"
#include "hwrenderer/utility/hw_cvars.h"
#include "r_utility.h"
static sector_t **fakesectorbuffer;
extern thread_local bool isWorkerThread;
//==========================================================================
//
// Check whether the player can look beyond this line
//
//==========================================================================
bool hw_CheckClip(side_t * sidedef, sector_t * frontsector, sector_t * backsector)
{
line_t *linedef = sidedef->linedef;
double bs_floorheight1;
double bs_floorheight2;
double bs_ceilingheight1;
double bs_ceilingheight2;
double fs_floorheight1;
double fs_floorheight2;
double fs_ceilingheight1;
double fs_ceilingheight2;
// Mirrors and horizons always block the view
//if (linedef->special==Line_Mirror || linedef->special==Line_Horizon) return true;
// Lines with portals must never block.
// Portals which require the sky flat are excluded here, because for them the special sky semantics apply.
if (!(frontsector->GetPortal(sector_t::ceiling)->mFlags & PORTSF_SKYFLATONLY) ||
!(frontsector->GetPortal(sector_t::floor)->mFlags & PORTSF_SKYFLATONLY) ||
!(backsector->GetPortal(sector_t::ceiling)->mFlags & PORTSF_SKYFLATONLY) ||
!(backsector->GetPortal(sector_t::floor)->mFlags & PORTSF_SKYFLATONLY))
{
return false;
}
// on large levels this distinction can save some time
// That's a lot of avoided multiplications if there's a lot to see!
if (frontsector->ceilingplane.isSlope())
{
fs_ceilingheight1 = frontsector->ceilingplane.ZatPoint(linedef->v1);
fs_ceilingheight2 = frontsector->ceilingplane.ZatPoint(linedef->v2);
}
else
{
fs_ceilingheight2 = fs_ceilingheight1 = frontsector->ceilingplane.fD();
}
if (frontsector->floorplane.isSlope())
{
fs_floorheight1 = frontsector->floorplane.ZatPoint(linedef->v1);
fs_floorheight2 = frontsector->floorplane.ZatPoint(linedef->v2);
}
else
{
fs_floorheight2 = fs_floorheight1 = -frontsector->floorplane.fD();
}
if (backsector->ceilingplane.isSlope())
{
bs_ceilingheight1 = backsector->ceilingplane.ZatPoint(linedef->v1);
bs_ceilingheight2 = backsector->ceilingplane.ZatPoint(linedef->v2);
}
else
{
bs_ceilingheight2 = bs_ceilingheight1 = backsector->ceilingplane.fD();
}
if (backsector->floorplane.isSlope())
{
bs_floorheight1 = backsector->floorplane.ZatPoint(linedef->v1);
bs_floorheight2 = backsector->floorplane.ZatPoint(linedef->v2);
}
else
{
bs_floorheight2 = bs_floorheight1 = -backsector->floorplane.fD();
}
// now check for closed sectors!
if (bs_ceilingheight1 <= fs_floorheight1 && bs_ceilingheight2 <= fs_floorheight2)
{
FTexture * tex = TexMan.GetTexture(sidedef->GetTexture(side_t::top), true);
if (!tex || !tex->isValid()) return false;
if (backsector->GetTexture(sector_t::ceiling) == skyflatnum &&
frontsector->GetTexture(sector_t::ceiling) == skyflatnum) return false;
return true;
}
if (fs_ceilingheight1 <= bs_floorheight1 && fs_ceilingheight2 <= bs_floorheight2)
{
FTexture * tex = TexMan.GetTexture(sidedef->GetTexture(side_t::bottom), true);
if (!tex || !tex->isValid()) return false;
// properly render skies (consider door "open" if both floors are sky):
if (backsector->GetTexture(sector_t::ceiling) == skyflatnum &&
frontsector->GetTexture(sector_t::ceiling) == skyflatnum) return false;
return true;
}
if (bs_ceilingheight1 <= bs_floorheight1 && bs_ceilingheight2 <= bs_floorheight2)
{
// preserve a kind of transparent door/lift special effect:
if (bs_ceilingheight1 < fs_ceilingheight1 || bs_ceilingheight2 < fs_ceilingheight2)
{
FTexture * tex = TexMan.GetTexture(sidedef->GetTexture(side_t::top), true);
if (!tex || !tex->isValid()) return false;
}
if (bs_floorheight1 > fs_floorheight1 || bs_floorheight2 > fs_floorheight2)
{
FTexture * tex = TexMan.GetTexture(sidedef->GetTexture(side_t::bottom), true);
if (!tex || !tex->isValid()) return false;
}
if (backsector->GetTexture(sector_t::ceiling) == skyflatnum &&
frontsector->GetTexture(sector_t::ceiling) == skyflatnum) return false;
if (backsector->GetTexture(sector_t::floor) == skyflatnum && frontsector->GetTexture(sector_t::floor)
== skyflatnum) return false;
return true;
}
return false;
}
//==========================================================================
//
// check for levels with exposed lower areas. May only be called if actual
// state is not known yet!
//
//==========================================================================
area_t hw_CheckViewArea(vertex_t *v1, vertex_t *v2, sector_t *frontsector, sector_t *backsector)
{
if (backsector->GetHeightSec() && !frontsector->GetHeightSec())
{
sector_t * s = backsector->heightsec;
double cz1 = frontsector->ceilingplane.ZatPoint(v1);
double cz2 = frontsector->ceilingplane.ZatPoint(v2);
double fz1 = s->floorplane.ZatPoint(v1);
double fz2 = s->floorplane.ZatPoint(v2);
// allow some tolerance in case slopes are involved
if (cz1 <= fz1 + 1. / 100 && cz2 <= fz2 + 1. / 100)
return area_below;
else
return area_normal;
}
return area_default;
}
//==========================================================================
//
//
//
//==========================================================================
static FMemArena FakeSectorAllocator(20 * sizeof(sector_t));
static sector_t *allocateSector(sector_t *sec)
{
if (fakesectorbuffer == nullptr)
{
unsigned numsectors = sec->Level->sectors.Size();
fakesectorbuffer = (sector_t**)FakeSectorAllocator.Alloc(numsectors * sizeof(sector_t*));
memset(fakesectorbuffer, 0, numsectors * sizeof(sector_t*));
}
auto sectornum = sec->sectornum;
fakesectorbuffer[sectornum] = (sector_t*)FakeSectorAllocator.Alloc(sizeof(sector_t));
return fakesectorbuffer[sectornum];
}
//==========================================================================
//
// This is mostly like R_FakeFlat but with a few alterations necessitated
// by hardware rendering
//
//==========================================================================
sector_t * hw_FakeFlat(sector_t * sec, area_t in_area, bool back, sector_t *localcopy)
{
if (!sec->GetHeightSec() || sec->heightsec==sec)
{
// check for backsectors with the ceiling lower than the floor. These will create
// visual glitches because upper amd lower textures overlap.
if (back && (sec->MoreFlags & SECMF_OVERLAPPING))
{
if (fakesectorbuffer && fakesectorbuffer[sec->sectornum]) return fakesectorbuffer[sec->sectornum];
auto dest = localcopy? localcopy : allocateSector(sec);
*dest = *sec;
dest->ceilingplane = sec->floorplane;
dest->ceilingplane.FlipVert();
dest->planes[sector_t::ceiling].TexZ = dest->planes[sector_t::floor].TexZ;
dest->ClearPortal(sector_t::ceiling);
dest->ClearPortal(sector_t::floor);
return dest;
}
return sec;
}
#ifdef _DEBUG
if (sec->sectornum==560)
{
int a = 0;
}
#endif
if (fakesectorbuffer && fakesectorbuffer[sec->sectornum])
{
return fakesectorbuffer[sec->sectornum];
}
assert(!(isWorkerThread && localcopy == nullptr));
if (in_area==area_above)
{
if (sec->heightsec->MoreFlags&SECMF_FAKEFLOORONLY /*|| sec->GetTexture(sector_t::ceiling)==skyflatnum*/) in_area=area_normal;
}
int diffTex = (sec->heightsec->MoreFlags & SECMF_CLIPFAKEPLANES);
sector_t * s = sec->heightsec;
auto dest = localcopy ? localcopy : allocateSector(sec);
*dest = *sec;
// Replace floor and ceiling height with control sector's heights.
if (diffTex)
{
if (s->floorplane.CopyPlaneIfValid (&dest->floorplane, &sec->ceilingplane))
{
dest->SetTexture(sector_t::floor, s->GetTexture(sector_t::floor), false);
dest->SetPlaneTexZQuick(sector_t::floor, s->GetPlaneTexZ(sector_t::floor));
dest->iboindex[sector_t::floor] = sec->iboindex[sector_t::vbo_fakefloor];
dest->vboheight[sector_t::floor] = s->vboheight[sector_t::floor];
}
else if (s->MoreFlags & SECMF_FAKEFLOORONLY)
{
if (in_area==area_below)
{
dest->CopyColors(s);
if (!(s->MoreFlags & SECMF_NOFAKELIGHT))
{
dest->lightlevel = s->lightlevel;
dest->SetPlaneLight(sector_t::floor, s->GetPlaneLight(sector_t::floor));
dest->SetPlaneLight(sector_t::ceiling, s->GetPlaneLight(sector_t::ceiling));
dest->ChangeFlags(sector_t::floor, -1, s->GetFlags(sector_t::floor));
dest->ChangeFlags(sector_t::ceiling, -1, s->GetFlags(sector_t::ceiling));
}
return dest;
}
return sec;
}
}
else
{
dest->SetPlaneTexZQuick(sector_t::floor, s->GetPlaneTexZ(sector_t::floor));
dest->floorplane = s->floorplane;
dest->iboindex[sector_t::floor] = sec->iboindex[sector_t::vbo_fakefloor];
dest->vboheight[sector_t::floor] = s->vboheight[sector_t::floor];
}
if (!(s->MoreFlags&SECMF_FAKEFLOORONLY))
{
if (diffTex)
{
if (s->ceilingplane.CopyPlaneIfValid (&dest->ceilingplane, &sec->floorplane))
{
dest->SetTexture(sector_t::ceiling, s->GetTexture(sector_t::ceiling), false);
dest->SetPlaneTexZQuick(sector_t::ceiling, s->GetPlaneTexZ(sector_t::ceiling));
dest->iboindex[sector_t::ceiling] = sec->iboindex[sector_t::vbo_fakeceiling];
dest->vboheight[sector_t::ceiling] = s->vboheight[sector_t::ceiling];
}
}
else
{
dest->ceilingplane = s->ceilingplane;
dest->SetPlaneTexZQuick(sector_t::ceiling, s->GetPlaneTexZ(sector_t::ceiling));
dest->iboindex[sector_t::ceiling] = sec->iboindex[sector_t::vbo_fakeceiling];
dest->vboheight[sector_t::ceiling] = s->vboheight[sector_t::ceiling];
}
}
if (in_area==area_below)
{
dest->CopyColors(s);
dest->SetPlaneTexZQuick(sector_t::floor, sec->GetPlaneTexZ(sector_t::floor));
dest->SetPlaneTexZQuick(sector_t::ceiling, s->GetPlaneTexZ(sector_t::floor));
dest->floorplane=sec->floorplane;
dest->ceilingplane=s->floorplane;
dest->ceilingplane.FlipVert();
dest->iboindex[sector_t::floor] = sec->iboindex[sector_t::floor];
dest->vboheight[sector_t::floor] = sec->vboheight[sector_t::floor];
dest->iboindex[sector_t::ceiling] = sec->iboindex[sector_t::vbo_fakefloor];
dest->vboheight[sector_t::ceiling] = s->vboheight[sector_t::floor];
dest->ClearPortal(sector_t::ceiling);
if (!(s->MoreFlags & SECMF_NOFAKELIGHT))
{
dest->lightlevel = s->lightlevel;
}
//if (!back)
{
dest->SetTexture(sector_t::floor, diffTex ? sec->GetTexture(sector_t::floor) : s->GetTexture(sector_t::floor), false);
dest->planes[sector_t::floor].xform = s->planes[sector_t::floor].xform;
//dest->ceilingplane = s->floorplane;
if (s->GetTexture(sector_t::ceiling) == skyflatnum)
{
dest->SetTexture(sector_t::ceiling, dest->GetTexture(sector_t::floor), false);
//dest->floorplane = dest->ceilingplane;
//dest->floorplane.FlipVert ();
//dest->floorplane.ChangeHeight (+1);
dest->planes[sector_t::ceiling].xform = dest->planes[sector_t::floor].xform;
}
else
{
dest->SetTexture(sector_t::ceiling, diffTex ? s->GetTexture(sector_t::floor) : s->GetTexture(sector_t::ceiling), false);
dest->planes[sector_t::ceiling].xform = s->planes[sector_t::ceiling].xform;
}
if (!(s->MoreFlags & SECMF_NOFAKELIGHT))
{
dest->SetPlaneLight(sector_t::floor, s->GetPlaneLight(sector_t::floor));
dest->SetPlaneLight(sector_t::ceiling, s->GetPlaneLight(sector_t::ceiling));
dest->ChangeFlags(sector_t::floor, -1, s->GetFlags(sector_t::floor));
dest->ChangeFlags(sector_t::ceiling, -1, s->GetFlags(sector_t::ceiling));
}
}
}
else if (in_area == area_above)
{
dest->CopyColors(s);
dest->SetPlaneTexZQuick(sector_t::ceiling, sec->GetPlaneTexZ(sector_t::ceiling));
dest->SetPlaneTexZQuick(sector_t::floor, s->GetPlaneTexZ(sector_t::ceiling));
dest->ceilingplane = sec->ceilingplane;
dest->floorplane = s->ceilingplane;
dest->floorplane.FlipVert();
dest->iboindex[sector_t::floor] = sec->iboindex[sector_t::vbo_fakeceiling];
dest->vboheight[sector_t::floor] = s->vboheight[sector_t::ceiling];
dest->iboindex[sector_t::ceiling] = sec->iboindex[sector_t::ceiling];
dest->vboheight[sector_t::ceiling] = sec->vboheight[sector_t::ceiling];
dest->ClearPortal(sector_t::floor);
if (!(s->MoreFlags & SECMF_NOFAKELIGHT))
{
dest->lightlevel = s->lightlevel;
}
//if (!back)
{
dest->SetTexture(sector_t::ceiling, diffTex ? sec->GetTexture(sector_t::ceiling) : s->GetTexture(sector_t::ceiling), false);
dest->SetTexture(sector_t::floor, s->GetTexture(sector_t::ceiling), false);
dest->planes[sector_t::ceiling].xform = dest->planes[sector_t::floor].xform = s->planes[sector_t::ceiling].xform;
if (s->GetTexture(sector_t::floor) != skyflatnum)
{
dest->SetTexture(sector_t::floor, s->GetTexture(sector_t::floor), false);
dest->planes[sector_t::floor].xform = s->planes[sector_t::floor].xform;
}
if (!(s->MoreFlags & SECMF_NOFAKELIGHT))
{
dest->lightlevel = s->lightlevel;
dest->SetPlaneLight(sector_t::floor, s->GetPlaneLight(sector_t::floor));
dest->SetPlaneLight(sector_t::ceiling, s->GetPlaneLight(sector_t::ceiling));
dest->ChangeFlags(sector_t::floor, -1, s->GetFlags(sector_t::floor));
dest->ChangeFlags(sector_t::ceiling, -1, s->GetFlags(sector_t::ceiling));
}
}
}
return dest;
}
void hw_ClearFakeFlat()
{
FakeSectorAllocator.FreeAll();
fakesectorbuffer = nullptr;
}

View file

@ -0,0 +1,17 @@
#pragma once
enum area_t : int
{
area_normal,
area_below,
area_above,
area_default
};
// Global functions.
bool hw_CheckClip(side_t * sidedef, sector_t * frontsector, sector_t * backsector);
void hw_ClearFakeFlat();
sector_t * hw_FakeFlat(sector_t * sec, area_t in_area, bool back, sector_t *localcopy = nullptr);
area_t hw_CheckViewArea(vertex_t *v1, vertex_t *v2, sector_t *frontsector, sector_t *backsector);

View file

@ -0,0 +1,718 @@
//
//---------------------------------------------------------------------------
//
// 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 "hwrenderer/dynlights/hw_lightbuffer.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->hasCanvas())
{
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 && !di->Level->lightadditivesurfaces)
{
dynlightindex = -1;
return; // no lights on additively blended surfaces.
}
while (node)
{
FDynamicLight * light = node->lightsource;
if (!light->IsActive())
{
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->Pos);
if ((planeh<light->Z() && ceiling) || (planeh>light->Z() && !ceiling))
{
node = node->nextLight;
continue;
}
p.Set(plane.plane.Normal(), plane.plane.fD());
draw_dlightf += lightdata.GetLight(portalgroup, p, light, false);
node = node->nextLight;
}
dynlightindex = screen->mLights->UploadLights(lightdata);
}
//==========================================================================
//
//
//
//==========================================================================
void GLFlat::DrawSubsectors(HWDrawInfo *di, FRenderState &state)
{
if (di->Level->HasDynamicLights && screen->BuffersArePersistent() && !di->isFullbrightScene())
{
SetupLights(di, section->lighthead, lightdata, sector->PortalGroup);
}
state.SetLightIndex(dynlightindex);
state.DrawIndexed(DT_Triangles, iboindex + section->vertexindex, section->vertexcount);
flatvertices += section->vertexcount;
flatprimitives++;
}
//==========================================================================
//
// Drawer for render hacks
//
//==========================================================================
void GLFlat::DrawOtherPlanes(HWDrawInfo *di, FRenderState &state)
{
state.SetMaterial(gltexture, CLAMP_NONE, 0, -1);
// Draw the subsectors assigned to it due to missing textures
auto pNode = (renderflags&SSRF_RENDERFLOOR) ?
di->otherFloorPlanes.CheckKey(sector->sectornum) : di->otherCeilingPlanes.CheckKey(sector->sectornum);
if (!pNode) return;
auto node = *pNode;
while (node)
{
state.SetLightIndex(node->lightindex);
auto num = node->sub->numlines;
flatvertices += num;
flatprimitives++;
state.Draw(DT_TriangleFan,node->vertexindex, num);
node = node->next;
}
}
//==========================================================================
//
// Drawer for render hacks
//
//==========================================================================
void GLFlat::DrawFloodPlanes(HWDrawInfo *di, FRenderState &state)
{
// Flood gaps with the back side's ceiling/floor texture
// This requires a stencil because the projected plane interferes with
// the depth buffer
state.SetMaterial(gltexture, CLAMP_NONE, 0, -1);
// Draw the subsectors assigned to it due to missing textures
auto pNode = (renderflags&SSRF_RENDERFLOOR) ?
di->floodFloorSegs.CheckKey(sector->sectornum) : di->floodCeilingSegs.CheckKey(sector->sectornum);
if (!pNode) return;
auto fnode = *pNode;
state.SetLightIndex(-1);
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);
state.SetStencil(0, SOP_Increment, SF_ColorMaskOff);
state.Draw(DT_TriangleFan, fnode->vertexindex, 4);
// Draw projected plane into stencil
state.EnableTexture(true);
state.SetEffect(EFF_NONE);
state.SetStencil(1, SOP_Keep, SF_DepthMaskOff);
state.EnableDepthTest(false);
state.Draw(DT_TriangleFan, fnode->vertexindex + 4, 4);
// clear stencil
state.SetEffect(EFF_STENCIL);
state.EnableTexture(false);
state.SetStencil(1, SOP_Decrement, SF_ColorMaskOff | SF_DepthMaskOff);
state.Draw(DT_TriangleFan, fnode->vertexindex, 4);
// restore old stencil op.
state.EnableTexture(true);
state.EnableDepthTest(true);
state.SetEffect(EFF_NONE);
state.SetDepthBias(0, 0);
state.SetStencil(0, SOP_Keep, SF_AllOn);
fnode = fnode->next;
}
}
//==========================================================================
//
//
//
//==========================================================================
void GLFlat::DrawFlat(HWDrawInfo *di, FRenderState &state, bool translucent)
{
#ifdef _DEBUG
if (sector->sectornum == gl_breaksec)
{
int a = 0;
}
#endif
int rel = getExtraLight();
state.SetNormal(plane.plane.Normal().X, plane.plane.Normal().Z, plane.plane.Normal().Y);
di->SetColor(state, lightlevel, rel, di->isFullbrightScene(), Colormap, alpha);
di->SetFog(state, lightlevel, rel, di->isFullbrightScene(), &Colormap, false);
state.SetObjectColor(FlatColor | 0xff000000);
state.SetAddColor(AddColor | 0xff000000);
if (hacktype & SSRF_PLANEHACK)
{
DrawOtherPlanes(di, state);
}
else if (hacktype & SSRF_FLOODHACK)
{
DrawFloodPlanes(di, state);
}
else 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 if (!hacktype)
{
state.SetMaterial(gltexture, CLAMP_XY, 0, -1);
state.SetLightIndex(dynlightindex);
state.Draw(DT_TriangleFan,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);
}
state.SetAddColor(0);
}
//==========================================================================
//
// GLFlat::PutFlat
//
// submit to the renderer
//
//==========================================================================
inline void GLFlat::PutFlat(HWDrawInfo *di, bool fog)
{
if (di->isFullbrightScene())
{
Colormap.Clear();
}
else if (!screen->BuffersArePersistent())
{
if (di->Level->HasDynamicLights && gltexture != nullptr && !di->isFullbrightScene() && !(hacktype & (SSRF_PLANEHACK|SSRF_FLOODHACK)) )
{
SetupLights(di, section->lighthead, lightdata, sector->PortalGroup);
}
}
di->AddFlat(this, fog);
}
//==========================================================================
//
// This draws one flat
// 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;
}
// For hacks this won't go into a render list.
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;
AddColor = 0;
}
else
{
Colormap.CopyFrom3DLight(light);
FlatColor = *plane.flatcolor;
// AddColor = sector->SpecialColors[sector_t::add];
}
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, int which)
{
lightlist_t * light;
FSectorPortal *port;
#ifdef _DEBUG
if (frontsector->sectornum == gl_breaksec)
{
int a = 0;
}
#endif
// Get the real sector for this one.
sector = &di->Level->sectors[frontsector->sectornum];
extsector_t::xfloor &x = sector->e->XFloor;
dynlightindex = -1;
hacktype = (which & (SSRF_PLANEHACK|SSRF_FLOODHACK));
uint8_t sink;
uint8_t &srf = hacktype? sink : di->section_renderflags[di->Level->sections.SectionIndex(section)];
const auto &vp = di->Viewpoint;
//
//
//
// do floors
//
//
//
if ((which & SSRF_RENDERFLOOR) && 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];
AddColor = frontsector->AdditiveColors[sector_t::floor];
port = frontsector->ValidatePortal(sector_t::floor);
if ((stack = (port != NULL)))
{
/* to be redone in a less invasive manner
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 ((which & SSRF_RENDERCEILING) && 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];
AddColor = frontsector->AdditiveColors[sector_t::ceiling];
port = frontsector->ValidatePortal(sector_t::ceiling);
if ((stack = (port != NULL)))
{
/* as above for floors
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, &sector->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 ((which & SSRF_RENDER3DPLANES) && x.ffloors.Size())
{
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;
}
}
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,381 @@
#pragma once
#include "tarray.h"
#include "r_utility.h"
#include "actor.h"
#include "hwrenderer/scene/hw_drawinfo.h"
#include "hwrenderer/scene/hw_drawstructs.h"
#include "hw_renderstate.h"
#include "hwrenderer/textures/hw_material.h"
struct GLSkyInfo
{
float x_offset[2];
float y_offset; // doubleskies don't have a y-offset
FMaterial * texture[2];
FTextureID skytexno1;
bool mirrored;
bool doublesky;
bool sky2;
PalEntry fadecolor;
bool operator==(const GLSkyInfo & inf)
{
return !memcmp(this, &inf, sizeof(*this));
}
bool operator!=(const GLSkyInfo & inf)
{
return !!memcmp(this, &inf, sizeof(*this));
}
void init(HWDrawInfo *di, int sky1, PalEntry fadecolor);
};
struct GLHorizonInfo
{
GLSectorPlane plane;
int lightlevel;
FColormap colormap;
PalEntry specialcolor;
};
struct FPortalSceneState;
class HWPortal
{
friend struct FPortalSceneState;
enum
{
STP_Stencil,
STP_DepthClear,
STP_DepthRestore,
STP_AllInOne
};
ActorRenderFlags savedvisibility;
TArray<unsigned int> mPrimIndices;
unsigned int mTopCap = ~0u, mBottomCap = ~0u;
void DrawPortalStencil(FRenderState &state, int pass);
public:
FPortalSceneState * mState;
TArray<GLWall> lines;
BoundingRect boundingBox;
int planesused = 0;
HWPortal(FPortalSceneState *s, bool local = false) : mState(s), boundingBox(false)
{
}
virtual ~HWPortal() {}
virtual int ClipSeg(seg_t *seg, const DVector3 &viewpos) { return PClip_Inside; }
virtual int ClipSubsector(subsector_t *sub) { return PClip_Inside; }
virtual int ClipPoint(const DVector2 &pos) { return PClip_Inside; }
virtual line_t *ClipLine() { return nullptr; }
virtual void * GetSource() const = 0; // GetSource MUST be implemented!
virtual const char *GetName() = 0;
virtual bool IsSky() { return false; }
virtual bool NeedCap() { return true; }
virtual bool NeedDepthBuffer() { return true; }
virtual void DrawContents(HWDrawInfo *di, FRenderState &state) = 0;
virtual void RenderAttached(HWDrawInfo *di) {}
void SetupStencil(HWDrawInfo *di, FRenderState &state, bool usestencil);
void RemoveStencil(HWDrawInfo *di, FRenderState &state, bool usestencil);
void AddLine(GLWall * l)
{
lines.Push(*l);
boundingBox.addVertex(l->glseg.x1, l->glseg.y1);
boundingBox.addVertex(l->glseg.x2, l->glseg.y2);
}
};
struct FPortalSceneState
{
int MirrorFlag = 0;
int PlaneMirrorFlag = 0;
int renderdepth = 0;
int PlaneMirrorMode = 0;
bool inskybox = 0;
UniqueList<GLSkyInfo> UniqueSkies;
UniqueList<GLHorizonInfo> UniqueHorizons;
UniqueList<secplane_t> UniquePlaneMirrors;
int skyboxrecursion = 0;
void BeginScene()
{
UniqueSkies.Clear();
UniqueHorizons.Clear();
UniquePlaneMirrors.Clear();
}
bool isMirrored() const
{
return !!((MirrorFlag ^ PlaneMirrorFlag) & 1);
}
void StartFrame();
bool RenderFirstSkyPortal(int recursion, HWDrawInfo *outer_di, FRenderState &state);
void EndFrame(HWDrawInfo *outer_di, FRenderState &state);
void RenderPortal(HWPortal *p, FRenderState &state, bool usestencil, HWDrawInfo *outer_di);
};
class HWScenePortalBase : public HWPortal
{
protected:
HWScenePortalBase(FPortalSceneState *state) : HWPortal(state, false)
{
}
public:
void ClearClipper(HWDrawInfo *di, Clipper *clipper);
virtual bool NeedDepthBuffer() { return true; }
virtual void DrawContents(HWDrawInfo *di, FRenderState &state)
{
if (Setup(di, state, di->mClipper))
{
di->DrawScene(di, DM_PORTAL);
Shutdown(di, state);
}
else state.ClearScreen();
}
virtual bool Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) = 0;
virtual void Shutdown(HWDrawInfo *di, FRenderState &rstate) {}
};
struct HWLinePortal : public HWScenePortalBase
{
// this must be the same as at the start of line_t, so that we can pass in this structure directly to P_ClipLineToPortal.
vertex_t *v1, *v2; // vertices, from v1 to v2
DVector2 delta; // precalculated v2 - v1 for side checking
angle_t angv1, angv2; // for quick comparisons with a line or subsector
HWLinePortal(FPortalSceneState *state, line_t *line) : HWScenePortalBase(state)
{
v1 = line->v1;
v2 = line->v2;
CalcDelta();
}
HWLinePortal(FPortalSceneState *state, FLinePortalSpan *line) : HWScenePortalBase(state)
{
if (line->lines[0]->mType != PORTT_LINKED || line->v1 == nullptr)
{
// For non-linked portals we must check the actual linedef.
line_t *lline = line->lines[0]->mDestination;
v1 = lline->v1;
v2 = lline->v2;
}
else
{
// For linked portals we can check the merged span.
v1 = line->v1;
v2 = line->v2;
}
CalcDelta();
}
void CalcDelta()
{
delta = v2->fPos() - v1->fPos();
}
line_t *line()
{
vertex_t **pv = &v1;
return reinterpret_cast<line_t*>(pv);
}
int ClipSeg(seg_t *seg, const DVector3 &viewpos) override;
int ClipSubsector(subsector_t *sub) override;
int ClipPoint(const DVector2 &pos);
bool NeedCap() override { return false; }
};
struct HWMirrorPortal : public HWLinePortal
{
// mirror portals always consist of single linedefs!
line_t * linedef;
protected:
bool Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) override;
void Shutdown(HWDrawInfo *di, FRenderState &rstate) override;
void * GetSource() const override { return linedef; }
const char *GetName() override;
public:
HWMirrorPortal(FPortalSceneState *state, line_t * line)
: HWLinePortal(state, line)
{
linedef = line;
}
};
struct HWLineToLinePortal : public HWLinePortal
{
FLinePortalSpan *glport;
protected:
bool Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) override;
virtual void * GetSource() const override { return glport; }
virtual const char *GetName() override;
virtual line_t *ClipLine() override { return line(); }
virtual void RenderAttached(HWDrawInfo *di) override;
public:
HWLineToLinePortal(FPortalSceneState *state, FLinePortalSpan *ll)
: HWLinePortal(state, ll)
{
glport = ll;
}
};
struct HWSkyboxPortal : public HWScenePortalBase
{
bool oldclamp;
int old_pm;
FSectorPortal * portal;
protected:
bool Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) override;
void Shutdown(HWDrawInfo *di, FRenderState &rstate) override;
virtual void * GetSource() const { return portal; }
virtual bool IsSky() { return true; }
virtual const char *GetName();
public:
HWSkyboxPortal(FPortalSceneState *state, FSectorPortal * pt) : HWScenePortalBase(state)
{
portal = pt;
}
};
struct HWSectorStackPortal : public HWScenePortalBase
{
TArray<subsector_t *> subsectors;
protected:
bool Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) override;
void Shutdown(HWDrawInfo *di, FRenderState &rstate) override;
virtual void * GetSource() const { return origin; }
virtual bool IsSky() { return true; } // although this isn't a real sky it can be handled as one.
virtual const char *GetName();
FSectorPortalGroup *origin;
public:
HWSectorStackPortal(FPortalSceneState *state, FSectorPortalGroup *pt) : HWScenePortalBase(state)
{
origin = pt;
}
void SetupCoverage(HWDrawInfo *di);
void AddSubsector(subsector_t *sub)
{
subsectors.Push(sub);
}
};
struct HWPlaneMirrorPortal : public HWScenePortalBase
{
int old_pm;
protected:
bool Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clipper) override;
void Shutdown(HWDrawInfo *di, FRenderState &rstate) override;
virtual void * GetSource() const { return origin; }
virtual const char *GetName();
secplane_t * origin;
public:
HWPlaneMirrorPortal(FPortalSceneState *state, secplane_t * pt) : HWScenePortalBase(state)
{
origin = pt;
}
};
struct HWHorizonPortal : public HWPortal
{
GLHorizonInfo * origin;
unsigned int voffset;
unsigned int vcount;
friend struct HWEEHorizonPortal;
protected:
virtual void DrawContents(HWDrawInfo *di, FRenderState &state);
virtual void * GetSource() const { return origin; }
virtual bool NeedDepthBuffer() { return false; }
virtual bool NeedCap() { return false; }
virtual const char *GetName();
public:
HWHorizonPortal(FPortalSceneState *state, GLHorizonInfo * pt, FRenderViewpoint &vp, bool local = false);
};
struct HWEEHorizonPortal : public HWPortal
{
FSectorPortal * portal;
protected:
virtual void DrawContents(HWDrawInfo *di, FRenderState &state);
virtual void * GetSource() const { return portal; }
virtual bool NeedDepthBuffer() { return false; }
virtual bool NeedCap() { return false; }
virtual const char *GetName();
public:
HWEEHorizonPortal(FPortalSceneState *state, FSectorPortal *pt) : HWPortal(state)
{
portal = pt;
}
};
struct HWSkyPortal : public HWPortal
{
GLSkyInfo * origin;
FSkyVertexBuffer *vertexBuffer;
friend struct HWEEHorizonPortal;
void RenderRow(HWDrawInfo *di, FRenderState &state, EDrawType prim, int row, bool apply = true);
void RenderBox(HWDrawInfo *di, FRenderState &state, FTextureID texno, FMaterial * gltex, float x_offset, bool sky2);
void RenderDome(HWDrawInfo *di, FRenderState &state, FMaterial * tex, float x_offset, float y_offset, bool mirror, int mode);
protected:
virtual void DrawContents(HWDrawInfo *di, FRenderState &state);
virtual void * GetSource() const { return origin; }
virtual bool IsSky() { return true; }
virtual bool NeedDepthBuffer() { return false; }
virtual const char *GetName();
public:
HWSkyPortal(FSkyVertexBuffer *vertexbuffer, FPortalSceneState *state, GLSkyInfo * pt, bool local = false)
: HWPortal(state, local)
{
origin = pt;
vertexBuffer = vertexbuffer;
}
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,158 @@
//
//---------------------------------------------------------------------------
//
// 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 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/
//
//--------------------------------------------------------------------------
//
/*
** hw_renderstate.cpp
** hardware independent part of render state.
**
*/
#include "hw_renderstate.h"
#include "hw_drawstructs.h"
#include "hw_portal.h"
#include "hwrenderer/utility/hw_lighting.h"
#include "hwrenderer/utility/hw_cvars.h"
//==========================================================================
//
// set current light color
//
//==========================================================================
void HWDrawInfo::SetColor(FRenderState &state, int sectorlightlevel, int rellight, bool fullbright, const FColormap &cm, float alpha, bool weapon)
{
if (fullbright)
{
state.SetColorAlpha(0xffffff, alpha, 0);
if (isSoftwareLighting()) state.SetSoftLightLevel(255);
else state.SetNoSoftLightLevel();
}
else
{
int hwlightlevel = CalcLightLevel(sectorlightlevel, rellight, weapon, cm.BlendFactor);
PalEntry pe = CalcLightColor(hwlightlevel, cm.LightColor, cm.BlendFactor);
state.SetColorAlpha(pe, alpha, cm.Desaturation);
if (isSoftwareLighting()) state.SetSoftLightLevel(hw_ClampLight(sectorlightlevel + rellight), cm.BlendFactor);
else state.SetNoSoftLightLevel();
}
}
//==========================================================================
//
// Lighting stuff
//
//==========================================================================
void HWDrawInfo::SetShaderLight(FRenderState &state, float level, float olight)
{
const float MAXDIST = 256.f;
const float THRESHOLD = 96.f;
const float FACTOR = 0.75f;
if (level > 0)
{
float lightdist, lightfactor;
if (olight < THRESHOLD)
{
lightdist = (MAXDIST / 2) + (olight * MAXDIST / THRESHOLD / 2);
olight = THRESHOLD;
}
else lightdist = MAXDIST;
lightfactor = 1.f + ((olight / level) - 1.f) * FACTOR;
if (lightfactor == 1.f) lightdist = 0.f; // save some code in the shader
state.SetLightParms(lightfactor, lightdist);
}
else
{
state.SetLightParms(1.f, 0.f);
}
}
//==========================================================================
//
// Sets the fog for the current polygon
//
//==========================================================================
void HWDrawInfo::SetFog(FRenderState &state, int lightlevel, int rellight, bool fullbright, const FColormap *cmap, bool isadditive)
{
PalEntry fogcolor;
float fogdensity;
if (Level->flags&LEVEL_HASFADETABLE)
{
fogdensity = 70;
fogcolor = 0x808080;
}
else if (cmap != NULL && !fullbright)
{
fogcolor = cmap->FadeColor;
fogdensity = GetFogDensity(lightlevel, fogcolor, cmap->FogDensity, cmap->BlendFactor);
fogcolor.a = 0;
}
else
{
fogcolor = 0;
fogdensity = 0;
}
// Make fog a little denser when inside a skybox
if (screen->mPortalState->inskybox) fogdensity += fogdensity / 2;
// no fog in enhanced vision modes!
if (fogdensity == 0 || gl_fogmode == 0)
{
state.EnableFog(false);
state.SetFog(0, 0);
}
else
{
if ((lightmode == ELightMode::Doom || (isSoftwareLighting() && cmap->BlendFactor > 0)) && fogcolor == 0)
{
float light = (float)CalcLightLevel(lightlevel, rellight, false, cmap->BlendFactor);
SetShaderLight(state, light, lightlevel);
}
else
{
state.SetLightParms(1.f, 0.f);
}
// For additive rendering using the regular fog color here would mean applying it twice
// so always use black
if (isadditive)
{
fogcolor = 0;
}
state.EnableFog(true);
state.SetFog(fogcolor, fogdensity);
// Korshun: fullbright fog like in software renderer.
if (isSoftwareLighting() && cmap->BlendFactor == 0 && Level->brightfog && fogdensity != 0 && fogcolor != 0)
{
state.SetSoftLightLevel(255);
}
}
}

View file

@ -0,0 +1,536 @@
#pragma once
#include "v_palette.h"
#include "vectors.h"
#include "g_levellocals.h"
#include "hw_drawstructs.h"
#include "hw_drawlist.h"
#include "r_data/matrix.h"
#include "hwrenderer/textures/hw_material.h"
struct FColormap;
class IVertexBuffer;
class IIndexBuffer;
enum EClearTarget
{
CT_Depth = 1,
CT_Stencil = 2,
CT_Color = 4
};
enum ERenderEffect
{
EFF_NONE = -1,
EFF_FOGBOUNDARY,
EFF_SPHEREMAP,
EFF_BURN,
EFF_STENCIL,
MAX_EFFECTS
};
enum EAlphaFunc
{
Alpha_GEqual = 0,
Alpha_Greater = 1
};
enum EDrawType
{
DT_Points = 0,
DT_Lines = 1,
DT_Triangles = 2,
DT_TriangleFan = 3,
DT_TriangleStrip = 4
};
enum EDepthFunc
{
DF_Less,
DF_LEqual,
DF_Always
};
enum EStencilFlags
{
SF_AllOn = 0,
SF_ColorMaskOff = 1,
SF_DepthMaskOff = 2,
};
enum EStencilOp
{
SOP_Keep = 0,
SOP_Increment = 1,
SOP_Decrement = 2
};
enum ECull
{
Cull_None,
Cull_CCW,
Cull_CW
};
struct FStateVec4
{
float vec[4];
void Set(float r, float g, float b, float a)
{
vec[0] = r;
vec[1] = g;
vec[2] = b;
vec[3] = a;
}
};
struct FMaterialState
{
FMaterial *mMaterial;
int mClampMode;
int mTranslation;
int mOverrideShader;
bool mChanged;
void Reset()
{
mMaterial = nullptr;
mTranslation = 0;
mClampMode = CLAMP_NONE;
mOverrideShader = -1;
mChanged = false;
}
};
struct FDepthBiasState
{
float mFactor;
float mUnits;
bool mChanged;
void Reset()
{
mFactor = 0;
mUnits = 0;
mChanged = false;
}
};
class FRenderState
{
protected:
uint8_t mFogEnabled;
uint8_t mTextureEnabled:1;
uint8_t mGlowEnabled : 1;
uint8_t mGradientEnabled : 1;
uint8_t mBrightmapEnabled : 1;
uint8_t mModelMatrixEnabled : 1;
uint8_t mTextureMatrixEnabled : 1;
uint8_t mSplitEnabled : 1;
int mLightIndex;
int mSpecialEffect;
int mTextureMode;
int mDesaturation;
int mSoftLight;
float mLightParms[4];
float mAlphaThreshold;
float mClipSplit[2];
float mInterpolationFactor;
FStateVec4 mNormal;
FStateVec4 mColor;
FStateVec4 mGlowTop, mGlowBottom;
FStateVec4 mGlowTopPlane, mGlowBottomPlane;
FStateVec4 mGradientTopPlane, mGradientBottomPlane;
FStateVec4 mSplitTopPlane, mSplitBottomPlane;
PalEntry mAddColor;
PalEntry mFogColor;
PalEntry mObjectColor;
PalEntry mObjectColor2;
FStateVec4 mDynColor;
FRenderStyle mRenderStyle;
FMaterialState mMaterial;
FDepthBiasState mBias;
IVertexBuffer *mVertexBuffer;
int mVertexOffsets[2]; // one per binding point
IIndexBuffer *mIndexBuffer;
public:
VSMatrix mModelMatrix;
VSMatrix mTextureMatrix;
public:
void Reset()
{
mTextureEnabled = true;
mGradientEnabled = mBrightmapEnabled = mFogEnabled = mGlowEnabled = false;
mFogColor.d = -1;
mTextureMode = -1;
mDesaturation = 0;
mAlphaThreshold = 0.5f;
mModelMatrixEnabled = false;
mTextureMatrixEnabled = false;
mSplitEnabled = false;
mAddColor = 0;
mObjectColor = 0xffffffff;
mObjectColor2 = 0;
mSoftLight = 0;
mLightParms[0] = mLightParms[1] = mLightParms[2] = 0.0f;
mLightParms[3] = -1.f;
mSpecialEffect = EFF_NONE;
mLightIndex = -1;
mInterpolationFactor = 0;
mRenderStyle = DefaultRenderStyle();
mMaterial.Reset();
mBias.Reset();
mVertexBuffer = nullptr;
mVertexOffsets[0] = mVertexOffsets[1] = 0;
mIndexBuffer = nullptr;
mColor.Set(1.0f, 1.0f, 1.0f, 1.0f);
mGlowTop.Set(0.0f, 0.0f, 0.0f, 0.0f);
mGlowBottom.Set(0.0f, 0.0f, 0.0f, 0.0f);
mGlowTopPlane.Set(0.0f, 0.0f, 0.0f, 0.0f);
mGlowBottomPlane.Set(0.0f, 0.0f, 0.0f, 0.0f);
mGradientTopPlane.Set(0.0f, 0.0f, 0.0f, 0.0f);
mGradientBottomPlane.Set(0.0f, 0.0f, 0.0f, 0.0f);
mSplitTopPlane.Set(0.0f, 0.0f, 0.0f, 0.0f);
mSplitBottomPlane.Set(0.0f, 0.0f, 0.0f, 0.0f);
mDynColor.Set(0.0f, 0.0f, 0.0f, 0.0f);
mModelMatrix.loadIdentity();
mTextureMatrix.loadIdentity();
ClearClipSplit();
}
void SetNormal(FVector3 norm)
{
mNormal.Set(norm.X, norm.Y, norm.Z, 0.f);
}
void SetNormal(float x, float y, float z)
{
mNormal.Set(x, y, z, 0.f);
}
void SetColor(float r, float g, float b, float a = 1.f, int desat = 0)
{
mColor.Set(r, g, b, a);
mDesaturation = desat;
}
void SetColor(PalEntry pe, int desat = 0)
{
mColor.Set(pe.r / 255.f, pe.g / 255.f, pe.b / 255.f, pe.a / 255.f);
mDesaturation = desat;
}
void SetColorAlpha(PalEntry pe, float alpha = 1.f, int desat = 0)
{
mColor.Set(pe.r / 255.f, pe.g / 255.f, pe.b / 255.f, alpha);
mDesaturation = desat;
}
void ResetColor()
{
mColor.Set(1, 1, 1, 1);
mDesaturation = 0;
}
void SetTextureMode(int mode)
{
mTextureMode = mode;
}
void SetTextureMode(FRenderStyle style)
{
if (style.Flags & STYLEF_RedIsAlpha)
{
mTextureMode = TM_ALPHATEXTURE;
}
else if (style.Flags & STYLEF_ColorIsFixed)
{
mTextureMode = TM_STENCIL;
}
else if (style.Flags & STYLEF_InvertSource)
{
mTextureMode = TM_INVERSE;
}
}
int GetTextureMode()
{
return mTextureMode;
}
void EnableTexture(bool on)
{
mTextureEnabled = on;
}
void EnableFog(uint8_t on)
{
mFogEnabled = on;
}
void SetEffect(int eff)
{
mSpecialEffect = eff;
}
void EnableGlow(bool on)
{
mGlowEnabled = on;
}
void EnableGradient(bool on)
{
mGradientEnabled = on;
}
void EnableBrightmap(bool on)
{
mBrightmapEnabled = on;
}
void EnableSplit(bool on)
{
mSplitEnabled = on;
}
void EnableModelMatrix(bool on)
{
mModelMatrixEnabled = on;
}
void EnableTextureMatrix(bool on)
{
mTextureMatrixEnabled = on;
}
void SetGlowParams(float *t, float *b)
{
mGlowTop.Set(t[0], t[1], t[2], t[3]);
mGlowBottom.Set(b[0], b[1], b[2], b[3]);
}
void SetSoftLightLevel(int llevel, int blendfactor = 0)
{
if (blendfactor == 0) mLightParms[3] = llevel / 255.f;
else mLightParms[3] = -1.f;
}
void SetNoSoftLightLevel()
{
mLightParms[3] = -1.f;
}
void SetGlowPlanes(const secplane_t &top, const secplane_t &bottom)
{
auto &tn = top.Normal();
auto &bn = bottom.Normal();
mGlowTopPlane.Set((float)tn.X, (float)tn.Y, (float)top.negiC, (float)top.fD());
mGlowBottomPlane.Set((float)bn.X, (float)bn.Y, (float)bottom.negiC, (float)bottom.fD());
}
void SetGradientPlanes(const secplane_t &top, const secplane_t &bottom)
{
auto &tn = top.Normal();
auto &bn = bottom.Normal();
mGradientTopPlane.Set((float)tn.X, (float)tn.Y, (float)top.negiC, (float)top.fD());
mGradientBottomPlane.Set((float)bn.X, (float)bn.Y, (float)bottom.negiC, (float)bottom.fD());
}
void SetSplitPlanes(const secplane_t &top, const secplane_t &bottom)
{
auto &tn = top.Normal();
auto &bn = bottom.Normal();
mSplitTopPlane.Set((float)tn.X, (float)tn.Y, (float)top.negiC, (float)top.fD());
mSplitBottomPlane.Set((float)bn.X, (float)bn.Y, (float)bottom.negiC, (float)bottom.fD());
}
void SetDynLight(float r, float g, float b)
{
mDynColor.Set(r, g, b, 0);
}
void SetObjectColor(PalEntry pe)
{
mObjectColor = pe;
}
void SetObjectColor2(PalEntry pe)
{
mObjectColor2 = pe;
}
void SetAddColor(PalEntry pe)
{
mAddColor = pe;
}
void SetFog(PalEntry c, float d)
{
const float LOG2E = 1.442692f; // = 1/log(2)
mFogColor = c;
if (d >= 0.0f) mLightParms[2] = d * (-LOG2E / 64000.f);
}
void SetLightParms(float f, float d)
{
mLightParms[1] = f;
mLightParms[0] = d;
}
PalEntry GetFogColor() const
{
return mFogColor;
}
void AlphaFunc(int func, float thresh)
{
if (func == Alpha_Greater) mAlphaThreshold = thresh;
else mAlphaThreshold = thresh - 0.001f;
}
void SetPlaneTextureRotation(GLSectorPlane *plane, FMaterial *texture)
{
if (hw_SetPlaneTextureRotation(plane, texture, mTextureMatrix))
{
EnableTextureMatrix(true);
}
}
void SetLightIndex(int index)
{
mLightIndex = index;
}
void SetRenderStyle(FRenderStyle rs)
{
mRenderStyle = rs;
}
void SetRenderStyle(ERenderStyle rs)
{
mRenderStyle = rs;
}
void SetDepthBias(float a, float b)
{
mBias.mFactor = a;
mBias.mUnits = b;
mBias.mChanged = true;
}
void ClearDepthBias()
{
mBias.mFactor = 0;
mBias.mUnits = 0;
mBias.mChanged = true;
}
void SetMaterial(FMaterial *mat, int clampmode, int translation, int overrideshader)
{
mMaterial.mMaterial = mat;
mMaterial.mClampMode = clampmode;
mMaterial.mTranslation = translation;
mMaterial.mOverrideShader = overrideshader;
mMaterial.mChanged = true;
}
void SetClipSplit(float bottom, float top)
{
mClipSplit[0] = bottom;
mClipSplit[1] = top;
}
void SetClipSplit(float *vals)
{
memcpy(mClipSplit, vals, 2 * sizeof(float));
}
void GetClipSplit(float *out)
{
memcpy(out, mClipSplit, 2 * sizeof(float));
}
void ClearClipSplit()
{
mClipSplit[0] = -1000000.f;
mClipSplit[1] = 1000000.f;
}
void SetVertexBuffer(IVertexBuffer *vb, int offset0, int offset1)
{
assert(vb);
mVertexBuffer = vb;
mVertexOffsets[0] = offset0;
mVertexOffsets[1] = offset1;
}
void SetIndexBuffer(IIndexBuffer *ib)
{
mIndexBuffer = ib;
}
template <class T> void SetVertexBuffer(T *buffer)
{
auto ptrs = buffer->GetBufferObjects();
SetVertexBuffer(ptrs.first, 0, 0);
SetIndexBuffer(ptrs.second);
}
void SetInterpolationFactor(float fac)
{
mInterpolationFactor = fac;
}
float GetInterpolationFactor()
{
return mInterpolationFactor;
}
// API-dependent render interface
// Draw commands
virtual void ClearScreen() = 0;
virtual void Draw(int dt, int index, int count, bool apply = true) = 0;
virtual void DrawIndexed(int dt, int index, int count, bool apply = true) = 0;
// Immediate render state change commands. These only change infrequently and should not clutter the render state.
virtual bool SetDepthClamp(bool on) = 0; // Deactivated only by skyboxes.
virtual void SetDepthMask(bool on) = 0; // Used by decals and indirectly by portal setup.
virtual void SetDepthFunc(int func) = 0; // Used by models, portals and mirror surfaces.
virtual void SetDepthRange(float min, float max) = 0; // Used by portal setup.
virtual void SetColorMask(bool r, bool g, bool b, bool a) = 0; // Used by portals.
virtual void EnableDrawBufferAttachments(bool on) = 0; // Used by fog boundary drawer.
virtual void SetStencil(int offs, int op, int flags=-1) = 0; // Used by portal setup and render hacks.
virtual void SetCulling(int mode) = 0; // Used by model drawer only.
virtual void EnableClipDistance(int num, bool state) = 0; // Use by sprite sorter for vertical splits.
virtual void Clear(int targets) = 0; // not used during normal rendering
virtual void EnableStencil(bool on) = 0; // always on for 3D, always off for 2D
virtual void SetScissor(int x, int y, int w, int h) = 0; // constant for 3D, changes for 2D
virtual void SetViewport(int x, int y, int w, int h) = 0; // constant for all 3D and all 2D
virtual void EnableDepthTest(bool on) = 0; // used by 2D, portals and render hacks.
virtual void EnableMultisampling(bool on) = 0; // only active for 2D
virtual void EnableLineSmooth(bool on) = 0; // constant setting for each 2D drawer operation
void SetColorMask(bool on)
{
SetColorMask(on, on, on, on);
}
};

View file

@ -0,0 +1,393 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2002-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 "a_sharedglobal.h"
#include "r_sky.h"
#include "r_state.h"
#include "r_utility.h"
#include "doomdata.h"
#include "g_levellocals.h"
#include "p_lnspec.h"
#include "hwrenderer/scene/hw_drawinfo.h"
#include "hwrenderer/scene/hw_drawstructs.h"
#include "hwrenderer/scene/hw_portal.h"
#include "hwrenderer/utility/hw_lighting.h"
#include "hwrenderer/textures/hw_material.h"
CVAR(Bool,gl_noskyboxes, false, 0)
//==========================================================================
//
// Set up the skyinfo struct
//
//==========================================================================
void GLSkyInfo::init(HWDrawInfo *di, int sky1, PalEntry FadeColor)
{
memset(this, 0, sizeof(*this));
if ((sky1 & PL_SKYFLAT) && (sky1 & (PL_SKYFLAT - 1)))
{
const line_t *l = &di->Level->lines[(sky1&(PL_SKYFLAT - 1)) - 1];
const side_t *s = l->sidedef[0];
int pos;
if (di->Level->flags & LEVEL_SWAPSKIES && s->GetTexture(side_t::bottom).isValid())
{
pos = side_t::bottom;
}
else
{
pos = side_t::top;
}
FTextureID texno = s->GetTexture(pos);
texture[0] = FMaterial::ValidateTexture(texno, false, true);
if (!texture[0] || !texture[0]->tex->isValid()) goto normalsky;
skytexno1 = texno;
x_offset[0] = s->GetTextureXOffset(pos) * (360.f/65536.f);
y_offset = s->GetTextureYOffset(pos);
mirrored = !l->args[2];
}
else
{
normalsky:
if (di->Level->flags&LEVEL_DOUBLESKY)
{
texture[1] = FMaterial::ValidateTexture(di->Level->skytexture1, false, true);
x_offset[1] = di->Level->hw_sky1pos;
doublesky = true;
}
if ((di->Level->flags&LEVEL_SWAPSKIES || (sky1 == PL_SKYFLAT) || (di->Level->flags&LEVEL_DOUBLESKY)) &&
di->Level->skytexture2 != di->Level->skytexture1) // If both skies are equal use the scroll offset of the first!
{
texture[0] = FMaterial::ValidateTexture(di->Level->skytexture2, false, true);
skytexno1 = di->Level->skytexture2;
sky2 = true;
x_offset[0] = di->Level->hw_sky2pos;
}
else if (!doublesky)
{
texture[0] = FMaterial::ValidateTexture(di->Level->skytexture1, false, true);
skytexno1 = di->Level->skytexture1;
x_offset[0] = di->Level->hw_sky1pos;
}
}
if (di->Level->skyfog > 0)
{
fadecolor = FadeColor;
fadecolor.a = 0;
}
else fadecolor = 0;
}
//==========================================================================
//
// Calculate sky texture for ceiling or floor
//
//==========================================================================
void GLWall::SkyPlane(HWDrawInfo *di, sector_t *sector, int plane, bool allowreflect)
{
int ptype = -1;
FSectorPortal *sportal = sector->ValidatePortal(plane);
if (sportal != nullptr && sportal->mFlags & PORTSF_INSKYBOX) sportal = nullptr; // no recursions, delete it here to simplify the following code
// Either a regular sky or a skybox with skyboxes disabled
if ((sportal == nullptr && sector->GetTexture(plane) == skyflatnum) || (gl_noskyboxes && sportal != nullptr && sportal->mType == PORTS_SKYVIEWPOINT))
{
GLSkyInfo skyinfo;
skyinfo.init(di, sector->sky, Colormap.FadeColor);
ptype = PORTALTYPE_SKY;
sky = &skyinfo;
PutPortal(di, ptype, plane);
}
else if (sportal != nullptr)
{
switch (sportal->mType)
{
case PORTS_STACKEDSECTORTHING:
case PORTS_PORTAL:
case PORTS_LINKEDPORTAL:
{
auto glport = sector->GetPortalGroup(plane);
if (glport != NULL)
{
if (sector->PortalBlocksView(plane)) return;
if (screen->instack[1 - plane]) return;
ptype = PORTALTYPE_SECTORSTACK;
portal = glport;
}
break;
}
case PORTS_SKYVIEWPOINT:
case PORTS_HORIZON:
case PORTS_PLANE:
ptype = PORTALTYPE_SKYBOX;
secportal = sportal;
break;
}
}
else if (allowreflect && sector->GetReflect(plane) > 0)
{
auto vpz = di->Viewpoint.Pos.Z;
if ((plane == sector_t::ceiling && vpz > sector->ceilingplane.fD()) ||
(plane == sector_t::floor && vpz < -sector->floorplane.fD())) return;
ptype = PORTALTYPE_PLANEMIRROR;
planemirror = plane == sector_t::ceiling ? &sector->ceilingplane : &sector->floorplane;
}
if (ptype != -1)
{
PutPortal(di, ptype, plane);
}
}
//==========================================================================
//
// Calculate sky texture for a line
//
//==========================================================================
void GLWall::SkyLine(HWDrawInfo *di, sector_t *fs, line_t *line)
{
FSectorPortal *secport = line->GetTransferredPortal();
GLSkyInfo skyinfo;
int ptype;
// JUSTHIT is used as an indicator that a skybox is in use.
// This is to avoid recursion
if (!gl_noskyboxes && secport && (secport->mSkybox == nullptr || !(secport->mFlags & PORTSF_INSKYBOX)))
{
ptype = PORTALTYPE_SKYBOX;
secportal = secport;
}
else
{
skyinfo.init(di, fs->sky, Colormap.FadeColor);
ptype = PORTALTYPE_SKY;
sky = &skyinfo;
}
ztop[0] = zceil[0];
ztop[1] = zceil[1];
zbottom[0] = zfloor[0];
zbottom[1] = zfloor[1];
PutPortal(di, ptype, -1);
}
//==========================================================================
//
// Skies on one sided walls
//
//==========================================================================
void GLWall::SkyNormal(HWDrawInfo *di, sector_t * fs,vertex_t * v1,vertex_t * v2)
{
ztop[0]=ztop[1]=32768.0f;
zbottom[0]=zceil[0];
zbottom[1]=zceil[1];
SkyPlane(di, fs, sector_t::ceiling, true);
ztop[0]=zfloor[0];
ztop[1]=zfloor[1];
zbottom[0]=zbottom[1]=-32768.0f;
SkyPlane(di, fs, sector_t::floor, true);
}
//==========================================================================
//
// Upper Skies on two sided walls
//
//==========================================================================
void GLWall::SkyTop(HWDrawInfo *di, seg_t * seg,sector_t * fs,sector_t * bs,vertex_t * v1,vertex_t * v2)
{
if (fs->GetTexture(sector_t::ceiling)==skyflatnum)
{
if (bs->special == GLSector_NoSkyDraw) return;
if (bs->GetTexture(sector_t::ceiling)==skyflatnum)
{
// if the back sector is closed the sky must be drawn!
if (bs->ceilingplane.ZatPoint(v1) > bs->floorplane.ZatPoint(v1) ||
bs->ceilingplane.ZatPoint(v2) > bs->floorplane.ZatPoint(v2) || bs->transdoor)
return;
// one more check for some ugly transparent door hacks
if (!bs->floorplane.isSlope() && !fs->floorplane.isSlope())
{
if (bs->GetPlaneTexZ(sector_t::floor)==fs->GetPlaneTexZ(sector_t::floor)+1.)
{
FTexture * tex = TexMan.GetTexture(seg->sidedef->GetTexture(side_t::bottom), true);
if (!tex || !tex->isValid()) return;
// very, very, very ugly special case (See Icarus MAP14)
// It is VERY important that this is only done for a floor height difference of 1
// or it will cause glitches elsewhere.
tex = TexMan.GetTexture(seg->sidedef->GetTexture(side_t::mid), true);
if (tex != NULL && !(seg->linedef->flags & ML_DONTPEGTOP) &&
seg->sidedef->GetTextureYOffset(side_t::mid) > 0)
{
ztop[0]=ztop[1]=32768.0f;
zbottom[0]=zbottom[1]=
bs->ceilingplane.ZatPoint(v2) + seg->sidedef->GetTextureYOffset(side_t::mid);
SkyPlane(di, fs, sector_t::ceiling, false);
return;
}
}
}
}
ztop[0]=ztop[1]=32768.0f;
FTexture * tex = TexMan.GetTexture(seg->sidedef->GetTexture(side_t::top), true);
if (bs->GetTexture(sector_t::ceiling) != skyflatnum)
{
zbottom[0]=zceil[0];
zbottom[1]=zceil[1];
}
else
{
zbottom[0] = bs->ceilingplane.ZatPoint(v1);
zbottom[1] = bs->ceilingplane.ZatPoint(v2);
flags|=GLWF_SKYHACK; // mid textures on such lines need special treatment!
}
}
else
{
float frontreflect = fs->GetReflect(sector_t::ceiling);
if (frontreflect > 0)
{
float backreflect = bs->GetReflect(sector_t::ceiling);
if (backreflect > 0 && bs->ceilingplane.fD() == fs->ceilingplane.fD() && !bs->isClosed())
{
// Don't add intra-portal line to the portal.
return;
}
}
else
{
int type = fs->GetPortalType(sector_t::ceiling);
if (type == PORTS_STACKEDSECTORTHING || type == PORTS_PORTAL || type == PORTS_LINKEDPORTAL)
{
auto pfront = fs->GetPortalGroup(sector_t::ceiling);
auto pback = bs->GetPortalGroup(sector_t::ceiling);
if (pfront == NULL || fs->PortalBlocksView(sector_t::ceiling)) return;
if (pfront == pback && !bs->PortalBlocksView(sector_t::ceiling)) return;
}
}
// stacked sectors
ztop[0] = ztop[1] = 32768.0f;
zbottom[0] = fs->ceilingplane.ZatPoint(v1);
zbottom[1] = fs->ceilingplane.ZatPoint(v2);
}
SkyPlane(di, fs, sector_t::ceiling, true);
}
//==========================================================================
//
// Lower Skies on two sided walls
//
//==========================================================================
void GLWall::SkyBottom(HWDrawInfo *di, seg_t * seg,sector_t * fs,sector_t * bs,vertex_t * v1,vertex_t * v2)
{
if (fs->GetTexture(sector_t::floor)==skyflatnum)
{
if (bs->special == GLSector_NoSkyDraw) return;
FTexture * tex = TexMan.GetTexture(seg->sidedef->GetTexture(side_t::bottom), true);
// For lower skies the normal logic only applies to walls with no lower texture.
if (!tex->isValid())
{
if (bs->GetTexture(sector_t::floor)==skyflatnum)
{
// if the back sector is closed the sky must be drawn!
if (bs->ceilingplane.ZatPoint(v1) > bs->floorplane.ZatPoint(v1) ||
bs->ceilingplane.ZatPoint(v2) > bs->floorplane.ZatPoint(v2))
return;
}
else
{
// Special hack for Vrack2b
if (bs->floorplane.ZatPoint(di->Viewpoint.Pos) > di->Viewpoint.Pos.Z) return;
}
}
zbottom[0]=zbottom[1]=-32768.0f;
if ((tex && tex->isValid()) || bs->GetTexture(sector_t::floor) != skyflatnum)
{
ztop[0] = zfloor[0];
ztop[1] = zfloor[1];
}
else
{
ztop[0] = bs->floorplane.ZatPoint(v1);
ztop[1] = bs->floorplane.ZatPoint(v2);
flags |= GLWF_SKYHACK; // mid textures on such lines need special treatment!
}
}
else
{
float frontreflect = fs->GetReflect(sector_t::floor);
if (frontreflect > 0)
{
float backreflect = bs->GetReflect(sector_t::floor);
if (backreflect > 0 && bs->floorplane.fD() == fs->floorplane.fD() && !bs->isClosed())
{
// Don't add intra-portal line to the portal.
return;
}
}
else
{
int type = fs->GetPortalType(sector_t::floor);
if (type == PORTS_STACKEDSECTORTHING || type == PORTS_PORTAL || type == PORTS_LINKEDPORTAL)
{
auto pfront = fs->GetPortalGroup(sector_t::floor);
auto pback = bs->GetPortalGroup(sector_t::floor);
if (pfront == NULL || fs->PortalBlocksView(sector_t::floor)) return;
if (pfront == pback && !bs->PortalBlocksView(sector_t::floor)) return;
}
}
// stacked sectors
zbottom[0]=zbottom[1]=-32768.0f;
ztop[0] = fs->floorplane.ZatPoint(v1);
ztop[1] = fs->floorplane.ZatPoint(v2);
}
SkyPlane(di, fs, sector_t::floor, true);
}

View file

@ -0,0 +1,327 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2003-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 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/
//
//--------------------------------------------------------------------------
//
/*
**
** Draws the sky. Loosely based on the JDoom sky and the ZDoomGL 0.66.2 sky.
**
** for FSkyVertexBuffer::SkyVertex only:
**---------------------------------------------------------------------------
** Copyright 2003 Tim Stump
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "doomtype.h"
#include "g_level.h"
#include "w_wad.h"
#include "r_state.h"
#include "r_utility.h"
#include "g_levellocals.h"
#include "r_sky.h"
#include "cmdlib.h"
#include "textures/skyboxtexture.h"
#include "hwrenderer/textures/hw_material.h"
#include "hw_skydome.h"
#include "hw_renderstate.h"
#include "hw_drawinfo.h"
#include "hwrenderer/data/buffers.h"
//-----------------------------------------------------------------------------
//
// Shamelessly lifted from Doomsday (written by Jaakko Keränen)
// also shamelessly lifted from ZDoomGL! ;)
//
//-----------------------------------------------------------------------------
EXTERN_CVAR(Float, skyoffset)
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
FSkyVertexBuffer::FSkyVertexBuffer()
{
CreateDome();
mVertexBuffer = screen->CreateVertexBuffer();
static const FVertexBufferAttribute format[] = {
{ 0, VATTR_VERTEX, VFmt_Float3, (int)myoffsetof(FSkyVertex, x) },
{ 0, VATTR_TEXCOORD, VFmt_Float2, (int)myoffsetof(FSkyVertex, u) },
{ 0, VATTR_COLOR, VFmt_Byte4, (int)myoffsetof(FSkyVertex, color) }
};
mVertexBuffer->SetFormat(1, 3, sizeof(FSkyVertex), format);
mVertexBuffer->SetData(mVertices.Size() * sizeof(FSkyVertex), &mVertices[0], true);
}
FSkyVertexBuffer::~FSkyVertexBuffer()
{
delete mVertexBuffer;
}
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
void FSkyVertexBuffer::SkyVertex(int r, int c, bool zflip)
{
static const FAngle maxSideAngle = 60.f;
static const float scale = 10000.;
FAngle topAngle = (c / (float)mColumns * 360.f);
FAngle sideAngle = maxSideAngle * float(mRows - r) / float(mRows);
float height = sideAngle.Sin();
float realRadius = scale * sideAngle.Cos();
FVector2 pos = topAngle.ToVector(realRadius);
float z = (!zflip) ? scale * height : -scale * height;
FSkyVertex vert;
vert.color = r == 0 ? 0xffffff : 0xffffffff;
// And the texture coordinates.
if (!zflip) // Flipped Y is for the lower hemisphere.
{
vert.u = (-c / (float)mColumns);
vert.v = (r / (float)mRows);
}
else
{
vert.u = (-c / (float)mColumns);
vert.v = 1.0f + ((mRows - r) / (float)mRows);
}
if (r != 4) z += 300;
// And finally the vertex.
vert.x = -pos.X; // Doom mirrors the sky vertically!
vert.y = z - 1.f;
vert.z = pos.Y;
mVertices.Push(vert);
}
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
void FSkyVertexBuffer::CreateSkyHemisphere(int hemi)
{
int r, c;
bool zflip = !!(hemi & SKYHEMI_LOWER);
mPrimStart.Push(mVertices.Size());
for (c = 0; c < mColumns; c++)
{
SkyVertex(1, c, zflip);
}
// The total number of triangles per hemisphere can be calculated
// as follows: rows * columns * 2 + 2 (for the top cap).
for (r = 0; r < mRows; r++)
{
mPrimStart.Push(mVertices.Size());
for (c = 0; c <= mColumns; c++)
{
SkyVertex(r + zflip, c, zflip);
SkyVertex(r + 1 - zflip, c, zflip);
}
}
}
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
void FSkyVertexBuffer::CreateDome()
{
// the first thing we put into the buffer is the fog layer object which is just 4 triangles around the viewpoint.
mVertices.Reserve(12);
mVertices[0].Set(1.0f, 1.0f, -1.0f);
mVertices[1].Set(1.0f, -1.0f, -1.0f);
mVertices[2].Set(-1.0f, 0.0f, -1.0f);
mVertices[3].Set(1.0f, 1.0f, -1.0f);
mVertices[4].Set(1.0f, -1.0f, -1.0f);
mVertices[5].Set(0.0f, 0.0f, 1.0f);
mVertices[6].Set(-1.0f, 0.0f, -1.0f);
mVertices[7].Set(1.0f, 1.0f, -1.0f);
mVertices[8].Set(0.0f, 0.0f, 1.0f);
mVertices[9].Set(1.0f, -1.0f, -1.0f);
mVertices[10].Set(-1.0f, 0.0f, -1.0f);
mVertices[11].Set(0.0f, 0.0f, 1.0f);
mColumns = 128;
mRows = 4;
CreateSkyHemisphere(SKYHEMI_UPPER);
CreateSkyHemisphere(SKYHEMI_LOWER);
mPrimStart.Push(mVertices.Size());
mSideStart = mVertices.Size();
mFaceStart[0] = mSideStart + 10;
mFaceStart[1] = mFaceStart[0] + 4;
mFaceStart[2] = mFaceStart[1] + 4;
mFaceStart[3] = mFaceStart[2] + 4;
mFaceStart[4] = mFaceStart[3] + 4;
mFaceStart[5] = mFaceStart[4] + 4;
mFaceStart[6] = mFaceStart[5] + 4;
mVertices.Reserve(10 + 7*4);
FSkyVertex *ptr = &mVertices[mSideStart];
// all sides
ptr[0].SetXYZ(128.f, 128.f, -128.f, 0, 0);
ptr[1].SetXYZ(128.f, -128.f, -128.f, 0, 1);
ptr[2].SetXYZ(-128.f, 128.f, -128.f, 0.25f, 0);
ptr[3].SetXYZ(-128.f, -128.f, -128.f, 0.25f, 1);
ptr[4].SetXYZ(-128.f, 128.f, 128.f, 0.5f, 0);
ptr[5].SetXYZ(-128.f, -128.f, 128.f, 0.5f, 1);
ptr[6].SetXYZ(128.f, 128.f, 128.f, 0.75f, 0);
ptr[7].SetXYZ(128.f, -128.f, 128.f, 0.75f, 1);
ptr[8].SetXYZ(128.f, 128.f, -128.f, 1, 0);
ptr[9].SetXYZ(128.f, -128.f, -128.f, 1, 1);
// north face
ptr[10].SetXYZ(128.f, 128.f, -128.f, 0, 0);
ptr[11].SetXYZ(-128.f, 128.f, -128.f, 1, 0);
ptr[12].SetXYZ(128.f, -128.f, -128.f, 0, 1);
ptr[13].SetXYZ(-128.f, -128.f, -128.f, 1, 1);
// east face
ptr[14].SetXYZ(-128.f, 128.f, -128.f, 0, 0);
ptr[15].SetXYZ(-128.f, 128.f, 128.f, 1, 0);
ptr[16].SetXYZ(-128.f, -128.f, -128.f, 0, 1);
ptr[17].SetXYZ(-128.f, -128.f, 128.f, 1, 1);
// south face
ptr[18].SetXYZ(-128.f, 128.f, 128.f, 0, 0);
ptr[19].SetXYZ(128.f, 128.f, 128.f, 1, 0);
ptr[20].SetXYZ(-128.f, -128.f, 128.f, 0, 1);
ptr[21].SetXYZ(128.f, -128.f, 128.f, 1, 1);
// west face
ptr[22].SetXYZ(128.f, 128.f, 128.f, 0, 0);
ptr[23].SetXYZ(128.f, 128.f, -128.f, 1, 0);
ptr[24].SetXYZ(128.f, -128.f, 128.f, 0, 1);
ptr[25].SetXYZ(128.f, -128.f, -128.f, 1, 1);
// bottom face
ptr[26].SetXYZ(128.f, -128.f, -128.f, 0, 0);
ptr[27].SetXYZ(-128.f, -128.f, -128.f, 1, 0);
ptr[28].SetXYZ(128.f, -128.f, 128.f, 0, 1);
ptr[29].SetXYZ(-128.f, -128.f, 128.f, 1, 1);
// top face
ptr[30].SetXYZ(128.f, 128.f, -128.f, 0, 0);
ptr[31].SetXYZ(-128.f, 128.f, -128.f, 1, 0);
ptr[32].SetXYZ(128.f, 128.f, 128.f, 0, 1);
ptr[33].SetXYZ(-128.f, 128.f, 128.f, 1, 1);
// top face flipped
ptr[34].SetXYZ(128.f, 128.f, -128.f, 0, 1);
ptr[35].SetXYZ(-128.f, 128.f, -128.f, 1, 1);
ptr[36].SetXYZ(128.f, 128.f, 128.f, 0, 0);
ptr[37].SetXYZ(-128.f, 128.f, 128.f, 1, 0);
}
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
void FSkyVertexBuffer::SetupMatrices(HWDrawInfo *di, FMaterial *tex, float x_offset, float y_offset, bool mirror, int mode, VSMatrix &modelMatrix, VSMatrix &textureMatrix)
{
int texw = tex->TextureWidth();
int texh = tex->TextureHeight();
modelMatrix.loadIdentity();
modelMatrix.rotate(-180.0f + x_offset, 0.f, 1.f, 0.f);
float xscale = texw < 1024.f ? floor(1024.f / float(texw)) : 1.f;
float yscale = 1.f;
if (texh <= 128 && (di->Level->flags & LEVEL_FORCETILEDSKY))
{
modelMatrix.translate(0.f, (-40 + tex->tex->GetSkyOffset() + skyoffset)*skyoffsetfactor, 0.f);
modelMatrix.scale(1.f, 1.2f * 1.17f, 1.f);
yscale = 240.f / texh;
}
else if (texh < 128)
{
// smaller sky textures must be tiled. We restrict it to 128 sky pixels, though
modelMatrix.translate(0.f, -1250.f, 0.f);
modelMatrix.scale(1.f, 128 / 230.f, 1.f);
yscale = float(128 / texh); // intentionally left as integer.
}
else if (texh < 200)
{
modelMatrix.translate(0.f, -1250.f, 0.f);
modelMatrix.scale(1.f, texh / 230.f, 1.f);
}
else if (texh <= 240)
{
modelMatrix.translate(0.f, (200 - texh + tex->tex->GetSkyOffset() + skyoffset)*skyoffsetfactor, 0.f);
modelMatrix.scale(1.f, 1.f + ((texh - 200.f) / 200.f) * 1.17f, 1.f);
}
else
{
modelMatrix.translate(0.f, (-40 + tex->tex->GetSkyOffset() + skyoffset)*skyoffsetfactor, 0.f);
modelMatrix.scale(1.f, 1.2f * 1.17f, 1.f);
yscale = 240.f / texh;
}
textureMatrix.loadIdentity();
textureMatrix.scale(mirror ? -xscale : xscale, yscale, 1.f);
textureMatrix.translate(1.f, y_offset / texh, 1.f);
}

View file

@ -0,0 +1,84 @@
#pragma once
#include "v_palette.h"
#include "r_data/matrix.h"
class FMaterial;
class FRenderState;
class IVertexBuffer;
struct HWSkyPortal;
struct HWDrawInfo;
struct FSkyVertex
{
float x, y, z, u, v;
PalEntry color;
void Set(float xx, float zz, float yy, float uu=0, float vv=0, PalEntry col=0xffffffff)
{
x = xx;
z = zz;
y = yy;
u = uu;
v = vv;
color = col;
}
void SetXYZ(float xx, float yy, float zz, float uu = 0, float vv = 0, PalEntry col = 0xffffffff)
{
x = xx;
y = yy;
z = zz;
u = uu;
v = vv;
color = col;
}
};
class FSkyVertexBuffer
{
friend struct HWSkyPortal;
public:
static const int SKYHEMI_UPPER = 1;
static const int SKYHEMI_LOWER = 2;
enum
{
SKYMODE_MAINLAYER = 0,
SKYMODE_SECONDLAYER = 1,
SKYMODE_FOGLAYER = 2
};
IVertexBuffer *mVertexBuffer;
TArray<FSkyVertex> mVertices;
TArray<unsigned int> mPrimStart;
int mRows, mColumns;
// indices for sky cubemap faces
int mFaceStart[7];
int mSideStart;
void SkyVertex(int r, int c, bool yflip);
void CreateSkyHemisphere(int hemi);
void CreateDome();
public:
FSkyVertexBuffer();
~FSkyVertexBuffer();
void SetupMatrices(HWDrawInfo *di, FMaterial *tex, float x_offset, float y_offset, bool mirror, int mode, VSMatrix &modelmatrix, VSMatrix &textureMatrix);
std::pair<IVertexBuffer *, IIndexBuffer *> GetBufferObjects() const
{
return std::make_pair(mVertexBuffer, nullptr);
}
int FaceStart(int i)
{
if (i >= 0 && i < 7) return mFaceStart[i];
else return mSideStart;
}
};

View file

@ -0,0 +1,222 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2003-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 "doomtype.h"
#include "g_level.h"
#include "w_wad.h"
#include "r_state.h"
#include "r_utility.h"
#include "g_levellocals.h"
#include "hwrenderer/scene/hw_skydome.h"
#include "hwrenderer/scene/hw_portal.h"
#include "hwrenderer/scene/hw_renderstate.h"
#include "textures/skyboxtexture.h"
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
void HWSkyPortal::RenderRow(HWDrawInfo *di, FRenderState &state, EDrawType prim, int row, bool apply)
{
state.Draw(prim, vertexBuffer->mPrimStart[row], vertexBuffer->mPrimStart[row + 1] - vertexBuffer->mPrimStart[row]);
}
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
void HWSkyPortal::RenderDome(HWDrawInfo *di, FRenderState &state, FMaterial * tex, float x_offset, float y_offset, bool mirror, int mode)
{
if (tex)
{
state.SetMaterial(tex, CLAMP_NONE, 0, -1);
state.EnableModelMatrix(true);
state.EnableTextureMatrix(true);
vertexBuffer->SetupMatrices(di, tex, x_offset, y_offset, mirror, mode, state.mModelMatrix, state.mTextureMatrix);
}
int rc = vertexBuffer->mRows + 1;
// The caps only get drawn for the main layer but not for the overlay.
if (mode == FSkyVertexBuffer::SKYMODE_MAINLAYER && tex != NULL)
{
PalEntry pe = tex->tex->GetSkyCapColor(false);
state.SetObjectColor(pe);
state.EnableTexture(false);
RenderRow(di, state, DT_TriangleFan, 0);
pe = tex->tex->GetSkyCapColor(true);
state.SetObjectColor(pe);
RenderRow(di, state, DT_TriangleFan, rc);
state.EnableTexture(true);
}
state.SetObjectColor(0xffffffff);
for (int i = 1; i <= vertexBuffer->mRows; i++)
{
RenderRow(di, state, DT_TriangleStrip, i, i == 1);
RenderRow(di, state, DT_TriangleStrip, rc + i, false);
}
state.EnableTextureMatrix(false);
state.EnableModelMatrix(false);
}
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
void HWSkyPortal::RenderBox(HWDrawInfo *di, FRenderState &state, FTextureID texno, FMaterial * gltex, float x_offset, bool sky2)
{
FSkyBox * sb = static_cast<FSkyBox*>(gltex->tex);
int faces;
FMaterial * tex;
state.EnableModelMatrix(true);
state.mModelMatrix.loadIdentity();
if (!sky2)
state.mModelMatrix.rotate(-180.0f+x_offset, di->Level->info->skyrotatevector.X, di->Level->info->skyrotatevector.Z, di->Level->info->skyrotatevector.Y);
else
state.mModelMatrix.rotate(-180.0f+x_offset, di->Level->info->skyrotatevector2.X, di->Level->info->skyrotatevector2.Z, di->Level->info->skyrotatevector2.Y);
if (sb->faces[5])
{
faces=4;
// north
tex = FMaterial::ValidateTexture(sb->faces[0], false);
state.SetMaterial(tex, CLAMP_XY, 0, -1);
state.Draw(DT_TriangleStrip, vertexBuffer->FaceStart(0), 4);
// east
tex = FMaterial::ValidateTexture(sb->faces[1], false);
state.SetMaterial(tex, CLAMP_XY, 0, -1);
state.Draw(DT_TriangleStrip, vertexBuffer->FaceStart(1), 4);
// south
tex = FMaterial::ValidateTexture(sb->faces[2], false);
state.SetMaterial(tex, CLAMP_XY, 0, -1);
state.Draw(DT_TriangleStrip, vertexBuffer->FaceStart(2), 4);
// west
tex = FMaterial::ValidateTexture(sb->faces[3], false);
state.SetMaterial(tex, CLAMP_XY, 0, -1);
state.Draw(DT_TriangleStrip, vertexBuffer->FaceStart(3), 4);
}
else
{
faces=1;
tex = FMaterial::ValidateTexture(sb->faces[0], false);
state.SetMaterial(tex, CLAMP_XY, 0, -1);
state.Draw(DT_TriangleStrip, vertexBuffer->FaceStart(-1), 10);
}
// top
tex = FMaterial::ValidateTexture(sb->faces[faces], false);
state.SetMaterial(tex, CLAMP_XY, 0, -1);
state.Draw(DT_TriangleStrip, vertexBuffer->FaceStart(sb->fliptop ? 6 : 5), 4);
// bottom
tex = FMaterial::ValidateTexture(sb->faces[faces+1], false);
state.SetMaterial(tex, CLAMP_XY, 0, -1);
state.Draw(DT_TriangleStrip, vertexBuffer->FaceStart(4), 4);
state.EnableModelMatrix(false);
}
//-----------------------------------------------------------------------------
//
//
//
//-----------------------------------------------------------------------------
void HWSkyPortal::DrawContents(HWDrawInfo *di, FRenderState &state)
{
bool drawBoth = false;
auto &vp = di->Viewpoint;
// We have no use for Doom lighting special handling here, so disable it for this function.
auto oldlightmode = di->lightmode;
if (di->isSoftwareLighting())
{
di->SetFallbackLightMode();
state.SetNoSoftLightLevel();
}
state.ResetColor();
state.EnableFog(false);
state.AlphaFunc(Alpha_GEqual, 0.f);
state.SetRenderStyle(STYLE_Translucent);
bool oldClamp = state.SetDepthClamp(true);
di->SetupView(state, 0, 0, 0, !!(mState->MirrorFlag & 1), !!(mState->PlaneMirrorFlag & 1));
state.SetVertexBuffer(vertexBuffer);
if (origin->texture[0] && origin->texture[0]->tex->isSkybox())
{
RenderBox(di, state, origin->skytexno1, origin->texture[0], origin->x_offset[0], origin->sky2);
}
else
{
if (origin->texture[0]==origin->texture[1] && origin->doublesky) origin->doublesky=false;
if (origin->texture[0])
{
state.SetTextureMode(TM_OPAQUE);
RenderDome(di, state, origin->texture[0], origin->x_offset[0], origin->y_offset, origin->mirrored, FSkyVertexBuffer::SKYMODE_MAINLAYER);
state.SetTextureMode(TM_NORMAL);
}
state.AlphaFunc(Alpha_Greater, 0.f);
if (origin->doublesky && origin->texture[1])
{
RenderDome(di, state, origin->texture[1], origin->x_offset[1], origin->y_offset, false, FSkyVertexBuffer::SKYMODE_SECONDLAYER);
}
if (di->Level->skyfog>0 && !di->isFullbrightScene() && (origin->fadecolor & 0xffffff) != 0)
{
PalEntry FadeColor = origin->fadecolor;
FadeColor.a = clamp<int>(di->Level->skyfog, 0, 255);
state.EnableTexture(false);
state.SetObjectColor(FadeColor);
state.Draw(DT_Triangles, 0, 12);
state.EnableTexture(true);
state.SetObjectColor(0xffffffff);
}
}
di->lightmode = oldlightmode;
state.SetDepthClamp(oldClamp);
}
const char *HWSkyPortal::GetName() { return "Sky"; }

View file

@ -0,0 +1,193 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2002-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_light.cpp
** Light level / fog management / dynamic lights
**
*/
#include "c_dispatch.h"
#include "p_local.h"
#include "p_effect.h"
#include "g_level.h"
#include "g_levellocals.h"
#include "actorinlines.h"
#include "hwrenderer/dynlights/hw_dynlightdata.h"
#include "hwrenderer/dynlights/hw_shadowmap.h"
#include "hwrenderer/scene/hw_drawinfo.h"
#include "r_data/models/models.h"
template<class T>
T smoothstep(const T edge0, const T edge1, const T x)
{
auto t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
return t * t * (3.0 - 2.0 * t);
}
//==========================================================================
//
// Sets a single light value from all dynamic lights affecting the specified location
//
//==========================================================================
void HWDrawInfo::GetDynSpriteLight(AActor *self, float x, float y, float z, FLightNode *node, int portalgroup, float *out)
{
FDynamicLight *light;
float frac, lr, lg, lb;
float radius;
out[0] = out[1] = out[2] = 0.f;
// Go through both light lists
while (node)
{
light=node->lightsource;
if (light->ShouldLightActor(self))
{
float dist;
FVector3 L;
// This is a performance critical section of code where we cannot afford to let the compiler decide whether to inline the function or not.
// This will do the calculations explicitly rather than calling one of AActor's utility functions.
if (Level->Displacements.size > 0)
{
int fromgroup = light->Sector->PortalGroup;
int togroup = portalgroup;
if (fromgroup == togroup || fromgroup == 0 || togroup == 0) goto direct;
DVector2 offset = Level->Displacements.getOffset(fromgroup, togroup);
L = FVector3(x - (float)(light->X() + offset.X), y - (float)(light->Y() + offset.Y), z - (float)light->Z());
}
else
{
direct:
L = FVector3(x - (float)light->X(), y - (float)light->Y(), z - (float)light->Z());
}
dist = (float)L.LengthSquared();
radius = light->GetRadius();
if (dist < radius * radius)
{
dist = sqrtf(dist); // only calculate the square root if we really need it.
frac = 1.0f - (dist / radius);
if (light->IsSpot())
{
L *= -1.0f / dist;
DAngle negPitch = -*light->pPitch;
DAngle Angle = light->target->Angles.Yaw;
double xyLen = negPitch.Cos();
double spotDirX = -Angle.Cos() * xyLen;
double spotDirY = -Angle.Sin() * xyLen;
double spotDirZ = -negPitch.Sin();
double cosDir = L.X * spotDirX + L.Y * spotDirY + L.Z * spotDirZ;
frac *= (float)smoothstep(light->pSpotOuterAngle->Cos(), light->pSpotInnerAngle->Cos(), cosDir);
}
if (frac > 0 && (!light->shadowmapped || screen->mShadowMap.ShadowTest(light, { x, y, z })))
{
lr = light->GetRed() / 255.0f;
lg = light->GetGreen() / 255.0f;
lb = light->GetBlue() / 255.0f;
if (light->IsSubtractive())
{
float bright = (float)FVector3(lr, lg, lb).Length();
FVector3 lightColor(lr, lg, lb);
lr = (bright - lr) * -1;
lg = (bright - lg) * -1;
lb = (bright - lb) * -1;
}
out[0] += lr * frac;
out[1] += lg * frac;
out[2] += lb * frac;
}
}
}
node = node->nextLight;
}
}
void HWDrawInfo::GetDynSpriteLight(AActor *thing, particle_t *particle, float *out)
{
if (thing != NULL)
{
GetDynSpriteLight(thing, (float)thing->X(), (float)thing->Y(), (float)thing->Center(), thing->section->lighthead, thing->Sector->PortalGroup, out);
}
else if (particle != NULL)
{
GetDynSpriteLight(NULL, (float)particle->Pos.X, (float)particle->Pos.Y, (float)particle->Pos.Z, particle->subsector->section->lighthead, particle->subsector->sector->PortalGroup, out);
}
}
// static so that we build up a reserve (memory allocations stop)
// For multithread processing each worker thread needs its own copy, though.
static thread_local TArray<FDynamicLight*> addedLightsArray;
void hw_GetDynModelLight(AActor *self, FDynLightData &modellightdata)
{
modellightdata.Clear();
if (self)
{
auto &addedLights = addedLightsArray; // avoid going through the thread local storage for each use.
addedLights.Clear();
float x = (float)self->X();
float y = (float)self->Y();
float z = (float)self->Center();
float radiusSquared = (float)(self->renderradius * self->renderradius);
dl_validcount++;
BSPWalkCircle(self->Level, x, y, radiusSquared, [&](subsector_t *subsector) // Iterate through all subsectors potentially touched by actor
{
auto section = subsector->section;
if (section->validcount == dl_validcount) return; // already done from a previous subsector.
FLightNode * node = section->lighthead;
while (node) // check all lights touching a subsector
{
FDynamicLight *light = node->lightsource;
if (light->ShouldLightActor(self))
{
int group = subsector->sector->PortalGroup;
DVector3 pos = light->PosRelative(group);
float radius = (float)(light->GetRadius() + self->renderradius);
double dx = pos.X - x;
double dy = pos.Y - y;
double dz = pos.Z - z;
double distSquared = dx * dx + dy * dy + dz * dz;
if (distSquared < radius * radius) // Light and actor touches
{
if (std::find(addedLights.begin(), addedLights.end(), light) == addedLights.end()) // Check if we already added this light from a different subsector
{
modellightdata.AddLightToList(group, light, true);
addedLights.Push(light);
}
}
}
node = node->nextLight;
}
});
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,31 @@
#pragma once
#include "r_data/matrix.h"
#include "r_utility.h"
struct HWViewpointUniforms
{
VSMatrix mProjectionMatrix;
VSMatrix mViewMatrix;
VSMatrix mNormalViewMatrix;
FVector4 mCameraPos;
FVector4 mClipLine;
float mGlobVis = 1.f;
int mPalLightLevels = 0;
int mViewHeight = 0;
float mClipHeight = 0.f;
float mClipHeightDirection = 0.f;
int mShadowmapFilter = 1;
void CalcDependencies()
{
mNormalViewMatrix.computeNormalMatrix(mViewMatrix);
}
void SetDefaults();
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,275 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2006-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 "r_defs.h"
#include "hwrenderer/data/flatvertices.h"
#include "hwrenderer/scene/hw_drawinfo.h"
#include "hwrenderer/scene/hw_drawstructs.h"
EXTERN_CVAR(Bool, gl_seamless)
//==========================================================================
//
// Split upper edge of wall
//
//==========================================================================
void GLWall::SplitUpperEdge(FFlatVertex *&ptr)
{
side_t *sidedef = seg->sidedef;
float polyw = glseg.fracright - glseg.fracleft;
float facu = (tcs[UPRGT].u - tcs[UPLFT].u) / polyw;
float facv = (tcs[UPRGT].v - tcs[UPLFT].v) / polyw;
float fact = (ztop[1] - ztop[0]) / polyw;
float facc = (zceil[1] - zceil[0]) / polyw;
float facf = (zfloor[1] - zfloor[0]) / polyw;
for (int i = 0; i < sidedef->numsegs - 1; i++)
{
seg_t *cseg = sidedef->segs[i];
float sidefrac = cseg->sidefrac;
if (sidefrac <= glseg.fracleft) continue;
if (sidefrac >= glseg.fracright) return;
float fracfac = sidefrac - glseg.fracleft;
ptr->x = cseg->v2->fX();
ptr->y = cseg->v2->fY();
ptr->z = ztop[0] + fact * fracfac;
ptr->u = tcs[UPLFT].u + facu * fracfac;
ptr->v = tcs[UPLFT].v + facv * fracfac;
ptr++;
}
}
//==========================================================================
//
// Split upper edge of wall
//
//==========================================================================
void GLWall::SplitLowerEdge(FFlatVertex *&ptr)
{
side_t *sidedef = seg->sidedef;
float polyw = glseg.fracright - glseg.fracleft;
float facu = (tcs[LORGT].u - tcs[LOLFT].u) / polyw;
float facv = (tcs[LORGT].v - tcs[LOLFT].v) / polyw;
float facb = (zbottom[1] - zbottom[0]) / polyw;
float facc = (zceil[1] - zceil[0]) / polyw;
float facf = (zfloor[1] - zfloor[0]) / polyw;
for (int i = sidedef->numsegs - 2; i >= 0; i--)
{
seg_t *cseg = sidedef->segs[i];
float sidefrac = cseg->sidefrac;
if (sidefrac >= glseg.fracright) continue;
if (sidefrac <= glseg.fracleft) return;
float fracfac = sidefrac - glseg.fracleft;
ptr->x = cseg->v2->fX();
ptr->y = cseg->v2->fY();
ptr->z = zbottom[0] + facb * fracfac;
ptr->u = tcs[LOLFT].u + facu * fracfac;
ptr->v = tcs[LOLFT].v + facv * fracfac;
ptr++;
}
}
//==========================================================================
//
// Split left edge of wall
//
//==========================================================================
void GLWall::SplitLeftEdge(FFlatVertex *&ptr)
{
if (vertexes[0] == NULL) return;
vertex_t * vi = vertexes[0];
if (vi->numheights)
{
int i = 0;
float polyh1 = ztop[0] - zbottom[0];
float factv1 = polyh1 ? (tcs[UPLFT].v - tcs[LOLFT].v) / polyh1 : 0;
float factu1 = polyh1 ? (tcs[UPLFT].u - tcs[LOLFT].u) / polyh1 : 0;
while (i<vi->numheights && vi->heightlist[i] <= zbottom[0]) i++;
while (i<vi->numheights && vi->heightlist[i] < ztop[0])
{
ptr->x = glseg.x1;
ptr->y = glseg.y1;
ptr->z = vi->heightlist[i];
ptr->u = factu1*(vi->heightlist[i] - ztop[0]) + tcs[UPLFT].u;
ptr->v = factv1*(vi->heightlist[i] - ztop[0]) + tcs[UPLFT].v;
ptr++;
i++;
}
}
}
//==========================================================================
//
// Split right edge of wall
//
//==========================================================================
void GLWall::SplitRightEdge(FFlatVertex *&ptr)
{
if (vertexes[1] == NULL) return;
vertex_t * vi = vertexes[1];
if (vi->numheights)
{
int i = vi->numheights - 1;
float polyh2 = ztop[1] - zbottom[1];
float factv2 = polyh2 ? (tcs[UPRGT].v - tcs[LORGT].v) / polyh2 : 0;
float factu2 = polyh2 ? (tcs[UPRGT].u - tcs[LORGT].u) / polyh2 : 0;
while (i>0 && vi->heightlist[i] >= ztop[1]) i--;
while (i>0 && vi->heightlist[i] > zbottom[1])
{
ptr->x = glseg.x2;
ptr->y = glseg.y2;
ptr->z = vi->heightlist[i];
ptr->u = factu2*(vi->heightlist[i] - ztop[1]) + tcs[UPRGT].u;
ptr->v = factv2*(vi->heightlist[i] - ztop[1]) + tcs[UPRGT].v;
ptr++;
i--;
}
}
}
//==========================================================================
//
// Create vertices for one wall
//
//==========================================================================
int GLWall::CreateVertices(FFlatVertex *&ptr, bool split)
{
auto oo = ptr;
ptr->Set(glseg.x1, zbottom[0], glseg.y1, tcs[LOLFT].u, tcs[LOLFT].v);
ptr++;
if (split && glseg.fracleft == 0) SplitLeftEdge(ptr);
ptr->Set(glseg.x1, ztop[0], glseg.y1, tcs[UPLFT].u, tcs[UPLFT].v);
ptr++;
if (split && !(flags & GLWF_NOSPLITUPPER && seg->sidedef->numsegs > 1)) SplitUpperEdge(ptr);
ptr->Set(glseg.x2, ztop[1], glseg.y2, tcs[UPRGT].u, tcs[UPRGT].v);
ptr++;
if (split && glseg.fracright == 1) SplitRightEdge(ptr);
ptr->Set(glseg.x2, zbottom[1], glseg.y2, tcs[LORGT].u, tcs[LORGT].v);
ptr++;
if (split && !(flags & GLWF_NOSPLITLOWER) && seg->sidedef->numsegs > 1) SplitLowerEdge(ptr);
return int(ptr - oo);
}
//==========================================================================
//
// Split left edge of wall
//
//==========================================================================
void GLWall::CountLeftEdge(unsigned &ptr)
{
if (vertexes[0] == NULL) return;
vertex_t * vi = vertexes[0];
if (vi->numheights)
{
int i = 0;
while (i<vi->numheights && vi->heightlist[i] <= zbottom[0]) i++;
while (i<vi->numheights && vi->heightlist[i] < ztop[0])
{
ptr++;
i++;
}
}
}
//==========================================================================
//
// Split right edge of wall
//
//==========================================================================
void GLWall::CountRightEdge(unsigned &ptr)
{
if (vertexes[1] == NULL) return;
vertex_t * vi = vertexes[1];
if (vi->numheights)
{
int i = vi->numheights - 1;
while (i>0 && vi->heightlist[i] >= ztop[1]) i--;
while (i>0 && vi->heightlist[i] > zbottom[1])
{
ptr++;
i--;
}
}
}
//==========================================================================
//
//
//
//==========================================================================
int GLWall::CountVertices()
{
unsigned ptr = 4;
if (glseg.fracleft == 0) CountLeftEdge(ptr);
if (glseg.fracright == 1) CountRightEdge(ptr);
// This may allocate a few vertices too many in case of a split linedef but this is a rare case that isn't worth the required overhead for a precise calculation.
if (!(flags & GLWF_NOSPLITUPPER)) ptr += seg->sidedef->numsegs - 1;
if (!(flags & GLWF_NOSPLITLOWER)) ptr += seg->sidedef->numsegs - 1;
return (int)ptr;
}
//==========================================================================
//
// build the vertices for this wall
//
//==========================================================================
void GLWall::MakeVertices(HWDrawInfo *di, bool nosplit)
{
if (vertcount == 0)
{
bool split = (gl_seamless && !nosplit && seg->sidedef != nullptr && !(seg->sidedef->Flags & WALLF_POLYOBJ) && !(flags & GLWF_NOSPLIT));
auto ret = screen->mVertexData->AllocVertices(split ? CountVertices() : 4);
vertindex = ret.second;
vertcount = CreateVertices(ret.first, split);
}
}

View file

@ -0,0 +1,608 @@
//
//---------------------------------------------------------------------------
//
// 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/
//
//--------------------------------------------------------------------------
//
/*
** hw_weapon.cpp
** Weapon sprite utilities
**
*/
#include "sbar.h"
#include "r_utility.h"
#include "v_video.h"
#include "doomstat.h"
#include "d_player.h"
#include "g_levellocals.h"
#include "r_data/models/models.h"
#include "hw_weapon.h"
#include "hw_fakeflat.h"
#include "hwrenderer/models/hw_models.h"
#include "hwrenderer/dynlights/hw_dynlightdata.h"
#include "hwrenderer/textures/hw_material.h"
#include "hwrenderer/utility/hw_lighting.h"
#include "hwrenderer/utility/hw_cvars.h"
#include "hwrenderer/scene/hw_drawinfo.h"
#include "hwrenderer/scene/hw_drawstructs.h"
#include "hwrenderer/data/flatvertices.h"
#include "hwrenderer/dynlights/hw_lightbuffer.h"
#include "hw_renderstate.h"
EXTERN_CVAR(Float, transsouls)
EXTERN_CVAR(Int, gl_fuzztype)
EXTERN_CVAR(Bool, r_drawplayersprites)
EXTERN_CVAR(Bool, r_deathcamera)
//==========================================================================
//
// R_DrawPSprite
//
//==========================================================================
void HWDrawInfo::DrawPSprite(HUDSprite *huds, FRenderState &state)
{
if (huds->RenderStyle.BlendOp == STYLEOP_Shadow)
{
state.SetColor(0.2f, 0.2f, 0.2f, 0.33f, huds->cm.Desaturation);
}
else
{
SetColor(state, huds->lightlevel, 0, isFullbrightScene(), huds->cm, huds->alpha, true);
}
state.SetLightIndex(-1);
state.SetRenderStyle(huds->RenderStyle);
state.SetTextureMode(huds->RenderStyle);
state.SetObjectColor(huds->ObjectColor);
if (huds->owner->Sector)
{
state.SetAddColor(huds->owner->Sector->AdditiveColors[sector_t::sprites] | 0xff000000);
}
else
{
state.SetAddColor(0);
}
state.SetDynLight(huds->dynrgb[0], huds->dynrgb[1], huds->dynrgb[2]);
state.EnableBrightmap(!(huds->RenderStyle.Flags & STYLEF_ColorIsFixed));
if (huds->mframe)
{
state.AlphaFunc(Alpha_GEqual, 0);
FGLModelRenderer renderer(this, state, huds->lightindex);
renderer.RenderHUDModel(huds->weapon, huds->mx, huds->my);
state.SetVertexBuffer(screen->mVertexData);
}
else
{
float thresh = (huds->tex->tex->GetTranslucency() || huds->OverrideShader != -1) ? 0.f : gl_mask_sprite_threshold;
state.AlphaFunc(Alpha_GEqual, thresh);
state.SetMaterial(huds->tex, CLAMP_XY_NOMIP, 0, huds->OverrideShader);
state.Draw(DT_TriangleStrip, huds->mx, 4);
}
state.SetTextureMode(TM_NORMAL);
state.AlphaFunc(Alpha_GEqual, gl_mask_sprite_threshold);
state.SetObjectColor(0xffffffff);
state.SetAddColor(0);
state.SetDynLight(0, 0, 0);
state.EnableBrightmap(false);
}
//==========================================================================
//
// R_DrawPlayerSprites
//
//==========================================================================
void HWDrawInfo::DrawPlayerSprites(bool hudModelStep, FRenderState &state)
{
auto oldlightmode = lightmode;
if (!hudModelStep && isSoftwareLighting()) SetFallbackLightMode(); // Software lighting cannot handle 2D content.
for (auto &hudsprite : hudsprites)
{
if ((!!hudsprite.mframe) == hudModelStep)
DrawPSprite(&hudsprite, state);
}
lightmode = oldlightmode;
}
//==========================================================================
//
//
//
//==========================================================================
static bool isBright(DPSprite *psp)
{
if (psp != nullptr && psp->GetState() != nullptr)
{
bool disablefullbright = false;
FTextureID lump = sprites[psp->GetSprite()].GetSpriteFrame(psp->GetFrame(), 0, 0., nullptr);
if (lump.isValid())
{
FTexture * tex = TexMan.GetTexture(lump, true);
if (tex) disablefullbright = tex->isFullbrightDisabled();
}
return psp->GetState()->GetFullbright() && !disablefullbright;
}
return false;
}
//==========================================================================
//
// Weapon position
//
//==========================================================================
static WeaponPosition GetWeaponPosition(player_t *player, double ticFrac)
{
WeaponPosition w;
P_BobWeapon(player, &w.bobx, &w.boby, ticFrac);
// Interpolate the main weapon layer once so as to be able to add it to other layers.
if ((w.weapon = player->FindPSprite(PSP_WEAPON)) != nullptr)
{
if (w.weapon->firstTic)
{
w.wx = (float)w.weapon->x;
w.wy = (float)w.weapon->y;
}
else
{
w.wx = (float)(w.weapon->oldx + (w.weapon->x - w.weapon->oldx) * ticFrac);
w.wy = (float)(w.weapon->oldy + (w.weapon->y - w.weapon->oldy) * ticFrac);
}
}
else
{
w.wx = 0;
w.wy = 0;
}
return w;
}
//==========================================================================
//
// Bobbing
//
//==========================================================================
static FVector2 BobWeapon(WeaponPosition &weap, DPSprite *psp, double ticFrac)
{
if (psp->firstTic)
{ // Can't interpolate the first tic.
psp->firstTic = false;
psp->oldx = psp->x;
psp->oldy = psp->y;
}
float sx = float(psp->oldx + (psp->x - psp->oldx) * ticFrac);
float sy = float(psp->oldy + (psp->y - psp->oldy) * ticFrac);
if (psp->Flags & PSPF_ADDBOB)
{
sx += (psp->Flags & PSPF_MIRROR) ? -weap.bobx : weap.bobx;
sy += weap.boby;
}
if (psp->Flags & PSPF_ADDWEAPON && psp->GetID() != PSP_WEAPON)
{
sx += weap.wx;
sy += weap.wy;
}
return { sx, sy };
}
//==========================================================================
//
// Lighting
//
//==========================================================================
WeaponLighting HWDrawInfo::GetWeaponLighting(sector_t *viewsector, const DVector3 &pos, int cm, area_t in_area, const DVector3 &playerpos)
{
WeaponLighting l;
if (cm)
{
l.lightlevel = 255;
l.cm.Clear();
l.isbelow = false;
}
else
{
auto fakesec = hw_FakeFlat(viewsector, in_area, false);
// calculate light level for weapon sprites
l.lightlevel = hw_ClampLight(fakesec->lightlevel);
// calculate colormap for weapon sprites
if (viewsector->e->XFloor.ffloors.Size() && !(Level->flags3 & LEVEL3_NOCOLOREDSPRITELIGHTING))
{
TArray<lightlist_t> & lightlist = viewsector->e->XFloor.lightlist;
for (unsigned i = 0; i<lightlist.Size(); i++)
{
double lightbottom;
if (i<lightlist.Size() - 1)
{
lightbottom = lightlist[i + 1].plane.ZatPoint(pos);
}
else
{
lightbottom = viewsector->floorplane.ZatPoint(pos);
}
if (lightbottom < pos.Z)
{
l.cm = lightlist[i].extra_colormap;
l.lightlevel = hw_ClampLight(*lightlist[i].p_lightlevel);
break;
}
}
}
else
{
l.cm = fakesec->Colormap;
if (Level->flags3 & LEVEL3_NOCOLOREDSPRITELIGHTING) l.cm.ClearColor();
}
l.lightlevel = CalcLightLevel(l.lightlevel, getExtraLight(), true, 0);
if (isSoftwareLighting() || l.lightlevel < 92)
{
// Korshun: the way based on max possible light level for sector like in software renderer.
double min_L = 36.0 / 31.0 - ((l.lightlevel / 255.0) * (63.0 / 31.0)); // Lightlevel in range 0-63
if (min_L < 0)
min_L = 0;
else if (min_L > 1.0)
min_L = 1.0;
l.lightlevel = int((1.0 - min_L) * 255);
}
else
{
l.lightlevel = (2 * l.lightlevel + 255) / 3;
}
l.lightlevel = viewsector->CheckSpriteGlow(l.lightlevel, playerpos);
l.isbelow = fakesec != viewsector && in_area == area_below;
}
// Korshun: fullbright fog in opengl, render weapon sprites fullbright (but don't cancel out the light color!)
if (Level->brightfog && ((Level->flags&LEVEL_HASFADETABLE) || l.cm.FadeColor != 0))
{
l.lightlevel = 255;
}
return l;
}
//==========================================================================
//
//
//
//==========================================================================
void HUDSprite::SetBright(bool isbelow)
{
if (!isbelow)
{
cm.MakeWhite();
}
else
{
// under water areas keep most of their color for fullbright objects
cm.LightColor.r = (3 * cm.LightColor.r + 0xff) / 4;
cm.LightColor.g = (3 * cm.LightColor.g + 0xff) / 4;
cm.LightColor.b = (3 * cm.LightColor.b + 0xff) / 4;
}
lightlevel = 255;
}
//==========================================================================
//
// Render Style
//
//==========================================================================
bool HUDSprite::GetWeaponRenderStyle(DPSprite *psp, AActor *playermo, sector_t *viewsector, WeaponLighting &lighting)
{
auto rs = psp->GetRenderStyle(playermo->RenderStyle, playermo->Alpha);
visstyle_t vis;
vis.RenderStyle = STYLE_Count;
vis.Alpha = rs.second;
vis.Invert = false;
playermo->AlterWeaponSprite(&vis);
alpha = (psp->Flags & PSPF_FORCEALPHA) ? 0.f : vis.Alpha;
if (vis.RenderStyle != STYLE_Count && !(psp->Flags & PSPF_FORCESTYLE))
{
RenderStyle = vis.RenderStyle;
}
else
{
RenderStyle = rs.first;
}
if (RenderStyle.BlendOp == STYLEOP_None) return false;
if (vis.Invert)
{
// this only happens for Strife's inverted weapon sprite
RenderStyle.Flags |= STYLEF_InvertSource;
}
// Set the render parameters
OverrideShader = -1;
if (RenderStyle.BlendOp == STYLEOP_Fuzz)
{
if (gl_fuzztype != 0)
{
// Todo: implement shader selection here
RenderStyle = LegacyRenderStyles[STYLE_Translucent];
OverrideShader = SHADER_NoTexture + gl_fuzztype;
alpha = 0.99f; // trans may not be 1 here
}
else
{
RenderStyle.BlendOp = STYLEOP_Shadow;
}
}
if (RenderStyle.Flags & STYLEF_TransSoulsAlpha)
{
alpha = transsouls;
}
else if (RenderStyle.Flags & STYLEF_Alpha1)
{
alpha = 1.f;
}
else if (alpha == 0.f)
{
alpha = vis.Alpha;
}
if (!RenderStyle.IsVisible(alpha)) return false; // if it isn't visible skip the rest.
PalEntry ThingColor = (playermo->RenderStyle.Flags & STYLEF_ColorIsFixed) ? playermo->fillcolor : 0xffffff;
ThingColor.a = 255;
const bool bright = isBright(psp);
ObjectColor = bright ? ThingColor : ThingColor.Modulate(viewsector->SpecialColors[sector_t::sprites]);
lightlevel = lighting.lightlevel;
cm = lighting.cm;
if (bright) SetBright(lighting.isbelow);
return true;
}
//==========================================================================
//
// Coordinates
//
//==========================================================================
bool HUDSprite::GetWeaponRect(HWDrawInfo *di, DPSprite *psp, float sx, float sy, player_t *player)
{
float tx;
float scale;
float scalex;
float ftexturemid;
// decide which patch to use
bool mirror;
FTextureID lump = sprites[psp->GetSprite()].GetSpriteFrame(psp->GetFrame(), 0, 0., &mirror);
if (!lump.isValid()) return false;
FMaterial * tex = FMaterial::ValidateTexture(lump, true, false);
if (!tex) return false;
float vw = (float)viewwidth;
float vh = (float)viewheight;
FloatRect r;
tex->GetSpriteRect(&r);
// calculate edges of the shape
scalex = (320.0f / (240.0f * r_viewwindow.WidescreenRatio)) * vw / 320;
float x1, y1, x2, y2, u1, v1, u2, v2;
tx = (psp->Flags & PSPF_MIRROR) ? ((160 - r.width) - (sx + r.left)) : (sx - (160 - r.left));
x1 = tx * scalex + vw / 2;
if (x1 > vw) return false; // off the right side
x1 += viewwindowx;
tx += r.width;
x2 = tx * scalex + vw / 2;
if (x2 < 0) return false; // off the left side
x2 += viewwindowx;
// killough 12/98: fix psprite positioning problem
ftexturemid = 100.f - sy - r.top - psp->GetYAdjust(screenblocks >= 11);
scale = (SCREENHEIGHT*vw) / (SCREENWIDTH * 200.0f);
y1 = viewwindowy + vh / 2 - (ftexturemid * scale);
y2 = y1 + (r.height * scale) + 1;
if (!(mirror) != !(psp->Flags & (PSPF_FLIP)))
{
u2 = tex->GetSpriteUL();
v1 = tex->GetSpriteVT();
u1 = tex->GetSpriteUR();
v2 = tex->GetSpriteVB();
}
else
{
u1 = tex->GetSpriteUL();
v1 = tex->GetSpriteVT();
u2 = tex->GetSpriteUR();
v2 = tex->GetSpriteVB();
}
auto verts = screen->mVertexData->AllocVertices(4);
mx = verts.second;
verts.first[0].Set(x1, y1, 0, u1, v1);
verts.first[1].Set(x1, y2, 0, u1, v2);
verts.first[2].Set(x2, y1, 0, u2, v1);
verts.first[3].Set(x2, y2, 0, u2, v2);
this->tex = tex;
return true;
}
//==========================================================================
//
// R_DrawPlayerSprites
//
//==========================================================================
void HWDrawInfo::PreparePlayerSprites(sector_t * viewsector, area_t in_area)
{
bool brightflash = false;
AActor * playermo = players[consoleplayer].camera;
player_t * player = playermo->player;
const auto &vp = Viewpoint;
AActor *camera = vp.camera;
// this is the same as the software renderer
if (!player ||
!r_drawplayersprites ||
!camera->player ||
(player->cheats & CF_CHASECAM) ||
(r_deathcamera && camera->health <= 0))
return;
const bool hudModelStep = IsHUDModelForPlayerAvailable(camera->player);
WeaponPosition weap = GetWeaponPosition(camera->player, vp.TicFrac);
WeaponLighting light = GetWeaponLighting(viewsector, vp.Pos, isFullbrightScene(), in_area, camera->Pos());
// hack alert! Rather than changing everything in the underlying lighting code let's just temporarily change
// light mode here to draw the weapon sprite.
auto oldlightmode = lightmode;
if (isSoftwareLighting()) SetFallbackLightMode();
for (DPSprite *psp = player->psprites; psp != nullptr && psp->GetID() < PSP_TARGETCENTER; psp = psp->GetNext())
{
if (!psp->GetState()) continue;
FSpriteModelFrame *smf = playermo->player->ReadyWeapon ? FindModelFrame(playermo->player->ReadyWeapon->GetClass(), psp->GetState()->sprite, psp->GetState()->GetFrame(), false) : nullptr;
// This is an 'either-or' proposition. This maybe needs some work to allow overlays with weapon models but as originally implemented this just won't work.
if (smf && !hudModelStep) continue;
if (!smf && hudModelStep) continue;
HUDSprite hudsprite;
hudsprite.owner = playermo;
hudsprite.mframe = smf;
hudsprite.weapon = psp;
if (!hudsprite.GetWeaponRenderStyle(psp, camera, viewsector, light)) continue;
FVector2 spos = BobWeapon(weap, psp, vp.TicFrac);
hudsprite.dynrgb[0] = hudsprite.dynrgb[1] = hudsprite.dynrgb[2] = 0;
hudsprite.lightindex = -1;
// set the lighting parameters
if (hudsprite.RenderStyle.BlendOp != STYLEOP_Shadow && Level->HasDynamicLights && !isFullbrightScene() && gl_light_sprites)
{
if (!hudModelStep)
{
GetDynSpriteLight(playermo, nullptr, hudsprite.dynrgb);
}
else
{
hw_GetDynModelLight(playermo, lightdata);
hudsprite.lightindex = screen->mLights->UploadLights(lightdata);
}
}
// [BB] In the HUD model step we just render the model and break out.
if (hudModelStep)
{
hudsprite.mx = spos.X;
hudsprite.my = spos.Y;
}
else
{
if (!hudsprite.GetWeaponRect(this, psp, spos.X, spos.Y, player)) continue;
}
hudsprites.Push(hudsprite);
}
lightmode = oldlightmode;
PrepareTargeterSprites();
}
//==========================================================================
//
// R_DrawPlayerSprites
//
//==========================================================================
void HWDrawInfo::PrepareTargeterSprites()
{
AActor * playermo = players[consoleplayer].camera;
player_t * player = playermo->player;
AActor *camera = Viewpoint.camera;
// this is the same as above
if (!player ||
!r_drawplayersprites ||
!camera->player ||
(player->cheats & CF_CHASECAM) ||
(r_deathcamera && camera->health <= 0))
return;
HUDSprite hudsprite;
hudsprite.owner = playermo;
hudsprite.mframe = nullptr;
hudsprite.cm.Clear();
hudsprite.lightlevel = 255;
hudsprite.ObjectColor = 0xffffffff;
hudsprite.alpha = 1;
hudsprite.RenderStyle = DefaultRenderStyle();
hudsprite.OverrideShader = -1;
hudsprite.dynrgb[0] = hudsprite.dynrgb[1] = hudsprite.dynrgb[2] = 0;
// The Targeter's sprites are always drawn normally.
for (DPSprite *psp = player->FindPSprite(PSP_TARGETCENTER); psp != nullptr; psp = psp->GetNext())
{
if (psp->GetState() != nullptr && (psp->GetID() != PSP_TARGETCENTER || CrosshairImage == nullptr))
{
hudsprite.weapon = psp;
if (hudsprite.GetWeaponRect(this, psp, psp->x, psp->y, player))
{
hudsprites.Push(hudsprite);
}
}
}
}

View file

@ -0,0 +1,52 @@
#pragma once
#include "vectors.h"
class DPSprite;
class player_t;
class AActor;
enum area_t : int;
struct FSpriteModelFrame;
struct HWDrawInfo;
class FMaterial;
struct WeaponPosition
{
float wx, wy;
float bobx, boby;
DPSprite *weapon;
};
struct WeaponLighting
{
FColormap cm;
int lightlevel;
bool isbelow;
};
struct HUDSprite
{
AActor *owner;
DPSprite *weapon;
FMaterial *tex;
FSpriteModelFrame *mframe;
FColormap cm;
int lightlevel;
PalEntry ObjectColor;
FRenderStyle RenderStyle;
float alpha;
int OverrideShader;
float mx, my;
float dynrgb[3];
int lightindex;
void SetBright(bool isbelow);
bool GetWeaponRenderStyle(DPSprite *psp, AActor *playermo, sector_t *viewsector, WeaponLighting &light);
bool GetWeaponRect(HWDrawInfo *di, DPSprite *psp, float sx, float sy, player_t *player);
};

View file

@ -0,0 +1,30 @@
#pragma once
#include <stdint.h>
#include "tarray.h"
typedef TMap<int, bool> SpriteHits;
class FTexture;
class IHardwareTexture
{
public:
enum
{
MAX_TEXTURES = 16
};
public:
public:
IHardwareTexture() {}
virtual ~IHardwareTexture() {}
virtual void AllocateBuffer(int w, int h, int texelsize) = 0;
virtual uint8_t *MapBuffer() = 0;
virtual unsigned int CreateTexture(unsigned char * buffer, int w, int h, int texunit, bool mipmap, int translation, const char *name) = 0;
void Resize(int swidth, int sheight, int width, int height, unsigned char *src_data, unsigned char *dst_data);
};

View file

@ -0,0 +1,496 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2004-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 "w_wad.h"
#include "m_png.h"
#include "sbar.h"
#include "stats.h"
#include "r_utility.h"
#include "c_dispatch.h"
#include "hw_ihwtexture.h"
#include "hw_material.h"
EXTERN_CVAR(Bool, gl_texture_usehires)
//===========================================================================
//
// Quick'n dirty image rescaling.
//
// This will only be used when the source texture is larger than
// what the hardware can manage (extremely rare in Doom)
//
// Code taken from wxWidgets
//
//===========================================================================
struct BoxPrecalc
{
int boxStart;
int boxEnd;
};
static void ResampleBoxPrecalc(TArray<BoxPrecalc>& boxes, int oldDim)
{
int newDim = boxes.Size();
const double scale_factor_1 = double(oldDim) / newDim;
const int scale_factor_2 = (int)(scale_factor_1 / 2);
for (int dst = 0; dst < newDim; ++dst)
{
// Source pixel in the Y direction
const int src_p = int(dst * scale_factor_1);
BoxPrecalc& precalc = boxes[dst];
precalc.boxStart = clamp<int>(int(src_p - scale_factor_1 / 2.0 + 1), 0, oldDim - 1);
precalc.boxEnd = clamp<int>(MAX<int>(precalc.boxStart + 1, int(src_p + scale_factor_2)), 0, oldDim - 1);
}
}
void IHardwareTexture::Resize(int swidth, int sheight, int width, int height, unsigned char *src_data, unsigned char *dst_data)
{
// This function implements a simple pre-blur/box averaging method for
// downsampling that gives reasonably smooth results To scale the image
// down we will need to gather a grid of pixels of the size of the scale
// factor in each direction and then do an averaging of the pixels.
TArray<BoxPrecalc> vPrecalcs(height, true);
TArray<BoxPrecalc> hPrecalcs(width, true);
ResampleBoxPrecalc(vPrecalcs, sheight);
ResampleBoxPrecalc(hPrecalcs, swidth);
int averaged_pixels, averaged_alpha, src_pixel_index;
double sum_r, sum_g, sum_b, sum_a;
for (int y = 0; y < height; y++) // Destination image - Y direction
{
// Source pixel in the Y direction
const BoxPrecalc& vPrecalc = vPrecalcs[y];
for (int x = 0; x < width; x++) // Destination image - X direction
{
// Source pixel in the X direction
const BoxPrecalc& hPrecalc = hPrecalcs[x];
// Box of pixels to average
averaged_pixels = 0;
averaged_alpha = 0;
sum_r = sum_g = sum_b = sum_a = 0.0;
for (int j = vPrecalc.boxStart; j <= vPrecalc.boxEnd; ++j)
{
for (int i = hPrecalc.boxStart; i <= hPrecalc.boxEnd; ++i)
{
// Calculate the actual index in our source pixels
src_pixel_index = j * swidth + i;
int a = src_data[src_pixel_index * 4 + 3];
if (a > 0) // do not use color from fully transparent pixels
{
sum_r += src_data[src_pixel_index * 4 + 0];
sum_g += src_data[src_pixel_index * 4 + 1];
sum_b += src_data[src_pixel_index * 4 + 2];
sum_a += a;
averaged_pixels++;
}
averaged_alpha++;
}
}
// Calculate the average from the sum and number of averaged pixels
dst_data[0] = (unsigned char)xs_CRoundToInt(sum_r / averaged_pixels);
dst_data[1] = (unsigned char)xs_CRoundToInt(sum_g / averaged_pixels);
dst_data[2] = (unsigned char)xs_CRoundToInt(sum_b / averaged_pixels);
dst_data[3] = (unsigned char)xs_CRoundToInt(sum_a / averaged_alpha);
dst_data += 4;
}
}
}
//===========================================================================
//
// Constructor
//
//===========================================================================
FMaterial::FMaterial(FTexture * tx, bool expanded)
{
mShaderIndex = SHADER_Default;
sourcetex = tex = tx;
if (tx->UseType == ETextureType::SWCanvas && static_cast<FWrapperTexture*>(tx)->GetColorFormat() == 0)
{
mShaderIndex = SHADER_Paletted;
}
else if (tx->isWarped())
{
mShaderIndex = tx->isWarped(); // This picks SHADER_Warp1 or SHADER_Warp2
}
else if (tx->isHardwareCanvas())
{
if (tx->shaderindex >= FIRST_USER_SHADER)
{
mShaderIndex = tx->shaderindex;
}
// no brightmap for cameratexture
}
else
{
if (tx->Normal && tx->Specular)
{
for (auto &texture : { tx->Normal, tx->Specular })
{
mTextureLayers.Push(texture);
}
mShaderIndex = SHADER_Specular;
}
else if (tx->Normal && tx->Metallic && tx->Roughness && tx->AmbientOcclusion)
{
for (auto &texture : { tx->Normal, tx->Metallic, tx->Roughness, tx->AmbientOcclusion })
{
mTextureLayers.Push(texture);
}
mShaderIndex = SHADER_PBR;
}
tx->CreateDefaultBrightmap();
if (tx->Brightmap)
{
mTextureLayers.Push(tx->Brightmap);
if (mShaderIndex == SHADER_Specular)
mShaderIndex = SHADER_SpecularBrightmap;
else if (mShaderIndex == SHADER_PBR)
mShaderIndex = SHADER_PBRBrightmap;
else
mShaderIndex = SHADER_Brightmap;
}
if (tx->shaderindex >= FIRST_USER_SHADER)
{
const UserShaderDesc &usershader = usershaders[tx->shaderindex - FIRST_USER_SHADER];
if (usershader.shaderType == mShaderIndex) // Only apply user shader if it matches the expected material
{
for (auto &texture : tx->CustomShaderTextures)
{
if (texture == nullptr) continue;
mTextureLayers.Push(texture);
}
mShaderIndex = tx->shaderindex;
}
}
}
mWidth = tx->GetWidth();
mHeight = tx->GetHeight();
mLeftOffset = tx->GetLeftOffset(0); // These only get used by decals and decals should not use renderer-specific offsets.
mTopOffset = tx->GetTopOffset(0);
mRenderWidth = tx->GetScaledWidth();
mRenderHeight = tx->GetScaledHeight();
mSpriteU[0] = mSpriteV[0] = 0.f;
mSpriteU[1] = mSpriteV[1] = 1.f;
mExpanded = expanded;
if (expanded)
{
int oldwidth = mWidth;
int oldheight = mHeight;
mTrimResult = TrimBorders(trim); // get the trim size before adding the empty frame
mWidth += 2;
mHeight += 2;
mRenderWidth = mRenderWidth * mWidth / oldwidth;
mRenderHeight = mRenderHeight * mHeight / oldheight;
}
SetSpriteRect();
mTextureLayers.ShrinkToFit();
tx->Material[expanded] = this;
if (tx->isHardwareCanvas()) tx->bTranslucent = 0;
}
//===========================================================================
//
// Destructor
//
//===========================================================================
FMaterial::~FMaterial()
{
}
//===========================================================================
//
// Set the sprite rectangle
//
//===========================================================================
void FMaterial::SetSpriteRect()
{
auto leftOffset = tex->GetLeftOffsetHW();
auto topOffset = tex->GetTopOffsetHW();
float fxScale = (float)tex->Scale.X;
float fyScale = (float)tex->Scale.Y;
// mSpriteRect is for positioning the sprite in the scene.
mSpriteRect.left = -leftOffset / fxScale;
mSpriteRect.top = -topOffset / fyScale;
mSpriteRect.width = mWidth / fxScale;
mSpriteRect.height = mHeight / fyScale;
if (mExpanded)
{
// a little adjustment to make sprites look better with texture filtering:
// create a 1 pixel wide empty frame around them.
int oldwidth = mWidth - 2;
int oldheight = mHeight - 2;
leftOffset += 1;
topOffset += 1;
// Reposition the sprite with the frame considered
mSpriteRect.left = -leftOffset / fxScale;
mSpriteRect.top = -topOffset / fyScale;
mSpriteRect.width = mWidth / fxScale;
mSpriteRect.height = mHeight / fyScale;
if (mTrimResult)
{
mSpriteRect.left += trim[0] / fxScale;
mSpriteRect.top += trim[1] / fyScale;
mSpriteRect.width -= (oldwidth - trim[2]) / fxScale;
mSpriteRect.height -= (oldheight - trim[3]) / fyScale;
mSpriteU[0] = trim[0] / (float)mWidth;
mSpriteV[0] = trim[1] / (float)mHeight;
mSpriteU[1] -= (oldwidth - trim[0] - trim[2]) / (float)mWidth;
mSpriteV[1] -= (oldheight - trim[1] - trim[3]) / (float)mHeight;
}
}
}
//===========================================================================
//
// Finds empty space around the texture.
// Used for sprites that got placed into a huge empty frame.
//
//===========================================================================
bool FMaterial::TrimBorders(uint16_t *rect)
{
auto texbuffer = sourcetex->CreateTexBuffer(0);
int w = texbuffer.mWidth;
int h = texbuffer.mHeight;
auto Buffer = texbuffer.mBuffer;
if (texbuffer.mBuffer == nullptr)
{
return false;
}
if (w != mWidth || h != mHeight)
{
// external Hires replacements cannot be trimmed.
return false;
}
int size = w*h;
if (size == 1)
{
// nothing to be done here.
rect[0] = 0;
rect[1] = 0;
rect[2] = 1;
rect[3] = 1;
return true;
}
int first, last;
for(first = 0; first < size; first++)
{
if (Buffer[first*4+3] != 0) break;
}
if (first >= size)
{
// completely empty
rect[0] = 0;
rect[1] = 0;
rect[2] = 1;
rect[3] = 1;
return true;
}
for(last = size-1; last >= first; last--)
{
if (Buffer[last*4+3] != 0) break;
}
rect[1] = first / w;
rect[3] = 1 + last/w - rect[1];
rect[0] = 0;
rect[2] = w;
unsigned char *bufferoff = Buffer + (rect[1] * w * 4);
h = rect[3];
for(int x = 0; x < w; x++)
{
for(int y = 0; y < h; y++)
{
if (bufferoff[(x+y*w)*4+3] != 0) goto outl;
}
rect[0]++;
}
outl:
rect[2] -= rect[0];
for(int x = w-1; rect[2] > 1; x--)
{
for(int y = 0; y < h; y++)
{
if (bufferoff[(x+y*w)*4+3] != 0)
{
return true;
}
}
rect[2]--;
}
return true;
}
//===========================================================================
//
//
//
//===========================================================================
IHardwareTexture *FMaterial::GetLayer(int i, int translation, FTexture **pLayer)
{
FTexture *layer = i == 0 ? tex : mTextureLayers[i - 1];
if (pLayer) *pLayer = layer;
if (layer && layer->UseType!=ETextureType::Null)
{
IHardwareTexture *hwtex = layer->SystemTextures.GetHardwareTexture(translation, mExpanded);
if (hwtex == nullptr)
{
hwtex = screen->CreateHardwareTexture();
layer->SystemTextures.AddHardwareTexture(translation, mExpanded, hwtex);
}
return hwtex;
}
return nullptr;
}
//===========================================================================
//
//
//
//===========================================================================
void FMaterial::Precache()
{
screen->PrecacheMaterial(this, 0);
}
//===========================================================================
//
//
//
//===========================================================================
void FMaterial::PrecacheList(SpriteHits &translations)
{
tex->SystemTextures.CleanUnused(translations, mExpanded);
SpriteHits::Iterator it(translations);
SpriteHits::Pair *pair;
while(it.NextPair(pair)) screen->PrecacheMaterial(this, pair->Key);
}
//===========================================================================
//
//
//
//===========================================================================
int FMaterial::GetAreas(FloatRect **pAreas) const
{
if (mShaderIndex == SHADER_Default) // texture splitting can only be done if there's no attached effects
{
*pAreas = sourcetex->areas;
return sourcetex->areacount;
}
else
{
return 0;
}
}
//==========================================================================
//
// Gets a texture from the texture manager and checks its validity for
// GL rendering.
//
//==========================================================================
FMaterial * FMaterial::ValidateTexture(FTexture * tex, bool expand, bool create)
{
again:
if (tex && tex->isValid())
{
if (tex->bNoExpand) expand = false;
FMaterial *hwtex = tex->Material[expand];
if (hwtex == NULL && create)
{
if (expand)
{
if (tex->isWarped() || tex->isHardwareCanvas() || tex->shaderindex >= FIRST_USER_SHADER || (tex->shaderindex >= SHADER_Specular && tex->shaderindex <= SHADER_PBRBrightmap))
{
tex->bNoExpand = true;
goto again;
}
if (tex->Brightmap != NULL &&
(tex->GetWidth() != tex->Brightmap->GetWidth() ||
tex->GetHeight() != tex->Brightmap->GetHeight())
)
{
// do not expand if the brightmap's size differs.
tex->bNoExpand = true;
goto again;
}
}
hwtex = new FMaterial(tex, expand);
}
return hwtex;
}
return NULL;
}
FMaterial * FMaterial::ValidateTexture(FTextureID no, bool expand, bool translate, bool create)
{
return ValidateTexture(TexMan.GetTexture(no, translate), expand, create);
}

View file

@ -0,0 +1,143 @@
#ifndef __GL_MATERIAL_H
#define __GL_MATERIAL_H
#include "m_fixed.h"
#include "textures/textures.h"
#include "r_defs.h"
EXTERN_CVAR(Bool, gl_precache)
struct FRemapTable;
class FTextureShader;
class IHardwareTexture;
enum
{
CLAMP_NONE = 0,
CLAMP_X = 1,
CLAMP_Y = 2,
CLAMP_XY = 3,
CLAMP_XY_NOMIP = 4,
CLAMP_NOFILTER = 5,
CLAMP_CAMTEX = 6,
};
//===========================================================================
//
// this is the material class for OpenGL.
//
//===========================================================================
class FMaterial
{
TArray<FTexture*> mTextureLayers;
int mShaderIndex;
short mLeftOffset;
short mTopOffset;
short mWidth;
short mHeight;
short mRenderWidth;
short mRenderHeight;
bool mExpanded;
bool mTrimResult;
uint16_t trim[4];
float mSpriteU[2], mSpriteV[2];
FloatRect mSpriteRect;
bool TrimBorders(uint16_t *rect);
public:
FTexture *tex;
FTexture *sourcetex; // in case of redirection this is different from tex.
FMaterial(FTexture *tex, bool forceexpand);
~FMaterial();
void SetSpriteRect();
void Precache();
void PrecacheList(SpriteHits &translations);
int GetShaderIndex() const { return mShaderIndex; }
void AddTextureLayer(FTexture *tex)
{
ValidateTexture(tex, false);
mTextureLayers.Push(tex);
}
bool isMasked() const
{
return sourcetex->bMasked;
}
bool isExpanded() const
{
return mExpanded;
}
int GetLayers() const
{
return mTextureLayers.Size() + 1;
}
bool hasCanvas()
{
return tex->isHardwareCanvas();
}
IHardwareTexture *GetLayer(int i, int translation, FTexture **pLayer = nullptr);
// Patch drawing utilities
void GetSpriteRect(FloatRect * r) const
{
*r = mSpriteRect;
}
// This is scaled size in integer units as needed by walls and flats
int TextureHeight() const { return mRenderHeight; }
int TextureWidth() const { return mRenderWidth; }
int GetAreas(FloatRect **pAreas) const;
int GetWidth() const
{
return mWidth;
}
int GetHeight() const
{
return mHeight;
}
int GetLeftOffset() const
{
return mLeftOffset;
}
int GetTopOffset() const
{
return mTopOffset;
}
// Get right/bottom UV coordinates for patch drawing
float GetUL() const { return 0; }
float GetVT() const { return 0; }
float GetUR() const { return 1; }
float GetVB() const { return 1; }
float GetU(float upix) const { return upix/(float)mWidth; }
float GetV(float vpix) const { return vpix/(float)mHeight; }
float GetSpriteUL() const { return mSpriteU[0]; }
float GetSpriteVT() const { return mSpriteV[0]; }
float GetSpriteUR() const { return mSpriteU[1]; }
float GetSpriteVB() const { return mSpriteV[1]; }
static FMaterial *ValidateTexture(FTexture * tex, bool expand, bool create = true);
static FMaterial *ValidateTexture(FTextureID no, bool expand, bool trans, bool create = true);
};
#endif

View file

@ -0,0 +1,246 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2004-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/
//
//--------------------------------------------------------------------------
//
/*
** Texture precaching
**
*/
#include "c_cvars.h"
#include "w_wad.h"
#include "r_data/r_translate.h"
#include "c_dispatch.h"
#include "r_state.h"
#include "actor.h"
#include "r_data/models/models.h"
#include "textures/skyboxtexture.h"
#include "hwrenderer/textures/hw_material.h"
#include "image.h"
//==========================================================================
//
// DFrameBuffer :: PrecacheTexture
//
//==========================================================================
static void PrecacheTexture(FTexture *tex, int cache)
{
if (cache & (FTextureManager::HIT_Wall | FTextureManager::HIT_Flat | FTextureManager::HIT_Sky))
{
FMaterial * gltex = FMaterial::ValidateTexture(tex, false);
if (gltex) gltex->Precache();
}
}
//==========================================================================
//
// DFrameBuffer :: PrecacheSprite
//
//==========================================================================
static void PrecacheSprite(FTexture *tex, SpriteHits &hits)
{
FMaterial * gltex = FMaterial::ValidateTexture(tex, true);
if (gltex) gltex->PrecacheList(hits);
}
//==========================================================================
//
// DFrameBuffer :: Precache
//
//==========================================================================
void hw_PrecacheTexture(uint8_t *texhitlist, TMap<PClassActor*, bool> &actorhitlist)
{
SpriteHits *spritelist = new SpriteHits[sprites.Size()];
SpriteHits **spritehitlist = new SpriteHits*[TexMan.NumTextures()];
TMap<PClassActor*, bool>::Iterator it(actorhitlist);
TMap<PClassActor*, bool>::Pair *pair;
uint8_t *modellist = new uint8_t[Models.Size()];
memset(modellist, 0, Models.Size());
memset(spritehitlist, 0, sizeof(SpriteHits**) * TexMan.NumTextures());
// this isn't done by the main code so it needs to be done here first:
// check skybox textures and mark the separate faces as used
for (int i = 0; i<TexMan.NumTextures(); i++)
{
// HIT_Wall must be checked for MBF-style sky transfers.
if (texhitlist[i] & (FTextureManager::HIT_Sky | FTextureManager::HIT_Wall))
{
FTexture *tex = TexMan.ByIndex(i);
if (tex->isSkybox())
{
FSkyBox *sb = static_cast<FSkyBox*>(tex);
for (int i = 0; i<6; i++)
{
if (sb->faces[i])
{
int index = sb->faces[i]->GetID().GetIndex();
texhitlist[index] |= FTextureManager::HIT_Flat;
}
}
}
}
}
// Check all used actors.
// 1. mark all sprites associated with its states
// 2. mark all model data and skins associated with its states
while (it.NextPair(pair))
{
PClassActor *cls = pair->Key;
auto remap = TranslationToTable(GetDefaultByType(cls)->Translation);
int gltrans = remap == nullptr ? 0 : remap->GetUniqueIndex();
for (unsigned i = 0; i < cls->GetStateCount(); i++)
{
auto &state = cls->GetStates()[i];
spritelist[state.sprite].Insert(gltrans, true);
FSpriteModelFrame * smf = FindModelFrame(cls, state.sprite, state.Frame, false);
if (smf != NULL)
{
for (int i = 0; i < MAX_MODELS_PER_FRAME; i++)
{
if (smf->skinIDs[i].isValid())
{
texhitlist[smf->skinIDs[i].GetIndex()] |= FTextureManager::HIT_Flat;
}
else if (smf->modelIDs[i] != -1)
{
Models[smf->modelIDs[i]]->PushSpriteMDLFrame(smf, i);
Models[smf->modelIDs[i]]->AddSkins(texhitlist);
}
if (smf->modelIDs[i] != -1)
{
modellist[smf->modelIDs[i]] = 1;
}
}
}
}
}
// mark all sprite textures belonging to the marked sprites.
for (int i = (int)(sprites.Size() - 1); i >= 0; i--)
{
if (spritelist[i].CountUsed())
{
int j, k;
for (j = 0; j < sprites[i].numframes; j++)
{
const spriteframe_t *frame = &SpriteFrames[sprites[i].spriteframes + j];
for (k = 0; k < 16; k++)
{
FTextureID pic = frame->Texture[k];
if (pic.isValid())
{
spritehitlist[pic.GetIndex()] = &spritelist[i];
}
}
}
}
}
// delete everything unused before creating any new resources to avoid memory usage peaks.
// delete unused models
for (unsigned i = 0; i < Models.Size(); i++)
{
if (!modellist[i]) Models[i]->DestroyVertexBuffer();
}
// delete unused textures
int cnt = TexMan.NumTextures();
for (int i = cnt - 1; i >= 0; i--)
{
FTexture *tex = TexMan.ByIndex(i);
if (tex != nullptr)
{
if (!texhitlist[i])
{
tex->SystemTextures.Clean(true, false);
}
if (spritehitlist[i] == nullptr || (*spritehitlist[i]).CountUsed() == 0)
{
tex->SystemTextures.Clean(false, true);
}
}
}
if (gl_precache)
{
FImageSource::BeginPrecaching();
// cache all used textures
for (int i = cnt - 1; i >= 0; i--)
{
FTexture *tex = TexMan.ByIndex(i);
if (tex != nullptr && tex->GetImage() != nullptr)
{
if (texhitlist[i] & (FTextureManager::HIT_Wall | FTextureManager::HIT_Flat | FTextureManager::HIT_Sky))
{
if (tex->GetImage() && tex->SystemTextures.GetHardwareTexture(0, false) == nullptr)
{
FImageSource::RegisterForPrecache(tex->GetImage());
}
}
// Only register untranslated sprites. Translated ones are very unlikely to require data that can be reused.
if (spritehitlist[i] != nullptr && (*spritehitlist[i]).CheckKey(0))
{
FImageSource::RegisterForPrecache(tex->GetImage());
}
}
}
// cache all used textures
for (int i = cnt - 1; i >= 0; i--)
{
FTexture *tex = TexMan.ByIndex(i);
if (tex != nullptr)
{
PrecacheTexture(tex, texhitlist[i]);
if (spritehitlist[i] != nullptr && (*spritehitlist[i]).CountUsed() > 0)
{
PrecacheSprite(tex, *spritehitlist[i]);
}
}
}
FImageSource::EndPrecaching();
// cache all used models
FModelRenderer *renderer = screen->CreateModelRenderer(-1);
for (unsigned i = 0; i < Models.Size(); i++)
{
if (modellist[i])
Models[i]->BuildVertexBuffer(renderer);
}
delete renderer;
}
delete[] spritehitlist;
delete[] spritelist;
delete[] modellist;
}

View file

@ -0,0 +1,130 @@
#pragma once
#include "tarray.h"
#include "hwrenderer/textures/hw_ihwtexture.h"
struct FTextureBuffer;
class IHardwareTexture;
class FHardwareTextureContainer
{
public:
enum
{
MAX_TEXTURES = 16
};
private:
struct TranslatedTexture
{
IHardwareTexture *hwTexture = nullptr;
int translation = 0;
void Delete()
{
if (hwTexture) delete hwTexture;
hwTexture = nullptr;
}
~TranslatedTexture()
{
Delete();
}
};
private:
TranslatedTexture hwDefTex[2];
TArray<TranslatedTexture> hwTex_Translated;
TranslatedTexture * GetTexID(int translation, bool expanded)
{
translation = TranslationToIndex(translation);
if (translation == 0)
{
return &hwDefTex[expanded];
}
if (expanded) translation = -translation;
// normally there aren't more than very few different
// translations here so this isn't performance critical.
unsigned index = hwTex_Translated.FindEx([=](auto &element)
{
return element.translation == translation;
});
if (index < hwTex_Translated.Size())
{
return &hwTex_Translated[index];
}
int add = hwTex_Translated.Reserve(1);
auto item = &hwTex_Translated[add];
item->translation = translation;
return item;
}
public:
void Clean(bool cleannormal, bool cleanexpanded)
{
if (cleannormal) hwDefTex[0].Delete();
if (cleanexpanded) hwDefTex[1].Delete();
for (int i = hwTex_Translated.Size() - 1; i >= 0; i--)
{
if (cleannormal && hwTex_Translated[i].translation > 0) hwTex_Translated.Delete(i);
else if (cleanexpanded && hwTex_Translated[i].translation < 0) hwTex_Translated.Delete(i);
}
}
IHardwareTexture * GetHardwareTexture(int translation, bool expanded)
{
auto tt = GetTexID(translation, expanded);
return tt->hwTexture;
}
void AddHardwareTexture(int translation, bool expanded, IHardwareTexture *tex)
{
auto tt = GetTexID(translation, expanded);
tt->Delete();
tt->hwTexture =tex;
}
//===========================================================================
//
// Deletes all allocated resources and considers translations
// This will only be called for sprites
//
//===========================================================================
void CleanUnused(SpriteHits &usedtranslations, bool expanded)
{
if (usedtranslations.CheckKey(0) == nullptr)
{
hwDefTex[expanded].Delete();
}
int fac = expanded ? -1 : 1;
for (int i = hwTex_Translated.Size()-1; i>= 0; i--)
{
if (usedtranslations.CheckKey(hwTex_Translated[i].translation * fac) == nullptr)
{
hwTex_Translated.Delete(i);
}
}
}
static int TranslationToIndex(int translation)
{
if (translation <= 0)
{
return -translation;
}
else
{
auto remap = TranslationToTable(translation);
return remap == nullptr ? 0 : remap->GetUniqueIndex();
}
}
};

View file

@ -0,0 +1,216 @@
/*
**
** Hardware render profiling info
**
**---------------------------------------------------------------------------
** Copyright 2007-2018 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "g_level.h"
#include "c_console.h"
#include "c_dispatch.h"
#include "r_utility.h"
#include "v_video.h"
#include "g_levellocals.h"
#include "hw_clock.h"
#include "i_time.h"
glcycle_t RenderWall,SetupWall,ClipWall;
glcycle_t RenderFlat,SetupFlat;
glcycle_t RenderSprite,SetupSprite;
glcycle_t All, Finish, PortalAll, Bsp;
glcycle_t ProcessAll, PostProcess;
glcycle_t RenderAll;
glcycle_t Dirty;
glcycle_t drawcalls;
glcycle_t twoD, Flush3D;
glcycle_t MTWait, WTTotal;
int vertexcount, flatvertices, flatprimitives;
int rendered_lines,rendered_flats,rendered_sprites,render_vertexsplit,render_texsplit,rendered_decals, rendered_portals;
int iter_dlightf, iter_dlight, draw_dlight, draw_dlightf;
void ResetProfilingData()
{
All.Reset();
All.Clock();
Bsp.Reset();
PortalAll.Reset();
RenderAll.Reset();
ProcessAll.Reset();
PostProcess.Reset();
RenderWall.Reset();
SetupWall.Reset();
ClipWall.Reset();
RenderFlat.Reset();
SetupFlat.Reset();
RenderSprite.Reset();
SetupSprite.Reset();
drawcalls.Reset();
MTWait.Reset();
WTTotal.Reset();
flatvertices=flatprimitives=vertexcount=0;
render_texsplit=render_vertexsplit=rendered_lines=rendered_flats=rendered_sprites=rendered_decals=rendered_portals = 0;
}
//-----------------------------------------------------------------------------
//
// Rendering statistics
//
//-----------------------------------------------------------------------------
static void AppendRenderTimes(FString &str)
{
double setupwall = SetupWall.TimeMS();
double clipwall = ClipWall.TimeMS();
double bsp = Bsp.TimeMS() - ClipWall.TimeMS();
str.AppendFormat("BSP = %2.3f, Clip=%2.3f\n"
"W: Render=%2.3f, Setup=%2.3f\n"
"F: Render=%2.3f, Setup=%2.3f\n"
"S: Render=%2.3f, Setup=%2.3f\n"
"2D: %2.3f Finish3D: %2.3f\n"
"Main thread total=%2.3f, Main thread waiting=%2.3f Worker thread total=%2.3f, Worker thread waiting=%2.3f\n"
"All=%2.3f, Render=%2.3f, Setup=%2.3f, Portal=%2.3f, Drawcalls=%2.3f, Postprocess=%2.3f, Finish=%2.3f\n",
bsp, clipwall,
RenderWall.TimeMS(), setupwall,
RenderFlat.TimeMS(), SetupFlat.TimeMS(),
RenderSprite.TimeMS(), SetupSprite.TimeMS(),
twoD.TimeMS(), Flush3D.TimeMS() - twoD.TimeMS(),
MTWait.TimeMS() + Bsp.TimeMS(), MTWait.TimeMS(), WTTotal.TimeMS(), WTTotal.TimeMS() - setupwall - SetupFlat.TimeMS() - SetupSprite.TimeMS(),
All.TimeMS() + Finish.TimeMS(), RenderAll.TimeMS(), ProcessAll.TimeMS(), PortalAll.TimeMS(), drawcalls.TimeMS(), PostProcess.TimeMS(), Finish.TimeMS());
}
static void AppendRenderStats(FString &out)
{
out.AppendFormat("Walls: %d (%d splits, %d t-splits, %d vertices)\n"
"Flats: %d (%d primitives, %d vertices)\n"
"Sprites: %d, Decals=%d, Portals: %d\n",
rendered_lines, render_vertexsplit, render_texsplit, vertexcount, rendered_flats, flatprimitives, flatvertices, rendered_sprites,rendered_decals, rendered_portals );
}
static void AppendLightStats(FString &out)
{
out.AppendFormat("DLight - Walls: %d processed, %d rendered - Flats: %d processed, %d rendered\n",
iter_dlight, draw_dlight, iter_dlightf, draw_dlightf );
}
ADD_STAT(rendertimes)
{
static FString buff;
static int64_t lasttime=0;
int64_t t=I_msTime();
if (t-lasttime>1000)
{
buff.Truncate(0);
AppendRenderTimes(buff);
lasttime=t;
}
return buff;
}
ADD_STAT(renderstats)
{
FString out;
AppendRenderStats(out);
return out;
}
ADD_STAT(lightstats)
{
FString out;
AppendLightStats(out);
return out;
}
static int printstats;
static bool switchfps;
static uint64_t waitstart;
EXTERN_CVAR(Bool, vid_fps)
void CheckBench()
{
if (printstats && ConsoleState == c_up)
{
// if we started the FPS counter ourselves or ran from the console
// we need to wait for it to stabilize before using it.
if (waitstart > 0 && I_msTime() - waitstart < 5000) return;
FString compose;
auto &vp = r_viewpoint;
auto Level = vp.ViewLevel;
compose.Format("Map %s: \"%s\",\nx = %1.4f, y = %1.4f, z = %1.4f, angle = %1.4f, pitch = %1.4f\n",
Level->MapName.GetChars(), Level->LevelName.GetChars(), vp.Pos.X, vp.Pos.Y, vp.Pos.Z, vp.Angles.Yaw.Degrees, vp.Angles.Pitch.Degrees);
AppendRenderStats(compose);
AppendRenderTimes(compose);
AppendLightStats(compose);
//AppendMissingTextureStats(compose);
compose.AppendFormat("%llu fps\n\n", (unsigned long long)screen->GetLastFPS());
FILE *f = fopen("benchmarks.txt", "at");
if (f != NULL)
{
fputs(compose.GetChars(), f);
fclose(f);
}
Printf("Benchmark info saved\n");
if (switchfps) vid_fps = false;
printstats = false;
}
}
CCMD(bench)
{
printstats = true;
if (vid_fps == 0)
{
vid_fps = 1;
waitstart = I_msTime();
switchfps = true;
}
else
{
if (ConsoleState == c_up) waitstart = I_msTime();
switchfps = false;
}
C_HideConsole ();
}
bool glcycle_t::active = false;
void checkBenchActive()
{
FStat *stat = FStat::FindStat("rendertimes");
glcycle_t::active = ((stat != NULL && stat->isActive()) || printstats);
}

View file

@ -0,0 +1,29 @@
#ifndef __GL_CLOCK_H
#define __GL_CLOCK_H
#include "stats.h"
#include "x86.h"
#include "m_fixed.h"
extern glcycle_t RenderWall,SetupWall,ClipWall;
extern glcycle_t RenderFlat,SetupFlat;
extern glcycle_t RenderSprite,SetupSprite;
extern glcycle_t All, Finish, PortalAll, Bsp;
extern glcycle_t ProcessAll, PostProcess;
extern glcycle_t RenderAll;
extern glcycle_t Dirty;
extern glcycle_t drawcalls, twoD, Flush3D;
extern glcycle_t MTWait, WTTotal;
extern int iter_dlightf, iter_dlight, draw_dlight, draw_dlightf;
extern int rendered_lines,rendered_flats,rendered_sprites,rendered_decals,render_vertexsplit,render_texsplit;
extern int rendered_portals;
extern int vertexcount, flatvertices, flatprimitives;
void ResetProfilingData();
void CheckBench();
void checkBenchActive();
#endif

View file

@ -0,0 +1,118 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2005-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 "c_cvars.h"
#include "c_dispatch.h"
#include "v_video.h"
#include "hw_cvars.h"
#include "hwrenderer/textures/hw_material.h"
#include "menu/menu.h"
// OpenGL stuff moved here
// GL related CVARs
CVAR(Bool, gl_portals, true, 0)
CVAR(Bool, gl_noquery, false, 0)
CVAR(Bool,gl_mirrors,true,0) // This is for debugging only!
CVAR(Bool,gl_mirror_envmap, true, CVAR_GLOBALCONFIG|CVAR_ARCHIVE)
CVAR(Bool, gl_seamless, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CUSTOM_CVAR(Int, r_mirror_recursions,4,CVAR_GLOBALCONFIG|CVAR_ARCHIVE)
{
if (self<0) self=0;
if (self>10) self=10;
}
bool gl_plane_reflection_i; // This is needed in a header that cannot include the CVAR stuff...
CUSTOM_CVAR(Bool, gl_plane_reflection, true, CVAR_GLOBALCONFIG|CVAR_ARCHIVE)
{
gl_plane_reflection_i = self;
}
CUSTOM_CVAR(Bool, gl_render_precise, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
{
gl_seamless=self;
}
CVAR (Float, vid_brightness, 0.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (Float, vid_contrast, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR (Float, vid_saturation, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR(Int, gl_satformula, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
//==========================================================================
//
// Texture CVARs
//
//==========================================================================
CUSTOM_CVAR(Float,gl_texture_filter_anisotropic,8.0f,CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL)
{
screen->TextureFilterChanged();
}
CCMD(gl_flush)
{
TexMan.FlushAll();
}
CUSTOM_CVAR(Int, gl_texture_filter, 4, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL)
{
if (self < 0 || self > 6) self=4;
screen->TextureFilterChanged();
}
CUSTOM_CVAR(Bool, gl_texture_usehires, true, CVAR_ARCHIVE|CVAR_NOINITCALL)
{
TexMan.FlushAll();
}
CVAR(Bool, gl_precache, false, CVAR_ARCHIVE)
CVAR(Bool, gl_trimsprites, true, CVAR_ARCHIVE);
//==========================================================================
//
// Sprite CVARs
//
//==========================================================================
CVAR(Bool, gl_usecolorblending, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CVAR(Bool, gl_spritebrightfog, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
CVAR(Bool, gl_sprite_blend, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
CVAR(Int, gl_spriteclip, 1, CVAR_ARCHIVE)
CVAR(Float, gl_sclipthreshold, 10.0, CVAR_ARCHIVE)
CVAR(Float, gl_sclipfactor, 1.8f, CVAR_ARCHIVE)
CVAR(Int, gl_particles_style, 2, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // 0 = square, 1 = round, 2 = smooth
CVAR(Int, gl_billboard_mode, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Bool, gl_billboard_faces_camera, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Bool, gl_billboard_particles, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Int, gl_enhanced_nv_stealth, 3, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CUSTOM_CVAR(Int, gl_fuzztype, 0, CVAR_ARCHIVE)
{
if (self < 0 || self > 8) self = 0;
}
CUSTOM_CVAR(Int, gl_shadowmap_filter, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
if (self < 0 || self > 8) self = 1;
}

View file

@ -0,0 +1,70 @@
#pragma once
#include "r_defs.h"
#include "c_cvars.h"
EXTERN_CVAR(Bool,gl_enhanced_nightvision)
EXTERN_CVAR(Int, screenblocks);
EXTERN_CVAR(Bool, gl_texture)
EXTERN_CVAR(Int, gl_texture_filter)
EXTERN_CVAR(Float, gl_texture_filter_anisotropic)
EXTERN_CVAR(Int, gl_texture_format)
EXTERN_CVAR(Bool, gl_texture_usehires)
EXTERN_CVAR(Bool, gl_usefb)
EXTERN_CVAR(Int, gl_weaponlight)
EXTERN_CVAR (Bool, gl_light_sprites);
EXTERN_CVAR (Bool, gl_light_particles);
EXTERN_CVAR (Bool, gl_light_shadowmap);
EXTERN_CVAR (Int, gl_shadowmap_quality);
EXTERN_CVAR(Int, gl_fogmode)
EXTERN_CVAR(Int, gl_lightmode)
EXTERN_CVAR(Bool,gl_mirror_envmap)
EXTERN_CVAR(Bool,gl_mirrors)
EXTERN_CVAR(Bool,gl_mirror_envmap)
EXTERN_CVAR(Bool, gl_seamless)
EXTERN_CVAR(Float, gl_mask_threshold)
EXTERN_CVAR(Float, gl_mask_sprite_threshold)
EXTERN_CVAR(Int, gl_multisample)
EXTERN_CVAR(Bool, gl_bloom)
EXTERN_CVAR(Float, gl_bloom_amount)
EXTERN_CVAR(Int, gl_bloom_kernel_size)
EXTERN_CVAR(Int, gl_tonemap)
EXTERN_CVAR(Float, gl_exposure)
EXTERN_CVAR(Bool, gl_lens)
EXTERN_CVAR(Float, gl_lens_k)
EXTERN_CVAR(Float, gl_lens_kcube)
EXTERN_CVAR(Float, gl_lens_chromatic)
EXTERN_CVAR(Int, gl_ssao)
EXTERN_CVAR(Int, gl_ssao_portals)
EXTERN_CVAR(Float, gl_ssao_strength)
EXTERN_CVAR(Int, gl_ssao_debug)
EXTERN_CVAR(Float, gl_ssao_bias)
EXTERN_CVAR(Float, gl_ssao_radius)
EXTERN_CVAR(Float, gl_ssao_blur_amount)
EXTERN_CVAR(Int, gl_debug_level)
EXTERN_CVAR(Bool, gl_debug_breakpoint)
EXTERN_CVAR(Bool, gl_usecolorblending)
EXTERN_CVAR(Bool, gl_spritebrightfog)
EXTERN_CVAR(Bool, gl_sprite_blend)
EXTERN_CVAR(Int, gl_spriteclip)
EXTERN_CVAR(Float, gl_sclipthreshold)
EXTERN_CVAR(Float, gl_sclipfactor)
EXTERN_CVAR(Int, gl_particles_style)
EXTERN_CVAR(Int, gl_billboard_mode)
EXTERN_CVAR(Bool, gl_billboard_faces_camera)
EXTERN_CVAR(Bool, gl_billboard_particles)
EXTERN_CVAR(Int, gl_enhanced_nv_stealth)
EXTERN_CVAR(Int, gl_fuzztype)
EXTERN_CVAR(Int, gl_shadowmap_filter)

View file

@ -0,0 +1,221 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 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 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/
//
//--------------------------------------------------------------------------
//
/*
** 2d drawer
** Renderer interface
**
*/
#include "doomstat.h"
#include "v_video.h"
#include "cmdlib.h"
#include "r_defs.h"
#include "hwrenderer/data/buffers.h"
#include "hwrenderer/data/flatvertices.h"
#include "hwrenderer/data/hw_viewpointbuffer.h"
#include "hwrenderer/utility/hw_clock.h"
#include "hwrenderer/utility/hw_cvars.h"
#include "hwrenderer/scene/hw_renderstate.h"
#include "r_videoscale.h"
//===========================================================================
//
// Vertex buffer for 2D drawer
//
//===========================================================================
class F2DVertexBuffer
{
IVertexBuffer *mVertexBuffer;
IIndexBuffer *mIndexBuffer;
public:
F2DVertexBuffer()
{
mVertexBuffer = screen->CreateVertexBuffer();
mIndexBuffer = screen->CreateIndexBuffer();
static const FVertexBufferAttribute format[] = {
{ 0, VATTR_VERTEX, VFmt_Float3, (int)myoffsetof(F2DDrawer::TwoDVertex, x) },
{ 0, VATTR_TEXCOORD, VFmt_Float2, (int)myoffsetof(F2DDrawer::TwoDVertex, u) },
{ 0, VATTR_COLOR, VFmt_Byte4, (int)myoffsetof(F2DDrawer::TwoDVertex, color0) }
};
mVertexBuffer->SetFormat(1, 3, sizeof(F2DDrawer::TwoDVertex), format);
}
~F2DVertexBuffer()
{
delete mIndexBuffer;
delete mVertexBuffer;
}
void UploadData(F2DDrawer::TwoDVertex *vertices, int vertcount, int *indices, int indexcount)
{
mVertexBuffer->SetData(vertcount * sizeof(*vertices), vertices, false);
mIndexBuffer->SetData(indexcount * sizeof(unsigned int), indices, false);
}
std::pair<IVertexBuffer *, IIndexBuffer *> GetBufferObjects() const
{
return std::make_pair(mVertexBuffer, mIndexBuffer);
}
};
//===========================================================================
//
// Draws the 2D stuff. This is the version for OpenGL 3 and later.
//
//===========================================================================
CVAR(Bool, gl_aalines, false, CVAR_ARCHIVE)
void Draw2D(F2DDrawer *drawer, FRenderState &state)
{
twoD.Clock();
const auto &mScreenViewport = screen->mScreenViewport;
state.SetViewport(mScreenViewport.left, mScreenViewport.top, mScreenViewport.width, mScreenViewport.height);
screen->mViewpoints->Set2D(state, screen->GetWidth(), screen->GetHeight());
state.EnableDepthTest(false);
state.EnableMultisampling(false);
state.EnableLineSmooth(gl_aalines);
auto &vertices = drawer->mVertices;
auto &indices = drawer->mIndices;
auto &commands = drawer->mData;
if (commands.Size() == 0)
{
twoD.Unclock();
return;
}
for (auto &v : vertices)
{
// Change from BGRA to RGBA
std::swap(v.color0.r, v.color0.b);
}
F2DVertexBuffer vb;
vb.UploadData(&vertices[0], vertices.Size(), &indices[0], indices.Size());
state.SetVertexBuffer(&vb);
state.EnableFog(false);
for(auto &cmd : commands)
{
int gltrans = -1;
state.SetRenderStyle(cmd.mRenderStyle);
state.EnableBrightmap(!(cmd.mRenderStyle.Flags & STYLEF_ColorIsFixed));
state.EnableFog(2); // Special 2D mode 'fog'.
state.SetTextureMode(cmd.mDrawMode);
int sciX, sciY, sciW, sciH;
if (cmd.mFlags & F2DDrawer::DTF_Scissor)
{
// scissor test doesn't use the current viewport for the coordinates, so use real screen coordinates
// Note that the origin here is the lower left corner!
sciX = screen->ScreenToWindowX(cmd.mScissor[0]);
sciY = screen->ScreenToWindowY(cmd.mScissor[3]);
sciW = screen->ScreenToWindowX(cmd.mScissor[2]) - sciX;
sciH = screen->ScreenToWindowY(cmd.mScissor[1]) - sciY;
}
else
{
sciX = sciY = sciW = sciH = -1;
}
state.SetScissor(sciX, sciY, sciW, sciH);
if (cmd.mSpecialColormap[0].a != 0)
{
state.SetTextureMode(TM_FIXEDCOLORMAP);
state.SetObjectColor(cmd.mSpecialColormap[0]);
state.SetObjectColor2(cmd.mSpecialColormap[1]);
}
state.SetFog(cmd.mColor1, 0);
state.SetColor(1, 1, 1, 1, cmd.mDesaturate);
state.AlphaFunc(Alpha_GEqual, 0.f);
if (cmd.mTexture != nullptr)
{
auto mat = FMaterial::ValidateTexture(cmd.mTexture, false);
if (mat == nullptr) continue;
if (gltrans == -1 && cmd.mTranslation != nullptr) gltrans = cmd.mTranslation->GetUniqueIndex();
state.SetMaterial(mat, cmd.mFlags & F2DDrawer::DTF_Wrap ? CLAMP_NONE : CLAMP_XY_NOMIP, -gltrans, -1);
state.EnableTexture(true);
// Canvas textures are stored upside down
if (cmd.mTexture->isHardwareCanvas())
{
state.mTextureMatrix.loadIdentity();
state.mTextureMatrix.scale(1.f, -1.f, 1.f);
state.mTextureMatrix.translate(0.f, 1.f, 0.0f);
state.EnableTextureMatrix(true);
}
if (cmd.mFlags & F2DDrawer::DTF_Burn)
{
state.SetEffect(EFF_BURN);
}
}
else
{
state.EnableTexture(false);
}
switch (cmd.mType)
{
case F2DDrawer::DrawTypeTriangles:
state.DrawIndexed(DT_Triangles, cmd.mIndexIndex, cmd.mIndexCount);
break;
case F2DDrawer::DrawTypeLines:
state.Draw(DT_Lines, cmd.mVertIndex, cmd.mVertCount);
break;
case F2DDrawer::DrawTypePoints:
state.Draw(DT_Points, cmd.mVertIndex, cmd.mVertCount);
break;
}
state.SetObjectColor(0xffffffff);
state.SetObjectColor2(0);
state.SetAddColor(0);
state.EnableTextureMatrix(false);
state.SetEffect(EFF_NONE);
}
state.SetScissor(-1, -1, -1, -1);
state.SetRenderStyle(STYLE_Translucent);
state.SetVertexBuffer(screen->mVertexData);
state.EnableTexture(true);
state.EnableBrightmap(true);
state.SetTextureMode(TM_NORMAL);
state.EnableFog(false);
state.ResetColor();
twoD.Unclock();
}

View file

@ -0,0 +1,289 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2002-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 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_light.cpp
** Light level / fog management / dynamic lights
**
**/
#include "c_cvars.h"
#include "r_sky.h"
#include "g_levellocals.h"
#include "hw_lighting.h"
#include "hwrenderer/scene/hw_drawinfo.h"
// externally settable lighting properties
static float distfogtable[2][256]; // light to fog conversion table for black fog
CVAR(Int, gl_weaponlight, 8, CVAR_ARCHIVE);
CVAR(Bool, gl_enhanced_nightvision, true, CVAR_ARCHIVE|CVAR_NOINITCALL)
//==========================================================================
//
// Sets up the fog tables
//
//==========================================================================
CUSTOM_CVAR(Int, gl_distfog, 70, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
for (int i = 0; i < 256; i++)
{
if (i < 164)
{
distfogtable[0][i] = (float)((gl_distfog >> 1) + (gl_distfog)*(164 - i) / 164);
}
else if (i < 230)
{
distfogtable[0][i] = (float)((gl_distfog >> 1) - (gl_distfog >> 1)*(i - 164) / (230 - 164));
}
else distfogtable[0][i] = 0;
if (i < 128)
{
distfogtable[1][i] = 6.f + (float)((gl_distfog >> 1) + (gl_distfog)*(128 - i) / 48);
}
else if (i < 216)
{
distfogtable[1][i] = (216.f - i) / ((216.f - 128.f)) * gl_distfog / 10;
}
else distfogtable[1][i] = 0;
}
}
CUSTOM_CVAR(Int,gl_fogmode,1,CVAR_ARCHIVE|CVAR_NOINITCALL)
{
if (self>2) self=2;
if (self<0) self=0;
}
//==========================================================================
//
// Get current light level
//
//==========================================================================
int HWDrawInfo::CalcLightLevel(int lightlevel, int rellight, bool weapon, int blendfactor)
{
int light;
if (lightlevel <= 0) return 0;
bool darklightmode = (isDarkLightMode()) || (isSoftwareLighting() && blendfactor > 0);
if (darklightmode && lightlevel < 192 && !weapon)
{
if (lightlevel > 100)
{
light = xs_CRoundToInt(192.f - (192 - lightlevel)* 1.87f);
if (light + rellight < 20)
{
light = 20 + (light + rellight - 20) / 5;
}
else
{
light += rellight;
}
}
else
{
light = (lightlevel + rellight) / 5;
}
}
else
{
light=lightlevel+rellight;
}
// Fake contrast should never turn a positive value into 0.
return clamp(light, 1, 255);
}
//==========================================================================
//
// Get current light color
//
//==========================================================================
PalEntry HWDrawInfo::CalcLightColor(int light, PalEntry pe, int blendfactor)
{
int r,g,b;
if (blendfactor == 0)
{
if (isSoftwareLighting())
{
return pe;
}
r = pe.r * light / 255;
g = pe.g * light / 255;
b = pe.b * light / 255;
}
else
{
// This is what Legacy does with colored light in 3D volumes. No, it doesn't really make sense...
// It also doesn't translate well to software style lighting.
int mixlight = light * (255 - blendfactor);
r = (mixlight + pe.r * blendfactor) / 255;
g = (mixlight + pe.g * blendfactor) / 255;
b = (mixlight + pe.b * blendfactor) / 255;
}
return PalEntry(255, uint8_t(r), uint8_t(g), uint8_t(b));
}
//==========================================================================
//
// calculates the current fog density
//
// Rules for fog:
//
// 1. If bit 4 of gl_lightmode is set always use the level's fog density.
// This is what Legacy's GL render does.
// 2. black fog means no fog and always uses the distfogtable based on the level's fog density setting
// 3. If outside fog is defined and the current fog color is the same as the outside fog
// the engine always uses the outside fog density to make the fog uniform across the Level->
// If the outside fog's density is undefined it uses the level's fog density and if that is
// not defined it uses a default of 70.
// 4. If a global fog density is specified it is being used for all fog on the level
// 5. If none of the above apply fog density is based on the light level as for the software renderer.
//
//==========================================================================
float HWDrawInfo::GetFogDensity(int lightlevel, PalEntry fogcolor, int sectorfogdensity, int blendfactor)
{
float density;
auto oldlightmode = lightmode;
if (isSoftwareLighting() && blendfactor > 0) lightmode = ELightMode::Doom; // The blendfactor feature does not work with software-style lighting.
if (lightmode == ELightMode::DoomLegacy)
{
// uses approximations of Legacy's default settings.
density = Level->fogdensity ? (float)Level->fogdensity : 18;
}
else if (sectorfogdensity != 0)
{
// case 1: Sector has an explicit fog density set.
density = (float)sectorfogdensity;
}
else if ((fogcolor.d & 0xffffff) == 0)
{
// case 2: black fog
if ((!isSoftwareLighting() || blendfactor > 0) && !(Level->flags3 & LEVEL3_NOLIGHTFADE))
{
density = distfogtable[lightmode != ELightMode::LinearStandard][hw_ClampLight(lightlevel)];
}
else
{
density = 0;
}
}
else if (Level->outsidefogdensity != 0 && APART(Level->info->outsidefog) != 0xff && (fogcolor.d & 0xffffff) == (Level->info->outsidefog & 0xffffff))
{
// case 3. outsidefogdensity has already been set as needed
density = (float)Level->outsidefogdensity;
}
else if (Level->fogdensity != 0)
{
// case 4: level has fog density set
density = (float)Level->fogdensity;
}
else if (lightlevel < 248)
{
// case 5: use light level
density = (float)clamp<int>(255 - lightlevel, 30, 255);
}
else
{
density = 0.f;
}
return density;
}
//==========================================================================
//
// Check if the current linedef is a candidate for a fog boundary
//
// Requirements for a fog boundary:
// - front sector has no fog
// - back sector has fog
// - at least one of both does not have a sky ceiling.
//
//==========================================================================
bool HWDrawInfo::CheckFog(sector_t *frontsector, sector_t *backsector)
{
if (frontsector == backsector) return false; // there can't be a boundary if both sides are in the same sector.
// Check for fog boundaries. This needs a few more checks for the sectors
PalEntry fogcolor = frontsector->Colormap.FadeColor;
if ((fogcolor.d & 0xffffff) == 0)
{
return false;
}
else if (fogcolor.a != 0)
{
}
else if (Level->outsidefogdensity != 0 && APART(Level->info->outsidefog) != 0xff && (fogcolor.d & 0xffffff) == (Level->info->outsidefog & 0xffffff))
{
}
else if (Level->fogdensity!=0 || lightmode == ELightMode::DoomLegacy)
{
// case 3: level has fog density set
}
else
{
// case 4: use light level
if (frontsector->lightlevel >= 248) return false;
}
fogcolor = backsector->Colormap.FadeColor;
if ((fogcolor.d & 0xffffff) == 0)
{
}
else if (Level->outsidefogdensity != 0 && APART(Level->info->outsidefog) != 0xff && (fogcolor.d & 0xffffff) == (Level->info->outsidefog & 0xffffff))
{
return false;
}
else if (Level->fogdensity!=0 || lightmode == ELightMode::DoomLegacy)
{
// case 3: level has fog density set
return false;
}
else
{
// case 4: use light level
if (backsector->lightlevel < 248) return false;
}
// in all other cases this might create more problems than it solves.
return ((frontsector->GetTexture(sector_t::ceiling)!=skyflatnum ||
backsector->GetTexture(sector_t::ceiling)!=skyflatnum));
}

View file

@ -0,0 +1,21 @@
#pragma once
#include "c_cvars.h"
#include "v_palette.h"
#include "templates.h"
#include "r_utility.h"
struct Colormap;
inline int hw_ClampLight(int lightlevel)
{
return clamp(lightlevel, 0, 255);
}
EXTERN_CVAR(Int, gl_weaponlight);
inline int getExtraLight()
{
return r_viewpoint.extralight * gl_weaponlight;
}

View file

@ -0,0 +1,187 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2004-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 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/
//
//--------------------------------------------------------------------------
//
/*
** hw_shaderpatcher.cpp
**
** Modifies shader source to account for different syntax versions or engine changes.
**
*/
#include "hw_shaderpatcher.h"
static bool IsGlslWhitespace(char c)
{
switch (c)
{
case ' ':
case '\r':
case '\n':
case '\t':
case '\f':
return true;
default:
return false;
}
}
static FString NextGlslToken(const char *chars, long len, long &pos)
{
// Eat whitespace
long tokenStart = pos;
while (tokenStart != len && IsGlslWhitespace(chars[tokenStart]))
tokenStart++;
// Find token end
long tokenEnd = tokenStart;
while (tokenEnd != len && !IsGlslWhitespace(chars[tokenEnd]) && chars[tokenEnd] != ';')
tokenEnd++;
pos = tokenEnd;
return FString(chars + tokenStart, tokenEnd - tokenStart);
}
static bool isShaderType(const char *name)
{
return !strcmp(name, "sampler1D") || !strcmp(name, "sampler2D") || !strcmp(name, "sampler3D") || !strcmp(name, "samplerCube") || !strcmp(name, "sampler2DMS");
}
FString RemoveLegacyUserUniforms(FString code)
{
// User shaders must declare their uniforms via the GLDEFS file.
// The following code searches for legacy uniform declarations in the shader itself and replaces them with whitespace.
long len = (long)code.Len();
char *chars = code.LockBuffer();
long startIndex = 0;
while (true)
{
long matchIndex = code.IndexOf("uniform", startIndex);
if (matchIndex == -1)
break;
bool isLegacyUniformName = false;
bool isKeywordStart = matchIndex == 0 || IsGlslWhitespace(chars[matchIndex - 1]);
bool isKeywordEnd = matchIndex + 7 == len || IsGlslWhitespace(chars[matchIndex + 7]);
if (isKeywordStart && isKeywordEnd)
{
long pos = matchIndex + 7;
FString type = NextGlslToken(chars, len, pos);
FString identifier = NextGlslToken(chars, len, pos);
isLegacyUniformName = type.Compare("float") == 0 && identifier.Compare("timer") == 0;
}
if (isLegacyUniformName)
{
long statementEndIndex = code.IndexOf(';', matchIndex + 7);
if (statementEndIndex == -1)
statementEndIndex = len;
for (long i = matchIndex; i <= statementEndIndex; i++)
{
if (!IsGlslWhitespace(chars[i]))
chars[i] = ' ';
}
startIndex = statementEndIndex;
}
else
{
startIndex = matchIndex + 7;
}
}
code.UnlockBuffer();
return code;
}
FString RemoveSamplerBindings(FString code, TArray<std::pair<FString, int>> &samplerstobind)
{
long len = (long)code.Len();
char *chars = code.LockBuffer();
long startIndex = 0;
long startpos, endpos;
while (true)
{
long matchIndex = code.IndexOf("layout(binding", startIndex);
if (matchIndex == -1)
break;
bool isSamplerUniformName = false;
bool isKeywordStart = matchIndex == 0 || IsGlslWhitespace(chars[matchIndex - 1]);
bool isKeywordEnd = matchIndex + 14 == len || IsGlslWhitespace(chars[matchIndex + 14]) || chars[matchIndex + 14] == '=';
if (isKeywordStart && isKeywordEnd)
{
long pos = matchIndex + 14;
startpos = matchIndex;
while (IsGlslWhitespace(chars[pos])) pos++;
if (chars[pos] == '=')
{
char *p;
pos++;
auto val = strtol(&chars[pos], &p, 0);
if (p != &chars[pos])
{
pos = long(p - chars);
while (IsGlslWhitespace(chars[pos])) pos++;
if (chars[pos] == ')')
{
endpos = ++pos;
FString uniform = NextGlslToken(chars, len, pos);
FString type = NextGlslToken(chars, len, pos);
FString identifier = NextGlslToken(chars, len, pos);
isSamplerUniformName = uniform.Compare("uniform") == 0 && isShaderType(type);
if (isSamplerUniformName)
{
samplerstobind.Push(std::make_pair(identifier, val));
for (auto pos = startpos; pos < endpos; pos++)
{
if (!IsGlslWhitespace(chars[pos]))
chars[pos] = ' ';
}
}
}
}
}
}
if (isSamplerUniformName)
{
startIndex = endpos;
}
else
{
startIndex = matchIndex + 7;
}
}
code.UnlockBuffer();
return code;
}

View file

@ -0,0 +1,9 @@
#pragma once
#include "tarray.h"
#include "zstring.h"
#include "utility"
FString RemoveLegacyUserUniforms(FString code);
FString RemoveSamplerBindings(FString code, TArray<std::pair<FString, int>> &samplerstobind); // For GL 3.3 compatibility which cannot declare sampler bindings in the sampler source.

View file

@ -0,0 +1,176 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2015 Christopher Bruns
// 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_stereo_leftright.cpp
** Offsets for left and right eye views
**
*/
#include "vectors.h" // RAD2DEG
#include "doomtype.h" // M_PI
#include "hwrenderer/utility/hw_cvars.h"
#include "hw_vrmodes.h"
#include "v_video.h"
// Set up 3D-specific console variables:
CVAR(Int, vr_mode, 0, CVAR_GLOBALCONFIG)
// switch left and right eye views
CVAR(Bool, vr_swap_eyes, false, CVAR_GLOBALCONFIG)
// intraocular distance in meters
CVAR(Float, vr_ipd, 0.062f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) // METERS
// distance between viewer and the display screen
CVAR(Float, vr_screendist, 0.80f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // METERS
// default conversion between (vertical) DOOM units and meters
CVAR(Float, vr_hunits_per_meter, 41.0f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // METERS
#define isqrt2 0.7071067812f
static VRMode vrmi_mono = { 1, 1.f, 1.f, 1.f,{ { 0.f, 1.f },{ 0.f, 0.f } } };
static VRMode vrmi_stereo = { 2, 1.f, 1.f, 1.f,{ { -.5f, 1.f },{ .5f, 1.f } } };
static VRMode vrmi_sbsfull = { 2, .5f, 1.f, 2.f,{ { -.5f, .5f },{ .5f, .5f } } };
static VRMode vrmi_sbssquished = { 2, .5f, 1.f, 1.f,{ { -.5f, 1.f },{ .5f, 1.f } } };
static VRMode vrmi_lefteye = { 1, 1.f, 1.f, 1.f, { { -.5f, 1.f },{ 0.f, 0.f } } };
static VRMode vrmi_righteye = { 1, 1.f, 1.f, 1.f,{ { .5f, 1.f },{ 0.f, 0.f } } };
static VRMode vrmi_topbottom = { 2, 1.f, .5f, 1.f,{ { -.5f, 1.f },{ .5f, 1.f } } };
static VRMode vrmi_checker = { 2, isqrt2, isqrt2, 1.f,{ { -.5f, 1.f },{ .5f, 1.f } } };
const VRMode *VRMode::GetVRMode(bool toscreen)
{
switch (toscreen && vid_rendermode == 4 ? vr_mode : 0)
{
default:
case VR_MONO:
return &vrmi_mono;
case VR_GREENMAGENTA:
case VR_REDCYAN:
case VR_QUADSTEREO:
case VR_AMBERBLUE:
return &vrmi_stereo;
case VR_SIDEBYSIDESQUISHED:
case VR_COLUMNINTERLEAVED:
return &vrmi_sbssquished;
case VR_SIDEBYSIDEFULL:
return &vrmi_sbsfull;
case VR_TOPBOTTOM:
case VR_ROWINTERLEAVED:
return &vrmi_topbottom;
case VR_LEFTEYEVIEW:
return &vrmi_lefteye;
case VR_RIGHTEYEVIEW:
return &vrmi_righteye;
case VR_CHECKERINTERLEAVED:
return &vrmi_checker;
}
}
void VRMode::AdjustViewport(DFrameBuffer *screen) const
{
screen->mSceneViewport.height = (int)(screen->mSceneViewport.height * mVerticalViewportScale);
screen->mSceneViewport.top = (int)(screen->mSceneViewport.top * mVerticalViewportScale);
screen->mSceneViewport.width = (int)(screen->mSceneViewport.width * mHorizontalViewportScale);
screen->mSceneViewport.left = (int)(screen->mSceneViewport.left * mHorizontalViewportScale);
screen->mScreenViewport.height = (int)(screen->mScreenViewport.height * mVerticalViewportScale);
screen->mScreenViewport.top = (int)(screen->mScreenViewport.top * mVerticalViewportScale);
screen->mScreenViewport.width = (int)(screen->mScreenViewport.width * mHorizontalViewportScale);
screen->mScreenViewport.left = (int)(screen->mScreenViewport.left * mHorizontalViewportScale);
}
VSMatrix VRMode::GetHUDSpriteProjection() const
{
VSMatrix mat;
int w = screen->GetWidth();
int h = screen->GetHeight();
float scaled_w = w / mWeaponProjectionScale;
float left_ofs = (w - scaled_w) / 2.f;
mat.ortho(left_ofs, left_ofs + scaled_w, (float)h, 0, -1.0f, 1.0f);
return mat;
}
float VREyeInfo::getShift() const
{
auto res = mShiftFactor * vr_ipd;
return vr_swap_eyes ? -res : res;
}
VSMatrix VREyeInfo::GetProjection(float fov, float aspectRatio, float fovRatio) const
{
VSMatrix result;
if (mShiftFactor == 0)
{
float fovy = (float)(2 * RAD2DEG(atan(tan(DEG2RAD(fov) / 2) / fovRatio)));
result.perspective(fovy, aspectRatio, screen->GetZNear(), screen->GetZFar());
return result;
}
else
{
double zNear = screen->GetZNear();
double zFar = screen->GetZFar();
// For stereo 3D, use asymmetric frustum shift in projection matrix
// Q: shouldn't shift vary with roll angle, at least for desktop display?
// A: No. (lab) roll is not measured on desktop display (yet)
double frustumShift = zNear * getShift() / vr_screendist; // meters cancel, leaving doom units
// double frustumShift = 0; // Turning off shift for debugging
double fH = zNear * tan(DEG2RAD(fov) / 2) / fovRatio;
double fW = fH * aspectRatio * mScaleFactor;
double left = -fW - frustumShift;
double right = fW - frustumShift;
double bottom = -fH;
double top = fH;
VSMatrix result(1);
result.frustum((float)left, (float)right, (float)bottom, (float)top, (float)zNear, (float)zFar);
return result;
}
}
/* virtual */
DVector3 VREyeInfo::GetViewShift(float yaw) const
{
if (mShiftFactor == 0)
{
// pass-through for Mono view
return { 0,0,0 };
}
else
{
double dx = -cos(DEG2RAD(yaw)) * vr_hunits_per_meter * getShift();
double dy = sin(DEG2RAD(yaw)) * vr_hunits_per_meter * getShift();
return { dx, dy, 0 };
}
}

View file

@ -0,0 +1,47 @@
#pragma once
#include "r_data/matrix.h"
class DFrameBuffer;
enum
{
VR_MONO = 0,
VR_GREENMAGENTA = 1,
VR_REDCYAN = 2,
VR_SIDEBYSIDEFULL = 3,
VR_SIDEBYSIDESQUISHED = 4,
VR_LEFTEYEVIEW = 5,
VR_RIGHTEYEVIEW = 6,
VR_QUADSTEREO = 7,
VR_AMBERBLUE = 9,
VR_TOPBOTTOM = 11,
VR_ROWINTERLEAVED = 12,
VR_COLUMNINTERLEAVED = 13,
VR_CHECKERINTERLEAVED = 14
};
struct VREyeInfo
{
float mShiftFactor;
float mScaleFactor;
VSMatrix GetProjection(float fov, float aspectRatio, float fovRatio) const;
DVector3 GetViewShift(float yaw) const;
private:
float getShift() const;
};
struct VRMode
{
int mEyeCount;
float mHorizontalViewportScale;
float mVerticalViewportScale;
float mWeaponProjectionScale;
VREyeInfo mEyes[2];
static const VRMode *GetVRMode(bool toscreen = true);
void AdjustViewport(DFrameBuffer *fb) const;
VSMatrix GetHUDSpriteProjection() const;
};