forked from 1b-agent/opencadstudio-upstream
perf: dedicated Face3D shader — N draw calls → 2
3DFACE entities previously created one WireGpu buffer per entity, resulting in N separate draw calls per frame. For files with large triangle meshes this was the dominant GPU CPU overhead. New pipeline: - face3d.wgsl: simple flat-color vertex + fragment shader (MVP only, no quad expansion, no pattern distance computation) - Face3DGpu: batches ALL Face3D entities into a single TriangleList buffer (6 vertices per face, per-vertex color) — 1 draw call - WireGpu::from_batch: merges N Face3D edge wire models into one quad-expanded buffer for edge rendering — 1 draw call - split_face3d_wires: separates face3d from other wires at Primitive build time via document handle lookup (O(N), once per epoch) Hit-testing via WireModel (CPU) is unaffected — face3d wires remain in entity_wires_arc() and hit_test_wires() as before. Before: N 3DFACE → N draw calls, N × 96 B/vertex WireVertex After: N 3DFACE → 2 draw calls (fill + edges), 28 B/vertex fill Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
1 parent
8c3964305b
commit
298098e764
5 files changed
+385
-4
No files matched your search
@@ -0,0 +1,93 @@
|
||||
// Face3D GPU buffer — batches all DXF 3DFACE entities into a single
|
||||
// TriangleList buffer for efficient rendering.
|
||||
//
|
||||
// Each Face3D quad (4 corners) produces 2 triangles → 6 vertices.
|
||||
// All entities are merged into one wgpu::Buffer → 1 draw call total.
|
||||
//
|
||||
// Vertex layout (28 bytes):
|
||||
// position [f32; 3] offset 0 12 B
|
||||
// color [f32; 4] offset 12 16 B
|
||||
// ------
|
||||
// 28 B / vertex
|
||||
|
||||
use crate::scene::wire_model::WireModel;
|
||||
use iced::wgpu;
|
||||
use iced::wgpu::util::DeviceExt;
|
||||
|
||||
// ── Vertex layout ──────────────────────────────────────────────────────────
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct Face3DVertex {
|
||||
pub position: [f32; 3],
|
||||
pub color: [f32; 4],
|
||||
}
|
||||
|
||||
impl Face3DVertex {
|
||||
pub fn layout<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<Face3DVertex>() as u64,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &[
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: 12,
|
||||
shader_location: 1,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── GPU handle ─────────────────────────────────────────────────────────────
|
||||
|
||||
pub struct Face3DGpu {
|
||||
pub vertex_buffer: wgpu::Buffer,
|
||||
pub vertex_count: u32,
|
||||
}
|
||||
|
||||
impl Face3DGpu {
|
||||
/// Build a batched GPU buffer from Face3D wire models.
|
||||
///
|
||||
/// Each WireModel's `key_vertices` holds the 4 corners in local space
|
||||
/// (world_offset already applied by tessellate.rs). Two triangles are
|
||||
/// emitted per face: (p0,p1,p2) and (p0,p2,p3).
|
||||
pub fn from_wires(device: &wgpu::Device, wires: &[WireModel]) -> Self {
|
||||
let mut vertices: Vec<Face3DVertex> = Vec::with_capacity(wires.len() * 6);
|
||||
|
||||
for wire in wires {
|
||||
// key_vertices has exactly 4 entries for Face3D (p0..p3).
|
||||
if wire.key_vertices.len() < 4 {
|
||||
continue;
|
||||
}
|
||||
let color = wire.color;
|
||||
let p = &wire.key_vertices;
|
||||
let v = |i: usize| Face3DVertex { position: p[i], color };
|
||||
|
||||
// Triangle 1: p0, p1, p2
|
||||
vertices.push(v(0));
|
||||
vertices.push(v(1));
|
||||
vertices.push(v(2));
|
||||
// Triangle 2: p0, p2, p3
|
||||
vertices.push(v(0));
|
||||
vertices.push(v(2));
|
||||
vertices.push(v(3));
|
||||
}
|
||||
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("face3d.vbuf"),
|
||||
contents: bytemuck::cast_slice(&vertices),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
|
||||
Self {
|
||||
vertex_buffer,
|
||||
vertex_count: vertices.len() as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod face3d_gpu;
|
||||
pub mod hatch_gpu;
|
||||
pub mod image_gpu;
|
||||
pub mod mesh_gpu;
|
||||
@@ -8,6 +9,7 @@ pub mod wire_gpu;
|
||||
use iced::wgpu;
|
||||
use iced::{Rectangle, Size};
|
||||
|
||||
pub use face3d_gpu::Face3DGpu;
|
||||
pub use hatch_gpu::HatchGpu;
|
||||
pub use image_gpu::ImageGpu;
|
||||
pub use mesh_gpu::MeshGpu;
|
||||
@@ -31,6 +33,7 @@ pub struct Pipeline {
|
||||
hatch_pipeline: wgpu::RenderPipeline,
|
||||
image_pipeline: wgpu::RenderPipeline,
|
||||
mesh_pipeline: wgpu::RenderPipeline,
|
||||
face3d_pipeline: wgpu::RenderPipeline,
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
uniform_bind_group: wgpu::BindGroup,
|
||||
hatch_bgl1: wgpu::BindGroupLayout,
|
||||
@@ -56,6 +59,9 @@ pub struct Pipeline {
|
||||
gpu_wipeouts: Vec<HatchGpu>,
|
||||
gpu_images: Vec<ImageGpu>,
|
||||
gpu_meshes: Vec<MeshGpu>,
|
||||
/// Batched 3DFACE fill (all faces in one buffer) and edges (merged wire).
|
||||
gpu_face3d_fill: Option<Face3DGpu>,
|
||||
gpu_face3d_edges: Option<WireGpu>,
|
||||
pub viewcube: ViewCubePipeline,
|
||||
/// Last geometry epoch for which GPU buffers were uploaded.
|
||||
/// Initialized to u64::MAX so the first frame always uploads.
|
||||
@@ -319,6 +325,60 @@ impl Pipeline {
|
||||
cache: None,
|
||||
});
|
||||
|
||||
// ── Face3D pipeline ────────────────────────────────────────────────
|
||||
let face3d_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("face3d.shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
|
||||
"../../shaders/face3d.wgsl"
|
||||
))),
|
||||
});
|
||||
|
||||
let face3d_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("face3d.pipeline_layout"),
|
||||
bind_group_layouts: &[&frame_bgl],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let face3d_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("face3d.pipeline"),
|
||||
layout: Some(&face3d_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &face3d_shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[face3d_gpu::Face3DVertex::layout()],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
cull_mode: None,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::LessEqual,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: MSAA_SAMPLES,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &face3d_shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
multiview: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
// ── Image pipeline ─────────────────────────────────────────────────
|
||||
let image_bgl1 = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("image.bgl1"),
|
||||
@@ -509,6 +569,7 @@ impl Pipeline {
|
||||
hatch_pipeline,
|
||||
image_pipeline,
|
||||
mesh_pipeline,
|
||||
face3d_pipeline,
|
||||
uniform_buffer,
|
||||
uniform_bind_group,
|
||||
hatch_bgl1,
|
||||
@@ -528,6 +589,8 @@ impl Pipeline {
|
||||
gpu_wipeouts: vec![],
|
||||
gpu_images: vec![],
|
||||
gpu_meshes: vec![],
|
||||
gpu_face3d_fill: None,
|
||||
gpu_face3d_edges: None,
|
||||
viewcube,
|
||||
cached_epoch: u64::MAX,
|
||||
}
|
||||
@@ -544,6 +607,19 @@ impl Pipeline {
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// Upload all 3DFACE entities as two batched GPU objects:
|
||||
/// - `gpu_face3d_fill`: filled triangles (1 buffer, 1 draw call)
|
||||
/// - `gpu_face3d_edges`: merged edge wires (1 buffer, 1 draw call)
|
||||
pub fn upload_face3d(&mut self, device: &wgpu::Device, face3d_wires: &[WireModel]) {
|
||||
if face3d_wires.is_empty() {
|
||||
self.gpu_face3d_fill = None;
|
||||
self.gpu_face3d_edges = None;
|
||||
return;
|
||||
}
|
||||
self.gpu_face3d_fill = Some(Face3DGpu::from_wires(device, face3d_wires));
|
||||
self.gpu_face3d_edges = WireGpu::from_batch(device, face3d_wires);
|
||||
}
|
||||
|
||||
pub fn upload_meshes(&mut self, device: &wgpu::Device, meshes: &[MeshModel]) {
|
||||
self.gpu_meshes = meshes
|
||||
.iter()
|
||||
@@ -704,6 +780,72 @@ impl Pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Pass 5a: 3DFACE fills ─────────────────────────────────────────
|
||||
if let Some(ref fill) = self.gpu_face3d_fill {
|
||||
if fill.vertex_count > 0 {
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("face3d.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: msaa,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
pass.set_viewport(0.0, 0.0, vp.width as f32, vp.height as f32, 0.0, 1.0);
|
||||
pass.set_pipeline(&self.face3d_pipeline);
|
||||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
pass.set_vertex_buffer(0, fill.vertex_buffer.slice(..));
|
||||
pass.draw(0..fill.vertex_count, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Pass 5b: 3DFACE edges (batched) ──────────────────────────────
|
||||
if let Some(ref edges) = self.gpu_face3d_edges {
|
||||
if edges.vertex_count >= 6 {
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("face3d_edges.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: msaa,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
pass.set_viewport(0.0, 0.0, vp.width as f32, vp.height as f32, 0.0, 1.0);
|
||||
pass.set_pipeline(&self.wire_pipeline);
|
||||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
pass.set_vertex_buffer(0, edges.vertex_buffer.slice(..));
|
||||
pass.draw(0..edges.vertex_count, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Pass 5: wires ─────────────────────────────────────────────────
|
||||
{
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
|
||||
@@ -125,6 +125,82 @@ impl WireGpu {
|
||||
Self::build(device, wire, [r, g, b, a * alpha])
|
||||
}
|
||||
|
||||
/// Merge multiple WireModels into a single GPU buffer (1 draw call for all).
|
||||
/// Each wire keeps its own color and pattern — they're stored per-vertex.
|
||||
/// Returns None if the combined vertex list is empty.
|
||||
pub fn from_batch(device: &wgpu::Device, wires: &[WireModel]) -> Option<Self> {
|
||||
let total_segs: usize = wires.iter().map(|w| w.points.len().saturating_sub(1)).sum();
|
||||
if total_segs == 0 {
|
||||
return None;
|
||||
}
|
||||
let mut vertices: Vec<WireVertex> = Vec::with_capacity(total_segs * 6);
|
||||
|
||||
for wire in wires {
|
||||
let color = wire.color;
|
||||
let pat0 = [wire.pattern[0], wire.pattern[1], wire.pattern[2], wire.pattern[3]];
|
||||
let pat1 = [wire.pattern[4], wire.pattern[5], wire.pattern[6], wire.pattern[7]];
|
||||
let half_width = wire.line_weight_px * 0.5;
|
||||
let n = wire.points.len();
|
||||
|
||||
// Cumulative arc-length per point.
|
||||
let mut dists = vec![0.0_f32; n];
|
||||
for i in 1..n {
|
||||
let p = wire.points[i - 1];
|
||||
let q = wire.points[i];
|
||||
if !p[0].is_finite() || !q[0].is_finite() {
|
||||
dists[i] = dists[i - 1];
|
||||
} else {
|
||||
let dx = q[0] - p[0];
|
||||
let dy = q[1] - p[1];
|
||||
let dz = q[2] - p[2];
|
||||
dists[i] = dists[i - 1] + (dx * dx + dy * dy + dz * dz).sqrt();
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..n.saturating_sub(1) {
|
||||
let a = wire.points[i];
|
||||
let b = wire.points[i + 1];
|
||||
if !a[0].is_finite() || !a[1].is_finite() || !a[2].is_finite()
|
||||
|| !b[0].is_finite() || !b[1].is_finite() || !b[2].is_finite()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let dist_a = dists[i];
|
||||
let dist_b = dists[i + 1];
|
||||
let make = |which_end: f32, side: f32| -> WireVertex {
|
||||
let dist = if which_end < 0.5 { dist_a } else { dist_b };
|
||||
WireVertex {
|
||||
pos_a: a, pos_b: b, which_end, side, color,
|
||||
distance: dist, half_width,
|
||||
pattern_length: wire.pattern_length,
|
||||
_pad: 0.0, pat0, pat1,
|
||||
}
|
||||
};
|
||||
vertices.push(make(0.0, -1.0));
|
||||
vertices.push(make(1.0, -1.0));
|
||||
vertices.push(make(1.0, 1.0));
|
||||
vertices.push(make(0.0, -1.0));
|
||||
vertices.push(make(1.0, 1.0));
|
||||
vertices.push(make(0.0, 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
if vertices.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("wire.batch.vbuf"),
|
||||
contents: bytemuck::cast_slice(&vertices),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
|
||||
Some(Self {
|
||||
vertex_buffer,
|
||||
vertex_count: vertices.len() as u32,
|
||||
})
|
||||
}
|
||||
|
||||
fn build(device: &wgpu::Device, wire: &WireModel, color: [f32; 4]) -> Self {
|
||||
let pat0 = [
|
||||
wire.pattern[0],
|
||||
|
||||
+37
-4
@@ -78,6 +78,9 @@ pub struct CameraState {
|
||||
#[derive(Debug)]
|
||||
pub struct Primitive {
|
||||
pub(super) wires: Arc<Vec<WireModel>>,
|
||||
/// 3DFACE entity wires — separated so they are uploaded to the dedicated
|
||||
/// face3d pipeline (fill + batched edges) instead of N individual WireGpu.
|
||||
pub(super) face3d_wires: Arc<Vec<WireModel>>,
|
||||
pub(super) hatches: Arc<Vec<HatchModel>>,
|
||||
/// Wipeout fills — rendered in a separate pass AFTER wires.
|
||||
pub(super) wipeout_hatches: Arc<Vec<HatchModel>>,
|
||||
@@ -125,6 +128,7 @@ impl shader::Primitive for Primitive {
|
||||
pipeline.upload_images(device, queue, &self.images[..]);
|
||||
pipeline.upload_meshes(device, &self.meshes[..]);
|
||||
pipeline.upload_wires(device, &self.wires[..]);
|
||||
pipeline.upload_face3d(device, &self.face3d_wires[..]);
|
||||
pipeline.cached_epoch = self.geometry_epoch;
|
||||
}
|
||||
if self.show_viewcube {
|
||||
@@ -280,10 +284,11 @@ impl Scene {
|
||||
self.selection.borrow_mut().vp_size = (bounds.width, bounds.height);
|
||||
|
||||
let entity_arc = self.entity_wires_arc();
|
||||
let (face3d_wires, other_wires) = split_face3d_wires(&entity_arc, &self.document);
|
||||
let all_wires = if self.interim_wire.is_none() && self.preview_wires.is_empty() {
|
||||
entity_arc
|
||||
Arc::new(other_wires)
|
||||
} else {
|
||||
let mut v = (*entity_arc).clone();
|
||||
let mut v = other_wires;
|
||||
if let Some(iw) = &self.interim_wire {
|
||||
v.push(iw.clone());
|
||||
}
|
||||
@@ -299,6 +304,7 @@ impl Scene {
|
||||
|
||||
Primitive {
|
||||
wires: all_wires,
|
||||
face3d_wires: Arc::new(face3d_wires),
|
||||
hatches: self.hatch_models_arc(),
|
||||
wipeout_hatches: self.wipeout_models_arc(),
|
||||
images: self.images_arc(),
|
||||
@@ -327,10 +333,11 @@ impl Scene {
|
||||
};
|
||||
|
||||
let base_arc = self.model_wires_for_viewport_arc(vp_handle);
|
||||
let (face3d_wires, other_wires) = split_face3d_wires(&base_arc, &self.document);
|
||||
let all_wires = if self.interim_wire.is_none() && self.preview_wires.is_empty() {
|
||||
base_arc
|
||||
Arc::new(other_wires)
|
||||
} else {
|
||||
let mut v = (*base_arc).clone();
|
||||
let mut v = other_wires;
|
||||
if let Some(iw) = &self.interim_wire {
|
||||
v.push(iw.clone());
|
||||
}
|
||||
@@ -340,6 +347,7 @@ impl Scene {
|
||||
|
||||
Primitive {
|
||||
wires: all_wires,
|
||||
face3d_wires: Arc::new(face3d_wires),
|
||||
hatches: self.hatch_models_arc(),
|
||||
wipeout_hatches: self.wipeout_models_arc(),
|
||||
images: self.images_arc(),
|
||||
@@ -439,3 +447,28 @@ pub(super) fn resolve_pattern(
|
||||
}
|
||||
(pat_len, pat)
|
||||
}
|
||||
|
||||
/// Partition a wire list into (face3d_wires, other_wires).
|
||||
///
|
||||
/// Uses a document handle lookup so no changes to WireModel are needed.
|
||||
/// O(N) per geometry epoch — acceptable since it runs once per epoch.
|
||||
fn split_face3d_wires(
|
||||
wires: &[WireModel],
|
||||
document: &acadrust::CadDocument,
|
||||
) -> (Vec<WireModel>, Vec<WireModel>) {
|
||||
let mut face3d = Vec::new();
|
||||
let mut others = Vec::new();
|
||||
for w in wires {
|
||||
let is_face3d = w.name.parse::<u64>()
|
||||
.ok()
|
||||
.and_then(|v| document.get_entity(Handle::new(v)))
|
||||
.map(|e| matches!(e, EntityType::Face3D(_)))
|
||||
.unwrap_or(false);
|
||||
if is_face3d {
|
||||
face3d.push(w.clone());
|
||||
} else {
|
||||
others.push(w.clone());
|
||||
}
|
||||
}
|
||||
(face3d, others)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Face3D shader — flat-shaded triangle fill for DXF 3DFACE entities.
|
||||
//
|
||||
// Vertex layout (28 bytes):
|
||||
// position [f32; 3] offset 0 12 B
|
||||
// color [f32; 4] offset 12 16 B
|
||||
|
||||
struct Uniforms {
|
||||
view_proj: mat4x4<f32>,
|
||||
viewport: vec2<f32>,
|
||||
_pad: vec2<f32>,
|
||||
};
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> u: Uniforms;
|
||||
|
||||
struct VertexIn {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
};
|
||||
|
||||
struct VertexOut {
|
||||
@builtin(position) clip_pos: vec4<f32>,
|
||||
@location(0) color: vec4<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(v: VertexIn) -> VertexOut {
|
||||
var out: VertexOut;
|
||||
out.clip_pos = u.view_proj * vec4<f32>(v.position, 1.0);
|
||||
out.color = v.color;
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
||||
return in.color;
|
||||
}
|
||||
Reference in new issue
Block a user