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 encode_relocation_copy(&self, encoder: &mut wgpu::CommandEncoder, source: &StorageHandle, target: &StorageHandle) -> Result<(), IoError> {
90 let src = self.memory.get(&source.id).expect("relocation source");
91 let dst = self.memory.get(&target.id).expect("relocation target");
92 let size = source.size().next_multiple_of(wgpu::COPY_BUFFER_ALIGNMENT);
93 if source.offset() % wgpu::COPY_BUFFER_ALIGNMENT != 0 || target.offset() % wgpu::COPY_BUFFER_ALIGNMENT != 0
94 || source.offset().checked_add(size).is_none_or(|end| end > src.size())
95 || target.offset().checked_add(size).is_none_or(|end| end > dst.size()) {
96 return Err(IoError::UnsupportedIoOperation { backtrace: ruda_core::backtrace::BackTrace::capture() });
97 }
98 encoder.copy_buffer_to_buffer(src, source.offset(), dst, target.offset(), size);
99 Ok(())
100 }
101
102 fn wait_relocation(&self) -> Result<(), IoError> {
103 #[cfg(not(target_family = "wasm"))]
104 {
105 self.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None })
106 .map(|_| ()).map_err(|error| IoError::Unknown {
107 description: format!("WGPU relocation wait: {error}"),
108 backtrace: ruda_core::backtrace::BackTrace::capture(),
109 })
110 }
111 #[cfg(target_family = "wasm")]
112 {
113 Err(IoError::UnsupportedIoOperation { backtrace: ruda_core::backtrace::BackTrace::capture() })
114 }
115 }
116}
117
118impl ComputeStorage for WgpuStorage {
119 type Resource = WgpuResource;
120
121 fn alignment(&self) -> usize {
122 self.mem_alignment
123 }
124
125 fn get_pinned(&mut self, handle: &StorageHandle, binding: ruda::runtime::memory_management::ManagedMemoryBinding) -> Self::Resource {
126 let mut resource = self.get(handle);
127 resource.pin = Some(binding.pin());
128 resource
129 }
130
131 fn supports_relocation(&self) -> bool {
132 self.relocation_queue.is_some() && cfg!(not(target_family = "wasm"))
133 }
134
135 fn relocation_barrier(&mut self) -> Result<(), IoError> {
136 let queue = self.relocation_queue.as_ref().expect("relocation queue");
137 queue.submit([]);
140 self.wait_relocation()
141 }
142
143 fn relocation_copy(&mut self, source: &StorageHandle, target: &StorageHandle) -> Result<(), IoError> {
144 self.relocation_copy_batch(core::iter::once((source, target)))
145 }
146
147 fn relocation_copy_batch<'a>(
148 &mut self,
149 copies: impl IntoIterator<Item = (&'a StorageHandle, &'a StorageHandle)>,
150 ) -> Result<(), IoError> {
151 let mut copies = copies.into_iter().peekable();
152 if copies.peek().is_none() { return Ok(()); }
153 let validation = self.device.push_error_scope(wgpu::ErrorFilter::Validation);
154 let allocation = self.device.push_error_scope(wgpu::ErrorFilter::OutOfMemory);
155 let internal = self.device.push_error_scope(wgpu::ErrorFilter::Internal);
156 let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("RUDA adaptive memory copies") });
157 let copied = copies.try_for_each(|(source, target)| self.encode_relocation_copy(&mut encoder, source, target));
158 self.relocation_queue.as_ref().expect("relocation queue").submit([encoder.finish()]);
159 let errors = [internal.pop(), allocation.pop(), validation.pop()];
160 #[cfg(not(target_family = "wasm"))]
161 {
162 let mut first = None;
163 for error in errors {
164 if let Some(error) = ruda_core::future::block_on(error) {
165 if first.is_none() { first = Some(error); }
166 }
167 }
168 copied?;
169 if let Some(error) = first {
170 return Err(IoError::Unknown { description: format!("WGPU relocation copy: {error}"),
171 backtrace: ruda_core::backtrace::BackTrace::capture() });
172 }
173 }
174 #[cfg(target_family = "wasm")]
175 { let _ = errors; copied?; }
176 Ok(())
177 }
178
179 fn relocation_complete(&mut self) -> Result<(), IoError> { self.wait_relocation() }
180
181 fn get(&mut self, handle: &StorageHandle) -> Self::Resource {
182 let buffer = self.memory.get(&handle.id).unwrap();
183 WgpuResource::new(buffer.clone(), handle.offset(), handle.size())
184 }
185
186 #[cfg_attr(
187 feature = "tracing",
188 tracing::instrument(level = "trace", skip(self, size))
189 )]
190 fn alloc(&mut self, size: u64) -> Result<StorageHandle, IoError> {
191 let id = StorageId::new();
192
193 let alloc_size = size.max(MIN_BUFFER_SIZE);
194
195 let scopes = self.relocation_queue.as_ref().map(|_| [
196 self.device.push_error_scope(wgpu::ErrorFilter::Validation),
197 self.device.push_error_scope(wgpu::ErrorFilter::OutOfMemory),
198 self.device.push_error_scope(wgpu::ErrorFilter::Internal),
199 ]);
200 let buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
201 label: None,
202 size: alloc_size,
203 usage: self.buffer_usages,
204 mapped_at_creation: false,
205 });
206
207 if let Some(scopes) = scopes {
208 let errors = scopes.into_iter().rev().map(|scope| scope.pop()).collect::<Vec<_>>();
209 #[cfg(not(target_family = "wasm"))]
210 {
211 let mut first = None;
212 for error in errors {
213 if let Some(error) = ruda_core::future::block_on(error) {
214 if first.is_none() { first = Some(error); }
215 }
216 }
217 if let Some(error) = first {
218 return Err(IoError::Unknown { description: format!("WGPU adaptive allocation: {error}"),
219 backtrace: ruda_core::backtrace::BackTrace::capture() });
220 }
221 }
222 #[cfg(target_family = "wasm")]
223 let _ = errors;
224 }
225
226 self.memory.insert(id, buffer);
227 Ok(StorageHandle::new(
228 id,
229 StorageUtilization { offset: 0, size },
230 ))
231 }
232
233 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
234 fn dealloc(&mut self, id: StorageId) {
235 self.memory.remove(&id);
236 }
237
238 fn flush(&mut self) {
239 }
241}