use std::cell::RefCell;
use std::sync::OnceLock;
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::Graphics::Dxgi::*;
use windows::Win32::System::Threading::{GetCurrentThreadId, WaitForSingleObject};
use windows::core::Interface;
use crate::gfx::backend::FrameParams;
use crate::gfx::render_types::*;
use super::allocator::{DeviceAllocator, PooledBuffer, PooledTexture};
use super::auto_exposure::AutoExposureResources;
use super::com;
use super::decal::*;
use super::fog::*;
use super::particle::{ParticleEmitterGpuState, ParticleResources};
use super::post::gbuffer::GbufferResources;
use super::post::ssao::*;
use super::post::ssr::*;
use super::post::taa::*;
use super::texture::*;
use crate::win32::input::*;
use crate::win32::window::*;
pub(super) const FRAMES: usize = 3; pub(super) const CB_ALIGN: u64 = 256;
pub(super) const MAX_SKINNED_OBJECTS: usize = 64;
pub(super) const MAX_CLONE_DRAWS: usize = 128;
pub(super) fn align256(n: u64) -> u64 {
(n + CB_ALIGN - 1) & !(CB_ALIGN - 1)
}
#[derive(Clone, Debug)]
pub(super) struct InstanceBucketLayout {
pub instance_byte_offset: u64,
pub instance_count: u32,
pub index_offset: usize,
pub index_count: usize,
pub instances: Vec<[[f32; 4]; 4]>,
}
pub(super) fn build_timestamp_resources(
alloc: &DeviceAllocator,
) -> (
Option<ID3D12QueryHeap>,
Option<PooledBuffer>,
*const u64,
u64,
) {
let device = alloc.device();
let frequency = unsafe { alloc.queue().GetTimestampFrequency() }.unwrap_or(0);
if frequency == 0 {
return (None, None, std::ptr::null(), 0);
}
let heap_desc = D3D12_QUERY_HEAP_DESC {
Type: D3D12_QUERY_HEAP_TYPE_TIMESTAMP,
Count: (super::pass_timing::SLOTS_PER_FRAME * FRAMES) as u32,
NodeMask: 0,
};
let mut heap: Option<ID3D12QueryHeap> = None;
if let Err(e) = unsafe { device.CreateQueryHeap(&heap_desc, &mut heap) } {
tracing::warn!("timestamp query heap create failed: {e}");
return (None, None, std::ptr::null(), 0);
}
let readback = match super::texture::create_buffer(
alloc,
super::pass_timing::FRAME_BLOCK_BYTES * FRAMES as u64,
D3D12_HEAP_TYPE_READBACK,
D3D12_RESOURCE_STATE_COPY_DEST,
) {
Ok(r) => r,
Err(e) => {
tracing::warn!("timestamp readback buffer create failed: {e}");
return (None, None, std::ptr::null(), 0);
}
};
let mut ptr = std::ptr::null_mut::<std::ffi::c_void>();
if let Err(e) = unsafe { readback.Map(0, None, Some(&mut ptr)) } {
tracing::warn!("timestamp readback map failed: {e}");
return (None, None, std::ptr::null(), 0);
}
(heap, Some(readback), ptr as *const u64, frequency)
}
pub(super) struct TimestampState {
pub query_heap: Option<ID3D12QueryHeap>,
pub readback: Option<PooledBuffer>,
pub readback_ptr: *const u64,
pub frequency: u64,
}
pub(super) struct BloomState {
pub mips: Vec<ID3D12Resource>,
pub mip_rtvs: Vec<D3D12_CPU_DESCRIPTOR_HANDLE>,
pub mip_srv_gpus: Vec<D3D12_GPU_DESCRIPTOR_HANDLE>,
pub mip_extents: Vec<(u32, u32)>,
pub root_sig: ID3D12RootSignature,
pub pso_prefilter: ID3D12PipelineState,
pub pso_downsample: ID3D12PipelineState,
pub pso_upsample: ID3D12PipelineState,
}
pub(super) struct SkinnedState {
pub pso: Option<ID3D12PipelineState>,
pub root_sig: Option<ID3D12RootSignature>,
pub shadow_pso: Option<ID3D12PipelineState>,
pub shadow_root_sig: Option<ID3D12RootSignature>,
pub vertex_buffer: Option<PooledBuffer>,
pub index_buffer: Option<PooledBuffer>,
pub vertex_buffer_view: D3D12_VERTEX_BUFFER_VIEW,
pub index_buffer_view: D3D12_INDEX_BUFFER_VIEW,
pub draw_objects: Vec<SkinnedDrawObject>,
pub joint_buffers: Vec<Vec<PooledBuffer>>,
pub joint_ptrs: Vec<Vec<*mut u8>>,
pub joint_matrices: Vec<Vec<[[f32; 4]; 4]>>,
pub srv_base_slot: usize,
pub skin_pipeline: Option<super::raytrace::SkinPipeline>,
pub deformed_buffers: Vec<ID3D12Resource>,
pub deformed_vbvs: Vec<D3D12_VERTEX_BUFFER_VIEW>,
pub morph_delta_buffers: Vec<Option<PooledBuffer>>,
pub morph_target_counts: Vec<u32>,
pub morph_weights: Vec<Vec<f32>>,
pub morph_weight_buffers: Vec<Vec<PooledBuffer>>,
pub morph_weight_ptrs: Vec<Vec<*mut u8>>,
pub deformed_primed: std::sync::atomic::AtomicBool,
}
pub(super) struct CullState {
pub main_bindless_root_sig: Option<ID3D12RootSignature>,
pub main_bindless_pso: Option<ID3D12PipelineState>,
pub world_pipelines: Vec<Option<ID3D12PipelineState>>,
pub bucket_stride: usize,
pub object_buffer_resources: Vec<PooledBuffer>,
pub object_buffer_ptrs: Vec<*mut u8>,
pub bindless_pool_gpu: Vec<D3D12_GPU_DESCRIPTOR_HANDLE>,
pub cull_root_sig: Option<ID3D12RootSignature>,
pub cull_pso: Option<ID3D12PipelineState>,
pub cull_pso_phase2: Option<ID3D12PipelineState>,
pub cull_command_signature: Option<ID3D12CommandSignature>,
pub draw_args_buffer_resources: Vec<PooledBuffer>,
pub draw_args_buffer_ptrs: Vec<*mut u8>,
pub indirect_cmd_buffers: Vec<ID3D12Resource>,
pub cull_status_buffers: Vec<ID3D12Resource>,
pub indirect_cmd_buffers_2: Vec<ID3D12Resource>,
pub shadow_bindless_root_sig: Option<ID3D12RootSignature>,
pub shadow_bindless_pso: Option<ID3D12PipelineState>,
pub shadow_bindless_cmd_sig: Option<ID3D12CommandSignature>,
pub cull_pso_shadow: Option<ID3D12PipelineState>,
pub shadow_indirect_buffers: Vec<ID3D12Resource>,
pub shadow_cull_status_buffers: Vec<ID3D12Resource>,
pub gbuffer_bindless_root_sig: Option<ID3D12RootSignature>,
pub gbuffer_bindless_pso: Option<ID3D12PipelineState>,
pub gbuffer_bindless_cmd_sig: Option<ID3D12CommandSignature>,
pub prev_model_buffers: Vec<PooledBuffer>,
pub prev_model_buffer_ptrs: Vec<*mut u8>,
pub occlusion_two_pass: bool,
pub hiz: Option<super::hiz::HiZResources>,
pub prev_view_proj: std::cell::Cell<[[f32; 4]; 4]>,
pub hiz_valid: std::cell::Cell<bool>,
}
pub(super) struct HotReloadState {
pub enabled: bool,
pub reload_pending: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
#[expect(
dead_code,
reason = "held so the watcher thread stays alive; dropping it stops the watcher"
)]
pub watcher: Option<crate::directx::hot_reload::WatcherHandle>,
}
pub(super) struct MeshStreamState {
pub vtx_alloc: crate::suballoc::range_alloc::RangeAllocator,
pub idx_alloc: crate::suballoc::range_alloc::RangeAllocator,
}
pub(super) struct ChunkStreamState {
pub vtx_alloc: crate::suballoc::range_alloc::RangeAllocator,
pub idx_alloc: crate::suballoc::range_alloc::RangeAllocator,
pub srv_base_slot: usize,
}
pub(super) struct HdrState {
pub color: ID3D12Resource,
pub color_rtv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub resolve: Option<ID3D12Resource>,
pub resolve_rtv: Option<D3D12_CPU_DESCRIPTOR_HANDLE>,
pub srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
pub msaa_samples: u32,
}
pub(super) struct SsaoState {
pub resources: Option<SsaoResources>,
#[expect(
dead_code,
reason = "held to keep the fallback texture resident; the pass binds white_srv_gpu"
)]
pub white: PooledTexture,
pub white_srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
}
pub(super) struct ParticleState {
pub resources: Option<ParticleResources>,
pub records: Vec<Option<crate::gfx::particles::ParticleEmitterRecord>>,
pub emitter_state: Vec<Option<ParticleEmitterGpuState>>,
pub free_slots: Vec<usize>,
pub srv_base_slot: usize,
pub last_elapsed: std::cell::Cell<f32>,
pub frame_index: std::cell::Cell<u32>,
}
pub(super) struct DecalState {
pub state: Option<DecalResources>,
pub records: Vec<Option<crate::gfx::decal::DecalRecord>>,
pub free_slots: Vec<usize>,
}
pub(super) struct CloneState {
pub srv_base_slot: usize,
pub count: usize,
pub slot_by_draw_idx: std::collections::HashMap<usize, usize>,
pub free_offsets: Vec<usize>,
}
pub(super) struct UpscaleState {
pub backend: Option<Box<dyn super::post::upscale::UpscaleBackend>>,
pub requested: crate::components::UpscalerBackend,
pub jitter: std::cell::Cell<[f32; 2]>,
pub prev_elapsed: std::cell::Cell<f32>,
}
pub(super) struct AutoExposureState {
pub resources: Option<AutoExposureResources>,
pub settings: Option<crate::gfx::auto_exposure::AutoExposureSettings>,
pub state: Option<crate::gfx::auto_exposure::AutoExposureState>,
pub bias_ev: f32,
pub last_elapsed: f32,
}
pub(super) struct ShadowState {
pub resource: Option<GpuResource<ID3D12Resource>>,
pub dsvs: Vec<D3D12_CPU_DESCRIPTOR_HANDLE>,
pub map_size: u32,
pub srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
pub light_dir: [f32; 3],
pub update: crate::components::ShadowUpdate,
pub distance: u32,
pub cascades: u32,
pub scheduler: crate::gfx::shadow_schedule::ShadowCascadeScheduler,
pub render_mask: u32,
pub uniforms: ShadowUniforms,
}
pub(super) struct SpotShadowState {
pub resource: Option<GpuResource<ID3D12Resource>>,
pub dsvs: Vec<D3D12_CPU_DESCRIPTOR_HANDLE>,
pub srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
pub buffer: PooledBuffer,
pub ubo: PooledBuffer,
pub ubo_stride: u64,
pub slice_size: u32,
pub scheduler: crate::gfx::spot_shadow::SpotShadowScheduler,
pub render_mask: u32,
}
impl SpotShadowState {
pub(crate) fn count(&self) -> u32 {
self.dsvs.len() as u32
}
pub(crate) fn advance(&mut self, every_frame: bool) {
let count = self.dsvs.len();
self.render_mask = self.scheduler.next_mask(every_frame, count);
}
pub(crate) fn slice_ubo_gva(&self, slice: u32) -> u64 {
debug_assert!(slice < self.count());
let base = com::gpu_va(&self.ubo);
base + slice as u64 * self.ubo_stride
}
}
pub(super) struct AreaLightState {
pub buffer: PooledBuffer,
#[expect(
dead_code,
reason = "owns the resource the LTC table descriptors point at"
)]
pub ltc_matrix: GpuResource,
#[expect(
dead_code,
reason = "owns the resource the LTC table descriptors point at"
)]
pub ltc_magnitude: GpuResource,
pub ltc_table_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
}
pub(super) struct FogState {
pub resources: Option<FogResources>,
pub settings: Option<crate::gfx::volumetric_fog::FogSettings>,
pub sun_dir: [f32; 3],
pub sun_color: [f32; 3],
}
pub(super) struct DxUniforms {
pub view_ubo_resources: Vec<PooledBuffer>,
pub view_ubo_ptrs: Vec<*mut u8>,
pub light_ubo: PooledBuffer,
pub local_light_buffer: PooledBuffer,
pub light_uniforms: crate::gfx::render_types::LightUniforms,
pub shadow_ubo_resources: Vec<PooledBuffer>,
pub shadow_ubo_ptrs: Vec<*mut u8>,
}
impl DxUniforms {
pub(super) fn unmap(&self) {
for res in self
.view_ubo_resources
.iter()
.chain(self.shadow_ubo_resources.iter())
{
unsafe { res.Unmap(0, None) };
}
}
}
pub(super) struct DxCommands {
pub command_allocators: Vec<ID3D12CommandAllocator>,
pub command_lists: Vec<ID3D12GraphicsCommandList>,
pub pass_allocators: Vec<ID3D12CommandAllocator>,
pub pass_cmd_lists: Vec<ID3D12GraphicsCommandList>,
pub end_command_allocators: Vec<ID3D12CommandAllocator>,
pub end_command_lists: Vec<ID3D12GraphicsCommandList>,
}
pub(super) struct DxFrameSync {
pub fence: ID3D12Fence,
pub fence_values: Vec<u64>,
pub next_fence_value: std::cell::Cell<u64>,
pub fence_event: windows::Win32::Foundation::HANDLE,
}
pub(super) struct DxGeometry {
pub vertex_buffer: PooledBuffer,
pub index_buffer: PooledBuffer,
pub vertex_buffer_view: D3D12_VERTEX_BUFFER_VIEW,
pub index_buffer_view: D3D12_INDEX_BUFFER_VIEW,
}
pub(super) struct DxInstanced {
pub root_sig: Option<ID3D12RootSignature>,
pub pso: Option<ID3D12PipelineState>,
pub clusters: Vec<InstancedCluster>,
pub upload_buffers: Vec<Vec<PooledBuffer>>,
pub upload_ptrs: Vec<Vec<*mut u8>>,
pub bucket_layouts: std::sync::RwLock<Vec<Vec<InstanceBucketLayout>>>,
}
pub(super) struct DxDescriptors {
pub srv_heap: ID3D12DescriptorHeap,
pub srv_descriptor_size: usize,
pub flat_pool_base_slot: usize,
pub flat_pool_len: usize,
pub probe_cube_base_slot: usize,
pub sampler_heap: ID3D12DescriptorHeap,
pub shadow_sampler_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
pub linear_sampler_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
pub text_sampler_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
pub textures: Vec<PooledTexture>,
pub fallback_textures: Vec<PooledTexture>,
#[expect(
dead_code,
reason = "held to keep the text atlases resident; the pass binds the SRV handles"
)]
pub text_atlas_textures: Vec<GpuResource>,
pub text_atlas_srv_gpus: Vec<D3D12_GPU_DESCRIPTOR_HANDLE>,
}
pub(super) struct DrawState {
pub objects: Vec<DrawObject>,
pub bvh: crate::gfx::bvh::Bvh,
pub always: Vec<u32>,
pub always_member: Vec<bool>,
pub visible_scratch: RefCell<Vec<u32>>,
pub graph_cache: RefCell<
Option<(
crate::gfx::render_graph::FrameGraphInputs,
crate::gfx::render_graph::CompiledGraph,
)>,
>,
pub n_objects: usize,
pub n_instances: usize,
pub n_chunk: usize,
pub n_skinned: usize,
pub n_clusters: usize,
}
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(super) struct ProbeState {
pub placements: Vec<crate::gfx::reflection_probe::ProbePlacement>,
pub bake_queue: crate::gfx::reflection_probe::ProbeBakeQueue,
pub set: concinnity_render::uniforms::ProbeSet,
pub rendering: Option<super::probe::RenderingBake>,
pub converting: Option<super::probe::ConvertingBake>,
pub maps: Vec<super::probe::ProbeCube>,
pub set_cbvs: Vec<PooledBuffer>,
pub set_cbv_ptrs: Vec<*mut u8>,
pub set_empty_cbv: PooledBuffer,
}
pub(super) struct StreamState {
pub pool_rewrites: crate::gfx::slot_rewrites::SlotRewriteQueue,
pub frame: u64,
pub retires: Vec<super::texture::StreamedUploadRetire>,
}
pub(super) struct SwapchainState {
pub handle: IDXGISwapChain3,
pub back_buffers: Vec<ID3D12Resource>,
pub rtv_heap: ID3D12DescriptorHeap,
pub rtv_descriptor_size: usize,
pub format: windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT,
pub present_sync_interval: u32,
pub allow_tearing: bool,
pub last_present_index: Option<usize>,
}
pub(super) struct Extents {
pub render_width: u32,
pub render_height: u32,
pub output_width: u32,
pub output_height: u32,
}
pub(super) struct DepthState {
pub dsv: D3D12_CPU_DESCRIPTOR_HANDLE,
pub resource: ID3D12Resource,
#[expect(
dead_code,
reason = "held to keep the DSV heap resident; the pass binds through dsv"
)]
pub heap: ID3D12DescriptorHeap,
}
pub(super) struct TextState {
pub root_sig: ID3D12RootSignature,
pub pso: Option<ID3D12PipelineState>,
pub upload: super::upload_ring::UploadRing,
}
pub(super) struct CompositeState {
pub root_sig: ID3D12RootSignature,
pub pso: ID3D12PipelineState,
}
pub(super) struct Diagnostics {
pub frame_stats: std::cell::Cell<crate::gfx::profile::RenderStats>,
pub draw_calls_accum: std::sync::atomic::AtomicU32,
pub info_queue: Option<ID3D12InfoQueue>,
}
pub(crate) struct DxContext {
pub(super) win_state: Option<Box<WindowState>>,
pub(super) fullscreen_display: crate::win32::display_mode::FullscreenDisplayMode,
pub(super) device: ID3D12Device,
pub(super) command_queue: ID3D12CommandQueue,
pub(super) alloc: DeviceAllocator,
pub(super) swapchain_config: crate::gfx::backend_init::SwapchainConfig,
pub(super) hdr_mode: crate::gfx::hdr_output::HdrOutputMode,
pub(super) swapchain: SwapchainState,
pub(super) hdr: HdrState,
pub(super) extent: Extents,
pub(super) upscale: UpscaleState,
pub(super) depth: DepthState,
pub(super) shadow: ShadowState,
pub(super) spot_shadow: SpotShadowState,
pub(super) area_light: AreaLightState,
pub(super) env_map: EnvironmentMapTextures,
pub(super) color_lut: GpuResource,
pub(super) descriptors: DxDescriptors,
pub(super) draw: DrawState,
pub(super) geometry: DxGeometry,
pub(super) mesh_stream: MeshStreamState,
pub(super) chunk_stream: ChunkStreamState,
pub(super) skinned: SkinnedState,
pub(super) uniforms: DxUniforms,
pub(super) main_root_sig: ID3D12RootSignature,
pub(super) main_pso: ID3D12PipelineState,
pub(super) cull: CullState,
pub(super) light_cull: super::light_cull::LightCullState,
pub(super) shadow_root_sig: Option<ID3D12RootSignature>,
pub(super) shadow_pso: Option<ID3D12PipelineState>,
pub(super) text: TextState,
pub(super) composite: CompositeState,
pub(super) bloom: BloomState,
pub(super) post_process: crate::gfx::render_types::PostProcessParams,
pub(super) gbuffer: Option<GbufferResources>,
pub(super) taa: Option<TaaResources>,
pub(super) ssao: SsaoState,
pub(super) transient_pool: super::transient_pool::TransientResourcePool,
pub(super) ssr: Option<SsrResources>,
pub(super) ssgi: Option<super::post::ssgi::SsgiResources>,
pub(super) reflection_composite:
Option<super::post::reflection_composite::ReflectionCompositeResources>,
pub(super) rt_reflections: Option<super::post::rt_reflections::RtReflectionsResources>,
pub(super) rt_accel: Option<super::raytrace::RtAccelData>,
pub(super) rt_dynamic_mode: super::raytrace::RtDynamicMode,
pub(super) rt_skinned_geometry: bool,
pub(super) rt_topology_dirty: bool,
pub(super) decal: DecalState,
pub(super) lines: super::line::LineState,
pub(super) main_depth_srv_gpu: D3D12_GPU_DESCRIPTOR_HANDLE,
pub(super) raymarch: Option<super::raymarch::RaymarchResources>,
pub(super) transparent: Option<super::transparent::TransparentResources>,
pub(super) planar_reflection: Option<super::planar::PlanarReflectionSet>,
pub(super) fog: FogState,
pub(super) particle: ParticleState,
pub(super) commands: DxCommands,
pub(super) diagnostics: Diagnostics,
pub(super) frame_sync: DxFrameSync,
pub(super) current_frame: usize,
pub(super) stream: StreamState,
pub(super) instanced: DxInstanced,
pub(super) view: ViewState,
pub(super) wireframe: super::wireframe::DxWireframe,
pub(super) bindless_main_shaders: super::init::pipelines::BindlessMainShaders,
pub(super) adapter: Option<IDXGIAdapter3>,
pub(super) auto_exposure: AutoExposureState,
pub(super) max_edr: Option<f32>,
pub(super) clone: CloneState,
pub(super) timestamps: TimestampState,
pub(super) hdr_encoding: Option<crate::gfx::hdr_output::HdrEncoding>,
pub(super) hot_reload: HotReloadState,
pub(super) quality_slots: super::quality::QualitySlotHandles,
pub(super) rt_capable: bool,
pub(super) rt_static_vertex_count: usize,
pub(super) probe: ProbeState,
}
unsafe impl Send for DxContext {}
static MAIN_THREAD_ID: OnceLock<u32> = OnceLock::new();
pub(super) fn record_main_thread() {
let _ = MAIN_THREAD_ID.set(unsafe { GetCurrentThreadId() });
}
#[inline]
#[track_caller]
pub(super) fn debug_assert_main_thread(entry: &str) {
debug_assert!(
MAIN_THREAD_ID
.get()
.is_none_or(|&main| unsafe { GetCurrentThreadId() } == main),
"{entry} must be called from the main thread: DxContext is main-thread-only \
(see `unsafe impl Send for DxContext`); driving GraphicsSystem off the main \
thread races the Win32 window + D3D12 command submission",
);
}
impl DxContext {
pub(crate) fn draw_frame(
&mut self,
params: FrameParams<'_>,
) -> crate::gfx::error::RenderResult<()> {
let FrameParams {
elapsed,
fov_y_radians,
near,
far,
cam_pos,
text_calls,
lines,
world_hidden,
view_mode,
show,
} = params;
self.view.mode = view_mode;
self.view.show = show;
self.view.far = far;
self.ensure_wireframe_pipelines();
if self.shader_reload_requested() {
self.clear_shader_reload_flag();
self.wait_idle();
match self.reload_shaders() {
Ok(()) => tracing::info!("hot-reload: shader pipelines rebuilt"),
Err(e) => tracing::error!("hot-reload: shader rebuild failed: {}", e),
}
}
if let Err(e) = self.maybe_handle_resize() {
tracing::error!("D3D12 resize failed: {e}");
}
let frame = self.current_frame;
let completed = unsafe { self.frame_sync.fence.GetCompletedValue() };
if self.frame_sync.fence_values[frame] > completed {
unsafe {
self.frame_sync.fence.SetEventOnCompletion(
self.frame_sync.fence_values[frame],
self.frame_sync.fence_event,
)
}
.map_err(|e| super::error::map_hresult(e.code(), "SetEventOnCompletion"))?;
unsafe { WaitForSingleObject(self.frame_sync.fence_event, u32::MAX) };
}
self.apply_streamed_texture_rewrites(frame);
self.alloc.begin_frame();
if self.stream.frame.is_multiple_of(1024) && tracing::enabled!(tracing::Level::DEBUG) {
tracing::debug!("device allocator: {}", self.alloc.stats());
}
if let Err(e) = self.bake_pending_probes(elapsed, near, far) {
tracing::warn!("reflection probe bake step failed: {e}");
}
self.update_auto_exposure(elapsed, frame);
let instanced_total: usize = self
.instanced
.clusters
.iter()
.map(|c| c.instances.len())
.sum();
let objects =
(self.draw.objects.len() + instanced_total + self.skinned.draw_objects.len()) as u32;
let skinned_visible = self
.skinned
.draw_objects
.iter()
.filter(|o| o.visible)
.count() as u32;
let skinned_pool_free = 0u32;
let vram_bytes = self
.adapter
.as_ref()
.and_then(|a| {
let mut info =
windows::Win32::Graphics::Dxgi::DXGI_QUERY_VIDEO_MEMORY_INFO::default();
unsafe {
a.QueryVideoMemoryInfo(
0,
windows::Win32::Graphics::Dxgi::DXGI_MEMORY_SEGMENT_GROUP_LOCAL,
&mut info,
)
}
.ok()
.map(|_| info.CurrentUsage)
})
.unwrap_or(0);
let timestamps_live =
!self.timestamps.readback_ptr.is_null() && self.timestamps.frequency > 0;
let ticks_to_micros = |ticks: u64| -> u32 {
(ticks.saturating_mul(1_000_000) / self.timestamps.frequency).min(u32::MAX as u64)
as u32
};
let block_base = if timestamps_live {
unsafe {
self.timestamps
.readback_ptr
.add(frame * super::pass_timing::SLOTS_PER_FRAME)
}
} else {
std::ptr::null()
};
let gpu_frame_us = if timestamps_live {
unsafe {
let ts_start = block_base.read();
let ts_end = block_base.add(1).read();
if ts_end > ts_start {
ticks_to_micros(ts_end - ts_start)
} else {
0
}
}
} else {
0
};
let mut pass_times_us = [("", 0u32); crate::gfx::profile::MAX_PASS_TIMINGS];
if timestamps_live {
for (i, name) in crate::gfx::render_graph::PASS_NAMES.iter().enumerate() {
if i >= crate::gfx::profile::MAX_PASS_TIMINGS {
break;
}
let off = 2 + 2 * i;
let ts_start = unsafe { block_base.add(off).read() };
let ts_end = unsafe { block_base.add(off + 1).read() };
let micros = if ts_end > ts_start {
ticks_to_micros(ts_end - ts_start)
} else {
0
};
pass_times_us[i] = (*name, micros);
}
}
self.diagnostics
.draw_calls_accum
.store(0, std::sync::atomic::Ordering::Relaxed);
self.diagnostics
.frame_stats
.set(crate::gfx::profile::RenderStats {
draw_calls: 0,
objects,
skinned_visible,
skinned_pool_free,
gpu_frame_us,
vram_bytes,
transient_pool_bytes: self.transient_pool.allocated_bytes(),
pass_times_us,
auto_exposure_ev: self.auto_exposure.state.as_ref().map(|s| s.current_ev),
max_edr: self.max_edr,
});
self.flush_validation();
unsafe { self.commands.command_allocators[frame].Reset() }
.map_err(|e| format!("start allocator reset: {e}"))?;
let start_cmd = self.commands.command_lists[frame].clone();
unsafe { start_cmd.Reset(&self.commands.command_allocators[frame], None) }
.map_err(|e| format!("start cmd reset: {e}"))?;
if let Some(heap) = self.timestamps.query_heap.as_ref() {
let (start_slot, _) = super::pass_timing::whole_frame_pair(frame);
let block_base = (frame * super::pass_timing::SLOTS_PER_FRAME) as u32;
unsafe {
start_cmd.EndQuery(heap, D3D12_QUERY_TYPE_TIMESTAMP, start_slot);
let pass_count = super::pass_timing::SLOTS_PER_FRAME / 2 - 1;
for pass_idx in 0..pass_count as u32 {
let pair_start = block_base + 2 + 2 * pass_idx;
let pair_end = pair_start + 1;
start_cmd.EndQuery(heap, D3D12_QUERY_TYPE_TIMESTAMP, pair_end);
start_cmd.EndQuery(heap, D3D12_QUERY_TYPE_TIMESTAMP, pair_start);
}
}
}
self.rt_dynamic_update(&start_cmd, frame);
unsafe { start_cmd.Close() }.map_err(|e| format!("start cmd close: {e}"))?;
self.ensure_line_pipeline(!lines.is_empty());
unsafe { self.commands.end_command_allocators[frame].Reset() }
.map_err(|e| format!("end allocator reset: {e}"))?;
let end_cmd = &self.commands.end_command_lists[frame];
unsafe { end_cmd.Reset(&self.commands.end_command_allocators[frame], None) }
.map_err(|e| format!("end cmd reset: {e}"))?;
let back_idx = unsafe { self.swapchain.handle.GetCurrentBackBufferIndex() } as usize;
let back_buffer = self.swapchain.back_buffers[back_idx].clone();
let rtv_base = unsafe { self.swapchain.rtv_heap.GetCPUDescriptorHandleForHeapStart() };
let back_buffer_rtv = D3D12_CPU_DESCRIPTOR_HANDLE {
ptr: rtv_base.ptr + back_idx * self.swapchain.rtv_descriptor_size,
};
if !self.shadow.dsvs.is_empty() {
let aspect =
self.extent.render_width.max(1) as f32 / self.extent.render_height.max(1) as f32;
let fresh =
crate::gfx::csm::compute_shadow_uniforms(crate::gfx::csm::ShadowUniformInputs {
view: self.view.matrix,
cam_pos,
fov_y_rad: fov_y_radians,
aspect,
near,
shadow_distance: (self.shadow.distance as f32).min(far),
light_dir_to_source: self.shadow.light_dir,
shadow_map_size: self.shadow.map_size,
active_cascades: self.shadow.cascades,
});
let update = self.shadow.update;
let mask = self
.shadow
.scheduler
.next_mask(update, self.shadow.cascades);
self.shadow.render_mask = mask;
self.shadow.uniforms.cascade_splits = fresh.cascade_splits;
self.shadow.uniforms.active_cascades = fresh.active_cascades;
for i in 0..crate::gfx::render_types::NUM_SHADOW_CASCADES {
if mask & (1u32 << i) != 0 {
self.shadow.uniforms.light_vps[i] = fresh.light_vps[i];
}
}
}
self.spot_shadow.advance(matches!(
self.shadow.update,
crate::components::ShadowUpdate::EveryFrame
));
let pass_cmd_lists = self.record_frame(
crate::directx::draw::RecordFrameTargets {
cmd: end_cmd,
back_buffer: &back_buffer,
back_buffer_rtv,
frame_idx: frame,
},
crate::directx::draw::RecordFrameView {
elapsed,
fov_y_radians,
near,
far,
cam_pos,
text_calls,
lines,
},
crate::directx::draw::RecordFrameResolution {
width: self.extent.render_width.max(1),
height: self.extent.render_height.max(1),
output_width: self.extent.output_width.max(1),
output_height: self.extent.output_height.max(1),
},
world_hidden,
)?;
let mut s = self.diagnostics.frame_stats.get();
s.draw_calls = self
.diagnostics
.draw_calls_accum
.load(std::sync::atomic::Ordering::Relaxed);
self.diagnostics.frame_stats.set(s);
if let (Some(heap), Some(readback)) = (
self.timestamps.query_heap.as_ref(),
self.timestamps.readback.as_ref(),
) {
let (_, end_slot) = super::pass_timing::whole_frame_pair(frame);
unsafe {
end_cmd.EndQuery(heap, D3D12_QUERY_TYPE_TIMESTAMP, end_slot);
end_cmd.ResolveQueryData(
heap,
D3D12_QUERY_TYPE_TIMESTAMP,
super::pass_timing::frame_block_base(frame),
super::pass_timing::SLOTS_PER_FRAME as u32,
&**readback,
super::pass_timing::frame_readback_byte_offset(frame),
);
}
}
unsafe { end_cmd.Close() }
.map_err(|e| super::error::map_hresult(e.code(), "end cmd close"))?;
let mut submission: Vec<Option<ID3D12CommandList>> =
Vec::with_capacity(2 + pass_cmd_lists.len());
let start_handle: ID3D12CommandList = start_cmd
.cast()
.map_err(|e| format!("start cmd cast: {e}"))?;
submission.push(Some(start_handle));
for cl in &pass_cmd_lists {
let h: ID3D12CommandList = cl.cast().map_err(|e| format!("per-pass cmd cast: {e}"))?;
submission.push(Some(h));
}
let end_handle: ID3D12CommandList =
end_cmd.cast().map_err(|e| format!("end cmd cast: {e}"))?;
submission.push(Some(end_handle));
unsafe { self.command_queue.ExecuteCommandLists(&submission) };
let present_flags =
if self.swapchain.present_sync_interval == 0 && self.swapchain.allow_tearing {
DXGI_PRESENT_ALLOW_TEARING
} else {
DXGI_PRESENT(0)
};
let present_result = unsafe {
self.swapchain
.handle
.Present(self.swapchain.present_sync_interval, present_flags)
};
if let Err(e) = present_result.ok() {
self.flush_validation();
let reason = unsafe { self.device.GetDeviceRemovedReason() };
return Err(super::error::classify_present_failure(
e.code(),
reason
.err()
.map(|r| r.code())
.unwrap_or(windows::core::HRESULT(0)),
));
}
self.swapchain.last_present_index = Some(back_idx);
let next_val = self.frame_sync.next_fence_value.get();
self.frame_sync.next_fence_value.set(next_val + 1);
self.frame_sync.fence_values[frame] = next_val;
unsafe { self.command_queue.Signal(&self.frame_sync.fence, next_val) }
.map_err(|e| super::error::map_hresult(e.code(), "Signal"))?;
self.current_frame = (self.current_frame + 1) % FRAMES;
Ok(())
}
fn flush_validation(&self) {
if let Some(ref iq) = self.diagnostics.info_queue {
drain_info_queue(iq);
}
}
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;
if let Some(offset) = self.clone.slot_by_draw_idx.remove(&index) {
self.clone.free_offsets.push(offset);
}
}
}
pub(super) fn ensure_always_draw(&mut self, slot: usize) {
if !self.draw.always_member[slot] {
self.draw.always.push(slot as u32);
self.draw.always_member[slot] = true;
}
}
pub(crate) fn set_fade(&mut self, fade: f32) {
self.view.scene_fade = fade.clamp(0.0, 1.0);
}
pub(crate) fn render_stats(&self) -> crate::gfx::profile::RenderStats {
self.diagnostics.frame_stats.get()
}
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(super) fn raymarch_enabled(&self) -> bool {
self.raymarch
.as_ref()
.map(|r| r.any_visible())
.unwrap_or(false)
}
pub(super) fn rt_reflections_active(&self) -> bool {
self.rt_reflections.is_some() && self.rt_accel.is_some()
}
pub(super) fn reflection_resolve_active(&self) -> bool {
self.rt_reflections_active() || self.ssr.as_ref().and_then(|s| s.resolve.as_ref()).is_some()
}
pub(in crate::directx) fn hdr_scene_target(&self) -> &ID3D12Resource {
self.hdr.resolve.as_ref().unwrap_or(&self.hdr.color)
}
pub(in crate::directx) fn hdr_scene_rtv(&self) -> D3D12_CPU_DESCRIPTOR_HANDLE {
match self.hdr.resolve_rtv {
Some(rtv) => rtv,
None => self.hdr.color_rtv,
}
}
pub(super) fn shade_mode(&self) -> f32 {
if self.view.mode == concinnity_core::gfx::view_modes::ViewMode::Unlit {
1.0
} else {
0.0
}
}
pub(super) fn rt_transparent_active(&self) -> bool {
self.rt_reflections_active()
&& self
.transparent
.as_ref()
.is_some_and(|t| t.rt_pipelines_ready())
}
pub(super) fn planar_pass_needed(&self) -> bool {
crate::gfx::planar_reflection::planar_pass_needed(
self.planar_reflection.is_some(),
self.transparent
.as_ref()
.is_some_and(|t| t.water_planar_slot_live()),
self.rt_transparent_active(),
)
}
pub(super) fn transparent_enabled(&self) -> bool {
self.transparent.as_ref().is_some_and(|t| t.any_visible()) || self.mesh_glass_visible()
}
pub(super) fn seethrough_meshes_enabled(&self) -> bool {
self.transparent
.as_ref()
.is_some_and(|t| t.mesh_pipelines_ready())
}
pub(super) fn mesh_glass_active(&self) -> bool {
self.seethrough_meshes_enabled() && self.rt_transparent_active()
}
fn mesh_glass_visible(&self) -> bool {
self.mesh_glass_active()
&& self.transparent.as_ref().is_some_and(|t| {
t.seethrough_mesh_indices().iter().any(|&i| {
self.draw
.objects
.get(i)
.is_some_and(|o| o.visible && o.resident)
})
})
}
pub(super) fn inc_draw_calls(&self, n: u32) {
self.diagnostics
.draw_calls_accum
.fetch_add(n, std::sync::atomic::Ordering::Relaxed);
}
}
impl DxContext {
#[inline]
pub(super) fn win(&self) -> &WindowState {
self.win_state
.as_ref()
.expect("DxContext window state present")
}
#[inline]
pub(super) fn win_mut(&mut self) -> &mut WindowState {
self.win_state
.as_mut()
.expect("DxContext window state present")
}
pub(crate) fn window_closed(&mut self) -> bool {
frame_tick(
self.win_state
.as_mut()
.expect("DxContext window state present"),
&mut self.fullscreen_display,
)
}
pub(crate) fn wait_idle(&self) {
let val = self.frame_sync.next_fence_value.get();
self.frame_sync.next_fence_value.set(val + 1);
if unsafe { self.command_queue.Signal(&self.frame_sync.fence, val) }.is_ok()
&& unsafe { self.frame_sync.fence.GetCompletedValue() } < val
&& let Ok(()) = unsafe {
self.frame_sync
.fence
.SetEventOnCompletion(val, self.frame_sync.fence_event)
}
{
unsafe { WaitForSingleObject(self.frame_sync.fence_event, u32::MAX) };
}
}
pub(crate) fn capture_cursor(&mut self) {
self.win_mut().recapture_on_click = true;
}
pub(crate) fn set_ui_cursor_hidden(&mut self, hidden: bool) {
do_set_ui_cursor_hidden(self.win_mut(), hidden);
}
pub(crate) fn cursor_outside_window(&self) -> bool {
self.win().cursor_outside_window
}
pub(crate) fn set_menu_mode(&mut self, on: bool) {
self.win_mut().menu_mode = on;
}
pub(crate) fn set_camera_capture(&mut self, capture: bool) {
if capture == self.win().cursor_captured {
return;
}
if capture {
let hwnd = self.win().hwnd;
do_capture_cursor(hwnd, self.win_mut());
} else {
do_release_cursor(self.win_mut());
}
}
pub(crate) fn set_vsync(&mut self, on: bool) {
self.swapchain.present_sync_interval = if on { 1 } else { 0 };
}
pub(crate) fn set_window_mode(&mut self, mode: crate::components::WindowMode) {
do_set_window_mode(self.win_mut(), mode);
}
pub(crate) fn set_window_size(&mut self, width: u32, height: u32) {
do_set_window_size(self.win_mut(), width, height);
}
pub(crate) fn display_modes(&self) -> Vec<crate::gfx::display_mode::DisplayMode> {
crate::win32::display_mode::enumerate(self.win().hwnd)
}
pub(crate) fn current_display_mode(&self) -> Option<crate::gfx::display_mode::DisplayMode> {
crate::win32::display_mode::current(self.win().hwnd)
}
pub(crate) fn set_display_mode(&mut self, mode: crate::gfx::display_mode::DisplayMode) {
self.fullscreen_display.set_desired(mode);
}
pub(crate) fn update_post_process(
&mut self,
tunables: crate::gfx::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.wait_idle();
if let Err(e) = super::draw::upload_light_uniforms(
&self.uniforms.light_ubo,
&self.uniforms.light_uniforms,
) {
tracing::warn!("set_ambient_intensity: re-upload light uniforms failed: {e}");
}
}
pub(crate) fn update_directional_lights(
&mut self,
lights: &[crate::components::DirectionalLight],
) {
let (directional, num_directional) = crate::gfx::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 = crate::gfx::lights::sun_direction(&self.uniforms.light_uniforms);
self.fog.sun_dir = self.shadow.light_dir;
self.fog.sun_color = crate::gfx::lights::sun_color(&self.uniforms.light_uniforms);
self.wait_idle();
if let Err(e) = super::draw::upload_light_uniforms(
&self.uniforms.light_ubo,
&self.uniforms.light_uniforms,
) {
tracing::warn!("update_directional_lights: re-upload light uniforms failed: {e}");
}
}
pub(crate) fn set_shadow_update(&mut self, update: crate::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: crate::gfx::backend::QualitySettings) {
if let (Some(live), Some(res)) = (q.ssao, self.ssao.resources.as_mut()) {
res.settings = live;
}
if let (Some(live), Some(res)) = (q.ssr, self.ssr.as_mut())
&& let Some(r) = res.resolve.as_mut()
{
r.settings = live;
}
if let (Some(live), Some(res)) = (q.ssgi, self.ssgi.as_mut()) {
res.settings.intensity = live.intensity;
res.settings.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 set_keymap(&mut self, keymap: &crate::gfx::keymap::KeyMap) {
self.win_mut().key.set_keymap(keymap);
}
pub(crate) fn take_input(&mut self) -> InputState {
take_input_snapshot(self.win_mut())
}
pub(crate) fn logical_size(&self) -> (f32, f32) {
(
self.extent.output_width as f32,
self.extent.output_height as f32,
)
}
pub(crate) fn capabilities(&self) -> crate::gfx::backend::DeviceCapabilities {
crate::gfx::backend::DeviceCapabilities {
ray_tracing: self.rt_capable,
selectable_upscaler: true,
reuses_build_slots: false,
rewrites_draws: false,
}
}
pub(crate) fn gpu_profile(&self) -> crate::gfx::backend::GpuProfile {
use crate::gfx::backend::{GpuClassInput, GpuProfile, GpuVendor, classify_tier};
let Some(adapter) = self.adapter.as_ref() else {
return GpuProfile::UNKNOWN;
};
let desc = match unsafe { adapter.GetDesc1() } {
Ok(d) => d,
Err(_) => return GpuProfile::UNKNOWN,
};
let vendor = match desc.VendorId {
0x10DE => GpuVendor::Nvidia,
0x1002 => GpuVendor::Amd,
0x8086 => GpuVendor::Intel,
_ => GpuVendor::Other,
};
let dedicated = desc.DedicatedVideoMemory as u64;
let discrete = dedicated >= (256u64 << 20);
let tier = classify_tier(&GpuClassInput {
vendor,
memory_budget_bytes: dedicated,
discrete,
apple_family: 0,
});
GpuProfile {
vendor,
tier,
memory_budget_bytes: dedicated,
unified_memory: !discrete,
discrete,
}
}
pub(super) fn object_srv_gpu(&self, obj_idx: usize) -> D3D12_GPU_DESCRIPTOR_HANDLE {
let srv_gpu_base = unsafe {
self.descriptors
.srv_heap
.GetGPUDescriptorHandleForHeapStart()
};
let slot = 3 + obj_idx * 2;
D3D12_GPU_DESCRIPTOR_HANDLE {
ptr: srv_gpu_base.ptr + (slot * self.descriptors.srv_descriptor_size) as u64,
}
}
pub(super) fn cluster_srv_gpu(&self, cluster_idx: usize) -> D3D12_GPU_DESCRIPTOR_HANDLE {
let srv_gpu_base = unsafe {
self.descriptors
.srv_heap
.GetGPUDescriptorHandleForHeapStart()
};
let slot = 3 + self.draw.n_objects * 2 + cluster_idx * 2;
D3D12_GPU_DESCRIPTOR_HANDLE {
ptr: srv_gpu_base.ptr + (slot * self.descriptors.srv_descriptor_size) as u64,
}
}
pub(super) fn skinned_srv_gpu(&self, i: usize) -> D3D12_GPU_DESCRIPTOR_HANDLE {
let srv_gpu_base = unsafe {
self.descriptors
.srv_heap
.GetGPUDescriptorHandleForHeapStart()
};
let slot = self.skinned.srv_base_slot + i * 2;
D3D12_GPU_DESCRIPTOR_HANDLE {
ptr: srv_gpu_base.ptr + (slot * self.descriptors.srv_descriptor_size) as u64,
}
}
}
impl crate::gfx::scene_flow::SceneControl for DxContext {
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 Drop for DxContext {
fn drop(&mut self) {
self.wait_idle();
if self.win_state.is_some() {
super::pso_library::shutdown();
}
if let Some(ws) = self.win_state.as_mut() {
do_release_cursor(ws);
}
self.uniforms.unmap();
self.light_cull.unmap();
unsafe { CloseHandle(self.frame_sync.fence_event) }.ok();
}
}
pub(super) fn drain_info_queue(iq: &ID3D12InfoQueue) {
let count = unsafe { iq.GetNumStoredMessages() };
for i in 0..count {
let mut len = 0usize;
if unsafe { iq.GetMessage(i, None, &mut len) }.is_err() {
continue;
}
if len < std::mem::size_of::<D3D12_MESSAGE>() {
continue;
}
let mut buf = vec![0u64; len.div_ceil(std::mem::size_of::<u64>())];
let msg_ptr = buf.as_mut_ptr() as *mut D3D12_MESSAGE;
if unsafe { iq.GetMessage(i, Some(msg_ptr), &mut len) }.is_err() {
continue;
}
let msg = unsafe { &*msg_ptr };
let text = if msg.pDescription.is_null() {
"(no description)".to_owned()
} else {
unsafe { std::ffi::CStr::from_ptr(msg.pDescription as *const i8) }
.to_string_lossy()
.into_owned()
};
match msg.Severity {
D3D12_MESSAGE_SEVERITY_CORRUPTION | D3D12_MESSAGE_SEVERITY_ERROR => {
tracing::error!(target: "d3d12", "{text}");
}
D3D12_MESSAGE_SEVERITY_WARNING => {
tracing::warn!(target: "d3d12", "{text}");
}
_ => {
tracing::debug!(target: "d3d12", "{text}");
}
}
}
unsafe { iq.ClearStoredMessages() };
}
pub(super) fn dump_on_err<T>(
info_queue: Option<&ID3D12InfoQueue>,
r: Result<T, String>,
) -> Result<T, String> {
if r.is_err()
&& let Some(iq) = info_queue
{
drain_info_queue(iq);
}
r
}