mod graph_runner;
mod render_device;
use bevy_derive::{Deref, DerefMut};
use bevy_tasks::ComputeTaskPool;
use bevy_utils::tracing::{error, info, info_span, warn};
pub use graph_runner::*;
pub use render_device::*;
use crate::{
diagnostic::{internal::DiagnosticsRecorder, RecordDiagnostics},
render_graph::RenderGraph,
render_phase::TrackedRenderPass,
render_resource::RenderPassDescriptor,
settings::{WgpuSettings, WgpuSettingsPriority},
view::{ExtractedWindows, ViewTarget},
};
use alloc::sync::Arc;
use bevy_ecs::{prelude::*, system::SystemState};
use bevy_time::TimeSender;
use bevy_utils::Instant;
use wgpu::{
Adapter, AdapterInfo, CommandBuffer, CommandEncoder, DeviceType, Instance, Queue,
RequestAdapterOptions,
};
pub fn render_system(world: &mut World, state: &mut SystemState<Query<Entity, With<ViewTarget>>>) {
world.resource_scope(|world, mut graph: Mut<RenderGraph>| {
graph.update(world);
});
let diagnostics_recorder = world.remove_resource::<DiagnosticsRecorder>();
let graph = world.resource::<RenderGraph>();
let render_device = world.resource::<RenderDevice>();
let render_queue = world.resource::<RenderQueue>();
let render_adapter = world.resource::<RenderAdapter>();
let res = RenderGraphRunner::run(
graph,
render_device.clone(), diagnostics_recorder,
&render_queue.0,
&render_adapter.0,
world,
|encoder| {
crate::view::screenshot::submit_screenshot_commands(world, encoder);
crate::gpu_readback::submit_readback_commands(world, encoder);
},
);
match res {
Ok(Some(diagnostics_recorder)) => {
world.insert_resource(diagnostics_recorder);
}
Ok(None) => {}
Err(e) => {
error!("Error running render graph:");
{
let mut src: &dyn core::error::Error = &e;
loop {
error!("> {}", src);
match src.source() {
Some(s) => src = s,
None => break,
}
}
}
panic!("Error running render graph: {e}");
}
}
{
let _span = info_span!("present_frames").entered();
let view_entities = state.get(world).iter().collect::<Vec<_>>();
for view_entity in view_entities {
world.entity_mut(view_entity).remove::<ViewTarget>();
}
let mut windows = world.resource_mut::<ExtractedWindows>();
for window in windows.values_mut() {
if let Some(wrapped_texture) = window.swap_chain_texture.take() {
if let Some(surface_texture) = wrapped_texture.try_unwrap() {
surface_texture.present();
}
}
}
#[cfg(feature = "tracing-tracy")]
bevy_utils::tracing::event!(
bevy_utils::tracing::Level::INFO,
message = "finished frame",
tracy.frame_mark = true
);
}
crate::view::screenshot::collect_screenshots(world);
let time_sender = world.resource::<TimeSender>();
if let Err(error) = time_sender.0.try_send(Instant::now()) {
match error {
bevy_time::TrySendError::Full(_) => {
panic!("The TimeSender channel should always be empty during render. You might need to add the bevy::core::time_system to your app.",);
}
bevy_time::TrySendError::Disconnected(_) => {
}
}
}
}
#[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>);
#[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()
}
}
#[derive(Resource, Clone, Deref, DerefMut)]
pub struct RenderQueue(pub Arc<WgpuWrapper<Queue>>);
#[derive(Resource, Clone, Debug, Deref, DerefMut)]
pub struct RenderAdapter(pub Arc<WgpuWrapper<Adapter>>);
#[derive(Resource, Clone, Deref, DerefMut)]
pub struct RenderInstance(pub Arc<WgpuWrapper<Instance>>);
#[derive(Resource, Clone, Deref, DerefMut)]
pub struct RenderAdapterInfo(pub WgpuWrapper<AdapterInfo>);
const GPU_NOT_FOUND_ERROR_MESSAGE: &str = if cfg!(target_os = "linux") {
"Unable to find a GPU! Make sure you have installed required drivers! For extra information, see: https://github.com/bevyengine/bevy/blob/latest/docs/linux_dependencies.md"
} else {
"Unable to find a GPU! Make sure you have installed required drivers!"
};
pub async fn initialize_renderer(
instance: &Instance,
options: &WgpuSettings,
request_adapter_options: &RequestAdapterOptions<'_, '_>,
) -> (RenderDevice, RenderQueue, RenderAdapterInfo, RenderAdapter) {
let adapter = instance
.request_adapter(request_adapter_options)
.await
.expect(GPU_NOT_FOUND_ERROR_MESSAGE);
let adapter_info = adapter.get_info();
info!("{:?}", adapter_info);
if adapter_info.device_type == DeviceType::Cpu {
warn!(
"The selected adapter is using a driver that only supports software rendering. \
This is likely to be very slow. See https://bevyengine.org/learn/errors/b0006/"
);
}
let mut features = wgpu::Features::empty();
let mut limits = options.limits.clone();
if matches!(options.priority, WgpuSettingsPriority::Functionality) {
features = adapter.features();
if adapter_info.device_type == DeviceType::DiscreteGpu {
features -= wgpu::Features::MAPPABLE_PRIMARY_BUFFERS;
}
features -= wgpu::Features::RAY_QUERY;
features -= wgpu::Features::RAY_TRACING_ACCELERATION_STRUCTURE;
limits = adapter.limits();
}
if let Some(disabled_features) = options.disabled_features {
features -= disabled_features;
}
features |= options.features;
if let Some(constrained_limits) = options.constrained_limits.as_ref() {
limits = wgpu::Limits {
max_texture_dimension_1d: limits
.max_texture_dimension_1d
.min(constrained_limits.max_texture_dimension_1d),
max_texture_dimension_2d: limits
.max_texture_dimension_2d
.min(constrained_limits.max_texture_dimension_2d),
max_texture_dimension_3d: limits
.max_texture_dimension_3d
.min(constrained_limits.max_texture_dimension_3d),
max_texture_array_layers: limits
.max_texture_array_layers
.min(constrained_limits.max_texture_array_layers),
max_bind_groups: limits
.max_bind_groups
.min(constrained_limits.max_bind_groups),
max_dynamic_uniform_buffers_per_pipeline_layout: limits
.max_dynamic_uniform_buffers_per_pipeline_layout
.min(constrained_limits.max_dynamic_uniform_buffers_per_pipeline_layout),
max_dynamic_storage_buffers_per_pipeline_layout: limits
.max_dynamic_storage_buffers_per_pipeline_layout
.min(constrained_limits.max_dynamic_storage_buffers_per_pipeline_layout),
max_sampled_textures_per_shader_stage: limits
.max_sampled_textures_per_shader_stage
.min(constrained_limits.max_sampled_textures_per_shader_stage),
max_samplers_per_shader_stage: limits
.max_samplers_per_shader_stage
.min(constrained_limits.max_samplers_per_shader_stage),
max_storage_buffers_per_shader_stage: limits
.max_storage_buffers_per_shader_stage
.min(constrained_limits.max_storage_buffers_per_shader_stage),
max_storage_textures_per_shader_stage: limits
.max_storage_textures_per_shader_stage
.min(constrained_limits.max_storage_textures_per_shader_stage),
max_uniform_buffers_per_shader_stage: limits
.max_uniform_buffers_per_shader_stage
.min(constrained_limits.max_uniform_buffers_per_shader_stage),
max_uniform_buffer_binding_size: limits
.max_uniform_buffer_binding_size
.min(constrained_limits.max_uniform_buffer_binding_size),
max_storage_buffer_binding_size: limits
.max_storage_buffer_binding_size
.min(constrained_limits.max_storage_buffer_binding_size),
max_vertex_buffers: limits
.max_vertex_buffers
.min(constrained_limits.max_vertex_buffers),
max_vertex_attributes: limits
.max_vertex_attributes
.min(constrained_limits.max_vertex_attributes),
max_vertex_buffer_array_stride: limits
.max_vertex_buffer_array_stride
.min(constrained_limits.max_vertex_buffer_array_stride),
max_push_constant_size: limits
.max_push_constant_size
.min(constrained_limits.max_push_constant_size),
min_uniform_buffer_offset_alignment: limits
.min_uniform_buffer_offset_alignment
.max(constrained_limits.min_uniform_buffer_offset_alignment),
min_storage_buffer_offset_alignment: limits
.min_storage_buffer_offset_alignment
.max(constrained_limits.min_storage_buffer_offset_alignment),
max_inter_stage_shader_components: limits
.max_inter_stage_shader_components
.min(constrained_limits.max_inter_stage_shader_components),
max_compute_workgroup_storage_size: limits
.max_compute_workgroup_storage_size
.min(constrained_limits.max_compute_workgroup_storage_size),
max_compute_invocations_per_workgroup: limits
.max_compute_invocations_per_workgroup
.min(constrained_limits.max_compute_invocations_per_workgroup),
max_compute_workgroup_size_x: limits
.max_compute_workgroup_size_x
.min(constrained_limits.max_compute_workgroup_size_x),
max_compute_workgroup_size_y: limits
.max_compute_workgroup_size_y
.min(constrained_limits.max_compute_workgroup_size_y),
max_compute_workgroup_size_z: limits
.max_compute_workgroup_size_z
.min(constrained_limits.max_compute_workgroup_size_z),
max_compute_workgroups_per_dimension: limits
.max_compute_workgroups_per_dimension
.min(constrained_limits.max_compute_workgroups_per_dimension),
max_buffer_size: limits
.max_buffer_size
.min(constrained_limits.max_buffer_size),
max_bindings_per_bind_group: limits
.max_bindings_per_bind_group
.min(constrained_limits.max_bindings_per_bind_group),
max_non_sampler_bindings: limits
.max_non_sampler_bindings
.min(constrained_limits.max_non_sampler_bindings),
max_color_attachments: limits
.max_color_attachments
.min(constrained_limits.max_color_attachments),
max_color_attachment_bytes_per_sample: limits
.max_color_attachment_bytes_per_sample
.min(constrained_limits.max_color_attachment_bytes_per_sample),
min_subgroup_size: limits
.min_subgroup_size
.max(constrained_limits.min_subgroup_size),
max_subgroup_size: limits
.max_subgroup_size
.min(constrained_limits.max_subgroup_size),
};
}
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: options.device_label.as_ref().map(AsRef::as_ref),
required_features: features,
required_limits: limits,
memory_hints: options.memory_hints.clone(),
},
options.trace_path.as_deref(),
)
.await
.unwrap();
let queue = Arc::new(WgpuWrapper::new(queue));
let adapter = Arc::new(WgpuWrapper::new(adapter));
(
RenderDevice::from(device),
RenderQueue(queue),
RenderAdapterInfo(WgpuWrapper::new(adapter_info)),
RenderAdapter(adapter),
)
}
pub struct RenderContext<'w> {
render_device: RenderDevice,
command_encoder: Option<CommandEncoder>,
command_buffer_queue: Vec<QueuedCommandBuffer<'w>>,
force_serial: bool,
diagnostics_recorder: Option<Arc<DiagnosticsRecorder>>,
}
impl<'w> RenderContext<'w> {
pub fn new(
render_device: RenderDevice,
adapter_info: AdapterInfo,
diagnostics_recorder: Option<DiagnosticsRecorder>,
) -> Self {
#[cfg(target_os = "windows")]
let force_serial =
adapter_info.driver.contains("AMD") && adapter_info.backend == wgpu::Backend::Vulkan;
#[cfg(not(target_os = "windows"))]
let force_serial = {
drop(adapter_info);
false
};
Self {
render_device,
command_encoder: None,
command_buffer_queue: Vec::new(),
force_serial,
diagnostics_recorder: diagnostics_recorder.map(Arc::new),
}
}
pub fn render_device(&self) -> &RenderDevice {
&self.render_device
}
pub fn diagnostic_recorder(&self) -> impl RecordDiagnostics {
self.diagnostics_recorder.clone()
}
pub fn command_encoder(&mut self) -> &mut CommandEncoder {
self.command_encoder.get_or_insert_with(|| {
self.render_device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default())
})
}
pub fn begin_tracked_render_pass<'a>(
&'a mut self,
descriptor: RenderPassDescriptor<'_>,
) -> TrackedRenderPass<'a> {
let command_encoder = self.command_encoder.get_or_insert_with(|| {
self.render_device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default())
});
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.flush_encoder();
self.command_buffer_queue
.push(QueuedCommandBuffer::Ready(command_buffer));
}
pub fn add_command_buffer_generation_task(
&mut self,
#[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
task: impl FnOnce(RenderDevice) -> CommandBuffer + 'w + Send,
#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))]
task: impl FnOnce(RenderDevice) -> CommandBuffer + 'w,
) {
self.flush_encoder();
self.command_buffer_queue
.push(QueuedCommandBuffer::Task(Box::new(task)));
}
pub fn finish(
mut self,
) -> (
Vec<CommandBuffer>,
RenderDevice,
Option<DiagnosticsRecorder>,
) {
self.flush_encoder();
let mut command_buffers = Vec::with_capacity(self.command_buffer_queue.len());
#[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
{
let mut task_based_command_buffers = ComputeTaskPool::get().scope(|task_pool| {
for (i, queued_command_buffer) in self.command_buffer_queue.into_iter().enumerate()
{
match queued_command_buffer {
QueuedCommandBuffer::Ready(command_buffer) => {
command_buffers.push((i, command_buffer));
}
QueuedCommandBuffer::Task(command_buffer_generation_task) => {
let render_device = self.render_device.clone();
if self.force_serial {
command_buffers
.push((i, command_buffer_generation_task(render_device)));
} else {
task_pool.spawn(async move {
(i, command_buffer_generation_task(render_device))
});
}
}
}
}
});
command_buffers.append(&mut task_based_command_buffers);
}
#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))]
for (i, queued_command_buffer) in self.command_buffer_queue.into_iter().enumerate() {
match queued_command_buffer {
QueuedCommandBuffer::Ready(command_buffer) => {
command_buffers.push((i, command_buffer));
}
QueuedCommandBuffer::Task(command_buffer_generation_task) => {
let render_device = self.render_device.clone();
command_buffers.push((i, command_buffer_generation_task(render_device)));
}
}
}
command_buffers.sort_unstable_by_key(|(i, _)| *i);
let mut command_buffers = command_buffers
.into_iter()
.map(|(_, cb)| cb)
.collect::<Vec<CommandBuffer>>();
let mut diagnostics_recorder = self.diagnostics_recorder.take().map(|v| {
Arc::try_unwrap(v)
.ok()
.expect("diagnostic recorder shouldn't be held longer than necessary")
});
if let Some(recorder) = &mut diagnostics_recorder {
let mut command_encoder = self
.render_device
.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
recorder.resolve(&mut command_encoder);
command_buffers.push(command_encoder.finish());
}
(command_buffers, self.render_device, diagnostics_recorder)
}
fn flush_encoder(&mut self) {
if let Some(encoder) = self.command_encoder.take() {
self.command_buffer_queue
.push(QueuedCommandBuffer::Ready(encoder.finish()));
}
}
}
enum QueuedCommandBuffer<'w> {
Ready(CommandBuffer),
#[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
Task(Box<dyn FnOnce(RenderDevice) -> CommandBuffer + 'w + Send>),
#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))]
Task(Box<dyn FnOnce(RenderDevice) -> CommandBuffer + 'w>),
}