use rustial_engine as rustial_math;
use rustial_engine::{
build_terrain_mesh, compute_rmse, count_differing_pixels, differing_pixel_fraction,
prepare_hillshade_raster, tile_bounds_world, CameraProjection, DecodedImage, ElevationGrid,
HillshadeLayer, MapState, TerrainMeshData, TileData, TileId, VisibleTile, WebMercator,
WorldCoord,
};
use rustial_renderer_wgpu::WgpuMapRenderer;
use std::sync::Arc;
const WIDTH: u32 = 128;
const HEIGHT: u32 = 128;
fn create_device() -> Option<(wgpu::Device, wgpu::Queue)> {
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("parity_test_device"),
..Default::default()
}))
.ok()?;
Some((device, queue))
}
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_grid_scalar_scene(updated: bool, off_origin: bool) -> (MapState, Vec<VisibleTile>) {
let mut state = MapState::new();
state.set_viewport(WIDTH, HEIGHT);
let target = if off_origin {
rustial_math::GeoCoord::from_lat_lon(0.0, 170.0)
} else {
rustial_math::GeoCoord::from_lat_lon(0.0, 0.0)
};
state.set_camera_target(target);
state.set_camera_distance(5_000.0);
state.set_camera_pitch(20_f64.to_radians());
state.set_camera_yaw(15_f64.to_radians());
state.update_camera(1.0 / 60.0);
let mut field = rustial_engine::ScalarField2D::from_data(
4,
4,
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,
],
);
if updated {
field.update_values(vec![
6.0, 5.0, 4.0, 3.0, 5.0, 4.0, 3.0, 2.0, 4.0, 3.0, 2.0, 1.0, 3.0, 2.0, 1.0, 0.0,
]);
}
state.set_grid_scalar(
"density",
rustial_engine::GeoGrid::new(target, 4, 4, 100.0, 100.0),
field,
rustial_engine::ColorRamp::new(vec![
rustial_engine::ColorStop {
value: 0.0,
color: [0.0, 0.1, 0.8, 0.4],
},
rustial_engine::ColorStop {
value: 1.0,
color: [0.9, 0.2, 0.1, 0.9],
},
]),
);
state.update();
(state, Vec::new())
}
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 {
rustial_math::GeoCoord::from_lat_lon(0.0, 170.0)
} else {
rustial_math::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(
rustial_math::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())
}
#[test]
fn render_to_buffer_produces_non_trivial_output() {
let Some((device, queue)) = create_device() else {
eprintln!("Skipping parity test: no suitable GPU adapter available");
return;
};
let Some((state, visible_tiles)) = build_canonical_scene() else {
eprintln!("Skipping parity test: could not build canonical scene");
return;
};
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer = WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
let pixels = renderer
.render_to_buffer(
&state,
&device,
&queue,
&visible_tiles,
state.vector_meshes(),
state.model_instances(),
)
.expect("render_to_buffer should succeed");
assert_eq!(
pixels.len(),
(WIDTH * HEIGHT * 4) as usize,
"pixel buffer has wrong size"
);
assert!(
pixels.iter().any(|&b| b != 0),
"render_to_buffer produced all-zero output"
);
let first_pixel = &pixels[0..4];
let all_same = pixels.chunks_exact(4).all(|p| p == first_pixel);
assert!(
!all_same,
"render_to_buffer produced a uniform (single-colour) image"
);
}
#[test]
fn render_to_buffer_is_deterministic_across_frames() {
let Some((device, queue)) = create_device() else {
eprintln!("Skipping determinism test: no suitable GPU adapter available");
return;
};
let Some((state, visible_tiles)) = build_canonical_scene() else {
eprintln!("Skipping determinism test: could not build canonical scene");
return;
};
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer = WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
let frame1 = renderer
.render_to_buffer(
&state,
&device,
&queue,
&visible_tiles,
state.vector_meshes(),
state.model_instances(),
)
.expect("frame 1 render failed");
let frame2 = renderer
.render_to_buffer(
&state,
&device,
&queue,
&visible_tiles,
state.vector_meshes(),
state.model_instances(),
)
.expect("frame 2 render failed");
let rmse = compute_rmse(&frame1, &frame2);
assert!(
rmse < f64::EPSILON,
"consecutive render_to_buffer calls produced different output (RMSE={rmse})"
);
}
#[test]
fn image_compare_utilities_work_end_to_end() {
let Some((device, queue)) = create_device() else {
eprintln!("Skipping image compare test: no suitable GPU adapter available");
return;
};
let Some((state, visible_tiles)) = build_canonical_scene() else {
eprintln!("Skipping image compare test: could not build canonical scene");
return;
};
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer = WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
let rendered = renderer
.render_to_buffer(
&state,
&device,
&queue,
&visible_tiles,
state.vector_meshes(),
state.model_instances(),
)
.expect("render failed");
assert!(compute_rmse(&rendered, &rendered) < f64::EPSILON);
assert_eq!(count_differing_pixels(&rendered, &rendered, 0), 0);
assert!(differing_pixel_fraction(&rendered, &rendered, 0) < f64::EPSILON);
let inverted: Vec<u8> = rendered.iter().map(|&b| 255 - b).collect();
let rmse = compute_rmse(&rendered, &inverted);
assert!(rmse > 10.0, "RMSE vs inverted too low: {rmse}");
let diff_count = count_differing_pixels(&rendered, &inverted, 5);
let total_pixels = (WIDTH * HEIGHT) as usize;
assert!(
diff_count > total_pixels / 2,
"expected most pixels to differ vs inverted, got {diff_count}/{total_pixels}"
);
let frac = differing_pixel_fraction(&rendered, &inverted, 5);
assert!(frac > 0.5, "differing fraction vs inverted too low: {frac}");
}
#[test]
fn grid_scalar_render_to_buffer_produces_non_trivial_output() {
let Some((device, queue)) = create_device() else {
eprintln!("Skipping GridScalar WGPU parity test: no suitable GPU adapter available");
return;
};
let (state, visible_tiles) = build_grid_scalar_scene(false, false);
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer = WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
let pixels = renderer
.render_to_buffer(
&state,
&device,
&queue,
&visible_tiles,
state.vector_meshes(),
state.model_instances(),
)
.expect("grid scalar render should succeed");
assert_eq!(pixels.len(), (WIDTH * HEIGHT * 4) as usize);
assert!(pixels.iter().any(|&b| b != 0));
let first_pixel = &pixels[0..4];
assert!(!pixels.chunks_exact(4).all(|p| p == first_pixel));
}
#[test]
fn grid_scalar_value_update_changes_render_output() {
let Some((device, queue)) = create_device() else {
eprintln!(
"Skipping GridScalar WGPU dynamic parity test: no suitable GPU adapter available"
);
return;
};
let (initial_state, visible_tiles) = build_grid_scalar_scene(false, false);
let (updated_state, _) = build_grid_scalar_scene(true, false);
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer = WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
let initial = renderer
.render_to_buffer(
&initial_state,
&device,
&queue,
&visible_tiles,
initial_state.vector_meshes(),
initial_state.model_instances(),
)
.expect("initial grid scalar render should succeed");
let updated = renderer
.render_to_buffer(
&updated_state,
&device,
&queue,
&visible_tiles,
updated_state.vector_meshes(),
updated_state.model_instances(),
)
.expect("updated grid scalar render should succeed");
let rmse = compute_rmse(&initial, &updated);
assert!(
rmse > 1.0,
"GridScalar value-only update should change WGPU output (RMSE={rmse:.4})"
);
}
#[test]
fn grid_scalar_off_origin_render_to_buffer_produces_non_trivial_output() {
let Some((device, queue)) = create_device() else {
eprintln!(
"Skipping GridScalar WGPU off-origin parity test: no suitable GPU adapter available"
);
return;
};
let (state, visible_tiles) = build_grid_scalar_scene(false, true);
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer = WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
let pixels = renderer
.render_to_buffer(
&state,
&device,
&queue,
&visible_tiles,
state.vector_meshes(),
state.model_instances(),
)
.expect("off-origin grid scalar render should succeed");
assert_eq!(pixels.len(), (WIDTH * HEIGHT * 4) as usize);
assert!(pixels.iter().any(|&b| b != 0));
}
#[test]
fn point_cloud_render_to_buffer_produces_non_trivial_output() {
let Some((device, queue)) = create_device() else {
eprintln!("Skipping PointCloud WGPU parity test: no suitable GPU adapter available");
return;
};
let (state, visible_tiles) = build_point_cloud_scene(false, false);
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer = WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
let pixels = renderer
.render_to_buffer(
&state,
&device,
&queue,
&visible_tiles,
state.vector_meshes(),
state.model_instances(),
)
.expect("point cloud render should succeed");
assert_eq!(pixels.len(), (WIDTH * HEIGHT * 4) as usize);
assert!(pixels.iter().any(|&b| b != 0));
let first_pixel = &pixels[0..4];
assert!(!pixels.chunks_exact(4).all(|p| p == first_pixel));
}
#[test]
fn point_cloud_value_update_changes_render_output() {
let Some((device, queue)) = create_device() else {
eprintln!(
"Skipping PointCloud WGPU dynamic parity test: no suitable GPU adapter available"
);
return;
};
let (initial_state, visible_tiles) = build_point_cloud_scene(false, false);
let (updated_state, _) = build_point_cloud_scene(true, false);
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer = WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
let initial = renderer
.render_to_buffer(
&initial_state,
&device,
&queue,
&visible_tiles,
initial_state.vector_meshes(),
initial_state.model_instances(),
)
.expect("initial point cloud render should succeed");
let updated = renderer
.render_to_buffer(
&updated_state,
&device,
&queue,
&visible_tiles,
updated_state.vector_meshes(),
updated_state.model_instances(),
)
.expect("updated point cloud render should succeed");
let rmse = compute_rmse(&initial, &updated);
assert!(
rmse > 1.0,
"PointCloud value update should change WGPU output (RMSE={rmse:.4})"
);
}
#[test]
fn point_cloud_off_origin_render_to_buffer_produces_non_trivial_output() {
let Some((device, queue)) = create_device() else {
eprintln!(
"Skipping PointCloud WGPU off-origin parity test: no suitable GPU adapter available"
);
return;
};
let (state, visible_tiles) = build_point_cloud_scene(false, true);
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer = WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
let pixels = renderer
.render_to_buffer(
&state,
&device,
&queue,
&visible_tiles,
state.vector_meshes(),
state.model_instances(),
)
.expect("off-origin point cloud render should succeed");
assert_eq!(pixels.len(), (WIDTH * HEIGHT * 4) as usize);
assert!(pixels.iter().any(|&b| b != 0));
}
fn build_grid_extrusion_scene(updated: bool, off_origin: bool) -> (MapState, Vec<VisibleTile>) {
let mut state = MapState::new();
state.set_viewport(WIDTH, HEIGHT);
let target = if off_origin {
rustial_math::GeoCoord::from_lat_lon(0.0, 170.0)
} else {
rustial_math::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_render_to_buffer_produces_non_trivial_output() {
let Some((device, queue)) = create_device() else {
eprintln!("Skipping GridExtrusion WGPU parity test: no suitable GPU adapter");
return;
};
let (state, visible_tiles) = build_grid_extrusion_scene(false, false);
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer = WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
let pixels = renderer
.render_to_buffer(
&state,
&device,
&queue,
&visible_tiles,
state.vector_meshes(),
state.model_instances(),
)
.expect("grid extrusion render should succeed");
assert_eq!(pixels.len(), (WIDTH * HEIGHT * 4) as usize);
assert!(pixels.iter().any(|&b| b != 0));
let first = &pixels[0..4];
assert!(!pixels.chunks_exact(4).all(|p| p == first));
}
#[test]
fn grid_extrusion_value_update_changes_render_output() {
let Some((device, queue)) = create_device() else {
eprintln!("Skipping GridExtrusion WGPU update test: no suitable GPU adapter");
return;
};
let (initial_state, visible_tiles) = build_grid_extrusion_scene(false, false);
let (updated_state, _) = build_grid_extrusion_scene(true, false);
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer = WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
let initial = renderer
.render_to_buffer(
&initial_state,
&device,
&queue,
&visible_tiles,
initial_state.vector_meshes(),
initial_state.model_instances(),
)
.expect("initial extrusion render should succeed");
let updated = renderer
.render_to_buffer(
&updated_state,
&device,
&queue,
&visible_tiles,
updated_state.vector_meshes(),
updated_state.model_instances(),
)
.expect("updated extrusion render should succeed");
let rmse = compute_rmse(&initial, &updated);
assert!(
rmse > 1.0,
"GridExtrusion value update should change WGPU output (RMSE={rmse:.4})"
);
}
#[test]
fn grid_extrusion_off_origin_render_to_buffer_produces_non_trivial_output() {
let Some((device, queue)) = create_device() else {
eprintln!("Skipping GridExtrusion WGPU off-origin test: no suitable GPU adapter");
return;
};
let (state, visible_tiles) = build_grid_extrusion_scene(false, true);
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer = WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
let pixels = renderer
.render_to_buffer(
&state,
&device,
&queue,
&visible_tiles,
state.vector_meshes(),
state.model_instances(),
)
.expect("off-origin extrusion render should succeed");
assert_eq!(pixels.len(), (WIDTH * HEIGHT * 4) as usize);
assert!(pixels.iter().any(|&b| b != 0));
}
fn build_column_scene(updated: bool, off_origin: bool) -> (MapState, Vec<VisibleTile>) {
let mut state = MapState::new();
state.set_viewport(WIDTH, HEIGHT);
let target = if off_origin {
rustial_math::GeoCoord::from_lat_lon(0.0, 170.0)
} else {
rustial_math::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(
rustial_math::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_render_to_buffer_produces_non_trivial_output() {
let Some((device, queue)) = create_device() else {
eprintln!("Skipping Column WGPU parity test: no suitable GPU adapter");
return;
};
let (state, visible_tiles) = build_column_scene(false, false);
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer = WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
let pixels = renderer
.render_to_buffer(
&state,
&device,
&queue,
&visible_tiles,
state.vector_meshes(),
state.model_instances(),
)
.expect("column render should succeed");
assert_eq!(pixels.len(), (WIDTH * HEIGHT * 4) as usize);
assert!(pixels.iter().any(|&b| b != 0));
let first = &pixels[0..4];
assert!(!pixels.chunks_exact(4).all(|p| p == first));
}
#[test]
fn column_value_update_changes_render_output() {
let Some((device, queue)) = create_device() else {
eprintln!("Skipping Column WGPU update test: no suitable GPU adapter");
return;
};
let (initial_state, visible_tiles) = build_column_scene(false, false);
let (updated_state, _) = build_column_scene(true, false);
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer = WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
let initial = renderer
.render_to_buffer(
&initial_state,
&device,
&queue,
&visible_tiles,
initial_state.vector_meshes(),
initial_state.model_instances(),
)
.expect("initial column render should succeed");
let updated = renderer
.render_to_buffer(
&updated_state,
&device,
&queue,
&visible_tiles,
updated_state.vector_meshes(),
updated_state.model_instances(),
)
.expect("updated column render should succeed");
let rmse = compute_rmse(&initial, &updated);
assert!(
rmse > 1.0,
"Column value update should change WGPU output (RMSE={rmse:.4})"
);
}
#[test]
fn column_off_origin_render_to_buffer_produces_non_trivial_output() {
let Some((device, queue)) = create_device() else {
eprintln!("Skipping Column WGPU off-origin test: no suitable GPU adapter");
return;
};
let (state, visible_tiles) = build_column_scene(false, true);
let format = wgpu::TextureFormat::Rgba8UnormSrgb;
let mut renderer = WgpuMapRenderer::new(&device, &queue, format, WIDTH, HEIGHT);
let pixels = renderer
.render_to_buffer(
&state,
&device,
&queue,
&visible_tiles,
state.vector_meshes(),
state.model_instances(),
)
.expect("off-origin column render should succeed");
assert_eq!(pixels.len(), (WIDTH * HEIGHT * 4) as usize);
assert!(pixels.iter().any(|&b| b != 0));
}