CSC/ECE 517 Spring 2018- Project M1802: 2D Canvas Rendering

From Expertiza_Wiki
Jump to navigation Jump to search

“M1802: Simplify the 2d Canvas Rendering”

As robust and fast Servo is, Servo’s implementation of its HTML 2D/3D “<canvas>” has several inefficiencies that make some websites perform slowly or run out of memory when performing complex canvas operations. The goal of this project was to make these websites perform better when loaded in Servo.


Introduction

This section briefly describes the main parts of this project: Servo, Rust, and HTML Canvas.

Servo

Named after a robot from the show Mystery Science Theater 3000, Servo is an experimental web browser layout engine developed by Mozilla. It is currently being developed for 64-bit OS X, 64-bit Linux, 64-bit Windows, and Android. While working with Samsung, Mozilla seeks to create a highly parallel environment where the many things in a web browser like rendering, image decoding, and HTML parsing are handled by isolated tasks. Here is a demo on how Servo performs against Firefox.

Rust

Since existing languages like C++ did not provide a direct way to achieve a parallel environment, Mozilla developed and uses a programming language called Rust to further develop Servo.

HTML Canvas

A <canvas> in HTML is a container for graphics that is used to draw graphics “on the fly” by using Javascript. However, the <canvas> element has no drawing abilities of its own since it only acts as a container for graphics. A script to actually draw the graphics. By using scripts, the HTML <canvas> can:

  • Be animated
  • Be interactive
  • Draw text
  • Be used in games


Project Description

The goal of this project was to remove the inefficiencies of Servo’s implemetation of the 2D/3D canvas. As reported with issue #10381, a gaming website called http://slither.io/ was performing extremely slowly as of Apr 3, 2016. As of Oct 21, 2016, it was reported with issue #13879 that http://slither.io/ was, in fact, crashing when loaded onto Servo. The suggestion to these issues was to, first, create a test case that measured the amount of time that Servo spends under “drawImage” while doing operations related to a canvas. Another suggestion was to change Servo’s implementation from using one thread per canvas to one thread for all canvases. The steps laid out for this project by Mozilla (both the initial setup steps and optimizing the canvas implementation) are shown below.

Initial Steps:

  • create a testcase that contains two canvases and uses the drawImage API to draw the contents of one canvas onto the other. Programmatically measure the time this operation takes.
  • To prepare for the big switch from 1 threads per canvas to 1 thread for all canvases, add a struct CanvasId(u64) type to components/canvas_traits/canvas.rs and add a CanvasId member to each variant of the CanvasMsg enum.
  • add a CanvasId member to Constellation in components/constellation/constellation.rs which is initialized to 0 and increased by 1 each time handle_create_canvas_paint_thread is called.
  • make the response_sender argument of handle_create_canvas_paint_thread also include the new CanvasId value, and pass it as an argument to CanvasPaintThread::start. Store the id when it is received for use in all canvas messages.
  • For each CanvasMsg that is processed by CanvasPaintThread, verify that the id received matches the id that was provided to CanvasPaintThread::start


Subsequent Steps:

  • extract the innards of CanvasPaintThread into a CanvasData structure,
  • make CanvasPaintThread store a hashtable of CanvasId->CanvasData
  • as part of Constellation::start, create a canvas paint thread and store the channel to communicate with it as a member of Constellation. Remove the initial canvas id from the API of Constellation::start.
  • when handle_create_canvas_paint_thread is invoked, communicate with the canvas thread and have it create a new entry in the hashtable.
  • when the canvas thread receives a message, perform the operation on the appropriate canvas according to the provided id
  • optimize the DrawImageInOther operation by drawing on the destination canvas directly, rather than relying on sending a message to another canvas thread. Remove the now-unnecessary IpcSender from the DrawImageInOther enum variant. Verify that the earlier test case demonstrates a performance improvement.
  • report on how slither.io performs in Servo after all these changes


Design Pattern

Design patterns were not applicable since our task was to add a few parts to code that already existed.


Implementation

This section covers the implementation of the initial steps for this project.

Building

After setting up the environment required to develop for Servo, we built and compiled Servo as per the instructions on Servo’s Github repo. We used Mozilla’s mach tools to build Servo with Cargo, which is the rust package manager.

git clone https://github.com/servo/servo
cd servo
./mach build --dev
./mach run tests/html/about-mozilla.html


Testing

After successfully building servo, we proceeded to test the amount of time spent under “drawImage” as shown below. The function "drawImage" copies the contents of one canvas in a view to another canvas in the same view. During the "Initial Steps," this test was created to test the initial performance of Servo with regards to canvas rendering.

var t0 = performance.now();
drawImage(25, 25);
var t1 = performance.now();
document.getElementById('Test Result').innerHTML = "DrawImage took " + (t1 - t0) + " milliseconds.";
console.log("DrawImage took " + (t1 - t0) + " milliseconds.");


Adding an Id to Canvas Communications

As shown below, we then associated a CanvasId with each variant of a canvas message; these messages are passed around various parts of the Servo implementation such as the 2D canvas struct, view layout, and various scripts. This required us to make other changed to allow a CanvasId to be sent with each message, as shown below.

Before After
pub enum CanvasMsg {
   Canvas2d(Canvas2dMsg),
   FromLayout(FromLayoutMsg),
   FromScript(FromScriptMsg),
   Recreate(Size2D<i32>),
   Close(),

}

pub enum CanvasMsg {
   Canvas2d(Canvas2dMsg, CanvasId),
   FromLayout(FromLayoutMsg, CanvasId),
   FromScript(FromScriptMsg, CanvasId),
   Recreate(Size2D<i32>, CanvasId),
   Close(CanvasId),

}


Then, we made the necessary changed to the constellation and the canvas paint thread to be able to initialize, increment, and store the CanvasId. To finish up with the initial steps, we programmatically checked to make sure that each id that was processed by CanvasPaintThread was the same id that was provided when the thread was started (CanvasPaintThread::start).

assert!(canvas_id == painter.canvas_id);


Running Servo

After each successful build through our development process, we ran Servo as per the instructions on Servo’s Github repo.

./servo [url] [arguments] # if you run with nightly build
./mach run [url] [arguments] # if you run with mach

# For example
./mach run https://www.google.com


Performance

Almost every time, http://slither.io/ ended up crashing. Other times, it performed very slowly. However, as of 4/2/2018, much more is left to be done (The Subsequent Steps). Our repo is here.


Conclusion

So far we have associated each variant of a canvas message with a particular canvas id. Even though there is not much improvement is the performance of http://slither.io/, we believe that an improvement can be noticed after going through the Subsequent Steps. Here is a link to our pull request. On 4/2/2018, our contribution was merged into Servo's master branch!

References

  1. https://en.wikipedia.org/wiki/Servo_(layout_engine)
  2. https://www.rust-lang.org/en-US/
  3. https://github.com/servo/servo
  4. https://github.com/servo/servo/wiki/Canvas-rendering-project
  5. https://www.w3schools.com/graphics/canvas_intro.asp
  6. https://github.com/servo/servo/issues/10381
  7. https://github.com/servo/servo/issues/13879