Skip to content

Commit

Permalink
can write to the first mip
Browse files Browse the repository at this point in the history
  • Loading branch information
schell committed Oct 13, 2024
1 parent 8d2fd47 commit 222d270
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 36 deletions.
16 changes: 8 additions & 8 deletions crates/renderling/src/bvol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub fn intersect_planes(p0: &Vec4, p1: &Vec4, p2: &Vec4) -> Vec3 {

/// Calculates distance between plane and point
pub fn dist_bpp(plane: &Vec4, point: Vec3) -> f32 {
plane.x * point.x + plane.y * point.y + plane.z * point.z + plane.w
(plane.x * point.x + plane.y * point.y + plane.z * point.z + plane.w).abs()
}

/// Calculates the most inside vertex of an AABB.
Expand Down Expand Up @@ -123,8 +123,8 @@ impl Aabb {
self.min == self.max
}

/// Determines whether this `Aabb` can be seen by `camera` after being transformed by
/// `transform`.
/// Determines whether this `Aabb` can be seen by `camera` after being
/// transformed by `transform`.
pub fn is_outside_camera_view(&self, camera: &Camera, transform: Transform) -> bool {
let transform = Mat4::from(transform);
let min = transform.transform_point3(self.min);
Expand Down Expand Up @@ -364,14 +364,14 @@ pub trait BVol {
/// In order for a bounding volume to be inside the frustum, it must not be
/// culled by any plane.
///
/// Coherence is provided by the `lpindex` argument, which should be the index of
/// the first plane found that culls this volume, given as part of the return
/// value of this function.
/// Coherence is provided by the `lpindex` argument, which should be the
/// index of the first plane found that culls this volume, given as part
/// of the return value of this function.
///
/// Returns `true` if the volume is outside the frustum, `false` otherwise.
///
/// Returns the index of first plane found that culls this volume, to cache and use later
/// as a short circuit.
/// Returns the index of first plane found that culls this volume, to cache
/// and use later as a short circuit.
fn coherent_test_is_volume_outside_frustum(
&self,
frustum: &Frustum,
Expand Down
45 changes: 42 additions & 3 deletions crates/renderling/src/camera.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Camera projection, view and utilities.
use crabslab::SlabItem;
use glam::{Mat4, Vec3};
use glam::{Mat4, Vec3, Vec4Swizzles};

use crate::bvol::Frustum;
use crate::bvol::{dist_bpp, Frustum};

/// A camera used for transforming the stage during rendering.
///
Expand Down Expand Up @@ -80,6 +80,23 @@ impl Camera {
pub fn view_projection(&self) -> Mat4 {
self.projection * self.view
}

pub fn z_near(&self) -> f32 {
dist_bpp(&self.frustum.planes[0], self.position)
}

pub fn z_far(&self) -> f32 {
dist_bpp(&self.frustum.planes[5], self.position)
}

/// Linearize and normalize a depth value.
pub fn linearize_depth_value(&self, depth: f32) -> f32 {
let z_near = self.z_near();
let z_far = self.z_far();
let z_linear = (2.0 * z_near) / (z_far + z_near - depth * (z_far - z_near));
// Normalize the linearized depth to [0, 1]
(z_linear - z_near) / (z_far - z_near)
}
}

/// Returns the projection and view matrices for a camera with default
Expand All @@ -88,8 +105,8 @@ impl Camera {
/// The default projection and view matrices are defined as:
///
/// ```rust
/// use renderling::prelude::*;
/// use glam::*;
/// use renderling::prelude::*;
///
/// let width = 800.0;
/// let height = 600.0;
Expand Down Expand Up @@ -148,3 +165,25 @@ pub fn default_ortho2d(width: f32, height: f32) -> (Mat4, Mat4) {
let view = Mat4::IDENTITY;
(projection, view)
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn camera_znear_zfar() {
let znear = 0.01;
let zfar = 100.0;
let projection = Mat4::perspective_rh(core::f32::consts::PI / 4.0, 1.0, znear, zfar);
let view = Mat4::look_at_rh(Vec3::new(5.0, 5.0, 10.0), Vec3::ZERO, Vec3::Y);
let camera = Camera::new(projection, view);

log::info!("near_plane: {}", camera.frustum.planes[0]);
log::info!("znear: {}", camera.z_near());
log::info!("far_plane: {}", camera.frustum.planes[5]);
log::info!("zfar: {}", camera.z_far());

assert_eq!(znear, camera.z_near(), "znear");
assert_eq!(zfar, camera.z_far(), "zfar");
}
}
4 changes: 2 additions & 2 deletions crates/renderling/src/cull.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub struct DepthPyramidDescriptor {
impl DepthPyramidDescriptor {
fn should_skip_invocation(&self, global_invocation: UVec3) -> bool {
let current_size = self.size >> self.mip_level;
global_invocation.x < current_size.x && global_invocation.y < current_size.y
!(global_invocation.x < current_size.x && global_invocation.y < current_size.y)
}

/// Return the [`Id`] of the depth at the given `mip_level` and coordinate.
Expand All @@ -85,7 +85,7 @@ pub type DepthPyramidImageMut = Image!(2D, format = r32f, depth = false);
///
/// It is assumed that a [`PyramidDescriptor`] is stored at index `0` in the
/// given slab.
#[spirv(compute(threads(32)))]
#[spirv(compute(threads(32, 32, 1)))]
pub fn compute_copy_depth_to_pyramid(
#[spirv(descriptor_set = 0, binding = 0, storage_buffer)] slab: &mut [u32],
#[spirv(descriptor_set = 0, binding = 1)] depth_texture: &DepthImage2d,
Expand Down
124 changes: 120 additions & 4 deletions crates/renderling/src/cull/cpu.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
//! CPU side of compute culling.
use crabslab::Array;
use crabslab::{Array, Slab};
use glam::UVec2;
use snafu::Snafu;
use snafu::{OptionExt, Snafu};
use std::sync::Arc;

use crate::{
slab::{GpuArray, Hybrid, SlabAllocator},
camera::Camera,
slab::{GpuArray, Hybrid, SlabAllocator, SlabAllocatorError},
texture::Texture,
};

Expand All @@ -22,6 +23,18 @@ pub enum CullingError {

#[snafu(display("Missing depth pyramid mip {index}"))]
MissingMip { index: usize },

#[snafu(display("{source}"))]
SlabError { source: SlabAllocatorError },

#[snafu(display("Could not read mip {index}"))]
ReadMip { index: usize },
}

impl From<SlabAllocatorError> for CullingError {
fn from(source: SlabAllocatorError) -> Self {
CullingError::SlabError { source }
}
}

const FRUSTUM_LABEL: Option<&str> = Some("compute-frustum-culling");
Expand Down Expand Up @@ -207,6 +220,7 @@ impl DepthPyramid {
queue: &wgpu::Queue,
size: UVec2,
) -> (Arc<wgpu::Buffer>, bool) {
log::info!("resizing depth pyramid to {size}");
let mip = self.slab.new_array(vec![]);
self.mip_data = vec![];
self.desc.modify(|desc| desc.mip = mip.array());
Expand Down Expand Up @@ -238,6 +252,44 @@ impl DepthPyramid {
pub fn size(&self) -> UVec2 {
self.desc.get().size
}

pub async fn read_images(
&self,
ctx: &crate::Context,
camera: &Camera,
) -> Result<Vec<image::DynamicImage>, CullingError> {
let size = self.size();
let slab_data = self
.slab
.read(ctx.get_device(), ctx.get_queue(), Self::LABEL, 0..)
.await?;
let mut images = vec![];
let mut min = f32::MAX;
let mut max = f32::MIN;
for (i, mip) in self.mip_data.iter().enumerate() {
let depth_data: Vec<f32> = slab_data
.read_vec(mip.array())
.into_iter()
.map(|depth| {
if i == 0 {
min = min.min(depth);
max = max.max(depth);
}
camera.linearize_depth_value(depth)
// depth
})
.collect();
log::info!("min: {min}");
log::info!("max: {max}");
let width = size.x >> i;
let height = size.y >> i;
let image: image::ImageBuffer<image::Luma<f32>, Vec<f32>> =
image::ImageBuffer::from_raw(width, height, depth_data)
.context(ReadMipSnafu { index: i })?;
images.push(image::DynamicImage::from(image));
}
Ok(images)
}
}

/// Copies the depth texture to the top of the depth pyramid.
Expand Down Expand Up @@ -378,7 +430,7 @@ impl ComputeCopyDepth {
/// Computes occlusion culling on the GPU.
pub struct OcclusionCulling {
sample_count: u32,
depth_pyramid: DepthPyramid,
pub(crate) depth_pyramid: DepthPyramid,
compute_copy_depth: ComputeCopyDepth,
}

Expand Down Expand Up @@ -413,6 +465,7 @@ impl OcclusionCulling {
) -> Result<(), CullingError> {
let sample_count = depth_texture.texture.sample_count();
if sample_count != self.sample_count {
log::warn!("sample_count changed, invalidating");
self.invalidate();
// let (bindgroup_layout, pipeline) =
// Self::create_bindgroup_layout_and_pipeline(device,
Expand All @@ -423,6 +476,7 @@ impl OcclusionCulling {
let extent = depth_texture.texture.size();
let size = UVec2::new(extent.width, extent.height);
let (depth_pyramid_buffer, should_invalidate) = if size != self.depth_pyramid.size() {
log::warn!("depth texture size changed, invalidating");
self.invalidate();
self.depth_pyramid.resize(device, queue, size)
} else {
Expand All @@ -448,3 +502,65 @@ impl OcclusionCulling {
Ok(())
}
}

#[cfg(test)]
mod test {
use crate::prelude::*;
use glam::{Mat4, Quat, Vec3, Vec4};


#[test]
fn occlusion_culling_sanity() {
let ctx = Context::headless(100, 100);
let stage = ctx.new_stage().with_background_color(Vec4::splat(1.0));
let camera_position = Vec3::new(0.0, 9.0, 9.0);
let camera = stage.new_value(Camera::new(
Mat4::perspective_rh(std::f32::consts::PI / 4.0, 1.0, 1.0, 24.0),
Mat4::look_at_rh(camera_position, Vec3::ZERO, Vec3::Y),
));
let geometry = stage.new_array(crate::test::gpu_cube_vertices());
let transform = stage.new_value(Transform {
scale: Vec3::new(6.0, 6.0, 6.0),
rotation: Quat::from_axis_angle(Vec3::Y, -std::f32::consts::FRAC_PI_4),
..Default::default()
});
let cube = stage.new_value(Renderlet {
camera_id: camera.id(),
vertices_array: geometry.array(),
transform_id: transform.id(),
..Default::default()
});
stage.add_renderlet(&cube);

let frame = ctx.get_next_frame().unwrap();
stage.render(&frame.view());
frame.present();

let frame = ctx.get_next_frame().unwrap();
stage.render(&frame.view());
let img = frame.read_image().unwrap();
img_diff::save("cull/pyramid/frame.png", img);
frame.present();

let depth_texture = stage.get_depth_texture();
let depth_img = depth_texture.read_image().unwrap();
img_diff::save("cull/pyramid/depth.png", depth_img);

let pyramid_images = futures_lite::future::block_on(
stage
.draw_calls
.read()
.unwrap()
.drawing_strategy
.as_indirect()
.unwrap()
.occlusion_culling
.depth_pyramid
.read_images(&ctx, &camera.get()),
)
.unwrap();
for (i, img) in pyramid_images.into_iter().enumerate() {
img_diff::save(&format!("cull/pyramid/mip_{i}.png"), img);
}
}
}
35 changes: 23 additions & 12 deletions crates/renderling/src/draw/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ impl InternalRenderlet {
}
}

struct IndirectDraws {
pub(crate) struct IndirectDraws {
slab: SlabAllocator<wgpu::Buffer>,
draws: Vec<Gpu<DrawIndirectArgs>>,
frustum_culling: FrustumCulling,
occlusion_culling: OcclusionCulling,
pub(crate) occlusion_culling: OcclusionCulling,
}

impl IndirectDraws {
Expand Down Expand Up @@ -113,7 +113,7 @@ impl From<Id<Renderlet>> for DrawIndirectArgs {
}
}

enum DrawingStrategy {
pub(crate) enum DrawingStrategy {
/// The standard drawing method that includes compute culling.
Indirect(IndirectDraws),
/// Fallback drawing method for web targets.
Expand All @@ -123,27 +123,38 @@ enum DrawingStrategy {
Direct,
}

impl DrawingStrategy {
#[cfg(test)]
pub fn as_indirect(&self) -> Option<&IndirectDraws> {
if let DrawingStrategy::Indirect(i) = self {
Some(i)
} else {
None
}
}
}

/// Used to determine which objects are drawn and maintains the
/// list of all [`Renderlet`]s.
pub struct DrawCalls {
/// Internal representation of all staged renderlets.
internal_renderlets: Vec<InternalRenderlet>,
drawing_strategy: DrawingStrategy,
pub(crate) drawing_strategy: DrawingStrategy,
}

impl DrawCalls {
/// Create a new [`DrawCalls`].
///
/// `use_compute_culling` can be used to set whether frustum culling is used as a GPU compute
/// step before drawing. This is a native-only option.
/// `use_compute_culling` can be used to set whether frustum culling is used
/// as a GPU compute step before drawing. This is a native-only option.
pub fn new(ctx: &Context, use_compute_culling: bool, size: UVec2, sample_count: u32) -> Self {
let can_use_multi_draw_indirect = ctx.get_adapter().features().contains(
wgpu::Features::INDIRECT_FIRST_INSTANCE | wgpu::Features::MULTI_DRAW_INDIRECT,
);
if use_compute_culling && !can_use_multi_draw_indirect {
log::warn!(
"`use_compute_culling` is `true`, but the MULTI_DRAW_INDIRECT feature \
is not available. No compute culling will occur."
"`use_compute_culling` is `true`, but the MULTI_DRAW_INDIRECT feature is not \
available. No compute culling will occur."
)
}
let can_use_compute_culling = use_compute_culling && can_use_multi_draw_indirect;
Expand Down Expand Up @@ -259,8 +270,8 @@ impl DrawCalls {

/// Perform pre-draw steps like compute culling, if available.
///
/// This does not do upkeep, please call [`DrawCalls::upkeep`] before calling this
/// function.
/// This does not do upkeep, please call [`DrawCalls::upkeep`] before
/// calling this function.
pub fn pre_draw(
&mut self,
device: &wgpu::Device,
Expand Down Expand Up @@ -292,8 +303,8 @@ impl DrawCalls {
.run(device, queue, depth_texture)?;
} else {
log::warn!(
"DrawCalls::pre_render called without first calling `upkeep` \
- no culling was performed"
"DrawCalls::pre_render called without first calling `upkeep` - no culling \
was performed"
);
}
}
Expand Down
Loading

0 comments on commit 222d270

Please sign in to comment.