Skip to content

Commit

Permalink
Added custom rendering operation support
Browse files Browse the repository at this point in the history
Refs #199
  • Loading branch information
ecton committed Nov 1, 2024
1 parent e510b68 commit c815ab4
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 83 additions & 0 deletions examples/shaders.rs
Original file line number Diff line number Diff line change
@@ -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<f32> {
let x = f32(i32(in_vertex_index) - 1);
let y = f32(i32(in_vertex_index & 1u) * 2 - 1);
return vec4<f32>(x, y, 0.0, 1.0);
}
@fragment
fn fs_main() -> @location(0) vec4<f32> {
return vec4<f32>(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()
}
59 changes: 58 additions & 1 deletion src/graphics.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::fmt::Debug;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;

use figures::units::{Px, UPx};
use figures::{
Expand All @@ -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;
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -539,3 +556,43 @@ impl FontState {
}
}
}

/// A custom rendering operation.
#[derive(Clone)]
pub struct RenderOperation(Arc<dyn RenderOp>);

impl RenderOperation {
/// Creates a new rendering operation that invokes `op` when executed.
pub fn new<Op>(op: Op) -> Self
where
Op: for<'a, 'context, 'pass> Fn(Point<Px>, 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<Px>, opacity: f32, graphics: &mut RenderingGraphics<'_, '_>);
}

impl<F> RenderOp for F
where
F: for<'a, 'context, 'pass> Fn(Point<Px>, f32, &'a mut RenderingGraphics<'context, 'pass>)
+ Send
+ Sync
+ 'static,
{
fn render(&self, origin: Point<Px>, opacity: f32, graphics: &mut RenderingGraphics<'_, '_>) {
self(origin, opacity, graphics);
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit c815ab4

Please sign in to comment.