Initial implementation for lightmap lump loading

This commit is contained in:
RaveYard 2023-09-13 18:08:54 +02:00 committed by Christoph Oelckers
parent 2b4db4c601
commit 537cd7790d
10 changed files with 354 additions and 35 deletions

View file

@ -192,11 +192,12 @@ public:
virtual void UpdateLightLists() { } virtual void UpdateLightLists() { }
TArray<LevelMeshSmoothingGroup> SmoothingGroups; // TODO fill TArray<LevelMeshSmoothingGroup> SmoothingGroups;
TArray<LevelMeshPortal> Portals; // TODO fill TArray<LevelMeshPortal> Portals;
int LMTextureCount = 0; int LMTextureCount = 0;
int LMTextureSize = 0; int LMTextureSize = 0;
TArray<uint16_t> LMTextureData; // TODO better place for this?
FVector3 SunDirection = FVector3(0.0f, 0.0f, -1.0f); FVector3 SunDirection = FVector3(0.0f, 0.0f, -1.0f);
FVector3 SunColor = FVector3(0.0f, 0.0f, 0.0f); FVector3 SunColor = FVector3(0.0f, 0.0f, 0.0f);

View file

@ -189,17 +189,17 @@ void VkTextureManager::CreateLightmap()
SetLightmap(1, 1, data); SetLightmap(1, 1, data);
} }
void VkTextureManager::CreateLightmap(int newLMTextureSize, int newLMTextureCount) void VkTextureManager::CreateLightmap(int newLMTextureSize, int newLMTextureCount, TArray<uint16_t>&& newPixelData)
{ {
if (LMTextureSize == newLMTextureSize && LMTextureCount == newLMTextureCount) if (LMTextureSize == newLMTextureSize && LMTextureCount == newLMTextureCount)
return; return;
LMTextureSize = newLMTextureSize; LMTextureSize = newLMTextureSize;
LMTextureCount = newLMTextureCount; LMTextureCount = newLMTextureCount;
int w = LMTextureSize; int w = newLMTextureSize;
int h = LMTextureSize; int h = newLMTextureSize;
int count = LMTextureCount; int count = newLMTextureCount;
int pixelsize = 8; int pixelsize = 8;
Lightmap.Reset(fb); Lightmap.Reset(fb);
@ -219,6 +219,47 @@ void VkTextureManager::CreateLightmap(int newLMTextureSize, int newLMTextureCoun
auto cmdbuffer = fb->GetCommands()->GetTransferCommands(); auto cmdbuffer = fb->GetCommands()->GetTransferCommands();
if (newPixelData.Size() >= w * h * count * 3)
{
assert(newPixelData.Size() == w * h * count * 3);
int totalSize = w * h * count * pixelsize;
auto stagingBuffer = BufferBuilder()
.Size(totalSize)
.Usage(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_CPU_ONLY)
.DebugName("VkHardwareTexture.mStagingBuffer")
.Create(fb->GetDevice());
uint16_t one = 0x3c00; // half-float 1.0
const uint16_t* src = newPixelData.Data();
uint16_t* data = (uint16_t*)stagingBuffer->Map(0, totalSize);
for (int i = w * h * count; i > 0; i--)
{
*(data++) = *(src++);
*(data++) = *(src++);
*(data++) = *(src++);
*(data++) = one;
}
stagingBuffer->Unmap();
VkImageTransition()
.AddImage(&Lightmap, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, true, 0, count)
.Execute(cmdbuffer);
VkBufferImageCopy region = {};
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = count;
region.imageExtent.depth = 1;
region.imageExtent.width = w;
region.imageExtent.height = h;
cmdbuffer->copyBufferToImage(stagingBuffer->buffer, Lightmap.Image->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
fb->GetCommands()->TransferDeleteList->Add(std::move(stagingBuffer));
newPixelData.Clear();
}
VkImageTransition() VkImageTransition()
.AddImage(&Lightmap, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, false, 0, count) .AddImage(&Lightmap, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, false, 0, count)
.Execute(cmdbuffer); .Execute(cmdbuffer);

View file

@ -23,7 +23,7 @@ public:
void BeginFrame(); void BeginFrame();
void CreateLightmap(int LMTextureSize, int LMTextureCount); void CreateLightmap(int newLMTextureSize, int newLMTextureCount, TArray<uint16_t>&& newPixelData);
void SetLightmap(int LMTextureSize, int LMTextureCount, const TArray<uint16_t>& LMTextureData); void SetLightmap(int LMTextureSize, int LMTextureCount, const TArray<uint16_t>& LMTextureData);
VkTextureImage* GetTexture(const PPTextureType& type, PPTexture* tex); VkTextureImage* GetTexture(const PPTextureType& type, PPTexture* tex);

View file

@ -474,7 +474,7 @@ void VulkanRenderDevice::BeginFrame()
if (levelMesh && levelMesh->GetSurfaceCount() > 0) if (levelMesh && levelMesh->GetSurfaceCount() > 0)
{ {
levelMesh->UpdateLightLists(); levelMesh->UpdateLightLists();
GetTextureManager()->CreateLightmap(levelMesh->LMTextureSize, levelMesh->LMTextureCount); GetTextureManager()->CreateLightmap(levelMesh->LMTextureSize, levelMesh->LMTextureCount, std::move(levelMesh->LMTextureData));
GetLightmap()->SetLevelMesh(levelMesh); GetLightmap()->SetLevelMesh(levelMesh);
#if 0 // full lightmap generation #if 0 // full lightmap generation

View file

@ -93,6 +93,7 @@ enum
CVAR (Bool, genblockmap, false, CVAR_SERVERINFO|CVAR_GLOBALCONFIG); CVAR (Bool, genblockmap, false, CVAR_SERVERINFO|CVAR_GLOBALCONFIG);
CVAR (Bool, gennodes, false, CVAR_SERVERINFO|CVAR_GLOBALCONFIG); CVAR (Bool, gennodes, false, CVAR_SERVERINFO|CVAR_GLOBALCONFIG);
CVAR (Bool, genlightmaps, true, CVAR_GLOBALCONFIG);
inline bool P_LoadBuildMap(uint8_t *mapdata, size_t len, FMapThing **things, int *numthings) inline bool P_LoadBuildMap(uint8_t *mapdata, size_t len, FMapThing **things, int *numthings)
{ {
@ -2938,7 +2939,7 @@ void MapLoader::CalcIndices()
// //
//========================================================================== //==========================================================================
void MapLoader::InitLevelMesh() void MapLoader::InitLevelMesh(MapData* map)
{ {
// Propagate sample distance where it isn't yet set // Propagate sample distance where it isn't yet set
for (auto& line : Level->lines) for (auto& line : Level->lines)
@ -2960,9 +2961,281 @@ void MapLoader::InitLevelMesh()
} }
} }
} }
// Last chance to make up our mind whether to use lightmaps or not
if (!Level->lightmaps)
{
if (map->Size(ML_LIGHTMAP))
{
Level->lightmaps = true;
Printf(PRINT_HIGH, "InitLevelMesh: The level contains LIGHTMAP, but no ZDRayInfo thing was detected in the level.\n");
}
Level->lightmaps = *genlightmaps;
}
// Levelmesh and lightmap binding/loading
Level->levelMesh = new DoomLevelMesh(*Level); Level->levelMesh = new DoomLevelMesh(*Level);
Level->lightmaps = true;
if (Level->lightmaps)
{
LoadLightmap(map);
Level->levelMesh->BindLightmapSurfacesToGeometry(*Level);
}
else
{
Level->levelMesh->Surfaces.Clear(); // Temp hack that disables lightmapping
}
}
void MapLoader::LoadLightmap(MapData* map)
{
if (!map->Size(ML_LIGHTMAP))
return;
FileReader fr;
if (!fr.OpenDecompressor(map->Reader(ML_LIGHTMAP), -1, FileSys::METHOD_ZLIB, false, true))
return;
int version = fr.ReadInt32();
if (version == 0)
{
Printf(PRINT_HIGH, "LoadLightmap: This is an old unsupported alpha version of the lightmap lump. Please rebuild the map with a newer version of zdray.\n");
return;
}
if (version != 1)
{
Printf(PRINT_HIGH, "LoadLightmap: unsupported lightmap lump version\n");
return;
}
uint16_t textureSize = fr.ReadUInt16();
uint16_t numTextures = fr.ReadUInt16();
uint32_t numSurfaces = fr.ReadUInt32();
uint32_t numTexCoords = fr.ReadUInt32();
uint32_t numSubsectors = fr.ReadUInt32();
uint32_t numTexBytes = numTextures * textureSize * textureSize * 3 * 2;
if (numSurfaces == 0 || numTexCoords == 0 || numTexBytes == 0)
return;
float sunDir[3], sunColor[3];
fr.Read(sunDir, sizeof(float) * 3);
fr.Read(sunColor, sizeof(float) * 3);
Level->SunDirection = FVector3(sunDir);
Level->SunColor = FVector3(sunColor);
/*if (numSubsectors != Level->subsectors.Size())
{
Printf(PRINT_HIGH, "LoadLightmap: subsector count for level doesn't match (%d in wad vs %d in engine)\n", (int)numSubsectors, (int)Level->subsectors.Size());
}*/
//Level->LMTexCoords.Resize(numTexCoords * 2);
// Allocate room for all surfaces
#if 0
unsigned int allSurfaces = 0;
for (unsigned int i = 0; i < Level->sides.Size(); i++)
allSurfaces += 4 + Level->sides[i].sector->e->XFloor.ffloors.Size();
for (unsigned int i = 0; i < Level->subsectors.Size(); i++)
allSurfaces += 2 + Level->subsectors[i].sector->e->XFloor.ffloors.Size() * 2;
Level->LMSurfaces.Resize(allSurfaces);
memset(&Level->LMSurfaces[0], 0, sizeof(LightmapSurface) * allSurfaces);
// Link the surfaces to sectors, sides and their 3D floors
unsigned int offset = 0;
for (unsigned int i = 0; i < Level->sides.Size(); i++)
{
auto& side = Level->sides[i];
side.lightmap = &Level->LMSurfaces[offset];
offset += 4 + side.sector->e->XFloor.ffloors.Size();
}
for (unsigned int i = 0; i < Level->subsectors.Size(); i++)
{
auto& subsector = Level->subsectors[i];
unsigned int count = 1 + subsector.sector->e->XFloor.ffloors.Size();
subsector.lightmap[0] = &Level->LMSurfaces[offset];
subsector.lightmap[1] = &Level->LMSurfaces[offset + count];
offset += count * 2;
}
#endif
bool errors = false;
// Load the surfaces we have lightmap data for
// TODO more optimized way:
auto findSurfaceIndex = [&](int type, int index, const sector_t* sec) {
const auto& surfaces = Level->levelMesh->Surfaces;
for (unsigned i = 0, count = surfaces.Size(); i < count; ++i)
{
if (surfaces[i].Type == type && surfaces[i].typeIndex == index && sec == surfaces[i].ControlSector)
{
return i;
}
}
return 0xffffffff;
};
TArray<DoomLevelMeshSurface> zdraySurfaces;
for (uint32_t i = 0; i < numSurfaces; i++)
{
LevelMeshSurfaceType type = (LevelMeshSurfaceType)fr.ReadUInt32();
uint32_t typeIndex = fr.ReadUInt32();
uint32_t controlSectorIndex = fr.ReadUInt32();
uint32_t lightmapNum = fr.ReadUInt32();
uint32_t firstTexCoord = fr.ReadUInt32();
auto controlSector = controlSectorIndex < Level->sectors.Size() ? &Level->sectors[controlSectorIndex] : nullptr;
// Check against the internal levelmesh
if (i >= Level->levelMesh->Surfaces.Size())
{
errors = true;
if (*developer >= 1)
{
Printf(PRINT_HIGH, "Lightmap lump surface index %d is out of bounds\n", i);
}
continue;
}
auto levelSurface = &Level->levelMesh->Surfaces[i];
if (levelSurface->Type != type || levelSurface->typeIndex != typeIndex || levelSurface->ControlSector != controlSector)
{
auto internalIndex = findSurfaceIndex(type, typeIndex, controlSector);
if (internalIndex < Level->levelMesh->Surfaces.Size())
{
levelSurface = &Level->levelMesh->Surfaces[internalIndex];
}
else
{
errors = true;
if (*developer >= 1)
{
Printf(PRINT_HIGH, "Lightmap lump surface %d mismatch. Couldn't find surface type:%d, typeindex:%d, controlsector:%d\n", i, type, typeIndex, controlSectorIndex);
}
// TODO detailed printout
continue;
}
}
DoomLevelMeshSurface surface;
surface.startUvIndex = firstTexCoord;
surface.typeIndex = typeIndex;
surface.ControlSector = controlSector;
surface.Type = type;
surface.numVerts = levelSurface->numVerts;
surface.atlasPageIndex = lightmapNum;
zdraySurfaces.Push(surface);
#if 0
if (type == ST_CEILING || type == ST_FLOOR)
{
surface.Subsector = &Level->subsectors[typeIndex];
surface.Subsector->firstline->sidedef->sector->HasLightmaps = true;
SetSubsectorLightmap(surface);
}
else if (type != ST_NULL)
{
surface.Side = &Level->sides[typeIndex];
SetSideLightmap(surface);
}
#endif
}
// Load texture coordinates
TArray<FVector2> zdrayUvs;
zdrayUvs.Resize(numTexCoords);
fr.Read(&zdrayUvs[0], numTexCoords * 2 * sizeof(float));
// Load lightmap textures
Level->levelMesh->LMTextureData.Resize(Level->levelMesh->LMTextureCount* Level->levelMesh->LMTextureSize * Level->levelMesh->LMTextureSize * 3);
TArray<uint16_t> textureData;
textureData.Resize((numTexBytes + 1) / 2);
uint8_t* data = (uint8_t*)&textureData[0];
fr.Read(data, numTexBytes);
// Remap the ZDRay atlas into internal lightmapper
for (auto& surface : zdraySurfaces)
{
auto& realSurface = Level->levelMesh->Surfaces[findSurfaceIndex(surface.Type, surface.typeIndex, surface.ControlSector)];
// what are the pixel boundaries in the atlas?
BBox bbox;
for (int i = 0; i < surface.numVerts; ++i)
{
auto& uv = zdrayUvs[surface.startUvIndex + i];
bbox.AddPoint(FVector3(uv.X, uv.Y, 0));
}
// calculate pixel positions
int srcMinX = (int)floorf(bbox.min.X * textureSize - 1.0f);
int srcMinY = (int)floorf(bbox.min.Y * textureSize - 1.0f);
int srcMaxX = (int)ceilf(bbox.max.X * textureSize + 1.0f);
int srcMaxY = (int)ceilf(bbox.max.Y * textureSize + 1.0f);
int srcPage = surface.atlasPageIndex;
int srcW = srcMaxX - srcMinX;
int srcH = srcMaxY - srcMinY;
// Now let's check
int dstX = realSurface.atlasX;
int dstY = realSurface.atlasY;
int dstPage = realSurface.atlasPageIndex;
// Sanity checks
if (srcMinX < 0 || srcMinY < 0 || dstX < 0 || dstY < 0 || srcMaxX >= textureSize || srcMaxY >= textureSize || dstX + srcW >= textureSize || dstY + srcH >= textureSize || srcPage >= numTextures || dstPage >= Level->levelMesh->LMTextureCount)
{
errors = true;
if (*developer >= 1)
{
Printf("Can't remap lightmap surface ((%d, %d), (%d, %d), %d) -> ((%d, %d), (%d, %d), %d)\n", srcMinX, srcMinY, srcMaxX, srcMaxY, srcPage, dstX, dstY, dstX + srcW, dstY + srcH, dstPage);
}
continue;
}
// copy pixels
uint16_t* src = &textureData[0];
uint16_t* dst = &Level->levelMesh->LMTextureData[0];
for (int y = srcMinY, y2 = dstY; y < srcMaxY; ++y, ++y2)
{
for (int x = srcMinX, x2 = dstX; x < srcMaxX; ++x, ++x2)
{
int index = (x + y * textureSize + srcPage * textureSize * textureSize) * 3;
int dstIndex = (x2 + y2 * textureSize + dstPage * textureSize * textureSize) * 3;
dst[dstIndex] = src[index];
dst[dstIndex + 1] = src[index + 1];
dst[dstIndex + 2] = src[index + 2];
}
}
realSurface.needsUpdate = false; // this surface is good
}
if (errors && developer <= 0)
{
Printf(PRINT_HIGH, "Pre-calculated LIGHTMAP surfaces do not match current level surfaces. Restart this level with 'developer 1' for further details.\nPerhaps you forget to rebuild lightmaps after modifying the map?");
}
#if 0
// Apply compression predictor
for (uint32_t i = 1; i < numTexBytes; i++)
data[i] += data[i - 1];
#endif
} }
//========================================================================== //==========================================================================
@ -2979,6 +3252,7 @@ void MapLoader::LoadLevel(MapData *map, const char *lumpname, int position)
Level->SunColor = FVector3(1.f, 1.f, 1.f); Level->SunColor = FVector3(1.f, 1.f, 1.f);
Level->SunDirection = FVector3(0.45f, 0.3f, 0.9f); Level->SunDirection = FVector3(0.45f, 0.3f, 0.9f);
Level->LightmapSampleDistance = 16; Level->LightmapSampleDistance = 16;
Level->lightmaps = false;
// note: most of this ordering is important // note: most of this ordering is important
ForceNodeBuild = gennodes; ForceNodeBuild = gennodes;
@ -3258,7 +3532,7 @@ void MapLoader::LoadLevel(MapData *map, const char *lumpname, int position)
// set up world state // set up world state
SpawnSpecials(); SpawnSpecials();
InitLevelMesh(); InitLevelMesh(map);
// disable reflective planes on sloped sectors. // disable reflective planes on sloped sectors.
for (auto &sec : Level->sectors) for (auto &sec : Level->sectors)

View file

@ -304,7 +304,8 @@ public:
void SetSlopes(); void SetSlopes();
void CopySlopes(); void CopySlopes();
void InitLevelMesh(); void InitLevelMesh(MapData* map);
void LoadLightmap(MapData* map);
void LoadLevel(MapData *map, const char *lumpname, int position); void LoadLevel(MapData *map, const char *lumpname, int position);

View file

@ -835,6 +835,7 @@ public:
auto pc = pitch.Cos(); auto pc = pitch.Cos();
Level->SunDirection = -FVector3 { pc * angle.Cos(), pc * angle.Sin(), -pitch.Sin() }; // [RaveYard]: is there a dedicated function for this? Level->SunDirection = -FVector3 { pc * angle.Cos(), pc * angle.Sin(), -pitch.Sin() }; // [RaveYard]: is there a dedicated function for this?
Level->lightmaps = true;
} }
} }

