Merge branch 'main' into add-transform-lerp

This commit is contained in:
Sigma-dev 2024-11-09 12:21:48 +01:00 committed by GitHub
commit 6e84ad5a92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
141 changed files with 1069 additions and 814 deletions

View file

@ -2,7 +2,7 @@
name: Performance Regression
about: Bevy running slowly after upgrading? Report a performance regression.
title: ''
labels: C-Bug, C-Performance, C-Regression, S-Needs-Triage
labels: C-Bug, C-Performance, P-Regression, S-Needs-Triage
assignees: ''
---

View file

@ -243,7 +243,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Check for typos
uses: crate-ci/typos@v1.26.8
uses: crate-ci/typos@v1.27.0
- name: Typos info
if: failure()
run: |
@ -316,7 +316,9 @@ jobs:
~/.cargo/git/db/
target/
key: ${{ runner.os }}-check-doc-${{ hashFiles('**/Cargo.toml') }}
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.NIGHTLY_TOOLCHAIN }}
- name: Install Linux dependencies
uses: ./.github/actions/install-linux-deps
with:
@ -327,7 +329,7 @@ jobs:
run: cargo run -p ci -- doc
env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0"
RUSTFLAGS: "-C debuginfo=0 --cfg docsrs_dep"
# This currently report a lot of false positives
# Enable it again once it's fixed - https://github.com/bevyengine/bevy/issues/1983
# - name: Installs cargo-deadlinks

View file

@ -244,7 +244,7 @@ jobs:
- name: First Wasm build
run: |
cargo build --release --example ui --target wasm32-unknown-unknown
cargo build --release --example testbed_ui --target wasm32-unknown-unknown
- name: Run examples
shell: bash

View file

@ -75,7 +75,7 @@ jobs:
run: cargo run -p ci -- doc
env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0"
RUSTFLAGS: "-C debuginfo=0 --cfg docsrs_dep"
open-issue:
name: Warn that weekly CI fails

View file

@ -474,6 +474,7 @@ hyper = { version = "1", features = ["server", "http1"] }
http-body-util = "0.1"
anyhow = "1"
macro_rules_attribute = "0.2"
accesskit = "0.17"
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
smol = "2"
@ -1216,7 +1217,7 @@ setup = [
"curl",
"-o",
"assets/models/bunny.meshlet_mesh",
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/8483db58832542383820c3f44e4730e566910be7/bunny.meshlet_mesh",
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/defbd9b32072624d40d57de7d345c66a9edf5d0b/bunny.meshlet_mesh",
],
]
@ -3136,17 +3137,6 @@ description = "Demonstrates how to control the relative depth (z-position) of UI
category = "UI (User Interface)"
wasm = true
[[example]]
name = "ui"
path = "examples/ui/ui.rs"
doc-scrape-examples = true
[package.metadata.example.ui]
name = "UI"
description = "Illustrates various features of Bevy UI"
category = "UI (User Interface)"
wasm = true
[[example]]
name = "ui_scaling"
path = "examples/ui/ui_scaling.rs"
@ -3857,6 +3847,13 @@ doc-scrape-examples = true
[package.metadata.example.testbed_3d]
hidden = true
[[example]]
name = "testbed_ui"
path = "examples/testbed/ui.rs"
doc-scrape-examples = true
[package.metadata.example.testbed_ui]
hidden = true
[[example]]
name = "testbed_ui_layout_rounding"

View file

@ -1,3 +1,5 @@
#![expect(dead_code, reason = "Many fields are unused/unread as they are just for benchmarking purposes.")]
use criterion::criterion_main;
mod components;

View file

@ -1,6 +1,6 @@
use bevy::prelude::*;
use bevy_ecs::prelude::*;
#[derive(Component)]
#[derive(Component, Clone)]
struct A(f32);
#[derive(Component)]
struct B(f32);
@ -12,19 +12,18 @@ impl Benchmark {
let mut world = World::default();
let entities = world
.spawn_batch((0..10000).map(|_| A(0.0)))
.collect::<Vec<_>>();
.spawn_batch(core::iter::repeat(A(0.)).take(10000))
.collect();
Self(world, entities)
}
pub fn run(&mut self) {
for entity in &self.1 {
self.0.insert_one(*entity, B(0.0)).unwrap();
self.0.entity_mut(*entity).insert(B(0.));
}
for entity in &self.1 {
self.0.remove_one::<B>(*entity).unwrap();
self.0.entity_mut(*entity).remove::<B>();
}
}
}

View file

@ -5,6 +5,7 @@ mod add_remove_big_table;
mod add_remove_sparse_set;
mod add_remove_table;
mod add_remove_very_big_table;
mod add_remove;
mod archetype_updates;
mod insert_simple;
mod insert_simple_unbatched;

View file

@ -15,7 +15,7 @@ bevy_derive = { path = "../bevy_derive", version = "0.15.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev" }
accesskit = "0.16"
accesskit = "0.17"
[lints]
workspace = true

View file

