mirror of
https://github.com/vladtrc/iw4L
synced 2026-10-26 02:11:39 -04:00
Stop rehashing FX element random draws every frame
Each FX random draw is a BLAKE3 hash. Velocity graph walks hashed the same three per-element draws for every sample they touched, and the element seed was rehashed several times per element per frame across update, sort and draw. Take the velocity draws once per walk and keep the element seed on the slot, set at spawn from the same inputs, so every value is unchanged. 12 bots on mp_rust: fx_update 0.57 -> 0.25 ms per frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+7
-11
@@ -1,6 +1,6 @@
|
||||
use fx_iw4::{
|
||||
FxElemType, FxRandomChannel, FxTrailEmittedVert, FxTrailSegmentDrawState, FxTrailVertex,
|
||||
draw_elem_handler_present, elem_norm_time, elem_random_seed, sample_life_span_msec, sample_u16,
|
||||
draw_elem_handler_present, elem_norm_time, sample_life_span_msec, sample_u16,
|
||||
spark_fountain_cluster_draw_allows, spark_fountain_slot_for_handle, trail_compute_u,
|
||||
trail_emit_index_quad, trail_emit_segment_verts, trail_uncompress_basis, vec3_length_sq,
|
||||
vec3_normalize,
|
||||
@@ -375,8 +375,7 @@ fn draw_one_elem(
|
||||
};
|
||||
let age = host.msec_now.wrapping_sub(elem.msec_begin);
|
||||
let norm = elem_norm_time(age, elem.life_span_msec);
|
||||
let elem_random_seed =
|
||||
elem_random_seed(effect.random_seed, elem.sequence, elem.msec_begin);
|
||||
let elem_random_seed = elem.random_seed;
|
||||
let origin = crate::spark::spark_elem_world_origin(
|
||||
elem.origin,
|
||||
elem.flags,
|
||||
@@ -423,7 +422,7 @@ fn draw_one_elem(
|
||||
};
|
||||
let age = host.msec_now.wrapping_sub(elem.msec_begin);
|
||||
let norm = elem_norm_time(age, elem.life_span_msec);
|
||||
let seed = elem_random_seed(effect.random_seed, elem.sequence, elem.msec_begin);
|
||||
let seed = elem.random_seed;
|
||||
let Some(size1) = on_spark_size1(FxSparkDrawQuery {
|
||||
def_name: effect.def_name.as_str(),
|
||||
catalog_index: effect.catalog_index,
|
||||
@@ -468,8 +467,7 @@ fn draw_one_elem(
|
||||
};
|
||||
let age = host.msec_now.wrapping_sub(elem.msec_begin);
|
||||
let norm = elem_norm_time(age, elem.life_span_msec);
|
||||
let elem_random_seed =
|
||||
elem_random_seed(effect.random_seed, elem.sequence, elem.msec_begin);
|
||||
let elem_random_seed = elem.random_seed;
|
||||
let origin = crate::spark::spark_elem_world_origin(
|
||||
elem.origin,
|
||||
elem.flags,
|
||||
@@ -534,8 +532,7 @@ fn draw_one_elem(
|
||||
};
|
||||
let age = host.msec_now.wrapping_sub(elem.msec_begin);
|
||||
let norm = elem_norm_time(age, elem.life_span_msec);
|
||||
let elem_random_seed =
|
||||
elem_random_seed(effect.random_seed, elem.sequence, elem.msec_begin);
|
||||
let elem_random_seed = elem.random_seed;
|
||||
let origin = crate::spark::spark_elem_world_origin(
|
||||
elem.origin,
|
||||
elem.flags,
|
||||
@@ -574,8 +571,7 @@ fn draw_one_elem(
|
||||
};
|
||||
let age = host.msec_now.wrapping_sub(elem.msec_begin);
|
||||
let norm = elem_norm_time(age, elem.life_span_msec);
|
||||
let elem_random_seed =
|
||||
elem_random_seed(effect.random_seed, elem.sequence, elem.msec_begin);
|
||||
let elem_random_seed = elem.random_seed;
|
||||
let origin = crate::spark::spark_elem_world_origin(
|
||||
elem.origin,
|
||||
elem.flags,
|
||||
@@ -626,7 +622,7 @@ fn draw_one_elem(
|
||||
};
|
||||
let age = host.msec_now.wrapping_sub(elem.msec_begin);
|
||||
let norm = elem_norm_time(age, elem.life_span_msec);
|
||||
let elem_random_seed = elem_random_seed(effect.random_seed, elem.sequence, elem.msec_begin);
|
||||
let elem_random_seed = elem.random_seed;
|
||||
let origin = crate::spark::spark_elem_world_origin(
|
||||
elem.origin,
|
||||
elem.flags,
|
||||
|
||||
@@ -22,6 +22,9 @@ pub struct FxElemSlot {
|
||||
pub msec_begin: i32,
|
||||
|
||||
pub life_span_msec: i32,
|
||||
/// `elem_random_seed` of the owning effect, sequence and `msec_begin`,
|
||||
/// none of which change while the element lives.
|
||||
pub random_seed: u64,
|
||||
pub base_vel: [f32; 3],
|
||||
|
||||
pub origin: [f32; 3],
|
||||
@@ -54,6 +57,7 @@ impl Default for FxElemSlot {
|
||||
prev_elem_handle: FX_ELEM_HANDLE_NONE,
|
||||
msec_begin: 0,
|
||||
life_span_msec: 0,
|
||||
random_seed: 0,
|
||||
base_vel: [0.0; 3],
|
||||
origin: [0.0; 3],
|
||||
spawn_origin: [[0.0; 2]; 3],
|
||||
|
||||
@@ -145,7 +145,7 @@ fn sort_distance(
|
||||
}
|
||||
let elem = host.elems.get(elem_slot)?;
|
||||
let effect = host.effect_at(effect_slot)?;
|
||||
let seed = fx_iw4::elem_random_seed(effect.random_seed, elem.sequence, elem.msec_begin);
|
||||
let seed = elem.random_seed;
|
||||
let world = spark_elem_world_origin(
|
||||
elem.origin,
|
||||
elem.flags,
|
||||
|
||||
@@ -476,6 +476,7 @@ fn spawn_elem(
|
||||
prev_elem_handle: FX_ELEM_HANDLE_NONE,
|
||||
msec_begin,
|
||||
life_span_msec: life,
|
||||
random_seed: life_idx,
|
||||
base_vel: [0.0; 3],
|
||||
origin,
|
||||
spawn_origin: elem_def.spawn_origin,
|
||||
|
||||
+14
-27
@@ -2,8 +2,8 @@ use fx_iw4::{
|
||||
FX_ELEM_TYPE_SPARK_CLOUD, FX_ELEM_TYPE_SPARK_FOUNTAIN, FX_STATUS_HAS_PENDING_LOOP_ELEMS,
|
||||
FX_STATUS_REF_COUNT_MASK_IW4, FxOrientFrame, FxUpdateEffectBolt, axis_to_quat,
|
||||
begin_iterating_over_effects_exclusive, bolt_compose_orientation, bolt_mark_lost,
|
||||
elem_norm_time, elem_random_seed, elem_uses_collision, end_iterating_over_effects,
|
||||
end_iterating_runs_gc, get_orientation, unit_quat_to_axis, update_effect_bolt, vector_vectors,
|
||||
elem_norm_time, elem_uses_collision, end_iterating_over_effects, end_iterating_runs_gc,
|
||||
get_orientation, unit_quat_to_axis, update_effect_bolt, vector_vectors,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -384,22 +384,20 @@ fn pending_collide_for_elem(
|
||||
}
|
||||
let def_index = elem.def_index;
|
||||
let base_vel = elem.base_vel;
|
||||
let sequence = elem.sequence;
|
||||
let elem_seed = elem.random_seed;
|
||||
let origin = elem.origin;
|
||||
let msec_begin = elem.msec_begin;
|
||||
let life_msec = elem.life_span_msec.max(1);
|
||||
let flags = elem.flags;
|
||||
let (def_name, catalog_index, effect_seed, now, alt) = match host.effect_at(effect_slot) {
|
||||
let (def_name, catalog_index, now, alt) = match host.effect_at(effect_slot) {
|
||||
Some(e) => (
|
||||
slot_def_name(e),
|
||||
e.catalog_index,
|
||||
e.random_seed,
|
||||
e.frame_now(),
|
||||
e.frame_when_played(),
|
||||
),
|
||||
None => return None,
|
||||
};
|
||||
let elem_seed = elem_random_seed(effect_seed, sequence, msec_begin);
|
||||
let spawn = host
|
||||
.elems
|
||||
.get(slot)
|
||||
@@ -614,22 +612,20 @@ fn update_element(
|
||||
let msec_begin = elem.msec_begin;
|
||||
let life_msec = elem.life_span_msec.max(1);
|
||||
let base_vel = elem.base_vel;
|
||||
let sequence = elem.sequence;
|
||||
let elem_seed = elem.random_seed;
|
||||
let origin = elem.origin;
|
||||
let elem_type = elem.elem_type;
|
||||
let spark_handle = elem.spark_cloud_handle;
|
||||
let at_rest_fraction = elem.at_rest_fraction;
|
||||
let (def_name, catalog_index, effect_seed, now, alt) = match host.effect_at(effect_slot) {
|
||||
let (def_name, catalog_index, now, alt) = match host.effect_at(effect_slot) {
|
||||
Some(e) => (
|
||||
slot_def_name(e),
|
||||
e.catalog_index,
|
||||
e.random_seed,
|
||||
e.frame_now(),
|
||||
e.frame_when_played(),
|
||||
),
|
||||
None => return true,
|
||||
};
|
||||
let elem_seed = elem_random_seed(effect_seed, sequence, msec_begin);
|
||||
let spawn = host
|
||||
.elems
|
||||
.get(slot)
|
||||
@@ -666,7 +662,7 @@ fn update_element(
|
||||
life_ms,
|
||||
dt_sec,
|
||||
base_vel,
|
||||
elem_random_seed: elem_random_seed(effect_seed, sequence, msec_begin),
|
||||
elem_random_seed: elem_seed,
|
||||
origin,
|
||||
prev_msec,
|
||||
msec_now,
|
||||
@@ -694,7 +690,6 @@ fn update_element(
|
||||
}
|
||||
}
|
||||
let emit_residual = host.elems.get(slot).map(|e| e.emit_residual).unwrap_or(0);
|
||||
let elem_seed = elem_random_seed(effect_seed, sequence, msec_begin);
|
||||
if let Some(sched) = on_emit(FxEmitQuery {
|
||||
def_name: def_name.as_str(),
|
||||
catalog_index,
|
||||
@@ -797,25 +792,17 @@ fn update_element(
|
||||
def_index,
|
||||
age_msec: msec_now.saturating_sub(msec_begin).max(0),
|
||||
life_msec,
|
||||
elem_random_seed: elem_random_seed(effect_seed, sequence, msec_begin),
|
||||
elem_random_seed: elem_seed,
|
||||
norm_time: elem_norm_time(msec_now.saturating_sub(msec_begin).max(0), life_msec),
|
||||
}) {
|
||||
let (origin_now, at_rest_now, spawn, seed) = match host.elems.get(slot) {
|
||||
Some(e) => {
|
||||
let seed = elem_random_seed(effect_seed, sequence, msec_begin);
|
||||
(
|
||||
e.origin,
|
||||
e.at_rest_fraction,
|
||||
Some(e.orient_spawn_params(seed)),
|
||||
seed,
|
||||
)
|
||||
}
|
||||
None => (
|
||||
origin,
|
||||
at_rest_fraction,
|
||||
None,
|
||||
elem_random_seed(effect_seed, sequence, msec_begin),
|
||||
Some(e) => (
|
||||
e.origin,
|
||||
e.at_rest_fraction,
|
||||
Some(e.orient_spawn_params(elem_seed)),
|
||||
elem_seed,
|
||||
),
|
||||
None => (origin, at_rest_fraction, None, elem_seed),
|
||||
};
|
||||
let world = spark_elem_world_origin(origin_now, flags, &now, &alt, spawn);
|
||||
let axis = spark_elem_axis(
|
||||
|
||||
@@ -7,22 +7,22 @@ pub struct FxElemVec3Range {
|
||||
}
|
||||
|
||||
impl FxElemVec3Range {
|
||||
fn sampled(self, seed: u64) -> [f32; 3] {
|
||||
core::array::from_fn(|axis| {
|
||||
self.base[axis]
|
||||
+ self.amplitude[axis]
|
||||
* crate::random::sample_f32(
|
||||
seed,
|
||||
[
|
||||
crate::random::FxRandomChannel::VelocityX,
|
||||
crate::random::FxRandomChannel::VelocityY,
|
||||
crate::random::FxRandomChannel::VelocityZ,
|
||||
][axis],
|
||||
)
|
||||
})
|
||||
fn sampled(self, random: [f32; 3]) -> [f32; 3] {
|
||||
core::array::from_fn(|axis| self.base[axis] + self.amplitude[axis] * random[axis])
|
||||
}
|
||||
}
|
||||
|
||||
/// Every sample of an element's velocity graph shares these three draws, and
|
||||
/// each one is a hash, so a graph walk takes them once.
|
||||
fn velocity_random(seed: u64) -> [f32; 3] {
|
||||
use crate::random::{FxRandomChannel, sample_f32};
|
||||
[
|
||||
sample_f32(seed, FxRandomChannel::VelocityX),
|
||||
sample_f32(seed, FxRandomChannel::VelocityY),
|
||||
sample_f32(seed, FxRandomChannel::VelocityZ),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn integrate_velocity_graph(
|
||||
samples: &[FxElemVec3Range],
|
||||
start_age01: f32,
|
||||
@@ -41,6 +41,7 @@ pub fn integrate_velocity_graph(
|
||||
let segment_count = samples.len() - 1;
|
||||
|
||||
let segment_span_ms = life_ms * segment_count as f32;
|
||||
let random = velocity_random(seed);
|
||||
let mut delta = [0.0f32; 3];
|
||||
let first = (libm::floorf(start * segment_count as f32) as usize).min(segment_count - 1);
|
||||
let end_excl =
|
||||
@@ -53,8 +54,8 @@ pub fn integrate_velocity_graph(
|
||||
if hi <= lo {
|
||||
continue;
|
||||
}
|
||||
let v0 = sample_lerp(samples, lo, seed);
|
||||
let v1 = sample_lerp(samples, hi, seed);
|
||||
let v0 = sample_lerp(samples, lo, random);
|
||||
let v1 = sample_lerp(samples, hi, random);
|
||||
let w = 0.5 * (hi - lo) * segment_span_ms;
|
||||
delta[0] += (v0[0] + v1[0]) * w;
|
||||
delta[1] += (v0[1] + v1[1]) * w;
|
||||
@@ -65,7 +66,7 @@ pub fn integrate_velocity_graph(
|
||||
|
||||
#[inline]
|
||||
pub fn sample_vel_graph_at_age(samples: &[FxElemVec3Range], age01: f32, seed: u64) -> [f32; 3] {
|
||||
sample_lerp(samples, age01, seed)
|
||||
sample_lerp(samples, age01, velocity_random(seed))
|
||||
}
|
||||
|
||||
fn clamp01(x: f32) -> f32 {
|
||||
@@ -78,15 +79,15 @@ fn clamp01(x: f32) -> f32 {
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_lerp(samples: &[FxElemVec3Range], age01: f32, seed: u64) -> [f32; 3] {
|
||||
fn sample_lerp(samples: &[FxElemVec3Range], age01: f32, random: [f32; 3]) -> [f32; 3] {
|
||||
let last = samples.len() - 1;
|
||||
let t = clamp01(age01);
|
||||
let seg = t * last as f32;
|
||||
let i = (libm::floorf(seg) as usize).min(last);
|
||||
let j = (i + 1).min(last);
|
||||
let f = if i == j { 0.0 } else { seg - i as f32 };
|
||||
let a = samples[i].sampled(seed);
|
||||
let b = samples[j].sampled(seed);
|
||||
let a = samples[i].sampled(random);
|
||||
let b = samples[j].sampled(random);
|
||||
[
|
||||
a[0] + (b[0] - a[0]) * f,
|
||||
a[1] + (b[1] - a[1]) * f,
|
||||
|
||||
Reference in New Issue
Block a user