Debugging WebRender

Nicolas Silva edited this page Jan 4, 2019 · 13 revisions

Links

These are a few useful resources for hacking on WebRender.

Overviews

Tutorials

Graphics stack

  • https://open.gl/ — modern OpenGL tutorial
  • https://www.opengl.org/wiki/ — very useful resource for exhaustive explanations of OpenGL features
  • The chromium/blink developers already try to use the gpu for drawing, see here some documentation of their implementation.

Tools

Here is a list of useful tools to work on or with WebRender.

Overlay debugging

The RendererOption struct has a set of flags that can be set to enable displaying various debugging information on top of the rendered content. These can be toggled with keyboard shortcuts when running wrench or the examples (See the keyboard input handling code in wrench/main.rs).

There is a full overlay and a compact one. Some of the developers run with the latter ON constantly in day-to-day browsing with WR-enabled Gecko.

Wrench

WebRender debugging and testing tool:

  • Live-editing of scenes described in yaml files.
  • Replay WebRender recordings.

This tool is under the wrench directory in this repository. cargo run -- --help in the wrench directory to see more information about how to use this tool.

For graphics debugging, the following options are useful: ---no-batch disables combining multiple items into instanced draw calls, which means every quad/item is drawn separately, and it's easier to see the side effects.

  • --angle on Windows enables Angle library, forcing D3D11 to be used for rendering. GPU tools work much better with D3D11, providing pixel history and shader debugging.

Wrench in headless mode with os-mesa

wrench can be run using the wrench/script/headless.py to get a software GL-context which matches what runs in CI and has the advantage of being compatible with rr.

The script uses an optimized build by default, but a debug build can be used instead by setting the OPTIMIZED environment variable to "false" or "0". headless.py also provides the option of running in a debugger by setting the DEBUGGER environment variable to "gdb", "cgdb", "rust-gdb", or "rr".

For example: OPTIMIZED=false DEBUGGER=rr ./script/headless.py rawtest

WebRender debug server

When compiled with the "debugger" feature flag, WebRender starts up a debug server that can be accessed with a browser. The web interface allows toggling overlay debugging information and visualizing useful information such as the display list, and batches.

example:

# go to wrench's directory
cd wrench
# show one of the test files with the debug server enabled
cargo run show reftests/image/tile-size.yaml --features 'webrender/debugger'
# show the web interface
firefox ../debugger/index.html

Apitrace

Works out of the box.

Project page: https://github.com/apitrace/apitrace

RenderDoc

Works out of the box (almost). Allows inspecting the graphics state and individual draw calls. Doesn't provide shader debugging or pixel history on OpenGL.

On windows, start firefox with the --wait-for-browser command-line option. Otherwise the launcher process starts firefox and terminates right away which RenderDoc understands as the program terminating before anything interesting happened.

Project: https://renderdoc.org/

Nvidia linux debugger

Project page: https://developer.nvidia.com/linux-graphics-debugger

Mesa environment variables (Linux)

Mesa provides some useful environment variables, for example INTEL_DEBUG=perf adds performance logging to stdout.

See http://www.mesa3d.org/envvars.html for a comprehensive list.

LPGPU2

Helpful to track down power usage on Android: https://github.com/codeplaysoftware/LPGPU2-CodeXL

Profiling with the Gecko profiler

WebRender in Gecko is compatible with the built-in profiling infrastructure, however WebRender's threads are not recorded by default and must be added. The thread names to add in the profiler settings are:

  • WRRenderBackend
  • WRSceneBuilder
  • WRWorker
  • Renderer

You can copy and paste the following: GeckoMain,Compositor,WRRenderBackend,WRSceneBuilder,WRWorker,Renderer

Binary recording

WebRender allows dumping all incoming display lists in a binary file that can be re-played later, and individual frames can even be converted to YAML reftests. In order to use it from Gecko, it needs to be run with ENABLE_WR_RECORDING=1 environment variable.

Capture infrastructure

WebRender has a debugging feature that serializes most of the internal state in a way that can be replayed in wrench (and inspected manually by looking at the generated text files in RON format). This is integrated in Firefox Nightly and can be used by pressing Ctrl + SHift + 3. Upon hitting these keys a folder wr-capture will be created in the local directory:

  • on Linux, this is typically your home folder (~/wr-capture)
  • on MacOS, TODO
  • on Windows, this would be the program files folder in which Nightly is installed. Typically it requires elevated privileges, so you'd need to run it as Administrator for the capture to appear.

To replay the capture in wrench, use the wrench load <capture-path> command.

Note: there isn't currently a way to convert WR captures into YAML testcases.

