如何优化Canvas drawImage工作? [英] How does the optimization of Canvas drawImage work?

查看:799
本文介绍了如何优化Canvas drawImage工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题



我试图使用Canvas创建一个粒子系统。有趣的是,drawImage的方法非常快。我得到90000颗粒,60 fps,甚至使用drawImage。然而,这只是用相同的图像。当我使用不同的图像的粒子,性能大大下降。然后我改变了图像的顺序,所以e。 G。首先绘制image1的所有粒子,然后绘制所有的image2等,并且性能再次良好。



问题
$ b

有没有人知道为什么?在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;
图像[]图像;

@Override
public void start(Stage primaryStage){

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

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

场景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){

//非常基本的框架计数器
if(now - prev> 1_000_000_000){

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

fps = frameCount;

prev = now;
framesCount = 0;

} else {
frameCount ++;
}

//清除画布
gc.setFill(Color.BLACK);
gc.fillRect(0,0,width,height);

//绘制图片
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 images [i] = createImage(rect);
}

}

/ **
*从节点中快照一个图像,考虑透明度。
*
* @param node
* @return
* /
public Image createImage(Node node){

WritableImage wi;

SnapshotParameters parameters = new SnapshotParameters();
parameter.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循环中,该图像在Canvas上绘制numIterations次。



当使用 index = i%1 ,i。 e。相同的图像一遍又一遍,那么在我的系统上的fps是60 fps。如果使用 index = i%2 ,i。 e。交替的图像,那么在我的系统的fps是在14 fps。这是一个很大的区别。



非常感谢您的帮助!

解决方案

画布的工作背景



Canvas保留缓冲区。每次向画布发出命令(例如绘制图像)时,命令都会附加到缓冲区。在下一个渲染脉冲上,通过处理每个命令并将它们渲染到纹理来刷新缓冲区。



在源代码中跟踪画布操作



注意:这里引用的链接代码是过时的,不是官方的,因为它是一个反向端口,但它是最简单的代码在线交叉引用,实现类似于(或相同)官方代码在JDK中。所以只需跟踪它:



您调用drawImage和GraphicsContext 写入图片 缓冲区



对于JavaFX图形系统的下一个脉冲或快照请求,缓冲区将被清空,发出渲染命令图像的渲染命令使用缓存纹理



p>

我想也许,当你交替图像,你不是保持一个活图像渲染在场景中作为场景图形的一部分,所以纹理缓存逐出旧的图片,导致系统抖动并重新创建图片的纹理(只是一个猜测)。但是,我在调试器(删除您的屏幕上的fps标签,并在您的应用程序已运行几秒钟后,在BaseShaderGraphics.drawTexture设置断点)的过程中。你会看到相同的缓存纹理被重用。纹理缓存看起来表现良好,做它的作业缓存每个图像的纹理,所以我真的不知道你观察到的减速的根本原因是什么。


Problem

I tried to create a particle system using a Canvas. Interestingly the method drawImage is very fast. I got to 90.000 particles at 60 fps, even using drawImage. However, that was only with the same image. When I used different images for the particles the performance dropped down considerably. Then I changed the order of the images, so that e. g. first all particles of image1 are drawn, then all of image2, etc and the performance was good again.

Question

Does anyone know why that is? Is there some internal caching mechanism in drawImage that one has to consider?

Code

Here's example code:

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);
    }

}

It's very basic. An image is created from a rectangle, then put into an array and in the AnimationTimer loop the image is drawn numIterations-times on the Canvas.

When you use index = i % 1, i. e. the same image over and over, then the fps are at 60 fps on my system. If you use index = i % 2, i. e. alternating images, then the fps are at 14 fps on my system. That's a considerable difference.

Thank you very much for the help!

解决方案

Background on how canvas works

Canvas keeps a buffer. Every time you issue a command to the canvas, such as drawing an image, the command is appended to the buffer. On the next render pulse, the buffer is flushed by processing each of the commands and rendering them to a texture. The texture is sent to the graphics card which will render it on the screen.

Tracing canvas operation in source code

Note: The linked code referenced here is outdated and not official as it is a backport, but it is the easiest code to cross-reference online and the implementation is similar (or the same) as the official code in the JDK. So just trace through it:

You invoke drawImage and the GraphicsContext writes an image to the buffer.

For the next pulse of the JavaFX graphics system or a snapshot request, the buffer is emptied, issuing render commands. The render command for the image uses a cached texture from a resource factory to render the image.

Why your app slows down (I don't actually know)

I thought perhaps, when you alternate images, you aren't keeping a "live" image to be rendered in the scene as part of the scene graph, so the texture cache evicts the old image, resulting in the system thrashing and recreating the texture for the image (just a guess). However, I stepped through the process in a debugger (remove your on screen fps label and set a breakpoint in BaseShaderGraphics.drawTexture after your app has been running for a few seconds). You will see the same cached textures being reused. The texture cache seems well behaved and doing it's job caching the textures for each image, so I really don't know what the root cause of your observed slowdown would be.

这篇关于如何优化Canvas drawImage工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