Skip to content

Commit 4fdfcff

Browse files
committed
Reuse immutable level data on reload
1 parent d2a94b4 commit 4fdfcff

9 files changed

Lines changed: 159 additions & 31 deletions

File tree

‎common/gothic.cpp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ Gothic::Gothic() {
9090
defaults->set("GAME", "useGothic1Controls", 1);
9191
defaults->set("GAME", "highlightMeleeFocus", 0);
9292
defaults->set("GAME", "useQuickSaveKeys", 1);
93+
defaults->set("ENGINE", "worldCacheMiB", 512);
9394

9495
defaults->set("GAME", "animatedWindows", 1);
9596
defaults->set("GAME", "subTitles", 1);

‎common/physics/dynamicworld.cpp‎

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -554,8 +554,20 @@ struct DynamicWorld::BBoxList final {
554554
DynamicWorld& wrld;
555555
};
556556

557-
DynamicWorld::DynamicWorld(World* owner, const zenkit::Mesh& worldMesh) {
558-
world.reset(new CollisionWorld());
557+
struct DynamicWorld::Landscape {
558+
std::vector<std::string> sectors;
559+
std::vector<btVector3> landVbo;
560+
std::unique_ptr<PhysicVbo> landMesh, waterMesh;
561+
std::unique_ptr<btCollisionShape> landShape, waterShape;
562+
size_t bytes = 0;
563+
};
564+
565+
std::shared_ptr<const DynamicWorld::Landscape> DynamicWorld::buildLandscape(const zenkit::Mesh& worldMesh) {
566+
auto result = std::make_shared<Landscape>();
567+
auto& sectors = result->sectors;
568+
auto& landVbo = result->landVbo;
569+
auto& landMesh = result->landMesh;
570+
auto& waterMesh = result->waterMesh;
559571

560572
{
561573
PackedMesh pkg(worldMesh,PackedMesh::PK_Physic);
@@ -584,12 +596,38 @@ DynamicWorld::DynamicWorld(World* owner, const zenkit::Mesh& worldMesh) {
584596
}
585597
}
586598

599+
result->bytes = sizeof(Landscape) + landVbo.capacity()*sizeof(btVector3) + landMesh->byteSize() + waterMesh->byteSize();
600+
result->bytes += sectors.capacity()*sizeof(std::string);
601+
for(const auto& sector:sectors)
602+
result->bytes += sector.capacity();
603+
auto shape = [&](PhysicVbo& mesh) -> std::unique_ptr<btCollisionShape> {
604+
if(mesh.isEmpty())
605+
return nullptr;
606+
auto value = std::make_unique<btMultimaterialTriangleMeshShape>(&mesh,mesh.useQuantization(),true);
607+
result->bytes += sizeof(*value) + value->getOptimizedBvh()->calculateSerializeBufferSize();
608+
return value;
609+
};
610+
result->landShape = shape(*landMesh);
611+
result->waterShape = shape(*waterMesh);
612+
return result;
613+
}
614+
615+
size_t DynamicWorld::landscapeBytes(const Landscape& landscape) {
616+
return landscape.bytes;
617+
}
618+
619+
DynamicWorld::DynamicWorld(World* owner, const zenkit::Mesh& mesh)
620+
:DynamicWorld(owner,buildLandscape(mesh)) {
621+
}
622+
623+
DynamicWorld::DynamicWorld(World* owner, std::shared_ptr<const Landscape> data) : landscape(std::move(data)) {
624+
world.reset(new CollisionWorld());
625+
587626
btVector3 bbox[2] = {btVector3(0,0,0), btVector3(0,0,0)};
588-
if(!landMesh->isEmpty()) {
627+
if(landscape->landShape) {
589628
Tempest::Matrix4x4 mt;
590629
mt.identity();
591-
landShape.reset(new btMultimaterialTriangleMeshShape(landMesh.get(),landMesh->useQuantization(),true));
592-
landBody = world->addCollisionBody(*landShape,mt,DynamicWorld::materialFriction(zenkit::MaterialGroup::NONE));
630+
landBody = world->addCollisionBody(*landscape->landShape,mt,DynamicWorld::materialFriction(zenkit::MaterialGroup::NONE));
593631
landBody->setUserIndex(C_Landscape);
594632

595633
btVector3 b[2] = {btVector3(0,0,0), btVector3(0,0,0)};
@@ -598,11 +636,10 @@ DynamicWorld::DynamicWorld(World* owner, const zenkit::Mesh& worldMesh) {
598636
bbox[1].setMax(b[1]);
599637
}
600638

601-
if(!waterMesh->isEmpty()) {
639+
if(landscape->waterShape) {
602640
Tempest::Matrix4x4 mt;
603641
mt.identity();
604-
waterShape.reset(new btMultimaterialTriangleMeshShape(waterMesh.get(),waterMesh->useQuantization(),true));
605-
waterBody = world->addCollisionBody(*waterShape,mt,0);
642+
waterBody = world->addCollisionBody(*landscape->waterShape,mt,0);
606643
waterBody->setUserIndex(C_Water);
607644
waterBody->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT | btCollisionObject::CF_NO_CONTACT_RESPONSE);
608645
// waterBody->setCollisionFlags(btCollisionObject::CO_HF_FLUID);
@@ -1120,7 +1157,7 @@ float DynamicWorld::rayBox(const Tempest::Vec3& orig, const Tempest::Vec3& dir,
11201157
}
11211158

11221159
std::string_view DynamicWorld::validateSectorName(std::string_view name) const {
1123-
return landMesh->validateSectorName(name);
1160+
return landscape->landMesh->validateSectorName(name);
11241161
}
11251162

11261163
bool DynamicWorld::hasCollision(const NpcItem& it, CollisionTest& out) {

‎common/physics/dynamicworld.h‎

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ class DynamicWorld final {
4141
static constexpr float bulletSpeed = 3; // centimeters per milliseconds
4242
static constexpr float spellSpeed = 1; // centimeters per milliseconds
4343

44+
struct Landscape;
45+
static std::shared_ptr<const Landscape> buildLandscape(const zenkit::Mesh& mesh);
46+
static size_t landscapeBytes(const Landscape& landscape);
4447
DynamicWorld(World *world, const zenkit::Mesh& mesh);
48+
DynamicWorld(World *world, std::shared_ptr<const Landscape> landscape);
4549
DynamicWorld(const DynamicWorld&)=delete;
4650
~DynamicWorld();
4751

@@ -304,16 +308,9 @@ class DynamicWorld final {
304308

305309
std::unique_ptr<CollisionWorld> world;
306310

307-
std::vector<std::string> sectors;
308-
309-
std::vector<btVector3> landVbo;
310-
std::unique_ptr<PhysicVbo> landMesh;
311-
std::unique_ptr<btCollisionShape> landShape;
311+
std::shared_ptr<const Landscape> landscape;
312312
std::unique_ptr<btRigidBody> landBody;
313-
314-
std::unique_ptr<btCollisionShape> waterShape;
315313
std::unique_ptr<btRigidBody> waterBody;
316-
std::unique_ptr<PhysicVbo> waterMesh;
317314

318315
std::unique_ptr<NpcBodyList> npcList;
319316
std::unique_ptr<BulletsList> bulletList;

‎common/physics/physicvbo.cpp‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ bool PhysicVbo::isEmpty() const {
9898
return segments.size()==0;
9999
}
100100

101+
size_t PhysicVbo::byteSize() const {
102+
return sizeof(*this) + vStorage.capacity()*sizeof(btVector3) + id.capacity()*sizeof(uint32_t) +
103+
segments.capacity()*sizeof(Segment) + size_t(m_indexedMeshes.capacity())*sizeof(btIndexedMesh);
104+
}
105+
101106
void PhysicVbo::adjustMesh(){
102107
for(int i=0;i<m_indexedMeshes.size();++i) {
103108
btIndexedMesh& meshIndex=m_indexedMeshes[i];

‎common/physics/physicvbo.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class PhysicVbo : public btTriangleIndexVertexArray {
2626
auto sectorName(size_t segment) const -> const char*;
2727
bool useQuantization() const;
2828
bool isEmpty() const;
29+
size_t byteSize() const;
2930

3031
void adjustMesh();
3132

‎common/resources.cpp‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
#include "graphics/mesh/animation.h"
2828
#include "graphics/mesh/attachbinder.h"
2929
#include "graphics/material.h"
30+
#include "graphics/shaders.h"
31+
#include "utils/workers.h"
32+
#include "world/worlddata.h"
33+
#include <future>
34+
#include <stdexcept>
3035
#include "dmusic/directmusic.h"
3136
#include "utils/fileext.h"
3237
#include "utils/gthfont.h"
@@ -122,11 +127,15 @@ Resources::Resources(Tempest::Device &device)
122127
}
123128

124129
void Resources::mountWork(const std::filesystem::path& path) {
130+
std::lock_guard<std::mutex> guard(inst->syncWorld);
131+
inst->worldCache.reset();
125132
inst->gothicAssets.mkdir("/_work");
126133
inst->gothicAssets.mount_host(path, "/_work", zenkit::VfsOverwriteBehavior::NONE);
127134
}
128135

129136
void Resources::loadVdfs(const std::vector<std::u16string>& modvdfs, bool modFilter) {
137+
std::lock_guard<std::mutex> guard(inst->syncWorld);
138+
inst->worldCache.reset();
130139
std::vector<Archive> archives;
131140
inst->detectVdf(archives,Gothic::nestedPath({u"Data"},Dir::FT_Dir));
132141

@@ -310,6 +319,69 @@ const zenkit::Vfs& Resources::vdfsIndex() {
310319
return inst->gothicAssets;
311320
}
312321

322+
std::shared_ptr<const WorldData> Resources::loadWorld(std::string_view name, zenkit::GameVersion version) {
323+
std::lock_guard<std::mutex> guard(inst->syncWorld);
324+
const auto* source = vdfsIndex().find(name);
325+
if(source==nullptr)
326+
throw std::runtime_error("unable to open Zen-file: " + std::string(name));
327+
const auto budget = size_t(std::clamp(Gothic::settingsGetI("ENGINE", "worldCacheMiB"),0,1024))*1024*1024;
328+
const bool softwareRayTracing = Shaders::options().doSoftwareRT;
329+
auto& cache = inst->worldCache;
330+
if(cache && cache->source==source && cache->version==version &&
331+
cache->softwareRayTracing==softwareRayTracing && cache->cacheBytes<=budget) {
332+
return cache;
333+
}
334+
// Only one level is retained. Release it before preparing a different level.
335+
cache.reset();
336+
auto data = std::make_shared<WorldData>();
337+
data->source = source;
338+
data->version = version;
339+
data->softwareRayTracing = softwareRayTracing;
340+
auto input = source->open_read();
341+
data->world.load(input.get(),version);
342+
343+
auto physics = std::async(std::launch::async, [&] {
344+
Workers::setThreadName("Loading: BVH thread");
345+
return DynamicWorld::buildLandscape(data->world.world_mesh);
346+
});
347+
data->visual = std::make_unique<PackedMesh>(data->world.world_mesh,PackedMesh::PK_VisualLnd);
348+
data->landscape = physics.get();
349+
// Discard the raw mesh after both prepared representations are ready.
350+
data->world.world_mesh = zenkit::Mesh();
351+
auto& mesh = *data->visual;
352+
auto bytes = [](const auto& values) { return values.capacity()*sizeof(values[0]); };
353+
auto& bsp = data->world.world_bsp_tree;
354+
decltype(bsp.polygon_indices)().swap(bsp.polygon_indices);
355+
decltype(bsp.leaf_polygons)().swap(bsp.leaf_polygons);
356+
decltype(bsp.light_points)().swap(bsp.light_points);
357+
decltype(bsp.portal_polygon_indices)().swap(bsp.portal_polygon_indices);
358+
std::vector<const zenkit::VirtualObject*> pending;
359+
for(const auto& vob:data->world.world_vobs)
360+
pending.push_back(vob.get());
361+
size_t vobCount = 0;
362+
while(!pending.empty()) {
363+
const auto* vob = pending.back();
364+
pending.pop_back();
365+
++vobCount;
366+
for(const auto& child:vob->children)
367+
pending.push_back(child.get());
368+
}
369+
// Allow an additional KiB per vob for derived descriptors, strings and child lists.
370+
// This is a cache estimate, not an allocator-level RAM limit.
371+
size_t metadataBytes = vobCount*(sizeof(zenkit::VirtualObject)+1024) + bytes(bsp.nodes) + bytes(bsp.leaf_node_indices) + bytes(bsp.sectors);
372+
for(const auto& sector:bsp.sectors)
373+
metadataBytes += sector.name.capacity() + bytes(sector.node_indices) + bytes(sector.portal_polygon_indices);
374+
if(data->world.way_net)
375+
metadataBytes += data->world.way_net->points.size()*512 + bytes(data->world.way_net->edges);
376+
const auto visualBytes = bytes(mesh.vertices) + bytes(mesh.verticesA) + bytes(mesh.indices) + bytes(mesh.indices8) +
377+
bytes(mesh.subMeshes) + bytes(mesh.meshletBounds) + bytes(mesh.verticesId) +
378+
bytes(mesh.bvhNodes) + bytes(mesh.bvh8Nodes);
379+
data->cacheBytes = metadataBytes + visualBytes + DynamicWorld::landscapeBytes(*data->landscape);
380+
if(data->cacheBytes<=budget)
381+
cache = data;
382+
return data;
383+
}
384+
313385
const Tempest::IndexBuffer<uint16_t>& Resources::cubeIbo() {
314386
return inst->cube;
315387
}

‎common/resources.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class Animation;
2525
class AttachBinder;
2626
class PfxEmitterMesh;
2727
class GthFont;
28+
struct WorldData;
2829

2930
namespace Dx8 {
3031
class DirectMusic;
@@ -84,6 +85,7 @@ class Resources final {
8485
static const char* renderer();
8586
static void mountWork(const std::filesystem::path& path);
8687
static void loadVdfs(const std::vector<std::u16string> &modvdfs, bool modFilter);
88+
static std::shared_ptr<const WorldData> loadWorld(std::string_view name, zenkit::GameVersion version);
8789

8890
static const Tempest::Sampler& shadowSampler();
8991

@@ -231,6 +233,8 @@ class Resources final {
231233
std::unique_ptr<Dx8::DirectMusic> dxMusic;
232234
DmLoader* dmLoader = nullptr;
233235
zenkit::Vfs gothicAssets;
236+
std::mutex syncWorld;
237+
std::shared_ptr<const WorldData> worldCache;
234238

235239
std::vector<uint8_t> fBuff, ddsBuf;
236240
Tempest::IndexBuffer<uint16_t> cube;

‎common/world/world.cpp‎

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "world.h"
2+
#include "worlddata.h"
23

34
#include <functional>
45
#include <future>
@@ -73,40 +74,34 @@ World::World(GameSession& game, std::string_view file, bool startup, std::functi
7374
}
7475

7576
try {
76-
auto buf = entry->open_read();
77-
zenkit::World world;
78-
world.load(buf.get(), version().game == 1 ? zenkit::GameVersion::GOTHIC_1
79-
: zenkit::GameVersion::GOTHIC_2);
77+
const auto data = Resources::loadWorld(wname, version().game==1 ? zenkit::GameVersion::GOTHIC_1
78+
: zenkit::GameVersion::GOTHIC_2);
79+
const auto& world = data->world;
8080

8181
loadProgress(20);
82-
auto& worldMesh = world.world_mesh;
83-
8482
auto wdynamicFut = std::async(std::launch::async, [&]() {
8583
Workers::setThreadName("Loading: BVH thread");
86-
return std::unique_ptr<DynamicWorld>(new DynamicWorld(this,worldMesh));
84+
return std::unique_ptr<DynamicWorld>(new DynamicWorld(this,data->landscape));
8785
});
8886
auto wviewFut = std::async(std::launch::async, [&]() {
8987
Workers::setThreadName("Loading: PackedMesh thread");
90-
PackedMesh vmesh(worldMesh,PackedMesh::PK_VisualLnd);
91-
return std::unique_ptr<WorldView>(new WorldView(vmesh, wname));
88+
return std::unique_ptr<WorldView>(new WorldView(*data->visual, wname));
9289
});
9390

9491
loadProgress(30);
9592

9693
{
97-
bsp.nodes = std::move(world.world_bsp_tree.nodes);
98-
bsp.sectors = std::move(world.world_bsp_tree.sectors);
99-
bsp.leaf_node_indices = std::move(world.world_bsp_tree.leaf_node_indices);
94+
bsp.nodes = world.world_bsp_tree.nodes;
95+
bsp.sectors = world.world_bsp_tree.sectors;
96+
bsp.leaf_node_indices = world.world_bsp_tree.leaf_node_indices;
10097
bsp.sectorsData.resize(bsp.sectors.size());
101-
world.world_bsp_tree = zenkit::BspTree();
10298
}
10399
loadProgress(50);
104100

105101
wview = wviewFut.get();
106102
loadProgress(60);
107103

108104
wdynamic = wdynamicFut.get();
109-
world.world_mesh = zenkit::Mesh();
110105
loadProgress(70);
111106

112107
globFx.reset(new GlobalEffects(*this));

‎common/world/worlddata.h‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#pragma once
2+
3+
#include <zenkit/World.hh>
4+
#include "graphics/mesh/submesh/packedmesh.h"
5+
#include "physics/dynamicworld.h"
6+
7+
// Original level data only. Never retain a GameSession, renderer, NPC or script VM here.
8+
struct WorldData {
9+
const zenkit::VfsNode* source = nullptr;
10+
zenkit::GameVersion version = zenkit::GameVersion::GOTHIC_2;
11+
bool softwareRayTracing = false;
12+
zenkit::World world;
13+
std::unique_ptr<const PackedMesh> visual;
14+
std::shared_ptr<const DynamicWorld::Landscape> landscape;
15+
size_t cacheBytes = 0;
16+
};

0 commit comments

Comments
 (0)