More level mesh buffer fun

This commit is contained in:
Magnus Norddahl 2024-08-23 00:01:08 +02:00
commit d28a3571b5
5 changed files with 359 additions and 244 deletions

View file

@ -4,6 +4,13 @@
LevelMesh::LevelMesh()
{
LevelMeshLimits limits;
limits.MaxVertices = 12;
limits.MaxIndexes = 3 * 4;
limits.MaxSurfaces = 1;
limits.MaxUniforms = 1;
Reset(limits);
// Default portal
LevelMeshPortal portal;
Portals.Push(portal);
@ -11,38 +18,35 @@ LevelMesh::LevelMesh()
AddEmptyMesh();
UpdateCollision();
Mesh.MaxVertices = std::max(Mesh.Vertices.Size() * 2, (unsigned int)10000);
Mesh.MaxIndexes = std::max(Mesh.Indexes.Size() * 2, (unsigned int)10000);
Mesh.MaxSurfaces = std::max(Mesh.SurfaceIndexes.Size() * 2, (unsigned int)10000);
Mesh.MaxUniforms = std::max(Mesh.Uniforms.Size() * 2, (unsigned int)10000);
Mesh.MaxSurfaceIndexes = std::max(Mesh.SurfaceIndexes.Size() * 2, (unsigned int)10000);
Mesh.MaxNodes = (int)std::max(Collision->get_nodes().size() * 2, (size_t)10000);
Mesh.MaxLights = 100'000;
Mesh.MaxLightIndexes = 4 * 1024 * 1024;
}
void LevelMesh::AddEmptyMesh()
{
GeometryAllocInfo ginfo = AllocGeometry(12, 3 * 4);
// Default empty mesh (we can't make it completely empty since vulkan doesn't like that)
float minval = -100001.0f;
float maxval = -100000.0f;
Mesh.Vertices.Push({ minval, minval, minval });
Mesh.Vertices.Push({ maxval, minval, minval });
Mesh.Vertices.Push({ maxval, maxval, minval });
Mesh.Vertices.Push({ minval, minval, minval });
Mesh.Vertices.Push({ minval, maxval, minval });
Mesh.Vertices.Push({ maxval, maxval, minval });
Mesh.Vertices.Push({ minval, minval, maxval });
Mesh.Vertices.Push({ maxval, minval, maxval });
Mesh.Vertices.Push({ maxval, maxval, maxval });
Mesh.Vertices.Push({ minval, minval, maxval });
Mesh.Vertices.Push({ minval, maxval, maxval });
Mesh.Vertices.Push({ maxval, maxval, maxval });
ginfo.Vertices[0] = { minval, minval, minval };
ginfo.Vertices[1] = { maxval, minval, minval };
ginfo.Vertices[2] = { maxval, maxval, minval };
ginfo.Vertices[3] = { minval, minval, minval };
ginfo.Vertices[4] = { minval, maxval, minval };
ginfo.Vertices[5] = { maxval, maxval, minval };
ginfo.Vertices[6] = { minval, minval, maxval };
ginfo.Vertices[7] = { maxval, minval, maxval };
ginfo.Vertices[8] = { maxval, maxval, maxval };
ginfo.Vertices[9] = { minval, minval, maxval };
ginfo.Vertices[10] = { minval, maxval, maxval };
ginfo.Vertices[11] = { maxval, maxval, maxval };
for (int i = 0; i < 3 * 4; i++)
Mesh.Indexes.Push(i);
ginfo.Indexes[i] = i;
UploadAll();
Mesh.IndexCount = ginfo.IndexCount;
UploadPortals();
}
LevelMeshSurface* LevelMesh::Trace(const FVector3& start, FVector3 direction, float maxDist)
@ -109,7 +113,7 @@ LevelMeshTileStats LevelMesh::GatherTilePixelStats()
void LevelMesh::UpdateCollision()
{
Collision = std::make_unique<TriangleMeshShape>(Mesh.Vertices.Data(), Mesh.Vertices.Size(), Mesh.Indexes.Data(), Mesh.Indexes.Size());
Collision = std::make_unique<TriangleMeshShape>(Mesh.Vertices.Data(), Mesh.Vertices.Size(), Mesh.Indexes.Data(), Mesh.IndexCount);
UploadCollision();
}

View file

