3b / cl-opengl

cl-opengl is a set of CFFI bindings to the OpenGL, GLU and GLUT APIs.
http://common-lisp.net/project/cl-opengl/
Other
279 stars 59 forks source link

Consing when calling certain GL functions #97

Closed realark closed 6 months ago

realark commented 4 years ago

Calling into certain GL: functions appears to be consing. These functions are run inside a tight loop, so that ends up generating a lot of garbage. This can be an issue for games trying to minimize GC pauses.

I've tried using the equivilent %GL functions but I get the same result.

It's unclear to me if this is actually an issue with cl-opengl or if the problem is with something cl-opengl calls into (CFFI, SBCL, etc).

Example: https://gist.github.com/realark/c98415c2165364107842055209123eac Critical Lines: https://gist.github.com/realark/c98415c2165364107842055209123eac#file-glconsingdemo-lisp-L170-L177

(Note that this issue also occurs with other gl functions not included in the demo.)

I've worked around this issue in my own game by defining my own FFI calls directly (not a great solution, I'll admit): https://github.com/realark/vert/blob/master/src/graphics/gl-utils.lisp#L605

(lisp-implementation-type)
"SBCL"
CL-USER> (lisp-implementation-version)
"2.0.6"
CL-USER> 
realark commented 4 years ago

The issue appears to be sbcl specific. Using use-program as an example, I expanded the macros enough until I found the sb-alien:with-alien macro to be responsible for generating the consing code.

(let ((address (gl-get-proc-address "glUseProgram")))
  (defun use-program (program)
    (multiple-value-prog1
        (with-float-traps-maybe-masked nil
          ;; BUG?? SBCL WITH-ALIEN macro is generating consing code. 
          (SB-ALIEN:WITH-ALIEN ((foreign-func
                                 (* (FUNCTION SB-ALIEN:VOID SB-ALIEN:UNSIGNED-INT))
                                 :local
                                 ADDRESS))
            (SB-ALIEN:ALIEN-FUNCALL foreign-func program)))
      (check-error 'use-program))))

I've been able to work around this issue by declaring an (optimize (speed 3)) in the body making the foreign funcall.

I'm not sure if this is how SBCL devs intend with-alien to work.

For CL-OPENGL, this can be fixed by adding an optimization hint in the generated function.

(defun generate-gl-function (foreign-name lisp-name result-type body &rest args)
  (let ((address (gl-get-proc-address foreign-name))
        (arg-list (mapcar #'first body)))
    (when (or (not (pointerp address)) (null-pointer-p address))
      (error "Couldn't find function ~A" foreign-name))
    (compile lisp-name
             `(lambda ,arg-list
                (declare (optimize (speed 3))) ; <----------------- NEW LINE. Consing disappears with this addition.
                (multiple-value-prog1
                    (with-float-traps-maybe-masked ()
                      (foreign-funcall-pointer
                       ,address
                       (:library opengl)
                       ,@(loop for i in body
                            collect (second i)
                            collect (first i))
                       ,result-type))
                  #-cl-opengl-no-check-error
                  (check-error ',lisp-name))))
    (apply lisp-name args)))

If this is an acceptable change, I'm happy to submit a PR.

robotjunkyard commented 4 years ago

I've been investigating this issue in my own project the last couple days. Even with SPEED 3 forced, certain GL functions seem to still do this. Specifically in my own project, mostly GL:TEX-PARAMETER and, to a lesser degree, GL:LIGHT have disproportionately high amounts of consing despite not even being called within tight loops.

Is there a way to determine the compiler policy under which CL-OPENGL package was compiled for the first time back when I had first loaded it via quicklisp on my machine? Maybe it wasn't compiled with a SPEED 3 policy.

3b commented 11 months ago

I'm not seeing any consing from those functions in normal situations. I do see some if I build all of cl-opengl with (optimize (debug 3)). Extra consing seems like a normal thing to expect in that case, so not sure if it is a problem here or not.

Adding (optimize (speed 3)) to the extension wrappers makes some of it go away, but also prints a bunch of compiler notes the first time they are used, which is somewhat annoying. I think those can be muffled though, so might add it in.

3b commented 6 months ago

Added some optimizations to the bindings generator, so possibly better now? If this is still a problem let me know and I'll investigate further