Support async texture loading

This commit is contained in:
Carter Anderson 2020-05-15 19:30:02 -07:00
parent 35adad6556
commit bf7f222318
17 changed files with 179 additions and 103 deletions

1
.vscode/launch.json vendored
View file

@ -993,6 +993,7 @@
"type": "lldb",
"request": "launch",
"name": "Debug example 'sprite'",
"env": { "CARGO_MANIFEST_DIR": "${workspaceFolder}" },
"cargo": {
"args": [
"build",

View file

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

View file

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

View file

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

View file

@ -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"
thiserror = "1.0"
anyhow = "1.0"

View file

@ -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::<PipelineAssignments>().unwrap();
let entity_render_resource_assignments =
resources.get::<EntityRenderResourceAssignments>().unwrap();
let entities_waiting_for_assets = resources.get::<EntitiesWaitingForAssets>().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::<Renderable>(*entity).unwrap();
if !renderable.is_visible || renderable.is_instanced {
if !renderable.is_visible || renderable.is_instanced || entities_waiting_for_assets.contains(entity) {
continue;
}

View file

@ -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::<Texture>()
.add_asset::<Shader>()
.add_asset::<PipelineDescriptor>()
.add_asset_loader(PngTextureLoader::default())
.add_resource(render_graph)
.init_resource::<PipelineAssignments>()
.init_resource::<PipelineCompiler>()
.init_resource::<RenderResourceAssignments>()
.init_resource::<VertexBufferDescriptors>()
.init_resource::<EntityRenderResourceAssignments>()
.init_resource::<EntitiesWaitingForAssets>()
// 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);
}

View file

@ -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::<Assets<Texture>>()
.read_resource::<RenderResources>()
.read_resource::<EntitiesWaitingForAssets>()
// TODO: this write on RenderResourceAssignments will prevent this system from running in parallel with other systems that do the same
.with_query(<(Read<T>, Read<Renderable>)>::query())
.with_query(<(Read<T>, Write<Renderable>)>::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::<T>(
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::<Assets<T>>()
.read_resource::<Assets<Texture>>()
.read_resource::<RenderResources>()
.read_resource::<EntitiesWaitingForAssets>()
// TODO: this write on RenderResourceAssignments will prevent this system from running in parallel with other systems that do the same
.with_query(<(Read<Handle<T>>, Read<Renderable>)>::query())
.with_query(<(Read<Handle<T>>, Write<Renderable>)>::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::<T>(
&uniforms,
&mut command_queue,
textures,
render_resource_context,
&mut renderable.render_resource_assignments,
)
if let Some(uniforms) = assets.get(&handle) {
setup_uniform_texture_resources::<T>(
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<T>(
entity: Entity,
uniforms: &T,
command_queue: &mut CommandQueue,
textures: &Assets<Texture>,
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<T>(
.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;
}
}
};

View file

@ -0,0 +1,25 @@
use legion::prelude::{Entity, Res};
use std::{sync::RwLock, collections::HashSet};
#[derive(Default)]
pub struct EntitiesWaitingForAssets {
pub entities: RwLock<HashSet<Entity>>,
}
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<EntitiesWaitingForAssets>) {
entities_waiting_for_assets.clear();
}
}

View file

@ -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::*;

View file

@ -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::*;

View file

@ -0,0 +1,26 @@
use bevy_asset::{AssetPath, AssetLoader};
use super::Texture;
use anyhow::Result;
#[derive(Clone, Default)]
pub struct PngTextureLoader;
impl AssetLoader<Texture> for PngTextureLoader {
fn from_bytes(&self, _asset_path: &AssetPath, bytes: Vec<u8>) -> Result<Texture> {
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
}
}

View file

@ -25,10 +25,11 @@ pub fn sprite_system() -> Box<dyn Schedulable> {
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;
}
}
}
})

View file

@ -557,7 +557,6 @@ impl RenderResourceContext for WgpuRenderResourceContext {
return Some(render_resource_set.id);
}
}
None
}
}

View file

@ -9,12 +9,10 @@ fn main() {
fn setup(
command_buffer: &mut CommandBuffer,
mut textures: ResMut<Assets<Texture>>,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
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())

View file

@ -10,16 +10,15 @@ fn main() {
/// sets up a scene with textured entities
fn setup(
command_buffer: &mut CommandBuffer,
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut textures: ResMut<Assets<Texture>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// 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;

View file

@ -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,