mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 12:13:25 +00:00
Merge New Renderer
This commit is contained in:
commit
8009af3879
194 changed files with 24520 additions and 326 deletions
75
Cargo.toml
75
Cargo.toml
|
@ -13,14 +13,18 @@ repository = "https://github.com/bevyengine/bevy"
|
|||
|
||||
[workspace]
|
||||
exclude = ["benches", "crates/bevy_ecs_compile_fail_tests"]
|
||||
members = ["crates/*", "examples/ios", "tools/ci", "errors"]
|
||||
members = ["crates/*", "pipelined/*", "examples/ios", "tools/ci", "errors"]
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"bevy_audio",
|
||||
"bevy_core_pipeline",
|
||||
"bevy_gilrs",
|
||||
"bevy_gltf",
|
||||
"bevy_gltf2",
|
||||
"bevy_wgpu",
|
||||
"bevy_sprite2",
|
||||
"bevy_render2",
|
||||
"bevy_pbr2",
|
||||
"bevy_winit",
|
||||
"render",
|
||||
"png",
|
||||
|
@ -50,6 +54,12 @@ bevy_gltf = ["bevy_internal/bevy_gltf"]
|
|||
bevy_wgpu = ["bevy_internal/bevy_wgpu"]
|
||||
bevy_winit = ["bevy_internal/bevy_winit"]
|
||||
|
||||
bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline"]
|
||||
bevy_render2 = ["bevy_internal/bevy_render2"]
|
||||
bevy_sprite2 = ["bevy_internal/bevy_sprite2"]
|
||||
bevy_pbr2 = ["bevy_internal/bevy_pbr2"]
|
||||
bevy_gltf2 = ["bevy_internal/bevy_gltf2"]
|
||||
|
||||
trace_chrome = ["bevy_internal/trace_chrome"]
|
||||
trace_tracy = ["bevy_internal/trace_tracy"]
|
||||
trace = ["bevy_internal/trace"]
|
||||
|
@ -95,6 +105,7 @@ ron = "0.6.2"
|
|||
serde = { version = "1", features = ["derive"] }
|
||||
# Needed to poll Task examples
|
||||
futures-lite = "1.11.3"
|
||||
crevice = { path = "crates/crevice", version = "0.8.0", features = ["glam"] }
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
|
@ -133,23 +144,51 @@ path = "examples/2d/text2d.rs"
|
|||
name = "texture_atlas"
|
||||
path = "examples/2d/texture_atlas.rs"
|
||||
|
||||
[[example]]
|
||||
name = "pipelined_texture_atlas"
|
||||
path = "examples/2d/pipelined_texture_atlas.rs"
|
||||
|
||||
# 3D Rendering
|
||||
[[example]]
|
||||
name = "3d_scene"
|
||||
path = "examples/3d/3d_scene.rs"
|
||||
|
||||
[[example]]
|
||||
name = "3d_scene_pipelined"
|
||||
path = "examples/3d/3d_scene_pipelined.rs"
|
||||
|
||||
[[example]]
|
||||
name = "many_cubes_pipelined"
|
||||
path = "examples/3d/many_cubes_pipelined.rs"
|
||||
|
||||
[[example]]
|
||||
name = "cornell_box_pipelined"
|
||||
path = "examples/3d/cornell_box_pipelined.rs"
|
||||
|
||||
[[example]]
|
||||
name = "load_gltf"
|
||||
path = "examples/3d/load_gltf.rs"
|
||||
|
||||
[[example]]
|
||||
name = "load_gltf_pipelined"
|
||||
path = "examples/3d/load_gltf_pipelined.rs"
|
||||
|
||||
[[example]]
|
||||
name = "msaa"
|
||||
path = "examples/3d/msaa.rs"
|
||||
|
||||
[[example]]
|
||||
name = "msaa_pipelined"
|
||||
path = "examples/3d/msaa_pipelined.rs"
|
||||
|
||||
[[example]]
|
||||
name = "orthographic"
|
||||
path = "examples/3d/orthographic.rs"
|
||||
|
||||
[[example]]
|
||||
name = "orthographic_pipelined"
|
||||
path = "examples/3d/orthographic_pipelined.rs"
|
||||
|
||||
[[example]]
|
||||
name = "parenting"
|
||||
path = "examples/3d/parenting.rs"
|
||||
|
@ -158,10 +197,22 @@ path = "examples/3d/parenting.rs"
|
|||
name = "pbr"
|
||||
path = "examples/3d/pbr.rs"
|
||||
|
||||
[[example]]
|
||||
name = "pbr_pipelined"
|
||||
path = "examples/3d/pbr_pipelined.rs"
|
||||
|
||||
[[example]]
|
||||
name = "render_to_texture"
|
||||
path = "examples/3d/render_to_texture.rs"
|
||||
|
||||
[[example]]
|
||||
name = "shadow_biases_pipelined"
|
||||
path = "examples/3d/shadow_biases_pipelined.rs"
|
||||
|
||||
[[example]]
|
||||
name = "shadow_caster_receiver_pipelined"
|
||||
path = "examples/3d/shadow_caster_receiver_pipelined.rs"
|
||||
|
||||
[[example]]
|
||||
name = "spawner"
|
||||
path = "examples/3d/spawner.rs"
|
||||
|
@ -170,6 +221,10 @@ path = "examples/3d/spawner.rs"
|
|||
name = "texture"
|
||||
path = "examples/3d/texture.rs"
|
||||
|
||||
[[example]]
|
||||
name = "texture_pipelined"
|
||||
path = "examples/3d/texture_pipelined.rs"
|
||||
|
||||
[[example]]
|
||||
name = "update_gltf_scene"
|
||||
path = "examples/3d/update_gltf_scene.rs"
|
||||
|
@ -413,11 +468,23 @@ path = "examples/shader/shader_custom_material.rs"
|
|||
name = "shader_defs"
|
||||
path = "examples/shader/shader_defs.rs"
|
||||
|
||||
[[example]]
|
||||
name = "custom_shader_pipelined"
|
||||
path = "examples/shader/custom_shader_pipelined.rs"
|
||||
|
||||
[[example]]
|
||||
name = "shader_defs_pipelined"
|
||||
path = "examples/shader/shader_defs_pipelined.rs"
|
||||
|
||||
# Tools
|
||||
[[example]]
|
||||
name = "bevymark"
|
||||
path = "examples/tools/bevymark.rs"
|
||||
|
||||
[[example]]
|
||||
name = "bevymark_pipelined"
|
||||
path = "examples/tools/bevymark_pipelined.rs"
|
||||
|
||||
# UI (User Interface)
|
||||
[[example]]
|
||||
name = "button"
|
||||
|
@ -444,6 +511,10 @@ path = "examples/ui/ui.rs"
|
|||
name = "clear_color"
|
||||
path = "examples/window/clear_color.rs"
|
||||
|
||||
[[example]]
|
||||
name = "clear_color_pipelined"
|
||||
path = "examples/window/clear_color_pipelined.rs"
|
||||
|
||||
[[example]]
|
||||
name = "multiple_windows"
|
||||
path = "examples/window/multiple_windows.rs"
|
||||
|
|
11
assets/shaders/custom_material.wgsl
Normal file
11
assets/shaders/custom_material.wgsl
Normal file
|
@ -0,0 +1,11 @@
|
|||
[[block]]
|
||||
struct CustomMaterial {
|
||||
color: vec4<f32>;
|
||||
};
|
||||
[[group(1), binding(0)]]
|
||||
var<uniform> material: CustomMaterial;
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn fragment() -> [[location(0)]] vec4<f32> {
|
||||
return material.color;
|
||||
}
|
33
assets/shaders/shader_defs.wgsl
Normal file
33
assets/shaders/shader_defs.wgsl
Normal file
|
@ -0,0 +1,33 @@
|
|||
#import bevy_pbr::mesh_view_bind_group
|
||||
#import bevy_pbr::mesh_struct
|
||||
|
||||
[[group(1), binding(0)]]
|
||||
var<uniform> mesh: Mesh;
|
||||
|
||||
struct Vertex {
|
||||
[[location(0)]] position: vec3<f32>;
|
||||
[[location(1)]] normal: vec3<f32>;
|
||||
[[location(2)]] uv: vec2<f32>;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
[[builtin(position)]] clip_position: vec4<f32>;
|
||||
};
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||
let world_position = mesh.model * vec4<f32>(vertex.position, 1.0);
|
||||
|
||||
var out: VertexOutput;
|
||||
out.clip_position = view.view_proj * world_position;
|
||||
return out;
|
||||
}
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn fragment() -> [[location(0)]] vec4<f32> {
|
||||
var color = vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
||||
# ifdef IS_RED
|
||||
color = vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||
# endif
|
||||
return color;
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
use crate::{CoreStage, Events, Plugin, PluginGroup, PluginGroupBuilder, StartupStage};
|
||||
pub use bevy_derive::AppLabel;
|
||||
use bevy_ecs::{
|
||||
prelude::{FromWorld, IntoExclusiveSystem},
|
||||
schedule::{
|
||||
|
@ -8,12 +9,14 @@ use bevy_ecs::{
|
|||
system::Resource,
|
||||
world::World,
|
||||
};
|
||||
use bevy_utils::tracing::debug;
|
||||
use bevy_utils::{tracing::debug, HashMap};
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
use bevy_utils::tracing::info_span;
|
||||
|
||||
bevy_utils::define_label!(AppLabel);
|
||||
|
||||
#[allow(clippy::needless_doctest_main)]
|
||||
/// Containers of app logic and data
|
||||
///
|
||||
|
@ -42,6 +45,12 @@ pub struct App {
|
|||
pub world: World,
|
||||
pub runner: Box<dyn Fn(App)>,
|
||||
pub schedule: Schedule,
|
||||
sub_apps: HashMap<Box<dyn AppLabel>, SubApp>,
|
||||
}
|
||||
|
||||
struct SubApp {
|
||||
app: App,
|
||||
runner: Box<dyn Fn(&mut World, &mut App)>,
|
||||
}
|
||||
|
||||
impl Default for App {
|
||||
|
@ -73,6 +82,7 @@ impl App {
|
|||
world: Default::default(),
|
||||
schedule: Default::default(),
|
||||
runner: Box::new(run_once),
|
||||
sub_apps: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,6 +95,9 @@ impl App {
|
|||
#[cfg(feature = "trace")]
|
||||
let _bevy_frame_update_guard = bevy_frame_update_span.enter();
|
||||
self.schedule.run(&mut self.world);
|
||||
for sub_app in self.sub_apps.values_mut() {
|
||||
(sub_app.runner)(&mut self.world, &mut sub_app.app);
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts the application by calling the app's [runner function](Self::set_runner).
|
||||
|
@ -823,6 +836,39 @@ impl App {
|
|||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_sub_app(
|
||||
&mut self,
|
||||
label: impl AppLabel,
|
||||
app: App,
|
||||
f: impl Fn(&mut World, &mut App) + 'static,
|
||||
) -> &mut Self {
|
||||
self.sub_apps.insert(
|
||||
Box::new(label),
|
||||
SubApp {
|
||||
app,
|
||||
runner: Box::new(f),
|
||||
},
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
/// Retrieves a "sub app" stored inside this [App]. This will panic if the sub app does not exist.
|
||||
pub fn sub_app(&mut self, label: impl AppLabel) -> &mut App {
|
||||
match self.get_sub_app(label) {
|
||||
Ok(app) => app,
|
||||
Err(label) => panic!("Sub-App with label '{:?}' does not exist", label),
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves a "sub app" inside this [App] with the given label, if it exists. Otherwise returns
|
||||
/// an [Err] containing the given label.
|
||||
pub fn get_sub_app(&mut self, label: impl AppLabel) -> Result<&mut App, impl AppLabel> {
|
||||
self.sub_apps
|
||||
.get_mut((&label) as &dyn AppLabel)
|
||||
.map(|sub_app| &mut sub_app.app)
|
||||
.ok_or(label)
|
||||
}
|
||||
}
|
||||
|
||||
fn run_once(mut app: App) {
|
||||
|
|
|
@ -22,16 +22,15 @@ fn ci_testing_exit_after(
|
|||
*current_frame += 1;
|
||||
}
|
||||
|
||||
pub(crate) fn setup_app(app_builder: &mut App) -> &mut App {
|
||||
pub(crate) fn setup_app(app: &mut App) -> &mut App {
|
||||
let filename =
|
||||
std::env::var("CI_TESTING_CONFIG").unwrap_or_else(|_| "ci_testing_config.ron".to_string());
|
||||
let config: CiTestingConfig = ron::from_str(
|
||||
&std::fs::read_to_string(filename).expect("error reading CI testing configuration file"),
|
||||
)
|
||||
.expect("error deserializing CI testing configuration file");
|
||||
app_builder
|
||||
.insert_resource(config)
|
||||
app.insert_resource(config)
|
||||
.add_system(ci_testing_exit_after);
|
||||
|
||||
app_builder
|
||||
app
|
||||
}
|
||||
|
|
|
@ -96,6 +96,7 @@ impl<T: Asset> Handle<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn weak(id: HandleId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
|
@ -129,6 +130,7 @@ impl<T: Asset> Handle<T> {
|
|||
self.handle_type = HandleType::Strong(sender);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clone_weak(&self) -> Self {
|
||||
Handle::weak(self.id)
|
||||
}
|
||||
|
|
|
@ -43,13 +43,19 @@ impl<T: Asset> LoadedAsset<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn with_dependency(mut self, asset_path: AssetPath) -> Self {
|
||||
pub fn add_dependency(&mut self, asset_path: AssetPath) {
|
||||
self.dependencies.push(asset_path.to_owned());
|
||||
}
|
||||
|
||||
pub fn with_dependency(mut self, asset_path: AssetPath) -> Self {
|
||||
self.add_dependency(asset_path);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_dependencies(mut self, asset_paths: Vec<AssetPath<'static>>) -> Self {
|
||||
self.dependencies.extend(asset_paths);
|
||||
pub fn with_dependencies(mut self, mut asset_paths: Vec<AssetPath<'static>>) -> Self {
|
||||
for asset_path in asset_paths.drain(..) {
|
||||
self.add_dependency(asset_path);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,9 @@ mod render_resource;
|
|||
mod render_resources;
|
||||
mod shader_defs;
|
||||
|
||||
use bevy_macro_utils::{derive_label, BevyManifest};
|
||||
use proc_macro::TokenStream;
|
||||
use quote::format_ident;
|
||||
|
||||
/// Derives the Bytes trait. Each field must also implements Bytes or this will fail.
|
||||
#[proc_macro_derive(Bytes)]
|
||||
|
@ -52,3 +54,11 @@ pub fn bevy_main(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|||
pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream {
|
||||
enum_variant_meta::derive_enum_variant_meta(input)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(AppLabel)]
|
||||
pub fn derive_app_label(input: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(input as syn::DeriveInput);
|
||||
let mut trait_path = BevyManifest::default().get_path("bevy_app");
|
||||
trait_path.segments.push(format_ident!("AppLabel").into());
|
||||
derive_label(input, trait_path)
|
||||
}
|
||||
|
|
|
@ -2,16 +2,16 @@ extern crate proc_macro;
|
|||
|
||||
mod component;
|
||||
|
||||
use bevy_macro_utils::BevyManifest;
|
||||
use bevy_macro_utils::{derive_label, BevyManifest};
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use proc_macro2::Span;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
parse_macro_input,
|
||||
punctuated::Punctuated,
|
||||
token::Comma,
|
||||
Data, DataStruct, DeriveInput, Field, Fields, GenericParam, Ident, Index, LitInt, Path, Result,
|
||||
Data, DataStruct, DeriveInput, Field, Fields, GenericParam, Ident, Index, LitInt, Result,
|
||||
Token,
|
||||
};
|
||||
|
||||
|
@ -429,46 +429,43 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
|||
#[proc_macro_derive(SystemLabel)]
|
||||
pub fn derive_system_label(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
derive_label(input, Ident::new("SystemLabel", Span::call_site())).into()
|
||||
let mut trait_path = bevy_ecs_path();
|
||||
trait_path.segments.push(format_ident!("schedule").into());
|
||||
trait_path
|
||||
.segments
|
||||
.push(format_ident!("SystemLabel").into());
|
||||
derive_label(input, trait_path)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(StageLabel)]
|
||||
pub fn derive_stage_label(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
derive_label(input, Ident::new("StageLabel", Span::call_site())).into()
|
||||
let mut trait_path = bevy_ecs_path();
|
||||
trait_path.segments.push(format_ident!("schedule").into());
|
||||
trait_path.segments.push(format_ident!("StageLabel").into());
|
||||
derive_label(input, trait_path)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(AmbiguitySetLabel)]
|
||||
pub fn derive_ambiguity_set_label(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
derive_label(input, Ident::new("AmbiguitySetLabel", Span::call_site())).into()
|
||||
let mut trait_path = bevy_ecs_path();
|
||||
trait_path.segments.push(format_ident!("schedule").into());
|
||||
trait_path
|
||||
.segments
|
||||
.push(format_ident!("AmbiguitySetLabel").into());
|
||||
derive_label(input, trait_path)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(RunCriteriaLabel)]
|
||||
pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
derive_label(input, Ident::new("RunCriteriaLabel", Span::call_site())).into()
|
||||
}
|
||||
|
||||
fn derive_label(input: DeriveInput, label_type: Ident) -> TokenStream2 {
|
||||
let ident = input.ident;
|
||||
let ecs_path: Path = bevy_ecs_path();
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause {
|
||||
where_token: Default::default(),
|
||||
predicates: Default::default(),
|
||||
});
|
||||
where_clause.predicates.push(syn::parse2(quote! { Self: Eq + ::std::fmt::Debug + ::std::hash::Hash + Clone + Send + Sync + 'static }).unwrap());
|
||||
|
||||
quote! {
|
||||
impl #impl_generics #ecs_path::schedule::#label_type for #ident #ty_generics #where_clause {
|
||||
fn dyn_clone(&self) -> Box<dyn #ecs_path::schedule::#label_type> {
|
||||
Box::new(Clone::clone(self))
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut trait_path = bevy_ecs_path();
|
||||
trait_path.segments.push(format_ident!("schedule").into());
|
||||
trait_path
|
||||
.segments
|
||||
.push(format_ident!("RunCriteriaLabel").into());
|
||||
derive_label(input, trait_path)
|
||||
}
|
||||
|
||||
pub(crate) fn bevy_ecs_path() -> syn::Path {
|
||||
|
|
|
@ -38,6 +38,8 @@ pub mod prelude {
|
|||
};
|
||||
}
|
||||
|
||||
pub use bevy_ecs_macros::all_tuples;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate as bevy_ecs;
|
||||
|
|
|
@ -45,6 +45,8 @@ pub trait WorldQuery {
|
|||
type State: FetchState;
|
||||
}
|
||||
|
||||
pub type QueryItem<'w, 's, Q> = <<Q as WorldQuery>::Fetch as Fetch<'w, 's>>::Item;
|
||||
|
||||
pub trait Fetch<'world, 'state>: Sized {
|
||||
type Item;
|
||||
type State: FetchState;
|
||||
|
|
|
@ -66,7 +66,7 @@ where
|
|||
matched_archetypes: Default::default(),
|
||||
archetype_component_access: Default::default(),
|
||||
};
|
||||
state.validate_world_and_update_archetypes(world);
|
||||
state.update_archetypes(world);
|
||||
state
|
||||
}
|
||||
|
||||
|
@ -87,11 +87,8 @@ where
|
|||
/// # Panics
|
||||
///
|
||||
/// Panics if the `world.id()` does not equal the current [`QueryState`] internal id.
|
||||
pub fn validate_world_and_update_archetypes(&mut self, world: &World) {
|
||||
if world.id() != self.world_id {
|
||||
panic!("Attempted to use {} with a mismatched World. QueryStates can only be used with the World they were created from.",
|
||||
std::any::type_name::<Self>());
|
||||
}
|
||||
pub fn update_archetypes(&mut self, world: &World) {
|
||||
self.validate_world(world);
|
||||
let archetypes = world.archetypes();
|
||||
let new_generation = archetypes.generation();
|
||||
let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation);
|
||||
|
@ -102,6 +99,14 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn validate_world(&self, world: &World) {
|
||||
if world.id() != self.world_id {
|
||||
panic!("Attempted to use {} with a mismatched World. QueryStates can only be used with the World they were created from.",
|
||||
std::any::type_name::<Self>());
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`Archetype`].
|
||||
pub fn new_archetype(&mut self, archetype: &Archetype) {
|
||||
if self.fetch_state.matches_archetype(archetype)
|
||||
|
@ -153,6 +158,27 @@ where
|
|||
unsafe { self.get_unchecked(world, entity) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_manual<'w, 's>(
|
||||
&'s self,
|
||||
world: &'w World,
|
||||
entity: Entity,
|
||||
) -> Result<<Q::Fetch as Fetch<'w, 's>>::Item, QueryEntityError>
|
||||
where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
self.validate_world(world);
|
||||
// SAFETY: query is read only and world is validated
|
||||
unsafe {
|
||||
self.get_unchecked_manual(
|
||||
world,
|
||||
entity,
|
||||
world.last_change_tick(),
|
||||
world.read_change_tick(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the query result for the given [`World`] and [`Entity`].
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -165,7 +191,7 @@ where
|
|||
world: &'w World,
|
||||
entity: Entity,
|
||||
) -> Result<<Q::Fetch as Fetch<'w, 's>>::Item, QueryEntityError> {
|
||||
self.validate_world_and_update_archetypes(world);
|
||||
self.update_archetypes(world);
|
||||
self.get_unchecked_manual(
|
||||
world,
|
||||
entity,
|
||||
|
@ -232,6 +258,28 @@ where
|
|||
unsafe { self.iter_unchecked(world) }
|
||||
}
|
||||
|
||||
/// Returns an [`Iterator`] over all possible combinations of `K` query results without repetition.
|
||||
/// This can only be called for read-only queries.
|
||||
///
|
||||
/// For permutations of size K of query returning N results, you will get:
|
||||
/// - if K == N: one permutation of all query results
|
||||
/// - if K < N: all possible K-sized combinations of query results, without repetition
|
||||
/// - if K > N: empty set (no K-sized combinations exist)
|
||||
///
|
||||
/// This can only be called for read-only queries, see [`Self::iter_combinations_mut`] for
|
||||
/// write-queries.
|
||||
#[inline]
|
||||
pub fn iter_manual<'w, 's>(&'s self, world: &'w World) -> QueryIter<'w, 's, Q, F>
|
||||
where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
self.validate_world(world);
|
||||
// SAFETY: query is read only and world is validated
|
||||
unsafe {
|
||||
self.iter_unchecked_manual(world, world.last_change_tick(), world.read_change_tick())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an [`Iterator`] over all possible combinations of `K` query results without repetition.
|
||||
/// This can only be called for read-only queries.
|
||||
///
|
||||
|
@ -281,7 +329,7 @@ where
|
|||
&'s mut self,
|
||||
world: &'w World,
|
||||
) -> QueryIter<'w, 's, Q, F> {
|
||||
self.validate_world_and_update_archetypes(world);
|
||||
self.update_archetypes(world);
|
||||
self.iter_unchecked_manual(world, world.last_change_tick(), world.read_change_tick())
|
||||
}
|
||||
|
||||
|
@ -298,7 +346,7 @@ where
|
|||
&'s mut self,
|
||||
world: &'w World,
|
||||
) -> QueryCombinationIter<'w, 's, Q, F, K> {
|
||||
self.validate_world_and_update_archetypes(world);
|
||||
self.update_archetypes(world);
|
||||
self.iter_combinations_unchecked_manual(
|
||||
world,
|
||||
world.last_change_tick(),
|
||||
|
@ -392,7 +440,7 @@ where
|
|||
world: &'w World,
|
||||
func: impl FnMut(<Q::Fetch as Fetch<'w, 's>>::Item),
|
||||
) {
|
||||
self.validate_world_and_update_archetypes(world);
|
||||
self.update_archetypes(world);
|
||||
self.for_each_unchecked_manual(
|
||||
world,
|
||||
func,
|
||||
|
@ -452,7 +500,7 @@ where
|
|||
batch_size: usize,
|
||||
func: impl Fn(<Q::Fetch as Fetch<'w, 's>>::Item) + Send + Sync + Clone,
|
||||
) {
|
||||
self.validate_world_and_update_archetypes(world);
|
||||
self.update_archetypes(world);
|
||||
self.par_for_each_unchecked_manual(
|
||||
world,
|
||||
task_pool,
|
||||
|
|
|
@ -1,115 +1,12 @@
|
|||
pub use bevy_ecs_macros::{AmbiguitySetLabel, RunCriteriaLabel, StageLabel, SystemLabel};
|
||||
use bevy_utils::define_label;
|
||||
|
||||
use std::{
|
||||
any::Any,
|
||||
borrow::Cow,
|
||||
fmt::Debug,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
define_label!(StageLabel);
|
||||
define_label!(SystemLabel);
|
||||
define_label!(AmbiguitySetLabel);
|
||||
define_label!(RunCriteriaLabel);
|
||||
|
||||
pub trait DynEq: Any {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
fn dyn_eq(&self, other: &dyn DynEq) -> bool;
|
||||
}
|
||||
|
||||
impl<T> DynEq for T
|
||||
where
|
||||
T: Any + Eq,
|
||||
{
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn dyn_eq(&self, other: &dyn DynEq) -> bool {
|
||||
if let Some(other) = other.as_any().downcast_ref::<T>() {
|
||||
return self == other;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DynHash: DynEq {
|
||||
fn as_dyn_eq(&self) -> &dyn DynEq;
|
||||
|
||||
fn dyn_hash(&self, state: &mut dyn Hasher);
|
||||
}
|
||||
|
||||
impl<T> DynHash for T
|
||||
where
|
||||
T: DynEq + Hash,
|
||||
{
|
||||
fn as_dyn_eq(&self) -> &dyn DynEq {
|
||||
self
|
||||
}
|
||||
|
||||
fn dyn_hash(&self, mut state: &mut dyn Hasher) {
|
||||
T::hash(self, &mut state);
|
||||
self.type_id().hash(&mut state);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait StageLabel: DynHash + Debug + Send + Sync + 'static {
|
||||
#[doc(hidden)]
|
||||
fn dyn_clone(&self) -> Box<dyn StageLabel>;
|
||||
}
|
||||
pub(crate) type BoxedStageLabel = Box<dyn StageLabel>;
|
||||
|
||||
pub trait SystemLabel: DynHash + Debug + Send + Sync + 'static {
|
||||
#[doc(hidden)]
|
||||
fn dyn_clone(&self) -> Box<dyn SystemLabel>;
|
||||
}
|
||||
pub(crate) type BoxedSystemLabel = Box<dyn SystemLabel>;
|
||||
|
||||
pub trait AmbiguitySetLabel: DynHash + Debug + Send + Sync + 'static {
|
||||
#[doc(hidden)]
|
||||
fn dyn_clone(&self) -> Box<dyn AmbiguitySetLabel>;
|
||||
}
|
||||
pub(crate) type BoxedAmbiguitySetLabel = Box<dyn AmbiguitySetLabel>;
|
||||
|
||||
pub trait RunCriteriaLabel: DynHash + Debug + Send + Sync + 'static {
|
||||
#[doc(hidden)]
|
||||
fn dyn_clone(&self) -> Box<dyn RunCriteriaLabel>;
|
||||
}
|
||||
pub(crate) type BoxedRunCriteriaLabel = Box<dyn RunCriteriaLabel>;
|
||||
|
||||
macro_rules! impl_label {
|
||||
($trait_name:ident) => {
|
||||
impl PartialEq for dyn $trait_name {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.dyn_eq(other.as_dyn_eq())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for dyn $trait_name {}
|
||||
|
||||
impl Hash for dyn $trait_name {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.dyn_hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn $trait_name> {
|
||||
fn clone(&self) -> Self {
|
||||
self.dyn_clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl $trait_name for Cow<'static, str> {
|
||||
fn dyn_clone(&self) -> Box<dyn $trait_name> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl $trait_name for &'static str {
|
||||
fn dyn_clone(&self) -> Box<dyn $trait_name> {
|
||||
Box::new(<&str>::clone(self))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_label!(StageLabel);
|
||||
impl_label!(SystemLabel);
|
||||
impl_label!(AmbiguitySetLabel);
|
||||
impl_label!(RunCriteriaLabel);
|
||||
|
|
|
@ -84,6 +84,8 @@ pub struct SystemStage {
|
|||
uninitialized_parallel: Vec<usize>,
|
||||
/// Saves the value of the World change_tick during the last tick check
|
||||
last_tick_check: u32,
|
||||
/// If true, buffers will be automatically applied at the end of the stage. If false, buffers must be manually applied.
|
||||
apply_buffers: bool,
|
||||
}
|
||||
|
||||
impl SystemStage {
|
||||
|
@ -105,6 +107,7 @@ impl SystemStage {
|
|||
uninitialized_before_commands: vec![],
|
||||
uninitialized_at_end: vec![],
|
||||
last_tick_check: Default::default(),
|
||||
apply_buffers: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,6 +207,21 @@ impl SystemStage {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn apply_buffers(&mut self, world: &mut World) {
|
||||
for container in self.parallel.iter_mut() {
|
||||
let system = container.system_mut();
|
||||
#[cfg(feature = "trace")]
|
||||
let span = bevy_utils::tracing::info_span!("system_commands", name = &*system.name());
|
||||
#[cfg(feature = "trace")]
|
||||
let _guard = span.enter();
|
||||
system.apply_buffers(world);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_apply_buffers(&mut self, apply_buffers: bool) {
|
||||
self.apply_buffers = apply_buffers;
|
||||
}
|
||||
|
||||
/// Topologically sorted parallel systems.
|
||||
///
|
||||
/// Note that systems won't be fully-formed until the stage has been run at least once.
|
||||
|
@ -832,9 +850,11 @@ impl Stage for SystemStage {
|
|||
}
|
||||
|
||||
// Apply parallel systems' buffers.
|
||||
for container in &mut self.parallel {
|
||||
if container.should_run {
|
||||
container.system_mut().apply_buffers(world);
|
||||
if self.apply_buffers {
|
||||
for container in &mut self.parallel {
|
||||
if container.should_run {
|
||||
container.system_mut().apply_buffers(world);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
query::{Access, FilteredAccessSet},
|
||||
system::{
|
||||
check_system_change_tick, ReadOnlySystemParamFetch, System, SystemParam, SystemParamFetch,
|
||||
SystemParamState,
|
||||
SystemParamItem, SystemParamState,
|
||||
},
|
||||
world::{World, WorldId},
|
||||
};
|
||||
|
@ -46,6 +46,11 @@ impl SystemMeta {
|
|||
pub fn set_non_send(&mut self) {
|
||||
self.is_send = false;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn check_change_tick(&mut self, change_tick: u32) {
|
||||
check_system_change_tick(&mut self.last_change_tick, change_tick, self.name.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Actually use this in FunctionSystem. We should probably only do this once Systems are constructed using a World reference
|
||||
|
@ -121,6 +126,10 @@ impl<Param: SystemParam> SystemState<Param> {
|
|||
self.world_id == world.id()
|
||||
}
|
||||
|
||||
pub(crate) fn new_archetype(&mut self, archetype: &Archetype) {
|
||||
self.param_state.new_archetype(archetype, &mut self.meta);
|
||||
}
|
||||
|
||||
fn validate_world_and_update_archetypes(&mut self, world: &World) {
|
||||
assert!(self.matches_world(world), "Encountered a mismatched World. A SystemState cannot be used with Worlds other than the one it was created with.");
|
||||
let archetypes = world.archetypes();
|
||||
|
@ -159,6 +168,74 @@ impl<Param: SystemParam> SystemState<Param> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A trait for defining systems with a [`SystemParam`] associated type.
|
||||
///
|
||||
/// This facilitates the creation of systems that are generic over some trait
|
||||
/// and that use that trait's associated types as `SystemParam`s.
|
||||
pub trait RunSystem: Send + Sync + 'static {
|
||||
/// The `SystemParam` type passed to the system when it runs.
|
||||
type Param: SystemParam;
|
||||
|
||||
/// Runs the system.
|
||||
fn run(param: SystemParamItem<Self::Param>);
|
||||
|
||||
/// Creates a concrete instance of the system for the specified `World`.
|
||||
fn system(world: &mut World) -> ParamSystem<Self::Param> {
|
||||
ParamSystem {
|
||||
run: Self::run,
|
||||
state: SystemState::new(world),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ParamSystem<P: SystemParam> {
|
||||
state: SystemState<P>,
|
||||
run: fn(SystemParamItem<P>),
|
||||
}
|
||||
|
||||
impl<P: SystemParam + 'static> System for ParamSystem<P> {
|
||||
type In = ();
|
||||
|
||||
type Out = ();
|
||||
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
self.state.meta().name.clone()
|
||||
}
|
||||
|
||||
fn new_archetype(&mut self, archetype: &Archetype) {
|
||||
self.state.new_archetype(archetype);
|
||||
}
|
||||
|
||||
fn component_access(&self) -> &Access<ComponentId> {
|
||||
self.state.meta().component_access_set.combined_access()
|
||||
}
|
||||
|
||||
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
|
||||
&self.state.meta().archetype_component_access
|
||||
}
|
||||
|
||||
fn is_send(&self) -> bool {
|
||||
self.state.meta().is_send()
|
||||
}
|
||||
|
||||
unsafe fn run_unsafe(&mut self, _input: Self::In, world: &World) -> Self::Out {
|
||||
let param = self.state.get_unchecked_manual(world);
|
||||
(self.run)(param);
|
||||
}
|
||||
|
||||
fn apply_buffers(&mut self, world: &mut World) {
|
||||
self.state.apply(world);
|
||||
}
|
||||
|
||||
fn initialize(&mut self, _world: &mut World) {
|
||||
// already initialized by nature of the SystemState being constructed
|
||||
}
|
||||
|
||||
fn check_change_tick(&mut self, change_tick: u32) {
|
||||
self.state.meta.check_change_tick(change_tick);
|
||||
}
|
||||
}
|
||||
|
||||
/// Conversion trait to turn something into a [`System`].
|
||||
///
|
||||
/// Use this to get a system from a function. Also note that every system implements this trait as
|
||||
|
|
|
@ -48,6 +48,8 @@ pub trait SystemParam: Sized {
|
|||
type Fetch: for<'w, 's> SystemParamFetch<'w, 's>;
|
||||
}
|
||||
|
||||
pub type SystemParamItem<'w, 's, P> = <<P as SystemParam>::Fetch as SystemParamFetch<'w, 's>>::Item;
|
||||
|
||||
/// The state of a [`SystemParam`].
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -1230,3 +1232,12 @@ macro_rules! impl_system_param_tuple {
|
|||
}
|
||||
|
||||
all_tuples!(impl_system_param_tuple, 0, 16, P);
|
||||
|
||||
pub mod lifetimeless {
|
||||
pub type SQuery<Q, F = ()> = super::Query<'static, 'static, Q, F>;
|
||||
pub type Read<T> = &'static T;
|
||||
pub type Write<T> = &'static mut T;
|
||||
pub type SRes<T> = super::Res<'static, T>;
|
||||
pub type SResMut<T> = super::ResMut<'static, T>;
|
||||
pub type SCommands = crate::system::Commands<'static, 'static>;
|
||||
}
|
||||
|
|
|
@ -11,17 +11,17 @@ categories = ["game-engines", "graphics", "gui", "rendering"]
|
|||
|
||||
[features]
|
||||
wgpu_trace = ["bevy_wgpu/trace"]
|
||||
trace = [ "bevy_app/trace", "bevy_ecs/trace" ]
|
||||
trace = [ "bevy_app/trace", "bevy_ecs/trace", "bevy_render2/trace" ]
|
||||
trace_chrome = [ "bevy_log/tracing-chrome" ]
|
||||
trace_tracy = [ "bevy_log/tracing-tracy" ]
|
||||
|
||||
# Image format support for texture loading (PNG and HDR are enabled by default)
|
||||
hdr = ["bevy_render/hdr"]
|
||||
png = ["bevy_render/png"]
|
||||
dds = ["bevy_render/dds"]
|
||||
tga = ["bevy_render/tga"]
|
||||
jpeg = ["bevy_render/jpeg"]
|
||||
bmp = ["bevy_render/bmp"]
|
||||
hdr = ["bevy_render/hdr", "bevy_render2/hdr" ]
|
||||
png = ["bevy_render/png", "bevy_render2/png" ]
|
||||
dds = ["bevy_render/dds", "bevy_render2/dds" ]
|
||||
tga = ["bevy_render/tga", "bevy_render2/tga" ]
|
||||
jpeg = ["bevy_render/jpeg", "bevy_render2/jpeg" ]
|
||||
bmp = ["bevy_render/bmp", "bevy_render2/bmp" ]
|
||||
|
||||
# Audio format support (MP3 is enabled by default)
|
||||
flac = ["bevy_audio/flac"]
|
||||
|
@ -63,11 +63,16 @@ bevy_window = { path = "../bevy_window", version = "0.5.0" }
|
|||
bevy_tasks = { path = "../bevy_tasks", version = "0.5.0" }
|
||||
# bevy (optional)
|
||||
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.5.0" }
|
||||
bevy_core_pipeline = { path = "../../pipelined/bevy_core_pipeline", optional = true, version = "0.5.0" }
|
||||
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.5.0" }
|
||||
bevy_gltf2 = { path = "../../pipelined/bevy_gltf2", optional = true, version = "0.5.0" }
|
||||
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.5.0" }
|
||||
bevy_pbr2 = { path = "../../pipelined/bevy_pbr2", optional = true, version = "0.5.0" }
|
||||
bevy_render = { path = "../bevy_render", optional = true, version = "0.5.0" }
|
||||
bevy_render2 = { path = "../../pipelined/bevy_render2", optional = true, version = "0.5.0" }
|
||||
bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.5.0" }
|
||||
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.5.0" }
|
||||
bevy_sprite2 = { path = "../../pipelined/bevy_sprite2", optional = true, version = "0.5.0" }
|
||||
bevy_text = { path = "../bevy_text", optional = true, version = "0.5.0" }
|
||||
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.5.0" }
|
||||
bevy_wgpu = { path = "../bevy_wgpu", optional = true, version = "0.5.0" }
|
||||
|
|
|
@ -109,3 +109,40 @@ impl PluginGroup for MinimalPlugins {
|
|||
group.add(ScheduleRunnerPlugin::default());
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PipelinedDefaultPlugins;
|
||||
|
||||
impl PluginGroup for PipelinedDefaultPlugins {
|
||||
fn build(&mut self, group: &mut PluginGroupBuilder) {
|
||||
group.add(bevy_log::LogPlugin::default());
|
||||
group.add(bevy_core::CorePlugin::default());
|
||||
group.add(bevy_transform::TransformPlugin::default());
|
||||
group.add(bevy_diagnostic::DiagnosticsPlugin::default());
|
||||
group.add(bevy_input::InputPlugin::default());
|
||||
group.add(bevy_window::WindowPlugin::default());
|
||||
group.add(bevy_asset::AssetPlugin::default());
|
||||
group.add(bevy_scene::ScenePlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_winit")]
|
||||
group.add(bevy_winit::WinitPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_render2")]
|
||||
{
|
||||
group.add(bevy_render2::RenderPlugin::default());
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_core_pipeline")]
|
||||
{
|
||||
group.add(bevy_core_pipeline::CorePipelinePlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_sprite2")]
|
||||
group.add(bevy_sprite2::SpritePlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_pbr2")]
|
||||
group.add(bevy_pbr2::PbrPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_gltf2")]
|
||||
group.add(bevy_gltf2::GltfPlugin::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,6 +82,12 @@ pub mod audio {
|
|||
pub use bevy_audio::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_core_pipeline")]
|
||||
pub mod core_pipeline {
|
||||
//! Core render pipeline.
|
||||
pub use bevy_core_pipeline::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_gilrs")]
|
||||
pub mod gilrs {
|
||||
pub use bevy_gilrs::*;
|
||||
|
@ -93,24 +99,48 @@ pub mod gltf {
|
|||
pub use bevy_gltf::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_gltf2")]
|
||||
pub mod gltf2 {
|
||||
//! Support for GLTF file loading.
|
||||
pub use bevy_gltf2::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
pub mod pbr {
|
||||
//! Physically based rendering.
|
||||
pub use bevy_pbr::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_pbr2")]
|
||||
pub mod pbr2 {
|
||||
//! Physically based rendering.
|
||||
pub use bevy_pbr2::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub mod render {
|
||||
//! Cameras, meshes, textures, shaders, and pipelines.
|
||||
pub use bevy_render::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_render2")]
|
||||
pub mod render2 {
|
||||
//! Cameras, meshes, textures, shaders, and pipelines.
|
||||
pub use bevy_render2::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
pub mod sprite {
|
||||
//! Items for sprites, rects, texture atlases, etc.
|
||||
pub use bevy_sprite::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_sprite2")]
|
||||
pub mod sprite2 {
|
||||
//! Items for sprites, rects, texture atlases, etc.
|
||||
pub use bevy_sprite2::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_text")]
|
||||
pub mod text {
|
||||
//! Text drawing, styling, and font assets.
|
||||
|
|
|
@ -15,6 +15,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
|
|||
tracing-subscriber = {version = "0.3.1", features = ["registry", "env-filter"]}
|
||||
tracing-chrome = { version = "0.4.0", optional = true }
|
||||
tracing-tracy = { version = "0.8.0", optional = true }
|
||||
tracing-log = "0.1.2"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_log-sys = "0.2.0"
|
||||
|
|
|
@ -13,6 +13,7 @@ pub use bevy_utils::tracing::{
|
|||
};
|
||||
|
||||
use bevy_app::{App, Plugin};
|
||||
use tracing_log::LogTracer;
|
||||
#[cfg(feature = "tracing-chrome")]
|
||||
use tracing_subscriber::fmt::{format::DefaultFields, FormattedFields};
|
||||
use tracing_subscriber::{prelude::*, registry::Registry, EnvFilter};
|
||||
|
@ -88,7 +89,7 @@ impl Plugin for LogPlugin {
|
|||
let settings = app.world.get_resource_or_insert_with(LogSettings::default);
|
||||
format!("{},{}", settings.level, settings.filter)
|
||||
};
|
||||
|
||||
LogTracer::init().unwrap();
|
||||
let filter_layer = EnvFilter::try_from_default_env()
|
||||
.or_else(|_| EnvFilter::try_new(&default_filter))
|
||||
.unwrap();
|
||||
|
|
|
@ -11,3 +11,4 @@ keywords = ["bevy"]
|
|||
[dependencies]
|
||||
cargo-manifest = "0.2.6"
|
||||
syn = "1.0"
|
||||
quote = "1.0"
|
||||
|
|
|
@ -8,6 +8,7 @@ pub use symbol::*;
|
|||
|
||||
use cargo_manifest::{DepsSet, Manifest};
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
pub struct BevyManifest {
|
||||
|
@ -65,3 +66,29 @@ fn get_path(path: &str) -> syn::Path {
|
|||
fn parse_str<T: syn::parse::Parse>(path: &str) -> T {
|
||||
syn::parse(path.parse::<TokenStream>().unwrap()).unwrap()
|
||||
}
|
||||
|
||||
/// Derive a label trait
|
||||
///
|
||||
/// # Args
|
||||
///
|
||||
/// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label trait
|
||||
/// - `trait_path`: The path [`syn::Path`] to the label trait
|
||||
pub fn derive_label(input: syn::DeriveInput, trait_path: syn::Path) -> TokenStream {
|
||||
let ident = input.ident;
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause {
|
||||
where_token: Default::default(),
|
||||
predicates: Default::default(),
|
||||
});
|
||||
where_clause.predicates.push(syn::parse2(quote! { Self: Eq + ::std::fmt::Debug + ::std::hash::Hash + Clone + Send + Sync + 'static }).unwrap());
|
||||
|
||||
(quote! {
|
||||
impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
|
||||
fn dyn_clone(&self) -> Box<dyn #trait_path> {
|
||||
Box::new(Clone::clone(self))
|
||||
}
|
||||
}
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@ license = "MIT OR Apache-2.0"
|
|||
keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
glam = { version = "0.18.0", features = ["serde", "bytemuck"] }
|
||||
glam = { version = "0.20.0", features = ["serde", "bytemuck"] }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] }
|
||||
|
|
|
@ -24,7 +24,7 @@ parking_lot = "0.11.0"
|
|||
thiserror = "1.0"
|
||||
serde = "1"
|
||||
smallvec = { version = "1.6", features = ["serde", "union", "const_generics"], optional = true }
|
||||
glam = { version = "0.18.0", features = ["serde"], optional = true }
|
||||
glam = { version = "0.20.0", features = ["serde"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ron = "0.6.2"
|
||||
|
|
|
@ -32,7 +32,7 @@ downcast-rs = "1.2.0"
|
|||
thiserror = "1.0"
|
||||
anyhow = "1.0.4"
|
||||
hex = "0.4.2"
|
||||
hexasphere = "5.0.0"
|
||||
hexasphere = "6.0.0"
|
||||
parking_lot = "0.11.0"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
LoadOp, Operations, PassDescriptor, RenderPassColorAttachment,
|
||||
RenderPassDepthStencilAttachment, TextureAttachment,
|
||||
},
|
||||
texture::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsage},
|
||||
texture::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages},
|
||||
Color,
|
||||
};
|
||||
use bevy_ecs::{component::Component, reflect::ReflectComponent, world::World};
|
||||
|
@ -126,7 +126,7 @@ pub(crate) fn add_base_graph(config: &BaseRenderGraphConfig, world: &mut World)
|
|||
dimension: TextureDimension::D2,
|
||||
format: TextureFormat::Depth32Float, /* PERF: vulkan docs recommend using 24
|
||||
* bit depth for better performance */
|
||||
usage: TextureUsage::OUTPUT_ATTACHMENT,
|
||||
usage: TextureUsages::OUTPUT_ATTACHMENT,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -220,7 +220,7 @@ pub(crate) fn add_base_graph(config: &BaseRenderGraphConfig, world: &mut World)
|
|||
sample_count: msaa.samples,
|
||||
dimension: TextureDimension::D2,
|
||||
format: TextureFormat::default(),
|
||||
usage: TextureUsage::OUTPUT_ATTACHMENT,
|
||||
usage: TextureUsages::OUTPUT_ATTACHMENT,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
@ -52,7 +52,7 @@ impl Node for WindowSwapChainNode {
|
|||
|
||||
let render_resource_context = render_context.resources_mut();
|
||||
|
||||
// create window swapchain when window is resized or created
|
||||
// reconfigure surface window is resized or created
|
||||
if self
|
||||
.window_created_event_reader
|
||||
.iter(window_created_events)
|
||||
|
@ -62,10 +62,10 @@ impl Node for WindowSwapChainNode {
|
|||
.iter(window_resized_events)
|
||||
.any(|e| e.id == window.id())
|
||||
{
|
||||
render_resource_context.create_swap_chain(window);
|
||||
render_resource_context.configure_surface(window);
|
||||
}
|
||||
|
||||
let swap_chain_texture = render_resource_context.next_swap_chain_texture(window);
|
||||
let swap_chain_texture = render_resource_context.next_surface_frame(window);
|
||||
output.set(
|
||||
WINDOW_TEXTURE,
|
||||
RenderResourceId::Texture(swap_chain_texture),
|
||||
|
|
|
@ -31,15 +31,15 @@ impl HeadlessRenderResourceContext {
|
|||
}
|
||||
|
||||
impl RenderResourceContext for HeadlessRenderResourceContext {
|
||||
fn create_swap_chain(&self, _window: &Window) {}
|
||||
fn configure_surface(&self, _window: &Window) {}
|
||||
|
||||
fn next_swap_chain_texture(&self, _window: &Window) -> TextureId {
|
||||
fn next_surface_frame(&self, _window: &Window) -> TextureId {
|
||||
TextureId::new()
|
||||
}
|
||||
|
||||
fn drop_swap_chain_texture(&self, _render_resource: TextureId) {}
|
||||
fn drop_surface_frame(&self, _render_resource: TextureId) {}
|
||||
|
||||
fn drop_all_swap_chain_textures(&self) {}
|
||||
fn drop_all_surface_frames(&self) {}
|
||||
|
||||
fn create_sampler(&self, _sampler_descriptor: &SamplerDescriptor) -> SamplerId {
|
||||
SamplerId::new()
|
||||
|
|
|
@ -12,10 +12,10 @@ use downcast_rs::{impl_downcast, Downcast};
|
|||
use std::ops::Range;
|
||||
|
||||
pub trait RenderResourceContext: Downcast + Send + Sync + 'static {
|
||||
fn create_swap_chain(&self, window: &Window);
|
||||
fn next_swap_chain_texture(&self, window: &Window) -> TextureId;
|
||||
fn drop_swap_chain_texture(&self, resource: TextureId);
|
||||
fn drop_all_swap_chain_textures(&self);
|
||||
fn configure_surface(&self, window: &Window);
|
||||
fn next_surface_frame(&self, window: &Window) -> TextureId;
|
||||
fn drop_surface_frame(&self, resource: TextureId);
|
||||
fn drop_all_surface_frames(&self);
|
||||
fn create_sampler(&self, sampler_descriptor: &SamplerDescriptor) -> SamplerId;
|
||||
fn create_texture(&self, texture_descriptor: TextureDescriptor) -> TextureId;
|
||||
fn create_buffer(&self, buffer_info: BufferInfo) -> BufferId;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use std::convert::TryFrom;
|
||||
use thiserror::Error;
|
||||
|
||||
use super::{Extent3d, Texture, TextureDimension, TextureFormat};
|
||||
|
@ -87,7 +86,8 @@ impl From<image::DynamicImage> for Texture {
|
|||
Vec::with_capacity(width as usize * height as usize * format.pixel_size());
|
||||
|
||||
for pixel in image.into_raw().chunks_exact(3) {
|
||||
// TODO unsafe_get in release builds?
|
||||
// TODO use the array_chunks method once stabilised
|
||||
// https://github.com/rust-lang/rust/issues/74985
|
||||
let r = pixel[0];
|
||||
let g = pixel[1];
|
||||
let b = pixel[2];
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::{Extent3d, Texture, TextureDimension, TextureFormat, TextureUsage};
|
||||
use super::{Extent3d, Texture, TextureDimension, TextureFormat, TextureUsages};
|
||||
|
||||
/// Describes a texture
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
|
@ -8,7 +8,7 @@ pub struct TextureDescriptor {
|
|||
pub sample_count: u32,
|
||||
pub dimension: TextureDimension,
|
||||
pub format: TextureFormat,
|
||||
pub usage: TextureUsage,
|
||||
pub usage: TextureUsages,
|
||||
}
|
||||
|
||||
impl From<&Texture> for TextureDescriptor {
|
||||
|
@ -19,7 +19,7 @@ impl From<&Texture> for TextureDescriptor {
|
|||
sample_count: 1,
|
||||
dimension: texture.dimension,
|
||||
format: texture.format,
|
||||
usage: TextureUsage::SAMPLED | TextureUsage::COPY_DST,
|
||||
usage: TextureUsages::SAMPLED | TextureUsages::COPY_DST,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ impl Default for TextureDescriptor {
|
|||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
format: TextureFormat::Rgba8UnormSrgb,
|
||||
usage: TextureUsage::SAMPLED | TextureUsage::COPY_DST,
|
||||
usage: TextureUsages::SAMPLED | TextureUsages::COPY_DST,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -276,7 +276,7 @@ impl Default for TextureFormat {
|
|||
|
||||
bitflags::bitflags! {
|
||||
#[repr(transparent)]
|
||||
pub struct TextureUsage: u32 {
|
||||
pub struct TextureUsages: u32 {
|
||||
const COPY_SRC = 1;
|
||||
const COPY_DST = 2;
|
||||
const SAMPLED = 4;
|
||||
|
|
|
@ -21,7 +21,7 @@ struct Rect {
|
|||
vec2 end;
|
||||
};
|
||||
|
||||
layout(set = 1, binding = 1) buffer TextureAtlas_textures {
|
||||
layout(set = 1, binding = 1) readonly buffer TextureAtlas_textures {
|
||||
Rect[] Textures;
|
||||
};
|
||||
|
||||
|
|
|
@ -168,10 +168,11 @@ impl TextureAtlasBuilder {
|
|||
&contains_smallest_box,
|
||||
) {
|
||||
Ok(rect_placements) => {
|
||||
atlas_texture = Texture::new_fill(
|
||||
Extent3d::new(current_width, current_height, 1),
|
||||
let size = Extent3d::new(current_width, current_height, 1);
|
||||
atlas_texture = Texture::new(
|
||||
size,
|
||||
TextureDimension::D2,
|
||||
&[0, 0, 0, 0],
|
||||
vec![0; self.format.pixel_size() * size.volume()],
|
||||
self.format,
|
||||
);
|
||||
Some(rect_placements)
|
||||
|
|
|
@ -190,7 +190,7 @@ impl GlobalTransform {
|
|||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn rotate(&mut self, rotation: Quat) {
|
||||
self.rotation *= rotation;
|
||||
self.rotation = rotation * self.rotation;
|
||||
}
|
||||
|
||||
/// Multiplies `self` with `transform` component by component, returning the
|
||||
|
|
|
@ -202,7 +202,7 @@ impl Transform {
|
|||
/// Rotates the transform by the given rotation.
|
||||
#[inline]
|
||||
pub fn rotate(&mut self, rotation: Quat) {
|
||||
self.rotation *= rotation;
|
||||
self.rotation = rotation * self.rotation;
|
||||
}
|
||||
|
||||
/// Multiplies `self` with `transform` component by component, returning the
|
||||
|
|
100
crates/bevy_utils/src/label.rs
Normal file
100
crates/bevy_utils/src/label.rs
Normal file
|
@ -0,0 +1,100 @@
|
|||
//! Traits used by label implementations
|
||||
|
||||
use std::{
|
||||
any::Any,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
pub trait DynEq: Any {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
fn dyn_eq(&self, other: &dyn DynEq) -> bool;
|
||||
}
|
||||
|
||||
impl<T> DynEq for T
|
||||
where
|
||||
T: Any + Eq,
|
||||
{
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn dyn_eq(&self, other: &dyn DynEq) -> bool {
|
||||
if let Some(other) = other.as_any().downcast_ref::<T>() {
|
||||
return self == other;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DynHash: DynEq {
|
||||
fn as_dyn_eq(&self) -> &dyn DynEq;
|
||||
|
||||
fn dyn_hash(&self, state: &mut dyn Hasher);
|
||||
}
|
||||
|
||||
impl<T> DynHash for T
|
||||
where
|
||||
T: DynEq + Hash,
|
||||
{
|
||||
fn as_dyn_eq(&self) -> &dyn DynEq {
|
||||
self
|
||||
}
|
||||
|
||||
fn dyn_hash(&self, mut state: &mut dyn Hasher) {
|
||||
T::hash(self, &mut state);
|
||||
self.type_id().hash(&mut state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro to define a new label trait
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_utils::define_label;
|
||||
/// define_label!(MyNewLabelTrait);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! define_label {
|
||||
($label_trait_name:ident) => {
|
||||
pub trait $label_trait_name:
|
||||
::bevy_utils::label::DynHash + ::std::fmt::Debug + Send + Sync + 'static
|
||||
{
|
||||
#[doc(hidden)]
|
||||
fn dyn_clone(&self) -> Box<dyn $label_trait_name>;
|
||||
}
|
||||
|
||||
impl PartialEq for dyn $label_trait_name {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.dyn_eq(other.as_dyn_eq())
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for dyn $label_trait_name {}
|
||||
|
||||
impl ::std::hash::Hash for dyn $label_trait_name {
|
||||
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.dyn_hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn $label_trait_name> {
|
||||
fn clone(&self) -> Self {
|
||||
self.dyn_clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl $label_trait_name for ::std::borrow::Cow<'static, str> {
|
||||
fn dyn_clone(&self) -> Box<dyn $label_trait_name> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl $label_trait_name for &'static str {
|
||||
fn dyn_clone(&self) -> Box<dyn $label_trait_name> {
|
||||
Box::new(<&str>::clone(self))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
mod enum_variant_meta;
|
||||
pub use enum_variant_meta::*;
|
||||
pub mod label;
|
||||
|
||||
pub use ahash::AHasher;
|
||||
pub use enum_variant_meta::*;
|
||||
pub use instant::{Duration, Instant};
|
||||
pub use tracing;
|
||||
pub use uuid::Uuid;
|
||||
|
|
|
@ -25,7 +25,7 @@ bevy_winit = { path = "../bevy_winit", optional = true, version = "0.5.0" }
|
|||
bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
|
||||
|
||||
# other
|
||||
wgpu = "0.9"
|
||||
wgpu = { version = "0.11.0", features = ["spirv"] }
|
||||
futures-lite = "1.4.0"
|
||||
crossbeam-channel = "0.5.0"
|
||||
crossbeam-utils = "0.8.1"
|
||||
|
|
|
@ -29,9 +29,7 @@ impl WgpuResourceDiagnosticsPlugin {
|
|||
DiagnosticId::from_u128(305855369913076220671125671543184691267);
|
||||
pub const SHADER_MODULES: DiagnosticId =
|
||||
DiagnosticId::from_u128(287681470908132753275843248383768232237);
|
||||
pub const SWAP_CHAINS: DiagnosticId =
|
||||
DiagnosticId::from_u128(199253035828743332241465305105689014605);
|
||||
pub const SWAP_CHAIN_OUTPUTS: DiagnosticId =
|
||||
pub const SURFACE_FRAMES: DiagnosticId =
|
||||
DiagnosticId::from_u128(112048874168736161226721327099863374234);
|
||||
pub const TEXTURES: DiagnosticId =
|
||||
DiagnosticId::from_u128(305955424195390184883220102469231911115);
|
||||
|
@ -47,10 +45,8 @@ impl WgpuResourceDiagnosticsPlugin {
|
|||
10,
|
||||
));
|
||||
|
||||
diagnostics.add(Diagnostic::new(Self::SWAP_CHAINS, "swap_chains", 10));
|
||||
|
||||
diagnostics.add(Diagnostic::new(
|
||||
Self::SWAP_CHAIN_OUTPUTS,
|
||||
Self::SURFACE_FRAMES,
|
||||
"swap_chain_outputs",
|
||||
10,
|
||||
));
|
||||
|
@ -99,19 +95,10 @@ impl WgpuResourceDiagnosticsPlugin {
|
|||
);
|
||||
|
||||
diagnostics.add_measurement(
|
||||
Self::SWAP_CHAINS,
|
||||
Self::SURFACE_FRAMES,
|
||||
render_resource_context
|
||||
.resources
|
||||
.window_swap_chains
|
||||
.read()
|
||||
.len() as f64,
|
||||
);
|
||||
|
||||
diagnostics.add_measurement(
|
||||
Self::SWAP_CHAIN_OUTPUTS,
|
||||
render_resource_context
|
||||
.resources
|
||||
.swap_chain_frames
|
||||
.surface_textures
|
||||
.read()
|
||||
.len() as f64,
|
||||
);
|
||||
|
|
|
@ -26,15 +26,12 @@ pub enum WgpuFeature {
|
|||
TimestampQuery,
|
||||
PipelineStatisticsQuery,
|
||||
MappablePrimaryBuffers,
|
||||
SampledTextureBindingArray,
|
||||
SampledTextureArrayDynamicIndexing,
|
||||
SampledTextureArrayNonUniformIndexing,
|
||||
UnsizedBindingArray,
|
||||
MultiDrawIndirect,
|
||||
MultiDrawIndirectCount,
|
||||
PushConstants,
|
||||
AddressModeClampToBorder,
|
||||
NonFillPolygonMode,
|
||||
PolygonModeLine,
|
||||
TextureCompressionEtc2,
|
||||
TextureCompressionAstcLdr,
|
||||
TextureAdapterSpecificFormatFeatures,
|
||||
|
@ -67,6 +64,8 @@ pub struct WgpuLimits {
|
|||
pub max_vertex_buffers: u32,
|
||||
pub max_vertex_attributes: u32,
|
||||
pub max_vertex_buffer_array_stride: u32,
|
||||
pub min_storage_buffer_offset_alignment: u32,
|
||||
pub min_uniform_buffer_offset_alignment: u32,
|
||||
}
|
||||
|
||||
impl Default for WgpuLimits {
|
||||
|
@ -93,6 +92,8 @@ impl Default for WgpuLimits {
|
|||
max_vertex_buffers: default.max_vertex_buffers,
|
||||
max_vertex_attributes: default.max_vertex_attributes,
|
||||
max_vertex_buffer_array_stride: default.max_vertex_buffer_array_stride,
|
||||
min_storage_buffer_offset_alignment: default.min_storage_buffer_offset_alignment,
|
||||
min_uniform_buffer_offset_alignment: default.min_uniform_buffer_offset_alignment,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -231,8 +231,15 @@ fn get_texture_view<'a>(
|
|||
panic!("Color attachment {} does not exist.", name);
|
||||
}
|
||||
},
|
||||
TextureAttachment::Id(render_resource) => refs.textures.get(render_resource).unwrap_or_else(|| &refs.swap_chain_frames.get(render_resource).unwrap().output.view),
|
||||
TextureAttachment::Input(_) => panic!("Encountered unset `TextureAttachment::Input`. The `RenderGraph` executor should always set `TextureAttachment::Inputs` to `TextureAttachment::RenderResource` before running. This is a bug, please report it!"),
|
||||
TextureAttachment::Id(render_resource) => refs
|
||||
.textures
|
||||
.get(render_resource)
|
||||
.unwrap_or_else(|| &refs.surface_textures.get(render_resource).unwrap().0),
|
||||
TextureAttachment::Input(_) => panic!(
|
||||
"Encountered unset `TextureAttachment::Input`. The `RenderGraph` executor should \
|
||||
always set `TextureAttachment::Inputs` to `TextureAttachment::RenderResource` before \
|
||||
running. This is a bug, please report it!"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,8 @@ pub struct WgpuRenderResourceContext {
|
|||
}
|
||||
|
||||
pub const COPY_BYTES_PER_ROW_ALIGNMENT: usize = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
|
||||
pub const BIND_BUFFER_ALIGNMENT: usize = wgpu::BIND_BUFFER_ALIGNMENT as usize;
|
||||
// TODO: fix this?
|
||||
pub const BIND_BUFFER_ALIGNMENT: usize = 256;
|
||||
pub const COPY_BUFFER_ALIGNMENT: usize = wgpu::COPY_BUFFER_ALIGNMENT as usize;
|
||||
pub const PUSH_CONSTANT_ALIGNMENT: u32 = wgpu::PUSH_CONSTANT_ALIGNMENT;
|
||||
|
||||
|
@ -94,6 +95,7 @@ impl WgpuRenderResourceContext {
|
|||
y: source_origin[1],
|
||||
z: source_origin[2],
|
||||
},
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
wgpu::ImageCopyTexture {
|
||||
texture: destination,
|
||||
|
@ -103,6 +105,7 @@ impl WgpuRenderResourceContext {
|
|||
y: destination_origin[1],
|
||||
z: destination_origin[2],
|
||||
},
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
size.wgpu_into(),
|
||||
)
|
||||
|
@ -134,6 +137,7 @@ impl WgpuRenderResourceContext {
|
|||
y: source_origin[1],
|
||||
z: source_origin[2],
|
||||
},
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
wgpu::ImageCopyBuffer {
|
||||
buffer: destination,
|
||||
|
@ -181,6 +185,7 @@ impl WgpuRenderResourceContext {
|
|||
y: destination_origin[1],
|
||||
z: destination_origin[2],
|
||||
},
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
size.wgpu_into(),
|
||||
);
|
||||
|
@ -206,11 +211,11 @@ impl WgpuRenderResourceContext {
|
|||
let shader_stage = if binding.shader_stage
|
||||
== BindingShaderStage::VERTEX | BindingShaderStage::FRAGMENT
|
||||
{
|
||||
wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT
|
||||
wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT
|
||||
} else if binding.shader_stage == BindingShaderStage::VERTEX {
|
||||
wgpu::ShaderStage::VERTEX
|
||||
wgpu::ShaderStages::VERTEX
|
||||
} else if binding.shader_stage == BindingShaderStage::FRAGMENT {
|
||||
wgpu::ShaderStage::FRAGMENT
|
||||
wgpu::ShaderStages::FRAGMENT
|
||||
} else {
|
||||
panic!("Invalid binding shader stage.")
|
||||
};
|
||||
|
@ -230,14 +235,15 @@ impl WgpuRenderResourceContext {
|
|||
bind_group_layouts.insert(descriptor.id, bind_group_layout);
|
||||
}
|
||||
|
||||
fn try_next_swap_chain_texture(&self, window_id: bevy_window::WindowId) -> Option<TextureId> {
|
||||
let mut window_swap_chains = self.resources.window_swap_chains.write();
|
||||
let mut swap_chain_outputs = self.resources.swap_chain_frames.write();
|
||||
fn try_next_surface_frame(&self, window_id: bevy_window::WindowId) -> Option<TextureId> {
|
||||
let mut window_surfaces = self.resources.window_surfaces.write();
|
||||
let mut surface_frames = self.resources.surface_textures.write();
|
||||
|
||||
let window_swap_chain = window_swap_chains.get_mut(&window_id).unwrap();
|
||||
let next_texture = window_swap_chain.get_current_frame().ok()?;
|
||||
let window_surface = window_surfaces.get_mut(&window_id).unwrap();
|
||||
let next_texture = window_surface.get_current_texture().ok()?;
|
||||
let view = next_texture.texture.create_view(&Default::default());
|
||||
let id = TextureId::new();
|
||||
swap_chain_outputs.insert(id, next_texture);
|
||||
surface_frames.insert(id, (view, next_texture));
|
||||
Some(id)
|
||||
}
|
||||
}
|
||||
|
@ -339,7 +345,6 @@ impl RenderResourceContext for WgpuRenderResourceContext {
|
|||
.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: wgpu::ShaderSource::SpirV(spirv),
|
||||
flags: Default::default(),
|
||||
});
|
||||
shader_modules.insert(shader_handle.clone_weak(), shader_module);
|
||||
}
|
||||
|
@ -358,43 +363,39 @@ impl RenderResourceContext for WgpuRenderResourceContext {
|
|||
self.create_shader_module_from_source(shader_handle, shader);
|
||||
}
|
||||
|
||||
fn create_swap_chain(&self, window: &Window) {
|
||||
fn configure_surface(&self, window: &Window) {
|
||||
let surfaces = self.resources.window_surfaces.read();
|
||||
let mut window_swap_chains = self.resources.window_swap_chains.write();
|
||||
|
||||
let swap_chain_descriptor: wgpu::SwapChainDescriptor = window.wgpu_into();
|
||||
let surface_configuration: wgpu::SurfaceConfiguration = window.wgpu_into();
|
||||
let surface = surfaces
|
||||
.get(&window.id())
|
||||
.expect("No surface found for window.");
|
||||
let swap_chain = self
|
||||
.device
|
||||
.create_swap_chain(surface, &swap_chain_descriptor);
|
||||
|
||||
window_swap_chains.insert(window.id(), swap_chain);
|
||||
surface.configure(&self.device, &surface_configuration);
|
||||
}
|
||||
|
||||
fn next_swap_chain_texture(&self, window: &bevy_window::Window) -> TextureId {
|
||||
if let Some(texture_id) = self.try_next_swap_chain_texture(window.id()) {
|
||||
fn next_surface_frame(&self, window: &bevy_window::Window) -> TextureId {
|
||||
if let Some(texture_id) = self.try_next_surface_frame(window.id()) {
|
||||
texture_id
|
||||
} else {
|
||||
self.resources
|
||||
.window_swap_chains
|
||||
.write()
|
||||
.remove(&window.id());
|
||||
self.create_swap_chain(window);
|
||||
self.try_next_swap_chain_texture(window.id())
|
||||
self.resources.window_surfaces.write().remove(&window.id());
|
||||
self.configure_surface(window);
|
||||
self.try_next_surface_frame(window.id())
|
||||
.expect("Failed to acquire next swap chain texture!")
|
||||
}
|
||||
}
|
||||
|
||||
fn drop_swap_chain_texture(&self, texture: TextureId) {
|
||||
let mut swap_chain_outputs = self.resources.swap_chain_frames.write();
|
||||
swap_chain_outputs.remove(&texture);
|
||||
fn drop_surface_frame(&self, texture: TextureId) {
|
||||
let mut surface_frames = self.resources.surface_textures.write();
|
||||
surface_frames.remove(&texture);
|
||||
}
|
||||
|
||||
fn drop_all_swap_chain_textures(&self) {
|
||||
let mut swap_chain_outputs = self.resources.swap_chain_frames.write();
|
||||
swap_chain_outputs.clear();
|
||||
fn drop_all_surface_frames(&self) {
|
||||
let mut surface_frames = self.resources.surface_textures.write();
|
||||
for (_, (_, texture)) in surface_frames.drain() {
|
||||
texture.present();
|
||||
}
|
||||
|
||||
surface_frames.clear();
|
||||
}
|
||||
|
||||
fn set_asset_resource_untyped(
|
||||
|
|
|
@ -24,13 +24,13 @@ pub struct WgpuRenderer {
|
|||
impl WgpuRenderer {
|
||||
pub async fn new(options: WgpuOptions) -> Self {
|
||||
let backend = match options.backend {
|
||||
WgpuBackend::Auto => wgpu::BackendBit::PRIMARY,
|
||||
WgpuBackend::Vulkan => wgpu::BackendBit::VULKAN,
|
||||
WgpuBackend::Metal => wgpu::BackendBit::METAL,
|
||||
WgpuBackend::Dx12 => wgpu::BackendBit::DX12,
|
||||
WgpuBackend::Dx11 => wgpu::BackendBit::DX11,
|
||||
WgpuBackend::Gl => wgpu::BackendBit::GL,
|
||||
WgpuBackend::BrowserWgpu => wgpu::BackendBit::BROWSER_WEBGPU,
|
||||
WgpuBackend::Auto => wgpu::Backends::PRIMARY,
|
||||
WgpuBackend::Vulkan => wgpu::Backends::VULKAN,
|
||||
WgpuBackend::Metal => wgpu::Backends::METAL,
|
||||
WgpuBackend::Dx12 => wgpu::Backends::DX12,
|
||||
WgpuBackend::Dx11 => wgpu::Backends::DX11,
|
||||
WgpuBackend::Gl => wgpu::Backends::GL,
|
||||
WgpuBackend::BrowserWgpu => wgpu::Backends::BROWSER_WEBGPU,
|
||||
};
|
||||
let instance = wgpu::Instance::new(backend);
|
||||
|
||||
|
@ -42,6 +42,7 @@ impl WgpuRenderer {
|
|||
WgpuPowerOptions::LowPower => wgpu::PowerPreference::LowPower,
|
||||
},
|
||||
compatible_surface: None,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.expect("Unable to find a GPU! Make sure you have installed required drivers!");
|
||||
|
@ -56,12 +57,14 @@ impl WgpuRenderer {
|
|||
#[cfg(not(feature = "trace"))]
|
||||
let trace_path = None;
|
||||
|
||||
let adapter_limits = adapter.limits();
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: options.device_label.as_ref().map(|a| a.as_ref()),
|
||||
features: options.features.wgpu_into(),
|
||||
limits: options.limits.wgpu_into(),
|
||||
limits: adapter_limits,
|
||||
},
|
||||
trace_path,
|
||||
)
|
||||
|
@ -129,7 +132,7 @@ impl WgpuRenderer {
|
|||
let render_resource_context = world
|
||||
.get_resource::<Box<dyn RenderResourceContext>>()
|
||||
.unwrap();
|
||||
render_resource_context.drop_all_swap_chain_textures();
|
||||
render_resource_context.drop_all_surface_frames();
|
||||
render_resource_context.remove_stale_bind_groups();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,8 @@ pub struct WgpuBindGroupInfo {
|
|||
pub struct WgpuResourcesReadLock<'a> {
|
||||
pub buffers: RwLockReadGuard<'a, HashMap<BufferId, Arc<wgpu::Buffer>>>,
|
||||
pub textures: RwLockReadGuard<'a, HashMap<TextureId, wgpu::TextureView>>,
|
||||
pub swap_chain_frames: RwLockReadGuard<'a, HashMap<TextureId, wgpu::SwapChainFrame>>,
|
||||
pub surface_textures:
|
||||
RwLockReadGuard<'a, HashMap<TextureId, (wgpu::TextureView, wgpu::SurfaceTexture)>>,
|
||||
pub render_pipelines:
|
||||
RwLockReadGuard<'a, HashMap<Handle<PipelineDescriptor>, wgpu::RenderPipeline>>,
|
||||
pub bind_groups: RwLockReadGuard<'a, HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>>,
|
||||
|
@ -59,7 +60,7 @@ impl<'a> WgpuResourcesReadLock<'a> {
|
|||
WgpuResourceRefs {
|
||||
buffers: &self.buffers,
|
||||
textures: &self.textures,
|
||||
swap_chain_frames: &self.swap_chain_frames,
|
||||
surface_textures: &self.surface_textures,
|
||||
render_pipelines: &self.render_pipelines,
|
||||
bind_groups: &self.bind_groups,
|
||||
used_bind_group_sender: &self.used_bind_group_sender,
|
||||
|
@ -73,7 +74,7 @@ impl<'a> WgpuResourcesReadLock<'a> {
|
|||
pub struct WgpuResourceRefs<'a> {
|
||||
pub buffers: &'a HashMap<BufferId, Arc<wgpu::Buffer>>,
|
||||
pub textures: &'a HashMap<TextureId, wgpu::TextureView>,
|
||||
pub swap_chain_frames: &'a HashMap<TextureId, wgpu::SwapChainFrame>,
|
||||
pub surface_textures: &'a HashMap<TextureId, (wgpu::TextureView, wgpu::SurfaceTexture)>,
|
||||
pub render_pipelines: &'a HashMap<Handle<PipelineDescriptor>, wgpu::RenderPipeline>,
|
||||
pub bind_groups: &'a HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>,
|
||||
pub used_bind_group_sender: &'a Sender<BindGroupId>,
|
||||
|
@ -84,8 +85,8 @@ pub struct WgpuResources {
|
|||
pub buffer_infos: Arc<RwLock<HashMap<BufferId, BufferInfo>>>,
|
||||
pub texture_descriptors: Arc<RwLock<HashMap<TextureId, TextureDescriptor>>>,
|
||||
pub window_surfaces: Arc<RwLock<HashMap<WindowId, wgpu::Surface>>>,
|
||||
pub window_swap_chains: Arc<RwLock<HashMap<WindowId, wgpu::SwapChain>>>,
|
||||
pub swap_chain_frames: Arc<RwLock<HashMap<TextureId, wgpu::SwapChainFrame>>>,
|
||||
pub surface_textures:
|
||||
Arc<RwLock<HashMap<TextureId, (wgpu::TextureView, wgpu::SurfaceTexture)>>>,
|
||||
pub buffers: Arc<RwLock<HashMap<BufferId, Arc<wgpu::Buffer>>>>,
|
||||
pub texture_views: Arc<RwLock<HashMap<TextureId, wgpu::TextureView>>>,
|
||||
pub textures: Arc<RwLock<HashMap<TextureId, wgpu::Texture>>>,
|
||||
|
@ -103,7 +104,7 @@ impl WgpuResources {
|
|||
WgpuResourcesReadLock {
|
||||
buffers: self.buffers.read(),
|
||||
textures: self.texture_views.read(),
|
||||
swap_chain_frames: self.swap_chain_frames.read(),
|
||||
surface_textures: self.surface_textures.read(),
|
||||
render_pipelines: self.render_pipelines.read(),
|
||||
bind_groups: self.bind_groups.read(),
|
||||
used_bind_group_sender: self.bind_group_counter.used_bind_group_sender.clone(),
|
||||
|
|
|
@ -13,7 +13,7 @@ use bevy_render::{
|
|||
texture::{
|
||||
AddressMode, Extent3d, FilterMode, SamplerBorderColor, SamplerDescriptor,
|
||||
StorageTextureAccess, TextureDescriptor, TextureDimension, TextureFormat,
|
||||
TextureSampleType, TextureUsage, TextureViewDimension,
|
||||
TextureSampleType, TextureUsages, TextureViewDimension,
|
||||
},
|
||||
};
|
||||
use bevy_window::Window;
|
||||
|
@ -83,11 +83,11 @@ impl WgpuFrom<&VertexAttribute> for wgpu::VertexAttribute {
|
|||
}
|
||||
}
|
||||
|
||||
impl WgpuFrom<InputStepMode> for wgpu::InputStepMode {
|
||||
impl WgpuFrom<InputStepMode> for wgpu::VertexStepMode {
|
||||
fn from(val: InputStepMode) -> Self {
|
||||
match val {
|
||||
InputStepMode::Vertex => wgpu::InputStepMode::Vertex,
|
||||
InputStepMode::Instance => wgpu::InputStepMode::Instance,
|
||||
InputStepMode::Vertex => wgpu::VertexStepMode::Vertex,
|
||||
InputStepMode::Instance => wgpu::VertexStepMode::Instance,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ impl WgpuFrom<InputStepMode> for wgpu::InputStepMode {
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct OwnedWgpuVertexBufferLayout {
|
||||
pub array_stride: wgpu::BufferAddress,
|
||||
pub step_mode: wgpu::InputStepMode,
|
||||
pub step_mode: wgpu::VertexStepMode,
|
||||
pub attributes: Vec<wgpu::VertexAttribute>,
|
||||
}
|
||||
|
||||
|
@ -137,9 +137,9 @@ impl WgpuFrom<Color> for wgpu::Color {
|
|||
}
|
||||
}
|
||||
|
||||
impl WgpuFrom<BufferUsage> for wgpu::BufferUsage {
|
||||
impl WgpuFrom<BufferUsage> for wgpu::BufferUsages {
|
||||
fn from(val: BufferUsage) -> Self {
|
||||
wgpu::BufferUsage::from_bits(val.bits()).unwrap()
|
||||
wgpu::BufferUsages::from_bits(val.bits()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,7 +190,9 @@ impl WgpuFrom<&BindType> for wgpu::BindingType {
|
|||
} => wgpu::BindingType::Buffer {
|
||||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: *has_dynamic_offset,
|
||||
min_binding_size: bind_type.get_uniform_size().and_then(wgpu::BufferSize::new),
|
||||
// FIXME: The line below cause a validation error
|
||||
// min_binding_size: bind_type.get_uniform_size().and_then(wgpu::BufferSize::new),
|
||||
min_binding_size: None,
|
||||
},
|
||||
BindType::StorageBuffer {
|
||||
has_dynamic_offset,
|
||||
|
@ -200,7 +202,9 @@ impl WgpuFrom<&BindType> for wgpu::BindingType {
|
|||
read_only: *readonly,
|
||||
},
|
||||
has_dynamic_offset: *has_dynamic_offset,
|
||||
min_binding_size: bind_type.get_uniform_size().and_then(wgpu::BufferSize::new),
|
||||
// FIXME: The line below cause a validation error
|
||||
// min_binding_size: bind_type.get_uniform_size().and_then(wgpu::BufferSize::new),
|
||||
min_binding_size: None,
|
||||
},
|
||||
BindType::Texture {
|
||||
view_dimension,
|
||||
|
@ -346,9 +350,9 @@ impl WgpuFrom<TextureFormat> for wgpu::TextureFormat {
|
|||
}
|
||||
}
|
||||
|
||||
impl WgpuFrom<TextureUsage> for wgpu::TextureUsage {
|
||||
fn from(val: TextureUsage) -> Self {
|
||||
wgpu::TextureUsage::from_bits(val.bits()).unwrap()
|
||||
impl WgpuFrom<TextureUsages> for wgpu::TextureUsages {
|
||||
fn from(val: TextureUsages) -> Self {
|
||||
wgpu::TextureUsages::from_bits(val.bits()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -526,9 +530,9 @@ impl WgpuFrom<PrimitiveState> for wgpu::PrimitiveState {
|
|||
}
|
||||
}
|
||||
|
||||
impl WgpuFrom<ColorWrite> for wgpu::ColorWrite {
|
||||
impl WgpuFrom<ColorWrite> for wgpu::ColorWrites {
|
||||
fn from(val: ColorWrite) -> Self {
|
||||
wgpu::ColorWrite::from_bits(val.bits()).unwrap()
|
||||
wgpu::ColorWrites::from_bits(val.bits()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -640,10 +644,10 @@ impl WgpuFrom<SamplerBorderColor> for wgpu::SamplerBorderColor {
|
|||
}
|
||||
}
|
||||
|
||||
impl WgpuFrom<&Window> for wgpu::SwapChainDescriptor {
|
||||
impl WgpuFrom<&Window> for wgpu::SurfaceConfiguration {
|
||||
fn from(window: &Window) -> Self {
|
||||
wgpu::SwapChainDescriptor {
|
||||
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
||||
wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: TextureFormat::default().wgpu_into(),
|
||||
width: window.physical_width().max(1),
|
||||
height: window.physical_height().max(1),
|
||||
|
@ -664,21 +668,12 @@ impl WgpuFrom<WgpuFeature> for wgpu::Features {
|
|||
WgpuFeature::TimestampQuery => wgpu::Features::TIMESTAMP_QUERY,
|
||||
WgpuFeature::PipelineStatisticsQuery => wgpu::Features::PIPELINE_STATISTICS_QUERY,
|
||||
WgpuFeature::MappablePrimaryBuffers => wgpu::Features::MAPPABLE_PRIMARY_BUFFERS,
|
||||
WgpuFeature::SampledTextureBindingArray => {
|
||||
wgpu::Features::SAMPLED_TEXTURE_BINDING_ARRAY
|
||||
}
|
||||
WgpuFeature::SampledTextureArrayDynamicIndexing => {
|
||||
wgpu::Features::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING
|
||||
}
|
||||
WgpuFeature::SampledTextureArrayNonUniformIndexing => {
|
||||
wgpu::Features::SAMPLED_TEXTURE_ARRAY_NON_UNIFORM_INDEXING
|
||||
}
|
||||
WgpuFeature::UnsizedBindingArray => wgpu::Features::UNSIZED_BINDING_ARRAY,
|
||||
WgpuFeature::MultiDrawIndirect => wgpu::Features::MULTI_DRAW_INDIRECT,
|
||||
WgpuFeature::MultiDrawIndirectCount => wgpu::Features::MULTI_DRAW_INDIRECT_COUNT,
|
||||
WgpuFeature::PushConstants => wgpu::Features::PUSH_CONSTANTS,
|
||||
WgpuFeature::AddressModeClampToBorder => wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER,
|
||||
WgpuFeature::NonFillPolygonMode => wgpu::Features::NON_FILL_POLYGON_MODE,
|
||||
WgpuFeature::PolygonModeLine => wgpu::Features::POLYGON_MODE_LINE,
|
||||
WgpuFeature::TextureCompressionEtc2 => wgpu::Features::TEXTURE_COMPRESSION_ETC2,
|
||||
WgpuFeature::TextureCompressionAstcLdr => wgpu::Features::TEXTURE_COMPRESSION_ASTC_LDR,
|
||||
WgpuFeature::TextureAdapterSpecificFormatFeatures => {
|
||||
|
@ -724,6 +719,8 @@ impl WgpuFrom<WgpuLimits> for wgpu::Limits {
|
|||
max_vertex_buffers: val.max_vertex_buffers,
|
||||
max_vertex_attributes: val.max_vertex_attributes,
|
||||
max_vertex_buffer_array_stride: val.max_vertex_buffer_array_stride,
|
||||
min_storage_buffer_offset_alignment: val.min_storage_buffer_offset_alignment,
|
||||
min_uniform_buffer_offset_alignment: val.min_uniform_buffer_offset_alignment,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ keywords = ["bevy"]
|
|||
bevy_app = { path = "../bevy_app", version = "0.5.0" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.5.0" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
|
||||
raw-window-handle = "0.3.0"
|
||||
|
||||
# other
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
mod event;
|
||||
mod raw_window_handle;
|
||||
mod system;
|
||||
mod window;
|
||||
mod windows;
|
||||
|
||||
pub use crate::raw_window_handle::*;
|
||||
pub use event::*;
|
||||
pub use system::*;
|
||||
pub use window::*;
|
||||
|
|
37
crates/bevy_window/src/raw_window_handle.rs
Normal file
37
crates/bevy_window/src/raw_window_handle.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||
|
||||
/// This wrapper exist to enable safely passing a [`RawWindowHandle`] across threads. Extracting the handle
|
||||
/// is still an unsafe operation, so the caller must still validate that using the raw handle is safe for a given context.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RawWindowHandleWrapper(RawWindowHandle);
|
||||
|
||||
impl RawWindowHandleWrapper {
|
||||
pub(crate) fn new(handle: RawWindowHandle) -> Self {
|
||||
Self(handle)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This returns a [`HasRawWindowHandle`] impl, which exposes [`RawWindowHandle`]. Some platforms
|
||||
/// have constraints on where/how this handle can be used. For example, some platforms don't support doing window
|
||||
/// operations off of the main thread. The caller must ensure the [`RawWindowHandle`] is only used in valid contexts.
|
||||
pub unsafe fn get_handle(&self) -> HasRawWindowHandleWrapper {
|
||||
HasRawWindowHandleWrapper(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
// SAFE: RawWindowHandle is just a normal "raw pointer", which doesn't impl Send/Sync. However the pointer is only
|
||||
// exposed via an unsafe method that forces the user to make a call for a given platform. (ex: some platforms don't
|
||||
// support doing window operations off of the main thread).
|
||||
// A recommendation for this pattern (and more context) is available here:
|
||||
// https://github.com/rust-windowing/raw-window-handle/issues/59
|
||||
unsafe impl Send for RawWindowHandleWrapper {}
|
||||
unsafe impl Sync for RawWindowHandleWrapper {}
|
||||
|
||||
pub struct HasRawWindowHandleWrapper(RawWindowHandle);
|
||||
|
||||
// SAFE: the caller has validated that this is a valid context to get RawWindowHandle
|
||||
unsafe impl HasRawWindowHandle for HasRawWindowHandleWrapper {
|
||||
fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
self.0
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use bevy_math::{DVec2, IVec2, Vec2};
|
||||
use bevy_utils::{tracing::warn, Uuid};
|
||||
use raw_window_handle::RawWindowHandle;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct WindowId(Uuid);
|
||||
|
@ -20,6 +21,8 @@ impl WindowId {
|
|||
|
||||
use std::fmt;
|
||||
|
||||
use crate::raw_window_handle::RawWindowHandleWrapper;
|
||||
|
||||
impl fmt::Display for WindowId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.to_simple().fmt(f)
|
||||
|
@ -123,6 +126,7 @@ pub struct Window {
|
|||
cursor_visible: bool,
|
||||
cursor_locked: bool,
|
||||
physical_cursor_position: Option<DVec2>,
|
||||
raw_window_handle: RawWindowHandleWrapper,
|
||||
focused: bool,
|
||||
mode: WindowMode,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
@ -198,6 +202,7 @@ impl Window {
|
|||
physical_height: u32,
|
||||
scale_factor: f64,
|
||||
position: Option<IVec2>,
|
||||
raw_window_handle: RawWindowHandle,
|
||||
) -> Self {
|
||||
Window {
|
||||
id,
|
||||
|
@ -216,6 +221,7 @@ impl Window {
|
|||
cursor_visible: window_descriptor.cursor_visible,
|
||||
cursor_locked: window_descriptor.cursor_locked,
|
||||
physical_cursor_position: None,
|
||||
raw_window_handle: RawWindowHandleWrapper::new(raw_window_handle),
|
||||
focused: true,
|
||||
mode: window_descriptor.mode,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
@ -519,6 +525,10 @@ impl Window {
|
|||
pub fn is_focused(&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandleWrapper {
|
||||
self.raw_window_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -24,6 +24,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
|
|||
# other
|
||||
winit = { version = "0.25.0", default-features = false }
|
||||
approx = { version = "0.5.0", default-features = false }
|
||||
raw-window-handle = "0.3.0"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
winit = { version = "0.25.0", features = ["web-sys"], default-features = false }
|
||||
|
|
|
@ -26,14 +26,6 @@ use winit::{
|
|||
};
|
||||
|
||||
use winit::dpi::LogicalSize;
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
use winit::platform::unix::EventLoopExtUnix;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WinitPlugin;
|
||||
|
@ -43,6 +35,9 @@ impl Plugin for WinitPlugin {
|
|||
app.init_resource::<WinitWindows>()
|
||||
.set_runner(winit_runner)
|
||||
.add_system_to_stage(CoreStage::PostUpdate, change_window.exclusive_system());
|
||||
let event_loop = EventLoop::new();
|
||||
handle_initial_window_events(&mut app.world, &event_loop);
|
||||
app.insert_non_send_resource(event_loop);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,21 +202,22 @@ where
|
|||
}
|
||||
|
||||
pub fn winit_runner(app: App) {
|
||||
winit_runner_with(app, EventLoop::new());
|
||||
winit_runner_with(app);
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
pub fn winit_runner_any_thread(app: App) {
|
||||
winit_runner_with(app, EventLoop::new_any_thread());
|
||||
}
|
||||
// #[cfg(any(
|
||||
// target_os = "linux",
|
||||
// target_os = "dragonfly",
|
||||
// target_os = "freebsd",
|
||||
// target_os = "netbsd",
|
||||
// target_os = "openbsd"
|
||||
// ))]
|
||||
// pub fn winit_runner_any_thread(app: App) {
|
||||
// winit_runner_with(app, EventLoop::new_any_thread());
|
||||
// }
|
||||
|
||||
pub fn winit_runner_with(mut app: App, mut event_loop: EventLoop<()>) {
|
||||
pub fn winit_runner_with(mut app: App) {
|
||||
let mut event_loop = app.world.remove_non_send::<EventLoop<()>>().unwrap();
|
||||
let mut create_window_event_reader = ManualEventReader::<CreateWindow>::default();
|
||||
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
|
||||
app.world.insert_non_send(event_loop.create_proxy());
|
||||
|
@ -534,3 +530,22 @@ fn handle_create_window_events(
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_initial_window_events(world: &mut World, event_loop: &EventLoop<()>) {
|
||||
let world = world.cell();
|
||||
let mut winit_windows = world.get_resource_mut::<WinitWindows>().unwrap();
|
||||
let mut windows = world.get_resource_mut::<Windows>().unwrap();
|
||||
let mut create_window_events = world.get_resource_mut::<Events<CreateWindow>>().unwrap();
|
||||
let mut window_created_events = world.get_resource_mut::<Events<WindowCreated>>().unwrap();
|
||||
for create_window_event in create_window_events.drain() {
|
||||
let window = winit_windows.create_window(
|
||||
event_loop,
|
||||
create_window_event.id,
|
||||
&create_window_event.descriptor,
|
||||
);
|
||||
windows.add(window);
|
||||
window_created_events.send(WindowCreated {
|
||||
id: create_window_event.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use bevy_math::IVec2;
|
||||
use bevy_utils::HashMap;
|
||||
use bevy_window::{Window, WindowDescriptor, WindowId, WindowMode};
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
use winit::dpi::LogicalSize;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -156,6 +157,7 @@ impl WinitWindows {
|
|||
.map(|position| IVec2::new(position.x, position.y));
|
||||
let inner_size = winit_window.inner_size();
|
||||
let scale_factor = winit_window.scale_factor();
|
||||
let raw_window_handle = winit_window.raw_window_handle();
|
||||
self.windows.insert(winit_window.id(), winit_window);
|
||||
Window::new(
|
||||
window_id,
|
||||
|
@ -164,6 +166,7 @@ impl WinitWindows {
|
|||
inner_size.height,
|
||||
scale_factor,
|
||||
position,
|
||||
raw_window_handle,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
36
crates/crevice/Cargo.toml
Normal file
36
crates/crevice/Cargo.toml
Normal file
|
@ -0,0 +1,36 @@
|
|||
[package]
|
||||
name = "crevice"
|
||||
description = "Create GLSL-compatible versions of structs with explicitly-initialized padding"
|
||||
version = "0.8.0"
|
||||
edition = "2021"
|
||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||
documentation = "https://docs.rs/crevice"
|
||||
homepage = "https://github.com/LPGhatguy/crevice"
|
||||
repository = "https://github.com/LPGhatguy/crevice"
|
||||
readme = "README.md"
|
||||
keywords = ["glsl", "std140", "std430"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
# resolver = "2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
|
||||
# [workspace]
|
||||
# members = ["crevice-derive", "crevice-tests"]
|
||||
# default-members = ["crevice-derive", "crevice-tests"]
|
||||
|
||||
[dependencies]
|
||||
crevice-derive = { version = "0.8.0", path = "crevice-derive" }
|
||||
|
||||
bytemuck = "1.4.1"
|
||||
mint = "0.5.8"
|
||||
|
||||
cgmath = { version = "0.18.0", optional = true }
|
||||
glam = { version = "0.20.0", features = ["mint"], optional = true }
|
||||
nalgebra = { version = "0.29.0", features = ["mint"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = "0.16.1"
|
201
crates/crevice/LICENSE-APACHE
Normal file
201
crates/crevice/LICENSE-APACHE
Normal file
|
@ -0,0 +1,201 @@
|
|||
i Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
19
crates/crevice/LICENSE-MIT
Normal file
19
crates/crevice/LICENSE-MIT
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2020 Lucien Greathouse
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
170
crates/crevice/README.md
Normal file
170
crates/crevice/README.md
Normal file
|
@ -0,0 +1,170 @@
|
|||
# Crevice
|
||||
|
||||
[![GitHub CI Status](https://github.com/LPGhatguy/crevice/workflows/CI/badge.svg)](https://github.com/LPGhatguy/crevice/actions)
|
||||
[![crevice on crates.io](https://img.shields.io/crates/v/crevice.svg)](https://crates.io/crates/crevice)
|
||||
[![crevice docs](https://img.shields.io/badge/docs-docs.rs-orange.svg)](https://docs.rs/crevice)
|
||||
|
||||
Crevice creates GLSL-compatible versions of types through the power of derive
|
||||
macros. Generated structures provide an [`as_bytes`][std140::Std140::as_bytes]
|
||||
method to allow safely packing data into buffers for uploading.
|
||||
|
||||
Generated structs also implement [`bytemuck::Zeroable`] and
|
||||
[`bytemuck::Pod`] for use with other libraries.
|
||||
|
||||
Crevice is similar to [`glsl-layout`][glsl-layout], but supports types from many
|
||||
math crates, can generate GLSL source from structs, and explicitly initializes
|
||||
padding to remove one source of undefined behavior.
|
||||
|
||||
Crevice has support for many Rust math libraries via feature flags, and most
|
||||
other math libraries by use of the mint crate. Crevice currently supports:
|
||||
|
||||
* mint 0.5, enabled by default
|
||||
* cgmath 0.18, using the `cgmath` feature
|
||||
* nalgebra 0.29, using the `nalgebra` feature
|
||||
* glam 0.19, using the `glam` feature
|
||||
|
||||
PRs are welcome to add or update math libraries to Crevice.
|
||||
|
||||
If your math library is not supported, it's possible to define structs using the
|
||||
types from mint and convert your math library's types into mint types. This is
|
||||
supported by most Rust math libraries.
|
||||
|
||||
Your math library may require you to turn on a feature flag to get mint support.
|
||||
For example, cgmath requires the "mint" feature to be enabled to allow
|
||||
conversions to and from mint types.
|
||||
|
||||
## Examples
|
||||
|
||||
### Single Value
|
||||
|
||||
Uploading many types can be done by deriving [`AsStd140`][std140::AsStd140] and
|
||||
using [`as_std140`][std140::AsStd140::as_std140] and
|
||||
[`as_bytes`][std140::Std140::as_bytes] to turn the result into bytes.
|
||||
|
||||
```glsl
|
||||
uniform MAIN {
|
||||
mat3 orientation;
|
||||
vec3 position;
|
||||
float scale;
|
||||
} main;
|
||||
```
|
||||
|
||||
```rust
|
||||
use crevice::std140::{AsStd140, Std140};
|
||||
|
||||
#[derive(AsStd140)]
|
||||
struct MainUniform {
|
||||
orientation: mint::ColumnMatrix3<f32>,
|
||||
position: mint::Vector3<f32>,
|
||||
scale: f32,
|
||||
}
|
||||
|
||||
let value = MainUniform {
|
||||
orientation: [
|
||||
[1.0, 0.0, 0.0],
|
||||
[0.0, 1.0, 0.0],
|
||||
[0.0, 0.0, 1.0],
|
||||
].into(),
|
||||
position: [1.0, 2.0, 3.0].into(),
|
||||
scale: 4.0,
|
||||
};
|
||||
|
||||
let value_std140 = value.as_std140();
|
||||
|
||||
upload_data_to_gpu(value_std140.as_bytes());
|
||||
```
|
||||
|
||||
### Sequential Types
|
||||
|
||||
More complicated data can be uploaded using the std140
|
||||
[`Writer`][std140::Writer] type.
|
||||
|
||||
```glsl
|
||||
struct PointLight {
|
||||
vec3 position;
|
||||
vec3 color;
|
||||
float brightness;
|
||||
};
|
||||
|
||||
buffer POINT_LIGHTS {
|
||||
uint len;
|
||||
PointLight[] lights;
|
||||
} point_lights;
|
||||
```
|
||||
|
||||
```rust
|
||||
use crevice::std140::{self, AsStd140};
|
||||
|
||||
#[derive(AsStd140)]
|
||||
struct PointLight {
|
||||
position: mint::Vector3<f32>,
|
||||
color: mint::Vector3<f32>,
|
||||
brightness: f32,
|
||||
}
|
||||
|
||||
let lights = vec![
|
||||
PointLight {
|
||||
position: [0.0, 1.0, 0.0].into(),
|
||||
color: [1.0, 0.0, 0.0].into(),
|
||||
brightness: 0.6,
|
||||
},
|
||||
PointLight {
|
||||
position: [0.0, 4.0, 3.0].into(),
|
||||
color: [1.0, 1.0, 1.0].into(),
|
||||
brightness: 1.0,
|
||||
},
|
||||
];
|
||||
|
||||
let target_buffer = map_gpu_buffer_for_write();
|
||||
let mut writer = std140::Writer::new(target_buffer);
|
||||
|
||||
let light_count = lights.len() as u32;
|
||||
writer.write(&light_count)?;
|
||||
|
||||
// Crevice will automatically insert the required padding to align the
|
||||
// PointLight structure correctly. In this case, there will be 12 bytes of
|
||||
// padding between the length field and the light list.
|
||||
|
||||
writer.write(lights.as_slice())?;
|
||||
|
||||
unmap_gpu_buffer();
|
||||
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
* `std` (default): Enables [`std::io::Write`]-based structs.
|
||||
* `cgmath`: Enables support for types from cgmath.
|
||||
* `nalgebra`: Enables support for types from nalgebra.
|
||||
* `glam`: Enables support for types from glam.
|
||||
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
|
||||
Crevice supports Rust 1.52.1 and newer due to use of new `const fn` features.
|
||||
|
||||
[glsl-layout]: https://github.com/rustgd/glsl-layout
|
||||
|
||||
[std140::AsStd140]: https://docs.rs/crevice/latest/crevice/std140/trait.AsStd140.html
|
||||
[std140::AsStd140::as_std140]: https://docs.rs/crevice/latest/crevice/std140/trait.AsStd140.html#method.as_std140
|
||||
[std140::Std140::as_bytes]: https://docs.rs/crevice/latest/crevice/std140/trait.Std140.html#method.as_bytes
|
||||
[std140::Writer]: https://docs.rs/crevice/latest/crevice/std140/struct.Writer.html
|
||||
|
||||
[`std::io::Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html
|
||||
|
||||
[`bytemuck::Pod`]: https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html
|
||||
[`bytemuck::Zeroable`]: https://docs.rs/bytemuck/latest/bytemuck/trait.Zeroable.html
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](http://www.apache.org/licenses/LICENSE-2.0))
|
||||
* MIT license ([LICENSE-MIT](http://opensource.org/licenses/MIT))
|
||||
|
||||
at your option.
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in the work by you, as defined in the Apache-2.0 license, shall
|
||||
be dual licensed as above, without any additional terms or conditions.
|
25
crates/crevice/README.tpl
Normal file
25
crates/crevice/README.tpl
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Crevice
|
||||
|
||||
{{readme}}
|
||||
|
||||
[std140::AsStd140]: https://docs.rs/crevice/latest/crevice/std140/trait.AsStd140.html
|
||||
[std140::AsStd140::as_std140]: https://docs.rs/crevice/latest/crevice/std140/trait.AsStd140.html#method.as_std140
|
||||
[std140::Std140::as_bytes]: https://docs.rs/crevice/latest/crevice/std140/trait.Std140.html#method.as_bytes
|
||||
[std140::Writer]: https://docs.rs/crevice/latest/crevice/std140/struct.Writer.html
|
||||
|
||||
[`std::io::Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html
|
||||
|
||||
[`bytemuck::Pod`]: https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html
|
||||
[`bytemuck::Zeroable`]: https://docs.rs/bytemuck/latest/bytemuck/trait.Zeroable.html
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
### Contribution
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
|
26
crates/crevice/crevice-derive/Cargo.toml
Normal file
26
crates/crevice/crevice-derive/Cargo.toml
Normal file
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "crevice-derive"
|
||||
description = "Derive crate for the 'crevice' crate"
|
||||
version = "0.8.0"
|
||||
edition = "2018"
|
||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||
documentation = "https://docs.rs/crevice-derive"
|
||||
homepage = "https://github.com/LPGhatguy/crevice"
|
||||
repository = "https://github.com/LPGhatguy/crevice"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# Enable methods that let you introspect into the generated structs.
|
||||
debug-methods = []
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
syn = "1.0.40"
|
||||
quote = "1.0.7"
|
||||
proc-macro2 = "1.0.21"
|
49
crates/crevice/crevice-derive/src/glsl.rs
Normal file
49
crates/crevice/crevice-derive/src/glsl.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use proc_macro2::{Literal, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::{parse_quote, Data, DeriveInput, Fields, Path};
|
||||
|
||||
pub fn emit(input: DeriveInput) -> TokenStream {
|
||||
let fields = match &input.data {
|
||||
Data::Struct(data) => match &data.fields {
|
||||
Fields::Named(fields) => fields,
|
||||
Fields::Unnamed(_) => panic!("Tuple structs are not supported"),
|
||||
Fields::Unit => panic!("Unit structs are not supported"),
|
||||
},
|
||||
Data::Enum(_) | Data::Union(_) => panic!("Only structs are supported"),
|
||||
};
|
||||
|
||||
let base_trait_path: Path = parse_quote!(::crevice::glsl::Glsl);
|
||||
let struct_trait_path: Path = parse_quote!(::crevice::glsl::GlslStruct);
|
||||
|
||||
let name = input.ident;
|
||||
let name_str = Literal::string(&name.to_string());
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
|
||||
let glsl_fields = fields.named.iter().map(|field| {
|
||||
let field_ty = &field.ty;
|
||||
let field_name_str = Literal::string(&field.ident.as_ref().unwrap().to_string());
|
||||
let field_as = quote! {<#field_ty as ::crevice::glsl::GlslArray>};
|
||||
|
||||
quote! {
|
||||
s.push_str("\t");
|
||||
s.push_str(#field_as::NAME);
|
||||
s.push_str(" ");
|
||||
s.push_str(#field_name_str);
|
||||
<#field_as::ArraySize as ::crevice::glsl::DimensionList>::push_to_string(s);
|
||||
s.push_str(";\n");
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
unsafe impl #impl_generics #base_trait_path for #name #ty_generics #where_clause {
|
||||
const NAME: &'static str = #name_str;
|
||||
}
|
||||
|
||||
unsafe impl #impl_generics #struct_trait_path for #name #ty_generics #where_clause {
|
||||
fn enumerate_fields(s: &mut String) {
|
||||
#( #glsl_fields )*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
288
crates/crevice/crevice-derive/src/layout.rs
Normal file
288
crates/crevice/crevice-derive/src/layout.rs
Normal file
|
@ -0,0 +1,288 @@
|
|||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{parse_quote, Data, DeriveInput, Fields, Ident, Path, Type};
|
||||
|
||||
pub fn emit(
|
||||
input: DeriveInput,
|
||||
trait_name: &'static str,
|
||||
mod_name: &'static str,
|
||||
min_struct_alignment: usize,
|
||||
) -> TokenStream {
|
||||
let mod_name = Ident::new(mod_name, Span::call_site());
|
||||
let trait_name = Ident::new(trait_name, Span::call_site());
|
||||
|
||||
let mod_path: Path = parse_quote!(::crevice::#mod_name);
|
||||
let trait_path: Path = parse_quote!(#mod_path::#trait_name);
|
||||
|
||||
let as_trait_name = format_ident!("As{}", trait_name);
|
||||
let as_trait_path: Path = parse_quote!(#mod_path::#as_trait_name);
|
||||
let as_trait_method = format_ident!("as_{}", mod_name);
|
||||
let from_trait_method = format_ident!("from_{}", mod_name);
|
||||
|
||||
let padded_name = format_ident!("{}Padded", trait_name);
|
||||
let padded_path: Path = parse_quote!(#mod_path::#padded_name);
|
||||
|
||||
let visibility = input.vis;
|
||||
let input_name = input.ident;
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
|
||||
let generated_name = format_ident!("{}{}", trait_name, input_name);
|
||||
|
||||
// Crevice's derive only works on regular structs. We could potentially
|
||||
// support transparent tuple structs in the future.
|
||||
let fields: Vec<_> = match &input.data {
|
||||
Data::Struct(data) => match &data.fields {
|
||||
Fields::Named(fields) => fields.named.iter().collect(),
|
||||
Fields::Unnamed(_) => panic!("Tuple structs are not supported"),
|
||||
Fields::Unit => panic!("Unit structs are not supported"),
|
||||
},
|
||||
Data::Enum(_) | Data::Union(_) => panic!("Only structs are supported"),
|
||||
};
|
||||
|
||||
// Gives the layout-specific version of the given type.
|
||||
let layout_version_of_ty = |ty: &Type| {
|
||||
quote! {
|
||||
<#ty as #as_trait_path>::Output
|
||||
}
|
||||
};
|
||||
|
||||
// Gives an expression returning the layout-specific alignment for the type.
|
||||
let layout_alignment_of_ty = |ty: &Type| {
|
||||
quote! {
|
||||
<<#ty as #as_trait_path>::Output as #trait_path>::ALIGNMENT
|
||||
}
|
||||
};
|
||||
|
||||
// Gives an expression telling whether the type should have trailing padding
|
||||
// at least equal to its alignment.
|
||||
let layout_pad_at_end_of_ty = |ty: &Type| {
|
||||
quote! {
|
||||
<<#ty as #as_trait_path>::Output as #trait_path>::PAD_AT_END
|
||||
}
|
||||
};
|
||||
|
||||
let field_alignments = fields.iter().map(|field| layout_alignment_of_ty(&field.ty));
|
||||
let struct_alignment = quote! {
|
||||
::crevice::internal::max_arr([
|
||||
#min_struct_alignment,
|
||||
#(#field_alignments,)*
|
||||
])
|
||||
};
|
||||
|
||||
// Generate names for each padding calculation function.
|
||||
let pad_fns: Vec<_> = (0..fields.len())
|
||||
.map(|index| format_ident!("_{}__{}Pad{}", input_name, trait_name, index))
|
||||
.collect();
|
||||
|
||||
// Computes the offset immediately AFTER the field with the given index.
|
||||
//
|
||||
// This function depends on the generated padding calculation functions to
|
||||
// do correct alignment. Be careful not to cause recursion!
|
||||
let offset_after_field = |target: usize| {
|
||||
let mut output = vec![quote!(0usize)];
|
||||
|
||||
for index in 0..=target {
|
||||
let field_ty = &fields[index].ty;
|
||||
let layout_ty = layout_version_of_ty(field_ty);
|
||||
|
||||
output.push(quote! {
|
||||
+ ::core::mem::size_of::<#layout_ty>()
|
||||
});
|
||||
|
||||
// For every field except our target field, also add the generated
|
||||
// padding. Padding occurs after each field, so it isn't included in
|
||||
// this value.
|
||||
if index < target {
|
||||
let pad_fn = &pad_fns[index];
|
||||
output.push(quote! {
|
||||
+ #pad_fn()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
output.into_iter().collect::<TokenStream>()
|
||||
};
|
||||
|
||||
let pad_fn_impls: TokenStream = fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, prev_field)| {
|
||||
let pad_fn = &pad_fns[index];
|
||||
|
||||
let starting_offset = offset_after_field(index);
|
||||
let prev_field_has_end_padding = layout_pad_at_end_of_ty(&prev_field.ty);
|
||||
let prev_field_alignment = layout_alignment_of_ty(&prev_field.ty);
|
||||
|
||||
let next_field_or_self_alignment = fields
|
||||
.get(index + 1)
|
||||
.map(|next_field| layout_alignment_of_ty(&next_field.ty))
|
||||
.unwrap_or(quote!(#struct_alignment));
|
||||
|
||||
quote! {
|
||||
/// Tells how many bytes of padding have to be inserted after
|
||||
/// the field with index #index.
|
||||
#[allow(non_snake_case)]
|
||||
const fn #pad_fn() -> usize {
|
||||
// First up, calculate our offset into the struct so far.
|
||||
// We'll use this value to figure out how far out of
|
||||
// alignment we are.
|
||||
let starting_offset = #starting_offset;
|
||||
|
||||
// If the previous field is a struct or array, we must align
|
||||
// the next field to at least THAT field's alignment.
|
||||
let min_alignment = if #prev_field_has_end_padding {
|
||||
#prev_field_alignment
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// We set our target alignment to the larger of the
|
||||
// alignment due to the previous field and the alignment
|
||||
// requirement of the next field.
|
||||
let alignment = ::crevice::internal::max(
|
||||
#next_field_or_self_alignment,
|
||||
min_alignment,
|
||||
);
|
||||
|
||||
// Using everything we've got, compute our padding amount.
|
||||
::crevice::internal::align_offset(starting_offset, alignment)
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let generated_struct_fields: TokenStream = fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, field)| {
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
let field_ty = layout_version_of_ty(&field.ty);
|
||||
let pad_field_name = format_ident!("_pad{}", index);
|
||||
let pad_fn = &pad_fns[index];
|
||||
|
||||
quote! {
|
||||
#field_name: #field_ty,
|
||||
#pad_field_name: [u8; #pad_fn()],
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let generated_struct_field_init: TokenStream = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
|
||||
quote! {
|
||||
#field_name: self.#field_name.#as_trait_method(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let input_struct_field_init: TokenStream = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
|
||||
quote! {
|
||||
#field_name: #as_trait_path::#from_trait_method(input.#field_name),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let struct_definition = quote! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
#[allow(non_snake_case)]
|
||||
#visibility struct #generated_name #ty_generics #where_clause {
|
||||
#generated_struct_fields
|
||||
}
|
||||
};
|
||||
|
||||
let debug_methods = if cfg!(feature = "debug-methods") {
|
||||
let debug_fields: TokenStream = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let field_name = field.ident.as_ref().unwrap();
|
||||
let field_ty = &field.ty;
|
||||
|
||||
quote! {
|
||||
fields.push(Field {
|
||||
name: stringify!(#field_name),
|
||||
size: ::core::mem::size_of::<#field_ty>(),
|
||||
offset: (&zeroed.#field_name as *const _ as usize)
|
||||
- (&zeroed as *const _ as usize),
|
||||
});
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
quote! {
|
||||
impl #impl_generics #generated_name #ty_generics #where_clause {
|
||||
fn debug_metrics() -> String {
|
||||
let size = ::core::mem::size_of::<Self>();
|
||||
let align = <Self as #trait_path>::ALIGNMENT;
|
||||
|
||||
let zeroed: Self = ::crevice::internal::bytemuck::Zeroable::zeroed();
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Field {
|
||||
name: &'static str,
|
||||
offset: usize,
|
||||
size: usize,
|
||||
}
|
||||
let mut fields = Vec::new();
|
||||
|
||||
#debug_fields
|
||||
|
||||
format!("Size {}, Align {}, fields: {:#?}", size, align, fields)
|
||||
}
|
||||
|
||||
fn debug_definitions() -> &'static str {
|
||||
stringify!(
|
||||
#struct_definition
|
||||
#pad_fn_impls
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
quote! {
|
||||
#pad_fn_impls
|
||||
#struct_definition
|
||||
|
||||
unsafe impl #impl_generics ::crevice::internal::bytemuck::Zeroable for #generated_name #ty_generics #where_clause {}
|
||||
unsafe impl #impl_generics ::crevice::internal::bytemuck::Pod for #generated_name #ty_generics #where_clause {}
|
||||
|
||||
unsafe impl #impl_generics #mod_path::#trait_name for #generated_name #ty_generics #where_clause {
|
||||
const ALIGNMENT: usize = #struct_alignment;
|
||||
const PAD_AT_END: bool = true;
|
||||
type Padded = #padded_path<Self, {::crevice::internal::align_offset(
|
||||
::core::mem::size_of::<#generated_name>(),
|
||||
#struct_alignment
|
||||
)}>;
|
||||
}
|
||||
|
||||
impl #impl_generics #as_trait_path for #input_name #ty_generics #where_clause {
|
||||
type Output = #generated_name;
|
||||
|
||||
fn #as_trait_method(&self) -> Self::Output {
|
||||
Self::Output {
|
||||
#generated_struct_field_init
|
||||
|
||||
..::crevice::internal::bytemuck::Zeroable::zeroed()
|
||||
}
|
||||
}
|
||||
|
||||
fn #from_trait_method(input: Self::Output) -> Self {
|
||||
Self {
|
||||
#input_struct_field_init
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#debug_methods
|
||||
}
|
||||
}
|
30
crates/crevice/crevice-derive/src/lib.rs
Normal file
30
crates/crevice/crevice-derive/src/lib.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
mod glsl;
|
||||
mod layout;
|
||||
|
||||
use proc_macro::TokenStream as CompilerTokenStream;
|
||||
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
|
||||
#[proc_macro_derive(AsStd140)]
|
||||
pub fn derive_as_std140(input: CompilerTokenStream) -> CompilerTokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let expanded = layout::emit(input, "Std140", "std140", 16);
|
||||
|
||||
CompilerTokenStream::from(expanded)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(AsStd430)]
|
||||
pub fn derive_as_std430(input: CompilerTokenStream) -> CompilerTokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let expanded = layout::emit(input, "Std430", "std430", 0);
|
||||
|
||||
CompilerTokenStream::from(expanded)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(GlslStruct)]
|
||||
pub fn derive_glsl_struct(input: CompilerTokenStream) -> CompilerTokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let expanded = glsl::emit(input);
|
||||
|
||||
CompilerTokenStream::from(expanded)
|
||||
}
|
20
crates/crevice/crevice-tests/Cargo.toml
Normal file
20
crates/crevice/crevice-tests/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "crevice-tests"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
wgpu-validation = ["wgpu", "naga", "futures"]
|
||||
|
||||
[dependencies]
|
||||
crevice = { path = ".." }
|
||||
crevice-derive = { path = "../crevice-derive", features = ["debug-methods"] }
|
||||
|
||||
anyhow = "1.0.44"
|
||||
bytemuck = "1.7.2"
|
||||
memoffset = "0.6.4"
|
||||
mint = "0.5.5"
|
||||
|
||||
futures = { version = "0.3.17", features = ["executor"], optional = true }
|
||||
naga = { version = "0.7.0", features = ["glsl-in", "wgsl-out"], optional = true }
|
||||
wgpu = { version = "0.11.0", optional = true }
|
268
crates/crevice/crevice-tests/src/gpu.rs
Normal file
268
crates/crevice/crevice-tests/src/gpu.rs
Normal file
|
@ -0,0 +1,268 @@
|
|||
use std::borrow::Cow;
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crevice::glsl::{Glsl, GlslStruct};
|
||||
use crevice::std140::{AsStd140, Std140};
|
||||
use crevice::std430::{AsStd430, Std430};
|
||||
use futures::executor::block_on;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
const BASE_SHADER: &str = "#version 450
|
||||
|
||||
{struct_definition}
|
||||
|
||||
layout({layout}, set = 0, binding = 0) readonly buffer INPUT {
|
||||
{struct_name} in_data;
|
||||
};
|
||||
|
||||
layout({layout}, set = 0, binding = 1) buffer OUTPUT {
|
||||
{struct_name} out_data;
|
||||
};
|
||||
|
||||
void main() {
|
||||
out_data = in_data;
|
||||
}";
|
||||
|
||||
pub fn test_round_trip_struct<T: Debug + PartialEq + AsStd140 + AsStd430 + GlslStruct>(value: T) {
|
||||
let shader_std140 = glsl_shader_for_struct::<T>("std140");
|
||||
let shader_std430 = glsl_shader_for_struct::<T>("std430");
|
||||
|
||||
let context = Context::new();
|
||||
context.test_round_trip_std140(&shader_std140, &value);
|
||||
context.test_round_trip_std430(&shader_std430, &value);
|
||||
}
|
||||
|
||||
pub fn test_round_trip_primitive<T: Debug + PartialEq + AsStd140 + AsStd430 + Glsl>(value: T) {
|
||||
let shader_std140 = glsl_shader_for_primitive::<T>("std140");
|
||||
let shader_std430 = glsl_shader_for_primitive::<T>("std430");
|
||||
|
||||
let context = Context::new();
|
||||
context.test_round_trip_std140(&shader_std140, &value);
|
||||
context.test_round_trip_std430(&shader_std430, &value);
|
||||
}
|
||||
|
||||
fn glsl_shader_for_struct<T: GlslStruct>(layout: &str) -> String {
|
||||
BASE_SHADER
|
||||
.replace("{struct_name}", T::NAME)
|
||||
.replace("{struct_definition}", &T::glsl_definition())
|
||||
.replace("{layout}", layout)
|
||||
}
|
||||
|
||||
fn glsl_shader_for_primitive<T: Glsl>(layout: &str) -> String {
|
||||
BASE_SHADER
|
||||
.replace("{struct_name}", T::NAME)
|
||||
.replace("{struct_definition}", "")
|
||||
.replace("{layout}", layout)
|
||||
}
|
||||
|
||||
fn compile_glsl(glsl: &str) -> String {
|
||||
match compile(glsl) {
|
||||
Ok(shader) => shader,
|
||||
Err(err) => {
|
||||
eprintln!("Bad shader: {}", glsl);
|
||||
panic!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Context<T> {
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
_phantom: PhantomData<*const T>,
|
||||
}
|
||||
|
||||
impl<T> Context<T>
|
||||
where
|
||||
T: Debug + PartialEq + AsStd140 + AsStd430 + Glsl,
|
||||
{
|
||||
fn new() -> Self {
|
||||
let (device, queue) = setup();
|
||||
Self {
|
||||
device,
|
||||
queue,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn test_round_trip_std140(&self, glsl_shader: &str, value: &T) {
|
||||
let mut data = Vec::new();
|
||||
data.extend_from_slice(value.as_std140().as_bytes());
|
||||
|
||||
let wgsl_shader = compile_glsl(glsl_shader);
|
||||
let bytes = self.round_trip(&wgsl_shader, &data);
|
||||
|
||||
let std140 = bytemuck::from_bytes::<<T as AsStd140>::Output>(&bytes);
|
||||
let output = T::from_std140(*std140);
|
||||
|
||||
if value != &output {
|
||||
println!(
|
||||
"std140 value did not round-trip through wgpu successfully.\n\
|
||||
Input: {:?}\n\
|
||||
Output: {:?}\n\n\
|
||||
GLSL shader:\n{}\n\n\
|
||||
WGSL shader:\n{}",
|
||||
value, output, glsl_shader, wgsl_shader,
|
||||
);
|
||||
|
||||
panic!("wgpu round-trip failure for {}", T::NAME);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_round_trip_std430(&self, glsl_shader: &str, value: &T) {
|
||||
let mut data = Vec::new();
|
||||
data.extend_from_slice(value.as_std430().as_bytes());
|
||||
|
||||
let wgsl_shader = compile_glsl(glsl_shader);
|
||||
let bytes = self.round_trip(&wgsl_shader, &data);
|
||||
|
||||
let std430 = bytemuck::from_bytes::<<T as AsStd430>::Output>(&bytes);
|
||||
let output = T::from_std430(*std430);
|
||||
|
||||
if value != &output {
|
||||
println!(
|
||||
"std430 value did not round-trip through wgpu successfully.\n\
|
||||
Input: {:?}\n\
|
||||
Output: {:?}\n\n\
|
||||
GLSL shader:\n{}\n\n\
|
||||
WGSL shader:\n{}",
|
||||
value, output, glsl_shader, wgsl_shader,
|
||||
);
|
||||
|
||||
panic!("wgpu round-trip failure for {}", T::NAME);
|
||||
}
|
||||
}
|
||||
|
||||
fn round_trip(&self, shader: &str, data: &[u8]) -> Vec<u8> {
|
||||
let input_buffer = self
|
||||
.device
|
||||
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Input Buffer"),
|
||||
contents: &data,
|
||||
usage: wgpu::BufferUsages::STORAGE
|
||||
| wgpu::BufferUsages::COPY_DST
|
||||
| wgpu::BufferUsages::COPY_SRC,
|
||||
});
|
||||
|
||||
let output_gpu_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Output Buffer"),
|
||||
size: data.len() as wgpu::BufferAddress,
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let output_cpu_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Output Buffer"),
|
||||
size: data.len() as wgpu::BufferAddress,
|
||||
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let cs_module = self
|
||||
.device
|
||||
.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(shader)),
|
||||
});
|
||||
|
||||
let compute_pipeline =
|
||||
self.device
|
||||
.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
||||
label: None,
|
||||
layout: None,
|
||||
module: &cs_module,
|
||||
entry_point: "main",
|
||||
});
|
||||
|
||||
let bind_group_layout = compute_pipeline.get_bind_group_layout(0);
|
||||
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: None,
|
||||
layout: &bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: input_buffer.as_entire_binding(),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: output_gpu_buffer.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let mut encoder = self
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||
|
||||
{
|
||||
let mut cpass =
|
||||
encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None });
|
||||
cpass.set_pipeline(&compute_pipeline);
|
||||
cpass.set_bind_group(0, &bind_group, &[]);
|
||||
cpass.dispatch(1, 1, 1);
|
||||
}
|
||||
|
||||
encoder.copy_buffer_to_buffer(
|
||||
&output_gpu_buffer,
|
||||
0,
|
||||
&output_cpu_buffer,
|
||||
0,
|
||||
data.len() as wgpu::BufferAddress,
|
||||
);
|
||||
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
|
||||
let output_slice = output_cpu_buffer.slice(..);
|
||||
let output_future = output_slice.map_async(wgpu::MapMode::Read);
|
||||
|
||||
self.device.poll(wgpu::Maintain::Wait);
|
||||
block_on(output_future).unwrap();
|
||||
|
||||
let output = output_slice.get_mapped_range().to_vec();
|
||||
output_cpu_buffer.unmap();
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
fn setup() -> (wgpu::Device, wgpu::Queue) {
|
||||
let instance = wgpu::Instance::new(wgpu::Backends::all());
|
||||
let adapter =
|
||||
block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())).unwrap();
|
||||
|
||||
println!("Adapter info: {:#?}", adapter.get_info());
|
||||
|
||||
block_on(adapter.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
features: wgpu::Features::empty(),
|
||||
limits: wgpu::Limits::downlevel_defaults(),
|
||||
},
|
||||
None,
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn compile(glsl_source: &str) -> anyhow::Result<String> {
|
||||
let mut parser = naga::front::glsl::Parser::default();
|
||||
|
||||
let module = parser
|
||||
.parse(
|
||||
&naga::front::glsl::Options {
|
||||
stage: naga::ShaderStage::Compute,
|
||||
defines: Default::default(),
|
||||
},
|
||||
glsl_source,
|
||||
)
|
||||
.map_err(|err| anyhow::format_err!("{:?}", err))?;
|
||||
|
||||
let info = naga::valid::Validator::new(
|
||||
naga::valid::ValidationFlags::default(),
|
||||
naga::valid::Capabilities::all(),
|
||||
)
|
||||
.validate(&module)?;
|
||||
|
||||
let wgsl = naga::back::wgsl::write_string(&module, &info)?;
|
||||
|
||||
Ok(wgsl)
|
||||
}
|
366
crates/crevice/crevice-tests/src/lib.rs
Normal file
366
crates/crevice/crevice-tests/src/lib.rs
Normal file
|
@ -0,0 +1,366 @@
|
|||
#![cfg(test)]
|
||||
|
||||
#[cfg(feature = "wgpu-validation")]
|
||||
mod gpu;
|
||||
|
||||
#[cfg(feature = "wgpu-validation")]
|
||||
use gpu::{test_round_trip_primitive, test_round_trip_struct};
|
||||
|
||||
#[cfg(not(feature = "wgpu-validation"))]
|
||||
fn test_round_trip_struct<T>(_value: T) {}
|
||||
|
||||
#[cfg(not(feature = "wgpu-validation"))]
|
||||
fn test_round_trip_primitive<T>(_value: T) {}
|
||||
|
||||
#[macro_use]
|
||||
mod util;
|
||||
|
||||
use crevice::glsl::GlslStruct;
|
||||
use crevice::std140::AsStd140;
|
||||
use crevice::std430::AsStd430;
|
||||
use mint::{ColumnMatrix2, ColumnMatrix3, ColumnMatrix4, Vector2, Vector3, Vector4};
|
||||
|
||||
#[test]
|
||||
fn two_f32() {
|
||||
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||
struct TwoF32 {
|
||||
x: f32,
|
||||
y: f32,
|
||||
}
|
||||
|
||||
assert_std140!((size = 16, align = 16) TwoF32 {
|
||||
x: 0,
|
||||
y: 4,
|
||||
});
|
||||
|
||||
assert_std430!((size = 8, align = 4) TwoF32 {
|
||||
x: 0,
|
||||
y: 4,
|
||||
});
|
||||
|
||||
test_round_trip_struct(TwoF32 { x: 5.0, y: 7.0 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec2() {
|
||||
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||
struct UseVec2 {
|
||||
one: Vector2<f32>,
|
||||
}
|
||||
|
||||
assert_std140!((size = 16, align = 16) UseVec2 {
|
||||
one: 0,
|
||||
});
|
||||
|
||||
test_round_trip_struct(UseVec2 {
|
||||
one: [1.0, 2.0].into(),
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mat2_bare() {
|
||||
type Mat2 = ColumnMatrix2<f32>;
|
||||
|
||||
assert_std140!((size = 32, align = 16) Mat2 {
|
||||
x: 0,
|
||||
y: 16,
|
||||
});
|
||||
|
||||
assert_std430!((size = 16, align = 8) Mat2 {
|
||||
x: 0,
|
||||
y: 8,
|
||||
});
|
||||
|
||||
// Naga doesn't work with std140 mat2 values.
|
||||
// https://github.com/gfx-rs/naga/issues/1400
|
||||
|
||||
// test_round_trip_primitive(Mat2 {
|
||||
// x: [1.0, 2.0].into(),
|
||||
// y: [3.0, 4.0].into(),
|
||||
// });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mat3_bare() {
|
||||
type Mat3 = ColumnMatrix3<f32>;
|
||||
|
||||
assert_std140!((size = 48, align = 16) Mat3 {
|
||||
x: 0,
|
||||
y: 16,
|
||||
z: 32,
|
||||
});
|
||||
|
||||
// Naga produces invalid HLSL for mat3 value.
|
||||
// https://github.com/gfx-rs/naga/issues/1466
|
||||
|
||||
// test_round_trip_primitive(Mat3 {
|
||||
// x: [1.0, 2.0, 3.0].into(),
|
||||
// y: [4.0, 5.0, 6.0].into(),
|
||||
// z: [7.0, 8.0, 9.0].into(),
|
||||
// });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mat4_bare() {
|
||||
type Mat4 = ColumnMatrix4<f32>;
|
||||
|
||||
assert_std140!((size = 64, align = 16) Mat4 {
|
||||
x: 0,
|
||||
y: 16,
|
||||
z: 32,
|
||||
w: 48,
|
||||
});
|
||||
|
||||
test_round_trip_primitive(Mat4 {
|
||||
x: [1.0, 2.0, 3.0, 4.0].into(),
|
||||
y: [5.0, 6.0, 7.0, 8.0].into(),
|
||||
z: [9.0, 10.0, 11.0, 12.0].into(),
|
||||
w: [13.0, 14.0, 15.0, 16.0].into(),
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mat3() {
|
||||
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||
struct TestData {
|
||||
one: ColumnMatrix3<f32>,
|
||||
}
|
||||
|
||||
// Naga produces invalid HLSL for mat3 value.
|
||||
// https://github.com/gfx-rs/naga/issues/1466
|
||||
|
||||
// test_round_trip_struct(TestData {
|
||||
// one: [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]].into(),
|
||||
// });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dvec4() {
|
||||
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||
struct UsingDVec4 {
|
||||
doubles: Vector4<f64>,
|
||||
}
|
||||
|
||||
assert_std140!((size = 32, align = 32) UsingDVec4 {
|
||||
doubles: 0,
|
||||
});
|
||||
|
||||
// Naga does not appear to support doubles.
|
||||
// https://github.com/gfx-rs/naga/issues/1272
|
||||
|
||||
// test_round_trip_struct(UsingDVec4 {
|
||||
// doubles: [1.0, 2.0, 3.0, 4.0].into(),
|
||||
// });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_f64() {
|
||||
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||
struct FourF64 {
|
||||
x: f64,
|
||||
y: f64,
|
||||
z: f64,
|
||||
w: f64,
|
||||
}
|
||||
|
||||
assert_std140!((size = 32, align = 16) FourF64 {
|
||||
x: 0,
|
||||
y: 8,
|
||||
z: 16,
|
||||
w: 24,
|
||||
});
|
||||
|
||||
// Naga does not appear to support doubles.
|
||||
// https://github.com/gfx-rs/naga/issues/1272
|
||||
|
||||
// test_round_trip_struct(FourF64 {
|
||||
// x: 5.0,
|
||||
// y: 7.0,
|
||||
// z: 9.0,
|
||||
// w: 11.0,
|
||||
// });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_vec3() {
|
||||
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||
struct TwoVec3 {
|
||||
one: Vector3<f32>,
|
||||
two: Vector3<f32>,
|
||||
}
|
||||
|
||||
print_std140!(TwoVec3);
|
||||
print_std430!(TwoVec3);
|
||||
|
||||
assert_std140!((size = 32, align = 16) TwoVec3 {
|
||||
one: 0,
|
||||
two: 16,
|
||||
});
|
||||
|
||||
assert_std430!((size = 32, align = 16) TwoVec3 {
|
||||
one: 0,
|
||||
two: 16,
|
||||
});
|
||||
|
||||
test_round_trip_struct(TwoVec3 {
|
||||
one: [1.0, 2.0, 3.0].into(),
|
||||
two: [4.0, 5.0, 6.0].into(),
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_vec4() {
|
||||
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||
struct TwoVec4 {
|
||||
one: Vector4<f32>,
|
||||
two: Vector4<f32>,
|
||||
}
|
||||
|
||||
assert_std140!((size = 32, align = 16) TwoVec4 {
|
||||
one: 0,
|
||||
two: 16,
|
||||
});
|
||||
|
||||
test_round_trip_struct(TwoVec4 {
|
||||
one: [1.0, 2.0, 3.0, 4.0].into(),
|
||||
two: [5.0, 6.0, 7.0, 8.0].into(),
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec3_then_f32() {
|
||||
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||
struct Vec3ThenF32 {
|
||||
one: Vector3<f32>,
|
||||
two: f32,
|
||||
}
|
||||
|
||||
assert_std140!((size = 16, align = 16) Vec3ThenF32 {
|
||||
one: 0,
|
||||
two: 12,
|
||||
});
|
||||
|
||||
test_round_trip_struct(Vec3ThenF32 {
|
||||
one: [1.0, 2.0, 3.0].into(),
|
||||
two: 4.0,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mat3_padding() {
|
||||
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||
struct Mat3Padding {
|
||||
// Three rows of 16 bytes (3x f32 + 4 bytes padding)
|
||||
one: mint::ColumnMatrix3<f32>,
|
||||
two: f32,
|
||||
}
|
||||
|
||||
assert_std140!((size = 64, align = 16) Mat3Padding {
|
||||
one: 0,
|
||||
two: 48,
|
||||
});
|
||||
|
||||
// Naga produces invalid HLSL for mat3 value.
|
||||
// https://github.com/gfx-rs/naga/issues/1466
|
||||
|
||||
// test_round_trip_struct(Mat3Padding {
|
||||
// one: [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]].into(),
|
||||
// two: 10.0,
|
||||
// });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn padding_after_struct() {
|
||||
#[derive(AsStd140)]
|
||||
struct TwoF32 {
|
||||
x: f32,
|
||||
}
|
||||
|
||||
#[derive(AsStd140)]
|
||||
struct PaddingAfterStruct {
|
||||
base_value: TwoF32,
|
||||
// There should be 8 bytes of padding inserted here.
|
||||
small_field: f32,
|
||||
}
|
||||
|
||||
assert_std140!((size = 32, align = 16) PaddingAfterStruct {
|
||||
base_value: 0,
|
||||
small_field: 16,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proper_offset_calculations_for_differing_member_sizes() {
|
||||
#[derive(AsStd140)]
|
||||
struct Foo {
|
||||
x: f32,
|
||||
}
|
||||
|
||||
#[derive(AsStd140)]
|
||||
struct Bar {
|
||||
first: Foo,
|
||||
second: Foo,
|
||||
}
|
||||
|
||||
#[derive(AsStd140)]
|
||||
struct Outer {
|
||||
leading: Bar,
|
||||
trailing: Foo,
|
||||
}
|
||||
|
||||
// Offset Size Contents
|
||||
// 0 4 Bar.leading.first.x
|
||||
// 4 12 [padding]
|
||||
// 16 4 Bar.leading.second.x
|
||||
// 20 12 [padding]
|
||||
// 32 4 Bar.trailing.x
|
||||
// 36 12 [padding]
|
||||
//
|
||||
// Total size is 48.
|
||||
|
||||
assert_std140!((size = 48, align = 16) Outer {
|
||||
leading: 0,
|
||||
trailing: 32,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_strides_small_value() {
|
||||
#[derive(Debug, PartialEq, AsStd140, AsStd430)]
|
||||
struct ArrayOfSmallValues {
|
||||
inner: [f32; 4],
|
||||
}
|
||||
|
||||
assert_std140!((size = 64, align = 16) ArrayOfSmallValues {
|
||||
inner: 0,
|
||||
});
|
||||
|
||||
assert_std430!((size = 16, align = 4) ArrayOfSmallValues {
|
||||
inner: 0,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_strides_vec3() {
|
||||
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||
struct ArrayOfVector3 {
|
||||
inner: [Vector3<f32>; 4],
|
||||
}
|
||||
|
||||
assert_std140!((size = 64, align = 16) ArrayOfVector3 {
|
||||
inner: 0,
|
||||
});
|
||||
|
||||
assert_std430!((size = 64, align = 16) ArrayOfVector3 {
|
||||
inner: 0,
|
||||
});
|
||||
|
||||
test_round_trip_struct(ArrayOfVector3 {
|
||||
inner: [
|
||||
[0.0, 1.0, 2.0].into(),
|
||||
[3.0, 4.0, 5.0].into(),
|
||||
[6.0, 7.0, 8.0].into(),
|
||||
[9.0, 10.0, 11.0].into(),
|
||||
],
|
||||
})
|
||||
}
|
143
crates/crevice/crevice-tests/src/util.rs
Normal file
143
crates/crevice/crevice-tests/src/util.rs
Normal file
|
@ -0,0 +1,143 @@
|
|||
#[macro_export]
|
||||
macro_rules! print_std140 {
|
||||
($type:ty) => {
|
||||
println!(
|
||||
"{}",
|
||||
<$type as crevice::std140::AsStd140>::Output::debug_metrics()
|
||||
);
|
||||
println!();
|
||||
println!();
|
||||
println!(
|
||||
"{}",
|
||||
<$type as crevice::std140::AsStd140>::Output::debug_definitions()
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! print_std430 {
|
||||
($type:ty) => {
|
||||
println!(
|
||||
"{}",
|
||||
<$type as crevice::std430::AsStd430>::Output::debug_metrics()
|
||||
);
|
||||
println!();
|
||||
println!();
|
||||
println!(
|
||||
"{}",
|
||||
<$type as crevice::std430::AsStd430>::Output::debug_definitions()
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_std140 {
|
||||
((size = $size:literal, align = $align:literal) $struct:ident {
|
||||
$( $field:ident: $offset:literal, )*
|
||||
}) => {{
|
||||
type Target = <$struct as crevice::std140::AsStd140>::Output;
|
||||
|
||||
let mut fail = false;
|
||||
|
||||
let actual_size = std::mem::size_of::<Target>();
|
||||
if actual_size != $size {
|
||||
fail = true;
|
||||
println!(
|
||||
"Invalid size for std140 struct {}\n\
|
||||
Expected: {}\n\
|
||||
Actual: {}\n",
|
||||
stringify!($struct),
|
||||
$size,
|
||||
actual_size,
|
||||
);
|
||||
}
|
||||
|
||||
let actual_alignment = <Target as crevice::std140::Std140>::ALIGNMENT;
|
||||
if actual_alignment != $align {
|
||||
fail = true;
|
||||
println!(
|
||||
"Invalid alignment for std140 struct {}\n\
|
||||
Expected: {}\n\
|
||||
Actual: {}\n",
|
||||
stringify!($struct),
|
||||
$align,
|
||||
actual_alignment,
|
||||
);
|
||||
}
|
||||
|
||||
$({
|
||||
let actual_offset = memoffset::offset_of!(Target, $field);
|
||||
if actual_offset != $offset {
|
||||
fail = true;
|
||||
println!(
|
||||
"Invalid offset for field {}\n\
|
||||
Expected: {}\n\
|
||||
Actual: {}\n",
|
||||
stringify!($field),
|
||||
$offset,
|
||||
actual_offset,
|
||||
);
|
||||
}
|
||||
})*
|
||||
|
||||
if fail {
|
||||
panic!("Invalid std140 result for {}", stringify!($struct));
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! assert_std430 {
|
||||
((size = $size:literal, align = $align:literal) $struct:ident {
|
||||
$( $field:ident: $offset:literal, )*
|
||||
}) => {{
|
||||
type Target = <$struct as crevice::std430::AsStd430>::Output;
|
||||
|
||||
let mut fail = false;
|
||||
|
||||
let actual_size = std::mem::size_of::<Target>();
|
||||
if actual_size != $size {
|
||||
fail = true;
|
||||
println!(
|
||||
"Invalid size for std430 struct {}\n\
|
||||
Expected: {}\n\
|
||||
Actual: {}\n",
|
||||
stringify!($struct),
|
||||
$size,
|
||||
actual_size,
|
||||
);
|
||||
}
|
||||
|
||||
let actual_alignment = <Target as crevice::std430::Std430>::ALIGNMENT;
|
||||
if actual_alignment != $align {
|
||||
fail = true;
|
||||
println!(
|
||||
"Invalid alignment for std430 struct {}\n\
|
||||
Expected: {}\n\
|
||||
Actual: {}\n",
|
||||
stringify!($struct),
|
||||
$align,
|
||||
actual_alignment,
|
||||
);
|
||||
}
|
||||
|
||||
$({
|
||||
let actual_offset = memoffset::offset_of!(Target, $field);
|
||||
if actual_offset != $offset {
|
||||
fail = true;
|
||||
println!(
|
||||
"Invalid offset for std430 field {}\n\
|
||||
Expected: {}\n\
|
||||
Actual: {}\n",
|
||||
stringify!($field),
|
||||
$offset,
|
||||
actual_offset,
|
||||
);
|
||||
}
|
||||
})*
|
||||
|
||||
if fail {
|
||||
panic!("Invalid std430 result for {}", stringify!($struct));
|
||||
}
|
||||
}};
|
||||
}
|
93
crates/crevice/src/glsl.rs
Normal file
93
crates/crevice/src/glsl.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
//! Defines traits and types for generating GLSL code from Rust definitions.
|
||||
|
||||
pub use crevice_derive::GlslStruct;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Type-level linked list of array dimensions
|
||||
pub struct Dimension<A, const N: usize> {
|
||||
_marker: PhantomData<A>,
|
||||
}
|
||||
|
||||
/// Type-level linked list terminator for array dimensions.
|
||||
pub struct DimensionNil;
|
||||
|
||||
/// Trait for type-level array dimensions. Probably shouldn't be implemented outside this crate.
|
||||
pub unsafe trait DimensionList {
|
||||
/// Write dimensions in square brackets to a string, list tail to list head.
|
||||
fn push_to_string(s: &mut String);
|
||||
}
|
||||
|
||||
unsafe impl DimensionList for DimensionNil {
|
||||
fn push_to_string(_: &mut String) {}
|
||||
}
|
||||
|
||||
unsafe impl<A: DimensionList, const N: usize> DimensionList for Dimension<A, N> {
|
||||
fn push_to_string(s: &mut String) {
|
||||
use std::fmt::Write;
|
||||
A::push_to_string(s);
|
||||
write!(s, "[{}]", N).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for types that have a GLSL equivalent. Useful for generating GLSL code
|
||||
/// from Rust structs.
|
||||
pub unsafe trait Glsl {
|
||||
/// The name of this type in GLSL, like `vec2` or `mat4`.
|
||||
const NAME: &'static str;
|
||||
}
|
||||
|
||||
/// Trait for types that can be represented as a struct in GLSL.
|
||||
///
|
||||
/// This trait should not generally be implemented by hand, but can be derived.
|
||||
pub unsafe trait GlslStruct: Glsl {
|
||||
/// The fields contained in this struct.
|
||||
fn enumerate_fields(s: &mut String);
|
||||
|
||||
/// Generates GLSL code that represents this struct and its fields.
|
||||
fn glsl_definition() -> String {
|
||||
let mut output = String::new();
|
||||
output.push_str("struct ");
|
||||
output.push_str(Self::NAME);
|
||||
output.push_str(" {\n");
|
||||
|
||||
Self::enumerate_fields(&mut output);
|
||||
|
||||
output.push_str("};");
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for types that are expressible as a GLSL type with (possibly zero) array dimensions.
|
||||
pub unsafe trait GlslArray {
|
||||
/// Base type name.
|
||||
const NAME: &'static str;
|
||||
/// Type-level linked list of array dimensions, ordered outer to inner.
|
||||
type ArraySize: DimensionList;
|
||||
}
|
||||
|
||||
unsafe impl<T: Glsl> GlslArray for T {
|
||||
const NAME: &'static str = <T as Glsl>::NAME;
|
||||
type ArraySize = DimensionNil;
|
||||
}
|
||||
|
||||
unsafe impl Glsl for f32 {
|
||||
const NAME: &'static str = "float";
|
||||
}
|
||||
|
||||
unsafe impl Glsl for f64 {
|
||||
const NAME: &'static str = "double";
|
||||
}
|
||||
|
||||
unsafe impl Glsl for i32 {
|
||||
const NAME: &'static str = "int";
|
||||
}
|
||||
|
||||
unsafe impl Glsl for u32 {
|
||||
const NAME: &'static str = "uint";
|
||||
}
|
||||
|
||||
unsafe impl<T: GlslArray, const N: usize> GlslArray for [T; N] {
|
||||
const NAME: &'static str = T::NAME;
|
||||
|
||||
type ArraySize = Dimension<T::ArraySize, N>;
|
||||
}
|
10
crates/crevice/src/imp.rs
Normal file
10
crates/crevice/src/imp.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
mod imp_mint;
|
||||
|
||||
#[cfg(feature = "cgmath")]
|
||||
mod imp_cgmath;
|
||||
|
||||
#[cfg(feature = "glam")]
|
||||
mod imp_glam;
|
||||
|
||||
#[cfg(feature = "nalgebra")]
|
||||
mod imp_nalgebra;
|
30
crates/crevice/src/imp/imp_cgmath.rs
Normal file
30
crates/crevice/src/imp/imp_cgmath.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
easy_impl! {
|
||||
Vec2 cgmath::Vector2<f32> { x, y },
|
||||
Vec3 cgmath::Vector3<f32> { x, y, z },
|
||||
Vec4 cgmath::Vector4<f32> { x, y, z, w },
|
||||
|
||||
IVec2 cgmath::Vector2<i32> { x, y },
|
||||
IVec3 cgmath::Vector3<i32> { x, y, z },
|
||||
IVec4 cgmath::Vector4<i32> { x, y, z, w },
|
||||
|
||||
UVec2 cgmath::Vector2<u32> { x, y },
|
||||
UVec3 cgmath::Vector3<u32> { x, y, z },
|
||||
UVec4 cgmath::Vector4<u32> { x, y, z, w },
|
||||
|
||||
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
|
||||
// BVec2 cgmath::Vector2<bool> { x, y },
|
||||
// BVec3 cgmath::Vector3<bool> { x, y, z },
|
||||
// BVec4 cgmath::Vector4<bool> { x, y, z, w },
|
||||
|
||||
DVec2 cgmath::Vector2<f64> { x, y },
|
||||
DVec3 cgmath::Vector3<f64> { x, y, z },
|
||||
DVec4 cgmath::Vector4<f64> { x, y, z, w },
|
||||
|
||||
Mat2 cgmath::Matrix2<f32> { x, y },
|
||||
Mat3 cgmath::Matrix3<f32> { x, y, z },
|
||||
Mat4 cgmath::Matrix4<f32> { x, y, z, w },
|
||||
|
||||
DMat2 cgmath::Matrix2<f64> { x, y },
|
||||
DMat3 cgmath::Matrix3<f64> { x, y, z },
|
||||
DMat4 cgmath::Matrix4<f64> { x, y, z, w },
|
||||
}
|
24
crates/crevice/src/imp/imp_glam.rs
Normal file
24
crates/crevice/src/imp/imp_glam.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
minty_impl! {
|
||||
mint::Vector2<f32> => glam::Vec2,
|
||||
mint::Vector3<f32> => glam::Vec3,
|
||||
mint::Vector4<f32> => glam::Vec4,
|
||||
mint::Vector2<i32> => glam::IVec2,
|
||||
mint::Vector3<i32> => glam::IVec3,
|
||||
mint::Vector4<i32> => glam::IVec4,
|
||||
mint::Vector2<u32> => glam::UVec2,
|
||||
mint::Vector3<u32> => glam::UVec3,
|
||||
mint::Vector4<u32> => glam::UVec4,
|
||||
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
|
||||
// mint::Vector2<bool> => glam::BVec2,
|
||||
// mint::Vector3<bool> => glam::BVec3,
|
||||
// mint::Vector4<bool> => glam::BVec4,
|
||||
mint::Vector2<f64> => glam::DVec2,
|
||||
mint::Vector3<f64> => glam::DVec3,
|
||||
mint::Vector4<f64> => glam::DVec4,
|
||||
mint::ColumnMatrix2<f32> => glam::Mat2,
|
||||
mint::ColumnMatrix3<f32> => glam::Mat3,
|
||||
mint::ColumnMatrix4<f32> => glam::Mat4,
|
||||
mint::ColumnMatrix2<f64> => glam::DMat2,
|
||||
mint::ColumnMatrix3<f64> => glam::DMat3,
|
||||
mint::ColumnMatrix4<f64> => glam::DMat4,
|
||||
}
|
30
crates/crevice/src/imp/imp_mint.rs
Normal file
30
crates/crevice/src/imp/imp_mint.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
easy_impl! {
|
||||
Vec2 mint::Vector2<f32> { x, y },
|
||||
Vec3 mint::Vector3<f32> { x, y, z },
|
||||
Vec4 mint::Vector4<f32> { x, y, z, w },
|
||||
|
||||
IVec2 mint::Vector2<i32> { x, y },
|
||||
IVec3 mint::Vector3<i32> { x, y, z },
|
||||
IVec4 mint::Vector4<i32> { x, y, z, w },
|
||||
|
||||
UVec2 mint::Vector2<u32> { x, y },
|
||||
UVec3 mint::Vector3<u32> { x, y, z },
|
||||
UVec4 mint::Vector4<u32> { x, y, z, w },
|
||||
|
||||
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
|
||||
// BVec2 mint::Vector2<bool> { x, y },
|
||||
// BVec3 mint::Vector3<bool> { x, y, z },
|
||||
// BVec4 mint::Vector4<bool> { x, y, z, w },
|
||||
|
||||
DVec2 mint::Vector2<f64> { x, y },
|
||||
DVec3 mint::Vector3<f64> { x, y, z },
|
||||
DVec4 mint::Vector4<f64> { x, y, z, w },
|
||||
|
||||
Mat2 mint::ColumnMatrix2<f32> { x, y },
|
||||
Mat3 mint::ColumnMatrix3<f32> { x, y, z },
|
||||
Mat4 mint::ColumnMatrix4<f32> { x, y, z, w },
|
||||
|
||||
DMat2 mint::ColumnMatrix2<f64> { x, y },
|
||||
DMat3 mint::ColumnMatrix3<f64> { x, y, z },
|
||||
DMat4 mint::ColumnMatrix4<f64> { x, y, z, w },
|
||||
}
|
24
crates/crevice/src/imp/imp_nalgebra.rs
Normal file
24
crates/crevice/src/imp/imp_nalgebra.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
minty_impl! {
|
||||
mint::Vector2<f32> => nalgebra::Vector2<f32>,
|
||||
mint::Vector3<f32> => nalgebra::Vector3<f32>,
|
||||
mint::Vector4<f32> => nalgebra::Vector4<f32>,
|
||||
mint::Vector2<i32> => nalgebra::Vector2<i32>,
|
||||
mint::Vector3<i32> => nalgebra::Vector3<i32>,
|
||||
mint::Vector4<i32> => nalgebra::Vector4<i32>,
|
||||
mint::Vector2<u32> => nalgebra::Vector2<u32>,
|
||||
mint::Vector3<u32> => nalgebra::Vector3<u32>,
|
||||
mint::Vector4<u32> => nalgebra::Vector4<u32>,
|
||||
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
|
||||
// mint::Vector2<bool> => nalgebra::Vector2<bool>,
|
||||
// mint::Vector3<bool> => nalgebra::Vector3<bool>,
|
||||
// mint::Vector4<bool> => nalgebra::Vector4<bool>,
|
||||
mint::Vector2<f64> => nalgebra::Vector2<f64>,
|
||||
mint::Vector3<f64> => nalgebra::Vector3<f64>,
|
||||
mint::Vector4<f64> => nalgebra::Vector4<f64>,
|
||||
mint::ColumnMatrix2<f32> => nalgebra::Matrix2<f32>,
|
||||
mint::ColumnMatrix3<f32> => nalgebra::Matrix3<f32>,
|
||||
mint::ColumnMatrix4<f32> => nalgebra::Matrix4<f32>,
|
||||
mint::ColumnMatrix2<f64> => nalgebra::Matrix2<f64>,
|
||||
mint::ColumnMatrix3<f64> => nalgebra::Matrix3<f64>,
|
||||
mint::ColumnMatrix4<f64> => nalgebra::Matrix4<f64>,
|
||||
}
|
40
crates/crevice/src/internal.rs
Normal file
40
crates/crevice/src/internal.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
//! This module is internal to crevice but used by its derive macro. No
|
||||
//! guarantees are made about its contents.
|
||||
|
||||
pub use bytemuck;
|
||||
|
||||
/// Gives the number of bytes needed to make `offset` be aligned to `alignment`.
|
||||
pub const fn align_offset(offset: usize, alignment: usize) -> usize {
|
||||
if alignment == 0 || offset % alignment == 0 {
|
||||
0
|
||||
} else {
|
||||
alignment - offset % alignment
|
||||
}
|
||||
}
|
||||
|
||||
/// Max of two `usize`. Implemented because the `max` method from `Ord` cannot
|
||||
/// be used in const fns.
|
||||
pub const fn max(a: usize, b: usize) -> usize {
|
||||
if a > b {
|
||||
a
|
||||
} else {
|
||||
b
|
||||
}
|
||||
}
|
||||
|
||||
/// Max of an array of `usize`. This function's implementation is funky because
|
||||
/// we have no for loops!
|
||||
pub const fn max_arr<const N: usize>(input: [usize; N]) -> usize {
|
||||
let mut max = 0;
|
||||
let mut i = 0;
|
||||
|
||||
while i < N {
|
||||
if input[i] > max {
|
||||
max = input[i];
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
max
|
||||
}
|
172
crates/crevice/src/lib.rs
Normal file
172
crates/crevice/src/lib.rs
Normal file
|
@ -0,0 +1,172 @@
|
|||
#![allow(
|
||||
clippy::new_without_default,
|
||||
clippy::needless_update,
|
||||
clippy::len_without_is_empty,
|
||||
clippy::needless_range_loop
|
||||
)]
|
||||
/*!
|
||||
[![GitHub CI Status](https://github.com/LPGhatguy/crevice/workflows/CI/badge.svg)](https://github.com/LPGhatguy/crevice/actions)
|
||||
[![crevice on crates.io](https://img.shields.io/crates/v/crevice.svg)](https://crates.io/crates/crevice)
|
||||
[![crevice docs](https://img.shields.io/badge/docs-docs.rs-orange.svg)](https://docs.rs/crevice)
|
||||
|
||||
Crevice creates GLSL-compatible versions of types through the power of derive
|
||||
macros. Generated structures provide an [`as_bytes`][std140::Std140::as_bytes]
|
||||
method to allow safely packing data into buffers for uploading.
|
||||
|
||||
Generated structs also implement [`bytemuck::Zeroable`] and
|
||||
[`bytemuck::Pod`] for use with other libraries.
|
||||
|
||||
Crevice is similar to [`glsl-layout`][glsl-layout], but supports types from many
|
||||
math crates, can generate GLSL source from structs, and explicitly initializes
|
||||
padding to remove one source of undefined behavior.
|
||||
|
||||
Crevice has support for many Rust math libraries via feature flags, and most
|
||||
other math libraries by use of the mint crate. Crevice currently supports:
|
||||
|
||||
* mint 0.5, enabled by default
|
||||
* cgmath 0.18, using the `cgmath` feature
|
||||
* nalgebra 0.29, using the `nalgebra` feature
|
||||
* glam 0.19, using the `glam` feature
|
||||
|
||||
PRs are welcome to add or update math libraries to Crevice.
|
||||
|
||||
If your math library is not supported, it's possible to define structs using the
|
||||
types from mint and convert your math library's types into mint types. This is
|
||||
supported by most Rust math libraries.
|
||||
|
||||
Your math library may require you to turn on a feature flag to get mint support.
|
||||
For example, cgmath requires the "mint" feature to be enabled to allow
|
||||
conversions to and from mint types.
|
||||
|
||||
## Examples
|
||||
|
||||
### Single Value
|
||||
|
||||
Uploading many types can be done by deriving [`AsStd140`][std140::AsStd140] and
|
||||
using [`as_std140`][std140::AsStd140::as_std140] and
|
||||
[`as_bytes`][std140::Std140::as_bytes] to turn the result into bytes.
|
||||
|
||||
```glsl
|
||||
uniform MAIN {
|
||||
mat3 orientation;
|
||||
vec3 position;
|
||||
float scale;
|
||||
} main;
|
||||
```
|
||||
|
||||
```rust
|
||||
use crevice::std140::{AsStd140, Std140};
|
||||
|
||||
#[derive(AsStd140)]
|
||||
struct MainUniform {
|
||||
orientation: mint::ColumnMatrix3<f32>,
|
||||
position: mint::Vector3<f32>,
|
||||
scale: f32,
|
||||
}
|
||||
|
||||
let value = MainUniform {
|
||||
orientation: [
|
||||
[1.0, 0.0, 0.0],
|
||||
[0.0, 1.0, 0.0],
|
||||
[0.0, 0.0, 1.0],
|
||||
].into(),
|
||||
position: [1.0, 2.0, 3.0].into(),
|
||||
scale: 4.0,
|
||||
};
|
||||
|
||||
let value_std140 = value.as_std140();
|
||||
|
||||
# fn upload_data_to_gpu(_value: &[u8]) {}
|
||||
upload_data_to_gpu(value_std140.as_bytes());
|
||||
```
|
||||
|
||||
### Sequential Types
|
||||
|
||||
More complicated data can be uploaded using the std140
|
||||
[`Writer`][std140::Writer] type.
|
||||
|
||||
```glsl
|
||||
struct PointLight {
|
||||
vec3 position;
|
||||
vec3 color;
|
||||
float brightness;
|
||||
};
|
||||
|
||||
buffer POINT_LIGHTS {
|
||||
uint len;
|
||||
PointLight[] lights;
|
||||
} point_lights;
|
||||
```
|
||||
|
||||
```rust
|
||||
use crevice::std140::{self, AsStd140};
|
||||
|
||||
#[derive(AsStd140)]
|
||||
struct PointLight {
|
||||
position: mint::Vector3<f32>,
|
||||
color: mint::Vector3<f32>,
|
||||
brightness: f32,
|
||||
}
|
||||
|
||||
let lights = vec![
|
||||
PointLight {
|
||||
position: [0.0, 1.0, 0.0].into(),
|
||||
color: [1.0, 0.0, 0.0].into(),
|
||||
brightness: 0.6,
|
||||
},
|
||||
PointLight {
|
||||
position: [0.0, 4.0, 3.0].into(),
|
||||
color: [1.0, 1.0, 1.0].into(),
|
||||
brightness: 1.0,
|
||||
},
|
||||
];
|
||||
|
||||
# fn map_gpu_buffer_for_write() -> &'static mut [u8] {
|
||||
# Box::leak(vec![0; 1024].into_boxed_slice())
|
||||
# }
|
||||
let target_buffer = map_gpu_buffer_for_write();
|
||||
let mut writer = std140::Writer::new(target_buffer);
|
||||
|
||||
let light_count = lights.len() as u32;
|
||||
writer.write(&light_count)?;
|
||||
|
||||
// Crevice will automatically insert the required padding to align the
|
||||
// PointLight structure correctly. In this case, there will be 12 bytes of
|
||||
// padding between the length field and the light list.
|
||||
|
||||
writer.write(lights.as_slice())?;
|
||||
|
||||
# fn unmap_gpu_buffer() {}
|
||||
unmap_gpu_buffer();
|
||||
|
||||
# Ok::<(), std::io::Error>(())
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
* `std` (default): Enables [`std::io::Write`]-based structs.
|
||||
* `cgmath`: Enables support for types from cgmath.
|
||||
* `nalgebra`: Enables support for types from nalgebra.
|
||||
* `glam`: Enables support for types from glam.
|
||||
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
|
||||
Crevice supports Rust 1.52.1 and newer due to use of new `const fn` features.
|
||||
|
||||
[glsl-layout]: https://github.com/rustgd/glsl-layout
|
||||
*/
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[macro_use]
|
||||
mod util;
|
||||
|
||||
pub mod glsl;
|
||||
pub mod std140;
|
||||
pub mod std430;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod internal;
|
||||
|
||||
mod imp;
|
18
crates/crevice/src/std140.rs
Normal file
18
crates/crevice/src/std140.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
//! Defines traits and types for working with data adhering to GLSL's `std140`
|
||||
//! layout specification.
|
||||
|
||||
mod dynamic_uniform;
|
||||
mod primitives;
|
||||
mod sizer;
|
||||
mod traits;
|
||||
#[cfg(feature = "std")]
|
||||
mod writer;
|
||||
|
||||
pub use self::dynamic_uniform::*;
|
||||
pub use self::primitives::*;
|
||||
pub use self::sizer::*;
|
||||
pub use self::traits::*;
|
||||
#[cfg(feature = "std")]
|
||||
pub use self::writer::*;
|
||||
|
||||
pub use crevice_derive::AsStd140;
|
68
crates/crevice/src/std140/dynamic_uniform.rs
Normal file
68
crates/crevice/src/std140/dynamic_uniform.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crate::internal::{align_offset, max};
|
||||
use crate::std140::{AsStd140, Std140};
|
||||
|
||||
/// Wrapper type that aligns the inner type to at least 256 bytes.
|
||||
///
|
||||
/// This type is useful for ensuring correct alignment when creating dynamic
|
||||
/// uniform buffers in APIs like WebGPU.
|
||||
pub struct DynamicUniform<T>(pub T);
|
||||
|
||||
impl<T: AsStd140> AsStd140 for DynamicUniform<T> {
|
||||
type Output = DynamicUniformStd140<<T as AsStd140>::Output>;
|
||||
|
||||
fn as_std140(&self) -> Self::Output {
|
||||
DynamicUniformStd140(self.0.as_std140())
|
||||
}
|
||||
|
||||
fn from_std140(value: Self::Output) -> Self {
|
||||
DynamicUniform(<T as AsStd140>::from_std140(value.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// std140 variant of [`DynamicUniform`].
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct DynamicUniformStd140<T>(T);
|
||||
|
||||
unsafe impl<T: Std140> Std140 for DynamicUniformStd140<T> {
|
||||
const ALIGNMENT: usize = max(256, T::ALIGNMENT);
|
||||
#[cfg(const_evaluatable_checked)]
|
||||
type Padded = crate::std140::Std140Padded<
|
||||
Self,
|
||||
{ align_offset(core::mem::size_of::<T>(), max(256, T::ALIGNMENT)) },
|
||||
>;
|
||||
#[cfg(not(const_evaluatable_checked))]
|
||||
type Padded = crate::std140::InvalidPadded;
|
||||
}
|
||||
|
||||
unsafe impl<T: Zeroable> Zeroable for DynamicUniformStd140<T> {}
|
||||
unsafe impl<T: Pod> Pod for DynamicUniformStd140<T> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use crate::std140::{self, WriteStd140};
|
||||
|
||||
#[test]
|
||||
fn size_is_unchanged() {
|
||||
let dynamic_f32 = DynamicUniform(0.0f32);
|
||||
|
||||
assert_eq!(dynamic_f32.std140_size(), 0.0f32.std140_size());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alignment_applies() {
|
||||
let mut output = Vec::new();
|
||||
let mut writer = std140::Writer::new(&mut output);
|
||||
|
||||
writer.write(&DynamicUniform(0.0f32)).unwrap();
|
||||
assert_eq!(writer.len(), 4);
|
||||
|
||||
writer.write(&DynamicUniform(1.0f32)).unwrap();
|
||||
assert_eq!(writer.len(), 260);
|
||||
}
|
||||
}
|
175
crates/crevice/src/std140/primitives.rs
Normal file
175
crates/crevice/src/std140/primitives.rs
Normal file
|
@ -0,0 +1,175 @@
|
|||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
use crate::glsl::Glsl;
|
||||
use crate::std140::{Std140, Std140Padded};
|
||||
|
||||
use crate::internal::{align_offset, max};
|
||||
use core::mem::size_of;
|
||||
|
||||
unsafe impl Std140 for f32 {
|
||||
const ALIGNMENT: usize = 4;
|
||||
type Padded = Std140Padded<Self, 12>;
|
||||
}
|
||||
|
||||
unsafe impl Std140 for f64 {
|
||||
const ALIGNMENT: usize = 8;
|
||||
type Padded = Std140Padded<Self, 8>;
|
||||
}
|
||||
|
||||
unsafe impl Std140 for i32 {
|
||||
const ALIGNMENT: usize = 4;
|
||||
type Padded = Std140Padded<Self, 12>;
|
||||
}
|
||||
|
||||
unsafe impl Std140 for u32 {
|
||||
const ALIGNMENT: usize = 4;
|
||||
type Padded = Std140Padded<Self, 12>;
|
||||
}
|
||||
|
||||
macro_rules! vectors {
|
||||
(
|
||||
$(
|
||||
#[$doc:meta] align($align:literal) $glsl_name:ident $name:ident <$prim:ident> ($($field:ident),+)
|
||||
)+
|
||||
) => {
|
||||
$(
|
||||
#[$doc]
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct $name {
|
||||
$(pub $field: $prim,)+
|
||||
}
|
||||
|
||||
unsafe impl Zeroable for $name {}
|
||||
unsafe impl Pod for $name {}
|
||||
|
||||
unsafe impl Std140 for $name {
|
||||
const ALIGNMENT: usize = $align;
|
||||
type Padded = Std140Padded<Self, {align_offset(size_of::<$name>(), max(16, $align))}>;
|
||||
}
|
||||
|
||||
unsafe impl Glsl for $name {
|
||||
const NAME: &'static str = stringify!($glsl_name);
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
vectors! {
|
||||
#[doc = "Corresponds to a GLSL `vec2` in std140 layout."] align(8) vec2 Vec2<f32>(x, y)
|
||||
#[doc = "Corresponds to a GLSL `vec3` in std140 layout."] align(16) vec3 Vec3<f32>(x, y, z)
|
||||
#[doc = "Corresponds to a GLSL `vec4` in std140 layout."] align(16) vec4 Vec4<f32>(x, y, z, w)
|
||||
|
||||
#[doc = "Corresponds to a GLSL `ivec2` in std140 layout."] align(8) ivec2 IVec2<i32>(x, y)
|
||||
#[doc = "Corresponds to a GLSL `ivec3` in std140 layout."] align(16) ivec3 IVec3<i32>(x, y, z)
|
||||
#[doc = "Corresponds to a GLSL `ivec4` in std140 layout."] align(16) ivec4 IVec4<i32>(x, y, z, w)
|
||||
|
||||
#[doc = "Corresponds to a GLSL `uvec2` in std140 layout."] align(8) uvec2 UVec2<u32>(x, y)
|
||||
#[doc = "Corresponds to a GLSL `uvec3` in std140 layout."] align(16) uvec3 UVec3<u32>(x, y, z)
|
||||
#[doc = "Corresponds to a GLSL `uvec4` in std140 layout."] align(16) uvec4 UVec4<u32>(x, y, z, w)
|
||||
|
||||
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
|
||||
|
||||
// #[doc = "Corresponds to a GLSL `bvec2` in std140 layout."] align(8) bvec2 BVec2<bool>(x, y)
|
||||
// #[doc = "Corresponds to a GLSL `bvec3` in std140 layout."] align(16) bvec3 BVec3<bool>(x, y, z)
|
||||
// #[doc = "Corresponds to a GLSL `bvec4` in std140 layout."] align(16) bvec4 BVec4<bool>(x, y, z, w)
|
||||
|
||||
#[doc = "Corresponds to a GLSL `dvec2` in std140 layout."] align(16) dvec2 DVec2<f64>(x, y)
|
||||
#[doc = "Corresponds to a GLSL `dvec3` in std140 layout."] align(32) dvec3 DVec3<f64>(x, y, z)
|
||||
#[doc = "Corresponds to a GLSL `dvec4` in std140 layout."] align(32) dvec4 DVec4<f64>(x, y, z, w)
|
||||
}
|
||||
|
||||
macro_rules! matrices {
|
||||
(
|
||||
$(
|
||||
#[$doc:meta]
|
||||
align($align:literal)
|
||||
$glsl_name:ident $name:ident {
|
||||
$($field:ident: $field_ty:ty,)+
|
||||
}
|
||||
)+
|
||||
) => {
|
||||
$(
|
||||
#[$doc]
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct $name {
|
||||
$(pub $field: $field_ty,)+
|
||||
}
|
||||
|
||||
unsafe impl Zeroable for $name {}
|
||||
unsafe impl Pod for $name {}
|
||||
|
||||
unsafe impl Std140 for $name {
|
||||
const ALIGNMENT: usize = $align;
|
||||
/// Matrices are technically arrays of primitives, and as such require pad at end.
|
||||
const PAD_AT_END: bool = true;
|
||||
type Padded = Std140Padded<Self, {align_offset(size_of::<$name>(), max(16, $align))}>;
|
||||
}
|
||||
|
||||
unsafe impl Glsl for $name {
|
||||
const NAME: &'static str = stringify!($glsl_name);
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
matrices! {
|
||||
#[doc = "Corresponds to a GLSL `mat2` in std140 layout."]
|
||||
align(16)
|
||||
mat2 Mat2 {
|
||||
x: Vec2,
|
||||
_pad_x: [f32; 2],
|
||||
y: Vec2,
|
||||
_pad_y: [f32; 2],
|
||||
}
|
||||
|
||||
#[doc = "Corresponds to a GLSL `mat3` in std140 layout."]
|
||||
align(16)
|
||||
mat3 Mat3 {
|
||||
x: Vec3,
|
||||
_pad_x: f32,
|
||||
y: Vec3,
|
||||
_pad_y: f32,
|
||||
z: Vec3,
|
||||
_pad_z: f32,
|
||||
}
|
||||
|
||||
#[doc = "Corresponds to a GLSL `mat4` in std140 layout."]
|
||||
align(16)
|
||||
mat4 Mat4 {
|
||||
x: Vec4,
|
||||
y: Vec4,
|
||||
z: Vec4,
|
||||
w: Vec4,
|
||||
}
|
||||
|
||||
#[doc = "Corresponds to a GLSL `dmat2` in std140 layout."]
|
||||
align(16)
|
||||
dmat2 DMat2 {
|
||||
x: DVec2,
|
||||
y: DVec2,
|
||||
}
|
||||
|
||||
#[doc = "Corresponds to a GLSL `dmat3` in std140 layout."]
|
||||
align(32)
|
||||
dmat3 DMat3 {
|
||||
x: DVec3,
|
||||
_pad_x: f64,
|
||||
y: DVec3,
|
||||
_pad_y: f64,
|
||||
z: DVec3,
|
||||
_pad_z: f64,
|
||||
}
|
||||
|
||||
#[doc = "Corresponds to a GLSL `dmat3` in std140 layout."]
|
||||
align(32)
|
||||
dmat4 DMat4 {
|
||||
x: DVec4,
|
||||
y: DVec4,
|
||||
z: DVec4,
|
||||
w: DVec4,
|
||||
}
|
||||
}
|
81
crates/crevice/src/std140/sizer.rs
Normal file
81
crates/crevice/src/std140/sizer.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
use core::mem::size_of;
|
||||
|
||||
use crate::internal::align_offset;
|
||||
use crate::std140::{AsStd140, Std140};
|
||||
|
||||
/**
|
||||
Type that computes the buffer size needed by a series of `std140` types laid
|
||||
out.
|
||||
|
||||
This type works well well when paired with `Writer`, precomputing a buffer's
|
||||
size to alleviate the need to dynamically re-allocate buffers.
|
||||
|
||||
## Example
|
||||
|
||||
```glsl
|
||||
struct Frob {
|
||||
vec3 size;
|
||||
float frobiness;
|
||||
}
|
||||
|
||||
buffer FROBS {
|
||||
uint len;
|
||||
Frob[] frobs;
|
||||
} frobs;
|
||||
```
|
||||
|
||||
```
|
||||
use crevice::std140::{self, AsStd140};
|
||||
|
||||
#[derive(AsStd140)]
|
||||
struct Frob {
|
||||
size: mint::Vector3<f32>,
|
||||
frobiness: f32,
|
||||
}
|
||||
|
||||
// Many APIs require that buffers contain at least enough space for all
|
||||
// fixed-size bindiongs to a buffer as well as one element of any arrays, if
|
||||
// there are any.
|
||||
let mut sizer = std140::Sizer::new();
|
||||
sizer.add::<u32>();
|
||||
sizer.add::<Frob>();
|
||||
|
||||
# fn create_buffer_with_size(size: usize) {}
|
||||
let buffer = create_buffer_with_size(sizer.len());
|
||||
# assert_eq!(sizer.len(), 32);
|
||||
```
|
||||
*/
|
||||
pub struct Sizer {
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl Sizer {
|
||||
/// Create a new `Sizer`.
|
||||
pub fn new() -> Self {
|
||||
Self { offset: 0 }
|
||||
}
|
||||
|
||||
/// Add a type's necessary padding and size to the `Sizer`. Returns the
|
||||
/// offset into the buffer where that type would be written.
|
||||
pub fn add<T>(&mut self) -> usize
|
||||
where
|
||||
T: AsStd140,
|
||||
{
|
||||
let size = size_of::<<T as AsStd140>::Output>();
|
||||
let alignment = <T as AsStd140>::Output::ALIGNMENT;
|
||||
let padding = align_offset(self.offset, alignment);
|
||||
|
||||
self.offset += padding;
|
||||
let write_here = self.offset;
|
||||
|
||||
self.offset += size;
|
||||
|
||||
write_here
|
||||
}
|
||||
|
||||
/// Returns the number of bytes required to contain all the types added to
|
||||
/// the `Sizer`.
|
||||
pub fn len(&self) -> usize {
|
||||
self.offset
|
||||
}
|
||||
}
|
284
crates/crevice/src/std140/traits.rs
Normal file
284
crates/crevice/src/std140/traits.rs
Normal file
|
@ -0,0 +1,284 @@
|
|||
use core::mem::{size_of, MaybeUninit};
|
||||
#[cfg(feature = "std")]
|
||||
use std::io::{self, Write};
|
||||
|
||||
use bytemuck::{bytes_of, Pod, Zeroable};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use crate::std140::Writer;
|
||||
|
||||
/// Trait implemented for all `std140` primitives. Generally should not be
|
||||
/// implemented outside this crate.
|
||||
pub unsafe trait Std140: Copy + Zeroable + Pod {
|
||||
/// The required alignment of the type. Must be a power of two.
|
||||
///
|
||||
/// This is distinct from the value returned by `std::mem::align_of` because
|
||||
/// `AsStd140` structs do not use Rust's alignment. This enables them to
|
||||
/// control and zero their padding bytes, making converting them to and from
|
||||
/// slices safe.
|
||||
const ALIGNMENT: usize;
|
||||
|
||||
/// Whether this type requires a padding at the end (ie, is a struct or an array
|
||||
/// of primitives).
|
||||
/// See <https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf#page=159>
|
||||
/// (rule 4 and 9)
|
||||
const PAD_AT_END: bool = false;
|
||||
/// Padded type (Std140Padded specialization)
|
||||
/// The usual implementation is
|
||||
/// type Padded = Std140Padded<Self, {align_offset(size_of::<Self>(), max(16, ALIGNMENT))}>;
|
||||
type Padded: Std140Convertible<Self>;
|
||||
|
||||
/// Casts the type to a byte array. Implementors should not override this
|
||||
/// method.
|
||||
///
|
||||
/// # Safety
|
||||
/// This is always safe due to the requirements of [`bytemuck::Pod`] being a
|
||||
/// prerequisite for this trait.
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
bytes_of(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait specifically for Std140::Padded, implements conversions between padded type and base type.
|
||||
pub trait Std140Convertible<T: Std140>: Copy {
|
||||
/// Convert from self to Std140
|
||||
fn into_std140(self) -> T;
|
||||
/// Convert from Std140 to self
|
||||
fn from_std140(_: T) -> Self;
|
||||
}
|
||||
|
||||
impl<T: Std140> Std140Convertible<T> for T {
|
||||
fn into_std140(self) -> T {
|
||||
self
|
||||
}
|
||||
fn from_std140(also_self: T) -> Self {
|
||||
also_self
|
||||
}
|
||||
}
|
||||
|
||||
/// Unfortunately, we cannot easily derive padded representation for generic Std140 types.
|
||||
/// For now, we'll just use this empty enum with no values.
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum InvalidPadded {}
|
||||
impl<T: Std140> Std140Convertible<T> for InvalidPadded {
|
||||
fn into_std140(self) -> T {
|
||||
unimplemented!()
|
||||
}
|
||||
fn from_std140(_: T) -> Self {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
/**
|
||||
Trait implemented for all types that can be turned into `std140` values.
|
||||
*
|
||||
This trait can often be `#[derive]`'d instead of manually implementing it. Any
|
||||
struct which contains only fields that also implement `AsStd140` can derive
|
||||
`AsStd140`.
|
||||
|
||||
Types from the mint crate implement `AsStd140`, making them convenient for use
|
||||
in uniform types. Most Rust math crates, like cgmath, nalgebra, and
|
||||
ultraviolet support mint.
|
||||
|
||||
## Example
|
||||
|
||||
```glsl
|
||||
uniform CAMERA {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
} camera;
|
||||
```
|
||||
|
||||
```no_run
|
||||
use crevice::std140::{AsStd140, Std140};
|
||||
|
||||
#[derive(AsStd140)]
|
||||
struct CameraUniform {
|
||||
view: mint::ColumnMatrix4<f32>,
|
||||
projection: mint::ColumnMatrix4<f32>,
|
||||
}
|
||||
|
||||
let view: mint::ColumnMatrix4<f32> = todo!("your math code here");
|
||||
let projection: mint::ColumnMatrix4<f32> = todo!("your math code here");
|
||||
|
||||
let camera = CameraUniform {
|
||||
view,
|
||||
projection,
|
||||
};
|
||||
|
||||
# fn write_to_gpu_buffer(bytes: &[u8]) {}
|
||||
let camera_std140 = camera.as_std140();
|
||||
write_to_gpu_buffer(camera_std140.as_bytes());
|
||||
```
|
||||
*/
|
||||
pub trait AsStd140 {
|
||||
/// The `std140` version of this value.
|
||||
type Output: Std140;
|
||||
|
||||
/// Convert this value into the `std140` version of itself.
|
||||
fn as_std140(&self) -> Self::Output;
|
||||
|
||||
/// Returns the size of the `std140` version of this type. Useful for
|
||||
/// pre-sizing buffers.
|
||||
fn std140_size_static() -> usize {
|
||||
size_of::<Self::Output>()
|
||||
}
|
||||
|
||||
/// Converts from `std140` version of self to self.
|
||||
fn from_std140(val: Self::Output) -> Self;
|
||||
}
|
||||
|
||||
impl<T> AsStd140 for T
|
||||
where
|
||||
T: Std140,
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
fn as_std140(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
|
||||
fn from_std140(x: Self) -> Self {
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Std140Padded<T: Std140, const PAD: usize> {
|
||||
inner: T,
|
||||
_padding: [u8; PAD],
|
||||
}
|
||||
|
||||
unsafe impl<T: Std140, const PAD: usize> Zeroable for Std140Padded<T, PAD> {}
|
||||
unsafe impl<T: Std140, const PAD: usize> Pod for Std140Padded<T, PAD> {}
|
||||
|
||||
impl<T: Std140, const PAD: usize> Std140Convertible<T> for Std140Padded<T, PAD> {
|
||||
fn into_std140(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
|
||||
fn from_std140(inner: T) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
_padding: [0u8; PAD],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct Std140Array<T: Std140, const N: usize>([T::Padded; N]);
|
||||
|
||||
unsafe impl<T: Std140, const N: usize> Zeroable for Std140Array<T, N> where T::Padded: Zeroable {}
|
||||
unsafe impl<T: Std140, const N: usize> Pod for Std140Array<T, N> where T::Padded: Pod {}
|
||||
unsafe impl<T: Std140, const N: usize> Std140 for Std140Array<T, N>
|
||||
where
|
||||
T::Padded: Pod,
|
||||
{
|
||||
const ALIGNMENT: usize = crate::internal::max(T::ALIGNMENT, 16);
|
||||
type Padded = Self;
|
||||
}
|
||||
|
||||
impl<T: Std140, const N: usize> Std140Array<T, N> {
|
||||
fn uninit_array() -> [MaybeUninit<T::Padded>; N] {
|
||||
unsafe { MaybeUninit::uninit().assume_init() }
|
||||
}
|
||||
|
||||
fn from_uninit_array(a: [MaybeUninit<T::Padded>; N]) -> Self {
|
||||
unsafe { core::mem::transmute_copy(&a) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsStd140, const N: usize> AsStd140 for [T; N]
|
||||
where
|
||||
<T::Output as Std140>::Padded: Pod,
|
||||
{
|
||||
type Output = Std140Array<T::Output, N>;
|
||||
fn as_std140(&self) -> Self::Output {
|
||||
let mut res = Self::Output::uninit_array();
|
||||
|
||||
for i in 0..N {
|
||||
res[i] = MaybeUninit::new(Std140Convertible::from_std140(self[i].as_std140()));
|
||||
}
|
||||
|
||||
Self::Output::from_uninit_array(res)
|
||||
}
|
||||
|
||||
fn from_std140(val: Self::Output) -> Self {
|
||||
let mut res: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
|
||||
for i in 0..N {
|
||||
res[i] = MaybeUninit::new(T::from_std140(Std140Convertible::into_std140(val.0[i])));
|
||||
}
|
||||
unsafe { core::mem::transmute_copy(&res) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait implemented for all types that can be written into a buffer as
|
||||
/// `std140` bytes. This type is more general than [`AsStd140`]: all `AsStd140`
|
||||
/// types implement `WriteStd140`, but not the other way around.
|
||||
///
|
||||
/// While `AsStd140` requires implementers to return a type that implements the
|
||||
/// `Std140` trait, `WriteStd140` directly writes bytes using a [`Writer`]. This
|
||||
/// makes `WriteStd140` usable for writing slices or other DSTs that could not
|
||||
/// implement `AsStd140` without allocating new memory on the heap.
|
||||
#[cfg(feature = "std")]
|
||||
pub trait WriteStd140 {
|
||||
/// Writes this value into the given [`Writer`] using `std140` layout rules.
|
||||
///
|
||||
/// Should return the offset of the first byte of this type, as returned by
|
||||
/// the first call to [`Writer::write`].
|
||||
fn write_std140<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize>;
|
||||
|
||||
/// The space required to write this value using `std140` layout rules. This
|
||||
/// does not include alignment padding that may be needed before or after
|
||||
/// this type when written as part of a larger buffer.
|
||||
fn std140_size(&self) -> usize {
|
||||
let mut writer = Writer::new(io::sink());
|
||||
self.write_std140(&mut writer).unwrap();
|
||||
writer.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T> WriteStd140 for T
|
||||
where
|
||||
T: AsStd140,
|
||||
{
|
||||
fn write_std140<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize> {
|
||||
writer.write_std140(&self.as_std140())
|
||||
}
|
||||
|
||||
fn std140_size(&self) -> usize {
|
||||
size_of::<<Self as AsStd140>::Output>()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T> WriteStd140 for [T]
|
||||
where
|
||||
T: WriteStd140,
|
||||
{
|
||||
fn write_std140<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize> {
|
||||
// if no items are written, offset is current position of the writer
|
||||
let mut offset = writer.len();
|
||||
|
||||
let mut iter = self.iter();
|
||||
|
||||
if let Some(item) = iter.next() {
|
||||
offset = item.write_std140(writer)?;
|
||||
}
|
||||
|
||||
for item in iter {
|
||||
item.write_std140(writer)?;
|
||||
}
|
||||
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
fn std140_size(&self) -> usize {
|
||||
let mut writer = Writer::new(io::sink());
|
||||
self.write_std140(&mut writer).unwrap();
|
||||
writer.len()
|
||||
}
|
||||
}
|
162
crates/crevice/src/std140/writer.rs
Normal file
162
crates/crevice/src/std140/writer.rs
Normal file
|
@ -0,0 +1,162 @@
|
|||
use std::io::{self, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use bytemuck::bytes_of;
|
||||
|
||||
use crate::internal::align_offset;
|
||||
use crate::std140::{AsStd140, Std140, WriteStd140};
|
||||
|
||||
/**
|
||||
Type that enables writing correctly aligned `std140` values to a buffer.
|
||||
|
||||
`Writer` is useful when many values need to be laid out in a row that cannot be
|
||||
represented by a struct alone, like dynamically sized arrays or dynamically
|
||||
laid-out values.
|
||||
|
||||
## Example
|
||||
In this example, we'll write a length-prefixed list of lights to a buffer.
|
||||
`std140::Writer` helps align correctly, even across multiple structs, which can
|
||||
be tricky and error-prone otherwise.
|
||||
|
||||
```glsl
|
||||
struct PointLight {
|
||||
vec3 position;
|
||||
vec3 color;
|
||||
float brightness;
|
||||
};
|
||||
|
||||
buffer POINT_LIGHTS {
|
||||
uint len;
|
||||
PointLight[] lights;
|
||||
} point_lights;
|
||||
```
|
||||
|
||||
```
|
||||
use crevice::std140::{self, AsStd140};
|
||||
|
||||
#[derive(AsStd140)]
|
||||
struct PointLight {
|
||||
position: mint::Vector3<f32>,
|
||||
color: mint::Vector3<f32>,
|
||||
brightness: f32,
|
||||
}
|
||||
|
||||
let lights = vec![
|
||||
PointLight {
|
||||
position: [0.0, 1.0, 0.0].into(),
|
||||
color: [1.0, 0.0, 0.0].into(),
|
||||
brightness: 0.6,
|
||||
},
|
||||
PointLight {
|
||||
position: [0.0, 4.0, 3.0].into(),
|
||||
color: [1.0, 1.0, 1.0].into(),
|
||||
brightness: 1.0,
|
||||
},
|
||||
];
|
||||
|
||||
# fn map_gpu_buffer_for_write() -> &'static mut [u8] {
|
||||
# Box::leak(vec![0; 1024].into_boxed_slice())
|
||||
# }
|
||||
let target_buffer = map_gpu_buffer_for_write();
|
||||
let mut writer = std140::Writer::new(target_buffer);
|
||||
|
||||
let light_count = lights.len() as u32;
|
||||
writer.write(&light_count)?;
|
||||
|
||||
// Crevice will automatically insert the required padding to align the
|
||||
// PointLight structure correctly. In this case, there will be 12 bytes of
|
||||
// padding between the length field and the light list.
|
||||
|
||||
writer.write(lights.as_slice())?;
|
||||
|
||||
# fn unmap_gpu_buffer() {}
|
||||
unmap_gpu_buffer();
|
||||
|
||||
# Ok::<(), std::io::Error>(())
|
||||
```
|
||||
*/
|
||||
pub struct Writer<W> {
|
||||
writer: W,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl<W: Write> Writer<W> {
|
||||
/// Create a new `Writer`, wrapping a buffer, file, or other type that
|
||||
/// implements [`std::io::Write`].
|
||||
pub fn new(writer: W) -> Self {
|
||||
Self { writer, offset: 0 }
|
||||
}
|
||||
|
||||
/// Write a new value to the underlying buffer, writing zeroed padding where
|
||||
/// necessary.
|
||||
///
|
||||
/// Returns the offset into the buffer that the value was written to.
|
||||
pub fn write<T>(&mut self, value: &T) -> io::Result<usize>
|
||||
where
|
||||
T: WriteStd140 + ?Sized,
|
||||
{
|
||||
value.write_std140(self)
|
||||
}
|
||||
|
||||
/// Write an iterator of values to the underlying buffer.
|
||||
///
|
||||
/// Returns the offset into the buffer that the first value was written to.
|
||||
/// If no values were written, returns the `len()`.
|
||||
pub fn write_iter<I, T>(&mut self, iter: I) -> io::Result<usize>
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: WriteStd140,
|
||||
{
|
||||
let mut offset = self.offset;
|
||||
|
||||
let mut iter = iter.into_iter();
|
||||
|
||||
if let Some(item) = iter.next() {
|
||||
offset = item.write_std140(self)?;
|
||||
}
|
||||
|
||||
for item in iter {
|
||||
item.write_std140(self)?;
|
||||
}
|
||||
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
/// Write an `Std140` type to the underlying buffer.
|
||||
pub fn write_std140<T>(&mut self, value: &T) -> io::Result<usize>
|
||||
where
|
||||
T: Std140,
|
||||
{
|
||||
let padding = align_offset(self.offset, T::ALIGNMENT);
|
||||
|
||||
for _ in 0..padding {
|
||||
self.writer.write_all(&[0])?;
|
||||
}
|
||||
self.offset += padding;
|
||||
|
||||
let value = value.as_std140();
|
||||
self.writer.write_all(bytes_of(&value))?;
|
||||
|
||||
let write_here = self.offset;
|
||||
self.offset += size_of::<T>();
|
||||
|
||||
Ok(write_here)
|
||||
}
|
||||
|
||||
/// Write a slice of values to the underlying buffer.
|
||||
#[deprecated(
|
||||
since = "0.6.0",
|
||||
note = "Use `write` instead -- it now works on slices."
|
||||
)]
|
||||
pub fn write_slice<T>(&mut self, slice: &[T]) -> io::Result<usize>
|
||||
where
|
||||
T: AsStd140,
|
||||
{
|
||||
self.write(slice)
|
||||
}
|
||||
|
||||
/// Returns the amount of data written by this `Writer`.
|
||||
pub fn len(&self) -> usize {
|
||||
self.offset
|
||||
}
|
||||
}
|
16
crates/crevice/src/std430.rs
Normal file
16
crates/crevice/src/std430.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
//! Defines traits and types for working with data adhering to GLSL's `std140`
|
||||
//! layout specification.
|
||||
|
||||
mod primitives;
|
||||
mod sizer;
|
||||
mod traits;
|
||||
#[cfg(feature = "std")]
|
||||
mod writer;
|
||||
|
||||
pub use self::primitives::*;
|
||||
pub use self::sizer::*;
|
||||
pub use self::traits::*;
|
||||
#[cfg(feature = "std")]
|
||||
pub use self::writer::*;
|
||||
|
||||
pub use crevice_derive::AsStd430;
|
173
crates/crevice/src/std430/primitives.rs
Normal file
173
crates/crevice/src/std430/primitives.rs
Normal file
|
@ -0,0 +1,173 @@
|
|||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
use crate::glsl::Glsl;
|
||||
use crate::std430::{Std430, Std430Padded};
|
||||
|
||||
use crate::internal::align_offset;
|
||||
use core::mem::size_of;
|
||||
|
||||
unsafe impl Std430 for f32 {
|
||||
const ALIGNMENT: usize = 4;
|
||||
type Padded = Self;
|
||||
}
|
||||
|
||||
unsafe impl Std430 for f64 {
|
||||
const ALIGNMENT: usize = 8;
|
||||
type Padded = Self;
|
||||
}
|
||||
|
||||
unsafe impl Std430 for i32 {
|
||||
const ALIGNMENT: usize = 4;
|
||||
type Padded = Self;
|
||||
}
|
||||
|
||||
unsafe impl Std430 for u32 {
|
||||
const ALIGNMENT: usize = 4;
|
||||
type Padded = Self;
|
||||
}
|
||||
|
||||
macro_rules! vectors {
|
||||
(
|
||||
$(
|
||||
#[$doc:meta] align($align:literal) $glsl_name:ident $name:ident <$prim:ident> ($($field:ident),+)
|
||||
)+
|
||||
) => {
|
||||
$(
|
||||
#[$doc]
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct $name {
|
||||
$(pub $field: $prim,)+
|
||||
}
|
||||
|
||||
unsafe impl Zeroable for $name {}
|
||||
unsafe impl Pod for $name {}
|
||||
|
||||
unsafe impl Std430 for $name {
|
||||
const ALIGNMENT: usize = $align;
|
||||
type Padded = Std430Padded<Self, {align_offset(size_of::<$name>(), $align)}>;
|
||||
}
|
||||
|
||||
unsafe impl Glsl for $name {
|
||||
const NAME: &'static str = stringify!($glsl_name);
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
vectors! {
|
||||
#[doc = "Corresponds to a GLSL `vec2` in std430 layout."] align(8) vec2 Vec2<f32>(x, y)
|
||||
#[doc = "Corresponds to a GLSL `vec3` in std430 layout."] align(16) vec3 Vec3<f32>(x, y, z)
|
||||
#[doc = "Corresponds to a GLSL `vec4` in std430 layout."] align(16) vec4 Vec4<f32>(x, y, z, w)
|
||||
|
||||
#[doc = "Corresponds to a GLSL `ivec2` in std430 layout."] align(8) ivec2 IVec2<i32>(x, y)
|
||||
#[doc = "Corresponds to a GLSL `ivec3` in std430 layout."] align(16) ivec3 IVec3<i32>(x, y, z)
|
||||
#[doc = "Corresponds to a GLSL `ivec4` in std430 layout."] align(16) ivec4 IVec4<i32>(x, y, z, w)
|
||||
|
||||
#[doc = "Corresponds to a GLSL `uvec2` in std430 layout."] align(8) uvec2 UVec2<u32>(x, y)
|
||||
#[doc = "Corresponds to a GLSL `uvec3` in std430 layout."] align(16) uvec3 UVec3<u32>(x, y, z)
|
||||
#[doc = "Corresponds to a GLSL `uvec4` in std430 layout."] align(16) uvec4 UVec4<u32>(x, y, z, w)
|
||||
|
||||
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
|
||||
|
||||
// #[doc = "Corresponds to a GLSL `bvec2` in std430 layout."] align(8) bvec2 BVec2<bool>(x, y)
|
||||
// #[doc = "Corresponds to a GLSL `bvec3` in std430 layout."] align(16) bvec3 BVec3<bool>(x, y, z)
|
||||
// #[doc = "Corresponds to a GLSL `bvec4` in std430 layout."] align(16) bvec4 BVec4<bool>(x, y, z, w)
|
||||
|
||||
#[doc = "Corresponds to a GLSL `dvec2` in std430 layout."] align(16) dvec2 DVec2<f64>(x, y)
|
||||
#[doc = "Corresponds to a GLSL `dvec3` in std430 layout."] align(32) dvec3 DVec3<f64>(x, y, z)
|
||||
#[doc = "Corresponds to a GLSL `dvec4` in std430 layout."] align(32) dvec4 DVec4<f64>(x, y, z, w)
|
||||
}
|
||||
|
||||
macro_rules! matrices {
|
||||
(
|
||||
$(
|
||||
#[$doc:meta]
|
||||
align($align:literal)
|
||||
$glsl_name:ident $name:ident {
|
||||
$($field:ident: $field_ty:ty,)+
|
||||
}
|
||||
)+
|
||||
) => {
|
||||
$(
|
||||
#[$doc]
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct $name {
|
||||
$(pub $field: $field_ty,)+
|
||||
}
|
||||
|
||||
unsafe impl Zeroable for $name {}
|
||||
unsafe impl Pod for $name {}
|
||||
|
||||
unsafe impl Std430 for $name {
|
||||
const ALIGNMENT: usize = $align;
|
||||
/// Matrices are technically arrays of primitives, and as such require pad at end.
|
||||
const PAD_AT_END: bool = true;
|
||||
type Padded = Std430Padded<Self, {align_offset(size_of::<$name>(), $align)}>;
|
||||
}
|
||||
|
||||
unsafe impl Glsl for $name {
|
||||
const NAME: &'static str = stringify!($glsl_name);
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
matrices! {
|
||||
#[doc = "Corresponds to a GLSL `mat2` in std430 layout."]
|
||||
align(8)
|
||||
mat2 Mat2 {
|
||||
x: Vec2,
|
||||
y: Vec2,
|
||||
}
|
||||
|
||||
#[doc = "Corresponds to a GLSL `mat3` in std430 layout."]
|
||||
align(16)
|
||||
mat3 Mat3 {
|
||||
x: Vec3,
|
||||
_pad_x: f32,
|
||||
y: Vec3,
|
||||
_pad_y: f32,
|
||||
z: Vec3,
|
||||
_pad_z: f32,
|
||||
}
|
||||
|
||||
#[doc = "Corresponds to a GLSL `mat4` in std430 layout."]
|
||||
align(16)
|
||||
mat4 Mat4 {
|
||||
x: Vec4,
|
||||
y: Vec4,
|
||||
z: Vec4,
|
||||
w: Vec4,
|
||||
}
|
||||
|
||||
#[doc = "Corresponds to a GLSL `dmat2` in std430 layout."]
|
||||
align(16)
|
||||
dmat2 DMat2 {
|
||||
x: DVec2,
|
||||
y: DVec2,
|
||||
}
|
||||
|
||||
#[doc = "Corresponds to a GLSL `dmat3` in std430 layout."]
|
||||
align(32)
|
||||
dmat3 DMat3 {
|
||||
x: DVec3,
|
||||
_pad_x: f64,
|
||||
y: DVec3,
|
||||
_pad_y: f64,
|
||||
z: DVec3,
|
||||
_pad_z: f64,
|
||||
}
|
||||
|
||||
#[doc = "Corresponds to a GLSL `dmat3` in std430 layout."]
|
||||
align(32)
|
||||
dmat4 DMat4 {
|
||||
x: DVec4,
|
||||
y: DVec4,
|
||||
z: DVec4,
|
||||
w: DVec4,
|
||||
}
|
||||
}
|
81
crates/crevice/src/std430/sizer.rs
Normal file
81
crates/crevice/src/std430/sizer.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
use core::mem::size_of;
|
||||
|
||||
use crate::internal::align_offset;
|
||||
use crate::std430::{AsStd430, Std430};
|
||||
|
||||
/**
|
||||
Type that computes the buffer size needed by a series of `std430` types laid
|
||||
out.
|
||||
|
||||
This type works well well when paired with `Writer`, precomputing a buffer's
|
||||
size to alleviate the need to dynamically re-allocate buffers.
|
||||
|
||||
## Example
|
||||
|
||||
```glsl
|
||||
struct Frob {
|
||||
vec3 size;
|
||||
float frobiness;
|
||||
}
|
||||
|
||||
buffer FROBS {
|
||||
uint len;
|
||||
Frob[] frobs;
|
||||
} frobs;
|
||||
```
|
||||
|
||||
```
|
||||
use crevice::std430::{self, AsStd430};
|
||||
|
||||
#[derive(AsStd430)]
|
||||
struct Frob {
|
||||
size: mint::Vector3<f32>,
|
||||
frobiness: f32,
|
||||
}
|
||||
|
||||
// Many APIs require that buffers contain at least enough space for all
|
||||
// fixed-size bindiongs to a buffer as well as one element of any arrays, if
|
||||
// there are any.
|
||||
let mut sizer = std430::Sizer::new();
|
||||
sizer.add::<u32>();
|
||||
sizer.add::<Frob>();
|
||||
|
||||
# fn create_buffer_with_size(size: usize) {}
|
||||
let buffer = create_buffer_with_size(sizer.len());
|
||||
# assert_eq!(sizer.len(), 32);
|
||||
```
|
||||
*/
|
||||
pub struct Sizer {
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl Sizer {
|
||||
/// Create a new `Sizer`.
|
||||
pub fn new() -> Self {
|
||||
Self { offset: 0 }
|
||||
}
|
||||
|
||||
/// Add a type's necessary padding and size to the `Sizer`. Returns the
|
||||
/// offset into the buffer where that type would be written.
|
||||
pub fn add<T>(&mut self) -> usize
|
||||
where
|
||||
T: AsStd430,
|
||||
{
|
||||
let size = size_of::<<T as AsStd430>::Output>();
|
||||
let alignment = <T as AsStd430>::Output::ALIGNMENT;
|
||||
let padding = align_offset(self.offset, alignment);
|
||||
|
||||
self.offset += padding;
|
||||
let write_here = self.offset;
|
||||
|
||||
self.offset += size;
|
||||
|
||||
write_here
|
||||
}
|
||||
|
||||
/// Returns the number of bytes required to contain all the types added to
|
||||
/// the `Sizer`.
|
||||
pub fn len(&self) -> usize {
|
||||
self.offset
|
||||
}
|
||||
}
|
283
crates/crevice/src/std430/traits.rs
Normal file
283
crates/crevice/src/std430/traits.rs
Normal file
|
@ -0,0 +1,283 @@
|
|||
use core::mem::{size_of, MaybeUninit};
|
||||
#[cfg(feature = "std")]
|
||||
use std::io::{self, Write};
|
||||
|
||||
use bytemuck::{bytes_of, Pod, Zeroable};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use crate::std430::Writer;
|
||||
|
||||
/// Trait implemented for all `std430` primitives. Generally should not be
|
||||
/// implemented outside this crate.
|
||||
pub unsafe trait Std430: Copy + Zeroable + Pod {
|
||||
/// The required alignment of the type. Must be a power of two.
|
||||
///
|
||||
/// This is distinct from the value returned by `std::mem::align_of` because
|
||||
/// `AsStd430` structs do not use Rust's alignment. This enables them to
|
||||
/// control and zero their padding bytes, making converting them to and from
|
||||
/// slices safe.
|
||||
const ALIGNMENT: usize;
|
||||
|
||||
/// Whether this type requires a padding at the end (ie, is a struct or an array
|
||||
/// of primitives).
|
||||
/// See <https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf#page=159>
|
||||
/// (rule 4 and 9)
|
||||
const PAD_AT_END: bool = false;
|
||||
/// Padded type (Std430Padded specialization)
|
||||
/// The usual implementation is
|
||||
/// type Padded = Std430Padded<Self, {align_offset(size_of::<Self>(), ALIGNMENT)}>;
|
||||
type Padded: Std430Convertible<Self>;
|
||||
|
||||
/// Casts the type to a byte array. Implementors should not override this
|
||||
/// method.
|
||||
///
|
||||
/// # Safety
|
||||
/// This is always safe due to the requirements of [`bytemuck::Pod`] being a
|
||||
/// prerequisite for this trait.
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
bytes_of(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait specifically for Std430::Padded, implements conversions between padded type and base type.
|
||||
pub trait Std430Convertible<T: Std430>: Copy {
|
||||
/// Convert from self to Std430
|
||||
fn into_std430(self) -> T;
|
||||
/// Convert from Std430 to self
|
||||
fn from_std430(_: T) -> Self;
|
||||
}
|
||||
|
||||
impl<T: Std430> Std430Convertible<T> for T {
|
||||
fn into_std430(self) -> T {
|
||||
self
|
||||
}
|
||||
fn from_std430(also_self: T) -> Self {
|
||||
also_self
|
||||
}
|
||||
}
|
||||
|
||||
/// Unfortunately, we cannot easily derive padded representation for generic Std140 types.
|
||||
/// For now, we'll just use this empty enum with no values.
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum InvalidPadded {}
|
||||
impl<T: Std430> Std430Convertible<T> for InvalidPadded {
|
||||
fn into_std430(self) -> T {
|
||||
unimplemented!()
|
||||
}
|
||||
fn from_std430(_: T) -> Self {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
/**
|
||||
Trait implemented for all types that can be turned into `std430` values.
|
||||
|
||||
This trait can often be `#[derive]`'d instead of manually implementing it. Any
|
||||
struct which contains only fields that also implement `AsStd430` can derive
|
||||
`AsStd430`.
|
||||
|
||||
Types from the mint crate implement `AsStd430`, making them convenient for use
|
||||
in uniform types. Most Rust geometry crates, like cgmath, nalgebra, and
|
||||
ultraviolet support mint.
|
||||
|
||||
## Example
|
||||
|
||||
```glsl
|
||||
uniform CAMERA {
|
||||
mat4 view;
|
||||
mat4 projection;
|
||||
} camera;
|
||||
```
|
||||
|
||||
```no_run
|
||||
use crevice::std430::{AsStd430, Std430};
|
||||
|
||||
#[derive(AsStd430)]
|
||||
struct CameraUniform {
|
||||
view: mint::ColumnMatrix4<f32>,
|
||||
projection: mint::ColumnMatrix4<f32>,
|
||||
}
|
||||
|
||||
let view: mint::ColumnMatrix4<f32> = todo!("your math code here");
|
||||
let projection: mint::ColumnMatrix4<f32> = todo!("your math code here");
|
||||
|
||||
let camera = CameraUniform {
|
||||
view,
|
||||
projection,
|
||||
};
|
||||
|
||||
# fn write_to_gpu_buffer(bytes: &[u8]) {}
|
||||
let camera_std430 = camera.as_std430();
|
||||
write_to_gpu_buffer(camera_std430.as_bytes());
|
||||
```
|
||||
*/
|
||||
pub trait AsStd430 {
|
||||
/// The `std430` version of this value.
|
||||
type Output: Std430;
|
||||
|
||||
/// Convert this value into the `std430` version of itself.
|
||||
fn as_std430(&self) -> Self::Output;
|
||||
|
||||
/// Returns the size of the `std430` version of this type. Useful for
|
||||
/// pre-sizing buffers.
|
||||
fn std430_size_static() -> usize {
|
||||
size_of::<Self::Output>()
|
||||
}
|
||||
|
||||
/// Converts from `std430` version of self to self.
|
||||
fn from_std430(value: Self::Output) -> Self;
|
||||
}
|
||||
|
||||
impl<T> AsStd430 for T
|
||||
where
|
||||
T: Std430,
|
||||
{
|
||||
type Output = Self;
|
||||
|
||||
fn as_std430(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
|
||||
fn from_std430(value: Self) -> Self {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Std430Padded<T: Std430, const PAD: usize> {
|
||||
inner: T,
|
||||
_padding: [u8; PAD],
|
||||
}
|
||||
|
||||
unsafe impl<T: Std430, const PAD: usize> Zeroable for Std430Padded<T, PAD> {}
|
||||
unsafe impl<T: Std430, const PAD: usize> Pod for Std430Padded<T, PAD> {}
|
||||
|
||||
impl<T: Std430, const PAD: usize> Std430Convertible<T> for Std430Padded<T, PAD> {
|
||||
fn into_std430(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
|
||||
fn from_std430(inner: T) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
_padding: [0u8; PAD],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct Std430Array<T: Std430, const N: usize>([T::Padded; N]);
|
||||
|
||||
unsafe impl<T: Std430, const N: usize> Zeroable for Std430Array<T, N> where T::Padded: Zeroable {}
|
||||
unsafe impl<T: Std430, const N: usize> Pod for Std430Array<T, N> where T::Padded: Pod {}
|
||||
unsafe impl<T: Std430, const N: usize> Std430 for Std430Array<T, N>
|
||||
where
|
||||
T::Padded: Pod,
|
||||
{
|
||||
const ALIGNMENT: usize = T::ALIGNMENT;
|
||||
type Padded = Self;
|
||||
}
|
||||
|
||||
impl<T: Std430, const N: usize> Std430Array<T, N> {
|
||||
fn uninit_array() -> [MaybeUninit<T::Padded>; N] {
|
||||
unsafe { MaybeUninit::uninit().assume_init() }
|
||||
}
|
||||
|
||||
fn from_uninit_array(a: [MaybeUninit<T::Padded>; N]) -> Self {
|
||||
unsafe { core::mem::transmute_copy(&a) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsStd430, const N: usize> AsStd430 for [T; N]
|
||||
where
|
||||
<T::Output as Std430>::Padded: Pod,
|
||||
{
|
||||
type Output = Std430Array<T::Output, N>;
|
||||
fn as_std430(&self) -> Self::Output {
|
||||
let mut res = Self::Output::uninit_array();
|
||||
|
||||
for i in 0..N {
|
||||
res[i] = MaybeUninit::new(Std430Convertible::from_std430(self[i].as_std430()));
|
||||
}
|
||||
|
||||
Self::Output::from_uninit_array(res)
|
||||
}
|
||||
|
||||
fn from_std430(val: Self::Output) -> Self {
|
||||
let mut res: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
|
||||
for i in 0..N {
|
||||
res[i] = MaybeUninit::new(T::from_std430(val.0[i].into_std430()));
|
||||
}
|
||||
unsafe { core::mem::transmute_copy(&res) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait implemented for all types that can be written into a buffer as
|
||||
/// `std430` bytes. This type is more general than [`AsStd430`]: all `AsStd430`
|
||||
/// types implement `WriteStd430`, but not the other way around.
|
||||
///
|
||||
/// While `AsStd430` requires implementers to return a type that implements the
|
||||
/// `Std430` trait, `WriteStd430` directly writes bytes using a [`Writer`]. This
|
||||
/// makes `WriteStd430` usable for writing slices or other DSTs that could not
|
||||
/// implement `AsStd430` without allocating new memory on the heap.
|
||||
#[cfg(feature = "std")]
|
||||
pub trait WriteStd430 {
|
||||
/// Writes this value into the given [`Writer`] using `std430` layout rules.
|
||||
///
|
||||
/// Should return the offset of the first byte of this type, as returned by
|
||||
/// the first call to [`Writer::write`].
|
||||
fn write_std430<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize>;
|
||||
|
||||
/// The space required to write this value using `std430` layout rules. This
|
||||
/// does not include alignment padding that may be needed before or after
|
||||
/// this type when written as part of a larger buffer.
|
||||
fn std430_size(&self) -> usize {
|
||||
let mut writer = Writer::new(io::sink());
|
||||
self.write_std430(&mut writer).unwrap();
|
||||
writer.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T> WriteStd430 for T
|
||||
where
|
||||
T: AsStd430,
|
||||
{
|
||||
fn write_std430<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize> {
|
||||
writer.write_std430(&self.as_std430())
|
||||
}
|
||||
|
||||
fn std430_size(&self) -> usize {
|
||||
size_of::<<Self as AsStd430>::Output>()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T> WriteStd430 for [T]
|
||||
where
|
||||
T: WriteStd430,
|
||||
{
|
||||
fn write_std430<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize> {
|
||||
let mut offset = writer.len();
|
||||
|
||||
let mut iter = self.iter();
|
||||
|
||||
if let Some(item) = iter.next() {
|
||||
offset = item.write_std430(writer)?;
|
||||
}
|
||||
|
||||
for item in self.iter() {
|
||||
item.write_std430(writer)?;
|
||||
}
|
||||
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
fn std430_size(&self) -> usize {
|
||||
let mut writer = Writer::new(io::sink());
|
||||
self.write_std430(&mut writer).unwrap();
|
||||
writer.len()
|
||||
}
|
||||
}
|
150
crates/crevice/src/std430/writer.rs
Normal file
150
crates/crevice/src/std430/writer.rs
Normal file
|
@ -0,0 +1,150 @@
|
|||
use std::io::{self, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use bytemuck::bytes_of;
|
||||
|
||||
use crate::internal::align_offset;
|
||||
use crate::std430::{AsStd430, Std430, WriteStd430};
|
||||
|
||||
/**
|
||||
Type that enables writing correctly aligned `std430` values to a buffer.
|
||||
|
||||
`Writer` is useful when many values need to be laid out in a row that cannot be
|
||||
represented by a struct alone, like dynamically sized arrays or dynamically
|
||||
laid-out values.
|
||||
|
||||
## Example
|
||||
In this example, we'll write a length-prefixed list of lights to a buffer.
|
||||
`std430::Writer` helps align correctly, even across multiple structs, which can
|
||||
be tricky and error-prone otherwise.
|
||||
|
||||
```glsl
|
||||
struct PointLight {
|
||||
vec3 position;
|
||||
vec3 color;
|
||||
float brightness;
|
||||
};
|
||||
|
||||
buffer POINT_LIGHTS {
|
||||
uint len;
|
||||
PointLight[] lights;
|
||||
} point_lights;
|
||||
```
|
||||
|
||||
```
|
||||
use crevice::std430::{self, AsStd430};
|
||||
|
||||
#[derive(AsStd430)]
|
||||
struct PointLight {
|
||||
position: mint::Vector3<f32>,
|
||||
color: mint::Vector3<f32>,
|
||||
brightness: f32,
|
||||
}
|
||||
|
||||
let lights = vec![
|
||||
PointLight {
|
||||
position: [0.0, 1.0, 0.0].into(),
|
||||
color: [1.0, 0.0, 0.0].into(),
|
||||
brightness: 0.6,
|
||||
},
|
||||
PointLight {
|
||||
position: [0.0, 4.0, 3.0].into(),
|
||||
color: [1.0, 1.0, 1.0].into(),
|
||||
brightness: 1.0,
|
||||
},
|
||||
];
|
||||
|
||||
# fn map_gpu_buffer_for_write() -> &'static mut [u8] {
|
||||
# Box::leak(vec![0; 1024].into_boxed_slice())
|
||||
# }
|
||||
let target_buffer = map_gpu_buffer_for_write();
|
||||
let mut writer = std430::Writer::new(target_buffer);
|
||||
|
||||
let light_count = lights.len() as u32;
|
||||
writer.write(&light_count)?;
|
||||
|
||||
// Crevice will automatically insert the required padding to align the
|
||||
// PointLight structure correctly. In this case, there will be 12 bytes of
|
||||
// padding between the length field and the light list.
|
||||
|
||||
writer.write(lights.as_slice())?;
|
||||
|
||||
# fn unmap_gpu_buffer() {}
|
||||
unmap_gpu_buffer();
|
||||
|
||||
# Ok::<(), std::io::Error>(())
|
||||
```
|
||||
*/
|
||||
pub struct Writer<W> {
|
||||
writer: W,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl<W: Write> Writer<W> {
|
||||
/// Create a new `Writer`, wrapping a buffer, file, or other type that
|
||||
/// implements [`std::io::Write`].
|
||||
pub fn new(writer: W) -> Self {
|
||||
Self { writer, offset: 0 }
|
||||
}
|
||||
|
||||
/// Write a new value to the underlying buffer, writing zeroed padding where
|
||||
/// necessary.
|
||||
///
|
||||
/// Returns the offset into the buffer that the value was written to.
|
||||
pub fn write<T>(&mut self, value: &T) -> io::Result<usize>
|
||||
where
|
||||
T: WriteStd430 + ?Sized,
|
||||
{
|
||||
value.write_std430(self)
|
||||
}
|
||||
|
||||
/// Write an iterator of values to the underlying buffer.
|
||||
///
|
||||
/// Returns the offset into the buffer that the first value was written to.
|
||||
/// If no values were written, returns the `len()`.
|
||||
pub fn write_iter<I, T>(&mut self, iter: I) -> io::Result<usize>
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: WriteStd430,
|
||||
{
|
||||
let mut offset = self.offset;
|
||||
|
||||
let mut iter = iter.into_iter();
|
||||
|
||||
if let Some(item) = iter.next() {
|
||||
offset = item.write_std430(self)?;
|
||||
}
|
||||
|
||||
for item in iter {
|
||||
item.write_std430(self)?;
|
||||
}
|
||||
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
/// Write an `Std430` type to the underlying buffer.
|
||||
pub fn write_std430<T>(&mut self, value: &T) -> io::Result<usize>
|
||||
where
|
||||
T: Std430,
|
||||
{
|
||||
let padding = align_offset(self.offset, T::ALIGNMENT);
|
||||
|
||||
for _ in 0..padding {
|
||||
self.writer.write_all(&[0])?;
|
||||
}
|
||||
self.offset += padding;
|
||||
|
||||
let value = value.as_std430();
|
||||
self.writer.write_all(bytes_of(&value))?;
|
||||
|
||||
let write_here = self.offset;
|
||||
self.offset += size_of::<T>();
|
||||
|
||||
Ok(write_here)
|
||||
}
|
||||
|
||||
/// Returns the amount of data written by this `Writer`.
|
||||
pub fn len(&self) -> usize {
|
||||
self.offset
|
||||
}
|
||||
}
|
97
crates/crevice/src/util.rs
Normal file
97
crates/crevice/src/util.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
#![allow(unused_macros)]
|
||||
|
||||
macro_rules! easy_impl {
|
||||
( $( $std_name:ident $imp_ty:ty { $($field:ident),* }, )* ) => {
|
||||
$(
|
||||
impl crate::std140::AsStd140 for $imp_ty {
|
||||
type Output = crate::std140::$std_name;
|
||||
|
||||
#[inline]
|
||||
fn as_std140(&self) -> Self::Output {
|
||||
crate::std140::$std_name {
|
||||
$(
|
||||
$field: self.$field.as_std140(),
|
||||
)*
|
||||
..bytemuck::Zeroable::zeroed()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_std140(value: Self::Output) -> Self {
|
||||
Self {
|
||||
$(
|
||||
$field: <_ as crate::std140::AsStd140>::from_std140(value.$field),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::std430::AsStd430 for $imp_ty {
|
||||
type Output = crate::std430::$std_name;
|
||||
|
||||
#[inline]
|
||||
fn as_std430(&self) -> Self::Output {
|
||||
crate::std430::$std_name {
|
||||
$(
|
||||
$field: self.$field.as_std430(),
|
||||
)*
|
||||
..bytemuck::Zeroable::zeroed()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_std430(value: Self::Output) -> Self {
|
||||
Self {
|
||||
$(
|
||||
$field: <_ as crate::std430::AsStd430>::from_std430(value.$field),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl crate::glsl::Glsl for $imp_ty {
|
||||
const NAME: &'static str = crate::std140::$std_name::NAME;
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! minty_impl {
|
||||
( $( $mint_ty:ty => $imp_ty:ty, )* ) => {
|
||||
$(
|
||||
impl crate::std140::AsStd140 for $imp_ty {
|
||||
type Output = <$mint_ty as crate::std140::AsStd140>::Output;
|
||||
|
||||
#[inline]
|
||||
fn as_std140(&self) -> Self::Output {
|
||||
let mint: $mint_ty = (*self).into();
|
||||
mint.as_std140()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_std140(value: Self::Output) -> Self {
|
||||
<$mint_ty>::from_std140(value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::std430::AsStd430 for $imp_ty {
|
||||
type Output = <$mint_ty as crate::std430::AsStd430>::Output;
|
||||
|
||||
#[inline]
|
||||
fn as_std430(&self) -> Self::Output {
|
||||
let mint: $mint_ty = (*self).into();
|
||||
mint.as_std430()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_std430(value: Self::Output) -> Self {
|
||||
<$mint_ty>::from_std430(value).into()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl crate::glsl::Glsl for $imp_ty {
|
||||
const NAME: &'static str = <$mint_ty>::NAME;
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
source: tests/test.rs
|
||||
expression: "TestGlsl::glsl_definition()"
|
||||
|
||||
---
|
||||
struct TestGlsl {
|
||||
vec3 foo[8][4];
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
source: tests/test.rs
|
||||
expression: "TestGlsl::glsl_definition()"
|
||||
|
||||
---
|
||||
struct TestGlsl {
|
||||
vec3 foo;
|
||||
mat2 bar;
|
||||
};
|
61
crates/crevice/tests/test.rs
Normal file
61
crates/crevice/tests/test.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use crevice::glsl::GlslStruct;
|
||||
use crevice::std140::AsStd140;
|
||||
|
||||
#[test]
|
||||
fn there_and_back_again() {
|
||||
#[derive(AsStd140, Debug, PartialEq)]
|
||||
struct ThereAndBackAgain {
|
||||
view: mint::ColumnMatrix3<f32>,
|
||||
origin: mint::Vector3<f32>,
|
||||
}
|
||||
|
||||
let x = ThereAndBackAgain {
|
||||
view: mint::ColumnMatrix3 {
|
||||
x: mint::Vector3 {
|
||||
x: 1.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
},
|
||||
y: mint::Vector3 {
|
||||
x: 0.0,
|
||||
y: 1.0,
|
||||
z: 0.0,
|
||||
},
|
||||
z: mint::Vector3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 1.0,
|
||||
},
|
||||
},
|
||||
origin: mint::Vector3 {
|
||||
x: 0.0,
|
||||
y: 1.0,
|
||||
z: 2.0,
|
||||
},
|
||||
};
|
||||
let x_as = x.as_std140();
|
||||
assert_eq!(<ThereAndBackAgain as AsStd140>::from_std140(x_as), x);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_struct_glsl() {
|
||||
#[allow(dead_code)]
|
||||
#[derive(GlslStruct)]
|
||||
struct TestGlsl {
|
||||
foo: mint::Vector3<f32>,
|
||||
bar: mint::ColumnMatrix2<f32>,
|
||||
}
|
||||
|
||||
insta::assert_display_snapshot!(TestGlsl::glsl_definition());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_struct_array_glsl() {
|
||||
#[allow(dead_code)]
|
||||
#[derive(GlslStruct)]
|
||||
struct TestGlsl {
|
||||
foo: [[mint::Vector3<f32>; 8]; 4],
|
||||
}
|
||||
|
||||
insta::assert_display_snapshot!(TestGlsl::glsl_definition());
|
||||
}
|
94
examples/2d/pipelined_texture_atlas.rs
Normal file
94
examples/2d/pipelined_texture_atlas.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
use bevy::{
|
||||
asset::LoadState,
|
||||
math::Vec3,
|
||||
prelude::{
|
||||
App, AssetServer, Assets, Commands, HandleUntyped, IntoSystem, Res, ResMut, State,
|
||||
SystemSet, Transform,
|
||||
},
|
||||
render2::{camera::OrthographicCameraBundle, texture::Image},
|
||||
sprite2::{
|
||||
PipelinedSpriteBundle, PipelinedSpriteSheetBundle, TextureAtlas, TextureAtlasBuilder,
|
||||
TextureAtlasSprite,
|
||||
},
|
||||
PipelinedDefaultPlugins,
|
||||
};
|
||||
|
||||
/// In this example we generate a new texture atlas (sprite sheet) from a folder containing
|
||||
/// individual sprites
|
||||
fn main() {
|
||||
App::new()
|
||||
.init_resource::<RpgSpriteHandles>()
|
||||
.add_plugins(PipelinedDefaultPlugins)
|
||||
.add_state(AppState::Setup)
|
||||
.add_system_set(SystemSet::on_enter(AppState::Setup).with_system(load_textures.system()))
|
||||
.add_system_set(SystemSet::on_update(AppState::Setup).with_system(check_textures.system()))
|
||||
.add_system_set(SystemSet::on_enter(AppState::Finished).with_system(setup.system()))
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
enum AppState {
|
||||
Setup,
|
||||
Finished,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RpgSpriteHandles {
|
||||
handles: Vec<HandleUntyped>,
|
||||
}
|
||||
|
||||
fn load_textures(mut rpg_sprite_handles: ResMut<RpgSpriteHandles>, asset_server: Res<AssetServer>) {
|
||||
rpg_sprite_handles.handles = asset_server.load_folder("textures/rpg").unwrap();
|
||||
}
|
||||
|
||||
fn check_textures(
|
||||
mut state: ResMut<State<AppState>>,
|
||||
rpg_sprite_handles: ResMut<RpgSpriteHandles>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
if let LoadState::Loaded =
|
||||
asset_server.get_group_load_state(rpg_sprite_handles.handles.iter().map(|handle| handle.id))
|
||||
{
|
||||
state.set(AppState::Finished).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
rpg_sprite_handles: Res<RpgSpriteHandles>,
|
||||
asset_server: Res<AssetServer>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
||||
mut textures: ResMut<Assets<Image>>,
|
||||
) {
|
||||
let mut texture_atlas_builder = TextureAtlasBuilder::default();
|
||||
for handle in rpg_sprite_handles.handles.iter() {
|
||||
let texture = textures.get(handle).unwrap();
|
||||
texture_atlas_builder.add_texture(handle.clone_weak().typed::<Image>(), texture);
|
||||
}
|
||||
|
||||
let texture_atlas = texture_atlas_builder.finish(&mut textures).unwrap();
|
||||
let texture_atlas_texture = texture_atlas.texture.clone();
|
||||
let vendor_handle = asset_server.get_handle("textures/rpg/chars/vendor/generic-rpg-vendor.png");
|
||||
let vendor_index = texture_atlas.get_texture_index(&vendor_handle).unwrap();
|
||||
let atlas_handle = texture_atlases.add(texture_atlas);
|
||||
|
||||
// set up a scene to display our texture atlas
|
||||
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
|
||||
// draw a sprite from the atlas
|
||||
commands.spawn_bundle(PipelinedSpriteSheetBundle {
|
||||
transform: Transform {
|
||||
translation: Vec3::new(150.0, 0.0, 0.0),
|
||||
scale: Vec3::splat(4.0),
|
||||
..Default::default()
|
||||
},
|
||||
sprite: TextureAtlasSprite::new(vendor_index),
|
||||
texture_atlas: atlas_handle,
|
||||
..Default::default()
|
||||
});
|
||||
// draw the atlas itself
|
||||
commands.spawn_bundle(PipelinedSpriteBundle {
|
||||
texture: texture_atlas_texture,
|
||||
transform: Transform::from_xyz(-300.0, 0.0, 0.0),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
255
examples/3d/3d_scene_pipelined.rs
Normal file
255
examples/3d/3d_scene_pipelined.rs
Normal file
|
@ -0,0 +1,255 @@
|
|||
use bevy::{
|
||||
core::Time,
|
||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
ecs::prelude::*,
|
||||
input::Input,
|
||||
math::{Quat, Vec3},
|
||||
pbr2::{
|
||||
AmbientLight, DirectionalLight, DirectionalLightBundle, PbrBundle, PointLight,
|
||||
PointLightBundle, StandardMaterial,
|
||||
},
|
||||
prelude::{App, Assets, BuildChildren, KeyCode, Transform},
|
||||
render2::{
|
||||
camera::{OrthographicProjection, PerspectiveCameraBundle},
|
||||
color::Color,
|
||||
mesh::{shape, Mesh},
|
||||
},
|
||||
PipelinedDefaultPlugins,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(PipelinedDefaultPlugins)
|
||||
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||
.add_plugin(LogDiagnosticsPlugin::default())
|
||||
.add_startup_system(setup)
|
||||
.add_system(movement)
|
||||
.add_system(animate_light_direction)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct Movable;
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// ground plane
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Plane { size: 10.0 })),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::WHITE,
|
||||
perceptual_roughness: 1.0,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// left wall
|
||||
let mut transform = Transform::from_xyz(2.5, 2.5, 0.0);
|
||||
transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2));
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Box::new(5.0, 0.15, 5.0))),
|
||||
transform,
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::INDIGO,
|
||||
perceptual_roughness: 1.0,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
// back (right) wall
|
||||
let mut transform = Transform::from_xyz(0.0, 2.5, -2.5);
|
||||
transform.rotate(Quat::from_rotation_x(std::f32::consts::FRAC_PI_2));
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Box::new(5.0, 0.15, 5.0))),
|
||||
transform,
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::INDIGO,
|
||||
perceptual_roughness: 1.0,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// cube
|
||||
commands
|
||||
.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::PINK,
|
||||
..Default::default()
|
||||
}),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..Default::default()
|
||||
})
|
||||
.insert(Movable);
|
||||
// sphere
|
||||
commands
|
||||
.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::UVSphere {
|
||||
radius: 0.5,
|
||||
..Default::default()
|
||||
})),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::LIME_GREEN,
|
||||
..Default::default()
|
||||
}),
|
||||
transform: Transform::from_xyz(1.5, 1.0, 1.5),
|
||||
..Default::default()
|
||||
})
|
||||
.insert(Movable);
|
||||
|
||||
// ambient light
|
||||
commands.insert_resource(AmbientLight {
|
||||
color: Color::ORANGE_RED,
|
||||
brightness: 0.02,
|
||||
});
|
||||
|
||||
// red point light
|
||||
commands
|
||||
.spawn_bundle(PointLightBundle {
|
||||
// transform: Transform::from_xyz(5.0, 8.0, 2.0),
|
||||
transform: Transform::from_xyz(1.0, 2.0, 0.0),
|
||||
point_light: PointLight {
|
||||
intensity: 1600.0, // lumens - roughly a 100W non-halogen incandescent bulb
|
||||
color: Color::RED,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.with_children(|builder| {
|
||||
builder.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::UVSphere {
|
||||
radius: 0.1,
|
||||
..Default::default()
|
||||
})),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::RED,
|
||||
emissive: Color::rgba_linear(100.0, 0.0, 0.0, 0.0),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
});
|
||||
|
||||
// green point light
|
||||
commands
|
||||
.spawn_bundle(PointLightBundle {
|
||||
// transform: Transform::from_xyz(5.0, 8.0, 2.0),
|
||||
transform: Transform::from_xyz(-1.0, 2.0, 0.0),
|
||||
point_light: PointLight {
|
||||
intensity: 1600.0, // lumens - roughly a 100W non-halogen incandescent bulb
|
||||
color: Color::GREEN,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.with_children(|builder| {
|
||||
builder.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::UVSphere {
|
||||
radius: 0.1,
|
||||
..Default::default()
|
||||
})),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::GREEN,
|
||||
emissive: Color::rgba_linear(0.0, 100.0, 0.0, 0.0),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
});
|
||||
|
||||
// blue point light
|
||||
commands
|
||||
.spawn_bundle(PointLightBundle {
|
||||
// transform: Transform::from_xyz(5.0, 8.0, 2.0),
|
||||
transform: Transform::from_xyz(0.0, 4.0, 0.0),
|
||||
point_light: PointLight {
|
||||
intensity: 1600.0, // lumens - roughly a 100W non-halogen incandescent bulb
|
||||
color: Color::BLUE,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.with_children(|builder| {
|
||||
builder.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::UVSphere {
|
||||
radius: 0.1,
|
||||
..Default::default()
|
||||
})),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::BLUE,
|
||||
emissive: Color::rgba_linear(0.0, 0.0, 100.0, 0.0),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
});
|
||||
|
||||
// directional 'sun' light
|
||||
const HALF_SIZE: f32 = 10.0;
|
||||
commands.spawn_bundle(DirectionalLightBundle {
|
||||
directional_light: DirectionalLight {
|
||||
// Configure the projection to better fit the scene
|
||||
shadow_projection: OrthographicProjection {
|
||||
left: -HALF_SIZE,
|
||||
right: HALF_SIZE,
|
||||
bottom: -HALF_SIZE,
|
||||
top: HALF_SIZE,
|
||||
near: -10.0 * HALF_SIZE,
|
||||
far: 10.0 * HALF_SIZE,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
transform: Transform {
|
||||
translation: Vec3::new(0.0, 2.0, 0.0),
|
||||
rotation: Quat::from_rotation_x(-std::f32::consts::FRAC_PI_4),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// camera
|
||||
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
fn animate_light_direction(
|
||||
time: Res<Time>,
|
||||
mut query: Query<&mut Transform, With<DirectionalLight>>,
|
||||
) {
|
||||
for mut transform in query.iter_mut() {
|
||||
transform.rotate(Quat::from_rotation_y(time.delta_seconds() * 0.5));
|
||||
}
|
||||
}
|
||||
|
||||
fn movement(
|
||||
input: Res<Input<KeyCode>>,
|
||||
time: Res<Time>,
|
||||
mut query: Query<&mut Transform, With<Movable>>,
|
||||
) {
|
||||
for mut transform in query.iter_mut() {
|
||||
let mut direction = Vec3::ZERO;
|
||||
if input.pressed(KeyCode::Up) {
|
||||
direction.y += 1.0;
|
||||
}
|
||||
if input.pressed(KeyCode::Down) {
|
||||
direction.y -= 1.0;
|
||||
}
|
||||
if input.pressed(KeyCode::Left) {
|
||||
direction.x -= 1.0;
|
||||
}
|
||||
if input.pressed(KeyCode::Right) {
|
||||
direction.x += 1.0;
|
||||
}
|
||||
|
||||
transform.translation += time.delta_seconds() * 2.0 * direction;
|
||||
}
|
||||
}
|
172
examples/3d/cornell_box_pipelined.rs
Normal file
172
examples/3d/cornell_box_pipelined.rs
Normal file
|
@ -0,0 +1,172 @@
|
|||
use bevy::{
|
||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
ecs::prelude::*,
|
||||
math::{Mat4, Quat, Vec3},
|
||||
pbr2::{
|
||||
AmbientLight, DirectionalLight, DirectionalLightBundle, PbrBundle, PointLight,
|
||||
PointLightBundle, StandardMaterial,
|
||||
},
|
||||
prelude::{App, Assets, BuildChildren, Transform},
|
||||
render2::{
|
||||
camera::{OrthographicProjection, PerspectiveCameraBundle},
|
||||
color::Color,
|
||||
mesh::{shape, Mesh},
|
||||
},
|
||||
PipelinedDefaultPlugins,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(PipelinedDefaultPlugins)
|
||||
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||
.add_plugin(LogDiagnosticsPlugin::default())
|
||||
.add_startup_system(setup)
|
||||
.run();
|
||||
}
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
let box_size = 2.0;
|
||||
let box_thickness = 0.15;
|
||||
let box_offset = (box_size + box_thickness) / 2.0;
|
||||
|
||||
// left - red
|
||||
let mut transform = Transform::from_xyz(-box_offset, box_offset, 0.0);
|
||||
transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2));
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Box::new(
|
||||
box_size,
|
||||
box_thickness,
|
||||
box_size,
|
||||
))),
|
||||
transform,
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::rgb(0.63, 0.065, 0.05),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
// right - green
|
||||
let mut transform = Transform::from_xyz(box_offset, box_offset, 0.0);
|
||||
transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2));
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Box::new(
|
||||
box_size,
|
||||
box_thickness,
|
||||
box_size,
|
||||
))),
|
||||
transform,
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::rgb(0.14, 0.45, 0.091),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
// bottom - white
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Box::new(
|
||||
box_size + 2.0 * box_thickness,
|
||||
box_thickness,
|
||||
box_size,
|
||||
))),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::rgb(0.725, 0.71, 0.68),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
// top - white
|
||||
let transform = Transform::from_xyz(0.0, 2.0 * box_offset, 0.0);
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Box::new(
|
||||
box_size + 2.0 * box_thickness,
|
||||
box_thickness,
|
||||
box_size,
|
||||
))),
|
||||
transform,
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::rgb(0.725, 0.71, 0.68),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
// back - white
|
||||
let mut transform = Transform::from_xyz(0.0, box_offset, -box_offset);
|
||||
transform.rotate(Quat::from_rotation_x(std::f32::consts::FRAC_PI_2));
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Box::new(
|
||||
box_size + 2.0 * box_thickness,
|
||||
box_thickness,
|
||||
box_size + 2.0 * box_thickness,
|
||||
))),
|
||||
transform,
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::rgb(0.725, 0.71, 0.68),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// ambient light
|
||||
commands.insert_resource(AmbientLight {
|
||||
color: Color::WHITE,
|
||||
brightness: 0.02,
|
||||
});
|
||||
// top light
|
||||
commands
|
||||
.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Plane { size: 0.4 })),
|
||||
transform: Transform::from_matrix(Mat4::from_scale_rotation_translation(
|
||||
Vec3::ONE,
|
||||
Quat::from_rotation_x(std::f32::consts::PI),
|
||||
Vec3::new(0.0, box_size + 0.5 * box_thickness, 0.0),
|
||||
)),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::WHITE,
|
||||
emissive: Color::WHITE * 100.0,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
})
|
||||
.with_children(|builder| {
|
||||
builder.spawn_bundle(PointLightBundle {
|
||||
point_light: PointLight {
|
||||
color: Color::WHITE,
|
||||
intensity: 25.0,
|
||||
..Default::default()
|
||||
},
|
||||
transform: Transform::from_translation((box_thickness + 0.05) * Vec3::Y),
|
||||
..Default::default()
|
||||
});
|
||||
});
|
||||
// directional light
|
||||
const HALF_SIZE: f32 = 10.0;
|
||||
commands.spawn_bundle(DirectionalLightBundle {
|
||||
directional_light: DirectionalLight {
|
||||
illuminance: 10000.0,
|
||||
shadow_projection: OrthographicProjection {
|
||||
left: -HALF_SIZE,
|
||||
right: HALF_SIZE,
|
||||
bottom: -HALF_SIZE,
|
||||
top: HALF_SIZE,
|
||||
near: -10.0 * HALF_SIZE,
|
||||
far: 10.0 * HALF_SIZE,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::PI / 2.0)),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// camera
|
||||
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||
transform: Transform::from_xyz(0.0, box_offset, 4.0)
|
||||
.looking_at(Vec3::new(0.0, box_offset, 0.0), Vec3::Y),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
62
examples/3d/load_gltf_pipelined.rs
Normal file
62
examples/3d/load_gltf_pipelined.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
use bevy::{
|
||||
core::Time,
|
||||
ecs::prelude::*,
|
||||
math::{EulerRot, Quat, Vec3},
|
||||
pbr2::{AmbientLight, DirectionalLight, DirectionalLightBundle},
|
||||
prelude::{App, AssetServer, SpawnSceneCommands, Transform},
|
||||
render2::{
|
||||
camera::{OrthographicProjection, PerspectiveCameraBundle},
|
||||
color::Color,
|
||||
},
|
||||
PipelinedDefaultPlugins,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.insert_resource(AmbientLight {
|
||||
color: Color::WHITE,
|
||||
brightness: 1.0 / 5.0f32,
|
||||
})
|
||||
.add_plugins(PipelinedDefaultPlugins)
|
||||
.add_startup_system(setup.system())
|
||||
.add_system(animate_light_direction.system())
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
commands.spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"));
|
||||
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||
transform: Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
|
||||
..Default::default()
|
||||
});
|
||||
const HALF_SIZE: f32 = 1.0;
|
||||
commands.spawn_bundle(DirectionalLightBundle {
|
||||
directional_light: DirectionalLight {
|
||||
shadow_projection: OrthographicProjection {
|
||||
left: -HALF_SIZE,
|
||||
right: HALF_SIZE,
|
||||
bottom: -HALF_SIZE,
|
||||
top: HALF_SIZE,
|
||||
near: -10.0 * HALF_SIZE,
|
||||
far: 10.0 * HALF_SIZE,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
fn animate_light_direction(
|
||||
time: Res<Time>,
|
||||
mut query: Query<&mut Transform, With<DirectionalLight>>,
|
||||
) {
|
||||
for mut transform in query.iter_mut() {
|
||||
transform.rotation = Quat::from_euler(
|
||||
EulerRot::ZYX,
|
||||
0.0,
|
||||
time.seconds_since_startup() as f32 * std::f32::consts::TAU / 10.0,
|
||||
-std::f32::consts::FRAC_PI_4,
|
||||
);
|
||||
}
|
||||
}
|
50
examples/3d/many_cubes_pipelined.rs
Normal file
50
examples/3d/many_cubes_pipelined.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use bevy::{
|
||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
ecs::prelude::*,
|
||||
pbr2::{PbrBundle, StandardMaterial},
|
||||
prelude::{App, Assets, Transform},
|
||||
render2::{
|
||||
camera::PerspectiveCameraBundle,
|
||||
color::Color,
|
||||
mesh::{shape, Mesh},
|
||||
},
|
||||
PipelinedDefaultPlugins,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(PipelinedDefaultPlugins)
|
||||
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||
.add_plugin(LogDiagnosticsPlugin::default())
|
||||
.add_startup_system(setup.system())
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
const WIDTH: usize = 100;
|
||||
const HEIGHT: usize = 100;
|
||||
for x in 0..WIDTH {
|
||||
for y in 0..HEIGHT {
|
||||
// cube
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::PINK,
|
||||
..Default::default()
|
||||
}),
|
||||
transform: Transform::from_xyz((x as f32) * 2.0, (y as f32) * 2.0, 0.0),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// camera
|
||||
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||
transform: Transform::from_xyz(80.0, 80.0, 300.0),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
68
examples/3d/msaa_pipelined.rs
Normal file
68
examples/3d/msaa_pipelined.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
use bevy::{
|
||||
ecs::prelude::*,
|
||||
input::Input,
|
||||
math::Vec3,
|
||||
pbr2::{PbrBundle, PointLightBundle, StandardMaterial},
|
||||
prelude::{App, Assets, KeyCode, Transform},
|
||||
render2::{
|
||||
camera::PerspectiveCameraBundle,
|
||||
color::Color,
|
||||
mesh::{shape, Mesh},
|
||||
view::Msaa,
|
||||
},
|
||||
PipelinedDefaultPlugins,
|
||||
};
|
||||
|
||||
/// This example shows how to configure Multi-Sample Anti-Aliasing. Setting the sample count higher
|
||||
/// will result in smoother edges, but it will also increase the cost to render those edges. The
|
||||
/// range should generally be somewhere between 1 (no multi sampling, but cheap) to 8 (crisp but
|
||||
/// expensive).
|
||||
/// Note that WGPU currently only supports 1 or 4 samples.
|
||||
/// Ultimately we plan on supporting whatever is natively supported on a given device.
|
||||
/// Check out this issue for more info: https://github.com/gfx-rs/wgpu/issues/1832
|
||||
fn main() {
|
||||
println!("Press 'm' to toggle MSAA");
|
||||
println!("Using 4x MSAA");
|
||||
App::new()
|
||||
.insert_resource(Msaa { samples: 4 })
|
||||
.add_plugins(PipelinedDefaultPlugins)
|
||||
.add_startup_system(setup)
|
||||
.add_system(cycle_msaa)
|
||||
.run();
|
||||
}
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// cube
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 2.0 })),
|
||||
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
|
||||
..Default::default()
|
||||
});
|
||||
// light
|
||||
commands.spawn_bundle(PointLightBundle {
|
||||
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||
..Default::default()
|
||||
});
|
||||
// camera
|
||||
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||
transform: Transform::from_xyz(-3.0, 3.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
fn cycle_msaa(input: Res<Input<KeyCode>>, mut msaa: ResMut<Msaa>) {
|
||||
if input.just_pressed(KeyCode::M) {
|
||||
if msaa.samples == 4 {
|
||||
println!("Not using MSAA");
|
||||
msaa.samples = 1;
|
||||
} else {
|
||||
println!("Using 4x MSAA");
|
||||
msaa.samples = 4;
|
||||
}
|
||||
}
|
||||
}
|
71
examples/3d/orthographic_pipelined.rs
Normal file
71
examples/3d/orthographic_pipelined.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use bevy::{
|
||||
ecs::prelude::*,
|
||||
math::Vec3,
|
||||
pbr2::{PbrBundle, PointLightBundle, StandardMaterial},
|
||||
prelude::{App, Assets, Transform},
|
||||
render2::{
|
||||
camera::OrthographicCameraBundle,
|
||||
color::Color,
|
||||
mesh::{shape, Mesh},
|
||||
},
|
||||
PipelinedDefaultPlugins,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(PipelinedDefaultPlugins)
|
||||
.add_startup_system(setup.system())
|
||||
.run();
|
||||
}
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// set up the camera
|
||||
let mut camera = OrthographicCameraBundle::new_3d();
|
||||
camera.orthographic_projection.scale = 3.0;
|
||||
camera.transform = Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y);
|
||||
|
||||
// camera
|
||||
commands.spawn_bundle(camera);
|
||||
|
||||
// plane
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })),
|
||||
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
|
||||
..Default::default()
|
||||
});
|
||||
// cubes
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
|
||||
transform: Transform::from_xyz(1.5, 0.5, 1.5),
|
||||
..Default::default()
|
||||
});
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
|
||||
transform: Transform::from_xyz(1.5, 0.5, -1.5),
|
||||
..Default::default()
|
||||
});
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
|
||||
transform: Transform::from_xyz(-1.5, 0.5, 1.5),
|
||||
..Default::default()
|
||||
});
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
|
||||
transform: Transform::from_xyz(-1.5, 0.5, -1.5),
|
||||
..Default::default()
|
||||
});
|
||||
// light
|
||||
commands.spawn_bundle(PointLightBundle {
|
||||
transform: Transform::from_xyz(3.0, 8.0, 5.0),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
86
examples/3d/pbr_pipelined.rs
Normal file
86
examples/3d/pbr_pipelined.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use bevy::{
|
||||
ecs::prelude::*,
|
||||
math::Vec3,
|
||||
pbr2::{PbrBundle, PointLight, PointLightBundle, StandardMaterial},
|
||||
prelude::{App, Assets, Transform},
|
||||
render2::{
|
||||
camera::{OrthographicCameraBundle, OrthographicProjection},
|
||||
color::Color,
|
||||
mesh::{shape, Mesh},
|
||||
},
|
||||
PipelinedDefaultPlugins,
|
||||
};
|
||||
|
||||
/// This example shows how to configure Physically Based Rendering (PBR) parameters.
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(PipelinedDefaultPlugins)
|
||||
.add_startup_system(setup.system())
|
||||
.run();
|
||||
}
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// add entities to the world
|
||||
for y in -2..=2 {
|
||||
for x in -5..=5 {
|
||||
let x01 = (x + 5) as f32 / 10.0;
|
||||
let y01 = (y + 2) as f32 / 4.0;
|
||||
// sphere
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Icosphere {
|
||||
radius: 0.45,
|
||||
subdivisions: 32,
|
||||
})),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::hex("ffd891").unwrap(),
|
||||
// vary key PBR parameters on a grid of spheres to show the effect
|
||||
metallic: y01,
|
||||
perceptual_roughness: x01,
|
||||
..Default::default()
|
||||
}),
|
||||
transform: Transform::from_xyz(x as f32, y as f32 + 0.5, 0.0),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
// unlit sphere
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Icosphere {
|
||||
radius: 0.45,
|
||||
subdivisions: 32,
|
||||
})),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::hex("ffd891").unwrap(),
|
||||
// vary key PBR parameters on a grid of spheres to show the effect
|
||||
unlit: true,
|
||||
..Default::default()
|
||||
}),
|
||||
transform: Transform::from_xyz(-5.0, -2.5, 0.0),
|
||||
..Default::default()
|
||||
});
|
||||
// light
|
||||
commands.spawn_bundle(PointLightBundle {
|
||||
transform: Transform::from_translation(Vec3::new(50.0, 50.0, 50.0)),
|
||||
point_light: PointLight {
|
||||
intensity: 50000.,
|
||||
range: 100.,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
// camera
|
||||
commands.spawn_bundle(OrthographicCameraBundle {
|
||||
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 8.0))
|
||||
.looking_at(Vec3::default(), Vec3::Y),
|
||||
orthographic_projection: OrthographicProjection {
|
||||
scale: 0.01,
|
||||
..Default::default()
|
||||
},
|
||||
..OrthographicCameraBundle::new_3d()
|
||||
});
|
||||
}
|
|
@ -13,7 +13,7 @@ use bevy::{
|
|||
},
|
||||
texture::{
|
||||
Extent3d, SamplerDescriptor, TextureDescriptor, TextureDimension, TextureFormat,
|
||||
TextureUsage,
|
||||
TextureUsages,
|
||||
},
|
||||
},
|
||||
window::WindowId,
|
||||
|
@ -66,7 +66,7 @@ fn add_render_to_texture_graph(graph: &mut RenderGraph, size: Extent3d) {
|
|||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
format: Default::default(),
|
||||
usage: TextureUsage::OUTPUT_ATTACHMENT | TextureUsage::SAMPLED,
|
||||
usage: TextureUsages::OUTPUT_ATTACHMENT | TextureUsages::SAMPLED,
|
||||
},
|
||||
Some(SamplerDescriptor::default()),
|
||||
Some(RENDER_TEXTURE_HANDLE),
|
||||
|
@ -82,7 +82,7 @@ fn add_render_to_texture_graph(graph: &mut RenderGraph, size: Extent3d) {
|
|||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
format: TextureFormat::Depth32Float,
|
||||
usage: TextureUsage::OUTPUT_ATTACHMENT | TextureUsage::SAMPLED,
|
||||
usage: TextureUsages::OUTPUT_ATTACHMENT | TextureUsages::SAMPLED,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
|
|
335
examples/3d/shadow_biases_pipelined.rs
Normal file
335
examples/3d/shadow_biases_pipelined.rs
Normal file
|
@ -0,0 +1,335 @@
|
|||
use bevy::{
|
||||
core::Time,
|
||||
ecs::prelude::*,
|
||||
input::{mouse::MouseMotion, Input},
|
||||
math::{EulerRot, Mat4, Quat, Vec2, Vec3},
|
||||
pbr2::{
|
||||
DirectionalLight, DirectionalLightBundle, PbrBundle, PointLight, PointLightBundle,
|
||||
StandardMaterial,
|
||||
},
|
||||
prelude::{App, Assets, KeyCode, Transform},
|
||||
render2::{
|
||||
camera::{Camera, OrthographicProjection, PerspectiveCameraBundle},
|
||||
color::Color,
|
||||
mesh::{shape, Mesh},
|
||||
},
|
||||
PipelinedDefaultPlugins,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
println!(
|
||||
"Controls:
|
||||
WSAD - forward/back/strafe left/right
|
||||
LShift - 'run'
|
||||
E - up
|
||||
Q - down
|
||||
L - switch between directional and point lights
|
||||
1/2 - decrease/increase point light depth bias
|
||||
3/4 - decrease/increase point light normal bias
|
||||
5/6 - decrease/increase direction light depth bias
|
||||
7/8 - decrease/increase direction light normal bias"
|
||||
);
|
||||
App::new()
|
||||
.add_plugins(PipelinedDefaultPlugins)
|
||||
.add_startup_system(setup)
|
||||
.add_system(adjust_point_light_biases)
|
||||
.add_system(toggle_light)
|
||||
.add_system(adjust_directional_light_biases)
|
||||
.add_system(camera_controller)
|
||||
.run();
|
||||
}
|
||||
|
||||
/// set up a 3D scene to test shadow biases and perspective projections
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
let spawn_plane_depth = 500.0f32;
|
||||
let spawn_height = 2.0;
|
||||
let sphere_radius = 0.25;
|
||||
|
||||
let white_handle = materials.add(StandardMaterial {
|
||||
base_color: Color::WHITE,
|
||||
perceptual_roughness: 1.0,
|
||||
..Default::default()
|
||||
});
|
||||
let sphere_handle = meshes.add(Mesh::from(shape::Icosphere {
|
||||
radius: sphere_radius,
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
println!("Using DirectionalLight");
|
||||
|
||||
commands.spawn_bundle(PointLightBundle {
|
||||
transform: Transform::from_xyz(5.0, 5.0, 0.0),
|
||||
point_light: PointLight {
|
||||
intensity: 0.0,
|
||||
range: spawn_plane_depth,
|
||||
color: Color::WHITE,
|
||||
shadow_depth_bias: 0.0,
|
||||
shadow_normal_bias: 0.0,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let theta = std::f32::consts::FRAC_PI_4;
|
||||
let light_transform = Mat4::from_euler(EulerRot::ZYX, 0.0, std::f32::consts::FRAC_PI_2, -theta);
|
||||
commands.spawn_bundle(DirectionalLightBundle {
|
||||
directional_light: DirectionalLight {
|
||||
illuminance: 100000.0,
|
||||
shadow_projection: OrthographicProjection {
|
||||
left: -0.35,
|
||||
right: 500.35,
|
||||
bottom: -0.1,
|
||||
top: 5.0,
|
||||
near: -5.0,
|
||||
far: 5.0,
|
||||
..Default::default()
|
||||
},
|
||||
shadow_depth_bias: 0.0,
|
||||
shadow_normal_bias: 0.0,
|
||||
..Default::default()
|
||||
},
|
||||
transform: Transform::from_matrix(light_transform),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// camera
|
||||
commands
|
||||
.spawn_bundle(PerspectiveCameraBundle {
|
||||
transform: Transform::from_xyz(-1.0, 1.0, 1.0)
|
||||
.looking_at(Vec3::new(-1.0, 1.0, 0.0), Vec3::Y),
|
||||
..Default::default()
|
||||
})
|
||||
.insert(CameraController::default());
|
||||
|
||||
for z_i32 in -spawn_plane_depth as i32..=0 {
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: sphere_handle.clone(),
|
||||
material: white_handle.clone(),
|
||||
transform: Transform::from_xyz(0.0, spawn_height, z_i32 as f32),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
// ground plane
|
||||
commands.spawn_bundle(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Plane {
|
||||
size: 2.0 * spawn_plane_depth,
|
||||
})),
|
||||
material: white_handle,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
fn toggle_light(
|
||||
input: Res<Input<KeyCode>>,
|
||||
mut point_lights: Query<&mut PointLight>,
|
||||
mut directional_lights: Query<&mut DirectionalLight>,
|
||||
) {
|
||||
if input.just_pressed(KeyCode::L) {
|
||||
for mut light in point_lights.iter_mut() {
|
||||
light.intensity = if light.intensity == 0.0 {
|
||||
println!("Using PointLight");
|
||||
100000000.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
}
|
||||
for mut light in directional_lights.iter_mut() {
|
||||
light.illuminance = if light.illuminance == 0.0 {
|
||||
println!("Using DirectionalLight");
|
||||
100000.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn adjust_point_light_biases(input: Res<Input<KeyCode>>, mut query: Query<&mut PointLight>) {
|
||||
let depth_bias_step_size = 0.01;
|
||||
let normal_bias_step_size = 0.1;
|
||||
for mut light in query.iter_mut() {
|
||||
if input.just_pressed(KeyCode::Key1) {
|
||||
light.shadow_depth_bias -= depth_bias_step_size;
|
||||
println!("PointLight shadow_depth_bias: {}", light.shadow_depth_bias);
|
||||
}
|
||||
if input.just_pressed(KeyCode::Key2) {
|
||||
light.shadow_depth_bias += depth_bias_step_size;
|
||||
println!("PointLight shadow_depth_bias: {}", light.shadow_depth_bias);
|
||||
}
|
||||
if input.just_pressed(KeyCode::Key3) {
|
||||
light.shadow_normal_bias -= normal_bias_step_size;
|
||||
println!(
|
||||
"PointLight shadow_normal_bias: {}",
|
||||
light.shadow_normal_bias
|
||||
);
|
||||
}
|
||||
if input.just_pressed(KeyCode::Key4) {
|
||||
light.shadow_normal_bias += normal_bias_step_size;
|
||||
println!(
|
||||
"PointLight shadow_normal_bias: {}",
|
||||
light.shadow_normal_bias
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn adjust_directional_light_biases(
|
||||
input: Res<Input<KeyCode>>,
|
||||
mut query: Query<&mut DirectionalLight>,
|
||||
) {
|
||||
let depth_bias_step_size = 0.01;
|
||||
let normal_bias_step_size = 0.1;
|
||||
for mut light in query.iter_mut() {
|
||||
if input.just_pressed(KeyCode::Key5) {
|
||||
light.shadow_depth_bias -= depth_bias_step_size;
|
||||
println!(
|
||||
"DirectionalLight shadow_depth_bias: {}",
|
||||
light.shadow_depth_bias
|
||||
);
|
||||
}
|
||||
if input.just_pressed(KeyCode::Key6) {
|
||||
light.shadow_depth_bias += depth_bias_step_size;
|
||||
println!(
|
||||
"DirectionalLight shadow_depth_bias: {}",
|
||||
light.shadow_depth_bias
|
||||
);
|
||||
}
|
||||
if input.just_pressed(KeyCode::Key7) {
|
||||
light.shadow_normal_bias -= normal_bias_step_size;
|
||||
println!(
|
||||
"DirectionalLight shadow_normal_bias: {}",
|
||||
light.shadow_normal_bias
|
||||
);
|
||||
}
|
||||
if input.just_pressed(KeyCode::Key8) {
|
||||
light.shadow_normal_bias += normal_bias_step_size;
|
||||
println!(
|
||||
"DirectionalLight shadow_normal_bias: {}",
|
||||
light.shadow_normal_bias
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct CameraController {
|
||||
pub enabled: bool,
|
||||
pub sensitivity: f32,
|
||||
pub key_forward: KeyCode,
|
||||
pub key_back: KeyCode,
|
||||
pub key_left: KeyCode,
|
||||
pub key_right: KeyCode,
|
||||
pub key_up: KeyCode,
|
||||
pub key_down: KeyCode,
|
||||
pub key_run: KeyCode,
|
||||
pub walk_speed: f32,
|
||||
pub run_speed: f32,
|
||||
pub friction: f32,
|
||||
pub pitch: f32,
|
||||
pub yaw: f32,
|
||||
pub velocity: Vec3,
|
||||
}
|
||||
|
||||
impl Default for CameraController {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
sensitivity: 0.5,
|
||||
key_forward: KeyCode::W,
|
||||
key_back: KeyCode::S,
|
||||
key_left: KeyCode::A,
|
||||
key_right: KeyCode::D,
|
||||
key_up: KeyCode::E,
|
||||
key_down: KeyCode::Q,
|
||||
key_run: KeyCode::LShift,
|
||||
walk_speed: 10.0,
|
||||
run_speed: 30.0,
|
||||
friction: 0.5,
|
||||
pitch: 0.0,
|
||||
yaw: 0.0,
|
||||
velocity: Vec3::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn camera_controller(
|
||||
time: Res<Time>,
|
||||
mut mouse_events: EventReader<MouseMotion>,
|
||||
key_input: Res<Input<KeyCode>>,
|
||||
mut query: Query<(&mut Transform, &mut CameraController), With<Camera>>,
|
||||
) {
|
||||
let dt = time.delta_seconds();
|
||||
|
||||
// Handle mouse input
|
||||
let mut mouse_delta = Vec2::ZERO;
|
||||
for mouse_event in mouse_events.iter() {
|
||||
mouse_delta += mouse_event.delta;
|
||||
}
|
||||
|
||||
for (mut transform, mut options) in query.iter_mut() {
|
||||
if !options.enabled {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle key input
|
||||
let mut axis_input = Vec3::ZERO;
|
||||
if key_input.pressed(options.key_forward) {
|
||||
axis_input.z += 1.0;
|
||||
}
|
||||
if key_input.pressed(options.key_back) {
|
||||
axis_input.z -= 1.0;
|
||||
}
|
||||
if key_input.pressed(options.key_right) {
|
||||
axis_input.x += 1.0;
|
||||
}
|
||||
if key_input.pressed(options.key_left) {
|
||||
axis_input.x -= 1.0;
|
||||
}
|
||||
if key_input.pressed(options.key_up) {
|
||||
axis_input.y += 1.0;
|
||||
}
|
||||
if key_input.pressed(options.key_down) {
|
||||
axis_input.y -= 1.0;
|
||||
}
|
||||
|
||||
// Apply movement update
|
||||
if axis_input != Vec3::ZERO {
|
||||
let max_speed = if key_input.pressed(options.key_run) {
|
||||
options.run_speed
|
||||
} else {
|
||||
options.walk_speed
|
||||
};
|
||||
options.velocity = axis_input.normalize() * max_speed;
|
||||
} else {
|
||||
let friction = options.friction.clamp(0.0, 1.0);
|
||||
options.velocity *= 1.0 - friction;
|
||||
if options.velocity.length_squared() < 1e-6 {
|
||||
options.velocity = Vec3::ZERO;
|
||||
}
|
||||
}
|
||||
let forward = transform.forward();
|
||||
let right = transform.right();
|
||||
transform.translation += options.velocity.x * dt * right
|
||||
+ options.velocity.y * dt * Vec3::Y
|
||||
+ options.velocity.z * dt * forward;
|
||||
|
||||
if mouse_delta != Vec2::ZERO {
|
||||
// Apply look update
|
||||
let (pitch, yaw) = (
|
||||
(options.pitch - mouse_delta.y * 0.5 * options.sensitivity * dt).clamp(
|
||||
-0.99 * std::f32::consts::FRAC_PI_2,
|
||||
0.99 * std::f32::consts::FRAC_PI_2,
|
||||
),
|
||||
options.yaw - mouse_delta.x * options.sensitivity * dt,
|
||||
);
|
||||
transform.rotation = Quat::from_euler(EulerRot::ZYX, 0.0, yaw, pitch);
|
||||
options.pitch = pitch;
|
||||
options.yaw = yaw;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue