From 8ce30cae83633c4f456557cf0f5cc857204930bb Mon Sep 17 00:00:00 2001 From: Halbear Date: Thu, 11 Jun 2026 16:16:41 +0100 Subject: [PATCH] post processing, FXAA and MSAA 2x-8x --- .../multi-sampled_post_process_frag.glsl | 55 +++++ .../shaders/post_process_frag.glsl | 91 +++++++ .../shaders/post_process_vtx.glsl | 9 + .../shaders/scene_fragment.glsl | 6 +- .../shaders/scene_vertex.glsl | 0 .../EngineResources/shaders/swap_frag.glsl | 10 + .../EngineResources/shaders/swap_vtx.glsl | 8 + resources/models/Corvette/corvetteclass.png | Bin 98164 -> 65316 bytes .../Terrain4J/EngineCore/Display/Render.java | 50 +++- .../EngineCore/Input/MouseListener.java | 1 + .../EngineCore/Logic/EngineConfig.java | 50 ++++ .../EngineCore/Logic/EngineInstance.java | 7 +- .../Terrain4J/EngineCore/Logic/InitData.java | 3 +- .../EngineCore/Logic/InitVoxelData.java | 9 + .../Logic/Rendering/ModelCompiler.java | 2 +- .../Logic/Rendering/VulkanContext.java | 2 +- .../Terrain4J/EngineCore/Main/GameCore.java | 69 ++++-- .../EngineCore/Main/PrimaryRuntime.java | 25 +- .../EngineCore/Main/Util/Maths/T4Math.java | 1 + .../EngineCore/Threads/FastTickThread.java | 1 + .../EngineCore/Threads/MainThread.java | 2 +- .../EngineCore/Threads/RenderThread.java | 5 +- .../Rendering/EmptyVertexBufferStruct.java | 16 ++ .../Rendering/Pipeline/DefaultPipeline.java | 193 +++++++++++++++ .../Vulkan/Rendering/Pipeline/GraphUtils.java | 5 +- .../Rendering/Pipeline/Images/Attachment.java | 7 +- .../Rendering/Pipeline/Images/Image.java | 8 + .../Rendering/Pipeline/Images/Texture.java | 1 + .../Pipeline/Images/TextureCache.java | 51 ++-- .../Pipeline/IndexedLinkedHashMap.java | 8 + .../Vulkan/Rendering/Pipeline/Pipeline.java | 161 +------------ .../Rendering/Pipeline/VoxelPipeline.java | 174 ++++++++++++++ .../Rendering/PostProcessing/PostProcess.java | 219 +++++++++++++++++ .../SpecializationConstants.java | 39 +++ .../Vulkan/Rendering/Shader/ShaderModule.java | 8 +- .../VkModel/CompatibleVoxelMesh.java | 225 ++++++++++++++++++ .../Rendering/VkModel/MaterialsCache.java | 12 +- .../Vulkan/Rendering/VkModel/ModelsCache.java | 75 +++++- .../DisplayToScreen/SceneRender.java | 203 ++++++++-------- .../DisplayToScreen/SceneRenderer.java | 24 ++ .../Structure/DisplayToScreen/Surface.java | 5 +- .../SwapChain.java | 4 +- .../Structure/SwapChain/SwapChainRender.java | 192 +++++++++++++++ .../EngineCore/Vulkan/Util/VulkanBuffer.java | 21 -- .../EngineCore/Vulkan/Util/VulkanUtils.java | 27 +++ terrain4j.properties | 15 +- 46 files changed, 1749 insertions(+), 350 deletions(-) create mode 100644 resources/EngineResources/shaders/multi-sampled_post_process_frag.glsl create mode 100644 resources/EngineResources/shaders/post_process_frag.glsl create mode 100644 resources/EngineResources/shaders/post_process_vtx.glsl rename resources/{ => EngineResources}/shaders/scene_fragment.glsl (78%) rename resources/{ => EngineResources}/shaders/scene_vertex.glsl (100%) create mode 100644 resources/EngineResources/shaders/swap_frag.glsl create mode 100644 resources/EngineResources/shaders/swap_vtx.glsl create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Logic/InitVoxelData.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/EmptyVertexBufferStruct.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/DefaultPipeline.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/VoxelPipeline.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/PostProcessing/PostProcess.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/PostProcessing/SpecializationConstants.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/VkModel/CompatibleVoxelMesh.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/DisplayToScreen/SceneRenderer.java rename src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/{DisplayToScreen => SwapChain}/SwapChain.java (97%) create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/SwapChain/SwapChainRender.java diff --git a/resources/EngineResources/shaders/multi-sampled_post_process_frag.glsl b/resources/EngineResources/shaders/multi-sampled_post_process_frag.glsl new file mode 100644 index 0000000..03d4636 --- /dev/null +++ b/resources/EngineResources/shaders/multi-sampled_post_process_frag.glsl @@ -0,0 +1,55 @@ +#version 450 + +layout(constant_id = 0) const int USE_AA = 0; + +const float GAMMA_CONST = 0.4545; +const float SPAN_MAX = 8.0; +const float REDUCE_MIN = 1.0/128.0; +const float REDUCE_MUL = 1.0/32.0; + +layout(location = 0) in vec2 inTextCoord; +layout(location = 0) out vec4 outFragColor; + +layout(set = 0, binding = 0) uniform sampler2DMS inputTexture; + +layout(set = 1, binding = 0) uniform ScreenSize{ + vec2 size; +} screenSize; + +vec4 gamma(vec4 color){ + return color = vec4(pow(color.rgb,vec3(GAMMA_CONST)),color.a); +} + + +vec4 msaa(int sampleCount, sampler2DMS textureIn,vec2 TextCoord){ + ivec2 pixelCoords = ivec2(TextCoord * textureSize(textureIn)); + vec4 colorSum = vec4(0.0); + + for(int i = 0; i < sampleCount; ++i) { + vec4 sampleColor = texelFetch(textureIn, pixelCoords, i); + sampleColor.rgb = sampleColor.rgb / (sampleColor.rgb + vec3(1.0)); + colorSum += sampleColor; + } + return colorSum / float(sampleCount); +} + +void main(){ + ivec2 pixelCoords = ivec2(inTextCoord * textureSize(inputTexture)); + + if(USE_AA == 0){ + outFragColor = texelFetch(inputTexture,pixelCoords,0); + } + if(USE_AA == 1){ + outFragColor = texelFetch(inputTexture,pixelCoords,0); + } + if(USE_AA == 2){ + outFragColor = msaa(2,inputTexture,inTextCoord); + } + if(USE_AA == 3){ + outFragColor = msaa(4,inputTexture,inTextCoord); + } + if(USE_AA == 4){ + outFragColor = msaa(8,inputTexture,inTextCoord); + } + outFragColor = gamma(outFragColor); +} \ No newline at end of file diff --git a/resources/EngineResources/shaders/post_process_frag.glsl b/resources/EngineResources/shaders/post_process_frag.glsl new file mode 100644 index 0000000..df90574 --- /dev/null +++ b/resources/EngineResources/shaders/post_process_frag.glsl @@ -0,0 +1,91 @@ +#version 450 + +layout(constant_id = 0) const int USE_AA = 0; + +const float GAMMA_CONST = 0.4545; +const float SPAN_MAX = 8.0; +const float REDUCE_MIN = 1.0/128.0; +const float REDUCE_MUL = 1.0/32.0; + +layout(location = 0) in vec2 inTextCoord; +layout(location = 0) out vec4 outFragColor; + +layout(set = 0, binding = 0) uniform sampler2D inputTexture; + +layout(set = 1, binding = 0) uniform ScreenSize{ + vec2 size; +} screenSize; + +vec4 gamma(vec4 color){ + return color = vec4(pow(color.rgb,vec3(GAMMA_CONST)),color.a); +} + +// Sourced from: https://mini.gmshaders.com/p/gm-shaders-mini-fxaa + +vec4 fxaa(sampler2D tex, vec2 uv) { + vec2 u_texel = 1.0 / screenSize.size; + + //Sample center and 4 corners + vec3 rgbCC = texture(tex, uv).rgb; + vec3 rgb00 = texture(tex, uv+vec2(-0.5,-0.5)*u_texel).rgb; + vec3 rgb10 = texture(tex, uv+vec2(+0.5,-0.5)*u_texel).rgb; + vec3 rgb01 = texture(tex, uv+vec2(-0.5,+0.5)*u_texel).rgb; + vec3 rgb11 = texture(tex, uv+vec2(+0.5,+0.5)*u_texel).rgb; + + //Luma coefficients + const vec3 luma = vec3(0.299, 0.587, 0.114); + //Get luma from the 5 samples + float lumaCC = dot(rgbCC, luma); + float luma00 = dot(rgb00, luma); + float luma10 = dot(rgb10, luma); + float luma01 = dot(rgb01, luma); + float luma11 = dot(rgb11, luma); + + //Compute gradient from luma values + vec2 dir = vec2((luma01 + luma11) - (luma00 + luma10), (luma00 + luma01) - (luma10 + luma11)); + //Diminish dir length based on total luma + float dirReduce = max((luma00 + luma10 + luma01 + luma11) * REDUCE_MUL, REDUCE_MIN); + //Divide dir by the distance to nearest edge plus dirReduce + float rcpDir = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); + //Multiply by reciprocal and limit to pixel span + dir = clamp(dir * rcpDir, -SPAN_MAX, SPAN_MAX) * u_texel.xy; + + //Average middle texels along dir line + vec4 A = 0.5 * ( + texture(tex, uv - dir * (1.0/6.0)) + + texture(tex, uv + dir * (1.0/6.0))); + + //Average with outer texels along dir line + vec4 B = A * 0.5 + 0.25 * ( + texture(tex, uv - dir * (0.5)) + + texture(tex, uv + dir * (0.5))); + + + //Get lowest and highest luma values + float lumaMin = min(lumaCC, min(min(luma00, luma10), min(luma01, luma11))); + float lumaMax = max(lumaCC, max(max(luma00, luma10), max(luma01, luma11))); + + //Get average luma + float lumaB = dot(B.rgb, luma); + //If the average is outside the luma range, using the middle average + return ((lumaB < lumaMin) || (lumaB > lumaMax)) ? A : B; +} + +void main(){ + + if(USE_AA == 1){ + outFragColor = fxaa(inputTexture, inTextCoord); + outFragColor = gamma(outFragColor); + return; + } + if(USE_AA == 2){ + + } + if(USE_AA == 3){ + + } + if(USE_AA == 4){ + + } + outFragColor = gamma(texture(inputTexture,inTextCoord)); +} \ No newline at end of file diff --git a/resources/EngineResources/shaders/post_process_vtx.glsl b/resources/EngineResources/shaders/post_process_vtx.glsl new file mode 100644 index 0000000..bcc462a --- /dev/null +++ b/resources/EngineResources/shaders/post_process_vtx.glsl @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0 ) out vec2 outTextCoord; + +void main() +{ + outTextCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outTextCoord.x * 2.0f - 1.0f, outTextCoord.y * -2.0f + 1.0f,0.0f,1.0f); +} \ No newline at end of file diff --git a/resources/shaders/scene_fragment.glsl b/resources/EngineResources/shaders/scene_fragment.glsl similarity index 78% rename from resources/shaders/scene_fragment.glsl rename to resources/EngineResources/shaders/scene_fragment.glsl index 6b51322..a1387ad 100644 --- a/resources/shaders/scene_fragment.glsl +++ b/resources/EngineResources/shaders/scene_fragment.glsl @@ -1,6 +1,6 @@ #version 450 -const int MAX_TEXTURES = 100; +const int MAX_TEXTURES = 500; layout(location = 0) in vec2 inTextCoords; layout(location = 0) out vec4 outFragColor; @@ -26,7 +26,9 @@ void main() { Material material = matUniform.materials[push_constants.materialIdx]; if(material.hasTexture == 1){ - outFragColor = texture(textSampler[material.textureIdx],inTextCoords); + vec4 texColor = texture(textSampler[material.textureIdx],inTextCoords); + if(texColor.a < 0.1){discard;} + outFragColor = texColor; } else{ outFragColor = material.diffuseColor; } diff --git a/resources/shaders/scene_vertex.glsl b/resources/EngineResources/shaders/scene_vertex.glsl similarity index 100% rename from resources/shaders/scene_vertex.glsl rename to resources/EngineResources/shaders/scene_vertex.glsl diff --git a/resources/EngineResources/shaders/swap_frag.glsl b/resources/EngineResources/shaders/swap_frag.glsl new file mode 100644 index 0000000..4b36f7d --- /dev/null +++ b/resources/EngineResources/shaders/swap_frag.glsl @@ -0,0 +1,10 @@ +#version 450 +layout(location = 0) in vec2 inTextCoord; +layout(location = 0) out vec4 outFragColor; + +layout(set = 0, binding = 0) uniform sampler2D albedoSampler; + +void main() { + vec3 albedo = texture(albedoSampler, inTextCoord).rgb; + outFragColor = vec4(albedo,1.0); +} diff --git a/resources/EngineResources/shaders/swap_vtx.glsl b/resources/EngineResources/shaders/swap_vtx.glsl new file mode 100644 index 0000000..ebbaca0 --- /dev/null +++ b/resources/EngineResources/shaders/swap_vtx.glsl @@ -0,0 +1,8 @@ +#version 450 + +layout(location = 0) out vec2 outTextCoord; + +void main() { + outTextCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outTextCoord.x * 2.0f - 1.0f, outTextCoord.y * -2.0f + 1.0f,0.0f,1.0f); +} diff --git a/resources/models/Corvette/corvetteclass.png b/resources/models/Corvette/corvetteclass.png index 3256cf44da408cfc1cc6bd64807f5d124e841b68..0234a6227da39f9ea9202235e5cbbbc720541439 100644 GIT binary patch literal 65316 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

