bevy/crates/bevy_wgpu/src/wgpu_resources.rs
Carter Anderson 115a009c16 cargo fmt
2020-04-24 18:55:15 -07:00

494 lines
18 KiB
Rust

use crate::{renderer::WgpuRenderResourceContext, wgpu_type_converter::WgpuInto};
use bevy_asset::{Handle, HandleUntyped};
use bevy_render::{
pipeline::{BindGroupDescriptorId, PipelineDescriptor},
render_resource::{BufferInfo, RenderResource, RenderResourceSetId, ResourceInfo},
renderer::RenderResourceContext,
shader::Shader,
texture::{Extent3d, SamplerDescriptor, TextureDescriptor},
};
use bevy_window::{Window, WindowId};
use std::{
collections::HashMap,
sync::{Arc, RwLock, RwLockReadGuard},
};
#[derive(Default)]
pub struct WgpuBindGroupInfo {
pub bind_groups: HashMap<RenderResourceSetId, wgpu::BindGroup>,
}
/// Grabs a read lock on all wgpu resources. When paired with WgpuResourceRefs, this allows
/// you to pass in wgpu resources to wgpu::RenderPass<'a> with the appropriate lifetime. This is accomplished by
/// grabbing a WgpuResourcesReadLock _before_ creating a wgpu::RenderPass, getting a WgpuResourcesRefs, and storing that
/// in the pass.
///
/// This is only a problem because RwLockReadGuard.read() erases the guard's lifetime and creates a new anonymous lifetime. If
/// you call RwLockReadGuard.read() during a pass, the reference will have an anonymous lifetime that lives for less than the
/// pass, which violates the lifetime constraints in place.
///
/// The biggest implication of this design (other than the additional boilerplate here) is that beginning a render pass
/// blocks writes to these resources. This means that if the pass attempts to write any resource, a deadlock will occur. WgpuResourceRefs
/// only has immutable references, so the only way to make a deadlock happen is to access WgpuResources directly in the pass. It also means
/// that other threads attempting to write resources will need to wait for pass encoding to finish. Almost all writes should occur before
/// passes start, so this hopefully won't be a problem.
///
/// It is worth comparing the performance of this to transactional / copy-based approaches. This lock based design guarantees
/// consistency, doesn't perform redundant allocations, and only blocks when a write is occurring. A copy based approach would
/// never block, but would require more allocations / state-synchronization, which I expect will be more expensive. It would also be
/// "eventually consistent" instead of "strongly consistent".
///
/// 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<RenderResource, wgpu::Buffer>>,
pub textures: RwLockReadGuard<'a, HashMap<RenderResource, wgpu::TextureView>>,
pub swap_chain_outputs: RwLockReadGuard<'a, HashMap<RenderResource, wgpu::SwapChainOutput>>,
pub render_pipelines:
RwLockReadGuard<'a, HashMap<Handle<PipelineDescriptor>, wgpu::RenderPipeline>>,
pub bind_groups: RwLockReadGuard<'a, HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>>,
}
impl<'a> WgpuResourcesReadLock<'a> {
pub fn refs(&'a self) -> WgpuResourceRefs<'a> {
WgpuResourceRefs {
buffers: &self.buffers,
textures: &self.textures,
swap_chain_outputs: &self.swap_chain_outputs,
render_pipelines: &self.render_pipelines,
bind_groups: &self.bind_groups,
}
}
}
/// Stores read only references to WgpuResource collections. See WgpuResourcesReadLock docs for context on why this exists
pub struct WgpuResourceRefs<'a> {
pub buffers: &'a HashMap<RenderResource, wgpu::Buffer>,
pub textures: &'a HashMap<RenderResource, wgpu::TextureView>,
pub swap_chain_outputs: &'a HashMap<RenderResource, wgpu::SwapChainOutput>,
pub render_pipelines: &'a HashMap<Handle<PipelineDescriptor>, wgpu::RenderPipeline>,
pub bind_groups: &'a HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>,
}
#[derive(Default, Clone)]
pub struct WgpuResources {
// TODO: remove this from WgpuResources. it doesn't need to be here
pub window_surfaces: Arc<RwLock<HashMap<WindowId, wgpu::Surface>>>,
pub window_swap_chains: Arc<RwLock<HashMap<WindowId, wgpu::SwapChain>>>,
pub swap_chain_outputs: Arc<RwLock<HashMap<RenderResource, wgpu::SwapChainOutput>>>,
pub buffers: Arc<RwLock<HashMap<RenderResource, wgpu::Buffer>>>,
pub texture_views: Arc<RwLock<HashMap<RenderResource, wgpu::TextureView>>>,
pub textures: Arc<RwLock<HashMap<RenderResource, wgpu::Texture>>>,
pub samplers: Arc<RwLock<HashMap<RenderResource, wgpu::Sampler>>>,
pub resource_info: Arc<RwLock<HashMap<RenderResource, ResourceInfo>>>,
pub shader_modules: Arc<RwLock<HashMap<Handle<Shader>, wgpu::ShaderModule>>>,
pub render_pipelines: Arc<RwLock<HashMap<Handle<PipelineDescriptor>, wgpu::RenderPipeline>>>,
pub bind_groups: Arc<RwLock<HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>>>,
pub bind_group_layouts: Arc<RwLock<HashMap<BindGroupDescriptorId, wgpu::BindGroupLayout>>>,
pub asset_resources: Arc<RwLock<HashMap<(HandleUntyped, usize), RenderResource>>>,
}
impl WgpuResources {
pub fn read(&self) -> WgpuResourcesReadLock {
WgpuResourcesReadLock {
buffers: self.buffers.read().unwrap(),
textures: self.texture_views.read().unwrap(),
swap_chain_outputs: self.swap_chain_outputs.read().unwrap(),
render_pipelines: self.render_pipelines.read().unwrap(),
bind_groups: self.bind_groups.read().unwrap(),
}
}
pub fn set_window_surface(&self, window_id: WindowId, surface: wgpu::Surface) {
self.window_surfaces
.write()
.unwrap()
.insert(window_id, surface);
}
pub fn next_swap_chain_texture(&self, window_id: WindowId) -> RenderResource {
let mut swap_chain_outputs = self.window_swap_chains.write().unwrap();
let swap_chain_output = swap_chain_outputs.get_mut(&window_id).unwrap();
let next_texture = swap_chain_output.get_next_texture().unwrap();
let render_resource = RenderResource::new();
// TODO: Add ResourceInfo
self.swap_chain_outputs
.write()
.unwrap()
.insert(render_resource, next_texture);
render_resource
}
pub fn remove_swap_chain_texture(&self, render_resource: RenderResource) {
self.swap_chain_outputs
.write()
.unwrap()
.remove(&render_resource);
}
pub fn remove_all_swap_chain_textures(&self) {
self.swap_chain_outputs.write().unwrap().clear();
}
pub fn create_window_swap_chain(&self, device: &wgpu::Device, window: &Window) {
let swap_chain_descriptor: wgpu::SwapChainDescriptor = window.wgpu_into();
let surfaces = self.window_surfaces.read().unwrap();
let surface = surfaces
.get(&window.id)
.expect("No surface found for window");
let swap_chain = device.create_swap_chain(surface, &swap_chain_descriptor);
self.window_swap_chains
.write()
.unwrap()
.insert(window.id, swap_chain);
}
pub fn add_resource_info(&self, resource: RenderResource, resource_info: ResourceInfo) {
self.resource_info
.write()
.unwrap()
.insert(resource, resource_info);
}
pub fn has_bind_group(
&self,
bind_group_descriptor_id: BindGroupDescriptorId,
render_resource_set_id: RenderResourceSetId,
) -> bool {
if let Some(bind_group_info) = self
.bind_groups
.read()
.unwrap()
.get(&bind_group_descriptor_id)
{
bind_group_info
.bind_groups
.get(&render_resource_set_id)
.is_some()
} else {
false
}
}
pub fn create_bind_group(
&self,
device: &wgpu::Device,
render_resource_set_id: RenderResourceSetId,
bind_group_descriptor: &wgpu::BindGroupDescriptor,
) -> wgpu::BindGroup {
log::trace!(
"created bind group for RenderResourceSet {:?}",
render_resource_set_id
);
log::trace!("{:#?}", bind_group_descriptor);
device.create_bind_group(bind_group_descriptor)
}
pub fn set_bind_group(
&self,
bind_group_descriptor_id: BindGroupDescriptorId,
render_resource_set_id: RenderResourceSetId,
bind_group: wgpu::BindGroup,
) {
let mut bind_groups = self.bind_groups.write().unwrap();
let bind_group_info = bind_groups
.entry(bind_group_descriptor_id)
.or_insert_with(|| WgpuBindGroupInfo::default());
bind_group_info
.bind_groups
.insert(render_resource_set_id, bind_group);
}
pub fn create_buffer(&self, device: &wgpu::Device, buffer_info: BufferInfo) -> RenderResource {
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: buffer_info.size as u64,
usage: buffer_info.buffer_usage.wgpu_into(),
});
let resource = RenderResource::new();
self.add_resource_info(resource, ResourceInfo::Buffer(buffer_info));
self.buffers.write().unwrap().insert(resource, buffer);
resource
}
pub fn create_buffer_with_data(
&self,
device: &wgpu::Device,
mut buffer_info: BufferInfo,
data: &[u8],
) -> RenderResource {
buffer_info.size = data.len();
let buffer = device.create_buffer_with_data(data, buffer_info.buffer_usage.wgpu_into());
self.assign_buffer(buffer, buffer_info)
}
// TODO: taking a closure isn't fantastic. is there any way to make this better without exposing the lock in the interface?
pub fn get_resource_info(
&self,
resource: RenderResource,
handle_info: &mut dyn FnMut(Option<&ResourceInfo>),
) {
let resource_info = self.resource_info.read().unwrap();
let info = resource_info.get(&resource);
handle_info(info);
}
pub fn remove_buffer(&self, resource: RenderResource) {
self.buffers.write().unwrap().remove(&resource);
self.resource_info.write().unwrap().remove(&resource);
}
pub fn assign_buffer(&self, buffer: wgpu::Buffer, buffer_info: BufferInfo) -> RenderResource {
let resource = RenderResource::new();
self.add_resource_info(resource, ResourceInfo::Buffer(buffer_info));
self.buffers.write().unwrap().insert(resource, buffer);
resource
}
// TODO: clean this up
pub fn begin_create_buffer_mapped_render_context(
buffer_info: &BufferInfo,
render_resources: &WgpuRenderResourceContext,
setup_data: &mut dyn FnMut(&mut [u8], &dyn RenderResourceContext),
) -> wgpu::Buffer {
let device = render_resources.device.clone();
let mut mapped = device.create_buffer_mapped(&wgpu::BufferDescriptor {
size: buffer_info.size as u64,
usage: buffer_info.buffer_usage.wgpu_into(),
label: None,
});
setup_data(&mut mapped.data, render_resources);
mapped.finish()
}
pub fn copy_buffer_to_buffer(
&self,
encoder: &mut wgpu::CommandEncoder,
source_buffer: RenderResource,
source_offset: u64,
destination_buffer: RenderResource,
destination_offset: u64,
size: u64,
) {
let buffers = self.buffers.read().unwrap();
let source = buffers.get(&source_buffer).unwrap();
let destination = buffers.get(&destination_buffer).unwrap();
encoder.copy_buffer_to_buffer(source, source_offset, destination, destination_offset, size);
}
pub fn copy_buffer_to_texture(
&self,
encoder: &mut wgpu::CommandEncoder,
source_buffer: RenderResource,
source_offset: u64,
source_bytes_per_row: u32,
destination_texture: RenderResource,
destination_origin: [u32; 3], // TODO: replace with math type
destination_mip_level: u32,
destination_array_layer: u32,
size: Extent3d,
) {
let buffers = self.buffers.read().unwrap();
let textures = self.textures.read().unwrap();
let source = buffers.get(&source_buffer).unwrap();
let destination = textures.get(&destination_texture).unwrap();
encoder.copy_buffer_to_texture(
wgpu::BufferCopyView {
buffer: source,
offset: source_offset,
bytes_per_row: source_bytes_per_row,
rows_per_image: 0, // NOTE: Example sets this to 0, but should it be height?
},
wgpu::TextureCopyView {
texture: destination,
mip_level: destination_mip_level,
array_layer: destination_array_layer,
origin: wgpu::Origin3d {
x: destination_origin[0],
y: destination_origin[1],
z: destination_origin[2],
},
},
size.wgpu_into(),
);
}
pub fn create_shader_module(
&self,
device: &wgpu::Device,
shader_handle: Handle<Shader>,
shader: &Shader,
) {
let shader_module = device.create_shader_module(&shader.get_spirv(None));
self.shader_modules
.write()
.unwrap()
.insert(shader_handle, shader_module);
}
pub fn create_sampler(
&self,
device: &wgpu::Device,
sampler_descriptor: &SamplerDescriptor,
) -> RenderResource {
let descriptor: wgpu::SamplerDescriptor = (*sampler_descriptor).wgpu_into();
let sampler = device.create_sampler(&descriptor);
let resource = RenderResource::new();
self.samplers.write().unwrap().insert(resource, sampler);
self.add_resource_info(resource, ResourceInfo::Sampler);
resource
}
pub fn create_texture(
&self,
device: &wgpu::Device,
texture_descriptor: &TextureDescriptor,
) -> RenderResource {
let descriptor: wgpu::TextureDescriptor = (*texture_descriptor).wgpu_into();
let texture = device.create_texture(&descriptor);
let texture_view = texture.create_default_view();
let resource = RenderResource::new();
self.add_resource_info(resource, ResourceInfo::Texture);
self.texture_views
.write()
.unwrap()
.insert(resource, texture_view);
self.textures.write().unwrap().insert(resource, texture);
resource
}
pub fn create_texture_with_data(
&self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
texture_descriptor: &TextureDescriptor,
bytes: &[u8],
) -> RenderResource {
let descriptor: wgpu::TextureDescriptor = (*texture_descriptor).wgpu_into();
let texture = device.create_texture(&descriptor);
let texture_view = texture.create_default_view();
let temp_buf = device.create_buffer_with_data(bytes, wgpu::BufferUsage::COPY_SRC);
encoder.copy_buffer_to_texture(
wgpu::BufferCopyView {
buffer: &temp_buf,
offset: 0,
bytes_per_row: 4 * descriptor.size.width,
rows_per_image: 0, // NOTE: Example sets this to 0, but should it be height?
},
wgpu::TextureCopyView {
texture: &texture,
mip_level: 0,
array_layer: 0,
origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
},
descriptor.size,
);
let resource = RenderResource::new();
self.add_resource_info(resource, ResourceInfo::Texture);
self.texture_views
.write()
.unwrap()
.insert(resource, texture_view);
resource
}
pub fn remove_texture(&self, resource: RenderResource) {
self.texture_views.write().unwrap().remove(&resource);
self.textures.write().unwrap().remove(&resource);
self.resource_info.write().unwrap().remove(&resource);
}
pub fn remove_sampler(&self, resource: RenderResource) {
self.samplers.write().unwrap().remove(&resource);
self.resource_info.write().unwrap().remove(&resource);
}
pub fn set_asset_resource<T>(
&mut self,
handle: Handle<T>,
render_resource: RenderResource,
index: usize,
) where
T: 'static,
{
self.asset_resources
.write()
.unwrap()
.insert((handle.into(), index), render_resource);
}
pub fn get_asset_resource<T>(
&mut self,
handle: Handle<T>,
index: usize,
) -> Option<RenderResource>
where
T: 'static,
{
self.asset_resources
.write()
.unwrap()
.get(&(handle.into(), index))
.cloned()
}
pub fn set_asset_resource_untyped(
&self,
handle: HandleUntyped,
render_resource: RenderResource,
index: usize,
) {
self.asset_resources
.write()
.unwrap()
.insert((handle, index), render_resource);
}
pub fn get_asset_resource_untyped(
&self,
handle: HandleUntyped,
index: usize,
) -> Option<RenderResource> {
self.asset_resources
.write()
.unwrap()
.get(&(handle, index))
.cloned()
}
pub fn create_bind_group_layout(
&self,
device: &wgpu::Device,
bind_group_id: BindGroupDescriptorId,
descriptor: &wgpu::BindGroupLayoutDescriptor,
) {
let wgpu_bind_group_layout = device.create_bind_group_layout(descriptor);
self.bind_group_layouts
.write()
.unwrap()
.insert(bind_group_id, wgpu_bind_group_layout);
}
pub fn create_render_pipeline(
&self,
device: &wgpu::Device,
descriptor: &wgpu::RenderPipelineDescriptor,
) -> wgpu::RenderPipeline {
device.create_render_pipeline(&descriptor)
}
pub fn set_render_pipeline(
&self,
pipeline_handle: Handle<PipelineDescriptor>,
render_pipeline: wgpu::RenderPipeline,
) {
self.render_pipelines
.write()
.unwrap()
.insert(pipeline_handle, render_pipeline);
}
}