concinnity-device 0.18.69

GPU backends (Metal, Vulkan, DirectX) behind a device facade for Concinnity
Documentation
// src/directx/backend.rs
//
// RenderBackend impl for DxContext. Thin forwarders to the inherent
// methods scattered across directx/{context,resources}.rs.
//
// Most forwarders are a mechanical 1:1 call into the inherent method, so a
// `forward!` token-muncher generates them. The `&mut self` arms prepend
// `debug_assert_main_thread` so every mutation reached through the boxed trait
// object proves the main-thread invariant the `unsafe impl Send for DxContext`
// rests on. Forwarders that rename, drop args, or have a custom body stay
// hand-written below the macro. Mirrors src/metal/backend.rs.

use crate::gfx::backend::{ChunkMesh, FrameParams, RenderBackend};
use crate::gfx::input::RenderInput;
use crate::gfx::mesh_payload::{SkinnedVertex, Vertex};
use crate::gfx::profile::RenderStats;
use crate::gfx::render_types::SkinnedDrawObject;

use super::context::{DxContext, debug_assert_main_thread};

// Generate a `RenderBackend` method that forwards to the inherent method of the
// same name. Inherent methods shadow trait methods in resolution, so
// `self.$name(...)` calls the inherent one (no recursion). The `&mut self` arms
// assert the main-thread invariant first; the `&self` arms are read-only and
// skip it. Recurses token-by-token over the method list.
macro_rules! forward {
    () => {};
    (fn $name:ident(&self $(, $arg:ident: $ty:ty)* $(,)?) -> $ret:ty; $($rest:tt)*) => {
        fn $name(&self $(, $arg: $ty)*) -> $ret { self.$name($($arg),*) }
        forward!($($rest)*);
    };
    (fn $name:ident(&self $(, $arg:ident: $ty:ty)* $(,)?); $($rest:tt)*) => {
        fn $name(&self $(, $arg: $ty)*) { self.$name($($arg),*) }
        forward!($($rest)*);
    };
    (fn $name:ident(&mut self $(, $arg:ident: $ty:ty)* $(,)?) -> $ret:ty; $($rest:tt)*) => {
        fn $name(&mut self $(, $arg: $ty)*) -> $ret {
            debug_assert_main_thread(stringify!($name));
            self.$name($($arg),*)
        }
        forward!($($rest)*);
    };
    (fn $name:ident(&mut self $(, $arg:ident: $ty:ty)* $(,)?); $($rest:tt)*) => {
        fn $name(&mut self $(, $arg: $ty)*) {
            debug_assert_main_thread(stringify!($name));
            self.$name($($arg),*)
        }
        forward!($($rest)*);
    };
}

