1use alloc::sync::Arc;
46use alloc::vec;
47use alloc::vec::Vec;
48use core::cell::Cell;
49
50use crate::vk_dev::{Device, Garbage, Shared};
51use crate::vk_ffi as vk;
52use crate::vk_inst::InstShared;
53use crate::*;
54
55pub(crate) struct SurfShared {
56 inst: Arc<InstShared>,
57 pub h: vk::Handle,
58 pub window: i32,
59}
60
61impl Drop for SurfShared {
62 fn drop(&mut self) {
63 if let Some(d) = self.inst.f.destroy_surface_khr {
64 unsafe { d(self.inst.inst, self.h, core::ptr::null()) };
66 }
67 }
68}
69
70#[derive(Clone)]
72pub struct Surface(pub(crate) Arc<SurfShared>);
73
74impl Surface {
75 pub fn track_size(&self) {}
77
78 pub fn new(instance: &Instance, window: i32) -> Result<Surface> {
80 #[cfg(windows)]
81 {
82 let nw = plat::win_native(window).ok_or(GpuError::Invalid("no such window"))?;
83 Surface::native(instance, nw, window)
84 }
85 #[cfg(target_os = "android")]
86 {
87 let nw = plat::win_gpu_child(window).ok_or(GpuError::NotReady)?;
88 Surface::native(instance, nw, window)
89 }
90 #[cfg(not(any(windows, target_os = "android")))]
91 {
92 let _ = (instance, window);
93 Err(GpuError::Unsupported("window surfaces on this target"))
94 }
95 }
96
97 #[cfg(target_os = "android")]
100 pub fn native(instance: &Instance, nw: plat::NativeWindow, window: i32) -> Result<Surface> {
101 let sh = &instance.sh;
102 if !sh.surface {
103 return Err(GpuError::Unsupported("window surfaces"));
104 }
105 let create = sh.f.create_android_surface_khr.ok_or(GpuError::Unsupported("VK_KHR_android_surface"))?;
106 let ci = vk::AndroidSurfaceCreateInfoKHR {
107 s_type: vk::STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR,
108 window: nw.window,
109 ..vk::Zero::zero()
110 };
111 let mut h = 0;
112 let r = unsafe { create(sh.inst, &ci, core::ptr::null(), &mut h) };
114 if r != vk::SUCCESS {
115 return Err(crate::vk_inst::err(r));
116 }
117 Ok(Surface(Arc::new(SurfShared { inst: sh.clone(), h, window })))
118 }
119
120 #[cfg(windows)]
123 pub fn native(instance: &Instance, nw: plat::NativeWindow, window: i32) -> Result<Surface> {
124 let sh = &instance.sh;
125 if !sh.surface {
126 return Err(GpuError::Unsupported("window surfaces"));
127 }
128 {
129 let create = sh.f.create_win32_surface_khr.ok_or(GpuError::Unsupported("VK_KHR_win32_surface"))?;
130 let ci = vk::Win32SurfaceCreateInfoKHR {
131 s_type: vk::STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
132 hinstance: nw.hinstance,
133 hwnd: nw.hwnd,
134 ..vk::Zero::zero()
135 };
136 let mut h = 0;
137 let r = unsafe { create(sh.inst, &ci, core::ptr::null(), &mut h) };
139 if r != vk::SUCCESS {
140 return Err(crate::vk_inst::err(r));
141 }
142 Ok(Surface(Arc::new(SurfShared { inst: sh.clone(), h, window })))
143 }
144 }
145}
146
147#[must_use]
149pub struct Frame {
150 image: u32,
151 slot: u32,
152 generation: u32,
153 size: (u32, u32),
154}
155
156#[must_use]
158pub enum TryAcquire {
159 Frame(Frame),
160 NotReady,
162 Stale,
164 NoArea,
166}
167
168impl Frame {
169 pub fn index(&self) -> u32 {
171 self.image
172 }
173 pub fn size(&self) -> (u32, u32) {
174 self.size
175 }
176}
177
178pub struct Swapchain {
180 sh: Arc<Shared>,
181 surf: Surface,
182 desc: SwapchainDesc,
183 h: vk::Handle,
184 format: Format,
185 vk_format: u32,
186 color_space: u32,
187 mode: PresentMode,
188 extent: (u32, u32),
189 images: Vec<Texture>,
190 acquire: Vec<vk::Handle>,
191 done: Vec<vk::Handle>,
192 slot_value: Vec<Cell<u64>>,
193 slot: Cell<u32>,
194 generation: u32,
195 stale: Cell<bool>,
196 outstanding: Cell<bool>,
197}
198
199const TURNED_IS_FINE: bool = cfg!(target_os = "android");
202
203fn present_mode_vk(m: PresentMode) -> u32 {
204 match m {
205 PresentMode::Fifo => vk::PRESENT_MODE_FIFO_KHR,
206 PresentMode::Mailbox => vk::PRESENT_MODE_MAILBOX_KHR,
207 PresentMode::Immediate => vk::PRESENT_MODE_IMMEDIATE_KHR,
208 }
209}
210
211impl Device {
212 pub fn create_swapchain(&self, surface: &Surface, desc: &SwapchainDesc) -> Result<Swapchain> {
213 let sh = &self.sh;
214 sh.check()?;
215 if !sh.swapchain {
216 return Err(GpuError::Unsupported("swapchains (VK_KHR_swapchain)"));
217 }
218 let f = &sh.inst.f;
219 let (Some(support), Some(formats)) = (f.get_physical_device_surface_support_khr, f.get_physical_device_surface_formats_khr)
220 else {
221 return Err(GpuError::Unsupported("surface queries"));
222 };
223 let mut ok = 0;
224 sh.vk(unsafe { support(sh.pd, sh.family, surface.0.h, &mut ok) })?;
226 if ok == 0 {
227 return Err(GpuError::Unsupported("presentation from this queue"));
228 }
229 let mut n = 0u32;
230 sh.vk(unsafe { formats(sh.pd, surface.0.h, &mut n, core::ptr::null_mut()) })?;
232 let mut fl: Vec<vk::SurfaceFormatKHR> = vec![vk::Zero::zero(); n as usize];
233 let r = unsafe { formats(sh.pd, surface.0.h, &mut n, fl.as_mut_ptr()) };
235 if r != vk::INCOMPLETE {
236 sh.vk(r)?;
237 }
238 fl.truncate(n as usize);
239 let want: [(u32, Format); 2] = if desc.srgb {
240 [(vk::FORMAT_B8G8R8A8_SRGB, Format::Bgra8Srgb), (vk::FORMAT_R8G8B8A8_SRGB, Format::Rgba8Srgb)]
241 } else {
242 [(vk::FORMAT_B8G8R8A8_UNORM, Format::Bgra8Unorm), (vk::FORMAT_R8G8B8A8_UNORM, Format::Rgba8Unorm)]
243 };
244 let pick = want.iter().find_map(|&(vf, ff)| {
245 fl.iter().find(|s| s.format == vf && s.color_space == vk::COLOR_SPACE_SRGB_NONLINEAR_KHR).map(|s| (vf, ff, s.color_space))
246 });
247 let Some((vk_format, format, color_space)) = pick else {
248 return Err(GpuError::Unsupported("an 8-bit RGBA / BGRA surface format"));
249 };
250 let mut d = *desc;
251 d.frames_in_flight = d.frames_in_flight.clamp(1, 3);
252 match plat::env("jfgui_GPU_PRESENT").as_deref() {
253 Some("fifo") => d.present = PresentMode::Fifo,
254 Some("mailbox") => d.present = PresentMode::Mailbox,
255 Some("immediate") => d.present = PresentMode::Immediate,
256 _ => {}
257 }
258 let mut sc = Swapchain {
259 sh: sh.clone(),
260 surf: surface.clone(),
261 desc: d,
262 h: 0,
263 format,
264 vk_format,
265 color_space,
266 mode: PresentMode::Fifo,
267 extent: (0, 0),
268 images: Vec::new(),
269 acquire: Vec::new(),
270 done: Vec::new(),
271 slot_value: (0..d.frames_in_flight).map(|_| Cell::new(0)).collect(),
272 slot: Cell::new(0),
273 generation: 0,
274 stale: Cell::new(false),
275 outstanding: Cell::new(false),
276 };
277 sc.build()?;
278 Ok(sc)
279 }
280}
281
282impl Swapchain {
283 fn semaphore(&self) -> Result<vk::Handle> {
284 let sh = &self.sh;
285 let si = vk::SemaphoreCreateInfo { s_type: vk::STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, ..vk::Zero::zero() };
286 let mut s = 0;
287 sh.vk(unsafe { (sh.f.create_semaphore)(sh.dev, &si, core::ptr::null(), &mut s) })?;
289 Ok(s)
290 }
291
292 fn release(&mut self) {
294 self.images.clear();
295 for s in self.acquire.drain(..).chain(self.done.drain(..)) {
296 self.sh.defer(Garbage::Semaphore(s));
297 }
298 }
299
300 fn build(&mut self) -> Result<()> {
302 let sh = self.sh.clone();
303 let inst = &sh.inst.f;
304 let (Some(caps_fn), Some(modes_fn)) =
305 (inst.get_physical_device_surface_capabilities_khr, inst.get_physical_device_surface_present_modes_khr)
306 else {
307 return Err(GpuError::Unsupported("surface queries"));
308 };
309 let (Some(create), Some(images_fn)) = (sh.f.create_swapchain_khr, sh.f.get_swapchain_images_khr) else {
310 return Err(GpuError::Unsupported("VK_KHR_swapchain"));
311 };
312 let surf = self.surf.0.h;
313 let mut caps: vk::SurfaceCapabilitiesKHR = vk::Zero::zero();
314 let r = unsafe { caps_fn(sh.pd, surf, &mut caps) };
316 sh.vk(r)?;
317 let mut ext = caps.current_extent;
318 if ext.width == u32::MAX {
319 let (ww, wh) = window_size(self.surf.0.window);
320 ext = vk::Extent2D {
321 width: ww.clamp(caps.min_image_extent.width, caps.max_image_extent.width),
322 height: wh.clamp(caps.min_image_extent.height, caps.max_image_extent.height),
323 };
324 }
325 self.stale.set(false);
326 self.outstanding.set(false);
327 let old = self.h;
328 if ext.width == 0 || ext.height == 0 {
329 self.release();
331 if old != 0 {
332 sh.defer(Garbage::Swapchain(old));
333 }
334 self.h = 0;
335 self.extent = (0, 0);
336 return Ok(());
337 }
338 let mut n = 0u32;
340 sh.vk(unsafe { modes_fn(sh.pd, surf, &mut n, core::ptr::null_mut()) })?;
342 let mut modes = vec![0u32; n as usize];
343 let r = unsafe { modes_fn(sh.pd, surf, &mut n, modes.as_mut_ptr()) };
345 if r != vk::INCOMPLETE {
346 sh.vk(r)?;
347 }
348 modes.truncate(n as usize);
349 self.mode = if modes.contains(&present_mode_vk(self.desc.present)) { self.desc.present } else { PresentMode::Fifo };
350 let mut count = if self.desc.min_images != 0 { self.desc.min_images.max(caps.min_image_count) } else { caps.min_image_count + 1 };
351 if caps.max_image_count != 0 {
352 count = count.min(caps.max_image_count);
353 }
354 let mut usage = vk::IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
355 if caps.supported_usage_flags & vk::IMAGE_USAGE_TRANSFER_DST_BIT != 0 {
356 usage |= vk::IMAGE_USAGE_TRANSFER_DST_BIT;
357 }
358 let copy_src = self.desc.copy_src && caps.supported_usage_flags & vk::IMAGE_USAGE_TRANSFER_SRC_BIT != 0;
359 if copy_src {
360 usage |= vk::IMAGE_USAGE_TRANSFER_SRC_BIT;
361 }
362 let alpha = if caps.supported_composite_alpha & vk::COMPOSITE_ALPHA_OPAQUE_BIT_KHR != 0 {
363 vk::COMPOSITE_ALPHA_OPAQUE_BIT_KHR
364 } else if caps.supported_composite_alpha & vk::COMPOSITE_ALPHA_INHERIT_BIT_KHR != 0 {
365 vk::COMPOSITE_ALPHA_INHERIT_BIT_KHR
366 } else {
367 1 << caps.supported_composite_alpha.trailing_zeros().min(31)
368 };
369 let ci = vk::SwapchainCreateInfoKHR {
370 s_type: vk::STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
371 surface: surf,
372 min_image_count: count,
373 image_format: self.vk_format,
374 image_color_space: self.color_space,
375 image_extent: ext,
376 image_array_layers: 1,
377 image_usage: usage,
378 image_sharing_mode: vk::SHARING_MODE_EXCLUSIVE,
379 pre_transform: if cfg!(target_os = "android") && caps.supported_transforms & vk::SURFACE_TRANSFORM_IDENTITY_BIT_KHR != 0 {
380 vk::SURFACE_TRANSFORM_IDENTITY_BIT_KHR } else {
382 caps.current_transform
383 },
384 composite_alpha: alpha,
385 present_mode: present_mode_vk(self.mode),
386 clipped: 1,
387 old_swapchain: old,
388 ..vk::Zero::zero()
389 };
390 let mut h = 0;
391 let r = unsafe { create(sh.dev, &ci, core::ptr::null(), &mut h) };
393 self.release();
394 if old != 0 {
395 sh.defer(Garbage::Swapchain(old));
396 }
397 self.h = 0;
398 sh.vk(r)?;
399 self.h = h;
400 self.extent = (ext.width, ext.height);
401 self.generation = self.generation.wrapping_add(1);
402 let mut n = 0u32;
403 sh.vk(unsafe { images_fn(sh.dev, h, &mut n, core::ptr::null_mut()) })?;
405 let mut imgs = vec![0u64; n as usize];
406 let r = unsafe { images_fn(sh.dev, h, &mut n, imgs.as_mut_ptr()) };
408 if r != vk::INCOMPLETE {
409 sh.vk(r)?;
410 }
411 imgs.truncate(n as usize);
412 let mut tu = TextureUsage::COLOR_TARGET | TextureUsage::COPY_DST;
413 if copy_src {
414 tu = tu | TextureUsage::COPY_SRC;
415 }
416 let td = TextureDesc::d2(ext.width, ext.height, self.format, tu);
417 for &img in &imgs {
418 self.images.push(crate::vk_res::wrap_image(&sh, img, td)?);
419 }
420 for _ in 0..self.desc.frames_in_flight {
421 let s = self.semaphore()?;
422 self.acquire.push(s);
423 }
424 for _ in 0..imgs.len() {
425 let s = self.semaphore()?;
426 self.done.push(s);
427 }
428 for v in &self.slot_value {
429 v.set(0);
430 }
431 Ok(())
432 }
433
434 pub fn rebuild(&mut self) -> Result<()> {
437 self.sh.queue_idle()?;
438 self.build()
439 }
440
441 pub fn try_acquire(&mut self) -> Result<TryAcquire> {
443 self.sh.check()?;
444 let Some(acq) = self.sh.f.acquire_next_image_khr else {
445 return Err(GpuError::Unsupported("VK_KHR_swapchain"));
446 };
447 if window_minimised(self.surf.0.window) {
448 return Ok(TryAcquire::NoArea);
449 }
450 if self.stale.get() || self.outstanding.get() || self.h == 0 || window_size(self.surf.0.window) != self.extent {
451 self.stale.set(true);
452 return Ok(TryAcquire::Stale);
453 }
454 let k = self.slot.get();
455 let prev = self.slot_value[k as usize].get();
456 if prev != 0 && prev > self.sh.refresh() {
457 self.sh.check()?;
458 return Ok(TryAcquire::NotReady);
459 }
460 let mut image = 0u32;
461 let r = unsafe { acq(self.sh.dev, self.h, 0, self.acquire[k as usize], 0, &mut image) };
463 match r {
464 vk::SUCCESS | vk::SUBOPTIMAL_KHR => {
465 if r == vk::SUBOPTIMAL_KHR && !TURNED_IS_FINE {
466 self.stale.set(true);
467 }
468 self.outstanding.set(true);
469 Ok(TryAcquire::Frame(Frame { image, slot: k, generation: self.generation, size: self.extent }))
470 }
471 vk::ERROR_OUT_OF_DATE_KHR => {
472 self.stale.set(true);
473 Ok(TryAcquire::Stale)
474 }
475 vk::TIMEOUT | vk::NOT_READY => Ok(TryAcquire::NotReady),
476 _ => {
477 self.sh.vk(r)?;
478 Ok(TryAcquire::NotReady)
479 }
480 }
481 }
482
483 pub fn acquire(&mut self) -> Result<Option<Frame>> {
485 self.sh.check()?;
486 let Some(acq) = self.sh.f.acquire_next_image_khr else {
487 return Err(GpuError::Unsupported("VK_KHR_swapchain"));
488 };
489 if window_minimised(self.surf.0.window) {
490 return Ok(None);
491 }
492 if self.outstanding.get() {
493 self.stale.set(true); }
495 if window_size(self.surf.0.window) != self.extent {
496 self.stale.set(true);
497 }
498 if self.stale.get() || self.h == 0 {
499 self.rebuild()?;
500 }
501 for attempt in 0..2 {
502 if self.h == 0 {
503 return Ok(None);
504 }
505 let k = self.slot.get();
506 let prev = self.slot_value[k as usize].get();
507 if prev != 0 && !self.sh.wait_value(prev, 10_000_000_000)? {
508 return Err(GpuError::Timeout);
509 }
510 let mut image = 0u32;
511 let r = unsafe { acq(self.sh.dev, self.h, 10_000_000_000, self.acquire[k as usize], 0, &mut image) };
513 match r {
514 vk::SUCCESS | vk::SUBOPTIMAL_KHR => {
515 if r == vk::SUBOPTIMAL_KHR && !TURNED_IS_FINE {
516 self.stale.set(true);
517 }
518 self.outstanding.set(true);
519 return Ok(Some(Frame { image, slot: k, generation: self.generation, size: self.extent }));
520 }
521 vk::ERROR_OUT_OF_DATE_KHR if attempt == 0 => self.rebuild()?,
522 vk::ERROR_OUT_OF_DATE_KHR => return Ok(None),
523 vk::TIMEOUT | vk::NOT_READY => return Err(GpuError::Timeout),
524 _ => {
525 self.sh.vk(r)?;
526 }
527 }
528 }
529 Ok(None)
530 }
531
532 pub fn view(&self, frame: &Frame) -> &TextureView {
534 self.images[frame.image as usize].view()
535 }
536
537 pub fn texture(&self, frame: &Frame) -> &Texture {
539 &self.images[frame.image as usize]
540 }
541
542 pub fn present<'a, I: IntoIterator<Item = CommandBuffer<'a>>>(&self, frame: Frame, cmds: I) -> Result<SubmitId> {
544 let sh = &self.sh;
545 let mut cbs = Vec::new();
546 let mut slots = Vec::new();
547 for c in cmds {
548 let s = c.into_slot();
549 cbs.push(s.cb);
550 slots.push(s);
551 }
552 if frame.generation != self.generation || !self.outstanding.get() || self.h == 0 {
553 for s in slots {
554 sh.give_slot(s);
555 }
556 return Err(GpuError::Invalid("present: a frame of an older swapchain"));
557 }
558 let k = frame.slot as usize;
559 let wait = vk::PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | vk::PIPELINE_STAGE_TRANSFER_BIT | vk::PIPELINE_STAGE_COMPUTE_SHADER_BIT;
560 let done = self.done[frame.image as usize];
561 let value = sh.submit(&cbs, slots, Some((self.acquire[k], wait)), Some(done))?;
562 self.slot_value[k].set(value);
563 self.slot.set((frame.slot + 1) % self.desc.frames_in_flight);
564 self.outstanding.set(false);
565 let Some(qp) = sh.f.queue_present_khr else {
566 return Err(GpuError::Unsupported("VK_KHR_swapchain"));
567 };
568 let pi = vk::PresentInfoKHR {
569 s_type: vk::STRUCTURE_TYPE_PRESENT_INFO_KHR,
570 wait_semaphore_count: 1,
571 p_wait_semaphores: &done,
572 swapchain_count: 1,
573 p_swapchains: &self.h,
574 p_image_indices: &frame.image,
575 ..vk::Zero::zero()
576 };
577 let r = sh.with_queue(|q| unsafe { qp(q, &pi) });
579 match r {
580 vk::SUCCESS => {}
581 vk::SUBOPTIMAL_KHR if TURNED_IS_FINE => {}
582 vk::SUBOPTIMAL_KHR | vk::ERROR_OUT_OF_DATE_KHR => self.stale.set(true),
583 _ => sh.vk(r)?,
584 }
585 Ok(SubmitId(value))
586 }
587
588 #[inline]
590 pub fn prepare(&mut self) -> Result<bool> {
591 Ok(true)
592 }
593
594 #[inline]
596 pub fn flush(&self) -> Result<bool> {
597 Ok(false)
598 }
599
600 #[inline]
602 pub fn busy(&self) -> bool {
603 false
604 }
605
606 #[inline]
608 pub fn next_look(&self, retry: u64) -> u64 {
609 retry
610 }
611
612 #[inline]
614 pub fn damage(&self, _rects: Option<&[[i32; 4]]>) {}
615
616 pub fn resize(&mut self) {
618 self.stale.set(true);
619 }
620
621 pub fn set_present_mode(&mut self, m: PresentMode) {
623 self.desc.present = m;
624 self.stale.set(true);
625 }
626
627 pub fn present_mode(&self) -> PresentMode {
628 self.mode
629 }
630 pub fn format(&self) -> Format {
631 self.format
632 }
633 pub fn size(&self) -> (u32, u32) {
635 self.extent
636 }
637 pub fn image_count(&self) -> usize {
638 self.images.len()
639 }
640}
641
642impl Drop for Swapchain {
643 fn drop(&mut self) {
644 let _ = self.sh.queue_idle(); self.release();
646 if self.h != 0 {
647 self.sh.defer(Garbage::Swapchain(self.h));
648 }
649 }
650}
651
652#[cfg(any(windows, target_os = "android"))]
653fn window_size(w: i32) -> (u32, u32) {
654 let (x, y) = plat::win_size(w);
655 (x.max(0) as u32, y.max(0) as u32)
656}
657
658#[cfg(any(windows, target_os = "android"))]
659fn window_minimised(w: i32) -> bool {
660 plat::win_state(w) & plat::WST_MINIMIZED != 0
661}
662
663#[cfg(not(any(windows, target_os = "android")))]
664fn window_size(_w: i32) -> (u32, u32) {
665 (0, 0)
666}
667
668#[cfg(not(any(windows, target_os = "android")))]
669fn window_minimised(_w: i32) -> bool {
670 true
671}