hud: improve memory usage

* Every HUDElement has its own mesh now
 * Reuse existing mesh data (don't allocate new memory)
 * Use Mesh data as cache for layouted elements (not a separate float array)
This commit is contained in:
Bixilon 2021-11-17 21:21:17 +01:00
parent e2f4170bb8
commit fc2b5a8502
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
12 changed files with 188 additions and 84 deletions

View File

@ -15,10 +15,10 @@ package de.bixilon.minosoft.gui.rendering.gui.elements
import de.bixilon.minosoft.gui.rendering.RenderConstants
import de.bixilon.minosoft.gui.rendering.gui.hud.HUDRenderer
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIMesh
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIMeshCache
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions
import de.bixilon.minosoft.gui.rendering.util.mesh.Mesh
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.isGreater
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.isSmaller
@ -26,6 +26,7 @@ import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.max
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.min
import de.bixilon.minosoft.gui.rendering.util.vec.vec4.Vec4iUtil.EMPTY
import de.bixilon.minosoft.gui.rendering.util.vec.vec4.Vec4iUtil.spaceSize
import de.bixilon.minosoft.util.collections.floats.DirectArrayFloatList
import glm_.vec2.Vec2i
import glm_.vec4.Vec4i
@ -41,7 +42,9 @@ abstract class Element(val hudRenderer: HUDRenderer) {
_parent = value
silentApply()
}
protected var cache = GUIMeshCache(hudRenderer.matrix, Mesh.TRIANGLE_TO_QUAD_ORDER, 0)
@Deprecated("Warning: Should not be directly accessed!")
val cache = GUIMeshCache(hudRenderer.matrix, renderWindow.renderSystem.primitiveMeshOrder, 1000)
open var cacheEnabled: Boolean = true
open var initialCacheSize: Int = 100
open var cacheUpToDate: Boolean = false
@ -123,21 +126,36 @@ abstract class Element(val hudRenderer: HUDRenderer) {
*/
fun render(offset: Vec2i, z: Int, consumer: GUIVertexConsumer, options: GUIVertexOptions?): Int {
val offset = Vec2i(offset)
var directRendering = false
if (consumer is GUIMesh && consumer.data == cache.data) {
directRendering = true
}
if (RenderConstants.DISABLE_GUI_CACHE || !cacheEnabled) {
return forceRender(offset, z, consumer, options)
if (directRendering) {
cache.clear()
}
val maxZ = forceRender(offset, z, consumer, options)
if (directRendering) {
cache.revision++
}
return maxZ
}
if (!cacheUpToDate || cache.offset != offset || hudRenderer.matrixChange || cache.matrix !== hudRenderer.matrix || z != cache.z) {
val cache = GUIMeshCache(hudRenderer.matrix, renderWindow.renderSystem.primitiveMeshOrder)
this.cache.clear()
cache.matrix = hudRenderer.matrix
cache.offset = Vec2i(offset)
cache.z = z
val maxZ = forceRender(offset, z, cache, options)
cache.maxZ = maxZ
cache.data.finish()
this.cache = cache
if (cache.data !is DirectArrayFloatList) {
// raw mesh data
cache.data.finish()
}
cacheUpToDate = true
}
consumer.addCache(cache)
if (!directRendering) {
consumer.addCache(cache)
}
return cache.maxZ
}

View File

@ -37,7 +37,6 @@ import de.bixilon.minosoft.gui.rendering.gui.hud.elements.other.WorldInfoHUDElem
import de.bixilon.minosoft.gui.rendering.gui.hud.elements.scoreboard.ScoreboardHUDElement
import de.bixilon.minosoft.gui.rendering.gui.hud.elements.tab.TabListHUDElement
import de.bixilon.minosoft.gui.rendering.gui.hud.elements.title.TitleHUDElement
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIMesh
import de.bixilon.minosoft.gui.rendering.modding.events.ResizeWindowEvent
import de.bixilon.minosoft.gui.rendering.system.base.IntegratedBufferTypes
import de.bixilon.minosoft.gui.rendering.system.base.RenderSystem
@ -49,7 +48,6 @@ import de.bixilon.minosoft.util.KUtil.synchronizedMapOf
import de.bixilon.minosoft.util.KUtil.toResourceLocation
import de.bixilon.minosoft.util.KUtil.toSynchronizedMap
import de.bixilon.minosoft.util.KUtil.unsafeCast
import de.bixilon.minosoft.util.collections.DirectArrayFloatList
import glm_.glm
import glm_.mat4x4.Mat4
import glm_.vec2.Vec2
@ -61,7 +59,6 @@ class HUDRenderer(
) : Renderer, OtherDrawable {
override val renderSystem: RenderSystem = renderWindow.renderSystem
val shader = renderWindow.renderSystem.createShader("minosoft:hud".toResourceLocation())
private lateinit var mesh: GUIMesh
var scaledSize: Vec2i = renderWindow.window.size
var matrix: Mat4 = Mat4()
private var enabled = true
@ -144,6 +141,9 @@ class HUDRenderer(
for (element in this.hudElements.toSynchronizedMap().values) {
element.postInit()
if (element is LayoutedHUDElement<*>) {
element.initMesh()
}
}
}
@ -154,15 +154,6 @@ class HUDRenderer(
}
override fun drawOther() {
val data = if (this::mesh.isInitialized) {
mesh.unload()
mesh.data.buffer.clear()
mesh.data
} else {
DirectArrayFloatList()
}
mesh = GUIMesh(renderWindow, matrix, data)
val hudElements = hudElements.toSynchronizedMap().values
val time = System.currentTimeMillis()
@ -192,13 +183,18 @@ class HUDRenderer(
element.draw()
}
if (element is LayoutedHUDElement<*>) {
z += element.layout.render(element.layoutOffset, z, mesh, null)
z += element.prepare(z)
}
}
setup()
mesh.load()
mesh.draw()
for (element in hudElements) {
if (element !is LayoutedHUDElement<*> || !element.enabled || element.mesh.data.isEmpty) {
continue
}
element.mesh.draw()
}
if (matrixChange) {
matrixChange = false

View File

@ -17,11 +17,17 @@ import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.gui.elements.Element
import de.bixilon.minosoft.gui.rendering.gui.hud.HUDElement
import de.bixilon.minosoft.gui.rendering.gui.hud.HUDRenderer
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIMesh
import de.bixilon.minosoft.gui.rendering.util.mesh.Mesh
import de.bixilon.minosoft.util.collections.floats.DirectArrayFloatList
import glm_.vec2.Vec2i
abstract class LayoutedHUDElement<T : Element>(final override val hudRenderer: HUDRenderer) : HUDElement {
override val renderWindow: RenderWindow = hudRenderer.renderWindow
final override val renderWindow: RenderWindow = hudRenderer.renderWindow
override var enabled = true
var mesh: GUIMesh = GUIMesh(renderWindow, hudRenderer.matrix, DirectArrayFloatList(1000))
private var lastRevision = 0L
abstract val layout: T
abstract val layoutOffset: Vec2i
@ -29,4 +35,32 @@ abstract class LayoutedHUDElement<T : Element>(final override val hudRenderer: H
override fun tick() {
layout.tick()
}
private fun createNewMesh() {
val mesh = this.mesh
if (mesh.state == Mesh.MeshStates.LOADED) {
mesh.unload()
}
this.mesh = GUIMesh(renderWindow, hudRenderer.matrix, mesh.data)
}
fun prepare(z: Int): Int {
val layoutOffset = layoutOffset
val usedZ = layout.render(layoutOffset, z, mesh, null)
val revision = layout.cache.revision
if (revision != lastRevision) {
createNewMesh()
this.mesh.load()
this.lastRevision = revision
}
return usedZ
}
fun initMesh() {
layout.cache.data = mesh.data
mesh.load()
}
}

View File

@ -25,7 +25,7 @@ import de.bixilon.minosoft.gui.rendering.input.camera.hit.BlockRaycastHit
import de.bixilon.minosoft.gui.rendering.input.camera.hit.EntityRaycastHit
import de.bixilon.minosoft.gui.rendering.system.base.BlendingFunctions
import de.bixilon.minosoft.util.KUtil.toResourceLocation
import de.bixilon.minosoft.util.collections.DirectArrayFloatList
import de.bixilon.minosoft.util.collections.floats.DirectArrayFloatList
class CrosshairHUDElement(hudRenderer: HUDRenderer) : CustomHUDElement(hudRenderer) {
private lateinit var crosshairAtlasElement: HUDAtlasElement

View File

@ -19,7 +19,7 @@ import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.AbstractTexture
import de.bixilon.minosoft.gui.rendering.util.mesh.Mesh
import de.bixilon.minosoft.gui.rendering.util.mesh.MeshStruct
import de.bixilon.minosoft.util.collections.DirectArrayFloatList
import de.bixilon.minosoft.util.collections.floats.DirectArrayFloatList
import glm_.mat4x4.Mat4
import glm_.vec2.Vec2
import glm_.vec2.Vec2t

View File

@ -16,27 +16,39 @@ package de.bixilon.minosoft.gui.rendering.gui.mesh
import de.bixilon.minosoft.data.text.RGBColor
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.AbstractTexture
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY
import de.bixilon.minosoft.util.collections.ArrayFloatList
import de.bixilon.minosoft.util.collections.floats.AbstractFloatList
import de.bixilon.minosoft.util.collections.floats.HeapArrayFloatList
import glm_.mat4x4.Mat4
import glm_.vec2.Vec2
import glm_.vec2.Vec2i
import glm_.vec2.Vec2t
class GUIMeshCache(
val matrix: Mat4,
var matrix: Mat4,
override val order: Array<Pair<Int, Int>>,
initialCacheSize: Int = 1000,
var data: AbstractFloatList = HeapArrayFloatList(initialCacheSize),
) : GUIVertexConsumer {
val data: ArrayFloatList = ArrayFloatList(initialCacheSize)
var revision: Long = 0
var offset: Vec2i = Vec2i.EMPTY
var z: Int = 0
var maxZ: Int = 0
fun clear() {
if (data.finished) {
data = HeapArrayFloatList(initialSize = data.size)
} else {
data.clear()
}
}
override fun addVertex(position: Vec2t<*>, z: Int, texture: AbstractTexture, uv: Vec2, tint: RGBColor, options: GUIVertexOptions?) {
data.addAll(GUIMesh.createVertex(matrix, position, z, texture, uv, tint, options))
revision++
}
override fun addCache(cache: GUIMeshCache) {
data.addAll(cache.data)
revision++
}
}

View File

@ -27,9 +27,13 @@ class FloatOpenGLVertexBuffer(override val structure: MeshStruct, data: FloatBuf
glBindVertexArray(vao)
bind()
val previousLimit = buffer.limit()
val previousPosition = buffer.position()
buffer.limit(buffer.position())
buffer.flip()
glBufferData(type.gl, buffer, drawTypes.gl)
buffer.limit(previousLimit)
buffer.position(previousPosition)
state = RenderBufferStates.UPLOADED
_data = null

View File

@ -16,7 +16,7 @@ package de.bixilon.minosoft.gui.rendering.util.mesh
import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.system.base.buffer.vertex.FloatVertexBuffer
import de.bixilon.minosoft.gui.rendering.system.base.buffer.vertex.PrimitiveTypes
import de.bixilon.minosoft.util.collections.DirectArrayFloatList
import de.bixilon.minosoft.util.collections.floats.DirectArrayFloatList
import glm_.vec2.Vec2
import glm_.vec3.Vec3
@ -53,6 +53,7 @@ abstract class Mesh(
_data = null
}
vertices = buffer.vertices
state = MeshStates.LOADED
}
fun draw() {

View File

@ -0,0 +1,15 @@
package de.bixilon.minosoft.util.collections
abstract class AbstractPrimitiveList<T> : Clearable {
var finished: Boolean = false
protected set
abstract val limit: Int
abstract val size: Int
abstract val isEmpty: Boolean
protected abstract fun ensureSize(needed: Int)
abstract fun add(value: T)
abstract fun finish()
}

View File

@ -0,0 +1,11 @@
package de.bixilon.minosoft.util.collections.floats
import de.bixilon.minosoft.util.collections.AbstractPrimitiveList
abstract class AbstractFloatList : AbstractPrimitiveList<Float>() {
abstract fun addAll(floats: FloatArray)
abstract fun addAll(floatList: AbstractFloatList)
abstract fun toArray(): FloatArray
}

View File

@ -11,25 +11,24 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.util.collections
package de.bixilon.minosoft.util.collections.floats
import de.bixilon.minosoft.util.KUtil
import org.lwjgl.system.MemoryUtil.memAllocFloat
import org.lwjgl.system.MemoryUtil.memFree
import java.nio.BufferOverflowException
import java.nio.FloatBuffer
class DirectArrayFloatList(
initialSize: Int = DEFAULT_INITIAL_SIZE,
) {
var buffer: FloatBuffer = memAllocFloat(initialSize) // ToDo: Clear when disconnected
) : AbstractFloatList() {
var buffer: FloatBuffer = memAllocFloat(initialSize)
private set
var finalized: Boolean = false
private set
val capacity: Int
override val limit: Int
get() = buffer.capacity()
val size: Int
override val size: Int
get() = buffer.position()
val isEmpty: Boolean
override val isEmpty: Boolean
get() = size == 0
private var unloaded = false
@ -43,17 +42,17 @@ class DirectArrayFloatList(
private var outputUpToDate = false
private fun checkFinalized() {
if (finalized) {
if (finished) {
throw IllegalStateException("ArrayFloatList is already finalized!")
}
}
private fun ensureSize(needed: Int) {
override fun ensureSize(needed: Int) {
checkFinalized()
if (capacity - size >= needed) {
if (limit - size >= needed) {
return
}
var newSize = capacity
var newSize = limit
while (newSize - size < needed) {
newSize += nextGrowStep
}
@ -70,35 +69,40 @@ class DirectArrayFloatList(
memFree(oldBuffer)
}
fun add(float: Float) {
override fun add(value: Float) {
ensureSize(1)
buffer.put(float)
buffer.put(value)
outputUpToDate = false
}
fun addAll(floats: FloatArray) {
override fun addAll(floats: FloatArray) {
ensureSize(floats.size)
buffer.put(floats)
try {
buffer.put(floats)
} catch (exception: BufferOverflowException) {
ensureSize(floats.size)
exception.printStackTrace()
}
outputUpToDate = false
}
fun addAll(floatList: DirectArrayFloatList) {
ensureSize(floatList.size)
if (FLOAT_PUT_METHOD == null) { // Java < 16
for (i in 0 until floatList.buffer.position()) {
buffer.put(floatList.buffer.get(i))
override fun addAll(floatList: AbstractFloatList) {
if (floatList is DirectArrayFloatList) {
ensureSize(floatList.size)
if (FLOAT_PUT_METHOD == null) { // Java < 16
for (i in 0 until floatList.buffer.position()) {
buffer.put(floatList.buffer.get(i))
}
} else {
FLOAT_PUT_METHOD.invoke(buffer, buffer.position(), floatList.buffer, 0, floatList.buffer.position())
buffer.position(buffer.position() + floatList.buffer.position())
}
} else {
FLOAT_PUT_METHOD.invoke(buffer, buffer.position(), floatList.buffer, 0, floatList.buffer.position())
buffer.position(buffer.position() + floatList.buffer.position())
addAll(floatList.toArray())
}
}
fun addAll(floatList: ArrayFloatList) {
ensureSize(floatList.size)
buffer.put(floatList.toArray())
}
private fun checkOutputArray() {
if (outputUpToDate) {
return
@ -111,7 +115,7 @@ class DirectArrayFloatList(
outputUpToDate = true
}
fun toArray(): FloatArray {
override fun toArray(): FloatArray {
checkOutputArray()
return output
}
@ -119,12 +123,20 @@ class DirectArrayFloatList(
fun unload() {
check(!unloaded) { "Already unloaded!" }
unloaded = true
finalized = true // Is unloaded
finished = true // Is unloaded
memFree(buffer)
}
fun finish() {
finalized = true
override fun clear() {
buffer.clear()
if (output.isNotEmpty()) {
output = FloatArray(0)
}
outputUpToDate = false
}
override fun finish() {
finished = true
val oldBuffer = buffer
buffer = memAllocFloat(oldBuffer.position())
if (FLOAT_PUT_METHOD == null) { // Java < 16

View File

@ -10,19 +10,16 @@
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.util.collections
package de.bixilon.minosoft.util.collections.floats
class ArrayFloatList(
class HeapArrayFloatList(
initialSize: Int = DEFAULT_INITIAL_SIZE,
) {
) : AbstractFloatList() {
private var data: FloatArray = FloatArray(initialSize)
var finalized: Boolean = false
private set
val limit: Int
override val limit: Int
get() = data.size
var size = 0
private set
val isEmpty: Boolean
override var size = 0
override val isEmpty: Boolean
get() = size == 0
private val nextGrowStep = when {
@ -35,19 +32,19 @@ class ArrayFloatList(
private var outputUpToDate = false
private fun checkFinalized() {
if (finalized) {
if (finished) {
throw IllegalStateException("ArrayFloatList is already finalized!")
}
}
fun clear() {
override fun clear() {
checkFinalized()
size = 0
outputUpToDate = false
output = FloatArray(0)
}
private fun ensureSize(needed: Int) {
override fun ensureSize(needed: Int) {
checkFinalized()
if (limit - size >= needed) {
return
@ -61,25 +58,29 @@ class ArrayFloatList(
System.arraycopy(oldData, 0, data, 0, oldData.size)
}
fun add(float: Float) {
override fun add(value: Float) {
ensureSize(1)
data[size++] = float
data[size++] = value
outputUpToDate = false
}
fun addAll(floats: FloatArray) {
override fun addAll(floats: FloatArray) {
ensureSize(floats.size)
System.arraycopy(floats, 0, data, size, floats.size)
size += floats.size
outputUpToDate = false
}
fun addAll(floatList: ArrayFloatList) {
override fun addAll(floatList: AbstractFloatList) {
ensureSize(floatList.size)
val source = if (floatList.finalized) {
floatList.output
val source: FloatArray = if (floatList is HeapArrayFloatList) {
if (floatList.finished) {
floatList.output
} else {
floatList.data
}
} else {
floatList.data
floatList.toArray()
}
System.arraycopy(source, 0, data, size, floatList.size)
size += floatList.size
@ -94,13 +95,13 @@ class ArrayFloatList(
outputUpToDate = true
}
fun toArray(): FloatArray {
override fun toArray(): FloatArray {
checkOutputArray()
return output
}
fun finish() {
finalized = true
override fun finish() {
finished = true
checkOutputArray()
data = FloatArray(0)
}