1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! Per-camera offscreen viewport texture management.
impl super::WgpuRenderer {
/// Ensures an offscreen viewport texture of the given size exists for
/// `camera_entity`, creating or resizing it when absent or mismatched.
///
/// Only called for cameras whose target the renderer owns; a host-owned
/// target is bound through
/// [`set_external_camera_viewport`](Self::set_external_camera_viewport)
/// instead, and the frame skips this.
pub fn ensure_camera_viewport(
&mut self,
camera_entity: nightshade_ecs::Entity,
width: u32,
height: u32,
) {
if let Some(viewport) = self.camera_viewports.get(&camera_entity)
&& viewport.size == (width, height)
{
return;
}
let texture = self.device.create_texture(&wgpu::TextureDescriptor {
label: Some(&format!("Viewport Texture (Camera {})", camera_entity.id)),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: self.surface_format,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
self.camera_viewports.insert(
camera_entity,
super::CameraViewport {
texture,
view,
size: (width, height),
has_rendered_at_least_once: false,
last_active_view: None,
last_settings_version: 0,
last_render_frame: 0,
last_camera_world_transform: None,
depth_target: None,
last_view_projection: None,
},
);
}
/// Binds a caller-owned texture and view as `camera_entity`'s render target,
/// so the frame renders the camera straight into it instead of into a
/// renderer-allocated viewport.
///
/// The binding carries the change-detection state of any previous viewport
/// for the same camera, so a host that rebinds every frame (an OpenXR
/// swapchain hands out a different image index per frame) does not reset the
/// camera's dirty tracking and force a redundant render.
///
/// Call before the frame that should use it. The renderer holds the handles
/// only until they are replaced or the camera goes away, and never frees the
/// underlying resource.
pub fn set_external_camera_viewport(
&mut self,
camera_entity: nightshade_ecs::Entity,
texture: wgpu::Texture,
view: wgpu::TextureView,
size: (u32, u32),
) {
let previous = self.camera_viewports.get(&camera_entity);
let has_rendered_at_least_once =
previous.is_some_and(|viewport| viewport.has_rendered_at_least_once);
let last_active_view = previous.and_then(|viewport| viewport.last_active_view);
let last_settings_version = previous.map_or(0, |viewport| viewport.last_settings_version);
let last_render_frame = previous.map_or(0, |viewport| viewport.last_render_frame);
let last_camera_world_transform =
previous.and_then(|viewport| viewport.last_camera_world_transform);
let depth_target = previous.and_then(|viewport| viewport.depth_target.clone());
let last_view_projection = previous.and_then(|viewport| viewport.last_view_projection);
self.camera_viewports.insert(
camera_entity,
super::CameraViewport {
texture,
view,
size,
has_rendered_at_least_once,
last_active_view,
last_settings_version,
last_render_frame,
last_camera_world_transform,
depth_target,
last_view_projection,
},
);
}
/// Publishes this camera's depth into a caller-owned image after it renders.
///
/// The frame's depth buffer is shared between cameras and is overwritten by
/// whichever renders next, so a host that needs a per-camera copy cannot take
/// one after the frame. Naming the destination here lets the frame take it at
/// the only moment it is still that camera's depth.
///
/// Submitting depth alongside color is what lets an OpenXR compositor
/// reproject a frame with parallax rather than as a flat plane.
pub fn set_external_camera_depth(
&mut self,
camera_entity: nightshade_ecs::Entity,
texture: wgpu::Texture,
array_layer: u32,
) {
if let Some(viewport) = self.camera_viewports.get_mut(&camera_entity) {
viewport.depth_target = Some((texture, array_layer));
}
}
}