mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
commit
0bf90bb98d
194 changed files with 24520 additions and 326 deletions
75
Cargo.toml
75
Cargo.toml
|
@ -13,14 +13,18 @@ repository = "https://github.com/bevyengine/bevy"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
exclude = ["benches", "crates/bevy_ecs_compile_fail_tests"]
|
exclude = ["benches", "crates/bevy_ecs_compile_fail_tests"]
|
||||||
members = ["crates/*", "examples/ios", "tools/ci", "errors"]
|
members = ["crates/*", "pipelined/*", "examples/ios", "tools/ci", "errors"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [
|
default = [
|
||||||
"bevy_audio",
|
"bevy_audio",
|
||||||
|
"bevy_core_pipeline",
|
||||||
"bevy_gilrs",
|
"bevy_gilrs",
|
||||||
"bevy_gltf",
|
"bevy_gltf2",
|
||||||
"bevy_wgpu",
|
"bevy_wgpu",
|
||||||
|
"bevy_sprite2",
|
||||||
|
"bevy_render2",
|
||||||
|
"bevy_pbr2",
|
||||||
"bevy_winit",
|
"bevy_winit",
|
||||||
"render",
|
"render",
|
||||||
"png",
|
"png",
|
||||||
|
@ -50,6 +54,12 @@ bevy_gltf = ["bevy_internal/bevy_gltf"]
|
||||||
bevy_wgpu = ["bevy_internal/bevy_wgpu"]
|
bevy_wgpu = ["bevy_internal/bevy_wgpu"]
|
||||||
bevy_winit = ["bevy_internal/bevy_winit"]
|
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_chrome = ["bevy_internal/trace_chrome"]
|
||||||
trace_tracy = ["bevy_internal/trace_tracy"]
|
trace_tracy = ["bevy_internal/trace_tracy"]
|
||||||
trace = ["bevy_internal/trace"]
|
trace = ["bevy_internal/trace"]
|
||||||
|
@ -95,6 +105,7 @@ ron = "0.6.2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
# Needed to poll Task examples
|
# Needed to poll Task examples
|
||||||
futures-lite = "1.11.3"
|
futures-lite = "1.11.3"
|
||||||
|
crevice = { path = "crates/crevice", version = "0.8.0", features = ["glam"] }
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "hello_world"
|
name = "hello_world"
|
||||||
|
@ -133,23 +144,51 @@ path = "examples/2d/text2d.rs"
|
||||||
name = "texture_atlas"
|
name = "texture_atlas"
|
||||||
path = "examples/2d/texture_atlas.rs"
|
path = "examples/2d/texture_atlas.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "pipelined_texture_atlas"
|
||||||
|
path = "examples/2d/pipelined_texture_atlas.rs"
|
||||||
|
|
||||||
# 3D Rendering
|
# 3D Rendering
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "3d_scene"
|
name = "3d_scene"
|
||||||
path = "examples/3d/3d_scene.rs"
|
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]]
|
[[example]]
|
||||||
name = "load_gltf"
|
name = "load_gltf"
|
||||||
path = "examples/3d/load_gltf.rs"
|
path = "examples/3d/load_gltf.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "load_gltf_pipelined"
|
||||||
|
path = "examples/3d/load_gltf_pipelined.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "msaa"
|
name = "msaa"
|
||||||
path = "examples/3d/msaa.rs"
|
path = "examples/3d/msaa.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "msaa_pipelined"
|
||||||
|
path = "examples/3d/msaa_pipelined.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "orthographic"
|
name = "orthographic"
|
||||||
path = "examples/3d/orthographic.rs"
|
path = "examples/3d/orthographic.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "orthographic_pipelined"
|
||||||
|
path = "examples/3d/orthographic_pipelined.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "parenting"
|
name = "parenting"
|
||||||
path = "examples/3d/parenting.rs"
|
path = "examples/3d/parenting.rs"
|
||||||
|
@ -158,10 +197,22 @@ path = "examples/3d/parenting.rs"
|
||||||
name = "pbr"
|
name = "pbr"
|
||||||
path = "examples/3d/pbr.rs"
|
path = "examples/3d/pbr.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "pbr_pipelined"
|
||||||
|
path = "examples/3d/pbr_pipelined.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "render_to_texture"
|
name = "render_to_texture"
|
||||||
path = "examples/3d/render_to_texture.rs"
|
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]]
|
[[example]]
|
||||||
name = "spawner"
|
name = "spawner"
|
||||||
path = "examples/3d/spawner.rs"
|
path = "examples/3d/spawner.rs"
|
||||||
|
@ -170,6 +221,10 @@ path = "examples/3d/spawner.rs"
|
||||||
name = "texture"
|
name = "texture"
|
||||||
path = "examples/3d/texture.rs"
|
path = "examples/3d/texture.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "texture_pipelined"
|
||||||
|
path = "examples/3d/texture_pipelined.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "update_gltf_scene"
|
name = "update_gltf_scene"
|
||||||
path = "examples/3d/update_gltf_scene.rs"
|
path = "examples/3d/update_gltf_scene.rs"
|
||||||
|
@ -413,11 +468,23 @@ path = "examples/shader/shader_custom_material.rs"
|
||||||
name = "shader_defs"
|
name = "shader_defs"
|
||||||
path = "examples/shader/shader_defs.rs"
|
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
|
# Tools
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "bevymark"
|
name = "bevymark"
|
||||||
path = "examples/tools/bevymark.rs"
|
path = "examples/tools/bevymark.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "bevymark_pipelined"
|
||||||
|
path = "examples/tools/bevymark_pipelined.rs"
|
||||||
|
|
||||||
# UI (User Interface)
|
# UI (User Interface)
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "button"
|
name = "button"
|
||||||
|
@ -444,6 +511,10 @@ path = "examples/ui/ui.rs"
|
||||||
name = "clear_color"
|
name = "clear_color"
|
||||||
path = "examples/window/clear_color.rs"
|
path = "examples/window/clear_color.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "clear_color_pipelined"
|
||||||
|
path = "examples/window/clear_color_pipelined.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "multiple_windows"
|
name = "multiple_windows"
|
||||||
path = "examples/window/multiple_windows.rs"
|
path = "examples/window/multiple_windows.rs"
|
||||||
|
|
11
assets/shaders/custom_material.wgsl
Normal file
11
assets/shaders/custom_material.wgsl
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[[block]]
|
||||||
|
struct CustomMaterial {
|
||||||
|
color: vec4<f32>;
|
||||||
|
};
|
||||||
|
[[group(1), binding(0)]]
|
||||||
|
var<uniform> material: CustomMaterial;
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fragment() -> [[location(0)]] vec4<f32> {
|
||||||
|
return material.color;
|
||||||
|
}
|
33
assets/shaders/shader_defs.wgsl
Normal file
33
assets/shaders/shader_defs.wgsl
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#import bevy_pbr::mesh_view_bind_group
|
||||||
|
#import bevy_pbr::mesh_struct
|
||||||
|
|
||||||
|
[[group(1), binding(0)]]
|
||||||
|
var<uniform> mesh: Mesh;
|
||||||
|
|
||||||
|
struct Vertex {
|
||||||
|
[[location(0)]] position: vec3<f32>;
|
||||||
|
[[location(1)]] normal: vec3<f32>;
|
||||||
|
[[location(2)]] uv: vec2<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
[[builtin(position)]] clip_position: vec4<f32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[stage(vertex)]]
|
||||||
|
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||||
|
let world_position = mesh.model * vec4<f32>(vertex.position, 1.0);
|
||||||
|
|
||||||
|
var out: VertexOutput;
|
||||||
|
out.clip_position = view.view_proj * world_position;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[stage(fragment)]]
|
||||||
|
fn fragment() -> [[location(0)]] vec4<f32> {
|
||||||
|
var color = vec4<f32>(0.0, 0.0, 1.0, 1.0);
|
||||||
|
# ifdef IS_RED
|
||||||
|
color = vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||||
|
# endif
|
||||||
|
return color;
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{CoreStage, Events, Plugin, PluginGroup, PluginGroupBuilder, StartupStage};
|
use crate::{CoreStage, Events, Plugin, PluginGroup, PluginGroupBuilder, StartupStage};
|
||||||
|
pub use bevy_derive::AppLabel;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
prelude::{FromWorld, IntoExclusiveSystem},
|
prelude::{FromWorld, IntoExclusiveSystem},
|
||||||
schedule::{
|
schedule::{
|
||||||
|
@ -8,12 +9,14 @@ use bevy_ecs::{
|
||||||
system::Resource,
|
system::Resource,
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
use bevy_utils::tracing::debug;
|
use bevy_utils::{tracing::debug, HashMap};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
use bevy_utils::tracing::info_span;
|
use bevy_utils::tracing::info_span;
|
||||||
|
|
||||||
|
bevy_utils::define_label!(AppLabel);
|
||||||
|
|
||||||
#[allow(clippy::needless_doctest_main)]
|
#[allow(clippy::needless_doctest_main)]
|
||||||
/// Containers of app logic and data
|
/// Containers of app logic and data
|
||||||
///
|
///
|
||||||
|
@ -42,6 +45,12 @@ pub struct App {
|
||||||
pub world: World,
|
pub world: World,
|
||||||
pub runner: Box<dyn Fn(App)>,
|
pub runner: Box<dyn Fn(App)>,
|
||||||
pub schedule: Schedule,
|
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 {
|
impl Default for App {
|
||||||
|
@ -73,6 +82,7 @@ impl App {
|
||||||
world: Default::default(),
|
world: Default::default(),
|
||||||
schedule: Default::default(),
|
schedule: Default::default(),
|
||||||
runner: Box::new(run_once),
|
runner: Box::new(run_once),
|
||||||
|
sub_apps: HashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +95,9 @@ impl App {
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
let _bevy_frame_update_guard = bevy_frame_update_span.enter();
|
let _bevy_frame_update_guard = bevy_frame_update_span.enter();
|
||||||
self.schedule.run(&mut self.world);
|
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).
|
/// Starts the application by calling the app's [runner function](Self::set_runner).
|
||||||
|
@ -823,6 +836,39 @@ impl App {
|
||||||
}
|
}
|
||||||
self
|
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) {
|
fn run_once(mut app: App) {
|
||||||
|
|
|
@ -22,16 +22,15 @@ fn ci_testing_exit_after(
|
||||||
*current_frame += 1;
|
*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 =
|
let filename =
|
||||||
std::env::var("CI_TESTING_CONFIG").unwrap_or_else(|_| "ci_testing_config.ron".to_string());
|
std::env::var("CI_TESTING_CONFIG").unwrap_or_else(|_| "ci_testing_config.ron".to_string());
|
||||||
let config: CiTestingConfig = ron::from_str(
|
let config: CiTestingConfig = ron::from_str(
|
||||||
&std::fs::read_to_string(filename).expect("error reading CI testing configuration file"),
|
&std::fs::read_to_string(filename).expect("error reading CI testing configuration file"),
|
||||||
)
|
)
|
||||||
.expect("error deserializing CI testing configuration file");
|
.expect("error deserializing CI testing configuration file");
|
||||||
app_builder
|
app.insert_resource(config)
|
||||||
.insert_resource(config)
|
|
||||||
.add_system(ci_testing_exit_after);
|
.add_system(ci_testing_exit_after);
|
||||||
|
|
||||||
app_builder
|
app
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,7 @@ impl<T: Asset> Handle<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn weak(id: HandleId) -> Self {
|
pub fn weak(id: HandleId) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
|
@ -129,6 +130,7 @@ impl<T: Asset> Handle<T> {
|
||||||
self.handle_type = HandleType::Strong(sender);
|
self.handle_type = HandleType::Strong(sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn clone_weak(&self) -> Self {
|
pub fn clone_weak(&self) -> Self {
|
||||||
Handle::weak(self.id)
|
Handle::weak(self.id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,13 +43,19 @@ impl<T: Asset> LoadedAsset<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_dependency(mut self, asset_path: AssetPath) -> Self {
|
pub fn add_dependency(&mut self, asset_path: AssetPath) {
|
||||||
self.dependencies.push(asset_path.to_owned());
|
self.dependencies.push(asset_path.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_dependency(mut self, asset_path: AssetPath) -> Self {
|
||||||
|
self.add_dependency(asset_path);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_dependencies(mut self, asset_paths: Vec<AssetPath<'static>>) -> Self {
|
pub fn with_dependencies(mut self, mut asset_paths: Vec<AssetPath<'static>>) -> Self {
|
||||||
self.dependencies.extend(asset_paths);
|
for asset_path in asset_paths.drain(..) {
|
||||||
|
self.add_dependency(asset_path);
|
||||||
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,9 @@ mod render_resource;
|
||||||
mod render_resources;
|
mod render_resources;
|
||||||
mod shader_defs;
|
mod shader_defs;
|
||||||
|
|
||||||
|
use bevy_macro_utils::{derive_label, BevyManifest};
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
use quote::format_ident;
|
||||||
|
|
||||||
/// Derives the Bytes trait. Each field must also implements Bytes or this will fail.
|
/// Derives the Bytes trait. Each field must also implements Bytes or this will fail.
|
||||||
#[proc_macro_derive(Bytes)]
|
#[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 {
|
pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream {
|
||||||
enum_variant_meta::derive_enum_variant_meta(input)
|
enum_variant_meta::derive_enum_variant_meta(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(AppLabel)]
|
||||||
|
pub fn derive_app_label(input: TokenStream) -> TokenStream {
|
||||||
|
let input = syn::parse_macro_input!(input as syn::DeriveInput);
|
||||||
|
let mut trait_path = BevyManifest::default().get_path("bevy_app");
|
||||||
|
trait_path.segments.push(format_ident!("AppLabel").into());
|
||||||
|
derive_label(input, trait_path)
|
||||||
|
}
|
||||||
|
|
|
@ -2,16 +2,16 @@ extern crate proc_macro;
|
||||||
|
|
||||||
mod component;
|
mod component;
|
||||||
|
|
||||||
use bevy_macro_utils::BevyManifest;
|
use bevy_macro_utils::{derive_label, BevyManifest};
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
use proc_macro2::Span;
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use syn::{
|
use syn::{
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
parse_macro_input,
|
parse_macro_input,
|
||||||
punctuated::Punctuated,
|
punctuated::Punctuated,
|
||||||
token::Comma,
|
token::Comma,
|
||||||
Data, DataStruct, DeriveInput, Field, Fields, GenericParam, Ident, Index, LitInt, Path, Result,
|
Data, DataStruct, DeriveInput, Field, Fields, GenericParam, Ident, Index, LitInt, Result,
|
||||||
Token,
|
Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -429,46 +429,43 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
||||||
#[proc_macro_derive(SystemLabel)]
|
#[proc_macro_derive(SystemLabel)]
|
||||||
pub fn derive_system_label(input: TokenStream) -> TokenStream {
|
pub fn derive_system_label(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let mut trait_path = bevy_ecs_path();
|
||||||
derive_label(input, Ident::new("SystemLabel", Span::call_site())).into()
|
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)]
|
#[proc_macro_derive(StageLabel)]
|
||||||
pub fn derive_stage_label(input: TokenStream) -> TokenStream {
|
pub fn derive_stage_label(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
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)]
|
#[proc_macro_derive(AmbiguitySetLabel)]
|
||||||
pub fn derive_ambiguity_set_label(input: TokenStream) -> TokenStream {
|
pub fn derive_ambiguity_set_label(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
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)]
|
#[proc_macro_derive(RunCriteriaLabel)]
|
||||||
pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream {
|
pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
derive_label(input, Ident::new("RunCriteriaLabel", Span::call_site())).into()
|
let mut trait_path = bevy_ecs_path();
|
||||||
}
|
trait_path.segments.push(format_ident!("schedule").into());
|
||||||
|
trait_path
|
||||||
fn derive_label(input: DeriveInput, label_type: Ident) -> TokenStream2 {
|
.segments
|
||||||
let ident = input.ident;
|
.push(format_ident!("RunCriteriaLabel").into());
|
||||||
let ecs_path: Path = bevy_ecs_path();
|
derive_label(input, trait_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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn bevy_ecs_path() -> syn::Path {
|
pub(crate) fn bevy_ecs_path() -> syn::Path {
|
||||||
|
|
|
@ -38,6 +38,8 @@ pub mod prelude {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use bevy_ecs_macros::all_tuples;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate as bevy_ecs;
|
use crate as bevy_ecs;
|
||||||
|
|
|
@ -45,6 +45,8 @@ pub trait WorldQuery {
|
||||||
type State: FetchState;
|
type State: FetchState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type QueryItem<'w, 's, Q> = <<Q as WorldQuery>::Fetch as Fetch<'w, 's>>::Item;
|
||||||
|
|
||||||
pub trait Fetch<'world, 'state>: Sized {
|
pub trait Fetch<'world, 'state>: Sized {
|
||||||
type Item;
|
type Item;
|
||||||
type State: FetchState;
|
type State: FetchState;
|
||||||
|
|
|
@ -66,7 +66,7 @@ where
|
||||||
matched_archetypes: Default::default(),
|
matched_archetypes: Default::default(),
|
||||||
archetype_component_access: Default::default(),
|
archetype_component_access: Default::default(),
|
||||||
};
|
};
|
||||||
state.validate_world_and_update_archetypes(world);
|
state.update_archetypes(world);
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,11 +87,8 @@ where
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the `world.id()` does not equal the current [`QueryState`] internal id.
|
/// Panics if the `world.id()` does not equal the current [`QueryState`] internal id.
|
||||||
pub fn validate_world_and_update_archetypes(&mut self, world: &World) {
|
pub fn update_archetypes(&mut self, world: &World) {
|
||||||
if world.id() != self.world_id {
|
self.validate_world(world);
|
||||||
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>());
|
|
||||||
}
|
|
||||||
let archetypes = world.archetypes();
|
let archetypes = world.archetypes();
|
||||||
let new_generation = archetypes.generation();
|
let new_generation = archetypes.generation();
|
||||||
let old_generation = std::mem::replace(&mut self.archetype_generation, new_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`].
|
/// Creates a new [`Archetype`].
|
||||||
pub fn new_archetype(&mut self, archetype: &Archetype) {
|
pub fn new_archetype(&mut self, archetype: &Archetype) {
|
||||||
if self.fetch_state.matches_archetype(archetype)
|
if self.fetch_state.matches_archetype(archetype)
|
||||||
|
@ -153,6 +158,27 @@ where
|
||||||
unsafe { self.get_unchecked(world, entity) }
|
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`].
|
/// Gets the query result for the given [`World`] and [`Entity`].
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -165,7 +191,7 @@ where
|
||||||
world: &'w World,
|
world: &'w World,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
) -> Result<<Q::Fetch as Fetch<'w, 's>>::Item, QueryEntityError> {
|
) -> Result<<Q::Fetch as Fetch<'w, 's>>::Item, QueryEntityError> {
|
||||||
self.validate_world_and_update_archetypes(world);
|
self.update_archetypes(world);
|
||||||
self.get_unchecked_manual(
|
self.get_unchecked_manual(
|
||||||
world,
|
world,
|
||||||
entity,
|
entity,
|
||||||
|
@ -232,6 +258,28 @@ where
|
||||||
unsafe { self.iter_unchecked(world) }
|
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.
|
/// Returns an [`Iterator`] over all possible combinations of `K` query results without repetition.
|
||||||
/// This can only be called for read-only queries.
|
/// This can only be called for read-only queries.
|
||||||
///
|
///
|
||||||
|
@ -281,7 +329,7 @@ where
|
||||||
&'s mut self,
|
&'s mut self,
|
||||||
world: &'w World,
|
world: &'w World,
|
||||||
) -> QueryIter<'w, 's, Q, F> {
|
) -> 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())
|
self.iter_unchecked_manual(world, world.last_change_tick(), world.read_change_tick())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,7 +346,7 @@ where
|
||||||
&'s mut self,
|
&'s mut self,
|
||||||
world: &'w World,
|
world: &'w World,
|
||||||
) -> QueryCombinationIter<'w, 's, Q, F, K> {
|
) -> QueryCombinationIter<'w, 's, Q, F, K> {
|
||||||
self.validate_world_and_update_archetypes(world);
|
self.update_archetypes(world);
|
||||||
self.iter_combinations_unchecked_manual(
|
self.iter_combinations_unchecked_manual(
|
||||||
world,
|
world,
|
||||||
world.last_change_tick(),
|
world.last_change_tick(),
|
||||||
|
@ -392,7 +440,7 @@ where
|
||||||
world: &'w World,
|
world: &'w World,
|
||||||
func: impl FnMut(<Q::Fetch as Fetch<'w, 's>>::Item),
|
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(
|
self.for_each_unchecked_manual(
|
||||||
world,
|
world,
|
||||||
func,
|
func,
|
||||||
|
@ -452,7 +500,7 @@ where
|
||||||
batch_size: usize,
|
batch_size: usize,
|
||||||
func: impl Fn(<Q::Fetch as Fetch<'w, 's>>::Item) + Send + Sync + Clone,
|
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(
|
self.par_for_each_unchecked_manual(
|
||||||
world,
|
world,
|
||||||
task_pool,
|
task_pool,
|
||||||
|
|
|
@ -1,115 +1,12 @@
|
||||||
pub use bevy_ecs_macros::{AmbiguitySetLabel, RunCriteriaLabel, StageLabel, SystemLabel};
|
pub use bevy_ecs_macros::{AmbiguitySetLabel, RunCriteriaLabel, StageLabel, SystemLabel};
|
||||||
|
use bevy_utils::define_label;
|
||||||
|
|
||||||
use std::{
|
define_label!(StageLabel);
|
||||||
any::Any,
|
define_label!(SystemLabel);
|
||||||
borrow::Cow,
|
define_label!(AmbiguitySetLabel);
|
||||||
fmt::Debug,
|
define_label!(RunCriteriaLabel);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(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(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(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>;
|
pub(crate) type BoxedRunCriteriaLabel = Box<dyn RunCriteriaLabel>;
|
||||||
|
|
||||||
macro_rules! impl_label {
|
|
||||||
($trait_name:ident) => {
|
|
||||||
impl PartialEq for dyn $trait_name {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.dyn_eq(other.as_dyn_eq())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for dyn $trait_name {}
|
|
||||||
|
|
||||||
impl Hash for dyn $trait_name {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.dyn_hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Box<dyn $trait_name> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
self.dyn_clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl $trait_name for Cow<'static, str> {
|
|
||||||
fn dyn_clone(&self) -> Box<dyn $trait_name> {
|
|
||||||
Box::new(self.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl $trait_name for &'static str {
|
|
||||||
fn dyn_clone(&self) -> Box<dyn $trait_name> {
|
|
||||||
Box::new(<&str>::clone(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_label!(StageLabel);
|
|
||||||
impl_label!(SystemLabel);
|
|
||||||
impl_label!(AmbiguitySetLabel);
|
|
||||||
impl_label!(RunCriteriaLabel);
|
|
||||||
|
|
|
@ -84,6 +84,8 @@ pub struct SystemStage {
|
||||||
uninitialized_parallel: Vec<usize>,
|
uninitialized_parallel: Vec<usize>,
|
||||||
/// Saves the value of the World change_tick during the last tick check
|
/// Saves the value of the World change_tick during the last tick check
|
||||||
last_tick_check: u32,
|
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 {
|
impl SystemStage {
|
||||||
|
@ -105,6 +107,7 @@ impl SystemStage {
|
||||||
uninitialized_before_commands: vec![],
|
uninitialized_before_commands: vec![],
|
||||||
uninitialized_at_end: vec![],
|
uninitialized_at_end: vec![],
|
||||||
last_tick_check: Default::default(),
|
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.
|
/// Topologically sorted parallel systems.
|
||||||
///
|
///
|
||||||
/// Note that systems won't be fully-formed until the stage has been run at least once.
|
/// 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.
|
// Apply parallel systems' buffers.
|
||||||
for container in &mut self.parallel {
|
if self.apply_buffers {
|
||||||
if container.should_run {
|
for container in &mut self.parallel {
|
||||||
container.system_mut().apply_buffers(world);
|
if container.should_run {
|
||||||
|
container.system_mut().apply_buffers(world);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
||||||
query::{Access, FilteredAccessSet},
|
query::{Access, FilteredAccessSet},
|
||||||
system::{
|
system::{
|
||||||
check_system_change_tick, ReadOnlySystemParamFetch, System, SystemParam, SystemParamFetch,
|
check_system_change_tick, ReadOnlySystemParamFetch, System, SystemParam, SystemParamFetch,
|
||||||
SystemParamState,
|
SystemParamItem, SystemParamState,
|
||||||
},
|
},
|
||||||
world::{World, WorldId},
|
world::{World, WorldId},
|
||||||
};
|
};
|
||||||
|
@ -46,6 +46,11 @@ impl SystemMeta {
|
||||||
pub fn set_non_send(&mut self) {
|
pub fn set_non_send(&mut self) {
|
||||||
self.is_send = false;
|
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
|
// 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()
|
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) {
|
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.");
|
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();
|
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`].
|
/// 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
|
/// Use this to get a system from a function. Also note that every system implements this trait as
|
||||||
|
|
|
@ -48,6 +48,8 @@ pub trait SystemParam: Sized {
|
||||||
type Fetch: for<'w, 's> SystemParamFetch<'w, 's>;
|
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`].
|
/// The state of a [`SystemParam`].
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -1230,3 +1232,12 @@ macro_rules! impl_system_param_tuple {
|
||||||
}
|
}
|
||||||
|
|
||||||
all_tuples!(impl_system_param_tuple, 0, 16, P);
|
all_tuples!(impl_system_param_tuple, 0, 16, P);
|
||||||
|
|
||||||
|
pub mod lifetimeless {
|
||||||
|
pub type SQuery<Q, F = ()> = super::Query<'static, 'static, Q, F>;
|
||||||
|
pub type Read<T> = &'static T;
|
||||||
|
pub type Write<T> = &'static mut T;
|
||||||
|
pub type SRes<T> = super::Res<'static, T>;
|
||||||
|
pub type SResMut<T> = super::ResMut<'static, T>;
|
||||||
|
pub type SCommands = crate::system::Commands<'static, 'static>;
|
||||||
|
}
|
||||||
|
|
|
@ -11,17 +11,17 @@ categories = ["game-engines", "graphics", "gui", "rendering"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
wgpu_trace = ["bevy_wgpu/trace"]
|
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_chrome = [ "bevy_log/tracing-chrome" ]
|
||||||
trace_tracy = [ "bevy_log/tracing-tracy" ]
|
trace_tracy = [ "bevy_log/tracing-tracy" ]
|
||||||
|
|
||||||
# Image format support for texture loading (PNG and HDR are enabled by default)
|
# Image format support for texture loading (PNG and HDR are enabled by default)
|
||||||
hdr = ["bevy_render/hdr"]
|
hdr = ["bevy_render/hdr", "bevy_render2/hdr" ]
|
||||||
png = ["bevy_render/png"]
|
png = ["bevy_render/png", "bevy_render2/png" ]
|
||||||
dds = ["bevy_render/dds"]
|
dds = ["bevy_render/dds", "bevy_render2/dds" ]
|
||||||
tga = ["bevy_render/tga"]
|
tga = ["bevy_render/tga", "bevy_render2/tga" ]
|
||||||
jpeg = ["bevy_render/jpeg"]
|
jpeg = ["bevy_render/jpeg", "bevy_render2/jpeg" ]
|
||||||
bmp = ["bevy_render/bmp"]
|
bmp = ["bevy_render/bmp", "bevy_render2/bmp" ]
|
||||||
|
|
||||||
# Audio format support (MP3 is enabled by default)
|
# Audio format support (MP3 is enabled by default)
|
||||||
flac = ["bevy_audio/flac"]
|
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_tasks = { path = "../bevy_tasks", version = "0.5.0" }
|
||||||
# bevy (optional)
|
# bevy (optional)
|
||||||
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.5.0" }
|
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_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_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_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_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.5.0" }
|
||||||
bevy_sprite = { path = "../bevy_sprite", 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_text = { path = "../bevy_text", optional = true, version = "0.5.0" }
|
||||||
bevy_ui = { path = "../bevy_ui", 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" }
|
bevy_wgpu = { path = "../bevy_wgpu", optional = true, version = "0.5.0" }
|
||||||
|
|
|
@ -109,3 +109,40 @@ impl PluginGroup for MinimalPlugins {
|
||||||
group.add(ScheduleRunnerPlugin::default());
|
group.add(ScheduleRunnerPlugin::default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct PipelinedDefaultPlugins;
|
||||||
|
|
||||||
|
impl PluginGroup for PipelinedDefaultPlugins {
|
||||||
|
fn build(&mut self, group: &mut PluginGroupBuilder) {
|
||||||
|
group.add(bevy_log::LogPlugin::default());
|
||||||
|
group.add(bevy_core::CorePlugin::default());
|
||||||
|
group.add(bevy_transform::TransformPlugin::default());
|
||||||
|
group.add(bevy_diagnostic::DiagnosticsPlugin::default());
|
||||||
|
group.add(bevy_input::InputPlugin::default());
|
||||||
|
group.add(bevy_window::WindowPlugin::default());
|
||||||
|
group.add(bevy_asset::AssetPlugin::default());
|
||||||
|
group.add(bevy_scene::ScenePlugin::default());
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy_winit")]
|
||||||
|
group.add(bevy_winit::WinitPlugin::default());
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy_render2")]
|
||||||
|
{
|
||||||
|
group.add(bevy_render2::RenderPlugin::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy_core_pipeline")]
|
||||||
|
{
|
||||||
|
group.add(bevy_core_pipeline::CorePipelinePlugin::default());
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy_sprite2")]
|
||||||
|
group.add(bevy_sprite2::SpritePlugin::default());
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy_pbr2")]
|
||||||
|
group.add(bevy_pbr2::PbrPlugin::default());
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy_gltf2")]
|
||||||
|
group.add(bevy_gltf2::GltfPlugin::default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -82,6 +82,12 @@ pub mod audio {
|
||||||
pub use bevy_audio::*;
|
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")]
|
#[cfg(feature = "bevy_gilrs")]
|
||||||
pub mod gilrs {
|
pub mod gilrs {
|
||||||
pub use bevy_gilrs::*;
|
pub use bevy_gilrs::*;
|
||||||
|
@ -93,24 +99,48 @@ pub mod gltf {
|
||||||
pub use bevy_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")]
|
#[cfg(feature = "bevy_pbr")]
|
||||||
pub mod pbr {
|
pub mod pbr {
|
||||||
//! Physically based rendering.
|
//! Physically based rendering.
|
||||||
pub use bevy_pbr::*;
|
pub use bevy_pbr::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "bevy_pbr2")]
|
||||||
|
pub mod pbr2 {
|
||||||
|
//! Physically based rendering.
|
||||||
|
pub use bevy_pbr2::*;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bevy_render")]
|
#[cfg(feature = "bevy_render")]
|
||||||
pub mod render {
|
pub mod render {
|
||||||
//! Cameras, meshes, textures, shaders, and pipelines.
|
//! Cameras, meshes, textures, shaders, and pipelines.
|
||||||
pub use bevy_render::*;
|
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")]
|
#[cfg(feature = "bevy_sprite")]
|
||||||
pub mod sprite {
|
pub mod sprite {
|
||||||
//! Items for sprites, rects, texture atlases, etc.
|
//! Items for sprites, rects, texture atlases, etc.
|
||||||
pub use bevy_sprite::*;
|
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")]
|
#[cfg(feature = "bevy_text")]
|
||||||
pub mod text {
|
pub mod text {
|
||||||
//! Text drawing, styling, and font assets.
|
//! Text drawing, styling, and font assets.
|
||||||
|
|
|
@ -15,6 +15,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
|
||||||
tracing-subscriber = {version = "0.3.1", features = ["registry", "env-filter"]}
|
tracing-subscriber = {version = "0.3.1", features = ["registry", "env-filter"]}
|
||||||
tracing-chrome = { version = "0.4.0", optional = true }
|
tracing-chrome = { version = "0.4.0", optional = true }
|
||||||
tracing-tracy = { version = "0.8.0", optional = true }
|
tracing-tracy = { version = "0.8.0", optional = true }
|
||||||
|
tracing-log = "0.1.2"
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
android_log-sys = "0.2.0"
|
android_log-sys = "0.2.0"
|
||||||
|
|
|
@ -13,6 +13,7 @@ pub use bevy_utils::tracing::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy_app::{App, Plugin};
|
use bevy_app::{App, Plugin};
|
||||||
|
use tracing_log::LogTracer;
|
||||||
#[cfg(feature = "tracing-chrome")]
|
#[cfg(feature = "tracing-chrome")]
|
||||||
use tracing_subscriber::fmt::{format::DefaultFields, FormattedFields};
|
use tracing_subscriber::fmt::{format::DefaultFields, FormattedFields};
|
||||||
use tracing_subscriber::{prelude::*, registry::Registry, EnvFilter};
|
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);
|
let settings = app.world.get_resource_or_insert_with(LogSettings::default);
|
||||||
format!("{},{}", settings.level, settings.filter)
|
format!("{},{}", settings.level, settings.filter)
|
||||||
};
|
};
|
||||||
|
LogTracer::init().unwrap();
|
||||||
let filter_layer = EnvFilter::try_from_default_env()
|
let filter_layer = EnvFilter::try_from_default_env()
|
||||||
.or_else(|_| EnvFilter::try_new(&default_filter))
|
.or_else(|_| EnvFilter::try_new(&default_filter))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -11,3 +11,4 @@ keywords = ["bevy"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cargo-manifest = "0.2.6"
|
cargo-manifest = "0.2.6"
|
||||||
syn = "1.0"
|
syn = "1.0"
|
||||||
|
quote = "1.0"
|
||||||
|
|
|
@ -8,6 +8,7 @@ pub use symbol::*;
|
||||||
|
|
||||||
use cargo_manifest::{DepsSet, Manifest};
|
use cargo_manifest::{DepsSet, Manifest};
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
use std::{env, path::PathBuf};
|
use std::{env, path::PathBuf};
|
||||||
|
|
||||||
pub struct BevyManifest {
|
pub struct BevyManifest {
|
||||||
|
@ -65,3 +66,29 @@ fn get_path(path: &str) -> syn::Path {
|
||||||
fn parse_str<T: syn::parse::Parse>(path: &str) -> T {
|
fn parse_str<T: syn::parse::Parse>(path: &str) -> T {
|
||||||
syn::parse(path.parse::<TokenStream>().unwrap()).unwrap()
|
syn::parse(path.parse::<TokenStream>().unwrap()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Derive a label trait
|
||||||
|
///
|
||||||
|
/// # Args
|
||||||
|
///
|
||||||
|
/// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label trait
|
||||||
|
/// - `trait_path`: The path [`syn::Path`] to the label trait
|
||||||
|
pub fn derive_label(input: syn::DeriveInput, trait_path: syn::Path) -> TokenStream {
|
||||||
|
let ident = input.ident;
|
||||||
|
|
||||||
|
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||||
|
let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause {
|
||||||
|
where_token: Default::default(),
|
||||||
|
predicates: Default::default(),
|
||||||
|
});
|
||||||
|
where_clause.predicates.push(syn::parse2(quote! { Self: Eq + ::std::fmt::Debug + ::std::hash::Hash + Clone + Send + Sync + 'static }).unwrap());
|
||||||
|
|
||||||
|
(quote! {
|
||||||
|
impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
|
||||||
|
fn dyn_clone(&self) -> Box<dyn #trait_path> {
|
||||||
|
Box::new(Clone::clone(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
|
@ -9,5 +9,5 @@ license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy"]
|
keywords = ["bevy"]
|
||||||
|
|
||||||
[dependencies]
|
[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"] }
|
bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] }
|
||||||
|
|
|
@ -24,7 +24,7 @@ parking_lot = "0.11.0"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
smallvec = { version = "1.6", features = ["serde", "union", "const_generics"], optional = true }
|
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]
|
[dev-dependencies]
|
||||||
ron = "0.6.2"
|
ron = "0.6.2"
|
||||||
|
|
|
@ -32,7 +32,7 @@ downcast-rs = "1.2.0"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
anyhow = "1.0.4"
|
anyhow = "1.0.4"
|
||||||
hex = "0.4.2"
|
hex = "0.4.2"
|
||||||
hexasphere = "5.0.0"
|
hexasphere = "6.0.0"
|
||||||
parking_lot = "0.11.0"
|
parking_lot = "0.11.0"
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
LoadOp, Operations, PassDescriptor, RenderPassColorAttachment,
|
LoadOp, Operations, PassDescriptor, RenderPassColorAttachment,
|
||||||
RenderPassDepthStencilAttachment, TextureAttachment,
|
RenderPassDepthStencilAttachment, TextureAttachment,
|
||||||
},
|
},
|
||||||
texture::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsage},
|
texture::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages},
|
||||||
Color,
|
Color,
|
||||||
};
|
};
|
||||||
use bevy_ecs::{component::Component, reflect::ReflectComponent, world::World};
|
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,
|
dimension: TextureDimension::D2,
|
||||||
format: TextureFormat::Depth32Float, /* PERF: vulkan docs recommend using 24
|
format: TextureFormat::Depth32Float, /* PERF: vulkan docs recommend using 24
|
||||||
* bit depth for better performance */
|
* 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,
|
sample_count: msaa.samples,
|
||||||
dimension: TextureDimension::D2,
|
dimension: TextureDimension::D2,
|
||||||
format: TextureFormat::default(),
|
format: TextureFormat::default(),
|
||||||
usage: TextureUsage::OUTPUT_ATTACHMENT,
|
usage: TextureUsages::OUTPUT_ATTACHMENT,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -52,7 +52,7 @@ impl Node for WindowSwapChainNode {
|
||||||
|
|
||||||
let render_resource_context = render_context.resources_mut();
|
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
|
if self
|
||||||
.window_created_event_reader
|
.window_created_event_reader
|
||||||
.iter(window_created_events)
|
.iter(window_created_events)
|
||||||
|
@ -62,10 +62,10 @@ impl Node for WindowSwapChainNode {
|
||||||
.iter(window_resized_events)
|
.iter(window_resized_events)
|
||||||
.any(|e| e.id == window.id())
|
.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(
|
output.set(
|
||||||
WINDOW_TEXTURE,
|
WINDOW_TEXTURE,
|
||||||
RenderResourceId::Texture(swap_chain_texture),
|
RenderResourceId::Texture(swap_chain_texture),
|
||||||
|
|
|
@ -31,15 +31,15 @@ impl HeadlessRenderResourceContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderResourceContext for 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()
|
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 {
|
fn create_sampler(&self, _sampler_descriptor: &SamplerDescriptor) -> SamplerId {
|
||||||
SamplerId::new()
|
SamplerId::new()
|
||||||
|
|
|
@ -12,10 +12,10 @@ use downcast_rs::{impl_downcast, Downcast};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
pub trait RenderResourceContext: Downcast + Send + Sync + 'static {
|
pub trait RenderResourceContext: Downcast + Send + Sync + 'static {
|
||||||
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;
|
||||||
fn drop_swap_chain_texture(&self, resource: TextureId);
|
fn drop_surface_frame(&self, resource: TextureId);
|
||||||
fn drop_all_swap_chain_textures(&self);
|
fn drop_all_surface_frames(&self);
|
||||||
fn create_sampler(&self, sampler_descriptor: &SamplerDescriptor) -> SamplerId;
|
fn create_sampler(&self, sampler_descriptor: &SamplerDescriptor) -> SamplerId;
|
||||||
fn create_texture(&self, texture_descriptor: TextureDescriptor) -> TextureId;
|
fn create_texture(&self, texture_descriptor: TextureDescriptor) -> TextureId;
|
||||||
fn create_buffer(&self, buffer_info: BufferInfo) -> BufferId;
|
fn create_buffer(&self, buffer_info: BufferInfo) -> BufferId;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use std::convert::TryFrom;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::{Extent3d, Texture, TextureDimension, TextureFormat};
|
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());
|
Vec::with_capacity(width as usize * height as usize * format.pixel_size());
|
||||||
|
|
||||||
for pixel in image.into_raw().chunks_exact(3) {
|
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 r = pixel[0];
|
||||||
let g = pixel[1];
|
let g = pixel[1];
|
||||||
let b = pixel[2];
|
let b = pixel[2];
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use super::{Extent3d, Texture, TextureDimension, TextureFormat, TextureUsage};
|
use super::{Extent3d, Texture, TextureDimension, TextureFormat, TextureUsages};
|
||||||
|
|
||||||
/// Describes a texture
|
/// Describes a texture
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
@ -8,7 +8,7 @@ pub struct TextureDescriptor {
|
||||||
pub sample_count: u32,
|
pub sample_count: u32,
|
||||||
pub dimension: TextureDimension,
|
pub dimension: TextureDimension,
|
||||||
pub format: TextureFormat,
|
pub format: TextureFormat,
|
||||||
pub usage: TextureUsage,
|
pub usage: TextureUsages,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Texture> for TextureDescriptor {
|
impl From<&Texture> for TextureDescriptor {
|
||||||
|
@ -19,7 +19,7 @@ impl From<&Texture> for TextureDescriptor {
|
||||||
sample_count: 1,
|
sample_count: 1,
|
||||||
dimension: texture.dimension,
|
dimension: texture.dimension,
|
||||||
format: texture.format,
|
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,
|
sample_count: 1,
|
||||||
dimension: TextureDimension::D2,
|
dimension: TextureDimension::D2,
|
||||||
format: TextureFormat::Rgba8UnormSrgb,
|
format: TextureFormat::Rgba8UnormSrgb,
|
||||||
usage: TextureUsage::SAMPLED | TextureUsage::COPY_DST,
|
usage: TextureUsages::SAMPLED | TextureUsages::COPY_DST,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -276,7 +276,7 @@ impl Default for TextureFormat {
|
||||||
|
|
||||||
bitflags::bitflags! {
|
bitflags::bitflags! {
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct TextureUsage: u32 {
|
pub struct TextureUsages: u32 {
|
||||||
const COPY_SRC = 1;
|
const COPY_SRC = 1;
|
||||||
const COPY_DST = 2;
|
const COPY_DST = 2;
|
||||||
const SAMPLED = 4;
|
const SAMPLED = 4;
|
||||||
|
|
|
@ -21,7 +21,7 @@ struct Rect {
|
||||||
vec2 end;
|
vec2 end;
|
||||||
};
|
};
|
||||||
|
|
||||||
layout(set = 1, binding = 1) buffer TextureAtlas_textures {
|
layout(set = 1, binding = 1) readonly buffer TextureAtlas_textures {
|
||||||
Rect[] Textures;
|
Rect[] Textures;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -168,10 +168,11 @@ impl TextureAtlasBuilder {
|
||||||
&contains_smallest_box,
|
&contains_smallest_box,
|
||||||
) {
|
) {
|
||||||
Ok(rect_placements) => {
|
Ok(rect_placements) => {
|
||||||
atlas_texture = Texture::new_fill(
|
let size = Extent3d::new(current_width, current_height, 1);
|
||||||
Extent3d::new(current_width, current_height, 1),
|
atlas_texture = Texture::new(
|
||||||
|
size,
|
||||||
TextureDimension::D2,
|
TextureDimension::D2,
|
||||||
&[0, 0, 0, 0],
|
vec![0; self.format.pixel_size() * size.volume()],
|
||||||
self.format,
|
self.format,
|
||||||
);
|
);
|
||||||
Some(rect_placements)
|
Some(rect_placements)
|
||||||
|
|
|
@ -190,7 +190,7 @@ impl GlobalTransform {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn rotate(&mut self, rotation: Quat) {
|
pub fn rotate(&mut self, rotation: Quat) {
|
||||||
self.rotation *= rotation;
|
self.rotation = rotation * self.rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Multiplies `self` with `transform` component by component, returning the
|
/// Multiplies `self` with `transform` component by component, returning the
|
||||||
|
|
|
@ -202,7 +202,7 @@ impl Transform {
|
||||||
/// Rotates the transform by the given rotation.
|
/// Rotates the transform by the given rotation.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn rotate(&mut self, rotation: Quat) {
|
pub fn rotate(&mut self, rotation: Quat) {
|
||||||
self.rotation *= rotation;
|
self.rotation = rotation * self.rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Multiplies `self` with `transform` component by component, returning the
|
/// Multiplies `self` with `transform` component by component, returning the
|
||||||
|
|
100
crates/bevy_utils/src/label.rs
Normal file
100
crates/bevy_utils/src/label.rs
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
//! Traits used by label implementations
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait DynEq: Any {
|
||||||
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
|
||||||
|
fn dyn_eq(&self, other: &dyn DynEq) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DynEq for T
|
||||||
|
where
|
||||||
|
T: Any + Eq,
|
||||||
|
{
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dyn_eq(&self, other: &dyn DynEq) -> bool {
|
||||||
|
if let Some(other) = other.as_any().downcast_ref::<T>() {
|
||||||
|
return self == other;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DynHash: DynEq {
|
||||||
|
fn as_dyn_eq(&self) -> &dyn DynEq;
|
||||||
|
|
||||||
|
fn dyn_hash(&self, state: &mut dyn Hasher);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DynHash for T
|
||||||
|
where
|
||||||
|
T: DynEq + Hash,
|
||||||
|
{
|
||||||
|
fn as_dyn_eq(&self) -> &dyn DynEq {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dyn_hash(&self, mut state: &mut dyn Hasher) {
|
||||||
|
T::hash(self, &mut state);
|
||||||
|
self.type_id().hash(&mut state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Macro to define a new label trait
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_utils::define_label;
|
||||||
|
/// define_label!(MyNewLabelTrait);
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! define_label {
|
||||||
|
($label_trait_name:ident) => {
|
||||||
|
pub trait $label_trait_name:
|
||||||
|
::bevy_utils::label::DynHash + ::std::fmt::Debug + Send + Sync + 'static
|
||||||
|
{
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn dyn_clone(&self) -> Box<dyn $label_trait_name>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for dyn $label_trait_name {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.dyn_eq(other.as_dyn_eq())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for dyn $label_trait_name {}
|
||||||
|
|
||||||
|
impl ::std::hash::Hash for dyn $label_trait_name {
|
||||||
|
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.dyn_hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Box<dyn $label_trait_name> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
self.dyn_clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $label_trait_name for ::std::borrow::Cow<'static, str> {
|
||||||
|
fn dyn_clone(&self) -> Box<dyn $label_trait_name> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $label_trait_name for &'static str {
|
||||||
|
fn dyn_clone(&self) -> Box<dyn $label_trait_name> {
|
||||||
|
Box::new(<&str>::clone(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
mod enum_variant_meta;
|
mod enum_variant_meta;
|
||||||
pub use enum_variant_meta::*;
|
pub mod label;
|
||||||
|
|
||||||
pub use ahash::AHasher;
|
pub use ahash::AHasher;
|
||||||
|
pub use enum_variant_meta::*;
|
||||||
pub use instant::{Duration, Instant};
|
pub use instant::{Duration, Instant};
|
||||||
pub use tracing;
|
pub use tracing;
|
||||||
pub use uuid::Uuid;
|
pub use uuid::Uuid;
|
||||||
|
|
|
@ -25,7 +25,7 @@ bevy_winit = { path = "../bevy_winit", optional = true, version = "0.5.0" }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
|
bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
|
||||||
|
|
||||||
# other
|
# other
|
||||||
wgpu = "0.9"
|
wgpu = { version = "0.11.0", features = ["spirv"] }
|
||||||
futures-lite = "1.4.0"
|
futures-lite = "1.4.0"
|
||||||
crossbeam-channel = "0.5.0"
|
crossbeam-channel = "0.5.0"
|
||||||
crossbeam-utils = "0.8.1"
|
crossbeam-utils = "0.8.1"
|
||||||
|
|
|
@ -29,9 +29,7 @@ impl WgpuResourceDiagnosticsPlugin {
|
||||||
DiagnosticId::from_u128(305855369913076220671125671543184691267);
|
DiagnosticId::from_u128(305855369913076220671125671543184691267);
|
||||||
pub const SHADER_MODULES: DiagnosticId =
|
pub const SHADER_MODULES: DiagnosticId =
|
||||||
DiagnosticId::from_u128(287681470908132753275843248383768232237);
|
DiagnosticId::from_u128(287681470908132753275843248383768232237);
|
||||||
pub const SWAP_CHAINS: DiagnosticId =
|
pub const SURFACE_FRAMES: DiagnosticId =
|
||||||
DiagnosticId::from_u128(199253035828743332241465305105689014605);
|
|
||||||
pub const SWAP_CHAIN_OUTPUTS: DiagnosticId =
|
|
||||||
DiagnosticId::from_u128(112048874168736161226721327099863374234);
|
DiagnosticId::from_u128(112048874168736161226721327099863374234);
|
||||||
pub const TEXTURES: DiagnosticId =
|
pub const TEXTURES: DiagnosticId =
|
||||||
DiagnosticId::from_u128(305955424195390184883220102469231911115);
|
DiagnosticId::from_u128(305955424195390184883220102469231911115);
|
||||||
|
@ -47,10 +45,8 @@ impl WgpuResourceDiagnosticsPlugin {
|
||||||
10,
|
10,
|
||||||
));
|
));
|
||||||
|
|
||||||
diagnostics.add(Diagnostic::new(Self::SWAP_CHAINS, "swap_chains", 10));
|
|
||||||
|
|
||||||
diagnostics.add(Diagnostic::new(
|
diagnostics.add(Diagnostic::new(
|
||||||
Self::SWAP_CHAIN_OUTPUTS,
|
Self::SURFACE_FRAMES,
|
||||||
"swap_chain_outputs",
|
"swap_chain_outputs",
|
||||||
10,
|
10,
|
||||||
));
|
));
|
||||||
|
@ -99,19 +95,10 @@ impl WgpuResourceDiagnosticsPlugin {
|
||||||
);
|
);
|
||||||
|
|
||||||
diagnostics.add_measurement(
|
diagnostics.add_measurement(
|
||||||
Self::SWAP_CHAINS,
|
Self::SURFACE_FRAMES,
|
||||||
render_resource_context
|
render_resource_context
|
||||||
.resources
|
.resources
|
||||||
.window_swap_chains
|
.surface_textures
|
||||||
.read()
|
|
||||||
.len() as f64,
|
|
||||||
);
|
|
||||||
|
|
||||||
diagnostics.add_measurement(
|
|
||||||
Self::SWAP_CHAIN_OUTPUTS,
|
|
||||||
render_resource_context
|
|
||||||
.resources
|
|
||||||
.swap_chain_frames
|
|
||||||
.read()
|
.read()
|
||||||
.len() as f64,
|
.len() as f64,
|
||||||
);
|
);
|
||||||
|
|
|
@ -26,15 +26,12 @@ pub enum WgpuFeature {
|
||||||
TimestampQuery,
|
TimestampQuery,
|
||||||
PipelineStatisticsQuery,
|
PipelineStatisticsQuery,
|
||||||
MappablePrimaryBuffers,
|
MappablePrimaryBuffers,
|
||||||
SampledTextureBindingArray,
|
|
||||||
SampledTextureArrayDynamicIndexing,
|
|
||||||
SampledTextureArrayNonUniformIndexing,
|
|
||||||
UnsizedBindingArray,
|
UnsizedBindingArray,
|
||||||
MultiDrawIndirect,
|
MultiDrawIndirect,
|
||||||
MultiDrawIndirectCount,
|
MultiDrawIndirectCount,
|
||||||
PushConstants,
|
PushConstants,
|
||||||
AddressModeClampToBorder,
|
AddressModeClampToBorder,
|
||||||
NonFillPolygonMode,
|
PolygonModeLine,
|
||||||
TextureCompressionEtc2,
|
TextureCompressionEtc2,
|
||||||
TextureCompressionAstcLdr,
|
TextureCompressionAstcLdr,
|
||||||
TextureAdapterSpecificFormatFeatures,
|
TextureAdapterSpecificFormatFeatures,
|
||||||
|
@ -67,6 +64,8 @@ pub struct WgpuLimits {
|
||||||
pub max_vertex_buffers: u32,
|
pub max_vertex_buffers: u32,
|
||||||
pub max_vertex_attributes: u32,
|
pub max_vertex_attributes: u32,
|
||||||
pub max_vertex_buffer_array_stride: 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 {
|
impl Default for WgpuLimits {
|
||||||
|
@ -93,6 +92,8 @@ impl Default for WgpuLimits {
|
||||||
max_vertex_buffers: default.max_vertex_buffers,
|
max_vertex_buffers: default.max_vertex_buffers,
|
||||||
max_vertex_attributes: default.max_vertex_attributes,
|
max_vertex_attributes: default.max_vertex_attributes,
|
||||||
max_vertex_buffer_array_stride: default.max_vertex_buffer_array_stride,
|
max_vertex_buffer_array_stride: default.max_vertex_buffer_array_stride,
|
||||||
|
min_storage_buffer_offset_alignment: default.min_storage_buffer_offset_alignment,
|
||||||
|
min_uniform_buffer_offset_alignment: default.min_uniform_buffer_offset_alignment,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -231,8 +231,15 @@ fn get_texture_view<'a>(
|
||||||
panic!("Color attachment {} does not exist.", name);
|
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::Id(render_resource) => refs
|
||||||
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!"),
|
.textures
|
||||||
|
.get(render_resource)
|
||||||
|
.unwrap_or_else(|| &refs.surface_textures.get(render_resource).unwrap().0),
|
||||||
|
TextureAttachment::Input(_) => panic!(
|
||||||
|
"Encountered unset `TextureAttachment::Input`. The `RenderGraph` executor should \
|
||||||
|
always set `TextureAttachment::Inputs` to `TextureAttachment::RenderResource` before \
|
||||||
|
running. This is a bug, please report it!"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,8 @@ pub struct WgpuRenderResourceContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const COPY_BYTES_PER_ROW_ALIGNMENT: usize = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
|
pub const 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 COPY_BUFFER_ALIGNMENT: usize = wgpu::COPY_BUFFER_ALIGNMENT as usize;
|
||||||
pub const PUSH_CONSTANT_ALIGNMENT: u32 = wgpu::PUSH_CONSTANT_ALIGNMENT;
|
pub const PUSH_CONSTANT_ALIGNMENT: u32 = wgpu::PUSH_CONSTANT_ALIGNMENT;
|
||||||
|
|
||||||
|
@ -94,6 +95,7 @@ impl WgpuRenderResourceContext {
|
||||||
y: source_origin[1],
|
y: source_origin[1],
|
||||||
z: source_origin[2],
|
z: source_origin[2],
|
||||||
},
|
},
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
},
|
},
|
||||||
wgpu::ImageCopyTexture {
|
wgpu::ImageCopyTexture {
|
||||||
texture: destination,
|
texture: destination,
|
||||||
|
@ -103,6 +105,7 @@ impl WgpuRenderResourceContext {
|
||||||
y: destination_origin[1],
|
y: destination_origin[1],
|
||||||
z: destination_origin[2],
|
z: destination_origin[2],
|
||||||
},
|
},
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
},
|
},
|
||||||
size.wgpu_into(),
|
size.wgpu_into(),
|
||||||
)
|
)
|
||||||
|
@ -134,6 +137,7 @@ impl WgpuRenderResourceContext {
|
||||||
y: source_origin[1],
|
y: source_origin[1],
|
||||||
z: source_origin[2],
|
z: source_origin[2],
|
||||||
},
|
},
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
},
|
},
|
||||||
wgpu::ImageCopyBuffer {
|
wgpu::ImageCopyBuffer {
|
||||||
buffer: destination,
|
buffer: destination,
|
||||||
|
@ -181,6 +185,7 @@ impl WgpuRenderResourceContext {
|
||||||
y: destination_origin[1],
|
y: destination_origin[1],
|
||||||
z: destination_origin[2],
|
z: destination_origin[2],
|
||||||
},
|
},
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
},
|
},
|
||||||
size.wgpu_into(),
|
size.wgpu_into(),
|
||||||
);
|
);
|
||||||
|
@ -206,11 +211,11 @@ impl WgpuRenderResourceContext {
|
||||||
let shader_stage = if binding.shader_stage
|
let shader_stage = if binding.shader_stage
|
||||||
== BindingShaderStage::VERTEX | BindingShaderStage::FRAGMENT
|
== BindingShaderStage::VERTEX | BindingShaderStage::FRAGMENT
|
||||||
{
|
{
|
||||||
wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT
|
wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT
|
||||||
} else if binding.shader_stage == BindingShaderStage::VERTEX {
|
} else if binding.shader_stage == BindingShaderStage::VERTEX {
|
||||||
wgpu::ShaderStage::VERTEX
|
wgpu::ShaderStages::VERTEX
|
||||||
} else if binding.shader_stage == BindingShaderStage::FRAGMENT {
|
} else if binding.shader_stage == BindingShaderStage::FRAGMENT {
|
||||||
wgpu::ShaderStage::FRAGMENT
|
wgpu::ShaderStages::FRAGMENT
|
||||||
} else {
|
} else {
|
||||||
panic!("Invalid binding shader stage.")
|
panic!("Invalid binding shader stage.")
|
||||||
};
|
};
|
||||||
|
@ -230,14 +235,15 @@ impl WgpuRenderResourceContext {
|
||||||
bind_group_layouts.insert(descriptor.id, bind_group_layout);
|
bind_group_layouts.insert(descriptor.id, bind_group_layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_next_swap_chain_texture(&self, window_id: bevy_window::WindowId) -> Option<TextureId> {
|
fn try_next_surface_frame(&self, window_id: bevy_window::WindowId) -> Option<TextureId> {
|
||||||
let mut window_swap_chains = self.resources.window_swap_chains.write();
|
let mut window_surfaces = self.resources.window_surfaces.write();
|
||||||
let mut swap_chain_outputs = self.resources.swap_chain_frames.write();
|
let mut surface_frames = self.resources.surface_textures.write();
|
||||||
|
|
||||||
let window_swap_chain = window_swap_chains.get_mut(&window_id).unwrap();
|
let window_surface = window_surfaces.get_mut(&window_id).unwrap();
|
||||||
let next_texture = window_swap_chain.get_current_frame().ok()?;
|
let next_texture = window_surface.get_current_texture().ok()?;
|
||||||
|
let view = next_texture.texture.create_view(&Default::default());
|
||||||
let id = TextureId::new();
|
let id = TextureId::new();
|
||||||
swap_chain_outputs.insert(id, next_texture);
|
surface_frames.insert(id, (view, next_texture));
|
||||||
Some(id)
|
Some(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -339,7 +345,6 @@ impl RenderResourceContext for WgpuRenderResourceContext {
|
||||||
.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
label: None,
|
label: None,
|
||||||
source: wgpu::ShaderSource::SpirV(spirv),
|
source: wgpu::ShaderSource::SpirV(spirv),
|
||||||
flags: Default::default(),
|
|
||||||
});
|
});
|
||||||
shader_modules.insert(shader_handle.clone_weak(), shader_module);
|
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);
|
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 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
|
let surface = surfaces
|
||||||
.get(&window.id())
|
.get(&window.id())
|
||||||
.expect("No surface found for window.");
|
.expect("No surface found for window.");
|
||||||
let swap_chain = self
|
surface.configure(&self.device, &surface_configuration);
|
||||||
.device
|
|
||||||
.create_swap_chain(surface, &swap_chain_descriptor);
|
|
||||||
|
|
||||||
window_swap_chains.insert(window.id(), swap_chain);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_swap_chain_texture(&self, window: &bevy_window::Window) -> TextureId {
|
fn next_surface_frame(&self, window: &bevy_window::Window) -> TextureId {
|
||||||
if let Some(texture_id) = self.try_next_swap_chain_texture(window.id()) {
|
if let Some(texture_id) = self.try_next_surface_frame(window.id()) {
|
||||||
texture_id
|
texture_id
|
||||||
} else {
|
} else {
|
||||||
self.resources
|
self.resources.window_surfaces.write().remove(&window.id());
|
||||||
.window_swap_chains
|
self.configure_surface(window);
|
||||||
.write()
|
self.try_next_surface_frame(window.id())
|
||||||
.remove(&window.id());
|
|
||||||
self.create_swap_chain(window);
|
|
||||||
self.try_next_swap_chain_texture(window.id())
|
|
||||||
.expect("Failed to acquire next swap chain texture!")
|
.expect("Failed to acquire next swap chain texture!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drop_swap_chain_texture(&self, texture: TextureId) {
|
fn drop_surface_frame(&self, texture: TextureId) {
|
||||||
let mut swap_chain_outputs = self.resources.swap_chain_frames.write();
|
let mut surface_frames = self.resources.surface_textures.write();
|
||||||
swap_chain_outputs.remove(&texture);
|
surface_frames.remove(&texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drop_all_swap_chain_textures(&self) {
|
fn drop_all_surface_frames(&self) {
|
||||||
let mut swap_chain_outputs = self.resources.swap_chain_frames.write();
|
let mut surface_frames = self.resources.surface_textures.write();
|
||||||
swap_chain_outputs.clear();
|
for (_, (_, texture)) in surface_frames.drain() {
|
||||||
|
texture.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
surface_frames.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_asset_resource_untyped(
|
fn set_asset_resource_untyped(
|
||||||
|
|
|
@ -24,13 +24,13 @@ pub struct WgpuRenderer {
|
||||||
impl WgpuRenderer {
|
impl WgpuRenderer {
|
||||||
pub async fn new(options: WgpuOptions) -> Self {
|
pub async fn new(options: WgpuOptions) -> Self {
|
||||||
let backend = match options.backend {
|
let backend = match options.backend {
|
||||||
WgpuBackend::Auto => wgpu::BackendBit::PRIMARY,
|
WgpuBackend::Auto => wgpu::Backends::PRIMARY,
|
||||||
WgpuBackend::Vulkan => wgpu::BackendBit::VULKAN,
|
WgpuBackend::Vulkan => wgpu::Backends::VULKAN,
|
||||||
WgpuBackend::Metal => wgpu::BackendBit::METAL,
|
WgpuBackend::Metal => wgpu::Backends::METAL,
|
||||||
WgpuBackend::Dx12 => wgpu::BackendBit::DX12,
|
WgpuBackend::Dx12 => wgpu::Backends::DX12,
|
||||||
WgpuBackend::Dx11 => wgpu::BackendBit::DX11,
|
WgpuBackend::Dx11 => wgpu::Backends::DX11,
|
||||||
WgpuBackend::Gl => wgpu::BackendBit::GL,
|
WgpuBackend::Gl => wgpu::Backends::GL,
|
||||||
WgpuBackend::BrowserWgpu => wgpu::BackendBit::BROWSER_WEBGPU,
|
WgpuBackend::BrowserWgpu => wgpu::Backends::BROWSER_WEBGPU,
|
||||||
};
|
};
|
||||||
let instance = wgpu::Instance::new(backend);
|
let instance = wgpu::Instance::new(backend);
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ impl WgpuRenderer {
|
||||||
WgpuPowerOptions::LowPower => wgpu::PowerPreference::LowPower,
|
WgpuPowerOptions::LowPower => wgpu::PowerPreference::LowPower,
|
||||||
},
|
},
|
||||||
compatible_surface: None,
|
compatible_surface: None,
|
||||||
|
..Default::default()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.expect("Unable to find a GPU! Make sure you have installed required drivers!");
|
.expect("Unable to find a GPU! Make sure you have installed required drivers!");
|
||||||
|
@ -56,12 +57,14 @@ impl WgpuRenderer {
|
||||||
#[cfg(not(feature = "trace"))]
|
#[cfg(not(feature = "trace"))]
|
||||||
let trace_path = None;
|
let trace_path = None;
|
||||||
|
|
||||||
|
let adapter_limits = adapter.limits();
|
||||||
|
|
||||||
let (device, queue) = adapter
|
let (device, queue) = adapter
|
||||||
.request_device(
|
.request_device(
|
||||||
&wgpu::DeviceDescriptor {
|
&wgpu::DeviceDescriptor {
|
||||||
label: options.device_label.as_ref().map(|a| a.as_ref()),
|
label: options.device_label.as_ref().map(|a| a.as_ref()),
|
||||||
features: options.features.wgpu_into(),
|
features: options.features.wgpu_into(),
|
||||||
limits: options.limits.wgpu_into(),
|
limits: adapter_limits,
|
||||||
},
|
},
|
||||||
trace_path,
|
trace_path,
|
||||||
)
|
)
|
||||||
|
@ -129,7 +132,7 @@ impl WgpuRenderer {
|
||||||
let render_resource_context = world
|
let render_resource_context = world
|
||||||
.get_resource::<Box<dyn RenderResourceContext>>()
|
.get_resource::<Box<dyn RenderResourceContext>>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
render_resource_context.drop_all_swap_chain_textures();
|
render_resource_context.drop_all_surface_frames();
|
||||||
render_resource_context.remove_stale_bind_groups();
|
render_resource_context.remove_stale_bind_groups();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,8 @@ pub struct WgpuBindGroupInfo {
|
||||||
pub struct WgpuResourcesReadLock<'a> {
|
pub struct WgpuResourcesReadLock<'a> {
|
||||||
pub buffers: RwLockReadGuard<'a, HashMap<BufferId, Arc<wgpu::Buffer>>>,
|
pub buffers: RwLockReadGuard<'a, HashMap<BufferId, Arc<wgpu::Buffer>>>,
|
||||||
pub textures: RwLockReadGuard<'a, HashMap<TextureId, wgpu::TextureView>>,
|
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:
|
pub render_pipelines:
|
||||||
RwLockReadGuard<'a, HashMap<Handle<PipelineDescriptor>, wgpu::RenderPipeline>>,
|
RwLockReadGuard<'a, HashMap<Handle<PipelineDescriptor>, wgpu::RenderPipeline>>,
|
||||||
pub bind_groups: RwLockReadGuard<'a, HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>>,
|
pub bind_groups: RwLockReadGuard<'a, HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>>,
|
||||||
|
@ -59,7 +60,7 @@ impl<'a> WgpuResourcesReadLock<'a> {
|
||||||
WgpuResourceRefs {
|
WgpuResourceRefs {
|
||||||
buffers: &self.buffers,
|
buffers: &self.buffers,
|
||||||
textures: &self.textures,
|
textures: &self.textures,
|
||||||
swap_chain_frames: &self.swap_chain_frames,
|
surface_textures: &self.surface_textures,
|
||||||
render_pipelines: &self.render_pipelines,
|
render_pipelines: &self.render_pipelines,
|
||||||
bind_groups: &self.bind_groups,
|
bind_groups: &self.bind_groups,
|
||||||
used_bind_group_sender: &self.used_bind_group_sender,
|
used_bind_group_sender: &self.used_bind_group_sender,
|
||||||
|
@ -73,7 +74,7 @@ impl<'a> WgpuResourcesReadLock<'a> {
|
||||||
pub struct WgpuResourceRefs<'a> {
|
pub struct WgpuResourceRefs<'a> {
|
||||||
pub buffers: &'a HashMap<BufferId, Arc<wgpu::Buffer>>,
|
pub buffers: &'a HashMap<BufferId, Arc<wgpu::Buffer>>,
|
||||||
pub textures: &'a HashMap<TextureId, wgpu::TextureView>,
|
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 render_pipelines: &'a HashMap<Handle<PipelineDescriptor>, wgpu::RenderPipeline>,
|
||||||
pub bind_groups: &'a HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>,
|
pub bind_groups: &'a HashMap<BindGroupDescriptorId, WgpuBindGroupInfo>,
|
||||||
pub used_bind_group_sender: &'a Sender<BindGroupId>,
|
pub used_bind_group_sender: &'a Sender<BindGroupId>,
|
||||||
|
@ -84,8 +85,8 @@ pub struct WgpuResources {
|
||||||
pub buffer_infos: Arc<RwLock<HashMap<BufferId, BufferInfo>>>,
|
pub buffer_infos: Arc<RwLock<HashMap<BufferId, BufferInfo>>>,
|
||||||
pub texture_descriptors: Arc<RwLock<HashMap<TextureId, TextureDescriptor>>>,
|
pub texture_descriptors: Arc<RwLock<HashMap<TextureId, TextureDescriptor>>>,
|
||||||
pub window_surfaces: Arc<RwLock<HashMap<WindowId, wgpu::Surface>>>,
|
pub window_surfaces: Arc<RwLock<HashMap<WindowId, wgpu::Surface>>>,
|
||||||
pub window_swap_chains: Arc<RwLock<HashMap<WindowId, wgpu::SwapChain>>>,
|
pub surface_textures:
|
||||||
pub swap_chain_frames: Arc<RwLock<HashMap<TextureId, wgpu::SwapChainFrame>>>,
|
Arc<RwLock<HashMap<TextureId, (wgpu::TextureView, wgpu::SurfaceTexture)>>>,
|
||||||
pub buffers: Arc<RwLock<HashMap<BufferId, Arc<wgpu::Buffer>>>>,
|
pub buffers: Arc<RwLock<HashMap<BufferId, Arc<wgpu::Buffer>>>>,
|
||||||
pub texture_views: Arc<RwLock<HashMap<TextureId, wgpu::TextureView>>>,
|
pub texture_views: Arc<RwLock<HashMap<TextureId, wgpu::TextureView>>>,
|
||||||
pub textures: Arc<RwLock<HashMap<TextureId, wgpu::Texture>>>,
|
pub textures: Arc<RwLock<HashMap<TextureId, wgpu::Texture>>>,
|
||||||
|
@ -103,7 +104,7 @@ impl WgpuResources {
|
||||||
WgpuResourcesReadLock {
|
WgpuResourcesReadLock {
|
||||||
buffers: self.buffers.read(),
|
buffers: self.buffers.read(),
|
||||||
textures: self.texture_views.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(),
|
render_pipelines: self.render_pipelines.read(),
|
||||||
bind_groups: self.bind_groups.read(),
|
bind_groups: self.bind_groups.read(),
|
||||||
used_bind_group_sender: self.bind_group_counter.used_bind_group_sender.clone(),
|
used_bind_group_sender: self.bind_group_counter.used_bind_group_sender.clone(),
|
||||||
|
|
|
@ -13,7 +13,7 @@ use bevy_render::{
|
||||||
texture::{
|
texture::{
|
||||||
AddressMode, Extent3d, FilterMode, SamplerBorderColor, SamplerDescriptor,
|
AddressMode, Extent3d, FilterMode, SamplerBorderColor, SamplerDescriptor,
|
||||||
StorageTextureAccess, TextureDescriptor, TextureDimension, TextureFormat,
|
StorageTextureAccess, TextureDescriptor, TextureDimension, TextureFormat,
|
||||||
TextureSampleType, TextureUsage, TextureViewDimension,
|
TextureSampleType, TextureUsages, TextureViewDimension,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use bevy_window::Window;
|
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 {
|
fn from(val: InputStepMode) -> Self {
|
||||||
match val {
|
match val {
|
||||||
InputStepMode::Vertex => wgpu::InputStepMode::Vertex,
|
InputStepMode::Vertex => wgpu::VertexStepMode::Vertex,
|
||||||
InputStepMode::Instance => wgpu::InputStepMode::Instance,
|
InputStepMode::Instance => wgpu::VertexStepMode::Instance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ impl WgpuFrom<InputStepMode> for wgpu::InputStepMode {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OwnedWgpuVertexBufferLayout {
|
pub struct OwnedWgpuVertexBufferLayout {
|
||||||
pub array_stride: wgpu::BufferAddress,
|
pub array_stride: wgpu::BufferAddress,
|
||||||
pub step_mode: wgpu::InputStepMode,
|
pub step_mode: wgpu::VertexStepMode,
|
||||||
pub attributes: Vec<wgpu::VertexAttribute>,
|
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 {
|
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 {
|
} => wgpu::BindingType::Buffer {
|
||||||
ty: BufferBindingType::Uniform,
|
ty: BufferBindingType::Uniform,
|
||||||
has_dynamic_offset: *has_dynamic_offset,
|
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 {
|
BindType::StorageBuffer {
|
||||||
has_dynamic_offset,
|
has_dynamic_offset,
|
||||||
|
@ -200,7 +202,9 @@ impl WgpuFrom<&BindType> for wgpu::BindingType {
|
||||||
read_only: *readonly,
|
read_only: *readonly,
|
||||||
},
|
},
|
||||||
has_dynamic_offset: *has_dynamic_offset,
|
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 {
|
BindType::Texture {
|
||||||
view_dimension,
|
view_dimension,
|
||||||
|
@ -346,9 +350,9 @@ impl WgpuFrom<TextureFormat> for wgpu::TextureFormat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WgpuFrom<TextureUsage> for wgpu::TextureUsage {
|
impl WgpuFrom<TextureUsages> for wgpu::TextureUsages {
|
||||||
fn from(val: TextureUsage) -> Self {
|
fn from(val: TextureUsages) -> Self {
|
||||||
wgpu::TextureUsage::from_bits(val.bits()).unwrap()
|
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 {
|
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 {
|
fn from(window: &Window) -> Self {
|
||||||
wgpu::SwapChainDescriptor {
|
wgpu::SurfaceConfiguration {
|
||||||
usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
format: TextureFormat::default().wgpu_into(),
|
format: TextureFormat::default().wgpu_into(),
|
||||||
width: window.physical_width().max(1),
|
width: window.physical_width().max(1),
|
||||||
height: window.physical_height().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::TimestampQuery => wgpu::Features::TIMESTAMP_QUERY,
|
||||||
WgpuFeature::PipelineStatisticsQuery => wgpu::Features::PIPELINE_STATISTICS_QUERY,
|
WgpuFeature::PipelineStatisticsQuery => wgpu::Features::PIPELINE_STATISTICS_QUERY,
|
||||||
WgpuFeature::MappablePrimaryBuffers => wgpu::Features::MAPPABLE_PRIMARY_BUFFERS,
|
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::UnsizedBindingArray => wgpu::Features::UNSIZED_BINDING_ARRAY,
|
||||||
WgpuFeature::MultiDrawIndirect => wgpu::Features::MULTI_DRAW_INDIRECT,
|
WgpuFeature::MultiDrawIndirect => wgpu::Features::MULTI_DRAW_INDIRECT,
|
||||||
WgpuFeature::MultiDrawIndirectCount => wgpu::Features::MULTI_DRAW_INDIRECT_COUNT,
|
WgpuFeature::MultiDrawIndirectCount => wgpu::Features::MULTI_DRAW_INDIRECT_COUNT,
|
||||||
WgpuFeature::PushConstants => wgpu::Features::PUSH_CONSTANTS,
|
WgpuFeature::PushConstants => wgpu::Features::PUSH_CONSTANTS,
|
||||||
WgpuFeature::AddressModeClampToBorder => wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER,
|
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::TextureCompressionEtc2 => wgpu::Features::TEXTURE_COMPRESSION_ETC2,
|
||||||
WgpuFeature::TextureCompressionAstcLdr => wgpu::Features::TEXTURE_COMPRESSION_ASTC_LDR,
|
WgpuFeature::TextureCompressionAstcLdr => wgpu::Features::TEXTURE_COMPRESSION_ASTC_LDR,
|
||||||
WgpuFeature::TextureAdapterSpecificFormatFeatures => {
|
WgpuFeature::TextureAdapterSpecificFormatFeatures => {
|
||||||
|
@ -724,6 +719,8 @@ impl WgpuFrom<WgpuLimits> for wgpu::Limits {
|
||||||
max_vertex_buffers: val.max_vertex_buffers,
|
max_vertex_buffers: val.max_vertex_buffers,
|
||||||
max_vertex_attributes: val.max_vertex_attributes,
|
max_vertex_attributes: val.max_vertex_attributes,
|
||||||
max_vertex_buffer_array_stride: val.max_vertex_buffer_array_stride,
|
max_vertex_buffer_array_stride: val.max_vertex_buffer_array_stride,
|
||||||
|
min_storage_buffer_offset_alignment: val.min_storage_buffer_offset_alignment,
|
||||||
|
min_uniform_buffer_offset_alignment: val.min_uniform_buffer_offset_alignment,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ keywords = ["bevy"]
|
||||||
bevy_app = { path = "../bevy_app", version = "0.5.0" }
|
bevy_app = { path = "../bevy_app", version = "0.5.0" }
|
||||||
bevy_math = { path = "../bevy_math", version = "0.5.0" }
|
bevy_math = { path = "../bevy_math", version = "0.5.0" }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
|
bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
|
||||||
|
raw-window-handle = "0.3.0"
|
||||||
|
|
||||||
# other
|
# other
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
mod event;
|
mod event;
|
||||||
|
mod raw_window_handle;
|
||||||
mod system;
|
mod system;
|
||||||
mod window;
|
mod window;
|
||||||
mod windows;
|
mod windows;
|
||||||
|
|
||||||
|
pub use crate::raw_window_handle::*;
|
||||||
pub use event::*;
|
pub use event::*;
|
||||||
pub use system::*;
|
pub use system::*;
|
||||||
pub use window::*;
|
pub use window::*;
|
||||||
|
|
37
crates/bevy_window/src/raw_window_handle.rs
Normal file
37
crates/bevy_window/src/raw_window_handle.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||||
|
|
||||||
|
/// This wrapper exist to enable safely passing a [`RawWindowHandle`] across threads. Extracting the handle
|
||||||
|
/// is still an unsafe operation, so the caller must still validate that using the raw handle is safe for a given context.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RawWindowHandleWrapper(RawWindowHandle);
|
||||||
|
|
||||||
|
impl RawWindowHandleWrapper {
|
||||||
|
pub(crate) fn new(handle: RawWindowHandle) -> Self {
|
||||||
|
Self(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// This returns a [`HasRawWindowHandle`] impl, which exposes [`RawWindowHandle`]. Some platforms
|
||||||
|
/// have constraints on where/how this handle can be used. For example, some platforms don't support doing window
|
||||||
|
/// operations off of the main thread. The caller must ensure the [`RawWindowHandle`] is only used in valid contexts.
|
||||||
|
pub unsafe fn get_handle(&self) -> HasRawWindowHandleWrapper {
|
||||||
|
HasRawWindowHandleWrapper(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFE: RawWindowHandle is just a normal "raw pointer", which doesn't impl Send/Sync. However the pointer is only
|
||||||
|
// exposed via an unsafe method that forces the user to make a call for a given platform. (ex: some platforms don't
|
||||||
|
// support doing window operations off of the main thread).
|
||||||
|
// A recommendation for this pattern (and more context) is available here:
|
||||||
|
// https://github.com/rust-windowing/raw-window-handle/issues/59
|
||||||
|
unsafe impl Send for RawWindowHandleWrapper {}
|
||||||
|
unsafe impl Sync for RawWindowHandleWrapper {}
|
||||||
|
|
||||||
|
pub struct HasRawWindowHandleWrapper(RawWindowHandle);
|
||||||
|
|
||||||
|
// SAFE: the caller has validated that this is a valid context to get RawWindowHandle
|
||||||
|
unsafe impl HasRawWindowHandle for HasRawWindowHandleWrapper {
|
||||||
|
fn raw_window_handle(&self) -> RawWindowHandle {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
use bevy_math::{DVec2, IVec2, Vec2};
|
use bevy_math::{DVec2, IVec2, Vec2};
|
||||||
use bevy_utils::{tracing::warn, Uuid};
|
use bevy_utils::{tracing::warn, Uuid};
|
||||||
|
use raw_window_handle::RawWindowHandle;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct WindowId(Uuid);
|
pub struct WindowId(Uuid);
|
||||||
|
@ -20,6 +21,8 @@ impl WindowId {
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::raw_window_handle::RawWindowHandleWrapper;
|
||||||
|
|
||||||
impl fmt::Display for WindowId {
|
impl fmt::Display for WindowId {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
self.0.to_simple().fmt(f)
|
self.0.to_simple().fmt(f)
|
||||||
|
@ -123,6 +126,7 @@ pub struct Window {
|
||||||
cursor_visible: bool,
|
cursor_visible: bool,
|
||||||
cursor_locked: bool,
|
cursor_locked: bool,
|
||||||
physical_cursor_position: Option<DVec2>,
|
physical_cursor_position: Option<DVec2>,
|
||||||
|
raw_window_handle: RawWindowHandleWrapper,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
mode: WindowMode,
|
mode: WindowMode,
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
@ -198,6 +202,7 @@ impl Window {
|
||||||
physical_height: u32,
|
physical_height: u32,
|
||||||
scale_factor: f64,
|
scale_factor: f64,
|
||||||
position: Option<IVec2>,
|
position: Option<IVec2>,
|
||||||
|
raw_window_handle: RawWindowHandle,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Window {
|
Window {
|
||||||
id,
|
id,
|
||||||
|
@ -216,6 +221,7 @@ impl Window {
|
||||||
cursor_visible: window_descriptor.cursor_visible,
|
cursor_visible: window_descriptor.cursor_visible,
|
||||||
cursor_locked: window_descriptor.cursor_locked,
|
cursor_locked: window_descriptor.cursor_locked,
|
||||||
physical_cursor_position: None,
|
physical_cursor_position: None,
|
||||||
|
raw_window_handle: RawWindowHandleWrapper::new(raw_window_handle),
|
||||||
focused: true,
|
focused: true,
|
||||||
mode: window_descriptor.mode,
|
mode: window_descriptor.mode,
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
@ -519,6 +525,10 @@ impl Window {
|
||||||
pub fn is_focused(&self) -> bool {
|
pub fn is_focused(&self) -> bool {
|
||||||
self.focused
|
self.focused
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn raw_window_handle(&self) -> RawWindowHandleWrapper {
|
||||||
|
self.raw_window_handle.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
@ -24,6 +24,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
|
||||||
# other
|
# other
|
||||||
winit = { version = "0.25.0", default-features = false }
|
winit = { version = "0.25.0", default-features = false }
|
||||||
approx = { version = "0.5.0", default-features = false }
|
approx = { version = "0.5.0", default-features = false }
|
||||||
|
raw-window-handle = "0.3.0"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
winit = { version = "0.25.0", features = ["web-sys"], default-features = false }
|
winit = { version = "0.25.0", features = ["web-sys"], default-features = false }
|
||||||
|
|
|
@ -26,14 +26,6 @@ use winit::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use winit::dpi::LogicalSize;
|
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)]
|
#[derive(Default)]
|
||||||
pub struct WinitPlugin;
|
pub struct WinitPlugin;
|
||||||
|
@ -43,6 +35,9 @@ impl Plugin for WinitPlugin {
|
||||||
app.init_resource::<WinitWindows>()
|
app.init_resource::<WinitWindows>()
|
||||||
.set_runner(winit_runner)
|
.set_runner(winit_runner)
|
||||||
.add_system_to_stage(CoreStage::PostUpdate, change_window.exclusive_system());
|
.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) {
|
pub fn winit_runner(app: App) {
|
||||||
winit_runner_with(app, EventLoop::new());
|
winit_runner_with(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(
|
// #[cfg(any(
|
||||||
target_os = "linux",
|
// target_os = "linux",
|
||||||
target_os = "dragonfly",
|
// target_os = "dragonfly",
|
||||||
target_os = "freebsd",
|
// target_os = "freebsd",
|
||||||
target_os = "netbsd",
|
// target_os = "netbsd",
|
||||||
target_os = "openbsd"
|
// target_os = "openbsd"
|
||||||
))]
|
// ))]
|
||||||
pub fn winit_runner_any_thread(app: App) {
|
// pub fn winit_runner_any_thread(app: App) {
|
||||||
winit_runner_with(app, EventLoop::new_any_thread());
|
// 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 create_window_event_reader = ManualEventReader::<CreateWindow>::default();
|
||||||
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
|
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
|
||||||
app.world.insert_non_send(event_loop.create_proxy());
|
app.world.insert_non_send(event_loop.create_proxy());
|
||||||
|
@ -534,3 +530,22 @@ fn handle_create_window_events(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_initial_window_events(world: &mut World, event_loop: &EventLoop<()>) {
|
||||||
|
let world = world.cell();
|
||||||
|
let mut winit_windows = world.get_resource_mut::<WinitWindows>().unwrap();
|
||||||
|
let mut windows = world.get_resource_mut::<Windows>().unwrap();
|
||||||
|
let mut create_window_events = world.get_resource_mut::<Events<CreateWindow>>().unwrap();
|
||||||
|
let mut window_created_events = world.get_resource_mut::<Events<WindowCreated>>().unwrap();
|
||||||
|
for create_window_event in create_window_events.drain() {
|
||||||
|
let window = winit_windows.create_window(
|
||||||
|
event_loop,
|
||||||
|
create_window_event.id,
|
||||||
|
&create_window_event.descriptor,
|
||||||
|
);
|
||||||
|
windows.add(window);
|
||||||
|
window_created_events.send(WindowCreated {
|
||||||
|
id: create_window_event.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use bevy_math::IVec2;
|
use bevy_math::IVec2;
|
||||||
use bevy_utils::HashMap;
|
use bevy_utils::HashMap;
|
||||||
use bevy_window::{Window, WindowDescriptor, WindowId, WindowMode};
|
use bevy_window::{Window, WindowDescriptor, WindowId, WindowMode};
|
||||||
|
use raw_window_handle::HasRawWindowHandle;
|
||||||
use winit::dpi::LogicalSize;
|
use winit::dpi::LogicalSize;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
@ -156,6 +157,7 @@ impl WinitWindows {
|
||||||
.map(|position| IVec2::new(position.x, position.y));
|
.map(|position| IVec2::new(position.x, position.y));
|
||||||
let inner_size = winit_window.inner_size();
|
let inner_size = winit_window.inner_size();
|
||||||
let scale_factor = winit_window.scale_factor();
|
let scale_factor = winit_window.scale_factor();
|
||||||
|
let raw_window_handle = winit_window.raw_window_handle();
|
||||||
self.windows.insert(winit_window.id(), winit_window);
|
self.windows.insert(winit_window.id(), winit_window);
|
||||||
Window::new(
|
Window::new(
|
||||||
window_id,
|
window_id,
|
||||||
|
@ -164,6 +166,7 @@ impl WinitWindows {
|
||||||
inner_size.height,
|
inner_size.height,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
position,
|
position,
|
||||||
|
raw_window_handle,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
36
crates/crevice/Cargo.toml
Normal file
36
crates/crevice/Cargo.toml
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
[package]
|
||||||
|
name = "crevice"
|
||||||
|
description = "Create GLSL-compatible versions of structs with explicitly-initialized padding"
|
||||||
|
version = "0.8.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||||
|
documentation = "https://docs.rs/crevice"
|
||||||
|
homepage = "https://github.com/LPGhatguy/crevice"
|
||||||
|
repository = "https://github.com/LPGhatguy/crevice"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = ["glsl", "std140", "std430"]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
# resolver = "2"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["std"]
|
||||||
|
std = []
|
||||||
|
|
||||||
|
# [workspace]
|
||||||
|
# members = ["crevice-derive", "crevice-tests"]
|
||||||
|
# default-members = ["crevice-derive", "crevice-tests"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
crevice-derive = { version = "0.8.0", path = "crevice-derive" }
|
||||||
|
|
||||||
|
bytemuck = "1.4.1"
|
||||||
|
mint = "0.5.8"
|
||||||
|
|
||||||
|
cgmath = { version = "0.18.0", optional = true }
|
||||||
|
glam = { version = "0.20.0", features = ["mint"], optional = true }
|
||||||
|
nalgebra = { version = "0.29.0", features = ["mint"], optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
insta = "0.16.1"
|
201
crates/crevice/LICENSE-APACHE
Normal file
201
crates/crevice/LICENSE-APACHE
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
i Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
19
crates/crevice/LICENSE-MIT
Normal file
19
crates/crevice/LICENSE-MIT
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2020 Lucien Greathouse
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
170
crates/crevice/README.md
Normal file
170
crates/crevice/README.md
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
# Crevice
|
||||||
|
|
||||||
|
[![GitHub CI Status](https://github.com/LPGhatguy/crevice/workflows/CI/badge.svg)](https://github.com/LPGhatguy/crevice/actions)
|
||||||
|
[![crevice on crates.io](https://img.shields.io/crates/v/crevice.svg)](https://crates.io/crates/crevice)
|
||||||
|
[![crevice docs](https://img.shields.io/badge/docs-docs.rs-orange.svg)](https://docs.rs/crevice)
|
||||||
|
|
||||||
|
Crevice creates GLSL-compatible versions of types through the power of derive
|
||||||
|
macros. Generated structures provide an [`as_bytes`][std140::Std140::as_bytes]
|
||||||
|
method to allow safely packing data into buffers for uploading.
|
||||||
|
|
||||||
|
Generated structs also implement [`bytemuck::Zeroable`] and
|
||||||
|
[`bytemuck::Pod`] for use with other libraries.
|
||||||
|
|
||||||
|
Crevice is similar to [`glsl-layout`][glsl-layout], but supports types from many
|
||||||
|
math crates, can generate GLSL source from structs, and explicitly initializes
|
||||||
|
padding to remove one source of undefined behavior.
|
||||||
|
|
||||||
|
Crevice has support for many Rust math libraries via feature flags, and most
|
||||||
|
other math libraries by use of the mint crate. Crevice currently supports:
|
||||||
|
|
||||||
|
* mint 0.5, enabled by default
|
||||||
|
* cgmath 0.18, using the `cgmath` feature
|
||||||
|
* nalgebra 0.29, using the `nalgebra` feature
|
||||||
|
* glam 0.19, using the `glam` feature
|
||||||
|
|
||||||
|
PRs are welcome to add or update math libraries to Crevice.
|
||||||
|
|
||||||
|
If your math library is not supported, it's possible to define structs using the
|
||||||
|
types from mint and convert your math library's types into mint types. This is
|
||||||
|
supported by most Rust math libraries.
|
||||||
|
|
||||||
|
Your math library may require you to turn on a feature flag to get mint support.
|
||||||
|
For example, cgmath requires the "mint" feature to be enabled to allow
|
||||||
|
conversions to and from mint types.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Single Value
|
||||||
|
|
||||||
|
Uploading many types can be done by deriving [`AsStd140`][std140::AsStd140] and
|
||||||
|
using [`as_std140`][std140::AsStd140::as_std140] and
|
||||||
|
[`as_bytes`][std140::Std140::as_bytes] to turn the result into bytes.
|
||||||
|
|
||||||
|
```glsl
|
||||||
|
uniform MAIN {
|
||||||
|
mat3 orientation;
|
||||||
|
vec3 position;
|
||||||
|
float scale;
|
||||||
|
} main;
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use crevice::std140::{AsStd140, Std140};
|
||||||
|
|
||||||
|
#[derive(AsStd140)]
|
||||||
|
struct MainUniform {
|
||||||
|
orientation: mint::ColumnMatrix3<f32>,
|
||||||
|
position: mint::Vector3<f32>,
|
||||||
|
scale: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = MainUniform {
|
||||||
|
orientation: [
|
||||||
|
[1.0, 0.0, 0.0],
|
||||||
|
[0.0, 1.0, 0.0],
|
||||||
|
[0.0, 0.0, 1.0],
|
||||||
|
].into(),
|
||||||
|
position: [1.0, 2.0, 3.0].into(),
|
||||||
|
scale: 4.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let value_std140 = value.as_std140();
|
||||||
|
|
||||||
|
upload_data_to_gpu(value_std140.as_bytes());
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sequential Types
|
||||||
|
|
||||||
|
More complicated data can be uploaded using the std140
|
||||||
|
[`Writer`][std140::Writer] type.
|
||||||
|
|
||||||
|
```glsl
|
||||||
|
struct PointLight {
|
||||||
|
vec3 position;
|
||||||
|
vec3 color;
|
||||||
|
float brightness;
|
||||||
|
};
|
||||||
|
|
||||||
|
buffer POINT_LIGHTS {
|
||||||
|
uint len;
|
||||||
|
PointLight[] lights;
|
||||||
|
} point_lights;
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use crevice::std140::{self, AsStd140};
|
||||||
|
|
||||||
|
#[derive(AsStd140)]
|
||||||
|
struct PointLight {
|
||||||
|
position: mint::Vector3<f32>,
|
||||||
|
color: mint::Vector3<f32>,
|
||||||
|
brightness: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
let lights = vec![
|
||||||
|
PointLight {
|
||||||
|
position: [0.0, 1.0, 0.0].into(),
|
||||||
|
color: [1.0, 0.0, 0.0].into(),
|
||||||
|
brightness: 0.6,
|
||||||
|
},
|
||||||
|
PointLight {
|
||||||
|
position: [0.0, 4.0, 3.0].into(),
|
||||||
|
color: [1.0, 1.0, 1.0].into(),
|
||||||
|
brightness: 1.0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let target_buffer = map_gpu_buffer_for_write();
|
||||||
|
let mut writer = std140::Writer::new(target_buffer);
|
||||||
|
|
||||||
|
let light_count = lights.len() as u32;
|
||||||
|
writer.write(&light_count)?;
|
||||||
|
|
||||||
|
// Crevice will automatically insert the required padding to align the
|
||||||
|
// PointLight structure correctly. In this case, there will be 12 bytes of
|
||||||
|
// padding between the length field and the light list.
|
||||||
|
|
||||||
|
writer.write(lights.as_slice())?;
|
||||||
|
|
||||||
|
unmap_gpu_buffer();
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* `std` (default): Enables [`std::io::Write`]-based structs.
|
||||||
|
* `cgmath`: Enables support for types from cgmath.
|
||||||
|
* `nalgebra`: Enables support for types from nalgebra.
|
||||||
|
* `glam`: Enables support for types from glam.
|
||||||
|
|
||||||
|
## Minimum Supported Rust Version (MSRV)
|
||||||
|
|
||||||
|
Crevice supports Rust 1.52.1 and newer due to use of new `const fn` features.
|
||||||
|
|
||||||
|
[glsl-layout]: https://github.com/rustgd/glsl-layout
|
||||||
|
|
||||||
|
[std140::AsStd140]: https://docs.rs/crevice/latest/crevice/std140/trait.AsStd140.html
|
||||||
|
[std140::AsStd140::as_std140]: https://docs.rs/crevice/latest/crevice/std140/trait.AsStd140.html#method.as_std140
|
||||||
|
[std140::Std140::as_bytes]: https://docs.rs/crevice/latest/crevice/std140/trait.Std140.html#method.as_bytes
|
||||||
|
[std140::Writer]: https://docs.rs/crevice/latest/crevice/std140/struct.Writer.html
|
||||||
|
|
||||||
|
[`std::io::Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html
|
||||||
|
|
||||||
|
[`bytemuck::Pod`]: https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html
|
||||||
|
[`bytemuck::Zeroable`]: https://docs.rs/bytemuck/latest/bytemuck/trait.Zeroable.html
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Licensed under either of
|
||||||
|
|
||||||
|
* Apache License, Version 2.0, ([LICENSE-APACHE](http://www.apache.org/licenses/LICENSE-2.0))
|
||||||
|
* MIT license ([LICENSE-MIT](http://opensource.org/licenses/MIT))
|
||||||
|
|
||||||
|
at your option.
|
||||||
|
|
||||||
|
### Contribution
|
||||||
|
|
||||||
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||||
|
for inclusion in the work by you, as defined in the Apache-2.0 license, shall
|
||||||
|
be dual licensed as above, without any additional terms or conditions.
|
25
crates/crevice/README.tpl
Normal file
25
crates/crevice/README.tpl
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Crevice
|
||||||
|
|
||||||
|
{{readme}}
|
||||||
|
|
||||||
|
[std140::AsStd140]: https://docs.rs/crevice/latest/crevice/std140/trait.AsStd140.html
|
||||||
|
[std140::AsStd140::as_std140]: https://docs.rs/crevice/latest/crevice/std140/trait.AsStd140.html#method.as_std140
|
||||||
|
[std140::Std140::as_bytes]: https://docs.rs/crevice/latest/crevice/std140/trait.Std140.html#method.as_bytes
|
||||||
|
[std140::Writer]: https://docs.rs/crevice/latest/crevice/std140/struct.Writer.html
|
||||||
|
|
||||||
|
[`std::io::Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html
|
||||||
|
|
||||||
|
[`bytemuck::Pod`]: https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html
|
||||||
|
[`bytemuck::Zeroable`]: https://docs.rs/bytemuck/latest/bytemuck/trait.Zeroable.html
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Licensed under either of
|
||||||
|
|
||||||
|
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
at your option.
|
||||||
|
|
||||||
|
### Contribution
|
||||||
|
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
|
26
crates/crevice/crevice-derive/Cargo.toml
Normal file
26
crates/crevice/crevice-derive/Cargo.toml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
[package]
|
||||||
|
name = "crevice-derive"
|
||||||
|
description = "Derive crate for the 'crevice' crate"
|
||||||
|
version = "0.8.0"
|
||||||
|
edition = "2018"
|
||||||
|
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||||
|
documentation = "https://docs.rs/crevice-derive"
|
||||||
|
homepage = "https://github.com/LPGhatguy/crevice"
|
||||||
|
repository = "https://github.com/LPGhatguy/crevice"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|
||||||
|
# Enable methods that let you introspect into the generated structs.
|
||||||
|
debug-methods = []
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = "1.0.40"
|
||||||
|
quote = "1.0.7"
|
||||||
|
proc-macro2 = "1.0.21"
|
49
crates/crevice/crevice-derive/src/glsl.rs
Normal file
49
crates/crevice/crevice-derive/src/glsl.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
use proc_macro2::{Literal, TokenStream};
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{parse_quote, Data, DeriveInput, Fields, Path};
|
||||||
|
|
||||||
|
pub fn emit(input: DeriveInput) -> TokenStream {
|
||||||
|
let fields = match &input.data {
|
||||||
|
Data::Struct(data) => match &data.fields {
|
||||||
|
Fields::Named(fields) => fields,
|
||||||
|
Fields::Unnamed(_) => panic!("Tuple structs are not supported"),
|
||||||
|
Fields::Unit => panic!("Unit structs are not supported"),
|
||||||
|
},
|
||||||
|
Data::Enum(_) | Data::Union(_) => panic!("Only structs are supported"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let base_trait_path: Path = parse_quote!(::crevice::glsl::Glsl);
|
||||||
|
let struct_trait_path: Path = parse_quote!(::crevice::glsl::GlslStruct);
|
||||||
|
|
||||||
|
let name = input.ident;
|
||||||
|
let name_str = Literal::string(&name.to_string());
|
||||||
|
|
||||||
|
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||||
|
|
||||||
|
let glsl_fields = fields.named.iter().map(|field| {
|
||||||
|
let field_ty = &field.ty;
|
||||||
|
let field_name_str = Literal::string(&field.ident.as_ref().unwrap().to_string());
|
||||||
|
let field_as = quote! {<#field_ty as ::crevice::glsl::GlslArray>};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
s.push_str("\t");
|
||||||
|
s.push_str(#field_as::NAME);
|
||||||
|
s.push_str(" ");
|
||||||
|
s.push_str(#field_name_str);
|
||||||
|
<#field_as::ArraySize as ::crevice::glsl::DimensionList>::push_to_string(s);
|
||||||
|
s.push_str(";\n");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
unsafe impl #impl_generics #base_trait_path for #name #ty_generics #where_clause {
|
||||||
|
const NAME: &'static str = #name_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl #impl_generics #struct_trait_path for #name #ty_generics #where_clause {
|
||||||
|
fn enumerate_fields(s: &mut String) {
|
||||||
|
#( #glsl_fields )*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
288
crates/crevice/crevice-derive/src/layout.rs
Normal file
288
crates/crevice/crevice-derive/src/layout.rs
Normal file
|
@ -0,0 +1,288 @@
|
||||||
|
use proc_macro2::{Span, TokenStream};
|
||||||
|
use quote::{format_ident, quote};
|
||||||
|
use syn::{parse_quote, Data, DeriveInput, Fields, Ident, Path, Type};
|
||||||
|
|
||||||
|
pub fn emit(
|
||||||
|
input: DeriveInput,
|
||||||
|
trait_name: &'static str,
|
||||||
|
mod_name: &'static str,
|
||||||
|
min_struct_alignment: usize,
|
||||||
|
) -> TokenStream {
|
||||||
|
let mod_name = Ident::new(mod_name, Span::call_site());
|
||||||
|
let trait_name = Ident::new(trait_name, Span::call_site());
|
||||||
|
|
||||||
|
let mod_path: Path = parse_quote!(::crevice::#mod_name);
|
||||||
|
let trait_path: Path = parse_quote!(#mod_path::#trait_name);
|
||||||
|
|
||||||
|
let as_trait_name = format_ident!("As{}", trait_name);
|
||||||
|
let as_trait_path: Path = parse_quote!(#mod_path::#as_trait_name);
|
||||||
|
let as_trait_method = format_ident!("as_{}", mod_name);
|
||||||
|
let from_trait_method = format_ident!("from_{}", mod_name);
|
||||||
|
|
||||||
|
let padded_name = format_ident!("{}Padded", trait_name);
|
||||||
|
let padded_path: Path = parse_quote!(#mod_path::#padded_name);
|
||||||
|
|
||||||
|
let visibility = input.vis;
|
||||||
|
let input_name = input.ident;
|
||||||
|
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||||
|
|
||||||
|
let generated_name = format_ident!("{}{}", trait_name, input_name);
|
||||||
|
|
||||||
|
// Crevice's derive only works on regular structs. We could potentially
|
||||||
|
// support transparent tuple structs in the future.
|
||||||
|
let fields: Vec<_> = match &input.data {
|
||||||
|
Data::Struct(data) => match &data.fields {
|
||||||
|
Fields::Named(fields) => fields.named.iter().collect(),
|
||||||
|
Fields::Unnamed(_) => panic!("Tuple structs are not supported"),
|
||||||
|
Fields::Unit => panic!("Unit structs are not supported"),
|
||||||
|
},
|
||||||
|
Data::Enum(_) | Data::Union(_) => panic!("Only structs are supported"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gives the layout-specific version of the given type.
|
||||||
|
let layout_version_of_ty = |ty: &Type| {
|
||||||
|
quote! {
|
||||||
|
<#ty as #as_trait_path>::Output
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gives an expression returning the layout-specific alignment for the type.
|
||||||
|
let layout_alignment_of_ty = |ty: &Type| {
|
||||||
|
quote! {
|
||||||
|
<<#ty as #as_trait_path>::Output as #trait_path>::ALIGNMENT
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gives an expression telling whether the type should have trailing padding
|
||||||
|
// at least equal to its alignment.
|
||||||
|
let layout_pad_at_end_of_ty = |ty: &Type| {
|
||||||
|
quote! {
|
||||||
|
<<#ty as #as_trait_path>::Output as #trait_path>::PAD_AT_END
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let field_alignments = fields.iter().map(|field| layout_alignment_of_ty(&field.ty));
|
||||||
|
let struct_alignment = quote! {
|
||||||
|
::crevice::internal::max_arr([
|
||||||
|
#min_struct_alignment,
|
||||||
|
#(#field_alignments,)*
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate names for each padding calculation function.
|
||||||
|
let pad_fns: Vec<_> = (0..fields.len())
|
||||||
|
.map(|index| format_ident!("_{}__{}Pad{}", input_name, trait_name, index))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Computes the offset immediately AFTER the field with the given index.
|
||||||
|
//
|
||||||
|
// This function depends on the generated padding calculation functions to
|
||||||
|
// do correct alignment. Be careful not to cause recursion!
|
||||||
|
let offset_after_field = |target: usize| {
|
||||||
|
let mut output = vec![quote!(0usize)];
|
||||||
|
|
||||||
|
for index in 0..=target {
|
||||||
|
let field_ty = &fields[index].ty;
|
||||||
|
let layout_ty = layout_version_of_ty(field_ty);
|
||||||
|
|
||||||
|
output.push(quote! {
|
||||||
|
+ ::core::mem::size_of::<#layout_ty>()
|
||||||
|
});
|
||||||
|
|
||||||
|
// For every field except our target field, also add the generated
|
||||||
|
// padding. Padding occurs after each field, so it isn't included in
|
||||||
|
// this value.
|
||||||
|
if index < target {
|
||||||
|
let pad_fn = &pad_fns[index];
|
||||||
|
output.push(quote! {
|
||||||
|
+ #pad_fn()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.into_iter().collect::<TokenStream>()
|
||||||
|
};
|
||||||
|
|
||||||
|
let pad_fn_impls: TokenStream = fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, prev_field)| {
|
||||||
|
let pad_fn = &pad_fns[index];
|
||||||
|
|
||||||
|
let starting_offset = offset_after_field(index);
|
||||||
|
let prev_field_has_end_padding = layout_pad_at_end_of_ty(&prev_field.ty);
|
||||||
|
let prev_field_alignment = layout_alignment_of_ty(&prev_field.ty);
|
||||||
|
|
||||||
|
let next_field_or_self_alignment = fields
|
||||||
|
.get(index + 1)
|
||||||
|
.map(|next_field| layout_alignment_of_ty(&next_field.ty))
|
||||||
|
.unwrap_or(quote!(#struct_alignment));
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
/// Tells how many bytes of padding have to be inserted after
|
||||||
|
/// the field with index #index.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
const fn #pad_fn() -> usize {
|
||||||
|
// First up, calculate our offset into the struct so far.
|
||||||
|
// We'll use this value to figure out how far out of
|
||||||
|
// alignment we are.
|
||||||
|
let starting_offset = #starting_offset;
|
||||||
|
|
||||||
|
// If the previous field is a struct or array, we must align
|
||||||
|
// the next field to at least THAT field's alignment.
|
||||||
|
let min_alignment = if #prev_field_has_end_padding {
|
||||||
|
#prev_field_alignment
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
// We set our target alignment to the larger of the
|
||||||
|
// alignment due to the previous field and the alignment
|
||||||
|
// requirement of the next field.
|
||||||
|
let alignment = ::crevice::internal::max(
|
||||||
|
#next_field_or_self_alignment,
|
||||||
|
min_alignment,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Using everything we've got, compute our padding amount.
|
||||||
|
::crevice::internal::align_offset(starting_offset, alignment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let generated_struct_fields: TokenStream = fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, field)| {
|
||||||
|
let field_name = field.ident.as_ref().unwrap();
|
||||||
|
let field_ty = layout_version_of_ty(&field.ty);
|
||||||
|
let pad_field_name = format_ident!("_pad{}", index);
|
||||||
|
let pad_fn = &pad_fns[index];
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#field_name: #field_ty,
|
||||||
|
#pad_field_name: [u8; #pad_fn()],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let generated_struct_field_init: TokenStream = fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| {
|
||||||
|
let field_name = field.ident.as_ref().unwrap();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#field_name: self.#field_name.#as_trait_method(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let input_struct_field_init: TokenStream = fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| {
|
||||||
|
let field_name = field.ident.as_ref().unwrap();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#field_name: #as_trait_path::#from_trait_method(input.#field_name),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let struct_definition = quote! {
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#visibility struct #generated_name #ty_generics #where_clause {
|
||||||
|
#generated_struct_fields
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let debug_methods = if cfg!(feature = "debug-methods") {
|
||||||
|
let debug_fields: TokenStream = fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| {
|
||||||
|
let field_name = field.ident.as_ref().unwrap();
|
||||||
|
let field_ty = &field.ty;
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
fields.push(Field {
|
||||||
|
name: stringify!(#field_name),
|
||||||
|
size: ::core::mem::size_of::<#field_ty>(),
|
||||||
|
offset: (&zeroed.#field_name as *const _ as usize)
|
||||||
|
- (&zeroed as *const _ as usize),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl #impl_generics #generated_name #ty_generics #where_clause {
|
||||||
|
fn debug_metrics() -> String {
|
||||||
|
let size = ::core::mem::size_of::<Self>();
|
||||||
|
let align = <Self as #trait_path>::ALIGNMENT;
|
||||||
|
|
||||||
|
let zeroed: Self = ::crevice::internal::bytemuck::Zeroable::zeroed();
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Field {
|
||||||
|
name: &'static str,
|
||||||
|
offset: usize,
|
||||||
|
size: usize,
|
||||||
|
}
|
||||||
|
let mut fields = Vec::new();
|
||||||
|
|
||||||
|
#debug_fields
|
||||||
|
|
||||||
|
format!("Size {}, Align {}, fields: {:#?}", size, align, fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_definitions() -> &'static str {
|
||||||
|
stringify!(
|
||||||
|
#struct_definition
|
||||||
|
#pad_fn_impls
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote!()
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#pad_fn_impls
|
||||||
|
#struct_definition
|
||||||
|
|
||||||
|
unsafe impl #impl_generics ::crevice::internal::bytemuck::Zeroable for #generated_name #ty_generics #where_clause {}
|
||||||
|
unsafe impl #impl_generics ::crevice::internal::bytemuck::Pod for #generated_name #ty_generics #where_clause {}
|
||||||
|
|
||||||
|
unsafe impl #impl_generics #mod_path::#trait_name for #generated_name #ty_generics #where_clause {
|
||||||
|
const ALIGNMENT: usize = #struct_alignment;
|
||||||
|
const PAD_AT_END: bool = true;
|
||||||
|
type Padded = #padded_path<Self, {::crevice::internal::align_offset(
|
||||||
|
::core::mem::size_of::<#generated_name>(),
|
||||||
|
#struct_alignment
|
||||||
|
)}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #impl_generics #as_trait_path for #input_name #ty_generics #where_clause {
|
||||||
|
type Output = #generated_name;
|
||||||
|
|
||||||
|
fn #as_trait_method(&self) -> Self::Output {
|
||||||
|
Self::Output {
|
||||||
|
#generated_struct_field_init
|
||||||
|
|
||||||
|
..::crevice::internal::bytemuck::Zeroable::zeroed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn #from_trait_method(input: Self::Output) -> Self {
|
||||||
|
Self {
|
||||||
|
#input_struct_field_init
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#debug_methods
|
||||||
|
}
|
||||||
|
}
|
30
crates/crevice/crevice-derive/src/lib.rs
Normal file
30
crates/crevice/crevice-derive/src/lib.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
mod glsl;
|
||||||
|
mod layout;
|
||||||
|
|
||||||
|
use proc_macro::TokenStream as CompilerTokenStream;
|
||||||
|
|
||||||
|
use syn::{parse_macro_input, DeriveInput};
|
||||||
|
|
||||||
|
#[proc_macro_derive(AsStd140)]
|
||||||
|
pub fn derive_as_std140(input: CompilerTokenStream) -> CompilerTokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let expanded = layout::emit(input, "Std140", "std140", 16);
|
||||||
|
|
||||||
|
CompilerTokenStream::from(expanded)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(AsStd430)]
|
||||||
|
pub fn derive_as_std430(input: CompilerTokenStream) -> CompilerTokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let expanded = layout::emit(input, "Std430", "std430", 0);
|
||||||
|
|
||||||
|
CompilerTokenStream::from(expanded)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(GlslStruct)]
|
||||||
|
pub fn derive_glsl_struct(input: CompilerTokenStream) -> CompilerTokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let expanded = glsl::emit(input);
|
||||||
|
|
||||||
|
CompilerTokenStream::from(expanded)
|
||||||
|
}
|
20
crates/crevice/crevice-tests/Cargo.toml
Normal file
20
crates/crevice/crevice-tests/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "crevice-tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
wgpu-validation = ["wgpu", "naga", "futures"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
crevice = { path = ".." }
|
||||||
|
crevice-derive = { path = "../crevice-derive", features = ["debug-methods"] }
|
||||||
|
|
||||||
|
anyhow = "1.0.44"
|
||||||
|
bytemuck = "1.7.2"
|
||||||
|
memoffset = "0.6.4"
|
||||||
|
mint = "0.5.5"
|
||||||
|
|
||||||
|
futures = { version = "0.3.17", features = ["executor"], optional = true }
|
||||||
|
naga = { version = "0.7.0", features = ["glsl-in", "wgsl-out"], optional = true }
|
||||||
|
wgpu = { version = "0.11.0", optional = true }
|
268
crates/crevice/crevice-tests/src/gpu.rs
Normal file
268
crates/crevice/crevice-tests/src/gpu.rs
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use crevice::glsl::{Glsl, GlslStruct};
|
||||||
|
use crevice::std140::{AsStd140, Std140};
|
||||||
|
use crevice::std430::{AsStd430, Std430};
|
||||||
|
use futures::executor::block_on;
|
||||||
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
|
const BASE_SHADER: &str = "#version 450
|
||||||
|
|
||||||
|
{struct_definition}
|
||||||
|
|
||||||
|
layout({layout}, set = 0, binding = 0) readonly buffer INPUT {
|
||||||
|
{struct_name} in_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout({layout}, set = 0, binding = 1) buffer OUTPUT {
|
||||||
|
{struct_name} out_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
out_data = in_data;
|
||||||
|
}";
|
||||||
|
|
||||||
|
pub fn test_round_trip_struct<T: Debug + PartialEq + AsStd140 + AsStd430 + GlslStruct>(value: T) {
|
||||||
|
let shader_std140 = glsl_shader_for_struct::<T>("std140");
|
||||||
|
let shader_std430 = glsl_shader_for_struct::<T>("std430");
|
||||||
|
|
||||||
|
let context = Context::new();
|
||||||
|
context.test_round_trip_std140(&shader_std140, &value);
|
||||||
|
context.test_round_trip_std430(&shader_std430, &value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_round_trip_primitive<T: Debug + PartialEq + AsStd140 + AsStd430 + Glsl>(value: T) {
|
||||||
|
let shader_std140 = glsl_shader_for_primitive::<T>("std140");
|
||||||
|
let shader_std430 = glsl_shader_for_primitive::<T>("std430");
|
||||||
|
|
||||||
|
let context = Context::new();
|
||||||
|
context.test_round_trip_std140(&shader_std140, &value);
|
||||||
|
context.test_round_trip_std430(&shader_std430, &value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn glsl_shader_for_struct<T: GlslStruct>(layout: &str) -> String {
|
||||||
|
BASE_SHADER
|
||||||
|
.replace("{struct_name}", T::NAME)
|
||||||
|
.replace("{struct_definition}", &T::glsl_definition())
|
||||||
|
.replace("{layout}", layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn glsl_shader_for_primitive<T: Glsl>(layout: &str) -> String {
|
||||||
|
BASE_SHADER
|
||||||
|
.replace("{struct_name}", T::NAME)
|
||||||
|
.replace("{struct_definition}", "")
|
||||||
|
.replace("{layout}", layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_glsl(glsl: &str) -> String {
|
||||||
|
match compile(glsl) {
|
||||||
|
Ok(shader) => shader,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Bad shader: {}", glsl);
|
||||||
|
panic!("{}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Context<T> {
|
||||||
|
device: wgpu::Device,
|
||||||
|
queue: wgpu::Queue,
|
||||||
|
_phantom: PhantomData<*const T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Context<T>
|
||||||
|
where
|
||||||
|
T: Debug + PartialEq + AsStd140 + AsStd430 + Glsl,
|
||||||
|
{
|
||||||
|
fn new() -> Self {
|
||||||
|
let (device, queue) = setup();
|
||||||
|
Self {
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_round_trip_std140(&self, glsl_shader: &str, value: &T) {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.extend_from_slice(value.as_std140().as_bytes());
|
||||||
|
|
||||||
|
let wgsl_shader = compile_glsl(glsl_shader);
|
||||||
|
let bytes = self.round_trip(&wgsl_shader, &data);
|
||||||
|
|
||||||
|
let std140 = bytemuck::from_bytes::<<T as AsStd140>::Output>(&bytes);
|
||||||
|
let output = T::from_std140(*std140);
|
||||||
|
|
||||||
|
if value != &output {
|
||||||
|
println!(
|
||||||
|
"std140 value did not round-trip through wgpu successfully.\n\
|
||||||
|
Input: {:?}\n\
|
||||||
|
Output: {:?}\n\n\
|
||||||
|
GLSL shader:\n{}\n\n\
|
||||||
|
WGSL shader:\n{}",
|
||||||
|
value, output, glsl_shader, wgsl_shader,
|
||||||
|
);
|
||||||
|
|
||||||
|
panic!("wgpu round-trip failure for {}", T::NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_round_trip_std430(&self, glsl_shader: &str, value: &T) {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.extend_from_slice(value.as_std430().as_bytes());
|
||||||
|
|
||||||
|
let wgsl_shader = compile_glsl(glsl_shader);
|
||||||
|
let bytes = self.round_trip(&wgsl_shader, &data);
|
||||||
|
|
||||||
|
let std430 = bytemuck::from_bytes::<<T as AsStd430>::Output>(&bytes);
|
||||||
|
let output = T::from_std430(*std430);
|
||||||
|
|
||||||
|
if value != &output {
|
||||||
|
println!(
|
||||||
|
"std430 value did not round-trip through wgpu successfully.\n\
|
||||||
|
Input: {:?}\n\
|
||||||
|
Output: {:?}\n\n\
|
||||||
|
GLSL shader:\n{}\n\n\
|
||||||
|
WGSL shader:\n{}",
|
||||||
|
value, output, glsl_shader, wgsl_shader,
|
||||||
|
);
|
||||||
|
|
||||||
|
panic!("wgpu round-trip failure for {}", T::NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn round_trip(&self, shader: &str, data: &[u8]) -> Vec<u8> {
|
||||||
|
let input_buffer = self
|
||||||
|
.device
|
||||||
|
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("Input Buffer"),
|
||||||
|
contents: &data,
|
||||||
|
usage: wgpu::BufferUsages::STORAGE
|
||||||
|
| wgpu::BufferUsages::COPY_DST
|
||||||
|
| wgpu::BufferUsages::COPY_SRC,
|
||||||
|
});
|
||||||
|
|
||||||
|
let output_gpu_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("Output Buffer"),
|
||||||
|
size: data.len() as wgpu::BufferAddress,
|
||||||
|
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let output_cpu_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("Output Buffer"),
|
||||||
|
size: data.len() as wgpu::BufferAddress,
|
||||||
|
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let cs_module = self
|
||||||
|
.device
|
||||||
|
.create_shader_module(&wgpu::ShaderModuleDescriptor {
|
||||||
|
label: None,
|
||||||
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(shader)),
|
||||||
|
});
|
||||||
|
|
||||||
|
let compute_pipeline =
|
||||||
|
self.device
|
||||||
|
.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: None,
|
||||||
|
module: &cs_module,
|
||||||
|
entry_point: "main",
|
||||||
|
});
|
||||||
|
|
||||||
|
let bind_group_layout = compute_pipeline.get_bind_group_layout(0);
|
||||||
|
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: None,
|
||||||
|
layout: &bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: input_buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: output_gpu_buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut encoder = self
|
||||||
|
.device
|
||||||
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut cpass =
|
||||||
|
encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None });
|
||||||
|
cpass.set_pipeline(&compute_pipeline);
|
||||||
|
cpass.set_bind_group(0, &bind_group, &[]);
|
||||||
|
cpass.dispatch(1, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.copy_buffer_to_buffer(
|
||||||
|
&output_gpu_buffer,
|
||||||
|
0,
|
||||||
|
&output_cpu_buffer,
|
||||||
|
0,
|
||||||
|
data.len() as wgpu::BufferAddress,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.queue.submit(std::iter::once(encoder.finish()));
|
||||||
|
|
||||||
|
let output_slice = output_cpu_buffer.slice(..);
|
||||||
|
let output_future = output_slice.map_async(wgpu::MapMode::Read);
|
||||||
|
|
||||||
|
self.device.poll(wgpu::Maintain::Wait);
|
||||||
|
block_on(output_future).unwrap();
|
||||||
|
|
||||||
|
let output = output_slice.get_mapped_range().to_vec();
|
||||||
|
output_cpu_buffer.unmap();
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup() -> (wgpu::Device, wgpu::Queue) {
|
||||||
|
let instance = wgpu::Instance::new(wgpu::Backends::all());
|
||||||
|
let adapter =
|
||||||
|
block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())).unwrap();
|
||||||
|
|
||||||
|
println!("Adapter info: {:#?}", adapter.get_info());
|
||||||
|
|
||||||
|
block_on(adapter.request_device(
|
||||||
|
&wgpu::DeviceDescriptor {
|
||||||
|
label: None,
|
||||||
|
features: wgpu::Features::empty(),
|
||||||
|
limits: wgpu::Limits::downlevel_defaults(),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile(glsl_source: &str) -> anyhow::Result<String> {
|
||||||
|
let mut parser = naga::front::glsl::Parser::default();
|
||||||
|
|
||||||
|
let module = parser
|
||||||
|
.parse(
|
||||||
|
&naga::front::glsl::Options {
|
||||||
|
stage: naga::ShaderStage::Compute,
|
||||||
|
defines: Default::default(),
|
||||||
|
},
|
||||||
|
glsl_source,
|
||||||
|
)
|
||||||
|
.map_err(|err| anyhow::format_err!("{:?}", err))?;
|
||||||
|
|
||||||
|
let info = naga::valid::Validator::new(
|
||||||
|
naga::valid::ValidationFlags::default(),
|
||||||
|
naga::valid::Capabilities::all(),
|
||||||
|
)
|
||||||
|
.validate(&module)?;
|
||||||
|
|
||||||
|
let wgsl = naga::back::wgsl::write_string(&module, &info)?;
|
||||||
|
|
||||||
|
Ok(wgsl)
|
||||||
|
}
|
366
crates/crevice/crevice-tests/src/lib.rs
Normal file
366
crates/crevice/crevice-tests/src/lib.rs
Normal file
|
@ -0,0 +1,366 @@
|
||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu-validation")]
|
||||||
|
mod gpu;
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu-validation")]
|
||||||
|
use gpu::{test_round_trip_primitive, test_round_trip_struct};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "wgpu-validation"))]
|
||||||
|
fn test_round_trip_struct<T>(_value: T) {}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "wgpu-validation"))]
|
||||||
|
fn test_round_trip_primitive<T>(_value: T) {}
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
use crevice::glsl::GlslStruct;
|
||||||
|
use crevice::std140::AsStd140;
|
||||||
|
use crevice::std430::AsStd430;
|
||||||
|
use mint::{ColumnMatrix2, ColumnMatrix3, ColumnMatrix4, Vector2, Vector3, Vector4};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_f32() {
|
||||||
|
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||||
|
struct TwoF32 {
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_std140!((size = 16, align = 16) TwoF32 {
|
||||||
|
x: 0,
|
||||||
|
y: 4,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_std430!((size = 8, align = 4) TwoF32 {
|
||||||
|
x: 0,
|
||||||
|
y: 4,
|
||||||
|
});
|
||||||
|
|
||||||
|
test_round_trip_struct(TwoF32 { x: 5.0, y: 7.0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vec2() {
|
||||||
|
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||||
|
struct UseVec2 {
|
||||||
|
one: Vector2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_std140!((size = 16, align = 16) UseVec2 {
|
||||||
|
one: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
test_round_trip_struct(UseVec2 {
|
||||||
|
one: [1.0, 2.0].into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mat2_bare() {
|
||||||
|
type Mat2 = ColumnMatrix2<f32>;
|
||||||
|
|
||||||
|
assert_std140!((size = 32, align = 16) Mat2 {
|
||||||
|
x: 0,
|
||||||
|
y: 16,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_std430!((size = 16, align = 8) Mat2 {
|
||||||
|
x: 0,
|
||||||
|
y: 8,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Naga doesn't work with std140 mat2 values.
|
||||||
|
// https://github.com/gfx-rs/naga/issues/1400
|
||||||
|
|
||||||
|
// test_round_trip_primitive(Mat2 {
|
||||||
|
// x: [1.0, 2.0].into(),
|
||||||
|
// y: [3.0, 4.0].into(),
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mat3_bare() {
|
||||||
|
type Mat3 = ColumnMatrix3<f32>;
|
||||||
|
|
||||||
|
assert_std140!((size = 48, align = 16) Mat3 {
|
||||||
|
x: 0,
|
||||||
|
y: 16,
|
||||||
|
z: 32,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Naga produces invalid HLSL for mat3 value.
|
||||||
|
// https://github.com/gfx-rs/naga/issues/1466
|
||||||
|
|
||||||
|
// test_round_trip_primitive(Mat3 {
|
||||||
|
// x: [1.0, 2.0, 3.0].into(),
|
||||||
|
// y: [4.0, 5.0, 6.0].into(),
|
||||||
|
// z: [7.0, 8.0, 9.0].into(),
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mat4_bare() {
|
||||||
|
type Mat4 = ColumnMatrix4<f32>;
|
||||||
|
|
||||||
|
assert_std140!((size = 64, align = 16) Mat4 {
|
||||||
|
x: 0,
|
||||||
|
y: 16,
|
||||||
|
z: 32,
|
||||||
|
w: 48,
|
||||||
|
});
|
||||||
|
|
||||||
|
test_round_trip_primitive(Mat4 {
|
||||||
|
x: [1.0, 2.0, 3.0, 4.0].into(),
|
||||||
|
y: [5.0, 6.0, 7.0, 8.0].into(),
|
||||||
|
z: [9.0, 10.0, 11.0, 12.0].into(),
|
||||||
|
w: [13.0, 14.0, 15.0, 16.0].into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mat3() {
|
||||||
|
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||||
|
struct TestData {
|
||||||
|
one: ColumnMatrix3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Naga produces invalid HLSL for mat3 value.
|
||||||
|
// https://github.com/gfx-rs/naga/issues/1466
|
||||||
|
|
||||||
|
// test_round_trip_struct(TestData {
|
||||||
|
// one: [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]].into(),
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dvec4() {
|
||||||
|
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||||
|
struct UsingDVec4 {
|
||||||
|
doubles: Vector4<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_std140!((size = 32, align = 32) UsingDVec4 {
|
||||||
|
doubles: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Naga does not appear to support doubles.
|
||||||
|
// https://github.com/gfx-rs/naga/issues/1272
|
||||||
|
|
||||||
|
// test_round_trip_struct(UsingDVec4 {
|
||||||
|
// doubles: [1.0, 2.0, 3.0, 4.0].into(),
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn four_f64() {
|
||||||
|
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||||
|
struct FourF64 {
|
||||||
|
x: f64,
|
||||||
|
y: f64,
|
||||||
|
z: f64,
|
||||||
|
w: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_std140!((size = 32, align = 16) FourF64 {
|
||||||
|
x: 0,
|
||||||
|
y: 8,
|
||||||
|
z: 16,
|
||||||
|
w: 24,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Naga does not appear to support doubles.
|
||||||
|
// https://github.com/gfx-rs/naga/issues/1272
|
||||||
|
|
||||||
|
// test_round_trip_struct(FourF64 {
|
||||||
|
// x: 5.0,
|
||||||
|
// y: 7.0,
|
||||||
|
// z: 9.0,
|
||||||
|
// w: 11.0,
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_vec3() {
|
||||||
|
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||||
|
struct TwoVec3 {
|
||||||
|
one: Vector3<f32>,
|
||||||
|
two: Vector3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
print_std140!(TwoVec3);
|
||||||
|
print_std430!(TwoVec3);
|
||||||
|
|
||||||
|
assert_std140!((size = 32, align = 16) TwoVec3 {
|
||||||
|
one: 0,
|
||||||
|
two: 16,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_std430!((size = 32, align = 16) TwoVec3 {
|
||||||
|
one: 0,
|
||||||
|
two: 16,
|
||||||
|
});
|
||||||
|
|
||||||
|
test_round_trip_struct(TwoVec3 {
|
||||||
|
one: [1.0, 2.0, 3.0].into(),
|
||||||
|
two: [4.0, 5.0, 6.0].into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_vec4() {
|
||||||
|
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||||
|
struct TwoVec4 {
|
||||||
|
one: Vector4<f32>,
|
||||||
|
two: Vector4<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_std140!((size = 32, align = 16) TwoVec4 {
|
||||||
|
one: 0,
|
||||||
|
two: 16,
|
||||||
|
});
|
||||||
|
|
||||||
|
test_round_trip_struct(TwoVec4 {
|
||||||
|
one: [1.0, 2.0, 3.0, 4.0].into(),
|
||||||
|
two: [5.0, 6.0, 7.0, 8.0].into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vec3_then_f32() {
|
||||||
|
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||||
|
struct Vec3ThenF32 {
|
||||||
|
one: Vector3<f32>,
|
||||||
|
two: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_std140!((size = 16, align = 16) Vec3ThenF32 {
|
||||||
|
one: 0,
|
||||||
|
two: 12,
|
||||||
|
});
|
||||||
|
|
||||||
|
test_round_trip_struct(Vec3ThenF32 {
|
||||||
|
one: [1.0, 2.0, 3.0].into(),
|
||||||
|
two: 4.0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mat3_padding() {
|
||||||
|
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||||
|
struct Mat3Padding {
|
||||||
|
// Three rows of 16 bytes (3x f32 + 4 bytes padding)
|
||||||
|
one: mint::ColumnMatrix3<f32>,
|
||||||
|
two: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_std140!((size = 64, align = 16) Mat3Padding {
|
||||||
|
one: 0,
|
||||||
|
two: 48,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Naga produces invalid HLSL for mat3 value.
|
||||||
|
// https://github.com/gfx-rs/naga/issues/1466
|
||||||
|
|
||||||
|
// test_round_trip_struct(Mat3Padding {
|
||||||
|
// one: [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]].into(),
|
||||||
|
// two: 10.0,
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn padding_after_struct() {
|
||||||
|
#[derive(AsStd140)]
|
||||||
|
struct TwoF32 {
|
||||||
|
x: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(AsStd140)]
|
||||||
|
struct PaddingAfterStruct {
|
||||||
|
base_value: TwoF32,
|
||||||
|
// There should be 8 bytes of padding inserted here.
|
||||||
|
small_field: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_std140!((size = 32, align = 16) PaddingAfterStruct {
|
||||||
|
base_value: 0,
|
||||||
|
small_field: 16,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn proper_offset_calculations_for_differing_member_sizes() {
|
||||||
|
#[derive(AsStd140)]
|
||||||
|
struct Foo {
|
||||||
|
x: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(AsStd140)]
|
||||||
|
struct Bar {
|
||||||
|
first: Foo,
|
||||||
|
second: Foo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(AsStd140)]
|
||||||
|
struct Outer {
|
||||||
|
leading: Bar,
|
||||||
|
trailing: Foo,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset Size Contents
|
||||||
|
// 0 4 Bar.leading.first.x
|
||||||
|
// 4 12 [padding]
|
||||||
|
// 16 4 Bar.leading.second.x
|
||||||
|
// 20 12 [padding]
|
||||||
|
// 32 4 Bar.trailing.x
|
||||||
|
// 36 12 [padding]
|
||||||
|
//
|
||||||
|
// Total size is 48.
|
||||||
|
|
||||||
|
assert_std140!((size = 48, align = 16) Outer {
|
||||||
|
leading: 0,
|
||||||
|
trailing: 32,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn array_strides_small_value() {
|
||||||
|
#[derive(Debug, PartialEq, AsStd140, AsStd430)]
|
||||||
|
struct ArrayOfSmallValues {
|
||||||
|
inner: [f32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_std140!((size = 64, align = 16) ArrayOfSmallValues {
|
||||||
|
inner: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_std430!((size = 16, align = 4) ArrayOfSmallValues {
|
||||||
|
inner: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn array_strides_vec3() {
|
||||||
|
#[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)]
|
||||||
|
struct ArrayOfVector3 {
|
||||||
|
inner: [Vector3<f32>; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_std140!((size = 64, align = 16) ArrayOfVector3 {
|
||||||
|
inner: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_std430!((size = 64, align = 16) ArrayOfVector3 {
|
||||||
|
inner: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
test_round_trip_struct(ArrayOfVector3 {
|
||||||
|
inner: [
|
||||||
|
[0.0, 1.0, 2.0].into(),
|
||||||
|
[3.0, 4.0, 5.0].into(),
|
||||||
|
[6.0, 7.0, 8.0].into(),
|
||||||
|
[9.0, 10.0, 11.0].into(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
143
crates/crevice/crevice-tests/src/util.rs
Normal file
143
crates/crevice/crevice-tests/src/util.rs
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! print_std140 {
|
||||||
|
($type:ty) => {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
<$type as crevice::std140::AsStd140>::Output::debug_metrics()
|
||||||
|
);
|
||||||
|
println!();
|
||||||
|
println!();
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
<$type as crevice::std140::AsStd140>::Output::debug_definitions()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! print_std430 {
|
||||||
|
($type:ty) => {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
<$type as crevice::std430::AsStd430>::Output::debug_metrics()
|
||||||
|
);
|
||||||
|
println!();
|
||||||
|
println!();
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
<$type as crevice::std430::AsStd430>::Output::debug_definitions()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! assert_std140 {
|
||||||
|
((size = $size:literal, align = $align:literal) $struct:ident {
|
||||||
|
$( $field:ident: $offset:literal, )*
|
||||||
|
}) => {{
|
||||||
|
type Target = <$struct as crevice::std140::AsStd140>::Output;
|
||||||
|
|
||||||
|
let mut fail = false;
|
||||||
|
|
||||||
|
let actual_size = std::mem::size_of::<Target>();
|
||||||
|
if actual_size != $size {
|
||||||
|
fail = true;
|
||||||
|
println!(
|
||||||
|
"Invalid size for std140 struct {}\n\
|
||||||
|
Expected: {}\n\
|
||||||
|
Actual: {}\n",
|
||||||
|
stringify!($struct),
|
||||||
|
$size,
|
||||||
|
actual_size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let actual_alignment = <Target as crevice::std140::Std140>::ALIGNMENT;
|
||||||
|
if actual_alignment != $align {
|
||||||
|
fail = true;
|
||||||
|
println!(
|
||||||
|
"Invalid alignment for std140 struct {}\n\
|
||||||
|
Expected: {}\n\
|
||||||
|
Actual: {}\n",
|
||||||
|
stringify!($struct),
|
||||||
|
$align,
|
||||||
|
actual_alignment,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$({
|
||||||
|
let actual_offset = memoffset::offset_of!(Target, $field);
|
||||||
|
if actual_offset != $offset {
|
||||||
|
fail = true;
|
||||||
|
println!(
|
||||||
|
"Invalid offset for field {}\n\
|
||||||
|
Expected: {}\n\
|
||||||
|
Actual: {}\n",
|
||||||
|
stringify!($field),
|
||||||
|
$offset,
|
||||||
|
actual_offset,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})*
|
||||||
|
|
||||||
|
if fail {
|
||||||
|
panic!("Invalid std140 result for {}", stringify!($struct));
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! assert_std430 {
|
||||||
|
((size = $size:literal, align = $align:literal) $struct:ident {
|
||||||
|
$( $field:ident: $offset:literal, )*
|
||||||
|
}) => {{
|
||||||
|
type Target = <$struct as crevice::std430::AsStd430>::Output;
|
||||||
|
|
||||||
|
let mut fail = false;
|
||||||
|
|
||||||
|
let actual_size = std::mem::size_of::<Target>();
|
||||||
|
if actual_size != $size {
|
||||||
|
fail = true;
|
||||||
|
println!(
|
||||||
|
"Invalid size for std430 struct {}\n\
|
||||||
|
Expected: {}\n\
|
||||||
|
Actual: {}\n",
|
||||||
|
stringify!($struct),
|
||||||
|
$size,
|
||||||
|
actual_size,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let actual_alignment = <Target as crevice::std430::Std430>::ALIGNMENT;
|
||||||
|
if actual_alignment != $align {
|
||||||
|
fail = true;
|
||||||
|
println!(
|
||||||
|
"Invalid alignment for std430 struct {}\n\
|
||||||
|
Expected: {}\n\
|
||||||
|
Actual: {}\n",
|
||||||
|
stringify!($struct),
|
||||||
|
$align,
|
||||||
|
actual_alignment,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$({
|
||||||
|
let actual_offset = memoffset::offset_of!(Target, $field);
|
||||||
|
if actual_offset != $offset {
|
||||||
|
fail = true;
|
||||||
|
println!(
|
||||||
|
"Invalid offset for std430 field {}\n\
|
||||||
|
Expected: {}\n\
|
||||||
|
Actual: {}\n",
|
||||||
|
stringify!($field),
|
||||||
|
$offset,
|
||||||
|
actual_offset,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})*
|
||||||
|
|
||||||
|
if fail {
|
||||||
|
panic!("Invalid std430 result for {}", stringify!($struct));
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
93
crates/crevice/src/glsl.rs
Normal file
93
crates/crevice/src/glsl.rs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
//! Defines traits and types for generating GLSL code from Rust definitions.
|
||||||
|
|
||||||
|
pub use crevice_derive::GlslStruct;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
/// Type-level linked list of array dimensions
|
||||||
|
pub struct Dimension<A, const N: usize> {
|
||||||
|
_marker: PhantomData<A>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type-level linked list terminator for array dimensions.
|
||||||
|
pub struct DimensionNil;
|
||||||
|
|
||||||
|
/// Trait for type-level array dimensions. Probably shouldn't be implemented outside this crate.
|
||||||
|
pub unsafe trait DimensionList {
|
||||||
|
/// Write dimensions in square brackets to a string, list tail to list head.
|
||||||
|
fn push_to_string(s: &mut String);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl DimensionList for DimensionNil {
|
||||||
|
fn push_to_string(_: &mut String) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<A: DimensionList, const N: usize> DimensionList for Dimension<A, N> {
|
||||||
|
fn push_to_string(s: &mut String) {
|
||||||
|
use std::fmt::Write;
|
||||||
|
A::push_to_string(s);
|
||||||
|
write!(s, "[{}]", N).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for types that have a GLSL equivalent. Useful for generating GLSL code
|
||||||
|
/// from Rust structs.
|
||||||
|
pub unsafe trait Glsl {
|
||||||
|
/// The name of this type in GLSL, like `vec2` or `mat4`.
|
||||||
|
const NAME: &'static str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for types that can be represented as a struct in GLSL.
|
||||||
|
///
|
||||||
|
/// This trait should not generally be implemented by hand, but can be derived.
|
||||||
|
pub unsafe trait GlslStruct: Glsl {
|
||||||
|
/// The fields contained in this struct.
|
||||||
|
fn enumerate_fields(s: &mut String);
|
||||||
|
|
||||||
|
/// Generates GLSL code that represents this struct and its fields.
|
||||||
|
fn glsl_definition() -> String {
|
||||||
|
let mut output = String::new();
|
||||||
|
output.push_str("struct ");
|
||||||
|
output.push_str(Self::NAME);
|
||||||
|
output.push_str(" {\n");
|
||||||
|
|
||||||
|
Self::enumerate_fields(&mut output);
|
||||||
|
|
||||||
|
output.push_str("};");
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for types that are expressible as a GLSL type with (possibly zero) array dimensions.
|
||||||
|
pub unsafe trait GlslArray {
|
||||||
|
/// Base type name.
|
||||||
|
const NAME: &'static str;
|
||||||
|
/// Type-level linked list of array dimensions, ordered outer to inner.
|
||||||
|
type ArraySize: DimensionList;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: Glsl> GlslArray for T {
|
||||||
|
const NAME: &'static str = <T as Glsl>::NAME;
|
||||||
|
type ArraySize = DimensionNil;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Glsl for f32 {
|
||||||
|
const NAME: &'static str = "float";
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Glsl for f64 {
|
||||||
|
const NAME: &'static str = "double";
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Glsl for i32 {
|
||||||
|
const NAME: &'static str = "int";
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Glsl for u32 {
|
||||||
|
const NAME: &'static str = "uint";
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: GlslArray, const N: usize> GlslArray for [T; N] {
|
||||||
|
const NAME: &'static str = T::NAME;
|
||||||
|
|
||||||
|
type ArraySize = Dimension<T::ArraySize, N>;
|
||||||
|
}
|
10
crates/crevice/src/imp.rs
Normal file
10
crates/crevice/src/imp.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
mod imp_mint;
|
||||||
|
|
||||||
|
#[cfg(feature = "cgmath")]
|
||||||
|
mod imp_cgmath;
|
||||||
|
|
||||||
|
#[cfg(feature = "glam")]
|
||||||
|
mod imp_glam;
|
||||||
|
|
||||||
|
#[cfg(feature = "nalgebra")]
|
||||||
|
mod imp_nalgebra;
|
30
crates/crevice/src/imp/imp_cgmath.rs
Normal file
30
crates/crevice/src/imp/imp_cgmath.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
easy_impl! {
|
||||||
|
Vec2 cgmath::Vector2<f32> { x, y },
|
||||||
|
Vec3 cgmath::Vector3<f32> { x, y, z },
|
||||||
|
Vec4 cgmath::Vector4<f32> { x, y, z, w },
|
||||||
|
|
||||||
|
IVec2 cgmath::Vector2<i32> { x, y },
|
||||||
|
IVec3 cgmath::Vector3<i32> { x, y, z },
|
||||||
|
IVec4 cgmath::Vector4<i32> { x, y, z, w },
|
||||||
|
|
||||||
|
UVec2 cgmath::Vector2<u32> { x, y },
|
||||||
|
UVec3 cgmath::Vector3<u32> { x, y, z },
|
||||||
|
UVec4 cgmath::Vector4<u32> { x, y, z, w },
|
||||||
|
|
||||||
|
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
|
||||||
|
// BVec2 cgmath::Vector2<bool> { x, y },
|
||||||
|
// BVec3 cgmath::Vector3<bool> { x, y, z },
|
||||||
|
// BVec4 cgmath::Vector4<bool> { x, y, z, w },
|
||||||
|
|
||||||
|
DVec2 cgmath::Vector2<f64> { x, y },
|
||||||
|
DVec3 cgmath::Vector3<f64> { x, y, z },
|
||||||
|
DVec4 cgmath::Vector4<f64> { x, y, z, w },
|
||||||
|
|
||||||
|
Mat2 cgmath::Matrix2<f32> { x, y },
|
||||||
|
Mat3 cgmath::Matrix3<f32> { x, y, z },
|
||||||
|
Mat4 cgmath::Matrix4<f32> { x, y, z, w },
|
||||||
|
|
||||||
|
DMat2 cgmath::Matrix2<f64> { x, y },
|
||||||
|
DMat3 cgmath::Matrix3<f64> { x, y, z },
|
||||||
|
DMat4 cgmath::Matrix4<f64> { x, y, z, w },
|
||||||
|
}
|
24
crates/crevice/src/imp/imp_glam.rs
Normal file
24
crates/crevice/src/imp/imp_glam.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
minty_impl! {
|
||||||
|
mint::Vector2<f32> => glam::Vec2,
|
||||||
|
mint::Vector3<f32> => glam::Vec3,
|
||||||
|
mint::Vector4<f32> => glam::Vec4,
|
||||||
|
mint::Vector2<i32> => glam::IVec2,
|
||||||
|
mint::Vector3<i32> => glam::IVec3,
|
||||||
|
mint::Vector4<i32> => glam::IVec4,
|
||||||
|
mint::Vector2<u32> => glam::UVec2,
|
||||||
|
mint::Vector3<u32> => glam::UVec3,
|
||||||
|
mint::Vector4<u32> => glam::UVec4,
|
||||||
|
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
|
||||||
|
// mint::Vector2<bool> => glam::BVec2,
|
||||||
|
// mint::Vector3<bool> => glam::BVec3,
|
||||||
|
// mint::Vector4<bool> => glam::BVec4,
|
||||||
|
mint::Vector2<f64> => glam::DVec2,
|
||||||
|
mint::Vector3<f64> => glam::DVec3,
|
||||||
|
mint::Vector4<f64> => glam::DVec4,
|
||||||
|
mint::ColumnMatrix2<f32> => glam::Mat2,
|
||||||
|
mint::ColumnMatrix3<f32> => glam::Mat3,
|
||||||
|
mint::ColumnMatrix4<f32> => glam::Mat4,
|
||||||
|
mint::ColumnMatrix2<f64> => glam::DMat2,
|
||||||
|
mint::ColumnMatrix3<f64> => glam::DMat3,
|
||||||
|
mint::ColumnMatrix4<f64> => glam::DMat4,
|
||||||
|
}
|
30
crates/crevice/src/imp/imp_mint.rs
Normal file
30
crates/crevice/src/imp/imp_mint.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
easy_impl! {
|
||||||
|
Vec2 mint::Vector2<f32> { x, y },
|
||||||
|
Vec3 mint::Vector3<f32> { x, y, z },
|
||||||
|
Vec4 mint::Vector4<f32> { x, y, z, w },
|
||||||
|
|
||||||
|
IVec2 mint::Vector2<i32> { x, y },
|
||||||
|
IVec3 mint::Vector3<i32> { x, y, z },
|
||||||
|
IVec4 mint::Vector4<i32> { x, y, z, w },
|
||||||
|
|
||||||
|
UVec2 mint::Vector2<u32> { x, y },
|
||||||
|
UVec3 mint::Vector3<u32> { x, y, z },
|
||||||
|
UVec4 mint::Vector4<u32> { x, y, z, w },
|
||||||
|
|
||||||
|
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
|
||||||
|
// BVec2 mint::Vector2<bool> { x, y },
|
||||||
|
// BVec3 mint::Vector3<bool> { x, y, z },
|
||||||
|
// BVec4 mint::Vector4<bool> { x, y, z, w },
|
||||||
|
|
||||||
|
DVec2 mint::Vector2<f64> { x, y },
|
||||||
|
DVec3 mint::Vector3<f64> { x, y, z },
|
||||||
|
DVec4 mint::Vector4<f64> { x, y, z, w },
|
||||||
|
|
||||||
|
Mat2 mint::ColumnMatrix2<f32> { x, y },
|
||||||
|
Mat3 mint::ColumnMatrix3<f32> { x, y, z },
|
||||||
|
Mat4 mint::ColumnMatrix4<f32> { x, y, z, w },
|
||||||
|
|
||||||
|
DMat2 mint::ColumnMatrix2<f64> { x, y },
|
||||||
|
DMat3 mint::ColumnMatrix3<f64> { x, y, z },
|
||||||
|
DMat4 mint::ColumnMatrix4<f64> { x, y, z, w },
|
||||||
|
}
|
24
crates/crevice/src/imp/imp_nalgebra.rs
Normal file
24
crates/crevice/src/imp/imp_nalgebra.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
minty_impl! {
|
||||||
|
mint::Vector2<f32> => nalgebra::Vector2<f32>,
|
||||||
|
mint::Vector3<f32> => nalgebra::Vector3<f32>,
|
||||||
|
mint::Vector4<f32> => nalgebra::Vector4<f32>,
|
||||||
|
mint::Vector2<i32> => nalgebra::Vector2<i32>,
|
||||||
|
mint::Vector3<i32> => nalgebra::Vector3<i32>,
|
||||||
|
mint::Vector4<i32> => nalgebra::Vector4<i32>,
|
||||||
|
mint::Vector2<u32> => nalgebra::Vector2<u32>,
|
||||||
|
mint::Vector3<u32> => nalgebra::Vector3<u32>,
|
||||||
|
mint::Vector4<u32> => nalgebra::Vector4<u32>,
|
||||||
|
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
|
||||||
|
// mint::Vector2<bool> => nalgebra::Vector2<bool>,
|
||||||
|
// mint::Vector3<bool> => nalgebra::Vector3<bool>,
|
||||||
|
// mint::Vector4<bool> => nalgebra::Vector4<bool>,
|
||||||
|
mint::Vector2<f64> => nalgebra::Vector2<f64>,
|
||||||
|
mint::Vector3<f64> => nalgebra::Vector3<f64>,
|
||||||
|
mint::Vector4<f64> => nalgebra::Vector4<f64>,
|
||||||
|
mint::ColumnMatrix2<f32> => nalgebra::Matrix2<f32>,
|
||||||
|
mint::ColumnMatrix3<f32> => nalgebra::Matrix3<f32>,
|
||||||
|
mint::ColumnMatrix4<f32> => nalgebra::Matrix4<f32>,
|
||||||
|
mint::ColumnMatrix2<f64> => nalgebra::Matrix2<f64>,
|
||||||
|
mint::ColumnMatrix3<f64> => nalgebra::Matrix3<f64>,
|
||||||
|
mint::ColumnMatrix4<f64> => nalgebra::Matrix4<f64>,
|
||||||
|
}
|
40
crates/crevice/src/internal.rs
Normal file
40
crates/crevice/src/internal.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
//! This module is internal to crevice but used by its derive macro. No
|
||||||
|
//! guarantees are made about its contents.
|
||||||
|
|
||||||
|
pub use bytemuck;
|
||||||
|
|
||||||
|
/// Gives the number of bytes needed to make `offset` be aligned to `alignment`.
|
||||||
|
pub const fn align_offset(offset: usize, alignment: usize) -> usize {
|
||||||
|
if alignment == 0 || offset % alignment == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
alignment - offset % alignment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Max of two `usize`. Implemented because the `max` method from `Ord` cannot
|
||||||
|
/// be used in const fns.
|
||||||
|
pub const fn max(a: usize, b: usize) -> usize {
|
||||||
|
if a > b {
|
||||||
|
a
|
||||||
|
} else {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Max of an array of `usize`. This function's implementation is funky because
|
||||||
|
/// we have no for loops!
|
||||||
|
pub const fn max_arr<const N: usize>(input: [usize; N]) -> usize {
|
||||||
|
let mut max = 0;
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
while i < N {
|
||||||
|
if input[i] > max {
|
||||||
|
max = input[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
max
|
||||||
|
}
|
172
crates/crevice/src/lib.rs
Normal file
172
crates/crevice/src/lib.rs
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
#![allow(
|
||||||
|
clippy::new_without_default,
|
||||||
|
clippy::needless_update,
|
||||||
|
clippy::len_without_is_empty,
|
||||||
|
clippy::needless_range_loop
|
||||||
|
)]
|
||||||
|
/*!
|
||||||
|
[![GitHub CI Status](https://github.com/LPGhatguy/crevice/workflows/CI/badge.svg)](https://github.com/LPGhatguy/crevice/actions)
|
||||||
|
[![crevice on crates.io](https://img.shields.io/crates/v/crevice.svg)](https://crates.io/crates/crevice)
|
||||||
|
[![crevice docs](https://img.shields.io/badge/docs-docs.rs-orange.svg)](https://docs.rs/crevice)
|
||||||
|
|
||||||
|
Crevice creates GLSL-compatible versions of types through the power of derive
|
||||||
|
macros. Generated structures provide an [`as_bytes`][std140::Std140::as_bytes]
|
||||||
|
method to allow safely packing data into buffers for uploading.
|
||||||
|
|
||||||
|
Generated structs also implement [`bytemuck::Zeroable`] and
|
||||||
|
[`bytemuck::Pod`] for use with other libraries.
|
||||||
|
|
||||||
|
Crevice is similar to [`glsl-layout`][glsl-layout], but supports types from many
|
||||||
|
math crates, can generate GLSL source from structs, and explicitly initializes
|
||||||
|
padding to remove one source of undefined behavior.
|
||||||
|
|
||||||
|
Crevice has support for many Rust math libraries via feature flags, and most
|
||||||
|
other math libraries by use of the mint crate. Crevice currently supports:
|
||||||
|
|
||||||
|
* mint 0.5, enabled by default
|
||||||
|
* cgmath 0.18, using the `cgmath` feature
|
||||||
|
* nalgebra 0.29, using the `nalgebra` feature
|
||||||
|
* glam 0.19, using the `glam` feature
|
||||||
|
|
||||||
|
PRs are welcome to add or update math libraries to Crevice.
|
||||||
|
|
||||||
|
If your math library is not supported, it's possible to define structs using the
|
||||||
|
types from mint and convert your math library's types into mint types. This is
|
||||||
|
supported by most Rust math libraries.
|
||||||
|
|
||||||
|
Your math library may require you to turn on a feature flag to get mint support.
|
||||||
|
For example, cgmath requires the "mint" feature to be enabled to allow
|
||||||
|
conversions to and from mint types.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Single Value
|
||||||
|
|
||||||
|
Uploading many types can be done by deriving [`AsStd140`][std140::AsStd140] and
|
||||||
|
using [`as_std140`][std140::AsStd140::as_std140] and
|
||||||
|
[`as_bytes`][std140::Std140::as_bytes] to turn the result into bytes.
|
||||||
|
|
||||||
|
```glsl
|
||||||
|
uniform MAIN {
|
||||||
|
mat3 orientation;
|
||||||
|
vec3 position;
|
||||||
|
float scale;
|
||||||
|
} main;
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use crevice::std140::{AsStd140, Std140};
|
||||||
|
|
||||||
|
#[derive(AsStd140)]
|
||||||
|
struct MainUniform {
|
||||||
|
orientation: mint::ColumnMatrix3<f32>,
|
||||||
|
position: mint::Vector3<f32>,
|
||||||
|
scale: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = MainUniform {
|
||||||
|
orientation: [
|
||||||
|
[1.0, 0.0, 0.0],
|
||||||
|
[0.0, 1.0, 0.0],
|
||||||
|
[0.0, 0.0, 1.0],
|
||||||
|
].into(),
|
||||||
|
position: [1.0, 2.0, 3.0].into(),
|
||||||
|
scale: 4.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let value_std140 = value.as_std140();
|
||||||
|
|
||||||
|
# fn upload_data_to_gpu(_value: &[u8]) {}
|
||||||
|
upload_data_to_gpu(value_std140.as_bytes());
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sequential Types
|
||||||
|
|
||||||
|
More complicated data can be uploaded using the std140
|
||||||
|
[`Writer`][std140::Writer] type.
|
||||||
|
|
||||||
|
```glsl
|
||||||
|
struct PointLight {
|
||||||
|
vec3 position;
|
||||||
|
vec3 color;
|
||||||
|
float brightness;
|
||||||
|
};
|
||||||
|
|
||||||
|
buffer POINT_LIGHTS {
|
||||||
|
uint len;
|
||||||
|
PointLight[] lights;
|
||||||
|
} point_lights;
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use crevice::std140::{self, AsStd140};
|
||||||
|
|
||||||
|
#[derive(AsStd140)]
|
||||||
|
struct PointLight {
|
||||||
|
position: mint::Vector3<f32>,
|
||||||
|
color: mint::Vector3<f32>,
|
||||||
|
brightness: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
let lights = vec![
|
||||||
|
PointLight {
|
||||||
|
position: [0.0, 1.0, 0.0].into(),
|
||||||
|
color: [1.0, 0.0, 0.0].into(),
|
||||||
|
brightness: 0.6,
|
||||||
|
},
|
||||||
|
PointLight {
|
||||||
|
position: [0.0, 4.0, 3.0].into(),
|
||||||
|
color: [1.0, 1.0, 1.0].into(),
|
||||||
|
brightness: 1.0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
# fn map_gpu_buffer_for_write() -> &'static mut [u8] {
|
||||||
|
# Box::leak(vec![0; 1024].into_boxed_slice())
|
||||||
|
# }
|
||||||
|
let target_buffer = map_gpu_buffer_for_write();
|
||||||
|
let mut writer = std140::Writer::new(target_buffer);
|
||||||
|
|
||||||
|
let light_count = lights.len() as u32;
|
||||||
|
writer.write(&light_count)?;
|
||||||
|
|
||||||
|
// Crevice will automatically insert the required padding to align the
|
||||||
|
// PointLight structure correctly. In this case, there will be 12 bytes of
|
||||||
|
// padding between the length field and the light list.
|
||||||
|
|
||||||
|
writer.write(lights.as_slice())?;
|
||||||
|
|
||||||
|
# fn unmap_gpu_buffer() {}
|
||||||
|
unmap_gpu_buffer();
|
||||||
|
|
||||||
|
# Ok::<(), std::io::Error>(())
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* `std` (default): Enables [`std::io::Write`]-based structs.
|
||||||
|
* `cgmath`: Enables support for types from cgmath.
|
||||||
|
* `nalgebra`: Enables support for types from nalgebra.
|
||||||
|
* `glam`: Enables support for types from glam.
|
||||||
|
|
||||||
|
## Minimum Supported Rust Version (MSRV)
|
||||||
|
|
||||||
|
Crevice supports Rust 1.52.1 and newer due to use of new `const fn` features.
|
||||||
|
|
||||||
|
[glsl-layout]: https://github.com/rustgd/glsl-layout
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
pub mod glsl;
|
||||||
|
pub mod std140;
|
||||||
|
pub mod std430;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub mod internal;
|
||||||
|
|
||||||
|
mod imp;
|
18
crates/crevice/src/std140.rs
Normal file
18
crates/crevice/src/std140.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
//! Defines traits and types for working with data adhering to GLSL's `std140`
|
||||||
|
//! layout specification.
|
||||||
|
|
||||||
|
mod dynamic_uniform;
|
||||||
|
mod primitives;
|
||||||
|
mod sizer;
|
||||||
|
mod traits;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
mod writer;
|
||||||
|
|
||||||
|
pub use self::dynamic_uniform::*;
|
||||||
|
pub use self::primitives::*;
|
||||||
|
pub use self::sizer::*;
|
||||||
|
pub use self::traits::*;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub use self::writer::*;
|
||||||
|
|
||||||
|
pub use crevice_derive::AsStd140;
|
68
crates/crevice/src/std140/dynamic_uniform.rs
Normal file
68
crates/crevice/src/std140/dynamic_uniform.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use crate::internal::{align_offset, max};
|
||||||
|
use crate::std140::{AsStd140, Std140};
|
||||||
|
|
||||||
|
/// Wrapper type that aligns the inner type to at least 256 bytes.
|
||||||
|
///
|
||||||
|
/// This type is useful for ensuring correct alignment when creating dynamic
|
||||||
|
/// uniform buffers in APIs like WebGPU.
|
||||||
|
pub struct DynamicUniform<T>(pub T);
|
||||||
|
|
||||||
|
impl<T: AsStd140> AsStd140 for DynamicUniform<T> {
|
||||||
|
type Output = DynamicUniformStd140<<T as AsStd140>::Output>;
|
||||||
|
|
||||||
|
fn as_std140(&self) -> Self::Output {
|
||||||
|
DynamicUniformStd140(self.0.as_std140())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_std140(value: Self::Output) -> Self {
|
||||||
|
DynamicUniform(<T as AsStd140>::from_std140(value.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// std140 variant of [`DynamicUniform`].
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct DynamicUniformStd140<T>(T);
|
||||||
|
|
||||||
|
unsafe impl<T: Std140> Std140 for DynamicUniformStd140<T> {
|
||||||
|
const ALIGNMENT: usize = max(256, T::ALIGNMENT);
|
||||||
|
#[cfg(const_evaluatable_checked)]
|
||||||
|
type Padded = crate::std140::Std140Padded<
|
||||||
|
Self,
|
||||||
|
{ align_offset(core::mem::size_of::<T>(), max(256, T::ALIGNMENT)) },
|
||||||
|
>;
|
||||||
|
#[cfg(not(const_evaluatable_checked))]
|
||||||
|
type Padded = crate::std140::InvalidPadded;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: Zeroable> Zeroable for DynamicUniformStd140<T> {}
|
||||||
|
unsafe impl<T: Pod> Pod for DynamicUniformStd140<T> {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use crate::std140::{self, WriteStd140};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn size_is_unchanged() {
|
||||||
|
let dynamic_f32 = DynamicUniform(0.0f32);
|
||||||
|
|
||||||
|
assert_eq!(dynamic_f32.std140_size(), 0.0f32.std140_size());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alignment_applies() {
|
||||||
|
let mut output = Vec::new();
|
||||||
|
let mut writer = std140::Writer::new(&mut output);
|
||||||
|
|
||||||
|
writer.write(&DynamicUniform(0.0f32)).unwrap();
|
||||||
|
assert_eq!(writer.len(), 4);
|
||||||
|
|
||||||
|
writer.write(&DynamicUniform(1.0f32)).unwrap();
|
||||||
|
assert_eq!(writer.len(), 260);
|
||||||
|
}
|
||||||
|
}
|
175
crates/crevice/src/std140/primitives.rs
Normal file
175
crates/crevice/src/std140/primitives.rs
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
|
use crate::glsl::Glsl;
|
||||||
|
use crate::std140::{Std140, Std140Padded};
|
||||||
|
|
||||||
|
use crate::internal::{align_offset, max};
|
||||||
|
use core::mem::size_of;
|
||||||
|
|
||||||
|
unsafe impl Std140 for f32 {
|
||||||
|
const ALIGNMENT: usize = 4;
|
||||||
|
type Padded = Std140Padded<Self, 12>;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Std140 for f64 {
|
||||||
|
const ALIGNMENT: usize = 8;
|
||||||
|
type Padded = Std140Padded<Self, 8>;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Std140 for i32 {
|
||||||
|
const ALIGNMENT: usize = 4;
|
||||||
|
type Padded = Std140Padded<Self, 12>;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Std140 for u32 {
|
||||||
|
const ALIGNMENT: usize = 4;
|
||||||
|
type Padded = Std140Padded<Self, 12>;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! vectors {
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
#[$doc:meta] align($align:literal) $glsl_name:ident $name:ident <$prim:ident> ($($field:ident),+)
|
||||||
|
)+
|
||||||
|
) => {
|
||||||
|
$(
|
||||||
|
#[$doc]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct $name {
|
||||||
|
$(pub $field: $prim,)+
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Zeroable for $name {}
|
||||||
|
unsafe impl Pod for $name {}
|
||||||
|
|
||||||
|
unsafe impl Std140 for $name {
|
||||||
|
const ALIGNMENT: usize = $align;
|
||||||
|
type Padded = Std140Padded<Self, {align_offset(size_of::<$name>(), max(16, $align))}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Glsl for $name {
|
||||||
|
const NAME: &'static str = stringify!($glsl_name);
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
vectors! {
|
||||||
|
#[doc = "Corresponds to a GLSL `vec2` in std140 layout."] align(8) vec2 Vec2<f32>(x, y)
|
||||||
|
#[doc = "Corresponds to a GLSL `vec3` in std140 layout."] align(16) vec3 Vec3<f32>(x, y, z)
|
||||||
|
#[doc = "Corresponds to a GLSL `vec4` in std140 layout."] align(16) vec4 Vec4<f32>(x, y, z, w)
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `ivec2` in std140 layout."] align(8) ivec2 IVec2<i32>(x, y)
|
||||||
|
#[doc = "Corresponds to a GLSL `ivec3` in std140 layout."] align(16) ivec3 IVec3<i32>(x, y, z)
|
||||||
|
#[doc = "Corresponds to a GLSL `ivec4` in std140 layout."] align(16) ivec4 IVec4<i32>(x, y, z, w)
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `uvec2` in std140 layout."] align(8) uvec2 UVec2<u32>(x, y)
|
||||||
|
#[doc = "Corresponds to a GLSL `uvec3` in std140 layout."] align(16) uvec3 UVec3<u32>(x, y, z)
|
||||||
|
#[doc = "Corresponds to a GLSL `uvec4` in std140 layout."] align(16) uvec4 UVec4<u32>(x, y, z, w)
|
||||||
|
|
||||||
|
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
|
||||||
|
|
||||||
|
// #[doc = "Corresponds to a GLSL `bvec2` in std140 layout."] align(8) bvec2 BVec2<bool>(x, y)
|
||||||
|
// #[doc = "Corresponds to a GLSL `bvec3` in std140 layout."] align(16) bvec3 BVec3<bool>(x, y, z)
|
||||||
|
// #[doc = "Corresponds to a GLSL `bvec4` in std140 layout."] align(16) bvec4 BVec4<bool>(x, y, z, w)
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `dvec2` in std140 layout."] align(16) dvec2 DVec2<f64>(x, y)
|
||||||
|
#[doc = "Corresponds to a GLSL `dvec3` in std140 layout."] align(32) dvec3 DVec3<f64>(x, y, z)
|
||||||
|
#[doc = "Corresponds to a GLSL `dvec4` in std140 layout."] align(32) dvec4 DVec4<f64>(x, y, z, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! matrices {
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
#[$doc:meta]
|
||||||
|
align($align:literal)
|
||||||
|
$glsl_name:ident $name:ident {
|
||||||
|
$($field:ident: $field_ty:ty,)+
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
) => {
|
||||||
|
$(
|
||||||
|
#[$doc]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct $name {
|
||||||
|
$(pub $field: $field_ty,)+
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Zeroable for $name {}
|
||||||
|
unsafe impl Pod for $name {}
|
||||||
|
|
||||||
|
unsafe impl Std140 for $name {
|
||||||
|
const ALIGNMENT: usize = $align;
|
||||||
|
/// Matrices are technically arrays of primitives, and as such require pad at end.
|
||||||
|
const PAD_AT_END: bool = true;
|
||||||
|
type Padded = Std140Padded<Self, {align_offset(size_of::<$name>(), max(16, $align))}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Glsl for $name {
|
||||||
|
const NAME: &'static str = stringify!($glsl_name);
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
matrices! {
|
||||||
|
#[doc = "Corresponds to a GLSL `mat2` in std140 layout."]
|
||||||
|
align(16)
|
||||||
|
mat2 Mat2 {
|
||||||
|
x: Vec2,
|
||||||
|
_pad_x: [f32; 2],
|
||||||
|
y: Vec2,
|
||||||
|
_pad_y: [f32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `mat3` in std140 layout."]
|
||||||
|
align(16)
|
||||||
|
mat3 Mat3 {
|
||||||
|
x: Vec3,
|
||||||
|
_pad_x: f32,
|
||||||
|
y: Vec3,
|
||||||
|
_pad_y: f32,
|
||||||
|
z: Vec3,
|
||||||
|
_pad_z: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `mat4` in std140 layout."]
|
||||||
|
align(16)
|
||||||
|
mat4 Mat4 {
|
||||||
|
x: Vec4,
|
||||||
|
y: Vec4,
|
||||||
|
z: Vec4,
|
||||||
|
w: Vec4,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `dmat2` in std140 layout."]
|
||||||
|
align(16)
|
||||||
|
dmat2 DMat2 {
|
||||||
|
x: DVec2,
|
||||||
|
y: DVec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `dmat3` in std140 layout."]
|
||||||
|
align(32)
|
||||||
|
dmat3 DMat3 {
|
||||||
|
x: DVec3,
|
||||||
|
_pad_x: f64,
|
||||||
|
y: DVec3,
|
||||||
|
_pad_y: f64,
|
||||||
|
z: DVec3,
|
||||||
|
_pad_z: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `dmat3` in std140 layout."]
|
||||||
|
align(32)
|
||||||
|
dmat4 DMat4 {
|
||||||
|
x: DVec4,
|
||||||
|
y: DVec4,
|
||||||
|
z: DVec4,
|
||||||
|
w: DVec4,
|
||||||
|
}
|
||||||
|
}
|
81
crates/crevice/src/std140/sizer.rs
Normal file
81
crates/crevice/src/std140/sizer.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
use core::mem::size_of;
|
||||||
|
|
||||||
|
use crate::internal::align_offset;
|
||||||
|
use crate::std140::{AsStd140, Std140};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Type that computes the buffer size needed by a series of `std140` types laid
|
||||||
|
out.
|
||||||
|
|
||||||
|
This type works well well when paired with `Writer`, precomputing a buffer's
|
||||||
|
size to alleviate the need to dynamically re-allocate buffers.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```glsl
|
||||||
|
struct Frob {
|
||||||
|
vec3 size;
|
||||||
|
float frobiness;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer FROBS {
|
||||||
|
uint len;
|
||||||
|
Frob[] frobs;
|
||||||
|
} frobs;
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
use crevice::std140::{self, AsStd140};
|
||||||
|
|
||||||
|
#[derive(AsStd140)]
|
||||||
|
struct Frob {
|
||||||
|
size: mint::Vector3<f32>,
|
||||||
|
frobiness: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Many APIs require that buffers contain at least enough space for all
|
||||||
|
// fixed-size bindiongs to a buffer as well as one element of any arrays, if
|
||||||
|
// there are any.
|
||||||
|
let mut sizer = std140::Sizer::new();
|
||||||
|
sizer.add::<u32>();
|
||||||
|
sizer.add::<Frob>();
|
||||||
|
|
||||||
|
# fn create_buffer_with_size(size: usize) {}
|
||||||
|
let buffer = create_buffer_with_size(sizer.len());
|
||||||
|
# assert_eq!(sizer.len(), 32);
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
pub struct Sizer {
|
||||||
|
offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sizer {
|
||||||
|
/// Create a new `Sizer`.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { offset: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a type's necessary padding and size to the `Sizer`. Returns the
|
||||||
|
/// offset into the buffer where that type would be written.
|
||||||
|
pub fn add<T>(&mut self) -> usize
|
||||||
|
where
|
||||||
|
T: AsStd140,
|
||||||
|
{
|
||||||
|
let size = size_of::<<T as AsStd140>::Output>();
|
||||||
|
let alignment = <T as AsStd140>::Output::ALIGNMENT;
|
||||||
|
let padding = align_offset(self.offset, alignment);
|
||||||
|
|
||||||
|
self.offset += padding;
|
||||||
|
let write_here = self.offset;
|
||||||
|
|
||||||
|
self.offset += size;
|
||||||
|
|
||||||
|
write_here
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of bytes required to contain all the types added to
|
||||||
|
/// the `Sizer`.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.offset
|
||||||
|
}
|
||||||
|
}
|
284
crates/crevice/src/std140/traits.rs
Normal file
284
crates/crevice/src/std140/traits.rs
Normal file
|
@ -0,0 +1,284 @@
|
||||||
|
use core::mem::{size_of, MaybeUninit};
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
use bytemuck::{bytes_of, Pod, Zeroable};
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use crate::std140::Writer;
|
||||||
|
|
||||||
|
/// Trait implemented for all `std140` primitives. Generally should not be
|
||||||
|
/// implemented outside this crate.
|
||||||
|
pub unsafe trait Std140: Copy + Zeroable + Pod {
|
||||||
|
/// The required alignment of the type. Must be a power of two.
|
||||||
|
///
|
||||||
|
/// This is distinct from the value returned by `std::mem::align_of` because
|
||||||
|
/// `AsStd140` structs do not use Rust's alignment. This enables them to
|
||||||
|
/// control and zero their padding bytes, making converting them to and from
|
||||||
|
/// slices safe.
|
||||||
|
const ALIGNMENT: usize;
|
||||||
|
|
||||||
|
/// Whether this type requires a padding at the end (ie, is a struct or an array
|
||||||
|
/// of primitives).
|
||||||
|
/// See <https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf#page=159>
|
||||||
|
/// (rule 4 and 9)
|
||||||
|
const PAD_AT_END: bool = false;
|
||||||
|
/// Padded type (Std140Padded specialization)
|
||||||
|
/// The usual implementation is
|
||||||
|
/// type Padded = Std140Padded<Self, {align_offset(size_of::<Self>(), max(16, ALIGNMENT))}>;
|
||||||
|
type Padded: Std140Convertible<Self>;
|
||||||
|
|
||||||
|
/// Casts the type to a byte array. Implementors should not override this
|
||||||
|
/// method.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// This is always safe due to the requirements of [`bytemuck::Pod`] being a
|
||||||
|
/// prerequisite for this trait.
|
||||||
|
fn as_bytes(&self) -> &[u8] {
|
||||||
|
bytes_of(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait specifically for Std140::Padded, implements conversions between padded type and base type.
|
||||||
|
pub trait Std140Convertible<T: Std140>: Copy {
|
||||||
|
/// Convert from self to Std140
|
||||||
|
fn into_std140(self) -> T;
|
||||||
|
/// Convert from Std140 to self
|
||||||
|
fn from_std140(_: T) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Std140> Std140Convertible<T> for T {
|
||||||
|
fn into_std140(self) -> T {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn from_std140(also_self: T) -> Self {
|
||||||
|
also_self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unfortunately, we cannot easily derive padded representation for generic Std140 types.
|
||||||
|
/// For now, we'll just use this empty enum with no values.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum InvalidPadded {}
|
||||||
|
impl<T: Std140> Std140Convertible<T> for InvalidPadded {
|
||||||
|
fn into_std140(self) -> T {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
fn from_std140(_: T) -> Self {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
Trait implemented for all types that can be turned into `std140` values.
|
||||||
|
*
|
||||||
|
This trait can often be `#[derive]`'d instead of manually implementing it. Any
|
||||||
|
struct which contains only fields that also implement `AsStd140` can derive
|
||||||
|
`AsStd140`.
|
||||||
|
|
||||||
|
Types from the mint crate implement `AsStd140`, making them convenient for use
|
||||||
|
in uniform types. Most Rust math crates, like cgmath, nalgebra, and
|
||||||
|
ultraviolet support mint.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```glsl
|
||||||
|
uniform CAMERA {
|
||||||
|
mat4 view;
|
||||||
|
mat4 projection;
|
||||||
|
} camera;
|
||||||
|
```
|
||||||
|
|
||||||
|
```no_run
|
||||||
|
use crevice::std140::{AsStd140, Std140};
|
||||||
|
|
||||||
|
#[derive(AsStd140)]
|
||||||
|
struct CameraUniform {
|
||||||
|
view: mint::ColumnMatrix4<f32>,
|
||||||
|
projection: mint::ColumnMatrix4<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let view: mint::ColumnMatrix4<f32> = todo!("your math code here");
|
||||||
|
let projection: mint::ColumnMatrix4<f32> = todo!("your math code here");
|
||||||
|
|
||||||
|
let camera = CameraUniform {
|
||||||
|
view,
|
||||||
|
projection,
|
||||||
|
};
|
||||||
|
|
||||||
|
# fn write_to_gpu_buffer(bytes: &[u8]) {}
|
||||||
|
let camera_std140 = camera.as_std140();
|
||||||
|
write_to_gpu_buffer(camera_std140.as_bytes());
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
pub trait AsStd140 {
|
||||||
|
/// The `std140` version of this value.
|
||||||
|
type Output: Std140;
|
||||||
|
|
||||||
|
/// Convert this value into the `std140` version of itself.
|
||||||
|
fn as_std140(&self) -> Self::Output;
|
||||||
|
|
||||||
|
/// Returns the size of the `std140` version of this type. Useful for
|
||||||
|
/// pre-sizing buffers.
|
||||||
|
fn std140_size_static() -> usize {
|
||||||
|
size_of::<Self::Output>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts from `std140` version of self to self.
|
||||||
|
fn from_std140(val: Self::Output) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsStd140 for T
|
||||||
|
where
|
||||||
|
T: Std140,
|
||||||
|
{
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn as_std140(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_std140(x: Self) -> Self {
|
||||||
|
x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct Std140Padded<T: Std140, const PAD: usize> {
|
||||||
|
inner: T,
|
||||||
|
_padding: [u8; PAD],
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: Std140, const PAD: usize> Zeroable for Std140Padded<T, PAD> {}
|
||||||
|
unsafe impl<T: Std140, const PAD: usize> Pod for Std140Padded<T, PAD> {}
|
||||||
|
|
||||||
|
impl<T: Std140, const PAD: usize> Std140Convertible<T> for Std140Padded<T, PAD> {
|
||||||
|
fn into_std140(self) -> T {
|
||||||
|
self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_std140(inner: T) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
_padding: [0u8; PAD],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct Std140Array<T: Std140, const N: usize>([T::Padded; N]);
|
||||||
|
|
||||||
|
unsafe impl<T: Std140, const N: usize> Zeroable for Std140Array<T, N> where T::Padded: Zeroable {}
|
||||||
|
unsafe impl<T: Std140, const N: usize> Pod for Std140Array<T, N> where T::Padded: Pod {}
|
||||||
|
unsafe impl<T: Std140, const N: usize> Std140 for Std140Array<T, N>
|
||||||
|
where
|
||||||
|
T::Padded: Pod,
|
||||||
|
{
|
||||||
|
const ALIGNMENT: usize = crate::internal::max(T::ALIGNMENT, 16);
|
||||||
|
type Padded = Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Std140, const N: usize> Std140Array<T, N> {
|
||||||
|
fn uninit_array() -> [MaybeUninit<T::Padded>; N] {
|
||||||
|
unsafe { MaybeUninit::uninit().assume_init() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_uninit_array(a: [MaybeUninit<T::Padded>; N]) -> Self {
|
||||||
|
unsafe { core::mem::transmute_copy(&a) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsStd140, const N: usize> AsStd140 for [T; N]
|
||||||
|
where
|
||||||
|
<T::Output as Std140>::Padded: Pod,
|
||||||
|
{
|
||||||
|
type Output = Std140Array<T::Output, N>;
|
||||||
|
fn as_std140(&self) -> Self::Output {
|
||||||
|
let mut res = Self::Output::uninit_array();
|
||||||
|
|
||||||
|
for i in 0..N {
|
||||||
|
res[i] = MaybeUninit::new(Std140Convertible::from_std140(self[i].as_std140()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::Output::from_uninit_array(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_std140(val: Self::Output) -> Self {
|
||||||
|
let mut res: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
|
||||||
|
for i in 0..N {
|
||||||
|
res[i] = MaybeUninit::new(T::from_std140(Std140Convertible::into_std140(val.0[i])));
|
||||||
|
}
|
||||||
|
unsafe { core::mem::transmute_copy(&res) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait implemented for all types that can be written into a buffer as
|
||||||
|
/// `std140` bytes. This type is more general than [`AsStd140`]: all `AsStd140`
|
||||||
|
/// types implement `WriteStd140`, but not the other way around.
|
||||||
|
///
|
||||||
|
/// While `AsStd140` requires implementers to return a type that implements the
|
||||||
|
/// `Std140` trait, `WriteStd140` directly writes bytes using a [`Writer`]. This
|
||||||
|
/// makes `WriteStd140` usable for writing slices or other DSTs that could not
|
||||||
|
/// implement `AsStd140` without allocating new memory on the heap.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub trait WriteStd140 {
|
||||||
|
/// Writes this value into the given [`Writer`] using `std140` layout rules.
|
||||||
|
///
|
||||||
|
/// Should return the offset of the first byte of this type, as returned by
|
||||||
|
/// the first call to [`Writer::write`].
|
||||||
|
fn write_std140<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize>;
|
||||||
|
|
||||||
|
/// The space required to write this value using `std140` layout rules. This
|
||||||
|
/// does not include alignment padding that may be needed before or after
|
||||||
|
/// this type when written as part of a larger buffer.
|
||||||
|
fn std140_size(&self) -> usize {
|
||||||
|
let mut writer = Writer::new(io::sink());
|
||||||
|
self.write_std140(&mut writer).unwrap();
|
||||||
|
writer.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl<T> WriteStd140 for T
|
||||||
|
where
|
||||||
|
T: AsStd140,
|
||||||
|
{
|
||||||
|
fn write_std140<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize> {
|
||||||
|
writer.write_std140(&self.as_std140())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn std140_size(&self) -> usize {
|
||||||
|
size_of::<<Self as AsStd140>::Output>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl<T> WriteStd140 for [T]
|
||||||
|
where
|
||||||
|
T: WriteStd140,
|
||||||
|
{
|
||||||
|
fn write_std140<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize> {
|
||||||
|
// if no items are written, offset is current position of the writer
|
||||||
|
let mut offset = writer.len();
|
||||||
|
|
||||||
|
let mut iter = self.iter();
|
||||||
|
|
||||||
|
if let Some(item) = iter.next() {
|
||||||
|
offset = item.write_std140(writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for item in iter {
|
||||||
|
item.write_std140(writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn std140_size(&self) -> usize {
|
||||||
|
let mut writer = Writer::new(io::sink());
|
||||||
|
self.write_std140(&mut writer).unwrap();
|
||||||
|
writer.len()
|
||||||
|
}
|
||||||
|
}
|
162
crates/crevice/src/std140/writer.rs
Normal file
162
crates/crevice/src/std140/writer.rs
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
use bytemuck::bytes_of;
|
||||||
|
|
||||||
|
use crate::internal::align_offset;
|
||||||
|
use crate::std140::{AsStd140, Std140, WriteStd140};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Type that enables writing correctly aligned `std140` values to a buffer.
|
||||||
|
|
||||||
|
`Writer` is useful when many values need to be laid out in a row that cannot be
|
||||||
|
represented by a struct alone, like dynamically sized arrays or dynamically
|
||||||
|
laid-out values.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
In this example, we'll write a length-prefixed list of lights to a buffer.
|
||||||
|
`std140::Writer` helps align correctly, even across multiple structs, which can
|
||||||
|
be tricky and error-prone otherwise.
|
||||||
|
|
||||||
|
```glsl
|
||||||
|
struct PointLight {
|
||||||
|
vec3 position;
|
||||||
|
vec3 color;
|
||||||
|
float brightness;
|
||||||
|
};
|
||||||
|
|
||||||
|
buffer POINT_LIGHTS {
|
||||||
|
uint len;
|
||||||
|
PointLight[] lights;
|
||||||
|
} point_lights;
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
use crevice::std140::{self, AsStd140};
|
||||||
|
|
||||||
|
#[derive(AsStd140)]
|
||||||
|
struct PointLight {
|
||||||
|
position: mint::Vector3<f32>,
|
||||||
|
color: mint::Vector3<f32>,
|
||||||
|
brightness: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
let lights = vec![
|
||||||
|
PointLight {
|
||||||
|
position: [0.0, 1.0, 0.0].into(),
|
||||||
|
color: [1.0, 0.0, 0.0].into(),
|
||||||
|
brightness: 0.6,
|
||||||
|
},
|
||||||
|
PointLight {
|
||||||
|
position: [0.0, 4.0, 3.0].into(),
|
||||||
|
color: [1.0, 1.0, 1.0].into(),
|
||||||
|
brightness: 1.0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
# fn map_gpu_buffer_for_write() -> &'static mut [u8] {
|
||||||
|
# Box::leak(vec![0; 1024].into_boxed_slice())
|
||||||
|
# }
|
||||||
|
let target_buffer = map_gpu_buffer_for_write();
|
||||||
|
let mut writer = std140::Writer::new(target_buffer);
|
||||||
|
|
||||||
|
let light_count = lights.len() as u32;
|
||||||
|
writer.write(&light_count)?;
|
||||||
|
|
||||||
|
// Crevice will automatically insert the required padding to align the
|
||||||
|
// PointLight structure correctly. In this case, there will be 12 bytes of
|
||||||
|
// padding between the length field and the light list.
|
||||||
|
|
||||||
|
writer.write(lights.as_slice())?;
|
||||||
|
|
||||||
|
# fn unmap_gpu_buffer() {}
|
||||||
|
unmap_gpu_buffer();
|
||||||
|
|
||||||
|
# Ok::<(), std::io::Error>(())
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
pub struct Writer<W> {
|
||||||
|
writer: W,
|
||||||
|
offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> Writer<W> {
|
||||||
|
/// Create a new `Writer`, wrapping a buffer, file, or other type that
|
||||||
|
/// implements [`std::io::Write`].
|
||||||
|
pub fn new(writer: W) -> Self {
|
||||||
|
Self { writer, offset: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a new value to the underlying buffer, writing zeroed padding where
|
||||||
|
/// necessary.
|
||||||
|
///
|
||||||
|
/// Returns the offset into the buffer that the value was written to.
|
||||||
|
pub fn write<T>(&mut self, value: &T) -> io::Result<usize>
|
||||||
|
where
|
||||||
|
T: WriteStd140 + ?Sized,
|
||||||
|
{
|
||||||
|
value.write_std140(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write an iterator of values to the underlying buffer.
|
||||||
|
///
|
||||||
|
/// Returns the offset into the buffer that the first value was written to.
|
||||||
|
/// If no values were written, returns the `len()`.
|
||||||
|
pub fn write_iter<I, T>(&mut self, iter: I) -> io::Result<usize>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = T>,
|
||||||
|
T: WriteStd140,
|
||||||
|
{
|
||||||
|
let mut offset = self.offset;
|
||||||
|
|
||||||
|
let mut iter = iter.into_iter();
|
||||||
|
|
||||||
|
if let Some(item) = iter.next() {
|
||||||
|
offset = item.write_std140(self)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for item in iter {
|
||||||
|
item.write_std140(self)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write an `Std140` type to the underlying buffer.
|
||||||
|
pub fn write_std140<T>(&mut self, value: &T) -> io::Result<usize>
|
||||||
|
where
|
||||||
|
T: Std140,
|
||||||
|
{
|
||||||
|
let padding = align_offset(self.offset, T::ALIGNMENT);
|
||||||
|
|
||||||
|
for _ in 0..padding {
|
||||||
|
self.writer.write_all(&[0])?;
|
||||||
|
}
|
||||||
|
self.offset += padding;
|
||||||
|
|
||||||
|
let value = value.as_std140();
|
||||||
|
self.writer.write_all(bytes_of(&value))?;
|
||||||
|
|
||||||
|
let write_here = self.offset;
|
||||||
|
self.offset += size_of::<T>();
|
||||||
|
|
||||||
|
Ok(write_here)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a slice of values to the underlying buffer.
|
||||||
|
#[deprecated(
|
||||||
|
since = "0.6.0",
|
||||||
|
note = "Use `write` instead -- it now works on slices."
|
||||||
|
)]
|
||||||
|
pub fn write_slice<T>(&mut self, slice: &[T]) -> io::Result<usize>
|
||||||
|
where
|
||||||
|
T: AsStd140,
|
||||||
|
{
|
||||||
|
self.write(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the amount of data written by this `Writer`.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.offset
|
||||||
|
}
|
||||||
|
}
|
16
crates/crevice/src/std430.rs
Normal file
16
crates/crevice/src/std430.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
//! Defines traits and types for working with data adhering to GLSL's `std140`
|
||||||
|
//! layout specification.
|
||||||
|
|
||||||
|
mod primitives;
|
||||||
|
mod sizer;
|
||||||
|
mod traits;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
mod writer;
|
||||||
|
|
||||||
|
pub use self::primitives::*;
|
||||||
|
pub use self::sizer::*;
|
||||||
|
pub use self::traits::*;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub use self::writer::*;
|
||||||
|
|
||||||
|
pub use crevice_derive::AsStd430;
|
173
crates/crevice/src/std430/primitives.rs
Normal file
173
crates/crevice/src/std430/primitives.rs
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
|
use crate::glsl::Glsl;
|
||||||
|
use crate::std430::{Std430, Std430Padded};
|
||||||
|
|
||||||
|
use crate::internal::align_offset;
|
||||||
|
use core::mem::size_of;
|
||||||
|
|
||||||
|
unsafe impl Std430 for f32 {
|
||||||
|
const ALIGNMENT: usize = 4;
|
||||||
|
type Padded = Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Std430 for f64 {
|
||||||
|
const ALIGNMENT: usize = 8;
|
||||||
|
type Padded = Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Std430 for i32 {
|
||||||
|
const ALIGNMENT: usize = 4;
|
||||||
|
type Padded = Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Std430 for u32 {
|
||||||
|
const ALIGNMENT: usize = 4;
|
||||||
|
type Padded = Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! vectors {
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
#[$doc:meta] align($align:literal) $glsl_name:ident $name:ident <$prim:ident> ($($field:ident),+)
|
||||||
|
)+
|
||||||
|
) => {
|
||||||
|
$(
|
||||||
|
#[$doc]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct $name {
|
||||||
|
$(pub $field: $prim,)+
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Zeroable for $name {}
|
||||||
|
unsafe impl Pod for $name {}
|
||||||
|
|
||||||
|
unsafe impl Std430 for $name {
|
||||||
|
const ALIGNMENT: usize = $align;
|
||||||
|
type Padded = Std430Padded<Self, {align_offset(size_of::<$name>(), $align)}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Glsl for $name {
|
||||||
|
const NAME: &'static str = stringify!($glsl_name);
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
vectors! {
|
||||||
|
#[doc = "Corresponds to a GLSL `vec2` in std430 layout."] align(8) vec2 Vec2<f32>(x, y)
|
||||||
|
#[doc = "Corresponds to a GLSL `vec3` in std430 layout."] align(16) vec3 Vec3<f32>(x, y, z)
|
||||||
|
#[doc = "Corresponds to a GLSL `vec4` in std430 layout."] align(16) vec4 Vec4<f32>(x, y, z, w)
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `ivec2` in std430 layout."] align(8) ivec2 IVec2<i32>(x, y)
|
||||||
|
#[doc = "Corresponds to a GLSL `ivec3` in std430 layout."] align(16) ivec3 IVec3<i32>(x, y, z)
|
||||||
|
#[doc = "Corresponds to a GLSL `ivec4` in std430 layout."] align(16) ivec4 IVec4<i32>(x, y, z, w)
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `uvec2` in std430 layout."] align(8) uvec2 UVec2<u32>(x, y)
|
||||||
|
#[doc = "Corresponds to a GLSL `uvec3` in std430 layout."] align(16) uvec3 UVec3<u32>(x, y, z)
|
||||||
|
#[doc = "Corresponds to a GLSL `uvec4` in std430 layout."] align(16) uvec4 UVec4<u32>(x, y, z, w)
|
||||||
|
|
||||||
|
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
|
||||||
|
|
||||||
|
// #[doc = "Corresponds to a GLSL `bvec2` in std430 layout."] align(8) bvec2 BVec2<bool>(x, y)
|
||||||
|
// #[doc = "Corresponds to a GLSL `bvec3` in std430 layout."] align(16) bvec3 BVec3<bool>(x, y, z)
|
||||||
|
// #[doc = "Corresponds to a GLSL `bvec4` in std430 layout."] align(16) bvec4 BVec4<bool>(x, y, z, w)
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `dvec2` in std430 layout."] align(16) dvec2 DVec2<f64>(x, y)
|
||||||
|
#[doc = "Corresponds to a GLSL `dvec3` in std430 layout."] align(32) dvec3 DVec3<f64>(x, y, z)
|
||||||
|
#[doc = "Corresponds to a GLSL `dvec4` in std430 layout."] align(32) dvec4 DVec4<f64>(x, y, z, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! matrices {
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
#[$doc:meta]
|
||||||
|
align($align:literal)
|
||||||
|
$glsl_name:ident $name:ident {
|
||||||
|
$($field:ident: $field_ty:ty,)+
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
) => {
|
||||||
|
$(
|
||||||
|
#[$doc]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct $name {
|
||||||
|
$(pub $field: $field_ty,)+
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Zeroable for $name {}
|
||||||
|
unsafe impl Pod for $name {}
|
||||||
|
|
||||||
|
unsafe impl Std430 for $name {
|
||||||
|
const ALIGNMENT: usize = $align;
|
||||||
|
/// Matrices are technically arrays of primitives, and as such require pad at end.
|
||||||
|
const PAD_AT_END: bool = true;
|
||||||
|
type Padded = Std430Padded<Self, {align_offset(size_of::<$name>(), $align)}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Glsl for $name {
|
||||||
|
const NAME: &'static str = stringify!($glsl_name);
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
matrices! {
|
||||||
|
#[doc = "Corresponds to a GLSL `mat2` in std430 layout."]
|
||||||
|
align(8)
|
||||||
|
mat2 Mat2 {
|
||||||
|
x: Vec2,
|
||||||
|
y: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `mat3` in std430 layout."]
|
||||||
|
align(16)
|
||||||
|
mat3 Mat3 {
|
||||||
|
x: Vec3,
|
||||||
|
_pad_x: f32,
|
||||||
|
y: Vec3,
|
||||||
|
_pad_y: f32,
|
||||||
|
z: Vec3,
|
||||||
|
_pad_z: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `mat4` in std430 layout."]
|
||||||
|
align(16)
|
||||||
|
mat4 Mat4 {
|
||||||
|
x: Vec4,
|
||||||
|
y: Vec4,
|
||||||
|
z: Vec4,
|
||||||
|
w: Vec4,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `dmat2` in std430 layout."]
|
||||||
|
align(16)
|
||||||
|
dmat2 DMat2 {
|
||||||
|
x: DVec2,
|
||||||
|
y: DVec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `dmat3` in std430 layout."]
|
||||||
|
align(32)
|
||||||
|
dmat3 DMat3 {
|
||||||
|
x: DVec3,
|
||||||
|
_pad_x: f64,
|
||||||
|
y: DVec3,
|
||||||
|
_pad_y: f64,
|
||||||
|
z: DVec3,
|
||||||
|
_pad_z: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc = "Corresponds to a GLSL `dmat3` in std430 layout."]
|
||||||
|
align(32)
|
||||||
|
dmat4 DMat4 {
|
||||||
|
x: DVec4,
|
||||||
|
y: DVec4,
|
||||||
|
z: DVec4,
|
||||||
|
w: DVec4,
|
||||||
|
}
|
||||||
|
}
|
81
crates/crevice/src/std430/sizer.rs
Normal file
81
crates/crevice/src/std430/sizer.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
use core::mem::size_of;
|
||||||
|
|
||||||
|
use crate::internal::align_offset;
|
||||||
|
use crate::std430::{AsStd430, Std430};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Type that computes the buffer size needed by a series of `std430` types laid
|
||||||
|
out.
|
||||||
|
|
||||||
|
This type works well well when paired with `Writer`, precomputing a buffer's
|
||||||
|
size to alleviate the need to dynamically re-allocate buffers.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```glsl
|
||||||
|
struct Frob {
|
||||||
|
vec3 size;
|
||||||
|
float frobiness;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer FROBS {
|
||||||
|
uint len;
|
||||||
|
Frob[] frobs;
|
||||||
|
} frobs;
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
use crevice::std430::{self, AsStd430};
|
||||||
|
|
||||||
|
#[derive(AsStd430)]
|
||||||
|
struct Frob {
|
||||||
|
size: mint::Vector3<f32>,
|
||||||
|
frobiness: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Many APIs require that buffers contain at least enough space for all
|
||||||
|
// fixed-size bindiongs to a buffer as well as one element of any arrays, if
|
||||||
|
// there are any.
|
||||||
|
let mut sizer = std430::Sizer::new();
|
||||||
|
sizer.add::<u32>();
|
||||||
|
sizer.add::<Frob>();
|
||||||
|
|
||||||
|
# fn create_buffer_with_size(size: usize) {}
|
||||||
|
let buffer = create_buffer_with_size(sizer.len());
|
||||||
|
# assert_eq!(sizer.len(), 32);
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
pub struct Sizer {
|
||||||
|
offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sizer {
|
||||||
|
/// Create a new `Sizer`.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { offset: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a type's necessary padding and size to the `Sizer`. Returns the
|
||||||
|
/// offset into the buffer where that type would be written.
|
||||||
|
pub fn add<T>(&mut self) -> usize
|
||||||
|
where
|
||||||
|
T: AsStd430,
|
||||||
|
{
|
||||||
|
let size = size_of::<<T as AsStd430>::Output>();
|
||||||
|
let alignment = <T as AsStd430>::Output::ALIGNMENT;
|
||||||
|
let padding = align_offset(self.offset, alignment);
|
||||||
|
|
||||||
|
self.offset += padding;
|
||||||
|
let write_here = self.offset;
|
||||||
|
|
||||||
|
self.offset += size;
|
||||||
|
|
||||||
|
write_here
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of bytes required to contain all the types added to
|
||||||
|
/// the `Sizer`.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.offset
|
||||||
|
}
|
||||||
|
}
|
283
crates/crevice/src/std430/traits.rs
Normal file
283
crates/crevice/src/std430/traits.rs
Normal file
|
@ -0,0 +1,283 @@
|
||||||
|
use core::mem::{size_of, MaybeUninit};
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
use bytemuck::{bytes_of, Pod, Zeroable};
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use crate::std430::Writer;
|
||||||
|
|
||||||
|
/// Trait implemented for all `std430` primitives. Generally should not be
|
||||||
|
/// implemented outside this crate.
|
||||||
|
pub unsafe trait Std430: Copy + Zeroable + Pod {
|
||||||
|
/// The required alignment of the type. Must be a power of two.
|
||||||
|
///
|
||||||
|
/// This is distinct from the value returned by `std::mem::align_of` because
|
||||||
|
/// `AsStd430` structs do not use Rust's alignment. This enables them to
|
||||||
|
/// control and zero their padding bytes, making converting them to and from
|
||||||
|
/// slices safe.
|
||||||
|
const ALIGNMENT: usize;
|
||||||
|
|
||||||
|
/// Whether this type requires a padding at the end (ie, is a struct or an array
|
||||||
|
/// of primitives).
|
||||||
|
/// See <https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf#page=159>
|
||||||
|
/// (rule 4 and 9)
|
||||||
|
const PAD_AT_END: bool = false;
|
||||||
|
/// Padded type (Std430Padded specialization)
|
||||||
|
/// The usual implementation is
|
||||||
|
/// type Padded = Std430Padded<Self, {align_offset(size_of::<Self>(), ALIGNMENT)}>;
|
||||||
|
type Padded: Std430Convertible<Self>;
|
||||||
|
|
||||||
|
/// Casts the type to a byte array. Implementors should not override this
|
||||||
|
/// method.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// This is always safe due to the requirements of [`bytemuck::Pod`] being a
|
||||||
|
/// prerequisite for this trait.
|
||||||
|
fn as_bytes(&self) -> &[u8] {
|
||||||
|
bytes_of(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait specifically for Std430::Padded, implements conversions between padded type and base type.
|
||||||
|
pub trait Std430Convertible<T: Std430>: Copy {
|
||||||
|
/// Convert from self to Std430
|
||||||
|
fn into_std430(self) -> T;
|
||||||
|
/// Convert from Std430 to self
|
||||||
|
fn from_std430(_: T) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Std430> Std430Convertible<T> for T {
|
||||||
|
fn into_std430(self) -> T {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn from_std430(also_self: T) -> Self {
|
||||||
|
also_self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unfortunately, we cannot easily derive padded representation for generic Std140 types.
|
||||||
|
/// For now, we'll just use this empty enum with no values.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum InvalidPadded {}
|
||||||
|
impl<T: Std430> Std430Convertible<T> for InvalidPadded {
|
||||||
|
fn into_std430(self) -> T {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
fn from_std430(_: T) -> Self {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
Trait implemented for all types that can be turned into `std430` values.
|
||||||
|
|
||||||
|
This trait can often be `#[derive]`'d instead of manually implementing it. Any
|
||||||
|
struct which contains only fields that also implement `AsStd430` can derive
|
||||||
|
`AsStd430`.
|
||||||
|
|
||||||
|
Types from the mint crate implement `AsStd430`, making them convenient for use
|
||||||
|
in uniform types. Most Rust geometry crates, like cgmath, nalgebra, and
|
||||||
|
ultraviolet support mint.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```glsl
|
||||||
|
uniform CAMERA {
|
||||||
|
mat4 view;
|
||||||
|
mat4 projection;
|
||||||
|
} camera;
|
||||||
|
```
|
||||||
|
|
||||||
|
```no_run
|
||||||
|
use crevice::std430::{AsStd430, Std430};
|
||||||
|
|
||||||
|
#[derive(AsStd430)]
|
||||||
|
struct CameraUniform {
|
||||||
|
view: mint::ColumnMatrix4<f32>,
|
||||||
|
projection: mint::ColumnMatrix4<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let view: mint::ColumnMatrix4<f32> = todo!("your math code here");
|
||||||
|
let projection: mint::ColumnMatrix4<f32> = todo!("your math code here");
|
||||||
|
|
||||||
|
let camera = CameraUniform {
|
||||||
|
view,
|
||||||
|
projection,
|
||||||
|
};
|
||||||
|
|
||||||
|
# fn write_to_gpu_buffer(bytes: &[u8]) {}
|
||||||
|
let camera_std430 = camera.as_std430();
|
||||||
|
write_to_gpu_buffer(camera_std430.as_bytes());
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
pub trait AsStd430 {
|
||||||
|
/// The `std430` version of this value.
|
||||||
|
type Output: Std430;
|
||||||
|
|
||||||
|
/// Convert this value into the `std430` version of itself.
|
||||||
|
fn as_std430(&self) -> Self::Output;
|
||||||
|
|
||||||
|
/// Returns the size of the `std430` version of this type. Useful for
|
||||||
|
/// pre-sizing buffers.
|
||||||
|
fn std430_size_static() -> usize {
|
||||||
|
size_of::<Self::Output>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts from `std430` version of self to self.
|
||||||
|
fn from_std430(value: Self::Output) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsStd430 for T
|
||||||
|
where
|
||||||
|
T: Std430,
|
||||||
|
{
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn as_std430(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_std430(value: Self) -> Self {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct Std430Padded<T: Std430, const PAD: usize> {
|
||||||
|
inner: T,
|
||||||
|
_padding: [u8; PAD],
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: Std430, const PAD: usize> Zeroable for Std430Padded<T, PAD> {}
|
||||||
|
unsafe impl<T: Std430, const PAD: usize> Pod for Std430Padded<T, PAD> {}
|
||||||
|
|
||||||
|
impl<T: Std430, const PAD: usize> Std430Convertible<T> for Std430Padded<T, PAD> {
|
||||||
|
fn into_std430(self) -> T {
|
||||||
|
self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_std430(inner: T) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
_padding: [0u8; PAD],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct Std430Array<T: Std430, const N: usize>([T::Padded; N]);
|
||||||
|
|
||||||
|
unsafe impl<T: Std430, const N: usize> Zeroable for Std430Array<T, N> where T::Padded: Zeroable {}
|
||||||
|
unsafe impl<T: Std430, const N: usize> Pod for Std430Array<T, N> where T::Padded: Pod {}
|
||||||
|
unsafe impl<T: Std430, const N: usize> Std430 for Std430Array<T, N>
|
||||||
|
where
|
||||||
|
T::Padded: Pod,
|
||||||
|
{
|
||||||
|
const ALIGNMENT: usize = T::ALIGNMENT;
|
||||||
|
type Padded = Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Std430, const N: usize> Std430Array<T, N> {
|
||||||
|
fn uninit_array() -> [MaybeUninit<T::Padded>; N] {
|
||||||
|
unsafe { MaybeUninit::uninit().assume_init() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_uninit_array(a: [MaybeUninit<T::Padded>; N]) -> Self {
|
||||||
|
unsafe { core::mem::transmute_copy(&a) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsStd430, const N: usize> AsStd430 for [T; N]
|
||||||
|
where
|
||||||
|
<T::Output as Std430>::Padded: Pod,
|
||||||
|
{
|
||||||
|
type Output = Std430Array<T::Output, N>;
|
||||||
|
fn as_std430(&self) -> Self::Output {
|
||||||
|
let mut res = Self::Output::uninit_array();
|
||||||
|
|
||||||
|
for i in 0..N {
|
||||||
|
res[i] = MaybeUninit::new(Std430Convertible::from_std430(self[i].as_std430()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::Output::from_uninit_array(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_std430(val: Self::Output) -> Self {
|
||||||
|
let mut res: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
|
||||||
|
for i in 0..N {
|
||||||
|
res[i] = MaybeUninit::new(T::from_std430(val.0[i].into_std430()));
|
||||||
|
}
|
||||||
|
unsafe { core::mem::transmute_copy(&res) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait implemented for all types that can be written into a buffer as
|
||||||
|
/// `std430` bytes. This type is more general than [`AsStd430`]: all `AsStd430`
|
||||||
|
/// types implement `WriteStd430`, but not the other way around.
|
||||||
|
///
|
||||||
|
/// While `AsStd430` requires implementers to return a type that implements the
|
||||||
|
/// `Std430` trait, `WriteStd430` directly writes bytes using a [`Writer`]. This
|
||||||
|
/// makes `WriteStd430` usable for writing slices or other DSTs that could not
|
||||||
|
/// implement `AsStd430` without allocating new memory on the heap.
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub trait WriteStd430 {
|
||||||
|
/// Writes this value into the given [`Writer`] using `std430` layout rules.
|
||||||
|
///
|
||||||
|
/// Should return the offset of the first byte of this type, as returned by
|
||||||
|
/// the first call to [`Writer::write`].
|
||||||
|
fn write_std430<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize>;
|
||||||
|
|
||||||
|
/// The space required to write this value using `std430` layout rules. This
|
||||||
|
/// does not include alignment padding that may be needed before or after
|
||||||
|
/// this type when written as part of a larger buffer.
|
||||||
|
fn std430_size(&self) -> usize {
|
||||||
|
let mut writer = Writer::new(io::sink());
|
||||||
|
self.write_std430(&mut writer).unwrap();
|
||||||
|
writer.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl<T> WriteStd430 for T
|
||||||
|
where
|
||||||
|
T: AsStd430,
|
||||||
|
{
|
||||||
|
fn write_std430<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize> {
|
||||||
|
writer.write_std430(&self.as_std430())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn std430_size(&self) -> usize {
|
||||||
|
size_of::<<Self as AsStd430>::Output>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl<T> WriteStd430 for [T]
|
||||||
|
where
|
||||||
|
T: WriteStd430,
|
||||||
|
{
|
||||||
|
fn write_std430<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize> {
|
||||||
|
let mut offset = writer.len();
|
||||||
|
|
||||||
|
let mut iter = self.iter();
|
||||||
|
|
||||||
|
if let Some(item) = iter.next() {
|
||||||
|
offset = item.write_std430(writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for item in self.iter() {
|
||||||
|
item.write_std430(writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn std430_size(&self) -> usize {
|
||||||
|
let mut writer = Writer::new(io::sink());
|
||||||
|
self.write_std430(&mut writer).unwrap();
|
||||||
|
writer.len()
|
||||||
|
}
|
||||||
|
}
|
150
crates/crevice/src/std430/writer.rs
Normal file
150
crates/crevice/src/std430/writer.rs
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
use bytemuck::bytes_of;
|
||||||
|
|
||||||
|
use crate::internal::align_offset;
|
||||||
|
use crate::std430::{AsStd430, Std430, WriteStd430};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Type that enables writing correctly aligned `std430` values to a buffer.
|
||||||
|
|
||||||
|
`Writer` is useful when many values need to be laid out in a row that cannot be
|
||||||
|
represented by a struct alone, like dynamically sized arrays or dynamically
|
||||||
|
laid-out values.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
In this example, we'll write a length-prefixed list of lights to a buffer.
|
||||||
|
`std430::Writer` helps align correctly, even across multiple structs, which can
|
||||||
|
be tricky and error-prone otherwise.
|
||||||
|
|
||||||
|
```glsl
|
||||||
|
struct PointLight {
|
||||||
|
vec3 position;
|
||||||
|
vec3 color;
|
||||||
|
float brightness;
|
||||||
|
};
|
||||||
|
|
||||||
|
buffer POINT_LIGHTS {
|
||||||
|
uint len;
|
||||||
|
PointLight[] lights;
|
||||||
|
} point_lights;
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
use crevice::std430::{self, AsStd430};
|
||||||
|
|
||||||
|
#[derive(AsStd430)]
|
||||||
|
struct PointLight {
|
||||||
|
position: mint::Vector3<f32>,
|
||||||
|
color: mint::Vector3<f32>,
|
||||||
|
brightness: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
let lights = vec![
|
||||||
|
PointLight {
|
||||||
|
position: [0.0, 1.0, 0.0].into(),
|
||||||
|
color: [1.0, 0.0, 0.0].into(),
|
||||||
|
brightness: 0.6,
|
||||||
|
},
|
||||||
|
PointLight {
|
||||||
|
position: [0.0, 4.0, 3.0].into(),
|
||||||
|
color: [1.0, 1.0, 1.0].into(),
|
||||||
|
brightness: 1.0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
# fn map_gpu_buffer_for_write() -> &'static mut [u8] {
|
||||||
|
# Box::leak(vec![0; 1024].into_boxed_slice())
|
||||||
|
# }
|
||||||
|
let target_buffer = map_gpu_buffer_for_write();
|
||||||
|
let mut writer = std430::Writer::new(target_buffer);
|
||||||
|
|
||||||
|
let light_count = lights.len() as u32;
|
||||||
|
writer.write(&light_count)?;
|
||||||
|
|
||||||
|
// Crevice will automatically insert the required padding to align the
|
||||||
|
// PointLight structure correctly. In this case, there will be 12 bytes of
|
||||||
|
// padding between the length field and the light list.
|
||||||
|
|
||||||
|
writer.write(lights.as_slice())?;
|
||||||
|
|
||||||
|
# fn unmap_gpu_buffer() {}
|
||||||
|
unmap_gpu_buffer();
|
||||||
|
|
||||||
|
# Ok::<(), std::io::Error>(())
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
pub struct Writer<W> {
|
||||||
|
writer: W,
|
||||||
|
offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> Writer<W> {
|
||||||
|
/// Create a new `Writer`, wrapping a buffer, file, or other type that
|
||||||
|
/// implements [`std::io::Write`].
|
||||||
|
pub fn new(writer: W) -> Self {
|
||||||
|
Self { writer, offset: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a new value to the underlying buffer, writing zeroed padding where
|
||||||
|
/// necessary.
|
||||||
|
///
|
||||||
|
/// Returns the offset into the buffer that the value was written to.
|
||||||
|
pub fn write<T>(&mut self, value: &T) -> io::Result<usize>
|
||||||
|
where
|
||||||
|
T: WriteStd430 + ?Sized,
|
||||||
|
{
|
||||||
|
value.write_std430(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write an iterator of values to the underlying buffer.
|
||||||
|
///
|
||||||
|
/// Returns the offset into the buffer that the first value was written to.
|
||||||
|
/// If no values were written, returns the `len()`.
|
||||||
|
pub fn write_iter<I, T>(&mut self, iter: I) -> io::Result<usize>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = T>,
|
||||||
|
T: WriteStd430,
|
||||||
|
{
|
||||||
|
let mut offset = self.offset;
|
||||||
|
|
||||||
|
let mut iter = iter.into_iter();
|
||||||
|
|
||||||
|
if let Some(item) = iter.next() {
|
||||||
|
offset = item.write_std430(self)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for item in iter {
|
||||||
|
item.write_std430(self)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write an `Std430` type to the underlying buffer.
|
||||||
|
pub fn write_std430<T>(&mut self, value: &T) -> io::Result<usize>
|
||||||
|
where
|
||||||
|
T: Std430,
|
||||||
|
{
|
||||||
|
let padding = align_offset(self.offset, T::ALIGNMENT);
|
||||||
|
|
||||||
|
for _ in 0..padding {
|
||||||
|
self.writer.write_all(&[0])?;
|
||||||
|
}
|
||||||
|
self.offset += padding;
|
||||||
|
|
||||||
|
let value = value.as_std430();
|
||||||
|
self.writer.write_all(bytes_of(&value))?;
|
||||||
|
|
||||||
|
let write_here = self.offset;
|
||||||
|
self.offset += size_of::<T>();
|
||||||
|
|
||||||
|
Ok(write_here)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the amount of data written by this `Writer`.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.offset
|
||||||
|
}
|
||||||
|
}
|
97
crates/crevice/src/util.rs
Normal file
97
crates/crevice/src/util.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
#![allow(unused_macros)]
|
||||||
|
|
||||||
|
macro_rules! easy_impl {
|
||||||
|
( $( $std_name:ident $imp_ty:ty { $($field:ident),* }, )* ) => {
|
||||||
|
$(
|
||||||
|
impl crate::std140::AsStd140 for $imp_ty {
|
||||||
|
type Output = crate::std140::$std_name;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn as_std140(&self) -> Self::Output {
|
||||||
|
crate::std140::$std_name {
|
||||||
|
$(
|
||||||
|
$field: self.$field.as_std140(),
|
||||||
|
)*
|
||||||
|
..bytemuck::Zeroable::zeroed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_std140(value: Self::Output) -> Self {
|
||||||
|
Self {
|
||||||
|
$(
|
||||||
|
$field: <_ as crate::std140::AsStd140>::from_std140(value.$field),
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::std430::AsStd430 for $imp_ty {
|
||||||
|
type Output = crate::std430::$std_name;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn as_std430(&self) -> Self::Output {
|
||||||
|
crate::std430::$std_name {
|
||||||
|
$(
|
||||||
|
$field: self.$field.as_std430(),
|
||||||
|
)*
|
||||||
|
..bytemuck::Zeroable::zeroed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_std430(value: Self::Output) -> Self {
|
||||||
|
Self {
|
||||||
|
$(
|
||||||
|
$field: <_ as crate::std430::AsStd430>::from_std430(value.$field),
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl crate::glsl::Glsl for $imp_ty {
|
||||||
|
const NAME: &'static str = crate::std140::$std_name::NAME;
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! minty_impl {
|
||||||
|
( $( $mint_ty:ty => $imp_ty:ty, )* ) => {
|
||||||
|
$(
|
||||||
|
impl crate::std140::AsStd140 for $imp_ty {
|
||||||
|
type Output = <$mint_ty as crate::std140::AsStd140>::Output;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn as_std140(&self) -> Self::Output {
|
||||||
|
let mint: $mint_ty = (*self).into();
|
||||||
|
mint.as_std140()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_std140(value: Self::Output) -> Self {
|
||||||
|
<$mint_ty>::from_std140(value).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::std430::AsStd430 for $imp_ty {
|
||||||
|
type Output = <$mint_ty as crate::std430::AsStd430>::Output;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn as_std430(&self) -> Self::Output {
|
||||||
|
let mint: $mint_ty = (*self).into();
|
||||||
|
mint.as_std430()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_std430(value: Self::Output) -> Self {
|
||||||
|
<$mint_ty>::from_std430(value).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl crate::glsl::Glsl for $imp_ty {
|
||||||
|
const NAME: &'static str = <$mint_ty>::NAME;
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
source: tests/test.rs
|
||||||
|
expression: "TestGlsl::glsl_definition()"
|
||||||
|
|
||||||
|
---
|
||||||
|
struct TestGlsl {
|
||||||
|
vec3 foo[8][4];
|
||||||
|
};
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
source: tests/test.rs
|
||||||
|
expression: "TestGlsl::glsl_definition()"
|
||||||
|
|
||||||
|
---
|
||||||
|
struct TestGlsl {
|
||||||
|
vec3 foo;
|
||||||
|
mat2 bar;
|
||||||
|
};
|
61
crates/crevice/tests/test.rs
Normal file
61
crates/crevice/tests/test.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
use crevice::glsl::GlslStruct;
|
||||||
|
use crevice::std140::AsStd140;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn there_and_back_again() {
|
||||||
|
#[derive(AsStd140, Debug, PartialEq)]
|
||||||
|
struct ThereAndBackAgain {
|
||||||
|
view: mint::ColumnMatrix3<f32>,
|
||||||
|
origin: mint::Vector3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = ThereAndBackAgain {
|
||||||
|
view: mint::ColumnMatrix3 {
|
||||||
|
x: mint::Vector3 {
|
||||||
|
x: 1.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0,
|
||||||
|
},
|
||||||
|
y: mint::Vector3 {
|
||||||
|
x: 0.0,
|
||||||
|
y: 1.0,
|
||||||
|
z: 0.0,
|
||||||
|
},
|
||||||
|
z: mint::Vector3 {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 1.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
origin: mint::Vector3 {
|
||||||
|
x: 0.0,
|
||||||
|
y: 1.0,
|
||||||
|
z: 2.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let x_as = x.as_std140();
|
||||||
|
assert_eq!(<ThereAndBackAgain as AsStd140>::from_std140(x_as), x);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_struct_glsl() {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(GlslStruct)]
|
||||||
|
struct TestGlsl {
|
||||||
|
foo: mint::Vector3<f32>,
|
||||||
|
bar: mint::ColumnMatrix2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
insta::assert_display_snapshot!(TestGlsl::glsl_definition());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_struct_array_glsl() {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(GlslStruct)]
|
||||||
|
struct TestGlsl {
|
||||||
|
foo: [[mint::Vector3<f32>; 8]; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
insta::assert_display_snapshot!(TestGlsl::glsl_definition());
|
||||||
|
}
|
94
examples/2d/pipelined_texture_atlas.rs
Normal file
94
examples/2d/pipelined_texture_atlas.rs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
use bevy::{
|
||||||
|
asset::LoadState,
|
||||||
|
math::Vec3,
|
||||||
|
prelude::{
|
||||||
|
App, AssetServer, Assets, Commands, HandleUntyped, IntoSystem, Res, ResMut, State,
|
||||||
|
SystemSet, Transform,
|
||||||
|
},
|
||||||
|
render2::{camera::OrthographicCameraBundle, texture::Image},
|
||||||
|
sprite2::{
|
||||||
|
PipelinedSpriteBundle, PipelinedSpriteSheetBundle, TextureAtlas, TextureAtlasBuilder,
|
||||||
|
TextureAtlasSprite,
|
||||||
|
},
|
||||||
|
PipelinedDefaultPlugins,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// In this example we generate a new texture atlas (sprite sheet) from a folder containing
|
||||||
|
/// individual sprites
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.init_resource::<RpgSpriteHandles>()
|
||||||
|
.add_plugins(PipelinedDefaultPlugins)
|
||||||
|
.add_state(AppState::Setup)
|
||||||
|
.add_system_set(SystemSet::on_enter(AppState::Setup).with_system(load_textures.system()))
|
||||||
|
.add_system_set(SystemSet::on_update(AppState::Setup).with_system(check_textures.system()))
|
||||||
|
.add_system_set(SystemSet::on_enter(AppState::Finished).with_system(setup.system()))
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
enum AppState {
|
||||||
|
Setup,
|
||||||
|
Finished,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct RpgSpriteHandles {
|
||||||
|
handles: Vec<HandleUntyped>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_textures(mut rpg_sprite_handles: ResMut<RpgSpriteHandles>, asset_server: Res<AssetServer>) {
|
||||||
|
rpg_sprite_handles.handles = asset_server.load_folder("textures/rpg").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_textures(
|
||||||
|
mut state: ResMut<State<AppState>>,
|
||||||
|
rpg_sprite_handles: ResMut<RpgSpriteHandles>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
) {
|
||||||
|
if let LoadState::Loaded =
|
||||||
|
asset_server.get_group_load_state(rpg_sprite_handles.handles.iter().map(|handle| handle.id))
|
||||||
|
{
|
||||||
|
state.set(AppState::Finished).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
rpg_sprite_handles: Res<RpgSpriteHandles>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
||||||
|
mut textures: ResMut<Assets<Image>>,
|
||||||
|
) {
|
||||||
|
let mut texture_atlas_builder = TextureAtlasBuilder::default();
|
||||||
|
for handle in rpg_sprite_handles.handles.iter() {
|
||||||
|
let texture = textures.get(handle).unwrap();
|
||||||
|
texture_atlas_builder.add_texture(handle.clone_weak().typed::<Image>(), texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
let texture_atlas = texture_atlas_builder.finish(&mut textures).unwrap();
|
||||||
|
let texture_atlas_texture = texture_atlas.texture.clone();
|
||||||
|
let vendor_handle = asset_server.get_handle("textures/rpg/chars/vendor/generic-rpg-vendor.png");
|
||||||
|
let vendor_index = texture_atlas.get_texture_index(&vendor_handle).unwrap();
|
||||||
|
let atlas_handle = texture_atlases.add(texture_atlas);
|
||||||
|
|
||||||
|
// set up a scene to display our texture atlas
|
||||||
|
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
|
||||||
|
// draw a sprite from the atlas
|
||||||
|
commands.spawn_bundle(PipelinedSpriteSheetBundle {
|
||||||
|
transform: Transform {
|
||||||
|
translation: Vec3::new(150.0, 0.0, 0.0),
|
||||||
|
scale: Vec3::splat(4.0),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
sprite: TextureAtlasSprite::new(vendor_index),
|
||||||
|
texture_atlas: atlas_handle,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// draw the atlas itself
|
||||||
|
commands.spawn_bundle(PipelinedSpriteBundle {
|
||||||
|
texture: texture_atlas_texture,
|
||||||
|
transform: Transform::from_xyz(-300.0, 0.0, 0.0),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
255
examples/3d/3d_scene_pipelined.rs
Normal file
255
examples/3d/3d_scene_pipelined.rs
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
use bevy::{
|
||||||
|
core::Time,
|
||||||
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||||
|
ecs::prelude::*,
|
||||||
|
input::Input,
|
||||||
|
math::{Quat, Vec3},
|
||||||
|
pbr2::{
|
||||||
|
AmbientLight, DirectionalLight, DirectionalLightBundle, PbrBundle, PointLight,
|
||||||
|
PointLightBundle, StandardMaterial,
|
||||||
|
},
|
||||||
|
prelude::{App, Assets, BuildChildren, KeyCode, Transform},
|
||||||
|
render2::{
|
||||||
|
camera::{OrthographicProjection, PerspectiveCameraBundle},
|
||||||
|
color::Color,
|
||||||
|
mesh::{shape, Mesh},
|
||||||
|
},
|
||||||
|
PipelinedDefaultPlugins,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(PipelinedDefaultPlugins)
|
||||||
|
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||||
|
.add_plugin(LogDiagnosticsPlugin::default())
|
||||||
|
.add_startup_system(setup)
|
||||||
|
.add_system(movement)
|
||||||
|
.add_system(animate_light_direction)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Movable;
|
||||||
|
|
||||||
|
/// set up a simple 3D scene
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
// ground plane
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Plane { size: 10.0 })),
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::WHITE,
|
||||||
|
perceptual_roughness: 1.0,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// left wall
|
||||||
|
let mut transform = Transform::from_xyz(2.5, 2.5, 0.0);
|
||||||
|
transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2));
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Box::new(5.0, 0.15, 5.0))),
|
||||||
|
transform,
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::INDIGO,
|
||||||
|
perceptual_roughness: 1.0,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// back (right) wall
|
||||||
|
let mut transform = Transform::from_xyz(0.0, 2.5, -2.5);
|
||||||
|
transform.rotate(Quat::from_rotation_x(std::f32::consts::FRAC_PI_2));
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Box::new(5.0, 0.15, 5.0))),
|
||||||
|
transform,
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::INDIGO,
|
||||||
|
perceptual_roughness: 1.0,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// cube
|
||||||
|
commands
|
||||||
|
.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::PINK,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.insert(Movable);
|
||||||
|
// sphere
|
||||||
|
commands
|
||||||
|
.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::UVSphere {
|
||||||
|
radius: 0.5,
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::LIME_GREEN,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
transform: Transform::from_xyz(1.5, 1.0, 1.5),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.insert(Movable);
|
||||||
|
|
||||||
|
// ambient light
|
||||||
|
commands.insert_resource(AmbientLight {
|
||||||
|
color: Color::ORANGE_RED,
|
||||||
|
brightness: 0.02,
|
||||||
|
});
|
||||||
|
|
||||||
|
// red point light
|
||||||
|
commands
|
||||||
|
.spawn_bundle(PointLightBundle {
|
||||||
|
// transform: Transform::from_xyz(5.0, 8.0, 2.0),
|
||||||
|
transform: Transform::from_xyz(1.0, 2.0, 0.0),
|
||||||
|
point_light: PointLight {
|
||||||
|
intensity: 1600.0, // lumens - roughly a 100W non-halogen incandescent bulb
|
||||||
|
color: Color::RED,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.with_children(|builder| {
|
||||||
|
builder.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::UVSphere {
|
||||||
|
radius: 0.1,
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::RED,
|
||||||
|
emissive: Color::rgba_linear(100.0, 0.0, 0.0, 0.0),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// green point light
|
||||||
|
commands
|
||||||
|
.spawn_bundle(PointLightBundle {
|
||||||
|
// transform: Transform::from_xyz(5.0, 8.0, 2.0),
|
||||||
|
transform: Transform::from_xyz(-1.0, 2.0, 0.0),
|
||||||
|
point_light: PointLight {
|
||||||
|
intensity: 1600.0, // lumens - roughly a 100W non-halogen incandescent bulb
|
||||||
|
color: Color::GREEN,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.with_children(|builder| {
|
||||||
|
builder.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::UVSphere {
|
||||||
|
radius: 0.1,
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::GREEN,
|
||||||
|
emissive: Color::rgba_linear(0.0, 100.0, 0.0, 0.0),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// blue point light
|
||||||
|
commands
|
||||||
|
.spawn_bundle(PointLightBundle {
|
||||||
|
// transform: Transform::from_xyz(5.0, 8.0, 2.0),
|
||||||
|
transform: Transform::from_xyz(0.0, 4.0, 0.0),
|
||||||
|
point_light: PointLight {
|
||||||
|
intensity: 1600.0, // lumens - roughly a 100W non-halogen incandescent bulb
|
||||||
|
color: Color::BLUE,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.with_children(|builder| {
|
||||||
|
builder.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::UVSphere {
|
||||||
|
radius: 0.1,
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::BLUE,
|
||||||
|
emissive: Color::rgba_linear(0.0, 0.0, 100.0, 0.0),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// directional 'sun' light
|
||||||
|
const HALF_SIZE: f32 = 10.0;
|
||||||
|
commands.spawn_bundle(DirectionalLightBundle {
|
||||||
|
directional_light: DirectionalLight {
|
||||||
|
// Configure the projection to better fit the scene
|
||||||
|
shadow_projection: OrthographicProjection {
|
||||||
|
left: -HALF_SIZE,
|
||||||
|
right: HALF_SIZE,
|
||||||
|
bottom: -HALF_SIZE,
|
||||||
|
top: HALF_SIZE,
|
||||||
|
near: -10.0 * HALF_SIZE,
|
||||||
|
far: 10.0 * HALF_SIZE,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
transform: Transform {
|
||||||
|
translation: Vec3::new(0.0, 2.0, 0.0),
|
||||||
|
rotation: Quat::from_rotation_x(-std::f32::consts::FRAC_PI_4),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// camera
|
||||||
|
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||||
|
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn animate_light_direction(
|
||||||
|
time: Res<Time>,
|
||||||
|
mut query: Query<&mut Transform, With<DirectionalLight>>,
|
||||||
|
) {
|
||||||
|
for mut transform in query.iter_mut() {
|
||||||
|
transform.rotate(Quat::from_rotation_y(time.delta_seconds() * 0.5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn movement(
|
||||||
|
input: Res<Input<KeyCode>>,
|
||||||
|
time: Res<Time>,
|
||||||
|
mut query: Query<&mut Transform, With<Movable>>,
|
||||||
|
) {
|
||||||
|
for mut transform in query.iter_mut() {
|
||||||
|
let mut direction = Vec3::ZERO;
|
||||||
|
if input.pressed(KeyCode::Up) {
|
||||||
|
direction.y += 1.0;
|
||||||
|
}
|
||||||
|
if input.pressed(KeyCode::Down) {
|
||||||
|
direction.y -= 1.0;
|
||||||
|
}
|
||||||
|
if input.pressed(KeyCode::Left) {
|
||||||
|
direction.x -= 1.0;
|
||||||
|
}
|
||||||
|
if input.pressed(KeyCode::Right) {
|
||||||
|
direction.x += 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
transform.translation += time.delta_seconds() * 2.0 * direction;
|
||||||
|
}
|
||||||
|
}
|
172
examples/3d/cornell_box_pipelined.rs
Normal file
172
examples/3d/cornell_box_pipelined.rs
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
use bevy::{
|
||||||
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||||
|
ecs::prelude::*,
|
||||||
|
math::{Mat4, Quat, Vec3},
|
||||||
|
pbr2::{
|
||||||
|
AmbientLight, DirectionalLight, DirectionalLightBundle, PbrBundle, PointLight,
|
||||||
|
PointLightBundle, StandardMaterial,
|
||||||
|
},
|
||||||
|
prelude::{App, Assets, BuildChildren, Transform},
|
||||||
|
render2::{
|
||||||
|
camera::{OrthographicProjection, PerspectiveCameraBundle},
|
||||||
|
color::Color,
|
||||||
|
mesh::{shape, Mesh},
|
||||||
|
},
|
||||||
|
PipelinedDefaultPlugins,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(PipelinedDefaultPlugins)
|
||||||
|
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||||
|
.add_plugin(LogDiagnosticsPlugin::default())
|
||||||
|
.add_startup_system(setup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set up a simple 3D scene
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
let box_size = 2.0;
|
||||||
|
let box_thickness = 0.15;
|
||||||
|
let box_offset = (box_size + box_thickness) / 2.0;
|
||||||
|
|
||||||
|
// left - red
|
||||||
|
let mut transform = Transform::from_xyz(-box_offset, box_offset, 0.0);
|
||||||
|
transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2));
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Box::new(
|
||||||
|
box_size,
|
||||||
|
box_thickness,
|
||||||
|
box_size,
|
||||||
|
))),
|
||||||
|
transform,
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::rgb(0.63, 0.065, 0.05),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// right - green
|
||||||
|
let mut transform = Transform::from_xyz(box_offset, box_offset, 0.0);
|
||||||
|
transform.rotate(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2));
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Box::new(
|
||||||
|
box_size,
|
||||||
|
box_thickness,
|
||||||
|
box_size,
|
||||||
|
))),
|
||||||
|
transform,
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::rgb(0.14, 0.45, 0.091),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// bottom - white
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Box::new(
|
||||||
|
box_size + 2.0 * box_thickness,
|
||||||
|
box_thickness,
|
||||||
|
box_size,
|
||||||
|
))),
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::rgb(0.725, 0.71, 0.68),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// top - white
|
||||||
|
let transform = Transform::from_xyz(0.0, 2.0 * box_offset, 0.0);
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Box::new(
|
||||||
|
box_size + 2.0 * box_thickness,
|
||||||
|
box_thickness,
|
||||||
|
box_size,
|
||||||
|
))),
|
||||||
|
transform,
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::rgb(0.725, 0.71, 0.68),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// back - white
|
||||||
|
let mut transform = Transform::from_xyz(0.0, box_offset, -box_offset);
|
||||||
|
transform.rotate(Quat::from_rotation_x(std::f32::consts::FRAC_PI_2));
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Box::new(
|
||||||
|
box_size + 2.0 * box_thickness,
|
||||||
|
box_thickness,
|
||||||
|
box_size + 2.0 * box_thickness,
|
||||||
|
))),
|
||||||
|
transform,
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::rgb(0.725, 0.71, 0.68),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// ambient light
|
||||||
|
commands.insert_resource(AmbientLight {
|
||||||
|
color: Color::WHITE,
|
||||||
|
brightness: 0.02,
|
||||||
|
});
|
||||||
|
// top light
|
||||||
|
commands
|
||||||
|
.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Plane { size: 0.4 })),
|
||||||
|
transform: Transform::from_matrix(Mat4::from_scale_rotation_translation(
|
||||||
|
Vec3::ONE,
|
||||||
|
Quat::from_rotation_x(std::f32::consts::PI),
|
||||||
|
Vec3::new(0.0, box_size + 0.5 * box_thickness, 0.0),
|
||||||
|
)),
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::WHITE,
|
||||||
|
emissive: Color::WHITE * 100.0,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.with_children(|builder| {
|
||||||
|
builder.spawn_bundle(PointLightBundle {
|
||||||
|
point_light: PointLight {
|
||||||
|
color: Color::WHITE,
|
||||||
|
intensity: 25.0,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
transform: Transform::from_translation((box_thickness + 0.05) * Vec3::Y),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// directional light
|
||||||
|
const HALF_SIZE: f32 = 10.0;
|
||||||
|
commands.spawn_bundle(DirectionalLightBundle {
|
||||||
|
directional_light: DirectionalLight {
|
||||||
|
illuminance: 10000.0,
|
||||||
|
shadow_projection: OrthographicProjection {
|
||||||
|
left: -HALF_SIZE,
|
||||||
|
right: HALF_SIZE,
|
||||||
|
bottom: -HALF_SIZE,
|
||||||
|
top: HALF_SIZE,
|
||||||
|
near: -10.0 * HALF_SIZE,
|
||||||
|
far: 10.0 * HALF_SIZE,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::PI / 2.0)),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// camera
|
||||||
|
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||||
|
transform: Transform::from_xyz(0.0, box_offset, 4.0)
|
||||||
|
.looking_at(Vec3::new(0.0, box_offset, 0.0), Vec3::Y),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
62
examples/3d/load_gltf_pipelined.rs
Normal file
62
examples/3d/load_gltf_pipelined.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
use bevy::{
|
||||||
|
core::Time,
|
||||||
|
ecs::prelude::*,
|
||||||
|
math::{EulerRot, Quat, Vec3},
|
||||||
|
pbr2::{AmbientLight, DirectionalLight, DirectionalLightBundle},
|
||||||
|
prelude::{App, AssetServer, SpawnSceneCommands, Transform},
|
||||||
|
render2::{
|
||||||
|
camera::{OrthographicProjection, PerspectiveCameraBundle},
|
||||||
|
color::Color,
|
||||||
|
},
|
||||||
|
PipelinedDefaultPlugins,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.insert_resource(AmbientLight {
|
||||||
|
color: Color::WHITE,
|
||||||
|
brightness: 1.0 / 5.0f32,
|
||||||
|
})
|
||||||
|
.add_plugins(PipelinedDefaultPlugins)
|
||||||
|
.add_startup_system(setup.system())
|
||||||
|
.add_system(animate_light_direction.system())
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
commands.spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"));
|
||||||
|
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||||
|
transform: Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
const HALF_SIZE: f32 = 1.0;
|
||||||
|
commands.spawn_bundle(DirectionalLightBundle {
|
||||||
|
directional_light: DirectionalLight {
|
||||||
|
shadow_projection: OrthographicProjection {
|
||||||
|
left: -HALF_SIZE,
|
||||||
|
right: HALF_SIZE,
|
||||||
|
bottom: -HALF_SIZE,
|
||||||
|
top: HALF_SIZE,
|
||||||
|
near: -10.0 * HALF_SIZE,
|
||||||
|
far: 10.0 * HALF_SIZE,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn animate_light_direction(
|
||||||
|
time: Res<Time>,
|
||||||
|
mut query: Query<&mut Transform, With<DirectionalLight>>,
|
||||||
|
) {
|
||||||
|
for mut transform in query.iter_mut() {
|
||||||
|
transform.rotation = Quat::from_euler(
|
||||||
|
EulerRot::ZYX,
|
||||||
|
0.0,
|
||||||
|
time.seconds_since_startup() as f32 * std::f32::consts::TAU / 10.0,
|
||||||
|
-std::f32::consts::FRAC_PI_4,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
50
examples/3d/many_cubes_pipelined.rs
Normal file
50
examples/3d/many_cubes_pipelined.rs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
use bevy::{
|
||||||
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||||
|
ecs::prelude::*,
|
||||||
|
pbr2::{PbrBundle, StandardMaterial},
|
||||||
|
prelude::{App, Assets, Transform},
|
||||||
|
render2::{
|
||||||
|
camera::PerspectiveCameraBundle,
|
||||||
|
color::Color,
|
||||||
|
mesh::{shape, Mesh},
|
||||||
|
},
|
||||||
|
PipelinedDefaultPlugins,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(PipelinedDefaultPlugins)
|
||||||
|
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||||
|
.add_plugin(LogDiagnosticsPlugin::default())
|
||||||
|
.add_startup_system(setup.system())
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
const WIDTH: usize = 100;
|
||||||
|
const HEIGHT: usize = 100;
|
||||||
|
for x in 0..WIDTH {
|
||||||
|
for y in 0..HEIGHT {
|
||||||
|
// cube
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::PINK,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
transform: Transform::from_xyz((x as f32) * 2.0, (y as f32) * 2.0, 0.0),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// camera
|
||||||
|
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||||
|
transform: Transform::from_xyz(80.0, 80.0, 300.0),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
68
examples/3d/msaa_pipelined.rs
Normal file
68
examples/3d/msaa_pipelined.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
use bevy::{
|
||||||
|
ecs::prelude::*,
|
||||||
|
input::Input,
|
||||||
|
math::Vec3,
|
||||||
|
pbr2::{PbrBundle, PointLightBundle, StandardMaterial},
|
||||||
|
prelude::{App, Assets, KeyCode, Transform},
|
||||||
|
render2::{
|
||||||
|
camera::PerspectiveCameraBundle,
|
||||||
|
color::Color,
|
||||||
|
mesh::{shape, Mesh},
|
||||||
|
view::Msaa,
|
||||||
|
},
|
||||||
|
PipelinedDefaultPlugins,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// This example shows how to configure Multi-Sample Anti-Aliasing. Setting the sample count higher
|
||||||
|
/// will result in smoother edges, but it will also increase the cost to render those edges. The
|
||||||
|
/// range should generally be somewhere between 1 (no multi sampling, but cheap) to 8 (crisp but
|
||||||
|
/// expensive).
|
||||||
|
/// Note that WGPU currently only supports 1 or 4 samples.
|
||||||
|
/// Ultimately we plan on supporting whatever is natively supported on a given device.
|
||||||
|
/// Check out this issue for more info: https://github.com/gfx-rs/wgpu/issues/1832
|
||||||
|
fn main() {
|
||||||
|
println!("Press 'm' to toggle MSAA");
|
||||||
|
println!("Using 4x MSAA");
|
||||||
|
App::new()
|
||||||
|
.insert_resource(Msaa { samples: 4 })
|
||||||
|
.add_plugins(PipelinedDefaultPlugins)
|
||||||
|
.add_startup_system(setup)
|
||||||
|
.add_system(cycle_msaa)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set up a simple 3D scene
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
// cube
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Cube { size: 2.0 })),
|
||||||
|
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// light
|
||||||
|
commands.spawn_bundle(PointLightBundle {
|
||||||
|
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// camera
|
||||||
|
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||||
|
transform: Transform::from_xyz(-3.0, 3.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cycle_msaa(input: Res<Input<KeyCode>>, mut msaa: ResMut<Msaa>) {
|
||||||
|
if input.just_pressed(KeyCode::M) {
|
||||||
|
if msaa.samples == 4 {
|
||||||
|
println!("Not using MSAA");
|
||||||
|
msaa.samples = 1;
|
||||||
|
} else {
|
||||||
|
println!("Using 4x MSAA");
|
||||||
|
msaa.samples = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
examples/3d/orthographic_pipelined.rs
Normal file
71
examples/3d/orthographic_pipelined.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
use bevy::{
|
||||||
|
ecs::prelude::*,
|
||||||
|
math::Vec3,
|
||||||
|
pbr2::{PbrBundle, PointLightBundle, StandardMaterial},
|
||||||
|
prelude::{App, Assets, Transform},
|
||||||
|
render2::{
|
||||||
|
camera::OrthographicCameraBundle,
|
||||||
|
color::Color,
|
||||||
|
mesh::{shape, Mesh},
|
||||||
|
},
|
||||||
|
PipelinedDefaultPlugins,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(PipelinedDefaultPlugins)
|
||||||
|
.add_startup_system(setup.system())
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set up a simple 3D scene
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
// set up the camera
|
||||||
|
let mut camera = OrthographicCameraBundle::new_3d();
|
||||||
|
camera.orthographic_projection.scale = 3.0;
|
||||||
|
camera.transform = Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y);
|
||||||
|
|
||||||
|
// camera
|
||||||
|
commands.spawn_bundle(camera);
|
||||||
|
|
||||||
|
// plane
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })),
|
||||||
|
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// cubes
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||||
|
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
|
||||||
|
transform: Transform::from_xyz(1.5, 0.5, 1.5),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||||
|
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
|
||||||
|
transform: Transform::from_xyz(1.5, 0.5, -1.5),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||||
|
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
|
||||||
|
transform: Transform::from_xyz(-1.5, 0.5, 1.5),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||||
|
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
|
||||||
|
transform: Transform::from_xyz(-1.5, 0.5, -1.5),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// light
|
||||||
|
commands.spawn_bundle(PointLightBundle {
|
||||||
|
transform: Transform::from_xyz(3.0, 8.0, 5.0),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
86
examples/3d/pbr_pipelined.rs
Normal file
86
examples/3d/pbr_pipelined.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
use bevy::{
|
||||||
|
ecs::prelude::*,
|
||||||
|
math::Vec3,
|
||||||
|
pbr2::{PbrBundle, PointLight, PointLightBundle, StandardMaterial},
|
||||||
|
prelude::{App, Assets, Transform},
|
||||||
|
render2::{
|
||||||
|
camera::{OrthographicCameraBundle, OrthographicProjection},
|
||||||
|
color::Color,
|
||||||
|
mesh::{shape, Mesh},
|
||||||
|
},
|
||||||
|
PipelinedDefaultPlugins,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// This example shows how to configure Physically Based Rendering (PBR) parameters.
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(PipelinedDefaultPlugins)
|
||||||
|
.add_startup_system(setup.system())
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set up a simple 3D scene
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
// add entities to the world
|
||||||
|
for y in -2..=2 {
|
||||||
|
for x in -5..=5 {
|
||||||
|
let x01 = (x + 5) as f32 / 10.0;
|
||||||
|
let y01 = (y + 2) as f32 / 4.0;
|
||||||
|
// sphere
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Icosphere {
|
||||||
|
radius: 0.45,
|
||||||
|
subdivisions: 32,
|
||||||
|
})),
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::hex("ffd891").unwrap(),
|
||||||
|
// vary key PBR parameters on a grid of spheres to show the effect
|
||||||
|
metallic: y01,
|
||||||
|
perceptual_roughness: x01,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
transform: Transform::from_xyz(x as f32, y as f32 + 0.5, 0.0),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// unlit sphere
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Icosphere {
|
||||||
|
radius: 0.45,
|
||||||
|
subdivisions: 32,
|
||||||
|
})),
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::hex("ffd891").unwrap(),
|
||||||
|
// vary key PBR parameters on a grid of spheres to show the effect
|
||||||
|
unlit: true,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
transform: Transform::from_xyz(-5.0, -2.5, 0.0),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// light
|
||||||
|
commands.spawn_bundle(PointLightBundle {
|
||||||
|
transform: Transform::from_translation(Vec3::new(50.0, 50.0, 50.0)),
|
||||||
|
point_light: PointLight {
|
||||||
|
intensity: 50000.,
|
||||||
|
range: 100.,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
// camera
|
||||||
|
commands.spawn_bundle(OrthographicCameraBundle {
|
||||||
|
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 8.0))
|
||||||
|
.looking_at(Vec3::default(), Vec3::Y),
|
||||||
|
orthographic_projection: OrthographicProjection {
|
||||||
|
scale: 0.01,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..OrthographicCameraBundle::new_3d()
|
||||||
|
});
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ use bevy::{
|
||||||
},
|
},
|
||||||
texture::{
|
texture::{
|
||||||
Extent3d, SamplerDescriptor, TextureDescriptor, TextureDimension, TextureFormat,
|
Extent3d, SamplerDescriptor, TextureDescriptor, TextureDimension, TextureFormat,
|
||||||
TextureUsage,
|
TextureUsages,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
window::WindowId,
|
window::WindowId,
|
||||||
|
@ -66,7 +66,7 @@ fn add_render_to_texture_graph(graph: &mut RenderGraph, size: Extent3d) {
|
||||||
sample_count: 1,
|
sample_count: 1,
|
||||||
dimension: TextureDimension::D2,
|
dimension: TextureDimension::D2,
|
||||||
format: Default::default(),
|
format: Default::default(),
|
||||||
usage: TextureUsage::OUTPUT_ATTACHMENT | TextureUsage::SAMPLED,
|
usage: TextureUsages::OUTPUT_ATTACHMENT | TextureUsages::SAMPLED,
|
||||||
},
|
},
|
||||||
Some(SamplerDescriptor::default()),
|
Some(SamplerDescriptor::default()),
|
||||||
Some(RENDER_TEXTURE_HANDLE),
|
Some(RENDER_TEXTURE_HANDLE),
|
||||||
|
@ -82,7 +82,7 @@ fn add_render_to_texture_graph(graph: &mut RenderGraph, size: Extent3d) {
|
||||||
sample_count: 1,
|
sample_count: 1,
|
||||||
dimension: TextureDimension::D2,
|
dimension: TextureDimension::D2,
|
||||||
format: TextureFormat::Depth32Float,
|
format: TextureFormat::Depth32Float,
|
||||||
usage: TextureUsage::OUTPUT_ATTACHMENT | TextureUsage::SAMPLED,
|
usage: TextureUsages::OUTPUT_ATTACHMENT | TextureUsages::SAMPLED,
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
335
examples/3d/shadow_biases_pipelined.rs
Normal file
335
examples/3d/shadow_biases_pipelined.rs
Normal file
|
@ -0,0 +1,335 @@
|
||||||
|
use bevy::{
|
||||||
|
core::Time,
|
||||||
|
ecs::prelude::*,
|
||||||
|
input::{mouse::MouseMotion, Input},
|
||||||
|
math::{EulerRot, Mat4, Quat, Vec2, Vec3},
|
||||||
|
pbr2::{
|
||||||
|
DirectionalLight, DirectionalLightBundle, PbrBundle, PointLight, PointLightBundle,
|
||||||
|
StandardMaterial,
|
||||||
|
},
|
||||||
|
prelude::{App, Assets, KeyCode, Transform},
|
||||||
|
render2::{
|
||||||
|
camera::{Camera, OrthographicProjection, PerspectiveCameraBundle},
|
||||||
|
color::Color,
|
||||||
|
mesh::{shape, Mesh},
|
||||||
|
},
|
||||||
|
PipelinedDefaultPlugins,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!(
|
||||||
|
"Controls:
|
||||||
|
WSAD - forward/back/strafe left/right
|
||||||
|
LShift - 'run'
|
||||||
|
E - up
|
||||||
|
Q - down
|
||||||
|
L - switch between directional and point lights
|
||||||
|
1/2 - decrease/increase point light depth bias
|
||||||
|
3/4 - decrease/increase point light normal bias
|
||||||
|
5/6 - decrease/increase direction light depth bias
|
||||||
|
7/8 - decrease/increase direction light normal bias"
|
||||||
|
);
|
||||||
|
App::new()
|
||||||
|
.add_plugins(PipelinedDefaultPlugins)
|
||||||
|
.add_startup_system(setup)
|
||||||
|
.add_system(adjust_point_light_biases)
|
||||||
|
.add_system(toggle_light)
|
||||||
|
.add_system(adjust_directional_light_biases)
|
||||||
|
.add_system(camera_controller)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set up a 3D scene to test shadow biases and perspective projections
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
let spawn_plane_depth = 500.0f32;
|
||||||
|
let spawn_height = 2.0;
|
||||||
|
let sphere_radius = 0.25;
|
||||||
|
|
||||||
|
let white_handle = materials.add(StandardMaterial {
|
||||||
|
base_color: Color::WHITE,
|
||||||
|
perceptual_roughness: 1.0,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let sphere_handle = meshes.add(Mesh::from(shape::Icosphere {
|
||||||
|
radius: sphere_radius,
|
||||||
|
..Default::default()
|
||||||
|
}));
|
||||||
|
|
||||||
|
println!("Using DirectionalLight");
|
||||||
|
|
||||||
|
commands.spawn_bundle(PointLightBundle {
|
||||||
|
transform: Transform::from_xyz(5.0, 5.0, 0.0),
|
||||||
|
point_light: PointLight {
|
||||||
|
intensity: 0.0,
|
||||||
|
range: spawn_plane_depth,
|
||||||
|
color: Color::WHITE,
|
||||||
|
shadow_depth_bias: 0.0,
|
||||||
|
shadow_normal_bias: 0.0,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let theta = std::f32::consts::FRAC_PI_4;
|
||||||
|
let light_transform = Mat4::from_euler(EulerRot::ZYX, 0.0, std::f32::consts::FRAC_PI_2, -theta);
|
||||||
|
commands.spawn_bundle(DirectionalLightBundle {
|
||||||
|
directional_light: DirectionalLight {
|
||||||
|
illuminance: 100000.0,
|
||||||
|
shadow_projection: OrthographicProjection {
|
||||||
|
left: -0.35,
|
||||||
|
right: 500.35,
|
||||||
|
bottom: -0.1,
|
||||||
|
top: 5.0,
|
||||||
|
near: -5.0,
|
||||||
|
far: 5.0,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
shadow_depth_bias: 0.0,
|
||||||
|
shadow_normal_bias: 0.0,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
transform: Transform::from_matrix(light_transform),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// camera
|
||||||
|
commands
|
||||||
|
.spawn_bundle(PerspectiveCameraBundle {
|
||||||
|
transform: Transform::from_xyz(-1.0, 1.0, 1.0)
|
||||||
|
.looking_at(Vec3::new(-1.0, 1.0, 0.0), Vec3::Y),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.insert(CameraController::default());
|
||||||
|
|
||||||
|
for z_i32 in -spawn_plane_depth as i32..=0 {
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: sphere_handle.clone(),
|
||||||
|
material: white_handle.clone(),
|
||||||
|
transform: Transform::from_xyz(0.0, spawn_height, z_i32 as f32),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ground plane
|
||||||
|
commands.spawn_bundle(PbrBundle {
|
||||||
|
mesh: meshes.add(Mesh::from(shape::Plane {
|
||||||
|
size: 2.0 * spawn_plane_depth,
|
||||||
|
})),
|
||||||
|
material: white_handle,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_light(
|
||||||
|
input: Res<Input<KeyCode>>,
|
||||||
|
mut point_lights: Query<&mut PointLight>,
|
||||||
|
mut directional_lights: Query<&mut DirectionalLight>,
|
||||||
|
) {
|
||||||
|
if input.just_pressed(KeyCode::L) {
|
||||||
|
for mut light in point_lights.iter_mut() {
|
||||||
|
light.intensity = if light.intensity == 0.0 {
|
||||||
|
println!("Using PointLight");
|
||||||
|
100000000.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for mut light in directional_lights.iter_mut() {
|
||||||
|
light.illuminance = if light.illuminance == 0.0 {
|
||||||
|
println!("Using DirectionalLight");
|
||||||
|
100000.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adjust_point_light_biases(input: Res<Input<KeyCode>>, mut query: Query<&mut PointLight>) {
|
||||||
|
let depth_bias_step_size = 0.01;
|
||||||
|
let normal_bias_step_size = 0.1;
|
||||||
|
for mut light in query.iter_mut() {
|
||||||
|
if input.just_pressed(KeyCode::Key1) {
|
||||||
|
light.shadow_depth_bias -= depth_bias_step_size;
|
||||||
|
println!("PointLight shadow_depth_bias: {}", light.shadow_depth_bias);
|
||||||
|
}
|
||||||
|
if input.just_pressed(KeyCode::Key2) {
|
||||||
|
light.shadow_depth_bias += depth_bias_step_size;
|
||||||
|
println!("PointLight shadow_depth_bias: {}", light.shadow_depth_bias);
|
||||||
|
}
|
||||||
|
if input.just_pressed(KeyCode::Key3) {
|
||||||
|
light.shadow_normal_bias -= normal_bias_step_size;
|
||||||
|
println!(
|
||||||
|
"PointLight shadow_normal_bias: {}",
|
||||||
|
light.shadow_normal_bias
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if input.just_pressed(KeyCode::Key4) {
|
||||||
|
light.shadow_normal_bias += normal_bias_step_size;
|
||||||
|
println!(
|
||||||
|
"PointLight shadow_normal_bias: {}",
|
||||||
|
light.shadow_normal_bias
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adjust_directional_light_biases(
|
||||||
|
input: Res<Input<KeyCode>>,
|
||||||
|
mut query: Query<&mut DirectionalLight>,
|
||||||
|
) {
|
||||||
|
let depth_bias_step_size = 0.01;
|
||||||
|
let normal_bias_step_size = 0.1;
|
||||||
|
for mut light in query.iter_mut() {
|
||||||
|
if input.just_pressed(KeyCode::Key5) {
|
||||||
|
light.shadow_depth_bias -= depth_bias_step_size;
|
||||||
|
println!(
|
||||||
|
"DirectionalLight shadow_depth_bias: {}",
|
||||||
|
light.shadow_depth_bias
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if input.just_pressed(KeyCode::Key6) {
|
||||||
|
light.shadow_depth_bias += depth_bias_step_size;
|
||||||
|
println!(
|
||||||
|
"DirectionalLight shadow_depth_bias: {}",
|
||||||
|
light.shadow_depth_bias
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if input.just_pressed(KeyCode::Key7) {
|
||||||
|
light.shadow_normal_bias -= normal_bias_step_size;
|
||||||
|
println!(
|
||||||
|
"DirectionalLight shadow_normal_bias: {}",
|
||||||
|
light.shadow_normal_bias
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if input.just_pressed(KeyCode::Key8) {
|
||||||
|
light.shadow_normal_bias += normal_bias_step_size;
|
||||||
|
println!(
|
||||||
|
"DirectionalLight shadow_normal_bias: {}",
|
||||||
|
light.shadow_normal_bias
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct CameraController {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub sensitivity: f32,
|
||||||
|
pub key_forward: KeyCode,
|
||||||
|
pub key_back: KeyCode,
|
||||||
|
pub key_left: KeyCode,
|
||||||
|
pub key_right: KeyCode,
|
||||||
|
pub key_up: KeyCode,
|
||||||
|
pub key_down: KeyCode,
|
||||||
|
pub key_run: KeyCode,
|
||||||
|
pub walk_speed: f32,
|
||||||
|
pub run_speed: f32,
|
||||||
|
pub friction: f32,
|
||||||
|
pub pitch: f32,
|
||||||
|
pub yaw: f32,
|
||||||
|
pub velocity: Vec3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CameraController {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: true,
|
||||||
|
sensitivity: 0.5,
|
||||||
|
key_forward: KeyCode::W,
|
||||||
|
key_back: KeyCode::S,
|
||||||
|
key_left: KeyCode::A,
|
||||||
|
key_right: KeyCode::D,
|
||||||
|
key_up: KeyCode::E,
|
||||||
|
key_down: KeyCode::Q,
|
||||||
|
key_run: KeyCode::LShift,
|
||||||
|
walk_speed: 10.0,
|
||||||
|
run_speed: 30.0,
|
||||||
|
friction: 0.5,
|
||||||
|
pitch: 0.0,
|
||||||
|
yaw: 0.0,
|
||||||
|
velocity: Vec3::ZERO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn camera_controller(
|
||||||
|
time: Res<Time>,
|
||||||
|
mut mouse_events: EventReader<MouseMotion>,
|
||||||
|
key_input: Res<Input<KeyCode>>,
|
||||||
|
mut query: Query<(&mut Transform, &mut CameraController), With<Camera>>,
|
||||||
|
) {
|
||||||
|
let dt = time.delta_seconds();
|
||||||
|
|
||||||
|
// Handle mouse input
|
||||||
|
let mut mouse_delta = Vec2::ZERO;
|
||||||
|
for mouse_event in mouse_events.iter() {
|
||||||
|
mouse_delta += mouse_event.delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (mut transform, mut options) in query.iter_mut() {
|
||||||
|
if !options.enabled {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle key input
|
||||||
|
let mut axis_input = Vec3::ZERO;
|
||||||
|
if key_input.pressed(options.key_forward) {
|
||||||
|
axis_input.z += 1.0;
|
||||||
|
}
|
||||||
|
if key_input.pressed(options.key_back) {
|
||||||
|
axis_input.z -= 1.0;
|
||||||
|
}
|
||||||
|
if key_input.pressed(options.key_right) {
|
||||||
|
axis_input.x += 1.0;
|
||||||
|
}
|
||||||
|
if key_input.pressed(options.key_left) {
|
||||||
|
axis_input.x -= 1.0;
|
||||||
|
}
|
||||||
|
if key_input.pressed(options.key_up) {
|
||||||
|
axis_input.y += 1.0;
|
||||||
|
}
|
||||||
|
if key_input.pressed(options.key_down) {
|
||||||
|
axis_input.y -= 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply movement update
|
||||||
|
if axis_input != Vec3::ZERO {
|
||||||
|
let max_speed = if key_input.pressed(options.key_run) {
|
||||||
|
options.run_speed
|
||||||
|
} else {
|
||||||
|
options.walk_speed
|
||||||
|
};
|
||||||
|
options.velocity = axis_input.normalize() * max_speed;
|
||||||
|
} else {
|
||||||
|
let friction = options.friction.clamp(0.0, 1.0);
|
||||||
|
options.velocity *= 1.0 - friction;
|
||||||
|
if options.velocity.length_squared() < 1e-6 {
|
||||||
|
options.velocity = Vec3::ZERO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let forward = transform.forward();
|
||||||
|
let right = transform.right();
|
||||||
|
transform.translation += options.velocity.x * dt * right
|
||||||
|
+ options.velocity.y * dt * Vec3::Y
|
||||||
|
+ options.velocity.z * dt * forward;
|
||||||
|
|
||||||
|
if mouse_delta != Vec2::ZERO {
|
||||||
|
// Apply look update
|
||||||
|
let (pitch, yaw) = (
|
||||||
|
(options.pitch - mouse_delta.y * 0.5 * options.sensitivity * dt).clamp(
|
||||||
|
-0.99 * std::f32::consts::FRAC_PI_2,
|
||||||
|
0.99 * std::f32::consts::FRAC_PI_2,
|
||||||
|
),
|
||||||
|
options.yaw - mouse_delta.x * options.sensitivity * dt,
|
||||||
|
);
|
||||||
|
transform.rotation = Quat::from_euler(EulerRot::ZYX, 0.0, yaw, pitch);
|
||||||
|
options.pitch = pitch;
|
||||||
|
options.yaw = yaw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue