Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move particle vertex processing to the GPU #2086

Open
wants to merge 33 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
bbd18ab
Move particle vertex processing to the GPU
wahfl2 Sep 23, 2023
17c3c2e
Optimize code; fix translucent particles
IMS212 Sep 23, 2023
10e3547
Some changes
IMS212 Sep 23, 2023
99ee29b
Fix multiple issues
IMS212 Sep 23, 2023
941189c
Introduce new test buffer system
IMS212 Sep 23, 2023
69c96d5
Merge pull request #1 from IMS212/gpu
wahfl2 Sep 25, 2023
c7b7901
Cache world lighting for particles (patch by jelly)
wahfl2 Sep 25, 2023
dec37f6
Optimize so we are less memory bound (we are still memory bound)
wahfl2 Sep 26, 2023
5c6884f
Begin buffer texture vertex pulling impl
wahfl2 Sep 26, 2023
a800faa
Implement vertex pulling via a buffer texture
wahfl2 Sep 26, 2023
06cbfd8
Transfer to full vertex pulling
wahfl2 Sep 26, 2023
c278c88
Fix particle addition crash
wahfl2 Sep 27, 2023
fe37166
Fix comment
wahfl2 Sep 27, 2023
ed09a66
Begin particle registry impl
wahfl2 Sep 27, 2023
2b84a1e
Begin particle registry impl
wahfl2 Sep 27, 2023
90da7b1
More changes
wahfl2 Sep 28, 2023
8753804
Begin particle texture cache
wahfl2 Sep 29, 2023
abf35c7
Particle cache implementation
wahfl2 Sep 29, 2023
26df41e
Fix uploading issues
wahfl2 Oct 1, 2023
8a976a5
Make uploading more robust, remove glBufferSubData
wahfl2 Oct 1, 2023
d5d7ba7
Re-add optimized CPU path for particles that call super
wahfl2 Oct 2, 2023
b7c3c9f
Don't load rdoc
wahfl2 Oct 2, 2023
c304265
Micro-optimize uvs equals
wahfl2 Oct 2, 2023
53d97b7
Add special-cases for specific particles
wahfl2 Nov 6, 2023
b8ebf4b
Fix override detection
wahfl2 Nov 6, 2023
6468b89
Move texture cache to its own buffer
wahfl2 Nov 24, 2023
43f4fec
Merge
wahfl2 Jan 31, 2024
a10b2c7
EvictingPool implementation
wahfl2 Feb 1, 2024
6984e7f
Link to reduce confusion
wahfl2 Feb 2, 2024
931835b
UV micro-optimizations
wahfl2 Mar 9, 2024
e0a853a
Initial direct staging buffer impl
wahfl2 Mar 19, 2024
7fc9c8c
Actually allocate storage for the particle buffer
wahfl2 Mar 19, 2024
1676866
Make builder slightly more robust
wahfl2 Mar 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package net.caffeinemc.mods.sodium.api.buffer;

import net.caffeinemc.mods.sodium.api.memory.MemoryIntrinsics;
import net.minecraft.client.util.GlAllocationUtils;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;

import java.nio.ByteBuffer;

