2013-05-24 22 views
12

Chúng tôi đang làm việc với canvas HTML5, hiển thị nhiều hình ảnh cùng một lúc.Tại sao chrome đấu tranh để hiển thị nhiều hình ảnh trên canvas khi các trình duyệt khác không hiển thị?

Điều này hoạt động khá tốt nhưng gần đây chúng tôi gặp sự cố với Chrome.

Khi vẽ hình ảnh trên canvas, bạn dường như đạt đến một điểm nhất định nơi hiệu suất giảm rất nhanh.

Nó không phải là một hiệu ứng chậm, có vẻ như bạn đi từ 60fps đến 2-4fps.

Dưới đây là một số mã sinh sản:

// Helpers 
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/random 
function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } 
// http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ 
window.requestAnimFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000/60); }; })(); 
// https://github.com/mrdoob/stats.js 
var Stats = function() { var e = Date.now(), t = e; var n = 0, r = Infinity, i = 0; var s = 0, o = Infinity, u = 0; var a = 0, f = 0; var l = document.createElement("div"); l.id = "stats"; l.addEventListener("mousedown", function (e) { e.preventDefault(); y(++f % 2) }, false); l.style.cssText = "width:80px;opacity:0.9;cursor:pointer"; var c = document.createElement("div"); c.id = "fps"; c.style.cssText = "padding:0 0 3px 3px;text-align:left;background-color:#002"; l.appendChild(c); var h = document.createElement("div"); h.id = "fpsText"; h.style.cssText = "color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px"; h.innerHTML = "FPS"; c.appendChild(h); var p = document.createElement("div"); p.id = "fpsGraph"; p.style.cssText = "position:relative;width:74px;height:30px;background-color:#0ff"; c.appendChild(p); while (p.children.length < 74) { var d = document.createElement("span"); d.style.cssText = "width:1px;height:30px;float:left;background-color:#113"; p.appendChild(d) } var v = document.createElement("div"); v.id = "ms"; v.style.cssText = "padding:0 0 3px 3px;text-align:left;background-color:#020;display:none"; l.appendChild(v); var m = document.createElement("div"); m.id = "msText"; m.style.cssText = "color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px"; m.innerHTML = "MS"; v.appendChild(m); var g = document.createElement("div"); g.id = "msGraph"; g.style.cssText = "position:relative;width:74px;height:30px;background-color:#0f0"; v.appendChild(g); while (g.children.length < 74) { var d = document.createElement("span"); d.style.cssText = "width:1px;height:30px;float:left;background-color:#131"; g.appendChild(d) } var y = function (e) { f = e; switch (f) { case 0: c.style.display = "block"; v.style.display = "none"; break; case 1: c.style.display = "none"; v.style.display = "block"; break } }; var b = function (e, t) { var n = e.appendChild(e.firstChild); n.style.height = t + "px" }; return { REVISION: 11, domElement: l, setMode: y, begin: function() { e = Date.now() }, end: function() { var f = Date.now(); n = f - e; r = Math.min(r, n); i = Math.max(i, n); m.textContent = n + " MS (" + r + "-" + i + ")"; b(g, Math.min(30, 30 - n/200 * 30)); a++; if (f > t + 1e3) { s = Math.round(a * 1e3/(f - t)); o = Math.min(o, s); u = Math.max(u, s); h.textContent = s + " FPS (" + o + "-" + u + ")"; b(p, Math.min(30, 30 - s/100 * 30)); t = f; a = 0 } return f }, update: function() { e = this.end() } } } 
// Firefox events suck 
function getOffsetXY(eventArgs) { return { X: eventArgs.offsetX == undefined ? eventArgs.layerX : eventArgs.offsetX, Y: eventArgs.offsetY == undefined ? eventArgs.layerY : eventArgs.offsetY }; } 
function getWheelDelta(eventArgs) { if (!eventArgs) eventArgs = event; var w = eventArgs.wheelDelta; var d = eventArgs.detail; if (d) { if (w) { return w/d/40 * d > 0 ? 1 : -1; } else { return -d/3; } } else { return w/120; } } 

// Reproduction Code 
var stats = new Stats(); 
document.body.appendChild(stats.domElement); 

