Add savelightmap and deletelightmap ccmds

This commit is contained in:
Magnus Norddahl 2024-09-30 18:48:48 +02:00
commit d69d190dc3
7 changed files with 465 additions and 0 deletions

View file

@ -227,6 +227,7 @@ public:
virtual void PostProcessScene(bool swscene, int fixedcm, float flash, const std::function<void()> &afterBloomDrawEndScene2D) { if (afterBloomDrawEndScene2D) afterBloomDrawEndScene2D(); }
virtual int GetLevelMeshPipelineID(const MeshApplyData& applyData, const SurfaceUniforms& surfaceUniforms, const FMaterialState& material) { return 0; }
virtual void DownloadLightmap(int arrayIndex, uint16_t* buffer) { }
void ScaleCoordsFromWindow(int16_t &x, int16_t &y);

View file

@ -267,3 +267,40 @@ void VkTextureManager::CreateLightmap(int newLMTextureSize, int newLMTextureCoun
.AddImage(&Lightmap, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, false, 0, 1, 0, LMTextureCount)
.Execute(cmdbuffer);
}
void VkTextureManager::DownloadLightmap(int arrayIndex, uint16_t* buffer)
{
unsigned int totalSize = LMTextureSize * LMTextureSize * 4;
auto stagingBuffer = BufferBuilder()
.Size(totalSize * sizeof(uint16_t))
.Usage(VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_CPU_ONLY)
.DebugName("DownloadLightmap")
.Create(fb->GetDevice());
auto cmdbuffer = fb->GetCommands()->GetTransferCommands();
PipelineBarrier()
.AddImage(Lightmap.Image.get(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, arrayIndex, 1)
.Execute(cmdbuffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
VkBufferImageCopy region = {};
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.baseArrayLayer = arrayIndex;
region.imageSubresource.layerCount = 1;
region.imageSubresource.mipLevel = 0;
region.imageExtent.width = LMTextureSize;
region.imageExtent.height = LMTextureSize;
region.imageExtent.depth = 1;
cmdbuffer->copyImageToBuffer(Lightmap.Image->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, stagingBuffer->buffer, 1, &region);
PipelineBarrier()
.AddImage(Lightmap.Image.get(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, arrayIndex, 1)
.Execute(cmdbuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
fb->GetCommands()->WaitForCommands(false);
uint16_t* srcdata = (uint16_t*)stagingBuffer->Map(0, totalSize * sizeof(uint16_t));
memcpy(buffer, srcdata, totalSize * sizeof(uint16_t));
stagingBuffer->Unmap();
}

View file

@ -24,6 +24,7 @@ public:
void BeginFrame();
void CreateLightmap(int newLMTextureSize, int newLMTextureCount, TArray<uint16_t>&& newPixelData);
void DownloadLightmap(int arrayIndex, uint16_t* buffer);
VkTextureImage* GetTexture(const PPTextureType& type, PPTexture* tex);
VkFormat GetTextureFormat(PPTexture* texture);

View file

@ -649,6 +649,11 @@ void VulkanRenderDevice::SetSceneRenderTarget(bool useSSAO)
mRenderState->SetRenderTarget(&GetBuffers()->SceneColor, GetBuffers()->SceneDepthStencil.View.get(), GetBuffers()->GetWidth(), GetBuffers()->GetHeight(), VK_FORMAT_R16G16B16A16_SFLOAT, GetBuffers()->GetSceneSamples());
}
void VulkanRenderDevice::DownloadLightmap(int arrayIndex, uint16_t* buffer)
{
mTextureManager->DownloadLightmap(arrayIndex, buffer);
}
int VulkanRenderDevice::GetBindlessTextureIndex(FMaterial* material, int clampmode, int translation)
{
FMaterialState materialState;

View file

@ -94,6 +94,7 @@ public:
int GetBindlessTextureIndex(FMaterial* material, int clampmode, int translation) override;
int GetLevelMeshPipelineID(const MeshApplyData& applyData, const SurfaceUniforms& surfaceUniforms, const FMaterialState& material) override;
void DownloadLightmap(int arrayIndex, uint16_t* buffer) override;
const VkPipelineKey& GetLevelMeshPipelineKey(int id) const;

View file

@ -94,6 +94,20 @@ CCMD(invalidatelightmap)
Printf("Marked %d out of %d tiles for update.\n", count, level.levelMesh->LightmapTiles.Size());
}
CCMD(savelightmap)
{
if (!RequireLightmap()) return;
level.levelMesh->SaveLightmapLump(level);
}
CCMD(deletelightmap)
{
if (!RequireLightmap()) return;
level.levelMesh->DeleteLightmapLump(level);
}
void DoomLevelMesh::PrintSurfaceInfo(const LevelMeshSurface* surface)
{
if (!RequireLevelMesh()) return;
@ -1769,3 +1783,405 @@ void DoomLevelMesh::CreatePortals(FLevelLocals& doomMap)
}
}
}
class LumpWriter
{
public:
LumpWriter(size_t size) { buffer.Reserve(size); }
void Write(const void* data, size_t size)
{
if (pos + size > buffer.size() || size > 0xffffffff)
I_FatalError("LumpWriter ran out of space!");
memcpy(buffer.data() + pos, data, size);
pos += size;
}
void Write8(const uint8_t val) { Write(&val, sizeof(uint8_t)); }
void Write16(const short val) { Write(&val, sizeof(uint16_t)); }
void Write32(const int val) { Write(&val, sizeof(uint32_t)); }
void WriteFloat(const float val) { Write(&val, sizeof(float)); }
TArray<uint8_t> DeflateCompress()
{
TArray<uint8_t> output;
enum { BUFFER_SIZE = 8192 };
uint8_t Buffer[BUFFER_SIZE];
mz_stream Stream = {};
int err = deflateInit(&Stream, 9);
if (err != Z_OK)
I_FatalError("Could not initialize deflate buffer.");
Stream.next_out = Buffer;
Stream.avail_out = BUFFER_SIZE;
Stream.next_in = buffer.data();
Stream.avail_in = buffer.size();
err = mz_deflate(&Stream, 0);
while (Stream.avail_out == 0 && err == Z_OK)
{
AddBytes(output, Buffer, BUFFER_SIZE);
Stream.next_out = Buffer;
Stream.avail_out = BUFFER_SIZE;
if (Stream.avail_in != 0)
{
err = mz_deflate(&Stream, 0);
}
}
if (err != Z_OK)
I_FatalError("Error deflating data.");
while (true)
{
err = mz_deflate(&Stream, Z_FINISH);
if (err != Z_OK)
{
break;
}
if (Stream.avail_out == 0)
{
AddBytes(output, Buffer, BUFFER_SIZE);
Stream.next_out = Buffer;
Stream.avail_out = BUFFER_SIZE;
}
}
mz_deflateEnd(&Stream);
AddBytes(output, Buffer, BUFFER_SIZE - Stream.avail_out);
return output;
}
private:
static void AddBytes(TArray<uint8_t>& output, const void* data, size_t size)
{
int index = output.Reserve(size);
memcpy(&output[index], data, size);
}
size_t pos = 0;
TArray<uint8_t> buffer;
};
class MapLump
{
public:
FString Name;
TArray<uint8_t> Data;
};
TArray<MapLump> LoadMapLumps(FileReader* reader, const char* wadType)
{
char magic[4] = {};
uint32_t numlumps = 0;
uint32_t infotableofs = 0;
if (reader->Read(magic, 4) != 4) return {};
if (memcmp(magic, wadType, 4) != 0) return {};
if (reader->Read(&numlumps, 4) != 4) return {};
if (reader->Read(&infotableofs, 4) != 4) return {};
if (reader->Seek(infotableofs, FileReader::SeekSet) == -1) return {};
TArray<MapLump> lumps;
TArray<uint32_t> offsets;
lumps.Reserve(numlumps);
offsets.Reserve(numlumps);
for (uint32_t i = 0; i < numlumps; i++)
{
uint32_t filepos = 0, lumpsize = 0;
char name[9] = {};
if (reader->Read(&filepos, 4) != 4) return {};
if (reader->Read(&lumpsize, 4) != 4) return {};
if (reader->Read(name, 8) != 8) return {};
offsets[i] = filepos;
lumps[i].Name = name;
lumps[i].Data.Reserve(lumpsize);
}
for (uint32_t i = 0; i < numlumps; i++)
{
if (reader->Seek(offsets[i], FileReader::SeekSet) == -1) return {};
if (reader->Read(lumps[i].Data.data(), lumps[i].Data.size()) != lumps[i].Data.size()) return {};
}
return lumps;
}
void SaveMapLumps(FileWriter* writer, const TArray<MapLump>& lumps, const char* wadType)
{
uint32_t numlumps = (uint32_t)lumps.size();
uint32_t infotableofs = 12;
for (uint32_t i = 0; i < numlumps; i++)
infotableofs += lumps[i].Data.size();
writer->Write(wadType, 4);
writer->Write(&numlumps, 4);
writer->Write(&infotableofs, 4);
TArray<uint32_t> offsets;
offsets.Reserve(numlumps);
uint32_t pos = 12;
for (uint32_t i = 0; i < numlumps; i++)
{
offsets[i] = pos;
writer->Write(lumps[i].Data.data(), lumps[i].Data.size());
pos += lumps[i].Data.size();
}
for (uint32_t i = 0; i < numlumps; i++)
{
uint32_t filepos = offsets[i];
uint32_t lumpsize = lumps[i].Data.size();
char name[8] = {};
memcpy(name, lumps[i].Name.GetChars(), lumps[i].Name.Len());
writer->Write(&filepos, 4);
writer->Write(&lumpsize, 4);
writer->Write(name, 8);
}
}
void DoomLevelMesh::SaveLightmapLump(FLevelLocals& doomMap)
{
/*
// LIGHTMAP V3 pseudo-C specification:
struct LightmapLump
{
int version = 2;
uint32_t tileCount;
uint32_t pixelCount;
uint32_t uvCount;
SurfaceEntry surfaces[surfaceCount];
uint16_t pixels[pixelCount * 3];
};
struct TileEntry
{
uint32_t type, typeIndex;
uint32_t controlSector; // 0xFFFFFFFF is none
uint16_t width, height; // in pixels
uint32_t pixelsOffset; // offset in pixels array
vec3 translateWorldToLocal;
vec3 projLocalToU;
vec3 projLocalToV;
};
*/
LMTextureData.Resize(LMTextureSize * LMTextureSize * LMTextureCount * 4);
for (int arrayIndex = 0; arrayIndex < LMTextureCount; arrayIndex++)
{
screen->DownloadLightmap(arrayIndex, LMTextureData.Data() + arrayIndex * LMTextureSize * LMTextureSize * 4);
}
// Calculate size of lump
uint32_t tileCount = 0;
uint32_t pixelCount = 0;
for (unsigned int i = 0; i < LightmapTiles.Size(); i++)
{
LightmapTile* tile = &LightmapTiles[i];
if (tile->AtlasLocation.ArrayIndex != -1)
{
tileCount++;
pixelCount += tile->AtlasLocation.Area();
}
}
const int version = 3;
const uint32_t headerSize = sizeof(int) + 2 * sizeof(uint32_t);
const uint32_t bytesPerTileEntry = sizeof(uint32_t) * 4 + sizeof(uint16_t) * 2 + sizeof(float) * 9;
const uint32_t bytesPerPixel = sizeof(uint16_t) * 3; // F16 RGB
uint32_t lumpSize = headerSize + tileCount * bytesPerTileEntry + pixelCount * bytesPerPixel;
LumpWriter lumpFile(lumpSize);
// Write header
lumpFile.Write32(version);
lumpFile.Write32(tileCount);
lumpFile.Write32(pixelCount);
// Write tiles
uint32_t pixelsOffset = 0;
for (unsigned int i = 0; i < LightmapTiles.Size(); i++)
{
LightmapTile* tile = &LightmapTiles[i];
if (tile->AtlasLocation.ArrayIndex == -1)
continue;
lumpFile.Write32(tile->Binding.Type);
lumpFile.Write32(tile->Binding.TypeIndex);
lumpFile.Write32(tile->Binding.ControlSector);
lumpFile.Write16(uint16_t(tile->AtlasLocation.Width));
lumpFile.Write16(uint16_t(tile->AtlasLocation.Height));
lumpFile.Write32(pixelsOffset * 3);
lumpFile.WriteFloat(tile->Transform.TranslateWorldToLocal.X);
lumpFile.WriteFloat(tile->Transform.TranslateWorldToLocal.Y);
lumpFile.WriteFloat(tile->Transform.TranslateWorldToLocal.Z);
lumpFile.WriteFloat(tile->Transform.ProjLocalToU.X);
lumpFile.WriteFloat(tile->Transform.ProjLocalToU.Y);
lumpFile.WriteFloat(tile->Transform.ProjLocalToU.Z);
lumpFile.WriteFloat(tile->Transform.ProjLocalToV.X);
lumpFile.WriteFloat(tile->Transform.ProjLocalToV.Y);
lumpFile.WriteFloat(tile->Transform.ProjLocalToV.Z);
pixelsOffset += tile->AtlasLocation.Area();
}
// Write surface pixels
for (unsigned int i = 0; i < LightmapTiles.Size(); i++)
{
LightmapTile* tile = &LightmapTiles[i];
if (tile->AtlasLocation.ArrayIndex == -1)
continue;
const uint16_t* pixels = LMTextureData.Data() + tile->AtlasLocation.ArrayIndex * LMTextureSize * LMTextureSize * 4;
int width = tile->AtlasLocation.Width;
int height = tile->AtlasLocation.Height;
for (int y = 0; y < height; y++)
{
const uint16_t* srcline = pixels + (tile->AtlasLocation.X + (tile->AtlasLocation.Y + y) * LMTextureSize) * 4;
for (int x = 0; x < width; x++)
{
lumpFile.Write16(*(srcline++));
lumpFile.Write16(*(srcline++));
lumpFile.Write16(*(srcline++));
srcline++;
}
}
}
FString fullpath = GetMapFilename(doomMap);
if (fullpath.Len() == 0)
return;
Printf("Saving LIGHTMAP lump into %s\n", fullpath.GetChars());
FileReader reader;
if (!reader.OpenFile(fullpath.GetChars()))
{
I_Error("Could not open map WAD file");
return;
}
TArray<MapLump> lumps = LoadMapLumps(&reader, "PWAD");
if (lumps.Size() == 0)
{
I_Error("Could not read map WAD file");
return;
}
reader.Close();
int lightmapIndex = -1;
int endmapIndex = lumps.size();
FString strLightmap = "LIGHTMAP";
FString strEndmap = "ENDMAP";
for (int i = 0, count = lumps.size(); i < count; i++)
{
if (lumps[i].Name == strLightmap)
lightmapIndex = i;
else if (lumps[i].Name == strEndmap)
endmapIndex = i;
}
if (lightmapIndex != -1)
lumps[lightmapIndex].Data = lumpFile.DeflateCompress();
else
lumps.Insert(endmapIndex, { "LIGHTMAP", lumpFile.DeflateCompress()});
std::unique_ptr<FileWriter> writer(FileWriter::Open(fullpath.GetChars()));
if (writer)
{
SaveMapLumps(writer.get(), lumps, "PWAD");
}
else
{
I_Error("Could not write map WAD file");
}
}
void DoomLevelMesh::DeleteLightmapLump(FLevelLocals& doomMap)
{
FString fullpath = GetMapFilename(doomMap);
if (fullpath.Len() == 0)
return;
Printf("Deleting LIGHTMAP lump into %s\n", fullpath.GetChars());
FileReader reader;
if (!reader.OpenFile(fullpath.GetChars()))
{
I_Error("Could not open map WAD file");
return;
}
TArray<MapLump> lumps = LoadMapLumps(&reader, "PWAD");
if (lumps.Size() == 0)
{
I_Error("Could not read map WAD file");
return;
}
reader.Close();
int lightmapIndex = -1;
FString strLightmap = "LIGHTMAP";
for (int i = 0, count = lumps.size(); i < count; i++)
{
if (lumps[i].Name == strLightmap)
lightmapIndex = i;
}
if (lightmapIndex == -1)
return;
lumps.Delete(lightmapIndex);
std::unique_ptr<FileWriter> writer(FileWriter::Open(fullpath.GetChars()));
if (writer)
{
SaveMapLumps(writer.get(), lumps, "PWAD");
}
else
{
I_Error("Could not write map WAD file");
}
}
FString DoomLevelMesh::GetMapFilename(FLevelLocals& doomMap)
{
const char* mapname = doomMap.MapName.GetChars();
FString fmt;
fmt.Format("maps/%s.wad", mapname);
int lump_wad = fileSystem.CheckNumForFullName(fmt.GetChars());
if (lump_wad == -1)
{
I_Error("Could not find map lump");
return {};
}
int wadnum = fileSystem.GetFileContainer(lump_wad);
if (wadnum == -1)
{
I_Error("Could not find map folder");
return {};
}
FString filename = fileSystem.GetFileFullName(lump_wad);
FString folder = fileSystem.GetResourceFileFullName(wadnum);
FString fullpath = folder + filename;
return fullpath;
}

View file

@ -109,6 +109,10 @@ public:
void CreateLights(FLevelLocals& doomMap);
void SaveLightmapLump(FLevelLocals& doomMap);
void DeleteLightmapLump(FLevelLocals& doomMap);
static FString GetMapFilename(FLevelLocals& doomMap);
void FloorHeightChanged(sector_t* sector) override;
void CeilingHeightChanged(sector_t* sector) override;
void MidTex3DHeightChanged(sector_t* sector) override;