Theme

alex/beta-minecraft-rust

Minecraft Beta 1.7.3 Rewrite

BRANCH main
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
Download file

root / crates / client / src / gpu / validation.rs

//! Opt-in Khronos validation, configured through `VULKAN_VALIDATION` and reported via `tracing`.

use std::backtrace::Backtrace;
use std::borrow::Cow;
use std::env::VarError;
use std::ffi::{CStr, c_char, c_void};
use std::fmt::Write;

use volcanic::*;

use super::zeroed;

pub(super) const VAR: &str = "VULKAN_VALIDATION";
pub(super) const LAYER: &CStr = c"VK_LAYER_KHRONOS_validation";

static TRUE: VkBool32 = VK_TRUE;
static FALSE: VkBool32 = VK_FALSE;

#[derive(Debug, thiserror::Error)]
#[error(
    "invalid {VAR} value {0:?}: expected none, best_practice, warn, error or fatal, \
     optionally followed by ,sync ,gpu and ,printf"
)]
pub struct ParseError(String);

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Level {
    BestPractice,
    Warn,
    Error,
    Fatal,
}

#[derive(Clone, Copy, Debug)]
pub struct Validation {
    pub level: Level,
    pub sync: bool,
    pub gpu: bool,
    pub printf: bool,
}

impl std::fmt::Display for Validation {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        f.write_str(match self.level {
            Level::BestPractice => "best_practice",
            Level::Warn => "warn",
            Level::Error => "error",
            Level::Fatal => "fatal",
        })?;
        for (on, name) in [
            (self.sync, ",sync"),
            (self.gpu, ",gpu"),
            (self.printf, ",printf"),
        ] {
            if on {
                f.write_str(name)?;
            }
        }
        Ok(())
    }
}

impl Validation {
    pub fn from_env() -> Result<Option<Self>, ParseError> {
        match std::env::var(VAR) {
            Ok(value) => Self::parse(&value),
            Err(VarError::NotPresent) => Ok(None),
            Err(VarError::NotUnicode(value)) => {
                Err(ParseError(value.to_string_lossy().into_owned()))
            }
        }
    }

    fn parse(text: &str) -> Result<Option<Self>, ParseError> {
        let text = text.trim();
        if matches!(text, "" | "none") {
            return Ok(None);
        }
        let error = || ParseError(text.to_owned());
        let mut parts = text.split(',').map(str::trim);
        let level = match parts.next() {
            Some("best_practice") => Level::BestPractice,
            Some("warn") => Level::Warn,
            Some("error") => Level::Error,
            Some("fatal") => Level::Fatal,
            _ => return Err(error()),
        };
        let mut validation = Self {
            level,
            sync: false,
            gpu: false,
            printf: false,
        };
        for part in parts {
            *match part {
                "sync" => &mut validation.sync,
                "gpu" => &mut validation.gpu,
                "printf" => &mut validation.printf,
                _ => return Err(error()),
            } = true;
        }
        Ok(Some(validation))
    }

    fn severities(self) -> VkDebugUtilsMessageSeverityFlagsEXT {
        let mut severities = VkDebugUtilsMessageSeverityFlagsEXT::ERROR_EXT;
        if matches!(self.level, Level::BestPractice | Level::Warn) {
            severities |= VkDebugUtilsMessageSeverityFlagsEXT::WARNING_EXT;
        }
        if self.printf {
            severities |= VkDebugUtilsMessageSeverityFlagsEXT::INFO_EXT;
        }
        severities
    }

    pub(super) fn report_flags(self) -> Vec<*const c_char> {
        let severities = self.severities();
        [
            (
                VkDebugUtilsMessageSeverityFlagsEXT::ERROR_EXT,
                [c"error"].as_slice(),
            ),
            (
                VkDebugUtilsMessageSeverityFlagsEXT::WARNING_EXT,
                &[c"warn", c"perf"],
            ),
            (VkDebugUtilsMessageSeverityFlagsEXT::INFO_EXT, &[c"info"]),
        ]
        .into_iter()
        .filter(|&(severity, _)| severities.contains(severity))
        .flat_map(|(_, flags)| flags.iter().map(|flag| flag.as_ptr()))
        .collect()
    }