var masterCanvas = document.getElementById('canvas'); 
var masterContext = masterCanvas.getContext('2d'); 

var viewOffsetX = 0; 
var viewOffsetY = 0; 
var viewScaleFactor = 1; 
var viewMinScaleFactor = 0.1; 
var viewMaxScaleFactor = 10; 

var mouseWheelSensitivity = 10; //Fudge Factor 
var isMouseDown = false; 
var lastMouseCoords = null; 

var imageDimensionPixelCount = 25; 
var paddingPixelCount = 2; 
var canvasDimensionImageCount = 50; 
var totalImageCount = Math.pow(canvasDimensionImageCount, 2); 

var images = null; 

function init() { 
    images = createLocalImages(totalImageCount, imageDimensionPixelCount); 
    initInteraction(); 
    renderLoop(); 
} 

function initInteraction() { 
    var handleMouseDown = function (eventArgs) { 
     isMouseDown = true; 
     var offsetXY = getOffsetXY(eventArgs); 

     lastMouseCoords = [ 
      offsetXY.X, 
      offsetXY.Y 
     ]; 
    }; 
    var handleMouseUp = function (eventArgs) { 
     isMouseDown = false; 
     lastMouseCoords = null; 
    } 

    var handleMouseMove = function (eventArgs) { 
     if (isMouseDown) { 
      var offsetXY = getOffsetXY(eventArgs); 
      var panX = offsetXY.X - lastMouseCoords[0]; 
      var panY = offsetXY.Y - lastMouseCoords[1]; 

      pan(panX, panY); 

      lastMouseCoords = [ 
       offsetXY.X, 
       offsetXY.Y 
      ]; 
     } 
    }; 

    var handleMouseWheel = function (eventArgs) { 
     var mouseX = eventArgs.pageX - masterCanvas.offsetLeft; 
     var mouseY = eventArgs.pageY - masterCanvas.offsetTop;     
     var zoom = 1 + (getWheelDelta(eventArgs)/mouseWheelSensitivity); 

     zoomAboutPoint(mouseX, mouseY, zoom); 

     if (eventArgs.preventDefault !== undefined) { 
      eventArgs.preventDefault(); 
     } else { 
      return false; 
     } 
    } 

    masterCanvas.addEventListener("mousedown", handleMouseDown, false); 
    masterCanvas.addEventListener("mouseup", handleMouseUp, false); 
    masterCanvas.addEventListener("mousemove", handleMouseMove, false); 
    masterCanvas.addEventListener("mousewheel", handleMouseWheel, false); 
    masterCanvas.addEventListener("DOMMouseScroll", handleMouseWheel, false); 
} 

function pan(panX, panY) { 
    masterContext.translate(panX/viewScaleFactor, panY/viewScaleFactor); 

    viewOffsetX -= panX/viewScaleFactor; 
    viewOffsetY -= panY/viewScaleFactor; 
} 

function zoomAboutPoint(zoomX, zoomY, zoomFactor) { 
    var newCanvasScale = viewScaleFactor * zoomFactor; 

    if (newCanvasScale < viewMinScaleFactor) { 
     zoomFactor = viewMinScaleFactor/viewScaleFactor; 
    } else if (newCanvasScale > viewMaxScaleFactor) { 
     zoomFactor = viewMaxScaleFactor/viewScaleFactor; 
    } 

    masterContext.translate(viewOffsetX, viewOffsetY); 
    masterContext.scale(zoomFactor, zoomFactor); 

    viewOffsetX = ((zoomX/viewScaleFactor) + viewOffsetX) - (zoomX/(viewScaleFactor * zoomFactor)); 
    viewOffsetY = ((zoomY/viewScaleFactor) + viewOffsetY) - (zoomY/(viewScaleFactor * zoomFactor)); 
    viewScaleFactor *= zoomFactor; 

    masterContext.translate(-viewOffsetX, -viewOffsetY); 
} 

function renderLoop() { 
    clearCanvas(); 
    renderCanvas(); 
    stats.update(); 
    requestAnimFrame(renderLoop); 
} 

function clearCanvas() { 
    masterContext.clearRect(viewOffsetX, viewOffsetY, masterCanvas.width/viewScaleFactor, masterCanvas.height/viewScaleFactor); 
} 

