ludei / webview-plus

Uniform webview on any Android 4.x device.
103 stars 16 forks source link

Webview+ has very slow drawImage operations compared to Webview. #12

Open agamemnus opened 10 years ago

agamemnus commented 10 years ago

About 4 times slower... 4 times.

To test.. I dunno, I dunno... I can send you my APK or zip file to test...

Android 4.4.2 here, Note 2.

ludei commented 10 years ago

Hello Agamenus

In order to attend this issue we need a test case, can you upload a simple test that reproduces the issue?

agamemnus commented 10 years ago

All right. I made a small test case but it still contains somewhat sensitive code. What is the best email I can use to send you the test case?

agamemnus commented 10 years ago

All right... I will attach a non-sensitive version here.

<!DOCTYPE html>
<html lang="en">
 <head>
  <script type="text/javascript" src = "cocoon.min.js"></script>
 </head>
 <body>
  <script type="text/javascript">
// 1) Path drawing is slower in Webview+ vs. Webview.
// 2) Clip is slower in Webview+ vs. Webview.
// 3) DrawImage inside of "apply_3d_effect" is slower in Webview+ vs. Webview.
var t1 = +new Date ()
for (var i = 0; i < 40; i++) {
 var canvas1 = document.createElement ('canvas')
 var ctx1 = canvas1.getContext ('2d')
 canvas1.width = 340
 canvas1.height = 270
 var shape_string = "M 0.0000,250.3895  L 0.0000,250.3895 0.0000,0.0000  L 0.0000,0.0000 275.0000,0.0000  C 219.8988,147.4189 335.8291,44.0002 335.8291,110.0002  C 335.8291,176.0002 186.3356,7.3333 235.8444,275.0002  C 57.2933,243.0562 176.0000,193.7555 110.0000,193.7555  C 44.0000,193.7555 158.0330,250.6112 0.0000,250.3895 z"
 //ctx1.save ()
 canvas_draw_path ({
  x           : 0,
  y           : 0,
  data        : shape_string,
  fillColor   : 'rgba(0,127,0,1)',
  strokeColor : 'rgba(63,63,63,1)',
  lineCap     : 'round',
  strokeWidth : 0,
  canvas      : canvas1
 })
 //ctx1.clip ()
 //apply_3d_effect ({canvas: canvas1, stroke_width: 1, cut_multiplier: 1, shape_string: shape_string}) <-- give me a good email to obtain this sekrit function.
}
alert (+new Date () - t1)
document.body.appendChild (canvas1)

// Create a simple SVG path from a string and draw it onto a canvas. Derived from kineticJS under MIT license.
function canvas_draw_path (init) {
 var x           = init.x || 0
 var y           = init.y || 0
 var data_string = init.data
 var canvas      = init.canvas

 var data_array = get_data_array_from_string (data_string)
 draw_func (data_array, canvas)

 function draw_func (data_array, canvas) {
  var context = canvas.getContext('2d')

  if (typeof init.strokeWidth != "undefined") context.lineWidth   = init.strokeWidth
  if (typeof init.strokeColor != "undefined") context.strokeStyle = init.strokeColor
  if (typeof init.lineCap     != "undefined") context.lineCap     = init.lineCap

  context.beginPath ()
  context.translate (init.x, init.y)
  for (var n = 0; n < data_array.length; n++) {
   var c = data_array[n].command
   var p = data_array[n].points
   switch (c) {
    case 'L' : context.lineTo (p[0], p[1]); break
    case 'M' : context.moveTo (p[0], p[1]); break
    case 'C' : context.bezierCurveTo (p[0], p[1], p[2], p[3], p[4], p[5]); break
    case 'z' : context.closePath (); break
   }
  }
  if ((typeof init.strokeWidth == "undefined") || (init.strokeWidth != 0)) context.stroke ()
  if ((typeof init.fillPatternImage != "undefined") || (typeof init.fillColor != "undefined")) {
   if (typeof init.fillPatternImage != "undefined") {
    var pattern = context.createPattern(init.fillPatternImage, 'no-repeat')
    if (typeof init.fillPatternOffset != "undefined") {
     context.save ()
     context.translate (-init.fillPatternOffset[0], 0)
     context.translate (0, -init.fillPatternOffset[1])
    }
    context.fillStyle = pattern
   } else {
    // fillColor is subordinate to fillPatternImage.
    context.fillStyle = init.fillColor
   }
   context.fill ()
  }
  if (typeof init.fillPatternOffset != "undefined") context.restore ()
 }

 function get_data_array_from_string (data_string) {
  // Command string.
  var cs = data_string

  // Command chars.
  var cc = ['M', 'L', 'C']

  // Convert white spaces to commas.
  cs = cs.replace (new RegExp(' ', 'g'), ',')

  // Create pipes so that we can split the data.
  for (var n = 0; n < cc.length; n++) {cs = cs.replace (new RegExp(cc[n], 'g'), '|' + cc[n])}

  // Create the array.
  var arr = cs.split('|')
  var ca = []

  // Init the context point.
  var cpx = 0, cpy = 0
  for (n = 1; n < arr.length; n++) {
   var str = arr[n]
   var c = str.charAt(0)
   str = str.slice(1)

   // Remove ,- for consistency.
   str = str.replace (new RegExp(',-', 'g'), '-')

   // Add commas so that it's easy to split.
   str = str.replace (new RegExp('-', 'g'), ',-')
   str = str.replace (new RegExp('e,-', 'g'), 'e-')
   var p = str.split(',')
   if (p.length > 0 && p[0] === '') p.shift()

   // Convert strings to floats.
   for (var i = 0; i < p.length; i++) {p[i] = parseFloat(p[i])}

   while (p.length > 0) {
    if (isNaN(p[0])) break // Case for a trailing comma before next command.
    var cmd = null
    var points = []
    var startX = cpx, startY = cpy
    switch (c) {
     case 'L' :
      cpx = p.shift ()
      cpy = p.shift ()
      points.push (cpx, cpy)
      break
     case 'M' :
      // Subsequent points are treated as absolute lineTo.
      cpx = p.shift ()
      cpy = p.shift ()
      cmd = 'M'
      points.push (cpx, cpy)
      c = 'L'
      break
     case 'C' :
      points.push (p.shift(), p.shift(), p.shift(), p.shift())
      cpx = p.shift ()
      cpy = p.shift ()
      points.push (cpx, cpy)
      break
    }
    ca.push ({
     command : cmd || c,
     points  : points,
     start   : {x: startX, y: startY}
    })
   }
   if ((c === 'z') || (c === 'Z')) ca.push ({command: 'z', points: [], start: undefined})
  }
  return ca
 }
}
  </script>
 </body>
