Skip to content

Commit

Permalink
swrenderer: refactor image drawing algorithm
Browse files Browse the repository at this point in the history
For tiling, we will need to know the actual source size in addition to
the scaling factor that can be different. So store the scaling factors
in the scene command, as well as an offset where to start.

This is more accurate in case of clipping and rotation.
For rotation that doesn't matter (appart from the fact that the testing
can now be more strict)
But for clipping this prevent glitches with partial rendering where it
would seem like the image are moving a bit by a pixel when it is redrawn
with a different clip
  • Loading branch information
ogoffart committed Feb 15, 2024
1 parent f03aedf commit 424f046
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 167 deletions.
326 changes: 182 additions & 144 deletions internal/core/software_renderer.rs

Large diffs are not rendered by default.

42 changes: 23 additions & 19 deletions internal/core/software_renderer/draw_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use super::{PhysicalLength, PhysicalRect};
use crate::graphics::{PixelFormat, Rgb8Pixel};
use crate::lengths::{PointLengths, RectLengths, SizeLengths};
use crate::software_renderer::fixed::Fixed;
use crate::Color;
use derive_more::{Add, Mul, Sub};
use integer_sqrt::IntegerSquareRoot;
Expand All @@ -20,59 +21,62 @@ pub(super) fn draw_texture_line(
texture: &super::SceneTexture,
line_buffer: &mut [impl TargetPixel],
) {
let super::SceneTexture { data, format, pixel_stride, source_size, color, alpha, rotation } =
*texture;
let source_size = source_size.cast::<usize>();
let super::SceneTexture {
data,
format,
pixel_stride,
extra: super::SceneTextureExtra { colorize, alpha, rotation, dx, dy, off_x, off_y },
} = *texture;
let span_size = span.size.cast::<usize>();
let y = (line - span.origin.y_length()).cast::<usize>();

let line_buffer = &mut line_buffer[span.origin.x as usize..][..span.size.width as usize];

let y = if rotation.mirror_width() { span_size.height - y.get() - 1 } else { y.get() };

const SHIFT: usize = 16;
let y = if rotation.mirror_width() { span_size.height - y.get() - 1 } else { y.get() } as i32;

if !rotation.is_transpose() {
let y_pos = (y * source_size.height / span_size.height) * pixel_stride as usize;
let mut delta = ((source_size.width << SHIFT) / span_size.width) as isize;
let mut pos = (y_pos << SHIFT) as isize;
let mut delta = Fixed::<i32, 8>::from_fixed(dx);
let mut pos = Fixed::from_integer(
(Fixed::<i32, 8>::from_fixed(off_y) + Fixed::<i32, 8>::from_fixed(dy) * y).truncate(),
) * pixel_stride as i32
+ Fixed::<i32, 8>::from_fixed(off_x);
if rotation.mirror_height() {
pos += (span_size.width as isize - 1) * delta;
pos += delta * (span_size.width as i32 - 1);
delta = -delta;
};
fetch_blend_pixel(
line_buffer,
format,
data,
alpha,
color,
colorize,
#[inline(always)]
|bpp| {
let p = (pos as usize >> SHIFT) * bpp;
let p = pos.truncate() as usize * bpp;
pos += delta;
p
},
);
} else {
let bpp = format.bpp();
let col = y * source_size.width / span_size.height;
let col = col * bpp;
let col = Fixed::<i32, 8>::from_fixed(off_x) + Fixed::<i32, 8>::from_fixed(dx) * y;
let col = col.truncate() as usize * bpp;
let stride = pixel_stride as usize * bpp;
let row_delta = ((source_size.height << SHIFT) / span_size.width) as isize;
let row_delta = Fixed::<i32, 8>::from_fixed(dy);
let (mut row, row_delta) = if rotation.mirror_height() {
((span_size.width as isize - 1) * row_delta, -row_delta)
(Fixed::from_fixed(off_y) + row_delta * (span_size.width as i32 - 1), -row_delta)
} else {
(0, row_delta)
(Fixed::from_fixed(off_y), row_delta)
};
fetch_blend_pixel(
line_buffer,
format,
data,
alpha,
color,
colorize,
#[inline(always)]
|_| {
let pos = (row as usize >> SHIFT) * stride + col;
let pos = row.truncate() as usize * stride + col;
row += row_delta;
pos
},
Expand Down
101 changes: 101 additions & 0 deletions internal/core/software_renderer/fixed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial

/// A Fixed point, represented with the T underlying type, and shifted by so many bits
#[derive(Default, Clone, Copy, Debug)]
pub struct Fixed<T, const SHIFT: usize>(T);

impl<
T: Copy
+ core::ops::Shl<usize, Output = T>
+ core::ops::Shr<usize, Output = T>
+ core::ops::Div<Output = T>
+ core::ops::Add<Output = T>
+ core::ops::Rem<Output = T>,
const SHIFT: usize,
> Fixed<T, SHIFT>
{
/// Create a fixed point from an integer value
pub fn from_integer(value: T) -> Self {
Self(value << SHIFT)
}

/// Get the integer part of the fixed point value
pub fn truncate(self) -> T {
self.0 >> SHIFT
}

pub fn from_fixed<
T2: core::ops::Shl<usize, Output = T2> + core::ops::Shr<usize, Output = T2> + Into<T>,
const SHIFT2: usize,
>(
value: Fixed<T2, SHIFT2>,
) -> Self {
if SHIFT > SHIFT2 {
let s: T = value.0.into();
Self(s << (SHIFT - SHIFT2))
} else {
Self((value.0 >> (SHIFT2 - SHIFT)).into())
}
}
pub fn try_from_fixed<
T2: core::ops::Shl<usize, Output = T2> + core::ops::Shr<usize, Output = T2> + TryInto<T>,
const SHIFT2: usize,
>(
value: Fixed<T2, SHIFT2>,
) -> Result<Self, T2::Error> {
Ok(if SHIFT > SHIFT2 {
let s: T = value.0.try_into()?;
Self(s << (SHIFT - SHIFT2))
} else {
Self((value.0 >> (SHIFT2 - SHIFT)).try_into()?)
})
}

pub(crate) fn from_f32(value: f32) -> Option<Self>
where
T: num_traits::FromPrimitive,
{
Some(Self(T::from_f32(value * (1 << SHIFT) as f32)?))
}
}

impl<T: core::ops::Add<Output = T>, const SHIFT: usize> core::ops::Add for Fixed<T, SHIFT> {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0.add(rhs.0))
}
}

impl<T: core::ops::Sub<Output = T>, const SHIFT: usize> core::ops::Sub for Fixed<T, SHIFT> {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0.sub(rhs.0))
}
}