function renderCanvas() { 
    for (var imageY = 0; imageY < canvasDimensionImageCount; imageY++) { 
     for (var imageX = 0; imageX < canvasDimensionImageCount; imageX++) { 
      var x = imageX * (imageDimensionPixelCount + paddingPixelCount); 
      var y = imageY * (imageDimensionPixelCount + paddingPixelCount); 

      var imageIndex = (imageY * canvasDimensionImageCount) + imageX; 
      var image = images[imageIndex]; 

      masterContext.drawImage(image, x, y, imageDimensionPixelCount, imageDimensionPixelCount); 
     } 
    } 
} 

function createLocalImages(imageCount, imageDimension) { 
    var tempCanvas = document.createElement('canvas'); 
    tempCanvas.width = imageDimension; 
    tempCanvas.height = imageDimension; 
    var tempContext = tempCanvas.getContext('2d'); 

    var images = new Array(); 

    for (var imageIndex = 0; imageIndex < imageCount; imageIndex++) { 
     tempContext.clearRect(0, 0, imageDimension, imageDimension); 
     tempContext.fillStyle = "rgb(" + getRandomInt(0, 255) + ", " + getRandomInt(0, 255) + ", " + getRandomInt(0, 255) + ")"; 
     tempContext.fillRect(0, 0, imageDimension, imageDimension); 

     var image = new Image(); 
     image.src = tempCanvas.toDataURL('image/png'); 

     images.push(image); 
    } 

    return images; 
} 

// Get this party started 
init(); 

Và một liên kết jsfiddle cho niềm vui tương tác của bạn: http://jsfiddle.net/BtyL6/14/

này được vẽ 50px x 50px hình ảnh trong một 50 x 50 (2500) lưới trên vải . Tôi cũng đã nhanh chóng thử với hình ảnh 25px x 25px và 50 x 50 (2500).

Chúng tôi có các ví dụ địa phương khác xử lý hình ảnh lớn hơn và số lượng hình ảnh lớn hơn và trình duyệt khác bắt đầu đấu tranh với những giá trị cao hơn này.

Để kiểm tra nhanh, tôi đã kích hoạt mã trong hình ảnh js fiddle đến 100px x 100px và 100 x 100 (10000) và vẫn chạy ở tốc độ 16fps khi thu nhỏ hoàn toàn. (Lưu ý: Tôi phải hạ thấp viewMinScaleFactor xuống 0,01 để vừa với tất cả khi thu nhỏ.)

Mặt khác Chrome có vẻ bị giới hạn và FPS giảm từ 60 xuống còn 2-4.


Dưới đây là một số thông tin về những gì chúng tôi đã cố gắng và kết quả:

Chúng tôi đã cố gắng sử dụng setInterval hơn requestAnimationFrame.

Nếu bạn tải 10 hình ảnh và vẽ chúng 250 lần mỗi hình thay vì 2500 hình ảnh được vẽ một lần thì vấn đề sẽ biến mất. Điều này dường như chỉ ra rằng chrome đang tấn công một số loại giới hạn/kích hoạt như lượng dữ liệu lưu trữ về hiển thị.

Chúng tôi đã chọn lọc (không hiển thị hình ảnh ngoài phạm vi hình ảnh) trong các ví dụ phức tạp hơn và điều này giúp giải pháp không phải là giải pháp vì chúng tôi có thể hiển thị tất cả hình ảnh cùng một lúc.

Chúng tôi chỉ hiển thị hình ảnh nếu có thay đổi trong mã cục bộ của chúng tôi, điều này giúp (khi không có gì thay đổi, rõ ràng) nhưng không phải là giải pháp đầy đủ vì canvas nên tương tác.

Trong mã ví dụ, chúng tôi đang tạo hình ảnh bằng canvas, nhưng mã cũng có thể chạy khi nhấn dịch vụ web để cung cấp hình ảnh và hành vi tương tự (chậm) sẽ được xem.


Chúng tôi thấy rất khó để tìm kiếm sự cố này, hầu hết kết quả là từ một vài năm trước và đã lỗi thời.

Nếu có thêm thông tin nào hữu ích thì vui lòng hỏi!


