This commit is contained in:
mcrcortex
2024-02-16 22:32:17 +10:00
parent 8293f0cfdd
commit 53d5d1c41f
12 changed files with 255 additions and 43 deletions

View File

@@ -0,0 +1,144 @@
package me.cortex.voxy.client.core.model;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.*;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.glBindTexture;
public class BakedBlockEntityModel {
private static final class BakedVertices implements VertexConsumer {
public final RenderLayer layer;
private float cX, cY, cZ;
private int cR, cG, cB, cA;
private float cU, cV;
private final List<int[]> vertices = new ArrayList<>();
private BakedVertices(RenderLayer layer) {
this.layer = layer;
}
@Override
public VertexConsumer vertex(double x, double y, double z) {
this.cX = (float) x;
this.cY = (float) y;
this.cZ = (float) z;
return this;
}
@Override
public VertexConsumer color(int red, int green, int blue, int alpha) {
this.cR = 0;//red;
this.cG = 0;//green;
this.cB = 0;//blue;
this.cA = alpha;
return this;
}
@Override
public VertexConsumer texture(float u, float v) {
this.cU = u;
this.cV = v;
return this;
}
@Override
public VertexConsumer overlay(int u, int v) {
return this;
}
@Override
public VertexConsumer light(int u, int v) {
return this;
}
@Override
public VertexConsumer normal(float x, float y, float z) {
return this;
}
@Override
public void fixedColor(int red, int green, int blue, int alpha) {
}
@Override
public void unfixColor() {
}
@Override
public void next() {
this.vertices.add(new int[]{
Float.floatToIntBits(this.cX), Float.floatToIntBits(this.cY), Float.floatToIntBits(this.cZ),
this.cR, this.cG, this.cB, this.cA,
Float.floatToIntBits(this.cU), Float.floatToIntBits(this.cV)});
}
public void putInto(VertexConsumer vc) {
for (var vert : this.vertices) {
vc.vertex(Float.intBitsToFloat(vert[0]), Float.intBitsToFloat(vert[1]), Float.intBitsToFloat(vert[2]))
.color(vert[3], vert[4], vert[5], vert[6])
.texture(Float.intBitsToFloat(vert[7]), Float.intBitsToFloat(vert[8]))
.next();
}
}
}
private final List<BakedVertices> layers;
private BakedBlockEntityModel(List<BakedVertices> layers) {
this.layers = layers;
}
public void renderOut() {
var vc = Tessellator.getInstance().getBuffer();
for (var layer : this.layers) {
vc.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR_TEXTURE);
if (layer.layer instanceof RenderLayer.MultiPhase mp) {
Identifier textureId = mp.phases.texture.getId().orElse(null);
if (textureId == null) {
System.err.println("ERROR: Empty texture id for layer: " + layer);
} else {
var texture = MinecraftClient.getInstance().getTextureManager().getTexture(textureId);
glBindTexture(GL_TEXTURE_2D, texture.getGlId());
}
}
layer.putInto(vc);
BufferRenderer.draw(vc.end());
}
}
public static BakedBlockEntityModel bake(BlockState state) {
Map<RenderLayer, BakedVertices> map = new HashMap<>();
var entity = ((BlockEntityProvider)state.getBlock()).createBlockEntity(BlockPos.ORIGIN, state);
if (entity == null) {
return null;
}
var renderer = MinecraftClient.getInstance().getBlockEntityRenderDispatcher().get(entity);
if (renderer != null) {
entity.setWorld(MinecraftClient.getInstance().world);
try {
renderer.render(entity, 0.0f, new MatrixStack(), layer->map.computeIfAbsent(layer, BakedVertices::new), 0, 0);
} catch (Exception e) {
System.err.println("Unable to bake block entity: " + entity);
e.printStackTrace();
}
}
entity.markRemoved();
if (map.isEmpty()) {
return null;
}
return new BakedBlockEntityModel(map.values().stream().toList());
}
}

