Skip to content

Commit 0da7658

Browse files
authored
Reworked the 3d api (#144)
This pull request simplifies the 3d rendering by introducing the `Treed` class and the `ShapeBuilder`. Boiler plate objects and classes were also removed to help understanding how to use them correctly. 3d related function names were renamed for consistency (ex: ofBox, filledBox -> box, filled) A major improvement comes in the `PersistentBuffer` class where memory comparison was previously checked by groups of 8 and 1 bytes to detect mismatch and is now replaced by `ByteBuffer.mismatch` which uses SIMD operations (I believe so) to return the index of the first mismatch between 2 buffers. Note that `BlockESP` had some features removed to accommodate the recent changes and they will be reintroduced further down the development. The `Particles` module was fully added back into the mod. Another pull request should be expected not that far from now which will rework the rendering backend to simplify future refactors.
1 parent e209394 commit 0da7658

38 files changed

+630
-802
lines changed

src/main/kotlin/com/lambda/event/events/RenderEvent.kt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,22 @@
1717

1818
package com.lambda.event.events
1919

20+
import com.lambda.context.SafeContext
2021
import com.lambda.event.Event
2122
import com.lambda.event.callback.Cancellable
2223
import com.lambda.event.callback.ICancellable
23-
import com.lambda.graphics.renderer.esp.global.DynamicESP
24-
import com.lambda.graphics.renderer.esp.global.StaticESP
24+
import com.lambda.event.listener.SafeListener.Companion.listen
25+
import com.lambda.graphics.renderer.esp.ShapeBuilder
26+
import com.lambda.graphics.renderer.esp.Treed
2527

26-
sealed class RenderEvent {
27-
class World : Event
28-
29-
class StaticESP : Event {
30-
val renderer = StaticESP
31-
}
28+
fun Any.onStaticRender(block: SafeContext.(ShapeBuilder) -> Unit) =
29+
listen<RenderEvent.Upload> { block(ShapeBuilder(Treed.Static.faceBuilder, Treed.Static.edgeBuilder)) }
30+
fun Any.onDynamicRender(block: SafeContext.(ShapeBuilder) -> Unit) =
31+
listen<RenderEvent.Upload> { block(ShapeBuilder(Treed.Dynamic.faceBuilder, Treed.Dynamic.edgeBuilder)) }
3232

33-
class DynamicESP : Event {
34-
val renderer = DynamicESP
35-
}
33+
sealed class RenderEvent {
34+
object Upload : Event
35+
object Render : Event
3636

3737
class UpdateTarget : ICancellable by Cancellable()
3838
}

src/main/kotlin/com/lambda/graphics/RenderMain.kt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ import com.lambda.event.listener.SafeListener.Companion.listen
2525
import com.lambda.graphics.gl.GlStateUtils.setupGL
2626
import com.lambda.graphics.gl.Matrices
2727
import com.lambda.graphics.gl.Matrices.resetMatrices
28-
import com.lambda.graphics.renderer.esp.global.DynamicESP
29-
import com.lambda.graphics.renderer.esp.global.StaticESP
28+
import com.lambda.graphics.renderer.esp.Treed
3029
import com.lambda.util.math.Vec2d
3130
import com.mojang.blaze3d.opengl.GlStateManager
3231
import com.mojang.blaze3d.systems.RenderSystem
@@ -56,21 +55,22 @@ object RenderMain {
5655

5756
GlStateManager._glBindFramebuffer(GL_FRAMEBUFFER, prevFramebuffer)
5857

59-
RenderEvent.World().post()
60-
StaticESP.render()
61-
DynamicESP.render()
58+
Treed.Static.render()
59+
Treed.Dynamic.render()
60+
61+
RenderEvent.Render.post()
6262
}
6363
}
6464

6565
init {
6666
listen<TickEvent.Post> {
67-
StaticESP.clear()
68-
RenderEvent.StaticESP().post()
69-
StaticESP.upload()
67+
Treed.Static.clear()
68+
Treed.Dynamic.clear()
69+
70+
RenderEvent.Upload.post()
7071

71-
DynamicESP.clear()
72-
RenderEvent.DynamicESP().post()
73-
DynamicESP.upload()
72+
Treed.Static.upload()
73+
Treed.Dynamic.upload()
7474
}
7575
}
7676
}

