Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_render/src/render_resource/uniform_buffer.rs
6596 views
1
use core::{marker::PhantomData, num::NonZero};
2
3
use crate::{
4
render_resource::Buffer,
5
renderer::{RenderDevice, RenderQueue},
6
};
7
use encase::{
8
internal::{AlignmentValue, BufferMut, WriteInto},
9
DynamicUniformBuffer as DynamicUniformBufferWrapper, ShaderType,
10
UniformBuffer as UniformBufferWrapper,
11
};
12
use wgpu::{
13
util::BufferInitDescriptor, BindingResource, BufferBinding, BufferDescriptor, BufferUsages,
14
};
15
16
use super::IntoBinding;
17
18
/// Stores data to be transferred to the GPU and made accessible to shaders as a uniform buffer.
19
///
20
/// Uniform buffers are available to shaders on a read-only basis. Uniform buffers are commonly used to make available to shaders
21
/// parameters that are constant during shader execution, and are best used for data that is relatively small in size as they are
22
/// only guaranteed to support up to 16kB per binding.
23
///
24
/// The contained data is stored in system RAM. [`write_buffer`](UniformBuffer::write_buffer) queues
25
/// copying of the data from system RAM to VRAM. Data in uniform buffers must follow [std140 alignment/padding requirements],
26
/// which is automatically enforced by this structure. Per the WGPU spec, uniform buffers cannot store runtime-sized array
27
/// (vectors), or structures with fields that are vectors.
28
///
29
/// Other options for storing GPU-accessible data are:
30
/// * [`BufferVec`](crate::render_resource::BufferVec)
31
/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer)
32
/// * [`DynamicUniformBuffer`]
33
/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer)
34
/// * [`RawBufferVec`](crate::render_resource::RawBufferVec)
35
/// * [`StorageBuffer`](crate::render_resource::StorageBuffer)
36
/// * [`Texture`](crate::render_resource::Texture)
37
///
38
/// [std140 alignment/padding requirements]: https://www.w3.org/TR/WGSL/#address-spaces-uniform
39
pub struct UniformBuffer<T: ShaderType> {
40
value: T,
41
scratch: UniformBufferWrapper<Vec<u8>>,
42
buffer: Option<Buffer>,
43
label: Option<String>,
44
changed: bool,
45
buffer_usage: BufferUsages,
46
}
47
48
impl<T: ShaderType> From<T> for UniformBuffer<T> {
49
fn from(value: T) -> Self {
50
Self {
51
value,
52
scratch: UniformBufferWrapper::new(Vec::new()),
53
buffer: None,
54
label: None,
55
changed: false,
56
buffer_usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM,
57
}
58
}
59
}
60
61
impl<T: ShaderType + Default> Default for UniformBuffer<T> {
62
fn default() -> Self {
63
Self {
64
value: T::default(),
65
scratch: UniformBufferWrapper::new(Vec::new()),
66
buffer: None,
67
label: None,
68
changed: false,
69
buffer_usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM,
70
}
71
}
72
}
73
74
impl<T: ShaderType + WriteInto> UniformBuffer<T> {
75
#[inline]
76
pub fn buffer(&self) -> Option<&Buffer> {
77
self.buffer.as_ref()
78
}
79
80
#[inline]
81
pub fn binding(&self) -> Option<BindingResource<'_>> {
82
Some(BindingResource::Buffer(
83
self.buffer()?.as_entire_buffer_binding(),
84
))
85
}
86
87
/// Set the data the buffer stores.
88
pub fn set(&mut self, value: T) {
89
self.value = value;
90
}
91
92
pub fn get(&self) -> &T {
93
&self.value
94
}
95
96
pub fn get_mut(&mut self) -> &mut T {
97
&mut self.value
98
}
99
100
pub fn set_label(&mut self, label: Option<&str>) {
101
let label = label.map(str::to_string);
102
103
if label != self.label {
104
self.changed = true;
105
}
106
107
self.label = label;
108
}
109
110
pub fn get_label(&self) -> Option<&str> {
111
self.label.as_deref()
112
}
113
114
/// Add more [`BufferUsages`] to the buffer.
115
///
116
/// This method only allows addition of flags to the default usage flags.
117
///
118
/// The default values for buffer usage are `BufferUsages::COPY_DST` and `BufferUsages::UNIFORM`.
119
pub fn add_usages(&mut self, usage: BufferUsages) {
120
self.buffer_usage |= usage;
121
self.changed = true;
122
}
123
124
/// Queues writing of data from system RAM to VRAM using the [`RenderDevice`]
125
/// and the provided [`RenderQueue`], if a GPU-side backing buffer already exists.
126
///
127
/// If a GPU-side buffer does not already exist for this data, such a buffer is initialized with currently
128
/// available data.
129
pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) {
130
self.scratch.write(&self.value).unwrap();
131
132
if self.changed || self.buffer.is_none() {
133
self.buffer = Some(device.create_buffer_with_data(&BufferInitDescriptor {
134
label: self.label.as_deref(),
135
usage: self.buffer_usage,
136
contents: self.scratch.as_ref(),
137
}));
138
self.changed = false;
139
} else if let Some(buffer) = &self.buffer {
140
queue.write_buffer(buffer, 0, self.scratch.as_ref());
141
}
142
}
143
}
144
145
impl<'a, T: ShaderType + WriteInto> IntoBinding<'a> for &'a UniformBuffer<T> {
146
#[inline]
147
fn into_binding(self) -> BindingResource<'a> {
148
self.buffer()
149
.expect("Failed to get buffer")
150
.as_entire_buffer_binding()
151
.into_binding()
152
}
153
}
154
155
/// Stores data to be transferred to the GPU and made accessible to shaders as a dynamic uniform buffer.
156
///
157
/// Dynamic uniform buffers are available to shaders on a read-only basis. Dynamic uniform buffers are commonly used to make
158
/// available to shaders runtime-sized arrays of parameters that are otherwise constant during shader execution, and are best
159
/// suited to data that is relatively small in size as they are only guaranteed to support up to 16kB per binding.
160
///
161
/// The contained data is stored in system RAM. [`write_buffer`](DynamicUniformBuffer::write_buffer) queues
162
/// copying of the data from system RAM to VRAM. Data in uniform buffers must follow [std140 alignment/padding requirements],
163
/// which is automatically enforced by this structure. Per the WGPU spec, uniform buffers cannot store runtime-sized array
164
/// (vectors), or structures with fields that are vectors.
165
///
166
/// Other options for storing GPU-accessible data are:
167
/// * [`BufferVec`](crate::render_resource::BufferVec)
168
/// * [`DynamicStorageBuffer`](crate::render_resource::DynamicStorageBuffer)
169
/// * [`GpuArrayBuffer`](crate::render_resource::GpuArrayBuffer)
170
/// * [`RawBufferVec`](crate::render_resource::RawBufferVec)
171
/// * [`StorageBuffer`](crate::render_resource::StorageBuffer)
172
/// * [`Texture`](crate::render_resource::Texture)
173
/// * [`UniformBuffer`]
174
///
175
/// [std140 alignment/padding requirements]: https://www.w3.org/TR/WGSL/#address-spaces-uniform
176
pub struct DynamicUniformBuffer<T: ShaderType> {
177
scratch: DynamicUniformBufferWrapper<Vec<u8>>,
178
buffer: Option<Buffer>,
179
label: Option<String>,
180
changed: bool,
181
buffer_usage: BufferUsages,
182
_marker: PhantomData<fn() -> T>,
183
}
184
185
impl<T: ShaderType> Default for DynamicUniformBuffer<T> {
186
fn default() -> Self {
187
Self {
188
scratch: DynamicUniformBufferWrapper::new(Vec::new()),
189
buffer: None,
190
label: None,
191
changed: false,
192
buffer_usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM,
193
_marker: PhantomData,
194
}
195
}
196
}
197
198
impl<T: ShaderType + WriteInto> DynamicUniformBuffer<T> {
199
pub fn new_with_alignment(alignment: u64) -> Self {
200
Self {
201
scratch: DynamicUniformBufferWrapper::new_with_alignment(Vec::new(), alignment),
202
buffer: None,
203
label: None,
204
changed: false,
205
buffer_usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM,
206
_marker: PhantomData,
207
}
208
}
209
210
#[inline]
211
pub fn buffer(&self) -> Option<&Buffer> {
212
self.buffer.as_ref()
213
}
214
215
#[inline]
216
pub fn binding(&self) -> Option<BindingResource<'_>> {
217
Some(BindingResource::Buffer(BufferBinding {
218
buffer: self.buffer()?,
219
offset: 0,
220
size: Some(T::min_size()),
221
}))
222
}
223
224
#[inline]
225
pub fn is_empty(&self) -> bool {
226
self.scratch.as_ref().is_empty()
227
}
228
229
/// Push data into the `DynamicUniformBuffer`'s internal vector (residing on system RAM).
230
#[inline]
231
pub fn push(&mut self, value: &T) -> u32 {
232
self.scratch.write(value).unwrap() as u32
233
}
234
235
pub fn set_label(&mut self, label: Option<&str>) {
236
let label = label.map(str::to_string);
237
238
if label != self.label {
239
self.changed = true;
240
}
241
242
self.label = label;
243
}
244
245
pub fn get_label(&self) -> Option<&str> {
246
self.label.as_deref()
247
}
248
249
/// Add more [`BufferUsages`] to the buffer.
250
///
251
/// This method only allows addition of flags to the default usage flags.
252
///
253
/// The default values for buffer usage are `BufferUsages::COPY_DST` and `BufferUsages::UNIFORM`.
254
pub fn add_usages(&mut self, usage: BufferUsages) {
255
self.buffer_usage |= usage;
256
self.changed = true;
257
}
258
259
/// Creates a writer that can be used to directly write elements into the target buffer.
260
///
261
/// This method uses less memory and performs fewer memory copies using over [`push`] and [`write_buffer`].
262
///
263
/// `max_count` *must* be greater than or equal to the number of elements that are to be written to the buffer, or
264
/// the writer will panic while writing. Dropping the writer will schedule the buffer write into the provided
265
/// [`RenderQueue`].
266
///
267
/// If there is no GPU-side buffer allocated to hold the data currently stored, or if a GPU-side buffer previously
268
/// allocated does not have enough capacity to hold `max_count` elements, a new GPU-side buffer is created.
269
///
270
/// Returns `None` if there is no allocated GPU-side buffer, and `max_count` is 0.
271
///
272
/// [`push`]: Self::push
273
/// [`write_buffer`]: Self::write_buffer
274
#[inline]
275
pub fn get_writer<'a>(
276
&'a mut self,
277
max_count: usize,
278
device: &RenderDevice,
279
queue: &'a RenderQueue,
280
) -> Option<DynamicUniformBufferWriter<'a, T>> {
281
let alignment = if cfg!(target_abi = "sim") {
282
// On iOS simulator on silicon macs, metal validation check that the host OS alignment
283
// is respected, but the device reports the correct value for iOS, which is smaller.
284
// Use the larger value.
285
// See https://github.com/gfx-rs/wgpu/issues/7057 - remove if it's not needed anymore.
286
AlignmentValue::new(256)
287
} else {
288
AlignmentValue::new(device.limits().min_uniform_buffer_offset_alignment as u64)
289
};
290
291
let mut capacity = self.buffer.as_deref().map(wgpu::Buffer::size).unwrap_or(0);
292
let size = alignment
293
.round_up(T::min_size().get())
294
.checked_mul(max_count as u64)
295
.unwrap();
296
297
if capacity < size || (self.changed && size > 0) {
298
let buffer = device.create_buffer(&BufferDescriptor {
299
label: self.label.as_deref(),
300
usage: self.buffer_usage,
301
size,
302
mapped_at_creation: false,
303
});
304
capacity = buffer.size();
305
self.buffer = Some(buffer);
306
self.changed = false;
307
}
308
309
if let Some(buffer) = self.buffer.as_deref() {
310
let buffer_view = queue
311
.write_buffer_with(buffer, 0, NonZero::<u64>::new(buffer.size())?)
312
.unwrap();
313
Some(DynamicUniformBufferWriter {
314
buffer: encase::DynamicUniformBuffer::new_with_alignment(
315
QueueWriteBufferViewWrapper {
316
capacity: capacity as usize,
317
buffer_view,
318
},
319
alignment.get(),
320
),
321
_marker: PhantomData,
322
})
323
} else {
324
None
325
}
326
}
327
328
/// Queues writing of data from system RAM to VRAM using the [`RenderDevice`]
329
/// and the provided [`RenderQueue`].
330
///
331
/// If there is no GPU-side buffer allocated to hold the data currently stored, or if a GPU-side buffer previously
332
/// allocated does not have enough capacity, a new GPU-side buffer is created.
333
#[inline]
334
pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) {
335
let capacity = self.buffer.as_deref().map(wgpu::Buffer::size).unwrap_or(0);
336
let size = self.scratch.as_ref().len() as u64;
337
338
if capacity < size || (self.changed && size > 0) {
339
self.buffer = Some(device.create_buffer_with_data(&BufferInitDescriptor {
340
label: self.label.as_deref(),
341
usage: self.buffer_usage,
342
contents: self.scratch.as_ref(),
343
}));
344
self.changed = false;
345
} else if let Some(buffer) = &self.buffer {
346
queue.write_buffer(buffer, 0, self.scratch.as_ref());
347
}
348
}
349
350
#[inline]
351
pub fn clear(&mut self) {
352
self.scratch.as_mut().clear();
353
self.scratch.set_offset(0);
354
}
355
}
356
357
/// A writer that can be used to directly write elements into the target buffer.
358
///
359
/// For more information, see [`DynamicUniformBuffer::get_writer`].
360
pub struct DynamicUniformBufferWriter<'a, T> {
361
buffer: encase::DynamicUniformBuffer<QueueWriteBufferViewWrapper<'a>>,
362
_marker: PhantomData<fn() -> T>,
363
}
364
365
impl<'a, T: ShaderType + WriteInto> DynamicUniformBufferWriter<'a, T> {
366
pub fn write(&mut self, value: &T) -> u32 {
367
self.buffer.write(value).unwrap() as u32
368
}
369
}
370
371
/// A wrapper to work around the orphan rule so that [`wgpu::QueueWriteBufferView`] can implement
372
/// [`BufferMut`].
373
struct QueueWriteBufferViewWrapper<'a> {
374
buffer_view: wgpu::QueueWriteBufferView<'a>,
375
// Must be kept separately and cannot be retrieved from buffer_view, as the read-only access will
376
// invoke a panic.
377
capacity: usize,
378
}
379
380
impl<'a> BufferMut for QueueWriteBufferViewWrapper<'a> {
381
#[inline]
382
fn capacity(&self) -> usize {
383
self.capacity
384
}
385
386
#[inline]
387
fn write<const N: usize>(&mut self, offset: usize, val: &[u8; N]) {
388
self.buffer_view.write(offset, val);
389
}
390
391
#[inline]
392
fn write_slice(&mut self, offset: usize, val: &[u8]) {
393
self.buffer_view.write_slice(offset, val);
394
}
395
}
396
397
impl<'a, T: ShaderType + WriteInto> IntoBinding<'a> for &'a DynamicUniformBuffer<T> {
398
#[inline]
399
fn into_binding(self) -> BindingResource<'a> {
400
self.binding().unwrap()
401
}
402
}
403
404