@kvark wrote some more info about the tool on their blog.

Chasing infrastructure

While capturing allows you to see a slice of the whole state at a particular point in the pipeline, more often than not we are only interested in a single item. There is logic in place to "chase" the processing of an individual primitive.

If you have a WR capture saved in wr-capture, and you know the local rectangle of an item to track, you can edit wr-capture/backend.ron to enable chasing of a primitive with this rectangle:

    frame_config: (
        enable_scrollbars: false,
        default_font_render_mode: Subpixel,
        dual_source_blending_is_supported: true,
        dual_source_blending_is_enabled: true,
        chase_primitive: LocalRect(((2, -261), (217, 309))), // <--- here
    ),

Then you'd need to load the capture as usual: cargo run -- load ~/wr-capture. No changes are observed at this stage, because nothing is processed: the built frame is simply displayed. In order to re-build the frame from a scene you can hit a button (say, left/right). You'd see the following output on the console:

loaded [DocumentId(IdNamespace(5), 0)]
Chasing PrimitiveIndex(16)
        preparing a run of length 1 in pipeline PipelineId(1, 11)
        run ScrollNodeAndClipChain { spatial_node_index: SpatialNodeIndex(7), clip_chain_index: ClipChainIndex(21) }
        transform [1.0, 0.0, 0.0, 0.0, -6.1793494, -1.6343956, 0.9659258, -0.0064395056, -1.6557517, -1.4732112, 0.25881904, -0.0017254603, 16093.576, 4550.9814, -2524.7356, 17.831573]
        effective clip chain from CoordinateSystemId(2) (applied)
                CoordinateSystemId(2) TypedRect(1920.0?3200.0 at (0.0,-2443.7988))
                CoordinateSystemId(2) TypedRect(1920.0?340.0 at (0.0,0.0))
                CoordinateSystemId(0) TypedRect(1920.0?962.0 at (0.0,78.0))
                CoordinateSystemId(0) TypedRect(1920.0?255.0 at (0.0,194.0))
        updating clip task with screen rect TypedRect(2000000000?999999744 at (-1000000000,255))
        base screen TypedRect(1920?785 at (0,255)), combined clip chain TypedRect(1920?184 at (0,265))
        segment tasks have been created for clipping
        considered visible and ready with local rect TypedRect(1920.0?3200.0 at (0.0,0.0))
                BrushSegment { local_rect: TypedRect(1920.0?3200.0 at (0.0,0.0)), clip_task_id: RenderTaskId(RenderTaskId(4, FrameId(1))), may_need_clip_mask: false, edge_flags: LEFT | TOP | RIGHT | BOTTOM, extra_data: [0.0, 0.0, 0.0, 0.0], brush_flags: (empty) }
        task target TypedRect(1920?775 at (0,0))
        PrimitiveHeader { local_rect: TypedRect(1920.0?3201.0 at (0.0,0.0)), local_clip_rect: TypedRect(1920.0?340.0 at(0.0,2443.7988)), task_address: RenderTaskAddress(5), specific_prim_address: GpuCacheAddress { u: 456, v: 42 }, clip_task_address: RenderTaskAddress(32767), transform_id: TransformPaletteId(16777223) }
        source CacheItem { texture_id: TextureCache(CacheTextureId(0)), uv_rect_handle: GpuCacheHandle { location: Some(CacheLocation { block_index: BlockIndex(3412), epoch: Epoch(2) }) }, uv_rect: TypedRect(400?300 at (0,512)), texture_layer: 3 }
        Image(Texture2DArray) PrimitiveHeaderIndex(0), task relative bounds TypedRect(2000000000?999999744 at (-1000000000,-10))

Here you can get a lot of valuable info to use in conjunction with regular debugging or WR capture fiddling:

  • primitive index
  • list of clips affecting an item
  • resulting local/screen rectangles
  • primitive header passed to GPU

"Show Overdraw" mode

In Firefox, "Show Overdraw" mode can be annoying to enable, because about:config is not particularly user friendly when you can't see the text. To add a keyboard shortcut for it, open about:config, open the Web Console to get chrome privileged JavaScript, then paste the following:

!function(){const e="gfx.webrender.debug.show-overdraw",r=Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService);window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow).addEventListener("keypress",function(n){n.shiftKey&&n.ctrlKey&&"%"===n.key&&r.setBoolPref(e,!r.getBoolPref(e))},!0)}();

This makes Ctrl+Shift+5 toggle "Show Overdraw" mode. Note that you will usually need to have chrome have the focus (for example, by clicking in the URL bar) for this to work.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.