Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Canvases generated from with_window_size are blurry due to using logical size instead of physical size. #816

Closed
a2aaron opened this issue Aug 30, 2020 · 4 comments
Milestone

Comments

@a2aaron
Copy link
Contributor

a2aaron commented Aug 30, 2020

Describe the bug
Canvases created with Canvas::with_window_size use the logical size of the
window, resulting in a blurry image when it is scaled up to the physical size of
the window.

Screenshots
Correct Image
Correct

Notice how the rectangle is crisp at the edge. This is what happens when I draw
the image directly to the screen, without using a Canvas.

Incorrect Image
Incorrect

Notice how the edges of the rectangle blur. This occurs because the Canvas is
scaled up from the logical size to the actual physical size of the screen. On my
machine, my HIDPI factor is 1.25, so this means the Canvas is scaled from 200x200
to 250x250, resulting in blurriness.

Below is a close up of the two images, showing the difference.
Closeup

Expected behavior
I would expect the canvas given by with_window_size to be of the same size
as the phyiscal dimensions of the window, so that when it is draw to the screen,
the result is exactly as sharp as it would be if you were to just draw things
to the screen directly in the first place

To reproduce

// This code demonstrates the blurry version of the rectangle.
// Comment out the lines with "comment this out" at the end to see the correct image.
fn draw(&mut self, ctx: &mut ggez::Context) -> ggez::GameResult {
    // Let's draw a rectangle
    let mesh = ggez::graphics::Mesh::new_rectangle(
        ctx,
        ggez::graphics::DrawMode::fill(),
        ggez::graphics::Rect::new(25.0, 25.0, 50.0, 100.0),
        ggez::graphics::WHITE,
    )?;

    let canvas = ggez::graphics::Canvas::with_window_size(ctx).expect("Couldn't make canvas!");

    // Draw the rectangle in the canvas
    ggez::graphics::set_canvas(ctx, Some(&canvas)); // comment this out
    ggez::graphics::draw(ctx, &mesh, ggez::graphics::DrawParam::default())?;

    // Now swap back to the screen and draw the canvas to the screen
    ggez::graphics::set_canvas(ctx, None); // comment this out
    ggez::graphics::draw(ctx, &canvas, ggez::graphics::DrawParam::default())?;

    ggez::graphics::present(ctx)?;
    Ok(())
}

Workarounds
It is possible to workaround this issue by scaling the Canvas up to the physical
size of the screen, and then scaling it back down before rendering. This is
frustrating though because the HIDPI factor lives in ggez:graphics::window() and
requires a bit of shuffling around to get it to work right.

// When we make our canvas, we first scale it up to the physical size of the window
let window = graphics::window(ctx);
let hidpi_factor = window.get_hidpi_factor();
let physical_size = window.get_inner_size().unwrap().to_physical(hidpi_factor);
let (width, height) = (physical_size.width as u16, physical_size.height as u16);
let samples = ggez::conf::NumSamples::One;

let canvas = ggez::graphics::Canvas::new(ctx, width, height, samples).expect("Couldn't make canvas!");

// Now we do our rendering as before.
ggez::graphics::set_canvas(ctx, Some(&canvas));
ggez::graphics::draw(ctx, &mesh, ggez::graphics::DrawParam::default())?;

// When we render the canvas to the screen, we now scale the canvas back down
ggez::graphics::set_canvas(ctx, None); 

let scaled = DrawParam::default().scale([1.0 / hidpi_factor, 1.0 / hidpi_factor]);
ggez::graphics::draw(ctx, &canvas, ggez::graphics::DrawParam::default())?;

ggez::graphics::present(ctx)?;
Ok(())
}

Hardware and Software:

  • ggez version: 0.5.1
  • OS: Windows 10, Update 2004 (Version 10.0.19041 Build 19041)
  • Graphics card: Intel(R) UHD Graphics 630
  • Graphics card drivers: Vendor provided Intel drivers (version 23.20.16.4973)
@icefoxen icefoxen added this to the 0.6 milestone Nov 12, 2020
@icefoxen
Copy link
Contributor

Check this out on the devel branch please, it's been updated to use a newer version of winit that is much more explicit about logical vs. physical sizes.

@PSteinhaus
Copy link
Member

PSteinhaus commented Feb 1, 2021

I just checked on the current devel and the problem is still the same (though the workaround would need to be adapted since the API changed somewhat).

Glancing over this I think that the problem lies in drawable_size, which says that it returns the drawable size in pixels, while actually returning the logical inner window size.

EDIT: Fixing his might need caution, as drawable_size is used in many places and changing it could have dramatic consequences. Also, digging through the source a bit I'm somewhat confused, as it seems that the width and height of WindowMode are actually supposed to be physical and not logical? This confuses me because currently they're logical...

@icefoxen
Copy link
Contributor

icefoxen commented Feb 1, 2021

Related: #883 #886

Making this design decision clear right now: All pixel coordinates in ggez should refer to physical pixels, not any silly nonsense that the windowing system lies to you about logical pixels. So if making drawable_size() return physical pixels breaks things, then those things are broken and also need fixed.

@PSteinhaus
Copy link
Member

I just checked: #884 fixes this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants