1+ /*
2+ * Copyright 2025 Lambda
3+ *
4+ * This program is free software: you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation, either version 3 of the License, or
7+ * (at your option) any later version.
8+ *
9+ * This program is distributed in the hope that it will be useful,
10+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+ * GNU General Public License for more details.
13+ *
14+ * You should have received a copy of the GNU General Public License
15+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16+ */
17+
18+ package com.lambda.graphics.buffer
19+
20+ import org.lwjgl.BufferUtils.createByteBuffer
21+ import org.lwjgl.system.MemoryUtil.memAddress0
22+ import org.lwjgl.system.MemoryUtil.memCopy
23+ import org.lwjgl.system.MemoryUtil.memPutByte
24+ import org.lwjgl.system.MemoryUtil.memPutFloat
25+ import org.lwjgl.system.MemoryUtil.memPutInt
26+ import java.awt.Color
27+ import java.nio.ByteBuffer
28+
29+ /* *
30+ * Dynamically resizable byte buffer designed for efficient vertex building.
31+ * Automatically grows capacity when needed, and provides
32+ * convenience methods for common data types used in vertex attributes.
33+ *
34+ * @property data The underlying [java.nio.ByteBuffer] storing the vertex data
35+ * @property capacity Current maximum capacity of the buffer in bytes
36+ * @property pointer Base memory address of the buffer
37+ * @property position Current write position in memory address space
38+ */
39+ class DynamicByteBuffer private constructor(initialCapacity : Int ) {
40+ var data: ByteBuffer = createByteBuffer(initialCapacity); private set
41+ var capacity = initialCapacity; private set
42+
43+ var pointer = memAddress0(data); private set
44+ var position = pointer; private set
45+
46+ /* *
47+ * Gets the total number of bytes written to the buffer since last reset
48+ */
49+ val bytesPut get() = position - pointer
50+
51+ /* *
52+ * Resets the write position to the beginning of the buffer while maintaining current capacity,
53+ * allowing for buffer reuse without reallocation
54+ */
55+ fun resetPosition () {
56+ position = pointer
57+ }
58+
59+ /* *
60+ * Writes a single byte value at the current position
61+ * @param value The byte value to write
62+ */
63+ fun putByte (value : Byte ) {
64+ require(1 )
65+ memPutByte(position, value)
66+ position + = 1
67+ }
68+
69+ /* *
70+ * Writes a 4-byte integer value at the current position
71+ * @param value The integer value to write
72+ */
73+ fun putInt (value : Int ) {
74+ require(4 )
75+ memPutInt(position, value)
76+ position + = 4
77+ }
78+
79+ /* *
80+ * Writes a 4-byte floating point value at the current position
81+ * @param value The double-precision value to write (will be converted to float)
82+ */
83+ fun putFloat (value : Double ) {
84+ require(4 )
85+ memPutFloat(position, value.toFloat())
86+ position + = 4
87+ }
88+
89+ /* *
90+ * Writes a 2-component vector as two consecutive 4-byte floats
91+ * @param x X-axis component
92+ * @param y Y-axis component
93+ */
94+ fun putVec2 (x : Double , y : Double ) {
95+ require(8 )
96+ memPutFloat(position + 0 , x.toFloat())
97+ memPutFloat(position + 4 , y.toFloat())
98+ position + = 8
99+ }
100+
101+ /* *
102+ * Writes a 3-component vector as three consecutive 4-byte floats
103+ * @param x X-axis component
104+ * @param y Y-axis component
105+ * @param z Z-axis component
106+ */
107+ fun putVec3 (x : Double , y : Double , z : Double ) {
108+ require(12 )
109+ memPutFloat(position + 0 , x.toFloat())
110+ memPutFloat(position + 4 , y.toFloat())
111+ memPutFloat(position + 8 , z.toFloat())
112+ position + = 12
113+ }
114+
115+ /* *
116+ * Writes a color as four consecutive bytes in RGBA format
117+ * @param color The color to write
118+ */
119+ fun putColor (color : Color ) {
120+ require(4 )
121+ memPutByte(position + 0 , color.red.toByte())
122+ memPutByte(position + 1 , color.green.toByte())
123+ memPutByte(position + 2 , color.blue.toByte())
124+ memPutByte(position + 3 , color.alpha.toByte())
125+ position + = 4
126+ }
127+
128+ /* *
129+ * Ensures the buffer has enough remaining space for the requested number of bytes.
130+ * Automatically grows the buffer if insufficient space remains.
131+ * @param size Number of bytes required for the next write operation
132+ */
133+ fun require (size : Int ) {
134+ if (capacity - bytesPut > size) return
135+ grow(capacity * 2 )
136+ }
137+
138+ /* *
139+ * Increases buffer capacity while preserving existing data. New capacity must be greater than current.
140+ * @param newCapacity New buffer capacity in bytes
141+ * @throws IllegalArgumentException if newCapacity is not greater than current capacity
142+ */
143+ fun grow (newCapacity : Int ) {
144+ check(newCapacity > capacity) {
145+ " Cannot grow buffer beyond its capacity"
146+ }
147+
148+ val newBuffer = createByteBuffer(newCapacity)
149+ val newPointer = memAddress0(newBuffer)
150+ val offset = position - pointer
151+
152+ memCopy(pointer, newPointer, offset)
153+ data = newBuffer
154+
155+ pointer = newPointer
156+ position = newPointer + offset
157+ capacity = newCapacity
158+ }
159+
160+ /* *
161+ * Reallocates the buffer with exact new capacity, resetting position and discarding existing data
162+ * @param newCapacity New buffer capacity in bytes
163+ */
164+ fun realloc (newCapacity : Int ) {
165+ if (newCapacity == capacity) {
166+ resetPosition()
167+ return
168+ }
169+
170+ val newBuffer = createByteBuffer(newCapacity)
171+ val newPointer = memAddress0(newBuffer)
172+
173+ data = newBuffer
174+ pointer = newPointer
175+ position = newPointer
176+ capacity = newCapacity
177+ }
178+
179+ companion object {
180+ /* *
181+ * Creates a new DynamicByteBuffer with specified initial capacity
182+ * @param initialCapacity Starting buffer size in bytes
183+ */
184+ fun dynamicByteBuffer (initialCapacity : Int ) =
185+ DynamicByteBuffer (initialCapacity)
186+ }
187+ }
0 commit comments