From c815ab49f0bfd8aaf37491e4e3c047ed2d7e7323 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Fri, 1 Nov 2024 11:02:00 -0700 Subject: [PATCH] Added custom rendering operation support Refs #199 --- CHANGELOG.md | 3 ++ Cargo.lock | 2 +- examples/shaders.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++ src/graphics.rs | 59 +++++++++++++++++++++++++++++++- src/lib.rs | 2 +- 5 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 examples/shaders.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b18800c8a..5cf91669d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -278,6 +278,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 outlines drawn in the user interface. - `FocusColor` is a new component that controls the color of the keyboard focus indicator. +- `Graphics::draw` is a new function that allows performing arbitrary `wgpu` + drawing operations when rendering. See the `shaders.rs` example for an example + on how to use this to render into a Canvas with a custom shader. [139]: https://github.com/khonsulabs/cushy/issues/139 diff --git a/Cargo.lock b/Cargo.lock index 9c24087e1..8cd0b8288 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1713,7 +1713,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kludgine" version = "0.11.0" -source = "git+https://github.com/khonsulabs/kludgine#2f7755a1a9b7cae67711f7c41ee2cd2c6b12fd64" +source = "git+https://github.com/khonsulabs/kludgine#be36e350ed45d642c0498ad98d85c48815ae45bc" dependencies = [ "ahash", "alot", diff --git a/examples/shaders.rs b/examples/shaders.rs new file mode 100644 index 000000000..8d3ab734e --- /dev/null +++ b/examples/shaders.rs @@ -0,0 +1,83 @@ +use std::borrow::Cow; + +use cushy::widget::MakeWidget; +use cushy::widgets::Canvas; +use cushy::{RenderOperation, Run}; +use kludgine::{wgpu, RenderingGraphics}; + +static TRIANGLE_SHADER: &str = r#" + @vertex + fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { + let x = f32(i32(in_vertex_index) - 1); + let y = f32(i32(in_vertex_index & 1u) * 2 - 1); + return vec4(x, y, 0.0, 1.0); + } + + @fragment + fn fs_main() -> @location(0) vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); + } +"#; + +fn main() -> cushy::Result { + let mut shader_op = None; + Canvas::new(move |ctx| { + if shader_op.is_none() { + // Compile the shader now that we have access to wgpu + let shader = ctx.gfx.inner_graphics().device().create_shader_module( + wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(TRIANGLE_SHADER)), + }, + ); + + let pipeline_layout = ctx.gfx.inner_graphics().device().create_pipeline_layout( + &wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[], + }, + ); + + let pipeline = ctx.gfx.inner_graphics().device().create_render_pipeline( + &wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: ctx.gfx.inner_graphics().multisample_state(), + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + compilation_options: wgpu::PipelineCompilationOptions::default(), + targets: &[Some(ctx.gfx.inner_graphics().texture_format().into())], + }), + multiview: None, + cache: None, + }, + ); + + // Create our rendering operation that uses the pipeline we created. + shader_op = Some(RenderOperation::new( + move |_origin, _opacity, ctx: &mut RenderingGraphics<'_, '_>| { + println!("Render to {_origin:?} clipped to {:?}", ctx.clip_rect()); + ctx.pass_mut().set_pipeline(&pipeline); + ctx.pass_mut().draw(0..3, 0..1); + }, + )); + } + + // Draw our shader + ctx.gfx.draw(shader_op.clone().expect("always initialized")); + }) + .contain() + .pad() + .expand() + .run() +} diff --git a/src/graphics.rs b/src/graphics.rs index 50e300c49..90511f3bb 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -1,4 +1,6 @@ +use std::fmt::Debug; use std::ops::{Deref, DerefMut}; +use std::sync::Arc; use figures::units::{Px, UPx}; use figures::{ @@ -10,7 +12,8 @@ use kludgine::drawing::Renderer; use kludgine::shapes::Shape; use kludgine::text::{MeasuredText, Text, TextOrigin}; use kludgine::{ - cosmic_text, ClipGuard, Color, Drawable, Kludgine, ShaderScalable, ShapeSource, TextureSource, + cosmic_text, ClipGuard, Color, Drawable, Kludgine, RenderingGraphics, ShaderScalable, + ShapeSource, TextureSource, }; use crate::animation::ZeroToOne; @@ -305,6 +308,20 @@ impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> { self.renderer.draw_measured_text(text, origin); } + /// Draws the custom rendering operation when this graphics is presented to + /// the screen. + /// + /// The rendering operation will be clipped automatically, but the rendering + /// operation will need to position and size itself accordingly. + pub fn draw(&mut self, op: RenderOperation) { + let origin = self.region.origin; + self.renderer.draw(kludgine::drawing::RenderOperation::new( + move |opacity, ctx: &mut kludgine::RenderingGraphics<'_, '_>| { + op.0.render(origin, opacity, ctx); + }, + )); + } + /// Returns a reference to the font system used to render. pub fn font_system(&mut self) -> &mut FontSystem { self.renderer.font_system() @@ -539,3 +556,43 @@ impl FontState { } } } + +/// A custom rendering operation. +#[derive(Clone)] +pub struct RenderOperation(Arc); + +impl RenderOperation { + /// Creates a new rendering operation that invokes `op` when executed. + pub fn new(op: Op) -> Self + where + Op: for<'a, 'context, 'pass> Fn(Point, f32, &'a mut RenderingGraphics<'context, 'pass>) + + Send + + Sync + + 'static, + { + Self(Arc::new(op)) + } +} + +impl Debug for RenderOperation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Arc::as_ptr(&self.0).fmt(f) + } +} + +trait RenderOp: Send + Sync + 'static { + /// Render to `graphics` with `opacity`. + fn render(&self, origin: Point, opacity: f32, graphics: &mut RenderingGraphics<'_, '_>); +} + +impl RenderOp for F +where + F: for<'a, 'context, 'pass> Fn(Point, f32, &'a mut RenderingGraphics<'context, 'pass>) + + Send + + Sync + + 'static, +{ + fn render(&self, origin: Point, opacity: f32, graphics: &mut RenderingGraphics<'_, '_>) { + self(origin, opacity, graphics); + } +} diff --git a/src/lib.rs b/src/lib.rs index 28e4308a1..726b4a5d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -137,7 +137,7 @@ pub use names::Name; pub use utils::{Lazy, ModifiersExt, ModifiersStateExt, WithClone}; pub use {figures, kludgine}; -pub use self::graphics::Graphics; +pub use self::graphics::{Graphics, RenderOperation}; pub use self::tick::{InputState, Tick}; /// Starts running a Cushy application, invoking `app_init` after the event loop