diff --git a/Cargo.toml b/Cargo.toml index e705200..5a003df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,22 +22,15 @@ name = "hello_gl" [[example]] name = "hello_glium" -[[example]] -name = "hello_wgpu" - [[example]] name = "demo_glium" -[[example]] -name = "demo_wgpu" - [[example]] name = "demo_gl" [features] default = ["image"] glium_backend = ["glium"] -wgpu_backend = ["wgpu", "futures", "bytemuck" ] gl_backend = ["gl", "glutin", "memoffset"] [dependencies] @@ -53,7 +46,6 @@ pulldown-cmark = { version = "0.9", default-features = false } image = { version = "0.24", optional = true, default-features = false, features = [ "png", "jpeg" ] } glium = { version = "0.32", optional = true } -wgpu = { version = "0.15", optional = true, features = [ "spirv" ] } futures = { version = "0.3", optional = true } bytemuck = { version = "1", optional = true } gl = { version = "0.14", optional = true} diff --git a/README.md b/README.md index 592ad2e..e31d6b7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Thyme is a Graphical User Interface (GUI) library written in pure, safe, Rust. A composite image showcasing three different themes: ![Screenshot](screenshot.png) -Thyme produces a set of Draw Lists which are sent to a swappable graphics backend - currently [Glium](https://github.com/glium/glium) and [wgpu](https://github.com/gfx-rs/wgpu-rs) are supported. The I/O backend is also swappable - although currently only [winit](https://github.com/rust-windowing/winit) is supported. Fonts are rendered to a texture on the GPU using [rusttype](https://github.com/redox-os/rusttype). +Thyme produces a set of Draw Lists which are sent to a swappable graphics backend - currently [Glium](https://github.com/glium/glium) and [Raw GL](https://github.com/brendanzab/gl-rs/) are supported. We have previously supported [wgpu](https://github.com/gfx-rs/wgpu) but the rate of change there has been too great for the author to keep up and support is not current. The I/O backend is also swappable - although currently only [winit](https://github.com/rust-windowing/winit) is supported. Fonts are rendered to a texture on the GPU using [rusttype](https://github.com/redox-os/rusttype). Performance is acceptable or better for most use cases, with the complete cycle of generating the widget tree, creating the draw data, and rendering taking less than 1 ms for quite complex UIs. @@ -22,14 +22,13 @@ The demo contains an example role playing game (RPG) character generator program git clone https://github.com/Grokmoo/thyme.git cd thyme cargo run --example demo_glium --features glium_backend # Run demo using glium -cargo run --example demo_wgpu --features wgpu_backend # Run demo using wgpu cargo run --example demo_gl --features gl_backend # Run demo using OpenGL ``` -Run the hello_world example with either Glium or wgpu: +Run the hello_world example with either Glium or GL: ```bash cargo run --example hello_glium --features glium_backend -cargo run --example hello_wgpu --features wgpu_backend +cargo run --example hello_gl --features gl_backend ``` ### Starting your own project @@ -38,10 +37,10 @@ Add the following to your Cargo.toml file: ```toml [dependencies] -thyme = { version = "0.7", features = ["wgpu_backend"] } +thyme = { version = "0.7", features = ["glium_backend"] } ``` -See [hello_wgpu](examples/hello_wgpu.rs) or [hello_glium](examples/hello_glium.rs) for the bare minimum to get started with your preferred renderer. As a starting point, you can copy the [data](examples/data) folder into your own project and import the resources there, as in the example. +See [hello_glium](examples/hello_glium.rs) for the bare minimum to get started with your preferred renderer. As a starting point, you can copy the [data](examples/data) folder into your own project and import the resources there, as in the example. ## [Documentation](https://docs.rs/thyme) @@ -53,7 +52,8 @@ At its core, Thyme is an immediate mode GUI library similar to other libraries s unlike many of those libraries Thyme is focused on extreme customizability and flexibility needed for production applications, especially games. With Thyme, you can customize exactly how you want your UI to look and operate. Thyme also focuses a great deal on being performant, while still -retaining the benefits of a true immediate mode GUI. +retaining the benefits of a true immediate mode GUI. Thyme also implements a number of tricks granting more layout flexibility than traditional +immediate mode libraries, although there are still some limitations. This flexibility comes at the cost of needing to specify theme, font, and image files. But, Thyme comes with some such files as examples to help you get started. Separating assets out in this manner can also significantly improve your workflow, especially with Thyme's built in support for live diff --git a/examples/demo_wgpu.rs b/examples/demo_wgpu.rs deleted file mode 100644 index 775ecc3..0000000 --- a/examples/demo_wgpu.rs +++ /dev/null @@ -1,150 +0,0 @@ -use std::sync::Arc; - -use winit::{event::{Event, WindowEvent}, event_loop::{EventLoop, ControlFlow}}; -use thyme::bench; - -mod demo; - -/// A basic RPG character sheet, using the wgpu backend. -/// This file contains the application setup code and wgpu specifics. -/// the `demo.rs` file contains the Thyme UI code and logic. -/// A simple party creator and character sheet for an RPG. -fn main() -> Result<(), Box> { - use winit::window::WindowBuilder; - - // initialize our very basic logger so error messages go to stdout - thyme::log::init(log::Level::Warn).unwrap(); - - let window_size = [1280.0, 720.0]; - let events_loop = EventLoop::new(); - - // create winit window - let window = WindowBuilder::new() - .with_title("Thyme WGPU Demo") - .with_inner_size(winit::dpi::LogicalSize::new(window_size[0], window_size[1])) - .build(&events_loop)?; - - // setup WGPU - let instance_desc = wgpu::InstanceDescriptor { - backends: wgpu::Backends::PRIMARY, - dx12_shader_compiler: wgpu::Dx12Compiler::Fxc, - }; - let instance = wgpu::Instance::new(instance_desc); - let surface = unsafe { instance.create_surface(&window).map_err(thyme::Error::WgpuSurface)? }; - let (_adapter, device, queue) = futures::executor::block_on(setup_wgpu(&instance, &surface)); - let surface_config = get_surface_config(window_size[0] as u32, window_size[1] as u32); - surface.configure(&device, &surface_config); - - // create thyme backend - let mut renderer = thyme::WgpuRenderer::new(Arc::clone(&device), Arc::clone(&queue)); - let mut io = thyme::WinitIo::new(&events_loop, window_size.into())?; - let mut context_builder = thyme::ContextBuilder::with_defaults(); - - demo::register_assets(&mut context_builder); - - let mut context = context_builder.build(&mut renderer, &mut io)?; - - let mut party = demo::Party::default(); - - let mut last_frame = std::time::Instant::now(); - let frame_time = std::time::Duration::from_millis(16); - - // run main loop - events_loop.run(move |event, _, control_flow| match event { - Event::MainEventsCleared => { - if std::time::Instant::now() > last_frame + frame_time { - window.request_redraw(); - } - *control_flow = ControlFlow::WaitUntil(last_frame + frame_time); - }, - Event::RedrawRequested(_) => { - last_frame = std::time::Instant::now(); - - party.check_context_changes(&mut context, &mut renderer); - - let frame = surface.get_current_texture().unwrap(); - let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default()); - let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - - bench::run("thyme", || { - window.set_cursor_visible(!party.theme_has_mouse_cursor()); - - let mut ui = context.create_frame(); - - bench::run("frame", || { - demo::build_ui(&mut ui, &mut party); - }); - - bench::run("draw", || { - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.5, g: 0.5, b: 0.5, a: 1.0 }), - store: true, - }, - })], - depth_stencil_attachment: None, - }); - - renderer.draw_frame(ui, &mut render_pass); - } - - queue.submit(Some(encoder.finish())); - frame.present(); - }); - }); - }, - Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => *control_flow = ControlFlow::Exit, - event => { - // recreate swap chain on resize, but also still pass the event to thyme - if let Event::WindowEvent { event: WindowEvent::Resized(_), ..} = event { - let size: (u32, u32) = window.inner_size().into(); - - let surface_config = get_surface_config(size.0, size.1); - surface.configure(&device, &surface_config); - } - - io.handle_event(&mut context, &event); - } - }) -} - -async fn setup_wgpu( - instance: &wgpu::Instance, - surface: &wgpu::Surface -) -> (wgpu::Adapter, Arc, Arc) { - let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::LowPower, - // Request an adapter which can render to our surface - compatible_surface: Some(surface), - force_fallback_adapter: false, - }).await.unwrap(); - - // Create the logical device and command queue - let (device, queue) = adapter.request_device( - &wgpu::DeviceDescriptor { - label: None, - features: wgpu::Features::empty(), - limits: wgpu::Limits::default(), - }, - None, - ).await.expect("Failed to create WGPU device"); - - (adapter, Arc::new(device), Arc::new(queue)) -} - -fn get_surface_config(width: u32, height: u32) -> wgpu::SurfaceConfiguration { - wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: wgpu::TextureFormat::Bgra8Unorm, - width, - height, - present_mode: wgpu::PresentMode::AutoVsync, - alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![], - } -} diff --git a/examples/hello_wgpu.rs b/examples/hello_wgpu.rs deleted file mode 100644 index adef6f5..0000000 --- a/examples/hello_wgpu.rs +++ /dev/null @@ -1,19 +0,0 @@ -fn main() -> Result<(), Box> { - let app = thyme::AppBuilder::new() - .with_logger() - .with_title("Thyme wgpu Demo") - .with_window_size(1280.0, 720.0) - .with_base_dir("examples/data") - .with_theme_files(&["themes/base.yml", "themes/pixel.yml"]) - .with_font_dir("fonts") - .with_image_dir("images") - .build_wgpu()?; - - app.main_loop(|ui| { - ui.window("window", |ui| { - ui.gap(20.0); - - ui.button("label", "Hello, World!"); - }); - }); -} \ No newline at end of file diff --git a/src/app_builder.rs b/src/app_builder.rs index f5b6dcf..0f05269 100644 --- a/src/app_builder.rs +++ b/src/app_builder.rs @@ -1,8 +1,5 @@ use std::path::PathBuf; -#[cfg(feature="wgpu_backend")] -use std::sync::Arc; - use crate::{Error, Point, BuildOptions, ContextBuilder, Context, WinitIo, Frame}; /// An easy to use but still fairly configurable builder, allowing you to get @@ -276,49 +273,6 @@ impl AppBuilder { Ok(GliumApp { io, renderer, context, display, event_loop }) } - /// Creates a [`WgpuApp`](struct.WgpuApp.html) object, setting up Thyme as specified - /// in this Builder and using the [`WgpuRenderer`](struct.WgpuRenderer.html). - #[cfg(feature="wgpu_backend")] - pub fn build_wgpu(self) -> Result { - use winit::{ - event_loop::EventLoop, - window::WindowBuilder, - dpi::LogicalSize - }; - - if self.logger { - crate::log::init(log::Level::Warn).unwrap(); - } - - let event_loop = EventLoop::new(); - let window = WindowBuilder::new() - .with_title(&self.title) - .with_inner_size(LogicalSize::new(self.window_size.x, self.window_size.y)) - .build(&event_loop).map_err(crate::winit_io::WinitError::Os).map_err(Error::Winit)?; - - // setup WGPU - let instance_desc = wgpu::InstanceDescriptor { - backends: wgpu::Backends::PRIMARY, - dx12_shader_compiler: wgpu::Dx12Compiler::Fxc, - }; - let instance = wgpu::Instance::new(instance_desc); - let surface = unsafe { instance.create_surface(&window).map_err(Error::WgpuSurface)? }; - let (_adapter, device, queue) = futures::executor::block_on(setup_wgpu(&instance, &surface)); - let surface_config = surface_config(self.window_size.x as u32, self.window_size.y as u32); - surface.configure(&device, &surface_config); - - // create thyme backend - let mut io = crate::WinitIo::new(&event_loop, self.window_size).map_err(Error::Winit)?; - let mut renderer = crate::WgpuRenderer::new(Arc::clone(&device), Arc::clone(&queue)); - let mut context_builder = crate::ContextBuilder::new(self.options.clone()); - - self.register_resources(&mut context_builder)?; - - let context = context_builder.build(&mut renderer, &mut io)?; - - Ok(WgpuApp { io, renderer, context, event_loop, window, surface, device, queue }) - } - fn register_resources(&self, context_builder: &mut ContextBuilder) -> Result<(), Error> { let theme_src = match self.themes.as_ref() { None => return Err(Error::Theme("No theme files specified".to_string())), @@ -352,102 +306,6 @@ impl AppBuilder { } } -/// The WgpuApp object, containing the Thyme [`Context`](struct.Context.html), [`Renderer`](struct.WgpuRenderer.html), -/// and [`IO`](struct.WinitIo.html). You can manually use the public members of this struct, or use [`main_loop`](#method.main_loop) -/// for basic use cases. -#[cfg(feature="wgpu_backend")] -pub struct WgpuApp { - /// The Thyme IO - pub io: WinitIo, - - /// The Thyme Renderer - pub renderer: crate::WgpuRenderer, - - /// The Thyme Context - pub context: Context, - - /// Winit Event loop - pub event_loop: winit::event_loop::EventLoop<()>, - - /// Winit window - pub window: winit::window::Window, - - /// The Wgpu output surface - pub surface: wgpu::Surface, - - /// Wgpu output device - pub device: Arc, - - /// Wgpu output queue - pub queue: Arc, -} - -#[cfg(feature="wgpu_backend")] -impl WgpuApp { - /// Runs the Winit main loop for this app - pub fn main_loop(self, f: F) -> ! { - use winit::{ - event::{Event, WindowEvent}, - event_loop::ControlFlow, - }; - - let event_loop = self.event_loop; - let mut renderer = self.renderer; - let mut io = self.io; - let mut context = self.context; - let window = self.window; - let queue = self.queue; - let device = self.device; - let surface = self.surface; - - event_loop.run(move |event, _, control_flow| { - match event { - Event::MainEventsCleared => { - let frame = surface.get_current_texture().unwrap(); - let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default()); - let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - - let mut ui = context.create_frame(); - - (f)(&mut ui); - - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), - store: true, - }, - })], - depth_stencil_attachment: None, - }); - - renderer.draw_frame(ui, &mut render_pass); - } - - queue.submit(Some(encoder.finish())); - frame.present(); - }, - Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => *control_flow = ControlFlow::Exit, - event => { - // reconfigure surface on resize, but also still pass the event to thyme - if let Event::WindowEvent { event: WindowEvent::Resized(_), ..} = event { - let size: (u32, u32) = window.inner_size().into(); - - let surface_config = surface_config(size.0, size.1); - surface.configure(&device, &surface_config); - } - - io.handle_event(&mut context, &event); - } - } - }) - } -} - /// The GlApp object, containing the Thyme [`Context`](struct.Context.html), [`Renderer`](struct.GlRenderer.html), and /// [`IO`](struct.WinitIo.html). YOu can manually use the public members of this struct, or use [`main_loop`](#method.main_loop) /// for basic use cases. @@ -615,42 +473,4 @@ fn add_path(path: PathBuf, out: &mut Vec<(String, PathBuf)>) { }; out.push((stem.to_string(), path)); -} - -#[cfg(feature="wgpu_backend")] -async fn setup_wgpu( - instance: &wgpu::Instance, - surface: &wgpu::Surface -) -> (wgpu::Adapter, Arc, Arc) { - let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::LowPower, - // Request an adapter which can render to our surface - compatible_surface: Some(surface), - force_fallback_adapter: false, - }).await.unwrap(); - - // Create the logical device and command queue - let (device, queue) = adapter.request_device( - &wgpu::DeviceDescriptor { - label: None, - features: wgpu::Features::empty(), - limits: wgpu::Limits::default(), - }, - None, - ).await.expect("Failed to create WGPU device"); - - (adapter, Arc::new(device), Arc::new(queue)) -} - -#[cfg(feature="wgpu_backend")] -fn surface_config(width: u32, height: u32) -> wgpu::SurfaceConfiguration { - wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: wgpu::TextureFormat::Bgra8Unorm, - width, - height, - present_mode: wgpu::PresentMode::AutoVsync, - alpha_mode: wgpu::CompositeAlphaMode::Auto, - view_formats: vec![], - } -} +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index be48771..37fccc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -414,12 +414,6 @@ mod glium_backend; #[cfg(feature = "glium_backend")] pub use glium_backend::{GliumRenderer, GliumError}; -#[cfg(feature = "wgpu_backend")] -mod wgpu_backend; - -#[cfg(feature = "wgpu_backend")] -pub use wgpu_backend::WgpuRenderer; - pub use app_builder::AppBuilder; #[cfg(feature="glium_backend")] @@ -477,10 +471,6 @@ pub enum Error { /// An error originating from OpenGl #[cfg(feature="gl_backend")] Gl(crate::gl_backend::GlError), - - /// An error originating from Wgpu's create surface routine - #[cfg(feature="wgpu_backend")] - WgpuSurface(wgpu::CreateSurfaceError), } impl std::fmt::Display for Error { @@ -502,9 +492,6 @@ impl std::fmt::Display for Error { #[cfg(feature="gl_backend")] Gl(error) => write!(f, "OpenGL Error: {}", error), - - #[cfg(feature="wgpu_backend")] - WgpuSurface(error) => write!(f, "Wgpu surface error: {}", error), } } } @@ -528,9 +515,6 @@ impl std::error::Error for Error { #[cfg(feature="gl_backend")] Gl(error) => Some(error), - - #[cfg(feature="wgpu_backend")] - WgpuSurface(error) => Some(error), } } } \ No newline at end of file diff --git a/src/wgpu_backend/mod.rs b/src/wgpu_backend/mod.rs deleted file mode 100644 index 5c4427e..0000000 --- a/src/wgpu_backend/mod.rs +++ /dev/null @@ -1,684 +0,0 @@ -use std::sync::Arc; -use std::num::NonZeroU32; - -use wgpu::{ - Buffer, BufferDescriptor, BufferUsages, BufferAddress, BufferBindingType, - BlendFactor, BlendOperation, ColorWrites, - BindingResource, BindGroupLayout, BindGroupEntry, BindingType, BindGroupDescriptor, BindGroup, BindGroupLayoutDescriptor, BindGroupLayoutEntry, - Device, Queue, RenderPipeline, RenderPass, - TextureFormat, TextureViewDimension, TextureViewDescriptor, TextureSampleType, - SamplerDescriptor, AddressMode, FilterMode, PrimitiveState, SamplerBindingType, - VertexStepMode, vertex_attr_array, - util::{BufferInitDescriptor, DeviceExt}, -}; - -use crate::render::{DrawMode, view_matrix, TextureData, TexCoord, DrawList}; -use crate::font::{FontDrawParams, FontTextureWriter}; -use crate::image::ImageDrawParams; -use crate::theme_definition::CharacterRange; -use crate::{Renderer, Frame, Point, Color, Rect}; - -/** -A Thyme [`Renderer`](trait.Renderer.html) for [`wgpu`](https://github.com/gfx-rs/wgpu-rs). - -The adapter registers image and font data as textures, and renders each frame. - -This renderer is implemented fairly naively at present and there is definitely room for optimization. -However, it is nonetheless already quite fast. - -See the thyme examples for how to setup and use this renderer. -*/ -pub struct WgpuRenderer { - device: Arc, - queue: Arc, - - image_pipe: RenderPipeline, - font_pipe: RenderPipeline, - - view_matrix_buffer: Buffer, - view_matrix_bind_group: BindGroup, - - texture_layout: BindGroupLayout, - - // assets loaded from context - textures: Vec, - fonts: Vec, - - // per frame data - draw_list: WgpuDrawList, - draw_groups: Vec, - buffered: Option, -} - -macro_rules! create_spirv { - ( $($name:tt)* ) => { - wgpu::ShaderModuleDescriptor { - label: Some( $($name)* ), - source: wgpu::util::make_spirv(include_bytes!( $($name)* )), - } - }; -} - -impl WgpuRenderer { - // TODO rework context builder so we don't need to hold on to device and queue reference - - /// Creates a new wgpu renderer, using the specified `device` and `queue`. These must be wrapped in an - /// `Arc` so that the renderer can hold onto the references. - pub fn new(device: Arc, queue: Arc) -> WgpuRenderer { - /* - Note that the SPIRV shaders are manually built using [`shaderc`](https://github.com/google/shaderc). - This is slightly inconvenient, but I have found configuring shaders to compile at build time reliably - in different environments too difficult. - - The commands to compile the shaders should be: - ```bash - cd src/wgpu_backend/shaders - glslc -fshader-stage=vertex -fentry-point=main -o vert.spirv vert.glsl - glslc -fshader-stage=fragment -fentry-point=main -o frag.spirv frag.glsl - glslc -fshader-stage=fragment -fentry-point=main -o frag_font.spirv frag_font.glsl - ``` - */ - let vert_shader = device.create_shader_module(create_spirv!("shaders/vert.spirv")); - let frag_shader = device.create_shader_module(create_spirv!("shaders/frag.spirv")); - let frag_font_shader = device.create_shader_module(create_spirv!("shaders/frag_font.spirv")); - - // setup the view matrix - let view_matrix_buffer = device.create_buffer(&BufferDescriptor { - label: Some("view matrix buffer"), - size: 64, // 4 x 4 x 4 bytes - usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - let view_matrix_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { - label: None, - entries: &[BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - }); - - let view_matrix_bind_group = device.create_bind_group(&BindGroupDescriptor { - label: Some("view matrix bind group"), - layout: &view_matrix_layout, - entries: &[BindGroupEntry { - binding: 0, - resource: BindingResource::Buffer(wgpu::BufferBinding { - buffer: &view_matrix_buffer, - offset: 0, - size: None, - }), - }], - }); - - // setup the texture layout - let texture_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { - label: Some("thyme texture layout"), - entries: &[ - BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: BindingType::Texture { - multisampled: false, - sample_type: TextureSampleType::Float { filterable: true }, - view_dimension: TextureViewDimension::D2, - }, - count: None, - }, - BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: BindingType::Sampler(SamplerBindingType::Filtering), - count: None, - }, - ], - }); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("thyme pipeline layout"), - bind_group_layouts: &[&view_matrix_layout, &texture_layout], - push_constant_ranges: &[], - }); - - let mut pipe_desc = wgpu::RenderPipelineDescriptor { - label: Some("thyme render pipeline"), - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module: &vert_shader, - entry_point: "main", - buffers: &[wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as BufferAddress, - step_mode: VertexStepMode::Vertex, - attributes: &vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Float32x4, 3 => Float32x2, 4 => Float32x2], - }], - }, - fragment: Some(wgpu::FragmentState { - module: &frag_shader, - entry_point: "main", - targets: &[Some( - wgpu::ColorTargetState { - format: TextureFormat::Bgra8Unorm, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: BlendFactor::SrcAlpha, - dst_factor: BlendFactor::OneMinusSrcAlpha, - operation: BlendOperation::Add, - }, - alpha: wgpu::BlendComponent { - src_factor: BlendFactor::OneMinusDstAlpha, - dst_factor: BlendFactor::One, - operation: BlendOperation::Add, - } - }), - write_mask: ColorWrites::ALL, - })], - }), - primitive: PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: None, - unclipped_depth: false, - polygon_mode: wgpu::PolygonMode::Fill, - conservative: false, - }, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - depth_stencil: None, - multiview: None, - }; - - let image_pipe = device.create_render_pipeline(&pipe_desc); - - pipe_desc.fragment = Some(wgpu::FragmentState { - module: &frag_font_shader, - entry_point: "main", - targets: &[Some( - wgpu::ColorTargetState { - format: TextureFormat::Bgra8Unorm, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: BlendFactor::SrcAlpha, - dst_factor: BlendFactor::OneMinusSrcAlpha, - operation: BlendOperation::Add, - }, - alpha: wgpu::BlendComponent { - src_factor: BlendFactor::OneMinusDstAlpha, - dst_factor: BlendFactor::One, - operation: BlendOperation::Add, - } - }), - write_mask: ColorWrites::ALL, - })], - }); - - let font_pipe = device.create_render_pipeline(&pipe_desc); - - WgpuRenderer { - view_matrix_buffer, - view_matrix_bind_group, - texture_layout, - textures: Vec::new(), - fonts: Vec::new(), - image_pipe, - font_pipe, - device, - queue, - draw_list: WgpuDrawList::new(), - draw_groups: Vec::new(), - buffered: None, - } - } - - /// Draws the current [`Frame`](struct.Frame.html) to the screen - pub fn draw_frame<'a>(&'a mut self, frame: Frame, render_pass: &mut RenderPass<'a>) { - let mouse_cursor = frame.mouse_cursor(); - let (context, widgets, render_groups) = frame.finish_frame(); - let context = context.internal().borrow(); - - let time_millis = context.time_millis(); - let scale = context.scale_factor(); - - self.update_view_matrix(Point::default(), context.display_size()); - self.draw_groups.clear(); - self.draw_list.clear(); - self.buffered.take(); - - // render all widget groups to buffers - for render_group in render_groups.into_iter().rev() { - let mut draw_mode = None; - - // render backgrounds - for widget in render_group.iter(&widgets) { - if !widget.visible() { continue; } - let image_handle = match widget.background() { - None => continue, - Some(handle) => handle, - }; - let time_millis = time_millis - context.base_time_millis_for(widget.id()); - let image = context.themes().image(image_handle); - - self.buffer_if_changed(&mut draw_mode, DrawMode::Image(image.texture())); - - image.draw( - &mut self.draw_list, - ImageDrawParams { - pos: widget.pos().into(), - size: widget.size().into(), - anim_state: widget.anim_state(), - clip: widget.clip(), - time_millis, - scale, - color: widget.image_color(), - } - ); - } - - // render foregrounds & text - for widget in render_group.iter(&widgets) { - if !widget.visible() { continue; } - - let border = widget.border(); - let fg_pos = widget.pos() + border.tl(); - let fg_size = widget.inner_size(); - - if let Some(image_handle) = widget.foreground() { - let time_millis = time_millis - context.base_time_millis_for(widget.id()); - let image = context.themes().image(image_handle); - - self.buffer_if_changed(&mut draw_mode, DrawMode::Image(image.texture())); - - image.draw( - &mut self.draw_list, - ImageDrawParams { - pos: fg_pos.into(), - size: fg_size.into(), - anim_state: widget.anim_state(), - clip: widget.clip(), - time_millis, - scale, - color: widget.image_color(), - } - ); - } - - if let Some(text) = widget.text() { - if let Some(font_sum) = widget.font() { - self.buffer_if_changed(&mut draw_mode, DrawMode::Font(font_sum.handle)); - let font = context.themes().font(font_sum.handle); - - let params = FontDrawParams { - area_size: fg_size * scale, - pos: fg_pos * scale, - indent: widget.text_indent(), - align: widget.text_align(), - color: widget.text_color(), - scale_factor: context.scale_factor(), - }; - - font.draw( - &mut self.draw_list, - params, - text, - widget.clip() * scale, - ) - } - } - } - - // draw any not already drawn vertices - if let Some(cur_mode) = draw_mode.take() { - self.buffer(cur_mode); - } - } - - if let Some((mouse_cursor, align, anim_state)) = mouse_cursor { - let image = context.themes().image(mouse_cursor); - let mouse_pos = context.mouse_pos(); - let size = image.base_size(); - let pos = mouse_pos - align.adjust_for(size); - let clip = Rect::new(pos, size); - - let params = ImageDrawParams { - pos: pos.into(), - size: size.into(), - anim_state, - clip, - time_millis, - scale, - color: Color::white(), - }; - - image.draw(&mut self.draw_list, params); - self.buffer(DrawMode::Image(image.texture())); - } - - // setup view matrix uniform - render_pass.set_bind_group(0, &self.view_matrix_bind_group, &[]); - - // draw buffers to render pass - let vertices = self.create_vertex_buffer(&self.draw_list.vertices); - let indices = self.create_index_buffer(&self.draw_list.indices); - // we need to store this data somewhere to satisfy wgpu's lifetime requirements - self.buffered = Some(BufferedData { - vertices, - indices, - }); - - if let Some(data) = &self.buffered { - render_pass.set_vertex_buffer(0, data.vertices.slice(..)); - render_pass.set_index_buffer(data.indices.slice(..), wgpu::IndexFormat::Uint16); - - for group in &self.draw_groups { - let texture = match &group.mode { - DrawMode::Image(handle) => { - render_pass.set_pipeline(&self.image_pipe); - &self.textures[handle.id()] - }, - DrawMode::Font(handle) => { - render_pass.set_pipeline(&self.font_pipe); - &self.fonts[handle.id()] - } - }; - - render_pass.set_bind_group(1, &texture.bind_group, &[]); - render_pass.draw_indexed(group.start..group.end, 0, 0..1); - } - } - } - - fn buffer_if_changed( - &mut self, - mode: &mut Option, - desired_mode: DrawMode, - ) { - match mode { - None => *mode = Some(desired_mode), - Some(cur_mode) => if *cur_mode != desired_mode { - self.buffer(*cur_mode); - *mode = Some(desired_mode); - } - } - } - - fn buffer(&mut self, mode: DrawMode) { - let end = self.draw_list.indices.len() as u32; - // if this is the first draw group, start at 0 - let start = match self.draw_groups.last() { - None => 0, - Some(group) => group.end, - }; - - self.draw_groups.push(DrawGroup { - start, - end, - mode, - }); - } - - fn create_vertex_buffer(&self, vertices: &[Vertex]) -> Buffer { - let data = bytemuck::cast_slice(vertices); - self.device.create_buffer_init(&BufferInitDescriptor { - label: Some("vertex buffer"), - contents: data, - usage: BufferUsages::VERTEX, - }) - } - - fn create_index_buffer(&self, indices: &[u16]) -> Buffer { - let data = bytemuck::cast_slice(indices); - self.device.create_buffer_init(&BufferInitDescriptor { - label: Some("index buffer"), - contents: data, - usage: BufferUsages::INDEX, - }) - } - - fn update_view_matrix(&self, display_pos: Point, display_size: Point) { - let view_matrix = view_matrix(display_pos, display_size); - let data = bytemuck::bytes_of(&view_matrix); - self.queue.write_buffer(&self.view_matrix_buffer, 0, data); - } - - fn create_texture( - &self, - image_data: &[u8], - width: u32, - height: u32, - format: wgpu::TextureFormat, - filter: FilterMode, - ) -> BindGroup { - let texture = self.device.create_texture(&wgpu::TextureDescriptor { - size: wgpu::Extent3d { width, height, depth_or_array_layers: 1, }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - label: None, - view_formats: &[], - }); - - let bytes = image_data.len(); - self.queue.write_texture( - wgpu::ImageCopyTextureBase { - texture: &texture, - mip_level: 0, - origin: wgpu::Origin3d { x: 0, y: 0, z: 0 }, - aspect: wgpu::TextureAspect::All, - }, - image_data, - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: NonZeroU32::new(bytes as u32 / height), - rows_per_image: NonZeroU32::new(height), - }, - wgpu::Extent3d { width, height, depth_or_array_layers: 1, }, - ); - - let view = texture.create_view(&TextureViewDescriptor::default()); - - let sampler = self.device.create_sampler(&SamplerDescriptor { - label: None, - border_color: None, - address_mode_u: AddressMode::ClampToEdge, - address_mode_v: AddressMode::ClampToEdge, - address_mode_w: AddressMode::ClampToEdge, - mag_filter: filter, - min_filter: filter, - mipmap_filter: filter, - lod_min_clamp: 0.0, - lod_max_clamp: 100.0, - compare: None, - anisotropy_clamp: None, - }); - - let bind_group = self.device.create_bind_group(&BindGroupDescriptor { - label: None, - layout: &self.texture_layout, - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView(&view), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&sampler), - }, - ], - }); - - bind_group - } -} - -impl Renderer for WgpuRenderer { - fn register_font( - &mut self, - handle: crate::render::FontHandle, - source: &crate::font::FontSource, - ranges: &[CharacterRange], - size: f32, - scale: f32, - ) -> Result { - let font = &source.font; - - let writer = FontTextureWriter::new(font, ranges, size, scale); - let writer_out = writer.write(handle, ranges)?; - - let bind_group = self.create_texture( - &writer_out.data, - writer_out.tex_width, - writer_out.tex_height, - wgpu::TextureFormat::R8Unorm, - FilterMode::Nearest, - ); - - assert!(handle.id() <= self.fonts.len()); - if handle.id() == self.fonts.len() { - self.fonts.push(Texture { bind_group }); - } else { - self.fonts[handle.id()] = Texture { bind_group }; - } - - - Ok(writer_out.font) - } - - fn register_texture( - &mut self, - handle: crate::render::TextureHandle, - image_data: &[u8], - dimensions: (u32, u32), - ) -> Result { - let bind_group = self.create_texture( - image_data, - dimensions.0, - dimensions.1, - wgpu::TextureFormat::Rgba8Unorm, - FilterMode::Linear, - ); - - assert!(handle.id() <= self.textures.len()); - if handle.id() == self.textures.len() { - self.textures.push(Texture { bind_group }); - } else { - self.textures[handle.id()] = Texture { bind_group }; - } - - Ok(TextureData::new(handle, dimensions.0, dimensions.1)) - } -} - -struct DrawGroup { - start: u32, - end: u32, - mode: DrawMode, -} - -struct BufferedData { - vertices: Buffer, - indices: Buffer, -} - -struct Texture { - bind_group: BindGroup, -} - -#[derive(Copy, Clone)] -#[repr(C)] -struct Vertex { - position: [f32; 2], - tex: [f32; 2], - color: [f32; 4], - clip_pos: [f32; 2], - clip_size: [f32; 2], -} - -// safety - Vertex is exactly 44 bytes with no padding. all bit patterns are allowed. -unsafe impl bytemuck::Pod for Vertex {} -unsafe impl bytemuck::Zeroable for Vertex {} - -struct WgpuDrawList { - vertices: Vec, - indices: Vec, -} - -impl WgpuDrawList { - fn new() -> Self { - WgpuDrawList { - vertices: Vec::new(), - indices: Vec::new(), - } - } - - fn clear(&mut self) { - self.vertices.clear(); - self.indices.clear(); - } -} - -impl DrawList for WgpuDrawList { - fn len(&self) -> usize { self.vertices.len() } - - fn back_adjust_positions(&mut self, since_index: usize, amount: Point) { - for vert in self.vertices.iter_mut().skip(since_index) { - vert.position[0] += amount.x; - vert.position[1] += amount.y; - } - } - - fn push_rect( - &mut self, - pos: [f32; 2], - size: [f32; 2], - tex: [TexCoord; 2], - color: Color, - clip: Rect, - ) { - let ul = Vertex { - position: [pos[0], pos[1]], - tex: tex[0].into(), - color: color.into(), - clip_pos: clip.pos.into(), - clip_size: clip.size.into(), - }; - - let lr = Vertex { - position: [pos[0] + size[0], pos[1] + size[1]], - tex: tex[1].into(), - color: color.into(), - clip_pos: clip.pos.into(), - clip_size: clip.size.into(), - }; - - let idx = self.vertices.len() as u16; - self.indices.extend_from_slice(&[idx, idx + 1, idx + 2, idx, idx + 2, idx + 3]); - - self.vertices.push(ul); - self.vertices.push(Vertex { - position: [ul.position[0], lr.position[1]], - tex: [ul.tex[0], lr.tex[1]], - color: ul.color, - clip_pos: clip.pos.into(), - clip_size: clip.size.into(), - }); - self.vertices.push(lr); - self.vertices.push(Vertex { - position: [lr.position[0], ul.position[1]], - tex: [lr.tex[0], ul.tex[1]], - color: lr.color, - clip_pos: clip.pos.into(), - clip_size: clip.size.into(), - }); - } -} diff --git a/src/wgpu_backend/shaders/frag.glsl b/src/wgpu_backend/shaders/frag.glsl deleted file mode 100644 index bfe2916..0000000 --- a/src/wgpu_backend/shaders/frag.glsl +++ /dev/null @@ -1,20 +0,0 @@ -#version 450 - -layout(set = 1, binding = 0) uniform texture2D tex; -layout(set = 1, binding = 1) uniform sampler samp; - -layout(location = 0) in vec2 v_tex_coords; -layout(location = 1) in vec4 v_color; -layout(location = 2) in vec4 v_clip; - -layout(location = 0) out vec4 color; - -void main() { - vec4 c = texture(sampler2D(tex, samp), v_tex_coords); - - if (v_clip.x < 0.0 || v_clip.y < 0.0 || v_clip.z < 0.0 || v_clip.w < 0.0) { - discard; - } - - color = v_color * c; -} \ No newline at end of file diff --git a/src/wgpu_backend/shaders/frag.spirv b/src/wgpu_backend/shaders/frag.spirv deleted file mode 100644 index 86d9f77..0000000 Binary files a/src/wgpu_backend/shaders/frag.spirv and /dev/null differ diff --git a/src/wgpu_backend/shaders/frag_font.glsl b/src/wgpu_backend/shaders/frag_font.glsl deleted file mode 100644 index 7d800dc..0000000 --- a/src/wgpu_backend/shaders/frag_font.glsl +++ /dev/null @@ -1,20 +0,0 @@ -#version 450 - -layout(set = 1, binding = 0) uniform texture2D tex; -layout(set = 1, binding = 1) uniform sampler samp; - -layout(location = 0) in vec2 v_tex_coords; -layout(location = 1) in vec4 v_color; -layout(location = 2) in vec4 v_clip; - -layout(location = 0) out vec4 color; - -void main() { - float a = texture(sampler2D(tex, samp), v_tex_coords).r; - - if (v_clip.x < 0.0 || v_clip.y < 0.0 || v_clip.z < 0.0 || v_clip.w < 0.0) { - discard; - } - - color = vec4(v_color.rgb, a); -} \ No newline at end of file diff --git a/src/wgpu_backend/shaders/frag_font.spirv b/src/wgpu_backend/shaders/frag_font.spirv deleted file mode 100644 index 2ecbc12..0000000 Binary files a/src/wgpu_backend/shaders/frag_font.spirv and /dev/null differ diff --git a/src/wgpu_backend/shaders/vert.glsl b/src/wgpu_backend/shaders/vert.glsl deleted file mode 100644 index 431312a..0000000 --- a/src/wgpu_backend/shaders/vert.glsl +++ /dev/null @@ -1,27 +0,0 @@ -#version 450 - -layout(set = 0, binding = 0) uniform View { mat4 matrix; }; - -layout(location = 0) in vec2 position; -layout(location = 1) in vec2 tex; -layout(location = 2) in vec4 color; -layout(location = 3) in vec2 clip_pos; -layout(location = 4) in vec2 clip_size; - -layout(location = 0) out vec2 v_tex_coords; -layout(location = 1) out vec4 v_color; -layout(location = 2) out vec4 v_clip; - -void main() { - gl_Position = matrix * vec4(position, 0.0, 1.0); - - v_tex_coords = tex; - v_color = color; - - v_clip = vec4( - position.x - clip_pos.x, - clip_pos.x + clip_size.x - position.x, - position.y - clip_pos.y, - clip_pos.y + clip_size.y - position.y - ); -} \ No newline at end of file diff --git a/src/wgpu_backend/shaders/vert.spirv b/src/wgpu_backend/shaders/vert.spirv deleted file mode 100644 index 071b6e2..0000000 Binary files a/src/wgpu_backend/shaders/vert.spirv and /dev/null differ