use ash::vk;
use concinnity_core::components;
use concinnity_core::gfx::auto_exposure;
use concinnity_core::gfx::render_types;
use concinnity_core::gfx::render_types::*;
use concinnity_core::input::keymap::KeyMap;
use concinnity_core::input::snapshot::InputSnapshot;
use concinnity_core::profile;
use concinnity_core::render::backend;
use concinnity_core::render::backend::FrameParams;
use concinnity_core::render::backend_init;
use concinnity_core::render::decal;
use concinnity_core::render::error;
use concinnity_core::render::hdr_output;
use concinnity_core::render::lights;
use concinnity_core::render::particles;
use concinnity_core::render::probe_book::ProbeBook;
use concinnity_core::render::render_graph;
use concinnity_core::render::scene_flow;
use concinnity_core::render::shadow_schedule;
use concinnity_core::render::skinned_slots;
use concinnity_core::render::slot_rewrites;
use concinnity_core::render::spot_shadow;
use concinnity_core::render::volumetric_fog;
use concinnity_core::window::display_mode;
use super::allocator::PooledBuffer;
use super::draw::*;
use super::post::*;
use super::texture::*;
use crate::vulkan::owned::{
OwnedDescriptorPool, OwnedFramebuffer, OwnedPipeline, OwnedPipelineLayout, OwnedRenderPass,
OwnedSampler, OwnedSetLayout, VkDevice,
};
pub(super) const HDR_FORMAT: vk::Format = vk::Format::R16G16B16A16_SFLOAT;
pub(super) struct VkShadow {
pub(super) render_pass: OwnedRenderPass,
pub(super) map: GpuImage,
pub(super) map_size: u32,
pub(super) framebuffers: Vec<OwnedFramebuffer>,
pub(super) pipeline: Option<OwnedPipeline>,
pub(super) pipeline_layout: Option<OwnedPipelineLayout>,
pub(super) global_set_layout: OwnedSetLayout,
pub(super) sampler: OwnedSampler,
pub(super) skinned_pipeline: Option<OwnedPipeline>,
pub(super) skinned_pipeline_layout: Option<OwnedPipelineLayout>,
pub(super) ubos: Vec<PooledBuffer>,
pub(super) uniforms: ShadowUniforms,
pub(super) light_dir: [f32; 3],
pub(super) update: components::ShadowUpdate,
pub(super) distance: u32,
pub(super) cascades: u32,
pub(super) scheduler: shadow_schedule::ShadowCascadeScheduler,
pub(super) render_mask: u32,
}
impl VkShadow {
pub(super) fn destroy(&mut self, _device: &VkDevice) {
self.map = GpuImage::null();
self.ubos.clear();
}
}
pub(super) struct VkSpotShadow {
pub(super) map: GpuImage,
pub(super) framebuffers: Vec<OwnedFramebuffer>,
pub(super) slice_size: u32,
pub(super) data_buffer: PooledBuffer,
pub(super) ubo: PooledBuffer,
pub(super) sets: Vec<vk::DescriptorSet>,
pub(super) _descriptor_pool: OwnedDescriptorPool,
pub(super) scheduler: spot_shadow::SpotShadowScheduler,
pub(super) render_mask: u32,
}
impl VkSpotShadow {
pub(super) fn count(&self) -> u32 {
self.framebuffers.len() as u32
}
pub(super) fn advance(&mut self, every_frame: bool) {
let count = self.framebuffers.len();
self.render_mask = self.scheduler.next_mask(every_frame, count);
}
pub(super) fn destroy(&mut self, _device: &VkDevice) {
self.map = GpuImage::null();
self.data_buffer = PooledBuffer::null();
self.ubo = PooledBuffer::null();
}
}
pub(super) struct VkAreaLight {
pub(super) buffer: PooledBuffer,
pub(super) ltc_matrix: GpuImage,
pub(super) ltc_magnitude: GpuImage,
}
impl VkAreaLight {
pub(super) fn destroy(&mut self, _device: &VkDevice) {
self.ltc_matrix = GpuImage::null();
self.ltc_magnitude = GpuImage::null();
self.buffer = PooledBuffer::null();
}
}
pub(super) struct VkSkinned {
pub(super) joint_set_layout: Option<OwnedSetLayout>,
pub(super) descriptor_pool: Option<OwnedDescriptorPool>,
pub(super) vertex_buffer: PooledBuffer,
pub(super) index_buffer: PooledBuffer,
pub(super) vertex_buffer_bytes: u64,
pub(super) index_buffer_bytes: u64,
pub(super) slots: skinned_slots::SkinnedSlots,
pub(super) joint_buffers: Vec<Vec<PooledBuffer>>,
pub(super) joint_sets: Vec<Vec<vk::DescriptorSet>>,
pub(super) skin: Option<super::raytrace::SkinPipeline>,
pub(super) deformed: Vec<super::raytrace::DeviceBuffer>,
pub(super) morph_delta_unique: Vec<PooledBuffer>,
pub(super) morph_delta_buffers: Vec<vk::Buffer>,
pub(super) morph_target_counts: Vec<u32>,
pub(super) morph_weight_buffers: Vec<Vec<PooledBuffer>>,
pub(super) deformed_primed: std::sync::atomic::AtomicBool,
}
impl VkSkinned {
pub(super) fn new() -> Self {
Self {
joint_set_layout: None,
descriptor_pool: None,
vertex_buffer: PooledBuffer::null(),
vertex_buffer_bytes: 0,
index_buffer: PooledBuffer::null(),
index_buffer_bytes: 0,
slots: skinned_slots::SkinnedSlots::new(),
joint_buffers: Vec::new(),
joint_sets: Vec::new(),
skin: None,
deformed: Vec::new(),
morph_delta_unique: Vec::new(),
morph_delta_buffers: Vec::new(),
morph_target_counts: Vec::new(),
morph_weight_buffers: Vec::new(),
deformed_primed: std::sync::atomic::AtomicBool::new(false),
}
}
pub(super) fn destroy(&mut self, device: &VkDevice) {
self.vertex_buffer = PooledBuffer::null();
self.index_buffer = PooledBuffer::null();
self.joint_buffers.clear();
self.morph_delta_unique.clear();
self.morph_weight_buffers.clear();
if let Some(skin) = self.skin.take() {
skin.destroy(device);
}
self.deformed.clear();
}
}
pub(super) struct VkGeometry {
pub(super) vertex_buffer: PooledBuffer,
pub(super) index_buffer: PooledBuffer,
pub(super) mesh_vtx_alloc: crate::suballoc::range_alloc::RangeAllocator,
pub(super) mesh_idx_alloc: crate::suballoc::range_alloc::RangeAllocator,
pub(super) vertex_buffer_bytes: u64,
pub(super) index_buffer_bytes: u64,
}
impl VkGeometry {
pub(super) fn destroy(&mut self) {
self.vertex_buffer = PooledBuffer::null();
self.index_buffer = PooledBuffer::null();
}
}
pub(super) struct VkDescriptors {
pub(super) global_set_layout: OwnedSetLayout,
pub(super) descriptor_pool: OwnedDescriptorPool,
pub(super) global_sets: Vec<vk::DescriptorSet>,
pub(super) shadow_global_sets: Vec<vk::DescriptorSet>,
}
impl VkDescriptors {
pub(super) fn destroy(&self, _device: &VkDevice) {}
}
pub(super) struct VkInstanced {
pub(super) clusters: Vec<InstancedCluster>,
pub(super) any_lod: bool,
pub(super) lod_buckets: Vec<Vec<InstancedLodBucket>>,
}
impl VkInstanced {
pub(super) fn new(clusters: Vec<InstancedCluster>) -> Self {
Self {
lod_buckets: vec![Vec::new(); clusters.len()],
any_lod: concinnity_core::gfx::lod::any_cluster_has_lod(&clusters),
clusters,
}
}
}
#[derive(Default)]
pub(super) struct VkChunkStream {
pub(super) vtx_alloc: crate::suballoc::range_alloc::RangeAllocator,
pub(super) idx_alloc: crate::suballoc::range_alloc::RangeAllocator,
}
impl VkChunkStream {
pub(super) fn destroy(&self, _device: &VkDevice) {}
}
pub(super) struct VkCull {
pub(super) bindless_pipeline: Option<OwnedPipeline>,
pub(super) bindless_pipeline_layout: Option<OwnedPipelineLayout>,
pub(super) bindless_set_layout: Option<OwnedSetLayout>,
pub(super) bindless_pool_size: usize,
pub(super) bindless_update_after_bind: bool,
pub(super) world_pipelines: Vec<Option<OwnedPipeline>>,
pub(super) bucket_stride: usize,
pub(super) bindless_main_spv: (Vec<u8>, Vec<u8>),
pub(super) bindless_sets: Vec<vk::DescriptorSet>,
pub(super) object_buffers: Vec<PooledBuffer>,
pub(super) cull_pipeline: Option<OwnedPipeline>,
pub(super) cull_pipeline_layout: Option<OwnedPipelineLayout>,
pub(super) cull_set_layout: Option<OwnedSetLayout>,
pub(super) cull_sets: Vec<vk::DescriptorSet>,
pub(super) draw_args_buffers: Vec<PooledBuffer>,
pub(super) indirect_buffers: Vec<PooledBuffer>,
pub(super) cull_status_buffers: Vec<PooledBuffer>,
pub(super) occlusion_two_pass: bool,
pub(super) cull_pipeline_phase2: Option<OwnedPipeline>,
pub(super) cull_sets2: Vec<vk::DescriptorSet>,
pub(super) _two_pass_pool: Option<OwnedDescriptorPool>,
pub(super) indirect_buffers2: Vec<PooledBuffer>,
pub(super) main_render_pass_phase1: Option<OwnedRenderPass>,
pub(super) main_render_pass_phase2: Option<OwnedRenderPass>,
pub(super) hiz: Option<crate::vulkan::hiz::HiZResources>,
pub(super) hiz_valid: bool,
pub(super) hiz_prev_view_proj: [[f32; 4]; 4],
pub(super) shadow_cull_pipeline: Option<OwnedPipeline>,
pub(super) shadow_cull_pipeline_layout: Option<OwnedPipelineLayout>,
pub(super) _shadow_cull_set_layout: Option<OwnedSetLayout>,
pub(super) shadow_cull_sets: Vec<Vec<vk::DescriptorSet>>,
pub(super) shadow_bindless_pipeline: Option<OwnedPipeline>,
pub(super) shadow_bindless_pipeline_layout: Option<OwnedPipelineLayout>,
pub(super) shadow_indirect_buffers: Vec<Vec<PooledBuffer>>,
pub(super) gbuffer_bindless_pipeline: Option<OwnedPipeline>,
pub(super) gbuffer_bindless_pipeline_layout: Option<OwnedPipelineLayout>,
pub(super) _gbuffer_set_layout: Option<OwnedSetLayout>,
pub(super) gbuffer_sets: Vec<vk::DescriptorSet>,
pub(super) prev_model_buffers: Vec<PooledBuffer>,
pub(super) model_history: Option<super::post::gbuffer::ModelHistoryPipeline>,
}
impl VkCull {
pub(super) fn destroy(&mut self, device: &VkDevice) {
if let Some(hiz) = &mut self.hiz {
hiz.destroy(device);
}
self.object_buffers.clear();
self.draw_args_buffers.clear();
self.indirect_buffers.clear();
self.cull_status_buffers.clear();
self.indirect_buffers2.clear();
self.shadow_indirect_buffers.clear();
self.prev_model_buffers.clear();
self.model_history = None;
}
}
pub(super) struct VkFrameSync {
pub(super) image_available: Vec<vk::Semaphore>,
pub(super) render_finished: Vec<vk::Semaphore>,
pub(super) in_flight: Vec<vk::Fence>,
}
impl VkFrameSync {
pub(super) fn destroy(&self, device: &VkDevice) {
unsafe {
for &s in &self.image_available {
device.destroy_semaphore(s, None);
}
for &s in &self.render_finished {
device.destroy_semaphore(s, None);
}
for &f in &self.in_flight {
device.destroy_fence(f, None);
}
}
}
}
pub(super) struct VkCommands {
pub(super) command_pool: vk::CommandPool,
pub(super) command_buffers: Vec<vk::CommandBuffer>,
pub(super) start_command_pools: Vec<vk::CommandPool>,
pub(super) start_command_buffers: Vec<vk::CommandBuffer>,
pub(super) pass_command_pools: Vec<vk::CommandPool>,
pub(super) pass_command_buffers: Vec<vk::CommandBuffer>,
}
impl VkCommands {
pub(super) fn destroy(&self, device: &VkDevice) {
unsafe {
device.destroy_command_pool(self.command_pool, None);
for &pool in self
.start_command_pools
.iter()
.chain(self.pass_command_pools.iter())
{
device.destroy_command_pool(pool, None);
}
}
}
}
pub(super) struct VkUniforms {
pub(super) view_ubo_buffers: Vec<PooledBuffer>,
pub(super) probe_set_ubo_buffers: Vec<PooledBuffer>,
pub(super) light_ubo_buffers: Vec<PooledBuffer>,
pub(super) light_dirty: concinnity_core::render::frame_dirty::FrameDirty,
pub(super) local_light_buffer: PooledBuffer,
pub(super) light_uniforms: render_types::LightUniforms,
}
impl VkUniforms {
pub(super) fn destroy(&mut self) {
self.view_ubo_buffers.clear();
self.probe_set_ubo_buffers.clear();
self.light_ubo_buffers.clear();
self.local_light_buffer = PooledBuffer::null();
}
}
pub(super) struct DecalState {
pub resources: Option<crate::vulkan::decal::DecalResources>,
pub set: decal::DecalSet,
}
pub(super) struct FogState {
pub resources: Option<crate::vulkan::fog::FogResources>,
pub settings: Option<volumetric_fog::FogSettings>,
pub sun_dir: [f32; 3],
pub sun_color: [f32; 3],
}
pub(super) struct AutoExposureState {
pub resources: Option<crate::vulkan::auto_exposure::AutoExposureResources>,
pub settings: Option<auto_exposure::AutoExposureSettings>,
pub state: Option<auto_exposure::AutoExposureState>,
pub bias_ev: f32,
pub last_elapsed: f32,
}
pub(super) struct HotReloadState {
pub enabled: bool,
pub reload_pending: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
}
impl HotReloadState {
pub(super) fn new(enabled: bool) -> Self {
Self {
enabled,
reload_pending: enabled
.then(|| std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false))),
}
}
}
#[derive(Default)]
pub(super) struct ParticleState {
pub resources: Option<crate::vulkan::particle::ParticleResources>,
pub records: Vec<Option<particles::ParticleEmitterRecord>>,
pub emitter_state: Vec<Option<crate::vulkan::particle::ParticleEmitterGpuState>>,
pub free_slots: Vec<usize>,
pub last_elapsed: std::cell::Cell<f32>,
pub frame_index: std::cell::Cell<u32>,
}
pub(super) struct CompositeState {
pub render_pass: OwnedRenderPass,
pub framebuffers: Vec<OwnedFramebuffer>,
pub pipeline: OwnedPipeline,
pub pipeline_layout: OwnedPipelineLayout,
pub _set_layout: OwnedSetLayout,
pub sets: Vec<vk::DescriptorSet>,
}
pub(super) struct BloomState {
pub write_pass: OwnedRenderPass,
pub blend_pass: OwnedRenderPass,
pub pipeline_prefilter: OwnedPipeline,
pub pipeline_downsample: OwnedPipeline,
pub pipeline_upsample: OwnedPipeline,
pub pipeline_layout: OwnedPipelineLayout,
pub set_layout: OwnedSetLayout,
pub descriptor_pool: OwnedDescriptorPool,
pub mips: Vec<Vec<GpuImage>>,
pub mip_extents: Vec<vk::Extent2D>,
pub write_framebuffers: Vec<Vec<OwnedFramebuffer>>,
pub blend_framebuffers: Vec<Vec<OwnedFramebuffer>>,
pub input_sets: Vec<Vec<vk::DescriptorSet>>,
}
pub(super) struct TextState {
pub atlas_textures: Vec<GpuImage>,
pub _set_layout: OwnedSetLayout,
pub atlas_sets: Vec<vk::DescriptorSet>,
pub pipeline: Option<OwnedPipeline>,
pub pipeline_layout: OwnedPipelineLayout,
pub _sampler: OwnedSampler,
pub upload: super::upload_ring::UploadRing,
}
pub(super) struct DrawState {
pub objects: Vec<DrawObject>,
pub graph_cache: Option<(render_graph::FrameGraphInputs, render_graph::CompiledGraph)>,
pub barrier_scratch: Option<super::graph_exec::VkBarrierScratch>,
pub n_objects: usize,
pub n_instances: usize,
pub n_runtime: usize,
pub n_skinned: usize,
}
impl DrawState {
pub(super) fn new(objects: Vec<DrawObject>, n_instances: usize, n_chunk_max: usize) -> Self {
let n_objects = objects.len();
Self {
n_objects,
objects,
graph_cache: None,
barrier_scratch: None,
n_instances,
n_runtime: n_chunk_max + clone_reserve(n_objects),
n_skinned: 0,
}
}
}
pub(super) struct ViewState {
pub clear_color: [f32; 4],
pub scene_fade: f32,
pub mode: concinnity_core::gfx::view_modes::ViewMode,
pub show: concinnity_core::gfx::view_modes::ShowFlags,
pub far: f32,
pub matrix: [[f32; 4]; 4],
pub sky_rot: [[f32; 4]; 3],
}
impl ViewState {
pub(super) fn new(clear_color: [f32; 4]) -> Self {
Self {
clear_color,
scene_fade: 0.0,
mode: Default::default(),
show: Default::default(),
far: 1.0,
matrix: concinnity_core::transform::IDENTITY,
sky_rot: concinnity_core::sky::SkyOrientation::IDENTITY_ROWS,
}
}
}
pub(super) struct ProbeState {
pub book: ProbeBook,
pub gpu: super::probe_set::ProbeSetGpu,
pub rendering: Option<super::probe::RenderingBake>,
pub prefiltering: Option<super::probe::PrefilteringBake>,
pub prefilter: Option<super::probe_prefilter::ProbePrefilterPipelines>,
}
impl ProbeState {
pub(super) fn new(
prefilter: Option<super::probe_prefilter::ProbePrefilterPipelines>,
gpu: super::probe_set::ProbeSetGpu,
) -> Self {
Self {
book: ProbeBook::new(),
gpu,
rendering: None,
prefiltering: None,
prefilter,
}
}
}
pub(super) struct StreamState {
pub pool_rewrites: slot_rewrites::SlotRewriteQueue,
pub frame: u64,
pub retires: Vec<StreamedUploadRetire>,
}
impl StreamState {
pub(super) fn new(frames: usize) -> Self {
Self {
pool_rewrites: slot_rewrites::SlotRewriteQueue::new(frames),
frame: 0,
retires: Vec::new(),
}
}
}
pub(super) struct SwapchainState {
pub loader: ash::khr::swapchain::Device,
pub handle: vk::SwapchainKHR,
pub images: Vec<vk::Image>,
pub image_views: Vec<vk::ImageView>,
pub format: vk::Format,
pub extent: vk::Extent2D,
pub last_present_index: Option<u32>,
}
impl SwapchainState {
pub(super) fn share(&self) -> Self {
Self {
loader: self.loader.clone(),
handle: self.handle,
images: self.images.clone(),
image_views: Vec::new(),
format: self.format,
extent: self.extent,
last_present_index: None,
}
}
}
pub(super) struct VkTargets {
pub(super) render_extent: vk::Extent2D,
pub(super) main_render_pass: OwnedRenderPass,
pub(super) msaa_samples: vk::SampleCountFlags,
pub(super) color_images: Vec<GpuImage>, pub(super) depth_images: Vec<GpuImage>, pub(super) hdr_resolve_images: Vec<GpuImage>, pub(super) framebuffers: Vec<OwnedFramebuffer>,
pub(super) transient_pool: super::transient_pool::TransientImagePool,
}
pub(super) struct VkSceneAssets {
pub(super) textures: Vec<GpuImage>,
pub(super) fallback_textures: Vec<GpuImage>,
pub(super) linear_sampler: OwnedSampler,
pub(super) cube_sampler: OwnedSampler,
pub(super) env_map: EnvironmentMapTextures,
pub(super) prefilter_mip_count: u32,
pub(super) color_lut: GpuImage,
pub(super) ssao_white: GpuImage,
}
pub(super) struct VkRayTracing {
pub(super) accel: Option<crate::vulkan::raytrace::RtAccelData>,
pub(super) dynamic_mode: concinnity_core::render::rt_geom::RtDynamicMode,
pub(super) skinned_geometry: bool,
pub(super) topology_dirty: bool,
pub(super) static_vertex_count: usize,
}
pub(super) struct VkHardware {
pub(super) instance: ash::Instance,
pub(super) device: super::owned::VkDevice,
pub(super) physical_device: vk::PhysicalDevice,
pub(super) surface: vk::SurfaceKHR,
pub(super) surface_loader: ash::khr::surface::Instance,
pub(super) graphics_queue: vk::Queue,
pub(super) present_queue: vk::Queue,
pub(super) graphics_family: u32,
pub(super) alloc: super::allocator::DeviceAllocator,
pub(super) timestamp_query_pool: Option<vk::QueryPool>,
pub(super) timestamp_period_ns: f32,
pub(super) device_local_heaps: Vec<u32>,
pub(super) memory_budget_supported: bool,
pub(super) rt_capable: bool,
pub(super) update_after_bind: bool,
pub(super) hdr_mode: hdr_output::HdrOutputMode,
pub(super) vsync: bool,
pub(super) swapchain_config: backend_init::SwapchainConfig,
pub(super) window: Option<super::window::PlatformWindow>,
pub(super) _entry: ash::Entry,
}
impl VkHardware {
pub(super) fn hand_over(&mut self) -> error::RenderResult<Self> {
Ok(Self {
window: Some(self.window.take().ok_or_else(|| {
error::RenderError::Other("apply_world_reload: window already taken".into())
})?),
instance: self.instance.clone(),
device: self.device.clone(),
physical_device: self.physical_device,
surface: self.surface,
surface_loader: self.surface_loader.clone(),
graphics_queue: self.graphics_queue,
present_queue: self.present_queue,
graphics_family: self.graphics_family,
alloc: self.alloc.clone(),
timestamp_query_pool: self.timestamp_query_pool.take(),
timestamp_period_ns: self.timestamp_period_ns,
device_local_heaps: self.device_local_heaps.clone(),
memory_budget_supported: self.memory_budget_supported,
rt_capable: self.rt_capable,
update_after_bind: self.update_after_bind,
hdr_mode: self.hdr_mode,
vsync: self.vsync,
swapchain_config: self.swapchain_config,
_entry: self._entry.clone(),
})
}
}
pub(crate) struct VkContext {
pub(super) swapchain: SwapchainState,
pub(super) targets: VkTargets,
pub(super) composite: CompositeState,
pub(super) shadow: VkShadow,
pub(super) spot_shadow: VkSpotShadow,
pub(super) area_light: VkAreaLight,
pub(super) scene: VkSceneAssets,
pub(super) cull: VkCull,
pub(super) light_cull: super::light_cull::VkLightCull,
pub(super) text: TextState,
pub(super) bloom: BloomState,
pub(super) post_process: render_types::PostProcessParams,
pub(super) taa: Option<TaaResources>,
pub(super) post: super::post::PostSupport,
pub(super) upscale: Option<Box<dyn VkUpscaleBackend>>,
pub(super) upscale_requested: components::UpscalerBackend,
pub(super) ssao: Option<SsaoResources>,
pub(super) ssr: Option<SsrResources>,
pub(super) reflection_composite: Option<ReflectionCompositeResources>,
pub(super) ssgi: Option<SsgiResources>,
pub(super) gbuffer: Option<GbufferResources>,
pub(super) model_history:
core::cell::RefCell<concinnity_core::render::model_history::ModelHistory>,
pub(super) rt_reflections: Option<RtReflectionsResources>,
pub(super) rt: VkRayTracing,
pub(super) decal: DecalState,
pub(super) lines: crate::vulkan::line::LineState,
pub(super) fog: FogState,
pub(super) raymarch: Option<crate::vulkan::raymarch::RaymarchResources>,
pub(super) transparent: Option<crate::vulkan::transparent::TransparentResources>,
pub(super) planar_reflection: Option<crate::vulkan::planar::PlanarReflectionSet>,
pub(super) particle: ParticleState,
pub(super) auto_exposure: AutoExposureState,
pub(super) hot_reload: HotReloadState,
pub(super) world_shader: Option<concinnity_core::components::ShaderPrograms>,
pub(super) frame_stats: std::cell::Cell<profile::RenderStats>,
pub(super) draw_calls_accum: std::sync::atomic::AtomicU32,
pub(super) descriptors: VkDescriptors,
pub(super) instanced: VkInstanced,
pub(super) geometry: VkGeometry,
pub(super) chunk_stream: VkChunkStream,
pub(super) skinned: VkSkinned,
pub(super) uniforms: VkUniforms,
pub(super) frame_sync: VkFrameSync,
pub(super) current_frame: usize,
pub(super) frames_in_flight: usize,
pub(super) commands: VkCommands,
pub(super) draw: DrawState,
pub(super) view: ViewState,
pub(super) wireframe: super::wireframe::VkWireframe,
pub(super) probe: ProbeState,
pub(super) stream: StreamState,
pub(super) reused_by_successor: bool,
pub(super) world_content_destroyed: bool,
pub(super) hw: VkHardware,
}
unsafe impl Send for VkContext {}
static MAIN_THREAD_ID: std::sync::OnceLock<std::thread::ThreadId> = std::sync::OnceLock::new();
pub(super) fn record_main_thread() {
let _ = MAIN_THREAD_ID.set(std::thread::current().id());
}
#[inline]
#[track_caller]
pub(super) fn debug_assert_main_thread(entry: &str) {
debug_assert!(
MAIN_THREAD_ID
.get()
.is_none_or(|main| *main == std::thread::current().id()),
"{entry} must be called from the main thread: VkContext is main-thread-only \
(see `unsafe impl Send for VkContext`); driving GraphicsSystem off the main \
thread races the GLFW window + Vulkan queue submission",
);
}
impl VkContext {
pub(crate) fn draw_frame(&mut self, params: FrameParams<'_>) -> error::RenderResult<()> {
let FrameParams {
elapsed,
fov_y_radians,
near,
far,
cam_pos,
text_calls,
lines,
world_hidden,
view_mode,
show,
sky_rot,
} = params;
self.view.mode = view_mode;
self.view.show = show;
self.view.far = far;
self.view.sky_rot = sky_rot;
self.apply_pending_rebuilds()?;
if self.frame_is_parked() {
return Ok(());
}
let frame = self.current_frame;
let mut gpu_wait = self.wait_frame_slot(frame)?;
self.service_background_work(elapsed, frame);
let timings = self.read_gpu_timings(frame);
self.begin_frame_stats(&gpu_wait, timings);
let Some(image_index) = self.acquire_frame(frame, &mut gpu_wait)? else {
return Ok(());
};
let device = self.hw.device.clone();
let device = &device;
let cmd = self.commands.command_buffers[frame];
unsafe {
device
.reset_command_buffer(cmd, vk::CommandBufferResetFlags::empty())
.map_err(|e| super::error::map_vk_result(e, "reset cmd buf"))?;
device
.begin_command_buffer(
cmd,
&vk::CommandBufferBeginInfo::default()
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
)
.map_err(|e| super::error::map_vk_result(e, "begin cmd buf"))?;
}
let mut submit_bufs = self.record_frame(
RecordFrameTargets {
cmd,
image_index,
frame_idx: frame,
},
RecordFrameView {
elapsed,
fov_y_radians,
near,
far,
cam_pos,
text_calls,
lines,
},
world_hidden,
)?;
unsafe { device.end_command_buffer(cmd) }
.map_err(|e| super::error::map_vk_result(e, "end cmd buf"))?;
submit_bufs.push(cmd);
self.submit_and_present(frame, image_index, &submit_bufs)
}
pub(crate) fn update_view(&mut self, matrix: [[f32; 4]; 4]) {
self.view.matrix = matrix;
}
pub(crate) fn update_models(&mut self, updates: &[(u32, [[f32; 4]; 4])]) {
for &(index, model) in updates {
if let Some(obj) = self.draw.objects.get_mut(index as usize) {
obj.model = model;
}
}
}
pub(crate) fn update_visibility(&mut self, index: usize, visible: bool) {
if let Some(obj) = self.draw.objects.get_mut(index) {
obj.visible = visible;
}
}
pub(crate) fn retire_draw_object(&mut self, index: usize) {
if let Some(obj) = self.draw.objects.get_mut(index) {
obj.visible = false;
obj.resident = false;
}
}
pub(super) fn shade_mode(&self) -> f32 {
if self.view.mode == concinnity_core::gfx::view_modes::ViewMode::Unlit {
1.0
} else {
0.0
}
}
pub(crate) fn set_fade(&mut self, fade: f32) {
self.view.scene_fade = fade.clamp(0.0, 1.0);
}
#[inline]
pub(super) fn window(&self) -> &super::window::PlatformWindow {
self.hw
.window
.as_ref()
.expect("VkContext window taken by reload_world")
}
#[inline]
pub(super) fn window_mut(&mut self) -> &mut super::window::PlatformWindow {
self.hw
.window
.as_mut()
.expect("VkContext window taken by reload_world")
}
#[inline]
pub(super) fn is_minimized(&self) -> bool {
let (w, h) = self.window().framebuffer_size();
extent_minimized(w, h)
}
pub(super) fn frame_is_parked(&self) -> bool {
if self.is_minimized() {
return true;
}
match self.surface_extent() {
Ok(extent) => !super::swapchain::extent_is_presentable(extent),
Err(_) => false,
}
}
pub(crate) fn window_closed(&mut self) -> bool {
self.window_mut().poll()
}
pub(crate) fn wait_idle(&self) {
let _ = unsafe { self.hw.device.device_wait_idle() };
}
pub(crate) fn render_stats(&self) -> profile::RenderStats {
self.frame_stats.get()
}
pub(super) fn query_vram_bytes(&self) -> u64 {
if !self.hw.memory_budget_supported || self.hw.device_local_heaps.is_empty() {
return 0;
}
let mut budget = vk::PhysicalDeviceMemoryBudgetPropertiesEXT::default();
let mut props2 = vk::PhysicalDeviceMemoryProperties2::default().push_next(&mut budget);
unsafe {
self.hw
.instance
.get_physical_device_memory_properties2(self.hw.physical_device, &mut props2);
}
self.hw
.device_local_heaps
.iter()
.map(|&i| budget.heap_usage[i as usize])
.sum()
}
pub(super) fn inc_draw_calls(&self, n: u32) {
self.draw_calls_accum
.fetch_add(n, std::sync::atomic::Ordering::Relaxed);
}
pub(crate) fn request_cursor_capture(&mut self) {
self.window_mut().request_cursor_capture();
}
pub(crate) fn set_ui_cursor_hidden(&mut self, hidden: bool) {
self.window_mut().set_ui_cursor_hidden(hidden);
}
pub(crate) fn cursor_outside_window(&self) -> bool {
self.window().cursor_outside_window()
}
pub(crate) fn set_menu_mode(&mut self, on: bool) {
self.window_mut().set_menu_mode(on);
}
pub(crate) fn set_camera_capture(&mut self, capture: bool) {
self.window_mut().set_camera_capture(capture);
}
pub(crate) fn set_vsync(&mut self, on: bool) {
if on == self.hw.vsync {
return;
}
self.hw.vsync = on;
if let Err(e) = self.rebuild_swapchain() {
tracing::warn!("set_vsync: rebuild_swapchain failed: {}", e);
}
}
pub(crate) fn set_window_mode(&mut self, mode: components::WindowMode) {
self.window_mut().set_window_mode(mode);
}
pub(crate) fn set_window_size(&mut self, width: u32, height: u32) {
self.window_mut().set_window_size(width, height);
}
pub(crate) fn display_modes(&self) -> Vec<display_mode::DisplayMode> {
self.window().display_modes()
}
pub(crate) fn current_display_mode(&self) -> Option<display_mode::DisplayMode> {
self.window().current_display_mode()
}
pub(crate) fn set_display_mode(&mut self, mode: display_mode::DisplayMode) {
self.window_mut().set_display_mode(mode);
}
pub(crate) fn update_post_process(&mut self, tunables: render_types::PostProcessTunables) {
self.post_process.set_tunables(tunables);
}
pub(crate) fn set_ambient_intensity(&mut self, value: f32) {
if self.uniforms.light_uniforms.ambient_intensity == value {
return;
}
self.uniforms.light_uniforms.ambient_intensity = value;
self.uniforms.light_dirty.mark_all();
}
pub(crate) fn update_directional_lights(&mut self, lights: &[components::DirectionalLight]) {
let (directional, num_directional) = lights::directional_light_data(lights);
let uniforms = &mut self.uniforms.light_uniforms;
if uniforms.directional == directional && uniforms.num_directional == num_directional {
return;
}
uniforms.directional = directional;
uniforms.num_directional = num_directional;
self.shadow.light_dir = lights::sun_direction(&self.uniforms.light_uniforms);
self.fog.sun_dir = self.shadow.light_dir;
self.fog.sun_color = lights::sun_color(&self.uniforms.light_uniforms);
self.uniforms.light_dirty.mark_all();
}
pub(crate) fn set_shadow_update(&mut self, update: components::ShadowUpdate) {
self.shadow.update = update;
}
pub(crate) fn set_shadow_distance(&mut self, distance: u32) {
self.shadow.distance = distance;
}
pub(crate) fn set_shadow_cascades(&mut self, count: u32) {
self.shadow.cascades = count;
}
pub(crate) fn update_quality_params(&mut self, q: backend::QualitySettings) {
if let (Some(live), Some(cur)) = (q.ssao, self.ssao.as_mut().map(|s| &mut s.settings)) {
*cur = live;
}
if let (Some(live), Some(cur)) =
(q.ssr, self.ssr.as_mut().and_then(|s| s.settings.as_mut()))
{
*cur = live;
}
if let (Some(live), Some(cur)) = (q.ssgi, self.ssgi.as_mut().map(|s| &mut s.settings)) {
cur.intensity = live.intensity;
cur.max_distance = live.max_distance;
}
if let (Some(live), Some(cur)) = (q.auto_exposure, self.auto_exposure.settings.as_mut()) {
*cur = live;
}
}
pub(crate) fn shader_reload_pending(
&self,
) -> Option<std::sync::Arc<std::sync::atomic::AtomicBool>> {
self.hot_reload
.reload_pending
.as_ref()
.map(std::sync::Arc::clone)
}
pub(crate) fn take_input(&mut self) -> InputSnapshot {
self.window_mut().take_input()
}
pub(crate) fn set_keymap(&mut self, keymap: &KeyMap) {
self.window_mut().set_keymap(keymap);
}
pub(crate) fn logical_size(&self) -> (f32, f32) {
self.window().logical_size()
}
pub(crate) fn top_content_inset(&self) -> f32 {
self.window().top_content_inset()
}
pub(crate) fn capabilities(&self) -> backend::DeviceCapabilities {
backend::DeviceCapabilities {
ray_tracing: self.hw.rt_capable,
selectable_upscaler: true,
reuses_build_slots: false,
rewrites_draws: false,
}
}
pub(crate) fn gpu_profile(&self) -> backend::GpuProfile {
use concinnity_core::render::backend::{
GpuClassInput, GpuProfile, GpuVendor, apple_family_from_device_name, classify_tier,
};
let props = unsafe {
self.hw
.instance
.get_physical_device_properties(self.hw.physical_device)
};
let vendor = match props.vendor_id {
0x10DE => GpuVendor::Nvidia,
0x1002 => GpuVendor::Amd,
0x8086 => GpuVendor::Intel,
0x106B => GpuVendor::Apple, _ => GpuVendor::Other,
};
let discrete = props.device_type == vk::PhysicalDeviceType::DISCRETE_GPU;
let unified = props.device_type == vk::PhysicalDeviceType::INTEGRATED_GPU;
let mem = unsafe {
self.hw
.instance
.get_physical_device_memory_properties(self.hw.physical_device)
};
let budget: u64 = (0..mem.memory_heap_count as usize)
.filter(|&i| {
mem.memory_heaps[i]
.flags
.contains(vk::MemoryHeapFlags::DEVICE_LOCAL)
})
.map(|i| mem.memory_heaps[i].size)
.sum();
let tier = classify_tier(&GpuClassInput {
vendor,
memory_budget_bytes: budget,
discrete,
apple_family: apple_family_from_device_name(&super::gpu_profile::device_name(&props)),
});
GpuProfile {
vendor,
tier,
memory_budget_bytes: budget,
unified_memory: unified,
discrete,
}
}
}
impl scene_flow::SceneControl for VkContext {
fn update_visibility(&mut self, draw_idx: usize, visible: bool) {
self.update_visibility(draw_idx, visible);
}
fn set_fade(&mut self, fade: f32) {
self.set_fade(fade);
}
}
impl VkContext {
pub(super) fn destroy_world_content(&mut self) {
if self.world_content_destroyed {
return;
}
self.world_content_destroyed = true;
let device = self.hw.device.clone();
let device = &device;
if let Some(rendering) = self.probe.rendering.take() {
rendering.destroy(device, self.commands.command_pool);
}
if let Some(prefiltering) = self.probe.prefiltering.take() {
prefiltering.destroy(device, self.commands.command_pool);
}
self.drain_stream_retires();
self.frame_sync.destroy(device);
self.commands.destroy(device);
self.destroy_swapchain_resources();
self.shadow.destroy(device);
self.spot_shadow.destroy(device);
self.area_light.destroy(device);
self.scene.env_map.irradiance = GpuImage::null();
self.scene.env_map.prefilter = GpuImage::null();
self.wireframe.destroy();
self.text.pipeline = None;
self.cull.destroy(device);
self.light_cull.destroy(device);
self.scene.color_lut = GpuImage::null();
self.post.cache.destroy();
self.taa = None;
if let Some(mut ssao) = self.ssao.take() {
ssao.destroy(device);
}
self.scene.ssao_white = GpuImage::null();
self.targets.transient_pool.destroy(device);
self.ssr = None;
if let Some(mut rc) = self.reflection_composite.take() {
rc.destroy(device);
}
self.ssgi = None;
if let Some(mut gb) = self.gbuffer.take() {
gb.destroy(device);
}
if let Some(mut rt) = self.rt_reflections.take() {
rt.destroy(device);
}
if let Some(mut accel) = self.rt.accel.take() {
accel.destroy(device);
}
if let Some(mut up) = self.upscale.take() {
up.destroy(device);
}
if let Some(mut decals) = self.decal.resources.take() {
decals.destroy(device);
}
if let Some(mut lines) = self.lines.resources.take() {
lines.destroy(device);
}
self.text.upload.destroy();
if let Some(mut fog) = self.fog.resources.take() {
fog.destroy(device);
}
if let Some(mut rm) = self.raymarch.take() {
rm.destroy(device);
}
if let Some(mut planar) = self.planar_reflection.take() {
planar.destroy(device);
}
if let Some(mut transparent) = self.transparent.take() {
transparent.destroy(device);
}
if let Some(mut ae) = self.auto_exposure.resources.take() {
ae.destroy(device);
}
self.destroy_particle_emitter_states(device);
if let Some(mut p) = self.particle.resources.take() {
p.destroy(device);
}
if let Some(pool) = self.hw.timestamp_query_pool.take() {
unsafe { device.destroy_query_pool(pool, None) };
}
self.chunk_stream.destroy(device);
self.skinned.destroy(device);
self.descriptors.destroy(device);
self.geometry.destroy();
self.uniforms.destroy();
self.scene.textures.clear();
self.scene.fallback_textures.clear();
self.text.atlas_textures.clear();
self.probe.gpu.release();
}
}
impl Drop for VkContext {
fn drop(&mut self) {
self.wait_idle();
self.destroy_world_content();
if !self.reused_by_successor {
self.hw.alloc.destroy();
}
if !self.reused_by_successor {
unsafe {
self.hw
.surface_loader
.destroy_surface(self.hw.surface, None)
};
}
}
}
fn extent_minimized(width: i32, height: i32) -> bool {
width <= 0 || height <= 0
}
#[cfg(test)]
mod tests {
use super::{DrawState, extent_minimized};
use concinnity_core::gfx::render_types::clone_reserve;
#[test]
fn draw_state_reserves_the_chunk_window_and_clone_budget() {
let draw = DrawState::new(Vec::new(), 3, 5);
assert_eq!(draw.n_objects, 0);
assert_eq!(draw.n_instances, 3);
assert_eq!(draw.n_runtime, 5 + clone_reserve(0));
assert_eq!(draw.n_skinned, 0);
}
#[test]
fn extent_minimized_gates_on_zero_or_negative_dimensions() {
assert!(!extent_minimized(1280, 720));
assert!(!extent_minimized(1, 1));
assert!(extent_minimized(0, 0));
assert!(extent_minimized(1280, 0));
assert!(extent_minimized(0, 720));
assert!(extent_minimized(-1, 720));
assert!(extent_minimized(1280, -1));
}
}