terrain optimizations
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit

This commit is contained in:
austin 2024-11-11 12:22:46 -05:00
parent 183c98367c
commit 8ddbe36b3d
18 changed files with 161 additions and 41 deletions

View File

@ -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

View File

@ -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<DrawCell>(new Vector3i(0,0,0), new Vector3i(worldDim, worldDim, worldDim));
this.chunkTree = new WorldOctTree<DrawCell>(
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<DrawCell> 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<DrawCell> 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<WorldOctTreeNode<DrawCell>> children = node.getChildren();
boolean isHomogenous = true;
for(int i = 0; i < 8; i++){
WorldOctTreeNode<DrawCell> 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<DrawCell> node){
Globals.profiler.beginCpuSample("ClientDrawCellManager.join");
//perform op
WorldOctTreeNode<DrawCell> 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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -704,12 +704,21 @@ public class CollisionEngine {
if(bodies.contains(body)){
bodies.remove(body);
}
//destroy all geometries
Iterator<DGeom> 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();
}

View File

@ -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();
}

View File

@ -42,6 +42,7 @@ public class AssetManager {
Map<String,Model> modelsLoadedIntoMemory = new ConcurrentHashMap<String,Model>();
List<String> modelsInQueue = new CopyOnWriteArrayList<String>();
List<String> modelsInDeleteQueue = new CopyOnWriteArrayList<String>();
List<MeshShaderOverride> shaderOverrides = new CopyOnWriteArrayList<MeshShaderOverride>();
Map<String,Texture> texturesLoadedIntoMemory = new ConcurrentHashMap<String,Texture>();
@ -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

View File

@ -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);

View File

@ -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";
/*

View File

@ -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;

View File

@ -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.");
}

View File

@ -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());
}
}

View File

@ -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,
});
}
}

View File

@ -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

View File

@ -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

View File

@ -62,7 +62,7 @@ public class WorldOctTree <T> {
this.maxLevel = (int)MathUtils.log2(dimRaw);
this.nodes = new ArrayList<WorldOctTreeNode<T>>();
this.root = new WorldOctTreeNode<T>(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 <T> {
/**
* 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<T> join(WorldOctTreeNode<T> existing){
public WorldOctTreeNode<T> join(WorldOctTreeNode<T> existing, T data){
if(existing.isLeaf()){
throw new IllegalArgumentException("Tried to split non-leaf!");
}
@ -134,6 +135,8 @@ public class WorldOctTree <T> {
Vector3i max = existing.getMaxBound();
int currentLevel = existing.getLevel();
WorldOctTreeNode<T> 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 <T> {
return new WorldOctTreeNode<T>(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

View File

@ -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<DrawCell> 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));