impl RenderBackend for DxContext {
    forward! {
        fn window_closed(&mut self) -> bool;
        fn capture_cursor(&mut self);
        fn set_ui_cursor_hidden(&mut self, hidden: bool);
        fn cursor_outside_window(&self) -> bool;
        fn set_menu_mode(&mut self, on: bool);
        fn set_camera_capture(&mut self, capture: bool);
        fn set_reflection_probes(&mut self, probes: &[crate::gfx::reflection_probe::ProbePlacement]);
        fn set_vsync(&mut self, on: bool);
        fn set_window_mode(&mut self, mode: crate::components::WindowMode);
        fn set_window_size(&mut self, width: u32, height: u32);
        fn display_modes(&self) -> Vec<crate::gfx::display_mode::DisplayMode>;
        fn current_display_mode(&self) -> Option<crate::gfx::display_mode::DisplayMode>;
        fn set_display_mode(&mut self, mode: crate::gfx::display_mode::DisplayMode);
        fn update_post_process(
            &mut self,
            tunables: crate::gfx::render_types::PostProcessTunables,
        );
        fn set_ambient_intensity(&mut self, value: f32);
        fn update_directional_lights(&mut self, lights: &[crate::components::DirectionalLight]);
        fn set_keymap(&mut self, keymap: &crate::gfx::keymap::KeyMap);
        fn apply_quality_settings(&mut self, settings: crate::gfx::backend::QualitySettings);
        fn set_shadow_update(&mut self, update: crate::components::ShadowUpdate);
        fn set_shadow_distance(&mut self, distance: u32);
        fn set_shadow_cascades(&mut self, count: u32);
        fn update_quality_params(&mut self, settings: crate::gfx::backend::QualitySettings);
        fn take_input(&mut self) -> RenderInput;
        fn wait_idle(&self);
        fn draw_frame(&mut self, params: FrameParams<'_>) -> crate::gfx::error::RenderResult<()>;
        fn update_view(&mut self, matrix: [[f32; 4]; 4]);
        fn update_models(&mut self, updates: &[(u32, [[f32; 4]; 4])]);
        fn retire_draw_object(&mut self, draw_idx: usize);
        fn update_skinned_pose(&mut self, skinned_index: usize, matrices: &[[[f32; 4]; 4]]);
        fn update_morph_weights(&mut self, skinned_index: usize, weights: &[f32]);
        fn reveal_skinned_instance(&mut self, instance_index: usize, model: [[f32; 4]; 4]);
        fn retire_skinned_draw_object(&mut self, skinned_index: usize);
        fn update_skinned_models(&mut self, updates: &[(u32, [[f32; 4]; 4])]);
        fn evict_texture_slot(&mut self, slot: usize) -> Result<(), String>;
        fn evict_mesh(&mut self, draw_idx: usize, retire_frame: u64) -> Result<(), String>;
        fn seed_mesh_streaming(&mut self, vtx_offset: u64, vtx_bytes: u64, idx_offset: u64, idx_bytes: u64);
        fn remove_chunk_mesh(&mut self, draw_idx: usize, retire_frame: u64) -> Result<(), String>;
        fn set_chunk_model(&mut self, draw_idx: usize, model: [[f32; 4]; 4]) -> Result<(), String>;
        fn add_decal(&mut self, record: crate::gfx::decal::DecalRecord) -> Result<usize, String>;
        fn remove_decal(&mut self, decal_id: usize) -> Result<(), String>;
        fn add_emitter(&mut self, record: crate::gfx::particles::ParticleEmitterRecord) -> Result<usize, String>;
        fn remove_emitter(&mut self, emitter_id: usize) -> Result<(), String>;
        fn render_stats(&self) -> RenderStats;
        fn capabilities(&self) -> crate::gfx::backend::DeviceCapabilities;
        fn gpu_profile(&self) -> crate::gfx::backend::GpuProfile;
        fn logical_size(&self) -> (f32, f32);
        fn update_color_lut(&mut self, size: u32, data: &[u8]) -> Result<(), String>;
        fn update_fog_settings(&mut self, settings: Option<crate::gfx::volumetric_fog::FogSettings>);
        fn update_mesh_geometry(&mut self, draw_idx: usize, verts: &[crate::gfx::mesh_payload::Vertex], idxs: &[u16], lod_alternates: &[(f32, Vec<u16>)]) -> Result<(), String>;
        fn update_world_shader_pipelines(&mut self, vert_bytes: Option<&[u8]>, frag_bytes: Option<&[u8]>, shadow_bytes: Option<&[u8]>, vert_instanced_bytes: Option<&[u8]>) -> Result<(), String>;
        fn update_skinned_mesh_geometry(&mut self, skinned_index: usize, vertex_base: u32, verts: &[crate::gfx::mesh_payload::SkinnedVertex], idxs: &[u16]) -> Result<(), String>;
        fn update_skinned_skeleton(&mut self, skinned_index: usize, new_joint_count: usize) -> Result<(), String>;
        fn rebuild_skinned_geometry(&mut self, changes: Vec<crate::gfx::backend::SkinnedDrawGeometryUpdate>) -> Result<Vec<crate::gfx::backend::SkinnedSlotLayout>, String>;
        fn clone_static_draw_object(&mut self, src_draw_idx: usize, model: [[f32; 4]; 4], dst: crate::gfx::draw_slot::SlotAlloc) -> Result<(), String>;
        fn evict_world_shader(&mut self, bucket: u32);
    }

    // Non-1:1 forwarders kept explicit.

    // The swapchain config this live context was built with. A live `cn editor`
    // reload reuses this backend (via `reload_world`) only when the new world's
    // `swapchain_config` matches; otherwise the swap does a full rebuild.
    fn hot_swap_config(&self) -> Option<crate::gfx::backend_init::SwapchainConfig> {
        Some(self.swapchain_config)
    }

    // Rebuild the world's content on the retained device + window + swapchain.
    // Inherent method named `apply_world_reload` so this forwarder does not
    // shadow-and-recurse (mirrors the Metal backend).
    fn reload_world(
        &mut self,
        init: crate::gfx::backend_init::BackendInit<'_>,
    ) -> crate::gfx::error::RenderResult<()> {
        Ok(self.apply_world_reload(init)?)
    }

    // Typed-boundary forwarders: the inherent methods report `String` errors,
    // which `?` coerces to `RenderError::Other`. Sites that can classify a
    // failure construct the typed variant directly instead.
    fn update_texture_slot(
        &mut self,
        slot: usize,
        image: &crate::build::texture::TextureImage,
    ) -> crate::gfx::error::RenderResult<()> {
        debug_assert_main_thread("update_texture_slot");
        Ok(DxContext::update_texture_slot(self, slot, image)?)
    }

    fn upload_mesh(
        &mut self,
        draw_idx: usize,
        verts: &[Vertex],
        idxs: &[u16],
        frame: u64,
    ) -> crate::gfx::error::RenderResult<()> {
        debug_assert_main_thread("upload_mesh");
        Ok(DxContext::upload_mesh(self, draw_idx, verts, idxs, frame)?)
    }

    fn setup_chunk_streaming(
        &mut self,
        chunk_vtx_bytes: usize,
        chunk_idx_bytes: usize,
        texture_slot: usize,
        normal_map_slot: usize,
    ) -> crate::gfx::error::RenderResult<()> {
        debug_assert_main_thread("setup_chunk_streaming");
        Ok(DxContext::setup_chunk_streaming(
            self,
            chunk_vtx_bytes,
            chunk_idx_bytes,
            texture_slot,
            normal_map_slot,
        )?)
    }

    fn add_chunk_mesh(
        &mut self,
        mesh: ChunkMesh<'_>,
        dst: crate::gfx::draw_slot::SlotAlloc,
    ) -> crate::gfx::error::RenderResult<()> {
        debug_assert_main_thread("add_chunk_mesh");
        DxContext::add_chunk_mesh(self, mesh, dst)
    }

    fn update_environment_map(&mut self, payload: &[u8]) -> crate::gfx::error::RenderResult<()> {
        debug_assert_main_thread("update_environment_map");
        Ok(DxContext::update_environment_map(self, payload)?)
    }

    fn rebuild_static_geometry(
        &mut self,
        changes: Vec<crate::gfx::backend::DrawGeometryUpdate>,
    ) -> crate::gfx::error::RenderResult<()> {
        debug_assert_main_thread("rebuild_static_geometry");
        Ok(DxContext::rebuild_static_geometry(self, changes)?)
    }

    fn install_world_shader(
        &mut self,
        bucket: u32,
        shader: crate::gfx::backend_init::ShaderBytes<'_>,
    ) -> crate::gfx::error::RenderResult<()> {
        debug_assert_main_thread("install_world_shader");
        DxContext::install_world_shader(self, bucket, shader)
            .map_err(crate::gfx::error::RenderError::ShaderCompile)
    }

    fn upload_skinned(
        &mut self,
        vertices: &[SkinnedVertex],
        indices: &[u32],
        draw_objects: Vec<SkinnedDrawObject>,
        _vert_bytes: &[u8],
        frag_bytes: &[u8],
        _shadow_bytes: &[u8],
    ) -> crate::gfx::error::RenderResult<()> {
        debug_assert_main_thread("upload_skinned");
        // DirectX compiles the vertex / shadow paths from inline HLSL; only the
        // fragment shader is supplied as a precompiled DXBC payload.
        Ok(self.upload_skinned(vertices, indices, draw_objects, frag_bytes)?)
    }

    // Trait method returns unit; the inherent returns Result (buffer
    // allocation can fail), so the forwarder logs instead of propagating.
    fn upload_skinned_morphs(
        &mut self,
        morphs: Vec<Option<std::sync::Arc<crate::gfx::mesh_payload::PayloadMorphs>>>,
    ) {
        debug_assert_main_thread("upload_skinned_morphs");
        if let Err(e) = DxContext::upload_skinned_morphs(self, morphs) {
            tracing::error!("DirectX: morph target upload failed: {}", e);
        }
    }

    fn draw_geometry_size(&self, draw_idx: usize) -> Option<(usize, usize)> {
        self.draw
            .objects
            .get(draw_idx)
            .map(|o| (o.vertex_count, o.index_count))
    }
    fn draw_lod_index_counts(&self, draw_idx: usize) -> Option<Vec<usize>> {
        self.draw
            .objects
            .get(draw_idx)
            .map(|o| o.lod_alternates.iter().map(|s| s.index_count).collect())
    }

    fn shader_reload_flag(&self) -> Option<std::sync::Arc<std::sync::atomic::AtomicBool>> {
        self.shader_reload_pending()
    }

    // Inherent method is named `capture_screenshot` to keep the forwarder
    // unambiguous (an inherent `screenshot` would shadow the trait method and
    // recurse); kept explicit out of the `forward!` macro for that rename.
    fn screenshot(&mut self, path: &str) -> Result<String, String> {
        debug_assert_main_thread("screenshot");
        self.capture_screenshot(path)
    }
}