- split shadowmap AABB tree into two parts. one for static and one for dynamic

- upload only the dynamic AABB subtree using glBufferSubData
- change internal raytracing stack limit from 16 to 32
- update shadowmap AABB tree after R_SetupFrame for proper frame interpolation
This commit is contained in:
Magnus Norddahl 2019-01-04 15:51:59 +01:00
parent 4f4d10ce5f
commit f10ded756e
9 changed files with 123 additions and 58 deletions

View file

@ -273,7 +273,6 @@ sector_t *FGLRenderer::RenderView(player_t* player)
fovratio = ratio; fovratio = ratio;
} }
UpdateShadowMap();
retsec = RenderViewpoint(r_viewpoint, player->camera, NULL, r_viewpoint.FieldOfView.Degrees, ratio, fovratio, true, true); retsec = RenderViewpoint(r_viewpoint, player->camera, NULL, r_viewpoint.FieldOfView.Degrees, ratio, fovratio, true, true);
} }
All.Unclock(); All.Unclock();

View file

@ -152,6 +152,8 @@ sector_t * FGLRenderer::RenderViewpoint (FRenderViewpoint &mainvp, AActor * came
{ {
R_SetupFrame (mainvp, r_viewwindow, camera); R_SetupFrame (mainvp, r_viewwindow, camera);
UpdateShadowMap();
// Render (potentially) multiple views for stereo 3d // Render (potentially) multiple views for stereo 3d
// Fixme. The view offsetting should be done with a static table and not require setup of the entire render state for the mode. // Fixme. The view offsetting should be done with a static table and not require setup of the entire render state for the mode.
auto vrmode = VRMode::GetVRMode(mainview && toscreen); auto vrmode = VRMode::GetVRMode(mainview && toscreen);

View file

@ -65,7 +65,7 @@ void GLBuffer::Bind()
} }
void GLBuffer::SetData(size_t size, void *data, bool staticdata) void GLBuffer::SetData(size_t size, const void *data, bool staticdata)
{ {
assert(nomap); // once it's mappable, it cannot be recreated anymore. assert(nomap); // once it's mappable, it cannot be recreated anymore.
Bind(); Bind();
@ -92,6 +92,12 @@ void GLBuffer::SetData(size_t size, void *data, bool staticdata)
InvalidateBufferState(); InvalidateBufferState();
} }
void GLBuffer::SetSubData(size_t offset, size_t size, const void *data)
{
Bind();
glBufferSubData(mUseType, offset, size, data);
}
void GLBuffer::Map() void GLBuffer::Map()
{ {
assert(nomap == false); // do not allow mapping of static buffers. Vulkan cannot do that so it should be blocked in OpenGL, too. assert(nomap == false); // do not allow mapping of static buffers. Vulkan cannot do that so it should be blocked in OpenGL, too.

View file

@ -22,7 +22,8 @@ protected:
GLBuffer(int usetype); GLBuffer(int usetype);
~GLBuffer(); ~GLBuffer();
void SetData(size_t size, void *data, bool staticdata) override; void SetData(size_t size, const void *data, bool staticdata) override;
void SetSubData(size_t offset, size_t size, const void *data) override;
void Map() override; void Map() override;
void Unmap() override; void Unmap() override;
void Resize(size_t newsize) override; void Resize(size_t newsize) override;

View file

@ -47,7 +47,8 @@ public:
IBuffer &operator=(const IBuffer &) = delete; IBuffer &operator=(const IBuffer &) = delete;
virtual ~IBuffer() = default; virtual ~IBuffer() = default;
virtual void SetData(size_t size, void *data, bool staticdata = true) = 0; 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 *Lock(unsigned int size) = 0;
virtual void Unlock() = 0; virtual void Unlock() = 0;
virtual void Resize(size_t newsize) = 0; virtual void Resize(size_t newsize) = 0;

View file

@ -38,35 +38,36 @@ LevelAABBTree::LevelAABBTree()
centroids.Push((v1 + v2) * 0.5f); centroids.Push((v1 + v2) * 0.5f);
} }
// Create a list of level lines we want to add: // Create the static subtree
TArray<int> line_elements; if (!GenerateTree(&centroids[0], false))
for (unsigned int i = 0; i < level.lines.Size(); i++) return;
int staticroot = nodes.Size() - 1;
dynamicStartNode = nodes.Size();
dynamicStartLine = lines.Size();
// Create the dynamic subtree
if (GenerateTree(&centroids[0], true))
{ {
if (!level.lines[i].backsector) int dynamicroot = nodes.Size() - 1;
{
#ifdef USE_POLYOBJ_SHADOWS // Create a shared root node
if (level.lines[i].sidedef[0] && (level.lines[i].sidedef[0]->Flags & WALLF_POLYOBJ)) FVector2 aabb_min, aabb_max;
polylines.Push(i); const auto &left = nodes[staticroot];
line_elements.Push(i); const auto &right = nodes[dynamicroot];
#else aabb_min.X = MIN(left.aabb_left, right.aabb_left);
if (!level.lines[i].sidedef[0] || !(level.lines[i].sidedef[0]->Flags & WALLF_POLYOBJ)) aabb_min.Y = MIN(left.aabb_top, right.aabb_top);
line_elements.Push(i); aabb_max.X = MAX(left.aabb_right, right.aabb_right);
#endif aabb_max.Y = MAX(left.aabb_bottom, right.aabb_bottom);
} nodes.Push({ aabb_min, aabb_max, staticroot, dynamicroot });
} }
// 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[0], &work_buffer[0]);
// Add the lines referenced by the leaf nodes // Add the lines referenced by the leaf nodes
lines.Resize(level.lines.Size()); lines.Resize(mapLines.Size());
for (unsigned int i = 0; i < level.lines.Size(); i++) for (unsigned int i = 0; i < mapLines.Size(); i++)
{ {
const auto &line = level.lines[i]; const auto &line = level.lines[mapLines[i]];
auto &treeline = lines[i]; auto &treeline = lines[i];
treeline.x = (float)line.v1->fX(); treeline.x = (float)line.v1->fX();
@ -76,13 +77,46 @@ LevelAABBTree::LevelAABBTree()
} }
} }
bool LevelAABBTree::GenerateTree(const FVector2 *centroids, bool dynamicsubtree)
{
// Create a list of level lines we want to add:
TArray<int> line_elements;
for (unsigned int i = 0; i < level.lines.Size(); i++)
{
if (!level.lines[i].backsector)
{
bool isPolyLine = level.lines[i].sidedef[0] && (level.lines[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 LevelAABBTree::Update()
{ {
bool modified = false; bool modified = false;
for (unsigned int ii = 0; ii < polylines.Size(); ii++) for (unsigned int i = dynamicStartLine; i < mapLines.Size(); i++)
{ {
int i = polylines[ii]; const auto &line = level.lines[mapLines[i]];
const auto &line = level.lines[i];
AABBTreeLine treeline; AABBTreeLine treeline;
treeline.x = (float)line.v1->fX(); treeline.x = (float)line.v1->fX();
@ -95,10 +129,10 @@ bool LevelAABBTree::Update()
TArray<int> path = FindNodePath(i, nodes.Size() - 1); TArray<int> path = FindNodePath(i, nodes.Size() - 1);
if (path.Size()) if (path.Size())
{ {
float x1 = (float)level.lines[i].v1->fX(); float x1 = (float)line.v1->fX();
float y1 = (float)level.lines[i].v1->fY(); float y1 = (float)line.v1->fY();
float x2 = (float)level.lines[i].v2->fX(); float x2 = (float)line.v2->fX();
float y2 = (float)level.lines[i].v2->fY(); float y2 = (float)line.v2->fY();
int nodeIndex = path[0]; int nodeIndex = path[0];
nodes[nodeIndex].aabb_left = MIN(x1, x2); nodes[nodeIndex].aabb_left = MIN(x1, x2);
@ -122,7 +156,7 @@ bool LevelAABBTree::Update()
} }
} }
} }
return !modified; return modified;
} }
TArray<int> LevelAABBTree::FindNodePath(unsigned int line, unsigned int node) TArray<int> LevelAABBTree::FindNodePath(unsigned int line, unsigned int node)
@ -165,7 +199,7 @@ double LevelAABBTree::RayTest(const DVector3 &ray_start, const DVector3 &ray_end
double hit_fraction = 1.0; double hit_fraction = 1.0;
// Walk the tree nodes // Walk the tree nodes
int stack[16]; int stack[32];
int stack_pos = 1; int stack_pos = 1;
stack[0] = nodes.Size() - 1; // root node is the last node in the list stack[0] = nodes.Size() - 1; // root node is the last node in the list
while (stack_pos > 0) while (stack_pos > 0)
@ -183,7 +217,7 @@ double LevelAABBTree::RayTest(const DVector3 &ray_start, const DVector3 &ray_end
hit_fraction = MIN(IntersectRayLine(ray_start, ray_end, nodes[node_index].line_index, raydelta, rayd, raydist2), hit_fraction); hit_fraction = MIN(IntersectRayLine(ray_start, ray_end, nodes[node_index].line_index, raydelta, rayd, raydist2), hit_fraction);
stack_pos--; stack_pos--;
} }
else if (stack_pos == 16) else if (stack_pos == 32)
{ {
stack_pos--; // stack overflow - tree is too deep! stack_pos--; // stack overflow - tree is too deep!
} }
@ -269,15 +303,15 @@ int LevelAABBTree::GenerateTreeNode(int *lines, int num_lines, const FVector2 *c
// Find bounding box and median of the lines // Find bounding box and median of the lines
FVector2 median = FVector2(0.0f, 0.0f); FVector2 median = FVector2(0.0f, 0.0f);
FVector2 aabb_min, aabb_max; FVector2 aabb_min, aabb_max;
aabb_min.X = (float)level.lines[lines[0]].v1->fX(); aabb_min.X = (float)level.lines[mapLines[lines[0]]].v1->fX();
aabb_min.Y = (float)level.lines[lines[0]].v1->fY(); aabb_min.Y = (float)level.lines[mapLines[lines[0]]].v1->fY();
aabb_max = aabb_min; aabb_max = aabb_min;
for (int i = 0; i < num_lines; i++) for (int i = 0; i < num_lines; i++)
{ {
float x1 = (float)level.lines[lines[i]].v1->fX(); float x1 = (float)level.lines[mapLines[lines[i]]].v1->fX();
float y1 = (float)level.lines[lines[i]].v1->fY(); float y1 = (float)level.lines[mapLines[lines[i]]].v1->fY();
float x2 = (float)level.lines[lines[i]].v2->fX(); float x2 = (float)level.lines[mapLines[lines[i]]].v2->fX();
float y2 = (float)level.lines[lines[i]].v2->fY(); float y2 = (float)level.lines[mapLines[lines[i]]].v2->fY();
aabb_min.X = MIN(aabb_min.X, x1); aabb_min.X = MIN(aabb_min.X, x1);
aabb_min.X = MIN(aabb_min.X, x2); aabb_min.X = MIN(aabb_min.X, x2);
@ -288,7 +322,7 @@ int LevelAABBTree::GenerateTreeNode(int *lines, int num_lines, const FVector2 *c
aabb_max.Y = MAX(aabb_max.Y, y1); aabb_max.Y = MAX(aabb_max.Y, y1);
aabb_max.Y = MAX(aabb_max.Y, y2); aabb_max.Y = MAX(aabb_max.Y, y2);
median += centroids[lines[i]]; median += centroids[mapLines[lines[i]]];
} }
median /= (float)num_lines; median /= (float)num_lines;
@ -324,7 +358,7 @@ int LevelAABBTree::GenerateTreeNode(int *lines, int num_lines, const FVector2 *c
{ {
int line_index = lines[i]; int line_index = lines[i];
float side = FVector3(centroids[lines[i]], 1.0f) | plane; float side = FVector3(centroids[mapLines[lines[i]]], 1.0f) | plane;
if (side >= 0.0f) if (side >= 0.0f)
{ {
work_buffer[left_count] = line_index; work_buffer[left_count] = line_index;

View file

@ -42,18 +42,26 @@ public:
// Constructs a tree for the current level // Constructs a tree for the current level
LevelAABBTree(); LevelAABBTree();
// 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> lines;
// 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. // 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); double RayTest(const DVector3 &ray_start, const DVector3 &ray_end);
bool Update(); bool Update();
const void *Nodes() const { return nodes.Data(); }
const void *Lines() const { return lines.Data(); }
size_t NodesSize() const { return nodes.Size() * sizeof(AABBTreeNode); }
size_t LinesSize() const { return lines.Size() * sizeof(AABBTreeLine); }
const void *DynamicNodes() const { return nodes.Data() + dynamicStartNode; }
const void *DynamicLines() const { return lines.Data() + dynamicStartLine; }
size_t DynamicNodesSize() const { return (nodes.Size() - dynamicStartNode) * sizeof(AABBTreeNode); }
size_t DynamicLinesSize() const { return (lines.Size() - dynamicStartLine) * sizeof(AABBTreeLine); }
size_t DynamicNodesOffset() const { return dynamicStartNode * sizeof(AABBTreeNode); }
size_t DynamicLinesOffset() const { return dynamicStartLine * sizeof(AABBTreeLine); }
private: private:
bool GenerateTree(const FVector2 *centroids, bool dynamicsubtree);
// Test if a ray overlaps an AABB node or not // Test if a ray overlaps an AABB node or not
bool OverlapRayAABB(const DVector2 &ray_start2d, const DVector2 &ray_end2d, const AABBTreeNode &node); bool OverlapRayAABB(const DVector2 &ray_start2d, const DVector2 &ray_end2d, const AABBTreeNode &node);
@ -65,7 +73,16 @@ private:
TArray<int> FindNodePath(unsigned int line, unsigned int node); TArray<int> FindNodePath(unsigned int line, unsigned int node);
TArray<int> polylines; // 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> lines;
int dynamicStartNode = 0;
int dynamicStartLine = 0;
TArray<int> mapLines;
}; };
} // namespace } // namespace

View file

@ -155,7 +155,7 @@ bool IShadowMap::ValidateAABBTree()
} }
if (mAABBTree) if (mAABBTree)
return mAABBTree->Update(); return true;
mAABBTree.reset(new hwrenderer::LevelAABBTree()); mAABBTree.reset(new hwrenderer::LevelAABBTree());
return false; return false;
@ -198,11 +198,16 @@ void IShadowMap::UploadAABBTree()
{ {
if (!mNodesBuffer) if (!mNodesBuffer)
mNodesBuffer = screen->CreateDataBuffer(2, true); mNodesBuffer = screen->CreateDataBuffer(2, true);
mNodesBuffer->SetData(sizeof(hwrenderer::AABBTreeNode) * mAABBTree->nodes.Size(), &mAABBTree->nodes[0]); mNodesBuffer->SetData(mAABBTree->NodesSize(), mAABBTree->Nodes());
if (!mLinesBuffer) if (!mLinesBuffer)
mLinesBuffer = screen->CreateDataBuffer(3, true); mLinesBuffer = screen->CreateDataBuffer(3, true);
mLinesBuffer->SetData(sizeof(hwrenderer::AABBTreeLine) * mAABBTree->lines.Size(), &mAABBTree->lines[0]); 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());
} }
} }

View file

@ -109,7 +109,7 @@ float rayTest(vec2 ray_start, vec2 ray_end)
// Walk the AABB binary tree searching for nodes touching the ray line segment's AABB box. // Walk the AABB binary tree searching for nodes touching the ray line segment's AABB box.
// When it reaches a leaf node, use a line segment intersection test to see if we got a hit. // When it reaches a leaf node, use a line segment intersection test to see if we got a hit.
int stack[16]; int stack[32];
int stack_pos = 1; int stack_pos = 1;
stack[0] = nodes.length() - 1; stack[0] = nodes.length() - 1;
while (stack_pos > 0) while (stack_pos > 0)
@ -125,7 +125,7 @@ float rayTest(vec2 ray_start, vec2 ray_end)
t = min(intersectRayLine(ray_start, ray_end, nodes[node_index].line_index, raydelta, rayd, raydist2), t); t = min(intersectRayLine(ray_start, ray_end, nodes[node_index].line_index, raydelta, rayd, raydist2), t);
stack_pos--; stack_pos--;
} }
else if (stack_pos == 16) else if (stack_pos == 32)
{ {
stack_pos--; // stack overflow stack_pos--; // stack overflow
} }