From 422889e5435b48bc60aedfb3e14c46de27150b3f Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Sun, 5 Jul 2020 11:30:34 -0700 Subject: [PATCH] lazily render lanes, same as road labels. greatly speeds up start-up times (helps with #125), doesnt melt the GPU on large maps. also paves the way for dynamically adjusting z-order rendering (for #126) because we no longer try to upload massive gobs of data to the GPU at once, huge_seattle on my system goes from initially loading in about a minute down to 11s --- game/src/app.rs | 3 +- game/src/edit/mod.rs | 12 +- game/src/main.rs | 1 - game/src/render/lane.rs | 290 ++++++++++++++++++++-------------------- game/src/render/map.rs | 29 +--- game/src/render/mod.rs | 1 - 6 files changed, 150 insertions(+), 186 deletions(-) diff --git a/game/src/app.rs b/game/src/app.rs index c90673c79b..91aedbeab4 100644 --- a/game/src/app.rs +++ b/game/src/app.rs @@ -497,7 +497,6 @@ impl ShowObject for ShowEverything { #[derive(Clone)] pub struct Flags { pub sim_flags: SimFlags, - pub draw_lane_markings: bool, // Number of agents to generate when requested. If unspecified, trips to/from borders will be // included. pub num_agents: Option, @@ -526,7 +525,7 @@ impl PerMap { mem.reset("Map and Sim", timer); timer.start("draw_map"); - let draw_map = DrawMap::new(&map, &flags, cs, ctx, timer); + let draw_map = DrawMap::new(&map, cs, ctx, timer); timer.stop("draw_map"); mem.reset("DrawMap", timer); diff --git a/game/src/edit/mod.rs b/game/src/edit/mod.rs index aff33cc226..4a2dbbe8f9 100644 --- a/game/src/edit/mod.rs +++ b/game/src/edit/mod.rs @@ -15,7 +15,7 @@ use crate::debug::DebugMode; use crate::game::{msg, State, Transition, WizardState}; use crate::helpers::ID; use crate::managed::{WrappedComposite, WrappedOutcome}; -use crate::render::{DrawIntersection, DrawLane, DrawRoad}; +use crate::render::{DrawIntersection, DrawRoad}; use crate::sandbox::{GameplayMode, SandboxMode, TimeWarpScreen}; use abstutil::Timer; use ezgui::{ @@ -508,15 +508,7 @@ pub fn apply_map_edits(ctx: &mut EventCtx, app: &mut App, edits: MapEdits) { // An edit to one lane potentially affects markings in all lanes in the same road, because // of one-way markings, driving lines, etc. for l in road.all_lanes() { - let lane = app.primary.map.get_l(l); - app.primary.draw_map.lanes[l.0] = DrawLane::new( - lane, - &app.primary.map, - app.primary.current_flags.draw_lane_markings, - &app.cs, - &mut timer, - ) - .finish(ctx.prerender, &app.cs, lane); + app.primary.draw_map.lanes[l.0].clear_rendering(); } } diff --git a/game/src/main.rs b/game/src/main.rs index 4a33c6f04a..83e68e3e84 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -30,7 +30,6 @@ fn main() { let mut flags = Flags { sim_flags: SimFlags::from_args(&mut args), - draw_lane_markings: !args.enabled("--dont_draw_lane_markings"), num_agents: args.optional_parse("--num_agents", |s| s.parse()), }; let mut opts = options::Options::default(); diff --git a/game/src/render/lane.rs b/game/src/render/lane.rs index 2bcc3878db..ca3c423fa7 100644 --- a/game/src/render/lane.rs +++ b/game/src/render/lane.rs @@ -1,24 +1,131 @@ use crate::app::App; -use crate::colors::ColorScheme; use crate::helpers::ID; use crate::render::{DrawOptions, Renderable, OUTLINE_THICKNESS}; use abstutil::Timer; -use ezgui::{Drawable, GeomBatch, GfxCtx, Prerender, RewriteColor}; +use ezgui::{Drawable, GeomBatch, GfxCtx, RewriteColor}; use geom::{Angle, ArrowCap, Distance, Line, PolyLine, Polygon, Pt2D}; use map_model::{Lane, LaneID, LaneType, Map, Road, TurnType, PARKING_SPOT_LENGTH}; +use std::cell::RefCell; -// Split into two phases like this, because AlmostDrawLane can be created in parallel, but GPU -// upload has to be serial. -pub struct AlmostDrawLane { +pub struct DrawLane { pub id: LaneID, - polygon: Polygon, + pub polygon: Polygon, zorder: isize, - draw_default: GeomBatch, + + draw_default: RefCell>, } -impl AlmostDrawLane { - pub fn finish(mut self, prerender: &Prerender, _: &ColorScheme, lane: &Lane) -> DrawLane { - // Need prerender to load the (cached) SVGs +impl DrawLane { + pub fn new(lane: &Lane, map: &Map) -> DrawLane { + DrawLane { + id: lane.id, + polygon: lane.lane_center_pts.make_polygons(lane.width), + zorder: map.get_r(lane.parent).zorder, + draw_default: RefCell::new(None), + } + } + + pub fn clear_rendering(&mut self) { + *self.draw_default.borrow_mut() = None; + } + + fn render(&self, g: &mut GfxCtx, app: &App) -> Drawable { + let map = &app.primary.map; + let lane = map.get_l(self.id); + let road = map.get_r(lane.parent); + + let mut draw = GeomBatch::new(); + let mut timer = Timer::throwaway(); + if lane.lane_type != LaneType::LightRail { + draw.push( + match lane.lane_type { + LaneType::Driving => app.cs.driving_lane, + LaneType::Bus => app.cs.bus_lane, + LaneType::Parking => app.cs.parking_lane, + LaneType::Sidewalk => app.cs.sidewalk, + LaneType::Biking => app.cs.bike_lane, + LaneType::SharedLeftTurn => app.cs.driving_lane, + LaneType::Construction => app.cs.parking_lane, + LaneType::LightRail => unreachable!(), + }, + self.polygon.clone(), + ); + } + match lane.lane_type { + LaneType::Sidewalk => { + draw.extend(app.cs.sidewalk_lines, calculate_sidewalk_lines(lane)); + } + LaneType::Parking => { + draw.extend( + app.cs.general_road_marking, + calculate_parking_lines(map, lane), + ); + } + LaneType::Driving | LaneType::Bus => { + draw.extend( + app.cs.general_road_marking, + calculate_driving_lines(map, lane, road, &mut timer), + ); + draw.extend( + app.cs.general_road_marking, + calculate_turn_markings(map, lane, &mut timer), + ); + draw.extend( + app.cs.general_road_marking, + calculate_one_way_markings(lane, road), + ); + } + LaneType::Biking => {} + LaneType::SharedLeftTurn => { + draw.push( + app.cs.road_center_line, + lane.lane_center_pts + .shift_right(lane.width / 2.0) + .get(&mut timer) + .make_polygons(Distance::meters(0.25)), + ); + draw.push( + app.cs.road_center_line, + lane.lane_center_pts + .shift_left(lane.width / 2.0) + .get(&mut timer) + .make_polygons(Distance::meters(0.25)), + ); + } + LaneType::Construction => {} + LaneType::LightRail => { + let track_width = lane.width / 4.0; + draw.push( + app.cs.light_rail_track, + lane.lane_center_pts + .shift_right((lane.width - track_width) / 2.5) + .get(&mut timer) + .make_polygons(track_width), + ); + draw.push( + app.cs.light_rail_track, + lane.lane_center_pts + .shift_left((lane.width - track_width) / 2.5) + .get(&mut timer) + .make_polygons(track_width), + ); + + // Start away from the intersections + let tile_every = Distance::meters(3.0); + let mut dist_along = tile_every; + while dist_along < lane.lane_center_pts.length() - tile_every { + let (pt, angle) = lane.dist_along(dist_along); + // Reuse perp_line. Project away an arbitrary amount + let pt2 = pt.project_away(Distance::meters(1.0), angle); + draw.push( + app.cs.light_rail_track, + perp_line(Line::new(pt, pt2), lane.width).make_polygons(track_width), + ); + dist_along += tile_every; + } + } + } + if lane.is_bus() || lane.is_biking() || lane.lane_type == LaneType::Construction { let buffer = Distance::meters(2.0); let btwn = Distance::meters(30.0); @@ -28,9 +135,9 @@ impl AlmostDrawLane { while dist + buffer <= len { let (pt, angle) = lane.lane_center_pts.dist_along(dist); if lane.is_bus() { - self.draw_default.append( + draw.append( GeomBatch::mapspace_svg( - prerender, + g.prerender, "../data/system/assets/map/bus_only.svg", ) .scale(0.06) @@ -38,17 +145,20 @@ impl AlmostDrawLane { .rotate(angle.shortest_rotation_towards(Angle::new_degs(-90.0))), ); } else if lane.is_biking() { - self.draw_default.append( - GeomBatch::mapspace_svg(prerender, "../data/system/assets/meters/bike.svg") - .scale(0.06) - .centered_on(pt) - .rotate(angle.shortest_rotation_towards(Angle::new_degs(-90.0))), + draw.append( + GeomBatch::mapspace_svg( + g.prerender, + "../data/system/assets/meters/bike.svg", + ) + .scale(0.06) + .centered_on(pt) + .rotate(angle.shortest_rotation_towards(Angle::new_degs(-90.0))), ); } else if lane.lane_type == LaneType::Construction { // TODO Still not quite centered right, but close enough - self.draw_default.append( + draw.append( GeomBatch::mapspace_svg( - prerender, + g.prerender, "../data/system/assets/map/under_construction.svg", ) .scale(0.05) @@ -63,137 +173,15 @@ impl AlmostDrawLane { } } - if self.zorder < 0 { - self.draw_default = self.draw_default.color(RewriteColor::ChangeAlpha(0.5)); - } - - DrawLane { - id: self.id, - polygon: self.polygon, - zorder: self.zorder, - draw_default: prerender.upload(self.draw_default), - } - } -} - -pub struct DrawLane { - pub id: LaneID, - pub polygon: Polygon, - zorder: isize, - - draw_default: Drawable, -} - -impl DrawLane { - pub fn new( - lane: &Lane, - map: &Map, - draw_lane_markings: bool, - cs: &ColorScheme, - timer: &mut Timer, - ) -> AlmostDrawLane { - let road = map.get_r(lane.parent); - let polygon = lane.lane_center_pts.make_polygons(lane.width); - - let mut draw = GeomBatch::new(); - if lane.lane_type != LaneType::LightRail { - draw.push( - match lane.lane_type { - LaneType::Driving => cs.driving_lane, - LaneType::Bus => cs.bus_lane, - LaneType::Parking => cs.parking_lane, - LaneType::Sidewalk => cs.sidewalk, - LaneType::Biking => cs.bike_lane, - LaneType::SharedLeftTurn => cs.driving_lane, - LaneType::Construction => cs.parking_lane, - LaneType::LightRail => unreachable!(), - }, - polygon.clone(), - ); - } - if draw_lane_markings { - match lane.lane_type { - LaneType::Sidewalk => { - draw.extend(cs.sidewalk_lines, calculate_sidewalk_lines(lane)); - } - LaneType::Parking => { - draw.extend(cs.general_road_marking, calculate_parking_lines(map, lane)); - } - LaneType::Driving | LaneType::Bus => { - draw.extend( - cs.general_road_marking, - calculate_driving_lines(map, lane, road, timer), - ); - draw.extend( - cs.general_road_marking, - calculate_turn_markings(map, lane, timer), - ); - draw.extend( - cs.general_road_marking, - calculate_one_way_markings(lane, road), - ); - } - LaneType::Biking => {} - LaneType::SharedLeftTurn => { - draw.push( - cs.road_center_line, - lane.lane_center_pts - .shift_right(lane.width / 2.0) - .get(timer) - .make_polygons(Distance::meters(0.25)), - ); - draw.push( - cs.road_center_line, - lane.lane_center_pts - .shift_left(lane.width / 2.0) - .get(timer) - .make_polygons(Distance::meters(0.25)), - ); - } - LaneType::Construction => {} - LaneType::LightRail => { - let track_width = lane.width / 4.0; - draw.push( - cs.light_rail_track, - lane.lane_center_pts - .shift_right((lane.width - track_width) / 2.5) - .get(timer) - .make_polygons(track_width), - ); - draw.push( - cs.light_rail_track, - lane.lane_center_pts - .shift_left((lane.width - track_width) / 2.5) - .get(timer) - .make_polygons(track_width), - ); - - // Start away from the intersections - let tile_every = Distance::meters(3.0); - let mut dist_along = tile_every; - while dist_along < lane.lane_center_pts.length() - tile_every { - let (pt, angle) = lane.dist_along(dist_along); - // Reuse perp_line. Project away an arbitrary amount - let pt2 = pt.project_away(Distance::meters(1.0), angle); - draw.push( - cs.light_rail_track, - perp_line(Line::new(pt, pt2), lane.width).make_polygons(track_width), - ); - dist_along += tile_every; - } - } - }; - } if road.zone.is_some() { - draw.push(cs.private_road.alpha(0.5), polygon.clone()); + draw.push(app.cs.private_road.alpha(0.5), self.polygon.clone()); } - AlmostDrawLane { - id: lane.id, - polygon, - zorder: road.zorder, - draw_default: draw, + if self.zorder < 0 { + draw = draw.color(RewriteColor::ChangeAlpha(0.5)); } + + g.upload(draw) } } @@ -202,8 +190,14 @@ impl Renderable for DrawLane { ID::Lane(self.id) } - fn draw(&self, g: &mut GfxCtx, _: &App, _: &DrawOptions) { - g.redraw(&self.draw_default); + fn draw(&self, g: &mut GfxCtx, app: &App, _: &DrawOptions) { + // Lazily calculate, because these are expensive to all do up-front, and most players won't + // exhaustively see every lane during a single session + let mut draw = self.draw_default.borrow_mut(); + if draw.is_none() { + *draw = Some(self.render(g, app)); + } + g.redraw(draw.as_ref().unwrap()); } fn get_outline(&self, map: &Map) -> Polygon { diff --git a/game/src/render/map.rs b/game/src/render/map.rs index 27d4912efc..89ef770cab 100644 --- a/game/src/render/map.rs +++ b/game/src/render/map.rs @@ -1,4 +1,4 @@ -use crate::app::{App, Flags}; +use crate::app::App; use crate::colors::ColorScheme; use crate::helpers::ID; use crate::render::building::DrawBuilding; @@ -46,13 +46,7 @@ pub struct DrawMap { } impl DrawMap { - pub fn new( - map: &Map, - flags: &Flags, - cs: &ColorScheme, - ctx: &EventCtx, - timer: &mut Timer, - ) -> DrawMap { + pub fn new(map: &Map, cs: &ColorScheme, ctx: &EventCtx, timer: &mut Timer) -> DrawMap { let mut roads: Vec = Vec::new(); timer.start_iter("make DrawRoads", map.all_roads().len()); for r in map.all_roads() { @@ -85,24 +79,11 @@ impl DrawMap { let draw_all_thick_roads = all_roads.upload(ctx); timer.stop("generate thick roads"); - let almost_lanes = - timer.parallelize("prepare DrawLanes", map.all_lanes().iter().collect(), |l| { - DrawLane::new( - l, - map, - flags.draw_lane_markings, - cs, - // TODO Really parallelize should give us something thread-safe that can at - // least take notes. - &mut Timer::throwaway(), - ) - }); - timer.start_iter("finalize DrawLanes", almost_lanes.len()); let mut lanes: Vec = Vec::new(); - for almost in almost_lanes { + timer.start_iter("make DrawLanes", map.all_lanes().len()); + for l in map.all_lanes() { timer.next(); - let lane = map.get_l(almost.id); - lanes.push(almost.finish(ctx.prerender, cs, lane)); + lanes.push(DrawLane::new(l, map)); } let mut intersections: Vec = Vec::new(); diff --git a/game/src/render/mod.rs b/game/src/render/mod.rs index efd464ed62..aa10cf1104 100644 --- a/game/src/render/mod.rs +++ b/game/src/render/mod.rs @@ -19,7 +19,6 @@ pub use crate::render::area::DrawArea; use crate::render::bike::DrawBike; use crate::render::car::DrawCar; pub use crate::render::intersection::{calculate_corners, DrawIntersection}; -pub use crate::render::lane::DrawLane; pub use crate::render::map::{AgentCache, AgentColorScheme, DrawMap}; pub use crate::render::pedestrian::{DrawPedCrowd, DrawPedestrian}; pub use crate::render::road::DrawRoad;