Search code examples
opengloptimizationclojurelwjgl

How can I fill this buffer faster in clojure


I'm trying to make a function that will take a BufferedImage and return a ByteBuffer I can then use as an OpenGL texture. To do so, I've learnt that I must do some byte-shifting which is not really relevant to my question. It has to do with the BufferedImage values being ARGB and OpenGL wanting RGBA.

The function I'm trying to implement (from java) is this one:

public static ByteBuffer toByteBuffer(BufferedImage img){
    byte[] byteArray = new byte[img.getWidth()*img.getHeight()*4];
    for(int i = 0; i < img.getWidth()*img.getHeight(); i++){
        int value = img.getRGB(i%img.getWidth(), (i-(i%img.getWidth()))/img.getWidth() );
        byteArray[i*4] = (byte) ((value<<8)>>24); 
        byteArray[i*4+1] = (byte) ((value<<16)>>24);
        byteArray[i*4+2] = (byte) ((value<<24)>>24);
        byteArray[i*4+3] = (byte) (value>>24); 
    }
    return (ByteBuffer) ByteBuffer.allocateDirect(byteArray.length).put(byteArray).flip();
}

And this is my attempt with clojure:

(defn sub-byte [^long b ^long x]
  (unchecked-byte (-> x
    (bit-shift-left (* 8 b))
    (bit-shift-right 24))))


(defn bufferedimage->bytebuffer [^BufferedImage img]
  (binding [*unchecked-math* true] 
    (let [w (.getWidth img)
          h (.getHeight img)
          ^bytes arr (make-array Byte/TYPE (* 4 w h))]
      (loop [i 0]
          (let [img-i (mod i w)
                img-j (quot i w)
                value (.getRGB img img-i img-j)]
            (aset arr (* i 4)       (sub-byte 1 value))
            (aset arr (+ 1 (* i 4)) (sub-byte 2 value))
            (aset arr (+ 2 (* i 4)) (sub-byte 3 value))
            (aset arr (+ 3 (* i 4)) (sub-byte 0 value))
            (when (< (+ i 1) (* w h)) (recur (+ i 1)))
            ))
      (cast ByteBuffer (-> (ByteBuffer/allocateDirect (count arr))
                           (.put arr)
                           (.flip))))))

This takes 10 seconds to load a 512*512 tileset, which is completely unacceptable. I'm trying to make this run in way less than one second.

Note that the part that's taking all the time is the loop.

I might as well mention that those times are taken using the REPL.

Also, note that I'm well aware I can use java for the performance-critical parts of my code, so this is more of a theoretical question so I can learn how to optimize my clojure code.


Solution

  • The issue with solution using function is revealed when you set *warn-on-reflection* to true:

    (set! *warn-on-reflection* true)
    

    When you load your code, the compiler will tell you that your sub-byte function returns Object and it cannot resolve statically a matching method.

    Reflection warning, web_app/so.clj:26:11 - call to static method aset on clojure.lang.RT can't be resolved (argument types: [B, int, java.lang.Object).
    

    Unfortunately, you cannot use a type hint for byte return value on your function as only long and double primitives are supported as return types:

    (defn sub-byte ^byte [^long b ^long x]
      (unchecked-byte (-> x
                          (bit-shift-left (* 8 b))
                          (bit-shift-right 24))))
    
    CompilerException java.lang.IllegalArgumentException: Only long and double primitives are supported, compiling:(web_app/so.clj:7:1)
    

    You might try to hint ^long as the return type but then the hinted result type is not what your function body returns (byte):

    (defn sub-byte ^long [^long b ^long x]
      (unchecked-byte (-> x
                          (bit-shift-left (* 8 b))
                          (bit-shift-right 24))))
    
    CompilerException java.lang.IllegalArgumentException: Mismatched primitive return, expected: long, had: byte, compiling:(web_app/so.clj:7:1)
    

    You can however have your function to return long but then you have to wrap it everywhere with unchecked-byte - this way you eliminate all reflections warnings:

    (defn sub-byte ^long [^long b ^long x]
      (-> x
          (bit-shift-left (* 8 b))
          (bit-shift-right 24))))
    
    (unchecked-byte (sub-byte ...))
    

    Another solution is to use macro as you already found out which will avoid any issues with function calls and its return types.