@ -11,6 +11,7 @@
#include "hw_levelmeshsurface.h"
#include "hw_materialstate.h"
#include "hw_surfaceuniforms.h"
#include "engineerrors.h"
#include <memory>
#include <dp_rect_pack.h>
@ -44,12 +45,32 @@ struct UniformsAllocInfo
int Count = 0;
};
struct SurfaceAllocInfo
{
LevelMeshSurface* Surface = nullptr;
int Index = 0;
};
struct LightAllocInfo
{
LevelMeshLight* Light = nullptr;
int Index = 0;
};
struct MeshBufferRange
{
int Offset = 0;
int Size = 0;
int Start = 0;
int End = 0;
bool operator<(const MeshBufferRange& other) const { return Offset < other.Offset; }
int Count() const { return End - Start; }
};
struct LevelMeshLimits
{
int MaxVertices = 0;
int MaxSurfaces = 0;
int MaxUniforms = 0;
int MaxIndexes = 0;
};
class LevelMesh
@ -71,20 +92,18 @@ public:
GeometryAllocInfo AllocGeometry(int vertexCount, int indexCount)
{
GeometryAllocInfo info;
info.VertexStart = Mesh.Vertices.Size();
info.VertexStart = RemoveRange(FreeLists.Vertex, vertexCount);
info.VertexCount = vertexCount;
info.IndexStart = Mesh.Indexes.Size();
info.IndexStart = RemoveRange(FreeLists.Index, indexCount);
info.IndexCount = indexCount;
Mesh.Vertices.Resize(info.VertexStart + vertexCount);
Mesh.UniformIndexes.Resize(info.VertexStart + vertexCount);
Mesh.Indexes.Resize(info.IndexStart + indexCount);
info.Vertices = &Mesh.Vertices[info.VertexStart];
info.UniformIndexes = &Mesh.UniformIndexes[info.VertexStart];
info.Indexes = &Mesh.Indexes[info.IndexStart];
AddRange(UploadRanges.Vertex, { info.VertexStart, info.VertexCount });
AddRange(UploadRanges.UniformIndexes, { info.VertexStart, info.VertexCount });
AddRange(UploadRanges.Index, { info.IndexStart, info.IndexCount });
AddRange(UploadRanges.Vertex, { info.VertexStart, info.VertexStart + info.VertexCount });
AddRange(UploadRanges.UniformIndexes, { info.VertexStart, info.VertexStart + info.VertexCount });
AddRange(UploadRanges.Index, { info.IndexStart, info.IndexStart + info.IndexCount });
AddRange(UploadRanges.SurfaceIndex, { info.IndexStart / 3, (info.IndexStart + info.IndexCount) / 3 + 1 });
return info;
}
@ -92,14 +111,23 @@ public:
UniformsAllocInfo AllocUniforms(int count)
{
UniformsAllocInfo info;
info.Start = Mesh.Uniforms.Size();
info.Start = RemoveRange(FreeLists.Uniforms, count);
info.Count = count;
Mesh.Uniforms.Resize(info.Start + count);
Mesh.Materials.Resize(info.Start + count);
info.Uniforms = &Mesh.Uniforms[info.Start];
info.Materials = &Mesh.Materials[info.Start];
AddRange(UploadRanges.Uniforms, { info.Start, info.Count });
AddRange(UploadRanges.Uniforms, { info.Start, info.Start + info.Count });
return info;
}
SurfaceAllocInfo AllocSurface()
{
SurfaceAllocInfo info;
info.Index = RemoveRange(FreeLists.Surface, 1);
info.Surface = &Mesh.Surfaces[info.Index];
AddRange(UploadRanges.Surface, { info.Index, info.Index + 1 });
return info;
}
@ -109,10 +137,10 @@ public:
// Convert triangles to degenerates
for (int i = 0; i < indexCount; i++)
Mesh.Indexes[indexStart + i] = 0;
AddRange(UploadRanges.Index, { indexStart, indexCount });
AddRange(UploadRanges.Index, { indexStart, indexStart + indexCount });
AddRange(FreeLists.Vertex, { vertexStart, vertexCount });
AddRange(FreeLists.Index, { indexStart, indexCount });
AddRange(FreeLists.Vertex, { vertexStart, vertexStart + vertexCount });
AddRange(FreeLists.Index, { indexStart, indexStart + indexCount });
}
void FreeUniforms(int start, int count)
@ -127,6 +155,28 @@ public:
AddRange(FreeLists.Surface, { (int)surfaceIndex, 1 });
}
// Sets the sizes of all the GPU buffers and empties the mesh
virtual void Reset(const LevelMeshLimits& limits)
{
Mesh.Vertices.Resize(limits.MaxVertices);
Mesh.UniformIndexes.Resize(limits.MaxVertices);
Mesh.Surfaces.Resize(limits.MaxSurfaces);
Mesh.Uniforms.Resize(limits.MaxUniforms);
Mesh.Materials.Resize(limits.MaxUniforms);
Mesh.Lights.Resize(16);
Mesh.LightIndexes.Resize(16);
Mesh.Indexes.Resize(limits.MaxIndexes);
Mesh.SurfaceIndexes.Resize(limits.MaxIndexes / 3 + 1);
FreeLists.Vertex.Clear(); FreeLists.Vertex.Push({ 0, limits.MaxVertices });
FreeLists.Index.Clear(); FreeLists.Index.Push({ 0, limits.MaxIndexes });
FreeLists.Uniforms.Clear(); FreeLists.Uniforms.Push({ 0, limits.MaxUniforms });
FreeLists.Surface.Clear(); FreeLists.Surface.Push({ 0, limits.MaxSurfaces });
}
// Data placed in GPU buffers
struct
{
@ -146,16 +196,10 @@ public:
// Index data
TArray<uint32_t> Indexes;
TArray<int> SurfaceIndexes;
int IndexCount = 0; // Index range filled with data
// Above data must not be resized beyond these limits as that's the size of the GPU buffers
int MaxVertices = 0;
int MaxIndexes = 0;
int MaxSurfaces = 0;
int MaxUniforms = 0;
int MaxSurfaceIndexes = 0;
// GPU buffer size for collision nodes
int MaxNodes = 0;
int MaxLights = 0;
int MaxLightIndexes = 0;
} Mesh;
// Ranges in mesh that have changed since last upload
@ -207,26 +251,10 @@ public:
void AddEmptyMesh();
void UploadAll()
void UploadPortals()
{
ClearRanges(UploadRanges.Vertex);
AddRange(UploadRanges.Vertex, { 0, (int)Mesh.Vertices.Size() });
ClearRanges(UploadRanges.Index);
AddRange(UploadRanges.Index, { 0, (int)Mesh.Indexes.Size() });
ClearRanges(UploadRanges.SurfaceIndex);
AddRange(UploadRanges.SurfaceIndex, { 0, (int)Mesh.SurfaceIndexes.Size() });
ClearRanges(UploadRanges.Surface);
AddRange(UploadRanges.Surface, { 0, (int)Mesh.Surfaces.Size() });
ClearRanges(UploadRanges.UniformIndexes);
AddRange(UploadRanges.UniformIndexes, { 0, (int)Mesh.UniformIndexes.Size() });
ClearRanges(UploadRanges.Uniforms);
AddRange(UploadRanges.Uniforms, { 0, (int)Mesh.Uniforms.Size() });
ClearRanges(UploadRanges.Portals);
UploadRanges.Portals.Clear();
AddRange(UploadRanges.Portals, { 0, (int)Portals.Size() });
ClearRanges(UploadRanges.Light);
AddRange(UploadRanges.Light, { 0, (int)Mesh.Lights.Size() });
ClearRanges(UploadRanges.LightIndex);
AddRange(UploadRanges.LightIndex, { 0, (int)Mesh.LightIndexes.Size() });
}
void UploadCollision()
@ -236,42 +264,81 @@ public:
UploadRanges.Node.Push({ 0, (int)Collision->get_nodes().size() });
}
void ClearRanges(TArray<MeshBufferRange>& ranges)
int RemoveRange(TArray<MeshBufferRange>& ranges, int count)
{
ranges.Clear();
for (unsigned int i = ranges.Size(); i > 0; i--)
{
auto& item = ranges[i - 1];
if (item.End - item.Start >= count)
{
int pos = item.Start;
item.Start += count;
if (item.Start == item.End)
{
ranges.Delete(i - 1);
}
return pos;
}
}
I_FatalError("Could not find space in level mesh buffer");
}
void AddRange(TArray<MeshBufferRange>& ranges, MeshBufferRange range)
{
if (range.Size <= 0)
// Empty range?
if (range.Start == range.End)
return;
auto right = std::lower_bound(ranges.begin(), ranges.end(), range);
// First element?
if (ranges.Size() == 0)
{
ranges.push_back(range);
return;
}
// Find start position in ranges
auto right = std::lower_bound(ranges.begin(), ranges.end(), range, [](const auto& a, const auto& b) { return a.Start < b.Start; });
bool leftExists = right != ranges.begin();
bool rightExists = right != ranges.end();
auto left = right;
if (leftExists)
--left;
if (leftExists && rightExists && left->Offset + left->Size == range.Offset && right->Offset == range.Offset + range.Size) // ####[--]####
{
left->Size += range.Size + right->Size;
ranges.Delete(right - ranges.begin());
}
else if (leftExists && left->Offset + left->Size == range.Offset) // ####[--] ####
{
left->Size += range.Size;
}
else if (rightExists && right->Offset == range.Offset + range.Size) // #### [--]####
{
right->Offset -= range.Size;
}
else // ##### [--] ####
// Is this a gap between two ranges?
if ((!leftExists || left->End < range.Start) && (!rightExists || right->Start > range.End))
{
ranges.Insert(right - ranges.begin(), range);
return;
}
// Are we extending the left or the right range?
if (leftExists && range.Start <= left->End)
{
left->End = std::max(left->End, range.End);
right = left;
}
else // if (rightExists && right->Start <= range.End)
{
right->Start = range.Start;
right->End = std::max(right->End, range.End);
left = right;
}
// Merge overlaps to the right
while (true)
{
++right;
if (right == ranges.end() || right->Start > range.End)
break;
left->End = std::max(right->End, range.End);
}
// Remove ranges now covered by the extended range
//ranges.erase(++left, right);
++left;
auto leftPos = left - ranges.begin();
auto rightPos = right - ranges.begin();
ranges.Delete(leftPos, rightPos - leftPos);
}
};

