So I've had a bug for quite some time now. It doesn't happen to me on my computer, nor any of the handful of computers I have access to, but as I've recently started distributing my app I get reports concerning it.
This seems to happen exclusively on 32b windowsIt has alwas manifested itself when textures are created. Either by STBImage.stbi_load or by glTexImage2D. And it's always at the same stage in the application, when two 4096x4096 .png textures are supposed to be uploaded to the GPU. I get the errors from either glGetError() or STBImage.stbi_failure_reason().
As I have stated, I don't have this problem on my rig. On my rig windows task manager shows that the JVM process is using ~130MB. GPUZ is showing used gpu memory is 1000MB, an increase of ~200MB when not running the app. In other words, my app is using rougly 130MB ram and 200MB vram. And that is completely according to my own calculations. And it's not growing.
I'm not doing anything very fancy in my app. I have 2 RBG8 textures, 4096x4096. A couple of FBO's the same size as the screen and about 5MB worth of VBO's. Everything works for 90% of the population of earth.
This is the flow of the program leading up to the bug:
GLFW & OpenGl & OpenAL Initialization
2x4096*256 textures created
vbo's are streamed (glbuffersubdata each frame)
User clicks play
textures are released
2*4096*4096 textures are created
Out of memory!
My theory is this:
There really should be enough memory around. The streaming VBO's fragment the off heap memory to a state where there isn't enough room for 64MB + overhead to be allocated in a continuous chunk. But instead of freeing memory or compacting the heap, it fails and I have to crash. Could also be page size?
I haven't found any explanation on how to monitor off heap memory allocated by lwjgl. If it's hiding somewhere, I want to see it.So, the app is out there, if any of you would like to help. This might seem like a call for attention to my app, but I assure you it's not. I'm going insane from this bug. One of us needs o be destroyed.
https://songsofsyx.itch.io/songs-of-syxThe bug should manifest itself when you press play in the main menu.
I've recently got this error report from one of those who have this issue, where I've intentionally crashed the app prior to the bug and printed out memory info.
MEM DIAGNOSE
--JRE Memory
--JRE Total: 499
--JRE Free: 477
--JRE Used: 22
--JRE Max: 999
NVIDIA:
--GPU Dedicated: 0
--GPU Total Available: 0
--GPU Current Available: 0
--GPU Evictions: 0
--GPU Evicted: 0
ATI:
--Renderbuffer Free: 7265316
--Texture Free: 7265316
--Vbo Free: 7265316
|---JRE Input Arguments : -Xms512m, -Xmx1024m,
[LWJGL] Version: 3.1.2 build 29
[LWJGL] OS: Windows 10 v10.0
[LWJGL] JRE: 1.8.0_221 x86
[LWJGL] JVM: Java HotSpot(TM) Client VM v25.221-b11 by Oracle Corporation
[LWJGL] Loading library (system): lwjgl32
[LWJGL] Found at: C:\Users\tobip\AppData\Local\Temp\lwjgltobip\3.1.2-build-29\lwjgl32.dll
[LWJGL] Loaded from org.lwjgl.librarypath: C:\Users\tobip\AppData\Local\Temp\lwjgltobip\3.1.2-build-29\lwjgl32.dll
[LWJGL] MemoryUtil accessor: MemoryAccessorUnsafe
[LWJGL] Loading library: jemalloc32
[LWJGL] Found at: C:\Users\tobip\AppData\Local\Temp\lwjgltobip\3.1.2-build-29\jemalloc32.dll
[LWJGL] Loaded from org.lwjgl.librarypath: C:\Users\tobip\AppData\Local\Temp\lwjgltobip\3.1.2-build-29\jemalloc32.dll
[LWJGL] MemoryUtil allocator: DebugAllocator
[LWJGL] Loading library: glfw32
[LWJGL] Found at: C:\Users\tobip\AppData\Local\Temp\lwjgltobip\3.1.2-build-29\glfw32.dll
[LWJGL] Loaded from org.lwjgl.librarypath: C:\Users\tobip\AppData\Local\Temp\lwjgltobip\3.1.2-build-29\glfw32.dll
[LWJGL] Loading library (system): lwjgl_stb32
[LWJGL] Found at: C:\Users\tobip\AppData\Local\Temp\lwjgltobip\3.1.2-build-29\lwjgl_stb32.dll
[LWJGL] Loaded from org.lwjgl.librarypath: C:\Users\tobip\AppData\Local\Temp\lwjgltobip\3.1.2-build-29\lwjgl_stb32.dll
[LWJGL] Loading library (system): lwjgl_opengl32
[LWJGL] Found at: C:\Users\tobip\AppData\Local\Temp\lwjgltobip\3.1.2-build-29\lwjgl_opengl32.dll
[LWJGL] Loaded from org.lwjgl.librarypath: C:\Users\tobip\AppData\Local\Temp\lwjgltobip\3.1.2-build-29\lwjgl_opengl32.dll
[LWJGL] Loading library: opengl32
[LWJGL] opengl32.dll not found in org.lwjgl.librarypath=C:\Users\tobip\AppData\Local\Temp\lwjgltobip\3.1.2-build-29
[LWJGL] Loaded from system paths
[LWJGL] [GL] Using KHR_debug for error logging.
[LWJGL] [GL] Warning: A non-debug context may not produce any debug output.
---Max Texture Dim: 16384
---Version: 3.3.13558 Core Profile Forward-Compatible Context 26.20.11015.5009
---SL Version: 4.60
---glRenderer: ATI Technologies Inc., Radeon RX Vega
---Forward compatible: true
SHADER COMPILED: class snake2d.VboSpritePoints$ShaderDebug 1
SHADER COMPILED: class snake2d.VboParticles$ShaderDebug 5
SOUND
[LWJGL] Loading library: OpenAL32
[LWJGL] Found at: C:\Users\tobip\AppData\Local\Temp\lwjgltobip\3.1.2-build-29\OpenAL32.dll
[LWJGL] Loaded from org.lwjgl.librarypath: C:\Users\tobip\AppData\Local\Temp\lwjgltobip\3.1.2-build-29\OpenAL32.dll
---AL version : 1.1 ALSOFT 1.17.2
---AL vendor : OpenAL Community
---AL renderer : OpenAL Soft
---OpenALC10: true
---OpenALC11: true
---ALC_FREQUENCY: 48000Hz
---ALC_REFRESH: 50Hz
---ALC_SYNC: false
---Created Mono Sources : 10
---Created Stereo Sources : 6
SHADER COMPILED: class snake2d.VboParticles$ShaderTexture 8
---Initiating random, seed: 6663922946396309042
class snake2d.SoundCore sucessfully destroyed
class snake2d.GraphicContext was successfully destroyed
Core was successfully disposed
The MEM-DIAGNOSE code looks like this:
static void diagnozeMem() {
int mb = 1014*1024;
System.err.println("MEM DIAGNOSE");
Runtime run = Runtime.getRuntime();
// available memory
System.err.println("--JRE Memory");
System.err.println("--JRE Total: " + run.totalMemory() / mb);
System.err.println("--JRE Free: " + run.freeMemory() / mb);
System.err.println("--JRE Used: "
+ (run.totalMemory() - run.freeMemory()) / mb);
System.err.println("--JRE Max: " + run.maxMemory() / mb);
int i;
System.err.println("NVIDIA: ");
i = glGetInteger(org.lwjgl.opengl.NVXGPUMemoryInfo.GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX);
System.err.println("--GPU Dedicated: " + i);
i = glGetInteger(org.lwjgl.opengl.NVXGPUMemoryInfo.GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX);
System.err.println("--GPU Total Available: " + i);
i = glGetInteger(org.lwjgl.opengl.NVXGPUMemoryInfo.GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX);
System.err.println("--GPU Current Available: " + i);
i = glGetInteger(org.lwjgl.opengl.NVXGPUMemoryInfo.GL_GPU_MEMORY_INFO_EVICTION_COUNT_NVX);
System.err.println("--GPU Evictions: " + i);
i = glGetInteger(org.lwjgl.opengl.NVXGPUMemoryInfo.GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX);
System.err.println("--GPU Evicted: " + i);
System.err.println("ATI: ");
i = glGetInteger(org.lwjgl.opengl.ATIMeminfo.GL_RENDERBUFFER_FREE_MEMORY_ATI);
System.err.println("--Renderbuffer Free: " + i);
i = glGetInteger(org.lwjgl.opengl.ATIMeminfo.GL_TEXTURE_FREE_MEMORY_ATI);
System.err.println("--Texture Free: " + i);
i = glGetInteger(org.lwjgl.opengl.ATIMeminfo.GL_VBO_FREE_MEMORY_ATI);
System.err.println("--Vbo Free: " + i);
glGetError();
}
This person was on ATI and I have no idea what it returns. Bytes, KB's MB's? Who knows. If it's bytes, then his 8GB GPU is indeed out of memory, but I wager it's KB's and that it's RAM that is suffering. The JVM tells me nothing useful.
Here are a few key point of my code. I've read through them hundreds of times myself and can't find anything.
Texture loading:
package snake2d.util.file;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.stb.STBImage;
import org.lwjgl.stb.STBImageWrite;
import org.lwjgl.system.MemoryStack;
import snake2d.util.misc.ErrorHandler;
public final class SnakeImage {
private final ByteBuffer image;
public final int height;
public final int width;
public final ImageGraphics rgb = new ImageGraphics();
public SnakeImage(String path) {
if (!new File(path).exists())
throw new ErrorHandler.DataError("File doesn't exist", path);
try (MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer w = stack.mallocInt(1);
IntBuffer h = stack.mallocInt(1);
IntBuffer comp = stack.mallocInt(1);
image = STBImage.stbi_load(path, w, h, comp, 4);
// if (comp.get() != 4)
// throw new ErrorHandler.DataError("Failed to load a texture file!"
// + "not RGBA data", path);
if (image == null)
throw new RuntimeException("Failed to load a texture file!"
+ System.lineSeparator() + STBImage.stbi_failure_reason());
width = w.get();
height = h.get();
}
}
public SnakeImage(String path, int width, int height) {
if (!new File(path).exists())
throw new ErrorHandler.DataError("File doesn't exist", path);
try (MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer w = stack.mallocInt(1);
IntBuffer h = stack.mallocInt(1);
IntBuffer comp = stack.mallocInt(1);
image = STBImage.stbi_load(path, w, h, comp, 4);
if (comp.get() != 4)
throw new ErrorHandler.DataError("Failed to load a texture file!"
+ "not RGBA data", path);
if (image == null)
throw new RuntimeException("Failed to load a texture file!"
+ System.lineSeparator() + STBImage.stbi_failure_reason());
this.width = w.get();
this.height = h.get();
if (this.width != width || this.height != height) {
throw new ErrorHandler.DataError("Image has wrong dimentions. Resize to: " + width + "x" + height, path);
}
}
}
public SnakeImage(int width, int height){
try {
image = BufferUtils.createByteBuffer(width*height*4);
}catch(OutOfMemoryError e) {
e.printStackTrace();
int s = width*height*4;
throw new RuntimeException(s + " " + Runtime.getRuntime().totalMemory() + " " + Runtime.getRuntime().freeMemory());
}
this.width = width;
this.height = height;
}
public ByteBuffer data() {
image.rewind();
return image;
}
public void save(String path) {
image.rewind();
STBImageWrite.stbi_write_png(path, width, height, 4, image, 0);
}
public final class ImageGraphics {
private ImageGraphics() {
}
private void boundCheck(int x, int y) {
if (x < 0 || y < 0 || x >= width || y >= height)
throw new RuntimeException(x + " " +y + "is out of bounds");
}
public void set(int x, int y, int r, int g, int b, int a) {
boundCheck(x, y);
int i = 4*(x + y*width);
image.position(i);
image.put((byte)r).put((byte)g).put((byte)b).put((byte)a);
}
public void set(int x, int y, int c) {
int r = (c >> 24) & 0x0FF;
int g = (c >> 16) & 0x0FF;
int b = (c >> 8) & 0x0FF;
int a = (c) & 0x0FF;
set(x, y, r, g, b, a);
}
public int get(int x, int y) {
boundCheck(x, y);
int i = 4*(x + y*width);
image.position(i);
int res = (image.get() & 0x0FF) << 24;
res |= (image.get() & 0x0FF) << 16;
res |= (image.get() & 0x0FF) << 8;
res |= (image.get() & 0x0FF);
return res;
}
}
}
texture opengl
package snake2d;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.opengl.GL31.*;
import java.nio.ByteBuffer;
import org.lwjgl.opengl.GL11;
import snake2d.util.file.SnakeImage;
class _TextureDiffuse {
final int id;
final int width;
final int height;
private boolean desposed = false;
private static int MAX_SIZE = 0x04000;
_TextureDiffuse(SnakeImage i, boolean pixelated){
width = i.width;
height = i.height;
if (width > MAX_SIZE || height > MAX_SIZE)
throw new RuntimeException();
id = glGenTextures();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_RECTANGLE, id);
//some strange filters. Experiment!
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, pixelated ? GL_NEAREST : GL_LINEAR);
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, i.data());
int e = GL11.glGetError();
if (e != GL_NO_ERROR) {
GlHelper.diagnozeMem();
throw new RuntimeException("Texture Error " + width + " " + height + " " + e);
}
GlHelper.checkErrors();
}
public void bind(){
if (desposed)
throw new IllegalStateException("trying to bind a texture that was disposed");
else
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_RECTANGLE, id);
}
void dis() {
glDeleteTextures(id);
GlHelper.checkErrors();
desposed = true;
}
public void uploadPixels(int px, int width, int py, int height, ByteBuffer pixels){
glActiveTexture(GL_TEXTURE0);
glTexSubImage2D(GL_TEXTURE_RECTANGLE, 0, px, py, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
}
public void uploadPixel(int px, int py, ByteBuffer pixel){
glActiveTexture(GL_TEXTURE0);
glTexSubImage2D(GL_TEXTURE_RECTANGLE, 0, px, py, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
}
public int getWidth(){return width;}
public int getHeight(){return height;}
}
VBO
package snake2d;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
import java.nio.ByteBuffer;
import org.lwjgl.opengl.GL11;
import org.lwjgl.system.MemoryUtil;
abstract class VboAbs {
private final int vertexArrayID;
private final int attributeElementID;
final ByteBuffer buffer;
final int MAX_ELEMENTS;
final int ELEMENT_SIZE;
private final int BUFFER_SIZE;
private final int NR_OF_ATTRIBUTES;
protected int count = 0;
protected int[] vFrom = new int[255];
protected int[] vTo = new int[255];
protected int current = 0;
private final int type;
private final int indexMul;
VboAbs(int type, int maxElements, VboAttribute... attributes){
MAX_ELEMENTS = maxElements;
NR_OF_ATTRIBUTES = attributes.length;
this.type = type;
int vertecies = 0;
if (type == GL_TRIANGLES) {
indexMul = 6;
vertecies = 4;
}else if (type == GL_POINTS) {
indexMul = 1;
vertecies = 1;
}else {
throw new RuntimeException("unsupported type");
}
int byteStride = 0;
for (VboAttribute v : attributes) {
byteStride += v.sizeInBytes;
}
if (byteStride % 4 != 0)
throw new RuntimeException(byteStride + " Needs padding with " + (4 - (byteStride % 4)));
ELEMENT_SIZE = byteStride*vertecies;
BUFFER_SIZE = ELEMENT_SIZE * MAX_ELEMENTS;
buffer = MemoryUtil.memAlloc(BUFFER_SIZE);
vertexArrayID = glGenVertexArrays();
glBindVertexArray(vertexArrayID);
attributeElementID = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, attributeElementID);
int index = 0;
int pointerOffset = 0;
for (VboAttribute v : attributes) {
if (v.isInt)
glVertexAttribIPointer(index, v.amount, v.glType, byteStride, pointerOffset);
else
glVertexAttribPointer(index, v.amount, v.glType, v.normalized, byteStride, pointerOffset);
index ++;
pointerOffset += v.sizeInBytes;
}
glBufferData(GL_ARRAY_BUFFER, buffer, GL_STREAM_DRAW);
if (type == GL_TRIANGLES) {
VboElementArrays.quadBind();
}else if (type == GL_POINTS) {
VboElementArrays.pointBind();
}
glBindVertexArray(0);
GlHelper.checkErrors();
}
final void flush(int from, int to){
if (from == to)
return;
glDrawElements(type, (to - from)*indexMul, GL11.GL_UNSIGNED_INT, from * 4* indexMul);
}
void clear(){
current = 0;
buffer.clear();
count = 0;
}
void dis(){
// Disable the VBO index from the VAO attributes list
glBindVertexArray(vertexArrayID);
glBindBuffer(GL_ARRAY_BUFFER, attributeElementID);
for (int i = 0; i < NR_OF_ATTRIBUTES; i++) {
glDisableVertexAttribArray(i);
}
// Dispose the buffer object
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(attributeElementID);
// Dispose the vertex array
glBindVertexArray(0);
glDeleteVertexArrays(vertexArrayID);
// Dispose the element buffer object
VboElementArrays.dispose();
GlHelper.checkErrors();
MemoryUtil.memFree(buffer);
}
final void bindAndUpload() {
if (count == 0) {
return;
}
buffer.flip();
bind();
glBufferSubData(GL_ARRAY_BUFFER, 0, buffer);
}
void bind() {
glBindVertexArray(vertexArrayID);
glBindBuffer(GL_ARRAY_BUFFER, attributeElementID);
for (int i = 0; i < NR_OF_ATTRIBUTES; i++) {
glEnableVertexAttribArray(i);
}
}
static class VboAttribute {
private final boolean isInt;
private final int amount;
private final int glType;
private final int sizeInBytes;
private final boolean normalized;
public VboAttribute(int amount, int glType, boolean normalized, int sizeInBytes){
isInt = false;
this.amount = amount;
this.glType = glType;
this.sizeInBytes = sizeInBytes*amount;
this.normalized = normalized;
}
public VboAttribute(int amount, int glType, int sizeInBytes){
isInt = true;
this.amount = amount;
this.glType = glType;
this.sizeInBytes = sizeInBytes*amount;
this.normalized = false;
}
}
}
If you can trigger the bug, or if you see anything in my code that could be a memory leak, please help me! I can't sleep any more...