From bf7f222318f47e86d7d7b8b14becdb5ad53ef125 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 15 May 2020 19:30:02 -0700 Subject: [PATCH] Support async texture loading --- .vscode/launch.json | 1 + README.md | 2 +- crates/bevy_app/src/app_builder.rs | 1 + crates/bevy_app/src/stage.rs | 5 +- crates/bevy_render/Cargo.toml | 3 +- .../assigned_meshes_draw_target.rs | 5 +- crates/bevy_render/src/lib.rs | 6 + .../src/render_graph/nodes/uniform_node.rs | 179 ++++++++++-------- .../entities_waiting_for_assets.rs | 25 +++ crates/bevy_render/src/render_resource/mod.rs | 2 + crates/bevy_render/src/texture/mod.rs | 2 + .../src/texture/png_texture_loader.rs | 26 +++ crates/bevy_ui/src/sprite.rs | 9 +- .../renderer/wgpu_render_resource_context.rs | 1 - examples/2d/sprite.rs | 6 +- examples/3d/texture.rs | 7 +- src/prelude.rs | 2 +- 17 files changed, 179 insertions(+), 103 deletions(-) create mode 100644 crates/bevy_render/src/render_resource/entities_waiting_for_assets.rs create mode 100644 crates/bevy_render/src/texture/png_texture_loader.rs diff --git a/.vscode/launch.json b/.vscode/launch.json index a33c779fda..5def1a4c10 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -993,6 +993,7 @@ "type": "lldb", "request": "launch", "name": "Debug example 'sprite'", + "env": { "CARGO_MANIFEST_DIR": "${workspaceFolder}" }, "cargo": { "args": [ "build", diff --git a/README.md b/README.md index 1169fca1a0..f9ca3fd819 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Bevy is still in the _very_ early stages of development. APIs can and will chang ## Design Goals -* Provide a first class user-experience for both 2D and 3D games. +* Provide a first class developer experience for both 2D and 3D games. * Easy for newbies to pick up, but infinitely flexible for power users. * Fast iterative compile times. Ideally less than 1 second for small to medium sized projects. * Data-first game development using ECS (Entity Component System) diff --git a/crates/bevy_app/src/app_builder.rs b/crates/bevy_app/src/app_builder.rs index f4c2940a6a..b87e34b79e 100644 --- a/crates/bevy_app/src/app_builder.rs +++ b/crates/bevy_app/src/app_builder.rs @@ -174,6 +174,7 @@ impl AppBuilder { self.add_startup_stage(stage::STARTUP) .add_stage(stage::FIRST) .add_stage(stage::EVENT_UPDATE) + .add_stage(stage::PRE_UPDATE) .add_stage(stage::UPDATE) .add_stage(stage::POST_UPDATE) .add_stage(stage::LAST) diff --git a/crates/bevy_app/src/stage.rs b/crates/bevy_app/src/stage.rs index 8634acb746..8c1d48dabb 100644 --- a/crates/bevy_app/src/stage.rs +++ b/crates/bevy_app/src/stage.rs @@ -7,10 +7,13 @@ pub const FIRST: &str = "first"; /// Name of app stage that updates events. Generally this should run before UPDATE pub const EVENT_UPDATE: &str = "event_update"; +/// Name of app stage responsible for performing setup before an update. Runs before UPDATE. +pub const PRE_UPDATE: &str = "pre_update"; + /// Name of app stage responsible for doing most app logic. Systems should be registered here by default. pub const UPDATE: &str = "update"; -/// Name of app stage responsible for processing the results of UPDATE. Runs immediately after UPDATE. +/// Name of app stage responsible for processing the results of UPDATE. Runs after UPDATE. pub const POST_UPDATE: &str = "post_update"; /// Name of app stage that runs after all other app stages diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 0eb914c0bc..e7050827c4 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -31,4 +31,5 @@ smallvec = "1.4.0" # TODO: replace once_cell with std equivalent if/when this lands: https://github.com/rust-lang/rfcs/pull/2788 once_cell = "1.3.1" downcast-rs = "1.1.1" -thiserror = "1.0" \ No newline at end of file +thiserror = "1.0" +anyhow = "1.0" \ No newline at end of file diff --git a/crates/bevy_render/src/draw_target/draw_targets/assigned_meshes_draw_target.rs b/crates/bevy_render/src/draw_target/draw_targets/assigned_meshes_draw_target.rs index b2893ed340..f257c80086 100644 --- a/crates/bevy_render/src/draw_target/draw_targets/assigned_meshes_draw_target.rs +++ b/crates/bevy_render/src/draw_target/draw_targets/assigned_meshes_draw_target.rs @@ -7,7 +7,7 @@ use crate::{ pass::RenderPass, pipeline::{PipelineAssignments, PipelineDescriptor}, render_resource::{ - resource_name, EntityRenderResourceAssignments, RenderResourceAssignments, ResourceInfo, + resource_name, EntityRenderResourceAssignments, RenderResourceAssignments, ResourceInfo, EntitiesWaitingForAssets, }, renderer::RenderContext, Renderable, @@ -28,6 +28,7 @@ impl DrawTarget for AssignedMeshesDrawTarget { let shader_pipeline_assignments = resources.get::().unwrap(); let entity_render_resource_assignments = resources.get::().unwrap(); + let entities_waiting_for_assets = resources.get::().unwrap(); let mut current_mesh_handle = None; let mut current_mesh_index_len = 0; let global_render_resource_assignments = @@ -45,7 +46,7 @@ impl DrawTarget for AssignedMeshesDrawTarget { .get(*assignment_id) .unwrap(); let renderable = world.get_component::(*entity).unwrap(); - if !renderable.is_visible || renderable.is_instanced { + if !renderable.is_visible || renderable.is_instanced || entities_waiting_for_assets.contains(entity) { continue; } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 6a1a332149..507bd1a1e2 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -44,6 +44,9 @@ use bevy_app::{stage, AppBuilder, AppPlugin}; use bevy_asset::AddAsset; use mesh::mesh_resource_provider_system; use render_graph::RenderGraph; +use texture::PngTextureLoader; +use render_resource::EntitiesWaitingForAssets; +use legion::prelude::IntoSystem; pub static RENDER_RESOURCE_STAGE: &str = "render_resource"; pub static RENDER_STAGE: &str = "render"; @@ -75,16 +78,19 @@ impl AppPlugin for RenderPlugin { .add_asset::() .add_asset::() .add_asset::() + .add_asset_loader(PngTextureLoader::default()) .add_resource(render_graph) .init_resource::() .init_resource::() .init_resource::() .init_resource::() .init_resource::() + .init_resource::() // core systems .add_system(entity_render_resource_assignments_system()) .init_system_to_stage(stage::POST_UPDATE, camera::camera_update_system) .add_system_to_stage(stage::POST_UPDATE, mesh::mesh_specializer_system()) + .add_system_to_stage(stage::PRE_UPDATE, EntitiesWaitingForAssets::clear_system.system()) // render resource provider systems .init_system_to_stage(RENDER_RESOURCE_STAGE, mesh_resource_provider_system); } diff --git a/crates/bevy_render/src/render_graph/nodes/uniform_node.rs b/crates/bevy_render/src/render_graph/nodes/uniform_node.rs index dc8d42d6d7..97c48e5d20 100644 --- a/crates/bevy_render/src/render_graph/nodes/uniform_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/uniform_node.rs @@ -2,8 +2,8 @@ use crate::{ pipeline::VertexBufferDescriptors, render_graph::{CommandQueue, Node, ResourceSlots, SystemNode}, render_resource::{ - BufferInfo, BufferUsage, RenderResource, RenderResourceAssignment, - RenderResourceAssignments, RenderResourceAssignmentsId, + BufferInfo, BufferUsage, EntitiesWaitingForAssets, RenderResource, + RenderResourceAssignment, RenderResourceAssignments, RenderResourceAssignmentsId, }, renderer::{RenderContext, RenderResourceContext, RenderResources}, shader::{AsUniforms, FieldBindType}, @@ -400,13 +400,14 @@ where )) .read_resource::>() .read_resource::() + .read_resource::() // TODO: this write on RenderResourceAssignments will prevent this system from running in parallel with other systems that do the same .with_query(<(Read, Read)>::query()) .with_query(<(Read, Write)>::query()) .build( move |_, world, - (textures, render_resources), + (textures, render_resources, entities_waiting_for_assets), (read_uniform_query, write_uniform_query)| { let render_resource_context = &*render_resources.context; @@ -428,7 +429,9 @@ where .setup_buffer_arrays(render_resource_context, dynamic_uniforms); let staging_buffer_size = uniform_buffer_arrays.update_staging_buffer_offsets(); - for (uniforms, mut renderable) in write_uniform_query.iter_mut(world) { + for (entity, (uniforms, mut renderable)) in + write_uniform_query.iter_entities_mut(world) + { if !renderable.is_visible { return; } @@ -437,10 +440,12 @@ where panic!("instancing not currently supported"); } else { setup_uniform_texture_resources::( + entity, &uniforms, &mut command_queue, textures, render_resource_context, + entities_waiting_for_assets, &mut renderable.render_resource_assignments, ) } @@ -554,19 +559,20 @@ where .read_resource::>() .read_resource::>() .read_resource::() + .read_resource::() // TODO: this write on RenderResourceAssignments will prevent this system from running in parallel with other systems that do the same .with_query(<(Read>, Read)>::query()) .with_query(<(Read>, Write)>::query()) .build( move |_, world, - (assets, textures, render_resources), + (assets, textures, render_resources, entities_waiting_for_assets), (read_handle_query, write_handle_query)| { let render_resource_context = &*render_resources.context; uniform_buffer_arrays.reset_new_item_counts(); // update uniform handles info - for (handle, renderable) in read_handle_query.iter(world) { + for (entity, (handle, renderable)) in read_handle_query.iter_entities(world) { if !renderable.is_visible { return; } @@ -574,11 +580,12 @@ where if renderable.is_instanced { panic!("instancing not currently supported"); } else { - let uniforms = assets - .get(&handle) - .expect("Handle points to a non-existent resource"); - // TODO: only increment count if we haven't seen this uniform handle before - uniform_buffer_arrays.increment_uniform_counts(&uniforms); + if let Some(uniforms) = assets.get(&handle) { + // TODO: only increment count if we haven't seen this uniform handle before + uniform_buffer_arrays.increment_uniform_counts(&uniforms); + } else { + entities_waiting_for_assets.add(entity) + } } } @@ -586,7 +593,9 @@ where .setup_buffer_arrays(render_resource_context, dynamic_uniforms); let staging_buffer_size = uniform_buffer_arrays.update_staging_buffer_offsets(); - for (handle, mut renderable) in write_handle_query.iter_mut(world) { + for (entity, (handle, mut renderable)) in + write_handle_query.iter_entities_mut(world) + { if !renderable.is_visible { return; } @@ -594,16 +603,17 @@ where if renderable.is_instanced { panic!("instancing not currently supported"); } else { - let uniforms = assets - .get(&handle) - .expect("Handle points to a non-existent resource"); - setup_uniform_texture_resources::( - &uniforms, - &mut command_queue, - textures, - render_resource_context, - &mut renderable.render_resource_assignments, - ) + if let Some(uniforms) = assets.get(&handle) { + setup_uniform_texture_resources::( + entity, + &uniforms, + &mut command_queue, + textures, + render_resource_context, + entities_waiting_for_assets, + &mut renderable.render_resource_assignments, + ) + } } } if staging_buffer_size == 0 { @@ -615,17 +625,16 @@ where if renderable.is_instanced { panic!("instancing not currently supported"); } else { - let uniforms = assets - .get(&handle) - .expect("Handle points to a non-existent resource"); - // TODO: only setup buffer if we haven't seen this handle before - uniform_buffer_arrays.setup_uniform_buffer_resources( - &uniforms, - dynamic_uniforms, - render_resource_context, - &mut renderable.render_resource_assignments, - &mut staging_buffer, - ); + if let Some(uniforms) = assets.get(&handle) { + // TODO: only setup buffer if we haven't seen this handle before + uniform_buffer_arrays.setup_uniform_buffer_resources( + &uniforms, + dynamic_uniforms, + render_resource_context, + &mut renderable.render_resource_assignments, + &mut staging_buffer, + ); + } } } } else { @@ -643,17 +652,16 @@ where if renderable.is_instanced { panic!("instancing not currently supported"); } else { - let uniforms = assets - .get(&handle) - .expect("Handle points to a non-existent resource"); - // TODO: only setup buffer if we haven't seen this handle before - uniform_buffer_arrays.setup_uniform_buffer_resources( - &uniforms, - dynamic_uniforms, - render_resource_context, - &mut renderable.render_resource_assignments, - &mut staging_buffer, - ); + if let Some(uniforms) = assets.get(&handle) { + // TODO: only setup buffer if we haven't seen this handle before + uniform_buffer_arrays.setup_uniform_buffer_resources( + &uniforms, + dynamic_uniforms, + render_resource_context, + &mut renderable.render_resource_assignments, + &mut staging_buffer, + ); + } } } }, @@ -684,10 +692,12 @@ where } fn setup_uniform_texture_resources( + entity: Entity, uniforms: &T, command_queue: &mut CommandQueue, textures: &Assets, render_resource_context: &dyn RenderResourceContext, + entities_waiting_for_assets: &EntitiesWaitingForAssets, render_resource_assignments: &mut RenderResourceAssignments, ) where T: AsUniforms, @@ -709,48 +719,49 @@ fn setup_uniform_texture_resources( .unwrap(), ), None => { - let texture = textures.get(&texture_handle).unwrap(); + if let Some(texture) = textures.get(&texture_handle) { + let texture_descriptor: TextureDescriptor = texture.into(); + let texture_resource = + render_resource_context.create_texture(texture_descriptor); + let texture_buffer = render_resource_context.create_buffer_with_data( + BufferInfo { + buffer_usage: BufferUsage::COPY_SRC, + ..Default::default() + }, + &texture.data, + ); + // TODO: bytes_per_row could be incorrect for some texture formats + command_queue.copy_buffer_to_texture( + texture_buffer, + 0, + (4 * texture.width) as u32, + texture_resource, + [0, 0, 0], + 0, + 0, + texture_descriptor.size.clone(), + ); + command_queue.free_buffer(texture_buffer); - let texture_descriptor: TextureDescriptor = texture.into(); - let texture_resource = - render_resource_context.create_texture(texture_descriptor); - // TODO: queue texture copy - // .create_texture_with_data(&texture_descriptor, &texture.data); - let texture_buffer = render_resource_context.create_buffer_with_data( - BufferInfo { - buffer_usage: BufferUsage::COPY_SRC, - ..Default::default() - }, - &texture.data, - ); - // TODO: bytes_per_row could be incorrect for some texture formats - command_queue.copy_buffer_to_texture( - texture_buffer, - 0, - (4 * texture.width) as u32, - texture_resource, - [0, 0, 0], - 0, - 0, - texture_descriptor.size.clone(), - ); - command_queue.free_buffer(texture_buffer); + let sampler_descriptor: SamplerDescriptor = texture.into(); + let sampler_resource = + render_resource_context.create_sampler(&sampler_descriptor); - let sampler_descriptor: SamplerDescriptor = texture.into(); - let sampler_resource = - render_resource_context.create_sampler(&sampler_descriptor); - - render_resource_context.set_asset_resource( - texture_handle, - texture_resource, - 0, - ); - render_resource_context.set_asset_resource( - texture_handle, - sampler_resource, - 1, - ); - (texture_resource, sampler_resource) + render_resource_context.set_asset_resource( + texture_handle, + texture_resource, + 0, + ); + render_resource_context.set_asset_resource( + texture_handle, + sampler_resource, + 1, + ); + (texture_resource, sampler_resource) + } else { + entities_waiting_for_assets.add(entity); + continue; + } } }; diff --git a/crates/bevy_render/src/render_resource/entities_waiting_for_assets.rs b/crates/bevy_render/src/render_resource/entities_waiting_for_assets.rs new file mode 100644 index 0000000000..fe5b1a0c59 --- /dev/null +++ b/crates/bevy_render/src/render_resource/entities_waiting_for_assets.rs @@ -0,0 +1,25 @@ +use legion::prelude::{Entity, Res}; +use std::{sync::RwLock, collections::HashSet}; + +#[derive(Default)] +pub struct EntitiesWaitingForAssets { + pub entities: RwLock>, +} + +impl EntitiesWaitingForAssets { + pub fn add(&self, entity: Entity) { + self.entities.write().expect("RwLock poisoned").insert(entity); + } + + pub fn contains(&self, entity: &Entity) -> bool { + self.entities.read().expect("RwLock poisoned").contains(entity) + } + + pub fn clear(&self) { + self.entities.write().expect("RwLock poisoned").clear(); + } + + pub fn clear_system(entities_waiting_for_assets: Res) { + entities_waiting_for_assets.clear(); + } +} diff --git a/crates/bevy_render/src/render_resource/mod.rs b/crates/bevy_render/src/render_resource/mod.rs index 7046750c31..1560fe2017 100644 --- a/crates/bevy_render/src/render_resource/mod.rs +++ b/crates/bevy_render/src/render_resource/mod.rs @@ -1,5 +1,6 @@ mod buffer; mod entity_render_resource_assignments; +mod entities_waiting_for_assets; mod render_resource; mod render_resource_assignments; mod resource_info; @@ -7,6 +8,7 @@ pub mod resource_name; pub use buffer::*; pub use entity_render_resource_assignments::*; +pub use entities_waiting_for_assets::*; pub use render_resource::*; pub use render_resource_assignments::*; pub use resource_info::*; diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 8afd94b07b..64218cf09b 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -2,8 +2,10 @@ mod sampler_descriptor; mod texture; mod texture_descriptor; mod texture_dimension; +mod png_texture_loader; pub use sampler_descriptor::*; pub use texture::*; pub use texture_descriptor::*; pub use texture_dimension::*; +pub use png_texture_loader::*; \ No newline at end of file diff --git a/crates/bevy_render/src/texture/png_texture_loader.rs b/crates/bevy_render/src/texture/png_texture_loader.rs new file mode 100644 index 0000000000..eeb3bd76f9 --- /dev/null +++ b/crates/bevy_render/src/texture/png_texture_loader.rs @@ -0,0 +1,26 @@ +use bevy_asset::{AssetPath, AssetLoader}; +use super::Texture; +use anyhow::Result; + +#[derive(Clone, Default)] +pub struct PngTextureLoader; + +impl AssetLoader for PngTextureLoader { + fn from_bytes(&self, _asset_path: &AssetPath, bytes: Vec) -> Result { + let decoder = png::Decoder::new(bytes.as_slice()); + let (info, mut reader) = decoder.read_info()?; + let mut data = vec![0; info.buffer_size()]; + reader.next_frame(&mut data)?; + Ok(Texture { + data, + width: info.width as usize, + height: info.height as usize, + }) + } + fn extensions(&self) -> &[&str] { + static EXTENSIONS: &[&str] = &[ + "png" + ]; + EXTENSIONS + } +} \ No newline at end of file diff --git a/crates/bevy_ui/src/sprite.rs b/crates/bevy_ui/src/sprite.rs index 2cf56bf547..9a98094e30 100644 --- a/crates/bevy_ui/src/sprite.rs +++ b/crates/bevy_ui/src/sprite.rs @@ -25,10 +25,11 @@ pub fn sprite_system() -> Box { for (sprite, handle, mut rect) in query.iter_mut(world) { let material = materials.get(&handle).unwrap(); if let Some(texture_handle) = material.texture { - let texture = textures.get(&texture_handle).unwrap(); - let aspect = texture.aspect(); - *rect.size.x_mut() = texture.width as f32 * sprite.scale; - *rect.size.y_mut() = rect.size.x() * aspect; + if let Some(texture) = textures.get(&texture_handle) { + let aspect = texture.aspect(); + *rect.size.x_mut() = texture.width as f32 * sprite.scale; + *rect.size.y_mut() = rect.size.x() * aspect; + } } } }) diff --git a/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs b/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs index d0473ffe38..66928262d0 100644 --- a/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs +++ b/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs @@ -557,7 +557,6 @@ impl RenderResourceContext for WgpuRenderResourceContext { return Some(render_resource_set.id); } } - None } } diff --git a/examples/2d/sprite.rs b/examples/2d/sprite.rs index 62cef7e404..5241ca3b85 100644 --- a/examples/2d/sprite.rs +++ b/examples/2d/sprite.rs @@ -9,12 +9,10 @@ fn main() { fn setup( command_buffer: &mut CommandBuffer, - mut textures: ResMut>, + asset_server: Res, mut materials: ResMut>, ) { - let texture = Texture::load(TextureType::Png("assets/branding/icon.png".to_string())); - let texture_handle = textures.add(texture); - + let texture_handle = asset_server.load("assets/branding/icon.png").unwrap(); command_buffer .build() .add_entity(Camera2dEntity::default()) diff --git a/examples/3d/texture.rs b/examples/3d/texture.rs index 7961deeaf6..592917c076 100644 --- a/examples/3d/texture.rs +++ b/examples/3d/texture.rs @@ -10,16 +10,15 @@ fn main() { /// sets up a scene with textured entities fn setup( command_buffer: &mut CommandBuffer, + asset_server: Res, mut meshes: ResMut>, mut textures: ResMut>, mut materials: ResMut>, ) { // load a texture - let texture = Texture::load(TextureType::Png( - "assets/branding/bevy_logo_dark_big.pn".to_string(), - )); + let texture_handle = asset_server.load_sync(&mut textures, "assets/branding/bevy_logo_dark_big.png").unwrap(); + let texture = textures.get(&texture_handle).unwrap(); let aspect = texture.aspect(); - let texture_handle = textures.add(texture); // create a new quad mesh. this is what we will apply the texture to let quad_width = 8.0; diff --git a/src/prelude.rs b/src/prelude.rs index 5078a96f37..80f617ba8d 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,5 +1,5 @@ #[cfg(feature = "asset")] -pub use crate::asset::{AddAsset, AssetEvent, Assets, Handle}; +pub use crate::asset::{AddAsset, AssetEvent, Assets, Handle, AssetServer}; #[cfg(feature = "core")] pub use crate::core::{ time::Time,