2015-07-28 79 views
1

問題Canvas drawImage的優化如何工作?

我試圖用Canvas創建一個粒子系統。有趣的是,drawImage方法非常快。即使使用drawImage,我也能以60 fps達到90.000個粒子。但是,那只是相同的圖像。當我爲粒子使用不同的圖像時,性能顯着下降。然後我改變了圖像的順序,這樣e。 G。首先繪製image1的所有粒子,然後繪製所有image2等,並且性能再次良好。

問題

有誰知道這是爲什麼?人們必須考慮drawImage中是否有一些內部緩存機制?

代碼

這裏的示例代碼:

import javafx.animation.AnimationTimer; 
import javafx.application.Application; 
import javafx.scene.Node; 
import javafx.scene.Scene; 
import javafx.scene.SnapshotParameters; 
import javafx.scene.canvas.Canvas; 
import javafx.scene.canvas.GraphicsContext; 
import javafx.scene.image.Image; 
import javafx.scene.image.WritableImage; 
import javafx.scene.layout.BorderPane; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.Rectangle; 
import javafx.stage.Stage; 

public class CanvasExample extends Application { 

    GraphicsContext gc; 
    double width = 800; 
    double height = 600; 
    Image[] images; 

    @Override 
    public void start(Stage primaryStage) { 

     Canvas canvas = new Canvas(width, height); 
     gc = canvas.getGraphicsContext2D(); 

     BorderPane root = new BorderPane(); 
     root.setCenter(canvas); 

     Scene scene = new Scene(root, width, height); 
     scene.setFill(Color.BEIGE); 

     primaryStage.setScene(scene); 
     primaryStage.show(); 

     createImages(255); 

     AnimationTimer loop = new AnimationTimer() { 

      double prev = 0; 
      double frameCount = 0; 
      double fps = 0; 

      @Override 
      public void handle(long now) { 

       // very basic frame counter 
       if(now - prev > 1_000_000_000) { 

        System.out.println("FPS: " + frameCount); 

        fps = frameCount; 

        prev = now; 
        frameCount = 0; 

       } else { 
        frameCount++; 
       } 

       // clear canvas 
       gc.setFill(Color.BLACK); 
       gc.fillRect(0, 0, width, height); 

       // paint images 
       int numIterations = 90000; 
       for(int i=0; i < numIterations; i++) { 

        int index = i % 2; // <==== change here: i % 1 is fast, i % 2 is slow 

        gc.drawImage(images[ index], 100, 100); 

       } 

       gc.setFill(Color.WHITE); 
       gc.fillText("fps: " + fps, 0, 10); 
      } 

     }; 

     loop.start(); 

    } 

    public void createImages(int count) { 

     Rectangle rect = new Rectangle(10,10); 
     rect.setFill(Color.RED); 

     images = new Image[count]; 

     for(int i=0; i < count; i++) { 
      images[i] = createImage(rect); 
     } 

    } 

    /** 
    * Snapshot an image out of a node, consider transparency. 
    * 
    * @param node 
    * @return 
    */ 
    public Image createImage(Node node) { 

     WritableImage wi; 

     SnapshotParameters parameters = new SnapshotParameters(); 
     parameters.setFill(Color.TRANSPARENT); 

     int imageWidth = (int) node.getBoundsInLocal().getWidth(); 
     int imageHeight = (int) node.getBoundsInLocal().getHeight(); 

     wi = new WritableImage(imageWidth, imageHeight); 
     node.snapshot(parameters, wi); 

     return wi; 

    } 

    public static void main(String[] args) { 
     launch(args); 
    } 

} 

這是非常基本的。圖像是從一個矩形創建的,然後放入一個數組中,並在AnimationTimer循環中,圖像在畫布上繪製numIterations-times。

當您使用index = i % 1,我。即一遍又一遍的相同圖像,然後在我的系統上的fps是60幀/秒。如果您使用index = i % 2,我。即交替的圖像,那麼我的系統上的fps是14 fps。這是相當大的差異。

非常感謝您的幫助!

回答

1

在畫布上是如何工作的

帆布保持一個緩衝背景。每次您向畫布發出命令(如繪製圖像)時,該命令都會附加到緩衝區。在下一個渲染脈衝中,通過處理每個命令並將它們渲染爲紋理來刷新緩衝區。紋理被髮送到顯卡上,並將它顯示在屏幕上。在源代碼

跟蹤帆布操作

注:此處引用的鏈接的代碼已經過時,而不是正式的,因爲它是一個補丁包,但是這是最簡單的代碼來交叉引用網上和實現類似於(或相同)作爲JDK中的官方代碼。所以只需追蹤它:

您調用drawImage和GraphicsContext writes an imagethe buffer

對於JavaFX圖形系統的下一個脈衝或快照請求,緩衝區被清空,issuing render commandsrender command for the image使用cached texture from a resource factory來渲染圖像。

爲什麼你的應用程序變慢(我真的不知道)

我想也許,當你備用圖像,你是不是保持一個「活」圖像中的場景呈現爲部分因此紋理緩存會清除舊圖像,導致系統抖動併爲圖像重新創建紋理(just a guess)。但是,我在調試器中逐步完成了這個過程(在您的應用程序運行幾秒鐘後,移除屏幕上的fps標籤並在BaseShaderGraphics.drawTexture中設置斷點)。你會看到相同的緩存紋理被重用。紋理緩存似乎表現良好,併爲每個圖像緩存紋理,所以我真的不知道你觀察到的放緩的根本原因是什麼。