Posted on April 15, 2022 by Carter Anderson
For those who don't know, Bevy is a refreshingly simple data-driven game engine built in Rust. You can check out Quick Start Guide to get started. Bevy is also free and open source forever! You can grab the full source code on GitHub. Check out Bevy Assets for a collection of community-developed plugins, games, and learning resources.
To update an existing Bevy App or Plugin to Bevy 0.7, check out our 0.6 to 0.7 Migration Guide.
As always, there are a ton of new features, bug fixes, and quality of life tweaks in this release, but here are some of the highlights:
- Skeletal animation and mesh skinning
- GLTF animation importing
- Unlimited* point lights in a scene
- Improved clustered forward rendering: dynamic/adaptive clustering and faster, more accurate cluster assignment
- Compressed texture support (KTX2 / DDS / .basis): load more textures in a scene, faster
- Compute shader / pipeline specialization: Bevy's flexible shader system was ported to compute shaders, enabling hot reloading, shader defs, and shader imports
- Render to texture: cameras can now be configured to render to a texture instead of a window
- Flexible mesh vertex layouts in shaders
- ECS improvements: Order systems using their names, Query::many_mut, use conflicting parameters in systems via ParamSets, WorldQuery derives
- Documentation improvements: better examples, more doc tests and more coverage
- More audio control: pause, volume, speed, and looping
- Power usage options to enable only updating Bevy Apps when input occurs
Skeletal Animation #
Bevy finally supports 3D skeletal animation!
This critical feature has been a long time coming, but we wanted to build it in a way that meshed nicely with the new Bevy renderer and didn't just "hack things in". This builds on our new Flexible Mesh Vertex Layouts, Shader Imports, and Material systems, which ensures that this logic is flexible and reusable, even with non-standard meshes and custom render pipelines.
And we're just getting started! Multi-track animation blending and higher level animation state management should arrive in the very near future. Now is a great time to start contributing animation features to Bevy. We've smashed through most of the foundational technical hurdles and what remains is largely high level api design choices. We already have a couple of draft RFCs open in these areas: Animation Composition and Animation Primitives. Feel free to join the conversation!
GLTF Animation Importing #
Bevy's GLTF importer was extended to import GLTF animations into the new
AnimationPlayer system. This supports both "skeletal animation" and arbitrary transform animations:
Unlimited* Point Lights #
Bevy can now render scenes with arbitrary numbers of point lights on platforms that support storage buffers (which is basically everything but WebGL). In the last Bevy release (0.6) we added Clustered Forward Rendering, which is a rendering technique that optimizes each fragment's light calculation costs by assigning lights to sub-volumes of the visible volume, called "clusters". However in the interest of platform compatibility (WebGL), we initially limited ourselves to 256 lights because that is what fit in a uniform buffer binding.
In Bevy 0.7, we added the ability to automatically "upgrade" to using unbounded storage buffers for Clustered Forward Rendering on platforms that support them, enabling unlimited* point lights. There is an asterisk there because in practice this is limited by memory and hardware constraints.
Light Clustering Features and Optimizations #
With the upper limit of 256 point lights removed, the only limit on lights is what the hardware can support and bottlenecks in our algorithms. To increase the number of lights, we made a number of optimizations to our clustering algorithms.
- Dynamic Light Clusters
- By default cluster x/y slices are now dynamically configured based on the lights in the scene, which can significantly increase performance in some scenes.
- The clustering behavior is now also user-configurable as FixedZ (the new default dynamic x/y behavior, fixing the number of z slices), custom fixed x/y/z slice values, single-cluster, and "no clustering", giving you control when you know a certain cluster configuration will perform even better.
- Additionally, in 0.6 the visible volume that is covered by all the clusters basically matched the full visible volume of the view frustum. This meant that if all the point lights were in the foreground, all the clusters beyond the lights were wasted space. In 0.7, it is possible to limit the far bound to be closer than the camera far bound, which means the lights can be spread across more clusters, which can significantly increase rendering performance.
- Iterative Sphere Refinement: Bevy now uses the Just Cause 3 iterative sphere refinement approach to cluster assignment, which gives us a ~10% performance increase on some benchmarks and more accurate clustering (which can also improve render performance).
- Light Frustum Change Detection: We now use Bevy ECS's change detection feature to only recalculate the view frustum of lights that have changed.
- Cluster Assignment Optimizations: The cluster assignment data access patterns and data structures received a variety of tweaks that improved performance.
Here is a video illustrating a progression from the old limit of 256 point lights to 25,000 point lights at 60fps!
(Note that the 25,000 lights example disables the debug light spheres to ensure that light calculations are the bottleneck)
And we have even more clustering optimizations in the works!
Configurable Light Visibility #
Lights can now be turned on and off using Bevy's standard
Compressed GPU Textures #
As scenes grow larger, so do their assets. Compressing these assets is a great way to save space. The Amazon Bistro scene featured below has well over 1GB of compressed textures.
PNG is a popular compressed format, but it must be decompressed before the GPU can use it. This can be a slow process for large scenes. Those textures are then used in their uncompressed form, taking up large quantities of limited memory. Compressed GPU textures can be used directly in their compressed format by the GPU and can be loaded without any additional processing. This reduces load times significantly. As they remain compressed, this also reduces RAM usage significantly.
The Bistro scene took a total of 12.9s to load with PNG textures, but only 1.5s with compressed textures - taking approximately a tenth of the load time! The total RAM usage was ~12GB with uncompressed textures, and 5GB with compressed textures, less than half!
The benefits don't stop there either - because the textures are compressed and can be used by the GPU in that format, reading from them uses less memory bandwidth, which can bring performance benefits. The Bistro scene gains about 10% in frame rate from using compressed textures.
Another benefit is that mipmaps are supported, which makes for smoother, less noisy textures. Bevy currently doesn't have support for automatically generating mipmaps for uncompressed textures, so using compressed textures is a nice way to have mipmaps now!
In summary, Bevy now supports loading compressed textures from
.basis files. This includes support for the standard ASTC, BCn, and ETC2 formats, as well as 'universal' formats like ETC1S and UASTC that can be transcoded to the standard formats supported by specific systems at runtime. The glTF loader was also extended to support loading these formats.
These features can be enabled using the
basis-universal cargo features.
Render To Texture #
Bevy now has initial support for rendering to texture by configuring the
render_target field on
Camera. This enables scenarios such as mirrors, split screen, 2d UI in 3d space, portals, etc.
Note that the current implementation is relatively low level. It will generally require interacting with Bevy's Render Graph and defining new camera types. If you would like to use this feature now, the render_to_texture example illustrates the steps required. We have plans for "high level render targets" that will make rendering to textures possible in just a few lines of code. Stay tuned for details!
Bevy-Native Compute Shaders #
Flexible Mesh Vertex Layouts #
In Bevy 0.7, it is now easy to make shaders support any Mesh vertex layout and arbitrary vertex attributes. Bevy's "shader pipeline specialization" system was extended to support "specializing on mesh vertex layouts".
For most Bevy users, this means that Materials, including the built in
StandardMaterial and custom shader materials now support arbitrary Meshes automatically, provided those Meshes have the vertex attributes required by the material shaders. It also means that if your Mesh is missing any attribute required by its material, rendering can fail gracefully.
We also made use of this system to implement joint weights and indices for our new Skeletal Animation implementation.
For Bevy users that like to write lower level graphics pipelines, this feature makes it possible to easily and efficiently specialize your pipelines according to Mesh vertex layouts:
Camera Marker Components #
In Bevy 0.7, Cameras now use the "marker component" pattern to determine the "camera type" (ex: 3D, 2D, UI), rather than using string names.
This means that it is now cheaper and easier to select cameras of a specific type:
Ergonomic System Ordering #
Bevy uses "labels" to define ordering constraints between its ECS systems when they run in parallel. In previous versions of Bevy, the only way to order systems was to define custom labels:
; app .add_system .add_system
In Bevy 0.7, manually defining labels is no longer required. You can order systems using functions, just like you do when adding systems!
app .add_system .add_system
The Bevy ECS labeling system is powerful and there are still legitimate use cases for custom labels (such as labeling multiple systems with the same label and exporting a stable public API as a plugin author). But most common use cases can take advantage of the ergonomic auto-labeling functionality.
Default Shorthand #
Bevy makes heavy use of Rust's struct update pattern in combination with the
Default trait when initializing entities. This significantly reduces the amount of typing required by enabling developers to only fill in the fields they want to change.
The standard way of doing this is to write out
This is much better than filling in each field's component manually:
However this can feel repetitive when you're doing it for tens or hundreds of entities. We added a way to make this even easier, without needing to resort to macros:
This is equivalent in functionality to
..Default::default(), it's just more compressed. And you can still use the longer form if you prefer. The
default() function is included in Bevy's prelude, so you don't need to manually import it. Ergonomics for the win!
Bevy ECS solves a hard problem: providing easy and fast access to data in parallel while still respecting Rust's strict mutability and ownership rules. Since our first release, we've supported efficiently accessing specific entities in our ECS Queries:
However, to respect Rust's mutability rules, we need to disallow apis that might produce "aliased mutability". Seasoned Bevy users will probably recognize this Rust borrow checker error:
You know Entity A and Entity B are different entities at runtime. But Rust's borrow checker has no way to know that at compile time! I'm sure you can imagine game development scenarios that would benefit from having mutable access to multiple components at the same time. This borrow checker restriction was a common pain point and the workarounds were ... not fun (using scopes to ensure conflicting accesses are dropped, copying data, re-querying things, etc).
Fortunately, Bevy 0.7 introduces a brand new set of apis to save the day!
There are plenty of variants:
// Same as many_mut, but returns a Result instead of panicking if let Ok = query.get_many_mut // There are also immutable/read-only variants let = query.many; if let Ok = query.get_many
And they all support arbitrary numbers of entities:
let = query.many;
To prevent aliased mutability, Bevy ECS disallows systems that have parameters that conflict with each other. For example, if two Queries both request write access to the same component in the same "archetype", that could result in aliased mutable access, so Bevy disallows that system and errors out.
Previous versions of Bevy supported conflicting Queries in the same system using QuerySets, which only allow access to one Query in the set at a time:
// These queries could each return a mutable A component for the same entity, so they must be put in a set to be considered a valid system.
Bevy 0.7 removes
QuerySet in favor of
ParamSet, which generalizes the QuerySet pattern for any system parameter:
But ParamSets aren't just limited to Queries! Consider this example, where the
EventWriter<Jump> parameter (which internally accesses the
Events<Jump> resource) conflicts with the raw access to that resource. Previously, expressing this wouldn't be possible. But with ParamSets, it is!
We still recommend avoiding ParamSets where possible for clarity's sake. But every so often they are a necessary and useful tool!
Deref / DerefMut Derives #
Rust encourages the use of the newtype pattern when expanding a type with new functionality or meaning. This is also a useful tool in Bevy:
This works just fine, but that
0 at the end of
items.0 sticks out like a sore thumb. Many of us in the Bevy Org think
.0 has no place in public apis. But the newtype pattern is still useful! Ideally, Rust would provide a way to express that
Items is a new type, while transparently provided access to the
Vec<Item> stored within. There are designs being discussed by the Rust team, but we don't want to wait for nice things!
Fortunately, the Deref / DerefMut traits in std provide the behavior we want. Users can already manually implement these traits, but for such a common pattern, we decided that providing our own trait derives was worth it. In Bevy 0.7, you can now derive Deref and DerefMut, enabling much nicer public apis:
std doc readers might notice that the Rust team recommends only using
DerefMut for smart pointers, to avoid confusion. Components like
Items are not smart pointers. We choose to ignore this advice, as this pattern works, is already widely used in the Rust ecosystem, and Good UX Comes First.
WorldQuery Derives #
Sometimes when building Bevy Apps you might find yourself repeating the same sets of components over and over in your queries:
We've noticed that the majority of direct
World resource access immediately unwraps the results of
let time = world..unwrap;
In Bevy 0.7 we added an ergonomic variant that internally panics:
let time = world.;
There is also a mutable variant:
let mut time = world.;
get_resource variants are still available for cases where users still want to manually handle the returned
AnyOf Queries #
Bevy ECS Queries now support
AnyOf, which will return results for entities that match "any of" the given component queries:
For the example above
AnyOf will return entities that have A and not B, B and not A, and both A and B.
&World System Param #
It is now possible for "normal systems" have
&World system params, which provide full read-only access to the entire
Just keep in mind that
&World will conflict with any mutable Query:
In these cases, consider using our new ParamSets to resolve the conflict:
ECS Soundness / Correctness Improvements #
Bevy ECS received a solid number of soundness and correctness bug fixes this release:
- Removed unsound lifetime annotations on
Query, which could be used to get aliased mutability in some situations.
World::entities_mutunsafe (because manually modifying entity metadata can invalidate safety assumptions)
- Removed unsound
World::components_mut(which allowed replacing component metadata, invalidating assumptions made elsewhere in World)
- Fixed a
ManuallyDropin resource id initialization instead of
forget()to avoid invalidating a data pointer before it is used.
We now also run the miri interpreter on Bevy ECS in our CI to help detect and prevent future soundness / correctness issues.
As Bevy ECS matures, our bar for unsafe code blocks and soundness must also mature. Bevy ECS will probably never be 100% free of unsafe code blocks because we are modeling parallel data access that Rust cannot reason about without our help. But we are committed to removing as much unsafe code as we can and improving the quality and scope of our unsafe code.
Audio Control #
Bevy's audio system has been in a ... minimalist state since our first release. Until now, it only supported pressing "play" on audio assets. Third party plugins such as bevy_kira_audio have filled in the gaps with much more flexible audio solutions.
In Bevy 0.7 we've started expanding what our built in audio plugin can do. It is now possible to pause, adjust volume, and set playback speed using
Playing audio now returns a
Handle<AudioSink> which can be used to play/pause/set_speed/set_volume:
; // later in another system
You can also now loop audio playback:
We plan to continue iterating on these APIs with even more functionality and usability improvements!
Sprite Anchors #
Sprite components can now define an
Anchor point (also known as a "pivot" point), which determines the "origin" of the sprite. Sprites still default to a "center" origin, but this is now configurable:
EventLoop Power Saving Modes #
By default Bevy will run updates "as fast as it can" (limited by the screen's refresh rate). This is great for most games, but some application types (such as GUI apps) need to prioritize CPU and GPU power usage.
Bevy 0.7 adds the ability to configure the [
UpdateMode] in [
WinitConfig] to configure how Bevy Apps run updates:
- Continuous: always update "as soon as possible" (honoring vsync configuration)
- Reactive: only update when there is a window event, a redraw is requested, or a configurable wait time has elapsed
- ReactiveLowPower: only update when there is user input (mouse movement, keyboard input, etc), a redraw is requested, or a configurable wait time has elapsed
These settings can be configured separately for focused windows and unfocused windows (enabling you to save power when a window loses focus).
ReactiveLowPower can significantly reduce power / resource usage, but it won't be suitable for every app type, as some apps need to assume that they are constantly being updated as quickly as possible. Therefore these settings are opt-in.
This app demos the various modes available. Note that Game mode was configured to lower its tick rate when it loses focus, which is not the default:
Documentation improvements #
Great docs make learning, using and building Bevy better. But as a young engine, they're still a work-in-progress.
Our docs team (led by
@alice-i-cecile) has started to systematically fix that, with the help of Rust's
Since 0.6, we've fully documented (and prevented doc-regressions for):
There have been many other doc improvements over this time period as well, including the addition of many helpful doc tests, and our bar for docs in new code continues to rise. A huge thanks to everyone making Bevy's docs better.
New contributors #
If you're interested in contributing, the docs team is always ready to help new contributors get their first Bevy PR merged ASAP. There have been a ton of new contributors who've helped out with docs, either as a writer or a reviewer. If this is you: thanks!
Better examples #
For many people, the best way to learn a tool is to see it in action. We've been steadily polishing our examples with better explanations, more coverage, and higher code quality. If you're new to Bevy, check out the much-improved Breakout example!
Dev Docs #
We now automatically deploy Bevy's
main development branch to https://dev-docs.bevyengine.org whenever a change is merged. This will help Bevy documentation authors easily validate their changes. And "bleeding edge" Bevy users can learn about API changes we're working on.
Website Improvements #
The Bevy Book now has a much nicer pager widget that displays previous / next section names:
We also added an "improve this page" footer link to make it easier for Bevy Book readers to contribute changes.
The sidebar got an overhaul that improves clarity and makes it possible to open/close sections without clicking on them:
The responsiveness of the website has also been improved and some sections layout much better on mobile.
Scene Viewer Tool #
Bevy now has a dedicated scene viewer tool that can load arbitrary GLTF scene files. If you check out the main Bevy repo you can try it out by running:
cargo run --release --example scene_viewer /some/path/castle.gltf
It has a built in "fly camera" and has tools to play animations and toggle lights and shadows.
Support Bevy #
Sponsorships help make my full time work on Bevy sustainable. If you believe in Bevy's mission, consider sponsoring me (@cart) ... every bit helps!
A huge thanks to the 123 contributors that made this release (and associated docs) possible! In random order:
Full Change Log #
- Mesh Skinning
- Animation Player
- Gltf animations
- Mesh vertex buffer layouts
- Render to a texture
- KTX2/DDS/.basis compressed texture support
- Audio control - play, pause, volume, speed, loop
- Auto-label function systems with SystemTypeIdLabel
- Dynamic light clusters
- Always update clusters and remove per-frame allocations
- default() shorthand
- use marker components for cameras instead of name strings
- Implement AnyOf queries
- Compute Pipeline Specialization
- Make get_resource (and friends) infallible
- bevy_pbr: Support flipping tangent space normal map y for DirectX normal maps
- Faster view frustum culling
- Use storage buffers for clustered forward point lights
- Add &World as SystemParam
- Add text wrapping support to Text2d
- Scene Viewer to display glTF files
- Internal Asset Hot Reloading
- Add FocusPolicy to NodeBundle and ImageBundle
- Allow iter combinations on queries with filters
- bevy_render: Support overriding wgpu features and limits
- bevy_render: Use RenderDevice to get limits/features and expose AdapterInfo
- Reduce power usage with configurable event loop
- can specify an anchor for a sprite
- Implement len and is_empty for EventReaders
- Add more FromWorld implementations
- Add cart's fork of ecs_bench_suite
- bevy_derive: Add derives for
- Add clear_schedule
- Add Query::contains
- bevy_render: Support removal of nodes, edges, subgraphs
- Implement init_resource for
- Added method to restart the current state
- Simplify sending empty events
- impl Command for <impl FnOnce(&mut World)>
- Useful error message when two assets have the save UUID
- bevy_asset: Add AssetServerSettings watch_for_changes member
- Add conversio from Color to u32
RenderAssetPlugin, and change
Imagepreparation system to run before others
- Add a helper for storage buffers similar to
- StandardMaterial: expose a cull_mode option
- Expose draw indirect
- Add view transform to view uniform
- Add a size method on Image.
- add Visibility for lights
- bevy_render: Provide a way to opt-out of the built-in frustum culling
- use error scope to handle errors on shader module creation
- include sources in shader validation error
- insert the gltf mesh name on the entity if there is one
- expose extras from gltf nodes
- gltf: add a name to nodes without names
- Enable drag-and-drop events on windows
- Add transform hierarchy stress test
- Add TransformBundle
- Add Transform::rotate_around method
- example on how to create an animation in code
- Add examples for Transforms
- Add mouse grab example
- examples: add screenspace texture shader example
- Add generic systems example
- add examples on how to have a data source running in another thread / in a task pool thread
- Simple 2d rotation example
- Add move sprite example.
- add an example using UI & states to create a game menu
- CI runs
cargo miri test -p bevy_ecs
- Tracy spans around main 3D passes
- Add automatic docs deployment to GitHub Pages
- Proper prehashing
- Move import_path definitions into shader source
Systemresponsible for updating its own archetypes
- Some small changes related to run criteria piping
- Remove unnecessary system labels
- Increment last event count on next instead of iter
- Obviate the need for
RunSystem, and remove it
- Cleanup some things which shouldn't be components
- Remove the config api
- Hide docs for concrete impls of Fetch, FetchState, and SystemParamState
- Move the CoreStage::Startup to a seperate StartupSchedule label
iter_muton Assets: send modified event only when asset is iterated over
- check if resource for asset already exists before adding it
- bevy_render: Batch insertion for prepare_uniform_components
- Change default
ColorMaterialcolor to white
- bevy_render: Only auto-disable mappable primary buffers for discrete GPUs
- bevy_render: Do not automatically enable MAPPABLE_PRIMARY_BUFFERS
- increase the maximum number of point lights with shadows to the max supported by the device
- perf: only recalculate frusta of changed lights
- bevy_pbr: Optimize assign_lights_to_clusters
- improve error messages for render graph runner
- Skinned extraction speedup
- Sprites - keep color as 4 f32
- Change scaling mode to FixedHorizontal
- Replace VSync with PresentMode
- do not set cursor grab on window creation if not asked for
- bevy_transform: Use Changed in the query for much faster transform_propagate_system
- Split bevy_hierarchy out from bevy_transform
- Make transform builder methods const
- many_cubes: Add a cube pattern suitable for benchmarking culling changes
- Make many_cubes example more interesting
- Run tests (including doc tests) in
cargo run -p cicommand
- Use more ergonomic span syntax
- Remove unsound lifetime annotations on
- Remove unsound lifetime annotations on
- Use ManuallyDrop instead of forget in insert_resource_with_id
- Backport soundness fix
- Fix clicked UI nodes getting reset when hovering child nodes
- Fix ui interactions when cursor disappears suddenly
- Fix node update
- Fix derive(SystemParam) macro
- SystemParam Derive fixes
- Do not crash if RenderDevice doesn't exist
- Fixed case of R == G, following original conversion formula
- Fixed the frustum-sphere collision and added tests
- bevy_render: Fix Quad flip
- Fix HDR asset support
- fix cluster tiling calculations
- bevy_pbr: Do not panic when more than 256 point lights are added the scene
- fix issues with too many point lights
- shader preprocessor - do not import if scope is not valid
- support all line endings in shader preprocessor
- Fix animation: shadow and wireframe support
- add AnimationPlayer component only on scene roots that are also animation roots
- Fix loading non-TriangleList meshes without normals in gltf loader
- gltf-loader: disable backface culling if material is double-sided
- Fix glTF perspective camera projection
- fix mul_vec3 transformation order: should be scale -> rotate -> translate