ruda_driver_wgpu/memory/
storage.rs1use ruda_kernel::dsl::server::IoError;
2use ruda::runtime::storage::{ComputeStorage, StorageHandle, StorageId, StorageUtilization};
3use hashbrown::HashMap;
4use std::num::NonZeroU64;
5use wgpu::BufferUsages;
6
7const MIN_BUFFER_SIZE: u64 = 32;
11
12pub struct WgpuStorage {
14 memory: HashMap<StorageId, wgpu::Buffer>,
15 device: wgpu::Device,
16 buffer_usages: BufferUsages,
17 mem_alignment: usize,
18 relocation_queue: Option<wgpu::Queue>,
19}
20
21impl core::fmt::Debug for WgpuStorage {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 f.write_str(format!("WgpuStorage {{ device: {:?} }}", self.device).as_str())
24 }
25}
26
27#[derive(new, Debug)]
29pub struct WgpuResource {
30 #[new(default)]
31 pin: Option<ruda::runtime::memory_management::MemoryResourcePin>,
32 pub buffer: wgpu::Buffer,
34 pub offset: u64,
36 pub size: u64,
42}
43
44impl WgpuResource {
45 pub(crate) fn address_pin(&self) -> Option<ruda::runtime::memory_management::MemoryResourcePin> {
46 self.pin.clone()
47 }
48
49 pub fn as_wgpu_bind_resource(&self) -> wgpu::BindingResource<'_> {
51 let size = NonZeroU64::new(self.size.next_multiple_of(4));
61
62 let binding = wgpu::BufferBinding {
63 buffer: &self.buffer,
64 offset: self.offset,
65 size,
66 };
67 wgpu::BindingResource::Buffer(binding)
68 }
69}
70
71impl WgpuStorage {
73 pub fn new(mem_alignment: usize, device: wgpu::Device, usages: BufferUsages) -> Self {
75 Self {
76 memory: HashMap::new(),
77 device,
78 buffer_usages: usages,
79 mem_alignment,
80 relocation_queue: None,
81 }
82 }
83
84 pub(crate) fn with_relocation_queue(mut self, queue: wgpu::Queue) -> Self {
85 self.relocation_queue = Some(queue);
86 self
87 }
88
89 fn wait_relocation(&self) -> Result<(), IoError> {
90 #[cfg(not(target_family = "wasm"))]
91 {
92 self.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None })
93 .map(|_| ()).map_err(|error| IoError::Unknown {
94 description: format!("WGPU relocation wait: {error}"),
95 backtrace: ruda_core::backtrace::BackTrace::capture(),
96 })
97 }
98 #[cfg(target_family = "wasm")]
99 {
100 Err(IoError::UnsupportedIoOperation { backtrace: ruda_core::backtrace::BackTrace::capture() })
101 }
102 }
103}
104
105impl ComputeStorage for WgpuStorage {
106 type Resource = WgpuResource;
107
108 fn alignment(&self) -> usize {
109 self.mem_alignment
110 }
111
112 fn get_pinned(&mut self, handle: &StorageHandle, binding: ruda::runtime::memory_management::ManagedMemoryBinding) -> Self::Resource {
113 let mut resource = self.get(handle);
114 resource.pin = Some(binding.pin());
115 resource
116 }
117
118 fn supports_relocation(&self) -> bool {
119 self.relocation_queue.is_some() && cfg!(not(target_family = "wasm"))
120 }
121
122 fn relocation_barrier(&mut self) -> Result<(), IoError> {
123 let queue = self.relocation_queue.as_ref().expect("relocation queue");
124 queue.submit([]);
127 self.wait_relocation()
128 }
129
130 fn relocation_copy(&mut self, source: &StorageHandle, target: &StorageHandle) -> Result<(), IoError> {
131 let src = self.memory.get(&source.id).expect("relocation source");
132 let dst = self.memory.get(&target.id).expect("relocation target");
133 let size = source.size().next_multiple_of(wgpu::COPY_BUFFER_ALIGNMENT);
134 if source.offset() % wgpu::COPY_BUFFER_ALIGNMENT != 0 || target.offset() % wgpu::COPY_BUFFER_ALIGNMENT != 0
135 || source.offset().checked_add(size).is_none_or(|end| end > src.size())
136 || target.offset().checked_add(size).is_none_or(|end| end > dst.size()) {
137 return Err(IoError::UnsupportedIoOperation { backtrace: ruda_core::backtrace::BackTrace::capture() });
138 }
139 let validation = self.device.push_error_scope(wgpu::ErrorFilter::Validation);
140 let allocation = self.device.push_error_scope(wgpu::ErrorFilter::OutOfMemory);
141 let internal = self.device.push_error_scope(wgpu::ErrorFilter::Internal);
142 let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("RUDA adaptive memory copies") });
143 encoder.copy_buffer_to_buffer(src, source.offset(), dst, target.offset(), size);
144 self.relocation_queue.as_ref().expect("relocation queue").submit([encoder.finish()]);
145 let errors = [internal.pop(), allocation.pop(), validation.pop()];
146 #[cfg(not(target_family = "wasm"))]
147 {
148 let mut first = None;
149 for error in errors {
150 if let Some(error) = ruda_core::future::block_on(error) {
151 if first.is_none() { first = Some(error); }
152 }
153 }
154 if let Some(error) = first {
155 return Err(IoError::Unknown { description: format!("WGPU relocation copy: {error}"),
156 backtrace: ruda_core::backtrace::BackTrace::capture() });
157 }
158 }
159 #[cfg(target_family = "wasm")]
160 let _ = errors;
161 Ok(())
162 }
163
164 fn relocation_complete(&mut self) -> Result<(), IoError> { self.wait_relocation() }
165
166 fn get(&mut self, handle: &StorageHandle) -> Self::Resource {
167 let buffer = self.memory.get(&handle.id).unwrap();
168 WgpuResource::new(buffer.clone(), handle.offset(), handle.size())
169 }
170
171 #[cfg_attr(
172 feature = "tracing",
173 tracing::instrument(level = "trace", skip(self, size))
174 )]
175 fn alloc(&mut self, size: u64) -> Result<StorageHandle, IoError> {
176 let id = StorageId::new();
177
178 let alloc_size = size.max(MIN_BUFFER_SIZE);
179
180 let scopes = self.relocation_queue.as_ref().map(|_| [
181 self.device.push_error_scope(wgpu::ErrorFilter::Validation),
182 self.device.push_error_scope(wgpu::ErrorFilter::OutOfMemory),
183 self.device.push_error_scope(wgpu::ErrorFilter::Internal),
184 ]);
185 let buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
186 label: None,
187 size: alloc_size,
188 usage: self.buffer_usages,
189 mapped_at_creation: false,
190 });
191
192 if let Some(scopes) = scopes {
193 let errors = scopes.into_iter().rev().map(|scope| scope.pop()).collect::<Vec<_>>();
194 #[cfg(not(target_family = "wasm"))]
195 {
196 let mut first = None;
197 for error in errors {
198 if let Some(error) = ruda_core::future::block_on(error) {
199 if first.is_none() { first = Some(error); }
200 }
201 }
202 if let Some(error) = first {
203 return Err(IoError::Unknown { description: format!("WGPU adaptive allocation: {error}"),
204 backtrace: ruda_core::backtrace::BackTrace::capture() });
205 }
206 }
207 #[cfg(target_family = "wasm")]
208 let _ = errors;
209 }
210
211 self.memory.insert(id, buffer);
212 Ok(StorageHandle::new(
213 id,
214 StorageUtilization { offset: 0, size },
215 ))
216 }
217
218 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
219 fn dealloc(&mut self, id: StorageId) {
220 self.memory.remove(&id);
221 }
222
223 fn flush(&mut self) {
224 }
226}