render: add direct buffer mapping/unmapping

This commit is contained in:
Carter Anderson 2020-07-11 12:29:07 -07:00
parent cb1ffb42c4
commit 6d58a5a033
9 changed files with 231 additions and 201 deletions

View File

@ -58,10 +58,8 @@ impl SystemNode for LightsNode {
LightsNodeSystemState {
command_queue: self.command_queue.clone(),
max_lights: self.max_lights,
tmp_count_buffer: None,
tmp_light_buffer: None,
light_buffer: None,
lights_are_dirty: true,
staging_buffer: None,
},
);
system
@ -71,10 +69,7 @@ impl SystemNode for LightsNode {
#[derive(Default)]
pub struct LightsNodeSystemState {
light_buffer: Option<BufferId>,
lights_are_dirty: bool,
// TODO: merge these
tmp_count_buffer: Option<BufferId>,
tmp_light_buffer: Option<BufferId>,
staging_buffer: Option<BufferId>,
command_queue: CommandQueue,
max_lights: usize,
}
@ -82,22 +77,30 @@ pub struct LightsNodeSystemState {
pub fn lights_node_system(
mut state: Local<LightsNodeSystemState>,
render_resource_context: Res<Box<dyn RenderResourceContext>>,
// TODO: this write on RenderResourceAssignments will prevent this system from running in parallel with other systems that do the same
// TODO: this write on RenderResourceBindings will prevent this system from running in parallel with other systems that do the same
mut render_resource_bindings: ResMut<RenderResourceBindings>,
mut query: Query<(&Light, &Transform, &Translation)>,
) {
let state = &mut state;
if !state.lights_are_dirty {
return;
}
let render_resource_context = &**render_resource_context;
if state.light_buffer.is_none() {
let light_uniform_size =
std::mem::size_of::<LightCount>() + state.max_lights * std::mem::size_of::<LightRaw>();
let light_count = query.iter().iter().count();
let size = std::mem::size_of::<LightRaw>();
let light_count_size = std::mem::size_of::<LightCount>();
let light_array_size = size * light_count;
let light_array_max_size = size * state.max_lights;
let current_light_uniform_size = light_count_size + light_array_size;
let max_light_uniform_size = light_count_size + light_array_max_size;
if let Some(staging_buffer) = state.staging_buffer {
if light_count == 0 {
return;
}
render_resource_context.map_buffer(staging_buffer);
} else {
let buffer = render_resource_context.create_buffer(BufferInfo {
size: light_uniform_size,
size: max_light_uniform_size,
buffer_usage: BufferUsage::UNIFORM | BufferUsage::COPY_SRC | BufferUsage::COPY_DST,
..Default::default()
});
@ -105,74 +108,45 @@ pub fn lights_node_system(
uniform::LIGHTS,
RenderResourceBinding::Buffer {
buffer,
range: 0..light_uniform_size as u64,
range: 0..max_light_uniform_size as u64,
dynamic_index: None,
},
);
state.light_buffer = Some(buffer);
let staging_buffer = render_resource_context.create_buffer(BufferInfo {
size: max_light_uniform_size,
buffer_usage: BufferUsage::COPY_SRC | BufferUsage::MAP_WRITE,
mapped_at_creation: true,
});
state.staging_buffer = Some(staging_buffer);
}
let light_count = query.iter().iter().count();
if light_count == 0 {
return;
}
state.lights_are_dirty = false;
let size = std::mem::size_of::<LightRaw>();
let total_size = size * light_count;
let light_count_size = std::mem::size_of::<LightCount>();
if let Some(old_tmp_light_buffer) = state.tmp_light_buffer {
render_resource_context.remove_buffer(old_tmp_light_buffer);
}
if let Some(old_tmp_count_buffer) = state.tmp_count_buffer {
render_resource_context.remove_buffer(old_tmp_count_buffer);
}
state.tmp_light_buffer = Some(render_resource_context.create_buffer_mapped(
BufferInfo {
size: total_size,
buffer_usage: BufferUsage::COPY_SRC,
..Default::default()
},
let staging_buffer = state.staging_buffer.unwrap();
render_resource_context.write_mapped_buffer(
staging_buffer,
0..current_light_uniform_size as u64,
&mut |data, _renderer| {
// light count
data[0..light_count_size].copy_from_slice([light_count as u32, 0, 0, 0].as_bytes());
// light array
for ((light, transform, translation), slot) in
query.iter().iter().zip(data.chunks_exact_mut(size))
query.iter().iter().zip(data[light_count_size..current_light_uniform_size].chunks_exact_mut(size))
{
slot.copy_from_slice(
LightRaw::from(&light, &transform.value, &translation).as_bytes(),
);
}
},
));
state.tmp_count_buffer = Some(render_resource_context.create_buffer_mapped(
BufferInfo {
size: light_count_size,
buffer_usage: BufferUsage::COPY_SRC,
..Default::default()
},
&mut |data, _renderer| {
data.copy_from_slice([light_count as u32, 0, 0, 0].as_bytes());
},
));
let tmp_count_buffer = state.tmp_count_buffer.unwrap();
);
render_resource_context.unmap_buffer(staging_buffer);
let light_buffer = state.light_buffer.unwrap();
state.command_queue.copy_buffer_to_buffer(
tmp_count_buffer,
staging_buffer,
0,
light_buffer,
0,
light_count_size as u64,
);
let tmp_light_buffer = state.tmp_light_buffer.unwrap();
state.command_queue.copy_buffer_to_buffer(
tmp_light_buffer,
0,
light_buffer,
light_count_size as u64,
total_size as u64,
max_light_uniform_size as u64,
);
}

View File

@ -51,6 +51,7 @@ impl SystemNode for CameraNode {
camera_name: self.camera_name.clone(),
command_queue: self.command_queue.clone(),
camera_buffer: None,
staging_buffer: None,
},
);
system
@ -62,6 +63,7 @@ pub struct CameraNodeState {
command_queue: CommandQueue,
camera_name: Cow<'static, str>,
camera_buffer: Option<BufferId>,
staging_buffer: Option<BufferId>,
}
pub fn camera_node_system(
@ -74,27 +76,7 @@ pub fn camera_node_system(
query: Query<(&Camera, &Transform)>,
) {
let render_resource_context = &**render_resource_context;
state.camera_buffer = Some(match state.camera_buffer {
Some(buffer) => buffer,
None => {
let size = std::mem::size_of::<[[f32; 4]; 4]>();
let buffer = render_resource_context.create_buffer(BufferInfo {
size,
buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM,
..Default::default()
});
render_resource_bindings.set(
&state.camera_name,
RenderResourceBinding::Buffer {
buffer,
range: 0..size as u64,
dynamic_index: None,
},
);
state.camera_buffer = Some(buffer);
buffer
}
});
let (camera, transform) = if let Some(camera_entity) = active_cameras.get(&state.camera_name) {
(
query.get::<Camera>(camera_entity).unwrap(),
@ -104,24 +86,55 @@ pub fn camera_node_system(
return;
};
let staging_buffer = if let Some(staging_buffer) = state.staging_buffer {
render_resource_context.map_buffer(staging_buffer);
staging_buffer
} else {
let size = std::mem::size_of::<[[f32; 4]; 4]>();
let buffer = render_resource_context.create_buffer(BufferInfo {
size,
buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM,
..Default::default()
});
render_resource_bindings.set(
&state.camera_name,
RenderResourceBinding::Buffer {
buffer,
range: 0..size as u64,
dynamic_index: None,
},
);
state.camera_buffer = Some(buffer);
let staging_buffer = render_resource_context.create_buffer(BufferInfo {
size,
buffer_usage: BufferUsage::COPY_SRC | BufferUsage::MAP_WRITE,
mapped_at_creation: true,
});
state.staging_buffer = Some(staging_buffer);
staging_buffer
};
let matrix_size = std::mem::size_of::<[[f32; 4]; 4]>();
let camera_matrix: [f32; 16] =
(camera.projection_matrix * transform.value.inverse()).to_cols_array();
let tmp_buffer = render_resource_context.create_buffer_mapped(
BufferInfo {
size: matrix_size,
buffer_usage: BufferUsage::COPY_SRC,
..Default::default()
},
render_resource_context.write_mapped_buffer(
staging_buffer,
0..matrix_size as u64,
&mut |data, _renderer| {
data[0..matrix_size].copy_from_slice(camera_matrix.as_bytes());
},
);
render_resource_context.unmap_buffer(staging_buffer);
let camera_buffer = state.camera_buffer.unwrap();
state
.command_queue
.copy_buffer_to_buffer(tmp_buffer, 0, camera_buffer, 0, matrix_size as u64);
state.command_queue.free_buffer(tmp_buffer);
state.command_queue.copy_buffer_to_buffer(
staging_buffer,
0,
camera_buffer,
0,
matrix_size as u64,
);
}

View File

@ -63,6 +63,8 @@ where
T: render_resource::RenderResources,
{
uniform_arrays: Vec<Option<(String, BufferArrayStatus)>>,
staging_buffer: Option<BufferId>,
staging_buffer_size: usize,
_marker: PhantomData<T>,
}
@ -73,6 +75,8 @@ where
fn default() -> Self {
Self {
uniform_arrays: Default::default(),
staging_buffer: Default::default(),
staging_buffer_size: 0,
_marker: Default::default(),
}
}
@ -166,6 +170,7 @@ where
let buffer = render_resource_context.create_buffer(BufferInfo {
size: total_size,
buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM,
..Default::default()
});
buffer_array_status.current_item_capacity = new_capacity;
@ -181,7 +186,7 @@ where
buffer_array_status.buffer = Some(buffer);
}
}
fn update_staging_buffer_offsets(&mut self) -> usize {
fn update_staging_buffer(&mut self, render_resource_context: &dyn RenderResourceContext) {
let mut size = 0;
for dynamic_buffer_array_status in self.uniform_arrays.iter_mut() {
if let Some((_name, ref mut buffer_array_status)) = dynamic_buffer_array_status {
@ -190,7 +195,24 @@ where
}
}
size
if self.staging_buffer_size != size {
if let Some(staging_buffer) = self.staging_buffer {
render_resource_context.remove_buffer(staging_buffer);
}
if size > 0 {
let staging_buffer = render_resource_context.create_buffer(BufferInfo {
buffer_usage: BufferUsage::COPY_SRC | BufferUsage::MAP_WRITE,
size,
..Default::default()
});
self.staging_buffer = Some(staging_buffer);
} else {
self.staging_buffer = None;
}
self.staging_buffer_size = size;
}
}
fn setup_uniform_buffer_resources(
@ -412,7 +434,9 @@ fn render_resources_node_system<T: RenderResources>(
state
.uniform_buffer_arrays
.setup_buffer_arrays(render_resource_context, state.dynamic_uniforms);
let staging_buffer_size = state.uniform_buffer_arrays.update_staging_buffer_offsets();
state
.uniform_buffer_arrays
.update_staging_buffer(render_resource_context);
for (uniforms, draw, render_pipelines) in &mut query.iter() {
if !draw.is_visible {
@ -426,28 +450,11 @@ fn render_resources_node_system<T: RenderResources>(
)
}
if staging_buffer_size == 0 {
let mut staging_buffer: [u8; 0] = [];
for (uniforms, draw, render_pipelines) in &mut query.iter() {
if !draw.is_visible {
return;
}
state.uniform_buffer_arrays.setup_uniform_buffer_resources(
&uniforms,
state.dynamic_uniforms,
render_resource_context,
&mut render_pipelines.bindings,
&mut staging_buffer,
);
}
} else {
let staging_buffer = render_resource_context.create_buffer_mapped(
BufferInfo {
buffer_usage: BufferUsage::COPY_SRC,
size: staging_buffer_size,
..Default::default()
},
if let Some(staging_buffer) = state.uniform_buffer_arrays.staging_buffer {
render_resource_context.map_buffer(staging_buffer);
render_resource_context.write_mapped_buffer(
staging_buffer,
0..state.uniform_buffer_arrays.staging_buffer_size as u64,
&mut |mut staging_buffer, _render_resource_context| {
for (uniforms, draw, render_pipelines) in &mut query.iter() {
if !draw.is_visible {
@ -464,11 +471,27 @@ fn render_resources_node_system<T: RenderResources>(
}
},
);
render_resource_context.unmap_buffer(staging_buffer);
state
.uniform_buffer_arrays
.copy_staging_buffer_to_final_buffers(&mut state.command_queue, staging_buffer);
state.command_queue.free_buffer(staging_buffer);
} else {
// TODO: can we just remove this?
let mut staging_buffer: [u8; 0] = [];
for (uniforms, draw, render_pipelines) in &mut query.iter() {
if !draw.is_visible {
return;
}
state.uniform_buffer_arrays.setup_uniform_buffer_resources(
&uniforms,
state.dynamic_uniforms,
render_resource_context,
&mut render_pipelines.bindings,
&mut staging_buffer,
);
}
}
}
@ -576,7 +599,7 @@ fn asset_render_resources_node_system<T: RenderResources>(
state
.uniform_buffer_arrays
.setup_buffer_arrays(render_resource_context, state.dynamic_uniforms);
let staging_buffer_size = state.uniform_buffer_arrays.update_staging_buffer_offsets();
state.uniform_buffer_arrays.update_staging_buffer(render_resource_context);
for asset_handle in modified_assets.iter() {
let asset = assets.get(&asset_handle).expect(EXPECT_ASSET_MESSAGE);
@ -589,28 +612,11 @@ fn asset_render_resources_node_system<T: RenderResources>(
);
}
if staging_buffer_size == 0 {
let mut staging_buffer: [u8; 0] = [];
for asset_handle in modified_assets.iter() {
let asset = assets.get(&asset_handle).expect(EXPECT_ASSET_MESSAGE);
let mut render_resource_bindings =
asset_render_resource_bindings.get_or_insert_mut(*asset_handle);
// TODO: only setup buffer if we haven't seen this handle before
state.uniform_buffer_arrays.setup_uniform_buffer_resources(
&asset,
state.dynamic_uniforms,
render_resource_context,
&mut render_resource_bindings,
&mut staging_buffer,
);
}
} else {
let staging_buffer = render_resource_context.create_buffer_mapped(
BufferInfo {
buffer_usage: BufferUsage::COPY_SRC,
size: staging_buffer_size,
..Default::default()
},
if let Some(staging_buffer) = state.uniform_buffer_arrays.staging_buffer {
render_resource_context.map_buffer(staging_buffer);
render_resource_context.write_mapped_buffer(
staging_buffer,
0..state.uniform_buffer_arrays.staging_buffer_size as u64,
&mut |mut staging_buffer, _render_resource_context| {
for asset_handle in modified_assets.iter() {
let asset = assets.get(&asset_handle).expect(EXPECT_ASSET_MESSAGE);
@ -627,11 +633,26 @@ fn asset_render_resources_node_system<T: RenderResources>(
}
},
);
render_resource_context.unmap_buffer(staging_buffer);
state
.uniform_buffer_arrays
.copy_staging_buffer_to_final_buffers(&mut state.command_queue, staging_buffer);
state.command_queue.free_buffer(staging_buffer);
} else {
let mut staging_buffer: [u8; 0] = [];
for asset_handle in modified_assets.iter() {
let asset = assets.get(&asset_handle).expect(EXPECT_ASSET_MESSAGE);
let mut render_resource_bindings =
asset_render_resource_bindings.get_or_insert_mut(*asset_handle);
// TODO: only setup buffer if we haven't seen this handle before
state.uniform_buffer_arrays.setup_uniform_buffer_resources(
&asset,
state.dynamic_uniforms,
render_resource_context,
&mut render_resource_bindings,
&mut staging_buffer,
);
}
}
for (asset_handle, _draw, render_pipelines) in &mut query.iter() {

View File

@ -13,6 +13,7 @@ impl BufferId {
pub struct BufferInfo {
pub size: usize,
pub buffer_usage: BufferUsage,
pub mapped_at_creation: bool,
}
impl Default for BufferInfo {
@ -20,6 +21,7 @@ impl Default for BufferInfo {
BufferInfo {
size: 0,
buffer_usage: BufferUsage::empty(),
mapped_at_creation: false,
}
}
}

View File

@ -30,19 +30,26 @@ impl SharedBuffers {
) -> Option<RenderResourceBinding> {
if let Some(size) = render_resource.buffer_byte_len() {
// PERF: this buffer will be slow
let staging_buffer = self.render_resource_context.create_buffer_mapped(
BufferInfo {
size,
buffer_usage: BufferUsage::COPY_SRC,
},
let staging_buffer = self.render_resource_context.create_buffer(BufferInfo {
size,
buffer_usage: BufferUsage::COPY_SRC | BufferUsage::MAP_WRITE,
mapped_at_creation: true,
});
self.render_resource_context.write_mapped_buffer(
staging_buffer,
0..size as u64,
&mut |data, _renderer| {
render_resource.write_buffer_bytes(data);
},
);
self.render_resource_context.unmap_buffer(staging_buffer);
let destination_buffer = self.render_resource_context.create_buffer(BufferInfo {
size,
buffer_usage: BufferUsage::COPY_DST | buffer_usage,
..Default::default()
});
let mut command_queue = self.command_queue.write().unwrap();

View File

@ -9,6 +9,7 @@ use bevy_asset::{Assets, Handle, HandleUntyped};
use bevy_window::{Window, WindowId};
use std::{
collections::HashMap,
ops::Range,
sync::{Arc, RwLock},
};
@ -52,15 +53,18 @@ impl RenderResourceContext for HeadlessRenderResourceContext {
self.add_buffer_info(buffer, buffer_info);
buffer
}
fn create_buffer_mapped(
fn write_mapped_buffer(
&self,
buffer_info: BufferInfo,
setup_data: &mut dyn FnMut(&mut [u8], &dyn RenderResourceContext),
) -> BufferId {
let mut buffer = vec![0; buffer_info.size];
setup_data(&mut buffer, self);
BufferId::new()
id: BufferId,
_range: Range<u64>,
write: &mut dyn FnMut(&mut [u8], &dyn RenderResourceContext),
) {
let size = self.buffer_info.read().unwrap().get(&id).unwrap().size;
let mut buffer = vec![0; size];
write(&mut buffer, self);
}
fn map_buffer(&self, _id: BufferId) {}
fn unmap_buffer(&self, _id: BufferId) {}
fn create_buffer_with_data(&self, buffer_info: BufferInfo, _data: &[u8]) -> BufferId {
let buffer = BufferId::new();
self.add_buffer_info(buffer, buffer_info);

View File

@ -7,6 +7,7 @@ use crate::{
use bevy_asset::{Assets, Handle, HandleUntyped};
use bevy_window::{Window, WindowId};
use downcast_rs::{impl_downcast, Downcast};
use std::ops::Range;
pub trait RenderResourceContext: Downcast + Send + Sync + 'static {
fn create_swap_chain(&self, window: &Window);
@ -17,11 +18,20 @@ pub trait RenderResourceContext: Downcast + Send + Sync + 'static {
fn create_texture(&self, texture_descriptor: TextureDescriptor) -> TextureId;
fn create_buffer(&self, buffer_info: BufferInfo) -> BufferId;
// TODO: remove RenderResourceContext here
fn create_buffer_mapped(
fn write_mapped_buffer(
&self,
buffer_info: BufferInfo,
setup_data: &mut dyn FnMut(&mut [u8], &dyn RenderResourceContext),
) -> BufferId;
id: BufferId,
range: Range<u64>,
write: &mut dyn FnMut(&mut [u8], &dyn RenderResourceContext),
);
fn map_buffer(
&self,
id: BufferId,
);
fn unmap_buffer(
&self,
id: BufferId,
);
fn create_buffer_with_data(&self, buffer_info: BufferInfo, data: &[u8]) -> BufferId;
fn create_shader_module(&self, shader_handle: Handle<Shader>, shaders: &Assets<Shader>);
fn create_shader_module_from_source(&self, shader_handle: Handle<Shader>, shader: &Shader);

View File

@ -15,7 +15,7 @@ use bevy_render::{
texture::{Extent3d, SamplerDescriptor, TextureDescriptor},
};
use bevy_window::{Window, WindowId};
use std::sync::Arc;
use std::{ops::Range, sync::Arc};
#[derive(Clone)]
pub struct WgpuRenderResourceContext {
@ -167,43 +167,12 @@ impl RenderResourceContext for WgpuRenderResourceContext {
label: None,
size: buffer_info.size as u64,
usage: buffer_info.buffer_usage.wgpu_into(),
mapped_at_creation: false,
mapped_at_creation: buffer_info.mapped_at_creation,
});
let id = BufferId::new();
buffer_infos.insert(id, buffer_info);
buffers.insert(id, buffer);
id
}
fn create_buffer_mapped(
&self,
buffer_info: BufferInfo,
setup_data: &mut dyn FnMut(&mut [u8], &dyn RenderResourceContext),
) -> BufferId {
let usage: wgpu::BufferUsage = buffer_info.buffer_usage.wgpu_into();
let buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
size: buffer_info.size as u64,
usage: usage | wgpu::BufferUsage::MAP_WRITE,
label: None,
mapped_at_creation: false,
});
let buffer_slice = buffer.slice(..);
let data = buffer_slice.map_async(wgpu::MapMode::Write);
self.device.poll(wgpu::Maintain::Wait);
if let Ok(()) = pollster::block_on(data) {
let mut data = buffer_slice.get_mapped_range_mut();
setup_data(&mut data, self);
} else {
panic!("failed to map buffer to host");
}
buffer.unmap();
let id = BufferId::new();
let mut buffer_infos = self.resources.buffer_infos.write().unwrap();
let mut buffers = self.resources.buffers.write().unwrap();
buffer_infos.insert(id, buffer_info);
buffers.insert(id, buffer);
buffers.insert(id, Arc::new(buffer));
id
}
@ -219,7 +188,7 @@ impl RenderResourceContext for WgpuRenderResourceContext {
let id = BufferId::new();
buffer_infos.insert(id, buffer_info);
buffers.insert(id, buffer);
buffers.insert(id, Arc::new(buffer));
id
}
@ -522,4 +491,34 @@ impl RenderResourceContext for WgpuRenderResourceContext {
.get(&buffer)
.cloned()
}
fn write_mapped_buffer(
&self,
id: BufferId,
range: Range<u64>,
write: &mut dyn FnMut(&mut [u8], &dyn RenderResourceContext),
) {
let buffer = {
let buffers = self.resources.buffers.read().unwrap();
buffers.get(&id).unwrap().clone()
};
let buffer_slice = buffer.slice(range);
let mut data = buffer_slice.get_mapped_range_mut();
write(&mut data, self);
}
fn map_buffer(&self, id: BufferId) {
let buffers = self.resources.buffers.read().unwrap();
let buffer = buffers.get(&id).unwrap();
let buffer_slice = buffer.slice(..);
let data = buffer_slice.map_async(wgpu::MapMode::Write);
self.device.poll(wgpu::Maintain::Wait);
if let Err(_) = pollster::block_on(data) {
panic!("failed to map buffer to host");
}
}
fn unmap_buffer(&self, id: BufferId) {
let buffers = self.resources.buffers.read().unwrap();
let buffer = buffers.get(&id).unwrap();
buffer.unmap();
}
}

View File

@ -39,7 +39,7 @@ pub struct WgpuBindGroupInfo {
/// Single threaded implementations don't need to worry about these lifetimes constraints at all. RenderPasses can use a RenderContext's
/// WgpuResources directly. RenderContext already has a lifetime greater than the RenderPass.
pub struct WgpuResourcesReadLock<'a> {
pub buffers: RwLockReadGuard<'a, HashMap<BufferId, wgpu::Buffer>>,
pub buffers: RwLockReadGuard<'a, HashMap<BufferId, Arc<wgpu::Buffer>>>,
pub textures: RwLockReadGuard<'a, HashMap<TextureId, wgpu::TextureView>>,
pub swap_chain_frames: RwLockReadGuard<'a, HashMap<TextureId, wgpu::SwapChainFrame>>,
pub render_pipelines:
@ -61,7 +61,7 @@ impl<'a> WgpuResourcesReadLock<'a> {
/// Stores read only references to WgpuResource collections. See WgpuResourcesReadLock docs for context on why this exists
pub struct WgpuResourceRefs<'a> {
pub buffers: &'a HashMap<BufferId, wgpu::Buffer>,
pub buffers: &'a HashMap<BufferId, Arc<wgpu::Buffer>>,
pub textures: &'a HashMap<TextureId, wgpu::TextureView>,
pub swap_chain_frames: &'a HashMap<TextureId, wgpu::SwapChainFrame>,
pub render_pipelines: &'a HashMap<Handle<PipelineDescriptor>, wgpu::RenderPipeline>,
@ -75,7 +75,7 @@ pub struct WgpuResources {
pub window_surfaces: Arc<RwLock<HashMap<WindowId, wgpu::Surface>>>,
pub window_swap_chains: Arc<RwLock<HashMap<WindowId, wgpu::SwapChain>>>,
pub swap_chain_frames: Arc<RwLock<HashMap<TextureId, wgpu::SwapChainFrame>>>,
pub buffers: Arc<RwLock<HashMap<BufferId, wgpu::Buffer>>>,
pub buffers: Arc<RwLock<HashMap<BufferId, Arc<wgpu::Buffer>>>>,
pub texture_views: Arc<RwLock<HashMap<TextureId, wgpu::TextureView>>>,
pub textures: Arc<RwLock<HashMap<TextureId, wgpu::Texture>>>,
pub samplers: Arc<RwLock<HashMap<SamplerId, wgpu::Sampler>>>,