use concinnity_core::components;
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::error;
use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_core::render::hdr_output;
use concinnity_core::render::lights;
use concinnity_core::render::pass_timing;
use concinnity_core::render::planar_reflection;
use concinnity_core::render::post::reflection_path::ReflectionPath;
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::slot_rewrites;
use concinnity_core::window::display_mode;
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 super::allocator::{DeviceAllocator, PooledBuffer, PooledTexture};
use super::auto_exposure::AutoExposureState;
use super::cull::CullState;
use super::decal::*;
use super::draw::shadow::ShadowState;
use super::draw::spot_shadow::SpotShadowState;
use super::fog::*;
use super::geometry_upload::{GeometryDest, GeometryUploads};
use super::hot_reload::HotReloadState;
use super::particle::ParticleState;
use super::post::bloom::BloomResources;
use super::post::gbuffer::GbufferResources;
use super::post::ssao::*;
use super::post::ssr::*;
use super::post::taa::*;
use super::post::upscale::UpscaleState;
use super::resources::skinning::SkinnedState;
use super::texture::*;
use crate::directx::descriptor_slot::{SamplerSlot, SrvSlot};
use crate::win32::window;
use crate::win32::window::{WindowState, frame_tick, take_input_snapshot};
pub(super) const FRAMES: usize = 3; pub(super) const CB_ALIGN: u64 = 256;
pub(super) const MAX_SKINNED_OBJECTS: usize = 64;
pub(super) fn align256(n: u64) -> u64 {
(n + CB_ALIGN - 1) & !(CB_ALIGN - 1)
}
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: (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 alloc.alloc_buffer(
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 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: SrvSlot,
pub msaa_samples: u32,
}
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: SrvSlot,
}
pub(super) struct DxUniforms {
pub view_ubo_resources: Vec<PooledBuffer>,
pub view_ubo_ptrs: Vec<*mut u8>,
pub light_ubo_resources: Vec<PooledBuffer>,
pub light_ubo_ptrs: Vec<*mut u8>,
pub light_dirty: std::cell::Cell<concinnity_core::render::frame_dirty::FrameDirty>,
pub local_light_buffer: PooledBuffer,
pub cluster_reach: concinnity_core::render::cluster_range::ClusterReach,
pub light_uniforms: render_types::LightUniforms,
pub shadow_ubo_resources: Vec<PooledBuffer>,
pub shadow_ubo_ptrs: Vec<*mut u8>,
pub probe_set_cbvs: Vec<PooledBuffer>,
pub probe_set_cbv_ptrs: Vec<*mut u8>,
pub probe_set_empty_cbv: PooledBuffer,
}
impl DxUniforms {
pub(super) fn mark_lights_dirty(&self) {
let mut dirty = self.light_dirty.get();
dirty.mark_all();
self.light_dirty.set(dirty);
}
pub(super) fn take_light_dirty(&self, frame: usize) -> bool {
let mut dirty = self.light_dirty.get();
let pending = dirty.take(frame);
self.light_dirty.set(dirty);
pending
}
pub(super) fn unmap(&self) {
for res in self
.view_ubo_resources
.iter()
.chain(self.light_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 clusters: Vec<InstancedCluster>,
pub any_lod: bool,
}
impl DxInstanced {
pub(super) fn new(clusters: Vec<InstancedCluster>) -> Self {
Self {
any_lod: concinnity_core::gfx::lod::any_cluster_has_lod(&clusters),
clusters,
}
}
}
pub(super) struct DxDescriptors {
pub srv_heap: ID3D12DescriptorHeap,
pub srv_descriptor_size: usize,
pub layout: super::init::heap_layout::SrvHeapLayout,
pub flat_pool_len: usize,
pub sampler_heap: ID3D12DescriptorHeap,
pub shadow_sampler_gpu: SamplerSlot,
pub linear_sampler_gpu: SamplerSlot,
pub text_sampler_gpu: SamplerSlot,
}
pub(super) struct ProbeState {
pub book: ProbeBook,
pub gpu: super::probe_set::ProbeSetGpu,
pub bake: super::probe::DxProbeBake,
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::DxProbeBake::default(),
prefilter,
}
}
}
pub(super) struct StreamState {
pub pool_rewrites: slot_rewrites::SlotRewriteQueue,
pub frame: u64,
pub retires: RetirePool<super::texture::StreamedUploadRetire>,
}
impl StreamState {
pub(super) const RETIRE_DEPTH: u64 = FRAMES as u64 + 1;
pub(super) fn new() -> Self {
Self {
pool_rewrites: slot_rewrites::SlotRewriteQueue::new(FRAMES),
frame: 0,
retires: RetirePool::new(),
}
}
}
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,
pub heap: ID3D12DescriptorHeap,
}
pub(super) struct TextState {
pub root_sig: ID3D12RootSignature,
pub pso: Option<ID3D12PipelineState>,
pub upload: super::upload_ring::UploadRing,
#[expect(
dead_code,
reason = "held to keep the text atlases resident; the pass binds the SRV handles"
)]
pub atlas_textures: Vec<GpuResource>,
pub atlas_srv_gpus: Vec<SrvSlot>,
}
pub(super) struct CompositeState {
pub root_sig: ID3D12RootSignature,
pub pso: ID3D12PipelineState,
}
#[derive(Default)]
pub(super) struct Diagnostics {
pub frame_stats: std::cell::Cell<profile::RenderStats>,
pub draw_calls_accum: std::sync::atomic::AtomicU32,
}
pub(super) struct DxTargets {
pub hdr: HdrState,
pub depth: DepthState,
pub main_depth_srv_gpu: SrvSlot,
pub extent: Extents,
pub transient_pool: super::transient_pool::TransientResourcePool,
pub reactive_mask: super::reactive_mask::ReactiveMask,
}
pub(super) struct DxSceneAssets {
pub env_map: EnvironmentMapTextures,
pub color_lut: GpuResource,
pub area_light: AreaLightState,
pub geometry: DxGeometry,
pub textures: Vec<PooledTexture>,
pub _fallback_textures: Vec<PooledTexture>,
}
pub(super) struct DxRayTracing {
pub accel: Option<super::raytrace::RtAccelData>,
pub dynamic_mode: concinnity_core::render::rt_geom::RtDynamicMode,
pub update_streak: concinnity_core::render::rt_accel::FailureStreak,
pub retired: RetirePool<super::raytrace::RtAccelData>,
pub retire_tick: u64,
pub skinned_geometry: bool,
pub skin: Option<super::raytrace::SkinPipeline>,
}
pub(super) struct DxHardware {
pub device: ID3D12Device,
pub command_queue: ID3D12CommandQueue,
pub alloc: DeviceAllocator,
pub adapter: Option<IDXGIAdapter3>,
pub info_queue: Option<ID3D12InfoQueue>,
pub rt_capable: bool,
pub hdr_mode: hdr_output::HdrOutputMode,
pub swapchain_config: backend_init::SwapchainConfig,
pub fullscreen_display: crate::win32::display_mode::FullscreenDisplayMode,
pub win_state: Option<Box<WindowState>>,
}
impl DxHardware {
pub(super) fn hand_over(&mut self) -> RenderResult<Self> {
Ok(Self {
win_state: Some(self.win_state.take().ok_or_else(|| {
RenderError::Other("apply_world_reload: window already taken".into())
})?),
fullscreen_display: std::mem::replace(
&mut self.fullscreen_display,
crate::win32::display_mode::FullscreenDisplayMode::new(),
),
alloc: DeviceAllocator::new(&self.device, &self.command_queue, FRAMES),
device: self.device.clone(),
command_queue: self.command_queue.clone(),
adapter: self.adapter.clone(),
info_queue: self.info_queue.clone(),
rt_capable: self.rt_capable,
hdr_mode: self.hdr_mode,
swapchain_config: self.swapchain_config,
})
}
pub(super) fn max_edr(&self) -> Option<f32> {
match self.hdr_mode {
hdr_output::HdrOutputMode::Hdr { max_edr, .. } => Some(max_edr),
hdr_output::HdrOutputMode::Sdr => None,
}
}
pub(super) fn hdr_encoding(&self) -> Option<hdr_output::HdrEncoding> {
match self.hdr_mode {
hdr_output::HdrOutputMode::Hdr { encoding, .. } => Some(encoding),
hdr_output::HdrOutputMode::Sdr => None,
}
}
}
pub(crate) struct DxContext {
pub(super) swapchain: SwapchainState,
pub(super) targets: DxTargets,
pub(super) upscale: UpscaleState,
pub(super) shadow: ShadowState,
pub(super) spot_shadow: SpotShadowState,
pub(super) scene: DxSceneAssets,
pub(super) descriptors: DxDescriptors,
pub(super) state: SceneState,
pub(super) graph_cache:
RefCell<Option<(render_graph::FrameGraphInputs, render_graph::CompiledGraph)>>,
pub(super) geometry_uploads: RefCell<GeometryUploads>,
pub(super) skinned: SkinnedState,
pub(super) uniforms: DxUniforms,
pub(super) cull: CullState,
pub(super) light_cull: super::light_cull::LightCullState,
pub(super) text: TextState,
pub(super) composite: CompositeState,
pub(super) bloom: Option<BloomResources>,
pub(super) post_process: render_types::PostProcessParams,
pub(super) gbuffer: Option<GbufferResources>,
pub(super) taa: Option<TaaResources>,
pub(super) post: super::post::descriptors::PostDescriptors,
pub(super) ssao: SsaoState,
pub(super) ssr: Option<SsrResources>,
pub(super) ssgi: Option<super::post::ssgi::SsgiResources>,
pub(super) reflection_composite:
Option<super::post::reflection_composite::DxReflectionCompositePass>,
pub(super) rt_reflections: Option<super::post::rt_reflections::RtReflectionsResources>,
pub(super) rt: DxRayTracing,
pub(super) decal: DecalState,
pub(super) lines: super::line::LineState,
pub(super) raymarch: Option<super::raymarch::RaymarchResources>,
pub(super) sky: super::sky::DxSky,
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) wireframe: super::wireframe::DxWireframe,
pub(super) auto_exposure: AutoExposureState,
pub(super) timestamps: TimestampState,
pub(super) hot_reload: HotReloadState,
pub(super) world_shader: Option<concinnity_core::components::ShaderPrograms>,
pub(super) quality_slots: super::quality::QualitySlotHandles,
pub(super) probe: ProbeState,
pub(super) hw: DxHardware,
}
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<'_>) -> 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();
}
let frame = self.current_frame;
let 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);
self.flush_validation();
let start_cmd = self.record_frame_start(frame)?;
self.ensure_line_pipeline(!lines.is_empty());
self.update_shadow_schedule(cam_pos, fov_y_radians, near, view_distance);
self.upload_probe_set(frame)?;
if let Some(params) = self.cull.material_params.as_mut() {
params.upload(frame);
}
unsafe { self.commands.end_command_allocators[frame].Reset() }
.map_err(|e| super::error::map_hresult(e.code(), "end allocator reset"))?;
let end_cmd = &self.commands.end_command_lists[frame];
unsafe { end_cmd.Reset(&self.commands.end_command_allocators[frame], None) }
.map_err(|e| super::error::map_hresult(e.code(), "end cmd reset"))?;
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,
};
let recorded = 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,
view_distance,
cam_pos,
text_calls,
lines,
},
crate::directx::draw::RecordFrameResolution {
width: self.targets.extent.render_width.max(1),
height: self.targets.extent.render_height.max(1),
output_width: self.targets.extent.output_width.max(1),
output_height: self.targets.extent.output_height.max(1),
},
world_hidden,
)?;
if let Some(taa) = self.taa.as_mut() {
taa.pass.advance();
}
if let Some(ssgi) = self.ssgi.as_mut() {
ssgi.advance();
}
self.finish_frame_stats();
let submitted = self.close_end_list(frame).and_then(|()| {
self.submit_and_present(
start_cmd,
&recorded.pass_cmd_lists,
back_idx,
frame,
gpu_wait,
)
});
if submitted.is_err() && recorded.primed_model_history {
self.state.model_history.get_mut().request_prime();
}
submitted
}
pub(super) fn flush_validation(&self) {
if let Some(ref iq) = self.hw.info_queue {
drain_info_queue(iq);
}
}
pub(crate) fn render_stats(&self) -> profile::RenderStats {
self.diagnostics.frame_stats.get()
}
pub(super) fn query_vram_bytes(&self) -> u64 {
let Some(adapter) = self.hw.adapter.as_ref() else {
return 0;
};
let mut info = windows::Win32::Graphics::Dxgi::DXGI_QUERY_VIDEO_MEMORY_INFO::default();
unsafe {
adapter.QueryVideoMemoryInfo(
0,
windows::Win32::Graphics::Dxgi::DXGI_MEMORY_SEGMENT_GROUP_LOCAL,
&mut info,
)
}
.map_or(0, |_| info.CurrentUsage)
}
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.reflection_path().rt_trace
}
pub(super) fn reflection_path(&self) -> ReflectionPath {
ReflectionPath::new(
self.ssr.as_ref().is_some_and(|s| s.resolve.is_some()),
self.rt_reflections.is_some(),
self.rt.accel.is_some(),
)
}
pub(super) fn reflection_resolve_active(&self) -> bool {
self.reflection_path().resolves()
}
pub(in crate::directx) fn hdr_scene_target(&self) -> &ID3D12Resource {
self.targets
.hdr
.resolve
.as_ref()
.unwrap_or(&self.targets.hdr.color)
}
pub(in crate::directx) fn hdr_scene_rtv(&self) -> D3D12_CPU_DESCRIPTOR_HANDLE {
match self.targets.hdr.resolve_rtv {
Some(rtv) => rtv,
None => self.targets.hdr.color_rtv,
}
}
pub(super) fn shade_mode(&self) -> f32 {
if self.state.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 {
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.state
.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.hw
.win_state
.as_ref()
.expect("DxContext window state present")
}
#[inline]
pub(super) fn win_mut(&mut self) -> &mut WindowState {
self.hw
.win_state
.as_mut()
.expect("DxContext window state present")
}
pub(crate) fn window_closed(&mut self) -> bool {
frame_tick(
self.hw
.win_state
.as_mut()
.expect("DxContext window state present"),
&mut self.hw.fullscreen_display,
)
}
pub(super) fn flush_geometry_uploads(&self) -> error::RenderResult<()> {
self.geometry_uploads.borrow_mut().flush(
&self.hw.device,
&self.hw.command_queue,
&GeometryDest {
vertex: &self.scene.geometry.vertex_buffer,
index: &self.scene.geometry.index_buffer,
},
)
}
pub(crate) fn wait_idle(&self) {
if let Err(e) = self.flush_geometry_uploads() {
tracing::error!("geometry upload submit failed: {e}");
}
let val = self.frame_sync.next_fence_value.get();
self.frame_sync.next_fence_value.set(val + 1);
if unsafe { self.hw.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 request_cursor_capture(&mut self) {
self.win_mut().recapture_on_click = true;
}
pub(crate) fn set_ui_cursor_hidden(&mut self, hidden: bool) {
window::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;
window::capture_cursor(hwnd, self.win_mut());
} else {
window::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: components::WindowMode) {
window::set_window_mode(self.win_mut(), mode);
}
pub(crate) fn set_window_size(&mut self, width: u32, height: u32) {
window::set_window_size(self.win_mut(), width, height);
}
pub(crate) fn display_modes(&self) -> Vec<display_mode::DisplayMode> {
crate::win32::display_mode::enumerate(self.win().hwnd)
}
pub(crate) fn current_display_mode(&self) -> Option<display_mode::DisplayMode> {
crate::win32::display_mode::current(self.win().hwnd)
}
pub(crate) fn set_display_mode(&mut self, mode: display_mode::DisplayMode) {
self.hw.fullscreen_display.set_desired(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.mark_lights_dirty();
}
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.mark_lights_dirty();
}
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(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.adaptation.as_mut()) {
cur.settings = live;
}
}
pub(crate) fn set_keymap(&mut self, keymap: &KeyMap) {
self.win_mut().key.set_keymap(keymap);
}
pub(crate) fn take_input(&mut self) -> InputSnapshot {
take_input_snapshot(self.win_mut())
}
pub(crate) fn logical_size(&self) -> (f32, f32) {
(
self.targets.extent.output_width as f32,
self.targets.extent.output_height as f32,
)
}
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, classify_tier,
};
let Some(adapter) = self.hw.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,
}
}
}
impl scene_flow::SceneControl for DxContext {
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 Drop for DxContext {
fn drop(&mut self) {
self.wait_idle();
if self.hw.win_state.is_some() {
super::pso_library::shutdown();
crate::shader::runtime_cache::checkpoint();
}
if let Some(ws) = self.hw.win_state.as_mut() {
window::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, E>(
info_queue: Option<&ID3D12InfoQueue>,
r: Result<T, E>,
) -> Result<T, E> {
if r.is_err()
&& let Some(iq) = info_queue
{
drain_info_queue(iq);
}
r
}