refactor(render): move WgpuWrapper into bevy_utils (#19303)

# Objective

- A step towards splitting out bevy_camera from bevy_render

## Solution

- Move a shim type into bevy_utils to avoid a dependency cycle
- Manually expand Deref/DerefMut to avoid having a bevy_derive
dependency so early in the dep tree

## Testing

- It compiles
This commit is contained in:
atlv 2025-05-26 23:43:49 -04:00 committed by GitHub
parent 7d32dfec18
commit 1732c2253b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 77 additions and 54 deletions

View File

@ -65,7 +65,9 @@ bevy_render_macros = { path = "macros", version = "0.16.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", features = [
"wgpu_wrapper",
] }
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev" }
@ -151,9 +153,6 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-featu
"web",
] }
[target.'cfg(all(target_arch = "wasm32", target_feature = "atomics"))'.dependencies]
send_wrapper = "0.6.0"
[lints]
workspace = true

View File

@ -15,7 +15,8 @@ use wgpu::{
PipelineStatisticsTypes, QuerySet, QuerySetDescriptor, QueryType, RenderPass,
};
use crate::renderer::{RenderAdapterInfo, RenderDevice, RenderQueue, WgpuWrapper};
use crate::renderer::{RenderAdapterInfo, RenderDevice, RenderQueue};
use bevy_utils::WgpuWrapper;
use super::RecordDiagnostics;

View File

