(CodeReview 환영합니다.)

물론 Rendering 관련된 코드를 Java 같은 객체지향 언어로짜는 일은 거의 없습니다. Java 뿐만이 아니라 거의 모든 OOP 의 단점 중 하나가 값을 메모리를 정확히 해제해주어야 될 때 힘들다는 점입니다(C# 은 Struct 를 지원해주므로 예외). 1초에 Loop 를 수십번 도는 Rendering 부분을 구현할 때 Object 생성을 하게되면 1초에 수백개의 Object 를 생성하게 되고, 따라서 어쩔수 없이 Rendering 부분은 Struct 등을 지원하는 언어로 짜게 됩니다.

하지만 본인이 Native 언어로 짜기 싫거나, OOP 적인 언어로 굳이 짜고 싶다면, 랜더링시 필요한 Data를 관리하는 클래스를 따로 만들어 주는 것이 좋습니다. 아래 예시는 OpenGL 에 사용하기 위해 개인적으로 만든 DataCollection 입니다. 원리는 java ArrayList 와 같이 resize and copy 를 통한 구현이다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Vec2fArrayForGL(capacity : Int = 64)
{
    private var capacity = capacity
    private var _count   = 0
    private var size     = 2
    private var buffer   = ByteBuffer.allocateDirect(capacity * size * java.lang.Float.BYTES).asFloatBuffer()
    private val count get() = _count

    fun add(x: Float, y: Float){
        if (_count >= capacity){
            capacity *= 2
            buffer = ByteBuffer.allocateDirect(capacity * size * java.lang.Float.BYTES).asFloatBuffer().put(buffer)
        }
        buffer.put(x)
        buffer.put(y)
        _count ++
    }

    fun getInternalBuffer(): FloatBuffer{
        return buffer.asReadOnlyBuffer()
    }

    fun clear(){
        buffer.position(0)
        _count = 0
    }

    protected fun finalize() {
       
    }
}

기본 ArrayList 와 테스트해보니.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
fun main() {

    val myIntArray = intArrayOf(0, 1, 2)
    myIntArray.getOrNull(0)

    for (j in 0 until 100){
        val startTime = System.currentTimeMillis()
        val list = Vec2fArrayForGL()
        for ( i in 0 until 200000) {
            list.add(Random.nextFloat(), Random.nextFloat())
        }
        println( "nativeBuffer Time : ${System.currentTimeMillis() - startTime}")

        val startTime2 = System.currentTimeMillis()
        val list2 = ArrayList(200000*2)
        for ( i in 0 until 200000) {
            list2.add(Random.nextFloat())
            list2.add(Random.nextFloat())
        }
        println( "arrayList Time    : ${System.currentTimeMillis() - startTime2}")
    }
}

nativeBuffer Time : 32
arrayList Time    : 12
nativeBuffer Time : 3
arrayList Time    : 18
nativeBuffer Time : 9
arrayList Time    : 16
nativeBuffer Time : 3
arrayList Time    : 19
nativeBuffer Time : 3
arrayList Time    : 5
nativeBuffer Time : 4
arrayList Time    : 4
nativeBuffer Time : 4
arrayList Time    : 19
nativeBuffer Time : 3
arrayList Time    : 5
nativeBuffer Time : 3
arrayList Time    : 4
nativeBuffer Time : 5
arrayList Time    : 21
nativeBuffer Time : 4
arrayList Time    : 5
nativeBuffer Time : 4
arrayList Time    : 5
nativeBuffer Time : 4
arrayList Time    : 4

다음과 같이 나왔습니다. JNI vs AutoBoxing 으로 별 차이는 없지만, NativeBuffer 가 OpenGL 과 같은 그래픽 라이브러리와의 호환성이 더 좋죠.

초반 결과는 NativeBuffer 처음 할당시에 JVM 에서 Native 공간 차지하느라 드는 시간이고( + Random() 초기화, 그리고 이는 적절한 처리를 통해 아예 안나게 할 수 있다.), 5번 째 테스트 이후부터 보면 될 것 같습니다.  arrayList 의 경우 주기적으로 data input 시간이 늘어날 때가 있는데, 초반은 NativeBuffer 와 같은 이유로 그렇다고 치지만, 왜 계속해서 주기적으로 그러한 현상이 일어나는지 모르겠군요. 정확한 이유 아시는 분은 알려주시면... 60fp 에서 1frame 이 약 16ms 이므로 40000 float 정도의 data 를 사용한다면 kotlin 으로만 RenderEngine 을 짜도 괜찮을것 같내요. 물론 이건 노트북으로 돌린거라 안드로이드에서도 돌려봐야 될 것 같습니다.