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

Implement SVG Renderer #782

Merged
merged 1 commit into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ jobs:
- Linux i686 musl
- Linux x86_64
- Linux x86_64 musl
- Linux x86_64 gnux32
# FIXME: We can't link anything without cross.
# - Linux x86_64 gnux32
- Linux arm
- Linux arm musl
- Linux arm Hardware Float
Expand Down Expand Up @@ -314,14 +315,12 @@ jobs:
target: x86_64-unknown-linux-musl
dylib: skip

- label: Linux x86_64 gnux32
target: x86_64-unknown-linux-gnux32
cross: skip
install_target: true
# FIXME: The tests don't run because without cross we can't
# successfully link anything.
tests: skip
dylib: skip
# FIXME: We can't link anything without cross.
# - label: Linux x86_64 gnux32
# target: x86_64-unknown-linux-gnux32
# cross: skip
# install_target: true
# dylib: skip

- label: Linux arm
target: arm-unknown-linux-gnueabi
Expand Down
12 changes: 10 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ snafu = { version = "0.8.0", default-features = false }
unicase = "2.6.0"

# std
image = { version = "0.24.0", features = [
image = { version = "0.25.0", features = [
"png",
], default-features = false, optional = true }

Expand All @@ -84,6 +84,9 @@ tiny-skia = { version = "0.11.1", default-features = false, features = [
], optional = true }
tiny-skia-path = { version = "0.11.1", default-features = false, optional = true }

# SVG Rendering
ahash = { version = "0.8.11", default-features = false, optional = true }

# Networking
splits-io-api = { version = "0.4.0", optional = true }

Expand Down Expand Up @@ -147,7 +150,7 @@ std = [
]
more-image-formats = [
"image?/bmp",
"image?/farbfeld",
"image?/ff",
"image?/hdr",
"image?/ico",
"image?/jpeg",
Expand All @@ -161,6 +164,7 @@ rendering = ["more-image-formats", "image?/gif"]
default-text-engine = ["rendering", "cosmic-text"]
font-loading = ["std", "default-text-engine"]
software-rendering = ["default-text-engine", "tiny-skia", "tiny-skia-path"]
svg-rendering = ["default-text-engine", "ahash"]
wasm-web = [
"std",
"cosmic-text?/wasm-web",
Expand Down Expand Up @@ -195,6 +199,10 @@ harness = false
name = "software_rendering"
harness = false

[[bench]]
name = "svg_rendering"
harness = false

[profile.max-opt]
inherits = "release"
lto = true
Expand Down
4 changes: 2 additions & 2 deletions benches/scene_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ cfg_if::cfg_if! {
let mut manager = SceneManager::new(Dummy);

c.bench_function("Scene Management (Default)", move |b| {
b.iter(|| manager.update_scene(Dummy, (300.0, 500.0), &state, &image_cache))
b.iter(|| manager.update_scene(Dummy, [300.0, 500.0], &state, &image_cache))
});
}

Expand All @@ -113,7 +113,7 @@ cfg_if::cfg_if! {
let mut manager = SceneManager::new(Dummy);

c.bench_function("Scene Management (Subsplits Layout)", move |b| {
b.iter(|| manager.update_scene(Dummy, (300.0, 800.0), &state, &image_cache))
b.iter(|| manager.update_scene(Dummy, [300.0, 800.0], &state, &image_cache))
});
}

Expand Down
104 changes: 104 additions & 0 deletions benches/svg_rendering.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
cfg_if::cfg_if! {
if #[cfg(feature = "svg-rendering")] {
use {
criterion::{criterion_group, criterion_main, Criterion},
livesplit_core::{
layout::{self, Layout},
rendering::svg::Renderer,
run::parser::livesplit,
settings::ImageCache,
Run, Segment, TimeSpan, Timer, TimingMethod,
},
std::fs,
};

criterion_main!(benches);
criterion_group!(benches, default, subsplits_layout);

fn default(c: &mut Criterion) {
let mut run = create_run(&["A", "B", "C", "D"]);
run.set_game_name("Some Game Name");
run.set_category_name("Some Category Name");
run.set_attempt_count(1337);
let mut timer = Timer::new(run).unwrap();
let mut layout = Layout::default_layout();
let mut image_cache = ImageCache::new();

start_run(&mut timer);
make_progress_run_with_splits_opt(&mut timer, &[Some(5.0), None, Some(10.0)]);

let state = layout.state(&mut image_cache, &timer.snapshot());
let mut renderer = Renderer::new();
let mut buf = String::new();

c.bench_function("SVG Rendering (Default)", move |b| {
b.iter(|| {
buf.clear();
renderer.render(&mut buf, &state, &image_cache, [300.0, 500.0]).unwrap();
})
});
}

fn subsplits_layout(c: &mut Criterion) {
let run = lss("tests/run_files/Celeste - Any% (1.2.1.5).lss");
let mut timer = Timer::new(run).unwrap();
let mut layout = lsl("tests/layout_files/subsplits.lsl");
let mut image_cache = ImageCache::new();

start_run(&mut timer);
make_progress_run_with_splits_opt(&mut timer, &[Some(10.0), None, Some(20.0), Some(55.0)]);

let state = layout.state(&mut image_cache, &timer.snapshot());
let mut renderer = Renderer::new();
let mut buf = String::new();

c.bench_function("SVG Rendering (Subsplits Layout)", move |b| {
b.iter(|| {
buf.clear();
renderer.render(&mut buf, &state, &image_cache, [300.0, 800.0]).unwrap();
})
});
}

fn file(path: &str) -> String {
fs::read_to_string(path).unwrap()
}

fn lss(path: &str) -> Run {
livesplit::parse(&file(path)).unwrap()
}

fn lsl(path: &str) -> Layout {
layout::parser::parse(&file(path)).unwrap()
}

fn create_run(names: &[&str]) -> Run {
let mut run = Run::new();
for &name in names {
run.push_segment(Segment::new(name));
}
run
}

fn start_run(timer: &mut Timer) {
timer.set_current_timing_method(TimingMethod::GameTime);
timer.start();
timer.initialize_game_time();
timer.pause_game_time();
timer.set_game_time(TimeSpan::zero());
}

fn make_progress_run_with_splits_opt(timer: &mut Timer, splits: &[Option<f64>]) {
for &split in splits {
if let Some(split) = split {
timer.set_game_time(TimeSpan::from_seconds(split));
timer.split();
} else {
timer.skip_split();
}
}
}
} else {
fn main() {}
}
}
8 changes: 4 additions & 4 deletions src/component/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ struct DrawInfo {
#[derive(Default)]
struct GridLines {
/// The offset of the first grid line followed by the grid line distance.
horizontal: Option<(f32, f32)>,
horizontal: Option<[f32; 2]>,
vertical: Option<f32>,
}

Expand Down Expand Up @@ -553,10 +553,10 @@ fn calculate_grid_lines(draw_info: &DrawInfo, x_axis: f32) -> GridLines {
// The x-axis should always be on a grid line.
let offset = x_axis % distance;

ret.horizontal = Some((offset, distance));
ret.horizontal = Some([offset, distance]);
} else {
// Show just one grid line, the x-axis.
ret.horizontal = Some((DEFAULT_X_AXIS, f32::INFINITY));
ret.horizontal = Some([DEFAULT_X_AXIS, f32::INFINITY]);
}

if let Some(scale_factor_x) = draw_info.scale_factor_x {
Expand All @@ -574,7 +574,7 @@ fn calculate_grid_lines(draw_info: &DrawInfo, x_axis: f32) -> GridLines {
/// Copies the information from `grid_lines` into `Vec`s.
fn update_grid_line_vecs(state: &mut State, grid_lines: GridLines) {
state.horizontal_grid_lines.clear();
if let Some((offset, distance)) = grid_lines.horizontal {
if let Some([offset, distance]) = grid_lines.horizontal {
let mut y = offset;
while y < HEIGHT {
state.horizontal_grid_lines.push(y);
Expand Down
55 changes: 22 additions & 33 deletions src/rendering/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@

#[cfg(feature = "software-rendering")]
pub mod software;
#[cfg(feature = "svg-rendering")]
pub mod svg;

use self::{
consts::{
Expand Down Expand Up @@ -178,17 +180,10 @@
impl<P: SharedOwnership, I: SharedOwnership, F, L: SharedOwnership> SceneManager<P, I, F, L> {
/// Creates a new scene manager.
pub fn new(
mut allocator: impl ResourceAllocator<Path = P, Image = I, Font = F, Label = L>,
allocator: impl ResourceAllocator<Path = P, Image = I, Font = F, Label = L>,
) -> Self {
let mut builder = allocator.path_builder();
builder.move_to(0.0, 0.0);
builder.line_to(0.0, 1.0);
builder.line_to(1.0, 1.0);
builder.line_to(1.0, 0.0);
builder.close();
let rectangle = Handle::new(0, builder.finish());

let mut handles = Handles::new(1, allocator);
let mut handles = Handles::new(0, allocator);
let rectangle = handles.build_square();
let fonts = FontCache::new(&mut handles);

Self {
Expand Down Expand Up @@ -217,10 +212,10 @@
pub fn update_scene<A: ResourceAllocator<Path = P, Image = I, Font = F, Label = L>>(
&mut self,
allocator: A,
resolution: (f32, f32),
resolution: [f32; 2],
state: &LayoutState,
image_cache: &ImageCache,
) -> Option<(f32, f32)> {
) -> Option<[f32; 2]> {
self.scene.clear();

// Ensure we have exactly as many cached components as the layout state.
Expand Down Expand Up @@ -250,10 +245,10 @@
fn render_vertical(
&mut self,
allocator: impl ResourceAllocator<Path = P, Image = I, Font = F, Label = L>,
resolution: (f32, f32),
resolution @ [width, height]: [f32; 2],
state: &LayoutState,
image_cache: &ImageCache,
) -> Option<(f32, f32)> {
) -> Option<[f32; 2]> {
let total_height = component::layout_height(state);

let cached_total_size = self
Expand All @@ -264,27 +259,24 @@
match cached_total_size {
CachedSize::Vertical(cached_total_height) => {
if cached_total_height.to_bits() != total_height.to_bits() {
new_resolution = Some((
resolution.0,
resolution.1 / *cached_total_height * total_height,
));
new_resolution = Some([width, height / *cached_total_height * total_height]);
*cached_total_height = total_height;
}
}
CachedSize::Horizontal(_) => {
let to_pixels = resolution.1 / TWO_ROW_HEIGHT;
let to_pixels = height / TWO_ROW_HEIGHT;
let new_height = total_height * to_pixels;
let new_width = DEFAULT_VERTICAL_WIDTH * to_pixels;
new_resolution = Some((new_width, new_height));
new_resolution = Some([new_width, new_height]);
*cached_total_size = CachedSize::Vertical(total_height);
}
}

let aspect_ratio = resolution.0 / resolution.1;
let aspect_ratio = width / height;

let mut context = RenderContext {
handles: Handles::new(self.next_id, allocator),
transform: Transform::scale(resolution.0, resolution.1),
transform: Transform::scale(width, height),
scene: &mut self.scene,
fonts: &mut self.fonts,
images: &mut self.images,
Expand Down Expand Up @@ -327,10 +319,10 @@
fn render_horizontal(
&mut self,
allocator: impl ResourceAllocator<Path = P, Image = I, Font = F, Label = L>,
resolution: (f32, f32),
resolution @ [width, height]: [f32; 2],
state: &LayoutState,
image_cache: &ImageCache,
) -> Option<(f32, f32)> {
) -> Option<[f32; 2]> {
let total_width = component::layout_width(state);

let cached_total_size = self
Expand All @@ -340,27 +332,24 @@

match cached_total_size {
CachedSize::Vertical(cached_total_height) => {
let new_height = resolution.1 * TWO_ROW_HEIGHT / *cached_total_height;
let new_height = height * TWO_ROW_HEIGHT / *cached_total_height;
let new_width = total_width * new_height / TWO_ROW_HEIGHT;
new_resolution = Some((new_width, new_height));
new_resolution = Some([new_width, new_height]);
*cached_total_size = CachedSize::Horizontal(total_width);
}
CachedSize::Horizontal(cached_total_width) => {
if cached_total_width.to_bits() != total_width.to_bits() {
new_resolution = Some((
resolution.0 / *cached_total_width * total_width,
resolution.1,
));
new_resolution = Some([width / *cached_total_width * total_width, height]);
*cached_total_width = total_width;
}
}
}

let aspect_ratio = resolution.0 / resolution.1;
let aspect_ratio = width / height;

let mut context = RenderContext {
handles: Handles::new(self.next_id, allocator),
transform: Transform::scale(resolution.0, resolution.1),
transform: Transform::scale(width, height),
scene: &mut self.scene,
fonts: &mut self.fonts,
images: &mut self.images,
Expand Down Expand Up @@ -538,7 +527,7 @@
.push(Entity::Image(image.handle.share(), transform));
}

fn render_key_value_component(

Check warning on line 530 in src/rendering/mod.rs

View workflow job for this annotation

GitHub Actions / Check clippy lints

this function has too many arguments (11/7)
&mut self,
key: &str,
abbreviations: &[Cow<'_, str>],
Expand Down Expand Up @@ -625,7 +614,7 @@
x + label.width(scale)
}

fn render_text_centered(

Check warning on line 617 in src/rendering/mod.rs

View workflow job for this annotation

GitHub Actions / Check clippy lints

this function has too many arguments (8/7)
&mut self,
text: &str,
label: &mut CachedLabel<A::Label>,
Expand Down Expand Up @@ -656,7 +645,7 @@
));
}

fn render_abbreviated_text_centered<'a>(

Check warning on line 648 in src/rendering/mod.rs

View workflow job for this annotation

GitHub Actions / Check clippy lints

this function has too many arguments (8/7)
&mut self,
abbreviations: impl IntoIterator<Item = &'a str> + Clone,
label: &mut AbbreviatedLabel<A::Label>,
Expand Down Expand Up @@ -708,7 +697,7 @@
x - width
}

fn render_abbreviated_text_align<'a>(

Check warning on line 700 in src/rendering/mod.rs

View workflow job for this annotation

GitHub Actions / Check clippy lints

this function has too many arguments (9/7)
&mut self,
abbreviations: impl IntoIterator<Item = &'a str> + Clone,
label: &mut AbbreviatedLabel<A::Label>,
Expand Down Expand Up @@ -789,7 +778,7 @@
fn decode_layout_background(
&mut self,
background: &LayoutBackground<ImageId>,
(mut width, mut height): (f32, f32),
[mut width, mut height]: [f32; 2],
) -> Option<Background<A::Image>> {
Some(match background {
LayoutBackground::Gradient(gradient) => Background::Shader(decode_gradient(gradient)?),
Expand Down
Loading
Loading