gpu invert node demo

This commit is contained in:
Firestar99
2025-06-05 16:00:36 +02:00
parent eb8605a0b3
commit bf2673428a
12 changed files with 1531 additions and 26 deletions

View File

@@ -15,6 +15,8 @@ gpu-executor = { path = "../gpu-executor" }
# Workspace dependencies
graphene-core = { workspace = true, features = ["std", "alloc", "gpu", "wgpu"] }
# required for cargo watch to pick up changes
graphene-core-shader = { path = "../gcore-shader" }
dyn-any = { workspace = true, features = ["log-bad-types", "rc", "glam"] }
node-macro = { workspace = true }
num-traits = { workspace = true }
@@ -40,3 +42,7 @@ half = "2.4.1"
# Optional dependencies
nvtx = { version = "1.3", optional = true }
[build-dependencies]
cargo-gpu = { workspace = true }
env_logger = { workspace = true }

View File

@@ -0,0 +1,34 @@
use cargo_gpu::CompileResultNagaExt;
use cargo_gpu::naga::back::wgsl::WriterFlags;
use cargo_gpu::naga::valid::Capabilities;
use cargo_gpu::spirv_builder::{MetadataPrintout, SpirvMetadata};
use std::path::PathBuf;
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::builder().init();
let shader_crate = PathBuf::from("../gcore-shader");
// install the toolchain and build the `rustc_codegen_spirv` codegen backend with it
let backend = cargo_gpu::Install::from_shader_crate(shader_crate.clone()).run()?;
// build the shader crate
let mut builder = backend.to_spirv_builder(shader_crate, "spirv-unknown-vulkan1.2");
builder.print_metadata = MetadataPrintout::DependencyOnly;
builder.spirv_metadata = SpirvMetadata::Full;
builder.shader_crate_features.default_features = false;
builder.shader_crate_features.features = vec![String::from("gpu")];
let spv_result = builder.build()?;
// transpile the spv binaries to wgsl
let wgsl_result = spv_result.naga_transpile(Capabilities::empty())?.to_wgsl(WriterFlags::empty())?;
let path_to_wgsl = wgsl_result.module.unwrap_single();
// emit path to wgsl into env var, used in `quad.rs` like this:
// > include_str!(env!("WGSL_SHADER_PATH"))
println!("cargo::rustc-env=WGSL_SHADER_PATH={}", path_to_wgsl.display());
// you could also generate some rust source code into the `std::env::var("OUT_DIR")` dir
// and use `include!(concat!(env!("OUT_DIR"), "/shader_symbols.rs"));` to include it
Ok(())
}

View File