View file

@ -80,7 +80,7 @@ void VkLevelMesh::CreateVulkanObjects()
.AddMemory(VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_SHADER_READ_BIT)
.Execute(fb->GetCommands()->GetTransferCommands(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
DynamicBLAS = CreateBLAS(true, 0, Mesh->Mesh.Indexes.Size());
DynamicBLAS = CreateBLAS(true, 0, Mesh->Mesh.IndexCount);
CreateTLASInstanceBuffer();
UploadTLASInstanceBuffer();
@ -128,7 +128,7 @@ void VkLevelMesh::BeginFrame()
deletelist->Add(std::move(DynamicBLAS.AccelStructBuffer));
deletelist->Add(std::move(DynamicBLAS.AccelStruct));
DynamicBLAS = CreateBLAS(true, 0, Mesh->Mesh.Indexes.Size());
DynamicBLAS = CreateBLAS(true, 0, Mesh->Mesh.IndexCount);
deletelist->Add(std::move(TopLevelAS.TransferBuffer));
deletelist->Add(std::move(TopLevelAS.InstanceBuffer));
@ -173,7 +173,7 @@ void VkLevelMesh::CreateBuffers()
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT |
VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR : 0) |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT)
.Size(Mesh->Mesh.MaxVertices * sizeof(FFlatVertex))
.Size(Mesh->Mesh.Vertices.Size() * sizeof(FFlatVertex))
.DebugName("VertexBuffer")
.Create(fb->GetDevice());
@ -181,7 +181,7 @@ void VkLevelMesh::CreateBuffers()
.Usage(
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT)
.Size(Mesh->Mesh.MaxVertices * sizeof(int))
.Size(Mesh->Mesh.UniformIndexes.size() * sizeof(int))
.DebugName("UniformIndexes")
.Create(fb->GetDevice());
@ -193,7 +193,7 @@ void VkLevelMesh::CreateBuffers()
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT |
VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR : 0) |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT)
.Size((size_t)Mesh->Mesh.MaxIndexes * sizeof(uint32_t))
.Size((size_t)Mesh->Mesh.Indexes.Size() * sizeof(uint32_t))
.DebugName("IndexBuffer")
.Create(fb->GetDevice());
@ -205,19 +205,19 @@ void VkLevelMesh::CreateBuffers()
SurfaceIndexBuffer = BufferBuilder()
.Usage(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)
.Size(Mesh->Mesh.MaxSurfaceIndexes * sizeof(int))
.Size(Mesh->Mesh.SurfaceIndexes.Size() * sizeof(int))
.DebugName("SurfaceBuffer")
.Create(fb->GetDevice());
SurfaceBuffer = BufferBuilder()
.Usage(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)
.Size(Mesh->Mesh.MaxSurfaces * sizeof(SurfaceInfo))
.Size(Mesh->Mesh.Surfaces.Size() * sizeof(SurfaceInfo))
.DebugName("SurfaceBuffer")
.Create(fb->GetDevice());
UniformsBuffer = BufferBuilder()
.Usage(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)
.Size(Mesh->Mesh.MaxUniforms * sizeof(SurfaceUniforms))
.Size(Mesh->Mesh.Uniforms.Size() * sizeof(SurfaceUniforms))
.DebugName("SurfaceUniformsBuffer")
.Create(fb->GetDevice());
@ -229,13 +229,13 @@ void VkLevelMesh::CreateBuffers()
LightBuffer = BufferBuilder()
.Usage(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)
.Size(Mesh->Mesh.MaxLights * sizeof(LightInfo))
.Size(Mesh->Mesh.Lights.Size() * sizeof(LightInfo))
.DebugName("LightBuffer")
.Create(fb->GetDevice());
LightIndexBuffer = BufferBuilder()
.Usage(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT)
.Size(Mesh->Mesh.MaxLightIndexes * sizeof(int32_t))
.Size(Mesh->Mesh.LightIndexes.Size() * sizeof(int32_t))
.DebugName("LightIndexBuffer")
.Create(fb->GetDevice());
}
@ -518,9 +518,9 @@ void VkLevelMeshUploader::UploadNodes()
{
const auto& srcnodes = Mesh->Mesh->Collision->get_nodes();
CollisionNode* nodes = (CollisionNode*)(data + datapos);
for (int i = 0, count = range.Size; i < count; i++)
for (int i = 0, count = range.Count(); i < count; i++)
{
const auto& node = srcnodes[range.Offset + i];
const auto& node = srcnodes[range.Start + i];
CollisionNode info;
info.center = SwapYZ(node.aabb.Center);
info.extents = SwapYZ(node.aabb.Extents);
@ -530,9 +530,9 @@ void VkLevelMeshUploader::UploadNodes()
*(nodes++) = info;
}
size_t copysize = range.Size * sizeof(CollisionNode);
size_t copysize = range.Count() * sizeof(CollisionNode);
if (copysize > 0)
copyCommands.emplace_back(transferBuffer.get(), Mesh->NodeBuffer.get(), datapos, sizeof(CollisionNodeBufferHeader) + range.Offset * sizeof(CollisionNode), copysize);
copyCommands.emplace_back(transferBuffer.get(), Mesh->NodeBuffer.get(), datapos, sizeof(CollisionNodeBufferHeader) + range.Start * sizeof(CollisionNode), copysize);
datapos += copysize;
}
}
@ -542,10 +542,10 @@ void VkLevelMeshUploader::UploadRanges(const TArray<MeshBufferRange>& ranges, co
{
for (const MeshBufferRange& range : ranges)
{
size_t copysize = range.Size * sizeof(T);
memcpy(data + datapos, srcbuffer + range.Offset, copysize);
size_t copysize = range.Count() * sizeof(T);
memcpy(data + datapos, srcbuffer + range.Start, copysize);
if (copysize > 0)
copyCommands.emplace_back(transferBuffer.get(), destbuffer, datapos, range.Offset * sizeof(T), copysize);
copyCommands.emplace_back(transferBuffer.get(), destbuffer, datapos, range.Start * sizeof(T), copysize);
datapos += copysize;
}
}
@ -555,9 +555,9 @@ void VkLevelMeshUploader::UploadSurfaces()
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Surface)
{
SurfaceInfo* surfaces = (SurfaceInfo*)(data + datapos);
for (int j = 0, count = range.Size; j < count; j++)
for (int j = 0, count = range.Count(); j < count; j++)
{
LevelMeshSurface* surface = &Mesh->Mesh->Mesh.Surfaces[range.Offset + j];
LevelMeshSurface* surface = &Mesh->Mesh->Mesh.Surfaces[range.Start + j];
SurfaceInfo info;
info.Normal = FVector3(surface->Plane.X, surface->Plane.Z, surface->Plane.Y);
@ -579,9 +579,9 @@ void VkLevelMeshUploader::UploadSurfaces()
*(surfaces++) = info;
}
size_t copysize = range.Size * sizeof(SurfaceInfo);
size_t copysize = range.Count() * sizeof(SurfaceInfo);
if (copysize > 0)
copyCommands.emplace_back(transferBuffer.get(), Mesh->SurfaceBuffer.get(), datapos, range.Offset * sizeof(SurfaceInfo), copysize);
copyCommands.emplace_back(transferBuffer.get(), Mesh->SurfaceBuffer.get(), datapos, range.Start * sizeof(SurfaceInfo), copysize);
datapos += copysize;
}
}
@ -590,10 +590,10 @@ void VkLevelMeshUploader::UploadUniforms()
{
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Uniforms)
{
for (int j = 0, count = range.Size; j < count; j++)
for (int j = 0, count = range.Count(); j < count; j++)
{
auto& surfaceUniforms = Mesh->Mesh->Mesh.Uniforms[range.Offset + j];
auto& material = Mesh->Mesh->Mesh.Materials[range.Offset + j];
auto& surfaceUniforms = Mesh->Mesh->Mesh.Uniforms[range.Start + j];
auto& material = Mesh->Mesh->Mesh.Materials[range.Start + j];
if (material.mMaterial)
{
auto source = material.mMaterial->Source();
@ -607,10 +607,10 @@ void VkLevelMeshUploader::UploadUniforms()
}
SurfaceUniforms* uniforms = (SurfaceUniforms*)(data + datapos);
size_t copysize = range.Size * sizeof(SurfaceUniforms);
size_t copysize = range.Count() * sizeof(SurfaceUniforms);
memcpy(uniforms, Mesh->Mesh->Mesh.Uniforms.Data(), copysize);
if (copysize > 0)
copyCommands.emplace_back(transferBuffer.get(), Mesh->UniformsBuffer.get(), datapos, range.Offset * sizeof(SurfaceUniforms), copysize);
copyCommands.emplace_back(transferBuffer.get(), Mesh->UniformsBuffer.get(), datapos, range.Start * sizeof(SurfaceUniforms), copysize);
datapos += copysize;
}
}
@ -620,17 +620,17 @@ void VkLevelMeshUploader::UploadPortals()
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Portals)
{
PortalInfo* portals = (PortalInfo*)(data + datapos);
for (int i = 0, count = range.Size; i < count; i++)
for (int i = 0, count = range.Count(); i < count; i++)
{
const auto& portal = Mesh->Mesh->Portals[range.Offset + i];
const auto& portal = Mesh->Mesh->Portals[range.Start + i];
PortalInfo info;
info.transformation = portal.transformation;
*(portals++) = info;
}
size_t copysize = range.Size * sizeof(PortalInfo);
size_t copysize = range.Count() * sizeof(PortalInfo);
if (copysize > 0)
copyCommands.emplace_back(transferBuffer.get(), Mesh->PortalBuffer.get(), datapos, range.Offset * sizeof(PortalInfo), copysize);
copyCommands.emplace_back(transferBuffer.get(), Mesh->PortalBuffer.get(), datapos, range.Start * sizeof(PortalInfo), copysize);
datapos += copysize;
}
}
@ -640,9 +640,9 @@ void VkLevelMeshUploader::UploadLights()
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Light)
{
LightInfo* lights = (LightInfo*)(data + datapos);
for (int i = 0, count = range.Size; i < count; i++)
for (int i = 0, count = range.Count(); i < count; i++)
{
const auto& light = Mesh->Mesh->Mesh.Lights[range.Offset + i];
const auto& light = Mesh->Mesh->Mesh.Lights[range.Start + i];
LightInfo info;
info.Origin = SwapYZ(light.Origin);
info.RelativeOrigin = SwapYZ(light.RelativeOrigin);
@ -656,9 +656,9 @@ void VkLevelMeshUploader::UploadLights()
*(lights++) = info;
}
size_t copysize = range.Size * sizeof(LightInfo);
size_t copysize = range.Count() * sizeof(LightInfo);
if (copysize > 0)
copyCommands.emplace_back(transferBuffer.get(), Mesh->LightBuffer.get(), datapos, range.Offset * sizeof(LightInfo), copysize);
copyCommands.emplace_back(transferBuffer.get(), Mesh->LightBuffer.get(), datapos, range.Start * sizeof(LightInfo), copysize);
datapos += copysize;
}
}
@ -670,16 +670,16 @@ size_t VkLevelMeshUploader::GetTransferSize()
if (!Mesh->useRayQuery)
{
if (Mesh->Mesh->UploadRanges.Node.Size() > 0) transferBufferSize += sizeof(CollisionNodeBufferHeader) + sizeof(CollisionNode);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Node) transferBufferSize += range.Size * sizeof(CollisionNode);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Node) transferBufferSize += range.Count() * sizeof(CollisionNode);
}
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Vertex) transferBufferSize += range.Size * sizeof(FFlatVertex);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.UniformIndexes) transferBufferSize += range.Size * sizeof(int);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Index) transferBufferSize += range.Size * sizeof(uint32_t);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.SurfaceIndex) transferBufferSize += range.Size * sizeof(int);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Surface) transferBufferSize += range.Size * sizeof(SurfaceInfo);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Uniforms) transferBufferSize += range.Size * sizeof(SurfaceUniforms);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Portals) transferBufferSize += range.Size * sizeof(PortalInfo);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.LightIndex) transferBufferSize += range.Size * sizeof(int32_t);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Light) transferBufferSize += range.Size * sizeof(LightInfo);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Vertex) transferBufferSize += range.Count() * sizeof(FFlatVertex);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.UniformIndexes) transferBufferSize += range.Count() * sizeof(int);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Index) transferBufferSize += range.Count() * sizeof(uint32_t);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.SurfaceIndex) transferBufferSize += range.Count() * sizeof(int);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Surface) transferBufferSize += range.Count() * sizeof(SurfaceInfo);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Uniforms) transferBufferSize += range.Count() * sizeof(SurfaceUniforms);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Portals) transferBufferSize += range.Count() * sizeof(PortalInfo);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.LightIndex) transferBufferSize += range.Count() * sizeof(int32_t);
for (const MeshBufferRange& range : Mesh->Mesh->UploadRanges.Light) transferBufferSize += range.Count() * sizeof(LightInfo);
return transferBufferSize;
}

