Skip to content

Nearest-neighbor sprite scaling produces 1-pixel artifacts on diagonal when window has odd dimensions #22875

@wizzeh

Description

@wizzeh

Bevy version and features

0.18.0 with the wayland feature enabled

[Optional] Relevant system information

I am running hyprland.

`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

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<AssetServer>) {
      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

An at sign with some weird pixel artifacts Here is a closeup view of how the sprite renders with odd window widths. An at sign And here is the sprite itself. Image Here is the window without the artifact on the right, where hyprland assigns it an even width. Image 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:

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()
        });
    }

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-RenderingDrawing game state to the screenC-BugAn unexpected or incorrect behaviorS-Needs-InvestigationThis issue requires detective work to figure out what's going wrong

    Type

    No type

    Projects

    Status

    Needs SME Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions