summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/src/debug_server.rs
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/webrender/src/debug_server.rs')
-rw-r--r--gfx/wr/webrender/src/debug_server.rs402
1 files changed, 402 insertions, 0 deletions
diff --git a/gfx/wr/webrender/src/debug_server.rs b/gfx/wr/webrender/src/debug_server.rs
new file mode 100644
index 0000000000..36ed9cd2eb
--- /dev/null
+++ b/gfx/wr/webrender/src/debug_server.rs
@@ -0,0 +1,402 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use api::units::DeviceIntSize;
+use api::channel::{fast_channel, Sender, Receiver};
+use api::DebugFlags;
+use crate::render_api::{ApiMsg, DebugCommand};
+use crate::print_tree::PrintTreePrinter;
+use crate::renderer;
+use std::thread;
+use ws;
+use base64::encode;
+use image_loader;
+
+// Messages that are sent from the render backend to the renderer
+// debug command queue. These are sent in a separate queue so
+// that none of these types are exposed to the RenderApi interfaces.
+// We can't use select!() as it's not stable...
+enum DebugMsg {
+ AddSender(ws::Sender),
+ RemoveSender(ws::util::Token),
+}
+
+// Represents a connection to a client.
+struct Server {
+ ws: ws::Sender,
+ debug_tx: Sender<DebugMsg>,
+ api_tx: Sender<ApiMsg>,
+ debug_flags: DebugFlags,
+}
+
+impl ws::Handler for Server {
+ fn on_open(&mut self, _: ws::Handshake) -> ws::Result<()> {
+ self.debug_tx
+ .send(DebugMsg::AddSender(self.ws.clone()))
+ .ok();
+
+ Ok(())
+ }
+
+ fn on_close(&mut self, _: ws::CloseCode, _: &str) {
+ self.debug_tx
+ .send(DebugMsg::RemoveSender(self.ws.token()))
+ .ok();
+ }
+
+ fn on_message(&mut self, msg: ws::Message) -> ws::Result<()> {
+ match msg {
+ ws::Message::Text(string) => {
+ // First, check for flag change commands.
+ let mut set_flags = true;
+ match string.as_str() {
+ "enable_profiler" => self.debug_flags.insert(DebugFlags::PROFILER_DBG),
+ "disable_profiler" => self.debug_flags.remove(DebugFlags::PROFILER_DBG),
+ "enable_texture_cache_debug" => self.debug_flags.insert(DebugFlags::TEXTURE_CACHE_DBG),
+ "disable_texture_cache_debug" => self.debug_flags.remove(DebugFlags::TEXTURE_CACHE_DBG),
+ "enable_render_target_debug" => self.debug_flags.insert(DebugFlags::RENDER_TARGET_DBG),
+ "disable_render_target_debug" => self.debug_flags.remove(DebugFlags::RENDER_TARGET_DBG),
+ "enable_gpu_time_queries" => self.debug_flags.insert(DebugFlags::GPU_TIME_QUERIES),
+ "disable_gpu_time_queries" => self.debug_flags.remove(DebugFlags::GPU_TIME_QUERIES),
+ "enable_gpu_sample_queries" => self.debug_flags.insert(DebugFlags::GPU_SAMPLE_QUERIES),
+ "disable_gpu_sample_queries" => self.debug_flags.remove(DebugFlags::GPU_SAMPLE_QUERIES),
+ "disable_opaque_pass" => self.debug_flags.insert(DebugFlags::DISABLE_OPAQUE_PASS),
+ "enable_opaque_pass" => self.debug_flags.remove(DebugFlags::DISABLE_OPAQUE_PASS),
+ "disable_alpha_pass" => self.debug_flags.insert(DebugFlags::DISABLE_ALPHA_PASS),
+ "enable_alpha_pass" => self.debug_flags.remove(DebugFlags::DISABLE_ALPHA_PASS),
+ "disable_clip_masks" => self.debug_flags.insert(DebugFlags::DISABLE_CLIP_MASKS),
+ "enable_clip_masks" => self.debug_flags.remove(DebugFlags::DISABLE_CLIP_MASKS),
+ "disable_text_prims" => self.debug_flags.insert(DebugFlags::DISABLE_TEXT_PRIMS),
+ "enable_text_prims" => self.debug_flags.remove(DebugFlags::DISABLE_TEXT_PRIMS),
+ "disable_gradient_prims" => self.debug_flags.insert(DebugFlags::DISABLE_GRADIENT_PRIMS),
+ "enable_gradient_prims" => self.debug_flags.remove(DebugFlags::DISABLE_GRADIENT_PRIMS),
+ _ => set_flags = false,
+ };
+
+ let cmd = if set_flags {
+ DebugCommand::SetFlags(self.debug_flags)
+ } else {
+ match string.as_str() {
+ "fetch_passes" => DebugCommand::FetchPasses,
+ "fetch_screenshot" => DebugCommand::FetchScreenshot,
+ "fetch_documents" => DebugCommand::FetchDocuments,
+ "fetch_spatial_tree" => DebugCommand::FetchClipScrollTree,
+ "fetch_render_tasks" => DebugCommand::FetchRenderTasks,
+ msg => {
+ error!("unknown msg {}", msg);
+ return Ok(());
+ }
+ }
+ };
+
+ let msg = ApiMsg::DebugCommand(cmd);
+ self.api_tx.send(msg).unwrap();
+ }
+ ws::Message::Binary(..) => {}
+ }
+
+ Ok(())
+ }
+}
+
+// Spawn a thread for a given renderer, and wait for
+// client connections.
+pub struct DebugServerImpl {
+ join_handle: Option<thread::JoinHandle<()>>,
+ broadcaster: ws::Sender,
+ debug_rx: Receiver<DebugMsg>,
+ senders: Vec<ws::Sender>,
+}
+
+impl DebugServerImpl {
+ pub fn new(api_tx: Sender<ApiMsg>) -> DebugServerImpl {
+ let (debug_tx, debug_rx) = fast_channel(64);
+
+ let socket = ws::Builder::new()
+ .build(move |out| {
+ Server {
+ ws: out,
+ debug_tx: debug_tx.clone(),
+ api_tx: api_tx.clone(),
+ debug_flags: DebugFlags::empty(),
+ }
+ })
+ .unwrap();
+
+ let broadcaster = socket.broadcaster();
+
+ let join_handle = Some(thread::spawn(move || {
+ let address = "127.0.0.1:3583";
+ debug!("WebRender debug server started: {}", address);
+ if let Err(..) = socket.listen(address) {
+ error!("ERROR: Unable to bind debugger websocket (port may be in use).");
+ }
+ }));
+
+ DebugServerImpl {
+ join_handle,
+ broadcaster,
+ debug_rx,
+ senders: Vec::new(),
+ }
+ }
+}
+
+impl renderer::DebugServer for DebugServerImpl {
+ fn send(&mut self, message: String) {
+ // Add any new connections that have been queued.
+ while let Ok(msg) = self.debug_rx.try_recv() {
+ match msg {
+ DebugMsg::AddSender(sender) => {
+ self.senders.push(sender);
+ }
+ DebugMsg::RemoveSender(token) => {
+ self.senders.retain(|sender| sender.token() != token);
+ }
+ }
+ }
+
+ // Broadcast the message to all senders. Keep
+ // track of the ones that failed, so they can
+ // be removed from the active sender list.
+ let mut disconnected_senders = Vec::new();
+
+ for (i, sender) in self.senders.iter().enumerate() {
+ if let Err(..) = sender.send(message.clone()) {
+ disconnected_senders.push(i);
+ }
+ }
+
+ // Remove the broken senders from the list
+ // for next broadcast. Remove in reverse
+ // order so the indices are valid for the
+ // entire loop.
+ for i in disconnected_senders.iter().rev() {
+ self.senders.remove(*i);
+ }
+ }
+}
+
+impl Drop for DebugServerImpl {
+ fn drop(&mut self) {
+ self.broadcaster.shutdown().ok();
+ self.join_handle.take().unwrap().join().ok();
+ }
+}
+
+// A serializable list of debug information about passes
+// that can be sent to the client.
+
+#[derive(Serialize)]
+pub enum BatchKind {
+ Clip,
+ Cache,
+ Opaque,
+ Alpha,
+}
+
+#[derive(Serialize)]
+pub struct PassList {
+ kind: &'static str,
+ passes: Vec<Pass>,
+}
+
+impl PassList {
+ pub fn new() -> PassList {
+ PassList {
+ kind: "passes",
+ passes: Vec::new(),
+ }
+ }
+
+ pub fn add(&mut self, pass: Pass) {
+ self.passes.push(pass);
+ }
+}
+
+#[derive(Serialize)]
+pub struct Pass {
+ pub targets: Vec<Target>,
+}
+
+#[derive(Serialize)]
+pub struct Target {
+ kind: &'static str,
+ batches: Vec<Batch>,
+}
+
+impl Target {
+ pub fn new(kind: &'static str) -> Target {
+ Target {
+ kind,
+ batches: Vec::new(),
+ }
+ }
+
+ pub fn add(&mut self, kind: BatchKind, description: &str, count: usize) {
+ if count > 0 {
+ self.batches.push(Batch {
+ kind,
+ description: description.to_owned(),
+ count,
+ });
+ }
+ }
+}
+
+#[derive(Serialize)]
+struct Batch {
+ kind: BatchKind,
+ description: String,
+ count: usize,
+}
+
+#[derive(Serialize)]
+pub struct TreeNode {
+ description: String,
+ children: Vec<TreeNode>,
+}
+
+impl TreeNode {
+ pub fn new(description: &str) -> TreeNode {
+ TreeNode {
+ description: description.to_owned(),
+ children: Vec::new(),
+ }
+ }
+
+ pub fn add_child(&mut self, child: TreeNode) {
+ self.children.push(child);
+ }
+
+ pub fn add_item(&mut self, description: &str) {
+ self.children.push(TreeNode::new(description));
+ }
+}
+
+#[derive(Serialize)]
+pub struct DocumentList {
+ kind: &'static str,
+ root: TreeNode,
+}
+
+impl DocumentList {
+ pub fn new() -> Self {
+ DocumentList {
+ kind: "documents",
+ root: TreeNode::new("root"),
+ }
+ }
+
+ pub fn add(&mut self, item: TreeNode) {
+ self.root.add_child(item);
+ }
+}
+
+#[derive(Serialize)]
+pub struct Screenshot {
+ kind: &'static str,
+ data: String
+}
+
+impl Screenshot {
+ pub fn new(size: DeviceIntSize, data: Vec<u8>) -> Self {
+ let mut output = Vec::with_capacity((size.width * size.height) as usize);
+ {
+ let encoder = image_loader::png::PNGEncoder::new(&mut output);
+ encoder.encode(
+ &data,
+ size.width as u32,
+ size.height as u32,
+ image_loader::ColorType::Rgba8,
+ ).unwrap();
+ }
+
+ let data = encode(&output);
+ Screenshot {
+ kind: "screenshot",
+ data
+ }
+ }
+}
+
+// A serializable list of debug information about spatial trees
+// that can be sent to the client
+
+#[derive(Serialize)]
+pub struct SpatialTreeList {
+ kind: &'static str,
+ root: TreeNode,
+}
+
+impl SpatialTreeList {
+ pub fn new() -> Self {
+ SpatialTreeList {
+ kind: "spatial_tree",
+ root: TreeNode::new("root"),
+ }
+ }
+
+ pub fn add(&mut self, item: TreeNode) {
+ self.root.add_child(item);
+ }
+}
+
+#[derive(Serialize)]
+pub struct RenderTaskList {
+ kind: &'static str,
+ root: TreeNode,
+}
+
+impl RenderTaskList {
+ pub fn new() -> Self {
+ RenderTaskList {
+ kind: "render_tasks",
+ root: TreeNode::new("root"),
+ }
+ }
+
+ pub fn add(&mut self, item: TreeNode) {
+ self.root.add_child(item);
+ }
+}
+
+// A TreeNode-based PrintTreePrinter to serialize pretty-printed
+// trees as json
+pub struct TreeNodeBuilder {
+ levels: Vec<TreeNode>,
+}
+
+impl TreeNodeBuilder {
+ pub fn new(root: TreeNode) -> TreeNodeBuilder {
+ TreeNodeBuilder { levels: vec![root] }
+ }
+
+ fn current_level_mut(&mut self) -> &mut TreeNode {
+ assert!(!self.levels.is_empty());
+ self.levels.last_mut().unwrap()
+ }
+
+ pub fn build(mut self) -> TreeNode {
+ assert!(self.levels.len() == 1);
+ self.levels.pop().unwrap()
+ }
+}
+
+impl PrintTreePrinter for TreeNodeBuilder {
+ fn new_level(&mut self, title: String) {
+ let level = TreeNode::new(&title);
+ self.levels.push(level);
+ }
+
+ fn end_level(&mut self) {
+ assert!(!self.levels.is_empty());
+ let last_level = self.levels.pop().unwrap();
+ self.current_level_mut().add_child(last_level);
+ }
+
+ fn add_item(&mut self, text: String) {
+ self.current_level_mut().add_item(&text);
+ }
+}