RenderAssetPlugin

This commit is contained in:
Carter Anderson 2021-06-24 20:04:28 -07:00
parent 09043b66ce
commit 3ef951dcbc
5 changed files with 212 additions and 152 deletions

View file

@ -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)]

View file

@ -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<A: RenderAsset>(PhantomData<fn() -> A>);
impl<A: RenderAsset> Plugin for RenderAssetPlugin<A> {
fn build(&self, app: &mut App) {
let render_app = app.sub_app_mut(0);
render_app
.init_resource::<ExtractedAssets<A>>()
.init_resource::<RenderAssets<A>>()
.add_system_to_stage(RenderStage::Extract, extract_render_asset::<A>.system())
.add_system_to_stage(RenderStage::Prepare, prepare_render_asset::<A>.system());
}
}
struct ExtractedAssets<A: RenderAsset> {
extracted: Vec<(Handle<A>, A::ExtractedAsset)>,
removed: Vec<Handle<A>>,
}
impl<A: RenderAsset> Default for ExtractedAssets<A> {
fn default() -> Self {
Self {
extracted: Default::default(),
removed: Default::default(),
}
}
}
pub type RenderAssets<A: RenderAsset> = HashMap<Handle<A>, A::PreparedAsset>;
fn extract_render_asset<A: RenderAsset>(
mut commands: Commands,
mut events: EventReader<AssetEvent<A>>,
assets: Res<Assets<A>>,
) {
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<R: RenderAsset>(
mut extracted_assets: ResMut<ExtractedAssets<R>>,
mut render_assets: ResMut<RenderAssets<R>>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
) {
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);
}
}

View file

@ -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<u8>,
pub gpu_data: Option<ImageGpuData>,
// 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,
}
}
}

View file

@ -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::<ImageTextureLoader>();
}
app.add_system_to_stage(CoreStage::PostUpdate, image_resource_system.system())
app.add_plugin(RenderAssetPlugin::<Image>::default())
.add_asset::<Image>();
let render_app = app.sub_app_mut(0);
@ -43,93 +41,6 @@ impl Plugin for ImagePlugin {
}
}
pub fn image_resource_system(
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut images: ResMut<Assets<Image>>,
mut image_events: EventReader<AssetEvent<Image>>,
) {
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;
}

View file

@ -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::<ViewUniform>() 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::<ViewUniform>() 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<Image>,
}
pub struct ExtractedSprites {
@ -175,21 +171,20 @@ pub struct ExtractedSprites {
pub fn extract_sprites(
mut commands: Commands,
textures: Res<Assets<Image>>,
images: Res<Assets<Image>>,
query: Query<(&Sprite, &GlobalTransform, &Handle<Image>)>,
) {
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<BindGroup>,
// TODO: these should be garbage collected if unused across X frames
texture_bind_groups: Vec<BindGroup>,
texture_bind_group_indices: HashMap<TextureViewId, usize>,
texture_bind_group_indices: HashMap<Handle<Image>, usize>,
}
impl Default for SpriteMeta {
@ -309,9 +304,10 @@ pub fn queue_sprites(
view_meta: Res<ViewMeta>,
sprite_shaders: Res<SpriteShaders>,
extracted_sprites: Res<ExtractedSprites>,
gpu_images: Res<RenderAssets<Image>>,
mut views: Query<&mut RenderPhase<Transparent2dPhase>>,
) {
// 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::<DrawSprite>().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::<DrawSprite>().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(..),