sdl2-bench: remove the SDL2 bench (aka. SDL2 demo) entirely

First, from now on I'm going to call the "SDL2 demo" the "SDL2 bench",
as a demo is software that exists to demonstrate, whereas its purpose
lately has been only fast prototyping, as we switched to make all demo
animations in the Egui port anyway.

Second, I'm removing the SDL2 bench anyway as it's a maintenance burden
that lost its utility. It's also affected by a bug in Pathfinder that
makes it impossible to zoom beyond a certain threshold which would
require someone to dig into Pathfinder's codebase, a task I don't think
anyone is interested in.
This commit is contained in:
Mikolaj Wielgus 2024-07-15 01:05:05 +02:00
parent 1e21a501d8
commit cd6c9fbe76
6 changed files with 29 additions and 520 deletions

View File

@ -16,15 +16,10 @@ required-features = ["cli"]
name = "topola-egui"
required-features = ["egui"]
[[bin]]
name = "topola-sdl2-demo"
required-features = ["sdl2"]
[features]
default = ["disable_contracts"]
cli = ["dep:clap"]
egui = ["dep:eframe", "dep:egui", "dep:rfd", "dep:futures"]
sdl2 = ["dep:sdl2", "dep:gl", "dep:pathfinder_canvas", "dep:pathfinder_geometry", "dep:pathfinder_gl", "dep:pathfinder_renderer", "dep:pathfinder_resources"]
disable_contracts = ["contracts/disable_contracts"]
[dependencies]
@ -75,12 +70,6 @@ version = "0.14.0"
optional = true
version = "0.3.30"
[dependencies.sdl2]
optional = true
version = "0.35.2"
default-features = false
features = ["bundled"]
[dependencies.gl]
optional = true
version = "0.14.0"

View File

@ -44,16 +44,6 @@ To build and open Topola in your browser, run
trunk serve
### SDL2 demo
Optionally, for shorter build times you may build the SDL2 demo instead
of the Egui port:
cargo build --features sdl2 --bin topola-sdl2-demo
cargo run --features sdl2 --bin topola-sdl2-demo
The downside is that the SDL2 demo's user interface is highly incomplete.
### Automated tests
Topola has automated tests to make sure its basic functionalities work.

View File

