diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 83749f48..e9cc4b3b 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -990,6 +990,13 @@ Split leaf/nonleaf tracks for node evaluation optimization (11/11/2024) Chunk data now stored/transmitted in 17 dim instead of 16 dim (Thereby cutting down on network/storage cost) +Unique actor concept +Asset manager pipeline to destroy models/meshes/textures +Terrain model freeing on destruction +Potential physics destruction fix +Join propagation further up tree +Lower client cache size (to fight memory stalling) +Manual free button on memory debug window diff --git a/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java b/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java index f4cc93b1..83e51686 100644 --- a/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java +++ b/src/main/java/electrosphere/client/terrain/cells/ClientDrawCellManager.java @@ -153,7 +153,11 @@ public class ClientDrawCellManager { * @param worldDim The size of the world in chunks */ public ClientDrawCellManager(VoxelTextureAtlas voxelTextureAtlas, int worldDim){ - this.chunkTree = new WorldOctTree(new Vector3i(0,0,0), new Vector3i(worldDim, worldDim, worldDim)); + this.chunkTree = new WorldOctTree( + new Vector3i(0,0,0), + new Vector3i(worldDim, worldDim, worldDim) + ); + this.chunkTree.getRoot().setData(DrawCell.generateTerrainCell(new Vector3i(0,0,0), chunkTree.getMaxLevel())); this.worldDim = worldDim; this.textureAtlas = voxelTextureAtlas; } @@ -222,7 +226,7 @@ public class ClientDrawCellManager { if(evaluationMap.containsKey(node)){ return false; } - if(node.getData() != null && node.getData().hasGenerated() && node.getData().isHomogenous()){ + if(node.getData().hasGenerated() && node.getData().isHomogenous()){ return false; } if(node.isLeaf()){ @@ -230,11 +234,7 @@ public class ClientDrawCellManager { Globals.profiler.beginCpuSample("ClientDrawCellManager.split"); //perform op WorldOctTreeNode container = chunkTree.split(node); - - //do deletions - this.twoLayerDestroy(node); - node.convertToLeaf(null); - + container.setData(DrawCell.generateTerrainCell(container.getMinBound(), this.chunkTree.getMaxLevel() - container.getLevel())); //do creations container.getChildren().forEach(child -> { @@ -244,9 +244,13 @@ public class ClientDrawCellManager { child.getMinBound().z ); DrawCell drawCell = DrawCell.generateTerrainCell(cellWorldPos,this.chunkTree.getMaxLevel() - child.getLevel()); - child.convertToLeaf(drawCell); + child.setLeaf(true); + child.setData(drawCell); evaluationMap.put(child,true); }); + + //do deletions + this.twoLayerDestroy(node); //update neighbors this.conditionalUpdateAdjacentNodes(container, container.getChildren().get(0).getLevel()); @@ -298,32 +302,24 @@ public class ClientDrawCellManager { } } else { if(this.shouldJoin(playerPos, node, distCache)) { - Globals.profiler.beginCpuSample("ClientDrawCellManager.join"); - //perform op - WorldOctTreeNode newLeaf = chunkTree.join(node); - - //do deletions - this.twoLayerDestroy(node); - - //do creations - DrawCell drawCell = DrawCell.generateTerrainCell(node.getMinBound(),this.chunkTree.getMaxLevel() - newLeaf.getLevel()); - newLeaf.convertToLeaf(drawCell); - - //update neighbors - this.conditionalUpdateAdjacentNodes(newLeaf, newLeaf.getLevel()); - evaluationMap.put(newLeaf,true); - - Globals.profiler.endCpuSample(); + this.join(node); updated = true; } else { this.validCellCount++; List> children = node.getChildren(); + boolean isHomogenous = true; for(int i = 0; i < 8; i++){ WorldOctTreeNode child = children.get(i); boolean childUpdate = recursivelyUpdateCells(child, playerPos, evaluationMap, minLeafLod, distCache); if(childUpdate == true){ updated = true; } + if(!child.getData().hasGenerated() || !child.getData().isHomogenous()){ + isHomogenous = false; + } + } + if(isHomogenous){ + this.join(node); } if((this.chunkTree.getMaxLevel() - node.getLevel()) < minLeafLod){ evaluationMap.put(node,true); @@ -394,7 +390,7 @@ public class ClientDrawCellManager { return node.canSplit() && (node.getLevel() != this.chunkTree.getMaxLevel()) && - (node.getData() == null || !node.getData().hasGenerated() || !node.getData().isHomogenous()) && + (!node.getData().hasGenerated() || !node.getData().isHomogenous()) && (node.getParent() != null || node == this.chunkTree.getRoot()) && ( ( @@ -613,6 +609,25 @@ public class ClientDrawCellManager { ; } + /** + * Joins a parent node + * @param node The parent node + */ + private void join(WorldOctTreeNode node){ + Globals.profiler.beginCpuSample("ClientDrawCellManager.join"); + //perform op + WorldOctTreeNode newLeaf = chunkTree.join(node, DrawCell.generateTerrainCell(node.getMinBound(),node.getData().lod)); + + //do deletions + this.twoLayerDestroy(node); + + //update neighbors + this.conditionalUpdateAdjacentNodes(newLeaf, newLeaf.getLevel()); + evaluationMap.put(newLeaf,true); + + Globals.profiler.endCpuSample(); + } + /** * Checks if this cell should request chunk data * @param pos the player's position diff --git a/src/main/java/electrosphere/client/terrain/cells/DrawCell.java b/src/main/java/electrosphere/client/terrain/cells/DrawCell.java index 741151bf..7dd6c4d6 100644 --- a/src/main/java/electrosphere/client/terrain/cells/DrawCell.java +++ b/src/main/java/electrosphere/client/terrain/cells/DrawCell.java @@ -113,6 +113,21 @@ public class DrawCell { rVal.worldPos = worldPos; return rVal; } + + /** + * Constructs a homogenous drawcell object + */ + public static DrawCell generateHomogenousTerrainCell( + Vector3i worldPos, + int lod + ){ + DrawCell rVal = new DrawCell(); + rVal.lod = lod; + rVal.worldPos = worldPos; + rVal.hasGenerated = true; + rVal.homogenous = true; + return rVal; + } /** * Generates a drawable entity based on this chunk diff --git a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java index a20df2ab..7e68ba21 100644 --- a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java +++ b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java @@ -55,7 +55,7 @@ public class ClientTerrainManager { public static final int INTERPOLATION_RATIO = ServerTerrainManager.SERVER_TERRAIN_MANAGER_INTERPOLATION_RATIO; //caches chunks from server - static final int CACHE_SIZE = 5000 + (int)(ClientDrawCellManager.FULL_RES_DIST * 10) + (int)(ClientDrawCellManager.HALF_RES_DIST * 10); + static final int CACHE_SIZE = 1000 + (int)(ClientDrawCellManager.FULL_RES_DIST * 10) + (int)(ClientDrawCellManager.HALF_RES_DIST * 10); /** * Size of the cache in bytes diff --git a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiMemory.java b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiMemory.java index b39c6d41..a1703017 100644 --- a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiMemory.java +++ b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiMemory.java @@ -76,6 +76,9 @@ public class ImGuiMemory { ImGui.text("Total Memory: " + formatMemory(totalMemory)); ImGui.text("Free Memory: " + formatMemory(freeMemory)); ImGui.text("Memory Usage: " + formatMemory(memoryUsage)); + if(ImGui.button("Manual free")){ + System.gc(); + } //memory usage graph memoryGraphDataset.addPoint(memoryUsage); diff --git a/src/main/java/electrosphere/collision/CollisionEngine.java b/src/main/java/electrosphere/collision/CollisionEngine.java index b661179f..50119bc1 100644 --- a/src/main/java/electrosphere/collision/CollisionEngine.java +++ b/src/main/java/electrosphere/collision/CollisionEngine.java @@ -704,12 +704,21 @@ public class CollisionEngine { if(bodies.contains(body)){ bodies.remove(body); } + //destroy all geometries Iterator geomIterator = body.getGeomIterator(); while(geomIterator.hasNext()){ DGeom geom = geomIterator.next(); space.remove(geom); + geom.DESTRUCTOR(); geom.destroy(); } + //destroy all joints + for(int i = 0; i < body.getNumJoints(); i++){ + DJoint joint = body.getJoint(i); + joint.DESTRUCTOR(); + joint.destroy(); + } + //destroy actual body body.destroy(); spaceLock.release(); } diff --git a/src/main/java/electrosphere/engine/Main.java b/src/main/java/electrosphere/engine/Main.java index cc78673a..0c373b55 100644 --- a/src/main/java/electrosphere/engine/Main.java +++ b/src/main/java/electrosphere/engine/Main.java @@ -261,6 +261,8 @@ public class Main { Globals.profiler.beginCpuSample("Load Assets"); LoggerInterface.loggerEngine.DEBUG_LOOP("Begin load assets"); Globals.assetManager.loadAssetsInQueue(); + LoggerInterface.loggerEngine.DEBUG_LOOP("Begin delete assets"); + Globals.assetManager.deleteModelsInDeleteQueue(); Globals.profiler.endCpuSample(); } diff --git a/src/main/java/electrosphere/engine/assetmanager/AssetManager.java b/src/main/java/electrosphere/engine/assetmanager/AssetManager.java index 2b4db1d3..6d3671a8 100644 --- a/src/main/java/electrosphere/engine/assetmanager/AssetManager.java +++ b/src/main/java/electrosphere/engine/assetmanager/AssetManager.java @@ -42,6 +42,7 @@ public class AssetManager { Map modelsLoadedIntoMemory = new ConcurrentHashMap(); List modelsInQueue = new CopyOnWriteArrayList(); + List modelsInDeleteQueue = new CopyOnWriteArrayList(); List shaderOverrides = new CopyOnWriteArrayList(); Map texturesLoadedIntoMemory = new ConcurrentHashMap(); @@ -179,7 +180,18 @@ public class AssetManager { - + /** + * Deletes all models in the delete queue + */ + public void deleteModelsInDeleteQueue(){ + for(String modelPath : modelsInDeleteQueue){ + Model model = this.fetchModel(modelPath); + if(model != null){ + model.delete(); + } + this.modelsLoadedIntoMemory.remove(modelPath); + } + } @@ -203,6 +215,14 @@ public class AssetManager { } return rVal; } + + /** + * Queues a model for deletion + * @param modelPath The path to the model + */ + public void queueModelForDeletion(String modelPath){ + modelsInDeleteQueue.add(modelPath); + } /** Registers a (presumably generated in code) model with the asset manager diff --git a/src/main/java/electrosphere/entity/ClientEntityUtils.java b/src/main/java/electrosphere/entity/ClientEntityUtils.java index f371a12e..cf465f4b 100644 --- a/src/main/java/electrosphere/entity/ClientEntityUtils.java +++ b/src/main/java/electrosphere/entity/ClientEntityUtils.java @@ -8,6 +8,7 @@ import org.joml.Vector3d; import electrosphere.engine.Globals; import electrosphere.entity.state.attach.AttachUtils; import electrosphere.entity.types.collision.CollisionObjUtils; +import electrosphere.renderer.actor.ActorUtils; /** * Client only entity utility functions @@ -42,6 +43,11 @@ public class ClientEntityUtils { } } + //delete unique model if present + if(entity.containsKey(EntityDataStrings.HAS_UNIQUE_MODEL)){ + ActorUtils.queueActorForDeletion(entity); + } + //check for client-specific stuff Globals.renderingEngine.getLightManager().destroyPointLight(entity); diff --git a/src/main/java/electrosphere/entity/EntityDataStrings.java b/src/main/java/electrosphere/entity/EntityDataStrings.java index 9b5bb1f0..2f44873a 100644 --- a/src/main/java/electrosphere/entity/EntityDataStrings.java +++ b/src/main/java/electrosphere/entity/EntityDataStrings.java @@ -27,6 +27,7 @@ public class EntityDataStrings { public static final String INSTANCED_ACTOR = "instancedActor"; public static final String DRAW_INSTANCED = "drawInstanced"; public static final String TEXTURE_INSTANCED_ACTOR = "textureInstancedActor"; + public static final String HAS_UNIQUE_MODEL = "hasUniqueModel"; /* diff --git a/src/main/java/electrosphere/entity/state/movement/editor/ClientEditorMovementTree.java b/src/main/java/electrosphere/entity/state/movement/editor/ClientEditorMovementTree.java index bf6112ee..4c627389 100644 --- a/src/main/java/electrosphere/entity/state/movement/editor/ClientEditorMovementTree.java +++ b/src/main/java/electrosphere/entity/state/movement/editor/ClientEditorMovementTree.java @@ -69,7 +69,7 @@ public class ClientEditorMovementTree implements BehaviorTree { static final double STATE_DIFFERENCE_CREEP_MULTIPLIER = 0.001; //while the movement tree is idle, slowly creep the position of the entity towards the true server position by this amount static final double STATE_DIFFERENCE_CREEP_CUTOFF = 0.01; //the cutoff for creep when we say it's "close enough" - public static final float EDITOR_MAX_VELOCITY = 0.5f; + public static final float EDITOR_MAX_VELOCITY = 1.0f; public static final float EDITOR_ACCEL = 1.0f; String animationStartUp = Animation.ANIMATION_MOVEMENT_STARTUP; diff --git a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java index f73edbd8..ec3c2bda 100644 --- a/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java +++ b/src/main/java/electrosphere/entity/types/terrain/TerrainChunk.java @@ -61,6 +61,7 @@ public class TerrainChunk { EntityCreationUtils.bypassShadowPass(rVal); EntityCreationUtils.bypassVolumetics(rVal); } + rVal.putData(EntityDataStrings.HAS_UNIQUE_MODEL, true); } else { LoggerInterface.loggerEngine.WARNING("Finished generating terrain polygons; however, entity has already been deleted."); } diff --git a/src/main/java/electrosphere/renderer/actor/ActorUtils.java b/src/main/java/electrosphere/renderer/actor/ActorUtils.java index 63cff187..f2de3ae4 100644 --- a/src/main/java/electrosphere/renderer/actor/ActorUtils.java +++ b/src/main/java/electrosphere/renderer/actor/ActorUtils.java @@ -40,5 +40,13 @@ public class ActorUtils { return entityActor.getStaticMorph(); } + /** + * Queues the model underlying an actor for deletion + * @param actorEntity The entity + */ + public static void queueActorForDeletion(Entity actorEntity){ + Actor actor = EntityUtils.getActor(actorEntity); + Globals.assetManager.queueModelForDeletion(actor.getModelPath()); + } } diff --git a/src/main/java/electrosphere/renderer/model/Material.java b/src/main/java/electrosphere/renderer/model/Material.java index 2d86ccd0..881f124e 100644 --- a/src/main/java/electrosphere/renderer/model/Material.java +++ b/src/main/java/electrosphere/renderer/model/Material.java @@ -4,6 +4,8 @@ import electrosphere.engine.Globals; import electrosphere.renderer.OpenGLState; import electrosphere.renderer.texture.Texture; import org.lwjgl.assimp.AIMaterial; +import org.lwjgl.opengl.GL40; + import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; import static org.lwjgl.opengl.GL13.GL_TEXTURE0; @@ -138,4 +140,14 @@ public class Material { } return rVal; } + + /** + * Frees the material + */ + public void free(){ + GL40.glDeleteTextures(new int[]{ + this.texturePointer, + this.normalPointer, + }); + } } diff --git a/src/main/java/electrosphere/renderer/model/Mesh.java b/src/main/java/electrosphere/renderer/model/Mesh.java index a57d46eb..9858ff9e 100644 --- a/src/main/java/electrosphere/renderer/model/Mesh.java +++ b/src/main/java/electrosphere/renderer/model/Mesh.java @@ -112,6 +112,21 @@ public class Mesh { vertexArrayObject = glGenVertexArrays(); glBindVertexArray(vertexArrayObject); } + + /** + * Frees this mesh + */ + public void free(){ + GL40.glDeleteBuffers(new int[]{ + vertexBuffer, + normalBuffer, + elementArrayBuffer, + boneWeightBuffer, + boneIndexBuffer, + textureCoordBuffer, + }); + GL40.glDeleteVertexArrays(vertexArrayObject); + } /** * Buffers vertex data to the gpu under this mesh container diff --git a/src/main/java/electrosphere/renderer/model/Model.java b/src/main/java/electrosphere/renderer/model/Model.java index 87cd3284..892f01ca 100644 --- a/src/main/java/electrosphere/renderer/model/Model.java +++ b/src/main/java/electrosphere/renderer/model/Model.java @@ -293,6 +293,18 @@ public class Model { } } } + + /** + * Deletes this model + */ + public void delete(){ + for(Mesh mesh : this.meshes){ + mesh.free(); + } + for(Material material : this.materials){ + material.free(); + } + } /** * Logs all animations for a given model diff --git a/src/main/java/electrosphere/util/ds/octree/WorldOctTree.java b/src/main/java/electrosphere/util/ds/octree/WorldOctTree.java index 63436884..c755f31d 100644 --- a/src/main/java/electrosphere/util/ds/octree/WorldOctTree.java +++ b/src/main/java/electrosphere/util/ds/octree/WorldOctTree.java @@ -62,7 +62,7 @@ public class WorldOctTree { this.maxLevel = (int)MathUtils.log2(dimRaw); this.nodes = new ArrayList>(); this.root = new WorldOctTreeNode(this, 0, new Vector3i(min), new Vector3i(max)); - this.root.isLeaf = true; + this.root.setLeaf(true); this.nodes.add(this.root); } @@ -124,9 +124,10 @@ public class WorldOctTree { /** * Joins a non-leaf node's children into a single node * @param parent The non-leaf + * @param data The container's data * @return The new leaf node */ - public WorldOctTreeNode join(WorldOctTreeNode existing){ + public WorldOctTreeNode join(WorldOctTreeNode existing, T data){ if(existing.isLeaf()){ throw new IllegalArgumentException("Tried to split non-leaf!"); } @@ -134,6 +135,8 @@ public class WorldOctTree { Vector3i max = existing.getMaxBound(); int currentLevel = existing.getLevel(); WorldOctTreeNode newContainer = new WorldOctTreeNode<>(this, currentLevel, min, max); + newContainer.setData(data); + newContainer.setLeaf(true); //replace existing node this.replaceNode(existing,newContainer); @@ -344,15 +347,6 @@ public class WorldOctTree { return new WorldOctTreeNode(tree, level, min, max); } - /** - * Converts this node to a leaf - * @param data The data to put in the leaf - */ - public void convertToLeaf(T data){ - this.isLeaf = true; - this.data = data; - } - /** * Sets whether this node is a leaf or not * @param isLeaf true if it is a leaf, false otherwise diff --git a/src/test/java/electrosphere/client/terrain/cells/ClientDrawCellManagerTests.java b/src/test/java/electrosphere/client/terrain/cells/ClientDrawCellManagerTests.java index d8243dd3..edc68deb 100644 --- a/src/test/java/electrosphere/client/terrain/cells/ClientDrawCellManagerTests.java +++ b/src/test/java/electrosphere/client/terrain/cells/ClientDrawCellManagerTests.java @@ -37,10 +37,10 @@ public class ClientDrawCellManagerTests { int worldDiscreteSize = 64; Globals.clientWorldData = new ClientWorldData(new Vector3f(0), new Vector3f(worldDiscreteSize * ServerTerrainChunk.CHUNK_DIMENSION), 0, worldDiscreteSize); ClientDrawCellManager manager = new ClientDrawCellManager(null, 64); - double precomputedMidDist = 0; Vector3i playerPos = new Vector3i(0,0,0); WorldOctTreeNode node = WorldOctTreeNode.constructorForTests(manager.chunkTree, 1, new Vector3i(16,0,0), new Vector3i(32,16,16)); - node.convertToLeaf(DrawCell.generateTerrainCell(new Vector3i(0,0,0),3)); + node.setLeaf(true); + node.setData(DrawCell.generateTerrainCell(new Vector3i(0,0,0),3)); assertTrue(manager.shouldSplit(playerPos, node, 0));