Jarvis, Optimise my shit
This commit is contained in:
parent
d1abde919d
commit
9424b29d0a
50 changed files with 1469 additions and 1232 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -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/
|
||||||
|
|
|
||||||
10
gradle/renderer-validation.init.gradle
Normal file
10
gradle/renderer-validation.init.gradle
Normal 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.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.4 KiB |
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
29
resources/EngineResources/shaders/bloom_extract_frag.glsl
Normal file
29
resources/EngineResources/shaders/bloom_extract_frag.glsl
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -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);
|
const float weight[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
|
||||||
|
|
||||||
vec3 safeBloomSample(vec2 uv)
|
vec3 safeBloomSample(vec2 uv) {
|
||||||
{
|
return max(texture(bloomImage, uv).rgb, vec3(0.0));
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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){
|
|
||||||
return color = vec4(pow(color.rgb,vec3(GAMMA_CONST)),color.a);
|
|
||||||
}
|
|
||||||
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) {
|
vec4 msaa(int sampleCount, sampler2DMS textureIn, vec2 TextCoord) {
|
||||||
ivec2 pixelCoords = ivec2(TextCoord * textureSize(textureIn));
|
ivec2 size = textureSize(textureIn);
|
||||||
|
ivec2 pixelCoords = clamp(ivec2(TextCoord * vec2(size)), ivec2(0), size - 1);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vec3 bloomAt(ivec2 pixel, ivec2 size) {
|
||||||
|
return texelFetch(bloomImage, clamp(pixel, ivec2(0), size - 1), 0).rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 upsampleBloom(vec2 uv) {
|
||||||
|
ivec2 size = textureSize(bloomImage, 0);
|
||||||
|
vec2 pixel = uv * vec2(size) - 0.5;
|
||||||
|
ivec2 base = ivec2(floor(pixel));
|
||||||
|
vec2 blend = fract(pixel);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
ivec2 pixelCoords = ivec2(inTextCoord * textureSize(inputTexture));
|
vec4 sceneColor = msaa(textureSamples(inputTexture), inputTexture, inTextCoord);
|
||||||
|
vec3 hdrColour = max(sceneColor.rgb + upsampleBloom(inTextCoord), vec3(0.0));
|
||||||
outFragColor = texelFetch(inputTexture, pixelCoords, 0);
|
vec3 result = vec3(1.0) - exp(-hdrColour * config.Exposure);
|
||||||
|
outFragColor = vec4(pow(result, vec3(1.0 / config.GAMMA_CONST)), sceneColor.a);
|
||||||
if(USE_AA == 0){
|
|
||||||
outFragColor = texelFetch(inputTexture,pixelCoords,0);
|
|
||||||
}
|
|
||||||
if(USE_AA == 1){
|
|
||||||
outFragColor = texelFetch(inputTexture,pixelCoords,0);
|
|
||||||
}
|
|
||||||
if(USE_AA == 2){
|
|
||||||
outFragColor = msaa(2,inputTexture,inTextCoord);
|
|
||||||
}
|
|
||||||
if(USE_AA == 3){
|
|
||||||
outFragColor = msaa(4,inputTexture,inTextCoord);
|
|
||||||
}
|
|
||||||
if(USE_AA == 4){
|
|
||||||
outFragColor = msaa(8,inputTexture,inTextCoord);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vec3 bloomAt(ivec2 pixel, ivec2 size) {
|
||||||
|
return texelFetch(bloomImage, clamp(pixel, ivec2(0), size - 1), 0).rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 upsampleBloom(vec2 uv) {
|
||||||
|
ivec2 size = textureSize(bloomImage, 0);
|
||||||
|
vec2 pixel = uv * vec2(size) - 0.5;
|
||||||
|
ivec2 base = ivec2(floor(pixel));
|
||||||
|
vec2 blend = fract(pixel);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
void main(){
|
void main(){
|
||||||
|
vec4 sceneColor = USE_AA == 1 ? fxaa(inputTexture, inTextCoord) : texture(inputTexture, inTextCoord);
|
||||||
if(USE_AA == 1){
|
vec3 hdrColour = max(sceneColor.rgb + upsampleBloom(inTextCoord), vec3(0.0));
|
||||||
outFragColor = fxaa(inputTexture, inTextCoord);
|
vec3 result = vec3(1.0) - exp(-hdrColour * config.Exposure);
|
||||||
}
|
outFragColor = vec4(pow(result, vec3(1.0 / config.GAMMA_CONST)), sceneColor.a);
|
||||||
else if(USE_AA == 2){
|
|
||||||
|
|
||||||
outFragColor = texture(inputTexture, inTextCoord);
|
|
||||||
}
|
|
||||||
else if(USE_AA == 3){
|
|
||||||
|
|
||||||
outFragColor = texture(inputTexture, inTextCoord);
|
|
||||||
}
|
|
||||||
else if(USE_AA == 4){
|
|
||||||
|
|
||||||
outFragColor = texture(inputTexture, inTextCoord);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
34
resources/EngineResources/shaders/ssr_composite_frag.glsl
Normal file
34
resources/EngineResources/shaders/ssr_composite_frag.glsl
Normal 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);
|
||||||
|
}
|
||||||
1
resources/ProgramResources/ServerCache/UUIDs.json
Normal file
1
resources/ProgramResources/ServerCache/UUIDs.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
[]
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
);
|
|
||||||
|
|
||||||
|
long resetLayout = resources.resetPipeline().GetVulkanPipelineLayout();
|
||||||
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resources.resetPipeline().GetVulkanPipeline());
|
||||||
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resetLayout, 0, resetDescriptorSet, null);
|
||||||
|
for (int job = 0; job < batch.positions.size(); job++) {
|
||||||
|
putJobConstants(pushConstants, batch.positions.get(job), job);
|
||||||
|
vkCmdPushConstants(cmd, resetLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, pushConstants);
|
||||||
|
vkCmdDispatch(cmd, 1, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
VulkanUtils.BufferBarrier(stack, cmd, resources.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());
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resources.voxelGenerationPipeline().GetVulkanPipeline());
|
||||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.voxelGenerationPipeline().GetVulkanPipelineLayout(),0, voxelDescriptorSet,null );
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, voxelLayout, 0, voxelDescriptorSet, null);
|
||||||
vkCmdPushConstants( cmd, resources.voxelGenerationPipeline().GetVulkanPipelineLayout(),VK_SHADER_STAGE_COMPUTE_BIT,0, pushConstants );
|
for (int job = 0; job < batch.positions.size(); job++) {
|
||||||
vkCmdDispatch(cmd, DISPATCH_SIZE, DISPATCH_SIZE, DISPATCH_SIZE);
|
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().GetBuffer(),
|
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);
|
||||||
|
|
||||||
|
long meshLayout = resources.meshGenerationPipeline().GetVulkanPipelineLayout();
|
||||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resources.meshGenerationPipeline().GetVulkanPipeline());
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resources.meshGenerationPipeline().GetVulkanPipeline());
|
||||||
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, meshLayout, 0, meshDescriptorSet, null);
|
||||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.meshGenerationPipeline().GetVulkanPipelineLayout(),0, meshDescriptorSet,null );
|
for (int job = 0; job < batch.positions.size(); job++) {
|
||||||
|
putJobConstants(pushConstants, batch.positions.get(job), job);
|
||||||
vkCmdPushConstants( cmd, resources.meshGenerationPipeline().GetVulkanPipelineLayout(),VK_SHADER_STAGE_COMPUTE_BIT,0, pushConstants );
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
}
|
||||||
|
facePoolSize -= facePoolSize % FACE_RECORD_SIZE_BYTES;
|
||||||
|
ranges = new FaceRangeAllocator(Math.toIntExact(facePoolSize / FACE_RECORD_SIZE_BYTES));
|
||||||
|
facePool = new VulkanBuffer(VkCtx, facePoolSize,
|
||||||
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||||
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0);
|
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0);
|
||||||
|
for (int frame = 0; frame < drawBuffers.length; frame++) {
|
||||||
|
for (int pass = 0; pass < 2; pass++) {
|
||||||
indirectPool = new VulkanBuffer(VkCtx,indirectPoolSize,VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
drawBuffers[frame][pass] = new VulkanBuffer(VkCtx, (long) maxChunks * DRAW_INDIRECT_COMMAND_SIZE,
|
||||||
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,0,0);
|
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO,
|
||||||
|
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
|
||||||
for (int i = 0; i < maxChunks; i++) {
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||||
freeSlots.add(i);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
textureSampler = new TextureSampler(VkCtx, new TextureSamplerInfo(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||||
|
VK_BORDER_COLOR_INT_OPAQUE_BLACK, 1, false));
|
||||||
|
AttachmentDescriptorSetLayout = new DescriptorSetLayout(VkCtx,
|
||||||
|
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, 1, VK_SHADER_STAGE_FRAGMENT_BIT));
|
||||||
|
MultiAttachmentDescriptorSetLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{
|
||||||
|
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, 1, VK_SHADER_STAGE_FRAGMENT_BIT),
|
||||||
|
new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, 1, VK_SHADER_STAGE_FRAGMENT_BIT)});
|
||||||
|
UpdateDescriptors(VkCtx, SrcAttachment);
|
||||||
|
|
||||||
ColourAttachment = CreateColourAttachment(VkCtx);
|
specConstants = new SpecializationConstants();
|
||||||
BloomPingAttachment = CreateColourAttachment(VkCtx);
|
boolean multisampled = !EngineConfig.getInstance().DeferredRendering() && EngineConfig.getInstance().RenderAA() > 1;
|
||||||
BloomPongAttachment = CreateColourAttachment(VkCtx);
|
pipeline = CreatePipeline(VkCtx, multisampled ? MULTI_PASS_FRAGMENT_SHADER_FILE_GLSL : SINGLE_PASS_FRAGMENT_SHADER_FILE_GLSL,
|
||||||
FinalAttachment = CreateColourAttachment(VkCtx);
|
MultiAttachmentDescriptorSetLayout, specConstants.GetSpecializationInfo());
|
||||||
|
bloomPipeline = CreatePipeline(VkCtx, BLOOM_FRAGMENT_SHADER_FILE_GLSL, AttachmentDescriptorSetLayout, null);
|
||||||
|
extractionPipeline = CreatePipeline(VkCtx, multisampled ? MULTI_EXTRACT_FRAGMENT_SHADER_FILE_GLSL : EXTRACT_FRAGMENT_SHADER_FILE_GLSL,
|
||||||
|
AttachmentDescriptorSetLayout, null);
|
||||||
|
}
|
||||||
|
|
||||||
List<Attachment> attachments = new ArrayList<>();
|
static int ScaledExtent(int size, float scale) {
|
||||||
attachments.add(ColourAttachment);
|
return Math.max(1, Math.round(size * scale));
|
||||||
attachments.add(BloomPingAttachment);
|
}
|
||||||
ColourAttachmentInfo = CreateColourAttachmentInfos(attachments,ClearValueColour);
|
|
||||||
|
static int ValidateBloomPasses(int passes) {
|
||||||
|
if (passes < 0 || (passes & 1) != 0) {
|
||||||
|
throw new IllegalArgumentException("Bloom requires horizontal/vertical pass pairs, or zero to disable it");
|
||||||
|
}
|
||||||
|
return 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);
|
BloomPingAttachmentInfo = CreateColourAttachmentInfo(BloomPingAttachment, ClearValueColour);
|
||||||
|
if (bloomPasses == 0) BloomPingAttachmentInfo.get(0).loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR);
|
||||||
BloomPongAttachmentInfo = CreateColourAttachmentInfo(BloomPongAttachment, ClearValueColour);
|
BloomPongAttachmentInfo = CreateColourAttachmentInfo(BloomPongAttachment, ClearValueColour);
|
||||||
FinalOutputInfo = CreateColourAttachmentInfo(FinalAttachment, ClearValueColour);
|
FinalOutputInfo = CreateColourAttachmentInfo(FinalAttachment, ClearValueColour);
|
||||||
RenderingInfo = CreateRenderInfo(ColourAttachment,ColourAttachmentInfo);
|
|
||||||
BloomPingRenderingInfo = CreateRenderInfo(BloomPingAttachment, BloomPingAttachmentInfo);
|
BloomPingRenderingInfo = CreateRenderInfo(BloomPingAttachment, BloomPingAttachmentInfo);
|
||||||
BloomPongRenderingInfo = CreateRenderInfo(BloomPongAttachment, BloomPongAttachmentInfo);
|
BloomPongRenderingInfo = CreateRenderInfo(BloomPongAttachment, BloomPongAttachmentInfo);
|
||||||
SingleRenderingInfo = CreateRenderInfo(FinalAttachment, FinalOutputInfo);
|
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);
|
private static Attachment CreateColourAttachment(VulkanContext VkCtx, int width, int height){
|
||||||
AttachmentDescriptorSetLayout = new DescriptorSetLayout(VkCtx, LayoutInfo);
|
return new Attachment(VkCtx, width, height, COLOUR_FORMAT, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
|
||||||
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();
|
|
||||||
ShaderModule[] shaderModules = CreateShaderModules(VkCtx, specConstants);
|
|
||||||
pipeline = CreatePipeline(VkCtx, shaderModules, new DescriptorSetLayout[]{AttachmentDescriptorSetLayout, FragmentUniformDescriptorSetLayout});
|
|
||||||
Arrays.asList(shaderModules).forEach(shader->shader.CleanUp(VkCtx));
|
|
||||||
shaderModules = CreateBloomShaderModules(VkCtx, specConstants);
|
|
||||||
bloomPipeline = CreateSingleOutputPipeline(VkCtx, shaderModules, new DescriptorSetLayout[]{MultiAttachmentDescriptorSetLayout[0], FragmentUniformDescriptorSetLayout});
|
|
||||||
Logger.debug("Post Process Renderer Pipeline -> [{}]",pipeline.GetVulkanPipeline());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static VkRenderingAttachmentInfo.Buffer CreateColourAttachmentInfos(List<Attachment> attachments, VkClearValue ClearValue){
|
|
||||||
int numAttachments = attachments.size();
|
|
||||||
VkRenderingAttachmentInfo.Buffer result = VkRenderingAttachmentInfo.calloc(numAttachments);
|
|
||||||
for (int i = 0; i < numAttachments; ++i) {
|
|
||||||
result.get(i)
|
|
||||||
.sType$Default()
|
|
||||||
.imageView(attachments.get(i).GetVkImageView().GetVulkanImageView())
|
|
||||||
.imageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)
|
|
||||||
.loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR)
|
|
||||||
.storeOp(VK_ATTACHMENT_STORE_OP_STORE)
|
|
||||||
.clearValue(ClearValue);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Attachment CreateColourAttachment(VulkanContext VkCtx){
|
|
||||||
SwapChain swapChain = VkCtx.GetSwapChain();
|
|
||||||
VkExtent2D SwapChainExtent = swapChain.GetSwapChainExtent();
|
|
||||||
return new Attachment(VkCtx,SwapChainExtent.width(),SwapChainExtent.height(),COLOUR_FORMAT,VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private 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);
|
||||||
int dstOldLayout = writePong && !bloomPongHasBeenWritten
|
Draw(VkCtx, MemStack, CommandHandle, bloomPipeline, dstBloomAttachment, dstRenderingInfo, srcDescriptorId, constants);
|
||||||
? VK_IMAGE_LAYOUT_UNDEFINED
|
EndWrite(MemStack, CommandHandle, dstBloomAttachment);
|
||||||
: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
||||||
|
|
||||||
VulkanUtils.ImageBarrier(MemStack,CommandHandle,dstBloomAttachment.GetVkImage().getVulkanImage(),
|
|
||||||
dstOldLayout,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
|
||||||
VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
|
|
||||||
VK_ACCESS_2_SHADER_READ_BIT,VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
|
|
||||||
VK_IMAGE_ASPECT_COLOR_BIT);
|
|
||||||
|
|
||||||
vkCmdBeginRendering(CommandHandle,dstRenderingInfo);
|
|
||||||
vkCmdBindPipeline(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,bloomPipeline.GetVulkanPipeline());
|
|
||||||
|
|
||||||
vkCmdSetViewport(CommandHandle,0,Viewport);
|
|
||||||
vkCmdSetScissor(CommandHandle,0,Scissor);
|
|
||||||
|
|
||||||
LongBuffer BloomDescriptorSets = MemStack.mallocLong(2)
|
|
||||||
.put(0,descriptorAllocator.GetDescriptorSet(srcDescriptorId).GetVkDescriptorSet())
|
|
||||||
.put(1,descriptorAllocator.GetDescriptorSet(bloomPropertiesDescriptorId).GetVkDescriptorSet());
|
|
||||||
vkCmdBindDescriptorSets(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,bloomPipeline.GetVulkanPipelineLayout(),0,BloomDescriptorSets,null);
|
|
||||||
|
|
||||||
vkCmdDraw(CommandHandle,3,1,0,0);
|
|
||||||
vkCmdEndRendering(CommandHandle);
|
|
||||||
|
|
||||||
VulkanUtils.ImageBarrier(MemStack,CommandHandle,dstBloomAttachment.GetVkImage().getVulkanImage(),
|
|
||||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
|
||||||
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,
|
|
||||||
VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,VK_ACCESS_2_SHADER_READ_BIT,
|
|
||||||
VK_IMAGE_ASPECT_COLOR_BIT);
|
|
||||||
|
|
||||||
if(writePong){
|
|
||||||
bloomPongHasBeenWritten = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The UI/swapchain owns the final layout. Discard contents, not prior read/write dependencies.
|
||||||
VulkanUtils.ImageBarrier(MemStack, CommandHandle, FinalAttachment.GetVkImage().getVulkanImage(),
|
VulkanUtils.ImageBarrier(MemStack, CommandHandle, FinalAttachment.GetVkImage().getVulkanImage(),
|
||||||
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
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_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||||
VK_ACCESS_2_NONE,VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
|
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||||
VK_IMAGE_ASPECT_COLOR_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(CommandHandle,SingleRenderingInfo);
|
Draw(VkCtx, MemStack, CommandHandle, pipeline, FinalAttachment, SingleRenderingInfo, DESCRIPTOR_ID_BLOOM_IMAGE_ATTACHMENT, constants);
|
||||||
vkCmdBindPipeline(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,bloomPipeline.GetVulkanPipeline());
|
attachmentsInitialized = true;
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
if (descSet == null) {
|
||||||
|
descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet(
|
||||||
vkCtx.GetDevice(), DESC_ID_SSAO_ATTACHMENTS, ssaoAttachmentDescriptorLayout);
|
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);
|
||||||
|
if (descSet == null) {
|
||||||
|
descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet(
|
||||||
vkCtx.GetDevice(), DESC_ID_SSAO_BLUR_INPUT, blurDescriptorLayout);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
lastSplitDist = cascadeSplits[i];
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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){
|
||||||
|
|
|
||||||
|
|
@ -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;}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue