1use super::wgsl;
2use crate::WgpuServer;
3use crate::{AutoRepresentationRef, CompilerInfo, WgpuCompiler};
4use cubecl_core::{CubeDim, ExecutionMode, WgpuCompilationOptions, server::KernelArguments};
5use cubecl_core::{MemoryConfiguration, prelude::Visibility};
6use cubecl_ir::DeviceProperties;
7use cubecl_runtime::{
8 compiler::{CompilationError, KernelCacheKey},
9 id::KernelId,
10};
11use std::{borrow::Cow, sync::Arc};
12use wgpu::{
13 Adapter, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BufferBindingType,
14 ComputePipeline, Device, PipelineLayoutDescriptor, Queue, ShaderModule, ShaderModuleDescriptor,
15 ShaderStages,
16};
17
18#[cfg(feature = "spirv")]
19use super::vulkan;
20
21#[cfg(all(feature = "msl", target_os = "macos"))]
22use super::metal;
23
24impl<C: WgpuCompiler> WgpuServer<C> {
25 #[allow(
29 clippy::type_complexity,
30 reason = "required because of error propagation"
31 )]
32 #[allow(unused_variables)]
33 pub fn load_cached_pipeline(
34 &mut self,
35 kernel_id: &KernelId,
36 bindings: &KernelArguments,
37 mode: ExecutionMode,
38 ) -> Result<
39 Option<Result<(Arc<ComputePipeline>, CompilerInfo), (u64, KernelCacheKey)>>,
40 CompilationError,
41 > {
42 #[cfg(not(feature = "spirv"))]
43 let res = Ok(None);
44 #[cfg(feature = "spirv")]
45 let res = if let Some(cache) = self.spirv_cache.as_mut() {
46 let key = (
47 self.utilities.properties_hash,
48 KernelCacheKey::new(kernel_id, self.build_id),
49 );
50 if let Some(entry) = cache.remove(&key) {
51 use crate::ParamsTransfer;
52
53 log::trace!("Using SPIR-V cache");
54
55 let params_transfer = match entry.kernel.immediate_size {
56 Some(_) => ParamsTransfer::Immediate,
57 None => ParamsTransfer::Uniform,
58 };
59 let repr = AutoRepresentationRef::SpirV(&entry.kernel);
60 let module = self.create_module(
61 &entry.entrypoint_name,
62 kernel_id.cube_dim.into(),
63 Some(repr),
64 "",
65 mode,
66 )?;
67 let pipeline =
68 self.create_pipeline(&entry.entrypoint_name, Some(repr), module, bindings);
69 Ok(Some(Ok((
70 pipeline,
71 CompilerInfo::Vulkan { params_transfer },
72 ))))
73 } else {
74 Ok(Some(Err(key)))
75 }
76 } else {
77 Ok(None)
78 };
79
80 res
81 }
82
83 pub fn create_module(
84 &self,
85 entrypoint_name: &str,
86 cube_dim: CubeDim,
87 repr: Option<AutoRepresentationRef<'_>>,
88 source: &str,
89 mode: ExecutionMode,
90 ) -> Result<ShaderModule, CompilationError> {
91 match repr {
92 #[cfg(feature = "spirv")]
93 Some(AutoRepresentationRef::SpirV(repr)) => unsafe {
94 Ok(self.device.create_shader_module_passthrough(
95 wgpu::ShaderModuleDescriptorPassthrough {
96 label: Some(entrypoint_name),
97 spirv: Some(Cow::Borrowed(&repr.assembled_module)),
98 entry_points: Cow::Borrowed(&[wgpu::PassthroughShaderEntryPoint {
99 name: entrypoint_name.into(),
100 workgroup_size: cube_dim.into(),
101 }]),
102 ..Default::default()
103 },
104 ))
105 },
106 #[cfg(all(feature = "msl", target_os = "macos"))]
107 Some(AutoRepresentationRef::Msl(_)) => unsafe {
108 Ok(self.device.create_shader_module_passthrough(
109 wgpu::ShaderModuleDescriptorPassthrough {
110 label: Some(entrypoint_name),
111 msl: Some(Cow::Borrowed(source)),
112 entry_points: Cow::Borrowed(&[wgpu::PassthroughShaderEntryPoint {
113 name: entrypoint_name.into(),
114 workgroup_size: cube_dim.into(),
115 }]),
116 ..Default::default()
117 },
118 ))
119 },
120 _ => {
121 let _ = cube_dim;
122 let checks = wgpu::ShaderRuntimeChecks {
123 bounds_checks: false,
127 force_loop_bounding: mode == ExecutionMode::Checked,
129 ..wgpu::ShaderRuntimeChecks::unchecked()
130 };
131
132 log::trace!("[cubecl-wgpu] compiling WGSL module `{entrypoint_name}`\n{source}");
133
134 let error_scope = self.device.push_error_scope(wgpu::ErrorFilter::Validation);
135
136 let module = unsafe {
139 self.device.create_shader_module_trusted(
140 ShaderModuleDescriptor {
141 label: Some(entrypoint_name),
142 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(source)),
143 },
144 checks,
145 )
146 };
147
148 let err_future = error_scope.pop();
151
152 #[cfg(not(target_family = "wasm"))]
153 if let Some(err) = cubecl_environment::future::block_on(err_future) {
154 log::error!(
155 "[cubecl-wgpu] WGSL compilation failed for kernel `{entrypoint_name}`:\n{err}\n--- shader source ({} bytes) ---\n{source}\n--- end shader ---",
156 source.len()
157 );
158 return Err(CompilationError::Generic {
159 reason: format!(
160 "WGSL compilation failed for kernel `{entrypoint_name}`: {err}"
161 ),
162 backtrace: cubecl_environment::backtrace::BackTrace::capture(),
163 });
164 }
165
166 #[cfg(target_family = "wasm")]
169 {
170 let entrypoint_name = entrypoint_name.to_string();
171 let source = source.to_string();
172 wasm_bindgen_futures::spawn_local(async move {
173 if let Some(err) = err_future.await {
174 log::error!(
175 "[cubecl-wgpu] WGSL compilation failed for kernel `{entrypoint_name}`:\n{err}\n--- shader source ({} bytes) ---\n{source}\n--- end shader ---",
176 source.len()
177 );
178 }
179 });
180 }
181
182 Ok(module)
183 }
184 }
185 }
186
187 #[allow(unused_variables)]
188 pub fn create_pipeline(
189 &self,
190 entrypoint_name: &str,
191 repr: Option<AutoRepresentationRef<'_>>,
192 module: ShaderModule,
193 bindings: &KernelArguments,
194 ) -> Arc<ComputePipeline> {
195 let bindings_info = match repr {
196 Some(AutoRepresentationRef::Wgsl(repr)) => Some(wgsl::bindings(repr, bindings)),
197 #[cfg(all(feature = "msl", target_os = "macos"))]
198 Some(AutoRepresentationRef::Msl(repr)) => Some(metal::bindings(repr, bindings)),
199 #[cfg(feature = "spirv")]
200 Some(AutoRepresentationRef::SpirV(repr)) => Some(vulkan::bindings(repr, bindings)),
201 _ => None,
202 };
203
204 let layout = bindings_info.map(|(bindings, immediate_size)| {
205 if !bindings.is_empty() {
206 let bindings = bindings
207 .into_iter()
208 .map(|visibility| match visibility {
209 Visibility::Uniform => BufferBindingType::Uniform,
210 Visibility::Read => BufferBindingType::Storage { read_only: true },
211 Visibility::ReadWrite => BufferBindingType::Storage { read_only: false },
212 })
213 .enumerate()
214 .map(|(i, ty)| BindGroupLayoutEntry {
215 binding: i as u32,
216 visibility: ShaderStages::COMPUTE,
217 ty: BindingType::Buffer {
218 ty,
219 has_dynamic_offset: false,
220 min_binding_size: None,
221 },
222 count: None,
223 })
224 .collect::<Vec<_>>();
225 let layout = self
226 .device
227 .create_bind_group_layout(&BindGroupLayoutDescriptor {
228 label: None,
229 entries: &bindings,
230 });
231 self.device
232 .create_pipeline_layout(&PipelineLayoutDescriptor {
233 label: None,
234 bind_group_layouts: &[Some(&layout)],
235 immediate_size: immediate_size as u32,
236 })
237 } else {
238 self.device
239 .create_pipeline_layout(&PipelineLayoutDescriptor {
240 label: None,
241 bind_group_layouts: &[],
242 immediate_size: immediate_size as u32,
243 })
244 }
245 });
246
247 let pipeline = self
248 .device
249 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
250 label: Some(entrypoint_name),
251 layout: layout.as_ref(),
252 module: &module,
253 entry_point: Some(entrypoint_name),
254 compilation_options: wgpu::PipelineCompilationOptions {
255 zero_initialize_workgroup_memory: false,
256 ..Default::default()
257 },
258 cache: None,
259 });
260 Arc::new(pipeline)
261 }
262}
263
264pub async fn request_device(adapter: &Adapter) -> (Device, Queue) {
265 if let Some(result) = request_vulkan_device(adapter).await {
266 return result;
267 }
268 if let Some(result) = request_metal_device(adapter).await {
269 return result;
270 }
271 wgsl::request_device(adapter).await
272}
273
274#[cfg(feature = "spirv")]
275async fn request_vulkan_device(adapter: &Adapter) -> Option<(Device, Queue)> {
276 if is_vulkan(adapter) {
277 vulkan::request_vulkan_device(adapter).await
278 } else {
279 None
280 }
281}
282
283#[cfg(not(feature = "spirv"))]
284async fn request_vulkan_device(_adapter: &Adapter) -> Option<(Device, Queue)> {
285 None
286}
287
288#[cfg(all(feature = "msl", target_os = "macos"))]
289async fn request_metal_device(adapter: &Adapter) -> Option<(Device, Queue)> {
290 if is_metal(adapter) {
291 Some(metal::request_metal_device(adapter).await)
292 } else {
293 None
294 }
295}
296
297#[cfg(not(all(feature = "msl", target_os = "macos")))]
298async fn request_metal_device(_adapter: &Adapter) -> Option<(Device, Queue)> {
299 None
300}
301
302pub fn register_features(
303 adapter: &Adapter,
304 props: &mut DeviceProperties,
305 comp_options: &mut WgpuCompilationOptions,
306 memory_config: &MemoryConfiguration,
307) {
308 if register_vulkan_features(adapter, props, comp_options, memory_config) {
309 return;
310 }
311 if register_metal_features(adapter, props, comp_options, memory_config) {
312 return;
313 }
314 wgsl::register_wgsl_features(adapter, props, comp_options);
315}
316
317#[cfg(feature = "spirv")]
318pub fn register_vulkan_features(
319 adapter: &Adapter,
320 props: &mut DeviceProperties,
321 comp_options: &mut WgpuCompilationOptions,
322 memory_config: &MemoryConfiguration,
323) -> bool {
324 if is_vulkan(adapter) {
325 vulkan::register_vulkan_features(adapter, props, comp_options, memory_config)
326 } else {
327 false
328 }
329}
330
331#[cfg(not(feature = "spirv"))]
332pub fn register_vulkan_features(
333 _adapter: &Adapter,
334 _props: &mut DeviceProperties,
335 _comp_options: &mut WgpuCompilationOptions,
336 _memory_config: &MemoryConfiguration,
337) -> bool {
338 false
339}
340
341#[cfg(all(feature = "msl", target_os = "macos"))]
342pub fn register_metal_features(
343 adapter: &Adapter,
344 props: &mut DeviceProperties,
345 comp_options: &mut WgpuCompilationOptions,
346 _memory_config: &MemoryConfiguration,
347) -> bool {
348 if is_metal(adapter) {
349 metal::register_metal_features(adapter, props, comp_options)
350 } else {
351 false
352 }
353}
354
355#[cfg(not(all(feature = "msl", target_os = "macos")))]
356pub fn register_metal_features(
357 _adapter: &Adapter,
358 _props: &mut DeviceProperties,
359 _comp_options: &mut WgpuCompilationOptions,
360 _memory_config: &MemoryConfiguration,
361) -> bool {
362 false
363}
364
365#[cfg(feature = "spirv")]
366fn is_vulkan(adapter: &Adapter) -> bool {
367 unsafe { adapter.as_hal::<wgpu::hal::api::Vulkan>().is_some() }
368}
369
370#[cfg(all(feature = "msl", target_os = "macos"))]
371fn is_metal(adapter: &Adapter) -> bool {
372 unsafe { adapter.as_hal::<wgpu::hal::api::Metal>().is_some() }
373}