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 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())),
})
}