use crate::diagnostic::internal::DiagnosticsRecorder;
use crate::render_phase::TrackedRenderPass;
use crate::render_resource::{CommandEncoder, RenderPassDescriptor};
use crate::renderer::{wgpu_wrapper, RenderDevice};
use alloc::borrow::Cow;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::change_detection::Tick;
use bevy_ecs::component::ComponentId;
use bevy_ecs::prelude::*;
use bevy_ecs::query::{QueryData, QueryFilter, QueryState};
use bevy_ecs::system::{
Deferred, SystemAccess, SystemBuffer, SystemMeta, SystemName, SystemParam,
SystemParamValidationError,
};
use bevy_ecs::world::unsafe_world_cell::UnsafeWorldCell;
use bevy_ecs::world::DeferredWorld;
#[cfg(feature = "trace")]
use bevy_log::info_span;
#[cfg(not(target_arch = "wasm32"))]
use bevy_tasks::ComputeTaskPool;
use core::marker::PhantomData;
use wgpu::CommandBuffer;
#[derive(Default)]
struct PendingCommandBuffersInner {
commands: Vec<PendingCommandBuffer>,
}
enum PendingCommandBuffer {
Buffer(CommandBuffer),
Encoder {
encoder: CommandEncoder,
#[cfg(feature = "trace")]
name: Cow<'static, str>,
},
}
wgpu_wrapper!(struct WgpuPendingCommandBuffersInner(PendingCommandBuffersInner));
#[derive(Resource)]
pub struct PendingCommandBuffers(WgpuPendingCommandBuffersInner);
impl Default for PendingCommandBuffers {
fn default() -> Self {
Self(WgpuPendingCommandBuffersInner::new(
PendingCommandBuffersInner::default(),
))
}
}
impl PendingCommandBuffers {
pub fn push(&mut self, buffers: impl IntoIterator<Item = CommandBuffer>) {
self.0
.commands
.extend(buffers.into_iter().map(PendingCommandBuffer::Buffer));
}
fn append(&mut self, commands: &mut Vec<PendingCommandBuffer>) {
self.0.commands.append(commands);
}
pub fn push_encoder(&mut self, encoder: CommandEncoder, name: impl Into<Cow<'static, str>>) {
#[cfg(not(feature = "trace"))]
let _ = name;
self.0.commands.push(PendingCommandBuffer::Encoder {
encoder,
#[cfg(feature = "trace")]
name: name.into(),
});
}
pub fn finish(&mut self) -> impl Iterator<Item = CommandBuffer> {
#[cfg(feature = "trace")]
let _finish_command_buffers_span = info_span!("finish_command_buffers").entered();
let commands = self.0.commands.drain(..);
#[cfg(target_arch = "wasm32")]
{
finish_sequential(commands)
}
#[cfg(not(target_arch = "wasm32"))]
{
finish_parallel(commands)
}
}
pub fn is_empty(&self) -> bool {
self.0.commands.is_empty()
}
pub fn len(&self) -> usize {
self.0.commands.len()
}
}
#[cfg(target_arch = "wasm32")]
fn finish_sequential(
commands: impl Iterator<Item = PendingCommandBuffer>,
) -> impl Iterator<Item = CommandBuffer> {
commands.into_iter().map(|command| match command {
PendingCommandBuffer::Buffer(command_buffer) => command_buffer,
PendingCommandBuffer::Encoder { encoder, .. } => encoder.finish(),
})
}
#[cfg(not(target_arch = "wasm32"))]
fn finish_parallel(
commands: impl Iterator<Item = PendingCommandBuffer>,
) -> impl Iterator<Item = CommandBuffer> {
let mut command_buffers = Vec::with_capacity(commands.size_hint().0);
let mut finished_encoders = ComputeTaskPool::get().scope(|scope| {
for (index, command) in commands.into_iter().enumerate() {
match command {
PendingCommandBuffer::Buffer(command_buffer) => {
command_buffers.push((index, command_buffer));
}
PendingCommandBuffer::Encoder {
encoder,
#[cfg(feature = "trace")]
name,
} => {
scope.spawn(async move {
#[cfg(feature = "trace")]
let _span =
info_span!("finish_command_buffer", system = name.as_ref()).entered();
(index, encoder.finish())
});
}
}
}
});
command_buffers.append(&mut finished_encoders);
command_buffers.sort_unstable_by_key(|(index, _)| *index);
command_buffers
.into_iter()
.map(|(_, command_buffer)| command_buffer)
}
#[derive(Default)]
struct RenderContextStateInner {
command_encoder: Option<CommandEncoder>,
commands: Vec<PendingCommandBuffer>,
render_device: Option<RenderDevice>,
}
impl RenderContextStateInner {
fn flush_encoder(&mut self) {
if let Some(encoder) = self.command_encoder.take() {
self.commands.push(PendingCommandBuffer::Encoder {
encoder,
#[cfg(feature = "trace")]
name: "RenderContextState::flush_encoder".into(),
});
}
}
}
wgpu_wrapper!(struct WgpuRenderContextStateInner(RenderContextStateInner));
pub struct RenderContextState(WgpuRenderContextStateInner);
impl Default for RenderContextState {
fn default() -> Self {
Self(WgpuRenderContextStateInner::new(
RenderContextStateInner::default(),
))
}
}
impl RenderContextState {
fn flush_encoder(&mut self) {
self.0.flush_encoder();
}
fn command_encoder(&mut self, label: &str) -> &mut CommandEncoder {
let render_device = self
.0
.render_device
.clone()
.expect("RenderDevice must be set before accessing command_encoder");
self.0.command_encoder.get_or_insert_with(|| {
render_device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some(label) })
})
}
}
impl SystemBuffer for RenderContextState {
fn queue(&mut self, _system_meta: &SystemMeta, mut world: DeferredWorld) {
#[cfg(feature = "trace")]
let _span =
info_span!("RenderContextState::apply", system = %_system_meta.name()).entered();
let inner = &mut *self.0;
inner.flush_encoder();
#[cfg(feature = "trace")]
for command in &mut inner.commands {
if let PendingCommandBuffer::Encoder { name, .. } = command {
*name = _system_meta.name().clone().into();
}
}
if !inner.commands.is_empty() {
let mut pending = world.resource_mut::<PendingCommandBuffers>();
pending.append(&mut inner.commands);
}
inner.render_device = None;
}
}
#[derive(SystemParam)]
pub struct RenderContext<'w, 's> {
state: Deferred<'s, RenderContextState>,
system_name: SystemName,
render_device: Res<'w, RenderDevice>,
diagnostics_recorder: Option<Res<'w, DiagnosticsRecorder>>,
}
impl<'w, 's> RenderContext<'w, 's> {
fn ensure_device(&mut self) {
if self.state.0.render_device.is_none() {
self.state.0.render_device = Some(self.render_device.clone());
}
}
pub fn render_device(&self) -> &RenderDevice {
&self.render_device
}
pub fn diagnostic_recorder(&self) -> Option<Res<'w, DiagnosticsRecorder>> {
self.diagnostics_recorder.as_ref().map(Res::clone)
}
pub fn command_encoder(&mut self) -> &mut CommandEncoder {
self.ensure_device();
self.state.command_encoder(self.system_name.as_str())
}
pub fn begin_tracked_render_pass<'a>(
&'a mut self,
descriptor: RenderPassDescriptor<'_>,
) -> TrackedRenderPass<'a> {
self.ensure_device();
let command_encoder = self.state.0.command_encoder.get_or_insert_with(|| {
self.render_device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some(self.system_name.as_str()),
})
});
let render_pass = command_encoder.begin_render_pass(&descriptor);
TrackedRenderPass::new(&self.render_device, render_pass)
}
pub fn add_command_buffer(&mut self, command_buffer: CommandBuffer) {
self.state.flush_encoder();
self.state
.0
.commands
.push(PendingCommandBuffer::Buffer(command_buffer));
}
}
#[derive(SystemParam)]
pub struct FlushCommands<'w> {
pending: ResMut<'w, PendingCommandBuffers>,
queue: Res<'w, super::RenderQueue>,
}
impl<'w> FlushCommands<'w> {
pub fn flush(&mut self) {
let mut buffers = self.pending.finish().peekable();
if buffers.peek().is_some() {
self.queue.submit(buffers);
}
}
}
#[derive(Resource, Debug, Clone, Copy, PartialEq, Eq, Deref, DerefMut)]
pub struct CurrentView(pub Entity);
pub struct ViewQuery<'w, 's, D: QueryData, F: QueryFilter = ()> {
entity: Entity,
item: D::Item<'w, 's>,
_filter: PhantomData<F>,
}
impl<'w, 's, D: QueryData, F: QueryFilter> ViewQuery<'w, 's, D, F> {
#[inline]
pub fn entity(&self) -> Entity {
self.entity
}
#[inline]
pub fn into_inner(self) -> D::Item<'w, 's> {
self.item
}
}
pub struct ViewQueryState<D: QueryData, F: QueryFilter> {
resource_id: ComponentId,
query_state: QueryState<D, F>,
}
unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
for ViewQuery<'a, '_, D, F>
{
type State = ViewQueryState<D, F>;
type Item<'w, 's> = ViewQuery<'w, 's, D, F>;
fn init_state(world: &mut World) -> Self::State {
ViewQueryState {
resource_id: world
.components_registrator()
.register_component::<CurrentView>(),
query_state: QueryState::new(world),
}
}
fn init_access(
state: &Self::State,
system_meta: &mut SystemMeta,
system_access: &mut SystemAccess,
world: &mut World,
) {
let component_access_set = system_access.require_shared_access::<Self>(system_meta);
component_access_set.add_resource_read(state.resource_id);
<Query<'_, '_, D, F> as SystemParam>::init_access(
&state.query_state,
system_meta,
system_access,
world,
);
}
#[inline]
unsafe fn get_param<'w, 's>(
state: &'s mut Self::State,
_system_meta: &SystemMeta,
world: UnsafeWorldCell<'w>,
_change_tick: Tick,
) -> Result<Self::Item<'w, 's>, SystemParamValidationError> {
let current_view = unsafe { world.get_resource::<CurrentView>() };
let Some(current_view) = current_view else {
return Err(SystemParamValidationError::skipped::<Self>(
"CurrentView resource not present",
));
};
let entity = current_view.entity();
let item = unsafe { state.query_state.get_unchecked(world, entity) }.map_err(|_| {
SystemParamValidationError::skipped::<Self>("Current view entity does not match query")
})?;
Ok(ViewQuery {
entity,
item,
_filter: PhantomData,
})
}
}
unsafe impl<'w, 's, D: bevy_ecs::query::ReadOnlyQueryData + 'static, F: QueryFilter + 'static>
bevy_ecs::system::ReadOnlySystemParam for ViewQuery<'w, 's, D, F>
{
}