From ac12b3f5a85862b1edc41d7226a1aba18f8e4eb8 Mon Sep 17 00:00:00 2001 From: austin Date: Tue, 7 May 2024 21:39:20 -0400 Subject: [PATCH] async load texture atlas --- docs/src/progress/renderertodo.md | 6 + .../terrain/cells/VoxelTextureAtlas.java | 68 ++++-------- .../java/electrosphere/engine/Globals.java | 8 +- src/main/java/electrosphere/engine/Main.java | 4 + .../engine/assetmanager/AssetManager.java | 30 ++++- .../assetmanager/queue/QueuedAsset.java | 19 ++++ .../assetmanager/queue/QueuedTexture.java | 51 +++++++++ .../engine/loadingthreads/ClientLoading.java | 2 +- .../loadingthreads/InitialAssetLoading.java | 104 ++++++++++++++++++ .../entity/ServerEntityUtils.java | 10 ++ .../entity/state/attack/ClientAttackTree.java | 3 +- .../entity/types/item/ItemUtils.java | 13 ++- .../datacell/GriddedDataCellManager.java | 24 ++++ .../datacell/interfaces/DataCellManager.java | 7 ++ 14 files changed, 293 insertions(+), 56 deletions(-) create mode 100644 src/main/java/electrosphere/engine/assetmanager/queue/QueuedAsset.java create mode 100644 src/main/java/electrosphere/engine/assetmanager/queue/QueuedTexture.java create mode 100644 src/main/java/electrosphere/engine/loadingthreads/InitialAssetLoading.java diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 1b6697fe..5504416a 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -259,6 +259,12 @@ Ground Texture Atlas system - Basic atlas working with marching cubes - Make it work with triplanar mapping +(05/05/2024) +Synchronize attack tree over network + +Clean up data + - Tree Model Paths + # TODO diff --git a/src/main/java/electrosphere/client/terrain/cells/VoxelTextureAtlas.java b/src/main/java/electrosphere/client/terrain/cells/VoxelTextureAtlas.java index dcde5a78..490d31ce 100644 --- a/src/main/java/electrosphere/client/terrain/cells/VoxelTextureAtlas.java +++ b/src/main/java/electrosphere/client/terrain/cells/VoxelTextureAtlas.java @@ -3,20 +3,7 @@ package electrosphere.client.terrain.cells; import java.util.HashMap; import java.util.Map; -import javax.imageio.ImageIO; - -import java.awt.Graphics; -import java.awt.image.BufferedImage; -import java.io.IOException; - -import org.joml.Vector2i; - -import electrosphere.engine.Globals; -import electrosphere.game.data.voxel.VoxelData; -import electrosphere.game.data.voxel.VoxelType; -import electrosphere.logger.LoggerInterface; import electrosphere.renderer.texture.Texture; -import electrosphere.util.FileUtils; /** * An atlas texture and accompanying map of all voxel textures @@ -39,39 +26,30 @@ public class VoxelTextureAtlas { public static final int ATLAS_DIM = 8192; //number of textures per row in the atlas public static final int ELEMENTS_PER_ROW = ATLAS_DIM / ATLAS_ELEMENT_DIM; - - /** - * Creates the voxel texture atlas object - * @return the atlas object - */ - public static VoxelTextureAtlas createVoxelTextureAtlas(VoxelData data){ - Globals.profiler.beginCpuSample("createVoxelTextureAtlas"); - VoxelTextureAtlas rVal = new VoxelTextureAtlas(); - int iterator = 0; - BufferedImage image = new BufferedImage(ATLAS_DIM, ATLAS_DIM, BufferedImage.TYPE_4BYTE_ABGR); - Graphics graphics = image.getGraphics(); - for(VoxelType type : data.getTypes()){ - if(type.getTexture() != null){ - int offX = iterator % ELEMENTS_PER_ROW; - int offY = iterator / ELEMENTS_PER_ROW; - try { - BufferedImage newType = ImageIO.read(FileUtils.getAssetFile(type.getTexture())); - graphics.drawImage(newType, iterator * ATLAS_ELEMENT_DIM * offX, ATLAS_DIM - ATLAS_ELEMENT_DIM - iterator * ATLAS_ELEMENT_DIM * offY, null); - } catch (IOException e) { - LoggerInterface.loggerRenderer.ERROR("Texture atlas failed to find texture " + type.getTexture(), e); - } - //put coords in map - rVal.typeCoordMap.put(type.getId(),iterator); - //iterate - iterator++; - } - } - Globals.profiler.endCpuSample(); - //construct texture atlas from buffered image - rVal.specular = new Texture(image); - rVal.normal = new Texture(image); - return rVal; + /** + * Puts an entry in the type-coord map to map a voxel type to a position + * @param type the voxel type + * @param coord the coordinate in the map + */ + public void putTypeCoord(int type, int coord){ + typeCoordMap.put(type,coord); + } + + /** + * Sets the specular + * @param specular the specular + */ + public void setSpecular(Texture specular){ + this.specular = specular; + } + + /** + * Sets the normal + * @param normal the normal + */ + public void setNormal(Texture normal){ + this.normal = normal; } /** diff --git a/src/main/java/electrosphere/engine/Globals.java b/src/main/java/electrosphere/engine/Globals.java index 4dc2a618..e8d43980 100644 --- a/src/main/java/electrosphere/engine/Globals.java +++ b/src/main/java/electrosphere/engine/Globals.java @@ -31,6 +31,7 @@ import electrosphere.controls.MouseCallback; import electrosphere.controls.ScrollCallback; import electrosphere.engine.assetmanager.AssetDataStrings; import electrosphere.engine.assetmanager.AssetManager; +import electrosphere.engine.loadingthreads.InitialAssetLoading; import electrosphere.engine.loadingthreads.LoadingThread; import electrosphere.engine.profiler.Profiler; import electrosphere.engine.time.Timekeeper; @@ -58,7 +59,6 @@ import electrosphere.renderer.light.PointLight; import electrosphere.renderer.light.SpotLight; import electrosphere.renderer.loading.ModelPretransforms; import electrosphere.renderer.meshgen.FluidChunkModelGeneration; -import electrosphere.renderer.meshgen.TerrainChunkModelGeneration; import electrosphere.renderer.model.Material; import electrosphere.renderer.shader.ShaderOptionMap; import electrosphere.renderer.shader.ShaderProgram; @@ -333,7 +333,7 @@ public class Globals { //chunk stuff //draw cell manager public static DrawCellManager drawCellManager; - public static VoxelTextureAtlas voxelTextureAtlas; + public static VoxelTextureAtlas voxelTextureAtlas = new VoxelTextureAtlas(); //fluid cell manager public static FluidCellManager fluidCellManager; @@ -348,6 +348,7 @@ public class Globals { //thread for loading different game states public static List loadingThreadsList = new LinkedList(); + public static InitialAssetLoading initialAssetLoadingThread = new InitialAssetLoading(); //manager for all widgets currently being drawn to screen public static ElementManager elementManager; @@ -551,9 +552,6 @@ public class Globals { assetManager.addShaderToQueue("Shaders/ui/debug/windowBorder/windowBound.vs", null, "Shaders/ui/debug/windowBorder/windowBound.fs"); assetManager.addShaderToQueue("Shaders/ui/debug/windowContentBorder/windowContentBound.vs", null, "Shaders/ui/debug/windowContentBorder/windowContentBound.fs"); - //voxel texture atlas - voxelTextureAtlas = VoxelTextureAtlas.createVoxelTextureAtlas(Globals.gameConfigCurrent.getVoxelData()); - //as these assets are required for the renderer to work, we go ahead and //load them into memory now. The loading time penalty is worth it I think. Globals.assetManager.loadAssetsInQueue(); diff --git a/src/main/java/electrosphere/engine/Main.java b/src/main/java/electrosphere/engine/Main.java index bfde3a89..a7238350 100644 --- a/src/main/java/electrosphere/engine/Main.java +++ b/src/main/java/electrosphere/engine/Main.java @@ -134,10 +134,14 @@ public class Main { //create the drawing context if(Globals.RUN_CLIENT && !Globals.HEADLESS){ + //create opengl context Globals.renderingEngine = new RenderingEngine(); Globals.renderingEngine.createOpenglContext(); Globals.initDefaultGraphicalResources(); ImGuiWindowMacros.initImGuiWindows(); + + //start initial asset loading + new Thread(Globals.initialAssetLoadingThread).start(); } Runtime.getRuntime().addShutdownHook(new Thread(() -> { diff --git a/src/main/java/electrosphere/engine/assetmanager/AssetManager.java b/src/main/java/electrosphere/engine/assetmanager/AssetManager.java index 3174ca54..7bc08807 100644 --- a/src/main/java/electrosphere/engine/assetmanager/AssetManager.java +++ b/src/main/java/electrosphere/engine/assetmanager/AssetManager.java @@ -3,8 +3,8 @@ package electrosphere.engine.assetmanager; import electrosphere.audio.AudioBuffer; import electrosphere.collision.CollisionBodyCreation; import electrosphere.collision.CollisionEngine; -import electrosphere.collision.PhysicsUtils; import electrosphere.collision.collidable.Collidable; +import electrosphere.engine.assetmanager.queue.QueuedAsset; import electrosphere.logger.LoggerInterface; import electrosphere.renderer.actor.ActorShaderMask; import electrosphere.renderer.buffer.HomogenousInstancedArray; @@ -22,10 +22,10 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Semaphore; import org.lwjgl.assimp.AIScene; import org.ode4j.ode.DBody; -import org.ode4j.ode.DWorld; /** * Manages all assets loaded into the engine including initially loading and destructing @@ -56,6 +56,11 @@ public class AssetManager { //A queue of homogenous buffers to allocate this render frame List instanceArrayBufferAllocationQueue = new CopyOnWriteArrayList(); + + + //assets queued to be loaded + Semaphore queuedAssetLock = new Semaphore(1); + List queuedAssets = new CopyOnWriteArrayList(); @@ -116,6 +121,13 @@ public class AssetManager { AIScene scene = ModelLoader.loadAIScene(currentPath); poseModelsLoadedIntoMemory.put(currentPath, new PoseModel(currentPath, scene)); } + //queued assets + queuedAssetLock.acquireUninterruptibly(); + for(QueuedAsset queuedAsset : queuedAssets){ + queuedAsset.load(); + } + queuedAssets.clear(); + queuedAssetLock.release(); //allocate homogenous buffers allocateHomogenousBuffers(); //allocate instance array buffers @@ -428,6 +440,20 @@ public class AssetManager { public void addInstanceArrayBufferToQueue(HomogenousInstancedArray buffer){ instanceArrayBufferAllocationQueue.add(buffer); } + + + // + //Async generic queued assets + // + /** + * Queues an asset to be loaded on the main thread + * @param asset the asset + */ + public void queuedAsset(QueuedAsset asset){ + queuedAssetLock.acquireUninterruptibly(); + this.queuedAssets.add(asset); + queuedAssetLock.release(); + } diff --git a/src/main/java/electrosphere/engine/assetmanager/queue/QueuedAsset.java b/src/main/java/electrosphere/engine/assetmanager/queue/QueuedAsset.java new file mode 100644 index 00000000..9ea8a06e --- /dev/null +++ b/src/main/java/electrosphere/engine/assetmanager/queue/QueuedAsset.java @@ -0,0 +1,19 @@ +package electrosphere.engine.assetmanager.queue; + +/** + * An asset that has its data ready to be buffered to gpu + */ +public interface QueuedAsset { + + /** + * Loads the asset + */ + public void load(); + + /** + * Checks whether the queued asset has been loaded or not + * @return True if it has been loaded to gpu/wherever, false otherise + */ + public boolean hasLoaded(); + +} diff --git a/src/main/java/electrosphere/engine/assetmanager/queue/QueuedTexture.java b/src/main/java/electrosphere/engine/assetmanager/queue/QueuedTexture.java new file mode 100644 index 00000000..80f3aeda --- /dev/null +++ b/src/main/java/electrosphere/engine/assetmanager/queue/QueuedTexture.java @@ -0,0 +1,51 @@ +package electrosphere.engine.assetmanager.queue; + +import java.awt.image.BufferedImage; + +import electrosphere.renderer.texture.Texture; + +/** + * A texture queued to be sent to the gpu + */ +public class QueuedTexture implements QueuedAsset { + + //true if loaded + boolean hasLoaded = false; + + //the resulting texture object + Texture texture = null; + + //data to be loaded + BufferedImage data; + + + /** + * Creates the queued texture object + * @param image the image to load to gpu + */ + public QueuedTexture(BufferedImage image){ + this.data = image; + } + + @Override + public void load() { + texture = new Texture(data); + hasLoaded = true; + } + + @Override + public boolean hasLoaded() { + return hasLoaded; + } + + /** + * Gets the texture object + * @return The texture if it has been loaded, otherwise null + */ + public Texture getTexture(){ + return texture; + } + + + +} diff --git a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java index 6acc6663..0cbb40ec 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java +++ b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java @@ -232,7 +232,7 @@ public class ClientLoading { } static void initDrawCellManager(){ - while(Globals.clientWorldData == null){ + while(Globals.clientWorldData == null || Globals.initialAssetLoadingThread.isLoading()){ try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException ex) { diff --git a/src/main/java/electrosphere/engine/loadingthreads/InitialAssetLoading.java b/src/main/java/electrosphere/engine/loadingthreads/InitialAssetLoading.java new file mode 100644 index 00000000..4b6ecdab --- /dev/null +++ b/src/main/java/electrosphere/engine/loadingthreads/InitialAssetLoading.java @@ -0,0 +1,104 @@ +package electrosphere.engine.loadingthreads; + +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import javax.imageio.ImageIO; + +import electrosphere.client.terrain.cells.VoxelTextureAtlas; +import electrosphere.engine.Globals; +import electrosphere.engine.assetmanager.queue.QueuedTexture; +import electrosphere.game.data.voxel.VoxelData; +import electrosphere.game.data.voxel.VoxelType; +import electrosphere.logger.LoggerInterface; +import electrosphere.util.FileUtils; + +/** + * The intention of this thread is to load basic assets that the engine should generally have available from the start. + * Examples: + * - Texture Atlas for terrain + * - Icons for items + */ +public class InitialAssetLoading implements Runnable { + + //tracks whether this thread is still doing work or not + boolean loading = true; + + /** + * Loads basic data + */ + public void LoadData(){ + + + loadTextureAtlas(); + LoggerInterface.loggerEngine.INFO("Finished loading texture atlas"); + + loading = false; + } + + /** + * Gets whether the thread is still loading or not + * @return true if loading, false otherwise + */ + public boolean isLoading(){ + return loading; + } + + /** + * Loads the texture atlas + */ + private void loadTextureAtlas(){ + //terrain texture atlas + Globals.profiler.beginCpuSample("createVoxelTextureAtlas"); + VoxelData data = Globals.gameConfigCurrent.getVoxelData(); + int iterator = 0; + BufferedImage image = new BufferedImage(VoxelTextureAtlas.ATLAS_DIM, VoxelTextureAtlas.ATLAS_DIM, BufferedImage.TYPE_4BYTE_ABGR); + Graphics graphics = image.getGraphics(); + for(VoxelType type : data.getTypes()){ + if(type.getTexture() != null){ + int offX = iterator % VoxelTextureAtlas.ELEMENTS_PER_ROW; + int offY = iterator / VoxelTextureAtlas.ELEMENTS_PER_ROW; + try { + BufferedImage newType = ImageIO.read(FileUtils.getAssetFile(type.getTexture())); + graphics.drawImage(newType, iterator * VoxelTextureAtlas.ATLAS_ELEMENT_DIM * offX, VoxelTextureAtlas.ATLAS_DIM - VoxelTextureAtlas.ATLAS_ELEMENT_DIM - iterator * VoxelTextureAtlas.ATLAS_ELEMENT_DIM * offY, null); + } catch (IOException e) { + LoggerInterface.loggerRenderer.ERROR("Texture atlas failed to find texture " + type.getTexture(), e); + } + //put coords in map + Globals.voxelTextureAtlas.putTypeCoord(type.getId(),iterator); + + //iterate + iterator++; + } + } + Globals.profiler.endCpuSample(); + + //queue to asset manager + QueuedTexture atlasQueuedTexture = new QueuedTexture(image); + Globals.assetManager.queuedAsset(atlasQueuedTexture); + + + //wait the texture to be loaded + while(!atlasQueuedTexture.hasLoaded()){ + try { + TimeUnit.MILLISECONDS.sleep(1); + } catch (InterruptedException e) { + LoggerInterface.loggerEngine.ERROR("failed to sleep", e); + } + } + + + //construct texture atlas from buffered image + Globals.voxelTextureAtlas.setSpecular(atlasQueuedTexture.getTexture()); + Globals.voxelTextureAtlas.setNormal(atlasQueuedTexture.getTexture()); + } + + @Override + public void run(){ + this.LoadData(); + } + + +} diff --git a/src/main/java/electrosphere/entity/ServerEntityUtils.java b/src/main/java/electrosphere/entity/ServerEntityUtils.java index 6c0dd5d3..21f33cef 100644 --- a/src/main/java/electrosphere/entity/ServerEntityUtils.java +++ b/src/main/java/electrosphere/entity/ServerEntityUtils.java @@ -91,4 +91,14 @@ public class ServerEntityUtils { EntityUtils.cleanUpEntity(entity); } + /** + * Guarantees that the returned position is in bounds of the server realm + * @param realm The realm to test + * @param position the position + * @return Either the position if it is in bounds, or the closest position that is in bounds + */ + public static Vector3d guaranteePositionIsInBounds(Realm realm, Vector3d position){ + return realm.getDataCellManager().guaranteePositionIsInBounds(position); + } + } diff --git a/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java b/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java index 9bc8132a..23ab3b2e 100644 --- a/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java +++ b/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java @@ -325,7 +325,7 @@ public class ClientAttackTree implements BehaviorTree { } } } - if(frameCurrent > currentMove.getWindupFrames() + currentMove.getAttackFrames() + currentMove.getCooldownFrames()){ + if(currentMove != null && frameCurrent > currentMove.getWindupFrames() + currentMove.getAttackFrames() + currentMove.getCooldownFrames()){ state = AttackTreeState.IDLE; frameCurrent = 0; if(parent.containsKey(EntityDataStrings.CLIENT_ROTATOR_TREE)){ @@ -374,6 +374,7 @@ public class ClientAttackTree implements BehaviorTree { } else if(state != AttackTreeState.IDLE){ //checks if we have a next move and if we're in the specified range of frames when we're allowed to chain into it if( + currentMove != null && currentMove.getNextMoveId() != null && !currentMove.getNextMoveId().equals("") && frameCurrent >= currentMove.getMoveChainWindowStart() && frameCurrent <= currentMove.getMoveChainWindowEnd() diff --git a/src/main/java/electrosphere/entity/types/item/ItemUtils.java b/src/main/java/electrosphere/entity/types/item/ItemUtils.java index 019aed0a..4b4ed975 100644 --- a/src/main/java/electrosphere/entity/types/item/ItemUtils.java +++ b/src/main/java/electrosphere/entity/types/item/ItemUtils.java @@ -121,9 +121,18 @@ public class ItemUtils { + /** + * Spawns an item on the server + * @param realm the realm to spawn in + * @param position the position to spawn at + * @param name the name of the item to spawn + * @return The item entity + */ public static Entity serverSpawnBasicItem(Realm realm, Vector3d position, String name){ Item item = Globals.gameConfigCurrent.getItemMap().getItem(name); - Entity rVal = EntityCreationUtils.createServerEntity(realm, position);// EntityUtils.spawnDrawableEntity(item.getModelPath()); + //must correct the position such that it spawns inside the realm + Vector3d correctedPosition = ServerEntityUtils.guaranteePositionIsInBounds(realm, position); + Entity rVal = EntityCreationUtils.createServerEntity(realm, correctedPosition);// EntityUtils.spawnDrawableEntity(item.getModelPath()); EntityCreationUtils.makeEntityPoseable(rVal, item.getModelPath()); if(item.getWeaponData() != null){ rVal.putData(EntityDataStrings.ITEM_IS_WEAPON, true); @@ -194,7 +203,7 @@ public class ItemUtils { //Burried underneath this is function call to initialize a server side entity. //The server initialization logic checks what type of entity this is, if this function is called prior to its type being stored //the server will not be able to synchronize it properly. - ServerEntityUtils.initiallyPositionEntity(realm,rVal,position); + ServerEntityUtils.initiallyPositionEntity(realm,rVal,correctedPosition); return rVal; } diff --git a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java index b12a7172..2812dae9 100644 --- a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java +++ b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java @@ -524,4 +524,28 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager } } + @Override + public Vector3d guaranteePositionIsInBounds(Vector3d positionToTest) { + Vector3d returnPos = new Vector3d(positionToTest); + if(positionToTest.x < 0){ + returnPos.x = 0; + } + if(positionToTest.x >= Globals.serverWorldData.convertChunkToRealSpace(Globals.serverWorldData.getWorldSizeDiscrete())){ + returnPos.x = Globals.serverWorldData.convertChunkToRealSpace(Globals.serverWorldData.getWorldSizeDiscrete()) - 1; + } + if(positionToTest.y < 0){ + returnPos.y = 0; + } + if(positionToTest.y >= Globals.serverWorldData.convertChunkToRealSpace(Globals.serverWorldData.getWorldSizeDiscrete())){ + returnPos.y = Globals.serverWorldData.convertChunkToRealSpace(Globals.serverWorldData.getWorldSizeDiscrete()) - 1; + } + if(positionToTest.z < 0){ + returnPos.z = 0; + } + if(positionToTest.z >= Globals.serverWorldData.convertChunkToRealSpace(Globals.serverWorldData.getWorldSizeDiscrete())){ + returnPos.z = Globals.serverWorldData.convertChunkToRealSpace(Globals.serverWorldData.getWorldSizeDiscrete()) - 1; + } + return returnPos; + } + } diff --git a/src/main/java/electrosphere/server/datacell/interfaces/DataCellManager.java b/src/main/java/electrosphere/server/datacell/interfaces/DataCellManager.java index 2253cbd6..0cd50802 100644 --- a/src/main/java/electrosphere/server/datacell/interfaces/DataCellManager.java +++ b/src/main/java/electrosphere/server/datacell/interfaces/DataCellManager.java @@ -76,5 +76,12 @@ public interface DataCellManager { * @param saveName The name of the save */ public void save(String saveName); + + /** + * Guarantees that the returned position is in bounds + * @param positionToTest the position + * @return Either the position if it is in bounds, or the closest position that is in bounds + */ + public Vector3d guaranteePositionIsInBounds(Vector3d positionToTest); }