From a804fb70e83a72673bf6bb125ee71fe8c7ca0769 Mon Sep 17 00:00:00 2001 From: exdal <63502313+exdal@users.noreply.github.com> Date: Sun, 21 Sep 2025 13:43:50 +0300 Subject: [PATCH 1/8] make hiz image transient --- Lorr/Engine/Scene/SceneRenderer.cc | 54 ++++++------------------------ Lorr/Engine/Scene/SceneRenderer.hh | 3 -- 2 files changed, 10 insertions(+), 47 deletions(-) diff --git a/Lorr/Engine/Scene/SceneRenderer.cc b/Lorr/Engine/Scene/SceneRenderer.cc index 97b059fa..a71bdc8b 100644 --- a/Lorr/Engine/Scene/SceneRenderer.cc +++ b/Lorr/Engine/Scene/SceneRenderer.cc @@ -1251,40 +1251,16 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Value{}; - if (self.hiz.extent() != hiz_extent || !self.hiz) { - if (self.hiz_view) { - device.destroy(self.hiz_view.id()); - } - - if (self.hiz) { - device.destroy(self.hiz.id()); - } - - auto hiz_info = ImageInfo{ - .format = vuk::Format::eR32Sfloat, - .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, - .type = vuk::ImageType::e2D, - .extent = hiz_extent, - .mip_count = std::bit_width(ls::max(hiz_extent.width, hiz_extent.height)) - 1_u32, - .name = "HiZ", - }; - self.hiz = Image::create(device, hiz_info).value(); - - auto hiz_view_info = ImageViewInfo{ - .image_usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, - .type = vuk::ImageViewType::e2D, - .subresource_range = { .aspectMask = vuk::ImageAspectFlagBits::eColor, .levelCount = hiz_info.mip_count }, - .name = "HiZ View", - }; - self.hiz_view = ImageView::create(device, self.hiz, hiz_view_info).value(); - - hiz_attachment = self.hiz_view.acquire(device, "HiZ", vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, vuk::eNone); - hiz_attachment = vuk::clear_image(std::move(hiz_attachment), vuk::DepthZero); - } else { - hiz_attachment = - self.hiz_view.acquire(device, "HiZ", vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, vuk::eComputeSampled); - } + auto hiz_attachment = vuk::declare_ia( + "hiz", + { .usage = vuk::ImageUsageFlagBits::eStorage | vuk::ImageUsageFlagBits::eSampled, + .extent = hiz_extent, + .format = vuk::Format::eR32Sfloat, + .sample_count = vuk::SampleCountFlagBits::e1, + .level_count = static_cast(std::bit_width(glm::max(hiz_extent.width, hiz_extent.height))), + .layer_count = 1 } + ); + hiz_attachment = vuk::clear_image(std::move(hiz_attachment), vuk::DepthZero); const auto debugging = self.debug_lines; auto debug_drawer_buffer = vuk::Value{}; @@ -2067,16 +2043,6 @@ auto SceneRenderer::cleanup(this SceneRenderer &self) -> void { device.destroy(self.materials_buffer.id()); self.materials_buffer = {}; } - - if (self.hiz_view) { - device.destroy(self.hiz_view.id()); - self.hiz_view = {}; - } - - if (self.hiz) { - device.destroy(self.hiz.id()); - self.hiz = {}; - } } } // namespace lr diff --git a/Lorr/Engine/Scene/SceneRenderer.hh b/Lorr/Engine/Scene/SceneRenderer.hh index d7e0c3f4..233a0e20 100644 --- a/Lorr/Engine/Scene/SceneRenderer.hh +++ b/Lorr/Engine/Scene/SceneRenderer.hh @@ -72,9 +72,6 @@ struct SceneRenderer { vuk::Extent3D sky_cubemap_extent = { .width = 32, .height = 32, .depth = 1 }; vuk::Extent3D sky_aerial_perspective_lut_extent = { .width = 32, .height = 32, .depth = 32 }; - Image hiz = {}; - ImageView hiz_view = {}; - Image hilbert_noise_lut = {}; ImageView hilbert_noise_lut_view = {}; From 957463e3ca8bef597469461f84fe3f2b99d5bc31 Mon Sep 17 00:00:00 2001 From: exdal <63502313+exdal@users.noreply.github.com> Date: Sun, 21 Sep 2025 14:27:52 +0300 Subject: [PATCH 2/8] move environment sun to lights struct --- .../Resources/shaders/passes/pbr_apply.slang | 5 +- .../passes/sky_aerial_perspective.slang | 5 +- .../shaders/passes/sky_cubemap.slang | 12 +- .../Resources/shaders/passes/sky_final.slang | 16 ++- .../Resources/shaders/passes/sky_view.slang | 5 +- Lorr/Engine/Resources/shaders/scene.slang | 60 ++++---- Lorr/Engine/Resources/shaders/sky.slang | 17 ++- Lorr/Engine/Scene/ECSModule/CoreComponents.hh | 17 ++- Lorr/Engine/Scene/GPUScene.hh | 53 +++---- Lorr/Engine/Scene/Scene.cc | 132 ++++++++++++++++-- Lorr/Engine/Scene/SceneRenderer.cc | 39 ------ 11 files changed, 239 insertions(+), 122 deletions(-) diff --git a/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang b/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang index c58a8c84..6f0450b0 100644 --- a/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang +++ b/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang @@ -49,10 +49,11 @@ func fs_main(VertexOutput input) -> f32x4 { let NDC = f32x3(input.tex_coord * 2.0 - 1.0, depth); let world_position_h = mul(params.camera.inv_projection_view_mat, f32x4(NDC, 1.0)); let world_position = world_position_h.xyz / world_position_h.w; + let directional_light = params.environment.lights.directional_light; // PBR constants + let L = directional_light.direction; let V = normalize(params.camera.position - world_position); - let L = normalize(params.environment.sun_direction); // temp let N = normalize(mapped_normal); let R = reflect(-V, N); @@ -74,7 +75,7 @@ func fs_main(VertexOutput input) -> f32x4 { params.environment.atmos_planet_radius, f32x2(eye_altitude, sun_cos_theta)); f32x3 sun_transmittance = params.sky_transmittance_lut.SampleLevel(params.linear_clamp_sampler, transmittance_uv, 0.0).rgb; - sun_illuminance = sun_transmittance * params.environment.sun_intensity; + sun_illuminance = sun_transmittance * directional_light.intensity; } if ((params.environment.flags & EnvironmentFlags::HasAtmosphere) != 0u) { diff --git a/Lorr/Engine/Resources/shaders/passes/sky_aerial_perspective.slang b/Lorr/Engine/Resources/shaders/passes/sky_aerial_perspective.slang index 4f5ee6b7..9467c18c 100644 --- a/Lorr/Engine/Resources/shaders/passes/sky_aerial_perspective.slang +++ b/Lorr/Engine/Resources/shaders/passes/sky_aerial_perspective.slang @@ -69,11 +69,12 @@ func cs_main( t_max_max = max(0.0, t_max_max - length_to_atmosphere); } + let directional_light = params.environment.lights.directional_light; AtmosphereIntegrateInfo info = {}; info.eye_pos = eye_pos; info.eye_dir = world_dir; - info.sun_dir = params.environment.sun_direction; - info.sun_intensity = params.environment.sun_intensity; + info.sun_dir = directional_light.direction; + info.sun_intensity = directional_light.intensity; info.eval_planet_luminance = false; info.eval_multiscattering = true; info.step_count = max(1.0, (f32(thread_id.z) + 1.0) * 2.0); diff --git a/Lorr/Engine/Resources/shaders/passes/sky_cubemap.slang b/Lorr/Engine/Resources/shaders/passes/sky_cubemap.slang index 2d7565d2..c0ef4bac 100644 --- a/Lorr/Engine/Resources/shaders/passes/sky_cubemap.slang +++ b/Lorr/Engine/Resources/shaders/passes/sky_cubemap.slang @@ -96,6 +96,8 @@ func cs_main( eye_altitude += params.environment.atmos_planet_radius + PLANET_RADIUS_OFFSET; let eye_pos = f32x3(0.0, eye_altitude, 0.0); + let directional_light = params.environment.lights.directional_light; + // these values are hardcoded let sample_count = 128; let subgroup_size = 32; @@ -107,7 +109,15 @@ func cs_main( rand_seed((i * subgroup_size + sg_thread_id + seed * sample_count)); let input_dir = rand_hemi_dir(output_dir); let result = get_atmosphere_illuminance_along_ray( - eye_pos, input_dir, params.environment.sun_direction, params.environment, params.sampler, params.sky_view_lut); + eye_pos, + input_dir, + directional_light.direction, + directional_light.intensity, + params.environment.atmos_atmos_radius, + params.environment.atmos_planet_radius, + params.environment.sky_view_lut_size.xy, + params.sampler, + params.sky_view_lut); let cos_weighed_result = result * dot(output_dir, input_dir); accumulated_result += std::subgroup_inclusive_add(cos_weighed_result); diff --git a/Lorr/Engine/Resources/shaders/passes/sky_final.slang b/Lorr/Engine/Resources/shaders/passes/sky_final.slang index d41f853a..eee43006 100644 --- a/Lorr/Engine/Resources/shaders/passes/sky_final.slang +++ b/Lorr/Engine/Resources/shaders/passes/sky_final.slang @@ -53,6 +53,7 @@ func fs_main( f32x3 NDC = f32x3(input.tex_coord * 2.0 - 1.0, depth); f32x4 world_pos_h = mul(params.camera.inv_projection_view_mat, f32x4(NDC, 1.0)); f32x3 world_pos = world_pos_h.xyz / world_pos_h.w; + let directional_light = params.environment.lights.directional_light; f32x3 camera_pos = params.camera.position; if (depth != 0.0) { @@ -71,11 +72,20 @@ func fs_main( eye_altitude += params.environment.atmos_planet_radius + PLANET_RADIUS_OFFSET; let eye_pos = f32x3(0.0, eye_altitude, 0.0); - var sun_dir = (params.environment.sun_direction); + var sun_dir = directional_light.direction; var eye_dir = normalize(world_pos - params.camera.position); let planet_intersection = std::ray_sphere_intersect_nearest(eye_pos, eye_dir, params.environment.atmos_planet_radius); - var color = get_atmosphere_illuminance_along_ray(eye_pos, eye_dir, sun_dir, params.environment, params.sampler, params.sky_view_lut); + var color = get_atmosphere_illuminance_along_ray( + eye_pos, + eye_dir, + sun_dir, + directional_light.intensity, + params.environment.atmos_atmos_radius, + params.environment.atmos_planet_radius, + params.environment.sky_view_lut_size.xy, + params.sampler, + params.sky_view_lut); let sun_cos_theta = dot(sun_dir, up); let transmittance_uv = transmittance_params_to_lut_uv( @@ -85,7 +95,7 @@ func fs_main( let sun_transmittance = params.sky_transmittance_lut.SampleLevel(params.sampler, transmittance_uv, 0.0).rgb; if (planet_intersection == -1.0) { - color += draw_sun(eye_dir, params.environment.sun_direction, 1.0) * params.environment.sun_intensity * sun_transmittance; + color += draw_sun(eye_dir, sun_dir, 1.0) * directional_light.intensity * sun_transmittance; } return f32x4(color, 1.0); diff --git a/Lorr/Engine/Resources/shaders/passes/sky_view.slang b/Lorr/Engine/Resources/shaders/passes/sky_view.slang index 4ebaca9a..45f4504f 100644 --- a/Lorr/Engine/Resources/shaders/passes/sky_view.slang +++ b/Lorr/Engine/Resources/shaders/passes/sky_view.slang @@ -35,6 +35,7 @@ func cs_main( uv, eye_altitude); + let directional_light = params.environment.lights.directional_light; let view_zenith_angle = sky_params.x; let light_view_angle = sky_params.y; let eye_dir = f32x3( @@ -49,14 +50,14 @@ func cs_main( } let up_vec = f32x3(0.0, 1.0, 0.0); - let sun_zenith_cos_angle = dot(params.environment.sun_direction, up_vec); + let sun_zenith_cos_angle = dot(directional_light.direction, up_vec); let sun_dir = normalize(f32x3(std::safe_sqrt(1.0 - sun_zenith_cos_angle * sun_zenith_cos_angle), sun_zenith_cos_angle, 0.0)); AtmosphereIntegrateInfo info = {}; info.eye_pos = eye_pos; info.eye_dir = eye_dir; info.sun_dir = sun_dir; - info.sun_intensity = params.environment.sun_intensity; + info.sun_intensity = directional_light.intensity; info.step_count = 32.0; info.eval_multiscattering = true; info.eval_planet_luminance = false; diff --git a/Lorr/Engine/Resources/shaders/scene.slang b/Lorr/Engine/Resources/shaders/scene.slang index 894f57f1..bb302a69 100644 --- a/Lorr/Engine/Resources/shaders/scene.slang +++ b/Lorr/Engine/Resources/shaders/scene.slang @@ -32,6 +32,34 @@ public enum CullFlags : u32 { MicroTriangles, }; + + +#ifndef MAX_DIRECTIONAL_LIGHT_CASCADES +#define MAX_DIRECTIONAL_LIGHT_CASCADES 6 +#endif + +public struct DirectionalLight { + public struct Cascade { + public f32x4x4 projection_view_mat; + public f32 far_bound; + public f32 texel_size; + }; + + public Cascade cascades[MAX_DIRECTIONAL_LIGHT_CASCADES]; + public f32x3 base_ambient_color; + public f32 intensity; + public f32x3 direction; + public u32 cascade_count; + public u32 cascade_size; + public f32 cascades_overlap_proportion; + public f32 depth_bias; + public f32 normal_bias; +}; + +public struct Lights { + public DirectionalLight directional_light; +}; + public enum EnvironmentFlags : u32 { None = 0, HasSun = 1 << 0, @@ -43,8 +71,7 @@ public enum EnvironmentFlags : u32 { public struct Environment { public EnvironmentFlags flags; // Sun - public f32x3 sun_direction; - public f32 sun_intensity; + public Lights lights; // Atmosphere public f32x3 atmos_rayleigh_scatter; public f32 atmos_rayleigh_density; @@ -79,10 +106,12 @@ public struct Camera { public mat4 projection_view_mat; public mat4 inv_projection_view_mat; public f32x3 position; + public f32x2 resolution; public f32 near_clip; public f32 far_clip; - public f32x2 resolution; public f32 acceptable_lod_error; + public f32 fov_deg; + public f32 aspect_ratio; }; public struct Transform { @@ -305,31 +334,6 @@ public struct Mesh { public Bounds bounds = {}; }; -#ifndef MAX_DIRECTIONAL_LIGHT_CASCADES -#define MAX_DIRECTIONAL_LIGHT_CASCADES 6 -#endif - -public struct DirectionalLight { - public struct Cascade { - public f32x4 projection; - }; - - public Cascade cascades[MAX_DIRECTIONAL_LIGHT_CASCADES]; - public u32 cascade_count; - public f32x4 color; - public f32x3 direction; -}; - -#ifndef MAX_DIRECTIONAL_LIGHTS -#define MAX_DIRECTIONAL_LIGHTS 1 -#endif - -public struct Lights { - public u32 directional_light_count; - public u32 _unused; - public DirectionalLight directional_lights[MAX_DIRECTIONAL_LIGHTS]; -}; - public struct VBGTAO { public f32 thickness; public f32 depth_range_scale_factor; diff --git a/Lorr/Engine/Resources/shaders/sky.slang b/Lorr/Engine/Resources/shaders/sky.slang index 9505c1a8..6b38af99 100644 --- a/Lorr/Engine/Resources/shaders/sky.slang +++ b/Lorr/Engine/Resources/shaders/sky.slang @@ -287,7 +287,10 @@ public func get_atmosphere_illuminance_along_ray( f32x3 eye_pos, f32x3 eye_dir, f32x3 sun_dir, - in Environment environment, + f32 sun_intensity, + f32 atmos_radius, + f32 planet_radius, + i32x2 sky_view_lut_size, in Sampler sampler, in Image2D sky_view_image ) -> f32x3 { @@ -297,11 +300,11 @@ public func get_atmosphere_illuminance_along_ray( normalize(f32x3(sun_dir.x, 0.0, sun_dir.z)), normalize(f32x3(eye_dir.x, 0.0, eye_dir.z)), ), -1.0, 1.0)); - let planet_intersection = std::ray_sphere_intersect_nearest(eye_pos, eye_dir, environment.atmos_planet_radius); + let planet_intersection = std::ray_sphere_intersect_nearest(eye_pos, eye_dir, planet_radius); let uv = sky_view_params_to_lut_uv( - environment.atmos_atmos_radius, - environment.atmos_planet_radius, - environment.sky_view_lut_size.xy, + atmos_radius, + planet_radius, + sky_view_lut_size, planet_intersection != -1.0, height, view_zenith_cos_angle, @@ -309,5 +312,5 @@ public func get_atmosphere_illuminance_along_ray( let result = sky_view_image.SampleLevel(sampler, uv, 0.0); let atmos_luminance = result.rgb * result.a; - return atmos_luminance * environment.sun_intensity; -} \ No newline at end of file + return atmos_luminance * sun_intensity; +} diff --git a/Lorr/Engine/Scene/ECSModule/CoreComponents.hh b/Lorr/Engine/Scene/ECSModule/CoreComponents.hh index 0d83bb0b..34cb767a 100644 --- a/Lorr/Engine/Scene/ECSModule/CoreComponents.hh +++ b/Lorr/Engine/Scene/ECSModule/CoreComponents.hh @@ -43,9 +43,6 @@ ECS_COMPONENT_BEGIN(RenderingMesh) ECS_COMPONENT_END(); ECS_COMPONENT_BEGIN(Environment) - ECS_COMPONENT_MEMBER(sun, bool, true) - ECS_COMPONENT_MEMBER(sun_direction, glm::vec2, { 90.0, 45.0 }) - ECS_COMPONENT_MEMBER(sun_intensity, f32, 10.0) ECS_COMPONENT_MEMBER(atmos, bool, true) ECS_COMPONENT_MEMBER(atmos_rayleigh_scattering, glm::vec3, { 5.802f, 13.558f, 33.100f }) ECS_COMPONENT_MEMBER(atmos_rayleigh_density, f32, 8.0) @@ -66,6 +63,20 @@ ECS_COMPONENT_BEGIN(Environment) ECS_COMPONENT_MEMBER(eye_k, f32, 12.5f) ECS_COMPONENT_END(); +ECS_COMPONENT_BEGIN(DirectionalLight) + ECS_COMPONENT_MEMBER(direction, glm::vec2, {90.0f, 45.0f}) + ECS_COMPONENT_MEMBER(base_ambient_color, glm::vec3, {0.4f, 0.4f, 0.4f}) + ECS_COMPONENT_MEMBER(intensity, f32, 10.0f) + ECS_COMPONENT_MEMBER(shadow_map_resolution, u32, 2048) + ECS_COMPONENT_MEMBER(cascade_count, u32, 4) + ECS_COMPONENT_MEMBER(first_cascade_far_bound, f32, 30.0f) + ECS_COMPONENT_MEMBER(maximum_shadow_distance, f32, 150.0f) + ECS_COMPONENT_MEMBER(minimum_shadow_distance, f32, 0.01f) + ECS_COMPONENT_MEMBER(cascade_overlap_propotion, f32, 0.2f) + ECS_COMPONENT_MEMBER(depth_bias, f32, 0.002f) + ECS_COMPONENT_MEMBER(normal_bias, f32, 1.8f) +ECS_COMPONENT_END(); + ECS_COMPONENT_BEGIN(VBGTAO) ECS_COMPONENT_MEMBER(thickness, f32, 0.25f) ECS_COMPONENT_MEMBER(depth_range_scale_factor, f32, 0.75f) diff --git a/Lorr/Engine/Scene/GPUScene.hh b/Lorr/Engine/Scene/GPUScene.hh index 1878921c..3b8d331d 100644 --- a/Lorr/Engine/Scene/GPUScene.hh +++ b/Lorr/Engine/Scene/GPUScene.hh @@ -65,6 +65,29 @@ enum class CullFlags : u32 { All = ~0_u32, }; +struct DirectionalLight { + constexpr static auto MAX_DIRECTIONAL_LIGHT_CASCADES = 6_sz; + struct Cascade { + alignas(4) glm::mat4 projection_view_mat = {}; + alignas(4) f32 far_bound = {}; + alignas(4) f32 texel_size = {}; + }; + + alignas(4) Cascade cascades[MAX_DIRECTIONAL_LIGHT_CASCADES] = {}; + alignas(4) glm::vec3 base_ambient_color = {}; + alignas(4) f32 intensity = {}; + alignas(4) glm::vec3 direction = {}; + alignas(4) u32 cascade_count = {}; + alignas(4) u32 cascade_size = {}; + alignas(4) f32 cascades_overlap_proportion = {}; + alignas(4) f32 depth_bias = {}; + alignas(4) f32 normal_bias = {}; +}; + +struct Lights { + alignas(4) DirectionalLight directional_light = {}; +}; + enum EnvironmentFlags : u32 { None = 0, HasSun = 1 << 0, @@ -75,8 +98,7 @@ enum EnvironmentFlags : u32 { struct Environment { alignas(4) u32 flags = EnvironmentFlags::None; // Sun - alignas(4) glm::vec3 sun_direction = {}; - alignas(4) f32 sun_intensity = 10.0f; + alignas(4) Lights lights = {}; // Atmosphere alignas(4) glm::vec3 atmos_rayleigh_scatter = { 0.005802f, 0.014338f, 0.032800f }; alignas(4) f32 atmos_rayleigh_density = 8.0f; @@ -115,10 +137,12 @@ struct Camera { alignas(4) glm::mat4 projection_view_mat = {}; alignas(4) glm::mat4 inv_projection_view_mat = {}; alignas(4) glm::vec3 position = {}; + alignas(4) glm::vec2 resolution = {}; alignas(4) f32 near_clip = 0.01f; alignas(4) f32 far_clip = 1000.0f; - alignas(4) glm::vec2 resolution = {}; alignas(4) f32 acceptable_lod_error = 0.0f; + alignas(4) f32 fov_deg = 65.0f; + alignas(4) f32 aspect_ratio = 1.777f; }; enum class TransformID : u64 { Invalid = ~0_u64 }; @@ -225,29 +249,6 @@ struct HistogramLuminance { alignas(4) f32 exposure = 0.0f; }; -struct DirectionalLight { - constexpr static auto MAX_DIRECTIONAL_LIGHT_CASCADES = 6_sz; - struct Cascade { - alignas(4) glm::vec4 projection = {}; - }; - - alignas(4) Cascade cascades[MAX_DIRECTIONAL_LIGHT_CASCADES] = {}; - alignas(4) u32 cascade_count = 0; - alignas(4) glm::vec4 color = {}; - alignas(4) glm::vec3 direction = {}; -}; - -struct Lights { - // If we increase this, realistically we would need to support - // multiple lights for sky atmosphere aswell, which would increase - // raymach counts (per sun count), and this means less performance - constexpr static auto MAX_DIRECTIONAL_LIGHTS = 1_u32; - - alignas(4) u32 directional_light_count = 0; - alignas(4) u32 _unused = 0; // for future light types - alignas(8) DirectionalLight directional_lights[MAX_DIRECTIONAL_LIGHTS] = {}; -}; - struct VBGTAO { alignas(4) f32 thickness = 0.25f; alignas(4) f32 depth_range_scale_factor = 0.75f; diff --git a/Lorr/Engine/Scene/Scene.cc b/Lorr/Engine/Scene/Scene.cc index fa63863c..a2e40a11 100644 --- a/Lorr/Engine/Scene/Scene.cc +++ b/Lorr/Engine/Scene/Scene.cc @@ -41,6 +41,104 @@ bool json_to_quat(simdjson::ondemand::value &o, glm::quat &quat) { return true; } +auto get_frustum_corners(f32 fov, f32 aspect_ratio, f32 z_near, f32 z_far) -> std::array { + auto tan_half_fov = glm::tan(glm::radians(fov) / 2.0f); + auto a = glm::abs(z_near) * tan_half_fov; + auto b = glm::abs(z_far) * tan_half_fov; + + return std::array{ + glm::vec3(a * aspect_ratio, -a, z_near), // bottom right + glm::vec3(a * aspect_ratio, a, z_near), // top right + glm::vec3(-a * aspect_ratio, a, z_near), // top left + glm::vec3(-a * aspect_ratio, -a, z_near), // bottom left + glm::vec3(b * aspect_ratio, -b, z_far), // bottom right + glm::vec3(b * aspect_ratio, b, z_far), // top right + glm::vec3(-b * aspect_ratio, b, z_far), // top left + glm::vec3(-b * aspect_ratio, -b, z_far), // bottom left + }; +} + +auto calculate_cascade_bounds(usize cascade_count, f32 nearest_bound, f32 maximum_shadow_distance) + -> std::array { + if (cascade_count == 1) { + return { maximum_shadow_distance }; + } + auto base = glm::pow(maximum_shadow_distance / nearest_bound, 1.0f / static_cast(cascade_count - 1)); + + auto result = std::array(); + for (u32 i = 0; i < cascade_count; i++) { + result[i] = nearest_bound * glm::pow(base, static_cast(i)); + } + + return result; +} + +auto calculate_cascaded_shadow_matrices(GPU::DirectionalLight &light, const ECS::DirectionalLight &light_comp, const GPU::Camera &camera) -> void { + ZoneScoped; + + auto overlap_factor = 1.0f - light_comp.cascade_overlap_propotion; + auto far_bounds = calculate_cascade_bounds(light.cascade_count, light_comp.first_cascade_far_bound, light_comp.maximum_shadow_distance); + auto near_bounds = std::array(); + near_bounds[0] = light_comp.minimum_shadow_distance; + for (u32 i = 1; i < light.cascade_count; i++) { + near_bounds[i] = overlap_factor * far_bounds[i - 1]; + } + + auto forward = glm::normalize(light.direction); + auto up = (glm::abs(glm::dot(forward, glm::vec3(0, 1, 0))) > 0.99f) ? glm::vec3(0, 0, 1) : glm::vec3(0, 1, 0); + auto right = glm::normalize(glm::cross(up, forward)); + up = glm::normalize(glm::cross(forward, right)); + + auto world_from_light = glm::mat4(glm::vec4(right, 0.0f), glm::vec4(up, 0.0f), glm::vec4(forward, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); + auto light_to_world_inverse = glm::transpose(world_from_light); + auto camera_to_world = camera.inv_view_mat; + + for (u32 cascade_index = 0; cascade_index < light.cascade_count; ++cascade_index) { + auto &cascade = light.cascades[cascade_index]; + auto split_near = near_bounds[cascade_index]; + auto split_far = far_bounds[cascade_index]; + auto corners = get_frustum_corners(camera.fov_deg, camera.aspect_ratio, -split_near, -split_far); + + auto min = glm::vec3(std::numeric_limits::max()); + auto max = glm::vec3(std::numeric_limits::lowest()); + for (const auto &corner : corners) { + auto world_corner = camera_to_world * glm::vec4(corner, 1.0f); + auto light_view_corner = glm::vec3(light_to_world_inverse * world_corner); + min = glm::min(min, light_view_corner); + max = glm::max(max, light_view_corner); + } + + auto body_diagonal = glm::length2(corners[0] - corners[6]); + auto far_plane_diagonal = glm::length2(corners[4] - corners[6]); + auto cascade_diameter = glm::ceil(glm::sqrt(glm::max(body_diagonal, far_plane_diagonal))); + f32 cascade_texel_size = cascade_diameter / static_cast(light.cascade_size); + + glm::vec3 center = glm::vec3( + glm::floor((min.x + max.x) * 0.5f / cascade_texel_size) * cascade_texel_size, + glm::floor((min.y + max.y) * 0.5f / cascade_texel_size) * cascade_texel_size, + max.z + ); + + auto cascade_from_world = + glm::mat4(light_to_world_inverse[0], light_to_world_inverse[1], light_to_world_inverse[2], glm::vec4(-center, 1.0f)); + + auto z_extension = camera.far_clip * 0.5f; + auto extended_min_z = min.z - z_extension; + auto extended_max_z = max.z + z_extension; + auto r = 1.0f / (extended_max_z - extended_min_z); + auto clip_from_cascade = glm::mat4( + glm::vec4(2.0 / cascade_diameter, 0.0, 0.0, 0.0), + glm::vec4(0.0, 2.0 / cascade_diameter, 0.0, 0.0), + glm::vec4(0.0, 0.0, r, 0.0), + glm::vec4(0.0, 0.0, -extended_min_z * r, 1.0) + ); + + cascade.projection_view_mat = clip_from_cascade * cascade_from_world; + cascade.far_bound = split_far; + cascade.texel_size = cascade_texel_size; + } +} + auto Scene::init(this Scene &self, const std::string &name) -> bool { ZoneScoped; @@ -584,6 +682,9 @@ auto Scene::prepare_frame(this Scene &self, SceneRenderer &renderer, u32 image_c auto rendering_meshes_query = self.get_world() .query_builder() .build(); + auto directional_light_query = self.get_world() + .query_builder() + .build(); auto environment_query = self.get_world() .query_builder() .build(); @@ -615,26 +716,20 @@ auto Scene::prepare_frame(this Scene &self, SceneRenderer &renderer, u32 image_c camera_data.projection_view_mat = camera_data.projection_mat * camera_data.view_mat; camera_data.inv_projection_view_mat = glm::inverse(camera_data.projection_view_mat); camera_data.position = t.position; + camera_data.resolution = c.resolution; camera_data.near_clip = c.near_clip; camera_data.far_clip = c.far_clip; - camera_data.resolution = c.resolution; camera_data.acceptable_lod_error = c.acceptable_lod_error; + camera_data.fov_deg = c.fov; + camera_data.aspect_ratio = aspect_ratio; }); } GPU::Environment environment = {}; environment_query.each([&environment](flecs::entity, ECS::Environment &environment_comp) { - environment.flags |= environment_comp.sun ? GPU::EnvironmentFlags::HasSun : 0; environment.flags |= environment_comp.atmos ? GPU::EnvironmentFlags::HasAtmosphere : 0; environment.flags |= environment_comp.eye_adaptation ? GPU::EnvironmentFlags::HasEyeAdaptation : 0; - auto light_dir_rad = glm::radians(environment_comp.sun_direction); - environment.sun_direction = { - glm::cos(light_dir_rad.x) * glm::sin(light_dir_rad.y), - glm::sin(light_dir_rad.x) * glm::sin(light_dir_rad.y), - glm::cos(light_dir_rad.y), - }; - environment.sun_intensity = environment_comp.sun_intensity; environment.atmos_rayleigh_scatter = environment_comp.atmos_rayleigh_scattering * 1e-3f; environment.atmos_rayleigh_density = environment_comp.atmos_rayleigh_density; environment.atmos_mie_scatter = environment_comp.atmos_mie_scattering * 1e-3f; @@ -652,6 +747,25 @@ auto Scene::prepare_frame(this Scene &self, SceneRenderer &renderer, u32 image_c environment.eye_ISO_K = environment_comp.eye_iso / environment_comp.eye_k; }); + directional_light_query.each([&environment](flecs::entity, const ECS::DirectionalLight &directional_light_comp) { + auto &directional_light = environment.lights.directional_light; + environment.flags |= GPU::EnvironmentFlags::HasSun; + + auto light_dir_rad = glm::radians(directional_light_comp.direction); + directional_light.direction = { + glm::cos(light_dir_rad.x) * glm::sin(light_dir_rad.y), + glm::sin(light_dir_rad.x) * glm::sin(light_dir_rad.y), + glm::cos(light_dir_rad.y), + }; + directional_light.base_ambient_color = directional_light_comp.base_ambient_color; + directional_light.intensity = directional_light_comp.intensity; + directional_light.cascade_count = directional_light_comp.cascade_count; + directional_light.cascade_size = directional_light_comp.shadow_map_resolution; + directional_light.cascades_overlap_proportion = directional_light_comp.cascade_overlap_propotion; + directional_light.depth_bias = directional_light_comp.depth_bias; + directional_light.normal_bias = directional_light_comp.normal_bias; + }); + auto regenerate_sky = false; regenerate_sky |= self.last_environment.atmos_rayleigh_scatter != environment.atmos_rayleigh_scatter; regenerate_sky |= self.last_environment.atmos_rayleigh_density != environment.atmos_rayleigh_density; diff --git a/Lorr/Engine/Scene/SceneRenderer.cc b/Lorr/Engine/Scene/SceneRenderer.cc index a71bdc8b..c60aac8d 100644 --- a/Lorr/Engine/Scene/SceneRenderer.cc +++ b/Lorr/Engine/Scene/SceneRenderer.cc @@ -47,7 +47,6 @@ auto SceneRenderer::init(this SceneRenderer &self) -> bool { { "CULLING_TRIANGLE_COUNT", std::to_string(Model::MAX_MESHLET_PRIMITIVES) }, { "MESH_MAX_LODS", std::to_string(GPU::Mesh::MAX_LODS) }, { "MAX_DIRECTIONAL_LIGHT_CASCADES", std::to_string(GPU::DirectionalLight::MAX_DIRECTIONAL_LIGHT_CASCADES) }, - { "MAX_DIRECTIONAL_LIGHTS", std::to_string(GPU::Lights::MAX_DIRECTIONAL_LIGHTS) }, { "HISTOGRAM_THREADS_X", std::to_string(GPU::HISTOGRAM_THREADS_X) }, { "HISTOGRAM_THREADS_Y", std::to_string(GPU::HISTOGRAM_THREADS_Y) }, }, @@ -432,45 +431,7 @@ auto SceneRenderer::prepare_frame(this SceneRenderer &self, FramePrepareInfo &in info.environment.multiscattering_lut_size = self.sky_multiscatter_lut_view.extent(); info.environment.aerial_perspective_lut_size = self.sky_aerial_perspective_lut_extent; prepared_frame.environment_buffer = transfer_man.scratch_buffer(info.environment); - - // glm::vec3 corners[8] = {}; - // glm::vec3 ndc_corners[8] = { - // glm::vec3(-1.0f, 1.0f, 0.0f), glm::vec3(1.0f, 1.0f, 0.0f), glm::vec3(1.0f, -1.0f, 0.0f), glm::vec3(-1.0f, -1.0f, 0.0f), - // glm::vec3(-1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, -1.0f, 1.0f), glm::vec3(-1.0f, -1.0f, 1.0f), - // }; - // - // for (int i = 0; i < 8; ++i) { - // auto world_corner = info.camera.inv_projection_view_mat * glm::vec4(ndc_corners[i], 1.0f); - // corners[i] = glm::vec3(world_corner) / world_corner.w; - // } - // - // auto center = glm::vec3(0.0f); - // for (const auto &c : corners) { - // center += c; - // } - // center /= static_cast(ls::count_of(corners)); - // - // auto shadow_map_size = 512.0f; - // auto light_pos = center - info.environment.sun_direction * (shadow_map_size * 0.5f); - // auto light_target = center; - // auto up = glm::vec3(0, 1, 0); - // if (1.0f - glm::abs(glm::dot(info.environment.sun_direction, up)) < 1e-4f) { - // up = glm::vec3(0, 0, 1); - // } - // - // auto view_mat = glm::lookAt(light_pos, glm::vec3(0), up); - // auto projection_mat = glm::orthoRH_ZO( - // -shadow_map_size * 0.5f, - // shadow_map_size * 0.5f, - // -shadow_map_size * 0.5f, - // shadow_map_size * 0.5f, - // -shadow_map_size * 0.5f, - // shadow_map_size * 0.5f - // ); - // projection_mat[1][1] *= -1.0; - prepared_frame.camera_buffer = transfer_man.scratch_buffer(info.camera); - prepared_frame.mesh_instance_count = info.mesh_instance_count; prepared_frame.max_meshlet_instance_count = info.max_meshlet_instance_count; prepared_frame.environment_flags = static_cast(info.environment.flags); From 40b8afd0b1b3767e355e232259e3d3c309ff4fbd Mon Sep 17 00:00:00 2001 From: exdal <63502313+exdal@users.noreply.github.com> Date: Sun, 21 Sep 2025 20:10:03 +0300 Subject: [PATCH 3/8] add cascaded shadow maps --- .../shaders/passes/cull_meshes.slang | 55 +- .../shaders/passes/cull_meshlets.slang | 76 ++- .../shaders/passes/cull_triangles.slang | 32 +- .../passes/generate_cull_commands.slang | 2 +- .../Resources/shaders/passes/pbr_apply.slang | 11 +- .../passes/shadowmap_cull_meshlets.slang | 47 ++ .../shaders/passes/shadowmap_draw.slang | 44 ++ .../shaders/passes/visbuffer_encode.slang | 8 +- Lorr/Engine/Resources/shaders/shadow.slang | 116 ++++ Lorr/Engine/Scene/Scene.cc | 10 +- Lorr/Engine/Scene/SceneRenderer.cc | 564 ++++++++++++++---- Lorr/Engine/Scene/SceneRenderer.hh | 4 +- 12 files changed, 747 insertions(+), 222 deletions(-) create mode 100644 Lorr/Engine/Resources/shaders/passes/shadowmap_cull_meshlets.slang create mode 100644 Lorr/Engine/Resources/shaders/passes/shadowmap_draw.slang create mode 100644 Lorr/Engine/Resources/shaders/shadow.slang diff --git a/Lorr/Engine/Resources/shaders/passes/cull_meshes.slang b/Lorr/Engine/Resources/shaders/passes/cull_meshes.slang index 5a3a9be0..e8e461c2 100644 --- a/Lorr/Engine/Resources/shaders/passes/cull_meshes.slang +++ b/Lorr/Engine/Resources/shaders/passes/cull_meshes.slang @@ -4,26 +4,27 @@ import scene; import cull; import debug_drawer; -[[vk::binding(0)]] ConstantBuffer camera; -[[vk::binding(1)]] StructuredBuffer meshes; -[[vk::binding(2)]] StructuredBuffer transforms; -[[vk::binding(3)]] Image2D hiz_image; -[[vk::binding(4)]] Sampler hiz_sampler; -[[vk::binding(5)]] RWStructuredBuffer mesh_instances; -[[vk::binding(6)]] RWStructuredBuffer meshlet_instances; -[[vk::binding(7)]] RWStructuredBuffer visible_meshlet_instances_count; -[[vk::binding(8)]] RWStructuredBuffer debug_drawer; +[[vk::binding(0)]] StructuredBuffer meshes; +[[vk::binding(1)]] StructuredBuffer transforms; +[[vk::binding(2)]] RWStructuredBuffer mesh_instances; +[[vk::binding(3)]] RWStructuredBuffer meshlet_instances; +[[vk::binding(4)]] RWStructuredBuffer visible_meshlet_instances_count; +[[vk::binding(5)]] RWStructuredBuffer debug_drawer; -#ifndef CULLING_MESHES_COUNT -#define CULLING_MESHES_COUNT 64 +#ifndef CULLING_MESH_COUNT +#define CULLING_MESH_COUNT 64 #endif [[shader("compute")]] -[[numthreads(CULLING_MESHES_COUNT, 1, 1)]] +[[numthreads(CULLING_MESH_COUNT, 1, 1)]] func cs_main( uint3 thread_id : SV_DispatchThreadID, uniform u32 mesh_instances_count, - uniform CullFlags cull_flags + uniform CullFlags cull_flags, + uniform f32x4x4 frustum_projection_view, + uniform f32x3 observer_position, + uniform f32 observer_max_resolution, + uniform f32 observer_acceptable_lod_error ) -> void { let mesh_instance_index = thread_id.x; if (mesh_instance_index >= mesh_instances_count) { @@ -33,7 +34,7 @@ func cs_main( let mesh_instance = &mesh_instances[mesh_instance_index]; let mesh = meshes[mesh_instance.mesh_index]; let transform = transforms[mesh_instance.transform_index]; - let mvp = mul(camera.projection_view_mat, transform.world); + let mvp = mul(frustum_projection_view, transform.world); let cull_frustum = (cull_flags & CullFlags::MeshFrustum) != 0; if (cull_frustum && !test_frustum(mvp, mesh.bounds.aabb_center, mesh.bounds.aabb_extent)) { @@ -45,33 +46,39 @@ func cs_main( // Credits: // - https://github.com/Sunset-Flock/Timberdoodle/blob/786f141e261dff4756e7f1a67dd7f7a5e1277956/src/scene/mesh_lod.hpp#L45 let aabb_center = mul(transform.world, f32x4(mesh.bounds.aabb_center, 1.0)).xyz; - let aabb_extent_x = length(transform.world[0]) * mesh.bounds.aabb_extent.x; - let aabb_extent_y = length(transform.world[1]) * mesh.bounds.aabb_extent.y; - let aabb_extent_z = length(transform.world[2]) * mesh.bounds.aabb_extent.z; - let aabb_rough_extent = max(max(aabb_extent_x, aabb_extent_y), aabb_extent_z); - let aabb_rough_camera_distance = max(length(aabb_center - camera.position) - 0.5 * aabb_rough_extent, 0.0); + let aabb_extent = mul(transform.world, f32x4(mesh.bounds.aabb_extent, 0.0)).xyz; + let aabb_rough_extent = max(aabb_extent.x, max(aabb_extent.y, aabb_extent.z)); + let aabb_rough_camera_distance = max(length(aabb_center - observer_position) - 0.5 * aabb_rough_extent, 0.0); - // Avoiding the atan here - let rough_resolution = max(camera.resolution.x, camera.resolution.y); let fov90_distance_to_screen_ratio = 2.0f; - let pixel_size_at_1m = fov90_distance_to_screen_ratio / rough_resolution; + let pixel_size_at_1m = fov90_distance_to_screen_ratio / observer_max_resolution; let aabb_size_at_1m = (aabb_rough_extent / aabb_rough_camera_distance); let rough_aabb_pixel_size = aabb_size_at_1m / pixel_size_at_1m; for (var i = 1; i < mesh.lod_count; i++) { let mesh_lod = mesh.lods[i]; let rough_pixel_error = rough_aabb_pixel_size * mesh_lod.error; - if (rough_pixel_error < camera.acceptable_lod_error) { + if (rough_pixel_error < observer_acceptable_lod_error) { lod_index = i; } else { break; } } #endif + +#ifdef DEBUG_DRAW + var debug_aabb = DebugAABB(); + debug_aabb.position = aabb_center; + debug_aabb.size = aabb_extent; + debug_aabb.color = f32x3(0.0, 0.0, 1.0); + debug_aabb.coord = DebugDrawCoord::World; + debug_draw_aabb(debug_drawer[0], debug_aabb); +#endif + mesh_instance.lod_index = lod_index; let mesh_lod = mesh.lods[lod_index]; let meshlet_count = mesh_lod.meshlet_count; - var base_meshlet_instance_offset = __atomic_add(visible_meshlet_instances_count[2], meshlet_count, MemoryOrder::Relaxed); + var base_meshlet_instance_offset = __atomic_add(visible_meshlet_instances_count[0], meshlet_count, MemoryOrder::Relaxed); for (u32 i = 0; i < meshlet_count; i++) { let offset = base_meshlet_instance_offset + i; meshlet_instances[offset].mesh_instance_index = mesh_instance_index; diff --git a/Lorr/Engine/Resources/shaders/passes/cull_meshlets.slang b/Lorr/Engine/Resources/shaders/passes/cull_meshlets.slang index 873f2ac2..c49f4fa6 100644 --- a/Lorr/Engine/Resources/shaders/passes/cull_meshlets.slang +++ b/Lorr/Engine/Resources/shaders/passes/cull_meshlets.slang @@ -2,23 +2,20 @@ import std; import gpu; import scene; import cull; -import debug_drawer; - -#include [[vk::constant_id(0)]] const bool LATE = false; -[[vk::binding(0)]] ConstantBuffer camera; -[[vk::binding(1)]] StructuredBuffer meshlet_instances; -[[vk::binding(2)]] StructuredBuffer mesh_instances; -[[vk::binding(3)]] StructuredBuffer meshes; -[[vk::binding(4)]] StructuredBuffer transforms; -[[vk::binding(5)]] Image2D hiz_image; -[[vk::binding(6)]] Sampler hiz_sampler; -[[vk::binding(7)]] RWStructuredBuffer visible_meshlet_instances_count; -[[vk::binding(8)]] RWStructuredBuffer visible_meshlet_instances_indices; -[[vk::binding(9)]] RWStructuredBuffer meshlet_instance_visibility_mask; -[[vk::binding(10)]] RWStructuredBuffer cull_triangles_cmd; -[[vk::binding(11)]] RWStructuredBuffer debug_drawer; +[[vk::binding(0)]] StructuredBuffer meshlet_instances; +[[vk::binding(1)]] StructuredBuffer mesh_instances; +[[vk::binding(2)]] StructuredBuffer meshes; +[[vk::binding(3)]] StructuredBuffer transforms; +[[vk::binding(4)]] Image2D hiz_image; +[[vk::binding(5)]] Sampler hiz_sampler; +[[vk::binding(6)]] StructuredBuffer visible_meshlet_instances_count; +[[vk::binding(7)]] RWStructuredBuffer early_visible_meshlet_instances_count; +[[vk::binding(8)]] RWStructuredBuffer late_visible_meshlet_instances_count; +[[vk::binding(9)]] RWStructuredBuffer visible_meshlet_instances_indices; +[[vk::binding(10)]] RWStructuredBuffer meshlet_instance_visibility_mask; +[[vk::binding(11)]] RWStructuredBuffer cull_triangles_cmd; #ifndef CULLING_MESHLET_COUNT #define CULLING_MESHLET_COUNT 64 @@ -29,9 +26,11 @@ import debug_drawer; func cs_main( uint group_thread_id : SV_GroupThreadID, uint global_thread_id : SV_DispatchThreadID, - uniform CullFlags cull_flags + uniform CullFlags cull_flags, + uniform f32 near_clip, + uniform f32x4x4 projection_view, ) -> void { - let meshlet_instance_count = visible_meshlet_instances_count[2]; + let meshlet_instance_count = visible_meshlet_instances_count[0]; let meshlet_instance_index = global_thread_id; if (meshlet_instance_index >= meshlet_instance_count) { return; @@ -40,7 +39,7 @@ func cs_main( let meshlet_instance = meshlet_instances[meshlet_instance_index]; let mesh_instance = mesh_instances[meshlet_instance.mesh_instance_index]; let transform = transforms[mesh_instance.transform_index]; - let mvp = mul(camera.projection_view_mat, transform.world); + let mvp = mul(projection_view, transform.world); let mesh = meshes[mesh_instance.mesh_index]; let mesh_lod = mesh.lods[mesh_instance.lod_index]; @@ -49,11 +48,16 @@ func cs_main( let cull_frustum = (cull_flags & CullFlags::MeshletFrustum) != 0; let cull_occlusion = (cull_flags & CullFlags::MeshletOcclusion) != 0; - let meshlet_instance_visibility_index = mesh_instance.meshlet_instance_visibility_offset + meshlet_instance.meshlet_index; - let mask_index = meshlet_instance_visibility_index / 32; - let bit_index = meshlet_instance_visibility_index - mask_index * 32; - let visibility_bit = 1 << bit_index; - let was_visible = (meshlet_instance_visibility_mask[mask_index] & visibility_bit) != 0; + var visibility_mask_index = 0; + var visibility_bit = 0; + var was_visible = false; + if (cull_occlusion) { + let meshlet_instance_visibility_index = mesh_instance.meshlet_instance_visibility_offset + meshlet_instance.meshlet_index; + visibility_mask_index = meshlet_instance_visibility_index / 32; + let bit_index = meshlet_instance_visibility_index - visibility_mask_index * 32; + visibility_bit = 1 << bit_index; + was_visible = (meshlet_instance_visibility_mask[visibility_mask_index] & visibility_bit) != 0; + } var visible = LATE ? true : was_visible; if (visible && cull_frustum) { @@ -61,30 +65,18 @@ func cs_main( } if (LATE && visible && cull_occlusion) { - if (let screen_aabb = project_aabb(mvp, camera.near_clip, bounds.aabb_center, bounds.aabb_extent)) { + if (let screen_aabb = project_aabb(mvp, near_clip, bounds.aabb_center, bounds.aabb_extent)) { visible = !test_occlusion(screen_aabb, hiz_image, hiz_sampler); -#ifdef DEBUG_DRAW - if (visible) { - let ndc_aabb_max = screen_aabb.max.xy * 2.0 - 1.0; - let ndc_aabb_min = screen_aabb.min.xy * 2.0 - 1.0; - var debug_rect = DebugRect(); - debug_rect.offset = f32x3((ndc_aabb_max + ndc_aabb_min) * 0.5, screen_aabb.max.z); - debug_rect.extent = ndc_aabb_max - ndc_aabb_min; - debug_rect.color = f32x3(1.0, 0.0, 0.0); - debug_rect.coord = DebugDrawCoord::NDC; - debug_draw_rect(debug_drawer[0], debug_rect); - } -#endif } } if (visible && (!LATE || !was_visible)) { var index = 0; if (!LATE) { - index = __atomic_add(visible_meshlet_instances_count[0], 1, MemoryOrder::Relaxed); + index = __atomic_add(early_visible_meshlet_instances_count[0], 1, MemoryOrder::Relaxed); } else { - let early_count = visible_meshlet_instances_count[0]; - let late_offset = __atomic_add(visible_meshlet_instances_count[1], 1, MemoryOrder::Relaxed); + let early_count = early_visible_meshlet_instances_count[0]; + let late_offset = __atomic_add(late_visible_meshlet_instances_count[0], 1, MemoryOrder::Relaxed); index = early_count + late_offset; } @@ -93,11 +85,11 @@ func cs_main( __atomic_add(cull_triangles_cmd[0].x, 1, MemoryOrder::Relaxed); } - if (LATE) { + if (LATE && cull_occlusion) { if (visible) { - __atomic_or(meshlet_instance_visibility_mask[mask_index], visibility_bit, MemoryOrder::Relaxed); + __atomic_or(meshlet_instance_visibility_mask[visibility_mask_index], visibility_bit, MemoryOrder::Relaxed); } else { - __atomic_and(meshlet_instance_visibility_mask[mask_index], ~visibility_bit, MemoryOrder::Relaxed); + __atomic_and(meshlet_instance_visibility_mask[visibility_mask_index], ~visibility_bit, MemoryOrder::Relaxed); } } } diff --git a/Lorr/Engine/Resources/shaders/passes/cull_triangles.slang b/Lorr/Engine/Resources/shaders/passes/cull_triangles.slang index 4d885696..f9582f86 100644 --- a/Lorr/Engine/Resources/shaders/passes/cull_triangles.slang +++ b/Lorr/Engine/Resources/shaders/passes/cull_triangles.slang @@ -1,5 +1,3 @@ -module cull_triangles; - import std; import gpu; import scene; @@ -7,15 +5,14 @@ import scene; import passes.visbuffer; [[vk::constant_id(0)]] const bool LATE = false; -[[vk::binding(0)]] ConstantBuffer camera; -[[vk::binding(1)]] StructuredBuffer visible_meshlet_instances_count; -[[vk::binding(2)]] StructuredBuffer visible_meshlet_instances_indices; -[[vk::binding(3)]] StructuredBuffer meshlet_instances; -[[vk::binding(4)]] StructuredBuffer mesh_instances; -[[vk::binding(5)]] StructuredBuffer meshes; -[[vk::binding(6)]] StructuredBuffer transforms; -[[vk::binding(7)]] RWStructuredBuffer draw_cmd; -[[vk::binding(8)]] RWStructuredBuffer reordered_indices; +[[vk::binding(0)]] StructuredBuffer early_visible_meshlet_instances_count; +[[vk::binding(1)]] StructuredBuffer visible_meshlet_instances_indices; +[[vk::binding(2)]] StructuredBuffer meshlet_instances; +[[vk::binding(3)]] StructuredBuffer mesh_instances; +[[vk::binding(4)]] StructuredBuffer meshes; +[[vk::binding(5)]] StructuredBuffer transforms; +[[vk::binding(6)]] RWStructuredBuffer draw_cmd; +[[vk::binding(7)]] RWStructuredBuffer reordered_indices; groupshared u32 base_index_shared; groupshared u32 triangles_passed_shared; @@ -111,11 +108,13 @@ func test_triangle(in f32x3x3 positions, in f32x2 resolution, CullFlags cull_fla func cs_main( uint3 group_id : SV_GroupID, uint3 group_thread_id : SV_GroupThreadID, - uniform CullFlags cull_flags + uniform CullFlags cull_flags, + uniform f32x2 resolution, + uniform f32x4x4 projection_view ) -> void { var visible_meshlet_instance_index = group_id.x; if (LATE) { - visible_meshlet_instance_index += visible_meshlet_instances_count[0]; + visible_meshlet_instance_index += early_visible_meshlet_instances_count[0]; } let local_index = group_thread_id.x; @@ -134,7 +133,7 @@ func cs_main( meshlet_triangle_count_shared = meshlet.triangle_count; let transform = transforms[mesh_instance.transform_index]; - model_view_proj_shared = mul(camera.projection_view_mat, transform.world); + model_view_proj_shared = mul(projection_view, transform.world); } GroupMemoryBarrierWithGroupSync(); @@ -148,8 +147,7 @@ func cs_main( let indices = meshlet.indices(mesh_lod, local_index); let positions = meshlet.positions(mesh, indices); - triangle_passed = test_triangle(positions, camera.resolution, cull_flags, local_index); - triangle_passed = true; + triangle_passed = test_triangle(positions, resolution, cull_flags, local_index); if (triangle_passed) { active_triangle_index = __atomic_add(triangles_passed_shared, 1, MemoryOrder::Relaxed); } @@ -169,4 +167,4 @@ func cs_main( reordered_indices[index_offset + 1] = (meshlet_instance_index << MESHLET_PRIMITIVE_BITS) | ((triangle_index + 1) & MESHLET_PRIMITIVE_MASK); reordered_indices[index_offset + 2] = (meshlet_instance_index << MESHLET_PRIMITIVE_BITS) | ((triangle_index + 2) & MESHLET_PRIMITIVE_MASK); } -} +} \ No newline at end of file diff --git a/Lorr/Engine/Resources/shaders/passes/generate_cull_commands.slang b/Lorr/Engine/Resources/shaders/passes/generate_cull_commands.slang index de936bd7..bb9814cf 100644 --- a/Lorr/Engine/Resources/shaders/passes/generate_cull_commands.slang +++ b/Lorr/Engine/Resources/shaders/passes/generate_cull_commands.slang @@ -7,5 +7,5 @@ import gpu; [[shader("compute")]] [[numthreads(1, 1, 1)]] func cs_main() -> void { - cull_meshlets_cmd[0].x = (visible_meshlet_instances_count[2] + (CULLING_MESHLET_COUNT - 1)) / CULLING_MESHLET_COUNT; + cull_meshlets_cmd[0].x = (visible_meshlet_instances_count[0] + (CULLING_MESHLET_COUNT - 1)) / CULLING_MESHLET_COUNT; } diff --git a/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang b/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang index 6f0450b0..0e03d529 100644 --- a/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang +++ b/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang @@ -3,6 +3,7 @@ import std; import pbr; import sky; import scene; +import shadow; #include @@ -17,6 +18,7 @@ struct ShaderParameters { Image2D normal_image; Image2D emissive_image; Image2D metallic_roughness_occlusion_image; + Texture2DArray directional_light_shadowmap; ConstantBuffer environment; ConstantBuffer camera; @@ -109,8 +111,15 @@ func fs_main(VertexOutput input) -> f32x4 { var material_surface_color = f32x3(0.0); let NoL = max(dot(N, L), 0.0); if (!intersects_planet && NoL > 0.0) { + let view_z = -mul(params.camera.view_mat, f32x4(world_position, 1.0)).z; + let directional_shadow = sample_shadow_map( + directional_light, + params.directional_light_shadowmap, + params.linear_clamp_sampler, + view_z, world_position, N, L); + let brdf = BRDF(V, N, L, albedo_color, roughness, metallic); - material_surface_color = brdf * horizon * sun_illuminance * NoL * occlusion; + material_surface_color = brdf * horizon * sun_illuminance * NoL * occlusion * directional_shadow; } // FINAL ──────────────────────────────────────────────────────────── diff --git a/Lorr/Engine/Resources/shaders/passes/shadowmap_cull_meshlets.slang b/Lorr/Engine/Resources/shaders/passes/shadowmap_cull_meshlets.slang new file mode 100644 index 00000000..941c7ea6 --- /dev/null +++ b/Lorr/Engine/Resources/shaders/passes/shadowmap_cull_meshlets.slang @@ -0,0 +1,47 @@ +import std; +import gpu; +import scene; +import cull; + +[[vk::binding(0)]] StructuredBuffer meshlet_instances; +[[vk::binding(1)]] StructuredBuffer mesh_instances; +[[vk::binding(2)]] StructuredBuffer meshes; +[[vk::binding(3)]] StructuredBuffer transforms; +[[vk::binding(4)]] StructuredBuffer all_visible_meshlet_instances_count; +[[vk::binding(5)]] RWStructuredBuffer visible_meshlet_instances_count; +[[vk::binding(6)]] RWStructuredBuffer visible_meshlet_instances_indices; +[[vk::binding(7)]] RWStructuredBuffer cull_triangles_cmd; + +#ifndef CULLING_MESHLET_COUNT +#define CULLING_MESHLET_COUNT 64 +#endif + +[[shader("compute")]] +[[numthreads(CULLING_MESHLET_COUNT, 1, 1)]] +func cs_main( + uint group_thread_id : SV_GroupThreadID, + uint global_thread_id : SV_DispatchThreadID, + uniform f32x4x4 projection_view, +) -> void { + let meshlet_instance_count = all_visible_meshlet_instances_count[0]; + let meshlet_instance_index = global_thread_id; + if (meshlet_instance_index >= meshlet_instance_count) { + return; + } + + let meshlet_instance = meshlet_instances[meshlet_instance_index]; + let mesh_instance = mesh_instances[meshlet_instance.mesh_instance_index]; + let transform = transforms[mesh_instance.transform_index]; + let mvp = mul(projection_view, transform.world); + + let mesh = meshes[mesh_instance.mesh_index]; + let mesh_lod = mesh.lods[mesh_instance.lod_index]; + let bounds = mesh_lod.meshlet_bounds[meshlet_instance.meshlet_index]; + + if (test_frustum(mvp, bounds.aabb_center, bounds.aabb_extent)) { + let index = __atomic_add(visible_meshlet_instances_count[0], 1, MemoryOrder::Relaxed); + visible_meshlet_instances_indices[index] = meshlet_instance_index; + + __atomic_add(cull_triangles_cmd[0].x, 1, MemoryOrder::Relaxed); + } +} diff --git a/Lorr/Engine/Resources/shaders/passes/shadowmap_draw.slang b/Lorr/Engine/Resources/shaders/passes/shadowmap_draw.slang new file mode 100644 index 00000000..e7765183 --- /dev/null +++ b/Lorr/Engine/Resources/shaders/passes/shadowmap_draw.slang @@ -0,0 +1,44 @@ +import std; +import gpu; +import scene; + +import passes.visbuffer; + +struct ShaderParameters { + StructuredBuffer meshlet_instances; + StructuredBuffer mesh_instances; + StructuredBuffer meshes; + StructuredBuffer transforms; +}; +ParameterBlock params; + +struct VertexOutput { + f32x4 position : SV_Position; +}; + +[[shader("vertex")]] +func vs_main(u32 vertex_index : SV_VertexID, uniform f32x4x4 projection_view) -> VertexOutput { + let vis = VisBufferData(vertex_index); + let meshlet_instance = params.meshlet_instances[vis.meshlet_instance_index]; + let mesh_instance = params.mesh_instances[meshlet_instance.mesh_instance_index]; + let mesh = params.meshes[mesh_instance.mesh_index]; + let mesh_lod = mesh.lods[mesh_instance.lod_index]; + let transform = params.transforms[mesh_instance.transform_index]; + let meshlet = mesh_lod.meshlets[meshlet_instance.meshlet_index]; + + let index = meshlet.index(mesh_lod, vis.triangle_index); + let vertex_pos = meshlet.position(mesh, index); + let tex_coord = meshlet.tex_coord(mesh, index); + let world_pos = transform.to_world_position(vertex_pos); + let clip_pos = mul(projection_view, f32x4(world_pos.xyz, 1.0)); + + VertexOutput output; + output.position = clip_pos; + + return output; +} + +[[shader("fragment")]] +func fs_main(VertexOutput input) -> f32x4 { + return f32x4(1.0); +} \ No newline at end of file diff --git a/Lorr/Engine/Resources/shaders/passes/visbuffer_encode.slang b/Lorr/Engine/Resources/shaders/passes/visbuffer_encode.slang index 96a91abd..d8847938 100644 --- a/Lorr/Engine/Resources/shaders/passes/visbuffer_encode.slang +++ b/Lorr/Engine/Resources/shaders/passes/visbuffer_encode.slang @@ -6,7 +6,6 @@ import scene; import passes.visbuffer; struct ShaderParameters { - ConstantBuffer camera; StructuredBuffer meshlet_instances; StructuredBuffer mesh_instances; StructuredBuffer meshes; @@ -26,7 +25,10 @@ struct VertexOutput { }; [[shader("vertex")]] -func vs_main(u32 vertex_index : SV_VertexID) -> VertexOutput { +func vs_main( + u32 vertex_index : SV_VertexID, + uniform f32x4x4 observer_projection_view_mat +) -> VertexOutput { let vis = VisBufferData(vertex_index); let meshlet_instance = params.meshlet_instances[vis.meshlet_instance_index]; let mesh_instance = params.mesh_instances[meshlet_instance.mesh_instance_index]; @@ -39,7 +41,7 @@ func vs_main(u32 vertex_index : SV_VertexID) -> VertexOutput { let vertex_pos = meshlet.position(mesh, index); let tex_coord = meshlet.tex_coord(mesh, index); let world_pos = transform.to_world_position(vertex_pos); - let clip_pos = mul(params.camera.projection_view_mat, f32x4(world_pos.xyz, 1.0)); + let clip_pos = mul(observer_projection_view_mat, f32x4(world_pos.xyz, 1.0)); VertexOutput output; output.position = clip_pos; diff --git a/Lorr/Engine/Resources/shaders/shadow.slang b/Lorr/Engine/Resources/shaders/shadow.slang new file mode 100644 index 00000000..a759b889 --- /dev/null +++ b/Lorr/Engine/Resources/shaders/shadow.slang @@ -0,0 +1,116 @@ +module shadow; + +import std; +import scene; + +#ifndef MAX_DIRECTIONAL_SHADOW_CASCADES +#define MAX_DIRECTIONAL_SHADOW_CASCADES 4 +#endif + +// TODO: Move this to color, yes i could actually do this instead of writing this text +// but i am SO FUCKING LAZY +func hsv_to_rgb(f32x3 hsv) -> f32x3 { + let n = f32x3(5.0, 3.0, 1.0); + let k = (n + hsv.x / 1.0471975512) % 6.0; + return hsv.z - hsv.z * hsv.y * max(f32x3(0.0), min(k, min(4.0 - k, f32x3(1.0)))); +} + +public func cascade_debug_visualization( + f32x3 output_color, + u32 cascade_index, + f32 view_z +) -> f32x3 { + let overlay_alpha = 0.70; + let cascade_color_hsv = f32x3( + f32(cascade_index) / f32(MAX_DIRECTIONAL_SHADOW_CASCADES + 1u) * TAU, + 1.0, + 0.5 + ); + let cascade_color = hsv_to_rgb(cascade_color_hsv); + return f32x3( + (1.0 - overlay_alpha) * output_color.rgb + overlay_alpha * cascade_color + ); +} + +public func get_cascade_index(in DirectionalLight light, f32 view_z) -> u32 { + for (var i = 0u; i < light.cascade_count; i++) { + if (view_z < light.cascades[i].far_bound) { + return i; + } + } + + return light.cascade_count - 1; +} + +func sample_cascade( + in DirectionalLight light, + Texture2DArray shadow_map, + SamplerState shadow_sampler, + f32x3 world_position, + f32x3 normal, + f32x3 light_direction, + u32 cascade_index +) -> f32 { + let cascade = light.cascades[cascade_index]; + let normal_offset = light.normal_bias * cascade.texel_size * normal; + let depth_offset = light.depth_bias * light_direction; + let offset_position = f32x3(world_position.xyz + normal_offset + depth_offset); + let offset_position_clip = mul(cascade.projection_view_mat, f32x4(offset_position, 1.0)); + if (offset_position_clip.w <= 0.0) { + return 1.0; + } + + let offset_position_ndc = offset_position_clip.xyz / offset_position_clip.w; + // No shadow outside the orthographic projection volume + if (any(offset_position_ndc.xy < -1.0) || + any(offset_position_ndc.xy > 1.0) || + offset_position_ndc.z < 0.0 || + offset_position_ndc.z > 1.0) { + return 1.0; + } + + let light_local = f32x3(offset_position_ndc.xy * 0.5 + 0.5, offset_position_ndc.z); + let shadow_depth = shadow_map.SampleLevel(shadow_sampler, f32x3(light_local.xy, cascade_index), 0.0); + + return light_local.z < shadow_depth ? 0.0 : 1.0; +} + +public func sample_shadow_map( + in DirectionalLight light, + Texture2DArray shadow_map, + SamplerState shadow_sampler, + f32 view_z, + f32x3 world_position, + f32x3 normal, + f32x3 light_direction +) -> f32 { + let cascade_index = get_cascade_index(light, view_z); + + var shadow = sample_cascade( + light, + shadow_map, + shadow_sampler, + world_position, + normal, + light_direction, + cascade_index); + + let next_cascade_index = cascade_index + 1u; + if (next_cascade_index < light.cascade_count) { + let this_far_bound = light.cascades[cascade_index].far_bound; + let next_near_bound = (1.0 - light.cascades_overlap_proportion) * this_far_bound; + if (view_z >= next_near_bound) { + var next_shadow = sample_cascade( + light, + shadow_map, + shadow_sampler, + world_position, + normal, + light_direction, + next_cascade_index); + + shadow = lerp(shadow, next_shadow, (view_z - next_near_bound) / (this_far_bound - next_near_bound)); + } + } + return shadow; +} \ No newline at end of file diff --git a/Lorr/Engine/Scene/Scene.cc b/Lorr/Engine/Scene/Scene.cc index a2e40a11..de4d5753 100644 --- a/Lorr/Engine/Scene/Scene.cc +++ b/Lorr/Engine/Scene/Scene.cc @@ -695,7 +695,7 @@ auto Scene::prepare_frame(this Scene &self, SceneRenderer &renderer, u32 image_c ls::option active_camera_data = override_camera; if (!active_camera_data.has_value()) { - camera_query.each([&active_camera_data](flecs::entity, ECS::Transform &t, ECS::Camera &c, ECS::ActiveCamera) { + camera_query.each([&active_camera_data](flecs::entity, const ECS::Transform &t, const ECS::Camera &c, const ECS::ActiveCamera) { auto aspect_ratio = c.resolution.x / c.resolution.y; auto projection_mat = glm::perspectiveRH_ZO(glm::radians(c.fov), aspect_ratio, c.far_clip, c.near_clip); projection_mat[1][1] *= -1; @@ -726,7 +726,7 @@ auto Scene::prepare_frame(this Scene &self, SceneRenderer &renderer, u32 image_c } GPU::Environment environment = {}; - environment_query.each([&environment](flecs::entity, ECS::Environment &environment_comp) { + environment_query.each([&environment](flecs::entity, const ECS::Environment &environment_comp) { environment.flags |= environment_comp.atmos ? GPU::EnvironmentFlags::HasAtmosphere : 0; environment.flags |= environment_comp.eye_adaptation ? GPU::EnvironmentFlags::HasEyeAdaptation : 0; @@ -747,7 +747,7 @@ auto Scene::prepare_frame(this Scene &self, SceneRenderer &renderer, u32 image_c environment.eye_ISO_K = environment_comp.eye_iso / environment_comp.eye_k; }); - directional_light_query.each([&environment](flecs::entity, const ECS::DirectionalLight &directional_light_comp) { + directional_light_query.each([&environment, &active_camera_data](flecs::entity, const ECS::DirectionalLight &directional_light_comp) { auto &directional_light = environment.lights.directional_light; environment.flags |= GPU::EnvironmentFlags::HasSun; @@ -764,6 +764,10 @@ auto Scene::prepare_frame(this Scene &self, SceneRenderer &renderer, u32 image_c directional_light.cascades_overlap_proportion = directional_light_comp.cascade_overlap_propotion; directional_light.depth_bias = directional_light_comp.depth_bias; directional_light.normal_bias = directional_light_comp.normal_bias; + + if (directional_light_comp.cascade_count > 0) { + calculate_cascaded_shadow_matrices(directional_light, directional_light_comp, *active_camera_data); + } }); auto regenerate_sky = false; diff --git a/Lorr/Engine/Scene/SceneRenderer.cc b/Lorr/Engine/Scene/SceneRenderer.cc index c60aac8d..93ea5d99 100644 --- a/Lorr/Engine/Scene/SceneRenderer.cc +++ b/Lorr/Engine/Scene/SceneRenderer.cc @@ -159,6 +159,18 @@ auto SceneRenderer::init(this SceneRenderer &self) -> bool { }; Pipeline::create(device, default_slang_session, vis_decode_pipeline_info, bindless_descriptor_set).value(); + auto shadowmap_cull_meshlets = PipelineCompileInfo{ + .module_name = "passes.shadowmap_cull_meshlets", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, shadowmap_cull_meshlets).value(); + + auto shadowmap_draw = PipelineCompileInfo{ + .module_name = "passes.shadowmap_draw", + .entry_points = { "vs_main", "fs_main" }, + }; + Pipeline::create(device, default_slang_session, shadowmap_draw).value(); + // ── PBR ───────────────────────────────────────────────────────────── auto pbr_apply_pipeline_info = PipelineCompileInfo{ .module_name = "passes.pbr_apply", @@ -490,34 +502,38 @@ auto SceneRenderer::prepare_frame(this SceneRenderer &self, FramePrepareInfo &in self.sky_multiscatter_lut_view.acquire(device, "sky multiscatter lut", vuk::ImageUsageFlagBits::eSampled, vuk::Access::eComputeSampled); } + prepared_frame.camera = info.camera; + if (info.environment.flags & GPU::EnvironmentFlags::HasSun) { + prepared_frame.directional_light = info.environment.lights.directional_light; + } prepared_frame.vbgtao = info.vbgtao; return prepared_frame; } static auto cull_meshes( + TransferManager &transfer_man, GPU::CullFlags cull_flags, u32 mesh_instance_count, - TransferManager &transfer_man, + glm::mat4 &frustum_projection_view, + glm::vec3 &observer_position, + glm::vec2 &observer_resolution, + f32 acceptable_lod_error, vuk::Value &meshes_buffer, vuk::Value &mesh_instances_buffer, vuk::Value &meshlet_instances_buffer, vuk::Value &visible_meshlet_instances_count_buffer, vuk::Value &transforms_buffer, - vuk::Value &hiz_attachment, - vuk::Value &camera_buffer, vuk::Value &debug_drawer_buffer ) -> vuk::Value { ZoneScoped; auto vis_cull_meshes_pass = vuk::make_pass( "vis cull meshes", - [cull_flags, mesh_instance_count]( + [cull_flags, mesh_instance_count, frustum_projection_view, observer_position, observer_resolution, acceptable_lod_error]( vuk::CommandBuffer &cmd_list, - VUK_BA(vuk::eComputeRead) camera, VUK_BA(vuk::eComputeRead) meshes, VUK_BA(vuk::eComputeRead) transforms, - VUK_IA(vuk::eComputeSampled) hiz, VUK_BA(vuk::eComputeRW) mesh_instances, VUK_BA(vuk::eComputeRW) meshlet_instances, VUK_BA(vuk::eComputeRW) visible_meshlet_instances_count, @@ -525,37 +541,41 @@ static auto cull_meshes( ) { cmd_list // .bind_compute_pipeline("passes.cull_meshes") - .bind_buffer(0, 0, camera) - .bind_buffer(0, 1, meshes) - .bind_buffer(0, 2, transforms) - .bind_image(0, 3, hiz) - .bind_sampler(0, 4, hiz_sampler_info) - .bind_buffer(0, 5, mesh_instances) - .bind_buffer(0, 6, meshlet_instances) - .bind_buffer(0, 7, visible_meshlet_instances_count) - .bind_buffer(0, 8, debug_drawer) - .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(mesh_instance_count, cull_flags)) + .bind_buffer(0, 0, meshes) + .bind_buffer(0, 1, transforms) + .bind_buffer(0, 2, mesh_instances) + .bind_buffer(0, 3, meshlet_instances) + .bind_buffer(0, 4, visible_meshlet_instances_count) + .bind_buffer(0, 5, debug_drawer) + .push_constants( + vuk::ShaderStageFlagBits::eCompute, + 0, + PushConstants( + mesh_instance_count, + cull_flags, + frustum_projection_view, + observer_position, + glm::max(observer_resolution.x, observer_resolution.y), + acceptable_lod_error + ) + ) .dispatch_invocations(mesh_instance_count); - return std::make_tuple(camera, meshes, transforms, hiz, mesh_instances, meshlet_instances, visible_meshlet_instances_count, debug_drawer); + return std::make_tuple(meshes, transforms, mesh_instances, meshlet_instances, visible_meshlet_instances_count, debug_drawer); } ); std::tie( - camera_buffer, meshes_buffer, transforms_buffer, - hiz_attachment, mesh_instances_buffer, meshlet_instances_buffer, visible_meshlet_instances_count_buffer, debug_drawer_buffer ) = vis_cull_meshes_pass( - std::move(camera_buffer), std::move(meshes_buffer), std::move(transforms_buffer), - std::move(hiz_attachment), std::move(mesh_instances_buffer), std::move(meshlet_instances_buffer), std::move(visible_meshlet_instances_count_buffer), @@ -585,21 +605,24 @@ static auto cull_meshes( } static auto cull_meshlets( + TransferManager &transfer_man, bool late, GPU::CullFlags cull_flags, - TransferManager &transfer_man, + f32 near_clip, + glm::vec2 &resolution, + glm::mat4 &projection_view, vuk::Value &hiz_attachment, vuk::Value &cull_meshlets_cmd_buffer, vuk::Value &visible_meshlet_instances_count_buffer, + vuk::Value &early_visible_meshlet_instances_count_buffer, + vuk::Value &late_visible_meshlet_instances_count_buffer, vuk::Value &visible_meshlet_instances_indices_buffer, vuk::Value &meshlet_instance_visibility_mask_buffer, vuk::Value &reordered_indices_buffer, vuk::Value &meshes_buffer, vuk::Value &mesh_instances_buffer, vuk::Value &meshlet_instances_buffer, - vuk::Value &transforms_buffer, - vuk::Value &camera_buffer, - vuk::Value &debug_drawer_buffer + vuk::Value &transforms_buffer ) -> vuk::Value { ZoneScoped; memory::ScopedStack stack; @@ -607,52 +630,52 @@ static auto cull_meshlets( // ── CULL MESHLETS ─────────────────────────────────────────────────── auto vis_cull_meshlets_pass = vuk::make_pass( stack.format("vis cull meshlets {}", late ? "late" : "early"), - [late, cull_flags]( + [late, cull_flags, near_clip, projection_view]( vuk::CommandBuffer &cmd_list, VUK_BA(vuk::eIndirectRead) dispatch_cmd, - VUK_BA(vuk::eComputeRead) camera, VUK_BA(vuk::eComputeRead) meshlet_instances, VUK_BA(vuk::eComputeRead) mesh_instances, VUK_BA(vuk::eComputeRead) meshes, VUK_BA(vuk::eComputeRead) transforms, VUK_IA(vuk::eComputeSampled) hiz, - VUK_BA(vuk::eComputeRW) visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRead) visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRW) early_visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRW) late_visible_meshlet_instances_count, VUK_BA(vuk::eComputeRW) visible_meshlet_instances_indices, VUK_BA(vuk::eComputeRW) meshlet_instance_visibility_mask, - VUK_BA(vuk::eComputeRW) cull_triangles_cmd, - VUK_BA(vuk::eComputeRW) debug_drawer + VUK_BA(vuk::eComputeRW) cull_triangles_cmd ) { cmd_list // .bind_compute_pipeline("passes.cull_meshlets") - .bind_buffer(0, 0, camera) - .bind_buffer(0, 1, meshlet_instances) - .bind_buffer(0, 2, mesh_instances) - .bind_buffer(0, 3, meshes) - .bind_buffer(0, 4, transforms) - .bind_image(0, 5, hiz) - .bind_sampler(0, 6, hiz_sampler_info) - .bind_buffer(0, 7, visible_meshlet_instances_count) - .bind_buffer(0, 8, visible_meshlet_instances_indices) - .bind_buffer(0, 9, meshlet_instance_visibility_mask) - .bind_buffer(0, 10, cull_triangles_cmd) - .bind_buffer(0, 11, debug_drawer) - .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, cull_flags) + .bind_buffer(0, 0, meshlet_instances) + .bind_buffer(0, 1, mesh_instances) + .bind_buffer(0, 2, meshes) + .bind_buffer(0, 3, transforms) + .bind_image(0, 4, hiz) + .bind_sampler(0, 5, hiz_sampler_info) + .bind_buffer(0, 6, visible_meshlet_instances_count) + .bind_buffer(0, 7, early_visible_meshlet_instances_count) + .bind_buffer(0, 8, late_visible_meshlet_instances_count) + .bind_buffer(0, 9, visible_meshlet_instances_indices) + .bind_buffer(0, 10, meshlet_instance_visibility_mask) + .bind_buffer(0, 11, cull_triangles_cmd) + .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(cull_flags, near_clip, projection_view)) .specialize_constants(0, late) .dispatch_indirect(dispatch_cmd); return std::make_tuple( dispatch_cmd, - camera, meshlet_instances, mesh_instances, meshes, transforms, hiz, visible_meshlet_instances_count, + early_visible_meshlet_instances_count, + late_visible_meshlet_instances_count, visible_meshlet_instances_indices, meshlet_instance_visibility_mask, - cull_triangles_cmd, - debug_drawer + cull_triangles_cmd ); } ); @@ -661,41 +684,40 @@ static auto cull_meshlets( std::tie( cull_meshlets_cmd_buffer, - camera_buffer, meshlet_instances_buffer, mesh_instances_buffer, meshes_buffer, transforms_buffer, hiz_attachment, visible_meshlet_instances_count_buffer, + early_visible_meshlet_instances_count_buffer, + late_visible_meshlet_instances_count_buffer, visible_meshlet_instances_indices_buffer, meshlet_instance_visibility_mask_buffer, - cull_triangles_cmd_buffer, - debug_drawer_buffer + cull_triangles_cmd_buffer ) = vis_cull_meshlets_pass( std::move(cull_meshlets_cmd_buffer), - std::move(camera_buffer), std::move(meshlet_instances_buffer), std::move(mesh_instances_buffer), std::move(meshes_buffer), std::move(transforms_buffer), std::move(hiz_attachment), std::move(visible_meshlet_instances_count_buffer), + std::move(early_visible_meshlet_instances_count_buffer), + std::move(late_visible_meshlet_instances_count_buffer), std::move(visible_meshlet_instances_indices_buffer), std::move(meshlet_instance_visibility_mask_buffer), - std::move(cull_triangles_cmd_buffer), - std::move(debug_drawer_buffer) + std::move(cull_triangles_cmd_buffer) ); // ── CULL TRIANGLES ────────────────────────────────────────────────── auto vis_cull_triangles_pass = vuk::make_pass( stack.format("vis cull triangles {}", late ? "late" : "early"), - [late, cull_flags]( + [late, cull_flags, resolution, projection_view]( vuk::CommandBuffer &cmd_list, VUK_BA(vuk::eIndirectRead) cull_triangles_cmd, - VUK_BA(vuk::eComputeRead) camera, - VUK_BA(vuk::eComputeRead) visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRead) early_visible_meshlet_instances_count, VUK_BA(vuk::eComputeRead) visible_meshlet_instances_indices, VUK_BA(vuk::eComputeRead) meshlet_instances, VUK_BA(vuk::eComputeRead) mesh_instances, @@ -706,22 +728,20 @@ static auto cull_meshlets( ) { cmd_list // .bind_compute_pipeline("passes.cull_triangles") - .bind_buffer(0, 0, camera) - .bind_buffer(0, 1, visible_meshlet_instances_count) - .bind_buffer(0, 2, visible_meshlet_instances_indices) - .bind_buffer(0, 3, meshlet_instances) - .bind_buffer(0, 4, mesh_instances) - .bind_buffer(0, 5, meshes) - .bind_buffer(0, 6, transforms) - .bind_buffer(0, 7, draw_indexed_cmd) - .bind_buffer(0, 8, reordered_indices) - .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, cull_flags) + .bind_buffer(0, 0, early_visible_meshlet_instances_count) + .bind_buffer(0, 1, visible_meshlet_instances_indices) + .bind_buffer(0, 2, meshlet_instances) + .bind_buffer(0, 3, mesh_instances) + .bind_buffer(0, 4, meshes) + .bind_buffer(0, 5, transforms) + .bind_buffer(0, 6, draw_indexed_cmd) + .bind_buffer(0, 7, reordered_indices) + .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(cull_flags, resolution, projection_view)) .specialize_constants(0, late) .dispatch_indirect(cull_triangles_cmd); return std::make_tuple( - camera, - visible_meshlet_instances_count, + early_visible_meshlet_instances_count, visible_meshlet_instances_indices, meshlet_instances, mesh_instances, @@ -736,8 +756,7 @@ static auto cull_meshlets( auto draw_command_buffer = transfer_man.scratch_buffer({ .instanceCount = 1 }); std::tie( - camera_buffer, - visible_meshlet_instances_count_buffer, + early_visible_meshlet_instances_count_buffer, visible_meshlet_instances_indices_buffer, meshlet_instances_buffer, mesh_instances_buffer, @@ -748,8 +767,7 @@ static auto cull_meshlets( ) = vis_cull_triangles_pass( std::move(cull_triangles_cmd_buffer), - std::move(camera_buffer), - std::move(visible_meshlet_instances_count_buffer), + std::move(early_visible_meshlet_instances_count_buffer), std::move(visible_meshlet_instances_indices_buffer), std::move(meshlet_instances_buffer), std::move(mesh_instances_buffer), @@ -764,6 +782,7 @@ static auto cull_meshlets( static auto draw_visbuffer( bool late, + glm::mat4 &projection_view, vuk::PersistentDescriptorSet &descriptor_set, vuk::Value &depth_attachment, vuk::Value &visbuffer_attachment, @@ -774,19 +793,17 @@ static auto draw_visbuffer( vuk::Value &mesh_instances_buffer, vuk::Value &meshlet_instances_buffer, vuk::Value &transforms_buffer, - vuk::Value &materials_buffer, - vuk::Value &camera_buffer + vuk::Value &materials_buffer ) -> void { ZoneScoped; memory::ScopedStack stack; auto vis_encode_pass = vuk::make_pass( stack.format("vis encode {}", late ? "late" : "early"), - [&descriptor_set]( + [&descriptor_set, projection_view]( vuk::CommandBuffer &cmd_list, VUK_BA(vuk::eIndirectRead) triangle_indirect, VUK_BA(vuk::eIndexRead) index_buffer, - VUK_BA(vuk::eVertexRead) camera, VUK_BA(vuk::eVertexRead) meshlet_instances, VUK_BA(vuk::eVertexRead) mesh_instances, VUK_BA(vuk::eVertexRead) meshes, @@ -805,34 +822,22 @@ static auto draw_visbuffer( .set_viewport(0, vuk::Rect2D::framebuffer()) .set_scissor(0, vuk::Rect2D::framebuffer()) .bind_persistent(1, descriptor_set) - .bind_buffer(0, 0, camera) - .bind_buffer(0, 1, meshlet_instances) - .bind_buffer(0, 2, mesh_instances) - .bind_buffer(0, 3, meshes) - .bind_buffer(0, 4, transforms) - .bind_buffer(0, 5, materials) - .bind_image(0, 6, overdraw) + .bind_buffer(0, 0, meshlet_instances) + .bind_buffer(0, 1, mesh_instances) + .bind_buffer(0, 2, meshes) + .bind_buffer(0, 3, transforms) + .bind_buffer(0, 4, materials) + .bind_image(0, 5, overdraw) .bind_index_buffer(index_buffer, vuk::IndexType::eUint32) + .push_constants(vuk::ShaderStageFlagBits::eVertex, 0, projection_view) .draw_indexed_indirect(1, triangle_indirect); - return std::make_tuple( - index_buffer, - camera, - meshlet_instances, - mesh_instances, - meshes, - transforms, - materials, - visbuffer, - depth, - overdraw - ); + return std::make_tuple(index_buffer, meshlet_instances, mesh_instances, meshes, transforms, materials, visbuffer, depth, overdraw); } ); std::tie( reordered_indices_buffer, - camera_buffer, meshlet_instances_buffer, mesh_instances_buffer, meshes_buffer, @@ -845,7 +850,6 @@ static auto draw_visbuffer( vis_encode_pass( std::move(draw_command_buffer), std::move(reordered_indices_buffer), - std::move(camera_buffer), std::move(meshlet_instances_buffer), std::move(mesh_instances_buffer), std::move(meshes_buffer), @@ -857,6 +861,218 @@ static auto draw_visbuffer( ); } +auto cull_shadowmap_meshlets( + TransferManager &transfer_man, + u32 cascade_index, + glm::vec2 &resolution, + glm::mat4 &projection_view, + vuk::Value &cull_meshlets_cmd_buffer, + vuk::Value &all_visible_meshlet_instances_count_buffer, + vuk::Value &visible_meshlet_instances_count_buffer, + vuk::Value &visible_meshlet_instances_indices_buffer, + vuk::Value &reordered_indices_buffer, + vuk::Value &meshes_buffer, + vuk::Value &mesh_instances_buffer, + vuk::Value &meshlet_instances_buffer, + vuk::Value &transforms_buffer +) -> vuk::Value { + ZoneScoped; + memory::ScopedStack stack; + + // ── CULL MESHLETS ─────────────────────────────────────────────────── + auto shadowmap_cull_meshlets_pass = vuk::make_pass( + stack.format("shadowmap cull meshlets cascade {}", cascade_index), + [projection_view]( + vuk::CommandBuffer &cmd_list, + VUK_BA(vuk::eIndirectRead) dispatch_cmd, + VUK_BA(vuk::eComputeRead) meshlet_instances, + VUK_BA(vuk::eComputeRead) mesh_instances, + VUK_BA(vuk::eComputeRead) meshes, + VUK_BA(vuk::eComputeRead) transforms, + VUK_BA(vuk::eComputeRead) all_visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRW) visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRW) visible_meshlet_instances_indices, + VUK_BA(vuk::eComputeRW) cull_triangles_cmd + ) { + cmd_list // + .bind_compute_pipeline("passes.shadowmap_cull_meshlets") + .bind_buffer(0, 0, meshlet_instances) + .bind_buffer(0, 1, mesh_instances) + .bind_buffer(0, 2, meshes) + .bind_buffer(0, 3, transforms) + .bind_buffer(0, 4, all_visible_meshlet_instances_count) + .bind_buffer(0, 5, visible_meshlet_instances_count) + .bind_buffer(0, 6, visible_meshlet_instances_indices) + .bind_buffer(0, 7, cull_triangles_cmd) + .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, projection_view) + .dispatch_indirect(dispatch_cmd); + + return std::make_tuple( + dispatch_cmd, + meshlet_instances, + mesh_instances, + meshes, + transforms, + all_visible_meshlet_instances_count, + visible_meshlet_instances_count, + visible_meshlet_instances_indices, + cull_triangles_cmd + ); + } + ); + + auto cull_triangles_cmd_buffer = transfer_man.scratch_buffer({ .x = 0, .y = 1, .z = 1 }); + + std::tie( + cull_meshlets_cmd_buffer, + meshlet_instances_buffer, + mesh_instances_buffer, + meshes_buffer, + transforms_buffer, + all_visible_meshlet_instances_count_buffer, + visible_meshlet_instances_count_buffer, + visible_meshlet_instances_indices_buffer, + cull_triangles_cmd_buffer + ) = + shadowmap_cull_meshlets_pass( + std::move(cull_meshlets_cmd_buffer), + std::move(meshlet_instances_buffer), + std::move(mesh_instances_buffer), + std::move(meshes_buffer), + std::move(transforms_buffer), + std::move(all_visible_meshlet_instances_count_buffer), + std::move(visible_meshlet_instances_count_buffer), + std::move(visible_meshlet_instances_indices_buffer), + std::move(cull_triangles_cmd_buffer) + ); + + // ── CULL TRIANGLES ────────────────────────────────────────────────── + auto shadowmap_cull_triangles_pass = vuk::make_pass( + stack.format("shadowmap cull triangles cascade {}", cascade_index), + [resolution, projection_view]( + vuk::CommandBuffer &cmd_list, + VUK_BA(vuk::eIndirectRead) cull_triangles_cmd, + VUK_BA(vuk::eComputeRead) visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRead) visible_meshlet_instances_indices, + VUK_BA(vuk::eComputeRead) meshlet_instances, + VUK_BA(vuk::eComputeRead) mesh_instances, + VUK_BA(vuk::eComputeRead) meshes, + VUK_BA(vuk::eComputeRead) transforms, + VUK_BA(vuk::eComputeRW) draw_indexed_cmd, + VUK_BA(vuk::eComputeWrite) reordered_indices + ) { + auto cull_flags = GPU::CullFlags::All; + cmd_list // + .bind_compute_pipeline("passes.cull_triangles") + .bind_buffer(0, 0, visible_meshlet_instances_count) + .bind_buffer(0, 1, visible_meshlet_instances_indices) + .bind_buffer(0, 2, meshlet_instances) + .bind_buffer(0, 3, mesh_instances) + .bind_buffer(0, 4, meshes) + .bind_buffer(0, 5, transforms) + .bind_buffer(0, 6, draw_indexed_cmd) + .bind_buffer(0, 7, reordered_indices) + .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(cull_flags, resolution, projection_view)) + .specialize_constants(0, false) + .dispatch_indirect(cull_triangles_cmd); + + return std::make_tuple( + visible_meshlet_instances_count, + visible_meshlet_instances_indices, + meshlet_instances, + mesh_instances, + meshes, + transforms, + draw_indexed_cmd, + reordered_indices + ); + } + ); + + auto draw_command_buffer = transfer_man.scratch_buffer({ .instanceCount = 1 }); + + std::tie( + visible_meshlet_instances_count_buffer, + visible_meshlet_instances_indices_buffer, + meshlet_instances_buffer, + mesh_instances_buffer, + meshes_buffer, + transforms_buffer, + draw_command_buffer, + reordered_indices_buffer + ) = + shadowmap_cull_triangles_pass( + std::move(cull_triangles_cmd_buffer), + std::move(visible_meshlet_instances_count_buffer), + std::move(visible_meshlet_instances_indices_buffer), + std::move(meshlet_instances_buffer), + std::move(mesh_instances_buffer), + std::move(meshes_buffer), + std::move(transforms_buffer), + std::move(draw_command_buffer), + std::move(reordered_indices_buffer) + ); + + return draw_command_buffer; +} + +auto draw_shadowmap( + u32 cascade_index, + glm::mat4 &projection_view, + vuk::Value &depth_attachment, + vuk::Value &draw_command_buffer, + vuk::Value &reordered_indices_buffer, + vuk::Value &meshes_buffer, + vuk::Value &mesh_instances_buffer, + vuk::Value &meshlet_instances_buffer, + vuk::Value &transforms_buffer +) -> void { + ZoneScoped; + memory::ScopedStack stack; + + auto draw_shadowmap_pass = vuk::make_pass( + stack.format("draw shadowmap casecade {}", cascade_index), + [projection_view]( + vuk::CommandBuffer &cmd_list, + VUK_BA(vuk::eIndirectRead) triangle_indirect, + VUK_BA(vuk::eIndexRead) index_buffer, + VUK_BA(vuk::eVertexRead) meshlet_instances, + VUK_BA(vuk::eVertexRead) mesh_instances, + VUK_BA(vuk::eVertexRead) meshes, + VUK_BA(vuk::eVertexRead) transforms, + VUK_IA(vuk::eDepthStencilRW) depth + ) { + cmd_list // + .bind_graphics_pipeline("passes.shadowmap_draw") + .set_rasterization({ .cullMode = vuk::CullModeFlagBits::eBack }) + .set_depth_stencil({ .depthTestEnable = true, .depthWriteEnable = true, .depthCompareOp = vuk::CompareOp::eGreaterOrEqual }) + .set_dynamic_state(vuk::DynamicStateFlagBits::eViewport | vuk::DynamicStateFlagBits::eScissor) + .set_viewport(0, vuk::Rect2D::framebuffer()) + .set_scissor(0, vuk::Rect2D::framebuffer()) + .bind_buffer(0, 0, meshlet_instances) + .bind_buffer(0, 1, mesh_instances) + .bind_buffer(0, 2, meshes) + .bind_buffer(0, 3, transforms) + .bind_index_buffer(index_buffer, vuk::IndexType::eUint32) + .push_constants(vuk::ShaderStageFlagBits::eVertex, 0, projection_view) + .draw_indexed_indirect(1, triangle_indirect); + + return std::make_tuple(index_buffer, meshlet_instances, mesh_instances, meshes, transforms, depth); + } + ); + + std::tie(reordered_indices_buffer, meshlet_instances_buffer, mesh_instances_buffer, meshes_buffer, transforms_buffer, depth_attachment) = + draw_shadowmap_pass( + std::move(draw_command_buffer), + std::move(reordered_indices_buffer), + std::move(meshlet_instances_buffer), + std::move(mesh_instances_buffer), + std::move(meshes_buffer), + std::move(transforms_buffer), + std::move(depth_attachment) + ); +} + static auto draw_hiz(vuk::Value &hiz_attachment, vuk::Value &depth_attachment) -> void { ZoneScoped; @@ -1265,6 +1481,95 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Value({}); + auto cull_meshlets_cmd_buffer = cull_meshes( + transfer_man, + GPU::CullFlags::MeshFrustum, + frame.mesh_instance_count, + current_cascade_projection_view, + frame.camera.position, + frame.camera.resolution, + frame.camera.acceptable_lod_error, + meshes_buffer, + mesh_instances_buffer, + meshlet_instances_buffer, + all_visible_meshlet_instances_count_buffer, + transforms_buffer, + debug_drawer_buffer + ); + auto visible_meshlet_instances_count_buffer = transfer_man.scratch_buffer({}); + auto draw_shadowmap_cmd_buffer = cull_shadowmap_meshlets( + transfer_man, + cascade_index, + directional_light_resolution, + current_cascade_projection_view, + cull_meshlets_cmd_buffer, + all_visible_meshlet_instances_count_buffer, + visible_meshlet_instances_count_buffer, + visible_meshlet_instances_indices_buffer, + reordered_indices_buffer, + meshes_buffer, + mesh_instances_buffer, + meshlet_instances_buffer, + transforms_buffer + ); + + draw_shadowmap( + cascade_index, + current_cascade_projection_view, + current_cascade_attachment, + draw_shadowmap_cmd_buffer, + reordered_indices_buffer, + meshes_buffer, + mesh_instances_buffer, + meshlet_instances_buffer, + transforms_buffer + ); + } + } + + // Visibility buffer + auto visbuffer_attachment = vuk::declare_ia( "visbuffer", { .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eColorAttachment, @@ -1303,56 +1608,50 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Value({}); + auto early_visible_meshlet_instances_count_buffer = transfer_man.scratch_buffer({}); + auto late_visible_meshlet_instances_count_buffer = transfer_man.scratch_buffer({}); - auto visible_meshlet_instances_count_buffer = transfer_man.scratch_buffer({}); auto cull_meshlets_cmd_buffer = cull_meshes( + transfer_man, info.cull_flags, frame.mesh_instance_count, - transfer_man, + frame.camera.projection_view_mat, + frame.camera.position, + frame.camera.resolution, + frame.camera.acceptable_lod_error, meshes_buffer, mesh_instances_buffer, meshlet_instances_buffer, visible_meshlet_instances_count_buffer, transforms_buffer, - hiz_attachment, - camera_buffer, debug_drawer_buffer ); auto early_draw_visbuffer_cmd_buffer = cull_meshlets( + transfer_man, false, info.cull_flags, - transfer_man, + frame.camera.near_clip, + frame.camera.resolution, + frame.camera.projection_view_mat, hiz_attachment, cull_meshlets_cmd_buffer, visible_meshlet_instances_count_buffer, + early_visible_meshlet_instances_count_buffer, + late_visible_meshlet_instances_count_buffer, visible_meshlet_instances_indices_buffer, meshlet_instance_visibility_mask_buffer, reordered_indices_buffer, meshes_buffer, mesh_instances_buffer, meshlet_instances_buffer, - transforms_buffer, - camera_buffer, - debug_drawer_buffer + transforms_buffer ); draw_visbuffer( false, + frame.camera.projection_view_mat, bindless_descriptor_set, depth_attachment, visbuffer_attachment, @@ -1363,32 +1662,35 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Value directional_camera_buffer = {}; vuk::Value sky_transmittance_lut = {}; vuk::Value sky_multiscatter_lut = {}; - ls::option vbgtao = {}; + GPU::Camera camera = {}; + ls::option directional_light = ls::nullopt; + ls::option vbgtao = ls::nullopt; }; struct SceneRenderInfo { From 66cdaa310d6066abd7016d9dbd11074be0d59c99 Mon Sep 17 00:00:00 2001 From: exdal <63502313+exdal@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:51:57 +0300 Subject: [PATCH 4/8] rewrite gpu scene --- Lorr/Editor/Window/SceneBrowserWindow.cc | 9 - Lorr/Engine/Graphics/Slang/Compiler.cc | 2 +- .../shaders/passes/histogram_average.slang | 10 +- .../shaders/passes/histogram_generate.slang | 5 +- .../Resources/shaders/passes/pbr_apply.slang | 26 +- .../passes/sky_aerial_perspective.slang | 32 +- .../shaders/passes/sky_cubemap.slang | 19 +- .../Resources/shaders/passes/sky_final.slang | 33 +- .../shaders/passes/sky_multiscattering.slang | 12 +- .../shaders/passes/sky_transmittance.slang | 18 +- .../Resources/shaders/passes/sky_view.slang | 27 +- .../Resources/shaders/passes/tonemap.slang | 8 +- .../shaders/passes/vbgtao_denoise.slang | 7 +- .../shaders/passes/vbgtao_generate.slang | 25 +- Lorr/Engine/Resources/shaders/scene.slang | 123 +- Lorr/Engine/Resources/shaders/shadow.slang | 68 +- Lorr/Engine/Resources/shaders/sky.slang | 66 +- Lorr/Engine/Scene/ECSModule/CoreComponents.hh | 39 +- Lorr/Engine/Scene/GPUScene.hh | 129 +- Lorr/Engine/Scene/Scene.cc | 134 +- Lorr/Engine/Scene/Scene.hh | 2 +- Lorr/Engine/Scene/SceneRenderer.cc | 2627 +++++++++-------- Lorr/Engine/Scene/SceneRenderer.hh | 18 +- 23 files changed, 1727 insertions(+), 1712 deletions(-) diff --git a/Lorr/Editor/Window/SceneBrowserWindow.cc b/Lorr/Editor/Window/SceneBrowserWindow.cc index 516ecb67..9b7160bb 100755 --- a/Lorr/Editor/Window/SceneBrowserWindow.cc +++ b/Lorr/Editor/Window/SceneBrowserWindow.cc @@ -91,15 +91,6 @@ static auto draw_hierarchy(SceneBrowserWindow &self) -> void { created_entity.child_of(active_scene->get_root()); } - ImGui::Separator(); - - if (ImGui::MenuItem("Environment")) { - auto created_entity = active_scene->create_entity(); - created_entity.set({}); - created_entity.set({}); - created_entity.child_of(active_scene->get_root()); - } - ImGui::EndMenu(); } diff --git a/Lorr/Engine/Graphics/Slang/Compiler.cc b/Lorr/Engine/Graphics/Slang/Compiler.cc index a81a7ad1..f856cdac 100644 --- a/Lorr/Engine/Graphics/Slang/Compiler.cc +++ b/Lorr/Engine/Graphics/Slang/Compiler.cc @@ -358,7 +358,7 @@ auto SlangCompiler::new_session(const SlangSessionInfo &info) -> ls::optionglobal_session->findProfile("spirv_1_5"), .flags = SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY, - //.floatingPointMode = SLANG_FLOATING_POINT_MODE_FAST, + .floatingPointMode = SLANG_FLOATING_POINT_MODE_FAST, .lineDirectiveMode = SLANG_LINE_DIRECTIVE_MODE_STANDARD, .forceGLSLScalarBufferLayout = true, .compilerOptionEntries = entries, diff --git a/Lorr/Engine/Resources/shaders/passes/histogram_average.slang b/Lorr/Engine/Resources/shaders/passes/histogram_average.slang index 5e1475b6..bb0934a3 100644 --- a/Lorr/Engine/Resources/shaders/passes/histogram_average.slang +++ b/Lorr/Engine/Resources/shaders/passes/histogram_average.slang @@ -7,7 +7,7 @@ import scene; #include struct ShaderParameters { - ConstantBuffer environment; + ConstantBuffer eye_adaptation; StructuredBuffer histogram_bin_indices; RWStructuredBuffer luminance; @@ -40,15 +40,15 @@ func cs_main( } if (gid == 0) { - let exposure_range = params.environment.eye_max_exposure - params.environment.eye_min_exposure; - let time_coeff = clamp(1.0 - exp(-params.environment.eye_adaptation_speed * delta_time), 0.0f, 1.0f); + let exposure_range = params.eye_adaptation.max_exposure - params.eye_adaptation.min_exposure; + let time_coeff = clamp(1.0 - exp(-params.eye_adaptation.adaptation_speed * delta_time), 0.0f, 1.0f); let weighted_average_log2 = (histogram_shared[0] / max(pixel_count - count_for_this_bin, 1.0)) - 1.0; let desired_luminance = - exp2(((weighted_average_log2 / (HISTOGRAM_BIN_COUNT - 1)) * exposure_range) + params.environment.eye_min_exposure); + exp2(((weighted_average_log2 / (HISTOGRAM_BIN_COUNT - 1)) * exposure_range) + params.eye_adaptation.min_exposure); let last_luminance = params.luminance[0].adapted_luminance; let adapted_luminance = last_luminance + (desired_luminance - last_luminance) * time_coeff; - let ev100 = ev100_from_luminance(adapted_luminance, params.environment.eye_ISO_K); + let ev100 = ev100_from_luminance(adapted_luminance, params.eye_adaptation.ISO_K); let exposure = 1.0 / (exp2(ev100) * 1.2); params.luminance[0] = HistogramLuminance(adapted_luminance, exposure); } diff --git a/Lorr/Engine/Resources/shaders/passes/histogram_generate.slang b/Lorr/Engine/Resources/shaders/passes/histogram_generate.slang index a5857642..1d712c21 100644 --- a/Lorr/Engine/Resources/shaders/passes/histogram_generate.slang +++ b/Lorr/Engine/Resources/shaders/passes/histogram_generate.slang @@ -8,8 +8,7 @@ import scene; struct ShaderParameters { Image2D src_image; - - ConstantBuffer environment; + ConstantBuffer eye_adaptation; RWStructuredBuffer histogram_bin_indices; }; @@ -40,7 +39,7 @@ func cs_main( if (all(thread_id.xy < src_extent.xy)) { const f32x3 color = params.src_image.Load(u32x3(thread_id.xy, 0)).rgb; const f32 luminance = std::rec2020_to_xyz(color).y; - const u32 bin_index = bin_lum(luminance, params.environment.eye_max_exposure, params.environment.eye_min_exposure); + const u32 bin_index = bin_lum(luminance, params.eye_adaptation.max_exposure, params.eye_adaptation.min_exposure); __atomic_add(histogram_shared[bin_index], 1, MemoryOrder::AcquireRelease); } diff --git a/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang b/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang index 0e03d529..d0e132cb 100644 --- a/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang +++ b/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang @@ -20,13 +20,17 @@ struct ShaderParameters { Image2D metallic_roughness_occlusion_image; Texture2DArray directional_light_shadowmap; - ConstantBuffer environment; ConstantBuffer camera; }; ParameterBlock params; [[shader("fragment")]] -func fs_main(VertexOutput input) -> f32x4 { +func fs_main( + VertexOutput input, + uniform Atmosphere *atmosphere, + uniform DirectionalLight *directional_light, + uniform DirectionalLightCascade *directional_light_cascades +) -> f32x4 { let pixel_pos = u32x3(u32x2(input.position.xy), 0); let depth = params.depth_image.Load(pixel_pos); if (depth == 0.0) { @@ -51,10 +55,9 @@ func fs_main(VertexOutput input) -> f32x4 { let NDC = f32x3(input.tex_coord * 2.0 - 1.0, depth); let world_position_h = mul(params.camera.inv_projection_view_mat, f32x4(NDC, 1.0)); let world_position = world_position_h.xyz / world_position_h.w; - let directional_light = params.environment.lights.directional_light; // PBR constants - let L = directional_light.direction; + let L = directional_light->direction; let V = normalize(params.camera.position - world_position); let N = normalize(mapped_normal); let R = reflect(-V, N); @@ -63,24 +66,22 @@ func fs_main(VertexOutput input) -> f32x4 { var sun_illuminance = f32x3(1.0); var intersects_planet = false; - if ((params.environment.flags & EnvironmentFlags::HasSun) != 0u) { + if (atmosphere) { var eye_altitude = max(world_position.y, 0.0) * CAMERA_SCALE_UNIT; - eye_altitude += params.environment.atmos_planet_radius + PLANET_RADIUS_OFFSET; + eye_altitude += atmosphere->planet_radius + PLANET_RADIUS_OFFSET; let planet_intersection = std::ray_sphere_intersect_nearest( - f32x3(world_position.x, world_position.y + eye_altitude, world_position.z), L, params.environment.atmos_planet_radius); + f32x3(world_position.x, world_position.y + eye_altitude, world_position.z), L, atmosphere->planet_radius); intersects_planet = planet_intersection != -1.0; f32 sun_cos_theta = dot(L, f32x3(0.0, 1.0, 0.0)); f32x2 transmittance_uv = transmittance_params_to_lut_uv( - params.environment.atmos_atmos_radius, - params.environment.atmos_planet_radius, + atmosphere->atmos_radius, + atmosphere->planet_radius, f32x2(eye_altitude, sun_cos_theta)); f32x3 sun_transmittance = params.sky_transmittance_lut.SampleLevel(params.linear_clamp_sampler, transmittance_uv, 0.0).rgb; sun_illuminance = sun_transmittance * directional_light.intensity; - } - if ((params.environment.flags & EnvironmentFlags::HasAtmosphere) != 0u) { let reflection_dir = lerp(N, R, 1.0 - roughness); let specular_sample = params.sky_cubemap.SampleLevel(params.linear_clamp_sampler, reflection_dir, 0.0); let diffuse_sample = params.sky_cubemap.SampleLevel(params.linear_clamp_sampler, N, 0.0); @@ -114,6 +115,7 @@ func fs_main(VertexOutput input) -> f32x4 { let view_z = -mul(params.camera.view_mat, f32x4(world_position, 1.0)).z; let directional_shadow = sample_shadow_map( directional_light, + directional_light_cascades, params.directional_light_shadowmap, params.linear_clamp_sampler, view_z, world_position, N, L); @@ -123,7 +125,7 @@ func fs_main(VertexOutput input) -> f32x4 { } // FINAL ──────────────────────────────────────────────────────────── - let base_ambient_color = f32x3(0.04) * sun_illuminance; + let base_ambient_color = directional_light.base_ambient_color * sun_illuminance; let indirect_illuminance_sum = indirect_illuminance + (base_ambient_color * albedo_color * occlusion); let final_color = material_surface_color + indirect_illuminance_sum + emission; diff --git a/Lorr/Engine/Resources/shaders/passes/sky_aerial_perspective.slang b/Lorr/Engine/Resources/shaders/passes/sky_aerial_perspective.slang index 9467c18c..61719075 100644 --- a/Lorr/Engine/Resources/shaders/passes/sky_aerial_perspective.slang +++ b/Lorr/Engine/Resources/shaders/passes/sky_aerial_perspective.slang @@ -9,7 +9,7 @@ struct ShaderParameters { Sampler sampler; Image2D sky_transmittance_lut; Image2D sky_multiscattering_lut; - ConstantBuffer environment; + ConstantBuffer atmosphere; ConstantBuffer camera; StorageImage3D sky_aerial_perspective_lut; }; @@ -20,15 +20,13 @@ func cs_main( u32x3 thread_id : SV_DispatchThreadID, uniform ParameterBlock params ) -> void { - let lut_size = params.environment.aerial_perspective_lut_size; + let lut_size = params.atmosphere.aerial_perspective_lut_size; let uv = f32x2(f32x2(thread_id.xy) + 0.5) / f32x2(lut_size.xy); let NDC = f32x3(2.0 * uv - 1.0, 1.0); let world_pos_h = mul(params.camera.inv_projection_view_mat, f32x4(NDC, 1.0)); let world_pos = world_pos_h.xyz / world_pos_h.w; let world_dir = normalize(world_pos - params.camera.position); - var eye_altitude = params.camera.position.y * CAMERA_SCALE_UNIT; - eye_altitude += params.environment.atmos_planet_radius + PLANET_RADIUS_OFFSET; - var eye_pos = f32x3(0.0, eye_altitude, 0.0); + var eye_pos = f32x3(0.0, params.atmosphere.eye_height, 0.0); f32 slice = ((f32(thread_id.z) + 0.5) * (1.0 / lut_size.z)); slice *= slice; @@ -36,27 +34,16 @@ func cs_main( let step_count = int(max(1.0, f32(thread_id.z + 1.0) * 2.0)); let per_slice_depth = f32(lut_size.x / lut_size.z); - let start_depth = params.environment.atmos_aerial_perspective_start_km * INV_CAMERA_SCALE_UNIT; + let start_depth = params.atmosphere.aerial_perspective_start_km * INV_CAMERA_SCALE_UNIT; var t_max = slice * per_slice_depth; let start_pos = eye_pos + start_depth * world_dir; let ray_pos = start_pos + t_max * world_dir; - var view_height = 0.0; - /* - view_height = length(ray_pos); - if (view_height <= (C.atmosphere.planet_radius + PLANET_RADIUS_OFFSET)) { - ray_pos = normalize(ray_pos) * (C.atmosphere.planet_radius + PLANET_RADIUS_OFFSET); - world_dir = normalize(ray_pos - eye_pos); - t_max = length(ray_pos - eye_pos); - } - */ - f32 t_max_max = t_max; - view_height = eye_altitude; - if (view_height >= params.environment.atmos_atmos_radius) { + if (params.atmosphere.eye_height >= params.atmosphere.atmos_radius) { f32x3 prev_ray_pos = eye_pos; - if (!move_to_top_atmosphere(eye_pos, world_dir, params.environment.atmos_atmos_radius)) { + if (!move_to_top_atmosphere(eye_pos, world_dir, params.atmosphere.atmos_radius)) { params.sky_aerial_perspective_lut.Store(thread_id, 0.0); return; } @@ -69,18 +56,17 @@ func cs_main( t_max_max = max(0.0, t_max_max - length_to_atmosphere); } - let directional_light = params.environment.lights.directional_light; AtmosphereIntegrateInfo info = {}; info.eye_pos = eye_pos; info.eye_dir = world_dir; - info.sun_dir = directional_light.direction; - info.sun_intensity = directional_light.intensity; + info.sun_dir = params.atmosphere.sun_direction; + info.sun_intensity = params.atmosphere.sun_intensity; info.eval_planet_luminance = false; info.eval_multiscattering = true; info.step_count = max(1.0, (f32(thread_id.z) + 1.0) * 2.0); let result = integrate_single_scattered_luminance( - info, params.environment, params.sampler, params.sky_transmittance_lut, params.sky_multiscattering_lut); + info, params.atmosphere, params.sampler, params.sky_transmittance_lut, params.sky_multiscattering_lut); let inv_luminance = 1.0 / max(result.luminance, float3(1.0 / 1048576.0)); let inv_mult = min(1048576.0, max(inv_luminance.x, max(inv_luminance.y, inv_luminance.z))); diff --git a/Lorr/Engine/Resources/shaders/passes/sky_cubemap.slang b/Lorr/Engine/Resources/shaders/passes/sky_cubemap.slang index c0ef4bac..5689a378 100644 --- a/Lorr/Engine/Resources/shaders/passes/sky_cubemap.slang +++ b/Lorr/Engine/Resources/shaders/passes/sky_cubemap.slang @@ -11,7 +11,7 @@ typealias RWTextureCube sky_view_lut; - ConstantBuffer environment; + ConstantBuffer atmosphere; ConstantBuffer camera; RWTextureCube ibl_cube; }; @@ -93,11 +93,9 @@ func cs_main( let up = f32x3(0.0, 1.0, 0.0); var eye_altitude = params.camera.position.y * CAMERA_SCALE_UNIT; - eye_altitude += params.environment.atmos_planet_radius + PLANET_RADIUS_OFFSET; + eye_altitude += params.atmosphere.planet_radius + PLANET_RADIUS_OFFSET; let eye_pos = f32x3(0.0, eye_altitude, 0.0); - let directional_light = params.environment.lights.directional_light; - // these values are hardcoded let sample_count = 128; let subgroup_size = 32; @@ -109,15 +107,12 @@ func cs_main( rand_seed((i * subgroup_size + sg_thread_id + seed * sample_count)); let input_dir = rand_hemi_dir(output_dir); let result = get_atmosphere_illuminance_along_ray( - eye_pos, - input_dir, - directional_light.direction, - directional_light.intensity, - params.environment.atmos_atmos_radius, - params.environment.atmos_planet_radius, - params.environment.sky_view_lut_size.xy, + params.atmosphere, params.sampler, - params.sky_view_lut); + params.sky_view_lut, + eye_pos, + input_dir + ); let cos_weighed_result = result * dot(output_dir, input_dir); accumulated_result += std::subgroup_inclusive_add(cos_weighed_result); diff --git a/Lorr/Engine/Resources/shaders/passes/sky_final.slang b/Lorr/Engine/Resources/shaders/passes/sky_final.slang index eee43006..e2fa35f0 100644 --- a/Lorr/Engine/Resources/shaders/passes/sky_final.slang +++ b/Lorr/Engine/Resources/shaders/passes/sky_final.slang @@ -11,7 +11,7 @@ struct ShaderParameters { Image3D sky_aerial_perspective_lut; Image2D sky_view_lut; Image2D depth_image; - ConstantBuffer environment; + ConstantBuffer atmosphere; ConstantBuffer camera; }; @@ -53,7 +53,6 @@ func fs_main( f32x3 NDC = f32x3(input.tex_coord * 2.0 - 1.0, depth); f32x4 world_pos_h = mul(params.camera.inv_projection_view_mat, f32x4(NDC, 1.0)); f32x3 world_pos = world_pos_h.xyz / world_pos_h.w; - let directional_light = params.environment.lights.directional_light; f32x3 camera_pos = params.camera.position; if (depth != 0.0) { @@ -61,41 +60,37 @@ func fs_main( return sample_aerial_perspective( params.sky_aerial_perspective_lut, params.sampler, - params.environment.aerial_perspective_lut_size, + params.atmosphere.aerial_perspective_lut_size, input.tex_coord, camera_relative_pos, - params.environment.atmos_aerial_perspective_start_km); + params.atmosphere.aerial_perspective_start_km); } let up = f32x3(0.0, 1.0, 0.0); var eye_altitude = params.camera.position.y * CAMERA_SCALE_UNIT; - eye_altitude += params.environment.atmos_planet_radius + PLANET_RADIUS_OFFSET; + eye_altitude += params.atmosphere.planet_radius + PLANET_RADIUS_OFFSET; let eye_pos = f32x3(0.0, eye_altitude, 0.0); - var sun_dir = directional_light.direction; var eye_dir = normalize(world_pos - params.camera.position); - let planet_intersection = std::ray_sphere_intersect_nearest(eye_pos, eye_dir, params.environment.atmos_planet_radius); + let planet_intersection = std::ray_sphere_intersect_nearest(eye_pos, eye_dir, params.atmosphere.planet_radius); var color = get_atmosphere_illuminance_along_ray( - eye_pos, - eye_dir, - sun_dir, - directional_light.intensity, - params.environment.atmos_atmos_radius, - params.environment.atmos_planet_radius, - params.environment.sky_view_lut_size.xy, + params.atmosphere, params.sampler, - params.sky_view_lut); + params.sky_view_lut, + eye_pos, + eye_dir + ); - let sun_cos_theta = dot(sun_dir, up); + let sun_cos_theta = dot(params.atmosphere.sun_direction, up); let transmittance_uv = transmittance_params_to_lut_uv( - params.environment.atmos_atmos_radius, - params.environment.atmos_planet_radius, + params.atmosphere.atmos_radius, + params.atmosphere.planet_radius, f32x2(eye_altitude, sun_cos_theta)); let sun_transmittance = params.sky_transmittance_lut.SampleLevel(params.sampler, transmittance_uv, 0.0).rgb; if (planet_intersection == -1.0) { - color += draw_sun(eye_dir, sun_dir, 1.0) * directional_light.intensity * sun_transmittance; + color += draw_sun(eye_dir, params.atmosphere.sun_direction, 1.0) * params.atmosphere.sun_intensity * sun_transmittance; } return f32x4(color, 1.0); diff --git a/Lorr/Engine/Resources/shaders/passes/sky_multiscattering.slang b/Lorr/Engine/Resources/shaders/passes/sky_multiscattering.slang index 8aaa3f19..fc184dc7 100644 --- a/Lorr/Engine/Resources/shaders/passes/sky_multiscattering.slang +++ b/Lorr/Engine/Resources/shaders/passes/sky_multiscattering.slang @@ -10,7 +10,7 @@ import embedded.hemisphere; struct ShaderParameters { Sampler sampler; Image2D sky_transmittance_lut; - ConstantBuffer environment; + ConstantBuffer atmosphere; StorageImage2D sky_multiscattering_lut; }; @@ -27,11 +27,11 @@ func cs_main( uniform ParameterBlock params ) -> void { let i = f32(thread_id.z); - var uv = f32x2(f32x2(thread_id.xy) + 0.5) / f32x2(params.environment.multiscattering_lut_size.xy); - uv = from_sub_uvs_to_unit(uv, f32x2(params.environment.multiscattering_lut_size.xy)); + var uv = f32x2(f32x2(thread_id.xy) + 0.5) / f32x2(params.atmosphere.multiscattering_lut_size.xy); + uv = from_sub_uvs_to_unit(uv, f32x2(params.atmosphere.multiscattering_lut_size.xy)); - let atmosphere_thickness = params.environment.atmos_atmos_radius - params.environment.atmos_planet_radius; - let altitude = params.environment.atmos_planet_radius + uv.y * atmosphere_thickness + PLANET_RADIUS_OFFSET; + let atmosphere_thickness = params.atmosphere.atmos_radius - params.atmosphere.planet_radius; + let altitude = params.atmosphere.planet_radius + uv.y * atmosphere_thickness + PLANET_RADIUS_OFFSET; let sun_zenith_angle = uv.x * 2.0 - 1.0; let sun_dir = f32x3(0.0, sun_zenith_angle, std::safe_sqrt(saturate(1.0 - sun_zenith_angle * sun_zenith_angle))); @@ -52,7 +52,7 @@ func cs_main( info.eval_planet_luminance = true; info.eval_multiscattering = false; info.step_count = 32.0; - var result = integrate_single_scattered_luminance(info, params.environment, params.sampler, params.sky_transmittance_lut, params.sky_transmittance_lut); + var result = integrate_single_scattered_luminance(info, params.atmosphere, params.sampler, params.sky_transmittance_lut, params.sky_transmittance_lut); result.multiscattering_as_1 = WaveActiveSum(result.multiscattering_as_1); result.luminance = WaveActiveSum(result.luminance); diff --git a/Lorr/Engine/Resources/shaders/passes/sky_transmittance.slang b/Lorr/Engine/Resources/shaders/passes/sky_transmittance.slang index 5b158c3f..603cd044 100644 --- a/Lorr/Engine/Resources/shaders/passes/sky_transmittance.slang +++ b/Lorr/Engine/Resources/shaders/passes/sky_transmittance.slang @@ -7,7 +7,7 @@ import scene; struct ShaderParameters { StorageImage2D sky_transmittance_lut; - ConstantBuffer environment; + ConstantBuffer atmosphere; }; [[shader("compute")]] @@ -16,13 +16,13 @@ func cs_main( u32x2 thread_id : SV_DispatchThreadID, uniform ParameterBlock params ) -> void { - f32x2 uv = f32x2(f32x2(thread_id.xy) + 0.5) / f32x2(params.environment.transmittance_lut_size.xy); + f32x2 uv = f32x2(f32x2(thread_id.xy) + 0.5) / f32x2(params.atmosphere.transmittance_lut_size.xy); f32 h = std::safe_sqrt( - params.environment.atmos_atmos_radius * params.environment.atmos_atmos_radius - - params.environment.atmos_planet_radius * params.environment.atmos_planet_radius); + params.atmosphere.atmos_radius * params.atmosphere.atmos_radius + - params.atmosphere.planet_radius * params.atmosphere.planet_radius); f32 rho = h * uv.y; - f32 lut_x = sqrt(rho * rho + params.environment.atmos_planet_radius * params.environment.atmos_planet_radius); - f32 d_min = params.environment.atmos_atmos_radius - lut_x; + f32 lut_x = sqrt(rho * rho + params.atmosphere.planet_radius * params.atmosphere.planet_radius); + f32 d_min = params.atmosphere.atmos_radius - lut_x; f32 d_max = rho + h; f32 d = d_min + uv.x * (d_max - d_min); f32 lut_y = d == 0.0 ? 1.0 : (h * h - rho * rho - d * d) / (2.0 * lut_x * d); @@ -32,13 +32,13 @@ func cs_main( f32x3 ray_pos = f32x3(0.0, 0.0, lut_x); const f32 STEP_COUNT = 420.0; - f32 distance = std::ray_sphere_intersect_nearest(ray_pos, sun_dir, params.environment.atmos_atmos_radius); + f32 distance = std::ray_sphere_intersect_nearest(ray_pos, sun_dir, params.atmosphere.atmos_radius); f32 distance_per_step = distance / STEP_COUNT; f32x3 optical_depth = 0.0; for (f32 i = 0.0; i < STEP_COUNT; i += 1.0) { ray_pos += sun_dir * distance_per_step; - let ray_altitude = length(ray_pos) - params.environment.atmos_planet_radius; - let medium = MediumScattering(params.environment, ray_altitude); + let ray_altitude = length(ray_pos) - params.atmosphere.planet_radius; + let medium = MediumScattering(params.atmosphere, ray_altitude); optical_depth += medium.extinction_sum * distance_per_step; } diff --git a/Lorr/Engine/Resources/shaders/passes/sky_view.slang b/Lorr/Engine/Resources/shaders/passes/sky_view.slang index 45f4504f..b1a52d21 100644 --- a/Lorr/Engine/Resources/shaders/passes/sky_view.slang +++ b/Lorr/Engine/Resources/shaders/passes/sky_view.slang @@ -9,7 +9,7 @@ struct ShaderParameters { Sampler sampler; Image2D sky_transmittance_lut; Image2D sky_multiscattering_lut; - ConstantBuffer environment; + ConstantBuffer atmosphere; ConstantBuffer camera; StorageImage2D sky_view_lut; }; @@ -20,22 +20,19 @@ func cs_main( u32x2 thread_id : SV_DispatchThreadID, uniform ParameterBlock params ) -> void { - if (any(thread_id >= u32x2(params.environment.sky_view_lut_size.xy))) { + if (any(thread_id >= u32x2(params.atmosphere.sky_view_lut_size.xy))) { return; } - let uv = f32x2(thread_id.xy) / f32x2(params.environment.sky_view_lut_size.xy); - var eye_altitude = params.camera.position.y * CAMERA_SCALE_UNIT; - eye_altitude += params.environment.atmos_planet_radius + PLANET_RADIUS_OFFSET; - var eye_pos = f32x3(0.0, eye_altitude, 0.0); + let uv = f32x2(thread_id.xy) / f32x2(params.atmosphere.sky_view_lut_size.xy); + var eye_pos = f32x3(0.0, params.atmosphere.eye_height, 0.0); let sky_params = uv_to_sky_view_lut_params( - params.environment.atmos_atmos_radius, - params.environment.atmos_planet_radius, - params.environment.sky_view_lut_size.xy, + params.atmosphere.atmos_radius, + params.atmosphere.planet_radius, + params.atmosphere.sky_view_lut_size.xy, uv, - eye_altitude); + params.atmosphere.eye_height); - let directional_light = params.environment.lights.directional_light; let view_zenith_angle = sky_params.x; let light_view_angle = sky_params.y; let eye_dir = f32x3( @@ -44,25 +41,25 @@ func cs_main( sin(light_view_angle) * sin(view_zenith_angle), ); - if (!move_to_top_atmosphere(eye_pos, eye_dir, params.environment.atmos_atmos_radius)) { + if (!move_to_top_atmosphere(eye_pos, eye_dir, params.atmosphere.atmos_radius)) { params.sky_view_lut.Store(thread_id.xy, 0.0); return; } let up_vec = f32x3(0.0, 1.0, 0.0); - let sun_zenith_cos_angle = dot(directional_light.direction, up_vec); + let sun_zenith_cos_angle = dot(params.atmosphere.sun_direction, up_vec); let sun_dir = normalize(f32x3(std::safe_sqrt(1.0 - sun_zenith_cos_angle * sun_zenith_cos_angle), sun_zenith_cos_angle, 0.0)); AtmosphereIntegrateInfo info = {}; info.eye_pos = eye_pos; info.eye_dir = eye_dir; info.sun_dir = sun_dir; - info.sun_intensity = directional_light.intensity; + info.sun_intensity = params.atmosphere.sun_intensity; info.step_count = 32.0; info.eval_multiscattering = true; info.eval_planet_luminance = false; let result = integrate_single_scattered_luminance( - info, params.environment, params.sampler, params.sky_transmittance_lut, params.sky_multiscattering_lut); + info, params.atmosphere, params.sampler, params.sky_transmittance_lut, params.sky_multiscattering_lut); let inv_luminance = 1.0 / max(result.luminance, float3(1.0 / 1048576.0)); let inv_mult = min(1048576.0, max(inv_luminance.x, max(inv_luminance.y, inv_luminance.z))); diff --git a/Lorr/Engine/Resources/shaders/passes/tonemap.slang b/Lorr/Engine/Resources/shaders/passes/tonemap.slang index 13900b34..35174186 100644 --- a/Lorr/Engine/Resources/shaders/passes/tonemap.slang +++ b/Lorr/Engine/Resources/shaders/passes/tonemap.slang @@ -10,7 +10,6 @@ struct ShaderParameters { Sampler sampler; Image2D input_image; - ConstantBuffer environment; ConstantBuffer histogram_luminance; }; uniform ParameterBlock params; @@ -551,9 +550,12 @@ struct GT7ToneMapping }; [[shader("fragment")]] -f32x4 fs_main(VertexOutput input) { +func fs_main( + VertexOutput input, + uniform bool has_eye_adaptation +) -> f32x4 { f32x3 color = params.input_image.SampleLevel(params.sampler, input.tex_coord, 0.0).rgb; - if (params.environment.flags & EnvironmentFlags::HasEyeAdaptation) { + if (has_eye_adaptation) { let exposure = params.histogram_luminance.exposure; color = color * (exposure + 1.0); } diff --git a/Lorr/Engine/Resources/shaders/passes/vbgtao_denoise.slang b/Lorr/Engine/Resources/shaders/passes/vbgtao_denoise.slang index ab1bc591..0714aef1 100644 --- a/Lorr/Engine/Resources/shaders/passes/vbgtao_denoise.slang +++ b/Lorr/Engine/Resources/shaders/passes/vbgtao_denoise.slang @@ -3,10 +3,12 @@ import gpu; import scene; struct ShaderParameters { + Sampler point_clamp_sampler; Texture2D occlusion_noisy; Texture2D depth_differences; + ConstantBuffer vbgtao; + RWTexture2D ambient_occlusion; - Sampler point_clamp_sampler; }; [[shader("compute")]] @@ -15,7 +17,6 @@ func cs_main( u32x2 thread_id : SV_DispatchThreadID, uniform ParameterBlock params, uniform i32x3 occlusion_noisy_extent, - uniform f32 power ) -> void { let pixel_coordinates = i32x2(thread_id.xy); let uv = f32x2(pixel_coordinates) / f32x2(occlusion_noisy_extent.xy); @@ -76,7 +77,7 @@ func cs_main( sum_weight += bottom_right_weight; var denoised_visibility = sum / sum_weight; - denoised_visibility = pow(max(denoised_visibility, 0.0f), power); + denoised_visibility = pow(max(denoised_visibility, 0.0f), params.vbgtao.denoise_power); params.ambient_occlusion.Store(pixel_coordinates, denoised_visibility); } diff --git a/Lorr/Engine/Resources/shaders/passes/vbgtao_generate.slang b/Lorr/Engine/Resources/shaders/passes/vbgtao_generate.slang index b8022ac4..07029591 100644 --- a/Lorr/Engine/Resources/shaders/passes/vbgtao_generate.slang +++ b/Lorr/Engine/Resources/shaders/passes/vbgtao_generate.slang @@ -10,14 +10,16 @@ import scene; constexpr static let SECTOR_COUNT = 32u; struct ShaderParameters { - ConstantBuffer camera; + SamplerState point_clamp_sampler; + SamplerState linear_clamp_sampler; Texture2D prefiltered_depth; Texture2D normals; Texture2D hilbert_noise; + ConstantBuffer camera; + ConstantBuffer vbgtao; + RWTexture2D ambient_occlusion; RWTexture2D depth_differences; - SamplerState point_clamp_sampler; - SamplerState linear_clamp_sampler; }; f32 fast_sqrt(f32 x) { @@ -136,9 +138,8 @@ func calc_visibility_mask( func cs_main( const uint2 pixel_coordinates : SV_DispatchThreadID, uniform ParameterBlock params, - uniform VBGTAO settings ) -> void { - let effect_radius = settings.depth_range_scale_factor * settings.radius * settings.radius_multiplier; + let effect_radius = params.vbgtao.depth_range_scale_factor * params.vbgtao.radius * params.vbgtao.radius_multiplier; let mip_sampling_offset = 3.30; let inv_far = 1.0 / params.camera.far_clip; @@ -151,14 +152,14 @@ func cs_main( let normal = load_normal_view_space(uv, params.camera, params.normals, params.point_clamp_sampler); let linear_depth = -origin.z * inv_far; - let thickness = settings.thickness * saturate(linear_depth) * settings.linear_thickness_multiplier; + let thickness = params.vbgtao.thickness * saturate(linear_depth) * params.vbgtao.linear_thickness_multiplier; let noise = load_noise(pixel_coordinates, params.hilbert_noise); let sample_scale = -(0.5 * effect_radius * params.camera.projection_mat[0][0]) / origin.z; var visibility = 0.0; - for (var slice_t = 0.0; slice_t < settings.slice_count; slice_t += 1.0) { - let slice = (slice_t + noise.x) / settings.slice_count; + for (var slice_t = 0.0; slice_t < params.vbgtao.slice_count; slice_t += 1.0) { + let slice = (slice_t + noise.x) / params.vbgtao.slice_count; let phi = slice * PI; let omega = f32x2(cos(phi), sin(phi)); @@ -174,11 +175,11 @@ func cs_main( var bitmask = 0u; let sample_mul = f32x2(omega.x, -omega.y) * sample_scale; - for (var sample_t = 0.0; sample_t < settings.sample_count_per_slice; sample_t += 1.0) { - var sample = (slice + sample_t * settings.sample_count_per_slice) * 0.6180339887498948482; + for (var sample_t = 0.0; sample_t < params.vbgtao.sample_count_per_slice; sample_t += 1.0) { + var sample = (slice + sample_t * params.vbgtao.sample_count_per_slice) * 0.6180339887498948482; sample = fract(noise.y + sample); - var s = (sample_t + sample) / settings.sample_count_per_slice; + var s = (sample_t + sample) / params.vbgtao.sample_count_per_slice; s *= s; let sample_offset = s * sample_mul; @@ -201,6 +202,6 @@ func cs_main( visibility += 1.0 - f32(countbits(bitmask)) / f32(SECTOR_COUNT); } - let ao = saturate(visibility / settings.slice_count); + let ao = saturate(visibility / params.vbgtao.slice_count); params.ambient_occlusion[pixel_coordinates] = ao; } \ No newline at end of file diff --git a/Lorr/Engine/Resources/shaders/scene.slang b/Lorr/Engine/Resources/shaders/scene.slang index bb302a69..f40a7e51 100644 --- a/Lorr/Engine/Resources/shaders/scene.slang +++ b/Lorr/Engine/Resources/shaders/scene.slang @@ -3,8 +3,6 @@ module scene; import std; import gpu; -#include - public const static f32 CAMERA_SCALE_UNIT = 0.01; public const static f32 INV_CAMERA_SCALE_UNIT = 1.0 / CAMERA_SCALE_UNIT; public const static f32 PLANET_RADIUS_OFFSET = 0.001; @@ -32,20 +30,33 @@ public enum CullFlags : u32 { MicroTriangles, }; - +public struct Camera { + public mat4 projection_mat; + public mat4 inv_projection_mat; + public mat4 view_mat; + public mat4 inv_view_mat; + public mat4 projection_view_mat; + public mat4 inv_projection_view_mat; + public f32x3 position; + public f32x2 resolution; + public f32 near_clip; + public f32 far_clip; + public f32 acceptable_lod_error; + public f32 fov_deg; + public f32 aspect_ratio; +}; #ifndef MAX_DIRECTIONAL_LIGHT_CASCADES #define MAX_DIRECTIONAL_LIGHT_CASCADES 6 #endif -public struct DirectionalLight { - public struct Cascade { - public f32x4x4 projection_view_mat; - public f32 far_bound; - public f32 texel_size; - }; +public struct DirectionalLightCascade { + public f32x4x4 projection_view_mat; + public f32 far_bound; + public f32 texel_size; +}; - public Cascade cascades[MAX_DIRECTIONAL_LIGHT_CASCADES]; +public struct DirectionalLight { public f32x3 base_ambient_color; public f32 intensity; public f32x3 direction; @@ -57,40 +68,27 @@ public struct DirectionalLight { }; public struct Lights { - public DirectionalLight directional_light; -}; - -public enum EnvironmentFlags : u32 { - None = 0, - HasSun = 1 << 0, - HasAtmosphere = 1 << 1, - HasEyeAdaptation = 1 << 2, - HasVBGTAO = 1 << 3, + public DirectionalLight *directional_light; + public DirectionalLightCascade *directional_light_cascades; }; -public struct Environment { - public EnvironmentFlags flags; - // Sun - public Lights lights; - // Atmosphere - public f32x3 atmos_rayleigh_scatter; - public f32 atmos_rayleigh_density; - public f32x3 atmos_mie_scatter; - public f32 atmos_mie_density; - public f32 atmos_mie_extinction; - public f32 atmos_mie_asymmetry; - public f32x3 atmos_ozone_absorption; - public f32 atmos_ozone_height; - public f32 atmos_ozone_thickness; - public f32x3 atmos_terrain_albedo; - public f32 atmos_planet_radius; - public f32 atmos_atmos_radius; - public f32 atmos_aerial_perspective_start_km; - // Eye adaptation - public f32 eye_min_exposure; - public f32 eye_max_exposure; - public f32 eye_adaptation_speed; - public f32 eye_ISO_K; +public struct Atmosphere { + public f32x3 sun_direction; + public f32 eye_height; + public f32x3 rayleigh_scatter; + public f32 rayleigh_density; + public f32x3 mie_scatter; + public f32 mie_density; + public f32 mie_extinction; + public f32 mie_asymmetry; + public f32x3 ozone_absorption; + public f32 ozone_height; + public f32 ozone_thickness; + public f32x3 terrain_albedo; + public f32 planet_radius; + public f32 atmos_radius; + public f32 aerial_perspective_start_km; + public f32 sun_intensity; public i32x3 transmittance_lut_size; public i32x3 sky_view_lut_size; @@ -98,20 +96,22 @@ public struct Environment { public i32x3 aerial_perspective_lut_size; }; -public struct Camera { - public mat4 projection_mat; - public mat4 inv_projection_mat; - public mat4 view_mat; - public mat4 inv_view_mat; - public mat4 projection_view_mat; - public mat4 inv_projection_view_mat; - public f32x3 position; - public f32x2 resolution; - public f32 near_clip; - public f32 far_clip; - public f32 acceptable_lod_error; - public f32 fov_deg; - public f32 aspect_ratio; +public struct EyeAdaptation { + public f32 min_exposure; + public f32 max_exposure; + public f32 adaptation_speed; + public f32 ISO_K; +}; + +public struct VBGTAO { + public f32 thickness; + public f32 depth_range_scale_factor; + public f32 radius; + public f32 radius_multiplier; + public f32 slice_count; + public f32 sample_count_per_slice; + public f32 denoise_power; + public f32 linear_thickness_multiplier; }; public struct Transform { @@ -333,14 +333,3 @@ public struct Mesh { public MeshLOD lods[MESH_MAX_LODS] = {}; public Bounds bounds = {}; }; - -public struct VBGTAO { - public f32 thickness; - public f32 depth_range_scale_factor; - public f32 radius; - public f32 radius_multiplier; - public f32 slice_count; - public f32 sample_count_per_slice; - public f32 denoise_power; - public f32 linear_thickness_multiplier; -}; diff --git a/Lorr/Engine/Resources/shaders/shadow.slang b/Lorr/Engine/Resources/shaders/shadow.slang index a759b889..1e5909b5 100644 --- a/Lorr/Engine/Resources/shaders/shadow.slang +++ b/Lorr/Engine/Resources/shaders/shadow.slang @@ -32,32 +32,30 @@ public func cascade_debug_visualization( ); } -public func get_cascade_index(in DirectionalLight light, f32 view_z) -> u32 { - for (var i = 0u; i < light.cascade_count; i++) { - if (view_z < light.cascades[i].far_bound) { +public func get_cascade_index(DirectionalLightCascade *cascades, u32 cascade_count, f32 view_z) -> u32 { + for (var i = 0u; i < cascade_count; i++) { + if (view_z < cascades[i].far_bound) { return i; } } - return light.cascade_count - 1; + return cascade_count - 1; } -func sample_cascade( - in DirectionalLight light, - Texture2DArray shadow_map, - SamplerState shadow_sampler, +func get_cascade_sample_info( + in DirectionalLightCascade cascade, f32x3 world_position, f32x3 normal, f32x3 light_direction, - u32 cascade_index -) -> f32 { - let cascade = light.cascades[cascade_index]; - let normal_offset = light.normal_bias * cascade.texel_size * normal; - let depth_offset = light.depth_bias * light_direction; + f32 normal_bias, + f32 depth_bias +) -> f32x4 { + let normal_offset = normal_bias * cascade.texel_size * normal; + let depth_offset = depth_bias * light_direction; let offset_position = f32x3(world_position.xyz + normal_offset + depth_offset); let offset_position_clip = mul(cascade.projection_view_mat, f32x4(offset_position, 1.0)); if (offset_position_clip.w <= 0.0) { - return 1.0; + return 0.0; } let offset_position_ndc = offset_position_clip.xyz / offset_position_clip.w; @@ -66,17 +64,15 @@ func sample_cascade( any(offset_position_ndc.xy > 1.0) || offset_position_ndc.z < 0.0 || offset_position_ndc.z > 1.0) { - return 1.0; + return 0.0; } - let light_local = f32x3(offset_position_ndc.xy * 0.5 + 0.5, offset_position_ndc.z); - let shadow_depth = shadow_map.SampleLevel(shadow_sampler, f32x3(light_local.xy, cascade_index), 0.0); - - return light_local.z < shadow_depth ? 0.0 : 1.0; + return f32x4(offset_position_ndc.xy * 0.5 + 0.5, offset_position_ndc.z, 1.0); } public func sample_shadow_map( - in DirectionalLight light, + DirectionalLight *light, + DirectionalLightCascade *cascades, Texture2DArray shadow_map, SamplerState shadow_sampler, f32 view_z, @@ -84,32 +80,36 @@ public func sample_shadow_map( f32x3 normal, f32x3 light_direction ) -> f32 { - let cascade_index = get_cascade_index(light, view_z); - - var shadow = sample_cascade( - light, - shadow_map, - shadow_sampler, + let cascade_index = get_cascade_index(cascades, light.cascade_count, view_z); + let this_cascade = cascades[cascade_index]; + let this_cascade_sample_info = get_cascade_sample_info( + this_cascade, world_position, normal, light_direction, - cascade_index); + light.normal_bias, + light.depth_bias); + + let shadow_depth = shadow_map.SampleLevel(shadow_sampler, f32x3(this_cascade_sample_info.xy, cascade_index), 0.0); + var shadow = this_cascade_sample_info.z < shadow_depth ? 0.0 : 1.0; let next_cascade_index = cascade_index + 1u; if (next_cascade_index < light.cascade_count) { - let this_far_bound = light.cascades[cascade_index].far_bound; + let this_far_bound = this_cascade.far_bound; let next_near_bound = (1.0 - light.cascades_overlap_proportion) * this_far_bound; if (view_z >= next_near_bound) { - var next_shadow = sample_cascade( - light, - shadow_map, - shadow_sampler, + let next_cascade = cascades[next_cascade_index]; + let next_cascade_sample_info = get_cascade_sample_info( + next_cascade, world_position, normal, light_direction, - next_cascade_index); + light.normal_bias, + light.depth_bias); - shadow = lerp(shadow, next_shadow, (view_z - next_near_bound) / (this_far_bound - next_near_bound)); + let next_shadow_depth = shadow_map.SampleLevel(shadow_sampler, f32x3(next_cascade_sample_info.xy, next_cascade_index), 0.0); + let next_cascade_shadow = next_cascade_sample_info.z < next_shadow_depth ? 0.0 : 1.0; + shadow = lerp(shadow, next_cascade_shadow, (view_z - next_near_bound) / (this_far_bound - next_near_bound)); } } return shadow; diff --git a/Lorr/Engine/Resources/shaders/sky.slang b/Lorr/Engine/Resources/shaders/sky.slang index 6b38af99..9e816f95 100644 --- a/Lorr/Engine/Resources/shaders/sky.slang +++ b/Lorr/Engine/Resources/shaders/sky.slang @@ -123,17 +123,17 @@ public struct MediumScattering { public f32x3 extinction_sum; [ForceInline] - public __init(in Environment environment, f32 altitude) { - let mie_density = exp(-altitude / environment.atmos_mie_density); - let rayleigh_density = exp(-altitude / environment.atmos_rayleigh_density); - let ozone_density = max(0.0, 1.0 - abs(altitude - environment.atmos_ozone_height) / environment.atmos_ozone_thickness); + public __init(in Atmosphere atmosphere, f32 altitude) { + let mie_density = exp(-altitude / atmosphere.mie_density); + let rayleigh_density = exp(-altitude / atmosphere.rayleigh_density); + let ozone_density = max(0.0, 1.0 - abs(altitude - atmosphere.ozone_height) / atmosphere.ozone_thickness); - let mie_extinction = environment.atmos_mie_extinction * mie_density; - let rayleigh_extinction = environment.atmos_rayleigh_scatter * rayleigh_density; - let ozone_extinction = environment.atmos_ozone_absorption * ozone_density; + let mie_extinction = atmosphere.mie_extinction * mie_density; + let rayleigh_extinction = atmosphere.rayleigh_scatter * rayleigh_density; + let ozone_extinction = atmosphere.ozone_absorption * ozone_density; - this.mie_scattering = environment.atmos_mie_scatter * mie_density; - this.rayleigh_scattering = environment.atmos_rayleigh_scatter * rayleigh_density; + this.mie_scattering = atmosphere.mie_scatter * mie_density; + this.rayleigh_scattering = atmosphere.rayleigh_scatter * rayleigh_density; this.extinction_sum = mie_extinction + rayleigh_extinction + ozone_extinction; } }; @@ -155,19 +155,19 @@ public struct AtmosphereIntegrateInfo { public func integrate_single_scattered_luminance( in AtmosphereIntegrateInfo info, - in Environment environment, + in Atmosphere atmosphere, in Sampler lut_sampler, in Image2D transmittance_image = {}, in Image2D multiscattering_image = {} ) -> AtmosphereLuminance { AtmosphereLuminance result = {}; - if (dot(info.eye_pos, info.eye_pos) <= environment.atmos_planet_radius * environment.atmos_planet_radius) { + if (dot(info.eye_pos, info.eye_pos) <= atmosphere.planet_radius * atmosphere.planet_radius) { return result; } - let planet_intersection = std::ray_sphere_intersect_nearest(info.eye_pos, info.eye_dir, environment.atmos_planet_radius); - let atmos_intersection = std::ray_sphere_intersect_nearest(info.eye_pos, info.eye_dir, environment.atmos_atmos_radius); + let planet_intersection = std::ray_sphere_intersect_nearest(info.eye_pos, info.eye_dir, atmosphere.planet_radius); + let atmos_intersection = std::ray_sphere_intersect_nearest(info.eye_pos, info.eye_dir, atmosphere.atmos_radius); var integration_length = 0.0; if (atmos_intersection == -1.0) { // No intersection @@ -181,7 +181,7 @@ public func integrate_single_scattered_luminance( let cos_theta = dot(info.sun_dir, info.eye_dir); let rayleigh_phase = std::rayleigh_phase(cos_theta); - let mie_phase = std::henyey_greenstein_draine_phase(environment.atmos_mie_asymmetry, cos_theta); + let mie_phase = std::henyey_greenstein_draine_phase(atmosphere.mie_asymmetry, cos_theta); var transmittance_sum = f32x3(1.0); var step_length = integration_length / info.step_count; @@ -194,26 +194,26 @@ public func integrate_single_scattered_luminance( let step_pos = info.eye_pos + new_ray_shift * info.eye_dir; let h = length(step_pos); - let altitude = h - environment.atmos_planet_radius; - let medium_info = MediumScattering(environment, altitude); + let altitude = h - atmosphere.planet_radius; + let medium_info = MediumScattering(atmosphere, altitude); let scattering_sum = medium_info.rayleigh_scattering + medium_info.mie_scattering; let up = normalize(step_pos); let sun_theta = dot(info.sun_dir, up); let transmittance_uv = transmittance_params_to_lut_uv( - environment.atmos_atmos_radius, environment.atmos_planet_radius, f32x2(h, sun_theta)); + atmosphere.atmos_radius, atmosphere.planet_radius, f32x2(h, sun_theta)); let sun_transmittance = transmittance_image.SampleLevel(lut_sampler, transmittance_uv, 0.0).rgb; var MS = f32x3(0.0); if (info.eval_multiscattering) { let multiscatter_uv = multiscattering_params_to_lut_uv( - environment.atmos_atmos_radius, environment.atmos_planet_radius, environment.multiscattering_lut_size.xy, altitude, sun_theta); + atmosphere.atmos_radius, atmosphere.planet_radius, atmosphere.multiscattering_lut_size.xy, altitude, sun_theta); MS = multiscattering_image.SampleLevel(lut_sampler, multiscatter_uv, 0.0).rgb; } var scattering_phase = medium_info.mie_scattering * mie_phase + medium_info.rayleigh_scattering * rayleigh_phase; - let earth_shadow = std::ray_sphere_intersect_nearest(step_pos, info.sun_dir, environment.atmos_planet_radius) == -1.0 ? 1.0 : 0.0; + let earth_shadow = std::ray_sphere_intersect_nearest(step_pos, info.sun_dir, atmosphere.planet_radius) == -1.0 ? 1.0 : 0.0; let sun_luminance = earth_shadow * sun_transmittance * scattering_phase + (MS * scattering_sum); let step_transmittance = exp(-step_length * medium_info.extinction_sum); @@ -237,10 +237,10 @@ public func integrate_single_scattered_luminance( let sun_theta = dot(info.sun_dir, up); let NoL = saturate(dot(normalize(info.sun_dir), normalize(up))); - let transmittance_uv = transmittance_params_to_lut_uv(environment.atmos_atmos_radius, environment.atmos_planet_radius, f32x2(h, sun_theta)); + let transmittance_uv = transmittance_params_to_lut_uv(atmosphere.atmos_radius, atmosphere.planet_radius, f32x2(h, sun_theta)); let sun_transmittance = transmittance_image.SampleLevel(lut_sampler, transmittance_uv, 0.0).rgb; - result.luminance += sun_transmittance * transmittance_sum * NoL * environment.atmos_terrain_albedo / PI; + result.luminance += sun_transmittance * transmittance_sum * NoL * atmosphere.terrain_albedo / PI; } return result; @@ -284,27 +284,23 @@ public func sample_aerial_perspective( } public func get_atmosphere_illuminance_along_ray( - f32x3 eye_pos, - f32x3 eye_dir, - f32x3 sun_dir, - f32 sun_intensity, - f32 atmos_radius, - f32 planet_radius, - i32x2 sky_view_lut_size, + in Atmosphere atmosphere, in Sampler sampler, - in Image2D sky_view_image + in Image2D sky_view_image, + f32x3 eye_pos, + f32x3 eye_dir ) -> f32x3 { let height = length(eye_pos); let view_zenith_cos_angle = acos(dot(eye_dir, f32x3(0.0, 1.0, 0.0))); let light_view_cos_angle = acos(clamp(dot( - normalize(f32x3(sun_dir.x, 0.0, sun_dir.z)), + normalize(f32x3(atmosphere.sun_direction.x, 0.0, atmosphere.sun_direction.z)), normalize(f32x3(eye_dir.x, 0.0, eye_dir.z)), ), -1.0, 1.0)); - let planet_intersection = std::ray_sphere_intersect_nearest(eye_pos, eye_dir, planet_radius); + let planet_intersection = std::ray_sphere_intersect_nearest(eye_pos, eye_dir, atmosphere.planet_radius); let uv = sky_view_params_to_lut_uv( - atmos_radius, - planet_radius, - sky_view_lut_size, + atmosphere.atmos_radius, + atmosphere.planet_radius, + atmosphere.sky_view_lut_size.xy, planet_intersection != -1.0, height, view_zenith_cos_angle, @@ -312,5 +308,5 @@ public func get_atmosphere_illuminance_along_ray( let result = sky_view_image.SampleLevel(sampler, uv, 0.0); let atmos_luminance = result.rgb * result.a; - return atmos_luminance * sun_intensity; + return atmos_luminance * atmosphere.sun_intensity; } diff --git a/Lorr/Engine/Scene/ECSModule/CoreComponents.hh b/Lorr/Engine/Scene/ECSModule/CoreComponents.hh index 34cb767a..7897b21e 100644 --- a/Lorr/Engine/Scene/ECSModule/CoreComponents.hh +++ b/Lorr/Engine/Scene/ECSModule/CoreComponents.hh @@ -42,25 +42,26 @@ ECS_COMPONENT_BEGIN(RenderingMesh) ECS_COMPONENT_MEMBER(mesh_index, u32, {}) ECS_COMPONENT_END(); -ECS_COMPONENT_BEGIN(Environment) - ECS_COMPONENT_MEMBER(atmos, bool, true) - ECS_COMPONENT_MEMBER(atmos_rayleigh_scattering, glm::vec3, { 5.802f, 13.558f, 33.100f }) - ECS_COMPONENT_MEMBER(atmos_rayleigh_density, f32, 8.0) - ECS_COMPONENT_MEMBER(atmos_mie_scattering, glm::vec3, { 3.996f, 3.996f, 3.996f }) - ECS_COMPONENT_MEMBER(atmos_mie_density, f32, 1.2f) - ECS_COMPONENT_MEMBER(atmos_mie_extinction, f32, 4.44f) - ECS_COMPONENT_MEMBER(atmos_mie_asymmetry, f32, 3.6f) - ECS_COMPONENT_MEMBER(atmos_ozone_absorption, glm::vec3, { 0.650f, 1.881f, 0.085f }) - ECS_COMPONENT_MEMBER(atmos_ozone_height, f32, 25.0f) - ECS_COMPONENT_MEMBER(atmos_ozone_thickness, f32, 15.0f) - ECS_COMPONENT_MEMBER(atmos_terrain_albedo, glm::vec3, { 0.3f, 0.3f, 0.3f }) - ECS_COMPONENT_MEMBER(atmos_aerial_perspective_start_km, f32, 8.0f) - ECS_COMPONENT_MEMBER(eye_adaptation, bool, true) - ECS_COMPONENT_MEMBER(eye_min_exposure, f32, -6.0f) - ECS_COMPONENT_MEMBER(eye_max_exposure, f32, 18.0f) - ECS_COMPONENT_MEMBER(eye_adaptation_speed, f32, 1.1f) - ECS_COMPONENT_MEMBER(eye_iso, f32, 100.0f) - ECS_COMPONENT_MEMBER(eye_k, f32, 12.5f) +ECS_COMPONENT_BEGIN(Atmosphere) + ECS_COMPONENT_MEMBER(rayleigh_scattering, glm::vec3, { 5.802f, 13.558f, 33.100f }) + ECS_COMPONENT_MEMBER(rayleigh_density, f32, 8.0) + ECS_COMPONENT_MEMBER(mie_scattering, glm::vec3, { 3.996f, 3.996f, 3.996f }) + ECS_COMPONENT_MEMBER(mie_density, f32, 1.2f) + ECS_COMPONENT_MEMBER(mie_extinction, f32, 4.44f) + ECS_COMPONENT_MEMBER(mie_asymmetry, f32, 3.6f) + ECS_COMPONENT_MEMBER(ozone_absorption, glm::vec3, { 0.650f, 1.881f, 0.085f }) + ECS_COMPONENT_MEMBER(ozone_height, f32, 25.0f) + ECS_COMPONENT_MEMBER(ozone_thickness, f32, 15.0f) + ECS_COMPONENT_MEMBER(terrain_albedo, glm::vec3, { 0.3f, 0.3f, 0.3f }) + ECS_COMPONENT_MEMBER(aerial_perspective_start_km, f32, 8.0f) +ECS_COMPONENT_END(); + +ECS_COMPONENT_BEGIN(EyeAdaptation) + ECS_COMPONENT_MEMBER(min_exposure, f32, -6.0f) + ECS_COMPONENT_MEMBER(max_exposure, f32, 18.0f) + ECS_COMPONENT_MEMBER(adaptation_speed, f32, 1.1f) + ECS_COMPONENT_MEMBER(ISO, f32, 100.0f) + ECS_COMPONENT_MEMBER(K, f32, 12.5f) ECS_COMPONENT_END(); ECS_COMPONENT_BEGIN(DirectionalLight) diff --git a/Lorr/Engine/Scene/GPUScene.hh b/Lorr/Engine/Scene/GPUScene.hh index 3b8d331d..3a96dd9f 100644 --- a/Lorr/Engine/Scene/GPUScene.hh +++ b/Lorr/Engine/Scene/GPUScene.hh @@ -65,15 +65,35 @@ enum class CullFlags : u32 { All = ~0_u32, }; +constexpr static f32 CAMERA_SCALE_UNIT = 0.01; +constexpr static f32 INV_CAMERA_SCALE_UNIT = 1.0 / CAMERA_SCALE_UNIT; +constexpr static f32 PLANET_RADIUS_OFFSET = 0.001; + +struct Camera { + alignas(4) glm::mat4 projection_mat = {}; + alignas(4) glm::mat4 inv_projection_mat = {}; + alignas(4) glm::mat4 view_mat = {}; + alignas(4) glm::mat4 inv_view_mat = {}; + alignas(4) glm::mat4 projection_view_mat = {}; + alignas(4) glm::mat4 inv_projection_view_mat = {}; + alignas(4) glm::vec3 position = {}; + alignas(4) glm::vec2 resolution = {}; + alignas(4) f32 near_clip = 0.01f; + alignas(4) f32 far_clip = 1000.0f; + alignas(4) f32 acceptable_lod_error = 0.0f; + alignas(4) f32 fov_deg = 65.0f; + alignas(4) f32 aspect_ratio = 1.777f; +}; + +struct DirectionalLightCascade { + alignas(4) glm::mat4 projection_view_mat = {}; + alignas(4) f32 far_bound = {}; + alignas(4) f32 texel_size = {}; +}; + struct DirectionalLight { - constexpr static auto MAX_DIRECTIONAL_LIGHT_CASCADES = 6_sz; - struct Cascade { - alignas(4) glm::mat4 projection_view_mat = {}; - alignas(4) f32 far_bound = {}; - alignas(4) f32 texel_size = {}; - }; - - alignas(4) Cascade cascades[MAX_DIRECTIONAL_LIGHT_CASCADES] = {}; + constexpr static auto MAX_CASCADE_COUNT = 6_u32; + alignas(4) glm::vec3 base_ambient_color = {}; alignas(4) f32 intensity = {}; alignas(4) glm::vec3 direction = {}; @@ -85,39 +105,27 @@ struct DirectionalLight { }; struct Lights { - alignas(4) DirectionalLight directional_light = {}; -}; - -enum EnvironmentFlags : u32 { - None = 0, - HasSun = 1 << 0, - HasAtmosphere = 1 << 1, - HasEyeAdaptation = 1 << 2, + alignas(8) u64 directional_light = 0; + alignas(8) u64 directional_light_cascades = 0; }; -struct Environment { - alignas(4) u32 flags = EnvironmentFlags::None; - // Sun - alignas(4) Lights lights = {}; - // Atmosphere - alignas(4) glm::vec3 atmos_rayleigh_scatter = { 0.005802f, 0.014338f, 0.032800f }; - alignas(4) f32 atmos_rayleigh_density = 8.0f; - alignas(4) glm::vec3 atmos_mie_scatter = { 0.003996f, 0.003996f, 0.003996f }; - alignas(4) f32 atmos_mie_density = 1.2f; - alignas(4) f32 atmos_mie_extinction = 0.004440f; - alignas(4) f32 atmos_mie_asymmetry = 3.5f; - alignas(4) glm::vec3 atmos_ozone_absorption = { 0.000650f, 0.001781f, 0.000085f }; - alignas(4) f32 atmos_ozone_height = 25.0f; - alignas(4) f32 atmos_ozone_thickness = 15.0f; - alignas(4) glm::vec3 atmos_terrain_albedo = { 0.3f, 0.3f, 0.3f }; - alignas(4) f32 atmos_planet_radius = 6360.0f; - alignas(4) f32 atmos_atmos_radius = 6460.0f; - alignas(4) f32 atmos_aerial_perspective_start_km = 8.0f; - // Eye adaptation - alignas(4) f32 eye_min_exposure = -6.0f; - alignas(4) f32 eye_max_exposure = 18.0f; - alignas(4) f32 eye_adaptation_speed = 1.1f; - alignas(4) f32 eye_ISO_K = 100.0f / 12.5f; +struct Atmosphere { + alignas(4) glm::vec3 sun_direction = {}; + alignas(4) f32 eye_height = {}; + alignas(4) glm::vec3 rayleigh_scatter = { 0.005802f, 0.014338f, 0.032800f }; + alignas(4) f32 rayleigh_density = 8.0f; + alignas(4) glm::vec3 mie_scatter = { 0.003996f, 0.003996f, 0.003996f }; + alignas(4) f32 mie_density = 1.2f; + alignas(4) f32 mie_extinction = 0.004440f; + alignas(4) f32 mie_asymmetry = 3.5f; + alignas(4) glm::vec3 ozone_absorption = { 0.000650f, 0.001781f, 0.000085f }; + alignas(4) f32 ozone_height = 25.0f; + alignas(4) f32 ozone_thickness = 15.0f; + alignas(4) glm::vec3 terrain_albedo = { 0.3f, 0.3f, 0.3f }; + alignas(4) f32 planet_radius = 6360.0f; + alignas(4) f32 atmos_radius = 6460.0f; + alignas(4) f32 aerial_perspective_start_km = 8.0f; + alignas(4) f32 sun_intensity = {}; alignas(4) vuk::Extent3D transmittance_lut_size = {}; alignas(4) vuk::Extent3D sky_view_lut_size = {}; @@ -125,24 +133,22 @@ struct Environment { alignas(4) vuk::Extent3D aerial_perspective_lut_size = {}; }; -constexpr static f32 CAMERA_SCALE_UNIT = 0.01; -constexpr static f32 INV_CAMERA_SCALE_UNIT = 1.0 / CAMERA_SCALE_UNIT; -constexpr static f32 PLANET_RADIUS_OFFSET = 0.001; +struct EyeAdaptation { + alignas(4) f32 min_exposure = -6.0f; + alignas(4) f32 max_exposure = 18.0f; + alignas(4) f32 adaptation_speed = 1.1f; + alignas(4) f32 ISO_K = 100.0f / 12.5f; +}; -struct Camera { - alignas(4) glm::mat4 projection_mat = {}; - alignas(4) glm::mat4 inv_projection_mat = {}; - alignas(4) glm::mat4 view_mat = {}; - alignas(4) glm::mat4 inv_view_mat = {}; - alignas(4) glm::mat4 projection_view_mat = {}; - alignas(4) glm::mat4 inv_projection_view_mat = {}; - alignas(4) glm::vec3 position = {}; - alignas(4) glm::vec2 resolution = {}; - alignas(4) f32 near_clip = 0.01f; - alignas(4) f32 far_clip = 1000.0f; - alignas(4) f32 acceptable_lod_error = 0.0f; - alignas(4) f32 fov_deg = 65.0f; - alignas(4) f32 aspect_ratio = 1.777f; +struct VBGTAO { + alignas(4) f32 thickness = 0.25f; + alignas(4) f32 depth_range_scale_factor = 0.75f; + alignas(4) f32 radius = 0.5f; + alignas(4) f32 radius_multiplier = 1.457f; + alignas(4) f32 slice_count = 3.0f; + alignas(4) f32 sample_count_per_slice = 3.0f; + alignas(4) f32 denoise_power = 1.1f; + alignas(4) f32 linear_thickness_multiplier = 300.0f; }; enum class TransformID : u64 { Invalid = ~0_u64 }; @@ -249,15 +255,4 @@ struct HistogramLuminance { alignas(4) f32 exposure = 0.0f; }; -struct VBGTAO { - alignas(4) f32 thickness = 0.25f; - alignas(4) f32 depth_range_scale_factor = 0.75f; - alignas(4) f32 radius = 0.5f; - alignas(4) f32 radius_multiplier = 1.457f; - alignas(4) f32 slice_count = 3.0f; - alignas(4) f32 sample_count_per_slice = 3.0f; - alignas(4) f32 denoise_power = 1.1f; - alignas(4) f32 linear_thickness_multiplier = 300.0f; -}; - } // namespace lr::GPU diff --git a/Lorr/Engine/Scene/Scene.cc b/Lorr/Engine/Scene/Scene.cc index de4d5753..4bf4f90c 100644 --- a/Lorr/Engine/Scene/Scene.cc +++ b/Lorr/Engine/Scene/Scene.cc @@ -59,13 +59,13 @@ auto get_frustum_corners(f32 fov, f32 aspect_ratio, f32 z_near, f32 z_far) -> st } auto calculate_cascade_bounds(usize cascade_count, f32 nearest_bound, f32 maximum_shadow_distance) - -> std::array { + -> std::array { if (cascade_count == 1) { return { maximum_shadow_distance }; } auto base = glm::pow(maximum_shadow_distance / nearest_bound, 1.0f / static_cast(cascade_count - 1)); - auto result = std::array(); + auto result = std::array(); for (u32 i = 0; i < cascade_count; i++) { result[i] = nearest_bound * glm::pow(base, static_cast(i)); } @@ -73,12 +73,17 @@ auto calculate_cascade_bounds(usize cascade_count, f32 nearest_bound, f32 maximu return result; } -auto calculate_cascaded_shadow_matrices(GPU::DirectionalLight &light, const ECS::DirectionalLight &light_comp, const GPU::Camera &camera) -> void { +auto calculate_cascaded_shadow_matrices( + GPU::DirectionalLight &light, + ls::span cascades, + const ECS::DirectionalLight &light_comp, + const GPU::Camera &camera +) -> void { ZoneScoped; auto overlap_factor = 1.0f - light_comp.cascade_overlap_propotion; auto far_bounds = calculate_cascade_bounds(light.cascade_count, light_comp.first_cascade_far_bound, light_comp.maximum_shadow_distance); - auto near_bounds = std::array(); + auto near_bounds = std::array(); near_bounds[0] = light_comp.minimum_shadow_distance; for (u32 i = 1; i < light.cascade_count; i++) { near_bounds[i] = overlap_factor * far_bounds[i - 1]; @@ -94,7 +99,7 @@ auto calculate_cascaded_shadow_matrices(GPU::DirectionalLight &light, const ECS: auto camera_to_world = camera.inv_view_mat; for (u32 cascade_index = 0; cascade_index < light.cascade_count; ++cascade_index) { - auto &cascade = light.cascades[cascade_index]; + auto &cascade = cascades[cascade_index]; auto split_near = near_bounds[cascade_index]; auto split_far = far_bounds[cascade_index]; auto corners = get_frustum_corners(camera.fov_deg, camera.aspect_ratio, -split_near, -split_far); @@ -679,18 +684,21 @@ auto Scene::prepare_frame(this Scene &self, SceneRenderer &renderer, u32 image_c auto camera_query = self.get_world() .query_builder() .build(); - auto rendering_meshes_query = self.get_world() - .query_builder() - .build(); auto directional_light_query = self.get_world() .query_builder() .build(); - auto environment_query = self.get_world() - .query_builder() + auto atmosphere_query = self.get_world() + .query_builder() + .build(); + auto eye_adaptation_query = self.get_world() + .query_builder() .build(); auto vbgtao_query = self.get_world() .query_builder() .build(); + auto rendering_meshes_query = self.get_world() + .query_builder() + .build(); // clang-format on ls::option active_camera_data = override_camera; @@ -725,31 +733,12 @@ auto Scene::prepare_frame(this Scene &self, SceneRenderer &renderer, u32 image_c }); } - GPU::Environment environment = {}; - environment_query.each([&environment](flecs::entity, const ECS::Environment &environment_comp) { - environment.flags |= environment_comp.atmos ? GPU::EnvironmentFlags::HasAtmosphere : 0; - environment.flags |= environment_comp.eye_adaptation ? GPU::EnvironmentFlags::HasEyeAdaptation : 0; - - environment.atmos_rayleigh_scatter = environment_comp.atmos_rayleigh_scattering * 1e-3f; - environment.atmos_rayleigh_density = environment_comp.atmos_rayleigh_density; - environment.atmos_mie_scatter = environment_comp.atmos_mie_scattering * 1e-3f; - environment.atmos_mie_density = environment_comp.atmos_mie_density; - environment.atmos_mie_extinction = environment_comp.atmos_mie_extinction * 1e-3f; - environment.atmos_mie_asymmetry = environment_comp.atmos_mie_asymmetry; - environment.atmos_ozone_absorption = environment_comp.atmos_ozone_absorption * 1e-3f; - environment.atmos_ozone_height = environment_comp.atmos_ozone_height; - environment.atmos_ozone_thickness = environment_comp.atmos_ozone_thickness; - environment.atmos_terrain_albedo = environment_comp.atmos_terrain_albedo; - environment.atmos_aerial_perspective_start_km = environment_comp.atmos_aerial_perspective_start_km; - environment.eye_min_exposure = environment_comp.eye_min_exposure; - environment.eye_max_exposure = environment_comp.eye_max_exposure; - environment.eye_adaptation_speed = environment_comp.eye_adaptation_speed; - environment.eye_ISO_K = environment_comp.eye_iso / environment_comp.eye_k; - }); - - directional_light_query.each([&environment, &active_camera_data](flecs::entity, const ECS::DirectionalLight &directional_light_comp) { - auto &directional_light = environment.lights.directional_light; - environment.flags |= GPU::EnvironmentFlags::HasSun; + ls::option directional_light_data = ls::nullopt; + GPU::DirectionalLightCascade directional_light_cascades[GPU::DirectionalLight::MAX_CASCADE_COUNT] = {}; + directional_light_query.each([&directional_light_data, + &directional_light_cascades, + &active_camera_data](flecs::entity, const ECS::DirectionalLight &directional_light_comp) { + auto &directional_light = directional_light_data.emplace(); auto light_dir_rad = glm::radians(directional_light_comp.direction); directional_light.direction = { @@ -759,32 +748,70 @@ auto Scene::prepare_frame(this Scene &self, SceneRenderer &renderer, u32 image_c }; directional_light.base_ambient_color = directional_light_comp.base_ambient_color; directional_light.intensity = directional_light_comp.intensity; - directional_light.cascade_count = directional_light_comp.cascade_count; + directional_light.cascade_count = ls::min(directional_light_comp.cascade_count, GPU::DirectionalLight::MAX_CASCADE_COUNT); directional_light.cascade_size = directional_light_comp.shadow_map_resolution; directional_light.cascades_overlap_proportion = directional_light_comp.cascade_overlap_propotion; directional_light.depth_bias = directional_light_comp.depth_bias; directional_light.normal_bias = directional_light_comp.normal_bias; if (directional_light_comp.cascade_count > 0) { - calculate_cascaded_shadow_matrices(directional_light, directional_light_comp, *active_camera_data); + calculate_cascaded_shadow_matrices(directional_light, directional_light_cascades, directional_light_comp, *active_camera_data); } }); + ls::option atmosphere_data = ls::nullopt; + atmosphere_query.each([&atmosphere_data, &active_camera_data, &directional_light_data](flecs::entity, const ECS::Atmosphere &atmos_comp) { + auto &atmosphere = atmosphere_data.emplace(); + atmosphere.rayleigh_scatter = atmos_comp.rayleigh_scattering * 1e-3f; + atmosphere.rayleigh_density = atmos_comp.rayleigh_density; + atmosphere.mie_scatter = atmos_comp.mie_scattering * 1e-3f; + atmosphere.mie_density = atmos_comp.mie_density; + atmosphere.mie_extinction = atmos_comp.mie_extinction * 1e-3f; + atmosphere.mie_asymmetry = atmos_comp.mie_asymmetry; + atmosphere.ozone_absorption = atmos_comp.ozone_absorption * 1e-3f; + atmosphere.ozone_height = atmos_comp.ozone_height; + atmosphere.ozone_thickness = atmos_comp.ozone_thickness; + atmosphere.terrain_albedo = atmos_comp.terrain_albedo; + atmosphere.aerial_perspective_start_km = atmos_comp.aerial_perspective_start_km; + + if (active_camera_data.has_value()) { + atmosphere.eye_height = active_camera_data->position.y * GPU::CAMERA_SCALE_UNIT; + atmosphere.eye_height += atmosphere.planet_radius + GPU::PLANET_RADIUS_OFFSET; + } + + if (directional_light_data.has_value()) { + atmosphere.sun_direction = directional_light_data->direction; + atmosphere.sun_intensity = directional_light_data->intensity; + } + }); + + ls::option eye_adaptation_data = ls::nullopt; + eye_adaptation_query.each([&eye_adaptation_data](flecs::entity, const ECS::EyeAdaptation &eye_adaptation_comp) { + auto &eye_adaptation = eye_adaptation_data.emplace(); + eye_adaptation.min_exposure = eye_adaptation_comp.min_exposure; + eye_adaptation.max_exposure = eye_adaptation_comp.max_exposure; + eye_adaptation.adaptation_speed = eye_adaptation_comp.adaptation_speed; + eye_adaptation.ISO_K = eye_adaptation_comp.ISO / eye_adaptation_comp.K; + }); + auto regenerate_sky = false; - regenerate_sky |= self.last_environment.atmos_rayleigh_scatter != environment.atmos_rayleigh_scatter; - regenerate_sky |= self.last_environment.atmos_rayleigh_density != environment.atmos_rayleigh_density; - regenerate_sky |= self.last_environment.atmos_mie_scatter != environment.atmos_mie_scatter; - regenerate_sky |= self.last_environment.atmos_mie_density != environment.atmos_mie_density; - regenerate_sky |= self.last_environment.atmos_mie_extinction != environment.atmos_mie_extinction; - regenerate_sky |= self.last_environment.atmos_mie_asymmetry != environment.atmos_mie_asymmetry; - regenerate_sky |= self.last_environment.atmos_ozone_absorption != environment.atmos_ozone_absorption; - regenerate_sky |= self.last_environment.atmos_ozone_height != environment.atmos_ozone_height; - regenerate_sky |= self.last_environment.atmos_ozone_thickness != environment.atmos_ozone_thickness; - regenerate_sky |= self.last_environment.atmos_terrain_albedo != environment.atmos_terrain_albedo; - regenerate_sky |= self.last_environment.atmos_atmos_radius != environment.atmos_atmos_radius; - regenerate_sky |= self.last_environment.atmos_planet_radius != environment.atmos_planet_radius; - regenerate_sky |= self.last_environment.atmos_terrain_albedo != environment.atmos_terrain_albedo; - self.last_environment = environment; + if (atmosphere_data.has_value()) { + auto &atmosphere = atmosphere_data.value(); + regenerate_sky |= self.last_atmosphere.rayleigh_scatter != atmosphere.rayleigh_scatter; + regenerate_sky |= self.last_atmosphere.rayleigh_density != atmosphere.rayleigh_density; + regenerate_sky |= self.last_atmosphere.mie_scatter != atmosphere.mie_scatter; + regenerate_sky |= self.last_atmosphere.mie_density != atmosphere.mie_density; + regenerate_sky |= self.last_atmosphere.mie_extinction != atmosphere.mie_extinction; + regenerate_sky |= self.last_atmosphere.mie_asymmetry != atmosphere.mie_asymmetry; + regenerate_sky |= self.last_atmosphere.ozone_absorption != atmosphere.ozone_absorption; + regenerate_sky |= self.last_atmosphere.ozone_height != atmosphere.ozone_height; + regenerate_sky |= self.last_atmosphere.ozone_thickness != atmosphere.ozone_thickness; + regenerate_sky |= self.last_atmosphere.terrain_albedo != atmosphere.terrain_albedo; + regenerate_sky |= self.last_atmosphere.atmos_radius != atmosphere.atmos_radius; + regenerate_sky |= self.last_atmosphere.planet_radius != atmosphere.planet_radius; + regenerate_sky |= self.last_atmosphere.terrain_albedo != atmosphere.terrain_albedo; + self.last_atmosphere = atmosphere; + } auto vbgtao = ls::option(); vbgtao_query.each([&vbgtao](flecs::entity, ECS::VBGTAO &vbgtao_comp) { @@ -911,8 +938,11 @@ auto Scene::prepare_frame(this Scene &self, SceneRenderer &renderer, u32 image_c .gpu_materials = self.gpu_materials, .gpu_meshes = gpu_meshes, .gpu_mesh_instances = gpu_mesh_instances, - .environment = environment, .camera = active_camera_data.value_or(GPU::Camera{}), + .directional_light = directional_light_data, + .directional_light_cascades = directional_light_cascades, + .atmosphere = atmosphere_data, + .eye_adaptation = eye_adaptation_data, .vbgtao = vbgtao, }; auto prepared_frame = renderer.prepare_frame(prepare_info); diff --git a/Lorr/Engine/Scene/Scene.hh b/Lorr/Engine/Scene/Scene.hh index df384f4a..f42e5284 100644 --- a/Lorr/Engine/Scene/Scene.hh +++ b/Lorr/Engine/Scene/Scene.hh @@ -47,7 +47,7 @@ private: GPU::CullFlags cull_flags = GPU::CullFlags::All; - GPU::Environment last_environment = {}; + GPU::Atmosphere last_atmosphere = {}; public: auto init(this Scene &, const std::string &name) -> bool; diff --git a/Lorr/Engine/Scene/SceneRenderer.cc b/Lorr/Engine/Scene/SceneRenderer.cc index 93ea5d99..c0664e1e 100644 --- a/Lorr/Engine/Scene/SceneRenderer.cc +++ b/Lorr/Engine/Scene/SceneRenderer.cc @@ -27,1371 +27,1385 @@ static constexpr auto hiz_sampler_info = vuk::SamplerCreateInfo{ .addressModeV = vuk::SamplerAddressMode::eClampToEdge, }; -auto SceneRenderer::init(this SceneRenderer &self) -> bool { +static auto cull_meshes( + TransferManager &transfer_man, + GPU::CullFlags cull_flags, + u32 mesh_instance_count, + glm::mat4 &frustum_projection_view, + glm::vec3 &observer_position, + glm::vec2 &observer_resolution, + f32 acceptable_lod_error, + vuk::Value &meshes_buffer, + vuk::Value &mesh_instances_buffer, + vuk::Value &meshlet_instances_buffer, + vuk::Value &visible_meshlet_instances_count_buffer, + vuk::Value &transforms_buffer, + vuk::Value &debug_drawer_buffer +) -> vuk::Value { ZoneScoped; - auto &device = App::mod(); - auto &bindless_descriptor_set = device.get_descriptor_set(); - auto &asset_man = App::mod(); - auto shaders_root = asset_man.asset_root_path(AssetType::Shader); + auto vis_cull_meshes_pass = vuk::make_pass( + "vis cull meshes", + [cull_flags, mesh_instance_count, frustum_projection_view, observer_position, observer_resolution, acceptable_lod_error]( + vuk::CommandBuffer &cmd_list, + VUK_BA(vuk::eComputeRead) meshes, + VUK_BA(vuk::eComputeRead) transforms, + VUK_BA(vuk::eComputeRW) mesh_instances, + VUK_BA(vuk::eComputeRW) meshlet_instances, + VUK_BA(vuk::eComputeRW) visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRW) debug_drawer + ) { + cmd_list // + .bind_compute_pipeline("passes.cull_meshes") + .bind_buffer(0, 0, meshes) + .bind_buffer(0, 1, transforms) + .bind_buffer(0, 2, mesh_instances) + .bind_buffer(0, 3, meshlet_instances) + .bind_buffer(0, 4, visible_meshlet_instances_count) + .bind_buffer(0, 5, debug_drawer) + .push_constants( + vuk::ShaderStageFlagBits::eCompute, + 0, + PushConstants( + mesh_instance_count, + cull_flags, + frustum_projection_view, + observer_position, + glm::max(observer_resolution.x, observer_resolution.y), + acceptable_lod_error + ) + ) + .dispatch_invocations(mesh_instance_count); - // ── EDITOR ────────────────────────────────────────────────────────── - auto default_slang_session = device.new_slang_session({ - .definitions = { -#ifdef LS_DEBUG - { "ENABLE_ASSERTIONS", "1" }, - { "DEBUG_DRAW", "1" }, -#endif // DEBUG - { "CULLING_MESH_COUNT", "64" }, - { "CULLING_MESHLET_COUNT", std::to_string(Model::MAX_MESHLET_INDICES) }, - { "CULLING_TRIANGLE_COUNT", std::to_string(Model::MAX_MESHLET_PRIMITIVES) }, - { "MESH_MAX_LODS", std::to_string(GPU::Mesh::MAX_LODS) }, - { "MAX_DIRECTIONAL_LIGHT_CASCADES", std::to_string(GPU::DirectionalLight::MAX_DIRECTIONAL_LIGHT_CASCADES) }, - { "HISTOGRAM_THREADS_X", std::to_string(GPU::HISTOGRAM_THREADS_X) }, - { "HISTOGRAM_THREADS_Y", std::to_string(GPU::HISTOGRAM_THREADS_Y) }, - }, - .root_directory = shaders_root, - }).value(); - auto editor_grid_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.editor_grid", - .entry_points = { "vs_main", "fs_main" }, - }; - Pipeline::create(device, default_slang_session, editor_grid_pipeline_info).value(); + return std::make_tuple(meshes, transforms, mesh_instances, meshlet_instances, visible_meshlet_instances_count, debug_drawer); + } + ); - auto editor_mousepick_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.editor_mousepick", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, editor_mousepick_pipeline_info).value(); - // ── SKY ───────────────────────────────────────────────────────────── - auto sky_transmittance_lut_info = ImageInfo{ - .format = vuk::Format::eR16G16B16A16Sfloat, - .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, - .type = vuk::ImageType::e2D, - .extent = { .width = 256, .height = 64, .depth = 1 }, - .name = "Sky Transmittance", - }; - std::tie(self.sky_transmittance_lut, self.sky_transmittance_lut_view) = Image::create_with_view(device, sky_transmittance_lut_info).value(); - auto sky_transmittance_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.sky_transmittance", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, sky_transmittance_pipeline_info).value(); + std::tie( + meshes_buffer, + transforms_buffer, + mesh_instances_buffer, + meshlet_instances_buffer, + visible_meshlet_instances_count_buffer, + debug_drawer_buffer + ) = + vis_cull_meshes_pass( + std::move(meshes_buffer), + std::move(transforms_buffer), + std::move(mesh_instances_buffer), + std::move(meshlet_instances_buffer), + std::move(visible_meshlet_instances_count_buffer), + std::move(debug_drawer_buffer) + ); - auto sky_multiscatter_lut_info = ImageInfo{ - .format = vuk::Format::eR16G16B16A16Sfloat, - .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, - .type = vuk::ImageType::e2D, - .extent = { .width = 64, .height = 64, .depth = 1 }, - .name = "Sky Multiscatter LUT", - }; - std::tie(self.sky_multiscatter_lut, self.sky_multiscatter_lut_view) = Image::create_with_view(device, sky_multiscatter_lut_info).value(); - auto sky_multiscatter_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.sky_multiscattering", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, sky_multiscatter_pipeline_info).value(); + auto generate_cull_commands_pass = vuk::make_pass( + "generate cull commands", + [](vuk::CommandBuffer &cmd_list, // + VUK_BA(vuk::eComputeRead) visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRW) cull_meshlets_cmd) { + cmd_list // + .bind_compute_pipeline("passes.generate_cull_commands") + .bind_buffer(0, 0, visible_meshlet_instances_count) + .bind_buffer(0, 1, cull_meshlets_cmd) + .dispatch(1); - auto sky_view_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.sky_view", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, sky_view_pipeline_info).value(); + return std::make_tuple(visible_meshlet_instances_count, cull_meshlets_cmd); + } + ); - auto sky_aerial_perspective_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.sky_aerial_perspective", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, sky_aerial_perspective_pipeline_info).value(); + auto cull_meshlets_cmd_buffer = transfer_man.scratch_buffer({ .x = 0, .y = 1, .z = 1 }); + std::tie(visible_meshlet_instances_count_buffer, cull_meshlets_cmd_buffer) = + generate_cull_commands_pass(std::move(visible_meshlet_instances_count_buffer), std::move(cull_meshlets_cmd_buffer)); - auto sky_final_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.sky_final", - .entry_points = { "vs_main", "fs_main" }, - }; - Pipeline::create(device, default_slang_session, sky_final_pipeline_info).value(); + return cull_meshlets_cmd_buffer; +} - auto sky_cubemap_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.sky_cubemap", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, sky_cubemap_pipeline_info).value(); +static auto cull_meshlets( + TransferManager &transfer_man, + bool late, + GPU::CullFlags cull_flags, + f32 near_clip, + glm::vec2 &resolution, + glm::mat4 &projection_view, + vuk::Value &hiz_attachment, + vuk::Value &cull_meshlets_cmd_buffer, + vuk::Value &visible_meshlet_instances_count_buffer, + vuk::Value &early_visible_meshlet_instances_count_buffer, + vuk::Value &late_visible_meshlet_instances_count_buffer, + vuk::Value &visible_meshlet_instances_indices_buffer, + vuk::Value &meshlet_instance_visibility_mask_buffer, + vuk::Value &reordered_indices_buffer, + vuk::Value &meshes_buffer, + vuk::Value &mesh_instances_buffer, + vuk::Value &meshlet_instances_buffer, + vuk::Value &transforms_buffer +) -> vuk::Value { + ZoneScoped; + memory::ScopedStack stack; - // ── VISBUFFER ─────────────────────────────────────────────────────── - auto generate_cull_commands_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.generate_cull_commands", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, generate_cull_commands_pipeline_info).value(); + // ── CULL MESHLETS ─────────────────────────────────────────────────── + auto vis_cull_meshlets_pass = vuk::make_pass( + stack.format("vis cull meshlets {}", late ? "late" : "early"), + [late, cull_flags, near_clip, projection_view]( + vuk::CommandBuffer &cmd_list, + VUK_BA(vuk::eIndirectRead) dispatch_cmd, + VUK_BA(vuk::eComputeRead) meshlet_instances, + VUK_BA(vuk::eComputeRead) mesh_instances, + VUK_BA(vuk::eComputeRead) meshes, + VUK_BA(vuk::eComputeRead) transforms, + VUK_IA(vuk::eComputeSampled) hiz, + VUK_BA(vuk::eComputeRead) visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRW) early_visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRW) late_visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRW) visible_meshlet_instances_indices, + VUK_BA(vuk::eComputeRW) meshlet_instance_visibility_mask, + VUK_BA(vuk::eComputeRW) cull_triangles_cmd + ) { + cmd_list // + .bind_compute_pipeline("passes.cull_meshlets") + .bind_buffer(0, 0, meshlet_instances) + .bind_buffer(0, 1, mesh_instances) + .bind_buffer(0, 2, meshes) + .bind_buffer(0, 3, transforms) + .bind_image(0, 4, hiz) + .bind_sampler(0, 5, hiz_sampler_info) + .bind_buffer(0, 6, visible_meshlet_instances_count) + .bind_buffer(0, 7, early_visible_meshlet_instances_count) + .bind_buffer(0, 8, late_visible_meshlet_instances_count) + .bind_buffer(0, 9, visible_meshlet_instances_indices) + .bind_buffer(0, 10, meshlet_instance_visibility_mask) + .bind_buffer(0, 11, cull_triangles_cmd) + .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(cull_flags, near_clip, projection_view)) + .specialize_constants(0, late) + .dispatch_indirect(dispatch_cmd); - auto vis_cull_meshes_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.cull_meshes", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, vis_cull_meshes_pipeline_info).value(); + return std::make_tuple( + dispatch_cmd, + meshlet_instances, + mesh_instances, + meshes, + transforms, + hiz, + visible_meshlet_instances_count, + early_visible_meshlet_instances_count, + late_visible_meshlet_instances_count, + visible_meshlet_instances_indices, + meshlet_instance_visibility_mask, + cull_triangles_cmd + ); + } + ); - auto vis_cull_meshlets_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.cull_meshlets", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, vis_cull_meshlets_pipeline_info).value(); + auto cull_triangles_cmd_buffer = transfer_man.scratch_buffer({ .x = 0, .y = 1, .z = 1 }); - auto vis_cull_triangles_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.cull_triangles", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, vis_cull_triangles_pipeline_info).value(); - - auto vis_encode_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.visbuffer_encode", - .entry_points = { "vs_main", "fs_main" }, - }; - Pipeline::create(device, default_slang_session, vis_encode_pipeline_info, bindless_descriptor_set).value(); - - auto vis_clear_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.visbuffer_clear", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, vis_clear_pipeline_info).value(); - - auto vis_decode_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.visbuffer_decode", - .entry_points = { "vs_main", "fs_main" }, - }; - Pipeline::create(device, default_slang_session, vis_decode_pipeline_info, bindless_descriptor_set).value(); - - auto shadowmap_cull_meshlets = PipelineCompileInfo{ - .module_name = "passes.shadowmap_cull_meshlets", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, shadowmap_cull_meshlets).value(); - - auto shadowmap_draw = PipelineCompileInfo{ - .module_name = "passes.shadowmap_draw", - .entry_points = { "vs_main", "fs_main" }, - }; - Pipeline::create(device, default_slang_session, shadowmap_draw).value(); - - // ── PBR ───────────────────────────────────────────────────────────── - auto pbr_apply_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.pbr_apply", - .entry_points = { "vs_main", "fs_main" }, - }; - Pipeline::create(device, default_slang_session, pbr_apply_pipeline_info).value(); - - // ── POST PROCESS ──────────────────────────────────────────────────── - auto histogram_generate_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.histogram_generate", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, histogram_generate_pipeline_info).value(); - - auto histogram_average_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.histogram_average", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, histogram_average_pipeline_info).value(); - - auto tonemap_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.tonemap", - .entry_points = { "vs_main", "fs_main" }, - }; - Pipeline::create(device, default_slang_session, tonemap_pipeline_info).value(); - - auto debug_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.debug", - .entry_points = { "vs_main", "fs_main" }, - }; - Pipeline::create(device, default_slang_session, debug_pipeline_info).value(); - - auto copy_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.copy", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, copy_pipeline_info).value(); + std::tie( + cull_meshlets_cmd_buffer, + meshlet_instances_buffer, + mesh_instances_buffer, + meshes_buffer, + transforms_buffer, + hiz_attachment, + visible_meshlet_instances_count_buffer, + early_visible_meshlet_instances_count_buffer, + late_visible_meshlet_instances_count_buffer, + visible_meshlet_instances_indices_buffer, + meshlet_instance_visibility_mask_buffer, + cull_triangles_cmd_buffer + ) = + vis_cull_meshlets_pass( + std::move(cull_meshlets_cmd_buffer), + std::move(meshlet_instances_buffer), + std::move(mesh_instances_buffer), + std::move(meshes_buffer), + std::move(transforms_buffer), + std::move(hiz_attachment), + std::move(visible_meshlet_instances_count_buffer), + std::move(early_visible_meshlet_instances_count_buffer), + std::move(late_visible_meshlet_instances_count_buffer), + std::move(visible_meshlet_instances_indices_buffer), + std::move(meshlet_instance_visibility_mask_buffer), + std::move(cull_triangles_cmd_buffer) + ); - auto hiz_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.hiz", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, hiz_pipeline_info).value(); + // ── CULL TRIANGLES ────────────────────────────────────────────────── + auto vis_cull_triangles_pass = vuk::make_pass( + stack.format("vis cull triangles {}", late ? "late" : "early"), + [late, cull_flags, resolution, projection_view]( + vuk::CommandBuffer &cmd_list, + VUK_BA(vuk::eIndirectRead) cull_triangles_cmd, + VUK_BA(vuk::eComputeRead) early_visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRead) visible_meshlet_instances_indices, + VUK_BA(vuk::eComputeRead) meshlet_instances, + VUK_BA(vuk::eComputeRead) mesh_instances, + VUK_BA(vuk::eComputeRead) meshes, + VUK_BA(vuk::eComputeRead) transforms, + VUK_BA(vuk::eComputeRW) draw_indexed_cmd, + VUK_BA(vuk::eComputeWrite) reordered_indices + ) { + cmd_list // + .bind_compute_pipeline("passes.cull_triangles") + .bind_buffer(0, 0, early_visible_meshlet_instances_count) + .bind_buffer(0, 1, visible_meshlet_instances_indices) + .bind_buffer(0, 2, meshlet_instances) + .bind_buffer(0, 3, mesh_instances) + .bind_buffer(0, 4, meshes) + .bind_buffer(0, 5, transforms) + .bind_buffer(0, 6, draw_indexed_cmd) + .bind_buffer(0, 7, reordered_indices) + .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(cull_flags, resolution, projection_view)) + .specialize_constants(0, late) + .dispatch_indirect(cull_triangles_cmd); - auto hiz_slow_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.hiz_slow", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, hiz_slow_pipeline_info).value(); + return std::make_tuple( + early_visible_meshlet_instances_count, + visible_meshlet_instances_indices, + meshlet_instances, + mesh_instances, + meshes, + transforms, + draw_indexed_cmd, + reordered_indices + ); + } + ); - auto visualize_overdraw_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.visualize_overdraw", - .entry_points = { "vs_main", "fs_main" }, - }; - Pipeline::create(device, default_slang_session, visualize_overdraw_pipeline_info).value(); + auto draw_command_buffer = transfer_man.scratch_buffer({ .instanceCount = 1 }); - auto vbgtao_prefilter_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.vbgtao_prefilter", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, vbgtao_prefilter_pipeline_info).value(); + std::tie( + early_visible_meshlet_instances_count_buffer, + visible_meshlet_instances_indices_buffer, + meshlet_instances_buffer, + mesh_instances_buffer, + meshes_buffer, + transforms_buffer, + draw_command_buffer, + reordered_indices_buffer + ) = + vis_cull_triangles_pass( + std::move(cull_triangles_cmd_buffer), + std::move(early_visible_meshlet_instances_count_buffer), + std::move(visible_meshlet_instances_indices_buffer), + std::move(meshlet_instances_buffer), + std::move(mesh_instances_buffer), + std::move(meshes_buffer), + std::move(transforms_buffer), + std::move(draw_command_buffer), + std::move(reordered_indices_buffer) + ); - auto vbgtao_generate_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.vbgtao_generate", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, vbgtao_generate_pipeline_info).value(); + return draw_command_buffer; +} - auto vbgtao_denoise_pipeline_info = PipelineCompileInfo{ - .module_name = "passes.vbgtao_denoise", - .entry_points = { "cs_main" }, - }; - Pipeline::create(device, default_slang_session, vbgtao_denoise_pipeline_info).value(); +static auto draw_visbuffer( + bool late, + glm::mat4 &projection_view, + vuk::PersistentDescriptorSet &descriptor_set, + vuk::Value &depth_attachment, + vuk::Value &visbuffer_attachment, + vuk::Value &overdraw_attachment, + vuk::Value &draw_command_buffer, + vuk::Value &reordered_indices_buffer, + vuk::Value &meshes_buffer, + vuk::Value &mesh_instances_buffer, + vuk::Value &meshlet_instances_buffer, + vuk::Value &transforms_buffer, + vuk::Value &materials_buffer +) -> void { + ZoneScoped; + memory::ScopedStack stack; - self.histogram_luminance_buffer = Buffer::create(device, sizeof(GPU::HistogramLuminance)).value(); - vuk::fill(vuk::acquire_buf("histogram luminance", *device.buffer(self.histogram_luminance_buffer.id()), vuk::eNone), 0); + auto vis_encode_pass = vuk::make_pass( + stack.format("vis encode {}", late ? "late" : "early"), + [&descriptor_set, projection_view]( + vuk::CommandBuffer &cmd_list, + VUK_BA(vuk::eIndirectRead) triangle_indirect, + VUK_BA(vuk::eIndexRead) index_buffer, + VUK_BA(vuk::eVertexRead) meshlet_instances, + VUK_BA(vuk::eVertexRead) mesh_instances, + VUK_BA(vuk::eVertexRead) meshes, + VUK_BA(vuk::eVertexRead) transforms, + VUK_BA(vuk::eFragmentRead) materials, + VUK_IA(vuk::eColorRW) visbuffer, + VUK_IA(vuk::eDepthStencilRW) depth, + VUK_IA(vuk::eFragmentRW) overdraw + ) { + cmd_list // + .bind_graphics_pipeline("passes.visbuffer_encode") + .set_rasterization({ .cullMode = vuk::CullModeFlagBits::eBack }) + .set_depth_stencil({ .depthTestEnable = true, .depthWriteEnable = true, .depthCompareOp = vuk::CompareOp::eGreaterOrEqual }) + .set_color_blend(visbuffer, vuk::BlendPreset::eOff) + .set_dynamic_state(vuk::DynamicStateFlagBits::eViewport | vuk::DynamicStateFlagBits::eScissor) + .set_viewport(0, vuk::Rect2D::framebuffer()) + .set_scissor(0, vuk::Rect2D::framebuffer()) + .bind_persistent(1, descriptor_set) + .bind_buffer(0, 0, meshlet_instances) + .bind_buffer(0, 1, mesh_instances) + .bind_buffer(0, 2, meshes) + .bind_buffer(0, 3, transforms) + .bind_buffer(0, 4, materials) + .bind_image(0, 5, overdraw) + .bind_index_buffer(index_buffer, vuk::IndexType::eUint32) + .push_constants(vuk::ShaderStageFlagBits::eVertex, 0, projection_view) + .draw_indexed_indirect(1, triangle_indirect); - // Hilbert Noise LUT - constexpr auto HILBERT_NOISE_LUT_WIDTH = 64_u32; - auto hilbert_noise_lut_info = ImageInfo{ - .format = vuk::Format::eR16Uint, - .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eTransferDst, - .type = vuk::ImageType::e2D, - .extent = { .width = HILBERT_NOISE_LUT_WIDTH, .height = HILBERT_NOISE_LUT_WIDTH, .depth = 1 }, - .name = "Hilbert Noise LUT", - }; - std::tie(self.hilbert_noise_lut, self.hilbert_noise_lut_view) = Image::create_with_view(device, hilbert_noise_lut_info).value(); + return std::make_tuple(index_buffer, meshlet_instances, mesh_instances, meshes, transforms, materials, visbuffer, depth, overdraw); + } + ); - auto hilbert_index = [](u32 pos_x, u32 pos_y) -> u16 { - auto index = 0_u32; - for (auto cur_level = HILBERT_NOISE_LUT_WIDTH / 2; cur_level > 0_u32; cur_level /= 2_u32) { - auto region_x = (pos_x & cur_level) > 0_u32; - auto region_y = (pos_y & cur_level) > 0_u32; - index += cur_level * cur_level * ((3_u32 * region_x) ^ region_y); - if (region_y == 0_u32) { - if (region_x == 1_u32) { - pos_x = (HILBERT_NOISE_LUT_WIDTH - 1_u32) - pos_x; - pos_y = (HILBERT_NOISE_LUT_WIDTH - 1_u32) - pos_y; - } + std::tie( + reordered_indices_buffer, + meshlet_instances_buffer, + mesh_instances_buffer, + meshes_buffer, + transforms_buffer, + materials_buffer, + visbuffer_attachment, + depth_attachment, + overdraw_attachment + ) = + vis_encode_pass( + std::move(draw_command_buffer), + std::move(reordered_indices_buffer), + std::move(meshlet_instances_buffer), + std::move(mesh_instances_buffer), + std::move(meshes_buffer), + std::move(transforms_buffer), + std::move(materials_buffer), + std::move(visbuffer_attachment), + std::move(depth_attachment), + std::move(overdraw_attachment) + ); +} - auto temp_pos_x = pos_x; - pos_x = pos_y; - pos_y = temp_pos_x; - } - } +auto cull_shadowmap_meshlets( + TransferManager &transfer_man, + u32 cascade_index, + glm::vec2 &resolution, + glm::mat4 &projection_view, + vuk::Value &cull_meshlets_cmd_buffer, + vuk::Value &all_visible_meshlet_instances_count_buffer, + vuk::Value &visible_meshlet_instances_count_buffer, + vuk::Value &visible_meshlet_instances_indices_buffer, + vuk::Value &reordered_indices_buffer, + vuk::Value &meshes_buffer, + vuk::Value &mesh_instances_buffer, + vuk::Value &meshlet_instances_buffer, + vuk::Value &transforms_buffer +) -> vuk::Value { + ZoneScoped; + memory::ScopedStack stack; - return index; - }; + // ── CULL MESHLETS ─────────────────────────────────────────────────── + auto shadowmap_cull_meshlets_pass = vuk::make_pass( + stack.format("shadowmap cull meshlets cascade {}", cascade_index), + [projection_view]( + vuk::CommandBuffer &cmd_list, + VUK_BA(vuk::eIndirectRead) dispatch_cmd, + VUK_BA(vuk::eComputeRead) meshlet_instances, + VUK_BA(vuk::eComputeRead) mesh_instances, + VUK_BA(vuk::eComputeRead) meshes, + VUK_BA(vuk::eComputeRead) transforms, + VUK_BA(vuk::eComputeRead) all_visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRW) visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRW) visible_meshlet_instances_indices, + VUK_BA(vuk::eComputeRW) cull_triangles_cmd + ) { + cmd_list // + .bind_compute_pipeline("passes.shadowmap_cull_meshlets") + .bind_buffer(0, 0, meshlet_instances) + .bind_buffer(0, 1, mesh_instances) + .bind_buffer(0, 2, meshes) + .bind_buffer(0, 3, transforms) + .bind_buffer(0, 4, all_visible_meshlet_instances_count) + .bind_buffer(0, 5, visible_meshlet_instances_count) + .bind_buffer(0, 6, visible_meshlet_instances_indices) + .bind_buffer(0, 7, cull_triangles_cmd) + .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, projection_view) + .dispatch_indirect(dispatch_cmd); - u16 hilbert_noise[HILBERT_NOISE_LUT_WIDTH * HILBERT_NOISE_LUT_WIDTH] = {}; - for (auto y = 0_u32; y < HILBERT_NOISE_LUT_WIDTH; y++) { - for (auto x = 0_u32; x < HILBERT_NOISE_LUT_WIDTH; x++) { - hilbert_noise[y * HILBERT_NOISE_LUT_WIDTH + x] = hilbert_index(x, y); + return std::make_tuple( + dispatch_cmd, + meshlet_instances, + mesh_instances, + meshes, + transforms, + all_visible_meshlet_instances_count, + visible_meshlet_instances_count, + visible_meshlet_instances_indices, + cull_triangles_cmd + ); } - } + ); - auto &transfer_man = device.transfer_man(); + auto cull_triangles_cmd_buffer = transfer_man.scratch_buffer({ .x = 0, .y = 1, .z = 1 }); - auto hilbert_noise_size_bytes = HILBERT_NOISE_LUT_WIDTH * HILBERT_NOISE_LUT_WIDTH * sizeof(u16); - auto hilbert_noise_buffer = transfer_man.alloc_image_buffer(hilbert_noise_lut_info.format, hilbert_noise_lut_info.extent); - std::memcpy(hilbert_noise_buffer->mapped_ptr, hilbert_noise, hilbert_noise_size_bytes); + std::tie( + cull_meshlets_cmd_buffer, + meshlet_instances_buffer, + mesh_instances_buffer, + meshes_buffer, + transforms_buffer, + all_visible_meshlet_instances_count_buffer, + visible_meshlet_instances_count_buffer, + visible_meshlet_instances_indices_buffer, + cull_triangles_cmd_buffer + ) = + shadowmap_cull_meshlets_pass( + std::move(cull_meshlets_cmd_buffer), + std::move(meshlet_instances_buffer), + std::move(mesh_instances_buffer), + std::move(meshes_buffer), + std::move(transforms_buffer), + std::move(all_visible_meshlet_instances_count_buffer), + std::move(visible_meshlet_instances_count_buffer), + std::move(visible_meshlet_instances_indices_buffer), + std::move(cull_triangles_cmd_buffer) + ); - auto hilbert_noise_lut_attachment = self.hilbert_noise_lut_view.discard(device, "hilbert noise", vuk::ImageUsageFlagBits::eTransferDst); - hilbert_noise_lut_attachment = transfer_man.upload(std::move(hilbert_noise_buffer), std::move(hilbert_noise_lut_attachment)); - hilbert_noise_lut_attachment = hilbert_noise_lut_attachment.as_released(vuk::eComputeSampled, vuk::DomainFlagBits::eGraphicsQueue); - transfer_man.wait_on(std::move(hilbert_noise_lut_attachment)); + // ── CULL TRIANGLES ────────────────────────────────────────────────── + auto shadowmap_cull_triangles_pass = vuk::make_pass( + stack.format("shadowmap cull triangles cascade {}", cascade_index), + [resolution, projection_view]( + vuk::CommandBuffer &cmd_list, + VUK_BA(vuk::eIndirectRead) cull_triangles_cmd, + VUK_BA(vuk::eComputeRead) visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRead) visible_meshlet_instances_indices, + VUK_BA(vuk::eComputeRead) meshlet_instances, + VUK_BA(vuk::eComputeRead) mesh_instances, + VUK_BA(vuk::eComputeRead) meshes, + VUK_BA(vuk::eComputeRead) transforms, + VUK_BA(vuk::eComputeRW) draw_indexed_cmd, + VUK_BA(vuk::eComputeWrite) reordered_indices + ) { + auto cull_flags = GPU::CullFlags::All; + cmd_list // + .bind_compute_pipeline("passes.cull_triangles") + .bind_buffer(0, 0, visible_meshlet_instances_count) + .bind_buffer(0, 1, visible_meshlet_instances_indices) + .bind_buffer(0, 2, meshlet_instances) + .bind_buffer(0, 3, mesh_instances) + .bind_buffer(0, 4, meshes) + .bind_buffer(0, 5, transforms) + .bind_buffer(0, 6, draw_indexed_cmd) + .bind_buffer(0, 7, reordered_indices) + .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(cull_flags, resolution, projection_view)) + .specialize_constants(0, false) + .dispatch_indirect(cull_triangles_cmd); - return true; -} + return std::make_tuple( + visible_meshlet_instances_count, + visible_meshlet_instances_indices, + meshlet_instances, + mesh_instances, + meshes, + transforms, + draw_indexed_cmd, + reordered_indices + ); + } + ); -auto SceneRenderer::destroy(this SceneRenderer &self) -> void { - ZoneScoped; + auto draw_command_buffer = transfer_man.scratch_buffer({ .instanceCount = 1 }); - self.cleanup(); + std::tie( + visible_meshlet_instances_count_buffer, + visible_meshlet_instances_indices_buffer, + meshlet_instances_buffer, + mesh_instances_buffer, + meshes_buffer, + transforms_buffer, + draw_command_buffer, + reordered_indices_buffer + ) = + shadowmap_cull_triangles_pass( + std::move(cull_triangles_cmd_buffer), + std::move(visible_meshlet_instances_count_buffer), + std::move(visible_meshlet_instances_indices_buffer), + std::move(meshlet_instances_buffer), + std::move(mesh_instances_buffer), + std::move(meshes_buffer), + std::move(transforms_buffer), + std::move(draw_command_buffer), + std::move(reordered_indices_buffer) + ); + + return draw_command_buffer; } -auto SceneRenderer::prepare_frame(this SceneRenderer &self, FramePrepareInfo &info) -> PreparedFrame { +auto draw_shadowmap( + u32 cascade_index, + glm::mat4 &projection_view, + vuk::Value &depth_attachment, + vuk::Value &draw_command_buffer, + vuk::Value &reordered_indices_buffer, + vuk::Value &meshes_buffer, + vuk::Value &mesh_instances_buffer, + vuk::Value &meshlet_instances_buffer, + vuk::Value &transforms_buffer +) -> void { ZoneScoped; + memory::ScopedStack stack; - auto &device = App::mod(); - auto &transfer_man = device.transfer_man(); - auto prepared_frame = PreparedFrame{}; + auto draw_shadowmap_pass = vuk::make_pass( + stack.format("draw shadowmap casecade {}", cascade_index), + [projection_view]( + vuk::CommandBuffer &cmd_list, + VUK_BA(vuk::eIndirectRead) triangle_indirect, + VUK_BA(vuk::eIndexRead) index_buffer, + VUK_BA(vuk::eVertexRead) meshlet_instances, + VUK_BA(vuk::eVertexRead) mesh_instances, + VUK_BA(vuk::eVertexRead) meshes, + VUK_BA(vuk::eVertexRead) transforms, + VUK_IA(vuk::eDepthStencilRW) depth + ) { + cmd_list // + .bind_graphics_pipeline("passes.shadowmap_draw") + .set_rasterization({ .cullMode = vuk::CullModeFlagBits::eBack }) + .set_depth_stencil({ .depthTestEnable = true, .depthWriteEnable = true, .depthCompareOp = vuk::CompareOp::eGreaterOrEqual }) + .set_dynamic_state(vuk::DynamicStateFlagBits::eViewport | vuk::DynamicStateFlagBits::eScissor) + .set_viewport(0, vuk::Rect2D::framebuffer()) + .set_scissor(0, vuk::Rect2D::framebuffer()) + .bind_buffer(0, 0, meshlet_instances) + .bind_buffer(0, 1, mesh_instances) + .bind_buffer(0, 2, meshes) + .bind_buffer(0, 3, transforms) + .bind_index_buffer(index_buffer, vuk::IndexType::eUint32) + .push_constants(vuk::ShaderStageFlagBits::eVertex, 0, projection_view) + .draw_indexed_indirect(1, triangle_indirect); - auto zero_fill_pass = vuk::make_pass("zero fill", [](vuk::CommandBuffer &command_buffer, VUK_BA(vuk::eTransferWrite) dst) { - command_buffer.fill_buffer(dst, 0_u32); + return std::make_tuple(index_buffer, meshlet_instances, mesh_instances, meshes, transforms, depth); + } + ); - return dst; - }); + std::tie(reordered_indices_buffer, meshlet_instances_buffer, mesh_instances_buffer, meshes_buffer, transforms_buffer, depth_attachment) = + draw_shadowmap_pass( + std::move(draw_command_buffer), + std::move(reordered_indices_buffer), + std::move(meshlet_instances_buffer), + std::move(mesh_instances_buffer), + std::move(meshes_buffer), + std::move(transforms_buffer), + std::move(depth_attachment) + ); +} - if (!info.dirty_transform_ids.empty()) { - auto rebuild_transforms = !self.transforms_buffer || self.transforms_buffer.data_size() <= info.gpu_transforms.size_bytes(); - self.transforms_buffer = self.transforms_buffer.resize(device, info.gpu_transforms.size_bytes()).value(); - prepared_frame.transforms_buffer = self.transforms_buffer.acquire(device, "transforms", rebuild_transforms ? vuk::eNone : vuk::eMemoryRead); +static auto draw_hiz(vuk::Value &hiz_attachment, vuk::Value &depth_attachment) -> void { + ZoneScoped; - if (rebuild_transforms) { - // If we resize buffer, we need to refill it again, so individual uploads are not required. - prepared_frame.transforms_buffer = transfer_man.upload(info.gpu_transforms, std::move(prepared_frame.transforms_buffer)); - } else { - // Buffer is not resized, upload individual transforms. - auto dirty_transforms_count = info.dirty_transform_ids.size(); - auto dirty_transforms_size_bytes = dirty_transforms_count * sizeof(GPU::Transforms); - auto upload_buffer = transfer_man.alloc_transient_buffer(vuk::MemoryUsage::eCPUtoGPU, dirty_transforms_size_bytes); - auto *dst_transform_ptr = reinterpret_cast(upload_buffer->mapped_ptr); - auto upload_offsets = std::vector(dirty_transforms_count); + auto hiz_generate_pass = vuk::make_pass( + "hiz generate", + [](vuk::CommandBuffer &cmd_list, // + VUK_IA(vuk::eComputeSampled) src, + VUK_IA(vuk::eComputeRW) dst) { + auto extent = dst->extent; + auto mip_count = dst->level_count; + LS_EXPECT(mip_count < 13); - for (const auto &[dirty_transform_id, offset] : std::views::zip(info.dirty_transform_ids, upload_offsets)) { - auto index = SlotMap_decode_id(dirty_transform_id).index; - const auto &transform = info.gpu_transforms[index]; - std::memcpy(dst_transform_ptr, &transform, sizeof(GPU::Transforms)); - offset = index * sizeof(GPU::Transforms); - dst_transform_ptr++; - } + auto dispatch_x = (extent.width + 63) >> 6; + auto dispatch_y = (extent.height + 63) >> 6; - auto update_transforms_pass = vuk::make_pass( - "update scene transforms", - [upload_offsets = std::move(upload_offsets)]( - vuk::CommandBuffer &cmd_list, // - VUK_BA(vuk::Access::eTransferRead) src_buffer, - VUK_BA(vuk::Access::eTransferWrite) dst_buffer - ) { - for (usize i = 0; i < upload_offsets.size(); i++) { - auto offset = upload_offsets[i]; - auto src_subrange = src_buffer->subrange(i * sizeof(GPU::Transforms), sizeof(GPU::Transforms)); - auto dst_subrange = dst_buffer->subrange(offset, sizeof(GPU::Transforms)); - cmd_list.copy_buffer(src_subrange, dst_subrange); - } + cmd_list // + .bind_compute_pipeline("passes.hiz") + .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(mip_count, (dispatch_x * dispatch_y) - 1, glm::mat2(1.0f))) + .specialize_constants(0, extent.width == extent.height && (extent.width & (extent.width - 1)) == 0 ? 1u : 0u) + .specialize_constants(1, extent.width) + .specialize_constants(2, extent.height); - return dst_buffer; - } - ); + *cmd_list.scratch_buffer(0, 0) = 0; + cmd_list.bind_sampler(0, 1, hiz_sampler_info); + cmd_list.bind_image(0, 2, src); - prepared_frame.transforms_buffer = update_transforms_pass(std::move(upload_buffer), std::move(prepared_frame.transforms_buffer)); - } - } else if (self.transforms_buffer) { - prepared_frame.transforms_buffer = self.transforms_buffer.acquire(device, "transforms", vuk::Access::eMemoryRead); - } + for (u32 i = 0; i < 13; i++) { + cmd_list.bind_image(0, i + 3, dst->mip(ls::min(i, mip_count - 1_u32))); + } - if (!info.dirty_material_indices.empty()) { - auto rebuild_materials = !self.materials_buffer || self.materials_buffer.data_size() <= info.gpu_materials.size_bytes(); - self.materials_buffer = self.materials_buffer.resize(device, info.gpu_materials.size_bytes()).value(); - prepared_frame.materials_buffer = self.materials_buffer.acquire(device, "materials", rebuild_materials ? vuk::eNone : vuk::eMemoryRead); + cmd_list.dispatch(dispatch_x, dispatch_y); - if (rebuild_materials) { - prepared_frame.materials_buffer = transfer_man.upload(info.gpu_materials, std::move(prepared_frame.materials_buffer)); - } else { - // TODO: Literally repeating code, find a solution to this - auto dirty_materials_count = info.dirty_material_indices.size(); - auto dirty_materials_size_bytes = dirty_materials_count * sizeof(GPU::Material); - auto upload_buffer = transfer_man.alloc_transient_buffer(vuk::MemoryUsage::eCPUtoGPU, dirty_materials_size_bytes); - auto *dst_materials_ptr = reinterpret_cast(upload_buffer->mapped_ptr); - auto upload_offsets = std::vector(dirty_materials_count); + return std::make_tuple(src, dst); + } + ); - for (const auto &[dirty_material, index, offset] : std::views::zip(info.gpu_materials, info.dirty_material_indices, upload_offsets)) { - std::memcpy(dst_materials_ptr, &dirty_material, sizeof(GPU::Material)); - offset = index * sizeof(GPU::Material); - dst_materials_ptr++; - } + auto hiz_generate_slow_pass = vuk::make_pass( + "hiz generate slow", + [](vuk::CommandBuffer &cmd_list, // + VUK_IA(vuk::eComputeSampled) src, + VUK_IA(vuk::eComputeRW) dst) { + auto extent = dst->extent; + auto mip_count = dst->level_count; - auto update_materials_pass = vuk::make_pass( - "update scene materials", - [upload_offsets = std::move(upload_offsets)]( - vuk::CommandBuffer &cmd_list, // - VUK_BA(vuk::Access::eTransferRead) src_buffer, - VUK_BA(vuk::Access::eTransferWrite) dst_buffer - ) { - for (usize i = 0; i < upload_offsets.size(); i++) { - auto offset = upload_offsets[i]; - auto src_subrange = src_buffer->subrange(i * sizeof(GPU::Material), sizeof(GPU::Material)); - auto dst_subrange = dst_buffer->subrange(offset, sizeof(GPU::Material)); - cmd_list.copy_buffer(src_subrange, dst_subrange); - } + cmd_list // + .bind_compute_pipeline("passes.hiz_slow") + .bind_sampler(0, 0, hiz_sampler_info); - return dst_buffer; + for (auto i = 0_u32; i < mip_count; i++) { + auto mip_width = std::max(1_u32, extent.width >> i); + auto mip_height = std::max(1_u32, extent.height >> i); + + auto mip = dst->mip(i); + if (i == 0) { + cmd_list.bind_image(0, 1, src); + } else { + auto mip = dst->mip(i - 1); + cmd_list.image_barrier(mip, vuk::eComputeWrite, vuk::eComputeSampled); + cmd_list.bind_image(0, 1, mip); } - ); - prepared_frame.materials_buffer = update_materials_pass(std::move(upload_buffer), std::move(prepared_frame.materials_buffer)); - } - } else if (self.materials_buffer) { - prepared_frame.materials_buffer = self.materials_buffer.acquire(device, "materials", vuk::eMemoryRead); - } + cmd_list.bind_image(0, 2, mip); + cmd_list.push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(mip_width, mip_height, i)); + cmd_list.dispatch_invocations(mip_width, mip_height); + } - if (!info.gpu_meshes.empty()) { - self.meshes_buffer = self.meshes_buffer.resize(device, info.gpu_meshes.size_bytes()).value(); - prepared_frame.meshes_buffer = self.meshes_buffer.acquire(device, "meshes", vuk::eNone); - prepared_frame.meshes_buffer = transfer_man.upload(info.gpu_meshes, std::move(prepared_frame.meshes_buffer)); - } else if (self.meshes_buffer) { - prepared_frame.meshes_buffer = self.meshes_buffer.acquire(device, "meshes", vuk::eMemoryRead); - } + cmd_list.image_barrier(dst, vuk::eComputeSampled, vuk::eComputeRW); - if (!info.gpu_mesh_instances.empty()) { - self.mesh_instances_buffer = self.mesh_instances_buffer.resize(device, info.gpu_mesh_instances.size_bytes()).value(); - prepared_frame.mesh_instances_buffer = self.mesh_instances_buffer.acquire(device, "mesh instances", vuk::eNone); - prepared_frame.mesh_instances_buffer = transfer_man.upload(info.gpu_mesh_instances, std::move(prepared_frame.mesh_instances_buffer)); + return std::make_tuple(src, dst); + } + ); - auto meshlet_instance_visibility_mask_size_bytes = (info.max_meshlet_instance_count + 31) / 32 * sizeof(u32); - self.meshlet_instance_visibility_mask_buffer = - self.meshlet_instance_visibility_mask_buffer.resize(device, meshlet_instance_visibility_mask_size_bytes).value(); - prepared_frame.meshlet_instance_visibility_mask_buffer = - self.meshlet_instance_visibility_mask_buffer.acquire(device, "meshlet instances visibility mask", vuk::eNone); - prepared_frame.meshlet_instance_visibility_mask_buffer = zero_fill_pass(std::move(prepared_frame.meshlet_instance_visibility_mask_buffer)); - } else if (self.mesh_instances_buffer) { - prepared_frame.mesh_instances_buffer = self.mesh_instances_buffer.acquire(device, "mesh instances", vuk::eMemoryRead); - prepared_frame.meshlet_instance_visibility_mask_buffer = - self.meshlet_instance_visibility_mask_buffer.acquire(device, "meshlet instances visibility mask", vuk::eMemoryRead); - } + std::tie(depth_attachment, hiz_attachment) = hiz_generate_slow_pass(std::move(depth_attachment), std::move(hiz_attachment)); +} - info.environment.transmittance_lut_size = self.sky_transmittance_lut_view.extent(); - info.environment.sky_view_lut_size = self.sky_view_lut_extent; - info.environment.multiscattering_lut_size = self.sky_multiscatter_lut_view.extent(); - info.environment.aerial_perspective_lut_size = self.sky_aerial_perspective_lut_extent; - prepared_frame.environment_buffer = transfer_man.scratch_buffer(info.environment); - prepared_frame.camera_buffer = transfer_man.scratch_buffer(info.camera); - prepared_frame.mesh_instance_count = info.mesh_instance_count; - prepared_frame.max_meshlet_instance_count = info.max_meshlet_instance_count; - prepared_frame.environment_flags = static_cast(info.environment.flags); +static auto draw_sky( + SceneRenderer &self, + u32 frame_index, + vuk::Value &sky_transmittance_lut_attachment, + vuk::Value &sky_multiscatter_lut_attachment, + vuk::Value &atmosphere_buffer, + vuk::Value &camera_buffer +) -> std::tuple, vuk::Value> { + ZoneScoped; - if (info.regenerate_sky || !self.sky_transmittance_lut_view) { - auto transmittance_lut_pass = vuk::make_pass( - "transmittance lut", - [](vuk::CommandBuffer &cmd_list, // - VUK_IA(vuk::eComputeRW) dst, - VUK_BA(vuk::eComputeRead) environment) { - cmd_list // - .bind_compute_pipeline("passes.sky_transmittance") - .bind_image(0, 0, dst) - .bind_buffer(0, 1, environment) - .dispatch_invocations_per_pixel(dst); + auto sky_view_lut_attachment = vuk::declare_ia( + "sky view lut", + { .image_type = vuk::ImageType::e2D, + .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, + .extent = self.sky_view_lut_extent, + .format = vuk::Format::eR16G16B16A16Sfloat, + .sample_count = vuk::Samples::e1, + .view_type = vuk::ImageViewType::e2D, + .level_count = 1, + .layer_count = 1 } + ); - return std::make_tuple(dst, environment); - } - ); + auto sky_cubemap_attachment = vuk::declare_ia( + "sky cubemap", + { .image_flags = vuk::ImageCreateFlagBits::eCubeCompatible, + .image_type = vuk::ImageType::e2D, + .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, + .extent = self.sky_cubemap_extent, + .format = vuk::Format::eR16G16B16A16Sfloat, + .sample_count = vuk::Samples::e1, + .view_type = vuk::ImageViewType::eCube, + .level_count = 1, + .layer_count = 6 } + ); - prepared_frame.sky_transmittance_lut = - self.sky_transmittance_lut_view.discard(device, "sky transmittance lut", vuk::ImageUsageFlagBits::eStorage); - std::tie(prepared_frame.sky_transmittance_lut, prepared_frame.environment_buffer) = - transmittance_lut_pass(std::move(prepared_frame.sky_transmittance_lut), std::move(prepared_frame.environment_buffer)); + // ── SKY VIEW LUT ──────────────────────────────────────────────────── + auto sky_view_pass = vuk::make_pass( + "sky view", + [](vuk::CommandBuffer &cmd_list, // + VUK_IA(vuk::eComputeSampled) sky_transmittance_lut, + VUK_IA(vuk::eComputeSampled) sky_multiscatter_lut, + VUK_BA(vuk::eComputeRead) atmosphere, + VUK_BA(vuk::eComputeRead) camera, + VUK_IA(vuk::eComputeRW) sky_view_lut) { + auto linear_clamp_sampler = vuk::SamplerCreateInfo{ + .magFilter = vuk::Filter::eLinear, + .minFilter = vuk::Filter::eLinear, + .addressModeU = vuk::SamplerAddressMode::eClampToEdge, + .addressModeV = vuk::SamplerAddressMode::eClampToEdge, + .addressModeW = vuk::SamplerAddressMode::eClampToEdge, + }; - auto multiscatter_lut_pass = vuk::make_pass( - "multiscatter lut", - [](vuk::CommandBuffer &cmd_list, // - VUK_IA(vuk::eComputeSampled) sky_transmittance_lut, - VUK_IA(vuk::eComputeRW) sky_multiscatter_lut, - VUK_BA(vuk::eComputeRead) environment) { - cmd_list // - .bind_compute_pipeline("passes.sky_multiscattering") - .bind_sampler(0, 0, { .magFilter = vuk::Filter::eLinear, .minFilter = vuk::Filter::eLinear }) - .bind_image(0, 1, sky_transmittance_lut) - .bind_buffer(0, 2, environment) - .bind_image(0, 3, sky_multiscatter_lut) - .dispatch(sky_multiscatter_lut->extent.width, sky_multiscatter_lut->extent.height); + cmd_list // + .bind_compute_pipeline("passes.sky_view") + .bind_sampler(0, 0, linear_clamp_sampler) + .bind_image(0, 1, sky_transmittance_lut) + .bind_image(0, 2, sky_multiscatter_lut) + .bind_buffer(0, 3, atmosphere) + .bind_buffer(0, 4, camera) + .bind_image(0, 5, sky_view_lut) + .dispatch_invocations_per_pixel(sky_view_lut); + return std::make_tuple(sky_transmittance_lut, sky_multiscatter_lut, atmosphere, camera, sky_view_lut); + } + ); - return std::make_tuple(sky_transmittance_lut, sky_multiscatter_lut, environment); - } + std::tie(sky_transmittance_lut_attachment, sky_multiscatter_lut_attachment, atmosphere_buffer, camera_buffer, sky_view_lut_attachment) = + sky_view_pass( + std::move(sky_transmittance_lut_attachment), + std::move(sky_multiscatter_lut_attachment), + std::move(atmosphere_buffer), + std::move(camera_buffer), + std::move(sky_view_lut_attachment) ); - prepared_frame.sky_multiscatter_lut = - self.sky_multiscatter_lut_view.discard(device, "sky multiscatter lut", vuk::ImageUsageFlagBits::eStorage); - std::tie(prepared_frame.sky_transmittance_lut, prepared_frame.sky_multiscatter_lut, prepared_frame.environment_buffer) = - multiscatter_lut_pass( - std::move(prepared_frame.sky_transmittance_lut), - std::move(prepared_frame.sky_multiscatter_lut), - std::move(prepared_frame.environment_buffer) - ); - } else { - prepared_frame.sky_transmittance_lut = - self.sky_transmittance_lut_view.acquire(device, "sky transmittance lut", vuk::ImageUsageFlagBits::eSampled, vuk::Access::eComputeSampled); - prepared_frame.sky_multiscatter_lut = - self.sky_multiscatter_lut_view.acquire(device, "sky multiscatter lut", vuk::ImageUsageFlagBits::eSampled, vuk::Access::eComputeSampled); - } + auto sky_cubemap_pass = vuk::make_pass( + "sky cubemap", + [frame_index]( + vuk::CommandBuffer &cmd_list, // + VUK_IA(vuk::eComputeSampled) sky_view_lut, + VUK_BA(vuk::eComputeRead) atmosphere, + VUK_BA(vuk::eComputeRead) camera, + VUK_IA(vuk::eComputeRW) sky_cubemap + ) { + auto linear_clamp_sampler = vuk::SamplerCreateInfo{ + .magFilter = vuk::Filter::eLinear, + .minFilter = vuk::Filter::eLinear, + .addressModeU = vuk::SamplerAddressMode::eClampToEdge, + .addressModeV = vuk::SamplerAddressMode::eClampToEdge, + .addressModeW = vuk::SamplerAddressMode::eClampToEdge, + }; - prepared_frame.camera = info.camera; - if (info.environment.flags & GPU::EnvironmentFlags::HasSun) { - prepared_frame.directional_light = info.environment.lights.directional_light; - } - prepared_frame.vbgtao = info.vbgtao; + cmd_list // + .bind_compute_pipeline("passes.sky_cubemap") + .bind_sampler(0, 0, linear_clamp_sampler) + .bind_image(0, 1, sky_view_lut) + .bind_buffer(0, 2, atmosphere) + .bind_buffer(0, 3, camera) + .bind_image(0, 4, sky_cubemap) + .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, frame_index) + .dispatch((sky_cubemap->extent.width + 2 - 1) / 2, (sky_cubemap->extent.height + 2 - 1) / 2, 6); - return prepared_frame; -} + return std::make_tuple(sky_view_lut, atmosphere, camera, sky_cubemap); + } + ); -static auto cull_meshes( - TransferManager &transfer_man, - GPU::CullFlags cull_flags, - u32 mesh_instance_count, - glm::mat4 &frustum_projection_view, - glm::vec3 &observer_position, - glm::vec2 &observer_resolution, - f32 acceptable_lod_error, - vuk::Value &meshes_buffer, - vuk::Value &mesh_instances_buffer, - vuk::Value &meshlet_instances_buffer, - vuk::Value &visible_meshlet_instances_count_buffer, - vuk::Value &transforms_buffer, - vuk::Value &debug_drawer_buffer -) -> vuk::Value { + std::tie(sky_view_lut_attachment, atmosphere_buffer, camera_buffer, sky_cubemap_attachment) = sky_cubemap_pass( + std::move(sky_view_lut_attachment), + std::move(atmosphere_buffer), + std::move(camera_buffer), + std::move(sky_cubemap_attachment) + ); + + return std::make_tuple(sky_view_lut_attachment, sky_cubemap_attachment); +} + +static auto apply_sky( + SceneRenderer &self, + vuk::Value &dst_attachment, + vuk::Value &depth_attachment, + vuk::Value &sky_view_lut_attachment, + vuk::Value &sky_transmittance_lut_attachment, + vuk::Value &sky_multiscatter_lut_attachment, + vuk::Value &atmosphere_buffer, + vuk::Value &camera_buffer +) -> void { ZoneScoped; - auto vis_cull_meshes_pass = vuk::make_pass( - "vis cull meshes", - [cull_flags, mesh_instance_count, frustum_projection_view, observer_position, observer_resolution, acceptable_lod_error]( - vuk::CommandBuffer &cmd_list, - VUK_BA(vuk::eComputeRead) meshes, - VUK_BA(vuk::eComputeRead) transforms, - VUK_BA(vuk::eComputeRW) mesh_instances, - VUK_BA(vuk::eComputeRW) meshlet_instances, - VUK_BA(vuk::eComputeRW) visible_meshlet_instances_count, - VUK_BA(vuk::eComputeRW) debug_drawer - ) { + auto sky_aerial_perspective_attachment = vuk::declare_ia( + "sky aerial perspective", + { .image_type = vuk::ImageType::e3D, + .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, + .extent = self.sky_aerial_perspective_lut_extent, + .sample_count = vuk::Samples::e1, + .view_type = vuk::ImageViewType::e3D, + .level_count = 1, + .layer_count = 1 } + ); + sky_aerial_perspective_attachment.same_format_as(sky_view_lut_attachment); + + // ── SKY AERIAL PERSPECTIVE ────────────────────────────────────────── + auto sky_aerial_perspective_pass = vuk::make_pass( + "sky aerial perspective", + [](vuk::CommandBuffer &cmd_list, // + VUK_IA(vuk::eComputeSampled) sky_transmittance_lut, + VUK_IA(vuk::eComputeSampled) sky_multiscatter_lut, + VUK_BA(vuk::eComputeRead) atmosphere, + VUK_BA(vuk::eComputeRead) camera, + VUK_IA(vuk::eComputeRW) sky_aerial_perspective_lut) { + auto linear_clamp_sampler = vuk::SamplerCreateInfo{ + .magFilter = vuk::Filter::eLinear, + .minFilter = vuk::Filter::eLinear, + .addressModeU = vuk::SamplerAddressMode::eClampToEdge, + .addressModeV = vuk::SamplerAddressMode::eClampToEdge, + .addressModeW = vuk::SamplerAddressMode::eClampToEdge, + }; + cmd_list // - .bind_compute_pipeline("passes.cull_meshes") - .bind_buffer(0, 0, meshes) - .bind_buffer(0, 1, transforms) - .bind_buffer(0, 2, mesh_instances) - .bind_buffer(0, 3, meshlet_instances) - .bind_buffer(0, 4, visible_meshlet_instances_count) - .bind_buffer(0, 5, debug_drawer) - .push_constants( - vuk::ShaderStageFlagBits::eCompute, - 0, - PushConstants( - mesh_instance_count, - cull_flags, - frustum_projection_view, - observer_position, - glm::max(observer_resolution.x, observer_resolution.y), - acceptable_lod_error - ) - ) - .dispatch_invocations(mesh_instance_count); + .bind_compute_pipeline("passes.sky_aerial_perspective") + .bind_sampler(0, 0, linear_clamp_sampler) + .bind_image(0, 1, sky_transmittance_lut) + .bind_image(0, 2, sky_multiscatter_lut) + .bind_buffer(0, 3, atmosphere) + .bind_buffer(0, 4, camera) + .bind_image(0, 5, sky_aerial_perspective_lut) + .dispatch_invocations_per_pixel(sky_aerial_perspective_lut); - return std::make_tuple(meshes, transforms, mesh_instances, meshlet_instances, visible_meshlet_instances_count, debug_drawer); + return std::make_tuple(sky_transmittance_lut, sky_multiscatter_lut, atmosphere, camera, sky_aerial_perspective_lut); } ); - std::tie( - meshes_buffer, - transforms_buffer, - mesh_instances_buffer, - meshlet_instances_buffer, - visible_meshlet_instances_count_buffer, - debug_drawer_buffer - ) = - vis_cull_meshes_pass( - std::move(meshes_buffer), - std::move(transforms_buffer), - std::move(mesh_instances_buffer), - std::move(meshlet_instances_buffer), - std::move(visible_meshlet_instances_count_buffer), - std::move(debug_drawer_buffer) + std::tie(sky_transmittance_lut_attachment, sky_multiscatter_lut_attachment, atmosphere_buffer, camera_buffer, sky_aerial_perspective_attachment) = + sky_aerial_perspective_pass( + std::move(sky_transmittance_lut_attachment), + std::move(sky_multiscatter_lut_attachment), + std::move(atmosphere_buffer), + std::move(camera_buffer), + std::move(sky_aerial_perspective_attachment) ); - auto generate_cull_commands_pass = vuk::make_pass( - "generate cull commands", + // ── SKY FINAL ─────────────────────────────────────────────────────── + auto sky_final_pass = vuk::make_pass( + "sky final", [](vuk::CommandBuffer &cmd_list, // - VUK_BA(vuk::eComputeRead) visible_meshlet_instances_count, - VUK_BA(vuk::eComputeRW) cull_meshlets_cmd) { + VUK_IA(vuk::eColorRW) dst, + VUK_IA(vuk::eFragmentSampled) sky_transmittance_lut, + VUK_IA(vuk::eFragmentSampled) sky_aerial_perspective_lut, + VUK_IA(vuk::eFragmentSampled) sky_view_lut, + VUK_IA(vuk::eFragmentSampled) depth, + VUK_BA(vuk::eFragmentRead) atmosphere, + VUK_BA(vuk::eFragmentRead) camera) { + auto linear_clamp_sampler = vuk::SamplerCreateInfo{ + .magFilter = vuk::Filter::eLinear, + .minFilter = vuk::Filter::eLinear, + .addressModeU = vuk::SamplerAddressMode::eClampToEdge, + .addressModeV = vuk::SamplerAddressMode::eClampToEdge, + .addressModeW = vuk::SamplerAddressMode::eClampToEdge, + }; + + vuk::PipelineColorBlendAttachmentState blend_info = { + .blendEnable = true, + .srcColorBlendFactor = vuk::BlendFactor::eOne, + .dstColorBlendFactor = vuk::BlendFactor::eSrcAlpha, + .colorBlendOp = vuk::BlendOp::eAdd, + .srcAlphaBlendFactor = vuk::BlendFactor::eZero, + .dstAlphaBlendFactor = vuk::BlendFactor::eOne, + .alphaBlendOp = vuk::BlendOp::eAdd, + }; + cmd_list // - .bind_compute_pipeline("passes.generate_cull_commands") - .bind_buffer(0, 0, visible_meshlet_instances_count) - .bind_buffer(0, 1, cull_meshlets_cmd) - .dispatch(1); + .bind_graphics_pipeline("passes.sky_final") + .set_rasterization({}) + .set_depth_stencil({}) + .set_color_blend(dst, blend_info) + .set_dynamic_state(vuk::DynamicStateFlagBits::eViewport | vuk::DynamicStateFlagBits::eScissor) + .set_viewport(0, vuk::Rect2D::framebuffer()) + .set_scissor(0, vuk::Rect2D::framebuffer()) + .bind_sampler(0, 0, linear_clamp_sampler) + .bind_image(0, 1, sky_transmittance_lut) + .bind_image(0, 2, sky_aerial_perspective_lut) + .bind_image(0, 3, sky_view_lut) + .bind_image(0, 4, depth) + .bind_buffer(0, 5, atmosphere) + .bind_buffer(0, 6, camera) + .draw(3, 1, 0, 0); - return std::make_tuple(visible_meshlet_instances_count, cull_meshlets_cmd); + return std::make_tuple(dst, depth, atmosphere, camera); } ); - auto cull_meshlets_cmd_buffer = transfer_man.scratch_buffer({ .x = 0, .y = 1, .z = 1 }); - std::tie(visible_meshlet_instances_count_buffer, cull_meshlets_cmd_buffer) = - generate_cull_commands_pass(std::move(visible_meshlet_instances_count_buffer), std::move(cull_meshlets_cmd_buffer)); - - return cull_meshlets_cmd_buffer; + std::tie(dst_attachment, depth_attachment, atmosphere_buffer, camera_buffer) = sky_final_pass( + std::move(dst_attachment), + std::move(sky_transmittance_lut_attachment), + std::move(sky_aerial_perspective_attachment), + std::move(sky_view_lut_attachment), + std::move(depth_attachment), + std::move(atmosphere_buffer), + std::move(camera_buffer) + ); } -static auto cull_meshlets( - TransferManager &transfer_man, - bool late, - GPU::CullFlags cull_flags, - f32 near_clip, - glm::vec2 &resolution, - glm::mat4 &projection_view, - vuk::Value &hiz_attachment, - vuk::Value &cull_meshlets_cmd_buffer, - vuk::Value &visible_meshlet_instances_count_buffer, - vuk::Value &early_visible_meshlet_instances_count_buffer, - vuk::Value &late_visible_meshlet_instances_count_buffer, - vuk::Value &visible_meshlet_instances_indices_buffer, - vuk::Value &meshlet_instance_visibility_mask_buffer, - vuk::Value &reordered_indices_buffer, - vuk::Value &meshes_buffer, - vuk::Value &mesh_instances_buffer, - vuk::Value &meshlet_instances_buffer, - vuk::Value &transforms_buffer -) -> vuk::Value { +auto SceneRenderer::init(this SceneRenderer &self) -> bool { ZoneScoped; - memory::ScopedStack stack; - // ── CULL MESHLETS ─────────────────────────────────────────────────── - auto vis_cull_meshlets_pass = vuk::make_pass( - stack.format("vis cull meshlets {}", late ? "late" : "early"), - [late, cull_flags, near_clip, projection_view]( - vuk::CommandBuffer &cmd_list, - VUK_BA(vuk::eIndirectRead) dispatch_cmd, - VUK_BA(vuk::eComputeRead) meshlet_instances, - VUK_BA(vuk::eComputeRead) mesh_instances, - VUK_BA(vuk::eComputeRead) meshes, - VUK_BA(vuk::eComputeRead) transforms, - VUK_IA(vuk::eComputeSampled) hiz, - VUK_BA(vuk::eComputeRead) visible_meshlet_instances_count, - VUK_BA(vuk::eComputeRW) early_visible_meshlet_instances_count, - VUK_BA(vuk::eComputeRW) late_visible_meshlet_instances_count, - VUK_BA(vuk::eComputeRW) visible_meshlet_instances_indices, - VUK_BA(vuk::eComputeRW) meshlet_instance_visibility_mask, - VUK_BA(vuk::eComputeRW) cull_triangles_cmd - ) { - cmd_list // - .bind_compute_pipeline("passes.cull_meshlets") - .bind_buffer(0, 0, meshlet_instances) - .bind_buffer(0, 1, mesh_instances) - .bind_buffer(0, 2, meshes) - .bind_buffer(0, 3, transforms) - .bind_image(0, 4, hiz) - .bind_sampler(0, 5, hiz_sampler_info) - .bind_buffer(0, 6, visible_meshlet_instances_count) - .bind_buffer(0, 7, early_visible_meshlet_instances_count) - .bind_buffer(0, 8, late_visible_meshlet_instances_count) - .bind_buffer(0, 9, visible_meshlet_instances_indices) - .bind_buffer(0, 10, meshlet_instance_visibility_mask) - .bind_buffer(0, 11, cull_triangles_cmd) - .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(cull_flags, near_clip, projection_view)) - .specialize_constants(0, late) - .dispatch_indirect(dispatch_cmd); + auto &device = App::mod(); + auto &bindless_descriptor_set = device.get_descriptor_set(); + auto &asset_man = App::mod(); + auto shaders_root = asset_man.asset_root_path(AssetType::Shader); + + // ── EDITOR ────────────────────────────────────────────────────────── + auto default_slang_session = device.new_slang_session({ + .definitions = { +#ifdef LS_DEBUG + { "ENABLE_ASSERTIONS", "1" }, + { "DEBUG_DRAW", "1" }, +#endif // DEBUG + { "CULLING_MESH_COUNT", "64" }, + { "CULLING_MESHLET_COUNT", std::to_string(Model::MAX_MESHLET_INDICES) }, + { "CULLING_TRIANGLE_COUNT", std::to_string(Model::MAX_MESHLET_PRIMITIVES) }, + { "MESH_MAX_LODS", std::to_string(GPU::Mesh::MAX_LODS) }, + { "MAX_DIRECTIONAL_LIGHT_CASCADES", std::to_string(GPU::DirectionalLight::MAX_CASCADE_COUNT) }, + { "HISTOGRAM_THREADS_X", std::to_string(GPU::HISTOGRAM_THREADS_X) }, + { "HISTOGRAM_THREADS_Y", std::to_string(GPU::HISTOGRAM_THREADS_Y) }, + }, + .root_directory = shaders_root, + }).value(); + auto editor_grid_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.editor_grid", + .entry_points = { "vs_main", "fs_main" }, + }; + Pipeline::create(device, default_slang_session, editor_grid_pipeline_info).value(); + + auto editor_mousepick_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.editor_mousepick", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, editor_mousepick_pipeline_info).value(); + // ── SKY ───────────────────────────────────────────────────────────── + auto sky_transmittance_lut_info = ImageInfo{ + .format = vuk::Format::eR16G16B16A16Sfloat, + .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, + .type = vuk::ImageType::e2D, + .extent = { .width = 256, .height = 64, .depth = 1 }, + .name = "Sky Transmittance", + }; + std::tie(self.sky_transmittance_lut, self.sky_transmittance_lut_view) = Image::create_with_view(device, sky_transmittance_lut_info).value(); + auto sky_transmittance_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.sky_transmittance", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, sky_transmittance_pipeline_info).value(); + + auto sky_multiscatter_lut_info = ImageInfo{ + .format = vuk::Format::eR16G16B16A16Sfloat, + .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, + .type = vuk::ImageType::e2D, + .extent = { .width = 64, .height = 64, .depth = 1 }, + .name = "Sky Multiscatter LUT", + }; + std::tie(self.sky_multiscatter_lut, self.sky_multiscatter_lut_view) = Image::create_with_view(device, sky_multiscatter_lut_info).value(); + auto sky_multiscatter_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.sky_multiscattering", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, sky_multiscatter_pipeline_info).value(); + + auto sky_view_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.sky_view", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, sky_view_pipeline_info).value(); + + auto sky_aerial_perspective_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.sky_aerial_perspective", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, sky_aerial_perspective_pipeline_info).value(); + + auto sky_final_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.sky_final", + .entry_points = { "vs_main", "fs_main" }, + }; + Pipeline::create(device, default_slang_session, sky_final_pipeline_info).value(); - return std::make_tuple( - dispatch_cmd, - meshlet_instances, - mesh_instances, - meshes, - transforms, - hiz, - visible_meshlet_instances_count, - early_visible_meshlet_instances_count, - late_visible_meshlet_instances_count, - visible_meshlet_instances_indices, - meshlet_instance_visibility_mask, - cull_triangles_cmd - ); - } - ); + auto sky_cubemap_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.sky_cubemap", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, sky_cubemap_pipeline_info).value(); - auto cull_triangles_cmd_buffer = transfer_man.scratch_buffer({ .x = 0, .y = 1, .z = 1 }); + // ── VISBUFFER ─────────────────────────────────────────────────────── + auto generate_cull_commands_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.generate_cull_commands", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, generate_cull_commands_pipeline_info).value(); - std::tie( - cull_meshlets_cmd_buffer, - meshlet_instances_buffer, - mesh_instances_buffer, - meshes_buffer, - transforms_buffer, - hiz_attachment, - visible_meshlet_instances_count_buffer, - early_visible_meshlet_instances_count_buffer, - late_visible_meshlet_instances_count_buffer, - visible_meshlet_instances_indices_buffer, - meshlet_instance_visibility_mask_buffer, - cull_triangles_cmd_buffer - ) = - vis_cull_meshlets_pass( - std::move(cull_meshlets_cmd_buffer), - std::move(meshlet_instances_buffer), - std::move(mesh_instances_buffer), - std::move(meshes_buffer), - std::move(transforms_buffer), - std::move(hiz_attachment), - std::move(visible_meshlet_instances_count_buffer), - std::move(early_visible_meshlet_instances_count_buffer), - std::move(late_visible_meshlet_instances_count_buffer), - std::move(visible_meshlet_instances_indices_buffer), - std::move(meshlet_instance_visibility_mask_buffer), - std::move(cull_triangles_cmd_buffer) - ); + auto vis_cull_meshes_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.cull_meshes", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, vis_cull_meshes_pipeline_info).value(); - // ── CULL TRIANGLES ────────────────────────────────────────────────── - auto vis_cull_triangles_pass = vuk::make_pass( - stack.format("vis cull triangles {}", late ? "late" : "early"), - [late, cull_flags, resolution, projection_view]( - vuk::CommandBuffer &cmd_list, - VUK_BA(vuk::eIndirectRead) cull_triangles_cmd, - VUK_BA(vuk::eComputeRead) early_visible_meshlet_instances_count, - VUK_BA(vuk::eComputeRead) visible_meshlet_instances_indices, - VUK_BA(vuk::eComputeRead) meshlet_instances, - VUK_BA(vuk::eComputeRead) mesh_instances, - VUK_BA(vuk::eComputeRead) meshes, - VUK_BA(vuk::eComputeRead) transforms, - VUK_BA(vuk::eComputeRW) draw_indexed_cmd, - VUK_BA(vuk::eComputeWrite) reordered_indices - ) { - cmd_list // - .bind_compute_pipeline("passes.cull_triangles") - .bind_buffer(0, 0, early_visible_meshlet_instances_count) - .bind_buffer(0, 1, visible_meshlet_instances_indices) - .bind_buffer(0, 2, meshlet_instances) - .bind_buffer(0, 3, mesh_instances) - .bind_buffer(0, 4, meshes) - .bind_buffer(0, 5, transforms) - .bind_buffer(0, 6, draw_indexed_cmd) - .bind_buffer(0, 7, reordered_indices) - .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(cull_flags, resolution, projection_view)) - .specialize_constants(0, late) - .dispatch_indirect(cull_triangles_cmd); + auto vis_cull_meshlets_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.cull_meshlets", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, vis_cull_meshlets_pipeline_info).value(); - return std::make_tuple( - early_visible_meshlet_instances_count, - visible_meshlet_instances_indices, - meshlet_instances, - mesh_instances, - meshes, - transforms, - draw_indexed_cmd, - reordered_indices - ); - } - ); + auto vis_cull_triangles_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.cull_triangles", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, vis_cull_triangles_pipeline_info).value(); - auto draw_command_buffer = transfer_man.scratch_buffer({ .instanceCount = 1 }); + auto vis_encode_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.visbuffer_encode", + .entry_points = { "vs_main", "fs_main" }, + }; + Pipeline::create(device, default_slang_session, vis_encode_pipeline_info, bindless_descriptor_set).value(); - std::tie( - early_visible_meshlet_instances_count_buffer, - visible_meshlet_instances_indices_buffer, - meshlet_instances_buffer, - mesh_instances_buffer, - meshes_buffer, - transforms_buffer, - draw_command_buffer, - reordered_indices_buffer - ) = - vis_cull_triangles_pass( - std::move(cull_triangles_cmd_buffer), - std::move(early_visible_meshlet_instances_count_buffer), - std::move(visible_meshlet_instances_indices_buffer), - std::move(meshlet_instances_buffer), - std::move(mesh_instances_buffer), - std::move(meshes_buffer), - std::move(transforms_buffer), - std::move(draw_command_buffer), - std::move(reordered_indices_buffer) - ); + auto vis_clear_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.visbuffer_clear", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, vis_clear_pipeline_info).value(); - return draw_command_buffer; -} + auto vis_decode_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.visbuffer_decode", + .entry_points = { "vs_main", "fs_main" }, + }; + Pipeline::create(device, default_slang_session, vis_decode_pipeline_info, bindless_descriptor_set).value(); -static auto draw_visbuffer( - bool late, - glm::mat4 &projection_view, - vuk::PersistentDescriptorSet &descriptor_set, - vuk::Value &depth_attachment, - vuk::Value &visbuffer_attachment, - vuk::Value &overdraw_attachment, - vuk::Value &draw_command_buffer, - vuk::Value &reordered_indices_buffer, - vuk::Value &meshes_buffer, - vuk::Value &mesh_instances_buffer, - vuk::Value &meshlet_instances_buffer, - vuk::Value &transforms_buffer, - vuk::Value &materials_buffer -) -> void { - ZoneScoped; - memory::ScopedStack stack; + auto shadowmap_cull_meshlets = PipelineCompileInfo{ + .module_name = "passes.shadowmap_cull_meshlets", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, shadowmap_cull_meshlets).value(); - auto vis_encode_pass = vuk::make_pass( - stack.format("vis encode {}", late ? "late" : "early"), - [&descriptor_set, projection_view]( - vuk::CommandBuffer &cmd_list, - VUK_BA(vuk::eIndirectRead) triangle_indirect, - VUK_BA(vuk::eIndexRead) index_buffer, - VUK_BA(vuk::eVertexRead) meshlet_instances, - VUK_BA(vuk::eVertexRead) mesh_instances, - VUK_BA(vuk::eVertexRead) meshes, - VUK_BA(vuk::eVertexRead) transforms, - VUK_BA(vuk::eFragmentRead) materials, - VUK_IA(vuk::eColorRW) visbuffer, - VUK_IA(vuk::eDepthStencilRW) depth, - VUK_IA(vuk::eFragmentRW) overdraw - ) { - cmd_list // - .bind_graphics_pipeline("passes.visbuffer_encode") - .set_rasterization({ .cullMode = vuk::CullModeFlagBits::eBack }) - .set_depth_stencil({ .depthTestEnable = true, .depthWriteEnable = true, .depthCompareOp = vuk::CompareOp::eGreaterOrEqual }) - .set_color_blend(visbuffer, vuk::BlendPreset::eOff) - .set_dynamic_state(vuk::DynamicStateFlagBits::eViewport | vuk::DynamicStateFlagBits::eScissor) - .set_viewport(0, vuk::Rect2D::framebuffer()) - .set_scissor(0, vuk::Rect2D::framebuffer()) - .bind_persistent(1, descriptor_set) - .bind_buffer(0, 0, meshlet_instances) - .bind_buffer(0, 1, mesh_instances) - .bind_buffer(0, 2, meshes) - .bind_buffer(0, 3, transforms) - .bind_buffer(0, 4, materials) - .bind_image(0, 5, overdraw) - .bind_index_buffer(index_buffer, vuk::IndexType::eUint32) - .push_constants(vuk::ShaderStageFlagBits::eVertex, 0, projection_view) - .draw_indexed_indirect(1, triangle_indirect); + auto shadowmap_draw = PipelineCompileInfo{ + .module_name = "passes.shadowmap_draw", + .entry_points = { "vs_main" }, + }; + Pipeline::create(device, default_slang_session, shadowmap_draw).value(); + + // ── PBR ───────────────────────────────────────────────────────────── + auto pbr_apply_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.pbr_apply", + .entry_points = { "vs_main", "fs_main" }, + }; + Pipeline::create(device, default_slang_session, pbr_apply_pipeline_info).value(); - return std::make_tuple(index_buffer, meshlet_instances, mesh_instances, meshes, transforms, materials, visbuffer, depth, overdraw); - } - ); + // ── POST PROCESS ──────────────────────────────────────────────────── + auto histogram_generate_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.histogram_generate", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, histogram_generate_pipeline_info).value(); - std::tie( - reordered_indices_buffer, - meshlet_instances_buffer, - mesh_instances_buffer, - meshes_buffer, - transforms_buffer, - materials_buffer, - visbuffer_attachment, - depth_attachment, - overdraw_attachment - ) = - vis_encode_pass( - std::move(draw_command_buffer), - std::move(reordered_indices_buffer), - std::move(meshlet_instances_buffer), - std::move(mesh_instances_buffer), - std::move(meshes_buffer), - std::move(transforms_buffer), - std::move(materials_buffer), - std::move(visbuffer_attachment), - std::move(depth_attachment), - std::move(overdraw_attachment) - ); -} + auto histogram_average_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.histogram_average", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, histogram_average_pipeline_info).value(); -auto cull_shadowmap_meshlets( - TransferManager &transfer_man, - u32 cascade_index, - glm::vec2 &resolution, - glm::mat4 &projection_view, - vuk::Value &cull_meshlets_cmd_buffer, - vuk::Value &all_visible_meshlet_instances_count_buffer, - vuk::Value &visible_meshlet_instances_count_buffer, - vuk::Value &visible_meshlet_instances_indices_buffer, - vuk::Value &reordered_indices_buffer, - vuk::Value &meshes_buffer, - vuk::Value &mesh_instances_buffer, - vuk::Value &meshlet_instances_buffer, - vuk::Value &transforms_buffer -) -> vuk::Value { - ZoneScoped; - memory::ScopedStack stack; + auto tonemap_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.tonemap", + .entry_points = { "vs_main", "fs_main" }, + }; + Pipeline::create(device, default_slang_session, tonemap_pipeline_info).value(); - // ── CULL MESHLETS ─────────────────────────────────────────────────── - auto shadowmap_cull_meshlets_pass = vuk::make_pass( - stack.format("shadowmap cull meshlets cascade {}", cascade_index), - [projection_view]( - vuk::CommandBuffer &cmd_list, - VUK_BA(vuk::eIndirectRead) dispatch_cmd, - VUK_BA(vuk::eComputeRead) meshlet_instances, - VUK_BA(vuk::eComputeRead) mesh_instances, - VUK_BA(vuk::eComputeRead) meshes, - VUK_BA(vuk::eComputeRead) transforms, - VUK_BA(vuk::eComputeRead) all_visible_meshlet_instances_count, - VUK_BA(vuk::eComputeRW) visible_meshlet_instances_count, - VUK_BA(vuk::eComputeRW) visible_meshlet_instances_indices, - VUK_BA(vuk::eComputeRW) cull_triangles_cmd - ) { - cmd_list // - .bind_compute_pipeline("passes.shadowmap_cull_meshlets") - .bind_buffer(0, 0, meshlet_instances) - .bind_buffer(0, 1, mesh_instances) - .bind_buffer(0, 2, meshes) - .bind_buffer(0, 3, transforms) - .bind_buffer(0, 4, all_visible_meshlet_instances_count) - .bind_buffer(0, 5, visible_meshlet_instances_count) - .bind_buffer(0, 6, visible_meshlet_instances_indices) - .bind_buffer(0, 7, cull_triangles_cmd) - .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, projection_view) - .dispatch_indirect(dispatch_cmd); + auto debug_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.debug", + .entry_points = { "vs_main", "fs_main" }, + }; + Pipeline::create(device, default_slang_session, debug_pipeline_info).value(); - return std::make_tuple( - dispatch_cmd, - meshlet_instances, - mesh_instances, - meshes, - transforms, - all_visible_meshlet_instances_count, - visible_meshlet_instances_count, - visible_meshlet_instances_indices, - cull_triangles_cmd - ); - } - ); + auto copy_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.copy", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, copy_pipeline_info).value(); - auto cull_triangles_cmd_buffer = transfer_man.scratch_buffer({ .x = 0, .y = 1, .z = 1 }); + auto hiz_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.hiz", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, hiz_pipeline_info).value(); - std::tie( - cull_meshlets_cmd_buffer, - meshlet_instances_buffer, - mesh_instances_buffer, - meshes_buffer, - transforms_buffer, - all_visible_meshlet_instances_count_buffer, - visible_meshlet_instances_count_buffer, - visible_meshlet_instances_indices_buffer, - cull_triangles_cmd_buffer - ) = - shadowmap_cull_meshlets_pass( - std::move(cull_meshlets_cmd_buffer), - std::move(meshlet_instances_buffer), - std::move(mesh_instances_buffer), - std::move(meshes_buffer), - std::move(transforms_buffer), - std::move(all_visible_meshlet_instances_count_buffer), - std::move(visible_meshlet_instances_count_buffer), - std::move(visible_meshlet_instances_indices_buffer), - std::move(cull_triangles_cmd_buffer) - ); + auto hiz_slow_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.hiz_slow", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, hiz_slow_pipeline_info).value(); - // ── CULL TRIANGLES ────────────────────────────────────────────────── - auto shadowmap_cull_triangles_pass = vuk::make_pass( - stack.format("shadowmap cull triangles cascade {}", cascade_index), - [resolution, projection_view]( - vuk::CommandBuffer &cmd_list, - VUK_BA(vuk::eIndirectRead) cull_triangles_cmd, - VUK_BA(vuk::eComputeRead) visible_meshlet_instances_count, - VUK_BA(vuk::eComputeRead) visible_meshlet_instances_indices, - VUK_BA(vuk::eComputeRead) meshlet_instances, - VUK_BA(vuk::eComputeRead) mesh_instances, - VUK_BA(vuk::eComputeRead) meshes, - VUK_BA(vuk::eComputeRead) transforms, - VUK_BA(vuk::eComputeRW) draw_indexed_cmd, - VUK_BA(vuk::eComputeWrite) reordered_indices - ) { - auto cull_flags = GPU::CullFlags::All; - cmd_list // - .bind_compute_pipeline("passes.cull_triangles") - .bind_buffer(0, 0, visible_meshlet_instances_count) - .bind_buffer(0, 1, visible_meshlet_instances_indices) - .bind_buffer(0, 2, meshlet_instances) - .bind_buffer(0, 3, mesh_instances) - .bind_buffer(0, 4, meshes) - .bind_buffer(0, 5, transforms) - .bind_buffer(0, 6, draw_indexed_cmd) - .bind_buffer(0, 7, reordered_indices) - .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(cull_flags, resolution, projection_view)) - .specialize_constants(0, false) - .dispatch_indirect(cull_triangles_cmd); + auto visualize_overdraw_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.visualize_overdraw", + .entry_points = { "vs_main", "fs_main" }, + }; + Pipeline::create(device, default_slang_session, visualize_overdraw_pipeline_info).value(); - return std::make_tuple( - visible_meshlet_instances_count, - visible_meshlet_instances_indices, - meshlet_instances, - mesh_instances, - meshes, - transforms, - draw_indexed_cmd, - reordered_indices - ); - } - ); + auto vbgtao_prefilter_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.vbgtao_prefilter", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, vbgtao_prefilter_pipeline_info).value(); - auto draw_command_buffer = transfer_man.scratch_buffer({ .instanceCount = 1 }); + auto vbgtao_generate_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.vbgtao_generate", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, vbgtao_generate_pipeline_info).value(); - std::tie( - visible_meshlet_instances_count_buffer, - visible_meshlet_instances_indices_buffer, - meshlet_instances_buffer, - mesh_instances_buffer, - meshes_buffer, - transforms_buffer, - draw_command_buffer, - reordered_indices_buffer - ) = - shadowmap_cull_triangles_pass( - std::move(cull_triangles_cmd_buffer), - std::move(visible_meshlet_instances_count_buffer), - std::move(visible_meshlet_instances_indices_buffer), - std::move(meshlet_instances_buffer), - std::move(mesh_instances_buffer), - std::move(meshes_buffer), - std::move(transforms_buffer), - std::move(draw_command_buffer), - std::move(reordered_indices_buffer) - ); + auto vbgtao_denoise_pipeline_info = PipelineCompileInfo{ + .module_name = "passes.vbgtao_denoise", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, vbgtao_denoise_pipeline_info).value(); - return draw_command_buffer; -} + self.histogram_luminance_buffer = Buffer::create(device, sizeof(GPU::HistogramLuminance)).value(); + vuk::fill(vuk::acquire_buf("histogram luminance", *device.buffer(self.histogram_luminance_buffer.id()), vuk::eNone), 0); -auto draw_shadowmap( - u32 cascade_index, - glm::mat4 &projection_view, - vuk::Value &depth_attachment, - vuk::Value &draw_command_buffer, - vuk::Value &reordered_indices_buffer, - vuk::Value &meshes_buffer, - vuk::Value &mesh_instances_buffer, - vuk::Value &meshlet_instances_buffer, - vuk::Value &transforms_buffer -) -> void { - ZoneScoped; - memory::ScopedStack stack; + // Hilbert Noise LUT + constexpr auto HILBERT_NOISE_LUT_WIDTH = 64_u32; + auto hilbert_noise_lut_info = ImageInfo{ + .format = vuk::Format::eR16Uint, + .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eTransferDst, + .type = vuk::ImageType::e2D, + .extent = { .width = HILBERT_NOISE_LUT_WIDTH, .height = HILBERT_NOISE_LUT_WIDTH, .depth = 1 }, + .name = "Hilbert Noise LUT", + }; + std::tie(self.hilbert_noise_lut, self.hilbert_noise_lut_view) = Image::create_with_view(device, hilbert_noise_lut_info).value(); - auto draw_shadowmap_pass = vuk::make_pass( - stack.format("draw shadowmap casecade {}", cascade_index), - [projection_view]( - vuk::CommandBuffer &cmd_list, - VUK_BA(vuk::eIndirectRead) triangle_indirect, - VUK_BA(vuk::eIndexRead) index_buffer, - VUK_BA(vuk::eVertexRead) meshlet_instances, - VUK_BA(vuk::eVertexRead) mesh_instances, - VUK_BA(vuk::eVertexRead) meshes, - VUK_BA(vuk::eVertexRead) transforms, - VUK_IA(vuk::eDepthStencilRW) depth - ) { - cmd_list // - .bind_graphics_pipeline("passes.shadowmap_draw") - .set_rasterization({ .cullMode = vuk::CullModeFlagBits::eBack }) - .set_depth_stencil({ .depthTestEnable = true, .depthWriteEnable = true, .depthCompareOp = vuk::CompareOp::eGreaterOrEqual }) - .set_dynamic_state(vuk::DynamicStateFlagBits::eViewport | vuk::DynamicStateFlagBits::eScissor) - .set_viewport(0, vuk::Rect2D::framebuffer()) - .set_scissor(0, vuk::Rect2D::framebuffer()) - .bind_buffer(0, 0, meshlet_instances) - .bind_buffer(0, 1, mesh_instances) - .bind_buffer(0, 2, meshes) - .bind_buffer(0, 3, transforms) - .bind_index_buffer(index_buffer, vuk::IndexType::eUint32) - .push_constants(vuk::ShaderStageFlagBits::eVertex, 0, projection_view) - .draw_indexed_indirect(1, triangle_indirect); + auto hilbert_index = [](u32 pos_x, u32 pos_y) -> u16 { + auto index = 0_u32; + for (auto cur_level = HILBERT_NOISE_LUT_WIDTH / 2; cur_level > 0_u32; cur_level /= 2_u32) { + auto region_x = (pos_x & cur_level) > 0_u32; + auto region_y = (pos_y & cur_level) > 0_u32; + index += cur_level * cur_level * ((3_u32 * region_x) ^ region_y); + if (region_y == 0_u32) { + if (region_x == 1_u32) { + pos_x = (HILBERT_NOISE_LUT_WIDTH - 1_u32) - pos_x; + pos_y = (HILBERT_NOISE_LUT_WIDTH - 1_u32) - pos_y; + } - return std::make_tuple(index_buffer, meshlet_instances, mesh_instances, meshes, transforms, depth); + auto temp_pos_x = pos_x; + pos_x = pos_y; + pos_y = temp_pos_x; + } } - ); - std::tie(reordered_indices_buffer, meshlet_instances_buffer, mesh_instances_buffer, meshes_buffer, transforms_buffer, depth_attachment) = - draw_shadowmap_pass( - std::move(draw_command_buffer), - std::move(reordered_indices_buffer), - std::move(meshlet_instances_buffer), - std::move(mesh_instances_buffer), - std::move(meshes_buffer), - std::move(transforms_buffer), - std::move(depth_attachment) - ); -} + return index; + }; -static auto draw_hiz(vuk::Value &hiz_attachment, vuk::Value &depth_attachment) -> void { - ZoneScoped; + u16 hilbert_noise[HILBERT_NOISE_LUT_WIDTH * HILBERT_NOISE_LUT_WIDTH] = {}; + for (auto y = 0_u32; y < HILBERT_NOISE_LUT_WIDTH; y++) { + for (auto x = 0_u32; x < HILBERT_NOISE_LUT_WIDTH; x++) { + hilbert_noise[y * HILBERT_NOISE_LUT_WIDTH + x] = hilbert_index(x, y); + } + } - auto hiz_generate_pass = vuk::make_pass( - "hiz generate", - [](vuk::CommandBuffer &cmd_list, // - VUK_IA(vuk::eComputeSampled) src, - VUK_IA(vuk::eComputeRW) dst) { - auto extent = dst->extent; - auto mip_count = dst->level_count; - LS_EXPECT(mip_count < 13); + auto &transfer_man = device.transfer_man(); - auto dispatch_x = (extent.width + 63) >> 6; - auto dispatch_y = (extent.height + 63) >> 6; + auto hilbert_noise_size_bytes = HILBERT_NOISE_LUT_WIDTH * HILBERT_NOISE_LUT_WIDTH * sizeof(u16); + auto hilbert_noise_buffer = transfer_man.alloc_image_buffer(hilbert_noise_lut_info.format, hilbert_noise_lut_info.extent); + std::memcpy(hilbert_noise_buffer->mapped_ptr, hilbert_noise, hilbert_noise_size_bytes); - cmd_list // - .bind_compute_pipeline("passes.hiz") - .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(mip_count, (dispatch_x * dispatch_y) - 1, glm::mat2(1.0f))) - .specialize_constants(0, extent.width == extent.height && (extent.width & (extent.width - 1)) == 0 ? 1u : 0u) - .specialize_constants(1, extent.width) - .specialize_constants(2, extent.height); + auto hilbert_noise_lut_attachment = self.hilbert_noise_lut_view.discard(device, "hilbert noise", vuk::ImageUsageFlagBits::eTransferDst); + hilbert_noise_lut_attachment = transfer_man.upload(std::move(hilbert_noise_buffer), std::move(hilbert_noise_lut_attachment)); + hilbert_noise_lut_attachment = hilbert_noise_lut_attachment.as_released(vuk::eComputeSampled, vuk::DomainFlagBits::eGraphicsQueue); + transfer_man.wait_on(std::move(hilbert_noise_lut_attachment)); - *cmd_list.scratch_buffer(0, 0) = 0; - cmd_list.bind_sampler(0, 1, hiz_sampler_info); - cmd_list.bind_image(0, 2, src); + return true; +} - for (u32 i = 0; i < 13; i++) { - cmd_list.bind_image(0, i + 3, dst->mip(ls::min(i, mip_count - 1_u32))); - } +auto SceneRenderer::destroy(this SceneRenderer &self) -> void { + ZoneScoped; - cmd_list.dispatch(dispatch_x, dispatch_y); + self.cleanup(); +} - return std::make_tuple(src, dst); - } - ); +auto SceneRenderer::prepare_frame(this SceneRenderer &self, FramePrepareInfo &info) -> PreparedFrame { + ZoneScoped; - auto hiz_generate_slow_pass = vuk::make_pass( - "hiz generate slow", - [](vuk::CommandBuffer &cmd_list, // - VUK_IA(vuk::eComputeSampled) src, - VUK_IA(vuk::eComputeRW) dst) { - auto extent = dst->extent; - auto mip_count = dst->level_count; + auto &device = App::mod(); + auto &transfer_man = device.transfer_man(); + auto prepared_frame = PreparedFrame{}; - cmd_list // - .bind_compute_pipeline("passes.hiz_slow") - .bind_sampler(0, 0, hiz_sampler_info); + auto zero_fill_pass = vuk::make_pass("zero fill", [](vuk::CommandBuffer &command_buffer, VUK_BA(vuk::eTransferWrite) dst) { + command_buffer.fill_buffer(dst, 0_u32); - for (auto i = 0_u32; i < mip_count; i++) { - auto mip_width = std::max(1_u32, extent.width >> i); - auto mip_height = std::max(1_u32, extent.height >> i); + return dst; + }); - auto mip = dst->mip(i); - if (i == 0) { - cmd_list.bind_image(0, 1, src); - } else { - auto mip = dst->mip(i - 1); - cmd_list.image_barrier(mip, vuk::eComputeWrite, vuk::eComputeSampled); - cmd_list.bind_image(0, 1, mip); - } + if (!info.dirty_transform_ids.empty()) { + auto rebuild_transforms = !self.transforms_buffer || self.transforms_buffer.data_size() <= info.gpu_transforms.size_bytes(); + self.transforms_buffer = self.transforms_buffer.resize(device, info.gpu_transforms.size_bytes()).value(); + prepared_frame.transforms_buffer = self.transforms_buffer.acquire(device, "transforms", rebuild_transforms ? vuk::eNone : vuk::eMemoryRead); - cmd_list.bind_image(0, 2, mip); - cmd_list.push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(mip_width, mip_height, i)); - cmd_list.dispatch_invocations(mip_width, mip_height); + if (rebuild_transforms) { + // If we resize buffer, we need to refill it again, so individual uploads are not required. + prepared_frame.transforms_buffer = transfer_man.upload(info.gpu_transforms, std::move(prepared_frame.transforms_buffer)); + } else { + // Buffer is not resized, upload individual transforms. + auto dirty_transforms_count = info.dirty_transform_ids.size(); + auto dirty_transforms_size_bytes = dirty_transforms_count * sizeof(GPU::Transforms); + auto upload_buffer = transfer_man.alloc_transient_buffer(vuk::MemoryUsage::eCPUtoGPU, dirty_transforms_size_bytes); + auto *dst_transform_ptr = reinterpret_cast(upload_buffer->mapped_ptr); + auto upload_offsets = std::vector(dirty_transforms_count); + + for (const auto &[dirty_transform_id, offset] : std::views::zip(info.dirty_transform_ids, upload_offsets)) { + auto index = SlotMap_decode_id(dirty_transform_id).index; + const auto &transform = info.gpu_transforms[index]; + std::memcpy(dst_transform_ptr, &transform, sizeof(GPU::Transforms)); + offset = index * sizeof(GPU::Transforms); + dst_transform_ptr++; } - cmd_list.image_barrier(dst, vuk::eComputeSampled, vuk::eComputeRW); + auto update_transforms_pass = vuk::make_pass( + "update scene transforms", + [upload_offsets = std::move(upload_offsets)]( + vuk::CommandBuffer &cmd_list, // + VUK_BA(vuk::Access::eTransferRead) src_buffer, + VUK_BA(vuk::Access::eTransferWrite) dst_buffer + ) { + for (usize i = 0; i < upload_offsets.size(); i++) { + auto offset = upload_offsets[i]; + auto src_subrange = src_buffer->subrange(i * sizeof(GPU::Transforms), sizeof(GPU::Transforms)); + auto dst_subrange = dst_buffer->subrange(offset, sizeof(GPU::Transforms)); + cmd_list.copy_buffer(src_subrange, dst_subrange); + } - return std::make_tuple(src, dst); - } - ); + return dst_buffer; + } + ); - std::tie(depth_attachment, hiz_attachment) = hiz_generate_slow_pass(std::move(depth_attachment), std::move(hiz_attachment)); -} + prepared_frame.transforms_buffer = update_transforms_pass(std::move(upload_buffer), std::move(prepared_frame.transforms_buffer)); + } + } else if (self.transforms_buffer) { + prepared_frame.transforms_buffer = self.transforms_buffer.acquire(device, "transforms", vuk::Access::eMemoryRead); + } -static auto draw_sky( - SceneRenderer &self, - u32 frame_index, - vuk::Value &sky_transmittance_lut_attachment, - vuk::Value &sky_multiscatter_lut_attachment, - vuk::Value &environment_buffer, - vuk::Value &camera_buffer -) -> std::tuple, vuk::Value> { - ZoneScoped; + if (!info.dirty_material_indices.empty()) { + auto rebuild_materials = !self.materials_buffer || self.materials_buffer.data_size() <= info.gpu_materials.size_bytes(); + self.materials_buffer = self.materials_buffer.resize(device, info.gpu_materials.size_bytes()).value(); + prepared_frame.materials_buffer = self.materials_buffer.acquire(device, "materials", rebuild_materials ? vuk::eNone : vuk::eMemoryRead); - auto sky_view_lut_attachment = vuk::declare_ia( - "sky view lut", - { .image_type = vuk::ImageType::e2D, - .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, - .extent = self.sky_view_lut_extent, - .format = vuk::Format::eR16G16B16A16Sfloat, - .sample_count = vuk::Samples::e1, - .view_type = vuk::ImageViewType::e2D, - .level_count = 1, - .layer_count = 1 } - ); + if (rebuild_materials) { + prepared_frame.materials_buffer = transfer_man.upload(info.gpu_materials, std::move(prepared_frame.materials_buffer)); + } else { + // TODO: Literally repeating code, find a solution to this + auto dirty_materials_count = info.dirty_material_indices.size(); + auto dirty_materials_size_bytes = dirty_materials_count * sizeof(GPU::Material); + auto upload_buffer = transfer_man.alloc_transient_buffer(vuk::MemoryUsage::eCPUtoGPU, dirty_materials_size_bytes); + auto *dst_materials_ptr = reinterpret_cast(upload_buffer->mapped_ptr); + auto upload_offsets = std::vector(dirty_materials_count); - auto sky_cubemap_attachment = vuk::declare_ia( - "sky cubemap", - { .image_flags = vuk::ImageCreateFlagBits::eCubeCompatible, - .image_type = vuk::ImageType::e2D, - .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, - .extent = self.sky_cubemap_extent, - .format = vuk::Format::eR16G16B16A16Sfloat, - .sample_count = vuk::Samples::e1, - .view_type = vuk::ImageViewType::eCube, - .level_count = 1, - .layer_count = 6 } - ); + for (const auto &[dirty_material, index, offset] : std::views::zip(info.gpu_materials, info.dirty_material_indices, upload_offsets)) { + std::memcpy(dst_materials_ptr, &dirty_material, sizeof(GPU::Material)); + offset = index * sizeof(GPU::Material); + dst_materials_ptr++; + } - // ── SKY VIEW LUT ──────────────────────────────────────────────────── - auto sky_view_pass = vuk::make_pass( - "sky view", - [](vuk::CommandBuffer &cmd_list, // - VUK_IA(vuk::eComputeSampled) sky_transmittance_lut, - VUK_IA(vuk::eComputeSampled) sky_multiscatter_lut, - VUK_BA(vuk::eComputeRead) environment, - VUK_BA(vuk::eComputeRead) camera, - VUK_IA(vuk::eComputeRW) sky_view_lut) { - auto linear_clamp_sampler = vuk::SamplerCreateInfo{ - .magFilter = vuk::Filter::eLinear, - .minFilter = vuk::Filter::eLinear, - .addressModeU = vuk::SamplerAddressMode::eClampToEdge, - .addressModeV = vuk::SamplerAddressMode::eClampToEdge, - .addressModeW = vuk::SamplerAddressMode::eClampToEdge, - }; + auto update_materials_pass = vuk::make_pass( + "update scene materials", + [upload_offsets = std::move(upload_offsets)]( + vuk::CommandBuffer &cmd_list, // + VUK_BA(vuk::Access::eTransferRead) src_buffer, + VUK_BA(vuk::Access::eTransferWrite) dst_buffer + ) { + for (usize i = 0; i < upload_offsets.size(); i++) { + auto offset = upload_offsets[i]; + auto src_subrange = src_buffer->subrange(i * sizeof(GPU::Material), sizeof(GPU::Material)); + auto dst_subrange = dst_buffer->subrange(offset, sizeof(GPU::Material)); + cmd_list.copy_buffer(src_subrange, dst_subrange); + } - cmd_list // - .bind_compute_pipeline("passes.sky_view") - .bind_sampler(0, 0, linear_clamp_sampler) - .bind_image(0, 1, sky_transmittance_lut) - .bind_image(0, 2, sky_multiscatter_lut) - .bind_buffer(0, 3, environment) - .bind_buffer(0, 4, camera) - .bind_image(0, 5, sky_view_lut) - .dispatch_invocations_per_pixel(sky_view_lut); - return std::make_tuple(sky_transmittance_lut, sky_multiscatter_lut, environment, camera, sky_view_lut); + return dst_buffer; + } + ); + + prepared_frame.materials_buffer = update_materials_pass(std::move(upload_buffer), std::move(prepared_frame.materials_buffer)); } - ); + } else if (self.materials_buffer) { + prepared_frame.materials_buffer = self.materials_buffer.acquire(device, "materials", vuk::eMemoryRead); + } - std::tie(sky_transmittance_lut_attachment, sky_multiscatter_lut_attachment, environment_buffer, camera_buffer, sky_view_lut_attachment) = - sky_view_pass( - std::move(sky_transmittance_lut_attachment), - std::move(sky_multiscatter_lut_attachment), - std::move(environment_buffer), - std::move(camera_buffer), - std::move(sky_view_lut_attachment) - ); + if (!info.gpu_meshes.empty()) { + self.meshes_buffer = self.meshes_buffer.resize(device, info.gpu_meshes.size_bytes()).value(); + prepared_frame.meshes_buffer = self.meshes_buffer.acquire(device, "meshes", vuk::eNone); + prepared_frame.meshes_buffer = transfer_man.upload(info.gpu_meshes, std::move(prepared_frame.meshes_buffer)); + } else if (self.meshes_buffer) { + prepared_frame.meshes_buffer = self.meshes_buffer.acquire(device, "meshes", vuk::eMemoryRead); + } - auto sky_cubemap_pass = vuk::make_pass( - "sky cubemap", - [frame_index]( - vuk::CommandBuffer &cmd_list, // - VUK_IA(vuk::eComputeSampled) sky_view_lut, - VUK_BA(vuk::eComputeRead) environment, - VUK_BA(vuk::eComputeRead) camera, - VUK_IA(vuk::eComputeRW) sky_cubemap - ) { - auto linear_clamp_sampler = vuk::SamplerCreateInfo{ - .magFilter = vuk::Filter::eLinear, - .minFilter = vuk::Filter::eLinear, - .addressModeU = vuk::SamplerAddressMode::eClampToEdge, - .addressModeV = vuk::SamplerAddressMode::eClampToEdge, - .addressModeW = vuk::SamplerAddressMode::eClampToEdge, - }; + if (!info.gpu_mesh_instances.empty()) { + self.mesh_instances_buffer = self.mesh_instances_buffer.resize(device, info.gpu_mesh_instances.size_bytes()).value(); + prepared_frame.mesh_instances_buffer = self.mesh_instances_buffer.acquire(device, "mesh instances", vuk::eNone); + prepared_frame.mesh_instances_buffer = transfer_man.upload(info.gpu_mesh_instances, std::move(prepared_frame.mesh_instances_buffer)); - cmd_list // - .bind_compute_pipeline("passes.sky_cubemap") - .bind_sampler(0, 0, linear_clamp_sampler) - .bind_image(0, 1, sky_view_lut) - .bind_buffer(0, 2, environment) - .bind_buffer(0, 3, camera) - .bind_image(0, 4, sky_cubemap) - .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, frame_index) - .dispatch((sky_cubemap->extent.width + 2 - 1) / 2, (sky_cubemap->extent.height + 2 - 1) / 2, 6); + auto meshlet_instance_visibility_mask_size_bytes = (info.max_meshlet_instance_count + 31) / 32 * sizeof(u32); + self.meshlet_instance_visibility_mask_buffer = + self.meshlet_instance_visibility_mask_buffer.resize(device, meshlet_instance_visibility_mask_size_bytes).value(); + prepared_frame.meshlet_instance_visibility_mask_buffer = + self.meshlet_instance_visibility_mask_buffer.acquire(device, "meshlet instances visibility mask", vuk::eNone); + prepared_frame.meshlet_instance_visibility_mask_buffer = zero_fill_pass(std::move(prepared_frame.meshlet_instance_visibility_mask_buffer)); + } else if (self.mesh_instances_buffer) { + prepared_frame.mesh_instances_buffer = self.mesh_instances_buffer.acquire(device, "mesh instances", vuk::eMemoryRead); + prepared_frame.meshlet_instance_visibility_mask_buffer = + self.meshlet_instance_visibility_mask_buffer.acquire(device, "meshlet instances visibility mask", vuk::eMemoryRead); + } - return std::make_tuple(sky_view_lut, environment, camera, sky_cubemap); - } - ); + prepared_frame.camera_buffer = transfer_man.scratch_buffer(info.camera); + if (info.directional_light.has_value()) { + auto &directional_light = info.directional_light.value(); + prepared_frame.directional_light_buffer = transfer_man.scratch_buffer(directional_light); - std::tie(sky_view_lut_attachment, environment_buffer, camera_buffer, sky_cubemap_attachment) = sky_cubemap_pass( - std::move(sky_view_lut_attachment), - std::move(environment_buffer), - std::move(camera_buffer), - std::move(sky_cubemap_attachment) - ); + for (u32 i = 0_u32; i < directional_light.cascade_count; i++) { + prepared_frame.directional_light_cascade_projections[i] = info.directional_light_cascades[i].projection_view_mat; + } + } + prepared_frame.directional_light_cascades_buffer = transfer_man.scratch_buffer(info.directional_light_cascades); - return std::make_tuple(sky_view_lut_attachment, sky_cubemap_attachment); -} + if (info.atmosphere.has_value()) { + auto &atmosphere = info.atmosphere.value(); + atmosphere.transmittance_lut_size = self.sky_transmittance_lut_view.extent(); + atmosphere.sky_view_lut_size = self.sky_view_lut_extent; + atmosphere.multiscattering_lut_size = self.sky_multiscatter_lut_view.extent(); + atmosphere.aerial_perspective_lut_size = self.sky_aerial_perspective_lut_extent; -static auto apply_sky( - SceneRenderer &self, - vuk::Value &dst_attachment, - vuk::Value &depth_attachment, - vuk::Value &sky_view_lut_attachment, - vuk::Value &sky_transmittance_lut_attachment, - vuk::Value &sky_multiscatter_lut_attachment, - vuk::Value &environment_buffer, - vuk::Value &camera_buffer -) -> void { - ZoneScoped; + prepared_frame.atmosphere_buffer = transfer_man.scratch_buffer(atmosphere); + } - auto sky_aerial_perspective_attachment = vuk::declare_ia( - "sky aerial perspective", - { .image_type = vuk::ImageType::e3D, - .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, - .extent = self.sky_aerial_perspective_lut_extent, - .sample_count = vuk::Samples::e1, - .view_type = vuk::ImageViewType::e3D, - .level_count = 1, - .layer_count = 1 } - ); - sky_aerial_perspective_attachment.same_format_as(sky_view_lut_attachment); + if (info.eye_adaptation.has_value()) { + auto &eye_adaptation = info.eye_adaptation.value(); + prepared_frame.eye_adaptation_buffer = transfer_man.scratch_buffer(eye_adaptation); + } - // ── SKY AERIAL PERSPECTIVE ────────────────────────────────────────── - auto sky_aerial_perspective_pass = vuk::make_pass( - "sky aerial perspective", - [](vuk::CommandBuffer &cmd_list, // - VUK_IA(vuk::eComputeSampled) sky_transmittance_lut, - VUK_IA(vuk::eComputeSampled) sky_multiscatter_lut, - VUK_BA(vuk::eComputeRead) environment, - VUK_BA(vuk::eComputeRead) camera, - VUK_IA(vuk::eComputeRW) sky_aerial_perspective_lut) { - auto linear_clamp_sampler = vuk::SamplerCreateInfo{ - .magFilter = vuk::Filter::eLinear, - .minFilter = vuk::Filter::eLinear, - .addressModeU = vuk::SamplerAddressMode::eClampToEdge, - .addressModeV = vuk::SamplerAddressMode::eClampToEdge, - .addressModeW = vuk::SamplerAddressMode::eClampToEdge, - }; + if (info.vbgtao.has_value()) { + auto &vbgtao = info.vbgtao.value(); + prepared_frame.vbgtao_buffer = transfer_man.scratch_buffer(vbgtao); + } - cmd_list // - .bind_compute_pipeline("passes.sky_aerial_perspective") - .bind_sampler(0, 0, linear_clamp_sampler) - .bind_image(0, 1, sky_transmittance_lut) - .bind_image(0, 2, sky_multiscatter_lut) - .bind_buffer(0, 3, environment) - .bind_buffer(0, 4, camera) - .bind_image(0, 5, sky_aerial_perspective_lut) - .dispatch_invocations_per_pixel(sky_aerial_perspective_lut); + prepared_frame.mesh_instance_count = info.mesh_instance_count; + prepared_frame.max_meshlet_instance_count = info.max_meshlet_instance_count; - return std::make_tuple(sky_transmittance_lut, sky_multiscatter_lut, environment, camera, sky_aerial_perspective_lut); - } - ); + if (info.regenerate_sky || !self.sky_transmittance_lut_view) { + auto transmittance_lut_pass = vuk::make_pass( + "transmittance lut", + [](vuk::CommandBuffer &cmd_list, // + VUK_IA(vuk::eComputeRW) dst, + VUK_BA(vuk::eComputeRead) atmosphere) { + cmd_list // + .bind_compute_pipeline("passes.sky_transmittance") + .bind_image(0, 0, dst) + .bind_buffer(0, 1, atmosphere) + .dispatch_invocations_per_pixel(dst); - std::tie( - sky_transmittance_lut_attachment, - sky_multiscatter_lut_attachment, - environment_buffer, - camera_buffer, - sky_aerial_perspective_attachment - ) = - sky_aerial_perspective_pass( - std::move(sky_transmittance_lut_attachment), - std::move(sky_multiscatter_lut_attachment), - std::move(environment_buffer), - std::move(camera_buffer), - std::move(sky_aerial_perspective_attachment) + return std::make_tuple(dst, atmosphere); + } ); - // ── SKY FINAL ─────────────────────────────────────────────────────── - auto sky_final_pass = vuk::make_pass( - "sky final", - [](vuk::CommandBuffer &cmd_list, // - VUK_IA(vuk::eColorRW) dst, - VUK_IA(vuk::eFragmentSampled) sky_transmittance_lut, - VUK_IA(vuk::eFragmentSampled) sky_aerial_perspective_lut, - VUK_IA(vuk::eFragmentSampled) sky_view_lut, - VUK_IA(vuk::eFragmentSampled) depth, - VUK_BA(vuk::eFragmentRead) environment, - VUK_BA(vuk::eFragmentRead) camera) { - auto linear_clamp_sampler = vuk::SamplerCreateInfo{ - .magFilter = vuk::Filter::eLinear, - .minFilter = vuk::Filter::eLinear, - .addressModeU = vuk::SamplerAddressMode::eClampToEdge, - .addressModeV = vuk::SamplerAddressMode::eClampToEdge, - .addressModeW = vuk::SamplerAddressMode::eClampToEdge, - }; + prepared_frame.sky_transmittance_lut = + self.sky_transmittance_lut_view.discard(device, "sky transmittance lut", vuk::ImageUsageFlagBits::eStorage); + std::tie(prepared_frame.sky_transmittance_lut, prepared_frame.atmosphere_buffer) = + transmittance_lut_pass(std::move(prepared_frame.sky_transmittance_lut), std::move(prepared_frame.atmosphere_buffer)); - vuk::PipelineColorBlendAttachmentState blend_info = { - .blendEnable = true, - .srcColorBlendFactor = vuk::BlendFactor::eOne, - .dstColorBlendFactor = vuk::BlendFactor::eSrcAlpha, - .colorBlendOp = vuk::BlendOp::eAdd, - .srcAlphaBlendFactor = vuk::BlendFactor::eZero, - .dstAlphaBlendFactor = vuk::BlendFactor::eOne, - .alphaBlendOp = vuk::BlendOp::eAdd, - }; + auto multiscatter_lut_pass = vuk::make_pass( + "multiscatter lut", + [](vuk::CommandBuffer &cmd_list, // + VUK_IA(vuk::eComputeSampled) sky_transmittance_lut, + VUK_IA(vuk::eComputeRW) sky_multiscatter_lut, + VUK_BA(vuk::eComputeRead) atmosphere) { + cmd_list // + .bind_compute_pipeline("passes.sky_multiscattering") + .bind_sampler(0, 0, { .magFilter = vuk::Filter::eLinear, .minFilter = vuk::Filter::eLinear }) + .bind_image(0, 1, sky_transmittance_lut) + .bind_buffer(0, 2, atmosphere) + .bind_image(0, 3, sky_multiscatter_lut) + .dispatch(sky_multiscatter_lut->extent.width, sky_multiscatter_lut->extent.height); - cmd_list // - .bind_graphics_pipeline("passes.sky_final") - .set_rasterization({}) - .set_depth_stencil({}) - .set_color_blend(dst, blend_info) - .set_dynamic_state(vuk::DynamicStateFlagBits::eViewport | vuk::DynamicStateFlagBits::eScissor) - .set_viewport(0, vuk::Rect2D::framebuffer()) - .set_scissor(0, vuk::Rect2D::framebuffer()) - .bind_sampler(0, 0, linear_clamp_sampler) - .bind_image(0, 1, sky_transmittance_lut) - .bind_image(0, 2, sky_aerial_perspective_lut) - .bind_image(0, 3, sky_view_lut) - .bind_image(0, 4, depth) - .bind_buffer(0, 5, environment) - .bind_buffer(0, 6, camera) - .draw(3, 1, 0, 0); + return std::make_tuple(sky_transmittance_lut, sky_multiscatter_lut, atmosphere); + } + ); - return std::make_tuple(dst, depth, environment, camera); - } - ); + prepared_frame.sky_multiscatter_lut = + self.sky_multiscatter_lut_view.discard(device, "sky multiscatter lut", vuk::ImageUsageFlagBits::eStorage); + std::tie(prepared_frame.sky_transmittance_lut, prepared_frame.sky_multiscatter_lut, prepared_frame.atmosphere_buffer) = multiscatter_lut_pass( + std::move(prepared_frame.sky_transmittance_lut), + std::move(prepared_frame.sky_multiscatter_lut), + std::move(prepared_frame.atmosphere_buffer) + ); + } else { + prepared_frame.sky_transmittance_lut = + self.sky_transmittance_lut_view.acquire(device, "sky transmittance lut", vuk::ImageUsageFlagBits::eSampled, vuk::Access::eComputeSampled); + prepared_frame.sky_multiscatter_lut = + self.sky_multiscatter_lut_view.acquire(device, "sky multiscatter lut", vuk::ImageUsageFlagBits::eSampled, vuk::Access::eComputeSampled); + } - std::tie(dst_attachment, depth_attachment, environment_buffer, camera_buffer) = sky_final_pass( - std::move(dst_attachment), - std::move(sky_transmittance_lut_attachment), - std::move(sky_aerial_perspective_attachment), - std::move(sky_view_lut_attachment), - std::move(depth_attachment), - std::move(environment_buffer), - std::move(camera_buffer) - ); + prepared_frame.camera = info.camera; + prepared_frame.directional_light = info.directional_light; + + return prepared_frame; } auto SceneRenderer::render(this SceneRenderer &self, vuk::Value &&dst_attachment, SceneRenderInfo &info, PreparedFrame &frame) @@ -1467,17 +1481,19 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Value({}); } - auto environment_buffer = std::move(frame.environment_buffer); auto camera_buffer = std::move(frame.camera_buffer); + auto directional_light_buffer = std::move(frame.directional_light_buffer); + auto directional_light_cascades_buffer = std::move(frame.directional_light_cascades_buffer); + auto atmosphere_buffer = std::move(frame.atmosphere_buffer); auto sky_view_lut_attachment = vuk::Value{}; auto sky_cubemap_attachment = vuk::Value{}; auto sky_transmittance_lut_attachment = std::move(frame.sky_transmittance_lut); auto sky_multiscatter_lut_attachment = std::move(frame.sky_multiscatter_lut); - if (frame.environment_flags & GPU::EnvironmentFlags::HasAtmosphere) { + if (atmosphere_buffer.node) { std::tie(sky_view_lut_attachment, sky_cubemap_attachment) = - draw_sky(self, frame_index, sky_transmittance_lut_attachment, sky_multiscatter_lut_attachment, environment_buffer, camera_buffer); + draw_sky(self, frame_index, sky_transmittance_lut_attachment, sky_multiscatter_lut_attachment, atmosphere_buffer, camera_buffer); } if (frame.mesh_instance_count) { @@ -1512,14 +1528,13 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Value({}); auto cull_meshlets_cmd_buffer = cull_meshes( @@ -1892,7 +1907,8 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Value); - if (frame.vbgtao.has_value()) { + if (frame.vbgtao_buffer.node) { + auto vbgtao_buffer = std::move(frame.vbgtao_buffer); auto vbgtao_prefilter_pass = vuk::make_pass( "vbgtao prefilter", [](vuk::CommandBuffer &command_buffer, // @@ -1938,15 +1954,14 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Valuedenoise_power]( - vuk::CommandBuffer &command_buffer, // - VUK_IA(vuk::eComputeSampled) noisy_occlusion, - VUK_IA(vuk::eComputeSampled) depth_differences, - VUK_IA(vuk::eComputeRW) ambient_occlusion - ) { + [](vuk::CommandBuffer &command_buffer, // + VUK_IA(vuk::eComputeSampled) noisy_occlusion, + VUK_IA(vuk::eComputeSampled) depth_differences, + VUK_BA(vuk::eComputeRead) vbgtao, + VUK_IA(vuk::eComputeRW) ambient_occlusion) { auto nearest_clamp_sampler = vuk::SamplerCreateInfo{ .magFilter = vuk::Filter::eNearest, .minFilter = vuk::Filter::eNearest, @@ -2028,11 +2044,12 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Valueextent, power)) + .bind_sampler(0, 0, nearest_clamp_sampler) + .bind_image(0, 1, noisy_occlusion) + .bind_image(0, 2, depth_differences) + .bind_buffer(0, 3, vbgtao) + .bind_image(0, 4, ambient_occlusion) + .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(noisy_occlusion->extent)) .dispatch_invocations_per_pixel(ambient_occlusion); return ambient_occlusion; @@ -2042,6 +2059,7 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Valuedevice_address, directional_light->device_address, directional_light_cascades->device_address) + ) .draw(3, 1, 0, 0); - return std::make_tuple(dst, environment, camera, sky_transmittance_lut, sky_cubemap, depth); + return std::make_tuple(dst, camera, atmosphere, sky_transmittance_lut, sky_cubemap, depth); } ); - std::tie(final_attachment, environment_buffer, camera_buffer, sky_transmittance_lut_attachment, sky_cubemap_attachment, depth_attachment) = - pbr_pass( - std::move(final_attachment), - std::move(environment_buffer), - std::move(camera_buffer), - std::move(sky_transmittance_lut_attachment), - std::move(sky_cubemap_attachment), - std::move(depth_attachment), - std::move(vbgtao_occlusion_attachment), - std::move(albedo_attachment), - std::move(normal_attachment), - std::move(emissive_attachment), - std::move(metallic_roughness_occlusion_attachment), - std::move(directional_light_shadowmap_attachment) - ); + if (directional_light_buffer.node && atmosphere_buffer.node) { + std::tie(final_attachment, camera_buffer, atmosphere_buffer, sky_transmittance_lut_attachment, sky_cubemap_attachment, depth_attachment) = + pbr_pass( + std::move(final_attachment), + std::move(camera_buffer), + std::move(atmosphere_buffer), + std::move(directional_light_buffer), + std::move(directional_light_cascades_buffer), + std::move(sky_transmittance_lut_attachment), + std::move(sky_cubemap_attachment), + std::move(depth_attachment), + std::move(vbgtao_occlusion_attachment), + std::move(albedo_attachment), + std::move(normal_attachment), + std::move(emissive_attachment), + std::move(metallic_roughness_occlusion_attachment), + std::move(directional_light_shadowmap_attachment) + ); + } } - if (frame.environment_flags & GPU::EnvironmentFlags::HasAtmosphere) { + if (atmosphere_buffer.node) { apply_sky( self, final_attachment, @@ -2127,69 +2155,71 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Valueextent) .dispatch_invocations_per_pixel(src_image); - return std::make_tuple(src_image, environment, histogram_bin_indices); + return std::make_tuple(src_image, eye_adaptation, histogram_bin_indices); } ); auto histogram_bin_indices_buffer = transfer_man.alloc_transient_buffer(vuk::MemoryUsage::eGPUonly, GPU::HISTOGRAM_BIN_COUNT * sizeof(u32)); vuk::fill(histogram_bin_indices_buffer, 0); - std::tie(final_attachment, environment_buffer, histogram_bin_indices_buffer) = - histogram_generate_pass(std::move(final_attachment), std::move(environment_buffer), std::move(histogram_bin_indices_buffer)); + std::tie(final_attachment, eye_adaptation_buffer, histogram_bin_indices_buffer) = + histogram_generate_pass(std::move(final_attachment), std::move(eye_adaptation_buffer), std::move(histogram_bin_indices_buffer)); // ── HISTOGRAM AVERAGE ─────────────────────────────────────────────── auto histogram_average_pass = vuk::make_pass( "histogram average", [pixel_count = f32(dst_attachment->extent.width * dst_attachment->extent.height), delta_time = info.delta_time]( vuk::CommandBuffer &cmd_list, // - VUK_BA(vuk::eComputeRead) environment, + VUK_BA(vuk::eComputeRead) eye_adaptation, VUK_BA(vuk::eComputeRead) histogram_bin_indices, VUK_BA(vuk::eComputeRW) histogram_luminance ) { cmd_list // .bind_compute_pipeline("passes.histogram_average") - .bind_buffer(0, 0, environment) + .bind_buffer(0, 0, eye_adaptation) .bind_buffer(0, 1, histogram_bin_indices) .bind_buffer(0, 2, histogram_luminance) .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(pixel_count, delta_time)) .dispatch(1); - return std::make_tuple(environment, histogram_luminance); + return std::make_tuple(eye_adaptation, histogram_luminance); } ); - std::tie(environment_buffer, histogram_luminance_buffer) = - histogram_average_pass(std::move(environment_buffer), std::move(histogram_bin_indices_buffer), std::move(histogram_luminance_buffer)); + std::tie(eye_adaptation_buffer, histogram_luminance_buffer) = + histogram_average_pass(std::move(eye_adaptation_buffer), std::move(histogram_bin_indices_buffer), std::move(histogram_luminance_buffer)); } // ── TONEMAP ───────────────────────────────────────────────────────── auto tonemap_pass = vuk::make_pass( "tonemap", - [](vuk::CommandBuffer &cmd_list, // - VUK_IA(vuk::eColorRW) dst, - VUK_IA(vuk::eFragmentSampled) src, - VUK_BA(vuk::eFragmentRead) environment, - VUK_BA(vuk::eFragmentUniformRead) histogram_luminance) { + [has_eye_adaptation = !!eye_adaptation_buffer.node]( + vuk::CommandBuffer &cmd_list, // + VUK_IA(vuk::eColorRW) dst, + VUK_IA(vuk::eFragmentSampled) src, + VUK_BA(vuk::eFragmentUniformRead) histogram_luminance + ) { cmd_list // .bind_graphics_pipeline("passes.tonemap") .set_rasterization({}) @@ -2199,15 +2229,14 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Value(has_eye_adaptation)) .draw(3, 1, 0, 0); return dst; } ); - dst_attachment = - tonemap_pass(std::move(dst_attachment), std::move(final_attachment), std::move(environment_buffer), std::move(histogram_luminance_buffer)); + dst_attachment = tonemap_pass(std::move(dst_attachment), std::move(final_attachment), std::move(histogram_luminance_buffer)); // ── EDITOR GRID ───────────────────────────────────────────────────── auto editor_grid_pass = vuk::make_pass( diff --git a/Lorr/Engine/Scene/SceneRenderer.hh b/Lorr/Engine/Scene/SceneRenderer.hh index 208d5378..2ee35986 100644 --- a/Lorr/Engine/Scene/SceneRenderer.hh +++ b/Lorr/Engine/Scene/SceneRenderer.hh @@ -20,28 +20,34 @@ struct FramePrepareInfo { ls::span gpu_meshes = {}; ls::span gpu_mesh_instances = {}; - GPU::Environment environment = {}; GPU::Camera camera = {}; - ls::option vbgtao = {}; + ls::option directional_light = ls::nullopt; + ls::span directional_light_cascades = {}; + ls::option atmosphere = ls::nullopt; + ls::option eye_adaptation = ls::nullopt; + ls::option vbgtao = ls::nullopt; }; struct PreparedFrame { u32 mesh_instance_count = 0; u32 max_meshlet_instance_count = 0; - GPU::EnvironmentFlags environment_flags = GPU::EnvironmentFlags::None; vuk::Value transforms_buffer = {}; vuk::Value meshes_buffer = {}; vuk::Value mesh_instances_buffer = {}; vuk::Value meshlet_instance_visibility_mask_buffer = {}; vuk::Value materials_buffer = {}; - vuk::Value environment_buffer = {}; vuk::Value camera_buffer = {}; - vuk::Value directional_camera_buffer = {}; + vuk::Value directional_light_buffer = {}; + vuk::Value directional_light_cascades_buffer = {}; + vuk::Value atmosphere_buffer = {}; + vuk::Value eye_adaptation_buffer = {}; + vuk::Value vbgtao_buffer = {}; vuk::Value sky_transmittance_lut = {}; vuk::Value sky_multiscatter_lut = {}; + GPU::Camera camera = {}; ls::option directional_light = ls::nullopt; - ls::option vbgtao = ls::nullopt; + glm::mat4 directional_light_cascade_projections[GPU::DirectionalLight::MAX_CASCADE_COUNT] = {}; }; struct SceneRenderInfo { From 8a504bf8e4ed7433ba62ed9370b189880553be2e Mon Sep 17 00:00:00 2001 From: exdal <63502313+exdal@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:10:08 +0300 Subject: [PATCH 5/8] fix cascades buffer --- .../Resources/shaders/passes/pbr_apply.slang | 6 +++--- Lorr/Engine/Resources/shaders/shadow.slang | 19 +++++++++++++------ Lorr/Engine/Scene/SceneRenderer.cc | 14 +++++++++++++- Lorr/Engine/xmake.lua | 2 +- xmake/packages.lua | 2 +- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang b/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang index d0e132cb..59c97e9c 100644 --- a/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang +++ b/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang @@ -57,7 +57,7 @@ func fs_main( let world_position = world_position_h.xyz / world_position_h.w; // PBR constants - let L = directional_light->direction; + let L = directional_light.direction; let V = normalize(params.camera.position - world_position); let N = normalize(mapped_normal); let R = reflect(-V, N); @@ -118,10 +118,10 @@ func fs_main( directional_light_cascades, params.directional_light_shadowmap, params.linear_clamp_sampler, - view_z, world_position, N, L); + view_z, world_position, N); let brdf = BRDF(V, N, L, albedo_color, roughness, metallic); - material_surface_color = brdf * horizon * sun_illuminance * NoL * occlusion * directional_shadow; + material_surface_color = brdf * horizon * sun_illuminance * NoL * directional_shadow * occlusion; } // FINAL ──────────────────────────────────────────────────────────── diff --git a/Lorr/Engine/Resources/shaders/shadow.slang b/Lorr/Engine/Resources/shaders/shadow.slang index 1e5909b5..fd06e9dc 100644 --- a/Lorr/Engine/Resources/shaders/shadow.slang +++ b/Lorr/Engine/Resources/shaders/shadow.slang @@ -42,7 +42,7 @@ public func get_cascade_index(DirectionalLightCascade *cascades, u32 cascade_cou return cascade_count - 1; } -func get_cascade_sample_info( +public func get_cascade_sample_info( in DirectionalLightCascade cascade, f32x3 world_position, f32x3 normal, @@ -77,8 +77,7 @@ public func sample_shadow_map( SamplerState shadow_sampler, f32 view_z, f32x3 world_position, - f32x3 normal, - f32x3 light_direction + f32x3 normal ) -> f32 { let cascade_index = get_cascade_index(cascades, light.cascade_count, view_z); let this_cascade = cascades[cascade_index]; @@ -86,10 +85,14 @@ public func sample_shadow_map( this_cascade, world_position, normal, - light_direction, + light.direction, light.normal_bias, light.depth_bias); + if (this_cascade_sample_info.w == 0.0) { + return 1.0; + } + let shadow_depth = shadow_map.SampleLevel(shadow_sampler, f32x3(this_cascade_sample_info.xy, cascade_index), 0.0); var shadow = this_cascade_sample_info.z < shadow_depth ? 0.0 : 1.0; @@ -103,14 +106,18 @@ public func sample_shadow_map( next_cascade, world_position, normal, - light_direction, + light.direction, light.normal_bias, light.depth_bias); + if (next_cascade_sample_info.w == 0.0) { + return shadow; + } + let next_shadow_depth = shadow_map.SampleLevel(shadow_sampler, f32x3(next_cascade_sample_info.xy, next_cascade_index), 0.0); let next_cascade_shadow = next_cascade_sample_info.z < next_shadow_depth ? 0.0 : 1.0; shadow = lerp(shadow, next_cascade_shadow, (view_z - next_near_bound) / (this_far_bound - next_near_bound)); } } return shadow; -} \ No newline at end of file +} diff --git a/Lorr/Engine/Scene/SceneRenderer.cc b/Lorr/Engine/Scene/SceneRenderer.cc index c0664e1e..f4c87b5b 100644 --- a/Lorr/Engine/Scene/SceneRenderer.cc +++ b/Lorr/Engine/Scene/SceneRenderer.cc @@ -1316,15 +1316,27 @@ auto SceneRenderer::prepare_frame(this SceneRenderer &self, FramePrepareInfo &in } prepared_frame.camera_buffer = transfer_man.scratch_buffer(info.camera); + + auto directional_light_cascade_count = 1_u32; if (info.directional_light.has_value()) { auto &directional_light = info.directional_light.value(); prepared_frame.directional_light_buffer = transfer_man.scratch_buffer(directional_light); + directional_light_cascade_count = ls::max(1_u32, directional_light.cascade_count); for (u32 i = 0_u32; i < directional_light.cascade_count; i++) { prepared_frame.directional_light_cascade_projections[i] = info.directional_light_cascades[i].projection_view_mat; } } - prepared_frame.directional_light_cascades_buffer = transfer_man.scratch_buffer(info.directional_light_cascades); + + prepared_frame.directional_light_cascades_buffer = + transfer_man.alloc_transient_buffer(vuk::MemoryUsage::eGPUtoCPU, directional_light_cascade_count * sizeof(GPU::DirectionalLightCascade)); + if (info.directional_light.has_value() && info.directional_light->cascade_count > 0) { + std::memcpy( + prepared_frame.directional_light_cascades_buffer->mapped_ptr, + info.directional_light_cascades.data(), + info.directional_light_cascades.size_bytes() + ); + } if (info.atmosphere.has_value()) { auto &atmosphere = info.atmosphere.value(); diff --git a/Lorr/Engine/xmake.lua b/Lorr/Engine/xmake.lua index 74689e10..8020ba44 100755 --- a/Lorr/Engine/xmake.lua +++ b/Lorr/Engine/xmake.lua @@ -51,7 +51,7 @@ target("Lorr") "flecs", "vuk", "meshoptimizer", - "ktx", + "ktx-lr", "svector", { public = true }) diff --git a/xmake/packages.lua b/xmake/packages.lua index affbab29..1ba21a26 100755 --- a/xmake/packages.lua +++ b/xmake/packages.lua @@ -68,6 +68,6 @@ add_requires("vuk 2025.09.01", { configs = { }, debug = is_mode("debug"), system = false }) add_requires("meshoptimizer v0.24", {system = false}) -add_requires("ktx v4.4.0", { debug = false, system = false }) +add_requires("local@ktx v4.4.0", { debug = is_plat("windows"), system = false, alias = "ktx-lr" }) add_requires("svector v1.0.3", {system = false}) From c7f6531259c4bcd1c3156b4e783c5534bb84ade9 Mon Sep 17 00:00:00 2001 From: exdal <63502313+exdal@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:28:57 +0300 Subject: [PATCH 6/8] lower value of base ambient color --- Lorr/Engine/Scene/ECSModule/CoreComponents.hh | 2 +- Lorr/Engine/Scene/SceneRenderer.cc | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Lorr/Engine/Scene/ECSModule/CoreComponents.hh b/Lorr/Engine/Scene/ECSModule/CoreComponents.hh index 7897b21e..be60bab9 100644 --- a/Lorr/Engine/Scene/ECSModule/CoreComponents.hh +++ b/Lorr/Engine/Scene/ECSModule/CoreComponents.hh @@ -66,7 +66,7 @@ ECS_COMPONENT_END(); ECS_COMPONENT_BEGIN(DirectionalLight) ECS_COMPONENT_MEMBER(direction, glm::vec2, {90.0f, 45.0f}) - ECS_COMPONENT_MEMBER(base_ambient_color, glm::vec3, {0.4f, 0.4f, 0.4f}) + ECS_COMPONENT_MEMBER(base_ambient_color, glm::vec3, {0.02f, 0.02f, 0.02f}) ECS_COMPONENT_MEMBER(intensity, f32, 10.0f) ECS_COMPONENT_MEMBER(shadow_map_resolution, u32, 2048) ECS_COMPONENT_MEMBER(cascade_count, u32, 4) diff --git a/Lorr/Engine/Scene/SceneRenderer.cc b/Lorr/Engine/Scene/SceneRenderer.cc index f4c87b5b..1efd702f 100644 --- a/Lorr/Engine/Scene/SceneRenderer.cc +++ b/Lorr/Engine/Scene/SceneRenderer.cc @@ -1540,7 +1540,6 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Value Date: Mon, 22 Sep 2025 19:10:37 +0300 Subject: [PATCH 7/8] tweak pbr shader a bit --- .../Resources/shaders/passes/pbr_apply.slang | 21 +- .../passes/shadowmap_cull_meshes.slang | 48 +++++ Lorr/Engine/Resources/shaders/shadow.slang | 13 +- Lorr/Engine/Scene/SceneRenderer.cc | 199 ++++++++++-------- 4 files changed, 174 insertions(+), 107 deletions(-) create mode 100644 Lorr/Engine/Resources/shaders/passes/shadowmap_cull_meshes.slang diff --git a/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang b/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang index 59c97e9c..89f531ca 100644 --- a/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang +++ b/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang @@ -19,6 +19,7 @@ struct ShaderParameters { Image2D emissive_image; Image2D metallic_roughness_occlusion_image; Texture2DArray directional_light_shadowmap; + SamplerComparisonState directional_light_sampler; ConstantBuffer camera; }; @@ -63,24 +64,20 @@ func fs_main( let R = reflect(-V, N); var indirect_illuminance = f32x3(0.0); - var sun_illuminance = f32x3(1.0); - var intersects_planet = false; if (atmosphere) { var eye_altitude = max(world_position.y, 0.0) * CAMERA_SCALE_UNIT; eye_altitude += atmosphere->planet_radius + PLANET_RADIUS_OFFSET; - let planet_intersection = std::ray_sphere_intersect_nearest( - f32x3(world_position.x, world_position.y + eye_altitude, world_position.z), L, atmosphere->planet_radius); - intersects_planet = planet_intersection != -1.0; f32 sun_cos_theta = dot(L, f32x3(0.0, 1.0, 0.0)); + let horizon_darkening = smoothstep(-0.1, 0.3, sun_cos_theta); f32x2 transmittance_uv = transmittance_params_to_lut_uv( atmosphere->atmos_radius, atmosphere->planet_radius, f32x2(eye_altitude, sun_cos_theta)); f32x3 sun_transmittance = params.sky_transmittance_lut.SampleLevel(params.linear_clamp_sampler, transmittance_uv, 0.0).rgb; - sun_illuminance = sun_transmittance * directional_light.intensity; + sun_illuminance = sun_transmittance * directional_light.intensity * horizon_darkening; let reflection_dir = lerp(N, R, 1.0 - roughness); let specular_sample = params.sky_cubemap.SampleLevel(params.linear_clamp_sampler, reflection_dir, 0.0); @@ -98,9 +95,7 @@ func fs_main( let specular_strength = (1.0 - roughness) * (1.0 - roughness); let specular = F * specular_sky * specular_strength; - let sky_illuminance = diffuse + specular; - - indirect_illuminance = sky_illuminance * occlusion; + indirect_illuminance = diffuse + specular; } // MATERIAL COLOR ─────────────────────────────────────────────────── @@ -111,13 +106,13 @@ func fs_main( var material_surface_color = f32x3(0.0); let NoL = max(dot(N, L), 0.0); - if (!intersects_planet && NoL > 0.0) { + if (NoL > 0.0) { let view_z = -mul(params.camera.view_mat, f32x4(world_position, 1.0)).z; let directional_shadow = sample_shadow_map( directional_light, directional_light_cascades, params.directional_light_shadowmap, - params.linear_clamp_sampler, + params.directional_light_sampler, view_z, world_position, N); let brdf = BRDF(V, N, L, albedo_color, roughness, metallic); @@ -125,8 +120,8 @@ func fs_main( } // FINAL ──────────────────────────────────────────────────────────── - let base_ambient_color = directional_light.base_ambient_color * sun_illuminance; - let indirect_illuminance_sum = indirect_illuminance + (base_ambient_color * albedo_color * occlusion); + let ambient_contribution = directional_light.base_ambient_color * albedo_color * sun_illuminance * occlusion; + let indirect_illuminance_sum = indirect_illuminance + ambient_contribution; let final_color = material_surface_color + indirect_illuminance_sum + emission; return f32x4(final_color, 1.0); diff --git a/Lorr/Engine/Resources/shaders/passes/shadowmap_cull_meshes.slang b/Lorr/Engine/Resources/shaders/passes/shadowmap_cull_meshes.slang new file mode 100644 index 00000000..f55ee2c5 --- /dev/null +++ b/Lorr/Engine/Resources/shaders/passes/shadowmap_cull_meshes.slang @@ -0,0 +1,48 @@ +import std; +import gpu; +import scene; +import cull; + +[[vk::binding(0)]] StructuredBuffer meshes; +[[vk::binding(1)]] StructuredBuffer transforms; +[[vk::binding(2)]] RWStructuredBuffer mesh_instances; +[[vk::binding(3)]] RWStructuredBuffer meshlet_instances; +[[vk::binding(4)]] RWStructuredBuffer visible_meshlet_instances_count; + +#ifndef CULLING_MESH_COUNT +#define CULLING_MESH_COUNT 64 +#endif + +[[shader("compute")]] +[[numthreads(CULLING_MESH_COUNT, 1, 1)]] +func cs_main( + uint3 thread_id : SV_DispatchThreadID, + uniform u32 mesh_instances_count, + uniform u32 cascade_index, + uniform f32x4x4 frustum_projection_view, +) -> void { + let mesh_instance_index = thread_id.x; + if (mesh_instance_index >= mesh_instances_count) { + return; + } + + let mesh_instance = &mesh_instances[mesh_instance_index]; + let mesh = meshes[mesh_instance.mesh_index]; + let transform = transforms[mesh_instance.transform_index]; + let mvp = mul(frustum_projection_view, transform.world); + + if (!test_frustum(mvp, mesh.bounds.aabb_center, mesh.bounds.aabb_extent)) { + return; + } + + let lod_index = min(mesh.lod_count - 1, cascade_index); + mesh_instance.lod_index = lod_index; + let mesh_lod = mesh.lods[lod_index]; + let meshlet_count = mesh_lod.meshlet_count; + var base_meshlet_instance_offset = __atomic_add(visible_meshlet_instances_count[0], meshlet_count, MemoryOrder::Relaxed); + for (u32 i = 0; i < meshlet_count; i++) { + let offset = base_meshlet_instance_offset + i; + meshlet_instances[offset].mesh_instance_index = mesh_instance_index; + meshlet_instances[offset].meshlet_index = i; + } +} diff --git a/Lorr/Engine/Resources/shaders/shadow.slang b/Lorr/Engine/Resources/shaders/shadow.slang index fd06e9dc..75758a8d 100644 --- a/Lorr/Engine/Resources/shaders/shadow.slang +++ b/Lorr/Engine/Resources/shaders/shadow.slang @@ -74,7 +74,7 @@ public func sample_shadow_map( DirectionalLight *light, DirectionalLightCascade *cascades, Texture2DArray shadow_map, - SamplerState shadow_sampler, + SamplerComparisonState shadow_sampler, f32 view_z, f32x3 world_position, f32x3 normal @@ -93,10 +93,10 @@ public func sample_shadow_map( return 1.0; } - let shadow_depth = shadow_map.SampleLevel(shadow_sampler, f32x3(this_cascade_sample_info.xy, cascade_index), 0.0); - var shadow = this_cascade_sample_info.z < shadow_depth ? 0.0 : 1.0; + var shadow = shadow_map.SampleCmpLevelZero( + shadow_sampler, f32x3(this_cascade_sample_info.xy, cascade_index), this_cascade_sample_info.z); - let next_cascade_index = cascade_index + 1u; + let next_cascade_index = cascade_index + 1; if (next_cascade_index < light.cascade_count) { let this_far_bound = this_cascade.far_bound; let next_near_bound = (1.0 - light.cascades_overlap_proportion) * this_far_bound; @@ -114,10 +114,11 @@ public func sample_shadow_map( return shadow; } - let next_shadow_depth = shadow_map.SampleLevel(shadow_sampler, f32x3(next_cascade_sample_info.xy, next_cascade_index), 0.0); - let next_cascade_shadow = next_cascade_sample_info.z < next_shadow_depth ? 0.0 : 1.0; + let next_cascade_shadow = shadow_map.SampleCmpLevelZero( + shadow_sampler, f32x3(next_cascade_sample_info.xy, next_cascade_index), next_cascade_sample_info.z); shadow = lerp(shadow, next_cascade_shadow, (view_z - next_near_bound) / (this_far_bound - next_near_bound)); } } + return shadow; } diff --git a/Lorr/Engine/Scene/SceneRenderer.cc b/Lorr/Engine/Scene/SceneRenderer.cc index 1efd702f..0a346287 100644 --- a/Lorr/Engine/Scene/SceneRenderer.cc +++ b/Lorr/Engine/Scene/SceneRenderer.cc @@ -18,7 +18,7 @@ static constexpr auto sampler_min_clamp_reduction_mode = VkSamplerReductionModeC .pNext = nullptr, .reductionMode = VK_SAMPLER_REDUCTION_MODE_MIN, }; -static constexpr auto hiz_sampler_info = vuk::SamplerCreateInfo{ +static constexpr auto hiz_sampler = vuk::SamplerCreateInfo{ .pNext = &sampler_min_clamp_reduction_mode, .magFilter = vuk::Filter::eLinear, .minFilter = vuk::Filter::eLinear, @@ -27,6 +27,30 @@ static constexpr auto hiz_sampler_info = vuk::SamplerCreateInfo{ .addressModeV = vuk::SamplerAddressMode::eClampToEdge, }; +static constexpr auto linear_clamp_sampler = vuk::SamplerCreateInfo{ + .magFilter = vuk::Filter::eLinear, + .minFilter = vuk::Filter::eLinear, + .addressModeU = vuk::SamplerAddressMode::eClampToEdge, + .addressModeV = vuk::SamplerAddressMode::eClampToEdge, + .addressModeW = vuk::SamplerAddressMode::eClampToEdge, +}; + +static constexpr auto linear_repeat_sampler = vuk::SamplerCreateInfo{ + .magFilter = vuk::Filter::eLinear, + .minFilter = vuk::Filter::eLinear, + .addressModeU = vuk::SamplerAddressMode::eRepeat, + .addressModeV = vuk::SamplerAddressMode::eRepeat, +}; + +static constexpr auto nearest_clamp_sampler = vuk::SamplerCreateInfo{ + .magFilter = vuk::Filter::eNearest, + .minFilter = vuk::Filter::eNearest, + .mipmapMode = vuk::SamplerMipmapMode::eNearest, + .addressModeU = vuk::SamplerAddressMode::eClampToEdge, + .addressModeV = vuk::SamplerAddressMode::eClampToEdge, + .addressModeW = vuk::SamplerAddressMode::eClampToEdge, +}; + static auto cull_meshes( TransferManager &transfer_man, GPU::CullFlags cull_flags, @@ -168,7 +192,7 @@ static auto cull_meshlets( .bind_buffer(0, 2, meshes) .bind_buffer(0, 3, transforms) .bind_image(0, 4, hiz) - .bind_sampler(0, 5, hiz_sampler_info) + .bind_sampler(0, 5, hiz_sampler) .bind_buffer(0, 6, visible_meshlet_instances_count) .bind_buffer(0, 7, early_visible_meshlet_instances_count) .bind_buffer(0, 8, late_visible_meshlet_instances_count) @@ -377,6 +401,75 @@ static auto draw_visbuffer( ); } +static auto shadowmap_cull_meshes( + TransferManager &transfer_man, + u32 mesh_instance_count, + u32 cascade_index, + glm::mat4 &frustum_projection_view, + vuk::Value &meshes_buffer, + vuk::Value &mesh_instances_buffer, + vuk::Value &meshlet_instances_buffer, + vuk::Value &visible_meshlet_instances_count_buffer, + vuk::Value &transforms_buffer +) -> vuk::Value { + ZoneScoped; + memory::ScopedStack stack; + + auto vis_cull_meshes_pass = vuk::make_pass( + stack.format("shadowmap cull meshes cascade {}", cascade_index), + [mesh_instance_count, cascade_index, frustum_projection_view]( + vuk::CommandBuffer &cmd_list, + VUK_BA(vuk::eComputeRead) meshes, + VUK_BA(vuk::eComputeRead) transforms, + VUK_BA(vuk::eComputeRW) mesh_instances, + VUK_BA(vuk::eComputeRW) meshlet_instances, + VUK_BA(vuk::eComputeRW) visible_meshlet_instances_count + ) { + cmd_list // + .bind_compute_pipeline("passes.shadowmap_cull_meshes") + .bind_buffer(0, 0, meshes) + .bind_buffer(0, 1, transforms) + .bind_buffer(0, 2, mesh_instances) + .bind_buffer(0, 3, meshlet_instances) + .bind_buffer(0, 4, visible_meshlet_instances_count) + .push_constants(vuk::ShaderStageFlagBits::eCompute, 0, PushConstants(mesh_instance_count, cascade_index, frustum_projection_view)) + .dispatch_invocations(mesh_instance_count); + + return std::make_tuple(meshes, transforms, mesh_instances, meshlet_instances, visible_meshlet_instances_count); + } + ); + + std::tie(meshes_buffer, transforms_buffer, mesh_instances_buffer, meshlet_instances_buffer, visible_meshlet_instances_count_buffer) = + vis_cull_meshes_pass( + std::move(meshes_buffer), + std::move(transforms_buffer), + std::move(mesh_instances_buffer), + std::move(meshlet_instances_buffer), + std::move(visible_meshlet_instances_count_buffer) + ); + + auto generate_cull_commands_pass = vuk::make_pass( + "generate cull commands", + [](vuk::CommandBuffer &cmd_list, // + VUK_BA(vuk::eComputeRead) visible_meshlet_instances_count, + VUK_BA(vuk::eComputeRW) cull_meshlets_cmd) { + cmd_list // + .bind_compute_pipeline("passes.generate_cull_commands") + .bind_buffer(0, 0, visible_meshlet_instances_count) + .bind_buffer(0, 1, cull_meshlets_cmd) + .dispatch(1); + + return std::make_tuple(visible_meshlet_instances_count, cull_meshlets_cmd); + } + ); + + auto cull_meshlets_cmd_buffer = transfer_man.scratch_buffer({ .x = 0, .y = 1, .z = 1 }); + std::tie(visible_meshlet_instances_count_buffer, cull_meshlets_cmd_buffer) = + generate_cull_commands_pass(std::move(visible_meshlet_instances_count_buffer), std::move(cull_meshlets_cmd_buffer)); + + return cull_meshlets_cmd_buffer; +} + auto cull_shadowmap_meshlets( TransferManager &transfer_man, u32 cascade_index, @@ -612,7 +705,7 @@ static auto draw_hiz(vuk::Value &hiz_attachment, vuk::Valu .specialize_constants(2, extent.height); *cmd_list.scratch_buffer(0, 0) = 0; - cmd_list.bind_sampler(0, 1, hiz_sampler_info); + cmd_list.bind_sampler(0, 1, hiz_sampler); cmd_list.bind_image(0, 2, src); for (u32 i = 0; i < 13; i++) { @@ -635,7 +728,7 @@ static auto draw_hiz(vuk::Value &hiz_attachment, vuk::Valu cmd_list // .bind_compute_pipeline("passes.hiz_slow") - .bind_sampler(0, 0, hiz_sampler_info); + .bind_sampler(0, 0, hiz_sampler); for (auto i = 0_u32; i < mip_count; i++) { auto mip_width = std::max(1_u32, extent.width >> i); @@ -708,14 +801,6 @@ static auto draw_sky( VUK_BA(vuk::eComputeRead) atmosphere, VUK_BA(vuk::eComputeRead) camera, VUK_IA(vuk::eComputeRW) sky_view_lut) { - auto linear_clamp_sampler = vuk::SamplerCreateInfo{ - .magFilter = vuk::Filter::eLinear, - .minFilter = vuk::Filter::eLinear, - .addressModeU = vuk::SamplerAddressMode::eClampToEdge, - .addressModeV = vuk::SamplerAddressMode::eClampToEdge, - .addressModeW = vuk::SamplerAddressMode::eClampToEdge, - }; - cmd_list // .bind_compute_pipeline("passes.sky_view") .bind_sampler(0, 0, linear_clamp_sampler) @@ -747,14 +832,6 @@ static auto draw_sky( VUK_BA(vuk::eComputeRead) camera, VUK_IA(vuk::eComputeRW) sky_cubemap ) { - auto linear_clamp_sampler = vuk::SamplerCreateInfo{ - .magFilter = vuk::Filter::eLinear, - .minFilter = vuk::Filter::eLinear, - .addressModeU = vuk::SamplerAddressMode::eClampToEdge, - .addressModeV = vuk::SamplerAddressMode::eClampToEdge, - .addressModeW = vuk::SamplerAddressMode::eClampToEdge, - }; - cmd_list // .bind_compute_pipeline("passes.sky_cubemap") .bind_sampler(0, 0, linear_clamp_sampler) @@ -812,14 +889,6 @@ static auto apply_sky( VUK_BA(vuk::eComputeRead) atmosphere, VUK_BA(vuk::eComputeRead) camera, VUK_IA(vuk::eComputeRW) sky_aerial_perspective_lut) { - auto linear_clamp_sampler = vuk::SamplerCreateInfo{ - .magFilter = vuk::Filter::eLinear, - .minFilter = vuk::Filter::eLinear, - .addressModeU = vuk::SamplerAddressMode::eClampToEdge, - .addressModeV = vuk::SamplerAddressMode::eClampToEdge, - .addressModeW = vuk::SamplerAddressMode::eClampToEdge, - }; - cmd_list // .bind_compute_pipeline("passes.sky_aerial_perspective") .bind_sampler(0, 0, linear_clamp_sampler) @@ -854,14 +923,6 @@ static auto apply_sky( VUK_IA(vuk::eFragmentSampled) depth, VUK_BA(vuk::eFragmentRead) atmosphere, VUK_BA(vuk::eFragmentRead) camera) { - auto linear_clamp_sampler = vuk::SamplerCreateInfo{ - .magFilter = vuk::Filter::eLinear, - .minFilter = vuk::Filter::eLinear, - .addressModeU = vuk::SamplerAddressMode::eClampToEdge, - .addressModeV = vuk::SamplerAddressMode::eClampToEdge, - .addressModeW = vuk::SamplerAddressMode::eClampToEdge, - }; - vuk::PipelineColorBlendAttachmentState blend_info = { .blendEnable = true, .srcColorBlendFactor = vuk::BlendFactor::eOne, @@ -1036,6 +1097,12 @@ auto SceneRenderer::init(this SceneRenderer &self) -> bool { }; Pipeline::create(device, default_slang_session, vis_decode_pipeline_info, bindless_descriptor_set).value(); + auto shadowmap_cull_meshes = PipelineCompileInfo{ + .module_name = "passes.shadowmap_cull_meshes", + .entry_points = { "cs_main" }, + }; + Pipeline::create(device, default_slang_session, shadowmap_cull_meshes).value(); + auto shadowmap_cull_meshlets = PipelineCompileInfo{ .module_name = "passes.shadowmap_cull_meshlets", .entry_points = { "cs_main" }, @@ -1548,20 +1615,16 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Value({}); - auto cull_meshlets_cmd_buffer = cull_meshes( + auto cull_meshlets_cmd_buffer = shadowmap_cull_meshes( transfer_man, - GPU::CullFlags::MeshFrustum, frame.mesh_instance_count, + cascade_index, current_cascade_projection_view, - frame.camera.position, - frame.camera.resolution, - frame.camera.acceptable_lod_error, meshes_buffer, mesh_instances_buffer, meshlet_instances_buffer, all_visible_meshlet_instances_count_buffer, - transforms_buffer, - debug_drawer_buffer + transforms_buffer ); auto visible_meshlet_instances_count_buffer = transfer_man.scratch_buffer({}); auto draw_shadowmap_cmd_buffer = cull_shadowmap_meshlets( @@ -1925,15 +1988,6 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Valuemip(0)) @@ -1973,24 +2027,6 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Value Date: Tue, 23 Sep 2025 17:08:53 +0300 Subject: [PATCH 8/8] fix lightless scenes --- .../Resources/shaders/passes/pbr_apply.slang | 44 ++--- Lorr/Engine/Resources/shaders/scene.slang | 6 + Lorr/Engine/Scene/GPUScene.hh | 6 + Lorr/Engine/Scene/SceneRenderer.cc | 161 +++++++++--------- Lorr/Engine/Scene/SceneRenderer.hh | 3 + 5 files changed, 122 insertions(+), 98 deletions(-) diff --git a/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang b/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang index 89f531ca..711e6bf7 100644 --- a/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang +++ b/Lorr/Engine/Resources/shaders/passes/pbr_apply.slang @@ -28,9 +28,7 @@ ParameterBlock params; [[shader("fragment")]] func fs_main( VertexOutput input, - uniform Atmosphere *atmosphere, - uniform DirectionalLight *directional_light, - uniform DirectionalLightCascade *directional_light_cascades + uniform PBRContext context ) -> f32x4 { let pixel_pos = u32x3(u32x2(input.position.xy), 0); let depth = params.depth_image.Load(pixel_pos); @@ -58,26 +56,30 @@ func fs_main( let world_position = world_position_h.xyz / world_position_h.w; // PBR constants - let L = directional_light.direction; + let Li = context.directional_light ? context.directional_light.intensity : 1.0; + let L = context.atmosphere ? context.atmosphere.sun_direction : f32x3(0.0, 1.0, 0.0); let V = normalize(params.camera.position - world_position); let N = normalize(mapped_normal); let R = reflect(-V, N); var indirect_illuminance = f32x3(0.0); var sun_illuminance = f32x3(1.0); - if (atmosphere) { + if (context.atmosphere) { + let planet_radius = context.atmosphere.planet_radius; + let atmos_radius = context.atmosphere.atmos_radius; + var eye_altitude = max(world_position.y, 0.0) * CAMERA_SCALE_UNIT; - eye_altitude += atmosphere->planet_radius + PLANET_RADIUS_OFFSET; + eye_altitude += planet_radius + PLANET_RADIUS_OFFSET; f32 sun_cos_theta = dot(L, f32x3(0.0, 1.0, 0.0)); let horizon_darkening = smoothstep(-0.1, 0.3, sun_cos_theta); f32x2 transmittance_uv = transmittance_params_to_lut_uv( - atmosphere->atmos_radius, - atmosphere->planet_radius, + atmos_radius, + planet_radius, f32x2(eye_altitude, sun_cos_theta)); f32x3 sun_transmittance = params.sky_transmittance_lut.SampleLevel(params.linear_clamp_sampler, transmittance_uv, 0.0).rgb; - sun_illuminance = sun_transmittance * directional_light.intensity * horizon_darkening; + sun_illuminance = sun_transmittance * Li * horizon_darkening; let reflection_dir = lerp(N, R, 1.0 - roughness); let specular_sample = params.sky_cubemap.SampleLevel(params.linear_clamp_sampler, reflection_dir, 0.0); @@ -107,21 +109,25 @@ func fs_main( var material_surface_color = f32x3(0.0); let NoL = max(dot(N, L), 0.0); if (NoL > 0.0) { - let view_z = -mul(params.camera.view_mat, f32x4(world_position, 1.0)).z; - let directional_shadow = sample_shadow_map( - directional_light, - directional_light_cascades, - params.directional_light_shadowmap, - params.directional_light_sampler, - view_z, world_position, N); + var directional_shadow = 1.0; + if (context.directional_light_cascades) { + let view_z = -mul(params.camera.view_mat, f32x4(world_position, 1.0)).z; + directional_shadow = sample_shadow_map( + context.directional_light, + context.directional_light_cascades, + params.directional_light_shadowmap, + params.directional_light_sampler, + view_z, world_position, N); + } let brdf = BRDF(V, N, L, albedo_color, roughness, metallic); - material_surface_color = brdf * horizon * sun_illuminance * NoL * directional_shadow * occlusion; + material_surface_color = brdf * horizon * sun_illuminance * NoL * directional_shadow; } // FINAL ──────────────────────────────────────────────────────────── - let ambient_contribution = directional_light.base_ambient_color * albedo_color * sun_illuminance * occlusion; - let indirect_illuminance_sum = indirect_illuminance + ambient_contribution; + let base_ambient_color = context.directional_light ? context.directional_light.base_ambient_color : f32x3(0.02); + let ambient_contribution = base_ambient_color * albedo_color * sun_illuminance; + let indirect_illuminance_sum = (indirect_illuminance + ambient_contribution) * occlusion; let final_color = material_surface_color + indirect_illuminance_sum + emission; return f32x4(final_color, 1.0); diff --git a/Lorr/Engine/Resources/shaders/scene.slang b/Lorr/Engine/Resources/shaders/scene.slang index f40a7e51..5de0e51a 100644 --- a/Lorr/Engine/Resources/shaders/scene.slang +++ b/Lorr/Engine/Resources/shaders/scene.slang @@ -96,6 +96,12 @@ public struct Atmosphere { public i32x3 aerial_perspective_lut_size; }; +public struct PBRContext { + public Atmosphere *atmosphere; + public DirectionalLight *directional_light; + public DirectionalLightCascade *directional_light_cascades; +}; + public struct EyeAdaptation { public f32 min_exposure; public f32 max_exposure; diff --git a/Lorr/Engine/Scene/GPUScene.hh b/Lorr/Engine/Scene/GPUScene.hh index 3a96dd9f..0711990e 100644 --- a/Lorr/Engine/Scene/GPUScene.hh +++ b/Lorr/Engine/Scene/GPUScene.hh @@ -133,6 +133,12 @@ struct Atmosphere { alignas(4) vuk::Extent3D aerial_perspective_lut_size = {}; }; +struct PBRContext { + alignas(8) u64 atmosphere = 0; + alignas(8) u64 directional_light = 0; + alignas(8) u64 directional_light_cascades = 0; +}; + struct EyeAdaptation { alignas(4) f32 min_exposure = -6.0f; alignas(4) f32 max_exposure = 18.0f; diff --git a/Lorr/Engine/Scene/SceneRenderer.cc b/Lorr/Engine/Scene/SceneRenderer.cc index 0a346287..9e09a0fd 100644 --- a/Lorr/Engine/Scene/SceneRenderer.cc +++ b/Lorr/Engine/Scene/SceneRenderer.cc @@ -758,40 +758,16 @@ static auto draw_hiz(vuk::Value &hiz_attachment, vuk::Valu } static auto draw_sky( - SceneRenderer &self, u32 frame_index, + vuk::Value &sky_view_lut_attachment, + vuk::Value &sky_cubemap_attachment, vuk::Value &sky_transmittance_lut_attachment, vuk::Value &sky_multiscatter_lut_attachment, vuk::Value &atmosphere_buffer, vuk::Value &camera_buffer -) -> std::tuple, vuk::Value> { +) -> void { ZoneScoped; - auto sky_view_lut_attachment = vuk::declare_ia( - "sky view lut", - { .image_type = vuk::ImageType::e2D, - .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, - .extent = self.sky_view_lut_extent, - .format = vuk::Format::eR16G16B16A16Sfloat, - .sample_count = vuk::Samples::e1, - .view_type = vuk::ImageViewType::e2D, - .level_count = 1, - .layer_count = 1 } - ); - - auto sky_cubemap_attachment = vuk::declare_ia( - "sky cubemap", - { .image_flags = vuk::ImageCreateFlagBits::eCubeCompatible, - .image_type = vuk::ImageType::e2D, - .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, - .extent = self.sky_cubemap_extent, - .format = vuk::Format::eR16G16B16A16Sfloat, - .sample_count = vuk::Samples::e1, - .view_type = vuk::ImageViewType::eCube, - .level_count = 1, - .layer_count = 6 } - ); - // ── SKY VIEW LUT ──────────────────────────────────────────────────── auto sky_view_pass = vuk::make_pass( "sky view", @@ -852,8 +828,6 @@ static auto draw_sky( std::move(camera_buffer), std::move(sky_cubemap_attachment) ); - - return std::make_tuple(sky_view_lut_attachment, sky_cubemap_attachment); } static auto apply_sky( @@ -979,7 +953,7 @@ auto SceneRenderer::init(this SceneRenderer &self) -> bool { #ifdef LS_DEBUG { "ENABLE_ASSERTIONS", "1" }, { "DEBUG_DRAW", "1" }, -#endif // DEBUG +#endif { "CULLING_MESH_COUNT", "64" }, { "CULLING_MESHLET_COUNT", std::to_string(Model::MAX_MESHLET_INDICES) }, { "CULLING_TRIANGLE_COUNT", std::to_string(Model::MAX_MESHLET_PRIMITIVES) }, @@ -1413,16 +1387,19 @@ auto SceneRenderer::prepare_frame(this SceneRenderer &self, FramePrepareInfo &in atmosphere.aerial_perspective_lut_size = self.sky_aerial_perspective_lut_extent; prepared_frame.atmosphere_buffer = transfer_man.scratch_buffer(atmosphere); + prepared_frame.has_atmosphere = true; } if (info.eye_adaptation.has_value()) { auto &eye_adaptation = info.eye_adaptation.value(); prepared_frame.eye_adaptation_buffer = transfer_man.scratch_buffer(eye_adaptation); + prepared_frame.has_eye_adaptation = true; } if (info.vbgtao.has_value()) { auto &vbgtao = info.vbgtao.value(); prepared_frame.vbgtao_buffer = transfer_man.scratch_buffer(vbgtao); + prepared_frame.has_vbgtao = true; } prepared_frame.mesh_instance_count = info.mesh_instance_count; @@ -1565,14 +1542,45 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Value{}; - auto sky_cubemap_attachment = vuk::Value{}; auto sky_transmittance_lut_attachment = std::move(frame.sky_transmittance_lut); auto sky_multiscatter_lut_attachment = std::move(frame.sky_multiscatter_lut); + auto sky_view_lut_attachment = vuk::declare_ia( + "sky view lut", + { .image_type = vuk::ImageType::e2D, + .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, + .extent = self.sky_view_lut_extent, + .format = vuk::Format::eR16G16B16A16Sfloat, + .sample_count = vuk::Samples::e1, + .view_type = vuk::ImageViewType::e2D, + .level_count = 1, + .layer_count = 1 } + ); + sky_view_lut_attachment = vuk::clear_image(std::move(sky_view_lut_attachment), vuk::Black); - if (atmosphere_buffer.node) { - std::tie(sky_view_lut_attachment, sky_cubemap_attachment) = - draw_sky(self, frame_index, sky_transmittance_lut_attachment, sky_multiscatter_lut_attachment, atmosphere_buffer, camera_buffer); + auto sky_cubemap_attachment = vuk::declare_ia( + "sky cubemap", + { .image_flags = vuk::ImageCreateFlagBits::eCubeCompatible, + .image_type = vuk::ImageType::e2D, + .usage = vuk::ImageUsageFlagBits::eSampled | vuk::ImageUsageFlagBits::eStorage, + .extent = self.sky_cubemap_extent, + .format = vuk::Format::eR16G16B16A16Sfloat, + .sample_count = vuk::Samples::e1, + .view_type = vuk::ImageViewType::eCube, + .layout = vuk::ImageLayout::eGeneral, + .level_count = 1, + .layer_count = 6 } + ); + + if (frame.has_atmosphere) { + draw_sky( + frame_index, + sky_view_lut_attachment, + sky_cubemap_attachment, + sky_transmittance_lut_attachment, + sky_multiscatter_lut_attachment, + atmosphere_buffer, + camera_buffer + ); } if (frame.mesh_instance_count) { @@ -1607,6 +1615,7 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Value); - if (frame.vbgtao_buffer.node) { + if (frame.has_vbgtao) { auto vbgtao_buffer = std::move(frame.vbgtao_buffer); auto vbgtao_prefilter_pass = vuk::make_pass( "vbgtao prefilter", @@ -2102,23 +2111,27 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Valuedevice_address : 0, + .directional_light = frame.directional_light.has_value() ? directional_light_buffer->device_address : 0, + .directional_light_cascades = frame.directional_light.has_value() ? directional_light_cascades_buffer->device_address : 0, + }; auto pbr_pass = vuk::make_pass( "pbr", - [](vuk::CommandBuffer &cmd_list, // - VUK_IA(vuk::eColorRW) dst, - VUK_BA(vuk::eFragmentRead) camera, - VUK_BA(vuk::eFragmentRead) atmosphere, - VUK_BA(vuk::eFragmentRead) directional_light, - VUK_BA(vuk::eFragmentRead) directional_light_cascades, - VUK_IA(vuk::eFragmentSampled) sky_transmittance_lut, - VUK_IA(vuk::eFragmentSampled) sky_cubemap, - VUK_IA(vuk::eFragmentSampled) depth, - VUK_IA(vuk::eFragmentSampled) ambient_occlusion, - VUK_IA(vuk::eFragmentSampled) albedo, - VUK_IA(vuk::eFragmentSampled) normal, - VUK_IA(vuk::eFragmentSampled) emissive, - VUK_IA(vuk::eFragmentSampled) metallic_roughness_occlusion, - VUK_IA(vuk::eFragmentSampled) directional_light_shadowmap) { + [pbr_context]( + vuk::CommandBuffer &cmd_list, // + VUK_IA(vuk::eColorRW) dst, + VUK_BA(vuk::eFragmentRead) camera, + VUK_IA(vuk::eFragmentSampled) sky_transmittance_lut, + VUK_IA(vuk::eFragmentSampled) sky_cubemap, + VUK_IA(vuk::eFragmentSampled) depth, + VUK_IA(vuk::eFragmentSampled) ambient_occlusion, + VUK_IA(vuk::eFragmentSampled) albedo, + VUK_IA(vuk::eFragmentSampled) normal, + VUK_IA(vuk::eFragmentSampled) emissive, + VUK_IA(vuk::eFragmentSampled) metallic_roughness_occlusion, + VUK_IA(vuk::eFragmentSampled) directional_light_shadowmap + ) { auto directional_light_sampler = vuk::SamplerCreateInfo{ .magFilter = vuk::Filter::eLinear, .minFilter = vuk::Filter::eLinear, @@ -2149,39 +2162,29 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Valuedevice_address, directional_light->device_address, directional_light_cascades->device_address) - ) + .push_constants(vuk::ShaderStageFlagBits::eFragment, 0, pbr_context) .draw(3, 1, 0, 0); - return std::make_tuple(dst, camera, atmosphere, sky_transmittance_lut, sky_cubemap, depth); + return std::make_tuple(dst, camera, sky_transmittance_lut, sky_cubemap, depth); } ); - if (directional_light_buffer.node && atmosphere_buffer.node) { - std::tie(final_attachment, camera_buffer, atmosphere_buffer, sky_transmittance_lut_attachment, sky_cubemap_attachment, depth_attachment) = - pbr_pass( - std::move(final_attachment), - std::move(camera_buffer), - std::move(atmosphere_buffer), - std::move(directional_light_buffer), - std::move(directional_light_cascades_buffer), - std::move(sky_transmittance_lut_attachment), - std::move(sky_cubemap_attachment), - std::move(depth_attachment), - std::move(vbgtao_occlusion_attachment), - std::move(albedo_attachment), - std::move(normal_attachment), - std::move(emissive_attachment), - std::move(metallic_roughness_occlusion_attachment), - std::move(directional_light_shadowmap_attachment) - ); - } + std::tie(final_attachment, camera_buffer, sky_transmittance_lut_attachment, sky_cubemap_attachment, depth_attachment) = pbr_pass( + std::move(final_attachment), + std::move(camera_buffer), + std::move(sky_transmittance_lut_attachment), + std::move(sky_cubemap_attachment), + std::move(depth_attachment), + std::move(vbgtao_occlusion_attachment), + std::move(albedo_attachment), + std::move(normal_attachment), + std::move(emissive_attachment), + std::move(metallic_roughness_occlusion_attachment), + std::move(directional_light_shadowmap_attachment) + ); } - if (atmosphere_buffer.node) { + if (frame.has_atmosphere) { apply_sky( self, final_attachment, @@ -2196,7 +2199,7 @@ auto SceneRenderer::render(this SceneRenderer &self, vuk::Value sky_transmittance_lut = {}; vuk::Value sky_multiscatter_lut = {}; + bool has_atmosphere = false; + bool has_eye_adaptation = false; + bool has_vbgtao = false; GPU::Camera camera = {}; ls::option directional_light = ls::nullopt; glm::mat4 directional_light_cascade_projections[GPU::DirectionalLight::MAX_CASCADE_COUNT] = {};