View File

@@ -3,6 +3,9 @@ package me.cortex.voxy.client.core.model;
import com.mojang.blaze3d.platform.GlConst;
import com.mojang.blaze3d.platform.GlStateManager;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectSet;
import me.cortex.voxy.client.core.IGetVoxelCore;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
@@ -53,6 +56,7 @@ import static org.lwjgl.opengl.GL45C.glTextureSubImage2D;
// to all other models already loaded, if it is a duplicate, create a mapping from the id to the already loaded id, this will help with meshing aswell
// as leaves and such will be able to be merged
public class ModelManager {
//TODO: replace the fluid BlockState with a client model id integer of the fluidState, requires looking up
// the fluid state in the mipper
private record ModelEntry(List<ColourDepthTextureData> textures, int fluidBlockStateId){
@@ -108,6 +112,7 @@ public class ModelManager {
private final List<Biome> biomes = new ArrayList<>();
private final List<Pair<Integer, BlockState>> modelsRequiringBiomeColours = new ArrayList<>();
private static final ObjectSet<BlockState> LOGGED_SELF_CULLING_WARNING = new ObjectOpenHashSet<>();
public ModelManager(int modelTextureSize) {
this.modelTextureSize = modelTextureSize;
@@ -225,6 +230,31 @@ public class ModelManager {
boolean needsDoubleSidedQuads = (sizes[0] < -0.1 && sizes[1] < -0.1) || (sizes[2] < -0.1 && sizes[3] < -0.1) || (sizes[4] < -0.1 && sizes[5] < -0.1);
boolean cullsSame = false;
{
//TODO: Could also move this into the RenderDataFactory and do it on the actual blockstates instead of a guestimation
boolean allTrue = true;
boolean allFalse = true;
//Guestimation test for if the block culls itself
for (var dir : Direction.values()) {
if (blockState.isSideInvisible(blockState, dir)) {
allFalse = false;
} else {
allTrue = false;
}
}
if (allFalse == allTrue) {//If only some sides where self culled then abort
cullsSame = false;
if (LOGGED_SELF_CULLING_WARNING.add(blockState)) System.err.println("Warning! blockstate: " + blockState + " only culled against its self some of the time");
}
if (allTrue) {
cullsSame = true;
}
}
//Each face gets 1 byte, with the top 2 bytes being for whatever
long metadata = 0;
@@ -234,6 +264,8 @@ public class ModelManager {
metadata |= (!blockState.getFluidState().isEmpty())?8:0;//Has a fluid state accosiacted with it
metadata |= isFluid?16:0;//Is a fluid
metadata |= cullsSame?32:0;
//TODO: add a bunch of control config options for overriding/setting options of metadata for each face of each type
for (int face = 5; face != -1; face--) {//In reverse order to make indexing into the metadata long easier
long faceUploadPtr = uploadPtr + 4L * face;//Each face gets 4 bytes worth of data
@@ -512,6 +544,10 @@ public class ModelManager {
return ((metadata>>(8*6))&1) != 0;
}
//NOTE: this might need to be moved to per face
public static boolean cullsSame(long metadata) {
return ((metadata>>(8*6))&32) != 0;
}
@@ -590,8 +626,8 @@ public class ModelManager {
int Y = ((id>>8)&0xFF) * this.modelTextureSize*2;
for (int subTex = 0; subTex < 6; subTex++) {
int x = X + (subTex%3)*this.modelTextureSize;
int y = Y + (subTex/3)*this.modelTextureSize;
int x = X + (subTex>>1)*this.modelTextureSize;
int y = Y + (subTex&1)*this.modelTextureSize;
GlStateManager._pixelStore(GlConst.GL_UNPACK_ROW_LENGTH, 0);
GlStateManager._pixelStore(GlConst.GL_UNPACK_SKIP_PIXELS, 0);

View File

@@ -102,6 +102,7 @@ public class ModelTextureBakery {
}
//TODO: For block entities, also somehow attempt to render the default block entity, e.g. chests and stuff
// cause that will result in ok looking micro details in the terrain
public ColourDepthTextureData[] renderFaces(BlockState state, long randomValue, boolean renderFluid) {
@@ -111,6 +112,8 @@ public class ModelTextureBakery {
.getBlockModels()
.getModel(state);
var entityModel = state.hasBlockEntity()?BakedBlockEntityModel.bake(state):null;
int oldFB = GlStateManager.getBoundFramebuffer();
var oldProjection = new Matrix4f(RenderSystem.getProjectionMatrix());
GL11C.glViewport(0, 0, this.width, this.height);
@@ -165,13 +168,12 @@ public class ModelTextureBakery {
this.rasterShader.bind();
glActiveTexture(GL_TEXTURE0);
int texId = MinecraftClient.getInstance().getTextureManager().getTexture(new Identifier("minecraft", "textures/atlas/blocks.png")).getGlId();
glBindTexture(GL_TEXTURE_2D, texId);
GlUniform.uniform1(0, 0);
var faces = new ColourDepthTextureData[FACE_VIEWS.size()];
for (int i = 0; i < faces.length; i++) {
faces[i] = captureView(state, model, FACE_VIEWS.get(i), randomValue, i, renderFluid);
//glBlitNamedFramebuffer(this.framebuffer.id, oldFB, 0,0,16,16,300*(i%3),300*(i/3),300*(i%3)+256,300*(i/3)+256, GL_COLOR_BUFFER_BIT, GL_NEAREST);
faces[i] = captureView(state, model, entityModel, FACE_VIEWS.get(i), randomValue, i, renderFluid, texId);
//glBlitNamedFramebuffer(this.framebuffer.id, oldFB, 0,0,16,16,300*(i>>1),300*(i&1),300*(i>>1)+256,300*(i&1)+256, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
renderLayer.endDrawing();
@@ -188,28 +190,21 @@ public class ModelTextureBakery {
return faces;
}
private ColourDepthTextureData captureView(BlockState state, BakedModel model, MatrixStack stack, long randomValue, int face, boolean renderFluid) {
private ColourDepthTextureData captureView(BlockState state, BakedModel model, BakedBlockEntityModel blockEntityModel, MatrixStack stack, long randomValue, int face, boolean renderFluid, int textureId) {
var vc = Tessellator.getInstance().getBuffer();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
float[] mat = new float[4*4];
new Matrix4f(RenderSystem.getProjectionMatrix()).mul(stack.peek().getPositionMatrix()).get(mat);
glUniformMatrix4fv(1, false, mat);
if (blockEntityModel != null && !renderFluid) {
blockEntityModel.renderOut();
}
vc.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR_TEXTURE);
//if (state.hasBlockEntity() && state.getBlock() == Blocks.CHEST) {
// //TODO: finish BlockEntity raster
// var entity = ((BlockEntityProvider)state.getBlock()).createBlockEntity(BlockPos.ORIGIN, state);
// var renderer = MinecraftClient.getInstance().getBlockEntityRenderDispatcher().get(entity);
// if (renderer != null) {
// entity.setWorld(MinecraftClient.getInstance().world);
// renderer.render(entity, 0.0f, new MatrixStack(), (layer) -> {
// //glBindTexture(GL_TEXTURE_2D);
// return vc;
// }, 0, 0);
// }
// entity.markRemoved();
//}
if (!renderFluid) {
renderQuads(vc, state, model, new MatrixStack(), randomValue);
} else {
@@ -240,13 +235,21 @@ public class ModelTextureBakery {
return null;
}
//TODO: make it so it returns air on some positions, e.g. so from UP,
@Override
public BlockState getBlockState(BlockPos pos) {
//TODO:FIXME: Dont hardcode
if (pos.equals(Direction.byId(face).getVector())) {
return Blocks.AIR.getDefaultState();
}
//Fixme:
// This makes it so that the top face of water is always air, if this is commented out
// the up block will be a liquid state which makes the sides full
// if this is uncommented, that issue is fixed but e.g. stacking water layers ontop of eachother
// doesnt fill the side of the block
//if (pos.getY() == 1) {
// return Blocks.AIR.getDefaultState();
//}
return state;
}
@@ -255,6 +258,9 @@ public class ModelTextureBakery {
if (pos.equals(Direction.byId(face).getVector())) {
return Blocks.AIR.getDefaultState().getFluidState();
}
//if (pos.getY() == 1) {
// return Blocks.AIR.getDefaultState().getFluidState();
//}
return state.getFluidState();
}
@@ -270,11 +276,7 @@ public class ModelTextureBakery {
}, vc, state, state.getFluidState());
}
float[] mat = new float[4*4];
new Matrix4f(RenderSystem.getProjectionMatrix()).mul(stack.peek().getPositionMatrix()).get(mat);
glUniformMatrix4fv(1, false, mat);
glBindTexture(GL_TEXTURE_2D, textureId);
BufferRenderer.draw(vc.end());

View File

@@ -105,7 +105,7 @@ public abstract class AbstractFarWorldRenderer {
UploadStream.INSTANCE.commit();
}
int maxUpdatesPerFrame = 10;
int maxUpdatesPerFrame = 40;
//Do any BlockChanges
while ((!this.blockStateUpdates.isEmpty()) && (maxUpdatesPerFrame-- > 0)) {

View File

@@ -0,0 +1,5 @@
package me.cortex.voxy.client.core.rendering;
public class FrustumLoDComputation {
}

View File

@@ -8,6 +8,7 @@ import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.mixin.joml.AccessFrustumIntersection;
import net.minecraft.block.Blocks;
import net.minecraft.block.FluidBlock;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.util.math.MatrixStack;
@@ -113,7 +114,8 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
glDisable(GL_BLEND);
//this.models.bakery.renderFaces(Blocks.OAK_LEAVES.getDefaultState(), 1234, false);
//this.models.bakery.renderFaces(Blocks.WATER.getDefaultState().with(FluidBlock.LEVEL, 1), 1234, true);
//this.models.bakery.renderFaces(Blocks.CHEST.getDefaultState(), 1234, false);
RenderLayer.getCutoutMipped().startDrawing();
@@ -141,8 +143,9 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
this.lodShader.bind();
glDisable(GL_CULL_FACE);
glPointSize(10);
//glPointSize(10);
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, 0, (int) (this.geometry.getSectionCount()*4.4), 0);
//glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, (int) (this.geometry.getSectionCount()*4.4), 0);
glEnable(GL_CULL_FACE);
glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);

View File

@@ -245,18 +245,29 @@ public class RenderDataFactory {
}
if (facingFluidClientId != -1) {
if (this.world.getMapper().getBlockStateFromId(selfBlockId).getFluidState().getFluid() == this.world.getMapper().getBlockStateFromId(facingState).getFluidState().getFluid()) {
return false;
if (this.world.getMapper().getBlockStateFromBlockId(selfBlockId).getBlock() == this.world.getMapper().getBlockStateFromBlockId(Mapper.getBlockId(facingState)).getBlock()) {
return false;
}
}
if (ModelManager.faceOccludes(facingMetadata, opposingFace)) {
return false;
}
//NOTE: if the model has a fluid state but is not a liquid need to see if the solid state had a face rendered and that face is occluding, if so, dont render the fluid state face
//if the model has a fluid state but is not a liquid need to see if the solid state had a face rendered and that face is occluding, if so, dont render the fluid state face
if (ModelManager.faceOccludes(metadata, face)) {
return false;
}
//TODO:FIXME FINISH
//TODO:FIXME SOMEHOW THIS IS CRITICAL!!!!!!!!!!!!!!!!!!
// so there is one more issue need to be fixed, if water is layered ontop of eachother, the side faces depend on the water state ontop
// this has been hackfixed in the model texture bakery but a proper solution that doesnt explode the sides of the water textures needs to be done
// the issue is that the fluid rendering depends on the up state aswell not just the face state which is really really painful to account for
// e.g the sides of a full water is 8 high or something, not the full block height, this results in a gap between water layers
@@ -276,8 +287,8 @@ public class RenderDataFactory {
return false;
}
if (ModelManager.isTranslucent(metadata) && selfBlockId == Mapper.getBlockId(facingState)) {
//If we are facing a block, and are translucent and it is the same block as us, cull the quad
if (ModelManager.cullsSame(metadata) && selfBlockId == Mapper.getBlockId(facingState)) {
//If we are facing a block, and we are both the same state, dont render that face
return false;
}

View File

@@ -28,6 +28,9 @@ public class BufferArena {
}
int size = (int) (buffer.size/this.elementSize);
long addr = this.allocationMap.alloc(size);
if (addr == -1) {
throw new IllegalStateException("Buffer arena out of memory");
}
long uploadPtr = UploadStream.INSTANCE.upload(this.buffer, addr * this.elementSize, buffer.size);
MemoryUtil.memCopy(buffer.address, uploadPtr, buffer.size);
this.used += size;

View File

@@ -36,6 +36,9 @@ public class UploadStream {
private long caddr = -1;
private long offset = 0;
public long upload(GlBuffer buffer, long destOffset, long size) {
if (destOffset<0) {
throw new IllegalArgumentException();
}
if (size > Integer.MAX_VALUE) {
throw new IllegalArgumentException();
}

View File

@@ -179,8 +179,7 @@ public class Mapper {
return composeMappingId(light, this.getIdForBlockState(state), this.getIdForBiome(biome));
}
public BlockState getBlockStateFromId(long id) {
int blockId = (int) ((id>>27)&((1<<20)-1));
public BlockState getBlockStateFromBlockId(int blockId) {
return this.blockId2stateEntry.get(blockId).state;
}

View File

@@ -86,7 +86,7 @@ void main() {
vec2 modelUV = vec2(modelId&0xFF, (modelId>>8)&0xFF)*(1.0/(256.0));
//TODO: make the face orientated by 2x3 so that division is not a integer div and modulo isnt needed
// as these are very slow ops
baseUV = modelUV + (vec2(face%3, face/3) * (1.0/(vec2(3.0, 2.0)*256.0)));
baseUV = modelUV + (vec2(face>>1, face&1) * (1.0/(vec2(3.0, 2.0)*256.0)));
//TODO: add an option to scale the quad size by the lod level so that
// e.g. at lod level 2 a face will have 2x2
uv = respectiveQuadSize + faceOffset;//Add in the face offset for 0,0 uv

View File

@@ -10,4 +10,10 @@ accessible field net/minecraft/client/render/GameRenderer zoom F
accessible field net/minecraft/client/world/ClientWorld worldRenderer Lnet/minecraft/client/render/WorldRenderer;
accessible field net/minecraft/world/biome/source/BiomeAccess seed J
accessible class net/minecraft/client/render/RenderLayer$MultiPhase
accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters
accessible field net/minecraft/client/render/RenderLayer$MultiPhaseParameters texture Lnet/minecraft/client/render/RenderPhase$TextureBase;
accessible method net/minecraft/client/render/RenderPhase$TextureBase getId ()Ljava/util/Optional;
accessible field net/minecraft/client/render/RenderLayer$MultiPhase phases Lnet/minecraft/client/render/RenderLayer$MultiPhaseParameters;
accessible method net/minecraft/client/render/GameRenderer getFov (Lnet/minecraft/client/render/Camera;FZ)D