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

2
.gitignore vendored
View file

@ -29,6 +29,7 @@ build/
.idea/**/dbnavigator.xml .idea/**/dbnavigator.xml
*.t4jlog *.t4jlog
*.spv *.spv
*.spv.options
# Gradle # Gradle
.idea/**/gradle.xml .idea/**/gradle.xml
.idea/**/libraries .idea/**/libraries
@ -90,3 +91,4 @@ fabric.properties
/resources/models/Forest/ /resources/models/Forest/
/resources/models/tree/ /resources/models/tree/
/resources/models/woman/ /resources/models/woman/
/docs/

View file

@ -0,0 +1,10 @@
def validationRun = System.currentTimeMillis()
gradle.projectsEvaluated {
allprojects {
tasks.withType(Test).configureEach {
binaryResultsDirectory.set(layout.buildDirectory.dir("renderer-validation/${validationRun}/binary"))
reports.junitXml.outputLocation.set(layout.buildDirectory.dir("renderer-validation/${validationRun}/xml"))
reports.html.outputLocation.set(layout.buildDirectory.dir("renderer-validation/${validationRun}/html"))
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before After
Before After

View file

@ -3,10 +3,10 @@
layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in; layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
const int CHUNK_SIZE = 16; const int CHUNK_SIZE = 16;
const int CHUNK_AREA = CHUNK_SIZE * CHUNK_SIZE; const int SCRATCH_SIZE = CHUNK_SIZE + 2;
const int VOXEL_COUNT = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE; const int SCRATCH_AREA = SCRATCH_SIZE * SCRATCH_SIZE;
const int SCRATCH_VOXEL_COUNT = SCRATCH_SIZE * SCRATCH_SIZE * SCRATCH_SIZE;
const uint VERTICES_PER_FACE = 6u;
const uint MAX_VISIBLE_FACES_PER_CHUNK = uint(CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE * 6); const uint MAX_VISIBLE_FACES_PER_CHUNK = uint(CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE * 6);
layout(std430, binding = 0) readonly buffer VoxelData { layout(std430, binding = 0) readonly buffer VoxelData {
@ -17,19 +17,8 @@ layout(std430, binding = 1) buffer FaceBuffer {
uint faces[]; uint faces[];
}; };
struct DrawCommand { layout(std430, binding = 2) buffer Counters {
uint vertexCount; uint faceCount[];
uint instanceCount;
uint firstVertex;
uint firstInstance;
};
layout(std430, binding = 2) buffer DrawCommands {
DrawCommand drawCmds[];
};
layout(std430, binding = 3) buffer Counters {
uint faceCount;
} counters; } counters;
struct Voxel { struct Voxel {
@ -52,23 +41,23 @@ layout(push_constant) uniform ChunkInfo {
int slot; int slot;
uint faceOffset; uint faceOffset;
uint voxelTypeCount; uint voxelTypeCount;
uint indirectCommandIndex;
uint biomeCount; uint biomeCount;
uint structureCount; uint structureCount;
uint worldSeed; uint worldSeed;
}; };
uint flatten(ivec3 pos) { uint flatten(ivec3 pos) {
return uint((pos.x * CHUNK_AREA) + (pos.y * CHUNK_SIZE) + pos.z); pos += ivec3(1);
return uint(slot * SCRATCH_VOXEL_COUNT + pos.x * SCRATCH_AREA + pos.y * SCRATCH_SIZE + pos.z);
} }
bool isSeeThrough(Voxel voxel, uint type) { bool isSeeThrough(Voxel voxel, uint type) {
if (type == 0u || voxel.BlockType == 3u || type > voxelTypeCount) return true; if (type == 0u || voxel.BlockType == 3u || type > voxelTypeCount) return true;
return voxelReg.materials[type - 1u].BlockType >= 1u; return voxelReg.materials[type - 1u].BlockType >= 1u;
} }
uint getVoxel(ivec3 pos) { uint getVoxel(ivec3 pos) {
if (pos.x < 0 || pos.x >= CHUNK_SIZE) return 0u; if (pos.x < -1 || pos.x > CHUNK_SIZE) return 0u;
if (pos.y < 0 || pos.y >= CHUNK_SIZE) return 0u; if (pos.y < -1 || pos.y > CHUNK_SIZE) return 0u;
if (pos.z < 0 || pos.z >= CHUNK_SIZE) return 0u; if (pos.z < -1 || pos.z > CHUNK_SIZE) return 0u;
return voxels[flatten(pos)]; return voxels[flatten(pos)];
} }
@ -175,7 +164,7 @@ void emitFace(ivec3 voxelPos, uint faceIndex, uint voxelType) {
return; return;
} }
uint localFace = atomicAdd(counters.faceCount, 1u); uint localFace = atomicAdd(counters.faceCount[slot], 1u);
if (localFace >= MAX_VISIBLE_FACES_PER_CHUNK) { if (localFace >= MAX_VISIBLE_FACES_PER_CHUNK) {
return; return;
@ -183,8 +172,6 @@ void emitFace(ivec3 voxelPos, uint faceIndex, uint voxelType) {
Voxel voxel = voxelReg.materials[voxelType - 1u]; Voxel voxel = voxelReg.materials[voxelType - 1u];
faces[faceOffset + localFace] = packFace(uvec3(voxelPos), faceIndex, voxel); faces[faceOffset + localFace] = packFace(uvec3(voxelPos), faceIndex, voxel);
atomicAdd(drawCmds[indirectCommandIndex].vertexCount, VERTICES_PER_FACE);
} }
void main() { void main() {

View file

@ -2,19 +2,8 @@
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
struct DrawCommand { layout(std430, binding = 0) buffer Counters {
uint vertexCount; uint faceCount[];
uint instanceCount;
uint firstVertex;
uint firstInstance;
};
layout(std430, binding = 0) buffer DrawCommands {
DrawCommand drawCmds[];
};
layout(std430, binding = 1) buffer Counters {
uint faceCount;
} counters; } counters;
layout(push_constant) uniform ChunkInfo { layout(push_constant) uniform ChunkInfo {
@ -22,17 +11,11 @@ layout(push_constant) uniform ChunkInfo {
int slot; int slot;
uint faceOffset; uint faceOffset;
uint voxelTypeCount; uint voxelTypeCount;
uint indirectCommandIndex;
uint biomeCount; uint biomeCount;
uint structureCount; uint structureCount;
uint worldSeed; uint worldSeed;
}; };
void main() { void main() {
drawCmds[indirectCommandIndex].vertexCount = 0u; counters.faceCount[slot] = 0u;
drawCmds[indirectCommandIndex].instanceCount = 1u;
drawCmds[indirectCommandIndex].firstVertex = faceOffset * 6u;
drawCmds[indirectCommandIndex].firstInstance = 0u;
counters.faceCount = 0u;
} }

View file

@ -2,7 +2,9 @@
layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in; layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
const int CHUNK_SIZE = 16; const int CHUNK_SIZE = 16;
const int CHUNK_AREA = CHUNK_SIZE * CHUNK_SIZE; const int SCRATCH_SIZE = CHUNK_SIZE + 2;
const int SCRATCH_AREA = SCRATCH_SIZE * SCRATCH_SIZE;
const int SCRATCH_VOXEL_COUNT = SCRATCH_SIZE * SCRATCH_SIZE * SCRATCH_SIZE;
const int STRUCTURE_CELL_SIZE = 16; const int STRUCTURE_CELL_SIZE = 16;
const int REPLACE_AIR_ONLY = 1; const int REPLACE_AIR_ONLY = 1;
const int REPLACE_FOLIAGE = 2; const int REPLACE_FOLIAGE = 2;
@ -14,7 +16,6 @@ layout(push_constant) uniform ChunkInfo {
int slot; int slot;
uint faceOffset; uint faceOffset;
uint voxelTypeCount; uint voxelTypeCount;
uint indirectCommandIndex;
uint biomeCount; uint biomeCount;
uint structureCount; uint structureCount;
uint worldSeed; uint worldSeed;
@ -453,8 +454,9 @@ StructureVoxelResult getStructureVoxelAt(ivec3 worldPos, uint biome) {
ivec3 baseCell = getStructureCell3D(worldPos, spacing); ivec3 baseCell = getStructureCell3D(worldPos, spacing);
for (int ox = -1; ox <= 1; ox++) { for (int ox = -1; ox <= 1; ox++) {
for (int oy = -1; oy <= 1; oy++) {
for (int oz = -1; oz <= 1; oz++) { for (int oz = -1; oz <= 1; oz++) {
ivec3 cell = baseCell + ivec3(ox, 0, oz); ivec3 cell = baseCell + ivec3(ox, oy, oz);
uint seed = hash31(cell + ivec3(0, int(structureId) * 97, 0)) ^ worldSeed; uint seed = hash31(cell + ivec3(0, int(structureId) * 97, 0)) ^ worldSeed;
@ -464,8 +466,15 @@ StructureVoxelResult getStructureVoxelAt(ivec3 worldPos, uint biome) {
ivec2 anchorXZ = getStructureAnchorXZ(cell.xz, seed, s.spawnSpacing); ivec2 anchorXZ = getStructureAnchorXZ(cell.xz, seed, s.spawnSpacing);
// Match getStructureBlock's uncentered footprint before sampling terrain.
ivec2 localXZ = worldPos.xz - anchorXZ;
if (localXZ.x < 0 || localXZ.x >= int(s.sizeX) ||
localXZ.y < 0 || localXZ.y >= int(s.sizeZ)) {
continue;
}
int referenceY = cell.y * 75; int referenceY = cell.y * 75;
float anchorHeight = getSurfaceHeight(anchorXZ.x, anchorXZ.y, referenceY); float anchorHeight = getSurfaceHeight(int(anchorXZ.x - s.sizeX/2), int(anchorXZ.y - s.sizeZ/2), referenceY);
int anchorY = int(floor(anchorHeight)) + 1; int anchorY = int(floor(anchorHeight)) + 1;
ivec3 origin = ivec3(anchorXZ.x, anchorY, anchorXZ.y); ivec3 origin = ivec3(anchorXZ.x, anchorY, anchorXZ.y);
@ -486,15 +495,19 @@ StructureVoxelResult getStructureVoxelAt(ivec3 worldPos, uint biome) {
} }
} }
} }
}
return result; return result;
} }
void main() { void main() {
ivec3 localPos = ivec3(gl_GlobalInvocationID.xyz); if (any(greaterThanEqual(gl_GlobalInvocationID.xyz, uvec3(SCRATCH_SIZE)))) {
return;
}
ivec3 localPos = ivec3(gl_GlobalInvocationID.xyz) - ivec3(1);
ivec3 worldPos = chunkPos * CHUNK_SIZE + localPos; ivec3 worldPos = chunkPos * CHUNK_SIZE + localPos;
uint biome = GetBiome(vec3(worldPos.z, worldPos.y, worldPos.x) * 0.05, biomeCount); uint biome = GetBiome(vec3(worldPos.z, worldPos.y, worldPos.x) * 0.005, biomeCount);
Biome currentBiome = BiomeReg.biomes[biome]; Biome currentBiome = BiomeReg.biomes[biome];
float YZone = floor(worldPos.y/150.0)*150.0; float YZone = floor(worldPos.y/150.0)*150.0;
@ -577,6 +590,7 @@ void main() {
} }
} }
uint index = uint((localPos.x * CHUNK_AREA) + (localPos.y * CHUNK_SIZE) + localPos.z); uint index = uint(slot * SCRATCH_VOXEL_COUNT + (localPos.x + 1) * SCRATCH_AREA +
(localPos.y + 1) * SCRATCH_SIZE + localPos.z + 1);
voxels[index] = voxelType; voxels[index] = voxelType;
} }

View file

@ -0,0 +1,29 @@
#version 450
layout(location = 0) in vec2 inTextCoord;
layout(location = 0) out vec4 outBloomColour;
layout(set = 0, binding = 0) uniform sampler2D inputTexture;
layout(push_constant) uniform PassConfig {
int horizontal;
float GAMMA_CONST;
float Exposure;
float blur_radius;
vec2 bloomSize;
} config;
vec3 extractBloom(vec2 uv) {
vec3 color = max(texture(inputTexture, uv).rgb, vec3(0.0));
float brightness = dot(color, vec3(0.2126, 0.7152, 0.0722));
float contribution = max(brightness - 1.0, 0.0);
return color * (contribution / (contribution + 0.5));
}
void main() {
vec2 offset = 0.25 / config.bloomSize;
vec3 bloom = extractBloom(inTextCoord + vec2(-offset.x, -offset.y))
+ extractBloom(inTextCoord + vec2( offset.x, -offset.y))
+ extractBloom(inTextCoord + vec2(-offset.x, offset.y))
+ extractBloom(inTextCoord + vec2( offset.x, offset.y));
outBloomColour = vec4(bloom * 0.25, 1.0);
}

View file

@ -1,61 +1,30 @@
#version 450 #version 450
layout(location = 0) out vec4 FragColor; layout(location = 0) out vec4 FragColor;
layout(location = 0) in vec2 TexCoords; layout(location = 0) in vec2 TexCoords;
layout(set = 0, binding = 0) uniform sampler2D bloomImage;
layout(set = 0, binding = 0) uniform sampler2D image; layout(push_constant) uniform PassConfig {
layout(set = 0, binding = 1) uniform sampler2D bloomImage;
layout(set = 1 , binding = 0) uniform PassConfig{
int ApplyToFinalImage;
int horizontal; int horizontal;
float GAMMA_CONST; float GAMMA_CONST;
float Exposure; float Exposure;
float blur_radius; float blur_radius;
vec3 padding; vec2 bloomSize;
} config; } config;
const float weight[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
vec3 safeBloomSample(vec2 uv) {
const float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); return max(texture(bloomImage, uv).rgb, vec3(0.0));
vec3 safeBloomSample(vec2 uv)
{
vec3 value = texture(bloomImage, uv).rgb;
return clamp(value, vec3(0.0), vec3(8.0));
} }
void main() void main() {
{ vec2 tex_offset = config.blur_radius / vec2(textureSize(bloomImage, 0));
if(config.ApplyToFinalImage != 0){ vec2 direction = config.horizontal != 0 ? vec2(tex_offset.x, 0.0) : vec2(0.0, tex_offset.y);
vec3 bloomColour = safeBloomSample(TexCoords);
vec3 hdrColour = texture(image, TexCoords).rgb;
hdrColour += bloomColour;
vec3 result = vec3(1.0) - exp(-hdrColour * config.Exposure);
result = pow(result, vec3(1.0 / config.GAMMA_CONST));
FragColor = vec4(result, 1.0);
return;
}
float blurRadius = config.blur_radius;
vec2 tex_offset = blurRadius / vec2(textureSize(bloomImage, 0));
vec3 result = safeBloomSample(TexCoords) * weight[0]; vec3 result = safeBloomSample(TexCoords) * weight[0];
for (int i = 1; i < 5; i++) {
if(config.horizontal != 0) result += safeBloomSample(TexCoords + direction * i) * weight[i];
{ result += safeBloomSample(TexCoords - direction * i) * weight[i];
for(int i = 1; i < 5; ++i)
{
result += safeBloomSample(TexCoords + vec2(tex_offset.x * i, 0.0)) * weight[i];
result += safeBloomSample(TexCoords - vec2(tex_offset.x * i, 0.0)) * weight[i];
} }
}
else
{
for(int i = 1; i < 5; ++i)
{
result += safeBloomSample(TexCoords + vec2(0.0, tex_offset.y * i)) * weight[i];
result += safeBloomSample(TexCoords - vec2(0.0, tex_offset.y * i)) * weight[i];
}
}
FragColor = vec4(result, 1.0); FragColor = vec4(result, 1.0);
} }

View file

@ -68,9 +68,12 @@ float chebyshevUpperBound(vec2 moments, float t) {
return p_max; return p_max;
} }
float calcVisibility(vec4 worldPosition, uint cascadeIndex, float ShadowBias, vec2 texelSize) { float calcVisibility(vec4 worldPosition, uint cascadeIndex, vec2 texelSize) {
vec4 shadowMapPosition = shadows.cascadeshadows[cascadeIndex].projViewMatrix * worldPosition; vec4 shadowMapPosition = shadows.cascadeshadows[cascadeIndex].projViewMatrix * worldPosition;
if (!(shadowMapPosition.w > 0.0) || any(isnan(shadowMapPosition)) || any(isinf(shadowMapPosition))) {
return 1.0;
}
shadowMapPosition.xyz /= shadowMapPosition.w; shadowMapPosition.xyz /= shadowMapPosition.w;
vec2 uv = vec2( vec2 uv = vec2(
@ -82,17 +85,27 @@ float calcVisibility(vec4 worldPosition, uint cascadeIndex, float ShadowBias, ve
if (uv.x < 0.0 || uv.x > 1.0 || if (uv.x < 0.0 || uv.x > 1.0 ||
uv.y < 0.0 || uv.y > 1.0 || uv.y < 0.0 || uv.y > 1.0 ||
depth < ShadowBias || depth > 1.0) { depth < 0.0 || depth > 1.0) {
return 1.0; return 1.0;
} }
float shadow = 0.0; float shadow = 0.0;
// vec2 moments = texture(shadowSampler, vec3((uv), cascadeIndex)).rg;
// float visibility = chebyshevUpperBound(moments, depth);
// shadow += visibility;
for(int x = -1; x <= 1; ++x) for(int x = -1; x <= 1; ++x)
{ {
for(int y = -1; y <= 1; ++y) for(int y = -1; y <= 1; ++y)
{ {
vec2 moments = texture(shadowSampler, vec3((uv + vec2(x, y) * texelSize), cascadeIndex)).rg; vec2 sampleUV = uv + vec2(x, y) * texelSize;
if (any(lessThan(sampleUV, vec2(0.0))) || any(greaterThan(sampleUV, vec2(1.0)))) {
shadow += 1.0;
continue;
}
sampleUV = clamp(sampleUV, texelSize * 0.5, vec2(1.0) - texelSize * 0.5);
vec2 moments = texture(shadowSampler, vec3(sampleUV, cascadeIndex)).rg;
float visibility = chebyshevUpperBound(moments, depth); float visibility = chebyshevUpperBound(moments, depth);
shadow += visibility; shadow += visibility;
} }
@ -212,8 +225,6 @@ void main() {
// outFragColor = vec4(emissive,1); // outFragColor = vec4(emissive,1);
// return; // return;
float ShadowBias = 0.05f;
float opacityf = Opacity.x + Opacity.y + Opacity.z; float opacityf = Opacity.x + Opacity.y + Opacity.z;
opacityf = opacityf/3.0; opacityf = opacityf/3.0;
@ -228,17 +239,20 @@ void main() {
vec3 F0 = vec3(0.04); vec3 F0 = vec3(0.04);
F0 = mix(F0, albedo, metallic); F0 = mix(F0, albedo, metallic);
uint cascadeIndex = 0; uint cascadeIndex = SHADOW_MAP_CASCADE_COUNT;
vec4 viewPos = sceneInfo.viewMatrix * worldPosW; vec4 viewPos = sceneInfo.viewMatrix * vec4(worldPos, 1.0);
float lastSplit = shadows.cascadeshadows[SHADOW_MAP_CASCADE_COUNT - 1].splitDistance.x;
float shadow = 1.0;
if (lastSplit < 0.0 && viewPos.z < 0.0 && viewPos.z >= lastSplit) {
cascadeIndex = 0;
for (uint i = 0; i < SHADOW_MAP_CASCADE_COUNT - 1; ++i) { for (uint i = 0; i < SHADOW_MAP_CASCADE_COUNT - 1; ++i) {
if (viewPos.z < shadows.cascadeshadows[i].splitDistance.x) { if (viewPos.z < shadows.cascadeshadows[i].splitDistance.x) {
cascadeIndex = i + 1; cascadeIndex = i + 1;
} }
} }
vec2 texelSizeShadow = 1.0 / textureSize(shadowSampler, 0).rg; vec2 texelSizeShadow = 1.0 / textureSize(shadowSampler, 0).rg;
shadow = calcVisibility(vec4(worldPos, 1), cascadeIndex, texelSizeShadow);
float shadow = calcVisibility(vec4(worldPos, 1), cascadeIndex,ShadowBias, texelSizeShadow); }
vec3 Lo = vec3(0.0); vec3 Lo = vec3(0.0);
for (uint i = 0; i < sceneInfo.numLights; i++) { for (uint i = 0; i < sceneInfo.numLights; i++) {
@ -254,7 +268,7 @@ void main() {
outFragColor = vec4(Lo + ambient, 1.0f); outFragColor = vec4(Lo + ambient, 1.0f);
outFragColor = vec4(outFragColor.xyz/2 + (outFragColor.xyz/2) * vec3(ssao,ssao,ssao),1); outFragColor = vec4(outFragColor.xyz/2 + (outFragColor.xyz/2) * vec3(ssao,ssao,ssao),1);
if (DEBUG_SHADOWS == 1) { if (DEBUG_SHADOWS == 1 && cascadeIndex < SHADOW_MAP_CASCADE_COUNT) {
switch (cascadeIndex) { switch (cascadeIndex) {
case 0: case 0:
outFragColor.rgb *= vec3(1.0f, 0.25f, 0.25f); outFragColor.rgb *= vec3(1.0f, 0.25f, 0.25f);

View file

@ -0,0 +1,34 @@
#version 450
layout(location = 0) in vec2 inTextCoord;
layout(location = 0) out vec4 outBloomColour;
layout(set = 0, binding = 0) uniform sampler2DMS inputTexture;
layout(push_constant) uniform PassConfig {
int horizontal;
float GAMMA_CONST;
float Exposure;
float blur_radius;
vec2 bloomSize;
} config;
vec3 extractBloom(vec2 uv) {
ivec2 size = textureSize(inputTexture);
ivec2 pixel = clamp(ivec2(uv * vec2(size)), ivec2(0), size - 1);
int samples = textureSamples(inputTexture);
vec3 color = vec3(0.0);
for (int i = 0; i < samples; i++) color += texelFetch(inputTexture, pixel, i).rgb;
color = max(color / float(samples), vec3(0.0));
float brightness = dot(color, vec3(0.2126, 0.7152, 0.0722));
float contribution = max(brightness - 1.0, 0.0);
return color * (contribution / (contribution + 0.5));
}
void main() {
vec2 offset = 0.25 / config.bloomSize;
vec3 bloom = extractBloom(inTextCoord + vec2(-offset.x, -offset.y))
+ extractBloom(inTextCoord + vec2( offset.x, -offset.y))
+ extractBloom(inTextCoord + vec2(-offset.x, offset.y))
+ extractBloom(inTextCoord + vec2( offset.x, offset.y));
outBloomColour = vec4(bloom * 0.25, 1.0);
}

View file

@ -1,72 +1,42 @@
#version 450 #version 450
layout(constant_id = 0) const int USE_AA = 0;
const float GAMMA_CONST = 0.4545;
const float SPAN_MAX = 8.0;
const float REDUCE_MIN = 1.0/128.0;
const float REDUCE_MUL = 1.0/32.0;
layout(location = 0) in vec2 inTextCoord; layout(location = 0) in vec2 inTextCoord;
layout(location = 0) out vec4 outFragColor; layout(location = 0) out vec4 outFragColor;
layout(location = 1) out vec4 outBloomColour;
layout(set = 0, binding = 0) uniform sampler2DMS inputTexture; layout(set = 0, binding = 0) uniform sampler2DMS inputTexture;
layout(set = 0, binding = 1) uniform sampler2D bloomImage;
layout(set = 1, binding = 0) uniform ScreenSize{ layout(push_constant) uniform PassConfig {
vec2 size; int horizontal;
} screenSize; float GAMMA_CONST;
float Exposure;
float blur_radius;
vec2 bloomSize;
} config;
vec4 gamma(vec4 color){ vec4 msaa(int sampleCount, sampler2DMS textureIn, vec2 TextCoord) {
return color = vec4(pow(color.rgb,vec3(GAMMA_CONST)),color.a); ivec2 size = textureSize(textureIn);
} ivec2 pixelCoords = clamp(ivec2(TextCoord * vec2(size)), ivec2(0), size - 1);
vec3 HDR(float Gamma, float Exposure, sampler2D Texture, vec2 TexCoords){
vec3 hdrColor = texture(Texture, TexCoords).rgb;
vec3 mapped = vec3(1.0) - exp(-hdrColor * Exposure);
mapped = pow(mapped, vec3(1.0 / Gamma));
return mapped;
}
vec4 msaa(int sampleCount, sampler2DMS textureIn,vec2 TextCoord){
ivec2 pixelCoords = ivec2(TextCoord * textureSize(textureIn));
vec4 colorSum = vec4(0.0); vec4 colorSum = vec4(0.0);
for (int i = 0; i < sampleCount; i++) colorSum += texelFetch(textureIn, pixelCoords, i);
for(int i = 0; i < sampleCount; ++i) {
vec4 sampleColor = texelFetch(textureIn, pixelCoords, i);
colorSum += sampleColor;
}
return colorSum / float(sampleCount); return colorSum / float(sampleCount);
} }
void main(){ vec3 bloomAt(ivec2 pixel, ivec2 size) {
ivec2 pixelCoords = ivec2(inTextCoord * textureSize(inputTexture)); return texelFetch(bloomImage, clamp(pixel, ivec2(0), size - 1), 0).rgb;
}
outFragColor = texelFetch(inputTexture, pixelCoords, 0);
vec3 upsampleBloom(vec2 uv) {
if(USE_AA == 0){ ivec2 size = textureSize(bloomImage, 0);
outFragColor = texelFetch(inputTexture,pixelCoords,0); vec2 pixel = uv * vec2(size) - 0.5;
} ivec2 base = ivec2(floor(pixel));
if(USE_AA == 1){ vec2 blend = fract(pixel);
outFragColor = texelFetch(inputTexture,pixelCoords,0); return mix(mix(bloomAt(base, size), bloomAt(base + ivec2(1, 0), size), blend.x),
} mix(bloomAt(base + ivec2(0, 1), size), bloomAt(base + ivec2(1, 1), size), blend.x), blend.y);
if(USE_AA == 2){ }
outFragColor = msaa(2,inputTexture,inTextCoord);
} void main() {
if(USE_AA == 3){ vec4 sceneColor = msaa(textureSamples(inputTexture), inputTexture, inTextCoord);
outFragColor = msaa(4,inputTexture,inTextCoord); vec3 hdrColour = max(sceneColor.rgb + upsampleBloom(inTextCoord), vec3(0.0));
} vec3 result = vec3(1.0) - exp(-hdrColour * config.Exposure);
if(USE_AA == 4){ outFragColor = vec4(pow(result, vec3(1.0 / config.GAMMA_CONST)), sceneColor.a);
outFragColor = msaa(8,inputTexture,inTextCoord);
}
vec3 color = outFragColor.rgb;
float brightness = dot(color, vec3(0.2126, 0.7152, 0.0722));
float threshold = 1.0;
float softKnee = 0.5;
float contribution = max(brightness - threshold, 0.0);
contribution = contribution / max(contribution + softKnee, 0.0001);
outBloomColour = vec4(color * contribution, 1.0);
} }

View file

@ -111,6 +111,11 @@ void main() {
vec3 localPos = vec3(voxelX, voxelY, voxelZ) + FACE_CORNERS[faceId][cornerIndex]; vec3 localPos = vec3(voxelX, voxelY, voxelZ) + FACE_CORNERS[faceId][cornerIndex];
vec3 Offset = vec3(0,0,0); vec3 Offset = vec3(0,0,0);
if (gl_InstanceIndex != 0) {
uint encoded = uint(gl_InstanceIndex);
Offset = vec3(int(encoded & 1023u) - 512, int((encoded >> 10u) & 1023u) - 512,
int((encoded >> 20u) & 1023u) - 512) * 16.0;
}
vec3 worldPos = localPos + Offset + push_constants.ModelOffset.xyz * 16.0; vec3 worldPos = localPos + Offset + push_constants.ModelOffset.xyz * 16.0;
vec4 worldPosVec4 = vec4(worldPos, 1.0); vec4 worldPosVec4 = vec4(worldPos, 1.0);

View file

@ -2,36 +2,27 @@
layout(constant_id = 0) const int USE_AA = 0; layout(constant_id = 0) const int USE_AA = 0;
const float GAMMA_CONST = 0.4545;
const float Exposure = 2.0;
const float SPAN_MAX = 8.0; const float SPAN_MAX = 8.0;
const float REDUCE_MIN = 1.0/128.0; const float REDUCE_MIN = 1.0/128.0;
const float REDUCE_MUL = 1.0/32.0; const float REDUCE_MUL = 1.0/32.0;
layout(location = 0) in vec2 inTextCoord; layout(location = 0) in vec2 inTextCoord;
layout(location = 0) out vec4 outFragColor; layout(location = 0) out vec4 outFragColor;
layout(location = 1) out vec4 outBloomColour;
layout(set = 0, binding = 0) uniform sampler2D inputTexture; layout(set = 0, binding = 0) uniform sampler2D inputTexture;
layout(set = 0, binding = 1) uniform sampler2D bloomImage;
layout(set = 1, binding = 0) uniform ScreenSize{ layout(push_constant) uniform PassConfig {
vec2 size; int horizontal;
} screenSize; float GAMMA_CONST;
float Exposure;
vec4 gamma(vec4 color){ float blur_radius;
return color = vec4(pow(color.rgb,vec3(GAMMA_CONST)),color.a); vec2 bloomSize;
} } config;
vec3 HDR(float Gamma, float Exposure, sampler2D Texture, vec2 TexCoords){
vec3 hdrColor = texture(Texture, TexCoords).rgb;
vec3 mapped = vec3(1.0) - exp(-hdrColor * Exposure);
mapped = pow(mapped, vec3(1.0 / Gamma));
return mapped;
}
// Sourced from: https://mini.gmshaders.com/p/gm-shaders-mini-fxaa // Sourced from: https://mini.gmshaders.com/p/gm-shaders-mini-fxaa
vec4 fxaa(sampler2D tex, vec2 uv) { vec4 fxaa(sampler2D tex, vec2 uv) {
vec2 u_texel = 1.0 / screenSize.size; vec2 u_texel = 1.0 / vec2(textureSize(inputTexture, 0));
//Sample center and 4 corners //Sample center and 4 corners
vec3 rgbCC = texture(tex, uv).rgb; vec3 rgbCC = texture(tex, uv).rgb;
@ -79,36 +70,22 @@ vec4 fxaa(sampler2D tex, vec2 uv) {
return ((lumaB < lumaMin) || (lumaB > lumaMax)) ? A : B; return ((lumaB < lumaMin) || (lumaB > lumaMax)) ? A : B;
} }
void main(){ vec3 bloomAt(ivec2 pixel, ivec2 size) {
return texelFetch(bloomImage, clamp(pixel, ivec2(0), size - 1), 0).rgb;
if(USE_AA == 1){ }
outFragColor = fxaa(inputTexture, inTextCoord);
} vec3 upsampleBloom(vec2 uv) {
else if(USE_AA == 2){ ivec2 size = textureSize(bloomImage, 0);
vec2 pixel = uv * vec2(size) - 0.5;
outFragColor = texture(inputTexture, inTextCoord); ivec2 base = ivec2(floor(pixel));
} vec2 blend = fract(pixel);
else if(USE_AA == 3){ return mix(mix(bloomAt(base, size), bloomAt(base + ivec2(1, 0), size), blend.x),
mix(bloomAt(base + ivec2(0, 1), size), bloomAt(base + ivec2(1, 1), size), blend.x), blend.y);
outFragColor = texture(inputTexture, inTextCoord); }
}
else if(USE_AA == 4){ void main(){
vec4 sceneColor = USE_AA == 1 ? fxaa(inputTexture, inTextCoord) : texture(inputTexture, inTextCoord);
outFragColor = texture(inputTexture, inTextCoord); vec3 hdrColour = max(sceneColor.rgb + upsampleBloom(inTextCoord), vec3(0.0));
} vec3 result = vec3(1.0) - exp(-hdrColour * config.Exposure);
else { outFragColor = vec4(pow(result, vec3(1.0 / config.GAMMA_CONST)), sceneColor.a);
outFragColor = texture(inputTexture, inTextCoord);
}
vec3 color = outFragColor.rgb;
float brightness = dot(color, vec3(0.2126, 0.7152, 0.0722));
float threshold = 1.0;
float softKnee = 0.5;
float contribution = max(brightness - threshold, 0.0);
contribution = contribution / max(contribution + softKnee, 0.0001);
outBloomColour = vec4(color * contribution, 1.0);
} }

View file

@ -10,9 +10,7 @@ layout(set = 0, binding = 3) uniform sampler2D pbrSampler;
layout(set = 1, binding = 0) uniform CameraProperties { layout(set = 1, binding = 0) uniform CameraProperties {
mat4 projection; mat4 projection;
mat4 invProjection;
mat4 view; mat4 view;
mat4 invView;
float ssrStepSize; float ssrStepSize;
float ssrMaxDistance; float ssrMaxDistance;
int maxSteps; int maxSteps;
@ -30,13 +28,12 @@ float edgeFade(vec2 uv) {
} }
void main() { void main() {
vec4 sceneColor = texture(albedoSampler, fragTexCoord); outColor = vec4(0.0);
vec4 posSample = texture(worldPosSampler, fragTexCoord); vec4 posSample = texture(worldPosSampler, fragTexCoord);
vec4 normalSample = texture(normalSampler, fragTexCoord); vec4 normalSample = texture(normalSampler, fragTexCoord);
vec4 pbrSample = texture(pbrSampler, fragTexCoord); vec4 pbrSample = texture(pbrSampler, fragTexCoord);
if (posSample.a == 0.0 || length(posSample.xyz) == 0.0 || length(normalSample.xyz) < 0.001) { if (posSample.a == 0.0 || length(posSample.xyz) == 0.0 || length(normalSample.xyz) < 0.001) {
outColor = sceneColor;
return; return;
} }
@ -54,11 +51,10 @@ void main() {
float materialReflection = reflectiveness + (refractiveness - 1.05); float materialReflection = reflectiveness + (refractiveness - 1.05);
materialReflection *= mix(0.75, 1.0, metallic); materialReflection *= mix(0.75, 1.0, metallic);
materialReflection *= smoothstep(0.7, 0.05, roughness); materialReflection *= 1.0 - smoothstep(0.05, 0.7, roughness);
materialReflection *= mix(0.45, 1.0, fresnel); materialReflection *= mix(0.45, 1.0, fresnel);
if (materialReflection <= MIN_REFLECTION) { if (materialReflection <= MIN_REFLECTION) {
outColor = sceneColor;
return; return;
} }
@ -66,7 +62,6 @@ void main() {
vec3 reflectDir = normalize(reflect(viewDir, worldNormal)); vec3 reflectDir = normalize(reflect(viewDir, worldNormal));
if (dot(reflectDir, worldNormal) <= 0.0) { if (dot(reflectDir, worldNormal) <= 0.0) {
outColor = sceneColor;
return; return;
} }
@ -115,7 +110,6 @@ void main() {
} }
if (!hitFound) { if (!hitFound) {
outColor = sceneColor;
return; return;
} }
@ -124,5 +118,5 @@ void main() {
float fade = edgeFade(hitUV); float fade = edgeFade(hitUV);
float weight = clamp(materialReflection * fade, 0.0, 0.85); float weight = clamp(materialReflection * fade, 0.0, 0.85);
outColor = vec4(mix(sceneColor.rgb, reflectedColor, weight), sceneColor.a); outColor = vec4(reflectedColor * weight, weight);
} }

View file

@ -65,7 +65,7 @@ void main()
} else { } else {
albedo = material.diffuseColor; albedo = material.diffuseColor;
} }
if (albedo.a < 0.5 || Translucency > 0.5 || Opacity < 0.5) { if (albedo.a < 0.5 || Translucency > 0.95 || Opacity < 0.5) {
discard; discard;
} }
@ -77,5 +77,5 @@ void main()
float dy = dFdy(depth); float dy = dFdy(depth);
moment2 += 0.25 * (dx * dx + dy * dy); moment2 += 0.25 * (dx * dx + dy * dy);
outFragColor = vec2(moment1, moment2); outFragColor = vec2(moment1, moment2) * (1 - Translucency);
} }

View file

@ -80,59 +80,11 @@ const vec3 FACE_CORNERS[8][4] = vec3[8][4](
vec3[4](vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 1.0), vec3(1.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0)) // Face 7 (cross model pane 2) vec3[4](vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 1.0), vec3(1.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0)) // Face 7 (cross model pane 2)
); );
vec3 buildFaceCornerPosition(uvec3 voxelPos, uint faceId, uint cornerIndex) {
vec3 p = vec3(voxelPos);
if (faceId == 0u) {
if (cornerIndex == 0u) return p + vec3(0.0, 1.0, 0.0);
if (cornerIndex == 1u) return p + vec3(0.0, 1.0, 1.0);
if (cornerIndex == 2u) return p + vec3(1.0, 1.0, 1.0);
return p + vec3(1.0, 1.0, 0.0);
}
if (faceId == 1u) {
if (cornerIndex == 0u) return p + vec3(0.0, 0.0, 0.0);
if (cornerIndex == 1u) return p + vec3(1.0, 0.0, 0.0);
if (cornerIndex == 2u) return p + vec3(1.0, 0.0, 1.0);
return p + vec3(0.0, 0.0, 1.0);
}
if (faceId == 2u) {
if (cornerIndex == 0u) return p + vec3(1.0, 0.0, 0.0);
if (cornerIndex == 1u) return p + vec3(1.0, 1.0, 0.0);
if (cornerIndex == 2u) return p + vec3(1.0, 1.0, 1.0);
return p + vec3(1.0, 0.0, 1.0);
}
if (faceId == 3u) {
if (cornerIndex == 0u) return p + vec3(0.0, 0.0, 0.0);
if (cornerIndex == 1u) return p + vec3(0.0, 0.0, 1.0);
if (cornerIndex == 2u) return p + vec3(0.0, 1.0, 1.0);
return p + vec3(0.0, 1.0, 0.0);
}
if (faceId == 4u) {
if (cornerIndex == 0u) return p + vec3(0.0, 0.0, 1.0);
if (cornerIndex == 1u) return p + vec3(1.0, 0.0, 1.0);
if (cornerIndex == 2u) return p + vec3(1.0, 1.0, 1.0);
return p + vec3(0.0, 1.0, 1.0);
}
if (cornerIndex == 0u) return p + vec3(0.0, 0.0, 0.0);
if (cornerIndex == 1u) return p + vec3(0.0, 1.0, 0.0);
if (cornerIndex == 2u) return p + vec3(1.0, 1.0, 0.0);
return p + vec3(1.0, 0.0, 0.0);
}
void main() { void main() {
uint faceRecordIndex = uint(gl_VertexIndex) / 6u; uint faceRecordIndex = uint(gl_VertexIndex) / 6u;
uint triangleVertex = (uint(gl_VertexIndex))% 6u; uint triangleVertex = (uint(gl_VertexIndex))% 6u;
uint cornerIndex = TRI_TO_CORNER[triangleVertex]; uint cornerIndex = TRI_TO_CORNER[triangleVertex];
//uint faceRecordIndex = uint(gl_VertexIndex) / 6u;
//uint triangleVertex = uint(gl_VertexIndex) % 6u;
//uint cornerIndex = TRI_TO_CORNER[triangleVertex];
uint packedFace = faces[faceRecordIndex]; uint packedFace = faces[faceRecordIndex];
uint voxelX = packedFace & 0xFu; uint voxelX = packedFace & 0xFu;
@ -143,14 +95,6 @@ void main() {
uint texIdxX = (packedFace >> 26u) & 0x7u; uint texIdxX = (packedFace >> 26u) & 0x7u;
uint texIdxY = (packedFace >> 29u) & 0x7u; uint texIdxY = (packedFace >> 29u) & 0x7u;
// (voxelPos.x & 0xFu) |
// ((voxelPos.y & 0xFu) << 4u) |
// ((voxelPos.z & 0xFu) << 8u) |
// ((faceIndex & 0x7u) << 12u) |
// ((materialId & 0x7FFu) << 15u) |
// ((textureIndexX & 0x7u) << 26u) |
// ((textureIndexY & 0x7u) << 29u);
vec2 texID = vec2((texIdxX)/4.0,(texIdxY)/4.0); vec2 texID = vec2((texIdxX)/4.0,(texIdxY)/4.0);
vec2 scalar = vec2(1.0/4.0,1.0/4.0); vec2 scalar = vec2(1.0/4.0,1.0/4.0);
@ -158,6 +102,11 @@ void main() {
vec3 localPos = vec3(voxelX, voxelY, voxelZ) + FACE_CORNERS[faceId][cornerIndex]; vec3 localPos = vec3(voxelX, voxelY, voxelZ) + FACE_CORNERS[faceId][cornerIndex];
vec3 worldPos = localPos + push_constants.ModelOffset.xyz * 16.0; vec3 worldPos = localPos + push_constants.ModelOffset.xyz * 16.0;
if (gl_InstanceIndex != 0) {
uint encoded = uint(gl_InstanceIndex);
worldPos += vec3(int(encoded & 1023u) - 512, int((encoded >> 10u) & 1023u) - 512,
int((encoded >> 20u) & 1023u) - 512) * 16.0;
}
outTextCoord = CORNER_UVS_GRASS_BLOCK[faceId][cornerIndex] * scalar + texID; outTextCoord = CORNER_UVS_GRASS_BLOCK[faceId][cornerIndex] * scalar + texID;
outMaterialIdx = matId; outMaterialIdx = matId;

View file

@ -7,7 +7,7 @@ layout(set = 0, binding = 0) uniform sampler2D ssaoSampler;
layout(set = 0, binding = 1) uniform sampler2D viewPosSampler; layout(set = 0, binding = 1) uniform sampler2D viewPosSampler;
void main() { void main() {
vec2 texelSize = 2.0 / vec2(textureSize(ssaoSampler, 0)); vec2 texelSize = 1.0 / vec2(textureSize(ssaoSampler, 0));
float centerAO = texture(ssaoSampler, inTextCoord).r; float centerAO = texture(ssaoSampler, inTextCoord).r;
float centerDepth = texture(viewPosSampler, inTextCoord).z; float centerDepth = texture(viewPosSampler, inTextCoord).z;
@ -15,7 +15,7 @@ void main() {
float totalAO = centerAO; float totalAO = centerAO;
float totalWeight = 1.0; float totalWeight = 1.0;
float weights[4] = float[](0.227027, 0.1216216, 0.054054, 0.016216); float weights[4] = float[](0.1216216, 0.1945946, 0.1945946, 0.1216216);
int offsets[4] = int[](-2, -1, 1, 2); int offsets[4] = int[](-2, -1, 1, 2);
const float Sharpness = 20.0; const float Sharpness = 20.0;

View file

@ -2,7 +2,6 @@
layout(location = 0) in vec2 inTextCoord; layout(location = 0) in vec2 inTextCoord;
layout(location = 0) out float outAO; layout(location = 0) out float outAO;
layout(location = 1) out vec4 viewPos;
layout(set = 0, binding = 0) uniform sampler2D posSampler; layout(set = 0, binding = 0) uniform sampler2D posSampler;
layout(set = 0, binding = 1) uniform sampler2D normalSampler; layout(set = 0, binding = 1) uniform sampler2D normalSampler;
@ -19,65 +18,52 @@ layout(set = 2, binding = 0) uniform SSAOInfo {
float radius; //140 float radius; //140
float bias; //144 float bias; //144
int kernelSize; //148 int kernelSize; //148
float ResolutionScale; //152;
vec2 Padding; //160;
} ssao; } ssao;
void main() { void main() {
int KERNEL_SIZE = clamp(ssao.kernelSize, 1, 64);
vec2 ScreenSize = ssao.screenSize * ssao.ResolutionScale;
int KERNEL_SIZE = ssao.kernelSize;
vec3 fragPos = texture(posSampler, inTextCoord).xyz; vec3 fragPos = texture(posSampler, inTextCoord).xyz;
vec3 worldNorm = normalize(texture(normalSampler, inTextCoord).xyz); vec3 worldNorm = texture(normalSampler, inTextCoord).xyz;
if (dot(worldNorm, worldNorm) < 0.001) { if (dot(worldNorm, worldNorm) < 0.001 || fragPos.z >= 0.0) {
outAO = 1.0; outAO = 1.0;
return; return;
} }
vec3 normal = normalize(mat3(ssao.view) * worldNorm); vec3 normal = normalize(mat3(ssao.view) * worldNorm);
viewPos = vec4(fragPos, 1); vec3 randomVec = texelFetch(noiseSampler, ivec2(gl_FragCoord.xy) % 4, 0).xyz;
vec3 tangent = randomVec - normal * dot(randomVec, normal);
vec2 noiseScale = ScreenSize / 4.0; if (dot(tangent, tangent) < 0.0001) {
vec3 randomVec = texture(noiseSampler, inTextCoord * noiseScale).xyz; tangent = cross(normal, abs(normal.z) < 0.9 ? vec3(0, 0, 1) : vec3(0, 1, 0));
vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal)); }
tangent = normalize(tangent);
vec3 bitangent = cross(normal, tangent); vec3 bitangent = cross(normal, tangent);
mat3 TBN = mat3(tangent, bitangent, normal); mat3 TBN = mat3(tangent, bitangent, normal);
float fragDepth = -fragPos.z; float fragDepth = -fragPos.z;
float minOptimalDistance = 15;
float maxKernelSize = float(ssao.kernelSize);
// Calculate a dynamic loop limit based on proximity
// If closer than 1.5 units, smoothly scale down the loop count
int dynamicKernelSize = int(KERNEL_SIZE);
if (fragDepth < minOptimalDistance) {
float proximityFactor = clamp(fragDepth / minOptimalDistance, 0.2, 1.0);
dynamicKernelSize = int(maxKernelSize * proximityFactor);
}
// Force a hard minimum baseline of samples so SSAO doesn't completely disappear
dynamicKernelSize = max(dynamicKernelSize, 16);
float occlusion = 0.0; float occlusion = 0.0;
float Passes = 0.0f; float Passes = 0.0f;
for (int i = 0; i < dynamicKernelSize; i++) { for (int i = 0; i < KERNEL_SIZE; i++) {
vec3 samplePos = fragPos + (TBN * kernel.samples[i].xyz) * ssao.radius; // Cover the complete radius distribution, even with a smaller sample budget.
int sampleIndex = i * 64 / KERNEL_SIZE;
vec3 samplePos = fragPos + (TBN * kernel.samples[sampleIndex].xyz) * ssao.radius;
vec4 offset = ssao.projection * vec4(samplePos, 1.0); vec4 offset = ssao.projection * vec4(samplePos, 1.0);
if (offset.w <= 0.0) continue;
offset.xyz /= offset.w; offset.xyz /= offset.w;
offset.xyz = offset.xyz * 0.5 + 0.5; offset.xyz = offset.xyz * 0.5 + 0.5;
vec2 sampleUV = clamp(vec2(offset.x, 1.0 - offset.y), 0.0, 1.0); vec2 sampleUV = vec2(offset.x, 1.0 - offset.y);
if (any(lessThan(sampleUV, vec2(0))) || any(greaterThan(sampleUV, vec2(1)))) continue;
vec3 sampleViewPos = texture(posSampler, sampleUV).xyz; vec3 sampleViewPos = texture(posSampler, sampleUV).xyz;
if (sampleViewPos.z >= 0.0) continue;
float sampleDepth = -sampleViewPos.z; float sampleDepth = -sampleViewPos.z;
float depthDelta = abs(fragDepth - sampleDepth); float depthDelta = abs(fragDepth - sampleDepth);
if (depthDelta > ssao.radius * 2.0) { if (depthDelta > ssao.radius * 2.0) {
continue; continue;
} }
float rangeCheck = smoothstep(0.0, 1.0, ssao.radius / depthDelta); float rangeCheck = smoothstep(0.0, 1.0, ssao.radius / max(depthDelta, 0.0001));
float isOccluded = step(samplePos.z + ssao.bias, sampleViewPos.z); float isOccluded = step(samplePos.z + ssao.bias, sampleViewPos.z);
occlusion += isOccluded * rangeCheck; occlusion += isOccluded * rangeCheck;
Passes += 1.0; Passes += 1.0;

View file

@ -0,0 +1,34 @@
#version 450
layout(location = 0) in vec2 fragTexCoord;
layout(location = 0) out vec4 outColor;
layout(set = 0, binding = 0) uniform sampler2D sceneSampler;
layout(set = 0, binding = 1) uniform sampler2D reflectionSampler;
layout(set = 0, binding = 2) uniform sampler2D pbrSampler;
layout(set = 0, binding = 3) uniform sampler2D normalSampler;
vec4 reflectionAt(ivec2 pixel, ivec2 size) {
return texelFetch(reflectionSampler, clamp(pixel, ivec2(0), size - 1), 0);
}
void main() {
vec4 sceneColor = texture(sceneSampler, fragTexCoord);
vec4 pbr = texture(pbrSampler, fragTexCoord);
vec4 normal = texture(normalSampler, fragTexCoord);
float materialReflection = (pbr.a + normal.a - 1.05) * (1.0 - smoothstep(0.05, 0.7, pbr.g));
if (dot(normal.xyz, normal.xyz) < 0.001 || materialReflection <= 0.01) {
outColor = sceneColor;
return;
}
// The shared sampler is nearest-filtered. Upsample only premultiplied reflections.
ivec2 size = textureSize(reflectionSampler, 0);
vec2 pixel = fragTexCoord * vec2(size) - 0.5;
ivec2 base = ivec2(floor(pixel));
vec2 blend = fract(pixel);
vec4 reflection = mix(
mix(reflectionAt(base, size), reflectionAt(base + ivec2(1, 0), size), blend.x),
mix(reflectionAt(base + ivec2(0, 1), size), reflectionAt(base + ivec2(1, 1), size), blend.x), blend.y);
outColor = vec4(sceneColor.rgb * (1.0 - reflection.a) + reflection.rgb, sceneColor.a);
}

View file

@ -0,0 +1 @@
[]

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

View file

@ -90,11 +90,24 @@ public class EngineConfig {
public float SoundVolume = 0.0f; public float SoundVolume = 0.0f;
public float WeaponVolume = 0.0f; public float WeaponVolume = 0.0f;
public float AmbientVolume = 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 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; public boolean RenderShadows = true;
@ -305,6 +318,7 @@ public class EngineConfig {
} }
} }
if(SuccessfulLoad) { if(SuccessfulLoad) {
renderQuality = RenderQualitySettings.fromProperties(EngineConfigVar);
IconName = (EngineConfigVar.getOrDefault("Software_Icon", "ProgramIcon.png").toString()); IconName = (EngineConfigVar.getOrDefault("Software_Icon", "ProgramIcon.png").toString());
LoadingScreenLocation = (EngineConfigVar.getOrDefault("LoadingScreenLocation", LoadingScreenLocation).toString()); LoadingScreenLocation = (EngineConfigVar.getOrDefault("LoadingScreenLocation", LoadingScreenLocation).toString());
IconPath = (EngineConfigVar.getOrDefault("Software_Icon_Path", "/WindowResources/Icon/").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())); FOV = (float)(DegToRad * Float.parseFloat(EngineConfigVar.getOrDefault("field_of_view", 60.0f).toString()));
zNearPlane = (Float.parseFloat(EngineConfigVar.getOrDefault("z_near_plane", 1.0f).toString())); zNearPlane = (Float.parseFloat(EngineConfigVar.getOrDefault("z_near_plane", 1.0f).toString()));
zFarPlane = (Float.parseFloat(EngineConfigVar.getOrDefault("z_far_plane", 100.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())); AAValue = (Integer.parseInt(EngineConfigVar.getOrDefault("anti_alias_mode", 1).toString()));
renderer = Integer.parseInt(EngineConfigVar.getOrDefault("Renderer", 1).toString()) == 0 ? Renderer.Forward: Renderer.Deferred; renderer = Integer.parseInt(EngineConfigVar.getOrDefault("Renderer", 1).toString()) == 0 ? Renderer.Forward: Renderer.Deferred;
ShadowMapSize = Integer.parseInt(EngineConfigVar.getOrDefault("ShadowMapSize", 2048).toString()); 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"); // ModelData treeModel = ModelLoader.LoadModel("resources/models/Forest/forest.json");
// models.add(treeModel); // models.add(treeModel);
// Actor3D treeEntity = new Actor3D("treeEntity", treeModel.ID(), new Vector3f(0.0f, 00.0f, 0.0f)); // 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); // scene.AddActor(treeEntity);
// materials.addAll(ModelLoader.LoadMaterials("resources/models/Forest/forest_mat.json")); // materials.addAll(ModelLoader.LoadMaterials("resources/models/Forest/forest_mat.json"));
MelonaData = ModelLoader.LoadModel("resources/models/cube/Cube.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().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().GetAmbientLightColour().set(0.3f, 0.35f, 0.5f);
// scene.GetLightingManager().SetAmbientLightIntensity(0.015f); // scene.GetLightingManager().SetAmbientLightIntensity(0.015f);
List<ILight> lights = new ArrayList<>(); 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(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 = 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); SkyLight.SetType(Light.LightType.Directional);
lights.add(SkyLight); 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(-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))); // 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(); Project3D projection = engineInstance.scene().GetProjection();
Vector3f CamRot = cam.GetRotation(); 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)); 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) { if (!PrimaryRuntime.IsServer && !RenderThread.Headless) {
VoxelWorldManager.UnloadFarChunks(ChunkPos, 16,cam.GetViewMatrix(),projection.GetProjectionMatrix()); int radius = EngineConfig.getInstance().GetVoxelChunkRadius();
VoxelWorldManager.GenerateChunks(ChunkPos, new Vector3i(16,16,16),cam.GetViewMatrix(),projection.GetProjectionMatrix()); if (!ChunkPos.equals(VoxelWorldManager.LastPosition)) {
} else { VoxelWorldManager.UnloadFarChunks(ChunkPos, radius, cam.GetViewMatrix(), projection.GetProjectionMatrix());
VoxelWorldManager.UnloadFarChunks(ChunkPos, 96,cam.GetViewMatrix(),projection.GetProjectionMatrix()); }
VoxelWorldManager.GenerateChunks(ChunkPos, new Vector3i(48,16,48),cam.GetViewMatrix(),projection.GetProjectionMatrix()); VoxelWorldManager.GenerateChunks(ChunkPos, new Vector3i(radius), cam.GetViewMatrix(), projection.GetProjectionMatrix());
} }
LastCamRotation.set(CamRot); LastCamRotation.set(CamRot);
if(PrimaryRuntime.IsServer){ if(PrimaryRuntime.IsServer){
@ -753,6 +753,16 @@ public class GameCore implements GameLogic {
DeviceInfo[4] = String.format(" VRAM: " + GPUProfiler.GetVramCapacity()); DeviceInfo[4] = String.format(" VRAM: " + GPUProfiler.GetVramCapacity());
Metrics.add(ISceneDetails); Metrics.add(ISceneDetails);
Metrics.add(DeviceInfo); 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(Metrics);
SettingPerformanceMetrics.addAll(EngineConfig.getInstance().GetMemoryProfiling()); SettingPerformanceMetrics.addAll(EngineConfig.getInstance().GetMemoryProfiling());
SettingPerformanceMetrics.addAll(EngineConfig.getInstance().GetSystemProfiling()); 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); verticesBuffer.put(0, vertices);
glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW);
glEnableVertexAttribArray(0); glEnableVertexAttribArray(0);
glVertexAttribPointer( glVertexAttribPointer(0, 3, GL_FLOAT, false, 14 * Float.BYTES, 0L);
0,
3,
GL_FLOAT,
false,
14 * Float.BYTES,
0L
);
glEnableVertexAttribArray(1); glEnableVertexAttribArray(1);
glVertexAttribPointer( glVertexAttribPointer(1, 3, GL_FLOAT, false, 14 * Float.BYTES, 3L * Float.BYTES);
1,
3,
GL_FLOAT,
false,
14 * Float.BYTES,
3L * Float.BYTES
);
glEnableVertexAttribArray(2); glEnableVertexAttribArray(2);
glVertexAttribPointer( glVertexAttribPointer(2, 3, GL_FLOAT, false, 14 * Float.BYTES, 6L * Float.BYTES);
2,
3,
GL_FLOAT,
false,
14 * Float.BYTES,
6L * Float.BYTES
);
glEnableVertexAttribArray(3); glEnableVertexAttribArray(3);
glVertexAttribPointer( glVertexAttribPointer(3, 3, GL_FLOAT, false, 14 * Float.BYTES, 9L * Float.BYTES);
3,
3,
GL_FLOAT,
false,
14 * Float.BYTES,
9L * Float.BYTES
);
glEnableVertexAttribArray(4); glEnableVertexAttribArray(4);
glVertexAttribPointer( glVertexAttribPointer(4, 2, GL_FLOAT, false, 14 * Float.BYTES, 12L * Float.BYTES);
4,
2,
GL_FLOAT,
false,
14 * Float.BYTES,
12L * Float.BYTES
);
vboId = glGenBuffers(); vboId = glGenBuffers();
VBO_ID_List.add(vboId); VBO_ID_List.add(vboId);
@ -94,23 +54,19 @@ public class GLMesh {
indicesBuffer.put(0, indices); indicesBuffer.put(0, indices);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0); glBindVertexArray(0);
MemoryUtil.memFree(verticesBuffer); MemoryUtil.memFree(verticesBuffer);
MemoryUtil.memFree(indicesBuffer); MemoryUtil.memFree(indicesBuffer);
} }
public void CleanUp() { public void CleanUp() {
VBO_ID_List.forEach(GL30::glDeleteBuffers); VBO_ID_List.forEach(GL30::glDeleteBuffers);
glDeleteVertexArrays(VAO_ID); glDeleteVertexArrays(VAO_ID);
} }
public int GetVertexCount() { public int GetVertexCount() {
return VertexCount; return VertexCount;
} }
public final int GetVaoID() { public final int GetVaoID() {
return VAO_ID; 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.Rendering.Shader.*;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.Device; 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.VulkanBuffer;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.shaderc.Shaderc; import org.lwjgl.util.shaderc.Shaderc;
@ -15,17 +16,21 @@ import java.nio.ByteBuffer;
import java.util.List; 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_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_DEVICE;
import static org.lwjgl.util.vma.Vma.VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
import static org.lwjgl.vulkan.VK13.*; import static org.lwjgl.vulkan.VK13.*;
public class SharedVoxelTerrainResources { public class SharedVoxelTerrainResources {
public static final int CHUNK_SIZE = 16; public static final int CHUNK_SIZE = 16;
public static final int VOXEL_COUNT = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE; 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 DRAW_INDIRECT_COMMAND_SIZE = 4 * Integer.BYTES;
public static final int COUNTER_BUFFER_SIZE = 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_VOXEL_TYPES = 256;
public static final int MAX_BIOME_TYPES = 256; public static final int MAX_BIOME_TYPES = 256;
@ -46,8 +51,11 @@ public class SharedVoxelTerrainResources {
private final VoxelMeshPool meshPool; private final VoxelMeshPool meshPool;
private final VulkanBuffer voxelData; // Each frame has disjoint, fixed-size job slices; no dispatch shares voxel/counter storage.
private final VulkanBuffer counters; 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 voxelRegistryBuffer;
private final VulkanBuffer biomeRegistryBuffer; private final VulkanBuffer biomeRegistryBuffer;
private final VulkanBuffer structureRegistryBuffer; private final VulkanBuffer structureRegistryBuffer;
@ -76,11 +84,22 @@ public class SharedVoxelTerrainResources {
public SharedVoxelTerrainResources(VulkanContext VkCtx, int maxResidentChunks) { public SharedVoxelTerrainResources(VulkanContext VkCtx, int maxResidentChunks) {
meshPool = new VoxelMeshPool(VkCtx, maxResidentChunks); meshPool = new VoxelMeshPool(VkCtx, maxResidentChunks);
voxelData = new VulkanBuffer(VkCtx, (long) VOXEL_COUNT * Integer.BYTES, 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); 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,
counters = new VulkanBuffer(VkCtx, COUNTER_BUFFER_SIZE, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0); 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, voxelRegistryBuffer = new VulkanBuffer(VkCtx, (long) MAX_VOXEL_TYPES * VOXEL_REG_DATA_SIZE,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 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[]{ 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, 0, 1, VK_SHADER_STAGE_COMPUTE_BIT)
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, 1, VK_SHADER_STAGE_COMPUTE_BIT)
}); });
meshGenerationLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{ 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, 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, 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, 2, 1, VK_SHADER_STAGE_COMPUTE_BIT)
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3, 1, VK_SHADER_STAGE_COMPUTE_BIT)
}); });
faceGraphicsLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{ faceGraphicsLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{
@ -165,18 +182,16 @@ public class SharedVoxelTerrainResources {
DescriptorAllocator allocator = VkCtx.GetDescriptorAllocator(); DescriptorAllocator allocator = VkCtx.GetDescriptorAllocator();
Device device = VkCtx.GetDevice(); Device device = VkCtx.GetDevice();
DescriptorSet resetSet = allocator.AddDescriptorSet(device, DESC_ID_RESET, resetLayout); DescriptorSet[] resetSets = allocator.AddDescriptorSets(device, DESC_ID_RESET, VulkanUtils.MAX_IN_FLIGHT, resetLayout);
resetSet.SetBuffer(device, meshPool.indirectPool(), meshPool.indirectPool().GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); DescriptorSet[] voxelSets = allocator.AddDescriptorSets(device, DESC_ID_VOXEL_GENERATION, VulkanUtils.MAX_IN_FLIGHT, voxelGenerationLayout);
resetSet.SetBuffer(device, counters, counters.GetRequestedSize(), 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); DescriptorSet[] meshSets = allocator.AddDescriptorSets(device, DESC_ID_MESH, VulkanUtils.MAX_IN_FLIGHT, meshGenerationLayout);
for (int frame = 0; frame < VulkanUtils.MAX_IN_FLIGHT; frame++) {
DescriptorSet voxelSet = allocator.AddDescriptorSet(device, DESC_ID_VOXEL_GENERATION, voxelGenerationLayout); resetSets[frame].SetBuffer(device, counters[frame], counters[frame].GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
voxelSet.SetBuffer(device, voxelData, voxelData.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);
DescriptorSet meshSet = allocator.AddDescriptorSet(device, DESC_ID_MESH, meshGenerationLayout); meshSets[frame].SetBuffer(device, scratchFaces[frame], scratchFaces[frame].GetRequestedSize(), 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
meshSet.SetBuffer(device, voxelData, voxelData.GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); meshSets[frame].SetBuffer(device, counters[frame], counters[frame].GetRequestedSize(), 2, 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 faceGraphicsSet = allocator.AddDescriptorSet(device, DESC_ID_FACE_GRAPHICS, faceGraphicsLayout); DescriptorSet faceGraphicsSet = allocator.AddDescriptorSet(device, DESC_ID_FACE_GRAPHICS, faceGraphicsLayout);
faceGraphicsSet.SetBuffer(device, meshPool.facePool(), meshPool.facePool().GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); faceGraphicsSet.SetBuffer(device, meshPool.facePool(), meshPool.facePool().GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
@ -311,12 +326,20 @@ public class SharedVoxelTerrainResources {
return meshPool; return meshPool;
} }
public VulkanBuffer voxelData() { public VulkanBuffer voxelData(int currentFrame) {
return voxelData; return voxelData[currentFrame];
} }
public VulkanBuffer counters() { public VulkanBuffer counters(int currentFrame) {
return counters; return counters[currentFrame];
}
public VulkanBuffer scratchFaces(int currentFrame) {
return scratchFaces[currentFrame];
}
public VulkanBuffer countReadback(int currentFrame) {
return countReadback[currentFrame];
} }
public Pipeline voxelGenerationPipeline() { public Pipeline voxelGenerationPipeline() {
@ -363,11 +386,18 @@ public class SharedVoxelTerrainResources {
faceGraphicsLayout.CleanUp(VkCtx); faceGraphicsLayout.CleanUp(VkCtx);
voxelDeclarationLayout.CleanUp(VkCtx); voxelDeclarationLayout.CleanUp(VkCtx);
biomeDeclarationLayout.CleanUp(VkCtx); biomeDeclarationLayout.CleanUp(VkCtx);
structureDeclarationLayout.CleanUp(VkCtx);
voxelData.cleanup(VkCtx); for (int frame = 0; frame < VulkanUtils.MAX_IN_FLIGHT; frame++) {
counters.cleanup(VkCtx); voxelData[frame].cleanup(VkCtx);
counters[frame].cleanup(VkCtx);
scratchFaces[frame].cleanup(VkCtx);
countReadback[frame].cleanup(VkCtx);
}
voxelRegistryBuffer.cleanup(VkCtx); voxelRegistryBuffer.cleanup(VkCtx);
biomeRegistryBuffer.cleanup(VkCtx); biomeRegistryBuffer.cleanup(VkCtx);
structureRegistryBuffer.cleanup(VkCtx);
structureVoxelDataBuffer.cleanup(VkCtx);
meshPool.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.Rendering.Shader.DescriptorAllocator;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.CommandBuffer; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.CommandBuffer;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils;
import org.joml.Vector3i;
import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.vulkan.VkBufferCopy;
import org.lwjgl.vulkan.VkCommandBuffer; import org.lwjgl.vulkan.VkCommandBuffer;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.LongBuffer; import java.nio.LongBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static org.lwjgl.vulkan.VK13.*; import static org.lwjgl.vulkan.VK13.*;
@ -16,83 +23,164 @@ public class VoxelChunkGenerator {
public static final int CHUNK_SIZE = 16; public static final int CHUNK_SIZE = 16;
public static final int LOCAL_SIZE = 4; public static final int LOCAL_SIZE = 4;
public static final int DISPATCH_SIZE = CHUNK_SIZE / LOCAL_SIZE; 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 SharedVoxelTerrainResources resources;
private final GenerationBatch[] pending = new GenerationBatch[VulkanUtils.MAX_IN_FLIGHT];
public VoxelChunkGenerator(SharedVoxelTerrainResources resources) { public VoxelChunkGenerator(SharedVoxelTerrainResources resources) {
this.resources = resources; this.resources = resources;
for (int frame = 0; frame < pending.length; frame++) {
pending[frame] = new GenerationBatch();
}
}
/**
* 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;
} }
public void recordGenerateChunk(VulkanContext VkCtx,CommandBuffer commandBuffer, RenderChunk chunk) {
try (MemoryStack stack = MemoryStack.stackPush()) { try (MemoryStack stack = MemoryStack.stackPush()) {
VkCommandBuffer cmd = commandBuffer.GetVulkanCommandBuffer(); VkCommandBuffer cmd = commandBuffer.GetVulkanCommandBuffer();
DescriptorAllocator allocator = VkCtx.GetDescriptorAllocator(); DescriptorAllocator allocator = VkCtx.GetDescriptorAllocator();
ByteBuffer pushConstants = stack.malloc(SharedVoxelTerrainResources.TERRAIN_GENERATION_PUSH_CONSTANT_SIZE); 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(20, VoxelWorldManager.GetVoxelTypeCount());
pushConstants.putInt(24, indirectCommandIndex); pushConstants.putInt(24, VoxelWorldManager.GetBiomeTypeCount());
pushConstants.putInt(28, VoxelWorldManager.GetBiomeTypeCount()); pushConstants.putInt(28, VoxelWorldManager.GetStructureTypeCount());
pushConstants.putInt(32, VoxelWorldManager.GetStructureTypeCount()); pushConstants.putInt(32, VoxelWorldManager.GetWorldSeed());
pushConstants.putInt(36, VoxelWorldManager.GetWorldSeed());
LongBuffer resetDescriptorSet = stack.longs(allocator.GetDescriptorSet( LongBuffer resetDescriptorSet = stack.longs(allocator.GetDescriptorSet(
resources.resetDescriptorId()).GetVkDescriptorSet()); resources.resetDescriptorId(), currentFrame).GetVkDescriptorSet());
LongBuffer voxelDescriptorSet = stack.longs( LongBuffer voxelDescriptorSet = stack.longs(
allocator.GetDescriptorSet(resources.voxelGenerationDescriptorId()).GetVkDescriptorSet(), allocator.GetDescriptorSet(resources.voxelGenerationDescriptorId(), currentFrame).GetVkDescriptorSet(),
allocator.GetDescriptorSet(resources.biomeRegDescriptorID()).GetVkDescriptorSet(), allocator.GetDescriptorSet(resources.biomeRegDescriptorID()).GetVkDescriptorSet(),
allocator.GetDescriptorSet(resources.structureRegDescriptorID()).GetVkDescriptorSet() allocator.GetDescriptorSet(resources.structureRegDescriptorID()).GetVkDescriptorSet());
);
LongBuffer meshDescriptorSet = stack.longs( LongBuffer meshDescriptorSet = stack.longs(
allocator.GetDescriptorSet(resources.meshDescriptorId()).GetVkDescriptorSet(), allocator.GetDescriptorSet(resources.meshDescriptorId(), currentFrame).GetVkDescriptorSet(),
allocator.GetDescriptorSet(resources.voxelRegDescriptorID()).GetVkDescriptorSet()); allocator.GetDescriptorSet(resources.voxelRegDescriptorID()).GetVkDescriptorSet());
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.resetPipeline().GetVulkanPipeline()); // Includes the parent's scratch-to-pool transfers earlier in this command buffer.
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);
VulkanUtils.BufferBarriers(stack, cmd, new long[]{ VulkanUtils.BufferBarriers(stack, cmd, new long[]{
resources.meshPool().indirectPoolHandle(), resources.scratchFaces(currentFrame).GetBuffer(),
resources.counters().GetBuffer() resources.voxelData(currentFrame).GetBuffer(),
}, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, 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_PIPELINE_STAGE_2_COMPUTE_SHADER_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 VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT);
);
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.voxelGenerationPipeline().GetVulkanPipeline()); long resetLayout = resources.resetPipeline().GetVulkanPipelineLayout();
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.voxelGenerationPipeline().GetVulkanPipelineLayout(),0, voxelDescriptorSet,null ); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resources.resetPipeline().GetVulkanPipeline());
vkCmdPushConstants( cmd, resources.voxelGenerationPipeline().GetVulkanPipelineLayout(),VK_SHADER_STAGE_COMPUTE_BIT,0, pushConstants ); vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resetLayout, 0, resetDescriptorSet, null);
vkCmdDispatch(cmd, DISPATCH_SIZE, DISPATCH_SIZE, DISPATCH_SIZE); 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_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_SHADER_READ_BIT);
vkCmdBindPipeline(cmd,VK_PIPELINE_BIND_POINT_COMPUTE,resources.meshGenerationPipeline().GetVulkanPipeline() ); long meshLayout = resources.meshGenerationPipeline().GetVulkanPipelineLayout();
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resources.meshGenerationPipeline().GetVulkanPipeline());
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.meshGenerationPipeline().GetVulkanPipelineLayout(),0, meshDescriptorSet,null ); vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, meshLayout, 0, meshDescriptorSet, null);
for (int job = 0; job < batch.positions.size(); job++) {
vkCmdPushConstants( cmd, resources.meshGenerationPipeline().GetVulkanPipelineLayout(),VK_SHADER_STAGE_COMPUTE_BIT,0, pushConstants ); 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); vkCmdDispatch(cmd, DISPATCH_SIZE, DISPATCH_SIZE, DISPATCH_SIZE);
}
VulkanUtils.BufferBarriers(stack, cmd, new long[]{ VulkanUtils.BufferBarriers(stack, cmd, new long[]{
resources.voxelData().GetBuffer(), resources.counters().GetBuffer(), resources.scratchFaces(currentFrame).GetBuffer(), resources.counters(currentFrame).GetBuffer()
resources.meshPool().facePoolHandle(), resources.meshPool().indirectPoolHandle() }, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, 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_TRANSFER_READ_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 | VkBufferCopy.Buffer copy = VkBufferCopy.calloc(1, stack)
VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT); .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; 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.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanBuffer; 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.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector3i; 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.ArrayDeque;
import java.util.Queue; 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.*; import static org.lwjgl.vulkan.VK13.*;
public class VoxelMeshPool{ public class VoxelMeshPool {
public static final int CHUNK_SIZE = 16; 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 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 FACE_RECORD_SIZE_BYTES = Integer.BYTES;
public static final int DRAW_INDIRECT_COMMAND_SIZE = 4 * Integer.BYTES; public static final int DRAW_INDIRECT_COMMAND_SIZE = 4 * Integer.BYTES;
private final int maxChunks; private final int maxChunks;
private final long faceSlotSizeBytes;
private final long indirectSlotSizeBytes;
private final VulkanBuffer facePool; 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<>(); private final Queue<Integer> freeSlots = new ArrayDeque<>();
public VoxelMeshPool(VulkanContext VkCtx, int maxChunks) { public VoxelMeshPool(VulkanContext VkCtx, int maxChunks) {
this.maxChunks = maxChunks; this.maxChunks = maxChunks;
long requestedBytes = (long) EngineConfig.getInstance().GetVoxelFacePoolMiB() * 1024 * 1024;
this.faceSlotSizeBytes = (long) MAX_VISIBLE_FACES_PER_CHUNK * FACE_RECORD_SIZE_BYTES; long facePoolSize;
this.indirectSlotSizeBytes = DRAW_INDIRECT_COMMAND_SIZE; try (var stack = MemoryStack.stackPush()) {
var properties = VkPhysicalDeviceProperties.calloc(stack);
long facePoolSize = faceSlotSizeBytes * maxChunks; vkGetPhysicalDeviceProperties(VkCtx.GetPhysicalDevice().GetPhysicalDevice(), properties);
long indirectPoolSize = indirectSlotSizeBytes * maxChunks; facePoolSize = Math.min(requestedBytes, Integer.toUnsignedLong(properties.limits().maxStorageBufferRange()));
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); facePoolSize -= facePoolSize % FACE_RECORD_SIZE_BYTES;
ranges = new FaceRangeAllocator(Math.toIntExact(facePoolSize / FACE_RECORD_SIZE_BYTES));
facePool = new VulkanBuffer(VkCtx, facePoolSize,
indirectPool = new VulkanBuffer(VkCtx,indirectPoolSize,VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,0,0); VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0);
for (int frame = 0; frame < drawBuffers.length; frame++) {
for (int i = 0; i < maxChunks; i++) { for (int pass = 0; pass < 2; pass++) {
freeSlots.add(i); 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);
public RenderChunk allocate(Vector3i position) { Logger.info("Voxel face pool: {} MiB, {} resident chunk IDs", facePoolSize / (1024 * 1024), maxChunks);
Integer slot = freeSlots.poll();
if (slot == null) {
return null;
} }
return new RenderChunk(
new Vector3i(position), public RenderChunk allocate(Vector3i position, int faceCount) {
slot, if (faceCount <= 0 || faceCount > MAX_VISIBLE_FACES_PER_CHUNK) {
faceOffsetBytes(slot), throw new IllegalArgumentException("Invalid generated face count: " + faceCount);
MAX_VISIBLE_FACES_PER_CHUNK, }
indirectCommandOffsetBytes(slot), if (freeSlots.isEmpty()) return null;
new Matrix4f().identity().translate( int faceOffset = ranges.allocate(faceCount);
new Vector3f( if (faceOffset < 0) return null;
position.x * CHUNK_SIZE, int slot = freeSlots.remove();
position.y * CHUNK_SIZE, return new RenderChunk(new Vector3i(position), slot, (long) faceOffset * FACE_RECORD_SIZE_BYTES,
position.z * CHUNK_SIZE 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) { public void free(RenderChunk chunk) {
ranges.free(Math.toIntExact(chunk.faceOffsetBytes() / FACE_RECORD_SIZE_BYTES));
freeSlots.add(chunk.slot()); freeSlots.add(chunk.slot());
} }
public long faceOffsetBytes(int slot) { public VulkanBuffer facePool() {return facePool;}
return faceSlotSizeBytes * slot; 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 long indirectCommandOffsetBytes(int slot) { public int freeSlotCount() {return freeSlots.size();}
return indirectSlotSizeBytes * slot; public long usedFaceBytes() {return facePool.GetRequestedSize() - (long) ranges.freeFaces() * FACE_RECORD_SIZE_BYTES;}
}
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 void cleanup(VulkanContext VkCtx) { public void cleanup(VulkanContext VkCtx) {
facePool.cleanup(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; 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.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.Pipeline; 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.Rendering.VkModel.MaterialsCache;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.CommandBuffer; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.CommandBuffer;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils;
import org.joml.*; 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.lwjgl.vulkan.VkCommandBuffer;
import org.tinylog.Logger; import org.tinylog.Logger;
@ -21,13 +25,23 @@ import static org.lwjgl.vulkan.VK13.*;
public class VoxelWorldManager { public class VoxelWorldManager {
public static final int CHUNK_SIZE = 16; public static final int CHUNK_SIZE = 16;
private static final int MAX_RESIDENT_CHUNKS = 8192 * 3; private static int MAX_CHUNK_GENERATIONS_PER_FRAME = EngineConfig.getInstance().GetVoxelGenerationsPerFrame();
private static final int MAX_CHUNK_GENERATIONS_PER_FRAME = 128;
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 ConcurrentLinkedQueue<Vector3i> RequestedChunks = new ConcurrentLinkedQueue<>();
private static final ConcurrentHashMap<Vector3i, RenderChunk> RenderChunks = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<Vector3i, RenderChunk> RenderChunks = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Vector3i, Boolean> KnownChunks = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<Vector3i, Boolean> KnownChunks = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Vector3i, VoxelChunkVisibility> CulledChunks = 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 List<Voxel> VoxelRegistry = new CopyOnWriteArrayList<>();
private static final ConcurrentHashMap<String, Integer> VoxelNameToIndex = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, Integer> VoxelNameToIndex = new ConcurrentHashMap<>();
@ -188,8 +202,10 @@ public class VoxelWorldManager {
return; 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); Generator = new VoxelChunkGenerator(Resources);
RenderContext = VkCtx;
GraphicsPushConstants = org.lwjgl.system.MemoryUtil.memAlloc( GraphicsPushConstants = org.lwjgl.system.MemoryUtil.memAlloc(
VulkanUtils.MATRIX4X4_SIZE + 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); Vector3i key = new Vector3i(Position);
if (KnownChunks.putIfAbsent(key, true) == null) RequestedChunks.add(key);
if (KnownChunks.containsKey(key)) {
return;
}
RequestedChunks.add(key);
KnownChunks.put(key, true);
} }
public static int GenerationOffset = 0; public static int GenerationOffset = 0;
public static Vector3i LastPosition = new Vector3i(0, 0, 0); public static Vector3i LastPosition = new Vector3i(0, 0, 0);
static volatile int IterationX = 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() 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) { public static synchronized void GenerateChunks(Vector3i Position, Vector3i Radius, Matrix4f cameraView,Matrix4f projectionMatrix) {
if (!Position.equals(LastPosition)) IterationX = 0;
projectionMatrix.mul(cameraView, viewProjectionMatrix); int radius = Math.max(Radius.x, Math.max(Radius.y, Radius.z));
frustumIntersection.set(viewProjectionMatrix); if (IterationX > radius) return;
int shell = IterationX++;
int ChunkX = Position.x; for (int x = -Math.min(shell, Radius.x); x <= Math.min(shell, Radius.x); x++) {
int ChunkY = Position.y; for (int y = -Math.min(shell, Radius.y); y <= Math.min(shell, Radius.y); y++) {
int ChunkY2 = Position.y; for (int z = -Math.min(shell, Radius.z); z <= Math.min(shell, Radius.z); z++) {
int ChunkZ = Position.z; if (Math.max(Math.abs(x), Math.max(Math.abs(y), Math.abs(z))) != shell) continue;
for (int Ry = 0; Ry <= Radius.y; Ry++) GenerateChunk(new Vector3i(Position.x + x, Position.y + y, Position.z + z));
{
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));
} }
} }
} }
}
}
IterationX++;
if (IterationX > Radius.x || Position.x != LastPosition.x || Position.y != LastPosition.y || Position.z != LastPosition.z)
{
IterationX = 0;
}
LastPosition.set(Position); 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) { if (Resources == null) {
Init(VkCtx); 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) { if (RegistryDirty) {
RegistryDirty = false; RegistryDirty = false;
@ -293,40 +313,18 @@ public class VoxelWorldManager {
} }
int generatedThisFrame = 0; List<Vector3i> batch = new ArrayList<>(MAX_CHUNK_GENERATIONS_PER_FRAME);
while (!PoolFull && batch.size() < MAX_CHUNK_GENERATIONS_PER_FRAME) {
while (!RequestedChunks.isEmpty() && generatedThisFrame < MAX_CHUNK_GENERATIONS_PER_FRAME) {
Vector3i position = RequestedChunks.poll(); Vector3i position = RequestedChunks.poll();
if (position == null) break;
if (position == null) { if (!VoxelDrawData.withinRadius(position, ResidentCenter, ResidentRadius)) {
break; KnownChunks.remove(position);
continue;
} }
batch.add(position);
// 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++;
} }
GeneratedLastFrame = batch.size();
Generator.recordGenerateChunks(VkCtx, commandBuffer, batch, currentFrame);
} }
public static SharedVoxelTerrainResources Resources() { public static SharedVoxelTerrainResources Resources() {
@ -334,23 +332,56 @@ public class VoxelWorldManager {
} }
public static void RecordRender(VkCommandBuffer CommandBuffer, Pipeline graphicsPipeline, MaterialsCache materialsCache, boolean shadowPass) { public static void RecordRender(VkCommandBuffer CommandBuffer, Pipeline graphicsPipeline, MaterialsCache materialsCache, boolean shadowPass) {
if (Resources == null) { RecordRender(CommandBuffer, graphicsPipeline, materialsCache, shadowPass, new Matrix4f[0]);
return;
} }
int terrainMaterialIndex; public static synchronized void RecordRender(VkCommandBuffer CommandBuffer, Pipeline graphicsPipeline,
MaterialsCache materialsCache, boolean shadowPass, Matrix4f[] clipMatrices) {
if (materialsCache.GetPosition("VoxelTerrain") < 0) { if (Resources == null) return;
terrainMaterialIndex = 0; 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);
}
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 { } 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,
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); 1, VoxelMeshPool.DRAW_INDIRECT_COMMAND_SIZE);
}); }
}
} }
private static void SetPushConstants(VkCommandBuffer CmdHandle, Vector4f ModelMatrix, Pipeline VkPipeline, int MaterialIndex, boolean shadowPass){ 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) { public static synchronized void UnloadFarChunks(Vector3i centerChunk, int unloadRadius,Matrix4f cameraView,Matrix4f projectionMatrix) {
int maxDistSq = unloadRadius * unloadRadius; ResidentCenter.set(centerChunk);
projectionMatrix.mul(cameraView, viewProjectionMatrix); ResidentRadius = unloadRadius;
frustumIntersection.set(viewProjectionMatrix);
RenderChunks.entrySet().removeIf(entry -> { RenderChunks.entrySet().removeIf(entry -> {
Vector3i pos = entry.getKey(); Vector3i pos = entry.getKey();
min.set(pos.x * CHUNK_SIZE, pos.y * CHUNK_SIZE, pos.z * CHUNK_SIZE); if (!VoxelDrawData.withinRadius(pos, centerChunk, unloadRadius)) {
max.set(min.x + CHUNK_SIZE, min.y + CHUNK_SIZE, min.z + CHUNK_SIZE); RetiredChunks.add(entry.getValue());
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);
KnownChunks.remove(pos); KnownChunks.remove(pos);
return true; 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; return false;
}); });
CulledChunks.entrySet().removeIf(entry -> { CulledChunks.entrySet().removeIf(entry -> {
Vector3i pos = entry.getKey(); Vector3i pos = entry.getKey();
int dx = pos.x - centerChunk.x; if (!VoxelDrawData.withinRadius(pos, centerChunk, unloadRadius)) {
int dy = pos.y - centerChunk.y;
int dz = pos.z - centerChunk.z;
int distSq = dx * dx + dy * dy + dz * dz;
if (distSq > maxDistSq) {
KnownChunks.remove(pos); KnownChunks.remove(pos);
return true; return true;
} }
@ -416,13 +421,7 @@ public class VoxelWorldManager {
}); });
RequestedChunks.removeIf(pos -> { RequestedChunks.removeIf(pos -> {
int dx = pos.x - centerChunk.x; if (!VoxelDrawData.withinRadius(pos, centerChunk, unloadRadius)) {
int dy = pos.y - centerChunk.y;
int dz = pos.z - centerChunk.z;
int distSq = dx * dx + dy * dy + dz * dz;
if (distSq > maxDistSq) {
KnownChunks.remove(pos); KnownChunks.remove(pos);
return true; return true;
} }
@ -434,6 +433,9 @@ public class VoxelWorldManager {
public static int GetRenderableChunkCount() { public static int GetRenderableChunkCount() {
return RenderChunks.size(); 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() { public static int GetCulledChunkCount() {
return CulledChunks.size(); 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) { if (Resources != null) {
Resources.cleanup(VkCtx); Resources.cleanup(VkCtx);
Resources = null; Resources = null;
@ -466,5 +468,12 @@ public class VoxelWorldManager {
RequestedChunks.clear(); RequestedChunks.clear();
RenderChunks.clear(); RenderChunks.clear();
KnownChunks.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; int pushRangeCount = pushConstantsRanges != null ? pushConstantsRanges.length : 0;
if (pushRangeCount > 0) { if (pushRangeCount > 0) {
pushRanges = VkPushConstantRange.calloc(pushRangeCount, stack); pushRanges = VkPushConstantRange.calloc(pushRangeCount, stack);
for (int i = 0; i < pushRangeCount; i++) { for (int i = 0; i < pushRangeCount; i++) {
PushConstantsRange range = pushConstantsRanges[i]; PushConstantsRange range = pushConstantsRanges[i];
pushRanges.get(i) pushRanges.get(i)
.stageFlags(range.Stage()) .stageFlags(range.Stage())
.offset(range.Offset()) .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); 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(); Matrix4f skyboxViewMatrix = new Matrix4f().identity();
skyboxViewMatrix.set(engineInstance.scene().GetCamera().GetViewMatrix()); 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 DEPTH_FORMAT = VK_FORMAT_D32_SFLOAT;
public static final int REFLECT_REFRACT = VK_FORMAT_R16G16_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 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 EMISSIVE_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
public static final int TRANSLUCECNY_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; public static final int TRANSLUCECNY_FORMAT = VK_FORMAT_R8G8B8A8_UNORM;
public static final int OPACITY_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; public static final int OPACITY_FORMAT = VK_FORMAT_R8G8B8A8_UNORM;
public static final int POSITION_FORMAT = VK_FORMAT_R32G32B32A32_SFLOAT; 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 VIEW_POS_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
public static final int SSAO_RAW_ATTACHMENT = VK_FORMAT_R8_UNORM; 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); 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(); Matrix4f skyboxViewMatrix = new Matrix4f().identity();

View file

@ -40,7 +40,7 @@ import static org.lwjgl.vulkan.VK10.*;
import static org.lwjgl.vulkan.VK13.*; import static org.lwjgl.vulkan.VK13.*;
public class LightRenderer { 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_ATT = "LIGHT_DESC_ID_ATT";
private static final String DESC_ID_NOSHADOW_ATT = "LIGHT_DESC_ID_NOSHADOW_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"; 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 vtxBuffStruct = new EmptyVertexBufferStruct();
var buildInfo = new PipelineBuildInfo(shaderModules, vtxBuffStruct.GetVertexInput(),new int[]{COLOUR_FORMAT}) var buildInfo = new PipelineBuildInfo(shaderModules, vtxBuffStruct.GetVertexInput(),new int[]{COLOUR_FORMAT})
.SetDescriptorSetLayouts(descSetLayouts) .SetDescriptorSetLayouts(descSetLayouts)
.BlendingIsUsed(true) .BlendingIsUsed(false)
.SetBlendingMethod(1) .SetBlendingMethod(1)
.SetDepthWrite(false) .SetDepthWrite(false)
.SetDepthTest(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.Images.Image;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.Pipeline; 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.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.Rendering.Shader.*;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.CommandBuffer; 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.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 net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils;
import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.shaderc.Shaderc; import org.lwjgl.util.shaderc.Shaderc;
import org.lwjgl.vulkan.*; import org.lwjgl.vulkan.*;
import org.tinylog.Logger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.LongBuffer;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -33,125 +26,97 @@ import static org.lwjgl.vulkan.VK13.*;
public class PostProcess { public class PostProcess {
public static final int COLOUR_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; 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_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_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_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_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_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_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_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_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 AttachmentDescriptorSetLayout;
private final DescriptorSetLayout[] MultiAttachmentDescriptorSetLayout = new DescriptorSetLayout[2]; private final DescriptorSetLayout MultiAttachmentDescriptorSetLayout;
private final VkClearValue ClearValueColour; private final VkClearValue ClearValueColour;
private final DescriptorSetLayout FragmentUniformDescriptorSetLayout;
private final Pipeline pipeline; private final Pipeline pipeline;
private final Pipeline bloomPipeline; private final Pipeline bloomPipeline;
private final VulkanBuffer ScreenSizeBuffer; private final Pipeline extractionPipeline;
private final VulkanBuffer BloomPropertiesHorizontalBuffer;
private final VulkanBuffer BloomPropertiesVerticalBuffer;
private final VulkanBuffer BloomPropertiesFinalBuffer;
private final SpecializationConstants specConstants; private final SpecializationConstants specConstants;
private final TextureSampler textureSampler; private final TextureSampler textureSampler;
private Attachment ColourAttachment;
private VkRenderingAttachmentInfo.Buffer ColourAttachmentInfo;
private VkRenderingAttachmentInfo.Buffer FinalOutputInfo;
private Attachment BloomPingAttachment; private Attachment BloomPingAttachment;
private Attachment BloomPongAttachment; private Attachment BloomPongAttachment;
private Attachment FinalAttachment; private Attachment FinalAttachment;
private VkRenderingAttachmentInfo.Buffer BloomPingAttachmentInfo; private VkRenderingAttachmentInfo.Buffer BloomPingAttachmentInfo;
private VkRenderingAttachmentInfo.Buffer BloomPongAttachmentInfo; private VkRenderingAttachmentInfo.Buffer BloomPongAttachmentInfo;
private VkRenderingInfo RenderingInfo; private VkRenderingAttachmentInfo.Buffer FinalOutputInfo;
private VkRenderingInfo SingleRenderingInfo; private VkRenderingInfo SingleRenderingInfo;
private VkRenderingInfo BloomPingRenderingInfo; private VkRenderingInfo BloomPingRenderingInfo;
private VkRenderingInfo BloomPongRenderingInfo; 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){ public PostProcess(VulkanContext VkCtx, Attachment SrcAttachment){
ClearValueColour = VkClearValue.calloc(); ClearValueColour = VkClearValue.calloc();
ClearValueColour.color(c->c.float32(0,0.0f).float32(1,0.0f).float32(2,0.0f).float32(3,0.0f)); ClearValueColour.color(c -> c.float32(0, 0.0f).float32(1, 0.0f).float32(2, 0.0f).float32(3, 0.0f));
CreateAttachments(VkCtx);
ColourAttachment = CreateColourAttachment(VkCtx); textureSampler = new TextureSampler(VkCtx, new TextureSamplerInfo(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
BloomPingAttachment = CreateColourAttachment(VkCtx); VK_BORDER_COLOR_INT_OPAQUE_BLACK, 1, false));
BloomPongAttachment = CreateColourAttachment(VkCtx); AttachmentDescriptorSetLayout = new DescriptorSetLayout(VkCtx,
FinalAttachment = CreateColourAttachment(VkCtx); new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, 1, VK_SHADER_STAGE_FRAGMENT_BIT));
MultiAttachmentDescriptorSetLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{
List<Attachment> attachments = new ArrayList<>(); new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, 1, VK_SHADER_STAGE_FRAGMENT_BIT),
attachments.add(ColourAttachment); new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, 1, VK_SHADER_STAGE_FRAGMENT_BIT)});
attachments.add(BloomPingAttachment); UpdateDescriptors(VkCtx, SrcAttachment);
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);
specConstants = new SpecializationConstants(); specConstants = new SpecializationConstants();
ShaderModule[] shaderModules = CreateShaderModules(VkCtx, specConstants); boolean multisampled = !EngineConfig.getInstance().DeferredRendering() && EngineConfig.getInstance().RenderAA() > 1;
pipeline = CreatePipeline(VkCtx, shaderModules, new DescriptorSetLayout[]{AttachmentDescriptorSetLayout, FragmentUniformDescriptorSetLayout}); pipeline = CreatePipeline(VkCtx, multisampled ? MULTI_PASS_FRAGMENT_SHADER_FILE_GLSL : SINGLE_PASS_FRAGMENT_SHADER_FILE_GLSL,
Arrays.asList(shaderModules).forEach(shader->shader.CleanUp(VkCtx)); MultiAttachmentDescriptorSetLayout, specConstants.GetSpecializationInfo());
shaderModules = CreateBloomShaderModules(VkCtx, specConstants); bloomPipeline = CreatePipeline(VkCtx, BLOOM_FRAGMENT_SHADER_FILE_GLSL, AttachmentDescriptorSetLayout, null);
bloomPipeline = CreateSingleOutputPipeline(VkCtx, shaderModules, new DescriptorSetLayout[]{MultiAttachmentDescriptorSetLayout[0], FragmentUniformDescriptorSetLayout}); extractionPipeline = CreatePipeline(VkCtx, multisampled ? MULTI_EXTRACT_FRAGMENT_SHADER_FILE_GLSL : EXTRACT_FRAGMENT_SHADER_FILE_GLSL,
Logger.debug("Post Process Renderer Pipeline -> [{}]",pipeline.GetVulkanPipeline()); AttachmentDescriptorSetLayout, null);
} }
private static VkRenderingAttachmentInfo.Buffer CreateColourAttachmentInfos(List<Attachment> attachments, VkClearValue ClearValue){ static int ScaledExtent(int size, float scale) {
int numAttachments = attachments.size(); return Math.max(1, Math.round(size * scale));
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);
}
return result;
} }
private static Attachment CreateColourAttachment(VulkanContext VkCtx){ static int ValidateBloomPasses(int passes) {
SwapChain swapChain = VkCtx.GetSwapChain(); if (passes < 0 || (passes & 1) != 0) {
VkExtent2D SwapChainExtent = swapChain.GetSwapChainExtent(); throw new IllegalArgumentException("Bloom requires horizontal/vertical pass pairs, or zero to disable it");
return new Attachment(VkCtx,SwapChainExtent.width(),SwapChainExtent.height(),COLOUR_FORMAT,VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); }
return passes;
}
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){ private static VkRenderingAttachmentInfo.Buffer CreateColourAttachmentInfo(Attachment SrcAttachment, VkClearValue ClearValue){
@ -159,283 +124,163 @@ public class PostProcess {
.sType$Default() .sType$Default()
.imageView(SrcAttachment.GetVkImageView().GetVulkanImageView()) .imageView(SrcAttachment.GetVkImageView().GetVulkanImageView())
.imageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) .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) .storeOp(VK_ATTACHMENT_STORE_OP_STORE)
.clearValue(ClearValue); .clearValue(ClearValue);
} }
private static Pipeline CreatePipeline(VulkanContext VkCtx, ShaderModule[] shaderModules, DescriptorSetLayout[] descriptorSetLayouts){ private static Pipeline CreatePipeline(VulkanContext VkCtx, String fragmentShader, DescriptorSetLayout descriptorSetLayout,
var VertexBufferStruct = new EmptyVertexBufferStruct(); VkSpecializationInfo specializationInfo){
var BuildInfo = new PipelineBuildInfo(shaderModules, VertexBufferStruct.GetVertexInput(), new int[]{COLOUR_FORMAT, COLOUR_FORMAT}) if (EngineConfig.getInstance().RecompileShaders()) {
.SetDescriptorSetLayouts(descriptorSetLayouts) ShaderCompiler.CompileGLSLShaderOnChange(VERTEX_SHADER_FILE_GLSL, Shaderc.shaderc_glsl_vertex_shader);
.BlendingIsUsed(true); ShaderCompiler.CompileGLSLShaderOnChange(fragmentShader, Shaderc.shaderc_glsl_fragment_shader);
var PipeLine = new DefaultPipeline(VkCtx, BuildInfo);
VertexBufferStruct.CleanUp();
return PipeLine;
} }
ShaderModule[] shaderModules = {
private static Pipeline CreateSingleOutputPipeline(VulkanContext VkCtx, ShaderModule[] shaderModules, DescriptorSetLayout[] descriptorSetLayouts){ 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 VertexBufferStruct = new EmptyVertexBufferStruct();
try {
var BuildInfo = new PipelineBuildInfo(shaderModules, VertexBufferStruct.GetVertexInput(), new int[]{COLOUR_FORMAT}) var BuildInfo = new PipelineBuildInfo(shaderModules, VertexBufferStruct.GetVertexInput(), new int[]{COLOUR_FORMAT})
.SetDescriptorSetLayouts(descriptorSetLayouts) .SetDescriptorSetLayouts(new DescriptorSetLayout[]{descriptorSetLayout})
.BlendingIsUsed(true); .SetPushConstantRanges(new PushConstantsRange[]{new PushConstantsRange(VK_SHADER_STAGE_FRAGMENT_BIT, 0, PASS_CONSTANT_SIZE)})
var PipeLine = new DefaultPipeline(VkCtx, BuildInfo); .SetDepthTest(false).SetDepthWrite(false).BlendingIsUsed(false);
return new DefaultPipeline(VkCtx, BuildInfo);
} finally {
VertexBufferStruct.CleanUp(); VertexBufferStruct.CleanUp();
return PipeLine; Arrays.stream(shaderModules).forEach(shader -> shader.CleanUp(VkCtx));
}
} }
private static VkRenderingInfo CreateRenderInfo(Attachment ColourAttachment, VkRenderingAttachmentInfo.Buffer ColourAttachmentInfo){ private static VkRenderingInfo CreateRenderInfo(Attachment ColourAttachment, VkRenderingAttachmentInfo.Buffer ColourAttachmentInfo){
VkRenderingInfo renderingInfo;
try(var MemStack = MemoryStack.stackPush()){
Image image = ColourAttachment.GetVkImage(); Image image = ColourAttachment.GetVkImage();
VkExtent2D extent2D = VkExtent2D.calloc(MemStack).width(image.GetWidth()).height(image.GetHeight()); return VkRenderingInfo.calloc().sType$Default()
var RenderArea = VkRect2D.calloc(MemStack).extent(extent2D); .renderArea(area -> area.offset(offset -> offset.set(0, 0)).extent(extent -> extent.set(image.GetWidth(), image.GetHeight())))
.layerCount(1).pColorAttachments(ColourAttachmentInfo);
renderingInfo = VkRenderingInfo.calloc()
.sType$Default()
.renderArea(RenderArea)
.layerCount(1)
.pColorAttachments(ColourAttachmentInfo);
}
return renderingInfo;
} }
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(); DescriptorAllocator descriptorAllocator = VkCtx.GetDescriptorAllocator();
Device device = VkCtx.GetDevice(); DescriptorSet descriptorSet = descriptorAllocator.GetDescriptorSet(descriptorID);
DescriptorSet descriptorSet = descriptorAllocator.AddDescriptorSets(device,descriptorID,1 , descriptorSetLayout)[0]; if (descriptorSet == null) descriptorSet = descriptorAllocator.AddDescriptorSet(VkCtx.GetDevice(), descriptorID, descriptorSetLayout);
List<ImageView> images = new ArrayList<>(); List<ImageView> images = attachments.stream().map(Attachment::GetVkImageView).toList();
for(int i = 0; i < attachment.length; i++) { descriptorSet.SetImages(VkCtx.GetDevice(), images, textureSampler, 0);
images.add(attachment[i].GetVkImageView());
}
descriptorSet.SetImages(device,images,textureSampler,0);
} }
private static void BeginWrite(MemoryStack stack, VkCommandBuffer command, Attachment attachment, boolean initialized){
private static void CreateAttachmentDescriptorSet(VulkanContext VkCtx, DescriptorSetLayout descriptorSetLayout, Attachment attachment, TextureSampler textureSampler, String descriptorID){ VulkanUtils.ImageBarrier(stack, command, attachment.GetVkImage().getVulkanImage(),
DescriptorAllocator descriptorAllocator = VkCtx.GetDescriptorAllocator(); initialized ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED,
Device device = VkCtx.GetDevice(); VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
DescriptorSet descriptorSet = descriptorAllocator.AddDescriptorSets(device,descriptorID,1 , descriptorSetLayout)[0]; initialized ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_2_NONE,
descriptorSet.SetImage(device,attachment.GetVkImageView(),textureSampler,0); 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){ private static void EndWrite(MemoryStack stack, VkCommandBuffer command, Attachment attachment){
if(EngineConfig.getInstance().RecompileShaders()){ VulkanUtils.ImageBarrier(stack, command, attachment.GetVkImage().getVulkanImage(),
ShaderCompiler.CompileGLSLShaderOnChange(VERTEX_SHADER_FILE_GLSL, Shaderc.shaderc_glsl_vertex_shader); VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
ShaderCompiler.CompileGLSLShaderOnChange(BLOOM_FRAGMENT_SHADER_FILE_GLSL, Shaderc.shaderc_glsl_fragment_shader); 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);
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 ShaderModule[] CreateShaderModules(VulkanContext VkCtx, SpecializationConstants specConstants){ private static void Draw(VulkanContext VkCtx, MemoryStack stack, VkCommandBuffer command, Pipeline passPipeline,
String ShaderPath = SINGLE_PASS_FRAGMENT_SHADER_FILE_GLSL; Attachment target, VkRenderingInfo renderingInfo, String descriptorID, ByteBuffer constants){
if(EngineConfig.getInstance().RecompileShaders()){ vkCmdBeginRendering(command, renderingInfo);
ShaderCompiler.CompileGLSLShaderOnChange(VERTEX_SHADER_FILE_GLSL, Shaderc.shaderc_glsl_vertex_shader); vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, passPipeline.GetVulkanPipeline());
if(EngineConfig.getInstance().RenderAA() > 1) ShaderPath = MULTI_PASS_FRAGMENT_SHADER_FILE_GLSL; Image image = target.GetVkImage();
ShaderCompiler.CompileGLSLShaderOnChange(ShaderPath, Shaderc.shaderc_glsl_fragment_shader); vkCmdSetViewport(command, 0, VkViewport.calloc(1, stack).x(0).y(image.GetHeight())
} .width(image.GetWidth()).height(-image.GetHeight()).minDepth(0.0f).maxDepth(1.0f));
ShaderPath = SINGLE_PASS_FRAGMENT_SHADER_FILE_SPV; vkCmdSetScissor(command, 0, VkRect2D.calloc(1, stack)
if(EngineConfig.getInstance().RenderAA() > 1) ShaderPath = MULTI_PASS_FRAGMENT_SHADER_FILE_SPV; .offset(offset -> offset.set(0, 0)).extent(extent -> extent.set(image.GetWidth(), image.GetHeight())));
return new ShaderModule[]{ vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS, passPipeline.GetVulkanPipelineLayout(), 0,
new ShaderModule(VkCtx,VK_SHADER_STAGE_VERTEX_BIT,VERTEX_SHADER_FILE_SPV,null), stack.longs(VkCtx.GetDescriptorAllocator().GetDescriptorSet(descriptorID).GetVkDescriptorSet()), null);
new ShaderModule(VkCtx,VK_SHADER_STAGE_FRAGMENT_BIT,ShaderPath,specConstants.GetSpecializationInfo()) 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){ 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()){ try(var MemStack = MemoryStack.stackPush()){
SwapChain swapChain = VkCtx.GetSwapChain();
VkCommandBuffer CommandHandle = commandBuffer.GetVulkanCommandBuffer(); 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(), EndWrite(MemStack, CommandHandle, SrcAttachment);
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, BeginWrite(MemStack, CommandHandle, BloomPingAttachment, attachmentsInitialized);
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 (bloomPasses == 0) {
VulkanUtils.ImageBarrier(MemStack,CommandHandle,ColourAttachment.GetVkImage().getVulkanImage(), vkCmdBeginRendering(CommandHandle, BloomPingRenderingInfo);
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());
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); vkCmdEndRendering(CommandHandle);
} else {
Draw(VkCtx, MemStack, CommandHandle, extractionPipeline, BloomPingAttachment, BloomPingRenderingInfo, DESCRIPTOR_ID_ATTACHMENT, constants);
}
EndWrite(MemStack, CommandHandle, BloomPingAttachment);
VulkanUtils.ImageBarrier(MemStack,CommandHandle,ColourAttachment.GetVkImage().getVulkanImage(), for(int i = 0; i < bloomPasses; i++){
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, boolean writePong = (i & 1) == 0;
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;
Attachment dstBloomAttachment = writePong ? BloomPongAttachment : BloomPingAttachment; Attachment dstBloomAttachment = writePong ? BloomPongAttachment : BloomPingAttachment;
VkRenderingInfo dstRenderingInfo = writePong ? BloomPongRenderingInfo : BloomPingRenderingInfo; VkRenderingInfo dstRenderingInfo = writePong ? BloomPongRenderingInfo : BloomPingRenderingInfo;
String srcDescriptorId = writePong ? DESCRIPTOR_ID_BLOOM_ATTACHMENT : DESCRIPTOR_ID_BLOOM_PONG_ATTACHMENT; 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; 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);
}
int dstOldLayout = writePong && !bloomPongHasBeenWritten // The UI/swapchain owns the final layout. Discard contents, not prior read/write dependencies.
? VK_IMAGE_LAYOUT_UNDEFINED VulkanUtils.ImageBarrier(MemStack, CommandHandle, FinalAttachment.GetVkImage().getVulkanImage(),
: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; 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,
VulkanUtils.ImageBarrier(MemStack,CommandHandle,dstBloomAttachment.GetVkImage().getVulkanImage(), VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
dstOldLayout,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT);
VK_ACCESS_2_SHADER_READ_BIT,VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, Draw(VkCtx, MemStack, CommandHandle, pipeline, FinalAttachment, SingleRenderingInfo, DESCRIPTOR_ID_BLOOM_IMAGE_ATTACHMENT, constants);
VK_IMAGE_ASPECT_COLOR_BIT); attachmentsInitialized = true;
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;
} }
} }
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);
}
}
public void Resize(VulkanContext VkCtx, Attachment SrcAttachment){ public void Resize(VulkanContext VkCtx, Attachment SrcAttachment){
RenderingInfo.free();; CleanUpAttachments(VkCtx);
ColourAttachment.CleanUp(VkCtx); CreateAttachments(VkCtx);
ColourAttachmentInfo.free(); UpdateDescriptors(VkCtx, SrcAttachment);
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);
} }
private void SetScreenSizeBuffer(VulkanContext VkCtx){ private void CleanUpAttachments(VulkanContext VkCtx){
long MappedMemory = ScreenSizeBuffer.MapMemory(VkCtx); BloomPingRenderingInfo.free();
FloatBuffer dataBuffer = MemoryUtil.memFloatBuffer(MappedMemory,(int)ScreenSizeBuffer.GetRequestedSize()); BloomPongRenderingInfo.free();
VkExtent2D SwapChainExtent = VkCtx.GetSwapChain().GetSwapChainExtent(); SingleRenderingInfo.free();
dataBuffer.put(0,SwapChainExtent.width()); BloomPingAttachmentInfo.free();
dataBuffer.put(1,SwapChainExtent.height()); BloomPongAttachmentInfo.free();
ScreenSizeBuffer.UnMapMemory(VkCtx); FinalOutputInfo.free();
} BloomPingAttachment.CleanUp(VkCtx);
BloomPongAttachment.CleanUp(VkCtx);
public static float GAMMA = 1.75f; FinalAttachment.CleanUp(VkCtx);
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);
} }
public Attachment GetAttachment(){return FinalAttachment;} public Attachment GetAttachment(){return FinalAttachment;}
public void CleanUp(VulkanContext VkCtx){ public void CleanUp(VulkanContext VkCtx){
ClearValueColour.free(); CleanUpAttachments(VkCtx);
ColourAttachment.CleanUp(VkCtx); pipeline.CleanUp(VkCtx);
BloomPingAttachment.CleanUp(VkCtx); bloomPipeline.CleanUp(VkCtx);
BloomPongAttachment.CleanUp(VkCtx); extractionPipeline.CleanUp(VkCtx);
FinalAttachment.CleanUp(VkCtx);
textureSampler.CleanUp(VkCtx); textureSampler.CleanUp(VkCtx);
AttachmentDescriptorSetLayout.CleanUp(VkCtx); AttachmentDescriptorSetLayout.CleanUp(VkCtx);
FragmentUniformDescriptorSetLayout.CleanUp(VkCtx); MultiAttachmentDescriptorSetLayout.CleanUp(VkCtx);
pipeline.CleanUp(VkCtx); ClearValueColour.free();
RenderingInfo.free();
ColourAttachmentInfo.free();
ScreenSizeBuffer.cleanup(VkCtx);
BloomPropertiesHorizontalBuffer.cleanup(VkCtx);
BloomPropertiesVerticalBuffer.cleanup(VkCtx);
BloomPropertiesFinalBuffer.cleanup(VkCtx);
specConstants.CleanUp(); specConstants.CleanUp();
} }
} }

View file

@ -65,9 +65,9 @@ public class AmbientOcclusionRenderer {
private VkRenderingAttachmentInfo.Buffer ssaoAttachmentInfo; private VkRenderingAttachmentInfo.Buffer ssaoAttachmentInfo;
private Attachment ssaoBlurAttachment; private Attachment ssaoBlurAttachment;
private VkRenderingAttachmentInfo.Buffer ssaoBlurAttachmentInfo; private VkRenderingAttachmentInfo.Buffer ssaoBlurAttachmentInfo;
private Attachment ssaoViewPosAttachment; private Attachment viewPositionAttachment;
private VkRenderingAttachmentInfo.Buffer ssaoViewPosAttachmentInfo; private boolean attachmentsInitialized;
private final Attachment[] SSAO_ATTACHMENTS = new Attachment[3]; private int sampleCount;
private final Pipeline ssaoPipeline; private final Pipeline ssaoPipeline;
private final Pipeline blurPipeline; private final Pipeline blurPipeline;
@ -89,17 +89,17 @@ public class AmbientOcclusionRenderer {
private Attachment NoiseImageAttachment; private Attachment NoiseImageAttachment;
public AmbientOcclusionRenderer(VulkanContext VkCtx, List<Attachment> MRTAttachments, Queue queue) { public AmbientOcclusionRenderer(VulkanContext VkCtx, List<Attachment> MRTAttachments, Queue queue) {
viewPositionAttachment = MRTAttachments.get(7);
List<Attachment> attachments = new ArrayList<>(); List<Attachment> attachments = new ArrayList<>();
attachments.add(MRTAttachments.get(7)); // position attachments.add(viewPositionAttachment);
attachments.add(MRTAttachments.get(2)); // normal attachments.add(MRTAttachments.get(2)); // normal
CreateAttachments(VkCtx); CreateAttachments(VkCtx);
clearColour = VkClearValue.calloc().color( 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); ssaoAttachmentInfo = CreateColourAttachmentInfo(ssaoAttachment, clearColour);
ssaoBlurAttachmentInfo = CreateColourAttachmentInfo(ssaoBlurAttachment, clearColour); ssaoBlurAttachmentInfo = CreateColourAttachmentInfo(ssaoBlurAttachment, clearColour);
ssaoViewPosAttachmentInfo = CreateColourAttachmentInfo(ssaoViewPosAttachment, clearColour);
var textureSamplerInfo = new TextureSamplerInfo(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, 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); textureSampler = new TextureSampler(VkCtx, textureSamplerInfo);
ByteBuffer NoiseTexturedata = SSAO_Utils.GenerateNoiseTexture(); ByteBuffer NoiseTexturedata = SSAO_Utils.GenerateNoiseTexture();
@ -173,9 +173,9 @@ public class AmbientOcclusionRenderer {
var vtxBuffStruct = new EmptyVertexBufferStruct(); var vtxBuffStruct = new EmptyVertexBufferStruct();
int[] formats; int[] formats;
if(blur){ if(blur){
formats = new int[]{SSAO_BLUR_ATTACHMENT, VIEW_POS_FORMAT}; formats = new int[]{SSAO_BLUR_ATTACHMENT};
} else { } else {
formats = new int[]{SSAO_RAW_ATTACHMENT, VIEW_POS_FORMAT}; formats = new int[]{SSAO_RAW_ATTACHMENT};
} }
var buildInfo = new PipelineBuildInfo(shaderModules, vtxBuffStruct.GetVertexInput(),formats) var buildInfo = new PipelineBuildInfo(shaderModules, vtxBuffStruct.GetVertexInput(),formats)
.SetDescriptorSetLayouts(descSetLayouts) .SetDescriptorSetLayouts(descSetLayouts)
@ -235,8 +235,11 @@ public class AmbientOcclusionRenderer {
} }
private void CreateSSAOAttachmentDescriptorSet(VulkanContext vkCtx, List<Attachment> Attachments ) { private void CreateSSAOAttachmentDescriptorSet(VulkanContext vkCtx, List<Attachment> Attachments ) {
DescriptorSet descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet( DescriptorSet descSet = vkCtx.GetDescriptorAllocator().GetDescriptorSet(DESC_ID_SSAO_ATTACHMENTS);
vkCtx.GetDevice(), DESC_ID_SSAO_ATTACHMENTS,ssaoAttachmentDescriptorLayout ); if (descSet == null) {
descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet(
vkCtx.GetDevice(), DESC_ID_SSAO_ATTACHMENTS, ssaoAttachmentDescriptorLayout);
}
List<ImageView> imageViews = new ArrayList<>(); List<ImageView> imageViews = new ArrayList<>();
Attachments.forEach(a -> imageViews.add(a.GetVkImageView())); Attachments.forEach(a -> imageViews.add(a.GetVkImageView()));
@ -246,12 +249,15 @@ public class AmbientOcclusionRenderer {
private void CreateBlurDescriptorSet(VulkanContext vkCtx) { private void CreateBlurDescriptorSet(VulkanContext vkCtx) {
DescriptorSet descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet( DescriptorSet descSet = vkCtx.GetDescriptorAllocator().GetDescriptorSet(DESC_ID_SSAO_BLUR_INPUT);
vkCtx.GetDevice(),DESC_ID_SSAO_BLUR_INPUT, blurDescriptorLayout); if (descSet == null) {
descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet(
vkCtx.GetDevice(), DESC_ID_SSAO_BLUR_INPUT, blurDescriptorLayout);
}
List<ImageView> imageViews = new ArrayList<>(); List<ImageView> imageViews = new ArrayList<>();
imageViews.add(ssaoAttachment.GetVkImageView()); imageViews.add(ssaoAttachment.GetVkImageView());
imageViews.add(ssaoViewPosAttachment.GetVkImageView()); imageViews.add(viewPositionAttachment.GetVkImageView());
descSet.SetImages(vkCtx.GetDevice(), imageViews,textureSampler,0); descSet.SetImages(vkCtx.GetDevice(), imageViews,textureSampler,0);
} }
@ -259,21 +265,20 @@ public class AmbientOcclusionRenderer {
private void CreateAttachments(VulkanContext VkCtx){ private void CreateAttachments(VulkanContext VkCtx){
SwapChain swapChain = VkCtx.GetSwapChain(); SwapChain swapChain = VkCtx.GetSwapChain();
VkExtent2D swapChainExtent = swapChain.GetSwapChainExtent(); VkExtent2D swapChainExtent = swapChain.GetSwapChainExtent();
var config = EngineConfig.getInstance();
float AspectRatio = (float)swapChainExtent.width()/(float)swapChainExtent.height(); float scale = config.GetSSAOScale();
int Width = swapChainExtent.width(); int Width = ScaledExtent(swapChainExtent.width(), scale);
int Height = swapChainExtent.height(); int Height = ScaledExtent(swapChainExtent.height(), scale);
int RawWidth = (int)(Math.round(Math.min(swapChainExtent.width()/2.0f, 1920 * AspectRatio))); sampleCount = config.GetSSAOSamples();
int RawHeight = (int)(Math.round(Math.min(swapChainExtent.height()/2.0f, 1080))); attachmentsInitialized = false;
//SSAO Raw //SSAO Raw
ssaoAttachment = new Attachment(VkCtx, Width, Height, SSAO_RAW_ATTACHMENT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 1); 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 //SSAO Blur
ssaoBlurAttachment = new Attachment(VkCtx, Width, Height, SSAO_BLUR_ATTACHMENT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 1); 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); static int ScaledExtent(int size, float scale) {
SSAO_ATTACHMENTS[1] = ssaoViewPosAttachment; return Math.max(1, Math.round(size * scale));
} }
public void Render(VulkanContext VkCtx,EngineInstance engineInstance, CommandBuffer cmdBuffer, MultiRenderTargetAttachments MRT,int CurrentFrame) { public void Render(VulkanContext VkCtx,EngineInstance engineInstance, CommandBuffer cmdBuffer, MultiRenderTargetAttachments MRT,int CurrentFrame) {
@ -285,20 +290,11 @@ public class AmbientOcclusionRenderer {
UpdateSSAOInfo(VkCtx, engineInstance, CurrentFrame); UpdateSSAOInfo(VkCtx, engineInstance, CurrentFrame);
VulkanUtils.ImageBarrier(stack, cmdHandle, ssaoAttachment.GetVkImage().getVulkanImage(), 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_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_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);
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,
VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT); VK_IMAGE_ASPECT_COLOR_BIT);
@ -335,11 +331,11 @@ public class AmbientOcclusionRenderer {
VK_IMAGE_ASPECT_COLOR_BIT); VK_IMAGE_ASPECT_COLOR_BIT);
VulkanUtils.ImageBarrier(stack, cmdHandle, ssaoBlurAttachment.GetVkImage().getVulkanImage(), 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_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_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_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT); VK_IMAGE_ASPECT_COLOR_BIT);
@ -367,6 +363,7 @@ public class AmbientOcclusionRenderer {
VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_2_SHADER_READ_BIT, VK_ACCESS_2_SHADER_READ_BIT,
VK_IMAGE_ASPECT_COLOR_BIT); VK_IMAGE_ASPECT_COLOR_BIT);
attachmentsInitialized = true;
} }
} }
@ -405,6 +402,8 @@ public class AmbientOcclusionRenderer {
vkCmdSetScissor(cmdHandle, 0, scissor); 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; public static float SSAO_RESOLUTION_SCALE = 0.5f;
private void UpdateSSAOInfo(VulkanContext vkCtx, EngineInstance engineInstance, int currentFrame) { 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 * 2, SSAO_RADIUS);
data.putFloat(offset + VulkanUtils.FLOAT_SIZE * 3, SSAO_BIAS); data.putFloat(offset + VulkanUtils.FLOAT_SIZE * 3, SSAO_BIAS);
offset += VulkanUtils.VEC4_SIZE; offset += VulkanUtils.VEC4_SIZE;
data.putInt(offset, SSAO_KERNEL_SIZE); data.putInt(offset, sampleCount);
offset +=VulkanUtils.INT_SIZE;
data.putFloat(offset, SSAO_RESOLUTION_SCALE);
offset += VulkanUtils.FLOAT_SIZE;
buffer.UnMapMemory(vkCtx); buffer.UnMapMemory(vkCtx);
} }
@ -444,6 +440,7 @@ public class AmbientOcclusionRenderer {
ssaoBlurAttachmentInfo.free(); ssaoBlurAttachmentInfo.free();
ssaoBlurAttachment.CleanUp(VkCtx); ssaoBlurAttachment.CleanUp(VkCtx);
viewPositionAttachment = attachments.get(0);
CreateAttachments(VkCtx); CreateAttachments(VkCtx);
ssaoAttachmentInfo = CreateColourAttachmentInfo(ssaoAttachment, clearColour); ssaoAttachmentInfo = CreateColourAttachmentInfo(ssaoAttachment, clearColour);
@ -451,9 +448,7 @@ public class AmbientOcclusionRenderer {
ssaoRenderInfo = CreateRenderInfo(ssaoAttachment, ssaoAttachmentInfo,true); ssaoRenderInfo = CreateRenderInfo(ssaoAttachment, ssaoAttachmentInfo,true);
ssaoBlurRenderInfo = CreateRenderInfo(ssaoBlurAttachment, ssaoBlurAttachmentInfo,false); ssaoBlurRenderInfo = CreateRenderInfo(ssaoBlurAttachment, ssaoBlurAttachmentInfo,false);
attachments.add(NoiseImageAttachment); CreateSSAOAttachmentDescriptorSet(VkCtx, List.of(attachments.get(0), attachments.get(1), NoiseImageAttachment));
CreateSSAOAttachmentDescriptorSet(VkCtx,attachments);
CreateBlurDescriptorSet(VkCtx); 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.Structure.SwapChain.SwapChain;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanBuffer; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanBuffer;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils;
import org.joml.Matrix4f;
import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.shaderc.Shaderc; import org.lwjgl.util.shaderc.Shaderc;
@ -35,16 +34,15 @@ public class ReflectionsRenderer {
private static final long SSR_INFO_BUFFER_SIZE = private static final long SSR_INFO_BUFFER_SIZE =
VulkanUtils.MATRIX4X4_SIZE + // projection VulkanUtils.MATRIX4X4_SIZE + // projection
VulkanUtils.MATRIX4X4_SIZE + // projection inv
VulkanUtils.MATRIX4X4_SIZE + // view VulkanUtils.MATRIX4X4_SIZE + // view
VulkanUtils.MATRIX4X4_SIZE + // view inv
VulkanUtils.VEC4_SIZE + // steps, distance, max steps VulkanUtils.VEC4_SIZE + // steps, distance, max steps
VulkanUtils.VEC4_SIZE; //Camera VulkanUtils.VEC4_SIZE; //Camera
public static float STEP_SIZE = 0.25f; public static float STEP_SIZE = 0.25f;
public static float MAX_DIST = 200.0f; 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_ATTACHMENTS = "SSR_DESC_ID_ATTACHMENTS";
private static final String DESC_ID_SSR_INFO = "SSR_DESC_ID_INFO"; 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_GLSL = "resources/EngineResources/shaders/screen_space_reflection_frag.glsl";
private static final String FRAGMENT_SHADER_FILE_SPV = FRAGMENT_SHADER_FILE_GLSL + ".spv"; 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 ssrAttachment;
private Attachment renderedSceneAttachment; private Attachment renderedSceneAttachment;
private VkRenderingAttachmentInfo.Buffer ssrAttachmentInfo; 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 ssrPipeline;
private final Pipeline compositePipeline;
private final TextureSampler textureSampler; private final TextureSampler textureSampler;
private DescriptorSetLayout ssrAttachmentDescriptorLayout; private DescriptorSetLayout ssrAttachmentDescriptorLayout;
@ -78,8 +82,9 @@ public class ReflectionsRenderer {
clearColour = VkClearValue.calloc().color( 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, 0.0f).float32(1, 0.0f).float32(2, 0.0f).float32(3, 0.0f));
ssrAttachmentInfo = CreateColourAttachmentInfo(ssrAttachment, clearColour); ssrAttachmentInfo = CreateColourAttachmentInfo(ssrAttachment, clearColour);
compositeAttachmentInfo = CreateColourAttachmentInfo(compositeAttachment, clearColour);
var textureSamplerInfo = new TextureSamplerInfo(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, 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); textureSampler = new TextureSampler(VkCtx, textureSamplerInfo);
int numAttachments = attachments.size(); int numAttachments = attachments.size();
@ -97,6 +102,7 @@ public class ReflectionsRenderer {
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, DESC_ID_SSR_INFO,ssrInfoDescriptorLayout); VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, DESC_ID_SSR_INFO,ssrInfoDescriptorLayout);
ssrRenderInfo = CreateRenderInfo(ssrAttachment, ssrAttachmentInfo,true); ssrRenderInfo = CreateRenderInfo(ssrAttachment, ssrAttachmentInfo,true);
compositeRenderInfo = CreateRenderInfo(compositeAttachment, compositeAttachmentInfo, false);
ShaderModule[] shaderModules = CreateRawShaderModules(VkCtx); ShaderModule[] shaderModules = CreateRawShaderModules(VkCtx);
ssrPipeline = CreatePipeline(VkCtx, shaderModules, ssrPipeline = CreatePipeline(VkCtx, shaderModules,
@ -105,6 +111,15 @@ public class ReflectionsRenderer {
ssrInfoDescriptorLayout ssrInfoDescriptorLayout
}); });
Arrays.stream(shaderModules).toList().forEach(shaderModule -> shaderModule.CleanUp(VkCtx)); 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 ) { private void CreateSSRAttachmentDescriptorSet(VulkanContext vkCtx, List<Attachment> Attachments ) {
DescriptorSet descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet( DescriptorSet descSet = vkCtx.GetDescriptorAllocator().GetDescriptorSet(DESC_ID_SSR_ATTACHMENTS);
if (descSet == null) {
descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet(
vkCtx.GetDevice(), DESC_ID_SSR_ATTACHMENTS, ssrAttachmentDescriptorLayout); vkCtx.GetDevice(), DESC_ID_SSR_ATTACHMENTS, ssrAttachmentDescriptorLayout);
}
List<ImageView> imageViews = new ArrayList<>(); List<ImageView> imageViews = new ArrayList<>();
Attachments.forEach(a -> imageViews.add(a.GetVkImageView())); Attachments.forEach(a -> imageViews.add(a.GetVkImageView()));
descSet.SetImages(vkCtx.GetDevice(), imageViews, textureSampler, 0); 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(); SwapChain swapChain = VkCtx.GetSwapChain();
VkExtent2D swapChainExtent = swapChain.GetSwapChainExtent(); VkExtent2D swapChainExtent = swapChain.GetSwapChainExtent();
float AspectRatio = (float)swapChainExtent.width()/(float)swapChainExtent.height();
int Width = swapChainExtent.width(); int Width = swapChainExtent.width();
int Height = swapChainExtent.height(); int Height = swapChainExtent.height();
int RawWidth = (int)(Math.round(Math.min(swapChainExtent.width()/2.0f, 1920 * AspectRatio))); var config = EngineConfig.getInstance();
int RawHeight = (int)(Math.round(Math.min(swapChainExtent.height()/2.0f, 1080))); float scale = config.GetSSRScale();
int RawWidth = ScaledExtent(Width, scale);
int RawHeight = ScaledExtent(Height, scale);
maxSteps = config.GetSSRMaxSteps();
attachmentsInitialized = false;
//SSR Raw //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) { public void Render(VulkanContext VkCtx,EngineInstance engineInstance, CommandBuffer cmdBuffer, int CurrentFrame) {
@ -201,9 +234,11 @@ public class ReflectionsRenderer {
VK_IMAGE_ASPECT_COLOR_BIT); VK_IMAGE_ASPECT_COLOR_BIT);
VulkanUtils.ImageBarrier(stack, cmdHandle, ssrAttachment.GetVkImage().getVulkanImage(), VulkanUtils.ImageBarrier(stack, cmdHandle, ssrAttachment.GetVkImage().getVulkanImage(),
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, attachmentsInitialized ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED,
VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_ACCESS_2_NONE, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, 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); VK_IMAGE_ASPECT_COLOR_BIT);
vkCmdBeginRendering(cmdHandle, ssrRenderInfo); vkCmdBeginRendering(cmdHandle, ssrRenderInfo);
@ -223,6 +258,27 @@ public class ReflectionsRenderer {
vkCmdDraw(cmdHandle, 3, 1, 0, 0); vkCmdDraw(cmdHandle, 3, 1, 0, 0);
vkCmdEndRendering(cmdHandle); 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() { public Attachment GetSSRAttachment() {
return ssrAttachment; return compositeAttachment;
} }
private void UpdateSSRInfo(VulkanContext vkCtx, EngineInstance engineInstance, int currentFrame) { private void UpdateSSRInfo(VulkanContext vkCtx, EngineInstance engineInstance, int currentFrame) {
@ -259,16 +315,12 @@ public class ReflectionsRenderer {
engineInstance.scene().GetProjection().GetProjectionMatrix().get(offset, data); engineInstance.scene().GetProjection().GetProjectionMatrix().get(offset, data);
offset += VulkanUtils.MATRIX4X4_SIZE; 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); engineInstance.scene().GetCamera().GetViewMatrix().get(offset, data);
offset += VulkanUtils.MATRIX4X4_SIZE; 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, STEP_SIZE);
data.putFloat(offset + VulkanUtils.FLOAT_SIZE, MAX_DIST); 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); data.putInt(offset + VulkanUtils.FLOAT_SIZE * 3, 0);
offset += VulkanUtils.VEC4_SIZE; offset += VulkanUtils.VEC4_SIZE;
@ -283,6 +335,9 @@ public class ReflectionsRenderer {
ssrRenderInfo.free(); ssrRenderInfo.free();
ssrAttachmentInfo.free(); ssrAttachmentInfo.free();
ssrAttachment.CleanUp(VkCtx); ssrAttachment.CleanUp(VkCtx);
compositeRenderInfo.free();
compositeAttachmentInfo.free();
compositeAttachment.CleanUp(VkCtx);
renderedSceneAttachment = attachments.get(0); renderedSceneAttachment = attachments.get(0);
@ -290,6 +345,8 @@ public class ReflectionsRenderer {
ssrAttachmentInfo = CreateColourAttachmentInfo(ssrAttachment, clearColour); ssrAttachmentInfo = CreateColourAttachmentInfo(ssrAttachment, clearColour);
ssrRenderInfo = CreateRenderInfo(ssrAttachment, ssrAttachmentInfo,true); ssrRenderInfo = CreateRenderInfo(ssrAttachment, ssrAttachmentInfo,true);
compositeAttachmentInfo = CreateColourAttachmentInfo(compositeAttachment, clearColour);
compositeRenderInfo = CreateRenderInfo(compositeAttachment, compositeAttachmentInfo, false);
CreateSSRAttachmentDescriptorSet(VkCtx, attachments); CreateSSRAttachmentDescriptorSet(VkCtx, attachments);
} }
@ -298,6 +355,10 @@ public class ReflectionsRenderer {
ssrAttachmentInfo.free(); ssrAttachmentInfo.free();
ssrAttachment.CleanUp(VkCtx); ssrAttachment.CleanUp(VkCtx);
ssrPipeline.CleanUp(VkCtx); ssrPipeline.CleanUp(VkCtx);
compositeRenderInfo.free();
compositeAttachmentInfo.free();
compositeAttachment.CleanUp(VkCtx);
compositePipeline.CleanUp(VkCtx);
clearColour.free(); clearColour.free();
Arrays.stream(ssrInfoBuffers).toList().forEach(b -> b.cleanup(VkCtx)); Arrays.stream(ssrInfoBuffers).toList().forEach(b -> b.cleanup(VkCtx));
ssrAttachmentDescriptorLayout.CleanUp(VkCtx); ssrAttachmentDescriptorLayout.CleanUp(VkCtx);

View file

@ -125,6 +125,12 @@ public class ShadowRenderer {
newPipeline = createPipeline(VulkanContext, shaderModules, new DescriptorSetLayout[]{uniformGeomDescriptorSetLayout, textDescriptorSetLayout, newPipeline = createPipeline(VulkanContext, shaderModules, new DescriptorSetLayout[]{uniformGeomDescriptorSetLayout, textDescriptorSetLayout,
descLayoutFrgStorage}); descLayoutFrgStorage});
Arrays.asList(shaderModules).forEach(s -> s.CleanUp(VulkanContext)); 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; VkRenderingInfo OldRenderInfo = renderingInfo;
Attachment OldColourAttachment = colorAttachment; Attachment OldColourAttachment = colorAttachment;
@ -132,6 +138,7 @@ public class ShadowRenderer {
VkRenderingAttachmentInfo OldDepthAttachmentInfo = depthAttachmentInfo; VkRenderingAttachmentInfo OldDepthAttachmentInfo = depthAttachmentInfo;
VkRenderingAttachmentInfo.Buffer OldColourAttachmentInfo = colorAttachmentInfo; VkRenderingAttachmentInfo.Buffer OldColourAttachmentInfo = colorAttachmentInfo;
Pipeline OldPipeline = pipeline; Pipeline OldPipeline = pipeline;
Pipeline oldPipelinePacked = pipelinePacked;
renderingInfo = NewRenderInfo; renderingInfo = NewRenderInfo;
colorAttachment = newColourAttachment; colorAttachment = newColourAttachment;
@ -139,6 +146,7 @@ public class ShadowRenderer {
depthAttachmentInfo = newDepthAttachmentInfo; depthAttachmentInfo = newDepthAttachmentInfo;
colorAttachmentInfo = newColourAttachmentInfo; colorAttachmentInfo = newColourAttachmentInfo;
pipeline = newPipeline; pipeline = newPipeline;
pipelinePacked = newPipelinePacked;
OldRenderInfo.free(); OldRenderInfo.free();
OldDepthAttachmentInfo.free(); OldDepthAttachmentInfo.free();
@ -146,13 +154,14 @@ public class ShadowRenderer {
OldColourAttachmentInfo.free(); OldColourAttachmentInfo.free();
OldColourAttachment.CleanUp(VulkanContext); OldColourAttachment.CleanUp(VulkanContext);
OldPipeline.CleanUp(VulkanContext); OldPipeline.CleanUp(VulkanContext);
oldPipelinePacked.CleanUp(VulkanContext);
} }
public ShadowRenderer(VulkanContext VulkanContext) { public ShadowRenderer(VulkanContext VulkanContext) {
ClearValueColour = VkClearValue.calloc().color( ClearValueColour = 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, 1.0f).float32(2, 0.0f).float32(3, 0.0f));
ClearValueDepth = VkClearValue.calloc().color(c -> c.float32(0, 1.0f)); ClearValueDepth = VkClearValue.calloc().depthStencil(d -> d.depth(1.0f).stencil(0));
depthAttachment = createDepthAttachment(VulkanContext); depthAttachment = createDepthAttachment(VulkanContext);
depthAttachmentInfo = createDepthAttachmentInfo(depthAttachment, ClearValueDepth); depthAttachmentInfo = createDepthAttachmentInfo(depthAttachment, ClearValueDepth);
@ -241,7 +250,7 @@ public class ShadowRenderer {
}) })
.SetDescriptorSetLayouts(DescriptorSetLayouts) .SetDescriptorSetLayouts(DescriptorSetLayouts)
.SetDescriptorSetLayouts(DescriptorSetLayouts) .SetDescriptorSetLayouts(DescriptorSetLayouts)
.SetDepthClamp(VulkanContext.GetDevice().getDepthClamp()) .SetDepthClamp(false)
.CullMode(VK_CULL_MODE_FRONT_BIT) .CullMode(VK_CULL_MODE_FRONT_BIT)
.DepthBuffer(VK_COMPARE_OP_LESS_OR_EQUAL); .DepthBuffer(VK_COMPARE_OP_LESS_OR_EQUAL);
var pipeline = new DefaultPipeline(VulkanContext, buildInfo); var pipeline = new DefaultPipeline(VulkanContext, buildInfo);
@ -267,7 +276,7 @@ public class ShadowRenderer {
new PushConstantsRange(VK_SHADER_STAGE_VERTEX_BIT, 0, PUSH_CONSTANTS_SIZE) new PushConstantsRange(VK_SHADER_STAGE_VERTEX_BIT, 0, PUSH_CONSTANTS_SIZE)
}) })
.SetDescriptorSetLayouts(DescriptorSetLayouts) .SetDescriptorSetLayouts(DescriptorSetLayouts)
.SetDepthClamp(VkCtx.GetDevice().getDepthClamp()) .SetDepthClamp(false)
.CullMode(VK_CULL_MODE_FRONT_BIT) .CullMode(VK_CULL_MODE_FRONT_BIT)
.DepthBuffer(VK_COMPARE_OP_LESS_OR_EQUAL); .DepthBuffer(VK_COMPARE_OP_LESS_OR_EQUAL);
@ -366,7 +375,7 @@ public class ShadowRenderer {
try (var stack = MemoryStack.stackPush()) { try (var stack = MemoryStack.stackPush()) {
IScene scene = engCtx.scene(); IScene scene = engCtx.scene();
ShadowUtils.updateCascadeShadows(cascadeShadows[currentFrame], scene); boolean validCascades = ShadowUtils.updateCascadeShadows(cascadeShadows[currentFrame], scene);
VkCommandBuffer cmdHandle = cmdBuffer.GetVulkanCommandBuffer(); VkCommandBuffer cmdHandle = cmdBuffer.GetVulkanCommandBuffer();
@ -384,6 +393,10 @@ public class ShadowRenderer {
VK_IMAGE_ASPECT_DEPTH_BIT); VK_IMAGE_ASPECT_DEPTH_BIT);
vkCmdBeginRendering(cmdHandle, renderingInfo); vkCmdBeginRendering(cmdHandle, renderingInfo);
if (!validCascades) {
vkCmdEndRendering(cmdHandle);
return;
}
vkCmdBindPipeline(cmdHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.GetVulkanPipeline()); vkCmdBindPipeline(cmdHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.GetVulkanPipeline());
@ -451,7 +464,12 @@ public class ShadowRenderer {
.put(3, descAllocator.GetDescriptorSet(VoxelWorldManager.Resources().faceGraphicsDescriptorId()).GetVkDescriptorSet()); .put(3, descAllocator.GetDescriptorSet(VoxelWorldManager.Resources().faceGraphicsDescriptorId()).GetVkDescriptorSet());
vkCmdBindPipeline(cmdHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelinePacked.GetVulkanPipeline()); vkCmdBindPipeline(cmdHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelinePacked.GetVulkanPipeline());
vkCmdBindDescriptorSets(cmdHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelinePacked.GetVulkanPipelineLayout(),0,descriptorSets,null); 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); 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.Camera;
import net.halbear.Terrain4J.EngineCore.Main.Scene.ILight; import net.halbear.Terrain4J.EngineCore.Main.Scene.ILight;
import net.halbear.Terrain4J.EngineCore.Main.Scene.IScene; 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 net.halbear.Terrain4J.EngineCore.RenderingAPI.Universal.Projection.Project3D;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Vector3f; import org.joml.Vector3f;
import org.joml.Vector4f;
import java.util.List; import java.util.List;
@ -21,11 +19,10 @@ public class ShadowUtils {
private ShadowUtils() { private ShadowUtils() {
} }
public static void updateCascadeShadows(CascadeShadows cascadeShadows, IScene scene) { public static boolean updateCascadeShadows(CascadeShadows cascadeShadows, IScene scene) {
Camera camera = scene.GetCamera(); Camera camera = scene.GetCamera();
Matrix4f viewMatrix = camera.GetViewMatrix(); Matrix4f viewMatrix = camera.GetViewMatrix();
Project3D projection = scene.GetProjection().GetNormalFarPlanes(); Project3D projection = scene.GetProjection();
Matrix4f projMatrix = projection.GetProjectionMatrix();
ILight[] lights = scene.GetLightingManager().GetLights(); ILight[] lights = scene.GetLightingManager().GetLights();
int numLights = lights.length; int numLights = lights.length;
ILight dirLight = null; ILight dirLight = null;
@ -36,114 +33,112 @@ public class ShadowUtils {
} }
} }
if (dirLight == null) { if (dirLight == null) {
throw new RuntimeException("Could not find directional light"); return disableCascades(cascadeShadows.GetCascadeData());
} }
Vector3f lightPos = dirLight.GetPosition(); Vector3f lightPos = dirLight.GetPosition();
float[] cascadeSplits = new float[SceneLightingManager.SHADOW_MAP_CASCADE_COUNT]; return updateCascadeShadows(cascadeShadows, viewMatrix, projection, lightPos,
EngineConfig.getInstance().MaxShadowDistance(), EngineConfig.getInstance().GetShadowMapSize());
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;
static boolean updateCascadeShadows(CascadeShadows cascadeShadows, Matrix4f viewMatrix, Project3D projection,
Vector3f lightPos, float maxShadowDistance, int shadowMapSize) {
List<CascadeData> cascadeDataList = cascadeShadows.GetCascadeData(); List<CascadeData> cascadeDataList = cascadeShadows.GetCascadeData();
int numCascades = cascadeDataList.size(); int numCascades = cascadeDataList.size();
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);
}
// Calculate split depths based on view camera frustum double lightLength = Math.sqrt((double) lightPos.x * lightPos.x + (double) lightPos.y * lightPos.y
// Based on method presented in https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html + (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++) { for (int i = 0; i < numCascades; i++) {
float p = (i + 1) / (float) (SceneLightingManager.SHADOW_MAP_CASCADE_COUNT); double p = (i + 1.0) / numCascades;
float log = (float) (minZ * java.lang.Math.pow(ratio, p)); double log = nearClip * Math.pow((double) farClip / nearClip, p);
float uniform = minZ + range * p; double uniform = nearClip + ((double) farClip - nearClip) * p;
float d = LAMBDA * (log - uniform) + uniform; float splitDist = i == numCascades - 1 ? farClip : (float) (LAMBDA * (log - uniform) + uniform);
cascadeSplits[i] = (d - nearClip) / clipRange; if (splitDist <= lastSplitDist) {
return disableCascades(cascadeDataList);
} }
float lastSplitDist = 0.0f; Vector3f minExtents = new Vector3f(Float.POSITIVE_INFINITY);
for (int i = 0; i < numCascades; i++) { Vector3f maxExtents = new Vector3f(Float.NEGATIVE_INFINITY);
float splitDist = cascadeSplits[i]; Vector3f corner = new Vector3f();
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();
for (int j = 0; j < 8; j++) { for (int j = 0; j < 8; j++) {
Vector4f invCorner = new Vector4f(frustumCorners[j], 1.0f).mul(invCam); corner.set(rays[j & 3]).mul(j < 4 ? lastSplitDist : splitDist);
frustumCorners[j] = new Vector3f(invCorner.x, invCorner.y, invCorner.z).div(invCorner.w); viewToLight.transformPosition(corner);
if (!corner.isFinite()) {
return disableCascades(cascadeDataList);
}
minExtents.min(corner);
maxExtents.max(corner);
} }
for (int j = 0; j < 4; j++) { // Tight light-space XY bounds; reserve two texels per edge for snapping and the 3x3 filter.
var dist = new Vector3f(frustumCorners[j + 4]).sub(frustumCorners[j]); float width = paddedExtent(maxExtents.x - minExtents.x, shadowMapSize);
frustumCorners[j + 4] = new Vector3f(frustumCorners[j]).add(new Vector3f(dist).mul(splitDist)); float height = paddedExtent(maxExtents.y - minExtents.y, shadowMapSize);
frustumCorners[j] = new Vector3f(frustumCorners[j]).add(new Vector3f(dist).mul(lastSplitDist)); 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 cascadeData = cascadeDataList.get(i);
cascadeData.SetSplitDistance((nearClip + splitDist * clipRange) * -1.0f); cascadeData.SetSplitDistance(-splitDist);
cascadeData.SetProjectionViewMatrix(lightOrthoMatrix.mul(lightViewMatrix)); cascadeData.SetProjectionViewMatrix(lightOrthoMatrix);
lastSplitDist = splitDist;
lastSplitDist = cascadeSplits[i];
} }
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){ public static byte[] CompileShader(String ShaderCode, int ShaderType, boolean HLSLshader){
long Compiler = 0; long Compiler = 0;
long Options = 0; long Options = 0;
long CompilationResult = 0;
byte[] CompiledShader; byte[] CompiledShader;
try{ try{
Compiler = Shaderc.shaderc_compiler_initialize(); Compiler = Shaderc.shaderc_compiler_initialize();
@ -22,9 +23,11 @@ public class ShaderCompiler {
if(EngineConfig.getInstance().DebugShaders()){ if(EngineConfig.getInstance().DebugShaders()){
Shaderc.shaderc_compile_options_set_generate_debug_info(Options); Shaderc.shaderc_compile_options_set_generate_debug_info(Options);
Shaderc.shaderc_compile_options_set_optimization_level(Options,0); 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); 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, Compiler,
ShaderCode, ShaderCode,
ShaderType, ShaderType,
@ -41,6 +44,7 @@ public class ShaderCompiler {
CompiledShader = new byte[buffer.remaining()]; CompiledShader = new byte[buffer.remaining()];
buffer.get(CompiledShader); buffer.get(CompiledShader);
} finally{ } finally{
if (CompilationResult != 0) Shaderc.shaderc_result_release(CompilationResult);
Shaderc.shaderc_compile_options_release(Options); Shaderc.shaderc_compile_options_release(Options);
Shaderc.shaderc_compiler_release(Compiler); Shaderc.shaderc_compiler_release(Compiler);
} }
@ -52,11 +56,15 @@ public class ShaderCompiler {
try{ try{
var glslFile = new File(ShaderFile); var glslFile = new File(ShaderFile);
var spvFile = new File(ShaderFile + ".spv"); 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()); Logger.debug("Compiling new [{}] shader from [{}] source",spvFile.getPath(),glslFile.getPath());
var ShaderCode = new String(Files.readAllBytes(glslFile.toPath())); var ShaderCode = new String(Files.readAllBytes(glslFile.toPath()));
CompiledShader = CompileShader(ShaderCode, ShaderType, false); CompiledShader = CompileShader(ShaderCode, ShaderType, false);
Files.write(spvFile.toPath(), CompiledShader); Files.write(spvFile.toPath(), CompiledShader);
Files.writeString(optionsFile.toPath(), options);
} else{ } else{
Logger.debug("Loading Compiled Shader [{}]",spvFile.getPath()); 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.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils;
import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.vulkan.VkShaderModuleCreateInfo; import org.lwjgl.vulkan.VkShaderModuleCreateInfo;
import static org.lwjgl.vulkan.VK13.vkCreateShaderModule; import static org.lwjgl.vulkan.VK13.vkCreateShaderModule;
import static org.lwjgl.vulkan.VK13.vkDestroyShaderModule; import static org.lwjgl.vulkan.VK13.vkDestroyShaderModule;
@ -35,17 +36,26 @@ public class ShaderModule {
} }
private static long CreateShaderModule(VulkanContext VkCtx, byte[] Code){ private static long CreateShaderModule(VulkanContext VkCtx, byte[] Code){
ByteBuffer CodePointer = CreateCodeBuffer(Code);
try(var MemStack = MemoryStack.stackPush()){ try(var MemStack = MemoryStack.stackPush()){
ByteBuffer CodePointer = MemStack.malloc(Code.length).put(0,Code);
var ShaderModuleCreateInfo = VkShaderModuleCreateInfo.calloc(MemStack) var ShaderModuleCreateInfo = VkShaderModuleCreateInfo.calloc(MemStack)
.sType$Default() .sType$Default()
.pCode(CodePointer); .pCode(CodePointer);
LongBuffer LongPoiner = MemStack.mallocLong(1); LongBuffer LongPoiner = MemStack.mallocLong(1);
VulkanUtils.vkCheck(vkCreateShaderModule(VkCtx.GetDevice().FetchVulkanDevice(), ShaderModuleCreateInfo, null, LongPoiner),"Failed to create a new Shader Module"); VulkanUtils.vkCheck(vkCreateShaderModule(VkCtx.GetDevice().FetchVulkanDevice(), ShaderModuleCreateInfo, null, LongPoiner),"Failed to create a new Shader Module");
return LongPoiner.get(0); 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 VkSpecializationInfo GetSpecializationInfo(){return SpecializationInfo;}
public void CleanUp(VulkanContext VkCtx){ public void CleanUp(VulkanContext VkCtx){

View file

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

View file

@ -11,7 +11,17 @@ Renderer=1
RenderingAPI=Vulkan RenderingAPI=Vulkan
RequestedImages=3 RequestedImages=3
ShaderRecompiling=true ShaderRecompiling=true
ShadowMapSize=512 ShadowMapSize=1024
max_shadow_distance=96
ssao_scale=0.5
ssao_samples=16
ssr_scale=0.5
ssr_steps=24
bloom_scale=0.25
bloom_passes=2
voxel_chunk_radius=32
voxel_generations_per_frame=1024
voxel_face_pool_mib=2048
Software_Icon=ProgramIcon.png Software_Icon=ProgramIcon.png
Software_Icon_Path=/WindowResources/Icon/ Software_Icon_Path=/WindowResources/Icon/
Software_Title=Terrain4J Game Engine Software_Title=Terrain4J Game Engine
@ -20,8 +30,8 @@ cap_fast_to_tickrate=true
cap_main_to_tickrate=true cap_main_to_tickrate=true
cap_master_to_tickrate=true cap_master_to_tickrate=true
compatibility_mode=false compatibility_mode=false
display_height=480 display_height=1080
display_width=640 display_width=1920
fast_thread_tickrate=240 fast_thread_tickrate=240
field_of_view=60.0f field_of_view=60.0f
frame_accuracy=0 frame_accuracy=0