I have been having some problems with my openGL application running out of memory and I am trying to track down my problem. To this end, I created a small test program that basically just loads a giant texture from a file calls glDeleteTextures and then loads it again, if I run this test program on OSX this process can run for hundreds of iterations with no problems (the most I ran it for was 1024 and this had no problems) but after about 14 iterations on windows I get a GLU_OUT_OF_MEMORY error. The texture I am loading over and over is a huge sky dome (4096 x 1024 and 4.9MB).
Here is the code I am running with comments for the non lisp inclined:
Test Program:
(with-glcontext (shared-opengl-view)
;;loop 1024 times
(dotimes (i 1024)
;; create a texture and return its texture name
(let ((texture (create-texture-from-file (truename "lui:resources;sky domes;DarkClouds.png"))))
;;Allocate two bytes to pass to glDeleteTextures
(ccl::rlet ((&texName :long))
;; put the texture string into our allocated bytes
(setf (ccl::%get-long &texName) texture)
;; Delete the textures?
(glDeleteTextures 1 &texName)))))
Create-texture-from-file (most of this method is made up of openGL calls so I think most openGL people should have a reasonable understanding of what's happening here but I can clarify anything thats confusing):
(defun CREATE-TEXTURE-FROM-FILE (Filename &key Verbose (Build-Mipmaps t) Repeat (Mag-Filter *Default-OpenGL-Texture-Magnification-Filter*) Forced-Depth) "
in: Filename {string},
&key Verbose {boolean}, Repeat
out: OpenGL-Texture-Name {int}, width, height, depth
Load the <Filename> texture inside the texture directory.
- Ideal file should be 32 bit ARGB compatible, e.g., .png with mask or 24 bit RGB
- 8 bit and 16 bit image will work too
- Image size must be 2^n x 2^m, at least 64 x 64
- This function must be called with active AGL context, e.g., inside OpenGL-Window INIT method."
(declare (ftype function create-image-from-file))
(rlet ((&texName :long))
(multiple-value-bind (&Image Width Height Depth) (create-image-from-file Filename :verbose Verbose :flip-vertical t :forced-depth Forced-Depth)
(unless &Image (return-from create-texture-from-file nil))
(glPixelStorei GL_UNPACK_ROW_LENGTH Width) ; Set proper unpacking row length for image
(glPixelStorei GL_UNPACK_ALIGNMENT 1) ; Set byte aligned unpacking (needed for 3-byte-per-pixel image)
(glGenTextures 1 &texName)
; Specify the texture's properties.
(glBindTexture GL_TEXTURE_2D (%get-long &texName))
(glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S (if Repeat GL_REPEAT GL_CLAMP_TO_EDGE))
(glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T (if Repeat GL_REPEAT GL_CLAMP_TO_EDGE))
(glTexParameteri gl_texture_2d gl_texture_mag_filter Mag-Filter) ;; make textures look smooth (can be costly)
;; Mipmaps: make texture look good at different sizes
(if Build-Mipmaps
(glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER GL_LINEAR_MIPMAP_NEAREST)
(glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER GL_LINEAR))
;; Experimental: anisotropic filtering to make textures less blurry at angle
#-:cocotron (glTexParameterf GL_TEXTURE_2D GL_TEXTURE_MAX_ANISOTROPY_EXT 16.0)
(let ((PixelFormat (ecase Depth (32 GL_RGBA) (24 GL_RGB)))
(InternalFormat (ecase Depth (32 GL_RGBA8) (24 GL_RGB8))))
(glTexImage2D GL_TEXTURE_2D 0 InternalFormat width height 0 PixelFormat GL_UNSIGNED_BYTE &Image)
(when Build-Mipmaps
(when Verbose (format t "~%Building Mipmaps~%"))
(unless
;; The calls to gluBuild2DMipmaps will return GLU_OUT_OF_MEMORY when we run out.
(zerop (print (gluBuild2DMipmaps GL_TEXTURE_2D InternalFormat width height PixelFormat GL_UNSIGNED_BYTE &Image)))
(print (get-opengl-error))
(error "could not create mipmaps"))
(when Verbose (format t "Completed Mipmaps~%"))))
;; OpenGL should have copied now the image data into texture memory: release
(dispose-vector &Image)
;; return the texture handle and image dimensions
(values
(%get-long &texName)
Width
Height
Depth))))
I have read elsewhere the glDeleteTextures does not completely dealocate all resources but none of these posts offered any alternative or solutions. Is there no such solution?
Windows OpenGL drivers are designed on the assumption that your code will actually draw stuff at some point. Because of that, they tend to avoid doing things exactly when you ask them to be done, deferring them to a more convenient time. And that's what you're running into here.
Just because you tell OpenGL that you're done with a texture object doesn't mean that OpenGL must immediately relinquish that memory. Doing that generally requires some pretty heavy-weight operations. So instead, drivers will defer this until later.
The problem is that "later" is usually defined as some form of
glDraw*
command,glEnd
,glFlush/Finish
, or a swap buffer command. Or something of that nature. If all you do is sit in a loop and create/delete textures, the driver may not get the chance to actually delete anything.Try making your test less artificial. Add a
glFinish
after eachglDeleteTextures
. If that doesn't work, draw stuff between deletions.Ultimately, you cannot force drivers to do what you want. All you can do is make your test application behave more like a normal application.
Also, since your error has the GLU prefix, could you try running the same program without building mipmaps?[already done] I don't thinkGLU_OUT_OF_MEMORY
necessarily means that you are out of GPU memory (in fact the mipmaps are probably built on the CPU), so maybe you are suffering from Heap fragmentation. The fact that you get the error on different Windows machines with very different GPUs seems to indicate that it's not a specific OpenGL driver problem; they are too different across vendors.Since you are using a large texture, and if not using
gluBuild2DMipmaps
doesn't solve the problem, you could pre-allocate the GPU memory for your large texture(s) once when your app starts and reuse the texture objects withglTexSubImage
instead of creating new ones on reload.edit: the following does not provide a solution (see question comments), but is still useful in my opinion
Since you get
GLU_OUT_OF_MEMORY
and notGL_OUT_OF_MEMORY
I assume the problem occurs during mipmap creation withgluBuild2DMipmaps
. Since maybe the glu function is at fault, perhaps you could try building your mipmaps in an offline process and then simply load them in Create-texture-from-file. Besides potentially solving your out of memory error, this would let you have much better control over the actual mipmap generation. You could for example use a gaussian filter instead of a box filter. It would also ensure identical mipmap content across platforms. My understanding is that this is how mipmaps are usually handled.I think you could try to push an empty image to the texture you want to delete by using glTexImage2d/3d. It worked for me with VBO's - I get rid of the data from the graphic cards by removing a buffer data using glBufferData and passing an empty array handle, because the glDeleteBuffers didn't removed the data by itself.