improved performance

This commit is contained in:
awtterpip
2023-09-09 23:27:12 -05:00
parent a4065d279d
commit 086c964e82
5 changed files with 210 additions and 156 deletions

View File

@@ -81,16 +81,17 @@ fn hands(
frame_state: Res<XrFrameState>, frame_state: Res<XrFrameState>,
xr_input: Res<XrInput>, xr_input: Res<XrInput>,
) { ) {
frame_state.lock().unwrap().map(|a| { let frame_state = *frame_state.lock().unwrap();
let right_controller = oculus_controller let right_controller = oculus_controller
.grip_space .grip_space
.right .right
.relate(&**&xr_input.stage, a.predicted_display_time) .relate(&**&xr_input.stage, frame_state.predicted_display_time)
.unwrap(); .unwrap();
let left_controller = oculus_controller let left_controller = oculus_controller
.grip_space .grip_space
.left .left
.relate(&**&xr_input.stage, a.predicted_display_time) .relate(&**&xr_input.stage, frame_state.predicted_display_time)
.unwrap(); .unwrap();
gizmos.rect( gizmos.rect(
right_controller.0.pose.position.to_vec3(), right_controller.0.pose.position.to_vec3(),
@@ -104,5 +105,4 @@ fn hands(
Vec2::new(0.05, 0.2), Vec2::new(0.05, 0.2),
Color::YELLOW_GREEN, Color::YELLOW_GREEN,
); );
});
} }

View File

@@ -7,7 +7,7 @@ use wgpu::Instance;
use crate::input::XrInput; use crate::input::XrInput;
use crate::resources::{ use crate::resources::{
XrEnvironmentBlendMode, XrFrameState, XrFrameWaiter, XrInstance, XrSession, XrSessionRunning, XrEnvironmentBlendMode, XrFrameState, XrFrameWaiter, XrInstance, XrSession, XrSessionRunning,
XrSwapchain, XrViews, XrSwapchain, XrViews, XrResolution, XrFormat,
}; };
pub fn initialize_xr_graphics( pub fn initialize_xr_graphics(
@@ -21,6 +21,8 @@ pub fn initialize_xr_graphics(
XrInstance, XrInstance,
XrSession, XrSession,
XrEnvironmentBlendMode, XrEnvironmentBlendMode,
XrResolution,
XrFormat,
XrSessionRunning, XrSessionRunning,
XrFrameWaiter, XrFrameWaiter,
XrSwapchain, XrSwapchain,

View File

@@ -14,7 +14,7 @@ use wgpu::{Instance, Texture};
use crate::input::XrInput; use crate::input::XrInput;
use crate::resources::{ use crate::resources::{
Swapchain, SwapchainInner, XrEnvironmentBlendMode, XrFrameState, XrFrameWaiter, XrInstance, Swapchain, SwapchainInner, XrEnvironmentBlendMode, XrFrameState, XrFrameWaiter, XrInstance,
XrSession, XrSessionRunning, XrSwapchain, XrViews, XrSession, XrSessionRunning, XrSwapchain, XrViews, XrResolution, XrFormat,
}; };
use crate::VIEW_TYPE; use crate::VIEW_TYPE;
@@ -29,6 +29,8 @@ pub fn initialize_xr_graphics(
XrInstance, XrInstance,
XrSession, XrSession,
XrEnvironmentBlendMode, XrEnvironmentBlendMode,
XrResolution,
XrFormat,
XrSessionRunning, XrSessionRunning,
XrFrameWaiter, XrFrameWaiter,
XrSwapchain, XrSwapchain,
@@ -367,20 +369,24 @@ pub fn initialize_xr_graphics(
xr_instance.clone().into(), xr_instance.clone().into(),
session.clone().into_any_graphics().into(), session.clone().into_any_graphics().into(),
blend_mode.into(), blend_mode.into(),
resolution.into(),
swapchain_format.into(),
AtomicBool::new(false).into(), AtomicBool::new(false).into(),
Mutex::new(frame_wait).into(), Mutex::new(frame_wait).into(),
Mutex::new(Swapchain::Vulkan(SwapchainInner { Swapchain::Vulkan(SwapchainInner {
stream: frame_stream, stream: Mutex::new(frame_stream),
handle, handle: Mutex::new(handle),
resolution,
format: swapchain_format,
buffers, buffers,
image_index: 0, image_index: Mutex::new(0),
})) })
.into(), .into(),
XrInput::new(xr_instance, session.into_any_graphics())?, XrInput::new(xr_instance, session.into_any_graphics())?,
Mutex::default().into(), Mutex::default().into(),
Mutex::default().into(), Mutex::new(xr::FrameState {
predicted_display_time: xr::Time::from_nanos(1),
predicted_display_period: xr::Duration::from_nanos(1),
should_render: true,
}).into(),
)) ))
} }

