graphic design tool
git clone https://git.lucas.co/cce-designer.git
feat: cutover — cce-designer renders on ash (VkRenderer), wgpu retired
State drops WgpuAdapter and the four wgpu pipelines for the vk module built in
milestones 1-3: 2D quads via draw_frame, the 3D canvas via handle-based meshes
+ stage_scene (same draw order, same viewport-changed cache, backdrop cleared
on hide/zero-size), text via TextSpans against the shaped-Buffer cache, and
resize collapses to renderer.resize + set_corner_radius. State::new is sync
now (no pollster block_on).
The curved-text path (circular network pane rim labels) is reimplemented in
the text stage instead of ported: TextSpan grows rotation (angle about a
physical-px center) and clip_circle (same discard as shader.wgsl), replacing
the offscreen glyphon render + TexturedVertex pipeline — graphics.rs and
shader_textured.wgsl are deleted. FontSystem/SwashCache now live on State.
wgpu and pollster are no longer direct dependencies (glyphon still pulls wgpu
transitively for cosmic-text/swash); geometry::Vertex3D::desc() went with them.
Verified live: full app on the ash stack — node graph over the 3D backdrop,
default-project sphere via update_mesh, menus/params/spinboxes, circular pane
toggled both ways over the HTTP API with circle-clipped grid and labels — with
zero validation messages end to end. 23/23 tests pass.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RFkXq68hDwVDMKnu9fckz3
Cargo.toml | 4 +-
src/app.rs | 857 ++++-------------------------------------------
src/geometry.rs | 12 -
src/graphics.rs | 26 --
src/main.rs | 6 +-
src/render.rs | 484 ++++++--------------------
src/shader_textured.wgsl | 41 ---
src/vk/glyph.wgsl | 12 +
src/vk/text.rs | 38 ++-
src/vk_smoke.rs | 6 +
10 files changed, 239 insertions(+), 1247 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
index b2287bd..481f74f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,9 +11,9 @@ calloop-wayland-source = "0.3.0"
wayland-client = { version = "0.31", features = ["system"] }
xkeysym = "0.2"
raw-window-handle = "0.6"
-wgpu = "24"
bytemuck = { version = "1", features = ["derive"] }
-pollster = "0.4"
+# glyphon supplies cosmic-text/swash (shaping + rasterization) for the ash text
+# stage; its wgpu renderer half is unused since the VkRenderer cutover.
glyphon = "0.8"
tokio = { version = "1", features = ["full"] }
glam = "0.29"
diff --git a/src/app.rs b/src/app.rs
index 5e78a03..f35cf67 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -34,17 +34,16 @@ use wayland_client::{
Connection, QueueHandle, Proxy,
};
-use wgpu::util::DeviceExt;
use cce_ui::widget::{Adapted, Breadcrumb, MenuBar, MenuController, ParametersBg, Splitter, Spreadsheet, StatusBar, TextLabel, WidgetHost, GraphNode, Graph, Button, Checkbox, Label, Dropdown};
use cce_ui::widget::UiContext;
use crate::viewport_3d::Viewport3D;
use cce_ui::colors;
-use glyphon::{Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, TextAtlas, TextRenderer, Viewport};
+use glyphon::{Attrs, Buffer, FontSystem, Metrics};
use glam::{Mat4, Vec3};
use crate::geometry::*;
use crate::shortcut::{ShortcutManager, Action};
-use crate::graphics::TexturedVertex;
+use crate::vk::{SceneDraw, TextSpan};
use cce_ui::engine::Vertex;
use crate::window::{AppState, WindowEvent};
@@ -964,45 +963,24 @@ pub struct ViewportUniforms {
}
pub struct State {
- pub wgpu_adapter: cce_ui::backend::WgpuAdapter,
- pub render_pipeline: wgpu::RenderPipeline,
- pub vertex_buffer: wgpu::Buffer,
+ pub renderer: crate::vk::VkRenderer,
+ pub font_system: FontSystem,
+ pub swash_cache: glyphon::SwashCache,
pub window: XdgWindow,
pub wl_surface: wl_surface::WlSurface,
- pub vertex_count: u32,
pub vertex_data: Vec<Vertex>,
- pub pipeline_3d: wgpu::RenderPipeline,
- pub bind_group_3d: wgpu::BindGroup,
- pub bind_group_layout_3d: wgpu::BindGroupLayout,
- pub uniform_buffer: wgpu::Buffer,
- pub bind_group_grid: wgpu::BindGroup,
- pub uniform_buffer_grid: wgpu::Buffer,
- pub bind_group_pivot: wgpu::BindGroup,
- pub uniform_buffer_pivot: wgpu::Buffer,
- pub vertex_buffer_3d: wgpu::Buffer,
- pub vertex_buffer_viewport_bg: wgpu::Buffer,
- pub vertex_count_3d: u32,
- pub vertex_buffer_spheres: wgpu::Buffer,
+ pub mesh_cube: crate::vk::MeshId,
+ pub mesh_viewport_bg: crate::vk::MeshId,
+ pub mesh_spheres: crate::vk::MeshId,
+ pub mesh_grid: crate::vk::MeshId,
+ pub mesh_origin: crate::vk::MeshId,
+ pub mesh_pivot: crate::vk::MeshId,
pub vertex_count_spheres: u32,
- pub vertex_buffer_grid: wgpu::Buffer,
- pub vertex_count_grid: u32,
- pub depth_texture: wgpu::Texture,
- pub depth_texture_view: wgpu::TextureView,
- pub backdrop_texture: wgpu::Texture,
- pub backdrop_texture_view: wgpu::TextureView,
- pub backdrop_sampler: wgpu::Sampler,
- pub backdrop_bind_group_layout: wgpu::BindGroupLayout,
- pub backdrop_bind_group: wgpu::BindGroup,
- pub window_info_buffer: wgpu::Buffer,
pub node_color: [f32; 3],
pub grid_color: [f32; 3],
pub cell_color: [f32; 3],
pub gap_color: [f32; 3],
- pub vertex_buffer_origin: wgpu::Buffer,
- pub vertex_count_origin: u32,
- pub vertex_buffer_pivot: wgpu::Buffer,
- pub vertex_count_pivot: u32,
pub origin_size: f32,
pub camera_pivot_size: f32,
@@ -1025,18 +1003,6 @@ pub struct State {
pub node_palette_filtered: Vec<usize>,
pub node_palette_selected: usize,
- pub curved_text_texture: wgpu::Texture,
- pub curved_text_texture_view: wgpu::TextureView,
- pub curved_text_sampler: wgpu::Sampler,
- pub curved_text_bind_group: wgpu::BindGroup,
- pub curved_text_pipeline: wgpu::RenderPipeline,
- pub curved_text_atlas: TextAtlas,
- pub curved_text_renderer: TextRenderer,
- pub curved_text_viewport: Viewport,
- pub textured_vertex_buffer: wgpu::Buffer,
- pub textured_vertex_count: u32,
-
-
pub drag_widget: Option<usize>,
pub focused_widget: Option<usize>,
@@ -1316,19 +1282,19 @@ impl State {
pub fn update_grid_geometry(&mut self) {
let linear_grid_color = cce_ui::colors::to_linear_rgb(self.grid_color);
let grid_verts = grid_vertices(self.grid_thickness, linear_grid_color);
- self.wgpu_adapter.queue.write_buffer(&self.vertex_buffer_grid, 0, bytemuck::cast_slice(&grid_verts));
+ self.renderer.update_mesh(self.mesh_grid, bytemuck::cast_slice(&grid_verts));
self.viewport_dirty = true;
}
pub fn update_origin_geometry(&mut self) {
let origin_verts = origin_vectors_vertices(self.origin_size);
- self.wgpu_adapter.queue.write_buffer(&self.vertex_buffer_origin, 0, bytemuck::cast_slice(&origin_verts));
+ self.renderer.update_mesh(self.mesh_origin, bytemuck::cast_slice(&origin_verts));
self.viewport_dirty = true;
}
pub fn update_pivot_geometry(&mut self) {
let pivot_verts = camera_pivot_vertices(self.camera_pivot_size);
- self.wgpu_adapter.queue.write_buffer(&self.vertex_buffer_pivot, 0, bytemuck::cast_slice(&pivot_verts));
+ self.renderer.update_mesh(self.mesh_pivot, bytemuck::cast_slice(&pivot_verts));
self.viewport_dirty = true;
}
@@ -1343,7 +1309,7 @@ impl State {
Vertex3D { position: [ 1.0, 1.0, 9.99], color: bg_color }, // Top-right
Vertex3D { position: [-1.0, 1.0, 9.99], color: bg_color }, // Top-left
];
- self.wgpu_adapter.queue.write_buffer(&self.vertex_buffer_viewport_bg, 0, bytemuck::cast_slice(&bg_verts));
+ self.renderer.update_mesh(self.mesh_viewport_bg, bytemuck::cast_slice(&bg_verts));
self.viewport_dirty = true;
}
@@ -2447,7 +2413,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
- pub async fn new(
+ pub fn new(
conn: &Connection,
qh: &QueueHandle<AppState>,
compositor_state: &CompositorState,
@@ -2479,469 +2445,38 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
let display_ptr = conn.backend().display_id().as_ptr() as *mut std::ffi::c_void;
let surface_ptr = wl_surface.id().as_ptr() as *mut std::ffi::c_void;
- // Bundled fonts only (the 5th param opts into system fonts in the render
- // FontSystem — Phase 6k; the designer's UI uses bundled families).
- let wgpu_adapter = cce_ui::backend::WgpuAdapter::new(display_ptr, surface_ptr, pw, ph, false).await;
-
- let device = &wgpu_adapter.device;
- let queue = &wgpu_adapter.queue;
-
- let config = &wgpu_adapter.config;
-
- let backdrop_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
- label: Some("Backdrop Bind Group Layout"),
- entries: &[
- wgpu::BindGroupLayoutEntry {
- binding: 0,
- visibility: wgpu::ShaderStages::FRAGMENT,
- ty: wgpu::BindingType::Texture {
- multisampled: false,
- view_dimension: wgpu::TextureViewDimension::D2,
- sample_type: wgpu::TextureSampleType::Float { filterable: true },
- },
- count: None,
- },
- wgpu::BindGroupLayoutEntry {
- binding: 1,
- visibility: wgpu::ShaderStages::FRAGMENT,
- ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
- count: None,
- },
- wgpu::BindGroupLayoutEntry {
- binding: 2,
- visibility: wgpu::ShaderStages::FRAGMENT,
- ty: wgpu::BindingType::Buffer {
- ty: wgpu::BufferBindingType::Uniform,
- has_dynamic_offset: false,
- min_binding_size: wgpu::BufferSize::new(16),
- },
- count: None,
- },
- ],
- });
-
- let backdrop_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
- label: Some("Backdrop Sampler"),
- address_mode_u: wgpu::AddressMode::ClampToEdge,
- address_mode_v: wgpu::AddressMode::ClampToEdge,
- address_mode_w: wgpu::AddressMode::ClampToEdge,
- mag_filter: wgpu::FilterMode::Linear,
- min_filter: wgpu::FilterMode::Linear,
- mipmap_filter: wgpu::FilterMode::Nearest,
- ..Default::default()
- });
-
- let backdrop_texture = device.create_texture(&wgpu::TextureDescriptor {
- label: Some("Backdrop Texture"),
- size: wgpu::Extent3d { width: pw.max(1), height: ph.max(1), depth_or_array_layers: 1 },
- mip_level_count: 1,
- sample_count: 1,
- dimension: wgpu::TextureDimension::D2,
- format: config.format,
- usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC,
- view_formats: &[],
- });
- let backdrop_texture_view = backdrop_texture.create_view(&wgpu::TextureViewDescriptor::default());
-
- let window_info_buffer = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("Window Info Buffer"),
- size: 16,
- usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
-
- let window_info_data = [
- pw as f32,
- ph as f32,
- cce_ui::color::backplate_corner_radius() * scale as f32,
- 0.0,
- ];
- queue.write_buffer(&window_info_buffer, 0, bytemuck::cast_slice(&window_info_data));
-
- let backdrop_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("Backdrop Bind Group"),
- layout: &backdrop_bind_group_layout,
- entries: &[
- wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::TextureView(&backdrop_texture_view),
- },
- wgpu::BindGroupEntry {
- binding: 1,
- resource: wgpu::BindingResource::Sampler(&backdrop_sampler),
- },
- wgpu::BindGroupEntry {
- binding: 2,
- resource: window_info_buffer.as_entire_binding(),
- },
- ],
- });
-
- let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some("Shader"),
- source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
- });
-
- let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
- label: Some("Pipeline Layout"),
- bind_group_layouts: &[&backdrop_bind_group_layout],
- push_constant_ranges: &[],
- });
-
- let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
- label: Some("Render Pipeline"),
- layout: Some(&pipeline_layout),
- vertex: wgpu::VertexState {
- module: &shader,
- entry_point: Some("vs_main"),
- buffers: &[Vertex::desc()],
- compilation_options: Default::default(),
- },
- fragment: Some(wgpu::FragmentState {
- module: &shader,
- entry_point: Some("fs_main"),
- targets: &[Some(wgpu::ColorTargetState {
- format: config.format,
- blend: Some(wgpu::BlendState::ALPHA_BLENDING),
- write_mask: wgpu::ColorWrites::ALL,
- })],
- compilation_options: Default::default(),
- }),
- primitive: wgpu::PrimitiveState {
- topology: wgpu::PrimitiveTopology::TriangleList,
- strip_index_format: None,
- front_face: wgpu::FrontFace::Ccw,
- cull_mode: None,
- polygon_mode: wgpu::PolygonMode::Fill,
- unclipped_depth: false,
- conservative: false,
- },
- depth_stencil: None,
- multisample: wgpu::MultisampleState { count: 1, mask: !0, alpha_to_coverage_enabled: false },
- multiview: None,
- cache: None,
- });
-
- // 3D pipeline
- let shader_3d = device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some("Shader 3D"),
- source: wgpu::ShaderSource::Wgsl(include_str!("shader_3d.wgsl").into()),
- });
-
- let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("Uniform Buffer"),
- size: 80,
- usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
-
- let bind_group_layout_3d = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
- label: Some("3D Bind Group Layout"),
- entries: &[wgpu::BindGroupLayoutEntry {
- binding: 0,
- visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
- ty: wgpu::BindingType::Buffer {
- ty: wgpu::BufferBindingType::Uniform,
- has_dynamic_offset: false,
- min_binding_size: wgpu::BufferSize::new(80),
- },
- count: None,
- }],
- });
-
- let pipeline_layout_3d = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
- label: Some("3D Pipeline Layout"),
- bind_group_layouts: &[&bind_group_layout_3d],
- push_constant_ranges: &[],
- });
-
- let bind_group_3d = device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("3D Bind Group"),
- layout: &bind_group_layout_3d,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
- buffer: &uniform_buffer,
- offset: 0,
- size: wgpu::BufferSize::new(80),
- }),
- }],
- });
-
- let uniform_buffer_grid = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("Grid Uniform Buffer"),
- size: 80,
- usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
-
- let bind_group_grid = device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("Grid 3D Bind Group"),
- layout: &bind_group_layout_3d,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
- buffer: &uniform_buffer_grid,
- offset: 0,
- size: wgpu::BufferSize::new(80),
- }),
- }],
- });
-
- let uniform_buffer_pivot = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("Pivot Uniform Buffer"),
- size: 80,
- usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
-
- let bind_group_pivot = device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("Pivot Bind Group"),
- layout: &bind_group_layout_3d,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
- buffer: &uniform_buffer_pivot,
- offset: 0,
- size: wgpu::BufferSize::new(80),
- }),
- }],
- });
-
- let vertex_buffer_3d = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
- label: Some("Cube Vertex Buffer"),
- contents: bytemuck::cast_slice(&cube_vertices()),
- usage: wgpu::BufferUsages::VERTEX,
- });
-
- let vertex_buffer_spheres = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("Sphere Node Vertex Buffer"),
- size: 1,
- usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
-
+ let corner_radius = cce_ui::color::backplate_corner_radius() * scale as f32;
+ let mut renderer = unsafe {
+ crate::vk::VkRenderer::new(display_ptr, surface_ptr, pw, ph, corner_radius)
+ };
+ // Bundled fonts only (the designer's UI uses bundled families).
+ let font_system = cce_ui::create_font_system();
+ let swash_cache = glyphon::SwashCache::new();
+
+ // Static 3D meshes; the spheres mesh starts empty and is rebuilt from
+ // the node graph (rebuild_scene_geometry).
+ let cube_verts = cube_vertices();
+ let mesh_cube = renderer.create_mesh(bytemuck::cast_slice(&cube_verts));
let linear_grid_color = cce_ui::colors::to_linear_rgb(settings.viewport.grid_color);
let grid_verts = grid_vertices(settings.viewport.grid_thickness, linear_grid_color);
- let vertex_count_grid = grid_verts.len() as u32;
- let vertex_buffer_grid = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
- label: Some("Grid Vertex Buffer"),
- contents: bytemuck::cast_slice(&grid_verts),
- usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- });
-
+ let mesh_grid = renderer.create_mesh(bytemuck::cast_slice(&grid_verts));
let origin_verts = origin_vectors_vertices(settings.viewport.origin_size);
- let vertex_count_origin = origin_verts.len() as u32;
- let vertex_buffer_origin = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
- label: Some("Origin Vectors Vertex Buffer"),
- contents: bytemuck::cast_slice(&origin_verts),
- usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- });
-
+ let mesh_origin = renderer.create_mesh(bytemuck::cast_slice(&origin_verts));
let pivot_verts = camera_pivot_vertices(settings.viewport.camera_pivot_size);
- let vertex_count_pivot = pivot_verts.len() as u32;
- let vertex_buffer_pivot = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
- label: Some("Camera Pivot Vertex Buffer"),
- contents: bytemuck::cast_slice(&pivot_verts),
- usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- });
-
+ let mesh_pivot = renderer.create_mesh(bytemuck::cast_slice(&pivot_verts));
let bg_color = cce_ui::colors::to_linear_rgb(settings.viewport.bg_color);
let bg_verts = [
Vertex3D { position: [-1.0, -1.0, 9.99], color: bg_color }, // Bottom-left
Vertex3D { position: [ 1.0, -1.0, 9.99], color: bg_color }, // Bottom-right
Vertex3D { position: [-1.0, 1.0, 9.99], color: bg_color }, // Top-left
-
Vertex3D { position: [ 1.0, -1.0, 9.99], color: bg_color }, // Bottom-right
Vertex3D { position: [ 1.0, 1.0, 9.99], color: bg_color }, // Top-right
Vertex3D { position: [-1.0, 1.0, 9.99], color: bg_color }, // Top-left
];
- let vertex_buffer_viewport_bg = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
- label: Some("Viewport BG Vertex Buffer"),
- contents: bytemuck::cast_slice(&bg_verts),
- usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- });
-
- let pipeline_3d = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
- label: Some("3D Pipeline"),
- layout: Some(&pipeline_layout_3d),
- vertex: wgpu::VertexState {
- module: &shader_3d,
- entry_point: Some("vs_main"),
- buffers: &[Vertex3D::desc()],
- compilation_options: Default::default(),
- },
- fragment: Some(wgpu::FragmentState {
- module: &shader_3d,
- entry_point: Some("fs_main"),
- targets: &[Some(wgpu::ColorTargetState {
- format: config.format,
- blend: Some(wgpu::BlendState::ALPHA_BLENDING),
- write_mask: wgpu::ColorWrites::ALL,
- })],
- compilation_options: Default::default(),
- }),
- primitive: wgpu::PrimitiveState {
- topology: wgpu::PrimitiveTopology::TriangleList,
- strip_index_format: None,
- front_face: wgpu::FrontFace::Ccw,
- cull_mode: Some(wgpu::Face::Back),
- polygon_mode: wgpu::PolygonMode::Fill,
- unclipped_depth: false,
- conservative: false,
- },
- depth_stencil: Some(wgpu::DepthStencilState {
- format: wgpu::TextureFormat::Depth32Float,
- depth_write_enabled: true,
- depth_compare: wgpu::CompareFunction::Less,
- stencil: wgpu::StencilState::default(),
- bias: wgpu::DepthBiasState::default(),
- }),
- multisample: wgpu::MultisampleState { count: 1, mask: !0, alpha_to_coverage_enabled: false },
- multiview: None,
- cache: None,
- });
-
- let cache = Cache::new(device);
-
- let shader_textured = device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some("Textured Shader"),
- source: wgpu::ShaderSource::Wgsl(include_str!("shader_textured.wgsl").into()),
- });
-
- let textured_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
- label: Some("Textured Bind Group Layout"),
- entries: &[
- wgpu::BindGroupLayoutEntry {
- binding: 0,
- visibility: wgpu::ShaderStages::FRAGMENT,
- ty: wgpu::BindingType::Texture {
- multisampled: false,
- view_dimension: wgpu::TextureViewDimension::D2,
- sample_type: wgpu::TextureSampleType::Float { filterable: true },
- },
- count: None,
- },
- wgpu::BindGroupLayoutEntry {
- binding: 1,
- visibility: wgpu::ShaderStages::FRAGMENT,
- ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
- count: None,
- },
- ],
- });
-
- let textured_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
- label: Some("Textured Pipeline Layout"),
- bind_group_layouts: &[&textured_bind_group_layout],
- push_constant_ranges: &[],
- });
-
- let curved_text_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
- label: Some("Textured Render Pipeline"),
- layout: Some(&textured_pipeline_layout),
- vertex: wgpu::VertexState {
- module: &shader_textured,
- entry_point: Some("vs_main"),
- buffers: &[TexturedVertex::desc()],
- compilation_options: Default::default(),
- },
- fragment: Some(wgpu::FragmentState {
- module: &shader_textured,
- entry_point: Some("fs_main"),
- targets: &[Some(wgpu::ColorTargetState {
- format: config.format,
- blend: Some(wgpu::BlendState::ALPHA_BLENDING),
- write_mask: wgpu::ColorWrites::ALL,
- })],
- compilation_options: Default::default(),
- }),
- primitive: wgpu::PrimitiveState {
- topology: wgpu::PrimitiveTopology::TriangleList,
- strip_index_format: None,
- front_face: wgpu::FrontFace::Ccw,
- cull_mode: None,
- unclipped_depth: false,
- polygon_mode: wgpu::PolygonMode::Fill,
- conservative: false,
- },
- depth_stencil: None,
- multisample: wgpu::MultisampleState::default(),
- multiview: None,
- cache: None,
- });
-
- let curved_text_texture = device.create_texture(&wgpu::TextureDescriptor {
- label: Some("Curved Text Texture"),
- size: wgpu::Extent3d {
- width: 1024,
- height: 1024,
- depth_or_array_layers: 1,
- },
- mip_level_count: 1,
- sample_count: 1,
- dimension: wgpu::TextureDimension::D2,
- format: config.format,
- usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
- view_formats: &[],
- });
- let curved_text_texture_view = curved_text_texture.create_view(&wgpu::TextureViewDescriptor::default());
-
- let curved_text_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
- label: Some("Curved Text Sampler"),
- address_mode_u: wgpu::AddressMode::ClampToEdge,
- address_mode_v: wgpu::AddressMode::ClampToEdge,
- address_mode_w: wgpu::AddressMode::ClampToEdge,
- mag_filter: wgpu::FilterMode::Linear,
- min_filter: wgpu::FilterMode::Linear,
- mipmap_filter: wgpu::FilterMode::Nearest,
- ..Default::default()
- });
-
- let curved_text_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("Curved Text Bind Group"),
- layout: &textured_bind_group_layout,
- entries: &[
- wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::TextureView(&curved_text_texture_view),
- },
- wgpu::BindGroupEntry {
- binding: 1,
- resource: wgpu::BindingResource::Sampler(&curved_text_sampler),
- },
- ],
- });
-
- let mut curved_text_atlas = TextAtlas::new(&device, &queue, &cache, config.format);
- let curved_text_renderer = TextRenderer::new(&mut curved_text_atlas, &device, wgpu::MultisampleState::default(), None);
- let mut curved_text_viewport = Viewport::new(&device, &cache);
- curved_text_viewport.update(&queue, Resolution { width: 1024, height: 1024 });
-
- let textured_vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("Textured Vertex Buffer"),
- size: 1,
- usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
-
+ let mesh_viewport_bg = renderer.create_mesh(bytemuck::cast_slice(&bg_verts));
+ let mesh_spheres = renderer.create_mesh(&[]);
let splitter_layout = cce_ui::layout::SplitterLayout::new(sw, SPLITTER_W, MIN_COLUMN);
- let (depth_texture, depth_texture_view) = {
- let tex = device.create_texture(&wgpu::TextureDescriptor {
- label: Some("Depth Texture"),
- size: wgpu::Extent3d { width: pw.max(1), height: ph.max(1), depth_or_array_layers: 1 },
- mip_level_count: 1,
- sample_count: 1,
- dimension: wgpu::TextureDimension::D2,
- format: wgpu::TextureFormat::Depth32Float,
- usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
- view_formats: &[],
- });
- let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
- (tex, view)
- };
-
let templates_root = load_fs_tree();
let node_templates = flatten_node_templates(&templates_root);
@@ -3035,13 +2570,6 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
positions.resize_with(WIDGET_COUNT, || (0.0, 0.0, 0.0, 0.0));
- let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("Vertex Buffer"),
- size: 1,
- usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
-
let mut shortcut_manager = ShortcutManager::new();
shortcut_manager.register("Ctrl+g", Action::ToggleGrid).unwrap();
shortcut_manager.register("Ctrl+e", Action::ToggleCube).unwrap();
@@ -3054,34 +2582,17 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
let mut state = Self {
window,
wl_surface,
- wgpu_adapter,
- render_pipeline,
- vertex_buffer,
- vertex_count: 0,
+ renderer,
+ font_system,
+ swash_cache,
vertex_data: Vec::with_capacity(4096),
- pipeline_3d,
- bind_group_3d,
- bind_group_layout_3d,
- uniform_buffer,
- bind_group_grid,
- uniform_buffer_grid,
- bind_group_pivot,
- uniform_buffer_pivot,
- vertex_buffer_3d,
- vertex_buffer_viewport_bg,
- vertex_count_3d: cube_vertices().len() as u32,
- vertex_buffer_spheres,
+ mesh_cube,
+ mesh_viewport_bg,
+ mesh_spheres,
+ mesh_grid,
+ mesh_origin,
+ mesh_pivot,
vertex_count_spheres: 0,
- vertex_buffer_grid,
- vertex_count_grid,
- depth_texture,
- depth_texture_view,
- backdrop_texture,
- backdrop_texture_view,
- backdrop_sampler,
- backdrop_bind_group_layout,
- backdrop_bind_group,
- window_info_buffer,
node_color: {
let nc = cce_ui::color::graph_node_color();
[nc[0], nc[1], nc[2]]
@@ -3089,10 +2600,6 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
grid_color: settings.viewport.grid_color,
cell_color: cce_ui::color::graph_cell_color(),
gap_color: cce_ui::color::graph_gap_color(),
- vertex_buffer_origin,
- vertex_count_origin,
- vertex_buffer_pivot,
- vertex_count_pivot,
origin_size: settings.viewport.origin_size,
camera_pivot_size: settings.viewport.camera_pivot_size,
fs_root: fs_root.clone(),
@@ -3111,17 +2618,6 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
node_palette_query: String::new(),
node_palette_filtered: Vec::new(),
node_palette_selected: 0,
- curved_text_texture,
- curved_text_texture_view,
- curved_text_sampler,
- curved_text_bind_group,
- curved_text_pipeline,
- curved_text_atlas,
- curved_text_renderer,
- curved_text_viewport,
- textured_vertex_buffer,
- textured_vertex_count: 0,
-
drag_widget: None,
focused_widget: None,
cursor_x: 0.0,
@@ -4122,42 +3618,9 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
self.physical_height = height;
self.width = width as f32 / self.scale as f32;
self.height = height as f32 / self.scale as f32;
- self.wgpu_adapter.resize(width, height);
-
- let (tex, view) = self.create_depth_texture();
- self.depth_texture = tex;
- self.depth_texture_view = view;
-
- let (b_tex, b_view) = self.create_backdrop_texture();
- self.backdrop_texture = b_tex;
- self.backdrop_texture_view = b_view;
-
- let window_info_data = [
- width as f32,
- height as f32,
- cce_ui::color::backplate_corner_radius() * self.scale as f32,
- 0.0,
- ];
- self.wgpu_adapter.queue.write_buffer(&self.window_info_buffer, 0, bytemuck::cast_slice(&window_info_data));
-
- self.backdrop_bind_group = self.wgpu_adapter.device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("Backdrop Bind Group"),
- layout: &self.backdrop_bind_group_layout,
- entries: &[
- wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::TextureView(&self.backdrop_texture_view),
- },
- wgpu::BindGroupEntry {
- binding: 1,
- resource: wgpu::BindingResource::Sampler(&self.backdrop_sampler),
- },
- wgpu::BindGroupEntry {
- binding: 2,
- resource: self.window_info_buffer.as_entire_binding(),
- },
- ],
- });
+ self.renderer
+ .set_corner_radius(cce_ui::color::backplate_corner_radius() * self.scale as f32);
+ self.renderer.resize(width, height);
if old_width > 0.0 {
let r = self.width / old_width;
@@ -5573,22 +5036,9 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
self.prepare_text();
- let output = match self.wgpu_adapter.surface.get_current_texture() {
- Ok(t) => t,
- Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
- self.wgpu_adapter.surface.configure(&self.wgpu_adapter.device, &self.wgpu_adapter.config);
- return false;
- }
- Err(wgpu::SurfaceError::Timeout) => return false,
- Err(e) => { eprintln!("Surface error: {e:?}"); return false; }
- };
-
- let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
- let mut encoder = self.wgpu_adapter.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
- label: Some("Encoder"),
- });
-
- // 3D canvas render pass (background layer)
+ // 3D canvas: stage the scene into the renderer's backdrop when the
+ // viewport is visible and its inputs changed; unstaged frames reuse the
+ // previous backdrop (the renderer's equivalent of the old cached pass).
if !self.is_detached_network && self.show_viewport {
let cx_logical = 0.0;
let cy_logical = HEADER_H;
@@ -5608,7 +5058,6 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
}
if cw > 0 && ch > 0 {
-
let mut camera_pos = Vec3::new(2.5, 1.8, 2.5);
let mut rx = 0.0f32;
let mut ry = 0.0f32;
@@ -5682,26 +5131,7 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
if viewport_changed {
let aspect = cw as f32 / ch as f32;
let (proj, view_mat, model) = self.viewport().get_matrices(aspect, Some(camera_pos), Some(Vec3::new(rx, ry, rz)), Some(pivot));
- let mvp = proj * view_mat * model;
- let window_size = [self.physical_width as f32, self.physical_height as f32];
- let window_radius = cce_ui::color::backplate_corner_radius() * self.scale as f32;
-
- let uniforms = ViewportUniforms {
- mvp: mvp.to_cols_array_2d(),
- window_size,
- window_radius,
- _padding: 0.0,
- };
- self.wgpu_adapter.queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
-
- let mvp_grid = proj * view_mat * model;
- let uniforms_grid = ViewportUniforms {
- mvp: mvp_grid.to_cols_array_2d(),
- window_size,
- window_radius,
- _padding: 0.0,
- };
- self.wgpu_adapter.queue.write_buffer(&self.uniform_buffer_grid, 0, bytemuck::cast_slice(&[uniforms_grid]));
+ let mvp = (proj * view_mat * model).to_cols_array_2d();
let cam_angle_y = camera_pos.x.atan2(camera_pos.z);
let rot_angle = if self.active_camera != "Default Camera" {
@@ -5712,79 +5142,27 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
self.viewport().rotation_y + cam_angle_y
};
let model_pivot = Mat4::from_translation(pivot) * Mat4::from_rotation_y(rot_angle);
- let mvp_pivot = proj * view_mat * model_pivot;
- let uniforms_pivot = ViewportUniforms {
- mvp: mvp_pivot.to_cols_array_2d(),
- window_size,
- window_radius,
- _padding: 0.0,
- };
- self.wgpu_adapter.queue.write_buffer(&self.uniform_buffer_pivot, 0, bytemuck::cast_slice(&[uniforms_pivot]));
-
- let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("3D Render Pass"),
- color_attachments: &[Some(wgpu::RenderPassColorAttachment {
- view: &self.backdrop_texture_view,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Clear(wgpu::Color {
- r: 0.0,
- g: 0.0,
- b: 0.0,
- a: 0.0,
- }),
- store: wgpu::StoreOp::Store,
- },
- })],
- depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
- view: &self.depth_texture_view,
- depth_ops: Some(wgpu::Operations {
- load: wgpu::LoadOp::Clear(1.0),
- store: wgpu::StoreOp::Discard,
- }),
- stencil_ops: None,
- }),
- timestamp_writes: None,
- occlusion_query_set: None,
- });
-
- pass.set_scissor_rect(sx, sy, cw, ch);
- pass.set_pipeline(&self.pipeline_3d);
-
- // Draw viewport background quad (rounds corners via shader)
- pass.set_bind_group(0, &self.bind_group_3d, &[]);
- pass.set_vertex_buffer(0, self.vertex_buffer_viewport_bg.slice(..));
- pass.draw(0..6, 0..1);
+ let mvp_pivot = (proj * view_mat * model_pivot).to_cols_array_2d();
+ // Same draw order as the wgpu pass: bg quad, grid, origin,
+ // pivot, cube, spheres.
+ let mut draws = vec![SceneDraw { mesh: self.mesh_viewport_bg, mvp }];
if self.viewport().show_grid {
- pass.set_bind_group(0, &self.bind_group_grid, &[]);
- pass.set_vertex_buffer(0, self.vertex_buffer_grid.slice(..));
- pass.draw(0..self.vertex_count_grid, 0..1);
+ draws.push(SceneDraw { mesh: self.mesh_grid, mvp });
}
-
if self.viewport().show_origin {
- pass.set_bind_group(0, &self.bind_group_3d, &[]);
- pass.set_vertex_buffer(0, self.vertex_buffer_origin.slice(..));
- pass.draw(0..self.vertex_count_origin, 0..1);
+ draws.push(SceneDraw { mesh: self.mesh_origin, mvp });
}
-
if self.viewport().show_camera_pivot {
- pass.set_bind_group(0, &self.bind_group_pivot, &[]);
- pass.set_vertex_buffer(0, self.vertex_buffer_pivot.slice(..));
- pass.draw(0..self.vertex_count_pivot, 0..1);
+ draws.push(SceneDraw { mesh: self.mesh_pivot, mvp: mvp_pivot });
}
-
- pass.set_bind_group(0, &self.bind_group_3d, &[]);
-
if self.viewport().show_cube {
- pass.set_vertex_buffer(0, self.vertex_buffer_3d.slice(..));
- pass.draw(0..self.vertex_count_3d, 0..1);
+ draws.push(SceneDraw { mesh: self.mesh_cube, mvp });
}
-
if self.vertex_count_spheres > 0 {
- pass.set_vertex_buffer(0, self.vertex_buffer_spheres.slice(..));
- pass.draw(0..self.vertex_count_spheres, 0..1);
+ draws.push(SceneDraw { mesh: self.mesh_spheres, mvp });
}
+ self.renderer.stage_scene((sx, sy, cw, ch), draws);
// Update viewport cache
self.last_viewport_camera_pos = camera_pos;
@@ -5806,112 +5184,23 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec
self.last_viewport_show_viewport = self.show_viewport;
self.viewport_dirty = false;
}
- } else {
- let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("Backdrop Clear Pass"),
- color_attachments: &[Some(wgpu::RenderPassColorAttachment {
- view: &self.backdrop_texture_view,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Clear({
- let linear_bg = cce_ui::colors::to_linear_rgb(self.viewport().bg_color);
- wgpu::Color {
- r: linear_bg[0] as f64,
- g: linear_bg[1] as f64,
- b: linear_bg[2] as f64,
- a: 1.0,
- }
- }),
- store: wgpu::StoreOp::Store,
- },
- })],
- depth_stencil_attachment: None,
- timestamp_writes: None,
- occlusion_query_set: None,
- });
+ } else if self.viewport_dirty {
+ // Zero-area pane: clear the backdrop once.
+ self.renderer
+ .stage_scene((0, 0, self.physical_width, self.physical_height), Vec::new());
+ self.last_viewport_show_viewport = false;
+ self.viewport_dirty = false;
}
- } else {
- let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("Backdrop Clear Pass"),
- color_attachments: &[Some(wgpu::RenderPassColorAttachment {
- view: &self.backdrop_texture_view,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
- store: wgpu::StoreOp::Store,
- },
- })],
- depth_stencil_attachment: None,
- timestamp_writes: None,
- occlusion_query_set: None,
- });
- }
-
- // Copy backdrop to output swapchain texture
- if !self.is_detached_network {
- encoder.copy_texture_to_texture(
- wgpu::TexelCopyTextureInfo {
- texture: &self.backdrop_texture,
- mip_level: 0,
- origin: wgpu::Origin3d::ZERO,
- aspect: wgpu::TextureAspect::All,
- },
- wgpu::TexelCopyTextureInfo {
- texture: &output.texture,
- mip_level: 0,
- origin: wgpu::Origin3d::ZERO,
- aspect: wgpu::TextureAspect::All,
- },
- wgpu::Extent3d {
- width: self.physical_width,
- height: self.physical_height,
- depth_or_array_layers: 1,
- },
- );
- }
-
- // UI render pass (foreground layer)
- {
- let load_op = if self.is_detached_network {
- wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 })
- } else {
- wgpu::LoadOp::Load
- };
-
- let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("UI Render Pass"),
- color_attachments: &[Some(wgpu::RenderPassColorAttachment {
- view: &view,
- resolve_target: None,
- ops: wgpu::Operations {
- load: load_op,
- store: wgpu::StoreOp::Store,
- },
- })],
- depth_stencil_attachment: None,
- timestamp_writes: None,
- occlusion_query_set: None,
- });
-
- pass.set_pipeline(&self.render_pipeline);
- pass.set_bind_group(0, &self.backdrop_bind_group, &[]);
- pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
- pass.draw(0..self.vertex_count, 0..1);
-
- if self.textured_vertex_count > 0 {
- // eprintln!("DEBUG_RENDER: textured_vertex_count={}", self.textured_vertex_count);
- pass.set_pipeline(&self.curved_text_pipeline);
- pass.set_bind_group(0, &self.curved_text_bind_group, &[]);
- pass.set_vertex_buffer(0, self.textured_vertex_buffer.slice(..));
- pass.draw(0..self.textured_vertex_count, 0..1);
- }
-
- self.wgpu_adapter.text_renderer.render(&self.wgpu_adapter.text_atlas, &self.wgpu_adapter.text_viewport, &mut pass).unwrap();
-
+ } else if !self.is_detached_network && self.viewport_dirty {
+ // Viewport hidden: clear the backdrop (the old path's clear pass),
+ // and force a re-stage when it comes back.
+ self.renderer
+ .stage_scene((0, 0, self.physical_width, self.physical_height), Vec::new());
+ self.last_viewport_show_viewport = false;
+ self.viewport_dirty = false;
}
- self.wgpu_adapter.queue.submit(std::iter::once(encoder.finish()));
- output.present();
+ let _ = self.renderer.draw_frame(&self.vertex_data);
tick_changed || panned
}
}
diff --git a/src/geometry.rs b/src/geometry.rs
index 908c44e..22fdd12 100644
--- a/src/geometry.rs
+++ b/src/geometry.rs
@@ -111,18 +111,6 @@ pub struct Vertex3D {
pub color: [f32; 3],
}
-impl Vertex3D {
- const ATTRIBS: [wgpu::VertexAttribute; 2] = wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3];
-
- pub fn desc() -> wgpu::VertexBufferLayout<'static> {
- wgpu::VertexBufferLayout {
- array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
- step_mode: wgpu::VertexStepMode::Vertex,
- attributes: &Self::ATTRIBS,
- }
- }
-}
-
pub fn cube_vertices() -> Vec<Vertex3D> {
let s = 0.5;
let data: &[([f32; 3], [f32; 3])] = &[
diff --git a/src/graphics.rs b/src/graphics.rs
deleted file mode 100644
index e467a5a..0000000
--- a/src/graphics.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-
-#[repr(C)]
-#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
-pub struct TexturedVertex {
- pub position: [f32; 2],
- pub tex_coords: [f32; 2],
- pub color: [f32; 4],
- pub clip_circle: [f32; 3],
-}
-
-impl TexturedVertex {
- const ATTRIBS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
- 0 => Float32x2,
- 1 => Float32x2,
- 2 => Float32x4,
- 3 => Float32x3,
- ];
-
- pub fn desc() -> wgpu::VertexBufferLayout<'static> {
- wgpu::VertexBufferLayout {
- array_stride: std::mem::size_of::<TexturedVertex>() as wgpu::BufferAddress,
- step_mode: wgpu::VertexStepMode::Vertex,
- attributes: &Self::ATTRIBS,
- }
- }
-}
diff --git a/src/main.rs b/src/main.rs
index e80f7d5..964ba9e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -42,7 +42,7 @@ use glam::{Mat4, Vec3};
pub mod app;
pub mod viewport_3d;
-pub mod graphics;
+pub mod vk;
pub mod api;
pub mod window;
pub mod geometry;
@@ -102,7 +102,7 @@ fn main() {
((1280.0 * scale) as u32, (800.0 * scale) as u32)
};
- let state = pollster::block_on(State::new(
+ let state = State::new(
&conn,
&qh,
&app.compositor_state,
@@ -110,7 +110,7 @@ fn main() {
pw, ph,
scale,
is_detached_network,
- ));
+ );
app.window = Some(state.window.clone());
app.surface = Some(state.wl_surface.clone());
diff --git a/src/render.rs b/src/render.rs
index c0a4610..3390111 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -1,5 +1,4 @@
-use glyphon::{Buffer, Resolution, TextArea, TextBounds};
use cce_ui::widget::TextLabel;
use cce_ui::colors;
use cce_ui::widget::WidgetHost;
@@ -14,11 +13,11 @@ use crate::app::{
LEFT_MENUBAR_IDX, PARAM_MENUBAR_IDX, NETWORK_PANEL_IDX,
push_circle_vertices, push_circle_border_vertices,
};
-use crate::graphics::TexturedVertex;
use cce_ui::engine::Vertex;
use crate::geometry::{
network_sphere_vertices_with_errors,
};
+use crate::vk::TextSpan;
use cce_ui::engine::{
push_widget_vertices, push_extra_quad_vertices,
push_extra_quad_vertices_clipped, push_arc_background_vertices,
@@ -26,36 +25,6 @@ use cce_ui::engine::{
};
impl State {
- pub(crate) fn create_depth_texture(&self) -> (wgpu::Texture, wgpu::TextureView) {
- let tex = self.wgpu_adapter.device.create_texture(&wgpu::TextureDescriptor {
- label: Some("Depth Texture"),
- size: wgpu::Extent3d { width: self.physical_width.max(1), height: self.physical_height.max(1), depth_or_array_layers: 1 },
- mip_level_count: 1,
- sample_count: 1,
- dimension: wgpu::TextureDimension::D2,
- format: wgpu::TextureFormat::Depth32Float,
- usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
- view_formats: &[],
- });
- let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
- (tex, view)
- }
-
- pub(crate) fn create_backdrop_texture(&self) -> (wgpu::Texture, wgpu::TextureView) {
- let tex = self.wgpu_adapter.device.create_texture(&wgpu::TextureDescriptor {
- label: Some("Backdrop Texture"),
- size: wgpu::Extent3d { width: self.physical_width.max(1), height: self.physical_height.max(1), depth_or_array_layers: 1 },
- mip_level_count: 1,
- sample_count: 1,
- dimension: wgpu::TextureDimension::D2,
- format: self.wgpu_adapter.config.format,
- usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC,
- view_formats: &[],
- });
- let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
- (tex, view)
- }
-
pub(crate) fn collect_vertices(&mut self, verts: &mut Vec<Vertex>) {
verts.clear();
let sw = self.width;
@@ -243,7 +212,7 @@ impl State {
let cx = self.circular_network_layout.x;
let cy = self.circular_network_layout.y;
let r = self.circular_network_layout.r;
-
+
let bg_color = w.color();
push_arc_background_vertices(
cx, cy, r,
@@ -256,7 +225,7 @@ impl State {
active_clip_circle,
verts,
);
-
+
let border_color = [0.22, 0.22, 0.28, 0.90 * self.network_opacity];
push_arc_background_vertices(
cx, cy, r - MENUBAR_H,
@@ -269,7 +238,7 @@ impl State {
active_clip_circle,
verts,
);
-
+
for (qx, qy, qw, qh, qc) in w.extra_quads() {
push_extra_quad_vertices(w, qx, qy, qw, qh, sw, sh, qc, active_clip_circle, verts);
}
@@ -396,22 +365,12 @@ impl State {
}
}
+ /// Rebuild the 2D vertex stream. The actual GPU upload happens in
+ /// `VkRenderer::draw_frame`, which consumes `vertex_data` every frame.
pub(crate) fn upload_vertices(&mut self) {
self.text_dirty = true;
let mut verts = std::mem::take(&mut self.vertex_data);
self.collect_vertices(&mut verts);
- self.vertex_count = verts.len() as u32;
- let data = bytemuck::cast_slice(&verts);
- let needed = data.len() as wgpu::BufferAddress;
- if needed > self.vertex_buffer.size() {
- self.vertex_buffer = self.wgpu_adapter.device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("Vertex Buffer"),
- size: needed,
- usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
- }
- self.wgpu_adapter.queue.write_buffer(&self.vertex_buffer, 0, data);
self.vertex_data = verts;
}
@@ -441,17 +400,7 @@ impl State {
let verts = geom.to_vertex3d_vec();
self.vertex_count_spheres = verts.len() as u32;
- let data = bytemuck::cast_slice(&verts);
- let needed = data.len() as wgpu::BufferAddress;
- if needed > self.vertex_buffer_spheres.size() {
- self.vertex_buffer_spheres = self.wgpu_adapter.device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("Vertex Buffer Spheres"),
- size: needed,
- usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
- }
- self.wgpu_adapter.queue.write_buffer(&self.vertex_buffer_spheres, 0, data);
+ self.renderer.update_mesh(self.mesh_spheres, bytemuck::cast_slice(&verts));
self.viewport_dirty = true;
}
@@ -501,11 +450,11 @@ impl State {
}
self.text_dirty = false;
- // 1. Prepare text on all widgets using self.wgpu_adapter.font_system
+ // 1. Prepare text on all widgets using self.font_system
for i in 0..WIDGET_COUNT {
let is_menubar = i == HEADER_IDX || i == LEFT_MENUBAR_IDX || i == RIGHT_MENUBAR_IDX || i == PARAM_MENUBAR_IDX || i == SPREADSHEET_MENUBAR_IDX;
if !is_menubar {
- self.slots.get_dyn_mut(i).prepare_text(&mut self.wgpu_adapter.font_system);
+ self.slots.get_dyn_mut(i).prepare_text(&mut self.font_system);
}
}
@@ -516,32 +465,22 @@ impl State {
let network_circle_x = self.circular_network_layout.x;
let network_circle_y = self.circular_network_layout.y;
let network_circle_radius = self.circular_network_layout.r;
+ let network_opacity = self.network_opacity;
+ let focused_widget = self.focused_widget;
+ let _ = sw;
+ let _ = sh;
let Self {
- ref mut wgpu_adapter,
+ ref mut renderer,
+ ref mut font_system,
+ ref mut swash_cache,
physical_width, physical_height, scale,
ref slots,
- ref curved_text_texture,
- ref mut curved_text_atlas,
- ref mut curved_text_renderer,
- ref mut curved_text_viewport,
- ref mut textured_vertex_buffer,
- ref mut textured_vertex_count,
ref mut text_buffer_cache,
ref ui_context,
+ ref positions,
..
- } = self;
-
- let cce_ui::backend::WgpuAdapter {
- ref device,
- ref queue,
- ref mut font_system,
- ref mut text_atlas,
- ref mut text_viewport,
- ref mut text_renderer,
- ref mut swash_cache,
- ..
- } = wgpu_adapter;
+ } = *self;
// Clear cache if too large to prevent unbounded memory growth
if text_buffer_cache.len() > 500 {
@@ -617,7 +556,7 @@ impl State {
if is_menubar {
continue;
}
- if self.focused_widget == Some(i) || i == PARAM_IDX {
+ if focused_widget == Some(i) || i == PARAM_IDX {
let mut popover_pc = cce_ui::layout::PopoverCollector::new();
w.render_popover(&mut popover_pc);
for (t, size, _x, _y, _tc, font_opt, _bounds) in popover_pc.texts {
@@ -630,44 +569,12 @@ impl State {
}
}
- let viewport = Resolution { width: *physical_width, height: *physical_height };
- text_viewport.update(queue, viewport);
- let s = *scale as f32;
-
- let mut popovers = Vec::new();
- fn collect_popovers(
- w: &dyn WidgetHost,
- popovers: &mut Vec<(f32, f32, f32, f32)>,
- ctx: &cce_ui::context::UiContext,
- ) {
- if let Some(rect) = w.popover_rect() {
- popovers.push(rect);
- }
- for child_ptr in ctx.tree.children_ptrs(w.base().id()) {
- unsafe {
- if let Some(child) = child_ptr.as_ref() {
- collect_popovers(child, popovers, ctx);
- }
- }
- }
- }
+ let s = scale as f32;
- for i in 0..WIDGET_COUNT {
- let w = slots.get_dyn(i);
- if w.visible() {
- collect_popovers(w, &mut popovers, ui_context);
- }
- }
-
- let mut areas: Vec<TextArea> = Vec::new();
-
- // Temporary storage for legacy buffers generated during this frame
- let mut legacy_buffers: Vec<&Buffer> = Vec::new();
- let mut legacy_labels: Vec<TextLabel> = Vec::new();
- let mut legacy_bounds: Vec<TextBounds> = Vec::new();
- let mut legacy_is_network: Vec<bool> = Vec::new();
-
- let mut curved_labels = Vec::new();
+ // Build the frame's spans against the shaped cache. From here the cache
+ // is only read (`get`), so the borrows stay immutable for the spans.
+ let text_buffer_cache = &*text_buffer_cache;
+ let mut spans: Vec<TextSpan> = Vec::new();
for i in 0..WIDGET_COUNT {
let w = slots.get_dyn(i);
@@ -681,30 +588,26 @@ impl State {
let is_node = i == CONTENT_IDX;
let is_network_part = i == CONTENT_IDX || i == LEFT_MENUBAR_IDX || i == BREADCRUMB_IDX || i == NETWORK_PANEL_IDX;
+ // Widget-level clip bounds (physical px), matching the old TextBounds.
let bounds = if is_node {
if circular_network_pane {
- TextBounds {
- left: ((network_circle_x - network_circle_radius) * s) as i32,
- top: ((network_circle_y - network_circle_radius) * s) as i32,
- right: ((network_circle_x + network_circle_radius) * s) as i32,
- bottom: (((network_circle_y + network_circle_radius) * s) as i32).max(0),
- }
+ [
+ ((network_circle_x - network_circle_radius) * s) as i32,
+ ((network_circle_y - network_circle_radius) * s) as i32,
+ ((network_circle_x + network_circle_radius) * s) as i32,
+ (((network_circle_y + network_circle_radius) * s) as i32).max(0),
+ ]
} else {
- let (gx, gy, gw, gh) = self.positions[CONTENT_IDX];
- TextBounds {
- left: (gx * s) as i32,
- top: (gy * s) as i32,
- right: ((gx + gw) * s) as i32,
- bottom: (((gy + gh) * s) as i32).max(0),
- }
+ let (gx, gy, gw, gh) = positions[CONTENT_IDX];
+ [
+ (gx * s) as i32,
+ (gy * s) as i32,
+ ((gx + gw) * s) as i32,
+ (((gy + gh) * s) as i32).max(0),
+ ]
}
} else {
- TextBounds {
- left: 0,
- top: 0,
- right: *physical_width as i32,
- bottom: *physical_height as i32,
- }
+ [0, 0, physical_width as i32, physical_height as i32]
};
for (text, x, y, font_size, color, font, label_bounds) in &widget_text[i] {
@@ -719,7 +622,38 @@ impl State {
}
if is_curved {
- curved_labels.push(TextLabel { text: text.clone(), x: *x, y: *y, font_size: *font_size, color: *color });
+ // Curved rim label: rotate the glyph quad about its center to
+ // face outward, clipped to the circular pane — the ash port of
+ // the old render-to-texture + TexturedVertex path.
+ let font_size = 12.0;
+ let tw = TextLabel::estimate_width(text, font_size);
+ let th = font_size;
+ let cx = x + tw / 2.0;
+ let cy = y + th / 2.0;
+ let theta = (cy - network_circle_y).atan2(cx - network_circle_x);
+ let angle = theta + std::f32::consts::FRAC_PI_2;
+ let key = (text.clone(), (font_size * 100.0) as u32, None);
+ if let Some(buf) = text_buffer_cache.get(&key) {
+ spans.push(TextSpan {
+ buffer: buf,
+ left: (x * s).round(),
+ top: (y * s).round(),
+ scale: s,
+ bounds: None,
+ default_color: [
+ color[0] as f32 / 255.0,
+ color[1] as f32 / 255.0,
+ color[2] as f32 / 255.0,
+ 1.0,
+ ],
+ rotation: Some((angle, cx * s, cy * s)),
+ clip_circle: [
+ network_circle_x * s,
+ network_circle_y * s,
+ network_circle_radius * s,
+ ],
+ });
+ }
} else {
if circular_network_pane && is_network_part && i != LEFT_MENUBAR_IDX {
let dx = x - network_circle_x;
@@ -735,43 +669,36 @@ impl State {
let pt = (t * s).round() as i32;
let pr = (r * s).round() as i32;
let pb = (b * s).round() as i32;
- item_bounds = TextBounds {
- left: item_bounds.left.max(pl),
- top: item_bounds.top.max(pt),
- right: item_bounds.right.min(pr),
- bottom: item_bounds.bottom.min(pb),
- };
+ item_bounds = [
+ item_bounds[0].max(pl),
+ item_bounds[1].max(pt),
+ item_bounds[2].min(pr),
+ item_bounds[3].min(pb),
+ ];
}
let key = (text.clone(), (font_size * 100.0) as u32, font.clone());
- let buf_ref = text_buffer_cache.get(&key).unwrap();
-
- legacy_buffers.push(buf_ref);
- legacy_labels.push(TextLabel { text: text.clone(), x: *x, y: *y, font_size: *font_size, color: *color });
- legacy_bounds.push(item_bounds);
- legacy_is_network.push(is_network_part);
+ let Some(buf) = text_buffer_cache.get(&key) else { continue };
+ let alpha = if is_network_part { network_opacity.clamp(0.0, 1.0) } else { 1.0 };
+ spans.push(TextSpan {
+ buffer: buf,
+ left: (x * s).round(),
+ top: (y * s).round(),
+ scale: s,
+ bounds: Some(item_bounds),
+ default_color: [
+ color[0] as f32 / 255.0,
+ color[1] as f32 / 255.0,
+ color[2] as f32 / 255.0,
+ alpha,
+ ],
+ rotation: None,
+ clip_circle: [0.0; 3],
+ });
}
}
}
- // Add the legacy buffered items
- for (((buf, label), bounds), is_net) in legacy_buffers.iter()
- .zip(legacy_labels.iter())
- .zip(legacy_bounds.iter())
- .zip(legacy_is_network.iter())
- {
- let alpha = if *is_net { (self.network_opacity * 255.0).clamp(0.0, 255.0) as u8 } else { 255 };
- areas.push(TextArea {
- buffer: *buf,
- left: (label.x * s).round(),
- top: (label.y * s).round(),
- scale: s,
- bounds: *bounds,
- default_color: glyphon::Color::rgba(label.color[0], label.color[1], label.color[2], alpha),
- custom_glyphs: &[],
- });
- }
-
- // Add popover text areas
+ // Popover text spans
for i in 0..WIDGET_COUNT {
let w = slots.get_dyn(i);
if !w.visible() {
@@ -781,230 +708,41 @@ impl State {
if is_menubar {
continue;
}
- if self.focused_widget == Some(i) || i == PARAM_IDX {
+ if focused_widget == Some(i) || i == PARAM_IDX {
let mut popover_pc = cce_ui::layout::PopoverCollector::new();
w.render_popover(&mut popover_pc);
for (t, size, x, y, tc, font_opt, label_bounds) in popover_pc.texts {
let key = (t.clone(), (size * 100.0) as u32, font_opt.clone());
- if let Some(buf_ref) = text_buffer_cache.get(&key) {
- let mut item_bounds = TextBounds {
- left: 0,
- top: 0,
- right: *physical_width as i32,
- bottom: *physical_height as i32,
- };
+ if let Some(buf) = text_buffer_cache.get(&key) {
+ let mut item_bounds = [0, 0, physical_width as i32, physical_height as i32];
if let Some([l, t_bound, r, b]) = label_bounds {
let pl = (l * s).round() as i32;
let pt = (t_bound * s).round() as i32;
let pr = (r * s).round() as i32;
let pb = (b * s).round() as i32;
- item_bounds = TextBounds {
- left: item_bounds.left.max(pl),
- top: item_bounds.top.max(pt),
- right: item_bounds.right.min(pr),
- bottom: item_bounds.bottom.min(pb),
- };
+ item_bounds = [
+ item_bounds[0].max(pl),
+ item_bounds[1].max(pt),
+ item_bounds[2].min(pr),
+ item_bounds[3].min(pb),
+ ];
}
- areas.push(TextArea {
- buffer: buf_ref,
+ spans.push(TextSpan {
+ buffer: buf,
left: (x * s).round(),
top: (y * s).round(),
scale: s,
- bounds: item_bounds,
- default_color: glyphon::Color::rgb(
- (tc[0] * 255.0) as u8,
- (tc[1] * 255.0) as u8,
- (tc[2] * 255.0) as u8,
- ),
- custom_glyphs: &[],
+ bounds: Some(item_bounds),
+ default_color: [tc[0], tc[1], tc[2], 1.0],
+ rotation: None,
+ clip_circle: [0.0; 3],
});
}
}
}
}
- if !popovers.is_empty() {
- for (idx, area) in areas.iter().enumerate() {
- for run in area.buffer.layout_runs() {
- println!("DEBUG: Area {}, text={:?}, x={}, y={}", idx, run.text, area.left, area.top);
- }
- }
- }
-
- text_renderer.prepare(device, queue, font_system, text_atlas, text_viewport, areas, swash_cache).unwrap();
-
- // Process curved labels
- let mut textured_verts = Vec::new();
-
- if !curved_labels.is_empty() {
- struct CurvedDrawInfo<'a> {
- label: TextLabel,
- tx: f32,
- ty: f32,
- tw: f32,
- th: f32,
- buffer: &'a Buffer,
- }
-
- let mut curved_draws = Vec::new();
- let mut current_x = 4.0;
- let mut current_y = 4.0;
- let font_size = 12.0;
- let row_height = (font_size + 8.0) * s;
-
- for label in curved_labels {
- let char_w = TextLabel::estimate_width(&label.text, font_size);
- let physical_w = char_w * s;
- if current_x + physical_w + 4.0 > 1024.0 {
- current_x = 4.0;
- current_y += row_height;
- }
- let key = (label.text.clone(), (font_size * 100.0) as u32, None);
- let buf = text_buffer_cache.get(&key).unwrap();
- curved_draws.push(CurvedDrawInfo {
- label: label.clone(),
- tx: current_x,
- ty: current_y,
- tw: char_w,
- th: font_size,
- buffer: buf,
- });
- current_x += physical_w + 8.0 * s;
- }
-
- let mut curved_areas = Vec::new();
- for draw in &curved_draws {
- curved_areas.push(TextArea {
- buffer: draw.buffer,
- left: draw.tx.round(),
- top: draw.ty.round(),
- scale: s,
- bounds: TextBounds {
- left: 0,
- top: 0,
- right: 1024,
- bottom: 1024,
- },
- default_color: glyphon::Color::rgb(255, 255, 255),
- custom_glyphs: &[],
- });
- }
-
- curved_text_renderer.prepare(
- device,
- queue,
- font_system,
- curved_text_atlas,
- curved_text_viewport,
- curved_areas,
- swash_cache,
- ).unwrap();
-
- let mut texture_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
- label: Some("Curved Text Texture Encoder"),
- });
- {
- let view_for_pass = curved_text_texture.create_view(&wgpu::TextureViewDescriptor::default());
- let mut pass = texture_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("Curved Text Render Pass"),
- color_attachments: &[Some(wgpu::RenderPassColorAttachment {
- view: &view_for_pass,
- resolve_target: None,
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
- store: wgpu::StoreOp::Store,
- },
- })],
- depth_stencil_attachment: None,
- timestamp_writes: None,
- occlusion_query_set: None,
- });
- curved_text_renderer.render(curved_text_atlas, curved_text_viewport, &mut pass).unwrap();
- }
- queue.submit(std::iter::once(texture_encoder.finish()));
-
- let clip_circle_val = if circular_network_pane {
- [network_circle_x * s, network_circle_y * s, network_circle_radius * s]
- } else {
- [0.0, 0.0, 0.0]
- };
-
- for draw in curved_draws {
- let dx = (draw.label.x + draw.tw / 2.0) - network_circle_x;
- let dy = (draw.label.y + draw.th / 2.0) - network_circle_y;
- let theta = dy.atan2(dx);
- let angle = theta + std::f32::consts::FRAC_PI_2;
-
- let cx = draw.label.x + draw.tw / 2.0;
- let cy = draw.label.y + draw.th / 2.0;
- let w_half = draw.tw / 2.0;
- let h_half = draw.th / 2.0;
-
- let cos_a = angle.cos();
- let sin_a = angle.sin();
-
- let local_pts = [
- [-w_half, -h_half],
- [w_half, -h_half],
- [-w_half, h_half],
- [w_half, h_half],
- ];
-
- let mut screen_pts = [[0.0; 2]; 4];
- for (k, pt) in local_pts.iter().enumerate() {
- let rx = pt[0] * cos_a - pt[1] * sin_a;
- let ry = pt[0] * sin_a + pt[1] * cos_a;
- screen_pts[k] = [cx + rx, cy + ry];
- }
-
- let ndc_pts = screen_pts.map(|pt| [
- (pt[0] / sw) * 2.0 - 1.0,
- 1.0 - (pt[1] / sh) * 2.0,
- ]);
-
- let u0 = draw.tx / 1024.0;
- let v0 = draw.ty / 1024.0;
- let u1 = (draw.tx + draw.tw * s) / 1024.0;
- let v1 = (draw.ty + draw.th * s) / 1024.0;
-
- let c = [
- draw.label.color[0] as f32 / 255.0,
- draw.label.color[1] as f32 / 255.0,
- draw.label.color[2] as f32 / 255.0,
- 1.0,
- ];
-
- let v_tl = TexturedVertex { position: ndc_pts[0], tex_coords: [u0, v0], color: c, clip_circle: clip_circle_val };
- let v_tr = TexturedVertex { position: ndc_pts[1], tex_coords: [u1, v0], color: c, clip_circle: clip_circle_val };
- let v_bl = TexturedVertex { position: ndc_pts[2], tex_coords: [u0, v1], color: c, clip_circle: clip_circle_val };
- let v_br = TexturedVertex { position: ndc_pts[3], tex_coords: [u1, v1], color: c, clip_circle: clip_circle_val };
-
- textured_verts.push(v_tl);
- textured_verts.push(v_tr);
- textured_verts.push(v_bl);
-
- textured_verts.push(v_tr);
- textured_verts.push(v_br);
- textured_verts.push(v_bl);
- }
- }
-
- *textured_vertex_count = textured_verts.len() as u32;
- if *textured_vertex_count > 0 {
- let data = bytemuck::cast_slice(&textured_verts);
- let needed = data.len() as wgpu::BufferAddress;
- if needed > textured_vertex_buffer.size() {
- *textured_vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
- label: Some("Textured Vertex Buffer"),
- size: needed,
- usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- mapped_at_creation: false,
- });
- }
- queue.write_buffer(textured_vertex_buffer, 0, data);
- }
+ renderer.prepare_text(font_system, swash_cache, &spans);
}
}
-
-
diff --git a/src/shader_textured.wgsl b/src/shader_textured.wgsl
deleted file mode 100644
index 24a34f0..0000000
--- a/src/shader_textured.wgsl
+++ /dev/null
@@ -1,41 +0,0 @@
-struct VertexInput {
- @location(0) position: vec2f,
- @location(1) tex_coords: vec2f,
- @location(2) color: vec4f,
- @location(3) clip_circle: vec3f,
-}
-
-struct VertexOutput {
- @builtin(position) clip_position: vec4f,
- @location(0) tex_coords: vec2f,
- @location(1) color: vec4f,
- @location(2) clip_circle: vec3f,
-}
-
-@vertex
-fn vs_main(in: VertexInput) -> VertexOutput {
- var out: VertexOutput;
- out.clip_position = vec4f(in.position, 0.0, 1.0);
- out.tex_coords = in.tex_coords;
- out.color = in.color;
- out.clip_circle = in.clip_circle;
- return out;
-}
-
-@group(0) @binding(0) var t_diffuse: texture_2d<f32>;
-@group(0) @binding(1) var s_diffuse: sampler;
-
-@fragment
-fn fs_main(in: VertexOutput) -> @location(0) vec4f {
- if (in.clip_circle.z > 0.0) {
- let dx = in.clip_position.x - in.clip_circle.x;
- let dy = in.clip_position.y - in.clip_circle.y;
- if (dx * dx + dy * dy > in.clip_circle.z * in.clip_circle.z) {
- discard;
- }
- }
- let tex_color = textureSample(t_diffuse, s_diffuse, in.tex_coords);
- return tex_color * in.color;
-}
-
-
diff --git a/src/vk/glyph.wgsl b/src/vk/glyph.wgsl
index e2eb4a8..8cd26e0 100644
--- a/src/vk/glyph.wgsl
+++ b/src/vk/glyph.wgsl
@@ -9,6 +9,7 @@ struct VertexOutput {
@builtin(position) clip_position: vec4f,
@location(0) uv: vec2f,
@location(1) color: vec4f,
+ @location(2) clip_circle: vec3f,
}
@vertex
@@ -16,15 +17,26 @@ fn vs_main(
@location(0) position: vec2f,
@location(1) uv: vec2f,
@location(2) color: vec4f,
+ @location(3) clip_circle: vec3f,
) -> VertexOutput {
var out: VertexOutput;
out.clip_position = vec4f(position, 0.0, 1.0);
out.uv = uv;
out.color = color;
+ out.clip_circle = clip_circle;
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4f {
+ // Same circle clip as shader.wgsl (framebuffer px): used by the circular
+ // network pane's curved rim labels.
+ if (in.clip_circle.z > 0.0) {
+ let dx = in.clip_position.x - in.clip_circle.x;
+ let dy = in.clip_position.y - in.clip_circle.y;
+ if (dx * dx + dy * dy > in.clip_circle.z * in.clip_circle.z) {
+ discard;
+ }
+ }
return in.color * textureSample(t_atlas, s_atlas, in.uv);
}
diff --git a/src/vk/text.rs b/src/vk/text.rs
index 6e88551..ecc5c63 100644
--- a/src/vk/text.rs
+++ b/src/vk/text.rs
@@ -42,6 +42,12 @@ pub struct TextSpan<'a> {
pub bounds: Option<[i32; 4]>,
/// 0..=1 sRGB + alpha, applied to glyphs without their own color.
pub default_color: [f32; 4],
+ /// Rotate the span's glyph quads by (radians, center_x, center_y) in
+ /// physical pixels — the circular network pane's curved rim labels.
+ pub rotation: Option<(f32, f32, f32)>,
+ /// Fragment circle clip (center_x, center_y, radius) in physical pixels;
+ /// zero radius disables (matches shader.wgsl's clip_circle).
+ pub clip_circle: [f32; 3],
}
#[repr(C)]
@@ -50,6 +56,7 @@ struct GlyphVertex {
position: [f32; 2],
uv: [f32; 2],
color: [f32; 4],
+ clip_circle: [f32; 3],
}
#[derive(Clone, Copy)]
@@ -198,6 +205,11 @@ impl TextStage {
.binding(0)
.format(vk::Format::R32G32B32A32_SFLOAT)
.offset(16),
+ vk::VertexInputAttributeDescription::default()
+ .location(3)
+ .binding(0)
+ .format(vk::Format::R32G32B32_SFLOAT)
+ .offset(32),
];
let vertex_input = vk::PipelineVertexInputStateCreateInfo::default()
.vertex_binding_descriptions(&vertex_bindings)
@@ -556,14 +568,28 @@ impl TextStage {
span.default_color
};
- let ndc = |px: f32, py: f32| {
- [(px / sw) * 2.0 - 1.0, 1.0 - (py / sh) * 2.0]
+ // Corner positions, optionally rotated about the span's center
+ // (physical px) before the NDC mapping.
+ let corners = match span.rotation {
+ None => [[x0, y0], [x1, y0], [x0, y1], [x1, y1]],
+ Some((angle, cx, cy)) => {
+ let (sin_a, cos_a) = angle.sin_cos();
+ let rot = |px: f32, py: f32| {
+ let (dx, dy) = (px - cx, py - cy);
+ [cx + dx * cos_a - dy * sin_a, cy + dx * sin_a + dy * cos_a]
+ };
+ [rot(x0, y0), rot(x1, y0), rot(x0, y1), rot(x1, y1)]
+ }
+ };
+ let ndc = |p: [f32; 2]| {
+ [(p[0] / sw) * 2.0 - 1.0, 1.0 - (p[1] / sh) * 2.0]
};
let uv = |u: f32, v: f32| [u / ATLAS_SIZE as f32, v / ATLAS_SIZE as f32];
- let tl = GlyphVertex { position: ndc(x0, y0), uv: uv(u0, v0), color };
- let tr = GlyphVertex { position: ndc(x1, y0), uv: uv(u1, v0), color };
- let bl = GlyphVertex { position: ndc(x0, y1), uv: uv(u0, v1), color };
- let br = GlyphVertex { position: ndc(x1, y1), uv: uv(u1, v1), color };
+ let clip_circle = span.clip_circle;
+ let tl = GlyphVertex { position: ndc(corners[0]), uv: uv(u0, v0), color, clip_circle };
+ let tr = GlyphVertex { position: ndc(corners[1]), uv: uv(u1, v0), color, clip_circle };
+ let bl = GlyphVertex { position: ndc(corners[2]), uv: uv(u0, v1), color, clip_circle };
+ let br = GlyphVertex { position: ndc(corners[3]), uv: uv(u1, v1), color, clip_circle };
self.pending_vertices.extend([tl, tr, bl, tr, br, bl]);
}
}
diff --git a/src/vk_smoke.rs b/src/vk_smoke.rs
index ad5b992..a4b11cd 100644
--- a/src/vk_smoke.rs
+++ b/src/vk_smoke.rs
@@ -349,6 +349,8 @@ fn main() {
scale: s,
bounds: None,
default_color: [0.92, 0.92, 0.95, 1.0],
+ rotation: None,
+ clip_circle: [0.0; 3],
},
TextSpan {
buffer: &body_buf,
@@ -358,6 +360,8 @@ fn main() {
bounds: None,
// Animated color: proves per-frame vertex rebuilds.
default_color: [pulse, 0.80, 0.55, 1.0],
+ rotation: None,
+ clip_circle: [0.0; 3],
},
TextSpan {
buffer: &clipped_buf,
@@ -371,6 +375,8 @@ fn main() {
(233.0 * s) as i32,
]),
default_color: [0.70, 0.85, 1.00, 1.0],
+ rotation: None,
+ clip_circle: [0.0; 3],
},
];
if let Some(renderer) = &mut app.renderer {