flo_draw 0.3.1

Hassle-free windowed 2D graphics rendering
Documentation
use flo_draw::*;
use flo_draw::canvas::*;
use flo_draw::render_canvas::*;
use ::desync::*;

use futures::prelude::*;
use futures::stream;
use futures::executor;

use std::mem;
use std::sync::*;
use std::collections::{HashMap};

///
/// Draws a circle, then intercepts the tessellation and displays that instead
///
/// flo_draw sends graphics to and from its various subsystems using streams rather than
/// method calls: this makes it very easy to intercept and change what's being sent around.
/// In this case we take the instructions just before they would have been sent to the GPU
/// and display them as line art, showing what the GPU would be rendering.
///
/// These actions are performed by the flo_render_canvas library. The instructions aren't 
/// specific to any particular API at this point, so they can be used to implement rendering 
/// on APIs or libraries that aren't explicitly supported by `flo_render` or to interoperate
/// with other rendering systems such as that which might be present in a game engine or a
/// UI layer. Note that there's no need to set up callbacks or implement traits in order to
/// retrieve this part of the state of the renderer.
///
/// It's possible to create a window that renders the GPU instructions directly by calling
/// `create_render_window`.
///
pub fn main() {
    with_2d_graphics(|| {
        // Create a drawing target. canvas is our renderer, and stream is where these instructions are sent to
        let (canvas, canvas_stream) = DrawingTarget::new();

        // Create a canvas renderer, and wrap it in a Desync. 
        // The canvas renderer generates instructions for a GPU-based renderer.
        // Desync is a companion library; it provides a convenient API for many asynchronous tasks, in this case stream processing
        let renderer                = CanvasRenderer::new();
        let renderer                = Arc::new(Desync::new(renderer));

        // Configure it to a viewport of 768x768 (want 'square pixels' so the rendering isn't squashed later on)
        renderer.desync(|renderer| { 
            renderer.set_viewport(0.0..768.0, 0.0..768.0, 768.0, 768.0, 1.0);
        });

        // Use Desync to process the rendering instructions on the stream (returning a stream of vecs, which we flatten to a stream of single instructions)
        let canvas_stream           = canvas_stream.ready_chunks(1000);
        let mut gpu_instructions    = pipe(renderer, canvas_stream, |renderer, drawing_instructions| {
            async move {
                renderer.draw(drawing_instructions.into_iter())
                    .collect::<Vec<_>>()
                    .await
            }.boxed()
        }).map(|as_vectors| stream::iter(as_vectors)).flatten();

        // Draw a circle that will get sent to the renderer we just set up (rather than directly to the window)
        canvas.draw(|gc| {
            // Set up the canvas
            gc.canvas_height(1000.0);
            gc.center_region(0.0, 0.0, 1000.0, 1000.0);

            // Draw a circle
            gc.new_path();
            gc.circle(500.0, 500.0, 250.0);

            gc.fill_color(Color::Rgba(0.3, 0.6, 0.8, 1.0));
            gc.fill();

            gc.line_width(6.0);
            gc.stroke_color(Color::Rgba(0.0, 0.0, 0.0, 1.0));
            gc.stroke();
        });

        // Dropping the canvas closes the stream so the list of drawing instructions ends
        mem::drop(canvas);

        // Create a window to render on
        let tessellation_window     = create_drawing_window("Circle Tessellation");

        // Run an executor to track the instructions that we would be sending to the GPU and render them to the tessellation window instead
        executor::block_on(async {
            // We to keep the vertex buffers around so we can render them once we get the index buffers
            let mut vertex_buffers  = HashMap::new();
            let mut index_buffers   = HashMap::new();

            while let Some(gpu_instruction) = gpu_instructions.next().await {
                // Render the tessellation to the tesselator window
                match &gpu_instruction {
                    RenderAction::SetTransform(Matrix(t)) => {
                        // Set an approximate equivalent of the transform the 'draw' instruction generated
                        tessellation_window.draw(|gc| {
                            gc.canvas_height(2.0);
                            gc.center_region(0.0, 0.0, 2.0, 2.0);
                            gc.transform(Transform2D([
                                [t[0][0], t[0][1], t[0][2]],
                                [t[1][0], t[1][1], t[1][2]],
                                [t[2][0], t[2][1], t[2][2]]
                            ]));
                        })
                    }

                    RenderAction::CreateVertex2DBuffer(buffer_id, vertices) => {
                        // Store the vertex buffer: we can render it when we get the corresponding index buffer
                        vertex_buffers.insert(*buffer_id, vertices.clone());
                    }

                    RenderAction::CreateIndexBuffer(buffer_id, indicies) => { 
                        // Store the index buffer for when we receive the rendering instruction
                        index_buffers.insert(*buffer_id, indicies.clone());
                    }

                    RenderAction::DrawIndexedTriangles(vertex_buffer_id, index_buffer_id, num_vertices) => {
                        // Fetch the buffers
                        let vertices = vertex_buffers.get(&vertex_buffer_id).unwrap();
                        let indicies = index_buffers.get(&index_buffer_id).unwrap();

                        tessellation_window.draw(|gc| {
                            // Render triangles from the index buffer
                            for triangle_num in 0..(num_vertices/3) {
                                // Use the index buffer to look up the vertices for this triangle
                                let index           = triangle_num * 3;
                                let i1: u16         = indicies[index+0];
                                let i2: u16         = indicies[index+1];
                                let i3: u16         = indicies[index+2];
                                let p1: &Vertex2D   = &vertices[i1 as usize];
                                let p2: &Vertex2D   = &vertices[i2 as usize];
                                let p3: &Vertex2D   = &vertices[i3 as usize];

                                let colour          = Color::Rgba(p1.color[0] as f32/255.0, p1.color[1] as f32/255.0, p1.color[2] as f32/255.0, p1.color[3] as f32/255.0);

                                // Render as lines
                                gc.new_path();

                                gc.move_to(p1.pos[0], p1.pos[1]);
                                gc.line_to(p2.pos[0], p2.pos[1]);
                                gc.line_to(p3.pos[0], p3.pos[1]);
                                gc.close_path();

                                gc.stroke_color(colour);
                                gc.stroke();
                            }
                        });
                    }

                    _ => {}
                }
            }
        })
    });
}