mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
RenderAssetPlugin
This commit is contained in:
parent
09043b66ce
commit
3ef951dcbc
5 changed files with 212 additions and 152 deletions
|
@ -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)]
|
||||
|
|
104
pipelined/bevy_render2/src/render_asset.rs
Normal file
104
pipelined/bevy_render2/src/render_asset.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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(..),
|
||||
|
|
Loading…
Reference in a new issue