From a6ae70558b6a18763e35af481e8df0c3c5ae35f3 Mon Sep 17 00:00:00 2001 From: mitchellhansen Date: Sun, 26 Apr 2026 20:12:22 -0700 Subject: [PATCH] feat: node graph detection editor with blend modes and per-node previews MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the linear detection layer stack with a DAG-based node graph. Backend (detect.rs, lib.rs): - Add BlendMode enum (Average, Min, Max, Multiply, Screen, Difference) - Add DetectionGraph / GraphNode / GraphEdge types and evaluate_graph() which topologically sorts and evaluates the DAG, returning per-node response maps for live previews - blend_maps() applies accumulative blend semantics per mode - ProcessPassPayload: layers → graph (DetectionGraphPayload) - ProcessResult: add node_previews HashMap — small JPEG thumbnails for each intermediate node, encoded via map_to_b64_small() - Update all tests to use the new graph payload shape Frontend (NodeGraph.jsx, store.js, App.jsx, PassPanel.jsx): - New NodeGraph component: SVG-overlay bezier wires, zoom/pan canvas, draggable nodes, port click-to-connect/wire, right-click-to-delete edges - Node types: Source (fixed), Kernel (full param sliders), Combine (blend mode selector + dynamic port count), Output (fixed) - Per-node JPEG thumbnail shown after each detect run - Detection view tab shows NodeGraph in main viewport at full size - defaultGraph() replaces defaultLayer() in store — Source→Kernel→Output Co-Authored-By: Claude Sonnet 4.6 --- src-frontend/src/App.jsx | 41 +- src-frontend/src/components/NodeGraph.jsx | 441 ++++++++++++++ src-frontend/src/components/PassPanel.jsx | 9 - src-frontend/src/store.js | 24 +- src/canvas/canvas_frame.rs | 51 -- src/canvas/canvas_state.rs | 640 -------------------- src/canvas/managed/canvas_text.rs | 73 --- src/canvas/managed/gpu_buffers.rs | 78 --- src/canvas/managed/handles.rs | 60 -- src/canvas/managed/mod.rs | 40 -- src/canvas/managed/shader/dynamic_vertex.rs | 182 ------ src/canvas/managed/shader/generic_shader.rs | 145 ----- src/canvas/managed/shader/mod.rs | 4 - src/canvas/managed/shader/shader_common.rs | 124 ---- src/canvas/managed/shader/text_shader.rs | 195 ------ src/canvas/mod.rs | 6 - src/compute/compu_frame.rs | 69 --- src/compute/compu_state.rs | 169 ------ src/compute/managed/compu_buffer.rs | 106 ---- src/compute/managed/compu_kernel.rs | 161 ----- src/compute/managed/handles.rs | 29 - src/compute/managed/mod.rs | 4 - src/compute/mod.rs | 6 - src/detect.rs | 209 +++++++ src/drawables/compu_sprite.rs | 59 -- src/drawables/mod.rs | 6 - src/drawables/polygon.rs | 71 --- src/drawables/rect.rs | 83 --- src/drawables/slider.rs | 68 --- src/drawables/sprite.rs | 107 ---- src/drawables/text.rs | 143 ----- src/lib.rs | 191 ++++-- src/vkprocessor.rs | 348 ----------- 33 files changed, 831 insertions(+), 3111 deletions(-) create mode 100644 src-frontend/src/components/NodeGraph.jsx delete mode 100644 src/canvas/canvas_frame.rs delete mode 100644 src/canvas/canvas_state.rs delete mode 100644 src/canvas/managed/canvas_text.rs delete mode 100644 src/canvas/managed/gpu_buffers.rs delete mode 100644 src/canvas/managed/handles.rs delete mode 100644 src/canvas/managed/mod.rs delete mode 100644 src/canvas/managed/shader/dynamic_vertex.rs delete mode 100644 src/canvas/managed/shader/generic_shader.rs delete mode 100644 src/canvas/managed/shader/mod.rs delete mode 100644 src/canvas/managed/shader/shader_common.rs delete mode 100644 src/canvas/managed/shader/text_shader.rs delete mode 100644 src/canvas/mod.rs delete mode 100644 src/compute/compu_frame.rs delete mode 100644 src/compute/compu_state.rs delete mode 100644 src/compute/managed/compu_buffer.rs delete mode 100644 src/compute/managed/compu_kernel.rs delete mode 100644 src/compute/managed/handles.rs delete mode 100644 src/compute/managed/mod.rs delete mode 100644 src/compute/mod.rs delete mode 100644 src/drawables/compu_sprite.rs delete mode 100644 src/drawables/mod.rs delete mode 100644 src/drawables/polygon.rs delete mode 100644 src/drawables/rect.rs delete mode 100644 src/drawables/slider.rs delete mode 100644 src/drawables/sprite.rs delete mode 100644 src/drawables/text.rs delete mode 100644 src/vkprocessor.rs diff --git a/src-frontend/src/App.jsx b/src-frontend/src/App.jsx index 76147b33..8898ff6c 100644 --- a/src-frontend/src/App.jsx +++ b/src-frontend/src/App.jsx @@ -1,5 +1,6 @@ import { useState, useCallback, useEffect, useRef } from 'react' import Viewport from './components/Viewport.jsx' +import NodeGraph from './components/NodeGraph.jsx' import PassPanel from './components/PassPanel.jsx' import PerfPanel from './components/PerfPanel.jsx' import Slider from './components/Slider.jsx' @@ -161,7 +162,7 @@ export default function App() { try { const result = await tauri.processPass({ pass_index: idx, - layers: pass.layers, + graph: pass.graph, threshold: pass.threshold, min_area: pass.min_area, rdp_epsilon: pass.rdp_epsilon, @@ -171,10 +172,11 @@ export default function App() { const js_process = Math.round(performance.now() - t0) setPerfData(pd => ({ ...(pd ?? {}), process: result.timings, js_process })) updatePass(idx, { - status: `${result.hull_count} hulls · ${result.coverage_pct}% coverage`, - vizB64: result.viz_b64, - hullCount: result.hull_count, - strokeCount: 0, + status: `${result.hull_count} hulls · ${result.coverage_pct}% coverage`, + vizB64: result.viz_b64, + hullCount: result.hull_count, + strokeCount: 0, + nodePreviews: result.node_previews ?? {}, }) await generateFillInner(idx, true) } catch (e) { @@ -269,7 +271,7 @@ export default function App() { async function dumpDebugState() { try { const configs = passes.map(p => ({ - layers: p.layers, threshold: p.threshold, min_area: p.min_area, + graph: p.graph, threshold: p.threshold, min_area: p.min_area, rdp_epsilon: p.rdp_epsilon, connectivity: p.connectivity, color_filter: p.colorFilter, strategy: p.strategy, spacing: p.spacing, angle: p.angle, @@ -404,15 +406,26 @@ export default function App() { >⏱ - {/* Viewport */} + {/* Viewport / Node graph */}
- + {viewMode === 'detection' ? ( + { + updatePass(activePass, { graph }) + scheduleProcess(activePass) + }} + nodePreviews={passes[activePass].nodePreviews} + /> + ) : ( + + )} {showPerf && } {!image && (
diff --git a/src-frontend/src/components/NodeGraph.jsx b/src-frontend/src/components/NodeGraph.jsx new file mode 100644 index 00000000..5e5f8651 --- /dev/null +++ b/src-frontend/src/components/NodeGraph.jsx @@ -0,0 +1,441 @@ +import { useRef, useState, useCallback, useEffect } from 'react' +import Slider from './Slider.jsx' +import { KERNELS, BLEND_MODES, defaultKernelProps, newNodeId } from '../store.js' + +// ── Layout constants ─────────────────────────────────────────────────────────── +const NODE_W = 220 +const PORT_R = 6 +const PORT_TOP = 18 // y offset of first port from node top (center of header) +const PORT_STRIDE = 26 // vertical gap between combine input ports + +const KERNEL_PARAMS = { + Luminance: ['blur_radius'], + Sobel: ['blur_radius'], + ColorGradient: ['blur_radius'], + Laplacian: ['blur_radius'], + Saturation: ['blur_radius', 'sat_min_value'], + Canny: ['canny_low', 'canny_high'], + XDoG: ['blur_radius', 'xdog_sigma2', 'xdog_tau', 'xdog_phi'], +} +const PARAM_META = { + blur_radius: { label: 'Blur σ', min: 0, max: 10, step: 0.1 }, + sat_min_value: { label: 'Min val', min: 0, max: 1, step: 0.01 }, + canny_low: { label: 'Low thr', min: 1, max: 254, step: 1 }, + canny_high: { label: 'High', min: 1, max: 254, step: 1 }, + xdog_sigma2: { label: 'σ2', min: 0.2, max: 10, step: 0.1 }, + xdog_tau: { label: 'τ', min: 0, max: 1, step: 0.01 }, + xdog_phi: { label: 'φ', min: 1, max: 100, step: 1 }, +} + +// ── Port position helpers (world coords) ─────────────────────────────────────── +function outPort(node) { + return { x: node.x + NODE_W, y: node.y + PORT_TOP } +} +function inPort(node, portIdx) { + return { x: node.x, y: node.y + PORT_TOP + portIdx * PORT_STRIDE } +} +function edgePath(from, to) { + const dx = Math.max(Math.abs(to.x - from.x) * 0.5, 60) + return `M ${from.x} ${from.y} C ${from.x + dx} ${from.y}, ${to.x - dx} ${to.y}, ${to.x} ${to.y}` +} + +// ── Main component ───────────────────────────────────────────────────────────── +export default function NodeGraph({ graph, onChange, nodePreviews }) { + const canvasRef = useRef(null) + const [pan, setPan] = useState({ x: 40, y: 40 }) + const [zoom, setZoom] = useState(1) + + // pan drag + const panRef = useRef(null) + + // node drag + const nodeDragRef = useRef(null) + + // wire in progress + const [wire, setWire] = useState(null) // { fromId, fromX, fromY, mouseX, mouseY } + const wireRef = useRef(null) + + // ── Coordinate conversion ────────────────────────────────────────────────── + const toWorld = useCallback((clientX, clientY) => { + const r = canvasRef.current.getBoundingClientRect() + return { + x: (clientX - r.left - pan.x) / zoom, + y: (clientY - r.top - pan.y) / zoom, + } + }, [pan, zoom]) + + // ── Wheel zoom ───────────────────────────────────────────────────────────── + const onWheel = useCallback(e => { + e.preventDefault() + const r = canvasRef.current.getBoundingClientRect() + const cx = e.clientX - r.left + const cy = e.clientY - r.top + const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1 + setZoom(z => { + const nz = Math.min(Math.max(z * factor, 0.2), 4) + setPan(p => ({ + x: cx - (cx - p.x) * (nz / z), + y: cy - (cy - p.y) * (nz / z), + })) + return nz + }) + }, []) + + useEffect(() => { + const el = canvasRef.current + el.addEventListener('wheel', onWheel, { passive: false }) + return () => el.removeEventListener('wheel', onWheel) + }, [onWheel]) + + // ── Canvas mouse handlers (pan + wire cancel) ────────────────────────────── + function onCanvasMouseDown(e) { + if (e.target !== canvasRef.current && !e.target.dataset.canvas) return + if (e.button !== 0) return + panRef.current = { startX: e.clientX - pan.x, startY: e.clientY - pan.y } + } + + // ── Global mouse move / up ───────────────────────────────────────────────── + useEffect(() => { + function onMove(e) { + if (panRef.current) { + setPan({ x: e.clientX - panRef.current.startX, y: e.clientY - panRef.current.startY }) + } + if (nodeDragRef.current) { + const { nodeId, startNodeX, startNodeY, startX, startY } = nodeDragRef.current + const wx = startNodeX + (e.clientX - startX) / zoom + const wy = startNodeY + (e.clientY - startY) / zoom + onChange({ ...graph, nodes: graph.nodes.map(n => n.id === nodeId ? { ...n, x: wx, y: wy } : n) }) + } + if (wireRef.current) { + const r = canvasRef.current.getBoundingClientRect() + const mx = (e.clientX - r.left - pan.x) / zoom + const my = (e.clientY - r.top - pan.y) / zoom + setWire(w => w ? { ...w, mouseX: mx, mouseY: my } : w) + wireRef.current = { ...wireRef.current, mouseX: mx, mouseY: my } + } + } + function onUp(e) { + panRef.current = null + nodeDragRef.current = null + if (e.key === undefined && wireRef.current) { + setWire(null) + wireRef.current = null + } + } + function onKey(e) { + if (e.key === 'Escape') { + setWire(null) + wireRef.current = null + } + } + window.addEventListener('mousemove', onMove) + window.addEventListener('mouseup', onUp) + window.addEventListener('keydown', onKey) + return () => { + window.removeEventListener('mousemove', onMove) + window.removeEventListener('mouseup', onUp) + window.removeEventListener('keydown', onKey) + } + }, [graph, onChange, pan, zoom]) + + // ── Wire interactions ────────────────────────────────────────────────────── + function startWire(e, fromId) { + e.stopPropagation() + const node = graph.nodes.find(n => n.id === fromId) + const p = outPort(node) + const state = { fromId, fromX: p.x, fromY: p.y, mouseX: p.x, mouseY: p.y } + wireRef.current = state + setWire(state) + } + + function endWire(e, toId, port) { + e.stopPropagation() + if (!wireRef.current) return + const { fromId } = wireRef.current + + // Prevent self-loop and duplicate edges + if (fromId === toId) { setWire(null); wireRef.current = null; return } + const exists = graph.edges.some(ed => ed.from === fromId && ed.to === toId && ed.port === port) + if (!exists) { + // Remove any existing edge to this specific port + const filtered = graph.edges.filter(ed => !(ed.to === toId && ed.port === port)) + onChange({ ...graph, edges: [...filtered, { from: fromId, to: toId, port }] }) + } + setWire(null) + wireRef.current = null + } + + function removeEdge(idx) { + onChange({ ...graph, edges: graph.edges.filter((_, i) => i !== idx) }) + } + + // ── Node mutations ───────────────────────────────────────────────────────── + function updateNode(id, patch) { + onChange({ ...graph, nodes: graph.nodes.map(n => n.id === id ? { ...n, ...patch } : n) }) + } + function deleteNode(id) { + onChange({ + nodes: graph.nodes.filter(n => n.id !== id), + edges: graph.edges.filter(e => e.from !== id && e.to !== id), + }) + } + function addNode(kind) { + const r = canvasRef.current.getBoundingClientRect() + const x = (r.width / 2 - pan.x) / zoom - NODE_W / 2 + const y = (r.height / 2 - pan.y) / zoom - 60 + const id = newNodeId(kind) + const base = { id, kind, x, y } + const node = kind === 'Kernel' + ? { ...base, ...defaultKernelProps() } + : { ...base, blendMode: 'Average', inputCount: 2 } + onChange({ ...graph, nodes: [...graph.nodes, node] }) + } + function addCombinePort(id) { + const n = graph.nodes.find(n => n.id === id) + updateNode(id, { inputCount: (n.inputCount ?? 2) + 1 }) + } + function removeCombinePort(id) { + const n = graph.nodes.find(n => n.id === id) + const cnt = n.inputCount ?? 2 + if (cnt <= 2) return + const lastPort = cnt - 1 + onChange({ + ...graph, + nodes: graph.nodes.map(nd => nd.id === id ? { ...nd, inputCount: cnt - 1 } : nd), + edges: graph.edges.filter(e => !(e.to === id && e.port === lastPort)), + }) + } + + // ── Node header drag start ───────────────────────────────────────────────── + function startNodeDrag(e, nodeId) { + e.stopPropagation() + if (e.button !== 0) return + const node = graph.nodes.find(n => n.id === nodeId) + nodeDragRef.current = { + nodeId, startNodeX: node.x, startNodeY: node.y, + startX: e.clientX, startY: e.clientY, + } + } + + // ── Node rendering ───────────────────────────────────────────────────────── + function renderNode(node) { + const isFixed = node.kind === 'Source' || node.kind === 'Output' + const preview = nodePreviews?.[node.id] + const inputCnt = node.kind === 'Combine' ? (node.inputCount ?? 2) : node.kind === 'Kernel' || node.kind === 'Output' ? 1 : 0 + const hasOut = node.kind !== 'Output' + + return ( +
+ {/* Input ports */} + {Array.from({ length: inputCnt }, (_, i) => ( +
endWire(e, node.id, i)} + style={{ + position: 'absolute', + left: -PORT_R, top: PORT_TOP + i * PORT_STRIDE - PORT_R, + width: PORT_R * 2, height: PORT_R * 2, borderRadius: '50%', + background: wire ? '#14b8a6' : '#374151', + border: '2px solid #14b8a6', cursor: 'crosshair', zIndex: 10, + boxShadow: wire ? '0 0 6px #14b8a6' : 'none', + transition: 'background 0.15s, box-shadow 0.15s', + }} + /> + ))} + + {/* Output port */} + {hasOut && ( +
startWire(e, node.id)} + style={{ + position: 'absolute', + right: -PORT_R, top: PORT_TOP - PORT_R, + width: PORT_R * 2, height: PORT_R * 2, borderRadius: '50%', + background: '#6366f1', border: '2px solid #818cf8', + cursor: 'crosshair', zIndex: 10, + }} + /> + )} + + {/* Node body */} +
+ {/* Header */} +
startNodeDrag(e, node.id)} + style={{ + padding: '4px 8px', cursor: 'move', + background: node.kind === 'Source' ? '#2e1065' : node.kind === 'Output' ? '#1c1003' : '#1e293b', + display: 'flex', alignItems: 'center', gap: 6, minHeight: 32, + }} + > + + {node.kind === 'Source' ? 'Source' + : node.kind === 'Output' ? 'Output' + : node.kind === 'Kernel' ? node.kernel ?? 'Kernel' + : 'Combine'} + + {!isFixed && ( + + )} +
+ + {/* Body */} +
+ + {/* Kernel controls */} + {node.kind === 'Kernel' && (<> + {/* Kernel selector */} +
+ {KERNELS.map(k => ( + + ))} +
+ updateNode(node.id, { weight: v })} /> + {(KERNEL_PARAMS[node.kernel] ?? []).map(p => { + const m = PARAM_META[p] + return updateNode(node.id, { [p]: v })} /> + })} + + )} + + {/* Combine controls */} + {node.kind === 'Combine' && (<> +
+ {BLEND_MODES.map(m => ( + + ))} +
+
+ Inputs: {node.inputCount ?? 2} + + +
+ )} + + {/* Preview thumbnail */} + {preview && ( + + )} +
+
+
+ ) + } + + // ── SVG edges ────────────────────────────────────────────────────────────── + const nodeById = Object.fromEntries(graph.nodes.map(n => [n.id, n])) + + const svgEdges = graph.edges.map((edge, idx) => { + const fn_ = nodeById[edge.from] + const tn = nodeById[edge.to] + if (!fn_ || !tn) return null + const from = outPort(fn_) + const to = inPort(tn, edge.port) + return ( + removeEdge(idx)} + /> + ) + }) + + const wireEdge = wire && ( + + ) + + // ── Canvas size for SVG ──────────────────────────────────────────────────── + const WORLD_SIZE = 4000 + + return ( +
+ {/* Toolbar */} +
+ + + + scroll to zoom · drag to pan · click wire to delete + +
+ + {/* World transform */} +
+ {/* SVG for edges */} + + + {svgEdges} + + {wireEdge} + + + {/* Nodes */} + {graph.nodes.map(renderNode)} +
+
+ ) +} diff --git a/src-frontend/src/components/PassPanel.jsx b/src-frontend/src/components/PassPanel.jsx index 1f1a8235..4176ba0c 100644 --- a/src-frontend/src/components/PassPanel.jsx +++ b/src-frontend/src/components/PassPanel.jsx @@ -1,6 +1,5 @@ import Section from './Section.jsx' import Slider from './Slider.jsx' -import DetectionLayers from './DetectionLayers.jsx' import ColorFilter from './ColorFilter.jsx' import { FILL_STRATEGIES, FILL_STRATEGY_PARAMS, FILL_USES_ANGLE } from '../store.js' @@ -46,14 +45,6 @@ export default function PassPanel({ )}
- {/* ── Detection ── kernels that produce the response map */} -
- setDetection({ layers })} - /> -
- {/* ── Hulls & Contours ── how the response map becomes components */}
Vec; -} - -/// Trait which may be inherited by objects that wish to receive events -pub trait Eventable { - fn notify(&mut self, event: &Event) -> (); -} - -/// Trait which may be inherited by objects that wish to be updated -pub trait Updatable { - fn update(&mut self, delta_time: f32) -> (); -} - -/// Accumulator for Vectors of VertexTypes -#[derive(Default)] -pub struct CanvasFrame { - pub map: Vec, - window_size: (u32, u32), -} - -impl CanvasFrame { - - pub fn new(window_size: (u32, u32)) -> CanvasFrame { - CanvasFrame { - map: vec![], - window_size: window_size, - } - } - /// Push this drawable onto the back of the accumulator - pub fn draw(&mut self, drawable: &dyn Drawable) { - for i in drawable.get(self.window_size) { - self.map.push(i); - } - } -} - - - diff --git a/src/canvas/canvas_state.rs b/src/canvas/canvas_state.rs deleted file mode 100644 index 3c28c5a9..00000000 --- a/src/canvas/canvas_state.rs +++ /dev/null @@ -1,640 +0,0 @@ -use vulkano::command_buffer::{AutoCommandBufferBuilder, DynamicState}; -use std::collections::{HashMap, HashSet}; -use vulkano::buffer::{BufferAccess, BufferUsage, ImmutableBuffer, CpuAccessibleBuffer}; -use std::sync::Arc; -use vulkano::format::{ClearValue, Format, R8Unorm, ClearValuesTuple}; -use vulkano::framebuffer::{FramebufferAbstract, Framebuffer, RenderPass, RenderPassAbstract}; -use vulkano::device::{Device, Queue}; -use vulkano::instance::PhysicalDevice; -use vulkano::image::immutable::ImmutableImage; -use vulkano::image::{Dimensions, ImageAccess, ImageDimensions, SwapchainImage, ImageUsage, AttachmentImage, ImageLayout}; -use vulkano::sampler::{Sampler, SamplerAddressMode, MipmapMode, Filter}; -use vulkano::descriptor::DescriptorSet; -use vulkano::descriptor::descriptor_set::PersistentDescriptorSet; -use std::path::PathBuf; -use image::GenericImageView; -use std::iter::FromIterator; -use vulkano::swapchain::Capabilities; -use vulkano::pipeline::viewport::Viewport; -use vulkano::descriptor::descriptor::DescriptorDescTy::TexelBuffer; -use crate::canvas::canvas_frame::CanvasFrame; -use std::hash::Hash; -use vulkano::pipeline::depth_stencil::{StencilFaceFlags, DynamicStencilValue}; -use vulkano::memory::pool::PotentialDedicatedAllocation::Generic; -use std::borrow::Borrow; -use std::fs::File; -use std::io::Read; -use rusttype::{Font, PositionedGlyph, Scale, Rect, point, GlyphId, Line, Curve, Segment}; -use vulkano::pipeline::vertex::{VertexDefinition, Vertex}; -use crate::canvas::managed::shader::dynamic_vertex::RuntimeVertexDef; -use crate::canvas::managed::handles::{CanvasTextureHandle, CanvasImageHandle, CanvasFontHandle, CompiledShaderHandle, Handle, DrawableHandle}; -use crate::canvas::managed::gpu_buffers::{CanvasImage, CanvasTexture, CanvasFont}; -use crate::canvas::managed::shader::shader_common::CompiledShader; -use crate::canvas::managed::shader::generic_shader::GenericShader; -use crate::VertexType; -use crate::util::vertex::{TextVertex3D, TextureVertex3D, ImageVertex3D, ColorVertex3D, CanvasFrameAllocation}; -use shade_runner::Input; -use winit::window::Window; - - -/// Canvas state is used for storage of texture and image buffers in addition to vertex buffers -/// Canvas state also contains logic for writing the stored buffers to the command_buffer -#[derive(Clone)] -pub struct CanvasState { - /// Generated during new() - dynamic_state: DynamicState, - /// Generated during new() - sampler: Arc, - - /// hold the image, texture, and Fonts the same was as we do CompuState - image_buffers: Vec>, - texture_buffers: Vec>, - font_buffers: Vec>, - - /// Compiled Graphics pipelines have a handle which self describe their position in this vector - shader_buffers: Vec>>, - - /// Looks like we gotta hold onto the queue for managing textures - queue: Arc, - device: Arc, - render_pass: Arc, -} - - -impl CanvasState { - /// This method is called once during initialization, then again whenever the window is resized - pub fn window_size_dependent_setup(&mut self, images: &[Arc>]) - -> Vec> { - let dimensions = images[0].dimensions(); - - self.dynamic_state.viewports = - Some(vec![Viewport { - origin: [0.0, 0.0], - dimensions: [dimensions.width() as f32, dimensions.height() as f32], - depth_range: 0.0..1.0, - }]); - - let dimensions = [dimensions.width(), dimensions.height()]; - let depth_buffer = AttachmentImage::transient(self.device.clone(), dimensions, Format::D32Sfloat_S8Uint).unwrap(); - - images.iter().map(|image| { - Arc::new( - Framebuffer::start(self.render_pass.clone()) - .add(image.clone()).unwrap() - .add(depth_buffer.clone()).unwrap() - .build().unwrap() - ) as Arc - }).collect::>() - } - - /// Creates a Canvas State. Which at this point is pretty empty - pub fn new(queue: Arc, - device: Arc, - physical: PhysicalDevice, - capabilities: Capabilities) -> CanvasState { - let format = capabilities.supported_formats[0].0; - - let render_pass = Arc::new(vulkano::single_pass_renderpass!( - device.clone(), - - // Attachments are outgoing like f_color - attachments: { - // `color` is a custom name we give to the first and only attachment. - color: { - // `load: Clear` means that we ask the GPU to clear the content of this - // attachment at the start of the drawing. - load: Clear, - // `store: Store` means that we ask the GPU to store the output of the draw - // in the actual image. We could also ask it to discard the result. - store: Store, - // `format: ` indicates the type of the format of the image. This has to - // be one of the types of the `vulkano::format` module (or alternatively one - // of your structs that implements the `FormatDesc` trait). Here we use the - // same format as the swapchain. - format: format, - samples: 1, - }, - - depth: { - load: Clear, - store: DontCare, - format: Format::D32Sfloat_S8Uint, - samples: 1, - } - }, - pass: { - // We use the attachment named `color` as the one and only color attachment. - color: [color], - // No depth-stencil attachment is indicated with empty brackets. - depth_stencil: {depth} - } - ).unwrap()); - - - CanvasState { - - // TODO: Might need to move this - dynamic_state: DynamicState { - line_width: None, - viewports: None, - scissors: None, - compare_mask: Some(DynamicStencilValue { - face: StencilFaceFlags::StencilFrontAndBack, - value: 0xFF, - }), - write_mask: Some(DynamicStencilValue { - face: StencilFaceFlags::StencilFrontAndBack, - value: 0xFF, - }), - reference: Some(DynamicStencilValue { - face: StencilFaceFlags::StencilFrontAndBack, - value: 0xFF, - }), - }, - sampler: Sampler::new(device.clone(), - Filter::Linear, Filter::Linear, - MipmapMode::Nearest, - SamplerAddressMode::Repeat, SamplerAddressMode::Repeat, - SamplerAddressMode::Repeat, 0.0, 1.0, 0.0, 0.0).unwrap(), - image_buffers: vec![], - texture_buffers: vec![], - shader_buffers: vec![], - font_buffers: vec![], - - queue: queue.clone(), - device: device.clone(), - render_pass: render_pass.clone(), - } - } - - /// Using the dimensions and suggested usage, load a CanvasImage and return it's handle - pub fn create_image(&mut self, dimensions: (u32, u32), usage: ImageUsage) -> Arc { - let handle = Arc::new(CanvasImageHandle { handle: self.image_buffers.len() as u32 }); - - let image = CanvasImage { - handle: handle.clone(), - buffer: AttachmentImage::with_usage( - self.device.clone(), - [dimensions.0, dimensions.1], - Format::R8G8B8A8Uint, - usage).unwrap(), - size: dimensions, - }; - - self.image_buffers.push(Arc::new(image)); - - handle - } - - /// Return the image buffer from an input image handle - pub fn get_image(&self, image_handle: Arc) -> Arc { - self.image_buffers.get((*image_handle).clone().get_handle() as usize).unwrap() - .clone().buffer.clone() - } - - /// Load a texture buffer from an input filename - fn get_texture_from_file(&self, image_filename: String) -> Arc> { - let project_root = - std::env::current_dir() - .expect("failed to get root directory"); - - let mut compute_path = project_root.clone(); - compute_path.push(PathBuf::from("resources/images/")); - compute_path.push(PathBuf::from(image_filename.clone())); - - let img = image::open(compute_path).expect("Couldn't find image"); - - let xy = img.dimensions(); - - let data_length = xy.0 * xy.1 * 4; - let pixel_count = img.raw_pixels().len(); - - let mut image_buffer = Vec::new(); - - if pixel_count != data_length as usize { - println!("Creating alpha channel for {}", image_filename.clone()); - for i in img.raw_pixels().iter() { - if (image_buffer.len() + 1) % 4 == 0 { - image_buffer.push(255); - } - image_buffer.push(*i); - } - image_buffer.push(255); - } else { - image_buffer = img.raw_pixels(); - } - - let (texture, tex_future) = ImmutableImage::from_iter( - image_buffer.iter().cloned(), - Dimensions::Dim2d { width: xy.0, height: xy.1 }, - Format::R8G8B8A8Srgb, - self.queue.clone(), - ).unwrap(); - - texture - } - - /// Load a texture using it's filename from a file. Returns the handle of the loaded texture - pub fn load_texture(&mut self, filename: String) -> Option> { - let handle = Arc::new(CanvasTextureHandle { - handle: self.texture_buffers.len() as u32 - }); - - let texture = Arc::new(CanvasTexture { - handle: handle.clone(), - buffer: self.get_texture_from_file(filename.clone()), - name: filename.clone(), - size: (0, 0), - }); - - self.texture_buffers.push(texture); - - Some(handle) - } - - /// Load and Compile a shader with the filename at resources/shaders - /// Takes physical and capabilities as we don't store that in Canvas - pub fn load_shader(&mut self, - filename: String, - capabilities: Capabilities) -> Option> - where T: CompiledShader, V: Vertex { - let handle = Arc::new(CompiledShaderHandle { - handle: self.shader_buffers.len() as u32 - }); - - let shader: Box = Box::new(T::new::( - filename.clone(), - self.device.clone(), - handle.clone(), - self.render_pass.clone(), - )); - - self.shader_buffers.push(Arc::new(shader)); - - Some(handle) - } - - /// - pub fn load_font(&mut self, name: String) -> Arc { - let handle = Arc::new(CanvasFontHandle { handle: self.font_buffers.len() as u32 }); - - self.font_buffers.push(Arc::new({ - let font = Font::from_bytes({ - let mut f = File::open("resources/fonts/sansation.ttf") - .expect("Font file not found"); - let mut font_data = Vec::new(); - f.read_to_end(&mut font_data).expect("Dont know"); - font_data - }).unwrap(); - - let mut current_x = 0; - let mut current_y = 0; - - let mut accumulator = Vec::new(); - - - for i in (0..255) { - let glyph = font.glyph('d'); - - let s = glyph.scaled(Scale { x: 1.0, y: 1.0 }); - - let shape = s.shape().unwrap(); - - for contour in shape { - for segment in contour.segments { - match segment { - Segment::Line(l) => { - accumulator.push(TextVertex3D { - position: [l.p[0].x as f32, l.p[0].y as f32, 0.0], - }); - } - Segment::Curve(c) => { - accumulator.push(TextVertex3D { - position: [c.p[0].x as f32, c.p[0].y as f32, 0.0], - }); - } - } - } - } - } - - CanvasFont { - handle: handle.clone(), - font: font.clone(), - name: name, - buffer: ImmutableBuffer::from_iter( - accumulator.iter().cloned(), - BufferUsage::vertex_buffer(), self.queue.clone()).unwrap().0, - } - })); - - handle - } - - /// Using the texture name, iterates through the stored textures and matches by the name - pub fn get_texture_handle(&self, texture_name: String) - -> Option> { - for i in self.texture_buffers.clone() { - if i.name == texture_name { - return Some(i.handle.clone()); - } - } - None - } - - /// Using the shader name, iterates through the stored shaders and matches by the name - pub fn get_shader_handle(&self, shader_name: String) - -> Option> { - for shader in self.shader_buffers.clone() { - if shader.get_name() == shader_name { - return Some(shader.get_handle().clone()); - } - } - None - } - - /// Using the font name, iterates through the stored fonts and matches by the name - pub fn get_font_handle(&self, font_name: String) -> Option> { - for font in self.font_buffers.clone() { - if font.name == font_name { - return Some(font.handle.clone()); - } - } - None - } - - /// Using the texture handle, grab the stored texture and return the buffer - pub fn get_texture(&self, texture_handle: Arc) - -> Arc> { - let handle = texture_handle.get_handle() as usize; - - if let Some(i) = self.texture_buffers.get(handle) { - return i.clone().buffer.clone(); - } else { - panic!("{} : Texture not loaded", handle); - } - } - - /// Builds the descriptor set for solid colors using the input kernel (needs to support solid colors) - fn get_solid_color_descriptor_set(&self, kernel: Arc) -> Box { - let o: Box = Box::new( - PersistentDescriptorSet::start( - kernel.clone().get_pipeline().clone().descriptor_set_layout(0).unwrap().clone(), - ).build().unwrap()); - o - } - - /// Consume and allocate the canvas frame data to the GPU - pub fn allocate(&mut self, canvas_frame: &CanvasFrame) -> CanvasFrameAllocation { - - let mut colored_vertex_buffer: Vec = Vec::default(); - let mut textured_vertex_buffer: HashMap, Vec> = HashMap::new(); - let mut image_vertex_buffer: HashMap, Vec> = HashMap::new(); - let mut text_instances: HashMap, Vec> = HashMap::new(); - let mut text_vertex_buffer: Vec = Vec::new(); - - // separate the mux of vertex containers back out - for value in &canvas_frame.map { - match value { - VertexType::TextureType(vertices, handle) => { - textured_vertex_buffer.entry(handle.clone()).or_insert(vertices.clone()).extend(vertices); - } - VertexType::ImageType(vertices, handle) => { - image_vertex_buffer.entry(handle.clone()).or_insert(vertices.clone()).extend(vertices); - } - VertexType::ColorType(vertices) => { - colored_vertex_buffer.extend(vertices); - } - VertexType::ThreeDType(vertices) => { - - } - VertexType::TextType(vertices) => { - text_vertex_buffer.extend(vertices); - } - }; - }; - - let mut allocated_colored_buffer: Vec> = Vec::new(); - if !colored_vertex_buffer.is_empty() { - allocated_colored_buffer.push(ImmutableBuffer::from_iter( - colored_vertex_buffer.iter().cloned(), - BufferUsage::vertex_buffer(), - self.queue.clone(), - ).unwrap().0); - } - - let mut allocated_text_buffer: Vec> = Vec::new(); - if !text_vertex_buffer.is_empty() { - allocated_text_buffer.push(ImmutableBuffer::from_iter( - text_vertex_buffer.iter().cloned(), - BufferUsage::vertex_buffer(), - self.queue.clone(), - ).unwrap().0); - } - - CanvasFrameAllocation { - colored_vertex_buffer: allocated_colored_buffer, - textured_vertex_buffer: textured_vertex_buffer.into_iter().map(|(k, v)| { - (k, - ImmutableBuffer::from_iter( - v.iter().cloned(), - BufferUsage::vertex_buffer(), - self.queue.clone(), - ).unwrap().0 as Arc<(dyn BufferAccess + Send + Sync)>) - }).collect(), - image_vertex_buffer: image_vertex_buffer.into_iter().map(|(k, v)| { - (k, - ImmutableBuffer::from_iter( - v.iter().cloned(), - BufferUsage::vertex_buffer(), - self.queue.clone(), - ).unwrap().0 as Arc<(dyn BufferAccess + Send + Sync)>) - }).collect(), - text_instances: Default::default(), - text_vertex_buffer: allocated_text_buffer, - } - } - - /// Pushes the draw commands to the command buffer. Requires the framebuffers and - /// image number to be passed in as they are taken care of by the vkprocessor - pub fn draw_commands(&mut self, - mut command_buffer: &mut AutoCommandBufferBuilder, - framebuffers: Vec>, - image_num: usize, - allocated_buffers: CanvasFrameAllocation) { - - // Specify the color to clear the framebuffer with i.e. blue - let clear_values = vec!( - ClearValue::Float([0.0, 0.0, 0.0, 0.0]), - ClearValue::DepthStencil((1.0, 0x00)), - ); - - self.dynamic_state = DynamicState { - line_width: None, - viewports: self.dynamic_state.viewports.clone(), - scissors: None, - compare_mask: None, - write_mask: None, - reference: None, - }; - - command_buffer = command_buffer.begin_render_pass( - framebuffers[image_num].clone(), false, clear_values.clone(), - ).unwrap(); - - // Solid colors - let mut shader = self.shader_buffers.get( - self.get_shader_handle(String::from("color-passthrough")) - .unwrap().clone().get_handle() as usize - ).unwrap(); - - // This looks a little weird as colored_vertex_buffer is a vec of GPU allocated vecs. - // But we can pass in multiple vertex buffers - - if !allocated_buffers.colored_vertex_buffer.is_empty() { - command_buffer = command_buffer.draw( - shader.get_pipeline().clone(), - &self.dynamic_state.clone(), - allocated_buffers.colored_vertex_buffer.clone(), - (), (), - ).unwrap(); - } - - // Images - let mut shader = self.shader_buffers.get( - self.get_shader_handle(String::from("simple_image")) - .unwrap().clone().get_handle() as usize - ).unwrap(); - - - if !allocated_buffers.image_vertex_buffer.is_empty() { - for (image_handle, vertex_buffer) in allocated_buffers.image_vertex_buffer.clone() { - let handle = image_handle.clone().get_handle() as usize; - let descriptor_set = self.image_buffers.get(handle).clone().unwrap().clone() - .get_descriptor_set(shader.get_pipeline().clone()); - - command_buffer = command_buffer.draw( - shader.get_pipeline().clone(), - // Multiple vertex buffers must have their definition in the pipeline! - &self.dynamic_state.clone(), vec![vertex_buffer], - vec![descriptor_set], (), - ).unwrap(); - } - } - - // Textures - let mut shader = self.shader_buffers.get( - self.get_shader_handle(String::from("simple_texture")) - .unwrap().clone().get_handle() as usize - ).unwrap(); - - if !allocated_buffers.textured_vertex_buffer.is_empty() { - for (texture_handle, vertex_buffer) in allocated_buffers.textured_vertex_buffer.clone() { - let handle = texture_handle.clone().get_handle() as usize; - let descriptor_set = self.texture_buffers.get(handle).clone().unwrap().clone() - .get_descriptor_set(shader.get_pipeline(), self.sampler.clone()); - - command_buffer = command_buffer.draw( - shader.get_pipeline().clone(), - // Multiple vertex buffers must have their definition in the pipeline! - &self.dynamic_state.clone(), vec![vertex_buffer], - vec![descriptor_set], (), - ).unwrap(); - } - } - - // Text - // let mut shader = self.shader_buffers.get( - // self.get_shader_handle(String::from("simple_text")) - // .unwrap().clone().get_handle() as usize - // ).unwrap(); -// -// self.dynamic_state = DynamicState { -// line_width: None, -// viewports: self.dynamic_state.viewports.clone(), -// scissors: None, -// compare_mask: Some(DynamicStencilValue { -// face: StencilFaceFlags::StencilFrontAndBack, -// value: 0x00, -// }), -// write_mask: Some(DynamicStencilValue { -// face: StencilFaceFlags::StencilFrontAndBack, -// value: 0xFF, -// }), -// reference: Some(DynamicStencilValue { -// face: StencilFaceFlags::StencilFrontAndBack, -// value: 0x00, -// }), -// }; -// -// if !allocated_buffers.text_vertex_buffer.is_empty() { -// command_buffer = command_buffer.draw( -// shader.get_pipeline().clone(), -// &self.dynamic_state.clone(), -// allocated_buffers.text_vertex_buffer.clone(), -// (), (), -// ).unwrap(); -// } -// -// self.dynamic_state = DynamicState { -// line_width: None, -// viewports: self.dynamic_state.viewports.clone(), -// scissors: None, -// compare_mask: Some(DynamicStencilValue { -// face: StencilFaceFlags::StencilFrontAndBack, -// value: 0xFF, -// }), -// write_mask: Some(DynamicStencilValue { -// face: StencilFaceFlags::StencilFrontAndBack, -// value: 0x00, -// }), -// reference: Some(DynamicStencilValue { -// face: StencilFaceFlags::StencilFrontAndBack, -// value: 0x00, -// }), -// }; - - // if !allocated_buffers.text_vertex_buffer.is_empty() { - // command_buffer = command_buffer.draw( - // shader.get_pipeline().clone(), - // &self.dynamic_state.clone(), - // allocated_buffers.text_vertex_buffer.clone(), - // (), (), - // ).unwrap(); - // } - - command_buffer - .end_render_pass() - .unwrap(); - } -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/canvas/managed/canvas_text.rs b/src/canvas/managed/canvas_text.rs deleted file mode 100644 index e583e8b7..00000000 --- a/src/canvas/managed/canvas_text.rs +++ /dev/null @@ -1,73 +0,0 @@ -use vulkano::descriptor::descriptor_set::PersistentDescriptorSet; -use rusttype::{Font, PositionedGlyph, Scale, Rect, point, GlyphId}; -use rusttype::gpu_cache::Cache; -use vulkano::buffer::{BufferAccess, BufferUsage, ImmutableBuffer, CpuAccessibleBuffer}; -use vulkano::device::{Device, Queue}; -use std::sync::Arc; -use vulkano::command_buffer::{AutoCommandBufferBuilder, DynamicState}; -use vulkano::image::{ImmutableImage, ImageUsage, ImageLayout, Dimensions}; -use vulkano::format::ClearValue; -use vulkano::format::Format::R8Unorm; -use std::fs::File; -use std::io::Read; - - -//pub struct Glyph {} -// -///// So currently, I'm using these as container classes which vkprocessor owns -///// I then use a CanvasFrame which accumulates lists of handles and vertices. -//pub struct CanvasFonto { -// font: Font<'static>, -// font_name: String, -// allocated_font_atlas: Arc<(dyn BufferAccess + Send + Sync)>, -//} -// -//impl CanvasFonto { -// -// pub fn parse_to_vertex_buffer(font: Font) -> Vec { -// -// let mut current_x = 0; -// let mut current_y = 0; -// -// let mut accumulator = Vec::new(); -// -// for i in (0..255) { -// -// let glyph = font.glyph(GlyphId{ 0: 40 }); -// -// let glyph_data = glyph.get_data().unwrap(); -// -// for vertex in glyph_data.clone().shape.clone().unwrap() { -// accumulator.push(TextVertex3D { -// position: [vertex.x as f32, vertex.y as f32, 0.0], -// }); -// } -// } -// -// accumulator -// } -// /// Load the font -// pub fn new(device: Arc, queue: Arc, font_name: String) -> CanvasFonto { -// -// let font = Font::from_bytes({ -// let mut f = File::open("resources/fonts/sansation.ttf").expect("Font file not found"); -// let mut font_data = Vec::new(); -// f.read_to_end(&mut font_data).expect("Dont know"); -// font_data -// }).unwrap(); -// -// CanvasFont { -// font: font.clone(), -// font_name: font_name, -// allocated_font_atlas: ImmutableBuffer::from_iter( -// CanvasFont::parse_to_vertex_buffer(font.clone()).iter().cloned(), -// BufferUsage::vertex_buffer(), queue).unwrap().0, -// } -// } -// -// /// Generate a vertex buffer from the font -// pub fn get_vertex_buffer(&self) -> Arc<(dyn BufferAccess + Send + Sync)> { -// return self.allocated_font_atlas.clone(); -// } -// -//} \ No newline at end of file diff --git a/src/canvas/managed/gpu_buffers.rs b/src/canvas/managed/gpu_buffers.rs deleted file mode 100644 index 6a2c2a38..00000000 --- a/src/canvas/managed/gpu_buffers.rs +++ /dev/null @@ -1,78 +0,0 @@ -use vulkano::image::{ImmutableImage, AttachmentImage}; -use std::sync::Arc; -use vulkano::format::{Format, R8Unorm}; -use vulkano::sampler::Sampler; -use vulkano::descriptor::{DescriptorSet, PipelineLayoutAbstract}; -use vulkano::descriptor::descriptor_set::PersistentDescriptorSet; -use vulkano::buffer::{CpuAccessibleBuffer, BufferAccess}; -use vulkano::pipeline::GraphicsPipelineAbstract; -use rusttype::Font; -use crate::canvas::managed::handles::{CanvasTextureHandle, CanvasImageHandle, CanvasFontHandle}; - - -/// Canvas buffer which represents an allocated Texture with a key and dimensions -#[derive(Clone)] -pub struct CanvasTexture { - pub(crate) handle: Arc, - pub(crate) buffer: Arc>, - pub(crate) name: String, - pub(crate) size: (u32, u32), -} - -impl CanvasTexture { - pub fn get_descriptor_set(&self, - pipeline: Arc, - sampler: Arc) -> Box { - let o: Box = Box::new( - PersistentDescriptorSet::start( - pipeline.clone().descriptor_set_layout(0).unwrap().clone(), - ) - .add_sampled_image(self.buffer.clone(), sampler.clone()).unwrap() - .build().unwrap()); - o - } -} - -/// Canvas buffer which represents an allocated image and dimension -#[derive(Clone)] -pub struct CanvasImage { - pub(crate) handle: Arc, - pub(crate) buffer: Arc, - pub(crate) size: (u32, u32), -} - -impl CanvasImage { - pub fn get_descriptor_set(&self, pipeline: Arc) - -> Box { - let o: Box = Box::new( - PersistentDescriptorSet::start( - pipeline.clone().descriptor_set_layout(0).unwrap().clone() - ) - .add_image(self.buffer.clone()).unwrap() - .build().unwrap()); - o - } -} - -/// Canvas Font which represents an allocated image and dimension -#[derive(Clone)] -pub struct CanvasFont { - pub(crate) handle: Arc, - pub(crate) buffer: Arc<(dyn BufferAccess + Send + Sync)>, // Font atlas - pub(crate) font: Font<'static>, - pub(crate) name: String, - -} - -impl CanvasFont { - pub fn get_descriptor_set(&self, pipeline: Arc) - -> Box { - let o: Box = Box::new( - PersistentDescriptorSet::start( - pipeline.clone().descriptor_set_layout(0).unwrap().clone() - ) - .add_buffer(self.buffer.clone()).unwrap() - .build().unwrap()); - o - } -} diff --git a/src/canvas/managed/handles.rs b/src/canvas/managed/handles.rs deleted file mode 100644 index a1463830..00000000 --- a/src/canvas/managed/handles.rs +++ /dev/null @@ -1,60 +0,0 @@ - -pub trait Handle { - fn get_handle(&self) -> u32; -} - -pub enum DrawableHandle { - Texture(CanvasTextureHandle), - Image(CanvasImageHandle), - Font(CanvasFontHandle), -} - -/// Typed wrapper for a u32 handle -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -pub struct CanvasFontHandle { - pub(in crate::canvas) handle: u32 -} - -impl Handle for CanvasFontHandle { - fn get_handle(&self) -> u32 { - self.handle - } -} - -/// Typed wrapper for a u32 handle -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -pub struct CanvasTextureHandle { - pub/*(in crate::canvas)*/ handle: u32 -} - -impl Handle for CanvasTextureHandle { - fn get_handle(&self) -> u32 { - self.handle - } -} - -/// Typed wrapper for a u32 handle -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -pub struct CanvasImageHandle { - pub(in crate::canvas) handle: u32 -} - -impl Handle for CanvasImageHandle { - fn get_handle(&self) -> u32 { - self.handle - } -} - -/// Typed wrapper for a u32 handle -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -pub struct CompiledShaderHandle { - pub(in crate::canvas) handle: u32 -} - -impl Handle for CompiledShaderHandle { - fn get_handle(&self) -> u32 { - self.handle - } -} - - diff --git a/src/canvas/managed/mod.rs b/src/canvas/managed/mod.rs deleted file mode 100644 index 682e852c..00000000 --- a/src/canvas/managed/mod.rs +++ /dev/null @@ -1,40 +0,0 @@ - -pub mod shader; -pub mod handles; -pub mod canvas_text; -pub mod gpu_buffers; - -use vulkano::pipeline::shader::{SpecializationConstants, SpecializationMapEntry}; - -#[repr(C)] -#[derive(Default, Debug, Clone)] -/// Specialization constants which can be passed to the shader. Pretty much placeholder ATM -struct ShaderSpecializationConstants { - first_constant: i32, - second_constant: u32, - third_constant: f32, -} - -unsafe impl SpecializationConstants for ShaderSpecializationConstants { - fn descriptors() -> &'static [SpecializationMapEntry] { - static DESCRIPTORS: [SpecializationMapEntry; 3] = [ - SpecializationMapEntry { - constant_id: 0, - offset: 0, - size: 4, - }, - SpecializationMapEntry { - constant_id: 1, - offset: 4, - size: 4, - }, - SpecializationMapEntry { - constant_id: 2, - offset: 8, - size: 4, - }, - ]; - - &DESCRIPTORS - } -} \ No newline at end of file diff --git a/src/canvas/managed/shader/dynamic_vertex.rs b/src/canvas/managed/shader/dynamic_vertex.rs deleted file mode 100644 index c3ba5f60..00000000 --- a/src/canvas/managed/shader/dynamic_vertex.rs +++ /dev/null @@ -1,182 +0,0 @@ -use vulkano::pipeline::vertex::{VertexDefinition, InputRate, AttributeInfo, IncompatibleVertexDefinitionError, VertexSource, VertexMemberInfo, VertexMemberTy}; -use vulkano::pipeline::shader::ShaderInterfaceDef; -use vulkano::buffer::BufferAccess; -use std::sync::Arc; -use cgmath::num_traits::real::Real; -use std::vec::IntoIter as VecIntoIter; -use std::mem; - -/// Runtime Vertex def is just a generic holder of "dynamic vertex definitions" -// This baby needs to be able to be copied.... -#[derive(Default, Debug, Clone)] -pub struct RuntimeVertexDef { - buffers: Vec<(u32, usize, InputRate)>, // (attribute id, stride, Vertex or Instance data) - vertex_buffer_ids: Vec<(usize, usize)>,// - attributes: Vec<(String, u32, AttributeInfo)>, - num_vertices: u32, -} - -impl RuntimeVertexDef { - - /// primitive is an input value or struct which can then describe - /// these damn values that are required for inputting them into vulkan - pub fn from_primitive(primitive: u32) -> RuntimeVertexDef { - - // Literally every value in this class - let mut buffers = Vec::new(); - let mut vertex_buffer_ids = Vec::new(); - let mut attributes = Vec::new(); - let mut num_vertices = u32::max_value(); - - // https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Box/glTF/Box.gltf - // https://github.com/tomaka/vulkano-examples/blob/gltf/gltf/gltf_system.rs - - - num_vertices = 3; - - -// for (attribute_id, attribute) in primitive.attributes().enumerate() { -// let (name, accessor) = match attribute.clone() { -// Attribute::Positions(accessor) => ("i_position".to_owned(), accessor), -// Attribute::Normals(accessor) => ("i_normal".to_owned(), accessor), -// Attribute::Tangents(accessor) => ("i_tangent".to_owned(), accessor), -// Attribute::Colors(0, accessor) => ("i_color_0".to_owned(), accessor), -// Attribute::TexCoords(0, accessor) => ("i_texcoord_0".to_owned(), accessor), -// Attribute::TexCoords(1, accessor) => ("i_texcoord_1".to_owned(), accessor), -// Attribute::Joints(0, accessor) => ("i_joints_0".to_owned(), accessor), -// Attribute::Weights(0, accessor) => ("i_weights_0".to_owned(), accessor), -// _ => unimplemented!(), -// }; -// -// if (accessor.count() as u32) < num_vertices { -// num_vertices = accessor.count() as u32; -// } -// -// let infos = AttributeInfo { -// offset: 0, -// format: match (accessor.data_type(), accessor.dimensions()) { -// (DataType::I8, Dimensions::Scalar) => Format::R8Snorm, -// (DataType::U8, Dimensions::Scalar) => Format::R8Unorm, -// (DataType::F32, Dimensions::Vec2) => Format::R32G32Sfloat, -// (DataType::F32, Dimensions::Vec3) => Format::R32G32B32Sfloat, -// (DataType::F32, Dimensions::Vec4) => Format::R32G32B32A32Sfloat, -// _ => unimplemented!() -// }, -// }; -// -// let view = accessor.view(); -// buffers.push((attribute_id as u32, -// view.stride().unwrap_or(accessor.size()), -// InputRate::Vertex -// )); -// attributes.push((name, attribute_id as u32, infos)); -// vertex_buffer_ids.push((view.buffer().index(), view.offset() + accessor.offset())); -// } - - RuntimeVertexDef { - buffers: buffers, - vertex_buffer_ids: vertex_buffer_ids, - num_vertices: num_vertices, - attributes: attributes, - } - } - - /// Returns the indices of the buffers to bind as vertex buffers and the byte offset, when - /// drawing the primitive. - pub fn vertex_buffer_ids(&self) -> &[(usize, usize)] { - &self.vertex_buffer_ids - } -} -/// Implementing VertexDefinition -unsafe impl VertexDefinition for RuntimeVertexDef - where I: ShaderInterfaceDef -{ - /// Iterator that returns the offset, the stride (in bytes) and input rate of each buffer. - type BuffersIter = VecIntoIter<(u32, usize, InputRate)>; - /// Iterator that returns the attribute location, buffer id, and infos. - type AttribsIter = VecIntoIter<(u32, u32, AttributeInfo)>; - - /// Builds the vertex definition to use to link this definition to a vertex shader's input - /// interface. - /// - /// At this point I need to have enough information from the implementing type to - /// describe its elements - /// - /// Needs: - /// buffers - /// attributes - /// - fn definition(&self, interface: &I) - -> Result<(Self::BuffersIter, Self::AttribsIter), IncompatibleVertexDefinitionError> - { - - let buffers_iter = self.buffers.clone().into_iter(); - - let mut attributes = Vec::default(); - - for input in interface.elements() { - - attributes.push(( - input.location.start as u32, - input.location.start as u32, - AttributeInfo { offset: 0, format: input.format } - )); - - println!("{:?}", input.location); - println!("{:?}", input.format); - println!("{:?}", input.name); - } - -// let mut attribs_iter = self.attributes.iter().map(|&(ref name, buffer_id, ref infos)| { -// let attrib_loc = interface -// .elements() -// .find(|e| e.name.as_ref().map(|n| &n[..]) == Some(&name[..])) -// .unwrap() -// .location.start; -// -// ( -// attrib_loc as u32, -// buffer_id, -// AttributeInfo { offset: infos.offset, format: infos.format } -// ) -// }).collect::>(); - - - // This does nothing? - for binding in interface.elements() { - if attributes.iter().any(|a| a.0 == binding.location.start) { - continue; - } - - attributes.push((binding.location.start, 0, - AttributeInfo { offset: 0, format: binding.format })); - } - - // The number of actually bound inputs - let buffers = vec![ - (0, mem::size_of::(), InputRate::Vertex), - (1, mem::size_of::(), InputRate::Vertex), - (2, mem::size_of::(), InputRate::Vertex), - (3, mem::size_of::(), InputRate::Vertex), - (4, mem::size_of::(), InputRate::Vertex), - (5, mem::size_of::(), InputRate::Vertex), - (6, mem::size_of::(), InputRate::Vertex), - ].into_iter(); - - Ok((buffers, attributes.into_iter())) - } -} - -/// I don't know what the fuck is going on here... It just repackages the buffs -/// Needs the num vertices -unsafe impl VertexSource>> for RuntimeVertexDef { - fn decode(&self, bufs: Vec>) - -> (Vec>, usize, usize) - { - ( - bufs.into_iter().map(|b| Box::new(b) as Box<_>).collect(), // Box up the buffers - self.num_vertices as usize, // Number of vertices - 1 // Number of instances - ) - } -} diff --git a/src/canvas/managed/shader/generic_shader.rs b/src/canvas/managed/shader/generic_shader.rs deleted file mode 100644 index 9e156daf..00000000 --- a/src/canvas/managed/shader/generic_shader.rs +++ /dev/null @@ -1,145 +0,0 @@ - -use vulkano::pipeline::GraphicsPipelineAbstract; -use std::sync::Arc; -use std::collections::{HashSet, HashMap}; -use vulkano::device::Device; -use vulkano::framebuffer::{RenderPassAbstract, Subpass}; -use vulkano::pipeline::GraphicsPipeline; -use vulkano::pipeline::shader::{GraphicsEntryPoint, ShaderModule, GraphicsShaderType, GeometryShaderExecutionMode, ShaderInterfaceDef}; -use shade_runner::{Input, Output, Layout, Entry}; -use std::ffi::CStr; -use std::marker::PhantomData; -use vulkano::pipeline::depth_stencil::{DepthStencil, Compare, DepthBounds, Stencil, StencilOp}; -use vulkano::pipeline::vertex::{SingleBufferDefinition, VertexDefinition, Vertex}; -use shade_runner as sr; -use vulkano::memory::pool::PotentialDedicatedAllocation::Generic; -use vulkano::SafeDeref; -use crate::canvas::managed::shader::shader_common::{ShaderType, CompiledShaderResources, CompiledShader}; -use crate::canvas::managed::handles::CompiledShaderHandle; -use crate::canvas::managed::shader::dynamic_vertex::RuntimeVertexDef; -use crate::canvas::managed::ShaderSpecializationConstants; -use crate::util::vertex::{VertexType, ColorVertex3D}; - -/// CanvasShader holds the pipeline and render pass for the input shader source -#[derive(Clone)] -pub struct GenericShader { - graphics_pipeline: Option>, - - handle: Arc, - name: String, - - device: Arc, - renderpass: Arc, -} - - -impl GenericShader { -} - -/// Gives CanvasShader the resource functions -impl CompiledShaderResources for GenericShader {} - -/// Convenience interface so we don't have to juggle shader types -impl CompiledShader for GenericShader { - - /// This will explode when the shader does not want to compile - fn new(filename: String, - device: Arc, - handle: Arc, - render_pass: Arc) -> GenericShader { - - let compiled_vertex = GenericShader::compile( - GenericShader::get_path(filename.clone(), ShaderType::VERTEX), - device.clone(), ShaderType::VERTEX - ); - - let vertex_entry_point = unsafe { - Some(compiled_vertex.1.graphics_entry_point( - &CStr::from_bytes_with_nul_unchecked(b"main\0"), - compiled_vertex.0.input.unwrap(), - compiled_vertex.0.output.unwrap(), - compiled_vertex.0.layout, - GenericShader::convert_vk(ShaderType::VERTEX) - )).unwrap() - }; - - let compiled_fragment = GenericShader::compile( - GenericShader::get_path(filename.clone(), ShaderType::FRAGMENT).into(), - device.clone(), ShaderType::FRAGMENT - ); - - let fragment_entry_point = unsafe { - Some(compiled_fragment.1.graphics_entry_point( - &CStr::from_bytes_with_nul_unchecked(b"main\0"), - compiled_fragment.0.input.unwrap(), - compiled_fragment.0.output.unwrap(), - compiled_fragment.0.layout, - GenericShader::convert_vk(ShaderType::FRAGMENT) - )).unwrap() - }; - - let vertex_definition = RuntimeVertexDef::from_primitive(0); - - GenericShader { - graphics_pipeline: - Some(Arc::new(GraphicsPipeline::start() - - .vertex_input(SingleBufferDefinition::::new()) - //.vertex_input(vertex_definition) - - .vertex_shader(vertex_entry_point.clone(), ShaderSpecializationConstants { - first_constant: 0, - second_constant: 0, - third_constant: 0.0, - }) - - .triangle_list() - // Use a resizable viewport set to draw over the entire window - .viewports_dynamic_scissors_irrelevant(1) - - .fragment_shader(fragment_entry_point.clone(), ShaderSpecializationConstants { - first_constant: 0, - second_constant: 0, - third_constant: 0.0, - }) - - .depth_stencil_simple_depth() - - // We have to indicate which subpass of which render pass this pipeline is going to be used - // in. The pipeline will only be usable from this particular subpass. - .render_pass(Subpass::from(render_pass.clone(), 0).unwrap()) - - .build(device.clone()) - .unwrap())), - - device: device, - handle: handle.clone(), - name: filename.clone(), - renderpass: render_pass.clone(), - } - } - - fn get_name(&self) -> String { - self.name.clone() - } - - fn get_handle(&self) -> Arc { - self.handle.clone() - } - - fn get_pipeline(&self) -> Arc { - self.graphics_pipeline.clone().unwrap() - } - - fn get_renderpass(&self) -> Arc { - self.renderpass.clone() - } - - fn recompile(self, render_pass: Arc) -> GenericShader { - GenericShader::new::(self.name, - self.device, - self.handle, - render_pass.clone()) - } -} - diff --git a/src/canvas/managed/shader/mod.rs b/src/canvas/managed/shader/mod.rs deleted file mode 100644 index 9c56f6ec..00000000 --- a/src/canvas/managed/shader/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod text_shader; -pub mod generic_shader; -pub mod dynamic_vertex; -pub mod shader_common; \ No newline at end of file diff --git a/src/canvas/managed/shader/shader_common.rs b/src/canvas/managed/shader/shader_common.rs deleted file mode 100644 index 14c5a8f1..00000000 --- a/src/canvas/managed/shader/shader_common.rs +++ /dev/null @@ -1,124 +0,0 @@ - -use std::collections::HashSet; -use std::path::PathBuf; -use vulkano::pipeline::GraphicsPipelineAbstract; -use vulkano::framebuffer::RenderPassAbstract; -use std::sync::Arc; -use vulkano::pipeline::shader::{ShaderModule, GraphicsShaderType, GeometryShaderExecutionMode}; -use vulkano::device::Device; -use shade_runner::Entry; -use shaderc::ShaderKind; -use crate::canvas::managed::handles::CompiledShaderHandle; -use vulkano::pipeline::vertex::Vertex; - -/* - -Realistically, what should the API for this thing look like... - -It's going to just generate a pipeline. But that consists of loading and compiling various shaders, - and generating a pipeline for those shaders and other customer behaviour. - -This best works I think if I allow users to - A.) impl from a base trait which allows resource lookup - B.) Generate 1 of each of the types of shaders - C.) Modify specilization constants, whatever that might mean - D.) impl from a base trait which defines it's interface - -*/ - - - -/// Inheriting this gives private functions to grab resources -pub trait CompiledShaderResources { - - fn get_path(filename: String, shader_type: ShaderType) -> PathBuf { - let project_root = - std::env::current_dir() - .expect("failed to get root directory"); - - let mut shader_path = project_root.clone(); - shader_path.push(PathBuf::from("resources/shaders/")); - - - let mut shader_path = shader_path.clone(); - - match shader_type { - ShaderType::VERTEX => { - shader_path.push(PathBuf::from(filename.clone() + ".vert")); - } - ShaderType::FRAGMENT => { - shader_path.push(PathBuf::from(filename.clone() + ".frag")); - } - ShaderType::GEOMETRY => { - shader_path.push(PathBuf::from(filename.clone() + ".geom")); - } - ShaderType::TESSELLATION_CONTROL => { - shader_path.push(PathBuf::from(filename.clone() + ".tesscont")); - } - ShaderType::TESSELLATION_EVALUATION => { - shader_path.push(PathBuf::from(filename.clone() + ".tesseval")); - } - } - - shader_path - } - - - fn compile(filepath: PathBuf, device: Arc, shader_type: ShaderType) -> (Entry, Arc) { - let compiled_shader = shade_runner::load(filepath, None, Self::convert_sr(shader_type), None) - .expect("Shader didn't compile"); - - let vulkano_entry = - shade_runner::parse(&compiled_shader) - .expect("failed to parse"); - - (vulkano_entry, unsafe { - ShaderModule::from_words(device.clone(), &compiled_shader.spriv.clone()) - }.unwrap()) - } - - fn convert_vk(shader_type: ShaderType) -> GraphicsShaderType { - match shader_type { - ShaderType::VERTEX => { GraphicsShaderType::Vertex } - ShaderType::FRAGMENT => { GraphicsShaderType::Fragment } - ShaderType::GEOMETRY => { GraphicsShaderType::Geometry(GeometryShaderExecutionMode::Triangles) } - ShaderType::TESSELLATION_CONTROL => { GraphicsShaderType::TessellationControl } - ShaderType::TESSELLATION_EVALUATION => { GraphicsShaderType::TessellationEvaluation } - } - } - - fn convert_sr(shader_type: ShaderType) -> ShaderKind { - match shader_type { - ShaderType::VERTEX => { ShaderKind::Vertex } - ShaderType::FRAGMENT => { ShaderKind::Fragment } - ShaderType::GEOMETRY => { ShaderKind::Geometry } - ShaderType::TESSELLATION_CONTROL => { ShaderKind::TessControl } - ShaderType::TESSELLATION_EVALUATION => { ShaderKind::TessEvaluation } - } - } -} - - - -pub trait CompiledShader { - fn new(filename: String, - device: Arc, - handle: Arc, - render_pass: Arc) -> Self where Self: Sized, V: Vertex,; - fn get_name(&self) -> String; - fn get_handle(&self) -> Arc; - fn get_pipeline(&self) -> Arc; - fn get_renderpass(&self) -> Arc; - fn recompile(self, render_pass: Arc) - -> Self where Self: Sized; -} - -/// Legacy ShaderType enum for single type shaders. -#[derive(PartialEq, Eq, Hash, Clone)] -pub enum ShaderType { - VERTEX = 0, - FRAGMENT = 1, - GEOMETRY = 2, - TESSELLATION_CONTROL = 3, - TESSELLATION_EVALUATION = 4, -} \ No newline at end of file diff --git a/src/canvas/managed/shader/text_shader.rs b/src/canvas/managed/shader/text_shader.rs deleted file mode 100644 index 28bfceb7..00000000 --- a/src/canvas/managed/shader/text_shader.rs +++ /dev/null @@ -1,195 +0,0 @@ -use vulkano::pipeline::GraphicsPipelineAbstract; -use std::sync::Arc; -use std::collections::{HashSet, HashMap}; -use vulkano::device::Device; -use vulkano::framebuffer::{RenderPassAbstract, Subpass}; -use vulkano::pipeline::GraphicsPipeline; -use vulkano::pipeline::shader::{GraphicsEntryPoint, ShaderModule, GraphicsShaderType, GeometryShaderExecutionMode}; -use shade_runner::{Input, Output, Layout, Entry}; -use std::ffi::CStr; -use std::marker::PhantomData; -use vulkano::pipeline::depth_stencil::{DepthStencil, Compare, DepthBounds, Stencil, StencilOp}; -use vulkano::pipeline::vertex::{SingleBufferDefinition, OneVertexOneInstanceDefinition, Vertex}; -use shade_runner as sr; -use crate::canvas::managed::shader::shader_common::{ShaderType, CompiledShaderResources, CompiledShader}; -use crate::canvas::managed::handles::CompiledShaderHandle; -use crate::canvas::managed::shader::generic_shader::GenericShader; -use crate::canvas::managed::shader::dynamic_vertex::RuntimeVertexDef; -use crate::canvas::managed::ShaderSpecializationConstants; -use crate::util::vertex::ColorVertex3D; -use vulkano::pipeline::blend::{LogicOp, AttachmentBlend, BlendOp, BlendFactor}; - - -/// CanvasShader holds the pipeline and render pass for the input shader source -#[derive(Clone)] -pub struct TextShader { - graphics_pipeline: Option>, - - handle: Arc, - name: String, - - device: Arc, - renderpass: Arc, -} - -impl TextShader {} - -/// Gives CanvasShader the resource functions -impl CompiledShaderResources for TextShader {} - -/// Convenience interface so we don't have to juggle shader types -impl CompiledShader for TextShader { - - /// This will explode when the shader does not want to compile - fn new(filename: String, - device: Arc, - handle: Arc, - render_pass: Arc) -> TextShader { - - let compiled_vertex = GenericShader::compile( - GenericShader::get_path(filename.clone(), ShaderType::VERTEX).into(), - device.clone(), ShaderType::VERTEX - ); - - let vertex_entry_point = unsafe { - Some(compiled_vertex.1.graphics_entry_point( - &CStr::from_bytes_with_nul_unchecked(b"main\0"), - compiled_vertex.0.input.unwrap(), - compiled_vertex.0.output.unwrap(), - compiled_vertex.0.layout, - GenericShader::convert_vk(ShaderType::VERTEX), - )).unwrap() - }; - - let compiled_fragment = GenericShader::compile( - GenericShader::get_path(filename.clone(), ShaderType::FRAGMENT).into(), - device.clone(), ShaderType::FRAGMENT - ); - - let fragment_entry_point = unsafe { - Some(compiled_fragment.1.graphics_entry_point( - &CStr::from_bytes_with_nul_unchecked(b"main\0"), - compiled_fragment.0.input.unwrap(), - compiled_fragment.0.output.unwrap(), - compiled_fragment.0.layout, - GenericShader::convert_vk(ShaderType::FRAGMENT), - )).unwrap() - }; - - let stencil = DepthStencil { - depth_compare: Compare::Less, - depth_write: true, - depth_bounds_test: DepthBounds::Disabled, - stencil_front: Stencil { - compare: Compare::NotEqual, - pass_op: StencilOp::Keep, - fail_op: StencilOp::IncrementAndWrap, - depth_fail_op: StencilOp::Keep, - compare_mask: None, - write_mask: None, - reference: None, - }, - stencil_back: Stencil { - compare: Compare::NotEqual, - pass_op: StencilOp::Keep, - fail_op: StencilOp::DecrementAndWrap, - depth_fail_op: StencilOp::Keep, - compare_mask: None, - write_mask: None, - reference: None, - }, - }; - - let vertex_definition = RuntimeVertexDef::from_primitive(0); - - - let blend = AttachmentBlend { - enabled: true, - color_op: BlendOp::Add, - // color_source: BlendFactor::SrcAlpha, - // color_destination: BlendFactor::OneMinusSrcAlpha, - color_source: BlendFactor::One, - color_destination: BlendFactor::One, - alpha_op: BlendOp::Add, - alpha_source: BlendFactor::One, - alpha_destination: BlendFactor::One, - mask_red: true, - mask_green: true, - mask_blue: true, - mask_alpha: true, - }; - - TextShader { - graphics_pipeline: - Some(Arc::new(GraphicsPipeline::start() - - .vertex_input(SingleBufferDefinition::::new()) - //.vertex_input(vertex_definition) - - .vertex_shader(vertex_entry_point.clone(), ShaderSpecializationConstants { - first_constant: 0, - second_constant: 0, - third_constant: 0.0, - }) - - .triangle_list() - // Use a resizable viewport set to draw over the entire window - .viewports_dynamic_scissors_irrelevant(1) - - .fragment_shader(fragment_entry_point.clone(), ShaderSpecializationConstants { - first_constant: 0, - second_constant: 0, - third_constant: 0.0, - }) - - .depth_stencil(stencil) -// .blend_collective(blend) - - //.blend_logic_op(LogicOp::Noop) - // We have to indicate which subpass of which render pass this pipeline is going to be used - // in. The pipeline will only be usable from this particular subpass. - .render_pass(Subpass::from(render_pass.clone(), 0).unwrap()) - - .build(device.clone()) - .unwrap())), - - device: device, - handle: handle.clone(), - name: filename.clone(), - renderpass: render_pass.clone(), - } - } - - fn get_name(&self) -> String { - self.name.clone() - } - - fn get_handle(&self) -> Arc { - self.handle.clone() - } - - fn get_pipeline(&self) -> Arc { - self.graphics_pipeline.clone().unwrap() - } - fn get_renderpass(&self) -> Arc { - self.renderpass.clone() - } - fn recompile(self, render_pass: Arc) -> TextShader { - TextShader::new::(self.name, - self.device, - self.handle, - self.renderpass.clone()) - } -} - - - - - - - - - - - - diff --git a/src/canvas/mod.rs b/src/canvas/mod.rs deleted file mode 100644 index 71f7215a..00000000 --- a/src/canvas/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ - -pub mod canvas_state; -pub mod canvas_frame; -pub mod managed; - - diff --git a/src/compute/compu_frame.rs b/src/compute/compu_frame.rs deleted file mode 100644 index 0c679278..00000000 --- a/src/compute/compu_frame.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::sync::Arc; -use crate::canvas::managed::handles::{CanvasImageHandle}; -use crate::compute::managed::handles::{CompuKernelHandle, CompuBufferHandle}; -use crate::drawables::compu_sprite::CompuSprite; -use crate::canvas::canvas_frame::Drawable; -use crate::util::vertex::VertexType; - -pub struct CompuFrame { - // Vec<(Buffer, Kernel)> - pub pure_compute: Vec<( - Arc, - Arc)>, - - // Vec<(Buffer, Image, Kernel)> - pub swapped_to_image: Vec<( - Arc, - Arc, - Arc)>, - - // Vec<(Input Buffer, Output Buffer, Kernel)> - pub swapped_to_buffer: Vec<( - Arc, - Arc, - Arc)>, - - window_size: (u32, u32), -} - -impl CompuFrame { - pub fn new(window_size: (u32, u32)) -> CompuFrame { - CompuFrame { - pure_compute: vec![], - swapped_to_image: vec![], - swapped_to_buffer: vec![], - window_size: window_size, - } - } - - pub fn add(&mut self, buffer: Arc, kernel: Arc) { - self.pure_compute.push((buffer, kernel)); - } - - /* - INPUT_BUFFER -> input -> kernel -> output - v------------------^ - OUTPUT_BUFFER -> input X kernel X output - */ - pub fn add_chained(&mut self, - input_buffer: Arc, - output_buffer: Arc, - kernel: Arc) { - self.swapped_to_buffer.push((input_buffer, output_buffer, kernel)); - } - - pub fn add_with_image_swap(&mut self, - buffer: Arc, - kernel: Arc, - sprite: &CompuSprite) { - - let compu_sprites = sprite.get(self.window_size); - - if compu_sprites.len() == 1 { - if let VertexType::ImageType(a, b) = compu_sprites.first().unwrap() { - self.swapped_to_image.push((buffer, b.clone(), kernel)) - }; - } - - } -} \ No newline at end of file diff --git a/src/compute/compu_state.rs b/src/compute/compu_state.rs deleted file mode 100644 index cbeeea9c..00000000 --- a/src/compute/compu_state.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::ffi::CStr; -use vulkano::buffer::{CpuAccessibleBuffer, BufferUsage}; -use std::sync::Arc; -use vulkano::framebuffer::RenderPassAbstract; -use vulkano::pipeline::{GraphicsPipelineAbstract, ComputePipeline}; -use vulkano::device::Device; -use image::ImageBuffer; -use image::GenericImageView; -use vulkano::image::{ImageUsage, AttachmentImage}; -use vulkano::descriptor::descriptor_set::{PersistentDescriptorSetBuf, PersistentDescriptorSet}; -use vulkano::format::Format; -use vulkano::descriptor::pipeline_layout::PipelineLayout; -use std::borrow::Borrow; -use image::Rgba; -use vulkano::command_buffer::AutoCommandBufferBuilder; -use std::path::PathBuf; -use shade_runner::{CompiledShaders, Entry, CompileError}; -use vulkano::pipeline::shader::ShaderModule; -use shaderc::CompileOptions; -use crate::compute::compu_frame::CompuFrame; -use crate::canvas::managed::handles::Handle; -use crate::compute::managed::compu_buffer::CompuBuffers; -use crate::compute::managed::handles::{CompuKernelHandle, CompuBufferHandle}; -use crate::compute::managed::compu_kernel::CompuKernel; -use crate::canvas::canvas_state::CanvasState; - - -/// State holding the compute buffers for computation and the kernels which will compute them -pub struct CompuState { - compute_buffers: Vec, - kernels: Vec, -} - -impl CompuState { - - pub fn new() -> CompuState { - CompuState { - compute_buffers: vec![], - kernels: vec![], - } - } - - /// Creates a 2d compute buffer from incoming data - pub fn new_compute_buffer(&mut self, - data: Vec, - dimensions: (u32, u32), - stride: u32, - device: Arc) -> Arc { - - let handle = Arc::new(CompuBufferHandle { - handle: self.compute_buffers.len() as u32 - }); - - self.compute_buffers.push( - (CompuBuffers::new(device.clone(), data, dimensions, stride, handle.clone()))); - - handle - } - - pub fn read_compute_buffer(&mut self, handle: Arc) -> Vec { - let mut buffer : &CompuBuffers = self.compute_buffers.get(handle.handle as usize).unwrap(); - let v = buffer.read_output_buffer(); - v.into_vec() - } - - /// Write to the compute buffer, ostensibly overwriting what's already there - pub fn write_compute_buffer(&self, handle: Arc, data: Vec) { - unimplemented!("read_compute_buffer is not implemented") - } - - pub fn new_kernel(&mut self, - filename: String, - device: Arc) -> Arc { - let handle = Arc::new(CompuKernelHandle { - handle: self.kernels.len() as u32 - }); - - self.kernels.push((CompuKernel::new(filename, device.clone(), handle.clone()))); - - handle - } - - pub fn get_kernel_handle(&self, kernel_name: String) -> Option> { - for i in self.kernels.clone() { - if i.get_name() == kernel_name { - return Some(i.get_handle()); - } - } - None - } - - pub fn compute_commands(&mut self, - compute_frame: &CompuFrame, - mut command_buffer: &mut AutoCommandBufferBuilder, - canvas: &CanvasState) { - - // i = (Buffer, Kernel) - for i in &compute_frame.pure_compute { - let buffer_id = (*i.0).clone().get_handle() as usize; - let kernel_id = (*i.1).clone().get_handle() as usize; - - let buffer = self.compute_buffers.get(buffer_id).unwrap(); - let kernel = self.kernels.get(kernel_id).unwrap(); - - let pipeline = kernel.clone().get_pipeline(); - let descriptorset = buffer.get_descriptor_set(kernel.clone().get_pipeline()); - - let size = buffer.get_size(); - - command_buffer = command_buffer - .dispatch([size.0 / 8, size.1 / 8, 1], pipeline, descriptorset, ()).unwrap() - } - - // i = (Buffer, Image, Kernel) - for i in &compute_frame.swapped_to_image { - let buffer_id = (*i.0).clone().get_handle() as usize; - let image_id = i.1.clone(); - let kernel_id = (*i.2).clone().handle as usize; - - let buffer = self.compute_buffers.get(buffer_id).unwrap(); - let image = canvas.get_image(image_id); - let kernel = self.kernels.get(kernel_id).unwrap(); - - let p = kernel.clone().get_pipeline(); - let d = buffer.get_descriptor_set(kernel.clone().get_pipeline()); - - let dimensions = image.dimensions(); - let dimensions = (dimensions[0], dimensions[1]); - if dimensions != buffer.get_size() { - panic!("Buffer sizes not the same"); - } - - let size = buffer.get_size(); - - command_buffer = command_buffer - .dispatch([size.0 / 8, size.1 / 8, 1], p, d, ()).unwrap() - .copy_buffer_to_image(buffer.get_output_buffer(), image).unwrap(); - } - - - // i = (Input Buffer, Output Buffer, Kernel) - // Input buffer -> Kernel -> Output buffer - for i in &compute_frame.swapped_to_buffer { - let input_buffer_id = (*i.0).clone().get_handle() as usize; - let output_buffer_id = (*i.1).clone().get_handle() as usize; - let kernel_id = (*i.2).clone().handle as usize; - - let input_buffer = self.compute_buffers.get(input_buffer_id).unwrap(); - let output_buffer = self.compute_buffers.get(output_buffer_id).unwrap(); - let kernel = self.kernels.get(kernel_id).unwrap(); - - let pipeline = kernel.clone().get_pipeline(); - let descriptor_set = input_buffer.get_descriptor_set(kernel.clone().get_pipeline()); - - if input_buffer.get_size() != output_buffer.get_size() { - panic!("Buffer sizes not the same"); - } - let size = input_buffer.get_size(); - - command_buffer = command_buffer - // .dispatch([size.0/8, size.1/8,1], pipeline, descriptor_set, ()).unwrap() - .copy_buffer( - input_buffer.get_output_buffer(), - output_buffer.get_input_buffer()).unwrap(); - } - - } -} - diff --git a/src/compute/managed/compu_buffer.rs b/src/compute/managed/compu_buffer.rs deleted file mode 100644 index f8020ef5..00000000 --- a/src/compute/managed/compu_buffer.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::sync::Arc; -use vulkano::device::Device; -use vulkano::buffer::{CpuAccessibleBuffer, BufferUsage}; -use vulkano::pipeline::ComputePipeline; -use vulkano::descriptor::pipeline_layout::PipelineLayout; -use vulkano::descriptor::descriptor_set::{PersistentDescriptorSet, PersistentDescriptorSetBuf}; -use image::ImageBuffer; -use image::Rgba; -use shade_runner::Layout; -use crate::compute::managed::handles::CompuBufferHandle; -use vulkano::descriptor::PipelineLayoutAbstract; - - -#[derive(Clone)] -pub struct CompuBuffers { - dimensions: (u32, u32), - device: Arc, - handle: Arc, - - io_buffers: Vec>>, - settings_buffer: Arc>, -} - -impl CompuBuffers { - pub fn new(device: Arc, data: Vec, - dimensions: (u32, u32), stride: u32, - handle: Arc) -> CompuBuffers { - - let data_length = dimensions.0 * dimensions.1 * stride; - - let input_buffer = { - let mut buff = data.iter(); - let data_iter = (0..data_length).map(|n| *(buff.next().unwrap())); - CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::all(), false, data_iter).unwrap() - }; - - let output_data = vec![0; data_length as usize]; - let output_buffer = { - let mut buff = output_data.iter(); - let data_iter = (0..data_length).map(|n| *(buff.next().unwrap())); - CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::all(), false, data_iter).unwrap() - }; - - // Settings buffer which holds i32's - // TODO: Compile macros into the kernel eventually to index them - let settings_buffer = { - let vec = vec![dimensions.0, dimensions.1]; - let mut buff = vec.iter(); - let data_iter = - (0..2).map(|n| *(buff.next().unwrap())); - CpuAccessibleBuffer::from_iter(device.clone(), - BufferUsage::all(), - false, data_iter).unwrap() - }; - - CompuBuffers { - dimensions: dimensions, - device: device.clone(), - - handle: handle, - io_buffers: vec![output_buffer, input_buffer], - settings_buffer: settings_buffer, - } - } - - pub fn get_size(&self) -> (u32, u32) { - self.dimensions - } - - pub fn get_descriptor_set(&self, compute_pipeline: std::sync::Arc>>) - -> Arc>>), - PersistentDescriptorSetBuf>>), - PersistentDescriptorSetBuf>>)>> { - - Arc::new(PersistentDescriptorSet::start(compute_pipeline.clone().descriptor_set_layout(0).unwrap().clone()) - .add_buffer(self.io_buffers.get(0).unwrap().clone()).unwrap() - .add_buffer(self.io_buffers.get(1).unwrap().clone()).unwrap() - .add_buffer(self.settings_buffer.clone()).unwrap() - .build().unwrap()) - } - - pub fn read_output_buffer(&self) -> ImageBuffer, Vec> { - let xy = self.get_size(); - - self.io_buffers.get(0).unwrap().write().unwrap().map(|x| x); - let data_buffer_content = self.io_buffers.get(0).unwrap().read().unwrap(); - - ImageBuffer::from_fn(xy.0, xy.1, |x, y| { - let r = data_buffer_content[((xy.0 * y + x) * 4 + 0) as usize] as u8; - let g = data_buffer_content[((xy.0 * y + x) * 4 + 1) as usize] as u8; - let b = data_buffer_content[((xy.0 * y + x) * 4 + 2) as usize] as u8; - let a = data_buffer_content[((xy.0 * y + x) * 4 + 3) as usize] as u8; - - image::Rgba([r, g, b, a]) - }) - } - - pub fn get_input_buffer(&self) -> Arc> { - self.io_buffers.get(1).unwrap().clone() - } - - pub fn get_output_buffer(&self) -> Arc> { - self.io_buffers.get(0).unwrap().clone() - } -} diff --git a/src/compute/managed/compu_kernel.rs b/src/compute/managed/compu_kernel.rs deleted file mode 100644 index c3879a2a..00000000 --- a/src/compute/managed/compu_kernel.rs +++ /dev/null @@ -1,161 +0,0 @@ -use vulkano::device::{Device}; -use vulkano::pipeline::{ComputePipeline}; -use std::sync::Arc; -use std::ffi::CStr; -use std::path::PathBuf; -use shade_runner as sr; -use vulkano::descriptor::pipeline_layout::PipelineLayout; -use shade_runner::{CompileError, Layout, Input, Output, CompiledShaders, Entry, CompiledShader}; -use shaderc::CompileOptions; -use vulkano::pipeline::shader::{ShaderModule, GraphicsEntryPoint, SpecializationConstants, SpecializationMapEntry}; -use crate::compute::managed::handles::CompuKernelHandle; - - -#[derive(Clone)] -pub struct CompuKernel { - - handle: Arc, - - compute_pipeline: Option>>>, - compute_kernel_path: PathBuf, - - name: String, - - shader: CompiledShader, - entry: Entry, - shader_module: Arc, - device: Arc, - specialization_constants: ComputeSpecializationConstants, -} - -impl CompuKernel { - - fn get_path(filename: String) -> PathBuf { - - let project_root = - std::env::current_dir() - .expect("failed to get root directory"); - - let mut compute_path = project_root.clone(); - compute_path.push(PathBuf::from("resources/shaders/")); - compute_path.push(PathBuf::from(filename)); - - compute_path - } - - pub fn new(filename: String, device: Arc, - handle: Arc) -> CompuKernel { - - let compute_path = CompuKernel::get_path(filename.clone()); - - let mut options = CompileOptions::new().ok_or(CompileError::CreateCompiler).unwrap(); - - let shader = sr::load_compute(compute_path.clone(), Some(options)) - .expect("Failed to compile"); - - let entry = sr::parse_compute(&shader) - .expect("Failed to parse"); - - let shader_module = unsafe { - vulkano::pipeline::shader::ShaderModule::from_words(device.clone(), &shader.spriv) - }.unwrap(); - - - CompuKernel { - name: filename, - handle: handle, - device: device, - shader: shader, - compute_pipeline: Option::None, - compute_kernel_path: compute_path, - entry: entry, - shader_module: shader_module, - specialization_constants: ComputeSpecializationConstants { - first_constant: 0, - second_constant: 0, - third_constant: 0.0 - } - } - } - - pub fn get_pipeline(&mut self) -> std::sync::Arc>> { - - match self.compute_pipeline.clone() { - Some(t) => t, - None => { - self.compute_pipeline = Some(Arc::new({ - unsafe { - ComputePipeline::new(self.device.clone(), &self.shader_module.compute_entry_point( - CStr::from_bytes_with_nul_unchecked(b"main\0"), - self.entry.layout.clone()), &self.specialization_constants, - ).unwrap() - } - })); - self.compute_pipeline.clone().unwrap() - } - } - } - - pub fn recompile_kernel(&mut self) -> std::sync::Arc>> { - self.compile_kernel(String::from(self.compute_kernel_path.clone().to_str().unwrap())) - } - - pub fn compile_kernel(&mut self, filename: String) -> std::sync::Arc>> { - - let mut options = CompileOptions::new().ok_or(CompileError::CreateCompiler).unwrap(); - self.compute_kernel_path = CompuKernel::get_path(filename); - - self.shader = - sr::load_compute(self.compute_kernel_path.clone(), Some(options)) - .expect("Failed to compile"); - - self.entry = - sr::parse_compute(&self.shader) - .expect("Failed to parse"); - - self.shader_module = unsafe { - vulkano::pipeline::shader::ShaderModule::from_words(self.device.clone(), &self.shader.spriv) - }.unwrap(); - - self.get_pipeline() - } - - pub fn get_handle(&self) -> Arc { - self.handle.clone() - } - pub fn get_name(&self) -> String { - self.name.clone() - } -} - -#[repr(C)] -#[derive(Default, Debug, Clone)] -pub struct ComputeSpecializationConstants { - first_constant: i32, - second_constant: u32, - third_constant: f32, -} - -unsafe impl SpecializationConstants for ComputeSpecializationConstants { - fn descriptors() -> &'static [SpecializationMapEntry] { - static DESCRIPTORS: [SpecializationMapEntry; 3] = [ - SpecializationMapEntry { - constant_id: 0, - offset: 0, - size: 4, - }, - SpecializationMapEntry { - constant_id: 1, - offset: 4, - size: 4, - }, - SpecializationMapEntry { - constant_id: 2, - offset: 8, - size: 4, - }, - ]; - - &DESCRIPTORS - } -} diff --git a/src/compute/managed/handles.rs b/src/compute/managed/handles.rs deleted file mode 100644 index f5338cec..00000000 --- a/src/compute/managed/handles.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::canvas::managed::handles::Handle; - -/// Typed wrapper for a u32 handle -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -pub struct CompuBufferHandle { - pub(in crate::compute) handle: u32, -} - -impl Handle for CompuBufferHandle { - fn get_handle(&self) -> u32 { - self.handle - } -} - -/// Typed wrapper for a u32 handle -#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] -pub struct CompuKernelHandle { - pub(in crate::compute) handle: u32, -} - -impl Handle for CompuKernelHandle { - fn get_handle(&self) -> u32 { - self.handle - } -} - - - - diff --git a/src/compute/managed/mod.rs b/src/compute/managed/mod.rs deleted file mode 100644 index a34df75a..00000000 --- a/src/compute/managed/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ - -pub mod compu_buffer; -pub mod compu_kernel; -pub mod handles; \ No newline at end of file diff --git a/src/compute/mod.rs b/src/compute/mod.rs deleted file mode 100644 index 7503ae4b..00000000 --- a/src/compute/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod compu_frame; -pub mod compu_state; -pub mod managed; - -use crate::compute::compu_state::CompuState; -use crate::compute::compu_frame::CompuFrame; \ No newline at end of file diff --git a/src/detect.rs b/src/detect.rs index ec02f51d..507154e3 100644 --- a/src/detect.rs +++ b/src/detect.rs @@ -383,6 +383,215 @@ fn gaussian_3x3(src: &[f32], w: usize, h: usize) -> Vec { out } +// ── Graph model ─────────────────────────────────────────────────────────────── + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum BlendMode { + Average, + Min, + Max, + Multiply, + Screen, + Difference, +} + +impl BlendMode { + pub fn from_str(s: &str) -> Self { + match s { + "Min" => BlendMode::Min, + "Max" => BlendMode::Max, + "Multiply" => BlendMode::Multiply, + "Screen" => BlendMode::Screen, + "Difference" => BlendMode::Difference, + _ => BlendMode::Average, + } + } + pub fn label(self) -> &'static str { + match self { + BlendMode::Average => "Avg", + BlendMode::Min => "Min", + BlendMode::Max => "Max", + BlendMode::Multiply => "Mul", + BlendMode::Screen => "Scr", + BlendMode::Difference => "Dif", + } + } +} + +#[derive(Debug, Clone)] +pub enum NodeKind { + Source, + Kernel(DetectionLayer), + Combine(BlendMode), + Output, +} + +#[derive(Debug, Clone)] +pub struct GraphNode { + pub id: String, + pub kind: NodeKind, +} + +#[derive(Debug, Clone)] +pub struct GraphEdge { + pub from: String, + pub to: String, + pub port: usize, +} + +#[derive(Debug, Clone, Default)] +pub struct DetectionGraph { + pub nodes: Vec, + pub edges: Vec, +} + +/// Evaluate the detection graph. Returns (final_response_map, per_node_maps). +/// Per-node maps: node_id → Vec response map (0=ink, 255=bg). +/// Source and Output nodes are excluded from per-node maps. +pub fn evaluate_graph( + rgb: &RgbImage, + graph: &DetectionGraph, +) -> (Vec, std::collections::HashMap>) { + use std::collections::{HashMap, VecDeque}; + + let n = (rgb.width() * rgb.height()) as usize; + let bg = || vec![255u8; n]; + + if graph.nodes.is_empty() { + return (bg(), HashMap::new()); + } + + // Build adjacency: incoming edges per node (sorted by port for combine) + let mut incoming: HashMap<&str, Vec<(&str, usize)>> = HashMap::new(); + let mut out_edges: HashMap<&str, Vec<&str>> = HashMap::new(); + for node in &graph.nodes { + incoming.entry(&node.id).or_default(); + out_edges.entry(&node.id).or_default(); + } + for edge in &graph.edges { + incoming.entry(&edge.to).or_default().push((&edge.from, edge.port)); + out_edges.entry(&edge.from).or_default().push(&edge.to); + } + + // Kahn's topological sort + let mut in_deg: HashMap<&str, usize> = graph.nodes.iter() + .map(|n| (n.id.as_str(), incoming[n.id.as_str()].len())) + .collect(); + + let mut queue: VecDeque<&str> = in_deg.iter() + .filter(|(_, &d)| d == 0) + .map(|(&id, _)| id) + .collect(); + let mut order: Vec<&str> = Vec::new(); + while let Some(id) = queue.pop_front() { + order.push(id); + for &next in out_edges.get(id).into_iter().flatten() { + let d = in_deg.get_mut(next).unwrap(); + *d -= 1; + if *d == 0 { queue.push_back(next); } + } + } + if order.len() != graph.nodes.len() { + return (bg(), HashMap::new()); // cycle + } + + let node_map: HashMap<&str, &GraphNode> = graph.nodes.iter() + .map(|n| (n.id.as_str(), n)) + .collect(); + + let mut outputs: HashMap<&str, Vec> = HashMap::new(); + + for &id in &order { + let node = node_map[id]; + let result: Option> = match &node.kind { + NodeKind::Source => None, + NodeKind::Kernel(layer) => Some(apply_layer(rgb, layer)), + NodeKind::Combine(mode) => { + let mut ins = incoming[id].clone(); + ins.sort_by_key(|&(_, p)| p); + let maps: Vec<&[u8]> = ins.iter() + .filter_map(|(fid, _)| outputs.get(fid).map(|v| v.as_slice())) + .collect(); + Some(if maps.is_empty() { bg() } else { blend_maps(&maps, *mode, n) }) + } + NodeKind::Output => { + let upstream = incoming[id].iter() + .find_map(|(fid, _)| outputs.get(fid).cloned()); + Some(upstream.unwrap_or_else(bg)) + } + }; + if let Some(map) = result { + outputs.insert(id, map); + } + } + + let final_map = graph.nodes.iter() + .find(|n| matches!(n.kind, NodeKind::Output)) + .and_then(|n| outputs.get(n.id.as_str()).cloned()) + .unwrap_or_else(bg); + + // Per-node previews — omit Source (no output) and Output (same as final_map) + let previews: HashMap> = outputs.into_iter() + .filter(|(id, _)| { + let key: &str = id; + node_map.get(key).map_or(true, |n| + !matches!(n.kind, NodeKind::Source | NodeKind::Output)) + }) + .map(|(id, map)| (id.to_string(), map)) + .collect(); + + (final_map, previews) +} + +fn blend_maps(maps: &[&[u8]], mode: BlendMode, n: usize) -> Vec { + match mode { + BlendMode::Average => { + let mut sum = vec![0u32; n]; + for map in maps { for (s, &v) in sum.iter_mut().zip(*map) { *s += v as u32; } } + let c = maps.len() as u32; + sum.iter().map(|&s| (s / c) as u8).collect() + } + BlendMode::Min => { + let mut r = vec![255u8; n]; + for map in maps { for (r, &v) in r.iter_mut().zip(*map) { if v < *r { *r = v; } } } + r + } + BlendMode::Max => { + let mut r = vec![0u8; n]; + for map in maps { for (r, &v) in r.iter_mut().zip(*map) { if v > *r { *r = v; } } } + r + } + BlendMode::Multiply => { + let mut r = vec![255u8; n]; + for map in maps { + for (r, &v) in r.iter_mut().zip(*map) { + *r = ((*r as u32 * v as u32) / 255) as u8; + } + } + r + } + BlendMode::Screen => { + let mut r = vec![0u8; n]; + for map in maps { + for (r, &v) in r.iter_mut().zip(*map) { + let a = *r as u32; let b = v as u32; + *r = (255 - (255 - a) * (255 - b) / 255) as u8; + } + } + r + } + BlendMode::Difference => { + let mut r = maps[0].to_vec(); + for map in &maps[1..] { + for (r, &v) in r.iter_mut().zip(*map) { + *r = (*r as i16 - v as i16).unsigned_abs() as u8; + } + } + r + } + } +} + // ── Tests ────────────────────────────────────────────────────────────────────── #[cfg(test)] diff --git a/src/drawables/compu_sprite.rs b/src/drawables/compu_sprite.rs deleted file mode 100644 index 5baeb103..00000000 --- a/src/drawables/compu_sprite.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::sync::Arc; -use crate::canvas::managed::handles::{CanvasImageHandle, CanvasTextureHandle}; -use crate::canvas::canvas_frame::Drawable; -use crate::util::vertex::{VertexType, ImageVertex3D}; - -pub struct CompuSprite { - - pub verts: VertexType, - - position: (f32, f32), - size: (f32, f32), - color: (f32, f32, f32, f32), - -} - -impl CompuSprite { - pub fn new(position: (f32, f32), - size: (f32, f32), - depth: u32, - image_size: (f32, f32), - image_handle: Arc) -> CompuSprite { - - let normalized_depth = (depth as f32 / 255.0); - - let verts = vec![ - ImageVertex3D { - v_position: [position.0, position.1, normalized_depth], // top left - ti_position: [-0.0, -0.0] }, - ImageVertex3D { - v_position: [position.0, position.1 + size.1, normalized_depth], // bottom left - ti_position: [-0.0, image_size.1] }, - ImageVertex3D { - v_position: [position.0 + size.0, position.1 + size.1, normalized_depth], // bottom right - ti_position: [image_size.0, image_size.1] }, - ImageVertex3D { - v_position: [position.0, position.1, normalized_depth], // top left - ti_position: [-0.0, -0.0] }, - ImageVertex3D { - v_position: [position.0 + size.0, position.1 + size.1, normalized_depth], // bottom right - ti_position: [image_size.0, image_size.1] }, - ImageVertex3D { - v_position: [position.0 + size.0, position.1, normalized_depth], // top right - ti_position: [image_size.0, -0.0] }, - ]; - - CompuSprite { - verts: VertexType::ImageType(verts, image_handle.clone()), - position: position, - size: size, - color: (0.0, 0.0, 0.0, 0.0), - } - } -} - -impl Drawable for CompuSprite { - fn get(&self, window_size: (u32, u32)) -> Vec { - vec![self.verts.clone()] - } -} \ No newline at end of file diff --git a/src/drawables/mod.rs b/src/drawables/mod.rs deleted file mode 100644 index 5bc05a9c..00000000 --- a/src/drawables/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod slider; -pub mod polygon; -pub mod sprite; -pub mod rect; -pub mod compu_sprite; -pub mod text; \ No newline at end of file diff --git a/src/drawables/polygon.rs b/src/drawables/polygon.rs deleted file mode 100644 index ba4d80ce..00000000 --- a/src/drawables/polygon.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::sync::Arc; -use crate::canvas::*; -use crate::canvas::managed::handles::{CanvasFontHandle, CanvasImageHandle, CanvasTextureHandle, Handle}; -use crate::canvas::canvas_frame::{Drawable}; -use crate::util::vertex::{VertexType, TextureVertex3D, Vertex3D, ColorVertex3D}; -use crate::drawables::sprite::Sprite; - -/// Convex multi verticy polygon -#[derive(Debug, Clone)] -pub struct Polygon { - - pub verts: VertexType, - - position: (f32, f32), - size: (f32, f32), -} - -/// Container class which implements drawable. -impl Polygon { - - /// - pub fn new(position: (f32, f32), - size: (f32, f32), - depth: u32,) -> Polygon { - - let normalized_depth = (depth as f32 / 255.0); - - let verts = vec![ - ColorVertex3D{v_position: [-0.5, -0.5, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [-1.0, 1.0, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [-0.25, 0.0, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [-0.25, 0.0, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [-1.0, 1.0, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [0.0, 0.5, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [0.25, 0.0, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [-1.0, 1.0, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [0.0, 0.5, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [0.5, -0.5, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [-1.0, 1.0, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [0.25, 0.0, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [0.25, -0.5, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [-1.0, 1.0, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [0.5, -0.5, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [0.25, -0.5, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [-1.0, 1.0, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [0.0, -0.1, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [-0.25, -0.5, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [-1.0, 1.0, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [0.0, -0.1, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [-0.5, -0.5, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [-1.0, 1.0, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ColorVertex3D{v_position: [-0.25, -0.5, normalized_depth], color: [1.0, 1.0, 0.0, 1.0] }, - ]; - - - Polygon { - verts: VertexType::ColorType(verts), - position: position, - size: size, - } - } -} -impl Drawable for Polygon { - fn get(&self, window_size: (u32, u32)) -> Vec { - vec![self.verts.clone()] - } - -} - - - diff --git a/src/drawables/rect.rs b/src/drawables/rect.rs deleted file mode 100644 index 9503ba3c..00000000 --- a/src/drawables/rect.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::canvas::canvas_frame::Drawable; -use crate::util::vertex::{VertexType, ColorVertex3D}; - -/// -#[derive(Debug, Clone)] -pub struct Rect { - position: (f32, f32), - size: (f32, f32), - color: (f32, f32, f32, f32), - depth: f32, -} - -/// Container class which implements drawable. -impl Rect { - /// - pub fn new(position: (f32, f32), - size: (f32, f32), - depth: u32, - color: (f32, f32, f32, f32)) -> Rect { - let normalized_depth = (depth as f32 / 255.0); - - Rect { - position: position, - size: size, - color: color, - depth: normalized_depth, - } - } - - fn generate_vertices(window_size: (u32, u32), - position: (f32, f32), - size: (f32, f32), - depth: f32, - color: (f32, f32, f32, f32)) -> Vec { - - let ss_position = ( - position.0 / window_size.0 as f32 - 1.0, - position.1 / window_size.1 as f32 - 1.0 - ); - - let ss_size = ( - size.0 / window_size.0 as f32, - size.1 / window_size.1 as f32 - ); - - vec![ - ColorVertex3D { - v_position: [ss_position.0, ss_position.1, depth], // top left - color: [color.0, color.1, color.2, color.3], - }, - ColorVertex3D { - v_position: [ss_position.0, ss_position.1 + ss_size.1, depth], // bottom left - color: [color.0, color.1, color.2, color.3], - }, - ColorVertex3D { - v_position: [ss_position.0 + ss_size.0, ss_position.1 + ss_size.1, depth], // bottom right - color: [color.0, color.1, color.2, color.3], - }, - ColorVertex3D { - v_position: [ss_position.0, ss_position.1, depth], // top left - color: [color.0, color.1, color.2, color.3], - }, - ColorVertex3D { - v_position: [ss_position.0 + ss_size.0, ss_position.1 + ss_size.1, depth], // bottom right - color: [color.0, color.1, color.2, color.3], - }, - ColorVertex3D { - v_position: [ss_position.0 + ss_size.0, ss_position.1, depth], // top right - color: [color.0, color.1, color.2, color.3], - }, - ] - } -} - -impl Drawable for Rect { - fn get(&self, window_size: (u32, u32)) -> Vec { - vec![ - VertexType::ColorType( - Rect::generate_vertices(window_size, self.position, self.size, self.depth, self.color) - ) - ] - } -} diff --git a/src/drawables/slider.rs b/src/drawables/slider.rs deleted file mode 100644 index d4416c03..00000000 --- a/src/drawables/slider.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::collections::HashSet; - -use winit::event::Event; - -use crate::canvas::canvas_frame::{Drawable, Eventable}; -use crate::drawables::rect::Rect; -use crate::drawables::sprite::Sprite; -use crate::util::vertex::VertexType; - -pub struct Slider { - handle: Rect, - guide: Vec, - - scaler: u32, - position: (f32, f32), - size: (f32, f32), - value: u16, -} - -impl Slider { - pub fn new(size: (f32, f32), position: (f32, f32), value: u16) -> Slider { - - // render the guide first - let red = (1.0, 0.0, 0.0, 0.0); - let green = (0.0, 1.0, 0.0, 0.0); - let blue = (0.0, 1.0, 1.0, 0.0); - let rg = (1.0, 1.0, 0.0, 0.0); - - let left_guide_bar = Rect::new((position.0, position.1), (2.0, size.1), 1, red); - let right_guide_bar = Rect::new((position.0 + size.0, position.1), (2.0, size.1), 1, blue); - let line = Rect::new((position.0, position.1 - (size.1 / 2.0) ), (size.0, 2.0), 1, green); - - let scale = value as f32 / u16::max_value() as f32; - let handle = Rect::new((position.0 + (size.0 * scale), position.1), (15.0, size.1), 1, rg); - - Slider { - handle: handle, - guide: vec![left_guide_bar, right_guide_bar, line], - scaler: 255, - position, - size, - value, - } - } -} - -impl Drawable for Slider { - fn get(&self, window_size: (u32, u32)) -> Vec { - let mut vertices = self.handle.get(window_size).clone(); - - vertices.extend_from_slice( - self.guide.iter() - .map(|x| x.get(window_size)) - .flatten() - .collect::>() - .as_slice() - ); - - vertices.extend_from_slice(self.guide[0].get(window_size).as_slice()); - vertices - } -} - -impl Eventable for Slider { - fn notify(&mut self, event: &Event) -> () { - unimplemented!() - } -} diff --git a/src/drawables/sprite.rs b/src/drawables/sprite.rs deleted file mode 100644 index 644e8297..00000000 --- a/src/drawables/sprite.rs +++ /dev/null @@ -1,107 +0,0 @@ -use std::sync::Arc; -use crate::canvas::*; -use crate::canvas::managed::handles::{CanvasFontHandle, CanvasImageHandle, CanvasTextureHandle, Handle}; -use crate::canvas::canvas_frame::{Drawable, Eventable, Updatable}; -use crate::util::vertex::{VertexType, TextureVertex3D, Vertex3D}; -use winit::event::{DeviceEvent, MouseButton, ElementState, Event, WindowEvent}; - -/// -#[derive(Debug, Clone)] -pub struct Sprite { - pub position: (f32, f32), - pub size: (f32, f32), - depth: f32, - texture_handle: Arc, -} - -/// Container class which implements drawable. -impl Sprite { - fn generate_verts(window_size: (u32, u32), position: (f32, f32), size: (f32, f32), depth: f32) -> Vec { - - let ss_position = ( - position.0 / window_size.0 as f32 - 1.0, - position.1 / window_size.1 as f32 - 1.0 - ); - - let ss_size = ( - size.0 / window_size.0 as f32, - size.1 / window_size.1 as f32 - ); - - vec![ - TextureVertex3D { - v_position: [ss_position.0, ss_position.1, depth], // top left - ti_position: [-0.0, -0.0], - }, - TextureVertex3D { - v_position: [ss_position.0, ss_position.1 + ss_size.1, depth], // bottom left - ti_position: [-0.0, 1.0], - }, - TextureVertex3D { - v_position: [ss_position.0 + ss_size.0, ss_position.1 + ss_size.1, depth], // bottom right - ti_position: [1.0, 1.0], - }, - TextureVertex3D { - v_position: [ss_position.0, ss_position.1, depth], // top left - ti_position: [-0.0, -0.0], - }, - TextureVertex3D { - v_position: [ss_position.0 + ss_size.0, ss_position.1 + ss_size.1, depth], // bottom right - ti_position: [1.0, 1.0], - }, - TextureVertex3D { - v_position: [ss_position.0 + ss_size.0, ss_position.1, depth], // top right - ti_position: [1.0, -0.0], - }, - ] - } - - /// - pub fn new(position: (f32, f32), - size: (f32, f32), - depth: u32, - texture_handle: Arc) -> Sprite { - let normalized_depth = (depth as f32 / 255.0); - - Sprite { - position: position, - size: size, - depth: normalized_depth, - texture_handle: texture_handle.clone(), - } - } -} - -impl Drawable for Sprite { - fn get(&self, window_size: (u32, u32)) -> Vec { - vec![ - VertexType::TextureType( - Sprite::generate_verts(window_size, self.position, self.size, self.depth), - self.texture_handle.clone()) - ] - } -} - -impl Eventable for Sprite { - fn notify(&mut self, event: &Event) -> () { - match event { - Event::WindowEvent { event: WindowEvent::MouseInput { device_id, state, button, modifiers }, .. } => { - match button { - MouseButton::Left => { - if *state == ElementState::Pressed { - self.position = (self.position.0, self.position.1 + 0.1); - } - } - _ => {} - } - } - _ => {} - } - } -} - -impl Updatable for Sprite { - fn update(&mut self, delta_time: f32) -> () { - - } -} diff --git a/src/drawables/text.rs b/src/drawables/text.rs deleted file mode 100644 index c3215380..00000000 --- a/src/drawables/text.rs +++ /dev/null @@ -1,143 +0,0 @@ -use crate::canvas::canvas_frame::Drawable; -use crate::util::vertex::{VertexType, ColorVertex3D}; - -/// -#[derive(Debug, Clone)] -pub struct Text { - pub verts: VertexType, - - position: (f32, f32), - size: (f32, f32), -} - -/// Container class which implements drawable. -impl Text { - - fn accumulator(depth: f32, accumulator: &mut f32, constant: f32) -> f32{ - - let accum = *accumulator; - *accumulator += constant; - return depth + accum + constant; - } - /// - pub fn new(position: (f32, f32), - size: (f32, f32), - depth: u32) -> Text { - let normalized_depth = (depth as f32 / 255.0); - - let mut depth_accumulator = 1.0; - let depth_accum_constant = -0.01 as f32; - - let verts = { - vec![ - ColorVertex3D { - v_position: [-0.5, -0.5, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)+0.1], - color: [1.0, 0.0, 0.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [-1.0, 1.0, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)+0.1], - color: [1.0, 0.8, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [-0.25, 0.0, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)+0.1], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [-0.25, 0.0, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [0.8, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [-1.0, 1.0, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [0.0, 0.5, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 0.8, 1.0/255.0], - }, - ColorVertex3D { - v_position: [0.25, 0.0, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [-1.0, 1.0, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 0.8, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [0.0, 0.5, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [0.5, -0.5, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [-1.0, 1.0, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [0.25, 0.0, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [0.25, -0.5, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [-1.0, 1.0, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [0.5, -0.5, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [0.25, -0.5, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [-1.0, 1.0, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [0.0, -0.1, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [-0.25, -0.5, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [-1.0, 1.0, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [0.0, -0.1, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [-0.5, -0.5, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [-1.0, 1.0, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }, - ColorVertex3D { - v_position: [-0.25, -0.5, Text::accumulator(normalized_depth, &mut depth_accumulator, depth_accum_constant)], - color: [1.0, 1.0, 1.0, 1.0/255.0], - }] - }; - - Text { - verts: VertexType::TextType(verts), - position: position, - size: size, - } - } -} - -impl Drawable for Text { - fn get(&self, window_size: (u32, u32)) -> Vec { - vec![self.verts.clone()] - } -} diff --git a/src/lib.rs b/src/lib.rs index ecd22658..3cb59989 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,17 +49,37 @@ pub struct ImageInfo { } #[derive(Deserialize, Clone, Debug)] -pub struct DetectionLayerPayload { - pub kernel: String, - pub weight: f32, - pub invert: bool, - pub blur_radius: f32, - pub sat_min_value: f32, - pub canny_low: f32, - pub canny_high: f32, - pub xdog_sigma2: f32, - pub xdog_tau: f32, - pub xdog_phi: f32, +pub struct GraphNodePayload { + pub id: String, + pub kind: String, // "Source" | "Kernel" | "Combine" | "Output" + pub x: f32, + pub y: f32, + // Kernel params (optional) + pub kernel: Option, + pub weight: Option, + pub invert: Option, + pub blur_radius: Option, + pub sat_min_value: Option, + pub canny_low: Option, + pub canny_high: Option, + pub xdog_sigma2: Option, + pub xdog_tau: Option, + pub xdog_phi: Option, + // Combine params (optional) + pub blend_mode: Option, +} + +#[derive(Deserialize, Clone, Debug)] +pub struct GraphEdgePayload { + pub from: String, + pub to: String, + pub port: usize, +} + +#[derive(Deserialize, Clone, Debug)] +pub struct DetectionGraphPayload { + pub nodes: Vec, + pub edges: Vec, } #[derive(Deserialize, Clone, Debug)] @@ -75,13 +95,13 @@ pub struct ColorFilterPayload { #[derive(Deserialize, Clone, Debug)] pub struct ProcessPassPayload { - pub pass_index: usize, - pub layers: Vec, - pub threshold: u8, - pub min_area: u32, - pub rdp_epsilon: f32, - pub connectivity: String, // "four" | "eight" - pub color_filter: ColorFilterPayload, + pub pass_index: usize, + pub graph: DetectionGraphPayload, + pub threshold: u8, + pub min_area: u32, + pub rdp_epsilon: f32, + pub connectivity: String, // "four" | "eight" + pub color_filter: ColorFilterPayload, } #[derive(Serialize, Clone, Default)] @@ -95,6 +115,7 @@ pub struct ProcessResult { pub hull_count: usize, pub coverage_pct: usize, pub viz_b64: String, + pub node_previews: std::collections::HashMap, pub timings: Vec, } @@ -154,33 +175,52 @@ pub struct PassStrokesPayload { // ── Helpers ──────────────────────────────────────────────────────────────────── -fn to_detection_params(layers: &[DetectionLayerPayload]) -> detect::DetectionParams { +fn to_detection_graph(payload: &DetectionGraphPayload) -> detect::DetectionGraph { use detect::DetectionKernel::*; - detect::DetectionParams { - layers: layers.iter().map(|l| { - let kernel = match l.kernel.as_str() { - "Sobel" => Sobel, - "ColorGradient" => ColorGradient, - "Laplacian" => Laplacian, - "Canny" => Canny, - "Saturation" => Saturation, - "XDoG" => XDoG, - _ => Luminance, - }; - detect::DetectionLayer { - kernel, - weight: l.weight, - invert: l.invert, - blur_radius: l.blur_radius, - sat_min_value: l.sat_min_value, - canny_low: l.canny_low, - canny_high: l.canny_high, - xdog_sigma2: l.xdog_sigma2, - xdog_tau: l.xdog_tau, - xdog_phi: l.xdog_phi, + let nodes = payload.nodes.iter().map(|n| { + let kind = match n.kind.as_str() { + "Source" => detect::NodeKind::Source, + "Kernel" => { + let kernel = match n.kernel.as_deref().unwrap_or("Luminance") { + "Sobel" => Sobel, + "ColorGradient" => ColorGradient, + "Laplacian" => Laplacian, + "Canny" => Canny, + "Saturation" => Saturation, + "XDoG" => XDoG, + _ => Luminance, + }; + detect::NodeKind::Kernel(detect::DetectionLayer { + kernel, + weight: n.weight.unwrap_or(1.0), + invert: n.invert.unwrap_or(false), + blur_radius: n.blur_radius.unwrap_or(0.0), + sat_min_value: n.sat_min_value.unwrap_or(0.1), + canny_low: n.canny_low.unwrap_or(50.0), + canny_high: n.canny_high.unwrap_or(150.0), + xdog_sigma2: n.xdog_sigma2.unwrap_or(1.6), + xdog_tau: n.xdog_tau.unwrap_or(0.98), + xdog_phi: n.xdog_phi.unwrap_or(10.0), + }) } - }).collect(), - } + "Combine" => { + let mode = detect::BlendMode::from_str( + n.blend_mode.as_deref().unwrap_or("Average") + ); + detect::NodeKind::Combine(mode) + } + _ => detect::NodeKind::Output, + }; + detect::GraphNode { id: n.id.clone(), kind } + }).collect(); + + let edges = payload.edges.iter().map(|e| detect::GraphEdge { + from: e.from.clone(), + to: e.to.clone(), + port: e.port, + }).collect(); + + detect::DetectionGraph { nodes, edges } } fn to_color_filter(p: &ColorFilterPayload) -> hulls::ColorFilter { @@ -213,6 +253,24 @@ fn rgba_to_b64_png(rgba: &[u8], w: u32, h: u32) -> String { B64.encode(buf.into_inner()) } +fn map_to_b64_small(map: &[u8], w: u32, h: u32) -> String { + const MAX_DIM: u32 = 256; + let gray = image::GrayImage::from_raw(w, h, map.to_vec()) + .expect("bad map buffer"); + let out = if w > MAX_DIM || h > MAX_DIM { + let scale = MAX_DIM as f32 / w.max(h) as f32; + let (sw, sh) = ((w as f32 * scale) as u32, (h as f32 * scale) as u32); + image::imageops::resize(&gray, sw, sh, image::imageops::FilterType::Nearest) + } else { + gray + }; + let mut buf = std::io::Cursor::new(Vec::new()); + image::DynamicImage::ImageLuma8(out) + .write_to(&mut buf, image::ImageFormat::Jpeg) + .unwrap(); + B64.encode(buf.into_inner()) +} + fn rgb_to_b64_jpeg(rgb: &image::RgbImage) -> String { const MAX_DIM: u32 = 1024; let (w, h) = rgb.dimensions(); @@ -240,7 +298,7 @@ fn process_pass_work( let mut steps: Vec = Vec::new(); let (w, h) = rgb.dimensions(); - let det_params = to_detection_params(&payload.layers); + let det_graph = to_detection_graph(&payload.graph); let hull_params = hulls::HullParams { threshold: payload.threshold, min_area: payload.min_area, @@ -254,9 +312,14 @@ fn process_pass_work( let color_filter = to_color_filter(&payload.color_filter); let mut t = Instant::now(); - let response = detect::apply_stack(rgb, &det_params); + let (response, node_outputs) = detect::evaluate_graph(rgb, &det_graph); t = lap!(steps, "detect", t); + let node_previews: std::collections::HashMap = node_outputs.iter() + .map(|(id, map)| (id.clone(), map_to_b64_small(map, w, h))) + .collect(); + t = lap!(steps, "node previews", t); + let all_hulls = hulls::extract_hulls(&response, rgb, w, h, &hull_params); t = lap!(steps, "hull extract", t); @@ -280,7 +343,7 @@ fn process_pass_work( steps.push(StepTime { label: "total".into(), ms: t0.elapsed().as_millis() as u64 }); - (extracted, ProcessResult { hull_count, coverage_pct, viz_b64, timings: steps }) + (extracted, ProcessResult { hull_count, coverage_pct, viz_b64, node_previews, timings: steps }) } fn generate_fill_work( @@ -803,18 +866,17 @@ mod blocking_tests { fn default_process_payload() -> ProcessPassPayload { ProcessPassPayload { pass_index: 0, - layers: vec![DetectionLayerPayload { - kernel: "Luminance".into(), - weight: 1.0, - invert: false, - blur_radius: 0.0, - sat_min_value: 0.0, - canny_low: 0.1, - canny_high: 0.3, - xdog_sigma2: 1.5, - xdog_tau: 0.98, - xdog_phi: 10.0, - }], + graph: DetectionGraphPayload { + nodes: vec![ + GraphNodePayload { id: "source".into(), kind: "Source".into(), x: 0.0, y: 0.0, kernel: None, weight: None, invert: None, blur_radius: None, sat_min_value: None, canny_low: None, canny_high: None, xdog_sigma2: None, xdog_tau: None, xdog_phi: None, blend_mode: None }, + GraphNodePayload { id: "k1".into(), kind: "Kernel".into(), x: 0.0, y: 0.0, kernel: Some("Luminance".into()), weight: Some(1.0), invert: Some(false), blur_radius: Some(0.0), sat_min_value: Some(0.0), canny_low: Some(50.0), canny_high: Some(150.0), xdog_sigma2: Some(1.6), xdog_tau: Some(0.98), xdog_phi: Some(10.0), blend_mode: None }, + GraphNodePayload { id: "output".into(), kind: "Output".into(), x: 0.0, y: 0.0, kernel: None, weight: None, invert: None, blur_radius: None, sat_min_value: None, canny_low: None, canny_high: None, xdog_sigma2: None, xdog_tau: None, xdog_phi: None, blend_mode: None }, + ], + edges: vec![ + GraphEdgePayload { from: "source".into(), to: "k1".into(), port: 0 }, + GraphEdgePayload { from: "k1".into(), to: "output".into(), port: 0 }, + ], + }, threshold: 128, min_area: 10, rdp_epsilon: 2.0, @@ -1181,7 +1243,18 @@ mod viz_tests { xdog_tau: lj["xdog_tau"].as_f64().unwrap_or(0.98) as f32, xdog_phi: lj["xdog_phi"].as_f64().unwrap_or(10.0) as f32, }; - let response = detect::apply_stack(&img, &detect::DetectionParams { layers: vec![layer] }); + let graph = detect::DetectionGraph { + nodes: vec![ + detect::GraphNode { id: "source".into(), kind: detect::NodeKind::Source }, + detect::GraphNode { id: "k1".into(), kind: detect::NodeKind::Kernel(layer) }, + detect::GraphNode { id: "output".into(), kind: detect::NodeKind::Output }, + ], + edges: vec![ + detect::GraphEdge { from: "source".into(), to: "k1".into(), port: 0 }, + detect::GraphEdge { from: "k1".into(), to: "output".into(), port: 0 }, + ], + }; + let (response, _) = detect::evaluate_graph(&img, &graph); let params = HullParams { threshold, min_area, rdp_epsilon: rdp_eps, connectivity: hulls::Connectivity::Four }; let hs = hulls::extract_hulls(&response, &img, w, h, ¶ms); diff --git a/src/vkprocessor.rs b/src/vkprocessor.rs deleted file mode 100644 index 9e7aac68..00000000 --- a/src/vkprocessor.rs +++ /dev/null @@ -1,348 +0,0 @@ -use vulkano::command_buffer::{AutoCommandBufferBuilder, DynamicState}; -use vulkano::device::{Device, DeviceExtensions, QueuesIter, Queue}; -use vulkano::instance::{Instance, PhysicalDevice}; -use vulkano::sync::{GpuFuture, FlushError, NowFuture}; -use vulkano::sync::now; -use vulkano::sync; -use std::sync::Arc; -use vulkano::swapchain::{Swapchain, PresentMode, SurfaceTransform, Surface, SwapchainCreationError, AcquireError, Capabilities, FullscreenExclusive, ColorSpace}; -use vulkano::image::swapchain::SwapchainImage; -use crate::compute::compu_state::CompuState; -use vulkano::image::ImageUsage; -use crate::compute::compu_frame::CompuFrame; -use crate::canvas::canvas_frame::{CanvasFrame}; -use std::time::Duration; -use vulkano::pipeline::depth_stencil::{DynamicStencilValue, StencilFaceFlags}; -use vulkano::pipeline::vertex::{OneVertexOneInstanceDefinition, SingleBufferDefinition}; -use crate::canvas::canvas_state::CanvasState; -use crate::canvas::managed::shader::generic_shader::GenericShader; -use crate::canvas::managed::shader::text_shader::TextShader; -use crate::canvas::managed::handles::{CanvasTextureHandle, CompiledShaderHandle, CanvasFontHandle, CanvasImageHandle}; -use crate::compute::managed::handles::{CompuKernelHandle, CompuBufferHandle}; -use crate::util::vertex::{VertexType, ColorVertex3D, TextVertex3D, TextureVertex3D, ImageVertex3D}; -use vulkano_text::DrawText; -use winit::window::{Window, WindowBuilder}; -use vulkano::instance::debug::DebugCallback; -use winit::dpi::LogicalSize; -use vulkano_win::VkSurfaceBuild; -use winit::event_loop::EventLoop; - - -/// VKProcessor holds the vulkan instance information, the swapchain, -/// and the compute and canvas states -pub struct VkProcessor { - // Vulkan state fields - //pub physical: PhysicalDevice<'a>, - pub device: Arc, - pub queues: QueuesIter, - pub queue: Arc, - - pub swapchain: Option>>, - pub swapchain_images: Option>>>, - - pub swapchain_recreate_needed: bool, - - /// State holding textures, images, and their related vertex buffers - canvas_state: CanvasState, - - /// State holding - compute_state: CompuState, - - capabilities: Capabilities, - -} - - -impl VkProcessor { - /// Creates a new VkProcessor from an instance and surface - /// This includes the physical device, queues, compute and canvas state - pub fn new(instance: Arc, surface: Arc>) -> VkProcessor { - - let physical = PhysicalDevice::enumerate(&instance).next().unwrap(); - - let queue_family = physical.queue_families().find(|&q| { - // We take the first queue that supports drawing to our window. - q.supports_graphics() && - surface.is_supported(q).unwrap_or(false) && - q.supports_compute() - }).unwrap(); - - let device_ext = DeviceExtensions { khr_swapchain: true, ..DeviceExtensions::none() }; - - let (device, mut queues) = Device::new(physical, - physical.supported_features(), - &device_ext, - [(queue_family, 0.5)].iter().cloned()).unwrap(); - let queue = queues.next().unwrap(); - - let capabilities = surface.capabilities(physical).unwrap(); - - VkProcessor { - device: device.clone(), - queue: queue.clone(), - queues: queues, - swapchain: None, - swapchain_images: None, - swapchain_recreate_needed: false, - compute_state: CompuState::new(), - capabilities: capabilities.clone(), - canvas_state: CanvasState::new(queue, device, physical, capabilities), - } - } - - pub fn get_canvas_state(&self) -> &CanvasState { - &self.canvas_state - } - - /// Using the surface, we calculate the surface capabilities and create the swapchain and swapchain images - pub fn create_swapchain(&mut self, instance: Arc, surface: Arc>) { - let (mut swapchain, images) = { - let physical = PhysicalDevice::enumerate(&instance).next().unwrap(); - let capabilities = surface.capabilities(physical).unwrap(); - let usage = capabilities.supported_usage_flags; - let alpha = capabilities.supported_composite_alpha.iter().next().unwrap(); - // Choosing the internal format that the images will have. - let format = capabilities.supported_formats[0].0; - let colorspace = capabilities.supported_formats[0].1; - - // Set the swapchains window dimensions - let initial_dimensions = if let dimensions = surface.window().inner_size() { - // convert to physical pixels - let dimensions: (u32, u32) = dimensions.to_logical::(surface.window().scale_factor()).into(); - [dimensions.0, dimensions.1] - } else { - // The window no longer exists so exit the application. - panic!("window closed"); - }; - - Swapchain::new(self.device.clone(), - surface.clone(), - capabilities.min_image_count, // number of attachment images - format, - initial_dimensions, - 1, // Layers - ImageUsage::color_attachment(), - &self.queue, - SurfaceTransform::Identity, - alpha, - PresentMode::Immediate, - FullscreenExclusive::Default, true, - colorspace).unwrap() - }; - - self.swapchain = Some(swapchain); - self.swapchain_images = Some(images); - } - - /// On screen resizes, the swapchain and images must be recreated - pub fn recreate_swapchain(&mut self, surface: &Arc>) { - let dimensions = if let dimensions = surface.window().inner_size() { - let dimensions: (u32, u32) = dimensions.to_logical::(surface.window().scale_factor()).into(); - [dimensions.0, dimensions.1] - } else { - return; - }; - - let (new_swapchain, new_images) = match self.swapchain.clone().unwrap().clone() - .recreate_with_dimensions(dimensions) { - Ok(r) => r, - // This error tends to happen when the user is manually resizing the window. - // Simply restarting the loop is the easiest way to fix this issue. - Err(SwapchainCreationError::UnsupportedDimensions) => panic!("Uh oh"), - Err(err) => panic!("{:?}", err) - }; - - self.swapchain = Some(new_swapchain); - self.swapchain_images = Some(new_images); - } - - /// A hardcoded list of textures which can be preloaded from this function - pub fn preload_textures(&mut self) { - self.canvas_state.load_texture(String::from("ford2.jpg")); - self.canvas_state.load_texture(String::from("funky-bird.jpg")); - self.canvas_state.load_texture(String::from("button.png")); - self.canvas_state.load_texture(String::from("background.jpg")); - self.canvas_state.load_texture(String::from("test2.png")); - self.canvas_state.load_texture(String::from("sfml.png")); - } - - /// A hardcoded list of kernels which can be preloaded from this function - pub fn preload_kernels(&mut self) { - self.compute_state.new_kernel(String::from("simple-homogenize.compute"), self.device.clone()); - self.compute_state.new_kernel(String::from("simple-edge.compute"), self.device.clone()); - } - - /// A hardcoded list of shaders which can be preloaded from this function - pub fn preload_shaders(&mut self) { - self.canvas_state.load_shader::(String::from("color-passthrough"), self.capabilities.clone()); - self.canvas_state.load_shader::(String::from("simple_texture"), self.capabilities.clone()); - self.canvas_state.load_shader::(String::from("simple_image"), self.capabilities.clone()); - self.canvas_state.load_shader::(String::from("simple_text"), self.capabilities.clone()); - } - - /// A hardcoded list of shaders which can be proloaded from this function - pub fn preload_fonts(&mut self) { - //self.canvas_state.load_font(String::from("sansation.ttf")); - } - - /// O(n) Lookup for the matching texture string - pub fn get_texture_handle(&self, texture_name: String) -> Option> { - self.canvas_state.get_texture_handle(texture_name) - } - - /// O(n) Lookup for the matching kernel string - pub fn get_kernel_handle(&self, kernel_name: String) -> Option> { - self.compute_state.get_kernel_handle(kernel_name) - } - - /// O(n) Lookup for the matching shader string - pub fn get_shader_handle(&self, shader_name: String) -> Option> { - self.canvas_state.get_shader_handle(shader_name) - } - - pub fn get_font_handle(&self, font_name: String) -> Option> { - self.canvas_state.get_font_handle(font_name) - } - - /// Create a new image which has the transfer usage - pub fn new_swap_image(&mut self, dimensions: (u32, u32)) -> Arc { - let mut usage = ImageUsage::none(); - usage.transfer_destination = true; - usage.storage = true; - - self.canvas_state.create_image(dimensions, usage) - } - - /// Builds a compute buffer and returns it's handle - pub fn new_compute_buffer(&mut self, data: Vec, dimensions: (u32, u32), stride: u32) -> Arc { - self.compute_state.new_compute_buffer(data, dimensions, stride, self.device.clone()) - } - - /// Takes a compute buffer handle and returns the read data - pub fn read_compute_buffer(&mut self, handle: Arc) -> Vec { - self.compute_state.read_compute_buffer(handle) - } - - /// Takes a compute buffer handle and writes the received data - pub fn write_compute_buffer(&self, handle: Arc, data: Vec) { - self.compute_state.write_compute_buffer(handle, data) - } - - /// Run the VKprocessor for a single frame, consuming the Canvas/Compu Frames - pub fn run(&mut self, - surface: &Arc>, - canvas_frame: &CanvasFrame, - compute_frame: &CompuFrame, - ) { - { - let g = hprof::enter("Waiting at queue"); - self.queue.wait(); - } - - let g = hprof::enter("Frame buffer, future, swapchain recreate"); - let mut framebuffers = - self.canvas_state.window_size_dependent_setup(&self.swapchain_images.clone().unwrap().clone()); - - // Whenever the window resizes we need to recreate everything dependent on the window size. - // In this example that includes the swapchain, the framebuffers and the dynamic state viewport. - if self.swapchain_recreate_needed { - self.recreate_swapchain(surface); - framebuffers = - self.canvas_state.window_size_dependent_setup(&self.swapchain_images.clone().unwrap().clone()); - self.swapchain_recreate_needed = false; - } - - // This function can block if no image is available. The parameter is an optional timeout - // after which the function call will return an error. - let (image_num, suboptimal, acquire_future) = - match vulkano::swapchain::acquire_next_image( - self.swapchain.clone().unwrap().clone(), - None, - ) { - Ok(r) => r, - Err(AcquireError::OutOfDate) => { - self.swapchain_recreate_needed = true; - return; - } - Err(err) => panic!("{:?}", err) - }; - - if suboptimal { - self.swapchain_recreate_needed = true; - } - - drop(g); - - let allocated_buffers = { - // take the canvas frame and create the vertex buffers - // TODO: This performs gpu buffer creation. Shouldn't be in hotpath?? - let g = hprof::enter("Canvas creates GPU buffers"); - self.canvas_state.allocate(canvas_frame) - }; - - // let mut draw_text = DrawText::new(self.device.clone(), self.queue.clone(), self.swapchain.unwrap().clone(), &self.swapchain_images.images); - - let mut command_buffer = - AutoCommandBufferBuilder::primary_one_time_submit(self.device.clone(), self.queue.family()).unwrap(); - - let g = hprof::enter("Push compute commands to command buffer"); - // Add the compute commands - self.compute_state.compute_commands(compute_frame, &mut command_buffer, &self.canvas_state); - drop(g); - - let g = hprof::enter("Push draw commands to command buffer"); - - // Add the draw commands - //let mut command_buffer = self.canvas_state.draw_commands(command_buffer, framebuffers, image_num); - self.canvas_state.draw_commands(&mut command_buffer, framebuffers, image_num, allocated_buffers); - - // And build - let command_buffer = command_buffer.build().unwrap(); - drop(g); - - // Wait on the previous frame, then execute the command buffer and present the image - { - let g = hprof::enter("Joining on the framebuffer"); - let mut future = sync::now(self.device.clone()) - .join(acquire_future); - drop(g); - - let g = hprof::enter("Running the kernel and waiting on the future"); - - let future = future - .then_execute(self.queue.clone(), command_buffer).unwrap() - .then_swapchain_present(self.queue.clone(), self.swapchain.clone().unwrap().clone(), image_num) - .then_signal_fence_and_flush(); - - match future { - Ok(future) => { - future.wait(None).unwrap(); - } - Err(FlushError::OutOfDate) => { - self.swapchain_recreate_needed = true; - } - Err(e) => { - println!("{:?}", e); - } - } - } - } -} - - - - - - - - - - - - - - - - - -