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