View file

@ -95,18 +95,8 @@ DoomLevelMesh::DoomLevelMesh(FLevelLocals &doomMap)
} }
SetupLightmapUvs(); SetupLightmapUvs();
BindLightmapSurfacesToGeometry(doomMap);
Collision = std::make_unique<TriangleMeshShape>(MeshVertices.Data(), MeshVertices.Size(), MeshElements.Data(), MeshElements.Size()); Collision = std::make_unique<TriangleMeshShape>(MeshVertices.Data(), MeshVertices.Size(), MeshElements.Data(), MeshElements.Size());
// Runtime stuff
for (auto& surface : Surfaces)
{
if ((surface.Type == ST_FLOOR || surface.Type == ST_CEILING) && surface.ControlSector)
{
XFloorToSurface[surface.Subsector->sector].Push(&surface);
}
}
} }
void DoomLevelMesh::CreatePortals() void DoomLevelMesh::CreatePortals()
@ -382,6 +372,15 @@ void DoomLevelMesh::BindLightmapSurfacesToGeometry(FLevelLocals& doomMap)
SetSideLightmap(&surface); SetSideLightmap(&surface);
} }
} }
// Runtime helper
for (auto& surface : Surfaces)
{
if ((surface.Type == ST_FLOOR || surface.Type == ST_CEILING) && surface.ControlSector)
{
XFloorToSurface[surface.Subsector->sector].Push(&surface);
}
}
} }
void DoomLevelMesh::SetSubsectorLightmap(DoomLevelMeshSurface* surface) void DoomLevelMesh::SetSubsectorLightmap(DoomLevelMeshSurface* surface)

