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:
@@ -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,
|
||||||
@@ -175,6 +176,7 @@ export default function App() {
|
|||||||
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,8 +406,18 @@ 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">
|
||||||
|
{viewMode === 'detection' ? (
|
||||||
|
<NodeGraph
|
||||||
|
graph={passes[activePass].graph}
|
||||||
|
onChange={graph => {
|
||||||
|
updatePass(activePass, { graph })
|
||||||
|
scheduleProcess(activePass)
|
||||||
|
}}
|
||||||
|
nodePreviews={passes[activePass].nodePreviews}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<Viewport
|
<Viewport
|
||||||
imageB64={displayB64}
|
imageB64={displayB64}
|
||||||
strokes={viewMode === 'gcode' || viewMode === 'fill' ? strokes : null}
|
strokes={viewMode === 'gcode' || viewMode === 'fill' ? strokes : null}
|
||||||
@@ -413,6 +425,7 @@ export default function App() {
|
|||||||
viewMode={viewMode}
|
viewMode={viewMode}
|
||||||
gcodeConfig={gcodeConfig}
|
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">
|
||||||
|
|||||||
441
src-frontend/src/components/NodeGraph.jsx
Normal file
441
src-frontend/src/components/NodeGraph.jsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
pub mod text_shader;
|
|
||||||
pub mod generic_shader;
|
|
||||||
pub mod dynamic_vertex;
|
|
||||||
pub mod shader_common;
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
|
|
||||||
pub mod canvas_state;
|
|
||||||
pub mod canvas_frame;
|
|
||||||
pub mod managed;
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
pub mod compu_buffer;
|
|
||||||
pub mod compu_kernel;
|
|
||||||
pub mod handles;
|
|
||||||
@@ -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;
|
|
||||||
209
src/detect.rs
209
src/detect.rs
@@ -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)]
|
||||||
|
|||||||
@@ -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()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
pub mod slider;
|
|
||||||
pub mod polygon;
|
|
||||||
pub mod sprite;
|
|
||||||
pub mod rect;
|
|
||||||
pub mod compu_sprite;
|
|
||||||
pub mod text;
|
|
||||||
@@ -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()]
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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) -> () {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
159
src/lib.rs
159
src/lib.rs
@@ -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)]
|
||||||
@@ -76,7 +96,7 @@ 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,
|
||||||
@@ -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,11 +175,13 @@ 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,
|
||||||
|
"Kernel" => {
|
||||||
|
let kernel = match n.kernel.as_deref().unwrap_or("Luminance") {
|
||||||
"Sobel" => Sobel,
|
"Sobel" => Sobel,
|
||||||
"ColorGradient" => ColorGradient,
|
"ColorGradient" => ColorGradient,
|
||||||
"Laplacian" => Laplacian,
|
"Laplacian" => Laplacian,
|
||||||
@@ -167,20 +190,37 @@ fn to_detection_params(layers: &[DetectionLayerPayload]) -> detect::DetectionPar
|
|||||||
"XDoG" => XDoG,
|
"XDoG" => XDoG,
|
||||||
_ => Luminance,
|
_ => Luminance,
|
||||||
};
|
};
|
||||||
detect::DetectionLayer {
|
detect::NodeKind::Kernel(detect::DetectionLayer {
|
||||||
kernel,
|
kernel,
|
||||||
weight: l.weight,
|
weight: n.weight.unwrap_or(1.0),
|
||||||
invert: l.invert,
|
invert: n.invert.unwrap_or(false),
|
||||||
blur_radius: l.blur_radius,
|
blur_radius: n.blur_radius.unwrap_or(0.0),
|
||||||
sat_min_value: l.sat_min_value,
|
sat_min_value: n.sat_min_value.unwrap_or(0.1),
|
||||||
canny_low: l.canny_low,
|
canny_low: n.canny_low.unwrap_or(50.0),
|
||||||
canny_high: l.canny_high,
|
canny_high: n.canny_high.unwrap_or(150.0),
|
||||||
xdog_sigma2: l.xdog_sigma2,
|
xdog_sigma2: n.xdog_sigma2.unwrap_or(1.6),
|
||||||
xdog_tau: l.xdog_tau,
|
xdog_tau: n.xdog_tau.unwrap_or(0.98),
|
||||||
xdog_phi: l.xdog_phi,
|
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, ¶ms);
|
let hs = hulls::extract_hulls(&response, &img, w, h, ¶ms);
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user