Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
170af2a4e9 | ||
|
|
95d1497169 | ||
|
|
4bf87bd91b |
@@ -8,3 +8,6 @@ lightkeeper/assets_runtime
|
|||||||
.idea/**
|
.idea/**
|
||||||
|
|
||||||
build/**
|
build/**
|
||||||
|
|
||||||
|
# Claude Code
|
||||||
|
CLAUDE.md
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ set(SRC_FILES
|
|||||||
"src/Event.cpp"
|
"src/Event.cpp"
|
||||||
|
|
||||||
"src/Components/Animator.cpp"
|
"src/Components/Animator.cpp"
|
||||||
|
"src/Components/LightComponent.cpp"
|
||||||
"src/Components/MeshRendererComponent.cpp"
|
"src/Components/MeshRendererComponent.cpp"
|
||||||
"src/Components/Rotator.cpp"
|
"src/Components/Rotator.cpp"
|
||||||
"src/Components/Spinner.cpp"
|
"src/Components/Spinner.cpp"
|
||||||
@@ -32,6 +33,7 @@ set(SRC_FILES
|
|||||||
"src/Graphics/Swapchain.cpp"
|
"src/Graphics/Swapchain.cpp"
|
||||||
"src/Graphics/Util.cpp"
|
"src/Graphics/Util.cpp"
|
||||||
"src/Graphics/RenderResources.cpp"
|
"src/Graphics/RenderResources.cpp"
|
||||||
|
"src/Graphics/ResourcesInspector.cpp"
|
||||||
|
|
||||||
"src/Graphics/Managers/VulkanInstanceManager.cpp"
|
"src/Graphics/Managers/VulkanInstanceManager.cpp"
|
||||||
"src/Graphics/Managers/MemoryManager.cpp"
|
"src/Graphics/Managers/MemoryManager.cpp"
|
||||||
@@ -48,6 +50,8 @@ set(SRC_FILES
|
|||||||
"src/Graphics/Pipelines/LineRenderingPass.cpp"
|
"src/Graphics/Pipelines/LineRenderingPass.cpp"
|
||||||
"src/Graphics/Pipelines/QuadRendererPass.cpp"
|
"src/Graphics/Pipelines/QuadRendererPass.cpp"
|
||||||
"src/Graphics/Pipelines/SkyboxPipeline.cpp"
|
"src/Graphics/Pipelines/SkyboxPipeline.cpp"
|
||||||
|
"src/Graphics/Pipelines/LightCullingPipeline.cpp"
|
||||||
|
"src/Graphics/Pipelines/ShadowMapPipeline.cpp"
|
||||||
"src/Graphics/Pipelines/SkinningPipeline.cpp"
|
"src/Graphics/Pipelines/SkinningPipeline.cpp"
|
||||||
"src/Graphics/Pipelines/ImguiPass.cpp"
|
"src/Graphics/Pipelines/ImguiPass.cpp"
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ vec4 sampleTextureCubeLinear(uint texID, vec3 p) {
|
|||||||
return texture(nonuniformEXT(samplerCube(textureCubes[texID], samplers[LINEAR_SAMPLER_ID])), p);
|
return texture(nonuniformEXT(samplerCube(textureCubes[texID], samplers[LINEAR_SAMPLER_ID])), p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float sampleTexture2DShadow(uint texID, vec3 p) {
|
||||||
|
return texture(nonuniformEXT(sampler2DShadow(textures[texID], samplers[SHADOW_SAMPLER_ID])), p);
|
||||||
|
}
|
||||||
|
|
||||||
float sampleTextureArrayShadow(uint texID, vec4 p) {
|
float sampleTextureArrayShadow(uint texID, vec4 p) {
|
||||||
return texture(nonuniformEXT(sampler2DArrayShadow(textureArrays[texID], samplers[SHADOW_SAMPLER_ID])), p);
|
return texture(nonuniformEXT(sampler2DArrayShadow(textureArrays[texID], samplers[SHADOW_SAMPLER_ID])), p);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
#version 460
|
||||||
|
|
||||||
|
#extension GL_GOOGLE_include_directive : require
|
||||||
|
#extension GL_EXT_buffer_reference : require
|
||||||
|
#extension GL_EXT_scalar_block_layout : require
|
||||||
|
|
||||||
|
#include "bindless.glsl"
|
||||||
|
#include "scene_data.glsl"
|
||||||
|
|
||||||
|
// Forward+ light culling.
|
||||||
|
// One workgroup per LIGHT_TILE_SIZE x LIGHT_TILE_SIZE screen tile:
|
||||||
|
// 1. find the min/max depth of the tile from the depth pre-pass
|
||||||
|
// 2. build the tile frustum in view space
|
||||||
|
// 3. test every light against it and write the visible ones to the tile's light list
|
||||||
|
|
||||||
|
layout (push_constant, scalar) uniform constants
|
||||||
|
{
|
||||||
|
SceneDataBuffer sceneData;
|
||||||
|
uvec2 screenSize;
|
||||||
|
uint depthTexID;
|
||||||
|
uint padding;
|
||||||
|
} pcs;
|
||||||
|
|
||||||
|
layout (local_size_x = LIGHT_TILE_SIZE, local_size_y = LIGHT_TILE_SIZE, local_size_z = 1) in;
|
||||||
|
|
||||||
|
#define THREADS_PER_TILE (LIGHT_TILE_SIZE * LIGHT_TILE_SIZE)
|
||||||
|
|
||||||
|
shared uint tileMinDepth;
|
||||||
|
shared uint tileMaxDepth;
|
||||||
|
shared uint tileLightCount;
|
||||||
|
|
||||||
|
// left, right, bottom, top side planes (view space, through the camera origin)
|
||||||
|
shared vec4 tilePlanes[4];
|
||||||
|
// view space distances along -Z
|
||||||
|
shared float tileNearDistance;
|
||||||
|
shared float tileFarDistance;
|
||||||
|
|
||||||
|
vec3 unprojectToView(vec2 ndc, float depth)
|
||||||
|
{
|
||||||
|
vec4 p = pcs.sceneData.invProj * vec4(ndc, depth, 1.0);
|
||||||
|
return p.xyz / p.w;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plane through the camera origin and two points, normal facing towards `inside`
|
||||||
|
vec4 makeSidePlane(vec3 a, vec3 b, vec3 inside)
|
||||||
|
{
|
||||||
|
vec3 n = normalize(cross(a, b));
|
||||||
|
if (dot(n, inside) < 0.0) {
|
||||||
|
n = -n;
|
||||||
|
}
|
||||||
|
return vec4(n, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sphereInsideTile(vec3 center, float radius)
|
||||||
|
{
|
||||||
|
float distance = -center.z;
|
||||||
|
if (distance + radius < tileNearDistance || distance - radius > tileFarDistance) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (dot(tilePlanes[i].xyz, center) + tilePlanes[i].w < -radius) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
uint localIndex = gl_LocalInvocationIndex;
|
||||||
|
uvec2 tileID = gl_WorkGroupID.xy;
|
||||||
|
uint tileIndex = tileID.y * pcs.sceneData.tileCountX + tileID.x;
|
||||||
|
uint tileOffset = tileIndex * LIGHT_TILE_STRIDE;
|
||||||
|
|
||||||
|
if (localIndex == 0) {
|
||||||
|
tileMinDepth = 0xFFFFFFFFu;
|
||||||
|
tileMaxDepth = 0u;
|
||||||
|
tileLightCount = 0u;
|
||||||
|
}
|
||||||
|
memoryBarrierShared();
|
||||||
|
barrier();
|
||||||
|
|
||||||
|
// 1. min / max depth
|
||||||
|
uvec2 pixel = gl_GlobalInvocationID.xy;
|
||||||
|
if (all(lessThan(pixel, pcs.screenSize))) {
|
||||||
|
float depth = texelFetch(
|
||||||
|
sampler2D(textures[pcs.depthTexID], samplers[NEAREST_SAMPLER_ID]),
|
||||||
|
ivec2(pixel),
|
||||||
|
0).r;
|
||||||
|
|
||||||
|
// depth == 1.0 is the cleared far plane (sky), nothing there receives light.
|
||||||
|
// Depth is always positive, so its bits can be compared as uints.
|
||||||
|
if (depth < 1.0) {
|
||||||
|
uint depthBits = floatBitsToUint(depth);
|
||||||
|
atomicMin(tileMinDepth, depthBits);
|
||||||
|
atomicMax(tileMaxDepth, depthBits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memoryBarrierShared();
|
||||||
|
barrier();
|
||||||
|
|
||||||
|
// 2. tile frustum
|
||||||
|
bool tileHasGeometry = tileMinDepth != 0xFFFFFFFFu;
|
||||||
|
|
||||||
|
if (localIndex == 0 && tileHasGeometry) {
|
||||||
|
vec2 screenSize = vec2(pcs.screenSize);
|
||||||
|
vec2 ndcMin = vec2(tileID * LIGHT_TILE_SIZE) / screenSize * 2.0 - 1.0;
|
||||||
|
vec2 ndcMax = vec2((tileID + 1) * LIGHT_TILE_SIZE) / screenSize * 2.0 - 1.0;
|
||||||
|
vec2 ndcCenter = (ndcMin + ndcMax) * 0.5;
|
||||||
|
|
||||||
|
vec3 corner00 = unprojectToView(vec2(ndcMin.x, ndcMin.y), 1.0);
|
||||||
|
vec3 corner10 = unprojectToView(vec2(ndcMax.x, ndcMin.y), 1.0);
|
||||||
|
vec3 corner11 = unprojectToView(vec2(ndcMax.x, ndcMax.y), 1.0);
|
||||||
|
vec3 corner01 = unprojectToView(vec2(ndcMin.x, ndcMax.y), 1.0);
|
||||||
|
vec3 center = unprojectToView(ndcCenter, 1.0);
|
||||||
|
|
||||||
|
tilePlanes[0] = makeSidePlane(corner01, corner00, center);
|
||||||
|
tilePlanes[1] = makeSidePlane(corner10, corner11, center);
|
||||||
|
tilePlanes[2] = makeSidePlane(corner00, corner10, center);
|
||||||
|
tilePlanes[3] = makeSidePlane(corner11, corner01, center);
|
||||||
|
|
||||||
|
tileNearDistance = -unprojectToView(ndcCenter, uintBitsToFloat(tileMinDepth)).z;
|
||||||
|
tileFarDistance = -unprojectToView(ndcCenter, uintBitsToFloat(tileMaxDepth)).z;
|
||||||
|
}
|
||||||
|
memoryBarrierShared();
|
||||||
|
barrier();
|
||||||
|
|
||||||
|
// 3. cull lights, every thread takes a slice of the light list
|
||||||
|
if (tileHasGeometry) {
|
||||||
|
for (uint i = localIndex; i < pcs.sceneData.numLights; i += THREADS_PER_TILE) {
|
||||||
|
Light light = pcs.sceneData.lights.data[i];
|
||||||
|
|
||||||
|
bool visible = light.type == TYPE_DIRECTIONAL_LIGHT;
|
||||||
|
if (!visible) {
|
||||||
|
vec3 viewPos = (pcs.sceneData.view * vec4(light.position, 1.0)).xyz;
|
||||||
|
visible = sphereInsideTile(viewPos, light.range);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
uint slot = atomicAdd(tileLightCount, 1u);
|
||||||
|
if (slot < MAX_LIGHTS_PER_TILE) {
|
||||||
|
pcs.sceneData.tileLightIndices.data[tileOffset + 1 + slot] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memoryBarrierShared();
|
||||||
|
barrier();
|
||||||
|
|
||||||
|
if (localIndex == 0) {
|
||||||
|
pcs.sceneData.tileLightIndices.data[tileOffset] = min(tileLightCount, uint(MAX_LIGHTS_PER_TILE));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#ifndef LIGHTING_GLSL
|
||||||
|
#define LIGHTING_GLSL
|
||||||
|
|
||||||
|
#include "lights.glsl"
|
||||||
|
|
||||||
|
#define PI 3.14159265359
|
||||||
|
|
||||||
|
// GGX / Trowbridge-Reitz normal distribution
|
||||||
|
float distributionGGX(float NoH, float roughness)
|
||||||
|
{
|
||||||
|
float a = roughness * roughness;
|
||||||
|
float a2 = a * a;
|
||||||
|
float d = NoH * NoH * (a2 - 1.0) + 1.0;
|
||||||
|
return a2 / (PI * d * d);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Smith-Schlick GGX, k remapped for direct lighting
|
||||||
|
float geometrySmithGGX(float NoV, float NoL, float roughness)
|
||||||
|
{
|
||||||
|
float r = roughness + 1.0;
|
||||||
|
float k = (r * r) / 8.0;
|
||||||
|
float ggxV = NoV / (NoV * (1.0 - k) + k);
|
||||||
|
float ggxL = NoL / (NoL * (1.0 - k) + k);
|
||||||
|
return ggxV * ggxL;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 fresnelSchlick(float VoH, vec3 f0)
|
||||||
|
{
|
||||||
|
return f0 + (1.0 - f0) * pow(1.0 - VoH, 5.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inverse square falloff, windowed so it reaches exactly 0 at the light range.
|
||||||
|
// The range is also the sphere the light culling uses, so no light gets cut off at a tile edge.
|
||||||
|
float calculateDistanceAttenuation(float distance, float range)
|
||||||
|
{
|
||||||
|
float window = clamp(1.0 - pow(distance / range, 4.0), 0.0, 1.0);
|
||||||
|
return (window * window) / max(distance * distance, 0.0001);
|
||||||
|
}
|
||||||
|
|
||||||
|
float calculateSpotAttenuation(Light light, vec3 l)
|
||||||
|
{
|
||||||
|
float cd = dot(normalize(light.direction), -l);
|
||||||
|
return smoothstep(light.spotOuterCos, light.spotInnerCos, cd);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 calculateLight(Light light, vec3 fragPos, vec3 n, vec3 v,
|
||||||
|
vec3 diffuseColor, float roughness, float metallic, vec3 f0, float occlusion)
|
||||||
|
{
|
||||||
|
vec3 l;
|
||||||
|
float attenuation = 1.0;
|
||||||
|
|
||||||
|
if (light.type == TYPE_DIRECTIONAL_LIGHT) {
|
||||||
|
l = normalize(-light.direction);
|
||||||
|
} else {
|
||||||
|
vec3 toLight = light.position - fragPos;
|
||||||
|
float distance = length(toLight);
|
||||||
|
l = toLight / max(distance, 0.0001);
|
||||||
|
|
||||||
|
attenuation = calculateDistanceAttenuation(distance, light.range);
|
||||||
|
if (light.type == TYPE_SPOT_LIGHT) {
|
||||||
|
attenuation *= calculateSpotAttenuation(light, l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float NoL = clamp(dot(n, l), 0.0, 1.0);
|
||||||
|
if (NoL <= 0.0 || attenuation <= 0.0) {
|
||||||
|
return vec3(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 h = normalize(v + l);
|
||||||
|
float NoV = clamp(dot(n, v), 0.0001, 1.0);
|
||||||
|
float NoH = clamp(dot(n, h), 0.0, 1.0);
|
||||||
|
float VoH = clamp(dot(v, h), 0.0, 1.0);
|
||||||
|
|
||||||
|
float D = distributionGGX(NoH, roughness);
|
||||||
|
float G = geometrySmithGGX(NoV, NoL, roughness);
|
||||||
|
vec3 F = fresnelSchlick(VoH, f0);
|
||||||
|
|
||||||
|
vec3 specular = (D * G * F) / (4.0 * NoV * NoL + 0.0001);
|
||||||
|
vec3 kd = (vec3(1.0) - F) * (1.0 - metallic);
|
||||||
|
vec3 diffuse = kd * diffuseColor / PI;
|
||||||
|
|
||||||
|
return (diffuse + specular) * light.color * light.intensity * attenuation * NoL * occlusion;
|
||||||
|
}
|
||||||
|
|
||||||
|
// blue -> green -> red, used to visualise how many lights a tile has
|
||||||
|
vec3 lightHeatmapColor(uint lightCount)
|
||||||
|
{
|
||||||
|
float t = clamp(float(lightCount) / 16.0, 0.0, 1.0);
|
||||||
|
if (t < 0.5) {
|
||||||
|
return mix(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0), t * 2.0);
|
||||||
|
}
|
||||||
|
return mix(vec3(0.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), (t - 0.5) * 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIGHTING_GLSL
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
#ifndef LIGHTS_GLSL
|
||||||
|
#define LIGHTS_GLSL
|
||||||
|
|
||||||
|
#extension GL_EXT_buffer_reference : require
|
||||||
|
|
||||||
|
#define TYPE_DIRECTIONAL_LIGHT 0
|
||||||
|
#define TYPE_POINT_LIGHT 1
|
||||||
|
#define TYPE_SPOT_LIGHT 2
|
||||||
|
|
||||||
|
// Forward+ tiling, must match LightCullingPipeline::TILE_SIZE / MAX_LIGHTS_PER_TILE
|
||||||
|
#define LIGHT_TILE_SIZE 16
|
||||||
|
#define MAX_LIGHTS_PER_TILE 255
|
||||||
|
// Every tile stores its light count first, followed by MAX_LIGHTS_PER_TILE light indices
|
||||||
|
#define LIGHT_TILE_STRIDE (MAX_LIGHTS_PER_TILE + 1)
|
||||||
|
|
||||||
|
// Must match GPULight in Light.h
|
||||||
|
struct Light {
|
||||||
|
vec3 position;
|
||||||
|
float range;
|
||||||
|
|
||||||
|
vec3 color;
|
||||||
|
float intensity;
|
||||||
|
|
||||||
|
// direction the light is pointing in (directional + spot lights)
|
||||||
|
vec3 direction;
|
||||||
|
uint type;
|
||||||
|
|
||||||
|
float spotInnerCos;
|
||||||
|
float spotOuterCos;
|
||||||
|
uint castShadows;
|
||||||
|
float padding;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout (buffer_reference, std430) readonly buffer LightsBuffer {
|
||||||
|
Light data[];
|
||||||
|
};
|
||||||
|
|
||||||
|
layout (buffer_reference, std430) buffer TileLightIndicesBuffer {
|
||||||
|
uint data[];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LIGHTS_GLSL
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
#include "bindless.glsl"
|
#include "bindless.glsl"
|
||||||
#include "scene_data.glsl"
|
#include "scene_data.glsl"
|
||||||
#include "mesh_pcs.glsl"
|
#include "mesh_pcs.glsl"
|
||||||
|
#include "lighting.glsl"
|
||||||
|
#include "shadows.glsl"
|
||||||
|
|
||||||
layout (location = 0) in vec3 inPos;
|
layout (location = 0) in vec3 inPos;
|
||||||
layout (location = 1) in vec2 inUV;
|
layout (location = 1) in vec2 inUV;
|
||||||
@@ -20,16 +22,13 @@ void main()
|
|||||||
uint samplerID = pcs.sceneData.materials.data[pcs.materialID].textureFilteringMode;
|
uint samplerID = pcs.sceneData.materials.data[pcs.materialID].textureFilteringMode;
|
||||||
|
|
||||||
vec4 diffuse = sampleTexture2D(material.diffuseTex, samplerID, inUV) * material.baseColor;
|
vec4 diffuse = sampleTexture2D(material.diffuseTex, samplerID, inUV) * material.baseColor;
|
||||||
outFragColor = diffuse;
|
vec3 baseColor = diffuse.rgb;
|
||||||
|
|
||||||
|
|
||||||
// if (diffuse.a < 0.1) {
|
// if (diffuse.a < 0.1) {
|
||||||
// discard;
|
// discard;
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
// vec3 baseColor = material.baseColor.rgb * diffuse.rgb;
|
vec3 normal = normalize(inNormal);
|
||||||
//
|
|
||||||
// vec3 normal = normalize(inNormal).rgb;
|
|
||||||
// if (inTangent != vec4(0.0)) {
|
// if (inTangent != vec4(0.0)) {
|
||||||
// // FIXME: sometimes Blender doesn't export tangents for some objects
|
// // FIXME: sometimes Blender doesn't export tangents for some objects
|
||||||
// // for some reason. When we will start computing tangents manually,
|
// // for some reason. When we will start computing tangents manually,
|
||||||
@@ -39,91 +38,74 @@ void main()
|
|||||||
// normal = inTBN * normalize(normal * 2.0 - 1.0);
|
// normal = inTBN * normalize(normal * 2.0 - 1.0);
|
||||||
// normal = normalize(normal);
|
// normal = normalize(normal);
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
// #ifdef PBR
|
|
||||||
// float metallicF = material.metallicRoughnessEmissive.r;
|
|
||||||
// float roughnessF = material.metallicRoughnessEmissive.g;
|
|
||||||
//
|
|
||||||
// vec4 metallicRoughness = sampleTexture2DLinear(material.metallicRoughnessTex, inUV);
|
|
||||||
//
|
|
||||||
// float roughness = roughnessF * metallicRoughness.g;
|
|
||||||
// // roughness *= roughness; // from perceptual to linear
|
|
||||||
// roughness = max(roughness, 1e-2); // 0.0 roughness leads to NaNs
|
|
||||||
//
|
|
||||||
// float metallic = metallicF * metallicRoughness.b;
|
|
||||||
//
|
|
||||||
// vec3 dielectricSpecular = vec3(0.04);
|
|
||||||
// vec3 black = vec3(0.0);
|
|
||||||
// vec3 diffuseColor = mix(baseColor * (1.0 - dielectricSpecular.r), black, metallic);
|
|
||||||
// vec3 f0 = mix(dielectricSpecular, baseColor, metallic);
|
|
||||||
// #else
|
|
||||||
// vec3 diffuseColor = baseColor;
|
|
||||||
// float metallic = 0.f;
|
|
||||||
// float roughness = 1.f;
|
|
||||||
// vec3 f0 = vec3(0.0);
|
|
||||||
// #endif
|
|
||||||
//
|
|
||||||
// vec3 cameraPos = pcs.sceneData.cameraPos.xyz;
|
|
||||||
// vec3 fragPos = inPos.xyz;
|
|
||||||
// vec3 n = normal;
|
|
||||||
// vec3 v = normalize(cameraPos - fragPos);
|
|
||||||
//
|
|
||||||
// // diffuseColor = vec3(1.0);
|
|
||||||
// // baseColor = vec3(1.0);
|
|
||||||
//
|
|
||||||
// vec3 fragColor = vec3(0.0);
|
|
||||||
// for (int i = 0; i < pcs.sceneData.numLights; i++) {
|
|
||||||
// Light light = pcs.sceneData.lights.data[i];
|
|
||||||
//
|
|
||||||
// vec3 l = light.direction;
|
|
||||||
// if (light.type != TYPE_DIRECTIONAL_LIGHT) {
|
|
||||||
// l = normalize(light.position - fragPos);
|
|
||||||
// }
|
|
||||||
// float NoL = clamp(dot(n, l), 0.0, 1.0);
|
|
||||||
//
|
|
||||||
// float occlusion = 1.0;
|
|
||||||
// if (light.type == TYPE_DIRECTIONAL_LIGHT) {
|
|
||||||
// occlusion = calculateCSMOcclusion(
|
|
||||||
// fragPos, cameraPos, NoL,
|
|
||||||
// pcs.sceneData.csmShadowMapId,
|
|
||||||
// pcs.sceneData.cascadeFarPlaneZs,
|
|
||||||
// pcs.sceneData.csmLightSpaceTMs);
|
|
||||||
// } else if (light.type == TYPE_POINT_LIGHT && light.shadowMapID != 0) {
|
|
||||||
// occlusion = calculatePointShadow(
|
|
||||||
// fragPos, light.position, NoL,
|
|
||||||
// light.shadowMapID,
|
|
||||||
// pcs.sceneData.pointLightFarPlane);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fragColor += calculateLight(light, fragPos, n, v, l,
|
|
||||||
// diffuseColor, roughness, metallic, f0, occlusion);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // emissive
|
|
||||||
// float emissiveF = material.metallicRoughnessEmissive.b;
|
|
||||||
// vec3 emissiveColor = emissiveF * sampleTexture2DLinear(material.emissiveTex, inUV).rgb;
|
|
||||||
// fragColor += emissiveColor;
|
|
||||||
//
|
|
||||||
// // ambient
|
|
||||||
// fragColor += baseColor * pcs.sceneData.ambientColor * pcs.sceneData.ambientIntensity;
|
|
||||||
//
|
|
||||||
// #if 0
|
|
||||||
// // CSM DEBUG
|
|
||||||
// uint cascadeIndex = chooseCascade(inPos, cameraPos, pcs.sceneData.cascadeFarPlaneZs);
|
|
||||||
// fragColor *= debugShadowsFactor(cascadeIndex);
|
|
||||||
// #endif
|
|
||||||
//
|
|
||||||
// #if 0
|
|
||||||
// // TANGENT DEBUG
|
|
||||||
// if (inTangent == vec4(0.0)) {
|
|
||||||
// fragColor = vec3(1.0f, 0.0f, 0.0f);
|
|
||||||
// }
|
|
||||||
// #endif
|
|
||||||
//
|
|
||||||
// #if 1
|
|
||||||
// // NORMAL DEBUG
|
|
||||||
// // fragColor = normal;
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// outFragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);
|
float metallicF = material.metallicRoughnessEmissive.r;
|
||||||
|
float roughnessF = material.metallicRoughnessEmissive.g;
|
||||||
|
|
||||||
|
vec4 metallicRoughness = sampleTexture2DLinear(material.metallicRoughnessTex, inUV);
|
||||||
|
|
||||||
|
float roughness = roughnessF * metallicRoughness.g;
|
||||||
|
// roughness *= roughness; // from perceptual to linear
|
||||||
|
roughness = max(roughness, 1e-2); // 0.0 roughness leads to NaNs
|
||||||
|
|
||||||
|
float metallic = metallicF * metallicRoughness.b;
|
||||||
|
|
||||||
|
vec3 dielectricSpecular = vec3(0.04);
|
||||||
|
vec3 black = vec3(0.0);
|
||||||
|
vec3 diffuseColor = mix(baseColor * (1.0 - dielectricSpecular.r), black, metallic);
|
||||||
|
vec3 f0 = mix(dielectricSpecular, baseColor, metallic);
|
||||||
|
|
||||||
|
vec3 cameraPos = pcs.sceneData.cameraPos.xyz;
|
||||||
|
vec3 fragPos = inPos.xyz;
|
||||||
|
vec3 n = normal;
|
||||||
|
vec3 v = normalize(cameraPos - fragPos);
|
||||||
|
|
||||||
|
// forward+: only loop over the lights the culling pass put in this pixel's tile
|
||||||
|
uvec2 tileID = uvec2(gl_FragCoord.xy) / LIGHT_TILE_SIZE;
|
||||||
|
uint tileOffset = (tileID.y * pcs.sceneData.tileCountX + tileID.x) * LIGHT_TILE_STRIDE;
|
||||||
|
uint tileLightCount = pcs.sceneData.tileLightIndices.data[tileOffset];
|
||||||
|
|
||||||
|
vec3 fragColor = vec3(0.0);
|
||||||
|
for (uint i = 0; i < tileLightCount; i++) {
|
||||||
|
uint lightIndex = pcs.sceneData.tileLightIndices.data[tileOffset + 1 + i];
|
||||||
|
Light light = pcs.sceneData.lights.data[lightIndex];
|
||||||
|
|
||||||
|
float occlusion = 1.0;
|
||||||
|
if (light.castShadows != 0) {
|
||||||
|
occlusion = calculateDirectionalShadow(
|
||||||
|
fragPos, n,
|
||||||
|
pcs.sceneData.lightViewProj,
|
||||||
|
pcs.sceneData.shadowMapId,
|
||||||
|
pcs.sceneData.shadowNormalOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
fragColor += calculateLight(light, fragPos, n, v,
|
||||||
|
diffuseColor, roughness, metallic, f0, occlusion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// emissive
|
||||||
|
float emissiveF = material.metallicRoughnessEmissive.b;
|
||||||
|
vec3 emissiveColor = emissiveF * sampleTexture2DLinear(material.emissiveTex, inUV).rgb;
|
||||||
|
fragColor += emissiveColor;
|
||||||
|
|
||||||
|
// ambient
|
||||||
|
fragColor += baseColor * pcs.sceneData.ambientColor * pcs.sceneData.ambientIntensity;
|
||||||
|
|
||||||
|
if (pcs.sceneData.debugLightHeatmap != 0) {
|
||||||
|
fragColor = mix(fragColor, lightHeatmapColor(tileLightCount), 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// TANGENT DEBUG
|
||||||
|
if (inTangent == vec4(0.0)) {
|
||||||
|
fragColor = vec3(1.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// NORMAL DEBUG
|
||||||
|
fragColor = normal;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
outFragColor = vec4(fragColor, diffuse.a);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ layout (location = 2) out vec3 outNormal;
|
|||||||
layout (location = 3) out vec4 outTangent;
|
layout (location = 3) out vec4 outTangent;
|
||||||
layout (location = 4) out mat3 outTBN;
|
layout (location = 4) out mat3 outTBN;
|
||||||
|
|
||||||
|
// The depth pre-pass reuses this shader, positions have to match bit for bit
|
||||||
|
// so the main pass can depth test against it with LESS_OR_EQUAL
|
||||||
|
invariant gl_Position;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
Vertex v = pcs.vertexBuffer.vertices[gl_VertexIndex];
|
Vertex v = pcs.vertexBuffer.vertices[gl_VertexIndex];
|
||||||
|
|||||||
@@ -6,11 +6,14 @@
|
|||||||
#extension GL_EXT_buffer_reference : require
|
#extension GL_EXT_buffer_reference : require
|
||||||
|
|
||||||
#include "materials.glsl"
|
#include "materials.glsl"
|
||||||
|
#include "lights.glsl"
|
||||||
|
|
||||||
|
// Must match GameRenderer::GPUSceneData
|
||||||
layout (buffer_reference, scalar) readonly buffer SceneDataBuffer {
|
layout (buffer_reference, scalar) readonly buffer SceneDataBuffer {
|
||||||
// camera
|
// camera
|
||||||
mat4 view;
|
mat4 view;
|
||||||
mat4 proj;
|
mat4 proj;
|
||||||
|
mat4 invProj;
|
||||||
mat4 viewProj;
|
mat4 viewProj;
|
||||||
vec4 cameraPos;
|
vec4 cameraPos;
|
||||||
|
|
||||||
@@ -23,6 +26,19 @@ layout (buffer_reference, scalar) readonly buffer SceneDataBuffer {
|
|||||||
float fogDensity;
|
float fogDensity;
|
||||||
|
|
||||||
MaterialsBuffer materials;
|
MaterialsBuffer materials;
|
||||||
|
|
||||||
|
// forward+
|
||||||
|
LightsBuffer lights;
|
||||||
|
TileLightIndicesBuffer tileLightIndices;
|
||||||
|
uint numLights;
|
||||||
|
uint tileCountX;
|
||||||
|
uint debugLightHeatmap;
|
||||||
|
|
||||||
|
// directional shadows
|
||||||
|
uint shadowMapId;
|
||||||
|
float shadowNormalOffset;
|
||||||
|
uint padding;
|
||||||
|
mat4 lightViewProj;
|
||||||
} sceneDataBuffer;
|
} sceneDataBuffer;
|
||||||
|
|
||||||
#endif // SCENE_DATA_GLSL
|
#endif // SCENE_DATA_GLSL
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#version 460
|
||||||
|
|
||||||
|
#extension GL_GOOGLE_include_directive : require
|
||||||
|
|
||||||
|
#include "mesh_pcs.glsl"
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
Vertex v = pcs.vertexBuffer.vertices[gl_VertexIndex];
|
||||||
|
|
||||||
|
gl_Position = pcs.sceneData.lightViewProj * pcs.transform * vec4(v.position, 1.0f);
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef SHADOWS_GLSL
|
||||||
|
#define SHADOWS_GLSL
|
||||||
|
|
||||||
|
// expects bindless.glsl to be included before this file (it has no include guard)
|
||||||
|
|
||||||
|
// Returns 1.0 when fully lit, 0.0 when fully in shadow
|
||||||
|
float calculateDirectionalShadow(vec3 fragPos, vec3 n, mat4 lightViewProj, uint shadowMapID, float normalOffset)
|
||||||
|
{
|
||||||
|
// pushing the sample point along the normal hides most of the shadow acne
|
||||||
|
vec4 lightClip = lightViewProj * vec4(fragPos + n * normalOffset, 1.0);
|
||||||
|
vec3 lightNDC = lightClip.xyz / lightClip.w;
|
||||||
|
vec2 uv = lightNDC.xy * 0.5 + 0.5;
|
||||||
|
|
||||||
|
// outside the shadow map counts as lit
|
||||||
|
if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))) || lightNDC.z > 1.0) {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 texelSize = 1.0 / vec2(textureSize(sampler2DShadow(textures[shadowMapID], samplers[SHADOW_SAMPLER_ID]), 0));
|
||||||
|
|
||||||
|
// 3x3 PCF, every tap is already bilinearly filtered by the compare sampler
|
||||||
|
float shadowed = 0.0;
|
||||||
|
for (int x = -1; x <= 1; x++) {
|
||||||
|
for (int y = -1; y <= 1; y++) {
|
||||||
|
shadowed += sampleTexture2DShadow(shadowMapID, vec3(uv + vec2(x, y) * texelSize, lightNDC.z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the shadow sampler compares with GREATER_OR_EQUAL (it was set up for reversed Z),
|
||||||
|
// with regular depth that returns 1 when the fragment is behind the occluder
|
||||||
|
return 1.0 - shadowed / 9.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SHADOWS_GLSL
|
||||||
@@ -10,7 +10,6 @@ layout(location = 0) out vec4 outColor;
|
|||||||
layout(push_constant) uniform SkyboxPC {
|
layout(push_constant) uniform SkyboxPC {
|
||||||
mat4 invViewProj;
|
mat4 invViewProj;
|
||||||
vec4 skyboxRot[3];
|
vec4 skyboxRot[3];
|
||||||
vec3 cameraPos;
|
|
||||||
uint skyboxTextureId;
|
uint skyboxTextureId;
|
||||||
} pcs;
|
} pcs;
|
||||||
|
|
||||||
@@ -22,7 +21,8 @@ void main()
|
|||||||
vec4 world = pcs.invViewProj * ndc;
|
vec4 world = pcs.invViewProj * ndc;
|
||||||
vec3 worldPos = world.xyz / world.w;
|
vec3 worldPos = world.xyz / world.w;
|
||||||
|
|
||||||
vec3 dir = normalize(worldPos - pcs.cameraPos);
|
// invViewProj has no translation, so the camera sits at the origin
|
||||||
|
vec3 dir = normalize(worldPos);
|
||||||
dir.y *= -1.0;
|
dir.y *= -1.0;
|
||||||
|
|
||||||
mat3 skyboxRot = mat3(
|
mat3 skyboxRot = mat3(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <destrum/Graphics/GfxDevice.h>
|
#include <destrum/Graphics/GfxDevice.h>
|
||||||
#include <destrum/Graphics/Renderer.h>
|
#include <destrum/Graphics/Renderer.h>
|
||||||
#include <destrum/Graphics/Pipelines/ImguiPass.h>
|
#include <destrum/Graphics/Pipelines/ImguiPass.h>
|
||||||
|
#include <destrum/Graphics/ResourcesInspector.h>
|
||||||
|
|
||||||
#include <destrum/Input/InputManager.h>
|
#include <destrum/Input/InputManager.h>
|
||||||
|
|
||||||
@@ -41,6 +42,9 @@ public:
|
|||||||
|
|
||||||
virtual void onWindowResize(int, int) {};
|
virtual void onWindowResize(int, int) {};
|
||||||
|
|
||||||
|
// Called inside the "Windows" menu of the main menu bar, add toggles for the game's own windows here
|
||||||
|
virtual void customWindowsMenu() {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SDL_Window* window{nullptr};
|
SDL_Window* window{nullptr};
|
||||||
AppParams m_params{};
|
AppParams m_params{};
|
||||||
@@ -72,6 +76,17 @@ protected:
|
|||||||
float m_PhysicsTimeScale = 1.0f;
|
float m_PhysicsTimeScale = 1.0f;
|
||||||
int m_PhysicsStepsLastFrame = 0;
|
int m_PhysicsStepsLastFrame = 0;
|
||||||
|
|
||||||
|
struct DebugWindows {
|
||||||
|
bool stats{true};
|
||||||
|
bool physics{true};
|
||||||
|
bool inspector{true};
|
||||||
|
bool imageCache{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
DebugWindows m_DebugWindows{};
|
||||||
|
ResourcesInspector m_ResourcesInspector;
|
||||||
|
|
||||||
|
void drawWindowsMenu();
|
||||||
void drawPhysicsPanel(float dt, float fixedDt, float accumulator);
|
void drawPhysicsPanel(float dt, float fixedDt, float accumulator);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#ifndef LIGHTCOMPONENT_H
|
||||||
|
#define LIGHTCOMPONENT_H
|
||||||
|
|
||||||
|
#include <destrum/ObjectModel/Component.h>
|
||||||
|
#include <destrum/Graphics/Light.h>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
class LightComponent final : public Component {
|
||||||
|
public:
|
||||||
|
explicit LightComponent(GameObject& parent, LightType type = LightType::Point);
|
||||||
|
|
||||||
|
LightComponent(const LightComponent& other) = delete;
|
||||||
|
LightComponent(LightComponent&& other) noexcept = delete;
|
||||||
|
LightComponent& operator=(const LightComponent& other) = delete;
|
||||||
|
LightComponent& operator=(LightComponent&& other) noexcept = delete;
|
||||||
|
|
||||||
|
void Render(const RenderContext& ctx) override;
|
||||||
|
|
||||||
|
std::string GetTypeName() const override { return "LightComponent"; }
|
||||||
|
|
||||||
|
nlohmann::json Serialize() const override;
|
||||||
|
void Deserialize(const nlohmann::json& data) override;
|
||||||
|
void ImGuiInspector() override;
|
||||||
|
|
||||||
|
void SetType(LightType type) { m_Type = type; }
|
||||||
|
[[nodiscard]] LightType GetType() const { return m_Type; }
|
||||||
|
|
||||||
|
void SetColor(const glm::vec3& color) { m_Color = color; }
|
||||||
|
[[nodiscard]] const glm::vec3& GetColor() const { return m_Color; }
|
||||||
|
|
||||||
|
void SetIntensity(float intensity);
|
||||||
|
[[nodiscard]] float GetIntensity() const { return m_Intensity; }
|
||||||
|
|
||||||
|
void SetRange(float range);
|
||||||
|
[[nodiscard]] float GetRange() const { return m_Range; }
|
||||||
|
|
||||||
|
void SetConeAngles(float innerConeAngle, float outerConeAngle);
|
||||||
|
[[nodiscard]] float GetInnerConeAngle() const { return m_InnerConeAngle; }
|
||||||
|
[[nodiscard]] float GetOuterConeAngle() const { return m_OuterConeAngle; }
|
||||||
|
|
||||||
|
void SetCastShadows(bool castShadows) { m_CastShadows = castShadows; }
|
||||||
|
[[nodiscard]] bool GetCastShadows() const { return m_CastShadows; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
LightType m_Type{LightType::Point};
|
||||||
|
glm::vec3 m_Color{1.0f};
|
||||||
|
float m_Intensity{1.0f};
|
||||||
|
float m_Range{10.0f};
|
||||||
|
float m_InnerConeAngle{0.35f};
|
||||||
|
float m_OuterConeAngle{0.5f};
|
||||||
|
bool m_CastShadows{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LIGHTCOMPONENT_H
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#ifndef LIGHT_H
|
||||||
|
#define LIGHT_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include <glm/vec2.hpp>
|
||||||
|
#include <glm/vec3.hpp>
|
||||||
|
|
||||||
|
enum class LightType : std::uint32_t {
|
||||||
|
Directional = 0,
|
||||||
|
Point = 1,
|
||||||
|
Spot = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Light {
|
||||||
|
LightType type{LightType::Point};
|
||||||
|
|
||||||
|
glm::vec3 position{0.0f};
|
||||||
|
glm::vec3 direction{0.0f, -1.0f, 0.0f};
|
||||||
|
|
||||||
|
glm::vec3 color{1.0f};
|
||||||
|
float intensity{1.0f};
|
||||||
|
|
||||||
|
float range{10.0f};
|
||||||
|
|
||||||
|
float innerConeAngle{0.35f};
|
||||||
|
float outerConeAngle{0.5f};
|
||||||
|
|
||||||
|
// directional lights only, the first one submitted each frame gets the shadow map
|
||||||
|
bool castShadows{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPULight {
|
||||||
|
glm::vec3 position;
|
||||||
|
float range;
|
||||||
|
|
||||||
|
glm::vec3 color;
|
||||||
|
float intensity;
|
||||||
|
|
||||||
|
glm::vec3 direction;
|
||||||
|
std::uint32_t type;
|
||||||
|
|
||||||
|
float spotInnerCos;
|
||||||
|
float spotOuterCos;
|
||||||
|
std::uint32_t castShadows;
|
||||||
|
float padding;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(GPULight) == 64, "GPULight must match the struct in lights.glsl");
|
||||||
|
|
||||||
|
#endif //LIGHT_H
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
#ifndef DESTRUM_IMGUI_PASS_H
|
#ifndef DESTRUM_IMGUI_PASS_H
|
||||||
#define DESTRUM_IMGUI_PASS_H
|
#define DESTRUM_IMGUI_PASS_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <vulkan/vulkan.h>
|
#include <vulkan/vulkan.h>
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/Swapchain.h>
|
||||||
|
|
||||||
class GfxDevice;
|
class GfxDevice;
|
||||||
|
|
||||||
@@ -33,9 +40,19 @@ public:
|
|||||||
[[nodiscard]] bool wantsMouse() const;
|
[[nodiscard]] bool wantsMouse() const;
|
||||||
[[nodiscard]] bool wantsKeyboard() const;
|
[[nodiscard]] bool wantsKeyboard() const;
|
||||||
|
|
||||||
|
// Wraps an image view for ImGui::Image in the frame that's being built. Call it every frame
|
||||||
|
// you draw the image, the descriptor set is freed again once the GPU is done with that frame.
|
||||||
|
// The image has to be in imageLayout by the time the UI gets rendered at the end of the frame.
|
||||||
|
[[nodiscard]] ImTextureID getFrameTexture(
|
||||||
|
VkImageView imageView,
|
||||||
|
VkImageLayout imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initVulkanBackend();
|
void initVulkanBackend();
|
||||||
|
|
||||||
|
void releaseTextures(std::vector<VkDescriptorSet>& textures);
|
||||||
|
void releaseAllTextures();
|
||||||
|
|
||||||
void transitionToColorAttachment(
|
void transitionToColorAttachment(
|
||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
VkImage image,
|
VkImage image,
|
||||||
@@ -50,6 +67,17 @@ private:
|
|||||||
VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
|
VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
|
||||||
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
|
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
|
||||||
|
|
||||||
|
struct FrameTexture {
|
||||||
|
VkImageView imageView{VK_NULL_HANDLE};
|
||||||
|
VkImageLayout imageLayout{VK_IMAGE_LAYOUT_UNDEFINED};
|
||||||
|
VkDescriptorSet descriptorSet{VK_NULL_HANDLE};
|
||||||
|
};
|
||||||
|
|
||||||
|
// created while building the current UI, not submitted yet
|
||||||
|
std::vector<FrameTexture> pendingTextures;
|
||||||
|
// submitted with a frame, freed once that frame's fence has been waited on
|
||||||
|
std::array<std::vector<VkDescriptorSet>, FRAMES_IN_FLIGHT> inFlightTextures;
|
||||||
|
|
||||||
bool initialized = false;
|
bool initialized = false;
|
||||||
bool frameBegun = false;
|
bool frameBegun = false;
|
||||||
bool contextCreated = false;
|
bool contextCreated = false;
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#ifndef LIGHTCULLINGPIPELINE_H
|
||||||
|
#define LIGHTCULLINGPIPELINE_H
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/ComputePipeline.h>
|
||||||
|
#include <destrum/Graphics/Light.h>
|
||||||
|
#include <destrum/Graphics/ids.h>
|
||||||
|
#include <destrum/Graphics/Resources/AppendableBuffer.h>
|
||||||
|
#include <destrum/Graphics/Resources/Buffer.h>
|
||||||
|
|
||||||
|
class GfxDevice;
|
||||||
|
class RenderResources;
|
||||||
|
|
||||||
|
class LightCullingPipeline final {
|
||||||
|
public:
|
||||||
|
|
||||||
|
static constexpr std::uint32_t TILE_SIZE = 16;
|
||||||
|
static constexpr std::uint32_t MAX_LIGHTS_PER_TILE = 255;
|
||||||
|
static constexpr std::uint32_t TILE_STRIDE = MAX_LIGHTS_PER_TILE + 1;
|
||||||
|
|
||||||
|
static constexpr std::size_t MAX_LIGHTS = 1024;
|
||||||
|
|
||||||
|
void init(GfxDevice& gfxDevice, RenderResources& resources, glm::ivec2 drawImageSize);
|
||||||
|
void cleanup(GfxDevice& gfxDevice);
|
||||||
|
|
||||||
|
void resize(GfxDevice& gfxDevice, glm::ivec2 drawImageSize);
|
||||||
|
|
||||||
|
void beginDrawing(std::size_t frameIndex);
|
||||||
|
void appendLight(const Light& light);
|
||||||
|
|
||||||
|
void flushCurrentFrame(VkCommandBuffer cmd, GfxDevice& gfxDevice);
|
||||||
|
|
||||||
|
void cullLights(
|
||||||
|
VkCommandBuffer cmd,
|
||||||
|
const RenderResources& resources,
|
||||||
|
const GPUBuffer& sceneDataBuffer,
|
||||||
|
ImageID depthImageId,
|
||||||
|
VkExtent2D renderExtent);
|
||||||
|
|
||||||
|
[[nodiscard]] VkDeviceAddress getLightsBufferAddress() const;
|
||||||
|
[[nodiscard]] VkDeviceAddress getTileLightIndicesBufferAddress() const { return tileLightIndicesBuffer.address; }
|
||||||
|
|
||||||
|
[[nodiscard]] std::uint32_t getNumLights() const;
|
||||||
|
[[nodiscard]] glm::uvec2 getTileCount() const { return tileCount; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createTileBuffer(GfxDevice& gfxDevice, glm::ivec2 drawImageSize);
|
||||||
|
|
||||||
|
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
|
||||||
|
std::unique_ptr<ComputePipeline> lightCullingPipeline;
|
||||||
|
|
||||||
|
struct PushConstants {
|
||||||
|
VkDeviceAddress sceneDataBuffer;
|
||||||
|
glm::uvec2 screenSize;
|
||||||
|
std::uint32_t depthTexId;
|
||||||
|
std::uint32_t padding;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PerFrameData {
|
||||||
|
AppendableBuffer<GPULight> lightsBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array<PerFrameData, FRAMES_IN_FLIGHT> framesData;
|
||||||
|
std::size_t currentFrameIndex{0};
|
||||||
|
bool warnedAboutLightLimit{false};
|
||||||
|
|
||||||
|
GPUBuffer tileLightIndicesBuffer;
|
||||||
|
glm::uvec2 tileCount{0u, 0u};
|
||||||
|
|
||||||
|
PerFrameData& getCurrentFrameData();
|
||||||
|
const PerFrameData& getCurrentFrameData() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //LIGHTCULLINGPIPELINE_H
|
||||||
@@ -27,6 +27,15 @@ public:
|
|||||||
VkFormat drawImageFormat,
|
VkFormat drawImageFormat,
|
||||||
VkFormat depthImageFormat);
|
VkFormat depthImageFormat);
|
||||||
|
|
||||||
|
void drawDepthPrepass(
|
||||||
|
VkCommandBuffer cmd,
|
||||||
|
VkExtent2D renderExtent,
|
||||||
|
const RenderResources& resources,
|
||||||
|
const Camera& camera,
|
||||||
|
const GPUBuffer& sceneDataBuffer,
|
||||||
|
const std::vector<MeshDrawCommand>& drawCommands,
|
||||||
|
const std::vector<std::size_t>& sortedDrawCommands);
|
||||||
|
|
||||||
void draw(
|
void draw(
|
||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
VkExtent2D renderExtent,
|
VkExtent2D renderExtent,
|
||||||
@@ -55,9 +64,20 @@ private:
|
|||||||
std::uint32_t padding;
|
std::uint32_t padding;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void drawMeshes(
|
||||||
|
VkCommandBuffer cmd,
|
||||||
|
const Pipeline& pipeline,
|
||||||
|
VkExtent2D renderExtent,
|
||||||
|
const RenderResources& resources,
|
||||||
|
const Camera& camera,
|
||||||
|
const GPUBuffer& sceneDataBuffer,
|
||||||
|
const std::vector<MeshDrawCommand>& drawCommands,
|
||||||
|
const std::vector<std::size_t>& sortedDrawCommands);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
|
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
|
||||||
std::unique_ptr<Pipeline> m_pipeline;
|
std::unique_ptr<Pipeline> m_pipeline;
|
||||||
|
std::unique_ptr<Pipeline> m_depthPrepassPipeline;
|
||||||
|
|
||||||
bool m_renderWireframe{false};
|
bool m_renderWireframe{false};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#ifndef SHADOWMAPPIPELINE_H
|
||||||
|
#define SHADOWMAPPIPELINE_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/Pipeline.h>
|
||||||
|
#include <destrum/Graphics/ids.h>
|
||||||
|
#include <destrum/Graphics/MeshDrawCommand.h>
|
||||||
|
#include <destrum/Graphics/Resources/Buffer.h>
|
||||||
|
|
||||||
|
class GfxDevice;
|
||||||
|
class RenderResources;
|
||||||
|
|
||||||
|
// Renders the depth of the scene as seen from a directional light, centered around the camera
|
||||||
|
class ShadowMapPipeline final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr std::uint32_t SHADOW_MAP_SIZE = 2048;
|
||||||
|
|
||||||
|
void init(GfxDevice& gfxDevice, RenderResources& resources);
|
||||||
|
void cleanup(VkDevice device);
|
||||||
|
|
||||||
|
void draw(
|
||||||
|
VkCommandBuffer cmd,
|
||||||
|
const RenderResources& resources,
|
||||||
|
const GPUBuffer& sceneDataBuffer,
|
||||||
|
const std::vector<MeshDrawCommand>& drawCommands,
|
||||||
|
const std::vector<std::size_t>& sortedDrawCommands);
|
||||||
|
|
||||||
|
[[nodiscard]] glm::mat4 calculateLightViewProj(const glm::vec3& lightDirection, const glm::vec3& center) const;
|
||||||
|
|
||||||
|
[[nodiscard]] ImageID getShadowMapId() const { return shadowMapId; }
|
||||||
|
[[nodiscard]] float getNormalOffset() const;
|
||||||
|
|
||||||
|
// half the width of the area around the camera that receives shadows
|
||||||
|
void setShadowDistance(float distance) { shadowDistance = glm::max(distance, 1.0f); }
|
||||||
|
[[nodiscard]] float getShadowDistance() const { return shadowDistance; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct PushConstants
|
||||||
|
{
|
||||||
|
glm::mat4 transform;
|
||||||
|
VkDeviceAddress sceneDataBuffer;
|
||||||
|
VkDeviceAddress vertexBuffer;
|
||||||
|
std::uint32_t materialId;
|
||||||
|
std::uint32_t padding;
|
||||||
|
};
|
||||||
|
|
||||||
|
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
|
||||||
|
std::unique_ptr<Pipeline> m_pipeline;
|
||||||
|
|
||||||
|
VkFormat shadowMapFormat{VK_FORMAT_D32_SFLOAT};
|
||||||
|
ImageID shadowMapId{NULL_IMAGE_ID};
|
||||||
|
|
||||||
|
float shadowDistance{25.0f};
|
||||||
|
// how far in front of / behind the camera (along the light) casters are still captured
|
||||||
|
float shadowDepthRange{100.0f};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //SHADOWMAPPIPELINE_H
|
||||||
@@ -35,7 +35,6 @@ private:
|
|||||||
struct SkyboxPushConstants {
|
struct SkyboxPushConstants {
|
||||||
glm::mat4 invViewProj;
|
glm::mat4 invViewProj;
|
||||||
glm::vec4 skyboxRot[3];
|
glm::vec4 skyboxRot[3];
|
||||||
glm::vec3 cameraPos;
|
|
||||||
std::uint32_t skyboxTextureId;
|
std::uint32_t skyboxTextureId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -53,11 +53,19 @@ public:
|
|||||||
|
|
||||||
[[nodiscard]] ImageID getWhiteTextureID() const { return whiteImageId; }
|
[[nodiscard]] ImageID getWhiteTextureID() const { return whiteImageId; }
|
||||||
[[nodiscard]] ImageID getErrorTextureID() const { return errorImageId; }
|
[[nodiscard]] ImageID getErrorTextureID() const { return errorImageId; }
|
||||||
|
[[nodiscard]] ImageID getDefaultNormalTextureID() const { return defaultNormalImageId; }
|
||||||
|
|
||||||
BindlessSetManager& getBindlessSetManager();
|
BindlessSetManager& getBindlessSetManager();
|
||||||
VkDescriptorSetLayout getBindlessDescSetLayout() const;
|
VkDescriptorSetLayout getBindlessDescSetLayout() const;
|
||||||
const VkDescriptorSet& getBindlessDescSet() const;
|
const VkDescriptorSet& getBindlessDescSet() const;
|
||||||
void bindBindlessDescSet(VkCommandBuffer cmd, VkPipelineLayout layout) const;
|
void bindBindlessDescSet(
|
||||||
|
VkCommandBuffer cmd,
|
||||||
|
VkPipelineLayout layout,
|
||||||
|
VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS) const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool isInitialized() const { return imageCache != nullptr && materialCache != nullptr; }
|
||||||
|
|
||||||
|
const ImageCache& images() const { return *imageCache; }
|
||||||
|
|
||||||
MeshCache& meshes() { return *meshCache; }
|
MeshCache& meshes() { return *meshCache; }
|
||||||
const MeshCache& meshes() const { return *meshCache; }
|
const MeshCache& meshes() const { return *meshCache; }
|
||||||
|
|||||||
@@ -5,9 +5,12 @@
|
|||||||
#include <glm/vec3.hpp>
|
#include <glm/vec3.hpp>
|
||||||
|
|
||||||
#include <destrum/Graphics/Camera.h>
|
#include <destrum/Graphics/Camera.h>
|
||||||
|
#include <destrum/Graphics/Light.h>
|
||||||
|
#include <destrum/Graphics/Pipelines/LightCullingPipeline.h>
|
||||||
#include <destrum/Graphics/Pipelines/LineRenderingPass.h>
|
#include <destrum/Graphics/Pipelines/LineRenderingPass.h>
|
||||||
#include <destrum/Graphics/Pipelines/MeshPipeline.h>
|
#include <destrum/Graphics/Pipelines/MeshPipeline.h>
|
||||||
#include <destrum/Graphics/Pipelines/QuadRendererPass.h>
|
#include <destrum/Graphics/Pipelines/QuadRendererPass.h>
|
||||||
|
#include <destrum/Graphics/Pipelines/ShadowMapPipeline.h>
|
||||||
#include <destrum/Graphics/ids.h>
|
#include <destrum/Graphics/ids.h>
|
||||||
#include <destrum/Graphics/MeshDrawCommand.h>
|
#include <destrum/Graphics/MeshDrawCommand.h>
|
||||||
#include <destrum/Graphics/Resources/NBuffer.h>
|
#include <destrum/Graphics/Resources/NBuffer.h>
|
||||||
@@ -15,14 +18,17 @@
|
|||||||
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include <destrum/Graphics/Pipelines/SkinningPipeline.h>
|
#include <destrum/Graphics/Pipelines/SkinningPipeline.h>
|
||||||
|
|
||||||
#include "Pipelines/SkyboxPipeline.h"
|
#include "Pipelines/SkyboxPipeline.h"
|
||||||
|
|
||||||
class GameRenderer {
|
class GameRenderer
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
struct SceneData {
|
struct SceneData
|
||||||
|
{
|
||||||
const Camera& camera;
|
const Camera& camera;
|
||||||
glm::vec3 ambientColor;
|
glm::vec3 ambientColor;
|
||||||
float ambientIntensity;
|
float ambientIntensity;
|
||||||
@@ -40,11 +46,15 @@ public:
|
|||||||
void cleanup(GfxDevice& gfxDevice);
|
void cleanup(GfxDevice& gfxDevice);
|
||||||
|
|
||||||
void drawMesh(MeshID id, const glm::mat4& transform, MaterialID materialId);
|
void drawMesh(MeshID id, const glm::mat4& transform, MaterialID materialId);
|
||||||
void drawSkinnedMesh(MeshID id, const glm::mat4& transform, MaterialID materialId, SkinnedMesh* skinnedMesh, std::size_t jointMatricesStartIndex);
|
void drawSkinnedMesh(MeshID id, const glm::mat4& transform, MaterialID materialId, SkinnedMesh* skinnedMesh,
|
||||||
|
std::size_t jointMatricesStartIndex);
|
||||||
|
void submitLight(const Light& light);
|
||||||
const GPUImage& getDrawImage() const;
|
const GPUImage& getDrawImage() const;
|
||||||
|
|
||||||
void resize(GfxDevice& gfxDevice, const glm::ivec2& newSize) {
|
void resize(GfxDevice& gfxDevice, const glm::ivec2& newSize)
|
||||||
|
{
|
||||||
createDrawImage(gfxDevice, newSize, false);
|
createDrawImage(gfxDevice, newSize, false);
|
||||||
|
lightCullingPipeline->resize(gfxDevice, newSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
Material& getMaterialMutable(MaterialID id);
|
Material& getMaterialMutable(MaterialID id);
|
||||||
@@ -54,18 +64,41 @@ public:
|
|||||||
|
|
||||||
void flushMaterialUpdates(GfxDevice& gfxDevice);
|
void flushMaterialUpdates(GfxDevice& gfxDevice);
|
||||||
|
|
||||||
[[nodiscard]] SkinningPipeline& getSkinningPipeline() const {
|
[[nodiscard]] SkinningPipeline& getSkinningPipeline() const
|
||||||
|
{
|
||||||
return *skinningPipeline;
|
return *skinningPipeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setRenderWireframe(bool wireframe) {
|
void setRenderWireframe(bool wireframe)
|
||||||
|
{
|
||||||
meshPipeline->setRenderWireframe(wireframe);
|
meshPipeline->setRenderWireframe(wireframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getRenderWireframe() {
|
bool getRenderWireframe()
|
||||||
|
{
|
||||||
return meshPipeline->getRenderWireframe();
|
return meshPipeline->getRenderWireframe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setRenderLightHeatmap(bool heatmap)
|
||||||
|
{
|
||||||
|
renderLightHeatmap = heatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getRenderLightHeatmap() const
|
||||||
|
{
|
||||||
|
return renderLightHeatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setShadowDistance(float distance)
|
||||||
|
{
|
||||||
|
shadowMapPipeline->setShadowDistance(distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
float getShadowDistance() const
|
||||||
|
{
|
||||||
|
return shadowMapPipeline->getShadowDistance();
|
||||||
|
}
|
||||||
|
|
||||||
RenderResources* getResources()
|
RenderResources* getResources()
|
||||||
{
|
{
|
||||||
return resources;
|
return resources;
|
||||||
@@ -96,33 +129,48 @@ private:
|
|||||||
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
|
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
|
||||||
|
|
||||||
|
|
||||||
struct GPUSceneData {
|
struct GPUSceneData
|
||||||
// camera
|
{
|
||||||
glm::mat4 view;
|
glm::mat4 view;
|
||||||
glm::mat4 proj;
|
glm::mat4 proj;
|
||||||
|
glm::mat4 invProj;
|
||||||
glm::mat4 viewProj;
|
glm::mat4 viewProj;
|
||||||
glm::vec4 cameraPos;
|
glm::vec4 cameraPos;
|
||||||
|
|
||||||
// ambient
|
|
||||||
glm::vec3 ambientColor;
|
glm::vec3 ambientColor;
|
||||||
float ambientIntensity;
|
float ambientIntensity;
|
||||||
|
|
||||||
// fog
|
|
||||||
glm::vec3 fogColor;
|
glm::vec3 fogColor;
|
||||||
float fogDensity;
|
float fogDensity;
|
||||||
|
|
||||||
VkDeviceAddress materialsBuffer;
|
VkDeviceAddress materialsBuffer;
|
||||||
|
|
||||||
|
VkDeviceAddress lightsBuffer;
|
||||||
|
VkDeviceAddress tileLightIndicesBuffer;
|
||||||
|
std::uint32_t numLights;
|
||||||
|
std::uint32_t tileCountX;
|
||||||
|
std::uint32_t debugLightHeatmap;
|
||||||
|
|
||||||
|
std::uint32_t shadowMapId;
|
||||||
|
float shadowNormalOffset;
|
||||||
|
std::uint32_t padding;
|
||||||
|
glm::mat4 lightViewProj;
|
||||||
};
|
};
|
||||||
|
|
||||||
NBuffer sceneDataBuffer;
|
NBuffer sceneDataBuffer;
|
||||||
|
|
||||||
|
|
||||||
std::unique_ptr<MeshPipeline> meshPipeline;
|
std::unique_ptr<MeshPipeline> meshPipeline;
|
||||||
std::unique_ptr<SkyboxPipeline> skyboxPipeline;
|
std::unique_ptr<SkyboxPipeline> skyboxPipeline;
|
||||||
std::unique_ptr<LineRenderingPass> lineRenderingPass;
|
std::unique_ptr<LineRenderingPass> lineRenderingPass;
|
||||||
std::unique_ptr<QuadRendererPass> quadRendererPass;
|
std::unique_ptr<QuadRendererPass> quadRendererPass;
|
||||||
|
|
||||||
std::unique_ptr<SkinningPipeline> skinningPipeline;
|
std::unique_ptr<SkinningPipeline> skinningPipeline;
|
||||||
|
std::unique_ptr<LightCullingPipeline> lightCullingPipeline;
|
||||||
|
std::unique_ptr<ShadowMapPipeline> shadowMapPipeline;
|
||||||
|
|
||||||
|
std::optional<glm::vec3> shadowLightDirection;
|
||||||
|
|
||||||
|
bool renderLightHeatmap{false};
|
||||||
bool initialized{false};
|
bool initialized{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef RESOURCESINSPECTOR_H
|
||||||
|
#define RESOURCESINSPECTOR_H
|
||||||
|
|
||||||
|
#include <destrum/Graphics/ids.h>
|
||||||
|
|
||||||
|
struct GPUImage;
|
||||||
|
class ImguiPass;
|
||||||
|
class RenderResources;
|
||||||
|
|
||||||
|
// ImGui windows for looking into the render resource caches
|
||||||
|
class ResourcesInspector final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void drawImageCacheWindow(bool* open, const RenderResources& resources, ImguiPass& imguiPass);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct PreviewInfo
|
||||||
|
{
|
||||||
|
bool previewable{false};
|
||||||
|
const char* reason{""};
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] static PreviewInfo getPreviewInfo(const GPUImage& image);
|
||||||
|
|
||||||
|
void drawImagePreview(const GPUImage& image, float maxSize, ImguiPass& imguiPass) const;
|
||||||
|
void drawImageDetails(ImageID id, const RenderResources& resources) const;
|
||||||
|
[[nodiscard]] bool matchesFilter(ImageID id, const RenderResources& resources) const;
|
||||||
|
|
||||||
|
char filter[128]{};
|
||||||
|
float thumbnailSize{48.0f};
|
||||||
|
bool onlyPreviewable{false};
|
||||||
|
ImageID selectedImageId{NULL_IMAGE_ID};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //RESOURCESINSPECTOR_H
|
||||||
@@ -77,6 +77,8 @@ public:
|
|||||||
[[nodiscard]] VkFormat getFormat() const { return m_swapchain.image_format; }
|
[[nodiscard]] VkFormat getFormat() const { return m_swapchain.image_format; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void nameSwapchainObjects(VkDevice device) const;
|
||||||
|
|
||||||
struct FrameData {
|
struct FrameData {
|
||||||
VkSemaphore swapchainSemaphore{VK_NULL_HANDLE};
|
VkSemaphore swapchainSemaphore{VK_NULL_HANDLE};
|
||||||
VkFence renderFence{VK_NULL_HANDLE};
|
VkFence renderFence{VK_NULL_HANDLE};
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#ifndef UTIL_H
|
#ifndef UTIL_H
|
||||||
#define UTIL_H
|
#define UTIL_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
@@ -87,6 +89,9 @@ namespace vkutil {
|
|||||||
VkDeviceSize offset = 0,
|
VkDeviceSize offset = 0,
|
||||||
VkDeviceSize size = VK_WHOLE_SIZE);
|
VkDeviceSize size = VK_WHOLE_SIZE);
|
||||||
|
|
||||||
|
// Object names (VK_EXT_debug_utils), shown in RenderDoc, Nsight and validation messages.
|
||||||
|
// All of these are no-ops when the extension isn't loaded.
|
||||||
|
void addDebugLabel(VkDevice device, VkObjectType objectType, std::uint64_t objectHandle, const char* label);
|
||||||
void addDebugLabel(VkDevice device, VkImage image, const char* label);
|
void addDebugLabel(VkDevice device, VkImage image, const char* label);
|
||||||
void addDebugLabel(VkDevice device, VkImageView imageView, const char* label);
|
void addDebugLabel(VkDevice device, VkImageView imageView, const char* label);
|
||||||
void addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* label);
|
void addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* label);
|
||||||
@@ -94,6 +99,62 @@ namespace vkutil {
|
|||||||
void addDebugLabel(VkDevice device, VkPipelineLayout layout, const char* label);
|
void addDebugLabel(VkDevice device, VkPipelineLayout layout, const char* label);
|
||||||
void addDebugLabel(VkDevice device, VkBuffer buffer, const char* label);
|
void addDebugLabel(VkDevice device, VkBuffer buffer, const char* label);
|
||||||
void addDebugLabel(VkDevice device, VkSampler sampler, const char* label);
|
void addDebugLabel(VkDevice device, VkSampler sampler, const char* label);
|
||||||
|
void addDebugLabel(VkDevice device, VkDescriptorPool descriptorPool, const char* label);
|
||||||
|
void addDebugLabel(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const char* label);
|
||||||
|
void addDebugLabel(VkDevice device, VkDescriptorSet descriptorSet, const char* label);
|
||||||
|
void addDebugLabel(VkDevice device, VkCommandPool commandPool, const char* label);
|
||||||
|
void addDebugLabel(VkDevice device, VkCommandBuffer commandBuffer, const char* label);
|
||||||
|
void addDebugLabel(VkDevice device, VkFence fence, const char* label);
|
||||||
|
void addDebugLabel(VkDevice device, VkSemaphore semaphore, const char* label);
|
||||||
|
void addDebugLabel(VkDevice device, VkQueue queue, const char* label);
|
||||||
|
|
||||||
|
// Command buffer regions, they nest like a stack: every begin needs a matching end
|
||||||
|
// in the same command buffer.
|
||||||
|
[[nodiscard]] bool debugLabelsEnabled();
|
||||||
|
void cmdBeginLabel(VkCommandBuffer cmd, const char* label, const glm::vec4& color = glm::vec4{1.0f});
|
||||||
|
void cmdEndLabel(VkCommandBuffer cmd);
|
||||||
|
void cmdInsertLabel(VkCommandBuffer cmd, const char* label, const glm::vec4& color = glm::vec4{1.0f});
|
||||||
|
|
||||||
|
// printf style, only formats when labels are actually recorded (cheap enough for per draw labels)
|
||||||
|
template <typename... Args>
|
||||||
|
void cmdBeginLabelFormatted(VkCommandBuffer cmd, const glm::vec4& color, const char* format, Args... args)
|
||||||
|
{
|
||||||
|
if (!debugLabelsEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char label[256];
|
||||||
|
std::snprintf(label, sizeof(label), format, args...);
|
||||||
|
cmdBeginLabel(cmd, label, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScopedCmdLabel {
|
||||||
|
public:
|
||||||
|
ScopedCmdLabel(VkCommandBuffer cmd, const char* label, const glm::vec4& color = glm::vec4{1.0f});
|
||||||
|
~ScopedCmdLabel();
|
||||||
|
|
||||||
|
ScopedCmdLabel(const ScopedCmdLabel& other) = delete;
|
||||||
|
ScopedCmdLabel(ScopedCmdLabel&& other) noexcept = delete;
|
||||||
|
ScopedCmdLabel& operator=(const ScopedCmdLabel& other) = delete;
|
||||||
|
ScopedCmdLabel& operator=(ScopedCmdLabel&& other) noexcept = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkCommandBuffer cmd{VK_NULL_HANDLE};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Colors used for the renderer's labels, so passes are easy to tell apart in a capture
|
||||||
|
namespace labelColors {
|
||||||
|
inline const glm::vec4 frame{0.8f, 0.8f, 0.8f, 1.0f};
|
||||||
|
inline const glm::vec4 compute{0.9f, 0.5f, 0.1f, 1.0f};
|
||||||
|
inline const glm::vec4 shadow{0.3f, 0.3f, 0.6f, 1.0f};
|
||||||
|
inline const glm::vec4 depth{0.4f, 0.4f, 0.4f, 1.0f};
|
||||||
|
inline const glm::vec4 opaque{0.2f, 0.7f, 0.3f, 1.0f};
|
||||||
|
inline const glm::vec4 sky{0.3f, 0.6f, 0.9f, 1.0f};
|
||||||
|
inline const glm::vec4 debug{0.9f, 0.2f, 0.6f, 1.0f};
|
||||||
|
inline const glm::vec4 ui{0.9f, 0.9f, 0.2f, 1.0f};
|
||||||
|
inline const glm::vec4 transfer{0.6f, 0.4f, 0.2f, 1.0f};
|
||||||
|
inline const glm::vec4 draw{0.5f, 0.8f, 0.5f, 1.0f};
|
||||||
|
}
|
||||||
|
|
||||||
struct RenderingInfoParams {
|
struct RenderingInfoParams {
|
||||||
VkExtent2D renderExtent;
|
VkExtent2D renderExtent;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public:
|
|||||||
void FixedUpdate(float dt);
|
void FixedUpdate(float dt);
|
||||||
void LateUpdate(float dt);
|
void LateUpdate(float dt);
|
||||||
void Render(const RenderContext& ctx);
|
void Render(const RenderContext& ctx);
|
||||||
void RenderImgui();
|
void RenderImgui(bool* open = nullptr);
|
||||||
|
|
||||||
void CleanupDestroyedGameObjects();
|
void CleanupDestroyedGameObjects();
|
||||||
void Unload();
|
void Unload();
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public:
|
|||||||
void LateUpdate(float dt);
|
void LateUpdate(float dt);
|
||||||
|
|
||||||
void Render(const RenderContext& ctx);
|
void Render(const RenderContext& ctx);
|
||||||
void RenderImgui();
|
void RenderImgui(bool* open = nullptr);
|
||||||
|
|
||||||
void HandleGameObjectDestroy();
|
void HandleGameObjectDestroy();
|
||||||
void DestroyGameObjects();
|
void DestroyGameObjects();
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
#ifndef DESTRUM_ENGINECOMPONENTLIST_H
|
#ifndef DESTRUM_ENGINECOMPONENTLIST_H
|
||||||
#define DESTRUM_ENGINECOMPONENTLIST_H
|
#define DESTRUM_ENGINECOMPONENTLIST_H
|
||||||
|
|
||||||
// Keep the component manifest in one place. Consumers define X before
|
|
||||||
// expanding one of these lists to generate registration or other metadata.
|
|
||||||
#include <destrum/Components/Animator.h>
|
#include <destrum/Components/Animator.h>
|
||||||
|
#include <destrum/Components/LightComponent.h>
|
||||||
#include <destrum/Components/MeshRendererComponent.h>
|
#include <destrum/Components/MeshRendererComponent.h>
|
||||||
#include <destrum/Components/OrbitAndSpin.h>
|
#include <destrum/Components/OrbitAndSpin.h>
|
||||||
#include <destrum/Components/Rotator.h>
|
#include <destrum/Components/Rotator.h>
|
||||||
@@ -15,6 +14,7 @@
|
|||||||
|
|
||||||
#define DESTRUM_ENGINE_COMPONENTS(X) \
|
#define DESTRUM_ENGINE_COMPONENTS(X) \
|
||||||
X(MeshRendererComponent) \
|
X(MeshRendererComponent) \
|
||||||
|
X(LightComponent) \
|
||||||
X(Rotator) \
|
X(Rotator) \
|
||||||
X(Spinner) \
|
X(Spinner) \
|
||||||
X(OrbitAndSpin) \
|
X(OrbitAndSpin) \
|
||||||
|
|||||||
@@ -22,11 +22,8 @@ public:
|
|||||||
static bool Load(Scene& scene, const std::filesystem::path& path,
|
static bool Load(Scene& scene, const std::filesystem::path& path,
|
||||||
std::function<void(float)> progressCallback = nullptr);
|
std::function<void(float)> progressCallback = nullptr);
|
||||||
|
|
||||||
// Thread-safe: parse and validate a scene file on any thread.
|
|
||||||
// No engine state is touched during parsing.
|
|
||||||
static LoadResult LoadSceneFile(const std::filesystem::path& path);
|
static LoadResult LoadSceneFile(const std::filesystem::path& path);
|
||||||
|
|
||||||
// Main-thread-only: construct a scene from pre-validated JSON.
|
|
||||||
static bool ConstructScene(Scene& scene, LoadResult& result,
|
static bool ConstructScene(Scene& scene, LoadResult& result,
|
||||||
std::function<void(float)> progressCallback = nullptr);
|
std::function<void(float)> progressCallback = nullptr);
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ namespace ImGuiUtils {
|
|||||||
std::string_view sceneName,
|
std::string_view sceneName,
|
||||||
const std::vector<std::shared_ptr<GameObject>>& objects,
|
const std::vector<std::shared_ptr<GameObject>>& objects,
|
||||||
const std::vector<std::shared_ptr<GameObject>>& pendingAdditions,
|
const std::vector<std::shared_ptr<GameObject>>& pendingAdditions,
|
||||||
ObjectId& selectedObjectId);
|
ObjectId& selectedObjectId,
|
||||||
|
bool* open = nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // DESTRUM_IMGUIUTILS_H
|
#endif // DESTRUM_IMGUIUTILS_H
|
||||||
|
|||||||
+48
-3
@@ -195,13 +195,31 @@ if (!isRunning) break;
|
|||||||
if (m_params.showDebugUi)
|
if (m_params.showDebugUi)
|
||||||
{
|
{
|
||||||
ZoneScopedN("Debug ImGui");
|
ZoneScopedN("Debug ImGui");
|
||||||
SceneManager::GetInstance().RenderImgui();
|
drawWindowsMenu();
|
||||||
|
|
||||||
ImGui::Begin("Debug");
|
if (m_DebugWindows.inspector)
|
||||||
|
{
|
||||||
|
SceneManager::GetInstance().RenderImgui(&m_DebugWindows.inspector);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_DebugWindows.stats)
|
||||||
|
{
|
||||||
|
if (ImGui::Begin("Debug", &m_DebugWindows.stats))
|
||||||
|
{
|
||||||
ImGui::Text("FPS: %.2f", avgFPS);
|
ImGui::Text("FPS: %.2f", avgFPS);
|
||||||
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_DebugWindows.physics)
|
||||||
|
{
|
||||||
drawPhysicsPanel(dt, fixedDt, accumulator);
|
drawPhysicsPanel(dt, fixedDt, accumulator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_DebugWindows.imageCache)
|
||||||
|
{
|
||||||
|
m_ResourcesInspector.drawImageCacheWindow(&m_DebugWindows.imageCache, resources, imguiPass);
|
||||||
|
}
|
||||||
|
|
||||||
TracyPlot("FPS", avgFPS);
|
TracyPlot("FPS", avgFPS);
|
||||||
TracyPlot("Frame Time ms", dt * 1000.0f);
|
TracyPlot("Frame Time ms", dt * 1000.0f);
|
||||||
@@ -359,9 +377,36 @@ void App::cleanup()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void App::drawWindowsMenu()
|
||||||
|
{
|
||||||
|
// ImGui appends to the main menu bar, so this ends up next to whatever menus the game added
|
||||||
|
if (!ImGui::BeginMainMenuBar())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginMenu("Windows"))
|
||||||
|
{
|
||||||
|
ImGui::MenuItem("Debug", nullptr, &m_DebugWindows.stats);
|
||||||
|
ImGui::MenuItem("Physics", nullptr, &m_DebugWindows.physics);
|
||||||
|
ImGui::MenuItem("Inspector", nullptr, &m_DebugWindows.inspector);
|
||||||
|
ImGui::MenuItem("Image Cache", nullptr, &m_DebugWindows.imageCache);
|
||||||
|
|
||||||
|
customWindowsMenu();
|
||||||
|
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndMainMenuBar();
|
||||||
|
}
|
||||||
|
|
||||||
void App::drawPhysicsPanel(float dt, float fixedDt, float accumulator)
|
void App::drawPhysicsPanel(float dt, float fixedDt, float accumulator)
|
||||||
{
|
{
|
||||||
ImGui::Begin("Physics");
|
if (!ImGui::Begin("Physics", &m_DebugWindows.physics))
|
||||||
|
{
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Text("Frame dt: %.3f ms", dt * 1000.0f);
|
ImGui::Text("Frame dt: %.3f ms", dt * 1000.0f);
|
||||||
ImGui::Text("Fixed dt: %.3f ms", fixedDt * 1000.0f);
|
ImGui::Text("Fixed dt: %.3f ms", fixedDt * 1000.0f);
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
#include <destrum/Components/LightComponent.h>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/RenderContext.h>
|
||||||
|
#include <destrum/ObjectModel/Transform.h>
|
||||||
|
|
||||||
|
#include <glm/gtc/constants.hpp>
|
||||||
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
#include <glm/gtx/quaternion.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
LightComponent::LightComponent(GameObject& parent, LightType type)
|
||||||
|
: Component(parent, "LightComponent")
|
||||||
|
, m_Type(type)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightComponent::SetIntensity(float intensity)
|
||||||
|
{
|
||||||
|
m_Intensity = std::max(intensity, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightComponent::SetRange(float range)
|
||||||
|
{
|
||||||
|
m_Range = std::max(range, 0.01f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightComponent::SetConeAngles(float innerConeAngle, float outerConeAngle)
|
||||||
|
{
|
||||||
|
m_InnerConeAngle = std::clamp(innerConeAngle, 0.0f, glm::half_pi<float>());
|
||||||
|
m_OuterConeAngle = std::clamp(outerConeAngle, m_InnerConeAngle, glm::half_pi<float>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightComponent::Render(const RenderContext& ctx)
|
||||||
|
{
|
||||||
|
auto& transform = GetTransform();
|
||||||
|
|
||||||
|
ctx.renderer.submitLight(Light{
|
||||||
|
.type = m_Type,
|
||||||
|
.position = transform.GetWorldPosition(),
|
||||||
|
.direction = transform.GetWorldRotation() * glm::vec3{0.0f, 0.0f, -1.0f},
|
||||||
|
.color = m_Color,
|
||||||
|
.intensity = m_Intensity,
|
||||||
|
.range = m_Range,
|
||||||
|
.innerConeAngle = m_InnerConeAngle,
|
||||||
|
.outerConeAngle = m_OuterConeAngle,
|
||||||
|
.castShadows = m_CastShadows,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json LightComponent::Serialize() const {
|
||||||
|
return {
|
||||||
|
{"type", static_cast<std::uint32_t>(m_Type)},
|
||||||
|
{"color", {m_Color.x, m_Color.y, m_Color.z}},
|
||||||
|
{"intensity", m_Intensity},
|
||||||
|
{"range", m_Range},
|
||||||
|
{"innerConeAngle", m_InnerConeAngle},
|
||||||
|
{"outerConeAngle", m_OuterConeAngle},
|
||||||
|
{"castShadows", m_CastShadows}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightComponent::Deserialize(const nlohmann::json& data) {
|
||||||
|
if (data.contains("type")) {
|
||||||
|
const auto type = data.at("type").get<std::uint32_t>();
|
||||||
|
if (type <= static_cast<std::uint32_t>(LightType::Spot)) {
|
||||||
|
m_Type = static_cast<LightType>(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.contains("color")) {
|
||||||
|
const auto& value = data.at("color");
|
||||||
|
m_Color = {value.at(0).get<float>(), value.at(1).get<float>(), value.at(2).get<float>()};
|
||||||
|
}
|
||||||
|
if (data.contains("intensity")) {
|
||||||
|
SetIntensity(data.at("intensity").get<float>());
|
||||||
|
}
|
||||||
|
if (data.contains("range")) {
|
||||||
|
SetRange(data.at("range").get<float>());
|
||||||
|
}
|
||||||
|
|
||||||
|
float innerConeAngle = m_InnerConeAngle;
|
||||||
|
float outerConeAngle = m_OuterConeAngle;
|
||||||
|
if (data.contains("innerConeAngle")) {
|
||||||
|
innerConeAngle = data.at("innerConeAngle").get<float>();
|
||||||
|
}
|
||||||
|
if (data.contains("outerConeAngle")) {
|
||||||
|
outerConeAngle = data.at("outerConeAngle").get<float>();
|
||||||
|
}
|
||||||
|
SetConeAngles(innerConeAngle, outerConeAngle);
|
||||||
|
|
||||||
|
if (data.contains("castShadows")) {
|
||||||
|
m_CastShadows = data.at("castShadows").get<bool>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightComponent::ImGuiInspector() {
|
||||||
|
constexpr std::array typeNames{"Directional", "Point", "Spot"};
|
||||||
|
|
||||||
|
int type = static_cast<int>(m_Type);
|
||||||
|
if (ImGui::Combo("Type", &type, typeNames.data(), static_cast<int>(typeNames.size()))) {
|
||||||
|
SetType(static_cast<LightType>(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::ColorEdit3("Color", &m_Color.x);
|
||||||
|
|
||||||
|
float intensity = m_Intensity;
|
||||||
|
if (ImGui::DragFloat("Intensity", &intensity, 0.05f, 0.0f)) {
|
||||||
|
SetIntensity(intensity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Type == LightType::Directional) {
|
||||||
|
ImGui::Checkbox("Cast Shadows", &m_CastShadows);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::TextDisabled("(first directional light only)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float range = m_Range;
|
||||||
|
if (ImGui::DragFloat("Range", &range, 0.05f, 0.01f)) {
|
||||||
|
SetRange(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Type == LightType::Spot) {
|
||||||
|
float innerConeAngle = m_InnerConeAngle;
|
||||||
|
float outerConeAngle = m_OuterConeAngle;
|
||||||
|
bool changed = ImGui::SliderAngle("Inner Cone", &innerConeAngle, 0.0f, 90.0f);
|
||||||
|
changed |= ImGui::SliderAngle("Outer Cone", &outerConeAngle, 0.0f, 90.0f);
|
||||||
|
if (changed) {
|
||||||
|
SetConeAngles(innerConeAngle, outerConeAngle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,9 @@ void MeshRendererComponent::Start() {
|
|||||||
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT |
|
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT |
|
||||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
|
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const std::string bufferName = GetGameObject()->GetName() + " (skinned vertices)";
|
||||||
|
vkutil::addDebugLabel(gfxDevice.getDevice(), m_skinnedMesh->skinnedVertexBuffer.buffer, bufferName.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ void BindlessSetManager::init(
|
|||||||
};
|
};
|
||||||
|
|
||||||
VK_CHECK(vkCreateDescriptorPool(device, &poolInfo, nullptr, &descPool));
|
VK_CHECK(vkCreateDescriptorPool(device, &poolInfo, nullptr, &descPool));
|
||||||
|
vkutil::addDebugLabel(device, descPool, "bindless descriptor pool");
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // build desc set layout
|
{ // build desc set layout
|
||||||
@@ -111,6 +112,7 @@ void BindlessSetManager::init(
|
|||||||
};
|
};
|
||||||
|
|
||||||
VK_CHECK(vkCreateDescriptorSetLayout(device, &info, nullptr, &descSetLayout));
|
VK_CHECK(vkCreateDescriptorSetLayout(device, &info, nullptr, &descSetLayout));
|
||||||
|
vkutil::addDebugLabel(device, descSetLayout, "bindless descriptor set layout");
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // alloc desc set
|
{ // alloc desc set
|
||||||
@@ -122,6 +124,7 @@ void BindlessSetManager::init(
|
|||||||
};
|
};
|
||||||
|
|
||||||
VK_CHECK(vkAllocateDescriptorSets(device, &allocInfo, &descSet));
|
VK_CHECK(vkAllocateDescriptorSets(device, &allocInfo, &descSet));
|
||||||
|
vkutil::addDebugLabel(device, descSet, "bindless descriptor set");
|
||||||
}
|
}
|
||||||
|
|
||||||
initDefaultSamplers(device, maxAnisotropy);
|
initDefaultSamplers(device, maxAnisotropy);
|
||||||
|
|||||||
@@ -99,8 +99,13 @@ void ComputePipeline::CreateComputePipeline(const std::string& compPath,
|
|||||||
throw std::runtime_error("Can't make compute pipeline!");
|
throw std::runtime_error("Can't make compute pipeline!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vkutil::addDebugLabel(m_device.getDevice(), m_compShaderModule, compFileName.c_str());
|
||||||
|
|
||||||
if (!configInfo.name.empty()) {
|
if (!configInfo.name.empty()) {
|
||||||
vkutil::addDebugLabel(GameState::GetInstance().Gfx().getDevice(), m_computePipeline, configInfo.name.c_str());
|
vkutil::addDebugLabel(m_device.getDevice(), m_computePipeline, configInfo.name.c_str());
|
||||||
|
|
||||||
|
const std::string layoutName = configInfo.name + " Layout";
|
||||||
|
vkutil::addDebugLabel(m_device.getDevice(), configInfo.pipelineLayout, layoutName.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const E
|
|||||||
|
|
||||||
auto swapchainLayout = VK_IMAGE_LAYOUT_UNDEFINED; {
|
auto swapchainLayout = VK_IMAGE_LAYOUT_UNDEFINED; {
|
||||||
ZoneScopedN("Clear Swapchain Image");
|
ZoneScopedN("Clear Swapchain Image");
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "Clear Swapchain Image", vkutil::labelColors::transfer);
|
||||||
|
|
||||||
const VkImageSubresourceRange clearRange = vkinit::imageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT);
|
const VkImageSubresourceRange clearRange = vkinit::imageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT);
|
||||||
vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_GENERAL);
|
vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_GENERAL);
|
||||||
@@ -167,6 +168,7 @@ void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const E
|
|||||||
|
|
||||||
if (true) {
|
if (true) {
|
||||||
ZoneScopedN("Copy DrawImage To Swapchain");
|
ZoneScopedN("Copy DrawImage To Swapchain");
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "Copy DrawImage To Swapchain", vkutil::labelColors::transfer);
|
||||||
|
|
||||||
vkutil::transitionImage(
|
vkutil::transitionImage(
|
||||||
cmd,
|
cmd,
|
||||||
@@ -190,6 +192,7 @@ void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const E
|
|||||||
|
|
||||||
if (props.imguiPass) {
|
if (props.imguiPass) {
|
||||||
ZoneScopedN("ImGui Render");
|
ZoneScopedN("ImGui Render");
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "ImGui Render", vkutil::labelColors::ui);
|
||||||
|
|
||||||
props.imguiPass->render(
|
props.imguiPass->render(
|
||||||
cmd,
|
cmd,
|
||||||
@@ -202,6 +205,7 @@ void GfxDevice::endFrame(VkCommandBuffer cmd, const GPUImage& drawImage, const E
|
|||||||
|
|
||||||
{
|
{
|
||||||
ZoneScopedN("Transition Swapchain To Present");
|
ZoneScopedN("Transition Swapchain To Present");
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "Transition Swapchain To Present", vkutil::labelColors::transfer);
|
||||||
|
|
||||||
vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
vkutil::transitionImage(cmd, swapchainImage, swapchainLayout, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||||
swapchainLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
swapchainLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ void VulkanImmediateExecutor::init(
|
|||||||
};
|
};
|
||||||
VK_CHECK(vkCreateFence(deviceHandle, &fenceCreateInfo, nullptr, &immFence));
|
VK_CHECK(vkCreateFence(deviceHandle, &fenceCreateInfo, nullptr, &immFence));
|
||||||
|
|
||||||
|
vkutil::addDebugLabel(deviceHandle, immCommandPool, "immediate command pool");
|
||||||
|
vkutil::addDebugLabel(deviceHandle, immCommandBuffer, "immediate command buffer");
|
||||||
|
vkutil::addDebugLabel(deviceHandle, immFence, "immediate fence");
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
cleanup(deviceHandle);
|
cleanup(deviceHandle);
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ void FrameManager::init(VkDevice dev, std::uint32_t queueFamily) {
|
|||||||
const auto cmdAllocInfo = vkinit::commandBufferAllocateInfo(commandPool, 1);
|
const auto cmdAllocInfo = vkinit::commandBufferAllocateInfo(commandPool, 1);
|
||||||
auto& mainCommandBuffer = frames[i].commandBuffer;
|
auto& mainCommandBuffer = frames[i].commandBuffer;
|
||||||
VK_CHECK(vkAllocateCommandBuffers(device, &cmdAllocInfo, &mainCommandBuffer));
|
VK_CHECK(vkAllocateCommandBuffers(device, &cmdAllocInfo, &mainCommandBuffer));
|
||||||
|
|
||||||
|
const std::string poolName = "frame " + std::to_string(i) + " command pool";
|
||||||
|
const std::string cmdName = "frame " + std::to_string(i) + " command buffer";
|
||||||
|
vkutil::addDebugLabel(device, commandPool, poolName.c_str());
|
||||||
|
vkutil::addDebugLabel(device, mainCommandBuffer, cmdName.c_str());
|
||||||
}
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
cleanup(dev);
|
cleanup(dev);
|
||||||
|
|||||||
@@ -178,7 +178,9 @@ std::optional<GPUImage> ImageManager::loadImageFromFile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
image.debugName = path.string();
|
image.debugName = path.string();
|
||||||
vkutil::addDebugLabel(device, image.image, path.string().c_str());
|
vkutil::addDebugLabel(device, image.image, image.debugName.c_str());
|
||||||
|
const std::string viewName = image.debugName + " (view)";
|
||||||
|
vkutil::addDebugLabel(device, image.imageView, viewName.c_str());
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,6 +156,9 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh
|
|||||||
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||||
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
|
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
|
||||||
|
|
||||||
|
const auto skinningBufferName = cpuMesh.name + " (skinning data)";
|
||||||
|
vkutil::addDebugLabel(gfxDevice.getDevice(), gpuMesh.skinningDataBuffer.buffer, skinningBufferName.c_str());
|
||||||
|
|
||||||
auto skinningStaging =
|
auto skinningStaging =
|
||||||
gfxDevice.createBuffer(
|
gfxDevice.createBuffer(
|
||||||
skinningDataSize,
|
skinningDataSize,
|
||||||
|
|||||||
@@ -146,11 +146,11 @@ void Pipeline::CreateGraphicsPipeline(const std::string& vertPath, const std::st
|
|||||||
|
|
||||||
CreateShaderModule(vertCode, &m_vertShaderModule);
|
CreateShaderModule(vertCode, &m_vertShaderModule);
|
||||||
|
|
||||||
// vkutil::addDebugLabel(
|
vkutil::addDebugLabel(
|
||||||
// m_device.getDevice(),
|
m_device.getDevice(),
|
||||||
// m_fragShaderModule,
|
m_vertShaderModule,
|
||||||
// VertFileName.c_str()
|
VertFileName.c_str()
|
||||||
// );
|
);
|
||||||
|
|
||||||
VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
|
VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
|
||||||
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||||
@@ -173,11 +173,11 @@ void Pipeline::CreateGraphicsPipeline(const std::string& vertPath, const std::st
|
|||||||
|
|
||||||
CreateShaderModule(fragCode, &m_fragShaderModule);
|
CreateShaderModule(fragCode, &m_fragShaderModule);
|
||||||
|
|
||||||
// vkutil::addDebugLabel(
|
vkutil::addDebugLabel(
|
||||||
// m_device.getDevice(),
|
m_device.getDevice(),
|
||||||
// m_fragShaderModule,
|
m_fragShaderModule,
|
||||||
// FragFileName.c_str()
|
FragFileName.c_str()
|
||||||
// );
|
);
|
||||||
|
|
||||||
|
|
||||||
VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
|
VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
|
||||||
@@ -231,18 +231,17 @@ void Pipeline::CreateGraphicsPipeline(const std::string& vertPath, const std::st
|
|||||||
pipelineInfo.basePipelineIndex = -1;
|
pipelineInfo.basePipelineIndex = -1;
|
||||||
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
|
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
|
||||||
|
|
||||||
if (!configInfo.name.empty()) {
|
|
||||||
// DebugLabel::SetObjectName(
|
|
||||||
// reinterpret_cast<uint64_t>(pipelineInfo.layout),
|
|
||||||
// VK_OBJECT_TYPE_PIPELINE_LAYOUT,
|
|
||||||
// configInfo.name.c_str()
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (vkCreateGraphicsPipelines(m_device.getDevice(), VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &m_graphicsPipeline) != VK_SUCCESS) {
|
if (vkCreateGraphicsPipelines(m_device.getDevice(), VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &m_graphicsPipeline) != VK_SUCCESS) {
|
||||||
throw std::runtime_error("Can't make pipeline!");
|
throw std::runtime_error("Can't make pipeline!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!configInfo.name.empty()) {
|
||||||
|
vkutil::addDebugLabel(m_device.getDevice(), m_graphicsPipeline, configInfo.name.c_str());
|
||||||
|
|
||||||
|
const std::string layoutName = configInfo.name + " Layout";
|
||||||
|
vkutil::addDebugLabel(m_device.getDevice(), configInfo.pipelineLayout, layoutName.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Pipeline::CreateShaderModule(const std::vector<char>& code, VkShaderModule* shaderModule) const {
|
void Pipeline::CreateShaderModule(const std::vector<char>& code, VkShaderModule* shaderModule) const {
|
||||||
|
|||||||
@@ -122,6 +122,15 @@ void ImguiPass::beginFrame() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the previous UI was built but never rendered (e.g. the frame got skipped while resizing),
|
||||||
|
// so the GPU never saw these
|
||||||
|
std::vector<VkDescriptorSet> unusedTextures;
|
||||||
|
for (const FrameTexture& texture : pendingTextures) {
|
||||||
|
unusedTextures.push_back(texture.descriptorSet);
|
||||||
|
}
|
||||||
|
pendingTextures.clear();
|
||||||
|
releaseTextures(unusedTextures);
|
||||||
|
|
||||||
ImGui_ImplVulkan_NewFrame();
|
ImGui_ImplVulkan_NewFrame();
|
||||||
ImGui_ImplSDL2_NewFrame();
|
ImGui_ImplSDL2_NewFrame();
|
||||||
|
|
||||||
@@ -155,6 +164,18 @@ void ImguiPass::render(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// beginFrame on the gfx device already waited for this frame index' fence,
|
||||||
|
// so the textures it used last time around can go
|
||||||
|
auto& inFlight = inFlightTextures[gfx->getCurrentFrameIndex() % FRAMES_IN_FLIGHT];
|
||||||
|
releaseTextures(inFlight);
|
||||||
|
|
||||||
|
for (const FrameTexture& texture : pendingTextures) {
|
||||||
|
inFlight.push_back(texture.descriptorSet);
|
||||||
|
}
|
||||||
|
pendingTextures.clear();
|
||||||
|
}
|
||||||
|
|
||||||
ImDrawData* drawData = ImGui::GetDrawData();
|
ImDrawData* drawData = ImGui::GetDrawData();
|
||||||
if (drawData == nullptr || drawData->CmdListsCount == 0) {
|
if (drawData == nullptr || drawData->CmdListsCount == 0) {
|
||||||
return;
|
return;
|
||||||
@@ -200,6 +221,8 @@ void ImguiPass::onSwapchainRecreated() {
|
|||||||
const auto newFormat = gfx->getSwapchainFormat();
|
const auto newFormat = gfx->getSwapchainFormat();
|
||||||
if (newFormat != colorFormat) {
|
if (newFormat != colorFormat) {
|
||||||
if (vulkanInitialized) {
|
if (vulkanInitialized) {
|
||||||
|
// the swapchain recreate waited for the device, nothing is in flight anymore
|
||||||
|
releaseAllTextures();
|
||||||
ImGui_ImplVulkan_Shutdown();
|
ImGui_ImplVulkan_Shutdown();
|
||||||
vulkanInitialized = false;
|
vulkanInitialized = false;
|
||||||
}
|
}
|
||||||
@@ -224,6 +247,7 @@ void ImguiPass::cleanup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (vulkanInitialized) {
|
if (vulkanInitialized) {
|
||||||
|
releaseAllTextures();
|
||||||
ImGui_ImplVulkan_Shutdown();
|
ImGui_ImplVulkan_Shutdown();
|
||||||
vulkanInitialized = false;
|
vulkanInitialized = false;
|
||||||
}
|
}
|
||||||
@@ -293,3 +317,49 @@ void ImguiPass::transitionToColorAttachment(
|
|||||||
|
|
||||||
vkCmdPipelineBarrier2(cmd, &dependencyInfo);
|
vkCmdPipelineBarrier2(cmd, &dependencyInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImTextureID ImguiPass::getFrameTexture(VkImageView imageView, VkImageLayout imageLayout) {
|
||||||
|
if (!vulkanInitialized || imageView == VK_NULL_HANDLE) {
|
||||||
|
return ImTextureID{};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const FrameTexture& texture : pendingTextures) {
|
||||||
|
if (texture.imageView == imageView && texture.imageLayout == imageLayout) {
|
||||||
|
return reinterpret_cast<ImTextureID>(texture.descriptorSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const VkDescriptorSet descriptorSet = ImGui_ImplVulkan_AddTexture(imageView, imageLayout);
|
||||||
|
if (descriptorSet == VK_NULL_HANDLE) {
|
||||||
|
return ImTextureID{};
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingTextures.push_back(FrameTexture{
|
||||||
|
.imageView = imageView,
|
||||||
|
.imageLayout = imageLayout,
|
||||||
|
.descriptorSet = descriptorSet,
|
||||||
|
});
|
||||||
|
return reinterpret_cast<ImTextureID>(descriptorSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImguiPass::releaseTextures(std::vector<VkDescriptorSet>& textures) {
|
||||||
|
if (vulkanInitialized) {
|
||||||
|
for (VkDescriptorSet descriptorSet : textures) {
|
||||||
|
ImGui_ImplVulkan_RemoveTexture(descriptorSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textures.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImguiPass::releaseAllTextures() {
|
||||||
|
std::vector<VkDescriptorSet> textures;
|
||||||
|
for (const FrameTexture& texture : pendingTextures) {
|
||||||
|
textures.push_back(texture.descriptorSet);
|
||||||
|
}
|
||||||
|
pendingTextures.clear();
|
||||||
|
releaseTextures(textures);
|
||||||
|
|
||||||
|
for (auto& inFlight : inFlightTextures) {
|
||||||
|
releaseTextures(inFlight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,272 @@
|
|||||||
|
#include <destrum/Graphics/Pipelines/LightCullingPipeline.h>
|
||||||
|
|
||||||
|
#include "destrum/FS/AssetFS.h"
|
||||||
|
#include "destrum/Graphics/GfxDevice.h"
|
||||||
|
#include "destrum/Graphics/RenderResources.h"
|
||||||
|
#include "destrum/Graphics/Util.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "volk.h"
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
|
||||||
|
void LightCullingPipeline::init(GfxDevice& gfxDevice, RenderResources& resources, glm::ivec2 drawImageSize)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const auto& device = gfxDevice.getDevice();
|
||||||
|
|
||||||
|
const auto pushConstant = VkPushConstantRange{
|
||||||
|
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||||
|
.offset = 0,
|
||||||
|
.size = sizeof(PushConstants),
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto pushConstants = std::array{pushConstant};
|
||||||
|
|
||||||
|
const auto layouts = std::array{
|
||||||
|
resources.getBindlessDescSetLayout()
|
||||||
|
};
|
||||||
|
|
||||||
|
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
|
||||||
|
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||||
|
pipelineLayoutInfo.setLayoutCount = static_cast<std::uint32_t>(layouts.size());
|
||||||
|
pipelineLayoutInfo.pSetLayouts = layouts.data();
|
||||||
|
pipelineLayoutInfo.pushConstantRangeCount = static_cast<std::uint32_t>(pushConstants.size());
|
||||||
|
pipelineLayoutInfo.pPushConstantRanges = pushConstants.data();
|
||||||
|
|
||||||
|
if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &m_pipelineLayout) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Could not make light culling pipeline layout");
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputePipelineConfigInfo pipelineConfig{};
|
||||||
|
pipelineConfig.name = "light culling compute pipeline";
|
||||||
|
pipelineConfig.pipelineLayout = m_pipelineLayout;
|
||||||
|
|
||||||
|
const auto lightCullingShaderPath = AssetFS::GetInstance().GetCookedPathForFile(
|
||||||
|
"engine://shaders/light_culling.comp");
|
||||||
|
|
||||||
|
lightCullingPipeline = std::make_unique<ComputePipeline>(
|
||||||
|
gfxDevice,
|
||||||
|
lightCullingShaderPath.generic_string(),
|
||||||
|
pipelineConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < FRAMES_IN_FLIGHT; ++i)
|
||||||
|
{
|
||||||
|
auto& lightsBuffer = framesData[i].lightsBuffer;
|
||||||
|
lightsBuffer.capacity = MAX_LIGHTS;
|
||||||
|
lightsBuffer.buffer = gfxDevice.createBuffer(
|
||||||
|
MAX_LIGHTS * sizeof(GPULight),
|
||||||
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||||
|
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||||
|
vkutil::addDebugLabel(device, lightsBuffer.buffer.buffer, "lights");
|
||||||
|
}
|
||||||
|
|
||||||
|
createTileBuffer(gfxDevice, drawImageSize);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
cleanup(gfxDevice);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightCullingPipeline::cleanup(GfxDevice& gfxDevice)
|
||||||
|
{
|
||||||
|
for (auto& frame : framesData)
|
||||||
|
{
|
||||||
|
gfxDevice.destroyBuffer(frame.lightsBuffer.buffer);
|
||||||
|
frame.lightsBuffer.buffer = {};
|
||||||
|
frame.lightsBuffer.size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
gfxDevice.destroyBuffer(tileLightIndicesBuffer);
|
||||||
|
tileLightIndicesBuffer = {};
|
||||||
|
tileCount = glm::uvec2{0u, 0u};
|
||||||
|
|
||||||
|
if (m_pipelineLayout != VK_NULL_HANDLE && gfxDevice.getDevice() != VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
|
vkDestroyPipelineLayout(gfxDevice.getDevice(), m_pipelineLayout, nullptr);
|
||||||
|
}
|
||||||
|
m_pipelineLayout = VK_NULL_HANDLE;
|
||||||
|
lightCullingPipeline.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightCullingPipeline::resize(GfxDevice& gfxDevice, glm::ivec2 drawImageSize)
|
||||||
|
{
|
||||||
|
gfxDevice.waitIdle();
|
||||||
|
|
||||||
|
gfxDevice.destroyBuffer(tileLightIndicesBuffer);
|
||||||
|
tileLightIndicesBuffer = {};
|
||||||
|
createTileBuffer(gfxDevice, drawImageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightCullingPipeline::createTileBuffer(GfxDevice& gfxDevice, glm::ivec2 drawImageSize)
|
||||||
|
{
|
||||||
|
assert(drawImageSize.x > 0 && drawImageSize.y > 0);
|
||||||
|
|
||||||
|
tileCount = glm::uvec2{
|
||||||
|
(static_cast<std::uint32_t>(drawImageSize.x) + TILE_SIZE - 1) / TILE_SIZE,
|
||||||
|
(static_cast<std::uint32_t>(drawImageSize.y) + TILE_SIZE - 1) / TILE_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::size_t numTiles = static_cast<std::size_t>(tileCount.x) * tileCount.y;
|
||||||
|
tileLightIndicesBuffer = gfxDevice.createBuffer(
|
||||||
|
numTiles * TILE_STRIDE * sizeof(std::uint32_t),
|
||||||
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
||||||
|
vkutil::addDebugLabel(gfxDevice.getDevice(), tileLightIndicesBuffer.buffer, "tile light indices");
|
||||||
|
|
||||||
|
spdlog::debug("Forward+ light grid: {}x{} tiles", tileCount.x, tileCount.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightCullingPipeline::beginDrawing(std::size_t frameIndex)
|
||||||
|
{
|
||||||
|
currentFrameIndex = frameIndex;
|
||||||
|
getCurrentFrameData().lightsBuffer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightCullingPipeline::appendLight(const Light& light)
|
||||||
|
{
|
||||||
|
auto& lightsBuffer = getCurrentFrameData().lightsBuffer;
|
||||||
|
if (lightsBuffer.size >= lightsBuffer.capacity)
|
||||||
|
{
|
||||||
|
if (!warnedAboutLightLimit)
|
||||||
|
{
|
||||||
|
spdlog::warn("Light limit of {} reached, extra lights are ignored", MAX_LIGHTS);
|
||||||
|
warnedAboutLightLimit = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float directionLength = glm::length(light.direction);
|
||||||
|
const glm::vec3 direction = directionLength > 1e-6f
|
||||||
|
? light.direction / directionLength
|
||||||
|
: glm::vec3{0.0f, -1.0f, 0.0f};
|
||||||
|
|
||||||
|
const auto gpuLight = GPULight{
|
||||||
|
.position = light.position,
|
||||||
|
.range = light.range,
|
||||||
|
.color = light.color,
|
||||||
|
.intensity = light.intensity,
|
||||||
|
.direction = direction,
|
||||||
|
.type = static_cast<std::uint32_t>(light.type),
|
||||||
|
.spotInnerCos = std::cos(light.innerConeAngle),
|
||||||
|
.spotOuterCos = std::cos(light.outerConeAngle),
|
||||||
|
.castShadows = light.castShadows ? 1u : 0u,
|
||||||
|
.padding = 0.0f,
|
||||||
|
};
|
||||||
|
lightsBuffer.append(gpuLight);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightCullingPipeline::flushCurrentFrame(VkCommandBuffer cmd, GfxDevice& gfxDevice)
|
||||||
|
{
|
||||||
|
auto& buffer = getCurrentFrameData().lightsBuffer.buffer;
|
||||||
|
gfxDevice.getMemoryManager().flushAllocation(buffer, 0, VK_WHOLE_SIZE);
|
||||||
|
vkutil::bufferHostWriteToShaderReadBarrier(
|
||||||
|
cmd,
|
||||||
|
buffer.buffer,
|
||||||
|
0,
|
||||||
|
static_cast<VkDeviceSize>(MAX_LIGHTS * sizeof(GPULight)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightCullingPipeline::cullLights(
|
||||||
|
VkCommandBuffer cmd,
|
||||||
|
const RenderResources& resources,
|
||||||
|
const GPUBuffer& sceneDataBuffer,
|
||||||
|
ImageID depthImageId,
|
||||||
|
VkExtent2D renderExtent)
|
||||||
|
{
|
||||||
|
if (!lightCullingPipeline)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// previous frame's mesh pass might still be reading the tile lists
|
||||||
|
const VkBufferMemoryBarrier2 tileBufferBarrier{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
||||||
|
.srcStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,
|
||||||
|
.srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT,
|
||||||
|
.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
|
||||||
|
.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
|
||||||
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
.buffer = tileLightIndicesBuffer.buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.size = VK_WHOLE_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
const VkDependencyInfo dependencyInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
||||||
|
.bufferMemoryBarrierCount = 1,
|
||||||
|
.pBufferMemoryBarriers = &tileBufferBarrier,
|
||||||
|
};
|
||||||
|
vkCmdPipelineBarrier2(cmd, &dependencyInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
lightCullingPipeline->bind(cmd);
|
||||||
|
resources.bindBindlessDescSet(cmd, m_pipelineLayout, VK_PIPELINE_BIND_POINT_COMPUTE);
|
||||||
|
|
||||||
|
const auto cs = PushConstants{
|
||||||
|
.sceneDataBuffer = sceneDataBuffer.address,
|
||||||
|
.screenSize = glm::uvec2{renderExtent.width, renderExtent.height},
|
||||||
|
.depthTexId = static_cast<std::uint32_t>(depthImageId),
|
||||||
|
.padding = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
vkCmdPushConstants(cmd,
|
||||||
|
m_pipelineLayout,
|
||||||
|
VK_SHADER_STAGE_COMPUTE_BIT,
|
||||||
|
0,
|
||||||
|
sizeof(PushConstants),
|
||||||
|
&cs);
|
||||||
|
|
||||||
|
// one workgroup per tile
|
||||||
|
vkCmdDispatch(cmd, tileCount.x, tileCount.y, 1);
|
||||||
|
|
||||||
|
const VkBufferMemoryBarrier2 tileBufferBarrier{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
||||||
|
.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
|
||||||
|
.srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
|
||||||
|
.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,
|
||||||
|
.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT,
|
||||||
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
.buffer = tileLightIndicesBuffer.buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.size = VK_WHOLE_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
const VkDependencyInfo dependencyInfo{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
||||||
|
.bufferMemoryBarrierCount = 1,
|
||||||
|
.pBufferMemoryBarriers = &tileBufferBarrier,
|
||||||
|
};
|
||||||
|
vkCmdPipelineBarrier2(cmd, &dependencyInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDeviceAddress LightCullingPipeline::getLightsBufferAddress() const
|
||||||
|
{
|
||||||
|
return getCurrentFrameData().lightsBuffer.buffer.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t LightCullingPipeline::getNumLights() const
|
||||||
|
{
|
||||||
|
return static_cast<std::uint32_t>(getCurrentFrameData().lightsBuffer.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
LightCullingPipeline::PerFrameData& LightCullingPipeline::getCurrentFrameData()
|
||||||
|
{
|
||||||
|
return framesData[currentFrameIndex % FRAMES_IN_FLIGHT];
|
||||||
|
}
|
||||||
|
|
||||||
|
const LightCullingPipeline::PerFrameData& LightCullingPipeline::getCurrentFrameData() const
|
||||||
|
{
|
||||||
|
return framesData[currentFrameIndex % FRAMES_IN_FLIGHT];
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <destrum/Graphics/RenderResources.h>
|
#include <destrum/Graphics/RenderResources.h>
|
||||||
#include <destrum/Graphics/Caches/MeshCache.h>
|
#include <destrum/Graphics/Caches/MeshCache.h>
|
||||||
#include <destrum/Graphics/Frustum.h>
|
#include <destrum/Graphics/Frustum.h>
|
||||||
|
#include <destrum/Graphics/Util.h>
|
||||||
|
|
||||||
#include "volk.h"
|
#include "volk.h"
|
||||||
#include "spdlog/spdlog.h"
|
#include "spdlog/spdlog.h"
|
||||||
@@ -23,7 +24,8 @@ void MeshPipeline::init(
|
|||||||
VkFormat drawImageFormat,
|
VkFormat drawImageFormat,
|
||||||
VkFormat depthImageFormat)
|
VkFormat depthImageFormat)
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
const auto vertexShader =
|
const auto vertexShader =
|
||||||
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/mesh.vert");
|
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/mesh.vert");
|
||||||
|
|
||||||
@@ -59,6 +61,29 @@ void MeshPipeline::init(
|
|||||||
throw std::runtime_error("Could not create mesh pipeline layout");
|
throw std::runtime_error("Could not create mesh pipeline layout");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
PipelineConfigInfo pipelineConfig{};
|
||||||
|
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
|
||||||
|
|
||||||
|
pipelineConfig.name = "Mesh Depth Prepass Pipeline";
|
||||||
|
pipelineConfig.pipelineLayout = m_pipelineLayout;
|
||||||
|
|
||||||
|
pipelineConfig.vertexAttributeDescriptions = {};
|
||||||
|
pipelineConfig.vertexBindingDescriptions = {};
|
||||||
|
|
||||||
|
pipelineConfig.colorAttachments = {};
|
||||||
|
pipelineConfig.colorBlendInfo.attachmentCount = 0;
|
||||||
|
pipelineConfig.depthAttachment = depthImageFormat;
|
||||||
|
|
||||||
|
m_depthPrepassPipeline = std::make_unique<Pipeline>(
|
||||||
|
gfxDevice,
|
||||||
|
vertexShader.string(),
|
||||||
|
"",
|
||||||
|
pipelineConfig
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
PipelineConfigInfo pipelineConfig{};
|
PipelineConfigInfo pipelineConfig{};
|
||||||
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
|
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
|
||||||
|
|
||||||
@@ -71,18 +96,50 @@ void MeshPipeline::init(
|
|||||||
pipelineConfig.colorAttachments = {drawImageFormat};
|
pipelineConfig.colorAttachments = {drawImageFormat};
|
||||||
pipelineConfig.depthAttachment = depthImageFormat;
|
pipelineConfig.depthAttachment = depthImageFormat;
|
||||||
|
|
||||||
|
// depth was already written by the pre-pass, only shade the visible surface
|
||||||
|
pipelineConfig.depthStencilInfo.depthWriteEnable = VK_FALSE;
|
||||||
|
pipelineConfig.depthStencilInfo.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
|
||||||
|
|
||||||
m_pipeline = std::make_unique<Pipeline>(
|
m_pipeline = std::make_unique<Pipeline>(
|
||||||
gfxDevice,
|
gfxDevice,
|
||||||
vertexShader.string(),
|
vertexShader.string(),
|
||||||
fragShader.string(),
|
fragShader.string(),
|
||||||
pipelineConfig
|
pipelineConfig
|
||||||
);
|
);
|
||||||
} catch (...) {
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
cleanup(gfxDevice.getDevice());
|
cleanup(gfxDevice.getDevice());
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MeshPipeline::drawDepthPrepass(
|
||||||
|
VkCommandBuffer cmd,
|
||||||
|
VkExtent2D renderExtent,
|
||||||
|
const RenderResources& resources,
|
||||||
|
const Camera& camera,
|
||||||
|
const GPUBuffer& sceneDataBuffer,
|
||||||
|
const std::vector<MeshDrawCommand>& drawCommands,
|
||||||
|
const std::vector<std::size_t>& sortedDrawCommands)
|
||||||
|
{
|
||||||
|
if (!m_depthPrepassPipeline)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawMeshes(
|
||||||
|
cmd,
|
||||||
|
*m_depthPrepassPipeline,
|
||||||
|
renderExtent,
|
||||||
|
resources,
|
||||||
|
camera,
|
||||||
|
sceneDataBuffer,
|
||||||
|
drawCommands,
|
||||||
|
sortedDrawCommands);
|
||||||
|
}
|
||||||
|
|
||||||
void MeshPipeline::draw(
|
void MeshPipeline::draw(
|
||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
VkExtent2D renderExtent,
|
VkExtent2D renderExtent,
|
||||||
@@ -92,13 +149,35 @@ void MeshPipeline::draw(
|
|||||||
const std::vector<MeshDrawCommand>& drawCommands,
|
const std::vector<MeshDrawCommand>& drawCommands,
|
||||||
const std::vector<std::size_t>& sortedDrawCommands)
|
const std::vector<std::size_t>& sortedDrawCommands)
|
||||||
{
|
{
|
||||||
if (!m_pipeline) {
|
if (!m_pipeline)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawMeshes(
|
||||||
|
cmd,
|
||||||
|
*m_pipeline,
|
||||||
|
renderExtent,
|
||||||
|
resources,
|
||||||
|
camera,
|
||||||
|
sceneDataBuffer,
|
||||||
|
drawCommands,
|
||||||
|
sortedDrawCommands);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshPipeline::drawMeshes(
|
||||||
|
VkCommandBuffer cmd,
|
||||||
|
const Pipeline& pipeline,
|
||||||
|
VkExtent2D renderExtent,
|
||||||
|
const RenderResources& resources,
|
||||||
|
const Camera& camera,
|
||||||
|
const GPUBuffer& sceneDataBuffer,
|
||||||
|
const std::vector<MeshDrawCommand>& drawCommands,
|
||||||
|
const std::vector<std::size_t>& sortedDrawCommands)
|
||||||
|
{
|
||||||
const MeshCache& meshCache = resources.meshes();
|
const MeshCache& meshCache = resources.meshes();
|
||||||
|
|
||||||
m_pipeline->bind(cmd);
|
pipeline.bind(cmd);
|
||||||
|
|
||||||
resources.bindBindlessDescSet(cmd, m_pipelineLayout);
|
resources.bindBindlessDescSet(cmd, m_pipelineLayout);
|
||||||
|
|
||||||
@@ -130,8 +209,10 @@ void MeshPipeline::draw(
|
|||||||
|
|
||||||
const auto frustum = edge::createFrustumFromCamera(camera);
|
const auto frustum = edge::createFrustumFromCamera(camera);
|
||||||
|
|
||||||
const auto drawOne = [&](const MeshDrawCommand& dc) {
|
const auto drawOne = [&](const MeshDrawCommand& dc)
|
||||||
if (!edge::isInFrustum(frustum, dc.worldBoundingSphere)) {
|
{
|
||||||
|
if (!edge::isInFrustum(frustum, dc.worldBoundingSphere))
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +220,18 @@ void MeshPipeline::draw(
|
|||||||
|
|
||||||
const auto& mesh = meshCache.getMesh(dc.meshId);
|
const auto& mesh = meshCache.getMesh(dc.meshId);
|
||||||
|
|
||||||
if (dc.meshId != prevMeshId) {
|
const std::string& meshKey = meshCache.getMeshKey(dc.meshId);
|
||||||
|
vkutil::cmdBeginLabelFormatted(
|
||||||
|
cmd,
|
||||||
|
vkutil::labelColors::draw,
|
||||||
|
"%s [mesh %zu, material %u%s]",
|
||||||
|
meshKey.empty() ? "Mesh" : meshKey.c_str(),
|
||||||
|
dc.meshId,
|
||||||
|
dc.materialId,
|
||||||
|
dc.skinnedMesh != nullptr ? ", skinned" : "");
|
||||||
|
|
||||||
|
if (dc.meshId != prevMeshId)
|
||||||
|
{
|
||||||
prevMeshId = dc.meshId;
|
prevMeshId = dc.meshId;
|
||||||
|
|
||||||
vkCmdBindIndexBuffer(
|
vkCmdBindIndexBuffer(
|
||||||
@@ -173,28 +265,34 @@ void MeshPipeline::draw(
|
|||||||
);
|
);
|
||||||
|
|
||||||
vkCmdDrawIndexed(cmd, mesh.numIndices, 1, 0, 0, 0);
|
vkCmdDrawIndexed(cmd, mesh.numIndices, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
vkutil::cmdEndLabel(cmd);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!sortedDrawCommands.empty()) {
|
if (!sortedDrawCommands.empty())
|
||||||
for (std::size_t drawIndex : sortedDrawCommands) {
|
{
|
||||||
|
for (std::size_t drawIndex : sortedDrawCommands)
|
||||||
|
{
|
||||||
assert(drawIndex < drawCommands.size());
|
assert(drawIndex < drawCommands.size());
|
||||||
drawOne(drawCommands[drawIndex]);
|
drawOne(drawCommands[drawIndex]);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
for (const MeshDrawCommand& dc : drawCommands) {
|
else
|
||||||
|
{
|
||||||
|
for (const MeshDrawCommand& dc : drawCommands)
|
||||||
|
{
|
||||||
drawOne(dc);
|
drawOne(dc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional:
|
|
||||||
// spdlog::debug("Actual mesh draw calls: {}", actualDrawCalls);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshPipeline::cleanup(VkDevice device)
|
void MeshPipeline::cleanup(VkDevice device)
|
||||||
{
|
{
|
||||||
m_pipeline.reset();
|
m_pipeline.reset();
|
||||||
|
m_depthPrepassPipeline.reset();
|
||||||
|
|
||||||
if (m_pipelineLayout != VK_NULL_HANDLE && device != VK_NULL_HANDLE) {
|
if (m_pipelineLayout != VK_NULL_HANDLE && device != VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
vkDestroyPipelineLayout(device, m_pipelineLayout, nullptr);
|
vkDestroyPipelineLayout(device, m_pipelineLayout, nullptr);
|
||||||
}
|
}
|
||||||
m_pipelineLayout = VK_NULL_HANDLE;
|
m_pipelineLayout = VK_NULL_HANDLE;
|
||||||
|
|||||||
@@ -42,12 +42,14 @@ void QuadRendererPass::init(GfxDevice& gfxDevice, RenderResources& resources, Vk
|
|||||||
vertexBytes,
|
vertexBytes,
|
||||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||||
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
|
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
|
||||||
|
vkutil::addDebugLabel(gfxDevice.getDevice(), vertexBuffer.buffer, "quad vertices");
|
||||||
|
|
||||||
const std::size_t indexBytes = InitialQuadCapacity * IndicesPerQuad * sizeof(std::uint32_t);
|
const std::size_t indexBytes = InitialQuadCapacity * IndicesPerQuad * sizeof(std::uint32_t);
|
||||||
indexBuffer = gfxDevice.createBuffer(
|
indexBuffer = gfxDevice.createBuffer(
|
||||||
indexBytes,
|
indexBytes,
|
||||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||||
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
|
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
|
||||||
|
vkutil::addDebugLabel(gfxDevice.getDevice(), indexBuffer.buffer, "quad indices");
|
||||||
|
|
||||||
quadCapacity = InitialQuadCapacity;
|
quadCapacity = InitialQuadCapacity;
|
||||||
|
|
||||||
@@ -321,6 +323,7 @@ void QuadRendererPass::ensureCapacity(
|
|||||||
newCapacity * VerticesPerQuad * sizeof(QuadVertex),
|
newCapacity * VerticesPerQuad * sizeof(QuadVertex),
|
||||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||||
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
|
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
|
||||||
|
vkutil::addDebugLabel(gfxDevice.getDevice(), vertexBuffer.buffer, "quad vertices");
|
||||||
|
|
||||||
if (indexBuffer.buffer != VK_NULL_HANDLE) {
|
if (indexBuffer.buffer != VK_NULL_HANDLE) {
|
||||||
gfxDevice.destroyBuffer(indexBuffer);
|
gfxDevice.destroyBuffer(indexBuffer);
|
||||||
@@ -329,6 +332,7 @@ void QuadRendererPass::ensureCapacity(
|
|||||||
newCapacity * IndicesPerQuad * sizeof(std::uint32_t),
|
newCapacity * IndicesPerQuad * sizeof(std::uint32_t),
|
||||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||||
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
|
VMA_MEMORY_USAGE_AUTO_PREFER_HOST);
|
||||||
|
vkutil::addDebugLabel(gfxDevice.getDevice(), indexBuffer.buffer, "quad indices");
|
||||||
|
|
||||||
quadCapacity = newCapacity;
|
quadCapacity = newCapacity;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
#include <destrum/Graphics/Pipelines/ShadowMapPipeline.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
|
#include <destrum/FS/AssetFS.h>
|
||||||
|
#include <destrum/Graphics/GfxDevice.h>
|
||||||
|
#include <destrum/Graphics/RenderResources.h>
|
||||||
|
#include <destrum/Graphics/Caches/MeshCache.h>
|
||||||
|
#include <destrum/Graphics/Util.h>
|
||||||
|
|
||||||
|
#include "volk.h"
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
|
|
||||||
|
void ShadowMapPipeline::init(GfxDevice& gfxDevice, RenderResources& resources)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const auto vertexShader =
|
||||||
|
AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/shadow.vert");
|
||||||
|
|
||||||
|
const auto bufferRange = VkPushConstantRange{
|
||||||
|
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
|
||||||
|
.offset = 0,
|
||||||
|
.size = sizeof(PushConstants),
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto pushConstantRanges = std::array{bufferRange};
|
||||||
|
|
||||||
|
const auto layouts = std::array{
|
||||||
|
resources.getBindlessDescSetLayout()
|
||||||
|
};
|
||||||
|
|
||||||
|
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
|
||||||
|
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||||
|
pipelineLayoutInfo.setLayoutCount = static_cast<std::uint32_t>(layouts.size());
|
||||||
|
pipelineLayoutInfo.pSetLayouts = layouts.data();
|
||||||
|
pipelineLayoutInfo.pushConstantRangeCount = static_cast<std::uint32_t>(pushConstantRanges.size());
|
||||||
|
pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges.data();
|
||||||
|
|
||||||
|
if (vkCreatePipelineLayout(
|
||||||
|
gfxDevice.getDevice(),
|
||||||
|
&pipelineLayoutInfo,
|
||||||
|
nullptr,
|
||||||
|
&m_pipelineLayout) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Could not create shadow map pipeline layout");
|
||||||
|
}
|
||||||
|
|
||||||
|
PipelineConfigInfo pipelineConfig{};
|
||||||
|
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
|
||||||
|
|
||||||
|
pipelineConfig.name = "Shadow Map Pipeline";
|
||||||
|
pipelineConfig.pipelineLayout = m_pipelineLayout;
|
||||||
|
|
||||||
|
pipelineConfig.vertexAttributeDescriptions = {};
|
||||||
|
pipelineConfig.vertexBindingDescriptions = {};
|
||||||
|
|
||||||
|
pipelineConfig.colorAttachments = {};
|
||||||
|
pipelineConfig.colorBlendInfo.attachmentCount = 0;
|
||||||
|
pipelineConfig.depthAttachment = shadowMapFormat;
|
||||||
|
|
||||||
|
// the light projection isn't Y flipped like the camera's, so the winding is mirrored
|
||||||
|
pipelineConfig.rasterizationInfo.cullMode = VK_CULL_MODE_NONE;
|
||||||
|
pipelineConfig.rasterizationInfo.depthBiasEnable = VK_TRUE;
|
||||||
|
pipelineConfig.rasterizationInfo.depthBiasConstantFactor = 0.0f;
|
||||||
|
pipelineConfig.rasterizationInfo.depthBiasSlopeFactor = 2.0f;
|
||||||
|
|
||||||
|
m_pipeline = std::make_unique<Pipeline>(
|
||||||
|
gfxDevice,
|
||||||
|
vertexShader.string(),
|
||||||
|
"",
|
||||||
|
pipelineConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
const auto createInfo = vkutil::CreateImageInfo{
|
||||||
|
.format = shadowMapFormat,
|
||||||
|
.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||||
|
.extent = VkExtent3D{SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 1},
|
||||||
|
};
|
||||||
|
|
||||||
|
shadowMapId = resources.createImage(gfxDevice, createInfo, "shadow map", nullptr, shadowMapId);
|
||||||
|
spdlog::info("Created {0}x{0} shadow map with id {1}", SHADOW_MAP_SIZE, shadowMapId);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
cleanup(gfxDevice.getDevice());
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowMapPipeline::cleanup(VkDevice device)
|
||||||
|
{
|
||||||
|
m_pipeline.reset();
|
||||||
|
|
||||||
|
if (m_pipelineLayout != VK_NULL_HANDLE && device != VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
|
vkDestroyPipelineLayout(device, m_pipelineLayout, nullptr);
|
||||||
|
}
|
||||||
|
m_pipelineLayout = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
shadowMapId = NULL_IMAGE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::mat4 ShadowMapPipeline::calculateLightViewProj(const glm::vec3& lightDirection, const glm::vec3& center) const
|
||||||
|
{
|
||||||
|
const glm::vec3 direction = glm::normalize(lightDirection);
|
||||||
|
const glm::vec3 up = std::abs(direction.y) > 0.99f ? glm::vec3{0.0f, 0.0f, 1.0f} : glm::vec3{0.0f, 1.0f, 0.0f};
|
||||||
|
|
||||||
|
const glm::mat4 lightView = glm::lookAt(glm::vec3{0.0f}, direction, up);
|
||||||
|
|
||||||
|
const float texelSize = (2.0f * shadowDistance) / static_cast<float>(SHADOW_MAP_SIZE);
|
||||||
|
glm::vec3 lightSpaceCenter = glm::vec3(lightView * glm::vec4(center, 1.0f));
|
||||||
|
lightSpaceCenter.x = std::floor(lightSpaceCenter.x / texelSize) * texelSize;
|
||||||
|
lightSpaceCenter.y = std::floor(lightSpaceCenter.y / texelSize) * texelSize;
|
||||||
|
|
||||||
|
const glm::mat4 lightProj = glm::orthoRH_ZO(
|
||||||
|
lightSpaceCenter.x - shadowDistance,
|
||||||
|
lightSpaceCenter.x + shadowDistance,
|
||||||
|
lightSpaceCenter.y - shadowDistance,
|
||||||
|
lightSpaceCenter.y + shadowDistance,
|
||||||
|
-lightSpaceCenter.z - shadowDepthRange,
|
||||||
|
-lightSpaceCenter.z + shadowDepthRange);
|
||||||
|
|
||||||
|
return lightProj * lightView;
|
||||||
|
}
|
||||||
|
|
||||||
|
float ShadowMapPipeline::getNormalOffset() const
|
||||||
|
{
|
||||||
|
const float texelSize = (2.0f * shadowDistance) / static_cast<float>(SHADOW_MAP_SIZE);
|
||||||
|
return texelSize * 1.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowMapPipeline::draw(
|
||||||
|
VkCommandBuffer cmd,
|
||||||
|
const RenderResources& resources,
|
||||||
|
const GPUBuffer& sceneDataBuffer,
|
||||||
|
const std::vector<MeshDrawCommand>& drawCommands,
|
||||||
|
const std::vector<std::size_t>& sortedDrawCommands)
|
||||||
|
{
|
||||||
|
if (!m_pipeline || shadowMapId == NULL_IMAGE_ID)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& shadowMap = resources.getImage(shadowMapId);
|
||||||
|
|
||||||
|
vkutil::transitionImage(
|
||||||
|
cmd,
|
||||||
|
shadowMap.image,
|
||||||
|
shadowMap.layout,
|
||||||
|
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
||||||
|
shadowMap.setLayout(VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
||||||
|
|
||||||
|
auto renderInfo = vkutil::createRenderingInfo({
|
||||||
|
.renderExtent = shadowMap.getExtent2D(),
|
||||||
|
.depthImageView = shadowMap.imageView,
|
||||||
|
.depthImageClearValue = 1.f,
|
||||||
|
});
|
||||||
|
renderInfo.renderingInfo.pDepthAttachment = &renderInfo.depthAttachment;
|
||||||
|
|
||||||
|
vkCmdBeginRendering(cmd, &renderInfo.renderingInfo);
|
||||||
|
|
||||||
|
m_pipeline->bind(cmd);
|
||||||
|
resources.bindBindlessDescSet(cmd, m_pipelineLayout);
|
||||||
|
|
||||||
|
const auto viewport = VkViewport{
|
||||||
|
.x = 0.f,
|
||||||
|
.y = 0.f,
|
||||||
|
.width = static_cast<float>(SHADOW_MAP_SIZE),
|
||||||
|
.height = static_cast<float>(SHADOW_MAP_SIZE),
|
||||||
|
.minDepth = 0.f,
|
||||||
|
.maxDepth = 1.f,
|
||||||
|
};
|
||||||
|
vkCmdSetViewport(cmd, 0, 1, &viewport);
|
||||||
|
|
||||||
|
const auto scissor = VkRect2D{
|
||||||
|
.offset = {},
|
||||||
|
.extent = shadowMap.getExtent2D(),
|
||||||
|
};
|
||||||
|
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
||||||
|
|
||||||
|
vkCmdSetPolygonModeEXT(cmd, VK_POLYGON_MODE_FILL);
|
||||||
|
|
||||||
|
const MeshCache& meshCache = resources.meshes();
|
||||||
|
MeshID prevMeshId = NULL_MESH_ID;
|
||||||
|
|
||||||
|
const auto drawOne = [&](const MeshDrawCommand& dc)
|
||||||
|
{
|
||||||
|
if (!dc.castShadow)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& mesh = meshCache.getMesh(dc.meshId);
|
||||||
|
|
||||||
|
const std::string& meshKey = meshCache.getMeshKey(dc.meshId);
|
||||||
|
vkutil::cmdBeginLabelFormatted(
|
||||||
|
cmd,
|
||||||
|
vkutil::labelColors::draw,
|
||||||
|
"%s [mesh %zu, material %u%s]",
|
||||||
|
meshKey.empty() ? "Mesh" : meshKey.c_str(),
|
||||||
|
dc.meshId,
|
||||||
|
dc.materialId,
|
||||||
|
dc.skinnedMesh != nullptr ? ", skinned" : "");
|
||||||
|
|
||||||
|
if (dc.meshId != prevMeshId)
|
||||||
|
{
|
||||||
|
prevMeshId = dc.meshId;
|
||||||
|
vkCmdBindIndexBuffer(cmd, mesh.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto pushConstants = PushConstants{
|
||||||
|
.transform = dc.transformMatrix,
|
||||||
|
.sceneDataBuffer = sceneDataBuffer.address,
|
||||||
|
.vertexBuffer =
|
||||||
|
dc.skinnedMesh != nullptr
|
||||||
|
? dc.skinnedMesh->skinnedVertexBuffer.address
|
||||||
|
: mesh.vertexBuffer.address,
|
||||||
|
.materialId = dc.materialId,
|
||||||
|
.padding = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
vkCmdPushConstants(
|
||||||
|
cmd,
|
||||||
|
m_pipelineLayout,
|
||||||
|
VK_SHADER_STAGE_VERTEX_BIT,
|
||||||
|
0,
|
||||||
|
sizeof(PushConstants),
|
||||||
|
&pushConstants);
|
||||||
|
|
||||||
|
vkCmdDrawIndexed(cmd, mesh.numIndices, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
vkutil::cmdEndLabel(cmd);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!sortedDrawCommands.empty())
|
||||||
|
{
|
||||||
|
for (std::size_t drawIndex : sortedDrawCommands)
|
||||||
|
{
|
||||||
|
assert(drawIndex < drawCommands.size());
|
||||||
|
drawOne(drawCommands[drawIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (const MeshDrawCommand& dc : drawCommands)
|
||||||
|
{
|
||||||
|
drawOne(dc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vkCmdEndRendering(cmd);
|
||||||
|
|
||||||
|
vkutil::transitionImage(
|
||||||
|
cmd,
|
||||||
|
shadowMap.image,
|
||||||
|
shadowMap.layout,
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||||
|
shadowMap.setLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||||
|
}
|
||||||
@@ -53,6 +53,9 @@ void SkinningPipeline::init(GfxDevice& gfxDevice) {
|
|||||||
MAX_JOINT_MATRICES * sizeof(glm::mat4),
|
MAX_JOINT_MATRICES * sizeof(glm::mat4),
|
||||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||||
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||||
|
|
||||||
|
const std::string bufferName = "joint matrices (frame " + std::to_string(i) + ")";
|
||||||
|
vkutil::addDebugLabel(device, jointMatricesBuffer.buffer.buffer, bufferName.c_str());
|
||||||
}
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
cleanup(gfxDevice);
|
cleanup(gfxDevice);
|
||||||
@@ -84,6 +87,9 @@ void SkinningPipeline::doSkinning(VkCommandBuffer cmd,
|
|||||||
assert(mesh.hasSkeleton);
|
assert(mesh.hasSkeleton);
|
||||||
assert(dc.skinnedMesh);
|
assert(dc.skinnedMesh);
|
||||||
|
|
||||||
|
const std::string& meshKey = meshCache.getMeshKey(dc.meshId);
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, meshKey.empty() ? "Skin Mesh" : meshKey.c_str(), vkutil::labelColors::compute);
|
||||||
|
|
||||||
const auto cs = PushConstants{
|
const auto cs = PushConstants{
|
||||||
.jointMatricesBuffer = getCurrentFrameData(frameIndex).jointMatricesBuffer.buffer.address,
|
.jointMatricesBuffer = getCurrentFrameData(frameIndex).jointMatricesBuffer.buffer.address,
|
||||||
.jointMatricesStartIndex = static_cast<std::uint32_t>(dc.jointMatricesStartIndex),
|
.jointMatricesStartIndex = static_cast<std::uint32_t>(dc.jointMatricesStartIndex),
|
||||||
|
|||||||
@@ -119,14 +119,16 @@ void SkyboxPipeline::draw(
|
|||||||
|
|
||||||
const glm::mat3 r = glm::mat3(skyboxRotation);
|
const glm::mat3 r = glm::mat3(skyboxRotation);
|
||||||
|
|
||||||
|
// rotation only, translation in the inverse makes the sky jitter far from the origin
|
||||||
|
const glm::mat4 viewRotation = glm::mat4(glm::mat3(camera.GetViewMatrix()));
|
||||||
|
|
||||||
const auto pcs = SkyboxPushConstants{
|
const auto pcs = SkyboxPushConstants{
|
||||||
.invViewProj = glm::inverse(camera.GetViewProjectionMatrix()),
|
.invViewProj = glm::inverse(camera.GetProjectionMatrix() * viewRotation),
|
||||||
.skyboxRot = {
|
.skyboxRot = {
|
||||||
glm::vec4(r[0], 0.f),
|
glm::vec4(r[0], 0.f),
|
||||||
glm::vec4(r[1], 0.f),
|
glm::vec4(r[1], 0.f),
|
||||||
glm::vec4(r[2], 0.f)
|
glm::vec4(r[2], 0.f)
|
||||||
},
|
},
|
||||||
.cameraPos = camera.GetPosition(),
|
|
||||||
.skyboxTextureId = static_cast<std::uint32_t>(skyboxTextureId),
|
.skyboxTextureId = static_cast<std::uint32_t>(skyboxTextureId),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,8 @@ ImageID RenderResources::createImage(
|
|||||||
|
|
||||||
if (!debugName.empty()) {
|
if (!debugName.empty()) {
|
||||||
vkutil::addDebugLabel(gfxDevice.getVkDevice(), image.image, debugName.c_str());
|
vkutil::addDebugLabel(gfxDevice.getVkDevice(), image.image, debugName.c_str());
|
||||||
|
const std::string viewName = debugName + " (view)";
|
||||||
|
vkutil::addDebugLabel(gfxDevice.getVkDevice(), image.imageView, viewName.c_str());
|
||||||
image.debugName = debugName;
|
image.debugName = debugName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,10 +268,13 @@ const VkDescriptorSet& RenderResources::getBindlessDescSet() const {
|
|||||||
return imageCache->bindlessSetManager.getDescSet();
|
return imageCache->bindlessSetManager.getDescSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderResources::bindBindlessDescSet(VkCommandBuffer cmd, VkPipelineLayout layout) const {
|
void RenderResources::bindBindlessDescSet(
|
||||||
|
VkCommandBuffer cmd,
|
||||||
|
VkPipelineLayout layout,
|
||||||
|
VkPipelineBindPoint bindPoint) const {
|
||||||
vkCmdBindDescriptorSets(
|
vkCmdBindDescriptorSets(
|
||||||
cmd,
|
cmd,
|
||||||
VK_PIPELINE_BIND_POINT_GRAPHICS,
|
bindPoint,
|
||||||
layout,
|
layout,
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ GameRenderer::GameRenderer()
|
|||||||
|
|
||||||
void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::ivec2 drawImageSize)
|
void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::ivec2 drawImageSize)
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
resources = &_resources;
|
resources = &_resources;
|
||||||
sceneDataBuffer.init(
|
sceneDataBuffer.init(
|
||||||
gfxDevice,
|
gfxDevice,
|
||||||
@@ -34,12 +35,18 @@ void GameRenderer::init(GfxDevice& gfxDevice, RenderResources& _resources, glm::
|
|||||||
meshPipeline = std::make_unique<MeshPipeline>();
|
meshPipeline = std::make_unique<MeshPipeline>();
|
||||||
meshPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
meshPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||||
|
|
||||||
skyboxPipeline = std::make_unique<SkyboxPipeline>();
|
skyboxPipeline = std::make_unique<SkyboxPipeline>();
|
||||||
skyboxPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
skyboxPipeline->init(gfxDevice, _resources, drawImageFormat, depthImageFormat);
|
||||||
|
|
||||||
skinningPipeline = std::make_unique<SkinningPipeline>();
|
skinningPipeline = std::make_unique<SkinningPipeline>();
|
||||||
skinningPipeline->init(gfxDevice);
|
skinningPipeline->init(gfxDevice);
|
||||||
|
|
||||||
|
lightCullingPipeline = std::make_unique<LightCullingPipeline>();
|
||||||
|
lightCullingPipeline->init(gfxDevice, _resources, drawImageSize);
|
||||||
|
|
||||||
|
shadowMapPipeline = std::make_unique<ShadowMapPipeline>();
|
||||||
|
shadowMapPipeline->init(gfxDevice, _resources);
|
||||||
|
|
||||||
lineRenderingPass = std::make_unique<LineRenderingPass>();
|
lineRenderingPass = std::make_unique<LineRenderingPass>();
|
||||||
lineRenderingPass->init(gfxDevice, drawImageFormat);
|
lineRenderingPass->init(gfxDevice, drawImageFormat);
|
||||||
|
|
||||||
@@ -48,7 +55,9 @@ skyboxPipeline = std::make_unique<SkyboxPipeline>();
|
|||||||
|
|
||||||
GameState::GetInstance().SetRenderer(this);
|
GameState::GetInstance().SetRenderer(this);
|
||||||
initialized = true;
|
initialized = true;
|
||||||
} catch (...) {
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
cleanup(gfxDevice);
|
cleanup(gfxDevice);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@@ -59,6 +68,8 @@ void GameRenderer::beginDrawing(GfxDevice& gfxDevice)
|
|||||||
flushMaterialUpdates(gfxDevice);
|
flushMaterialUpdates(gfxDevice);
|
||||||
meshDrawCommands.clear();
|
meshDrawCommands.clear();
|
||||||
skinningPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
|
skinningPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
|
||||||
|
lightCullingPipeline->beginDrawing(gfxDevice.getCurrentFrameIndex());
|
||||||
|
shadowLightDirection.reset();
|
||||||
QuadRenderingManager::GetInstance().ClearQuads();
|
QuadRenderingManager::GetInstance().ClearQuads();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,13 +81,16 @@ void GameRenderer::endDrawing()
|
|||||||
std::stable_sort(
|
std::stable_sort(
|
||||||
sortedMeshDrawCommands.begin(),
|
sortedMeshDrawCommands.begin(),
|
||||||
sortedMeshDrawCommands.end(),
|
sortedMeshDrawCommands.end(),
|
||||||
[this](std::size_t left, std::size_t right) {
|
[this](std::size_t left, std::size_t right)
|
||||||
|
{
|
||||||
const auto& lhs = meshDrawCommands[left];
|
const auto& lhs = meshDrawCommands[left];
|
||||||
const auto& rhs = meshDrawCommands[right];
|
const auto& rhs = meshDrawCommands[right];
|
||||||
if (lhs.meshId != rhs.meshId) {
|
if (lhs.meshId != rhs.meshId)
|
||||||
|
{
|
||||||
return lhs.meshId < rhs.meshId;
|
return lhs.meshId < rhs.meshId;
|
||||||
}
|
}
|
||||||
if (lhs.materialId != rhs.materialId) {
|
if (lhs.materialId != rhs.materialId)
|
||||||
|
{
|
||||||
return lhs.materialId < rhs.materialId;
|
return lhs.materialId < rhs.materialId;
|
||||||
}
|
}
|
||||||
return (lhs.skinnedMesh != nullptr) < (rhs.skinnedMesh != nullptr);
|
return (lhs.skinnedMesh != nullptr) < (rhs.skinnedMesh != nullptr);
|
||||||
@@ -87,9 +101,11 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
{
|
{
|
||||||
ZoneScopedN("GameRenderer::draw");
|
ZoneScopedN("GameRenderer::draw");
|
||||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "GameRenderer::draw");
|
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "GameRenderer::draw");
|
||||||
|
vkutil::ScopedCmdLabel frameLabel(cmd, "GameRenderer::draw", vkutil::labelColors::frame);
|
||||||
|
|
||||||
{
|
{
|
||||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Skinning");
|
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Skinning");
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "Skinning", vkutil::labelColors::compute);
|
||||||
|
|
||||||
skinningPipeline->flushCurrentFrame(
|
skinningPipeline->flushCurrentFrame(
|
||||||
cmd,
|
cmd,
|
||||||
@@ -106,9 +122,24 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Lights Buffer Flush");
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "Lights Buffer Flush", vkutil::labelColors::transfer);
|
||||||
|
|
||||||
|
lightCullingPipeline->flushCurrentFrame(cmd, gfxDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
const glm::mat4 lightViewProj = shadowLightDirection
|
||||||
|
? shadowMapPipeline->calculateLightViewProj(
|
||||||
|
*shadowLightDirection,
|
||||||
|
sceneData.camera.GetPosition())
|
||||||
|
: glm::mat4{1.0f};
|
||||||
|
|
||||||
|
// TODO: there has to be a better way to do this
|
||||||
const auto gpuSceneData = GPUSceneData{
|
const auto gpuSceneData = GPUSceneData{
|
||||||
.view = sceneData.camera.GetViewMatrix(),
|
.view = sceneData.camera.GetViewMatrix(),
|
||||||
.proj = sceneData.camera.GetProjectionMatrix(),
|
.proj = sceneData.camera.GetProjectionMatrix(),
|
||||||
|
.invProj = glm::inverse(sceneData.camera.GetProjectionMatrix()),
|
||||||
.viewProj = sceneData.camera.GetViewProjectionMatrix(),
|
.viewProj = sceneData.camera.GetViewProjectionMatrix(),
|
||||||
.cameraPos = glm::vec4{sceneData.camera.m_position, 1.f},
|
.cameraPos = glm::vec4{sceneData.camera.m_position, 1.f},
|
||||||
.ambientColor = {sceneData.ambientColor},
|
.ambientColor = {sceneData.ambientColor},
|
||||||
@@ -116,10 +147,20 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
.fogColor = {sceneData.fogColor},
|
.fogColor = {sceneData.fogColor},
|
||||||
.fogDensity = sceneData.fogDensity,
|
.fogDensity = sceneData.fogDensity,
|
||||||
.materialsBuffer = resources->materials().getMaterialDataBufferAddress(),
|
.materialsBuffer = resources->materials().getMaterialDataBufferAddress(),
|
||||||
|
.lightsBuffer = lightCullingPipeline->getLightsBufferAddress(),
|
||||||
|
.tileLightIndicesBuffer = lightCullingPipeline->getTileLightIndicesBufferAddress(),
|
||||||
|
.numLights = lightCullingPipeline->getNumLights(),
|
||||||
|
.tileCountX = lightCullingPipeline->getTileCount().x,
|
||||||
|
.debugLightHeatmap = renderLightHeatmap ? 1u : 0u,
|
||||||
|
.shadowMapId = static_cast<std::uint32_t>(shadowMapPipeline->getShadowMapId()),
|
||||||
|
.shadowNormalOffset = shadowMapPipeline->getNormalOffset(),
|
||||||
|
.padding = 0,
|
||||||
|
.lightViewProj = lightViewProj,
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Material Buffer Barrier");
|
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Material Buffer Barrier");
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "Material Buffer Barrier", vkutil::labelColors::transfer);
|
||||||
|
|
||||||
vkutil::bufferHostWriteToShaderReadBarrier(
|
vkutil::bufferHostWriteToShaderReadBarrier(
|
||||||
cmd,
|
cmd,
|
||||||
@@ -131,6 +172,7 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
|
|
||||||
{
|
{
|
||||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Upload Scene Data");
|
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Upload Scene Data");
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "Upload Scene Data", vkutil::labelColors::transfer);
|
||||||
|
|
||||||
sceneDataBuffer.uploadNewData(
|
sceneDataBuffer.uploadNewData(
|
||||||
cmd,
|
cmd,
|
||||||
@@ -165,16 +207,81 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
depthImage.setLayout(VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
depthImage.setLayout(VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shadowLightDirection)
|
||||||
|
{
|
||||||
|
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Shadow Map");
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "Shadow Map", vkutil::labelColors::shadow);
|
||||||
|
|
||||||
|
shadowMapPipeline->draw(
|
||||||
|
cmd,
|
||||||
|
*resources,
|
||||||
|
sceneDataBuffer.getBuffer(),
|
||||||
|
meshDrawCommands,
|
||||||
|
sortedMeshDrawCommands);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Depth Prepass");
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "Depth Prepass", vkutil::labelColors::depth);
|
||||||
|
|
||||||
|
auto depthPrepassInfo = vkutil::createRenderingInfo({
|
||||||
|
.renderExtent = drawImage.getExtent2D(),
|
||||||
|
.depthImageView = depthImage.imageView,
|
||||||
|
.depthImageClearValue = 1.f,
|
||||||
|
});
|
||||||
|
depthPrepassInfo.renderingInfo.pDepthAttachment = &depthPrepassInfo.depthAttachment;
|
||||||
|
|
||||||
|
vkCmdBeginRendering(cmd, &depthPrepassInfo.renderingInfo);
|
||||||
|
|
||||||
|
meshPipeline->drawDepthPrepass(
|
||||||
|
cmd,
|
||||||
|
drawImage.getExtent2D(),
|
||||||
|
*resources,
|
||||||
|
camera,
|
||||||
|
sceneDataBuffer.getBuffer(),
|
||||||
|
meshDrawCommands,
|
||||||
|
sortedMeshDrawCommands);
|
||||||
|
|
||||||
|
vkCmdEndRendering(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "Light Culling");
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "Light Culling", vkutil::labelColors::compute);
|
||||||
|
|
||||||
|
vkutil::transitionImage(
|
||||||
|
cmd,
|
||||||
|
depthImage.image,
|
||||||
|
depthImage.layout,
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||||
|
depthImage.setLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||||
|
|
||||||
|
lightCullingPipeline->cullLights(
|
||||||
|
cmd,
|
||||||
|
*resources,
|
||||||
|
sceneDataBuffer.getBuffer(),
|
||||||
|
depthImageId,
|
||||||
|
drawImage.getExtent2D());
|
||||||
|
|
||||||
|
vkutil::transitionImage(
|
||||||
|
cmd,
|
||||||
|
depthImage.image,
|
||||||
|
depthImage.layout,
|
||||||
|
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
||||||
|
depthImage.setLayout(VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
||||||
|
}
|
||||||
|
|
||||||
auto renderInfo = vkutil::createRenderingInfo({
|
auto renderInfo = vkutil::createRenderingInfo({
|
||||||
.renderExtent = drawImage.getExtent2D(),
|
.renderExtent = drawImage.getExtent2D(),
|
||||||
.colorImageView = drawImage.imageView,
|
.colorImageView = drawImage.imageView,
|
||||||
.colorImageClearValue = glm::vec4{0.f, 0.f, 0.f, 1.f},
|
.colorImageClearValue = glm::vec4{0.f, 0.f, 0.f, 1.f},
|
||||||
.depthImageView = depthImage.imageView,
|
.depthImageView = depthImage.imageView,
|
||||||
.depthImageClearValue = 1.f,
|
|
||||||
});
|
});
|
||||||
renderInfo.renderingInfo.pColorAttachments = &renderInfo.colorAttachment;
|
renderInfo.renderingInfo.pColorAttachments = &renderInfo.colorAttachment;
|
||||||
renderInfo.renderingInfo.pDepthAttachment = &renderInfo.depthAttachment;
|
renderInfo.renderingInfo.pDepthAttachment = &renderInfo.depthAttachment;
|
||||||
|
|
||||||
|
vkutil::cmdBeginLabel(cmd, "Forward Pass", vkutil::labelColors::opaque);
|
||||||
|
|
||||||
{
|
{
|
||||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "vkCmdBeginRendering");
|
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "vkCmdBeginRendering");
|
||||||
|
|
||||||
@@ -183,6 +290,7 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
|
|
||||||
{
|
{
|
||||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "MeshPipeline::draw");
|
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "MeshPipeline::draw");
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "MeshPipeline::draw", vkutil::labelColors::opaque);
|
||||||
|
|
||||||
meshPipeline->draw(
|
meshPipeline->draw(
|
||||||
cmd,
|
cmd,
|
||||||
@@ -196,6 +304,7 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
|
|
||||||
{
|
{
|
||||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "SkyboxPipeline::draw");
|
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "SkyboxPipeline::draw");
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "SkyboxPipeline::draw", vkutil::labelColors::sky);
|
||||||
|
|
||||||
skyboxPipeline->draw(cmd, *resources, camera);
|
skyboxPipeline->draw(cmd, *resources, camera);
|
||||||
}
|
}
|
||||||
@@ -206,8 +315,11 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
vkCmdEndRendering(cmd);
|
vkCmdEndRendering(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vkutil::cmdEndLabel(cmd);
|
||||||
|
|
||||||
{
|
{
|
||||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "LineRenderingPass::draw");
|
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "LineRenderingPass::draw");
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "LineRenderingPass::draw", vkutil::labelColors::debug);
|
||||||
|
|
||||||
lineRenderingPass->draw(
|
lineRenderingPass->draw(
|
||||||
cmd,
|
cmd,
|
||||||
@@ -219,6 +331,7 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
|
|
||||||
{
|
{
|
||||||
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "QuadRendererPass::draw");
|
TracyVkZone(gfxDevice.getTracyVkCtx(), cmd, "QuadRendererPass::draw");
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "QuadRendererPass::draw", vkutil::labelColors::ui);
|
||||||
|
|
||||||
quadRendererPass->draw(
|
quadRendererPass->draw(
|
||||||
cmd,
|
cmd,
|
||||||
@@ -230,7 +343,6 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
static_cast<float>(drawImage.extent.height),
|
static_cast<float>(drawImage.extent.height),
|
||||||
0.0f, -1.0f, 1.0f));
|
0.0f, -1.0f, 1.0f));
|
||||||
}
|
}
|
||||||
// vkutil::cmdEndLabel(cmd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameRenderer::drawQuads(
|
void GameRenderer::drawQuads(
|
||||||
@@ -246,11 +358,12 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
|||||||
{
|
{
|
||||||
VkDevice device = gfxDevice.getDevice();
|
VkDevice device = gfxDevice.getDevice();
|
||||||
|
|
||||||
if (device != VK_NULL_HANDLE) {
|
if (device != VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
vkDeviceWaitIdle(device);
|
vkDeviceWaitIdle(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skinningPipeline)
|
if (skinningPipeline)
|
||||||
skinningPipeline->cleanup(gfxDevice);
|
skinningPipeline->cleanup(gfxDevice);
|
||||||
|
|
||||||
if (lineRenderingPass)
|
if (lineRenderingPass)
|
||||||
@@ -259,6 +372,12 @@ if (skinningPipeline)
|
|||||||
if (quadRendererPass)
|
if (quadRendererPass)
|
||||||
quadRendererPass->cleanup(gfxDevice);
|
quadRendererPass->cleanup(gfxDevice);
|
||||||
|
|
||||||
|
if (lightCullingPipeline)
|
||||||
|
lightCullingPipeline->cleanup(gfxDevice);
|
||||||
|
|
||||||
|
if (shadowMapPipeline)
|
||||||
|
shadowMapPipeline->cleanup(device);
|
||||||
|
|
||||||
if (skyboxPipeline)
|
if (skyboxPipeline)
|
||||||
skyboxPipeline->cleanup(device);
|
skyboxPipeline->cleanup(device);
|
||||||
|
|
||||||
@@ -281,6 +400,9 @@ if (skinningPipeline)
|
|||||||
skinningPipeline.reset();
|
skinningPipeline.reset();
|
||||||
lineRenderingPass.reset();
|
lineRenderingPass.reset();
|
||||||
quadRendererPass.reset();
|
quadRendererPass.reset();
|
||||||
|
lightCullingPipeline.reset();
|
||||||
|
shadowMapPipeline.reset();
|
||||||
|
shadowLightDirection.reset();
|
||||||
pendingMaterialUploads.clear();
|
pendingMaterialUploads.clear();
|
||||||
meshDrawCommands.clear();
|
meshDrawCommands.clear();
|
||||||
sortedMeshDrawCommands.clear();
|
sortedMeshDrawCommands.clear();
|
||||||
@@ -323,6 +445,22 @@ void GameRenderer::drawSkinnedMesh(MeshID id,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameRenderer::submitLight(const Light& light)
|
||||||
|
{
|
||||||
|
Light submittedLight = light;
|
||||||
|
|
||||||
|
// there's a single shadow map, the first shadow casting directional light gets it
|
||||||
|
submittedLight.castShadows =
|
||||||
|
light.castShadows && light.type == LightType::Directional && !shadowLightDirection;
|
||||||
|
|
||||||
|
if (submittedLight.castShadows)
|
||||||
|
{
|
||||||
|
shadowLightDirection = light.direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
lightCullingPipeline->appendLight(submittedLight);
|
||||||
|
}
|
||||||
|
|
||||||
const GPUImage& GameRenderer::getDrawImage() const
|
const GPUImage& GameRenderer::getDrawImage() const
|
||||||
{
|
{
|
||||||
assert(resources);
|
assert(resources);
|
||||||
@@ -349,7 +487,8 @@ void GameRenderer::setSkyboxTexture(ImageID skyboxImageId)
|
|||||||
|
|
||||||
void GameRenderer::flushMaterialUpdates(GfxDevice& gfxDevice)
|
void GameRenderer::flushMaterialUpdates(GfxDevice& gfxDevice)
|
||||||
{
|
{
|
||||||
if (!pendingMaterialUploads.empty()) {
|
if (!pendingMaterialUploads.empty())
|
||||||
|
{
|
||||||
// Material data is currently shared by all frame contexts. Wait before
|
// Material data is currently shared by all frame contexts. Wait before
|
||||||
// mutating it so an older in-flight frame cannot read the overwritten
|
// mutating it so an older in-flight frame cannot read the overwritten
|
||||||
// range. This can later be replaced with per-frame material buffers.
|
// range. This can later be replaced with per-frame material buffers.
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ void CubeMap::RenderToCubemap(
|
|||||||
}
|
}
|
||||||
|
|
||||||
gfxDevice.getImmediateExecuter().immediateSubmit([&](VkCommandBuffer cmd) {
|
gfxDevice.getImmediateExecuter().immediateSubmit([&](VkCommandBuffer cmd) {
|
||||||
|
vkutil::ScopedCmdLabel label(cmd, "HDRI To Cubemap", vkutil::labelColors::sky);
|
||||||
|
|
||||||
VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||||
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||||
@@ -108,6 +110,8 @@ void CubeMap::RenderToCubemap(
|
|||||||
scissor.extent = {size, size};
|
scissor.extent = {size, size};
|
||||||
|
|
||||||
for (std::uint32_t face = 0; face < 6; ++face) {
|
for (std::uint32_t face = 0; face < 6; ++face) {
|
||||||
|
vkutil::cmdBeginLabelFormatted(cmd, vkutil::labelColors::draw, "Cubemap Face %u", face);
|
||||||
|
|
||||||
PC pc{};
|
PC pc{};
|
||||||
pc.viewMtx = viewMatrices[face];
|
pc.viewMtx = viewMatrices[face];
|
||||||
pc.projMtx = m_projection;
|
pc.projMtx = m_projection;
|
||||||
@@ -153,6 +157,8 @@ void CubeMap::RenderToCubemap(
|
|||||||
vkCmdDraw(cmd, 36, 1, 0, 0);
|
vkCmdDraw(cmd, 36, 1, 0, 0);
|
||||||
|
|
||||||
vkCmdEndRendering(cmd);
|
vkCmdEndRendering(cmd);
|
||||||
|
|
||||||
|
vkutil::cmdEndLabel(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||||
@@ -199,6 +205,8 @@ void CubeMap::CreateCubeMap(
|
|||||||
.mipMap = false,
|
.mipMap = false,
|
||||||
.isCubemap = true
|
.isCubemap = true
|
||||||
});
|
});
|
||||||
|
cubeMapImage.debugName = "skybox cubemap";
|
||||||
|
vkutil::addDebugLabel(gfxDevice.getDevice(), cubeMapImage.image, cubeMapImage.debugName.c_str());
|
||||||
|
|
||||||
std::array<VkImageView, 6> faceViews{};
|
std::array<VkImageView, 6> faceViews{};
|
||||||
|
|
||||||
@@ -220,6 +228,9 @@ void CubeMap::CreateCubeMap(
|
|||||||
&viewInfo,
|
&viewInfo,
|
||||||
nullptr,
|
nullptr,
|
||||||
&faceViews[face]));
|
&faceViews[face]));
|
||||||
|
|
||||||
|
const std::string faceViewName = "skybox cubemap face " + std::to_string(face) + " (view)";
|
||||||
|
vkutil::addDebugLabel(gfxDevice.getDevice(), faceViews[face], faceViewName.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
spdlog::info("HDRI image id = {}", m_hdrImage);
|
spdlog::info("HDRI image id = {}", m_hdrImage);
|
||||||
@@ -305,6 +316,7 @@ void CubeMap::InitCubemapPipeline(
|
|||||||
|
|
||||||
PipelineConfigInfo pipelineConfig{};
|
PipelineConfigInfo pipelineConfig{};
|
||||||
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
|
Pipeline::DefaultPipelineConfigInfo(pipelineConfig);
|
||||||
|
pipelineConfig.name = "HDRI To Cubemap Pipeline";
|
||||||
|
|
||||||
pipelineConfig.vertexAttributeDescriptions = {};
|
pipelineConfig.vertexAttributeDescriptions = {};
|
||||||
pipelineConfig.vertexBindingDescriptions = {};
|
pipelineConfig.vertexBindingDescriptions = {};
|
||||||
|
|||||||
@@ -0,0 +1,439 @@
|
|||||||
|
#include <destrum/Graphics/ResourcesInspector.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <charconv>
|
||||||
|
#include <system_error>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <vulkan/vk_enum_string_helper.h>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/Caches/ImageCache.h>
|
||||||
|
#include <destrum/Graphics/Caches/MaterialCache.h>
|
||||||
|
#include <destrum/Graphics/Pipelines/ImguiPass.h>
|
||||||
|
#include <destrum/Graphics/RenderResources.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
[[nodiscard]] const char* StripPrefix(const char* name, const char* prefix)
|
||||||
|
{
|
||||||
|
const std::size_t length = std::strlen(prefix);
|
||||||
|
return std::strncmp(name, prefix, length) == 0 ? name + length : name;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const char* FormatName(VkFormat format)
|
||||||
|
{
|
||||||
|
return StripPrefix(string_VkFormat(format), "VK_FORMAT_");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const char* LayoutName(VkImageLayout layout)
|
||||||
|
{
|
||||||
|
return StripPrefix(string_VkImageLayout(layout), "VK_IMAGE_LAYOUT_");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::string UsageNames(VkImageUsageFlags usage)
|
||||||
|
{
|
||||||
|
struct UsageName
|
||||||
|
{
|
||||||
|
VkImageUsageFlagBits bit;
|
||||||
|
const char* name;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr UsageName usageNames[] = {
|
||||||
|
{VK_IMAGE_USAGE_SAMPLED_BIT, "sampled"},
|
||||||
|
{VK_IMAGE_USAGE_STORAGE_BIT, "storage"},
|
||||||
|
{VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, "color attachment"},
|
||||||
|
{VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, "depth attachment"},
|
||||||
|
{VK_IMAGE_USAGE_TRANSFER_SRC_BIT, "transfer src"},
|
||||||
|
{VK_IMAGE_USAGE_TRANSFER_DST_BIT, "transfer dst"},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
for (const UsageName& usageName : usageNames)
|
||||||
|
{
|
||||||
|
if ((usage & usageName.bit) == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!result.empty())
|
||||||
|
{
|
||||||
|
result += ", ";
|
||||||
|
}
|
||||||
|
result += usageName.name;
|
||||||
|
}
|
||||||
|
return result.empty() ? "none" : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsDepthFormat(VkFormat format)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case VK_FORMAT_D16_UNORM:
|
||||||
|
case VK_FORMAT_X8_D24_UNORM_PACK32:
|
||||||
|
case VK_FORMAT_D32_SFLOAT:
|
||||||
|
case VK_FORMAT_D16_UNORM_S8_UINT:
|
||||||
|
case VK_FORMAT_D24_UNORM_S8_UINT:
|
||||||
|
case VK_FORMAT_D32_SFLOAT_S8_UINT:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool ContainsCaseInsensitive(std::string_view haystack, std::string_view needle)
|
||||||
|
{
|
||||||
|
const auto it = std::search(
|
||||||
|
haystack.begin(), haystack.end(),
|
||||||
|
needle.begin(), needle.end(),
|
||||||
|
[](char a, char b)
|
||||||
|
{
|
||||||
|
return std::tolower(static_cast<unsigned char>(a)) == std::tolower(static_cast<unsigned char>(b));
|
||||||
|
});
|
||||||
|
return it != haystack.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const char* TextureIntentName(TextureIntent intent)
|
||||||
|
{
|
||||||
|
switch (intent)
|
||||||
|
{
|
||||||
|
case TextureIntent::ColorSrgb: return "color (sRGB)";
|
||||||
|
case TextureIntent::DataLinear: return "data (linear)";
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourcesInspector::PreviewInfo ResourcesInspector::getPreviewInfo(const GPUImage& image)
|
||||||
|
{
|
||||||
|
if (image.image == VK_NULL_HANDLE || image.imageView == VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
|
return {false, "no image view"};
|
||||||
|
}
|
||||||
|
if ((image.usage & VK_IMAGE_USAGE_SAMPLED_BIT) == 0)
|
||||||
|
{
|
||||||
|
return {false, "not sampleable"};
|
||||||
|
}
|
||||||
|
if (image.isCubemap)
|
||||||
|
{
|
||||||
|
return {false, "cubemap"};
|
||||||
|
}
|
||||||
|
if (image.numLayers > 1)
|
||||||
|
{
|
||||||
|
return {false, "image array"};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image.getLayout() == VK_IMAGE_LAYOUT_UNDEFINED)
|
||||||
|
{
|
||||||
|
return {false, "never written"};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImGui reads the image when the UI gets rendered at the very end of the frame. Render targets
|
||||||
|
// like the draw / depth image are in an attachment or transfer layout by then.
|
||||||
|
if (image.getLayout() != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||||
|
{
|
||||||
|
return {false, "render target"};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {true, ""};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcesInspector::drawImagePreview(const GPUImage& image, float maxSize, ImguiPass& imguiPass) const
|
||||||
|
{
|
||||||
|
const float width = static_cast<float>(image.extent.width);
|
||||||
|
const float height = static_cast<float>(image.extent.height);
|
||||||
|
const float scale = maxSize / std::max({width, height, 1.0f});
|
||||||
|
const ImVec2 size{std::max(width * scale, 1.0f), std::max(height * scale, 1.0f)};
|
||||||
|
|
||||||
|
const PreviewInfo preview = getPreviewInfo(image);
|
||||||
|
if (preview.previewable)
|
||||||
|
{
|
||||||
|
const ImTextureID textureId = imguiPass.getFrameTexture(image.imageView);
|
||||||
|
if (textureId != ImTextureID{})
|
||||||
|
{
|
||||||
|
ImGui::ImageWithBg(textureId, size, ImVec2{0.0f, 0.0f}, ImVec2{1.0f, 1.0f}, ImVec4{0.1f, 0.1f, 0.1f, 1.0f});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// placeholder box with the reason it can't be shown
|
||||||
|
const ImVec2 boxSize{maxSize, std::min(maxSize, std::max(size.y, ImGui::GetTextLineHeight() * 2.0f))};
|
||||||
|
const ImVec2 min = ImGui::GetCursorScreenPos();
|
||||||
|
const ImVec2 max{min.x + boxSize.x, min.y + boxSize.y};
|
||||||
|
|
||||||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
|
drawList->AddRectFilled(min, max, ImGui::GetColorU32(ImGuiCol_FrameBg));
|
||||||
|
drawList->AddRect(min, max, ImGui::GetColorU32(ImGuiCol_Border));
|
||||||
|
|
||||||
|
// small thumbnails just get a dash, the reason is in the tooltip
|
||||||
|
const char* text = ImGui::CalcTextSize(preview.reason).x <= boxSize.x - 8.0f ? preview.reason : "-";
|
||||||
|
const ImVec2 textSize = ImGui::CalcTextSize(text);
|
||||||
|
drawList->AddText(
|
||||||
|
ImVec2{min.x + (boxSize.x - textSize.x) * 0.5f, min.y + (boxSize.y - textSize.y) * 0.5f},
|
||||||
|
ImGui::GetColorU32(ImGuiCol_TextDisabled),
|
||||||
|
text);
|
||||||
|
|
||||||
|
ImGui::Dummy(boxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcesInspector::drawImageDetails(ImageID id, const RenderResources& resources) const
|
||||||
|
{
|
||||||
|
const ImageCache& cache = resources.images();
|
||||||
|
const GPUImage& image = cache.images[id];
|
||||||
|
|
||||||
|
ImGui::Text("Image ID: %u", static_cast<unsigned>(id));
|
||||||
|
ImGui::TextWrapped("Name: %s", image.debugName.empty() ? "<unnamed>" : image.debugName.c_str());
|
||||||
|
|
||||||
|
if (id == resources.getWhiteTextureID())
|
||||||
|
{
|
||||||
|
ImGui::TextColored(ImVec4{0.6f, 0.8f, 1.0f, 1.0f}, "Default white (also metallic/roughness + emissive default)");
|
||||||
|
}
|
||||||
|
if (id == resources.getErrorTextureID())
|
||||||
|
{
|
||||||
|
ImGui::TextColored(ImVec4{1.0f, 0.4f, 0.8f, 1.0f}, "Error texture (used when a load fails)");
|
||||||
|
}
|
||||||
|
if (id == resources.getDefaultNormalTextureID())
|
||||||
|
{
|
||||||
|
ImGui::TextColored(ImVec4{0.6f, 0.8f, 1.0f, 1.0f}, "Default normal map");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
ImGui::Text("Size: %u x %u", image.extent.width, image.extent.height);
|
||||||
|
ImGui::Text("Format: %s%s", FormatName(image.format), IsDepthFormat(image.format) ? " (shown as red)" : "");
|
||||||
|
ImGui::Text("Mips: %u Layers: %u%s", image.mipLevels, image.numLayers, image.isCubemap ? " cubemap" : "");
|
||||||
|
ImGui::Text("Layout: %s", LayoutName(image.getLayout()));
|
||||||
|
if (const PreviewInfo preview = getPreviewInfo(image); !preview.previewable)
|
||||||
|
{
|
||||||
|
ImGui::TextDisabled("No preview: %s", preview.reason);
|
||||||
|
}
|
||||||
|
ImGui::TextWrapped("Usage: %s", UsageNames(image.usage).c_str());
|
||||||
|
|
||||||
|
if (const auto it = cache.loadedImagesInfo.find(id); it != cache.loadedImagesInfo.end())
|
||||||
|
{
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::TextWrapped("File: %s", it->second.path.generic_string().c_str());
|
||||||
|
ImGui::Text("Intent: %s%s", TextureIntentName(it->second.intent), it->second.mipMap ? ", mipmapped" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
const MaterialCache& materials = resources.materials();
|
||||||
|
std::vector<std::size_t> users;
|
||||||
|
for (std::size_t materialId = 0; materialId < materials.materials.size(); ++materialId)
|
||||||
|
{
|
||||||
|
if (materials.materials[materialId].diffuseTexture == id)
|
||||||
|
{
|
||||||
|
users.push_back(materialId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!users.empty())
|
||||||
|
{
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("Diffuse texture of %zu material(s):", users.size());
|
||||||
|
|
||||||
|
constexpr std::size_t maxListed = 8;
|
||||||
|
for (std::size_t i = 0; i < std::min(users.size(), maxListed); ++i)
|
||||||
|
{
|
||||||
|
const Material& material = materials.materials[users[i]];
|
||||||
|
ImGui::BulletText(
|
||||||
|
"#%zu %s",
|
||||||
|
users[i],
|
||||||
|
material.name.empty() ? "<unnamed>" : material.name.c_str());
|
||||||
|
}
|
||||||
|
if (users.size() > maxListed)
|
||||||
|
{
|
||||||
|
ImGui::TextDisabled("... and %zu more", users.size() - maxListed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourcesInspector::matchesFilter(ImageID id, const RenderResources& resources) const
|
||||||
|
{
|
||||||
|
const std::string_view filterText{filter};
|
||||||
|
if (filterText.empty())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a plain number matches the image id exactly
|
||||||
|
unsigned filterId = 0;
|
||||||
|
const auto [end, error] = std::from_chars(filterText.data(), filterText.data() + filterText.size(), filterId);
|
||||||
|
if (error == std::errc{} && end == filterText.data() + filterText.size())
|
||||||
|
{
|
||||||
|
return filterId == id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageCache& cache = resources.images();
|
||||||
|
if (ContainsCaseInsensitive(cache.images[id].debugName, filterText))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto it = cache.loadedImagesInfo.find(id); it != cache.loadedImagesInfo.end())
|
||||||
|
{
|
||||||
|
return ContainsCaseInsensitive(it->second.path.generic_string(), filterText);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourcesInspector::drawImageCacheWindow(bool* open, const RenderResources& resources, ImguiPass& imguiPass)
|
||||||
|
{
|
||||||
|
if (!ImGui::Begin("Image Cache", open))
|
||||||
|
{
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resources.isInitialized())
|
||||||
|
{
|
||||||
|
ImGui::TextDisabled("Render resources aren't initialized yet");
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageCache& cache = resources.images();
|
||||||
|
|
||||||
|
std::vector<ImageID> visibleIds;
|
||||||
|
std::size_t imageCount = 0;
|
||||||
|
for (std::size_t i = 0; i < cache.images.size(); ++i)
|
||||||
|
{
|
||||||
|
const auto id = static_cast<ImageID>(i);
|
||||||
|
const GPUImage& image = cache.images[i];
|
||||||
|
if (image.image == VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
++imageCount;
|
||||||
|
if (!matchesFilter(id, resources))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (onlyPreviewable && !getPreviewInfo(image).previewable)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
visibleIds.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Text("%zu images (bindless capacity %u)", imageCount, cache.getMaxImageCount());
|
||||||
|
|
||||||
|
ImGui::SetNextItemWidth(220.0f);
|
||||||
|
ImGui::InputTextWithHint("##filter", "Filter by name, path or ID", filter, sizeof(filter));
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(140.0f);
|
||||||
|
ImGui::SliderFloat("Thumbnail", &thumbnailSize, 24.0f, 256.0f, "%.0f px");
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Previewable only", &onlyPreviewable);
|
||||||
|
|
||||||
|
if (selectedImageId != NULL_IMAGE_ID &&
|
||||||
|
(selectedImageId >= cache.images.size() || cache.images[selectedImageId].image == VK_NULL_HANDLE))
|
||||||
|
{
|
||||||
|
selectedImageId = NULL_IMAGE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedImageId != NULL_IMAGE_ID)
|
||||||
|
{
|
||||||
|
if (ImGui::CollapsingHeader("Selected Image", ImGuiTreeNodeFlags_DefaultOpen))
|
||||||
|
{
|
||||||
|
drawImagePreview(cache.images[selectedImageId], 256.0f, imguiPass);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
drawImageDetails(selectedImageId, resources);
|
||||||
|
if (ImGui::SmallButton("Clear selection"))
|
||||||
|
{
|
||||||
|
selectedImageId = NULL_IMAGE_ID;
|
||||||
|
}
|
||||||
|
ImGui::EndGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr ImGuiTableFlags tableFlags =
|
||||||
|
ImGuiTableFlags_Borders |
|
||||||
|
ImGuiTableFlags_RowBg |
|
||||||
|
ImGuiTableFlags_ScrollY |
|
||||||
|
ImGuiTableFlags_Resizable |
|
||||||
|
ImGuiTableFlags_SizingStretchProp;
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("##images", 6, tableFlags))
|
||||||
|
{
|
||||||
|
ImGui::TableSetupScrollFreeze(0, 1);
|
||||||
|
ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 40.0f);
|
||||||
|
ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthFixed, thumbnailSize + 4.0f);
|
||||||
|
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 3.0f);
|
||||||
|
ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, 90.0f);
|
||||||
|
ImGui::TableSetupColumn("Format", ImGuiTableColumnFlags_WidthStretch, 1.5f);
|
||||||
|
ImGui::TableSetupColumn("Layout", ImGuiTableColumnFlags_WidthStretch, 1.5f);
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
// only the visible rows get a texture descriptor
|
||||||
|
const float rowHeight = thumbnailSize + ImGui::GetStyle().CellPadding.y * 2.0f;
|
||||||
|
ImGuiListClipper clipper;
|
||||||
|
clipper.Begin(static_cast<int>(visibleIds.size()), rowHeight);
|
||||||
|
while (clipper.Step())
|
||||||
|
{
|
||||||
|
for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row)
|
||||||
|
{
|
||||||
|
const ImageID id = visibleIds[static_cast<std::size_t>(row)];
|
||||||
|
const GPUImage& image = cache.images[id];
|
||||||
|
|
||||||
|
ImGui::PushID(static_cast<int>(id));
|
||||||
|
ImGui::TableNextRow(ImGuiTableRowFlags_None, thumbnailSize);
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
const std::string idLabel = std::to_string(id);
|
||||||
|
if (ImGui::Selectable(
|
||||||
|
idLabel.c_str(),
|
||||||
|
selectedImageId == id,
|
||||||
|
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap,
|
||||||
|
ImVec2{0.0f, thumbnailSize}))
|
||||||
|
{
|
||||||
|
selectedImageId = selectedImageId == id ? NULL_IMAGE_ID : id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginItemTooltip())
|
||||||
|
{
|
||||||
|
drawImagePreview(image, 256.0f, imguiPass);
|
||||||
|
drawImageDetails(id, resources);
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
drawImagePreview(image, thumbnailSize, imguiPass);
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (const auto it = cache.loadedImagesInfo.find(id); it != cache.loadedImagesInfo.end())
|
||||||
|
{
|
||||||
|
// loaded images are named after their full path, the file name is enough here
|
||||||
|
ImGui::TextWrapped("%s", it->second.path.filename().generic_string().c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui::TextWrapped("%s", image.debugName.empty() ? "<unnamed>" : image.debugName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("%u x %u", image.extent.width, image.extent.height);
|
||||||
|
if (image.mipLevels > 1)
|
||||||
|
{
|
||||||
|
ImGui::TextDisabled("%u mips", image.mipLevels);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::TextUnformatted(FormatName(image.format));
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::TextUnformatted(LayoutName(image.getLayout()));
|
||||||
|
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
@@ -21,6 +21,22 @@ void Swapchain::initSync(VkDevice device) {
|
|||||||
for (std::uint32_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
for (std::uint32_t i = 0; i < FRAMES_IN_FLIGHT; ++i) {
|
||||||
VK_CHECK(vkCreateFence(device, &fenceCreateInfo, nullptr, &frames[i].renderFence));
|
VK_CHECK(vkCreateFence(device, &fenceCreateInfo, nullptr, &frames[i].renderFence));
|
||||||
VK_CHECK(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &frames[i].swapchainSemaphore));
|
VK_CHECK(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &frames[i].swapchainSemaphore));
|
||||||
|
|
||||||
|
const auto fenceName = std::format("frame {} render fence", i);
|
||||||
|
const auto semaphoreName = std::format("frame {} swapchain semaphore", i);
|
||||||
|
vkutil::addDebugLabel(device, frames[i].renderFence, fenceName.c_str());
|
||||||
|
vkutil::addDebugLabel(device, frames[i].swapchainSemaphore, semaphoreName.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Swapchain::nameSwapchainObjects(VkDevice device) const {
|
||||||
|
for (std::size_t i = 0; i < images.size(); ++i) {
|
||||||
|
const auto imageName = std::format("swapchain image {}", i);
|
||||||
|
const auto viewName = std::format("swapchain image {} (view)", i);
|
||||||
|
const auto semaphoreName = std::format("swapchain image {} render semaphore", i);
|
||||||
|
vkutil::addDebugLabel(device, images[i], imageName.c_str());
|
||||||
|
vkutil::addDebugLabel(device, imageViews[i], viewName.c_str());
|
||||||
|
vkutil::addDebugLabel(device, imageRenderSemaphores[i], semaphoreName.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +101,8 @@ void Swapchain::createSwapchain(
|
|||||||
VK_CHECK(vkCreateSemaphore(vkbDevice, &sci, nullptr, &sem));
|
VK_CHECK(vkCreateSemaphore(vkbDevice, &sci, nullptr, &sem));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nameSwapchainObjects(vkbDevice);
|
||||||
|
|
||||||
extent = m_swapchain.extent;
|
extent = m_swapchain.extent;
|
||||||
dirty = false;
|
dirty = false;
|
||||||
}
|
}
|
||||||
@@ -187,6 +205,8 @@ void Swapchain::recreateSwapchain(
|
|||||||
VK_CHECK(vkCreateSemaphore(vkbDevice, &sci, nullptr, &sem));
|
VK_CHECK(vkCreateSemaphore(vkbDevice, &sci, nullptr, &sem));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nameSwapchainObjects(vkbDevice);
|
||||||
|
|
||||||
extent = m_swapchain.extent;
|
extent = m_swapchain.extent;
|
||||||
dirty = false;
|
dirty = false;
|
||||||
}
|
}
|
||||||
|
|||||||
+101
-101
@@ -188,54 +188,124 @@ void vkutil::bufferHostWriteToTransferReadBarrier(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void vkutil::addDebugLabel(VkDevice device, VkImage image, const char* label) {
|
void vkutil::addDebugLabel(VkDevice device, VkObjectType objectType, std::uint64_t objectHandle, const char* label) {
|
||||||
|
if (!vkSetDebugUtilsObjectNameEXT || device == VK_NULL_HANDLE || objectHandle == 0 || label == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
||||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
||||||
.objectType = VK_OBJECT_TYPE_IMAGE,
|
.objectType = objectType,
|
||||||
.objectHandle = (std::uint64_t)image,
|
.objectHandle = objectHandle,
|
||||||
.pObjectName = label,
|
.pObjectName = label,
|
||||||
};
|
};
|
||||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vkutil::addDebugLabel(VkDevice device, VkImage image, const char* label) {
|
||||||
|
addDebugLabel(device, VK_OBJECT_TYPE_IMAGE, reinterpret_cast<std::uint64_t>(image), label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vkutil::addDebugLabel(VkDevice device, VkImageView imageView, const char* label) {
|
||||||
|
addDebugLabel(device, VK_OBJECT_TYPE_IMAGE_VIEW, reinterpret_cast<std::uint64_t>(imageView), label);
|
||||||
}
|
}
|
||||||
|
|
||||||
void vkutil::addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* label) {
|
void vkutil::addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* label) {
|
||||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
addDebugLabel(device, VK_OBJECT_TYPE_SHADER_MODULE, reinterpret_cast<std::uint64_t>(shaderModule), label);
|
||||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
|
||||||
.objectType = VK_OBJECT_TYPE_SHADER_MODULE,
|
|
||||||
.objectHandle = (std::uint64_t)shaderModule,
|
|
||||||
.pObjectName = label,
|
|
||||||
};
|
|
||||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void vkutil::addDebugLabel(VkDevice device, VkPipeline pipeline, const char* label) {
|
void vkutil::addDebugLabel(VkDevice device, VkPipeline pipeline, const char* label) {
|
||||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
addDebugLabel(device, VK_OBJECT_TYPE_PIPELINE, reinterpret_cast<std::uint64_t>(pipeline), label);
|
||||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
}
|
||||||
.objectType = VK_OBJECT_TYPE_PIPELINE,
|
|
||||||
.objectHandle = (std::uint64_t)pipeline,
|
void vkutil::addDebugLabel(VkDevice device, VkPipelineLayout layout, const char* label) {
|
||||||
.pObjectName = label,
|
addDebugLabel(device, VK_OBJECT_TYPE_PIPELINE_LAYOUT, reinterpret_cast<std::uint64_t>(layout), label);
|
||||||
};
|
|
||||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void vkutil::addDebugLabel(VkDevice device, VkBuffer buffer, const char* label) {
|
void vkutil::addDebugLabel(VkDevice device, VkBuffer buffer, const char* label) {
|
||||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
addDebugLabel(device, VK_OBJECT_TYPE_BUFFER, reinterpret_cast<std::uint64_t>(buffer), label);
|
||||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
|
||||||
.objectType = VK_OBJECT_TYPE_BUFFER,
|
|
||||||
.objectHandle = (std::uint64_t)buffer,
|
|
||||||
.pObjectName = label,
|
|
||||||
};
|
|
||||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void vkutil::addDebugLabel(VkDevice device, VkSampler sampler, const char* label) {
|
void vkutil::addDebugLabel(VkDevice device, VkSampler sampler, const char* label) {
|
||||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
addDebugLabel(device, VK_OBJECT_TYPE_SAMPLER, reinterpret_cast<std::uint64_t>(sampler), label);
|
||||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
}
|
||||||
.objectType = VK_OBJECT_TYPE_SAMPLER,
|
|
||||||
.objectHandle = (std::uint64_t)sampler,
|
void vkutil::addDebugLabel(VkDevice device, VkDescriptorPool descriptorPool, const char* label) {
|
||||||
.pObjectName = label,
|
addDebugLabel(device, VK_OBJECT_TYPE_DESCRIPTOR_POOL, reinterpret_cast<std::uint64_t>(descriptorPool), label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vkutil::addDebugLabel(VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const char* label) {
|
||||||
|
addDebugLabel(device, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, reinterpret_cast<std::uint64_t>(descriptorSetLayout), label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vkutil::addDebugLabel(VkDevice device, VkDescriptorSet descriptorSet, const char* label) {
|
||||||
|
addDebugLabel(device, VK_OBJECT_TYPE_DESCRIPTOR_SET, reinterpret_cast<std::uint64_t>(descriptorSet), label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vkutil::addDebugLabel(VkDevice device, VkCommandPool commandPool, const char* label) {
|
||||||
|
addDebugLabel(device, VK_OBJECT_TYPE_COMMAND_POOL, reinterpret_cast<std::uint64_t>(commandPool), label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vkutil::addDebugLabel(VkDevice device, VkCommandBuffer commandBuffer, const char* label) {
|
||||||
|
addDebugLabel(device, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(commandBuffer), label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vkutil::addDebugLabel(VkDevice device, VkFence fence, const char* label) {
|
||||||
|
addDebugLabel(device, VK_OBJECT_TYPE_FENCE, reinterpret_cast<std::uint64_t>(fence), label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vkutil::addDebugLabel(VkDevice device, VkSemaphore semaphore, const char* label) {
|
||||||
|
addDebugLabel(device, VK_OBJECT_TYPE_SEMAPHORE, reinterpret_cast<std::uint64_t>(semaphore), label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vkutil::addDebugLabel(VkDevice device, VkQueue queue, const char* label) {
|
||||||
|
addDebugLabel(device, VK_OBJECT_TYPE_QUEUE, reinterpret_cast<std::uint64_t>(queue), label);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool vkutil::debugLabelsEnabled() {
|
||||||
|
return vkCmdBeginDebugUtilsLabelEXT != nullptr && vkCmdEndDebugUtilsLabelEXT != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vkutil::cmdBeginLabel(VkCommandBuffer cmd, const char* label, const glm::vec4& color) {
|
||||||
|
if (!debugLabelsEnabled() || label == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto labelInfo = VkDebugUtilsLabelEXT{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
|
||||||
|
.pLabelName = label,
|
||||||
|
.color = {color.x, color.y, color.z, color.w},
|
||||||
};
|
};
|
||||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
vkCmdBeginDebugUtilsLabelEXT(cmd, &labelInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vkutil::cmdEndLabel(VkCommandBuffer cmd) {
|
||||||
|
if (!debugLabelsEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vkCmdEndDebugUtilsLabelEXT(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vkutil::cmdInsertLabel(VkCommandBuffer cmd, const char* label, const glm::vec4& color) {
|
||||||
|
if (!vkCmdInsertDebugUtilsLabelEXT || label == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto labelInfo = VkDebugUtilsLabelEXT{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
|
||||||
|
.pLabelName = label,
|
||||||
|
.color = {color.x, color.y, color.z, color.w},
|
||||||
|
};
|
||||||
|
vkCmdInsertDebugUtilsLabelEXT(cmd, &labelInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
vkutil::ScopedCmdLabel::ScopedCmdLabel(VkCommandBuffer cmd, const char* label, const glm::vec4& color)
|
||||||
|
: cmd(cmd) {
|
||||||
|
cmdBeginLabel(cmd, label, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
vkutil::ScopedCmdLabel::~ScopedCmdLabel() {
|
||||||
|
cmdEndLabel(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
vkutil::RenderInfo vkutil::createRenderingInfo(const RenderingInfoParams& params) {
|
vkutil::RenderInfo vkutil::createRenderingInfo(const RenderingInfoParams& params) {
|
||||||
@@ -319,76 +389,6 @@ VkShaderModule vkutil::loadShaderModule(const std::filesystem::path& path, VkDev
|
|||||||
return shaderModule;
|
return shaderModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addDebugLabel(VkDevice device, VkImage image, const char* label) {
|
|
||||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
|
||||||
.objectType = VK_OBJECT_TYPE_IMAGE,
|
|
||||||
.objectHandle = (std::uint64_t)image,
|
|
||||||
.pObjectName = label,
|
|
||||||
};
|
|
||||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addDebugLabel(VkDevice device, VkImageView imageView, const char* label) {
|
|
||||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
|
||||||
.objectType = VK_OBJECT_TYPE_IMAGE_VIEW,
|
|
||||||
.objectHandle = (std::uint64_t)imageView,
|
|
||||||
.pObjectName = label,
|
|
||||||
};
|
|
||||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addDebugLabel(VkDevice device, VkShaderModule shaderModule, const char* label) {
|
|
||||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
|
||||||
.objectType = VK_OBJECT_TYPE_SHADER_MODULE,
|
|
||||||
.objectHandle = (std::uint64_t)shaderModule,
|
|
||||||
.pObjectName = label,
|
|
||||||
};
|
|
||||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addDebugLabel(VkDevice device, VkPipeline pipeline, const char* label) {
|
|
||||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
|
||||||
.objectType = VK_OBJECT_TYPE_PIPELINE,
|
|
||||||
.objectHandle = (std::uint64_t)pipeline,
|
|
||||||
.pObjectName = label,
|
|
||||||
};
|
|
||||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addDebugLabel(VkDevice device, VkPipelineLayout layout, const char* label) {
|
|
||||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
|
||||||
.objectType = VK_OBJECT_TYPE_PIPELINE_LAYOUT,
|
|
||||||
.objectHandle = (std::uint64_t)layout,
|
|
||||||
.pObjectName = label,
|
|
||||||
};
|
|
||||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addDebugLabel(VkDevice device, VkBuffer buffer, const char* label) {
|
|
||||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
|
||||||
.objectType = VK_OBJECT_TYPE_BUFFER,
|
|
||||||
.objectHandle = (std::uint64_t)buffer,
|
|
||||||
.pObjectName = label,
|
|
||||||
};
|
|
||||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addDebugLabel(VkDevice device, VkSampler sampler, const char* label) {
|
|
||||||
const auto nameInfo = VkDebugUtilsObjectNameInfoEXT{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
|
||||||
.objectType = VK_OBJECT_TYPE_SAMPLER,
|
|
||||||
.objectHandle = (std::uint64_t)sampler,
|
|
||||||
.pObjectName = label,
|
|
||||||
};
|
|
||||||
if (vkSetDebugUtilsObjectNameEXT) vkSetDebugUtilsObjectNameEXT(device, &nameInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
vkutil::RenderInfo createRenderingInfo(const vkutil::RenderingInfoParams& params) {
|
vkutil::RenderInfo createRenderingInfo(const vkutil::RenderingInfoParams& params) {
|
||||||
return vkutil::createRenderingInfo(params);
|
return vkutil::createRenderingInfo(params);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,12 +164,13 @@ void Scene::Render(const RenderContext& ctx) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::RenderImgui() {
|
void Scene::RenderImgui(bool* open) {
|
||||||
ImGuiUtils::RenderSceneInspector(
|
ImGuiUtils::RenderSceneInspector(
|
||||||
m_name,
|
m_name,
|
||||||
m_objects,
|
m_objects,
|
||||||
m_pendingAdditions,
|
m_pendingAdditions,
|
||||||
m_SelectedObjectId);
|
m_SelectedObjectId,
|
||||||
|
open);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scene::CleanupDestroyedGameObjects() {
|
void Scene::CleanupDestroyedGameObjects() {
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ void SceneManager::Render(const RenderContext& ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::RenderImgui() {
|
void SceneManager::RenderImgui(bool* open) {
|
||||||
for (const auto& scene : m_activeScenes) {
|
for (const auto& scene : m_activeScenes) {
|
||||||
scene->RenderImgui();
|
scene->RenderImgui(open);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,8 @@ namespace ImGuiUtils {
|
|||||||
std::string_view sceneName,
|
std::string_view sceneName,
|
||||||
const std::vector<std::shared_ptr<GameObject>>& objectsSource,
|
const std::vector<std::shared_ptr<GameObject>>& objectsSource,
|
||||||
const std::vector<std::shared_ptr<GameObject>>& pendingAdditions,
|
const std::vector<std::shared_ptr<GameObject>>& pendingAdditions,
|
||||||
ObjectId& selectedObjectId) {
|
ObjectId& selectedObjectId,
|
||||||
|
bool* open) {
|
||||||
RegisterEngineComponents();
|
RegisterEngineComponents();
|
||||||
|
|
||||||
std::vector<GameObject*> objects;
|
std::vector<GameObject*> objects;
|
||||||
@@ -154,7 +155,7 @@ namespace ImGuiUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SetNextWindowSize(ImVec2(640.0f, 520.0f), ImGuiCond_FirstUseEver);
|
ImGui::SetNextWindowSize(ImVec2(640.0f, 520.0f), ImGuiCond_FirstUseEver);
|
||||||
if (!ImGui::Begin("Inspector")) {
|
if (!ImGui::Begin("Inspector", open)) {
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+24
@@ -64,8 +64,32 @@ set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
|||||||
set(ASSIMP_INJECT_DEBUG_POSTFIX OFF CACHE BOOL "" FORCE)
|
set(ASSIMP_INJECT_DEBUG_POSTFIX OFF CACHE BOOL "" FORCE)
|
||||||
set(ASSIMP_INSTALL OFF CACHE BOOL "" FORCE)
|
set(ASSIMP_INSTALL OFF CACHE BOOL "" FORCE)
|
||||||
set(ASSIMP_WARNINGS_AS_ERRORS OFF CACHE BOOL "" FORCE)
|
set(ASSIMP_WARNINGS_AS_ERRORS OFF CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
|
# assimp is unbearably slow unoptimized (a big FBX takes ~10s), so build it optimized even in Debug.
|
||||||
|
# This only affects targets created inside the assimp directory (including its bundled zlib).
|
||||||
|
set(DESTRUM_SAVED_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
|
||||||
|
set(DESTRUM_SAVED_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
|
||||||
|
if (MSVC)
|
||||||
|
foreach (flags_var CMAKE_C_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG)
|
||||||
|
string(REPLACE "/RTC1" "" ${flags_var} "${${flags_var}}")
|
||||||
|
string(REPLACE "/Od" "/O2" ${flags_var} "${${flags_var}}")
|
||||||
|
string(REPLACE "/Ob0" "/Ob2" ${flags_var} "${${flags_var}}")
|
||||||
|
endforeach ()
|
||||||
|
else ()
|
||||||
|
string(APPEND CMAKE_C_FLAGS_DEBUG " -O2")
|
||||||
|
string(APPEND CMAKE_CXX_FLAGS_DEBUG " -O2")
|
||||||
|
endif ()
|
||||||
|
|
||||||
add_subdirectory(assimp)
|
add_subdirectory(assimp)
|
||||||
|
|
||||||
|
# assimp's own CMakeLists appends "/Od" to the Debug flags again, target options come later and win
|
||||||
|
if (MSVC)
|
||||||
|
target_compile_options(assimp PRIVATE $<$<CONFIG:Debug>:/O2>)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(CMAKE_C_FLAGS_DEBUG "${DESTRUM_SAVED_C_FLAGS_DEBUG}")
|
||||||
|
set(CMAKE_CXX_FLAGS_DEBUG "${DESTRUM_SAVED_CXX_FLAGS_DEBUG}")
|
||||||
|
|
||||||
set(IMGUI_DIR "${CMAKE_CURRENT_LIST_DIR}/imgui")
|
set(IMGUI_DIR "${CMAKE_CURRENT_LIST_DIR}/imgui")
|
||||||
|
|
||||||
add_library(imgui STATIC
|
add_library(imgui STATIC
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ public:
|
|||||||
void customFixedUpdate(float dt) override;
|
void customFixedUpdate(float dt) override;
|
||||||
|
|
||||||
void onWindowResize(int newWidth, int newHeight) override;
|
void onWindowResize(int newWidth, int newHeight) override;
|
||||||
|
void customWindowsMenu() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void drawDebugMenuBar();
|
void drawDebugMenuBar();
|
||||||
@@ -49,6 +50,7 @@ private:
|
|||||||
bool renderBoxColliders{false};
|
bool renderBoxColliders{false};
|
||||||
bool renderQuads{true};
|
bool renderQuads{true};
|
||||||
bool renderDebugLines{true};
|
bool renderDebugLines{true};
|
||||||
|
bool showTestWindow{true};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //LIGHTKEEPER_H
|
#endif //LIGHTKEEPER_H
|
||||||
|
|||||||
+127
-67
@@ -6,6 +6,7 @@
|
|||||||
#include <destrum/Assets/AssetManager.h>
|
#include <destrum/Assets/AssetManager.h>
|
||||||
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
||||||
#include <destrum/Graphics/Managers/QuadRenderingManager.h>
|
#include <destrum/Graphics/Managers/QuadRenderingManager.h>
|
||||||
|
#include "glm/gtc/constants.hpp"
|
||||||
#include "glm/gtx/transform.hpp"
|
#include "glm/gtx/transform.hpp"
|
||||||
#include "spdlog/spdlog.h"
|
#include "spdlog/spdlog.h"
|
||||||
#include <destrum/Components/Physics/Rigidbody.h>
|
#include <destrum/Components/Physics/Rigidbody.h>
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
#include "destrum/ObjectModel/GameObject.h"
|
#include "destrum/ObjectModel/GameObject.h"
|
||||||
#include "destrum/Util/ModelDoc.h"
|
#include "destrum/Util/ModelDoc.h"
|
||||||
#include "destrum/Components/Animator.h"
|
#include "destrum/Components/Animator.h"
|
||||||
|
#include "destrum/Components/LightComponent.h"
|
||||||
#include <components/FirstPersonController.h>
|
#include <components/FirstPersonController.h>
|
||||||
#include <components/TriggerLoggerComponent.h>
|
#include <components/TriggerLoggerComponent.h>
|
||||||
|
|
||||||
@@ -151,65 +153,65 @@ void LightKeeper::customInit()
|
|||||||
characterModelOptions.loadSkeleton = true;
|
characterModelOptions.loadSkeleton = true;
|
||||||
characterModelOptions.loadAnimations = true;
|
characterModelOptions.loadAnimations = true;
|
||||||
|
|
||||||
// auto charModel = ModelDoc::LoadModel(
|
auto charModel = ModelDoc::LoadModel(
|
||||||
// AssetFS::GetInstance().GetFullPath("engine://cotw-capybara-male/source/capybara.fbx").generic_string(),
|
AssetFS::GetInstance().GetFullPath("engine://cotw-capybara-male/source/capybara.fbx").generic_string(),
|
||||||
// characterModelOptions
|
characterModelOptions
|
||||||
// );
|
);
|
||||||
// ModelDocUtils::LogModelDocSummary(charModel, "capybara.fbx");
|
ModelDocUtils::LogModelDocSummary(charModel, "capybara.fbx");
|
||||||
//
|
|
||||||
// const auto& charPrimitive = ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow(
|
const auto& charPrimitive = ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow(
|
||||||
// charModel,
|
charModel,
|
||||||
// "engine://cotw-capybara-male/source/capybara.fbx"
|
"engine://cotw-capybara-male/source/capybara.fbx"
|
||||||
// );
|
);
|
||||||
//
|
|
||||||
// const auto charMeshID = resources.meshes().addMesh(gfxDevice, charPrimitive.mesh);
|
const auto charMeshID = resources.meshes().addMesh(gfxDevice, charPrimitive.mesh);
|
||||||
//
|
|
||||||
// const auto charTexturePath = ModelDocUtils::PickTexturePath(
|
const auto charTexturePath = ModelDocUtils::PickTexturePath(
|
||||||
// charModel,
|
charModel,
|
||||||
// charPrimitive,
|
charPrimitive,
|
||||||
// AssetFS::GetInstance().GetFullPath(
|
AssetFS::GetInstance().GetFullPath(
|
||||||
// "engine://cotw-capybara-male/textures/capybara_male_light_brown_dif.ddsc.DECA.RE.pngballs")
|
"engine://cotw-capybara-male/textures/capybara_male_light_brown_dif.ddsc.DECA.RE.pngballs")
|
||||||
// );
|
);
|
||||||
//
|
|
||||||
// const auto charTextureID = resources.loadImageFromFile(gfxDevice, charTexturePath);
|
const auto charTextureID = resources.loadImageFromFile(gfxDevice, charTexturePath);
|
||||||
// // const auto charTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("engine://char.jpg"));
|
// const auto charTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("engine://char.jpg"));
|
||||||
// const auto charMaterialID = resources.materials().addMaterial({
|
const auto charMaterialID = resources.materials().addMaterial({
|
||||||
// .baseColor = ModelDocUtils::GetImportedBaseColor(
|
.baseColor = ModelDocUtils::GetImportedBaseColor(
|
||||||
// charModel, charPrimitive),
|
charModel, charPrimitive),
|
||||||
// .diffuseTexture = charTextureID,
|
.diffuseTexture = charTextureID,
|
||||||
// .name = ModelDocUtils::GetImportedMaterialName(
|
.name = ModelDocUtils::GetImportedMaterialName(
|
||||||
// charModel, charPrimitive, "CharacterMaterial"),
|
charModel, charPrimitive, "CharacterMaterial"),
|
||||||
// });
|
});
|
||||||
//
|
|
||||||
// const auto charMeshComp = CharObj->AddComponent<MeshRendererComponent>();
|
const auto charMeshComp = CharObj->AddComponent<MeshRendererComponent>();
|
||||||
// charMeshComp->SetMeshID(charMeshID);
|
charMeshComp->SetMeshID(charMeshID);
|
||||||
// charMeshComp->SetMaterialID(charMaterialID);
|
charMeshComp->SetMaterialID(charMaterialID);
|
||||||
//
|
|
||||||
// const auto animator = CharObj->AddComponent<Animator>();
|
const auto animator = CharObj->AddComponent<Animator>();
|
||||||
// animator->setSkeleton(std::move(charModel.skeleton));
|
animator->setSkeleton(std::move(charModel.skeleton));
|
||||||
//
|
|
||||||
// std::string firstAnimationName;
|
std::string firstAnimationName;
|
||||||
//
|
|
||||||
// for (auto& clip : charModel.animations)
|
for (auto& clip : charModel.animations)
|
||||||
// {
|
{
|
||||||
// spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
|
spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
|
||||||
//
|
|
||||||
// if (firstAnimationName.empty())
|
if (firstAnimationName.empty())
|
||||||
// firstAnimationName = clip.name;
|
firstAnimationName = clip.name;
|
||||||
//
|
|
||||||
// animator->addClip(std::make_shared<SkeletalAnimation>(std::move(clip)));
|
animator->addClip(std::make_shared<SkeletalAnimation>(std::move(clip)));
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// if (!firstAnimationName.empty())
|
if (!firstAnimationName.empty())
|
||||||
// {
|
{
|
||||||
// spdlog::info("Playing animation: '{}'", firstAnimationName);
|
spdlog::info("Playing animation: '{}'", firstAnimationName);
|
||||||
// animator->play(firstAnimationName);
|
animator->play(firstAnimationName);
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// animator->play("capybara_canter_fwd_01|capybara_canter_fwd_01|run");
|
animator->play("capybara_canter_fwd_01|capybara_canter_fwd_01|run");
|
||||||
//
|
|
||||||
// CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f, 0.f, 0.f));
|
CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f, 0.f, 0.f));
|
||||||
// CharObj->GetTransform().SetWorldScale(0.01f, 0.01f, 0.01f);
|
CharObj->GetTransform().SetWorldScale(0.01f, 0.01f, 0.01f);
|
||||||
|
|
||||||
// ModelDoc::LoadOptions options{};
|
// ModelDoc::LoadOptions options{};
|
||||||
// options.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
|
// options.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
|
||||||
@@ -447,12 +449,59 @@ void LightKeeper::customInit()
|
|||||||
triggerObj->AddComponent<TriggerLoggerComponent>();
|
triggerObj->AddComponent<TriggerLoggerComponent>();
|
||||||
triggerObj->GetTransform().SetWorldPosition({0.0f, 0.0f, 0.0f});
|
triggerObj->GetTransform().SetWorldPosition({0.0f, 0.0f, 0.0f});
|
||||||
|
|
||||||
auto playerObj = scene.CreateGameObject("Player");
|
// Forward+ test lights: a dim sun and a ring of colored point lights orbiting the origin
|
||||||
playerObj->GetTransform().SetWorldPosition(glm::vec3(0.0f, 2.0f, -5.0f));
|
// auto sunObj = scene.CreateGameObject("Sun");
|
||||||
auto* fps = playerObj->AddComponent<FirstPersonController>();
|
// auto* sun = sunObj->AddComponent<LightComponent>(LightType::Directional);
|
||||||
m_Player = playerObj;
|
// sun->SetColor(glm::vec3{1.0f, 0.95f, 0.85f});
|
||||||
m_FPSController = fps;
|
// sun->SetIntensity(1.0f);
|
||||||
scene.GetPhysics().RegisterGameObject(*playerObj);
|
// sun->SetCastShadows(true);
|
||||||
|
// sunObj->GetTransform().SetWorldRotation(glm::vec3{-50.0f, 30.0f, 0.0f});
|
||||||
|
//
|
||||||
|
// // static shadow casters, so the sun's shadows are visible without spawning balls
|
||||||
|
// for (int i = 0; i < 3; i++)
|
||||||
|
// {
|
||||||
|
// auto casterObj = scene.CreateGameObject("ShadowCaster " + std::to_string(i));
|
||||||
|
// auto* casterMesh = casterObj->AddComponent<MeshRendererComponent>();
|
||||||
|
// casterMesh->SetMeshID(sphereMesh);
|
||||||
|
// casterMesh->SetMaterialID(sphereMaterial);
|
||||||
|
// casterObj->GetTransform().SetWorldPosition(glm::vec3{-4.0f + 4.0f * static_cast<float>(i), 0.5f, 3.0f});
|
||||||
|
// casterObj->GetTransform().SetWorldScale(glm::vec3{0.015f});
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// constexpr int pointLightCount = 32;
|
||||||
|
// constexpr float pointLightRingRadius = 6.0f;
|
||||||
|
// for (int i = 0; i < pointLightCount; i++)
|
||||||
|
// {
|
||||||
|
// const float t = static_cast<float>(i) / static_cast<float>(pointLightCount);
|
||||||
|
// const float angle = t * glm::two_pi<float>();
|
||||||
|
//
|
||||||
|
// auto lightObj = scene.CreateGameObject("PointLight " + std::to_string(i));
|
||||||
|
// lightObj->GetTransform().SetWorldPosition(glm::vec3{
|
||||||
|
// std::cos(angle) * pointLightRingRadius,
|
||||||
|
// -0.25f,
|
||||||
|
// std::sin(angle) * pointLightRingRadius
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// auto* light = lightObj->AddComponent<LightComponent>(LightType::Point);
|
||||||
|
// light->SetColor(glm::vec3{
|
||||||
|
// 0.5f + 0.5f * std::cos(glm::two_pi<float>() * t),
|
||||||
|
// 0.5f + 0.5f * std::cos(glm::two_pi<float>() * (t + 0.33f)),
|
||||||
|
// 0.5f + 0.5f * std::cos(glm::two_pi<float>() * (t + 0.67f))
|
||||||
|
// });
|
||||||
|
// light->SetIntensity(1.0f);
|
||||||
|
// light->SetRange(3.0f);
|
||||||
|
//
|
||||||
|
// auto* orbit = lightObj->AddComponent<Rotator>(pointLightRingRadius, 0.5f);
|
||||||
|
// orbit->SetAxis(glm::vec3{0.0f, 1.0f, 0.0f});
|
||||||
|
// orbit->SetPivotPosition(glm::vec3{0.0f, -0.25f, 0.0f});
|
||||||
|
// }
|
||||||
|
|
||||||
|
// auto playerObj = scene.CreateGameObject("Player");
|
||||||
|
// playerObj->GetTransform().SetWorldPosition(glm::vec3(0.0f, 2.0f, -5.0f));
|
||||||
|
// auto* fps = playerObj->AddComponent<FirstPersonController>();
|
||||||
|
// m_Player = playerObj;
|
||||||
|
// m_FPSController = fps;
|
||||||
|
// scene.GetPhysics().RegisterGameObject(*playerObj);
|
||||||
|
|
||||||
const auto texPath = AssetFS::GetInstance().GetFullPath("engine://textures/kobe.png");
|
const auto texPath = AssetFS::GetInstance().GetFullPath("engine://textures/kobe.png");
|
||||||
texID = resources.loadImageFromFile(gfxDevice, texPath);
|
texID = resources.loadImageFromFile(gfxDevice, texPath);
|
||||||
@@ -490,8 +539,8 @@ void LightKeeper::customUpdate(float dt)
|
|||||||
renderer.setRenderWireframe(!renderer.getRenderWireframe());
|
renderer.setRenderWireframe(!renderer.getRenderWireframe());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_params.showDebugUi) {
|
if (m_params.showDebugUi && showTestWindow) {
|
||||||
ImGui::Begin("Test");
|
ImGui::Begin("Test", &showTestWindow);
|
||||||
if (ImGui::Button("SPawn ball"))
|
if (ImGui::Button("SPawn ball"))
|
||||||
{
|
{
|
||||||
auto sphere = SceneManager::GetInstance().GetCurrentScene().CreateGameObject("Sphere");
|
auto sphere = SceneManager::GetInstance().GetCurrentScene().CreateGameObject("Sphere");
|
||||||
@@ -514,6 +563,12 @@ void LightKeeper::customUpdate(float dt)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LightKeeper::customWindowsMenu()
|
||||||
|
{
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::MenuItem("Test", nullptr, &showTestWindow);
|
||||||
|
}
|
||||||
|
|
||||||
void LightKeeper::drawDebugMenuBar()
|
void LightKeeper::drawDebugMenuBar()
|
||||||
{
|
{
|
||||||
if (!ImGui::BeginMainMenuBar()) {
|
if (!ImGui::BeginMainMenuBar()) {
|
||||||
@@ -553,6 +608,11 @@ void LightKeeper::drawDebugMenuBar()
|
|||||||
ImGui::MenuItem("Box Colliders", nullptr, &renderBoxColliders);
|
ImGui::MenuItem("Box Colliders", nullptr, &renderBoxColliders);
|
||||||
ImGui::MenuItem("2D Quads", nullptr, &renderQuads);
|
ImGui::MenuItem("2D Quads", nullptr, &renderQuads);
|
||||||
ImGui::MenuItem("Debug Lines", nullptr, &renderDebugLines);
|
ImGui::MenuItem("Debug Lines", nullptr, &renderDebugLines);
|
||||||
|
|
||||||
|
bool lightHeatmap = renderer.getRenderLightHeatmap();
|
||||||
|
if (ImGui::MenuItem("Forward+ Light Heatmap", nullptr, &lightHeatmap)) {
|
||||||
|
renderer.setRenderLightHeatmap(lightHeatmap);
|
||||||
|
}
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <destrum/Components/Physics/BoxCollider.h>
|
#include <destrum/Components/Physics/BoxCollider.h>
|
||||||
#include <destrum/Components/Physics/Collider.h>
|
#include <destrum/Components/Physics/Collider.h>
|
||||||
#include <destrum/Components/Animator.h>
|
#include <destrum/Components/Animator.h>
|
||||||
|
#include <destrum/Components/LightComponent.h>
|
||||||
#include <destrum/Components/Physics/Rigidbody.h>
|
#include <destrum/Components/Physics/Rigidbody.h>
|
||||||
#include <destrum/Graphics/Material.h>
|
#include <destrum/Graphics/Material.h>
|
||||||
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
#include <destrum/Graphics/Managers/LineRenderingManager.h>
|
||||||
@@ -171,6 +172,7 @@ namespace {
|
|||||||
|
|
||||||
const char* canonicalNames[] = {
|
const char* canonicalNames[] = {
|
||||||
"MeshRendererComponent",
|
"MeshRendererComponent",
|
||||||
|
"LightComponent",
|
||||||
"Rotator",
|
"Rotator",
|
||||||
"Spinner",
|
"Spinner",
|
||||||
"OrbitAndSpin",
|
"OrbitAndSpin",
|
||||||
@@ -447,6 +449,42 @@ namespace {
|
|||||||
"Animator scene data must not embed animation keyframes");
|
"Animator scene data must not embed animation keyframes");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testLightComponentRoundTrip()
|
||||||
|
{
|
||||||
|
GameObject object{"light"};
|
||||||
|
auto* light = object.AddComponent<LightComponent>(LightType::Spot);
|
||||||
|
light->SetColor(glm::vec3{1.0f, 0.5f, 0.25f});
|
||||||
|
light->SetIntensity(4.0f);
|
||||||
|
light->SetRange(7.5f);
|
||||||
|
light->SetConeAngles(0.2f, 0.4f);
|
||||||
|
light->SetCastShadows(true);
|
||||||
|
|
||||||
|
const auto data = light->Serialize();
|
||||||
|
|
||||||
|
GameObject loadedObject{"loaded light"};
|
||||||
|
auto* loaded = loadedObject.AddComponent<LightComponent>();
|
||||||
|
loaded->Deserialize(data);
|
||||||
|
|
||||||
|
check(loaded->GetType() == LightType::Spot,
|
||||||
|
"LightComponent must round-trip its type");
|
||||||
|
check(loaded->GetColor() == glm::vec3{1.0f, 0.5f, 0.25f},
|
||||||
|
"LightComponent must round-trip its color");
|
||||||
|
check(loaded->GetIntensity() == 4.0f && loaded->GetRange() == 7.5f,
|
||||||
|
"LightComponent must round-trip intensity and range");
|
||||||
|
check(loaded->GetInnerConeAngle() == 0.2f && loaded->GetOuterConeAngle() == 0.4f,
|
||||||
|
"LightComponent must round-trip its cone angles");
|
||||||
|
check(loaded->GetCastShadows(),
|
||||||
|
"LightComponent must round-trip its cast shadows flag");
|
||||||
|
|
||||||
|
loaded->SetConeAngles(0.5f, 0.1f);
|
||||||
|
check(loaded->GetOuterConeAngle() >= loaded->GetInnerConeAngle(),
|
||||||
|
"LightComponent outer cone must never be smaller than the inner cone");
|
||||||
|
|
||||||
|
loaded->SetRange(0.0f);
|
||||||
|
check(loaded->GetRange() > 0.0f,
|
||||||
|
"LightComponent range must stay positive");
|
||||||
|
}
|
||||||
|
|
||||||
void testSimpleColorMaterial()
|
void testSimpleColorMaterial()
|
||||||
{
|
{
|
||||||
const auto material = Material::SimpleColor(
|
const auto material = Material::SimpleColor(
|
||||||
@@ -561,6 +599,7 @@ int main()
|
|||||||
testJoltSphereCollisions();
|
testJoltSphereCollisions();
|
||||||
testSceneJoltSphereCollisions();
|
testSceneJoltSphereCollisions();
|
||||||
testAnimatorAssetReferences();
|
testAnimatorAssetReferences();
|
||||||
|
testLightComponentRoundTrip();
|
||||||
testSimpleColorMaterial();
|
testSimpleColorMaterial();
|
||||||
testAssetReferenceCacheKeys();
|
testAssetReferenceCacheKeys();
|
||||||
testTriggerZone();
|
testTriggerZone();
|
||||||
|
|||||||
Reference in New Issue
Block a user