Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions Native/hook_core/src/hook_render_d3d11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::time::{SystemTime, Duration};

use global_state::{GLOBAL_STATE, LOADED_MODS, METRICS_TRACK_MOD_PRIMS, HWND};
use mod_stats::mod_stats;
use shared_dx::dx11rs::{DX11RenderState};
use shared_dx::dx11rs::{DX11RenderState, VertexFormat};
use shared_dx::types::{HookDeviceState, DevicePointer, DX11Metrics, D3D11Tex};
use shared_dx::types_dx11::{HookDirect3D11Context};
use shared_dx::util::{write_log_file, ReleaseOnDrop};
Expand Down Expand Up @@ -386,6 +386,33 @@ fn compute_prim_vert_count(index_count: UINT, rs:&DX11RenderState) -> Option<(u3
Some((prim_count,vert_count))
}

/// True if `current_layout_ptr` declares any semantic that wasn't present in
/// the layout used to fill `d3d11d.vb`. When that happens, the existing fill
/// is missing data (e.g. NORMAL bytes are zero because the fill layout was a
/// depth-only pass), and the mod needs to be released and refilled.
///
/// Returns false if the mask wasn't recorded at fill time (mask == 0), if the
/// current layout pointer is null, or if the layout isn't in the context's
/// layout map. All of those preserve pre-refill behavior.
unsafe fn needs_refill_for_layout(
d3d11d: &ModD3DData11,
current_layout_ptr: *mut ID3D11InputLayout,
layouts: &FnvHashMap<usize, VertexFormat>,
) -> bool {
if current_layout_ptr.is_null() {
return false;
}
let old_mask = d3d11d.vlayout_semantic_mask;
if old_mask == 0 {
return false;
}
let new_mask = match layouts.get(&(current_layout_ptr as usize)) {
Some(vf) => vf.semantic_mask(),
None => return false,
};
VertexFormat::has_extra_semantics(old_mask, new_mask)
}