View file

@ -29,6 +29,7 @@ public:
void CreatePortals(); void CreatePortals();
void DumpMesh(const FString& objFilename, const FString& mtlFilename) const; void DumpMesh(const FString& objFilename, const FString& mtlFilename) const;
void BindLightmapSurfacesToGeometry(FLevelLocals& doomMap);
bool TraceSky(const FVector3& start, FVector3 direction, float dist) bool TraceSky(const FVector3& start, FVector3 direction, float dist)
{ {
@ -58,7 +59,6 @@ private:
void CreateFloorSurface(FLevelLocals &doomMap, subsector_t *sub, sector_t *sector, int typeIndex, bool is3DFloor); void CreateFloorSurface(FLevelLocals &doomMap, subsector_t *sub, sector_t *sector, int typeIndex, bool is3DFloor);
void CreateSideSurfaces(FLevelLocals &doomMap, side_t *side); void CreateSideSurfaces(FLevelLocals &doomMap, side_t *side);
void BindLightmapSurfacesToGeometry(FLevelLocals& doomMap);
void SetSubsectorLightmap(DoomLevelMeshSurface* surface); void SetSubsectorLightmap(DoomLevelMeshSurface* surface);
void SetSideLightmap(DoomLevelMeshSurface* surface); void SetSideLightmap(DoomLevelMeshSurface* surface);

View file

@ -504,25 +504,27 @@ void HWFlat::ProcessSector(HWDrawInfo *di, FRenderState& state, sector_t * front
// //
// Lightmaps // Lightmaps
// //
if (level.lightmaps)
for (int i = 0, count = sector->subsectorcount; i < count; ++i)
{ {
for (int plane = 0; plane < 2; ++plane) for (int i = 0, count = sector->subsectorcount; i < count; ++i)
{ {
if (auto lightmap = sector->subsectors[i]->lightmap[plane][0]) for (int plane = 0; plane < 2; ++plane)
{ {
state.PushVisibleSurface(lightmap); if (auto lightmap = sector->subsectors[i]->lightmap[plane][0])
{
state.PushVisibleSurface(lightmap);
}
} }
} }
}
if (auto subsectors = sector->Level->levelMesh->XFloorToSurface.CheckKey(sector)) if (auto subsectors = sector->Level->levelMesh->XFloorToSurface.CheckKey(sector))
{
for (auto* surface : *subsectors)
{ {
if (surface) for (auto* surface : *subsectors)
{ {
state.PushVisibleSurface(surface); if (surface)
{
state.PushVisibleSurface(surface);
}
} }
} }
} }