    /// `report_flags` must outlive the returned settings.
    pub(super) fn layer_settings(self, report_flags: &[*const c_char]) -> [VkLayerSettingEXT; 6] {
        let setting =
            |name: &CStr, r#type, values: *const c_void, count: usize| VkLayerSettingEXT {
                pLayerName: LAYER.as_ptr(),
                pSettingName: name.as_ptr(),
                r#type,
                valueCount: count as u32,
                pValues: values,
            };
        let flag = |name, on| {
            let value: *const VkBool32 = if on { &TRUE } else { &FALSE };
            setting(name, VkLayerSettingTypeEXT::BOOL32, value.cast(), 1)
        };
        [
            setting(
                c"report_flags",
                VkLayerSettingTypeEXT::String,
                report_flags.as_ptr().cast(),
                report_flags.len(),
            ),
            flag(
                c"validate_best_practices",
                self.level == Level::BestPractice,
            ),
            flag(c"validate_sync", self.sync),
            flag(c"gpuav_enable", self.gpu),
            flag(c"printf_enable", self.printf),
            flag(c"printf_to_stdout", false),
        ]
    }

    pub(super) fn messenger_info(self) -> VkDebugUtilsMessengerCreateInfoEXT {
        VkDebugUtilsMessengerCreateInfoEXT {
            sType: VkStructureType::DebugUtilsMessengerCreateInfoExt,
            messageSeverity: self.severities(),
            messageType: VkDebugUtilsMessageTypeFlagsEXT::GENERAL_EXT
                | VkDebugUtilsMessageTypeFlagsEXT::VALIDATION_EXT
                | VkDebugUtilsMessageTypeFlagsEXT::PERFORMANCE_EXT,
            pfnUserCallback: Some(if self.level == Level::Fatal {
                report::<true>
            } else {
                report::<false>
            }),
            ..zeroed()
        }
    }
}

fn text<'a>(ptr: *const c_char) -> Cow<'a, str> {
    if ptr.is_null() {
        Cow::Borrowed("")
    } else {
        unsafe { CStr::from_ptr(ptr) }.to_string_lossy()
    }
}

unsafe extern "system" fn report<const FATAL: bool>(
    severity: VkDebugUtilsMessageSeverityFlagBitsEXT,
    _types: VkDebugUtilsMessageTypeFlagsEXT,
    data: *const VkDebugUtilsMessengerCallbackDataEXT,
    _user: *mut c_void,
) -> VkBool32 {
    let data = unsafe { &*data };
    let message = text(data.pMessage);
    let vuid = text(data.pMessageIdName);
    if severity.contains(VkDebugUtilsMessageSeverityFlagsEXT::INFO_EXT) {
        if vuid.contains("DEBUG-PRINTF") {
            tracing::info!(target: "vulkan::printf", "{}", message.trim_end());
        } else {
            tracing::debug!(target: "vulkan", "{}", message.trim_end());
        }
        return VK_FALSE;
    }

    let (summary, spec) = match message.split_once("The Vulkan spec states:") {
        Some((summary, spec)) => (summary, Some(spec.trim())),
        None => (&*message, None),
    };
    let mut out = summary.trim_end().to_owned();
    if !vuid.is_empty() {
        let _ = write!(out, "\n    vuid={vuid}");
    }
    let objects = match data.objectCount {
        0 => &[][..],
        count => unsafe { std::slice::from_raw_parts(data.pObjects, count as usize) },
    };
    if !objects.is_empty() {
        out.push_str("\n    objects=[");
        for (i, object) in objects.iter().enumerate() {
            let separator = if i == 0 { "" } else { ", " };
            let kind = object_type_name(object.objectType);
            let _ = write!(out, "{separator}{kind} {:#x}", object.objectHandle);
            if !object.pObjectName.is_null() {
                let _ = write!(out, " {:?}", text(object.pObjectName));
            }
        }
        out.push(']');
    }
    if let Some(spec) = spec {
        let (statement, url) = spec
            .strip_suffix(')')
            .and_then(|rest| rest.rsplit_once(" (http"))
            .map_or((spec, None), |(statement, url)| (statement, Some(url)));
        let _ = write!(out, "\n    spec: {statement:?}");
        if let Some(url) = url {
            let _ = write!(out, "\n    http{url}");
        }
    }

    if severity.contains(VkDebugUtilsMessageSeverityFlagsEXT::ERROR_EXT) {
        tracing::error!(target: "vulkan", "{out}");
        if FATAL {
            tracing::error!(
                target: "vulkan",
                "aborting on validation error ({VAR}=fatal)\n{}",
                Backtrace::force_capture()
            );
            std::process::abort();
        }
    } else {
        tracing::warn!(target: "vulkan", "{out}");
    }
    VK_FALSE
}

fn object_type_name(kind: VkObjectType) -> Cow<'static, str> {
    Cow::Borrowed(match kind {
        VkObjectType::Instance => "Instance",
        VkObjectType::PhysicalDevice => "PhysicalDevice",
        VkObjectType::Device => "Device",
        VkObjectType::Queue => "Queue",
        VkObjectType::Semaphore => "Semaphore",
        VkObjectType::CommandBuffer => "CommandBuffer",
        VkObjectType::Fence => "Fence",
        VkObjectType::DeviceMemory => "DeviceMemory",
        VkObjectType::Buffer => "Buffer",
        VkObjectType::Image => "Image",
        VkObjectType::ImageView => "ImageView",
        VkObjectType::ShaderModule => "ShaderModule",
        VkObjectType::Pipeline => "Pipeline",
        VkObjectType::Sampler => "Sampler",
        VkObjectType::CommandPool => "CommandPool",
        VkObjectType::SurfaceKhr => "Surface",
        VkObjectType::SwapchainKhr => "Swapchain",
        VkObjectType::DebugUtilsMessengerExt => "DebugUtilsMessenger",
        other => return Cow::Owned(format!("ObjectType({})", other.as_raw())),
    })
}