【发布时间】:2016-02-12 11:46:18
【问题描述】:
我想创建一个在画布中执行许多渲染的应用程序。 普通的 JavaFX 方式会阻塞 GUI:在下面的应用程序代码中按下按钮非常困难(使用 Java 8 运行)。
我在网上搜索过,JavaFX 不支持后台渲染:所有的渲染操作(如strokeLine)都存储在缓冲区中,稍后在JavaFX 应用程序线程中执行。所以我什至不能使用两个画布并在渲染后进行交换。
此外,javafx.scene.Node.snapshot(SnapshotParameters, WritableImage) 不能用于在后台线程中创建图像,因为它需要在 JavaFX 应用程序线程中运行,因此它也会阻塞 GUI。
有什么想法可以拥有一个具有许多渲染操作的非阻塞 GUI? (我只想在渲染以某种方式在后台执行或定期暂停时按下按钮等)
package canvastest;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.stage.Stage;
public class DrawLinieTest extends Application
{
int interations = 2;
double lineSpacing = 1;
Random rand = new Random(666);
List<Color> colorList;
final VBox root = new VBox();
Canvas canvas = new Canvas(1200, 800);
Canvas canvas2 = new Canvas(1200, 800);
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<?> drawShapesFuture;
{
colorList = new ArrayList<>(256);
colorList.add(Color.ALICEBLUE);
colorList.add(Color.ANTIQUEWHITE);
colorList.add(Color.AQUA);
colorList.add(Color.AQUAMARINE);
colorList.add(Color.AZURE);
colorList.add(Color.BEIGE);
colorList.add(Color.BISQUE);
colorList.add(Color.BLACK);
colorList.add(Color.BLANCHEDALMOND);
colorList.add(Color.BLUE);
colorList.add(Color.BLUEVIOLET);
colorList.add(Color.BROWN);
colorList.add(Color.BURLYWOOD);
}
public static void main(String[] args)
{
launch(args);
}
@Override
public void start(Stage primaryStage)
{
primaryStage.setTitle("Drawing Operations Test");
System.out.println("Init...");
// inital draw that creates a big internal operation buffer (GrowableDataBuffer)
drawShapes(canvas.getGraphicsContext2D(), lineSpacing);
drawShapes(canvas2.getGraphicsContext2D(), lineSpacing);
System.out.println("Start testing...");
new CanvasRedrawTask().start();
Button btn = new Button("test " + System.nanoTime());
btn.setOnAction((ActionEvent e) ->
{
btn.setText("test " + System.nanoTime());
});
root.getChildren().add(btn);
root.getChildren().add(canvas);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
private void drawShapes(GraphicsContext gc, double f)
{
System.out.println(">>> BEGIN: drawShapes ");
gc.clearRect(0, 0, gc.getCanvas().getWidth(), gc.getCanvas().getHeight());
gc.setLineWidth(10);
gc.setLineCap(StrokeLineCap.ROUND);
long time = System.nanoTime();
double w = gc.getCanvas().getWidth() - 80;
double h = gc.getCanvas().getHeight() - 80;
int c = 0;
for (int i = 0; i < interations; i++)
{
for (double x = 0; x < w; x += f)
{
for (double y = 0; y < h; y += f)
{
gc.setStroke(colorList.get(rand.nextInt(colorList.size())));
gc.strokeLine(40 + x, 10 + y, 10 + x, 40 + y);
c++;
}
}
}
System.out.println("<<< END: drawShapes: " + ((System.nanoTime() - time) / 1000 / 1000) + "ms");
}
public synchronized void drawShapesAsyc(final double f)
{
if (drawShapesFuture != null && !drawShapesFuture.isDone())
return;
drawShapesFuture = executorService.submit(() ->
{
drawShapes(canvas2.getGraphicsContext2D(), lineSpacing);
Platform.runLater(() ->
{
root.getChildren().remove(canvas);
Canvas t = canvas;
canvas = canvas2;
canvas2 = t;
root.getChildren().add(canvas);
});
});
}
class CanvasRedrawTask extends AnimationTimer
{
long time = System.nanoTime();
@Override
public void handle(long now)
{
drawShapesAsyc(lineSpacing);
long f = (System.nanoTime() - time) / 1000 / 1000;
System.out.println("Time since last redraw " + f + " ms");
time = System.nanoTime();
}
}
}
编辑 编辑代码以显示发送绘图操作并交换画布的后台线程不能解决问题!因为所有的渲染操作(如strokeLine)都存储在一个缓冲区中,稍后在JavaFX应用线程中执行。
【问题讨论】:
-
在过去,您绘制位图,然后在屏幕上进行 bitblt。
-
“所以我什至不能使用两个画布并在渲染后进行交换。” - 你应该可以这样做。 GraphicsContext 文档说:“一个 Canvas 只包含一个 GraphicsContext,并且只有一个缓冲区。如果它没有附加到任何场景,那么它可以被任何线程修改,只要它只从一个使用一次线程。”
-
@Romain Hippeau:“过去你在位图上绘制,然后在屏幕上进行 bitblt”:这就是我在旧版本中使用 BufferedImage 所做的。但现在我想要一个纯 JavaFX 解决方案。正如我所说:JavaFX 不允许在后台线程中创建快照。
-
@Marco13:好吧,我写的有点不对。您可以交换画布节点。但这并没有太大的区别。函数“drawShapes”只需要几毫秒。这就是我通过交换画布节点可以节省的全部内容。正如我所说:所有渲染操作(如strokeLine)都存储在缓冲区中,稍后在JavaFX应用程序线程中执行。
-
我明白了。有一些相关的问题,但似乎没有一个有用。在某些时候,必须进行绘制,据我所知,所有可能的解决方法(例如
Canvas#snapshot)都没有带来优势,因为它们(因此,实际的绘制)总是绑定到 JavaFX 线程.真遗憾。更丑陋的解决方法(比如使用 Swing 将这些东西绘制成BufferedImage- 他们 仍然做得对)在这里可能是不可接受的。