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 each glDeleteTextures
. 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With