7iiaEY(7lwiVwrRunR>LTB~sRQ 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 literal 98164 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%0D&vu7qcV|KaTx440tGmMi~X%=YFaE9A4oR<11=@ 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%0D$DZw%Da5r*uDbGuG<7zkUXCnko%s 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

1qH%Vkv!C#(ui`_t{xGiXz5_XpRq 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 J{NnuE{{T Materials = new ArrayList<>(); + private List Models = new ArrayList<>(); + private List VoxelModels = new ArrayList<>(); private final MaterialsCache materialsCache; private TextureCache textureCache; + public TextureCache GetTextureCache(){return textureCache;} + public MaterialsCache GetMaterialsCache(){return materialsCache;} + public Render(EngineInstance engineInstance) { RendererContext = new VulkanContext(engineInstance.window()); CurrentFrame = 0; @@ -68,6 +76,8 @@ public class Render { RenderCompleteSemaphores[i] = new Semaphore(RendererContext); } sceneRender = new SceneRender(RendererContext); + PostProcessor = new PostProcess(RendererContext,sceneRender.GetAttachmentColour()); + swapChainRender = new SwapChainRender(RendererContext,PostProcessor.GetAttachment()); textureCache = new TextureCache(); materialsCache = new MaterialsCache(); modelsCache = new ModelsCache(); @@ -76,19 +86,27 @@ public class Render { public void Initialise(InitData initData){ - List Materials = initData.Materials(); + Materials.addAll(initData.Materials()); Logger.debug("Loading {} Materials", Materials.size()); + //materialsCache.CleanUp(RendererContext); materialsCache.LoadMaterials(RendererContext, Materials,textureCache, CommandPools[0], GraphicsQueue); Logger.debug("Loaded {} Materials", Materials.size()); Logger.debug("Transitioning Textures"); + //textureCache.CleanUp(RendererContext); textureCache.TransitionTexts(RendererContext, CommandPools[0], GraphicsQueue); Logger.debug("Transitioned Textures"); - List Models = initData.Models(); + Models.addAll(initData.Models()); Logger.debug("Loading {} models", Models.size()); + //modelsCache.CleanUp(RendererContext); modelsCache.loadModels(RendererContext, Models, CommandPools[0], GraphicsQueue); Logger.debug("Loaded {} models", Models.size()); + + VoxelModels.addAll(initData.voxelModels()); + Logger.debug("Loading {} Voxel models", VoxelModels.size()); + modelsCache.loadVoxelModels(RendererContext, VoxelModels, CommandPools[0], GraphicsQueue); + Logger.debug("Loaded {} Voxel models", VoxelModels.size()); sceneRender.LoadMaterials(RendererContext,materialsCache,textureCache); } @@ -105,6 +123,8 @@ public class Render { RendererContext.GetDevice().waitIdle(); Logger.debug("Waiting Vulkan Context"); sceneRender.cleanup(RendererContext); + PostProcessor.CleanUp(RendererContext); + swapChainRender.CleanUp(RendererContext); Logger.debug("Dynamic Renderer Cleaned up"); Arrays.asList(RenderCompleteSemaphores).forEach(i->i.cleanup(RendererContext)); Logger.debug("Render Semaphores cleaned up"); @@ -128,12 +148,18 @@ public class Render { var CommandPool = CommandPools[CurrentFrame]; var CommandBuffer = CommandBuffers[CurrentFrame]; RecordingStart(CommandPool, CommandBuffer); + + sceneRender.Render(engineInstance,RendererContext,CommandBuffer, modelsCache,materialsCache,CurrentFrame); + PostProcessor.Render(RendererContext,CommandBuffer,sceneRender.GetAttachmentColour()); + int ImageIndex;// = swapChain.FetchNextImage(RendererContext.GetDevice(), PresentCompleteSemaphores[CurrentFrame]); if (Resize || (ImageIndex = swapChain.FetchNextImage(RendererContext.GetDevice(), PresentCompleteSemaphores[CurrentFrame])) < 0){ resize(engineInstance); return; } - sceneRender.Render(engineInstance,RendererContext,CommandBuffer, modelsCache,materialsCache,ImageIndex,CurrentFrame); + + swapChainRender.Render(RendererContext,CommandBuffer,PostProcessor.GetAttachment(),ImageIndex); + RecordingStop(CommandBuffer); Submit(CommandBuffer, CurrentFrame, ImageIndex); Resize = swapChain.PresentImage(PresentQueue, RenderCompleteSemaphores[ImageIndex],ImageIndex); @@ -161,6 +187,8 @@ public class Render { VkExtent2D extend = RendererContext.GetSwapChain().GetSwapChainExtent(); engineInstance.scene().GetProjection().Resize(extend.width(),extend.height()); sceneRender.Resize(engineInstance,RendererContext); + PostProcessor.Resize(RendererContext,sceneRender.GetAttachmentColour()); + swapChainRender.Resize(RendererContext,PostProcessor.GetAttachment()); } private void ResetFence(int currentFrame){ diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Input/MouseListener.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Input/MouseListener.java index 9f679ba..dc112ea 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Input/MouseListener.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Input/MouseListener.java @@ -30,6 +30,7 @@ public class MouseListener { middleButtonPressed = button == GLFW_MOUSE_BUTTON_3 && action == GLFW_PRESS; }); } + public boolean IsWindowFocused(){return FocusWindow;} public Vector2f getCurrentPos() { return currentPos; } 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 493afb0..e0c1103 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/EngineConfig.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/EngineConfig.java @@ -8,6 +8,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Properties; +import static org.lwjgl.vulkan.VK10.*; + public class EngineConfig { public static final double DegToRad = Math.PI/180.0; public static final double RadToDeg = 180.0/Math.PI; @@ -51,6 +53,8 @@ public class EngineConfig { private float zNearPlane; private boolean GPU_SUPPORTS_LOGGING = true; private int MaxDescriptors = 1000; + private int AAValue = 1; + private AntiAliasType AAMode = AntiAliasType.FXAA; public void GPU_Does_Not_Support_Logging(){ GPU_SUPPORTS_LOGGING = false; @@ -58,6 +62,32 @@ public class EngineConfig { public boolean doesGPUSupportLogging(){return GPU_SUPPORTS_LOGGING;} public String GetDefaultTexturePath(){return DefaultTexturePath;} public int GetMaxDescriptors(){return MaxDescriptors;} + public int RenderAA(){return AAValue;} + + public enum AntiAliasType{ + NONE, + FXAA, + MSAAx2, + MSAAx4, + MSAAx8 + } + + public int GetRenderSampleCount(){ + switch(AAMode){ + case MSAAx2: + return VK_SAMPLE_COUNT_2_BIT; + case MSAAx4: + return VK_SAMPLE_COUNT_4_BIT; + case MSAAx8: + return VK_SAMPLE_COUNT_8_BIT; + case FXAA: + return VK_SAMPLE_COUNT_1_BIT; + case NONE: + return VK_SAMPLE_COUNT_1_BIT; + default: + return VK_SAMPLE_COUNT_1_BIT; + } + } private EngineConfig() { var EngineConfigVar = new Properties(); @@ -120,6 +150,25 @@ public class EngineConfig { FOV = (float)(DegToRad * Float.parseFloat(EngineConfigVar.getOrDefault("field_of_view", 60.0f).toString())); zNearPlane = (Float.parseFloat(EngineConfigVar.getOrDefault("z_near_plane", 1.0f).toString())); zFarPlane = (Float.parseFloat(EngineConfigVar.getOrDefault("z_far_plane", 100.0f).toString())); + AAValue = (Integer.parseInt(EngineConfigVar.getOrDefault("anti_alias_mode", 1).toString())); + switch(AAValue){ + case 0: + AAMode = AntiAliasType.NONE; + break; + case 2: + AAMode = AntiAliasType.MSAAx2; + break; + case 3: + AAMode = AntiAliasType.MSAAx4; + break; + case 4: + AAMode = AntiAliasType.MSAAx8; + break; + case 1: + default: + AAMode = AntiAliasType.FXAA; + break; + } DefaultTexturePath = EngineConfigVar.getOrDefault("DefaultTexturePath","/EngineResources/Texture/DefaultTexture.png").toString(); Logger.debug("\n\nsuccessfully loaded configuration: \n{}\n\n",EngineConfigVar.toString()); } @@ -149,6 +198,7 @@ public class EngineConfig { EngineConfigVar.setProperty("z_far_plane","100"); EngineConfigVar.setProperty("DefaultTexturePath",DefaultTexturePath); EngineConfigVar.setProperty("MaxDescriptors","1000"); + EngineConfigVar.setProperty("anti_alias_mode","1"); 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/EngineInstance.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/EngineInstance.java index 87ab35c..569d824 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/EngineInstance.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/EngineInstance.java @@ -1,9 +1,14 @@ package net.halbear.Terrain4J.EngineCore.Logic; +import net.halbear.Terrain4J.EngineCore.Display.Render; +import net.halbear.Terrain4J.EngineCore.Main.PrimaryRuntime; import net.halbear.Terrain4J.EngineCore.Main.Scene.Scene; import net.halbear.Terrain4J.EngineCore.Display.Window; +import net.halbear.Terrain4J.EngineCore.Threads.FastTickThread; +import net.halbear.Terrain4J.EngineCore.Threads.MainThread; +import net.halbear.Terrain4J.EngineCore.Threads.RenderThread; -public record EngineInstance(Window window, Scene scene) { +public record EngineInstance(Window window, Scene scene, PrimaryRuntime PrimaryThread) { public void cleanup(){ window.cleanup(); } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/InitData.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/InitData.java index 0e44d94..9806620 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/InitData.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/InitData.java @@ -2,8 +2,9 @@ package net.halbear.Terrain4J.EngineCore.Logic; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.MaterialData; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.ModelData; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.CompatibleVoxelMesh; import java.util.List; -public record InitData(List Models, List Materials) { +public record InitData(List Models, List voxelModels, List Materials) { } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/InitVoxelData.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/InitVoxelData.java new file mode 100644 index 0000000..7f4bffa --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/InitVoxelData.java @@ -0,0 +1,9 @@ +package net.halbear.Terrain4J.EngineCore.Logic; + +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.MaterialData; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.CompatibleVoxelMesh; + +import java.util.List; + +public record InitVoxelData(List Models, List Materials) { +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/Rendering/ModelCompiler.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/Rendering/ModelCompiler.java index d72bdac..5ce37f8 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/Rendering/ModelCompiler.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/Rendering/ModelCompiler.java @@ -208,7 +208,7 @@ public class ModelCompiler { int EmbeddedTextureIndex = matcher.matches() && matcher.groupCount() > 0 ? Integer.parseInt(matcher.group(1)) : -1; if (EmbeddedTextureIndex >= 0 && EmbeddedTextureIndex < EmbeddedTextureCount){ var aiTexture = AITexture.create(aiScene.mTextures().get(EmbeddedTextureIndex)); - String BaseFile = aiTexture.mFilename().dataString() + ".png"; + String BaseFile = aiTexture.mFilename().dataString() + "ModelTexture" +EmbeddedTextureIndex + ".png"; TexturePath = BaseDirectory + File.separator + BaseFile; Logger.info("Dumping Texture File to [{}]",TexturePath); var Channel = FileChannel.open(Path.of(TexturePath), StandardOpenOption.CREATE,StandardOpenOption.WRITE); diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/Rendering/VulkanContext.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/Rendering/VulkanContext.java index f67460d..27d542f 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/Rendering/VulkanContext.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/Rendering/VulkanContext.java @@ -7,7 +7,7 @@ import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.DescriptorAlloca 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; -import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DisplayToScreen.SwapChain; +import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.SwapChain.SwapChain; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.VulkanInstance; import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanMemoryAllocator; import org.tinylog.Logger; 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 21e8ce7..9e46529 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/GameCore.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/GameCore.java @@ -3,16 +3,15 @@ package net.halbear.Terrain4J.EngineCore.Main; import net.halbear.Terrain4J.EngineCore.Display.Window; import net.halbear.Terrain4J.EngineCore.Input.KeyboardInput; import net.halbear.Terrain4J.EngineCore.Input.MouseListener; -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.Logic.*; import net.halbear.Terrain4J.EngineCore.Logic.Rendering.ModelLoader; import net.halbear.Terrain4J.EngineCore.Main.Scene.Actor; import net.halbear.Terrain4J.EngineCore.Main.Scene.Camera; import net.halbear.Terrain4J.EngineCore.Main.Scene.Scene; +import net.halbear.Terrain4J.EngineCore.Main.Util.Maths.T4Math; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.MaterialData; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.ModelData; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.CompatibleVoxelMesh; import org.joml.Vector2f; import org.joml.Vector3f; import org.tinylog.Logger; @@ -33,6 +32,8 @@ public class GameCore implements GameLogic { private List Velocities = new ArrayList<>(); private Actor CubeActor; private List CubeActors = new ArrayList<>(); + private Vector2f LastMousePos = new Vector2f(0,0); + private int Ticks = 0; @Override public void cleanup() { @@ -41,50 +42,80 @@ public class GameCore implements GameLogic { @Override public InitData Initialise(EngineInstance engineInstance) { + + CompatibleVoxelMesh GrassCube = new CompatibleVoxelMesh("GrassBlock") + .CreateMeshFace(CompatibleVoxelMesh.FaceDirection.North, "resources/EngineResources/Texture/VoxelTextures/VoxelTexture1.png") + .CreateMeshFace(CompatibleVoxelMesh.FaceDirection.East, "resources/EngineResources/Texture/VoxelTextures/VoxelTexture14.png") + .CreateMeshFace(CompatibleVoxelMesh.FaceDirection.South, "resources/EngineResources/Texture/VoxelTextures/VoxelTexture13.png") + .CreateMeshFace(CompatibleVoxelMesh.FaceDirection.West, "resources/EngineResources/Texture/VoxelTextures/VoxelTexture12.png") + .CreateMeshFace(CompatibleVoxelMesh.FaceDirection.Up, "resources/EngineResources/Texture/VoxelTextures/VoxelTexture15.png") + .CreateMeshFace(CompatibleVoxelMesh.FaceDirection.Down, "resources/EngineResources/Texture/VoxelTextures/VoxelTexture0.png").CompileMesh(); + + InitVoxelData Voxels = CompatibleVoxelMesh.GetVoxelModelsGenerated(); + List VoxelModels = Voxels.Models(); + for(int i = 0; i < VoxelModels.size(); i++){ + Logger.debug("Voxel -> [{}]",VoxelModels.get(i).ID()); + } Scene scene = engineInstance.scene(); List models = new ArrayList<>(); + //ModelData SponzaData = ModelLoader.LoadModel("resources/models/Unit02/evangelion_unit-02.json"); + //List SponzaMaterial = ModelLoader.LoadMaterials("resources/models/Unit02/evangelion_unit-02_mat.json"); ModelData SponzaData = ModelLoader.LoadModel("resources/models/Sponza/Sponza.json"); List SponzaMaterial = ModelLoader.LoadMaterials("resources/models/Sponza/Sponza_mat.json"); scene.AddActor(new Actor("Sponza", SponzaData.ID(), new Vector3f(0,0,-5f))); models.add(ModelLoader.LoadModel("resources/models/Metro/OBJ/MetroLeadCar.json")); - models.add(ModelLoader.LoadModel("resources/models/Corvette/CorvetteOBJ.json")); - //models.add(ModelLoader.LoadModel("resources/models/Sloop/SloopOBJ.json")); models.add(ModelLoader.LoadModel("resources/models/melona/melona.json")); models.add(ModelLoader.LoadModel("resources/models/Alice/Welsh040Alice.json")); + models.add(ModelLoader.LoadModel("resources/models/Sloop/SloopOBJ.json")); + models.add(ModelLoader.LoadModel("resources/models/Corvette/corvetteclass.json")); for(int i = 0; i < 50000; i++) { - CubeActors.add(new Actor("CubeEntity" + i, models.get((int)Math.round(Math.min(Math.max((Math.random() * models.size() - 1),0),models.size() - 1))).ID(), new Vector3f((float)(-200 + Math.random() * 400), (float)(-50 + Math.random() * 100), (float)(250 + Math.random() * -500)))); + CubeActors.add(new Actor("AdvancedModel" + i, models.get((int)Math.round(Math.min(Math.max(Math.random() *(Math.random() * models.size() - 1),0),models.size() - 1))).ID(), new Vector3f((float)(-200 + Math.random() * 400), (float)(-50 + Math.random() * 100), (float)(250 + Math.random() * -500)))); angles.add((float)(Math.random() * 360)); rotatingAngles.add(new Vector3f((float)(Math.random()*2), (float)(Math.random()*2), (float)(Math.random()*2))); - Velocities.add(new Vector3f((float)(Math.random()*2), (float)(Math.random()*2), (float)(Math.random()*2))); + Velocities.add(new Vector3f((float)(-2 + Math.random()*4), -2 + (float)(Math.random()*4), -2 + (float)(Math.random()*4))); } CubeActors.forEach(scene::AddActor); List materials = new ArrayList<>(); materials.addAll(ModelLoader.LoadMaterials("resources/models/Metro/OBJ/MetroLeadCar_mat.json")); - materials.addAll(ModelLoader.LoadMaterials("resources/models/Corvette/CorvetteOBJ_mat.json")); - materials.addAll(ModelLoader.LoadMaterials("resources/models/Sloop/SloopOBJ_mat.json")); + materials.addAll(ModelLoader.LoadMaterials("resources/models/Corvette/corvetteclass_mat.json")); materials.addAll(ModelLoader.LoadMaterials("resources/models/melona/melona_mat.json")); materials.addAll(ModelLoader.LoadMaterials("resources/models/Alice/Welsh040Alice_mat.json")); + materials.addAll(ModelLoader.LoadMaterials("resources/models/Sloop/SloopOBJ_mat.json")); materials.addAll(SponzaMaterial); + materials.addAll(Voxels.Materials()); models.add(SponzaData); Camera camera = scene.GetCamera(); - camera.SetPosition(0.0f, 0.0f, 0.0f); - camera.SetRotation((float) Math.toRadians(0.0f), (float) Math.toRadians(0.0f),0); - return new InitData(models,materials); + camera.SetPosition(40.0f, 155.0f, -42.0f); + camera.SetPosition(0,0,0); + camera.SetRotation((float) Math.toRadians(10.0f), (float) Math.toRadians(-90.0f),0); + camera.SetRotation(0,0,0); + return new InitData(models,VoxelModels,materials); } @Override public void CameraInput(EngineInstance engineInstance, long FrameDiffNanoSeconds) { + float Multiplier = MOUSE_SENSITIVITY; Scene scene = engineInstance.scene(); Window window = engineInstance.window(); Camera camera = scene.GetCamera(); MouseListener MouseInput = window.getMouseInput(); + Vector2f CurrentMousePos = window.getMouseInput().getCurrentPos(); + Vector2f deltaPos = new Vector2f(0,0); + deltaPos.x = 0; + deltaPos.y = 0; + if (LastMousePos.x >= 0 && LastMousePos.y >= 0 && window.getMouseInput().IsWindowFocused()) { + deltaPos.x = CurrentMousePos.x - LastMousePos.x; + deltaPos.y = CurrentMousePos.y - LastMousePos.y; + } + LastMousePos.x = CurrentMousePos.x; + LastMousePos.y = CurrentMousePos.y; if(MouseInput.isRightButtonPressed()){ - Vector2f deltaPos = MouseInput.getDeltaPos(); - camera.AddRotation((float)Math.toRadians(-deltaPos.y * MOUSE_SENSITIVITY),(float)Math.toRadians(-deltaPos.x * MOUSE_SENSITIVITY),0); + + camera.AddRotation((float)Math.toRadians(-deltaPos.y * Multiplier),(float)Math.toRadians(-deltaPos.x * Multiplier),0); } } @@ -122,9 +153,13 @@ public class GameCore implements GameLogic { for(int i = 0; i < angles.size()/2; i++) { angles.set(i, (angles.get(i) + 0.1f* DeltaTime) % 360); CubeActors.get(i).GetRotation().identity().rotateAxis((float) EngineConfig.DegToRad * angles.get(i), rotatingAngles.get(i)); - //CubeActors.get(i).GetPosition().add((Velocities.get(i).x/100.0f) * DeltaTime,(Velocities.get(i).y/100.0f) * DeltaTime,(Velocities.get(i).z/100.0f) * DeltaTime); + CubeActors.get(i).GetPosition().add((Velocities.get(i).x/100.0f) * DeltaTime,(Velocities.get(i).y/100.0f) * DeltaTime,(Velocities.get(i).z/100.0f) * DeltaTime); CubeActors.get(i).UpdateModelMatrix(); } + Ticks++; + if(Ticks == 1000){ + + } } @Override @@ -133,7 +168,7 @@ public class GameCore implements GameLogic { for(int i = angles.size()/2; i < angles.size(); i++) { angles.set(i, (angles.get(i) + 1.0f* DeltaTime) % 360); CubeActors.get(i).GetRotation().identity().rotateAxis((float) EngineConfig.DegToRad * angles.get(i), rotatingAngles.get(i)); - // CubeActors.get(i).GetPosition().add((Velocities.get(i).x/100.0f) * DeltaTime,(Velocities.get(i).y/100.0f) * DeltaTime,(Velocities.get(i).z/100.0f) * DeltaTime); + CubeActors.get(i).GetPosition().add((Velocities.get(i).x/100.0f) * DeltaTime,(Velocities.get(i).y/100.0f) * DeltaTime,(Velocities.get(i).z/100.0f) * DeltaTime); CubeActors.get(i).UpdateModelMatrix(); } } 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 bcef070..7511a9b 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/PrimaryRuntime.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/PrimaryRuntime.java @@ -10,6 +10,11 @@ import net.halbear.Terrain4J.EngineCore.Threads.MainThread; import net.halbear.Terrain4J.EngineCore.Threads.RenderThread; import org.tinylog.Logger; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + public class PrimaryRuntime { private int Frames = 0; private int TickFrames = 0; @@ -30,9 +35,23 @@ public class PrimaryRuntime { private static GameLogic gameLogic; public PrimaryRuntime(String windowTitle, GameLogic appLogic) { + String[] knownDLSSfiles = {"nvngx_dlss.dll", "nvngx_dlssg.dll", "_nvngx.dll"}; + for (String dllName : knownDLSSfiles) { + Path dllPath = Paths.get(System.getProperty("user.dir"), dllName); + if (Files.exists(dllPath)) { + try { + Logger.warn("DLSS is not supported, and is not allowed to be used with Terrain4J: [{}]",dllName); + Files.delete(dllPath); + Logger.info("removed local DLSS hook."); + } catch (IOException e) { + Logger.error("Could not remove DLSS DLL. Exiting to prevent pipeline corruption."); + System.exit(1); + } + } + } window = new Window(windowTitle); gameLogic = appLogic; - engineInstance = new EngineInstance(window, new Scene(window)); + engineInstance = new EngineInstance(window, new Scene(window),this); InitData initData = appLogic.Initialise(engineInstance); PrimaryThread = new MainThread(engineInstance, appLogic); FastThread = new FastTickThread(engineInstance, appLogic); @@ -40,6 +59,9 @@ public class PrimaryRuntime { cpuProfiler = new CPUMonitor(); Thread.currentThread().setName("Terrain4J Primary Runtime Thread"); } + public static MainThread GetMainThread(){return PrimaryThread;} + public static RenderThread GetRenderThread(){return DrawingThread;} + public static FastTickThread GetFastThread(){return FastThread;} public static void UpdateFrameAccuracy(){ if(EngineConfig.getInstance().IsEngineThrottled()){ Logger.debug("ThrottlingEngine"); @@ -101,7 +123,6 @@ public class PrimaryRuntime { EngineConfig.FrameRate_PRIMARYTHREAD = Frames; EngineConfig.TPS = TickFrames; Logger.info("Primary Thread FPS: " + Frames + " TPS: " + TickFrames + " Window Draw FPS: " + EngineConfig.RENDERFRAMES); - Logger.info(cpuProfiler.LogCPUUsage() + "\t"); Frames = 0; TickFrames = 0; EngineConfig.RENDERFRAMES = 0; diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Util/Maths/T4Math.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Util/Maths/T4Math.java index fe9572b..2682f1d 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Util/Maths/T4Math.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/Util/Maths/T4Math.java @@ -4,4 +4,5 @@ public class T4Math { public static double Log2(int n){ return Math.log(n) / Math.log(n); } + public static double Square(double n){return n*n;} } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/FastTickThread.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/FastTickThread.java index c4c062f..5080445 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/FastTickThread.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/FastTickThread.java @@ -15,6 +15,7 @@ public class FastTickThread extends EngineThread { @Override public void TickEvent(long nsTimeDiff){ + gameLogic.Input(PrimaryRuntime.GetEngineInstance(), (nsTimeDiff)); gameLogic.UpdateFastThread(PrimaryRuntime.GetEngineInstance(), nsTimeDiff); } @Override 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 4dff1eb..b9c38e6 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/MainThread.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/MainThread.java @@ -10,7 +10,7 @@ public class MainThread extends EngineThread { @Override public void TickEvent(long nsTimeDiff){ - gameLogic.Update(PrimaryRuntime.GetEngineInstance(), nsTimeDiff); + } @Override public void FrameEvent(long now, long InitialTime){ 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 5d43670..b55d620 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/RenderThread.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Threads/RenderThread.java @@ -18,13 +18,14 @@ public class RenderThread extends EngineThread { render.Initialise(initData); } + public Render GetRenderer(){return render;} + @Override public void TickEvent(long nsTimeDiff){ - } @Override public void FrameEvent(long now, long InitialTime){ - gameLogic.Input(PrimaryRuntime.GetEngineInstance(), (now - InitialTime)); + gameLogic.Update(PrimaryRuntime.GetEngineInstance(), (now - InitialTime)); render.render(PrimaryRuntime.GetEngineInstance()); } @Override diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/EmptyVertexBufferStruct.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/EmptyVertexBufferStruct.java new file mode 100644 index 0000000..c4a2fc0 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/EmptyVertexBufferStruct.java @@ -0,0 +1,16 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering; + +import org.lwjgl.vulkan.VkPipelineVertexInputStateCreateInfo; + +public class EmptyVertexBufferStruct { + + private final VkPipelineVertexInputStateCreateInfo VertexInput; + + public EmptyVertexBufferStruct(){ + VertexInput = VkPipelineVertexInputStateCreateInfo.calloc(); + VertexInput.sType$Default(); + } + + public void CleanUp(){VertexInput.free();} + public VkPipelineVertexInputStateCreateInfo GetVertexInput(){return VertexInput;} +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/DefaultPipeline.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/DefaultPipeline.java new file mode 100644 index 0000000..bd6b1b5 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/DefaultPipeline.java @@ -0,0 +1,193 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline; + +import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; +import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.DescriptorSetLayout; +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.VK13.*; +import static org.lwjgl.vulkan.VK13.VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; +import static org.lwjgl.vulkan.VK13.VK_BLEND_FACTOR_SRC_ALPHA; +import static org.lwjgl.vulkan.VK13.VK_BLEND_FACTOR_ZERO; +import static org.lwjgl.vulkan.VK13.VK_BLEND_OP_ADD; +import static org.lwjgl.vulkan.VK13.VK_COLOR_COMPONENT_A_BIT; +import static org.lwjgl.vulkan.VK13.VK_COLOR_COMPONENT_B_BIT; +import static org.lwjgl.vulkan.VK13.VK_COLOR_COMPONENT_G_BIT; +import static org.lwjgl.vulkan.VK13.VK_COLOR_COMPONENT_R_BIT; +import static org.lwjgl.vulkan.VK13.VK_CULL_MODE_NONE; +import static org.lwjgl.vulkan.VK13.VK_DYNAMIC_STATE_SCISSOR; +import static org.lwjgl.vulkan.VK13.VK_DYNAMIC_STATE_VIEWPORT; +import static org.lwjgl.vulkan.VK13.VK_FORMAT_UNDEFINED; +import static org.lwjgl.vulkan.VK13.VK_FRONT_FACE_CLOCKWISE; +import static org.lwjgl.vulkan.VK13.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK13.VK_SAMPLE_COUNT_1_BIT; +import static org.lwjgl.vulkan.VK13.VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; +import static org.lwjgl.vulkan.VK13.vkCreateGraphicsPipelines; +import static org.lwjgl.vulkan.VK13.vkCreatePipelineLayout; +import static org.lwjgl.vulkan.VK13.vkDestroyPipeline; +import static org.lwjgl.vulkan.VK13.vkDestroyPipelineLayout; + +public class DefaultPipeline implements Pipeline { + private final long VulkanPipeline; + private final long VulkanPipelineLayout; + + public DefaultPipeline(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); + if(shaderModule.GetSpecializationInfo() != null){ + ShaderStages.get(i).pSpecializationInfo(shaderModule.GetSpecializationInfo()); + } + } + + 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(VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO) + .polygonMode(VK_POLYGON_MODE_FILL) + .cullMode(VK_CULL_MODE_NONE) + .frontFace(VK_FRONT_FACE_CLOCKWISE) + .lineWidth(1.0f); + int sampleCount = EngineConfig.getInstance().GetRenderSampleCount(); + var MultisampleStateCreateInfo = VkPipelineMultisampleStateCreateInfo.calloc(MemStack) + .sType$Default() + .rasterizationSamples(sampleCount) + .sampleShadingEnable(false) + .minSampleShading(1.0f) + .pSampleMask(null) + .alphaToCoverageEnable(false) + .alphaToOneEnable(false); + + VkPipelineDepthStencilStateCreateInfo DepthStencil = null; + if(BuildInfo.GetDepthFormat() != VK_FORMAT_UNDEFINED){ + DepthStencil = VkPipelineDepthStencilStateCreateInfo.calloc(MemStack) + .sType(VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO) + .depthTestEnable(true) + .depthWriteEnable(true) + .depthCompareOp(VK_COMPARE_OP_GREATER_OR_EQUAL) + .depthBoundsTestEnable(false) + .stencilTestEnable(false); + } + + + VkPipelineDynamicStateCreateInfo VulkanPipelineDynamicStateCreateInfo = VkPipelineDynamicStateCreateInfo.calloc(MemStack) + .sType$Default() + .pDynamicStates(MemStack.ints( + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + )); + + + VkPushConstantRange.Buffer VkPushConstRangeBuffer = null; + PushConstantsRange[] PushConstRanges = BuildInfo.GetPushConstantRanges(); + int PushConstCount = PushConstRanges != null ? PushConstRanges.length != 0 ? PushConstRanges.length : 0 : 0; + if(PushConstCount > 0){ + VkPushConstRangeBuffer = VkPushConstantRange.calloc(PushConstCount,MemStack); + for(int i = 0; i < PushConstCount; i++){ + PushConstantsRange pushConstantsRange = PushConstRanges[i]; + VkPushConstRangeBuffer.get(i) + .stageFlags(pushConstantsRange.Stage()) + .offset(pushConstantsRange.Offset()) + .size(pushConstantsRange.Size()); + } + } + DescriptorSetLayout[] descriptorSetLayouts = BuildInfo.GetDescriptorSetLayouts(); + int LayoutCount = descriptorSetLayouts != null ? descriptorSetLayouts.length : 0; + LongBuffer ppLayout = MemStack.mallocLong(LayoutCount); + for(int i = 0; i < LayoutCount; i++){ + ppLayout.put(i, descriptorSetLayouts[i].GetVkDescriptorLayout()); + } + 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(BuildInfo.BlendingUsed()); + if(BuildInfo.BlendingUsed()){ + BlendAttributeState.get(0).colorBlendOp(VK_BLEND_OP_ADD) + .alphaBlendOp(VK_BLEND_OP_ADD) + .srcColorBlendFactor(VK_BLEND_FACTOR_SRC_ALPHA) + .dstColorBlendFactor(VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA) + .srcAlphaBlendFactor(VK_BLEND_FACTOR_ONE) + .dstAlphaBlendFactor(VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA); + } + + 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); + + if(DepthStencil != null){RendererCreateInfo.depthAttachmentFormat(BuildInfo.GetDepthFormat());} + + var PipelineLayoutCreateInfoPtr = VkPipelineLayoutCreateInfo.calloc(MemStack) + .sType$Default() + .pSetLayouts(ppLayout) + .pPushConstantRanges(VkPushConstRangeBuffer); + + 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); + + + if(DepthStencil != null){PipelineCreateInfo.pDepthStencilState(DepthStencil);} + + 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/GraphUtils.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/GraphUtils.java index a5d4664..c2c15ad 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/GraphUtils.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/GraphUtils.java @@ -1,6 +1,7 @@ package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline; import net.halbear.Terrain4J.EngineCore.Display.GLFWImageParser; +import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.ImageSrc; import org.lwjgl.system.MemoryStack; import org.tinylog.Logger; @@ -29,8 +30,8 @@ public class GraphUtils { IntBuffer height = MemStack.mallocInt(1); image = stbi_load(Filename, width, height, channels, 4); if (image == null) { - Logger.error("Could not load or find image inputted."); - throw new IOException("Image file [" + Filename + "] not loaded: " + stbi_failure_reason()); + Logger.error("Could not load or find image inputted. Image file [" + Filename + "] not loaded: " + stbi_failure_reason()); + image = stbi_load(EngineConfig.getInstance().GetDefaultTexturePath(), width, height, channels, 4); } newImage = new ImageSrc(image, width.get(0), height.get(0), channels.get(0)); } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/Attachment.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/Attachment.java index 980f3c2..573efd1 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/Attachment.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/Attachment.java @@ -1,5 +1,6 @@ package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images; +import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DisplayToScreen.ImageView; @@ -11,8 +12,8 @@ public class Attachment { private boolean DepthAttachment; public Attachment(VulkanContext VkCtx, int Width, int Height, int Format, int Usage){ - var ImageData = new Image.ImageData().Width(Width).Height(Height).Format(Format) - .Usage(Usage | VK_IMAGE_USAGE_SAMPLED_BIT).MemoryUsage(VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT); + var ImageData = new Image.ImageData().Width(Width).Height(Height) + .Usage(Usage | VK_IMAGE_USAGE_SAMPLED_BIT).Format(Format).MemoryUsage(VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT).SampleCount(EngineConfig.getInstance().GetRenderSampleCount()); VkImage = new Image(VkCtx, ImageData); int AspectMask = 0; @@ -25,7 +26,7 @@ public class Attachment { DepthAttachment = true; } var ImageViewData = new ImageView.ImageViewData().Format(VkImage.GetFormat()).AspectMask(AspectMask); - VkImageView = new ImageView(VkCtx.GetDevice(), VkImage.getVulkanImage(), ImageViewData,false); + VkImageView = new ImageView(VkCtx.GetDevice(), VkImage.getVulkanImage(), ImageViewData,DepthAttachment); } public Image GetVkImage(){return VkImage;} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/Image.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/Image.java index 2e6274c..bacd8bb 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/Image.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/Image.java @@ -1,5 +1,6 @@ package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images; +import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.Device; import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils; @@ -22,12 +23,19 @@ public class Image { private final int Format; private final int MipLevels; private final long VulkanImage; + private final int Width; + private final int Height; //private final long VulkanMemory; + public int GetWidth(){return Width;} + public int GetHeight(){return Height;} + public Image(VulkanContext VkCtx, ImageData imageData){ try(var MemStack = MemoryStack.stackPush()){ this.Format = imageData.Format; this.MipLevels = imageData.MipMapLevels; + this.Width = imageData.Width; + this.Height = imageData.Height; VkImageCreateInfo vkImageCreateInfo = VkImageCreateInfo.calloc(MemStack) .sType$Default() diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/Texture.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/Texture.java index 3487d3d..7d6f047 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/Texture.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/Texture.java @@ -9,6 +9,7 @@ import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; import org.lwjgl.vulkan.*; +import org.tinylog.Logger; import java.nio.ByteBuffer; diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/TextureCache.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/TextureCache.java index be6b8f8..8859b5c 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/TextureCache.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/Images/TextureCache.java @@ -4,6 +4,7 @@ import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.GraphUtils; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.IndexedLinkedHashMap; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.VulkanMaterial; 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.Queues.Queue; @@ -17,25 +18,16 @@ import java.util.UUID; import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8A8_SRGB; public class TextureCache { - public static final int MAX_TEXTURES = 100; + public static final int MAX_TEXTURES = 500; private final IndexedLinkedHashMap TextureMap; + private final IndexedLinkedHashMap UnpaddedTextureMap; public TextureCache(){ TextureMap = new IndexedLinkedHashMap<>(); + UnpaddedTextureMap = new IndexedLinkedHashMap<>(); } - public Texture AddTexture(VulkanContext VkCtx, String ID, ImageSrc imageSrc, int Format){ - if(TextureMap.size() > MAX_TEXTURES){ - throw new IllegalArgumentException("Texture Cache Is Full"); - } - Texture newTexture = TextureMap.get(ID); - if(newTexture == null){ - newTexture = new Texture(VkCtx, ID, imageSrc, Format); - TextureMap.put(ID, newTexture); - } - return newTexture; - } - public Texture AddTexture(VulkanContext VkCtx, String ID, String TexturePath, int Format) { + public Texture AddToMainMap(VulkanContext VkCtx, String ID, String TexturePath, int Format) { ImageSrc imageSrc = null; Texture result = null; try{ @@ -50,15 +42,45 @@ public class TextureCache { } return result; } + public Texture AddTexture(VulkanContext VkCtx, String ID, ImageSrc imageSrc, int Format){ + if(TextureMap.size() > MAX_TEXTURES){ + throw new IllegalArgumentException("Texture Cache Is Full"); + } + Texture newTexture = UnpaddedTextureMap.get(ID); + if(newTexture == null){ + newTexture = new Texture(VkCtx, ID, imageSrc, Format); + UnpaddedTextureMap.put(ID, newTexture); + TextureMap.put(ID, newTexture); + } + return newTexture; + } + public Texture AddTexture(VulkanContext VkCtx, String ID, String TexturePath, int Format) { + ImageSrc imageSrc = null; + Texture result = null; + try{ + + imageSrc = GraphUtils.LoadImage(TexturePath); + result = AddTexture(VkCtx, ID, imageSrc, Format); + } catch (IOException exception){ + Logger.error("Could not load texture from patch [{}], exception: {}",TexturePath, exception); + } finally{ + if(imageSrc != null){ + GraphUtils.CleanImageData(imageSrc); + } + } + return result; + } public void TransitionTexts(VulkanContext VkCtx, CommandPool CmdPool, Queue queue){ Logger.debug("Recording Texture Transition"); + TextureMap.clear(); + TextureMap.putAll(UnpaddedTextureMap); int TextureCount = TextureMap.size(); if(TextureCount < MAX_TEXTURES){ int PaddingTextCount = MAX_TEXTURES - TextureCount; String DefaultTexturePath = EngineConfig.getInstance().GetDefaultTexturePath(); for(int i = 0; i < PaddingTextCount; i++){ - AddTexture(VkCtx, UUID.randomUUID().toString(), DefaultTexturePath, VK_FORMAT_R8G8B8A8_SRGB); + AddToMainMap(VkCtx, UUID.randomUUID().toString(), DefaultTexturePath, VK_FORMAT_R8G8B8A8_SRGB); } } var CommandBuffer = new CommandBuffer(VkCtx, CmdPool, true, true); @@ -71,6 +93,7 @@ public class TextureCache { Logger.debug("Recorded Texture Transition"); } + public IndexedLinkedHashMap GetTextureCache(){return TextureMap;} public List GetTextureList(){return new ArrayList<>(TextureMap.values());} public int GetPosition(String ID){ int result = -1; diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/IndexedLinkedHashMap.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/IndexedLinkedHashMap.java index b02c82c..5bc28fa 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/IndexedLinkedHashMap.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/IndexedLinkedHashMap.java @@ -3,6 +3,7 @@ package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; public class IndexedLinkedHashMap extends LinkedHashMap { private final List IndexList = new ArrayList<>(); @@ -20,4 +21,11 @@ public class IndexedLinkedHashMap extends LinkedHashMap { if(!super.containsKey(key)) IndexList.add(key); return super.put(key,value); } + //@Override + //public void putAll(Map m) { + // m.forEach((k, v)->{ + // put(k,v); + // IndexList.add(k); + // }); + //} } 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 index 0e39196..68dc670 100644 --- 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 @@ -1,163 +1,10 @@ package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline; import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; -import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.DescriptorSetLayout; -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); - - VkPipelineDepthStencilStateCreateInfo DepthStencil = null; - if(BuildInfo.GetDepthFormat() != VK_FORMAT_UNDEFINED){ - DepthStencil = VkPipelineDepthStencilStateCreateInfo.calloc(MemStack) - .sType$Default() - .depthTestEnable(true) - .depthWriteEnable(true) - .depthCompareOp(VK_COMPARE_OP_LESS_OR_EQUAL) - .depthBoundsTestEnable(false) - .stencilTestEnable(false); - } - - - VkPipelineDynamicStateCreateInfo VulkanPipelineDynamicStateCreateInfo = VkPipelineDynamicStateCreateInfo.calloc(MemStack) - .sType$Default() - .pDynamicStates(MemStack.ints( - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR - )); - - - VkPushConstantRange.Buffer VkPushConstRangeBuffer = null; - PushConstantsRange[] PushConstRanges = BuildInfo.GetPushConstantRanges(); - int PushConstCount = PushConstRanges.length != 0 ? PushConstRanges.length : 0; - if(PushConstCount > 0){ - VkPushConstRangeBuffer = VkPushConstantRange.calloc(PushConstCount,MemStack); - for(int i = 0; i < PushConstCount; i++){ - PushConstantsRange pushConstantsRange = PushConstRanges[i]; - VkPushConstRangeBuffer.get(i) - .stageFlags(pushConstantsRange.Stage()) - .offset(pushConstantsRange.Offset()) - .size(pushConstantsRange.Size()); - } - } - DescriptorSetLayout[] descriptorSetLayouts = BuildInfo.GetDescriptorSetLayouts(); - int LayoutCount = descriptorSetLayouts != null ? descriptorSetLayouts.length : 0; - LongBuffer ppLayout = MemStack.mallocLong(LayoutCount); - for(int i = 0; i < LayoutCount; i++){ - ppLayout.put(i, descriptorSetLayouts[i].GetVkDescriptorLayout()); - } - 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(BuildInfo.BlendingUsed()); - if(BuildInfo.BlendingUsed()){ - BlendAttributeState.get(0).colorBlendOp(VK_BLEND_OP_ADD) - .alphaBlendOp(VK_BLEND_OP_ADD) - .srcColorBlendFactor(VK_BLEND_FACTOR_SRC_ALPHA) - .dstColorBlendFactor(VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA) - .srcAlphaBlendFactor(VK_BLEND_FACTOR_SRC_ALPHA) - .dstAlphaBlendFactor(VK_BLEND_FACTOR_ZERO); - } - - - 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); - - if(DepthStencil != null){RendererCreateInfo.depthAttachmentFormat(BuildInfo.GetDepthFormat());} - - var PipelineLayoutCreateInfoPtr = VkPipelineLayoutCreateInfo.calloc(MemStack) - .sType$Default() - .pSetLayouts(ppLayout) - .pPushConstantRanges(VkPushConstRangeBuffer); - - 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); - - if(DepthStencil != null){PipelineCreateInfo.pDepthStencilState(DepthStencil);} - - 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); - } +public interface Pipeline { + public long GetVulkanPipeline(); + public long GetVulkanPipelineLayout(); + public void CleanUp(VulkanContext VkCtx); } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/VoxelPipeline.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/VoxelPipeline.java new file mode 100644 index 0000000..a0dac07 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/Pipeline/VoxelPipeline.java @@ -0,0 +1,174 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline; + +import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.DescriptorSetLayout; +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.LongBuffer; + +import static org.lwjgl.vulkan.VK10.*; + +public class VoxelPipeline implements Pipeline{ + private final long VulkanPipeline; + private final long VulkanPipelineLayout; + + public VoxelPipeline(VulkanContext VkCtx, PipelineBuildInfo BuildInfo){ + Device device = VkCtx.GetDevice(); + try(var MemStack = MemoryStack.stackPush()) { + LongBuffer longPtr = MemStack.mallocLong(1); + VkPushConstantRange.Buffer VkPushConstRangeBuffer = null; + PushConstantsRange[] PushConstRanges = BuildInfo.GetPushConstantRanges(); + int PushConstCount = PushConstRanges.length != 0 ? PushConstRanges.length : 0; + if (PushConstCount > 0) { + VkPushConstRangeBuffer = VkPushConstantRange.calloc(PushConstCount, MemStack); + for (int i = 0; i < PushConstCount; i++) { + PushConstantsRange pushConstantsRange = PushConstRanges[i]; + VkPushConstRangeBuffer.get(i) + .stageFlags(pushConstantsRange.Stage()) + .offset(pushConstantsRange.Offset()) + .size(pushConstantsRange.Size()); + } + } + DescriptorSetLayout[] descriptorSetLayouts = BuildInfo.GetDescriptorSetLayouts(); + int LayoutCount = descriptorSetLayouts != null ? descriptorSetLayouts.length : 0; + LongBuffer ppLayout = MemStack.mallocLong(LayoutCount); + for (int i = 0; i < LayoutCount; i++) { + ppLayout.put(i, descriptorSetLayouts[i].GetVkDescriptorLayout()); + } + + var PipelineLayoutCreateInfoPtr = VkPipelineLayoutCreateInfo.calloc(MemStack) + .sType$Default() + .pSetLayouts(ppLayout) + .pPushConstantRanges(VkPushConstRangeBuffer); + + VulkanUtils.vkCheck(vkCreatePipelineLayout(device.FetchVulkanDevice(), PipelineLayoutCreateInfoPtr, null, longPtr) + , "Unable to create new pipeline layout"); + VulkanPipelineLayout = longPtr.get(0); + VulkanPipeline = CreateVoxelPipeline(VkCtx,device.FetchVulkanDevice(), 0, VulkanPipelineLayout,MemStack); + } + } + + public static long CreatePipelineLayout(VkDevice device,LongBuffer DescriptorSetLayout, MemoryStack MemStack){ + VkDescriptorSetLayoutBinding.Buffer BindingBuffer = VkDescriptorSetLayoutBinding.calloc(2,MemStack); + BindingBuffer.get(0) + .binding(0) + .descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) + .descriptorCount(1) + .stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + BindingBuffer.get(1) + .binding(1) + .descriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) + .descriptorCount(1) + .stageFlags(VK_SHADER_STAGE_FRAGMENT_BIT); + VkDescriptorSetLayoutCreateInfo LayoutInfo = VkDescriptorSetLayoutCreateInfo.calloc(MemStack) + .sType$Default() + .pBindings(BindingBuffer); + + LongBuffer LongPtr = MemStack.mallocLong(1); + VulkanUtils.vkCheck(vkCreateDescriptorSetLayout(device, LayoutInfo, + null, LongPtr),"Could not create new Descriptor Set Layout"); + Long pDescriptorSetLayout = LongPtr.get(0); + DescriptorSetLayout.put(0,pDescriptorSetLayout); + LongBuffer LayoutPointer = MemStack.longs(pDescriptorSetLayout); + VkPipelineLayoutCreateInfo PipelineLayoutInfo = VkPipelineLayoutCreateInfo.calloc(MemStack) + .sType$Default() + .pSetLayouts(LayoutPointer); + VulkanUtils.vkCheck(vkCreatePipelineLayout(device, PipelineLayoutInfo, + null, LongPtr),"Could not create Pipeline Layout"); + return LongPtr.get(0); + } + + public static long CreateVoxelPipeline(VulkanContext VkCtx,VkDevice device, long RenderPass, long PipelineLayout, MemoryStack MemStack) { + LongBuffer longPtr = MemStack.mallocLong(1); + VkVertexInputBindingDescription.Buffer BindingDescriptionBuffer = VkVertexInputBindingDescription.calloc(1, MemStack) + .binding(0) + .stride(5) + .inputRate(VK_VERTEX_INPUT_RATE_VERTEX); + VkVertexInputAttributeDescription.Buffer AttributeDescriptionsBuffer = VkVertexInputAttributeDescription.calloc(2, MemStack); + + AttributeDescriptionsBuffer.get(0) + .location(0) + .binding(0) + .format(VK_FORMAT_R8G8B8_UINT) + .offset(0); + AttributeDescriptionsBuffer.get(1) + .location(1) + .binding(0) + .format(VK_FORMAT_R8G8_UINT); + VkPipelineVertexInputStateCreateInfo VertexInputInfo = VkPipelineVertexInputStateCreateInfo.calloc(MemStack) + .sType(VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO) + .pVertexBindingDescriptions(BindingDescriptionBuffer) + .pVertexAttributeDescriptions(AttributeDescriptionsBuffer); + var AssemblyStateCreateInfo = VkPipelineInputAssemblyStateCreateInfo.calloc(MemStack) + .sType(VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO) + .topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST).primitiveRestartEnable(false); + var ViewportCreateStateInfo = VkPipelineViewportStateCreateInfo.calloc(MemStack) + .sType$Default() + .viewportCount(1) + .scissorCount(1); + var RasterizationStateCreateInfo = VkPipelineRasterizationStateCreateInfo.calloc(MemStack) + .sType(VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO) + .depthClampEnable(false) + .rasterizerDiscardEnable(false) + .polygonMode(VK_POLYGON_MODE_FILL) + .cullMode(VK_CULL_MODE_BACK_BIT) + .frontFace(VK_FRONT_FACE_CLOCKWISE) + .lineWidth(1.0f) + .depthBiasEnable(false); + var MultisampleStateCreateInfo = VkPipelineMultisampleStateCreateInfo.calloc(MemStack) + .sType$Default() + .sampleShadingEnable(false) + .rasterizationSamples(VK_SAMPLE_COUNT_1_BIT); + VkPipelineColorBlendAttachmentState.Buffer ColourBlendAttachment = 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); + VkPipelineColorBlendStateCreateInfo ColourBlending = VkPipelineColorBlendStateCreateInfo.calloc(MemStack) + .sType(VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO) + .logicOpEnable(false) + .pAttachments(ColourBlendAttachment); + + VkGraphicsPipelineCreateInfo.Buffer PipelineInfo = VkGraphicsPipelineCreateInfo.calloc(1) + .sType(VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO) + .pVertexInputState(VertexInputInfo) + .pInputAssemblyState(AssemblyStateCreateInfo) + .pViewportState(ViewportCreateStateInfo) + .pRasterizationState(RasterizationStateCreateInfo) + .pMultisampleState(MultisampleStateCreateInfo) + .pColorBlendState(ColourBlending) + .layout(PipelineLayout) + .renderPass(RenderPass) + .subpass(0); + VulkanUtils.vkCheck(vkCreateGraphicsPipelines(device, + VkCtx.GetVkPipelineCache().GetVkPipelineCache(), PipelineInfo, + null, longPtr),"Could not create new pipeline"); + + // Clean up allocation structs + PipelineInfo.free(); ColourBlending.free(); ColourBlendAttachment.free(); + MultisampleStateCreateInfo.free(); RasterizationStateCreateInfo.free(); ViewportCreateStateInfo.free(); + AssemblyStateCreateInfo.free(); VertexInputInfo.free(); AssemblyStateCreateInfo.free(); + BindingDescriptionBuffer.free(); + + return longPtr.get(0); + } + @Override + public long GetVulkanPipeline() { + return VulkanPipeline; + } + + @Override + public long GetVulkanPipelineLayout() { + return VulkanPipelineLayout; + } + + @Override + 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/PostProcessing/PostProcess.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/PostProcessing/PostProcess.java new file mode 100644 index 0000000..aa39317 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/PostProcessing/PostProcess.java @@ -0,0 +1,219 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.PostProcessing; + +import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; +import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.EmptyVertexBufferStruct; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.DefaultPipeline; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.Attachment; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.Image; +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.Shader.*; +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.SwapChain.SwapChain; +import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanBuffer; +import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.util.shaderc.Shaderc; +import org.lwjgl.vulkan.*; + +import java.nio.FloatBuffer; +import java.nio.LongBuffer; +import java.util.Arrays; + +import static org.lwjgl.vulkan.VK10.*; +import static org.lwjgl.vulkan.VK13.*; + +public class PostProcess { + public static final int COLOUR_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; + private static final String DESCRIPTOR_ID_ATTACHMENT = "POST_DESC_ID_ATT"; + private static final String DESCRIPTOR_ID_SCREEN_SIZE = "POST_DESC_ID_SCREEN_SIZE"; + private static final String MULTI_PASS_FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/multi-sampled_post_process_frag.glsl"; + private static final String MULTI_PASS_FRAGMENT_SHADER_FILE_SPV = MULTI_PASS_FRAGMENT_SHADER_FILE_GLSL + ".spv"; + private static final String SINGLE_PASS_FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/post_process_frag.glsl"; + private static final String SINGLE_PASS_FRAGMENT_SHADER_FILE_SPV = SINGLE_PASS_FRAGMENT_SHADER_FILE_GLSL + ".spv"; + private static final String VERTEX_SHADER_FILE_GLSL = "resources/EngineResources/shaders/post_process_vtx.glsl"; + private static final String VERTEX_SHADER_FILE_SPV = VERTEX_SHADER_FILE_GLSL + ".spv"; + + private final DescriptorSetLayout AttachmentDescriptorSetLayout; + private final VkClearValue ClearValueColour; + private final DescriptorSetLayout FragmentUniformDescriptorSetLayout; + private final Pipeline pipeline; + private final VulkanBuffer ScreenSizeBuffer; + private final SpecializationConstants specConstants; + private final TextureSampler textureSampler; + private Attachment ColourAttachment; + private VkRenderingAttachmentInfo.Buffer ColourAttachmentInfo; + private VkRenderingInfo RenderingInfo; + + public PostProcess(VulkanContext VkCtx, Attachment SrcAttachment){ + ClearValueColour = VkClearValue.calloc(); + ClearValueColour.color(c->c.float32(0,0.0f).float32(1,0.0f).float32(2,0.0f).float32(3,0.0f)); + + ColourAttachment = CreateColourAttachment(VkCtx); + ColourAttachmentInfo = CreateColourAttachmentInfo(ColourAttachment,ClearValueColour); + RenderingInfo = CreateRenderInfo(ColourAttachment,ColourAttachmentInfo); + + var TextureSamplerInfo = new TextureSamplerInfo(VK_SAMPLER_ADDRESS_MODE_REPEAT,VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK,1,true); + textureSampler = new TextureSampler(VkCtx, TextureSamplerInfo); + + var LayoutInfo = new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,0,1,VK_SHADER_STAGE_FRAGMENT_BIT); + AttachmentDescriptorSetLayout = new DescriptorSetLayout(VkCtx, LayoutInfo); + CreateAttachmentDescriptorSet(VkCtx, AttachmentDescriptorSetLayout, SrcAttachment,textureSampler); + + LayoutInfo = new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0,1,VK_SHADER_STAGE_FRAGMENT_BIT); + FragmentUniformDescriptorSetLayout = new DescriptorSetLayout(VkCtx, LayoutInfo); + ScreenSizeBuffer = VulkanUtils.CreateHostVisibleBuffer(VkCtx, VulkanUtils.VEC2_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,DESCRIPTOR_ID_SCREEN_SIZE,FragmentUniformDescriptorSetLayout); + SetScreenSizeBuffer(VkCtx); + + specConstants = new SpecializationConstants(); + ShaderModule[] shaderModules = CreateShaderModules(VkCtx, specConstants); + + pipeline = CreatePipeline(VkCtx, shaderModules, new DescriptorSetLayout[]{AttachmentDescriptorSetLayout, FragmentUniformDescriptorSetLayout}); + Arrays.asList(shaderModules).forEach(shader->shader.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 SrcAttachment, VkClearValue ClearValue){ + return VkRenderingAttachmentInfo.calloc(1) + .sType$Default() + .imageView(SrcAttachment.GetVkImageView().GetVulkanImageView()) + .imageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) + .loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR) + .storeOp(VK_ATTACHMENT_STORE_OP_STORE) + .clearValue(ClearValue); + } + + private static Pipeline CreatePipeline(VulkanContext VkCtx, ShaderModule[] shaderModules, DescriptorSetLayout[] descriptorSetLayouts){ + var VertexBufferStruct = new EmptyVertexBufferStruct(); + var BuildInfo = new PipelineBuildInfo(shaderModules,VertexBufferStruct.GetVertexInput(),COLOUR_FORMAT).SetDescriptorSetLayouts(descriptorSetLayouts).BlendingIsUsed(true); + var PipeLine = new DefaultPipeline(VkCtx,BuildInfo); + VertexBufferStruct.CleanUp(); + return PipeLine; + } + + private static VkRenderingInfo CreateRenderInfo(Attachment ColourAttachment, VkRenderingAttachmentInfo.Buffer ColourAttachmentInfo){ + VkRenderingInfo renderingInfo; + try(var MemStack = MemoryStack.stackPush()){ + Image image = ColourAttachment.GetVkImage(); + VkExtent2D extent2D = VkExtent2D.calloc(MemStack).width(image.GetWidth()).height(image.GetHeight()); + var RenderArea = VkRect2D.calloc(MemStack).extent(extent2D); + + renderingInfo = VkRenderingInfo.calloc() + .sType$Default() + .renderArea(RenderArea) + .layerCount(1) + .pColorAttachments(ColourAttachmentInfo); + } + return renderingInfo; + } + + private static void CreateAttachmentDescriptorSet(VulkanContext VkCtx, DescriptorSetLayout descriptorSetLayout, Attachment attachment, TextureSampler textureSampler){ + DescriptorAllocator descriptorAllocator = VkCtx.GetDescriptorAllocator(); + Device device = VkCtx.GetDevice(); + DescriptorSet descriptorSet = descriptorAllocator.AddDescriptorSets(device,DESCRIPTOR_ID_ATTACHMENT,1 , descriptorSetLayout)[0]; + descriptorSet.SetImage(device,attachment.GetVkImageView(),textureSampler,0); + } + + private static ShaderModule[] CreateShaderModules(VulkanContext VkCtx, SpecializationConstants specConstants){ + String ShaderPath = SINGLE_PASS_FRAGMENT_SHADER_FILE_GLSL; + if(EngineConfig.getInstance().RecompileShaders()){ + ShaderCompiler.CompileGLSLShaderOnChange(VERTEX_SHADER_FILE_GLSL, Shaderc.shaderc_glsl_vertex_shader); + if(EngineConfig.getInstance().RenderAA() > 1) ShaderPath = MULTI_PASS_FRAGMENT_SHADER_FILE_GLSL; + ShaderCompiler.CompileGLSLShaderOnChange(ShaderPath, Shaderc.shaderc_glsl_fragment_shader); + } + ShaderPath = SINGLE_PASS_FRAGMENT_SHADER_FILE_SPV; + if(EngineConfig.getInstance().RenderAA() > 1) ShaderPath = MULTI_PASS_FRAGMENT_SHADER_FILE_SPV; + return new ShaderModule[]{ + new ShaderModule(VkCtx,VK_SHADER_STAGE_VERTEX_BIT,VERTEX_SHADER_FILE_SPV,null), + new ShaderModule(VkCtx,VK_SHADER_STAGE_FRAGMENT_BIT,ShaderPath,specConstants.GetSpecializationInfo()) + }; + } + + public void Render(VulkanContext VkCtx, CommandBuffer commandBuffer, Attachment SrcAttachment){ + try(var MemStack = MemoryStack.stackPush()){ + SwapChain swapChain = VkCtx.GetSwapChain(); + VkCommandBuffer CommandHandle = commandBuffer.GetVulkanCommandBuffer(); + + VulkanUtils.ImageBarrier(MemStack, CommandHandle, SrcAttachment.GetVkImage().getVulkanImage(), + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,VK_ACCESS_2_SHADER_READ_BIT,VK_IMAGE_ASPECT_COLOR_BIT); + VulkanUtils.ImageBarrier(MemStack,CommandHandle,ColourAttachment.GetVkImage().getVulkanImage(), + VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_PIPELINE_STAGE_2_TOP_OF_PIPE_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,RenderingInfo); + vkCmdBindPipeline(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,pipeline.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); + + DescriptorAllocator descriptorAllocator = VkCtx.GetDescriptorAllocator(); + LongBuffer DescriptorSets = MemStack.mallocLong(2) + .put(0,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_ATTACHMENT).GetVkDescriptorSet()) + .put(1,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_SCREEN_SIZE).GetVkDescriptorSet()); + vkCmdBindDescriptorSets(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,pipeline.GetVulkanPipelineLayout(),0,DescriptorSets,null); + vkCmdDraw(CommandHandle,3,1,0,0); + vkCmdEndRendering(CommandHandle); + } + } + + public void Resize(VulkanContext VkCtx, Attachment SrcAttachment){ + RenderingInfo.free();; + ColourAttachment.CleanUp(VkCtx); + ColourAttachmentInfo.free(); + ColourAttachment = CreateColourAttachment(VkCtx); + ColourAttachmentInfo = CreateColourAttachmentInfo(ColourAttachment,ClearValueColour); + RenderingInfo = CreateRenderInfo(ColourAttachment,ColourAttachmentInfo); + + DescriptorAllocator descriptorAllocator = VkCtx.GetDescriptorAllocator(); + DescriptorSet descriptorSet = descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_ATTACHMENT); + descriptorSet.SetImage(VkCtx.GetDevice(),SrcAttachment.GetVkImageView(),textureSampler,0); + + SetScreenSizeBuffer(VkCtx); + } + + private void SetScreenSizeBuffer(VulkanContext VkCtx){ + long MappedMemory = ScreenSizeBuffer.MapMemory(VkCtx); + FloatBuffer dataBuffer = MemoryUtil.memFloatBuffer(MappedMemory,(int)ScreenSizeBuffer.GetRequestedSize()); + VkExtent2D SwapChainExtent = VkCtx.GetSwapChain().GetSwapChainExtent(); + dataBuffer.put(0,SwapChainExtent.width()); + dataBuffer.put(1,SwapChainExtent.height()); + ScreenSizeBuffer.UnMapMemory(VkCtx); + } + + public Attachment GetAttachment(){return ColourAttachment;} + + public void CleanUp(VulkanContext VkCtx){ + ClearValueColour.free(); + ColourAttachment.CleanUp(VkCtx); + textureSampler.CleanUp(VkCtx); + AttachmentDescriptorSetLayout.CleanUp(VkCtx); + FragmentUniformDescriptorSetLayout.CleanUp(VkCtx); + pipeline.CleanUp(VkCtx); + RenderingInfo.free(); + ColourAttachmentInfo.free(); + ScreenSizeBuffer.cleanup(VkCtx); + specConstants.CleanUp(); + } +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/PostProcessing/SpecializationConstants.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/PostProcessing/SpecializationConstants.java new file mode 100644 index 0000000..a55ce40 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/PostProcessing/SpecializationConstants.java @@ -0,0 +1,39 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.PostProcessing; + +import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; +import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.vulkan.VkSpecializationInfo; +import org.lwjgl.vulkan.VkSpecializationMapEntry; + +import java.nio.ByteBuffer; + +public class SpecializationConstants { + + private final ByteBuffer data; + private final VkSpecializationMapEntry.Buffer SpecializationEntryMap; + private final VkSpecializationInfo SpecializationInfo; + + public SpecializationConstants(){ + var EngineCfg = EngineConfig.getInstance(); + data = MemoryUtil.memAlloc(VulkanUtils.INT_SIZE); + data.putInt(EngineCfg.RenderAA()); + data.flip(); + + SpecializationEntryMap = VkSpecializationMapEntry.calloc(1); + SpecializationEntryMap.get(0) + .constantID(0) + .size(VulkanUtils.INT_SIZE) + .offset(0); + + SpecializationInfo = VkSpecializationInfo.calloc(); + SpecializationInfo.pData(data).pMapEntries(SpecializationEntryMap); + } + + public void CleanUp(){ + MemoryUtil.memFree(SpecializationEntryMap); + SpecializationInfo.free(); + MemoryUtil.memFree(data); + } + public VkSpecializationInfo GetSpecializationInfo(){return SpecializationInfo;} +} 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 index cb0abe9..9624fda 100644 --- 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 @@ -6,6 +6,8 @@ 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.lwjgl.vulkan.VkSpecializationInfo; import org.tinylog.Logger; import java.io.File; @@ -18,12 +20,14 @@ import java.nio.file.Files; public class ShaderModule { private final long Handle; private final int ShaderStage; + private final VkSpecializationInfo SpecializationInfo; - public ShaderModule(VulkanContext VkCtx, int ShaderStage, String ShaderSPVFile){ + public ShaderModule(VulkanContext VkCtx, int ShaderStage, String ShaderSPVFile,VkSpecializationInfo SpecializationInfo){ try{ byte[] Contents = Files.readAllBytes(new File(ShaderSPVFile).toPath()); Handle = CreateShaderModule(VkCtx, Contents); this.ShaderStage = ShaderStage; + this.SpecializationInfo = SpecializationInfo; } catch(IOException exception){ Logger.error("Cannot Read Shader Files",exception); throw new RuntimeException(exception); @@ -42,6 +46,8 @@ public class ShaderModule { } } + public VkSpecializationInfo GetSpecializationInfo(){return SpecializationInfo;} + public void CleanUp(VulkanContext VkCtx){ vkDestroyShaderModule(VkCtx.GetDevice().FetchVulkanDevice(), Handle, null); } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/VkModel/CompatibleVoxelMesh.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/VkModel/CompatibleVoxelMesh.java new file mode 100644 index 0000000..08e003f --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/VkModel/CompatibleVoxelMesh.java @@ -0,0 +1,225 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel; + +import net.halbear.Terrain4J.EngineCore.Display.Render; +import net.halbear.Terrain4J.EngineCore.Logic.InitVoxelData; + +import org.joml.Vector4f; +import org.lwjgl.system.MemoryUtil; +import org.tinylog.Logger; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + + +public class CompatibleVoxelMesh { + private static final int VERTEX_STRIDE = 6; + public static List voxelModels = new ArrayList<>(); + public static List TexturePaths = new ArrayList<>(); + private Render renderInstance; + private float[] VertexArray; + private int[] IndicesArray; + private final String ID; + private boolean[] Faces = new boolean[]{false,false,false,false,false,false}; + private int[] Textureindex = new int[6]; + private Vector4f[] DiffuseColours = new Vector4f[]{new Vector4f(0.6f, 0.6f, 0.6f, 1.0f),new Vector4f(0.5f, 0.5f, 0.5f, 1.0f),new Vector4f(0.4f, 0.4f, 0.4f, 1.0f),new Vector4f(0.5f, 0.5f, 0.5f, 1.0f),new Vector4f(0.7f, 0.7f, 0.7f, 1.0f),new Vector4f(0.3f, 0.3f, 0.3f, 1.0f)}; + + static{ + TexturePaths.add("resources/EngineResources/Texture/DefaultTexture.png"); + } + + public enum FaceDirection{ + North(0), + South(2), + East(1), + West(3), + Up(4), + Down(5); + + private final int value; + + FaceDirection(int value) { + this.value = value; + } + + // 3. Add a getter method to access the number + public int getValue() { + return value; + } + } + + public record VoxelModelInitData(VoxelModelData voxelModel, List materialData){} + public record VoxelModelData(String ID, List meshes, FloatBuffer vertexBuffer, IntBuffer indexBuffer){} + public record VoxelMeshData(String ID, FloatBuffer vertexBuffer, IntBuffer indexBuffer, int VertexCount, int IndexCount){} + + public CompatibleVoxelMesh(String VoxelID){ + VertexArray = new float[6 * 20]; + IndicesArray = new int[6 * 6]; + ID = VoxelID; + } + public CompatibleVoxelMesh(Render rendererInstance, String VoxelID){ + renderInstance = rendererInstance; + VertexArray = new float[6 * 20]; + IndicesArray = new int[6 * 6]; + ID = VoxelID; + } + public CompatibleVoxelMesh SetSideTexture(int SideNumber, String TextureID){ + int index = TexturePaths.indexOf(TextureID); + if(index < 0){ + TexturePaths.add(TextureID); + index = TexturePaths.indexOf(TextureID); + } + Textureindex[SideNumber] = index; + return this; + } + + public CompatibleVoxelMesh CreateMeshFace(FaceDirection faceDirection, String TextureID){ + int side = faceDirection.getValue(); + Faces[side] = true; + + int index = TexturePaths.indexOf(TextureID); + if (index < 0) { + TexturePaths.add(TextureID); + index = TexturePaths.indexOf(TextureID); + } + Textureindex[side] = index; + + float[] srcFace = switch (faceDirection) { + case North -> FrontFace; + case South -> BackFace; + case East -> RightFace; + case West -> LeftFace; + case Up -> TopFace; + case Down -> BottomFace; + }; + + System.arraycopy(srcFace, 0, VertexArray, side * 20, 20); + return this; + } + + public static InitVoxelData GetVoxelModelsGenerated(){ + List models = new ArrayList<>(); + List materials = new ArrayList<>(); + for(int i = 0; i < voxelModels.size(); i++){ + models.add(voxelModels.get(i).voxelModel()); + materials.addAll(voxelModels.get(i).materialData()); + } + return new InitVoxelData(models,materials); + } + + public CompatibleVoxelMesh CompileMesh(){ + CompileMeshNoReturn(); + return this; + } + + public void CompileMeshNoReturn() { + List meshes = new ArrayList<>(); + List materials = new ArrayList<>(); + List AddedMaterials = new ArrayList<>(); + int faceCount = 0; + for (int i = 0; i < 6; i++) { + if (Faces[i]) { + faceCount++; + } + } + VoxelMeshData data = new VoxelMeshData(ID, MemoryUtil.memAllocFloat(20*faceCount), MemoryUtil.memAllocInt(6*faceCount), 4, 6); + float[] vertexArray = new float[faceCount * 20]; + faceCount = 0; + for(int i = 0; i < 6; i++){ + if(Faces[i]){ + String TextureID = "N/A_NULL"; + if(Textureindex[i] >= 0 && Textureindex[i] < TexturePaths.size()) TextureID = TexturePaths.get(Textureindex[i]); + else TextureID = "resources/EngineResources/Texture/DefaultTexture.png"; + if(!AddedMaterials.contains(TextureID)) { + materials.add(new MaterialData(TextureID, TextureID,DiffuseColours[i])); + AddedMaterials.add(TextureID); + } + for(int j = 0; j < 20; j++){ + vertexArray[(faceCount * 20) + j] = VertexArray[(i * 20) + j]; + } + if(!Objects.equals(TextureID, "N/A_NULL")){ + meshes.add(new MeshData(ID + "_" + i, TextureID,faceCount * 20, 20, faceCount * 6, 6)); + } + int offset = faceCount * 4; + data.indexBuffer.put(offset); + data.indexBuffer.put(offset + 1); + data.indexBuffer.put(offset + 2); + data.indexBuffer.put(offset + 2); + data.indexBuffer.put(offset + 3); + data.indexBuffer.put(offset); + faceCount++; + } + } + Logger.debug("Voxel with [{}] sides generated",faceCount); + data.indexBuffer.flip(); + data.vertexBuffer.put(vertexArray).flip(); + VoxelModelData model = new VoxelModelData(ID,meshes, data.vertexBuffer, data.indexBuffer); + voxelModels.add(new VoxelModelInitData(model, materials)); + } + + public float[] FetchFaceWithTexture(float TextureIndex, FaceDirection direction){ + float[] meshData = new float[20]; + this.Textureindex[direction.getValue()] = (int) TextureIndex; + switch (direction){ + case North -> { + System.arraycopy(FrontFace, 0, meshData, 0, 20); + } + case South -> { + System.arraycopy(BackFace, 0, meshData, 0, 20); + } + case East -> { + System.arraycopy(RightFace, 0, meshData, 0, 20); + } + case West -> { + System.arraycopy(LeftFace, 0, meshData, 0, 20); + } + case Up -> { + System.arraycopy(TopFace, 0, meshData, 0, 20); + } + case Down -> { + System.arraycopy(BottomFace, 0, meshData, 0, 20); + } + } + return meshData; + } + + public static final float[] FrontFace = new float[]{ + -0.5f, -0.5f, 0.5f, 0.0f, 1.0f, + 0.5f, -0.5f, 0.5f, 1.0f, 1.0f, + 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, + -0.5f, 0.5f, 0.5f, 0.0f, 0.0f + }; + public static final float[] BackFace = new float[]{ + 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, + -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, + -0.5f, 0.5f, -0.5f, 1.0f, 0.0f, + 0.5f, 0.5f, -0.5f, 0.0f, 0.0f + }; + public static final float[] TopFace = new float[]{ + -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, + 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, + -0.5f, 0.5f, -0.5f, 0.0f, 0.0f + }; + public static final float[] BottomFace = new float[]{ + -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, + 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, + 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, + -0.5f, -0.5f, 0.5f, 0.0f, 0.0f + }; + public static final float[] LeftFace = new float[]{ + -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, + -0.5f, -0.5f, 0.5f, 1.0f, 1.0f, + -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, + -0.5f, 0.5f, -0.5f, 0.0f, 0.0f + }; + public static final float[] RightFace = new float[]{ + 0.5f, -0.5f, 0.5f, 0.0f, 1.0f, + 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.5f, 0.0f, 0.0f + }; + +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/VkModel/MaterialsCache.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/VkModel/MaterialsCache.java index 78b0806..2e1eb55 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/VkModel/MaterialsCache.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Rendering/VkModel/MaterialsCache.java @@ -56,15 +56,19 @@ public class MaterialsCache { if(ValidTexture){ Logger.debug("Loading Texture [{}]",TexturePath); Texture newTexture = textureCache.AddTexture(VkCtx, TexturePath, TexturePath, VK_FORMAT_R8G8B8A8_SRGB); - TransparentTexture = newTexture.HasTransparency(); + if(newTexture == null) { + TransparentTexture = false; + TexturePath = "resources/EngineResources/Texture/DefaultTexture.png"; + } + else TransparentTexture = newTexture.HasTransparency(); } else{ + TexturePath = "resources/EngineResources/Texture/DefaultTexture.png"; TransparentTexture = Material.DiffuseColour().w < 1.0f; } VulkanMaterial newMaterial = new VulkanMaterial(Material.ID(),TransparentTexture); MaterialsMap.put(newMaterial.ID(), newMaterial); Logger.trace(Material.toString()); - data.position(Offset); - Material.DiffuseColour().get(data); + Material.DiffuseColour().get(Offset,data); Logger.debug("Set Diffuse Colour -> [{}]",Material.DiffuseColour()); data.putInt(Offset + VulkanUtils.VEC4_SIZE, ValidTexture ? 1 : 0); data.putInt(Offset + VulkanUtils.VEC4_SIZE + VulkanUtils.INT_SIZE, textureCache.GetPosition(TexturePath)); @@ -76,6 +80,7 @@ public class MaterialsCache { } data.position(0); + SrcBuffer.Flush(VkCtx); SrcBuffer.UnMapMemory(VkCtx); transferBuffer.RecordTransferCommand(CmdBuffer); CmdBuffer.EndRecording(); @@ -91,6 +96,7 @@ public class MaterialsCache { } } + public IndexedLinkedHashMap GetMaterialCache(){return MaterialsMap;} public VulkanMaterial GetMaterial(String ID){return MaterialsMap.get(ID);} public VulkanBuffer GetMaterialsBuffer(){return MaterialsBuffer;} 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 3f83e32..37705d6 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 @@ -10,10 +10,7 @@ import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils; import org.lwjgl.system.MemoryUtil; import org.tinylog.Logger; -import java.io.BufferedInputStream; -import java.io.DataInputStream; -import java.io.FileInputStream; -import java.io.IOException; +import java.io.*; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.ArrayList; @@ -31,6 +28,39 @@ public class ModelsCache { ModelsMap = new HashMap<>(); } + public void loadVoxelModels(VulkanContext VkCtx, List Models, CommandPool commandPool, Queue queue){ + try { + List StagingBufferList = new ArrayList<>(); + var Command = new CommandBuffer(VkCtx, commandPool, true, true); + Command.BeginRecording(); + + for (CompatibleVoxelMesh.VoxelModelData modelData : Models) { + VulkanModel VKModel = new VulkanModel(modelData.ID()); + ModelsMap.put(VKModel.GetID(), VKModel); + + for (MeshData meshData : modelData.meshes()) { + TransferBuffer VerticesBuffers = CreateVoxelVerticesBuffer(VkCtx, meshData,modelData.vertexBuffer()); + TransferBuffer IndicesBuffers = CreateVoxelIndicesBuffer(VkCtx, meshData,modelData.indexBuffer()); + StagingBufferList.add(VerticesBuffers.SrcBuffer()); + StagingBufferList.add(IndicesBuffers.SrcBuffer()); + VerticesBuffers.RecordTransferCommand(Command); + IndicesBuffers.RecordTransferCommand(Command); + Logger.debug("Creating new Vulkan Mesh -> ID=[{}] MaterialID=[{}]",meshData.ID(),meshData.MaterialID()); + VulkanMesh VkMesh = new VulkanMesh(meshData.ID(), VerticesBuffers.DstBuffer(), IndicesBuffers.DstBuffer(), + meshData.IndexSize(), meshData.MaterialID()); + VKModel.GetVkMeshList().add(VkMesh); + } + } + Command.EndRecording(); + Command.SubmitAndWait(VkCtx,queue); + Command.cleanup(VkCtx,commandPool); + + StagingBufferList.forEach(b -> b.cleanup(VkCtx)); + } catch (Exception exception){ + throw new RuntimeException(exception); + } + } + public void loadModels(VulkanContext VkCtx, List Models, CommandPool commandPool, Queue queue){ try { List StagingBufferList = new ArrayList<>(); @@ -69,6 +99,43 @@ public class ModelsCache { } } + private static TransferBuffer CreateVoxelIndicesBuffer(VulkanContext VkCtx, MeshData meshData, IntBuffer IndexBuffer) throws IOException{ + int BufferSize = meshData.IndexSize(); + var SrcBuffer = new VulkanBuffer(VkCtx,BufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_AUTO,VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + var DstBuffer = new VulkanBuffer(VkCtx, BufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO,0,0); + long MappedMemory = SrcBuffer.MapMemory(VkCtx); + IntBuffer data = MemoryUtil.memIntBuffer(MappedMemory,(int) SrcBuffer.GetRequestedSize()); + IndexBuffer.rewind(); + for(int i = 0; i < meshData.IndexSize(); i++){ + data.put(IndexBuffer.get(meshData.OffsetIndex() + i)); + } + SrcBuffer.UnMapMemory(VkCtx); + return new TransferBuffer(SrcBuffer,DstBuffer); + } + private static TransferBuffer CreateVoxelVerticesBuffer(VulkanContext VkCtx, MeshData meshData, FloatBuffer VertexBuffer) throws IOException { + Logger.debug("Creating Vertices Transfer Buffer, Mesh Data -> ID=[{}] VertexSize=[{}]",meshData.ID(),meshData.VertexSize()); + int BufferSize = meshData.VertexSize(); + var SrcBuffer = new VulkanBuffer(VkCtx,BufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_AUTO,VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + var DstBuffer = new VulkanBuffer(VkCtx, BufferSize, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO,0,0); + if (BufferSize <= 0) { + Logger.error("Computed buffer allocation size cannot be 0. SrcBuffer -> [{}] DstBuffer -> [{}]",SrcBuffer,DstBuffer); + throw new RuntimeException("Computed buffer allocation size cannot be 0. SrcBuffer -> ["+SrcBuffer+"] DstBuffer -> ["+DstBuffer+"]"); + } + Logger.debug("Starting to create vertex Transfer Buffer"); + long MappedMemory = SrcBuffer.MapMemory(VkCtx); + FloatBuffer data = MemoryUtil.memFloatBuffer(MappedMemory, (int) SrcBuffer.GetRequestedSize()); + VertexBuffer.rewind(); + for(int i = 0; i < meshData.VertexSize(); i++){ + data.put(VertexBuffer.get(meshData.OffsetVertex() + i)); + } + SrcBuffer.UnMapMemory(VkCtx); + return new TransferBuffer(SrcBuffer,DstBuffer); + } + private static TransferBuffer CreateVerticesBuffer(VulkanContext VkCtx, MeshData meshData, DataInputStream VertexStream) throws IOException { Logger.debug("Creating Vertices Transfer Buffer, Mesh Data -> ID=[{}] VertexSize=[{}]",meshData.ID(),meshData.VertexSize()); int BufferSize = meshData.VertexSize(); 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 8adace2..1b7a853 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,11 +3,12 @@ package net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DisplayToScreen; 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.GameCore; import net.halbear.Terrain4J.EngineCore.Main.PrimaryRuntime; import net.halbear.Terrain4J.EngineCore.Main.Scene.Actor; import net.halbear.Terrain4J.EngineCore.Main.Scene.Scene; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.DefaultPipeline; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.Attachment; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.Image; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.Texture; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.TextureCache; import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Pipeline; @@ -18,6 +19,7 @@ import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VertexBufferStructure; 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.SwapChain.SwapChain; import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanBuffer; import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils; import org.joml.Matrix4f; @@ -32,11 +34,10 @@ import java.nio.LongBuffer; import java.util.Arrays; import java.util.List; -import static org.lwjgl.vulkan.KHRSwapchain.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; import static org.lwjgl.vulkan.KHRSynchronization2.VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL_KHR; import static org.lwjgl.vulkan.VK13.*; -public class SceneRender {//dynamic rendering +public class SceneRender implements SceneRenderer {//dynamic rendering private static final String DESCRIPTOR_ID_MAT = "SCN_DESC_ID_MAT"; private static final String DESCRIPTOR_ID_PRJ = "SCN_DESC_ID_PRJ"; @@ -49,26 +50,28 @@ public class SceneRender {//dynamic rendering private final DescriptorSetLayout descriptorLayoutVertexUniform; private final TextureSampler textureSampler; + private static final int COLOUR_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; private static final int DEPTH_FORMAT = VK_FORMAT_D16_UNORM; private final VkClearValue ClearValueDepth; private final ByteBuffer PushConstBuffer; - private Attachment[] AttDepth; - private VkRenderingAttachmentInfo[] AttInfoDepth; + private Attachment AttachmentDepth; + private Attachment AttachmentColour; + private VkRenderingAttachmentInfo AttachmentInfoDepth; private VkClearValue ClearValueColour; - private VkRenderingAttachmentInfo.Buffer[] AttachmentInfoColour; - private VkRenderingInfo[] RenderInfo; - private float R = 0f; - private float G = 0.5f; - private float B = 0.7f; + private VkRenderingAttachmentInfo.Buffer AttachmentInfoColour; + private VkRenderingInfo RenderInfo; + private float R = 0.5f; + private float G = 0.75f; + private float B = 1.0f; private boolean Rb = false; private boolean Gb = true; private boolean Bb = false; public int Frames = 0; private static String LastGPULog = ""; - private static final String FRAGMENT_SHADER_FILE_GLSL = "resources/shaders/scene_fragment.glsl"; + private static final String FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/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_GLSL = "resources/EngineResources/shaders/scene_vertex.glsl"; private static final String VERTEX_SHADER_FILE_SPV = VERTEX_SHADER_FILE_GLSL + ".spv"; private final VulkanBuffer[] BufferViewMatrices; private final Pipeline VkPipeline; @@ -76,14 +79,14 @@ public class SceneRender {//dynamic rendering public SceneRender(VulkanContext vulkanContext){ 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, 1.0f)); - ClearValueDepth = VkClearValue.calloc().depthStencil(c -> c.depth(1.0f).stencil()); - AttDepth = createDepthAttachments(vulkanContext); - AttInfoDepth = createDepthAttachmentsInfo(vulkanContext, AttDepth, ClearValueDepth); - 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, AttInfoDepth); + c -> c.float32(0, R).float32(1, G).float32(2, B).float32(3, 1.0f)); + ClearValueDepth = VkClearValue.calloc().color(c -> c.float32(0, 0.0f)); + AttachmentColour = CreateColourAttachment(vulkanContext); + AttachmentDepth = CreateDepthAttachment(vulkanContext); + AttachmentInfoDepth = CreateDepthAttachmentInfo(AttachmentDepth, ClearValueDepth); + AttachmentInfoColour = CreateColourAttachmentInfo(AttachmentColour,ClearValueColour); + RenderInfo = CreateRenderInfo(AttachmentColour, AttachmentInfoColour, AttachmentInfoDepth); + ShaderModule[] shaderModules = SceneRender.CreateShaderModules(vulkanContext); PushConstBuffer = (MemoryUtil.memAlloc(PUSH_CONSTANTS_SIZE)); @@ -114,6 +117,40 @@ public class SceneRender {//dynamic rendering Arrays.asList(shaderModules).forEach(shaderModule -> shaderModule.CleanUp(vulkanContext)); } + 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_CLEAR) + .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 Attachment[] createDepthAttachments(VulkanContext VkCtx){ SwapChain swapChain = VkCtx.GetSwapChain(); int ImageCount = swapChain.GetImageCount(); @@ -149,15 +186,14 @@ public class SceneRender {//dynamic rendering ShaderCompiler.CompileGLSLShaderOnChange(FRAGMENT_SHADER_FILE_GLSL, Shaderc.shaderc_glsl_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) + new ShaderModule(VkCtx, VK_SHADER_STAGE_VERTEX_BIT, VERTEX_SHADER_FILE_SPV,null), + new ShaderModule(VkCtx, VK_SHADER_STAGE_FRAGMENT_BIT, FRAGMENT_SHADER_FILE_SPV,null) }; } private static Pipeline CreatePipeline(VulkanContext VkCtx, ShaderModule[] ShaderModules, DescriptorSetLayout[] DescriptorSetLayouts){ var vertexBufferStructure = new VertexBufferStructure(); - var BuildInfo = new PipelineBuildInfo(ShaderModules,vertexBufferStructure.getVertexInput(), - VkCtx.GetSurface().GetSurfaceFormat().ImageFormat()) + var BuildInfo = new PipelineBuildInfo(ShaderModules,vertexBufferStructure.getVertexInput(),COLOUR_FORMAT) .SetDepthFormat(DEPTH_FORMAT) .SetPushConstantRanges( new PushConstantsRange[]{ @@ -166,62 +202,33 @@ public class SceneRender {//dynamic rendering }) .SetDescriptorSetLayouts(DescriptorSetLayouts) .BlendingIsUsed(true); - var pipeline = new Pipeline(VkCtx, BuildInfo); + var pipeline = new DefaultPipeline(VkCtx, BuildInfo); vertexBufferStructure.cleanup(); return pipeline; } - public void UpdateColours(VulkanContext vulkanContext){ - if (Gb){ - if (G < 1.0f && Math.random() <0.5) G+=0.0005f; - else if (G >= 1.0f) Gb = false; - } else{ - if (G >0.0f&& Math.random() <0.5) G-=0.0005f; - else if (G <= 0.0f) Gb = true; - } - if (Rb){ - if (R < 1.0f&& Math.random() <0.3) R+=0.0005f; - else if (R >= 1.0f) Rb = false; - } else{ - if (R >0.0f&& Math.random() <0.3) R-=0.0005f; - else if (R <= 0.0f) Rb = true; - } - if (Bb){ - if (B < 1.0f&& Math.random() <0.7) B+=0.0005f; - else if (B >= 1.0f) Bb = false; - } else{ - if (B >0.0f&& Math.random() <0.7) B-=0.0005f; - else if (B <= 0.0f) Bb = true; - } - //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, AttInfoDepth); - } - public void Render(EngineInstance engineInstance,VulkanContext vulkanContext, CommandBuffer commandBuffer, ModelsCache modelsCache,MaterialsCache materialsCache, int ImageIndex, int CurrentFrame){ - UpdateColours(vulkanContext); + public void Render(EngineInstance engineInstance,VulkanContext vulkanContext, CommandBuffer commandBuffer, ModelsCache modelsCache,MaterialsCache materialsCache, int CurrentFrame){ try(var MemStack = MemoryStack.stackPush()){ - SwapChain swapChain = vulkanContext.GetSwapChain(); - long SwapChainImage = swapChain.GetVulkanImageView(ImageIndex).GetVulkanImage(); VkCommandBuffer CommandHandle = commandBuffer.GetVulkanCommandBuffer(); - VulkanUtils.ImageBarrier(MemStack, CommandHandle, SwapChainImage, + 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); - VulkanUtils.ImageBarrier(MemStack, CommandHandle, AttDepth[ImageIndex].GetVkImage().getVulkanImage(), + VulkanUtils.ImageBarrier(MemStack, CommandHandle, AttachmentDepth.GetVkImage().getVulkanImage(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT, VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_IMAGE_ASPECT_DEPTH_BIT); - vkCmdBeginRendering(CommandHandle, RenderInfo[ImageIndex]); + vkCmdBeginRendering(CommandHandle, RenderInfo); vkCmdBindPipeline(CommandHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, VkPipeline.GetVulkanPipeline()); - VkExtent2D swapChainExtent = swapChain.GetSwapChainExtent(); - int width = swapChainExtent.width(); - int height = swapChainExtent.height(); + Image ColourImage = AttachmentColour.GetVkImage(); + int width = ColourImage.GetWidth(); + int height = ColourImage.GetHeight(); var Viewport = VkViewport.calloc(1,MemStack) .x(0) .y(height) @@ -239,7 +246,7 @@ public class SceneRender {//dynamic rendering DescriptorAllocator descriptorAllocator = vulkanContext.GetDescriptorAllocator(); LongBuffer DescriptorSets = MemStack.mallocLong(4) .put(0,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_PRJ).GetVkDescriptorSet()) - .put(1,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_VIEW).GetVkDescriptorSet()) + .put(1,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_VIEW, CurrentFrame).GetVkDescriptorSet()) .put(2,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_MAT).GetVkDescriptorSet()) .put(3,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_TEXT).GetVkDescriptorSet()); vkCmdBindDescriptorSets(CommandHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, VkPipeline.GetVulkanPipelineLayout(),0,DescriptorSets,null); @@ -248,12 +255,6 @@ public class SceneRender {//dynamic rendering RenderActors(engineInstance, CommandHandle, modelsCache, materialsCache, true); vkCmdEndRendering(CommandHandle); - - VulkanUtils.ImageBarrier(MemStack, CommandHandle, SwapChainImage, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, - VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_2_NONE, - VK_IMAGE_ASPECT_COLOR_BIT); } } @@ -294,19 +295,23 @@ public class SceneRender {//dynamic rendering private void SetPushConstants(VkCommandBuffer CmdHandle, Matrix4f ModelMatrix, int MaterialIndex){ ModelMatrix.get(0,PushConstBuffer); PushConstBuffer.putInt(VulkanUtils.MATRIX4X4_SIZE, MaterialIndex); - vkCmdPushConstants(CmdHandle, VkPipeline.GetVulkanPipelineLayout(), VK_SHADER_STAGE_VERTEX_BIT, 0, PushConstBuffer.slice(0,VulkanUtils.MATRIX4X4_SIZE)); - vkCmdPushConstants(CmdHandle, VkPipeline.GetVulkanPipelineLayout(), VK_SHADER_STAGE_FRAGMENT_BIT, VulkanUtils.MATRIX4X4_SIZE, PushConstBuffer.slice(VulkanUtils.MATRIX4X4_SIZE, VulkanUtils.INT_SIZE)); + vkCmdPushConstants(CmdHandle, VkPipeline.GetVulkanPipelineLayout(), VK_SHADER_STAGE_VERTEX_BIT, 0, + PushConstBuffer.slice(0,VulkanUtils.MATRIX4X4_SIZE)); + vkCmdPushConstants(CmdHandle, VkPipeline.GetVulkanPipelineLayout(), VK_SHADER_STAGE_FRAGMENT_BIT, VulkanUtils.MATRIX4X4_SIZE, + PushConstBuffer.slice(VulkanUtils.MATRIX4X4_SIZE, VulkanUtils.INT_SIZE)); } public void Resize(EngineInstance instance, VulkanContext VkCtx){ - Arrays.asList(RenderInfo).forEach(VkRenderingInfo::free); - Arrays.asList(AttInfoDepth).forEach(VkRenderingAttachmentInfo::free); - Arrays.asList(AttachmentInfoColour).forEach(VkRenderingAttachmentInfo.Buffer::free); - Arrays.asList(AttDepth).forEach(Attachment->Attachment.CleanUp(VkCtx)); - AttDepth = createDepthAttachments(VkCtx); - AttachmentInfoColour = CreateColourAttachmentsInfo(VkCtx, ClearValueColour); - AttInfoDepth = createDepthAttachmentsInfo(VkCtx, AttDepth, ClearValueDepth); - RenderInfo = CreateRenderInfo(VkCtx, AttachmentInfoColour, AttInfoDepth); + RenderInfo.free(); + AttachmentInfoColour.free(); + AttachmentColour.CleanUp(VkCtx); + AttachmentDepth.CleanUp(VkCtx); + AttachmentInfoDepth.free(); + AttachmentColour = CreateColourAttachment(VkCtx); + AttachmentDepth = CreateDepthAttachment(VkCtx); + AttachmentInfoColour = CreateColourAttachmentInfo(AttachmentColour, ClearValueColour); + AttachmentInfoDepth = CreateDepthAttachmentInfo(AttachmentDepth, ClearValueDepth); + RenderInfo = CreateRenderInfo(AttachmentColour, AttachmentInfoColour, AttachmentInfoDepth); VulkanUtils.CopyMatrixToBuffer(VkCtx, BufferProjectionMatrix, instance.scene().GetProjection().GetProjectionMatrix(),0); } @@ -322,24 +327,19 @@ public class SceneRender {//dynamic rendering descriptorSet.SetImageArray(device,imageViews,textureSampler,0); } - private static VkRenderingInfo[] CreateRenderInfo(VulkanContext vulkanContext, VkRenderingAttachmentInfo.Buffer[] ColourAttachments, VkRenderingAttachmentInfo[] DepthAttachments){ - SwapChain swapChain = vulkanContext.GetSwapChain(); - int ImageCount = swapChain.GetImageCount(); - var Result = new VkRenderingInfo[ImageCount]; - + private static VkRenderingInfo CreateRenderInfo(Attachment ColourAttachment, VkRenderingAttachmentInfo.Buffer ColourAttachmentInfo, VkRenderingAttachmentInfo DepthAttachmentInfo){ + VkRenderingInfo Result; try(var MemStack = MemoryStack.stackPush()){ - VkExtent2D Extent = swapChain.GetSwapChainExtent(); + VkExtent2D Extent = VkExtent2D.calloc(MemStack); + Extent.width(ColourAttachment.GetVkImage().GetWidth()); + Extent.height(ColourAttachment.GetVkImage().GetHeight()); var RenderArea = VkRect2D.calloc(MemStack).extent(Extent); - - for(int i = 0; i < ImageCount; i++){ - var RenderInfo = VkRenderingInfo.calloc() - .sType$Default() - .renderArea(RenderArea) - .layerCount(1) - .pColorAttachments(ColourAttachments[i]) - .pDepthAttachment(DepthAttachments[i]); - Result[i] = RenderInfo; - } + Result = VkRenderingInfo.calloc() + .sType$Default() + .renderArea(RenderArea) + .layerCount(1) + .pColorAttachments(ColourAttachmentInfo) + .pDepthAttachment(DepthAttachmentInfo); } return Result; } @@ -361,12 +361,12 @@ public class SceneRender {//dynamic rendering } public void cleanup(VulkanContext VkCtx){ VkPipeline.CleanUp(VkCtx); - Arrays.asList(RenderInfo).forEach(VkRenderingInfo::free); Arrays.asList(BufferViewMatrices).forEach(buffer -> buffer.cleanup(VkCtx)); - Arrays.asList(AttachmentInfoColour).forEach(VkRenderingAttachmentInfo.Buffer::free); - Arrays.asList(AttInfoDepth).forEach(VkRenderingAttachmentInfo::free); - Arrays.asList(AttachmentInfoColour).forEach(VkRenderingAttachmentInfo.Buffer::free); - Arrays.asList(AttDepth).forEach(Attachment->Attachment.CleanUp(VkCtx)); + RenderInfo.free(); + AttachmentInfoColour.free(); + AttachmentColour.CleanUp(VkCtx); + AttachmentDepth.CleanUp(VkCtx); + AttachmentInfoDepth.free(); MemoryUtil.memFree(PushConstBuffer); ClearValueDepth.free(); ClearValueColour.free(); @@ -376,4 +376,9 @@ public class SceneRender {//dynamic rendering descriptorLayoutVertexUniform.CleanUp(VkCtx); textureSampler.CleanUp(VkCtx); } + + @Override + public Attachment GetAttachmentColour() { + return AttachmentColour; + } } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/DisplayToScreen/SceneRenderer.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/DisplayToScreen/SceneRenderer.java new file mode 100644 index 0000000..56f93ff --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/DisplayToScreen/SceneRenderer.java @@ -0,0 +1,24 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DisplayToScreen; + +import net.halbear.Terrain4J.EngineCore.Logic.EngineInstance; +import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.Attachment; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.TextureCache; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Pipeline; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.DescriptorSetLayout; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.ShaderModule; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.MaterialsCache; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.ModelsCache; +import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.CommandBuffer; +import org.lwjgl.vulkan.VkClearValue; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkRenderingAttachmentInfo; + +public interface SceneRenderer { + + public void Render(EngineInstance engineInstance, VulkanContext vulkanContext, CommandBuffer commandBuffer, ModelsCache modelsCache, MaterialsCache materialsCache, int CurrentFrame); + public void Resize(EngineInstance instance, VulkanContext VkCtx); + public void LoadMaterials(VulkanContext VkCtx, MaterialsCache materialsCache, TextureCache textureCache); + public void cleanup(VulkanContext VkCtx); + public Attachment GetAttachmentColour(); +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/DisplayToScreen/Surface.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/DisplayToScreen/Surface.java index 9905847..52ea5a1 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/DisplayToScreen/Surface.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/DisplayToScreen/Surface.java @@ -15,6 +15,7 @@ import java.nio.IntBuffer; import java.nio.LongBuffer; import static net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils.vkCheck; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8A8_UNORM; import static org.lwjgl.vulkan.VK13.VK_FORMAT_B8G8R8A8_SRGB; public class Surface { @@ -52,11 +53,11 @@ public class Surface { var SurfaceFormats = VkSurfaceFormatKHR.calloc(SurfaceFormatCount, MemStack); vkCheck(KHRSurface.vkGetPhysicalDeviceSurfaceFormatsKHR(PhysDevice.GetPhysicalDevice(),VulkanSurface, IntPointer, SurfaceFormats), "Failed to get surface formats"); - ImageFormat = VK_FORMAT_B8G8R8A8_SRGB; + ImageFormat = VK_FORMAT_B8G8R8A8_UNORM; ColourSpace = SurfaceFormats.get(0).colorSpace(); for(int i = 0; i < SurfaceFormatCount; i++){ VkSurfaceFormatKHR SurfaceFormatKHR = SurfaceFormats.get(i); - if (SurfaceFormatKHR.format() == VK_FORMAT_B8G8R8A8_SRGB && SurfaceFormatKHR.colorSpace() == KHRSurface.VK_COLOR_SPACE_SRGB_NONLINEAR_KHR){ + if (SurfaceFormatKHR.format() == VK_FORMAT_B8G8R8A8_UNORM && SurfaceFormatKHR.colorSpace() == KHRSurface.VK_COLOR_SPACE_SRGB_NONLINEAR_KHR){ ImageFormat = SurfaceFormatKHR.format(); ColourSpace = SurfaceFormatKHR.colorSpace(); break; diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/DisplayToScreen/SwapChain.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/SwapChain/SwapChain.java similarity index 97% rename from src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/DisplayToScreen/SwapChain.java rename to src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/SwapChain/SwapChain.java index c72fe6b..5c46f38 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/DisplayToScreen/SwapChain.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/SwapChain/SwapChain.java @@ -1,10 +1,12 @@ -package net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DisplayToScreen; +package net.halbear.Terrain4J.EngineCore.Vulkan.Structure.SwapChain; import net.halbear.Terrain4J.EngineCore.Display.Window; import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.Device; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.GPUSynchronisation.Semaphore; import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.Queues.Queue; +import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DisplayToScreen.ImageView; +import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DisplayToScreen.Surface; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; import org.lwjgl.vulkan.*; diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/SwapChain/SwapChainRender.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/SwapChain/SwapChainRender.java new file mode 100644 index 0000000..bfd7559 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Structure/SwapChain/SwapChainRender.java @@ -0,0 +1,192 @@ +package net.halbear.Terrain4J.EngineCore.Vulkan.Structure.SwapChain; + +import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; +import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.EmptyVertexBufferStruct; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.DefaultPipeline; +import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.Attachment; +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.Shader.*; +import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.CommandBuffer; +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.util.shaderc.Shaderc; +import org.lwjgl.vulkan.*; + +import java.nio.LongBuffer; +import java.util.Arrays; + +import static org.lwjgl.vulkan.KHRSwapchain.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; +import static org.lwjgl.vulkan.KHRSynchronization2.VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL_KHR; +import static org.lwjgl.vulkan.VK10.*; +import static org.lwjgl.vulkan.VK13.*; + +public class SwapChainRender { + + private static final String DESCRIPTOR_ID_ATTACHMENT = "FWD_DESC_ID_ATT"; + private static final String FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/swap_frag.glsl"; + private static final String FRAGMENT_SHADER_FILE_SPV = FRAGMENT_SHADER_FILE_GLSL + ".spv"; + private static final String VERTEX_SHADER_FILE_GLSL = "resources/EngineResources/shaders/swap_vtx.glsl"; + private static final String VERTEX_SHADER_FILE_SPV = VERTEX_SHADER_FILE_GLSL + ".spv"; + private final DescriptorSetLayout AttachmentDescriptorSetLayout; + private final VkClearValue ClearValueColour; + private final Pipeline pipeline; + private final TextureSampler textureSampler; + private VkRenderingAttachmentInfo.Buffer[] ColourAttachmentsInfo; + private VkRenderingInfo[] RenderInfo; + + public SwapChainRender(VulkanContext VkCtx, Attachment SrcAttachment){ + ClearValueColour = VkClearValue.calloc(); + ClearValueColour.color(c->c.float32(0,0.0f).float32(1,0.0f).float32(2,0.0f).float32(3,0.0f)); + + ColourAttachmentsInfo = CreateColourAttachmentsInfo(VkCtx,ClearValueColour); + RenderInfo = CreateRenderInfo(VkCtx,ColourAttachmentsInfo); + + var TextureSamplerInfo = new TextureSamplerInfo(VK_SAMPLER_ADDRESS_MODE_REPEAT,VK_BORDER_COLOR_INT_OPAQUE_BLACK,1,true); + textureSampler = new TextureSampler(VkCtx,TextureSamplerInfo); + + var LayoutInfo = new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,0,1,VK_SHADER_STAGE_FRAGMENT_BIT); + AttachmentDescriptorSetLayout = new DescriptorSetLayout(VkCtx,LayoutInfo); + CreateAttachmentDescriptorSet(VkCtx,AttachmentDescriptorSetLayout,SrcAttachment,textureSampler); + + ShaderModule[] shaderModules = CreateShaderModules(VkCtx); + + pipeline = CreatePipeline(VkCtx,shaderModules,new DescriptorSetLayout[]{AttachmentDescriptorSetLayout}); + Arrays.asList(shaderModules).forEach(s->s.CleanUp(VkCtx)); + } + + private static void CreateAttachmentDescriptorSet(VulkanContext VkCtx, DescriptorSetLayout descriptorSetLayout, Attachment attachment, TextureSampler sampler){ + DescriptorAllocator descriptorAllocator = VkCtx.GetDescriptorAllocator(); + Device device = VkCtx.GetDevice(); + DescriptorSet descriptorSet = descriptorAllocator.AddDescriptorSets(device,DESCRIPTOR_ID_ATTACHMENT,1,descriptorSetLayout)[0]; + descriptorSet.SetImage(device,attachment.GetVkImageView(),sampler,0); + } + + private static VkRenderingAttachmentInfo.Buffer[] CreateColourAttachmentsInfo(VulkanContext VkCtx, VkClearValue ClearView){ + SwapChain swapChain = VkCtx.GetSwapChain(); + int ImageCount = swapChain.GetImageCount(); + var Result = new VkRenderingAttachmentInfo.Buffer[ImageCount]; + + for(int i = 0; i < ImageCount; i++){ + var Attachments = VkRenderingAttachmentInfo.calloc(1); + Attachments.get(0) + .sType$Default() + .imageView(swapChain.GetVulkanImageView(i).GetVulkanImageView()) + .imageLayout(VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL_KHR) + .loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR) + .storeOp(VK_ATTACHMENT_STORE_OP_STORE) + .clearValue(ClearView); + Result[i] = Attachments; + } + return Result; + } + private static Pipeline CreatePipeline(VulkanContext VkCtx, ShaderModule[] shaderModules, DescriptorSetLayout[] descriptorSetLayouts){ + var VertexBufferStruct = new EmptyVertexBufferStruct(); + var BuildInfo = new PipelineBuildInfo(shaderModules, VertexBufferStruct.GetVertexInput(),VkCtx.GetSurface().GetSurfaceFormat().ImageFormat()).SetDescriptorSetLayouts(descriptorSetLayouts).BlendingIsUsed(true); + var pipeline = new DefaultPipeline(VkCtx,BuildInfo); + VertexBufferStruct.CleanUp(); + return pipeline; + } + + private static VkRenderingInfo[] CreateRenderInfo(VulkanContext VkCtx, VkRenderingAttachmentInfo.Buffer[] ColourAttachments){ + SwapChain swapChain = VkCtx.GetSwapChain(); + int ImageCount = swapChain.GetImageCount(); + var Result = new VkRenderingInfo[ImageCount]; + + try(var MemStack = MemoryStack.stackPush()){ + VkExtent2D extent2D = swapChain.GetSwapChainExtent(); + var RenderArea = VkRect2D.calloc(MemStack).extent(extent2D); + + for(int i = 0; i < ImageCount; i++){ + var renderingInfo = VkRenderingInfo.calloc() + .sType$Default() + .renderArea(RenderArea) + .layerCount(1) + .pColorAttachments(ColourAttachments[i]); + Result[i] = renderingInfo; + } + } + return Result; + } + + 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_glsl_fragment_shader); + } + return new ShaderModule[]{ + new ShaderModule(VkCtx, VK_SHADER_STAGE_VERTEX_BIT, VERTEX_SHADER_FILE_SPV, null), + new ShaderModule(VkCtx, VK_SHADER_STAGE_FRAGMENT_BIT, FRAGMENT_SHADER_FILE_SPV, null) + }; + } + + public void Render(VulkanContext VkCtx, CommandBuffer commandBuffer, Attachment SrcAttachment, int ImageIndex){ + try(var MemStack = MemoryStack.stackPush()){ + SwapChain swapChain = VkCtx.GetSwapChain(); + + long SwapChainImage = swapChain.GetVulkanImageView(ImageIndex).GetVulkanImage(); + VkCommandBuffer CommandHandle = commandBuffer.GetVulkanCommandBuffer(); + + VulkanUtils.ImageBarrier(MemStack,CommandHandle,SwapChainImage, + 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); + VulkanUtils.ImageBarrier(MemStack,CommandHandle,SrcAttachment.GetVkImage().getVulkanImage(), + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,VK_ACCESS_2_SHADER_READ_BIT,VK_IMAGE_ASPECT_COLOR_BIT); + + vkCmdBeginRendering(CommandHandle, RenderInfo[ImageIndex]); + vkCmdBindPipeline(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,pipeline.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); + + DescriptorAllocator descriptorAllocator = VkCtx.GetDescriptorAllocator(); + LongBuffer DescriptorSets = MemStack.mallocLong(1).put(0,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_ATTACHMENT).GetVkDescriptorSet()); + vkCmdBindDescriptorSets(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,pipeline.GetVulkanPipelineLayout(),0,DescriptorSets,null); + + vkCmdDraw(CommandHandle,3,1,0,0); + + vkCmdEndRendering(CommandHandle); + + VulkanUtils.ImageBarrier(MemStack,CommandHandle,SwapChainImage,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, + VK_PIPELINE_STAGE_2_NONE, VK_IMAGE_ASPECT_COLOR_BIT); + } + } + + public void Resize(VulkanContext VkCtx, Attachment SrcAttachment){ + Arrays.asList(RenderInfo).forEach(VkRenderingInfo::free); + Arrays.asList(ColourAttachmentsInfo).forEach(VkRenderingAttachmentInfo.Buffer::free); + ColourAttachmentsInfo = CreateColourAttachmentsInfo(VkCtx,ClearValueColour); + RenderInfo = CreateRenderInfo(VkCtx,ColourAttachmentsInfo); + + DescriptorAllocator descriptorAllocator = VkCtx.GetDescriptorAllocator(); + DescriptorSet descriptorSet = descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_ATTACHMENT); + descriptorSet.SetImage(VkCtx.GetDevice(),SrcAttachment.GetVkImageView(),textureSampler,0); + } + + public void CleanUp(VulkanContext VkCtx){ + textureSampler.CleanUp(VkCtx); + AttachmentDescriptorSetLayout.CleanUp(VkCtx); + pipeline.CleanUp(VkCtx); + Arrays.asList(RenderInfo).forEach(VkRenderingInfo::free); + Arrays.asList(ColourAttachmentsInfo).forEach(VkRenderingAttachmentInfo.Buffer::free); + ClearValueColour.free(); + } +} diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Util/VulkanBuffer.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Util/VulkanBuffer.java index c79ea6b..3af05c7 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Util/VulkanBuffer.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Vulkan/Util/VulkanBuffer.java @@ -56,27 +56,6 @@ public class VulkanBuffer { Buffer = LongPointer.get(0); Allocation = AllocationPointer.get(0); pointerBuffer = MemoryUtil.memAllocPointer(1); - /* var MemoryRequirements = VkMemoryRequirements.calloc(MemStack); - vkGetBufferMemoryRequirements(device.FetchVulkanDevice(), Buffer, MemoryRequirements); - - var MemoryAllocation = VkMemoryAllocateInfo.calloc(MemStack) - .sType$Default() - .allocationSize(MemoryRequirements.size()) - .memoryTypeIndex(VulkanUtils.MemoryTypeFromProperties(vulkanContext, MemoryRequirements.memoryTypeBits(), RequestMask)); - - vkCheck(vkAllocateMemory(device.FetchVulkanDevice(),MemoryAllocation,null,LongPointer),"Failed to allocate memory for Vulkan"); - AllocationSize = MemoryAllocation.allocationSize(); - Memory = LongPointer.get(0); - pointerBuffer = MemoryUtil.memAllocPointer(1); - if (Buffer == 0) { - Logger.error("Vulkan crash blocked: Native bufferHandle cannot be 0!"); - throw new IllegalArgumentException("Vulkan crash blocked: Native bufferHandle cannot be 0!"); - } - if (Memory == 0) { - Logger.error("Vulkan crash blocked: Native memoryHandle cannot be 0! Device out of memory or allocation failed."); - throw new IllegalArgumentException("Vulkan crash blocked: Native memoryHandle cannot be 0! Device out of memory or allocation failed."); - } - vkCheck(vkBindBufferMemory(device.FetchVulkanDevice(),Buffer,Memory,0),"Failed to Bind Vulkan Memory Buffer");*/ } } public void cleanup(VulkanContext vulkanContext){ 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 2ad671c..e9dfe30 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 @@ -10,7 +10,13 @@ import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; import org.lwjgl.vulkan.*; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; import java.util.Locale; import static org.lwjgl.util.vma.Vma.VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; @@ -25,6 +31,7 @@ public class VulkanUtils { public static final int INT_SIZE = 4; public static final int MATRIX4X4_SIZE = 16 * FLOAT_SIZE; public static final int VEC4_SIZE = 4 * FLOAT_SIZE; + public static final int VEC2_SIZE = 2 * FLOAT_SIZE; public static void CopyMatrixToBuffer(VulkanContext VkCtx, VulkanBuffer VkBuffer, Matrix4f matrix, int Offset){ long MappedMemory = VkBuffer.MapMemory(VkCtx); @@ -136,4 +143,24 @@ public class VulkanUtils { throw new RuntimeException(ErrorMessage + ": " + ErrorCode + " [" + Error + "]"); } } + + public static InputStream convertFloatBufferToInputStream(FloatBuffer floatBuffer) { + floatBuffer.rewind(); + byte[] byteArray = new byte[floatBuffer.remaining() * 4]; + ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray); + byteBuffer.order(ByteOrder.nativeOrder()); + byteBuffer.asFloatBuffer().put(floatBuffer); + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray); + return new BufferedInputStream(byteArrayInputStream); + } + + public static InputStream convertIntBufferToInputStream(IntBuffer intBuffer) { + intBuffer.rewind(); + byte[] byteArray = new byte[intBuffer.remaining() * 4]; + ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray); + byteBuffer.order(ByteOrder.nativeOrder()); + byteBuffer.asIntBuffer().put(intBuffer); + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray); + return new BufferedInputStream(byteArrayInputStream); + } } diff --git a/terrain4j.properties b/terrain4j.properties index 25bd983..6162312 100644 --- a/terrain4j.properties +++ b/terrain4j.properties @@ -13,14 +13,21 @@ frame_accuracy=0 throttle_on_unfocus=true throttle_accuracy=500000000 #event tick rate for game process -master_tick_rate=120 +master_tick_rate=240 main_thread_tickrate=120 fast_thread_tickrate=240 -cap_master_to_tickrate=false +cap_master_to_tickrate=true cap_main_to_tickrate=true cap_fast_to_tickrate=true #Renderer Properties +#AA settings +#0=No AA +#1=FXAA +#2=MSAAx2 +#3=MSAAx4 +#4=MSAAx8 +anti_alias_mode=0 Debug_Shaders = false ShaderRecompiling = true #Vulkan Debugging toggle @@ -34,7 +41,7 @@ PhysicalDeviceName= #3D Projection Properties field_of_view=60.0f -z_near_plane=0.10f -z_far_plane=1000.0f +z_far_plane=1f +z_near_plane=10000.0f