diff --git a/src/common/rendering/hwrenderer/data/hw_levelmesh.h b/src/common/rendering/hwrenderer/data/hw_levelmesh.h index 3cbcaa4ac6..739e978151 100644 --- a/src/common/rendering/hwrenderer/data/hw_levelmesh.h +++ b/src/common/rendering/hwrenderer/data/hw_levelmesh.h @@ -192,11 +192,12 @@ public: virtual void UpdateLightLists() { } - TArray SmoothingGroups; // TODO fill - TArray Portals; // TODO fill + TArray SmoothingGroups; + TArray Portals; int LMTextureCount = 0; int LMTextureSize = 0; + TArray LMTextureData; // TODO better place for this? FVector3 SunDirection = FVector3(0.0f, 0.0f, -1.0f); FVector3 SunColor = FVector3(0.0f, 0.0f, 0.0f); diff --git a/src/common/rendering/vulkan/textures/vk_texture.cpp b/src/common/rendering/vulkan/textures/vk_texture.cpp index b37f53197a..d29d7094e3 100644 --- a/src/common/rendering/vulkan/textures/vk_texture.cpp +++ b/src/common/rendering/vulkan/textures/vk_texture.cpp @@ -189,17 +189,17 @@ void VkTextureManager::CreateLightmap() SetLightmap(1, 1, data); } -void VkTextureManager::CreateLightmap(int newLMTextureSize, int newLMTextureCount) +void VkTextureManager::CreateLightmap(int newLMTextureSize, int newLMTextureCount, TArray&& newPixelData) { if (LMTextureSize == newLMTextureSize && LMTextureCount == newLMTextureCount) return; LMTextureSize = newLMTextureSize; LMTextureCount = newLMTextureCount; - - int w = LMTextureSize; - int h = LMTextureSize; - int count = LMTextureCount; + + int w = newLMTextureSize; + int h = newLMTextureSize; + int count = newLMTextureCount; int pixelsize = 8; Lightmap.Reset(fb); @@ -219,6 +219,47 @@ void VkTextureManager::CreateLightmap(int newLMTextureSize, int newLMTextureCoun 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, ®ion); + + fb->GetCommands()->TransferDeleteList->Add(std::move(stagingBuffer)); + + newPixelData.Clear(); + } + VkImageTransition() .AddImage(&Lightmap, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, false, 0, count) .Execute(cmdbuffer); diff --git a/src/common/rendering/vulkan/textures/vk_texture.h b/src/common/rendering/vulkan/textures/vk_texture.h index da609b5b1d..530abc9137 100644 --- a/src/common/rendering/vulkan/textures/vk_texture.h +++ b/src/common/rendering/vulkan/textures/vk_texture.h @@ -23,7 +23,7 @@ public: void BeginFrame(); - void CreateLightmap(int LMTextureSize, int LMTextureCount); + void CreateLightmap(int newLMTextureSize, int newLMTextureCount, TArray&& newPixelData); void SetLightmap(int LMTextureSize, int LMTextureCount, const TArray& LMTextureData); VkTextureImage* GetTexture(const PPTextureType& type, PPTexture* tex); diff --git a/src/common/rendering/vulkan/vk_renderdevice.cpp b/src/common/rendering/vulkan/vk_renderdevice.cpp index 64abf97c55..b15e0550eb 100644 --- a/src/common/rendering/vulkan/vk_renderdevice.cpp +++ b/src/common/rendering/vulkan/vk_renderdevice.cpp @@ -474,7 +474,7 @@ void VulkanRenderDevice::BeginFrame() if (levelMesh && levelMesh->GetSurfaceCount() > 0) { levelMesh->UpdateLightLists(); - GetTextureManager()->CreateLightmap(levelMesh->LMTextureSize, levelMesh->LMTextureCount); + GetTextureManager()->CreateLightmap(levelMesh->LMTextureSize, levelMesh->LMTextureCount, std::move(levelMesh->LMTextureData)); GetLightmap()->SetLevelMesh(levelMesh); #if 0 // full lightmap generation diff --git a/src/maploader/maploader.cpp b/src/maploader/maploader.cpp index d9ad8ea4a2..7b6da7cd08 100644 --- a/src/maploader/maploader.cpp +++ b/src/maploader/maploader.cpp @@ -93,6 +93,7 @@ enum CVAR (Bool, genblockmap, 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) { @@ -2938,7 +2939,7 @@ void MapLoader::CalcIndices() // //========================================================================== -void MapLoader::InitLevelMesh() +void MapLoader::InitLevelMesh(MapData* map) { // Propagate sample distance where it isn't yet set 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->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 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 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 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->SunDirection = FVector3(0.45f, 0.3f, 0.9f); Level->LightmapSampleDistance = 16; + Level->lightmaps = false; // note: most of this ordering is important ForceNodeBuild = gennodes; @@ -3258,7 +3532,7 @@ void MapLoader::LoadLevel(MapData *map, const char *lumpname, int position) // set up world state SpawnSpecials(); - InitLevelMesh(); + InitLevelMesh(map); // disable reflective planes on sloped sectors. for (auto &sec : Level->sectors) diff --git a/src/maploader/maploader.h b/src/maploader/maploader.h index a151fb3088..3dcbddc182 100644 --- a/src/maploader/maploader.h +++ b/src/maploader/maploader.h @@ -304,7 +304,8 @@ public: void SetSlopes(); void CopySlopes(); - void InitLevelMesh(); + void InitLevelMesh(MapData* map); + void LoadLightmap(MapData* map); void LoadLevel(MapData *map, const char *lumpname, int position); diff --git a/src/maploader/udmf.cpp b/src/maploader/udmf.cpp index 4e1069c012..a078fd8611 100644 --- a/src/maploader/udmf.cpp +++ b/src/maploader/udmf.cpp @@ -835,6 +835,7 @@ public: auto pc = pitch.Cos(); Level->SunDirection = -FVector3 { pc * angle.Cos(), pc * angle.Sin(), -pitch.Sin() }; // [RaveYard]: is there a dedicated function for this? + Level->lightmaps = true; } } diff --git a/src/rendering/hwrenderer/doom_levelmesh.cpp b/src/rendering/hwrenderer/doom_levelmesh.cpp index 287740284a..32d15ed7c8 100644 --- a/src/rendering/hwrenderer/doom_levelmesh.cpp +++ b/src/rendering/hwrenderer/doom_levelmesh.cpp @@ -95,18 +95,8 @@ DoomLevelMesh::DoomLevelMesh(FLevelLocals &doomMap) } SetupLightmapUvs(); - BindLightmapSurfacesToGeometry(doomMap); Collision = std::make_unique(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() @@ -382,6 +372,15 @@ void DoomLevelMesh::BindLightmapSurfacesToGeometry(FLevelLocals& doomMap) 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) diff --git a/src/rendering/hwrenderer/doom_levelmesh.h b/src/rendering/hwrenderer/doom_levelmesh.h index bfca86fdda..0cf89fd50e 100644 --- a/src/rendering/hwrenderer/doom_levelmesh.h +++ b/src/rendering/hwrenderer/doom_levelmesh.h @@ -29,6 +29,7 @@ public: void CreatePortals(); void DumpMesh(const FString& objFilename, const FString& mtlFilename) const; + void BindLightmapSurfacesToGeometry(FLevelLocals& doomMap); 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 CreateSideSurfaces(FLevelLocals &doomMap, side_t *side); - void BindLightmapSurfacesToGeometry(FLevelLocals& doomMap); void SetSubsectorLightmap(DoomLevelMeshSurface* surface); void SetSideLightmap(DoomLevelMeshSurface* surface); diff --git a/src/rendering/hwrenderer/scene/hw_flats.cpp b/src/rendering/hwrenderer/scene/hw_flats.cpp index 115731edf7..acbbe858b2 100644 --- a/src/rendering/hwrenderer/scene/hw_flats.cpp +++ b/src/rendering/hwrenderer/scene/hw_flats.cpp @@ -504,25 +504,27 @@ void HWFlat::ProcessSector(HWDrawInfo *di, FRenderState& state, sector_t * front // // Lightmaps // - - for (int i = 0, count = sector->subsectorcount; i < count; ++i) + if (level.lightmaps) { - 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)) - { - for (auto* surface : *subsectors) + if (auto subsectors = sector->Level->levelMesh->XFloorToSurface.CheckKey(sector)) { - if (surface) + for (auto* surface : *subsectors) { - state.PushVisibleSurface(surface); + if (surface) + { + state.PushVisibleSurface(surface); + } } } }