Skip to content

Commit

Permalink
Move particle vertex processing to the GPU
Browse files Browse the repository at this point in the history
  • Loading branch information
wahfl2 committed Sep 23, 2023
1 parent 5e788b3 commit 8bcf416
Show file tree
Hide file tree
Showing 12 changed files with 548 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package me.jellysquid.mods.sodium.client.render.particle;

import me.jellysquid.mods.sodium.client.gl.shader.*;
import me.jellysquid.mods.sodium.client.render.particle.shader.ParticleShaderBindingPoints;
import me.jellysquid.mods.sodium.client.render.particle.shader.ParticleShaderInterface;
import net.minecraft.util.Identifier;

public class ShaderBillboardParticleRenderer {
protected GlProgram<ParticleShaderInterface> activeProgram;

public ShaderBillboardParticleRenderer() {
this.activeProgram = createShader("particles/particle");
}

public GlProgram<ParticleShaderInterface> getActiveProgram() {
return activeProgram;
}

private GlProgram<ParticleShaderInterface> createShader(String path) {
ShaderConstants constants = ShaderConstants.builder().build();

GlShader vertShader = ShaderLoader.loadShader(ShaderType.VERTEX,
new Identifier("sodium", path + ".vsh"), constants);

GlShader fragShader = ShaderLoader.loadShader(ShaderType.FRAGMENT,
new Identifier("sodium", path + ".fsh"), constants);

try {
return GlProgram.builder(new Identifier("sodium", "billboard_particle_shader"))
.attachShader(vertShader)
.attachShader(fragShader)
.bindAttribute("in_Position", ParticleShaderBindingPoints.ATTRIBUTE_POSITION)
.bindAttribute("in_Size", ParticleShaderBindingPoints.ATTRIBUTE_SIZE)
.bindAttribute("in_TexCoord", ParticleShaderBindingPoints.ATTRIBUTE_TEXTURE)
.bindAttribute("in_Color", ParticleShaderBindingPoints.ATTRIBUTE_COLOR)
.bindAttribute("in_Light", ParticleShaderBindingPoints.ATTRIBUTE_LIGHT_TEXTURE)
.bindAttribute("in_Angle", ParticleShaderBindingPoints.ATTRIBUTE_ANGLE)
.bindFragmentData("out_FragColor", ParticleShaderBindingPoints.FRAG_COLOR)
.link(ParticleShaderInterface::new);
} finally {
vertShader.delete();
fragShader.delete();
}
}

public void begin() {
// pass.startDrawing(); .. Do I need a pass?

this.activeProgram.bind();
this.activeProgram.getInterface().setupState();
}

public void end() {
this.activeProgram.unbind();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package me.jellysquid.mods.sodium.client.render.particle.shader;

import com.google.common.collect.ImmutableMap;
import me.jellysquid.mods.sodium.client.gl.attribute.GlVertexAttributeBinding;
import me.jellysquid.mods.sodium.client.gl.attribute.GlVertexAttributeFormat;
import me.jellysquid.mods.sodium.client.gl.attribute.GlVertexFormat;
import me.jellysquid.mods.sodium.client.render.chunk.shader.ChunkShaderBindingPoints;
import me.jellysquid.mods.sodium.client.render.chunk.vertex.format.ChunkMeshAttribute;
import net.caffeinemc.mods.sodium.api.vertex.attributes.CommonVertexAttribute;
import net.caffeinemc.mods.sodium.api.vertex.attributes.common.ColorAttribute;
import net.caffeinemc.mods.sodium.api.vertex.attributes.common.LightAttribute;
import net.caffeinemc.mods.sodium.api.vertex.attributes.common.PositionAttribute;
import net.caffeinemc.mods.sodium.api.vertex.attributes.common.TextureAttribute;
import net.caffeinemc.mods.sodium.api.vertex.format.VertexFormatDescription;
import net.caffeinemc.mods.sodium.api.vertex.format.VertexFormatRegistry;
import net.minecraft.client.render.VertexFormat;
import net.minecraft.client.render.VertexFormatElement;
import org.lwjgl.opengl.GL20C;
import org.lwjgl.opengl.GL30C;
import org.lwjgl.system.MemoryUtil;

import java.util.Map;

import static net.minecraft.client.render.VertexFormats.*;

public class BillboardParticleVertex {
public static final int POSITION_OFFSET = 0;
public static final int SIZE_OFFSET = 12;
public static final int TEX_UV_OFFSET = 16;
public static final int COLOR_OFFSET = 24;
public static final int LIGHT_UV_OFFSET = 28;
public static final int ANGLE_OFFSET = 32;
public static final int STRIDE = 36;

public static final GlVertexFormat<ParticleMeshAttribute> VERTEX_FORMAT = GlVertexFormat.builder(ParticleMeshAttribute.class, STRIDE)
.addElement(ParticleMeshAttribute.POSITION, POSITION_OFFSET, GlVertexAttributeFormat.FLOAT, 3, false, false)
.addElement(ParticleMeshAttribute.SIZE, SIZE_OFFSET, GlVertexAttributeFormat.FLOAT, 1, false, false)
.addElement(ParticleMeshAttribute.TEX_COORD, TEX_UV_OFFSET, GlVertexAttributeFormat.FLOAT, 2, false, false)
.addElement(ParticleMeshAttribute.COLOR, COLOR_OFFSET, GlVertexAttributeFormat.UNSIGNED_BYTE, 4, true, false)
.addElement(ParticleMeshAttribute.LIGHT_UV, LIGHT_UV_OFFSET, GlVertexAttributeFormat.UNSIGNED_SHORT, 2, false, true)
.addElement(ParticleMeshAttribute.ANGLE, ANGLE_OFFSET, GlVertexAttributeFormat.FLOAT, 1, false, false)
.build();

public static final VertexFormat MC_VERTEX_FORMAT = new VertexFormat(ImmutableMap.ofEntries(
Map.entry("in_Position", POSITION_ELEMENT),
Map.entry("in_Size", new VertexFormatElement(
0,
VertexFormatElement.ComponentType.FLOAT,
VertexFormatElement.Type.GENERIC,
1
)),
Map.entry("in_TexCoord", TEXTURE_ELEMENT),
Map.entry("in_Color", COLOR_ELEMENT),
Map.entry("in_Light", LIGHT_ELEMENT),
Map.entry("in_Angle", new VertexFormatElement(
0,
VertexFormatElement.ComponentType.FLOAT,
VertexFormatElement.Type.GENERIC,
1
))
));

public static final GlVertexAttributeBinding[] ATTRIBUTE_BINDINGS = new GlVertexAttributeBinding[] {
new GlVertexAttributeBinding(ParticleShaderBindingPoints.ATTRIBUTE_POSITION,
VERTEX_FORMAT.getAttribute(ParticleMeshAttribute.POSITION)),
new GlVertexAttributeBinding(ParticleShaderBindingPoints.ATTRIBUTE_SIZE,
VERTEX_FORMAT.getAttribute(ParticleMeshAttribute.SIZE)),
new GlVertexAttributeBinding(ParticleShaderBindingPoints.ATTRIBUTE_TEXTURE,
VERTEX_FORMAT.getAttribute(ParticleMeshAttribute.TEX_COORD)),
new GlVertexAttributeBinding(ParticleShaderBindingPoints.ATTRIBUTE_COLOR,
VERTEX_FORMAT.getAttribute(ParticleMeshAttribute.COLOR)),
new GlVertexAttributeBinding(ParticleShaderBindingPoints.ATTRIBUTE_LIGHT_TEXTURE,
VERTEX_FORMAT.getAttribute(ParticleMeshAttribute.LIGHT_UV)),
new GlVertexAttributeBinding(ParticleShaderBindingPoints.ATTRIBUTE_ANGLE,
VERTEX_FORMAT.getAttribute(ParticleMeshAttribute.ANGLE)),
};

public static final VertexFormatDescription VERTEX_FORMAT_DESCRIPTION = VertexFormatRegistry.instance()
.get(MC_VERTEX_FORMAT);

public static void put(long ptr, float x, float y, float z,
float u, float v, int color, int light, float size, float angle) {
PositionAttribute.put(ptr + POSITION_OFFSET, x, y, z);
MemoryUtil.memPutFloat(ptr + SIZE_OFFSET, size);
TextureAttribute.put(ptr + TEX_UV_OFFSET, u, v);
ColorAttribute.set(ptr + COLOR_OFFSET, color);
LightAttribute.set(ptr + LIGHT_UV_OFFSET, light);
MemoryUtil.memPutFloat(ptr + ANGLE_OFFSET, angle);
}

public static void bindVertexFormat() {
for (GlVertexAttributeBinding attrib : ATTRIBUTE_BINDINGS) {
if (attrib.isIntType()) {
GL30C.glVertexAttribIPointer(attrib.getIndex(), attrib.getCount(), attrib.getFormat(),
attrib.getStride(), attrib.getPointer());
} else {
GL20C.glVertexAttribPointer(attrib.getIndex(), attrib.getCount(), attrib.getFormat(), attrib.isNormalized(),
attrib.getStride(), attrib.getPointer());
}
GL20C.glEnableVertexAttribArray(attrib.getIndex());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package me.jellysquid.mods.sodium.client.render.particle.shader;

public enum ParticleMeshAttribute {
POSITION,
SIZE,
TEX_COORD,
COLOR,
LIGHT_UV,
ANGLE,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package me.jellysquid.mods.sodium.client.render.particle.shader;

public class ParticleShaderBindingPoints {
public static final int ATTRIBUTE_POSITION = 1;
public static final int ATTRIBUTE_SIZE = 2;
public static final int ATTRIBUTE_TEXTURE = 3;
public static final int ATTRIBUTE_COLOR = 4;
public static final int ATTRIBUTE_LIGHT_TEXTURE = 5;
public static final int ATTRIBUTE_ANGLE = 6;

public static final int FRAG_COLOR = 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package me.jellysquid.mods.sodium.client.render.particle.shader;

import com.mojang.blaze3d.platform.GlStateManager;
import me.jellysquid.mods.sodium.client.gl.shader.uniform.GlUniformFloat4v;
import me.jellysquid.mods.sodium.client.gl.shader.uniform.GlUniformInt;
import me.jellysquid.mods.sodium.client.gl.shader.uniform.GlUniformMatrix4f;
import me.jellysquid.mods.sodium.client.render.chunk.shader.ChunkShaderTextureSlot;
import me.jellysquid.mods.sodium.client.render.chunk.shader.ShaderBindingContext;
import me.jellysquid.mods.sodium.client.util.TextureUtil;
import org.joml.*;
import org.lwjgl.opengl.GL32C;

public class ParticleShaderInterface {
private final GlUniformInt uniformParticleTexture;
private final GlUniformInt uniformLightTexture;
private final GlUniformMatrix4f uniformModelViewMatrix;
private final GlUniformMatrix4f uniformProjectionMatrix;
private final GlUniformFloat4v uniformCameraRotation;

public ParticleShaderInterface(ShaderBindingContext context) {
this.uniformParticleTexture = context.bindUniform("u_ParticleTex", GlUniformInt::new);
this.uniformLightTexture = context.bindUniform("u_LightTex", GlUniformInt::new);
this.uniformModelViewMatrix = context.bindUniform("u_ModelViewMatrix", GlUniformMatrix4f::new);
this.uniformProjectionMatrix = context.bindUniform("u_ProjectionMatrix", GlUniformMatrix4f::new);
this.uniformCameraRotation = context.bindUniform("u_CameraRotation", GlUniformFloat4v::new);
}

public void setProjectionMatrix(Matrix4fc matrix) {
this.uniformProjectionMatrix.set(matrix);
}

public void setModelViewMatrix(Matrix4fc matrix) {
this.uniformModelViewMatrix.set(matrix);
}
public void setCameraRotation(Quaternionfc quaternion) {
this.uniformCameraRotation.set(new float[] {
quaternion.x(),
quaternion.y(),
quaternion.z(),
quaternion.w(),
});
}

public void setupState() {
// "BlockTexture" should represent the particle textures if bound correctly
this.bindParticleTexture(ParticleShaderTextureSlot.TEXTURE, TextureUtil.getBlockTextureId());
this.bindLightTexture(ParticleShaderTextureSlot.LIGHT, TextureUtil.getLightTextureId());
}

private void bindParticleTexture(ParticleShaderTextureSlot slot, int textureId) {
GlStateManager._activeTexture(GL32C.GL_TEXTURE0 + slot.ordinal());
GlStateManager._bindTexture(textureId);

uniformParticleTexture.setInt(slot.ordinal());
}

private void bindLightTexture(ParticleShaderTextureSlot slot, int textureId) {
GlStateManager._activeTexture(GL32C.GL_TEXTURE0 + slot.ordinal());
GlStateManager._bindTexture(textureId);

uniformLightTexture.setInt(slot.ordinal());
}

private enum ParticleShaderTextureSlot {
TEXTURE,
LIGHT,
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package me.jellysquid.mods.sodium.mixin.features.render.particle;

import net.caffeinemc.mods.sodium.api.vertex.format.common.ParticleVertex;
import me.jellysquid.mods.sodium.client.render.particle.shader.BillboardParticleVertex;
import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter;
import net.caffeinemc.mods.sodium.api.util.ColorABGR;
import net.minecraft.client.particle.BillboardParticle;
Expand All @@ -10,7 +10,6 @@
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import org.joml.Quaternionf;
import org.lwjgl.system.MemoryStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
Expand Down Expand Up @@ -50,17 +49,6 @@ public void buildGeometry(VertexConsumer vertexConsumer, Camera camera, float ti
float y = (float) (MathHelper.lerp(tickDelta, this.prevPosY, this.y) - vec3d.getY());
float z = (float) (MathHelper.lerp(tickDelta, this.prevPosZ, this.z) - vec3d.getZ());

Quaternionf quaternion;

if (this.angle == 0.0F) {
quaternion = camera.getRotation();
} else {
float angle = MathHelper.lerp(tickDelta, this.prevAngle, this.angle);

quaternion = new Quaternionf(camera.getRotation());
quaternion.rotateZ(angle);
}

float size = this.getSize(tickDelta);
int light = this.getBrightness(tickDelta);

Expand All @@ -71,67 +59,34 @@ public void buildGeometry(VertexConsumer vertexConsumer, Camera camera, float ti

int color = ColorABGR.pack(this.red , this.green, this.blue, this.alpha);

float angle = MathHelper.lerp(tickDelta, this.prevAngle, this.angle);

var writer = VertexBufferWriter.of(vertexConsumer);

try (MemoryStack stack = MemoryStack.stackPush()) {
long buffer = stack.nmalloc(4 * ParticleVertex.STRIDE);
long buffer = stack.nmalloc(4 * BillboardParticleVertex.STRIDE);
long ptr = buffer;

writeVertex(ptr, quaternion,-1.0F, -1.0F, x, y, z, maxU, maxV, color, light, size);
ptr += ParticleVertex.STRIDE;
writeVertex(ptr, x, y, z, maxU, maxV, color, light, size, angle);
ptr += BillboardParticleVertex.STRIDE;

writeVertex(ptr, quaternion,-1.0F, 1.0F, x, y, z, maxU, minV, color, light, size);
ptr += ParticleVertex.STRIDE;
writeVertex(ptr, x, y, z, maxU, minV, color, light, size, angle);
ptr += BillboardParticleVertex.STRIDE;

writeVertex(ptr, quaternion,1.0F, 1.0F, x, y, z, minU, minV, color, light, size);
ptr += ParticleVertex.STRIDE;
writeVertex(ptr, x, y, z, minU, minV, color, light, size, angle);
ptr += BillboardParticleVertex.STRIDE;

writeVertex(ptr, quaternion,1.0F, -1.0F, x, y, z, minU, maxV, color, light, size);
ptr += ParticleVertex.STRIDE;
writeVertex(ptr, x, y, z, minU, maxV, color, light, size, angle);
ptr += BillboardParticleVertex.STRIDE;

writer.push(stack, buffer, 4, ParticleVertex.FORMAT);
writer.push(stack, buffer, 4, BillboardParticleVertex.VERTEX_FORMAT_DESCRIPTION);
}

}

@Unique
@SuppressWarnings("UnnecessaryLocalVariable")
private static void writeVertex(long buffer,
Quaternionf rotation,
float posX, float posY,
float originX, float originY, float originZ,
float u, float v, int color, int light, float size) {
// Quaternion q0 = new Quaternion(rotation);
float q0x = rotation.x();
float q0y = rotation.y();
float q0z = rotation.z();
float q0w = rotation.w();

// q0.hamiltonProduct(x, y, 0.0f, 0.0f)
float q1x = (q0w * posX) - (q0z * posY);
float q1y = (q0w * posY) + (q0z * posX);
float q1w = (q0x * posY) - (q0y * posX);
float q1z = -(q0x * posX) - (q0y * posY);

// Quaternion q2 = new Quaternion(rotation);
// q2.conjugate()
float q2x = -q0x;
float q2y = -q0y;
float q2z = -q0z;
float q2w = q0w;

// q2.hamiltonProduct(q1)
float q3x = q1z * q2x + q1x * q2w + q1y * q2z - q1w * q2y;
float q3y = q1z * q2y - q1x * q2z + q1y * q2w + q1w * q2x;
float q3z = q1z * q2z + q1x * q2y - q1y * q2x + q1w * q2w;

// Vector3f f = new Vector3f(q2.getX(), q2.getY(), q2.getZ())
// f.multiply(size)
// f.add(pos)
float fx = (q3x * size) + originX;
float fy = (q3y * size) + originY;
float fz = (q3z * size) + originZ;

ParticleVertex.put(buffer, fx, fy, fz, u, v, color, light);
private static void writeVertex(long buffer, float originX, float originY, float originZ,
float u, float v, int color, int light, float size, float angle) {

BillboardParticleVertex.put(buffer, originX, originY, originZ, u, v, color, light, size, angle);
}
}
Loading

0 comments on commit 8bcf416

Please sign in to comment.