/**
* Provides a growing buffer with a push method for convenience.
* Otherwise, this class is very un-managed.
*/
public class UnmanagedBufferBuilder {
private ByteBuffer buffer;
private int byteOffset = 0;

public UnmanagedBufferBuilder(int initialCapacity) {
this.buffer = GlAllocationUtils.allocateByteBuffer(initialCapacity);
}

public void ensureCapacity(int capacity) {
if (capacity > this.buffer.capacity()) {
int newCapacity = (int) Math.ceil(this.buffer.capacity() * 1.5);
ByteBuffer byteBuffer = GlAllocationUtils.resizeByteBuffer(this.buffer, Math.max(newCapacity, capacity));
byteBuffer.rewind();
this.buffer = byteBuffer;
}
}

/**
* Copies memory from the stack onto the end of this buffer builder.
*/
public void push(MemoryStack ignoredStack, long src, int size) {
ensureCapacity(byteOffset + size);

long dst = MemoryUtil.memAddress(this.buffer, this.byteOffset);
MemoryIntrinsics.copyMemory(src, dst, size);
byteOffset += size;
}

public Built build() {
return new Built(this.byteOffset, MemoryUtil.memSlice(this.buffer, 0, this.byteOffset));
}

public void reset() {
this.byteOffset = 0;
}

/**
* Builds and resets this builder.
* Make sure to use/upload the return value before pushing more data.
* @return a built buffer containing all the data pushed to this builder
*/
public Built end() {
int endOffset = this.byteOffset;
this.byteOffset = 0;
return new Built(endOffset, MemoryUtil.memSlice(this.buffer, 0, endOffset));
}

public static class Built {
public int size;
public ByteBuffer buffer;

Built(int size, ByteBuffer buffer) {
this.size = size;
this.buffer = buffer;
}
}
}
50 changes: 50 additions & 0 deletions src/api/java/net/caffeinemc/mods/sodium/api/util/RawUVs.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package net.caffeinemc.mods.sodium.api.util;

import org.lwjgl.system.MemoryUtil;

public record RawUVs(float minU, float minV, float maxU, float maxV) {
public static final int STRIDE = 16;

public void put(long ptr) {
MemoryUtil.memPutFloat(ptr + 0, minU);
MemoryUtil.memPutFloat(ptr + 4, minV);
MemoryUtil.memPutFloat(ptr + 8, maxU);
MemoryUtil.memPutFloat(ptr + 12, maxV);
}

public static void putNull(long ptr) {
MemoryUtil.memPutFloat(ptr + 0, Float.NaN);
MemoryUtil.memPutFloat(ptr + 4, Float.NaN);
MemoryUtil.memPutFloat(ptr + 8, Float.NaN);
MemoryUtil.memPutFloat(ptr + 12, Float.NaN);
}

public long key() {
return ((long) Float.floatToRawIntBits(minU)) << 32 | Float.floatToRawIntBits(minV);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof RawUVs other) {
return Float.floatToRawIntBits(minU) == Float.floatToRawIntBits(other.minU()) &&
Float.floatToRawIntBits(minV) == Float.floatToRawIntBits(other.minV()) &&
Float.floatToRawIntBits(maxU) == Float.floatToRawIntBits(other.maxU()) &&
Float.floatToRawIntBits(maxV) == Float.floatToRawIntBits(other.maxV());
} else {
return false;
}
}

