aboutsummaryrefslogtreecommitdiff
path: root/apps/fparkan-game/src
diff options
context:
space:
mode:
Diffstat (limited to 'apps/fparkan-game/src')
-rw-r--r--apps/fparkan-game/src/main.rs647
1 files changed, 477 insertions, 170 deletions
diff --git a/apps/fparkan-game/src/main.rs b/apps/fparkan-game/src/main.rs
index 8a92599..d615aaf 100644
--- a/apps/fparkan-game/src/main.rs
+++ b/apps/fparkan-game/src/main.rs
@@ -739,7 +739,6 @@ impl MaterialPhaseBinding {
struct MaterialVariantSet {
phase_material_indices: Vec<usize>,
range_indices: Vec<usize>,
- pipeline_category: u8,
}
#[derive(Clone, Debug)]
@@ -753,7 +752,7 @@ struct MaterialAnimationBinding {
stage_textures: Option<MaterialStageTextures>,
}
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, PartialEq)]
struct ActiveMaterialState {
material_index: usize,
diffuse_alpha: f32,
@@ -772,19 +771,51 @@ fn selected_material_phase(
Ok((material_index, pipeline_category))
}
-fn update_material_animations(
+#[derive(Clone, Copy, Debug, PartialEq)]
+struct MaterialRangeSelection {
+ range_index: usize,
+ material_index: usize,
+ pipeline_category: u8,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+struct MaterialPropertiesUpdate {
+ material_index: usize,
+ diffuse_alpha: f32,
+ overlay_alpha: f32,
+ base_directional: [f32; 3],
+ base_additive: [f32; 3],
+ overlay_directional: Option<[f32; 3]>,
+ overlay_additive: Option<[f32; 3]>,
+ specular_rgb: [f32; 3],
+ specular_power: u8,
+ overlay_specular_rgb: Option<[f32; 3]>,
+ overlay_power: Option<u8>,
+ uv_transforms: [[f32; 4]; 4],
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+struct MaterialStagePhaseUpdate {
+ binding_index: usize,
+ material_index: usize,
+ phases: [usize; 3],
+}
+
+#[derive(Clone, Debug, Default, PartialEq)]
+struct MaterialFrameUpdate {
+ active_materials: HashMap<usize, ActiveMaterialState>,
+ range_selections: Vec<MaterialRangeSelection>,
+ properties: Vec<MaterialPropertiesUpdate>,
+ stage_phases: Vec<MaterialStagePhaseUpdate>,
+}
+
+fn sample_material_animations(
bindings: &mut [MaterialAnimationBinding],
materials: &[VulkanStaticMaterial],
- renderer: &mut VulkanSmokeRenderer,
clock_ms: u32,
-) -> Result<HashMap<usize, ActiveMaterialState>, String> {
- // Keep the resolved selector beside the draw range for this frame. The
- // renderer's material arrays are keyed by selector, so any later
- // environment pass that writes alpha or lighting must target the phase
- // variant selected above rather than the material that happened to be
- // active when the range was allocated.
- let mut active_materials = HashMap::new();
- for binding in bindings {
+) -> Result<MaterialFrameUpdate, String> {
+ let mut update = MaterialFrameUpdate::default();
+ for (binding_index, binding) in bindings.iter_mut().enumerate() {
let base_sample = binding.base.sample(binding.initial_phase_index, clock_ms)?;
let detail_sample = binding
.detail
@@ -802,7 +833,7 @@ fn update_material_animations(
.map(|overlay_detail| overlay_detail.sample(binding.initial_phase_index, clock_ms))
.transpose()?;
let mut updated_materials = HashSet::new();
- for variant in &mut binding.variants {
+ for variant in &binding.variants {
let (material_index, pipeline_category) =
selected_material_phase(variant, base_sample.phase_index, |index| {
materials
@@ -810,25 +841,19 @@ fn update_material_animations(
.map(|material| material.pipeline_category)
})?;
for &range_index in &variant.range_indices {
- active_materials.insert(
+ update.active_materials.insert(
range_index,
ActiveMaterialState {
material_index,
diffuse_alpha: base_sample.coefficients.opacity,
},
);
- renderer
- .set_draw_range_material(range_index, material_index)
- .map_err(|error| format!("update MAT0 material draw selector: {error}"))?;
- if variant.pipeline_category != pipeline_category {
- renderer
- .set_draw_range_material_category(range_index, pipeline_category)
- .map_err(|error| {
- format!("update MAT0 material pipeline category: {error}")
- })?;
- }
+ update.range_selections.push(MaterialRangeSelection {
+ range_index,
+ material_index,
+ pipeline_category,
+ });
}
- variant.pipeline_category = pipeline_category;
if !updated_materials.insert(material_index) {
continue;
}
@@ -852,87 +877,16 @@ fn update_material_animations(
overlay_coefficients.map(|coefficients| coefficients.additive_rgb),
)
};
- renderer
- .set_material_alphas(
- material_index,
- base_coefficients.opacity.clamp(0.0, 1.0),
- overlay_coefficients
- .map_or(0.0, |coefficients| coefficients.opacity.clamp(0.0, 1.0)),
- )
- .map_err(|error| format!("update MAT0 material alpha: {error}"))?;
- renderer
- .set_material_lighting_with_overlay(
+ if !material.lightmap_mode && binding.stage_textures.is_some() {
+ update.stage_phases.push(MaterialStagePhaseUpdate {
+ binding_index,
material_index,
- base_directional,
- base_additive,
- overlay_directional,
- overlay_additive,
- )
- .map_err(|error| format!("update MAT0 material lighting: {error}"))?;
- renderer
- .set_material_specular(
- material_index,
- base_coefficients.specular_rgb,
- base_coefficients.power,
- overlay_coefficients.map(|coefficients| coefficients.specular_rgb),
- overlay_coefficients.map(|coefficients| coefficients.power),
- )
- .map_err(|error| format!("update MAT0 material specular: {error}"))?;
-
- // Detail and overlay textures have independent MAT0 phase
- // selectors. Their descriptor images are updated only when a
- // selected base material sees a new stage tuple; this keeps the
- // steady-state frame path from idling Vulkan for every draw.
- if !material.lightmap_mode {
- if let Some(stage_textures) = binding.stage_textures.as_mut() {
- let stage_phases = [
+ phases: [
detail_sample.map_or(0, |sample| sample.phase_index),
overlay_sample.map_or(0, |sample| sample.phase_index),
overlay_detail_sample.map_or(0, |sample| sample.phase_index),
- ];
- if stage_textures
- .applied_material_phases
- .get(&material_index)
- .copied()
- != Some(stage_phases)
- {
- let detail_texture = stage_textures
- .detail
- .as_ref()
- .and_then(|textures| textures.get(stage_phases[0]))
- .cloned();
- let overlay_texture = stage_textures
- .overlay
- .as_ref()
- .and_then(|textures| textures.get(stage_phases[1]))
- .cloned();
- let overlay_detail_texture = stage_textures
- .overlay_detail
- .as_ref()
- .and_then(|textures| textures.get(stage_phases[2]))
- .cloned();
- if let Some(texture) = detail_texture.as_ref() {
- renderer
- .update_material_texture(material_index, 1, texture)
- .map_err(|error| format!("update MAT0 detail texture: {error}"))?;
- }
- if let Some(texture) = overlay_texture.as_ref() {
- renderer
- .update_material_texture(material_index, 2, texture)
- .map_err(|error| format!("update MAT0 overlay texture: {error}"))?;
- }
- if let Some(texture) = overlay_detail_texture.as_ref() {
- renderer
- .update_material_texture(material_index, 3, texture)
- .map_err(|error| {
- format!("update MAT0 overlay detail texture: {error}")
- })?;
- }
- stage_textures
- .applied_material_phases
- .insert(material_index, stage_phases);
- }
- }
+ ],
+ });
}
let overlay_uv = overlay_sample
.map(|sample| {
@@ -980,15 +934,113 @@ fn update_material_animations(
let base_uv = binding
.base
.checked_page_uv_transform(base_sample.phase_index)?;
- renderer
- .set_material_uv_transforms(
- material_index,
- [base_uv, detail_uv, overlay_uv, overlay_detail_uv],
- )
- .map_err(|error| format!("update MAT0 material Page transform: {error}"))?;
+ update.properties.push(MaterialPropertiesUpdate {
+ material_index,
+ diffuse_alpha: base_coefficients.opacity.clamp(0.0, 1.0),
+ overlay_alpha: overlay_coefficients
+ .map_or(0.0, |coefficients| coefficients.opacity.clamp(0.0, 1.0)),
+ base_directional,
+ base_additive,
+ overlay_directional,
+ overlay_additive,
+ specular_rgb: base_coefficients.specular_rgb,
+ specular_power: base_coefficients.power,
+ overlay_specular_rgb: overlay_coefficients
+ .map(|coefficients| coefficients.specular_rgb),
+ overlay_power: overlay_coefficients.map(|coefficients| coefficients.power),
+ uv_transforms: [base_uv, detail_uv, overlay_uv, overlay_detail_uv],
+ });
}
}
- Ok(active_materials)
+ Ok(update)
+}
+
+fn apply_material_frame_update(
+ update: &MaterialFrameUpdate,
+ bindings: &mut [MaterialAnimationBinding],
+ renderer: &mut VulkanSmokeRenderer,
+) -> Result<(), String> {
+ for selection in &update.range_selections {
+ renderer
+ .set_draw_range_material(selection.range_index, selection.material_index)
+ .map_err(|error| format!("update MAT0 material draw selector: {error}"))?;
+ renderer
+ .set_draw_range_material_category(selection.range_index, selection.pipeline_category)
+ .map_err(|error| format!("update MAT0 material pipeline category: {error}"))?;
+ }
+ for properties in &update.properties {
+ renderer
+ .set_material_alphas(
+ properties.material_index,
+ properties.diffuse_alpha,
+ properties.overlay_alpha,
+ )
+ .map_err(|error| format!("update MAT0 material alpha: {error}"))?;
+ renderer
+ .set_material_lighting_with_overlay(
+ properties.material_index,
+ properties.base_directional,
+ properties.base_additive,
+ properties.overlay_directional,
+ properties.overlay_additive,
+ )
+ .map_err(|error| format!("update MAT0 material lighting: {error}"))?;
+ renderer
+ .set_material_specular(
+ properties.material_index,
+ properties.specular_rgb,
+ properties.specular_power,
+ properties.overlay_specular_rgb,
+ properties.overlay_power,
+ )
+ .map_err(|error| format!("update MAT0 material specular: {error}"))?;
+ renderer
+ .set_material_uv_transforms(properties.material_index, properties.uv_transforms)
+ .map_err(|error| format!("update MAT0 material Page transform: {error}"))?;
+ }
+ for stage_update in &update.stage_phases {
+ let stage_textures = bindings
+ .get_mut(stage_update.binding_index)
+ .and_then(|binding| binding.stage_textures.as_mut())
+ .ok_or_else(|| "MAT0 stage texture binding disappeared".to_string())?;
+ if stage_textures
+ .applied_material_phases
+ .get(&stage_update.material_index)
+ .copied()
+ == Some(stage_update.phases)
+ {
+ continue;
+ }
+ let textures = [
+ stage_textures
+ .detail
+ .as_ref()
+ .and_then(|textures| textures.get(stage_update.phases[0])),
+ stage_textures
+ .overlay
+ .as_ref()
+ .and_then(|textures| textures.get(stage_update.phases[1])),
+ stage_textures
+ .overlay_detail
+ .as_ref()
+ .and_then(|textures| textures.get(stage_update.phases[2])),
+ ];
+ for (stage, texture) in textures.into_iter().enumerate() {
+ if let Some(texture) = texture {
+ renderer
+ .update_material_texture(stage_update.material_index, stage + 1, texture)
+ .map_err(|error| match stage {
+ 0 => format!("update MAT0 detail texture: {error}"),
+ 1 => format!("update MAT0 overlay texture: {error}"),
+ _ => format!("update MAT0 overlay detail texture: {error}"),
+ })?;
+ }
+ }
+ stage_textures
+ .applied_material_phases
+ .insert(stage_update.material_index, stage_update.phases);
+ }
+ Ok(())
}
fn native_world_transparent(range: &VulkanStaticDrawRange, diffuse_alpha: f32) -> bool {
@@ -1449,7 +1501,6 @@ fn append_environment_material(
variants: vec![MaterialVariantSet {
phase_material_indices,
range_indices: Vec::new(),
- pipeline_category: phases[0].pipeline_category,
}],
base: MaterialPhaseBinding {
document,
@@ -2678,23 +2729,17 @@ impl EnvironmentGpuScene {
fn update(
&mut self,
mesh: &mut VulkanStaticMesh,
- materials: &[VulkanStaticMaterial],
renderer: &mut VulkanSmokeRenderer,
+ material_update: &MaterialFrameUpdate,
sky_frame: &SkyFrame,
sky_mesh: &SkyMesh,
environment_frame: &EnvironmentFrame,
camera: &PreviewCameraFrame,
viewport: [f32; 2],
- material_clock_ms: u32,
terrain: &TerrainWorld,
sun_occlusion_range_indices: &[usize],
) -> Result<(), String> {
- let active_materials = update_material_animations(
- &mut self.material_animations,
- materials,
- renderer,
- material_clock_ms,
- )?;
+ let active_materials = &material_update.active_materials;
// Resolve the native glare once for the frame. CSky feeds the
// boosted primary color back into the light manager and uses the
// positive boost delta for both the screen-gradient horizon floor and
@@ -5033,7 +5078,6 @@ fn static_preview_terrain_base_materials(
variants: vec![MaterialVariantSet {
phase_material_indices,
range_indices: Vec::new(),
- pipeline_category: base_phases[initial_phase_index].1.pipeline_category,
}],
base: MaterialPhaseBinding {
document: base_document,
@@ -5184,7 +5228,6 @@ fn static_preview_component_materials(
variants: vec![MaterialVariantSet {
phase_material_indices,
range_indices: Vec::new(),
- pipeline_category: material.pipeline_category,
}],
base: MaterialPhaseBinding {
document: material.mat0.clone(),
@@ -5299,7 +5342,7 @@ fn append_static_preview_component_with_lightmaps(
// advances to another phase. The range's original selector identifies
// the base binding, while the lightmap selector identifies the secondary
// texture. Keep the generated phase family and the binding/variant it
- // belongs to so update_material_animations can rebind every frame.
+ // belongs to so the pending MAT0 packet can rebind every frame.
let mut lightmap_materials: HashMap<(usize, u8), (Vec<usize>, usize, usize)> = HashMap::new();
for range in component.draw_ranges {
let source_material_index = selector_remap
@@ -5331,7 +5374,6 @@ fn append_static_preview_component_with_lightmaps(
"static preview material selector {source_material_index} has no MAT0 animation binding"
)
})?;
- let initial_phase_index = bindings[binding_index].initial_phase_index;
let lightmap = lightmap_textures
.get(usize::from(range.lightmap_index))
.expect("lightmap presence checked above");
@@ -5371,16 +5413,6 @@ fn append_static_preview_component_with_lightmaps(
materials.push(lightmap_material);
phase_material_indices.push(usize::from(index));
}
- let initial_selector = phase_material_indices
- .get(initial_phase_index)
- .copied()
- .ok_or_else(|| "static preview lightmap phase table is empty".to_string())?;
- let pipeline_category = materials
- .get(initial_selector)
- .map(|material| material.pipeline_category)
- .ok_or_else(|| {
- "static preview lightmap material selector is unavailable".to_string()
- })?;
let bindings = material_animations.as_deref_mut().ok_or_else(|| {
"static preview lightmap animation storage is unavailable".to_string()
})?;
@@ -5388,7 +5420,6 @@ fn append_static_preview_component_with_lightmaps(
bindings[binding_index].variants.push(MaterialVariantSet {
phase_material_indices: phase_material_indices.clone(),
range_indices: Vec::new(),
- pipeline_category,
});
let value = (phase_material_indices, binding_index, variant_index);
lightmap_materials.insert(key, value.clone());
@@ -5580,6 +5611,8 @@ struct SceneFrameUpdate {
camera: PreviewCameraFrame,
viewport: [f32; 2],
aspect: f32,
+ material_update: MaterialFrameUpdate,
+ environment_material_update: Option<MaterialFrameUpdate>,
environment: Option<DynamicEnvironmentFrame>,
}
@@ -6123,34 +6156,6 @@ impl StaticVulkanApp {
if self.renderer.is_none() {
return Ok(None);
}
- let active_materials = {
- let renderer = self
- .renderer
- .as_mut()
- .ok_or_else(|| "native Vulkan renderer was not initialized".to_string())?;
- update_material_animations(
- &mut self.material_animations,
- &self.materials,
- renderer,
- self.material_time_ms,
- )?
- };
- {
- let renderer = self
- .renderer
- .as_mut()
- .ok_or_else(|| "native Vulkan renderer was not initialized".to_string())?;
- Self::update_world_transparency(
- &self.mesh,
- &self.materials,
- &self.world_range_indices,
- &self.world_range_index_counts,
- &self.native_handedness_by_range,
- self.view_camera.position,
- &active_materials,
- renderer,
- )?;
- }
let camera = self.view_camera;
if let Some(audio) = self.audio.as_mut() {
audio
@@ -6180,13 +6185,13 @@ impl StaticVulkanApp {
if let Some(frame) = environment_frame.as_ref() {
self.environment_primitive_count = frame.environment_frame.primitives.len();
}
- self.apply_environment_view(environment_frame.as_ref(), camera, viewport)?;
Ok(environment_frame)
}
fn apply_environment_view(
&mut self,
frame: Option<&DynamicEnvironmentFrame>,
+ environment_material_update: Option<&MaterialFrameUpdate>,
camera: PreviewCameraFrame,
viewport: [f32; 2],
) -> Result<(), String> {
@@ -6205,16 +6210,17 @@ impl StaticVulkanApp {
frame.absolute_time_seconds,
);
if let Some(environment_gpu) = self.environment_gpu.as_mut() {
+ let material_update = environment_material_update
+ .ok_or_else(|| "sampled environment material state is missing".to_string())?;
environment_gpu.update(
&mut self.mesh,
- &self.materials,
renderer,
+ material_update,
&sky_frame,
&frame.sky_mesh,
&frame.environment_frame,
&camera,
viewport,
- self.material_time_ms,
&self.terrain,
&self.sun_occlusion_range_indices,
)?;
@@ -6285,7 +6291,6 @@ impl StaticVulkanApp {
.environment_frame
.reproject_for_camera(environment_camera);
}
- self.apply_environment_view(update.environment.as_ref(), camera, viewport)?;
update.camera = camera;
update.viewport = viewport;
update.aspect = aspect;
@@ -6293,6 +6298,52 @@ impl StaticVulkanApp {
Ok(())
}
+ fn apply_pending_scene_update(&mut self) -> Result<(), String> {
+ let Some(frame) = self.lifecycle.pending.take() else {
+ return Ok(());
+ };
+ let result = (|| {
+ {
+ let Some(renderer) = self.renderer.as_mut() else {
+ return Err("native Vulkan renderer was not initialized".to_string());
+ };
+ apply_material_frame_update(
+ &frame.material_update,
+ &mut self.material_animations,
+ renderer,
+ )?;
+ Self::update_world_transparency(
+ &self.mesh,
+ &self.materials,
+ &self.world_range_indices,
+ &self.world_range_index_counts,
+ &self.native_handedness_by_range,
+ frame.camera.position,
+ &frame.material_update.active_materials,
+ renderer,
+ )?;
+ if let Some(material_update) = frame.environment_material_update.as_ref() {
+ let environment_gpu = self.environment_gpu.as_mut().ok_or_else(|| {
+ "sampled environment material state lost its owner".to_string()
+ })?;
+ apply_material_frame_update(
+ material_update,
+ &mut environment_gpu.material_animations,
+ renderer,
+ )?;
+ }
+ }
+ self.apply_environment_view(
+ frame.environment.as_ref(),
+ frame.environment_material_update.as_ref(),
+ frame.camera,
+ frame.viewport,
+ )
+ })();
+ self.lifecycle.pending = Some(frame);
+ result
+ }
+
fn sample_frame_clock(&mut self) -> FrameClockSample {
if let Some(target_ms) = self.control_clock_target_ms {
return self.lifecycle.sample_replay(target_ms, self.target_frames);
@@ -6324,12 +6375,30 @@ impl StaticVulkanApp {
self.update_free_camera(sample.delta_seconds, viewport)?;
self.update_material_clock(sample);
self.update_controlled_component_geometry(sample)?;
+ let material_update = sample_material_animations(
+ &mut self.material_animations,
+ &self.materials,
+ self.material_time_ms,
+ )?;
+ let environment_material_update = self
+ .environment_gpu
+ .as_mut()
+ .map(|environment_gpu| {
+ sample_material_animations(
+ &mut environment_gpu.material_animations,
+ &self.materials,
+ self.material_time_ms,
+ )
+ })
+ .transpose()?;
let environment = self.update_environment(sample, viewport)?;
Ok(SceneFrameUpdate {
sample,
camera: self.view_camera,
viewport,
aspect,
+ material_update,
+ environment_material_update,
environment,
})
}
@@ -6678,6 +6747,13 @@ impl ApplicationHandler for StaticVulkanApp {
let size = window.inner_size();
size.width != 0 && size.height != 0
});
+ if drawable_available {
+ if let Err(error) = self.apply_pending_scene_update() {
+ self.error = Some(error);
+ event_loop.exit();
+ return;
+ }
+ }
let outcome = {
let Some(renderer) = self.renderer.as_mut() else {
self.error = Some("native Vulkan renderer was not initialized".to_string());
@@ -7080,6 +7156,168 @@ fn usage() -> String {
mod tests {
use super::*;
use fparkan_fx::environment::{WeatherInput, WeatherState};
+ use fparkan_material::{MaterialAnimationBlock, MaterialKey};
+
+ fn material_test_phase(parameters: [u8; 18]) -> MaterialPhase {
+ MaterialPhase {
+ parameters,
+ texture_raw: [0; 16],
+ }
+ }
+
+ fn random_material_binding(range_index: usize) -> MaterialAnimationBinding {
+ let mut overlay_phase = [0; 18];
+ overlay_phase[..3].copy_from_slice(&[100, 50, 25]);
+ overlay_phase[3] = 25;
+ overlay_phase[4..7].copy_from_slice(&[30, 60, 90]);
+ overlay_phase[8..11].copy_from_slice(&[40, 80, 120]);
+ overlay_phase[16] = 5;
+ let mut active_phase = [0; 18];
+ active_phase[..3].copy_from_slice(&[25, 50, 75]);
+ active_phase[3] = 75;
+ active_phase[4..7].copy_from_slice(&[64, 128, 192]);
+ active_phase[8..11].copy_from_slice(&[100, 150, 200]);
+ active_phase[16] = 9;
+ active_phase[17] = 1;
+ let document = Mat0Document {
+ version: 0,
+ animation_block_count: 1,
+ phases: vec![
+ material_test_phase(overlay_phase),
+ material_test_phase(active_phase),
+ ],
+ prefix: Vec::new(),
+ header_opaque: [0; 2],
+ animation_blocks: vec![MaterialAnimationBlock {
+ header_raw: 3,
+ keys: vec![
+ MaterialKey {
+ k0: 0,
+ k1: 20,
+ k2: 0,
+ },
+ MaterialKey {
+ k0: 1,
+ k1: 40,
+ k2: 0,
+ },
+ ],
+ bytes: Vec::new(),
+ }],
+ };
+ let frozen_binding = |phase_index: usize, uv_transform: [f32; 4]| MaterialPhaseBinding {
+ document: document.clone(),
+ animation_block_index: 0,
+ wear_row_start_ms: 0,
+ random_state: 0,
+ frozen_phase_index: Some(phase_index),
+ phase_uv_transforms: vec![uv_transform; 2],
+ };
+ let texture = solid_environment_texture([255; 4]);
+ MaterialAnimationBinding {
+ initial_phase_index: 0,
+ variants: vec![MaterialVariantSet {
+ phase_material_indices: vec![4, 5],
+ range_indices: vec![range_index],
+ }],
+ base: MaterialPhaseBinding {
+ document: document.clone(),
+ animation_block_index: 0,
+ wear_row_start_ms: 0,
+ random_state: 0,
+ frozen_phase_index: None,
+ phase_uv_transforms: vec![[0.0, 0.0, 0.5, 0.5], [0.5, 0.25, 0.25, 0.5]],
+ },
+ detail: Some(frozen_binding(1, [0.1, 0.2, 0.3, 0.4])),
+ overlay: Some(frozen_binding(0, [0.3, 0.4, 0.5, 0.6])),
+ overlay_detail: Some(frozen_binding(1, [0.6, 0.7, 0.8, 0.9])),
+ stage_textures: Some(MaterialStageTextures {
+ detail: Some(vec![texture.clone(), texture.clone()]),
+ overlay: Some(vec![texture.clone()]),
+ overlay_detail: Some(vec![texture.clone(), texture]),
+ applied_material_phases: HashMap::from([(5, [1, 0, 1])]),
+ }),
+ }
+ }
+
+ fn packet_test_materials() -> Vec<VulkanStaticMaterial> {
+ let mut materials = Vec::new();
+ for _ in 0..6 {
+ append_environment_gradient_material(&mut materials).expect("test material");
+ }
+ materials[4].pipeline_category = 1;
+ materials[5].pipeline_category = 3;
+ materials
+ }
+
+ #[test]
+ fn sampled_material_packet_keeps_native_selector_properties_and_stage_phases() {
+ let materials = packet_test_materials();
+ let mut bindings = vec![random_material_binding(7)];
+ let packet = sample_material_animations(&mut bindings, &materials, 500)
+ .expect("sample random MAT0 state");
+
+ assert_eq!(
+ packet.range_selections,
+ vec![MaterialRangeSelection {
+ range_index: 7,
+ material_index: 5,
+ pipeline_category: 3,
+ }]
+ );
+ assert_eq!(
+ packet.active_materials.get(&7),
+ Some(&ActiveMaterialState {
+ material_index: 5,
+ diffuse_alpha: 0.75,
+ })
+ );
+ let properties = packet.properties[0];
+ assert_eq!(
+ (properties.diffuse_alpha, properties.overlay_alpha),
+ (0.75, 0.25)
+ );
+ assert_eq!(
+ (properties.base_directional, properties.base_additive),
+ (
+ [64.0 / 255.0, 128.0 / 255.0, 192.0 / 255.0],
+ [25.0 / 255.0, 50.0 / 255.0, 75.0 / 255.0],
+ )
+ );
+ assert_eq!(
+ (properties.overlay_directional, properties.overlay_additive),
+ (
+ Some([30.0 / 255.0, 60.0 / 255.0, 90.0 / 255.0]),
+ Some([100.0 / 255.0, 50.0 / 255.0, 25.0 / 255.0]),
+ )
+ );
+ assert_eq!(
+ (properties.specular_rgb, properties.specular_power),
+ ([100.0 / 255.0, 150.0 / 255.0, 200.0 / 255.0], 9)
+ );
+ assert_eq!(
+ (properties.overlay_specular_rgb, properties.overlay_power),
+ (Some([40.0 / 255.0, 80.0 / 255.0, 120.0 / 255.0]), Some(5))
+ );
+ assert_eq!(
+ properties.uv_transforms,
+ [
+ [0.5, 0.25, 0.25, 0.5],
+ [0.1, 0.2, 0.3, 0.4],
+ [0.3, 0.4, 0.5, 0.6],
+ [0.6, 0.7, 0.8, 0.9],
+ ]
+ );
+ assert_eq!(
+ packet.stage_phases,
+ vec![MaterialStagePhaseUpdate {
+ binding_index: 0,
+ material_index: 5,
+ phases: [1, 0, 1],
+ }]
+ );
+ assert_eq!(bindings[0].base.random_state, 2_531_011);
+ }
fn control_pose_test_model() -> ModelAsset {
let mut nodes = vec![u8::MAX; 38];
@@ -7482,6 +7720,18 @@ mod tests {
);
app.free_camera = Some(free_camera);
app.pressed_keys.insert(KeyCode::KeyW);
+ app.materials = packet_test_materials();
+ app.material_animations = vec![random_material_binding(0)];
+ let empty_range = app.shadow_range;
+ app.environment_gpu = Some(EnvironmentGpuScene {
+ material_animations: vec![random_material_binding(1)],
+ sky_layers: Vec::new(),
+ sprites: Vec::new(),
+ rain: empty_range,
+ snow: empty_range,
+ lightning: empty_range,
+ lightning_material_index: 0,
+ });
let start = app.last_tick;
let sample = app.sample_live_frame_clock_at(start + Duration::from_millis(225));
assert_eq!(sample.time_ms, 225);
@@ -7489,6 +7739,17 @@ mod tests {
let mut update = app
.prepare_scene_update(sample, [1280.0, 720.0])
.expect("apply one full live tick to the scene");
+ assert!(update.environment.is_none());
+ assert!(update.environment_material_update.is_some());
+ let world_material_packet = update.material_update.clone();
+ let sky_material_packet = update.environment_material_update.clone();
+ assert_eq!(app.material_animations[0].base.random_state, 2_531_011);
+ assert_eq!(
+ app.environment_gpu.as_ref().unwrap().material_animations[0]
+ .base
+ .random_state,
+ 2_531_011
+ );
let expected_resized_camera = app
.free_camera
.expect("free-flight input controller")
@@ -7560,8 +7821,37 @@ mod tests {
let controller_after_input = app.free_camera.unwrap();
app.refresh_pending_viewport([800.0, 600.0])
.expect("refresh only viewport-dependent state after recreation");
- let refreshed = app.lifecycle.pending.as_ref().expect("pending sample");
+ let refreshed = app
+ .lifecycle
+ .pending
+ .as_ref()
+ .expect("pending sample")
+ .clone();
assert_eq!(refreshed.sample, sample);
+ assert_eq!(refreshed.material_update, world_material_packet);
+ assert_eq!(refreshed.environment_material_update, sky_material_packet);
+ assert_eq!(app.material_animations[0].base.random_state, 2_531_011);
+ assert_eq!(
+ app.environment_gpu.as_ref().unwrap().material_animations[0]
+ .base
+ .random_state,
+ 2_531_011
+ );
+ app.refresh_pending_viewport([1024.0, 768.0])
+ .expect("refresh the same pending sample a second time");
+ let repeated = app.lifecycle.pending.as_ref().expect("pending sample");
+ assert_eq!(repeated.sample, sample);
+ assert_eq!(repeated.material_update, world_material_packet);
+ assert_eq!(repeated.environment_material_update, sky_material_packet);
+ assert_eq!(repeated.viewport, [1024.0, 768.0]);
+ assert_eq!(repeated.aspect, 4.0 / 3.0);
+ assert_eq!(app.material_animations[0].base.random_state, 2_531_011);
+ assert_eq!(
+ app.environment_gpu.as_ref().unwrap().material_animations[0]
+ .base
+ .random_state,
+ 2_531_011
+ );
assert_eq!(refreshed.viewport, [800.0, 600.0]);
assert_eq!(refreshed.aspect, 4.0 / 3.0);
assert_eq!(refreshed.camera.position, update.camera.position);
@@ -7635,6 +7925,25 @@ mod tests {
reprojected_counts[slot] += 1;
}
assert!(reprojected_counts.iter().all(|count| *count > 0));
+ assert_eq!(
+ app.apply_pending_scene_update().unwrap_err(),
+ "native Vulkan renderer was not initialized"
+ );
+ let restored = app
+ .lifecycle
+ .pending
+ .as_ref()
+ .expect("restored pending sample");
+ assert_eq!(restored.sample, sample);
+ assert_eq!(restored.material_update, world_material_packet);
+ assert_eq!(restored.environment_material_update, sky_material_packet);
+ assert_eq!(app.material_animations[0].base.random_state, 2_531_011);
+ assert_eq!(
+ app.environment_gpu.as_ref().unwrap().material_animations[0]
+ .base
+ .random_state,
+ 2_531_011
+ );
assert_eq!(app.mesh.vertices, b_vertices);
assert_eq!(app.shadow_casters[0].triangles, b_triangles);
assert_eq!(
@@ -7710,6 +8019,8 @@ mod tests {
camera,
viewport,
aspect,
+ material_update: MaterialFrameUpdate::default(),
+ environment_material_update: None,
environment: None,
}
}
@@ -7866,7 +8177,6 @@ mod tests {
variants: vec![MaterialVariantSet {
phase_material_indices: vec![10],
range_indices: vec![0, 2],
- pipeline_category: 0,
}],
base: empty_binding(),
detail: None,
@@ -7927,7 +8237,6 @@ mod tests {
let variant = MaterialVariantSet {
phase_material_indices: vec![10, 11],
range_indices: vec![3],
- pipeline_category: 1,
};
let category_for = |material_index| match material_index {
10 => Some(1),
@@ -8195,7 +8504,6 @@ mod tests {
variants: vec![MaterialVariantSet {
phase_material_indices: vec![10, 11],
range_indices: Vec::new(),
- pipeline_category: 0,
}],
base: MaterialPhaseBinding {
document: empty_mat0,
@@ -8216,7 +8524,6 @@ mod tests {
bindings[binding_index].variants.push(MaterialVariantSet {
phase_material_indices: vec![20, 21],
range_indices: vec![7],
- pipeline_category: 0,
});
let lightmap_variant = &bindings[binding_index].variants[1];