EDIT: Đã thay đổi js fiddle URL để phản ánh cùng mã như trong câu hỏi. Bản thân mã không thực sự thay đổi, chỉ là định dạng. Nhưng tôi muốn nhất quán.


EDIT: jsfiddle cập nhật và và mã với css để ngăn việc chọn và gọi requestAnim sau khi render vòng lặp được thực hiện.

+0

Tôi đã thử nghiệm mã trong Chrome 27.0.1453.94 và 29.0.1516.3 canary. Trong ** Chrome canary ** Tôi không có vấn đề về hiệu suất. Ngay cả trong một máy tính cấp thấp, tôi nhận được 15 khung hình/giây với tất cả các hình ảnh trên màn hình. Ví dụ, Internet Explorer 10 với mức thu phóng tối thiểu hiệu suất vẫn ở trên 5 khung hình/giây. Đó rõ ràng là một vấn đề thực tế và dường như các nhà phát triển Chrome đã sửa nó cho các bản phát hành chính thức tiếp theo. –

+0

Chỉ cần chỉ ra rằng mã trong Fiddle có nhiều vấn đề. Mã này vẫn còn sống! Đôi khi nó làm việc một cách duyên dáng và chỉ cần bấm chạy một lần nữa trang bị đóng băng, ngay cả trong Canary như tôi đã đăng trước đó. Ken đã làm một công việc tốt đẹp trong câu trả lời của anh, btw. –

+0

Xin chào gfcarv, đồng nghiệp của tôi đã được thử nghiệm ở Canary và cũng tìm thấy nó được cải thiện. Hy vọng rằng nó chỉ là một hồi quy và nó sẽ được sửa chữa sớm. Như đã đề cập ở trên, mã này chỉ đơn giản là sinh sản của vấn đề. Mã "thực" có nhiều tính năng hơn để cải thiện hiệu suất trong các điều kiện nhất định. Tuy nhiên, ngay cả những thứ như culling và chỉ vẽ lại khi cần thiết không giúp giảm mức sử dụng tài nguyên tối đa và không ngăn Chrome "kích hoạt" chế độ chậm này. Thật không may mã của Ken giảm bớt vấn đề bằng cách không còn thực hiện các hành động cần thiết, cụ thể là vẽ hình ảnh vào canvas. – AndyJ

Trả lời

1

Đây là một lỗi chính đáng trong chrome.

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

Nó đã được cố định và phải ở trong một thông cáo đường chính chrome sớm.

+0

Cảm ơn bạn đã cập nhật. Có thể xác nhận nó đã hoạt động tốt trong phiên bản Canary 31. – K3N

+0

Cảm ơn bạn đã dùng thử! – AndyJ

6

Trong Canary mã này đóng băng trên máy tính của tôi. Khi đến lý do tại sao điều này xảy ra trong Chrome, câu trả lời đơn giản là nó sử dụng triển khai khác với f.ex. FF. Chi tiết chuyên sâu tôi không biết, nhưng rõ ràng là có chỗ để tối ưu hóa việc triển khai trong lĩnh vực này.

tôi có thể cung cấp cho một số tip tuy nhiên về cách bạn thể tối ưu hóa mã số để làm cho nó chạy trong Chrome cũng :-)

Có một vài điều ở đây:

  • Bạn đang lưu trữ mỗi khối màu như hình ảnh. Điều này dường như có tác động hiệu suất rất lớn trên Canary/Chrome.
  • Bạn đang kêu gọi requestAnimationFrame vào đầu vòng lặp
  • Bạn đang bù trừ và render ngay cả khi không có thay đổi

Cố gắng (giải quyết các điểm):

  • Nếu bạn chỉ cần khối màu sắc rắn, vẽ chúng trực tiếp bằng cách sử dụng fillRect() thay vào đó và giữ các chỉ mục màu trong một mảng (thay vì hình ảnh). Ngay cả khi bạn kéo chúng vào một khung hình ngoài màn hình, bạn sẽ chỉ phải vẽ một bản vẽ với canvas chính thay vì nhiều thao tác vẽ hình ảnh.
  • Di chuyển requestAnimationFrame vào cuối khối mã để tránh xếp chồng.
  • Sử dụng cờ bẩn để ngăn chặn vẽ không cần thiết:

