Revert "Merge pull request #2609 from brson/parallel-render"

This reverts commit 850bd2891d, reversing
changes made to 5b0feac32a.
This commit is contained in:
Lars Bergstrom 2014-06-20 17:43:14 -05:00
parent 850bd2891d
commit 3f8f065b76
5 changed files with 176 additions and 287 deletions

View file

@ -59,7 +59,6 @@ pub extern "C" fn cef_run_message_loop() {
headless: false, headless: false,
hard_fail: false, hard_fail: false,
bubble_widths_separately: false, bubble_widths_separately: false,
native_threading: false
}; };
native::start(0, 0 as **u8, proc() { native::start(0, 0 as **u8, proc() {
servo::run(opts); servo::run(opts);

View file

@ -105,6 +105,7 @@ pub struct RenderTask<C> {
port: Receiver<Msg>, port: Receiver<Msg>,
compositor: C, compositor: C,
constellation_chan: ConstellationChan, constellation_chan: ConstellationChan,
font_ctx: Box<FontContext>,
opts: Opts, opts: Opts,
/// A channel to the profiler. /// A channel to the profiler.
@ -113,6 +114,9 @@ pub struct RenderTask<C> {
/// The graphics context to use. /// The graphics context to use.
graphics_context: GraphicsContext, graphics_context: GraphicsContext,
/// The native graphics context.
native_graphics_context: Option<NativePaintingGraphicsContext>,
/// The layers to be rendered. /// The layers to be rendered.
render_layers: SmallVec1<RenderLayer>, render_layers: SmallVec1<RenderLayer>,
@ -122,11 +126,8 @@ pub struct RenderTask<C> {
/// A counter for epoch messages /// A counter for epoch messages
epoch: Epoch, epoch: Epoch,
/// Renderer workers /// A data structure to store unused LayerBuffers
worker_txs: Vec<Sender<WorkerMsg>>, buffer_map: BufferMap<Box<LayerBuffer>>,
/// The receiver on which we receive rendered buffers from the workers
worker_result_rx: Receiver<Box<LayerBuffer>>
} }
// If we implement this as a function, we get borrowck errors from borrowing // If we implement this as a function, we get borrowck errors from borrowing
@ -137,13 +138,6 @@ macro_rules! native_graphics_context(
) )
) )
enum WorkerMsg {
// This is tupled so all the data can be pulled out of the message as one variable
WorkerRender((BufferRequest, Arc<DisplayList>, uint, uint, f32)),
WorkerUnusedBuffer(Box<LayerBuffer>),
WorkerExit(Sender<()>)
}
fn initialize_layers<C:RenderListener>( fn initialize_layers<C:RenderListener>(
compositor: &mut C, compositor: &mut C,
pipeline_id: PipelineId, pipeline_id: PipelineId,
@ -174,17 +168,22 @@ impl<C:RenderListener + Send> RenderTask<C> {
send_on_failure(&mut builder, FailureMsg(failure_msg), c); send_on_failure(&mut builder, FailureMsg(failure_msg), c);
builder.spawn(proc() { builder.spawn(proc() {
{ { // Ensures RenderTask and graphics context are destroyed before shutdown msg
let native_graphics_context = compositor.get_graphics_metadata().map(
|md| NativePaintingGraphicsContext::from_metadata(&md));
let cpu_painting = opts.cpu_painting; let cpu_painting = opts.cpu_painting;
let (worker_result_tx, worker_result_rx) = channel();
// FIXME: rust/#5967 // FIXME: rust/#5967
let mut render_task = RenderTask { let mut render_task = RenderTask {
id: id, id: id,
port: port, port: port,
compositor: compositor, compositor: compositor,
constellation_chan: constellation_chan, constellation_chan: constellation_chan,
font_ctx: box FontContext::new(FontContextInfo {
backend: opts.render_backend.clone(),
needs_font_list: false,
profiler_chan: profiler_chan.clone(),
}),
opts: opts, opts: opts,
profiler_chan: profiler_chan, profiler_chan: profiler_chan,
@ -194,21 +193,22 @@ impl<C:RenderListener + Send> RenderTask<C> {
GpuGraphicsContext GpuGraphicsContext
}, },
native_graphics_context: native_graphics_context,
render_layers: SmallVec1::new(), render_layers: SmallVec1::new(),
paint_permission: false, paint_permission: false,
epoch: Epoch(0), epoch: Epoch(0),
worker_txs: vec![], buffer_map: BufferMap::new(10000000),
worker_result_rx: worker_result_rx
}; };
// Now spawn the workers. We're only doing this after creating
// the RenderTask object because spawn_workers was originally
// written to be run afterward, and was refactored like so.
let worker_txs = render_task.spawn_workers(worker_result_tx);
render_task.worker_txs = worker_txs;
render_task.start(); render_task.start();
// Destroy all the buffers.
match render_task.native_graphics_context.as_ref() {
Some(ctx) => render_task.buffer_map.clear(ctx),
None => (),
}
} }
debug!("render_task: shutdown_chan send"); debug!("render_task: shutdown_chan send");
@ -246,7 +246,7 @@ impl<C:RenderListener + Send> RenderTask<C> {
} }
UnusedBufferMsg(unused_buffers) => { UnusedBufferMsg(unused_buffers) => {
for buffer in unused_buffers.move_iter().rev() { for buffer in unused_buffers.move_iter().rev() {
self.worker_txs.get(buffer.render_idx).send(WorkerUnusedBuffer(buffer)); self.buffer_map.insert(native_graphics_context!(self), buffer);
} }
} }
PaintPermissionGranted => { PaintPermissionGranted => {
@ -267,11 +267,6 @@ impl<C:RenderListener + Send> RenderTask<C> {
self.paint_permission = false; self.paint_permission = false;
} }
ExitMsg(response_ch) => { ExitMsg(response_ch) => {
for worker_tx in self.worker_txs.iter() {
let (tx, rx) = channel();
worker_tx.send(WorkerExit(tx));
rx.recv();
}
debug!("render_task: exitmsg response send"); debug!("render_task: exitmsg response send");
response_ch.map(|ch| ch.send(())); response_ch.map(|ch| ch.send(()));
break; break;
@ -280,55 +275,28 @@ impl<C:RenderListener + Send> RenderTask<C> {
} }
} }
fn spawn_workers(&mut self, result_tx: Sender<Box<LayerBuffer>>) -> Vec<Sender<WorkerMsg>> { /// Renders one layer and sends the tiles back to the layer.
let mut worker_chans = vec![]; ///
for render_idx in range(0, self.opts.n_render_threads) { /// FIXME(pcwalton): We will probably want to eventually send all layers belonging to a page in
let (tx, rx) = channel(); /// one transaction, to avoid the user seeing inconsistent states.
let result_tx = result_tx.clone(); fn render(&mut self, tiles: Vec<BufferRequest>, scale: f32, layer_id: LayerId) {
time::profile(time::RenderingCategory, self.profiler_chan.clone(), || {
// FIXME: Try not to create a new array here.
let mut new_buffers = vec!();
let opts = self.opts.clone(); // Find the appropriate render layer.
let graphics_context = self.graphics_context; let render_layer = match self.render_layers.iter().find(|layer| layer.id == layer_id) {
let render_backend = self.opts.render_backend; Some(render_layer) => render_layer,
let native_graphics_context = self.compositor.get_graphics_metadata().map( None => return,
|md| NativePaintingGraphicsContext::from_metadata(&md));
let native_graphics_context = native_graphics_context.expect("need native graphics context");
let native_graphics_context = Some(native_graphics_context);
let font_ctx_info = FontContextInfo {
backend: self.opts.render_backend,
needs_font_list: false,
profiler_chan: self.profiler_chan.clone(),
}; };
let profiler_chan = self.profiler_chan.clone();
let buffer_map: BufferMap<Box<LayerBuffer>> = BufferMap::new(10000000);
spawn(proc() { self.compositor.set_render_state(RenderingRenderState);
let mut buffer_map = buffer_map;
let mut native_graphics_context = native_graphics_context;
loop {
let render_msg: WorkerMsg = rx.recv();
let render_data = match render_msg {
WorkerRender(render_data) => render_data,
WorkerUnusedBuffer(buffer) => {
buffer_map.insert(native_graphics_context.get_ref(), buffer);
continue
}
WorkerExit(tx) => {
// Cleanup and tell the RenderTask we're done
buffer_map.clear(native_graphics_context.get_ref());
drop(native_graphics_context.take_unwrap());
tx.send(());
break
}
};
let (tile,
display_list,
layer_position_x,
layer_position_y,
scale) = render_data;
// Divide up the layer into tiles.
for tile in tiles.iter() {
// Optimize the display list for this tile. // Optimize the display list for this tile.
let page_rect_au = geometry::f32_rect_to_au_rect(tile.page_rect); let page_rect_au = geometry::f32_rect_to_au_rect(tile.page_rect);
let optimizer = DisplayListOptimizer::new(display_list, let optimizer = DisplayListOptimizer::new(render_layer.display_list.clone(),
page_rect_au); page_rect_au);
let display_list = optimizer.optimize(); let display_list = optimizer.optimize();
@ -336,16 +304,16 @@ impl<C:RenderListener + Send> RenderTask<C> {
let height = tile.screen_rect.size.height; let height = tile.screen_rect.size.height;
let size = Size2D(width as i32, height as i32); let size = Size2D(width as i32, height as i32);
let draw_target = match graphics_context { let draw_target = match self.graphics_context {
CpuGraphicsContext => { CpuGraphicsContext => {
DrawTarget::new(render_backend, size, B8G8R8A8) DrawTarget::new(self.opts.render_backend, size, B8G8R8A8)
} }
GpuGraphicsContext => { GpuGraphicsContext => {
// FIXME(pcwalton): Cache the components of draw targets // FIXME(pcwalton): Cache the components of draw targets
// (texture color buffer, renderbuffers) instead of recreating them. // (texture color buffer, renderbuffers) instead of recreating them.
let draw_target = let draw_target =
DrawTarget::new_with_fbo(render_backend, DrawTarget::new_with_fbo(self.opts.render_backend,
native_graphics_context.get_ref(), native_graphics_context!(self),
size, size,
B8G8R8A8); B8G8R8A8);
draw_target.make_current(); draw_target.make_current();
@ -354,12 +322,11 @@ impl<C:RenderListener + Send> RenderTask<C> {
}; };
{ {
let mut font_ctx = box FontContext::new(font_ctx_info.clone());
// Build the render context. // Build the render context.
let mut ctx = RenderContext { let mut ctx = RenderContext {
draw_target: &draw_target, draw_target: &draw_target,
font_ctx: &mut font_ctx, font_ctx: &mut self.font_ctx,
opts: &opts, opts: &self.opts,
page_rect: tile.page_rect, page_rect: tile.page_rect,
screen_rect: tile.screen_rect, screen_rect: tile.screen_rect,
}; };
@ -369,8 +336,8 @@ impl<C:RenderListener + Send> RenderTask<C> {
let matrix = matrix.scale(scale as AzFloat, scale as AzFloat); let matrix = matrix.scale(scale as AzFloat, scale as AzFloat);
let matrix = matrix.translate(-(tile.page_rect.origin.x) as AzFloat, let matrix = matrix.translate(-(tile.page_rect.origin.x) as AzFloat,
-(tile.page_rect.origin.y) as AzFloat); -(tile.page_rect.origin.y) as AzFloat);
let matrix = matrix.translate(-(layer_position_x as AzFloat), let matrix = matrix.translate(-(render_layer.position.origin.x as AzFloat),
-(layer_position_y as AzFloat)); -(render_layer.position.origin.y as AzFloat));
ctx.draw_target.set_transform(&matrix); ctx.draw_target.set_transform(&matrix);
@ -378,7 +345,7 @@ impl<C:RenderListener + Send> RenderTask<C> {
ctx.clear(); ctx.clear();
// Draw the display list. // Draw the display list.
profile(time::RenderingDrawingCategory, profiler_chan.clone(), || { profile(time::RenderingDrawingCategory, self.profiler_chan.clone(), || {
display_list.draw_into_context(&mut ctx); display_list.draw_into_context(&mut ctx);
ctx.draw_target.flush(); ctx.draw_target.flush();
}); });
@ -389,10 +356,9 @@ impl<C:RenderListener + Send> RenderTask<C> {
// //
// FIXME(pcwalton): We should supply the texture and native surface *to* the // FIXME(pcwalton): We should supply the texture and native surface *to* the
// draw target in GPU rendering mode, so that it doesn't have to recreate it. // draw target in GPU rendering mode, so that it doesn't have to recreate it.
let buffer = match graphics_context { let buffer = match self.graphics_context {
CpuGraphicsContext => { CpuGraphicsContext => {
let maybe_buffer = buffer_map.find(tile.screen_rect.size); let buffer = match self.buffer_map.find(tile.screen_rect.size) {
let buffer = match maybe_buffer {
Some(buffer) => { Some(buffer) => {
let mut buffer = buffer; let mut buffer = buffer;
buffer.rect = tile.page_rect; buffer.rect = tile.page_rect;
@ -406,7 +372,7 @@ impl<C:RenderListener + Send> RenderTask<C> {
// in case it dies in transit to the compositor task. // in case it dies in transit to the compositor task.
let mut native_surface: NativeSurface = let mut native_surface: NativeSurface =
layers::platform::surface::NativeSurfaceMethods::new( layers::platform::surface::NativeSurfaceMethods::new(
native_graphics_context.get_ref(), native_graphics_context!(self),
Size2D(width as i32, height as i32), Size2D(width as i32, height as i32),
width as i32 * 4); width as i32 * 4);
native_surface.mark_wont_leak(); native_surface.mark_wont_leak();
@ -416,14 +382,13 @@ impl<C:RenderListener + Send> RenderTask<C> {
rect: tile.page_rect, rect: tile.page_rect,
screen_pos: tile.screen_rect, screen_pos: tile.screen_rect,
resolution: scale, resolution: scale,
stride: (width * 4) as uint, stride: (width * 4) as uint
render_idx: render_idx
} }
} }
}; };
draw_target.snapshot().get_data_surface().with_data(|data| { draw_target.snapshot().get_data_surface().with_data(|data| {
buffer.native_surface.upload(native_graphics_context.get_ref(), data); buffer.native_surface.upload(native_graphics_context!(self), data);
debug!("RENDERER uploading to native surface {:d}", debug!("RENDERER uploading to native surface {:d}",
buffer.native_surface.get_id() as int); buffer.native_surface.get_id() as int);
}); });
@ -447,58 +412,12 @@ impl<C:RenderListener + Send> RenderTask<C> {
rect: tile.page_rect, rect: tile.page_rect,
screen_pos: tile.screen_rect, screen_pos: tile.screen_rect,
resolution: scale, resolution: scale,
stride: (width * 4) as uint, stride: (width * 4) as uint
render_idx: render_idx
} }
} }
}; };
result_tx.send(buffer); new_buffers.push(buffer);
}
});
worker_chans.push(tx)
}
return worker_chans;
}
/// Renders one layer and sends the tiles back to the layer.
///
/// FIXME(pcwalton): We will probably want to eventually send all layers belonging to a page in
/// one transaction, to avoid the user seeing inconsistent states.
fn render(&mut self, tiles: Vec<BufferRequest>, scale: f32, layer_id: LayerId) {
let mut tiles = Some(tiles);
time::profile(time::RenderingCategory, self.profiler_chan.clone(), || {
let tiles = tiles.take_unwrap();
// FIXME: Try not to create a new array here.
let mut new_buffers = vec!();
// Find the appropriate render layer.
let render_layer = match self.render_layers.iter().find(|layer| layer.id == layer_id) {
Some(render_layer) => render_layer,
None => return,
};
self.compositor.set_render_state(RenderingRenderState);
// Distribute the tiles to the workers
let num_tiles = tiles.len();
let mut worker_idx = 0;
for tile in tiles.move_iter() {
let display_list = render_layer.display_list.clone();
let layer_position_x = render_layer.position.origin.x;
let layer_position_y = render_layer.position.origin.y;
self.worker_txs.get(worker_idx).send(WorkerRender((tile, display_list, layer_position_x, layer_position_y, scale)));
// Round-robin the work
worker_idx = (worker_idx + 1) % self.worker_txs.len();
}
for _ in range(0, num_tiles) {
new_buffers.push(self.worker_result_rx.recv());
} }
let layer_buffer_set = box LayerBufferSet { let layer_buffer_set = box LayerBufferSet {
@ -517,3 +436,4 @@ impl<C:RenderListener + Send> RenderTask<C> {
}) })
} }
} }

View file

@ -51,15 +51,23 @@ extern crate core_graphics;
#[cfg(target_os="macos")] #[cfg(target_os="macos")]
extern crate core_text; extern crate core_text;
#[cfg(not(test))]
use compositing::{CompositorChan, CompositorTask}; use compositing::{CompositorChan, CompositorTask};
#[cfg(not(test))]
use constellation::Constellation; use constellation::Constellation;
#[cfg(not(test))]
use servo_msg::constellation_msg::{ConstellationChan, InitLoadUrlMsg}; use servo_msg::constellation_msg::{ConstellationChan, InitLoadUrlMsg};
#[cfg(not(test))]
use servo_net::image_cache_task::{ImageCacheTask, SyncImageCacheTask}; use servo_net::image_cache_task::{ImageCacheTask, SyncImageCacheTask};
#[cfg(not(test))]
use servo_net::resource_task::ResourceTask; use servo_net::resource_task::ResourceTask;
#[cfg(not(test))]
use servo_util::time::Profiler; use servo_util::time::Profiler;
#[cfg(not(test))]
use servo_util::opts; use servo_util::opts;
#[cfg(not(test))]
use servo_util::url::parse_url; use servo_util::url::parse_url;
@ -67,7 +75,9 @@ use servo_util::url::parse_url;
use std::os; use std::os;
#[cfg(not(test), target_os="android")] #[cfg(not(test), target_os="android")]
use std::str; use std::str;
#[cfg(not(test))]
use std::task::TaskOpts; use std::task::TaskOpts;
#[cfg(not(test))]
use url::Url; use url::Url;
@ -151,42 +161,12 @@ pub extern "C" fn android_start(argc: int, argv: **u8) -> int {
}) })
} }
fn spawn_main(opts: opts::Opts, #[cfg(not(test))]
compositor_port: Receiver<compositing::Msg>, pub fn run(opts: opts::Opts) {
profiler_chan: servo_util::time::ProfilerChan,
result_port: Receiver<ConstellationChan>,
p: proc(): Send) {
if !opts.native_threading {
let mut pool_config = green::PoolConfig::new(); let mut pool_config = green::PoolConfig::new();
pool_config.event_loop_factory = rustuv::event_loop; pool_config.event_loop_factory = rustuv::event_loop;
let mut pool = green::SchedPool::new(pool_config); let mut pool = green::SchedPool::new(pool_config);
pool.spawn(TaskOpts::new(), p);
let constellation_chan = result_port.recv();
debug!("preparing to enter main loop");
CompositorTask::create(opts,
compositor_port,
constellation_chan,
profiler_chan);
pool.shutdown();
} else {
native::task::spawn(p);
let constellation_chan = result_port.recv();
debug!("preparing to enter main loop");
CompositorTask::create(opts,
compositor_port,
constellation_chan,
profiler_chan);
}
}
pub fn run(opts: opts::Opts) {
let (compositor_port, compositor_chan) = CompositorChan::new(); let (compositor_port, compositor_chan) = CompositorChan::new();
let profiler_chan = Profiler::create(opts.profiler_period); let profiler_chan = Profiler::create(opts.profiler_period);
@ -194,7 +174,7 @@ pub fn run(opts: opts::Opts) {
let profiler_chan_clone = profiler_chan.clone(); let profiler_chan_clone = profiler_chan.clone();
let (result_chan, result_port) = channel(); let (result_chan, result_port) = channel();
spawn_main(opts.clone(), compositor_port, profiler_chan, result_port, proc() { pool.spawn(TaskOpts::new(), proc() {
let opts = &opts_clone; let opts = &opts_clone;
// Create a Servo instance. // Create a Servo instance.
let resource_task = ResourceTask(); let resource_task = ResourceTask();
@ -230,5 +210,15 @@ pub fn run(opts: opts::Opts) {
// Send the constallation Chan as the result // Send the constallation Chan as the result
result_chan.send(constellation_chan); result_chan.send(constellation_chan);
}); });
let constellation_chan = result_port.recv();
debug!("preparing to enter main loop");
CompositorTask::create(opts,
compositor_port,
constellation_chan,
profiler_chan);
pool.shutdown();
} }

View file

@ -30,9 +30,6 @@ pub struct LayerBuffer {
/// NB: stride is in pixels, like OpenGL GL_UNPACK_ROW_LENGTH. /// NB: stride is in pixels, like OpenGL GL_UNPACK_ROW_LENGTH.
pub stride: uint, pub stride: uint,
/// Used by the RenderTask to route buffers to the correct graphics context for recycling
pub render_idx: uint
} }
/// A set of layer buffers. This is an atomic unit used to switch between the front and back /// A set of layer buffers. This is an atomic unit used to switch between the front and back

View file

@ -61,9 +61,6 @@ pub struct Opts {
/// may wish to turn this flag on in order to benchmark style recalculation against other /// may wish to turn this flag on in order to benchmark style recalculation against other
/// browser engines. /// browser engines.
pub bubble_widths_separately: bool, pub bubble_widths_separately: bool,
/// Use native threads instead of green threads
pub native_threading: bool
} }
fn print_usage(app: &str, opts: &[getopts::OptGroup]) { fn print_usage(app: &str, opts: &[getopts::OptGroup]) {
@ -80,22 +77,21 @@ pub fn from_cmdline_args(args: &[String]) -> Option<Opts> {
let app_name = args[0].to_str(); let app_name = args[0].to_str();
let args = args.tail(); let args = args.tail();
let opts = vec![ let opts = vec!(
getopts::optflag("c", "cpu", "CPU rendering"), getopts::optflag("c", "cpu", "CPU rendering"),
getopts::optopt("o", "output", "Output file", "output.png"), getopts::optopt("o", "output", "Output file", "output.png"),
getopts::optopt("r", "rendering", "Rendering backend", "direct2d|core-graphics|core-graphics-accelerated|cairo|skia."), getopts::optopt("r", "rendering", "Rendering backend", "direct2d|core-graphics|core-graphics-accelerated|cairo|skia."),
getopts::optopt("s", "size", "Size of tiles", "512"), getopts::optopt("s", "size", "Size of tiles", "512"),
getopts::optopt("", "device-pixel-ratio", "Device pixels per px", ""), getopts::optopt("", "device-pixel-ratio", "Device pixels per px", ""),
getopts::optopt("t", "threads", "Number of render threads", "1"),
getopts::optflagopt("p", "profile", "Profiler flag and output interval", "10"), getopts::optflagopt("p", "profile", "Profiler flag and output interval", "10"),
getopts::optflag("x", "exit", "Exit after load flag"), getopts::optflag("x", "exit", "Exit after load flag"),
getopts::optopt("t", "threads", "Number of render threads", "[n-cores]"), getopts::optopt("y", "layout-threads", "Number of threads to use for layout", "1"),
getopts::optopt("y", "layout-threads", "Number of layout threads", "1"),
getopts::optflag("z", "headless", "Headless mode"), getopts::optflag("z", "headless", "Headless mode"),
getopts::optflag("f", "hard-fail", "Exit on task failure instead of displaying about:failure"), getopts::optflag("f", "hard-fail", "Exit on task failure instead of displaying about:failure"),
getopts::optflag("b", "bubble-widths", "Bubble intrinsic widths separately like other engines"), getopts::optflag("b", "bubble-widths", "Bubble intrinsic widths separately like other engines"),
getopts::optflag("n", "native-threading", "Use native threading instead of green threading"),
getopts::optflag("h", "help", "Print this message") getopts::optflag("h", "help", "Print this message")
]; );
let opt_match = match getopts::getopts(args, opts.as_slice()) { let opt_match = match getopts::getopts(args, opts.as_slice()) {
Ok(m) => m, Ok(m) => m,
@ -148,17 +144,7 @@ pub fn from_cmdline_args(args: &[String]) -> Option<Opts> {
let n_render_threads: uint = match opt_match.opt_str("t") { let n_render_threads: uint = match opt_match.opt_str("t") {
Some(n_render_threads_str) => from_str(n_render_threads_str.as_slice()).unwrap(), Some(n_render_threads_str) => from_str(n_render_threads_str.as_slice()).unwrap(),
None => { None => 1, // FIXME: Number of cores.
// FIXME (rust/14707): This still isn't exposed publicly via std::rt??
// FIXME (rust/14704): Terrible name for this lint, which here is allowing
// Rust types, not C types
#[allow(ctypes)]
extern {
fn rust_get_num_cpus() -> uint;
}
unsafe { rust_get_num_cpus() as uint }
}
}; };
// if only flag is present, default to 5 second period // if only flag is present, default to 5 second period
@ -173,8 +159,6 @@ pub fn from_cmdline_args(args: &[String]) -> Option<Opts> {
None => cmp::max(rt::default_sched_threads() * 3 / 4, 1), None => cmp::max(rt::default_sched_threads() * 3 / 4, 1),
}; };
let native_threading = opt_match.opt_present("h") || opt_match.opt_present("help");
Some(Opts { Some(Opts {
urls: urls, urls: urls,
render_backend: render_backend, render_backend: render_backend,
@ -189,6 +173,5 @@ pub fn from_cmdline_args(args: &[String]) -> Option<Opts> {
headless: opt_match.opt_present("z"), headless: opt_match.opt_present("z"),
hard_fail: opt_match.opt_present("f"), hard_fail: opt_match.opt_present("f"),
bubble_widths_separately: opt_match.opt_present("b"), bubble_widths_separately: opt_match.opt_present("b"),
native_threading: native_threading
}) })
} }