use std::{
sync::{mpsc, Arc, Mutex},
thread,
};
use crate::{platform, utils::*, Pipeline, RenderTarget, TextMetrics};
use platform::Image;
#[derive(Clone)]
struct PipelineWrapper(pub Box<dyn Pipeline>);
impl PartialEq for PipelineWrapper {
fn eq(&self, _: &Self) -> bool {
true
}
}
#[derive(Clone, PartialEq)]
enum RenderTask {
Start(),
Resize {
width: f64,
height: f64,
},
RegisterFont {
family: String,
font_file: &'static [u8],
},
FillRect {
x: f64,
y: f64,
width: f64,
height: f64,
},
StrokeRect {
x: f64,
y: f64,
width: f64,
height: f64,
},
FillText {
text: String,
x: f64,
y: f64,
},
Fill(),
Stroke(),
BeginPath(),
ClosePath(),
Rectangle {
x: f64,
y: f64,
width: f64,
height: f64,
},
Arc {
x: f64,
y: f64,
radius: f64,
start_angle: f64,
end_angle: f64,
},
MoveTo {
x: f64,
y: f64,
},
LineTo {
x: f64,
y: f64,
},
QuadraticCurveTo {
cpx: f64,
cpy: f64,
x: f64,
y: f64,
},
BesierCurveTo {
cp1x: f64,
cp1y: f64,
cp2x: f64,
cp2y: f64,
x: f64,
y: f64,
},
DrawRenderTarget {
render_target: RenderTarget,
x: f64,
y: f64,
},
DrawImage {
image: Image,
x: f64,
y: f64,
},
DrawImageWithClip {
image: Image,
clip: Rectangle,
x: f64,
y: f64,
},
DrawPipeline {
x: f64,
y: f64,
width: f64,
height: f64,
pipeline: PipelineWrapper,
},
Clip(),
SetLineWidth {
line_width: f64,
},
SetAlpha {
alpha: f32,
},
SetFontFamily {
family: String,
},
SetFontSize {
size: f64,
},
SetFillStyle {
fill_style: Brush,
},
SetStrokeStyle {
stroke_style: Brush,
},
Save(),
Restore(),
Clear {
brush: Brush,
},
SetTransform {
h_scaling: f64,
h_skewing: f64,
v_skewing: f64,
v_scaling: f64,
h_moving: f64,
v_moving: f64,
},
Finish(),
Terminate(),
}
enum RenderResult {
Finish { data: Vec<u32> },
}
struct RenderWorker {
render_thread: Option<thread::JoinHandle<()>>,
}
fn is_single_tasks(task: &RenderTask) -> bool {
match task {
RenderTask::Start() => true,
RenderTask::Resize { .. } => true,
RenderTask::RegisterFont { .. } => true,
RenderTask::DrawRenderTarget { .. } => true,
RenderTask::DrawImage { .. } => true,
RenderTask::DrawImageWithClip { .. } => true,
RenderTask::DrawPipeline { .. } => true,
RenderTask::SetTransform { .. } => true,
RenderTask::Terminate { .. } => true,
_ => false,
}
}
impl RenderWorker {
fn new(
width: f64,
height: f64,
receiver: Arc<Mutex<mpsc::Receiver<Vec<RenderTask>>>>,
sender: Arc<Mutex<mpsc::Sender<RenderResult>>>,
) -> Self {
let render_thread = thread::spawn(move || {
let mut tasks_collection = vec![];
let mut render_context_2_d = platform::RenderContext2D::new(width, height);
loop {
let mut tasks = receiver.lock().unwrap().recv().unwrap();
if tasks.len() == 1 && is_single_tasks(tasks.get(0).unwrap()) {
match tasks.remove(0) {
RenderTask::Start() => {
tasks_collection.clear();
continue;
}
RenderTask::Resize { width, height } => {
render_context_2_d.resize(width, height);
continue;
}
RenderTask::RegisterFont { family, font_file } => {
render_context_2_d.register_font(family.as_str(), font_file);
continue;
}
RenderTask::DrawRenderTarget {
render_target,
x,
y,
} => {
render_context_2_d.draw_render_target(&render_target, x, y);
}
RenderTask::DrawImage { image, x, y } => {
render_context_2_d.draw_image(&image, x, y);
}
RenderTask::DrawImageWithClip { image, clip, x, y } => {
render_context_2_d.draw_image_with_clip(&image, clip, x, y);
}
RenderTask::DrawPipeline {
x,
y,
width,
height,
pipeline,
} => {
render_context_2_d.draw_pipeline(x, y, width, height, pipeline.0);
}
RenderTask::SetTransform {
h_scaling,
h_skewing,
v_skewing,
v_scaling,
h_moving,
v_moving,
} => {
render_context_2_d.set_transform(
h_scaling, h_skewing, v_skewing, v_scaling, h_moving, v_moving,
);
}
RenderTask::Terminate() => {
return;
}
_ => {}
};
}
tasks_collection.push(tasks);
if !tasks_collection.is_empty() {
for task in tasks_collection.remove(0) {
match task {
RenderTask::FillRect {
x,
y,
width,
height,
} => {
render_context_2_d.fill_rect(x, y, width, height);
}
RenderTask::StrokeRect {
x,
y,
width,
height,
} => {
render_context_2_d.stroke_rect(x, y, width, height);
}
RenderTask::FillText { text, x, y } => {
render_context_2_d.fill_text(text.as_str(), x, y);
}
RenderTask::Fill() => {
render_context_2_d.fill();
}
RenderTask::Stroke() => {
render_context_2_d.stroke();
}
RenderTask::BeginPath() => {
render_context_2_d.begin_path();
}
RenderTask::ClosePath() => {
render_context_2_d.close_path();
}
RenderTask::Rectangle {
x,
y,
width,
height,
} => {
render_context_2_d.rect(x, y, width, height);
}
RenderTask::Arc {
x,
y,
radius,
start_angle,
end_angle,
} => {
render_context_2_d.arc(x, y, radius, start_angle, end_angle);
}
RenderTask::MoveTo { x, y } => {
render_context_2_d.move_to(x, y);
}
RenderTask::LineTo { x, y } => {
render_context_2_d.line_to(x, y);
}
RenderTask::QuadraticCurveTo { cpx, cpy, x, y } => {
render_context_2_d.quadratic_curve_to(cpx, cpy, x, y);
}
RenderTask::BesierCurveTo {
cp1x,
cp1y,
cp2x,
cp2y,
x,
y,
} => {
render_context_2_d.bezier_curve_to(cp1x, cp1y, cp2x, cp2y, x, y);
}
RenderTask::SetLineWidth { line_width } => {
render_context_2_d.set_line_width(line_width);
}
RenderTask::SetAlpha { alpha } => {
render_context_2_d.set_alpha(alpha);
}
RenderTask::Clip() => {
render_context_2_d.clip();
}
RenderTask::SetFontFamily { family } => {
render_context_2_d.set_font_family(family);
}
RenderTask::SetFontSize { size } => {
render_context_2_d.set_font_size(size);
}
RenderTask::SetFillStyle { fill_style } => {
render_context_2_d.set_fill_style(fill_style);
}
RenderTask::SetStrokeStyle { stroke_style } => {
render_context_2_d.set_stroke_style(stroke_style);
}
RenderTask::Save() => {
render_context_2_d.save();
}
RenderTask::Restore() => {
render_context_2_d.restore();
}
RenderTask::Clear { brush } => {
render_context_2_d.clear(&brush);
}
RenderTask::Finish() => {
sender
.lock()
.unwrap()
.send(RenderResult::Finish {
data: render_context_2_d.data().iter().copied().collect(),
})
.expect("Could not send render result to main thread.");
}
_ => {}
};
}
}
}
});
RenderWorker {
render_thread: Some(render_thread),
}
}
}
pub struct RenderContext2D {
output: Vec<u32>,
worker: RenderWorker,
sender: mpsc::Sender<Vec<RenderTask>>,
result_receiver: mpsc::Receiver<RenderResult>,
tasks: Vec<RenderTask>,
measure_context: platform::RenderContext2D,
}
impl Drop for RenderContext2D {
fn drop(&mut self) {
self.sender
.send(vec![RenderTask::Terminate()])
.expect("Could not send terminate to render thread.");
if let Some(thread) = self.worker.render_thread.take() {
thread.join().unwrap();
}
}
}
impl RenderContext2D {
pub fn new(width: f64, height: f64) -> Self {
let (sender, receiver) = mpsc::channel();
let (result_sender, result_receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let result_sender = Arc::new(Mutex::new(result_sender));
let worker = RenderWorker::new(width, height, receiver, result_sender);
RenderContext2D {
output: vec![0; width as usize * height as usize],
worker,
sender,
result_receiver,
tasks: vec![],
measure_context: platform::RenderContext2D::new(width, height),
}
}
fn send_tasks(&mut self) {
if !self.tasks.is_empty() {
self.sender
.send(self.tasks.to_vec())
.expect("Could not send render task.");
self.tasks.clear();
}
}
pub fn start(&mut self) {
self.sender
.send(vec![RenderTask::Start()])
.expect("Could not send start ot render thread.");
}
pub fn finish(&mut self) {
self.tasks.push(RenderTask::Finish());
self.send_tasks();
}
pub fn resize(&mut self, width: f64, height: f64) {
self.sender
.send(vec![RenderTask::Resize { width, height }])
.expect("Could not send resize to render thread.");
}
pub fn register_font(&mut self, family: &str, font_file: &'static [u8]) {
self.measure_context.register_font(family, font_file);
self.sender
.send(vec![RenderTask::RegisterFont {
family: family.to_string(),
font_file,
}])
.expect("Could not send register font to render thread.");
}
pub fn fill_rect(&mut self, x: f64, y: f64, width: f64, height: f64) {
self.tasks.push(RenderTask::FillRect {
x,
y,
width,
height,
});
}
pub fn stroke_rect(&mut self, x: f64, y: f64, width: f64, height: f64) {
self.tasks.push(RenderTask::StrokeRect {
x,
y,
width,
height,
});
}
pub fn fill_text(&mut self, text: &str, x: f64, y: f64) {
self.tasks.push(RenderTask::FillText {
text: text.to_string(),
x,
y,
});
}
pub fn measure(
&mut self,
text: &str,
font_size: f64,
family: impl Into<String>,
) -> TextMetrics {
self.measure_context.set_font_family(family);
self.measure_context.set_font_size(font_size);
self.measure_text(text)
}
pub fn measure_text(&mut self, text: &str) -> TextMetrics {
self.measure_context.measure_text(text)
}
pub fn fill(&mut self) {
self.tasks.push(RenderTask::Fill());
}
pub fn stroke(&mut self) {
self.tasks.push(RenderTask::Stroke());
}
pub fn begin_path(&mut self) {
self.send_tasks();
self.tasks.push(RenderTask::BeginPath());
}
pub fn close_path(&mut self) {
self.tasks.push(RenderTask::ClosePath());
self.send_tasks();
}
pub fn rect(&mut self, x: f64, y: f64, width: f64, height: f64) {
self.tasks.push(RenderTask::Rectangle {
x,
y,
width,
height,
});
}
pub fn arc(&mut self, x: f64, y: f64, radius: f64, start_angle: f64, end_angle: f64) {
self.tasks.push(RenderTask::Arc {
x,
y,
radius,
start_angle,
end_angle,
});
}
pub fn move_to(&mut self, x: f64, y: f64) {
self.tasks.push(RenderTask::MoveTo { x, y });
}
pub fn line_to(&mut self, x: f64, y: f64) {
self.tasks.push(RenderTask::LineTo { x, y });
}
pub fn quadratic_curve_to(&mut self, cpx: f64, cpy: f64, x: f64, y: f64) {
self.tasks
.push(RenderTask::QuadraticCurveTo { cpx, cpy, x, y });
}
pub fn bezier_curve_to(&mut self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) {
self.tasks.push(RenderTask::BesierCurveTo {
cp1x,
cp1y,
cp2x,
cp2y,
x,
y,
});
}
pub fn draw_render_target(&mut self, render_target: &RenderTarget, x: f64, y: f64) {
self.sender
.send(vec![RenderTask::DrawRenderTarget {
render_target: render_target.clone(),
x,
y,
}])
.expect("Could not send render target to render thread.");
}
pub fn draw_image(&mut self, image: &mut Image, x: f64, y: f64) {
self.sender
.send(vec![RenderTask::DrawImage {
image: image.clone(),
x,
y,
}])
.expect("Could not send image to render thread.");
}
pub fn draw_image_with_clip(&mut self, image: &mut Image, clip: Rectangle, x: f64, y: f64) {
self.sender
.send(vec![RenderTask::DrawImageWithClip {
image: image.clone(),
clip,
x,
y,
}])
.expect("Could not send clipped image to render thread.");
}
pub fn draw_pipeline(
&mut self,
x: f64,
y: f64,
width: f64,
height: f64,
pipeline: Box<dyn Pipeline>,
) {
self.sender
.send(vec![RenderTask::DrawPipeline {
x,
y,
width,
height,
pipeline: PipelineWrapper(pipeline),
}])
.expect("Could not send draw_pipeline to render thread.");
}
pub fn clip(&mut self) {
self.tasks.push(RenderTask::Clip());
}
pub fn set_line_width(&mut self, line_width: f64) {
self.tasks.push(RenderTask::SetLineWidth { line_width });
}
pub fn set_alpha(&mut self, alpha: f32) {
self.tasks.push(RenderTask::SetAlpha { alpha });
}
pub fn set_font_family(&mut self, family: impl Into<String>) {
let family = family.into();
self.tasks.push(RenderTask::SetFontFamily { family });
}
pub fn set_font_size(&mut self, size: f64) {
self.tasks.push(RenderTask::SetFontSize { size });
}
pub fn set_fill_style(&mut self, fill_style: Brush) {
self.tasks.push(RenderTask::SetFillStyle { fill_style });
}
pub fn set_stroke_style(&mut self, stroke_style: Brush) {
self.tasks.push(RenderTask::SetStrokeStyle { stroke_style });
}
pub fn set_transform(
&mut self,
h_scaling: f64,
h_skewing: f64,
v_skewing: f64,
v_scaling: f64,
h_moving: f64,
v_moving: f64,
) {
self.tasks.push(RenderTask::SetTransform {
h_scaling,
h_skewing,
v_skewing,
v_scaling,
h_moving,
v_moving,
});
}
pub fn save(&mut self) {
self.tasks.push(RenderTask::Save());
}
pub fn restore(&mut self) {
self.tasks.push(RenderTask::Restore());
}
pub fn clear(&mut self, brush: &Brush) {
let brush = brush.clone();
self.tasks.push(RenderTask::Clear { brush });
}
pub fn data(&mut self) -> Option<&[u32]> {
if let Ok(RenderResult::Finish { data }) = self.result_receiver.try_recv() {
self.output = data;
Some(&self.output)
} else {
None
}
}
pub fn data_mut(&mut self) -> &mut [u32] {
&mut self.output
}
pub fn data_u8_mut(&mut self) -> &mut [u8] {
let p = self.output[..].as_mut_ptr();
let len = self.output[..].len();
unsafe { std::slice::from_raw_parts_mut(p as *mut u8, len * std::mem::size_of::<u32>()) }
}
}