terrain optimizations
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit
This commit is contained in:
parent
183c98367c
commit
8ddbe36b3d
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
/*
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.");
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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));
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user