@@ -0,0 +1,150 @@
use crate::{Context, WgpuExecutor};
use graphene_core::Ctx;
use graphene_core::application_io::{ImageTexture, TextureFrameTable};
use graphene_core::instances::Instance;
use std::borrow::Cow;
use std::sync::Arc;
use wgpu::{
BindGroupDescriptor, BindGroupEntry, BindingResource, ColorTargetState, Device, Face, FragmentState, FrontFace, LoadOp, Operations, PolygonMode, PrimitiveState, PrimitiveTopology, Queue,
RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, ShaderModuleDescriptor, ShaderSource, StoreOp, TextureDescriptor, TextureDimension, TextureFormat,
TextureViewDescriptor, VertexState,
};
const WGSL_SHADER: &str = include_str!(env!("WGSL_SHADER_PATH"));
#[node_macro::node(category(""))]
async fn gpu_invert<'a: 'n>(_: impl Ctx, input: TextureFrameTable, executor: &'a WgpuExecutor) -> TextureFrameTable {
let Context { device, queue, .. } = &executor.context;
// this should be cached
let graphics_pipeline = GraphitePerPixelGraphicsPipeline::new(device, "gpu_invertgpu_invert_shadergpu_invert_fragment");
graphics_pipeline.run(input, queue)
}
pub struct GraphitePerPixelGraphicsPipeline {
device: Arc<Device>,
render_pipeline_f32: wgpu::RenderPipeline,
render_pipeline_f16: wgpu::RenderPipeline,
render_pipeline_srgb8: wgpu::RenderPipeline,
}
impl GraphitePerPixelGraphicsPipeline {
pub fn new(device: &Arc<Device>, fragment_shader_name: &str) -> Self {
let shader_module = device.create_shader_module(ShaderModuleDescriptor {
label: Some("graphite wgsl shader"),
source: ShaderSource::Wgsl(Cow::Borrowed(WGSL_SHADER)),
});
let create_render_pipeline = |format| {
device.create_render_pipeline(&RenderPipelineDescriptor {
label: Some("gpu_invert"),
layout: None,
vertex: VertexState {
module: &shader_module,
entry_point: Some("fullscreen_vertexfullscreen_vertex"),
compilation_options: Default::default(),
buffers: &[],
},
primitive: PrimitiveState {
topology: PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: FrontFace::Ccw,
cull_mode: Some(Face::Back),
unclipped_depth: false,
polygon_mode: PolygonMode::Fill,
conservative: false,
},
depth_stencil: None,
multisample: Default::default(),
fragment: Some(FragmentState {
module: &shader_module,
entry_point: Some(fragment_shader_name),
compilation_options: Default::default(),
targets: &[Some(ColorTargetState {
format,
blend: None,
write_mask: Default::default(),
})],
}),
multiview: None,
cache: None,
})
};
Self {
device: device.clone(),
render_pipeline_f32: create_render_pipeline(TextureFormat::Rgba32Float),
render_pipeline_f16: create_render_pipeline(TextureFormat::Rgba16Float),
render_pipeline_srgb8: create_render_pipeline(TextureFormat::Rgba8UnormSrgb),
}
}
pub fn get(&self, format: TextureFormat) -> &wgpu::RenderPipeline {
match format {
TextureFormat::Rgba32Float => &self.render_pipeline_f32,
TextureFormat::Rgba16Float => &self.render_pipeline_f16,
TextureFormat::Rgba8UnormSrgb => &self.render_pipeline_srgb8,
_ => panic!("unsupported"),
}
}
pub fn run(&self, input: TextureFrameTable, queue: &Arc<Queue>) -> TextureFrameTable {
let device = &self.device;
let mut cmd = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("gpu_invert") });
let out = input
.instance_ref_iter()
.map(|instance| {
let view_in = instance.instance.texture.create_view(&TextureViewDescriptor::default());
let format = instance.instance.texture.format();
let pipeline = self.get(format);
let bind_group = device.create_bind_group(&BindGroupDescriptor {
label: Some("gpu_invert bind group"),
// `get_bind_group_layout` allocates unnecessary memory, we could create it manually to not do that
layout: &pipeline.get_bind_group_layout(0),
entries: &[BindGroupEntry {
binding: 0,
resource: BindingResource::TextureView(&view_in),
}],
});
let tex_out = device.create_texture(&TextureDescriptor {
label: Some("gpu_invert_out"),
size: instance.instance.texture.size(),
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[format],
});
let view_out = tex_out.create_view(&TextureViewDescriptor::default());
let mut rp = cmd.begin_render_pass(&RenderPassDescriptor {
label: Some("gpu_invert rp"),
color_attachments: &[Some(RenderPassColorAttachment {
view: &view_out,
resolve_target: None,
ops: Operations {
// should be dont_care but wgpu doesn't expose that
load: LoadOp::Clear(wgpu::Color::BLACK),
store: StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rp.set_pipeline(&pipeline);
rp.set_bind_group(0, Some(&bind_group), &[]);
rp.draw(0..3, 0..1);
Instance {
instance: ImageTexture { texture: Arc::new(tex_out) },
transform: *instance.transform,
alpha_blending: *instance.alpha_blending,
source_node_id: *instance.source_node_id,
}
})
.collect::<TextureFrameTable>();
queue.submit([cmd.finish()]);
out
}
}

View File

@@ -1,5 +1,6 @@
mod context;
mod executor;
mod gcore_shader_nodes;
use anyhow::{Result, bail};
pub use context::Context;