#![cfg(feature = "cross-parity")]
use std::sync::Arc;
use std::time::Duration;
use rustial_engine::{
build_terrain_mesh, compute_rmse, count_differing_pixels, differing_pixel_fraction,
prepare_hillshade_raster, CameraProjection, DecodedImage, HillshadeLayer, MapState,
TerrainMeshData, TileData, VisibleTile,
};
use rustial_engine::{tile_bounds_world, ElevationGrid, GeoCoord, TileId, WebMercator, WorldCoord};
const WIDTH: u32 = 128;
const HEIGHT: u32 = 128;
fn build_canonical_scene() -> Option<(MapState, Vec<VisibleTile>)> {
let tile = TileId::new(5, 16, 16);
let bounds = tile_bounds_world(&tile);
let center_world = WorldCoord::new(
(bounds.min.position.x + bounds.max.position.x) * 0.5,
(bounds.min.position.y + bounds.max.position.y) * 0.5,
0.0,
);
let center_geo = WebMercator::unproject(¢er_world);
let mut state = MapState::new();
state.set_viewport(WIDTH, HEIGHT);
state.set_camera_target(center_geo);
state.set_camera_distance(1_700_000.0);
state.set_camera_pitch(55_f64.to_radians());
state.set_camera_yaw(15_f64.to_radians());
state.update_camera(1.0 / 60.0);
let mut pixel_data = vec![0u8; 256 * 256 * 4];
for (i, pixel) in pixel_data.chunks_exact_mut(4).enumerate() {
let x = (i % 256) as u8;
let y = (i / 256) as u8;
pixel[0] = 80u8.saturating_add(x / 4);
pixel[1] = 120u8.saturating_add(y / 3);
pixel[2] = 60u8.saturating_add((x ^ y) & 31);
pixel[3] = 255;
}
let visible_tiles = vec![VisibleTile {
target: tile,
actual: tile,
data: Some(TileData::Raster(DecodedImage {
width: 256,
height: 256,
data: Arc::new(pixel_data),
})),
fade_opacity: 1.0,
}];
state.set_visible_tiles(visible_tiles.clone());
let elevation = ElevationGrid::from_data(
tile,
4,
4,
vec![
0.0, 15_000.0, 45_000.0, 80_000.0, 8_000.0, 35_000.0, 70_000.0, 110_000.0, 20_000.0,
50_000.0, 100_000.0, 140_000.0, 35_000.0, 70_000.0, 130_000.0, 180_000.0,
],
)?;
let terrain_mesh: TerrainMeshData = build_terrain_mesh(
&tile,
&elevation,
CameraProjection::WebMercator,
8,
1.0,
0.0,
1,
);
state.set_terrain_meshes(vec![terrain_mesh]);
state.push_layer(Box::new(HillshadeLayer::new("hillshade")));
state.set_hillshade_rasters(vec![prepare_hillshade_raster(&elevation, 1.0, 1)]);
Some((state, visible_tiles))
}
fn build_point_cloud_scene(updated: bool, off_origin: bool) -> (MapState, Vec<VisibleTile>) {
let mut state = MapState::new();
state.set_viewport(WIDTH, HEIGHT);
let target = if off_origin {
GeoCoord::from_lat_lon(0.0, 170.0)
} else {
GeoCoord::from_lat_lon(0.0, 0.0)
};
state.set_camera_target(target);
state.set_camera_distance(2_500.0);
state.set_camera_pitch(35_f64.to_radians());
state.set_camera_yaw(22_f64.to_radians());
state.update_camera(1.0 / 60.0);
let offsets = [-0.004, -0.002, 0.0, 0.002, 0.004];
let mut points = Vec::new();
for (row, lat_offset) in offsets.iter().enumerate() {
for (col, lon_offset) in offsets.iter().enumerate() {
let idx = row * offsets.len() + col;
let radius = if updated && idx % 2 == 0 { 30.0 } else { 18.0 };
let intensity = if updated {
if idx % 2 == 0 {
1.0
} else {
0.15
}
} else if idx % 2 == 0 {
0.2
} else {
0.85
};
let mut point = rustial_engine::PointInstance::new(
GeoCoord::from_lat_lon(target.lat + lat_offset, target.lon + lon_offset),
radius,
)
.with_pick_id(idx as u64 + 1)
.with_intensity(intensity);
if idx % 3 == 0 {
point = point.with_color([0.95, 0.85, 0.2, 0.9]);
}
points.push(point);
}
}
state.set_point_cloud(
"points",
rustial_engine::PointInstanceSet::new(points),
rustial_engine::ColorRamp::new(vec![
rustial_engine::ColorStop {
value: 0.0,
color: [0.1, 0.2, 0.9, 0.5],
},
rustial_engine::ColorStop {
value: 0.5,
color: [0.2, 0.9, 0.8, 0.8],
},
rustial_engine::ColorStop {
value: 1.0,
color: [0.95, 0.2, 0.1, 0.95],
},
]),
);
state.update();
(state, Vec::new())
}
fn render_wgpu(state: &MapState, visible_tiles: &[VisibleTile]) -> Option<Vec<u8>> {
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
compatible_surface: None,
force_fallback_adapter: false,
}))
.ok()?;
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
label: Some("cross_parity_wgpu_device"),
..Default::default()
}))
.ok()?;
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer =
rustial_renderer_wgpu::WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
renderer.render_to_buffer(
state,
&device,
&queue,
visible_tiles,
state.vector_meshes(),
state.model_instances(),
)
}
use bevy::app::{App, AppExit, ScheduleRunnerPlugin};
use bevy::asset::{Assets, RenderAssetUsages};
use bevy::image::Image;
use bevy::prelude::*;
use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages};
use bevy::render::settings::{RenderCreation, WgpuSettings};
use bevy::render::RenderPlugin;
use rustial_renderer_bevy::components::MapCamera;
use rustial_renderer_bevy::{MapStateResource, RustialBevyConfig, RustialBevyPlugin};
#[derive(Resource)]
struct BevyRenderTargetHandle(Handle<Image>);
#[derive(Resource, Default)]
struct BevyCapturedPixels(Option<Vec<u8>>);
#[derive(Resource)]
struct BevyFrameCounter(u32);
const SETTLE_FRAMES: u32 = 8;
const MAX_FRAMES: u32 = 14;
fn setup_bevy_render_target(
mut commands: Commands,
mut images: ResMut<Assets<Image>>,
camera_entities: Query<Entity, With<MapCamera>>,
) {
let size = Extent3d {
width: WIDTH,
height: HEIGHT,
depth_or_array_layers: 1,
};
let mut image = Image::new_fill(
size,
TextureDimension::D2,
&[0, 0, 0, 255],
TextureFormat::bevy_default(),
RenderAssetUsages::default(),
);
image.texture_descriptor.usage = TextureUsages::TEXTURE_BINDING
| TextureUsages::COPY_DST
| TextureUsages::COPY_SRC
| TextureUsages::RENDER_ATTACHMENT;
let handle = images.add(image);
for entity in camera_entities.iter() {
commands
.entity(entity)
.insert(bevy::camera::RenderTarget::Image(
bevy::camera::ImageRenderTarget {
handle: handle.clone(),
scale_factor: 1.0,
},
));
}
commands.insert_resource(BevyRenderTargetHandle(handle));
}
fn bevy_tick_and_capture(
mut counter: ResMut<BevyFrameCounter>,
images: Res<Assets<Image>>,
target: Option<Res<BevyRenderTargetHandle>>,
mut captured: ResMut<BevyCapturedPixels>,
mut exit: MessageWriter<AppExit>,
) {
counter.0 += 1;
if counter.0 >= SETTLE_FRAMES {
if let Some(ref target) = target {
if let Some(image) = images.get(&target.0) {
if let Some(ref data) = image.data {
if !data.is_empty() {
captured.0 = Some(data.clone());
}
}
}
}
}
if captured.0.is_some() || counter.0 >= MAX_FRAMES {
exit.write(AppExit::Success);
}
}
fn render_bevy(state: MapState) -> Option<Vec<u8>> {
let mut app = App::new();
app.add_plugins(
DefaultPlugins
.set(WindowPlugin {
primary_window: None,
..default()
})
.set(RenderPlugin {
render_creation: RenderCreation::Automatic(WgpuSettings {
backends: Some(bevy::render::settings::Backends::all()),
..default()
}),
..default()
})
.set(bevy::image::ImagePlugin::default()),
);
app.add_plugins(ScheduleRunnerPlugin::run_loop(Duration::from_millis(16)));
app.insert_resource(RustialBevyConfig {
viewport: (WIDTH, HEIGHT),
..default()
});
app.add_plugins(RustialBevyPlugin);
app.insert_resource(MapStateResource(state));
app.insert_resource(BevyFrameCounter(0));
app.init_resource::<BevyCapturedPixels>();
app.add_systems(PostStartup, setup_bevy_render_target);
app.add_systems(Update, bevy_tick_and_capture);
app.run();
let captured = app.world().resource::<BevyCapturedPixels>();
captured.0.clone()
}
#[test]
fn cross_renderer_produces_structurally_equivalent_output() {
if cfg!(target_os = "windows") {
eprintln!("Skipping cross-parity test on Windows: the Bevy headless event loop must run on the main thread");
return;
}
let Some((state, visible_tiles)) = build_canonical_scene() else {
eprintln!("Skipping cross-parity test: could not build canonical scene");
return;
};
let wgpu_pixels = match render_wgpu(&state, &visible_tiles) {
Some(pixels) => pixels,
None => {
eprintln!(
"Skipping cross-parity test: WGPU headless render failed \
(no suitable GPU adapter or headless rendering unsupported)"
);
return;
}
};
assert!(
wgpu_pixels.iter().any(|&b| b != 0),
"WGPU render produced all-zero output"
);
let first = &wgpu_pixels[0..4];
assert!(
!wgpu_pixels.chunks_exact(4).all(|p| p == first),
"WGPU render produced uniform output"
);
let Some((bevy_state, _)) = build_canonical_scene() else {
eprintln!("Skipping cross-parity test: could not build canonical scene for Bevy");
return;
};
let bevy_pixels = match render_bevy(bevy_state) {
Some(pixels) => pixels,
None => {
eprintln!(
"Skipping cross-parity test: Bevy headless render failed \
(no suitable GPU adapter or headless rendering unsupported)"
);
return;
}
};
assert!(
bevy_pixels.iter().any(|&b| b != 0),
"Bevy render produced all-zero output"
);
if bevy_pixels.len() >= 8 {
let first_bevy = &bevy_pixels[0..4];
assert!(
!bevy_pixels.chunks_exact(4).all(|p| p == first_bevy),
"Bevy render produced uniform output"
);
}
let wgpu_len = wgpu_pixels.len();
let bevy_len = bevy_pixels.len();
let expected_len = (WIDTH * HEIGHT * 4) as usize;
if wgpu_len != expected_len {
eprintln!(
"WGPU buffer size mismatch: expected {expected_len}, got {wgpu_len}. \
Skipping pixel comparison."
);
return;
}
if bevy_len != expected_len {
eprintln!(
"Bevy buffer size mismatch: expected {expected_len}, got {bevy_len}. \
This may indicate a different internal format or DPI scaling. \
Skipping pixel comparison."
);
return;
}
let rmse = compute_rmse(&wgpu_pixels, &bevy_pixels);
let diff_frac = differing_pixel_fraction(&wgpu_pixels, &bevy_pixels, 10);
let diff_count = count_differing_pixels(&wgpu_pixels, &bevy_pixels, 10);
let total_pixels = (WIDTH * HEIGHT) as usize;
eprintln!("=== Cross-renderer parity results ===");
eprintln!(" Image size: {WIDTH}x{HEIGHT} ({total_pixels} pixels)");
eprintln!(" RMSE: {rmse:.4}");
eprintln!(
" Differing pixels (threshold=10): {diff_count}/{total_pixels} ({:.1}%)",
diff_frac * 100.0
);
assert!(
rmse < 40.0,
"RMSE between WGPU and Bevy outputs is too high: {rmse:.4} (threshold: 40.0). \
This indicates a significant structural difference beyond tonemapping."
);
if rmse > 5.0 {
eprintln!(
" NOTE: RMSE {rmse:.4} exceeds the ideal threshold of 5.0. \
This is expected due to Bevy's PBR tonemapping (TonyMcMapface) \
vs WGPU's linear-space output. Accepted deviation."
);
}
if diff_frac > 0.15 {
eprintln!(
" NOTE: {:.1}% of pixels differ (threshold=10). \
This is expected due to Bevy's PBR pipeline differences. \
Accepted deviation.",
diff_frac * 100.0
);
}
eprintln!("=== Cross-renderer parity test PASSED ===");
}
#[test]
fn both_renderers_produce_non_trivial_output() {
if cfg!(target_os = "windows") {
eprintln!("Skipping both-renderers test on Windows: the Bevy headless event loop must run on the main thread");
return;
}
let Some((state, visible_tiles)) = build_canonical_scene() else {
eprintln!("Skipping: could not build canonical scene");
return;
};
if let Some(pixels) = render_wgpu(&state, &visible_tiles) {
assert!(pixels.iter().any(|&b| b != 0), "WGPU: all-zero output");
let first = &pixels[0..4];
assert!(
!pixels.chunks_exact(4).all(|p| p == first),
"WGPU: uniform output"
);
eprintln!("WGPU headless render: OK ({} bytes)", pixels.len());
} else {
eprintln!("WGPU headless render: skipped (no GPU adapter)");
}
let Some((bevy_state, _)) = build_canonical_scene() else {
eprintln!("Skipping Bevy: could not build canonical scene");
return;
};
if let Some(pixels) = render_bevy(bevy_state) {
assert!(pixels.iter().any(|&b| b != 0), "Bevy: all-zero output");
if pixels.len() >= 8 {
let first = &pixels[0..4];
assert!(
!pixels.chunks_exact(4).all(|p| p == first),
"Bevy: uniform output"
);
}
eprintln!("Bevy headless render: OK ({} bytes)", pixels.len());
} else {
eprintln!("Bevy headless render: skipped (no GPU adapter)");
}
}
#[test]
fn point_cloud_cross_renderer_produces_structurally_equivalent_output() {
if cfg!(target_os = "windows") {
eprintln!("Skipping point-cloud cross-parity test on Windows: the Bevy headless event loop must run on the main thread");
return;
}
let (state, visible_tiles) = build_point_cloud_scene(false, false);
let wgpu_pixels = match render_wgpu(&state, &visible_tiles) {
Some(pixels) => pixels,
None => {
eprintln!("Skipping point-cloud cross-parity test: WGPU headless render failed");
return;
}
};
let (bevy_state, _) = build_point_cloud_scene(false, false);
let bevy_pixels = match render_bevy(bevy_state) {
Some(pixels) => pixels,
None => {
eprintln!("Skipping point-cloud cross-parity test: Bevy headless render failed");
return;
}
};
let expected_len = (WIDTH * HEIGHT * 4) as usize;
if wgpu_pixels.len() != expected_len || bevy_pixels.len() != expected_len {
eprintln!(
"Skipping point-cloud cross-parity pixel comparison: unexpected buffer sizes wgpu={} bevy={} expected={expected_len}",
wgpu_pixels.len(),
bevy_pixels.len()
);
return;
}
let rmse = compute_rmse(&wgpu_pixels, &bevy_pixels);
let diff_frac = differing_pixel_fraction(&wgpu_pixels, &bevy_pixels, 10);
assert!(
rmse < 40.0,
"PointCloud cross-renderer RMSE too high: {rmse:.4}"
);
assert!(
diff_frac < 0.95,
"PointCloud differing fraction too high: {:.2}%",
diff_frac * 100.0
);
}
#[test]
fn point_cloud_cross_renderer_value_update_changes_both_outputs() {
if cfg!(target_os = "windows") {
eprintln!("Skipping point-cloud cross-parity update test on Windows: the Bevy headless event loop must run on the main thread");
return;
}
let (wgpu_initial_state, visible_tiles) = build_point_cloud_scene(false, false);
let (wgpu_updated_state, _) = build_point_cloud_scene(true, false);
let wgpu_initial = match render_wgpu(&wgpu_initial_state, &visible_tiles) {
Some(pixels) => pixels,
None => {
eprintln!("Skipping point-cloud cross-parity update test: WGPU initial render failed");
return;
}
};
let wgpu_updated = match render_wgpu(&wgpu_updated_state, &visible_tiles) {
Some(pixels) => pixels,
None => {
eprintln!("Skipping point-cloud cross-parity update test: WGPU updated render failed");
return;
}
};
let bevy_initial = match render_bevy(build_point_cloud_scene(false, false).0) {
Some(pixels) => pixels,
None => {
eprintln!("Skipping point-cloud cross-parity update test: Bevy initial render failed");
return;
}
};
let bevy_updated = match render_bevy(build_point_cloud_scene(true, false).0) {
Some(pixels) => pixels,
None => {
eprintln!("Skipping point-cloud cross-parity update test: Bevy updated render failed");
return;
}
};
assert!(compute_rmse(&wgpu_initial, &wgpu_updated) > 1.0);
assert!(compute_rmse(&bevy_initial, &bevy_updated) > 1.0);
}
fn build_grid_extrusion_scene(updated: bool) -> (MapState, Vec<VisibleTile>) {
let mut state = MapState::new();
state.set_viewport(WIDTH, HEIGHT);
let target = GeoCoord::from_lat_lon(0.0, 0.0);
state.set_camera_target(target);
state.set_camera_distance(5_000.0);
state.set_camera_pitch(45_f64.to_radians());
state.set_camera_yaw(20_f64.to_radians());
state.update_camera(1.0 / 60.0);
let values = if updated {
vec![
8.0, 7.0, 6.0, 5.0, 7.0, 6.0, 5.0, 4.0, 6.0, 5.0, 4.0, 3.0, 5.0, 4.0, 3.0, 2.0,
]
} else {
vec![
0.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 4.0, 2.0, 3.0, 4.0, 5.0, 3.0, 4.0, 5.0, 6.0,
]
};
let field = rustial_engine::ScalarField2D::from_data(4, 4, values);
state.set_grid_extrusion(
"extrusion",
rustial_engine::GeoGrid::new(target, 4, 4, 200.0, 200.0),
field,
rustial_engine::ColorRamp::new(vec![
rustial_engine::ColorStop {
value: 0.0,
color: [0.1, 0.3, 0.9, 0.8],
},
rustial_engine::ColorStop {
value: 1.0,
color: [0.95, 0.15, 0.1, 0.95],
},
]),
rustial_engine::ExtrusionParams {
height_scale: 100.0,
base_meters: 0.0,
},
);
state.update();
(state, Vec::new())
}
#[test]
fn grid_extrusion_cross_renderer_produces_structurally_equivalent_output() {
if cfg!(target_os = "windows") {
eprintln!("Skipping grid-extrusion cross-parity test on Windows: Bevy headless event loop requires main thread");
return;
}
let (state, visible_tiles) = build_grid_extrusion_scene(false);
let wgpu_pixels = match render_wgpu(&state, &visible_tiles) {
Some(p) => p,
None => {
eprintln!("Skipping: WGPU render failed");
return;
}
};
let (bevy_state, _) = build_grid_extrusion_scene(false);
let bevy_pixels = match render_bevy(bevy_state) {
Some(p) => p,
None => {
eprintln!("Skipping: Bevy render failed");
return;
}
};
let expected_len = (WIDTH * HEIGHT * 4) as usize;
if wgpu_pixels.len() != expected_len || bevy_pixels.len() != expected_len {
eprintln!("Skipping pixel comparison: buffer size mismatch");
return;
}
let rmse = compute_rmse(&wgpu_pixels, &bevy_pixels);
assert!(
rmse < 40.0,
"GridExtrusion cross-renderer RMSE too high: {rmse:.4}"
);
}
#[test]
fn grid_extrusion_cross_renderer_value_update_changes_both_outputs() {
if cfg!(target_os = "windows") {
eprintln!("Skipping grid-extrusion cross-parity update test on Windows");
return;
}
let (wgpu_initial_state, visible_tiles) = build_grid_extrusion_scene(false);
let (wgpu_updated_state, _) = build_grid_extrusion_scene(true);
let wgpu_initial = match render_wgpu(&wgpu_initial_state, &visible_tiles) {
Some(p) => p,
None => {
eprintln!("Skipping: WGPU initial failed");
return;
}
};
let wgpu_updated = match render_wgpu(&wgpu_updated_state, &visible_tiles) {
Some(p) => p,
None => {
eprintln!("Skipping: WGPU updated failed");
return;
}
};
let bevy_initial = match render_bevy(build_grid_extrusion_scene(false).0) {
Some(p) => p,
None => {
eprintln!("Skipping: Bevy initial failed");
return;
}
};
let bevy_updated = match render_bevy(build_grid_extrusion_scene(true).0) {
Some(p) => p,
None => {
eprintln!("Skipping: Bevy updated failed");
return;
}
};
assert!(compute_rmse(&wgpu_initial, &wgpu_updated) > 1.0);
assert!(compute_rmse(&bevy_initial, &bevy_updated) > 1.0);
}
fn build_column_scene(updated: bool) -> (MapState, Vec<VisibleTile>) {
let mut state = MapState::new();
state.set_viewport(WIDTH, HEIGHT);
let target = GeoCoord::from_lat_lon(0.0, 0.0);
state.set_camera_target(target);
state.set_camera_distance(3_500.0);
state.set_camera_pitch(40_f64.to_radians());
state.set_camera_yaw(18_f64.to_radians());
state.update_camera(1.0 / 60.0);
let offsets = [-0.005, -0.0025, 0.0, 0.0025, 0.005];
let mut columns = Vec::new();
for (row, &lat_off) in offsets.iter().enumerate() {
for (col, &lon_off) in offsets.iter().enumerate() {
let idx = row * offsets.len() + col;
let height = if updated {
((idx as f64 + 3.0) * 22.0).min(500.0)
} else {
((idx as f64 + 1.0) * 15.0).min(400.0)
};
let mut c = rustial_engine::ColumnInstance::new(
GeoCoord::from_lat_lon(target.lat + lat_off, target.lon + lon_off),
height,
40.0,
)
.with_pick_id(idx as u64 + 1);
if idx % 3 == 0 {
c = c.with_color([0.9, 0.8, 0.15, 0.9]);
}
columns.push(c);
}
}
state.set_instanced_columns(
"columns",
rustial_engine::ColumnInstanceSet::new(columns),
rustial_engine::ColorRamp::new(vec![
rustial_engine::ColorStop {
value: 0.0,
color: [0.1, 0.6, 0.2, 0.7],
},
rustial_engine::ColorStop {
value: 0.5,
color: [0.9, 0.9, 0.1, 0.85],
},
rustial_engine::ColorStop {
value: 1.0,
color: [0.95, 0.15, 0.1, 0.95],
},
]),
);
state.update();
(state, Vec::new())
}
#[test]
fn column_cross_renderer_produces_structurally_equivalent_output() {
if cfg!(target_os = "windows") {
eprintln!("Skipping column cross-parity test on Windows: Bevy headless event loop requires main thread");
return;
}
let (state, visible_tiles) = build_column_scene(false);
let wgpu_pixels = match render_wgpu(&state, &visible_tiles) {
Some(p) => p,
None => {
eprintln!("Skipping: WGPU render failed");
return;
}
};
let (bevy_state, _) = build_column_scene(false);
let bevy_pixels = match render_bevy(bevy_state) {
Some(p) => p,
None => {
eprintln!("Skipping: Bevy render failed");
return;
}
};
let expected_len = (WIDTH * HEIGHT * 4) as usize;
if wgpu_pixels.len() != expected_len || bevy_pixels.len() != expected_len {
eprintln!("Skipping pixel comparison: buffer size mismatch");
return;
}
let rmse = compute_rmse(&wgpu_pixels, &bevy_pixels);
assert!(
rmse < 40.0,
"Column cross-renderer RMSE too high: {rmse:.4}"
);
}
#[test]
fn column_cross_renderer_value_update_changes_both_outputs() {
if cfg!(target_os = "windows") {
eprintln!("Skipping column cross-parity update test on Windows");
return;
}
let (wgpu_initial_state, visible_tiles) = build_column_scene(false);
let (wgpu_updated_state, _) = build_column_scene(true);
let wgpu_initial = match render_wgpu(&wgpu_initial_state, &visible_tiles) {
Some(p) => p,
None => {
eprintln!("Skipping: WGPU initial failed");
return;
}
};
let wgpu_updated = match render_wgpu(&wgpu_updated_state, &visible_tiles) {
Some(p) => p,
None => {
eprintln!("Skipping: WGPU updated failed");
return;
}
};
let bevy_initial = match render_bevy(build_column_scene(false).0) {
Some(p) => p,
None => {
eprintln!("Skipping: Bevy initial failed");
return;
}
};
let bevy_updated = match render_bevy(build_column_scene(true).0) {
Some(p) => p,
None => {
eprintln!("Skipping: Bevy updated failed");
return;
}
};
assert!(compute_rmse(&wgpu_initial, &wgpu_updated) > 1.0);
assert!(compute_rmse(&bevy_initial, &bevy_updated) > 1.0);
}