Nearest-neighbor sprite scaling produces 1-pixel artifacts on diagonal when window has odd dimensions
- Dominant language
- Rust
- Stars
- 48.2k
- Forks
- 4.8k
- Avg merge
- 3d 16h
- Merged PRs (30d)
- 171
Description
## Bevy version and features
0.18.0 with the wayland feature enabled
## \[Optional\] Relevant system information
I am running hyprland.
```ignore
`AdapterInfo { name: "AMD Radeon RX 6800 XT (RADV NAVI21)", vendor: 4098, device: 29631, device_type: DiscreteGpu, driver: "radv", driver_info: "Mesa 25.2.6", backend: Vulkan }`
```
## What you did
```rust
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands, asset_server: Res) {
commands.spawn((
Camera2d,
Msaa::Off,
Projection::Orthographic(OrthographicProjection {
scale: 1.0 / 6.0,
..OrthographicProjection::default_2d()
}),
));
commands.spawn((
Sprite::from_image(asset_server.load("at_sign.png")),
Transform::from_xyz(0.0, 0.0, 0.0),
));
}
```
## What went wrong
Here is a closeup view of how the sprite renders with odd window widths.
And here is the sprite itself.
Here is the window without the artifact on the right, where hyprland assigns it an even width.
And here it is on the left with the artifact, where hyprland has assigned it an odd width.
I didn't even restart the game between these screenshots. I just moved the window.
## Additional information
I tried scaling the sprite instead of the camera, and re-exporting the sprite, but neither had any effect. I ended up adding this system to my setup which was effective at eliminating the bug:
```rust
fn force_even_viewport(windows: Query<&Window>, mut cameras: Query<&mut Camera>) {
let Ok(window) = windows.single() else {
return;
};
let Ok(mut camera) = cameras.single_mut() else {
return;
};
let w = window.physical_width();
let h = window.physical_height();
let even_w = w & !1;
let even_h = h & !1;
if w == even_w && h == even_h {
camera.viewport = None;
} else {
camera.viewport = Some(Viewport {
physical_position: UVec2::ZERO,
physical_size: UVec2::new(even_w, even_h),
..default()
});
}
```
Contributor guide
Research direction
Reproduce the provided setup with Bevy 0.18.0, ImagePlugin::default_nearest(), Camera2d, and an odd physical window width under Wayland. Compare the sprite with even and odd dimensions, then trace the camera viewport and nearest-neighbor rendering behavior; done means the diagonal 1-pixel artifact no longer appears without requiring the force_even_viewport workaround.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- computer-graphics, game-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100