From 3ef951dcbc95362e093867598f41515443a4c1f2 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 24 Jun 2021 20:04:28 -0700 Subject: [PATCH] RenderAssetPlugin --- pipelined/bevy_render2/src/lib.rs | 3 +- pipelined/bevy_render2/src/render_asset.rs | 104 ++++++++++++++++++ .../src/texture/{texture.rs => image.rs} | 75 +++++++++++-- pipelined/bevy_render2/src/texture/mod.rs | 103 ++--------------- pipelined/bevy_sprite2/src/render/mod.rs | 79 ++++++------- 5 files changed, 212 insertions(+), 152 deletions(-) create mode 100644 pipelined/bevy_render2/src/render_asset.rs rename pipelined/bevy_render2/src/texture/{texture.rs => image.rs} (87%) diff --git a/pipelined/bevy_render2/src/lib.rs b/pipelined/bevy_render2/src/lib.rs index 693f2806d2..1500846112 100644 --- a/pipelined/bevy_render2/src/lib.rs +++ b/pipelined/bevy_render2/src/lib.rs @@ -2,6 +2,7 @@ pub mod camera; pub mod color; pub mod core_pipeline; pub mod mesh; +pub mod render_asset; pub mod render_graph; pub mod render_phase; pub mod render_resource; @@ -22,7 +23,7 @@ use crate::{ texture::ImagePlugin, view::{ViewPlugin, WindowRenderPlugin}, }; -use bevy_app::{App, Plugin, StartupStage}; +use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; #[derive(Default)] diff --git a/pipelined/bevy_render2/src/render_asset.rs b/pipelined/bevy_render2/src/render_asset.rs new file mode 100644 index 0000000000..aace214fd1 --- /dev/null +++ b/pipelined/bevy_render2/src/render_asset.rs @@ -0,0 +1,104 @@ +use std::marker::PhantomData; + +use crate::{ + renderer::{RenderDevice, RenderQueue}, + RenderStage, +}; +use bevy_app::{App, Plugin}; +use bevy_asset::{Asset, AssetEvent, Assets, Handle}; +use bevy_ecs::prelude::*; +use bevy_utils::{HashMap, HashSet}; + +pub trait RenderAsset: Asset { + type ExtractedAsset: Send + Sync + 'static; + type PreparedAsset: Send + Sync + 'static; + fn extract_asset(&self) -> Self::ExtractedAsset; + fn prepare_asset( + extracted_asset: Self::ExtractedAsset, + render_device: &RenderDevice, + render_queue: &RenderQueue, + ) -> Self::PreparedAsset; +} + +/// Extracts assets into gpu-usable data +#[derive(Default)] +pub struct RenderAssetPlugin(PhantomData A>); + +impl Plugin for RenderAssetPlugin { + fn build(&self, app: &mut App) { + let render_app = app.sub_app_mut(0); + render_app + .init_resource::>() + .init_resource::>() + .add_system_to_stage(RenderStage::Extract, extract_render_asset::.system()) + .add_system_to_stage(RenderStage::Prepare, prepare_render_asset::.system()); + } +} + +struct ExtractedAssets { + extracted: Vec<(Handle, A::ExtractedAsset)>, + removed: Vec>, +} + +impl Default for ExtractedAssets { + fn default() -> Self { + Self { + extracted: Default::default(), + removed: Default::default(), + } + } +} + +pub type RenderAssets = HashMap, A::PreparedAsset>; + +fn extract_render_asset( + mut commands: Commands, + mut events: EventReader>, + assets: Res>, +) { + let mut changed_assets = HashSet::default(); + let mut removed = Vec::new(); + for event in events.iter() { + match event { + AssetEvent::Created { handle } => { + changed_assets.insert(handle); + } + AssetEvent::Modified { handle } => { + changed_assets.insert(handle); + } + AssetEvent::Removed { handle } => { + if !changed_assets.remove(handle) { + removed.push(handle.clone_weak()); + } + } + } + } + + let mut extracted_assets = Vec::new(); + for handle in changed_assets.drain() { + if let Some(asset) = assets.get(handle) { + extracted_assets.push((handle.clone_weak(), asset.extract_asset())); + } + } + + commands.insert_resource(ExtractedAssets { + extracted: extracted_assets, + removed, + }) +} + +fn prepare_render_asset( + mut extracted_assets: ResMut>, + mut render_assets: ResMut>, + render_device: Res, + render_queue: Res, +) { + for removed in extracted_assets.removed.iter() { + render_assets.remove(removed); + } + + for (handle, extracted_asset) in extracted_assets.extracted.drain(..) { + let prepared_asset = R::prepare_asset(extracted_asset, &render_device, &render_queue); + render_assets.insert(handle, prepared_asset); + } +} diff --git a/pipelined/bevy_render2/src/texture/texture.rs b/pipelined/bevy_render2/src/texture/image.rs similarity index 87% rename from pipelined/bevy_render2/src/texture/texture.rs rename to pipelined/bevy_render2/src/texture/image.rs index d70743c2c6..1c724d3b26 100644 --- a/pipelined/bevy_render2/src/texture/texture.rs +++ b/pipelined/bevy_render2/src/texture/image.rs @@ -1,25 +1,23 @@ use super::image_texture_conversion::image_to_texture; -use crate::render_resource::{Sampler, Texture, TextureView}; +use crate::{ + render_asset::RenderAsset, + render_resource::{Sampler, Texture, TextureView}, + renderer::{RenderDevice, RenderQueue}, +}; use bevy_reflect::TypeUuid; use thiserror::Error; -use wgpu::{Extent3d, TextureDimension, TextureFormat}; +use wgpu::{ + Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, TextureDimension, TextureFormat, + TextureViewDescriptor, +}; pub const TEXTURE_ASSET_INDEX: u64 = 0; pub const SAMPLER_ASSET_INDEX: u64 = 1; -// TODO: this shouldn't live in the Texture type -#[derive(Debug, Clone)] -pub struct ImageGpuData { - pub texture: Texture, - pub texture_view: TextureView, - pub sampler: Sampler, -} - #[derive(Debug, Clone, TypeUuid)] #[uuid = "6ea26da6-6cf8-4ea2-9986-1d7bf6c17d6f"] pub struct Image { pub data: Vec, - pub gpu_data: Option, // TODO: this nesting makes accessing Image metadata verbose. Either flatten out descriptor or add accessors pub texture_descriptor: wgpu::TextureDescriptor<'static>, pub sampler_descriptor: wgpu::SamplerDescriptor<'static>, @@ -29,7 +27,6 @@ impl Default for Image { fn default() -> Self { Image { data: Default::default(), - gpu_data: None, texture_descriptor: wgpu::TextureDescriptor { size: wgpu::Extent3d { width: 1, @@ -336,3 +333,57 @@ impl TextureFormatPixelInfo for TextureFormat { } } } + +#[derive(Debug, Clone)] +pub struct GpuImage { + pub texture: Texture, + pub texture_view: TextureView, + pub sampler: Sampler, +} + +impl RenderAsset for Image { + type ExtractedAsset = Image; + type PreparedAsset = GpuImage; + + fn extract_asset(&self) -> Self::ExtractedAsset { + self.clone() + } + + fn prepare_asset( + image: Self::ExtractedAsset, + render_device: &RenderDevice, + render_queue: &RenderQueue, + ) -> Self::PreparedAsset { + let texture = render_device.create_texture(&image.texture_descriptor); + let sampler = render_device.create_sampler(&image.sampler_descriptor); + + let width = image.texture_descriptor.size.width as usize; + let format_size = image.texture_descriptor.format.pixel_size(); + render_queue.write_texture( + ImageCopyTexture { + texture: &texture, + mip_level: 0, + origin: Origin3d::ZERO, + }, + &image.data, + ImageDataLayout { + offset: 0, + bytes_per_row: Some( + std::num::NonZeroU32::new( + image.texture_descriptor.size.width * format_size as u32, + ) + .unwrap(), + ), + rows_per_image: None, + }, + image.texture_descriptor.size, + ); + + let texture_view = texture.create_view(&TextureViewDescriptor::default()); + GpuImage { + texture, + texture_view, + sampler, + } + } +} diff --git a/pipelined/bevy_render2/src/texture/mod.rs b/pipelined/bevy_render2/src/texture/mod.rs index 5168d7a708..7ea379a88e 100644 --- a/pipelined/bevy_render2/src/texture/mod.rs +++ b/pipelined/bevy_render2/src/texture/mod.rs @@ -1,27 +1,25 @@ #[cfg(feature = "hdr")] mod hdr_texture_loader; -mod image_texture_loader; #[allow(clippy::module_inception)] -mod texture; +mod image; +mod image_texture_loader; mod texture_cache; pub(crate) mod image_texture_conversion; #[cfg(feature = "hdr")] pub use hdr_texture_loader::*; +pub use self::image::*; pub use image_texture_loader::*; -pub use texture::*; pub use texture_cache::*; use crate::{ - renderer::{RenderDevice, RenderQueue}, + render_asset::RenderAssetPlugin, RenderStage, }; -use bevy_app::{App, CoreStage, Plugin}; -use bevy_asset::{AddAsset, AssetEvent, Assets}; +use bevy_app::{App, Plugin}; +use bevy_asset::{AddAsset}; use bevy_ecs::prelude::*; -use bevy_utils::HashSet; -use wgpu::{ImageCopyTexture, ImageDataLayout, Origin3d, TextureViewDescriptor}; // TODO: replace Texture names with Image names? pub struct ImagePlugin; @@ -33,7 +31,7 @@ impl Plugin for ImagePlugin { app.init_asset_loader::(); } - app.add_system_to_stage(CoreStage::PostUpdate, image_resource_system.system()) + app.add_plugin(RenderAssetPlugin::::default()) .add_asset::(); let render_app = app.sub_app_mut(0); @@ -43,93 +41,6 @@ impl Plugin for ImagePlugin { } } -pub fn image_resource_system( - render_device: Res, - render_queue: Res, - mut images: ResMut>, - mut image_events: EventReader>, -) { - let mut changed_images = HashSet::default(); - for event in image_events.iter() { - match event { - AssetEvent::Created { handle } => { - changed_images.insert(handle); - } - AssetEvent::Modified { handle } => { - changed_images.insert(handle); - // TODO: uncomment this to support mutated textures - // remove_current_texture_resources(render_resource_context, handle, &mut textures); - } - AssetEvent::Removed { handle } => { - // if texture was modified and removed in the same update, ignore the - // modification events are ordered so future modification - // events are ok - changed_images.remove(handle); - } - } - } - - for image_handle in changed_images.iter() { - if let Some(image) = images.get_mut(*image_handle) { - // TODO: this avoids creating new textures each frame because storing gpu data in the texture flags it as - // modified. this prevents hot reloading and therefore can't be used in an actual impl. - if image.gpu_data.is_some() { - continue; - } - - let texture = render_device.create_texture(&image.texture_descriptor); - let sampler = render_device.create_sampler(&image.sampler_descriptor); - - let width = image.texture_descriptor.size.width as usize; - let format_size = image.texture_descriptor.format.pixel_size(); - // let mut aligned_data = vec![ - // 0; - // format_size - // * aligned_width - // * image.texture_descriptor.size.height as usize - // * image.texture_descriptor.size.depth_or_array_layers - // as usize - // ]; - // image - // .data - // .chunks_exact(format_size * width) - // .enumerate() - // .for_each(|(index, row)| { - // let offset = index * aligned_width * format_size; - // aligned_data[offset..(offset + width * format_size)].copy_from_slice(row); - // }); - - // TODO: this might require different alignment. docs seem to say that we don't need it though - render_queue.write_texture( - ImageCopyTexture { - texture: &texture, - mip_level: 0, - origin: Origin3d::ZERO, - }, - &image.data, - ImageDataLayout { - offset: 0, - bytes_per_row: Some( - std::num::NonZeroU32::new( - image.texture_descriptor.size.width * format_size as u32, - ) - .unwrap(), - ), - rows_per_image: None, - }, - image.texture_descriptor.size, - ); - - let texture_view = texture.create_view(&TextureViewDescriptor::default()); - image.gpu_data = Some(ImageGpuData { - texture, - texture_view, - sampler, - }); - } - } -} - pub trait BevyDefault { fn bevy_default() -> Self; } diff --git a/pipelined/bevy_sprite2/src/render/mod.rs b/pipelined/bevy_sprite2/src/render/mod.rs index 812048005b..ffd4c05309 100644 --- a/pipelined/bevy_sprite2/src/render/mod.rs +++ b/pipelined/bevy_sprite2/src/render/mod.rs @@ -5,13 +5,14 @@ use bevy_math::{Mat4, Vec2, Vec3, Vec4Swizzles}; use bevy_render2::{ core_pipeline::Transparent2dPhase, mesh::{shape::Quad, Indices, Mesh, VertexAttributeValues}, + render_asset::RenderAssets, render_graph::{Node, NodeRunError, RenderGraphContext}, render_phase::{Draw, DrawFunctions, Drawable, RenderPhase, TrackedRenderPass}, render_resource::*, renderer::{RenderContext, RenderDevice}, shader::Shader, texture::{BevyDefault, Image}, - view::{ViewMeta, ViewUniformOffset, ViewUniform}, + view::{ViewMeta, ViewUniform, ViewUniformOffset}, }; use bevy_transform::components::GlobalTransform; use bevy_utils::HashMap; @@ -48,23 +49,20 @@ impl FromWorld for SpriteShaders { source: ShaderSource::SpirV(Cow::Borrowed(&fragment_spirv)), }); - let view_layout = - render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStage::VERTEX | ShaderStage::FRAGMENT, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: true, - // TODO: verify this is correct - min_binding_size: BufferSize::new( - std::mem::size_of::() as u64 - ), - }, - count: None, - }], - label: None, - }); + let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStage::VERTEX | ShaderStage::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: true, + // TODO: verify this is correct + min_binding_size: BufferSize::new(std::mem::size_of::() as u64), + }, + count: None, + }], + label: None, + }); let material_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ @@ -164,9 +162,7 @@ impl FromWorld for SpriteShaders { struct ExtractedSprite { transform: Mat4, size: Vec2, - // TODO: use asset handle here instead of owned renderer handles (lots of arc cloning) - texture_view: TextureView, - sampler: Sampler, + handle: Handle, } pub struct ExtractedSprites { @@ -175,21 +171,20 @@ pub struct ExtractedSprites { pub fn extract_sprites( mut commands: Commands, - textures: Res>, + images: Res>, query: Query<(&Sprite, &GlobalTransform, &Handle)>, ) { let mut extracted_sprites = Vec::new(); for (sprite, transform, handle) in query.iter() { - if let Some(texture) = textures.get(handle) { - if let Some(gpu_data) = &texture.gpu_data { - extracted_sprites.push(ExtractedSprite { - transform: transform.compute_matrix(), - size: sprite.size, - texture_view: gpu_data.texture_view.clone(), - sampler: gpu_data.sampler.clone(), - }) - } + if !images.contains(handle) { + continue; } + + extracted_sprites.push(ExtractedSprite { + transform: transform.compute_matrix(), + size: sprite.size, + handle: handle.clone_weak(), + }) } commands.insert_resource(ExtractedSprites { @@ -211,7 +206,7 @@ pub struct SpriteMeta { view_bind_group: Option, // TODO: these should be garbage collected if unused across X frames texture_bind_groups: Vec, - texture_bind_group_indices: HashMap, + texture_bind_group_indices: HashMap, usize>, } impl Default for SpriteMeta { @@ -309,9 +304,10 @@ pub fn queue_sprites( view_meta: Res, sprite_shaders: Res, extracted_sprites: Res, + gpu_images: Res>, mut views: Query<&mut RenderPhase>, ) { - // TODO: define this without needing to check every frame + // TODO: define this without needing to check every frame sprite_meta.view_bind_group.get_or_insert_with(|| { render_device.create_bind_group(&BindGroupDescriptor { entries: &[BindGroupEntry { @@ -323,27 +319,25 @@ pub fn queue_sprites( }) }); let sprite_meta = &mut *sprite_meta; + let draw_sprite_function = draw_functions.read().get_id::().unwrap(); for mut transparent_phase in views.iter_mut() { - // TODO: free old bind groups? clear_unused_bind_groups() currently does this for us? Moving to RAII would also do this for us? - let draw_sprite_function = draw_functions.read().get_id::().unwrap(); - let texture_bind_groups = &mut sprite_meta.texture_bind_groups; - // let material_layout = ; for (i, sprite) in extracted_sprites.sprites.iter().enumerate() { let bind_group_index = *sprite_meta .texture_bind_group_indices - .entry(sprite.texture_view.id()) + .entry(sprite.handle.clone_weak()) .or_insert_with(|| { + let gpu_image = gpu_images.get(&sprite.handle).unwrap(); let index = texture_bind_groups.len(); let bind_group = render_device.create_bind_group(&BindGroupDescriptor { entries: &[ BindGroupEntry { binding: 0, - resource: BindingResource::TextureView(&sprite.texture_view), + resource: BindingResource::TextureView(&gpu_image.texture_view), }, BindGroupEntry { binding: 1, - resource: BindingResource::Sampler(&sprite.sampler), + resource: BindingResource::Sampler(&gpu_image.sampler), }, ], label: None, @@ -410,10 +404,9 @@ impl Draw for DrawSprite { ) { const INDICES: usize = 6; let (sprite_shaders, sprite_meta, views) = self.params.get(world); - let (sprite_shaders, sprite_meta, views) = - (sprite_shaders.into_inner(), sprite_meta.into_inner(), views); let view_uniform = views.get(view).unwrap(); - pass.set_render_pipeline(&sprite_shaders.pipeline); + let sprite_meta = sprite_meta.into_inner(); + pass.set_render_pipeline(&sprite_shaders.into_inner().pipeline); pass.set_vertex_buffer(0, sprite_meta.vertices.buffer().unwrap().slice(..)); pass.set_index_buffer( sprite_meta.indices.buffer().unwrap().slice(..),