orbtk_render/concurrent/
mod.rs

1use std::{
2    sync::{mpsc, Arc, Mutex},
3    thread,
4};
5
6use crate::{platform, utils::*, PipelineTrait, RenderTarget, TextMetrics};
7use platform::Image;
8
9#[derive(Clone)]
10struct PipelineWrapper(pub Box<dyn PipelineTrait>);
11
12impl PartialEq for PipelineWrapper {
13    fn eq(&self, _: &Self) -> bool {
14        true
15    }
16}
17
18// Used to sent render tasks to render thread.
19#[derive(Clone, PartialEq)]
20enum RenderTask {
21    // Single tasks
22    Start(),
23    SetBackground(Color),
24    Resize {
25        width: f64,
26        height: f64,
27    },
28    RegisterFont {
29        family: String,
30        font_file: &'static [u8],
31    },
32
33    // Multi tasks
34    FillRect {
35        x: f64,
36        y: f64,
37        width: f64,
38        height: f64,
39    },
40    StrokeRect {
41        x: f64,
42        y: f64,
43        width: f64,
44        height: f64,
45    },
46    FillText {
47        text: String,
48        x: f64,
49        y: f64,
50    },
51    Fill(),
52    Stroke(),
53    BeginPath(),
54    ClosePath(),
55    Rectangle {
56        x: f64,
57        y: f64,
58        width: f64,
59        height: f64,
60    },
61    Arc {
62        x: f64,
63        y: f64,
64        radius: f64,
65        start_angle: f64,
66        end_angle: f64,
67    },
68    MoveTo {
69        x: f64,
70        y: f64,
71    },
72    LineTo {
73        x: f64,
74        y: f64,
75    },
76    QuadraticCurveTo {
77        cpx: f64,
78        cpy: f64,
79        x: f64,
80        y: f64,
81    },
82    BesierCurveTo {
83        cp1x: f64,
84        cp1y: f64,
85        cp2x: f64,
86        cp2y: f64,
87        x: f64,
88        y: f64,
89    },
90    DrawRenderTarget {
91        render_target: RenderTarget,
92        x: f64,
93        y: f64,
94    },
95    DrawImage {
96        image: Image,
97        x: f64,
98        y: f64,
99    },
100    DrawImageWithClip {
101        image: Image,
102        clip: Rectangle,
103        x: f64,
104        y: f64,
105    },
106    DrawPipeline {
107        x: f64,
108        y: f64,
109        width: f64,
110        height: f64,
111        pipeline: PipelineWrapper,
112    },
113    Clip(),
114    SetLineWidth {
115        line_width: f64,
116    },
117    SetAlpha {
118        alpha: f32,
119    },
120    SetFontFamily {
121        family: String,
122    },
123    SetFontSize {
124        size: f64,
125    },
126    SetFillStyle {
127        fill_style: Brush,
128    },
129    SetStrokeStyle {
130        stroke_style: Brush,
131    },
132    Save(),
133    Restore(),
134    Clear {
135        brush: Brush,
136    },
137    SetTransform {
138        h_scaling: f64,
139        h_skewing: f64,
140        v_skewing: f64,
141        v_scaling: f64,
142        h_moving: f64,
143        v_moving: f64,
144    },
145    Finish(),
146    Terminate(),
147}
148
149// Used to send results to the main thread.
150enum RenderResult {
151    Finish { data: Vec<u32> },
152}
153
154// Wrapper for the render thread.
155struct RenderWorker {
156    render_thread: Option<thread::JoinHandle<()>>,
157}
158
159fn is_single_tasks(task: &RenderTask) -> bool {
160    match task {
161        RenderTask::Start() => true,
162        RenderTask::SetBackground(_) => true,
163        RenderTask::Resize { .. } => true,
164        RenderTask::RegisterFont { .. } => true,
165        RenderTask::DrawRenderTarget { .. } => true,
166        RenderTask::DrawImage { .. } => true,
167        RenderTask::DrawImageWithClip { .. } => true,
168        RenderTask::DrawPipeline { .. } => true,
169        RenderTask::SetTransform { .. } => true,
170        RenderTask::Terminate { .. } => true,
171        _ => false,
172    }
173}
174
175impl RenderWorker {
176    fn new(
177        width: f64,
178        height: f64,
179        finish_sender: mpsc::Sender<bool>,
180        receiver: Arc<Mutex<mpsc::Receiver<Vec<RenderTask>>>>,
181        sender: Arc<Mutex<mpsc::Sender<RenderResult>>>,
182    ) -> Self {
183        let render_thread = thread::spawn(move || {
184            let mut tasks_collection = vec![];
185
186            let mut render_context_2_d = platform::RenderContext2D::new(width, height);
187
188            loop {
189                let mut tasks = receiver.lock().unwrap().recv().unwrap();
190
191                // single tasks
192                if tasks.len() == 1 && is_single_tasks(tasks.get(0).unwrap()) {
193                    match tasks.remove(0) {
194                        RenderTask::Start() => {
195                            tasks_collection.clear();
196                            render_context_2_d.start();
197                            continue;
198                        }
199                        RenderTask::SetBackground(background) => {
200                            render_context_2_d.set_background(background);
201                            continue;
202                        }
203                        RenderTask::Resize { width, height } => {
204                            render_context_2_d.resize(width, height);
205                            continue;
206                        }
207                        RenderTask::RegisterFont { family, font_file } => {
208                            render_context_2_d.register_font(family.as_str(), font_file);
209                            continue;
210                        }
211                        RenderTask::DrawRenderTarget {
212                            render_target,
213                            x,
214                            y,
215                        } => {
216                            render_context_2_d.draw_render_target(&render_target, x, y);
217                        }
218                        RenderTask::DrawImage { image, x, y } => {
219                            render_context_2_d.draw_image(&image, x, y);
220                        }
221                        RenderTask::DrawImageWithClip { image, clip, x, y } => {
222                            render_context_2_d.draw_image_with_clip(&image, clip, x, y);
223                        }
224                        RenderTask::DrawPipeline {
225                            x,
226                            y,
227                            width,
228                            height,
229                            pipeline,
230                        } => {
231                            render_context_2_d.draw_pipeline(x, y, width, height, pipeline.0);
232                        }
233                        RenderTask::SetTransform {
234                            h_scaling,
235                            h_skewing,
236                            v_skewing,
237                            v_scaling,
238                            h_moving,
239                            v_moving,
240                        } => {
241                            render_context_2_d.set_transform(
242                                h_scaling, h_skewing, v_skewing, v_scaling, h_moving, v_moving,
243                            );
244                        }
245                        RenderTask::Terminate() => {
246                            return;
247                        }
248                        _ => {}
249                    };
250                }
251
252                tasks_collection.push(tasks);
253
254                if !tasks_collection.is_empty() {
255                    for task in tasks_collection.remove(0) {
256                        match task {
257                            RenderTask::FillRect {
258                                x,
259                                y,
260                                width,
261                                height,
262                            } => {
263                                render_context_2_d.fill_rect(x, y, width, height);
264                            }
265                            RenderTask::StrokeRect {
266                                x,
267                                y,
268                                width,
269                                height,
270                            } => {
271                                render_context_2_d.stroke_rect(x, y, width, height);
272                            }
273                            RenderTask::FillText { text, x, y } => {
274                                render_context_2_d.fill_text(text.as_str(), x, y);
275                            }
276                            RenderTask::Fill() => {
277                                render_context_2_d.fill();
278                            }
279                            RenderTask::Stroke() => {
280                                render_context_2_d.stroke();
281                            }
282                            RenderTask::BeginPath() => {
283                                render_context_2_d.begin_path();
284                            }
285                            RenderTask::ClosePath() => {
286                                render_context_2_d.close_path();
287                            }
288                            RenderTask::Rectangle {
289                                x,
290                                y,
291                                width,
292                                height,
293                            } => {
294                                render_context_2_d.rect(x, y, width, height);
295                            }
296                            RenderTask::Arc {
297                                x,
298                                y,
299                                radius,
300                                start_angle,
301                                end_angle,
302                            } => {
303                                render_context_2_d.arc(x, y, radius, start_angle, end_angle);
304                            }
305                            RenderTask::MoveTo { x, y } => {
306                                render_context_2_d.move_to(x, y);
307                            }
308                            RenderTask::LineTo { x, y } => {
309                                render_context_2_d.line_to(x, y);
310                            }
311                            RenderTask::QuadraticCurveTo { cpx, cpy, x, y } => {
312                                render_context_2_d.quadratic_curve_to(cpx, cpy, x, y);
313                            }
314                            RenderTask::BesierCurveTo {
315                                cp1x,
316                                cp1y,
317                                cp2x,
318                                cp2y,
319                                x,
320                                y,
321                            } => {
322                                render_context_2_d.bezier_curve_to(cp1x, cp1y, cp2x, cp2y, x, y);
323                            }
324                            RenderTask::SetLineWidth { line_width } => {
325                                render_context_2_d.set_line_width(line_width);
326                            }
327                            RenderTask::SetAlpha { alpha } => {
328                                render_context_2_d.set_alpha(alpha);
329                            }
330                            RenderTask::Clip() => {
331                                render_context_2_d.clip();
332                            }
333                            RenderTask::SetFontFamily { family } => {
334                                render_context_2_d.set_font_family(family);
335                            }
336                            RenderTask::SetFontSize { size } => {
337                                render_context_2_d.set_font_size(size);
338                            }
339                            RenderTask::SetFillStyle { fill_style } => {
340                                render_context_2_d.set_fill_style(fill_style);
341                            }
342                            RenderTask::SetStrokeStyle { stroke_style } => {
343                                render_context_2_d.set_stroke_style(stroke_style);
344                            }
345                            RenderTask::Save() => {
346                                render_context_2_d.save();
347                            }
348                            RenderTask::Restore() => {
349                                render_context_2_d.restore();
350                            }
351                            RenderTask::Clear { brush } => {
352                                render_context_2_d.clear(&brush);
353                            }
354                            RenderTask::Finish() => {
355                                sender
356                                    .lock()
357                                    .unwrap()
358                                    .send(RenderResult::Finish {
359                                        data: render_context_2_d.data().iter().copied().collect(),
360                                    })
361                                    .expect("Could not send render result to main thread.");
362                                finish_sender
363                                    .send(true)
364                                    .expect("Could not send render result to main thread.");
365                            }
366                            _ => {}
367                        };
368                    }
369                }
370            }
371        });
372
373        RenderWorker {
374            render_thread: Some(render_thread),
375        }
376    }
377}
378
379/// The RenderContext2D provides a concurrent render ctx.
380pub struct RenderContext2D {
381    output: Vec<u32>,
382    worker: RenderWorker,
383    sender: mpsc::Sender<Vec<RenderTask>>,
384    result_receiver: mpsc::Receiver<RenderResult>,
385    finish_receiver: mpsc::Receiver<bool>,
386    tasks: Vec<RenderTask>,
387    measure_context: platform::RenderContext2D,
388}
389
390impl Drop for RenderContext2D {
391    fn drop(&mut self) {
392        self.sender
393            .send(vec![RenderTask::Terminate()])
394            .expect("Could not send terminate to render thread.");
395        if let Some(thread) = self.worker.render_thread.take() {
396            thread.join().unwrap();
397        }
398    }
399}
400
401impl RenderContext2D {
402    /// Creates a new render ctx 2d.
403    pub fn new(width: f64, height: f64) -> Self {
404        let (sender, receiver) = mpsc::channel();
405
406        let (finish_sender, finish_receiver) = mpsc::channel();
407        let (result_sender, result_receiver) = mpsc::channel();
408
409        let receiver = Arc::new(Mutex::new(receiver));
410        let result_sender = Arc::new(Mutex::new(result_sender));
411
412        let worker = RenderWorker::new(width, height, finish_sender, receiver, result_sender);
413
414        RenderContext2D {
415            output: vec![0; width as usize * height as usize],
416            worker,
417            sender,
418            result_receiver,
419            finish_receiver,
420            tasks: vec![],
421            measure_context: platform::RenderContext2D::new(width, height),
422        }
423    }
424
425    pub fn finish_receiver(&self) -> &mpsc::Receiver<bool> {
426        &self.finish_receiver
427    }
428
429    pub fn set_background(&mut self, background: Color) {
430        self.sender
431            .send(vec![RenderTask::SetBackground(background)])
432            .expect("Could not send set background to render thread.");
433    }
434
435    // Sends a render task to the render thread.
436    fn send_tasks(&mut self) {
437        if !self.tasks.is_empty() {
438            self.sender
439                .send(self.tasks.to_vec())
440                .expect("Could not send render task.");
441            self.tasks.clear();
442        }
443    }
444
445    /// Starts a new render pipeline.
446    pub fn start(&mut self) {
447        self.sender
448            .send(vec![RenderTask::Start()])
449            .expect("Could not send start to render thread.");
450    }
451
452    /// Finishes the current render pipeline.
453    pub fn finish(&mut self) {
454        self.tasks.push(RenderTask::Finish());
455        self.send_tasks();
456    }
457
458    /// Resizes the render ctx.
459    pub fn resize(&mut self, width: f64, height: f64) {
460        self.sender
461            .send(vec![RenderTask::Resize { width, height }])
462            .expect("Could not send resize to render thread.");
463    }
464
465    /// Registers a new font file.
466    pub fn register_font(&mut self, family: &str, font_file: &'static [u8]) {
467        self.measure_context.register_font(family, font_file);
468        self.sender
469            .send(vec![RenderTask::RegisterFont {
470                family: family.to_string(),
471                font_file,
472            }])
473            .expect("Could not send register font to render thread.");
474    }
475
476    // Rectangles
477
478    /// Draws a filled rectangle whose starting point is at the coordinates {x, y} with the
479    /// specified width and height and whose style is determined by the fillStyle attribute.
480    pub fn fill_rect(&mut self, x: f64, y: f64, width: f64, height: f64) {
481        self.tasks.push(RenderTask::FillRect {
482            x,
483            y,
484            width,
485            height,
486        });
487    }
488
489    /// Draws a rectangle that is stroked (outlined) according to the current strokeStyle and other ctx settings.
490    pub fn stroke_rect(&mut self, x: f64, y: f64, width: f64, height: f64) {
491        self.tasks.push(RenderTask::StrokeRect {
492            x,
493            y,
494            width,
495            height,
496        });
497    }
498
499    // Text
500
501    /// Draws (fills) a given text at the given (x, y) position.
502    pub fn fill_text(&mut self, text: &str, x: f64, y: f64) {
503        self.tasks.push(RenderTask::FillText {
504            text: text.to_string(),
505            x,
506            y,
507        });
508    }
509
510    pub fn measure(
511        &mut self,
512        text: &str,
513        font_size: f64,
514        family: impl Into<String>,
515    ) -> TextMetrics {
516        self.measure_context.set_font_family(family);
517        self.measure_context.set_font_size(font_size);
518        self.measure_text(text)
519    }
520
521    /// Returns a TextMetrics object.
522    pub fn measure_text(&mut self, text: &str) -> TextMetrics {
523        self.measure_context.measure_text(text)
524    }
525
526    /// Fills the current or given path with the current file style.
527    pub fn fill(&mut self) {
528        self.tasks.push(RenderTask::Fill());
529    }
530
531    /// Strokes {outlines} the current or given path with the current stroke style.
532    pub fn stroke(&mut self) {
533        self.tasks.push(RenderTask::Stroke());
534    }
535
536    /// Starts a new path by emptying the list of sub-paths. Call this when you want to create a new path.
537    pub fn begin_path(&mut self) {
538        self.send_tasks();
539        self.tasks.push(RenderTask::BeginPath());
540    }
541
542    /// Attempts to add a straight line from the current point to the start of the current sub-path.
543    /// If the shape has already been closed or has only one point, this function does nothing.
544    pub fn close_path(&mut self) {
545        self.tasks.push(RenderTask::ClosePath());
546        self.send_tasks();
547    }
548    /// Adds a rectangle to the current path.
549    pub fn rect(&mut self, x: f64, y: f64, width: f64, height: f64) {
550        self.tasks.push(RenderTask::Rectangle {
551            x,
552            y,
553            width,
554            height,
555        });
556    }
557
558    /// Creates a circular arc centered at (x, y) with a radius of radius.
559    /// The path starts at startAngle and ends at endAngle.
560    pub fn arc(&mut self, x: f64, y: f64, radius: f64, start_angle: f64, end_angle: f64) {
561        self.tasks.push(RenderTask::Arc {
562            x,
563            y,
564            radius,
565            start_angle,
566            end_angle,
567        });
568    }
569
570    /// Begins a new sub-path at the point specified by the given {x, y} coordinates.
571
572    pub fn move_to(&mut self, x: f64, y: f64) {
573        self.tasks.push(RenderTask::MoveTo { x, y });
574    }
575
576    /// Adds a straight line to the current sub-path by connecting the sub-path's last point to
577    /// the specified {x, y} coordinates.
578    pub fn line_to(&mut self, x: f64, y: f64) {
579        self.tasks.push(RenderTask::LineTo { x, y });
580    }
581
582    /// Adds a quadratic Bézier curve to the current sub-path.
583    pub fn quadratic_curve_to(&mut self, cpx: f64, cpy: f64, x: f64, y: f64) {
584        self.tasks
585            .push(RenderTask::QuadraticCurveTo { cpx, cpy, x, y });
586    }
587
588    /// Adds a cubic Bézier curve to the current sub-path. It requires three points:
589    /// the first two are control points and the third one is the end point.
590    /// The starting point is the latest point in the current path, which can be changed using
591    /// MoveTo{} before creating the Bézier curve.
592    pub fn bezier_curve_to(&mut self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) {
593        self.tasks.push(RenderTask::BesierCurveTo {
594            cp1x,
595            cp1y,
596            cp2x,
597            cp2y,
598            x,
599            y,
600        });
601    }
602
603    // Draw image
604
605    pub fn draw_render_target(&mut self, render_target: &RenderTarget, x: f64, y: f64) {
606        self.sender
607            .send(vec![RenderTask::DrawRenderTarget {
608                render_target: render_target.clone(),
609                x,
610                y,
611            }])
612            .expect("Could not send render target to render thread.");
613    }
614
615    /// Draws the image.
616    pub fn draw_image(&mut self, image: &mut Image, x: f64, y: f64) {
617        self.sender
618            .send(vec![RenderTask::DrawImage {
619                image: image.clone(),
620                x,
621                y,
622            }])
623            .expect("Could not send image to render thread.");
624    }
625
626    /// Draws the given part of the image.
627    pub fn draw_image_with_clip(&mut self, image: &mut Image, clip: Rectangle, x: f64, y: f64) {
628        self.sender
629            .send(vec![RenderTask::DrawImageWithClip {
630                image: image.clone(),
631                clip,
632                x,
633                y,
634            }])
635            .expect("Could not send clipped image to render thread.");
636    }
637
638    pub fn draw_pipeline(
639        &mut self,
640        x: f64,
641        y: f64,
642        width: f64,
643        height: f64,
644        pipeline: Box<dyn PipelineTrait>,
645    ) {
646        self.sender
647            .send(vec![RenderTask::DrawPipeline {
648                x,
649                y,
650                width,
651                height,
652                pipeline: PipelineWrapper(pipeline),
653            }])
654            .expect("Could not send draw_pipeline to render thread.");
655    }
656
657    /// Creates a clipping path from the current sub-paths.
658    /// Everything drawn after clip() is called appears inside the clipping path only.
659    pub fn clip(&mut self) {
660        self.tasks.push(RenderTask::Clip());
661    }
662
663    // Line styles
664
665    /// Sets the thickness of lines.
666    pub fn set_line_width(&mut self, line_width: f64) {
667        self.tasks.push(RenderTask::SetLineWidth { line_width });
668    }
669
670    /// Sets the alpha value,
671    pub fn set_alpha(&mut self, alpha: f32) {
672        self.tasks.push(RenderTask::SetAlpha { alpha });
673    }
674
675    /// Specifies the font family.
676    pub fn set_font_family(&mut self, family: impl Into<String>) {
677        let family = family.into();
678        self.tasks.push(RenderTask::SetFontFamily { family });
679    }
680
681    /// Specifies the font size.
682    pub fn set_font_size(&mut self, size: f64) {
683        self.tasks.push(RenderTask::SetFontSize { size });
684    }
685
686    // Fill and stroke style
687
688    /// Specifies the fill color to use inside shapes.
689    pub fn set_fill_style(&mut self, fill_style: Brush) {
690        self.tasks.push(RenderTask::SetFillStyle { fill_style });
691    }
692
693    /// Specifies the fill stroke to use inside shapes.
694    pub fn set_stroke_style(&mut self, stroke_style: Brush) {
695        self.tasks.push(RenderTask::SetStrokeStyle { stroke_style });
696    }
697
698    // Transformations
699
700    /// Sets the transformation.
701    pub fn set_transform(
702        &mut self,
703        h_scaling: f64,
704        h_skewing: f64,
705        v_skewing: f64,
706        v_scaling: f64,
707        h_moving: f64,
708        v_moving: f64,
709    ) {
710        self.tasks.push(RenderTask::SetTransform {
711            h_scaling,
712            h_skewing,
713            v_skewing,
714            v_scaling,
715            h_moving,
716            v_moving,
717        });
718    }
719
720    // Canvas states
721
722    /// Saves the entire state of the canvas by pushing the current state onto a stack.
723    pub fn save(&mut self) {
724        self.tasks.push(RenderTask::Save());
725    }
726
727    /// Restores the most recently saved canvas state by popping the top entry in the drawing state stack.
728    /// If there is no saved state, this method does nothing.
729    pub fn restore(&mut self) {
730        self.tasks.push(RenderTask::Restore());
731    }
732
733    pub fn clear(&mut self, brush: &Brush) {
734        let brush = brush.clone();
735        self.tasks.push(RenderTask::Clear { brush });
736    }
737
738    pub fn data(&mut self) -> Option<&[u32]> {
739        if let Ok(RenderResult::Finish { data }) = self.result_receiver.try_recv() {
740            self.output = data;
741            Some(&self.output)
742        } else {
743            None
744        }
745    }
746
747    pub fn data_mut(&mut self) -> &mut [u32] {
748        &mut self.output
749    }
750
751    pub fn data_u8_mut(&mut self) -> &mut [u8] {
752        let p = self.output[..].as_mut_ptr();
753        let len = self.output[..].len();
754        // we want to return an [u8] slice instead of a [u32] slice. This is a safe thing to
755        // do because requirements of a [u32] slice are stricter.
756        unsafe { std::slice::from_raw_parts_mut(p as *mut u8, len * std::mem::size_of::<u32>()) }
757    }
758}