async load texture atlas
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2024-05-07 21:39:20 -04:00
parent cfbd4c05ce
commit ac12b3f5a8
14 changed files with 293 additions and 56 deletions

View File

@ -259,6 +259,12 @@ Ground Texture Atlas system
- Basic atlas working with marching cubes - Basic atlas working with marching cubes
- Make it work with triplanar mapping - Make it work with triplanar mapping
(05/05/2024)
Synchronize attack tree over network
Clean up data
- Tree Model Paths
# TODO # TODO

View File

@ -3,20 +3,7 @@ package electrosphere.client.terrain.cells;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.renderer.texture.Texture;
import electrosphere.util.FileUtils;
/** /**
* An atlas texture and accompanying map of all voxel textures * An atlas texture and accompanying map of all voxel textures
@ -39,39 +26,30 @@ public class VoxelTextureAtlas {
public static final int ATLAS_DIM = 8192; public static final int ATLAS_DIM = 8192;
//number of textures per row in the atlas //number of textures per row in the atlas
public static final int ELEMENTS_PER_ROW = ATLAS_DIM / ATLAS_ELEMENT_DIM; 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++; * 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
Globals.profiler.endCpuSample(); */
//construct texture atlas from buffered image public void putTypeCoord(int type, int coord){
rVal.specular = new Texture(image); typeCoordMap.put(type,coord);
rVal.normal = new Texture(image); }
return rVal;
/**
* 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;
} }
/** /**

View File

@ -31,6 +31,7 @@ import electrosphere.controls.MouseCallback;
import electrosphere.controls.ScrollCallback; import electrosphere.controls.ScrollCallback;
import electrosphere.engine.assetmanager.AssetDataStrings; import electrosphere.engine.assetmanager.AssetDataStrings;
import electrosphere.engine.assetmanager.AssetManager; import electrosphere.engine.assetmanager.AssetManager;
import electrosphere.engine.loadingthreads.InitialAssetLoading;
import electrosphere.engine.loadingthreads.LoadingThread; import electrosphere.engine.loadingthreads.LoadingThread;
import electrosphere.engine.profiler.Profiler; import electrosphere.engine.profiler.Profiler;
import electrosphere.engine.time.Timekeeper; import electrosphere.engine.time.Timekeeper;
@ -58,7 +59,6 @@ import electrosphere.renderer.light.PointLight;
import electrosphere.renderer.light.SpotLight; import electrosphere.renderer.light.SpotLight;
import electrosphere.renderer.loading.ModelPretransforms; import electrosphere.renderer.loading.ModelPretransforms;
import electrosphere.renderer.meshgen.FluidChunkModelGeneration; import electrosphere.renderer.meshgen.FluidChunkModelGeneration;
import electrosphere.renderer.meshgen.TerrainChunkModelGeneration;
import electrosphere.renderer.model.Material; import electrosphere.renderer.model.Material;
import electrosphere.renderer.shader.ShaderOptionMap; import electrosphere.renderer.shader.ShaderOptionMap;
import electrosphere.renderer.shader.ShaderProgram; import electrosphere.renderer.shader.ShaderProgram;
@ -333,7 +333,7 @@ public class Globals {
//chunk stuff //chunk stuff
//draw cell manager //draw cell manager
public static DrawCellManager drawCellManager; public static DrawCellManager drawCellManager;
public static VoxelTextureAtlas voxelTextureAtlas; public static VoxelTextureAtlas voxelTextureAtlas = new VoxelTextureAtlas();
//fluid cell manager //fluid cell manager
public static FluidCellManager fluidCellManager; public static FluidCellManager fluidCellManager;
@ -348,6 +348,7 @@ public class Globals {
//thread for loading different game states //thread for loading different game states
public static List<LoadingThread> loadingThreadsList = new LinkedList<LoadingThread>(); public static List<LoadingThread> loadingThreadsList = new LinkedList<LoadingThread>();
public static InitialAssetLoading initialAssetLoadingThread = new InitialAssetLoading();
//manager for all widgets currently being drawn to screen //manager for all widgets currently being drawn to screen
public static ElementManager elementManager; 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/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"); 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 //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. //load them into memory now. The loading time penalty is worth it I think.
Globals.assetManager.loadAssetsInQueue(); Globals.assetManager.loadAssetsInQueue();

View File

@ -134,10 +134,14 @@ public class Main {
//create the drawing context //create the drawing context
if(Globals.RUN_CLIENT && !Globals.HEADLESS){ if(Globals.RUN_CLIENT && !Globals.HEADLESS){
//create opengl context
Globals.renderingEngine = new RenderingEngine(); Globals.renderingEngine = new RenderingEngine();
Globals.renderingEngine.createOpenglContext(); Globals.renderingEngine.createOpenglContext();
Globals.initDefaultGraphicalResources(); Globals.initDefaultGraphicalResources();
ImGuiWindowMacros.initImGuiWindows(); ImGuiWindowMacros.initImGuiWindows();
//start initial asset loading
new Thread(Globals.initialAssetLoadingThread).start();
} }
Runtime.getRuntime().addShutdownHook(new Thread(() -> { Runtime.getRuntime().addShutdownHook(new Thread(() -> {

View File

@ -3,8 +3,8 @@ package electrosphere.engine.assetmanager;
import electrosphere.audio.AudioBuffer; import electrosphere.audio.AudioBuffer;
import electrosphere.collision.CollisionBodyCreation; import electrosphere.collision.CollisionBodyCreation;
import electrosphere.collision.CollisionEngine; import electrosphere.collision.CollisionEngine;
import electrosphere.collision.PhysicsUtils;
import electrosphere.collision.collidable.Collidable; import electrosphere.collision.collidable.Collidable;
import electrosphere.engine.assetmanager.queue.QueuedAsset;
import electrosphere.logger.LoggerInterface; import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.actor.ActorShaderMask; import electrosphere.renderer.actor.ActorShaderMask;
import electrosphere.renderer.buffer.HomogenousInstancedArray; import electrosphere.renderer.buffer.HomogenousInstancedArray;
@ -22,10 +22,10 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Semaphore;
import org.lwjgl.assimp.AIScene; import org.lwjgl.assimp.AIScene;
import org.ode4j.ode.DBody; import org.ode4j.ode.DBody;
import org.ode4j.ode.DWorld;
/** /**
* Manages all assets loaded into the engine including initially loading and destructing * 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 //A queue of homogenous buffers to allocate this render frame
List<HomogenousInstancedArray> instanceArrayBufferAllocationQueue = new CopyOnWriteArrayList<HomogenousInstancedArray>(); List<HomogenousInstancedArray> instanceArrayBufferAllocationQueue = new CopyOnWriteArrayList<HomogenousInstancedArray>();
//assets queued to be loaded
Semaphore queuedAssetLock = new Semaphore(1);
List<QueuedAsset> queuedAssets = new CopyOnWriteArrayList<QueuedAsset>();
@ -116,6 +121,13 @@ public class AssetManager {
AIScene scene = ModelLoader.loadAIScene(currentPath); AIScene scene = ModelLoader.loadAIScene(currentPath);
poseModelsLoadedIntoMemory.put(currentPath, new PoseModel(currentPath, scene)); poseModelsLoadedIntoMemory.put(currentPath, new PoseModel(currentPath, scene));
} }
//queued assets
queuedAssetLock.acquireUninterruptibly();
for(QueuedAsset queuedAsset : queuedAssets){
queuedAsset.load();
}
queuedAssets.clear();
queuedAssetLock.release();
//allocate homogenous buffers //allocate homogenous buffers
allocateHomogenousBuffers(); allocateHomogenousBuffers();
//allocate instance array buffers //allocate instance array buffers
@ -428,6 +440,20 @@ public class AssetManager {
public void addInstanceArrayBufferToQueue(HomogenousInstancedArray buffer){ public void addInstanceArrayBufferToQueue(HomogenousInstancedArray buffer){
instanceArrayBufferAllocationQueue.add(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();
}

View File

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

View File

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

View File

@ -232,7 +232,7 @@ public class ClientLoading {
} }
static void initDrawCellManager(){ static void initDrawCellManager(){
while(Globals.clientWorldData == null){ while(Globals.clientWorldData == null || Globals.initialAssetLoadingThread.isLoading()){
try { try {
TimeUnit.MILLISECONDS.sleep(10); TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException ex) { } catch (InterruptedException ex) {

View File

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

View File

@ -91,4 +91,14 @@ public class ServerEntityUtils {
EntityUtils.cleanUpEntity(entity); 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);
}
} }

View File

@ -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; state = AttackTreeState.IDLE;
frameCurrent = 0; frameCurrent = 0;
if(parent.containsKey(EntityDataStrings.CLIENT_ROTATOR_TREE)){ if(parent.containsKey(EntityDataStrings.CLIENT_ROTATOR_TREE)){
@ -374,6 +374,7 @@ public class ClientAttackTree implements BehaviorTree {
} else if(state != AttackTreeState.IDLE){ } 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 //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( if(
currentMove != null &&
currentMove.getNextMoveId() != null && currentMove.getNextMoveId() != null &&
!currentMove.getNextMoveId().equals("") && !currentMove.getNextMoveId().equals("") &&
frameCurrent >= currentMove.getMoveChainWindowStart() && frameCurrent <= currentMove.getMoveChainWindowEnd() frameCurrent >= currentMove.getMoveChainWindowStart() && frameCurrent <= currentMove.getMoveChainWindowEnd()

View File

@ -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){ public static Entity serverSpawnBasicItem(Realm realm, Vector3d position, String name){
Item item = Globals.gameConfigCurrent.getItemMap().getItem(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()); EntityCreationUtils.makeEntityPoseable(rVal, item.getModelPath());
if(item.getWeaponData() != null){ if(item.getWeaponData() != null){
rVal.putData(EntityDataStrings.ITEM_IS_WEAPON, true); 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. //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 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. //the server will not be able to synchronize it properly.
ServerEntityUtils.initiallyPositionEntity(realm,rVal,position); ServerEntityUtils.initiallyPositionEntity(realm,rVal,correctedPosition);
return rVal; return rVal;
} }

View File

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

View File

@ -76,5 +76,12 @@ public interface DataCellManager {
* @param saveName The name of the save * @param saveName The name of the save
*/ */
public void save(String saveName); 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);
} }