2D rendering (big feature)

This commit is contained in:
Halbear 2026-08-14 22:22:03 +01:00
parent 6d7ea4ebc5
commit 5a05b9d65e
21 changed files with 1075 additions and 22 deletions

View file

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

View file

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

View file

@ -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<Long, List<String>> 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<List<String>> Buckets() {
return Cells.values();
}
}

View file

@ -87,7 +87,6 @@ public class GameCore implements GameLogic {
public InitData Initialise(EngineInstance engineInstance) {
Scene scene = (Scene) engineInstance.scene();
List<ModelData> models = new ArrayList<>();
MelonaData = ModelLoader.LoadModel("resources/models/cube/Cube.json");
CubeModelID = MelonaData.ID();
List<MaterialData> 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);

View file

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

View file

@ -38,4 +38,6 @@ public interface IScene {
public void SetSkyBox(SkyBox skyBox);
public DynamicTerrainMesh GetTerrainMesh();
public void CreateTerrainMesh();
public List<Sprite> GetSprites();
public Camera2D Get2DCamera();
}

View file

@ -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<String, Actor> Actors;
private final List<String> ActiveActors;
private final Camera2D camera2D;
private final Map<String, Sprite> Sprites;
private final List<String> ActiveSprites;
private final Map<String, CameraFrame> 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<Sprite> GetSprites() {
List<Sprite> 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");

View file

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

View file

@ -76,6 +76,18 @@ public class ClientConnectionThread implements Runnable{
return true;
}
public static final Map<Byte, Boolean> 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);

View file

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

View file

@ -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<ModelData> Models, CommandPool commandPool, Queue queue){
try {
List<VulkanBuffer> StagingBufferList = new ArrayList<>();

View file

@ -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) {}
}

View file

@ -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<String, GeneratedSpriteMesh> SPRITE_MESHES = new HashMap<>();
public static final Map<String, String> 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) {}
}

View file

@ -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<Long,Long> 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<ImageView> 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<Sprite> 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<VulkanMesh> 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);
}
}
}
}
}
}
}

View file

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

View file

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

View file

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