7

我注意到 Java & JOGL 和 C# & Tao.OpenGL 在将 PNG 从存储加载到内存时以及在加载 BufferedImage (java) 或 Bitmap (C# - 两者都是硬盘上的 PNG) '进入'OpenGL 时存在很大的性能差异.

这种差异非常大,所以我认为我做错了什么,但是经过大量搜索和尝试不同的加载技术后,我无法减少这种差异。

使用 Java,我在 248 毫秒内加载图像并在 728 毫秒内加载到 OpenGL 中。在 C# 上,加载图像需要 54 毫秒,加载/创建纹理需要 34 毫秒。

上面有问题的图像是一个包含透明度的 PNG,大小为 7200x255,用于 2D 动画精灵。我意识到尺寸真的很荒谬,并且正在考虑切割精灵,但是仍然存在很大的差异(并且令人困惑)。

在 Java 端,代码如下所示:

BufferedImage image = ImageIO.read(new File(fileName));
texture = TextureIO.newTexture(image, false);
texture.setTexParameteri(GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
texture.setTexParameteri(GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);

C# 代码使用:

Bitmap t = new Bitmap(fileName);

t.RotateFlip(RotateFlipType.RotateNoneFlipY);
Rectangle r = new Rectangle(0, 0, t.Width, t.Height);

BitmapData bd = t.LockBits(r, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

Gl.glBindTexture(Gl.GL_TEXTURE_2D, tID);
Gl.glTexImage2D(Gl.GL_TEXTURE_2D, 0, Gl.GL_RGBA, t.Width, t.Height, 0, Gl.GL_BGRA, Gl.GL_UNSIGNED_BYTE, bd.Scan0);
Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR);
Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR);

t.UnlockBits(bd);
t.Dispose();

经过大量测试后,我只能得出结论,Java/JOGL 在这里速度较慢 - PNG 读取可能没有那么快,或者我仍然做错了什么。

谢谢。

编辑2:

我发现创建格式为 TYPE_INT_ARGB_PRE 的新 BufferedImage 将 OpenGL 纹理加载时间减少了近一半 - 这包括必须创建新的 BufferedImage,从中获取 Graphics2D,然后将先前加载的图像渲染到它。

Edit3:5 种变体的基准测试结果。我写了一个小基准测试工具,下面的结果来自加载一组 33 个 png,大多数都很宽,5 倍。

testStart: ImageIO.read(file) -> TextureIO.newTexture(image)  
result: avg = 10250ms, total = 51251  
testStart: ImageIO.read(bis) -> TextureIO.newTexture(image)  
result: avg = 10029ms, total = 50147  
testStart: ImageIO.read(file) -> TextureIO.newTexture(argbImage)  
result: avg = 5343ms, total = 26717  
testStart: ImageIO.read(bis) -> TextureIO.newTexture(argbImage)  
result: avg = 5534ms, total = 27673  
testStart: TextureIO.newTexture(file)  
result: avg = 10395ms, total = 51979  

ImageIO.read(bis) 指的是 James Branigan 在下面的回答中描述的技术。argbImage 指的是我之前编辑中描述的技术:

img = ImageIO.read(file);
argbImg = new BufferedImage(img.getWidth(), img.getHeight(), TYPE_INT_ARGB_PRE);
g = argbImg.createGraphics();
g.drawImage(img, 0, 0, null);
texture = TextureIO.newTexture(argbImg, false);

任何更多的加载方法(来自文件的图像,或图像到 OpenGL)将不胜感激,我将更新这些基准。

4

5 回答 5

7

简短 的回答 JOGL 纹理类做的比必要的要多,我想这就是它们慢的原因。几天前我遇到了同样的问题,现在通过使用低级 API(glGenTextures、glBindTexture、glTexParameterf 和 glTexImage2D)加载纹理来修复它。加载时间从大约 1 秒减少到“没有明显延迟”,但我还没有进行任何系统分析。

长答案 如果您查看 JOGL TextureIO、TextureData 和 Texture 类的文档和源代码,您会注意到它们所做的不仅仅是将纹理上传到 GPU 上:

  • 处理不同的图像格式
  • 阿尔法预乘

我不确定其中哪一个需要更多时间。但在很多情况下,你知道你有什么样的图像数据可用,并且不需要做任何预乘。

无论如何,alpha 预乘功能在此类中完全放错了位置(从软件架构的角度来看),而且我没有找到任何方法来禁用它。尽管文档声称这是“数学上正确的方法”(我实际上并不相信这一点),但在很多情况下您不想使用 alpha 预乘,或者事先已经完成(例如性能原因)。

毕竟,使用低级 API 加载纹理非常简单,除非您需要它来处理不同的图像格式。这是一些适用于我所有 RGBA 纹理图像的 scala 代码:

val textureIDList = new Array[Int](1)
gl.glGenTextures(1, textureIDList, 0)
gl.glBindTexture(GL.GL_TEXTURE_2D, textureIDList(0))
gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR)
gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR)
val dataBuffer = image.getRaster.getDataBuffer   // image is a java.awt.image.BufferedImage (loaded from a PNG file)
val buffer: Buffer = dataBuffer match {
  case b: DataBufferByte => ByteBuffer.wrap(b.getData)
  case _ => null
}
gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, image.getWidth, image.getHeight, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, buffer)

...

gl.glDeleteTextures(1, textureIDList, 0)
于 2010-05-10T21:33:30.990 回答
1

我不确定它是否会完全缩小性能差距,但您应该能够使用 ImageIO.read 方法,该方法采用 InputStream 并传入包装 FileInputStream 的 BufferedInputStream。这应该会大大减少 JVM 必须执行的本地文件 I/O 调用的数量。它看起来像这样:

文件文件 = 新文件(文件名);
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis, 8192); //8K 读取
BufferedImage 图像 = ImageIO.read(bis);
于 2009-12-20T01:55:16.517 回答
1

您是否有机会研究过JAI(Java Advanced Imaging),它为 png 压缩/解压缩等任务实现了本机加速。PNG解压缩的Java实现可能是这里的问题。你用的是哪个版本的jvm?

我使用加载和渲染数千个纹理的应用程序,为此我使用 DDS 格式的纯 Java 实现 - 可用于 NASA WorldWind。DDS 纹理加载到 GL 中的速度更快,因为它可以被显卡理解。

感谢您的基准测试,并希望使用您的实验来测试 DDS 加载时间。还可以调整 JAI 和 JVM 可用的内存,以允许加载更多段和解压缩。

于 2009-12-22T13:53:25.560 回答
1

实际上,我像这样在 JOGL 中加载我的纹理:

TextureData data = TextureIO.newTextureData(stream, false, fileFormat);
Texture2D tex = new Texture2D(...);   // contains glTexImage2D
tex.bind(g);
tex.uploadData(g, 0, data);  // contains glTexSubImage2D

以这种方式加载纹理可以绕过构建 BufferedImage 和解释它的额外工作。这对我来说相当快。你可以分析出来。我在等你的结果。

于 2010-01-20T09:19:55.817 回答
0

您也可以尝试直接从 BufferedImage 加载纹理这里有一个示例

使用它,您可以查看图像加载是否花费时间,或者写入创建/视频内存。

您可能还想考虑图像的大小为 2 次方,即 16,32,64,128,256,1024... 尺寸,某些 gfx 卡将无法处理非 2 次方尺寸,您会得到空白纹理在那些 gfx 卡上使用时。

于 2012-04-10T09:12:20.903 回答