View file

@ -150,72 +150,119 @@ EXTERN_CVAR(Float, lm_scale);
DoomLevelMesh::DoomLevelMesh(FLevelLocals& doomMap)
{
// Remove the empty mesh added in the LevelMesh constructor
Mesh.Vertices.clear();
Mesh.Indexes.clear();
// Try to estimate what the worst case memory needs are for the level
LevelMeshLimits limits;
limits.MaxVertices = (doomMap.vertexes.Size() * 10) * 2;
limits.MaxSurfaces = (doomMap.sides.Size() * 3 + doomMap.subsectors.Size() * 2) * 2;
limits.MaxUniforms = (doomMap.sides.Size() * 3 + doomMap.sectors.Size() * 2) * 2;
limits.MaxIndexes = limits.MaxVertices * 10;
Reset(limits);
SunColor = doomMap.SunColor; // TODO keep only one copy?
SunDirection = doomMap.SunDirection;
LightmapSampleDistance = doomMap.LightmapSampleDistance;
BuildSectorGroups(doomMap);
CreatePortals(doomMap);
LightmapSampleDistance = doomMap.LightmapSampleDistance;
CreateSurfaces(doomMap);
LinkSurfaces(doomMap);
SortIndexes();
BuildTileSurfaceLists();
UpdateCollision();
// Assume double the size of the static mesh will be enough for anything dynamic.
Mesh.MaxVertices = std::max(Mesh.Vertices.size() * 2, (size_t)10000);
Mesh.MaxIndexes = std::max(Mesh.Indexes.size() * 2, (size_t)10000);
Mesh.MaxSurfaces = std::max(Mesh.SurfaceIndexes.size() * 2, (size_t)10000);
Mesh.MaxUniforms = std::max(Mesh.Uniforms.size() * 2, (size_t)10000);
Mesh.MaxSurfaceIndexes = std::max(Mesh.SurfaceIndexes.size() * 2, (size_t)10000);
Mesh.MaxNodes = std::max(Collision->get_nodes().size() * 2, (size_t)10000);
Mesh.MaxLights = 100'000;
Mesh.MaxLightIndexes = 4 * 1024 * 1024;
UploadAll();
UploadPortals();
}
void DoomLevelMesh::BeginFrame(FLevelLocals& doomMap)
{
CreateLights(doomMap);
}
void DoomLevelMesh::CreateLights(FLevelLocals& doomMap)
{
if (Mesh.Lights.Size() != 0)
if (LightsCreated)
return;
for (unsigned int i = 0, count = Mesh.Surfaces.Size(); i < count; i++)
for (const SideSurfaceBlock& side : Sides)
{
Mesh.Surfaces[i].LightList.Pos = Mesh.LightIndexes.Size();
Mesh.Surfaces[i].LightList.Count = 0;
std::pair<FLightNode*, int> nodePortalGroup = GetSurfaceLightNode(i);
FLightNode* node = nodePortalGroup.first;
int portalgroup = nodePortalGroup.second;
if (!node)
continue;
int listpos = 0;
while (node)
int index = side.FirstSurface;
while (index != -1)
{
FDynamicLight* light = node->lightsource;
if (light && light->Trace())
{
int lightindex = GetLightIndex(light, portalgroup);
if (lightindex >= 0)
{
AddRange(UploadRanges.LightIndex, { (int)Mesh.LightIndexes.Size(), 1 });
Mesh.LightIndexes.Push(lightindex);
Mesh.Surfaces[i].LightList.Count++;
}
}
node = node->nextLight;
CreateLightList(doomMap, index);
index = DoomSurfaceInfos[index].NextSurface;
}
}
for (const FlatSurfaceBlock& flat : Flats)
{
int index = flat.FirstSurface;
while (index != -1)
{
CreateLightList(doomMap, index);
index = DoomSurfaceInfos[index].NextSurface;
}
}
LightsCreated = true;
}
void DoomLevelMesh::CreateLightList(FLevelLocals& doomMap, int surfaceIndex)
{
Mesh.Surfaces[surfaceIndex].LightList.Pos = Mesh.LightIndexes.Size();
Mesh.Surfaces[surfaceIndex].LightList.Count = 0;
std::pair<FLightNode*, int> nodePortalGroup = GetSurfaceLightNode(surfaceIndex);
FLightNode* node = nodePortalGroup.first;
int portalgroup = nodePortalGroup.second;
if (!node)
return;
int listpos = 0;
while (node)
{
FDynamicLight* light = node->lightsource;
if (light && light->Trace())
{
int lightindex = GetLightIndex(light, portalgroup);
if (lightindex >= 0)
{
AddRange(UploadRanges.LightIndex, { (int)Mesh.LightIndexes.Size(), (int)Mesh.LightIndexes.Size() + 1 });
Mesh.LightIndexes.Push(lightindex);
Mesh.Surfaces[surfaceIndex].LightList.Count++;
}
}
node = node->nextLight;
}
}
std::pair<FLightNode*, int> DoomLevelMesh::GetSurfaceLightNode(int surfaceIndex)
{
auto doomsurf = &DoomSurfaceInfos[surfaceIndex];
FLightNode* node = nullptr;
int portalgroup = 0;
if (doomsurf->Type == ST_FLOOR || doomsurf->Type == ST_CEILING)
{
node = doomsurf->Subsector->section->lighthead;
portalgroup = doomsurf->Subsector->sector->PortalGroup;
}
else if (doomsurf->Type == ST_MIDDLESIDE || doomsurf->Type == ST_UPPERSIDE || doomsurf->Type == ST_LOWERSIDE)
{
bool isPolyLine = !!(doomsurf->Side->Flags & WALLF_POLYOBJ);
if (isPolyLine)
{
subsector_t* subsector = level.PointInRenderSubsector((doomsurf->Side->V1()->fPos() + doomsurf->Side->V2()->fPos()) * 0.5);
node = subsector->section->lighthead;
portalgroup = subsector->sector->PortalGroup;
}
else
{
node = doomsurf->Side->lighthead;
portalgroup = doomsurf->Side->sector->PortalGroup;
}
}
return { node, portalgroup };
}
int DoomLevelMesh::GetLightIndex(FDynamicLight* light, int portalgroup)
@ -270,16 +317,11 @@ int DoomLevelMesh::GetLightIndex(FDynamicLight* light, int portalgroup)
int lightindex = Mesh.Lights.Size();
light->levelmesh[index].index = lightindex + 1;
light->levelmesh[index].portalgroup = portalgroup;
AddRange(UploadRanges.Light, { (int)Mesh.Lights.Size(), 1 });
AddRange(UploadRanges.Light, { (int)Mesh.Lights.Size(), (int)Mesh.Lights.Size() + 1 });
Mesh.Lights.Push(meshlight);
return lightindex;
}
void DoomLevelMesh::BeginFrame(FLevelLocals& doomMap)
{
CreateLights(doomMap);
}
bool DoomLevelMesh::TraceSky(const FVector3& start, FVector3 direction, float dist)
{
FVector3 end = start + direction * dist;
@ -287,34 +329,6 @@ bool DoomLevelMesh::TraceSky(const FVector3& start, FVector3 direction, float di
return surface && surface->IsSky;
}
std::pair<FLightNode*, int> DoomLevelMesh::GetSurfaceLightNode(int surfaceIndex)
{
auto doomsurf = &DoomSurfaceInfos[surfaceIndex];
FLightNode* node = nullptr;
int portalgroup = 0;
if (doomsurf->Type == ST_FLOOR || doomsurf->Type == ST_CEILING)
{
node = doomsurf->Subsector->section->lighthead;
portalgroup = doomsurf->Subsector->sector->PortalGroup;
}
else if (doomsurf->Type == ST_MIDDLESIDE || doomsurf->Type == ST_UPPERSIDE || doomsurf->Type == ST_LOWERSIDE)
{
bool isPolyLine = !!(doomsurf->Side->Flags & WALLF_POLYOBJ);
if (isPolyLine)
{
subsector_t* subsector = level.PointInRenderSubsector((doomsurf->Side->V1()->fPos() + doomsurf->Side->V2()->fPos()) * 0.5);
node = subsector->section->lighthead;
portalgroup = subsector->sector->PortalGroup;
}
else
{
node = doomsurf->Side->lighthead;
portalgroup = doomsurf->Side->sector->PortalGroup;
}
}
return { node, portalgroup };
}
void DoomLevelMesh::CreateSurfaces(FLevelLocals& doomMap)
{
bindings.clear();
@ -415,15 +429,15 @@ void DoomLevelMesh::SideTextureChanged(struct side_t* side, int section)
void DoomLevelMesh::SectorLightChanged(struct sector_t* sector)
{
};
}
void DoomLevelMesh::SectorLightThinkerCreated(struct sector_t* sector, class DLighting* lightthinker)
{
};
}
void DoomLevelMesh::SectorLightThinkerDestroyed(struct sector_t* sector, class DLighting* lightthinker)
{
};
}
void DoomLevelMesh::UpdateSide(FLevelLocals& doomMap, unsigned int sideIndex)
{
@ -557,6 +571,7 @@ void DoomLevelMesh::CreateWallSurface(side_t* side, HWWallDispatcher& disp, Mesh
GeometryAllocInfo ginfo = AllocGeometry(numVertices, numIndexes);
UniformsAllocInfo uinfo = AllocUniforms(numUniforms);
SurfaceAllocInfo sinfo = AllocSurface();
int pipelineID = 0;
int uniformsIndex = uinfo.Start;
@ -613,7 +628,7 @@ void DoomLevelMesh::CreateWallSurface(side_t* side, HWWallDispatcher& disp, Mesh
sampleDimension = side->textures[side_t::bottom].LightmapSampleDistance;
}
DoomSurfaceInfo info;
DoomSurfaceInfo& info = DoomSurfaceInfos[sinfo.Index];
info.Type = wallpart.LevelMeshInfo.Type;
info.ControlSector = wallpart.LevelMeshInfo.ControlSector;
info.TypeIndex = side->Index();
@ -621,26 +636,24 @@ void DoomLevelMesh::CreateWallSurface(side_t* side, HWWallDispatcher& disp, Mesh
if (sideIndex < Sides.Size())
{
info.NextSurface = Sides[sideIndex].FirstSurface;
Sides[sideIndex].FirstSurface = Mesh.Surfaces.Size();
Sides[sideIndex].FirstSurface = sinfo.Index;
}
LevelMeshSurface surf;
surf.PipelineID = pipelineID;
surf.SectorGroup = sectorGroup[side->sector->Index()];
surf.Alpha = float(side->linedef->alpha);
surf.MeshLocation.StartVertIndex = ginfo.VertexStart;
surf.MeshLocation.StartElementIndex = ginfo.IndexStart;
surf.MeshLocation.NumVerts = ginfo.VertexCount;
surf.MeshLocation.NumElements = ginfo.IndexCount;
surf.Plane = FVector4(N.X, N.Y, 0.0f, v1 | N);
surf.Texture = wallpart.texture;
surf.PortalIndex = isPortal ? linePortals[side->linedef->Index()] : 0;
surf.IsSky = isPortal ? (wallpart.portaltype == PORTALTYPE_SKY || wallpart.portaltype == PORTALTYPE_SKYBOX || wallpart.portaltype == PORTALTYPE_HORIZON) : false;
surf.Bounds = GetBoundsFromSurface(surf);
surf.LightmapTileIndex = disp.Level->lightmaps ? AddSurfaceToTile(info, surf, sampleDimension, !!(side->sector->Flags & SECF_LM_DYNAMIC)) : -1;
sinfo.Surface->PipelineID = pipelineID;
sinfo.Surface->SectorGroup = sectorGroup[side->sector->Index()];
sinfo.Surface->Alpha = float(side->linedef->alpha);
sinfo.Surface->MeshLocation.StartVertIndex = ginfo.VertexStart;
sinfo.Surface->MeshLocation.StartElementIndex = ginfo.IndexStart;
sinfo.Surface->MeshLocation.NumVerts = ginfo.VertexCount;
sinfo.Surface->MeshLocation.NumElements = ginfo.IndexCount;
sinfo.Surface->Plane = FVector4(N.X, N.Y, 0.0f, v1 | N);
sinfo.Surface->Texture = wallpart.texture;
sinfo.Surface->PortalIndex = isPortal ? linePortals[side->linedef->Index()] : 0;
sinfo.Surface->IsSky = isPortal ? (wallpart.portaltype == PORTALTYPE_SKY || wallpart.portaltype == PORTALTYPE_SKYBOX || wallpart.portaltype == PORTALTYPE_HORIZON) : false;
sinfo.Surface->Bounds = GetBoundsFromSurface(*sinfo.Surface);
sinfo.Surface->LightmapTileIndex = disp.Level->lightmaps ? AddSurfaceToTile(info, *sinfo.Surface, sampleDimension, !!(side->sector->Flags & SECF_LM_DYNAMIC)) : -1;
Mesh.Surfaces.Push(surf);
DoomSurfaceInfos.Push(info);
SetSideLightmap(sinfo.Index);
}
}
@ -872,30 +885,55 @@ void DoomLevelMesh::CreateFlatSurface(HWFlatDispatcher& disp, MeshBuilder& state
surf.Bounds = GetBoundsFromSurface(surf);
surf.LightmapTileIndex = disp.Level->lightmaps ? AddSurfaceToTile(info, surf, sampleDimension, !!(flatpart.sector->Flags & SECF_LM_DYNAMIC)) : -1;
SurfaceAllocInfo sinfo = AllocSurface();
if (sectorIndex < Flats.Size())
{
info.NextSurface = Flats[sectorIndex].FirstSurface;
Flats[sectorIndex].FirstSurface = Mesh.Surfaces.Size();
Flats[sectorIndex].FirstSurface = sinfo.Index;
}
Mesh.Surfaces.Push(surf);
DoomSurfaceInfos.Push(info);
*sinfo.Surface = surf;
DoomSurfaceInfos[sinfo.Index] = info;
SetSubsectorLightmap(sinfo.Index);
}
}
}
void DoomLevelMesh::SortIndexes()
{
DrawList.Clear();
PortalList.Clear();
// Order surfaces by pipeline
std::unordered_map<int64_t, TArray<int>> pipelineSurfaces;
for (size_t i = 0; i < Mesh.Surfaces.Size(); i++)
for (const SideSurfaceBlock& side : Sides)
{
LevelMeshSurface* s = &Mesh.Surfaces[i];
pipelineSurfaces[(int64_t(s->PipelineID) << 32) | int64_t(s->IsSky)].Push(i);
int index = side.FirstSurface;
while (index != -1)
{
LevelMeshSurface* s = &Mesh.Surfaces[index];
pipelineSurfaces[(int64_t(s->PipelineID) << 32) | int64_t(s->IsSky)].Push(index);
index = DoomSurfaceInfos[index].NextSurface;
}
}
for (const FlatSurfaceBlock& flat : Flats)
{
int index = flat.FirstSurface;
while (index != -1)
{
LevelMeshSurface* s = &Mesh.Surfaces[index];
pipelineSurfaces[(int64_t(s->PipelineID) << 32) | int64_t(s->IsSky)].Push(index);
index = DoomSurfaceInfos[index].NextSurface;
}
}
// Create reorder surface indexes by pipeline and create a draw range for each
// Reorder surface indexes by pipeline and create a draw range for each
TArray<uint32_t> sortedIndexes;
TArray<int> sortedSurfaceIndexes;
for (const auto& it : pipelineSurfaces)
{
LevelSubmeshDrawRange range;
@ -919,7 +957,7 @@ void DoomLevelMesh::SortIndexes()
for (unsigned int j = 0; j < count; j += 3)
{
Mesh.SurfaceIndexes.Push((int)i);
sortedSurfaceIndexes.Push((int)i);
}
}
@ -931,21 +969,19 @@ void DoomLevelMesh::SortIndexes()
PortalList.Push(range);
}
Mesh.Indexes.Swap(sortedIndexes);
}
void DoomLevelMesh::LinkSurfaces(FLevelLocals& doomMap)
{
for (unsigned int i = 0, count = Mesh.Surfaces.Size(); i < count; i++)
// Place result at front of buffers and upload
{
if (DoomSurfaceInfos[i].Type == ST_FLOOR || DoomSurfaceInfos[i].Type == ST_CEILING)
{
SetSubsectorLightmap(i);
}
else
{
SetSideLightmap(i);
}
int count = sortedIndexes.Size();
memcpy(Mesh.Indexes.Data(), sortedIndexes.Data(), count * sizeof(uint32_t));
UploadRanges.Index.Clear();
UploadRanges.Index.Push({ 0, count });
Mesh.IndexCount = count;
}
{
int count = sortedSurfaceIndexes.Size();
memcpy(Mesh.SurfaceIndexes.Data(), sortedSurfaceIndexes.Data(), count * sizeof(int));
UploadRanges.SurfaceIndex.Clear();
UploadRanges.SurfaceIndex.Push({ 0, count });
}
}
@ -1031,8 +1067,10 @@ void DoomLevelMesh::DumpMesh(const FString& objFilename, const FString& mtlFilen
{
auto f = fopen(objFilename.GetChars(), "w");
// To do: this dumps the entire vertex buffer, including those not in use
fprintf(f, "# DoomLevelMesh debug export\n");
fprintf(f, "# Vertices: %u, Indexes: %u, Surfaces: %u\n", Mesh.Vertices.Size(), Mesh.Indexes.Size(), Mesh.Surfaces.Size());
fprintf(f, "# Vertices: %u, Indexes: %u, Surfaces: %u\n", Mesh.Vertices.Size(), Mesh.IndexCount, Mesh.Surfaces.Size());
fprintf(f, "mtllib %s\n", mtlFilename.GetChars());
double scale = 1 / 10.0;
@ -1075,7 +1113,7 @@ void DoomLevelMesh::DumpMesh(const FString& objFilename, const FString& mtlFilen
bool useErrorMaterial = false;
int highestUsedAtlasPage = -1;
for (unsigned i = 0, count = Mesh.Indexes.Size(); i + 2 < count; i += 3)
for (unsigned i = 0, count = Mesh.IndexCount; i + 2 < count; i += 3)
{
auto index = Mesh.SurfaceIndexes[i / 3];

View file

@ -72,8 +72,15 @@ public:
void SectorLightThinkerCreated(struct sector_t* sector, class DLighting* lightthinker) override;
void SectorLightThinkerDestroyed(struct sector_t* sector, class DLighting* lightthinker) override;
void Reset(const LevelMeshLimits& limits) override
{
LevelMesh::Reset(limits);
DoomSurfaceInfos.Resize(limits.MaxSurfaces);
}
private:
void CreateSurfaces(FLevelLocals& doomMap);
void CreateLightList(FLevelLocals& doomMap, int surfaceIndex);
void UpdateSide(FLevelLocals& doomMap, unsigned int sideIndex);
void UpdateFlat(FLevelLocals& doomMap, unsigned int sectorIndex);
@ -89,8 +96,6 @@ private:
void CreateWallSurface(side_t* side, HWWallDispatcher& disp, MeshBuilder& state, TArray<HWWall>& list, bool isPortal, bool translucent, unsigned int sectorIndex);
void CreateFlatSurface(HWFlatDispatcher& disp, MeshBuilder& state, TArray<HWFlat>& list, bool isSky, bool translucent, unsigned int sectorIndex);
void LinkSurfaces(FLevelLocals& doomMap);
BBox GetBoundsFromSurface(const LevelMeshSurface& surface) const;
int AddSurfaceToTile(const DoomSurfaceInfo& info, const LevelMeshSurface& surf, uint16_t sampleDimension, bool alwaysUpdate);
@ -108,4 +113,5 @@ private:
TArray<FlatSurfaceBlock> Flats;
std::map<LightmapTileBinding, int> bindings;
MeshBuilder state;
bool LightsCreated = false;
};