src/main/kotlin/com/lambda/graphics/buffer/DynamicByteBuffer.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ class DynamicByteBuffer private constructor(initialCapacity: Int) {
150150
val offset = position - pointer
151151

152152
memCopy(pointer, newPointer, offset)
153-
data = newBuffer
154153

154+
data = newBuffer
155155
pointer = newPointer
156156
position = newPointer + offset
157157
capacity = newCapacity
@@ -176,6 +176,13 @@ class DynamicByteBuffer private constructor(initialCapacity: Int) {
176176
capacity = newCapacity
177177
}
178178

179+
/**
180+
* Returns the relative index of the first mismatch between this and the given buffer, otherwise -1 if no mismatch.
181+
*
182+
* @see [ByteBuffer.mismatch]
183+
*/
184+
fun mismatch(other: DynamicByteBuffer) = data.mismatch(other.data)
185+
179186
companion object {
180187
/**
181188
* Creates a new DynamicByteBuffer with specified initial capacity
@@ -184,4 +191,4 @@ class DynamicByteBuffer private constructor(initialCapacity: Int) {
184191
fun dynamicByteBuffer(initialCapacity: Int) =
185192
DynamicByteBuffer(initialCapacity)
186193
}
187-
}
194+
}

src/main/kotlin/com/lambda/graphics/pipeline/PersistentBuffer.kt

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,8 @@
1818
package com.lambda.graphics.pipeline
1919

2020
import com.lambda.graphics.buffer.Buffer.Companion.createPipelineBuffer
21-
import com.lambda.graphics.buffer.DynamicByteBuffer
2221
import com.lambda.graphics.buffer.DynamicByteBuffer.Companion.dynamicByteBuffer
2322
import com.lambda.graphics.gl.kibibyte
24-
import org.lwjgl.system.MemoryUtil
2523
import org.lwjgl.system.MemoryUtil.memCopy
2624

2725
/**
@@ -64,7 +62,7 @@ class PersistentBuffer(
6462
}
6563

6664
if (snapshotData > 0 && snapshot.capacity >= byteBuffer.bytesPut) {
67-
if (memcmp(snapshot, byteBuffer, uploadOffset, dataCount)) return
65+
if (snapshot.mismatch(byteBuffer) >= 0) return
6866
}
6967

7068
glBuffer.update(uploadOffset, dataCount, dataStart)
@@ -90,30 +88,4 @@ class PersistentBuffer(
9088
}
9189

9290
fun use(block: () -> Unit) = glBuffer.bind { block() }
93-
94-
private fun memcmp(a: DynamicByteBuffer, b: DynamicByteBuffer, position: Long, size: Long): Boolean {
95-
if (a.capacity != b.capacity) return false
96-
97-
val end = position + size
98-
var head = position
99-
100-
// Process the aligned bytes in chunks of 8 until we've reached the end
101-
while (head + 8 <= end) {
102-
val first = MemoryUtil.memGetLong(a.pointer + head)
103-
val second = MemoryUtil.memGetLong(b.pointer + head)
104-
if (first != second) return false
105-
106-
head += 8
107-
}
108-
109-
while (head < end) {
110-
val first = MemoryUtil.memGetByte(a.pointer + head)
111-
val second = MemoryUtil.memGetByte(b.pointer + head)
112-
if (first != second) return false
113-
114-
head++
115-
}
116-
117-
return true
118-
}
11991
}

src/main/kotlin/com/lambda/graphics/pipeline/VertexBuilder.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ import org.joml.Vector4d
2929
class VertexBuilder(
3030
private val direct: VertexPipeline? = null
3131
) {
32-
val vertices by lazy { mutableListOf<Attribute>() }
33-
val indices by lazy { mutableListOf<Int>() }
32+
val vertices by lazy(LazyThreadSafetyMode.PUBLICATION) { mutableListOf<Attribute>() }
33+
val indices by lazy(LazyThreadSafetyMode.PUBLICATION) { mutableListOf<Int>() }
3434

3535
private var verticesCounter = 0
3636

@@ -115,9 +115,6 @@ class VertexBuilder(
115115
fun collect(vararg indices: Int) =
116116
indices
117117

118-
fun use(block: VertexBuilder.() -> Unit) =
119-
apply(block)
120-
121118
/**
122119
* Creates a new vertex with specified attributes
123120
* @param block Configuration lambda for defining vertex attributes

src/main/kotlin/com/lambda/graphics/renderer/esp/ChunkedESP.kt

Lines changed: 30 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ package com.lambda.graphics.renderer.esp
2020
import com.lambda.event.events.RenderEvent
2121
import com.lambda.event.events.TickEvent
2222
import com.lambda.event.events.WorldEvent
23+
import com.lambda.event.events.onStaticRender
2324
import com.lambda.event.listener.SafeListener.Companion.listen
2425
import com.lambda.event.listener.SafeListener.Companion.listenConcurrently
25-
import com.lambda.graphics.renderer.esp.impl.StaticESPRenderer
2626
import com.lambda.module.modules.client.StyleEditor
2727
import com.lambda.threading.awaitMainThread
28+
import com.lambda.util.world.FastVector
29+
import com.lambda.util.world.fastVectorOf
2830
import net.minecraft.util.math.ChunkPos
2931
import net.minecraft.world.World
3032
import net.minecraft.world.chunk.WorldChunk
@@ -33,13 +35,11 @@ import java.util.concurrent.ConcurrentLinkedDeque
3335

3436
class ChunkedESP private constructor(
3537
owner: Any,
36-
private val update: StaticESPRenderer.(World, Int, Int, Int) -> Unit
38+
private val update: ShapeBuilder.(World, FastVector) -> Unit
3739
) {
3840
private val rendererMap = ConcurrentHashMap<Long, EspChunk>()
3941
private val WorldChunk.renderer
40-
get() = rendererMap.getOrPut(pos.toLong()) {
41-
EspChunk(this, this@ChunkedESP)
42-
}
42+
get() = rendererMap.getOrPut(pos.toLong()) { EspChunk(this, this@ChunkedESP) }
4343

4444
private val uploadQueue = ConcurrentLinkedDeque<() -> Unit>()
4545
private val rebuildQueue = ConcurrentLinkedDeque<EspChunk>()
@@ -52,92 +52,63 @@ class ChunkedESP private constructor(
5252
}
5353

5454
init {
55-
listenConcurrently<WorldEvent.BlockUpdate.Client> { event ->
56-
world.getWorldChunk(event.pos).renderer.notifyChunks()
57-
}
55+
//listen<WorldEvent.BlockUpdate.Client> { rebuildQueue.add(rendererMap[ChunkPos.toLong(it.pos)] ?: return@listen) }
56+
listen<WorldEvent.ChunkEvent.Load> { it.chunk.renderer.notifyChunks() }
57+
listen<WorldEvent.ChunkEvent.Unload> { rendererMap.remove(it.chunk.pos.toLong())?.notifyChunks() }
5858

59-
listenConcurrently<WorldEvent.ChunkEvent.Load> { event ->
60-
event.chunk.renderer.notifyChunks()
61-
}
62-
63-
listenConcurrently<WorldEvent.ChunkEvent.Unload> { event ->
64-
rendererMap.remove(event.chunk.pos.toLong())?.notifyChunks()
65-
}
66-
67-
owner.listenConcurrently<TickEvent.Pre> {
59+
listenConcurrently<TickEvent.Post> {
6860
if (++ticks % StyleEditor.updateFrequency == 0) {
6961
val polls = minOf(StyleEditor.rebuildsPerTick, rebuildQueue.size)
7062

71-
repeat(polls) {
72-
rebuildQueue.poll()?.rebuild()
73-
}
63+
repeat(polls) { rebuildQueue.poll()?.rebuild() }
64+
7465
ticks = 0
7566
}
7667
}
7768

78-
owner.listen<TickEvent.Pre> {
79-
if (uploadQueue.isEmpty()) return@listen
69+
owner.onStaticRender {
70+
if (uploadQueue.isEmpty()) return@onStaticRender
8071

8172
val polls = minOf(StyleEditor.uploadsPerTick, uploadQueue.size)
8273

83-
repeat(polls) {
84-
uploadQueue.poll()?.invoke()
85-
}
74+
repeat(polls) { uploadQueue.poll()?.invoke() }
8675
}
8776

88-
owner.listen<RenderEvent.World> {
89-
rendererMap.values.forEach {
90-
it.renderer?.render()
91-
}
92-
}
77+
owner.listen<RenderEvent.Render> { rendererMap.values.forEach { it.renderer.render() } }
9378
}
9479

9580
companion object {
9681
fun Any.newChunkedESP(
97-
update: StaticESPRenderer.(World, Int, Int, Int) -> Unit
98-
) = ChunkedESP(this, update)
82+
update: ShapeBuilder.(World, FastVector) -> Unit
83+
) = ChunkedESP(this@newChunkedESP, update)
9984
}
10085

10186
private class EspChunk(val chunk: WorldChunk, val owner: ChunkedESP) {
102-
var renderer: StaticESPRenderer? = null
103-
104-
private val chunkOffsets = listOf(1 to 0, 0 to 1, -1 to 0, 0 to -1)
87+
var renderer = Treed(static = true)
88+
private val builder: ShapeBuilder
89+
get() = ShapeBuilder(renderer.faceBuilder, renderer.edgeBuilder)
10590

106-
val neighbors = chunkOffsets.map {
107-
ChunkPos(chunk.pos.x + it.first, chunk.pos.z + it.second)
108-
}.toTypedArray()
91+
val neighbors = listOf(1 to 0, 0 to 1, -1 to 0, 0 to -1)
92+
.map { ChunkPos(chunk.pos.x + it.first, chunk.pos.z + it.second) }
10993

11094
fun notifyChunks() {
11195
neighbors.forEach {
11296
owner.rendererMap[it.toLong()]?.let {
113-
owner.rebuildQueue.apply {
114-
if (!contains(it)) add(it)
115-
}
97+
if (!owner.rebuildQueue.contains(it))
98+
owner.rebuildQueue.add(it)
11699
}
117100
}
118101
}
119102

120103
suspend fun rebuild() {
121-
val newRenderer = awaitMainThread { StaticESPRenderer() }
104+
renderer = awaitMainThread { Treed(static = true) }
122105

123-
iterateChunk { x, y, z ->
124-
owner.update(newRenderer, chunk.world, x, y, z)
125-
}
106+
for (x in chunk.pos.startX..chunk.pos.endX)
107+
for (z in chunk.pos.startZ..chunk.pos.endZ)
108+
for (y in chunk.bottomY..chunk.height)
109+
owner.update(builder, chunk.world, fastVectorOf(x, y, z))
126110

127-
owner.uploadQueue.add {
128-
newRenderer.upload()
129-
renderer = newRenderer
130-
}
131-
}
132-
133-
private fun iterateChunk(block: (Int, Int, Int) -> Unit) = chunk.apply {
134-
for (x in pos.startX..pos.endX) {
135-
for (z in pos.startZ..pos.endZ) {
136-
for (y in bottomY..height) {
137-
block(x, y, z)
138-
}
139-
}
140-
}
111+
owner.uploadQueue.add { renderer.upload() }
141112
}
142113
}
143114
}

src/main/kotlin/com/lambda/graphics/renderer/esp/DynamicAABB.kt

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class DynamicAABB {
2626
private var prev: Box? = null
2727
private var curr: Box? = null
2828

29+
val pair get() = prev?.let { prev -> curr?.let { curr -> prev to curr } }
30+
2931
fun update(box: Box): DynamicAABB {
3032
prev = curr ?: box
3133
curr = box
@@ -38,16 +40,6 @@ class DynamicAABB {
3840
curr = null
3941
}
4042

41-
fun getBoxPair(): Pair<Box, Box>? {
42-
prev?.let { previous ->
43-
curr?.let { current ->
44-
return previous to current
45-
}
46-
}
47-
48-
return null
49-
}
50-
5143
companion object {
5244
val Entity.dynamicBox
5345
get() = DynamicAABB().apply {

0 commit comments

Comments
 (0)