Skip to content

Commit

Permalink
Flexible camera bindings (#1689)
Browse files Browse the repository at this point in the history
Alternative to #1203 and #1611

Camera bindings have historically been "hacked in". They were _required_ in all shaders and only supported a single Mat4. PBR (#1554) requires the CameraView matrix, but adding this using the "hacked" method forced users to either include all possible camera data in a single binding (#1203) or include all possible bindings (#1611).

This approach instead assigns each "active camera" its own RenderResourceBindings, which are populated by CameraNode. The PassNode then retrieves (and initializes) the relevant bind groups for all render pipelines used by visible entities. 

* Enables any number of camera bindings , including zero (with any set or binding number ... set 0 should still be used to avoid rebinds).
* Renames Camera binding to CameraViewProj
* Adds CameraView binding
  • Loading branch information
cart committed Mar 19, 2021
1 parent 6121e5f commit dd4a196
Show file tree
Hide file tree
Showing 15 changed files with 270 additions and 235 deletions.
2 changes: 1 addition & 1 deletion assets/shaders/hot.vert
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

layout(location = 0) in vec3 Vertex_Position;

layout(set = 0, binding = 0) uniform Camera {
layout(set = 0, binding = 0) uniform CameraViewProj {
mat4 ViewProj;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ layout(location = 2) in vec2 v_Uv;

layout(location = 0) out vec4 o_Target;

layout(set = 0, binding = 0) uniform Camera {
layout(set = 0, binding = 0) uniform CameraViewProj {
mat4 ViewProj;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ layout(location = 0) out vec3 v_Position;
layout(location = 1) out vec3 v_Normal;
layout(location = 2) out vec2 v_Uv;

layout(set = 0, binding = 0) uniform Camera {
layout(set = 0, binding = 0) uniform CameraViewProj {
mat4 ViewProj;
};

Expand Down
25 changes: 17 additions & 8 deletions crates/bevy_render/src/camera/active_cameras.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
use crate::renderer::RenderResourceBindings;

use super::Camera;
use bevy_ecs::{
entity::Entity,
system::{Query, ResMut},
};
use bevy_utils::HashMap;

#[derive(Debug, Default)]
pub struct ActiveCamera {
pub entity: Option<Entity>,
pub bindings: RenderResourceBindings,
}

#[derive(Debug, Default)]
pub struct ActiveCameras {
pub cameras: HashMap<String, Option<Entity>>,
cameras: HashMap<String, ActiveCamera>,
}

impl ActiveCameras {
pub fn add(&mut self, name: &str) {
self.cameras.insert(name.to_string(), None);
self.cameras
.insert(name.to_string(), ActiveCamera::default());
}

pub fn set(&mut self, name: &str, entity: Entity) {
self.cameras.insert(name.to_string(), Some(entity));
pub fn get(&self, name: &str) -> Option<&ActiveCamera> {
self.cameras.get(name)
}

pub fn get(&self, name: &str) -> Option<Entity> {
self.cameras.get(name).and_then(|e| *e)
pub fn get_mut(&mut self, name: &str) -> Option<&mut ActiveCamera> {
self.cameras.get_mut(name)
}
}

Expand All @@ -29,11 +38,11 @@ pub fn active_cameras_system(
query: Query<(Entity, &Camera)>,
) {
for (name, active_camera) in active_cameras.cameras.iter_mut() {
if active_camera.is_none() {
if active_camera.entity.is_none() {
for (camera_entity, camera) in query.iter() {
if let Some(ref current_name) = camera.name {
if current_name == name {
*active_camera = Some(camera_entity);
active_camera.entity = Some(camera_entity);
}
}
}
Expand Down
130 changes: 84 additions & 46 deletions crates/bevy_render/src/render_graph/nodes/camera_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
render_graph::{CommandQueue, Node, ResourceSlots, SystemNode},
renderer::{
BufferId, BufferInfo, BufferMapMode, BufferUsage, RenderContext, RenderResourceBinding,
RenderResourceBindings, RenderResourceContext,
RenderResourceContext,
},
};
use bevy_core::AsBytes;
Expand Down Expand Up @@ -50,88 +50,126 @@ impl SystemNode for CameraNode {
config.0 = Some(CameraNodeState {
camera_name: self.camera_name.clone(),
command_queue: self.command_queue.clone(),
camera_buffer: None,
staging_buffer: None,
})
});
Box::new(system)
}
}

const CAMERA_VIEW_PROJ: &str = "CameraViewProj";
const CAMERA_VIEW: &str = "CameraView";

#[derive(Debug, Default)]
pub struct CameraNodeState {
command_queue: CommandQueue,
camera_name: Cow<'static, str>,
camera_buffer: Option<BufferId>,
staging_buffer: Option<BufferId>,
}

const MATRIX_SIZE: usize = std::mem::size_of::<[[f32; 4]; 4]>();

pub fn camera_node_system(
mut state: Local<CameraNodeState>,
active_cameras: Res<ActiveCameras>,
mut active_cameras: ResMut<ActiveCameras>,
render_resource_context: Res<Box<dyn RenderResourceContext>>,
// PERF: this write on RenderResourceAssignments will prevent this system from running in
// parallel with other systems that do the same
mut render_resource_bindings: ResMut<RenderResourceBindings>,
query: Query<(&Camera, &GlobalTransform)>,
mut query: Query<(&Camera, &GlobalTransform)>,
) {
let render_resource_context = &**render_resource_context;

let (camera, global_transform) = if let Some(entity) = active_cameras.get(&state.camera_name) {
query.get(entity).unwrap()
} else {
return;
};
let ((camera, global_transform), bindings) =
if let Some(active_camera) = active_cameras.get_mut(&state.camera_name) {
if let Some(entity) = active_camera.entity {
(query.get_mut(entity).unwrap(), &mut active_camera.bindings)
} else {
return;
}
} else {
return;
};

let staging_buffer = if let Some(staging_buffer) = state.staging_buffer {
render_resource_context.map_buffer(staging_buffer, BufferMapMode::Write);
staging_buffer
} else {
let size = std::mem::size_of::<[[f32; 4]; 4]>();
let staging_buffer = render_resource_context.create_buffer(BufferInfo {
size: MATRIX_SIZE * 2,
buffer_usage: BufferUsage::COPY_SRC | BufferUsage::MAP_WRITE,
mapped_at_creation: true,
});

state.staging_buffer = Some(staging_buffer);
staging_buffer
};

if bindings.get(CAMERA_VIEW_PROJ).is_none() {
let buffer = render_resource_context.create_buffer(BufferInfo {
size,
size: MATRIX_SIZE,
buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM,
..Default::default()
});
render_resource_bindings.set(
&state.camera_name,
bindings.set(
CAMERA_VIEW_PROJ,
RenderResourceBinding::Buffer {
buffer,
range: 0..size as u64,
range: 0..MATRIX_SIZE as u64,
dynamic_index: None,
},
);
state.camera_buffer = Some(buffer);
}

let staging_buffer = render_resource_context.create_buffer(BufferInfo {
size,
buffer_usage: BufferUsage::COPY_SRC | BufferUsage::MAP_WRITE,
mapped_at_creation: true,
if bindings.get(CAMERA_VIEW).is_none() {
let buffer = render_resource_context.create_buffer(BufferInfo {
size: MATRIX_SIZE,
buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM,
..Default::default()
});
bindings.set(
CAMERA_VIEW,
RenderResourceBinding::Buffer {
buffer,
range: 0..MATRIX_SIZE as u64,
dynamic_index: None,
},
);
}

state.staging_buffer = Some(staging_buffer);
staging_buffer
};
let view = global_transform.compute_matrix();

let matrix_size = std::mem::size_of::<[[f32; 4]; 4]>();
let camera_matrix: [f32; 16] =
(camera.projection_matrix * global_transform.compute_matrix().inverse()).to_cols_array();

render_resource_context.write_mapped_buffer(
staging_buffer,
0..matrix_size as u64,
&mut |data, _renderer| {
data[0..matrix_size].copy_from_slice(camera_matrix.as_bytes());
},
);
render_resource_context.unmap_buffer(staging_buffer);
if let Some(RenderResourceBinding::Buffer { buffer, .. }) = bindings.get(CAMERA_VIEW) {
render_resource_context.write_mapped_buffer(
staging_buffer,
0..MATRIX_SIZE as u64,
&mut |data, _renderer| {
data[0..MATRIX_SIZE].copy_from_slice(view.to_cols_array_2d().as_bytes());
},
);
state.command_queue.copy_buffer_to_buffer(
staging_buffer,
0,
*buffer,
0,
MATRIX_SIZE as u64,
);
}

if let Some(RenderResourceBinding::Buffer { buffer, .. }) = bindings.get(CAMERA_VIEW_PROJ) {
let view_proj = camera.projection_matrix * view.inverse();
render_resource_context.write_mapped_buffer(
staging_buffer,
MATRIX_SIZE as u64..(2 * MATRIX_SIZE) as u64,
&mut |data, _renderer| {
data[0..MATRIX_SIZE].copy_from_slice(view_proj.to_cols_array_2d().as_bytes());
},
);
state.command_queue.copy_buffer_to_buffer(
staging_buffer,
MATRIX_SIZE as u64,
*buffer,
0,
MATRIX_SIZE as u64,
);
}

let camera_buffer = state.camera_buffer.unwrap();
state.command_queue.copy_buffer_to_buffer(
staging_buffer,
0,
camera_buffer,
0,
matrix_size as u64,
);
render_resource_context.unmap_buffer(staging_buffer);
}
Loading

0 comments on commit dd4a196

Please sign in to comment.