Skip to content

Commit bdd9335

Browse files
committed
尝试写了一个异步渲染粒子, 但是显然失败了
1 parent 4a1773e commit bdd9335

7 files changed

Lines changed: 257 additions & 11 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package cn.coostack.cooparticlesapi.mixin;
2+
3+
import cn.coostack.cooparticlesapi.CooParticleAPI;
4+
import cn.coostack.cooparticlesapi.utils.ParticleAsyncRenderHelper;
5+
import com.google.common.collect.EvictingQueue;
6+
import com.llamalad7.mixinextras.sugar.Local;
7+
import net.minecraft.client.particle.Particle;
8+
import net.minecraft.client.particle.ParticleManager;
9+
import net.minecraft.client.particle.ParticleTextureSheet;
10+
import net.minecraft.client.render.*;
11+
import net.minecraft.client.texture.TextureManager;
12+
import org.spongepowered.asm.mixin.Final;
13+
import org.spongepowered.asm.mixin.Mixin;
14+
import org.spongepowered.asm.mixin.Shadow;
15+
import org.spongepowered.asm.mixin.injection.At;
16+
import org.spongepowered.asm.mixin.injection.ModifyVariable;
17+
import org.spongepowered.asm.mixin.injection.Redirect;
18+
19+
import java.util.List;
20+
import java.util.Queue;
21+
22+
/**
23+
* TODO
24+
* 异步生成BuiltBuffer时
25+
* 在主线程draw会导致 buffer no longer valid的异常
26+
* 原理未知
27+
*/
28+
@Mixin(ParticleManager.class)
29+
public class ParticleManagerRenderMixin {
30+
@Redirect(method = "renderParticles", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/ParticleTextureSheet;begin(Lnet/minecraft/client/render/Tessellator;Lnet/minecraft/client/texture/TextureManager;)Lnet/minecraft/client/render/BufferBuilder;"))
31+
public BufferBuilder renderParticles(ParticleTextureSheet instance,
32+
Tessellator tessellator,
33+
TextureManager textureManager,
34+
@Local(ordinal = 0) Queue<Particle> queue,
35+
@Local(argsOnly = true) Camera camera,
36+
@Local(argsOnly = true) float tickDelta,
37+
@Local(ordinal = 0) ParticleTextureSheet sheet) {
38+
Object[] array = queue.toArray();
39+
// 异步调用...
40+
ParticleAsyncRenderHelper.INSTANCE
41+
.renderParticlesAsync(array, instance, textureManager, camera, tickDelta);
42+
// 中断原来的判断
43+
return null;
44+
}
45+
}

src/main/kotlin/cn/coostack/cooparticlesapi/CooParticleAPIClient.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import cn.coostack.cooparticlesapi.test.particle.style.ExampleSequencedStyle
3232
import cn.coostack.cooparticlesapi.test.particle.style.ExampleStyle
3333
import cn.coostack.cooparticlesapi.test.particle.style.RomaMagicTestStyle
3434
import cn.coostack.cooparticlesapi.test.particle.style.RotateTestStyle
35+
import cn.coostack.cooparticlesapi.utils.ParticleAsyncRenderHelper
3536
import net.fabricmc.api.ClientModInitializer
3637
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
3738
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientWorldEvents
@@ -114,16 +115,19 @@ object CooParticleAPIClient : ClientModInitializer {
114115
ParticleEmittersManager.clientEmitters.clear()
115116
ParticleStyleManager.clearAllVisible()
116117
ClientParticleGroupManager.clearAllVisible()
118+
ParticleAsyncRenderHelper.close()
117119
}
118120
ClientTickEvents.START_WORLD_TICK.register {
119121
ClientParticleGroupManager.doClientTick()
120122
ParticleStyleManager.doTickClient()
121123
ParticleEmittersManager.doTickClient()
124+
ParticleAsyncRenderHelper.reloadIfClosed()
122125
}
123126
ClientWorldEvents.AFTER_CLIENT_WORLD_CHANGE.register { _, _ ->
124127
ParticleEmittersManager.clientEmitters.clear()
125128
ParticleStyleManager.clearAllVisible()
126129
ClientParticleGroupManager.clearAllVisible()
130+
ParticleAsyncRenderHelper.reloadIfClosed()
127131
}
128132
}
129133

