Skip to content

Commit 22dbd88

Browse files
committed
基于GPT5.6 实现了一个CParticle粒子系统,
兼容目前的Composition 组合 和Emitter的部分操作 使用纯GPU粒子在参数调用上会有一些出入(基于粒子的AlphaHelper算是无法使用了)
1 parent 9416df6 commit 22dbd88

71 files changed

Lines changed: 6571 additions & 61 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎common/build.gradle‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ dependencies {
4141
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2"
4242
testImplementation 'org.joml:joml:1.10.8'
4343
testRuntimeOnly files(configurations.runtimeClasspath)
44+
testCompileOnly files(fileTree("$buildDir/moddev/artifacts") {
45+
include "vanilla-${minecraft_version}-*.jar"
46+
exclude "*-client-extra-*", "*-merged.jar", "*-sources.jar"
47+
})
4448
testRuntimeOnly files(fileTree("$buildDir/moddev/artifacts") {
4549
include "vanilla-${minecraft_version}-*.jar"
4650
exclude "*-client-extra-*", "*-merged.jar", "*-sources.jar"

‎common/src/main/java/cn/coostack/cooparticlesapi/mixin/LevelRendererMixin.java‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import cn.coostack.cooparticlesapi.CooParticlesAPIClient;
44
import cn.coostack.cooparticlesapi.accessor.LevelRendererAccessor;
5+
import cn.coostack.cooparticlesapi.cparticle.CParticleSystemManager;
56
import cn.coostack.cooparticlesapi.renderer.client.ClientRenderEntityManager;
67
import cn.coostack.cooparticlesapi.renderer.client.ClientRenderPipelineManager;
78
import com.mojang.blaze3d.vertex.PoseStack;
@@ -26,6 +27,18 @@ public class LevelRendererMixin {
2627
@Nullable
2728
private ClientLevel level;
2829

30+
@Inject(method = "renderLevel", at = @At("HEAD"))
31+
private void cooParticlesAPI$beginRenderFrame(DeltaTracker deltaTracker,
32+
boolean renderBlockOutline,
33+
Camera camera,
34+
GameRenderer gameRenderer,
35+
LightTexture lightTexture,
36+
Matrix4f frustumMatrix,
37+
Matrix4f projectionMatrix,
38+
CallbackInfo info) {
39+
CParticleSystemManager.beginRenderFrame();
40+
}
41+
2942
@Inject(method = "renderLevel",
3043
at = @At(value = "INVOKE",
3144
target = "Lnet/minecraft/util/profiling/ProfilerFiller;popPush(Ljava/lang/String;)V",
@@ -49,14 +62,21 @@ public void renderBeforeEntity(DeltaTracker deltaTracker,
4962
ClientRenderPipelineManager.INSTANCE.beginFrame(tickDelta, frustumMatrix, projectionMatrix);
5063
MultiBufferSource.BufferSource bufferSource =
5164
((LevelRendererAccessor) this).renderBuffers().bufferSource();
65+
boolean irisShaderPackInUse = CooParticlesAPIClient.checkIrisShaderPackUsed();
5266
ClientRenderEntityManager.INSTANCE.renderRenderTypePass(
5367
tickDelta,
5468
frustumMatrix,
5569
projectionMatrix,
5670
new PoseStack(),
5771
bufferSource,
5872
camera,
59-
CooParticlesAPIClient.checkIrisShaderPackUsed()
73+
irisShaderPackInUse
74+
);
75+
ClientRenderEntityManager.INSTANCE.renderIrisWorldPass(
76+
tickDelta,
77+
frustumMatrix,
78+
projectionMatrix,
79+
irisShaderPackInUse
6080
);
6181
}
6282

‎common/src/main/java/cn/coostack/cooparticlesapi/mixin/ParticleEngineAccessor.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
import net.minecraft.client.particle.Particle;
55
import net.minecraft.client.particle.ParticleEngine;
66
import net.minecraft.client.particle.ParticleRenderType;
7+
import net.minecraft.client.particle.SpriteSet;
78
import net.minecraft.client.particle.TrackingEmitter;
89
import net.minecraft.core.particles.ParticleOptions;
910
import net.minecraft.core.particles.ParticleGroup;
11+
import net.minecraft.resources.ResourceLocation;
1012
import org.spongepowered.asm.mixin.Mixin;
1113
import org.spongepowered.asm.mixin.gen.Accessor;
1214
import org.spongepowered.asm.mixin.gen.Invoker;
@@ -31,4 +33,10 @@ public interface ParticleEngineAccessor {
3133

3234
@Accessor
3335
Object2IntOpenHashMap<ParticleGroup> getTrackedParticleCounts();
36+
37+
/**
38+
* cparticle GPU粒子系统用: 用于把 ParticleType 解析成粒子图集里的 sprite (取UV)
39+
*/
40+
@Accessor
41+
Map<ResourceLocation, ? extends SpriteSet> getSpriteSets();
3442
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package cn.coostack.cooparticlesapi.mixin;
2+
3+
import cn.coostack.cooparticlesapi.cparticle.CParticleSystemManager;
4+
import net.minecraft.client.particle.ParticleEngine;
5+
import org.spongepowered.asm.mixin.Mixin;
6+
import org.spongepowered.asm.mixin.injection.At;
7+
import org.spongepowered.asm.mixin.injection.Inject;
8+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
9+
10+
@Mixin(ParticleEngine.class)
11+
public abstract class ParticleEngineDebugMixin {
12+
@Inject(method = "countParticles", at = @At("RETURN"), cancellable = true)
13+
private void cooParticlesAPI$appendCParticleCount(CallbackInfoReturnable<String> info) {
14+
info.setReturnValue(info.getReturnValue() + " + " + CParticleSystemManager.totalAlive());
15+
}
16+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package cn.coostack.cooparticlesapi.mixin.compat.iris;
2+
3+
import org.spongepowered.asm.mixin.Mixin;
4+
import org.spongepowered.asm.mixin.Pseudo;
5+
import org.spongepowered.asm.mixin.Unique;
6+
import org.spongepowered.asm.mixin.injection.At;
7+
import org.spongepowered.asm.mixin.injection.Inject;
8+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
9+
10+
import java.lang.reflect.Constructor;
11+
import java.lang.reflect.Method;
12+
13+
@Pseudo
14+
@Mixin(targets = "net.irisshaders.iris.pipeline.programs.ShaderKey", remap = false)
15+
public abstract class ShaderKeyIrisCompatMixin {
16+
@Unique
17+
private static final float cooparticlesapi$PARTICLE_ALPHA_THRESHOLD = 0.001F;
18+
@Unique
19+
private static volatile boolean cooparticlesapi$particleAlphaTestResolved;
20+
@Unique
21+
private static Object cooparticlesapi$particleAlphaTest;
22+
23+
@Inject(method = "getAlphaTest", at = @At("RETURN"), cancellable = true, require = 0, remap = false)
24+
private void cooparticlesapi$lowerParticleAlphaThreshold(CallbackInfoReturnable<Object> cir) {
25+
String shaderKey = ((Enum<?>) (Object) this).name();
26+
if (!"PARTICLES".equals(shaderKey) && !"PARTICLES_TRANS".equals(shaderKey)) {
27+
return;
28+
}
29+
Object alphaTest = cooparticlesapi$getParticleAlphaTest(cir.getReturnValue());
30+
if (alphaTest != null) {
31+
cir.setReturnValue(alphaTest);
32+
}
33+
}
34+
35+
@Unique
36+
private static Object cooparticlesapi$getParticleAlphaTest(Object original) {
37+
if (cooparticlesapi$particleAlphaTestResolved) {
38+
return cooparticlesapi$particleAlphaTest;
39+
}
40+
synchronized (ShaderKeyIrisCompatMixin.class) {
41+
if (cooparticlesapi$particleAlphaTestResolved) {
42+
return cooparticlesapi$particleAlphaTest;
43+
}
44+
cooparticlesapi$particleAlphaTest = cooparticlesapi$createParticleAlphaTest(original);
45+
cooparticlesapi$particleAlphaTestResolved = true;
46+
return cooparticlesapi$particleAlphaTest;
47+
}
48+
}
49+
50+
@Unique
51+
private static Object cooparticlesapi$createParticleAlphaTest(Object original) {
52+
if (original == null) {
53+
return null;
54+
}
55+
try {
56+
Class<?> alphaTestClass = original.getClass();
57+
Method functionAccessor = alphaTestClass.getMethod("function");
58+
Object function = functionAccessor.invoke(original);
59+
Constructor<?> constructor = alphaTestClass.getConstructor(function.getClass(), float.class);
60+
return constructor.newInstance(function, cooparticlesapi$PARTICLE_ALPHA_THRESHOLD);
61+
} catch (ReflectiveOperationException ignored) {
62+
return null;
63+
}
64+
}
65+
}

‎common/src/main/kotlin/cn/coostack/cooparticlesapi/CooParticlesAPIClient.kt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cn.coostack.cooparticlesapi
22

33
import cn.coostack.cooparticlesapi.animation.AnimateManager
4+
import cn.coostack.cooparticlesapi.cparticle.CParticleSystemManager
45
import cn.coostack.cooparticlesapi.data.holder.DataHolderManager
56
import cn.coostack.cooparticlesapi.display.DisplayEntityManager
67
import cn.coostack.cooparticlesapi.display.CooRenderTypeResourceRegistry
@@ -156,6 +157,7 @@ object CooParticlesAPIClient {
156157
@JvmStatic
157158
fun reloadShaderPrograms() {
158159
renderInit = false
160+
CParticleSystemManager.onResourceReload()
159161
OpenGlRenderEntityModelExecutor.release()
160162
OpenGlMaskBloomEffectExecutor.release()
161163
RenderEntityModelExecutors.reset()
@@ -199,6 +201,7 @@ object CooParticlesAPIClient {
199201
ParticleCompositionManager.clearClient()
200202
DisplayEntityManager.clearClient()
201203
ControlParticleManager.clearClient()
204+
CParticleSystemManager.clear()
202205
ClientRenderEntityManager.clear()
203206
CooPostEffects.client.clear()
204207
DataHolderManager.clearClient()
@@ -237,6 +240,7 @@ object CooParticlesAPIClient {
237240
DataHolderManager.tick()
238241
ClientCameraUtil.tick()
239242
ParticleCompositionManager.tickClient()
243+
CParticleSystemManager.tick()
240244
TestManager.doTickClient()
241245
AnimateManager.tickClient()
242246
CooClientPacketManager.tick()

‎common/src/main/kotlin/cn/coostack/cooparticlesapi/compat/IrisCompat.kt‎

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package cn.coostack.cooparticlesapi.compat
22

33
import cn.coostack.cooparticlesapi.CooParticlesAPIClient
4+
import cn.coostack.cooparticlesapi.cparticle.CParticleRenderPass
5+
import cn.coostack.cooparticlesapi.renderer.runtime.IrisWorldPassMode
6+
import net.minecraft.client.renderer.GameRenderer
47
import net.minecraft.client.renderer.RenderStateShard
58
import net.minecraft.client.renderer.RenderType
69
import net.minecraft.client.renderer.ShaderInstance
710
import org.slf4j.LoggerFactory
811
import java.lang.invoke.MethodHandle
912
import java.lang.invoke.MethodHandles
1013
import java.lang.invoke.MethodType
14+
import java.lang.reflect.Method
15+
import java.util.Optional
1116

1217
/**
1318
* 与 IRIS 互操作的反射桥接,不依赖 IRIS 类型也不需要 mixin。
@@ -43,6 +48,18 @@ object IrisCompat {
4348
@Volatile
4449
private var entityRenderStateShard: RenderStateShard? = null
4550

51+
@Volatile
52+
private var particleRenderingMethodsResolved = false
53+
54+
@Volatile
55+
private var particleRenderingMethods: ParticleRenderingMethods? = null
56+
57+
@Volatile
58+
private var particleTranslucentShaderMethodResolved = false
59+
60+
@Volatile
61+
private var particleTranslucentShaderMethod: Method? = null
62+
4663
/** 单例 MethodHandle:永远返回 false (= "请不要跳过我")。 */
4764
private val NEVER_SKIP: MethodHandle by lazy {
4865
MethodHandles.constant(Boolean::class.javaPrimitiveType, false)
@@ -83,6 +100,79 @@ object IrisCompat {
83100
}
84101
}
85102

103+
/** Iris 的 MIXED 设置会把粒子分成 opaque/translucent 两次调用。 */
104+
@JvmStatic
105+
fun usesMixedParticleRendering(): Boolean {
106+
if (!CooParticlesAPIClient.checkIrisShaderPackUsed()) return false
107+
val methods = resolveParticleRenderingMethods() ?: return false
108+
return try {
109+
val manager = methods.getPipelineManager.invoke(null)
110+
val pipeline = (methods.getPipeline.invoke(manager) as Optional<*>).orElse(null) ?: return false
111+
val setting = methods.getParticleRenderingSettings.invoke(pipeline) as? Enum<*>
112+
setting?.name == "MIXED"
113+
} catch (t: Throwable) {
114+
LOGGER.warn("Failed to query Iris particle rendering mode", t)
115+
false
116+
}
117+
}
118+
119+
/**
120+
* 在 Iris 粒子 shader 的 writing framebuffer 中执行 GPU 粒子绘制。
121+
*
122+
* 原版粒子批次结束后 Iris 已通过 ShaderInstance.clear() 切回主目标,不能依赖
123+
* RenderSystem 中残留的 shader。TRANSLUCENT pass 使用 Iris 的公开 ShaderAccess 入口。
124+
*/
125+
@JvmStatic
126+
fun runWithParticleShader(pass: CParticleRenderPass, draw: () -> Unit) {
127+
if (!CooParticlesAPIClient.checkIrisShaderPackUsed()) {
128+
draw()
129+
return
130+
}
131+
132+
val particleShader = when (pass) {
133+
CParticleRenderPass.TRANSLUCENT -> getParticleTranslucentShader()
134+
CParticleRenderPass.ALL, CParticleRenderPass.OPAQUE -> GameRenderer.getParticleShader()
135+
CParticleRenderPass.NONE -> null
136+
}
137+
if (particleShader == null) {
138+
draw()
139+
return
140+
}
141+
142+
particleShader.apply()
143+
try {
144+
draw()
145+
} finally {
146+
particleShader.clear()
147+
}
148+
}
149+
150+
/** 在 Iris 对应的 entity framebuffer 中执行 RenderEntity 的本地 world pass。 */
151+
@JvmStatic
152+
fun runWithEntityShader(mode: IrisWorldPassMode, draw: () -> Unit) {
153+
if (!CooParticlesAPIClient.checkIrisShaderPackUsed()) {
154+
draw()
155+
return
156+
}
157+
158+
val entityShader = when (mode) {
159+
IrisWorldPassMode.ENTITY_SOLID -> GameRenderer.getRendertypeEntitySolidShader()
160+
IrisWorldPassMode.ENTITY_CUTOUT -> GameRenderer.getRendertypeEntityCutoutShader()
161+
IrisWorldPassMode.ENTITY_TRANSLUCENT -> GameRenderer.getRendertypeEntityTranslucentShader()
162+
}
163+
if (entityShader == null) {
164+
draw()
165+
return
166+
}
167+
168+
entityShader.apply()
169+
try {
170+
draw()
171+
} finally {
172+
entityShader.clear()
173+
}
174+
}
175+
86176
private fun resolveSetShouldSkip(cls: Class<*>): MethodHandle? {
87177
if (setShouldSkipResolved) return setShouldSkipHandle
88178
synchronized(this) {
@@ -140,4 +230,67 @@ object IrisCompat {
140230
return entityRenderTypeWrapperHandle
141231
}
142232
}
233+
234+
private fun resolveParticleRenderingMethods(): ParticleRenderingMethods? {
235+
if (particleRenderingMethodsResolved) return particleRenderingMethods
236+
synchronized(this) {
237+
if (particleRenderingMethodsResolved) return particleRenderingMethods
238+
particleRenderingMethodsResolved = true
239+
particleRenderingMethods = try {
240+
val irisClass = Class.forName("net.irisshaders.iris.Iris")
241+
val pipelineManagerClass = Class.forName("net.irisshaders.iris.pipeline.PipelineManager")
242+
val worldPipelineClass = Class.forName("net.irisshaders.iris.pipeline.WorldRenderingPipeline")
243+
ParticleRenderingMethods(
244+
irisClass.getMethod("getPipelineManager"),
245+
pipelineManagerClass.getMethod("getPipeline"),
246+
worldPipelineClass.getMethod("getParticleRenderingSettings")
247+
)
248+
} catch (_: ClassNotFoundException) {
249+
null
250+
} catch (_: NoSuchMethodException) {
251+
null
252+
} catch (t: Throwable) {
253+
LOGGER.warn("Unexpected failure resolving Iris particle rendering mode", t)
254+
null
255+
}
256+
return particleRenderingMethods
257+
}
258+
}
259+
260+
private fun getParticleTranslucentShader(): ShaderInstance? {
261+
val method = resolveParticleTranslucentShaderMethod() ?: return null
262+
return try {
263+
method.invoke(null) as? ShaderInstance
264+
} catch (t: Throwable) {
265+
LOGGER.warn("Failed to get Iris translucent particle shader", t)
266+
null
267+
}
268+
}
269+
270+
private fun resolveParticleTranslucentShaderMethod(): Method? {
271+
if (particleTranslucentShaderMethodResolved) return particleTranslucentShaderMethod
272+
synchronized(this) {
273+
if (particleTranslucentShaderMethodResolved) return particleTranslucentShaderMethod
274+
particleTranslucentShaderMethodResolved = true
275+
particleTranslucentShaderMethod = try {
276+
Class.forName("net.irisshaders.iris.pipeline.programs.ShaderAccess")
277+
.getMethod("getParticleTranslucentShader")
278+
} catch (_: ClassNotFoundException) {
279+
null
280+
} catch (_: NoSuchMethodException) {
281+
null
282+
} catch (t: Throwable) {
283+
LOGGER.warn("Unexpected failure resolving Iris translucent particle shader", t)
284+
null
285+
}
286+
return particleTranslucentShaderMethod
287+
}
288+
}
289+
290+
private data class ParticleRenderingMethods(
291+
val getPipelineManager: Method,
292+
val getPipeline: Method,
293+
val getParticleRenderingSettings: Method,
294+
)
295+
143296
}

0 commit comments

Comments
 (0)