max / shop
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
1 file changed,
+45 insertions,
-16 deletions
| @@ -819,19 +819,47 @@ | |||
| 819 | 819 | awaiting_frame: bool, | |
| 820 | 820 | } | |
| 821 | 821 | ||
| 822 | - | /// Render one frame and request the next `wl_surface.frame` callback. All | |
| 823 | - | /// content changes (PTY reads, resize, focus toggle, activity-light phase) | |
| 824 | - | /// go through this — never called directly except by the coalescing paths | |
| 825 | - | /// (main-loop-after-events and CompositorHandler::frame). | |
| 822 | + | /// Render one frame. All content changes (PTY reads, resize, focus toggle, | |
| 823 | + | /// activity-light phase) go through this — never called directly except by | |
| 824 | + | /// the coalescing paths (main-loop-after-events and the frame callback). | |
| 825 | + | /// | |
| 826 | + | /// The frame callback that gates the next render is requested inside `draw`, | |
| 827 | + | /// on the commit that carries it, and `awaiting_frame` is set here only when | |
| 828 | + | /// that commit actually happened. The two have to agree: a callback counted as | |
| 829 | + | /// in flight but never sent is never answered, and since `awaiting_frame` | |
| 830 | + | /// blocks every later render, one failed frame would leave the window blank | |
| 831 | + | /// and unresponsive for the life of the process. A frame we could not present | |
| 832 | + | /// leaves `dirty` set instead, so the next event retries it. | |
| 826 | 833 | fn render_now(app: &mut App) { | |
| 827 | - | // Register the frame callback BEFORE presenting so it becomes active on | |
| 828 | - | // the commit that queue.present issues. `awaiting_frame` blocks further | |
| 829 | - | // renders until the compositor tells us it's ready for another one. | |
| 830 | - | app.chrome.xdg_window.wl_surface().frame(&app.qh, ()); | |
| 831 | - | app.awaiting_frame = true; | |
| 832 | - | app.dirty = false; | |
| 833 | - | if let Err(e) = draw(app) { | |
| 834 | - | warn!("render: {e:?}"); | |
| 834 | + | match draw(app) { | |
| 835 | + | Ok(()) => { | |
| 836 | + | app.awaiting_frame = true; | |
| 837 | + | app.dirty = false; | |
| 838 | + | } | |
| 839 | + | Err(e) => warn!("render: {e:?}"), | |
| 840 | + | } | |
| 841 | + | } | |
| 842 | + | ||
| 843 | + | /// The swapchain image to draw into, reconfiguring once if the surface went | |
| 844 | + | /// stale under us. | |
| 845 | + | /// | |
| 846 | + | /// `Outdated` and `Lost` mean the swapchain no longer matches the surface, | |
| 847 | + | /// which a reconfigure fixes; a compositor that resizes us between our own | |
| 848 | + | /// configure and this acquire produces one on an ordinary frame. `Timeout` | |
| 849 | + | /// means the compositor is holding every buffer and has none for us yet, so | |
| 850 | + | /// there is nothing to fix and the frame is skipped. | |
| 851 | + | fn acquire(app: &mut App) -> anyhow::Result<wgpu::SurfaceTexture> { | |
| 852 | + | use wgpu::CurrentSurfaceTexture::{Lost, Outdated, Suboptimal, Success}; | |
| 853 | + | match app.surface.get_current_texture() { | |
| 854 | + | Success(t) | Suboptimal(t) => Ok(t), | |
| 855 | + | Outdated | Lost => { | |
| 856 | + | app.surface.configure(&app.device, &app.surface_config); | |
| 857 | + | match app.surface.get_current_texture() { | |
| 858 | + | Success(t) | Suboptimal(t) => Ok(t), | |
| 859 | + | other => anyhow::bail!("surface acquire after reconfigure: {other:?}"), | |
| 860 | + | } | |
| 861 | + | } | |
| 862 | + | other => anyhow::bail!("surface acquire: {other:?}"), | |
| 835 | 863 | } | |
| 836 | 864 | } | |
| 837 | 865 | ||
| @@ -839,10 +867,7 @@ | |||
| 839 | 867 | // path; longer ones bury the arithmetic. | |
| 840 | 868 | #[allow(clippy::many_single_char_names)] | |
| 841 | 869 | fn draw(app: &mut App) -> anyhow::Result<()> { | |
| 842 | - | let frame = match app.surface.get_current_texture() { | |
| 843 | - | wgpu::CurrentSurfaceTexture::Success(t) | wgpu::CurrentSurfaceTexture::Suboptimal(t) => t, | |
| 844 | - | other => anyhow::bail!("surface acquire: {other:?}"), | |
| 845 | - | }; | |
| 870 | + | let frame = acquire(app)?; | |
| 846 | 871 | let view = frame | |
| 847 | 872 | .texture | |
| 848 | 873 | .create_view(&wgpu::TextureViewDescriptor::default()); | |
| @@ -1035,6 +1060,10 @@ | |||
| 1035 | 1060 | ); | |
| 1036 | 1061 | } | |
| 1037 | 1062 | app.queue.submit(std::iter::once(encoder.finish())); | |
| 1063 | + | // Requested before presenting so it rides the commit `present` issues. | |
| 1064 | + | // Both go down the same connection in the order they were made, so this | |
| 1065 | + | // is the last point at which the request still lands on this frame. | |
| 1066 | + | app.chrome.xdg_window.wl_surface().frame(&app.qh, ()); | |
| 1038 | 1067 | app.queue.present(frame); | |
| 1039 | 1068 | Ok(()) | |
| 1040 | 1069 | } |