use ash::vk;
use concinnity_core::components;
use concinnity_core::gfx::auto_exposure;
use concinnity_core::gfx::frustum::Frustum;
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::retire_pool::RetirePool;
use concinnity_core::render::scene_flow;
use concinnity_core::render::scene_state::SceneState;
use concinnity_core::render::shadow_schedule;
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::geometry_upload::{CopySubmit, GeometryDest, GeometryUploads};
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) global_set_layout: OwnedSetLayout,
pub(super) sampler: OwnedSampler,
pub(super) ubos: Vec<PooledBuffer>,
pub(super) uniforms: ShadowUniforms,
pub(super) light_dir: [f32; 3],
pub(super) cadence: backend_init::ShadowCadence,
pub(super) scheduler: shadow_schedule::ShadowCascadeScheduler,
pub(super) render_mask: u32,
}
impl VkShadow {
pub(super) fn enabled(&self) -> bool {
!self.framebuffers.is_empty()
}
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) frusta: Vec<Frustum>,
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 refreshed_slices(&self) -> impl Iterator<Item = u32> {
spot_shadow::refreshed_slices(self.render_mask, self.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) vertex_buffer: PooledBuffer,
pub(super) index_buffer: PooledBuffer,
pub(super) vertex_buffer_bytes: u64,
pub(super) index_buffer_bytes: u64,
pub(super) joint_buffers: Vec<Vec<PooledBuffer>>,
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 {
vertex_buffer: PooledBuffer::null(),
vertex_buffer_bytes: 0,
index_buffer: PooledBuffer::null(),
index_buffer_bytes: 0,
joint_buffers: 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) 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,
}
impl VkInstanced {
pub(super) fn new(clusters: Vec<InstancedCluster>) -> Self {
Self {
any_lod: concinnity_core::gfx::lod::any_cluster_has_lod(&clusters),
clusters,
}
}
}
pub(super) struct VkCullKernels {
pub(super) pipeline: OwnedPipeline,
pub(super) pipeline_phase2: Option<OwnedPipeline>,
pub(super) pipeline_layout: OwnedPipelineLayout,
pub(super) set_layout: OwnedSetLayout,
}
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:
concinnity_core::render::world_pipelines::WorldPipelines<super::pipeline::BucketPipelines>,
pub(super) bucket_stride: usize,
pub(super) bindless_main_spv: super::pipeline::BindlessSpv,
pub(super) prepass_layout: Option<super::post::gbuffer::PrepassLayout>,
pub(super) prepass_pipeline: Option<OwnedPipeline>,
pub(super) bindless_sets: Vec<vk::DescriptorSet>,
pub(super) object_buffers: Vec<PooledBuffer>,
pub(super) material_params: Option<super::material_params::VkMaterialParams>,
pub(super) cull_kernels: Option<VkCullKernels>,
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_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) spot_cull_sets: Vec<Vec<vk::DescriptorSet>>,
pub(super) spot_indirect_buffers: Vec<Vec<PooledBuffer>>,
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.material_params = None;
self.draw_args_buffers.clear();
self.indirect_buffers.clear();
self.cull_status_buffers.clear();
self.indirect_buffers2.clear();
self.shadow_indirect_buffers.clear();
self.spot_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) cluster_reach: concinnity_core::render::cluster_range::ClusterReach,
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 adaptation: Option<auto_exposure::ExposureAdaptation>,
pub last_elapsed: f32,
}
pub(super) struct HotReloadState {
pub enabled: bool,
pub reload_pending: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
pub generation: u64,
}
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))),
generation: 0,
}
}
}
#[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 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 ProbeState {
pub book: ProbeBook,
pub gpu: super::probe_set::ProbeSetGpu,
pub bake: super::probe::VkProbeBake,
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,
bake: super::probe::VkProbeBake::default(),
prefilter,
}
}
}
pub(super) struct StreamState {
pub pool_rewrites: slot_rewrites::SlotRewriteQueue,
pub frame: u64,
pub retires: RetirePool<StreamedUploadRetire>,
pub retire_depth: u64,
}
impl StreamState {
pub(super) fn new(frames: usize) -> Self {
Self {
pool_rewrites: slot_rewrites::SlotRewriteQueue::new(frames),
frame: 0,
retires: RetirePool::new(),
retire_depth: frames as u64 + 1,
}
}
}
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) reactive_mask_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) retired: RetirePool<crate::vulkan::raytrace::RtAccelData>,
pub(super) retire_tick: u64,
pub(super) dynamic_mode: concinnity_core::render::rt_geom::RtDynamicMode,
pub(super) skinned_geometry: bool,
pub(super) update_streak: concinnity_core::render::rt_accel::FailureStreak,
pub(super) skin: Option<crate::vulkan::raytrace::SkinPipeline>,
}
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: Option<VkBloomPass>,
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: crate::upscale_sdk::UpscaleRequest,
pub(super) ssao: Option<SsaoResources>,
pub(super) ssr: Option<SsrResources>,
pub(super) reflection_composite: Option<VkReflectionCompositePass>,
pub(super) ssgi: Option<SsgiResources>,
pub(super) gbuffer: Option<GbufferResources>,
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) sky: crate::vulkan::sky::VkSky,
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) pipeline_gate: crate::vulkan::pipeline_builder::PipelineGate,
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) geometry_uploads: core::cell::RefCell<GeometryUploads>,
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) state: SceneState,
pub(super) graph_cache: Option<(render_graph::FrameGraphInputs, render_graph::CompiledGraph)>,
pub(super) barrier_scratch: Option<super::graph_exec::VkBarrierScratch>,
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,
view_distance,
cam_pos,
text_calls,
lines,
world_hidden,
view_mode,
show,
sky_rot,
history_reset,
} = params;
self.state.view.mode = view_mode;
self.state.view.show = show;
self.state.view.near = near;
self.state.view.view_distance = view_distance;
self.state.view.sky_rot = sky_rot;
self.apply_pending_rebuilds()?;
if history_reset {
self.reset_temporal_history();
}
if self.frame_is_parked() {
return Ok(());
}
let frame = self.current_frame;
let mut gpu_wait = self.wait_frame_slot(frame)?;
self.flush_geometry_uploads()?;
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,
view_distance,
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(super) fn shade_mode(&self) -> f32 {
if self.state.view.mode == concinnity_core::gfx::view_modes::ViewMode::Unlit {
1.0
} else {
0.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(super) fn flush_geometry_uploads(&self) -> error::RenderResult<()> {
self.geometry_uploads.borrow_mut().flush(
&self.hw.device,
CopySubmit {
command_pool: self.commands.command_pool,
queue: self.hw.graphics_queue,
},
GeometryDest {
vertex: self.geometry.vertex_buffer.buffer(),
index: self.geometry.index_buffer.buffer(),
},
)
}
pub(crate) fn wait_idle(&self) {
if let Err(e) = self.flush_geometry_uploads() {
tracing::error!("geometry upload submit failed: {e}");
}
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_cadence(&mut self, cadence: backend_init::ShadowCadence) {
self.shadow.cadence = cadence;
}
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.adaptation.as_mut()) {
cur.settings = 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: DrawIndex, visible: bool) {
self.state.update_visibility(draw_idx, visible);
}
fn set_fade(&mut self, fade: f32) {
self.state.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;
let (rendering, prefiltering) = self.probe.bake.take();
if let Some(rendering) = rendering {
rendering.destroy(device, self.commands.command_pool);
}
if let Some(prefiltering) = prefiltering {
prefiltering.destroy(device, self.commands.command_pool);
}
self.drain_stream_retires();
self.frame_sync.destroy(device);
self.geometry_uploads.get_mut().destroy();
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;
self.bloom = None;
self.ssao = None;
self.scene.ssao_white = GpuImage::null();
self.targets.transient_pool.destroy(device);
self.ssr = None;
self.reflection_composite = None;
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);
}
self.rt.destroy_accels();
self.rt.skin = None;
if let Some(mut up) = self.upscale.take() {
up.destroy();
}
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.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.pipeline_gate.close();
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::extent_minimized;
#[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));
}
}