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