@ -1,14 +1,14 @@
use std::{
fs::File,
sync::{mpsc::Sender, Arc, Mutex},
path::Path,
sync::{mpsc::Sender, Arc, Mutex},
};
use topola::{
autorouter::invoker::{
Command, Execute, ExecuteWithStatus, Invoker, InvokerError, InvokerStatus,
},
specctra::{mesadata::SpecctraMesadata, design::SpecctraDesign},
specctra::{design::SpecctraDesign, mesadata::SpecctraMesadata},
};
use crate::{
@ -45,8 +45,11 @@ impl Top {
) -> Result<(), InvokerError> {
let mut open_design =
Trigger::new(Action::new("Open", egui::Modifiers::CTRL, egui::Key::O));
let mut export_session =
Trigger::new(Action::new("Export session file", egui::Modifiers::CTRL, egui::Key::S));
let mut export_session = Trigger::new(Action::new(
"Export session file",
egui::Modifiers::CTRL,
egui::Key::S,
));
let mut import_history = Trigger::new(Action::new(
"Import history",
egui::Modifiers::CTRL,

View File

@ -1,382 +0,0 @@
extern crate sdl2;
mod painter;
macro_rules! dbg_dot {
($graph:expr) => {
use petgraph::dot::Dot;
println!("{:?}", Dot::new(&$graph));
};
}
use geo::point;
use painter::Painter;
use petgraph::graph::NodeIndex;
use petgraph::visit::{EdgeRef, IntoEdgeReferences};
use topola::autorouter::selection::Selection;
use topola::autorouter::{Autorouter, AutorouterStatus};
use topola::drawing::dot::FixedDotWeight;
use topola::drawing::graph::{MakePrimitive, PrimitiveIndex};
use topola::drawing::primitive::MakePrimitiveShape;
use topola::drawing::rules::{AccessRules, Conditions};
use topola::drawing::seg::FixedSegWeight;
use topola::drawing::{Infringement, LayoutException};
use topola::geometry::primitive::{AccessPrimitiveShape, PrimitiveShape};
use topola::geometry::shape::AccessShape;
use topola::layout::poly::MakePolyShape;
use topola::layout::Layout;
use topola::router::draw::DrawException;
use topola::router::navmesh::Navmesh;
use topola::router::tracer::Tracer;
use topola::specctra::design::SpecctraDesign;
use topola::specctra::mesadata::SpecctraMesadata;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::video::{GLProfile, Window};
use sdl2::EventPump;
use pathfinder_canvas::{Canvas, CanvasFontContext, ColorU};
use pathfinder_geometry::vector::{vec2f, vec2i, Vector2F};
use pathfinder_gl::{GLDevice, GLVersion};
use pathfinder_renderer::concurrent::rayon::RayonExecutor;
use pathfinder_renderer::concurrent::scene_proxy::SceneProxy;
use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererMode, RendererOptions};
use pathfinder_renderer::gpu::renderer::Renderer;
use pathfinder_renderer::options::BuildOptions;
use pathfinder_resources::embedded::EmbeddedResourceLoader;
use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader;
use std::time::Duration;
use topola::math::Circle;
use topola::router::Router;
struct SimpleRules {
net_clearances: HashMap<(usize, usize), f64>,
}
impl AccessRules for SimpleRules {
fn clearance(&self, conditions1: &Conditions, conditions2: &Conditions) -> f64 {
if let (Some(net1), Some(net2)) = (conditions1.maybe_net, conditions2.maybe_net) {
*self.net_clearances.get(&(net1, net2)).unwrap_or(&10.0)
} else {
10.0
}
}
fn largest_clearance(&self, maybe_net: Option<usize>) -> f64 {
let mut highest_clearance = 0.0;
if let Some(net) = maybe_net {
for ((net1, net2), clearance) in self.net_clearances.iter() {
if *net1 == net || *net2 == net {
highest_clearance = *clearance;
}
}
}
highest_clearance
}
}
// Clunky enum to work around borrow checker.
enum RouterOrLayout<'a, R: AccessRules> {
Router(&'a mut Router<'a, R>),
Layout(&'a Layout<R>),
}
fn main() -> Result<(), anyhow::Error> {
let sdl_context = sdl2::init().unwrap();
let video_subsystem = sdl_context.video().unwrap();
let gl_attr = video_subsystem.gl_attr();
gl_attr.set_context_profile(GLProfile::Core);
gl_attr.set_context_version(4, 0);
let window = video_subsystem
.window("Topola demo", 800, 600)
.opengl()
.position_centered()
.build()
.unwrap();
let _context = window.gl_create_context().unwrap();
gl::load_with(|name| video_subsystem.gl_get_proc_address(name) as *const _);
// doing this later (after pathfinder assumes control of the context) would be a bad idea
// but for the early clear it's simpler than passing a blank canvas to pathfinder
unsafe {
gl::ClearColor(0.0, 0.0, 0.0, 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
}
window.gl_swap_window();
// XXX: not sure if this automatically fallbacks if we get a GL3 context
// or if we have to detect it and/or retry
let device = GLDevice::new(GLVersion::GL4, 0);
let mode = RendererMode::default_for_device(&device);
let options = RendererOptions {
dest: DestFramebuffer::full_window(vec2i(800, 600)),
background_color: Some(ColorU::black().to_f32()),
//show_debug_ui: true,
..RendererOptions::default()
};
let resource_loader = EmbeddedResourceLoader::new();
let mut renderer = Renderer::new(device, &resource_loader, mode, options);
let font_context = CanvasFontContext::from_system_source();
// TODO: make a type like this wrapping the details of pathfinder
// so we don't pass so many arguments to render_times()
//let mut canvas = window.into_canvas().build().unwrap();
let mut event_pump = sdl_context.event_pump().unwrap();
let _i = 0;
/*let mut router = Router::new(Layout::new(SimpleRules {
net_clearances: HashMap::from([
((1, 2), 8.0),
((2, 1), 8.0),
((2, 3), 3.0),
((3, 2), 3.0),
((3, 4), 15.0),
((4, 3), 15.0),
]),
}));*/
let design_file = File::open(
"tests/single_layer/data/de9_tht_female_to_tht_female/de9_tht_female_to_tht_female.dsn",
)
.unwrap();
let design_bufread = BufReader::new(design_file);
let design = SpecctraDesign::load(design_bufread)?;
let board = design.make_board();
let mut view = View {
pan: vec2f(-80000.0, -60000.0),
zoom: 0.005,
};
render_times(
&mut event_pump,
&window,
&mut renderer,
&font_context,
&mut view,
RouterOrLayout::Layout(board.layout()),
None,
None,
&[],
&[],
&[],
-1,
);
let mut autorouter = Autorouter::new(board).unwrap();
if let Ok(mut autoroute) = autorouter.autoroute_walk(&Selection::new()) {
loop {
let status = match autoroute.step(&mut autorouter) {
Ok(status) => status,
Err(err) => break,
};
if let AutorouterStatus::Finished = status {
break;
}
}
}
// these are both on net 1 in the test file
/*let _ = router.route_band(
dot_indices[1],
dot_indices[2],
3.0,
)?;*/
render_times(
&mut event_pump,
&window,
&mut renderer,
&font_context,
&mut view,
RouterOrLayout::Layout(autorouter.board().layout()),
None,
None,
&[],
&[],
&[],
-1,
);
Ok(())
}
struct View {
pan: Vector2F,
zoom: f32,
}
fn render_times(
event_pump: &mut EventPump,
window: &Window,
renderer: &mut Renderer<GLDevice>,
font_context: &CanvasFontContext,
view: &mut View,
mut router_or_layout: RouterOrLayout<impl AccessRules>,
_unused: Option<()>,
mut maybe_navmesh: Option<Navmesh>,
path: &[NodeIndex<usize>],
ghosts: &[PrimitiveShape],
highlighteds: &[PrimitiveIndex],
times: i64,
) {
let mut i = 0;
'running: loop {
for event in event_pump.poll_iter() {
match event {
Event::Quit { .. }
| Event::KeyDown {
keycode: Some(Keycode::Escape),
..
} => break 'running,
Event::MouseWheel { y, .. } => {
view.zoom *= f32::powf(1.4, y as f32);
}
Event::MouseMotion {
xrel,
yrel,
mousestate,
..
} => {
if mousestate.left() {
view.pan += vec2f(xrel as f32, yrel as f32) / view.zoom;
}
}
_ => {}
}
}
renderer.options_mut().background_color = Some(ColorU::new(0, 10, 35, 255).to_f32());
let window_size = window.size();
let mut canvas = Canvas::new(vec2f(window_size.0 as f32, window_size.1 as f32))
.get_context_2d(font_context.clone());
let center = vec2f(400.0, 300.0);
canvas.translate(center);
canvas.scale(vec2f(view.zoom, view.zoom));
canvas.translate(-center + view.pan);
let mut painter = Painter::new(&mut canvas);
let layout = match router_or_layout {
RouterOrLayout::Router(ref mut router) => {
let state = event_pump.mouse_state();
/*if let Some(band) = maybe_band {
router
.reroute_band(
band,
point! {x: state.x() as f64, y: state.y() as f64},
3.0,
)
.ok();
maybe_navmesh = None;
}*/
router.layout()
}
RouterOrLayout::Layout(ref layout) => layout.clone(),
};
for node in layout.drawing().layer_primitive_nodes(1) {
let color = if highlighteds.contains(&node) {
ColorU::new(100, 100, 255, 255)
} else {
ColorU::new(52, 52, 200, 255)
};
let shape = node.primitive(layout.drawing()).shape();
painter.paint_primitive(&shape, color, view.zoom);
}
for poly in layout.layer_poly_nodes(1) {
painter.paint_polygon(
&layout.poly(poly).shape().polygon,
ColorU::new(52, 52, 200, 255),
view.zoom,
);
}
for node in layout.drawing().layer_primitive_nodes(0) {
let color = if highlighteds.contains(&node) {
ColorU::new(255, 100, 100, 255)
} else {
ColorU::new(200, 52, 52, 255)
};
let shape = node.primitive(layout.drawing()).shape();
painter.paint_primitive(&shape, color, view.zoom);
}
for poly in layout.layer_poly_nodes(0) {
painter.paint_polygon(
&layout.poly(poly).shape().polygon,
ColorU::new(200, 52, 52, 255),
view.zoom,
);
}
for ghost in ghosts {
painter.paint_primitive(&ghost, ColorU::new(75, 75, 150, 255), view.zoom);
}
if let Some(ref navmesh) = maybe_navmesh {
for edge in navmesh.graph().edge_references() {
let from =
PrimitiveIndex::from(navmesh.graph().node_weight(edge.source()).unwrap().node)
.primitive(layout.drawing())
.shape()
.center();
let to =
PrimitiveIndex::from(navmesh.graph().node_weight(edge.target()).unwrap().node)
.primitive(layout.drawing())
.shape()
.center();
let color = 'blk: {
if let (Some(source_pos), Some(target_pos)) = (
path.iter().position(|node| *node == edge.source()),
path.iter().position(|node| *node == edge.target()),
) {
if target_pos == source_pos + 1 {
break 'blk ColorU::new(250, 250, 0, 255);
}
}
ColorU::new(125, 125, 125, 255)
};
painter.paint_edge(from, to, color, view.zoom);
}
}
let mut scene = SceneProxy::from_scene(
canvas.into_canvas().into_scene(),
renderer.mode().level,
RayonExecutor,
);
scene.build_and_render(renderer, BuildOptions::default());
window.gl_swap_window();
i += 1;
if times != -1 && i >= times {
return;
}
::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
}
}

View File

@ -1,98 +0,0 @@
use geo::{CoordsIter, Point, Polygon};
use pathfinder_canvas::{
vec2f, ArcDirection, Canvas, CanvasRenderingContext2D, ColorU, FillRule, Path2D, RectF,
};
use topola::geometry::primitive::{AccessPrimitiveShape, PrimitiveShape};
pub struct Painter<'a> {
canvas: &'a mut CanvasRenderingContext2D,
}
impl<'a> Painter<'a> {
pub fn new(canvas: &'a mut CanvasRenderingContext2D) -> Self {
Self { canvas }
}
pub fn paint_primitive(&mut self, shape: &PrimitiveShape, color: ColorU, zoom: f32) {
self.canvas.set_stroke_style(color);
self.canvas.set_fill_style(color);
match shape {
PrimitiveShape::Dot(dot) => {
let mut path = Path2D::new();
path.ellipse(
vec2f(dot.circle.pos.x() as f32, -dot.circle.pos.y() as f32),
dot.circle.r as f32,
0.0,
0.0,
std::f32::consts::TAU,
);
self.canvas.fill_path(path, FillRule::Winding);
}
PrimitiveShape::Seg(seg) => {
let mut path = Path2D::new();
path.move_to(vec2f(seg.from.x() as f32, -seg.from.y() as f32));
path.line_to(vec2f(seg.to.x() as f32, -seg.to.y() as f32));
self.canvas.set_line_width(seg.width as f32);
self.canvas.stroke_path(path);
}
PrimitiveShape::Bend(bend) => {
/*let delta1 = bend.from - bend.c.pos;
let delta2 = bend.to - bend.c.pos;
let angle1 = delta1.y().atan2(delta1.x());
let angle2 = delta2.y().atan2(delta2.x());
let mut path = Path2D::new();
path.arc(
vec2f(bend.c.pos.x() as f32, -bend.c.pos.y() as f32),
bend.circle().r as f32,
angle1 as f32,
angle2 as f32,
ArcDirection::CW,
);
self.canvas.set_line_width(bend.width as f32);
self.canvas.stroke_path(path);*/
}
}
let envelope = AccessPrimitiveShape::envelope(shape, 0.0);
// XXX: points represented as arrays can't be conveniently converted to vector types
let topleft = vec2f(envelope.lower()[0] as f32, -envelope.upper()[1] as f32);
let bottomright = vec2f(envelope.upper()[0] as f32, -envelope.lower()[1] as f32);
self.canvas.set_line_width(2.0 / zoom);
self.canvas
.set_stroke_style(ColorU::new(100, 100, 100, 255));
self.canvas
.stroke_rect(RectF::new(topleft, bottomright - topleft));
}
pub fn paint_polygon(&mut self, polygon: &Polygon, color: ColorU, zoom: f32) {
let mut path = Path2D::new();
let mut it = polygon.exterior_coords_iter();
if let Some(initial_vertex) = it.next() {
path.move_to(vec2f(initial_vertex.x as f32, -initial_vertex.y as f32));
}
for vertex in it {
path.line_to(vec2f(vertex.x as f32, -vertex.y as f32));
}
path.close_path();
self.canvas.set_stroke_style(color);
self.canvas.set_fill_style(color);
self.canvas.set_line_width(0.0);
self.canvas.fill_path(path, FillRule::Winding);
}
pub fn paint_edge(&mut self, from: Point, to: Point, color: ColorU, zoom: f32) {
let mut path = Path2D::new();
path.move_to(vec2f(from.x() as f32, from.y() as f32));
path.line_to(vec2f(to.x() as f32, to.y() as f32));
self.canvas.set_stroke_style(color);
self.canvas.set_line_width(2.0 / zoom);
self.canvas.stroke_path(path);
}
}

View File

@ -7,22 +7,19 @@ use crate::{
board::{mesadata::AccessMesadata, Board},
drawing::{
dot::FixedDotWeight,
graph::{GetLayer, GetMaybeNet, MakePrimitive},
primitive::MakePrimitiveShape,
seg::FixedSegWeight,
Drawing,
graph::{GetMaybeNet, GetLayer, MakePrimitive},
primitive::MakePrimitiveShape
},
geometry::{
primitive::{PrimitiveShape},
GetWidth,
},
geometry::{primitive::PrimitiveShape, GetWidth},
layout::{poly::SolidPolyWeight, Layout},
math::Circle,
specctra::{
mesadata::SpecctraMesadata,
read::{self, ListTokenizer},
write::{self, ListWriter},
structure::{self, DsnFile, Layer, Pcb, Shape},
write::{self, ListWriter},
},
};
@ -58,7 +55,6 @@ impl SpecctraDesign {
) -> Result<(), std::io::Error> {
let mesadata = board.mesadata();
let drawing = board.layout().drawing();
//dbg!(&geometry);
let mut net_outs = HashMap::<usize, structure::NetOut>::new();
for index in drawing.primitive_nodes() {
@ -68,10 +64,16 @@ impl SpecctraDesign {
let coords = match primitive.shape() {
PrimitiveShape::Seg(seg) => {
vec![
structure::Point { x: seg.from.x(), y: seg.from.y() },
structure::Point { x: seg.to.x(), y: seg.to.y() },
structure::Point {
x: seg.from.x(),
y: seg.from.y(),
},
structure::Point {
x: seg.to.x(),
y: seg.to.y(),
},
]
},
}
PrimitiveShape::Bend(bend) => {
// Since general circle arcs don't seem to be supported
@ -86,12 +88,14 @@ impl SpecctraDesign {
let mut points = Vec::new();
for i in 0..=segment_count {
let x = circle.pos.x() + circle.r * (angle_from + i as f64 * angle_step).cos();
let y = circle.pos.y() + circle.r * (angle_from + i as f64 * angle_step).sin();
let x = circle.pos.x()
+ circle.r * (angle_from + i as f64 * angle_step).cos();
let y = circle.pos.y()
+ circle.r * (angle_from + i as f64 * angle_step).sin();
points.push(structure::Point { x, y });
}
points
},
}
// Intentionally skipped for now.
// Topola stores trace segments and dots joining them
@ -103,7 +107,10 @@ impl SpecctraDesign {
let wire = structure::WireOut {
path: structure::Path {
layer: mesadata.layer_layername(primitive.layer()).unwrap().to_owned(),
layer: mesadata
.layer_layername(primitive.layer())
.unwrap()
.to_owned(),
width: primitive.width(),
coords,
},