作为我在这里解释的问题的解决方案,我决定编写一个与我的程序一起运行的图像渲染器,这将允许我查询屏幕的特定区域。
渲染器本身工作正常,但它仍然会大大降低帧速率(但不如 LibGDX 截屏那么多)。但是,由于这是一个单独的系统,我可以创建一个Thread
,然后使用LinkedBlockingQueue
. 我试过了,它几乎就像一个魅力。
我之所以说这几乎像一个魅力,是因为这就是帧速率图的样子:
虽然该程序大部分时间以 60 fps 左右的速度运行,但每 0.2 秒就会出现一个仅持续几帧的延迟峰值,而且它的时间足以让人注意到。由于这是使用多线程进行图像渲染的结果,并且根据我的经验,我发现多处理可以提供更好的结果,我想知道这是否是多处理的一个好主意,因为我也知道之间的通信进程往往更麻烦,我想避免在不确定是否值得的情况下重新设计整个渲染器以使用多处理。
TL;DR:我一方面有神经网络,另一方面有实时图像渲染。分配负载、多线程或多处理的更好选择是什么?
编辑:通常我不会太在意这些滞后尖峰,因为它们几乎不引人注意,但我也在使用物理引擎,这些滞后尖峰非常剧烈,足以弄乱它,这就是为什么我想要获得最平滑的FPS 可能。
编辑 2:我渲染图像的方式如下:我有一个WorldRenderer
实现Runnable
. 在我的“主要”功能(不是真正的主要功能,但没关系)中,我创建了一个Thread
并立即启动它。在类的run
方法中WorldRenderer
,我有一个while
循环,它从 a 获取数据LinkedBlockingQueue
并将其传递给另一个方法,以便它处理该数据。数据包含Map<String, Object>
我放置信号的位置(告诉渲染器要做什么)和RenderQuery
实例,这是一个只包含一堆不同形状的类。然后它调用该render
方法。此方法检查每一帧是否是第 n 帧,如果是,则通过迭代ArrayList<RenderQuery>
例如,每个形状都是逐像素绘制的。我实现了它,以便它每帧将固定数量的形状绘制到后缓冲区,当它没有更多的形状时,它会将图像推送到前缓冲区,但结果却是滞后的。
这是WorldRenderer
课程:
package me.kolterdyx.artificiallife.graphics.renderer;
import com.badlogic.gdx.graphics.Color;
import me.kolterdyx.artificiallife.graphics.renderer.shapes.CircleShape;
import me.kolterdyx.artificiallife.graphics.renderer.shapes.LineShape;
import me.kolterdyx.artificiallife.graphics.renderer.shapes.RectShape;
import me.kolterdyx.artificiallife.graphics.renderer.shapes.Shape;
import me.kolterdyx.artificiallife.level.entities.Entity;
import javax.imageio.ImageIO;
import java.awt.RenderingHints;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
public class WorldRenderer implements Runnable {
private int[][][] backBuffer, frontBuffer;
private ArrayList<RenderQuery> renderQueries;
private final int width, height;
private ArrayList<Entity> entities;
private final BlockingQueue dataQueue;
private boolean exit = false;
private int counter;
private ArrayList<RenderQuery> extraQueries;
public WorldRenderer(BlockingQueue dataQueue, ArrayList<Entity> entities, int width, int height){
this.dataQueue = dataQueue;
frontBuffer = new int[width][height][3];
backBuffer = new int[width][height][3];
renderQueries = new ArrayList<>();
extraQueries = new ArrayList<>();
this.width = width;
this.height = height;
this.entities = entities;
}
private void setEntities(ArrayList<Entity> array){
// System.out.println("Received "+entities.size()+" entities");
entities = array;
}
private void getSignal(int signal){
System.out.println("Received signal: "+signal);
switch (signal){
case 0 -> {
saveImage();
}
case -1 -> {
exit = true;
}
}
}
public int[][][] getArea(int x, int y, int width, int height){
return null;
}
public int[][][] getBuffer() {
return frontBuffer;
}
private void setPixel(int x, int y, Color color){
int[] colorArray = new int[]{(int) (color.r*255), (int) (color.g*255), (int) (color.b*255)};
backBuffer[x+width/2][y+height/2] = colorArray;
}
public void renderShape(Shape shape){
switch (shape.getType()){
case CIRCLE -> {
CircleShape circle = (CircleShape) shape;
int radius = circle.getRadius();
int ox = (int) circle.getPos().x;
int oy = (int) circle.getPos().y;
if (circle.isFilled()){
for (int x = -radius; x < radius; x++){
int height = (int)Math.sqrt(radius*radius - x*x);
for (int y = -height; y < height; y++){
setPixel(x+ox, y+oy, circle.getColor());
}
}
} else {
for (float t=0;t<Math.PI*2; t+= Math.PI*2/20f){
int x = (int) (radius*Math.cos(t));
int y = (int) (radius*Math.sin(t));
setPixel(x+ox, y+oy, circle.getColor());
}
}
}
case RECT -> {
RectShape rect = (RectShape) shape;
int ox = (int) rect.getPos().x;
int oy = (int) rect.getPos().y;
if (rect.isFilled()) {
for (int x = ox; x < rect.getSize().x+ox; x++) {
for (int y = oy; y < rect.getSize().y+oy; y++) {
setPixel(x, y, rect.getColor());
}
}
} else {
for (int x=ox; x < rect.getSize().x+ox; x++){
for (int i=0; i<rect.getThickness(); i++){
setPixel(x, oy+i, rect.getColor());
setPixel(x, (int) (oy+rect.getSize().y)-i, rect.getColor());
}
}
for (int y=oy; y < rect.getSize().y+oy; y++){
for (int i=0; i<rect.getThickness(); i++) {
setPixel(ox+i, y, rect.getColor());
setPixel((int) (ox + rect.getSize().x)-i, y, rect.getColor());
}
}
}
}
case LINE -> {
LineShape line = (LineShape) shape;
}
}
}
public void addRenderQuery(RenderQuery query){
renderQueries.add(query);
}
public void render(){
counter++;
if (renderQueries.size() == 0){
for (Entity entity : entities){
if (entity.hasRenderQuery()){
addRenderQuery(entity.getRenderQuery());
}
}
for (RenderQuery query : extraQueries){
addRenderQuery(query);
}
frontBuffer = backBuffer.clone();
backBuffer = new int[width][height][3];
}
for (int i=0; i<1;i++){
if (renderQueries.size()==0)break;
renderQuery(renderQueries.get(0));
renderQueries.remove(0);
}
}
private void renderQuery(RenderQuery query) {
for (Shape shape : query.getShapes()){
try {
renderShape(shape);
} catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}
}
}
public void saveImage(int[][][] imagePixels){
int height = imagePixels.length;
int width = imagePixels[0].length;
int[][] flat = new int[width*height][3];
// Flatten the image into a 2D array.
int index=0;
for(int row=0; row<height; row++) {
for(int col=0; col<width; col++) {
for(int rgb=0; rgb<3; rgb++) {
flat[index][rgb]=imagePixels[row][col][rgb];
}
index++;
}
}
int[] outPixels = new int[flat.length*flat[0].length];
for(int i=0; i < flat.length; i++){
outPixels[i*3] = flat[i][0];
outPixels[i*3+1] = flat[i][1];
outPixels[i*3+2] = flat[i][2];
}
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
WritableRaster raster = (WritableRaster) image.getData();
raster.setPixels(0, 0, width, height, outPixels);
image.setData(raster);
File outputFile = new File("image.png");
try {
ImageIO.write(rotateImage(image, 270), "png", outputFile);
} catch (IOException e) {
e.printStackTrace();
}
}
private BufferedImage rotateImage(BufferedImage buffImage, double angle) {
double radian = Math.toRadians(angle);
double sin = Math.abs(Math.sin(radian));
double cos = Math.abs(Math.cos(radian));
int width = buffImage.getWidth();
int height = buffImage.getHeight();
int nWidth = (int) Math.floor((double) width * cos + (double) height * sin);
int nHeight = (int) Math.floor((double) height * cos + (double) width * sin);
BufferedImage rotatedImage = new BufferedImage(
nWidth, nHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = rotatedImage.createGraphics();
graphics.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphics.translate((nWidth - width) / 2, (nHeight - height) / 2);
// rotation around the center point
graphics.rotate(radian, (double) (width / 2), (double) (height / 2));
graphics.drawImage(buffImage, 0, 0, null);
graphics.dispose();
return rotatedImage;
}
public void saveImage(){
System.out.println("Saving image");
saveImage(frontBuffer);
}
@Override
public void run() {
try {
while (!exit){
getData(dataQueue.take());
dataQueue.clear();
render();
}
System.out.println("stopped");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void getData(Object object) {
if (object instanceof Map<?,?> data){
if (data.containsKey("signal")){
getSignal((int) data.get("signal"));
}
if (data.containsKey("entities")){
setEntities((ArrayList<Entity>) data.get("entities"));
}
if (data.containsKey("extraQueries")){
extraQueries = (ArrayList<RenderQuery>) data.get("extraQueries");
}
}
}
}
编辑 4:删除了编辑 3,因为它实际上并没有解决问题。