1use 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#[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#[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#[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() .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 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 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 let buffer_content = image_buffer.read()?;
245 debug_assert!(buffer_content.len() * 4 >= pixel_count);
246 let buffer_content = unsafe {
247 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}