src/main/kotlin/cn/coostack/cooparticlesapi/config/APIConfig.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@ class APIConfig {
1515
*/
1616
var particleCountLimit = 65536
1717
get() = max(field, 1)
18+
19+
/**
20+
* Math3DUtil的 threadPool最大线程数
21+
*/
22+
var calculateThreadCount = 4
23+
get() = max(field, 1)
1824
}

src/main/kotlin/cn/coostack/cooparticlesapi/items/TestParticleItem.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import cn.coostack.cooparticlesapi.network.particle.emitters.impl.FireClassParti
1010
import cn.coostack.cooparticlesapi.network.particle.emitters.impl.LightningClassParticleEmitters
1111
import cn.coostack.cooparticlesapi.network.particle.emitters.impl.PhysicsParticleEmitters
1212
import cn.coostack.cooparticlesapi.network.particle.emitters.impl.PresetTestEmitters
13+
import cn.coostack.cooparticlesapi.network.particle.emitters.impl.SimpleParticleEmitters
1314
import cn.coostack.cooparticlesapi.network.particle.emitters.type.EmittersShootTypes
1415
import cn.coostack.cooparticlesapi.network.particle.style.ParticleStyleManager
1516
import cn.coostack.cooparticlesapi.particles.impl.ControlableCloudEffect
@@ -32,14 +33,20 @@ class TestParticleItem(settings: Settings) : Item(settings) {
3233
if (world.isClient) {
3334
return TypedActionResult.success(user.getStackInHand(hand))
3435
}
35-
// testRotate(world, user)
36+
testRotate(world, user)
3637
// CameraUtil.startShakeCamera(240, 0.25)
37-
testRomaCircle(world, user)
38-
38+
// testRomaCircle(world, user)
3939
// 线性阻力
4040
return super.use(world, user, hand)
4141
}
4242