impl<T: core::ops::AddAssign, const SHIFT: usize> core::ops::AddAssign for Fixed<T, SHIFT> {
fn add_assign(&mut self, rhs: Self) {
self.0.add_assign(rhs.0)
}
}

impl<T: core::ops::SubAssign, const SHIFT: usize> core::ops::SubAssign for Fixed<T, SHIFT> {
fn sub_assign(&mut self, rhs: Self) {
self.0.sub_assign(rhs.0)
}
}

impl<T: core::ops::Mul<Output = T>, const SHIFT: usize> core::ops::Mul<T> for Fixed<T, SHIFT> {
type Output = Self;
fn mul(self, rhs: T) -> Self::Output {
Self(self.0.mul(rhs))
}
}

impl<T: core::ops::Neg<Output = T>, const SHIFT: usize> core::ops::Neg for Fixed<T, SHIFT> {
type Output = Self;
fn neg(self) -> Self::Output {
Self(-self.0)
}
}
3 changes: 0 additions & 3 deletions tests/screenshots/cases/software/basic/border-image.slint
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@

import { Slider } from "std-widgets.slint";

// ROTATION_THRESHOLD=360 - because rendering pixelized image is not accurate in rotation
// SKIP_CLIPPING - without smooth scaling, any small difference in the starting point of the drawing of scaled image may result in pixel being off

export component TestCase inherits Window {
width: 64px;
height: 64px;
Expand Down
2 changes: 2 additions & 0 deletions tests/screenshots/cases/software/basic/border.slint
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial

// ROTATION_THRESHOLD=45 - the border radius algoritm don't give the same result from every rotation

TestCase := Window {
width: 64px;
height: 64px;
Expand Down
Binary file modified tests/screenshots/references/software/basic/border-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/screenshots/references/software/basic/images.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion tests/screenshots/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ fn compare_images(
}
let percentage_different = failed_pixel_count * 100 / reference.as_slice().len();
if rotated != RenderingRotation::NoRotation
&& (percentage_different <= 1 || max_color_difference < options.rotation_threshold)
&& (percentage_different < 1 || max_color_difference < options.rotation_threshold)
{
return Ok(());
}
Expand Down

0 comments on commit 424f046

Please sign in to comment.