fn update_drawn_recently(metrics:&mut DX11Metrics, prim_count:u32, vert_count: u32, checkres:&CheckRenderModResult) {
if METRICS_TRACK_MOD_PRIMS {
use shared_dx::types::MetricsDrawStatus::*;
Expand Down Expand Up @@ -661,19 +688,34 @@ pub unsafe extern "system" fn hook_draw_indexed(
} else {
profile_end!(hdi, mod_precheck);
profile_start!(hdi, mod_check);
// If a draw needs the mod refilled (current layout has
// semantics the original fill didn't see), the closure
// can't mutate the mod directly — it has only an immutable
// ref. Stash the request here and let the post-check
// block handle release + reload.
let mut override_mod_status = None;
let mod_status = check_and_render_mod(prim_count, vert_count,
|d3dd,nmod| {
profile_start!(hdi, mod_render);
let res = if let ModD3DData::D3D11(d3d11d) = d3dd {
render_mod_d3d11(THIS, hook_context, d3d11d, nmod, override_texture, sel_stage, (prim_count,vert_count))
if needs_refill_for_layout(d3d11d,
state.rs.current_input_layout,
&state.rs.context_input_layouts_by_ptr) {
override_mod_status = Some(
CheckRenderModResult::NotRenderedButLoadRequested(
nmod.name.clone()));
false
} else {
render_mod_d3d11(THIS, hook_context, d3d11d, nmod, override_texture, sel_stage, (prim_count,vert_count))
}
} else {
false
};
profile_end!(hdi, mod_render);
res
});
profile_end!(hdi, mod_check);
mod_status
override_mod_status.unwrap_or(mod_status)
};

profile_start!(hdi, post_mod_check);
Expand All @@ -689,6 +731,11 @@ pub unsafe extern "system" fn hook_draw_indexed(
Ok(mut loaded_mods_guard) => {
let nmod = mod_load::get_mod_by_name(name, &mut *loaded_mods_guard);
if let Some(nmod) = nmod {
// If the mod is already loaded but we need
// a refill (semantics-mismatch path), drop
// the stale d3d data so the Unloaded path
// below can prime a fresh load.
mod_load::reset_for_reload(nmod);
// need to store current input layout in the d3d data
if let ModD3DState::Unloaded = nmod.d3d_data {
let il = state.rs.current_input_layout;
Expand Down
18 changes: 18 additions & 0 deletions Native/mod_load/src/mod_load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ fn clear_d3d_data(nmd:&mut NativeModData) {
nmd.d3d_data = native_mod::ModD3DState::Unloaded;
}

/// If the mod is currently `Loaded`, release its d3d resources and transition
/// to `Unloaded`. The standard "render an unloaded mod" path then primes the
/// reload using the current input layout on the next frame.
///
/// Used when something about the current draw makes the existing fill stale
/// (e.g. the layout in use exposes semantics the original fill didn't see).
pub unsafe fn reset_for_reload(nmd: &mut NativeModData) {
if let native_mod::ModD3DState::Loaded(ref mut d3dd) = nmd.d3d_data {
write_log_file(&format!("resetting mod {} for reload", nmd.name));
d3dd.release();
nmd.d3d_data = native_mod::ModD3DState::Unloaded;
}
}

pub unsafe fn clear_loaded_mods(device: DevicePointer) {
let lock = GLOBAL_STATE_LOCK.lock();
if let Err(_e) = lock {
Expand Down Expand Up @@ -411,6 +425,10 @@ pub unsafe fn load_d3d_data11(device: *mut ID3D11Device, callbacks: interop::Man
d3d_data.vb = vertex_buffer;
d3d_data.vert_size = vert_size as u32;
d3d_data.vert_count = vert_count as u32;
// Remember which semantics the fill layout exposed, so we can detect later
// draws that use a richer layout (e.g. depth-only pass filled the VB, then
// a color pass with NORMAL shows up) and trigger a refill.
d3d_data.vlayout_semantic_mask = vlayout.semantic_mask();

// load textures, if any
let dp = DevicePointer::D3D11(device);
Expand Down
77 changes: 76 additions & 1 deletion Native/shared_dx/src/dx11rs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,58 @@ pub struct VertexFormat {
pub size: u32,
}

/// Packed bitmask of (semantic, semantic_index) pairs declared by a vertex
/// layout, restricted to the semantics ModelMod knows how to fill.
///
/// Two distinct layouts that declare the same set of supported semantics
/// produce equal masks. The hot-path refill check is `(new & !old) != 0`.
pub type SemanticMask = u128;

/// Subset of D3D semantic names ModelMod fills. Anything not listed here
/// (custom engine semantics, etc.) is silently dropped from the mask, which
/// matches the existing fill behavior.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum Semantic {
Position = 0,
Normal = 1,
TexCoord = 2,
Binormal = 3,
Bitangent = 4,
Color = 5,
Tangent = 6,
BlendIndices = 7,
BlendWeight = 8,
}

/// Number of distinct semantic indices per semantic that fit in the mask.
/// 9 semantics * 14 indices = 126 bits, fits in u128 with 2 spare. Indices
/// at or above this value get clamped to the top slot (extremely rare in
/// practice; ModelMod would not be filling them anyway).
const SEM_INDEX_SLOTS: u32 = 14;

impl Semantic {
/// D3D treats semantic names as case-insensitive; match that here.
fn from_name_bytes(name: &[u8]) -> Option<Self> {
if name.eq_ignore_ascii_case(b"POSITION") { Some(Semantic::Position) }
else if name.eq_ignore_ascii_case(b"NORMAL") { Some(Semantic::Normal) }
else if name.eq_ignore_ascii_case(b"TEXCOORD") { Some(Semantic::TexCoord) }
else if name.eq_ignore_ascii_case(b"BINORMAL") { Some(Semantic::Binormal) }
else if name.eq_ignore_ascii_case(b"BITANGENT") { Some(Semantic::Bitangent) }
else if name.eq_ignore_ascii_case(b"COLOR") { Some(Semantic::Color) }
else if name.eq_ignore_ascii_case(b"TANGENT") { Some(Semantic::Tangent) }
else if name.eq_ignore_ascii_case(b"BLENDINDICES") { Some(Semantic::BlendIndices) }
else if name.eq_ignore_ascii_case(b"BLENDWEIGHT") { Some(Semantic::BlendWeight) }
else { None }
}

#[inline]
fn mask_bit(self, index: u32) -> SemanticMask {
let idx = index.min(SEM_INDEX_SLOTS - 1);
1u128 << ((self as u32) * SEM_INDEX_SLOTS + idx)
}
}

impl VertexFormat {
/// Create a shallow copy of the vertex format. This will copy the layout vector, but the
/// pointers in the vector elements will still point to the same strings as the original.
Expand All @@ -27,6 +79,29 @@ impl VertexFormat {
size: self.size,
}
}

/// Compute a bitmask of the (semantic, semantic_index) pairs declared by
/// this layout, restricted to semantics ModelMod fills. Cheap enough to
/// recompute on each modded draw (CStr scan over typically <=16 elements).
pub fn semantic_mask(&self) -> SemanticMask {
let mut mask: SemanticMask = 0;
for elem in &self.layout {
if elem.SemanticName.is_null() {
continue;
}
let name_bytes = unsafe { CStr::from_ptr(elem.SemanticName) }.to_bytes();
if let Some(sem) = Semantic::from_name_bytes(name_bytes) {
mask |= sem.mask_bit(elem.SemanticIndex);
}
}
mask
}

/// True if `new` declares any (semantic, index) pair not present in `old`.
#[inline]
pub fn has_extra_semantics(old: SemanticMask, new: SemanticMask) -> bool {
(new & !old) != 0
}
}

impl Display for VertexFormat {
Expand All @@ -35,7 +110,7 @@ impl Display for VertexFormat {
for i in 0..self.layout.len() {
let bytename = unsafe { CStr::from_ptr(self.layout[i].SemanticName) }.to_str();

write!(f, "{:?}", bytename)?;
write!(f, "{:?}/{}", bytename, self.layout[i].SemanticIndex)?;
if i < self.layout.len() - 1 {
write!(f, ", ")?;
}
Expand Down
9 changes: 9 additions & 0 deletions Native/types/src/d3ddata.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

use winapi::shared::d3d9::*;
use winapi::um::d3d11::{ID3D11Buffer, ID3D11InputLayout, ID3D11Texture2D, ID3D11Resource, ID3D11ShaderResourceView};
use shared_dx::dx11rs::SemanticMask;

pub struct ModD3DData9 {
pub vb: *mut IDirect3DVertexBuffer9,
Expand Down Expand Up @@ -73,6 +74,10 @@ impl Drop for ModD3DData9 {
pub struct ModD3DData11 {
pub vb: *mut ID3D11Buffer,
pub vlayout: *mut ID3D11InputLayout,
/// Semantic-name/index bitmask of the layout that was used to fill `vb`.
/// Zero means "not tracked" — the refill check treats that as "don't
/// refill".
pub vlayout_semantic_mask: SemanticMask,
pub textures: [*mut ID3D11Texture2D; 4],
pub has_textures: bool,
pub srvs: [*mut ID3D11ShaderResourceView; 4],
Expand Down Expand Up @@ -106,6 +111,7 @@ impl Clone for ModD3DData11 {
Self {
vb: self.vb,
vlayout: self.vlayout,
vlayout_semantic_mask: self.vlayout_semantic_mask,
textures: self.textures,
has_textures: self.has_textures,
srvs: self.srvs,
Expand All @@ -122,6 +128,7 @@ impl ModD3DData11 {
Self {
vb: null_mut(),
vlayout: null_mut(),
vlayout_semantic_mask: 0,
textures: [null_mut(); 4],
has_textures: false,
srvs: [null_mut(); 4],
Expand All @@ -136,6 +143,7 @@ impl ModD3DData11 {
Self {
vb: null_mut(),
vlayout: layout,
vlayout_semantic_mask: 0,
textures: [null_mut(); 4],
has_textures: false,
srvs: [null_mut(); 4],
Expand All @@ -156,6 +164,7 @@ impl ModD3DData11 {
//if rc == 0 { util::write_log_file("releasing vlayout on d3d11 data");}
self.vlayout = std::ptr::null_mut();
}
self.vlayout_semantic_mask = 0;
for srv in self.srvs.iter_mut() {
if !srv.is_null() {
let bsrv = *srv as *mut ID3D11Resource;
Expand Down
Loading