Jarvis, Optimise my shit

This commit is contained in:
Halbear 2026-09-25 23:37:22 +01:00
parent d1abde919d
commit 9424b29d0a
50 changed files with 1469 additions and 1232 deletions

View file

@ -5,6 +5,7 @@ import net.halbear.Terrain4J.EngineCore.Logic.EngineInstance;
import net.halbear.Terrain4J.EngineCore.Logic.InitData;
import net.halbear.Terrain4J.EngineCore.Logic.Rendering.SkyBox;
import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.Profiling.VulkanFrameTimings;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration.Biome;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration.Voxel;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration.VoxelChunkGenerator;
@ -56,9 +57,11 @@ public class VulkanRenderer implements Renderer {
private final CommandPool[] CommandPools;
private final Fence[] Fences;
private final Queue.GraphicsQueue GraphicsQueue;
private final VulkanFrameTimings gpuTimings;
public VulkanFrameTimings GetGpuTimings(){return gpuTimings;}
private final Semaphore[] PresentCompleteSemaphores;
private final Queue.PresentQueue PresentQueue;
private final Semaphore[] RenderCompleteSemaphores;
private Semaphore[] RenderCompleteSemaphores;
private final SceneRenderer sceneRender;
private final AmbientOcclusionRenderer ssaoRenderer;
private final ReflectionsRenderer ssrRender;
@ -104,6 +107,7 @@ public class VulkanRenderer implements Renderer {
RendererContext = new VulkanContext(engineInstance.window());
CurrentFrame = 0;
GraphicsQueue = new Queue.GraphicsQueue(RendererContext, 0);
gpuTimings = new VulkanFrameTimings(RendererContext, GraphicsQueue.GetQueueFamilyIndex());
PresentQueue = new Queue.PresentQueue(RendererContext, 0);
CommandPools= new CommandPool[VulkanUtils.MAX_IN_FLIGHT];
CommandBuffers= new CommandBuffer[VulkanUtils.MAX_IN_FLIGHT];
@ -390,9 +394,11 @@ public class VulkanRenderer implements Renderer {
private void RecordingStart(CommandPool commandPool, CommandBuffer commandBuffer){
commandPool.Reset(RendererContext);
commandBuffer.BeginRecording();
gpuTimings.begin(RendererContext, commandBuffer.GetVulkanCommandBuffer(), CurrentFrame);
}
private void RecordingStop(CommandBuffer commandBuffer){
gpuTimings.mark(commandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "UI and present copy");
commandBuffer.EndRecording();
}
@ -401,6 +407,7 @@ public class VulkanRenderer implements Renderer {
Logger.debug("Waiting Vulkan Context");
VoxelWorldManager.Cleanup(RendererContext);
gpuTimings.cleanup(RendererContext);
sceneRender.cleanup(RendererContext);
if(ssaoRenderer != null)ssaoRenderer.cleanup(RendererContext);
@ -453,26 +460,32 @@ public class VulkanRenderer implements Renderer {
var CommandPool = CommandPools[CurrentFrame];
var CommandBuffer = CommandBuffers[CurrentFrame];
RecordingStart(CommandPool, CommandBuffer);
VoxelWorldManager.RecordGeneration(RendererContext,CommandBuffer);
sceneRender.Render(engineInstance,RendererContext,CommandBuffer, modelsCache,materialsCache,CurrentFrame);
boolean RenderShadows = ShadowRenderer.AllowShadowRendering() && EngineConfig.getInstance().RenderShadows();
if(RenderShadows) {shadowRender.render(engineInstance, RendererContext, CommandBuffer, modelsCache, materialsCache, CurrentFrame); }
ssaoRenderer.Render(RendererContext,engineInstance, CommandBuffer, sceneRender.GetMRTAttachments(), CurrentFrame);
if(RenderShadows) {
lightRenderer.render(engineInstance, RendererContext, CommandBuffer, sceneRender.GetMRTAttachments(),
shadowRender.getShadowAttachment(), ssaoRenderer.getSSAOBlurAttachment(), CurrentFrame, shadowRender.getCascadeShadows(CurrentFrame));
} else lightRenderer.render(engineInstance, RendererContext, CommandBuffer, sceneRender.GetMRTAttachments(),ssaoRenderer.getSSAOBlurAttachment(),CurrentFrame);
ssrRender.Render( RendererContext, engineInstance, CommandBuffer, CurrentFrame);
PostProcessor.Render(RendererContext,CommandBuffer,ssrRender.GetSSRAttachment());
spriteRenderer.Render(engineInstance,RendererContext,CommandBuffer,PostProcessor.GetAttachment(),modelsCache,CurrentFrame);
GuiRender.Render(RendererContext,CommandBuffer,CurrentFrame,PostProcessor.GetAttachment());
int ImageIndex;
if (Resize || (ImageIndex = swapChain.FetchNextImage(RendererContext.GetDevice(), PresentCompleteSemaphores[imageAcquisitionIndex])) < 0){
resize(engineInstance);
return;
}
RecordingStart(CommandPool, CommandBuffer);
VoxelWorldManager.RecordGeneration(RendererContext,CommandBuffer,CurrentFrame);
gpuTimings.mark(CommandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "voxel generation");
sceneRender.Render(engineInstance,RendererContext,CommandBuffer, modelsCache,materialsCache,CurrentFrame);
gpuTimings.mark(CommandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "geometry");
boolean RenderShadows = ShadowRenderer.AllowShadowRendering() && EngineConfig.getInstance().RenderShadows();
if(RenderShadows) {shadowRender.render(engineInstance, RendererContext, CommandBuffer, modelsCache, materialsCache, CurrentFrame); }
gpuTimings.mark(CommandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "shadows");
ssaoRenderer.Render(RendererContext,engineInstance, CommandBuffer, sceneRender.GetMRTAttachments(), CurrentFrame);
gpuTimings.mark(CommandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "SSAO");
if(RenderShadows) {
lightRenderer.render(engineInstance, RendererContext, CommandBuffer, sceneRender.GetMRTAttachments(),
shadowRender.getShadowAttachment(), ssaoRenderer.getSSAOBlurAttachment(), CurrentFrame, shadowRender.getCascadeShadows(CurrentFrame));
} else lightRenderer.render(engineInstance, RendererContext, CommandBuffer, sceneRender.GetMRTAttachments(),ssaoRenderer.getSSAOBlurAttachment(),CurrentFrame);
gpuTimings.mark(CommandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "lighting");
ssrRender.Render( RendererContext, engineInstance, CommandBuffer, CurrentFrame);
gpuTimings.mark(CommandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "SSR");
PostProcessor.Render(RendererContext,CommandBuffer,ssrRender.GetSSRAttachment());
gpuTimings.mark(CommandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "bloom and tone mapping");
spriteRenderer.Render(engineInstance,RendererContext,CommandBuffer,PostProcessor.GetAttachment(),modelsCache,CurrentFrame);
GuiRender.Render(RendererContext,CommandBuffer,CurrentFrame,PostProcessor.GetAttachment());
swapChainRender.Render(RendererContext,CommandBuffer,PostProcessor.GetAttachment(),ImageIndex);
@ -495,7 +508,7 @@ public class VulkanRenderer implements Renderer {
return;
}
RecordingStart(CommandPool, CommandBuffer);
VoxelWorldManager.RecordGeneration(RendererContext,CommandBuffer);
VoxelWorldManager.RecordGeneration(RendererContext,CommandBuffer,CurrentFrame);
sceneRender.Render(engineInstance,RendererContext,CommandBuffer, modelsCache,materialsCache,CurrentFrame);
PostProcessor.Render(RendererContext,CommandBuffer,sceneRender.GetAttachmentColour());
spriteRenderer.Render(engineInstance,RendererContext,CommandBuffer,PostProcessor.GetAttachment(),modelsCache,CurrentFrame);
@ -548,7 +561,7 @@ public class VulkanRenderer implements Renderer {
private void resize(EngineInstance engineInstance){
Window window = engineInstance.window();
if(window.getWidth() == 0 && window.getHeight() == 0){
if(window.getWidth() == 0 || window.getHeight() == 0){
return;
}
Resize = false;
@ -557,6 +570,7 @@ public class VulkanRenderer implements Renderer {
Arrays.asList(RenderCompleteSemaphores).forEach(i->i.cleanup(RendererContext));
Arrays.asList(PresentCompleteSemaphores).forEach(i->i.cleanup(RendererContext));
RenderCompleteSemaphores = new Semaphore[RendererContext.GetSwapChain().GetImageCount()];
for(int i = 0; i < VulkanUtils.MAX_IN_FLIGHT; i++){
PresentCompleteSemaphores[i] = new Semaphore(RendererContext);
}
@ -569,13 +583,13 @@ public class VulkanRenderer implements Renderer {
sceneRender.Resize(engineInstance,RendererContext);
if(Deferred) {
List<Attachment> attachments = new ArrayList<>(sceneRender.GetMRTAttachments().GetColourAttachments());
attachments.add(ssaoRenderer.getSSAOBlurAttachment());
attachments.add(shadowRender.getShadowAttachment());
lightRenderer.resize(RendererContext, attachments);
List<Attachment> ssaoAttachments = new ArrayList<>();
ssaoAttachments.add(attachments.get(7));
ssaoAttachments.add(attachments.get(2));
ssaoRenderer.resize(RendererContext, ssaoAttachments);
attachments.add(ssaoRenderer.getSSAOBlurAttachment());
attachments.add(shadowRender.getShadowAttachment());
lightRenderer.resize(RendererContext, attachments);
ssaoAttachments.clear();
ssaoAttachments.add(lightRenderer.getAttachment());
ssaoAttachments.add(attachments.get(0));

View file

@ -90,11 +90,24 @@ public class EngineConfig {
public float SoundVolume = 0.0f;
public float WeaponVolume = 0.0f;
public float AmbientVolume = 0.0f;
public float MaxShadowDistance = 16384.0f;
public float MaxShadowDistance = 96.0f;
private RenderQualitySettings renderQuality = RenderQualitySettings.fromProperties(new Properties());
public float GetSSAOScale(){return renderQuality.ssaoScale();}
public int GetSSAOSamples(){return renderQuality.ssaoSamples();}
public float GetSSRScale(){return renderQuality.ssrScale();}
public int GetSSRMaxSteps(){return renderQuality.ssrSteps();}
public float GetBloomScale(){return renderQuality.bloomScale();}
public int GetBloomPasses(){return renderQuality.bloomPasses();}
public int GetVoxelChunkRadius(){return renderQuality.chunkRadius();}
public int GetVoxelGenerationsPerFrame(){return renderQuality.generationsPerFrame();}
public int GetVoxelFacePoolMiB(){return renderQuality.facePoolMiB();}
public float MaxShadowDistance(){return MaxShadowDistance;}
public void MaxShadowDistance(float newDistance){ MaxShadowDistance = newDistance;}
public void MaxShadowDistance(float newDistance){
MaxShadowDistance = Float.isFinite(newDistance) ? Math.clamp(newDistance, 1.0f, 16384.0f) : 96.0f;
}
public boolean RenderShadows = true;
@ -305,6 +318,7 @@ public class EngineConfig {
}
}
if(SuccessfulLoad) {
renderQuality = RenderQualitySettings.fromProperties(EngineConfigVar);
IconName = (EngineConfigVar.getOrDefault("Software_Icon", "ProgramIcon.png").toString());
LoadingScreenLocation = (EngineConfigVar.getOrDefault("LoadingScreenLocation", LoadingScreenLocation).toString());
IconPath = (EngineConfigVar.getOrDefault("Software_Icon_Path", "/WindowResources/Icon/").toString());
@ -333,7 +347,7 @@ 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()));
MaxShadowDistance = (Float.parseFloat(EngineConfigVar.getOrDefault("max_shadow_distance", 16384.0f).toString()));
MaxShadowDistance(Float.parseFloat(EngineConfigVar.getOrDefault("max_shadow_distance", 96.0f).toString()));
AAValue = (Integer.parseInt(EngineConfigVar.getOrDefault("anti_alias_mode", 1).toString()));
renderer = Integer.parseInt(EngineConfigVar.getOrDefault("Renderer", 1).toString()) == 0 ? Renderer.Forward: Renderer.Deferred;
ShadowMapSize = Integer.parseInt(EngineConfigVar.getOrDefault("ShadowMapSize", 2048).toString());

View file

@ -0,0 +1,38 @@
package net.halbear.Terrain4J.EngineCore.Logic;
import java.util.Properties;
public record RenderQualitySettings(float ssaoScale, int ssaoSamples, float ssrScale, int ssrSteps,
float bloomScale, int bloomPasses, int chunkRadius,
int generationsPerFrame, int facePoolMiB) {
public static RenderQualitySettings fromProperties(Properties properties) {
int passes = integer(properties, "bloom_passes", 2, 0, 10);
return new RenderQualitySettings(
decimal(properties, "ssao_scale", 0.5f, 0.25f, 1),
integer(properties, "ssao_samples", 16, 4, 64),
decimal(properties, "ssr_scale", 0.5f, 0.25f, 1),
integer(properties, "ssr_steps", 24, 4, 128),
decimal(properties, "bloom_scale", 0.25f, 0.125f, 1),
passes + (passes & 1),
integer(properties, "voxel_chunk_radius", 16, 1, 32),
integer(properties, "voxel_generations_per_frame", 16, 16, 64),
integer(properties, "voxel_face_pool_mib", 256, 32, 1024));
}
private static int integer(Properties properties, String key, int fallback, int min, int max) {
try {
return Math.clamp(Integer.parseInt(properties.getProperty(key, Integer.toString(fallback))), min, max);
} catch (NumberFormatException ignored) {
return fallback;
}
}
private static float decimal(Properties properties, String key, float fallback, float min, float max) {
try {
float value = Float.parseFloat(properties.getProperty(key, Float.toString(fallback)));
return Float.isFinite(value) ? Math.clamp(value, min, max) : fallback;
} catch (NumberFormatException ignored) {
return fallback;
}
}
}

View file

@ -118,7 +118,7 @@ public class GameCore implements GameLogic {
// ModelData treeModel = ModelLoader.LoadModel("resources/models/Forest/forest.json");
// models.add(treeModel);
// Actor3D treeEntity = new Actor3D("treeEntity", treeModel.ID(), new Vector3f(0.0f, 00.0f, 0.0f));
// treeEntity.SetScale(0.1f);
// treeEntity.SetScale(10f);
// scene.AddActor(treeEntity);
// materials.addAll(ModelLoader.LoadMaterials("resources/models/Forest/forest_mat.json"));
MelonaData = ModelLoader.LoadModel("resources/models/cube/Cube.json");
@ -200,19 +200,19 @@ public class GameCore implements GameLogic {
}
scene.GetLightingManager().GetAmbientLightColour().set(1.0f, 0.9f, 0.75f);
scene.GetLightingManager().SetAmbientLightIntensity(0.2f);
scene.GetLightingManager().SetAmbientLightIntensity(0.3f);
// scene.GetLightingManager().GetAmbientLightColour().set(0.3f, 0.35f, 0.5f);
// scene.GetLightingManager().SetAmbientLightIntensity(0.015f);
List<ILight> lights = new ArrayList<>();
//SkyLight = new Light(new Vector3f(1.5f, 1.20f, 1.0f),new Vector3f(0.0f, -1.0f, 0.3f), true, 4.00f);
SkyLight = new Light(new Vector3f(2.75f, 1.75f, 0.3f),new Vector3f(0.0f, -1.0f, 0.0f), true, 1.5f);
SkyLight = new Light(new Vector3f(2.25f, 1.75f, 0.5f),new Vector3f(0.0f, -1.0f, 0.0f), true, 1.5f);
// SkyLight = new Light(new Vector3f(0.15f, 0.80f, 1.5f),new Vector3f(0.0f, -1.0f, 0.3f), true, 5.00f);
SkyLight.SetType(Light.LightType.Directional);
lights.add(SkyLight);
lights.add(SkyLight);
//lights.add(SkyLight);
//
// lights.add(new Light(new Vector3f(1.9f + (float)Math.random(),1.5f,1.0f),new Vector3f(-433, 445f, -424f).div(20.0f),false,3000.0f/10.0f+ (float)(Math.random() * 100.0/10.0f)));
// lights.add(new Light(new Vector3f(1.9f+ (float)Math.random(),1.5f,1.0f),new Vector3f(-210, 445f, 460f).div(20.0f),false,700.0f/10.0f + (float)(Math.random() * 150.0f/10.0f)));
@ -686,12 +686,12 @@ public class GameCore implements GameLogic {
Project3D projection = engineInstance.scene().GetProjection();
Vector3f CamRot = cam.GetRotation();
Vector3i ChunkPos = new Vector3i((int)Math.floor(CamPos.x/ (float) VoxelChunkGenerator.CHUNK_SIZE),(int)Math.floor(CamPos.y/ (float)VoxelChunkGenerator.CHUNK_SIZE),(int)Math.floor(CamPos.z/ (float)VoxelChunkGenerator.CHUNK_SIZE));
if(ChunkPos.x != VoxelWorldManager.LastPosition.x || ChunkPos.y != VoxelWorldManager.LastPosition.y || ChunkPos.z != VoxelWorldManager.LastPosition.z || CamRot.x != LastCamRotation.x || CamRot.y != LastCamRotation.y || CamRot.z != LastCamRotation.z) {
VoxelWorldManager.UnloadFarChunks(ChunkPos, 16,cam.GetViewMatrix(),projection.GetProjectionMatrix());
VoxelWorldManager.GenerateChunks(ChunkPos, new Vector3i(16,16,16),cam.GetViewMatrix(),projection.GetProjectionMatrix());
} else {
VoxelWorldManager.UnloadFarChunks(ChunkPos, 96,cam.GetViewMatrix(),projection.GetProjectionMatrix());
VoxelWorldManager.GenerateChunks(ChunkPos, new Vector3i(48,16,48),cam.GetViewMatrix(),projection.GetProjectionMatrix());
if (!PrimaryRuntime.IsServer && !RenderThread.Headless) {
int radius = EngineConfig.getInstance().GetVoxelChunkRadius();
if (!ChunkPos.equals(VoxelWorldManager.LastPosition)) {
VoxelWorldManager.UnloadFarChunks(ChunkPos, radius, cam.GetViewMatrix(), projection.GetProjectionMatrix());
}
VoxelWorldManager.GenerateChunks(ChunkPos, new Vector3i(radius), cam.GetViewMatrix(), projection.GetProjectionMatrix());
}
LastCamRotation.set(CamRot);
if(PrimaryRuntime.IsServer){
@ -753,6 +753,16 @@ public class GameCore implements GameLogic {
DeviceInfo[4] = String.format(" VRAM: " + GPUProfiler.GetVramCapacity());
Metrics.add(ISceneDetails);
Metrics.add(DeviceInfo);
if (PrimaryRuntime.GetRenderThread().GetRenderer() instanceof net.halbear.Terrain4J.EngineCore.Display.VulkanRenderer renderer) {
List<String> gpu = new ArrayList<>();
gpu.add("GPU TIMESTAMPS (ms, completed frame):");
renderer.GetGpuTimings().milliseconds().forEach((pass, time) -> gpu.add(String.format(" %s: %.3f", pass, time)));
gpu.add(String.format(" Chunks: %d visible / %d meshed / %d pending; %d generated/frame",
VoxelWorldManager.GetVisibleLastFrame(), VoxelWorldManager.GetRenderableChunkCount(),
VoxelWorldManager.GetRequestedChunkCount(), VoxelWorldManager.GetGeneratedLastFrame()));
gpu.add(String.format(" Packed faces: %.1f MiB", VoxelWorldManager.GetUsedFaceBytes() / (1024.0 * 1024.0)));
Metrics.add(gpu.toArray(String[]::new));
}
SettingPerformanceMetrics.addAll(Metrics);
SettingPerformanceMetrics.addAll(EngineConfig.getInstance().GetMemoryProfiling());
SettingPerformanceMetrics.addAll(EngineConfig.getInstance().GetSystemProfiling());

View file

@ -0,0 +1,86 @@
package net.halbear.Terrain4J.EngineCore.Profiling;
import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.vulkan.VkCommandBuffer;
import org.lwjgl.vulkan.VkPhysicalDeviceProperties;
import org.lwjgl.vulkan.VkQueryPoolCreateInfo;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.lwjgl.vulkan.VK13.*;
public final class VulkanFrameTimings {
private static final int QUERY_COUNT = 16;
private final long[] pools = new long[VulkanUtils.MAX_IN_FLIGHT];
private final String[][] names = new String[VulkanUtils.MAX_IN_FLIGHT][QUERY_COUNT];
private final int[] counts = new int[VulkanUtils.MAX_IN_FLIGHT];
private final float timestampPeriod;
private final int validBits;
private volatile Map<String, Double> milliseconds = Map.of();
public VulkanFrameTimings(VulkanContext context, int queueFamily) {
validBits = context.GetPhysicalDevice().GetQueueFamilyProperties().get(queueFamily).timestampValidBits();
try (var stack = MemoryStack.stackPush()) {
var properties = VkPhysicalDeviceProperties.calloc(stack);
vkGetPhysicalDeviceProperties(context.GetPhysicalDevice().GetPhysicalDevice(), properties);
timestampPeriod = properties.limits().timestampPeriod();
if (validBits == 0 || timestampPeriod <= 0) return;
var info = VkQueryPoolCreateInfo.calloc(stack).sType$Default()
.queryType(VK_QUERY_TYPE_TIMESTAMP).queryCount(QUERY_COUNT);
var result = stack.mallocLong(1);
for (int i = 0; i < pools.length; i++) {
VulkanUtils.vkCheck(vkCreateQueryPool(context.GetDevice().FetchVulkanDevice(), info, null, result), "Create GPU timing queries");
pools[i] = result.get(0);
}
}
}
// The caller has already waited on this frame slot's fence; never wait for queries here.
public void begin(VulkanContext context, VkCommandBuffer command, int frame) {
if (pools[frame] == 0) return;
if (counts[frame] > 1) {
try (var stack = MemoryStack.stackPush()) {
var result = stack.mallocLong(counts[frame]);
int status = vkGetQueryPoolResults(context.GetDevice().FetchVulkanDevice(), pools[frame],
0, counts[frame], result, Long.BYTES, VK_QUERY_RESULT_64_BIT);
if (status == VK_SUCCESS) {
Map<String, Double> times = new LinkedHashMap<>();
for (int i = 1; i < counts[frame]; i++) {
times.put(names[frame][i], elapsed(result.get(i - 1), result.get(i), validBits, timestampPeriod));
}
times.put("total", elapsed(result.get(0), result.get(counts[frame] - 1), validBits, timestampPeriod));
milliseconds = Collections.unmodifiableMap(times);
}
}
}
counts[frame] = 0;
vkCmdResetQueryPool(command, pools[frame], 0, QUERY_COUNT);
mark(command, frame, "start");
}
public void mark(VkCommandBuffer command, int frame, String name) {
if (pools[frame] == 0) return;
int index = counts[frame]++;
if (index >= QUERY_COUNT) throw new IllegalStateException("Too many GPU timing markers");
names[frame][index] = name;
vkCmdWriteTimestamp2(command, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, pools[frame], index);
}
static double elapsed(long start, long end, int bits, float period) {
long delta = end - start;
if (bits < 64) delta &= (1L << bits) - 1;
return delta * (double) period / 1_000_000.0;
}
public Map<String, Double> milliseconds() {return milliseconds;}
public void cleanup(VulkanContext context) {
for (long pool : pools) {
if (pool != 0) vkDestroyQueryPool(context.GetDevice().FetchVulkanDevice(), pool, null);
}
}
}

View file

@ -37,56 +37,16 @@ public class GLMesh {
verticesBuffer.put(0, vertices);
glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(
0,
3,
GL_FLOAT,
false,
14 * Float.BYTES,
0L
);
glVertexAttribPointer(0, 3, GL_FLOAT, false, 14 * Float.BYTES, 0L);
glEnableVertexAttribArray(1);
glVertexAttribPointer(
1,
3,
GL_FLOAT,
false,
14 * Float.BYTES,
3L * Float.BYTES
);
glVertexAttribPointer(1, 3, GL_FLOAT, false, 14 * Float.BYTES, 3L * Float.BYTES);
glEnableVertexAttribArray(2);
glVertexAttribPointer(
2,
3,
GL_FLOAT,
false,
14 * Float.BYTES,
6L * Float.BYTES
);
glVertexAttribPointer(2, 3, GL_FLOAT, false, 14 * Float.BYTES, 6L * Float.BYTES);
glEnableVertexAttribArray(3);
glVertexAttribPointer(
3,
3,
GL_FLOAT,
false,
14 * Float.BYTES,
9L * Float.BYTES
);
glVertexAttribPointer(3, 3, GL_FLOAT, false, 14 * Float.BYTES, 9L * Float.BYTES);
glEnableVertexAttribArray(4);
glVertexAttribPointer(
4,
2,
GL_FLOAT,
false,
14 * Float.BYTES,
12L * Float.BYTES
);
glVertexAttribPointer(4, 2, GL_FLOAT, false, 14 * Float.BYTES, 12L * Float.BYTES);
vboId = glGenBuffers();
VBO_ID_List.add(vboId);
@ -94,23 +54,19 @@ public class GLMesh {
indicesBuffer.put(0, indices);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
MemoryUtil.memFree(verticesBuffer);
MemoryUtil.memFree(indicesBuffer);
}
public void CleanUp() {
VBO_ID_List.forEach(GL30::glDeleteBuffers);
glDeleteVertexArrays(VAO_ID);
}
public int GetVertexCount() {
return VertexCount;
}
public final int GetVaoID() {
return VAO_ID;
}

View file

@ -0,0 +1,50 @@
package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration;
import java.util.TreeMap;
public final class FaceRangeAllocator {
private final TreeMap<Integer, Integer> free = new TreeMap<>();
private final TreeMap<Integer, Integer> allocated = new TreeMap<>();
private int freeFaces;
public FaceRangeAllocator(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException("Face capacity must be positive");
free.put(0, capacity);
freeFaces = capacity;
}
public int allocate(int count) {
if (count <= 0) throw new IllegalArgumentException("Face count must be positive");
for (var range : free.entrySet()) {
if (range.getValue() < count) continue;
int offset = range.getKey();
int remaining = range.getValue() - count;
free.remove(offset);
if (remaining > 0) free.put(offset + count, remaining);
allocated.put(offset, count);
freeFaces -= count;
return offset;
}
return -1;
}
public void free(int offset) {
Integer count = allocated.remove(offset);
if (count == null) throw new IllegalArgumentException("Unknown face allocation");
freeFaces += count;
var previous = free.lowerEntry(offset);
if (previous != null && previous.getKey() + previous.getValue() == offset) {
offset = previous.getKey();
count += previous.getValue();
free.remove(previous.getKey());
}
var next = free.ceilingEntry(offset);
if (next != null && offset + count == next.getKey()) {
count += next.getValue();
free.remove(next.getKey());
}
free.put(offset, count);
}
public int freeFaces() {return freeFaces;}
}

View file

@ -0,0 +1,20 @@
package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration;
import org.joml.Vector3i;
import java.util.Objects;
/** Faces remain valid until recordGenerateChunks reuses their frame slot. */
public record GeneratedVoxelChunk(Vector3i position, int faceCount, long sourceBuffer, long sourceOffsetBytes) {
public GeneratedVoxelChunk {
position = new Vector3i(Objects.requireNonNull(position));
if (faceCount < 0 || faceCount > VoxelChunkGenerator.MAX_FACES_PER_CHUNK) {
throw new IllegalArgumentException("Invalid generated face count: " + Integer.toUnsignedLong(faceCount));
}
}
@Override
public Vector3i position() {
return new Vector3i(position);
}
}

View file

@ -8,6 +8,7 @@ import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.P
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Shader.*;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.Device;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanBuffer;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.shaderc.Shaderc;
@ -15,17 +16,21 @@ import java.nio.ByteBuffer;
import java.util.List;
import static org.lwjgl.util.vma.Vma.VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
import static org.lwjgl.util.vma.Vma.VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
import static org.lwjgl.util.vma.Vma.VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
import static org.lwjgl.util.vma.Vma.VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
import static org.lwjgl.vulkan.VK13.*;
public class SharedVoxelTerrainResources {
public static final int CHUNK_SIZE = 16;
public static final int VOXEL_COUNT = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;
public static final int SCRATCH_SIZE = CHUNK_SIZE + 2;
public static final int SCRATCH_VOXEL_COUNT = SCRATCH_SIZE * SCRATCH_SIZE * SCRATCH_SIZE;
public static final int DRAW_INDIRECT_COMMAND_SIZE = 4 * Integer.BYTES;
public static final int COUNTER_BUFFER_SIZE = Integer.BYTES;
public static final int TERRAIN_GENERATION_PUSH_CONSTANT_SIZE = Integer.BYTES * 10;
public static final int TERRAIN_GENERATION_PUSH_CONSTANT_SIZE = Integer.BYTES * 9;
public static final int MAX_VOXEL_TYPES = 256;
public static final int MAX_BIOME_TYPES = 256;
@ -46,8 +51,11 @@ public class SharedVoxelTerrainResources {
private final VoxelMeshPool meshPool;
private final VulkanBuffer voxelData;
private final VulkanBuffer counters;
// Each frame has disjoint, fixed-size job slices; no dispatch shares voxel/counter storage.
private final VulkanBuffer[] voxelData = new VulkanBuffer[VulkanUtils.MAX_IN_FLIGHT];
private final VulkanBuffer[] counters = new VulkanBuffer[VulkanUtils.MAX_IN_FLIGHT];
private final VulkanBuffer[] scratchFaces = new VulkanBuffer[VulkanUtils.MAX_IN_FLIGHT];
private final VulkanBuffer[] countReadback = new VulkanBuffer[VulkanUtils.MAX_IN_FLIGHT];
private final VulkanBuffer voxelRegistryBuffer;
private final VulkanBuffer biomeRegistryBuffer;
private final VulkanBuffer structureRegistryBuffer;
@ -76,11 +84,22 @@ public class SharedVoxelTerrainResources {
public SharedVoxelTerrainResources(VulkanContext VkCtx, int maxResidentChunks) {
meshPool = new VoxelMeshPool(VkCtx, maxResidentChunks);
voxelData = new VulkanBuffer(VkCtx, (long) VOXEL_COUNT * Integer.BYTES,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0);
counters = new VulkanBuffer(VkCtx, COUNTER_BUFFER_SIZE, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0);
for (int frame = 0; frame < VulkanUtils.MAX_IN_FLIGHT; frame++) {
voxelData[frame] = new VulkanBuffer(VkCtx,
(long) SCRATCH_VOXEL_COUNT * Integer.BYTES * VoxelChunkGenerator.MAX_BATCH_SIZE,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0);
counters[frame] = new VulkanBuffer(VkCtx, (long) COUNTER_BUFFER_SIZE * VoxelChunkGenerator.MAX_BATCH_SIZE,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0);
scratchFaces[frame] = new VulkanBuffer(VkCtx,
VoxelChunkGenerator.SCRATCH_FACE_BYTES_PER_JOB * VoxelChunkGenerator.MAX_BATCH_SIZE,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0);
countReadback[frame] = new VulkanBuffer(VkCtx, (long) COUNTER_BUFFER_SIZE * VoxelChunkGenerator.MAX_BATCH_SIZE,
VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_HOST,
VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
}
voxelRegistryBuffer = new VulkanBuffer(VkCtx, (long) MAX_VOXEL_TYPES * VOXEL_REG_DATA_SIZE,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
@ -104,15 +123,13 @@ public class SharedVoxelTerrainResources {
});
resetLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, 1, VK_SHADER_STAGE_COMPUTE_BIT),
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, 1, VK_SHADER_STAGE_COMPUTE_BIT)
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, 1, VK_SHADER_STAGE_COMPUTE_BIT)
});
meshGenerationLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, 1, VK_SHADER_STAGE_COMPUTE_BIT),
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, 1, VK_SHADER_STAGE_COMPUTE_BIT),
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2, 1, VK_SHADER_STAGE_COMPUTE_BIT),
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3, 1, VK_SHADER_STAGE_COMPUTE_BIT)
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2, 1, VK_SHADER_STAGE_COMPUTE_BIT)
});
faceGraphicsLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{
@ -165,18 +182,16 @@ public class SharedVoxelTerrainResources {
DescriptorAllocator allocator = VkCtx.GetDescriptorAllocator();
Device device = VkCtx.GetDevice();
DescriptorSet resetSet = allocator.AddDescriptorSet(device, DESC_ID_RESET, resetLayout);
resetSet.SetBuffer(device, meshPool.indirectPool(), meshPool.indirectPool().GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
resetSet.SetBuffer(device, counters, counters.GetRequestedSize(), 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
DescriptorSet voxelSet = allocator.AddDescriptorSet(device, DESC_ID_VOXEL_GENERATION, voxelGenerationLayout);
voxelSet.SetBuffer(device, voxelData, voxelData.GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
DescriptorSet meshSet = allocator.AddDescriptorSet(device, DESC_ID_MESH, meshGenerationLayout);
meshSet.SetBuffer(device, voxelData, voxelData.GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
meshSet.SetBuffer(device, meshPool.facePool(), meshPool.facePool().GetRequestedSize(), 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
meshSet.SetBuffer(device, meshPool.indirectPool(), meshPool.indirectPool().GetRequestedSize(), 2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
meshSet.SetBuffer(device, counters, counters.GetRequestedSize(), 3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
DescriptorSet[] resetSets = allocator.AddDescriptorSets(device, DESC_ID_RESET, VulkanUtils.MAX_IN_FLIGHT, resetLayout);
DescriptorSet[] voxelSets = allocator.AddDescriptorSets(device, DESC_ID_VOXEL_GENERATION, VulkanUtils.MAX_IN_FLIGHT, voxelGenerationLayout);
DescriptorSet[] meshSets = allocator.AddDescriptorSets(device, DESC_ID_MESH, VulkanUtils.MAX_IN_FLIGHT, meshGenerationLayout);
for (int frame = 0; frame < VulkanUtils.MAX_IN_FLIGHT; frame++) {
resetSets[frame].SetBuffer(device, counters[frame], counters[frame].GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
voxelSets[frame].SetBuffer(device, voxelData[frame], voxelData[frame].GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
meshSets[frame].SetBuffer(device, voxelData[frame], voxelData[frame].GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
meshSets[frame].SetBuffer(device, scratchFaces[frame], scratchFaces[frame].GetRequestedSize(), 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
meshSets[frame].SetBuffer(device, counters[frame], counters[frame].GetRequestedSize(), 2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
}
DescriptorSet faceGraphicsSet = allocator.AddDescriptorSet(device, DESC_ID_FACE_GRAPHICS, faceGraphicsLayout);
faceGraphicsSet.SetBuffer(device, meshPool.facePool(), meshPool.facePool().GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
@ -311,12 +326,20 @@ public class SharedVoxelTerrainResources {
return meshPool;
}
public VulkanBuffer voxelData() {
return voxelData;
public VulkanBuffer voxelData(int currentFrame) {
return voxelData[currentFrame];
}
public VulkanBuffer counters() {
return counters;
public VulkanBuffer counters(int currentFrame) {
return counters[currentFrame];
}
public VulkanBuffer scratchFaces(int currentFrame) {
return scratchFaces[currentFrame];
}
public VulkanBuffer countReadback(int currentFrame) {
return countReadback[currentFrame];
}
public Pipeline voxelGenerationPipeline() {
@ -363,11 +386,18 @@ public class SharedVoxelTerrainResources {
faceGraphicsLayout.CleanUp(VkCtx);
voxelDeclarationLayout.CleanUp(VkCtx);
biomeDeclarationLayout.CleanUp(VkCtx);
structureDeclarationLayout.CleanUp(VkCtx);
voxelData.cleanup(VkCtx);
counters.cleanup(VkCtx);
for (int frame = 0; frame < VulkanUtils.MAX_IN_FLIGHT; frame++) {
voxelData[frame].cleanup(VkCtx);
counters[frame].cleanup(VkCtx);
scratchFaces[frame].cleanup(VkCtx);
countReadback[frame].cleanup(VkCtx);
}
voxelRegistryBuffer.cleanup(VkCtx);
biomeRegistryBuffer.cleanup(VkCtx);
structureRegistryBuffer.cleanup(VkCtx);
structureVoxelDataBuffer.cleanup(VkCtx);
meshPool.cleanup(VkCtx);
}
}

View file

@ -4,11 +4,18 @@ import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Shader.DescriptorAllocator;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.CommandBuffer;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils;
import org.joml.Vector3i;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.vulkan.VkBufferCopy;
import org.lwjgl.vulkan.VkCommandBuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.LongBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static org.lwjgl.vulkan.VK13.*;
@ -16,83 +23,164 @@ public class VoxelChunkGenerator {
public static final int CHUNK_SIZE = 16;
public static final int LOCAL_SIZE = 4;
public static final int DISPATCH_SIZE = CHUNK_SIZE / LOCAL_SIZE;
public static final int MAX_BATCH_SIZE = 64;
public static final int MAX_FACES_PER_CHUNK = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE * 6;
public static final long SCRATCH_FACE_BYTES_PER_JOB = (long) MAX_FACES_PER_CHUNK * Integer.BYTES;
public static final int VOXEL_DISPATCH_SIZE = (SharedVoxelTerrainResources.SCRATCH_SIZE + LOCAL_SIZE - 1) / LOCAL_SIZE;
private final SharedVoxelTerrainResources resources;
private final GenerationBatch[] pending = new GenerationBatch[VulkanUtils.MAX_IN_FLIGHT];
public VoxelChunkGenerator(SharedVoxelTerrainResources resources) {
this.resources = resources;
for (int frame = 0; frame < pending.length; frame++) {
pending[frame] = new GenerationBatch();
}
}
public void recordGenerateChunk(VulkanContext VkCtx,CommandBuffer commandBuffer, RenderChunk chunk) {
/**
* Call only after currentFrame's submission fence signals. This never waits on the GPU.
* Results are consumed once, including empty chunks. Copy their faces before recording
* another batch into this frame slot; other frame slots do not invalidate them.
*/
public List<GeneratedVoxelChunk> collectCompleted(VulkanContext VkCtx, int currentFrame) {
GenerationBatch batch = pending[Objects.checkIndex(currentFrame, pending.length)];
if (batch.positions.isEmpty()) {
return List.of();
}
long address = resources.countReadback(currentFrame).MapMemory(VkCtx);
ByteBuffer counts = MemoryUtil.memByteBuffer(address, batch.positions.size() * Integer.BYTES);
return batch.collect(counts, resources.scratchFaces(currentFrame).GetBuffer());
}
/**
* Records one batch (at most MAX_BATCH_SIZE), without submission or waits. The caller
* limits scheduling using EngineConfig.GetVoxelGenerationsPerFrame(), collects this
* frame's previous results after its fence, and records scratch-to-pool copies first.
*/
public void recordGenerateChunks(VulkanContext VkCtx, CommandBuffer commandBuffer,
List<Vector3i> positions, int currentFrame) {
GenerationBatch batch = pending[Objects.checkIndex(currentFrame, pending.length)];
batch.stage(positions);
if (batch.positions.isEmpty()) {
return;
}
try (MemoryStack stack = MemoryStack.stackPush()) {
VkCommandBuffer cmd = commandBuffer.GetVulkanCommandBuffer();
DescriptorAllocator allocator = VkCtx.GetDescriptorAllocator();
ByteBuffer pushConstants = stack.malloc(SharedVoxelTerrainResources.TERRAIN_GENERATION_PUSH_CONSTANT_SIZE);
int indirectCommandIndex = (int) (chunk.indirectCommandOffsetBytes() / VoxelMeshPool.DRAW_INDIRECT_COMMAND_SIZE);
int faceOffset = (int) (chunk.faceOffsetBytes() / VoxelMeshPool.FACE_RECORD_SIZE_BYTES);
pushConstants.putInt(0, chunk.position().x);
pushConstants.putInt(4, chunk.position().y);
pushConstants.putInt(8, chunk.position().z);
pushConstants.putInt(12, chunk.slot());
pushConstants.putInt(16, faceOffset);
pushConstants.putInt(20, VoxelWorldManager.GetVoxelTypeCount());
pushConstants.putInt(24, indirectCommandIndex);
pushConstants.putInt(28, VoxelWorldManager.GetBiomeTypeCount());
pushConstants.putInt(32, VoxelWorldManager.GetStructureTypeCount());
pushConstants.putInt(36, VoxelWorldManager.GetWorldSeed());
pushConstants.putInt(24, VoxelWorldManager.GetBiomeTypeCount());
pushConstants.putInt(28, VoxelWorldManager.GetStructureTypeCount());
pushConstants.putInt(32, VoxelWorldManager.GetWorldSeed());
LongBuffer resetDescriptorSet = stack.longs(allocator.GetDescriptorSet(
resources.resetDescriptorId()).GetVkDescriptorSet());
resources.resetDescriptorId(), currentFrame).GetVkDescriptorSet());
LongBuffer voxelDescriptorSet = stack.longs(
allocator.GetDescriptorSet(resources.voxelGenerationDescriptorId()).GetVkDescriptorSet(),
allocator.GetDescriptorSet(resources.voxelGenerationDescriptorId(), currentFrame).GetVkDescriptorSet(),
allocator.GetDescriptorSet(resources.biomeRegDescriptorID()).GetVkDescriptorSet(),
allocator.GetDescriptorSet(resources.structureRegDescriptorID()).GetVkDescriptorSet()
);
allocator.GetDescriptorSet(resources.structureRegDescriptorID()).GetVkDescriptorSet());
LongBuffer meshDescriptorSet = stack.longs(
allocator.GetDescriptorSet(resources.meshDescriptorId()).GetVkDescriptorSet(),
allocator.GetDescriptorSet(resources.meshDescriptorId(), currentFrame).GetVkDescriptorSet(),
allocator.GetDescriptorSet(resources.voxelRegDescriptorID()).GetVkDescriptorSet());
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.resetPipeline().GetVulkanPipeline());
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.resetPipeline().GetVulkanPipelineLayout(),0, resetDescriptorSet,null );
vkCmdPushConstants( cmd, resources.resetPipeline().GetVulkanPipelineLayout(),VK_SHADER_STAGE_COMPUTE_BIT,0, pushConstants );
vkCmdDispatch(cmd, 1, 1, 1);
// Includes the parent's scratch-to-pool transfers earlier in this command buffer.
VulkanUtils.BufferBarriers(stack, cmd, new long[]{
resources.meshPool().indirectPoolHandle(),
resources.counters().GetBuffer()
}, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
resources.scratchFaces(currentFrame).GetBuffer(),
resources.voxelData(currentFrame).GetBuffer(),
resources.counters(currentFrame).GetBuffer()
}, VK_PIPELINE_STAGE_2_TRANSFER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
VK_ACCESS_2_SHADER_WRITE_BIT,
VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT
);
VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT,
VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT);
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.voxelGenerationPipeline().GetVulkanPipeline());
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.voxelGenerationPipeline().GetVulkanPipelineLayout(),0, voxelDescriptorSet,null );
vkCmdPushConstants( cmd, resources.voxelGenerationPipeline().GetVulkanPipelineLayout(),VK_SHADER_STAGE_COMPUTE_BIT,0, pushConstants );
vkCmdDispatch(cmd, DISPATCH_SIZE, DISPATCH_SIZE, DISPATCH_SIZE);
long resetLayout = resources.resetPipeline().GetVulkanPipelineLayout();
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resources.resetPipeline().GetVulkanPipeline());
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resetLayout, 0, resetDescriptorSet, null);
for (int job = 0; job < batch.positions.size(); job++) {
putJobConstants(pushConstants, batch.positions.get(job), job);
vkCmdPushConstants(cmd, resetLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, pushConstants);
vkCmdDispatch(cmd, 1, 1, 1);
}
VulkanUtils.BufferBarrier(stack, cmd,resources.voxelData().GetBuffer(),
VulkanUtils.BufferBarrier(stack, cmd, resources.counters(currentFrame).GetBuffer(),
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
VK_ACCESS_2_SHADER_WRITE_BIT, VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT);
long voxelLayout = resources.voxelGenerationPipeline().GetVulkanPipelineLayout();
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resources.voxelGenerationPipeline().GetVulkanPipeline());
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, voxelLayout, 0, voxelDescriptorSet, null);
for (int job = 0; job < batch.positions.size(); job++) {
putJobConstants(pushConstants, batch.positions.get(job), job);
vkCmdPushConstants(cmd, voxelLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, pushConstants);
vkCmdDispatch(cmd, VOXEL_DISPATCH_SIZE, VOXEL_DISPATCH_SIZE, VOXEL_DISPATCH_SIZE);
}
VulkanUtils.BufferBarrier(stack, cmd, resources.voxelData(currentFrame).GetBuffer(),
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
VK_ACCESS_2_SHADER_WRITE_BIT, VK_ACCESS_2_SHADER_READ_BIT);
vkCmdBindPipeline(cmd,VK_PIPELINE_BIND_POINT_COMPUTE,resources.meshGenerationPipeline().GetVulkanPipeline() );
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.meshGenerationPipeline().GetVulkanPipelineLayout(),0, meshDescriptorSet,null );
vkCmdPushConstants( cmd, resources.meshGenerationPipeline().GetVulkanPipelineLayout(),VK_SHADER_STAGE_COMPUTE_BIT,0, pushConstants );
vkCmdDispatch(cmd, DISPATCH_SIZE, DISPATCH_SIZE, DISPATCH_SIZE);
long meshLayout = resources.meshGenerationPipeline().GetVulkanPipelineLayout();
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resources.meshGenerationPipeline().GetVulkanPipeline());
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, meshLayout, 0, meshDescriptorSet, null);
for (int job = 0; job < batch.positions.size(); job++) {
putJobConstants(pushConstants, batch.positions.get(job), job);
vkCmdPushConstants(cmd, meshLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, pushConstants);
vkCmdDispatch(cmd, DISPATCH_SIZE, DISPATCH_SIZE, DISPATCH_SIZE);
}
VulkanUtils.BufferBarriers(stack, cmd, new long[]{
resources.voxelData().GetBuffer(), resources.counters().GetBuffer(),
resources.meshPool().facePoolHandle(), resources.meshPool().indirectPoolHandle()
}, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT,
VK_ACCESS_2_SHADER_WRITE_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT |
VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT);
resources.scratchFaces(currentFrame).GetBuffer(), resources.counters(currentFrame).GetBuffer()
}, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_2_TRANSFER_BIT,
VK_ACCESS_2_SHADER_WRITE_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
VkBufferCopy.Buffer copy = VkBufferCopy.calloc(1, stack)
.srcOffset(0).dstOffset(0).size((long) batch.positions.size() * Integer.BYTES);
vkCmdCopyBuffer(cmd, resources.counters(currentFrame).GetBuffer(),
resources.countReadback(currentFrame).GetBuffer(), copy);
VulkanUtils.BufferBarrier(stack, cmd, resources.countReadback(currentFrame).GetBuffer(),
VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_PIPELINE_STAGE_2_HOST_BIT,
VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_ACCESS_2_HOST_READ_BIT);
}
}
static void putJobConstants(ByteBuffer pushConstants, Vector3i position, int job) {
Objects.checkIndex(job, MAX_BATCH_SIZE);
pushConstants.putInt(0, position.x);
pushConstants.putInt(4, position.y);
pushConstants.putInt(8, position.z);
pushConstants.putInt(12, job);
pushConstants.putInt(16, job * MAX_FACES_PER_CHUNK);
}
static final class GenerationBatch {
private List<Vector3i> positions = List.of();
void stage(List<Vector3i> requested) {
if (!positions.isEmpty()) {
throw new IllegalStateException("Collect completed generation before reusing this frame slot");
}
if (requested.size() > MAX_BATCH_SIZE) {
throw new IllegalArgumentException("Voxel generation batch exceeds " + MAX_BATCH_SIZE);
}
List<Vector3i> snapshot = new ArrayList<>(requested.size());
for (Vector3i position : requested) {
snapshot.add(new Vector3i(Objects.requireNonNull(position)));
}
positions = List.copyOf(snapshot);
}
List<GeneratedVoxelChunk> collect(ByteBuffer counts, long sourceBuffer) {
ByteBuffer nativeCounts = counts.duplicate().order(ByteOrder.nativeOrder());
List<GeneratedVoxelChunk> completed = new ArrayList<>(positions.size());
for (int job = 0; job < positions.size(); job++) {
int faceCount = nativeCounts.getInt(job * Integer.BYTES);
completed.add(new GeneratedVoxelChunk(positions.get(job), faceCount,
sourceBuffer, job * SCRATCH_FACE_BYTES_PER_JOB));
}
positions = List.of();
return List.copyOf(completed);
}
}
}

View file

@ -0,0 +1,35 @@
package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration;
import org.joml.FrustumIntersection;
import org.joml.Matrix4f;
import org.joml.Vector3i;
public final class VoxelDrawData {
private VoxelDrawData() {}
public static int encodeOffset(Vector3i position, Vector3i center) {
int x = position.x - center.x + 512;
int y = position.y - center.y + 512;
int z = position.z - center.z + 512;
if (x < 0 || x > 1023 || y < 0 || y > 1023 || z < 0 || z > 1023) {
throw new IllegalArgumentException("Chunk outside indirect draw coordinate range");
}
return x | (y << 10) | (z << 20);
}
public static FrustumIntersection frustum(Matrix4f matrix) {
// JOML's frustum test expects -w <= z <= w; Vulkan clips 0 <= z <= w.
Matrix4f clip = new Matrix4f(matrix);
clip.m02(2 * matrix.m02() - matrix.m03());
clip.m12(2 * matrix.m12() - matrix.m13());
clip.m22(2 * matrix.m22() - matrix.m23());
clip.m32(2 * matrix.m32() - matrix.m33());
return new FrustumIntersection(clip);
}
public static boolean withinRadius(Vector3i position, Vector3i center, int radius) {
return Math.abs((long) position.x - center.x) <= radius
&& Math.abs((long) position.y - center.y) <= radius
&& Math.abs((long) position.z - center.z) <= radius;
}
}

View file

@ -1,114 +1,88 @@
package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration;
import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig;
import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanBuffer;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector3i;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.vulkan.VkPhysicalDeviceProperties;
import org.tinylog.Logger;
import java.util.ArrayDeque;
import java.util.Queue;
import static org.lwjgl.util.vma.Vma.VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
import static org.lwjgl.util.vma.Vma.*;
import static org.lwjgl.vulkan.VK13.*;
public class VoxelMeshPool{
public class VoxelMeshPool {
public static final int CHUNK_SIZE = 16;
public static final int MAX_VISIBLE_FACES_PER_CHUNK = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE * 6;
public static final int FACE_RECORD_SIZE_BYTES = Integer.BYTES;
public static final int DRAW_INDIRECT_COMMAND_SIZE = 4 * Integer.BYTES;
private final int maxChunks;
private final long faceSlotSizeBytes;
private final long indirectSlotSizeBytes;
private final VulkanBuffer facePool;
private final VulkanBuffer indirectPool;
private final VulkanBuffer[][] drawBuffers = new VulkanBuffer[VulkanUtils.MAX_IN_FLIGHT][2];
private final FaceRangeAllocator ranges;
private final Queue<Integer> freeSlots = new ArrayDeque<>();
public VoxelMeshPool(VulkanContext VkCtx, int maxChunks) {
this.maxChunks = maxChunks;
this.faceSlotSizeBytes = (long) MAX_VISIBLE_FACES_PER_CHUNK * FACE_RECORD_SIZE_BYTES;
this.indirectSlotSizeBytes = DRAW_INDIRECT_COMMAND_SIZE;
long facePoolSize = faceSlotSizeBytes * maxChunks;
long indirectPoolSize = indirectSlotSizeBytes * maxChunks;
facePool = new VulkanBuffer(VkCtx,facePoolSize,VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,0,0);
indirectPool = new VulkanBuffer(VkCtx,indirectPoolSize,VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,0,0);
for (int i = 0; i < maxChunks; i++) {
freeSlots.add(i);
long requestedBytes = (long) EngineConfig.getInstance().GetVoxelFacePoolMiB() * 1024 * 1024;
long facePoolSize;
try (var stack = MemoryStack.stackPush()) {
var properties = VkPhysicalDeviceProperties.calloc(stack);
vkGetPhysicalDeviceProperties(VkCtx.GetPhysicalDevice().GetPhysicalDevice(), properties);
facePoolSize = Math.min(requestedBytes, Integer.toUnsignedLong(properties.limits().maxStorageBufferRange()));
}
facePoolSize -= facePoolSize % FACE_RECORD_SIZE_BYTES;
ranges = new FaceRangeAllocator(Math.toIntExact(facePoolSize / FACE_RECORD_SIZE_BYTES));
facePool = new VulkanBuffer(VkCtx, facePoolSize,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0);
for (int frame = 0; frame < drawBuffers.length; frame++) {
for (int pass = 0; pass < 2; pass++) {
drawBuffers[frame][pass] = new VulkanBuffer(VkCtx, (long) maxChunks * DRAW_INDIRECT_COMMAND_SIZE,
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
}
}
for (int i = 0; i < maxChunks; i++) freeSlots.add(i);
Logger.info("Voxel face pool: {} MiB, {} resident chunk IDs", facePoolSize / (1024 * 1024), maxChunks);
}
public RenderChunk allocate(Vector3i position) {
Integer slot = freeSlots.poll();
if (slot == null) {
return null;
public RenderChunk allocate(Vector3i position, int faceCount) {
if (faceCount <= 0 || faceCount > MAX_VISIBLE_FACES_PER_CHUNK) {
throw new IllegalArgumentException("Invalid generated face count: " + faceCount);
}
return new RenderChunk(
new Vector3i(position),
slot,
faceOffsetBytes(slot),
MAX_VISIBLE_FACES_PER_CHUNK,
indirectCommandOffsetBytes(slot),
new Matrix4f().identity().translate(
new Vector3f(
position.x * CHUNK_SIZE,
position.y * CHUNK_SIZE,
position.z * CHUNK_SIZE
)
)
);
if (freeSlots.isEmpty()) return null;
int faceOffset = ranges.allocate(faceCount);
if (faceOffset < 0) return null;
int slot = freeSlots.remove();
return new RenderChunk(new Vector3i(position), slot, (long) faceOffset * FACE_RECORD_SIZE_BYTES,
faceCount, (long) slot * DRAW_INDIRECT_COMMAND_SIZE,
new Matrix4f().translation(position.x * CHUNK_SIZE, position.y * CHUNK_SIZE, position.z * CHUNK_SIZE));
}
public void free(RenderChunk chunk) {
ranges.free(Math.toIntExact(chunk.faceOffsetBytes() / FACE_RECORD_SIZE_BYTES));
freeSlots.add(chunk.slot());
}
public long faceOffsetBytes(int slot) {
return faceSlotSizeBytes * slot;
}
public long indirectCommandOffsetBytes(int slot) {
return indirectSlotSizeBytes * slot;
}
public VulkanBuffer facePool() {
return facePool;
}
public VulkanBuffer indirectPool() {
return indirectPool;
}
public long facePoolHandle() {
return facePool.GetBuffer();
}
public long indirectPoolHandle() {
return indirectPool.GetBuffer();
}
public int maxChunks() {
return maxChunks;
}
public int freeSlotCount() {
return freeSlots.size();
}
public VulkanBuffer facePool() {return facePool;}
public long facePoolHandle() {return facePool.GetBuffer();}
public VulkanBuffer drawBuffer(int frame, boolean shadowPass) {return drawBuffers[frame][shadowPass ? 1 : 0];}
public int maxChunks() {return maxChunks;}
public int freeSlotCount() {return freeSlots.size();}
public long usedFaceBytes() {return facePool.GetRequestedSize() - (long) ranges.freeFaces() * FACE_RECORD_SIZE_BYTES;}
public void cleanup(VulkanContext VkCtx) {
facePool.cleanup(VkCtx);
indirectPool.cleanup(VkCtx);
for (VulkanBuffer[] frame : drawBuffers) {
for (VulkanBuffer buffer : frame) buffer.cleanup(VkCtx);
}
}
}

View file

@ -1,11 +1,15 @@
package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration;
import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig;
import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.Pipeline;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.VkModel.MaterialsCache;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.CommandBuffer;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils;
import org.joml.*;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.vulkan.VkBufferCopy;
import org.lwjgl.vulkan.VkCommandBuffer;
import org.tinylog.Logger;
@ -21,13 +25,23 @@ import static org.lwjgl.vulkan.VK13.*;
public class VoxelWorldManager {
public static final int CHUNK_SIZE = 16;
private static final int MAX_RESIDENT_CHUNKS = 8192 * 3;
private static final int MAX_CHUNK_GENERATIONS_PER_FRAME = 128;
private static int MAX_CHUNK_GENERATIONS_PER_FRAME = EngineConfig.getInstance().GetVoxelGenerationsPerFrame();
public static int GetMaxChunkGenerationsPerFrame(){return MAX_CHUNK_GENERATIONS_PER_FRAME;}
public static void SetMaxChunkGenerationsPerFrame(int value){MAX_CHUNK_GENERATIONS_PER_FRAME = Math.clamp(value, 16, 64);}
private static final ConcurrentLinkedQueue<Vector3i> RequestedChunks = new ConcurrentLinkedQueue<>();
private static final ConcurrentHashMap<Vector3i, RenderChunk> RenderChunks = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Vector3i, Boolean> KnownChunks = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Vector3i, VoxelChunkVisibility> CulledChunks = new ConcurrentHashMap<>();
private static final ConcurrentLinkedQueue<RenderChunk> RetiredChunks = new ConcurrentLinkedQueue<>();
private static final Vector3i ResidentCenter = new Vector3i();
private static int ResidentRadius = EngineConfig.getInstance().GetVoxelChunkRadius();
private static VulkanContext RenderContext;
private static int DrawFrame;
private static volatile int GeneratedLastFrame;
private static volatile int VisibleLastFrame;
private static boolean PoolFull;
private static final List<Voxel> VoxelRegistry = new CopyOnWriteArrayList<>();
private static final ConcurrentHashMap<String, Integer> VoxelNameToIndex = new ConcurrentHashMap<>();
@ -188,8 +202,10 @@ public class VoxelWorldManager {
return;
}
Resources = new SharedVoxelTerrainResources(VkCtx, MAX_RESIDENT_CHUNKS);
int diameter = EngineConfig.getInstance().GetVoxelChunkRadius() * 2 + 1;
Resources = new SharedVoxelTerrainResources(VkCtx, diameter * diameter * diameter);
Generator = new VoxelChunkGenerator(Resources);
RenderContext = VkCtx;
GraphicsPushConstants = org.lwjgl.system.MemoryUtil.memAlloc(
VulkanUtils.MATRIX4X4_SIZE +
@ -197,30 +213,15 @@ public class VoxelWorldManager {
);
}
public static void GenerateChunk(Vector3i Position) {
public static synchronized void GenerateChunk(Vector3i Position) {
Vector3i key = new Vector3i(Position);
if (KnownChunks.containsKey(key)) {
return;
}
RequestedChunks.add(key);
KnownChunks.put(key, true);
if (KnownChunks.putIfAbsent(key, true) == null) RequestedChunks.add(key);
}
public static int GenerationOffset = 0;
public static Vector3i LastPosition = new Vector3i(0, 0, 0);
static volatile int IterationX = 0;
static Vector3i ChunkPos = new Vector3i(0, 0, 0);
static Vector3i ChunkPos2 = new Vector3i(0, 0, 0);
private static final FrustumIntersection frustumIntersection = new FrustumIntersection();
private static final Matrix4f viewProjectionMatrix = new Matrix4f();
private static Vector3f max = new Vector3f();
private static Vector3f max2 = new Vector3f();
private static Vector3f min = new Vector3f();
private static Vector3f min2 = new Vector3f();
public static synchronized void ResetIterationX()
{
@ -228,52 +229,71 @@ public class VoxelWorldManager {
}
public static synchronized void GenerateChunks(Vector3i Position, Vector3i Radius, Matrix4f cameraView,Matrix4f projectionMatrix) {
projectionMatrix.mul(cameraView, viewProjectionMatrix);
frustumIntersection.set(viewProjectionMatrix);
int ChunkX = Position.x;
int ChunkY = Position.y;
int ChunkY2 = Position.y;
int ChunkZ = Position.z;
for (int Ry = 0; Ry <= Radius.y; Ry++)
{
for (int Rx = -IterationX; Rx <= IterationX; Rx++)
{
for (int Rz = -IterationX; Rz <= IterationX; Rz++)
{
ChunkX = Position.x + Rx;
ChunkY = Position.y + Ry;
ChunkY2 = Position.y - Ry;
ChunkZ = Position.z + Rz;
min.set(ChunkX * CHUNK_SIZE, ChunkY * CHUNK_SIZE, ChunkZ * CHUNK_SIZE);
min2.set(ChunkX * CHUNK_SIZE, ChunkY2 * CHUNK_SIZE, ChunkZ * CHUNK_SIZE);
max.set(min.x + CHUNK_SIZE, min.y + CHUNK_SIZE, min.z + CHUNK_SIZE);
max2.set(min2.x + CHUNK_SIZE, min2.y + CHUNK_SIZE, min2.z + CHUNK_SIZE);
if (Rx == -IterationX || Rz == -IterationX || Rx == IterationX || Rz == IterationX)
{
if(frustumIntersection.testAab(min.x, min.y, min.z, max.x, max.y, max.z)) {
GenerateChunk(ChunkPos.set(ChunkX, ChunkY, ChunkZ));
}
if(frustumIntersection.testAab(min2.x, min2.y, min2.z, max2.x, max2.y, max2.z)) {
GenerateChunk(ChunkPos2.set(ChunkX, ChunkY2, ChunkZ));
}
}
if (!Position.equals(LastPosition)) IterationX = 0;
int radius = Math.max(Radius.x, Math.max(Radius.y, Radius.z));
if (IterationX > radius) return;
int shell = IterationX++;
for (int x = -Math.min(shell, Radius.x); x <= Math.min(shell, Radius.x); x++) {
for (int y = -Math.min(shell, Radius.y); y <= Math.min(shell, Radius.y); y++) {
for (int z = -Math.min(shell, Radius.z); z <= Math.min(shell, Radius.z); z++) {
if (Math.max(Math.abs(x), Math.max(Math.abs(y), Math.abs(z))) != shell) continue;
GenerateChunk(new Vector3i(Position.x + x, Position.y + y, Position.z + z));
}
}
}
IterationX++;
if (IterationX > Radius.x || Position.x != LastPosition.x || Position.y != LastPosition.y || Position.z != LastPosition.z)
{
IterationX = 0;
}
LastPosition.set(Position);
}
public static void RecordGeneration(VulkanContext VkCtx, CommandBuffer commandBuffer) {
public static synchronized void RecordGeneration(VulkanContext VkCtx, CommandBuffer commandBuffer, int currentFrame) {
if (Resources == null) {
Init(VkCtx);
}
DrawFrame = currentFrame;
RenderChunk retired;
while ((retired = RetiredChunks.poll()) != null) {
Resources.meshPool().free(retired);
PoolFull = false;
}
try (var stack = MemoryStack.stackPush()) {
var cmd = commandBuffer.GetVulkanCommandBuffer();
var completed = Generator.collectCompleted(VkCtx, currentFrame);
if (!completed.isEmpty()) {
// Reused ranges may still be read by an earlier submission on this queue.
VulkanUtils.BufferBarrier(stack, cmd, Resources.meshPool().facePoolHandle(),
VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_TRANSFER_BIT,
VK_PIPELINE_STAGE_2_TRANSFER_BIT,
VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
var copy = VkBufferCopy.calloc(1, stack);
for (var generated : completed) {
Vector3i position = generated.position();
if (!VoxelDrawData.withinRadius(position, ResidentCenter, ResidentRadius)) {
KnownChunks.remove(position);
continue;
}
if (generated.faceCount() == 0) {
CulledChunks.put(position, VoxelChunkVisibility.EMPTY_AIR);
continue;
}
RenderChunk chunk = Resources.meshPool().allocate(position, generated.faceCount());
if (chunk == null) {
if (!PoolFull) Logger.warn("Voxel face budget exhausted; pending chunks retained. Increase voxel_face_pool_mib or reduce radius.");
PoolFull = true;
RequestedChunks.add(position);
continue;
}
copy.srcOffset(generated.sourceOffsetBytes()).dstOffset(chunk.faceOffsetBytes())
.size((long) generated.faceCount() * VoxelMeshPool.FACE_RECORD_SIZE_BYTES);
vkCmdCopyBuffer(cmd, generated.sourceBuffer(), Resources.meshPool().facePoolHandle(), copy);
RenderChunks.put(position, chunk);
}
VulkanUtils.BufferBarrier(stack, cmd, Resources.meshPool().facePoolHandle(),
VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT,
VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_ACCESS_2_SHADER_READ_BIT);
}
}
if (RegistryDirty || BiomeRegistryDirty || StructureRegistryDirty) VkCtx.GetDevice().waitIdle();
if (RegistryDirty) {
RegistryDirty = false;
@ -293,40 +313,18 @@ public class VoxelWorldManager {
}
int generatedThisFrame = 0;
while (!RequestedChunks.isEmpty() && generatedThisFrame < MAX_CHUNK_GENERATIONS_PER_FRAME) {
List<Vector3i> batch = new ArrayList<>(MAX_CHUNK_GENERATIONS_PER_FRAME);
while (!PoolFull && batch.size() < MAX_CHUNK_GENERATIONS_PER_FRAME) {
Vector3i position = RequestedChunks.poll();
if (position == null) {
break;
if (position == null) break;
if (!VoxelDrawData.withinRadius(position, ResidentCenter, ResidentRadius)) {
KnownChunks.remove(position);
continue;
}
// VoxelChunkVisibility visibility = VoxelTerrainHeightSampler.classifyChunk(
// position.x,
// position.y,
// position.z,
// VoxelChunkGenerator.CHUNK_SIZE
// );
//
// if (visibility == VoxelChunkVisibility.EMPTY_AIR) {
// CulledChunks.put(new Vector3i(position), visibility);
// continue;
// }
RenderChunk renderChunk = Resources.meshPool().allocate(position);
if (renderChunk == null) {
Logger.warn("Voxel mesh pool full. Could not allocate chunk {}", position);
RequestedChunks.add(position);
break;
}
Generator.recordGenerateChunk(VkCtx, commandBuffer, renderChunk);
RenderChunks.put(new Vector3i(position), renderChunk);
generatedThisFrame++;
batch.add(position);
}
GeneratedLastFrame = batch.size();
Generator.recordGenerateChunks(VkCtx, commandBuffer, batch, currentFrame);
}
public static SharedVoxelTerrainResources Resources() {
@ -334,23 +332,56 @@ public class VoxelWorldManager {
}
public static void RecordRender(VkCommandBuffer CommandBuffer, Pipeline graphicsPipeline, MaterialsCache materialsCache, boolean shadowPass) {
if (Resources == null) {
return;
RecordRender(CommandBuffer, graphicsPipeline, materialsCache, shadowPass, new Matrix4f[0]);
}
public static synchronized void RecordRender(VkCommandBuffer CommandBuffer, Pipeline graphicsPipeline,
MaterialsCache materialsCache, boolean shadowPass, Matrix4f[] clipMatrices) {
if (Resources == null) return;
var device = RenderContext.GetDevice();
int material = Math.max(0, materialsCache.GetPosition("VoxelTerrain"));
FrustumIntersection[] frustums = new FrustumIntersection[clipMatrices.length];
for (int i = 0; i < frustums.length; i++) frustums[i] = VoxelDrawData.frustum(clipMatrices[i]);
var buffer = Resources.meshPool().drawBuffer(DrawFrame, shadowPass);
ByteBuffer data = MemoryUtil.memByteBuffer(buffer.MapMemory(RenderContext), (int) buffer.GetRequestedSize());
boolean instanced = device.drawIndirectFirstInstance();
List<RenderChunk> visible = new ArrayList<>();
for (RenderChunk chunk : RenderChunks.values()) {
Vector3i position = chunk.position();
float x = position.x * (float) CHUNK_SIZE;
float y = position.y * (float) CHUNK_SIZE;
float z = position.z * (float) CHUNK_SIZE;
boolean inView = frustums.length == 0;
for (FrustumIntersection frustum : frustums) {
if (frustum.testAab(x, y, z, x + CHUNK_SIZE, y + CHUNK_SIZE, z + CHUNK_SIZE)) {
inView = true;
break;
}
}
if (!inView) continue;
int offset = visible.size() * VoxelMeshPool.DRAW_INDIRECT_COMMAND_SIZE;
data.putInt(offset, chunk.maxFaceCount() * 6);
data.putInt(offset + 4, 1);
data.putInt(offset + 8, Math.toIntExact(chunk.faceOffsetBytes() / 4 * 6));
data.putInt(offset + 12, instanced ? VoxelDrawData.encodeOffset(position, ResidentCenter) : 0);
visible.add(chunk);
}
int terrainMaterialIndex;
if (materialsCache.GetPosition("VoxelTerrain") < 0) {
terrainMaterialIndex = 0;
buffer.UnMapMemory(RenderContext);
if (!shadowPass) VisibleLastFrame = visible.size();
if (instanced) {
SetPushConstants(CommandBuffer, new Vector4f(ResidentCenter, 0), graphicsPipeline, material, shadowPass);
int batchSize = device.multiDrawIndirect() ? device.maxDrawIndirectCount() : 1;
for (int first = 0; first < visible.size(); first += batchSize) {
vkCmdDrawIndirect(CommandBuffer, buffer.GetBuffer(), (long) first * VoxelMeshPool.DRAW_INDIRECT_COMMAND_SIZE,
Math.min(batchSize, visible.size() - first), VoxelMeshPool.DRAW_INDIRECT_COMMAND_SIZE);
}
} else {
terrainMaterialIndex = materialsCache.GetPosition("VoxelTerrain");
for (int i = 0; i < visible.size(); i++) {
SetPushConstants(CommandBuffer, new Vector4f(visible.get(i).position(), 0), graphicsPipeline, material, shadowPass);
vkCmdDrawIndirect(CommandBuffer, buffer.GetBuffer(), (long) i * VoxelMeshPool.DRAW_INDIRECT_COMMAND_SIZE,
1, VoxelMeshPool.DRAW_INDIRECT_COMMAND_SIZE);
}
}
RenderChunks.forEach((position, chunk) -> {
SetPushConstants(CommandBuffer, new Vector4f(position, 0), graphicsPipeline, terrainMaterialIndex, shadowPass);
vkCmdDrawIndirect(CommandBuffer, Resources.meshPool().indirectPoolHandle(), chunk.indirectCommandOffsetBytes(),
1, VoxelMeshPool.DRAW_INDIRECT_COMMAND_SIZE);
});
}
private static void SetPushConstants(VkCommandBuffer CmdHandle, Vector4f ModelMatrix, Pipeline VkPipeline, int MaterialIndex, boolean shadowPass){
@ -366,48 +397,22 @@ public class VoxelWorldManager {
public static synchronized void UnloadFarChunks(Vector3i centerChunk, int unloadRadius,Matrix4f cameraView,Matrix4f projectionMatrix) {
int maxDistSq = unloadRadius * unloadRadius;
projectionMatrix.mul(cameraView, viewProjectionMatrix);
frustumIntersection.set(viewProjectionMatrix);
ResidentCenter.set(centerChunk);
ResidentRadius = unloadRadius;
RenderChunks.entrySet().removeIf(entry -> {
Vector3i pos = entry.getKey();
min.set(pos.x * CHUNK_SIZE, pos.y * CHUNK_SIZE, pos.z * CHUNK_SIZE);
max.set(min.x + CHUNK_SIZE, min.y + CHUNK_SIZE, min.z + CHUNK_SIZE);
boolean InView = frustumIntersection.testAab(min.x, min.y, min.z, max.x, max.y, max.z);
if (!InView) {
RenderChunk chunk = entry.getValue();
Resources.meshPool().free(chunk);
if (!VoxelDrawData.withinRadius(pos, centerChunk, unloadRadius)) {
RetiredChunks.add(entry.getValue());
KnownChunks.remove(pos);
return true;
}
int dx = pos.x - centerChunk.x;
int dy = pos.y - centerChunk.y;
int dz = pos.z - centerChunk.z;
int distSq = dx * dx + dy * dy + dz * dz;
if (distSq > maxDistSq) {
RenderChunk chunk = entry.getValue();
Resources.meshPool().free(chunk);
KnownChunks.remove(pos);
return true;
}
return false;
});
CulledChunks.entrySet().removeIf(entry -> {
Vector3i pos = entry.getKey();
int dx = pos.x - centerChunk.x;
int dy = pos.y - centerChunk.y;
int dz = pos.z - centerChunk.z;
int distSq = dx * dx + dy * dy + dz * dz;
if (distSq > maxDistSq) {
if (!VoxelDrawData.withinRadius(pos, centerChunk, unloadRadius)) {
KnownChunks.remove(pos);
return true;
}
@ -416,13 +421,7 @@ public class VoxelWorldManager {
});
RequestedChunks.removeIf(pos -> {
int dx = pos.x - centerChunk.x;
int dy = pos.y - centerChunk.y;
int dz = pos.z - centerChunk.z;
int distSq = dx * dx + dy * dy + dz * dz;
if (distSq > maxDistSq) {
if (!VoxelDrawData.withinRadius(pos, centerChunk, unloadRadius)) {
KnownChunks.remove(pos);
return true;
}
@ -434,6 +433,9 @@ public class VoxelWorldManager {
public static int GetRenderableChunkCount() {
return RenderChunks.size();
}
public static int GetGeneratedLastFrame() {return GeneratedLastFrame;}
public static int GetVisibleLastFrame() {return VisibleLastFrame;}
public static synchronized long GetUsedFaceBytes() {return Resources == null ? 0 : Resources.meshPool().usedFaceBytes();}
public static int GetCulledChunkCount() {
return CulledChunks.size();
@ -452,7 +454,7 @@ public class VoxelWorldManager {
}
public static void Cleanup(VulkanContext VkCtx) {
public static synchronized void Cleanup(VulkanContext VkCtx) {
if (Resources != null) {
Resources.cleanup(VkCtx);
Resources = null;
@ -466,5 +468,12 @@ public class VoxelWorldManager {
RequestedChunks.clear();
RenderChunks.clear();
KnownChunks.clear();
CulledChunks.clear();
RetiredChunks.clear();
Generator = null;
RenderContext = null;
IterationX = 0;
PoolFull = false;
RegistryDirty = BiomeRegistryDirty = StructureRegistryDirty = true;
}
}

View file

@ -36,10 +36,8 @@ public class ComputePipeline implements Pipeline {
int pushRangeCount = pushConstantsRanges != null ? pushConstantsRanges.length : 0;
if (pushRangeCount > 0) {
pushRanges = VkPushConstantRange.calloc(pushRangeCount, stack);
for (int i = 0; i < pushRangeCount; i++) {
PushConstantsRange range = pushConstantsRanges[i];
pushRanges.get(i)
.stageFlags(range.Stage())
.offset(range.Offset())

View file

@ -431,7 +431,9 @@ public class DeferredSceneRender implements SceneRenderer {//dynamic rendering
vkCmdBindDescriptorSets(CommandHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, VkVoxelPipeline.GetVulkanPipelineLayout(), 0, voxelDescriptorSets, null);
VoxelWorldManager.RecordRender(CommandHandle, VkVoxelPipeline, materialsCache, false);
VoxelWorldManager.RecordRender(CommandHandle, VkVoxelPipeline, materialsCache, false,
new Matrix4f[]{engineInstance.scene().GetProjection().GetProjectionMatrix()
.mul(engineInstance.scene().GetCamera().GetViewMatrix(), new Matrix4f())});
Matrix4f skyboxViewMatrix = new Matrix4f().identity();
skyboxViewMatrix.set(engineInstance.scene().GetCamera().GetViewMatrix());

View file

@ -16,10 +16,10 @@ public class MultiRenderTargetAttachments {
public static final int DEPTH_FORMAT = VK_FORMAT_D32_SFLOAT;
public static final int REFLECT_REFRACT = VK_FORMAT_R16G16_SFLOAT;
public static final int NORMAL_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
public static final int PBR_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
public static final int PBR_FORMAT = VK_FORMAT_R8G8B8A8_UNORM;
public static final int EMISSIVE_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
public static final int TRANSLUCECNY_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
public static final int OPACITY_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
public static final int TRANSLUCECNY_FORMAT = VK_FORMAT_R8G8B8A8_UNORM;
public static final int OPACITY_FORMAT = VK_FORMAT_R8G8B8A8_UNORM;
public static final int POSITION_FORMAT = VK_FORMAT_R32G32B32A32_SFLOAT;
public static final int VIEW_POS_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
public static final int SSAO_RAW_ATTACHMENT = VK_FORMAT_R8_UNORM;

View file

@ -428,7 +428,9 @@ public class ForwardSceneRender implements SceneRenderer {//dynamic rendering
vkCmdBindDescriptorSets(CommandHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, VkVoxelPipeline.GetVulkanPipelineLayout(), 0, voxelDescriptorSets, null);
VoxelWorldManager.RecordRender(CommandHandle, VkVoxelPipeline, materialsCache, false);
VoxelWorldManager.RecordRender(CommandHandle, VkVoxelPipeline, materialsCache, false,
new Matrix4f[]{engineInstance.scene().GetProjection().GetProjectionMatrix()
.mul(engineInstance.scene().GetCamera().GetViewMatrix(), new Matrix4f())});
Matrix4f skyboxViewMatrix = new Matrix4f().identity();

View file

@ -40,7 +40,7 @@ import static org.lwjgl.vulkan.VK10.*;
import static org.lwjgl.vulkan.VK13.*;
public class LightRenderer {
private static final int COLOUR_FORMAT = VK_FORMAT_R32G32B32A32_SFLOAT;
private static final int COLOUR_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
private static final String DESC_ID_ATT = "LIGHT_DESC_ID_ATT";
private static final String DESC_ID_NOSHADOW_ATT = "LIGHT_DESC_ID_NOSHADOW_ATT";
private static final String DESC_ID_SHADOW_MATRICES = "LIGHT_DESC_ID_SHADOW_MATRICES";
@ -172,7 +172,7 @@ public class LightRenderer {
var vtxBuffStruct = new EmptyVertexBufferStruct();
var buildInfo = new PipelineBuildInfo(shaderModules, vtxBuffStruct.GetVertexInput(),new int[]{COLOUR_FORMAT})
.SetDescriptorSetLayouts(descSetLayouts)
.BlendingIsUsed(true)
.BlendingIsUsed(false)
.SetBlendingMethod(1)
.SetDepthWrite(false)
.SetDepthTest(false);

View file

@ -8,23 +8,16 @@ import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.I
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.Images.Image;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.Pipeline;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.PipelineBuildInfo;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.PushConstantsRange;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Shader.*;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.CommandBuffer;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.Device;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DisplayToScreen.ImageView;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.SwapChain.SwapChain;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanBuffer;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.shaderc.Shaderc;
import org.lwjgl.vulkan.*;
import org.tinylog.Logger;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.LongBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -33,125 +26,97 @@ import static org.lwjgl.vulkan.VK13.*;
public class PostProcess {
public static final int COLOUR_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
private static final int PASS_CONSTANT_SIZE = 24;
private static final String DESCRIPTOR_ID_ATTACHMENT = "POST_DESC_ID_ATT";
private static final String DESCRIPTOR_ID_BLOOM_PROPERTIES_HORIZONTAL = "POST_DESC_ID_BLOOM_PROPERTIES_HORIZONTAL";
private static final String DESCRIPTOR_ID_BLOOM_PROPERTIES_VERTICAL = "POST_DESC_ID_BLOOM_PROPERTIES_VERTICAL";
private static final String DESCRIPTOR_ID_BLOOM_PROPERTIES_FINAL = "POST_DESC_ID_BLOOM_PROPERTIES_FINAL";
private static final String DESCRIPTOR_ID_BLOOM_ATTACHMENT = "POST_DESC_ID_BLOOM_ATT";
private static final String DESCRIPTOR_ID_BLOOM_PONG_ATTACHMENT = "POST_DESC_ID_BLOOM_PONG_ATT";
private static final String DESCRIPTOR_ID_BLOOM_IMAGE_ATTACHMENT = "POST_DESC_ID_BLOOM_IMAGE_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 BLOOM_FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/bloom_pass_frag.glsl";
private static final String BLOOM_FRAGMENT_SHADER_FILE_SPV = BLOOM_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 EXTRACT_FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/bloom_extract_frag.glsl";
private static final String MULTI_EXTRACT_FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/multi-sampled_bloom_extract_frag.glsl";
private static final String VERTEX_SHADER_FILE_GLSL = "resources/EngineResources/shaders/basic_screen_vertex.glsl";
private static final String VERTEX_SHADER_FILE_SPV = VERTEX_SHADER_FILE_GLSL + ".spv";
private final DescriptorSetLayout AttachmentDescriptorSetLayout;
private final DescriptorSetLayout[] MultiAttachmentDescriptorSetLayout = new DescriptorSetLayout[2];
private final DescriptorSetLayout MultiAttachmentDescriptorSetLayout;
private final VkClearValue ClearValueColour;
private final DescriptorSetLayout FragmentUniformDescriptorSetLayout;
private final Pipeline pipeline;
private final Pipeline bloomPipeline;
private final VulkanBuffer ScreenSizeBuffer;
private final VulkanBuffer BloomPropertiesHorizontalBuffer;
private final VulkanBuffer BloomPropertiesVerticalBuffer;
private final VulkanBuffer BloomPropertiesFinalBuffer;
private final Pipeline extractionPipeline;
private final SpecializationConstants specConstants;
private final TextureSampler textureSampler;
private Attachment ColourAttachment;
private VkRenderingAttachmentInfo.Buffer ColourAttachmentInfo;
private VkRenderingAttachmentInfo.Buffer FinalOutputInfo;
private Attachment BloomPingAttachment;
private Attachment BloomPongAttachment;
private Attachment FinalAttachment;
private VkRenderingAttachmentInfo.Buffer BloomPingAttachmentInfo;
private VkRenderingAttachmentInfo.Buffer BloomPongAttachmentInfo;
private VkRenderingInfo RenderingInfo;
private VkRenderingAttachmentInfo.Buffer FinalOutputInfo;
private VkRenderingInfo SingleRenderingInfo;
private VkRenderingInfo BloomPingRenderingInfo;
private VkRenderingInfo BloomPongRenderingInfo;
private boolean attachmentsInitialized;
private int bloomPasses;
public static volatile float GAMMA = 1.75f;
public static volatile float EXPOSURE = 2.5f;
public static volatile float BLOOM_RADIUS = 2.5f;
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);
BloomPingAttachment = CreateColourAttachment(VkCtx);
BloomPongAttachment = CreateColourAttachment(VkCtx);
FinalAttachment = CreateColourAttachment(VkCtx);
List<Attachment> attachments = new ArrayList<>();
attachments.add(ColourAttachment);
attachments.add(BloomPingAttachment);
ColourAttachmentInfo = CreateColourAttachmentInfos(attachments,ClearValueColour);
BloomPingAttachmentInfo = CreateColourAttachmentInfo(BloomPingAttachment,ClearValueColour);
BloomPongAttachmentInfo = CreateColourAttachmentInfo(BloomPongAttachment,ClearValueColour);
FinalOutputInfo = CreateColourAttachmentInfo(FinalAttachment,ClearValueColour);
RenderingInfo = CreateRenderInfo(ColourAttachment,ColourAttachmentInfo);
BloomPingRenderingInfo = CreateRenderInfo(BloomPingAttachment, BloomPingAttachmentInfo);
BloomPongRenderingInfo = CreateRenderInfo(BloomPongAttachment, BloomPongAttachmentInfo);
SingleRenderingInfo = CreateRenderInfo(FinalAttachment,FinalOutputInfo);
var textureSamplerInfo = new TextureSamplerInfo(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
VK_BORDER_COLOR_INT_OPAQUE_BLACK, 1, true);
textureSampler = new TextureSampler(VkCtx, textureSamplerInfo);
DescriptorSetLayout.LayoutInformation[] descSetLayouts = new DescriptorSetLayout.LayoutInformation[2];
for (int i = 0; i < 2; i++) {
descSetLayouts[i] = new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, i, 1, VK_SHADER_STAGE_FRAGMENT_BIT);
}
var LayoutInfo = new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,0,1,VK_SHADER_STAGE_FRAGMENT_BIT);
AttachmentDescriptorSetLayout = new DescriptorSetLayout(VkCtx, LayoutInfo);
MultiAttachmentDescriptorSetLayout[0] = new DescriptorSetLayout(VkCtx, descSetLayouts);
CreateAttachmentDescriptorSet(VkCtx, AttachmentDescriptorSetLayout, SrcAttachment,textureSampler, DESCRIPTOR_ID_ATTACHMENT);
CreateAttachmentDescriptorSets(VkCtx, MultiAttachmentDescriptorSetLayout[0], new Attachment[]{BloomPingAttachment, BloomPingAttachment},textureSampler, DESCRIPTOR_ID_BLOOM_ATTACHMENT);
CreateAttachmentDescriptorSets(VkCtx, MultiAttachmentDescriptorSetLayout[0], new Attachment[]{BloomPongAttachment, BloomPongAttachment},textureSampler, DESCRIPTOR_ID_BLOOM_PONG_ATTACHMENT);
CreateAttachmentDescriptorSets(VkCtx, MultiAttachmentDescriptorSetLayout[0], new Attachment[]{ColourAttachment, BloomPingAttachment},textureSampler, DESCRIPTOR_ID_BLOOM_IMAGE_ATTACHMENT);
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);
BloomPropertiesHorizontalBuffer = VulkanUtils.CreateHostVisibleBuffer(VkCtx, VulkanUtils.BOOLEAN_SIZE * 2 + VulkanUtils.FLOAT_SIZE * 2+ VulkanUtils.VEC4_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,DESCRIPTOR_ID_BLOOM_PROPERTIES_HORIZONTAL,FragmentUniformDescriptorSetLayout);
BloomPropertiesVerticalBuffer = VulkanUtils.CreateHostVisibleBuffer(VkCtx, VulkanUtils.BOOLEAN_SIZE * 2 + VulkanUtils.FLOAT_SIZE * 2 + VulkanUtils.VEC4_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,DESCRIPTOR_ID_BLOOM_PROPERTIES_VERTICAL,FragmentUniformDescriptorSetLayout);
BloomPropertiesFinalBuffer = VulkanUtils.CreateHostVisibleBuffer(VkCtx, VulkanUtils.BOOLEAN_SIZE * 2 + VulkanUtils.FLOAT_SIZE * 2+ VulkanUtils.VEC4_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,DESCRIPTOR_ID_BLOOM_PROPERTIES_FINAL,FragmentUniformDescriptorSetLayout);
SetScreenSizeBuffer(VkCtx);
SetBloomPropertiesBuffer(VkCtx, BloomPropertiesHorizontalBuffer, true, false);
SetBloomPropertiesBuffer(VkCtx, BloomPropertiesVerticalBuffer, false, false);
SetBloomPropertiesBuffer(VkCtx, BloomPropertiesFinalBuffer, false, true);
ClearValueColour.color(c -> c.float32(0, 0.0f).float32(1, 0.0f).float32(2, 0.0f).float32(3, 0.0f));
CreateAttachments(VkCtx);
textureSampler = new TextureSampler(VkCtx, new TextureSamplerInfo(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
VK_BORDER_COLOR_INT_OPAQUE_BLACK, 1, false));
AttachmentDescriptorSetLayout = new DescriptorSetLayout(VkCtx,
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, 1, VK_SHADER_STAGE_FRAGMENT_BIT));
MultiAttachmentDescriptorSetLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, 1, VK_SHADER_STAGE_FRAGMENT_BIT),
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, 1, VK_SHADER_STAGE_FRAGMENT_BIT)});
UpdateDescriptors(VkCtx, SrcAttachment);
specConstants = new SpecializationConstants();
ShaderModule[] shaderModules = CreateShaderModules(VkCtx, specConstants);
pipeline = CreatePipeline(VkCtx, shaderModules, new DescriptorSetLayout[]{AttachmentDescriptorSetLayout, FragmentUniformDescriptorSetLayout});
Arrays.asList(shaderModules).forEach(shader->shader.CleanUp(VkCtx));
shaderModules = CreateBloomShaderModules(VkCtx, specConstants);
bloomPipeline = CreateSingleOutputPipeline(VkCtx, shaderModules, new DescriptorSetLayout[]{MultiAttachmentDescriptorSetLayout[0], FragmentUniformDescriptorSetLayout});
Logger.debug("Post Process Renderer Pipeline -> [{}]",pipeline.GetVulkanPipeline());
boolean multisampled = !EngineConfig.getInstance().DeferredRendering() && EngineConfig.getInstance().RenderAA() > 1;
pipeline = CreatePipeline(VkCtx, multisampled ? MULTI_PASS_FRAGMENT_SHADER_FILE_GLSL : SINGLE_PASS_FRAGMENT_SHADER_FILE_GLSL,
MultiAttachmentDescriptorSetLayout, specConstants.GetSpecializationInfo());
bloomPipeline = CreatePipeline(VkCtx, BLOOM_FRAGMENT_SHADER_FILE_GLSL, AttachmentDescriptorSetLayout, null);
extractionPipeline = CreatePipeline(VkCtx, multisampled ? MULTI_EXTRACT_FRAGMENT_SHADER_FILE_GLSL : EXTRACT_FRAGMENT_SHADER_FILE_GLSL,
AttachmentDescriptorSetLayout, null);
}
private static VkRenderingAttachmentInfo.Buffer CreateColourAttachmentInfos(List<Attachment> attachments, VkClearValue ClearValue){
int numAttachments = attachments.size();
VkRenderingAttachmentInfo.Buffer result = VkRenderingAttachmentInfo.calloc(numAttachments);
for (int i = 0; i < numAttachments; ++i) {
result.get(i)
.sType$Default()
.imageView(attachments.get(i).GetVkImageView().GetVulkanImageView())
.imageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)
.loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR)
.storeOp(VK_ATTACHMENT_STORE_OP_STORE)
.clearValue(ClearValue);
static int ScaledExtent(int size, float scale) {
return Math.max(1, Math.round(size * scale));
}
static int ValidateBloomPasses(int passes) {
if (passes < 0 || (passes & 1) != 0) {
throw new IllegalArgumentException("Bloom requires horizontal/vertical pass pairs, or zero to disable it");
}
return result;
return passes;
}
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_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
private void CreateAttachments(VulkanContext VkCtx){
var extent = VkCtx.GetSwapChain().GetSwapChainExtent();
var config = EngineConfig.getInstance();
float scale = config.GetBloomScale();
int width = ScaledExtent(extent.width(), scale);
int height = ScaledExtent(extent.height(), scale);
bloomPasses = ValidateBloomPasses(config.GetBloomPasses());
attachmentsInitialized = false;
BloomPingAttachment = CreateColourAttachment(VkCtx, width, height);
BloomPongAttachment = CreateColourAttachment(VkCtx, width, height);
FinalAttachment = CreateColourAttachment(VkCtx, extent.width(), extent.height());
BloomPingAttachmentInfo = CreateColourAttachmentInfo(BloomPingAttachment, ClearValueColour);
if (bloomPasses == 0) BloomPingAttachmentInfo.get(0).loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR);
BloomPongAttachmentInfo = CreateColourAttachmentInfo(BloomPongAttachment, ClearValueColour);
FinalOutputInfo = CreateColourAttachmentInfo(FinalAttachment, ClearValueColour);
BloomPingRenderingInfo = CreateRenderInfo(BloomPingAttachment, BloomPingAttachmentInfo);
BloomPongRenderingInfo = CreateRenderInfo(BloomPongAttachment, BloomPongAttachmentInfo);
SingleRenderingInfo = CreateRenderInfo(FinalAttachment, FinalOutputInfo);
}
private static Attachment CreateColourAttachment(VulkanContext VkCtx, int width, int height){
return new Attachment(VkCtx, width, height, COLOUR_FORMAT, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
}
private static VkRenderingAttachmentInfo.Buffer CreateColourAttachmentInfo(Attachment SrcAttachment, VkClearValue ClearValue){
@ -159,283 +124,163 @@ public class PostProcess {
.sType$Default()
.imageView(SrcAttachment.GetVkImageView().GetVulkanImageView())
.imageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)
.loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR)
.loadOp(VK_ATTACHMENT_LOAD_OP_DONT_CARE)
.storeOp(VK_ATTACHMENT_STORE_OP_STORE)
.clearValue(ClearValue);
}
private static Pipeline CreatePipeline(VulkanContext VkCtx, ShaderModule[] shaderModules, DescriptorSetLayout[] descriptorSetLayouts){
private static Pipeline CreatePipeline(VulkanContext VkCtx, String fragmentShader, DescriptorSetLayout descriptorSetLayout,
VkSpecializationInfo specializationInfo){
if (EngineConfig.getInstance().RecompileShaders()) {
ShaderCompiler.CompileGLSLShaderOnChange(VERTEX_SHADER_FILE_GLSL, Shaderc.shaderc_glsl_vertex_shader);
ShaderCompiler.CompileGLSLShaderOnChange(fragmentShader, Shaderc.shaderc_glsl_fragment_shader);
}
ShaderModule[] shaderModules = {
new ShaderModule(VkCtx, VK_SHADER_STAGE_VERTEX_BIT, VERTEX_SHADER_FILE_GLSL + ".spv", null),
new ShaderModule(VkCtx, VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShader + ".spv", specializationInfo)
};
var VertexBufferStruct = new EmptyVertexBufferStruct();
var BuildInfo = new PipelineBuildInfo(shaderModules, VertexBufferStruct.GetVertexInput(), new int[]{COLOUR_FORMAT, COLOUR_FORMAT})
.SetDescriptorSetLayouts(descriptorSetLayouts)
.BlendingIsUsed(true);
var PipeLine = new DefaultPipeline(VkCtx, BuildInfo);
VertexBufferStruct.CleanUp();
return PipeLine;
}
private static Pipeline CreateSingleOutputPipeline(VulkanContext VkCtx, ShaderModule[] shaderModules, DescriptorSetLayout[] descriptorSetLayouts){
var VertexBufferStruct = new EmptyVertexBufferStruct();
var BuildInfo = new PipelineBuildInfo(shaderModules, VertexBufferStruct.GetVertexInput(), new int[]{COLOUR_FORMAT})
.SetDescriptorSetLayouts(descriptorSetLayouts)
.BlendingIsUsed(true);
var PipeLine = new DefaultPipeline(VkCtx, BuildInfo);
VertexBufferStruct.CleanUp();
return PipeLine;
try {
var BuildInfo = new PipelineBuildInfo(shaderModules, VertexBufferStruct.GetVertexInput(), new int[]{COLOUR_FORMAT})
.SetDescriptorSetLayouts(new DescriptorSetLayout[]{descriptorSetLayout})
.SetPushConstantRanges(new PushConstantsRange[]{new PushConstantsRange(VK_SHADER_STAGE_FRAGMENT_BIT, 0, PASS_CONSTANT_SIZE)})
.SetDepthTest(false).SetDepthWrite(false).BlendingIsUsed(false);
return new DefaultPipeline(VkCtx, BuildInfo);
} finally {
VertexBufferStruct.CleanUp();
Arrays.stream(shaderModules).forEach(shader -> shader.CleanUp(VkCtx));
}
}
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;
Image image = ColourAttachment.GetVkImage();
return VkRenderingInfo.calloc().sType$Default()
.renderArea(area -> area.offset(offset -> offset.set(0, 0)).extent(extent -> extent.set(image.GetWidth(), image.GetHeight())))
.layerCount(1).pColorAttachments(ColourAttachmentInfo);
}
private static void CreateAttachmentDescriptorSets(VulkanContext VkCtx, DescriptorSetLayout descriptorSetLayout, Attachment[] attachment, TextureSampler textureSampler, String descriptorID){
private void UpdateDescriptors(VulkanContext VkCtx, Attachment SrcAttachment){
CreateAttachmentDescriptorSets(VkCtx, AttachmentDescriptorSetLayout, List.of(SrcAttachment), DESCRIPTOR_ID_ATTACHMENT);
CreateAttachmentDescriptorSets(VkCtx, AttachmentDescriptorSetLayout, List.of(BloomPingAttachment), DESCRIPTOR_ID_BLOOM_ATTACHMENT);
CreateAttachmentDescriptorSets(VkCtx, AttachmentDescriptorSetLayout, List.of(BloomPongAttachment), DESCRIPTOR_ID_BLOOM_PONG_ATTACHMENT);
CreateAttachmentDescriptorSets(VkCtx, MultiAttachmentDescriptorSetLayout, List.of(SrcAttachment, BloomPingAttachment), DESCRIPTOR_ID_BLOOM_IMAGE_ATTACHMENT);
}
private void CreateAttachmentDescriptorSets(VulkanContext VkCtx, DescriptorSetLayout descriptorSetLayout,
List<Attachment> attachments, String descriptorID){
DescriptorAllocator descriptorAllocator = VkCtx.GetDescriptorAllocator();
Device device = VkCtx.GetDevice();
DescriptorSet descriptorSet = descriptorAllocator.AddDescriptorSets(device,descriptorID,1 , descriptorSetLayout)[0];
List<ImageView> images = new ArrayList<>();
for(int i = 0; i < attachment.length; i++) {
images.add(attachment[i].GetVkImageView());
}
descriptorSet.SetImages(device,images,textureSampler,0);
DescriptorSet descriptorSet = descriptorAllocator.GetDescriptorSet(descriptorID);
if (descriptorSet == null) descriptorSet = descriptorAllocator.AddDescriptorSet(VkCtx.GetDevice(), descriptorID, descriptorSetLayout);
List<ImageView> images = attachments.stream().map(Attachment::GetVkImageView).toList();
descriptorSet.SetImages(VkCtx.GetDevice(), images, textureSampler, 0);
}
private static void CreateAttachmentDescriptorSet(VulkanContext VkCtx, DescriptorSetLayout descriptorSetLayout, Attachment attachment, TextureSampler textureSampler, String descriptorID){
DescriptorAllocator descriptorAllocator = VkCtx.GetDescriptorAllocator();
Device device = VkCtx.GetDevice();
DescriptorSet descriptorSet = descriptorAllocator.AddDescriptorSets(device,descriptorID,1 , descriptorSetLayout)[0];
descriptorSet.SetImage(device,attachment.GetVkImageView(),textureSampler,0);
private static void BeginWrite(MemoryStack stack, VkCommandBuffer command, Attachment attachment, boolean initialized){
VulkanUtils.ImageBarrier(stack, command, attachment.GetVkImage().getVulkanImage(),
initialized ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
initialized ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_2_NONE,
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
initialized ? VK_ACCESS_2_SHADER_READ_BIT : VK_ACCESS_2_NONE, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT);
}
private static ShaderModule[] CreateBloomShaderModules(VulkanContext VkCtx, SpecializationConstants specConstants){
if(EngineConfig.getInstance().RecompileShaders()){
ShaderCompiler.CompileGLSLShaderOnChange(VERTEX_SHADER_FILE_GLSL, Shaderc.shaderc_glsl_vertex_shader);
ShaderCompiler.CompileGLSLShaderOnChange(BLOOM_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,BLOOM_FRAGMENT_SHADER_FILE_SPV,null)
};
private static void EndWrite(MemoryStack stack, VkCommandBuffer command, Attachment attachment){
VulkanUtils.ImageBarrier(stack, command, attachment.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);
}
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())
};
private static void Draw(VulkanContext VkCtx, MemoryStack stack, VkCommandBuffer command, Pipeline passPipeline,
Attachment target, VkRenderingInfo renderingInfo, String descriptorID, ByteBuffer constants){
vkCmdBeginRendering(command, renderingInfo);
vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, passPipeline.GetVulkanPipeline());
Image image = target.GetVkImage();
vkCmdSetViewport(command, 0, VkViewport.calloc(1, stack).x(0).y(image.GetHeight())
.width(image.GetWidth()).height(-image.GetHeight()).minDepth(0.0f).maxDepth(1.0f));
vkCmdSetScissor(command, 0, VkRect2D.calloc(1, stack)
.offset(offset -> offset.set(0, 0)).extent(extent -> extent.set(image.GetWidth(), image.GetHeight())));
vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS, passPipeline.GetVulkanPipelineLayout(), 0,
stack.longs(VkCtx.GetDescriptorAllocator().GetDescriptorSet(descriptorID).GetVkDescriptorSet()), null);
vkCmdPushConstants(command, passPipeline.GetVulkanPipelineLayout(), VK_SHADER_STAGE_FRAGMENT_BIT, 0, constants);
vkCmdDraw(command, 3, 1, 0, 0);
vkCmdEndRendering(command);
}
public void Render(VulkanContext VkCtx, CommandBuffer commandBuffer, Attachment SrcAttachment){
SetBloomPropertiesBuffer(VkCtx, BloomPropertiesHorizontalBuffer, true, false);
SetBloomPropertiesBuffer(VkCtx, BloomPropertiesVerticalBuffer, false, false);
SetBloomPropertiesBuffer(VkCtx, BloomPropertiesFinalBuffer, false, true);
try(var MemStack = MemoryStack.stackPush()){
SwapChain swapChain = VkCtx.GetSwapChain();
VkCommandBuffer CommandHandle = commandBuffer.GetVulkanCommandBuffer();
ByteBuffer constants = MemStack.malloc(PASS_CONSTANT_SIZE);
constants.putInt(0, 0);
constants.putFloat(4, Math.max(GAMMA, 0.001f));
constants.putFloat(8, Math.max(EXPOSURE, 0.0f));
constants.putFloat(12, Math.max(BLOOM_RADIUS, 0.0f));
constants.putFloat(16, BloomPingAttachment.GetVkImage().GetWidth());
constants.putFloat(20, BloomPingAttachment.GetVkImage().GetHeight());
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);
VulkanUtils.ImageBarrier(MemStack,CommandHandle,BloomPingAttachment.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);
VulkanUtils.ImageBarrier(MemStack,CommandHandle,BloomPongAttachment.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());
EndWrite(MemStack, CommandHandle, SrcAttachment);
BeginWrite(MemStack, CommandHandle, BloomPingAttachment, attachmentsInitialized);
if (bloomPasses == 0) {
vkCmdBeginRendering(CommandHandle, BloomPingRenderingInfo);
vkCmdEndRendering(CommandHandle);
} else {
Draw(VkCtx, MemStack, CommandHandle, extractionPipeline, BloomPingAttachment, BloomPingRenderingInfo, DESCRIPTOR_ID_ATTACHMENT, constants);
}
EndWrite(MemStack, CommandHandle, BloomPingAttachment);
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);
VulkanUtils.ImageBarrier(MemStack,CommandHandle,ColourAttachment.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,BloomPingAttachment.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);
int Passes = 10;
boolean bloomPongHasBeenWritten = false;
for(int i = 0; i < Passes; i++){
boolean writePong = i % 2 == 0;
Attachment srcBloomAttachment = writePong ? BloomPingAttachment : BloomPongAttachment;
for(int i = 0; i < bloomPasses; i++){
boolean writePong = (i & 1) == 0;
Attachment dstBloomAttachment = writePong ? BloomPongAttachment : BloomPingAttachment;
VkRenderingInfo dstRenderingInfo = writePong ? BloomPongRenderingInfo : BloomPingRenderingInfo;
String srcDescriptorId = writePong ? DESCRIPTOR_ID_BLOOM_ATTACHMENT : DESCRIPTOR_ID_BLOOM_PONG_ATTACHMENT;
String bloomPropertiesDescriptorId = writePong ? DESCRIPTOR_ID_BLOOM_PROPERTIES_HORIZONTAL : DESCRIPTOR_ID_BLOOM_PROPERTIES_VERTICAL;
int dstOldLayout = writePong && !bloomPongHasBeenWritten
? VK_IMAGE_LAYOUT_UNDEFINED
: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
VulkanUtils.ImageBarrier(MemStack,CommandHandle,dstBloomAttachment.GetVkImage().getVulkanImage(),
dstOldLayout,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_ACCESS_2_SHADER_READ_BIT,VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT);
vkCmdBeginRendering(CommandHandle,dstRenderingInfo);
vkCmdBindPipeline(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,bloomPipeline.GetVulkanPipeline());
vkCmdSetViewport(CommandHandle,0,Viewport);
vkCmdSetScissor(CommandHandle,0,Scissor);
LongBuffer BloomDescriptorSets = MemStack.mallocLong(2)
.put(0,descriptorAllocator.GetDescriptorSet(srcDescriptorId).GetVkDescriptorSet())
.put(1,descriptorAllocator.GetDescriptorSet(bloomPropertiesDescriptorId).GetVkDescriptorSet());
vkCmdBindDescriptorSets(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,bloomPipeline.GetVulkanPipelineLayout(),0,BloomDescriptorSets,null);
vkCmdDraw(CommandHandle,3,1,0,0);
vkCmdEndRendering(CommandHandle);
VulkanUtils.ImageBarrier(MemStack,CommandHandle,dstBloomAttachment.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);
if(writePong){
bloomPongHasBeenWritten = true;
}
BeginWrite(MemStack, CommandHandle, dstBloomAttachment, attachmentsInitialized || i > 0);
constants.putInt(0, writePong ? 1 : 0);
Draw(VkCtx, MemStack, CommandHandle, bloomPipeline, dstBloomAttachment, dstRenderingInfo, srcDescriptorId, constants);
EndWrite(MemStack, CommandHandle, dstBloomAttachment);
}
VulkanUtils.ImageBarrier(MemStack,CommandHandle,FinalAttachment.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,SingleRenderingInfo);
vkCmdBindPipeline(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,bloomPipeline.GetVulkanPipeline());
vkCmdSetViewport(CommandHandle,0,Viewport);
vkCmdSetScissor(CommandHandle,0,Scissor);
LongBuffer BloomDescriptorSets = MemStack.mallocLong(2)
.put(0,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_BLOOM_IMAGE_ATTACHMENT).GetVkDescriptorSet())
.put(1,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_BLOOM_PROPERTIES_FINAL).GetVkDescriptorSet());
vkCmdBindDescriptorSets(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,bloomPipeline.GetVulkanPipelineLayout(),0,BloomDescriptorSets,null);
vkCmdDraw(CommandHandle,3,1,0,0);
vkCmdEndRendering(CommandHandle);
// The UI/swapchain owns the final layout. Discard contents, not prior read/write dependencies.
VulkanUtils.ImageBarrier(MemStack, CommandHandle, FinalAttachment.GetVkImage().getVulkanImage(),
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT);
Draw(VkCtx, MemStack, CommandHandle, pipeline, FinalAttachment, SingleRenderingInfo, DESCRIPTOR_ID_BLOOM_IMAGE_ATTACHMENT, constants);
attachmentsInitialized = true;
}
}
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);
CreateAttachmentDescriptorSet(VkCtx, AttachmentDescriptorSetLayout, SrcAttachment,textureSampler, DESCRIPTOR_ID_ATTACHMENT);
CreateAttachmentDescriptorSets(VkCtx, MultiAttachmentDescriptorSetLayout[0], new Attachment[]{BloomPingAttachment, BloomPingAttachment},textureSampler, DESCRIPTOR_ID_BLOOM_ATTACHMENT);
CreateAttachmentDescriptorSets(VkCtx, MultiAttachmentDescriptorSetLayout[0], new Attachment[]{BloomPongAttachment, BloomPongAttachment},textureSampler, DESCRIPTOR_ID_BLOOM_PONG_ATTACHMENT);
CreateAttachmentDescriptorSets(VkCtx, MultiAttachmentDescriptorSetLayout[0], new Attachment[]{ColourAttachment, BloomPingAttachment},textureSampler, DESCRIPTOR_ID_BLOOM_IMAGE_ATTACHMENT);
SetScreenSizeBuffer(VkCtx);
SetBloomPropertiesBuffer(VkCtx, BloomPropertiesHorizontalBuffer, true, false);
SetBloomPropertiesBuffer(VkCtx, BloomPropertiesVerticalBuffer, false, false);
SetBloomPropertiesBuffer(VkCtx, BloomPropertiesFinalBuffer, false, true);
CleanUpAttachments(VkCtx);
CreateAttachments(VkCtx);
UpdateDescriptors(VkCtx, SrcAttachment);
}
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 static float GAMMA = 1.75f;
public static float EXPOSURE = 2.5f;
public static float BLOOM_RADIUS = 2.5f;
private void SetBloomPropertiesBuffer(VulkanContext VkCtx, VulkanBuffer buffer, boolean horizontal, boolean Complete){
long MappedMemory = buffer.MapMemory(VkCtx);
ByteBuffer dataBuffer = MemoryUtil.memByteBuffer(MappedMemory,(int)buffer.GetRequestedSize());
int Offset = 0;
dataBuffer.putInt(Offset,Complete ? 1 : 0);
Offset += VulkanUtils.BOOLEAN_SIZE;
dataBuffer.putInt(Offset,horizontal ? 1 : 0);
Offset += VulkanUtils.BOOLEAN_SIZE;
dataBuffer.putFloat(Offset,GAMMA);
Offset += VulkanUtils.FLOAT_SIZE;
dataBuffer.putFloat(Offset,EXPOSURE);
Offset += VulkanUtils.FLOAT_SIZE;
dataBuffer.putFloat(Offset,BLOOM_RADIUS);
Offset += VulkanUtils.FLOAT_SIZE;
buffer.UnMapMemory(VkCtx);
private void CleanUpAttachments(VulkanContext VkCtx){
BloomPingRenderingInfo.free();
BloomPongRenderingInfo.free();
SingleRenderingInfo.free();
BloomPingAttachmentInfo.free();
BloomPongAttachmentInfo.free();
FinalOutputInfo.free();
BloomPingAttachment.CleanUp(VkCtx);
BloomPongAttachment.CleanUp(VkCtx);
FinalAttachment.CleanUp(VkCtx);
}
public Attachment GetAttachment(){return FinalAttachment;}
public void CleanUp(VulkanContext VkCtx){
ClearValueColour.free();
ColourAttachment.CleanUp(VkCtx);
BloomPingAttachment.CleanUp(VkCtx);
BloomPongAttachment.CleanUp(VkCtx);
FinalAttachment.CleanUp(VkCtx);
CleanUpAttachments(VkCtx);
pipeline.CleanUp(VkCtx);
bloomPipeline.CleanUp(VkCtx);
extractionPipeline.CleanUp(VkCtx);
textureSampler.CleanUp(VkCtx);
AttachmentDescriptorSetLayout.CleanUp(VkCtx);
FragmentUniformDescriptorSetLayout.CleanUp(VkCtx);
pipeline.CleanUp(VkCtx);
RenderingInfo.free();
ColourAttachmentInfo.free();
ScreenSizeBuffer.cleanup(VkCtx);
BloomPropertiesHorizontalBuffer.cleanup(VkCtx);
BloomPropertiesVerticalBuffer.cleanup(VkCtx);
BloomPropertiesFinalBuffer.cleanup(VkCtx);
MultiAttachmentDescriptorSetLayout.CleanUp(VkCtx);
ClearValueColour.free();
specConstants.CleanUp();
}
}

View file

@ -65,9 +65,9 @@ public class AmbientOcclusionRenderer {
private VkRenderingAttachmentInfo.Buffer ssaoAttachmentInfo;
private Attachment ssaoBlurAttachment;
private VkRenderingAttachmentInfo.Buffer ssaoBlurAttachmentInfo;
private Attachment ssaoViewPosAttachment;
private VkRenderingAttachmentInfo.Buffer ssaoViewPosAttachmentInfo;
private final Attachment[] SSAO_ATTACHMENTS = new Attachment[3];
private Attachment viewPositionAttachment;
private boolean attachmentsInitialized;
private int sampleCount;
private final Pipeline ssaoPipeline;
private final Pipeline blurPipeline;
@ -89,17 +89,17 @@ public class AmbientOcclusionRenderer {
private Attachment NoiseImageAttachment;
public AmbientOcclusionRenderer(VulkanContext VkCtx, List<Attachment> MRTAttachments, Queue queue) {
viewPositionAttachment = MRTAttachments.get(7);
List<Attachment> attachments = new ArrayList<>();
attachments.add(MRTAttachments.get(7)); // position
attachments.add(viewPositionAttachment);
attachments.add(MRTAttachments.get(2)); // normal
CreateAttachments(VkCtx);
clearColour = VkClearValue.calloc().color(
c -> c.float32(0, 0.0f).float32(1, 0.0f).float32(2, 0.0f).float32(3, 0.0f));
c -> c.float32(0, 1.0f).float32(1, 0.0f).float32(2, 0.0f).float32(3, 0.0f));
ssaoAttachmentInfo = CreateColourAttachmentInfo(ssaoAttachment, clearColour);
ssaoBlurAttachmentInfo = CreateColourAttachmentInfo(ssaoBlurAttachment, clearColour);
ssaoViewPosAttachmentInfo = CreateColourAttachmentInfo(ssaoViewPosAttachment, clearColour);
var textureSamplerInfo = new TextureSamplerInfo(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
VK_BORDER_COLOR_INT_OPAQUE_BLACK, 1, true);
VK_BORDER_COLOR_INT_OPAQUE_BLACK, 1, false);
textureSampler = new TextureSampler(VkCtx, textureSamplerInfo);
ByteBuffer NoiseTexturedata = SSAO_Utils.GenerateNoiseTexture();
@ -173,9 +173,9 @@ public class AmbientOcclusionRenderer {
var vtxBuffStruct = new EmptyVertexBufferStruct();
int[] formats;
if(blur){
formats = new int[]{SSAO_BLUR_ATTACHMENT, VIEW_POS_FORMAT};
formats = new int[]{SSAO_BLUR_ATTACHMENT};
} else {
formats = new int[]{SSAO_RAW_ATTACHMENT, VIEW_POS_FORMAT};
formats = new int[]{SSAO_RAW_ATTACHMENT};
}
var buildInfo = new PipelineBuildInfo(shaderModules, vtxBuffStruct.GetVertexInput(),formats)
.SetDescriptorSetLayouts(descSetLayouts)
@ -235,8 +235,11 @@ public class AmbientOcclusionRenderer {
}
private void CreateSSAOAttachmentDescriptorSet(VulkanContext vkCtx, List<Attachment> Attachments ) {
DescriptorSet descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet(
vkCtx.GetDevice(), DESC_ID_SSAO_ATTACHMENTS,ssaoAttachmentDescriptorLayout );
DescriptorSet descSet = vkCtx.GetDescriptorAllocator().GetDescriptorSet(DESC_ID_SSAO_ATTACHMENTS);
if (descSet == null) {
descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet(
vkCtx.GetDevice(), DESC_ID_SSAO_ATTACHMENTS, ssaoAttachmentDescriptorLayout);
}
List<ImageView> imageViews = new ArrayList<>();
Attachments.forEach(a -> imageViews.add(a.GetVkImageView()));
@ -246,12 +249,15 @@ public class AmbientOcclusionRenderer {
private void CreateBlurDescriptorSet(VulkanContext vkCtx) {
DescriptorSet descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet(
vkCtx.GetDevice(),DESC_ID_SSAO_BLUR_INPUT, blurDescriptorLayout);
DescriptorSet descSet = vkCtx.GetDescriptorAllocator().GetDescriptorSet(DESC_ID_SSAO_BLUR_INPUT);
if (descSet == null) {
descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet(
vkCtx.GetDevice(), DESC_ID_SSAO_BLUR_INPUT, blurDescriptorLayout);
}
List<ImageView> imageViews = new ArrayList<>();
imageViews.add(ssaoAttachment.GetVkImageView());
imageViews.add(ssaoViewPosAttachment.GetVkImageView());
imageViews.add(viewPositionAttachment.GetVkImageView());
descSet.SetImages(vkCtx.GetDevice(), imageViews,textureSampler,0);
}
@ -259,21 +265,20 @@ public class AmbientOcclusionRenderer {
private void CreateAttachments(VulkanContext VkCtx){
SwapChain swapChain = VkCtx.GetSwapChain();
VkExtent2D swapChainExtent = swapChain.GetSwapChainExtent();
float AspectRatio = (float)swapChainExtent.width()/(float)swapChainExtent.height();
int Width = swapChainExtent.width();
int Height = swapChainExtent.height();
int RawWidth = (int)(Math.round(Math.min(swapChainExtent.width()/2.0f, 1920 * AspectRatio)));
int RawHeight = (int)(Math.round(Math.min(swapChainExtent.height()/2.0f, 1080)));
var config = EngineConfig.getInstance();
float scale = config.GetSSAOScale();
int Width = ScaledExtent(swapChainExtent.width(), scale);
int Height = ScaledExtent(swapChainExtent.height(), scale);
sampleCount = config.GetSSAOSamples();
attachmentsInitialized = false;
//SSAO Raw
ssaoAttachment = new Attachment(VkCtx, Width, Height, SSAO_RAW_ATTACHMENT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 1);
SSAO_ATTACHMENTS[0] = ssaoAttachment;
//SSAO Blur
ssaoBlurAttachment = new Attachment(VkCtx, Width, Height, SSAO_BLUR_ATTACHMENT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 1);
SSAO_ATTACHMENTS[1] = ssaoBlurAttachment;
}
ssaoViewPosAttachment = new Attachment(VkCtx, Width, Height, VIEW_POS_FORMAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 1);
SSAO_ATTACHMENTS[1] = ssaoViewPosAttachment;
static int ScaledExtent(int size, float scale) {
return Math.max(1, Math.round(size * scale));
}
public void Render(VulkanContext VkCtx,EngineInstance engineInstance, CommandBuffer cmdBuffer, MultiRenderTargetAttachments MRT,int CurrentFrame) {
@ -285,20 +290,11 @@ public class AmbientOcclusionRenderer {
UpdateSSAOInfo(VkCtx, engineInstance, CurrentFrame);
VulkanUtils.ImageBarrier(stack, cmdHandle, ssaoAttachment.GetVkImage().getVulkanImage(),
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
attachmentsInitialized ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,
attachmentsInitialized ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_2_NONE,
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_ACCESS_2_SHADER_READ_BIT,
VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT);
VulkanUtils.ImageBarrier(stack, cmdHandle, ssaoViewPosAttachment.GetVkImage().getVulkanImage(),
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_ACCESS_2_SHADER_READ_BIT,
attachmentsInitialized ? VK_ACCESS_2_SHADER_READ_BIT : VK_ACCESS_2_NONE,
VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT);
@ -335,11 +331,11 @@ public class AmbientOcclusionRenderer {
VK_IMAGE_ASPECT_COLOR_BIT);
VulkanUtils.ImageBarrier(stack, cmdHandle, ssaoBlurAttachment.GetVkImage().getVulkanImage(),
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
attachmentsInitialized ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,
attachmentsInitialized ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_2_NONE,
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_ACCESS_2_SHADER_READ_BIT,
attachmentsInitialized ? VK_ACCESS_2_SHADER_READ_BIT : VK_ACCESS_2_NONE,
VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT);
@ -367,6 +363,7 @@ public class AmbientOcclusionRenderer {
VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_2_SHADER_READ_BIT,
VK_IMAGE_ASPECT_COLOR_BIT);
attachmentsInitialized = true;
}
}
@ -405,6 +402,8 @@ public class AmbientOcclusionRenderer {
vkCmdSetScissor(cmdHandle, 0, scissor);
}
// Retained for the legacy debug overlay; attachment quality comes from EngineConfig on resize.
@Deprecated
public static float SSAO_RESOLUTION_SCALE = 0.5f;
private void UpdateSSAOInfo(VulkanContext vkCtx, EngineInstance engineInstance, int currentFrame) {
@ -425,10 +424,7 @@ public class AmbientOcclusionRenderer {
data.putFloat(offset + VulkanUtils.FLOAT_SIZE * 2, SSAO_RADIUS);
data.putFloat(offset + VulkanUtils.FLOAT_SIZE * 3, SSAO_BIAS);
offset += VulkanUtils.VEC4_SIZE;
data.putInt(offset, SSAO_KERNEL_SIZE);
offset +=VulkanUtils.INT_SIZE;
data.putFloat(offset, SSAO_RESOLUTION_SCALE);
offset += VulkanUtils.FLOAT_SIZE;
data.putInt(offset, sampleCount);
buffer.UnMapMemory(vkCtx);
}
@ -444,6 +440,7 @@ public class AmbientOcclusionRenderer {
ssaoBlurAttachmentInfo.free();
ssaoBlurAttachment.CleanUp(VkCtx);
viewPositionAttachment = attachments.get(0);
CreateAttachments(VkCtx);
ssaoAttachmentInfo = CreateColourAttachmentInfo(ssaoAttachment, clearColour);
@ -451,9 +448,7 @@ public class AmbientOcclusionRenderer {
ssaoRenderInfo = CreateRenderInfo(ssaoAttachment, ssaoAttachmentInfo,true);
ssaoBlurRenderInfo = CreateRenderInfo(ssaoBlurAttachment, ssaoBlurAttachmentInfo,false);
attachments.add(NoiseImageAttachment);
CreateSSAOAttachmentDescriptorSet(VkCtx,attachments);
CreateSSAOAttachmentDescriptorSet(VkCtx, List.of(attachments.get(0), attachments.get(1), NoiseImageAttachment));
CreateBlurDescriptorSet(VkCtx);
}

View file

@ -15,7 +15,6 @@ import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DisplayToS
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.SwapChain.SwapChain;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanBuffer;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils;
import org.joml.Matrix4f;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.shaderc.Shaderc;
@ -35,16 +34,15 @@ public class ReflectionsRenderer {
private static final long SSR_INFO_BUFFER_SIZE =
VulkanUtils.MATRIX4X4_SIZE + // projection
VulkanUtils.MATRIX4X4_SIZE + // projection inv
VulkanUtils.MATRIX4X4_SIZE + // view
VulkanUtils.MATRIX4X4_SIZE + // view inv
VulkanUtils.VEC4_SIZE + // steps, distance, max steps
VulkanUtils.VEC4_SIZE; //Camera
public static float STEP_SIZE = 0.25f;
public static float MAX_DIST = 200.0f;
public static int MAX_STEPS = 64;
private static final String DESC_ID_SSR_ATTACHMENTS = "SSR_DESC_ID_ATTACHMENTS";
private static final String DESC_ID_SSR_INFO = "SSR_DESC_ID_INFO";
private static final String DESC_ID_SSR_COMPOSITE = "SSR_DESC_ID_COMPOSITE";
private static final String COMPOSITE_FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/ssr_composite_frag.glsl";
private static final String FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/screen_space_reflection_frag.glsl";
private static final String FRAGMENT_SHADER_FILE_SPV = FRAGMENT_SHADER_FILE_GLSL + ".spv";
@ -53,8 +51,14 @@ public class ReflectionsRenderer {
private Attachment ssrAttachment;
private Attachment renderedSceneAttachment;
private VkRenderingAttachmentInfo.Buffer ssrAttachmentInfo;
private Attachment compositeAttachment;
private VkRenderingAttachmentInfo.Buffer compositeAttachmentInfo;
private VkRenderingInfo compositeRenderInfo;
private boolean attachmentsInitialized;
private int maxSteps;
private final Pipeline ssrPipeline;
private final Pipeline compositePipeline;
private final TextureSampler textureSampler;
private DescriptorSetLayout ssrAttachmentDescriptorLayout;
@ -78,8 +82,9 @@ public class ReflectionsRenderer {
clearColour = VkClearValue.calloc().color(
c -> c.float32(0, 0.0f).float32(1, 0.0f).float32(2, 0.0f).float32(3, 0.0f));
ssrAttachmentInfo = CreateColourAttachmentInfo(ssrAttachment, clearColour);
compositeAttachmentInfo = CreateColourAttachmentInfo(compositeAttachment, clearColour);
var textureSamplerInfo = new TextureSamplerInfo(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
VK_BORDER_COLOR_INT_OPAQUE_BLACK, 1, true);
VK_BORDER_COLOR_INT_OPAQUE_BLACK, 1, false);
textureSampler = new TextureSampler(VkCtx, textureSamplerInfo);
int numAttachments = attachments.size();
@ -97,6 +102,7 @@ public class ReflectionsRenderer {
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, DESC_ID_SSR_INFO,ssrInfoDescriptorLayout);
ssrRenderInfo = CreateRenderInfo(ssrAttachment, ssrAttachmentInfo,true);
compositeRenderInfo = CreateRenderInfo(compositeAttachment, compositeAttachmentInfo, false);
ShaderModule[] shaderModules = CreateRawShaderModules(VkCtx);
ssrPipeline = CreatePipeline(VkCtx, shaderModules,
@ -105,6 +111,15 @@ public class ReflectionsRenderer {
ssrInfoDescriptorLayout
});
Arrays.stream(shaderModules).toList().forEach(shaderModule -> shaderModule.CleanUp(VkCtx));
if (EngineConfig.getInstance().RecompileShaders()) {
ShaderCompiler.CompileGLSLShaderOnChange(COMPOSITE_FRAGMENT_SHADER_FILE_GLSL, Shaderc.shaderc_glsl_fragment_shader);
}
shaderModules = new ShaderModule[]{
new ShaderModule(VkCtx, VK_SHADER_STAGE_VERTEX_BIT, VERTEX_SHADER_FILE_SPV, null),
new ShaderModule(VkCtx, VK_SHADER_STAGE_FRAGMENT_BIT, COMPOSITE_FRAGMENT_SHADER_FILE_GLSL + ".spv", null)
};
compositePipeline = CreatePipeline(VkCtx, shaderModules, new DescriptorSetLayout[]{ssrAttachmentDescriptorLayout});
Arrays.stream(shaderModules).toList().forEach(shaderModule -> shaderModule.CleanUp(VkCtx));
}
@ -162,13 +177,23 @@ public class ReflectionsRenderer {
}
private void CreateSSRAttachmentDescriptorSet(VulkanContext vkCtx, List<Attachment> Attachments ) {
DescriptorSet descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet(
vkCtx.GetDevice(), DESC_ID_SSR_ATTACHMENTS, ssrAttachmentDescriptorLayout);
DescriptorSet descSet = vkCtx.GetDescriptorAllocator().GetDescriptorSet(DESC_ID_SSR_ATTACHMENTS);
if (descSet == null) {
descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet(
vkCtx.GetDevice(), DESC_ID_SSR_ATTACHMENTS, ssrAttachmentDescriptorLayout);
}
List<ImageView> imageViews = new ArrayList<>();
Attachments.forEach(a -> imageViews.add(a.GetVkImageView()));
descSet.SetImages(vkCtx.GetDevice(), imageViews, textureSampler, 0);
DescriptorSet compositeSet = vkCtx.GetDescriptorAllocator().GetDescriptorSet(DESC_ID_SSR_COMPOSITE);
if (compositeSet == null) {
compositeSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet(
vkCtx.GetDevice(), DESC_ID_SSR_COMPOSITE, ssrAttachmentDescriptorLayout);
}
compositeSet.SetImages(vkCtx.GetDevice(), List.of(renderedSceneAttachment.GetVkImageView(),
ssrAttachment.GetVkImageView(), Attachments.get(3).GetVkImageView(), Attachments.get(2).GetVkImageView()), textureSampler, 0);
}
@ -178,13 +203,21 @@ public class ReflectionsRenderer {
SwapChain swapChain = VkCtx.GetSwapChain();
VkExtent2D swapChainExtent = swapChain.GetSwapChainExtent();
float AspectRatio = (float)swapChainExtent.width()/(float)swapChainExtent.height();
int Width = swapChainExtent.width();
int Height = swapChainExtent.height();
int RawWidth = (int)(Math.round(Math.min(swapChainExtent.width()/2.0f, 1920 * AspectRatio)));
int RawHeight = (int)(Math.round(Math.min(swapChainExtent.height()/2.0f, 1080)));
var config = EngineConfig.getInstance();
float scale = config.GetSSRScale();
int RawWidth = ScaledExtent(Width, scale);
int RawHeight = ScaledExtent(Height, scale);
maxSteps = config.GetSSRMaxSteps();
attachmentsInitialized = false;
//SSR Raw
ssrAttachment = new Attachment(VkCtx, Width, Height, SSR_FORMAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 1);
ssrAttachment = new Attachment(VkCtx, RawWidth, RawHeight, SSR_FORMAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 1);
compositeAttachment = new Attachment(VkCtx, Width, Height, SSR_FORMAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 1);
}
static int ScaledExtent(int size, float scale) {
return Math.max(1, Math.round(size * scale));
}
public void Render(VulkanContext VkCtx,EngineInstance engineInstance, CommandBuffer cmdBuffer, int CurrentFrame) {
@ -201,9 +234,11 @@ public class ReflectionsRenderer {
VK_IMAGE_ASPECT_COLOR_BIT);
VulkanUtils.ImageBarrier(stack, cmdHandle, ssrAttachment.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,
attachmentsInitialized ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
attachmentsInitialized ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_2_NONE,
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
attachmentsInitialized ? VK_ACCESS_2_SHADER_READ_BIT : VK_ACCESS_2_NONE, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT);
vkCmdBeginRendering(cmdHandle, ssrRenderInfo);
@ -223,6 +258,27 @@ public class ReflectionsRenderer {
vkCmdDraw(cmdHandle, 3, 1, 0, 0);
vkCmdEndRendering(cmdHandle);
VulkanUtils.ImageBarrier(stack, cmdHandle, ssrAttachment.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);
// Discard old contents, but wait for the previous frame's post-process reads.
VulkanUtils.ImageBarrier(stack, cmdHandle, compositeAttachment.GetVkImage().getVulkanImage(),
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT);
vkCmdBeginRendering(cmdHandle, compositeRenderInfo);
vkCmdBindPipeline(cmdHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, compositePipeline.GetVulkanPipeline());
SetViewportAndScissor(stack, cmdHandle, compositeAttachment);
vkCmdBindDescriptorSets(cmdHandle, VK_PIPELINE_BIND_POINT_GRAPHICS,
compositePipeline.GetVulkanPipelineLayout(), 0,
stack.longs(descAllocator.GetDescriptorSet(DESC_ID_SSR_COMPOSITE).GetVkDescriptorSet()), null);
vkCmdDraw(cmdHandle, 3, 1, 0, 0);
vkCmdEndRendering(cmdHandle);
attachmentsInitialized = true;
}
}
@ -247,7 +303,7 @@ public class ReflectionsRenderer {
public Attachment GetSSRAttachment() {
return ssrAttachment;
return compositeAttachment;
}
private void UpdateSSRInfo(VulkanContext vkCtx, EngineInstance engineInstance, int currentFrame) {
@ -259,16 +315,12 @@ public class ReflectionsRenderer {
engineInstance.scene().GetProjection().GetProjectionMatrix().get(offset, data);
offset += VulkanUtils.MATRIX4X4_SIZE;
engineInstance.scene().GetProjection().GetProjectionMatrix().invert(new Matrix4f()).get(offset, data);
offset += VulkanUtils.MATRIX4X4_SIZE;
engineInstance.scene().GetCamera().GetViewMatrix().get(offset, data);
offset += VulkanUtils.MATRIX4X4_SIZE;
engineInstance.scene().GetCamera().GetViewMatrix().invert(new Matrix4f()).get(offset, data);
offset += VulkanUtils.MATRIX4X4_SIZE;
data.putFloat(offset, STEP_SIZE);
data.putFloat(offset + VulkanUtils.FLOAT_SIZE, MAX_DIST);
data.putInt(offset + VulkanUtils.FLOAT_SIZE * 2, MAX_STEPS);
data.putInt(offset + VulkanUtils.FLOAT_SIZE * 2, maxSteps);
data.putInt(offset + VulkanUtils.FLOAT_SIZE * 3, 0);
offset += VulkanUtils.VEC4_SIZE;
@ -283,6 +335,9 @@ public class ReflectionsRenderer {
ssrRenderInfo.free();
ssrAttachmentInfo.free();
ssrAttachment.CleanUp(VkCtx);
compositeRenderInfo.free();
compositeAttachmentInfo.free();
compositeAttachment.CleanUp(VkCtx);
renderedSceneAttachment = attachments.get(0);
@ -290,6 +345,8 @@ public class ReflectionsRenderer {
ssrAttachmentInfo = CreateColourAttachmentInfo(ssrAttachment, clearColour);
ssrRenderInfo = CreateRenderInfo(ssrAttachment, ssrAttachmentInfo,true);
compositeAttachmentInfo = CreateColourAttachmentInfo(compositeAttachment, clearColour);
compositeRenderInfo = CreateRenderInfo(compositeAttachment, compositeAttachmentInfo, false);
CreateSSRAttachmentDescriptorSet(VkCtx, attachments);
}
@ -298,6 +355,10 @@ public class ReflectionsRenderer {
ssrAttachmentInfo.free();
ssrAttachment.CleanUp(VkCtx);
ssrPipeline.CleanUp(VkCtx);
compositeRenderInfo.free();
compositeAttachmentInfo.free();
compositeAttachment.CleanUp(VkCtx);
compositePipeline.CleanUp(VkCtx);
clearColour.free();
Arrays.stream(ssrInfoBuffers).toList().forEach(b -> b.cleanup(VkCtx));
ssrAttachmentDescriptorLayout.CleanUp(VkCtx);

View file

@ -125,6 +125,12 @@ public class ShadowRenderer {
newPipeline = createPipeline(VulkanContext, shaderModules, new DescriptorSetLayout[]{uniformGeomDescriptorSetLayout, textDescriptorSetLayout,
descLayoutFrgStorage});
Arrays.asList(shaderModules).forEach(s -> s.CleanUp(VulkanContext));
shaderModules = createPackedVertexShaderModules(VulkanContext);
Pipeline newPipelinePacked = CreateVoxelPipeline(VulkanContext, shaderModules, new DescriptorSetLayout[]{
uniformGeomDescriptorSetLayout, textDescriptorSetLayout, descLayoutFrgStorage,
VoxelWorldManager.Resources().faceGraphicsLayout()
});
Arrays.asList(shaderModules).forEach(s -> s.CleanUp(VulkanContext));
VkRenderingInfo OldRenderInfo = renderingInfo;
Attachment OldColourAttachment = colorAttachment;
@ -132,6 +138,7 @@ public class ShadowRenderer {
VkRenderingAttachmentInfo OldDepthAttachmentInfo = depthAttachmentInfo;
VkRenderingAttachmentInfo.Buffer OldColourAttachmentInfo = colorAttachmentInfo;
Pipeline OldPipeline = pipeline;
Pipeline oldPipelinePacked = pipelinePacked;
renderingInfo = NewRenderInfo;
colorAttachment = newColourAttachment;
@ -139,6 +146,7 @@ public class ShadowRenderer {
depthAttachmentInfo = newDepthAttachmentInfo;
colorAttachmentInfo = newColourAttachmentInfo;
pipeline = newPipeline;
pipelinePacked = newPipelinePacked;
OldRenderInfo.free();
OldDepthAttachmentInfo.free();
@ -146,13 +154,14 @@ public class ShadowRenderer {
OldColourAttachmentInfo.free();
OldColourAttachment.CleanUp(VulkanContext);
OldPipeline.CleanUp(VulkanContext);
oldPipelinePacked.CleanUp(VulkanContext);
}
public ShadowRenderer(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));
c -> c.float32(0, 1.0f).float32(1, 1.0f).float32(2, 0.0f).float32(3, 0.0f));
ClearValueDepth = VkClearValue.calloc().depthStencil(d -> d.depth(1.0f).stencil(0));
depthAttachment = createDepthAttachment(VulkanContext);
depthAttachmentInfo = createDepthAttachmentInfo(depthAttachment, ClearValueDepth);
@ -241,7 +250,7 @@ public class ShadowRenderer {
})
.SetDescriptorSetLayouts(DescriptorSetLayouts)
.SetDescriptorSetLayouts(DescriptorSetLayouts)
.SetDepthClamp(VulkanContext.GetDevice().getDepthClamp())
.SetDepthClamp(false)
.CullMode(VK_CULL_MODE_FRONT_BIT)
.DepthBuffer(VK_COMPARE_OP_LESS_OR_EQUAL);
var pipeline = new DefaultPipeline(VulkanContext, buildInfo);
@ -267,7 +276,7 @@ public class ShadowRenderer {
new PushConstantsRange(VK_SHADER_STAGE_VERTEX_BIT, 0, PUSH_CONSTANTS_SIZE)
})
.SetDescriptorSetLayouts(DescriptorSetLayouts)
.SetDepthClamp(VkCtx.GetDevice().getDepthClamp())
.SetDepthClamp(false)
.CullMode(VK_CULL_MODE_FRONT_BIT)
.DepthBuffer(VK_COMPARE_OP_LESS_OR_EQUAL);
@ -366,7 +375,7 @@ public class ShadowRenderer {
try (var stack = MemoryStack.stackPush()) {
IScene scene = engCtx.scene();
ShadowUtils.updateCascadeShadows(cascadeShadows[currentFrame], scene);
boolean validCascades = ShadowUtils.updateCascadeShadows(cascadeShadows[currentFrame], scene);
VkCommandBuffer cmdHandle = cmdBuffer.GetVulkanCommandBuffer();
@ -384,6 +393,10 @@ public class ShadowRenderer {
VK_IMAGE_ASPECT_DEPTH_BIT);
vkCmdBeginRendering(cmdHandle, renderingInfo);
if (!validCascades) {
vkCmdEndRendering(cmdHandle);
return;
}
vkCmdBindPipeline(cmdHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.GetVulkanPipeline());
@ -451,7 +464,12 @@ public class ShadowRenderer {
.put(3, descAllocator.GetDescriptorSet(VoxelWorldManager.Resources().faceGraphicsDescriptorId()).GetVkDescriptorSet());
vkCmdBindPipeline(cmdHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelinePacked.GetVulkanPipeline());
vkCmdBindDescriptorSets(cmdHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelinePacked.GetVulkanPipelineLayout(),0,descriptorSets,null);
VoxelWorldManager.RecordRender(cmdHandle,pipelinePacked,materialsCache,true);
List<CascadeData> cascadeDataList = cascadeShadows[currentFrame].GetCascadeData();
Matrix4f[] clipMatrices = new Matrix4f[cascadeDataList.size()];
for (int i = 0; i < clipMatrices.length; i++) {
clipMatrices[i] = cascadeDataList.get(i).GetProjViewMatrix();
}
VoxelWorldManager.RecordRender(cmdHandle, pipelinePacked, materialsCache, true, clipMatrices);
vkCmdEndRendering(cmdHandle);
}
}

View file

@ -4,11 +4,9 @@ import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig;
import net.halbear.Terrain4J.EngineCore.Main.Scene.Camera;
import net.halbear.Terrain4J.EngineCore.Main.Scene.ILight;
import net.halbear.Terrain4J.EngineCore.Main.Scene.IScene;
import net.halbear.Terrain4J.EngineCore.Main.Scene.SceneLightingManager;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Universal.Projection.Project3D;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import java.util.List;
@ -21,11 +19,10 @@ public class ShadowUtils {
private ShadowUtils() {
}
public static void updateCascadeShadows(CascadeShadows cascadeShadows, IScene scene) {
public static boolean updateCascadeShadows(CascadeShadows cascadeShadows, IScene scene) {
Camera camera = scene.GetCamera();
Matrix4f viewMatrix = camera.GetViewMatrix();
Project3D projection = scene.GetProjection().GetNormalFarPlanes();
Matrix4f projMatrix = projection.GetProjectionMatrix();
Project3D projection = scene.GetProjection();
ILight[] lights = scene.GetLightingManager().GetLights();
int numLights = lights.length;
ILight dirLight = null;
@ -36,114 +33,112 @@ public class ShadowUtils {
}
}
if (dirLight == null) {
throw new RuntimeException("Could not find directional light");
return disableCascades(cascadeShadows.GetCascadeData());
}
Vector3f lightPos = dirLight.GetPosition();
float[] cascadeSplits = new float[SceneLightingManager.SHADOW_MAP_CASCADE_COUNT];
float nearClip = projection.GetNearZ();//projection.GetNearZ();
float farClip = EngineConfig.getInstance().MaxShadowDistance();//Math.min(projection.GetFarZ(), EngineConfig.getInstance().MaxShadowDistance());
float clipRange = farClip - nearClip;
float minZ = nearClip;
float maxZ = nearClip + clipRange;
float range = maxZ - minZ;
float ratio = maxZ / minZ;
return updateCascadeShadows(cascadeShadows, viewMatrix, projection, lightPos,
EngineConfig.getInstance().MaxShadowDistance(), EngineConfig.getInstance().GetShadowMapSize());
}
static boolean updateCascadeShadows(CascadeShadows cascadeShadows, Matrix4f viewMatrix, Project3D projection,
Vector3f lightPos, float maxShadowDistance, int shadowMapSize) {
List<CascadeData> cascadeDataList = cascadeShadows.GetCascadeData();
int numCascades = cascadeDataList.size();
// Calculate split depths based on view camera frustum
// Based on method presented in https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html
for (int i = 0; i < numCascades; i++) {
float p = (i + 1) / (float) (SceneLightingManager.SHADOW_MAP_CASCADE_COUNT);
float log = (float) (minZ * java.lang.Math.pow(ratio, p));
float uniform = minZ + range * p;
float d = LAMBDA * (log - uniform) + uniform;
cascadeSplits[i] = (d - nearClip) / clipRange;
float nearClip = Math.min(projection.GetNearZ(), projection.GetFarZ());
float farClip = Math.min(Math.max(projection.GetNearZ(), projection.GetFarZ()), maxShadowDistance);
Matrix4f projMatrix = projection.GetProjectionMatrix();
if (numCascades == 0 || !Float.isFinite(maxShadowDistance) || !Float.isFinite(nearClip)
|| !Float.isFinite(farClip) || nearClip <= 0.0f || farClip <= nearClip || shadowMapSize <= 4
|| !viewMatrix.isFinite() || !projMatrix.isFinite() || !lightPos.isFinite()) {
return disableCascades(cascadeDataList);
}
float lastSplitDist = 0.0f;
double lightLength = Math.sqrt((double) lightPos.x * lightPos.x + (double) lightPos.y * lightPos.y
+ (double) lightPos.z * lightPos.z);
if (lightLength == 0.0) {
return disableCascades(cascadeDataList);
}
Vector3f lightDir = new Vector3f((float) (lightPos.x / lightLength), (float) (lightPos.y / lightLength),
(float) (lightPos.z / lightLength));
Vector3f up = Math.abs(lightDir.dot(UP)) > 0.99f ? UP_ALT : UP;
Matrix4f lightViewMatrix = new Matrix4f().lookAt(new Vector3f(), lightDir, up);
Matrix4f invCam = new Matrix4f(viewMatrix).invert();
Matrix4f viewToLight = new Matrix4f(lightViewMatrix).mul(invCam);
Matrix4f inverseProjection = new Matrix4f(projMatrix).invert();
// Unproject inside Vulkan's 0..1 depth range, not at a possibly infinite far plane.
// Rays scaled to view-space depth work with both normal and reversed camera projections.
Vector3f[] rays = new Vector3f[4];
for (int j = 0; j < rays.length; j++) {
Vector3f ray = inverseProjection.transformProject(new Vector3f((j & 1) == 0 ? -1 : 1,
(j & 2) == 0 ? -1 : 1, 0.5f));
if (!ray.isFinite() || ray.z >= 0.0f) {
return disableCascades(cascadeDataList);
}
rays[j] = ray.div(-ray.z);
}
float lastSplitDist = nearClip;
for (int i = 0; i < numCascades; i++) {
float splitDist = cascadeSplits[i];
double p = (i + 1.0) / numCascades;
double log = nearClip * Math.pow((double) farClip / nearClip, p);
double uniform = nearClip + ((double) farClip - nearClip) * p;
float splitDist = i == numCascades - 1 ? farClip : (float) (LAMBDA * (log - uniform) + uniform);
if (splitDist <= lastSplitDist) {
return disableCascades(cascadeDataList);
}
Vector3f[] frustumCorners = new Vector3f[]{
new Vector3f(-1.0f, 1.0f, 0.0f),
new Vector3f(1.0f, 1.0f, 0.0f),
new Vector3f(1.0f, -1.0f, 0.0f),
new Vector3f(-1.0f, -1.0f, 0.0f),
new Vector3f(-1.0f, 1.0f, 1.0f),
new Vector3f(1.0f, 1.0f, 1.0f),
new Vector3f(1.0f, -1.0f, 1.0f),
new Vector3f(-1.0f, -1.0f, 1.0f),
};
// Project frustum corners into world space
var invCam = (new Matrix4f(projMatrix).mul(viewMatrix)).invert();
Vector3f minExtents = new Vector3f(Float.POSITIVE_INFINITY);
Vector3f maxExtents = new Vector3f(Float.NEGATIVE_INFINITY);
Vector3f corner = new Vector3f();
for (int j = 0; j < 8; j++) {
Vector4f invCorner = new Vector4f(frustumCorners[j], 1.0f).mul(invCam);
frustumCorners[j] = new Vector3f(invCorner.x, invCorner.y, invCorner.z).div(invCorner.w);
corner.set(rays[j & 3]).mul(j < 4 ? lastSplitDist : splitDist);
viewToLight.transformPosition(corner);
if (!corner.isFinite()) {
return disableCascades(cascadeDataList);
}
minExtents.min(corner);
maxExtents.max(corner);
}
for (int j = 0; j < 4; j++) {
var dist = new Vector3f(frustumCorners[j + 4]).sub(frustumCorners[j]);
frustumCorners[j + 4] = new Vector3f(frustumCorners[j]).add(new Vector3f(dist).mul(splitDist));
frustumCorners[j] = new Vector3f(frustumCorners[j]).add(new Vector3f(dist).mul(lastSplitDist));
// Tight light-space XY bounds; reserve two texels per edge for snapping and the 3x3 filter.
float width = paddedExtent(maxExtents.x - minExtents.x, shadowMapSize);
float height = paddedExtent(maxExtents.y - minExtents.y, shadowMapSize);
float centerX = minExtents.x * 0.5f + maxExtents.x * 0.5f;
float centerY = minExtents.y * 0.5f + maxExtents.y * 0.5f;
float snappedX = (float) Math.rint((double) centerX / (width / shadowMapSize));
float snappedY = (float) Math.rint((double) centerY / (height / shadowMapSize));
// Include off-camera casters up to the coverage distance toward the light.
// Shadow depth remains forward 0..1 regardless of the camera's depth convention.
Matrix4f lightOrthoMatrix = new Matrix4f().ortho(-width * 0.5f, width * 0.5f,
-height * 0.5f, height * 0.5f, -maxExtents.z - farClip, -minExtents.z + 1.0f, true);
lightOrthoMatrix.m30(-2.0f * snappedX / shadowMapSize);
lightOrthoMatrix.m31(-2.0f * snappedY / shadowMapSize);
lightOrthoMatrix.mul(lightViewMatrix);
if (!Float.isFinite(width) || !Float.isFinite(height) || !lightOrthoMatrix.isFinite()) {
return disableCascades(cascadeDataList);
}
var frustumCenter = new Vector3f(0.0f);
for (int j = 0; j < 8; j++) {
frustumCenter.add(frustumCorners[j]);
}
frustumCenter.div(8.0f);
var up = UP;
float sphereRadius = 0.0f;
for (int j = 0; j < 8; j++) {
float dist = new Vector3f(frustumCorners[j]).sub(frustumCenter).length();
sphereRadius = java.lang.Math.max(sphereRadius, dist);
}
sphereRadius = (float) java.lang.Math.ceil(sphereRadius * 16.0f) / 16.0f;
var maxExtents = new Vector3f(sphereRadius, sphereRadius, sphereRadius);
var minExtents = new Vector3f(maxExtents).mul(-1.0f);
var lightDir = new Vector3f(lightPos.x, lightPos.y, lightPos.z);
var shadowCameraPos = new Vector3f(frustumCenter).add(lightDir.mul(minExtents.z));
float dot = java.lang.Math.abs(new Vector3f(lightPos.x, lightPos.y, lightPos.z).dot(up));
if (dot == 1.0f) {
up = UP_ALT;
}
var lightViewMatrix = new Matrix4f().lookAt(shadowCameraPos, frustumCenter, up);
var lightOrthoMatrix = new Matrix4f().ortho
(minExtents.x, maxExtents.x, minExtents.y, maxExtents.y, 0.0f, maxExtents.z - minExtents.z, true);
int shadowMapSize = EngineConfig.getInstance().GetShadowMapSize();
Vector4f shadowOrigin = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
lightViewMatrix.transform(shadowOrigin);
shadowOrigin.mul(shadowMapSize / 2.0f);
Vector4f roundedOrigin = new Vector4f(shadowOrigin).round();
Vector4f roundOffset = roundedOrigin.sub(shadowOrigin);
roundOffset.mul(2.0f / shadowMapSize);
roundOffset.z = 0.0f;
roundOffset.w = 0.0f;
lightOrthoMatrix.m30(lightOrthoMatrix.m30() + roundOffset.x);
lightOrthoMatrix.m31(lightOrthoMatrix.m31() + roundOffset.y);
lightOrthoMatrix.m32(lightOrthoMatrix.m32() + roundOffset.z);
lightOrthoMatrix.m33(lightOrthoMatrix.m33() + roundOffset.w);
// Store split distance and matrix in cascade
CascadeData cascadeData = cascadeDataList.get(i);
cascadeData.SetSplitDistance((nearClip + splitDist * clipRange) * -1.0f);
cascadeData.SetProjectionViewMatrix(lightOrthoMatrix.mul(lightViewMatrix));
lastSplitDist = cascadeSplits[i];
cascadeData.SetSplitDistance(-splitDist);
cascadeData.SetProjectionViewMatrix(lightOrthoMatrix);
lastSplitDist = splitDist;
}
return true;
}
private static float paddedExtent(float extent, int shadowMapSize) {
double rounded = Math.max(1.0 / 16.0, Math.ceil((double) extent * 16.0) / 16.0);
return (float) (rounded * shadowMapSize / (shadowMapSize - 4.0));
}
private static boolean disableCascades(List<CascadeData> cascadeDataList) {
for (CascadeData cascadeData : cascadeDataList) {
cascadeData.SetSplitDistance(0.0f);
cascadeData.GetProjViewMatrix().identity();
}
return false;
}
}

View file

@ -15,6 +15,7 @@ public class ShaderCompiler {
public static byte[] CompileShader(String ShaderCode, int ShaderType, boolean HLSLshader){
long Compiler = 0;
long Options = 0;
long CompilationResult = 0;
byte[] CompiledShader;
try{
Compiler = Shaderc.shaderc_compiler_initialize();
@ -22,9 +23,11 @@ public class ShaderCompiler {
if(EngineConfig.getInstance().DebugShaders()){
Shaderc.shaderc_compile_options_set_generate_debug_info(Options);
Shaderc.shaderc_compile_options_set_optimization_level(Options,0);
} else {
Shaderc.shaderc_compile_options_set_optimization_level(Options, Shaderc.shaderc_optimization_level_performance);
}
Shaderc.shaderc_compile_options_set_source_language(Options, HLSLshader ? Shaderc.shaderc_source_language_hlsl : Shaderc.shaderc_source_language_glsl);
long CompilationResult = Shaderc.shaderc_compile_into_spv(
CompilationResult = Shaderc.shaderc_compile_into_spv(
Compiler,
ShaderCode,
ShaderType,
@ -41,6 +44,7 @@ public class ShaderCompiler {
CompiledShader = new byte[buffer.remaining()];
buffer.get(CompiledShader);
} finally{
if (CompilationResult != 0) Shaderc.shaderc_result_release(CompilationResult);
Shaderc.shaderc_compile_options_release(Options);
Shaderc.shaderc_compiler_release(Compiler);
}
@ -52,11 +56,15 @@ public class ShaderCompiler {
try{
var glslFile = new File(ShaderFile);
var spvFile = new File(ShaderFile + ".spv");
if(!spvFile.exists() || glslFile.lastModified() > spvFile.lastModified()){
var optionsFile = new File(ShaderFile + ".spv.options");
String options = "performance-v1:debug=" + EngineConfig.getInstance().DebugShaders();
boolean optionsChanged = !optionsFile.exists() || !Files.readString(optionsFile.toPath()).equals(options);
if(!spvFile.exists() || glslFile.lastModified() > spvFile.lastModified() || optionsChanged){
Logger.debug("Compiling new [{}] shader from [{}] source",spvFile.getPath(),glslFile.getPath());
var ShaderCode = new String(Files.readAllBytes(glslFile.toPath()));
CompiledShader = CompileShader(ShaderCode, ShaderType, false);
Files.write(spvFile.toPath(), CompiledShader);
Files.writeString(optionsFile.toPath(), options);
} else{
Logger.debug("Loading Compiled Shader [{}]",spvFile.getPath());
}

View file

@ -3,6 +3,7 @@ package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Shader;
import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.vulkan.VkShaderModuleCreateInfo;
import static org.lwjgl.vulkan.VK13.vkCreateShaderModule;
import static org.lwjgl.vulkan.VK13.vkDestroyShaderModule;
@ -35,17 +36,26 @@ public class ShaderModule {
}
private static long CreateShaderModule(VulkanContext VkCtx, byte[] Code){
ByteBuffer CodePointer = CreateCodeBuffer(Code);
try(var MemStack = MemoryStack.stackPush()){
ByteBuffer CodePointer = MemStack.malloc(Code.length).put(0,Code);
var ShaderModuleCreateInfo = VkShaderModuleCreateInfo.calloc(MemStack)
.sType$Default()
.pCode(CodePointer);
LongBuffer LongPoiner = MemStack.mallocLong(1);
VulkanUtils.vkCheck(vkCreateShaderModule(VkCtx.GetDevice().FetchVulkanDevice(), ShaderModuleCreateInfo, null, LongPoiner),"Failed to create a new Shader Module");
return LongPoiner.get(0);
} finally {
MemoryUtil.memFree(CodePointer);
}
}
static ByteBuffer CreateCodeBuffer(byte[] Code) {
if (Code.length == 0 || Code.length % Integer.BYTES != 0) {
throw new IllegalArgumentException("SPIR-V must contain complete 32-bit words");
}
return MemoryUtil.memAlloc(Code.length).put(Code).flip();
}
public VkSpecializationInfo GetSpecializationInfo(){return SpecializationInfo;}
public void CleanUp(VulkanContext VkCtx){

View file

@ -23,6 +23,9 @@ public class Device {
private final boolean depthClamp;
private final VkDevice VulkanDevice;
private final boolean SamplesAnisotropy;
private final boolean multiDrawIndirect;
private final boolean drawIndirectFirstInstance;
private final int maxDrawIndirectCount;
public static final int VENDOR_AMD = 0x1002;
public static final int VENDOR_NVIDIA = 0x10DE;
public static final int VENDOR_INTEL = 0x8086;
@ -66,6 +69,10 @@ public class Device {
var features = features2.features();
VkPhysicalDeviceFeatures SupportedFeatures = PhysDevice.GetPhysicalDeviceFeatures();
multiDrawIndirect = SupportedFeatures.multiDrawIndirect();
drawIndirectFirstInstance = SupportedFeatures.drawIndirectFirstInstance();
features.multiDrawIndirect(multiDrawIndirect);
features.drawIndirectFirstInstance(drawIndirectFirstInstance);
SamplesAnisotropy = SupportedFeatures.samplerAnisotropy();
if(SamplesAnisotropy){
features.samplerAnisotropy(true);
@ -83,8 +90,9 @@ public class Device {
.pQueueCreateInfos(QueueCreationInfoBuffer);
PointerBuffer pp = MemStack.mallocPointer(1);
VkPhysicalDeviceProperties deviceProps = VkPhysicalDeviceProperties.malloc();
VkPhysicalDeviceProperties deviceProps = VkPhysicalDeviceProperties.malloc(MemStack);
vkGetPhysicalDeviceProperties(PhysDevice.GetPhysicalDevice(), deviceProps);
maxDrawIndirectCount = (int) Math.min(Integer.MAX_VALUE, Integer.toUnsignedLong(deviceProps.limits().maxDrawIndirectCount()));
int VendorID = deviceProps.vendorID();
DeviceName = deviceProps.deviceNameString();
@ -106,6 +114,9 @@ public class Device {
}
public GPU_VENDOR GetVendor(){return Vendor;}
public boolean multiDrawIndirect(){return multiDrawIndirect;}
public boolean drawIndirectFirstInstance(){return drawIndirectFirstInstance;}
public int maxDrawIndirectCount(){return maxDrawIndirectCount;}
public String GetDeviceName(){return DeviceName;}