43+
private fun testLargeParticles(world: World, user: PlayerEntity) {
44+
val emitters = SimpleParticleEmitters(user.eyePos, world, ControlableParticleData())
45+
emitters.count = 4096
46+
47+
ParticleEmittersManager.spawnEmitters(emitters)
48+
}
49+
4350
private fun testRotate(world: World, user: PlayerEntity) {
4451
val style = RotateTestStyle(user.uuid)
4552
ParticleStyleManager.spawnStyle(world as ServerWorld, user.pos, style)

src/main/kotlin/cn/coostack/cooparticlesapi/test/particle/style/RotateTestStyle.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class RotateTestStyle(val player: UUID, uuid: UUID = UUID.randomUUID()) :
2828

2929
override fun getCurrentFrames(): Map<StyleData, RelativeLocation> {
3030
return PointsBuilder()
31+
.addBall(5.0,200)
3132
.addLine(
3233
RelativeLocation(0.0, -2.0, 0.0),
3334
RelativeLocation(0.0, 4.0, 0.0),

src/main/kotlin/cn/coostack/cooparticlesapi/utils/Math3DUtil.kt

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
package cn.coostack.cooparticlesapi.utils
22

3+
import cn.coostack.cooparticlesapi.config.APIConfig
4+
import cn.coostack.cooparticlesapi.config.APIConfigManager
35
import net.minecraft.util.math.Vec3d
46
import org.joml.Quaterniond
57
import org.joml.Vector3d
68
import org.joml.Vector3f
79
import java.util.ArrayList
10+
import java.util.concurrent.CopyOnWriteArrayList
11+
import java.util.concurrent.Executors
12+
import java.util.concurrent.FutureTask
813
import kotlin.math.*
914
import kotlin.random.Random
1015

1116
object Math3DUtil {
1217
private val random = Random(System.currentTimeMillis())
18+
private val threadPool = Executors.newFixedThreadPool(APIConfigManager.getConfig().calculateThreadCount)
1319

1420
/**
1521
* 将RGB值转换为Minecraft粒子使用的 rgb值(/255)
@@ -470,8 +476,16 @@ object Math3DUtil {
470476
* @param angle 角度 输入一个弧度制角度
471477
*/
472478
fun rotateAsAxis(locList: List<RelativeLocation>, axis: RelativeLocation, angle: Double): List<RelativeLocation> {
479+
val q = Quaterniond()
480+
q.rotateAxis(angle, Vector3d(axis.x, axis.y, axis.z))
481+
val temp = Vector3d(0.0, 0.0, 0.0)
473482
for (loc in locList) {
474-
RotationMatrix.fromAxisAngle(axis, angle).applyTo(loc)
483+
// RotationMatrix.fromAxisAngle(axis, angle).applyTo(loc)
484+
temp.set(loc.x, loc.y, loc.z)
485+
temp.rotate(q)
486+
loc.x = temp.x
487+
loc.y = temp.y
488+
loc.z = temp.z
475489
}
476490
return locList
477491
}
@@ -484,10 +498,39 @@ object Math3DUtil {
484498
toPoint: RelativeLocation,
485499
axis: RelativeLocation
486500
): List<RelativeLocation> {
501+
return rotatePointsToPointAsync(shape, toPoint, axis, 10)
502+
}
503+
504+
/**
505+
* 让图形的对称轴指向某个点(图形跟着转变)
506+
*
507+
* 使用多线程并发修改shape的值 (FutureTask)
508+
*/
509+
fun rotatePointsToPointAsync(
510+
shape: List<RelativeLocation>,
511+
toPoint: RelativeLocation,
512+
axis: RelativeLocation,
513+
threads: Int
514+
): List<RelativeLocation> {
515+
val copy = CopyOnWriteArrayList(shape)
487516
// 同向共线
488517
if (axis.cross(toPoint).length() in -1e-5..1e-5 && axis.dot(toPoint) > 0) {
489518
return shape
490519
}
520+
if (copy.isEmpty()) return shape
521+
var actualThreads = threads
522+
if (threads >= copy.size) {
523+
actualThreads = copy.size
524+
}
525+
// 计算每一个线程处理的点的平均个数
526+
val taskPreThreadCount = copy.size / actualThreads
527+
var notHandledTaskCount = copy.size % actualThreads
528+
// 划分索引范围 从0开始
529+
// 索引计算规则如下 从0开始 到 taskPreThreadCount + n 结束 左闭右开
530+
// 下一个thread就是 taskPreThreadCount + n 开始 n一般为1或者0
531+
var currentIndex = 0
532+
533+
// 计算旋转四元数
491534
val q = Quaterniond()
492535
// 差值
493536
val na = axis.normalize()
@@ -504,14 +547,36 @@ object Math3DUtil {
504547
val toQ = Quaterniond()
505548
.rotateY(-toYaw)
506549
.rotateX(-toPitch)
507-
return shape.onEach {
508-
val vector = Vector3d(it.x, it.y, it.z)
509-
vector.rotate(q)
510-
vector.rotate(toQ)
511-
it.x = vector.x
512-
it.y = vector.y
513-
it.z = vector.z
550+
551+
// 开始分配旋转任务
552+
val tasks = ArrayList<FutureTask<Unit>>()
553+
repeat(actualThreads) {
554+
var next = currentIndex + taskPreThreadCount // 取到 taskHandledIndexStart ..< next
555+
if (notHandledTaskCount > 0) {
556+
next++
557+
notHandledTaskCount--
558+
}
559+
val taskHandledIndexStart = currentIndex
560+
currentIndex = next
561+
// 创建任务
562+
val vector = Vector3d(0.0, 0.0, 0.0)
563+
val task = FutureTask {
564+
for (i in taskHandledIndexStart..<next) {
565+
val it = copy[i]
566+
// 复用节约内存
567+
vector.set(it.x, it.y, it.z)
568+
vector.rotate(q)
569+
vector.rotate(toQ)
570+
it.x = vector.x
571+
it.y = vector.y
572+
it.z = vector.z
573+
}
574+
}
575+
threadPool.submit(task)
576+
tasks.add(task)
514577
}
578+
tasks.forEach { it.get() }
579+
return shape
515580
}
516581

517582
/**
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package cn.coostack.cooparticlesapi.utils
2+
3+
import cn.coostack.cooparticlesapi.config.APIConfigManager
4+
import net.minecraft.client.particle.Particle
5+
import net.minecraft.client.particle.ParticleTextureSheet
6+
import net.minecraft.client.render.BufferBuilder
7+
import net.minecraft.client.render.BufferRenderer
8+
import net.minecraft.client.render.BuiltBuffer
9+
import net.minecraft.client.render.Camera
10+
import net.minecraft.client.render.Tessellator
11+
import net.minecraft.client.texture.TextureManager
12+
import net.minecraft.util.crash.CrashException
13+
import net.minecraft.util.crash.CrashReport
14+
import org.joml.Vector3d
15+
import java.util.ArrayList
16+
import java.util.Arrays
17+
import java.util.concurrent.Executors
18+
import java.util.concurrent.FutureTask
19+
20+
object ParticleAsyncRenderHelper {
21+
@JvmStatic
22+
val threadCount: Int
23+
get() = APIConfigManager.getConfig().calculateThreadCount
24+
private var threadPool = Executors.newFixedThreadPool(threadCount)
25+
26+
27+
fun close() {
28+
threadPool.shutdownNow()
29+
}
30+
31+
fun reloadIfClosed() {
32+
if (!threadPool.isShutdown) {
33+
return
34+
}
35+
threadPool = Executors.newFixedThreadPool(threadCount)
36+
}
37+
38+
/**
39+
* 异步渲染粒子
40+
* TODO 会因为非法访问内存导致崩溃(原因未知)
41+
* 好像是顶点渲染的部分导致的
42+
*/
43+
fun renderParticlesAsync(
44+
particles: Array<Any>,
45+
instance: ParticleTextureSheet,
46+
textureManager: TextureManager,
47+
camera: Camera,
48+
tickDelta: Float
49+
) {
50+
var actualThreads = threadCount
51+
val size = particles.size
52+
if (size <= actualThreads) {
53+
actualThreads = particles.size
54+
}
55+
// 计算每一个线程处理的点的平均个数
56+
val taskPreThreadCount = size / actualThreads
57+
var notHandledTaskCount = size % actualThreads
58+
// 划分索引范围 从0开始
59+
// 索引计算规则如下 从0开始 到 taskPreThreadCount + n 结束 左闭右开
60+
// 下一个thread就是 taskPreThreadCount + n 开始 n一般为1或者0
61+
var currentIndex = 0
62+
val tasks = ArrayList<FutureTask<BufferBuilder?>>()
63+
repeat(actualThreads) {
64+
val tessellator = Tessellator.getInstance()
65+
var next = currentIndex + taskPreThreadCount // 取到 taskHandledIndexStart ..< next
66+
if (notHandledTaskCount > 0) {
67+
next++
68+
notHandledTaskCount--
69+
}
70+
val taskHandledIndexStart = currentIndex
71+
currentIndex = next
72+
val builder = instance.begin(tessellator, textureManager) ?: return@repeat
73+
// 复用子数组 taskHandledIndexStart - next
74+
val array = particles.sliceArray(taskHandledIndexStart..<next)
75+
// 创建任务
76+
val task = submitParticlesRender(
77+
array, builder, camera, tickDelta
78+
)
79+
task ?: return@repeat
80+
tasks.add(task)
81+
}
82+
tasks.forEach {
83+
val builder = it.get() ?: return@forEach
84+
val built = builder.endNullable() ?: return@forEach
85+
BufferRenderer.drawWithGlobalProgram(built)
86+
}
87+
}
88+
89+
fun submitParticlesRender(
90+
particles: Array<out Any>,
91+
builder: BufferBuilder,
92+
camera: Camera,
93+
tickDelta: Float
94+
): FutureTask<BufferBuilder?>? {
95+
val task = FutureTask {
96+
particles.forEach { particle ->
97+
render(particle, builder, camera, tickDelta)
98+
}
99+
return@FutureTask builder
100+
}
101+
threadPool.submit(task)
102+
return task
103+
}
104+
105+
// 渲染一个粒子
106+
fun render(p: Any, builder: BufferBuilder, camera: Camera, tickDelta: Float) {
107+
if (p !is Particle) return
108+
try {
109+
p.buildGeometry(builder, camera, tickDelta)
110+
} catch (throws: Throwable) {
111+
val crash = CrashReport.create(throws, "Rendering Particle Async by CooParticlesAPI")
112+
val section = crash.addElement("Particle being rendered")
113+
section.add("Particle", p::toString)
114+
section.add("Particle Type", p.type::toString)
115+
throw CrashException(crash)
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)