feat: node graph detection editor with blend modes and per-node previews

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<String, base64> — 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 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 20:12:22 -07:00
parent c4155c504e
commit a6ae70558b
33 changed files with 831 additions and 3111 deletions

View File

@@ -1,5 +1,6 @@
import { useState, useCallback, useEffect, useRef } from 'react' import { useState, useCallback, useEffect, useRef } from 'react'
import Viewport from './components/Viewport.jsx' import Viewport from './components/Viewport.jsx'
import NodeGraph from './components/NodeGraph.jsx'
import PassPanel from './components/PassPanel.jsx' import PassPanel from './components/PassPanel.jsx'
import PerfPanel from './components/PerfPanel.jsx' import PerfPanel from './components/PerfPanel.jsx'
import Slider from './components/Slider.jsx' import Slider from './components/Slider.jsx'
@@ -161,7 +162,7 @@ export default function App() {
try { try {
const result = await tauri.processPass({ const result = await tauri.processPass({
pass_index: idx, pass_index: idx,
layers: pass.layers, graph: pass.graph,
threshold: pass.threshold, threshold: pass.threshold,
min_area: pass.min_area, min_area: pass.min_area,
rdp_epsilon: pass.rdp_epsilon, rdp_epsilon: pass.rdp_epsilon,
@@ -171,10 +172,11 @@ export default function App() {
const js_process = Math.round(performance.now() - t0) const js_process = Math.round(performance.now() - t0)
setPerfData(pd => ({ ...(pd ?? {}), process: result.timings, js_process })) setPerfData(pd => ({ ...(pd ?? {}), process: result.timings, js_process }))
updatePass(idx, { updatePass(idx, {
status: `${result.hull_count} hulls · ${result.coverage_pct}% coverage`, status: `${result.hull_count} hulls · ${result.coverage_pct}% coverage`,
vizB64: result.viz_b64, vizB64: result.viz_b64,
hullCount: result.hull_count, hullCount: result.hull_count,
strokeCount: 0, strokeCount: 0,
nodePreviews: result.node_previews ?? {},
}) })
await generateFillInner(idx, true) await generateFillInner(idx, true)
} catch (e) { } catch (e) {
@@ -269,7 +271,7 @@ export default function App() {
async function dumpDebugState() { async function dumpDebugState() {
try { try {
const configs = passes.map(p => ({ 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, rdp_epsilon: p.rdp_epsilon, connectivity: p.connectivity,
color_filter: p.colorFilter, strategy: p.strategy, color_filter: p.colorFilter, strategy: p.strategy,
spacing: p.spacing, angle: p.angle, spacing: p.spacing, angle: p.angle,
@@ -404,15 +406,26 @@ export default function App() {
></button> ></button>
</div> </div>
{/* Viewport */} {/* Viewport / Node graph */}
<div className="flex-1 relative overflow-hidden"> <div className="flex-1 relative overflow-hidden">
<Viewport {viewMode === 'detection' ? (
imageB64={displayB64} <NodeGraph
strokes={viewMode === 'gcode' || viewMode === 'fill' ? strokes : null} graph={passes[activePass].graph}
imgSize={image} onChange={graph => {
viewMode={viewMode} updatePass(activePass, { graph })
gcodeConfig={gcodeConfig} scheduleProcess(activePass)
/> }}
nodePreviews={passes[activePass].nodePreviews}
/>
) : (
<Viewport
imageB64={displayB64}
strokes={viewMode === 'gcode' || viewMode === 'fill' ? strokes : null}
imgSize={image}
viewMode={viewMode}
gcodeConfig={gcodeConfig}
/>
)}
{showPerf && <PerfPanel data={perfData} fps={fps} longTasks={longTasks} />} {showPerf && <PerfPanel data={perfData} fps={fps} longTasks={longTasks} />}
{!image && ( {!image && (
<div className="absolute inset-0 flex items-center justify-center pointer-events-none"> <div className="absolute inset-0 flex items-center justify-center pointer-events-none">

View File

@@ -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 (
<div key={node.id}
style={{
position: 'absolute', left: node.x, top: node.y,
width: NODE_W, userSelect: 'none',
}}
>
{/* Input ports */}
{Array.from({ length: inputCnt }, (_, i) => (
<div key={i}
onMouseUp={e => 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 && (
<div
onMouseDown={e => 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 */}
<div style={{
background: '#1a1a2e',
border: `1px solid ${node.kind === 'Source' ? '#7c3aed' : node.kind === 'Output' ? '#b45309' : '#374151'}`,
borderRadius: 8, overflow: 'hidden',
boxShadow: '0 4px 16px rgba(0,0,0,0.5)',
}}>
{/* Header */}
<div
onMouseDown={e => 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,
}}
>
<span style={{ flex: 1, fontSize: 11, fontWeight: 600, color: '#e2e8f0', letterSpacing: '0.05em' }}>
{node.kind === 'Source' ? 'Source'
: node.kind === 'Output' ? 'Output'
: node.kind === 'Kernel' ? node.kernel ?? 'Kernel'
: 'Combine'}
</span>
{!isFixed && (
<button
onClick={() => deleteNode(node.id)}
onMouseDown={e => e.stopPropagation()}
style={{ color: '#6b7280', fontSize: 11, cursor: 'pointer', background: 'none', border: 'none', lineHeight: 1 }}
></button>
)}
</div>
{/* Body */}
<div style={{ padding: '6px 8px', display: 'flex', flexDirection: 'column', gap: 4 }}>
{/* Kernel controls */}
{node.kind === 'Kernel' && (<>
{/* Kernel selector */}
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
{KERNELS.map(k => (
<button key={k}
onMouseDown={e => e.stopPropagation()}
onClick={() => updateNode(node.id, { kernel: k })}
style={{
padding: '1px 5px', borderRadius: 3, fontSize: 10, cursor: 'pointer', border: 'none',
background: node.kernel === k ? '#4f46e5' : '#1e293b',
color: node.kernel === k ? '#fff' : '#94a3b8',
}}
>{k}</button>
))}
</div>
<Slider label="Weight" value={node.weight ?? 1} min={0} max={2} step={0.05}
onChange={v => updateNode(node.id, { weight: v })} />
{(KERNEL_PARAMS[node.kernel] ?? []).map(p => {
const m = PARAM_META[p]
return <Slider key={p} label={m.label} value={node[p] ?? 0} min={m.min} max={m.max} step={m.step}
onChange={v => updateNode(node.id, { [p]: v })} />
})}
<label style={{ display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer', fontSize: 10, color: '#94a3b8' }}>
<input type="checkbox" checked={node.invert ?? false}
onChange={e => updateNode(node.id, { invert: e.target.checked })}
style={{ accentColor: '#6366f1' }}
/>
Invert
</label>
</>)}
{/* Combine controls */}
{node.kind === 'Combine' && (<>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
{BLEND_MODES.map(m => (
<button key={m}
onMouseDown={e => e.stopPropagation()}
onClick={() => updateNode(node.id, { blendMode: m })}
style={{
padding: '1px 5px', borderRadius: 3, fontSize: 10, cursor: 'pointer', border: 'none',
background: node.blendMode === m ? '#0f766e' : '#1e293b',
color: node.blendMode === m ? '#fff' : '#94a3b8',
}}
>{m}</button>
))}
</div>
<div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>
<span style={{ fontSize: 10, color: '#6b7280' }}>Inputs: {node.inputCount ?? 2}</span>
<button onMouseDown={e => e.stopPropagation()} onClick={() => addCombinePort(node.id)}
style={{ fontSize: 11, color: '#6366f1', background: 'none', border: 'none', cursor: 'pointer', padding: '0 2px' }}>+</button>
<button onMouseDown={e => e.stopPropagation()} onClick={() => removeCombinePort(node.id)}
style={{ fontSize: 11, color: '#6b7280', background: 'none', border: 'none', cursor: 'pointer', padding: '0 2px' }}></button>
</div>
</>)}
{/* Preview thumbnail */}
{preview && (
<img src={`data:image/jpeg;base64,${preview}`} alt=""
style={{ width: '100%', borderRadius: 4, marginTop: 2, display: 'block', imageRendering: 'pixelated' }}
/>
)}
</div>
</div>
</div>
)
}
// ── 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 (
<path key={idx}
d={edgePath(from, to)}
stroke="#6366f1" strokeWidth={1.5} fill="none" opacity={0.7}
strokeLinecap="round"
style={{ cursor: 'pointer' }}
onClick={() => removeEdge(idx)}
/>
)
})
const wireEdge = wire && (
<path
d={edgePath({ x: wire.fromX, y: wire.fromY }, { x: wire.mouseX, y: wire.mouseY })}
stroke="#818cf8" strokeWidth={1.5} fill="none" strokeDasharray="6 3"
strokeLinecap="round" opacity={0.85}
/>
)
// ── Canvas size for SVG ────────────────────────────────────────────────────
const WORLD_SIZE = 4000
return (
<div style={{ position: 'relative', width: '100%', height: '100%', overflow: 'hidden', background: '#0a0a12' }}
ref={canvasRef}
onMouseDown={onCanvasMouseDown}
data-canvas="1"
>
{/* Toolbar */}
<div style={{ position: 'absolute', top: 8, left: 8, zIndex: 30, display: 'flex', gap: 4 }}>
<button onClick={() => addNode('Kernel')}
style={{ padding: '3px 10px', borderRadius: 4, fontSize: 11, cursor: 'pointer', border: '1px solid #374151', background: '#1e293b', color: '#94a3b8' }}
>+ Kernel</button>
<button onClick={() => addNode('Combine')}
style={{ padding: '3px 10px', borderRadius: 4, fontSize: 11, cursor: 'pointer', border: '1px solid #374151', background: '#1e293b', color: '#94a3b8' }}
>+ Combine</button>
<span style={{ fontSize: 10, color: '#374151', alignSelf: 'center', paddingLeft: 4 }}>
scroll to zoom · drag to pan · click wire to delete
</span>
</div>
{/* World transform */}
<div
data-canvas="1"
style={{
position: 'absolute', left: 0, top: 0,
width: WORLD_SIZE, height: WORLD_SIZE,
transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`,
transformOrigin: '0 0',
}}
>
{/* SVG for edges */}
<svg
style={{ position: 'absolute', top: 0, left: 0, width: WORLD_SIZE, height: WORLD_SIZE, overflow: 'visible', pointerEvents: 'none' }}
>
<g style={{ pointerEvents: 'all' }}>
{svgEdges}
</g>
{wireEdge}
</svg>
{/* Nodes */}
{graph.nodes.map(renderNode)}
</div>
</div>
)
}

View File

@@ -1,6 +1,5 @@
import Section from './Section.jsx' import Section from './Section.jsx'
import Slider from './Slider.jsx' import Slider from './Slider.jsx'
import DetectionLayers from './DetectionLayers.jsx'
import ColorFilter from './ColorFilter.jsx' import ColorFilter from './ColorFilter.jsx'
import { FILL_STRATEGIES, FILL_STRATEGY_PARAMS, FILL_USES_ANGLE } from '../store.js' import { FILL_STRATEGIES, FILL_STRATEGY_PARAMS, FILL_USES_ANGLE } from '../store.js'
@@ -46,14 +45,6 @@ export default function PassPanel({
)} )}
</div> </div>
{/* ── Detection ── kernels that produce the response map */}
<Section title="Detection" defaultOpen accent={C.detection}>
<DetectionLayers
layers={pass.layers}
onChange={layers => setDetection({ layers })}
/>
</Section>
{/* ── Hulls & Contours ── how the response map becomes components */} {/* ── Hulls & Contours ── how the response map becomes components */}
<Section title="Hulls & Contours" defaultOpen accent={C.hulls}> <Section title="Hulls & Contours" defaultOpen accent={C.hulls}>
<Slider label="Threshold" value={pass.threshold} min={1} max={254} step={1} <Slider label="Threshold" value={pass.threshold} min={1} max={254} step={1}

View File

@@ -2,6 +2,7 @@
// This file defines the shapes / defaults. // This file defines the shapes / defaults.
export const KERNELS = ['Luminance','Sobel','ColorGradient','Laplacian','Canny','Saturation','XDoG'] export const KERNELS = ['Luminance','Sobel','ColorGradient','Laplacian','Canny','Saturation','XDoG']
export const BLEND_MODES = ['Average','Min','Max','Multiply','Screen','Difference']
export const FILL_STRATEGIES = ['hatch','zigzag','offset','spiral','outline','circles','voronoi','hilbert','waves','flow'] export const FILL_STRATEGIES = ['hatch','zigzag','offset','spiral','outline','circles','voronoi','hilbert','waves','flow']
@@ -19,7 +20,7 @@ export const FILL_STRATEGY_PARAMS = {
// Strategies that use the angle slider // Strategies that use the angle slider
export const FILL_USES_ANGLE = new Set(['hatch', 'zigzag', 'flow']) export const FILL_USES_ANGLE = new Set(['hatch', 'zigzag', 'flow'])
export function defaultLayer() { export function defaultKernelProps() {
return { return {
kernel: 'Luminance', weight: 1.0, invert: false, kernel: 'Luminance', weight: 1.0, invert: false,
blur_radius: 0.0, sat_min_value: 0.1, blur_radius: 0.0, sat_min_value: 0.1,
@@ -28,6 +29,24 @@ export function defaultLayer() {
} }
} }
let _nodeSeq = 0
export function newNodeId(kind) { return `${kind.toLowerCase()}_${++_nodeSeq}` }
export function defaultGraph() {
const kId = newNodeId('kernel')
return {
nodes: [
{ id: 'source', kind: 'Source', x: 60, y: 160 },
{ id: kId, kind: 'Kernel', x: 290, y: 100, ...defaultKernelProps() },
{ id: 'output', kind: 'Output', x: 560, y: 160 },
],
edges: [
{ from: 'source', to: kId, port: 0 },
{ from: kId, to: 'output', port: 0 },
],
}
}
export function defaultColorFilter() { export function defaultColorFilter() {
return { enabled: false, hue_min: 0, hue_max: 360, sat_min: 0, sat_max: 1, val_min: 0, val_max: 1 } return { enabled: false, hue_min: 0, hue_max: 360, sat_min: 0, sat_max: 1, val_min: 0, val_max: 1 }
} }
@@ -37,7 +56,8 @@ export function defaultPass(index) {
return { return {
label: `Pass ${index + 1}`, label: `Pass ${index + 1}`,
penColor: colors[index] ?? [128,128,128], penColor: colors[index] ?? [128,128,128],
layers: [defaultLayer()], graph: defaultGraph(),
nodePreviews: {},
threshold: 128, threshold: 128,
min_area: 4, min_area: 4,
rdp_epsilon: 1.5, rdp_epsilon: 1.5,

View File

@@ -1,51 +0,0 @@
use std::sync::Arc;
use std::collections::HashMap;
use std::hash::Hash;
use crate::canvas::*;
use crate::canvas::managed::shader::dynamic_vertex::RuntimeVertexDef;
use crate::canvas::managed::handles::{CanvasTextureHandle, CanvasImageHandle, CanvasFontHandle, Handle};
use vulkano::pipeline::vertex::Vertex;
use std::any::Any;
use crate::VertexType;
use winit::event::Event;
/// Trait which may be inherited by objects that wish to be drawn to the screen
pub trait Drawable {
fn get(&self, window_size: (u32, u32)) -> Vec<VertexType>;
}
/// Trait which may be inherited by objects that wish to receive events
pub trait Eventable<T> {
fn notify(&mut self, event: &Event<T>) -> ();
}
/// 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<VertexType>,
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);
}
}
}

View File

@@ -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<Sampler>,
/// hold the image, texture, and Fonts the same was as we do CompuState
image_buffers: Vec<Arc<CanvasImage>>,
texture_buffers: Vec<Arc<CanvasTexture>>,
font_buffers: Vec<Arc<CanvasFont>>,
/// Compiled Graphics pipelines have a handle which self describe their position in this vector
shader_buffers: Vec<Arc<Box<dyn CompiledShader>>>,
/// Looks like we gotta hold onto the queue for managing textures
queue: Arc<Queue>,
device: Arc<Device>,
render_pass: Arc<dyn RenderPassAbstract + Send + Sync>,
}
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<SwapchainImage<Window>>])
-> Vec<Arc<dyn FramebufferAbstract + Send + Sync>> {
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<dyn FramebufferAbstract + Send + Sync>
}).collect::<Vec<_>>()
}
/// Creates a Canvas State. Which at this point is pretty empty
pub fn new(queue: Arc<Queue>,
device: Arc<Device>,
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: <ty>` 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<CanvasImageHandle> {
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<CanvasImageHandle>) -> Arc<AttachmentImage> {
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<ImmutableImage<Format>> {
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<Arc<CanvasTextureHandle>> {
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<T: 'static, V>(&mut self,
filename: String,
capabilities: Capabilities) -> Option<Arc<CompiledShaderHandle>>
where T: CompiledShader, V: Vertex {
let handle = Arc::new(CompiledShaderHandle {
handle: self.shader_buffers.len() as u32
});
let shader: Box<dyn CompiledShader> = Box::new(T::new::<V>(
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<CanvasFontHandle> {
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<Arc<CanvasTextureHandle>> {
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<Arc<CompiledShaderHandle>> {
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<Arc<CanvasFontHandle>> {
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<CanvasTextureHandle>)
-> Arc<ImmutableImage<Format>> {
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<GenericShader>) -> Box<dyn DescriptorSet + Send + Sync> {
let o: Box<dyn DescriptorSet + Send + Sync> = 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<ColorVertex3D> = Vec::default();
let mut textured_vertex_buffer: HashMap<Arc<CanvasTextureHandle>, Vec<TextureVertex3D>> = HashMap::new();
let mut image_vertex_buffer: HashMap<Arc<CanvasImageHandle>, Vec<ImageVertex3D>> = HashMap::new();
let mut text_instances: HashMap<Arc<CanvasFontHandle>, Vec<TextVertex3D>> = HashMap::new();
let mut text_vertex_buffer: Vec<ColorVertex3D> = 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<Arc<(dyn BufferAccess + Send + Sync)>> = 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<Arc<(dyn BufferAccess + Send + Sync)>> = 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<Arc<dyn FramebufferAbstract + Send + Sync>>,
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();
}
}

View File

@@ -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<TextVertex3D> {
//
// 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<Device>, queue: Arc<Queue>, 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();
// }
//
//}

View File

@@ -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<CanvasTextureHandle>,
pub(crate) buffer: Arc<ImmutableImage<Format>>,
pub(crate) name: String,
pub(crate) size: (u32, u32),
}
impl CanvasTexture {
pub fn get_descriptor_set(&self,
pipeline: Arc<dyn GraphicsPipelineAbstract + Sync + Send>,
sampler: Arc<Sampler>) -> Box<dyn DescriptorSet + Send + Sync> {
let o: Box<dyn DescriptorSet + Send + Sync> = 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<CanvasImageHandle>,
pub(crate) buffer: Arc<AttachmentImage>,
pub(crate) size: (u32, u32),
}
impl CanvasImage {
pub fn get_descriptor_set(&self, pipeline: Arc<dyn GraphicsPipelineAbstract + Sync + Send>)
-> Box<dyn DescriptorSet + Send + Sync> {
let o: Box<dyn DescriptorSet + Send + Sync> = 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<CanvasFontHandle>,
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<dyn GraphicsPipelineAbstract + Sync + Send>)
-> Box<dyn DescriptorSet + Send + Sync> {
let o: Box<dyn DescriptorSet + Send + Sync> = Box::new(
PersistentDescriptorSet::start(
pipeline.clone().descriptor_set_layout(0).unwrap().clone()
)
.add_buffer(self.buffer.clone()).unwrap()
.build().unwrap());
o
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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<I> VertexDefinition<I> 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::<Vec<_>>();
// 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::<i32>(), InputRate::Vertex),
(1, mem::size_of::<i32>(), InputRate::Vertex),
(2, mem::size_of::<i32>(), InputRate::Vertex),
(3, mem::size_of::<i32>(), InputRate::Vertex),
(4, mem::size_of::<i32>(), InputRate::Vertex),
(5, mem::size_of::<i32>(), InputRate::Vertex),
(6, mem::size_of::<i32>(), 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<Vec<Arc<dyn BufferAccess + Send + Sync>>> for RuntimeVertexDef {
fn decode(&self, bufs: Vec<Arc<dyn BufferAccess + Send + Sync>>)
-> (Vec<Box<dyn BufferAccess + Send + Sync>>, 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
)
}
}

View File

@@ -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<Arc<dyn GraphicsPipelineAbstract + Sync + Send>>,
handle: Arc<CompiledShaderHandle>,
name: String,
device: Arc<Device>,
renderpass: Arc<dyn RenderPassAbstract + Send + Sync>,
}
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<V: Vertex>(filename: String,
device: Arc<Device>,
handle: Arc<CompiledShaderHandle>,
render_pass: Arc<dyn RenderPassAbstract + Send + Sync>) -> 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::<V>::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<CompiledShaderHandle> {
self.handle.clone()
}
fn get_pipeline(&self) -> Arc<dyn GraphicsPipelineAbstract + Sync + Send> {
self.graphics_pipeline.clone().unwrap()
}
fn get_renderpass(&self) -> Arc<dyn RenderPassAbstract + Send + Sync> {
self.renderpass.clone()
}
fn recompile<V: Vertex>(self, render_pass: Arc<dyn RenderPassAbstract + Send + Sync>) -> GenericShader {
GenericShader::new::<V>(self.name,
self.device,
self.handle,
render_pass.clone())
}
}

View File

@@ -1,4 +0,0 @@
pub mod text_shader;
pub mod generic_shader;
pub mod dynamic_vertex;
pub mod shader_common;

View File

@@ -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<Device>, shader_type: ShaderType) -> (Entry, Arc<ShaderModule>) {
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<V>(filename: String,
device: Arc<Device>,
handle: Arc<CompiledShaderHandle>,
render_pass: Arc<dyn RenderPassAbstract + Send + Sync>) -> Self where Self: Sized, V: Vertex,;
fn get_name(&self) -> String;
fn get_handle(&self) -> Arc<CompiledShaderHandle>;
fn get_pipeline(&self) -> Arc<dyn GraphicsPipelineAbstract + Sync + Send>;
fn get_renderpass(&self) -> Arc<dyn RenderPassAbstract + Send + Sync>;
fn recompile<V: Vertex>(self, render_pass: Arc<dyn RenderPassAbstract + Send + Sync>)
-> 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,
}

View File

@@ -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<Arc<dyn GraphicsPipelineAbstract + Sync + Send>>,
handle: Arc<CompiledShaderHandle>,
name: String,
device: Arc<Device>,
renderpass: Arc<dyn RenderPassAbstract + Send + Sync>,
}
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<V: Vertex>(filename: String,
device: Arc<Device>,
handle: Arc<CompiledShaderHandle>,
render_pass: Arc<dyn RenderPassAbstract + Send + Sync>) -> 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::<V>::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<CompiledShaderHandle> {
self.handle.clone()
}
fn get_pipeline(&self) -> Arc<dyn GraphicsPipelineAbstract + Sync + Send> {
self.graphics_pipeline.clone().unwrap()
}
fn get_renderpass(&self) -> Arc<dyn RenderPassAbstract + Send + Sync> {
self.renderpass.clone()
}
fn recompile<V: Vertex>(self, render_pass: Arc<dyn RenderPassAbstract + Send + Sync>) -> TextShader {
TextShader::new::<V>(self.name,
self.device,
self.handle,
self.renderpass.clone())
}
}

View File

@@ -1,6 +0,0 @@
pub mod canvas_state;
pub mod canvas_frame;
pub mod managed;

View File

@@ -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<CompuBufferHandle>,
Arc<CompuKernelHandle>)>,
// Vec<(Buffer, Image, Kernel)>
pub swapped_to_image: Vec<(
Arc<CompuBufferHandle>,
Arc<CanvasImageHandle>,
Arc<CompuKernelHandle>)>,
// Vec<(Input Buffer, Output Buffer, Kernel)>
pub swapped_to_buffer: Vec<(
Arc<CompuBufferHandle>,
Arc<CompuBufferHandle>,
Arc<CompuKernelHandle>)>,
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<CompuBufferHandle>, kernel: Arc<CompuKernelHandle>) {
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<CompuBufferHandle>,
output_buffer: Arc<CompuBufferHandle>,
kernel: Arc<CompuKernelHandle>) {
self.swapped_to_buffer.push((input_buffer, output_buffer, kernel));
}
pub fn add_with_image_swap(&mut self,
buffer: Arc<CompuBufferHandle>,
kernel: Arc<CompuKernelHandle>,
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))
};
}
}
}

View File

@@ -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<CompuBuffers>,
kernels: Vec<CompuKernel>,
}
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<u8>,
dimensions: (u32, u32),
stride: u32,
device: Arc<Device>) -> Arc<CompuBufferHandle> {
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<CompuBufferHandle>) -> Vec<u8> {
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<CompuBufferHandle>, data: Vec<u8>) {
unimplemented!("read_compute_buffer is not implemented")
}
pub fn new_kernel(&mut self,
filename: String,
device: Arc<Device>) -> Arc<CompuKernelHandle> {
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<Arc<CompuKernelHandle>> {
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();
}
}
}

View File

@@ -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<Device>,
handle: Arc<CompuBufferHandle>,
io_buffers: Vec<Arc<CpuAccessibleBuffer<[u8]>>>,
settings_buffer: Arc<CpuAccessibleBuffer<[u32]>>,
}
impl CompuBuffers {
pub fn new(device: Arc<Device>, data: Vec<u8>,
dimensions: (u32, u32), stride: u32,
handle: Arc<CompuBufferHandle>) -> 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<ComputePipeline<PipelineLayout<Layout>>>)
-> Arc<PersistentDescriptorSet<((((),
PersistentDescriptorSetBuf<std::sync::Arc<vulkano::buffer::cpu_access::CpuAccessibleBuffer<[u8]>>>),
PersistentDescriptorSetBuf<std::sync::Arc<vulkano::buffer::cpu_access::CpuAccessibleBuffer<[u8]>>>),
PersistentDescriptorSetBuf<std::sync::Arc<vulkano::buffer::cpu_access::CpuAccessibleBuffer<[u32]>>>)>> {
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<Rgba<u8>, Vec<u8>> {
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<CpuAccessibleBuffer<[u8]>> {
self.io_buffers.get(1).unwrap().clone()
}
pub fn get_output_buffer(&self) -> Arc<CpuAccessibleBuffer<[u8]>> {
self.io_buffers.get(0).unwrap().clone()
}
}

View File

@@ -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<CompuKernelHandle>,
compute_pipeline: Option<std::sync::Arc<ComputePipeline<PipelineLayout<Layout>>>>,
compute_kernel_path: PathBuf,
name: String,
shader: CompiledShader,
entry: Entry,
shader_module: Arc<ShaderModule>,
device: Arc<Device>,
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<Device>,
handle: Arc<CompuKernelHandle>) -> 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<ComputePipeline<PipelineLayout<Layout>>> {
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<ComputePipeline<PipelineLayout<shade_runner::layouts::Layout>>> {
self.compile_kernel(String::from(self.compute_kernel_path.clone().to_str().unwrap()))
}
pub fn compile_kernel(&mut self, filename: String) -> std::sync::Arc<ComputePipeline<PipelineLayout<Layout>>> {
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<CompuKernelHandle> {
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
}
}

View File

@@ -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
}
}

View File

@@ -1,4 +0,0 @@
pub mod compu_buffer;
pub mod compu_kernel;
pub mod handles;

View File

@@ -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;

View File

@@ -383,6 +383,215 @@ fn gaussian_3x3(src: &[f32], w: usize, h: usize) -> Vec<f32> {
out 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<GraphNode>,
pub edges: Vec<GraphEdge>,
}
/// Evaluate the detection graph. Returns (final_response_map, per_node_maps).
/// Per-node maps: node_id → Vec<u8> 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<u8>, std::collections::HashMap<String, Vec<u8>>) {
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<u8>> = HashMap::new();
for &id in &order {
let node = node_map[id];
let result: Option<Vec<u8>> = 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<String, Vec<u8>> = 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<u8> {
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 ────────────────────────────────────────────────────────────────────── // ── Tests ──────────────────────────────────────────────────────────────────────
#[cfg(test)] #[cfg(test)]

View File

@@ -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<CanvasImageHandle>) -> 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<VertexType> {
vec![self.verts.clone()]
}
}

View File

@@ -1,6 +0,0 @@
pub mod slider;
pub mod polygon;
pub mod sprite;
pub mod rect;
pub mod compu_sprite;
pub mod text;

View File

@@ -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<VertexType> {
vec![self.verts.clone()]
}
}

View File

@@ -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<ColorVertex3D> {
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<VertexType> {
vec![
VertexType::ColorType(
Rect::generate_vertices(window_size, self.position, self.size, self.depth, self.color)
)
]
}
}

View File

@@ -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<Rect>,
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<VertexType> {
let mut vertices = self.handle.get(window_size).clone();
vertices.extend_from_slice(
self.guide.iter()
.map(|x| x.get(window_size))
.flatten()
.collect::<Vec<VertexType>>()
.as_slice()
);
vertices.extend_from_slice(self.guide[0].get(window_size).as_slice());
vertices
}
}
impl<T> Eventable<T> for Slider {
fn notify(&mut self, event: &Event<T>) -> () {
unimplemented!()
}
}

View File

@@ -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<CanvasTextureHandle>,
}
/// Container class which implements drawable.
impl Sprite {
fn generate_verts(window_size: (u32, u32), position: (f32, f32), size: (f32, f32), depth: f32) -> Vec<TextureVertex3D> {
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<CanvasTextureHandle>) -> 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<VertexType> {
vec![
VertexType::TextureType(
Sprite::generate_verts(window_size, self.position, self.size, self.depth),
self.texture_handle.clone())
]
}
}
impl<T> Eventable<T> for Sprite {
fn notify(&mut self, event: &Event<T>) -> () {
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) -> () {
}
}

View File

@@ -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<VertexType> {
vec![self.verts.clone()]
}
}

View File

@@ -49,17 +49,37 @@ pub struct ImageInfo {
} }
#[derive(Deserialize, Clone, Debug)] #[derive(Deserialize, Clone, Debug)]
pub struct DetectionLayerPayload { pub struct GraphNodePayload {
pub kernel: String, pub id: String,
pub weight: f32, pub kind: String, // "Source" | "Kernel" | "Combine" | "Output"
pub invert: bool, pub x: f32,
pub blur_radius: f32, pub y: f32,
pub sat_min_value: f32, // Kernel params (optional)
pub canny_low: f32, pub kernel: Option<String>,
pub canny_high: f32, pub weight: Option<f32>,
pub xdog_sigma2: f32, pub invert: Option<bool>,
pub xdog_tau: f32, pub blur_radius: Option<f32>,
pub xdog_phi: f32, pub sat_min_value: Option<f32>,
pub canny_low: Option<f32>,
pub canny_high: Option<f32>,
pub xdog_sigma2: Option<f32>,
pub xdog_tau: Option<f32>,
pub xdog_phi: Option<f32>,
// Combine params (optional)
pub blend_mode: Option<String>,
}
#[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<GraphNodePayload>,
pub edges: Vec<GraphEdgePayload>,
} }
#[derive(Deserialize, Clone, Debug)] #[derive(Deserialize, Clone, Debug)]
@@ -75,13 +95,13 @@ pub struct ColorFilterPayload {
#[derive(Deserialize, Clone, Debug)] #[derive(Deserialize, Clone, Debug)]
pub struct ProcessPassPayload { pub struct ProcessPassPayload {
pub pass_index: usize, pub pass_index: usize,
pub layers: Vec<DetectionLayerPayload>, pub graph: DetectionGraphPayload,
pub threshold: u8, pub threshold: u8,
pub min_area: u32, pub min_area: u32,
pub rdp_epsilon: f32, pub rdp_epsilon: f32,
pub connectivity: String, // "four" | "eight" pub connectivity: String, // "four" | "eight"
pub color_filter: ColorFilterPayload, pub color_filter: ColorFilterPayload,
} }
#[derive(Serialize, Clone, Default)] #[derive(Serialize, Clone, Default)]
@@ -95,6 +115,7 @@ pub struct ProcessResult {
pub hull_count: usize, pub hull_count: usize,
pub coverage_pct: usize, pub coverage_pct: usize,
pub viz_b64: String, pub viz_b64: String,
pub node_previews: std::collections::HashMap<String, String>,
pub timings: Vec<StepTime>, pub timings: Vec<StepTime>,
} }
@@ -154,33 +175,52 @@ pub struct PassStrokesPayload {
// ── Helpers ──────────────────────────────────────────────────────────────────── // ── Helpers ────────────────────────────────────────────────────────────────────
fn to_detection_params(layers: &[DetectionLayerPayload]) -> detect::DetectionParams { fn to_detection_graph(payload: &DetectionGraphPayload) -> detect::DetectionGraph {
use detect::DetectionKernel::*; use detect::DetectionKernel::*;
detect::DetectionParams { let nodes = payload.nodes.iter().map(|n| {
layers: layers.iter().map(|l| { let kind = match n.kind.as_str() {
let kernel = match l.kernel.as_str() { "Source" => detect::NodeKind::Source,
"Sobel" => Sobel, "Kernel" => {
"ColorGradient" => ColorGradient, let kernel = match n.kernel.as_deref().unwrap_or("Luminance") {
"Laplacian" => Laplacian, "Sobel" => Sobel,
"Canny" => Canny, "ColorGradient" => ColorGradient,
"Saturation" => Saturation, "Laplacian" => Laplacian,
"XDoG" => XDoG, "Canny" => Canny,
_ => Luminance, "Saturation" => Saturation,
}; "XDoG" => XDoG,
detect::DetectionLayer { _ => Luminance,
kernel, };
weight: l.weight, detect::NodeKind::Kernel(detect::DetectionLayer {
invert: l.invert, kernel,
blur_radius: l.blur_radius, weight: n.weight.unwrap_or(1.0),
sat_min_value: l.sat_min_value, invert: n.invert.unwrap_or(false),
canny_low: l.canny_low, blur_radius: n.blur_radius.unwrap_or(0.0),
canny_high: l.canny_high, sat_min_value: n.sat_min_value.unwrap_or(0.1),
xdog_sigma2: l.xdog_sigma2, canny_low: n.canny_low.unwrap_or(50.0),
xdog_tau: l.xdog_tau, canny_high: n.canny_high.unwrap_or(150.0),
xdog_phi: l.xdog_phi, 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 { 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()) 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 { fn rgb_to_b64_jpeg(rgb: &image::RgbImage) -> String {
const MAX_DIM: u32 = 1024; const MAX_DIM: u32 = 1024;
let (w, h) = rgb.dimensions(); let (w, h) = rgb.dimensions();
@@ -240,7 +298,7 @@ fn process_pass_work(
let mut steps: Vec<StepTime> = Vec::new(); let mut steps: Vec<StepTime> = Vec::new();
let (w, h) = rgb.dimensions(); 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 { let hull_params = hulls::HullParams {
threshold: payload.threshold, threshold: payload.threshold,
min_area: payload.min_area, min_area: payload.min_area,
@@ -254,9 +312,14 @@ fn process_pass_work(
let color_filter = to_color_filter(&payload.color_filter); let color_filter = to_color_filter(&payload.color_filter);
let mut t = Instant::now(); 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); t = lap!(steps, "detect", t);
let node_previews: std::collections::HashMap<String, String> = 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); let all_hulls = hulls::extract_hulls(&response, rgb, w, h, &hull_params);
t = lap!(steps, "hull extract", t); 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 }); 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( fn generate_fill_work(
@@ -803,18 +866,17 @@ mod blocking_tests {
fn default_process_payload() -> ProcessPassPayload { fn default_process_payload() -> ProcessPassPayload {
ProcessPassPayload { ProcessPassPayload {
pass_index: 0, pass_index: 0,
layers: vec![DetectionLayerPayload { graph: DetectionGraphPayload {
kernel: "Luminance".into(), nodes: vec![
weight: 1.0, 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 },
invert: false, 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 },
blur_radius: 0.0, 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 },
sat_min_value: 0.0, ],
canny_low: 0.1, edges: vec![
canny_high: 0.3, GraphEdgePayload { from: "source".into(), to: "k1".into(), port: 0 },
xdog_sigma2: 1.5, GraphEdgePayload { from: "k1".into(), to: "output".into(), port: 0 },
xdog_tau: 0.98, ],
xdog_phi: 10.0, },
}],
threshold: 128, threshold: 128,
min_area: 10, min_area: 10,
rdp_epsilon: 2.0, 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_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, 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, let params = HullParams { threshold, min_area, rdp_epsilon: rdp_eps,
connectivity: hulls::Connectivity::Four }; connectivity: hulls::Connectivity::Four };
let hs = hulls::extract_hulls(&response, &img, w, h, &params); let hs = hulls::extract_hulls(&response, &img, w, h, &params);

View File

@@ -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<Device>,
pub queues: QueuesIter,
pub queue: Arc<Queue>,
pub swapchain: Option<Arc<Swapchain<Window>>>,
pub swapchain_images: Option<Vec<Arc<SwapchainImage<Window>>>>,
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<Instance>, surface: Arc<Surface<Window>>) -> 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<Instance>, surface: Arc<Surface<Window>>) {
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::<u32>(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<Surface<Window>>) {
let dimensions = if let dimensions = surface.window().inner_size() {
let dimensions: (u32, u32) = dimensions.to_logical::<u32>(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::<GenericShader, ColorVertex3D>(String::from("color-passthrough"), self.capabilities.clone());
self.canvas_state.load_shader::<GenericShader, TextureVertex3D>(String::from("simple_texture"), self.capabilities.clone());
self.canvas_state.load_shader::<GenericShader, ImageVertex3D>(String::from("simple_image"), self.capabilities.clone());
self.canvas_state.load_shader::<TextShader, ColorVertex3D>(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<Arc<CanvasTextureHandle>> {
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<Arc<CompuKernelHandle>> {
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<Arc<CompiledShaderHandle>> {
self.canvas_state.get_shader_handle(shader_name)
}
pub fn get_font_handle(&self, font_name: String) -> Option<Arc<CanvasFontHandle>> {
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<CanvasImageHandle> {
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<u8>, dimensions: (u32, u32), stride: u32) -> Arc<CompuBufferHandle> {
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<CompuBufferHandle>) -> Vec<u8> {
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<CompuBufferHandle>, data: Vec<u8>) {
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<Surface<Window>>,
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);
}
}
}
}
}