</html>
agamemnus commented 10 years ago

Bump...

agamemnus commented 10 years ago

It would help if you could say something about this... like "we're working on it", or "the testcase looks fine on our end", etc..

mcfarljw commented 10 years ago

@agamemnus I can't comment on the specifics of your code, but I've found in my projects that slow canvas speeds were directly related to my coding. It's frustrating because using a desktop browser it'll often run my "bad" code at an acceptable speed, but then when wrapped with the webview+ the issues present themselves on an actual device. I have fixed all of my canvas speed issues by improving my code. It might also be helpful to add that I use EaselJS (https://github.com/CreateJS/EaselJS/) for all of my canvas stuff.

agamemnus commented 10 years ago

Sure, and I managed to increase performance by batching the effects. It's still 4-5x slower in Webview+ versus Webview (not versus PC).

mcfarljw commented 10 years ago

Hmm, that's strange. I've never seen the webview+ go slower than the native webview (even the improved 4.4.2 version). What I would do to debug the problem is use the Chrome profiler to debug the application on device and see what exactly is using the most cpu processing power.

agamemnus commented 10 years ago

It is all in the drawImage, but only happens with the clip operatIons and Bezier curves. The clip operation and Bezier curve commands themselves are not slower, but the drawImage is. I have a suspicion that webview+ sends canvas textures to the gpu and then back again unnecessarily for some reason.

agamemnus commented 10 years ago

It looks like setting a high shadowBlur value for a shadow makes Webview+ and Canvas subsequent related drawing operations extremely slow.... anyone working on this? :-(

mcfarljw commented 10 years ago

Shadow and box-shadow are very expensive to use, especially on mobile devices. If I remember correctly the size of the shadow exponentially decreased performance as well. I think this is a much larger issue than the webview+. We actually had to entirely remove them from our app on mobile.

agamemnus commented 10 years ago

Ugh. Well, what doesn't make sense here is why Webview+ is so slow and Webview isn't, at least for that. For everything else Webview+ seems much faster. Could it be that Webview is using the CPU to compute the blurring and Webview+ is using the GPU? (and the GPU maybe can't handle it as well)

I wonder if there is a switch for that.

mcfarljw commented 10 years ago

How many devices have you tested it on? Android 4.4.2 can be a bit deceiving because they improved the webview, but using any version before that the issues with the webview are very visible in comparison to the webview+.

I don't know if the webview+ uses it or not, but when I use crosswalk (https://crosswalk-project.org/) I have to specifically force the gpu by disabling the gpu black list --ignore-gpu-blacklist. This might be a valid questions for @ludei. Here is the link to the list of chromium flags:

http://peter.sh/experiments/chromium-command-line-switches/

agamemnus commented 10 years ago

Ok, thanks. Just my Note 2, 4.4.2.. theoretically Webview+ is even more advanced than 4.4.2's Chromium. Possibly it's a bug in Chromium, but not sure how to rebuild with a different Chromium base.

agamemnus commented 10 years ago

So, this might be a Chromium bug, or at least partly... but it's not consistent across devices. Perhaps something to do with GPU hardware acceleration of shadows plus a Chromium bug somewhere? I made a bug report if you'd like to star it...

https://code.google.com/p/chromium/issues/detail?id=403223

ludei commented 10 years ago

I'll keep this issue open until the next major release of Webview+ that may solve this problem.