renderling/pbr/brdf/
cpu.rs

1//! CPU side of BRDF stuff.
2use craballoc::runtime::WgpuRuntime;
3
4use crate::texture;
5
6/// Pre-computed texture of the brdf integration.
7#[derive(Clone)]
8pub struct BrdfLut {
9    pub(crate) inner: texture::Texture,
10}
11
12impl BrdfLut {
13    /// Create a new pre-computed BRDF look-up texture.
14    pub fn new(runtime: impl AsRef<WgpuRuntime>) -> Self {
15        let runtime = runtime.as_ref();
16        let device = &runtime.device;
17        let queue = &runtime.queue;
18        let vertex_linkage = crate::linkage::brdf_lut_convolution_vertex::linkage(device);
19        let fragment_linkage = crate::linkage::brdf_lut_convolution_fragment::linkage(device);
20        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
21            label: Some("brdf_lut_convolution"),
22            layout: None,
23            vertex: wgpu::VertexState {
24                module: &vertex_linkage.module,
25                entry_point: Some(vertex_linkage.entry_point),
26                buffers: &[],
27                compilation_options: Default::default(),
28            },
29            primitive: wgpu::PrimitiveState {
30                topology: wgpu::PrimitiveTopology::TriangleList,
31                strip_index_format: None,
32                front_face: wgpu::FrontFace::Ccw,
33                cull_mode: None,
34                unclipped_depth: false,
35                polygon_mode: wgpu::PolygonMode::Fill,
36                conservative: false,
37            },
38            depth_stencil: None,
39            multisample: wgpu::MultisampleState {
40                mask: !0,
41                alpha_to_coverage_enabled: false,
42                count: 1,
43            },
44            fragment: Some(wgpu::FragmentState {
45                module: &fragment_linkage.module,
46                entry_point: Some(fragment_linkage.entry_point),
47                targets: &[Some(wgpu::ColorTargetState {
48                    format: wgpu::TextureFormat::Rg16Float,
49                    blend: Some(wgpu::BlendState {
50                        color: wgpu::BlendComponent::REPLACE,
51                        alpha: wgpu::BlendComponent::REPLACE,
52                    }),
53                    write_mask: wgpu::ColorWrites::ALL,
54                })],
55                compilation_options: Default::default(),
56            }),
57            multiview: None,
58            cache: None,
59        });
60
61        let framebuffer = texture::Texture::new_with(
62            runtime,
63            Some("brdf_lut"),
64            Some(
65                wgpu::TextureUsages::RENDER_ATTACHMENT
66                    | wgpu::TextureUsages::TEXTURE_BINDING
67                    | wgpu::TextureUsages::COPY_SRC,
68            ),
69            None,
70            wgpu::TextureFormat::Rg16Float,
71            2,
72            2,
73            512,
74            512,
75            1,
76            &[],
77        );
78
79        let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
80        {
81            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
82                label: Some("brdf_lut_convolution"),
83                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
84                    view: &framebuffer.view,
85                    resolve_target: None,
86                    ops: wgpu::Operations {
87                        load: wgpu::LoadOp::Clear(wgpu::Color::RED),
88                        store: wgpu::StoreOp::Store,
89                    },
90                    depth_slice: None,
91                })],
92                depth_stencil_attachment: None,
93                ..Default::default()
94            });
95
96            render_pass.set_pipeline(&pipeline);
97            render_pass.draw(0..6, 0..1);
98        }
99        queue.submit([encoder.finish()]);
100
101        BrdfLut { inner: framebuffer }
102    }
103
104    /// Return the underlying [`Texture`](crate::texture::Texture).
105    pub fn texture(&self) -> &texture::Texture {
106        &self.inner
107    }
108}
109
110#[cfg(test)]
111mod test {
112    use crate::{context::Context, pbr::brdf::BrdfLut, test::BlockOnFuture, texture::Texture};
113
114    #[test]
115    fn precomputed_brdf() {
116        assert_eq!(2, std::mem::size_of::<u16>());
117        let r = Context::headless(32, 32).block();
118        let brdf_lut = BrdfLut::new(&r);
119        assert_eq!(
120            wgpu::TextureFormat::Rg16Float,
121            brdf_lut.texture().texture.format()
122        );
123        let copied_buffer = Texture::read(&r, &brdf_lut.texture().texture, 512, 512, 2, 2);
124        let pixels = copied_buffer.pixels(r.get_device()).block().unwrap();
125        let pixels: Vec<f32> = bytemuck::cast_slice::<u8, u16>(pixels.as_slice())
126            .iter()
127            .copied()
128            .map(|bits| half::f16::from_bits(bits).to_f32())
129            .collect();
130        assert_eq!(512 * 512 * 2, pixels.len());
131        let pixels: Vec<f32> = pixels
132            .chunks_exact(2)
133            .flat_map(|pixel| match pixel {
134                [r, g] => [*r, *g, 0.0, 1.0],
135                _ => unreachable!(),
136            })
137            .collect();
138
139        let img: image::ImageBuffer<image::Rgba<f32>, Vec<f32>> =
140            image::ImageBuffer::from_vec(512, 512, pixels).unwrap();
141        let img = image::DynamicImage::from(img);
142        let img = img.into_rgba8();
143        img_diff::assert_img_eq("skybox/brdf_lut.png", img);
144    }
145}