julia_set/
vulkan.rs

1//! GLSL / Vulkan backend for Julia sets.
2
3use anyhow::format_err;
4use shaderc::{CompilationArtifact, CompileOptions, OptimizationLevel, ShaderKind};
5use vulkano::{
6    buffer::{BufferUsage, CpuAccessibleBuffer},
7    command_buffer::AutoCommandBufferBuilder,
8    descriptor::{
9        descriptor::{DescriptorBufferDesc, DescriptorDesc, DescriptorDescTy, ShaderStages},
10        descriptor_set::{PersistentDescriptorSet, UnsafeDescriptorSetLayout},
11        pipeline_layout::{PipelineLayout, PipelineLayoutDesc, PipelineLayoutDescPcRange},
12        PipelineLayoutAbstract,
13    },
14    device::{Device, DeviceExtensions, Queue},
15    instance::{Instance, InstanceExtensions, PhysicalDevice, QueueFamily},
16    pipeline::{shader::ShaderModule, ComputePipeline},
17    sync::{self, GpuFuture},
18};
19
20use std::{ffi::CStr, iter, slice, sync::Arc};
21
22use crate::{compiler::Compiler, Backend, Function, ImageBuffer, Params, Render};
23
24const PROGRAM: &str = include_str!(concat!(env!("OUT_DIR"), "/program.glsl"));
25
26const LOCAL_WORKGROUP_SIZES: [u32; 2] = [16, 16];
27
28fn compile_shader(function: &str) -> shaderc::Result<CompilationArtifact> {
29    let mut compiler = shaderc::Compiler::new().ok_or_else(|| {
30        shaderc::Error::NullResultObject("Cannot initialize `shaderc` compiler".to_owned())
31    })?;
32    let mut options = CompileOptions::new().ok_or_else(|| {
33        shaderc::Error::NullResultObject("Cannot initialize `shaderc` compiler options".to_owned())
34    })?;
35    options.add_macro_definition("COMPUTE", Some(function));
36    options.set_optimization_level(OptimizationLevel::Performance);
37    compiler.compile_into_spirv(
38        PROGRAM,
39        ShaderKind::Compute,
40        "program.glsl",
41        "main",
42        Some(&options),
43    )
44}
45
46/// Hand-written layout spec for the compute GLSL shader.
47#[derive(Debug, Clone, Copy)]
48struct Layout;
49
50unsafe impl PipelineLayoutDesc for Layout {
51    fn num_sets(&self) -> usize {
52        1
53    }
54
55    fn num_bindings_in_set(&self, set: usize) -> Option<usize> {
56        if set == 0 {
57            Some(2)
58        } else {
59            None
60        }
61    }
62
63    fn descriptor(&self, set: usize, binding: usize) -> Option<DescriptorDesc> {
64        let stages = ShaderStages {
65            compute: true,
66            ..ShaderStages::none()
67        };
68
69        match (set, binding) {
70            (0, 0) => Some(DescriptorDesc {
71                ty: DescriptorDescTy::Buffer(DescriptorBufferDesc {
72                    dynamic: Some(false),
73                    storage: true,
74                }),
75                array_count: 1,
76                stages,
77                readonly: false,
78            }),
79
80            (0, 1) => Some(DescriptorDesc {
81                ty: DescriptorDescTy::Buffer(DescriptorBufferDesc {
82                    dynamic: Some(false),
83                    storage: false,
84                }),
85                array_count: 1,
86                stages,
87                readonly: true,
88            }),
89
90            _ => None,
91        }
92    }
93
94    fn num_push_constants_ranges(&self) -> usize {
95        0
96    }
97
98    fn push_constants_range(&self, _num: usize) -> Option<PipelineLayoutDescPcRange> {
99        None
100    }
101}
102
103#[derive(Debug, Clone, Copy)]
104#[repr(C, packed)]
105struct VulkanParams {
106    view_center: [f32; 2],
107    view_size: [f32; 2],
108    image_size: [u32; 2],
109    inf_distance_sq: f32,
110    max_iterations: u32,
111}
112
113/// Backend based on [Vulkan].
114///
115/// [Vulkan]: https://www.khronos.org/vulkan/
116#[cfg_attr(docsrs, doc(cfg(feature = "vulkan_backend")))]
117#[derive(Debug, Clone, Default)]
118pub struct Vulkan;
119
120impl Backend<&Function> for Vulkan {
121    type Error = anyhow::Error;
122    type Program = VulkanProgram;
123
124    fn create_program(&self, function: &Function) -> Result<Self::Program, Self::Error> {
125        let compiled = Compiler::for_gl().compile(function);
126        VulkanProgram::new(&compiled)
127    }
128}
129
130/// Program produced by the [`Vulkan`] backend.
131#[cfg_attr(docsrs, doc(cfg(feature = "vulkan_backend")))]
132#[derive(Debug)]
133pub struct VulkanProgram {
134    device: Arc<Device>,
135    queue: Arc<Queue>,
136    pipeline: Arc<ComputePipeline<PipelineLayout<Layout>>>,
137    layout: Arc<UnsafeDescriptorSetLayout>,
138}
139
140impl VulkanProgram {
141    fn new(compiled_function: &str) -> anyhow::Result<Self> {
142        let instance = Instance::new(None, &InstanceExtensions::none(), None)?;
143        let device = PhysicalDevice::enumerate(&instance)
144            .next()
145            .ok_or_else(|| format_err!("Physical device not found for instance {:?}", instance))?;
146        let queue_family = device
147            .queue_families()
148            .find(QueueFamily::supports_compute)
149            .ok_or_else(|| format_err!("No support of compute shaders on {:?}", device))?;
150
151        let (device, mut queues) = Device::new(
152            device,
153            device.supported_features(),
154            &DeviceExtensions {
155                khr_storage_buffer_storage_class: true,
156                ..DeviceExtensions::none()
157            },
158            iter::once((queue_family, 0.5)),
159        )?;
160        let queue = queues
161            .next()
162            .ok_or_else(|| format_err!("Cannot initialize compute queue on device {:?}", device))?;
163
164        let shader = compile_shader(compiled_function)?;
165        let shader = unsafe { ShaderModule::from_words(device.clone(), shader.as_binary())? };
166        let entry_point_name = CStr::from_bytes_with_nul(b"main\0").unwrap();
167        let entry_point = unsafe { shader.compute_entry_point(entry_point_name, Layout) };
168
169        let pipeline = ComputePipeline::new(device.clone(), &entry_point, &(), None)?;
170        let layout = pipeline
171            .layout()
172            .descriptor_set_layout(0)
173            .unwrap() // safe: we know for sure that we have 0-th descriptor set
174            .to_owned();
175
176        Ok(Self {
177            device,
178            queue,
179            pipeline: Arc::new(pipeline),
180            layout,
181        })
182    }
183}
184
185impl Render for VulkanProgram {
186    type Error = anyhow::Error;
187
188    #[allow(clippy::cast_possible_truncation)]
189    fn render(&self, params: &Params) -> anyhow::Result<ImageBuffer> {
190        // Bind uniforms: the output image buffer and the rendering params.
191        let pixel_count = (params.image_size[0] * params.image_size[1]) as usize;
192        let image_buffer = unsafe {
193            CpuAccessibleBuffer::<[u32]>::uninitialized_array(
194                self.device.clone(),
195                (pixel_count + 3) / 4,
196                BufferUsage {
197                    storage_buffer: true,
198                    transfer_destination: true,
199                    ..BufferUsage::none()
200                },
201                true,
202            )
203        }?;
204
205        let gl_params = VulkanParams {
206            view_center: params.view_center,
207            view_size: [params.view_width(), params.view_height],
208            image_size: params.image_size,
209            inf_distance_sq: params.inf_distance * params.inf_distance,
210            max_iterations: u32::from(params.max_iterations),
211        };
212        let params_buffer = CpuAccessibleBuffer::from_data(
213            self.device.clone(),
214            BufferUsage::uniform_buffer(),
215            false,
216            gl_params,
217        )?;
218
219        let descriptor_set = PersistentDescriptorSet::start(self.layout.clone())
220            .add_buffer(image_buffer.clone())?
221            .add_buffer(params_buffer)?
222            .build()?;
223
224        // Create the commands to render the image and copy it to the buffer.
225        let mut command_buffer = AutoCommandBufferBuilder::primary_one_time_submit(
226            self.device.clone(),
227            self.queue.family(),
228        )?;
229        let task_dimensions = [
230            (params.image_size[0] + LOCAL_WORKGROUP_SIZES[0] - 1) / LOCAL_WORKGROUP_SIZES[0],
231            (params.image_size[1] + LOCAL_WORKGROUP_SIZES[1] - 1) / LOCAL_WORKGROUP_SIZES[1],
232            1,
233        ];
234        command_buffer
235            .fill_buffer(image_buffer.clone(), 0)?
236            .dispatch(task_dimensions, self.pipeline.clone(), descriptor_set, ())?;
237        let command_buffer = command_buffer.build()?;
238        sync::now(self.device.clone())
239            .then_execute(self.queue.clone(), command_buffer)?
240            .then_signal_fence_and_flush()?
241            .wait(None)?;
242
243        // Convert the buffer into an `ImageBuffer`.
244        let buffer_content = image_buffer.read()?;
245        debug_assert!(buffer_content.len() * 4 >= pixel_count);
246        let buffer_content = unsafe {
247            // SAFETY: Buffer length is correct by construction, and `[u8]` doesn't require
248            // any special alignment.
249            slice::from_raw_parts(buffer_content.as_ptr() as *const u8, pixel_count)
250        };
251
252        Ok(ImageBuffer::from_vec(
253            params.image_size[0],
254            params.image_size[1],
255            buffer_content.to_vec(),
256        )
257        .unwrap())
258    }
259}