mirror of
https://github.com/bevyengine/bevy
synced 2024-11-23 21:23:05 +00:00
Merge branch 'main' into add-transform-lerp
This commit is contained in:
commit
6e84ad5a92
141 changed files with 1069 additions and 814 deletions
|
@ -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: ''
|
||||
---
|
||||
|
||||
|
|
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
@ -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
|
||||
|
|
2
.github/workflows/validation-jobs.yml
vendored
2
.github/workflows/validation-jobs.yml
vendored
|
@ -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
|
||||
|
|
2
.github/workflows/weekly.yml
vendored
2
.github/workflows/weekly.yml
vendored
|
@ -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
|
||||
|
|
21
Cargo.toml
21
Cargo.toml
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?;
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
//! ));
|
||||
//! }
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -89,6 +89,7 @@ impl SpecializedComputePipeline for AutoExposurePipeline {
|
|||
AutoExposurePass::Average => "compute_average".into(),
|
||||
},
|
||||
push_constant_ranges: vec![],
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,6 +98,7 @@ impl SpecializedRenderPipeline for BlitPipeline {
|
|||
..Default::default()
|
||||
},
|
||||
push_constant_ranges: Vec::new(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,6 +127,7 @@ impl SpecializedRenderPipeline for BloomDownsamplingPipeline {
|
|||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: Vec::new(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -124,6 +124,7 @@ impl SpecializedRenderPipeline for BloomUpsamplingPipeline {
|
|||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: Vec::new(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -233,6 +233,7 @@ impl SpecializedRenderPipeline for CasPipeline {
|
|||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: Vec::new(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -160,6 +160,7 @@ impl FromWorld for CopyDeferredLightingIdPipeline {
|
|||
}),
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: vec![],
|
||||
zero_initialize_workgroup_memory: false,
|
||||
});
|
||||
|
||||
Self {
|
||||
|
|
|
@ -806,6 +806,7 @@ impl SpecializedRenderPipeline for DepthOfFieldPipeline {
|
|||
},
|
||||
targets,
|
||||
}),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -196,6 +196,7 @@ impl SpecializedRenderPipeline for FxaaPipeline {
|
|||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: Vec::new(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,6 +141,7 @@ impl SpecializedRenderPipeline for MotionBlurPipeline {
|
|||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: vec![],
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -208,6 +208,7 @@ fn specialize_oit_resolve_pipeline(
|
|||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: vec![],
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -344,6 +344,7 @@ impl SpecializedRenderPipeline for PostProcessingPipeline {
|
|||
depth_stencil: None,
|
||||
multisample: default(),
|
||||
push_constant_ranges: vec![],
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -233,6 +233,7 @@ impl SpecializedRenderPipeline for SkyboxPipeline {
|
|||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -355,6 +355,7 @@ impl SpecializedRenderPipeline for TaaPipeline {
|
|||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: Vec::new(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -307,6 +307,7 @@ impl SpecializedRenderPipeline for TonemappingPipeline {
|
|||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: Vec::new(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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>) {}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
] }
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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>()
|
||||
|
|
|
@ -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 \
|
||||
/// / \
|
||||
/// |-------------------------------------|
|
||||
|
|
|
@ -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))]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -390,6 +390,7 @@ impl SpecializedRenderPipeline for DeferredLightingLayout {
|
|||
}),
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: vec![],
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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::{
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}),
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2020,6 +2020,7 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
|||
alpha_to_coverage_enabled,
|
||||
},
|
||||
label: Some(label),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -560,6 +560,7 @@ impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline {
|
|||
primitive: default(),
|
||||
depth_stencil: None,
|
||||
multisample: default(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -600,6 +600,7 @@ impl SpecializedRenderPipeline for VolumetricFogPipeline {
|
|||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>>()
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -496,6 +496,7 @@ impl SpecializedRenderPipeline for ScreenshotToScreenPipeline {
|
|||
})],
|
||||
}),
|
||||
push_constant_ranges: Vec::new(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>()
|
||||
|
|
|
@ -674,6 +674,7 @@ impl SpecializedMeshPipeline for Mesh2dPipeline {
|
|||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
label: Some(label.into()),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -323,6 +323,7 @@ impl SpecializedRenderPipeline for SpritePipeline {
|
|||
},
|
||||
label: Some("sprite_pipeline".into()),
|
||||
push_constant_ranges: Vec::new(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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>()
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -121,6 +121,7 @@ impl SpecializedRenderPipeline for UiPipeline {
|
|||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
label: Some("ui_pipeline".into()),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue