Skip to content

Commit

Permalink
Lazy VBO generation
Browse files Browse the repository at this point in the history
  • Loading branch information
jaskarth authored and FoundationGames committed Aug 7, 2023
1 parent f10e3b8 commit beb55f0
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 103 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.foundationgames.phonos.block.entity;

import io.github.foundationgames.phonos.client.render.BlockEntityClientState;
import io.github.foundationgames.phonos.sound.emitter.SoundSource;
import io.github.foundationgames.phonos.util.UniqueId;
import io.github.foundationgames.phonos.world.sound.block.BlockConnectionLayout;
Expand All @@ -22,12 +23,14 @@ public abstract class AbstractOutputBlockEntity extends BlockEntity implements S
public final BlockEntityOutputs outputs;
protected @Nullable NbtCompound pendingNbt = null;
protected final long emitterId;
private BlockEntityClientState clientState;

public AbstractOutputBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state, BlockConnectionLayout outputLayout) {
super(type, pos, state);

this.emitterId = UniqueId.ofBlock(pos);
this.outputs = new BlockEntityOutputs(outputLayout, this);
this.clientState = null;
}

@Override
Expand Down Expand Up @@ -115,4 +118,23 @@ public void forEachChild(LongConsumer action) {
public BlockEntityOutputs getOutputs() {
return this.outputs;
}

@Override
public BlockEntityClientState getClientState() {
if (this.clientState == null) {
this.clientState = new BlockEntityClientState();
}

this.clientState.genState(this.outputs);
return this.clientState;
}

@Override
public void markRemoved() {
if (this.hasWorld() && this.world.isClient && this.clientState != null) {
this.clientState.dirty = true;
this.clientState.close();
}
super.markRemoved();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.foundationgames.phonos.block.entity;

import io.github.foundationgames.phonos.block.PhonosBlocks;
import io.github.foundationgames.phonos.client.render.BlockEntityClientState;
import io.github.foundationgames.phonos.network.PayloadPackets;
import io.github.foundationgames.phonos.sound.SoundStorage;
import io.github.foundationgames.phonos.sound.emitter.SoundEmitterTree;
Expand Down Expand Up @@ -46,6 +47,7 @@ public class ElectronicJukeboxBlockEntity extends JukeboxBlockEntity implements
private final BlockEntityType<?> type;
private @Nullable NbtCompound pendingNbt = null;
private final long emitterId;
private BlockEntityClientState clientState;

private @Nullable SoundEmitterTree playingSound = null;

Expand All @@ -55,6 +57,7 @@ public ElectronicJukeboxBlockEntity(BlockEntityType<?> type, BlockPos pos, Block
this.emitterId = UniqueId.ofBlock(pos);

this.outputs = new BlockEntityOutputs(OUTPUT_LAYOUT, this);
this.clientState = null;
}

public ElectronicJukeboxBlockEntity(BlockPos pos, BlockState state) {
Expand Down Expand Up @@ -197,6 +200,25 @@ public BlockEntityOutputs getOutputs() {
return this.outputs;
}

@Override
public BlockEntityClientState getClientState() {
if (this.clientState == null) {
this.clientState = new BlockEntityClientState();
}

this.clientState.genState(this.outputs);
return this.clientState;
}

@Override
public void markRemoved() {
if (this.hasWorld() && this.world.isClient && this.clientState != null) {
this.clientState.dirty = true;
this.clientState.close();
}
super.markRemoved();
}

@Override
public long emitterId() {
return emitterId;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.github.foundationgames.phonos.client.render;

import io.github.foundationgames.phonos.world.sound.CableConnection;
import io.github.foundationgames.phonos.world.sound.block.BlockEntityOutputs;
import net.minecraft.client.gl.VertexBuffer;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;

public class BlockEntityClientState {
public boolean dirty;
public @Nullable VertexBuffer buffer = null;
private List<CableConnection> cachedCons = new ArrayList<>();

public void genState(BlockEntityOutputs outs) {
List<CableConnection> connections = new ArrayList<>();
outs.forEach((i, o) -> connections.add(o));

if (!cachedCons.equals(connections) || this.buffer == null) {
// Mark ourselves as needing re-rendering
dirty = true;

// Close the existing buffer
if (buffer != null) {
buffer.close();
}

buffer = null;
cachedCons = connections;
}
}

public void close() {
if (buffer != null) {
buffer.close();
}

buffer = null;
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package io.github.foundationgames.phonos.client.render;

import com.mojang.blaze3d.systems.RenderSystem;
import io.github.foundationgames.phonos.config.PhonosClientConfig;
import io.github.foundationgames.phonos.mixin.WorldRendererAccess;
import io.github.foundationgames.phonos.util.PhonosUtil;
import io.github.foundationgames.phonos.util.Pose3f;
import io.github.foundationgames.phonos.world.sound.CableConnection;
import io.github.foundationgames.phonos.world.sound.CablePlugPoint;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gl.VertexBuffer;
import net.minecraft.client.model.Model;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.render.*;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
Expand Down Expand Up @@ -65,9 +66,11 @@ private static void lerpCableEnd(Vector4f[] out, Vector4f[] begin, Vector4f[] en
}
}

public static void renderConnection(PhonosClientConfig config, World world, CableConnection conn, MatrixStack matrices, VertexConsumer buffer, Model cableEndModel, int overlay, float tickDelta) {
public static void renderConnection(BlockEntityClientState clientState, BlockEntity e, PhonosClientConfig config, World world, CableConnection conn, MatrixStack matrices, VertexConsumer buffer, Model cableEndModel, int overlay, float tickDelta) {
int startLight, endLight;

// Connection points are always rendered immediate

matrices.push();
transformConnPoint(world, conn.start, matrices, cableStPt, tickDelta);

Expand All @@ -87,105 +90,101 @@ public static void renderConnection(PhonosClientConfig config, World world, Cabl
cableEndModel.render(matrices, buffer, endLight, overlay, 1, 1, 1, 1);
matrices.pop();

if (config.cableCulling) {
var frustum = ((WorldRendererAccess) MinecraftClient.getInstance().worldRenderer).phonos$getFrustum();

if (!frustum.isVisible(
Math.min(cableStPt.x, cableEnPt.x), Math.min(cableStPt.y, cableEnPt.y), Math.min(cableStPt.z, cableEnPt.z),
Math.max(cableStPt.x, cableEnPt.x), Math.max(cableStPt.y, cableEnPt.y), Math.max(cableStPt.z, cableEnPt.z)
)) {
return;
// Rendering for the first time? Generate VBOs now.
if (clientState.dirty) {
BufferBuilder builder = Tessellator.getInstance().getBuffer();

matrices = new MatrixStack();
matrices.push();

float vOffset = conn.color != null ? 0.125f : 0;
float r = conn.color != null ? conn.color.getColorComponents()[0] : 1;
float g = conn.color != null ? conn.color.getColorComponents()[1] : 1;
float b = conn.color != null ? conn.color.getColorComponents()[2] : 1;
float length = cableStPt.distance(cableEnPt);

double detail = config.cableLODNearDetail;
int segments = Math.max((int) Math.ceil(4 * length * detail), 1);

// TODO: baking does not support LODs
// if (config.cableLODs) {
// float cx = (cableStPt.x + cableEnPt.x) * 0.5f;
// float cy = (cableStPt.y + cableEnPt.y) * 0.5f;
// float cz = (cableStPt.z + cableEnPt.z) * 0.5f;
//
// double sqDist = MinecraftClient.getInstance().gameRenderer.getCamera().getPos()
// .squaredDistanceTo(cx, cy, cz);
// double delta = MathHelper.clamp(sqDist / (length * length * 4), 0, 1);
// detail = MathHelper.lerp(delta, config.cableLODNearDetail, config.cableLODFarDetail);
//
// segments = Math.max((int) Math.ceil(4 * length * detail), Math.min(3, segments));
// }

final float texUWid = (float) (0.25 / detail);

cableRotAxis.set(cableEnPt.z - cableStPt.z, 0, cableEnPt.x - cableStPt.x);

float cablePitch = (float) Math.atan2(cableEnPt.y - cableStPt.y, cableRotAxis.length());
float cableYaw = (float) Math.atan2(cableRotAxis.z, cableRotAxis.x);
cableRotAxis.normalize();

loadCableEnd(cableStart);
loadCableEnd(cableEnd);
cableNormal[0].set(-PhonosUtil.SQRT2DIV2, -PhonosUtil.SQRT2DIV2, 0);
cableNormal[1].set(PhonosUtil.SQRT2DIV2, -PhonosUtil.SQRT2DIV2, 0);
cableNormal[2].set(PhonosUtil.SQRT2DIV2, PhonosUtil.SQRT2DIV2, 0);
cableNormal[3].set(-PhonosUtil.SQRT2DIV2, PhonosUtil.SQRT2DIV2, 0);

rotationCache.setAngleAxis(cablePitch, 1, 0, 0);
transformCableEnd(cableStart, vec -> vec.rotate(rotationCache));
transformCableEnd(cableEnd, vec -> vec.rotate(rotationCache));
transformCableNormal(cableNormal, vec -> vec.rotate(rotationCache));

rotationCache.setAngleAxis(Math.PI + cableYaw, 0, 1, 0);
transformCableEnd(cableStart, vec -> vec.rotate(rotationCache));
transformCableEnd(cableEnd, vec -> vec.rotate(rotationCache));
transformCableNormal(cableNormal, vec -> vec.rotate(rotationCache));

transformCableEnd(cableStart, vec -> vec.set(vec.x + cableStPt.x, vec.y + cableStPt.y, vec.z + cableStPt.z, 1));
transformCableEnd(cableEnd, vec -> vec.set(vec.x + cableEnPt.x, vec.y + cableEnPt.y, vec.z + cableEnPt.z, 1));

transformCableNormal(cableNormal, matrices.peek().getNormalMatrix()::transform);

for (int s = 0; s < segments; s++) {
float startDelta = (float) s / segments;
float endDelta = (float) (s + 1) / segments;
float startYOffset = length * 0.15f * (0.25f - (float) Math.pow(startDelta - 0.5, 2));
float endYOffset = length * 0.15f * (0.25f - (float) Math.pow(endDelta - 0.5, 2));
int segStartLight = PhonosUtil.lerpLight(startDelta, startLight, endLight);
int segEndLight = PhonosUtil.lerpLight(endDelta, startLight, endLight);

lerpCableEnd(currCableStart, cableStart, cableEnd, startDelta);
lerpCableEnd(currCableEnd, cableStart, cableEnd, endDelta);

transformCableEnd(currCableStart, vec -> vec.add(0, -startYOffset, 0, 0));
transformCableEnd(currCableEnd, vec -> vec.add(0, -endYOffset, 0, 0));

transformCableEnd(currCableStart, matrices.peek().getPositionMatrix()::transform);
transformCableEnd(currCableEnd, matrices.peek().getPositionMatrix()::transform);

for (int i = 0; i < 4; i++) {
float vOffset2 = i % 2 == 0 ? vOffset + 0.0625f : vOffset;
int next = (i + 1) % 4;
var nml = cableNormal[i];

builder.vertex(currCableStart[i].x, currCableStart[i].y, currCableStart[i].z).color(r, g, b, 1)
.texture(texUWid, 0.3125f + vOffset2).overlay(overlay).light(segStartLight).normal(nml.x, nml.y, nml.z).next();
builder.vertex(currCableEnd[i].x, currCableEnd[i].y, currCableEnd[i].z).color(r, g, b, 1)
.texture(0, 0.3125f + vOffset2).overlay(overlay).light(segEndLight).normal(nml.x, nml.y, nml.z).next();
builder.vertex(currCableEnd[next].x, currCableEnd[next].y, currCableEnd[next].z).color(r, g, b, 1)
.texture(0, 0.375f + vOffset2).overlay(overlay).light(segEndLight).normal(nml.x, nml.y, nml.z).next();
builder.vertex(currCableStart[next].x, currCableStart[next].y, currCableStart[next].z).color(r, g, b, 1)
.texture(texUWid, 0.375f + vOffset2).overlay(overlay).light(segStartLight).normal(nml.x, nml.y, nml.z).next();
}
}
}

matrices.push();

float vOffset = conn.color != null ? 0.125f : 0;
float r = conn.color != null ? conn.color.getColorComponents()[0] : 1;
float g = conn.color != null ? conn.color.getColorComponents()[1] : 1;
float b = conn.color != null ? conn.color.getColorComponents()[2] : 1;
float length = cableStPt.distance(cableEnPt);

double detail = config.cableLODNearDetail;
int segments = Math.max((int) Math.ceil(4 * length * detail), 1);

if (config.cableLODs) {
float cx = (cableStPt.x + cableEnPt.x) * 0.5f;
float cy = (cableStPt.y + cableEnPt.y) * 0.5f;
float cz = (cableStPt.z + cableEnPt.z) * 0.5f;

double sqDist = MinecraftClient.getInstance().gameRenderer.getCamera().getPos()
.squaredDistanceTo(cx, cy, cz);
double delta = MathHelper.clamp(sqDist / (length * length * 4), 0, 1);
detail = MathHelper.lerp(delta, config.cableLODNearDetail, config.cableLODFarDetail);

segments = Math.max((int) Math.ceil(4 * length * detail), Math.min(3, segments));
matrices.pop();
}

final float texUWid = (float) Math.ceil(length / segments) * 0.25f;

cableRotAxis.set(cableEnPt.z - cableStPt.z, 0, cableEnPt.x - cableStPt.x);

float cablePitch = (float) Math.atan2(cableEnPt.y - cableStPt.y, cableRotAxis.length());
float cableYaw = (float) Math.atan2(cableRotAxis.z, cableRotAxis.x);
cableRotAxis.normalize();

loadCableEnd(cableStart);
loadCableEnd(cableEnd);
cableNormal[0].set(-PhonosUtil.SQRT2DIV2, -PhonosUtil.SQRT2DIV2, 0);
cableNormal[1].set(PhonosUtil.SQRT2DIV2, -PhonosUtil.SQRT2DIV2, 0);
cableNormal[2].set(PhonosUtil.SQRT2DIV2, PhonosUtil.SQRT2DIV2, 0);
cableNormal[3].set(-PhonosUtil.SQRT2DIV2, PhonosUtil.SQRT2DIV2, 0);

rotationCache.setAngleAxis(cablePitch, 1, 0, 0);
transformCableEnd(cableStart, vec -> vec.rotate(rotationCache));
transformCableEnd(cableEnd, vec -> vec.rotate(rotationCache));
transformCableNormal(cableNormal, vec -> vec.rotate(rotationCache));

rotationCache.setAngleAxis(Math.PI + cableYaw, 0, 1, 0);
transformCableEnd(cableStart, vec -> vec.rotate(rotationCache));
transformCableEnd(cableEnd, vec -> vec.rotate(rotationCache));
transformCableNormal(cableNormal, vec -> vec.rotate(rotationCache));

transformCableEnd(cableStart, vec -> vec.set(vec.x + cableStPt.x, vec.y + cableStPt.y, vec.z + cableStPt.z, 1));
transformCableEnd(cableEnd, vec -> vec.set(vec.x + cableEnPt.x, vec.y + cableEnPt.y, vec.z + cableEnPt.z, 1));

transformCableNormal(cableNormal, matrices.peek().getNormalMatrix()::transform);

for (int s = 0; s < segments; s++) {
float startDelta = (float)s / segments;
float endDelta = (float)(s + 1) / segments;
float startYOffset = length * 0.15f * (0.25f - (float) Math.pow(startDelta - 0.5, 2));
float endYOffset = length * 0.15f * (0.25f - (float) Math.pow(endDelta - 0.5, 2));
int segStartLight = PhonosUtil.lerpLight(startDelta, startLight, endLight);
int segEndLight = PhonosUtil.lerpLight(endDelta, startLight, endLight);

lerpCableEnd(currCableStart, cableStart, cableEnd, startDelta);
lerpCableEnd(currCableEnd, cableStart, cableEnd, endDelta);

transformCableEnd(currCableStart, vec -> vec.add(0, -startYOffset, 0, 0));
transformCableEnd(currCableEnd, vec -> vec.add(0, -endYOffset, 0, 0));

transformCableEnd(currCableStart, matrices.peek().getPositionMatrix()::transform);
transformCableEnd(currCableEnd, matrices.peek().getPositionMatrix()::transform);

for (int i = 0; i < 4; i++) {
float vOffset2 = i % 2 == 0 ? vOffset + 0.0625f : vOffset;
int next = (i + 1) % 4;
var nml = cableNormal[i];

buffer.vertex(currCableStart[i].x, currCableStart[i].y, currCableStart[i].z).color(r, g, b, 1)
.texture(texUWid, 0.3125f + vOffset2).overlay(overlay).light(segStartLight).normal(nml.x, nml.y, nml.z).next();
buffer.vertex(currCableEnd[i].x, currCableEnd[i].y, currCableEnd[i].z).color(r, g, b, 1)
.texture(0, 0.3125f + vOffset2).overlay(overlay).light(segEndLight).normal(nml.x, nml.y, nml.z).next();
buffer.vertex(currCableEnd[next].x, currCableEnd[next].y, currCableEnd[next].z).color(r, g, b, 1)
.texture(0, 0.375f + vOffset2).overlay(overlay).light(segEndLight).normal(nml.x, nml.y, nml.z).next();
buffer.vertex(currCableStart[next].x, currCableStart[next].y, currCableStart[next].z).color(r, g, b, 1)
.texture(texUWid, 0.375f + vOffset2).overlay(overlay).light(segStartLight).normal(nml.x, nml.y, nml.z).next();
}
}

matrices.pop();
}


Expand Down
Loading

0 comments on commit beb55f0

Please sign in to comment.