@ -103,7 +103,7 @@ use crate::{
mesh::{MeshPlugin, MorphPlugin, RenderMesh},
render_asset::prepare_assets,
render_resource::{PipelineCache, Shader, ShaderLoader},
renderer::{render_system, RenderInstance, WgpuWrapper},
renderer::{render_system, RenderInstance},
settings::RenderCreation,
storage::StoragePlugin,
view::{ViewPlugin, WindowRenderPlugin},
@ -112,6 +112,7 @@ use alloc::sync::Arc;
use bevy_app::{App, AppLabel, Plugin, SubApp};
use bevy_asset::{AssetApp, AssetServer};
use bevy_ecs::{prelude::*, schedule::ScheduleLabel};
use bevy_utils::WgpuWrapper;
use bitflags::bitflags;
use core::ops::{Deref, DerefMut};
use std::sync::Mutex;

View File

@ -1,4 +1,3 @@
use crate::renderer::WgpuWrapper;
use crate::{
define_atomic_id,
render_asset::RenderAssets,
@ -9,6 +8,7 @@ use crate::{
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::system::{SystemParam, SystemParamItem};
pub use bevy_render_macros::AsBindGroup;
use bevy_utils::WgpuWrapper;
use core::ops::Deref;
use encase::ShaderType;
use thiserror::Error;

View File

@ -1,5 +1,5 @@
use crate::define_atomic_id;
use crate::renderer::WgpuWrapper;
use bevy_utils::WgpuWrapper;
use core::ops::Deref;
define_atomic_id!(BindGroupLayoutId);

View File

@ -1,5 +1,5 @@
use crate::define_atomic_id;
use crate::renderer::WgpuWrapper;
use bevy_utils::WgpuWrapper;
use core::ops::{Bound, Deref, RangeBounds};
define_atomic_id!(BufferId);

View File

@ -1,12 +1,12 @@
use super::ShaderDefVal;
use crate::mesh::VertexBufferLayout;
use crate::renderer::WgpuWrapper;
use crate::{
define_atomic_id,
render_resource::{BindGroupLayout, Shader},
};
use alloc::borrow::Cow;
use bevy_asset::Handle;
use bevy_utils::WgpuWrapper;
use core::ops::Deref;
use wgpu::{
ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, PushConstantRange,

View File

@ -1,4 +1,3 @@
use crate::renderer::WgpuWrapper;
use crate::{
render_resource::*,
renderer::{RenderAdapter, RenderDevice},
@ -14,6 +13,7 @@ use bevy_ecs::{
use bevy_platform::collections::{hash_map::EntryRef, HashMap, HashSet};
use bevy_tasks::Task;
use bevy_utils::default;
use bevy_utils::WgpuWrapper;
use core::{future::Future, hash::Hash, mem, ops::Deref};
use naga::valid::Capabilities;
use std::sync::{Mutex, PoisonError};

View File

@ -1,7 +1,7 @@
use crate::define_atomic_id;
use crate::renderer::WgpuWrapper;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::resource::Resource;
use bevy_utils::WgpuWrapper;
use core::ops::Deref;
define_atomic_id!(TextureId);

View File

@ -4,6 +4,7 @@ mod render_device;
use bevy_derive::{Deref, DerefMut};
#[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
use bevy_tasks::ComputeTaskPool;
use bevy_utils::WgpuWrapper;
pub use graph_runner::*;
pub use render_device::*;
use tracing::{error, info, info_span, warn};
@ -120,46 +121,6 @@ pub fn render_system(world: &mut World, state: &mut SystemState<Query<Entity, Wi
}
}
/// A wrapper to safely make `wgpu` types Send / Sync on web with atomics enabled.
///
/// On web with `atomics` enabled the inner value can only be accessed
/// or dropped on the `wgpu` thread or else a panic will occur.
/// On other platforms the wrapper simply contains the wrapped value.
#[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
#[derive(Debug, Clone, Deref, DerefMut)]
pub struct WgpuWrapper<T>(T);
#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))]
#[derive(Debug, Clone, Deref, DerefMut)]
pub struct WgpuWrapper<T>(send_wrapper::SendWrapper<T>);
// SAFETY: SendWrapper is always Send + Sync.
#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))]
unsafe impl<T> Send for WgpuWrapper<T> {}
#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))]
unsafe impl<T> Sync for WgpuWrapper<T> {}
#[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
impl<T> WgpuWrapper<T> {
pub fn new(t: T) -> Self {
Self(t)
}
pub fn into_inner(self) -> T {
self.0
}
}
#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))]
impl<T> WgpuWrapper<T> {
pub fn new(t: T) -> Self {
Self(send_wrapper::SendWrapper::new(t))
}
pub fn into_inner(self) -> T {
self.0.take()
}
}
/// This queue is used to enqueue tasks for the GPU to execute asynchronously.
#[derive(Resource, Clone, Deref, DerefMut)]
pub struct RenderQueue(pub Arc<WgpuWrapper<Queue>>);

View File

@ -3,8 +3,8 @@ use crate::render_resource::{
BindGroup, BindGroupLayout, Buffer, ComputePipeline, RawRenderPipelineDescriptor,
RenderPipeline, Sampler, Texture,
};
use crate::WgpuWrapper;
use bevy_ecs::resource::Resource;
use bevy_utils::WgpuWrapper;
use wgpu::{
util::DeviceExt, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor,
BindGroupLayoutEntry, BufferAsyncError, BufferBindingType, MaintainResult,

View File

@ -1,12 +1,13 @@
use crate::{
render_resource::{SurfaceTexture, TextureView},
renderer::{RenderAdapter, RenderDevice, RenderInstance},
Extract, ExtractSchedule, Render, RenderApp, RenderSystems, WgpuWrapper,
Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
};
use bevy_app::{App, Plugin};
use bevy_ecs::{entity::EntityHashMap, prelude::*};
use bevy_platform::collections::HashSet;
use bevy_utils::default;
use bevy_utils::WgpuWrapper;
use bevy_window::{
CompositeAlphaMode, PresentMode, PrimaryWindow, RawHandleWrapper, Window, WindowClosing,
};

View File

@ -11,6 +11,8 @@ keywords = ["bevy"]
[features]
default = ["parallel"]
wgpu_wrapper = ["dep:send_wrapper"]
# Provides access to the `Parallel` type.
parallel = ["bevy_platform/std", "dep:thread_local"]
@ -19,6 +21,9 @@ bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-fea
thread_local = { version = "1.0", optional = true }
[target.'cfg(all(target_arch = "wasm32", target_feature = "atomics"))'.dependencies]
send_wrapper = { version = "0.6.0", optional = true }
[dev-dependencies]
static_assertions = "1.1.0"

View File

@ -48,6 +48,8 @@ pub mod prelude {
pub mod synccell;
pub mod syncunsafecell;
#[cfg(feature = "wgpu_wrapper")]
mod wgpu_wrapper;
mod default;
mod once;
@ -57,6 +59,9 @@ pub use once::OnceFlag;
pub use default::default;
#[cfg(feature = "wgpu_wrapper")]
pub use wgpu_wrapper::WgpuWrapper;
use core::mem::ManuallyDrop;
/// A type which calls a function when dropped.

View File

@ -0,0 +1,50 @@
/// A wrapper to safely make `wgpu` types Send / Sync on web with atomics enabled.
///
/// On web with `atomics` enabled the inner value can only be accessed
/// or dropped on the `wgpu` thread or else a panic will occur.
/// On other platforms the wrapper simply contains the wrapped value.
#[derive(Debug, Clone)]
pub struct WgpuWrapper<T>(
#[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] T,
#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] send_wrapper::SendWrapper<T>,
);
// SAFETY: SendWrapper is always Send + Sync.
#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))]
#[expect(unsafe_code, reason = "Blanket-impl Send requires unsafe.")]
unsafe impl<T> Send for WgpuWrapper<T> {}
#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))]
#[expect(unsafe_code, reason = "Blanket-impl Sync requires unsafe.")]
unsafe impl<T> Sync for WgpuWrapper<T> {}
impl<T> WgpuWrapper<T> {
/// Constructs a new instance of `WgpuWrapper` which will wrap the specified value.
pub fn new(t: T) -> Self {
#[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
return Self(t);
#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))]
return Self(send_wrapper::SendWrapper::new(t));
}
/// Unwraps the value.
pub fn into_inner(self) -> T {
#[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
return self.0;
#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))]
return self.0.take();
}
}
impl<T> core::ops::Deref for WgpuWrapper<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> core::ops::DerefMut for WgpuWrapper<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}