mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
WIP
This commit is contained in:
15
Cargo.lock
generated
15
Cargo.lock
generated
@@ -366,6 +366,20 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brush-next-nodes"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"core-types",
|
||||
"dyn-any",
|
||||
"glam",
|
||||
"node-macro",
|
||||
"raster-types",
|
||||
"wgpu",
|
||||
"wgpu-executor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brush-nodes"
|
||||
version = "0.1.0"
|
||||
@@ -2113,6 +2127,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"blending-nodes",
|
||||
"brush-next-nodes",
|
||||
"brush-nodes",
|
||||
"bytemuck",
|
||||
"core-types",
|
||||
|
||||
@@ -80,6 +80,7 @@ vector-types = { path = "node-graph/libraries/vector-types" }
|
||||
graphic-types = { path = "node-graph/libraries/graphic-types" }
|
||||
rendering = { path = "node-graph/libraries/rendering" }
|
||||
brush-nodes = { path = "node-graph/nodes/brush" }
|
||||
brush-next-nodes = { path = "node-graph/nodes/brush-next" }
|
||||
blending-nodes = { path = "node-graph/nodes/blending" }
|
||||
graphene-core = { path = "node-graph/nodes/gcore" }
|
||||
graphic-nodes = { path = "node-graph/nodes/graphic" }
|
||||
|
||||
23
node-graph/nodes/brush-next/Cargo.toml
Normal file
23
node-graph/nodes/brush-next/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "brush-next-nodes"
|
||||
description = "Next-generation GPU brush rendering nodes for Graphene"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
dyn-any = { workspace = true }
|
||||
core-types = { workspace = true }
|
||||
raster-types = { workspace = true, features = ["wgpu"] }
|
||||
wgpu-executor = { workspace = true }
|
||||
node-macro = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
glam = { workspace = true }
|
||||
wgpu = { workspace = true }
|
||||
bytemuck = { workspace = true }
|
||||
1
node-graph/nodes/brush-next/src/lib.rs
Normal file
1
node-graph/nodes/brush-next/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod render_brush;
|
||||
230
node-graph/nodes/brush-next/src/render_brush.rs
Normal file
230
node-graph/nodes/brush-next/src/render_brush.rs
Normal file
@@ -0,0 +1,230 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::{ATTR_TRANSFORM, Ctx, ProtoNodeIdentifier};
|
||||
use glam::{DAffine2, DVec2, UVec2, Vec2};
|
||||
use raster_types::{GPU, Raster};
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use wgpu::util::DeviceExt;
|
||||
use wgpu_executor::{AsyncWgpuPipeline, WgpuExecutor, WgpuPipelineCache};
|
||||
|
||||
const WGPU_EXECUTOR_IDENTIFIER: ProtoNodeIdentifier = ProtoNodeIdentifier::new("graphene_std::platform_application_io::WgpuExecutorNode");
|
||||
|
||||
#[node_macro::node(category("Raster: Brush"))]
|
||||
pub async fn render_brush_next<'a: 'n>(
|
||||
_ctx: impl Ctx,
|
||||
#[scope(brush_pipeline::IDENTIFIER)] pipeline: WgpuPipelineCache,
|
||||
start: DVec2,
|
||||
end: DVec2,
|
||||
#[default(8)] stamps: u32,
|
||||
#[default(16.)] radius: f64,
|
||||
) -> List<Raster<GPU>> {
|
||||
let pad = DVec2::splat(radius);
|
||||
let bbox_min = start.min(end) - pad;
|
||||
let bbox_max = start.max(end) + pad;
|
||||
let bbox_size = (bbox_max - bbox_min).max(DVec2::ONE);
|
||||
let size = bbox_size.ceil().as_uvec2().max(UVec2::ONE);
|
||||
|
||||
let args = BrushPipelineArgs {
|
||||
start: start - bbox_min,
|
||||
end: end - bbox_min,
|
||||
stamps,
|
||||
radius: radius as f32,
|
||||
size,
|
||||
_marker: PhantomData,
|
||||
};
|
||||
let texture = pipeline.run::<BrushPipeline>(&args).await;
|
||||
let raster = Raster::<GPU>::new_gpu(texture.as_ref().clone());
|
||||
let transform = DAffine2::from_translation(bbox_min) * DAffine2::from_scale(DVec2::new(size.x as f64, size.y as f64));
|
||||
List::new_from_item(Item::new_from_element(raster).with_attribute(ATTR_TRANSFORM, transform))
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""), inject_scope)]
|
||||
async fn brush_pipeline<'a: 'n>(_ctx: impl Ctx, #[scope(WGPU_EXECUTOR_IDENTIFIER)] executor: &'a WgpuExecutor, #[data] pipeline: WgpuPipelineCache) -> WgpuPipelineCache {
|
||||
executor.pipeline_init::<BrushPipeline>(pipeline);
|
||||
pipeline.clone()
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
||||
struct Stamp {
|
||||
center_px: [f32; 2],
|
||||
radius_px: f32,
|
||||
_pad: f32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
|
||||
struct Globals {
|
||||
viewport_size: [f32; 2],
|
||||
_pad: [f32; 2],
|
||||
}
|
||||
|
||||
pub struct BrushPipeline {
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
bind_group_layout: wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
pub struct BrushPipelineArgs<'a> {
|
||||
pub start: DVec2,
|
||||
pub end: DVec2,
|
||||
pub stamps: u32,
|
||||
pub radius: f32,
|
||||
pub size: UVec2,
|
||||
pub _marker: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl AsyncWgpuPipeline for BrushPipeline {
|
||||
type Args<'a> = BrushPipelineArgs<'a>;
|
||||
type Out = Arc<wgpu::Texture>;
|
||||
|
||||
fn create(executor: &WgpuExecutor) -> Self {
|
||||
let device = &executor.context().device;
|
||||
let shader = device.create_shader_module(wgpu::include_wgsl!("render_brush.wgsl"));
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("brush_bind_group_layout"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("brush_pipeline_layout"),
|
||||
bind_group_layouts: &[Some(&bind_group_layout)],
|
||||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let instance_layout = wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<Stamp>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &[
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
|
||||
shader_location: 1,
|
||||
format: wgpu::VertexFormat::Float32,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("brush_pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: Some("vs_main"),
|
||||
compilation_options: Default::default(),
|
||||
buffers: &[instance_layout],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: Some("fs_main"),
|
||||
compilation_options: Default::default(),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||
blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
Self { pipeline, bind_group_layout }
|
||||
}
|
||||
|
||||
async fn run<'a>(&'a self, executor: &'a WgpuExecutor, args: &'a Self::Args<'_>) -> Self::Out {
|
||||
let &BrushPipelineArgs { start, end, stamps, radius, size, .. } = args;
|
||||
|
||||
let size = size.max(UVec2::ONE);
|
||||
let output = executor.request_texture(size).await;
|
||||
|
||||
let device = &executor.context().device;
|
||||
let queue = &executor.context().queue;
|
||||
|
||||
let count = stamps.max(1);
|
||||
let mut instances = Vec::with_capacity(count as usize);
|
||||
for i in 0..count {
|
||||
let t = if count == 1 { 0.0 } else { i as f64 / (count - 1) as f64 };
|
||||
let center = start.lerp(end, t);
|
||||
instances.push(Stamp {
|
||||
center_px: [center.x as f32, center.y as f32],
|
||||
radius_px: radius,
|
||||
_pad: 0.0,
|
||||
});
|
||||
}
|
||||
|
||||
let instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("brush_instance_buffer"),
|
||||
contents: bytemuck::cast_slice(&instances),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
|
||||
let globals = Globals {
|
||||
viewport_size: Vec2::new(size.x as f32, size.y as f32).to_array(),
|
||||
_pad: [0.0; 2],
|
||||
};
|
||||
|
||||
let globals_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("brush_globals_buffer"),
|
||||
contents: bytemuck::bytes_of(&globals),
|
||||
usage: wgpu::BufferUsages::UNIFORM,
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("brush_bind_group"),
|
||||
layout: &self.bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: globals_buffer.as_entire_binding(),
|
||||
}],
|
||||
});
|
||||
|
||||
let output_view = output.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("brush_encoder") });
|
||||
|
||||
{
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("brush_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &output_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
depth_slice: None,
|
||||
})],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
pass.set_pipeline(&self.pipeline);
|
||||
pass.set_bind_group(0, &bind_group, &[]);
|
||||
pass.set_vertex_buffer(0, instance_buffer.slice(..));
|
||||
pass.draw(0..3, 0..count);
|
||||
}
|
||||
|
||||
queue.submit([encoder.finish()]);
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
50
node-graph/nodes/brush-next/src/render_brush.wgsl
Normal file
50
node-graph/nodes/brush-next/src/render_brush.wgsl
Normal file
@@ -0,0 +1,50 @@
|
||||
struct Globals {
|
||||
viewport_size: vec2<f32>,
|
||||
_pad: vec2<f32>,
|
||||
};
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> globals: Globals;
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) center_px: vec2<f32>,
|
||||
@location(1) radius_px: f32,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
@location(0) center_px: vec2<f32>,
|
||||
@location(1) radius_px: f32,
|
||||
) -> VertexOutput {
|
||||
let viewport = globals.viewport_size;
|
||||
|
||||
let offset_x = f32(i32(vertex_index) - 1) * 2.0;
|
||||
let offset_y = f32(i32(vertex_index & 1u) * 2 - 1) * 2.0;
|
||||
|
||||
let pos_px = center_px + vec2<f32>(offset_x, offset_y) * radius_px;
|
||||
|
||||
let clip = vec2<f32>(
|
||||
2.0 * pos_px.x / viewport.x - 1.0,
|
||||
1.0 - 2.0 * pos_px.y / viewport.y,
|
||||
);
|
||||
|
||||
var out: VertexOutput;
|
||||
out.clip_position = vec4<f32>(clip, 0.0, 1.0);
|
||||
out.center_px = center_px;
|
||||
out.radius_px = radius_px;
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let frag_px = in.clip_position.xy;
|
||||
let d = distance(frag_px, in.center_px);
|
||||
if (d > in.radius_px) {
|
||||
discard;
|
||||
}
|
||||
let t = d / in.radius_px;
|
||||
let alpha = 1.0 - smoothstep(0.0, 1.0, t);
|
||||
return vec4<f32>(0.0, 0.0, 0.0, alpha);
|
||||
}
|
||||
@@ -49,6 +49,7 @@ rendering = { workspace = true }
|
||||
graphene-application-io = { workspace = true }
|
||||
raster-nodes = { workspace = true }
|
||||
brush-nodes = { workspace = true }
|
||||
brush-next-nodes = { workspace = true }
|
||||
graphene-core = { workspace = true }
|
||||
graphic-nodes = { workspace = true }
|
||||
repeat-nodes = { workspace = true }
|
||||
|
||||
@@ -6,6 +6,7 @@ pub mod render_node;
|
||||
pub mod render_pixel_preview;
|
||||
pub mod text;
|
||||
pub use blending_nodes;
|
||||
pub use brush_next_nodes as brush_next;
|
||||
pub use brush_nodes as brush;
|
||||
pub use core_types::*;
|
||||
pub use graphene_application_io as application_io;
|
||||
|
||||
Reference in New Issue
Block a user