├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── THANKS.md ├── canvas ├── Cargo.toml ├── README.md ├── src │ ├── canvas.rs │ ├── color.rs │ ├── context.rs │ ├── conversion_streams │ │ ├── dashed_lines.rs │ │ ├── glyph_layout.rs │ │ ├── mod.rs │ │ ├── outline_fonts.rs │ │ └── path_stream.rs │ ├── decoding.rs │ ├── draw.rs │ ├── draw_resource.rs │ ├── draw_stream.rs │ ├── drawing_target.rs │ ├── encoding.rs │ ├── font.rs │ ├── font_face.rs │ ├── font_line_layout.rs │ ├── gradient.rs │ ├── lib.rs │ ├── namespace.rs │ ├── path.rs │ ├── primitives.rs │ ├── scenery │ │ ├── drawing_request.rs │ │ ├── mod.rs │ │ └── sprite_properties.rs │ ├── sprite.rs │ ├── texture.rs │ └── transform2d.rs ├── test_data │ ├── Lato-Regular.ttf │ └── Lato-license.md └── tests │ └── readme.rs ├── canvas_events ├── Cargo.toml └── src │ ├── draw_event.rs │ ├── draw_event_request.rs │ ├── draw_window_request.rs │ ├── key.rs │ ├── lib.rs │ ├── pointer_event.rs │ └── render_request.rs ├── draw ├── Cargo.toml ├── GUIDE.md ├── README.md ├── examples │ ├── Lato-Bold.ttf │ ├── Lato-Regular.ttf │ ├── Lato-license.md │ ├── bounce.rs │ ├── bounce_sprites.rs │ ├── canvas_window.rs │ ├── circle.rs │ ├── clip.rs │ ├── dashed_line.rs │ ├── erase.rs │ ├── flo_and_carrot.png │ ├── flo_drawing_on_window.png │ ├── follow_mouse.rs │ ├── gradient.rs │ ├── hello_world.rs │ ├── layer_alpha.rs │ ├── layer_reorder.rs │ ├── mandelbrot.rs │ ├── mascot.rs │ ├── mascot_dynamic_texture.rs │ ├── mascot_filter.rs │ ├── mascot_shadow.rs │ ├── mascot_sprite.rs │ ├── mascot_texture.rs │ ├── mirror_windows.rs │ ├── render_window.rs │ ├── render_window_double_resolve.rs │ ├── show_tessellation.rs │ ├── show_text_tessellation.rs │ ├── sprite.rs │ ├── text_layout.rs │ ├── texture.rs │ ├── texture_filters.rs │ ├── texture_from_sprite.rs │ ├── texture_from_sprite_dynamic.rs │ ├── texture_spin.rs │ ├── texture_sprites.rs │ ├── update_title.rs │ ├── vectoroids.rs │ ├── wibble.rs │ ├── wibble_dynamic_mascot.rs │ └── wibble_mascot.rs ├── guide_images │ ├── s_gradients.png │ ├── s_graphics_primitives.png │ ├── s_layers.png │ ├── s_sprites.png │ ├── s_text_layout.png │ ├── s_text_rendering.png │ ├── s_textures.png │ └── s_transforms.png ├── images │ ├── beeb.png │ ├── mandelbrot.png │ ├── mascot.png │ └── wibble.png ├── src │ ├── draw_scene │ │ ├── drawing_window_program.rs │ │ ├── glutin_render_window_program.rs │ │ ├── glutin_scene.rs │ │ ├── mod.rs │ │ ├── render_window_program.rs │ │ ├── scene.rs │ │ ├── software_drawing_window_program.rs │ │ ├── software_scene.rs │ │ ├── wgpu_render_window_program.rs │ │ └── wgpu_scene.rs │ ├── drawing_window.rs │ ├── glutin │ │ ├── event_conversion.rs │ │ ├── glutin_runtime.rs │ │ ├── glutin_thread.rs │ │ ├── glutin_thread_event.rs │ │ ├── glutin_window.rs │ │ └── mod.rs │ ├── lib.rs │ ├── render_window.rs │ ├── software │ │ ├── event_conversion.rs │ │ ├── mod.rs │ │ ├── winit_runtime.rs │ │ ├── winit_thread.rs │ │ ├── winit_thread_event.rs │ │ └── winit_window.rs │ ├── wgpu │ │ ├── event_conversion.rs │ │ ├── mod.rs │ │ ├── winit_runtime.rs │ │ ├── winit_thread.rs │ │ ├── winit_thread_event.rs │ │ └── winit_window.rs │ └── window_properties.rs └── tests │ └── readme.rs ├── images ├── bounce.png ├── flo_drawing_on_window.png ├── flo_drawing_on_window_small.png ├── gradient.png ├── mandelbrot.png ├── mascot.png ├── textlayout.png └── wibble.png ├── render ├── Cargo.toml ├── bindings │ ├── metal_bindings.h │ └── metal_vertex2d.h ├── build.rs ├── examples │ ├── raw_wgpu_winit_circle.rs │ └── raw_wgpu_winit_triangle.rs ├── shaders │ ├── dashed_line │ │ └── dashed_line.glslf │ ├── filters │ │ ├── alpha_blend.glslf │ │ ├── alpha_blend.wgsl │ │ ├── blur_29.glslf │ │ ├── blur_61.glslf │ │ ├── blur_9.glslf │ │ ├── blur_fixed.wgsl │ │ ├── blur_texture.glslf │ │ ├── blur_texture.wgsl │ │ ├── displacement.glslf │ │ ├── displacement.wgsl │ │ ├── mask.glslf │ │ ├── mask.wgsl │ │ ├── premultiply.glslf │ │ ├── reduce.wgsl │ │ ├── tint.glslf │ │ └── tint.wgsl │ ├── simple │ │ ├── clip_mask.metal │ │ ├── clip_mask.wgsl │ │ ├── clip_none.wgsl │ │ ├── color_invert_alpha.wgsl │ │ ├── color_multiply_alpha.wgsl │ │ ├── color_no_post_processing.wgsl │ │ ├── multisample_resolve_4.glslf │ │ ├── postprocessing.metal │ │ ├── rasterizer.metal │ │ ├── resolve.glslv │ │ ├── simple.glslf │ │ ├── simple.glslv │ │ ├── simple.metal │ │ └── simple.wgsl │ └── texture │ │ ├── alpha_no_premultiply.wgsl │ │ ├── alpha_premultiplied.wgsl │ │ ├── gradient.glslf │ │ ├── gradient.glslv │ │ ├── gradient.wgsl │ │ ├── gradient_fragment.metal │ │ ├── texture.glslf │ │ ├── texture.glslv │ │ ├── texture.wgsl │ │ ├── texture_fragment.metal │ │ ├── texture_multisample.wgsl │ │ ├── texture_none.wgsl │ │ ├── texture_pos_input.wgsl │ │ ├── texture_pos_separate.wgsl │ │ └── texture_sampler.wgsl └── src │ ├── action │ ├── blend_mode.rs │ ├── color.rs │ ├── identities.rs │ ├── mod.rs │ ├── render_action.rs │ ├── render_action_type.rs │ ├── render_target_type.rs │ ├── shader_type.rs │ └── texture_filter.rs │ ├── buffer │ ├── matrix.rs │ ├── mod.rs │ └── vertex.rs │ ├── gl_renderer │ ├── buffer.rs │ ├── error.rs │ ├── gl_renderer.rs │ ├── mod.rs │ ├── render_target.rs │ ├── shader.rs │ ├── shader_collection.rs │ ├── shader_program.rs │ ├── shader_uniforms.rs │ ├── standard_shader_programs.rs │ ├── texture.rs │ ├── vertex.rs │ └── vertex_array.rs │ ├── lib.rs │ ├── metal_renderer │ ├── bindings.rs │ ├── buffer.rs │ ├── convert.rs │ ├── matrix_buffer.rs │ ├── metal_renderer.rs │ ├── mod.rs │ ├── pipeline_configuration.rs │ └── render_target.rs │ ├── offscreen │ ├── error.rs │ ├── metal.rs │ ├── mod.rs │ ├── offscreen_trait.rs │ ├── opengl.rs │ ├── opengl_cgl_init.rs │ ├── opengl_egl_init.rs │ ├── opengl_wgl_init.rs │ ├── test.rs │ └── wgpu_offscreen.rs │ ├── profiler.rs │ └── wgpu_renderer │ ├── alpha_blend_filter.rs │ ├── blur_filter.rs │ ├── displacement_map_filter.rs │ ├── mask_filter.rs │ ├── mod.rs │ ├── pipeline.rs │ ├── pipeline_configuration.rs │ ├── reduce_filter.rs │ ├── render_pass_resources.rs │ ├── render_target.rs │ ├── renderer_state.rs │ ├── samplers.rs │ ├── shader_cache.rs │ ├── texture.rs │ ├── texture_settings.rs │ ├── tint_filter.rs │ ├── to_buffer.rs │ ├── wgpu_renderer.rs │ └── wgpu_shader.rs ├── render_canvas ├── Cargo.toml ├── examples │ ├── Lato-Regular.ttf │ ├── Lato-license.md │ ├── guide_illustrations.rs │ ├── png_mascot.rs │ ├── png_triangle.rs │ └── raw_wgpu_winit.rs ├── src │ ├── canvas_renderer │ │ ├── canvas_renderer.rs │ │ ├── mod.rs │ │ ├── tessellate_build_path.rs │ │ ├── tessellate_font.rs │ │ ├── tessellate_frame.rs │ │ ├── tessellate_gradients.rs │ │ ├── tessellate_layers.rs │ │ ├── tessellate_namespaces.rs │ │ ├── tessellate_path.rs │ │ ├── tessellate_properties.rs │ │ ├── tessellate_sprites.rs │ │ ├── tessellate_state.rs │ │ ├── tessellate_textures.rs │ │ └── tessellate_transform.rs │ ├── dynamic_texture_state.rs │ ├── fill_state.rs │ ├── layer_bounds.rs │ ├── layer_handle.rs │ ├── layer_state.rs │ ├── lib.rs │ ├── matrix.rs │ ├── offscreen │ │ ├── hardware.rs │ │ ├── initialise.rs │ │ ├── mod.rs │ │ ├── offscreen_trait.rs │ │ ├── render_offscreen.rs │ │ └── software.rs │ ├── render_entity.rs │ ├── render_entity_details.rs │ ├── render_gradient.rs │ ├── render_texture.rs │ ├── renderer_core.rs │ ├── renderer_layer.rs │ ├── renderer_stream.rs │ ├── renderer_worker.rs │ ├── resource_ids.rs │ ├── stroke_settings.rs │ ├── texture_filter_request.rs │ └── texture_render_request.rs └── tests │ └── render_tests.rs ├── render_gl_offscreen ├── Cargo.toml ├── build.rs ├── linux_gbm.h ├── src │ ├── egl.rs │ ├── gbm.rs │ └── lib.rs └── tiny_gbm.h └── render_software ├── Cargo.toml ├── README.md ├── examples ├── circle_to_png.rs ├── flo_drawing_on_window.png ├── render_canvas_drawing.rs ├── software_basic.rs ├── software_dynamic_sprite.rs ├── software_gradient.rs ├── software_mascot.rs ├── software_mascot_sprite.rs ├── software_mascot_texture.rs ├── software_render_perf.rs ├── software_sprite.rs ├── software_text_layout.rs ├── software_texture.rs ├── software_texture_filters.rs ├── software_texture_scaling.rs └── software_texture_transform.rs ├── src ├── draw │ ├── canvas_drawing.rs │ ├── canvas_drawing_region_renderer.rs │ ├── drawing_state.rs │ ├── dynamic_sprites.rs │ ├── gradient.rs │ ├── layer.rs │ ├── mod.rs │ ├── path.rs │ ├── pixel_programs.rs │ ├── prepared_layer.rs │ ├── sprite.rs │ ├── stroke.rs │ ├── texture.rs │ └── transform.rs ├── edgeplan │ ├── edge_descriptor.rs │ ├── edge_descriptor_intercept.rs │ ├── edge_id.rs │ ├── edge_intercept_direction.rs │ ├── edge_plan.rs │ ├── edge_plan_intercept.rs │ ├── mod.rs │ ├── shape_descriptor.rs │ └── shape_id.rs ├── edges │ ├── bezier_subpath_edge.rs │ ├── clipping_edge.rs │ ├── contour_edge.rs │ ├── flattened_bezier_subpath_edge.rs │ ├── line_stroke_edge.rs │ ├── mod.rs │ ├── polyline_edge.rs │ └── rectangle_edge.rs ├── filters │ ├── alpha_blend_filter.rs │ ├── combined_filter.rs │ ├── displacement_map_filter.rs │ ├── gaussian_blur_filter.rs │ ├── mask_filter.rs │ ├── mod.rs │ ├── pixel_filter_trait.rs │ ├── texture_filter.rs │ └── tint_filter.rs ├── lib.rs ├── pixel │ ├── alpha_blend_trait.rs │ ├── f32_linear.rs │ ├── f32_linear_texture_reader.rs │ ├── gamma_lut.rs │ ├── mip_map.rs │ ├── mod.rs │ ├── pixel_program.rs │ ├── pixel_program_cache.rs │ ├── pixel_program_runner.rs │ ├── pixel_trait.rs │ ├── rgba_texture.rs │ ├── texture_reader.rs │ ├── to_gamma_colorspace_trait.rs │ ├── to_linear_colorspace_trait.rs │ ├── u16_linear_texture.rs │ ├── u16_rgba.rs │ ├── u32_argb.rs │ ├── u32_fixed_point.rs │ ├── u32_linear.rs │ ├── u32_linear_texture_reader.rs │ └── u8_rgba.rs ├── pixel_programs │ ├── basic_sprite.rs │ ├── basic_texture.rs │ ├── bilinear_texture.rs │ ├── blend.rs │ ├── debug_ypos.rs │ ├── filtered_scanline.rs │ ├── gradient_linear.rs │ ├── mip_map_texture.rs │ ├── mod.rs │ ├── solid_color.rs │ ├── source_over.rs │ └── transformed_sprite.rs ├── render │ ├── edge_plan.rs │ ├── edgeplan_region_renderer.rs │ ├── frame_size.rs │ ├── image_render.rs │ ├── mod.rs │ ├── render_frame.rs │ ├── render_slice.rs │ ├── render_source_trait.rs │ ├── render_target_trait.rs │ ├── renderer.rs │ ├── rgba_frame.rs │ ├── scanline_renderer.rs │ ├── terminal_render.rs │ ├── u16_linear_frame_renderer.rs │ ├── u32_frame_renderer.rs │ └── u8_frame_renderer.rs └── scanplan │ ├── alpha_coverage.rs │ ├── background_scan_planner.rs │ ├── buffer_stack.rs │ ├── debug_ypos_scan_planner.rs │ ├── intercept_blend.rs │ ├── mod.rs │ ├── pixel_scan_planner.rs │ ├── scan_planner.rs │ ├── scanline_intercept.rs │ ├── scanline_plan.rs │ ├── scanline_shard_intercept.rs │ ├── scanline_transform.rs │ ├── scanspan.rs │ ├── shard.rs │ ├── shard_scan_planner.rs │ └── shard_subpixel.rs ├── test_data ├── Lato-Bold.ttf ├── Lato-Regular.ttf └── Lato-license.md └── tests ├── apex_tests.rs ├── bezier_subpath_edge_tests.rs ├── canvas_render_tests.rs ├── clip_edge_tests.rs ├── edge_plan_render_tests.rs ├── f32_linear_pixel_tests.rs ├── filter_tests.rs ├── mascot_tests.rs ├── pixel_planner_tests.rs ├── polyline_edge_tests.rs ├── scanline_plan_tests.rs ├── shard_scan_planner_tests.rs ├── shard_tests.rs ├── texture_tests.rs └── u32_linear_pixel_tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/target-* 3 | */**/Cargo.lock 4 | */**/xcuserdata/** 5 | *.xcworkspace 6 | *.xcscheme 7 | *.flo 8 | .DS_Store 9 | triangle.png 10 | flo.png 11 | etc 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "canvas", 5 | "canvas_events", 6 | "render", 7 | "render_canvas", 8 | "render_software", 9 | "render_gl_offscreen", 10 | "draw", 11 | ] 12 | 13 | [workspace.dependencies] 14 | wgpu = "24" 15 | wgpu-profiler = "0.22" 16 | 17 | [patch.crates-io] 18 | flo_canvas = { path = "./canvas" } 19 | flo_canvas_events = { path = "./canvas_events" } 20 | flo_render = { path = "./render" } 21 | flo_render_canvas = { path = "./render_canvas" } 22 | flo_render_gl_offscreen = { path = "./render_gl_offscreen" } 23 | flo_render_software = { path = "./render_software" } 24 | flo_draw = { path = "./draw" } 25 | 26 | flo_curves = { git = "https://github.com/Logicalshift/flo_curves", branch = "v0.8" } 27 | flo_binding = { git = "https://github.com/Logicalshift/flo_binding", branch = "v3.0" } 28 | flo_scene = { git = "https://github.com/Logicalshift/flo_scene", branch = "v0.2" } 29 | flo_scene_guest = { git = "https://github.com/Logicalshift/flo_scene", branch = "v0.2" } 30 | desync = { git = "https://github.com/Logicalshift/desync", branch = "v0.9" } 31 | 32 | # allsorts is very slow when built for debug, so packages using flo_draw should consider optimising it even in debug builds 33 | [profile.dev.package.allsorts] 34 | opt-level = 2 35 | 36 | [profile.dev.package.wgpu] 37 | opt-level = 2 38 | 39 | [profile.dev.package.lyon] 40 | opt-level = 2 41 | 42 | [profile.release] 43 | debug = 1 44 | -------------------------------------------------------------------------------- /THANKS.md: -------------------------------------------------------------------------------- 1 | Thankyou to everyone who has contributed to this library and to the authors of 2 | the crates that flo_draw depends on. 3 | 4 | Apika Luca (https://github.com/Brayan-724): bug reports and code contributions 5 | Ales Tsurko (https://github.com/ales-tsurko): bug reports, suggestions and code contributions 6 | -------------------------------------------------------------------------------- /canvas/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flo_canvas" 3 | version = "0.4.0" 4 | authors = ["Andrew Hunter"] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | readme = "README.md" 8 | categories = [ "graphics", "rendering::data-formats" ] 9 | repository = "https://github.com/Logicalshift/flo_draw" 10 | description = "Library for describing graphics context drawing actions without requiring a specific implementation" 11 | 12 | include = [ "Cargo.toml", "src/**/*", "test_data/**/*", "README.md" ] 13 | 14 | [features] 15 | outline-fonts = [ "allsorts", "ttf-parser", "pathfinder_geometry" ] 16 | image-loading = [ "image" ] 17 | scenery = [ "flo_scene" ] 18 | 19 | [dependencies] 20 | flo_curves = "0.8" 21 | flo_stream = "0.7" 22 | flo_scene = { version = "0.2", optional = true } 23 | uuid = { version = "1.0", features = [ "v4", "serde" ] } 24 | 25 | futures = "0.3" 26 | desync = "0.9" 27 | once_cell = "1.18" 28 | rust-hsluv = "0.1" 29 | itertools = "0.14" 30 | serde = { version = "1.0", features = [ "rc" ] } 31 | serde_derive = "1.0" 32 | allsorts = { version = "0.15", optional = true } 33 | ttf-parser = { version = "0.25", optional = true } 34 | pathfinder_geometry = { version = "0.5", optional = true } 35 | image = { version = "0.25", optional = true } 36 | smallvec = "1.6" 37 | ouroboros = "0.18" 38 | 39 | [dev-dependencies] 40 | serde_json = "1.0" 41 | -------------------------------------------------------------------------------- /canvas/README.md: -------------------------------------------------------------------------------- 1 | ```toml 2 | flo_canvas = "0.4" 3 | ``` 4 | 5 | # flo_canvas 6 | 7 | `flo_canvas` is a library that provides a way to describe 2D drawings, without providing any 8 | concrete implementation of how those drawings should be rendered. It supports streaming updates 9 | to allow canvases to be displayed in any user interface library that understands the `Draw` 10 | instructions, and it provides a serialization and deserialization mechanism for sending canvas 11 | instructions to other applications. 12 | 13 | This library was designed to support FlowBetween, an interactive animation editor. However, 14 | it has several implementations that make it useful outside that context. In particular, the 15 | `flo_draw` crate provides a straightforward way to render canvases into a window. `flo_render` 16 | and `flo_render_canvas` combine to provide a general-purpose way of rendering 2D canvases using 17 | modern 3D-accellerated graphics hardware: this includes the ability to render canvases 18 | off-screen to a bitmap on Linux, OS X and Windows systems. 19 | 20 | FlowBetween itself has some implementations that are not quite so accessible but may still be 21 | of interest. In particular `canvas.js` provides an implementation of `flo_canvas` in javascript, 22 | suitable for rendering to a HTML canvas. 23 | -------------------------------------------------------------------------------- /canvas/src/conversion_streams/mod.rs: -------------------------------------------------------------------------------- 1 | mod path_stream; 2 | pub use self::path_stream::*; 3 | 4 | 5 | #[cfg(feature = "outline-fonts")] mod glyph_layout; 6 | #[cfg(feature = "outline-fonts")] mod outline_fonts; 7 | 8 | #[cfg(feature = "outline-fonts")] pub use self::glyph_layout::*; 9 | #[cfg(feature = "outline-fonts")] pub use self::outline_fonts::*; 10 | 11 | mod dashed_lines; 12 | 13 | pub use self::dashed_lines::*; 14 | -------------------------------------------------------------------------------- /canvas/src/path.rs: -------------------------------------------------------------------------------- 1 | use super::draw::*; 2 | 3 | /// 4 | /// Operations that define paths 5 | /// 6 | #[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] 7 | pub enum PathOp { 8 | /// Begins a new path 9 | NewPath, 10 | 11 | /// Move to a new point 12 | Move(f32, f32), 13 | 14 | /// Line to point 15 | Line(f32, f32), 16 | 17 | /// Bezier curve to point 18 | BezierCurve(((f32, f32), (f32, f32)), (f32, f32)), 19 | 20 | /// Closes the current subpath 21 | ClosePath, 22 | } 23 | 24 | impl Into for PathOp { 25 | #[inline] 26 | fn into(self) -> Draw { 27 | Draw::Path(self) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /canvas/src/scenery/drawing_request.rs: -------------------------------------------------------------------------------- 1 | use crate::draw::*; 2 | 3 | use flo_scene::*; 4 | use serde::*; 5 | 6 | use std::sync::*; 7 | 8 | /// 9 | /// A request to a 2D drawing target 10 | /// 11 | #[derive(Debug, Clone)] 12 | #[derive(Serialize, Deserialize)] 13 | pub enum DrawingRequest { 14 | /// Perform the specified drawing actions 15 | Draw(Arc>), 16 | } 17 | 18 | impl SceneMessage for DrawingRequest { } 19 | -------------------------------------------------------------------------------- /canvas/src/scenery/mod.rs: -------------------------------------------------------------------------------- 1 | mod drawing_request; 2 | /* mod sprite_properties; */ 3 | 4 | pub use drawing_request::*; 5 | /* pub use sprite_properties::*; */ 6 | -------------------------------------------------------------------------------- /canvas/src/sprite.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Identifier of a canvas 'sprite' 3 | /// 4 | /// A 'sprite' is just a placeholder for a set of pre-rendered actions (it's useful for things like 5 | /// images or drawings that are expected to repeat). Sprites survive layer and canvas clears so they 6 | /// can be re-used repeatedly. The drawing layer may cache these actions in order to render the sprite 7 | /// quickly. 8 | /// 9 | /// Sprites are also faster to draw when rendering to a remote surface as they only need to be sent 10 | /// across once before they can be re-rendered as often as necessary. 11 | /// 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] 13 | pub struct SpriteId(pub u64); 14 | 15 | /// 16 | /// A position within a sprite 17 | /// 18 | #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] 19 | pub struct SpritePosition(pub f32, pub f32); 20 | 21 | /// 22 | /// A size within a sprite 23 | /// 24 | #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] 25 | pub struct SpriteSize(pub f32, pub f32); 26 | 27 | /// 28 | /// A bounding box within a sprite 29 | /// 30 | #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] 31 | pub struct SpriteBounds(pub SpritePosition, pub SpriteSize); 32 | -------------------------------------------------------------------------------- /canvas/test_data/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/canvas/test_data/Lato-Regular.ttf -------------------------------------------------------------------------------- /canvas/test_data/Lato-license.md: -------------------------------------------------------------------------------- 1 | Lato is included under the Open Font License (https://scripts.sil.org/OFL). 2 | It has an official site here: https://www.latofonts.com/ 3 | 4 | It is incorporated into flo_canvas when built for testing, but is not present in debug or 5 | release builds. 6 | -------------------------------------------------------------------------------- /canvas/tests/readme.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | 3 | /// 4 | /// Reads the README file for the crate 5 | /// 6 | fn readme() -> &'static str { 7 | let readme_bytes = include_bytes!("../README.md"); 8 | let readme_str = str::from_utf8(readme_bytes); 9 | 10 | readme_str.expect("Could not decode README.md") 11 | } 12 | 13 | #[test] 14 | fn starts_with_version_number_toml() { 15 | let major_version = env!("CARGO_PKG_VERSION_MAJOR"); 16 | let minor_version = env!("CARGO_PKG_VERSION_MINOR"); 17 | 18 | let expected = format!("```toml 19 | flo_canvas = \"{}.{}\" 20 | ```", major_version, minor_version); 21 | 22 | println!("{}", expected); 23 | assert!(readme().starts_with(&expected)); 24 | } 25 | -------------------------------------------------------------------------------- /canvas_events/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flo_canvas_events" 3 | version = "0.4.0" 4 | authors = ["Andrew Hunter"] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | repository = "https://github.com/Logicalshift/flo_draw" 8 | description = "User input events for a flo_draw canvas" 9 | categories = [ "graphics", "rendering", "gui" ] 10 | 11 | [dependencies] 12 | flo_canvas = { version = "0.4", features = [ "scenery" ] } 13 | flo_render = "0.4" 14 | flo_scene = "0.2" 15 | serde = "1.0" 16 | -------------------------------------------------------------------------------- /canvas_events/src/draw_event.rs: -------------------------------------------------------------------------------- 1 | use crate::key::*; 2 | use crate::pointer_event::*; 3 | 4 | use flo_canvas::*; 5 | use flo_scene::*; 6 | 7 | /// 8 | /// Events that can arrive from a flo_draw window 9 | /// 10 | #[derive(Clone, PartialEq, Debug)] 11 | #[derive(serde::Serialize, serde::Deserialize)] 12 | pub enum DrawEvent { 13 | /// Request to re-render the window (this is automatic for canvas windows) 14 | Redraw, 15 | 16 | /// Indicates that a frame has finished rendering to the canvas 17 | NewFrame, 18 | 19 | /// The window has a new scale 20 | Scale(f64), 21 | 22 | /// Window has a new size 23 | Resize(f64, f64), 24 | 25 | /// Canvas transformation for the window has changed (this will convert between window coordinates and canvas coordinates) 26 | CanvasTransform(Transform2D), 27 | 28 | /// A pointer device has changed its state 29 | Pointer(PointerAction, PointerId, PointerState), 30 | 31 | /// The user has pressed a key (parameters are scancode and the name of the key that was pressed, if known) 32 | KeyDown(u64, Option), 33 | 34 | /// The user has released a key (parameters are scancode and the name of the key that was pressed, if known) 35 | KeyUp(u64, Option), 36 | 37 | /// Window has been closed 38 | Closed 39 | } 40 | 41 | impl SceneMessage for DrawEvent { } 42 | -------------------------------------------------------------------------------- /canvas_events/src/draw_event_request.rs: -------------------------------------------------------------------------------- 1 | use super::draw_event::*; 2 | 3 | /// Draw events are already specified in the flo_canvas_evevents library and are sent singly so this is just an alias for that type 4 | pub type DrawEventRequest = DrawEvent; 5 | -------------------------------------------------------------------------------- /canvas_events/src/key.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Represents a key 3 | /// 4 | #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)] 5 | #[derive(serde::Serialize, serde::Deserialize)] 6 | pub enum Key { 7 | Unknown, 8 | 9 | ModifierShift, 10 | ModifierCtrl, 11 | ModifierAlt, 12 | ModifierMeta, 13 | ModifierSuper, 14 | ModifierHyper, 15 | 16 | KeyTab, 17 | 18 | KeyA, 19 | KeyB, 20 | KeyC, 21 | KeyD, 22 | KeyE, 23 | KeyF, 24 | KeyG, 25 | KeyH, 26 | KeyI, 27 | KeyJ, 28 | KeyK, 29 | KeyL, 30 | KeyM, 31 | KeyN, 32 | KeyO, 33 | KeyP, 34 | KeyQ, 35 | KeyR, 36 | KeyS, 37 | KeyT, 38 | KeyU, 39 | KeyV, 40 | KeyW, 41 | KeyX, 42 | KeyY, 43 | KeyZ, 44 | 45 | Key1, 46 | Key2, 47 | Key3, 48 | Key4, 49 | Key5, 50 | Key6, 51 | Key7, 52 | Key8, 53 | Key9, 54 | Key0, 55 | 56 | KeyUp, 57 | KeyDown, 58 | KeyLeft, 59 | KeyRight, 60 | 61 | KeyBackslash, 62 | KeyForwardslash, 63 | KeyBacktick, 64 | KeyComma, 65 | KeyFullstop, 66 | KeySemicolon, 67 | KeyQuote, 68 | KeyMinus, 69 | KeyEquals, 70 | 71 | KeySpace, 72 | KeyEscape, 73 | KeyInsert, 74 | KeyHome, 75 | KeyPgUp, 76 | KeyDelete, 77 | KeyEnd, 78 | KeyPgDown, 79 | KeyBackspace, 80 | KeyEnter, 81 | 82 | KeyF1, 83 | KeyF2, 84 | KeyF3, 85 | KeyF4, 86 | KeyF5, 87 | KeyF6, 88 | KeyF7, 89 | KeyF8, 90 | KeyF9, 91 | KeyF10, 92 | KeyF11, 93 | KeyF12, 94 | KeyF13, 95 | KeyF14, 96 | KeyF15, 97 | KeyF16, 98 | 99 | KeyNumpad0, 100 | KeyNumpad1, 101 | KeyNumpad2, 102 | KeyNumpad3, 103 | KeyNumpad4, 104 | KeyNumpad5, 105 | KeyNumpad6, 106 | KeyNumpad7, 107 | KeyNumpad8, 108 | KeyNumpad9, 109 | KeyNumpadDivide, 110 | KeyNumpadMultiply, 111 | KeyNumpadMinus, 112 | KeyNumpadAdd, 113 | KeyNumpadEnter, 114 | KeyNumpadDecimal, 115 | } 116 | -------------------------------------------------------------------------------- /canvas_events/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! # Events 3 | //! 4 | //! `flo_draw` is currently based on glutin, but uses its own event structure: this is to make it so that 5 | //! it's possible for future versions to replace glutin easily if that ever proves to be necessary, and 6 | //! to support easy porting of code using `flo_draw` to other windowing systems. This also isolates software 7 | //! implemented using `flo_draw` from changes to glutin. 8 | //! 9 | 10 | mod key; 11 | mod draw_event; 12 | mod pointer_event; 13 | 14 | mod render_request; 15 | mod draw_event_request; 16 | 17 | mod draw_window_request; 18 | 19 | pub use self::key::*; 20 | pub use self::draw_event::*; 21 | pub use self::pointer_event::*; 22 | 23 | pub use self::render_request::*; 24 | pub use self::draw_event_request::*; 25 | 26 | pub use self::draw_window_request::*; 27 | -------------------------------------------------------------------------------- /canvas_events/src/render_request.rs: -------------------------------------------------------------------------------- 1 | use flo_render::*; 2 | use flo_scene::*; 3 | 4 | /// 5 | /// A request to a low-level render target 6 | /// 7 | #[derive(Debug)] 8 | #[derive(serde::Serialize, serde::Deserialize)] 9 | pub enum RenderRequest { 10 | /// Performs the specified set of render actions immediately 11 | Render(Vec) 12 | } 13 | 14 | impl SceneMessage for RenderRequest { 15 | } 16 | -------------------------------------------------------------------------------- /draw/examples/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/examples/Lato-Bold.ttf -------------------------------------------------------------------------------- /draw/examples/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/examples/Lato-Regular.ttf -------------------------------------------------------------------------------- /draw/examples/Lato-license.md: -------------------------------------------------------------------------------- 1 | Lato is included under the Open Font License (https://scripts.sil.org/OFL). 2 | It has an official site here: https://www.latofonts.com/ 3 | -------------------------------------------------------------------------------- /draw/examples/canvas_window.rs: -------------------------------------------------------------------------------- 1 | use flo_draw::*; 2 | use flo_canvas::*; 3 | 4 | /// 5 | /// Simple example that displays a canvas window and renders a triangle 6 | /// 7 | pub fn main() { 8 | // 'with_2d_graphics' is used to support operating systems that can't run event loops anywhere other than the main thread 9 | with_2d_graphics(|| { 10 | // Create a window 11 | let canvas = create_drawing_window("Canvas window"); 12 | 13 | // Render a triangle to it 14 | canvas.draw(|gc| { 15 | // Clear the canvas and set up the coordinates 16 | gc.clear_canvas(Color::Rgba(0.3, 0.2, 0.0, 1.0)); 17 | gc.canvas_height(1000.0); 18 | gc.center_region(0.0, 0.0, 1000.0, 1000.0); 19 | 20 | // Draw a rectangle... 21 | gc.new_path(); 22 | gc.move_to(0.0, 0.0); 23 | gc.line_to(1000.0, 0.0); 24 | gc.line_to(1000.0, 1000.0); 25 | gc.line_to(0.0, 1000.0); 26 | gc.line_to(0.0, 0.0); 27 | 28 | gc.fill_color(Color::Rgba(1.0, 1.0, 0.8, 1.0)); 29 | gc.fill(); 30 | 31 | // Draw a triangle on top 32 | gc.new_path(); 33 | gc.move_to(200.0, 200.0); 34 | gc.line_to(800.0, 200.0); 35 | gc.line_to(500.0, 800.0); 36 | gc.line_to(200.0, 200.0); 37 | 38 | gc.fill_color(Color::Rgba(0.0, 0.0, 0.8, 1.0)); 39 | gc.fill(); 40 | }); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /draw/examples/circle.rs: -------------------------------------------------------------------------------- 1 | use flo_draw::*; 2 | use flo_canvas::*; 3 | 4 | /// 5 | /// Displays a filled circle 6 | /// 7 | pub fn main() { 8 | with_2d_graphics(|| { 9 | // Create a window 10 | let canvas = create_drawing_window("Circle"); 11 | 12 | // Draw a circle 13 | canvas.draw(|gc| { 14 | // Set up the canvas 15 | gc.canvas_height(1000.0); 16 | gc.center_region(0.0, 0.0, 1000.0, 1000.0); 17 | 18 | // Draw a circle 19 | gc.new_path(); 20 | gc.circle(500.0, 500.0, 250.0); 21 | 22 | gc.fill_color(Color::Rgba(0.3, 0.6, 0.8, 1.0)); 23 | gc.fill(); 24 | 25 | gc.line_width(6.0); 26 | gc.stroke_color(Color::Rgba(0.0, 0.0, 0.0, 1.0)); 27 | gc.stroke(); 28 | }); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /draw/examples/clip.rs: -------------------------------------------------------------------------------- 1 | use flo_draw::*; 2 | use flo_draw::canvas::*; 3 | 4 | /// 5 | /// Demonstrates using a clipping path 6 | /// 7 | pub fn main() { 8 | with_2d_graphics(|| { 9 | // Create a window 10 | let canvas = create_drawing_window("Clipping demonstration"); 11 | 12 | // Clip a large path using a circular clipping path 13 | canvas.draw(|gc| { 14 | // Set up the canvas 15 | gc.clear_canvas(Color::Rgba(0.95, 1.0, 0.9, 1.0)); 16 | gc.canvas_height(1000.0); 17 | gc.center_region(0.0, 0.0, 1000.0, 1000.0); 18 | 19 | gc.new_path(); 20 | gc.rect(800.0, 800.0, 900.0, 900.0); 21 | gc.fill_color(Color::Rgba(0.6, 0.0, 0.0, 1.0)); 22 | gc.fill(); 23 | 24 | gc.new_path(); 25 | gc.circle(500.0, 500.0, 200.0); 26 | gc.circle(150.0, 850.0, 100.0); 27 | gc.clip(); 28 | 29 | gc.new_path(); 30 | gc.rect(0.0, 0.0, 1000.0, 1000.0); 31 | gc.fill_color(Color::Rgba(0.0, 0.0, 0.6, 1.0)); 32 | gc.fill(); 33 | 34 | gc.new_path(); 35 | gc.move_to(0.0, 1000.0); 36 | gc.line_to(1000.0, 0.0); 37 | gc.stroke_color(Color::Rgba(0.0, 0.6, 0.0, 1.0)); 38 | gc.line_width(16.0); 39 | gc.stroke(); 40 | 41 | gc.unclip(); 42 | 43 | gc.new_path(); 44 | gc.rect(100.0, 100.0, 200.0, 200.0); 45 | gc.fill_color(Color::Rgba(0.6, 0.0, 0.0, 1.0)); 46 | gc.fill(); 47 | }); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /draw/examples/dashed_line.rs: -------------------------------------------------------------------------------- 1 | use flo_draw::*; 2 | use flo_draw::canvas::*; 3 | 4 | use std::thread; 5 | use std::time::{Duration}; 6 | 7 | /// 8 | /// Draws a dashed line 9 | /// 10 | pub fn main() { 11 | with_2d_graphics(|| { 12 | // Create a window 13 | let canvas = create_drawing_window("Dashed line"); 14 | 15 | let mut offset = 0.0; 16 | loop { 17 | canvas.draw(|gc| { 18 | gc.clear_canvas(Color::Rgba(1.0, 1.0, 1.0, 1.0)); 19 | 20 | // Set up the canvas 21 | gc.canvas_height(1000.0); 22 | gc.center_region(0.0, 0.0, 1000.0, 1000.0); 23 | 24 | gc.line_width(8.0); 25 | gc.new_dash_pattern(); 26 | gc.dash_offset(offset % 60.0); 27 | gc.dash_length(20.0); 28 | gc.dash_length(10.0); 29 | gc.dash_length(20.0); 30 | gc.dash_length(10.0); 31 | gc.stroke_color(Color::Rgba(0.0, 0.0, 0.6, 1.0)); 32 | 33 | gc.new_path(); 34 | gc.rect(100.0, 100.0, 900.0, 900.0); 35 | gc.stroke(); 36 | 37 | gc.new_path(); 38 | gc.move_to(200.0, 200.0); 39 | gc.line_to(800.0, 800.0); 40 | gc.stroke(); 41 | 42 | gc.new_path(); 43 | gc.circle(300.0, 700.0, 100.0); 44 | gc.stroke(); 45 | 46 | gc.new_path(); 47 | gc.circle(700.0, 300.0, 100.0); 48 | gc.fill(); 49 | }); 50 | 51 | offset += 1.0; 52 | thread::sleep(Duration::from_nanos(1_000_000_000 / 60)); 53 | } 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /draw/examples/erase.rs: -------------------------------------------------------------------------------- 1 | use flo_draw::*; 2 | use flo_canvas::*; 3 | 4 | use std::sync::*; 5 | 6 | /// 7 | /// Erases 'Hello, World' from a rectangle to allow the background to show through 8 | /// 9 | pub fn main() { 10 | with_2d_graphics(|| { 11 | let lato = CanvasFontFace::from_slice(include_bytes!("Lato-Regular.ttf")); 12 | 13 | // Create a window 14 | let canvas = create_drawing_window("Hello"); 15 | 16 | let hello_size = measure_text(&lato, "Hello, World", 100.0); 17 | let (min, max) = hello_size.inner_bounds; 18 | 19 | let x_pos = (1000.0 - (max.x()-min.x()))/2.0; 20 | let y_pos = (1000.0 - (max.y()-min.y()))/2.0; 21 | 22 | // Say 'hello, world' 23 | canvas.draw(|gc| { 24 | // Set up the canvas 25 | gc.canvas_height(1000.0); 26 | gc.center_region(0.0, 0.0, 1000.0, 1000.0); 27 | 28 | gc.layer(LayerId(2)); 29 | 30 | // Draw a rectangle 31 | gc.new_path(); 32 | gc.rect(100.0, 100.0, 900.0, 900.0); 33 | gc.fill_color(Color::Rgba(0.0, 0.0, 0.6, 1.0)); 34 | gc.fill(); 35 | 36 | // Load a font 37 | gc.define_font_data(FontId(1), Arc::clone(&lato)); 38 | gc.set_font_size(FontId(1), 100.0); 39 | 40 | // Erase a hole in our text 41 | gc.blend_mode(BlendMode::DestinationOut); 42 | gc.fill_color(Color::Rgba(0.0, 0.0, 0.0, 1.0)); 43 | gc.draw_text(FontId(1), "Hello, World".to_string(), x_pos as _, y_pos as _); 44 | gc.blend_mode(BlendMode::SourceOver); 45 | 46 | // Draw a line underneath (it will show through the erased section) 47 | gc.layer(LayerId(1)); 48 | gc.new_path(); 49 | gc.rect(300.0, 450.0, 700.0, 490.0); 50 | gc.fill_color(Color::Rgba(0.6, 0.0, 0.0, 1.0)); 51 | gc.fill(); 52 | }); 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /draw/examples/flo_and_carrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/examples/flo_and_carrot.png -------------------------------------------------------------------------------- /draw/examples/flo_drawing_on_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/examples/flo_drawing_on_window.png -------------------------------------------------------------------------------- /draw/examples/hello_world.rs: -------------------------------------------------------------------------------- 1 | use flo_draw::*; 2 | use flo_canvas::*; 3 | 4 | use std::sync::*; 5 | 6 | /// 7 | /// Displays 'Hello, World' in a window 8 | /// 9 | pub fn main() { 10 | with_2d_graphics(|| { 11 | let lato = CanvasFontFace::from_slice(include_bytes!("Lato-Regular.ttf")); 12 | 13 | // Create a window 14 | let canvas = create_drawing_window("Hello"); 15 | 16 | let hello_size = measure_text(&lato, "Hello, World", 100.0); 17 | let (min, max) = hello_size.inner_bounds; 18 | 19 | let x_pos = (1000.0 - (max.x()-min.x()))/2.0; 20 | let y_pos = (1000.0 - (max.y()-min.y()))/2.0; 21 | 22 | // Say 'hello, world' 23 | canvas.draw(|gc| { 24 | // Set up the canvas 25 | gc.canvas_height(1000.0); 26 | gc.center_region(0.0, 0.0, 1000.0, 1000.0); 27 | 28 | // Load a font 29 | gc.define_font_data(FontId(1), Arc::clone(&lato)); 30 | gc.set_font_size(FontId(1), 100.0); 31 | 32 | // Draw some text in our font 33 | gc.fill_color(Color::Rgba(0.0, 0.0, 0.6, 1.0)); 34 | gc.draw_text(FontId(1), "Hello, World".to_string(), x_pos as _, y_pos as _); 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /draw/examples/layer_reorder.rs: -------------------------------------------------------------------------------- 1 | use flo_draw::*; 2 | use flo_canvas::*; 3 | 4 | use std::thread; 5 | use std::time::{Duration}; 6 | 7 | use rand::*; 8 | 9 | pub fn main() { 10 | with_2d_graphics(|| { 11 | // Create a window with a canvas to draw on 12 | let canvas = create_drawing_window("Reordering layers"); 13 | let lato_bold = CanvasFontFace::from_slice(include_bytes!("Lato-Bold.ttf")); 14 | 15 | canvas.draw(|gc| { 16 | gc.clear_canvas(Color::Rgba(0.5, 0.8, 1.0, 1.0)); 17 | gc.canvas_height(1000.0); 18 | gc.center_region(0.0, 0.0, 1000.0, 1000.0); 19 | 20 | gc.define_font_data(FontId(1), lato_bold); 21 | gc.set_font_size(FontId(1), 1000.0); 22 | 23 | gc.layer(LayerId(0)); 24 | gc.fill_color(Color::Rgba(0.0, 0.0, 0.6, 1.0)); 25 | 26 | gc.begin_line_layout(500.0, 150.0, TextAlignment::Center); 27 | gc.layout_text(FontId(1), "A".to_string()); 28 | gc.draw_text_layout(); 29 | 30 | gc.layer(LayerId(1)); 31 | gc.fill_color(Color::Rgba(0.0, 0.6, 0.0, 1.0)); 32 | 33 | gc.begin_line_layout(500.0, 150.0, TextAlignment::Center); 34 | gc.layout_text(FontId(1), "B".to_string()); 35 | gc.draw_text_layout(); 36 | 37 | gc.layer(LayerId(2)); 38 | gc.fill_color(Color::Rgba(0.8, 0.8, 0.0, 1.0)); 39 | 40 | gc.begin_line_layout(500.0, 150.0, TextAlignment::Center); 41 | gc.layout_text(FontId(1), "C".to_string()); 42 | gc.draw_text_layout(); 43 | }); 44 | 45 | loop { 46 | thread::sleep(Duration::from_secs(2)); 47 | 48 | let first_layer = random_range(0..3); 49 | let second_layer = random_range(0..3); 50 | 51 | println!("{:?} before {:?}", second_layer, first_layer); 52 | 53 | canvas.draw(|gc| { 54 | gc.layer(LayerId(first_layer)); 55 | gc.place_layer_before(NamespaceId::default(), LayerId(second_layer)); 56 | }) 57 | } 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /draw/examples/mirror_windows.rs: -------------------------------------------------------------------------------- 1 | use flo_draw::*; 2 | use flo_draw::canvas::*; 3 | 4 | use futures::prelude::*; 5 | 6 | use std::thread; 7 | use std::time::{Duration}; 8 | 9 | /// 10 | /// Draws to two windows simultaneously 11 | /// 12 | pub fn main() { 13 | with_2d_graphics(|| { 14 | // Create a canvas window: canvases store their drawing instructions and can mirror them to multiple targets 15 | let canvas = create_canvas_window("Mirror windows"); 16 | 17 | // Create a duplicate window from the same canvas (gluting creates windows on top of each other, annoyingly) 18 | let _ = create_drawing_window_from_stream(canvas.stream().ready_chunks(10000), "Second window (might need to drag to see the other window)"); 19 | 20 | // Draw to both windows at once 21 | let mut p = 0.0f32; 22 | loop { 23 | p += 0.02; 24 | 25 | canvas.draw(|gc| { 26 | gc.clear_canvas(Color::Rgba(1.0, 1.0, 1.0, 1.0)); 27 | 28 | gc.canvas_height(1000.0); 29 | 30 | let x = p.sin() * 500.0; 31 | let y = (p*3.0).cos() * 200.0; 32 | 33 | gc.new_path(); 34 | gc.circle(x, y, 50.0); 35 | gc.fill_color(Color::Rgba(0.7, 0.0, 0.0, 1.0)); 36 | gc.fill(); 37 | }); 38 | 39 | // 60fps 40 | thread::sleep(Duration::from_nanos(1_000_000_000 / 60)); 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /draw/examples/sprite.rs: -------------------------------------------------------------------------------- 1 | use flo_draw::*; 2 | use flo_canvas::*; 3 | 4 | /// 5 | /// Simple example that displays a canvas window and renders a triangle 6 | /// 7 | pub fn main() { 8 | with_2d_graphics(|| { 9 | // Create a window 10 | let canvas = create_drawing_window("Basic sprite rendering"); 11 | 12 | // Sprites are a way to rapidly repeat a set of drawing instructions 13 | canvas.draw(|gc| { 14 | // Clear the canvas and set up the coordinates 15 | gc.clear_canvas(Color::Rgba(0.0, 1.0, 0.0, 1.0)); 16 | gc.canvas_height(1000.0); 17 | gc.center_region(0.0, 0.0, 1000.0, 1000.0); 18 | 19 | // Create a triangle sprite 20 | gc.sprite(SpriteId(0)); 21 | gc.clear_sprite(); 22 | gc.new_path(); 23 | gc.move_to(200.0, 200.0); 24 | gc.line_to(800.0, 200.0); 25 | gc.line_to(500.0, 800.0); 26 | gc.line_to(200.0, 200.0); 27 | 28 | gc.fill_color(Color::Rgba(0.8, 0.4, 0.2, 1.0)); 29 | gc.fill(); 30 | 31 | // Draw the triangle in a few places 32 | gc.layer(LayerId(0)); 33 | 34 | gc.sprite_transform(SpriteTransform::Identity); 35 | gc.draw_sprite(SpriteId(0)); 36 | 37 | gc.sprite_transform(SpriteTransform::Identity); 38 | gc.sprite_transform(SpriteTransform::Scale(0.5, 0.5)); 39 | gc.draw_sprite(SpriteId(0)); 40 | 41 | gc.sprite_transform(SpriteTransform::Identity); 42 | gc.sprite_transform(SpriteTransform::Rotate(30.0)); 43 | gc.draw_sprite(SpriteId(0)); 44 | 45 | gc.sprite_transform(SpriteTransform::Identity); 46 | gc.sprite_transform(SpriteTransform::Translate(100.0, 100.0)); 47 | gc.draw_sprite(SpriteId(0)); 48 | 49 | gc.sprite_transform(SpriteTransform::Identity); 50 | gc.sprite_transform(SpriteTransform::Translate(200.0, 100.0)); 51 | gc.draw_sprite(SpriteId(0)); 52 | 53 | gc.sprite_transform(SpriteTransform::Identity); 54 | gc.sprite_transform(SpriteTransform::Transform2D(Transform2D::translate(300.0, 100.0))); 55 | gc.draw_sprite(SpriteId(0)); 56 | }); 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /draw/examples/texture.rs: -------------------------------------------------------------------------------- 1 | use flo_draw::*; 2 | use flo_draw::canvas::*; 3 | 4 | use std::io; 5 | 6 | /// 7 | /// Simple example that displays a canvas window and renders an image from a png file 8 | /// 9 | pub fn main() { 10 | // 'with_2d_graphics' is used to support operating systems that can't run event loops anywhere other than the main thread 11 | with_2d_graphics(|| { 12 | // Load a png file 13 | let flo_bytes: &[u8] = include_bytes!["flo_drawing_on_window.png"]; 14 | 15 | // Create a window 16 | let canvas = create_drawing_window("Flo drawing on a window"); 17 | 18 | // Render the png to the window 19 | canvas.draw(|gc| { 20 | // Clear the canvas and set up the coordinates 21 | gc.clear_canvas(Color::Rgba(1.0, 1.0, 1.0, 1.0)); 22 | gc.canvas_height(1000.0); 23 | gc.center_region(0.0, 0.0, 1000.0, 1000.0); 24 | 25 | // Set up the texture 26 | let (flo_w, flo_h) = gc.load_texture(TextureId(0), io::Cursor::new(flo_bytes)).unwrap(); 27 | 28 | let ratio = (flo_w as f32)/(flo_h as f32); 29 | let height = 1000.0 / ratio; 30 | let y_pos = (1000.0-height)/2.0; 31 | 32 | // Draw a rectangle... 33 | gc.new_path(); 34 | gc.rect(0.0, y_pos, 1000.0, y_pos+height); 35 | 36 | // Fill with the texture we just loaded 37 | gc.fill_texture(TextureId(0), 0.0, y_pos+height as f32, 1000.0, y_pos); 38 | gc.fill(); 39 | }); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /draw/examples/update_title.rs: -------------------------------------------------------------------------------- 1 | use flo_draw::*; 2 | use flo_canvas::*; 3 | use flo_binding::*; 4 | 5 | use std::thread; 6 | use std::time::{Duration}; 7 | 8 | /// 9 | /// Simple example that displays a canvas window, then updates the title once a second 10 | /// 11 | pub fn main() { 12 | // 'with_2d_graphics' is used to support operating systems that can't run event loops anywhere other than the main thread 13 | with_2d_graphics(|| { 14 | // Create some window properties with a title binding 15 | let title = bind("Title".to_string()); 16 | let mut window_properties = WindowProperties::from(&()); 17 | 18 | window_properties.title = BindRef::from(title.clone()); 19 | 20 | // Create a window with these properties 21 | let canvas = create_drawing_window(window_properties); 22 | 23 | // Render a triangle to it 24 | canvas.draw(|gc| { 25 | // Clear the canvas and set up the coordinates 26 | gc.clear_canvas(Color::Rgba(0.3, 0.2, 0.0, 1.0)); 27 | gc.canvas_height(1000.0); 28 | gc.center_region(0.0, 0.0, 1000.0, 1000.0); 29 | 30 | // Draw a rectangle... 31 | gc.new_path(); 32 | gc.move_to(0.0, 0.0); 33 | gc.line_to(1000.0, 0.0); 34 | gc.line_to(1000.0, 1000.0); 35 | gc.line_to(0.0, 1000.0); 36 | gc.line_to(0.0, 0.0); 37 | 38 | gc.fill_color(Color::Rgba(1.0, 1.0, 0.8, 1.0)); 39 | gc.fill(); 40 | 41 | // Draw a triangle on top 42 | gc.new_path(); 43 | gc.move_to(200.0, 200.0); 44 | gc.line_to(800.0, 200.0); 45 | gc.line_to(500.0, 800.0); 46 | gc.line_to(200.0, 200.0); 47 | 48 | gc.fill_color(Color::Rgba(0.0, 0.0, 0.8, 1.0)); 49 | gc.fill(); 50 | }); 51 | 52 | // Fairly boring 'update the title once a second' sequence 53 | let mut count = 0; 54 | loop { 55 | thread::sleep(Duration::from_secs(1)); 56 | 57 | count += 1; 58 | title.set(format!("Running for {} seconds", count)); 59 | } 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /draw/guide_images/s_gradients.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/guide_images/s_gradients.png -------------------------------------------------------------------------------- /draw/guide_images/s_graphics_primitives.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/guide_images/s_graphics_primitives.png -------------------------------------------------------------------------------- /draw/guide_images/s_layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/guide_images/s_layers.png -------------------------------------------------------------------------------- /draw/guide_images/s_sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/guide_images/s_sprites.png -------------------------------------------------------------------------------- /draw/guide_images/s_text_layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/guide_images/s_text_layout.png -------------------------------------------------------------------------------- /draw/guide_images/s_text_rendering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/guide_images/s_text_rendering.png -------------------------------------------------------------------------------- /draw/guide_images/s_textures.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/guide_images/s_textures.png -------------------------------------------------------------------------------- /draw/guide_images/s_transforms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/guide_images/s_transforms.png -------------------------------------------------------------------------------- /draw/images/beeb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/images/beeb.png -------------------------------------------------------------------------------- /draw/images/mandelbrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/images/mandelbrot.png -------------------------------------------------------------------------------- /draw/images/mascot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/images/mascot.png -------------------------------------------------------------------------------- /draw/images/wibble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/draw/images/wibble.png -------------------------------------------------------------------------------- /draw/src/draw_scene/glutin_scene.rs: -------------------------------------------------------------------------------- 1 | use crate::glutin::*; 2 | 3 | use futures::prelude::*; 4 | use once_cell::sync::Lazy; 5 | 6 | use flo_scene::*; 7 | 8 | use std::sync::*; 9 | 10 | /// The scene context used for flo_draw, or None if a scene context has not been created yet 11 | static DRAW_SCENE: Lazy>>> = Lazy::new(|| Mutex::new(None)); 12 | 13 | /// 14 | /// Retrieves or creates a scene for flo_draw 15 | /// 16 | #[allow(dead_code)] 17 | pub fn flo_draw_glutin_scene() -> Arc { 18 | let mut scene = DRAW_SCENE.lock().unwrap(); 19 | 20 | // Start a new scene if none was running 21 | if scene.is_none() { 22 | // Create a new scene, and run it on the glutin thread 23 | let new_scene = Arc::new(Scene::default()); 24 | 25 | // Store as the active scene 26 | *scene = Some(Arc::clone(&new_scene)); 27 | 28 | // Run on the glutin thread 29 | glutin_thread().send_event(GlutinThreadEvent::RunProcess(Box::new(move || async move { 30 | new_scene.run_scene_with_threads(4).await; 31 | }.boxed()))); 32 | } 33 | 34 | // Unwrap the scene context 35 | scene.as_ref().unwrap().clone() 36 | } 37 | -------------------------------------------------------------------------------- /draw/src/draw_scene/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! The draw_scene module provides an interface to flo_draw using the flo_scene library. 3 | //! 4 | //! `flo_scene` is a message and property passing framework. It's good for developing more complex applications 5 | //! with flo_draw, such as those with a user interface. 6 | //! 7 | //! There are three main types of request: a `RenderRequest` which is a request for low-level graphics operations, 8 | //! a `DrawRequest` which is a request for a high-level 2D graphics operation, and a `DrawEventRequest` which is 9 | //! a request in the other direction to process a user interaction. 10 | //! 11 | //! `DrawWindowRequest` provides a set of requests for interacting directly with the window: this is mainly a way 12 | //! to obtain the events and rendering event channels for a particular window. 13 | //! 14 | 15 | mod render_window_program; 16 | mod drawing_window_program; 17 | mod scene; 18 | 19 | #[cfg(feature="render-opengl")] 20 | mod glutin_render_window_program; 21 | #[cfg(feature="render-opengl")] 22 | mod glutin_scene; 23 | 24 | #[cfg(feature="render-wgpu")] 25 | mod wgpu_render_window_program; 26 | #[cfg(feature="render-wgpu")] 27 | mod wgpu_scene; 28 | 29 | #[cfg(feature="render-software")] 30 | mod software_drawing_window_program; 31 | #[cfg(feature="render-software")] 32 | mod software_scene; 33 | 34 | pub use self::render_window_program::*; 35 | pub use self::drawing_window_program::*; 36 | pub use self::scene::*; 37 | -------------------------------------------------------------------------------- /draw/src/draw_scene/render_window_program.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(feature="render-opengl", not(feature="render-wgpu")))] 2 | use super::glutin_render_window_program::*; 3 | 4 | #[cfg(feature="render-wgpu")] 5 | use super::wgpu_render_window_program::*; 6 | 7 | #[cfg(feature="render-software")] 8 | use super::software_drawing_window_program::*; 9 | 10 | use flo_scene::*; 11 | 12 | use std::sync::*; 13 | 14 | /// 15 | /// Creates a render window in a scene for OpenGL rendering with the specified program ID 16 | /// 17 | #[cfg(all(feature="render-opengl", not(feature="render-wgpu")))] 18 | pub fn create_render_window_sub_program(scene: &Arc, program_id: SubProgramId, initial_size: (u64, u64)) -> Result<(), ConnectionError> { 19 | create_glutin_render_window_program(scene, program_id, initial_size) 20 | } 21 | 22 | /// 23 | /// Creates a render window in a scene for WGPU rendering, with a specified program ID 24 | /// 25 | #[cfg(all(feature="render-wgpu"))] 26 | pub fn create_render_window_sub_program(scene: &Arc, program_id: SubProgramId, initial_size: (u64, u64)) -> Result<(), ConnectionError> { 27 | create_wgpu_render_window_program(scene, program_id, initial_size) 28 | } 29 | 30 | /// 31 | /// Creates a drawing window in a scene for software rendering, with a specified program ID 32 | /// 33 | /// (Note that a drawing window differs from a render window in that it takes canvas drawing instructions directly instead of render actions) 34 | /// 35 | #[cfg(all(feature="render-software", not(any(feature="render-opengl", feature="render-wgpu"))))] 36 | pub fn create_render_window_sub_program(scene: &Arc, program_id: SubProgramId, initial_size: (u64, u64)) -> Result<(), ConnectionError> { 37 | create_software_draw_window_program(scene, program_id, initial_size) 38 | } 39 | 40 | /// 41 | /// Retrieves or creates a scene context for flo_draw 42 | /// 43 | #[cfg(all(not(feature="render-wgpu"), not(feature="render-opengl"), not(feature="render-software")))] 44 | pub fn create_render_window_sub_program(context: &Arc, program_id: SubProgramId, initial_size: (u64, u64)) -> Result<(), ConnectionError> { 45 | panic!("No default renderer was specified when flo_draw was compiled (use `render-wgpu` or `render-opengl`)") 46 | } 47 | -------------------------------------------------------------------------------- /draw/src/draw_scene/scene.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature="render-opengl")] 2 | use super::glutin_scene::*; 3 | 4 | #[cfg(feature="render-wgpu")] 5 | use super::wgpu_scene::*; 6 | 7 | #[cfg(feature="render-software")] 8 | use super::software_scene::*; 9 | 10 | use flo_scene::*; 11 | use std::sync::*; 12 | 13 | /// 14 | /// Retrieves or creates a scene context for flo_draw 15 | /// 16 | #[cfg(all(feature="render-opengl", not(feature="render-wgpu")))] 17 | pub fn flo_draw_scene_context() -> Arc { 18 | flo_draw_glutin_scene() 19 | } 20 | 21 | /// 22 | /// Retrieves or creates a scene context for flo_draw 23 | /// 24 | #[cfg(all(feature="render-wgpu"))] 25 | pub fn flo_draw_scene_context() -> Arc { 26 | flo_draw_wgpu_scene() 27 | } 28 | 29 | 30 | /// 31 | /// Retrieves or creates a scene context for flo_draw 32 | /// 33 | #[cfg(all(feature="render-software", not(any(feature="render-wgpu", feature="render-opengl"))))] 34 | pub fn flo_draw_scene_context() -> Arc { 35 | flo_draw_software_scene() 36 | } 37 | 38 | /// 39 | /// Retrieves or creates a scene context for flo_draw 40 | /// 41 | #[cfg(all(not(feature="render-wgpu"), not(feature="render-opengl"), not(feature="render-software")))] 42 | pub fn flo_draw_scene_context() -> Arc { 43 | panic!("No default renderer was specified when flo_draw was compiled (use `render-wgpu`, `render-opengl` or `render-software`)") 44 | } 45 | -------------------------------------------------------------------------------- /draw/src/draw_scene/software_scene.rs: -------------------------------------------------------------------------------- 1 | use crate::software::*; 2 | 3 | use futures::prelude::*; 4 | use once_cell::sync::{Lazy}; 5 | 6 | use flo_scene::*; 7 | 8 | use std::sync::*; 9 | 10 | /// The scene context used for flo_draw, or None if a scene context has not been created yet 11 | static DRAW_SCENE_CONTEXT: Lazy>>> = Lazy::new(|| Mutex::new(None)); 12 | 13 | /// 14 | /// Retrieves or creates a scene for flo_draw, intended for the software renderer 15 | /// 16 | pub fn flo_draw_software_scene() -> Arc { 17 | let mut scene = DRAW_SCENE_CONTEXT.lock().unwrap(); 18 | 19 | // Start a new scene if none was running 20 | if scene.is_none() { 21 | // Create a new scene context, and run it on the winit thread 22 | let new_scene = Arc::new(Scene::default()); 23 | 24 | // Store as the active scene 25 | *scene = Some(Arc::clone(&new_scene)); 26 | 27 | // Run on the winit thread 28 | winit_thread().send_event(WinitThreadEvent::RunProcess(Box::new(move || async move { 29 | new_scene.run_scene_with_threads(4).await; 30 | }.boxed()))); 31 | } 32 | 33 | // Unwrap the scene context 34 | scene.as_ref().unwrap().clone() 35 | } 36 | -------------------------------------------------------------------------------- /draw/src/draw_scene/wgpu_scene.rs: -------------------------------------------------------------------------------- 1 | use crate::wgpu::*; 2 | 3 | use futures::prelude::*; 4 | use once_cell::sync::{Lazy}; 5 | 6 | use flo_scene::*; 7 | 8 | use std::sync::*; 9 | 10 | /// The scene context used for flo_draw, or None if a scene context has not been created yet 11 | static DRAW_SCENE_CONTEXT: Lazy>>> = Lazy::new(|| Mutex::new(None)); 12 | 13 | /// 14 | /// Retrieves or creates a scene for flo_draw 15 | /// 16 | pub fn flo_draw_wgpu_scene() -> Arc { 17 | let mut scene = DRAW_SCENE_CONTEXT.lock().unwrap(); 18 | 19 | // Start a new scene if none was running 20 | if scene.is_none() { 21 | // Create a new scene context, and run it on the winit thread 22 | let new_scene = Arc::new(Scene::default()); 23 | 24 | // Store as the active scene 25 | *scene = Some(Arc::clone(&new_scene)); 26 | 27 | // Run on the winit thread 28 | winit_thread().send_event(WinitThreadEvent::RunProcess(Box::new(move || async move { 29 | new_scene.run_scene_with_threads(4).await; 30 | }.boxed()))); 31 | } 32 | 33 | // Unwrap the scene context 34 | scene.as_ref().unwrap().clone() 35 | } 36 | -------------------------------------------------------------------------------- /draw/src/glutin/glutin_thread_event.rs: -------------------------------------------------------------------------------- 1 | use crate::events::*; 2 | use crate::window_properties::*; 3 | 4 | use flo_stream::*; 5 | use flo_render::*; 6 | 7 | use futures::future::{LocalBoxFuture}; 8 | use futures::stream::{BoxStream}; 9 | 10 | use winit::window::{WindowId}; 11 | 12 | /// 13 | /// Event that can be sent to a glutin thread 14 | /// 15 | pub enum GlutinThreadEvent { 16 | /// Creates a window that will render the specified actions 17 | CreateRenderWindow(BoxStream<'static, Vec>, Publisher, WindowProperties), 18 | 19 | /// Runs a future on the Glutin thread 20 | RunProcess(Box LocalBoxFuture<'static, ()>>), 21 | 22 | /// Polls the future with the specified ID 23 | WakeFuture(u64), 24 | 25 | /// Stop sending events for the specified window 26 | StopSendingToWindow(WindowId), 27 | 28 | /// Tells the UI thread to stop when there are no more windows open 29 | StopWhenAllWindowsClosed 30 | } 31 | -------------------------------------------------------------------------------- /draw/src/glutin/mod.rs: -------------------------------------------------------------------------------- 1 | mod glutin_thread; 2 | mod glutin_window; 3 | mod glutin_runtime; 4 | mod event_conversion; 5 | mod glutin_thread_event; 6 | 7 | pub (crate) use self::glutin_thread::*; 8 | pub (crate) use self::glutin_thread_event::*; 9 | 10 | pub use self::glutin_thread::{with_2d_graphics}; 11 | -------------------------------------------------------------------------------- /draw/src/software/mod.rs: -------------------------------------------------------------------------------- 1 | mod event_conversion; 2 | mod winit_window; 3 | mod winit_thread; 4 | mod winit_runtime; 5 | mod winit_thread_event; 6 | 7 | pub (crate) use self::winit_thread::*; 8 | pub (crate) use self::winit_thread_event::*; 9 | 10 | pub use self::winit_thread::{with_2d_graphics}; 11 | -------------------------------------------------------------------------------- /draw/src/software/winit_thread_event.rs: -------------------------------------------------------------------------------- 1 | use crate::events::*; 2 | use crate::window_properties::*; 3 | 4 | use flo_canvas::*; 5 | use flo_stream::*; 6 | 7 | use futures::future::{LocalBoxFuture}; 8 | use futures::stream::{BoxStream}; 9 | use futures::channel::oneshot; 10 | 11 | use winit::window::{WindowId}; 12 | 13 | use std::fmt; 14 | use std::fmt::*; 15 | use std::sync::*; 16 | 17 | /// 18 | /// Event that can be sent to a winit thread 19 | /// 20 | pub enum WinitThreadEvent { 21 | /// Creates a window that will render the specified actions 22 | CreateDrawingWindow(BoxStream<'static, Arc>>, Publisher, WindowProperties), 23 | 24 | /// Runs a future on the winit thread 25 | RunProcess(Box LocalBoxFuture<'static, ()>>), 26 | 27 | /// Polls the future with the specified ID 28 | WakeFuture(u64), 29 | 30 | /// Resolves a yield request by sending an empty message (used to yield to process events) 31 | Yield(oneshot::Sender<()>), 32 | 33 | /// Stop sending events for the specified window 34 | StopSendingToWindow(WindowId), 35 | 36 | /// Tells the UI thread to stop when there are no more windows open 37 | StopWhenAllWindowsClosed, 38 | } 39 | 40 | impl Debug for WinitThreadEvent { 41 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 42 | use self::WinitThreadEvent::*; 43 | 44 | match self { 45 | CreateDrawingWindow(_, _, _) => write!(f, "CreateDrawingWindow(...)"), 46 | RunProcess(_) => write!(f, "RunProcess(...)"), 47 | WakeFuture(id) => write!(f, "WakeFuture({})", id), 48 | Yield(_) => write!(f, "Yield(...)"), 49 | StopSendingToWindow(id) => write!(f, "StopSendingToWindow({:?})", id), 50 | StopWhenAllWindowsClosed => write!(f, "StopWhenAllWindowsClosed"), 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /draw/src/wgpu/mod.rs: -------------------------------------------------------------------------------- 1 | mod event_conversion; 2 | mod winit_window; 3 | mod winit_thread; 4 | mod winit_runtime; 5 | mod winit_thread_event; 6 | 7 | pub (crate) use self::winit_thread::*; 8 | pub (crate) use self::winit_thread_event::*; 9 | 10 | pub use self::winit_thread::{with_2d_graphics}; 11 | -------------------------------------------------------------------------------- /draw/src/wgpu/winit_thread_event.rs: -------------------------------------------------------------------------------- 1 | use crate::events::*; 2 | use crate::window_properties::*; 3 | 4 | use flo_stream::*; 5 | use flo_render::*; 6 | 7 | use futures::future::{LocalBoxFuture}; 8 | use futures::stream::{BoxStream}; 9 | use futures::channel::oneshot; 10 | 11 | use wgpu; 12 | use winit::window::{WindowId}; 13 | 14 | use std::fmt; 15 | use std::fmt::*; 16 | 17 | /// 18 | /// Event that can be sent to a winit thread 19 | /// 20 | pub enum WinitThreadEvent { 21 | /// Creates a window that will render the specified actions 22 | CreateRenderWindow(BoxStream<'static, Vec>, Publisher, WindowProperties), 23 | 24 | /// Runs a future on the winit thread 25 | RunProcess(Box LocalBoxFuture<'static, ()>>), 26 | 27 | /// Polls the future with the specified ID 28 | WakeFuture(u64), 29 | 30 | /// Presents a surface to the specified window and signals the sender when done (cancelling any previous request for that window) 31 | PresentSurface(WindowId, wgpu::SurfaceTexture, oneshot::Sender<()>), 32 | 33 | /// Resolves a yield request by sending an empty message (used to yield to process events) 34 | Yield(oneshot::Sender<()>), 35 | 36 | /// Stop sending events for the specified window 37 | StopSendingToWindow(WindowId), 38 | 39 | /// Tells the UI thread to stop when there are no more windows open 40 | StopWhenAllWindowsClosed, 41 | } 42 | 43 | impl Debug for WinitThreadEvent { 44 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 45 | use self::WinitThreadEvent::*; 46 | 47 | match self { 48 | CreateRenderWindow(_, _, _) => write!(f, "CreateRenderWindow(...)"), 49 | RunProcess(_) => write!(f, "RunProcess(...)"), 50 | WakeFuture(id) => write!(f, "WakeFuture({})", id), 51 | PresentSurface(id, _, _) => write!(f, "PresentSurface({:?}, ...)", id), 52 | Yield(_) => write!(f, "Yield(...)"), 53 | StopSendingToWindow(id) => write!(f, "StopSendingToWindow({:?})", id), 54 | StopWhenAllWindowsClosed => write!(f, "StopWhenAllWindowsClosed"), 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /draw/tests/readme.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | 3 | /// 4 | /// Reads the README file for the crate 5 | /// 6 | fn readme() -> &'static str { 7 | let readme_bytes = include_bytes!("../README.md"); 8 | let readme_str = str::from_utf8(readme_bytes); 9 | 10 | readme_str.expect("Could not decode README.md") 11 | } 12 | 13 | #[test] 14 | fn starts_with_version_number_toml() { 15 | let major_version = env!("CARGO_PKG_VERSION_MAJOR"); 16 | let minor_version = env!("CARGO_PKG_VERSION_MINOR"); 17 | 18 | let expected = format!("```toml 19 | flo_draw = \"{}.{}\" 20 | ```", major_version, minor_version); 21 | 22 | println!("{}", expected); 23 | assert!(readme().starts_with(&expected)); 24 | } 25 | -------------------------------------------------------------------------------- /images/bounce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/images/bounce.png -------------------------------------------------------------------------------- /images/flo_drawing_on_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/images/flo_drawing_on_window.png -------------------------------------------------------------------------------- /images/flo_drawing_on_window_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/images/flo_drawing_on_window_small.png -------------------------------------------------------------------------------- /images/gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/images/gradient.png -------------------------------------------------------------------------------- /images/mandelbrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/images/mandelbrot.png -------------------------------------------------------------------------------- /images/mascot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/images/mascot.png -------------------------------------------------------------------------------- /images/textlayout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/images/textlayout.png -------------------------------------------------------------------------------- /images/wibble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/images/wibble.png -------------------------------------------------------------------------------- /render/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flo_render" 3 | version = "0.4.0" 4 | authors = ["Andrew Hunter"] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | repository = "https://github.com/Logicalshift/flowbetween" 8 | description = "Streaming API for rendering graphics on hardware" 9 | categories = [ "graphics", "rendering", "rendering::graphics-api" ] 10 | resolver = "2" 11 | 12 | include = [ "Cargo.toml", "src/**/*", "shaders/**/*", "bindings/**/*", "build.rs" ] 13 | 14 | [features] 15 | opengl = [ "gl", "libc", "flo_render_gl_offscreen" ] 16 | osx-metal = [ "metal", "cocoa", "flo_canvas" ] 17 | render-wgpu = [ "wgpu", "flo_canvas", "once_cell", "desync", "futures" ] 18 | profile = [ ] 19 | 20 | [build-dependencies] 21 | bindgen = "0.69" 22 | 23 | [dependencies] 24 | gl = { version = "0.14", optional = true } 25 | wgpu = { workspace = true, optional = true } 26 | metal = { version = "0.32", optional = true } 27 | cocoa = { version = "0.26", optional = true } 28 | libc = { version = "0.2", optional = true } 29 | flo_canvas = { version = "0.4", optional = true } 30 | flo_render_gl_offscreen = { version = "0.4", optional = true } 31 | desync = { version = "0.9", optional = true } 32 | once_cell = { version = "1.18", optional = true } 33 | futures = { version = "0.3", optional = true } 34 | wgpu-profiler = { workspace = true, optional = true } 35 | serde = { version = "1.0", features = [ "serde_derive" ] } 36 | 37 | [dev-dependencies] 38 | winit = "0.30" 39 | futures = "0.3" 40 | 41 | [target.'cfg(target_os = "macos")'.dependencies] 42 | core-foundation = "0.10" 43 | -------------------------------------------------------------------------------- /render/bindings/metal_bindings.h: -------------------------------------------------------------------------------- 1 | /// 2 | /// The input locations for the Metal vertex shaders 3 | /// 4 | typedef enum VertexInputIndex { 5 | /// The transformation matrix 6 | VertexInputIndexMatrix = 0, 7 | 8 | /// The vertices to render 9 | VertexInputIndexVertices = 1, 10 | 11 | /// The texture transformation matrix 12 | VertexTextureMatrix = 2 13 | } VertexInputIndex; 14 | 15 | /// 16 | /// The input locations for the Metal fragment shaders 17 | /// 18 | typedef enum FragmentInputIndex { 19 | /// The texture to render 20 | FragmentIndexTexture = 0, 21 | 22 | /// The eraser texture to render 23 | FragmentIndexEraseTexture = 1, 24 | 25 | /// The clip mask texture to apply to the rendering 26 | FragmentIndexClipMaskTexture = 2, 27 | 28 | /// The alpha value to use for the fragment 29 | FragmentAlpha = 3 30 | } FragmentInputIndex; 31 | -------------------------------------------------------------------------------- /render/bindings/metal_vertex2d.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "metal_bindings.h" 4 | 5 | typedef struct MetalVertex2D { 6 | vector_float2 pos; 7 | vector_float2 tex_coord; 8 | vector_uchar4 color; 9 | } MetalVertex2D; 10 | -------------------------------------------------------------------------------- /render/shaders/filters/alpha_blend.glslf: -------------------------------------------------------------------------------- 1 | uniform sampler2D t_Texture; 2 | out vec4 f_Color; 3 | uniform float texture_alpha; 4 | 5 | void main() { 6 | ivec2 pos = ivec2(gl_FragCoord.x, gl_FragCoord.y); 7 | f_Color = texelFetch(t_Texture, pos, 0); 8 | 9 | #ifdef INVERT_COLOUR_ALPHA 10 | // Blend towards one as the alpha approaches 0 (used for the multiply blend mode) 11 | f_Color[0] = 1 - ((1-f_Color[0]) * (f_Color[3])); 12 | f_Color[1] = 1 - ((1-f_Color[1]) * (f_Color[3])); 13 | f_Color[2] = 1 - ((1-f_Color[2]) * (f_Color[3])); 14 | #endif 15 | 16 | f_Color *= texture_alpha; 17 | } 18 | -------------------------------------------------------------------------------- /render/shaders/filters/alpha_blend.wgsl: -------------------------------------------------------------------------------- 1 | struct RasterData { 2 | @location(0) texture_pos: vec2, 3 | @builtin(position) pos: vec4 4 | } 5 | 6 | @group(0) 7 | @binding(0) 8 | var input_texture: texture_2d; 9 | 10 | @group(0) 11 | @binding(1) 12 | var f_alpha: f32; 13 | 14 | @vertex 15 | fn filter_vertex_shader( 16 | @location(0) pos: vec2, 17 | @location(1) tex_coord: vec2, 18 | @location(2) color: vec4, 19 | ) -> RasterData { 20 | var result: RasterData; 21 | 22 | let texture_size = vec2(textureDimensions(input_texture)); 23 | let texture_pos_1 = vec2((pos[0]+1.0)/2.0, 1.0-((pos[1]+1.0)/2.0)); 24 | let texture_pos = vec2(texture_size * texture_pos_1); 25 | 26 | result.pos = vec4(pos[0], pos[1], 0.0, 1.0); 27 | result.texture_pos = texture_pos; 28 | 29 | return result; 30 | } 31 | 32 | @fragment 33 | fn filter_fragment_shader_premultiply(vertex: RasterData) -> @location(0) vec4 { 34 | let texture_pos = vec2(vertex.texture_pos); 35 | 36 | var color = textureLoad(input_texture, texture_pos, 0); 37 | color = color * f_alpha; 38 | 39 | return color; 40 | } 41 | 42 | @fragment 43 | fn filter_fragment_shader_not_premultiplied(vertex: RasterData) -> @location(0) vec4 { 44 | let texture_pos = vec2(vertex.texture_pos); 45 | 46 | var color = textureLoad(input_texture, texture_pos, 0); 47 | color[3] *= f_alpha; 48 | 49 | return color; 50 | } 51 | -------------------------------------------------------------------------------- /render/shaders/filters/blur_29.glslf: -------------------------------------------------------------------------------- 1 | uniform sampler2D t_Texture; 2 | uniform float t_Offset[7]; 3 | uniform float t_Weight[7]; 4 | out vec4 f_Color; 5 | 6 | // See for a description of how we use bilinear sampling here 7 | 8 | // Horizontal and vertical blurs can be done in separate passes, and a blur can be increased to a larger radius by repeatedly applying the effect 9 | void main() { 10 | vec2 size = textureSize(t_Texture, 0); 11 | 12 | f_Color = texture(t_Texture, vec2(gl_FragCoord) / size) * t_Weight[0]; 13 | 14 | for (int idx=1; idx<7; ++idx) { 15 | #ifdef FILTER_HORIZ 16 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) + vec2(t_Offset[idx], 0.0)) / size) * t_Weight[idx]; 17 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) - vec2(t_Offset[idx], 0.0)) / size) * t_Weight[idx]; 18 | #else 19 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) + vec2(0.0, t_Offset[idx])) / size) * t_Weight[idx]; 20 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) - vec2(0.0, t_Offset[idx])) / size) * t_Weight[idx]; 21 | #endif 22 | } 23 | 24 | #ifdef INVERT_COLOUR_ALPHA 25 | // Blend towards one as the alpha approaches 0 (used for the multiply blend mode) 26 | f_Color[0] = 1 - ((1-f_Color[0]) * (f_Color[3])); 27 | f_Color[1] = 1 - ((1-f_Color[1]) * (f_Color[3])); 28 | f_Color[2] = 1 - ((1-f_Color[2]) * (f_Color[3])); 29 | #endif 30 | 31 | #ifdef MULTIPLY_ALPHA 32 | // This means that the input texture does not have pre-multiplied alpha but we want the output texture to be set up this way 33 | // This is used in particular for some blend modes (Multiply, Screen) 34 | f_Color[0] *= f_Color[3]; 35 | f_Color[1] *= f_Color[3]; 36 | f_Color[2] *= f_Color[3]; 37 | #endif 38 | } 39 | -------------------------------------------------------------------------------- /render/shaders/filters/blur_61.glslf: -------------------------------------------------------------------------------- 1 | uniform sampler2D t_Texture; 2 | uniform float t_Offset[15]; 3 | uniform float t_Weight[15]; 4 | out vec4 f_Color; 5 | 6 | // See for a description of how we use bilinear sampling here 7 | 8 | // Horizontal and vertical blurs can be done in separate passes, and a blur can be increased to a larger radius by repeatedly applying the effect 9 | void main() { 10 | vec2 size = textureSize(t_Texture, 0); 11 | 12 | f_Color = texture(t_Texture, vec2(gl_FragCoord) / size) * t_Weight[0]; 13 | 14 | for (int idx=1; idx<15; ++idx) { 15 | #ifdef FILTER_HORIZ 16 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) + vec2(t_Offset[idx], 0.0)) / size) * t_Weight[idx]; 17 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) - vec2(t_Offset[idx], 0.0)) / size) * t_Weight[idx]; 18 | #else 19 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) + vec2(0.0, t_Offset[idx])) / size) * t_Weight[idx]; 20 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) - vec2(0.0, t_Offset[idx])) / size) * t_Weight[idx]; 21 | #endif 22 | } 23 | 24 | #ifdef INVERT_COLOUR_ALPHA 25 | // Blend towards one as the alpha approaches 0 (used for the multiply blend mode) 26 | f_Color[0] = 1 - ((1-f_Color[0]) * (f_Color[3])); 27 | f_Color[1] = 1 - ((1-f_Color[1]) * (f_Color[3])); 28 | f_Color[2] = 1 - ((1-f_Color[2]) * (f_Color[3])); 29 | #endif 30 | 31 | #ifdef MULTIPLY_ALPHA 32 | // This means that the input texture does not have pre-multiplied alpha but we want the output texture to be set up this way 33 | // This is used in particular for some blend modes (Multiply, Screen) 34 | f_Color[0] *= f_Color[3]; 35 | f_Color[1] *= f_Color[3]; 36 | f_Color[2] *= f_Color[3]; 37 | #endif 38 | } 39 | -------------------------------------------------------------------------------- /render/shaders/filters/blur_9.glslf: -------------------------------------------------------------------------------- 1 | uniform sampler2D t_Texture; 2 | uniform float t_Offset[3]; 3 | uniform float t_Weight[3]; 4 | out vec4 f_Color; 5 | 6 | // See for a description of how we use bilinear sampling here 7 | 8 | // Horizontal and vertical blurs can be done in separate passes, and a blur can be increased to a larger radius by repeatedly applying the effect 9 | void main() { 10 | vec2 size = textureSize(t_Texture, 0); 11 | 12 | f_Color = texture(t_Texture, vec2(gl_FragCoord) / size) * t_Weight[0]; 13 | 14 | #ifdef FILTER_HORIZ 15 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) + vec2(t_Offset[1], 0.0)) / size) * t_Weight[1]; 16 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) + vec2(t_Offset[2], 0.0)) / size) * t_Weight[2]; 17 | 18 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) - vec2(t_Offset[1], 0.0)) / size) * t_Weight[1]; 19 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) - vec2(t_Offset[2], 0.0)) / size) * t_Weight[2]; 20 | #else 21 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) + vec2(0.0, t_Offset[1])) / size) * t_Weight[1]; 22 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) + vec2(0.0, t_Offset[2])) / size) * t_Weight[2]; 23 | 24 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) - vec2(0.0, t_Offset[1])) / size) * t_Weight[1]; 25 | f_Color += texture(t_Texture, (vec2(gl_FragCoord) - vec2(0.0, t_Offset[2])) / size) * t_Weight[2]; 26 | #endif 27 | 28 | #ifdef INVERT_COLOUR_ALPHA 29 | // Blend towards one as the alpha approaches 0 (used for the multiply blend mode) 30 | f_Color[0] = 1 - ((1-f_Color[0]) * (f_Color[3])); 31 | f_Color[1] = 1 - ((1-f_Color[1]) * (f_Color[3])); 32 | f_Color[2] = 1 - ((1-f_Color[2]) * (f_Color[3])); 33 | #endif 34 | 35 | #ifdef MULTIPLY_ALPHA 36 | // This means that the input texture does not have pre-multiplied alpha but we want the output texture to be set up this way 37 | // This is used in particular for some blend modes (Multiply, Screen) 38 | f_Color[0] *= f_Color[3]; 39 | f_Color[1] *= f_Color[3]; 40 | f_Color[2] *= f_Color[3]; 41 | #endif 42 | } 43 | -------------------------------------------------------------------------------- /render/shaders/filters/blur_texture.glslf: -------------------------------------------------------------------------------- 1 | uniform sampler2D t_Texture; 2 | uniform sampler1D t_OffsetTexture; 3 | uniform sampler1D t_WeightTexture; 4 | out vec4 f_Color; 5 | 6 | // See for a description of how we use bilinear sampling here 7 | 8 | // Horizontal and vertical blurs can be done in separate passes, and a blur can be increased to a larger radius by repeatedly applying the effect 9 | void main() { 10 | int num_weights = textureSize(t_WeightTexture, 0); 11 | vec2 size = textureSize(t_Texture, 0); 12 | 13 | float weight = texelFetch(t_WeightTexture, 0, 0)[0]; 14 | f_Color = texture(t_Texture, vec2(gl_FragCoord) / size) * weight; 15 | 16 | for (int idx=1; idx, 3 | @location(1) displace_pos: vec2, 4 | @builtin(position) pos: vec4 5 | } 6 | 7 | @group(0) 8 | @binding(0) 9 | var input_texture: texture_2d; 10 | 11 | @group(0) 12 | @binding(1) 13 | var displace_texture: texture_2d; 14 | 15 | @group(0) 16 | @binding(2) 17 | var displace_sampler: sampler; 18 | 19 | @group(0) 20 | @binding(3) 21 | var scale: vec2; 22 | 23 | @vertex 24 | fn filter_vertex_shader( 25 | @location(0) pos: vec2, 26 | @location(1) tex_coord: vec2, 27 | @location(2) color: vec4, 28 | ) -> RasterData { 29 | var result: RasterData; 30 | 31 | let texture_size = vec2(textureDimensions(input_texture)); 32 | let displace_pos = vec2((pos[0]+1.0)/2.0, 1.0-((pos[1]+1.0)/2.0)); 33 | let texture_pos = vec2(texture_size * displace_pos); 34 | 35 | result.pos = vec4(pos[0], pos[1], 0.0, 1.0); 36 | result.displace_pos = displace_pos; 37 | result.texture_pos = texture_pos; 38 | 39 | return result; 40 | } 41 | 42 | @fragment 43 | fn filter_fragment_shader(vertex: RasterData) -> @location(0) vec4 { 44 | let displace_pos = vertex.displace_pos; 45 | 46 | let texture_displace = textureSample(displace_texture, displace_sampler, displace_pos); 47 | let displacement = vec2((texture_displace[0] - 0.5) * 2.0, (texture_displace[1] - 0.5) * 2.0)*scale; 48 | 49 | let color = textureSample(input_texture, displace_sampler, displace_pos + displacement); 50 | 51 | return color; 52 | } 53 | -------------------------------------------------------------------------------- /render/shaders/filters/mask.glslf: -------------------------------------------------------------------------------- 1 | uniform sampler2D t_Texture; 2 | uniform sampler2D t_FilterTexture; 3 | out vec4 f_Color; 4 | 5 | void main() { 6 | ivec2 pos = ivec2(gl_FragCoord.x, gl_FragCoord.y); 7 | f_Color = texelFetch(t_Texture, pos, 0); 8 | 9 | ivec2 tex_size = textureSize(t_Texture, 0); 10 | vec2 alpha_pos = vec2(float(pos[0]) / float(tex_size[0]), float(pos[1]) / float(tex_size[1])); 11 | float texture_alpha = texture(t_FilterTexture, alpha_pos)[3]; 12 | 13 | #ifdef INVERT_COLOUR_ALPHA 14 | // Blend towards one as the alpha approaches 0 (used for the multiply blend mode) 15 | f_Color[0] = 1 - ((1-f_Color[0]) * (f_Color[3])); 16 | f_Color[1] = 1 - ((1-f_Color[1]) * (f_Color[3])); 17 | f_Color[2] = 1 - ((1-f_Color[2]) * (f_Color[3])); 18 | #endif 19 | 20 | f_Color *= texture_alpha; 21 | } 22 | -------------------------------------------------------------------------------- /render/shaders/filters/mask.wgsl: -------------------------------------------------------------------------------- 1 | struct RasterData { 2 | @location(0) texture_pos: vec2, 3 | @location(1) clip_pos: vec2, 4 | @builtin(position) pos: vec4 5 | } 6 | 7 | @group(0) 8 | @binding(0) 9 | var input_texture: texture_2d; 10 | 11 | @group(0) 12 | @binding(1) 13 | var mask_texture: texture_2d; 14 | 15 | @group(0) 16 | @binding(2) 17 | var mask_sampler: sampler; 18 | 19 | @vertex 20 | fn filter_vertex_shader( 21 | @location(0) pos: vec2, 22 | @location(1) tex_coord: vec2, 23 | @location(2) color: vec4, 24 | ) -> RasterData { 25 | var result: RasterData; 26 | 27 | let texture_size = vec2(textureDimensions(input_texture)); 28 | let clip_pos = vec2((pos[0]+1.0)/2.0, 1.0-((pos[1]+1.0)/2.0)); 29 | let texture_pos = vec2(texture_size * clip_pos); 30 | 31 | result.pos = vec4(pos[0], pos[1], 0.0, 1.0); 32 | result.clip_pos = clip_pos; 33 | result.texture_pos = texture_pos; 34 | 35 | return result; 36 | } 37 | 38 | @fragment 39 | fn filter_fragment_shader_premultiply(vertex: RasterData) -> @location(0) vec4 { 40 | let texture_pos = vec2(vertex.texture_pos); 41 | let clip_pos = vertex.clip_pos; 42 | 43 | var color = textureLoad(input_texture, texture_pos, 0); 44 | let clip_color = textureSample(mask_texture, mask_sampler, clip_pos); 45 | 46 | color = color * clip_color[3]; 47 | 48 | return color; 49 | } 50 | 51 | @fragment 52 | fn filter_fragment_shader_no_premultiply(vertex: RasterData) -> @location(0) vec4 { 53 | let texture_pos = vec2(vertex.texture_pos); 54 | let clip_pos = vertex.clip_pos; 55 | 56 | var color = textureLoad(input_texture, texture_pos, 0); 57 | let clip_color = textureSample(mask_texture, mask_sampler, clip_pos); 58 | 59 | color = vec4(color[0], color[1], color[2], color[3] * clip_color[3]); 60 | 61 | return color; 62 | } 63 | -------------------------------------------------------------------------------- /render/shaders/filters/premultiply.glslf: -------------------------------------------------------------------------------- 1 | uniform sampler2D t_Texture; 2 | out vec4 f_Color; 3 | 4 | void main() { 5 | ivec2 pos = ivec2(gl_FragCoord.x, gl_FragCoord.y); 6 | f_Color = texelFetch(t_Texture, pos, 0); 7 | 8 | #ifdef INVERT_COLOUR_ALPHA 9 | // Blend towards one as the alpha approaches 0 (used for the multiply blend mode) 10 | f_Color[0] = 1 - ((1-f_Color[0]) * (f_Color[3])); 11 | f_Color[1] = 1 - ((1-f_Color[1]) * (f_Color[3])); 12 | f_Color[2] = 1 - ((1-f_Color[2]) * (f_Color[3])); 13 | #endif 14 | 15 | f_Color[0] *= f_Color[3]; 16 | f_Color[1] *= f_Color[3]; 17 | f_Color[2] *= f_Color[3]; 18 | } 19 | -------------------------------------------------------------------------------- /render/shaders/filters/reduce.wgsl: -------------------------------------------------------------------------------- 1 | struct RasterData { 2 | @location(0) texture_pos: vec2, 3 | @builtin(position) pos: vec4 4 | } 5 | 6 | @group(0) 7 | @binding(0) 8 | var input_texture: texture_2d; 9 | 10 | @group(0) 11 | @binding(1) 12 | var input_sampler: sampler; 13 | 14 | @vertex 15 | fn filter_vertex_shader( 16 | @location(0) pos: vec2, 17 | @location(1) tex_coord: vec2, 18 | @location(2) color: vec4, 19 | ) -> RasterData { 20 | var result: RasterData; 21 | 22 | let texture_size = vec2(textureDimensions(input_texture)); 23 | 24 | let top_left = vec2(1.0, 1.0); 25 | let bottom_right = vec2(texture_size[0] - 1.0, texture_size[1] - 1.0); 26 | 27 | // Convert the range of the position to a value betwen 0-1 28 | var texture_pos = vec2((pos[0] + 1.0) / 2.0, 1.0 - ((pos[1] + 1.0) / 2.0)); 29 | 30 | // Convert to a position on the texture. We want to half the size of the texture, so start at (1.0, 1.0) - between the first four pixels of the texture 31 | texture_pos = (bottom_right-top_left) * texture_pos + top_left; 32 | 33 | // Convert back to coordinates in the range 0-1 34 | texture_pos = texture_pos / texture_size; 35 | 36 | result.pos = vec4(pos[0], pos[1], 0.0, 1.0); 37 | result.texture_pos = texture_pos; 38 | 39 | return result; 40 | } 41 | 42 | @fragment 43 | fn filter_fragment_shader(vertex: RasterData) -> @location(0) vec4 { 44 | return textureSample(input_texture, input_sampler, vertex.texture_pos); 45 | } 46 | -------------------------------------------------------------------------------- /render/shaders/filters/tint.glslf: -------------------------------------------------------------------------------- 1 | uniform sampler2D t_Texture; 2 | out vec4 f_Color; 3 | uniform vec4 texture_tint; 4 | 5 | void main() { 6 | ivec2 pos = ivec2(gl_FragCoord.x, gl_FragCoord.y); 7 | f_Color = texelFetch(t_Texture, pos, 0); 8 | 9 | #ifdef INVERT_COLOUR_ALPHA 10 | // Blend towards one as the alpha approaches 0 (used for the multiply blend mode) 11 | f_Color[0] = 1 - ((1-f_Color[0]) * (f_Color[3])); 12 | f_Color[1] = 1 - ((1-f_Color[1]) * (f_Color[3])); 13 | f_Color[2] = 1 - ((1-f_Color[2]) * (f_Color[3])); 14 | #endif 15 | 16 | f_Color *= texture_tint; 17 | } 18 | -------------------------------------------------------------------------------- /render/shaders/filters/tint.wgsl: -------------------------------------------------------------------------------- 1 | struct RasterData { 2 | @location(0) texture_pos: vec2, 3 | @builtin(position) pos: vec4 4 | } 5 | 6 | @group(0) 7 | @binding(0) 8 | var input_texture: texture_2d; 9 | 10 | @group(0) 11 | @binding(1) 12 | var f_tint: vec4; 13 | 14 | @vertex 15 | fn filter_vertex_shader( 16 | @location(0) pos: vec2, 17 | @location(1) tex_coord: vec2, 18 | @location(2) color: vec4, 19 | ) -> RasterData { 20 | var result: RasterData; 21 | 22 | let texture_size = vec2(textureDimensions(input_texture)); 23 | let texture_pos_1 = vec2((pos[0]+1.0)/2.0, 1.0-((pos[1]+1.0)/2.0)); 24 | let texture_pos = vec2(texture_size * texture_pos_1); 25 | 26 | result.pos = vec4(pos[0], pos[1], 0.0, 1.0); 27 | result.texture_pos = texture_pos; 28 | 29 | return result; 30 | } 31 | 32 | @fragment 33 | fn filter_fragment_shader_premultiply(vertex: RasterData) -> @location(0) vec4 { 34 | let texture_pos = vec2(vertex.texture_pos); 35 | 36 | var color = textureLoad(input_texture, texture_pos, 0); 37 | color = color * f_tint; 38 | 39 | return color; 40 | } 41 | 42 | @fragment 43 | fn filter_fragment_shader_not_premultiplied(vertex: RasterData) -> @location(0) vec4 { 44 | let texture_pos = vec2(vertex.texture_pos); 45 | 46 | var color = textureLoad(input_texture, texture_pos, 0); 47 | color = color * f_tint; 48 | 49 | return color; 50 | } 51 | -------------------------------------------------------------------------------- /render/shaders/simple/clip_mask.metal: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #import "rasterizer.metal" 4 | 5 | float4 apply_clip_mask( 6 | float4 color, 7 | float2 paper_coord, 8 | metal::texture2d_ms clip_mask_texture) { 9 | // Work out the coordinates in the clip mask texture (which applies to the whole screen) 10 | paper_coord[0] *= float(clip_mask_texture.get_width()); 11 | paper_coord[1] *= float(clip_mask_texture.get_height()); 12 | 13 | // Sample the clip mask 14 | const uint num_samples = clip_mask_texture.get_num_samples(); 15 | const uint2 clip_coord = uint2(paper_coord); 16 | half clip_mask_total = 0; 17 | 18 | for (uint sample_num=0; sample_num; 4 | 5 | fn clip(color: vec4, position: vec4) -> vec4 { 6 | let clip_x = position[0]; 7 | let clip_y = position[1]; 8 | 9 | let clip_pos = vec2(i32(clip_x), i32(clip_y)); 10 | var clip_alpha = f32(0.0); 11 | 12 | for (var sample_num: i32 = 0; sample_num < 4; sample_num++) { 13 | clip_alpha += textureLoad(clip_texture, clip_pos, sample_num)[0]; 14 | } 15 | 16 | clip_alpha *= 0.25; 17 | 18 | let clip_color = vec4( 19 | color[0] * clip_alpha, 20 | color[1] * clip_alpha, 21 | color[2] * clip_alpha, 22 | color[3] * clip_alpha 23 | ); 24 | 25 | return clip_color; 26 | } 27 | -------------------------------------------------------------------------------- /render/shaders/simple/clip_none.wgsl: -------------------------------------------------------------------------------- 1 | fn clip(color: vec4, position: vec4) -> vec4 { 2 | return color; 3 | } -------------------------------------------------------------------------------- /render/shaders/simple/color_invert_alpha.wgsl: -------------------------------------------------------------------------------- 1 | fn color_post_process(col: vec4) -> vec4 { 2 | let new_col = vec4( 3 | 1.0 - ((1.0-col[0]) * (col[3])), 4 | 1.0 - ((1.0-col[1]) * (col[3])), 5 | 1.0 - ((1.0-col[2]) * (col[3])), 6 | col[3] 7 | ); 8 | 9 | return new_col; 10 | } 11 | -------------------------------------------------------------------------------- /render/shaders/simple/color_multiply_alpha.wgsl: -------------------------------------------------------------------------------- 1 | fn color_post_process(col: vec4) -> vec4 { 2 | let new_col = vec4( 3 | col[0] * col[3], 4 | col[1] * col[3], 5 | col[2] * col[3], 6 | col[3] 7 | ); 8 | 9 | return new_col; 10 | } 11 | -------------------------------------------------------------------------------- /render/shaders/simple/color_no_post_processing.wgsl: -------------------------------------------------------------------------------- 1 | fn color_post_process(col: vec4) -> vec4 { 2 | return col; 3 | } 4 | -------------------------------------------------------------------------------- /render/shaders/simple/multisample_resolve_4.glslf: -------------------------------------------------------------------------------- 1 | /// 2 | /// For blending a multi-sample texture onto another texture: this will map the equivalent coordinates of the fragments from the source texture 3 | /// 4 | 5 | uniform sampler2DMS t_SourceTexture; 6 | uniform float t_Alpha; 7 | 8 | out vec4 f_Color; 9 | 10 | void main() { 11 | ivec2 pos = ivec2(gl_FragCoord.x, gl_FragCoord.y); 12 | 13 | vec4 sample1 = texelFetch(t_SourceTexture, pos, 0); 14 | vec4 sample2 = texelFetch(t_SourceTexture, pos, 1); 15 | vec4 sample3 = texelFetch(t_SourceTexture, pos, 2); 16 | vec4 sample4 = texelFetch(t_SourceTexture, pos, 3); 17 | 18 | vec4 avg = (sample1 + sample2 + sample3 + sample4) / 4.0; 19 | avg *= t_Alpha; // Assumes pre-multiplied alpha 20 | 21 | f_Color = avg; 22 | 23 | #ifdef INVERT_COLOUR_ALPHA 24 | // Blend towards one as the alpha approaches 0 (used for the multiply blend mode) 25 | f_Color[0] = 1 - ((1-f_Color[0]) * (f_Color[3])); 26 | f_Color[1] = 1 - ((1-f_Color[1]) * (f_Color[3])); 27 | f_Color[2] = 1 - ((1-f_Color[2]) * (f_Color[3])); 28 | #endif 29 | 30 | #ifdef MULTIPLY_ALPHA 31 | // This means that the input texture does not have pre-multiplied alpha but we want the output texture to be set up this way 32 | // This is used in particular for some blend modes (Multiply, Screen) 33 | f_Color[0] *= f_Color[3]; 34 | f_Color[1] *= f_Color[3]; 35 | f_Color[2] *= f_Color[3]; 36 | #endif 37 | } 38 | -------------------------------------------------------------------------------- /render/shaders/simple/postprocessing.metal: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #import "rasterizer.metal" 4 | 5 | float4 invert_color_alpha(float4 col) { 6 | col[0] = 1 - ((1-col[0]) * col[3]); 7 | col[1] = 1 - ((1-col[1]) * col[3]); 8 | col[2] = 1 - ((1-col[2]) * col[3]); 9 | 10 | return col; 11 | } 12 | 13 | float4 multiply_alpha(float4 col) { 14 | col[0] = 1 - ((1-col[0]) * col[3]); 15 | col[1] = 1 - ((1-col[1]) * col[3]); 16 | col[2] = 1 - ((1-col[2]) * col[3]); 17 | 18 | return col; 19 | } 20 | -------------------------------------------------------------------------------- /render/shaders/simple/rasterizer.metal: -------------------------------------------------------------------------------- 1 | typedef struct { 2 | float4 v_Position [[position]]; 3 | float4 v_Color; 4 | float2 v_TexCoord; 5 | float2 v_PaperCoord; 6 | } RasterizerData; 7 | 8 | float4 apply_clip_mask( 9 | float4 color, 10 | float2 paper_coord, 11 | metal::texture2d_ms eraser_texture); 12 | 13 | float4 invert_color_alpha(float4 col); 14 | float4 multiply_alpha(float4 col); 15 | -------------------------------------------------------------------------------- /render/shaders/simple/resolve.glslv: -------------------------------------------------------------------------------- 1 | layout (location = 0) in vec2 a_Pos; 2 | layout (location = 1) in vec2 a_TexCoord; 3 | layout (location = 2) in vec4 a_Color; 4 | 5 | void main() { 6 | gl_Position = vec4(a_Pos, 0.0, 1.0); 7 | } 8 | -------------------------------------------------------------------------------- /render/shaders/simple/simple.glslf: -------------------------------------------------------------------------------- 1 | in VS_OUTPUT { 2 | vec4 v_Color; 3 | vec2 v_TexCoord; 4 | vec2 v_PaperCoord; 5 | } IN; 6 | 7 | out vec4 f_Color; 8 | 9 | #ifdef ERASE_MASK 10 | uniform sampler2DMS t_EraseMask; 11 | #endif 12 | 13 | #ifdef CLIP_MASK 14 | uniform sampler2DMS t_ClipMask; 15 | #endif 16 | 17 | void main() { 18 | f_Color = IN.v_Color; 19 | 20 | #ifdef CLIP_MASK 21 | ivec2 clipSize = textureSize(t_ClipMask); 22 | 23 | float clipWidth = float(clipSize[0]); 24 | float clipHeight = float(clipSize[1]); 25 | float clipX = IN.v_PaperCoord[0] * clipWidth; 26 | float clipY = IN.v_PaperCoord[1] * clipHeight; 27 | 28 | ivec2 clipPos = ivec2(int(clipX), int(clipY)); 29 | float clipColor = 0.0; 30 | 31 | for (int i=0; i<4; ++i) { 32 | clipColor += texelFetch(t_ClipMask, clipPos, i)[0]; 33 | } 34 | 35 | clipColor /= 4.0; 36 | 37 | f_Color[0] *= clipColor; 38 | f_Color[1] *= clipColor; 39 | f_Color[2] *= clipColor; 40 | f_Color[3] *= clipColor; 41 | #endif 42 | 43 | #ifdef INVERT_COLOUR_ALPHA 44 | // Blend towards one as the alpha approaches 0 (used for the multiply blend mode) 45 | f_Color[0] = 1 - ((1-f_Color[0]) * (f_Color[3])); 46 | f_Color[1] = 1 - ((1-f_Color[1]) * (f_Color[3])); 47 | f_Color[2] = 1 - ((1-f_Color[2]) * (f_Color[3])); 48 | #endif 49 | 50 | #ifdef MULTIPLY_ALPHA 51 | // This means that the input texture does not have pre-multiplied alpha but we want the output texture to be set up this way 52 | // This is used in particular for some blend modes (Multiply, Screen) 53 | f_Color[0] *= f_Color[3]; 54 | f_Color[1] *= f_Color[3]; 55 | f_Color[2] *= f_Color[3]; 56 | #endif 57 | } 58 | -------------------------------------------------------------------------------- /render/shaders/simple/simple.glslv: -------------------------------------------------------------------------------- 1 | layout (location = 0) in vec2 a_Pos; 2 | layout (location = 1) in vec2 a_TexCoord; 3 | layout (location = 2) in vec4 a_Color; 4 | 5 | uniform mat4 transform; 6 | 7 | out VS_OUTPUT { 8 | vec4 v_Color; 9 | vec2 v_TexCoord; 10 | vec2 v_PaperCoord; 11 | } OUT; 12 | 13 | void main() { 14 | OUT.v_Color = vec4(a_Color[0]/255.0, a_Color[1]/255.0, a_Color[2]/255.0, a_Color[3]/255.0); 15 | OUT.v_TexCoord = a_TexCoord; 16 | gl_Position = vec4(a_Pos, 0.0, 1.0) * transform; 17 | OUT.v_PaperCoord = vec2((gl_Position[0]+1.0)/2.0, (gl_Position[1]+1.0)/2.0); 18 | } 19 | -------------------------------------------------------------------------------- /render/shaders/simple/simple.metal: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #import "./bindings/metal_vertex2d.h" 4 | #import "rasterizer.metal" 5 | 6 | vertex RasterizerData simple_vertex( 7 | uint vertex_id [[ vertex_id ]], 8 | constant matrix_float4x4 *transform [[ buffer(VertexInputIndexMatrix )]], 9 | constant MetalVertex2D *vertices [[ buffer(VertexInputIndexVertices) ]]) { 10 | uchar4 byte_color = vertices[vertex_id].color; 11 | float4 color = float4(byte_color[0], byte_color[1], byte_color[2], byte_color[3]); 12 | color[0] /= 255.0; 13 | color[1] /= 255.0; 14 | color[2] /= 255.0; 15 | color[3] /= 255.0; 16 | 17 | float4 position = float4(vertices[vertex_id].pos[0], vertices[vertex_id].pos[1], 0.0, 1.0) * *transform; 18 | float2 tex_coord = vertices[vertex_id].tex_coord; 19 | float2 paper_coord = float2((position[0]+1.0)/2.0, 1.0-((position[1]+1.0)/2.0)); 20 | 21 | RasterizerData data; 22 | 23 | data.v_Position = position; 24 | data.v_Color = color; 25 | data.v_TexCoord = tex_coord; 26 | data.v_PaperCoord = paper_coord; 27 | 28 | return data; 29 | } 30 | 31 | fragment float4 simple_fragment( 32 | RasterizerData in [[stage_in]]) { 33 | return in.v_Color; 34 | } 35 | 36 | fragment float4 simple_clip_mask_multisample_fragment( 37 | RasterizerData in [[stage_in]], 38 | metal::texture2d_ms clip_mask_texture [[ texture(FragmentIndexClipMaskTexture) ]]) { 39 | float4 color = apply_clip_mask(in.v_Color, in.v_PaperCoord, clip_mask_texture); 40 | return color; 41 | } 42 | 43 | fragment float4 simple_fragment_invert_color_alpha( 44 | RasterizerData in [[stage_in]]) { 45 | return invert_color_alpha(in.v_Color); 46 | } 47 | 48 | fragment float4 simple_clip_mask_multisample_fragment_invert_color_alpha( 49 | RasterizerData in [[stage_in]], 50 | metal::texture2d_ms clip_mask_texture [[ texture(FragmentIndexClipMaskTexture) ]]) { 51 | float4 color = apply_clip_mask(in.v_Color, in.v_PaperCoord, clip_mask_texture); 52 | return invert_color_alpha(color); 53 | } 54 | -------------------------------------------------------------------------------- /render/shaders/simple/simple.wgsl: -------------------------------------------------------------------------------- 1 | struct RasterData { 2 | @location(0) color: vec4, 3 | @builtin(position) pos: vec4 4 | } 5 | 6 | @group(0) 7 | @binding(0) 8 | var transform: mat4x4; 9 | 10 | @vertex 11 | fn simple_vertex_shader( 12 | @location(0) pos: vec2, 13 | @location(1) tex_coord: vec2, 14 | @location(2) color: vec4, 15 | ) -> RasterData { 16 | var result: RasterData; 17 | 18 | var color_r = vec4(f32(color[0]), f32(color[1]), f32(color[2]), f32(color[3])); 19 | color_r[0] /= 255.0; 20 | color_r[1] /= 255.0; 21 | color_r[2] /= 255.0; 22 | color_r[3] /= 255.0; 23 | 24 | result.color = color_r; 25 | result.pos = vec4(pos[0], pos[1], 0.0, 1.0) * transform; 26 | 27 | return result; 28 | } 29 | 30 | @fragment 31 | fn simple_fragment_shader(vertex: RasterData) -> @location(0) vec4 { 32 | var color = vertex.color; 33 | 34 | color = clip(color, vertex.pos); 35 | color = color_post_process(color); 36 | 37 | return color; 38 | } 39 | -------------------------------------------------------------------------------- /render/shaders/texture/alpha_no_premultiply.wgsl: -------------------------------------------------------------------------------- 1 | fn alpha_blend(col: vec4, alpha: f32) -> vec4 { 2 | return vec4( 3 | col[0], 4 | col[1], 5 | col[2], 6 | col[3] * alpha, 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /render/shaders/texture/alpha_premultiplied.wgsl: -------------------------------------------------------------------------------- 1 | fn alpha_blend(col: vec4, alpha: f32) -> vec4 { 2 | return vec4( 3 | col[0] * alpha, 4 | col[1] * alpha, 5 | col[2] * alpha, 6 | col[3] * alpha, 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /render/shaders/texture/gradient.glslf: -------------------------------------------------------------------------------- 1 | in VS_OUTPUT { 2 | float v_TexCoord; 3 | vec2 v_PaperCoord; 4 | } IN; 5 | 6 | out vec4 f_Color; 7 | 8 | uniform sampler1D t_Texture; 9 | uniform float texture_alpha; 10 | 11 | #ifdef ERASE_MASK 12 | uniform sampler2DMS t_EraseMask; 13 | #endif 14 | 15 | #ifdef CLIP_MASK 16 | uniform sampler2DMS t_ClipMask; 17 | #endif 18 | 19 | void main() { 20 | f_Color = texture(t_Texture, IN.v_TexCoord); 21 | 22 | f_Color[3] *= texture_alpha; 23 | 24 | #ifdef CLIP_MASK 25 | ivec2 clipSize = textureSize(t_ClipMask); 26 | 27 | float clipWidth = float(clipSize[0]); 28 | float clipHeight = float(clipSize[1]); 29 | float clipX = IN.v_PaperCoord[0] * clipWidth; 30 | float clipY = IN.v_PaperCoord[1] * clipHeight; 31 | 32 | ivec2 clipPos = ivec2(int(clipX), int(clipY)); 33 | float clipColor = 0.0; 34 | 35 | for (int i=0; i<4; ++i) { 36 | clipColor += texelFetch(t_ClipMask, clipPos, i)[0]; 37 | } 38 | 39 | clipColor /= 4.0; 40 | 41 | f_Color[0] *= clipColor; 42 | f_Color[1] *= clipColor; 43 | f_Color[2] *= clipColor; 44 | f_Color[3] *= clipColor; 45 | #endif 46 | 47 | #ifdef INVERT_COLOUR_ALPHA 48 | // Blend towards one as the alpha approaches 0 (used for the multiply blend mode) 49 | f_Color[0] = 1 - ((1-f_Color[0]) * (f_Color[3])); 50 | f_Color[1] = 1 - ((1-f_Color[1]) * (f_Color[3])); 51 | f_Color[2] = 1 - ((1-f_Color[2]) * (f_Color[3])); 52 | #endif 53 | 54 | #ifdef MULTIPLY_ALPHA 55 | // This means that the input texture does not have pre-multiplied alpha but we want the output texture to be set up this way 56 | // This is used in particular for some blend modes (Multiply, Screen) 57 | f_Color[0] *= f_Color[3]; 58 | f_Color[1] *= f_Color[3]; 59 | f_Color[2] *= f_Color[3]; 60 | #endif 61 | } 62 | -------------------------------------------------------------------------------- /render/shaders/texture/gradient.glslv: -------------------------------------------------------------------------------- 1 | layout (location = 0) in vec2 a_Pos; 2 | layout (location = 1) in vec2 a_TexCoord; 3 | layout (location = 2) in vec4 a_Color; 4 | 5 | uniform mat4 transform; 6 | uniform mat4 texture_transform; 7 | 8 | out VS_OUTPUT { 9 | float v_TexCoord; 10 | vec2 v_PaperCoord; 11 | } OUT; 12 | 13 | void main() { 14 | vec4 texCoord = vec4(a_Pos, 0.0, 1.0) * texture_transform; 15 | gl_Position = vec4(a_Pos, 0.0, 1.0) * transform; 16 | 17 | OUT.v_TexCoord = texCoord[0]; 18 | OUT.v_PaperCoord = vec2((gl_Position[0]+1.0)/2.0, (gl_Position[1]+1.0)/2.0); 19 | } 20 | -------------------------------------------------------------------------------- /render/shaders/texture/gradient.wgsl: -------------------------------------------------------------------------------- 1 | struct RasterData { 2 | @location(0) color: vec4, 3 | @location(1) tex_coord: vec2, 4 | @builtin(position) pos: vec4 5 | } 6 | 7 | struct TextureSettings { 8 | @location(0) transform: mat4x4, 9 | @location(1) alpha: f32 10 | } 11 | 12 | @group(0) 13 | @binding(0) 14 | var transform: mat4x4; 15 | 16 | @group(2) 17 | @binding(0) 18 | var texture_settings: TextureSettings; 19 | 20 | @group(2) 21 | @binding(1) 22 | var f_texture: texture_1d; 23 | 24 | @group(2) 25 | @binding(2) 26 | var f_sampler: sampler; 27 | 28 | @vertex 29 | fn gradient_vertex_shader( 30 | @location(0) pos: vec2, 31 | @location(1) tex_coord: vec2, 32 | @location(2) color: vec4, 33 | ) -> RasterData { 34 | var result: RasterData; 35 | 36 | var color_r = vec4(f32(color[0]), f32(color[1]), f32(color[2]), f32(color[3])); 37 | color_r[0] /= 255.0; 38 | color_r[1] /= 255.0; 39 | color_r[2] /= 255.0; 40 | color_r[3] /= 255.0; 41 | 42 | let tex_coord_r = texture_position(pos, tex_coord, texture_settings.transform); 43 | 44 | result.color = color_r; 45 | result.tex_coord = tex_coord_r; 46 | result.pos = vec4(pos[0], pos[1], 0.0, 1.0) * transform; 47 | 48 | return result; 49 | } 50 | 51 | @fragment 52 | fn gradient_fragment_shader(vertex: RasterData) -> @location(0) vec4 { 53 | var color = textureSample(f_texture, f_sampler, vertex.tex_coord[0]); 54 | color = alpha_blend(color, texture_settings.alpha); 55 | 56 | color = clip(color, vertex.pos); 57 | color = color_post_process(color); 58 | 59 | return color; 60 | } 61 | -------------------------------------------------------------------------------- /render/shaders/texture/texture.glslf: -------------------------------------------------------------------------------- 1 | in VS_OUTPUT { 2 | vec2 v_TexCoord; 3 | vec2 v_PaperCoord; 4 | } IN; 5 | 6 | out vec4 f_Color; 7 | 8 | uniform sampler2D t_Texture; 9 | uniform float texture_alpha; 10 | 11 | #ifdef ERASE_MASK 12 | uniform sampler2DMS t_EraseMask; 13 | #endif 14 | 15 | #ifdef CLIP_MASK 16 | uniform sampler2DMS t_ClipMask; 17 | #endif 18 | 19 | void main() { 20 | f_Color = texture(t_Texture, IN.v_TexCoord); 21 | 22 | #ifdef PREMULITPLIED_INPUT_ALPHA 23 | f_Color *= texture_alpha; 24 | #else 25 | f_Color[3] *= texture_alpha; 26 | #endif 27 | 28 | #ifdef CLIP_MASK 29 | ivec2 clipSize = textureSize(t_ClipMask); 30 | 31 | float clipWidth = float(clipSize[0]); 32 | float clipHeight = float(clipSize[1]); 33 | float clipX = IN.v_PaperCoord[0] * clipWidth; 34 | float clipY = IN.v_PaperCoord[1] * clipHeight; 35 | 36 | ivec2 clipPos = ivec2(int(clipX), int(clipY)); 37 | float clipColor = 0.0; 38 | 39 | for (int i=0; i<4; ++i) { 40 | clipColor += texelFetch(t_ClipMask, clipPos, i)[0]; 41 | } 42 | 43 | clipColor /= 4.0; 44 | 45 | f_Color[0] *= clipColor; 46 | f_Color[1] *= clipColor; 47 | f_Color[2] *= clipColor; 48 | f_Color[3] *= clipColor; 49 | #endif 50 | 51 | #ifdef INVERT_COLOUR_ALPHA 52 | // Blend towards one as the alpha approaches 0 (used for the multiply blend mode) 53 | f_Color[0] = 1 - ((1-f_Color[0]) * (f_Color[3])); 54 | f_Color[1] = 1 - ((1-f_Color[1]) * (f_Color[3])); 55 | f_Color[2] = 1 - ((1-f_Color[2]) * (f_Color[3])); 56 | #endif 57 | 58 | #ifdef MULTIPLY_ALPHA 59 | // This means that the input texture does not have pre-multiplied alpha but we want the output texture to be set up this way 60 | // This is used in particular for some blend modes (Multiply, Screen) 61 | f_Color[0] *= f_Color[3]; 62 | f_Color[1] *= f_Color[3]; 63 | f_Color[2] *= f_Color[3]; 64 | #endif 65 | } 66 | -------------------------------------------------------------------------------- /render/shaders/texture/texture.glslv: -------------------------------------------------------------------------------- 1 | layout (location = 0) in vec2 a_Pos; 2 | layout (location = 1) in vec2 a_TexCoord; 3 | layout (location = 2) in vec4 a_Color; 4 | 5 | uniform mat4 transform; 6 | uniform mat4 texture_transform; 7 | 8 | out VS_OUTPUT { 9 | vec2 v_TexCoord; 10 | vec2 v_PaperCoord; 11 | } OUT; 12 | 13 | void main() { 14 | vec4 texCoord = vec4(a_Pos, 0.0, 1.0) * texture_transform; 15 | gl_Position = vec4(a_Pos, 0.0, 1.0) * transform; 16 | 17 | OUT.v_TexCoord = vec2(texCoord[0], texCoord[1]); 18 | OUT.v_PaperCoord = vec2((gl_Position[0]+1.0)/2.0, (gl_Position[1]+1.0)/2.0); 19 | } 20 | -------------------------------------------------------------------------------- /render/shaders/texture/texture.wgsl: -------------------------------------------------------------------------------- 1 | struct RasterData { 2 | @location(0) color: vec4, 3 | @location(1) tex_coord: vec2, 4 | @builtin(position) pos: vec4 5 | } 6 | 7 | struct TextureSettings { 8 | @location(0) transform: mat4x4, 9 | @location(1) alpha: f32 10 | } 11 | 12 | @group(0) 13 | @binding(0) 14 | var transform: mat4x4; 15 | 16 | @group(2) 17 | @binding(0) 18 | var texture_settings: TextureSettings; 19 | 20 | @vertex 21 | fn texture_vertex_shader( 22 | @location(0) pos: vec2, 23 | @location(1) tex_coord: vec2, 24 | @location(2) color: vec4, 25 | ) -> RasterData { 26 | var result: RasterData; 27 | 28 | var color_r = vec4(f32(color[0]), f32(color[1]), f32(color[2]), f32(color[3])); 29 | color_r[0] /= 255.0; 30 | color_r[1] /= 255.0; 31 | color_r[2] /= 255.0; 32 | color_r[3] /= 255.0; 33 | 34 | let tex_coord_r = texture_position(pos, tex_coord, texture_settings.transform); 35 | 36 | result.color = color_r; 37 | result.tex_coord = tex_coord_r; 38 | result.pos = vec4(pos[0], pos[1], 0.0, 1.0) * transform; 39 | 40 | return result; 41 | } 42 | 43 | @fragment 44 | fn texture_fragment_shader(vertex: RasterData) -> @location(0) vec4 { 45 | var color = texture_color(vertex.color, vertex.tex_coord); 46 | color = alpha_blend(color, texture_settings.alpha); 47 | 48 | color = clip(color, vertex.pos); 49 | color = color_post_process(color); 50 | 51 | return color; 52 | } 53 | -------------------------------------------------------------------------------- /render/shaders/texture/texture_multisample.wgsl: -------------------------------------------------------------------------------- 1 | @group(2) 2 | @binding(1) 3 | var f_texture: texture_multisampled_2d; 4 | 5 | fn texture_color(vertex_color: vec4, texture_pos: vec2) -> vec4 { 6 | let size = vec2(textureDimensions(f_texture)); 7 | let num_samples = i32(textureNumSamples(f_texture)); 8 | 9 | let pos = vec2(size * texture_pos); 10 | 11 | var sample_totals = vec4(); 12 | for (var sample_num = i32(0); sample_num < num_samples; sample_num++) { 13 | sample_totals += textureLoad(f_texture, pos, sample_num); 14 | } 15 | 16 | let sample_col = sample_totals / f32(num_samples); 17 | 18 | return sample_col; 19 | } 20 | -------------------------------------------------------------------------------- /render/shaders/texture/texture_none.wgsl: -------------------------------------------------------------------------------- 1 | fn texture_color(vertex_color: vec4, texture_pos: vec2) -> vec4 { 2 | return vertex_color; 3 | } 4 | -------------------------------------------------------------------------------- /render/shaders/texture/texture_pos_input.wgsl: -------------------------------------------------------------------------------- 1 | fn texture_position(pos: vec2, tex_coord: vec2, texture_transform: mat4x4) -> vec2 { 2 | let tex_pos = vec4(pos[0], pos[1], 0.0, 1.0) * texture_transform; 3 | 4 | return vec2(tex_pos[0], tex_pos[1]); 5 | } 6 | -------------------------------------------------------------------------------- /render/shaders/texture/texture_pos_separate.wgsl: -------------------------------------------------------------------------------- 1 | fn texture_position(pos: vec2, tex_coord: vec2, texture_transform: mat4x4) -> vec2 { 2 | let tex_pos = vec4(tex_coord[0], tex_coord[1], 0.0, 1.0) * texture_transform; 3 | 4 | return vec2(tex_pos[0], tex_pos[1]); 5 | } 6 | -------------------------------------------------------------------------------- /render/shaders/texture/texture_sampler.wgsl: -------------------------------------------------------------------------------- 1 | @group(2) 2 | @binding(1) 3 | var f_texture: texture_2d; 4 | 5 | @group(2) 6 | @binding(2) 7 | var f_sampler: sampler; 8 | 9 | fn texture_color(vertex_color: vec4, texture_pos: vec2) -> vec4 { 10 | let raw_color = textureSample(f_texture, f_sampler, texture_pos); 11 | 12 | return raw_color; 13 | } 14 | -------------------------------------------------------------------------------- /render/src/action/blend_mode.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// The blending modes that the renderer must support (most of the Porter-Duff modes) 3 | /// 4 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 5 | #[derive(serde::Serialize, serde::Deserialize)] 6 | pub enum BlendMode { 7 | SourceOver, 8 | DestinationOver, 9 | SourceIn, 10 | DestinationIn, 11 | SourceOut, 12 | DestinationOut, 13 | SourceATop, 14 | DestinationATop, 15 | 16 | Screen, 17 | Multiply, 18 | 19 | AllChannelAlphaSourceOver, 20 | AllChannelAlphaDestinationOver 21 | } 22 | -------------------------------------------------------------------------------- /render/src/action/color.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Represents an RGBA colour as 8-bit valus 3 | /// 4 | #[derive(Clone, Copy, PartialEq, Debug, Hash)] 5 | #[derive(serde::Serialize, serde::Deserialize)] 6 | pub struct Rgba8(pub [u8; 4]); 7 | -------------------------------------------------------------------------------- /render/src/action/identities.rs: -------------------------------------------------------------------------------- 1 | /// An identifier corresponding to a vertex buffer 2 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] 3 | #[derive(serde::Serialize, serde::Deserialize)] 4 | pub struct VertexBufferId(pub usize); 5 | 6 | /// An identifier corresponding to an index buffer 7 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] 8 | #[derive(serde::Serialize, serde::Deserialize)] 9 | pub struct IndexBufferId(pub usize); 10 | 11 | /// An identifier corresponding to a render target 12 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] 13 | #[derive(serde::Serialize, serde::Deserialize)] 14 | pub struct RenderTargetId(pub usize); 15 | 16 | /// An identifier corresponding to a texture 17 | #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] 18 | #[derive(serde::Serialize, serde::Deserialize)] 19 | pub struct TextureId(pub usize); 20 | -------------------------------------------------------------------------------- /render/src/action/mod.rs: -------------------------------------------------------------------------------- 1 | mod identities; 2 | mod render_action; 3 | mod render_action_type; 4 | mod render_target_type; 5 | mod color; 6 | mod blend_mode; 7 | mod shader_type; 8 | mod texture_filter; 9 | 10 | pub use self::identities::*; 11 | pub use self::render_action::*; 12 | pub use self::render_action_type::*; 13 | pub use self::render_target_type::*; 14 | pub use self::color::*; 15 | pub use self::blend_mode::*; 16 | pub use self::shader_type::*; 17 | pub use self::texture_filter::*; 18 | -------------------------------------------------------------------------------- /render/src/action/render_target_type.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// The types of render target that can be created by the render layer 3 | /// 4 | #[derive(Clone, Copy, PartialEq, Debug, Hash)] 5 | #[derive(serde::Serialize, serde::Deserialize)] 6 | pub enum RenderTargetType { 7 | /// Standard off-screen render target (with a texture) 8 | Standard, 9 | 10 | /// Off-screen render target for reading back to the CPU 11 | StandardForReading, 12 | 13 | /// Multisampled render target 14 | Multisampled, 15 | 16 | /// Multisampled texture render target 17 | MultisampledTexture, 18 | 19 | /// Monochrome off-screen render target (only writes the red channel) 20 | Monochrome, 21 | 22 | /// Multisampled monochrome off-screen render target (only writes the red channel) 23 | MonochromeMultisampledTexture 24 | } 25 | -------------------------------------------------------------------------------- /render/src/action/shader_type.rs: -------------------------------------------------------------------------------- 1 | use super::identities::*; 2 | 3 | use crate::buffer::*; 4 | 5 | /// 6 | /// The shaders that can be chosen for the renderer 7 | /// 8 | #[derive(Clone, Copy, PartialEq, Debug)] 9 | #[derive(serde::Serialize, serde::Deserialize)] 10 | pub enum ShaderType { 11 | /// Flat colour shader 12 | /// The erase texture (which should be a MSAA texture) is subtracted from anything drawn, if present 13 | Simple { clip_texture: Option }, 14 | 15 | /// Flat colour with 'dashed line' texturing using a 1D texture 16 | DashedLine { dash_texture: TextureId, clip_texture: Option }, 17 | 18 | /// Colour derived from a texture with a transform mapping from canvas coordinates to texture coordinates 19 | Texture { texture: TextureId, texture_transform: Matrix, repeat: bool, alpha: f32, clip_texture: Option }, 20 | 21 | /// Colour derived from a 1D texture using a transform mapping (used for rendering linear gradients) 22 | LinearGradient { texture: TextureId, texture_transform: Matrix, repeat: bool, alpha: f32, clip_texture: Option } 23 | } 24 | 25 | impl ShaderType { 26 | /// 27 | /// Adds a clip mask texture to the existing shader 28 | /// 29 | pub fn with_clip_mask(self, new_clip_mask_texture: Option) -> ShaderType { 30 | use self::ShaderType::*; 31 | 32 | match self { 33 | Simple { clip_texture: _ } => Simple { clip_texture: new_clip_mask_texture }, 34 | DashedLine { dash_texture, clip_texture: _ } => DashedLine { dash_texture: dash_texture, clip_texture: new_clip_mask_texture }, 35 | Texture { texture, texture_transform, repeat, alpha, clip_texture: _ } => Texture { texture: texture, texture_transform: texture_transform, repeat, alpha, clip_texture: new_clip_mask_texture }, 36 | LinearGradient { texture, texture_transform, repeat, alpha, clip_texture: _ } => LinearGradient { texture: texture, texture_transform: texture_transform, repeat, alpha, clip_texture: new_clip_mask_texture } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /render/src/buffer/matrix.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Represents an OpenGL transformation matrix 3 | /// 4 | #[derive(Clone, Copy, Debug, PartialEq)] 5 | #[derive(serde::Serialize, serde::Deserialize)] 6 | pub struct Matrix(pub [[f32; 4]; 4]); 7 | 8 | impl Matrix { 9 | /// 10 | /// Returns the identity matrix 11 | /// 12 | pub fn identity() -> Matrix { 13 | Matrix([ 14 | [1.0, 0.0, 0.0, 0.0], 15 | [0.0, 1.0, 0.0, 0.0], 16 | [0.0, 0.0, 1.0, 0.0], 17 | [0.0, 0.0, 0.0, 1.0] 18 | ]) 19 | } 20 | 21 | /// 22 | /// Multiplies this matrix with another one 23 | /// 24 | pub fn multiply(self, b: Matrix) -> Matrix { 25 | let Matrix(a) = self; 26 | let Matrix(b) = b; 27 | 28 | let mut res = [[0.0; 4]; 4]; 29 | 30 | for row in 0..4 { 31 | for col in 0..4 { 32 | for pos in 0..4 { 33 | res[row][col] += a[row][pos] * b[pos][col]; 34 | } 35 | } 36 | } 37 | 38 | Matrix(res) 39 | } 40 | 41 | /// 42 | /// Converts this matrix to an OpenGL matrix 43 | /// 44 | pub fn to_opengl_matrix(&self) -> [f32; 16] { 45 | let Matrix(matrix) = self; 46 | 47 | [ 48 | matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3], 49 | matrix[1][0], matrix[1][1], matrix[1][2], matrix[1][3], 50 | matrix[2][0], matrix[2][1], matrix[2][2], matrix[2][3], 51 | matrix[3][0], matrix[3][1], matrix[3][2], matrix[3][3] 52 | ] 53 | } 54 | 55 | /// 56 | /// Flips the Y-coordinates of this matrix 57 | /// 58 | pub fn flip_y(self) -> Matrix { 59 | let Matrix(mut matrix) = self; 60 | 61 | matrix[1][0] = -matrix[1][0]; 62 | matrix[1][1] = -matrix[1][1]; 63 | matrix[1][2] = -matrix[1][2]; 64 | matrix[1][3] = -matrix[1][3]; 65 | 66 | Matrix(matrix) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /render/src/buffer/mod.rs: -------------------------------------------------------------------------------- 1 | mod vertex; 2 | mod matrix; 3 | 4 | pub use self::vertex::*; 5 | pub use self::matrix::*; 6 | -------------------------------------------------------------------------------- /render/src/buffer/vertex.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// 2D vertex representation 3 | /// 4 | #[derive(Clone, Copy, PartialEq, Debug)] 5 | #[derive(serde::Serialize, serde::Deserialize)] 6 | #[repr(C, packed)] 7 | pub struct Vertex2D { 8 | pub pos: [f32; 2], 9 | pub tex_coord: [f32; 2], 10 | pub color: [u8; 4] 11 | } 12 | 13 | impl Vertex2D { 14 | /// 15 | /// Creates a 2D vertex with the position set and the other values zeroed out 16 | /// 17 | pub fn with_pos(x: f32, y: f32) -> Vertex2D { 18 | Vertex2D { 19 | pos: [x, y], 20 | tex_coord: [0.0, 0.0], 21 | color: [0, 0, 0, 0] 22 | } 23 | } 24 | 25 | /// 26 | /// Updates this vertex with a particular colour 27 | /// 28 | pub fn with_color(self, r: f32, g: f32, b: f32, a: f32) -> Vertex2D { 29 | Vertex2D { 30 | pos: self.pos, 31 | tex_coord: self.tex_coord, 32 | color: [(r*255.0) as _, (g*255.0) as _, (b*255.0) as _, (a*255.0) as _] 33 | } 34 | } 35 | 36 | /// 37 | /// Updates this vertex with a texture coordinate 38 | /// 39 | pub fn with_texture_coordinates(self, x: f32, y: f32) -> Vertex2D { 40 | Vertex2D { 41 | pos: self.pos, 42 | tex_coord: [x, y], 43 | color: self.color 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /render/src/gl_renderer/buffer.rs: -------------------------------------------------------------------------------- 1 | use gl; 2 | 3 | use std::mem; 4 | use std::ops::{Deref}; 5 | use std::ffi::{c_void}; 6 | 7 | /// 8 | /// Abstraction of an OpenGL buffer object 9 | /// 10 | pub struct Buffer { 11 | buffer: gl::types::GLuint 12 | } 13 | 14 | impl Buffer { 15 | /// 16 | /// Creates a new buffer 17 | /// 18 | pub fn new() -> Buffer { 19 | unsafe { 20 | let mut new_buffer = 0; 21 | gl::GenBuffers(1, &mut new_buffer); 22 | 23 | Buffer { 24 | buffer: new_buffer 25 | } 26 | } 27 | } 28 | 29 | /// 30 | /// Fills the buffer with static draw data 31 | /// 32 | pub fn static_draw(&mut self, data: &[TData]) 33 | where TData: Sized { 34 | unsafe { 35 | gl::BindBuffer(gl::ARRAY_BUFFER, self.buffer); 36 | gl::BufferData( 37 | gl::ARRAY_BUFFER, 38 | (mem::size_of::() * data.len()) as isize, 39 | data.as_ptr() as *const c_void, 40 | gl::STATIC_DRAW); 41 | } 42 | } 43 | } 44 | 45 | impl Drop for Buffer { 46 | fn drop(&mut self) { 47 | unsafe { 48 | gl::DeleteBuffers(1, &mut self.buffer); 49 | } 50 | } 51 | } 52 | 53 | impl Deref for Buffer { 54 | type Target = gl::types::GLuint; 55 | 56 | fn deref(&self) -> &gl::types::GLuint { 57 | &self.buffer 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /render/src/gl_renderer/error.rs: -------------------------------------------------------------------------------- 1 | use gl; 2 | 3 | #[derive(Debug, Clone, PartialEq, Hash)] 4 | pub enum GlError { 5 | /// Error without a string translation 6 | UnknownError(u32), 7 | 8 | InvalidOperation, 9 | InvalidEnum, 10 | 11 | /// Error where we can provide a string version 12 | #[allow(dead_code)] 13 | Error(u32, String) 14 | } 15 | 16 | /// 17 | /// Collects OpenGL errors and panics if there are any 18 | /// 19 | #[cfg(debug_assertions)] 20 | pub fn panic_on_gl_error(context: &str) { 21 | let errors = check_for_gl_errors(); 22 | 23 | if errors.len() > 0 { 24 | panic!("{}: Unexpected OpenGL errors: {:?}", context, errors); 25 | } 26 | } 27 | 28 | /// 29 | /// Collects OpenGL errors and panics if there are any 30 | /// 31 | #[cfg(not(debug_assertions))] 32 | pub fn panic_on_gl_error(context: &str) { 33 | let errors = check_for_gl_errors(); 34 | 35 | if errors.len() > 0 { 36 | println!("{}: Unexpected OpenGL errors: {:?}", context, errors); 37 | } 38 | } 39 | 40 | /// 41 | /// Returns all errors that are currently set in a GL context 42 | /// 43 | pub fn check_for_gl_errors() -> Vec { 44 | let mut result = vec![]; 45 | 46 | // Read all of ther errors that are set in the current context 47 | while let Some(error) = check_next_gl_error() { 48 | result.push(error) 49 | } 50 | 51 | result 52 | } 53 | 54 | /// 55 | /// Returns the next GL error 56 | /// 57 | fn check_next_gl_error() -> Option { 58 | let error = unsafe { gl::GetError() }; 59 | 60 | match error { 61 | gl::NO_ERROR => None, 62 | gl::INVALID_OPERATION => Some(GlError::InvalidOperation), 63 | gl::INVALID_ENUM => Some(GlError::InvalidEnum), 64 | unknown => Some(GlError::UnknownError(unknown)) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /render/src/gl_renderer/mod.rs: -------------------------------------------------------------------------------- 1 | mod gl_renderer; 2 | 3 | mod error; 4 | mod vertex_array; 5 | mod buffer; 6 | mod vertex; 7 | mod shader; 8 | mod texture; 9 | mod render_target; 10 | mod shader_program; 11 | mod shader_uniforms; 12 | mod shader_collection; 13 | mod standard_shader_programs; 14 | 15 | pub use self::gl_renderer::*; 16 | 17 | pub use self::error::*; 18 | pub use self::render_target::*; 19 | -------------------------------------------------------------------------------- /render/src/gl_renderer/shader_uniforms.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// The uniforms for shaders used by the 2D rendering engine 3 | /// 4 | #[derive(Clone, Copy, Hash, PartialEq, Eq)] 5 | pub enum ShaderUniform { 6 | /// The transformation matrix to use 7 | Transform, 8 | 9 | /// The texture bound to the 'clip' operation 10 | ClipTexture, 11 | 12 | /// The texture used for the dash pattern 13 | DashTexture, 14 | 15 | /// Texture used for picking the colour of a fragment 16 | Texture, 17 | 18 | /// The transform applied to the texture coordinates 19 | TextureTransform, 20 | 21 | /// The alpha adjustment applied to the texture colour 22 | TextureAlpha, 23 | 24 | /// The colour tint applied to the texture colour 25 | TextureTint, 26 | 27 | /// The texture for a MSAA shader 28 | MsaaTexture, 29 | 30 | /// The alpha value to use for a MSAA shader 31 | MsaaAlpha, 32 | 33 | /// The weights for the gaussian blur shader 34 | BlurWeights, 35 | 36 | /// The offsets for the gaussian blur shader 37 | BlurOffsets, 38 | 39 | /// The weights for the gaussian blur shader (when defined as a texture) 40 | TextureBlurWeights, 41 | 42 | /// The weights for the gaussian blur shader (when defined as a texture) 43 | TextureBlurOffsets, 44 | 45 | /// The input texture for a filter that needs one 46 | FilterTexture, 47 | 48 | /// The scale factor used for a filter 49 | FilterScale, 50 | } 51 | -------------------------------------------------------------------------------- /render/src/gl_renderer/vertex.rs: -------------------------------------------------------------------------------- 1 | use crate::buffer::*; 2 | 3 | use gl; 4 | 5 | use std::mem; 6 | 7 | impl Vertex2D { 8 | /// 9 | /// Defines the attributes for this structure onto whatever vertex array object is currently bound 10 | /// 11 | pub fn define_attributes() { 12 | unsafe { 13 | // Define the attributes 14 | let stride = mem::size_of::() as gl::types::GLint; 15 | let pos = 0; 16 | 17 | // Attribute 0: a_Pos 18 | gl::EnableVertexAttribArray(0); 19 | gl::VertexAttribPointer( 20 | 0, 21 | 2, 22 | gl::FLOAT, 23 | gl::FALSE, 24 | stride, 25 | pos as *const gl::types::GLvoid); 26 | 27 | let pos = pos + 2*mem::size_of::(); 28 | 29 | // Attribute 1: a_TexCoord 30 | gl::EnableVertexAttribArray(1); 31 | gl::VertexAttribPointer( 32 | 1, 33 | 2, 34 | gl::FLOAT, 35 | gl::FALSE, 36 | stride, 37 | pos as *const gl::types::GLvoid); 38 | 39 | let pos = pos + 2*mem::size_of::(); 40 | 41 | // Attribute 2: a_Color 42 | gl::EnableVertexAttribArray(2); 43 | gl::VertexAttribPointer( 44 | 2, 45 | 4, 46 | gl::UNSIGNED_BYTE, 47 | gl::FALSE, 48 | stride, 49 | pos as *const gl::types::GLvoid); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /render/src/gl_renderer/vertex_array.rs: -------------------------------------------------------------------------------- 1 | use gl; 2 | 3 | use std::ops::{Deref}; 4 | 5 | /// 6 | /// Abstraction for an OpenGL vertex array object 7 | /// 8 | pub struct VertexArray { 9 | vertex_array_object: gl::types::GLuint 10 | } 11 | 12 | impl VertexArray { 13 | /// 14 | /// Creates a new vertex array 15 | /// 16 | pub fn new() -> VertexArray { 17 | unsafe { 18 | // Create the array 19 | let mut new_array: gl::types::GLuint = 0; 20 | gl::GenVertexArrays(1, &mut new_array); 21 | 22 | // Encapsulate into the structure 23 | VertexArray { 24 | vertex_array_object: new_array 25 | } 26 | } 27 | } 28 | } 29 | 30 | impl Deref for VertexArray { 31 | type Target = gl::types::GLuint; 32 | 33 | fn deref(&self) -> &gl::types::GLuint { 34 | &self.vertex_array_object 35 | } 36 | } 37 | 38 | impl Drop for VertexArray { 39 | fn drop(&mut self) { 40 | unsafe { 41 | gl::DeleteVertexArrays(1, &mut self.vertex_array_object); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /render/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod action; 2 | mod buffer; 3 | #[cfg(feature="gl")] mod gl_renderer; 4 | #[cfg(feature="osx-metal")] mod metal_renderer; 5 | #[cfg(feature="render-wgpu")] mod wgpu_renderer; 6 | mod offscreen; 7 | 8 | #[cfg(feature="profile")] 9 | mod profiler; 10 | 11 | pub use self::action::*; 12 | pub use self::buffer::*; 13 | pub use self::offscreen::*; 14 | #[cfg(feature="gl")] pub use self::gl_renderer::GlRenderer; 15 | #[cfg(feature="osx-metal")] pub use self::metal_renderer::MetalRenderer; 16 | #[cfg(feature="render-wgpu")] pub use self::wgpu_renderer::WgpuRenderer; 17 | 18 | #[cfg(feature="render-wgpu")] 19 | pub use wgpu; 20 | -------------------------------------------------------------------------------- /render/src/metal_renderer/bindings.rs: -------------------------------------------------------------------------------- 1 | #![allow(warnings)] 2 | 3 | include!(concat!(env!("OUT_DIR"), "/metal_vertex2d.rs")); 4 | -------------------------------------------------------------------------------- /render/src/metal_renderer/buffer.rs: -------------------------------------------------------------------------------- 1 | use super::bindings::*; 2 | use crate::buffer::*; 3 | 4 | use metal; 5 | 6 | use std::ops::{Deref}; 7 | use std::ffi::{c_void}; 8 | 9 | /// 10 | /// Manages a metal buffer 11 | /// 12 | pub struct Buffer { 13 | /// The buffer that this is managing 14 | buffer: metal::Buffer 15 | } 16 | 17 | impl Buffer { 18 | /// 19 | /// Creates a buffer from some vertices 20 | /// 21 | pub fn from_vertices>(device: &metal::Device, vertices: VertexIterator) -> Buffer { 22 | // Convert the vertices to Metal format 23 | let vertices = vertices.into_iter() 24 | .map(|vertex| MetalVertex2D::from(vertex)) 25 | .collect::>(); 26 | 27 | // Generate the buffer 28 | let buffer = device.new_buffer_with_data(vertices.as_ptr() as *const c_void, 29 | (vertices.len() * std::mem::size_of::()) as u64, 30 | metal::MTLResourceOptions::CPUCacheModeDefaultCache | metal::MTLResourceOptions::StorageModeManaged); 31 | 32 | // Return the buffer object 33 | Buffer { 34 | buffer: buffer 35 | } 36 | } 37 | 38 | /// 39 | /// Creates a buffer from a list of indexes 40 | /// 41 | pub fn from_indices(device: &metal::Device, indices: Vec) -> Buffer { 42 | let buffer = device.new_buffer_with_data(indices.as_ptr() as *const c_void, 43 | (indices.len() * std::mem::size_of::()) as u64, 44 | metal::MTLResourceOptions::CPUCacheModeDefaultCache | metal::MTLResourceOptions::StorageModeManaged); 45 | 46 | Buffer { 47 | buffer: buffer 48 | } 49 | } 50 | } 51 | 52 | impl Deref for Buffer { 53 | type Target = metal::Buffer; 54 | 55 | fn deref(&self) -> &metal::Buffer { 56 | &self.buffer 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /render/src/metal_renderer/convert.rs: -------------------------------------------------------------------------------- 1 | use super::bindings::*; 2 | use crate::buffer::*; 3 | 4 | use std::mem; 5 | 6 | impl From for MetalVertex2D { 7 | fn from(src: Vertex2D) -> MetalVertex2D { 8 | unsafe { 9 | // The SIMD types do not come out in a very convenient form, so we use mem::transmute here 10 | MetalVertex2D { 11 | pos: mem::transmute(src.pos), 12 | tex_coord: mem::transmute(src.tex_coord), 13 | color: mem::transmute(src.color) 14 | } 15 | } 16 | } 17 | } 18 | 19 | impl From for matrix_float4x4 { 20 | fn from(Matrix(src): Matrix) -> matrix_float4x4 { 21 | unsafe { 22 | matrix_float4x4 { 23 | columns: [ 24 | mem::transmute(src[0]), 25 | mem::transmute(src[1]), 26 | mem::transmute(src[2]), 27 | mem::transmute(src[3]), 28 | ] 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /render/src/metal_renderer/matrix_buffer.rs: -------------------------------------------------------------------------------- 1 | use super::bindings::*; 2 | use crate::buffer::*; 3 | 4 | use metal; 5 | 6 | use std::ptr; 7 | use std::mem; 8 | use std::ops::{Deref}; 9 | use std::ffi::{c_void}; 10 | 11 | /// 12 | /// Manages a metal buffer containing a matrix 13 | /// 14 | pub struct MatrixBuffer { 15 | /// The buffer that this is managing 16 | buffer: metal::Buffer 17 | } 18 | 19 | impl MatrixBuffer { 20 | /// 21 | /// Creates a matrix buffer from a matrix 22 | /// 23 | pub fn from_matrix(device: &metal::Device, matrix: Matrix) -> MatrixBuffer { 24 | // Convert to the internal representation 25 | let matrix = matrix_float4x4::from(matrix); 26 | 27 | // Generate a metal buffer containing the matrix 28 | let matrix_ptr: *const matrix_float4x4 = &matrix; 29 | let buffer = device.new_buffer_with_data(matrix_ptr as *const c_void, 30 | std::mem::size_of::() as u64, 31 | metal::MTLResourceOptions::CPUCacheModeDefaultCache | metal::MTLResourceOptions::StorageModeManaged); 32 | 33 | // Return the buffer object 34 | MatrixBuffer { 35 | buffer: buffer 36 | } 37 | } 38 | 39 | /// 40 | /// Updates the matrix in this buffer 41 | /// 42 | pub fn set_matrix(&mut self, matrix: Matrix) { 43 | // Convert to the internal representation 44 | let matrix = matrix_float4x4::from(matrix); 45 | 46 | // Copy the matrix to the buffer 47 | unsafe { 48 | let content = self.buffer.contents(); 49 | ptr::copy(&matrix, content as *mut matrix_float4x4, mem::size_of::()); 50 | } 51 | 52 | // Tell the buffer its been modified 53 | self.buffer.did_modify_range(metal::NSRange::new(0, mem::size_of::() as u64)); 54 | } 55 | } 56 | 57 | impl Deref for MatrixBuffer { 58 | type Target = metal::Buffer; 59 | 60 | fn deref(&self) -> &metal::Buffer { 61 | &self.buffer 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /render/src/metal_renderer/mod.rs: -------------------------------------------------------------------------------- 1 | mod metal_renderer; 2 | mod bindings; 3 | mod convert; 4 | mod buffer; 5 | mod matrix_buffer; 6 | mod render_target; 7 | mod pipeline_configuration; 8 | 9 | pub use self::metal_renderer::*; 10 | pub use self::render_target::*; 11 | -------------------------------------------------------------------------------- /render/src/offscreen/error.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | 3 | /// 4 | /// Errors that can happen when trying to initialise the renderer 5 | /// 6 | #[derive(Clone, Debug, PartialEq)] 7 | pub enum RenderInitError { 8 | /// The required rendering API is not available 9 | ApiNotAvailable, 10 | 11 | /// Indicates that the graphics device could not be opened 12 | CannotOpenGraphicsDevice, 13 | 14 | /// Indicates that the graphics device could not be attached to 15 | CannotCreateGraphicsDevice, 16 | 17 | /// The graphics driver failed to initialise 18 | CannotStartGraphicsDriver, 19 | 20 | /// The graphics display is not available 21 | DisplayNotAvailable, 22 | 23 | /// A required extension was missing 24 | MissingRequiredExtension, 25 | 26 | /// Unable to configure the display 27 | CouldNotConfigureDisplay, 28 | 29 | /// The context failed to create 30 | CouldNotCreateContext, 31 | 32 | /// The render surface failed to create 33 | CouldNotCreateSurface, 34 | 35 | /// Could not set the active context 36 | ContextDidNotStart 37 | } 38 | -------------------------------------------------------------------------------- /render/src/offscreen/mod.rs: -------------------------------------------------------------------------------- 1 | mod error; 2 | mod offscreen_trait; 3 | 4 | #[cfg(feature="opengl")] mod opengl; 5 | #[cfg(all(feature="opengl", target_os = "windows"))] mod opengl_wgl_init; 6 | #[cfg(all(feature="opengl", target_os = "linux"))] mod opengl_egl_init; 7 | #[cfg(all(feature="opengl", target_os = "macos"))] mod opengl_cgl_init; 8 | #[cfg(feature="osx-metal")] mod metal; 9 | #[cfg(feature="render-wgpu")] mod wgpu_offscreen; 10 | 11 | pub use self::error::*; 12 | pub use self::offscreen_trait::*; 13 | 14 | #[cfg(all(feature="opengl", target_os = "windows"))] pub use self::opengl_wgl_init::*; 15 | #[cfg(all(feature="opengl", target_os = "linux"))] pub use self::opengl_egl_init::*; 16 | #[cfg(all(feature="opengl", target_os = "macos"))] pub use self::opengl_cgl_init::*; 17 | #[cfg(feature="osx-metal")] pub use self::metal::*; 18 | #[cfg(feature="render-wgpu")] pub use self::wgpu_offscreen::*; 19 | 20 | #[cfg(test)] mod test; 21 | -------------------------------------------------------------------------------- /render/src/offscreen/offscreen_trait.rs: -------------------------------------------------------------------------------- 1 | use crate::action::*; 2 | 3 | /// 4 | /// Trait implemented by FlowBetween offscreen render targets 5 | /// 6 | pub trait OffscreenRenderTarget { 7 | /// 8 | /// Sends render actions to this offscreen render target 9 | /// 10 | fn render>(&mut self, actions: ActionIter); 11 | 12 | /// 13 | /// Consumes this render target and returns the realized pixels as a byte array 14 | /// 15 | fn realize(self) -> Vec; 16 | } 17 | 18 | /// 19 | /// Trait implemented by objects that represent a offscreen drawing context 20 | /// 21 | pub trait OffscreenRenderContext { 22 | type RenderTarget: OffscreenRenderTarget; 23 | 24 | /// 25 | /// Creates a new render target for this context 26 | /// 27 | fn create_render_target(&mut self, width: usize, height: usize) -> Self::RenderTarget; 28 | } 29 | -------------------------------------------------------------------------------- /render/src/wgpu_renderer/mod.rs: -------------------------------------------------------------------------------- 1 | mod texture; 2 | mod pipeline; 3 | mod samplers; 4 | mod to_buffer; 5 | mod wgpu_shader; 6 | mod shader_cache; 7 | mod render_target; 8 | mod wgpu_renderer; 9 | mod renderer_state; 10 | mod texture_settings; 11 | mod render_pass_resources; 12 | mod pipeline_configuration; 13 | 14 | mod blur_filter; 15 | mod mask_filter; 16 | mod tint_filter; 17 | mod reduce_filter; 18 | mod alpha_blend_filter; 19 | mod displacement_map_filter; 20 | 21 | pub use self::wgpu_renderer::*; 22 | -------------------------------------------------------------------------------- /render/src/wgpu_renderer/shader_cache.rs: -------------------------------------------------------------------------------- 1 | use wgpu; 2 | 3 | use std::sync::*; 4 | use std::hash::{Hash}; 5 | use std::collections::{HashMap}; 6 | 7 | /// 8 | /// Trait implemented by types that can be converted to a shader 9 | /// 10 | pub trait WgpuShaderLoader { 11 | /// Loads a shader using this definition, returning the shader module and the vertex and fragment shader entry points 12 | fn load(&self, device: &wgpu::Device) -> (Arc, String, String); 13 | } 14 | 15 | /// 16 | /// Caches the WGPU shaders so that they only need to be loaded once 17 | /// 18 | pub struct ShaderCache 19 | where 20 | TShader: WgpuShaderLoader + Hash + Eq, 21 | { 22 | /// The device that the shaders will be loaded on 23 | device: Arc, 24 | 25 | /// The shaders stored in this cache 26 | shaders: HashMap, String, String)> 27 | } 28 | 29 | impl ShaderCache 30 | where 31 | TShader: WgpuShaderLoader + Hash + Eq + Clone, 32 | { 33 | /// 34 | /// Creates an empty shader cache 35 | /// 36 | pub fn empty(device: Arc) -> ShaderCache { 37 | ShaderCache { 38 | device: device, 39 | shaders: HashMap::new(), 40 | } 41 | } 42 | 43 | /// 44 | /// Loads the specified shader if it's not in the cache 45 | /// 46 | pub fn load_shader(&mut self, shader: &TShader) { 47 | if !self.shaders.contains_key(shader) { 48 | let new_shader = shader.load(&*self.device); 49 | self.shaders.insert(shader.clone(), new_shader); 50 | } 51 | } 52 | 53 | /// 54 | /// Retrieves the specified shader, if it's in the cache 55 | /// 56 | #[inline] 57 | pub fn get_shader<'a>(&'a self, shader: &TShader) -> Option<(&'a wgpu::ShaderModule, &'a str, &'a str)> { 58 | self.shaders.get(shader) 59 | .map(|(shader_ref, vertex_name, fragment_name)| { 60 | (&**shader_ref, vertex_name.as_str(), fragment_name.as_str()) 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /render/src/wgpu_renderer/texture.rs: -------------------------------------------------------------------------------- 1 | use wgpu; 2 | 3 | use std::sync::*; 4 | 5 | /// 6 | /// Representation of a texture stored in the WGPU renderer 7 | /// 8 | #[derive(Clone)] 9 | pub (crate) struct WgpuTexture { 10 | /// The descriptor used to create the texture 11 | pub descriptor: wgpu::TextureDescriptor<'static>, 12 | 13 | /// The WGPU texture stored here 14 | pub texture: Arc, 15 | 16 | /// True if this texture has premultiplied alpha 17 | pub is_premultiplied: bool, 18 | } 19 | -------------------------------------------------------------------------------- /render/src/wgpu_renderer/texture_settings.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Layout for the TextureSettings uniform 3 | /// 4 | #[derive(Clone, Copy, PartialEq, Debug, Default)] 5 | #[repr(C, packed)] 6 | pub struct TextureSettings { 7 | pub transform: [[f32; 4]; 4], 8 | pub alpha: f32, 9 | pub _padding: [u32; 3] 10 | } 11 | -------------------------------------------------------------------------------- /render_canvas/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flo_render_canvas" 3 | version = "0.4.0" 4 | authors = ["Andrew Hunter"] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | repository = "https://github.com/Logicalshift/flo_draw" 8 | description = "Converts flo_canvas streams to flo_render streams" 9 | categories = [ "graphics", "rendering", "rendering::engine" ] 10 | 11 | include = [ "Cargo.toml", "src/**/*", "svg/**/*" ] 12 | 13 | [features] 14 | default = [ "render-software" ] 15 | opengl = [ "flo_render/opengl" ] 16 | osx-metal = [ "flo_render/osx-metal" ] 17 | render-wgpu = [ "flo_render/render-wgpu" ] 18 | render-software = [ "flo_render_software" ] 19 | profile = [ "flo_render/profile" ] 20 | 21 | scenery = [ "flo_canvas/scenery" ] 22 | 23 | [dependencies] 24 | flo_render = "0.4" 25 | flo_canvas = "0.4" 26 | flo_stream = "0.7" 27 | futures = "0.3" 28 | desync = "0.9" 29 | lyon = "1.0" 30 | num_cpus = "1.13" 31 | flo_render_software = { version = "0.4", optional = true } 32 | 33 | [dev-dependencies] 34 | png = "0.17" 35 | once_cell = "1.18" 36 | winit = "0.30" 37 | flo_canvas = { version = "0.4", features = [ "outline-fonts" ] } -------------------------------------------------------------------------------- /render_canvas/examples/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/render_canvas/examples/Lato-Regular.ttf -------------------------------------------------------------------------------- /render_canvas/examples/Lato-license.md: -------------------------------------------------------------------------------- 1 | Lato is included under the Open Font License (https://scripts.sil.org/OFL). 2 | It has an official site here: https://www.latofonts.com/ 3 | -------------------------------------------------------------------------------- /render_canvas/examples/png_triangle.rs: -------------------------------------------------------------------------------- 1 | use flo_canvas::*; 2 | use flo_render_canvas::*; 3 | 4 | use futures::stream; 5 | use futures::executor; 6 | 7 | use png; 8 | 9 | use std::io::*; 10 | use std::path; 11 | use std::fs::*; 12 | 13 | /// 14 | /// Saves a file 'triangle.png' with a triangle in it 15 | /// 16 | pub fn main() { 17 | executor::block_on(async { 18 | // Create an offscreen context 19 | let mut context = initialize_offscreen_rendering().unwrap(); 20 | 21 | // Describe what to draw 22 | let mut drawing = vec![]; 23 | drawing.clear_canvas(Color::Rgba(0.0, 0.0, 0.0, 0.0)); 24 | drawing.canvas_height(1000.0); 25 | drawing.transform(Transform2D::scale(1.0, -1.0)); 26 | drawing.center_region(0.0, 0.0, 1000.0, 1000.0); 27 | 28 | drawing.new_path(); 29 | drawing.move_to(200.0, 200.0); 30 | drawing.line_to(800.0, 200.0); 31 | drawing.line_to(500.0, 800.0); 32 | drawing.line_to(200.0, 200.0); 33 | 34 | drawing.fill_color(Color::Rgba(0.0, 0.6, 0.8, 1.0)); 35 | drawing.fill(); 36 | 37 | // Render an image to bytes 38 | let image = render_canvas_offscreen(&mut context, 1024, 768, stream::iter(drawing)).await; 39 | 40 | // Save to a png file 41 | let path = path::Path::new(r"triangle.png"); 42 | let file = File::create(path).unwrap(); 43 | let ref mut writer = BufWriter::new(file); 44 | 45 | let mut png_encoder = png::Encoder::new(writer, 1024, 768); 46 | png_encoder.set_color(png::ColorType::Rgba); 47 | png_encoder.set_depth(png::BitDepth::Eight); 48 | let mut png_writer = png_encoder.write_header().unwrap(); 49 | 50 | png_writer.write_image_data(&image).unwrap(); 51 | }); 52 | } -------------------------------------------------------------------------------- /render_canvas/src/canvas_renderer/mod.rs: -------------------------------------------------------------------------------- 1 | mod canvas_renderer; 2 | mod tessellate_path; 3 | mod tessellate_frame; 4 | mod tessellate_build_path; 5 | mod tessellate_properties; 6 | mod tessellate_transform; 7 | mod tessellate_state; 8 | mod tessellate_namespaces; 9 | mod tessellate_layers; 10 | mod tessellate_sprites; 11 | mod tessellate_textures; 12 | mod tessellate_gradients; 13 | mod tessellate_font; 14 | 15 | pub use self::canvas_renderer::*; 16 | -------------------------------------------------------------------------------- /render_canvas/src/canvas_renderer/tessellate_font.rs: -------------------------------------------------------------------------------- 1 | use super::canvas_renderer::*; 2 | 3 | use flo_canvas as canvas; 4 | 5 | // The font routines are expected to be implemented by post-processing the output stream of rendering instructions, so they are currently empty here 6 | // See `drawing_with_laid_out_text()` and ` drawing_with_text_as_paths` from flo_canvas for one way to achieve this 7 | 8 | impl CanvasRenderer { 9 | /// 10 | /// Performs an operation on a font 11 | /// 12 | #[inline] 13 | pub (super) fn tes_font(&mut self, _font_id: canvas::FontId, _font_op: canvas::FontOp) { } 14 | 15 | /// 16 | /// Begins laying out text on a line: the coordinates specify the baseline position 17 | /// 18 | #[inline] 19 | pub (super) fn tes_begin_line_layout(&mut self, _x: f32, _y: f32, _aligment: canvas::TextAlignment) { } 20 | 21 | /// 22 | /// Renders the text in the current layout 23 | /// 24 | #[inline] 25 | pub (super) fn tes_draw_laid_out_text(&mut self) { } 26 | 27 | /// 28 | /// Draws a string using a font with a baseline starting at the specified position 29 | /// 30 | #[inline] 31 | pub (super) fn tes_draw_text(&mut self, _font_id: canvas::FontId, _text: String, _x: f32, _y: f32) { } 32 | } 33 | -------------------------------------------------------------------------------- /render_canvas/src/canvas_renderer/tessellate_frame.rs: -------------------------------------------------------------------------------- 1 | use super::canvas_renderer::*; 2 | 3 | impl CanvasRenderer { 4 | /// Suspends rendering to the display until the next 'ShowFrame' 5 | /// 6 | /// The renderer may perform tessellation or rendering in the background after 'StartFrame' but won't 7 | /// commit anything to the visible frame buffer until 'ShowFrame' is hit. If 'StartFrame' is nested, 8 | /// then the frame won't be displayed until 'ShowFrame' has been requested at least that many times. 9 | /// 10 | /// The frame state persists across a 'ClearCanvas' 11 | #[inline] 12 | pub (super) fn tes_start_frame(&self) { 13 | self.core.desync(|core| { 14 | core.frame_starts += 1; 15 | }); 16 | } 17 | 18 | /// Displays any requested queued after 'StartFrame' 19 | #[inline] 20 | pub (super) fn tes_show_frame(&self) { 21 | self.core.desync(|core| { 22 | if core.frame_starts > 0 { 23 | core.frame_starts -= 1; 24 | } 25 | }); 26 | } 27 | 28 | /// Resets the frame count back to 0 (for when regenerating the state of a canvas) 29 | #[inline] 30 | pub (super) fn tes_reset_frame(&self) { 31 | self.core.desync(|core| { 32 | core.frame_starts = 0; 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /render_canvas/src/canvas_renderer/tessellate_namespaces.rs: -------------------------------------------------------------------------------- 1 | use super::canvas_renderer::*; 2 | 3 | use flo_canvas as canvas; 4 | 5 | impl CanvasRenderer { 6 | /// 7 | /// Clears the currently selected sprite 8 | /// 9 | #[inline] 10 | pub (super) fn tes_namespace(&mut self, namespace: canvas::NamespaceId) { 11 | // The current namespace is used to identify different groupds of resources 12 | self.current_namespace = namespace.local_id(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /render_canvas/src/dynamic_texture_state.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// The state a dynamic texture was in the last time it was rendered 3 | /// 4 | #[derive(PartialEq)] 5 | pub struct DynamicTextureState { 6 | /// The viewport size for the texture 7 | pub (super) viewport: (f32, f32), 8 | 9 | /// The number of times the sprite was modified 10 | pub (super) sprite_modification_count: usize 11 | } -------------------------------------------------------------------------------- /render_canvas/src/layer_handle.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Handle referencing a renderer layer 3 | /// 4 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] 5 | pub struct LayerHandle(pub u64); 6 | -------------------------------------------------------------------------------- /render_canvas/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod resource_ids; 2 | mod render_entity; 3 | mod render_entity_details; 4 | mod layer_state; 5 | mod fill_state; 6 | mod stroke_settings; 7 | mod layer_bounds; 8 | mod canvas_renderer; 9 | mod layer_handle; 10 | mod render_texture; 11 | mod render_gradient; 12 | mod texture_render_request; 13 | mod texture_filter_request; 14 | mod renderer_core; 15 | mod renderer_layer; 16 | mod renderer_worker; 17 | mod renderer_stream; 18 | mod offscreen; 19 | mod matrix; 20 | mod dynamic_texture_state; 21 | 22 | pub use self::canvas_renderer::*; 23 | pub use self::offscreen::*; 24 | 25 | pub use flo_render::*; 26 | pub use flo_canvas as canvas; 27 | -------------------------------------------------------------------------------- /render_canvas/src/matrix.rs: -------------------------------------------------------------------------------- 1 | use flo_canvas as canvas; 2 | use flo_render as render; 3 | 4 | /// 5 | /// Converts a canvas transform to a rendering matrix 6 | /// 7 | pub fn transform_to_matrix(transform: &canvas::Transform2D) -> render::Matrix { 8 | let canvas::Transform2D(t) = transform; 9 | 10 | render::Matrix([ 11 | [t[0][0], t[0][1], 0.0, t[0][2]], 12 | [t[1][0], t[1][1], 0.0, t[1][2]], 13 | [t[2][0], t[2][1], 1.0, t[2][2]], 14 | [0.0, 0.0, 0.0, 1.0] 15 | ]) 16 | } 17 | -------------------------------------------------------------------------------- /render_canvas/src/offscreen/mod.rs: -------------------------------------------------------------------------------- 1 | mod hardware; 2 | mod initialise; 3 | mod offscreen_trait; 4 | mod render_offscreen; 5 | #[cfg(feature="render-software")] mod software; 6 | 7 | pub use hardware::*; 8 | pub use initialise::*; 9 | pub use offscreen_trait::*; 10 | pub use render_offscreen::*; 11 | #[cfg(feature="render-software")] pub use software::*; 12 | -------------------------------------------------------------------------------- /render_canvas/src/offscreen/offscreen_trait.rs: -------------------------------------------------------------------------------- 1 | use flo_canvas::*; 2 | 3 | /// 4 | /// Offscreen drawing contexts can be used to create offscreen drawing targets (which can be used to render `Draw` commands and generate a `Vec` containing the RGBA 5 | /// data that results from rendering them) 6 | /// 7 | pub trait OffscreenDrawingContext { 8 | /// Drawing targets are where canvas `Draw` instructions can be sent 9 | type DrawingTarget: OffscreenDrawingTarget; 10 | 11 | /// 12 | /// Creates a new drawing target for this context 13 | /// 14 | fn create_drawing_target(&mut self, width: usize, height: usize) -> Self::DrawingTarget; 15 | } 16 | 17 | /// 18 | /// An offscreen drawing target is used to send drawing commands and generate a buffer 19 | /// 20 | pub trait OffscreenDrawingTarget { 21 | /// 22 | /// Sends render actions to this offscreen render target 23 | /// 24 | fn draw_actions(&mut self, actions: impl 'static + IntoIterator) -> impl std::future::Future; 25 | 26 | /// 27 | /// Consumes this render target and returns the realized pixels as a byte array 28 | /// 29 | fn realize(self) -> Vec; 30 | } 31 | 32 | // TODO: can we make the OffscreenRenderTarget Send? This would let us wrap the target in a Desync and buffer up stuff sent to the GraphicsContext implementation (which is not very good for the hardware renderer at the moment) 33 | -------------------------------------------------------------------------------- /render_canvas/src/offscreen/render_offscreen.rs: -------------------------------------------------------------------------------- 1 | use super::offscreen_trait::*; 2 | 3 | use flo_canvas::*; 4 | 5 | use futures::prelude::*; 6 | 7 | /// 8 | /// Renders a canvas in an offscreen context, returning the resulting bitmap 9 | /// 10 | pub fn render_canvas_offscreen<'a>(context: &'a mut impl OffscreenDrawingContext, width: usize, height: usize, actions: impl 'a + Stream) -> impl 'a+Future> { 11 | async move { 12 | // Perform as many drawing actions simultaneously as we can 13 | let actions = Box::pin(actions); 14 | let mut actions = actions.ready_chunks(10000); 15 | 16 | // Create the offscreen render target 17 | let mut drawing_target = context.create_drawing_target(width, height); 18 | 19 | // Send the drawing instructions from the action stream 20 | while let Some(drawing) = actions.next().await { 21 | // Render the next set of actions 22 | drawing_target.draw_actions(drawing).await; 23 | } 24 | 25 | // Result is the realized rendering 26 | drawing_target.realize() 27 | } 28 | } -------------------------------------------------------------------------------- /render_canvas/src/render_entity.rs: -------------------------------------------------------------------------------- 1 | use super::texture_filter_request::*; 2 | 3 | use flo_canvas as canvas; 4 | use flo_render as render; 5 | 6 | use lyon::tessellation::{VertexBuffers}; 7 | 8 | /// 9 | /// How a vertex buffer is intended to be used 10 | /// 11 | pub enum VertexBufferIntent { 12 | /// Will be drawn using DrawIndexed 13 | Draw, 14 | 15 | /// Will be rendered to the clipping area using EnableClipping 16 | Clip 17 | } 18 | 19 | /// 20 | /// Single rendering operation for a layer 21 | /// 22 | pub enum RenderEntity { 23 | /// Render operation is missing 24 | Missing, 25 | 26 | /// Render operation is waiting to be tessellated (with a unique entity ID) 27 | Tessellating(usize), 28 | 29 | /// Tessellation waiting to be sent to the renderer 30 | VertexBuffer(VertexBuffers, VertexBufferIntent), 31 | 32 | /// Render a vertex buffer 33 | DrawIndexed(render::VertexBufferId, render::IndexBufferId, usize), 34 | 35 | /// Render the sprite layer with the specified ID 36 | RenderSprite(usize, canvas::SpriteId, canvas::Transform2D), 37 | 38 | /// Render a sprite to an off-screen texture and then apply a filter to it 39 | RenderSpriteWithFilters(usize, canvas::SpriteId, canvas::Transform2D, Vec), 40 | 41 | /// Updates the transformation matrix for the layer 42 | SetTransform(canvas::Transform2D), 43 | 44 | /// Sets the blend mode to use for the following rendering 45 | SetBlendMode(render::BlendMode), 46 | 47 | /// Use flat colour shading for the following rendering 48 | SetFlatColor, 49 | 50 | /// Sets the dash pattern to use for the following rendering 51 | SetDashPattern(Vec), 52 | 53 | /// Sets the fill texture to use for the following rendering 54 | SetFillTexture(render::TextureId, render::Matrix, bool, f32), 55 | 56 | /// Sets the gradient texture to use for the following rendering 57 | SetFillGradient(render::TextureId, render::Matrix, bool, f32), 58 | 59 | /// Use the specified vertex buffer to define a clipping mask 60 | EnableClipping(render::VertexBufferId, render::IndexBufferId, usize), 61 | 62 | /// Stop clipping 63 | DisableClipping 64 | } 65 | -------------------------------------------------------------------------------- /render_canvas/src/render_entity_details.rs: -------------------------------------------------------------------------------- 1 | use super::layer_bounds::*; 2 | 3 | use flo_render::*; 4 | use flo_canvas as canvas; 5 | 6 | /// 7 | /// Provides information about a render entity 8 | /// 9 | pub struct RenderEntityDetails { 10 | /// The bounds for the render entity 11 | pub bounds: LayerBounds 12 | } 13 | 14 | impl RenderEntityDetails { 15 | /// 16 | /// Creates a new details object from a set of vertices 17 | /// 18 | pub fn from_vertices<'a>(vertices: impl IntoIterator, transform: &canvas::Transform2D) -> RenderEntityDetails { 19 | // Work out the minimum and maximu 20 | let mut min = (f32::MAX, f32::MAX); 21 | let mut max = (f32::MIN, f32::MIN); 22 | 23 | for vertex in vertices { 24 | let [x, y] = vertex.pos; 25 | 26 | min.0 = f32::min(x, min.0); 27 | min.1 = f32::min(y, min.1); 28 | max.0 = f32::max(x, max.0); 29 | max.1 = f32::max(y, max.1); 30 | } 31 | 32 | // Get the bounds and convert via the transform 33 | let bounds = LayerBounds { 34 | min_x: min.0, 35 | min_y: min.1, 36 | max_x: max.0, 37 | max_y: max.1 38 | }; 39 | let bounds = bounds.transform(transform); 40 | 41 | RenderEntityDetails { 42 | bounds 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /render_canvas/src/render_gradient.rs: -------------------------------------------------------------------------------- 1 | use flo_render as render; 2 | use flo_canvas as canvas; 3 | 4 | /// 5 | /// Ued to indicate the state of a gradient: these are loaded as 1-dimensional textures when they are used 6 | /// 7 | #[derive(Clone)] 8 | pub enum RenderGradient { 9 | Defined(Vec), 10 | Ready(render::TextureId, Vec) 11 | } 12 | -------------------------------------------------------------------------------- /render_canvas/src/render_texture.rs: -------------------------------------------------------------------------------- 1 | use flo_render as render; 2 | 3 | /// 4 | /// Used to indicate the state of a texture 5 | /// 6 | /// A 'loading' texture is one where we're still writing data, where a 'Ready' texture is one where we've 7 | /// generated the mipmap and are using it somewhere in the core 8 | /// 9 | #[derive(Clone, Copy, Debug)] 10 | pub enum RenderTexture { 11 | Loading(render::TextureId), 12 | Ready(render::TextureId) 13 | } 14 | 15 | impl Into for RenderTexture { 16 | fn into(self) -> render::TextureId { 17 | match self { 18 | RenderTexture::Loading(texture_id) => texture_id, 19 | RenderTexture::Ready(texture_id) => texture_id 20 | } 21 | } 22 | } 23 | 24 | impl Into for &RenderTexture { 25 | fn into(self) -> render::TextureId { 26 | match self { 27 | RenderTexture::Loading(texture_id) => *texture_id, 28 | RenderTexture::Ready(texture_id) => *texture_id 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /render_canvas/src/resource_ids.rs: -------------------------------------------------------------------------------- 1 | use flo_render::*; 2 | 3 | pub (crate) const MAIN_RENDER_TARGET: RenderTargetId = RenderTargetId(0); 4 | pub (crate) const CLIP_RENDER_TARGET: RenderTargetId = RenderTargetId(1); 5 | pub (crate) const RESOLVE_RENDER_TARGET: RenderTargetId = RenderTargetId(2); 6 | 7 | pub (crate) const MAIN_RENDER_TEXTURE: TextureId = TextureId(0); 8 | pub (crate) const CLIP_RENDER_TEXTURE: TextureId = TextureId(1); 9 | pub (crate) const DASH_TEXTURE: TextureId = TextureId(2); 10 | -------------------------------------------------------------------------------- /render_canvas/src/stroke_settings.rs: -------------------------------------------------------------------------------- 1 | use flo_canvas as canvas; 2 | use flo_render as render; 3 | 4 | /// 5 | /// The settings for a path 6 | /// 7 | #[derive(Clone, Debug)] 8 | pub struct StrokeSettings { 9 | pub stroke_color: render::Rgba8, 10 | pub join: canvas::LineJoin, 11 | pub cap: canvas::LineCap, 12 | pub dash_pattern: Vec, 13 | pub dash_offset: f32, 14 | pub line_width: f32 15 | } 16 | 17 | impl StrokeSettings { 18 | /// 19 | /// Creates a new path settings with the default values for the renderer 20 | /// 21 | pub fn new() -> StrokeSettings { 22 | StrokeSettings { 23 | stroke_color: render::Rgba8([0, 0, 0, 255]), 24 | join: canvas::LineJoin::Round, 25 | cap: canvas::LineCap::Butt, 26 | dash_pattern: vec![], 27 | dash_offset: 0.0, 28 | line_width: 1.0 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /render_gl_offscreen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flo_render_gl_offscreen" 3 | version = "0.4.0" 4 | authors = ["Andrew Hunter"] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | repository = "https://github.com/Logicalshift/flo_draw" 8 | description = "Re-exports platform-appropriate libraries for initialising offscreen rendering" 9 | 10 | # Cargo can base dependencies on features or the current platform but not both (which is also why you have to specify 11 | # 'gtk' on linux), so we need a separate crate to re-export egl/wgl/cgl based on the platform we're compiling on 12 | 13 | [target.'cfg(target_os = "linux")'.build-dependencies] 14 | bindgen = "0.69" 15 | 16 | [dependencies] 17 | 18 | [target.'cfg(target_os = "linux")'.dependencies] 19 | egl = "0.2" 20 | 21 | [target.'cfg(target_os = "macos")'.dependencies] 22 | cgl = "0.3" 23 | 24 | [target.'cfg(target_os = "windows")'.dependencies] 25 | glutin_wgl_sys = "0.5" 26 | winapi = { version = "0.3", features = [ "winnt", "winuser", "wingdi", "libloaderapi" ] } 27 | -------------------------------------------------------------------------------- /render_gl_offscreen/build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(target_os = "linux"))] 2 | fn main() { 3 | // No build steps to take for non-linux OSes 4 | } 5 | 6 | #[cfg(target_os = "linux")] 7 | fn main() { 8 | use std::env; 9 | use std::path::{PathBuf}; 10 | 11 | // Linux build: generate bindings for gbm 12 | let out = PathBuf::from(env::var("OUT_DIR").unwrap()); 13 | let out = out.join("gbm.rs"); 14 | 15 | bindgen::Builder::default() 16 | .header("tiny_gbm.h") 17 | .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) 18 | .generate() 19 | .expect("Failed to generate bindings for gbm") 20 | .write_to_file(out) 21 | .expect("Could not write gbm.rs"); 22 | } 23 | -------------------------------------------------------------------------------- /render_gl_offscreen/linux_gbm.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /render_gl_offscreen/src/egl.rs: -------------------------------------------------------------------------------- 1 | pub use ::egl::*; 2 | 3 | #[link(name = "EGL")] 4 | extern { } 5 | 6 | // 7 | // Constants missing from the EGL crate 8 | // 9 | 10 | pub const EGL_PLATFORM_GBM_KHR: EGLenum = 0x31D7; 11 | pub const EGL_PLATFORM_WAYLAND_KHR: EGLenum = 0x31D8; 12 | pub const EGL_PLATFORM_X11_KHR: EGLenum = 0x31D5; 13 | pub const EGL_PLATFORM_X11_SCREEN_KHR: EGLenum = 0x31D6; 14 | pub const EGL_PLATFORM_DEVICE_EXT: EGLenum = 0x313F; 15 | pub const EGL_PLATFORM_WAYLAND_EXT: EGLenum = 0x31D8; 16 | pub const EGL_PLATFORM_X11_EXT: EGLenum = 0x31D5; 17 | pub const EGL_PLATFORM_X11_SCREEN_EXT: EGLenum = 0x31D6; 18 | pub const EGL_PLATFORM_GBM_MESA: EGLenum = 0x31D7; 19 | pub const EGL_PLATFORM_SURFACELESS_MESA: EGLenum = 0x31DD; 20 | pub const EGL_CONTEXT_MAJOR_VERSION: EGLint = 0x3098; 21 | pub const EGL_CONTEXT_MINOR_VERSION: EGLint = 0x30FB; 22 | 23 | // 24 | // FFI functions missing from the EGL crate 25 | // 26 | 27 | pub mod ffi { 28 | use ::egl::*; 29 | use std::ffi::{c_void}; 30 | 31 | extern { 32 | pub fn eglGetPlatformDisplay(platform: EGLenum, native_display: *mut c_void, attributes: *const EGLint) -> EGLDisplay; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /render_gl_offscreen/src/gbm.rs: -------------------------------------------------------------------------------- 1 | #![allow(warnings)] 2 | 3 | include!(concat!(env!("OUT_DIR"), "/gbm.rs")); 4 | 5 | #[link(name = "gbm")] 6 | extern {} 7 | -------------------------------------------------------------------------------- /render_gl_offscreen/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os="linux")] pub mod egl; 2 | #[cfg(target_os="linux")] pub mod gbm; 3 | #[cfg(target_os="macos")] pub mod cgl { pub use ::cgl::*; } 4 | #[cfg(target_os="windows")] pub mod wgl { pub use ::glutin_wgl_sys::*; } 5 | #[cfg(target_os="windows")] pub mod winapi { pub use ::winapi::*; } 6 | -------------------------------------------------------------------------------- /render_gl_offscreen/tiny_gbm.h: -------------------------------------------------------------------------------- 1 | struct gbm_device; 2 | 3 | struct gbm_device* gbm_create_device(int fd); 4 | -------------------------------------------------------------------------------- /render_software/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flo_render_software" 3 | version = "0.4.0" 4 | authors = ["Andrew Hunter"] 5 | license = "Apache-2.0" 6 | edition = "2018" 7 | readme = "README.md" 8 | repository = "https://github.com/Logicalshift/flo_draw" 9 | description = "Software renderer for flo_canvas vector graphics" 10 | categories = [ "graphics", "rendering", "gui" ] 11 | include = [ "Cargo.toml", "examples/**/*", "src/**/*", "test_data/**/*", "README.md" ] 12 | 13 | [features] 14 | default = [ "render_png", "render_term", "multithreading" ] 15 | render_png = [ "png" ] 16 | render_term = [ "render_png", "base64" ] 17 | multithreading = [ "rayon" ] 18 | 19 | [dependencies] 20 | once_cell = "1.18" 21 | smallvec = "1.11" 22 | wide = "0.7" 23 | flo_canvas = "0.4" 24 | flo_sparse_array = "0.1" 25 | itertools = "0.12" 26 | png = { version = "0.17", optional = true } 27 | base64 = { version = "0.21", optional = true } 28 | rayon = { version = "1.7", optional = true } 29 | 30 | [dev-dependencies] 31 | flo_canvas = { version = "0.4", features = ["image-loading", "outline-fonts"] } 32 | futures = "0.3" 33 | -------------------------------------------------------------------------------- /render_software/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/render_software/README.md -------------------------------------------------------------------------------- /render_software/examples/flo_drawing_on_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/render_software/examples/flo_drawing_on_window.png -------------------------------------------------------------------------------- /render_software/examples/render_canvas_drawing.rs: -------------------------------------------------------------------------------- 1 | use flo_render_software::draw::*; 2 | use flo_render_software::pixel::*; 3 | use flo_render_software::render::*; 4 | use flo_render_software::scanplan::*; 5 | 6 | use flo_render_software::canvas::*; 7 | 8 | use std::time::{Instant}; 9 | 10 | /// 11 | /// Draw some canvas commands to the terminal 12 | /// 13 | pub fn main() { 14 | let mut drawing = Vec::::new(); 15 | 16 | drawing.clear_canvas(Color::Rgba(0.0, 0.0, 0.0, 0.0)); 17 | drawing.identity_transform(); 18 | drawing.circle(0.0, 0.0, 0.5); 19 | drawing.fill_color(Color::Rgba(1.0, 0.0, 0.0, 1.0)); 20 | drawing.fill(); 21 | 22 | // Create a canvas drawing and draw the mascot to it 23 | let mut canvas_drawing = CanvasDrawing::::empty(); 24 | canvas_drawing.draw(drawing); 25 | 26 | // Time some rendering (useful for profiling/optimisation) 27 | let mut frame = vec![0u8; 1920*1080*4]; 28 | let mut rgba = FrameU8Rgba::from_bytes(1920, 1080, 2.2, &mut frame).unwrap(); 29 | 30 | let render_start = Instant::now(); 31 | for _ in 0..100 { 32 | let renderer = CanvasDrawingRegionRenderer::new(PixelScanPlanner::default(), ScanlineRenderer::new(canvas_drawing.program_runner(1080.0)), 1080); 33 | rgba.render(renderer, &canvas_drawing); 34 | } 35 | let render_time = Instant::now().duration_since(render_start); 36 | let avg_micros = render_time.as_micros() / 100; 37 | println!("Frame render time: {}.{}ms", avg_micros/1000, avg_micros%1000); 38 | 39 | // Render the mascot to the terminal 40 | let mut term_renderer = TerminalRenderTarget::new(500, 400); 41 | 42 | let renderer = CanvasDrawingRegionRenderer::new(PixelScanPlanner::default(), ScanlineRenderer::new(canvas_drawing.program_runner(400.0)), 400); 43 | term_renderer.render(renderer, &canvas_drawing); 44 | } 45 | -------------------------------------------------------------------------------- /render_software/examples/software_gradient.rs: -------------------------------------------------------------------------------- 1 | use flo_render_software::render::*; 2 | use flo_render_software::canvas::*; 3 | 4 | use std::f32; 5 | 6 | /// 7 | /// Draws a simple linear gradient 8 | /// 9 | pub fn main() { 10 | // Create drawing instructions for the png 11 | let mut canvas = vec![]; 12 | 13 | let angle = (30.0/360.0) * (2.0 * f32::consts::PI); 14 | 15 | // Clear the canvas and set up the coordinates 16 | canvas.clear_canvas(Color::Rgba(1.0, 1.0, 1.0, 1.0)); 17 | canvas.canvas_height(1000.0); 18 | canvas.center_region(0.0, 0.0, 1000.0, 1000.0); 19 | 20 | canvas.layer(LayerId(0)); 21 | canvas.clear_layer(); 22 | 23 | // Set up the canvas 24 | canvas.canvas_height(1000.0); 25 | canvas.center_region(0.0, 0.0, 1000.0, 1000.0); 26 | 27 | // Set up a gradient 28 | canvas.create_gradient(GradientId(1), Color::Rgba(0.8, 0.0, 0.0, 1.0)); 29 | canvas.gradient_stop(GradientId(1), 0.33, Color::Rgba(0.3, 0.8, 0.0, 1.0)); 30 | canvas.gradient_stop(GradientId(1), 0.66, Color::Rgba(0.0, 0.3, 0.8, 1.0)); 31 | canvas.gradient_stop(GradientId(1), 1.0, Color::Rgba(0.6, 0.3, 0.9, 1.0)); 32 | 33 | let x1 = 500.0 - 300.0*f32::cos(angle); 34 | let y1 = 500.0 - 300.0*f32::sin(angle); 35 | let x2 = 500.0 + 300.0*f32::cos(angle); 36 | let y2 = 500.0 + 300.0*f32::sin(angle); 37 | 38 | // Draw a circle using the gradient 39 | canvas.new_path(); 40 | canvas.circle(500.0, 500.0, 250.0); 41 | canvas.fill_gradient(GradientId(1), x1, y1, x2, y2); 42 | canvas.fill(); 43 | 44 | canvas.line_width(4.0); 45 | canvas.stroke_color(Color::Rgba(0.0, 0.0, 0.0, 1.0)); 46 | canvas.stroke(); 47 | 48 | // Draw indicators where the gradient is moving between 49 | canvas.line_width(1.0); 50 | 51 | canvas.new_path(); 52 | canvas.circle(x1, y1, 8.0); 53 | canvas.stroke(); 54 | 55 | canvas.new_path(); 56 | canvas.circle(x2, y2, 8.0); 57 | canvas.stroke(); 58 | 59 | // Render to the terminal window 60 | render_drawing(&mut TerminalRenderTarget::new(1920, 1080), canvas.iter().cloned()); 61 | } 62 | -------------------------------------------------------------------------------- /render_software/examples/software_sprite.rs: -------------------------------------------------------------------------------- 1 | use flo_render_software::render::*; 2 | use flo_render_software::canvas::*; 3 | 4 | /// 5 | /// Simple example that displays a canvas window and renders a triangle 6 | /// 7 | pub fn main() { 8 | let mut draw = vec![]; 9 | 10 | // Clear the canvas and set up the coordinates 11 | draw.clear_canvas(Color::Rgba(0.0, 1.0, 0.0, 1.0)); 12 | draw.canvas_height(1000.0); 13 | draw.center_region(0.0, 0.0, 1000.0, 1000.0); 14 | 15 | // Create a triangle sprite 16 | draw.sprite(SpriteId(0)); 17 | draw.clear_sprite(); 18 | draw.new_path(); 19 | draw.move_to(200.0, 200.0); 20 | draw.line_to(800.0, 200.0); 21 | draw.line_to(500.0, 800.0); 22 | draw.line_to(200.0, 200.0); 23 | 24 | draw.fill_color(Color::Rgba(0.8, 0.4, 0.2, 1.0)); 25 | draw.fill(); 26 | 27 | // Draw the triangle in a few places 28 | draw.layer(LayerId(0)); 29 | 30 | draw.sprite_transform(SpriteTransform::Identity); 31 | draw.draw_sprite(SpriteId(0)); 32 | 33 | draw.sprite_transform(SpriteTransform::Identity); 34 | draw.sprite_transform(SpriteTransform::Scale(0.5, 0.5)); 35 | draw.draw_sprite(SpriteId(0)); 36 | 37 | draw.sprite_transform(SpriteTransform::Identity); 38 | draw.sprite_transform(SpriteTransform::Rotate(30.0)); 39 | draw.draw_sprite(SpriteId(0)); 40 | 41 | draw.sprite_transform(SpriteTransform::Identity); 42 | draw.sprite_transform(SpriteTransform::Translate(100.0, 100.0)); 43 | draw.draw_sprite(SpriteId(0)); 44 | 45 | draw.sprite_transform(SpriteTransform::Identity); 46 | draw.sprite_transform(SpriteTransform::Translate(200.0, 100.0)); 47 | draw.draw_sprite(SpriteId(0)); 48 | 49 | draw.sprite_transform(SpriteTransform::Identity); 50 | draw.sprite_transform(SpriteTransform::Transform2D(Transform2D::translate(300.0, 100.0))); 51 | draw.draw_sprite(SpriteId(0)); 52 | 53 | // Render to the terminal window 54 | render_drawing(&mut TerminalRenderTarget::new(1920, 1080), draw.iter().cloned()); 55 | } 56 | -------------------------------------------------------------------------------- /render_software/examples/software_texture.rs: -------------------------------------------------------------------------------- 1 | use flo_render_software::render::*; 2 | use flo_render_software::canvas::*; 3 | 4 | use std::io; 5 | 6 | /// 7 | /// Draws FlowBetween's mascot as a texture 8 | /// 9 | pub fn main() { 10 | // Load a png file 11 | let flo_bytes: &[u8] = include_bytes!["flo_drawing_on_window.png"]; 12 | 13 | // Create drawing instructions for the png 14 | let mut canvas = vec![]; 15 | 16 | // Clear the canvas and set up the coordinates 17 | canvas.clear_canvas(Color::Rgba(1.0, 1.0, 1.0, 1.0)); 18 | canvas.canvas_height(1000.0); 19 | canvas.center_region(0.0, 0.0, 1000.0, 1000.0); 20 | 21 | // Set up the texture 22 | let (flo_w, flo_h) = canvas.load_texture(TextureId(0), io::Cursor::new(flo_bytes)).unwrap(); 23 | 24 | let ratio = (flo_w as f32)/(flo_h as f32); 25 | let height = 1000.0 / ratio; 26 | let y_pos = (1000.0-height)/2.0; 27 | 28 | // Draw a rectangle... 29 | canvas.new_path(); 30 | canvas.rect(0.0, y_pos, 1000.0, y_pos+height); 31 | 32 | // Fill with the texture we just loaded 33 | canvas.fill_texture(TextureId(0), 0.0, y_pos+height as f32, 1000.0, y_pos); 34 | canvas.fill(); 35 | 36 | // Render to the terminal window 37 | render_drawing(&mut TerminalRenderTarget::new(1920, 1080), canvas.iter().cloned()); 38 | } 39 | -------------------------------------------------------------------------------- /render_software/examples/software_texture_scaling.rs: -------------------------------------------------------------------------------- 1 | use flo_render_software::render::*; 2 | use flo_render_software::canvas::*; 3 | 4 | use std::io; 5 | 6 | fn draw_scaled_mascot(canvas: &mut impl GraphicsPrimitives, x: f32, y: f32, width: f32, height: f32) { 7 | canvas.new_path(); 8 | canvas.rect(x, y, x+width, y+height); 9 | 10 | canvas.fill_texture(TextureId(0), x, y+height, x+width, y); 11 | canvas.fill(); 12 | } 13 | 14 | /// 15 | /// Draws FlowBetween's mascot as a texture at different scales 16 | /// 17 | pub fn main() { 18 | // Load a png file 19 | let flo_bytes: &[u8] = include_bytes!["flo_drawing_on_window.png"]; 20 | 21 | // Create drawing instructions for the png 22 | let mut canvas = vec![]; 23 | 24 | // Clear the canvas and set up the coordinates 25 | canvas.clear_canvas(Color::Rgba(1.0, 1.0, 1.0, 1.0)); 26 | canvas.canvas_height(1000.0); 27 | canvas.center_region(0.0, 0.0, 1000.0, 1000.0); 28 | 29 | // Set up the texture 30 | let (flo_w, flo_h) = canvas.load_texture(TextureId(0), io::Cursor::new(flo_bytes)).unwrap(); 31 | 32 | let ratio = (flo_w as f32)/(flo_h as f32); 33 | 34 | // Draw a bunch of mascots 35 | draw_scaled_mascot(&mut canvas, 87.5 + 0.0, 0.0, 50.0, 50.0/ratio); 36 | draw_scaled_mascot(&mut canvas, 87.5 + 75.0, 0.0, 100.0, 100.0/ratio); 37 | draw_scaled_mascot(&mut canvas, 87.5 + 200.0, 0.0, 200.0, 200.0/ratio); 38 | draw_scaled_mascot(&mut canvas, 87.5 + 425.0, 0.0, 400.0, 400.0/ratio); 39 | draw_scaled_mascot(&mut canvas, 250.0, 500.0, 500.0, 500.0/ratio); 40 | 41 | // Render to the terminal window 42 | render_drawing(&mut TerminalRenderTarget::new(1920, 1080), canvas.iter().cloned()); 43 | } 44 | -------------------------------------------------------------------------------- /render_software/examples/software_texture_transform.rs: -------------------------------------------------------------------------------- 1 | use flo_render_software::render::*; 2 | use flo_render_software::canvas::*; 3 | 4 | use std::io; 5 | 6 | fn draw_scaled_mascot(canvas: &mut impl GraphicsPrimitives, x: f32, y: f32, width: f32, height: f32, flo_w: f32, flo_h: f32) { 7 | canvas.new_path(); 8 | canvas.rect(x, y, x+width, y+height); 9 | 10 | canvas.fill_texture(TextureId(0), x, y+height, x+width, y); 11 | canvas.fill_transform(Transform2D::translate(-flo_w/2.0, -flo_h/2.0)); 12 | canvas.fill_transform(Transform2D::rotate_degrees(30.0)); 13 | canvas.fill_transform(Transform2D::translate(flo_w/2.0, flo_h/2.0)); 14 | canvas.fill(); 15 | } 16 | 17 | /// 18 | /// Draws FlowBetween's mascot as a texture at different scales 19 | /// 20 | pub fn main() { 21 | // Load a png file 22 | let flo_bytes: &[u8] = include_bytes!["flo_drawing_on_window.png"]; 23 | 24 | // Create drawing instructions for the png 25 | let mut canvas = vec![]; 26 | 27 | // Clear the canvas and set up the coordinates 28 | canvas.clear_canvas(Color::Rgba(1.0, 1.0, 1.0, 1.0)); 29 | canvas.canvas_height(1000.0); 30 | canvas.center_region(0.0, 0.0, 1000.0, 1000.0); 31 | 32 | // Set up the texture 33 | let (flo_w, flo_h) = canvas.load_texture(TextureId(0), io::Cursor::new(flo_bytes)).unwrap(); 34 | 35 | let ratio = (flo_w as f32)/(flo_h as f32); 36 | 37 | // Draw a bunch of mascots 38 | draw_scaled_mascot(&mut canvas, 87.5 + 0.0, 0.0, 50.0, 50.0/ratio, flo_w as _, flo_h as _); 39 | draw_scaled_mascot(&mut canvas, 87.5 + 75.0, 0.0, 100.0, 100.0/ratio, flo_w as _, flo_h as _); 40 | draw_scaled_mascot(&mut canvas, 87.5 + 200.0, 0.0, 200.0, 200.0/ratio, flo_w as _, flo_h as _); 41 | draw_scaled_mascot(&mut canvas, 87.5 + 425.0, 0.0, 400.0, 400.0/ratio, flo_w as _, flo_h as _); 42 | draw_scaled_mascot(&mut canvas, 250.0, 500.0, 500.0, 500.0/ratio, flo_w as _, flo_h as _); 43 | 44 | // Render to the terminal window 45 | render_drawing(&mut TerminalRenderTarget::new(1920, 1080), canvas.iter().cloned()); 46 | } 47 | -------------------------------------------------------------------------------- /render_software/src/draw/mod.rs: -------------------------------------------------------------------------------- 1 | mod canvas_drawing; 2 | mod drawing_state; 3 | mod layer; 4 | mod prepared_layer; 5 | mod pixel_programs; 6 | mod path; 7 | mod stroke; 8 | mod transform; 9 | mod canvas_drawing_region_renderer; 10 | mod texture; 11 | mod gradient; 12 | mod dynamic_sprites; 13 | mod sprite; 14 | 15 | pub use canvas_drawing::*; 16 | pub use canvas_drawing_region_renderer::*; 17 | -------------------------------------------------------------------------------- /render_software/src/draw/prepared_layer.rs: -------------------------------------------------------------------------------- 1 | use crate::edgeplan::*; 2 | 3 | use flo_canvas as canvas; 4 | 5 | use std::sync::*; 6 | 7 | /// 8 | /// A layer that has been prepared for rendering 9 | /// 10 | #[derive(Clone)] 11 | pub struct PreparedLayer { 12 | /// The edges that are part of this layer (prepared for rendering) 13 | pub (super) edges: Arc>>, 14 | 15 | /// The bounding box of the edge plan, calculated as it was prepared 16 | pub (super) bounds: ((f64, f64), (f64, f64)), 17 | 18 | /// Transform to map render coordinates to sprite coordinates (the coordinates used by the original render) 19 | /// 20 | /// Note that we store the sprite in render coordinate as things like the flattening edges assume that the coordinates 21 | /// work this way (and are thus simpler as they don't have to understand the difference between a sprite and a normal layer) 22 | pub (super) inverse_transform: canvas::Transform2D, 23 | } 24 | -------------------------------------------------------------------------------- /render_software/src/edgeplan/edge_descriptor_intercept.rs: -------------------------------------------------------------------------------- 1 | use super::edge_intercept_direction::*; 2 | 3 | use std::cmp::{Ordering}; 4 | 5 | /// 6 | /// Describes a position within an edge descriptor 7 | /// 8 | /// These are ordered, and have three parts: the subpath ID, the 'edge ID' for where there are multiple edges and the edge position 9 | /// which can distinguish multiple intercepts along the same edge (it's usually the 't' value for the intercept) 10 | /// 11 | #[derive(Clone, Copy, Debug, PartialEq)] 12 | pub struct EdgePosition(pub usize, pub usize, pub f64); 13 | 14 | /// 15 | /// Describes an intercept from an edge descriptor 16 | /// 17 | #[derive(Clone, Copy, PartialEq, Debug)] 18 | pub struct EdgeDescriptorIntercept { 19 | /// 20 | /// The x-position of this intercept 21 | /// 22 | pub x_pos: f64, 23 | 24 | /// 25 | /// The direction that the edge that is being crossed is travelling in 26 | /// 27 | pub direction: EdgeInterceptDirection, 28 | 29 | /// 30 | /// The position of this intercept 31 | /// 32 | pub position: EdgePosition, 33 | } 34 | 35 | impl PartialOrd for EdgePosition { 36 | #[inline] 37 | fn partial_cmp(&self, other: &Self) -> Option { 38 | Some(self.cmp(other)) 39 | } 40 | } 41 | 42 | impl Eq for EdgePosition { 43 | 44 | } 45 | 46 | impl Ord for EdgePosition { 47 | #[inline] 48 | fn cmp(&self, other: &Self) -> Ordering { 49 | if self.0 < other.0 { 50 | Ordering::Less 51 | } else if self.0 > other.0 { 52 | Ordering::Greater 53 | } else if self.1 < other.1 { 54 | Ordering::Less 55 | } else if self.1 > other.1 { 56 | Ordering::Greater 57 | } else { 58 | self.2.total_cmp(&other.2) 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /render_software/src/edgeplan/edge_id.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Identifies an edge within an edge plan 3 | /// 4 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 5 | pub struct EdgeId(pub (crate) usize); 6 | -------------------------------------------------------------------------------- /render_software/src/edgeplan/edge_intercept_direction.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Describes the direction of an edge intercept 3 | /// 4 | /// * `Toggle` intercepts enter and leave the shape every time an edge is crossed. 5 | /// * `DirectionOut` indicates an edge with the normal facing outwards (increasing the intercept counter). 6 | /// * `DirectionIn` indicates an edge with the normal facing inwards (decreasing the intercept counter). 7 | /// 8 | /// `Toggle` can be used to implement the even-odd winding rule, and the `DirectionOut` and `DirectionIn` 9 | /// directions can be used for the non-zero winding rule. 10 | /// 11 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 12 | pub enum EdgeInterceptDirection { 13 | /// 14 | /// If the LHS of the edge is inside of the shape, the RHS is outside of the shape, and vice versa 15 | /// 16 | /// This should not be combined with the `DirectionIn` and `DirectionOut` directons but if it is, 17 | /// this will set the count to 0 if the count is non-zero or 1 otherwise. 18 | /// 19 | Toggle, 20 | 21 | /// 22 | /// Adds 1 to the intercept count for the shape when passing the edge left-to-right. If the 23 | /// intercept count is non-zero after this, then the RHS is inside the shape, otherwise it is 24 | /// outside. 25 | /// 26 | DirectionOut, 27 | 28 | /// 29 | /// Subtracts 1 from the intercept count for the shape when passing the edge left-to-right. If the 30 | /// intercept count is non-zero after this, then the RHS is inside the shape, otherwise it is 31 | /// outside. 32 | /// 33 | DirectionIn, 34 | } 35 | -------------------------------------------------------------------------------- /render_software/src/edgeplan/edge_plan_intercept.rs: -------------------------------------------------------------------------------- 1 | use super::edge_intercept_direction::*; 2 | use super::shape_id::*; 3 | 4 | /// 5 | /// An intercept found against an edge along a scanline from an edgeplan 6 | /// 7 | /// These are all generated against a known y position, so only the x-position of the intercept is specified 8 | /// 9 | #[derive(Copy, Clone, Debug)] 10 | pub struct EdgePlanIntercept { 11 | pub shape: ShapeId, 12 | pub direction: EdgeInterceptDirection, 13 | pub x_pos: f64, 14 | } 15 | 16 | /// 17 | /// An intercept found against an edge along a pair of scanlines from an edgeplan 18 | /// 19 | /// These are generated between a pair of y positions 20 | /// 21 | #[derive(Copy, Clone, Debug)] 22 | pub struct EdgePlanShardIntercept { 23 | /// The shape that was intercepted 24 | pub shape: ShapeId, 25 | 26 | /// The subpixel along the shape that was intercepted (when multiple intercepts might appear on a scanline, otherwise 0) 27 | pub subpixel: u8, 28 | 29 | /// The maximum opacity to apply once inside the shape 30 | /// 31 | /// Opacity is used around shape 'apexes', where a shape only partially covers a scanline 32 | pub opacity: f32, 33 | 34 | /// The direction that the line that was crossed was intercepted 35 | pub direction: EdgeInterceptDirection, 36 | 37 | /// The place where the intercept starts (where it has 0% coverage of the new state) 38 | pub lower_x: f64, 39 | 40 | /// The place where the intercept finished (where it has 100% coverage) 41 | pub upper_x: f64, 42 | } 43 | -------------------------------------------------------------------------------- /render_software/src/edgeplan/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! # EdgePlan 3 | //! 4 | //! An 'edge-plan' is an intermediate representation of a 2D scene that can be used to generate a 'scan-plan'. It 5 | //! represents a scene as a series of edges that start or stop a program that generates pixels, optionally mixing 6 | //! the new program with the old one. To rasterize an 'edge-plan', a simple ray-casting algorithm is applied. 7 | //! 8 | //! Anti-aliasing can be achieved by tracing an edge over sub-pixels and partially mixing in the new program where 9 | //! it partially covers a pixel. A less-accurate form of anti-aliasing can also be used where we assume that the 10 | //! edges are linked by 1-pixel high linear sections. 11 | //! 12 | 13 | mod edge_intercept_direction; 14 | mod edge_descriptor; 15 | mod edge_descriptor_intercept; 16 | mod edge_id; 17 | mod shape_descriptor; 18 | mod shape_id; 19 | mod edge_plan; 20 | mod edge_plan_intercept; 21 | 22 | pub use edge_intercept_direction::*; 23 | pub use edge_descriptor::*; 24 | pub use edge_descriptor_intercept::*; 25 | pub use edge_id::*; 26 | pub use shape_descriptor::*; 27 | pub use shape_id::*; 28 | pub use edge_plan::*; 29 | pub use edge_plan_intercept::*; 30 | -------------------------------------------------------------------------------- /render_software/src/edgeplan/shape_descriptor.rs: -------------------------------------------------------------------------------- 1 | use crate::pixel::*; 2 | 3 | use smallvec::*; 4 | 5 | /// 6 | /// A shape descriptor provides information on the pixel programs to run inside of a shape 7 | /// 8 | #[derive(Clone, Debug)] 9 | pub struct ShapeDescriptor { 10 | /// The identifiers of the pixel programs to run when rendering this shape. These should be in reverse order (ie, the last one in this list will be run first) 11 | pub programs: SmallVec<[PixelProgramDataId; 1]>, 12 | 13 | /// Set to true if the shape is opaque (if transparent, programs should be provided with the results generated by the underlying shapes) 14 | pub is_opaque: bool, 15 | 16 | /// Shapes with a higher z-index are drawn first where they overlap with other shapes 17 | pub z_index: i64, 18 | } 19 | 20 | impl ShapeDescriptor { 21 | /// 22 | /// Declares an opaque shape that will fill pixels using the specified program 23 | /// 24 | #[inline] 25 | pub fn opaque(program: PixelProgramDataId) -> ShapeDescriptor { 26 | ShapeDescriptor { 27 | programs: smallvec![program], 28 | is_opaque: true, 29 | z_index: 0 30 | } 31 | } 32 | 33 | /// 34 | /// Declares a transparent shape that will blend pixels using the specified program 35 | /// 36 | #[inline] 37 | pub fn transparent(program: PixelProgramDataId) -> ShapeDescriptor { 38 | ShapeDescriptor { 39 | programs: smallvec![program], 40 | is_opaque: false, 41 | z_index: 0 42 | } 43 | } 44 | 45 | /// 46 | /// Updates a shape descriptor with a z-index to specify render ordering 47 | /// 48 | #[inline] 49 | pub fn with_z_index(mut self, z_index: i64) -> ShapeDescriptor { 50 | self.z_index = z_index; 51 | self 52 | } 53 | 54 | /// 55 | /// Updates a shape descriptor with an extra program to run on the pixels (this will be run with the values output by the earlier programs) 56 | /// 57 | #[inline] 58 | pub fn with_extra_program(mut self, program: PixelProgramDataId) -> ShapeDescriptor { 59 | self.programs.push(program); 60 | self 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /render_software/src/edgeplan/shape_id.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicUsize, Ordering}; 2 | 3 | /// 4 | /// Identifies a shape that an edge is a part of (ie, when an edge is crossed, we are entering or leaving this shape) 5 | /// 6 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 7 | pub struct ShapeId(pub (crate) usize); 8 | 9 | impl ShapeId { 10 | /// 11 | /// Creates a new shpae ID (unique within this process) 12 | /// 13 | pub fn new() -> ShapeId { 14 | static NEXT_VALUE: AtomicUsize = AtomicUsize::new(0); 15 | 16 | let next_value = NEXT_VALUE.fetch_add(1, Ordering::Relaxed); 17 | ShapeId(next_value) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /render_software/src/edges/mod.rs: -------------------------------------------------------------------------------- 1 | mod contour_edge; 2 | mod bezier_subpath_edge; 3 | mod line_stroke_edge; 4 | mod rectangle_edge; 5 | mod polyline_edge; 6 | mod flattened_bezier_subpath_edge; 7 | mod clipping_edge; 8 | 9 | pub use contour_edge::*; 10 | pub use bezier_subpath_edge::*; 11 | pub use line_stroke_edge::*; 12 | pub use rectangle_edge::*; 13 | pub use polyline_edge::*; 14 | pub use flattened_bezier_subpath_edge::*; 15 | pub use clipping_edge::*; 16 | -------------------------------------------------------------------------------- /render_software/src/edges/rectangle_edge.rs: -------------------------------------------------------------------------------- 1 | use crate::edgeplan::*; 2 | 3 | use flo_canvas as canvas; 4 | 5 | use std::ops::{Range}; 6 | use std::sync::*; 7 | 8 | /// 9 | /// Describes the edges of an axis-aligned rectangular region (this is the simplest possible drawing primitive) 10 | /// 11 | #[derive(Clone)] 12 | pub struct RectangleEdge { 13 | shape_id: ShapeId, 14 | x_bounds: Range, 15 | y_bounds: Range, 16 | } 17 | 18 | impl RectangleEdge { 19 | /// 20 | /// Creates a new rectangle covering the specified region 21 | /// 22 | pub fn new(shape_id: ShapeId, x_bounds: Range, y_bounds: Range) -> Self { 23 | Self { shape_id, x_bounds, y_bounds } 24 | } 25 | } 26 | 27 | impl EdgeDescriptor for RectangleEdge { 28 | fn clone_as_object(&self) -> Arc { 29 | Arc::new(self.clone()) 30 | } 31 | 32 | #[inline] 33 | fn prepare_to_render(&mut self) { 34 | } 35 | 36 | #[inline] 37 | fn shape(&self) -> ShapeId { 38 | self.shape_id 39 | } 40 | 41 | #[inline] 42 | fn bounding_box(&self) -> ((f64, f64), (f64, f64)) { 43 | ((self.x_bounds.start, self.y_bounds.start), (self.x_bounds.end, self.y_bounds.end)) 44 | } 45 | 46 | fn transform(&self, transform: &canvas::Transform2D) -> Arc { 47 | todo!() 48 | } 49 | 50 | #[inline] 51 | fn intercepts(&self, y_positions: &[f64], output: &mut [Vec]) { 52 | for idx in 0..y_positions.len() { 53 | let y_pos = y_positions[idx]; 54 | 55 | if !(y_pos < self.y_bounds.start || y_pos >= self.y_bounds.end) { 56 | output[idx].push(EdgeDescriptorIntercept { direction: EdgeInterceptDirection::Toggle, x_pos: self.x_bounds.start, position: EdgePosition(0, 0, y_pos-self.y_bounds.start) }); 57 | output[idx].push(EdgeDescriptorIntercept { direction: EdgeInterceptDirection::Toggle, x_pos: self.x_bounds.end, position: EdgePosition(0, 1, self.y_bounds.end-y_pos) }); 58 | } 59 | } 60 | } 61 | 62 | fn apexes(&self, output: &mut Vec) { 63 | output.extend([self.y_bounds.start, self.y_bounds.end]); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /render_software/src/filters/alpha_blend_filter.rs: -------------------------------------------------------------------------------- 1 | use super::pixel_filter_trait::*; 2 | use crate::pixel::*; 3 | 4 | use std::sync::*; 5 | 6 | /// 7 | /// The alpha blend filter 8 | /// 9 | pub struct AlphaBlendFilter 10 | where 11 | TPixel: Pixel, 12 | { 13 | alpha: TPixel::Component, 14 | } 15 | 16 | impl AlphaBlendFilter 17 | where 18 | TPixel: Pixel, 19 | { 20 | /// 21 | /// Creates an alpha blend filter that will adjust the alpha value of its target by the specified amount 22 | /// 23 | pub fn with_alpha(alpha: f64) -> Self { 24 | Self { 25 | alpha: TPixel::Component::with_value(alpha) 26 | } 27 | } 28 | } 29 | 30 | impl PixelFilter for AlphaBlendFilter 31 | where 32 | TPixel: Pixel, 33 | { 34 | type Pixel = TPixel; 35 | 36 | #[inline] 37 | fn with_scale(&self, _x_scale: f64, _y_scale: f64) -> Option>> { 38 | None 39 | } 40 | 41 | #[inline] 42 | fn input_lines(&self) -> (usize, usize) { 43 | (0, 0) 44 | } 45 | 46 | #[inline] 47 | fn extra_columns(&self) -> (usize, usize) { 48 | (0, 0) 49 | } 50 | 51 | fn filter_line(&self, _ypos: usize, input_lines: &[&[Self::Pixel]], output_line: &mut [Self::Pixel]) { 52 | for (input, output) in input_lines[0].iter().zip(output_line.iter_mut()) { 53 | *output = *input * self.alpha; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /render_software/src/filters/mod.rs: -------------------------------------------------------------------------------- 1 | mod pixel_filter_trait; 2 | mod texture_filter; 3 | mod alpha_blend_filter; 4 | mod displacement_map_filter; 5 | mod gaussian_blur_filter; 6 | mod mask_filter; 7 | mod tint_filter; 8 | mod combined_filter; 9 | 10 | pub use pixel_filter_trait::*; 11 | pub use texture_filter::*; 12 | pub use alpha_blend_filter::*; 13 | pub use displacement_map_filter::*; 14 | pub use gaussian_blur_filter::*; 15 | pub use mask_filter::*; 16 | pub use tint_filter::*; 17 | pub use combined_filter::*; 18 | -------------------------------------------------------------------------------- /render_software/src/filters/tint_filter.rs: -------------------------------------------------------------------------------- 1 | use super::pixel_filter_trait::*; 2 | use crate::pixel::*; 3 | 4 | use flo_canvas as canvas; 5 | 6 | use std::sync::*; 7 | 8 | /// 9 | /// The tint blend filter 10 | /// 11 | pub struct TintFilter 12 | where 13 | TPixel: Pixel, 14 | { 15 | tint: TPixel, 16 | } 17 | 18 | impl TintFilter 19 | where 20 | TPixel: Pixel, 21 | { 22 | /// 23 | /// Creates a tint filter that will adjust the colours of its target by the specified amount 24 | /// 25 | pub fn with_color(color: canvas::Color, gamma: f64) -> Self { 26 | Self { 27 | tint: TPixel::from_color(color, gamma) 28 | } 29 | } 30 | } 31 | 32 | impl PixelFilter for TintFilter 33 | where 34 | TPixel: Pixel, 35 | { 36 | type Pixel = TPixel; 37 | 38 | #[inline] 39 | fn with_scale(&self, _x_scale: f64, _y_scale: f64) -> Option>> { 40 | None 41 | } 42 | 43 | #[inline] 44 | fn input_lines(&self) -> (usize, usize) { 45 | (0, 0) 46 | } 47 | 48 | #[inline] 49 | fn extra_columns(&self) -> (usize, usize) { 50 | (0, 0) 51 | } 52 | 53 | fn filter_line(&self, _ypos: usize, input_lines: &[&[Self::Pixel]], output_line: &mut [Self::Pixel]) { 54 | for (input, output) in input_lines[0].iter().zip(output_line.iter_mut()) { 55 | *output = *input * self.tint; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /render_software/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// Kinds of edges that can be used in an edge plan 2 | pub mod edges; 3 | 4 | /// An edge plan divides a 2D spaces into regions using arbitrary edge definitions, and can be rendered down into a scan plan 5 | pub mod edgeplan; 6 | 7 | /// A scan plan describes the actions required to draw a single scanline (modelling a 1 dimensional space) 8 | pub mod scanplan; 9 | 10 | /// A pixel models a single colour sample (thematically it could be considered 0 dimensional, though really a pixel is better modelled as aggregation of the light passing through a particular region) 11 | pub mod pixel; 12 | 13 | /// Well-known pixel programs 14 | pub mod pixel_programs; 15 | 16 | /// The filters module implements filters that can be run against pixel output 17 | pub mod filters; 18 | 19 | /// Renderers convert from data represented by a series of instructions to a simpler form 20 | pub mod render; 21 | 22 | /// The 'draw' module converts from `flo_canvas::Draw` instructions to layered edge plans 23 | pub mod draw; 24 | 25 | pub use flo_canvas as canvas; 26 | pub use flo_canvas::curves as curves; 27 | -------------------------------------------------------------------------------- /render_software/src/pixel/gamma_lut.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// A 16 to 8 bit gamma correction look-up table 3 | /// 4 | pub struct U8GammaLut { 5 | gamma: f64, 6 | look_up_table: [u8;65536], 7 | } 8 | 9 | impl U8GammaLut { 10 | /// 11 | /// Creates a gamma look-up table for a gamma correction value 12 | /// 13 | pub fn new(gamma: f64) -> Self { 14 | // Allocate the LUT (64k in size) 15 | let mut lut = [0u8; 65536]; 16 | 17 | // Calculate the 65536 gamma-corrected values 18 | for idx in 0..65536 { 19 | let t = (idx as f64)/65535.0; 20 | let t = t.powf(gamma); 21 | let t = (t * 255.0) as u8; 22 | 23 | lut[idx] = t; 24 | } 25 | 26 | // Store the final look-up table 27 | U8GammaLut { 28 | gamma: gamma, 29 | look_up_table: lut 30 | } 31 | } 32 | 33 | /// 34 | /// Returns the gamma correction value this table is using 35 | /// 36 | #[inline] 37 | pub fn gamma(&self) -> f64 { 38 | self.gamma 39 | } 40 | 41 | /// 42 | /// Looks up a gamma corrected value. `val` can be from 0 to 65535 (where 65535 represents an intensity of 1.0) 43 | /// 44 | #[inline] 45 | pub fn look_up(&self, val: u16) -> u8 { 46 | unsafe { *self.look_up_table.get_unchecked(val as usize) } 47 | } 48 | } -------------------------------------------------------------------------------- /render_software/src/pixel/mod.rs: -------------------------------------------------------------------------------- 1 | mod pixel_trait; 2 | mod alpha_blend_trait; 3 | mod u32_fixed_point; 4 | pub (crate) mod gamma_lut; 5 | mod to_gamma_colorspace_trait; 6 | mod to_linear_colorspace_trait; 7 | mod u8_rgba; 8 | mod u16_rgba; 9 | mod u32_argb; 10 | mod f32_linear; 11 | mod f32_linear_texture_reader; 12 | mod u32_linear; 13 | mod u32_linear_texture_reader; 14 | mod pixel_program; 15 | mod pixel_program_cache; 16 | mod pixel_program_runner; 17 | mod rgba_texture; 18 | mod u16_linear_texture; 19 | mod texture_reader; 20 | mod mip_map; 21 | 22 | pub use pixel_trait::*; 23 | pub use alpha_blend_trait::*; 24 | pub use u32_fixed_point::*; 25 | pub use to_gamma_colorspace_trait::*; 26 | pub use to_linear_colorspace_trait::*; 27 | pub use u8_rgba::*; 28 | pub use u32_argb::*; 29 | pub use f32_linear::*; 30 | pub use u32_linear::*; 31 | pub use pixel_program::*; 32 | pub use pixel_program_cache::*; 33 | pub use pixel_program_runner::*; 34 | pub use rgba_texture::*; 35 | pub use texture_reader::*; 36 | pub use u16_linear_texture::*; 37 | pub use u16_rgba::*; 38 | pub use mip_map::*; 39 | -------------------------------------------------------------------------------- /render_software/src/pixel/texture_reader.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Trait implemented by types that can be read from a texture type 3 | /// 4 | pub trait TextureReader : Send + Sync + Sized 5 | where 6 | TTexture: Send + Sync, 7 | { 8 | /// 9 | /// Returns the size of a texture in pixels 10 | /// 11 | fn texture_size(texture: &TTexture) -> (f64, f64); 12 | 13 | /// 14 | /// Reads a sequence of pixels from this texture into a target array 15 | /// 16 | /// Coordinates are in fractions of pixels to allow for a texture reader to support bilinear interpolation or mipmapping 17 | /// 18 | fn read_pixels(texture: &TTexture, positions: &[(f64, f64)]) -> Vec; 19 | 20 | /// 21 | /// Reads a set of pixels across a linear gradient 22 | /// 23 | /// This is a common way that pixels are read out from a texture, so this function can be overridden to optimise this 24 | /// for different types of texture storage if needed. 25 | /// 26 | /// This reads `count` pixels at locations `t = 0, 1, 2, ...` such that `u = dx * t + offset` and `x = x_gradient.0 * u + x_gradient.1`, 27 | /// `y = y_gradient.0 * u + y_gradient.1`. 28 | /// 29 | #[inline] 30 | fn read_pixels_linear(texture: &TTexture, offset: f64, dx: f64, x_gradient: (f64, f64), y_gradient: (f64, f64), count: usize) -> Vec { 31 | // Allocate enough space to store the pixels 32 | let mut positions = Vec::with_capacity(count); 33 | 34 | // Calculate the positions for the pixels 35 | positions.extend((0..count).map(|t| { 36 | let t = t as f64; 37 | let u = dx * t + offset; 38 | let x = x_gradient.0 * u + x_gradient.1; 39 | let y = y_gradient.0 * u + y_gradient.1; 40 | 41 | (x, y) 42 | })); 43 | 44 | Self::read_pixels(texture, &positions) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /render_software/src/pixel/to_gamma_colorspace_trait.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Trait implemented by pixel types that can be converted to a gamma-corrected colour space 3 | /// 4 | pub trait ToGammaColorSpace : Sized { 5 | /// Converts this pixel from its current colour space to a gamma corrected colour space 6 | fn to_gamma_colorspace(input_pixels: &[Self], output_pixels: &mut [TargetPixel], gamma: f64); 7 | } 8 | -------------------------------------------------------------------------------- /render_software/src/pixel/to_linear_colorspace_trait.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Trait implemented by pixel types that can convert themselves to a linear colour space 3 | /// 4 | /// Linear colour spaces have no gamma correction applied to their values (ie, doubling the size of a component 5 | /// doubles its brightness) 6 | /// 7 | pub trait ToLinearColorSpace : Sized { 8 | /// 9 | /// Converts a set of pixels to the target pixel type 10 | /// 11 | fn to_linear_colorspace(input_pixels: &[Self], output_pixels: &mut [TargetPixel]); 12 | } 13 | -------------------------------------------------------------------------------- /render_software/src/pixel_programs/debug_ypos.rs: -------------------------------------------------------------------------------- 1 | use crate::pixel::*; 2 | use crate::scanplan::*; 3 | 4 | use flo_canvas::*; 5 | 6 | use std::marker::{PhantomData}; 7 | 8 | /// 9 | /// Debugging program that outputs a colour that indicates the y position used to generate a particular scanline 10 | /// 11 | /// This can be used to get the parameters needed to reproduce buggy scanline generation 12 | /// 13 | pub struct DebugYposProgram { 14 | pixel: PhantomData 15 | } 16 | 17 | impl Default for DebugYposProgram 18 | where 19 | TPixel: Pixel, 20 | { 21 | fn default() -> Self { 22 | DebugYposProgram { pixel: PhantomData } 23 | } 24 | } 25 | 26 | impl PixelProgram for DebugYposProgram 27 | where 28 | TPixel: Pixel, 29 | { 30 | type Pixel = TPixel; 31 | type ProgramData = f64; 32 | 33 | fn draw_pixels(&self, _: &PixelProgramRenderCache, target: &mut [Self::Pixel], x_range: std::ops::Range, _: &ScanlineTransform, y_pos: f64, multiplier: &f64) { 34 | // Use the multiplier to calculate the y-position 35 | let y_pos = y_pos * *multiplier; 36 | 37 | // Calculate RGB values for the y position (gives us a 0-100 range from all three colour components) 38 | let r = y_pos % 1.0; 39 | let g = ((y_pos - r) / 10.0) % 1.0; 40 | let b = ((((y_pos - r) / 10.0) - g) / 10.0) % 1.0; 41 | 42 | // Fill the target range with the specified pixel colour 43 | let pixel = TPixel::from_color(Color::Rgba(r as _, g as _, b as _, 1.0), 2.2); 44 | 45 | target[(x_range.start as usize)..(x_range.end as usize)].iter_mut() 46 | .for_each(|target_pixel| *target_pixel = pixel); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /render_software/src/pixel_programs/mod.rs: -------------------------------------------------------------------------------- 1 | mod solid_color; 2 | mod source_over; 3 | mod blend; 4 | mod basic_texture; 5 | mod bilinear_texture; 6 | mod mip_map_texture; 7 | mod basic_sprite; 8 | mod transformed_sprite; 9 | mod gradient_linear; 10 | mod filtered_scanline; 11 | mod debug_ypos; 12 | 13 | pub use solid_color::*; 14 | pub use source_over::*; 15 | pub use blend::*; 16 | pub use basic_texture::*; 17 | pub use bilinear_texture::*; 18 | pub use mip_map_texture::*; 19 | pub use basic_sprite::*; 20 | pub use transformed_sprite::*; 21 | pub use gradient_linear::*; 22 | pub use filtered_scanline::*; 23 | pub use debug_ypos::*; 24 | -------------------------------------------------------------------------------- /render_software/src/pixel_programs/solid_color.rs: -------------------------------------------------------------------------------- 1 | use crate::pixel::*; 2 | use crate::scanplan::*; 3 | 4 | use std::marker::{PhantomData}; 5 | use std::ops::{Range}; 6 | use std::sync::*; 7 | 8 | /// Data for a solid colour pixel 9 | pub struct SolidColorData(pub TPixel); 10 | 11 | /// 12 | /// Pixel program that writes out a solid colour according to the solid colour data it's supplied 13 | /// 14 | pub struct SolidColorProgram { 15 | pixel: PhantomData> 16 | } 17 | 18 | impl Default for SolidColorProgram { 19 | fn default() -> Self { 20 | SolidColorProgram { pixel: PhantomData } 21 | } 22 | } 23 | 24 | impl PixelProgram for SolidColorProgram { 25 | type Pixel = TPixel; 26 | type ProgramData = SolidColorData; 27 | 28 | #[inline] 29 | fn draw_pixels(&self, _data_cache: &PixelProgramRenderCache, target: &mut [Self::Pixel], x_range: Range, _: &ScanlineTransform, _y_pos: f64, program_data: &Self::ProgramData) { 30 | for pixel in target[(x_range.start as usize)..(x_range.end as usize)].iter_mut() { 31 | *pixel = program_data.0; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /render_software/src/pixel_programs/source_over.rs: -------------------------------------------------------------------------------- 1 | use super::solid_color::*; 2 | 3 | use crate::pixel::*; 4 | use crate::scanplan::*; 5 | 6 | use std::marker::{PhantomData}; 7 | use std::ops::{Range}; 8 | use std::sync::*; 9 | 10 | /// 11 | /// Pixel program that applies an alpha-blended colour using the source over algorithm 12 | /// 13 | pub struct SourceOverColorProgram { 14 | pixel: PhantomData> 15 | } 16 | 17 | impl Default for SourceOverColorProgram 18 | where 19 | TPixel: AlphaBlend 20 | { 21 | fn default() -> Self { 22 | SourceOverColorProgram { pixel: PhantomData } 23 | } 24 | } 25 | 26 | impl PixelProgram for SourceOverColorProgram 27 | where 28 | TPixel: AlphaBlend 29 | { 30 | type Pixel = TPixel; 31 | type ProgramData = SolidColorData; 32 | 33 | #[inline] 34 | fn draw_pixels(&self, _data_cache: &PixelProgramRenderCache, target: &mut [Self::Pixel], x_range: Range, _: &ScanlineTransform, _y_pos: f64, program_data: &Self::ProgramData) { 35 | for pixel in target[(x_range.start as usize)..(x_range.end as usize)].iter_mut() { 36 | *pixel = program_data.0.source_over(*pixel); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /render_software/src/render/edge_plan.rs: -------------------------------------------------------------------------------- 1 | use super::render_source_trait::*; 2 | use super::edgeplan_region_renderer::*; 3 | use super::scanline_renderer::*; 4 | 5 | use crate::edgeplan::*; 6 | use crate::pixel::*; 7 | use crate::scanplan::*; 8 | 9 | impl RenderSource for EdgePlan 10 | where 11 | TEdge: EdgeDescriptor, 12 | TScanPlanner: ScanPlanner, 13 | TProgramRunner: PixelProgramRunner, 14 | TProgramRunner::TPixel: 'static + Send + Default + Copy + AlphaBlend, 15 | { 16 | /// The region renderer takes instances of this type and uses them to generate pixel values in a region 17 | type RegionRenderer = EdgePlanRegionRenderer>; 18 | 19 | /// 20 | /// Builds a region renderer that can read from this type and output pixels along rows 21 | /// 22 | fn create_region_renderer(planner: TScanPlanner, pixel_runner: TProgramRunner) -> Self::RegionRenderer { 23 | let scanline_renderer = ScanlineRenderer::new(pixel_runner); 24 | let region_renderer = EdgePlanRegionRenderer::new(planner, scanline_renderer); 25 | 26 | region_renderer 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /render_software/src/render/frame_size.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Describes the size of a frame in pixels 3 | /// 4 | #[derive(Copy, Clone, Debug)] 5 | pub struct FrameSize { 6 | pub width: usize, 7 | pub height: usize, 8 | } 9 | 10 | /// 11 | /// Describes the size of a frame in pixels, and the gamma correction value to apply to the rendering result 12 | /// 13 | /// This is used when the target frame buffer uses a non-linear colour space (as is typical when rendering to RGBA bytes) 14 | /// 15 | #[derive(Copy, Clone, Debug)] 16 | pub struct GammaFrameSize { 17 | pub width: usize, 18 | pub height: usize, 19 | pub gamma: f64, 20 | } 21 | 22 | /// 23 | /// Converts a FrameSize into a GammaFrameSize (uses the standard 2.2 gamma value) 24 | /// 25 | impl From for GammaFrameSize { 26 | #[inline] 27 | fn from(frame_size: FrameSize) -> GammaFrameSize { 28 | GammaFrameSize { 29 | width: frame_size.width, 30 | height: frame_size.height, 31 | gamma: 2.2 } 32 | } 33 | } 34 | 35 | impl From for FrameSize { 36 | #[inline] 37 | fn from(frame_size: GammaFrameSize) -> FrameSize { 38 | FrameSize { 39 | width: frame_size.width, 40 | height: frame_size.height 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /render_software/src/render/mod.rs: -------------------------------------------------------------------------------- 1 | mod render_source_trait; 2 | mod render_target_trait; 3 | mod renderer; 4 | mod scanline_renderer; 5 | mod render_slice; 6 | mod frame_size; 7 | mod edgeplan_region_renderer; 8 | mod edge_plan; 9 | mod u8_frame_renderer; 10 | mod u32_frame_renderer; 11 | mod u16_linear_frame_renderer; 12 | mod rgba_frame; 13 | mod render_frame; 14 | mod image_render; 15 | mod terminal_render; 16 | 17 | pub use render_source_trait::*; 18 | pub use render_target_trait::*; 19 | pub use renderer::*; 20 | pub use scanline_renderer::*; 21 | pub use render_slice::*; 22 | pub use frame_size::*; 23 | pub use edgeplan_region_renderer::*; 24 | pub use u8_frame_renderer::*; 25 | pub use u32_frame_renderer::*; 26 | pub use u16_linear_frame_renderer::*; 27 | pub use rgba_frame::*; 28 | pub use render_frame::*; 29 | pub use image_render::*; 30 | pub use terminal_render::*; 31 | -------------------------------------------------------------------------------- /render_software/src/render/render_frame.rs: -------------------------------------------------------------------------------- 1 | use crate::draw::*; 2 | use crate::pixel::*; 3 | use crate::scanplan::*; 4 | 5 | use super::render_source_trait::*; 6 | use super::render_target_trait::*; 7 | use super::scanline_renderer::*; 8 | 9 | use flo_canvas as canvas; 10 | 11 | /// 12 | /// Renders and entire frame to a render target from a render source 13 | /// 14 | pub fn render_frame_with_planner<'a, TScanPlanner, TProgramRunner, TSource, TTarget>(scan_planner: TScanPlanner, program_runner: TProgramRunner, source: &TSource, target: &mut TTarget) 15 | where 16 | TScanPlanner: ScanPlanner, 17 | TProgramRunner: PixelProgramRunner, 18 | TProgramRunner::TPixel: 'static, 19 | TSource: RenderSource, 20 | TTarget: RenderTarget, 21 | { 22 | let region_renderer = TSource::create_region_renderer(scan_planner, program_runner); 23 | target.render(region_renderer, source); 24 | } 25 | 26 | /// 27 | /// Renders a set of drawing instructions to a target using the default settings 28 | /// 29 | pub fn render_drawing(target: &mut TTarget, drawing: impl IntoIterator) 30 | where 31 | TTarget: RenderTarget, 32 | { 33 | // Prepare a canvas drawing 34 | let mut canvas_drawing = CanvasDrawing::::empty(); 35 | canvas_drawing.draw(drawing); 36 | 37 | let renderer = CanvasDrawingRegionRenderer::new(ShardScanPlanner::default(), ScanlineRenderer::new(canvas_drawing.program_runner(target.height() as _)), target.height()); 38 | target.render(renderer, &canvas_drawing); 39 | } 40 | -------------------------------------------------------------------------------- /render_software/src/render/render_slice.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Specifies a portion of a frame to render 3 | /// 4 | #[derive(Debug)] 5 | pub struct RenderSlice { 6 | /// The width in pixels of a scanline 7 | pub width: usize, 8 | 9 | /// The y-positions that should be rendered to the buffer 10 | pub y_positions: Vec, 11 | } 12 | -------------------------------------------------------------------------------- /render_software/src/render/render_source_trait.rs: -------------------------------------------------------------------------------- 1 | use super::renderer::*; 2 | use super::render_slice::*; 3 | 4 | use crate::pixel::*; 5 | use crate::scanplan::*; 6 | 7 | /// 8 | /// A render source can create an edge region renderer to be used with a render target 9 | /// 10 | pub trait RenderSource 11 | where 12 | TScanPlanner: ScanPlanner, 13 | TProgramRunner: PixelProgramRunner, 14 | { 15 | /// The region renderer takes instances of this type and uses them to generate pixel values in a region 16 | type RegionRenderer: Renderer; 17 | 18 | /// 19 | /// Builds a region renderer that can read from this type and output pixels along rows 20 | /// 21 | fn create_region_renderer(planner: TScanPlanner, pixel_runner: TProgramRunner) -> Self::RegionRenderer; 22 | } 23 | -------------------------------------------------------------------------------- /render_software/src/render/render_target_trait.rs: -------------------------------------------------------------------------------- 1 | use super::renderer::*; 2 | use super::render_slice::*; 3 | 4 | /// 5 | /// Trait implemented by types that can act as a render target 6 | /// 7 | /// The 'IntermediatePixel' type is used to perform the initial rendering and blending, before conversion to the final format 8 | /// 9 | pub trait RenderTarget { 10 | /// 11 | /// Retrieves the width of the target in pixels 12 | /// 13 | fn width(&self) -> usize; 14 | 15 | /// 16 | /// Retrieves the height of the target in pixels 17 | /// 18 | fn height(&self) -> usize; 19 | 20 | /// 21 | /// Renders a frame to this render target 22 | /// 23 | /// The renderer that is passed in here is a region renderer, which takes a list of y-positions and generates the pixels for those rows in the results. 24 | /// 25 | fn render(&mut self, region_renderer: TRegionRenderer, source_data: &TRegionRenderer::Source) 26 | where 27 | TRegionRenderer: Renderer; 28 | } 29 | -------------------------------------------------------------------------------- /render_software/src/render/renderer.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// A renderer converts from a set of source instructions to a set of destination values 3 | /// 4 | pub trait Renderer : Send + Sync { 5 | /// The region is used to specify what region is being rendered 6 | type Region: ?Sized; 7 | 8 | /// The source is the source instructions for the rendering 9 | type Source: Send + Sync + ?Sized; 10 | 11 | /// The dest is the target buffer type for the rendering 12 | type Dest: Send + ?Sized; 13 | 14 | /// 15 | /// Renders a set of instructions to a destination 16 | /// 17 | fn render(&self, region: &Self::Region, source: &Self::Source, dest: &mut Self::Dest); 18 | } 19 | -------------------------------------------------------------------------------- /render_software/src/scanplan/mod.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! # ScanPlan 3 | //! 4 | //! The 'scan-plan' is a low-level model of a rasterized scene. It represents each line of the final result as a 'plan' 5 | //! of programs to apply to ranges of pixels. This plan can be executed to generate the final scene, or combined with 6 | //! other plans to create more complex renders. 7 | //! 8 | //! There are a few advantages of making this plan: notably it avoids overdraw (where individual pixels are rendered 9 | //! multiple times) and it makes it easy to efficiently mix colours using f32 precision. This can make rendering faster 10 | //! for complex scenes as work can be avoided rendering pixels that will be obscured later on, and it makes it easy to 11 | //! parallize both the rendering and the generation tasks. Less complex scenes may render more slowly due to the extra 12 | //! work involved, however. 13 | //! 14 | 15 | mod alpha_coverage; 16 | pub (crate) mod buffer_stack; 17 | mod intercept_blend; 18 | mod pixel_scan_planner; 19 | mod shard; 20 | mod shard_subpixel; 21 | mod shard_scan_planner; 22 | mod background_scan_planner; 23 | mod debug_ypos_scan_planner; 24 | mod scanspan; 25 | mod scanline_plan; 26 | mod scanline_intercept; 27 | mod scanline_shard_intercept; 28 | mod scanline_transform; 29 | mod scan_planner; 30 | 31 | pub use pixel_scan_planner::*; 32 | pub use shard::*; 33 | pub use shard_scan_planner::*; 34 | pub use background_scan_planner::*; 35 | pub use debug_ypos_scan_planner::*; 36 | pub use scanspan::*; 37 | pub use scanline_plan::*; 38 | pub use scanline_intercept::*; 39 | pub use scanline_shard_intercept::*; 40 | pub use scanline_transform::*; 41 | pub use scan_planner::*; 42 | -------------------------------------------------------------------------------- /render_software/src/scanplan/scan_planner.rs: -------------------------------------------------------------------------------- 1 | use super::scanline_plan::*; 2 | use super::scanline_transform::*; 3 | 4 | use crate::edgeplan::*; 5 | 6 | use std::ops::{Range}; 7 | 8 | /// 9 | /// A scan planner is an algorithm that discovers where along a scanline to render pixels using pixel programs 10 | /// 11 | pub trait ScanPlanner : Send + Sync { 12 | /// The type of edge stored in the edge plan for this planner 13 | type Edge: EdgeDescriptor; 14 | 15 | /// 16 | /// For every scanline in `y_positions`, use the edge plan to find the intercepts at a set of y-positions, clipped to the specified x-range, and 17 | /// generating the output in the `scanlines` array. 18 | /// 19 | /// The y-position is copied into the scanlines array, and the scanlines are always generated in the same order that they are requested in. 20 | /// 21 | fn plan_scanlines(&self, edge_plan: &EdgePlan, transform: &ScanlineTransform, y_positions: &[f64], x_range: Range, scanlines: &mut [(f64, ScanlinePlan)]); 22 | } 23 | -------------------------------------------------------------------------------- /render_software/test_data/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/render_software/test_data/Lato-Bold.ttf -------------------------------------------------------------------------------- /render_software/test_data/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Logicalshift/flo_draw/810bdf54586a21e0dff7848c01398f3800b6e8da/render_software/test_data/Lato-Regular.ttf -------------------------------------------------------------------------------- /render_software/test_data/Lato-license.md: -------------------------------------------------------------------------------- 1 | Lato is included under the Open Font License (https://scripts.sil.org/OFL). 2 | It has an official site here: https://www.latofonts.com/ 3 | -------------------------------------------------------------------------------- /render_software/tests/canvas_render_tests.rs: -------------------------------------------------------------------------------- 1 | use flo_render_software::draw::*; 2 | use flo_render_software::pixel::*; 3 | use flo_render_software::render::*; 4 | use flo_render_software::scanplan::*; 5 | 6 | use flo_render_software::canvas::*; 7 | 8 | #[test] 9 | pub fn render_simple_circle() { 10 | // Render a basic circle 11 | let mut drawing = Vec::::new(); 12 | 13 | drawing.clear_canvas(Color::Rgba(0.0, 0.0, 0.0, 0.0)); 14 | drawing.identity_transform(); 15 | drawing.circle(0.0, 0.0, 0.5); 16 | drawing.fill_color(Color::Rgba(1.0, 0.0, 0.0, 1.0)); 17 | drawing.fill(); 18 | 19 | // Create a canvas drawing and draw the mascot to it 20 | let mut canvas_drawing = CanvasDrawing::::empty(); 21 | canvas_drawing.draw(drawing); 22 | 23 | // Time some rendering (useful for profiling/optimisation) 24 | let mut frame = vec![0u8; 1920*1080*4]; 25 | let mut rgba = FrameU8Rgba::from_bytes(1920, 1080, 2.2, &mut frame).unwrap(); 26 | 27 | let renderer = CanvasDrawingRegionRenderer::new(PixelScanPlanner::default(), ScanlineRenderer::new(canvas_drawing.program_runner(1080.0)), 1080); 28 | rgba.render(renderer, &canvas_drawing); 29 | } 30 | --------------------------------------------------------------------------------