alex/beta-minecraft-rust
Minecraft Beta 1.7.3 Rewrite
BRANCH main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
root crates client src gpu swapchain.rs
use std::ptr::{null, null_mut};
use std::sync::Arc;
use std::time::{Duration, Instant};
use volcanic::*;
use super::pacing::{self, Clock, Pacer};
use super::resources::view_create_info;
use super::{Device, Error, Result, Surface, check, create, zeroed};
/// Present stages that tell when an image reached the display, most accurate first.
const DISPLAY_STAGES: [VkPresentStageFlagsEXT; 3] = [
VkPresentStageFlagsEXT::IMAGE_FIRST_PIXEL_VISIBLE_EXT,
VkPresentStageFlagsEXT::IMAGE_FIRST_PIXEL_OUT_EXT,
VkPresentStageFlagsEXT::REQUEST_DEQUEUED_EXT,
];
/// Time domains that can be calibrated against `Instant`, preferred first.
const TIME_DOMAINS: [VkTimeDomainKHR; 3] = [
VkTimeDomainKHR::ClockMonotonic,
VkTimeDomainKHR::SwapchainLocalExt,
VkTimeDomainKHR::PresentStageLocalExt,
];
const RECALIBRATION_PRESENTS: u64 = 1024;
/// A present mode that renders like the others, unlike the shared presentable image modes.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PresentMode {
Immediate,
Mailbox,
Fifo,
FifoRelaxed,
FifoLatestReady,
}
impl PresentMode {
pub const ALL: [Self; 5] = [
Self::Immediate,
Self::Mailbox,
Self::Fifo,
Self::FifoRelaxed,
Self::FifoLatestReady,
];
/// Whether images queue up for refresh cycles, which present timing schedules.
pub fn is_fifo(self) -> bool {
!matches!(self, Self::Immediate | Self::Mailbox)
}
fn vk(self) -> VkPresentModeKHR {
match self {
Self::Immediate => VkPresentModeKHR::Immediate,
Self::Mailbox => VkPresentModeKHR::Mailbox,
Self::Fifo => VkPresentModeKHR::Fifo,
Self::FifoRelaxed => VkPresentModeKHR::FifoRelaxed,
Self::FifoLatestReady => VkPresentModeKHR::FifoLatestReady,
}
}
}
/// How the swapchain presents frames.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Presentation {
pub mode: PresentMode,
/// Pace frames with present timing, which needs a FIFO present mode.
pub pacing: bool,
}
/// An acquired swapchain image.
#[derive(Clone, Copy)]
pub struct Frame {
pub index: u32,
pub image: VkImage,
pub view: VkImageView,
/// Signal this from the frame's last submission; presentation waits on it.
pub render_finished: VkSemaphore,
}
pub struct Swapchain {
handle: VkSwapchainKHR,
images: Vec<VkImage>,
views: Vec<VkImageView>,
render_finished: Vec<VkSemaphore>,
extent: VkExtent2D,
format: VkFormat,
presentation: Presentation,
/// The present modes the surface offers and the device may use, in `PresentMode::ALL` order.
present_modes: Vec<PresentMode>,
timing_support: Option<TimingSupport>,
timing: Option<Timing>,
surface: Surface,
device: Arc<Device>,
}
/// What the surface offers for `VK_EXT_present_timing`.
#[derive(Clone, Copy)]
struct TimingSupport {
display_stage: VkPresentStageFlagsEXT,
absolute: bool,
}
/// Present timing of a swapchain created with `VK_SWAPCHAIN_CREATE_PRESENT_TIMING_BIT_EXT`.
struct Timing {
support: TimingSupport,
/// The time domain and its id.
domain: (VkTimeDomainKHR, u64),
/// The time domain's clocks at the queue operations end and display stages.
clocks: [Clock; 2],
domains_counter: u64,
properties_counter: u64,
next_id: u64,
/// Presents whose timing results are still in the swapchain's results queue.
outstanding: u32,
queue_size: u32,
pacer: Pacer,
}
impl Swapchain {
pub fn new(
device: &Arc<Device>,
surface: Surface,
extent: VkExtent2D,
presentation: Presentation,
) -> Result<Self> {
let mut swapchain = Self {
handle: VK_NULL_HANDLE,
images: Vec::new(),
views: Vec::new(),
render_finished: Vec::new(),
extent,
format: surface_format(device, &surface)?,
presentation: Presentation {
mode: PresentMode::Fifo,
pacing: false,
},
present_modes: present_modes(device, &surface)?,
timing_support: timing_support(device, &surface),
timing: None,
surface,
device: device.clone(),
};
swapchain.configure(presentation);
swapchain.recreate(extent)?;
Ok(swapchain)
}
pub fn extent(&self) -> VkExtent2D {
self.extent
}
pub fn format(&self) -> VkFormat {
self.format
}
pub fn present_modes(&self) -> &[PresentMode] {
&self.present_modes
}
pub fn supports_pacing(&self) -> bool {
self.timing_support.is_some()
}
pub fn presentation(&self) -> Presentation {
self.presentation
}
/// Switches to the available presentation closest to `requested`, and returns whether the
/// swapchain must be recreated for it. FIFO is always available.
pub fn configure(&mut self, requested: Presentation) -> bool {
let fallbacks: &[PresentMode] = if requested.mode.is_fifo() {
&[]
} else {
&[PresentMode::Mailbox, PresentMode::Immediate]
};
let mode = [requested.mode]
.iter()
.chain(fallbacks)
.copied()
.find(|mode| self.present_modes.contains(mode))
.unwrap_or(PresentMode::Fifo);
let pacing = requested.pacing && mode.is_fifo() && self.supports_pacing();
let presentation = Presentation { mode, pacing };
std::mem::replace(&mut self.presentation, presentation) != presentation
}
/// When pacing, plans the next frame and returns when it will be displayed.
pub fn begin_frame(&mut self, min_interval: Duration) -> Option<Instant> {
let timing = self.timing.as_mut()?;
timing.pacer.begin(timing.next_id, min_interval)
}
/// When pacing, when to start rendering the frame after the current one.
pub fn next_frame(&self) -> Option<Instant> {
self.timing.as_ref()?.pacer.wake()
}
/// Reports time the current frame spent waiting for the GPU or for a swapchain image.
pub fn waited(&mut self, duration: Duration) {
if let Some(timing) = &mut self.timing {
timing.pacer.waited(duration);
}
}
pub fn recreate(&mut self, requested: VkExtent2D) -> Result<()> {
let device = self.device.clone();
let instance = &device.instance.commands;
device.wait_idle()?;
let mut capabilities = zeroed::<VkSurfaceCapabilitiesKHR>();
check(unsafe {
instance.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
device.physical,
self.surface.handle,
&mut capabilities,
)
})?;
let Presentation { mode, pacing } = self.presentation;
let timing_flags = if pacing {
VkSwapchainCreateFlagsKHR::PRESENT_TIMING_EXT
| VkSwapchainCreateFlagsKHR::PRESENT_ID_2_KHR
} else {
VkSwapchainCreateFlagsKHR::empty()
};
self.extent = if capabilities.currentExtent.width == u32::MAX {
VkExtent2D {
width: requested.width.clamp(
capabilities.minImageExtent.width,
capabilities.maxImageExtent.width,
),
height: requested.height.clamp(
capabilities.minImageExtent.height,
capabilities.maxImageExtent.height,
),
}
} else {
capabilities.currentExtent
};
let mut image_count = capabilities.minImageCount + 1;
if capabilities.maxImageCount > 0 {
image_count = image_count.min(capabilities.maxImageCount);
}
// Pipelines never write alpha, so every mode shows an opaque image
let composite_alpha = [
VkCompositeAlphaFlagsKHR::OPAQUE_KHR,
VkCompositeAlphaFlagsKHR::PRE_MULTIPLIED_KHR,
VkCompositeAlphaFlagsKHR::POST_MULTIPLIED_KHR,
VkCompositeAlphaFlagsKHR::INHERIT_KHR,
]
.into_iter()
.find(|&mode| capabilities.supportedCompositeAlpha.contains(mode))
.unwrap_or(VkCompositeAlphaFlagsKHR::OPAQUE_KHR);
let old = self.handle;
let info = VkSwapchainCreateInfoKHR {
sType: VkStructureType::SwapchainCreateInfoKhr,
flags: timing_flags,
surface: self.surface.handle,
minImageCount: image_count,
imageFormat: self.format,
imageColorSpace: VkColorSpaceKHR::SrgbNonlinear,
imageExtent: self.extent,
imageArrayLayers: 1,
imageUsage: VkImageUsageFlags::COLOR_ATTACHMENT
| VkImageUsageFlags::TRANSFER_DST
| VkImageUsageFlags::TRANSFER_SRC,
imageSharingMode: VkSharingMode::Exclusive,
preTransform: capabilities.currentTransform,
compositeAlpha: composite_alpha,
presentMode: mode.vk(),
clipped: VK_TRUE,
oldSwapchain: old,
..zeroed()
};
let commands = &device.commands;
self.timing = None;
check(unsafe {
commands.vkCreateSwapchainKHR(device.handle, &info, null(), &mut self.handle)
})?;
self.destroy_images();
if old != VK_NULL_HANDLE {
unsafe { commands.vkDestroySwapchainKHR(device.handle, old, null()) };
}
self.images = super::enumerate(|count, items| unsafe {
commands.vkGetSwapchainImagesKHR(device.handle, self.handle, count, items)
})?;
for &image in &self.images {
let view_info = view_create_info(image, self.format, VkImageAspectFlags::COLOR);
self.views.push(create(|view| unsafe {
commands.vkCreateImageView(device.handle, &view_info, null(), view)
})?);
self.render_finished.push(device.create_semaphore()?);
}
if let Some(support) = self.timing_support.filter(|_| pacing) {
self.timing = Timing::new(&device, self.handle, support, 4 * self.images.len() as u32)
.inspect_err(|error| tracing::warn!("frame pacing unavailable: {error}"))
.ok();
}
tracing::debug!(
"swapchain {}x{} with {} images, {mode:?}",
self.extent.width,
self.extent.height,
self.images.len()
);
Ok(())
}
/// Returns `None` when the swapchain is out of date and must be recreated.
pub fn acquire(&mut self, image_available: VkSemaphore) -> Result<Option<Frame>> {
let mut index = 0;
let result = unsafe {
self.device.commands.vkAcquireNextImageKHR(
self.device.handle,
self.handle,
u64::MAX,
image_available,
VK_NULL_HANDLE,
&mut index,
)
};
match result {
VkResult::Success | VkResult::SuboptimalKhr => {
let i = index as usize;
Ok(Some(Frame {
index,
image: self.images[i],
view: self.views[i],
render_finished: self.render_finished[i],
}))
}
VkResult::ErrorOutOfDateKhr => Ok(None),
error => Err(Error::Vulkan(error)),
}
}
/// Returns `false` when the swapchain should be recreated.
pub fn present(&mut self, frame: &Frame) -> Result<bool> {
let mut info = VkPresentInfoKHR {
sType: VkStructureType::PresentInfoKhr,
waitSemaphoreCount: 1,
pWaitSemaphores: &frame.render_finished,
swapchainCount: 1,
pSwapchains: &self.handle,
pImageIndices: &frame.index,
..zeroed()
};
let (commands, queue) = (&self.device.commands, self.device.queue);
let result = match &mut self.timing {
Some(timing) => {
let id = timing.next_id;
timing.next_id += 1;
// Without free result slots, presenting with stage queries would fail
let queries = if timing.outstanding < timing.queue_size {
timing.outstanding += 1;
VkPresentStageFlagsEXT::QUEUE_OPERATIONS_END_EXT | timing.support.display_stage
} else {
VkPresentStageFlagsEXT::empty()
};
let target = (timing.pacer.target(id))
.filter(|_| timing.support.absolute)
.map(|target| pacing::nanos(timing.clocks[1], target));
if let Some(time) = timing.pacer.present_time(id) {
let wait = time.saturating_duration_since(Instant::now());
std::thread::sleep(wait);
timing.pacer.waited(wait);
}
let timing_info = VkPresentTimingInfoEXT {
sType: VkStructureType::PresentTimingInfoExt,
flags: VkPresentTimingInfoFlagsEXT::PRESENT_AT_NEAREST_REFRESH_CYCLE_EXT,
targetTime: target.unwrap_or(0),
timeDomainId: timing.domain.1,
presentStageQueries: queries,
targetTimeDomainPresentStage: timing.support.display_stage,
..zeroed()
};
let timings = VkPresentTimingsInfoEXT {
sType: VkStructureType::PresentTimingsInfoExt,
swapchainCount: 1,
pTimingInfos: &timing_info,
..zeroed()
};
let present_id = VkPresentId2KHR {
sType: VkStructureType::PresentId2Khr,
pNext: (&raw const timings).cast(),
swapchainCount: 1,
pPresentIds: &id,
};
info.pNext = (&raw const present_id).cast();
unsafe { commands.vkQueuePresentKHR(queue, &info) }
}
None => unsafe { commands.vkQueuePresentKHR(queue, &info) },
};
if let Some(timing) = &mut self.timing
&& let Err(error) = timing.poll(&self.device, self.handle)
{
tracing::warn!("frame pacing stopped: {error}");
self.timing = None;
}
match result {
VkResult::Success => Ok(true),
VkResult::SuboptimalKhr | VkResult::ErrorOutOfDateKhr => Ok(false),
error => Err(Error::Vulkan(error)),
}
}
fn destroy_images(&mut self) {
let (commands, device) = (&self.device.commands, self.device.handle);
for view in self.views.drain(..) {
unsafe { commands.vkDestroyImageView(device, view, null()) };
}
for semaphore in self.render_finished.drain(..) {
unsafe { commands.vkDestroySemaphore(device, semaphore, null()) };
}
self.images.clear();
}
}
impl Timing {
fn new(
device: &Device,
swapchain: VkSwapchainKHR,
support: TimingSupport,
queue_size: u32,
) -> Result<Self> {
let commands = &device.commands;
check(unsafe {
commands.vkSetSwapchainPresentTimingQueueSizeEXT(device.handle, swapchain, queue_size)
})?;
let (domain, domains_counter) = time_domain(device, swapchain)?;
Ok(Self {
support,
domain,
clocks: calibrate(device, swapchain, domain, support.display_stage)?,
domains_counter,
properties_counter: 0,
next_id: 1,
outstanding: 0,
queue_size,
pacer: Pacer::new(!support.absolute),
})
}
/// Feeds new timing results to the pacer and follows changes of the swapchain's timing.
fn poll(&mut self, device: &Device, swapchain: VkSwapchainKHR) -> Result<()> {
const BATCH: usize = 8;
let commands = &device.commands;
let info = VkPastPresentationTimingInfoEXT {
sType: VkStructureType::PastPresentationTimingInfoExt,
swapchain,
..zeroed()
};
let mut stages = [[zeroed::<VkPresentStageTimeEXT>(); 2]; BATCH];
let mut timings = [zeroed::<VkPastPresentationTimingEXT>(); BATCH];
let mut domain_lost = false;
loop {
for (timing, stages) in timings.iter_mut().zip(&mut stages) {
*timing = VkPastPresentationTimingEXT {
sType: VkStructureType::PastPresentationTimingExt,
presentStageCount: stages.len() as u32,
pPresentStages: stages.as_mut_ptr(),
..zeroed()
};
}
let mut past = VkPastPresentationTimingPropertiesEXT {
sType: VkStructureType::PastPresentationTimingPropertiesExt,
presentationTimingCount: BATCH as u32,
pPresentationTimings: timings.as_mut_ptr(),
..zeroed()
};
let result =
unsafe { commands.vkGetPastPresentationTimingEXT(device.handle, &info, &mut past) };
match result {
VkResult::Success | VkResult::Incomplete => {}
// The swapchain is recreated after the present that reported it
VkResult::ErrorOutOfDateKhr => return Ok(()),
error => return Err(Error::Vulkan(error)),
}
let count = past.presentationTimingCount as usize;
for (timing, stages) in timings.iter().zip(&stages).take(count) {
self.outstanding = self.outstanding.saturating_sub(1);
if timing.timeDomainId != self.domain.1 {
domain_lost = true;
continue;
}
let stages = &stages[..timing.presentStageCount as usize];
let time = |stage, clock| {
let time = stages.iter().find(|s| s.stage == stage && s.time != 0)?;
Some(pacing::instant(clock, time.time))
};
let [queue_end, display] = self.clocks;
self.pacer.feedback(
timing.presentId,
time(VkPresentStageFlagsEXT::QUEUE_OPERATIONS_END_EXT, queue_end),
time(self.support.display_stage, display),
);
}
if past.timingPropertiesCounter != self.properties_counter
|| !self.pacer.refresh_known()
{
self.refresh(device, swapchain)?;
}
let domains_changed = past.timeDomainsCounter != self.domains_counter || domain_lost;
if domains_changed {
(self.domain, self.domains_counter) = time_domain(device, swapchain)?;
domain_lost = false;
}
if domains_changed || self.next_id.is_multiple_of(RECALIBRATION_PRESENTS) {
let stage = self.support.display_stage;
self.clocks = calibrate(device, swapchain, self.domain, stage)?;
}
if result == VkResult::Success {
return Ok(());
}
}
}
fn refresh(&mut self, device: &Device, swapchain: VkSwapchainKHR) -> Result<()> {
let mut properties = VkSwapchainTimingPropertiesEXT {
sType: VkStructureType::SwapchainTimingPropertiesExt,
..zeroed()
};
let result = unsafe {
device.commands.vkGetSwapchainTimingPropertiesEXT(
device.handle,
swapchain,
&mut properties,
&mut self.properties_counter,
)
};
match result {
VkResult::Success => {
tracing::debug!(
"refresh duration {} ns, interval {} ns",
properties.refreshDuration,
properties.refreshInterval
);
self.pacer
.set_refresh(properties.refreshDuration, properties.refreshInterval);
Ok(())
}
VkResult::NotReady => Ok(()),
error => Err(Error::Vulkan(error)),
}
}
}
fn present_modes(device: &Device, surface: &Surface) -> Result<Vec<PresentMode>> {
let offered = super::enumerate(|count, items| unsafe {
device
.instance
.commands
.vkGetPhysicalDeviceSurfacePresentModesKHR(
device.physical,
surface.handle,
count,
items,
)
})?;
Ok(PresentMode::ALL
.into_iter()
.filter(|&mode| mode != PresentMode::FifoLatestReady || device.fifo_latest_ready)
.filter(|mode| offered.contains(&mode.vk()))
.collect())
}
fn timing_support(device: &Device, surface: &Surface) -> Option<TimingSupport> {
if !device.present_timing {
return None;
}
let mut timing = VkPresentTimingSurfaceCapabilitiesEXT {
sType: VkStructureType::PresentTimingSurfaceCapabilitiesExt,
..zeroed()
};
let mut id2 = VkSurfaceCapabilitiesPresentId2KHR {
sType: VkStructureType::SurfaceCapabilitiesPresentId2Khr,
pNext: (&raw mut timing).cast(),
..zeroed()
};
let mut capabilities = VkSurfaceCapabilities2KHR {
sType: VkStructureType::SurfaceCapabilities2Khr,
pNext: (&raw mut id2).cast(),
..zeroed()
};
let info = VkPhysicalDeviceSurfaceInfo2KHR {
sType: VkStructureType::PhysicalDeviceSurfaceInfo2Khr,
surface: surface.handle,
..zeroed()
};
let result = unsafe {
device
.instance
.commands
.vkGetPhysicalDeviceSurfaceCapabilities2KHR(device.physical, &info, &mut capabilities)
};
tracing::debug!(
"surface present timing: {}, absolute times: {}, stages: {:?}",
timing.presentTimingSupported == VK_TRUE,
timing.presentAtAbsoluteTimeSupported == VK_TRUE,
timing.presentStageQueries
);
let supported = result == VkResult::Success
&& timing.presentTimingSupported == VK_TRUE
&& id2.presentId2Supported == VK_TRUE;
let display_stage = DISPLAY_STAGES
.into_iter()
.find(|&stage| supported && timing.presentStageQueries.contains(stage))?;
Some(TimingSupport {
display_stage,
absolute: device.present_at_absolute_time
&& timing.presentAtAbsoluteTimeSupported == VK_TRUE,
})
}
/// The preferred time domain of the swapchain with its id, and the time domains counter.
fn time_domain(
device: &Device,
swapchain: VkSwapchainKHR,
) -> Result<((VkTimeDomainKHR, u64), u64)> {
let get = |properties: &mut VkSwapchainTimeDomainPropertiesEXT, counter: *mut u64| {
check(unsafe {
device.commands.vkGetSwapchainTimeDomainPropertiesEXT(
device.handle,
swapchain,
properties,
counter,
)
})
};
let mut properties = VkSwapchainTimeDomainPropertiesEXT {
sType: VkStructureType::SwapchainTimeDomainPropertiesExt,
..zeroed()
};
get(&mut properties, null_mut())?;
let mut domains = vec![VkTimeDomainKHR::Device; properties.timeDomainCount as usize];
let mut ids = vec![0; domains.len()];
properties.pTimeDomains = domains.as_mut_ptr();
properties.pTimeDomainIds = ids.as_mut_ptr();
let mut counter = 0;
get(&mut properties, &mut counter)?;
let available = || {
domains
.iter()
.zip(&ids)
.take(properties.timeDomainCount as usize)
};
tracing::debug!(
"swapchain time domains: {:?}",
available().collect::<Vec<_>>()
);
let domain = TIME_DOMAINS
.into_iter()
.find_map(|wanted| available().find(|(domain, _)| **domain == wanted))
.ok_or(Error::NoPresentTimeDomain)?;
Ok(((*domain.0, *domain.1), counter))
}
/// The clocks of `domain` at the queue operations end and display stages, which differ only for
/// stage-local time domains.
fn calibrate(
device: &Device,
swapchain: VkSwapchainKHR,
(domain, id): (VkTimeDomainKHR, u64),
display_stage: VkPresentStageFlagsEXT,
) -> Result<[Clock; 2]> {
let clock = |stage| {
let swapchain_info = VkSwapchainCalibratedTimestampInfoEXT {
sType: VkStructureType::SwapchainCalibratedTimestampInfoExt,
swapchain,
presentStage: if domain == VkTimeDomainKHR::PresentStageLocalExt {
stage
} else {
VkPresentStageFlagsEXT::empty()
},
timeDomainId: id,
..zeroed()
};
let info = VkCalibratedTimestampInfoKHR {
sType: VkStructureType::CalibratedTimestampInfoKhr,
pNext: if domain == VkTimeDomainKHR::ClockMonotonic {
null()
} else {
(&raw const swapchain_info).cast()
},
timeDomain: domain,
};
let (mut timestamp, mut deviation) = (0, 0);
check(unsafe {
device.commands.vkGetCalibratedTimestampsKHR(
device.handle,
1,
&info,
&mut timestamp,
&mut deviation,
)
})?;
Ok::<_, Error>((timestamp, Instant::now()))
};
Ok([
clock(VkPresentStageFlagsEXT::QUEUE_OPERATIONS_END_EXT)?,
clock(display_stage)?,
])
}
/// Colors are written as displayed, so the image must be 8-bit UNORM without sRGB encoding.
fn surface_format(device: &Device, surface: &Surface) -> Result<VkFormat> {
let formats = super::enumerate(|count, items| unsafe {
device
.instance
.commands
.vkGetPhysicalDeviceSurfaceFormatsKHR(device.physical, surface.handle, count, items)
})?;
[VkFormat::B8G8R8A8Unorm, VkFormat::R8G8B8A8Unorm]
.into_iter()
.find(|&format| {
formats.iter().any(|available| {
available.format == format && available.colorSpace == VkColorSpaceKHR::SrgbNonlinear
})
})
.ok_or(Error::UnsupportedSurfaceFormat)
}
impl Drop for Swapchain {
fn drop(&mut self) {
let _ = self.device.wait_idle();
self.destroy_images();
let commands = &self.device.commands;
unsafe { commands.vkDestroySwapchainKHR(self.device.handle, self.handle, null()) };
}
}