From 2f626ba00f89635ce8dcee97240504c1f5c939c6 Mon Sep 17 00:00:00 2001 From: Halbear Date: Thu, 14 May 2026 14:30:48 +0100 Subject: [PATCH] Vertex Rendering! --- resources/shaders/scene_fragment.glsl | 8 ++ resources/shaders/scene_fragment.glsl.spv | Bin 0 -> 428 bytes resources/shaders/scene_vertex.glsl | 8 ++ resources/shaders/scene_vertex.glsl.spv | Bin 0 -> 896 bytes .../Terrain4J/EngineCore/Display/Render.java | 19 ++- .../EngineCore/Logic/EngineConfig.java | 13 ++- .../Terrain4J/EngineCore/Logic/GameLogic.java | 2 +- .../Terrain4J/EngineCore/Logic/InitData.java | 8 ++ .../EngineCore/Logic/VulkanContext.java | 9 +- .../Terrain4J/EngineCore/Main/GameCore.java | 21 +++- .../EngineCore/Main/PrimaryRuntime.java | 8 +- .../EngineCore/Threads/MainThread.java | 2 +- .../EngineCore/Threads/RenderThread.java | 4 +- .../Vulkan/Rendering/Pipeline/Pipeline.java | 108 ++++++++++++++++++ .../Rendering/Pipeline/PipelineBuildInfo.java | 18 +++ .../Rendering/Pipeline/PipelineCache.java | 32 ++++++ .../Rendering/Shader/ShaderCompiler.java | 68 +++++++++++ .../Vulkan/Rendering/Shader/ShaderModule.java | 51 +++++++++ .../DisplayToScreen/SceneRender.java | 76 +++++++++++- terrain4j.properties | 20 +++- 20 files changed, 450 insertions(+), 25 deletions(-) create mode 100644 resources/shaders/scene_fragment.glsl create mode 100644 resources/shaders/scene_fragment.glsl.spv create mode 100644 resources/shaders/scene_vertex.glsl create mode 100644 resources/shaders/scene_vertex.glsl.spv create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Logic/InitData.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Pipeline.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/PipelineBuildInfo.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/PipelineCache.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Shader/ShaderCompiler.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Shader/ShaderModule.java diff --git a/resources/shaders/scene_fragment.glsl b/resources/shaders/scene_fragment.glsl new file mode 100644 index 0000000..130d3f9 --- /dev/null +++ b/resources/shaders/scene_fragment.glsl @@ -0,0 +1,8 @@ +#version 450 + +layout(location = 0) out vec4 outFragColor; + +void main() +{ + outFragColor = vec4(1, 0, 0, 1); +} \ No newline at end of file diff --git a/resources/shaders/scene_fragment.glsl.spv b/resources/shaders/scene_fragment.glsl.spv new file mode 100644 index 0000000000000000000000000000000000000000..7e0e9b852d4c5d7fc3de284fcd5d7484d84cb43b GIT binary patch literal 428 zcmYjO%}T>S7@Rgut!=G}ptsWEv88yhMG*8Lx|cu?KEP6vP!`gpq^aP|XY;AN6gsmi zWW(h9W-{MT*r0iP0yNOW8K2*Ci6#|LYx`)T_h`1OlG*+IPL2TrZ%Tt@UGL0+uk;>Pp`CJ}n`=o9N z>8Q^PxrgQ!`|7P+tzU|)C`n=Hl0FpSx#A|;W$d?t&##%csf~ym`n9vT`j7bTFzSz` zKmKv`lr|xkw@UzD&;PvVIWT3F>iUE{(go!Ugnqu}=&0Whm5}j?Ofyt>;4M55M|@E# Ar2qf` literal 0 HcmV?d00001 diff --git a/resources/shaders/scene_vertex.glsl b/resources/shaders/scene_vertex.glsl new file mode 100644 index 0000000..5a51046 --- /dev/null +++ b/resources/shaders/scene_vertex.glsl @@ -0,0 +1,8 @@ +#version 450 + +layout(location = 0) in vec3 inPos; + +void main() +{ + gl_Position = vec4(inPos,1); +} \ No newline at end of file diff --git a/resources/shaders/scene_vertex.glsl.spv b/resources/shaders/scene_vertex.glsl.spv new file mode 100644 index 0000000000000000000000000000000000000000..b94dfb30f02972c3919e89a345a950060dabcc51 GIT binary patch literal 896 zcmYk3OH0F05QVR8V{3iC>JzEB6p9NKK~zK~E&?io+aT>lF2q=xDC)}J=C5)ic)mPp zr%Y$&%$e6swX#z;X3R{QX|rK+nKu<7M%Z{}{op?6Bx$d+f3OE*##A#RoLQrCNlp6v z=wke7>4LPXUq$v?#F}XuKM4Js8$Y-TyZwHcq#uzDBNyAS=LWW$x;G22F~W0kH+t=j z5>#!83Wer4KDn2~rS2sbTF?0Gp5ZP|@7<>+QWrni#1Z#SC0MYd95ryQrZ&B-wxuiL^R__ z2RXDY-jr;)+i?-|CSc5&$#{8R<`B Models = initData.Models(); + Logger.debug("Loading {} models", Models.size()); + modelsCache.loadModels(RendererContext, Models, CommandPools[0], GraphicsQueue); + Logger.debug("Loaded {} models", Models.size()); } private void RecordingStart(CommandPool commandPool, CommandBuffer commandBuffer){ @@ -69,7 +83,7 @@ public class Render { public void cleanup() { RendererContext.GetDevice().waitIdle(); Logger.debug("Waiting Vulkan Context"); - sceneRender.cleanup(); + sceneRender.cleanup(RendererContext); Logger.debug("Dynamic Renderer Cleaned up"); Arrays.asList(RenderCompleteSemaphores).forEach(i->i.cleanup(RendererContext)); Logger.debug("Render Semaphores cleaned up"); @@ -82,6 +96,7 @@ public class Render { CommandPools[i].cleanup(RendererContext); } Logger.debug("Command pools cleaned up"); + modelsCache.CleanUp(RendererContext); RendererContext.cleanup(); } public void render(EngineInstance engineInstance){ @@ -94,7 +109,7 @@ public class Render { if (ImageIndex < 0){ return; } - sceneRender.Render(RendererContext,CommandBuffer,ImageIndex); + sceneRender.Render(RendererContext,CommandBuffer, modelsCache,ImageIndex); RecordingStop(CommandBuffer); Submit(CommandBuffer, CurrentFrame, ImageIndex); swapChain.PresentImage(PresentQueue, RenderCompleteSemaphores[ImageIndex],ImageIndex); 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 b3afaa1..d01ef99 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/EngineConfig.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/EngineConfig.java @@ -30,6 +30,8 @@ public class EngineConfig { public static int FrameRate_WINDOW =0; public static int RENDERFRAMES =0; public static boolean VSYNC = false; + public static boolean ShaderDebug = false; + public static boolean RecompileShaders = false; private boolean vkValidated; private String PhysicalDeviceName; private int RequestedImages; @@ -42,7 +44,12 @@ public class EngineConfig { public boolean GetVSYNC(){ return VSYNC; } - + public boolean DebugShaders(){ + return ShaderDebug; + } + public boolean RecompileShaders(){ + return RecompileShaders; + } private EngineConfig() { var EngineConfigVar = new Properties(); Path path = Paths.get(""); @@ -95,6 +102,8 @@ public class EngineConfig { VSYNC = Boolean.parseBoolean(EngineConfigVar.getOrDefault("vsync", true).toString()); vkValidated = Boolean.parseBoolean(EngineConfigVar.getOrDefault("vkValidated", false).toString()); RequestedImages = Integer.parseInt(EngineConfigVar.getOrDefault("RequestedImages", 3).toString()); + ShaderDebug =Boolean.parseBoolean(EngineConfigVar.getOrDefault("Debug_Shaders", false).toString()); + RecompileShaders =Boolean.parseBoolean(EngineConfigVar.getOrDefault("ShaderRecompiling", true).toString()); Logger.debug("\n\nsuccessfully loaded configuration: \n{}\n\n",EngineConfigVar.toString()); } else if(!ConfigFile.exists()){ @@ -112,6 +121,8 @@ public class EngineConfig { EngineConfigVar.setProperty("display_height","720"); EngineConfigVar.setProperty("vsync","true"); EngineConfigVar.setProperty("vkValidated","false"); + EngineConfigVar.setProperty("Debug_Shaders","false"); + EngineConfigVar.setProperty("ShaderRecompiling","true"); EngineConfigVar.store(new FileWriter(path.toAbsolutePath().toString() + "/" + FILENAME), "created new properties file"); Logger.debug("Wrote New Config File [{}]", ConfigFile.getAbsolutePath()); } catch (IOException excp2) { diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/GameLogic.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/GameLogic.java index ab417c9..7778a95 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/GameLogic.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/GameLogic.java @@ -2,7 +2,7 @@ package net.halbear.Terrain4J.EngineCore.Logic; public interface GameLogic { void cleanup(); - void Initialise(EngineInstance engineInstance); + InitData Initialise(EngineInstance engineInstance); void Input(EngineInstance engineInstance, long FrameDiffNanoSeconds); void Update(EngineInstance engineInstance, long FrameDiffNanoSeconds); } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/InitData.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/InitData.java new file mode 100644 index 0000000..cabb4e7 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/InitData.java @@ -0,0 +1,8 @@ +package net.halbear.Terrain4J.EngineCore.Logic; + +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.ModelData; + +import java.util.List; + +public record InitData(List Models) { +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/VulkanContext.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/VulkanContext.java index ac1e4e6..cfd270c 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/VulkanContext.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/VulkanContext.java @@ -1,6 +1,7 @@ package net.halbear.Terrain4J.EngineCore.Logic; import net.halbear.Terrain4J.EngineCore.Display.Window; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.PipelineCache; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.Device; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.PhysicalDevice; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DisplayToScreen.Surface; @@ -15,6 +16,7 @@ public class VulkanContext { private Surface surface; private final PhysicalDevice PhysDevice; private SwapChain swapChain; + private final PipelineCache VkPipelineCache; public VulkanContext(Window window){ var EngineGlobals = EngineConfig.getInstance(); @@ -24,20 +26,21 @@ public class VulkanContext { surface = new Surface(Instance, PhysDevice, window); swapChain = new SwapChain(window, device, surface, EngineGlobals.GetRequestedImages(), EngineGlobals.GetVSYNC()); VulkanLayersOpen+=5; + VkPipelineCache = new PipelineCache(device); } public void cleanup(){ swapChain.cleanup(device); surface.cleanup(Instance); + VkPipelineCache.CleanUp(device); device.cleanup(); PhysDevice.cleanup(); Instance.cleanup(); Logger.debug("Render Thread Finished ending Vulkan Processes\nVulkan Layers Open:[{}]", VulkanContext.VulkanLayersOpen < 0 ? "All processes closed and vulkan instance removed" : VulkanContext.VulkanLayersOpen); VulkanLayersOpen--; } - public SwapChain GetSwapChain(){ - return swapChain; - } + public PipelineCache GetVkPipelineCache(){return VkPipelineCache;} + public SwapChain GetSwapChain(){return swapChain;} public Device GetDevice(){ return device; } 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 5d5ee70..1ce8fe7 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/GameCore.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/GameCore.java @@ -2,6 +2,12 @@ package net.halbear.Terrain4J.EngineCore.Main; import net.halbear.Terrain4J.EngineCore.Logic.EngineInstance; import net.halbear.Terrain4J.EngineCore.Logic.GameLogic; +import net.halbear.Terrain4J.EngineCore.Logic.InitData; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.MeshData; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.ModelData; + +import java.util.ArrayList; +import java.util.List; public class GameCore implements GameLogic { @Override @@ -10,8 +16,19 @@ public class GameCore implements GameLogic { } @Override - public void Initialise(EngineInstance engineInstance) { - + public InitData Initialise(EngineInstance engineInstance) { + var ModelID = "TriangleModel"; + MeshData meshData = new MeshData("triangle-mesh", new float[]{ + -0.5f, -0.5f, 0.0f, + 0.0f, 0.5f, 0.0f, + 0.5f, -0.5f, 0.0f}, + new int[]{0, 1, 2}); + List MeshDataList = new ArrayList<>(); + MeshDataList.add(meshData); + ModelData modelData = new ModelData(ModelID, MeshDataList); + List models = new ArrayList<>(); + models.add(modelData); + return new InitData(models); } @Override diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/PrimaryRuntime.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/PrimaryRuntime.java index bdf8731..7eecf2a 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/PrimaryRuntime.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/PrimaryRuntime.java @@ -1,10 +1,7 @@ package net.halbear.Terrain4J.EngineCore.Main; import net.halbear.Terrain4J.EngineCore.Display.Window; -import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; -import net.halbear.Terrain4J.EngineCore.Logic.EngineInstance; -import net.halbear.Terrain4J.EngineCore.Logic.GameLogic; -import net.halbear.Terrain4J.EngineCore.Logic.VulkanContext; +import net.halbear.Terrain4J.EngineCore.Logic.*; import net.halbear.Terrain4J.EngineCore.Threads.MainThread; import net.halbear.Terrain4J.EngineCore.Threads.RenderThread; import org.tinylog.Logger; @@ -24,9 +21,10 @@ public class PrimaryRuntime { public PrimaryRuntime(String windowTitle, GameLogic appLogic) { window = new Window(windowTitle); + InitData initData = appLogic.Initialise(engineInstance); engineInstance = new EngineInstance(window, new Scene(window)); PrimaryThread = new MainThread(engineInstance, appLogic); - DrawingThread = new RenderThread(window, engineInstance); + DrawingThread = new RenderThread(window, engineInstance, initData); Thread.currentThread().setName("Terrain4J Primary Runtime Thread"); } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/MainThread.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/MainThread.java index 90346b7..c2e3cd5 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/MainThread.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/MainThread.java @@ -4,6 +4,7 @@ import net.halbear.Terrain4J.EngineCore.Display.Window; import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; import net.halbear.Terrain4J.EngineCore.Logic.EngineInstance; import net.halbear.Terrain4J.EngineCore.Logic.GameLogic; +import net.halbear.Terrain4J.EngineCore.Logic.InitData; import net.halbear.Terrain4J.EngineCore.Main.PrimaryRuntime; import net.halbear.Terrain4J.EngineCore.Main.Scene; import org.tinylog.Logger; @@ -34,7 +35,6 @@ public class MainThread implements Runnable { } public MainThread(EngineInstance engineInstance, GameLogic appLogic){ this.gameLogic = appLogic; - gameLogic.Initialise(engineInstance); Logger.debug("Starting Main Thread"); UpdateTickRateThreshold(); mainThread = new Thread(this); diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/RenderThread.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/RenderThread.java index 2f5c377..3c636ac 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/RenderThread.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/RenderThread.java @@ -4,6 +4,7 @@ import net.halbear.Terrain4J.EngineCore.Display.Render; import net.halbear.Terrain4J.EngineCore.Display.Window; import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; import net.halbear.Terrain4J.EngineCore.Logic.EngineInstance; +import net.halbear.Terrain4J.EngineCore.Logic.InitData; import net.halbear.Terrain4J.EngineCore.Logic.VulkanContext; import net.halbear.Terrain4J.EngineCore.Main.PrimaryRuntime; import org.tinylog.Logger; @@ -15,9 +16,10 @@ public class RenderThread implements Runnable { private final Render render; private Window window; - public RenderThread(Window window,EngineInstance engineInstance) { + public RenderThread(Window window, EngineInstance engineInstance, InitData initData) { //this.window = window; render = new Render(engineInstance); + render.Initialise(initData); renderThread = new Thread(this); renderThread.setName("Terrain4J Vulkan Render Thread"); } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Pipeline.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Pipeline.java new file mode 100644 index 0000000..c68d82b --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Pipeline.java @@ -0,0 +1,108 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline; + +import net.halbear.Terrain4J.EngineCore.Logic.VulkanContext; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.ShaderModule; +import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.Device; +import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.*; +import org.tinylog.Logger; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.LongBuffer; + +import static org.lwjgl.vulkan.VK10.*; +import static org.lwjgl.vulkan.VK13.VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + +public class Pipeline { + private final long VulkanPipeline; + private final long VulkanPipelineLayout; + + public Pipeline(VulkanContext VkCtx, PipelineBuildInfo BuildInfo){ + Logger.debug("Creating Pipeline"); + Device device = VkCtx.GetDevice(); + try(var MemStack = MemoryStack.stackPush()){ + LongBuffer longPtr = MemStack.mallocLong(1); + ByteBuffer main = MemStack.UTF8("main"); + ShaderModule[] ShaderModules = BuildInfo.GetShaderModules(); + int ModuleCount = ShaderModules.length; + var ShaderStages = VkPipelineShaderStageCreateInfo.calloc(ModuleCount, MemStack); + for(int i = 0; i < ModuleCount; i++){ + ShaderModule shaderModule = ShaderModules[i]; + ShaderStages.get(i) + .sType$Default() + .stage(shaderModule.GetShaderStage()) + .module(shaderModule.GetHandle()) + .pName(main); + } + + var AssemblyStateCreateInfo = VkPipelineInputAssemblyStateCreateInfo.calloc(MemStack) + .sType$Default() + .topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + var ViewportCreateStateInfo = VkPipelineViewportStateCreateInfo.calloc(MemStack) + .sType$Default() + .viewportCount(1) + .scissorCount(1); + var RasterizationStateCreateInfo = VkPipelineRasterizationStateCreateInfo.calloc(MemStack) + .sType$Default() + .polygonMode(VK_POLYGON_MODE_FILL) + .cullMode(VK_CULL_MODE_NONE) + .frontFace(VK_FRONT_FACE_CLOCKWISE) + .lineWidth(1.0f); + var MultisampleStateCreateInfo = VkPipelineMultisampleStateCreateInfo.calloc(MemStack) + .sType$Default() + .rasterizationSamples(VK_SAMPLE_COUNT_1_BIT); + VkPipelineDynamicStateCreateInfo VulkanPipelineDynamicStateCreateInfo = VkPipelineDynamicStateCreateInfo.calloc(MemStack) + .sType(VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO) + .pDynamicStates(MemStack.ints( + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + )); + var BlendAttributeState = VkPipelineColorBlendAttachmentState.calloc(1,MemStack) + .colorWriteMask(VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT) + .blendEnable(false); + var ColourBlendState = VkPipelineColorBlendStateCreateInfo.calloc(MemStack) + .sType$Default() + .pAttachments(BlendAttributeState); + IntBuffer ColourFormats = MemStack.mallocInt(1); + ColourFormats.put(0,BuildInfo.GetColourFormat()); + var RendererCreateInfo = VkPipelineRenderingCreateInfo.calloc(MemStack) + .sType$Default() + .colorAttachmentCount(1) + .pColorAttachmentFormats(ColourFormats); + var PipelineLayoutCreateInfoPtr = VkPipelineLayoutCreateInfo.calloc(MemStack) + .sType$Default(); + VulkanUtils.vkCheck(vkCreatePipelineLayout(device.FetchVulkanDevice(), PipelineLayoutCreateInfoPtr,null,longPtr) + ,"Unable to create new pipeline layout"); + VulkanPipelineLayout = longPtr.get(0); + + var PipelineCreateInfo = VkGraphicsPipelineCreateInfo.calloc(1,MemStack) + .sType$Default() + .renderPass(VK_NULL_HANDLE) + .pStages(ShaderStages) + .pVertexInputState(BuildInfo.GetVertexInputStateCreateInfo()) + .pInputAssemblyState(AssemblyStateCreateInfo) + .pViewportState(ViewportCreateStateInfo) + .pRasterizationState(RasterizationStateCreateInfo) + .pColorBlendState(ColourBlendState) + .pMultisampleState(MultisampleStateCreateInfo) + .pDynamicState(VulkanPipelineDynamicStateCreateInfo) + .layout(VulkanPipelineLayout) + .pNext(RendererCreateInfo); + VulkanUtils.vkCheck(vkCreateGraphicsPipelines(device.FetchVulkanDevice(), + VkCtx.GetVkPipelineCache().GetVkPipelineCache(), PipelineCreateInfo, + null, longPtr),"Could not create new pipeline"); + VulkanPipeline = longPtr.get(0); + } + } + public long GetVulkanPipeline(){return VulkanPipeline;} + public long GetVulkanPipelineLayout(){return VulkanPipelineLayout;} + public void CleanUp(VulkanContext VkCtx){ + Logger.debug("destroying Pipeline"); + VkDevice vkDevice = VkCtx.GetDevice().FetchVulkanDevice(); + vkDestroyPipelineLayout(vkDevice,VulkanPipelineLayout,null); + vkDestroyPipeline(vkDevice,VulkanPipeline,null); + } + +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/PipelineBuildInfo.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/PipelineBuildInfo.java new file mode 100644 index 0000000..178e892 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/PipelineBuildInfo.java @@ -0,0 +1,18 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline; + +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.ShaderModule; +import org.lwjgl.vulkan.VkPipelineVertexInputStateCreateInfo; + +public class PipelineBuildInfo { + private final int ColourFormat; + private final ShaderModule[] ShaderModules; + private final VkPipelineVertexInputStateCreateInfo VertexInput; + public PipelineBuildInfo(ShaderModule[] shaderModules, VkPipelineVertexInputStateCreateInfo VertexInput, int ColourFormat){ + this.ColourFormat = ColourFormat; + this.ShaderModules = shaderModules; + this.VertexInput = VertexInput; + } + public int GetColourFormat(){return ColourFormat;} + public ShaderModule[] GetShaderModules(){return ShaderModules;} + public VkPipelineVertexInputStateCreateInfo GetVertexInputStateCreateInfo(){return VertexInput;} +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/PipelineCache.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/PipelineCache.java new file mode 100644 index 0000000..ff923ee --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/PipelineCache.java @@ -0,0 +1,32 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline; + +import net.halbear.Terrain4J.EngineCore.Logic.VulkanContext; +import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.Device; +import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkPipelineCacheCreateInfo; +import org.tinylog.Logger; + +import java.nio.LongBuffer; + +import static org.lwjgl.vulkan.VK13.vkCreatePipelineCache; +import static org.lwjgl.vulkan.VK13.vkDestroyPipelineCache; + +public class PipelineCache { + private final long VulkanPipelineCache; + public PipelineCache(Device device){ + Logger.debug("Creating new Vk Pipeline Cache"); + try(var MemStack = MemoryStack.stackPush()){ + var PipelineCacheCreateInfo = VkPipelineCacheCreateInfo.calloc(MemStack).sType$Default(); + LongBuffer longPtr = MemStack.mallocLong(1); + VulkanUtils.vkCheck(vkCreatePipelineCache(device.FetchVulkanDevice(), PipelineCacheCreateInfo + ,null, longPtr),"Error Creating New Pipeline Cache"); + VulkanPipelineCache = longPtr.get(0); + } + } + public void CleanUp(Device device){ + Logger.debug("Destroying Vulkan Pipeline Cache"); + vkDestroyPipelineCache(device.FetchVulkanDevice(), VulkanPipelineCache, null); + } + public long GetVkPipelineCache(){return VulkanPipelineCache;} +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Shader/ShaderCompiler.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Shader/ShaderCompiler.java new file mode 100644 index 0000000..ee6eff4 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Shader/ShaderCompiler.java @@ -0,0 +1,68 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader; + +import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; +import org.lwjgl.util.shaderc.Shaderc; +import org.tinylog.Logger; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; + +public class ShaderCompiler { + private ShaderCompiler(){} + + public static byte[] CompileShader(String ShaderCode, int ShaderType, boolean HLSLshader){ + long Compiler = 0; + long Options = 0; + byte[] CompiledShader; + try{ + Compiler = Shaderc.shaderc_compiler_initialize(); + Options = Shaderc.shaderc_compile_options_initialize(); + if(EngineConfig.getInstance().DebugShaders()){ + Shaderc.shaderc_compile_options_set_generate_debug_info(Options); + Shaderc.shaderc_compile_options_set_optimization_level(Options,0); + } + Shaderc.shaderc_compile_options_set_source_language(Options, HLSLshader ? Shaderc.shaderc_source_language_hlsl : Shaderc.shaderc_source_language_glsl); + long CompilationResult = Shaderc.shaderc_compile_into_spv( + Compiler, + ShaderCode, + ShaderType, + HLSLshader ? "shader.hlsl" : "shader.glsl", + "main", + Options + ); + if(Shaderc.shaderc_result_get_compilation_status(CompilationResult) != + Shaderc.shaderc_compilation_status_success){ + throw new RuntimeException("Failed to Compile Shader: " + Shaderc.shaderc_result_get_error_message(CompilationResult)); + } + ByteBuffer buffer = Shaderc.shaderc_result_get_bytes(CompilationResult); + assert buffer != null; + CompiledShader = new byte[buffer.remaining()]; + buffer.get(CompiledShader); + } finally{ + Shaderc.shaderc_compile_options_release(Options); + Shaderc.shaderc_compiler_release(Compiler); + } + return CompiledShader; + } + + public static void CompileGLSLShaderOnChange(String ShaderFile, int ShaderType){ + byte[] CompiledShader; + try{ + var glslFile = new File(ShaderFile); + var spvFile = new File(ShaderFile + ".spv"); + if(!spvFile.exists() || glslFile.lastModified() > spvFile.lastModified()){ + Logger.debug("Compiling new [{}] shader from [{}] source",spvFile.getPath(),glslFile.getPath()); + var ShaderCode = new String(Files.readAllBytes(glslFile.toPath())); + CompiledShader = CompileShader(ShaderCode, ShaderType, false); + Files.write(spvFile.toPath(), CompiledShader); + } else{ + Logger.debug("Loading Compiled Shader [{}]",spvFile.getPath()); + } + } catch(IOException exception){ + Logger.error(exception); + throw new RuntimeException(exception); + } + } +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Shader/ShaderModule.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Shader/ShaderModule.java new file mode 100644 index 0000000..2bd38c8 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Shader/ShaderModule.java @@ -0,0 +1,51 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader; + +import net.halbear.Terrain4J.EngineCore.Logic.VulkanContext; +import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkShaderModuleCreateInfo; +import static org.lwjgl.vulkan.VK13.vkCreateShaderModule; +import static org.lwjgl.vulkan.VK13.vkDestroyShaderModule; +import org.tinylog.Logger; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import java.nio.file.Files; + + +public class ShaderModule { + private final long Handle; + private final int ShaderStage; + + public ShaderModule(VulkanContext VkCtx, int ShaderStage, String ShaderSPVFile){ + try{ + byte[] Contents = Files.readAllBytes(new File(ShaderSPVFile).toPath()); + Handle = CreateShaderModule(VkCtx, Contents); + this.ShaderStage = ShaderStage; + } catch(IOException exception){ + Logger.error("Cannot Read Shader Files",exception); + throw new RuntimeException(exception); + } + } + + private static long CreateShaderModule(VulkanContext VkCtx, byte[] Code){ + try(var MemStack = MemoryStack.stackPush()){ + ByteBuffer CodePointer = MemStack.malloc(Code.length).put(0,Code); + var ShaderModuleCreateInfo = VkShaderModuleCreateInfo.calloc(MemStack) + .sType$Default() + .pCode(CodePointer); + LongBuffer LongPoiner = MemStack.mallocLong(1); + VulkanUtils.vkCheck(vkCreateShaderModule(VkCtx.GetDevice().FetchVulkanDevice(), ShaderModuleCreateInfo, null, LongPoiner),"Failed to create a new Shader Module"); + return LongPoiner.get(0); + } + } + + public void CleanUp(VulkanContext VkCtx){ + vkDestroyShaderModule(VkCtx.GetDevice().FetchVulkanDevice(), Handle, null); + } + + public long GetHandle(){return Handle;} + public int GetShaderStage(){return ShaderStage;} +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/DisplayToScreen/SceneRender.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/DisplayToScreen/SceneRender.java index a575f83..8334544 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/DisplayToScreen/SceneRender.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/DisplayToScreen/SceneRender.java @@ -3,12 +3,23 @@ package net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DisplayToScreen; import net.halbear.Terrain4J.EngineCore.Display.Render; import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; import net.halbear.Terrain4J.EngineCore.Logic.VulkanContext; +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.PipelineCache; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.ShaderCompiler; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.ShaderModule; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VertexBufferStructure; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.ModelsCache; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.VulkanMesh; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.VulkanModel; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.CommandBuffer; import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils; import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.shaderc.Shaderc; import org.lwjgl.vulkan.*; import org.tinylog.Logger; +import java.nio.LongBuffer; import java.util.Arrays; import static org.lwjgl.vulkan.KHRSwapchain.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; @@ -25,12 +36,40 @@ public class SceneRender { //dynamic rendering; private boolean Rb = false; private boolean Gb = true; private boolean Bb = false; + + private static final String FRAGMENT_SHADER_FILE_GLSL = "resources/shaders/scene_fragment.glsl"; + private static final String FRAGMENT_SHADER_FILE_SPV = FRAGMENT_SHADER_FILE_GLSL + ".spv"; + private static final String VERTEX_SHADER_FILE_GLSL = "resources/shaders/scene_vertex.glsl"; + private static final String VERTEX_SHADER_FILE_SPV = VERTEX_SHADER_FILE_GLSL + ".spv"; + private final Pipeline VkPipeline; + public SceneRender(VulkanContext vulkanContext){ ClearValueColour = VkClearValue.calloc().color(c->c.float32(0,R).float32(1,G).float32(2,B).float32(3,1.0f)); AttachmentInfoColour = CreateColourAttachmentsInfo(vulkanContext,ClearValueColour); RenderInfo = CreateRenderInfo(vulkanContext, AttachmentInfoColour); - } + ShaderModule[] shaderModules = SceneRender.CreateShaderModules(vulkanContext); + VkPipeline = CreatePipeline(vulkanContext, shaderModules); + Arrays.asList(shaderModules).forEach(shaderModule -> shaderModule.CleanUp(vulkanContext)); + } + private static ShaderModule[] CreateShaderModules(VulkanContext VkCtx){ + if(EngineConfig.getInstance().RecompileShaders()){ + ShaderCompiler.CompileGLSLShaderOnChange(VERTEX_SHADER_FILE_GLSL, Shaderc.shaderc_glsl_vertex_shader); + ShaderCompiler.CompileGLSLShaderOnChange(FRAGMENT_SHADER_FILE_GLSL, Shaderc.shaderc_fragment_shader); + } + return new ShaderModule[]{ + new ShaderModule(VkCtx, VK_SHADER_STAGE_VERTEX_BIT, VERTEX_SHADER_FILE_SPV), + new ShaderModule(VkCtx, VK_SHADER_STAGE_FRAGMENT_BIT, FRAGMENT_SHADER_FILE_SPV) + }; + } + private static Pipeline CreatePipeline(VulkanContext VkCtx, ShaderModule[] ShaderModules){ + var vertexBufferStructure = new VertexBufferStructure(); + var BuildInfo = new PipelineBuildInfo(ShaderModules,vertexBufferStructure.getVertexInput(), + VkCtx.GetSurface().GetSurfaceFormat().ImageFormat()); + var pipeline = new Pipeline(VkCtx, BuildInfo); + vertexBufferStructure.cleanup(); + return pipeline; + } public void UpdateColours(VulkanContext vulkanContext){ if (Gb){ if (G < 1.0f && Math.random() <0.5) G+=0.0005f; @@ -58,7 +97,7 @@ public class SceneRender { //dynamic rendering; RenderInfo = CreateRenderInfo(vulkanContext, AttachmentInfoColour); } - public void Render(VulkanContext vulkanContext, CommandBuffer commandBuffer, int ImageIndex){ + public void Render(VulkanContext vulkanContext, CommandBuffer commandBuffer,ModelsCache modelsCache, int ImageIndex){ UpdateColours(vulkanContext); try(var MemStack = MemoryStack.stackPush()){ SwapChain swapChain = vulkanContext.GetSwapChain(); @@ -70,6 +109,35 @@ public class SceneRender { //dynamic rendering; 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); vkCmdBeginRendering(CommandHandle, RenderInfo[ImageIndex]); + vkCmdBindPipeline(CommandHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, VkPipeline.GetVulkanPipeline()); + + VkExtent2D swapChainExtent = swapChain.GetSwapChainExtent(); + int width = swapChainExtent.width(); + int height = swapChainExtent.height(); + 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); + + LongBuffer offsets = MemStack.mallocLong(1).put(0,0L); + LongBuffer vertexBuffer = MemStack.mallocLong(1); + var VulkanModels = modelsCache.GetModelMap().values(); + for(VulkanModel VkModel : VulkanModels){ + for(VulkanMesh mesh : VkModel.GetVkMeshList()){ + vertexBuffer.put(0,mesh.VerticesBuffer().GetBuffer()); + vkCmdBindVertexBuffers(CommandHandle,0,vertexBuffer,offsets); + vkCmdBindIndexBuffer(CommandHandle,mesh.IndicesBuffer().GetBuffer(),0,VK_INDEX_TYPE_UINT32); + vkCmdDrawIndexed(CommandHandle,mesh.IndicesCount(),1,0,0,0); + } + } vkCmdEndRendering(CommandHandle); VulkanUtils.ImageBarrier(MemStack, CommandHandle, SwapChainImage, @@ -113,9 +181,11 @@ public class SceneRender { //dynamic rendering; } return result; } - public void cleanup(){ + public void cleanup(VulkanContext VkCtx){ + VkPipeline.CleanUp(VkCtx); Arrays.asList(RenderInfo).forEach(VkRenderingInfo::free); Arrays.asList(AttachmentInfoColour).forEach(VkRenderingAttachmentInfo.Buffer::free); ClearValueColour.free(); + } } diff --git a/terrain4j.properties b/terrain4j.properties index 778e029..e3b88aa 100644 --- a/terrain4j.properties +++ b/terrain4j.properties @@ -1,6 +1,13 @@ -#Default Window dimensions +#Application Properties +#Title that shows up on app window +Software_Title=Terrain4J Game Engine +Software_Icon=ProgramIcon.png +Software_Icon_Path=/WindowResources/Icon/ +#Launch Window dimensions display_height=480 display_width=640 + +#Engine Core Settings #the precision of frames, set this to 16000 and it will limit the whole engine to 64FPS frame_accuracy=0 #event tick rate for game process @@ -8,15 +15,16 @@ master_tick_rate=120 main_thread_tickrate=120 fast_thread_tickrate=240 cap_main_to_tickrate=false + +#Renderer Properties +Debug_Shaders = false +ShaderRecompiling = true #Vulkan Debugging toggle vkValidated=true - vsync=false +#Vsync images RequestedImages=3 #This is the name of the GPU you want the engine to use PhysicalDeviceName= -#Title that shows up on app window -Software_Title=Terrain4J Game Engine -Software_Icon=ProgramIcon.png -Software_Icon_Path=/WindowResources/Icon/ +