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 GPU mode #34

Merged
merged 3 commits into from
Nov 26, 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
638 changes: 633 additions & 5 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ include = [

[dependencies]
ansi_term = "0.12.1"
bytemuck = "1.20.0"
chrono = "0.4.38"
flume = "0.11.1"
futures = "0.3.31"
image = "0.25.4"
num-traits = "0.2.19"
rand = "0.8.5"
Expand All @@ -34,4 +37,5 @@ toml = "0.8.19"
tui-input = "0.10.1"
tui-markup = { version = "0.5.0", features = ["ratatui", "ansi"] }
tui-scrollview = "=0.4.1"
wgpu = "23.0.0"

33 changes: 20 additions & 13 deletions src/app/main_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,33 +49,40 @@ impl App {
// Cycle through all the running jobs, non-blockingly handle messages
// and remove finished ones.
self.parallel_jobs.retain_mut(|job| {

// Display the current progression as a prioritized log message.
*self
.app_state
.prioritized_log_messages
.get_mut(&job.id)
.expect("There was no entry in the prioritized log messages corresponding to the current job.") = format!(
"Screenshot progression:\nline {}/{} (<command {:?}%>)",
job.rendered_lines,
job.size.y,
job.rendered_lines * 100 / job.size.y
);

for message in job.receiver.try_iter() {
match message {
SlaveMessage::LineRender => {
job.rendered_lines += 1;
// Display the current progression as a prioritized log message.
*self
.app_state
.prioritized_log_messages
.get_mut(&job.id)
.expect("There was no entry in the prioritized log messages corresponding to the current job.") = format!(
"Screenshot progression:\nline {}/{} (<command {:?}%>)",
job.rendered_lines,
job.size.y,
job.rendered_lines * 100 / job.size.y
);
}
SlaveMessage::JobFinished => {
// remove the priorotized progression message when the screenshot if
// finished
self.app_state.prioritized_log_messages.remove(&job.id);

let result = job.handle.take().unwrap().join().unwrap();

job.finished(&mut self.app_state, result);
return false;
}
SlaveMessage::SetMessage(message) => {

*self
.app_state
.prioritized_log_messages
.get_mut(&job.id)
.expect("There was no entry in the prioritized log messages corresponding to the current job.") = message;
}
}
}
true
Expand Down
74 changes: 50 additions & 24 deletions src/app/parallel_jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use std::{
thread::{self, JoinHandle},
};

use chrono::Utc;
use chrono::{Local, Utc};
use futures::executor;
use image::ImageBuffer;
use ratatui::style::Color;

Expand All @@ -23,7 +24,7 @@ pub(crate) struct ScreenshotMaster {
/// Used to keep track of the progression and display
/// a percentage in the main process.
pub(crate) rendered_lines: i32,
pub(crate) handle: Option<JoinHandle<DivergMatrix>>,
pub(crate) handle: Option<JoinHandle<Result<DivergMatrix, String>>>,
pub(crate) id: i64,
pub(crate) frac_name: &'static str,
}
Expand All @@ -35,6 +36,7 @@ pub(crate) struct ScreenshotMaster {
pub(crate) enum SlaveMessage {
LineRender,
JobFinished,
SetMessage(String),
}

/// The struct representing the screenshot job state
Expand All @@ -43,7 +45,7 @@ impl ScreenshotMaster {
pub(crate) fn new(
size: Vec2<i32>,
receiver: Receiver<SlaveMessage>,
handle: JoinHandle<DivergMatrix>,
handle: JoinHandle<Result<DivergMatrix, String>>,
frac_name: &'static str,
) -> Self {
Self {
Expand All @@ -57,26 +59,37 @@ impl ScreenshotMaster {
}
/// Handles the output of the screenshot child process:
/// Save the render to a png file, and print a log message.
pub(crate) fn finished(&self, state: &mut AppState, result: DivergMatrix) {
let buf = ImageBuffer::from_fn(self.size.x as u32, self.size.y as u32, |x, y| {
let color = state
.render_settings
.color_from_div(&result[self.size.y as usize - y as usize - 1][x as usize]);
if let Color::Rgb(r, g, b) = color {
image::Rgb([r, g, b])
} else {
image::Rgb([0, 0, 0])
}
});
pub(crate) fn finished(&self, state: &mut AppState, result: Result<DivergMatrix, String>) {
match result {
Ok(result) => {
let height = self.size.y as usize;
let buf =
ImageBuffer::from_par_fn(self.size.x as u32, self.size.y as u32, |x, y| {
let color = state
.render_settings
.color_from_div(&result[height - y as usize - 1][x as usize]);
if let Color::Rgb(r, g, b) = color {
image::Rgb([r, g, b])
} else {
image::Rgb([0, 0, 0])
}
});

let filename = format!("{}{}.png", self.frac_name, Utc::now().timestamp());
let filename = format!(
"{} {}.png",
self.frac_name,
Local::now().format("%F %H-%M-%S")
);

let _ = buf.save_with_format(&filename, image::ImageFormat::Png);
let _ = buf.save_with_format(&filename, image::ImageFormat::Png);

state.log_success(format!(
"Screenshot ({}x{}) saved to <acc {}>",
self.size.x, self.size.y, filename
));
state.log_success(format!(
"Screenshot ({}x{}) saved to <acc {}>",
self.size.x, self.size.y, filename
));
}
Err(err) => state.log_error(format!("Could not finish screenshot, reason: {err}")),
}
}
}

Expand All @@ -102,11 +115,24 @@ impl ScreenshotSlave {
}
impl ScreenshotSlave {
/// Creates a new process, running the screenshot rendering.
pub(crate) fn start(mut screenshot: Self) -> JoinHandle<DivergMatrix> {
pub(crate) fn start(mut screenshot: Self) -> JoinHandle<Result<DivergMatrix, String>> {
thread::spawn(move || screenshot.run())
}
pub(crate) fn run(&mut self) -> DivergMatrix {
self.rs_copy
.get_diverg_matrix_with_status(&self.size, &self.sender)
pub(crate) fn run(&mut self) -> Result<DivergMatrix, String> {
if self.rs_copy.use_gpu {
executor::block_on(self.rs_copy.initialize_gpu(Some(&self.sender)))?;
let result = executor::block_on(
self.rs_copy
.get_gpu_diverg_matrix(&self.size, Some(&self.sender)),
);
self.sender
.send(SlaveMessage::JobFinished)
.map_err(|err| format!("Could not open message channel: {err}"))?;
result
} else {
Ok(self
.rs_copy
.get_diverg_matrix_with_status(&self.size, &self.sender))
}
}
}
25 changes: 23 additions & 2 deletions src/app/render_canvas.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::collections::HashMap;
use std::time::Instant;

use futures::executor;

use crate::{app::App, app_state::Stats, helpers::Vec2};

impl App {
Expand All @@ -10,10 +12,29 @@ impl App {
if self.app_state.redraw_canvas {
self.app_state.redraw_canvas = false;

self.diverg_matrix = self.app_state.render_settings.get_diverg_matrix(&Vec2::new(
let size = Vec2::new(
self.app_state.render_settings.canvas_size.x,
self.app_state.render_settings.canvas_size.y,
));
);

self.diverg_matrix = if self.app_state.render_settings.use_gpu {
match executor::block_on(
self.app_state
.render_settings
.get_gpu_diverg_matrix(&size, None),
) {
Ok(res) => res,
Err(err) => {
self.app_state.render_settings.use_gpu = false;
self.app_state.log_error(format!(
"Disabling GPU mode, because the render failed with error: {err}",
));
self.app_state.render_settings.get_diverg_matrix(&size)
}
}
} else {
self.app_state.render_settings.get_diverg_matrix(&size)
}
}

if self.app_state.repaint_canvas {
Expand Down
9 changes: 9 additions & 0 deletions src/commands/frac.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use futures::executor;

use super::Command;
use crate::{
fractals::{get_frac_index_by_name, FRACTALS},
Expand Down Expand Up @@ -40,8 +42,15 @@ pub(crate) fn execute_frac(state: &mut AppState, args: Vec<&str>) -> Result<(),
let frac_obj = &FRACTALS[frac_i];
state.log_info_title(frac_obj.name, frac_obj.details);
} else {
state.request_redraw();
state.render_settings.frac_index = frac_i;
state.log_success(format!("Successfully selected fractal: <acc {frac_name}>."));
if let Err(err) = executor::block_on(state.render_settings.update_fractal_shader(None)) {
state.render_settings.use_gpu = false;
return Err(format!(
"Disabling GPU mode because fractal shader could not be loaded: {err}"
));
};
}
Ok(())
}
Expand Down
32 changes: 32 additions & 0 deletions src/commands/gpu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use super::Command;
use crate::AppState;
use futures::executor;

pub(crate) fn execute_gpu(state: &mut AppState, _args: Vec<&str>) -> Result<(), String> {
if state.render_settings.use_gpu {
state.render_settings.use_gpu = false;
state.log_info("GPU mode disabled.");
} else {
match executor::block_on(state.render_settings.initialize_gpu(None)) {
Ok(_) => {
state.log_success("GPU mode initialized successfully!");
state.render_settings.use_gpu = true;
}
Err(err) => state.log_error(format!("GPU mode could not be initialized: {err}")),
};
state.request_redraw();
}
Ok(())
}
pub(crate) const GPU: Command = Command {
execute: &execute_gpu,
name: "gpu",
accepted_arg_count: &[0],
detailed_desc: None,
basic_desc: concat!(
"Switch GPU mode on or off. In GPU mode, renders are done using the parallel ",
"computing capabilities of your hardware, which greatly improves rendering speeds. ",
"Please note that floating point precision is limited to 32 bits for now, but arbitrary ",
"precision will be implemementd soon."
),
};
4 changes: 3 additions & 1 deletion src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub(crate) mod clear;
pub(crate) mod color;
pub(crate) mod command_increment;
pub(crate) mod frac;
pub(crate) mod gpu;
pub(crate) mod help;
pub(crate) mod load;
pub(crate) mod max_iter;
Expand Down Expand Up @@ -33,7 +34,7 @@ pub(crate) struct Command {
pub(crate) accepted_arg_count: &'static [usize],
}

pub(crate) fn get_commands_list() -> [&'static Command; 14] {
pub(crate) fn get_commands_list() -> [&'static Command; 15] {
[
&help::HELP,
&quit::QUIT,
Expand All @@ -42,6 +43,7 @@ pub(crate) fn get_commands_list() -> [&'static Command; 14] {
&save::SAVE,
&load::LOAD,
&capture::CAPTURE,
&gpu::GPU,
&pos::POS,
&prec::PREC,
&max_iter::MAX_ITER,
Expand Down
20 changes: 20 additions & 0 deletions src/components/canvas.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Contains the `Canvas` widget.

use futures::executor;
use ratatui::{
buffer::Buffer,
crossterm::event::{KeyCode, MouseButton, MouseEvent, MouseEventKind},
Expand Down Expand Up @@ -138,6 +139,14 @@ impl<'a> Canvas<'a> {
KeyCode::Char('f') => {
state.render_settings.frac_index =
(state.render_settings.frac_index + 1) % FRACTALS.len();
if let Err(err) =
executor::block_on(state.render_settings.update_fractal_shader(None))
{
state.render_settings.use_gpu = false;
state.log_error(format!(
"Disabling GPU mode because fractal shader could not be loaded: {err}"
));
};
}
// Increment the color palette index
KeyCode::Char('c') => {
Expand Down Expand Up @@ -243,6 +252,17 @@ impl<'a> Widget for Canvas<'a> {
.left_aligned()
.style(Style::default().fg(ratatui::style::Color::LightGreen)),
)
.title_top(
Line::from(format!(
"GpuMode[{}]",
if self.state.render_settings.use_gpu {
"on"
} else {
"off"
}
))
.left_aligned(),
)
.title_top(
Line::from(format!("Prec[{}]", self.state.render_settings.prec)).right_aligned(),
)
Expand Down
19 changes: 3 additions & 16 deletions src/frac_logic/fractal_logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,18 @@ const INITIAL_CANVAS_WIDTH: i32 = 5;
pub(crate) type DivergMatrix = Vec<Vec<i32>>;

impl RenderSettings {
/// TODO: This function may be useless, ackshualli
/// Returns divergence lines from `first_line` to `last_line` included.
pub(crate) fn get_diverg_lines(
/// Returns a divergence matrix of the specified size.
fn _get_diverg_matrix_with_status(
&self,
size: &Vec2<i32>,
first_line: i32,
last_line: i32,
sender: Option<&Sender<SlaveMessage>>,
) -> DivergMatrix {
// Get the canvas coordinates of each row
let half_x = size.x / 2;
let half_y = size.y / 2;
let cell_size = self.get_plane_wid() / size.x;

let div_matrix = (-half_y + first_line..=-half_y + last_line)
let div_matrix = (-half_y..=-half_y + size.y - 1)
.into_par_iter()
.map(|y| {
let line = (-half_x..=-half_x + size.x)
Expand Down Expand Up @@ -59,16 +56,6 @@ impl RenderSettings {
div_matrix
}

/// Returns a divergence matrix of the specified size.
fn _get_diverg_matrix_with_status(
&self,
size: &Vec2<i32>,
sender: Option<&Sender<SlaveMessage>>,
) -> DivergMatrix {
let last_line = size.y - 1;
self.get_diverg_lines(size, 0, last_line, sender)
}

/// Returns a divergence matrix, and send an update to the channel
/// after each line is rendered.
pub(crate) fn get_diverg_matrix_with_status(
Expand Down
Loading