@ -6,14 +6,19 @@
)]
//! Accessibility for Bevy
//!
//! As of Bevy version 0.15 `accesskit` is no longer re-exported from this crate.
//!
//! If you need to use `accesskit`, you will need to add it as a separate dependency in your `Cargo.toml`.
//!
//! Make sure to use the same version of `accesskit` as Bevy.
extern crate alloc;
use alloc::sync::Arc;
use core::sync::atomic::{AtomicBool, Ordering};
pub use accesskit;
use accesskit::NodeBuilder;
use accesskit::Node;
use bevy_app::Plugin;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
@ -84,10 +89,10 @@ impl ManageAccessibilityUpdates {
/// If the entity doesn't have a parent, or if the immediate parent doesn't have
/// an `AccessibilityNode`, its node will be an immediate child of the primary window.
#[derive(Component, Clone, Deref, DerefMut)]
pub struct AccessibilityNode(pub NodeBuilder);
pub struct AccessibilityNode(pub Node);
impl From<NodeBuilder> for AccessibilityNode {
fn from(node: NodeBuilder) -> Self {
impl From<Node> for AccessibilityNode {
fn from(node: Node) -> Self {
Self(node)
}
}

View file

@ -746,7 +746,15 @@ impl WeightsCurveEvaluator {
None => {
self.blend_register_blend_weight = Some(weight_to_blend);
self.blend_register_morph_target_weights.clear();
self.blend_register_morph_target_weights.extend(stack_iter);
// In the additive case, the values pushed onto the blend register need
// to be scaled by the weight.
if additive {
self.blend_register_morph_target_weights
.extend(stack_iter.map(|m| m * weight_to_blend));
} else {
self.blend_register_morph_target_weights.extend(stack_iter);
}
}
Some(ref mut current_weight) => {
@ -877,7 +885,9 @@ where
} = self.stack.pop().unwrap();
match self.blend_register.take() {
None => self.blend_register = Some((value_to_blend, weight_to_blend)),
None => {
self.initialize_blend_register(value_to_blend, weight_to_blend, additive);
}
Some((mut current_value, mut current_weight)) => {
current_weight += weight_to_blend;
@ -912,6 +922,22 @@ where
Ok(())
}
fn initialize_blend_register(&mut self, value: A, weight: f32, additive: bool) {
if additive {
let scaled_value = A::blend(
[BlendInput {
weight,
value,
additive: true,
}]
.into_iter(),
);
self.blend_register = Some((scaled_value, weight));
} else {
self.blend_register = Some((value, weight));
}
}
fn push_blend_register(
&mut self,
weight: f32,

View file

@ -217,9 +217,8 @@ pub enum AnimationNodeType {
/// additively.
///
/// The weights of all the children of this node are *not* normalized to
/// 1.0. Rather, the first child is used as a base, ignoring its weight,
/// while the others are multiplied by their respective weights and then
/// added in sequence to the base.
/// 1.0. Rather, each child is multiplied by its respective weight and
/// added in sequence.
///
/// Add nodes are primarily useful for superimposing an animation for a
/// portion of a rig on top of the main animation. For example, an add node

View file

@ -164,6 +164,12 @@ impl AssetWriter for FileAssetWriter {
Ok(())
}
async fn create_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
async_fs::create_dir_all(full_path).await?;
Ok(())
}
async fn remove_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
async_fs::remove_dir_all(full_path).await?;

View file

@ -205,6 +205,12 @@ impl AssetWriter for FileAssetWriter {
Ok(())
}
async fn create_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
std::fs::create_dir_all(full_path)?;
Ok(())
}
async fn remove_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
std::fs::remove_dir_all(full_path)?;

View file

@ -384,6 +384,12 @@ pub trait AssetWriter: Send + Sync + 'static {
old_path: &'a Path,
new_path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Creates a directory at the given path, including all parent directories if they do not
/// already exist.
fn create_directory<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Removes the directory at the given path, including all assets _and_ directories in that directory.
fn remove_directory<'a>(
&'a self,
@ -460,6 +466,12 @@ pub trait ErasedAssetWriter: Send + Sync + 'static {
old_path: &'a Path,
new_path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
/// Creates a directory at the given path, including all parent directories if they do not
/// already exist.
fn create_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
/// Removes the directory at the given path, including all assets _and_ directories in that directory.
fn remove_directory<'a>(
&'a self,
@ -523,6 +535,12 @@ impl<T: AssetWriter> ErasedAssetWriter for T {
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
Box::pin(Self::rename_meta(self, old_path, new_path))
}
fn create_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
Box::pin(Self::create_directory(self, path))
}
fn remove_directory<'a>(
&'a self,
path: &'a Path,

View file

@ -264,6 +264,17 @@ where
}
}
impl AudioPlayer<AudioSource> {
/// Creates a new [`AudioPlayer`] with the given [`Handle<AudioSource>`].
///
/// For convenience reasons, this hard-codes the [`AudioSource`] type. If you want to
/// initialize an [`AudioPlayer`] with a different type, just initialize it directly using normal
/// tuple struct syntax.
pub fn new(source: Handle<AudioSource>) -> Self {
Self(source)
}
}
/// Bundle for playing a sound.
///
/// Insert this bundle onto an entity to trigger a sound source to begin playing.

View file

@ -21,7 +21,7 @@
//!
//! fn play_background_audio(asset_server: Res<AssetServer>, mut commands: Commands) {
//! commands.spawn((
//! AudioPlayer::<AudioSource>(asset_server.load("background_audio.ogg")),
//! AudioPlayer::new(asset_server.load("background_audio.ogg")),
//! PlaybackSettings::LOOP,
//! ));
//! }

View file

@ -23,7 +23,7 @@ derive_more = { version = "1", default-features = false, features = [
"from",
"display",
] }
wgpu-types = { version = "22", default-features = false, optional = true }
wgpu-types = { version = "23", default-features = false, optional = true }
encase = { version = "0.10", default-features = false }
[features]

View file

@ -20,7 +20,6 @@ bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" }
# other
serde = { version = "1.0", optional = true }
uuid = "1.0"
[features]
default = ["bevy_reflect"]

View file

@ -89,6 +89,7 @@ impl SpecializedComputePipeline for AutoExposurePipeline {
AutoExposurePass::Average => "compute_average".into(),
},
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -98,6 +98,7 @@ impl SpecializedRenderPipeline for BlitPipeline {
..Default::default()
},
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -127,6 +127,7 @@ impl SpecializedRenderPipeline for BloomDownsamplingPipeline {
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -39,7 +39,7 @@ use upsampling_pipeline::{
const BLOOM_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(929599476923908);
const BLOOM_TEXTURE_FORMAT: TextureFormat = TextureFormat::Rg11b10Float;
const BLOOM_TEXTURE_FORMAT: TextureFormat = TextureFormat::Rg11b10Ufloat;
pub struct BloomPlugin;

View file

@ -124,6 +124,7 @@ impl SpecializedRenderPipeline for BloomUpsamplingPipeline {
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -233,6 +233,7 @@ impl SpecializedRenderPipeline for CasPipeline {
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -160,6 +160,7 @@ impl FromWorld for CopyDeferredLightingIdPipeline {
}),
multisample: MultisampleState::default(),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
});
Self {

View file

@ -806,6 +806,7 @@ impl SpecializedRenderPipeline for DepthOfFieldPipeline {
},
targets,
}),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -196,6 +196,7 @@ impl SpecializedRenderPipeline for FxaaPipeline {
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -141,6 +141,7 @@ impl SpecializedRenderPipeline for MotionBlurPipeline {
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -208,6 +208,7 @@ fn specialize_oit_resolve_pipeline(
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}

View file

@ -344,6 +344,7 @@ impl SpecializedRenderPipeline for PostProcessingPipeline {
depth_stencil: None,
multisample: default(),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -233,6 +233,7 @@ impl SpecializedRenderPipeline for SkyboxPipeline {
write_mask: ColorWrites::ALL,
})],
}),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -105,6 +105,7 @@ impl SpecializedRenderPipeline for SkyboxPrepassPipeline {
entry_point: "fragment".into(),
targets: prepass_target_descriptors(key.normal_prepass, true, false),
}),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -512,6 +512,7 @@ impl SpecializedRenderPipeline for SmaaEdgeDetectionPipeline {
bias: default(),
}),
multisample: MultisampleState::default(),
zero_initialize_workgroup_memory: false,
}
}
}
@ -571,6 +572,7 @@ impl SpecializedRenderPipeline for SmaaBlendingWeightCalculationPipeline {
bias: default(),
}),
multisample: MultisampleState::default(),
zero_initialize_workgroup_memory: false,
}
}
}
@ -607,6 +609,7 @@ impl SpecializedRenderPipeline for SmaaNeighborhoodBlendingPipeline {
primitive: PrimitiveState::default(),
depth_stencil: None,
multisample: MultisampleState::default(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -355,6 +355,7 @@ impl SpecializedRenderPipeline for TaaPipeline {
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -307,6 +307,7 @@ impl SpecializedRenderPipeline for TonemappingPipeline {
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -46,28 +46,32 @@ fn prepare_view_upscaling_pipelines(
let mut output_textures = HashSet::new();
for (entity, view_target, camera) in view_targets.iter() {
let out_texture_id = view_target.out_texture().id();
let blend_state = if let Some(ExtractedCamera {
output_mode: CameraOutputMode::Write { blend_state, .. },
..
}) = camera
{
match *blend_state {
None => {
// If we've already seen this output for a camera and it doesn't have a output blend
// mode configured, default to alpha blend so that we don't accidentally overwrite
// the output texture
if output_textures.contains(&out_texture_id) {
Some(BlendState::ALPHA_BLENDING)
} else {
None
let blend_state = if let Some(extracted_camera) = camera {
match extracted_camera.output_mode {
CameraOutputMode::Skip => None,
CameraOutputMode::Write { blend_state, .. } => {
let already_seen = output_textures.contains(&out_texture_id);
output_textures.insert(out_texture_id);
match blend_state {
None => {
// If we've already seen this output for a camera and it doesn't have a output blend
// mode configured, default to alpha blend so that we don't accidentally overwrite
// the output texture
if already_seen {
Some(BlendState::ALPHA_BLENDING)
} else {
None
}
}
_ => blend_state,
}
}
_ => *blend_state,
}
} else {
output_textures.insert(out_texture_id);
None
};
output_textures.insert(out_texture_id);
let key = BlitPipelineKey {
texture_format: view_target.out_texture_format(),

View file

@ -487,7 +487,7 @@ impl ComponentHooks {
/// Will panic if the component already has an `on_add` hook
pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_add(hook)
.expect("Component id: {:?}, already has an on_add hook")
.expect("Component already has an on_add hook")
}
/// Register a [`ComponentHook`] that will be run when this component is added (with `.insert`)
@ -505,7 +505,7 @@ impl ComponentHooks {
/// Will panic if the component already has an `on_insert` hook
pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_insert(hook)
.expect("Component id: {:?}, already has an on_insert hook")
.expect("Component already has an on_insert hook")
}
/// Register a [`ComponentHook`] that will be run when this component is about to be dropped,
@ -527,7 +527,7 @@ impl ComponentHooks {
/// Will panic if the component already has an `on_replace` hook
pub fn on_replace(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_replace(hook)
.expect("Component id: {:?}, already has an on_replace hook")
.expect("Component already has an on_replace hook")
}
/// Register a [`ComponentHook`] that will be run when this component is removed from an entity.
@ -538,7 +538,7 @@ impl ComponentHooks {
/// Will panic if the component already has an `on_remove` hook
pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_remove(hook)
.expect("Component id: {:?}, already has an on_remove hook")
.expect("Component already has an on_remove hook")
}
/// Attempt to register a [`ComponentHook`] that will be run when this component is added to an entity.
@ -1512,8 +1512,11 @@ impl<'a> TickCells<'a> {
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
pub struct ComponentTicks {
pub(crate) added: Tick,
pub(crate) changed: Tick,
/// Tick recording the time this component or resource was added.
pub added: Tick,
/// Tick recording the time this component or resource was most recently changed.
pub changed: Tick,
}
impl ComponentTicks {
@ -1531,19 +1534,8 @@ impl ComponentTicks {
self.changed.is_newer_than(last_run, this_run)
}
/// Returns the tick recording the time this component or resource was most recently changed.
#[inline]
pub fn last_changed_tick(&self) -> Tick {
self.changed
}
/// Returns the tick recording the time this component or resource was added.
#[inline]
pub fn added_tick(&self) -> Tick {
self.added
}
pub(crate) fn new(change_tick: Tick) -> Self {
/// Creates a new instance with the same change tick for `added` and `changed`.
pub fn new(change_tick: Tick) -> Self {
Self {
added: change_tick,
changed: change_tick,

View file

@ -650,6 +650,11 @@ impl ScheduleGraph {
.and_then(|system| system.inner.as_deref())
}
/// Returns `true` if the given system set is part of the graph. Otherwise, returns `false`.
pub fn contains_set(&self, set: impl SystemSet) -> bool {
self.system_set_ids.contains_key(&set.intern())
}
/// Returns the system at the given [`NodeId`].
///
/// Panics if it doesn't exist.
@ -1956,13 +1961,13 @@ pub enum ScheduleBuildError {
#[display("System dependencies contain cycle(s).\n{_0}")]
DependencyCycle(String),
/// Tried to order a system (set) relative to a system set it belongs to.
#[display("`{0}` and `{_1}` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.")]
#[display("`{_0}` and `{_1}` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.")]
CrossDependency(String, String),
/// Tried to order system sets that share systems.
#[display("`{0}` and `{_1}` have a `before`-`after` relationship (which may be transitive) but share systems.")]
#[display("`{_0}` and `{_1}` have a `before`-`after` relationship (which may be transitive) but share systems.")]
SetsHaveOrderButIntersect(String, String),
/// Tried to order a system (set) relative to all instances of some system function.
#[display("Tried to order against `{0}` in a schedule that has more than one `{0}` instance. `{_0}` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.")]
#[display("Tried to order against `{_0}` in a schedule that has more than one `{_0}` instance. `{_0}` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.")]
SystemTypeSetAmbiguity(String),
/// Systems with conflicting access have indeterminate run order.
///

View file

@ -1651,6 +1651,8 @@ impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>>
/// Use [`Option<Single<D, F>>`] instead if zero or one matching entities can exist.
///
/// See [`Query`] for more details.
///
/// [System parameter]: crate::system::SystemParam
pub struct Single<'w, D: QueryData, F: QueryFilter = ()> {
pub(crate) item: D::Item<'w>,
pub(crate) _filter: PhantomData<F>,
@ -1687,6 +1689,8 @@ impl<'w, D: QueryData, F: QueryFilter> Single<'w, D, F> {
/// which must individually check each query result for a match.
///
/// See [`Query`] for more details.
///
/// [System parameter]: crate::system::SystemParam
pub struct Populated<'w, 's, D: QueryData, F: QueryFilter = ()>(pub(crate) Query<'w, 's, D, F>);
impl<'w, 's, D: QueryData, F: QueryFilter> Deref for Populated<'w, 's, D, F> {

View file

@ -4036,7 +4036,6 @@ mod tests {
}
#[test]
#[ignore] // This should pass, but it currently fails due to limitations in our access model.
fn ref_compatible_with_resource_mut() {
fn borrow_system(_: Query<EntityRef>, _: ResMut<R>) {}
@ -4067,7 +4066,6 @@ mod tests {
}
#[test]
#[ignore] // This should pass, but it currently fails due to limitations in our access model.
fn mut_compatible_with_resource() {
fn borrow_mut_system(_: Res<R>, _: Query<EntityMut>) {}
@ -4075,7 +4073,6 @@ mod tests {
}
#[test]
#[ignore] // This should pass, but it currently fails due to limitations in our access model.
fn mut_compatible_with_resource_mut() {
fn borrow_mut_system(_: ResMut<R>, _: Query<EntityMut>) {}

View file

@ -8,7 +8,7 @@ use bevy_ecs::prelude::Commands;
use bevy_ecs::system::NonSendMut;
use bevy_ecs::system::ResMut;
use bevy_input::gamepad::{
GamepadConnection, GamepadConnectionEvent, GamepadInfo, RawGamepadAxisChangedEvent,
GamepadConnection, GamepadConnectionEvent, RawGamepadAxisChangedEvent,
RawGamepadButtonChangedEvent, RawGamepadEvent,
};
use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter};
@ -26,15 +26,13 @@ pub fn gilrs_event_startup_system(
gamepads.id_to_entity.insert(id, entity);
gamepads.entity_to_id.insert(entity, id);
let info = GamepadInfo {
name: gamepad.name().into(),
vendor_id: gamepad.vendor_id(),
product_id: gamepad.product_id(),
};
events.send(GamepadConnectionEvent {
gamepad: entity,
connection: GamepadConnection::Connected(info),
connection: GamepadConnection::Connected {
name: gamepad.name().to_string(),
vendor_id: gamepad.vendor_id(),
product_id: gamepad.product_id(),
},
});
}
}
@ -62,20 +60,17 @@ pub fn gilrs_event_system(
entity
});
let info = GamepadInfo {
name: pad.name().into(),
vendor_id: pad.vendor_id(),
product_id: pad.product_id(),
};
events.send(
GamepadConnectionEvent::new(entity, GamepadConnection::Connected(info.clone()))
.into(),
);
connection_events.send(GamepadConnectionEvent::new(
let event = GamepadConnectionEvent::new(
entity,
GamepadConnection::Connected(info),
));
GamepadConnection::Connected {
name: pad.name().to_string(),
vendor_id: pad.vendor_id(),
product_id: pad.product_id(),
},
);
events.send(event.clone().into());
connection_events.send(event);
}
EventType::Disconnected => {
let gamepad = gamepads

View file

@ -161,6 +161,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
},
label: Some("LineGizmo Pipeline 2D".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}
@ -261,6 +262,7 @@ impl SpecializedRenderPipeline for LineJointGizmoPipeline {
},
label: Some("LineJointGizmo Pipeline 2D".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -158,6 +158,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
},
label: Some("LineGizmo Pipeline".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}
@ -256,6 +257,7 @@ impl SpecializedRenderPipeline for LineJointGizmoPipeline {
},
label: Some("LineJointGizmo Pipeline".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -248,7 +248,7 @@ pub(crate) enum ConvertAttributeError {
"Vertex attribute {_0} has format {_1:?} but expected {_3:?} for target attribute {_2}"
)]
WrongFormat(String, VertexFormat, String, VertexFormat),
#[display("{0} in accessor {_1}")]
#[display("{_0} in accessor {_1}")]
AccessFailed(AccessFailed, usize),
#[display("Unknown vertex attribute {_0}")]
UnknownName(String),

View file

@ -49,7 +49,7 @@ image = { version = "0.25.2", default-features = false }
# misc
bitflags = { version = "2.3", features = ["serde"] }
bytemuck = { version = "1.5" }
wgpu = { version = "22", default-features = false }
wgpu = { version = "23", default-features = false }
serde = { version = "1", features = ["derive"] }
derive_more = { version = "1", default-features = false, features = [
"error",

View file

@ -1,3 +1,5 @@
//! [DirectDraw Surface](https://en.wikipedia.org/wiki/DirectDraw_Surface) functionality.
#[cfg(debug_assertions)]
use bevy_utils::warn_once;
use ddsfile::{Caps2, D3DFormat, Dds, DxgiFormat};
@ -16,7 +18,8 @@ pub fn dds_buffer_to_image(
is_srgb: bool,
) -> Result<Image, TextureError> {
let mut cursor = Cursor::new(buffer);
let dds = Dds::read(&mut cursor).expect("Failed to parse DDS file");
let dds = Dds::read(&mut cursor)
.map_err(|error| TextureError::InvalidData(format!("Failed to parse DDS file: {error}")))?;
let texture_format = dds_format_to_texture_format(&dds, is_srgb)?;
if !supported_compressed_formats.supports(texture_format) {
return Err(TextureError::UnsupportedTextureFormat(format!(
@ -182,7 +185,7 @@ pub fn dds_format_to_texture_format(
DxgiFormat::R10G10B10A2_Typeless | DxgiFormat::R10G10B10A2_UNorm => {
TextureFormat::Rgb10a2Unorm
}
DxgiFormat::R11G11B10_Float => TextureFormat::Rg11b10Float,
DxgiFormat::R11G11B10_Float => TextureFormat::Rg11b10Ufloat,
DxgiFormat::R8G8B8A8_Typeless
| DxgiFormat::R8G8B8A8_UNorm
| DxgiFormat::R8G8B8A8_UNorm_sRGB => {

View file

@ -641,7 +641,7 @@ pub fn ktx2_dfd_to_texture_format(
&& sample_information[2].channel_type == 2
&& sample_information[2].bit_length == 10
{
TextureFormat::Rg11b10Float
TextureFormat::Rg11b10Ufloat
} else if sample_information[0].channel_type == 0
&& sample_information[0].bit_length == 9
&& sample_information[1].channel_type == 1
@ -1276,7 +1276,7 @@ pub fn ktx2_format_to_texture_format(
ktx2::Format::R32G32B32A32_SINT => TextureFormat::Rgba32Sint,
ktx2::Format::R32G32B32A32_SFLOAT => TextureFormat::Rgba32Float,
ktx2::Format::B10G11R11_UFLOAT_PACK32 => TextureFormat::Rg11b10Float,
ktx2::Format::B10G11R11_UFLOAT_PACK32 => TextureFormat::Rg11b10Ufloat,
ktx2::Format::E5B9G9R9_UFLOAT_PACK32 => TextureFormat::Rgb9e5Ufloat,
ktx2::Format::X8_D24_UNORM_PACK32 => TextureFormat::Depth24Plus,

View file

@ -21,6 +21,7 @@ serialize = ["serde", "smol_str/serde"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.15.0-dev", default-features = false }
bevy_core = { path = "../bevy_core", version = "0.15.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev", default-features = false, features = [
"serialize",
] }

View file

@ -45,16 +45,16 @@ where
/// If the `input_device`:
/// - was present before, the position data is updated, and the old value is returned.
/// - wasn't present before, `None` is returned.
pub fn set(&mut self, input_device: T, position_data: f32) -> Option<f32> {
self.axis_data.insert(input_device, position_data)
pub fn set(&mut self, input_device: impl Into<T>, position_data: f32) -> Option<f32> {
self.axis_data.insert(input_device.into(), position_data)
}
/// Returns the position data of the provided `input_device`.
///
/// This will be clamped between [`Axis::MIN`] and [`Axis::MAX`] inclusive.
pub fn get(&self, input_device: T) -> Option<f32> {
pub fn get(&self, input_device: impl Into<T>) -> Option<f32> {
self.axis_data
.get(&input_device)
.get(&input_device.into())
.copied()
.map(|value| value.clamp(Self::MIN, Self::MAX))
}
@ -66,13 +66,13 @@ where
/// Use for things like camera zoom, where you want devices like mouse wheels to be able to
/// exceed the normal range. If being able to move faster on one input device
/// than another would give an unfair advantage, you should likely use [`Axis::get`] instead.
pub fn get_unclamped(&self, input_device: T) -> Option<f32> {
self.axis_data.get(&input_device).copied()
pub fn get_unclamped(&self, input_device: impl Into<T>) -> Option<f32> {
self.axis_data.get(&input_device.into()).copied()
}
/// Removes the position data of the `input_device`, returning the position data if the input device was previously set.
pub fn remove(&mut self, input_device: T) -> Option<f32> {
self.axis_data.remove(&input_device)
pub fn remove(&mut self, input_device: impl Into<T>) -> Option<f32> {
self.axis_data.remove(&input_device.into())
}
/// Returns an iterator over all axes.

View file

@ -1,6 +1,7 @@
//! The gamepad input functionality.
use crate::{Axis, ButtonInput, ButtonState};
use bevy_core::Name;
use bevy_ecs::{
change_detection::DetectChangesMut,
component::Component,
@ -148,7 +149,7 @@ impl GamepadConnectionEvent {
/// Is the gamepad connected?
pub fn connected(&self) -> bool {
matches!(self.connection, GamepadConnection::Connected(_))
matches!(self.connection, GamepadConnection::Connected { .. })
}
/// Is the gamepad disconnected?
@ -306,29 +307,29 @@ pub enum ButtonSettingsError {
},
}
/// The [`Gamepad`] [`component`](Component) stores a connected gamepad's metadata such as the `name` and its [`GamepadButton`] and [`GamepadAxis`].
/// Stores a connected gamepad's state and any metadata such as the device name.
///
/// The [`entity`](Entity) representing a gamepad and its [`minimal components`](GamepadSettings) are automatically managed.
/// An entity with this component is spawned automatically after [`GamepadConnectionEvent`]
/// and updated by [`gamepad_event_processing_system`].
///
/// # Usage
///
/// The only way to obtain a [`Gamepad`] is by [`query`](Query).
/// See also [`GamepadSettings`] for configuration.
///
/// # Examples
///
/// ```
/// # use bevy_input::gamepad::{Gamepad, GamepadAxis, GamepadButton};
/// # use bevy_ecs::system::Query;
/// # use bevy_core::Name;
/// #
/// fn gamepad_usage_system(gamepads: Query<&Gamepad>) {
/// for gamepad in gamepads.iter() {
/// println!("{}", gamepad.name());
/// fn gamepad_usage_system(gamepads: Query<(&Name, &Gamepad)>) {
/// for (name, gamepad) in &gamepads {
/// println!("{name}");
///
/// if gamepad.just_pressed(GamepadButton::North) {
/// println!("{} just pressed North", gamepad.name())
/// if gamepad.digital.just_pressed(GamepadButton::North) {
/// println!("{name} just pressed North")
/// }
///
/// if let Some(left_stick_x) = gamepad.get(GamepadAxis::LeftStickX) {
/// if let Some(left_stick_x) = gamepad.analog.get(GamepadAxis::LeftStickX) {
/// println!("left stick X: {}", left_stick_x)
/// }
/// }
@ -338,206 +339,6 @@ pub enum ButtonSettingsError {
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
#[require(GamepadSettings)]
pub struct Gamepad {
info: GamepadInfo,
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state
pub(crate) digital: ButtonInput<GamepadButton>,
/// [`Axis`] of [`GamepadButton`] representing their analog state.
pub(crate) analog: Axis<GamepadInput>,
}
impl Gamepad {
/// Creates a gamepad with the given metadata.
fn new(info: GamepadInfo) -> Self {
let mut analog = Axis::default();
for button in GamepadButton::all().iter().copied() {
analog.set(button.into(), 0.0);
}
for axis_type in GamepadAxis::all().iter().copied() {
analog.set(axis_type.into(), 0.0);
}
Self {
info,
analog,
digital: ButtonInput::default(),
}
}
/// The name of the gamepad.
///
/// This name is generally defined by the OS.
///
/// For example on Windows the name may be "HID-compliant game controller".
pub fn name(&self) -> &str {
self.info.name.as_str()
}
/// Returns the USB vendor ID as assigned by the USB-IF, if available.
pub fn vendor_id(&self) -> Option<u16> {
self.info.vendor_id
}
/// Returns the USB product ID as assigned by the [vendor], if available.
///
/// [vendor]: Self::vendor_id
pub fn product_id(&self) -> Option<u16> {
self.info.product_id
}
/// Returns the analog data of the provided [`GamepadAxis`] or [`GamepadButton`].
///
/// This will be clamped between [[`Axis::MIN`],[`Axis::MAX`]].
pub fn get(&self, input: impl Into<GamepadInput>) -> Option<f32> {
self.analog.get(input.into())
}
/// Returns the unclamped analog data of the provided [`GamepadAxis`] or [`GamepadButton`].
///
/// This value may be outside the [`Axis::MIN`] and [`Axis::MAX`] range.
pub fn get_unclamped(&self, input: impl Into<GamepadInput>) -> Option<f32> {
self.analog.get_unclamped(input.into())
}
/// Returns the left stick as a [`Vec2`]
pub fn left_stick(&self) -> Vec2 {
Vec2 {
x: self.get(GamepadAxis::LeftStickX).unwrap_or(0.0),
y: self.get(GamepadAxis::LeftStickY).unwrap_or(0.0),
}
}
/// Returns the right stick as a [`Vec2`]
pub fn right_stick(&self) -> Vec2 {
Vec2 {
x: self.get(GamepadAxis::RightStickX).unwrap_or(0.0),
y: self.get(GamepadAxis::RightStickY).unwrap_or(0.0),
}
}
/// Returns the directional pad as a [`Vec2`]
pub fn dpad(&self) -> Vec2 {
Vec2 {
x: self.get(GamepadButton::DPadRight).unwrap_or(0.0)
- self.get(GamepadButton::DPadLeft).unwrap_or(0.0),
y: self.get(GamepadButton::DPadUp).unwrap_or(0.0)
- self.get(GamepadButton::DPadDown).unwrap_or(0.0),
}
}
/// Returns `true` if the [`GamepadButton`] has been pressed.
pub fn pressed(&self, button_type: GamepadButton) -> bool {
self.digital.pressed(button_type)
}
/// Returns `true` if any item in [`GamepadButton`] has been pressed.
pub fn any_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
button_inputs
.into_iter()
.any(|button_type| self.pressed(button_type))
}
/// Returns `true` if all items in [`GamepadButton`] have been pressed.
pub fn all_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
button_inputs
.into_iter()
.all(|button_type| self.pressed(button_type))
}
/// Returns `true` if the [`GamepadButton`] has been pressed during the current frame.
///
/// Note: This function does not imply information regarding the current state of [`ButtonInput::pressed`] or [`ButtonInput::just_released`].
pub fn just_pressed(&self, button_type: GamepadButton) -> bool {
self.digital.just_pressed(button_type)
}
/// Returns `true` if any item in [`GamepadButton`] has been pressed during the current frame.
pub fn any_just_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
button_inputs
.into_iter()
.any(|button_type| self.just_pressed(button_type))
}
/// Returns `true` if all items in [`GamepadButton`] have been just pressed.
pub fn all_just_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
button_inputs
.into_iter()
.all(|button_type| self.just_pressed(button_type))
}
/// Returns `true` if the [`GamepadButton`] has been released during the current frame.
///
/// Note: This function does not imply information regarding the current state of [`ButtonInput::pressed`] or [`ButtonInput::just_pressed`].
pub fn just_released(&self, button_type: GamepadButton) -> bool {
self.digital.just_released(button_type)
}
/// Returns `true` if any item in [`GamepadButton`] has just been released.
pub fn any_just_released(
&self,
button_inputs: impl IntoIterator<Item = GamepadButton>,
) -> bool {
button_inputs
.into_iter()
.any(|button_type| self.just_released(button_type))
}
/// Returns `true` if all items in [`GamepadButton`] have just been released.
pub fn all_just_released(
&self,
button_inputs: impl IntoIterator<Item = GamepadButton>,
) -> bool {
button_inputs
.into_iter()
.all(|button_type| self.just_released(button_type))
}
/// Returns an iterator over all digital [button]s that are pressed.
///
/// [button]: GamepadButton
pub fn get_pressed(&self) -> impl Iterator<Item = &GamepadButton> {
self.digital.get_pressed()
}
/// Returns an iterator over all digital [button]s that were just pressed.
///
/// [button]: GamepadButton
pub fn get_just_pressed(&self) -> impl Iterator<Item = &GamepadButton> {
self.digital.get_just_pressed()
}
/// Returns an iterator over all digital [button]s that were just released.
///
/// [button]: GamepadButton
pub fn get_just_released(&self) -> impl Iterator<Item = &GamepadButton> {
self.digital.get_just_released()
}
/// Returns an iterator over all analog [axes].
///
/// [axes]: GamepadInput
pub fn get_analog_axes(&self) -> impl Iterator<Item = &GamepadInput> {
self.analog.all_axes()
}
}
// Note that we don't expose `gilrs::Gamepad::uuid` due to
// https://gitlab.com/gilrs-project/gilrs/-/issues/153.
//
/// Metadata associated with a [`Gamepad`].
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct GamepadInfo {
/// The name of the gamepad.
///
/// This name is generally defined by the OS.
///
/// For example on Windows the name may be "HID-compliant game controller".
pub name: String,
/// The USB vendor ID as assigned by the USB-IF, if available.
pub vendor_id: Option<u16>,
@ -545,6 +346,59 @@ pub struct GamepadInfo {
///
/// [vendor]: Self::vendor_id
pub product_id: Option<u16>,
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state
pub digital: ButtonInput<GamepadButton>,
/// [`Axis`] of [`GamepadButton`] representing their analog state.
pub analog: Axis<GamepadInput>,
}
impl Gamepad {
/// Returns the left stick as a [`Vec2`]
pub fn left_stick(&self) -> Vec2 {
Vec2 {
x: self.analog.get(GamepadAxis::LeftStickX).unwrap_or(0.0),
y: self.analog.get(GamepadAxis::LeftStickY).unwrap_or(0.0),
}
}
/// Returns the right stick as a [`Vec2`]
pub fn right_stick(&self) -> Vec2 {
Vec2 {
x: self.analog.get(GamepadAxis::RightStickX).unwrap_or(0.0),
y: self.analog.get(GamepadAxis::RightStickY).unwrap_or(0.0),
}
}
/// Returns the directional pad as a [`Vec2`]
pub fn dpad(&self) -> Vec2 {
Vec2 {
x: self.analog.get(GamepadButton::DPadRight).unwrap_or(0.0)
- self.analog.get(GamepadButton::DPadLeft).unwrap_or(0.0),
y: self.analog.get(GamepadButton::DPadUp).unwrap_or(0.0)
- self.analog.get(GamepadButton::DPadDown).unwrap_or(0.0),
}
}
}
impl Default for Gamepad {
fn default() -> Self {
let mut analog = Axis::default();
for button in GamepadButton::all().iter().copied() {
analog.set(button, 0.0);
}
for axis_type in GamepadAxis::all().iter().copied() {
analog.set(axis_type, 0.0);
}
Self {
vendor_id: None,
product_id: None,
digital: Default::default(),
analog,
}
}
}
/// Represents gamepad input types that are mapped in the range [0.0, 1.0].
@ -1356,12 +1210,23 @@ pub fn gamepad_connection_system(
for connection_event in connection_events.read() {
let id = connection_event.gamepad;
match &connection_event.connection {
GamepadConnection::Connected(info) => {
GamepadConnection::Connected {
name,
vendor_id,
product_id,
} => {
let Some(mut gamepad) = commands.get_entity(id) else {
warn!("Gamepad {:} removed before handling connection event.", id);
continue;
};
gamepad.insert(Gamepad::new(info.clone()));
gamepad.insert((
Name::new(name.clone()),
Gamepad {
vendor_id: *vendor_id,
product_id: *product_id,
..Default::default()
},
));
info!("Gamepad {:?} connected.", id);
}
GamepadConnection::Disconnected => {
@ -1379,6 +1244,9 @@ pub fn gamepad_connection_system(
}
}
// Note that we don't expose `gilrs::Gamepad::uuid` due to
// https://gitlab.com/gilrs-project/gilrs/-/issues/153.
//
/// The connection status of a gamepad.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
@ -1389,7 +1257,20 @@ pub fn gamepad_connection_system(
)]
pub enum GamepadConnection {
/// The gamepad is connected.
Connected(GamepadInfo),
Connected {
/// The name of the gamepad.
///
/// This name is generally defined by the OS.
///
/// For example on Windows the name may be "HID-compliant game controller".
name: String,
/// The USB vendor ID as assigned by the USB-IF, if available.
vendor_id: Option<u16>,
/// The USB product ID as assigned by the vendor, if available.
product_id: Option<u16>,
},
/// The gamepad is disconnected.
Disconnected,
}
@ -1426,12 +1307,12 @@ pub fn gamepad_event_processing_system(
};
let Some(filtered_value) = gamepad_settings
.get_axis_settings(axis)
.filter(value, gamepad_axis.get(axis))
.filter(value, gamepad_axis.analog.get(axis))
else {
continue;
};
gamepad_axis.analog.set(axis.into(), filtered_value);
gamepad_axis.analog.set(axis, filtered_value);
let send_event = GamepadAxisChangedEvent::new(gamepad, axis, filtered_value);
processed_axis_events.send(send_event);
processed_events.send(GamepadEvent::from(send_event));
@ -1447,16 +1328,16 @@ pub fn gamepad_event_processing_system(
};
let Some(filtered_value) = settings
.get_button_axis_settings(button)
.filter(value, gamepad_buttons.get(button))
.filter(value, gamepad_buttons.analog.get(button))
else {
continue;
};
let button_settings = settings.get_button_settings(button);
gamepad_buttons.analog.set(button.into(), filtered_value);
gamepad_buttons.analog.set(button, filtered_value);
if button_settings.is_released(filtered_value) {
// Check if button was previously pressed
if gamepad_buttons.pressed(button) {
if gamepad_buttons.digital.pressed(button) {
processed_digital_events.send(GamepadButtonStateChangedEvent::new(
gamepad,
button,
@ -1468,7 +1349,7 @@ pub fn gamepad_event_processing_system(
gamepad_buttons.digital.release(button);
} else if button_settings.is_pressed(filtered_value) {
// Check if button was previously not pressed
if !gamepad_buttons.pressed(button) {
if !gamepad_buttons.digital.pressed(button) {
processed_digital_events.send(GamepadButtonStateChangedEvent::new(
gamepad,
button,
@ -1626,8 +1507,8 @@ mod tests {
GamepadAxis, GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent,
GamepadButtonStateChangedEvent,
GamepadConnection::{Connected, Disconnected},
GamepadConnectionEvent, GamepadEvent, GamepadInfo, GamepadSettings,
RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent,
GamepadConnectionEvent, GamepadEvent, GamepadSettings, RawGamepadAxisChangedEvent,
RawGamepadButtonChangedEvent, RawGamepadEvent,
};
use crate::ButtonState;
use bevy_app::{App, PreUpdate};
@ -2000,11 +1881,11 @@ mod tests {
.resource_mut::<Events<GamepadConnectionEvent>>()
.send(GamepadConnectionEvent::new(
gamepad,
Connected(GamepadInfo {
name: String::from("Gamepad test"),
Connected {
name: "Test gamepad".to_string(),
vendor_id: None,
product_id: None,
}),
},
));
gamepad
}
@ -2522,13 +2403,8 @@ mod tests {
assert_eq!(event.button, GamepadButton::DPadDown);
assert_eq!(event.state, ButtonState::Pressed);
}
assert!(ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.pressed(GamepadButton::DPadDown));
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
assert!(gamepad.digital.pressed(GamepadButton::DPadDown));
ctx.app
.world_mut()
@ -2543,13 +2419,8 @@ mod tests {
.len(),
0
);
assert!(ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.pressed(GamepadButton::DPadDown));
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
assert!(gamepad.digital.pressed(GamepadButton::DPadDown));
}
#[test]
@ -2568,23 +2439,13 @@ mod tests {
ctx.update();
// Check it is flagged for this frame
assert!(ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.just_pressed(GamepadButton::DPadDown));
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
assert!(gamepad.digital.just_pressed(GamepadButton::DPadDown));
ctx.update();
//Check it clears next frame
assert!(!ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.just_pressed(GamepadButton::DPadDown));
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
assert!(!gamepad.digital.just_pressed(GamepadButton::DPadDown));
}
#[test]
fn gamepad_buttons_released() {
@ -2627,13 +2488,8 @@ mod tests {
assert_eq!(event.button, GamepadButton::DPadDown);
assert_eq!(event.state, ButtonState::Released);
}
assert!(!ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.pressed(GamepadButton::DPadDown));
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
assert!(!gamepad.digital.pressed(GamepadButton::DPadDown));
ctx.app
.world_mut()
.resource_mut::<Events<GamepadButtonStateChangedEvent>>()
@ -2672,23 +2528,13 @@ mod tests {
ctx.update();
// Check it is flagged for this frame
assert!(ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.just_released(GamepadButton::DPadDown));
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
assert!(gamepad.digital.just_released(GamepadButton::DPadDown));
ctx.update();
//Check it clears next frame
assert!(!ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.just_released(GamepadButton::DPadDown));
// Check it clears next frame
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
assert!(!gamepad.digital.just_released(GamepadButton::DPadDown));
}
#[test]

View file

@ -57,7 +57,7 @@ use gamepad::{
gamepad_connection_system, gamepad_event_processing_system, GamepadAxis,
GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent,
GamepadButtonStateChangedEvent, GamepadConnection, GamepadConnectionEvent, GamepadEvent,
GamepadInfo, GamepadInput, GamepadRumbleRequest, GamepadSettings, RawGamepadAxisChangedEvent,
GamepadInput, GamepadRumbleRequest, GamepadSettings, RawGamepadAxisChangedEvent,
RawGamepadButtonChangedEvent, RawGamepadEvent,
};
@ -142,7 +142,6 @@ impl Plugin for InputPlugin {
.register_type::<GamepadButtonChangedEvent>()
.register_type::<GamepadAxisChangedEvent>()
.register_type::<GamepadButtonStateChangedEvent>()
.register_type::<GamepadInfo>()
.register_type::<GamepadConnection>()
.register_type::<GamepadSettings>()
.register_type::<GamepadAxis>()

View file

@ -180,7 +180,7 @@ impl NormedVectorSpace for f32 {
///
/// 3. Importantly, the interpolation must be *subdivision-stable*: for any interpolation curve
/// between two (unnamed) values and any parameter-value pairs `(t0, p)` and `(t1, q)`, the
/// interpolation curve between `p` and `q` must be the *linear* reparametrization of the original
/// interpolation curve between `p` and `q` must be the *linear* reparameterization of the original
/// interpolation curve restricted to the interval `[t0, t1]`.
///
/// The last of these conditions is very strong and indicates something like constant speed. It
@ -197,7 +197,7 @@ impl NormedVectorSpace for f32 {
/// / \
/// / \
/// / linear \
/// / reparametrization \
/// / reparameterization \
/// / t = t0 * (1 - s) + t1 * s \
/// / \
/// |-------------------------------------|

View file

@ -362,7 +362,7 @@ where
}
}
/// A curve that has had its domain changed by a linear reparametrization (stretching and scaling).
/// A curve that has had its domain changed by a linear reparameterization (stretching and scaling).
/// Curves of this type are produced by [`Curve::reparametrize_linear`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]

View file

@ -903,7 +903,7 @@ where
}
}
/// An error indicating that a linear reparametrization couldn't be performed because of
/// An error indicating that a linear reparameterization couldn't be performed because of
/// malformed inputs.
#[derive(Debug, Error, Display)]
#[display("Could not build a linear function to reparametrize this curve")]
@ -912,8 +912,8 @@ pub enum LinearReparamError {
#[display("This curve has unbounded domain")]
SourceCurveUnbounded,
/// The target interval for reparametrization was unbounded.
#[display("The target interval for reparametrization is unbounded")]
/// The target interval for reparameterization was unbounded.
#[display("The target interval for reparameterization is unbounded")]
TargetIntervalUnbounded,
}
@ -1195,7 +1195,7 @@ mod tests {
}
#[test]
fn reparametrization() {
fn reparameterization() {
let curve = function_curve(interval(1.0, f32::INFINITY).unwrap(), ops::log2);
let reparametrized_curve = curve
.by_ref()

View file

@ -292,6 +292,22 @@ impl Default for CircularSector {
}
}
impl Measured2d for CircularSector {
#[inline(always)]
fn area(&self) -> f32 {
self.arc.radius.squared() * self.arc.half_angle
}
#[inline(always)]
fn perimeter(&self) -> f32 {
if self.half_angle() >= PI {
self.arc.radius * 2.0 * PI
} else {
2.0 * self.radius() + self.arc_length()
}
}
}
impl CircularSector {
/// Create a new [`CircularSector`] from a `radius` and an `angle`
#[inline(always)]
@ -382,12 +398,6 @@ impl CircularSector {
pub fn sagitta(&self) -> f32 {
self.arc.sagitta()
}
/// Returns the area of this sector
#[inline(always)]
pub fn area(&self) -> f32 {
self.arc.radius.squared() * self.arc.half_angle
}
}
/// A primitive representing a circular segment:
@ -425,6 +435,17 @@ impl Default for CircularSegment {
}
}
impl Measured2d for CircularSegment {
#[inline(always)]
fn area(&self) -> f32 {
0.5 * self.arc.radius.squared() * (self.arc.angle() - ops::sin(self.arc.angle()))
}
#[inline(always)]
fn perimeter(&self) -> f32 {
self.chord_length() + self.arc_length()
}
}
impl CircularSegment {
/// Create a new [`CircularSegment`] from a `radius`, and an `angle`
#[inline(always)]
@ -515,17 +536,12 @@ impl CircularSegment {
pub fn sagitta(&self) -> f32 {
self.arc.sagitta()
}
/// Returns the area of this segment
#[inline(always)]
pub fn area(&self) -> f32 {
0.5 * self.arc.radius.squared() * (self.arc.angle() - ops::sin(self.arc.angle()))
}
}
#[cfg(test)]
mod arc_tests {
use core::f32::consts::FRAC_PI_4;
use core::f32::consts::SQRT_2;
use approx::assert_abs_diff_eq;
@ -548,7 +564,9 @@ mod arc_tests {
is_minor: bool,
is_major: bool,
sector_area: f32,
sector_perimeter: f32,
segment_area: f32,
segment_perimeter: f32,
}
impl ArcTestCase {
@ -581,6 +599,7 @@ mod arc_tests {
assert_abs_diff_eq!(self.apothem, sector.apothem());
assert_abs_diff_eq!(self.sagitta, sector.sagitta());
assert_abs_diff_eq!(self.sector_area, sector.area());
assert_abs_diff_eq!(self.sector_perimeter, sector.perimeter());
}
fn check_segment(&self, segment: CircularSegment) {
@ -593,6 +612,7 @@ mod arc_tests {
assert_abs_diff_eq!(self.apothem, segment.apothem());
assert_abs_diff_eq!(self.sagitta, segment.sagitta());
assert_abs_diff_eq!(self.segment_area, segment.area());
assert_abs_diff_eq!(self.segment_perimeter, segment.perimeter());
}
}
@ -615,7 +635,9 @@ mod arc_tests {
is_minor: true,
is_major: false,
sector_area: 0.0,
sector_perimeter: 2.0,
segment_area: 0.0,
segment_perimeter: 0.0,
};
tests.check_arc(Arc2d::new(1.0, 0.0));
@ -642,7 +664,9 @@ mod arc_tests {
is_minor: true,
is_major: false,
sector_area: 0.0,
sector_perimeter: 0.0,
segment_area: 0.0,
segment_perimeter: 0.0,
};
tests.check_arc(Arc2d::new(0.0, FRAC_PI_4));
@ -670,7 +694,9 @@ mod arc_tests {
is_minor: true,
is_major: false,
sector_area: FRAC_PI_4,
sector_perimeter: FRAC_PI_2 + 2.0,
segment_area: FRAC_PI_4 - 0.5,
segment_perimeter: FRAC_PI_2 + SQRT_2,
};
tests.check_arc(Arc2d::from_turns(1.0, 0.25));
@ -697,7 +723,9 @@ mod arc_tests {
is_minor: true,
is_major: true,
sector_area: FRAC_PI_2,
sector_perimeter: PI + 2.0,
segment_area: FRAC_PI_2,
segment_perimeter: PI + 2.0,
};
tests.check_arc(Arc2d::from_radians(1.0, PI));
@ -724,7 +752,9 @@ mod arc_tests {
is_minor: false,
is_major: true,
sector_area: PI,
sector_perimeter: 2.0 * PI,
segment_area: PI,
segment_perimeter: 2.0 * PI,
};
tests.check_arc(Arc2d::from_degrees(1.0, 360.0));

View file

@ -24,7 +24,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" }
# misc
bitflags = { version = "2.3", features = ["serde"] }
bytemuck = { version = "1.5" }
wgpu = { version = "22", default-features = false }
wgpu = { version = "23", default-features = false }
serde = { version = "1", features = ["derive"] }
hexasphere = "15.0"
derive_more = { version = "1", default-features = false, features = [

View file

@ -62,7 +62,7 @@ lz4_flex = { version = "0.11", default-features = false, features = [
], optional = true }
range-alloc = { version = "0.1.3", optional = true }
half = { version = "2", features = ["bytemuck"], optional = true }
meshopt = { version = "0.3.0", optional = true }
meshopt = { version = "0.4", optional = true }
metis = { version = "0.2", optional = true }
itertools = { version = "0.13", optional = true }
bitvec = { version = "1", optional = true }

View file

@ -390,6 +390,7 @@ impl SpecializedRenderPipeline for DeferredLightingLayout {
}),
multisample: MultisampleState::default(),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -14,9 +14,8 @@ use derive_more::derive::{Display, Error};
use half::f16;
use itertools::Itertools;
use meshopt::{
build_meshlets,
ffi::{meshopt_Meshlet, meshopt_simplifyWithAttributes},
generate_vertex_remap_multi, Meshlets, SimplifyOptions, VertexDataAdapter, VertexStream,
build_meshlets, ffi::meshopt_Meshlet, generate_vertex_remap_multi,
simplify_with_attributes_and_locks, Meshlets, SimplifyOptions, VertexDataAdapter, VertexStream,
};
use metis::Graph;
use smallvec::SmallVec;
@ -29,7 +28,7 @@ const SIMPLIFICATION_FAILURE_PERCENTAGE: f32 = 0.95;
/// Default vertex position quantization factor for use with [`MeshletMesh::from_mesh`].
///
/// Snaps vertices to the nearest 1/16th of a centimeter (1/2^4).
pub const DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR: u8 = 4;
pub const MESHLET_DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR: u8 = 4;
const CENTIMETERS_PER_METER: f32 = 100.0;
@ -54,7 +53,7 @@ impl MeshletMesh {
/// Vertices are snapped to the nearest (1/2^x)th of a centimeter, where x = `vertex_position_quantization_factor`.
/// E.g. if x = 4, then vertices are snapped to the nearest 1/2^4 = 1/16th of a centimeter.
///
/// Use [`DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR`] as a default, adjusting lower to save memory and disk space, and higher to prevent artifacts if needed.
/// Use [`MESHLET_DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR`] as a default, adjusting lower to save memory and disk space, and higher to prevent artifacts if needed.
///
/// To ensure that two different meshes do not have cracks between them when placed directly next to each other:
/// * Use the same quantization factor when converting each mesh to a meshlet mesh
@ -72,6 +71,7 @@ impl MeshletMesh {
let vertex_buffer = mesh.create_packed_vertex_buffer_data();
let vertex_stride = mesh.get_vertex_size() as usize;
let vertices = VertexDataAdapter::new(&vertex_buffer, vertex_stride, 0).unwrap();
let vertex_normals = bytemuck::cast_slice(&vertex_buffer[12..16]);
let mut meshlets = compute_meshlets(&indices, &vertices);
let mut bounding_spheres = meshlets
.iter()
@ -102,7 +102,7 @@ impl MeshletMesh {
Some(&indices),
);
let mut vertex_locks = vec![0; vertices.vertex_count];
let mut vertex_locks = vec![false; vertices.vertex_count];
// Build further LODs
let mut simplification_queue = Vec::from_iter(0..meshlets.len());
@ -138,9 +138,14 @@ impl MeshletMesh {
}
// Simplify the group to ~50% triangle count
let Some((simplified_group_indices, mut group_error)) =
simplify_meshlet_group(&group_meshlets, &meshlets, &vertices, &vertex_locks)
else {
let Some((simplified_group_indices, mut group_error)) = simplify_meshlet_group(
&group_meshlets,
&meshlets,
&vertices,
vertex_normals,
vertex_stride,
&vertex_locks,
) else {
// Couldn't simplify the group enough, retry its meshlets later
retry_queue.extend_from_slice(&group_meshlets);
continue;
@ -338,7 +343,7 @@ fn group_meshlets(
}
fn lock_group_borders(
vertex_locks: &mut [u8],
vertex_locks: &mut [bool],
groups: &[SmallVec<[usize; TARGET_MESHLETS_PER_GROUP]>],
meshlets: &Meshlets,
position_only_vertex_remap: &[u32],
@ -369,17 +374,17 @@ fn lock_group_borders(
// Lock vertices used by more than 1 group
for i in 0..vertex_locks.len() {
let vertex_id = position_only_vertex_remap[i] as usize;
vertex_locks[i] = (position_only_locks[vertex_id] == -2) as u8;
vertex_locks[i] = position_only_locks[vertex_id] == -2;
}
}
#[allow(unsafe_code)]
#[allow(clippy::undocumented_unsafe_blocks)]
fn simplify_meshlet_group(
group_meshlets: &[usize],
meshlets: &Meshlets,
vertices: &VertexDataAdapter<'_>,
vertex_locks: &[u8],
vertex_normals: &[f32],
vertex_stride: usize,
vertex_locks: &[bool],
) -> Option<(Vec<u32>, f16)> {
// Build a new index buffer into the mesh vertex data by combining all meshlet data in the group
let mut group_indices = Vec::new();
@ -391,33 +396,19 @@ fn simplify_meshlet_group(
}
// Simplify the group to ~50% triangle count
// TODO: Simplify using vertex attributes
let mut error = 0.0;
let simplified_group_indices = unsafe {
let vertex_data = vertices.reader.get_ref();
let vertex_data = vertex_data.as_ptr().cast::<u8>();
let positions = vertex_data.add(vertices.position_offset);
let mut result: Vec<u32> = vec![0; group_indices.len()];
let index_count = meshopt_simplifyWithAttributes(
result.as_mut_ptr().cast(),
group_indices.as_ptr().cast(),
group_indices.len(),
positions.cast::<f32>(),
vertices.vertex_count,
vertices.vertex_stride,
core::ptr::null(),
0,
core::ptr::null(),
0,
vertex_locks.as_ptr().cast(),
group_indices.len() / 2,
f32::MAX,
(SimplifyOptions::Sparse | SimplifyOptions::ErrorAbsolute).bits(),
core::ptr::from_mut(&mut error),
);
result.resize(index_count, 0u32);
result
};
let simplified_group_indices = simplify_with_attributes_and_locks(
&group_indices,
vertices,
vertex_normals,
&[0.5; 3],
vertex_stride,
vertex_locks,
group_indices.len() / 2,
f32::MAX,
SimplifyOptions::Sparse | SimplifyOptions::ErrorAbsolute,
Some(&mut error),
);
// Check if we were able to simplify at least a little
if simplified_group_indices.len() as f32 / group_indices.len() as f32

View file

@ -200,6 +200,7 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass<M: Material>(
entry_point: material_fragment.entry_point,
targets: material_fragment.targets,
}),
zero_initialize_workgroup_memory: false,
};
let material_id = instance_manager.get_material_id(material_id.untyped());
@ -353,6 +354,7 @@ pub fn prepare_material_meshlet_meshes_prepass<M: Material>(
entry_point,
targets: material_fragment.targets,
}),
zero_initialize_workgroup_memory: false,
};
let material_id = instance_manager.get_material_id(material_id.untyped());

View file

@ -36,7 +36,7 @@ pub(crate) use self::{
pub use self::asset::{MeshletMesh, MeshletMeshLoader, MeshletMeshSaver};
#[cfg(feature = "meshlet_processor")]
pub use self::from_mesh::{
MeshToMeshletMeshConversionError, DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR,
MeshToMeshletMeshConversionError, MESHLET_DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR,
};
use self::{

View file

@ -76,6 +76,7 @@ impl FromWorld for MeshletPipelines {
shader: MESHLET_FILL_CLUSTER_BUFFERS_SHADER_HANDLE,
shader_defs: vec!["MESHLET_FILL_CLUSTER_BUFFERS_PASS".into()],
entry_point: "fill_cluster_buffers".into(),
zero_initialize_workgroup_memory: false,
},
),
@ -92,6 +93,7 @@ impl FromWorld for MeshletPipelines {
"MESHLET_FIRST_CULLING_PASS".into(),
],
entry_point: "cull_clusters".into(),
zero_initialize_workgroup_memory: false,
}),
cull_second: pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
@ -107,6 +109,7 @@ impl FromWorld for MeshletPipelines {
"MESHLET_SECOND_CULLING_PASS".into(),
],
entry_point: "cull_clusters".into(),
zero_initialize_workgroup_memory: false,
}),
downsample_depth_first: pipeline_cache.queue_compute_pipeline(
@ -120,6 +123,7 @@ impl FromWorld for MeshletPipelines {
shader: MESHLET_DOWNSAMPLE_DEPTH_SHADER_HANDLE,
shader_defs: vec!["MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT".into()],
entry_point: "downsample_depth_first".into(),
zero_initialize_workgroup_memory: false,
},
),
@ -134,6 +138,7 @@ impl FromWorld for MeshletPipelines {
shader: MESHLET_DOWNSAMPLE_DEPTH_SHADER_HANDLE,
shader_defs: vec!["MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT".into()],
entry_point: "downsample_depth_second".into(),
zero_initialize_workgroup_memory: false,
},
),
@ -148,6 +153,7 @@ impl FromWorld for MeshletPipelines {
shader: MESHLET_DOWNSAMPLE_DEPTH_SHADER_HANDLE,
shader_defs: vec![],
entry_point: "downsample_depth_first".into(),
zero_initialize_workgroup_memory: false,
},
),
@ -162,6 +168,7 @@ impl FromWorld for MeshletPipelines {
shader: MESHLET_DOWNSAMPLE_DEPTH_SHADER_HANDLE,
shader_defs: vec![],
entry_point: "downsample_depth_second".into(),
zero_initialize_workgroup_memory: false,
},
),
@ -182,6 +189,7 @@ impl FromWorld for MeshletPipelines {
.into(),
],
entry_point: "rasterize_cluster".into(),
zero_initialize_workgroup_memory: false,
},
),
@ -203,6 +211,7 @@ impl FromWorld for MeshletPipelines {
.into(),
],
entry_point: "rasterize_cluster".into(),
zero_initialize_workgroup_memory: false,
},
),
@ -226,6 +235,7 @@ impl FromWorld for MeshletPipelines {
.into(),
],
entry_point: "rasterize_cluster".into(),
zero_initialize_workgroup_memory: false,
}),
visibility_buffer_hardware_raster: pipeline_cache.queue_render_pipeline(
@ -269,6 +279,7 @@ impl FromWorld for MeshletPipelines {
write_mask: ColorWrites::empty(),
})],
}),
zero_initialize_workgroup_memory: false,
},
),
@ -309,6 +320,7 @@ impl FromWorld for MeshletPipelines {
write_mask: ColorWrites::empty(),
})],
}),
zero_initialize_workgroup_memory: false,
},
),
@ -356,6 +368,7 @@ impl FromWorld for MeshletPipelines {
write_mask: ColorWrites::empty(),
})],
}),
zero_initialize_workgroup_memory: false,
}),
resolve_depth: pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor {
@ -381,6 +394,7 @@ impl FromWorld for MeshletPipelines {
entry_point: "resolve_depth".into(),
targets: vec![],
}),
zero_initialize_workgroup_memory: false,
}),
resolve_depth_shadow_view: pipeline_cache.queue_render_pipeline(
@ -407,6 +421,7 @@ impl FromWorld for MeshletPipelines {
entry_point: "resolve_depth".into(),
targets: vec![],
}),
zero_initialize_workgroup_memory: false,
},
),
@ -434,6 +449,7 @@ impl FromWorld for MeshletPipelines {
entry_point: "resolve_material_depth".into(),
targets: vec![],
}),
zero_initialize_workgroup_memory: false,
},
),
@ -448,6 +464,7 @@ impl FromWorld for MeshletPipelines {
shader: MESHLET_REMAP_1D_TO_2D_DISPATCH_SHADER_HANDLE,
shader_defs: vec![],
entry_point: "remap_dispatch".into(),
zero_initialize_workgroup_memory: false,
})
}),
}

View file

@ -571,6 +571,7 @@ where
},
push_constant_ranges: vec![],
label: Some("prepass_pipeline".into()),
zero_initialize_workgroup_memory: false,
};
// This is a bit risky because it's possible to change something that would

View file

@ -10,11 +10,10 @@ use core::num::NonZero;
use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, Handle};
use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d};
use bevy_ecs::{
component::Component,
entity::Entity,
query::{Has, QueryState},
query::{Has, QueryState, Without},
schedule::{common_conditions::resource_exists, IntoSystemConfigs as _},
system::{lifetimeless::Read, Commands, Res, ResMut, Resource},
world::{FromWorld, World},
@ -24,7 +23,8 @@ use bevy_render::{
BatchedInstanceBuffers, GpuPreprocessingSupport, IndirectParameters,
IndirectParametersBuffer, PreprocessWorkItem,
},
render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext},
graph::CameraDriverLabel,
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext},
render_resource::{
binding_types::{storage_buffer, storage_buffer_read_only, uniform_buffer},
BindGroup, BindGroupEntries, BindGroupLayout, BindingResource, BufferBinding,
@ -65,12 +65,15 @@ pub struct GpuMeshPreprocessPlugin {
/// The render node for the mesh uniform building pass.
pub struct GpuPreprocessNode {
view_query: QueryState<(
Entity,
Read<PreprocessBindGroup>,
Read<ViewUniformOffset>,
Has<GpuCulling>,
)>,
view_query: QueryState<
(
Entity,
Read<PreprocessBindGroup>,
Read<ViewUniformOffset>,
Has<GpuCulling>,
),
Without<SkipGpuPreprocess>,
>,
}
/// The compute shader pipelines for the mesh uniform building pass.
@ -108,9 +111,14 @@ bitflags! {
/// The compute shader bind group for the mesh uniform building pass.
///
/// This goes on the view.
#[derive(Component)]
#[derive(Component, Clone)]
pub struct PreprocessBindGroup(BindGroup);
/// Stops the `GpuPreprocessNode` attempting to generate the buffer for this view
/// useful to avoid duplicating effort if the bind group is shared between views
#[derive(Component)]
pub struct SkipGpuPreprocess;
impl Plugin for GpuMeshPreprocessPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(
@ -136,10 +144,12 @@ impl Plugin for GpuMeshPreprocessPlugin {
}
// Stitch the node in.
let gpu_preprocess_node = GpuPreprocessNode::from_world(render_app.world_mut());
let mut render_graph = render_app.world_mut().resource_mut::<RenderGraph>();
render_graph.add_node(NodePbr::GpuPreprocess, gpu_preprocess_node);
render_graph.add_node_edge(NodePbr::GpuPreprocess, CameraDriverLabel);
render_app
.add_render_graph_node::<GpuPreprocessNode>(Core3d, NodePbr::GpuPreprocess)
.add_render_graph_edges(Core3d, (NodePbr::GpuPreprocess, Node3d::Prepass))
.add_render_graph_edges(Core3d, (NodePbr::GpuPreprocess, NodePbr::ShadowPass))
.init_resource::<PreprocessPipelines>()
.init_resource::<SpecializedComputePipelines<PreprocessPipeline>>()
.add_systems(
@ -200,7 +210,7 @@ impl Node for GpuPreprocessNode {
// Grab the index buffer for this view.
let Some(index_buffer) = index_buffers.get(&view) else {
warn!("The preprocessing index buffer wasn't present");
return Ok(());
continue;
};
// Select the right pipeline, depending on whether GPU culling is in
@ -280,6 +290,7 @@ impl SpecializedComputePipeline for PreprocessPipeline {
shader: MESH_PREPROCESS_SHADER_HANDLE,
shader_defs,
entry_point: "main".into(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -2020,6 +2020,7 @@ impl SpecializedMeshPipeline for MeshPipeline {
alpha_to_coverage_enabled,
},
label: Some(label),
zero_initialize_workgroup_memory: false,
})
}
}

View file

@ -110,4 +110,4 @@ const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: u32 = 64u;
@group(0) @binding(31) var<storage, read_write> oit_layers: array<vec2<u32>>;
@group(0) @binding(32) var<storage, read_write> oit_layer_ids: array<atomic<i32>>;
@group(0) @binding(33) var<uniform> oit_settings: types::OrderIndependentTransparencySettings;
#endif OIT_ENABLED
#endif // OIT_ENABLED

View file

@ -448,6 +448,7 @@ impl FromWorld for SsaoPipelines {
shader: PREPROCESS_DEPTH_SHADER_HANDLE,
shader_defs: Vec::new(),
entry_point: "preprocess_depth".into(),
zero_initialize_workgroup_memory: false,
});
let spatial_denoise_pipeline =
@ -461,6 +462,7 @@ impl FromWorld for SsaoPipelines {
shader: SPATIAL_DENOISE_SHADER_HANDLE,
shader_defs: Vec::new(),
entry_point: "spatial_denoise".into(),
zero_initialize_workgroup_memory: false,
});
Self {
@ -513,6 +515,7 @@ impl SpecializedComputePipeline for SsaoPipelines {
shader: SSAO_SHADER_HANDLE,
shader_defs,
entry_point: "ssao".into(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -560,6 +560,7 @@ impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline {
primitive: default(),
depth_stencil: None,
multisample: default(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -600,6 +600,7 @@ impl SpecializedRenderPipeline for VolumetricFogPipeline {
write_mask: ColorWrites::ALL,
})],
}),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -254,7 +254,7 @@ pub struct DragEntry {
/// An entry in the cache that drives the `pointer_events` system, storing additional data
/// about pointer button presses.
#[derive(Debug, Clone, Default)]
pub struct PointerState {
pub struct PointerButtonState {
/// Stores the press location and start time for each button currently being pressed by the pointer.
pub pressing: HashMap<Entity, (Location, Instant, HitData)>,
/// Stores the starting and current locations for each entity currently being dragged by the pointer.
@ -263,6 +263,42 @@ pub struct PointerState {
pub dragging_over: HashMap<Entity, HitData>,
}
/// State for all pointers.
#[derive(Debug, Clone, Default, Resource)]
pub struct PointerState {
/// Pressing and dragging state, organized by pointer and button.
pub pointer_buttons: HashMap<(PointerId, PointerButton), PointerButtonState>,
}
impl PointerState {
/// Retrieves the current state for a specific pointer and button, if it has been created.
pub fn get(&self, pointer_id: PointerId, button: PointerButton) -> Option<&PointerButtonState> {
self.pointer_buttons.get(&(pointer_id, button))
}
/// Provides write access to the state of a pointer and button, creating it if it does not yet exist.
pub fn get_mut(
&mut self,
pointer_id: PointerId,
button: PointerButton,
) -> &mut PointerButtonState {
self.pointer_buttons
.entry((pointer_id, button))
.or_default()
}
/// Clears all the data assoceated with all of the buttons on a pointer. Does not free the underlying memory.
pub fn clear(&mut self, pointer_id: PointerId) {
for button in PointerButton::iter() {
if let Some(state) = self.pointer_buttons.get_mut(&(pointer_id, button)) {
state.pressing.clear();
state.dragging.clear();
state.dragging_over.clear();
}
}
}
}
/// A helper system param for accessing the picking event writers.
#[derive(SystemParam)]
pub struct PickingEventWriters<'w> {
@ -316,8 +352,7 @@ pub fn pointer_events(
pointer_map: Res<PointerMap>,
hover_map: Res<HoverMap>,
previous_hover_map: Res<PreviousHoverMap>,
// Local state
mut pointer_state: Local<HashMap<(PointerId, PointerButton), PointerState>>,
mut pointer_state: ResMut<PointerState>,
// Output
mut commands: Commands,
mut event_writers: PickingEventWriters,
@ -352,7 +387,7 @@ pub fn pointer_events(
// Possibly send DragEnter events
for button in PointerButton::iter() {
let state = pointer_state.entry((pointer_id, button)).or_default();
let state = pointer_state.get_mut(pointer_id, button);
for drag_target in state
.dragging
@ -397,7 +432,7 @@ pub fn pointer_events(
match action {
// Pressed Button
PointerAction::Pressed { direction, button } => {
let state = pointer_state.entry((pointer_id, button)).or_default();
let state = pointer_state.get_mut(pointer_id, button);
// The sequence of events emitted depends on if this is a press or a release
match direction {
@ -519,7 +554,7 @@ pub fn pointer_events(
PointerAction::Moved { delta } => {
// Triggers during movement even if not over an entity
for button in PointerButton::iter() {
let state = pointer_state.entry((pointer_id, button)).or_default();
let state = pointer_state.get_mut(pointer_id, button);
// Emit DragEntry and DragStart the first time we move while pressing an entity
for (press_target, (location, _, hit)) in state.pressing.iter() {
@ -619,14 +654,8 @@ pub fn pointer_events(
commands.trigger_targets(cancel_event.clone(), hovered_entity);
event_writers.cancel_events.send(cancel_event);
}
// Clear the local state for the canceled pointer
for button in PointerButton::iter() {
if let Some(state) = pointer_state.get_mut(&(pointer_id, button)) {
state.pressing.clear();
state.dragging.clear();
state.dragging_over.clear();
}
}
// Clear the state for the canceled pointer
pointer_state.clear(pointer_id);
}
}
}
@ -662,7 +691,7 @@ pub fn pointer_events(
// Possibly send DragLeave events
for button in PointerButton::iter() {
let state = pointer_state.entry((pointer_id, button)).or_default();
let state = pointer_state.get_mut(pointer_id, button);
state.dragging_over.remove(&hovered_entity);
for drag_target in state.dragging.keys() {
let drag_leave_event = Pointer::new(

View file

@ -30,8 +30,8 @@
//! ## Expressive Events
//!
//! The events in this module (see [`events`]) cannot be listened to with normal `EventReader`s.
//! Instead, they are dispatched to *ovservers* attached to specific entities. When events are generated, they
//! bubble up the entity hierarchy starting from their target, until they reach the root or bubbling is haulted
//! Instead, they are dispatched to *observers* attached to specific entities. When events are generated, they
//! bubble up the entity hierarchy starting from their target, until they reach the root or bubbling is halted
//! with a call to [`Trigger::propagate`](bevy_ecs::observer::Trigger::propagate).
//! See [`Observer`] for details.
//!
@ -73,8 +73,8 @@
//!
//! #### Input Agnostic
//!
//! Picking provides a generic Pointer abstracton, which is useful for reacting to many different
//! types of input devices. Pointers can be controlled with anything, whether its the included mouse
//! Picking provides a generic Pointer abstraction, which is useful for reacting to many different
//! types of input devices. Pointers can be controlled with anything, whether it's the included mouse
//! or touch inputs, or a custom gamepad input system you write yourself to control a virtual pointer.
//!
//! ## Robustness
@ -381,6 +381,7 @@ impl Plugin for InteractionPlugin {
app.init_resource::<focus::HoverMap>()
.init_resource::<focus::PreviousHoverMap>()
.init_resource::<PointerState>()
.add_event::<Pointer<Cancel>>()
.add_event::<Pointer<Click>>()
.add_event::<Pointer<Down>>()

View file

@ -51,7 +51,7 @@ glam = { version = "0.29", features = ["serde"], optional = true }
petgraph = { version = "0.6", features = ["serde-1"], optional = true }
smol_str = { version = "0.2.0", features = ["serde"], optional = true }
uuid = { version = "1.0", optional = true, features = ["v4", "serde"] }
wgpu-types = { version = "22", features = ["serde"], optional = true }
wgpu-types = { version = "23", features = ["serde"], optional = true }
[dev-dependencies]
ron = "0.8.0"

View file

@ -301,11 +301,11 @@
//! [fully-qualified type name]: bevy_reflect::TypePath::type_path
use async_channel::{Receiver, Sender};
use bevy_app::prelude::*;
use bevy_app::{prelude::*, MainScheduleOrder};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
entity::Entity,
schedule::IntoSystemConfigs,
schedule::{IntoSystemConfigs, ScheduleLabel},
system::{Commands, In, IntoSystem, ResMut, Resource, System, SystemId},
world::World,
};
@ -434,11 +434,16 @@ impl Plugin for RemotePlugin {
);
}
app.init_schedule(RemoteLast)
.world_mut()
.resource_mut::<MainScheduleOrder>()
.insert_after(Last, RemoteLast);
app.insert_resource(remote_methods)
.init_resource::<RemoteWatchingRequests>()
.add_systems(PreStartup, setup_mailbox_channel)
.add_systems(
Update,
RemoteLast,
(
process_remote_requests,
process_ongoing_watching_requests,
@ -449,6 +454,10 @@ impl Plugin for RemotePlugin {
}
}
/// Schedule that contains all systems to process Bevy Remote Protocol requests
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
struct RemoteLast;
/// A type to hold the allowed types of systems to be used as method handlers.
#[derive(Debug)]
pub enum RemoteMethodHandler {

View file

@ -67,14 +67,14 @@ codespan-reporting = "0.11.0"
# It is enabled for now to avoid having to do a significant overhaul of the renderer just for wasm.
# When the 'atomics' feature is enabled `fragile-send-sync-non-atomic` does nothing
# and Bevy instead wraps `wgpu` types to verify they are not used off their origin thread.
wgpu = { version = "22", default-features = false, features = [
wgpu = { version = "23", default-features = false, features = [
"wgsl",
"dx12",
"metal",
"naga-ir",
"fragile-send-sync-non-atomic-wasm",
] }
naga = { version = "22", features = ["wgsl-in"] }
naga = { version = "23", features = ["wgsl-in"] }
serde = { version = "1", features = ["derive"] }
bytemuck = { version = "1.5", features = ["derive", "must_cast"] }
downcast-rs = "1.2.0"
@ -97,12 +97,12 @@ offset-allocator = "0.2"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# Omit the `glsl` feature in non-WebAssembly by default.
naga_oil = { version = "0.15", default-features = false, features = [
naga_oil = { version = "0.16", default-features = false, features = [
"test_shader",
] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
naga_oil = "0.15"
naga_oil = "0.16"
js-sys = "0.3"
web-sys = { version = "0.3.67", features = [
'Blob',

View file

@ -101,7 +101,7 @@ where
///
/// Each bin corresponds to a single batch set. For unbatchable entities,
/// prefer `unbatchable_values` instead.
pub(crate) batchable_mesh_values: HashMap<BPI::BinKey, Vec<(Entity, MainEntity)>>,
pub batchable_mesh_values: HashMap<BPI::BinKey, Vec<(Entity, MainEntity)>>,
/// A list of `BinKey`s for unbatchable items.
///
@ -112,7 +112,7 @@ where
/// The unbatchable bins.
///
/// Each entity here is rendered in a separate drawcall.
pub(crate) unbatchable_mesh_values: HashMap<BPI::BinKey, UnbatchableBinnedEntities>,
pub unbatchable_mesh_values: HashMap<BPI::BinKey, UnbatchableBinnedEntities>,
/// Items in the bin that aren't meshes at all.
///
@ -155,9 +155,9 @@ pub struct BinnedRenderPhaseBatch {
}
/// Information about the unbatchable entities in a bin.
pub(crate) struct UnbatchableBinnedEntities {
pub struct UnbatchableBinnedEntities {
/// The entities.
pub(crate) entities: Vec<(Entity, MainEntity)>,
pub entities: Vec<(Entity, MainEntity)>,
/// The GPU array buffer indices of each unbatchable binned entity.
pub(crate) buffer_indices: UnbatchableBinnedEntityIndexSet,

View file

@ -45,6 +45,18 @@ impl From<wgpu::BindGroup> for BindGroup {
}
}
impl<'a> From<&'a BindGroup> for Option<&'a wgpu::BindGroup> {
fn from(value: &'a BindGroup) -> Self {
Some(value.deref())
}
}
impl<'a> From<&'a mut BindGroup> for Option<&'a wgpu::BindGroup> {
fn from(value: &'a mut BindGroup) -> Self {
Some(&*value)
}
}
impl Deref for BindGroup {
type Target = wgpu::BindGroup;

View file

@ -108,6 +108,9 @@ pub struct RenderPipelineDescriptor {
pub multisample: MultisampleState,
/// The compiled fragment stage, its entry point, and the color targets.
pub fragment: Option<FragmentState>,
/// Whether to zero-initialize workgroup memory by default. If you're not sure, set this to true.
/// If this is false, reading from workgroup variables before writing to them will result in garbage values.
pub zero_initialize_workgroup_memory: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
@ -147,4 +150,7 @@ pub struct ComputePipelineDescriptor {
/// The name of the entry point in the compiled shader. There must be a
/// function with this name in the shader.
pub entry_point: Cow<'static, str>,
/// Whether to zero-initialize workgroup memory by default. If you're not sure, set this to true.
/// If this is false, reading from workgroup variables before writing to them will result in garbage values.
pub zero_initialize_workgroup_memory: bool,
}

View file

@ -669,6 +669,7 @@ impl PipelineCache {
let device = self.device.clone();
let shader_cache = self.shader_cache.clone();
let layout_cache = self.layout_cache.clone();
create_pipeline_task(
async move {
let mut shader_cache = shader_cache.lock().unwrap();
@ -731,11 +732,10 @@ impl PipelineCache {
)
});
// TODO: Expose this somehow
// TODO: Expose the rest of this somehow
let compilation_options = PipelineCompilationOptions {
constants: &std::collections::HashMap::new(),
zero_initialize_workgroup_memory: false,
vertex_pulling_transform: Default::default(),
constants: &default(),
zero_initialize_workgroup_memory: descriptor.zero_initialize_workgroup_memory,
};
let descriptor = RawRenderPipelineDescriptor {
@ -747,7 +747,7 @@ impl PipelineCache {
primitive: descriptor.primitive,
vertex: RawVertexState {
buffers: &vertex_buffer_layouts,
entry_point: descriptor.vertex.entry_point.deref(),
entry_point: Some(descriptor.vertex.entry_point.deref()),
module: &vertex_module,
// TODO: Should this be the same as the fragment compilation options?
compilation_options: compilation_options.clone(),
@ -755,7 +755,7 @@ impl PipelineCache {
fragment: fragment_data
.as_ref()
.map(|(module, entry_point, targets)| RawFragmentState {
entry_point,
entry_point: Some(entry_point),
module,
targets,
// TODO: Should this be the same as the vertex compilation options?
@ -780,6 +780,7 @@ impl PipelineCache {
let device = self.device.clone();
let shader_cache = self.shader_cache.clone();
let layout_cache = self.layout_cache.clone();
create_pipeline_task(
async move {
let mut shader_cache = shader_cache.lock().unwrap();
@ -812,12 +813,12 @@ impl PipelineCache {
label: descriptor.label.as_deref(),
layout: layout.as_ref().map(|layout| -> &PipelineLayout { layout }),
module: &compute_module,
entry_point: &descriptor.entry_point,
// TODO: Expose this somehow
entry_point: Some(&descriptor.entry_point),
// TODO: Expose the rest of this somehow
compilation_options: PipelineCompilationOptions {
constants: &std::collections::HashMap::new(),
zero_initialize_workgroup_memory: false,
vertex_pulling_transform: Default::default(),
constants: &default(),
zero_initialize_workgroup_memory: descriptor
.zero_initialize_workgroup_memory,
},
cache: None,
};

View file

@ -496,6 +496,7 @@ impl SpecializedRenderPipeline for ScreenshotToScreenPipeline {
})],
}),
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -8,12 +8,6 @@ use bevy_render::{
use bevy_transform::components::{GlobalTransform, Transform};
/// A [`Bundle`] of components for drawing a single sprite from an image.
///
/// # Extra behaviors
///
/// You may add one or both of the following components to enable additional behaviors:
/// - [`ImageScaleMode`](crate::ImageScaleMode) to enable either slicing or tiling of the texture
/// - [`TextureAtlas`](crate::TextureAtlas) to draw a specific section of the texture
#[derive(Bundle, Clone, Debug, Default)]
#[deprecated(
since = "0.15.0",

View file

@ -30,7 +30,7 @@ pub mod prelude {
#[doc(hidden)]
pub use crate::{
bundle::SpriteBundle,
sprite::{ImageScaleMode, Sprite},
sprite::{Sprite, SpriteImageMode},
texture_atlas::{TextureAtlas, TextureAtlasLayout, TextureAtlasSources},
texture_slice::{BorderRect, SliceScaleMode, TextureSlice, TextureSlicer},
ColorMaterial, ColorMesh2dBundle, MeshMaterial2d, TextureAtlasBuilder,
@ -106,7 +106,7 @@ impl Plugin for SpritePlugin {
app.init_asset::<TextureAtlasLayout>()
.register_asset_reflect::<TextureAtlasLayout>()
.register_type::<Sprite>()
.register_type::<ImageScaleMode>()
.register_type::<SpriteImageMode>()
.register_type::<TextureSlicer>()
.register_type::<Anchor>()
.register_type::<TextureAtlas>()

View file

@ -674,6 +674,7 @@ impl SpecializedMeshPipeline for Mesh2dPipeline {
alpha_to_coverage_enabled: false,
},
label: Some(label.into()),
zero_initialize_workgroup_memory: false,
})
}
}

View file

@ -323,6 +323,7 @@ impl SpecializedRenderPipeline for SpritePipeline {
},
label: Some("sprite_pipeline".into()),
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -34,6 +34,8 @@ pub struct Sprite {
pub rect: Option<Rect>,
/// [`Anchor`] point of the sprite in the world
pub anchor: Anchor,
/// How the sprite's image will be scaled.
pub image_mode: SpriteImageMode,
}
impl Sprite {
@ -79,9 +81,12 @@ impl From<Handle<Image>> for Sprite {
}
/// Controls how the image is altered when scaled.
#[derive(Component, Debug, Clone, Reflect)]
#[reflect(Component, Debug)]
pub enum ImageScaleMode {
#[derive(Default, Debug, Clone, Reflect, PartialEq)]
#[reflect(Debug)]
pub enum SpriteImageMode {
/// The sprite will take on the size of the image by default, and will be stretched or shrunk if [`Sprite::custom_size`] is set.
#[default]
Auto,
/// The texture will be cut in 9 slices, keeping the texture in proportions on resize
Sliced(TextureSlicer),
/// The texture will be repeated if stretched beyond `stretched_value`
@ -96,6 +101,17 @@ pub enum ImageScaleMode {
},
}
impl SpriteImageMode {
/// Returns true if this mode uses slices internally ([`SpriteImageMode::Sliced`] or [`SpriteImageMode::Tiled`])
#[inline]
pub fn uses_slices(&self) -> bool {
matches!(
self,
SpriteImageMode::Sliced(..) | SpriteImageMode::Tiled { .. }
)
}
}
/// How a sprite is positioned relative to its [`Transform`].
/// It defaults to `Anchor::Center`.
#[derive(Component, Debug, Clone, Copy, PartialEq, Default, Reflect)]

View file

@ -1,4 +1,4 @@
use crate::{ExtractedSprite, ImageScaleMode, Sprite, TextureAtlasLayout};
use crate::{ExtractedSprite, Sprite, SpriteImageMode, TextureAtlasLayout};
use super::TextureSlice;
use bevy_asset::{AssetEvent, Assets};
@ -8,7 +8,7 @@ use bevy_render::texture::Image;
use bevy_transform::prelude::*;
use bevy_utils::HashSet;
/// Component storing texture slices for sprite entities with a [`ImageScaleMode`]
/// Component storing texture slices for tiled or sliced sprite entities
///
/// This component is automatically inserted and updated
#[derive(Debug, Clone, Component)]
@ -69,24 +69,19 @@ impl ComputedTextureSlices {
}
}
/// Generates sprite slices for a `sprite` given a `scale_mode`. The slices
/// Generates sprite slices for a [`Sprite`] with [`SpriteImageMode::Sliced`] or [`SpriteImageMode::Sliced`]. The slices
/// will be computed according to the `image_handle` dimensions or the sprite rect.
///
/// Returns `None` if the image asset is not loaded
///
/// # Arguments
///
/// * `sprite` - The sprite component, will be used to find the draw area size
/// * `scale_mode` - The image scaling component
/// * `image_handle` - The texture to slice or tile
/// * `sprite` - The sprite component with the image handle and image mode
/// * `images` - The image assets, use to retrieve the image dimensions
/// * `atlas` - Optional texture atlas, if set the slicing will happen on the matching sub section
/// of the texture
/// * `atlas_layouts` - The atlas layout assets, used to retrieve the texture atlas section rect
#[must_use]
fn compute_sprite_slices(
sprite: &Sprite,
scale_mode: &ImageScaleMode,
images: &Assets<Image>,
atlas_layouts: &Assets<TextureAtlasLayout>,
) -> Option<ComputedTextureSlices> {
@ -111,9 +106,9 @@ fn compute_sprite_slices(
(size, rect)
}
};
let slices = match scale_mode {
ImageScaleMode::Sliced(slicer) => slicer.compute_slices(texture_rect, sprite.custom_size),
ImageScaleMode::Tiled {
let slices = match &sprite.image_mode {
SpriteImageMode::Sliced(slicer) => slicer.compute_slices(texture_rect, sprite.custom_size),
SpriteImageMode::Tiled {
tile_x,
tile_y,
stretch_value,
@ -125,18 +120,21 @@ fn compute_sprite_slices(
};
slice.tiled(*stretch_value, (*tile_x, *tile_y))
}
SpriteImageMode::Auto => {
unreachable!("Slices should not be computed for SpriteImageMode::Stretch")
}
};
Some(ComputedTextureSlices(slices))
}
/// System reacting to added or modified [`Image`] handles, and recompute sprite slices
/// on matching sprite entities with a [`ImageScaleMode`] component
/// on sprite entities with a matching [`SpriteImageMode`]
pub(crate) fn compute_slices_on_asset_event(
mut commands: Commands,
mut events: EventReader<AssetEvent<Image>>,
images: Res<Assets<Image>>,
atlas_layouts: Res<Assets<TextureAtlasLayout>>,
sprites: Query<(Entity, &ImageScaleMode, &Sprite)>,
sprites: Query<(Entity, &Sprite)>,
) {
// We store the asset ids of added/modified image assets
let added_handles: HashSet<_> = events
@ -150,29 +148,31 @@ pub(crate) fn compute_slices_on_asset_event(
return;
}
// We recompute the sprite slices for sprite entities with a matching asset handle id
for (entity, scale_mode, sprite) in &sprites {
for (entity, sprite) in &sprites {
if !sprite.image_mode.uses_slices() {
continue;
}
if !added_handles.contains(&sprite.image.id()) {
continue;
}
if let Some(slices) = compute_sprite_slices(sprite, scale_mode, &images, &atlas_layouts) {
if let Some(slices) = compute_sprite_slices(sprite, &images, &atlas_layouts) {
commands.entity(entity).insert(slices);
}
}
}
/// System reacting to changes on relevant sprite bundle components to compute the sprite slices
/// on matching sprite entities with a [`ImageScaleMode`] component
/// System reacting to changes on the [`Sprite`] component to compute the sprite slices
pub(crate) fn compute_slices_on_sprite_change(
mut commands: Commands,
images: Res<Assets<Image>>,
atlas_layouts: Res<Assets<TextureAtlasLayout>>,
changed_sprites: Query<
(Entity, &ImageScaleMode, &Sprite),
Or<(Changed<ImageScaleMode>, Changed<Sprite>)>,
>,
changed_sprites: Query<(Entity, &Sprite), Changed<Sprite>>,
) {
for (entity, scale_mode, sprite) in &changed_sprites {
if let Some(slices) = compute_sprite_slices(sprite, scale_mode, &images, &atlas_layouts) {
for (entity, sprite) in &changed_sprites {
if !sprite.image_mode.uses_slices() {
continue;
}
if let Some(slices) = compute_sprite_slices(sprite, &images, &atlas_layouts) {
commands.entity(entity).insert(slices);
}
}

View file

@ -10,7 +10,7 @@ use bevy_reflect::Reflect;
/// sections will be scaled or tiled.
///
/// See [9-sliced](https://en.wikipedia.org/wiki/9-slice_scaling) textures.
#[derive(Debug, Clone, Reflect)]
#[derive(Debug, Clone, Reflect, PartialEq)]
pub struct TextureSlicer {
/// The sprite borders, defining the 9 sections of the image
pub border: BorderRect,
@ -23,7 +23,7 @@ pub struct TextureSlicer {
}
/// Defines how a texture slice scales when resized
#[derive(Debug, Copy, Clone, Default, Reflect)]
#[derive(Debug, Copy, Clone, Default, Reflect, PartialEq)]
pub enum SliceScaleMode {
/// The slice will be stretched to fit the area
#[default]

View file

@ -116,6 +116,8 @@ impl Plugin for TextPlugin {
.register_type::<TextColor>()
.register_type::<TextSpan>()
.register_type::<TextBounds>()
.register_type::<TextLayout>()
.register_type::<ComputedTextBlock>()
.init_asset_loader::<FontLoader>()
.init_resource::<FontAtlasSets>()
.init_resource::<TextPipeline>()

View file

@ -28,7 +28,8 @@ impl Default for CosmicBuffer {
/// A sub-entity of a [`ComputedTextBlock`].
///
/// Returned by [`ComputedTextBlock::entities`].
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, Reflect)]
#[reflect(Debug)]
pub struct TextEntity {
/// The entity.
pub entity: Entity,
@ -41,7 +42,8 @@ pub struct TextEntity {
/// See [`TextLayout`].
///
/// Automatically updated by 2d and UI text systems.
#[derive(Component, Debug, Clone)]
#[derive(Component, Debug, Clone, Reflect)]
#[reflect(Component, Debug, Default)]
pub struct ComputedTextBlock {
/// Buffer for managing text layout and creating [`TextLayoutInfo`].
///
@ -49,6 +51,7 @@ pub struct ComputedTextBlock {
/// `TextLayoutInfo`. If you want to control the buffer contents manually or use the `cosmic-text`
/// editor, then you need to not use `TextLayout` and instead manually implement the conversion to
/// `TextLayoutInfo`.
#[reflect(ignore)]
pub(crate) buffer: CosmicBuffer,
/// Entities for all text spans in the block, including the root-level text.
///

View file

@ -211,6 +211,36 @@ impl GlobalTransform {
self.0.translation
}
/// Get the rotation as a [`Quat`].
///
/// The transform is expected to be non-degenerate and without shearing, or the output will be invalid.
///
/// # Warning
///
/// This is calculated using `to_scale_rotation_translation`, meaning that you
/// should probably use it directly if you also need translation or scale.
#[inline]
pub fn rotation(&self) -> Quat {
self.to_scale_rotation_translation().1
}
/// Get the scale as a [`Vec3`].
///
/// The transform is expected to be non-degenerate and without shearing, or the output will be invalid.
///
/// Some of the computations overlap with `to_scale_rotation_translation`, which means you should use
/// it instead if you also need rotation.
#[inline]
pub fn scale(&self) -> Vec3 {
//Formula based on glam's implementation https://github.com/bitshifter/glam-rs/blob/2e4443e70c709710dfb25958d866d29b11ed3e2b/src/f32/affine3a.rs#L290
let det = self.0.matrix3.determinant();
Vec3::new(
self.0.matrix3.x_axis.length() * det.signum(),
self.0.matrix3.y_axis.length(),
self.0.matrix3.z_axis.length(),
)
}
/// Get an upper bound of the radius from the given `extents`.
#[inline]
pub fn radius_vec3a(&self, extents: Vec3A) -> f32 {
@ -363,4 +393,18 @@ mod test {
t1_prime.compute_transform(),
);
}
#[test]
fn scale() {
let test_values = [-42.42, 0., 42.42];
for x in test_values {
for y in test_values {
for z in test_values {
let scale = Vec3::new(x, y, z);
let gt = GlobalTransform::from_scale(scale);
assert_eq!(gt.scale(), gt.to_scale_rotation_translation().0);
}
}
}
}
}

View file

@ -43,6 +43,7 @@ derive_more = { version = "1", default-features = false, features = [
] }
nonmax = "0.5"
smallvec = "1.11"
accesskit = "0.17"
[features]
default = ["bevy_ui_picking_backend"]

View file

@ -1,13 +1,10 @@
use crate::{
experimental::UiChildren,
prelude::{Button, Label},
widget::{TextUiReader, UiImage},
widget::{ImageNode, TextUiReader},
ComputedNode,
};
use bevy_a11y::{
accesskit::{NodeBuilder, Rect, Role},
AccessibilityNode,
};
use bevy_a11y::AccessibilityNode;
use bevy_app::{App, Plugin, PostUpdate};
use bevy_ecs::{
prelude::{DetectChanges, Entity},
@ -19,7 +16,9 @@ use bevy_ecs::{
use bevy_render::{camera::CameraUpdateSystem, prelude::Camera};
use bevy_transform::prelude::GlobalTransform;
fn calc_name(
use accesskit::{Node, Rect, Role};
fn calc_label(
text_reader: &mut TextUiReader,
children: impl Iterator<Item = Entity>,
) -> Option<Box<str>> {
@ -70,18 +69,18 @@ fn button_changed(
mut text_reader: TextUiReader,
) {
for (entity, accessible) in &mut query {
let name = calc_name(&mut text_reader, ui_children.iter_ui_children(entity));
let label = calc_label(&mut text_reader, ui_children.iter_ui_children(entity));
if let Some(mut accessible) = accessible {
accessible.set_role(Role::Button);
if let Some(name) = name {
accessible.set_name(name);
if let Some(name) = label {
accessible.set_label(name);
} else {
accessible.clear_name();
accessible.clear_label();
}
} else {
let mut node = NodeBuilder::new(Role::Button);
if let Some(name) = name {
node.set_name(name);
let mut node = Node::new(Role::Button);
if let Some(label) = label {
node.set_label(label);
}
commands
.entity(entity)
@ -92,23 +91,26 @@ fn button_changed(
fn image_changed(
mut commands: Commands,
mut query: Query<(Entity, Option<&mut AccessibilityNode>), (Changed<UiImage>, Without<Button>)>,
mut query: Query<
(Entity, Option<&mut AccessibilityNode>),
(Changed<ImageNode>, Without<Button>),
>,
ui_children: UiChildren,
mut text_reader: TextUiReader,
) {
for (entity, accessible) in &mut query {
let name = calc_name(&mut text_reader, ui_children.iter_ui_children(entity));
let label = calc_label(&mut text_reader, ui_children.iter_ui_children(entity));
if let Some(mut accessible) = accessible {
accessible.set_role(Role::Image);
if let Some(name) = name {
accessible.set_name(name);
if let Some(label) = label {
accessible.set_label(label);
} else {
accessible.clear_name();
accessible.clear_label();
}
} else {
let mut node = NodeBuilder::new(Role::Image);
if let Some(name) = name {
node.set_name(name);
let mut node = Node::new(Role::Image);
if let Some(label) = label {
node.set_label(label);
}
commands
.entity(entity)
@ -127,18 +129,18 @@ fn label_changed(
.iter(entity)
.map(|(_, _, text, _, _)| text.into())
.collect::<Vec<String>>();
let name = Some(values.join(" ").into_boxed_str());
let label = Some(values.join(" ").into_boxed_str());
if let Some(mut accessible) = accessible {
accessible.set_role(Role::Label);
if let Some(name) = name {
accessible.set_name(name);
if let Some(label) = label {
accessible.set_label(label);
} else {
accessible.clear_name();
accessible.clear_label();
}
} else {
let mut node = NodeBuilder::new(Role::Label);
if let Some(name) = name {
node.set_name(name);
let mut node = Node::new(Role::Label);
if let Some(label) = label {
node.set_label(label);
}
commands
.entity(entity)

View file

@ -221,7 +221,7 @@ pub fn ui_layout_system(
|| node.is_changed()
|| content_size
.as_ref()
.map(|c| c.measure.is_some())
.map(|c| c.is_changed() || c.measure.is_some())
.unwrap_or(false)
{
let layout_context = LayoutContext::new(

View file

@ -8,7 +8,7 @@
//! This crate contains Bevy's UI system, which can be used to create UI for both 2D and 3D games
//! # Basic usage
//! Spawn UI elements with [`widget::Button`], [`UiImage`], [`Text`](prelude::Text) and [`Node`]
//! Spawn UI elements with [`widget::Button`], [`ImageNode`], [`Text`](prelude::Text) and [`Node`]
//! This UI is laid out with the Flexbox and CSS Grid layout models (see <https://cssreference.io/flexbox/>)
pub mod measurement;
@ -40,7 +40,7 @@ pub use measurement::*;
pub use render::*;
pub use ui_material::*;
pub use ui_node::*;
use widget::{UiImage, UiImageSize};
use widget::{ImageNode, ImageNodeSize};
/// The UI prelude.
///
@ -58,11 +58,11 @@ pub mod prelude {
node_bundles::*,
ui_material::*,
ui_node::*,
widget::{Button, Label, UiImage},
widget::{Button, ImageNode, Label},
Interaction, MaterialNode, UiMaterialPlugin, UiScale,
},
// `bevy_sprite` re-exports for texture slicing
bevy_sprite::{BorderRect, ImageScaleMode, SliceScaleMode, TextureSlicer},
bevy_sprite::{BorderRect, SliceScaleMode, SpriteImageMode, TextureSlicer},
};
}
@ -155,8 +155,8 @@ impl Plugin for UiPlugin {
.register_type::<RelativeCursorPosition>()
.register_type::<ScrollPosition>()
.register_type::<TargetCamera>()
.register_type::<UiImage>()
.register_type::<UiImageSize>()
.register_type::<ImageNode>()
.register_type::<ImageNodeSize>()
.register_type::<UiRect>()
.register_type::<UiScale>()
.register_type::<BorderColor>()
@ -208,7 +208,7 @@ impl Plugin for UiPlugin {
update_clipping_system.after(TransformSystem::TransformPropagate),
// Potential conflicts: `Assets<Image>`
// They run independently since `widget::image_node_system` will only ever observe
// its own UiImage, and `widget::text_system` & `bevy_text::update_text2d_layout`
// its own ImageNode, and `widget::text_system` & `bevy_text::update_text2d_layout`
// will never modify a pre-existing `Image` asset.
widget::update_image_content_size_system
.in_set(UiSystem::Prepare)
@ -265,7 +265,7 @@ fn build_text_interop(app: &mut App) {
// Since both systems will only ever insert new [`Image`] assets,
// they will never observe each other's effects.
.ambiguous_with(bevy_text::update_text2d_layout)
// We assume Text is on disjoint UI entities to UiImage and UiTextureAtlasImage
// We assume Text is on disjoint UI entities to ImageNode and UiTextureAtlasImage
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(widget::update_image_content_size_system),
widget::text_system

View file

@ -2,9 +2,9 @@
#![expect(deprecated)]
use crate::{
widget::{Button, UiImageSize},
BackgroundColor, BorderColor, BorderRadius, ComputedNode, ContentSize, FocusPolicy,
Interaction, MaterialNode, Node, ScrollPosition, UiImage, UiMaterial, ZIndex,
widget::{Button, ImageNodeSize},
BackgroundColor, BorderColor, BorderRadius, ComputedNode, ContentSize, FocusPolicy, ImageNode,
Interaction, MaterialNode, Node, ScrollPosition, UiMaterial, ZIndex,
};
use bevy_ecs::bundle::Bundle;
use bevy_render::view::{InheritedVisibility, ViewVisibility, Visibility};
@ -57,16 +57,10 @@ pub struct NodeBundle {
}
/// A UI node that is an image
///
/// # Extra behaviors
///
/// You may add one or both of the following components to enable additional behaviors:
/// - [`ImageScaleMode`](bevy_sprite::ImageScaleMode) to enable either slicing or tiling of the texture
/// - [`TextureAtlas`](bevy_sprite::TextureAtlas) to draw a specific section of the texture
#[derive(Bundle, Debug, Default)]
#[deprecated(
since = "0.15.0",
note = "Use the `UiImage` component instead. Inserting `UiImage` will also insert the other components required automatically."
note = "Use the `ImageNode` component instead. Inserting `ImageNode` will also insert the other components required automatically."
)]
pub struct ImageBundle {
/// Describes the logical size of the node
@ -79,7 +73,7 @@ pub struct ImageBundle {
/// The image of the node.
///
/// To tint the image, change the `color` field of this component.
pub image: UiImage,
pub image: ImageNode,
/// The color of the background that will fill the containing node.
pub background_color: BackgroundColor,
/// The border radius of the node
@ -87,7 +81,7 @@ pub struct ImageBundle {
/// The size of the image in pixels
///
/// This component is set automatically
pub image_size: UiImageSize,
pub image_size: ImageNodeSize,
/// Whether this node should block interaction with lower nodes
pub focus_policy: FocusPolicy,
/// The transform of the node
@ -110,12 +104,6 @@ pub struct ImageBundle {
}
/// A UI node that is a button
///
/// # Extra behaviors
///
/// You may add one or both of the following components to enable additional behaviors:
/// - [`ImageScaleMode`](bevy_sprite::ImageScaleMode) to enable either slicing or tiling of the texture
/// - [`TextureAtlas`](bevy_sprite::TextureAtlas) to draw a specific section of the texture
#[derive(Bundle, Clone, Debug)]
#[deprecated(
since = "0.15.0",
@ -138,7 +126,7 @@ pub struct ButtonBundle {
/// The border radius of the node
pub border_radius: BorderRadius,
/// The image of the node
pub image: UiImage,
pub image: ImageNode,
/// The background color that will fill the containing node
pub background_color: BackgroundColor,
/// The transform of the node

View file

@ -32,7 +32,7 @@ use bevy_render::{
use bevy_transform::prelude::GlobalTransform;
use bytemuck::{Pod, Zeroable};
use super::{QUAD_INDICES, QUAD_VERTEX_POSITIONS};
use super::{stack_z_offsets, QUAD_INDICES, QUAD_VERTEX_POSITIONS};
pub const BOX_SHADOW_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(17717747047134343426);
@ -206,6 +206,7 @@ impl SpecializedRenderPipeline for BoxShadowPipeline {
alpha_to_coverage_enabled: false,
},
label: Some("box_shadow_pipeline".into()),
zero_initialize_workgroup_memory: false,
}
}
}
@ -365,7 +366,7 @@ pub fn queue_shadows(
pipeline,
entity: (*entity, extracted_shadow.main_entity),
sort_key: (
FloatOrd(extracted_shadow.stack_index as f32 - 0.1),
FloatOrd(extracted_shadow.stack_index as f32 + stack_z_offsets::BOX_SHADOW),
entity.index(),
),
batch_range: 0..0,

View file

@ -4,7 +4,7 @@ mod render_pass;
mod ui_material_pipeline;
pub mod ui_texture_slice_pipeline;
use crate::widget::UiImage;
use crate::widget::ImageNode;
use crate::{
experimental::UiChildren, BackgroundColor, BorderColor, CalculatedClip, ComputedNode,
DefaultUiCamera, Outline, ResolvedBorderRadius, TargetCamera, UiAntiAlias, UiBoxShadowSamples,
@ -41,7 +41,7 @@ use bevy_render::{
ExtractSchedule, Render,
};
use bevy_sprite::TextureAtlasLayout;
use bevy_sprite::{BorderRect, ImageScaleMode, SpriteAssetEvents};
use bevy_sprite::{BorderRect, SpriteAssetEvents};
use crate::{Display, Node};
use bevy_text::{ComputedTextBlock, PositionedGlyph, TextColor, TextLayoutInfo};
@ -75,7 +75,7 @@ pub mod graph {
/// When this is _not_ possible, pick a suitably unique index unlikely to clash with other things (ex: `0.1826823` not `0.1`).
///
/// Offsets should be unique for a given node entity to avoid z fighting.
/// These should pretty much _always_ be larger than -1.0 and smaller than 1.0 to avoid clipping into nodes
/// These should pretty much _always_ be larger than -0.5 and smaller than 0.5 to avoid clipping into nodes
/// above / below the current node in the stack.
///
/// A z-index of 0.0 is the baseline, which is used as the primary "background color" of the node.
@ -83,7 +83,9 @@ pub mod graph {
/// Note that nodes "stack" on each other, so a negative offset on the node above could clip _into_
/// a positive offset on a node below.
pub mod stack_z_offsets {
pub const BACKGROUND_COLOR: f32 = 0.0;
pub const BOX_SHADOW: f32 = -0.1;
pub const TEXTURE_SLICE: f32 = 0.0;
pub const NODE: f32 = 0.0;
pub const MATERIAL: f32 = 0.18267;
}
@ -108,7 +110,7 @@ pub fn build_ui_render(app: &mut App) {
render_app
.init_resource::<SpecializedRenderPipelines<UiPipeline>>()
.init_resource::<UiImageBindGroups>()
.init_resource::<ImageNodeBindGroups>()
.init_resource::<UiMeta>()
.init_resource::<ExtractedUiNodes>()
.allow_ambiguous_resource::<ExtractedUiNodes>()
@ -309,18 +311,15 @@ pub fn extract_uinode_images(
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
default_ui_camera: Extract<DefaultUiCamera>,
uinode_query: Extract<
Query<
(
Entity,
&ComputedNode,
&GlobalTransform,
&ViewVisibility,
Option<&CalculatedClip>,
Option<&TargetCamera>,
&UiImage,
),
Without<ImageScaleMode>,
>,
Query<(
Entity,
&ComputedNode,
&GlobalTransform,
&ViewVisibility,
Option<&CalculatedClip>,
Option<&TargetCamera>,
&ImageNode,
)>,
>,
mapping: Extract<Query<RenderEntity>>,
) {
@ -338,6 +337,7 @@ pub fn extract_uinode_images(
if !view_visibility.get()
|| image.color.is_fully_transparent()
|| image.image.id() == TRANSPARENT_IMAGE_HANDLE.id()
|| image.image_mode.uses_slices()
{
continue;
}
@ -863,7 +863,7 @@ pub fn queue_uinodes(
pipeline,
entity: (*entity, extracted_uinode.main_entity),
sort_key: (
FloatOrd(extracted_uinode.stack_index as f32 + stack_z_offsets::BACKGROUND_COLOR),
FloatOrd(extracted_uinode.stack_index as f32 + stack_z_offsets::NODE),
entity.index(),
),
// batch_range will be calculated in prepare_uinodes
@ -874,7 +874,7 @@ pub fn queue_uinodes(
}
#[derive(Resource, Default)]
pub struct UiImageBindGroups {
pub struct ImageNodeBindGroups {
pub values: HashMap<AssetId<Image>, BindGroup>,
}
@ -887,7 +887,7 @@ pub fn prepare_uinodes(
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
view_uniforms: Res<ViewUniforms>,
ui_pipeline: Res<UiPipeline>,
mut image_bind_groups: ResMut<UiImageBindGroups>,
mut image_bind_groups: ResMut<ImageNodeBindGroups>,
gpu_images: Res<RenderAssets<GpuImage>>,
mut phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
events: Res<SpriteAssetEvents>,

View file

@ -121,6 +121,7 @@ impl SpecializedRenderPipeline for UiPipeline {
alpha_to_coverage_enabled: false,
},
label: Some("ui_pipeline".into()),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -1,6 +1,6 @@
use core::ops::Range;
use super::{UiBatch, UiImageBindGroups, UiMeta};
use super::{ImageNodeBindGroups, UiBatch, UiMeta};
use crate::DefaultCameraView;
use bevy_ecs::{
prelude::*,
@ -185,7 +185,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetUiViewBindGroup<I> {
}
pub struct SetUiTextureBindGroup<const I: usize>;
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetUiTextureBindGroup<I> {
type Param = SRes<UiImageBindGroups>;
type Param = SRes<ImageNodeBindGroups>;
type ViewQuery = ();
type ItemQuery = Read<UiBatch>;

View file

@ -199,6 +199,7 @@ where
alpha_to_coverage_enabled: false,
},
label: Some("ui_material_pipeline".into()),
zero_initialize_workgroup_memory: false,
};
if let Some(vertex_shader) = &self.vertex_shader {
descriptor.vertex.shader = vertex_shader.clone();

Some files were not shown because too many files have changed in this diff Show more