diff --git a/CHANGELOG.md b/CHANGELOG.md index c680017..c9602de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,11 +16,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 widgets. - Customization. The `ui::core` module can be used to implement custom widgets and renderers. +- __Mesh support__. The types `Shape` and `Mesh` have been introduced. + Rectangles, circles, ellipses, and polylines can now be drawn with ease using + fill or stroke modes. [#50] - `Input` trait. It allows to implement reusable input handlers. [#35] - `KeyboardAndMouse` input handler. Useful to quickstart development and have easy access to the keyboard and the mouse from the get-go. [#35] - `CursorTaken` and `CursorReturned` input events. They are fired when the cursor - is used/freed by the user interface. + is used/freed by the user interface. [#35] - Off-screen text rendering support. `Font::draw` now supports any `Target` instead of a window `Frame`. [#25] - `Game::debug` performance tracking. Time spent on this method is now shown in @@ -73,6 +76,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#30]: https://github.com/hecrj/coffee/pull/30 [#35]: https://github.com/hecrj/coffee/pull/35 [#37]: https://github.com/hecrj/coffee/pull/37 +[#50]: https://github.com/hecrj/coffee/pull/50 [Elm]: https://elm-lang.org [The Elm Architecture]: https://guide.elm-lang.org/architecture/ [`stretch`]: https://github.com/vislyhq/stretch diff --git a/Cargo.toml b/Cargo.toml index bf60272..045e470 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ nalgebra = "0.18" rayon = "1.0" stretch = "0.2" twox-hash = "1.3" +lyon_tessellation = "0.13" # gfx (OpenGL) gfx = { version = "0.18", optional = true } diff --git a/examples/mesh.rs b/examples/mesh.rs new file mode 100644 index 0000000..357a575 --- /dev/null +++ b/examples/mesh.rs @@ -0,0 +1,360 @@ +use coffee::graphics::{ + Color, Frame, HorizontalAlignment, Mesh, Point, Rectangle, Shape, Window, + WindowSettings, +}; +use coffee::input::KeyboardAndMouse; +use coffee::load::Task; +use coffee::ui::{ + slider, Align, Column, Element, Justify, Radio, Renderer, Row, Slider, Text, +}; +use coffee::{Game, Result, Timer, UserInterface}; + +use std::ops::RangeInclusive; + +fn main() -> Result<()> { + ::run(WindowSettings { + title: String::from("Mesh - Coffee"), + size: (1280, 1024), + resizable: false, + fullscreen: false, + }) +} + +struct Example { + shape: ShapeOption, + mode: ModeOption, + stroke_width: u16, + radius: f32, + vertical_radius: f32, + color: Color, + polyline_points: Vec, + + stroke_width_slider: slider::State, + radius_slider: slider::State, + vertical_radius_slider: slider::State, + color_sliders: [slider::State; 3], +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ShapeOption { + Rectangle, + Circle, + Ellipse, + Polyline, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ModeOption { + Fill, + Stroke, +} + +impl Game for Example { + type Input = KeyboardAndMouse; + type LoadingScreen = (); + + fn load(_window: &Window) -> Task { + Task::new(move || Example { + shape: ShapeOption::Rectangle, + mode: ModeOption::Fill, + color: Color::WHITE, + radius: 100.0, + vertical_radius: 50.0, + stroke_width: 2, + polyline_points: Vec::new(), + + stroke_width_slider: slider::State::new(), + radius_slider: slider::State::new(), + vertical_radius_slider: slider::State::new(), + color_sliders: [slider::State::new(); 3], + }) + } + + fn interact(&mut self, input: &mut KeyboardAndMouse, _window: &mut Window) { + match self.shape { + ShapeOption::Polyline => { + self.polyline_points.extend(input.clicks()); + } + _ => {} + } + } + + fn draw(&mut self, frame: &mut Frame, _timer: &Timer) { + frame.clear(Color { + r: 0.3, + g: 0.3, + b: 0.6, + a: 1.0, + }); + + let mut mesh = Mesh::new(); + + let shape = match self.shape { + ShapeOption::Rectangle => Shape::Rectangle(Rectangle { + x: frame.width() / 4.0 - 100.0, + y: frame.height() / 2.0 - 50.0, + width: 200.0, + height: 100.0, + }), + ShapeOption::Circle => Shape::Circle { + center: Point::new(frame.width() / 4.0, frame.height() / 2.0), + radius: self.radius, + }, + ShapeOption::Ellipse => Shape::Ellipse { + center: Point::new(frame.width() / 4.0, frame.height() / 2.0), + horizontal_radius: self.radius, + vertical_radius: self.vertical_radius, + rotation: 0.0, + }, + ShapeOption::Polyline => Shape::Polyline { + points: self.polyline_points.clone(), + }, + }; + + match self.mode { + ModeOption::Fill => { + mesh.fill(shape, self.color); + } + ModeOption::Stroke => { + mesh.stroke(shape, self.color, self.stroke_width); + } + } + + mesh.draw(&mut frame.as_target()); + } +} + +impl UserInterface for Example { + type Message = Message; + type Renderer = Renderer; + + fn react(&mut self, msg: Message) { + match msg { + Message::ShapeSelected(shape) => { + self.shape = shape; + } + Message::ModeSelected(mode) => { + self.mode = mode; + } + Message::StrokeWidthChanged(stroke_width) => { + self.stroke_width = stroke_width; + } + Message::RadiusChanged(radius) => { + self.radius = radius; + } + Message::VerticalRadiusChanged(radius) => { + self.vertical_radius = radius; + } + Message::ColorChanged(color) => { + self.color = color; + } + } + } + + fn layout(&mut self, window: &Window) -> Element { + let mut shape_and_mode = Column::new() + .max_width(500) + .spacing(20) + .push(shape_selector(self.shape)) + .push(mode_selector(self.mode)); + + match self.mode { + ModeOption::Fill => {} + ModeOption::Stroke => { + shape_and_mode = shape_and_mode.push(stroke_width_slider( + &mut self.stroke_width_slider, + self.stroke_width, + )); + } + } + + let mut controls = Column::new().max_width(500).spacing(20); + + match self.shape { + ShapeOption::Rectangle => {} + ShapeOption::Circle => { + controls = controls.push(radius_slider( + "Radius:", + &mut self.radius_slider, + self.radius, + Message::RadiusChanged, + )); + } + ShapeOption::Ellipse => { + controls = controls + .push(radius_slider( + "Horizontal radius:", + &mut self.radius_slider, + self.radius, + Message::RadiusChanged, + )) + .push(radius_slider( + "Vertical radius:", + &mut self.vertical_radius_slider, + self.vertical_radius, + Message::VerticalRadiusChanged, + )); + } + ShapeOption::Polyline => { + controls = controls.push( + Text::new("Click to draw!") + .size(40) + .horizontal_alignment(HorizontalAlignment::Center), + ) + } + } + + controls = + controls.push(color_sliders(&mut self.color_sliders, self.color)); + + Column::new() + .width(window.width() as u32) + .height(window.height() as u32) + .padding(20) + .align_items(Align::End) + .justify_content(Justify::SpaceBetween) + .push(shape_and_mode) + .push(controls) + .into() + } +} + +#[derive(Debug, Clone, Copy)] +enum Message { + ShapeSelected(ShapeOption), + ModeSelected(ModeOption), + StrokeWidthChanged(u16), + RadiusChanged(f32), + VerticalRadiusChanged(f32), + ColorChanged(Color), +} + +fn shape_selector(current: ShapeOption) -> Element<'static, Message> { + let options = [ + ShapeOption::Rectangle, + ShapeOption::Circle, + ShapeOption::Ellipse, + ShapeOption::Polyline, + ] + .iter() + .cloned() + .fold(Column::new().padding(10).spacing(10), |container, shape| { + container.push(Radio::new( + shape, + &format!("{:?}", shape), + Some(current), + Message::ShapeSelected, + )) + }); + + Column::new() + .spacing(10) + .push(Text::new("Shape:")) + .push(options) + .into() +} + +fn mode_selector(current: ModeOption) -> Element<'static, Message> { + let options = [ModeOption::Fill, ModeOption::Stroke].iter().cloned().fold( + Row::new().padding(10).spacing(10), + |container, mode| { + container.push(Radio::new( + mode, + &format!("{:?}", mode), + Some(current), + Message::ModeSelected, + )) + }, + ); + + Column::new() + .spacing(10) + .push(Text::new("Mode:")) + .push(options) + .into() +} + +fn radius_slider<'a>( + label: &str, + state: &'a mut slider::State, + radius: f32, + on_change: fn(f32) -> Message, +) -> Element<'a, Message> { + slider_with_label( + label, + state, + 50.0..=200.0, + radius, + &format!("{:.*} px", 2, radius), + on_change, + ) +} + +fn stroke_width_slider( + state: &mut slider::State, + stroke_width: u16, +) -> Element { + slider_with_label( + "Stroke width:", + state, + 1.0..=20.0, + stroke_width as f32, + &format!("{:.*} px", 2, stroke_width), + |width| Message::StrokeWidthChanged(width.round() as u16), + ) +} + +fn color_sliders( + [red, green, blue]: &mut [slider::State; 3], + color: Color, +) -> Element { + Column::new() + .spacing(10) + .push(Text::new("Color:")) + .push( + Row::new() + .spacing(10) + .push(Slider::new(red, 0.0..=1.0, color.r, move |r| { + Message::ColorChanged(Color { r, ..color }) + })) + .push(Slider::new(green, 0.0..=1.0, color.g, move |g| { + Message::ColorChanged(Color { g, ..color }) + })) + .push(Slider::new(blue, 0.0..=1.0, color.b, move |b| { + Message::ColorChanged(Color { b, ..color }) + })), + ) + .push( + Text::new(&format!( + "({:.*}, {:.*}, {:.*})", + 2, color.r, 2, color.g, 2, color.b + )) + .horizontal_alignment(HorizontalAlignment::Center), + ) + .into() +} + +fn slider_with_label<'a>( + label: &str, + state: &'a mut slider::State, + range: RangeInclusive, + value: f32, + format: &str, + on_change: fn(f32) -> Message, +) -> Element<'a, Message> { + Column::new() + .spacing(10) + .push(Text::new(label)) + .push( + Row::new() + .spacing(10) + .push(Slider::new(state, range, value, on_change)) + .push( + Text::new(format) + .width(150) + .height(50) + .horizontal_alignment(HorizontalAlignment::Center), + ), + ) + .into() +} diff --git a/src/graphics/backend_gfx/mod.rs b/src/graphics/backend_gfx/mod.rs index b7c6d58..8273df4 100644 --- a/src/graphics/backend_gfx/mod.rs +++ b/src/graphics/backend_gfx/mod.rs @@ -1,14 +1,16 @@ mod font; mod format; -mod pipeline; +mod quad; mod surface; pub mod texture; +mod triangle; mod types; pub use font::Font; -pub use pipeline::Instance; +pub use quad::Quad; pub use surface::{winit, Surface}; pub use texture::Texture; +pub use triangle::Vertex; pub use types::TargetView; use gfx::{self, Device}; @@ -16,7 +18,6 @@ use gfx_device_gl as gl; use crate::graphics::{Color, Transformation}; use crate::Result; -use pipeline::Pipeline; /// A link between your game and a graphics processor. /// @@ -35,7 +36,8 @@ pub struct Gpu { device: gl::Device, factory: gl::Factory, encoder: gfx::Encoder, - pipeline: Pipeline, + triangle_pipeline: triangle::Pipeline, + quad_pipeline: quad::Pipeline, } impl Gpu { @@ -49,15 +51,22 @@ impl Gpu { let mut encoder: gfx::Encoder = factory.create_command_buffer().into(); - let pipeline = - Pipeline::new(&mut factory, &mut encoder, surface.target()); + let triangle_pipeline = triangle::Pipeline::new( + &mut factory, + &mut encoder, + surface.target(), + ); + + let quad_pipeline = + quad::Pipeline::new(&mut factory, &mut encoder, surface.target()); Ok(( Gpu { device, factory, encoder, - pipeline, + triangle_pipeline, + quad_pipeline, }, surface, )) @@ -107,20 +116,37 @@ impl Gpu { Font::from_bytes(&mut self.factory, bytes) } + pub(super) fn draw_triangles( + &mut self, + vertices: &[Vertex], + indices: &[u16], + view: &TargetView, + transformation: &Transformation, + ) { + self.triangle_pipeline.draw( + &mut self.factory, + &mut self.encoder, + vertices, + indices, + transformation, + view, + ); + } + pub(super) fn draw_texture_quads( &mut self, texture: &Texture, - instances: &[Instance], + instances: &[Quad], view: &TargetView, transformation: &Transformation, ) { - self.pipeline.bind_texture(texture); + self.quad_pipeline.bind_texture(texture); - self.pipeline.draw_quads( + self.quad_pipeline.draw_textured( &mut self.encoder, instances, - &transformation, - &view, + transformation, + view, ); } diff --git a/src/graphics/backend_gfx/pipeline.rs b/src/graphics/backend_gfx/quad.rs similarity index 93% rename from src/graphics/backend_gfx/pipeline.rs rename to src/graphics/backend_gfx/quad.rs index cc83337..11abd41 100644 --- a/src/graphics/backend_gfx/pipeline.rs +++ b/src/graphics/backend_gfx/quad.rs @@ -4,7 +4,7 @@ use gfx_device_gl as gl; use super::format; use super::texture::Texture; -use crate::graphics::{Quad, Transformation}; +use crate::graphics::{self, Transformation}; const MAX_INSTANCES: u32 = 100_000; const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; @@ -29,7 +29,7 @@ gfx_defines! { position: [f32; 2] = "a_Pos", } - vertex Instance { + vertex Quad { src: [f32; 4] = "a_Src", translation: [f32; 2] = "a_Translation", scale: [f32; 2] = "a_Scale", @@ -44,7 +44,7 @@ gfx_defines! { vertices: gfx::VertexBuffer = (), texture: gfx::TextureSampler<[f32; 4]> = "t_Texture", globals: gfx::ConstantBuffer = "Globals", - instances: gfx::InstanceBuffer = (), + instances: gfx::InstanceBuffer = (), out: gfx::RawRenderTarget = ( "Target0", @@ -135,10 +135,10 @@ impl Pipeline { self.data.texture.0 = texture.view().clone(); } - pub fn draw_quads( + pub fn draw_textured( &mut self, encoder: &mut gfx::Encoder, - instances: &[Instance], + instances: &[Quad], transformation: &Transformation, view: &gfx::handle::RawRenderTargetView, ) { @@ -182,8 +182,8 @@ impl Shader { pub fn new(factory: &mut gl::Factory, init: pipe::Init<'_>) -> Shader { let set = factory .create_shader_set( - include_bytes!("shader/basic.vert"), - include_bytes!("shader/basic.frag"), + include_bytes!("shader/quad.vert"), + include_bytes!("shader/quad.frag"), ) .expect("Shader set creation"); @@ -208,13 +208,13 @@ impl Shader { } } -impl From for Instance { - fn from(quad: Quad) -> Instance { +impl From for Quad { + fn from(quad: graphics::Quad) -> Quad { let source = quad.source; let position = quad.position; let (width, height) = quad.size; - Instance { + Quad { src: [source.x, source.y, source.width, source.height], translation: [position.x, position.y], scale: [width, height], diff --git a/src/graphics/backend_gfx/shader/basic.frag b/src/graphics/backend_gfx/shader/quad.frag similarity index 100% rename from src/graphics/backend_gfx/shader/basic.frag rename to src/graphics/backend_gfx/shader/quad.frag diff --git a/src/graphics/backend_gfx/shader/basic.vert b/src/graphics/backend_gfx/shader/quad.vert similarity index 100% rename from src/graphics/backend_gfx/shader/basic.vert rename to src/graphics/backend_gfx/shader/quad.vert diff --git a/src/graphics/backend_gfx/shader/triangle.frag b/src/graphics/backend_gfx/shader/triangle.frag new file mode 100644 index 0000000..8329be0 --- /dev/null +++ b/src/graphics/backend_gfx/shader/triangle.frag @@ -0,0 +1,13 @@ +#version 150 core + +in vec4 v_Color; + +out vec4 Target0; + +layout (std140) uniform Globals { + mat4 u_MVP; +}; + +void main() { + Target0 = v_Color; +} diff --git a/src/graphics/backend_gfx/shader/triangle.vert b/src/graphics/backend_gfx/shader/triangle.vert new file mode 100644 index 0000000..9e93563 --- /dev/null +++ b/src/graphics/backend_gfx/shader/triangle.vert @@ -0,0 +1,23 @@ +#version 150 core + +in vec2 a_Pos; +in vec4 a_Color; + +layout (std140) uniform Globals { + mat4 u_MVP; +}; + +out vec4 v_Color; + +const mat4 INVERT_Y_AXIS = mat4( + vec4(1.0, 0.0, 0.0, 0.0), + vec4(0.0, -1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(0.0, 0.0, 0.0, 1.0) +); + +void main() { + v_Color = a_Color; + + gl_Position = INVERT_Y_AXIS * u_MVP * vec4(a_Pos, 0.0, 1.0); +} diff --git a/src/graphics/backend_gfx/triangle.rs b/src/graphics/backend_gfx/triangle.rs new file mode 100644 index 0000000..69f3546 --- /dev/null +++ b/src/graphics/backend_gfx/triangle.rs @@ -0,0 +1,203 @@ +use gfx::traits::FactoryExt; +use gfx::{self, *}; +use gfx_device_gl as gl; + +use super::format; +use crate::graphics::Transformation; + +gfx_defines! { + vertex Vertex { + position: [f32; 2] = "a_Pos", + color: [f32; 4] = "a_Color", + } + + constant Globals { + mvp: [[f32; 4]; 4] = "u_MVP", + } + + pipeline pipe { + vertices: gfx::VertexBuffer = (), + globals: gfx::ConstantBuffer = "Globals", + out: gfx::RawRenderTarget = + ( + "Target0", + format::COLOR, + gfx::state::ColorMask::all(), + Some(gfx::preset::blend::ALPHA) + ), + } +} + +pub struct Pipeline { + data: pipe::Data, + indices: gfx::handle::Buffer, + shader: Shader, + globals: Globals, +} + +impl Pipeline { + const INITIAL_BUFFER_SIZE: usize = 100_000; + + pub fn new( + factory: &mut gl::Factory, + encoder: &mut gfx::Encoder, + target: &gfx::handle::RawRenderTargetView, + ) -> Pipeline { + let vertices = factory + .create_buffer( + Self::INITIAL_BUFFER_SIZE, + gfx::buffer::Role::Vertex, + gfx::memory::Usage::Dynamic, + gfx::memory::Bind::SHADER_RESOURCE, + ) + .expect("Vertex buffer creation"); + + let indices = factory + .create_buffer( + Self::INITIAL_BUFFER_SIZE, + gfx::buffer::Role::Index, + gfx::memory::Usage::Dynamic, + gfx::memory::Bind::empty(), + ) + .expect("Index buffer creation"); + + let data = pipe::Data { + vertices, + globals: factory.create_constant_buffer(1), + out: target.clone(), + }; + + let init = pipe::Init { + out: ( + "Target0", + format::COLOR, + gfx::state::ColorMask::all(), + Some(gfx::preset::blend::ALPHA), + ), + ..pipe::new() + }; + + let shader = Shader::new(factory, init); + + let globals = Globals { + mvp: Transformation::identity().into(), + }; + + encoder + .update_buffer(&data.globals, &[globals], 0) + .expect("Globals initialization"); + + Pipeline { + data, + indices, + shader, + globals, + } + } + + pub fn draw( + &mut self, + factory: &mut gl::Factory, + encoder: &mut gfx::Encoder, + vertices: &[Vertex], + indices: &[u16], + transformation: &Transformation, + view: &gfx::handle::RawRenderTargetView, + ) { + let transformation_matrix: [[f32; 4]; 4] = + transformation.clone().into(); + + if self.globals.mvp != transformation_matrix { + self.globals.mvp = transformation_matrix; + + encoder + .update_buffer(&self.data.globals, &[self.globals], 0) + .expect("Globals upload"); + } + + self.data.out = view.clone(); + + if self.data.vertices.len() < vertices.len() + || self.indices.len() < indices.len() + { + let vertices = factory + .create_buffer( + Self::INITIAL_BUFFER_SIZE, + gfx::buffer::Role::Vertex, + gfx::memory::Usage::Dynamic, + gfx::memory::Bind::SHADER_RESOURCE, + ) + .expect("Vertex buffer creation"); + + let indices = factory + .create_buffer( + Self::INITIAL_BUFFER_SIZE, + gfx::buffer::Role::Index, + gfx::memory::Usage::Dynamic, + gfx::memory::Bind::empty(), + ) + .expect("Index buffer creation"); + + self.data.vertices = vertices; + self.indices = indices; + } + + encoder + .update_buffer(&self.data.vertices, &vertices, 0) + .expect("Vertex upload"); + + encoder + .update_buffer(&self.indices, &indices, 0) + .expect("Index upload"); + + let slice = gfx::Slice { + start: 0, + end: indices.len() as u32, + base_vertex: 0, + instances: None, + buffer: gfx::IndexBuffer::Index16(self.indices.clone()), + }; + + encoder.draw(&slice, &self.shader.state, &self.data); + } +} + +pub struct Shader { + state: gfx::pso::PipelineState, +} + +impl Shader { + pub fn new(factory: &mut gl::Factory, init: pipe::Init<'_>) -> Shader { + let set = factory + .create_shader_set( + include_bytes!("shader/triangle.vert"), + include_bytes!("shader/triangle.frag"), + ) + .expect("Shader set creation"); + + let rasterizer = gfx::state::Rasterizer { + front_face: gfx::state::FrontFace::CounterClockwise, + cull_face: gfx::state::CullFace::Nothing, + method: gfx::state::RasterMethod::Fill, + offset: None, + samples: None, + }; + + let state = factory + .create_pipeline_state( + &set, + Primitive::TriangleList, + rasterizer, + init, + ) + .expect("Pipeline state creation"); + + Shader { state } + } +} + +impl Vertex { + pub fn new(position: [f32; 2], color: [f32; 4]) -> Vertex { + Vertex { position, color } + } +} diff --git a/src/graphics/backend_wgpu/mod.rs b/src/graphics/backend_wgpu/mod.rs index 245d2ac..244c825 100644 --- a/src/graphics/backend_wgpu/mod.rs +++ b/src/graphics/backend_wgpu/mod.rs @@ -1,24 +1,26 @@ mod font; -mod pipeline; +mod quad; mod surface; pub mod texture; +mod triangle; mod types; pub use font::Font; -pub use pipeline::Instance; +pub use quad::Quad; pub use surface::{winit, Surface}; pub use texture::Texture; +pub use triangle::Vertex; pub use types::TargetView; use crate::graphics::{Color, Transformation}; use crate::{Error, Result}; -use pipeline::Pipeline; #[allow(missing_debug_implementations)] #[allow(missing_docs)] pub struct Gpu { device: wgpu::Device, - pipeline: Pipeline, + quad_pipeline: quad::Pipeline, + triangle_pipeline: triangle::Pipeline, encoder: wgpu::CommandEncoder, } @@ -39,7 +41,8 @@ impl Gpu { }, }); - let pipeline = Pipeline::new(&mut device); + let quad_pipeline = quad::Pipeline::new(&mut device); + let triangle_pipeline = triangle::Pipeline::new(&mut device); let window = builder .build(events_loop) @@ -54,7 +57,8 @@ impl Gpu { Ok(( Gpu { device, - pipeline, + quad_pipeline, + triangle_pipeline, encoder, }, surface, @@ -79,14 +83,14 @@ impl Gpu { &mut self, image: &image::DynamicImage, ) -> Texture { - Texture::new(&mut self.device, &self.pipeline, image) + Texture::new(&mut self.device, &self.quad_pipeline, image) } pub(super) fn upload_texture_array( &mut self, layers: &[image::DynamicImage], ) -> Texture { - Texture::new_array(&mut self.device, &self.pipeline, layers) + Texture::new_array(&mut self.device, &self.quad_pipeline, layers) } pub(super) fn create_drawable_texture( @@ -94,27 +98,49 @@ impl Gpu { width: u16, height: u16, ) -> texture::Drawable { - texture::Drawable::new(&mut self.device, &self.pipeline, width, height) + texture::Drawable::new( + &mut self.device, + &self.quad_pipeline, + width, + height, + ) } pub(super) fn upload_font(&mut self, bytes: &'static [u8]) -> Font { Font::from_bytes(&mut self.device, bytes) } + pub(super) fn draw_triangles( + &mut self, + vertices: &[Vertex], + indices: &[u16], + view: &TargetView, + transformation: &Transformation, + ) { + self.triangle_pipeline.draw( + &mut self.device, + &mut self.encoder, + vertices, + indices, + transformation, + view, + ); + } + pub(super) fn draw_texture_quads( &mut self, texture: &Texture, - instances: &[Instance], + instances: &[Quad], view: &TargetView, transformation: &Transformation, ) { - self.pipeline.draw_texture_quads( + self.quad_pipeline.draw_textured( &mut self.device, &mut self.encoder, texture.binding(), instances, - &transformation, - &view, + transformation, + view, ); } diff --git a/src/graphics/backend_wgpu/pipeline.rs b/src/graphics/backend_wgpu/quad.rs similarity index 93% rename from src/graphics/backend_wgpu/pipeline.rs rename to src/graphics/backend_wgpu/quad.rs index 80ef9c1..0c1bef6 100644 --- a/src/graphics/backend_wgpu/pipeline.rs +++ b/src/graphics/backend_wgpu/quad.rs @@ -1,6 +1,6 @@ use std::mem; -use crate::graphics::{Quad, Transformation}; +use crate::graphics::{self, Transformation}; pub struct Pipeline { pipeline: wgpu::RenderPipeline, @@ -86,10 +86,10 @@ impl Pipeline { bind_group_layouts: &[&constant_layout, &texture_layout], }); - let vs_module = device - .create_shader_module(include_bytes!("shader/basic.vert.spv")); - let fs_module = device - .create_shader_module(include_bytes!("shader/basic.frag.spv")); + let vs_module = + device.create_shader_module(include_bytes!("shader/quad.vert.spv")); + let fs_module = + device.create_shader_module(include_bytes!("shader/quad.frag.spv")); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { @@ -137,7 +137,7 @@ impl Pipeline { }], }, wgpu::VertexBufferDescriptor { - stride: mem::size_of::() as u32, + stride: mem::size_of::() as u32, step_mode: wgpu::InputStepMode::Instance, attributes: &[ wgpu::VertexAttributeDescriptor { @@ -181,7 +181,7 @@ impl Pipeline { .fill_from_slice(&QUAD_INDICES); let instances = device.create_buffer(&wgpu::BufferDescriptor { - size: mem::size_of::() as u32 * Instance::MAX as u32, + size: mem::size_of::() as u32 * Quad::MAX as u32, usage: wgpu::BufferUsageFlags::VERTEX | wgpu::BufferUsageFlags::TRANSFER_DST, }); @@ -213,12 +213,12 @@ impl Pipeline { TextureBinding(binding) } - pub fn draw_texture_quads( + pub fn draw_textured( &mut self, device: &mut wgpu::Device, encoder: &mut wgpu::CommandEncoder, texture: &TextureBinding, - instances: &[Instance], + instances: &[Quad], transformation: &Transformation, target: &wgpu::TextureView, ) { @@ -240,7 +240,7 @@ impl Pipeline { let total = instances.len(); while i < total { - let end = (i + Instance::MAX).min(total); + let end = (i + Quad::MAX).min(total); let amount = end - i; let instance_buffer = device @@ -255,7 +255,7 @@ impl Pipeline { 0, &self.instances, 0, - (mem::size_of::() * amount) as u32, + (mem::size_of::() * amount) as u32, ); { let mut render_pass = @@ -292,7 +292,7 @@ impl Pipeline { ); } - i += Instance::MAX; + i += Quad::MAX; } } } @@ -320,24 +320,24 @@ const QUAD_VERTS: [Vertex; 4] = [ ]; #[derive(Debug, Clone, Copy)] -pub struct Instance { - pub source: [f32; 4], - pub scale: [f32; 2], - pub translation: [f32; 2], +pub struct Quad { + source: [f32; 4], + scale: [f32; 2], + translation: [f32; 2], pub layer: u32, } -impl Instance { +impl Quad { const MAX: usize = 100_000; } -impl From for Instance { - fn from(quad: Quad) -> Instance { +impl From for Quad { + fn from(quad: graphics::Quad) -> Quad { let source = quad.source; let position = quad.position; let (width, height) = quad.size; - Instance { + Quad { source: [source.x, source.y, source.width, source.height], translation: [position.x, position.y], scale: [width, height], diff --git a/src/graphics/backend_wgpu/shader/basic.frag b/src/graphics/backend_wgpu/shader/quad.frag similarity index 100% rename from src/graphics/backend_wgpu/shader/basic.frag rename to src/graphics/backend_wgpu/shader/quad.frag diff --git a/src/graphics/backend_wgpu/shader/basic.frag.spv b/src/graphics/backend_wgpu/shader/quad.frag.spv similarity index 100% rename from src/graphics/backend_wgpu/shader/basic.frag.spv rename to src/graphics/backend_wgpu/shader/quad.frag.spv diff --git a/src/graphics/backend_wgpu/shader/basic.vert b/src/graphics/backend_wgpu/shader/quad.vert similarity index 100% rename from src/graphics/backend_wgpu/shader/basic.vert rename to src/graphics/backend_wgpu/shader/quad.vert diff --git a/src/graphics/backend_wgpu/shader/basic.vert.spv b/src/graphics/backend_wgpu/shader/quad.vert.spv similarity index 100% rename from src/graphics/backend_wgpu/shader/basic.vert.spv rename to src/graphics/backend_wgpu/shader/quad.vert.spv diff --git a/src/graphics/backend_wgpu/shader/triangle.frag b/src/graphics/backend_wgpu/shader/triangle.frag new file mode 100644 index 0000000..50ec9a9 --- /dev/null +++ b/src/graphics/backend_wgpu/shader/triangle.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec4 v_Color; + +layout(location = 0) out vec4 o_Target; + +void main() { + o_Target = v_Color; +} diff --git a/src/graphics/backend_wgpu/shader/triangle.frag.spv b/src/graphics/backend_wgpu/shader/triangle.frag.spv new file mode 100644 index 0000000..4bedb41 Binary files /dev/null and b/src/graphics/backend_wgpu/shader/triangle.frag.spv differ diff --git a/src/graphics/backend_wgpu/shader/triangle.vert b/src/graphics/backend_wgpu/shader/triangle.vert new file mode 100644 index 0000000..888d5d7 --- /dev/null +++ b/src/graphics/backend_wgpu/shader/triangle.vert @@ -0,0 +1,16 @@ +#version 450 + +layout(location = 0) in vec2 a_Pos; +layout(location = 1) in vec4 a_Color; + +layout (set = 0, binding = 0) uniform Globals { + mat4 u_Transform; +}; + +layout(location = 0) flat out vec4 v_Color; + +void main() { + v_Color = a_Color; + + gl_Position = u_Transform * vec4(a_Pos, 0.0, 1.0); +} diff --git a/src/graphics/backend_wgpu/shader/triangle.vert.spv b/src/graphics/backend_wgpu/shader/triangle.vert.spv new file mode 100644 index 0000000..932730b Binary files /dev/null and b/src/graphics/backend_wgpu/shader/triangle.vert.spv differ diff --git a/src/graphics/backend_wgpu/texture.rs b/src/graphics/backend_wgpu/texture.rs index dfffe35..e2ee633 100644 --- a/src/graphics/backend_wgpu/texture.rs +++ b/src/graphics/backend_wgpu/texture.rs @@ -2,14 +2,14 @@ use std::fmt; use std::rc::Rc; use super::types::TargetView; -use crate::graphics::gpu::pipeline::{self, Pipeline}; +use crate::graphics::gpu::quad::{self, Pipeline}; use crate::graphics::Transformation; #[derive(Clone)] pub struct Texture { texture: Rc, view: TargetView, - binding: Rc, + binding: Rc, width: u16, height: u16, layers: u16, @@ -93,7 +93,7 @@ impl Texture { &self.view } - pub(super) fn binding(&self) -> &pipeline::TextureBinding { + pub(super) fn binding(&self) -> &quad::TextureBinding { &self.binding } @@ -161,7 +161,7 @@ fn create_texture_array( height: u16, layers: Option<&[&[u8]]>, usage: wgpu::TextureUsageFlags, -) -> (wgpu::Texture, wgpu::TextureView, pipeline::TextureBinding) { +) -> (wgpu::Texture, wgpu::TextureView, quad::TextureBinding) { let extent = wgpu::Extent3d { width: width as u32, height: height as u32, diff --git a/src/graphics/backend_wgpu/triangle.rs b/src/graphics/backend_wgpu/triangle.rs new file mode 100644 index 0000000..af92b44 --- /dev/null +++ b/src/graphics/backend_wgpu/triangle.rs @@ -0,0 +1,250 @@ +use std::mem; + +use crate::graphics::Transformation; + +pub struct Pipeline { + pipeline: wgpu::RenderPipeline, + transform: wgpu::Buffer, + constants: wgpu::BindGroup, + vertices: wgpu::Buffer, + indices: wgpu::Buffer, + buffer_size: u32, +} + +impl Pipeline { + const INITIAL_BUFFER_SIZE: u32 = 100_000; + + pub fn new(device: &mut wgpu::Device) -> Pipeline { + let transform_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStageFlags::VERTEX, + ty: wgpu::BindingType::UniformBuffer, + }], + }); + + let matrix: [f32; 16] = Transformation::identity().into(); + + let transform_buffer = device + .create_buffer_mapped( + 16, + wgpu::BufferUsageFlags::UNIFORM + | wgpu::BufferUsageFlags::TRANSFER_DST, + ) + .fill_from_slice(&matrix[..]); + + let constant_bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &transform_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &transform_buffer, + range: 0..64, + }, + }], + }); + + let layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&transform_layout], + }); + + let vs_module = device + .create_shader_module(include_bytes!("shader/triangle.vert.spv")); + let fs_module = device + .create_shader_module(include_bytes!("shader/triangle.frag.spv")); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &layout, + vertex_stage: wgpu::PipelineStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: wgpu::PipelineStageDescriptor { + module: &fs_module, + entry_point: "main", + }, + rasterization_state: wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Ccw, + cull_mode: wgpu::CullMode::Back, + depth_bias: 0, + depth_bias_slope_scale: 0.0, + depth_bias_clamp: 0.0, + }, + primitive_topology: wgpu::PrimitiveTopology::TriangleList, + color_states: &[wgpu::ColorStateDescriptor { + format: wgpu::TextureFormat::Bgra8UnormSrgb, + color: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + write_mask: wgpu::ColorWriteFlags::ALL, + }], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[wgpu::VertexBufferDescriptor { + stride: mem::size_of::() as u32, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[ + wgpu::VertexAttributeDescriptor { + attribute_index: 0, + format: wgpu::VertexFormat::Float2, + offset: 0, + }, + wgpu::VertexAttributeDescriptor { + attribute_index: 1, + format: wgpu::VertexFormat::Float4, + offset: 4 * 2, + }, + ], + }], + sample_count: 1, + }); + + let vertices = device.create_buffer(&wgpu::BufferDescriptor { + size: Self::INITIAL_BUFFER_SIZE, + usage: wgpu::BufferUsageFlags::VERTEX, + }); + + let indices = device.create_buffer(&wgpu::BufferDescriptor { + size: Self::INITIAL_BUFFER_SIZE, + usage: wgpu::BufferUsageFlags::INDEX, + }); + + Pipeline { + pipeline, + transform: transform_buffer, + constants: constant_bind_group, + vertices, + indices, + buffer_size: Self::INITIAL_BUFFER_SIZE, + } + } + + pub fn draw( + &mut self, + device: &mut wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + vertices: &[Vertex], + indices: &[u16], + transformation: &Transformation, + target: &wgpu::TextureView, + ) { + if vertices.is_empty() || indices.is_empty() { + return; + } + + let matrix: [f32; 16] = transformation.clone().into(); + + let transform_buffer = device + .create_buffer_mapped(16, wgpu::BufferUsageFlags::TRANSFER_SRC) + .fill_from_slice(&matrix[..]); + + encoder.copy_buffer_to_buffer( + &transform_buffer, + 0, + &self.transform, + 0, + 16 * 4, + ); + + if self.buffer_size < vertices.len() as u32 + || self.buffer_size < indices.len() as u32 + { + let new_size = vertices.len().max(indices.len()) as u32; + + self.vertices = device.create_buffer(&wgpu::BufferDescriptor { + size: new_size, + usage: wgpu::BufferUsageFlags::VERTEX, + }); + + self.indices = device.create_buffer(&wgpu::BufferDescriptor { + size: new_size, + usage: wgpu::BufferUsageFlags::INDEX, + }); + + self.buffer_size = new_size; + } + + let vertex_buffer = device + .create_buffer_mapped( + vertices.len(), + wgpu::BufferUsageFlags::TRANSFER_SRC, + ) + .fill_from_slice(vertices); + + let index_buffer = device + .create_buffer_mapped( + indices.len(), + wgpu::BufferUsageFlags::TRANSFER_SRC, + ) + .fill_from_slice(indices); + + encoder.copy_buffer_to_buffer( + &vertex_buffer, + 0, + &self.vertices, + 0, + (mem::size_of::() * vertices.len()) as u32, + ); + + encoder.copy_buffer_to_buffer( + &index_buffer, + 0, + &self.indices, + 0, + (mem::size_of::() * indices.len()) as u32, + ); + + { + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[ + wgpu::RenderPassColorAttachmentDescriptor { + attachment: target, + load_op: wgpu::LoadOp::Load, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, + }, + }, + ], + depth_stencil_attachment: None, + }); + + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, &self.constants); + render_pass.set_index_buffer(&self.indices, 0); + render_pass.set_vertex_buffers(&[(&self.vertices, 0)]); + + render_pass.draw_indexed(0..indices.len() as u32, 0, 0..1); + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Vertex { + _position: [f32; 2], + _color: [f32; 4], +} + +impl Vertex { + pub fn new(position: [f32; 2], color: [f32; 4]) -> Vertex { + Vertex { + _position: position, + _color: color, + } + } +} diff --git a/src/graphics/batch.rs b/src/graphics/batch.rs index 8615a73..97eea0c 100644 --- a/src/graphics/batch.rs +++ b/src/graphics/batch.rs @@ -9,7 +9,7 @@ use crate::graphics::{Image, IntoQuad, Point, Target, Transformation, Vector}; /// [`Image`]: struct.Image.html pub struct Batch { image: Image, - instances: Vec, + instances: Vec, x_unit: f32, y_unit: f32, } @@ -37,7 +37,7 @@ impl Batch { #[inline] pub fn add(&mut self, quad: Q) { let instance = - gpu::Instance::from(quad.into_quad(self.x_unit, self.y_unit)); + gpu::Quad::from(quad.into_quad(self.x_unit, self.y_unit)); self.instances.push(instance); } @@ -80,9 +80,7 @@ impl Extend for Batch { let y_unit = self.y_unit; self.instances.extend( - iter.map(|quad| { - gpu::Instance::from(quad.into_quad(x_unit, y_unit)) - }), + iter.map(|quad| gpu::Quad::from(quad.into_quad(x_unit, y_unit))), ); } } @@ -104,9 +102,8 @@ impl ParallelExtend for Batch { let y_unit = self.y_unit; self.instances.par_extend( - par_iter.map(|quad| { - gpu::Instance::from(quad.into_quad(x_unit, y_unit)) - }), + par_iter + .map(|quad| gpu::Quad::from(quad.into_quad(x_unit, y_unit))), ); } } diff --git a/src/graphics/canvas.rs b/src/graphics/canvas.rs index 0c608e1..7ca4487 100644 --- a/src/graphics/canvas.rs +++ b/src/graphics/canvas.rs @@ -68,7 +68,7 @@ impl Canvas { pub fn draw(&self, quad: Q, target: &mut Target<'_>) { target.draw_texture_quads( &self.drawable.texture(), - &[gpu::Instance::from(quad.into_quad( + &[gpu::Quad::from(quad.into_quad( 1.0 / self.width() as f32, 1.0 / self.height() as f32, ))], diff --git a/src/graphics/image.rs b/src/graphics/image.rs index 86d19ca..3f23502 100644 --- a/src/graphics/image.rs +++ b/src/graphics/image.rs @@ -103,7 +103,7 @@ impl Image { pub fn draw(&self, quad: Q, target: &mut Target<'_>) { target.draw_texture_quads( &self.texture, - &[gpu::Instance::from(quad.into_quad( + &[gpu::Quad::from(quad.into_quad( 1.0 / self.width() as f32, 1.0 / self.height() as f32, ))], diff --git a/src/graphics/mesh.rs b/src/graphics/mesh.rs new file mode 100644 index 0000000..5512ddd --- /dev/null +++ b/src/graphics/mesh.rs @@ -0,0 +1,175 @@ +use crate::graphics::{gpu, Color, Rectangle, Shape, Target}; + +use lyon_tessellation as lyon; + +/// A set of shapes that can be drawn. +#[derive(Debug, Clone)] +pub struct Mesh { + buffers: lyon::VertexBuffers, +} + +impl Mesh { + /// Creates a new empty [`Mesh`]. + /// + /// [`Mesh`]: struct.Mesh.html + pub fn new() -> Mesh { + Mesh { + buffers: lyon::VertexBuffers::new(), + } + } + + /// Adds a filled [`Shape`] to the [`Mesh`]. + /// + /// [`Shape`]: enum.Shape.html + /// [`Mesh`]: struct.Mesh.html + #[inline] + pub fn fill(&mut self, shape: Shape, color: Color) { + let mut builder = lyon::BuffersBuilder::new( + &mut self.buffers, + WithColor(color.into_linear()), + ); + + match shape { + Shape::Rectangle(Rectangle { + x, + y, + width, + height, + }) => { + let _ = lyon::basic_shapes::fill_rectangle( + &lyon::math::rect(x, y, width, height), + &Self::fill_options(), + &mut builder, + ) + .expect("Fill rectangle"); + } + Shape::Circle { center, radius } => { + let _ = lyon::basic_shapes::fill_circle( + lyon::math::point(center.x, center.y), + radius, + &Self::fill_options(), + &mut builder, + ) + .expect("Fill circle"); + } + Shape::Ellipse { + center, + horizontal_radius, + vertical_radius, + rotation, + } => { + let _ = lyon::basic_shapes::fill_ellipse( + lyon::math::point(center.x, center.y), + lyon::math::vector(horizontal_radius, vertical_radius), + lyon::math::Angle::radians(rotation), + &Self::fill_options(), + &mut builder, + ) + .expect("Fill ellipse"); + } + Shape::Polyline { points } => { + let _ = lyon::basic_shapes::fill_polyline( + points + .iter() + .map(|point| lyon::math::point(point.x, point.y)), + &mut lyon::FillTessellator::new(), + &Self::fill_options(), + &mut builder, + ) + .expect("Fill polyline"); + } + } + } + + /// Adds the stroke of a [`Shape`] to the [`Mesh`]. + /// + /// [`Shape`]: enum.Shape.html + /// [`Mesh`]: struct.Mesh.html + #[inline] + pub fn stroke(&mut self, shape: Shape, color: Color, width: u16) { + let mut builder = lyon::BuffersBuilder::new( + &mut self.buffers, + WithColor(color.into_linear()), + ); + + match shape { + Shape::Rectangle(Rectangle { + x, + y, + width: rect_width, + height, + }) => { + let _ = lyon::basic_shapes::stroke_rectangle( + &lyon::math::rect(x, y, rect_width, height), + &Self::stroke_options(width), + &mut builder, + ) + .expect("Stroke rectangle"); + } + Shape::Circle { center, radius } => { + let _ = lyon::basic_shapes::stroke_circle( + lyon::math::point(center.x, center.y), + radius, + &Self::stroke_options(width), + &mut builder, + ) + .expect("Stroke circle"); + } + Shape::Ellipse { + center, + horizontal_radius, + vertical_radius, + rotation, + } => { + let _ = lyon::basic_shapes::stroke_ellipse( + lyon::math::point(center.x, center.y), + lyon::math::vector(horizontal_radius, vertical_radius), + lyon::math::Angle::radians(rotation), + &Self::stroke_options(width), + &mut builder, + ) + .expect("Stroke ellipse"); + } + Shape::Polyline { points } => { + let _ = lyon::basic_shapes::stroke_polyline( + points + .iter() + .map(|point| lyon::math::point(point.x, point.y)), + false, + &Self::stroke_options(width), + &mut builder, + ) + .expect("Fill polyline"); + } + } + } + + /// Draws the [`Mesh`] . + /// + /// [`Mesh`]: struct.Mesh.html + pub fn draw(&self, target: &mut Target<'_>) { + target.draw_triangles(&self.buffers.vertices, &self.buffers.indices); + } + + fn fill_options() -> lyon::FillOptions { + lyon::FillOptions::DEFAULT.with_normals(false) + } + + fn stroke_options(width: u16) -> lyon::StrokeOptions { + lyon::StrokeOptions::DEFAULT.with_line_width(width as f32) + } +} + +struct WithColor([f32; 4]); + +impl lyon::VertexConstructor for WithColor { + fn new_vertex(&mut self, vertex: lyon::FillVertex) -> gpu::Vertex { + gpu::Vertex::new([vertex.position.x, vertex.position.y], self.0) + } +} + +impl lyon::VertexConstructor for WithColor { + fn new_vertex(&mut self, vertex: lyon::StrokeVertex) -> gpu::Vertex { + gpu::Vertex::new([vertex.position.x, vertex.position.y], self.0) + } +} diff --git a/src/graphics/mod.rs b/src/graphics/mod.rs index 9226b74..f9b2e59 100644 --- a/src/graphics/mod.rs +++ b/src/graphics/mod.rs @@ -110,9 +110,11 @@ mod canvas; mod color; mod font; mod image; +mod mesh; mod point; mod quad; mod rectangle; +mod shape; mod sprite; mod target; mod text; @@ -128,9 +130,11 @@ pub use canvas::Canvas; pub use color::Color; pub use font::Font; pub use gpu::Gpu; +pub use mesh::Mesh; pub use point::Point; pub use quad::{IntoQuad, Quad}; pub use rectangle::Rectangle; +pub use shape::Shape; pub use sprite::Sprite; pub use target::Target; pub use text::{HorizontalAlignment, Text, VerticalAlignment}; diff --git a/src/graphics/shape.rs b/src/graphics/shape.rs new file mode 100644 index 0000000..80c2607 --- /dev/null +++ b/src/graphics/shape.rs @@ -0,0 +1,38 @@ +use crate::graphics::{Point, Rectangle}; + +/// A geometric figure. +#[derive(Debug, Clone, PartialEq)] +pub enum Shape { + /// A rectangle. + Rectangle(Rectangle), + + /// A circle. + Circle { + /// The center of the circle. + center: Point, + + /// The radius of the circle. + radius: f32, + }, + + /// An ellipse. + Ellipse { + /// The center of the ellipse + center: Point, + + /// The horizontal radius of the ellipse. + horizontal_radius: f32, + + /// The vertical radius of the ellipse. + vertical_radius: f32, + + /// The rotation of the ellipse in radians. + rotation: f32, + }, + + /// A polyline. + Polyline { + /// The points of the polyline. + points: Vec, + }, +} diff --git a/src/graphics/target.rs b/src/graphics/target.rs index 422c1ab..2e65317 100644 --- a/src/graphics/target.rs +++ b/src/graphics/target.rs @@ -1,4 +1,4 @@ -use crate::graphics::gpu::{Font, Gpu, Instance, TargetView, Texture}; +use crate::graphics::gpu::{self, Font, Gpu, TargetView, Texture, Vertex}; use crate::graphics::{Color, Transformation}; /// A rendering target. @@ -92,10 +92,23 @@ impl<'a> Target<'a> { self.gpu.clear(&self.view, color); } + pub(super) fn draw_triangles( + &mut self, + vertices: &[Vertex], + indices: &[u16], + ) { + self.gpu.draw_triangles( + vertices, + indices, + &self.view, + &self.transformation, + ); + } + pub(super) fn draw_texture_quads( &mut self, texture: &Texture, - instances: &[Instance], + instances: &[gpu::Quad], ) { self.gpu.draw_texture_quads( texture, diff --git a/src/graphics/texture_array/batch.rs b/src/graphics/texture_array/batch.rs index 09ff310..14e8642 100644 --- a/src/graphics/texture_array/batch.rs +++ b/src/graphics/texture_array/batch.rs @@ -1,6 +1,5 @@ use super::{Index, TextureArray}; -use crate::graphics::gpu::Instance; -use crate::graphics::{IntoQuad, Point, Target, Transformation, Vector}; +use crate::graphics::{gpu, IntoQuad, Point, Target, Transformation, Vector}; /// A collection of quads that can be drawn with a [`TextureArray`] all at once. /// @@ -8,7 +7,7 @@ use crate::graphics::{IntoQuad, Point, Target, Transformation, Vector}; #[derive(Debug)] pub struct Batch { texture_array: TextureArray, - instances: Vec, + instances: Vec, } impl Batch { @@ -36,7 +35,7 @@ impl Batch { quad.source.x += index.offset.x; quad.source.y += index.offset.y; - let mut instance = Instance::from(quad); + let mut instance = gpu::Quad::from(quad); instance.layer = index.layer.into();