Merge pull request #3175 from cart/merge-renderer

Merge New Renderer
This commit is contained in:
Carter Anderson 2021-11-24 13:31:17 -08:00 committed by GitHub
commit 0bf90bb98d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
194 changed files with 24520 additions and 326 deletions

View file

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

View 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;
}

View 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;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -38,6 +38,8 @@ pub mod prelude {
};
}
pub use bevy_ecs_macros::all_tuples;
#[cfg(test)]
mod tests {
use crate as bevy_ecs;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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());
}
}
}

View file

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

View file

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

View file

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

View file

@ -11,3 +11,4 @@ keywords = ["bevy"]
[dependencies]
cargo-manifest = "0.2.6"
syn = "1.0"
quote = "1.0"

View file

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

View file

@ -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"] }

View file

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

View file

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

View file

@ -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,
},
),
);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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))
}
}
};
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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!"
),
}
}

View file

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

View file

@ -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();
}
}

View file

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

View file

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

View file

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

View file

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

View 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
}
}

View file

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

View file

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

View file

@ -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,
});
}
}

View file

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

View 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.

View 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
View 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
View 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.

View 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"

View 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 )*
}
}
}
}

View 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
}
}

View 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)
}

View 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 }

View 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)
}

View 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(),
],
})
}

View 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));
}
}};
}

View 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
View 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;

View 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 },
}

View 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,
}

View 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 },
}

View 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>,
}

View 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
View 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;

View 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;

View 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);
}
}

View 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,
}
}

View 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
}
}

View 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()
}
}

View 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
}
}

View 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;

View 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,
}
}

View 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
}
}

View 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()
}
}

View 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
}
}

View 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;
}
)*
};
}

View file

@ -0,0 +1,8 @@
---
source: tests/test.rs
expression: "TestGlsl::glsl_definition()"
---
struct TestGlsl {
vec3 foo[8][4];
};

View file

@ -0,0 +1,9 @@
---
source: tests/test.rs
expression: "TestGlsl::glsl_definition()"
---
struct TestGlsl {
vec3 foo;
mat2 bar;
};

View 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());
}

View 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()
});
}

View 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;
}
}

View 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()
});
}

View 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,
);
}
}

View 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()
});
}

View 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;
}
}
}

View 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()
});
}

View 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()
});
}

View file

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

View 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