From 5a05b9d65ed62087eb6820e9a8f14bbaf1c210b2 Mon Sep 17 00:00:00 2001 From: Halbear Date: Fri, 14 Aug 2026 22:22:03 +0100 Subject: [PATCH] 2D rendering (big feature) --- .../SpriteShaders/sprite_fragment.glsl | 25 ++ .../sprite_translucent_fragment.glsl | 19 + .../SpriteShaders/sprite_vertex.glsl | 36 ++ .../shaders/lighting_frag.glsl | 2 +- .../Terrain4J/EngineCore/Display/Render.java | 9 + .../EngineCore/Logic/EngineConfig.java | 8 +- .../Logic/Physics/StaticSpatialHashGrid.java | 45 +++ .../Terrain4J/EngineCore/Main/GameCore.java | 8 +- .../EngineCore/Main/Scene/Camera2D.java | 48 +++ .../EngineCore/Main/Scene/IScene.java | 2 + .../EngineCore/Main/Scene/Scene.java | 33 ++ .../EngineCore/Main/Scene/Sprite.java | 118 +++++++ .../Threads/ClientConnectionThread.java | 39 +- .../Threads/ServerConnectionThread.java | 2 + .../Vulkan/Rendering/VkModel/ModelsCache.java | 60 ++++ .../BillboardMeshGenerator.java | 47 +++ .../SpriteRendering/SpriteMeshGenerator.java | 116 ++++++ .../SpriteRendering/SpriteRenderer.java | 334 ++++++++++++++++++ .../SpriteVertexBufferStructure.java | 69 ++++ .../SpriteRendering/TextureAlphaBounds.java | 69 ++++ .../EngineCore/Vulkan/Util/VulkanUtils.java | 8 + 21 files changed, 1075 insertions(+), 22 deletions(-) create mode 100644 resources/EngineResources/SpriteShaders/sprite_fragment.glsl create mode 100644 resources/EngineResources/SpriteShaders/sprite_translucent_fragment.glsl create mode 100644 resources/EngineResources/SpriteShaders/sprite_vertex.glsl create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Logic/Physics/StaticSpatialHashGrid.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/Camera2D.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/Sprite.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/BillboardMeshGenerator.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/SpriteMeshGenerator.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/SpriteRenderer.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/SpriteVertexBufferStructure.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/TextureAlphaBounds.java diff --git a/resources/EngineResources/SpriteShaders/sprite_fragment.glsl b/resources/EngineResources/SpriteShaders/sprite_fragment.glsl new file mode 100644 index 0000000..0b05827 --- /dev/null +++ b/resources/EngineResources/SpriteShaders/sprite_fragment.glsl @@ -0,0 +1,25 @@ +#version 450 + +const int MAX_TEXTURES = 128; +const float GAMMA_CONST = 0.4545; +layout(location = 0) in vec2 inUV; + +layout(location = 0) out vec4 outColor; +layout(push_constant) uniform pc { + layout(offset = 24) uint textureIndex; +} push_constants; + +layout(set = 1, binding = 0) uniform sampler2D spriteTextures[MAX_TEXTURES]; + +vec4 gamma(vec4 color){ + return color = vec4(pow(color.rgb,vec3(GAMMA_CONST)),color.a); +} + +void main() { + vec4 tex = texture(spriteTextures[push_constants.textureIndex], inUV); + outColor = gamma(tex); + + if (outColor.a <= 0.09) { + discard; + } +} \ No newline at end of file diff --git a/resources/EngineResources/SpriteShaders/sprite_translucent_fragment.glsl b/resources/EngineResources/SpriteShaders/sprite_translucent_fragment.glsl new file mode 100644 index 0000000..e6505ca --- /dev/null +++ b/resources/EngineResources/SpriteShaders/sprite_translucent_fragment.glsl @@ -0,0 +1,19 @@ +#version 450 + +const int MAX_TEXTURES = 128; + +layout(location = 0) in vec2 inUV; + +layout(location = 0) out vec4 outColor; +layout(push_constant) uniform pc { + layout(offset = 24) uint textureIndex; +} push_constants; + +layout(set = 1, binding = 0) uniform sampler2D spriteTextures[MAX_TEXTURES]; + +void main() { + vec4 tex = texture(spriteTextures[push_constants.textureIndex], inUV); + outColor = tex; + + if(outColor.a >= 0.9 || outColor.a <= 0.001){discard;} +} \ No newline at end of file diff --git a/resources/EngineResources/SpriteShaders/sprite_vertex.glsl b/resources/EngineResources/SpriteShaders/sprite_vertex.glsl new file mode 100644 index 0000000..28e01c3 --- /dev/null +++ b/resources/EngineResources/SpriteShaders/sprite_vertex.glsl @@ -0,0 +1,36 @@ +#version 450 + +layout(location = 0) in vec2 inPos; +layout(location = 1) in vec2 inUV; + +layout(location = 0) out vec2 outUV; + + +layout(push_constant) uniform pc { + vec2 modelSize; + vec2 position; + vec2 screenSize; + +} push_constants; + +layout(set = 0, binding = 0) uniform ViewUniform{ + vec2 camPos; + vec2 camScale; +} viewUniform; + + +void main() { + vec2 ScaledPos = inPos * push_constants.modelSize; + vec2 worldPos = push_constants.position + ScaledPos; + worldPos += viewUniform.camPos; + vec2 ndc = vec2( + (worldPos.x / push_constants.screenSize.x) * 2.0 - 1.0, + (worldPos.y / push_constants.screenSize.y) * 2.0 - 1.0 + ); + + ndc.y = -ndc.y; + + + gl_Position = vec4(ndc * viewUniform.camScale, 0.0, 1.0); + outUV = vec2(inUV.x, 1 - inUV.y); +} \ No newline at end of file diff --git a/resources/EngineResources/shaders/lighting_frag.glsl b/resources/EngineResources/shaders/lighting_frag.glsl index 158a06b..a7fada2 100644 --- a/resources/EngineResources/shaders/lighting_frag.glsl +++ b/resources/EngineResources/shaders/lighting_frag.glsl @@ -153,5 +153,5 @@ void main() { outFragColor = vec4(albedo,1.0f); return; } - + // outFragColor = vec4(normal, 1.0f); } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Display/Render.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Display/Render.java index 5a54032..c04dcf4 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Display/Render.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Display/Render.java @@ -12,6 +12,7 @@ import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.DeferredRendering.Light import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.TextureCache; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.PostProcessing.PostProcess; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.*; +import net.halbear.Terrain4J.EngineCore.Vulkan.SpriteRendering.SpriteRenderer; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.CommandBuffer; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.CommandPool; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.GPUSynchronisation.Fence; @@ -47,6 +48,7 @@ public class Render { private final Queue.PresentQueue PresentQueue; private final Semaphore[] RenderCompleteSemaphores; private final SceneRenderer sceneRender; + private final SpriteRenderer spriteRenderer; private final LightRenderer lightRenderer; private final PostProcess PostProcessor; private final SwapChainRender swapChainRender; @@ -102,6 +104,7 @@ public class Render { PostProcessor = new PostProcess(RendererContext, sceneRender.GetAttachmentColour()); lightRenderer = null; } + spriteRenderer= new SpriteRenderer(engineInstance, RendererContext, PostProcessor.GetAttachment()); GuiRender = new GuiRenderer(engineInstance, RendererContext, GraphicsQueue, PostProcessor.GetAttachment()); swapChainRender = new SwapChainRender(RendererContext,PostProcessor.GetAttachment()); textureCache = new TextureCache(); @@ -147,6 +150,8 @@ public class Render { Logger.debug("Loaded {} models", Models.size()); sceneRender.LoadMaterials(RendererContext,materialsCache,textureCache); + spriteRenderer.CompileSprites(RendererContext,modelsCache, textureCache); + spriteRenderer.LoadMaterials(RendererContext,materialsCache,textureCache); GuiRender.LoadTextures(RendererContext,initData.GuiTextures(),textureCache); } @@ -169,6 +174,7 @@ public class Render { if(lightRenderer != null)lightRenderer.cleanup(RendererContext); PostProcessor.CleanUp(RendererContext); swapChainRender.CleanUp(RendererContext); + spriteRenderer.cleanup(RendererContext); GuiRender.CleanUp(RendererContext); modelsCache.CleanUp(RendererContext); materialsCache.CleanUp(RendererContext); @@ -200,6 +206,7 @@ public class Render { sceneRender.Render(engineInstance,RendererContext,CommandBuffer, modelsCache,materialsCache,CurrentFrame); lightRenderer.render(engineInstance, RendererContext, CommandBuffer, sceneRender.GetMRTAttachments(), CurrentFrame); PostProcessor.Render(RendererContext,CommandBuffer,lightRenderer.getAttachment()); + spriteRenderer.Render(engineInstance,RendererContext,CommandBuffer,PostProcessor.GetAttachment(),modelsCache,CurrentFrame); GuiRender.Render(RendererContext,CommandBuffer,CurrentFrame,PostProcessor.GetAttachment()); int ImageIndex; @@ -226,6 +233,7 @@ public class Render { sceneRender.Render(engineInstance,RendererContext,CommandBuffer, modelsCache,materialsCache,CurrentFrame); PostProcessor.Render(RendererContext,CommandBuffer,sceneRender.GetAttachmentColour()); + spriteRenderer.Render(engineInstance,RendererContext,CommandBuffer,PostProcessor.GetAttachment(),modelsCache,CurrentFrame); GuiRender.Render(RendererContext,CommandBuffer,CurrentFrame,PostProcessor.GetAttachment()); int ImageIndex; @@ -275,6 +283,7 @@ public class Render { } else{ PostProcessor.Resize(RendererContext, sceneRender.GetAttachmentColour()); } + spriteRenderer.Resize(engineInstance,RendererContext,PostProcessor.GetAttachment()); GuiRender.Resize(RendererContext,PostProcessor.GetAttachment()); swapChainRender.Resize(RendererContext,PostProcessor.GetAttachment()); } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/EngineConfig.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/EngineConfig.java index d5cb95d..3457de6 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/EngineConfig.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/EngineConfig.java @@ -80,10 +80,10 @@ public class EngineConfig { public boolean RenderCollisionMesh = true; public float PhysicsSpeed = 1.0f; - public float MusicVolume = 1.0f; - public float SoundVolume = 1.0f; - public float WeaponVolume = 1.0f; - public float AmbientVolume = 1.0f; + public float MusicVolume = 0.0f; + public float SoundVolume = 0.0f; + public float WeaponVolume = 0.0f; + public float AmbientVolume = 0.0f; public float PlayerOnline = 1.0f; diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/Physics/StaticSpatialHashGrid.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/Physics/StaticSpatialHashGrid.java new file mode 100644 index 0000000..0e7b7f2 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/Physics/StaticSpatialHashGrid.java @@ -0,0 +1,45 @@ +package net.halbear.Terrain4J.EngineCore.Logic.Physics; + +import org.joml.primitives.AABBf; + +import java.util.*; + +public class StaticSpatialHashGrid { + public static float CellSize = 16f; + + private static final Map> Cells = new HashMap<>(); + + public static void Clear() { + Cells.clear(); + } + + public static void Insert(String bodyID, AABBf worldBounds) { + int minX = (int) Math.floor(worldBounds.minX / CellSize); + int minY = (int) Math.floor(worldBounds.minY / CellSize); + int minZ = (int) Math.floor(worldBounds.minZ / CellSize); + int maxX = (int) Math.floor(worldBounds.maxX / CellSize); + int maxY = (int) Math.floor(worldBounds.maxY / CellSize); + int maxZ = (int) Math.floor(worldBounds.maxZ / CellSize); + + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + for (int z = minZ; z <= maxZ; z++) { + long key = HashCell(x, y, z); + Cells.computeIfAbsent(key, k -> new ArrayList<>()).add(bodyID); + } + } + } + } + + private static long HashCell(int x, int y, int z) { + long xi = ((long) x) & 0x1FFFFFL; + long yi = ((long) y) & 0x1FFFFFL; + long zi = ((long) z) & 0x1FFFFFL; + return (xi << 42) | (yi << 21) | zi; + } + + public static Collection> Buckets() { + return Cells.values(); + } + +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/GameCore.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/GameCore.java index d0b2ec2..746ce7c 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/GameCore.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/GameCore.java @@ -87,7 +87,6 @@ public class GameCore implements GameLogic { public InitData Initialise(EngineInstance engineInstance) { Scene scene = (Scene) engineInstance.scene(); List models = new ArrayList<>(); - MelonaData = ModelLoader.LoadModel("resources/models/cube/Cube.json"); CubeModelID = MelonaData.ID(); List MelonaMat = ModelLoader.LoadMaterials("resources/models/cube/Cube_mat.json"); @@ -112,6 +111,13 @@ public class GameCore implements GameLogic { scene.MusketItem = MusketItem; scene.RevolverItem = RevolverItem; scene.FlintLockItem = FlintLock; + + String texturePath = "resources/EngineResources/Texture/Terrain4JTitleImage.png"; + + for(int i = 0; i < 200; i++) { + float height = 16 + (float)(128f* Math.random()); + scene.AddSprite(new Sprite("Title" + i, texturePath, new Vector2f(3440.0f * (float) Math.random(), 1440.0f * (float) Math.random()), new Vector2f(height,height), new Vector2f(1,1))); + } boolean createOfflinePlayer = !PrimaryRuntime.Server && !ClientConnectionThread.Connected; materials.addAll(MelonaMat); diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/Camera2D.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/Camera2D.java new file mode 100644 index 0000000..9e21a94 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/Camera2D.java @@ -0,0 +1,48 @@ +package net.halbear.Terrain4J.EngineCore.Main.Scene; + +import org.joml.Vector2f; + +public class Camera2D { + private final Vector2f Position; + private final Vector2f Rotation; + private final Vector2f Scale; + + public Camera2D(Vector2f position, Vector2f rotation) { + Position = position; + Rotation = rotation; + Scale = new Vector2f(1.0f, 1.0f); + } + + public Camera2D(){ + Position = new Vector2f(); + Rotation = new Vector2f(); + Scale = new Vector2f(1.0f, 1.0f); + } + + public Vector2f GetScale() { + return Scale; + } + + public Camera2D SetScale(Vector2f scale) { + Scale.set(scale); + return this; + } + + public Vector2f GetPosition() { + return Position; + } + + public Vector2f GetRotation() { + return Rotation; + } + + public Camera2D SetRotation(Vector2f rotation) { + Rotation.set(rotation); + return this; + } + + public Camera2D SetPosition(Vector2f position) { + Position.set(position); + return this; + } +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/IScene.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/IScene.java index b251c8e..38940dd 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/IScene.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/IScene.java @@ -38,4 +38,6 @@ public interface IScene { public void SetSkyBox(SkyBox skyBox); public DynamicTerrainMesh GetTerrainMesh(); public void CreateTerrainMesh(); + public List GetSprites(); + public Camera2D Get2DCamera(); } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/Scene.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/Scene.java index 1d573af..414b8c1 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/Scene.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/Scene.java @@ -42,8 +42,14 @@ public class Scene implements IScene { public static long LastGunFire = 0; private static final float MOUSE_SENSITIVITY = 0.1f; private static final float MOVEMENT_SPEED = 0.025f; + private final Map Actors; private final List ActiveActors; + + private final Camera2D camera2D; + private final Map Sprites; + private final List ActiveSprites; + private final Map CameraFrames; private final Project3D Projection; private final Camera camera; @@ -101,16 +107,43 @@ public class Scene implements IScene { terrainMesh = new DynamicTerrainMesh(PrimaryRuntime.GetRenderThread().GetRenderer().GetVulkanContext().GetDevice(), Heights, Width, Height); } + @Override + public List GetSprites() { + List actors = new ArrayList<>(); + for(int i = 0; i < ActiveSprites.size(); i++){ + actors.add(Sprites.get(ActiveSprites.get(i))); + } + return actors; + } + + public void AddSprite(Sprite NewActor){ + if(NewActor == null) return; + + Sprites.put(NewActor.GetID(), NewActor); + + if(!ActiveSprites.contains(NewActor.GetID())) { + ActiveSprites.add(NewActor.GetID()); + } + } + + @Override + public Camera2D Get2DCamera() { + return camera2D; + } + public SkyBox GetSkyBox(){return skyBox;} public Scene(Window window) { // CreateTerrainMesh(); lightingManager = new SceneLightingManager(1000,new Vector3f(1f,1f,1f), 0.5f); Actors = new HashMap<>(); + Sprites = new HashMap<>(); audioInstances = new HashMap<>(); ActiveActors = new ArrayList<>(); + ActiveSprites = new ArrayList<>(); CameraFrames = new HashMap<>(); var EngConfig = EngineConfig.getInstance(); + camera2D = new Camera2D(); camera = new Camera("DebugCamera"); LastCameraFrame = new CameraFrame(camera.GetPosition(),camera.GetRotation(),camera.GetFOV(), "NO_FRAME"); diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/Sprite.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/Sprite.java new file mode 100644 index 0000000..5703d85 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Scene/Sprite.java @@ -0,0 +1,118 @@ +package net.halbear.Terrain4J.EngineCore.Main.Scene; + +import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.TextureCache; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.ModelsCache; +import org.joml.Vector2f; +import org.joml.Vector4f; + +import java.util.HashMap; +import java.util.Map; + +import static net.halbear.Terrain4J.EngineCore.Vulkan.SpriteRendering.SpriteMeshGenerator.GenerateModelFromTexture; + +public class Sprite { + public static final Map SpriteRegistry = new HashMap<>(); + + private String ModelID; + private final String textureId; + private final Vector2f position; + private final Vector2f scale; + private final Vector2f size; + private final Vector4f colour; + private float rotation; + private int layer; + private final String ID; + private int texturePosition; + + public Sprite(String ID, String textureId, Vector2f position, Vector2f Size, Vector2f Scale) { + this.ID = ID; + this.ModelID = "TO_BE_FULFILLED_LATER"; + this.textureId = textureId; + this.position = new Vector2f().set(position); + this.scale = new Vector2f().set(Scale); + this.size = new Vector2f().set(Size); + this.colour = new Vector4f(1.0f); + this.rotation = 0.0f; + this.layer = 0; + SpriteRegistry.put(ID, this); + } + public Sprite(String ID, String ModelID, String textureId, Vector2f position, Vector2f Scale, Vector2f Size) { + this.ID = ID; + this.ModelID = ModelID; + this.textureId = textureId; + this.position = new Vector2f().set(position); + this.scale = new Vector2f().set(Scale); + this.size = new Vector2f().set(Size); + this.colour = new Vector4f(1.0f); + this.rotation = 0.0f; + this.layer = 0; + SpriteRegistry.put(ID, this); + } + + public Sprite(String ID, String TextureID, Vector2f Position, Vector2f Scale, Vector2f Size, TextureCache textureCache, ModelsCache modelsCache, VulkanContext VkCtx){ + this.ID = ID; + this.textureId = TextureID; + this.ModelID = GenerateModelFromTexture(textureCache, modelsCache, VkCtx, TextureID, Size.y); + texturePosition = textureCache.GetPosition(textureId); + this.position = new Vector2f().set(Position); + this.scale = new Vector2f().set(Scale); + this.size = new Vector2f().set(Size); + this.colour = new Vector4f(1.0f); + SpriteRegistry.put(ID, this); + } + + public Sprite CompileSprite(TextureCache textureCache, ModelsCache modelsCache, VulkanContext VkCtx){ + this.ModelID = GenerateModelFromTexture(textureCache, modelsCache, VkCtx, textureId, this.size.y); + texturePosition = textureCache.GetPosition(textureId); + return this; + } + + public int GetTexturePosition(){ + return texturePosition; + } + + public String GetID(){ + return ID; + } + + public String GetModelID() { + return ModelID; + } + + public String GetTextureID() { + return textureId; + } + + public Vector2f GetPosition() { + return position; + } + + public Vector2f GetSize() { + return size; + } + + public Vector2f GetScale() { + return scale; + } + + public Vector4f GetColour() { + return colour; + } + + public float GetRotation() { + return rotation; + } + + public void SetRotation(float rotation) { + this.rotation = rotation; + } + + public int GetLayer() { + return layer; + } + + public void SetLayer(int layer) { + this.layer = layer; + } +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/ClientConnectionThread.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/ClientConnectionThread.java index 79ea579..45ccfc0 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/ClientConnectionThread.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/ClientConnectionThread.java @@ -76,6 +76,18 @@ public class ClientConnectionThread implements Runnable{ return true; } + public static final Map BypassNullTargets = new HashMap<>(); + + static{ + BypassNullTargets.put(Packet.PACKET_CREATE_ACTOR, true); + BypassNullTargets.put(Packet.PACKET_PING, true); + BypassNullTargets.put(Packet.PACKET_POSSESS_ACTOR, true); + BypassNullTargets.put(Packet.PACKET_DESTROY_ACTOR, true ); + BypassNullTargets.put(Packet.PACKET_ASSIGN_UUID, true ); + BypassNullTargets.put(Packet.PACKET_SEND_PLAYER_COUNT, true ); + BypassNullTargets.put(Packet.PACKET_FIRE_RESULT, true); + } + public static void DecodePackets(EngineInstance engineInstance, GameCore gameCore, IScene scene){ if(!IncomingPacketQueue.isEmpty()){ Packet packet; @@ -90,31 +102,26 @@ public class ClientConnectionThread implements Runnable{ vector3f = Packet.DecodeAsVector3f(packet.Data); } - if(Target == null - && packet.PacketTypeByte != Packet.PACKET_CREATE_ACTOR - && packet.PacketTypeByte != Packet.PACKET_PING - && packet.PacketTypeByte != Packet.PACKET_POSSESS_ACTOR - && packet.PacketTypeByte != Packet.PACKET_DESTROY_ACTOR - && packet.PacketTypeByte != Packet.PACKET_ASSIGN_UUID - && packet.PacketTypeByte != Packet.PACKET_SEND_PLAYER_COUNT) { + if(Target == null && + (!BypassNullTargets.containsKey(packet.PacketTypeByte) || !BypassNullTargets.get(packet.PacketTypeByte))) { continue; } switch (packet.PacketTypeByte) { - case 0: + case Packet.PACKET_UPDATE_LOCATION: Target.SetNewPosition(vector3f); break; - case 1: + case Packet.PACKET_UPDATE_ROTATION: quaternionfdata = Packet.DecodeAsQuaternation(packet.Data); Target.SetNewRotation(quaternionfdata); break; - case 2: + case Packet.PACKET_UPDATE_LINEAR_VELOCITY: Target.SetNewVelocity(vector3f); break; - case 3: + case Packet.PACKET_UPDATE_ANGULAR_VELOCITY: Target.SetNewAngularVelocity(vector3f); break; - case 4: //"ACtorType:ModelID:x:y:z:rx:ry:rz:sx:sy:sz:hx:hy:hz:MassKG:vx:vy:vz:avx:avy:avz:posOffx:posOffy:posOffz:camOffx:camOffy:camOffz" + case Packet.PACKET_CREATE_ACTOR: //"ACtorType:ModelID:x:y:z:rx:ry:rz:sx:sy:sz:hx:hy:hz:MassKG:vx:vy:vz:avx:avy:avz:posOffx:posOffy:posOffz:camOffx:camOffy:camOffz" try { stringdata = Packet.DecodeAsString(packet.Data); String[] Split = stringdata.split(":"); @@ -217,7 +224,7 @@ public class ClientConnectionThread implements Runnable{ Logger.error(e, "Failed to decode create actor packet for actor [{}]", packet.TargetID); } break; - case 6: + case Packet.PACKET_MOVEMENT_UPDATE: MovementUpdate = Packet.DecodeAsMovementUpdate(packet.Data); if(packet.TargetID.equals(OwnedActorID)) { @@ -230,16 +237,16 @@ public class ClientConnectionThread implements Runnable{ Target.SetNewVelocity(MovementUpdate.LinearVelocity); Target.SetNewAngularVelocity(MovementUpdate.AngularVelocity); break; - case 8: + case Packet.PACKET_POSSESS_ACTOR: TryPossessActor(packet.TargetID, gameCore, scene); break; - case 9: + case Packet.PACKET_DESTROY_ACTOR: Actor actorToDestroy = Actor.Actors.get(packet.TargetID); if(actorToDestroy != null) { scene.RemoveActor(actorToDestroy); } break; - case 11: + case Packet.PACKET_UPDATE_MODEL_ID: Logger.debug("Rebound packet received for actor [{}]", packet.TargetID); stringdata = Packet.DecodeAsString(packet.Data); Actor actorToChangeModelID = Actor.Actors.get(packet.TargetID); diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/ServerConnectionThread.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/ServerConnectionThread.java index 9ab9bcd..92ccf51 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/ServerConnectionThread.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/ServerConnectionThread.java @@ -14,6 +14,7 @@ import net.halbear.Terrain4J.EngineCore.Main.Util.Maths.T4Raycast; import net.halbear.Terrain4J.EngineCore.ServerNetworking.Packet; import org.joml.Quaternionf; import org.joml.Vector3f; +import org.tinylog.Logger; import java.io.*; import java.net.Socket; @@ -248,6 +249,7 @@ public class ServerConnectionThread implements Runnable{ } else { resultPoint = new Vector3f(request.Direction).normalize().mul(request.MaxRange).add(request.Origin); } + // Logger.debug("\n Raycast Result: " + hit + "\n Raycast Request: Origin->" + request.Origin + " | Direction->" + request.Direction + " | MaxRange->" + request.MaxRange + "\n Result point: " + resultPoint + "\n Hit Actor: " + hitActorID); Packet result = new Packet(Packet.PACKET_FIRE_RESULT); result.SetID(hitActorID); diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/VkModel/ModelsCache.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/VkModel/ModelsCache.java index 33ee2c4..3855b4e 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/VkModel/ModelsCache.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/VkModel/ModelsCache.java @@ -4,6 +4,7 @@ import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; import net.halbear.Terrain4J.EngineCore.Main.PrimaryRuntime; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.DescriptorAllocator; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.DescriptorSet; +import net.halbear.Terrain4J.EngineCore.Vulkan.SpriteRendering.SpriteMeshGenerator; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.CommandBuffer; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.CommandPool; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.GPUSynchronisation.TransferBuffer; @@ -18,6 +19,7 @@ import org.lwjgl.vulkan.VkCommandBuffer; import org.tinylog.Logger; import java.io.*; +import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.LongBuffer; @@ -79,6 +81,64 @@ public class ModelsCache { } } + + public String createSpriteModel(VulkanContext vulkanContext, SpriteMeshGenerator.GeneratedSpriteMesh generatedMesh, String modelId) { + if (ModelsMap.containsKey(modelId)) { + return modelId; + } + + float[] vertices = generatedMesh.vertices(); + int[] indices = generatedMesh.indices(); + + int vertexBufferSize = vertices.length * VulkanUtils.FLOAT_SIZE; + int indexBufferSize = indices.length * VulkanUtils.INT_SIZE; + + VulkanBuffer verticesBuffer = new VulkanBuffer( + vulkanContext, + vertexBufferSize, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT + ); + + VulkanBuffer indicesBuffer = new VulkanBuffer( + vulkanContext, + indexBufferSize, + VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT + ); + + long mappedVertices = verticesBuffer.MapMemory(vulkanContext); + ByteBuffer vertexByteBuffer = MemoryUtil.memByteBuffer(mappedVertices, vertexBufferSize); + FloatBuffer vertexFloatBuffer = vertexByteBuffer.asFloatBuffer(); + vertexFloatBuffer.put(vertices); + verticesBuffer.UnMapMemory(vulkanContext); + + long mappedIndices = indicesBuffer.MapMemory(vulkanContext); + ByteBuffer indexByteBuffer = MemoryUtil.memByteBuffer(mappedIndices, indexBufferSize); + IntBuffer indexIntBuffer = indexByteBuffer.asIntBuffer(); + indexIntBuffer.put(indices); + indicesBuffer.UnMapMemory(vulkanContext); + + VulkanModel model = new VulkanModel(modelId); + VulkanMesh mesh = new VulkanMesh( + modelId + "_MESH", + verticesBuffer, + indicesBuffer, + indices.length, + "NULL_MAT" + ); + + model.GetVkMeshList().add(mesh); + ModelsMap.put(modelId, model); + + return modelId; + } + + public void loadModels(VulkanContext VkCtx, List Models, CommandPool commandPool, Queue queue){ try { List StagingBufferList = new ArrayList<>(); diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/BillboardMeshGenerator.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/BillboardMeshGenerator.java new file mode 100644 index 0000000..5184803 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/BillboardMeshGenerator.java @@ -0,0 +1,47 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.SpriteRendering; + +public final class BillboardMeshGenerator { + + private BillboardMeshGenerator() { + } + + public static GeneratedSpriteMesh createQuad(float width, float height) { + float halfWidth = width * 0.5f; + float halfHeight = height * 0.5f; + + float normalX = 0.0f; + float normalY = 0.0f; + float normalZ = 1.0f; + + float tangentX = 1.0f; + float tangentY = 0.0f; + float tangentZ = 0.0f; + + float bitangentX = 0.0f; + float bitangentY = 1.0f; + float bitangentZ = 0.0f; + + float[] vertices = new float[] { + // pos.xyz normal.xyz tangent.xyz bitangent.xyz uv.xy + -halfWidth, -halfHeight, 0.0f, normalX, normalY, normalZ, tangentX, tangentY, tangentZ, bitangentX, bitangentY, bitangentZ, 0.0f, 1.0f, + halfWidth, -halfHeight, 0.0f, normalX, normalY, normalZ, tangentX, tangentY, tangentZ, bitangentX, bitangentY, bitangentZ, 1.0f, 1.0f, + halfWidth, halfHeight, 0.0f, normalX, normalY, normalZ, tangentX, tangentY, tangentZ, bitangentX, bitangentY, bitangentZ, 1.0f, 0.0f, + -halfWidth, halfHeight, 0.0f, normalX, normalY, normalZ, tangentX, tangentY, tangentZ, bitangentX, bitangentY, bitangentZ, 0.0f, 0.0f + }; + + int[] indices = new int[] { + 0, 1, 2, + 2, 3, 0 + }; + + return new GeneratedSpriteMesh(vertices, indices); + } + + public static GeneratedSpriteMesh createQuadFromTextureSize(int textureWidth, int textureHeight, float worldHeight) { + float aspect = (float) textureWidth / (float) textureHeight; + float worldWidth = worldHeight * aspect; + return createQuad(worldWidth, worldHeight); + } + + public record GeneratedSpriteMesh(float[] vertices, int[] indices) {} +} \ No newline at end of file diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/SpriteMeshGenerator.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/SpriteMeshGenerator.java new file mode 100644 index 0000000..2a01464 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/SpriteMeshGenerator.java @@ -0,0 +1,116 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.SpriteRendering; + +import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.TextureCache; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.ModelsCache; + +import java.util.HashMap; +import java.util.Map; + +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8A8_SRGB; + +public final class SpriteMeshGenerator { + + public static final Map SPRITE_MESHES = new HashMap<>(); + public static final Map CACHED_MODELS = new HashMap<>(); + + public static void CacheModels(VulkanContext vkCtx, ModelsCache cache){ + CACHED_MODELS.clear(); + SPRITE_MESHES.forEach((x, y)->{ + CACHED_MODELS.put(x, cache.createSpriteModel(vkCtx, y,x)); + }); + } + + private SpriteMeshGenerator() { + } + + + public static GeneratedSpriteMesh createUnitQuad() { + float[] vertices = new float[] { + // pos.xy uv.xy + -0.5f, -0.5f, 0.0f, 1.0f, + 0.5f, -0.5f, 1.0f, 1.0f, + 0.5f, 0.5f, 1.0f, 0.0f, + -0.5f, 0.5f, 0.0f, 0.0f + }; + + int[] indices = new int[] { + 0, 1, 2, + 2, 3, 0 + }; + + return new GeneratedSpriteMesh(vertices, indices); + } + + public static String GenerateModelFromTexture(TextureCache textureCache, ModelsCache modelsCache, VulkanContext VkCtx, String TexturePath, float BaseHeight){ + textureCache.AddTexture(VkCtx, TexturePath, TexturePath, VK_FORMAT_R8G8B8A8_SRGB); + var texture = textureCache.GetTexture(TexturePath); + int Width = texture.GetWidth(); + int Height = texture.GetHeight(); + float aspect = (float) Width / (float) Height; + return modelsCache.createSpriteModel(VkCtx, createQuad(BaseHeight*aspect, BaseHeight), TexturePath); + } + + public static GeneratedSpriteMesh createQuad(float width, float height) { + float halfWidth = width * 0.5f; + float halfHeight = height * 0.5f; + + float[] vertices = new float[] { + // pos.xy uv.xy + -halfWidth, -halfHeight, 0.0f, 1.0f, + halfWidth, -halfHeight, 1.0f, 1.0f, + halfWidth, halfHeight, 1.0f, 0.0f, + -halfWidth, halfHeight, 0.0f, 0.0f + }; + + int[] indices = new int[] { + 0, 1, 2, + 2, 3, 0 + }; + + return new GeneratedSpriteMesh(vertices, indices); + } + + public static void writeSprite(float[] dst,int offset,float x,float y,float width,float height) { + // vertex format: + // pos.xy, uv.xy, color.rgba + + int i = offset; + + // bottom-left + dst[i++] = x; + dst[i++] = y + height; + dst[i++] = 0.0f; + dst[i++] = 1.0f; + + // bottom-right + dst[i++] = x + width; + dst[i++] = y + height; + dst[i++] = 1.0f; + dst[i++] = 1.0f; + + // top-right + dst[i++] = x + width; + dst[i++] = y; + dst[i++] = 1.0f; + dst[i++] = 0.0f; + + // top-left + dst[i++] = x; + dst[i++] = y; + dst[i++] = 0.0f; + dst[i++] = 0.0f; + } + + public static void writeIndices(int[] dst, int offset, int vertexBase) { + dst[offset] = vertexBase; + dst[offset + 1] = vertexBase + 1; + dst[offset + 2] = vertexBase + 2; + + dst[offset + 3] = vertexBase + 2; + dst[offset + 4] = vertexBase + 3; + dst[offset + 5] = vertexBase; + } + + public record GeneratedSpriteMesh(float[] vertices, int[] indices) {} +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/SpriteRenderer.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/SpriteRenderer.java new file mode 100644 index 0000000..87a3565 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/SpriteRenderer.java @@ -0,0 +1,334 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.SpriteRendering; + +import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; +import net.halbear.Terrain4J.EngineCore.Logic.EngineInstance; +import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; +import net.halbear.Terrain4J.EngineCore.Main.Scene.IScene; +import net.halbear.Terrain4J.EngineCore.Main.Scene.Sprite; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.DeferredRendering.MultiRenderTargetAttachments; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.DefaultPipeline; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.*; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Pipeline; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.PipelineBuildInfo; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.PushConstantsRange; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.*; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.*; +import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.CommandBuffer; +import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.Device; +import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DisplayToScreen.ImageView; +import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.SwapChain.SwapChain; +import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanBuffer; +import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils; +import org.joml.Vector2f; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.util.shaderc.Shaderc; +import org.lwjgl.vulkan.*; +import org.tinylog.Logger; + +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import java.util.*; + +import static org.lwjgl.vulkan.VK10.*; +import static org.lwjgl.vulkan.VK10.VK_BORDER_COLOR_INT_OPAQUE_BLACK; +import static org.lwjgl.vulkan.VK13.*; +import static org.lwjgl.vulkan.VK13.vkCmdEndRendering; + +public class SpriteRenderer { + private static final String DESCRIPTOR_ID_PRJ = "SPRITE_DESC_ID_PRJ"; + private static final String DESCRIPTOR_ID_VIEW = "SPRITE_DESC_ID_VIEW"; + private static final String DESCRIPTOR_ID_TEXTURE = "SPRITE_DESC_ID_TEXT"; + private static final String SPRITE_FRAGMENT_SHADER_GLSL = "resources/EngineResources/SpriteShaders/sprite_fragment.glsl"; + private static final String SPRITE_FRAGMENT_SHADER_SPV = SPRITE_FRAGMENT_SHADER_GLSL + ".spv"; + private static final String SPRITE_TRANSLUCENT_FRAGMENT_SHADER_GLSL = "resources/EngineResources/SpriteShaders/sprite_translucent_fragment.glsl"; + private static final String SPRITE_TRANSLUCENT_FRAGMENT_SHADER_SPV = SPRITE_FRAGMENT_SHADER_GLSL + ".spv"; + private static final String SPRITE_VERTEX_SHADER_GLSL = "resources/EngineResources/SpriteShaders/sprite_vertex.glsl"; + private static final String SPRITE_VERTEX_SHADER_SPV = SPRITE_VERTEX_SHADER_GLSL + ".spv"; + private static final int PUSH_CONSTANTS_SIZE = VulkanUtils.VEC2_SIZE + VulkanUtils.VEC2_SIZE + VulkanUtils.VEC2_SIZE + VulkanUtils.INT_SIZE; + + private final VulkanBuffer[] IndexBuffers; + private final VulkanBuffer[] VertexBuffers; + private final TextureSampler TextureSampler; + private final Map TexturesMap; + private Pipeline VkPipelineOpaque; + private Pipeline VkPipelineTranslucent; + private final DescriptorSetLayout TextDescriptorSetLayout; + private final DescriptorSetLayout descriptorLayoutVertexUniform; + + public static final int COLOUR_FORMAT = VK_FORMAT_R32G32B32A32_SFLOAT; + public static final int DEPTH_FORMAT = VK_FORMAT_D32_SFLOAT; + private final VkClearValue ClearValueDepth; + private final ByteBuffer PushConstBuffer; + private Attachment AttachmentDepth; + private Attachment AttachmentColour; + private VkRenderingAttachmentInfo AttachmentInfoDepth; + private VkClearValue ClearValueColour; + private VkRenderingAttachmentInfo.Buffer AttachmentInfoColour; + private VkRenderingInfo RenderInfo; + private final VulkanBuffer[] BufferViewMatrices; + + public SpriteRenderer(EngineInstance engineInstance, VulkanContext VkCtx, Attachment dstAttachment){ + ClearValueColour = VkClearValue.calloc().color( + c -> c.float32(0, 0.0f).float32(1, 0.0f).float32(2, 0.0f).float32(3, 0.0f)); + ClearValueDepth = VkClearValue.calloc().color(c -> c.float32(0, 0.0f)); + // AttachmentColour = CreateColourAttachment(VkCtx);// + AttachmentInfoColour = CreateColourAttachmentInfo(dstAttachment, ClearValueColour); + AttachmentDepth = CreateDepthAttachment(VkCtx); + AttachmentInfoDepth = CreateDepthAttachmentInfo(AttachmentDepth, ClearValueDepth); + RenderInfo = CreateRenderInfo(dstAttachment,AttachmentInfoColour, AttachmentInfoDepth); + //RenderInfo = CreateRenderInfo(AttachmentColour, AttachmentInfoColour, AttachmentInfoDepth); + VertexBuffers = new VulkanBuffer[VulkanUtils.MAX_IN_FLIGHT]; + IndexBuffers = new VulkanBuffer[VulkanUtils.MAX_IN_FLIGHT]; + PushConstBuffer = (MemoryUtil.memAlloc(PUSH_CONSTANTS_SIZE)); + var textureSamplerInfo = new TextureSamplerInfo(VK_SAMPLER_ADDRESS_MODE_REPEAT, + VK_BORDER_COLOR_INT_OPAQUE_BLACK, 1, true); + TextureSampler = new TextureSampler(VkCtx, textureSamplerInfo); + TextDescriptorSetLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + 0, TextureCache.MAX_TEXTURES, VK_SHADER_STAGE_FRAGMENT_BIT)); + descriptorLayoutVertexUniform = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + 0, 1, VK_SHADER_STAGE_VERTEX_BIT)); + BufferViewMatrices = VulkanUtils.CreateHostVisibleBuffers(VkCtx, VulkanUtils.VEC2_SIZE * 2, VulkanUtils.MAX_IN_FLIGHT, + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, DESCRIPTOR_ID_VIEW, descriptorLayoutVertexUniform); + + TexturesMap = new HashMap<>(); + DescriptorSetLayout[] layouts = new DescriptorSetLayout[]{ + descriptorLayoutVertexUniform, + TextDescriptorSetLayout + }; + ShaderModule[] shadermodules = CreateShaderModules(VkCtx,0); + VkPipelineOpaque = CreatePipeline(VkCtx, shadermodules, layouts, true, false, true, false, 1); + shadermodules = CreateShaderModules(VkCtx,2); + VkPipelineTranslucent = CreatePipeline(VkCtx, shadermodules, layouts, false, true, true, false, 1); + Arrays.asList(shadermodules).forEach(s->s.CleanUp(VkCtx)); + } + + private static Attachment CreateColourAttachment(VulkanContext VkCtx){ + SwapChain swapChain = VkCtx.GetSwapChain(); + VkExtent2D SwapChainExtent = swapChain.GetSwapChainExtent(); + return new Attachment(VkCtx, SwapChainExtent.width(), SwapChainExtent.height(), COLOUR_FORMAT,VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); + } + + + private static VkRenderingAttachmentInfo.Buffer CreateColourAttachmentInfo(Attachment attachment, VkClearValue ClearValue){ + return VkRenderingAttachmentInfo.calloc(1) + .sType$Default() + .imageView(attachment.GetVkImageView().GetVulkanImageView()) + .imageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) + .loadOp(VK_ATTACHMENT_LOAD_OP_LOAD) + .storeOp(VK_ATTACHMENT_STORE_OP_STORE) + .clearValue(ClearValue); + } + + private static Attachment CreateDepthAttachment(VulkanContext VkCtx){ + SwapChain swapChain = VkCtx.GetSwapChain(); + VkExtent2D swapChainExtent = swapChain.GetSwapChainExtent(); + return new Attachment(VkCtx, swapChainExtent.width(), swapChainExtent.height(), + DEPTH_FORMAT,VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT); + } + + + private static VkRenderingAttachmentInfo CreateDepthAttachmentInfo(Attachment DepthAttachment, VkClearValue clearValue){ + return VkRenderingAttachmentInfo.calloc() + .sType$Default() + .imageView(DepthAttachment.GetVkImageView().GetVulkanImageView()) + .imageLayout(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) + .loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR) + .storeOp(VK_ATTACHMENT_STORE_OP_DONT_CARE) + .clearValue(clearValue); + } + private static ShaderModule[] CreateShaderModules(VulkanContext VkCtx, int Translucent){ + if(EngineConfig.getInstance().RecompileShaders()){ + ShaderCompiler.CompileGLSLShaderOnChange(SPRITE_VERTEX_SHADER_GLSL, Shaderc.shaderc_glsl_vertex_shader); + ShaderCompiler.CompileGLSLShaderOnChange( Translucent == 2 ? SPRITE_TRANSLUCENT_FRAGMENT_SHADER_GLSL : SPRITE_FRAGMENT_SHADER_GLSL, Shaderc.shaderc_glsl_fragment_shader); + } + return new ShaderModule[]{ + new ShaderModule(VkCtx, VK_SHADER_STAGE_VERTEX_BIT, SPRITE_VERTEX_SHADER_SPV,null), + new ShaderModule(VkCtx, VK_SHADER_STAGE_FRAGMENT_BIT, Translucent == 2 ? SPRITE_TRANSLUCENT_FRAGMENT_SHADER_SPV : SPRITE_FRAGMENT_SHADER_SPV,null) + }; + + + } + + private static Pipeline CreatePipeline(VulkanContext VkCtx, ShaderModule[] ShaderModules, DescriptorSetLayout[] DescriptorSetLayouts, boolean DepthWrite, boolean DualRender, boolean Blending, boolean AlphaToCoverage, int BlendingMethod){ + var vertexBufferStructure = new SpriteVertexBufferStructure(); + var BuildInfo = new PipelineBuildInfo(ShaderModules,vertexBufferStructure.getVertexInput(),new int[]{MultiRenderTargetAttachments.ALBEDO_FORMAT}) + .SetDepthFormat(MultiRenderTargetAttachments.DEPTH_FORMAT) + .SetDepthWrite(DepthWrite) + .SetPushConstantRanges( + new PushConstantsRange[]{ + new PushConstantsRange(VK_SHADER_STAGE_VERTEX_BIT,0,VulkanUtils.VEC2_SIZE), + new PushConstantsRange(VK_SHADER_STAGE_VERTEX_BIT,VulkanUtils.VEC2_SIZE,VulkanUtils.VEC2_SIZE), + new PushConstantsRange(VK_SHADER_STAGE_VERTEX_BIT,VulkanUtils.VEC2_SIZE * 2,VulkanUtils.VEC2_SIZE), + new PushConstantsRange(VK_SHADER_STAGE_FRAGMENT_BIT,VulkanUtils.VEC2_SIZE * 3,VulkanUtils.INT_SIZE) + }) + .SetDescriptorSetLayouts(DescriptorSetLayouts) + .SetBlendingMethod(BlendingMethod) + .BlendingIsUsed(Blending) + .SetDualPass(DualRender) + .SetAlphaToCoverage(AlphaToCoverage); + var pipeline = new DefaultPipeline(VkCtx, BuildInfo); + Logger.debug("Pipeline Created"); + vertexBufferStructure.cleanup(); + return pipeline; + } + + private void SetPushConstants(VkCommandBuffer CmdHandle, Vector2f ScreenSize, Vector2f ModelSize, Vector2f Position, int TextureIndex, Pipeline VkPipeline){ + ModelSize.get(0,PushConstBuffer); + Position.get(VulkanUtils.VEC2_SIZE, PushConstBuffer); + ScreenSize.get(VulkanUtils.VEC2_SIZE * 2, PushConstBuffer); + PushConstBuffer.putInt(VulkanUtils.VEC2_SIZE * 3, TextureIndex); + vkCmdPushConstants(CmdHandle, VkPipeline.GetVulkanPipelineLayout(), VK_SHADER_STAGE_VERTEX_BIT, 0, + PushConstBuffer.slice(0,VulkanUtils.VEC2_SIZE)); + vkCmdPushConstants(CmdHandle, VkPipeline.GetVulkanPipelineLayout(), VK_SHADER_STAGE_VERTEX_BIT, VulkanUtils.VEC2_SIZE, + PushConstBuffer.slice(VulkanUtils.VEC2_SIZE,VulkanUtils.VEC2_SIZE)); + vkCmdPushConstants(CmdHandle, VkPipeline.GetVulkanPipelineLayout(), VK_SHADER_STAGE_VERTEX_BIT, VulkanUtils.VEC2_SIZE * 2, + PushConstBuffer.slice(VulkanUtils.VEC2_SIZE * 2,VulkanUtils.VEC2_SIZE)); + vkCmdPushConstants(CmdHandle, VkPipeline.GetVulkanPipelineLayout(), VK_SHADER_STAGE_FRAGMENT_BIT, VulkanUtils.VEC2_SIZE * 3, + PushConstBuffer.slice(VulkanUtils.VEC2_SIZE* 3, VulkanUtils.INT_SIZE)); + } + + + public void Resize(EngineInstance instance, VulkanContext VkCtx, Attachment dstAttachment){ + AttachmentInfoDepth.free(); + //AttachmentColour.CleanUp(VkCtx); + AttachmentDepth.CleanUp(VkCtx); + AttachmentDepth = CreateDepthAttachment(VkCtx); + AttachmentInfoDepth = CreateDepthAttachmentInfo(AttachmentDepth, ClearValueDepth); + RenderInfo.free(); + AttachmentInfoColour.free(); + AttachmentInfoColour = CreateColourAttachmentInfo(dstAttachment, ClearValueColour); + RenderInfo = CreateRenderInfo(dstAttachment,AttachmentInfoColour, AttachmentInfoDepth); + } + + public void LoadMaterials(VulkanContext VkCtx, MaterialsCache materialsCache, TextureCache textureCache){ + DescriptorAllocator descriptorAllocator = VkCtx.GetDescriptorAllocator(); + Device device = VkCtx.GetDevice(); + List imageViews = textureCache.GetTextureList().stream().map(ITexture::GetImageView).toList(); + DescriptorSet descriptorSet = VkCtx.GetDescriptorAllocator().AddDescriptorSet(device,DESCRIPTOR_ID_TEXTURE,TextDescriptorSetLayout); + descriptorSet.SetImageArray(device,imageViews,TextureSampler,0); + } + + private static VkRenderingInfo CreateRenderInfo(Attachment ColourAttachment, VkRenderingAttachmentInfo.Buffer ColourAttachmentInfo, VkRenderingAttachmentInfo DepthAttachmentInfo){ + VkRenderingInfo Result; + try(var MemStack = MemoryStack.stackPush()){ + VkExtent2D Extent = VkExtent2D.calloc(MemStack); + Extent.width(ColourAttachment.GetVkImage().GetWidth()); + Extent.height(ColourAttachment.GetVkImage().GetHeight()); + var RenderArea = VkRect2D.calloc(MemStack).extent(Extent); + Result = VkRenderingInfo.calloc() + .sType$Default() + .renderArea(RenderArea) + .layerCount(1) + .pColorAttachments(ColourAttachmentInfo) + .pDepthAttachment(DepthAttachmentInfo); + } + return Result; + } + public void cleanup(VulkanContext VkCtx){ + + VkPipelineTranslucent.CleanUp(VkCtx); + VkPipelineOpaque.CleanUp(VkCtx); + Arrays.asList(BufferViewMatrices).forEach(buffer -> buffer.cleanup(VkCtx)); + RenderInfo.free(); + AttachmentInfoColour.free(); + // AttachmentColour.CleanUp(VkCtx); + AttachmentDepth.CleanUp(VkCtx); + AttachmentInfoDepth.free(); + MemoryUtil.memFree(PushConstBuffer); + ClearValueDepth.free(); + ClearValueColour.free(); + TextDescriptorSetLayout.CleanUp(VkCtx); + descriptorLayoutVertexUniform.CleanUp(VkCtx); + TextureSampler.CleanUp(VkCtx); + } + + + public void ForwardRender(EngineInstance engineInstance, VulkanContext vulkanContext, CommandBuffer commandBuffer, ModelsCache modelsCache, Attachment SrcAttachment, int CurrentFrame){ + try(var MemStack = MemoryStack.stackPush()){ + VkCommandBuffer CommandHandle = commandBuffer.GetVulkanCommandBuffer(); +// VulkanUtils.ImageBarrier(MemStack, CommandHandle, AttachmentColour.GetVkImage().getVulkanImage(), +// VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, +// VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, +// VK_ACCESS_2_NONE, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT); + + long InitialTime = System.nanoTime(); + vkCmdBeginRendering(CommandHandle, RenderInfo); + + vkCmdBindPipeline(CommandHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, VkPipelineOpaque.GetVulkanPipeline()); + + Image ColourImage = SrcAttachment.GetVkImage(); + int width = ColourImage.GetWidth(); + int height = ColourImage.GetHeight(); + var Viewport = VkViewport.calloc(1,MemStack) + .x(0) + .y(height) + .height(-height) + .width(width) + .minDepth(0.0F) + .maxDepth(1.0F); + vkCmdSetViewport(CommandHandle, 0, Viewport); + var Scissor = VkRect2D.calloc(1,MemStack) + .extent(it -> it.width(width).height(height)) + .offset(it->it.x(0).y(0)); + vkCmdSetScissor(CommandHandle, 0, Scissor); + + VulkanUtils.CopyVector2ToBuffer(vulkanContext, BufferViewMatrices[CurrentFrame],engineInstance.scene().Get2DCamera().GetPosition(), 0); + VulkanUtils.CopyVector2ToBuffer(vulkanContext, BufferViewMatrices[CurrentFrame],engineInstance.scene().Get2DCamera().GetScale(), VulkanUtils.VEC2_SIZE); + DescriptorAllocator descriptorAllocator = vulkanContext.GetDescriptorAllocator(); + LongBuffer DescriptorSets = MemStack.mallocLong(2) + .put(0,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_VIEW, CurrentFrame).GetVkDescriptorSet()) + .put(1,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_TEXTURE).GetVkDescriptorSet()); + vkCmdBindDescriptorSets(CommandHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, VkPipelineOpaque.GetVulkanPipelineLayout(),0,DescriptorSets,null); + RenderActors(engineInstance, CommandHandle, modelsCache, false, VkPipelineOpaque); + + vkCmdBindPipeline(CommandHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, VkPipelineTranslucent.GetVulkanPipeline()); + vkCmdBindDescriptorSets(CommandHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, VkPipelineTranslucent.GetVulkanPipelineLayout(), 0, DescriptorSets, null); + RenderActors(engineInstance, CommandHandle, modelsCache, true, VkPipelineTranslucent); + + vkCmdEndRendering(CommandHandle); + } + } + + public void CompileSprites(VulkanContext VkCtx, ModelsCache modelsCache, TextureCache textureCache){ + Sprite.SpriteRegistry.forEach((id, sprite) -> { + sprite.CompileSprite(textureCache,modelsCache,VkCtx); + }); + } + + public void Render(EngineInstance engineInstance,VulkanContext vulkanContext, CommandBuffer commandBuffer, Attachment SrcAttachment, ModelsCache modelsCache, int CurrentFrame){ + + ForwardRender(engineInstance,vulkanContext,commandBuffer,modelsCache,SrcAttachment,CurrentFrame); + } + + public static final Vector2f ScreenSize = new Vector2f(3440,1440); + + private void RenderActors(EngineInstance instance, VkCommandBuffer CommandHandle, ModelsCache modelsCache, boolean Transparent, Pipeline pipeline){ + try(var MemStack = MemoryStack.stackPush()){ + LongBuffer offsets = MemStack.mallocLong(1).put(0,0L); + LongBuffer vertexBuffer = MemStack.mallocLong(1); + IScene scene = instance.scene(); + List Sprites = scene.GetSprites(); + int ActorCount = Sprites.size(); + for(int i = 0; i < ActorCount; i++){ + if(i < Sprites.size()) { + var Actor = Sprites.get(i); + if(Actor != null) { + VulkanModel model = modelsCache.GetModel(Actor.GetModelID()); + List VkMeshList = model.GetVkMeshList(); + int MeshCount = VkMeshList.size(); + for (int j = 0; j < MeshCount; j++) { + var VkMesh = VkMeshList.get(j); + SetPushConstants(CommandHandle, ScreenSize, Actor.GetScale(), Actor.GetPosition(),Actor.GetTexturePosition(),pipeline); + vertexBuffer.put(0, VkMesh.VerticesBuffer().GetBuffer()); + vkCmdBindVertexBuffers(CommandHandle, 0, vertexBuffer, offsets); + vkCmdBindIndexBuffer(CommandHandle, VkMesh.IndicesBuffer().GetBuffer(), 0, VK_INDEX_TYPE_UINT32); + vkCmdDrawIndexed(CommandHandle, VkMesh.IndicesCount(), 1, 0, 0, 0); + } + } + } + } + } + } + +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/SpriteVertexBufferStructure.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/SpriteVertexBufferStructure.java new file mode 100644 index 0000000..fc68a85 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/SpriteVertexBufferStructure.java @@ -0,0 +1,69 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.SpriteRendering; + +import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils; +import org.lwjgl.vulkan.VkPipelineVertexInputStateCreateInfo; +import org.lwjgl.vulkan.VkVertexInputAttributeDescription; +import org.lwjgl.vulkan.VkVertexInputBindingDescription; + +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32_SFLOAT; +import static org.lwjgl.vulkan.VK13.VK_FORMAT_R32G32B32_SFLOAT; +import static org.lwjgl.vulkan.VK13.VK_VERTEX_INPUT_RATE_VERTEX; + +public class SpriteVertexBufferStructure { + public static final int TEXT_COORD_COMPONENTS = 2; + private static final int NUMBER_OF_ATTRIBUTES = 2; + private static final int POSITION_COMPONENTS = 2; + public static int VertexInputSize = 5 * VulkanUtils.FLOAT_SIZE; + + private final VkPipelineVertexInputStateCreateInfo VertexInput; + private final VkVertexInputAttributeDescription.Buffer VertexInputAttributes; + private final VkVertexInputBindingDescription.Buffer VertexInputBindings; + + public static int GetVertexInputSize(){ + int Offset = 0; + Offset += POSITION_COMPONENTS * VulkanUtils.FLOAT_SIZE; + Offset += TEXT_COORD_COMPONENTS * VulkanUtils.FLOAT_SIZE; + VertexInputSize = Offset; + return VertexInputSize; + } + + public SpriteVertexBufferStructure(){ + VertexInputAttributes = VkVertexInputAttributeDescription.calloc(NUMBER_OF_ATTRIBUTES); + VertexInputBindings = VkVertexInputBindingDescription.calloc(1); + VertexInput = VkPipelineVertexInputStateCreateInfo.calloc(); + + int i =0; + int Offset = 0; + //vertex position + VertexInputAttributes.get(i) + .binding(0) + .location(i) + .format(VK_FORMAT_R32G32B32_SFLOAT) + .offset(Offset); + i++; + Offset += POSITION_COMPONENTS * VulkanUtils.FLOAT_SIZE; + //texture coordinates + VertexInputAttributes.get(i) + .binding(0) + .location(i) + .format(VK_FORMAT_R32G32_SFLOAT) + .offset(Offset); + + int Stride = Offset + TEXT_COORD_COMPONENTS * VulkanUtils.FLOAT_SIZE; + VertexInputBindings.get(0) + .binding(0) + .stride(Stride) + .inputRate(VK_VERTEX_INPUT_RATE_VERTEX); + VertexInput + .sType$Default() + .pVertexAttributeDescriptions(VertexInputAttributes) + .pVertexBindingDescriptions(VertexInputBindings); + } + public void cleanup(){ + VertexInput.free(); + VertexInputAttributes.free(); + } + public VkPipelineVertexInputStateCreateInfo getVertexInput(){ + return VertexInput; + } +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/TextureAlphaBounds.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/TextureAlphaBounds.java new file mode 100644 index 0000000..a326a2b --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/SpriteRendering/TextureAlphaBounds.java @@ -0,0 +1,69 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.SpriteRendering; + +import java.nio.ByteBuffer; + +public final class TextureAlphaBounds { + + private TextureAlphaBounds() { + } + + public static AlphaBounds find(ByteBuffer rgba, int width, int height, int alphaThreshold) { + int minX = width; + int minY = height; + int maxX = -1; + int maxY = -1; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int index = ((y * width) + x) * 4; + int alpha = rgba.get(index + 3) & 0xFF; + + if (alpha >= alphaThreshold) { + if (x < minX) { + minX = x; + } + if (y < minY) { + minY = y; + } + if (x > maxX) { + maxX = x; + } + if (y > maxY) { + maxY = y; + } + } + } + } + + if (maxX < minX || maxY < minY) { + return new AlphaBounds(0, 0, width - 1, height - 1, width, height); + } + + return new AlphaBounds(minX, minY, maxX, maxY, width, height); + } + public record AlphaBounds( int minX,int minY,int maxX, int maxY,int textureWidth,int textureHeight) { + public int width() { + return maxX - minX + 1; + } + + public int height() { + return maxY - minY + 1; + } + + public float u0() { + return (float) minX / (float) textureWidth; + } + + public float v0() { + return (float) minY / (float) textureHeight; + } + + public float u1() { + return (float) (maxX + 1) / (float) textureWidth; + } + + public float v1() { + return (float) (maxY + 1) / (float) textureHeight; + } + } +} \ No newline at end of file diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Util/VulkanUtils.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Util/VulkanUtils.java index 5b3daf3..ee9cb27 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Util/VulkanUtils.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Util/VulkanUtils.java @@ -8,6 +8,7 @@ import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.DescriptorSet; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.DescriptorSetLayout; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.Device; import org.joml.Matrix4f; +import org.joml.Vector2f; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; import org.lwjgl.vulkan.*; @@ -78,6 +79,13 @@ public class VulkanUtils { } } + public static void CopyVector2ToBuffer(VulkanContext VkCtx, VulkanBuffer VkBuffer, Vector2f vector, int Offset){ + long MappedMemory = VkBuffer.MapMemory(VkCtx); + ByteBuffer matrixBuffer = MemoryUtil.memByteBuffer(MappedMemory, (int)VkBuffer.GetRequestedSize()); + vector.get(Offset, matrixBuffer); + VkBuffer.UnMapMemory(VkCtx); + } + public static void CopyMatrixToBuffer(VulkanContext VkCtx, VulkanBuffer VkBuffer, Matrix4f matrix, int Offset){ long MappedMemory = VkBuffer.MapMemory(VkCtx); ByteBuffer matrixBuffer = MemoryUtil.memByteBuffer(MappedMemory, (int)VkBuffer.GetRequestedSize());