tôi sửa đổi mã một chút - tôi sửa đổi nó để sử dụng màu sắc rắn để chứng minh nơi tác động hiệu suất là trong Chrome/Canary.

tôi đặt một lá cờ bẩn trong phạm vi toàn cầu là đúng (để làm cho cảnh ban đầu) được thiết lập là true mỗi lần di chuyển chuột xảy ra:

//global 
var isDirty = true; 

//mouse move handler 
var handleMouseMove = function (eventArgs) { 

    // other code 

    isDirty = true; 

    // other code 
}; 

//render loop 
function renderLoop() { 
    if (isDirty) { 
     clearCanvas(); 
     renderCanvas(); 
    } 
    stats.update(); 
    requestAnimFrame(renderLoop); 
} 

//in renderCanvas at the end: 
function renderCanvas() { 
    // other code 
    isDirty = false; 
} 

Bạn sẽ tất nhiên cần phải kiểm tra cẩn thận cho cờ isDirty ở nơi khác và cũng giới thiệu thêm tiêu chí nếu nó bị xóa tại thời điểm sai. Tôi sẽ lưu trữ vị trí cũ của con chuột và chỉ (trong di chuyển chuột) nếu nó thay đổi thiết lập cờ bẩn - Tôi đã không sửa đổi phần này mặc dù.

Như bạn có thể thấy, bạn sẽ có thể chạy điều này trong Chrome và trong FF ở FPS cao hơn.

Tôi cũng giả định (tôi đã không kiểm tra) rằng bạn có thể tối ưu hóa chức năng clearCanvas() bằng cách chỉ vẽ các khoảng trống/khoảng trống thay vì xóa toàn bộ canvas. Nhưng điều đó cần được kiểm tra.

Thêm CSS-quy tắc để ngăn chặn sự vải được lựa chọn khi sử dụng chuột:

Đối với tối ưu hóa hơn nữa trong trường hợp như thế này, đó là sự kiện thúc đẩy , bạn không thực sự cần một vòng lặp hoạt hình ở tất cả. Bạn chỉ có thể gọi lại khi coords hoặc mouse-wheel thay đổi.

Modification:
http://jsfiddle.net/BtyL6/10/

+0

Xin chào Ken cảm ơn vì nỗ lực và trả lời chi tiết! Các khối màu được lưu trữ dưới dạng hình ảnh vì chúng tôi đang vẽ hình ảnh, thay vì chúng tôi đang vẽ các khối màu, mã thực sử dụng ảnh. Tôi đã thử nghiệm với requestAnim di chuyển, nó không có sự khác biệt nhưng tôi đã thay đổi để có nó ở cuối bây giờ như cả MDN và MSDN cho thấy điều đó. Như đã đề cập trong bài đăng gốc, mã thực tế đã vẽ lại và phân loại được thực hiện. Những điều này giúp giảm mức sử dụng tài nguyên trong một số trường hợp, nhưng không giảm mức sử dụng tài nguyên tối đa. Một lần nữa, cảm ơn bạn đã trả lời! – AndyJ

+0

Không có vấn đề @AndyJ. Bạn đã cố gắng lưu bản vẽ hình ảnh vào canvas ngoài màn hình ("kết xuất trước") và trích xuất từ ​​đó vào khung chính bằng một thao tác đơn lẻ chưa? – K3N

+0

Một trong những ví dụ của chúng tôi, chúng tôi đã làm tại địa phương và điều đó dường như không giúp ích gì. Vấn đề dường như được kích hoạt bởi chrome vẽ một lượng dữ liệu duy nhất nhất định vào canvas ở bất kỳ đâu. Những gì tôi có ý nghĩa bởi "dữ liệu duy nhất" là nếu bạn tải cùng một URL vào một hình ảnh và vẽ nó hơn và hơn, hoặc thậm chí 10 URL tương tự thì vấn đề không xảy ra. Tuy nhiên nếu bạn tải 2.500 URL duy nhất thì vấn đề xảy ra. Điều này xảy ra khi URL ở bên ngoài hoặc được tạo bằng toDataURL. Chúng tôi có các ví dụ mà chúng tôi vẽ lên canvas, vẽ lên canvas ngoài màn hình, vẽ ra nhiều canvas ngoài màn hình. – AndyJ

Các vấn đề liên quan