@Override
public int hashCode() {
int result = 1;

result = 31 * result + Float.floatToRawIntBits(minU);
result = 31 * result + Float.floatToRawIntBits(minV);
result = 31 * result + Float.floatToRawIntBits(maxU);
result = 31 * result + Float.floatToRawIntBits(maxV);

return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package me.jellysquid.mods.sodium.client.gl.arena.staging;

import me.jellysquid.mods.sodium.client.SodiumClientMod;
import me.jellysquid.mods.sodium.client.gl.buffer.*;
import me.jellysquid.mods.sodium.client.gl.device.CommandList;
import me.jellysquid.mods.sodium.client.gl.util.EnumBitField;
import org.lwjgl.system.MemoryStack;

public class StagingBufferBuilder {
private static final EnumBitField<GlBufferStorageFlags> STORAGE_FLAGS =
EnumBitField.of(GlBufferStorageFlags.PERSISTENT, GlBufferStorageFlags.CLIENT_STORAGE, GlBufferStorageFlags.MAP_WRITE);

private static final EnumBitField<GlBufferMapFlags> MAP_FLAGS =
EnumBitField.of(GlBufferMapFlags.PERSISTENT, GlBufferMapFlags.INVALIDATE_BUFFER, GlBufferMapFlags.WRITE, GlBufferMapFlags.EXPLICIT_FLUSH);

private final MappedBuffer mappedBuffer;

private int capacity;

private int start = 0;
private int pos = 0;

private int numFrames = 0;

public StagingBufferBuilder(CommandList commandList, int capacity) {
this.capacity = capacity;

GlImmutableBuffer buffer = commandList.createImmutableBuffer(capacity, STORAGE_FLAGS);
GlBufferMapping map = commandList.mapBuffer(buffer, 0, capacity, MAP_FLAGS);

this.mappedBuffer = new MappedBuffer(buffer, map);
}

public void push(MemoryStack stack, long ptr, int size) {
if (pos + size > this.capacity) {
// TODO: Needs a fallback instead of throwing, (even if it is highly unlikely)

throw new IllegalStateException("Particle data buffer overflowed!\n" +
"Capacity: " + this.capacity);
}

this.mappedBuffer.map.write(stack, ptr, size, pos);
pos += size;
}

public void endAndUpload(CommandList commandList, GlBuffer dst, long writeOffset) {
this.flush(commandList, dst, this.start, this.pos - this.start, writeOffset);
this.start = this.pos;
}

public void flipFrame() {
this.numFrames++;
if (numFrames >= SodiumClientMod.options().advanced.cpuRenderAheadLimit) {
this.reset();
}
}

private void reset() {
this.start = 0;
this.pos = 0;
this.numFrames = 0;
}

private void flush(CommandList commandList, GlBuffer dst, int readOffset, int length, long writeOffset) {
commandList.flushMappedRange(this.mappedBuffer.map, readOffset, length);
commandList.copyBufferSubData(this.mappedBuffer.buffer, dst, readOffset, writeOffset, length);
}

private record MappedBuffer(GlImmutableBuffer buffer,
GlBufferMapping map) {
public void delete(CommandList commandList) {
commandList.unmap(this.map);
commandList.deleteBuffer(this.buffer);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package me.jellysquid.mods.sodium.client.gl.buffer;

import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;

import java.nio.ByteBuffer;
Expand All @@ -19,6 +20,10 @@ public void write(ByteBuffer data, int writeOffset) {
MemoryUtil.memCopy(MemoryUtil.memAddress(data), MemoryUtil.memAddress(this.map, writeOffset), data.remaining());
}

public void write(MemoryStack ignoredStack, long ptr, int size, int writeOffset) {
MemoryUtil.memCopy(ptr, MemoryUtil.memAddress(this.map, writeOffset), size);
}

public GlBuffer getBufferObject() {
return this.buffer;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package me.jellysquid.mods.sodium.client.gl.buffer;

import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import me.jellysquid.mods.sodium.client.gl.device.CommandList;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL31;

import java.nio.ByteBuffer;

public class GlBufferTexture {
private final GlBuffer buffer;

private final int glTexHandle;

private final int textureNum;

public GlBufferTexture(GlBuffer buffer, int textureNum) {
this.buffer = buffer;
this.glTexHandle = GlStateManager._genTexture();
this.textureNum = textureNum;
}

public int getTextureNum() {
return textureNum;
}

public void bind() {
GL11.glBindTexture(GL31.GL_TEXTURE_BUFFER, this.glTexHandle);
GL31.glTexBuffer(GL31.GL_TEXTURE_BUFFER, GL31.GL_R32UI, this.buffer.handle());
RenderSystem.setShaderTexture(this.textureNum, this.glTexHandle);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package me.jellysquid.mods.sodium.client.gl.buffer;

import me.jellysquid.mods.sodium.client.SodiumClientMod;
import me.jellysquid.mods.sodium.client.gl.arena.staging.FallbackStagingBuffer;
import me.jellysquid.mods.sodium.client.gl.arena.staging.MappedStagingBuffer;
import me.jellysquid.mods.sodium.client.gl.arena.staging.StagingBuffer;
import me.jellysquid.mods.sodium.client.gl.device.CommandList;
import me.jellysquid.mods.sodium.client.gl.device.RenderDevice;

import java.nio.ByteBuffer;

public class GlContinuousUploadBuffer extends GlMutableBuffer {
private static final GlBufferUsage BUFFER_USAGE = GlBufferUsage.STATIC_DRAW;
private static final int DEFAULT_INITIAL_CAPACITY = 1024;

private final StagingBuffer stagingBuffer;

private int capacity;

public GlContinuousUploadBuffer(CommandList commands, int initialCapacity) {
super();
this.capacity = initialCapacity;
this.stagingBuffer = createStagingBuffer(commands);
commands.allocateStorage(this, this.capacity, BUFFER_USAGE);
}

public GlContinuousUploadBuffer(CommandList commands) {
this(commands, DEFAULT_INITIAL_CAPACITY);
}

public void uploadOverwrite(CommandList commandList, ByteBuffer data, int size) {
ensureCapacity(commandList, size);
this.stagingBuffer.enqueueCopy(commandList, data, this, 0);
this.stagingBuffer.flush(commandList);
}

public void ensureCapacity(CommandList commandList, int capacity) {
if (capacity > this.capacity) {
this.capacity = capacity;
commandList.allocateStorage(this, capacity, BUFFER_USAGE);
}
}

private static StagingBuffer createStagingBuffer(CommandList commandList) {
if (SodiumClientMod.options().advanced.useAdvancedStagingBuffers && MappedStagingBuffer.isSupported(RenderDevice.INSTANCE)) {
return new MappedStagingBuffer(commandList);
}

return new FallbackStagingBuffer(commandList);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package me.jellysquid.mods.sodium.client.render.particle;

import me.jellysquid.mods.sodium.client.gl.arena.staging.StagingBufferBuilder;
import me.jellysquid.mods.sodium.client.render.particle.cache.ParticleTextureCache;
import net.caffeinemc.mods.sodium.api.buffer.UnmanagedBufferBuilder;
import net.minecraft.client.render.Camera;

public interface BillboardExtended {
void sodium$buildParticleData(StagingBufferBuilder builder, ParticleTextureCache registry, Camera camera, float tickDelta);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package me.jellysquid.mods.sodium.client.render.particle;

import me.jellysquid.mods.sodium.client.gl.arena.staging.StagingBufferBuilder;
import me.jellysquid.mods.sodium.client.gl.buffer.GlBufferTexture;
import me.jellysquid.mods.sodium.client.gl.buffer.GlBufferUsage;
import me.jellysquid.mods.sodium.client.gl.buffer.GlContinuousUploadBuffer;
import me.jellysquid.mods.sodium.client.gl.buffer.GlMutableBuffer;
import me.jellysquid.mods.sodium.client.gl.device.CommandList;
import net.caffeinemc.mods.sodium.api.buffer.UnmanagedBufferBuilder;

public class ParticleBuffers {
private final GlMutableBuffer particleData;
private final GlContinuousUploadBuffer textureCache;

private final GlBufferTexture particleDataTex;
private final GlBufferTexture textureCacheTex;

public ParticleBuffers(CommandList commandList) {
this.particleData = commandList.createMutableBuffer();
commandList.allocateStorage(particleData, 3 * 16384 * 32, GlBufferUsage.STATIC_DRAW);

this.textureCache = new GlContinuousUploadBuffer(commandList);

this.particleDataTex = new GlBufferTexture(particleData, 3);
this.textureCacheTex = new GlBufferTexture(textureCache, 4);
}

public void uploadParticleData(CommandList commandList, StagingBufferBuilder data, UnmanagedBufferBuilder.Built cache) {
data.endAndUpload(commandList, this.particleData, 0);
this.textureCache.uploadOverwrite(commandList, cache.buffer, cache.size);
}

public void bind() {
this.particleDataTex.bind();
this.textureCacheTex.bind();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package me.jellysquid.mods.sodium.client.render.particle;

public interface ParticleExtended {
void sodium$configure(ParticleRenderView renderView);
}
Loading