View File

@@ -11,7 +11,10 @@ use bevy::app::PluginGroupBuilder;
use bevy::ecs::system::SystemState; use bevy::ecs::system::SystemState;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews}; use bevy::render::camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews};
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue}; use bevy::render::pipelined_rendering::RenderExtractApp;
use bevy::render::renderer::{
render_system, RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue,
};
use bevy::render::settings::RenderSettings; use bevy::render::settings::RenderSettings;
use bevy::render::{Render, RenderApp, RenderPlugin, RenderSet}; use bevy::render::{Render, RenderApp, RenderPlugin, RenderSet};
use bevy::window::{PrimaryWindow, RawHandleWrapper}; use bevy::window::{PrimaryWindow, RawHandleWrapper};
@@ -37,6 +40,8 @@ pub struct FutureXrResources(
XrInstance, XrInstance,
XrSession, XrSession,
XrEnvironmentBlendMode, XrEnvironmentBlendMode,
XrResolution,
XrFormat,
XrSessionRunning, XrSessionRunning,
XrFrameWaiter, XrFrameWaiter,
XrSwapchain, XrSwapchain,
@@ -66,6 +71,8 @@ impl Plugin for OpenXrPlugin {
xr_instance, xr_instance,
session, session,
blend_mode, blend_mode,
resolution,
format,
session_running, session_running,
frame_waiter, frame_waiter,
swapchain, swapchain,
@@ -80,6 +87,8 @@ impl Plugin for OpenXrPlugin {
xr_instance, xr_instance,
session, session,
blend_mode, blend_mode,
resolution,
format,
session_running, session_running,
frame_waiter, frame_waiter,
swapchain, swapchain,
@@ -112,6 +121,8 @@ impl Plugin for OpenXrPlugin {
xr_instance, xr_instance,
session, session,
blend_mode, blend_mode,
resolution,
format,
session_running, session_running,
frame_waiter, frame_waiter,
swapchain, swapchain,
@@ -125,6 +136,8 @@ impl Plugin for OpenXrPlugin {
app.insert_resource(xr_instance.clone()) app.insert_resource(xr_instance.clone())
.insert_resource(session.clone()) .insert_resource(session.clone())
.insert_resource(blend_mode.clone()) .insert_resource(blend_mode.clone())
.insert_resource(resolution.clone())
.insert_resource(format.clone())
.insert_resource(session_running.clone()) .insert_resource(session_running.clone())
.insert_resource(frame_waiter.clone()) .insert_resource(frame_waiter.clone())
.insert_resource(swapchain.clone()) .insert_resource(swapchain.clone())
@@ -133,30 +146,44 @@ impl Plugin for OpenXrPlugin {
.insert_resource(frame_state.clone()) .insert_resource(frame_state.clone())
.insert_resource(action_sets.clone()); .insert_resource(action_sets.clone());
let swapchain_mut = swapchain.lock().unwrap(); let (left, right) = swapchain.get_render_views();
let (left, right) = swapchain_mut.get_render_views();
let format = swapchain_mut.format();
let left = ManualTextureView { let left = ManualTextureView {
texture_view: left.into(), texture_view: left.into(),
size: swapchain_mut.resolution(), size: *resolution,
format, format: *format,
}; };
let right = ManualTextureView { let right = ManualTextureView {
texture_view: right.into(), texture_view: right.into(),
size: swapchain_mut.resolution(), size: *resolution,
format, format: *format,
}; };
let mut manual_texture_views = app.world.resource_mut::<ManualTextureViews>(); let mut manual_texture_views = app.world.resource_mut::<ManualTextureViews>();
manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left); manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left);
manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right); manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right);
drop(manual_texture_views); drop(manual_texture_views);
drop(swapchain_mut); let pipeline_app = app.sub_app_mut(RenderExtractApp);
pipeline_app
.insert_resource(xr_instance.clone())
.insert_resource(session.clone())
.insert_resource(blend_mode.clone())
.insert_resource(resolution.clone())
.insert_resource(format.clone())
.insert_resource(session_running.clone())
.insert_resource(frame_waiter.clone())
.insert_resource(swapchain.clone())
.insert_resource(input.clone())
.insert_resource(views.clone())
.insert_resource(frame_state.clone())
.insert_resource(action_sets.clone());
drop(pipeline_app);
let render_app = app.sub_app_mut(RenderApp); let render_app = app.sub_app_mut(RenderApp);
render_app render_app
.insert_resource(xr_instance) .insert_resource(xr_instance)
.insert_resource(session) .insert_resource(session)
.insert_resource(blend_mode) .insert_resource(blend_mode)
.insert_resource(resolution)
.insert_resource(format)
.insert_resource(session_running) .insert_resource(session_running)
.insert_resource(frame_waiter) .insert_resource(frame_waiter)
.insert_resource(swapchain) .insert_resource(swapchain)
@@ -168,9 +195,9 @@ impl Plugin for OpenXrPlugin {
render_app.add_systems( render_app.add_systems(
Render, Render,
( (
pre_frame.in_set(RenderSet::Prepare).before(post_frame), begin_frame.before(render_system).after(RenderSet::ExtractCommands),
post_frame.in_set(RenderSet::Prepare), locate_views.before(render_system),
post_queue_submit.in_set(RenderSet::Cleanup), end_frame.after(render_system),
), ),
); );
} }
@@ -187,116 +214,120 @@ impl PluginGroup for DefaultXrPlugins {
} }
} }
pub fn pre_frame( pub fn begin_frame(
instance: Res<XrInstance>, instance: Res<XrInstance>,
session: Res<XrSession>, session: Res<XrSession>,
session_running: Res<XrSessionRunning>, session_running: Res<XrSessionRunning>,
frame_state: Res<XrFrameState>, resolution: Res<XrResolution>,
frame_waiter: Res<XrFrameWaiter>, format: Res<XrFormat>,
swapchain: Res<XrSwapchain>, swapchain: Res<XrSwapchain>,
xr_input: Res<XrInput>, frame_waiter: Res<XrFrameWaiter>,
action_sets: Res<ActionSets>, frame_state: Res<XrFrameState>,
mut manual_texture_views: ResMut<ManualTextureViews>, mut manual_texture_views: ResMut<ManualTextureViews>,
) { ) {
while let Some(event) = instance.poll_event(&mut Default::default()).unwrap() { {
use xr::Event::*; let _span = info_span!("xr_poll_events");
match event { while let Some(event) = instance.poll_event(&mut Default::default()).unwrap() {
SessionStateChanged(e) => { use xr::Event::*;
// Session state change is where we can begin and end sessions, as well as match event {
// find quit messages! SessionStateChanged(e) => {
info!("entered XR state {:?}", e.state()); // Session state change is where we can begin and end sessions, as well as
match e.state() { // find quit messages!
xr::SessionState::READY => { info!("entered XR state {:?}", e.state());
session.begin(VIEW_TYPE).unwrap(); match e.state() {
session_running.store(true, std::sync::atomic::Ordering::Relaxed); xr::SessionState::READY => {
session.begin(VIEW_TYPE).unwrap();
session_running.store(true, std::sync::atomic::Ordering::Relaxed);
}
xr::SessionState::STOPPING => {
session.end().unwrap();
session_running.store(false, std::sync::atomic::Ordering::Relaxed);
}
xr::SessionState::EXITING | xr::SessionState::LOSS_PENDING => return,
_ => {}
} }
xr::SessionState::STOPPING => {
session.end().unwrap();
session_running.store(false, std::sync::atomic::Ordering::Relaxed);
}
xr::SessionState::EXITING | xr::SessionState::LOSS_PENDING => return,
_ => {}
} }
InstanceLossPending(_) => return,
EventsLost(e) => {
warn!("lost {} XR events", e.lost_event_count());
}
_ => {}
} }
InstanceLossPending(_) => return,
EventsLost(e) => {
warn!("lost {} XR events", e.lost_event_count());
}
_ => {}
} }
} }
if !session_running.load(std::sync::atomic::Ordering::Relaxed) { {
// Don't grind up the CPU let _span = info_span!("xr_wait_frame").entered();
//std::thread::sleep(std::time::Duration::from_millis(10)); *frame_state.lock().unwrap() = frame_waiter.lock().unwrap().wait().unwrap();
return;
} }
{
*frame_state.lock().unwrap() = Some(frame_waiter.lock().unwrap().wait().unwrap()); let _span = info_span!("xr_begin_frame").entered();
swapchain.begin().unwrap()
let mut swapchain = swapchain.lock().unwrap(); }
{
swapchain.begin().unwrap(); let _span = info_span!("xr_acquire_image").entered();
swapchain.update_render_views(); swapchain.acquire_image().unwrap()
let (left, right) = swapchain.get_render_views(); }
/*let mut active_action_sets = vec![]; {
for i in &action_sets.0 { let _span = info_span!("xr_wait_image").entered();
active_action_sets.push(xr::ActiveActionSet::new(i)); swapchain.wait_image().unwrap();
}
{
let _span = info_span!("xr_update_manual_texture_views").entered();
let (left, right) = swapchain.get_render_views();
let left = ManualTextureView {
texture_view: left.into(),
size: **resolution,
format: **format,
};
let right = ManualTextureView {
texture_view: right.into(),
size: **resolution,
format: **format,
};
manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left);
manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right);
} }
info!("action sets: {:#?}", action_sets.0.len());
match session.sync_actions(&active_action_sets) {
Err(err) => {
warn!("{}", err);
}
_ => {}
}*/
let format = swapchain.format();
let left = ManualTextureView {
texture_view: left.into(),
size: swapchain.resolution(),
format,
};
let right = ManualTextureView {
texture_view: right.into(),
size: swapchain.resolution(),
format,
};
manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left);
manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right);
} }
pub fn post_frame( pub fn end_frame(
xr_frame_state: Res<XrFrameState>,
views: Res<XrViews>,
input: Res<XrInput>,
swapchain: Res<XrSwapchain>,
resolution: Res<XrResolution>,
environment_blend_mode: Res<XrEnvironmentBlendMode>,
) {
{
let _span = info_span!("xr_release_image").entered();
swapchain.release_image().unwrap();
}
{
let _span = info_span!("xr_end_frame").entered();
swapchain
.end(
xr_frame_state.lock().unwrap().predicted_display_time,
&*views.lock().unwrap(),
&input.stage,
**resolution,
**environment_blend_mode,
)
.unwrap();
}
}
pub fn locate_views(
views: Res<XrViews>, views: Res<XrViews>,
input: Res<XrInput>, input: Res<XrInput>,
session: Res<XrSession>, session: Res<XrSession>,
xr_frame_state: Res<XrFrameState>, xr_frame_state: Res<XrFrameState>,
) { ) {
let _span = info_span!("xr_locate_views").entered();
*views.lock().unwrap() = session *views.lock().unwrap() = session
.locate_views( .locate_views(
VIEW_TYPE, VIEW_TYPE,
xr_frame_state xr_frame_state.lock().unwrap().predicted_display_time,
.lock()
.unwrap()
.unwrap()
.predicted_display_time,
&input.stage, &input.stage,
) )
.unwrap() .unwrap()
.1; .1;
} }
pub fn post_queue_submit(
xr_frame_state: Res<XrFrameState>,
views: Res<XrViews>,
input: Res<XrInput>,
swapchain: Res<XrSwapchain>,
environment_blend_mode: Res<XrEnvironmentBlendMode>,
) {
let xr_frame_state = xr_frame_state.lock().unwrap().unwrap();
let views = &*views.lock().unwrap();
let stage = &input.stage;
swapchain
.lock()
.unwrap()
.post_queue_submit(xr_frame_state, views, stage, **environment_blend_mode)
.unwrap();
}

View File

@@ -8,11 +8,12 @@ use openxr as xr;
xr_resource_wrapper!(XrInstance, xr::Instance); xr_resource_wrapper!(XrInstance, xr::Instance);
xr_resource_wrapper!(XrSession, xr::Session<xr::AnyGraphics>); xr_resource_wrapper!(XrSession, xr::Session<xr::AnyGraphics>);
xr_resource_wrapper!(XrEnvironmentBlendMode, xr::EnvironmentBlendMode); xr_resource_wrapper!(XrEnvironmentBlendMode, xr::EnvironmentBlendMode);
xr_resource_wrapper!(XrViewConfigurationViews, Vec<xr::ViewConfigurationView>); xr_resource_wrapper!(XrResolution, UVec2);
xr_resource_wrapper!(XrFormat, wgpu::TextureFormat);
xr_arc_resource_wrapper!(XrSessionRunning, AtomicBool); xr_arc_resource_wrapper!(XrSessionRunning, AtomicBool);
xr_arc_resource_wrapper!(XrFrameWaiter, Mutex<xr::FrameWaiter>); xr_arc_resource_wrapper!(XrFrameWaiter, Mutex<xr::FrameWaiter>);
xr_arc_resource_wrapper!(XrSwapchain, Mutex<Swapchain>); xr_arc_resource_wrapper!(XrSwapchain, Swapchain);
xr_arc_resource_wrapper!(XrFrameState, Mutex<Option<xr::FrameState>>); xr_arc_resource_wrapper!(XrFrameState, Mutex<xr::FrameState>);
xr_arc_resource_wrapper!(XrViews, Mutex<Vec<xr::View>>); xr_arc_resource_wrapper!(XrViews, Mutex<Vec<xr::View>>);
pub enum Swapchain { pub enum Swapchain {
@@ -20,67 +21,70 @@ pub enum Swapchain {
} }
impl Swapchain { impl Swapchain {
pub(crate) fn begin(&mut self) -> xr::Result<()> { pub(crate) fn begin(&self) -> xr::Result<()> {
match self { match self {
Swapchain::Vulkan(swap) => swap.begin(), Swapchain::Vulkan(swapchain) => swapchain.begin(),
}
}
pub(crate) fn update_render_views(&mut self) {
match self {
Swapchain::Vulkan(swap) => swap.update_render_views(),
} }
} }
pub(crate) fn get_render_views(&self) -> (wgpu::TextureView, wgpu::TextureView) { pub(crate) fn get_render_views(&self) -> (wgpu::TextureView, wgpu::TextureView) {
match self { match self {
Swapchain::Vulkan(swap) => swap.get_render_views(), Swapchain::Vulkan(swapchain) => swapchain.get_render_views(),
} }
} }
pub(crate) fn format(&self) -> wgpu::TextureFormat { pub(crate) fn acquire_image(&self) -> xr::Result<()> {
match self { match self {
Swapchain::Vulkan(swap) => swap.format, Swapchain::Vulkan(swapchain) => swapchain.acquire_image(),
} }
} }
pub(crate) fn resolution(&self) -> UVec2 { pub(crate) fn wait_image(&self) -> xr::Result<()> {
match self { match self {
Swapchain::Vulkan(swap) => swap.resolution, Swapchain::Vulkan(swapchain) => swapchain.wait_image(),
} }
} }
pub(crate) fn post_queue_submit( pub(crate) fn release_image(&self) -> xr::Result<()> {
&mut self, match self {
xr_frame_state: xr::FrameState, Swapchain::Vulkan(swapchain) => swapchain.release_image(),
}
}
pub(crate) fn end(
&self,
predicted_display_time: xr::Time,
views: &[openxr::View], views: &[openxr::View],
stage: &xr::Space, stage: &xr::Space,
resolution: UVec2,
environment_blend_mode: xr::EnvironmentBlendMode, environment_blend_mode: xr::EnvironmentBlendMode,
) -> xr::Result<()> { ) -> xr::Result<()> {
match self { match self {
Swapchain::Vulkan(swap) => { Swapchain::Vulkan(swapchain) => swapchain.end(
swap.post_queue_submit(xr_frame_state, views, stage, environment_blend_mode) predicted_display_time,
} views,
stage,
resolution,
environment_blend_mode,
),
} }
} }
} }
pub struct SwapchainInner<G: xr::Graphics> { pub struct SwapchainInner<G: xr::Graphics> {
pub(crate) stream: xr::FrameStream<G>, pub(crate) stream: Mutex<xr::FrameStream<G>>,
pub(crate) handle: xr::Swapchain<G>, pub(crate) handle: Mutex<xr::Swapchain<G>>,
pub(crate) resolution: UVec2,
pub(crate) format: wgpu::TextureFormat,
pub(crate) buffers: Vec<wgpu::Texture>, pub(crate) buffers: Vec<wgpu::Texture>,
pub(crate) image_index: usize, pub(crate) image_index: Mutex<usize>,
} }
impl<G: xr::Graphics> SwapchainInner<G> { impl<G: xr::Graphics> SwapchainInner<G> {
fn begin(&mut self) -> xr::Result<()> { fn begin(&self) -> xr::Result<()> {
self.stream.begin() self.stream.lock().unwrap().begin()
} }
fn get_render_views(&self) -> (wgpu::TextureView, wgpu::TextureView) { fn get_render_views(&self) -> (wgpu::TextureView, wgpu::TextureView) {
let texture = &self.buffers[self.image_index]; let texture = &self.buffers[*self.image_index.lock().unwrap()];
( (
texture.create_view(&wgpu::TextureViewDescriptor { texture.create_view(&wgpu::TextureViewDescriptor {
@@ -97,30 +101,41 @@ impl<G: xr::Graphics> SwapchainInner<G> {
) )
} }
fn update_render_views(&mut self) { fn acquire_image(&self) -> xr::Result<()> {
let image_index = self.handle.acquire_image().unwrap(); let image_index = self.handle.lock().unwrap().acquire_image()?;
self.handle.wait_image(xr::Duration::INFINITE).unwrap(); *self.image_index.lock().unwrap() = image_index as _;
Ok(())
self.image_index = image_index as _;
} }
fn post_queue_submit( fn wait_image(&self) -> xr::Result<()> {
&mut self, self.handle
xr_frame_state: xr::FrameState, .lock()
.unwrap()
.wait_image(xr::Duration::INFINITE)
}
fn release_image(&self) -> xr::Result<()> {
self.handle.lock().unwrap().release_image()
}
fn end(
&self,
predicted_display_time: xr::Time,
views: &[openxr::View], views: &[openxr::View],
stage: &xr::Space, stage: &xr::Space,
resolution: UVec2,
environment_blend_mode: xr::EnvironmentBlendMode, environment_blend_mode: xr::EnvironmentBlendMode,
) -> xr::Result<()> { ) -> xr::Result<()> {
self.handle.release_image().unwrap();
let rect = xr::Rect2Di { let rect = xr::Rect2Di {
offset: xr::Offset2Di { x: 0, y: 0 }, offset: xr::Offset2Di { x: 0, y: 0 },
extent: xr::Extent2Di { extent: xr::Extent2Di {
width: self.resolution.x as _, width: resolution.x as _,
height: self.resolution.y as _, height: resolution.y as _,
}, },
}; };
self.stream.end( let swapchain = self.handle.lock().unwrap();
xr_frame_state.predicted_display_time, self.stream.lock().unwrap().end(
predicted_display_time,
environment_blend_mode, environment_blend_mode,
&[&xr::CompositionLayerProjection::new().space(stage).views(&[ &[&xr::CompositionLayerProjection::new().space(stage).views(&[
xr::CompositionLayerProjectionView::new() xr::CompositionLayerProjectionView::new()
@@ -128,7 +143,7 @@ impl<G: xr::Graphics> SwapchainInner<G> {
.fov(views[0].fov) .fov(views[0].fov)
.sub_image( .sub_image(
xr::SwapchainSubImage::new() xr::SwapchainSubImage::new()
.swapchain(&self.handle) .swapchain(&swapchain)
.image_array_index(0) .image_array_index(0)
.image_rect(rect), .image_rect(rect),
), ),
@@ -137,7 +152,7 @@ impl<G: xr::Graphics> SwapchainInner<G> {
.fov(views[1].fov) .fov(views[1].fov)
.sub_image( .sub_image(
xr::SwapchainSubImage::new() xr::SwapchainSubImage::new()
.swapchain(&self.handle) .swapchain(&swapchain)
.image_array_index(1) .image_array_index(1)
.image_rect(rect), .image_rect(rect),
), ),