Uhhhhh refactor??? ig???

This commit is contained in:
mcrcortex
2024-07-29 09:09:57 +10:00
parent 25712a850e
commit ca899dd506
56 changed files with 640 additions and 1029 deletions

View File

@@ -50,7 +50,9 @@ public class VoxyConfig {
e.printStackTrace();
}
}
return new VoxyConfig();
var config = new VoxyConfig();
config.defaultSaveConfig = ContextSelectionSystem.DEFAULT_STORAGE_CONFIG;
return config;
}
public void save() {
//Unsafe, todo: fixme! needs to be atomic!

View File

@@ -1,17 +0,0 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.common.world.WorldSection;
public interface AbstractRenderWorldInteractor {
void processBuildResult(BuiltSection section);
void sectionUpdated(WorldSection worldSection);
void setRenderGen(RenderGenerationService renderService);
void initPosition(int x, int z);
void setCenter(int x, int y, int z);
}

View File

@@ -1,66 +0,0 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.rendering.AbstractFarWorldRenderer;
import me.cortex.voxy.client.core.rendering.IRenderInterface;
import me.cortex.voxy.client.core.rendering.RenderTracker;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.client.saver.ContextSelectionSystem;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.WorldSection;
import net.minecraft.client.MinecraftClient;
public class DefaultRenderWorldInteractor implements AbstractRenderWorldInteractor {
private final DistanceTracker distanceTracker;
private final RenderTracker renderTracker;
public DefaultRenderWorldInteractor(ContextSelectionSystem.WorldConfig cfg, WorldEngine world, IRenderInterface renderer) {
this.renderTracker = new RenderTracker(world, (AbstractFarWorldRenderer) renderer);
//To get to chunk scale multiply the scale by 2, the scale is after how many chunks does the lods halve
int q = VoxyConfig.CONFIG.qualityScale;
int minY = MinecraftClient.getInstance().world.getBottomSectionCoord()/2;
int maxY = MinecraftClient.getInstance().world.getTopSectionCoord()/2;
if (cfg.minYOverride != Integer.MAX_VALUE) {
minY = cfg.minYOverride;
}
if (cfg.maxYOverride != Integer.MIN_VALUE) {
maxY = cfg.maxYOverride;
}
this.distanceTracker = new DistanceTracker(this.renderTracker, new int[]{q,q,q,q},
(VoxyConfig.CONFIG.renderDistance<0?VoxyConfig.CONFIG.renderDistance:((VoxyConfig.CONFIG.renderDistance+1)/2)),
minY, maxY);
System.out.println("Distance tracker initialized");
}
@Override
public void processBuildResult(BuiltSection section) {
this.renderTracker.processBuildResult(section);
}
@Override
public void sectionUpdated(WorldSection worldSection) {
this.renderTracker.sectionUpdated(worldSection);
}
@Override
public void setRenderGen(RenderGenerationService renderService) {
this.renderTracker.setRenderGen(renderService);
}
@Override
public void initPosition(int x, int z) {
this.distanceTracker.init(x,z);
}
@Override
public void setCenter(int x, int y, int z) {
this.distanceTracker.setCenter(x,y,z);
}
}

View File

@@ -1,6 +1,5 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.client.core.rendering.AbstractFarWorldRenderer;
import me.cortex.voxy.client.core.rendering.Viewport;
import net.fabricmc.loader.api.FabricLoader;
import org.vivecraft.client_vr.ClientDataHolderVR;
@@ -9,7 +8,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class ViewportSelector <T extends Viewport> {
public class ViewportSelector <T extends Viewport<?>> {
public static final boolean VIVECRAFT_INSTALLED = FabricLoader.getInstance().isModLoaded("vivecraft");
private final Supplier<T> creator;

View File

@@ -3,7 +3,7 @@ package me.cortex.voxy.client.core;
import com.mojang.blaze3d.systems.RenderSystem;
import me.cortex.voxy.client.Voxy;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.model.ModelManager;
import me.cortex.voxy.client.core.model.OffThreadModelBakerySystem;
import me.cortex.voxy.client.core.rendering.*;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.client.core.rendering.post.PostProcessing;
@@ -54,11 +54,8 @@ import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING;
//REMOVE setRenderGen like holy hell
public class VoxelCore {
private final WorldEngine world;
private final RenderGenerationService renderGen;
private final ModelManager modelManager;
private final AbstractRenderWorldInteractor interactor;
private final IRenderInterface renderer;
private final RenderService renderer;
private final ViewportSelector viewportSelector;
private final PostProcessing postProcessing;
@@ -73,46 +70,12 @@ public class VoxelCore {
//Trigger the shared index buffer loading
SharedIndexBuffer.INSTANCE.id();
Capabilities.init();//Ensure clinit is called
this.modelManager = new ModelManager(16);
this.renderer = this.createRenderBackend();
this.renderer = new RenderService(this.world);
System.out.println("Using " + this.renderer.getClass().getSimpleName());
this.viewportSelector = new ViewportSelector<>(this.renderer::createViewport);
//Ungodly hacky code
if (this.renderer instanceof AbstractRenderWorldInteractor) {
this.interactor = (AbstractRenderWorldInteractor) this.renderer;
} else {
this.interactor = new DefaultRenderWorldInteractor(cfg, this.world, this.renderer);
}
System.out.println("Renderer initialized");
this.renderGen = new RenderGenerationService(this.world, this.modelManager, VoxyConfig.CONFIG.renderThreads, this.interactor::processBuildResult, this.renderer.generateMeshlets());
this.world.setDirtyCallback(this.interactor::sectionUpdated);
this.interactor.setRenderGen(this.renderGen);
System.out.println("Render tracker and generator initialized");
this.postProcessing = new PostProcessing();
this.world.getMapper().setCallbacks(this.renderer::addBlockState, this.renderer::addBiome);
////Resave the db incase it failed a recovery
//this.world.getMapper().forceResaveStates();
var biomeRegistry = MinecraftClient.getInstance().world.getRegistryManager().get(RegistryKeys.BIOME);
for (var biome : this.world.getMapper().getBiomeEntries()) {
//this.renderer.getModelManager().addBiome(biome.id, biomeRegistry.get(new Identifier(biome.biome)));
this.renderer.addBiome(biome);
}
for (var state : this.world.getMapper().getStateEntries()) {
//this.renderer.getModelManager().addEntry(state.id, state.state);
this.renderer.addBlockState(state);
}
//this.renderer.getModelManager().updateEntry(0, Blocks.GRASS_BLOCK.getDefaultState());
System.out.println("Voxy core initialized");
}
@@ -120,16 +83,8 @@ public class VoxelCore {
this.world.ingestService.enqueueIngest(worldChunk);
}
boolean firstTime = true;
public void renderSetup(Frustum frustum, Camera camera) {
if (this.firstTime) {
//TODO: remove initPosition
this.interactor.initPosition(camera.getBlockPos().getX(), camera.getBlockPos().getZ());
this.firstTime = false;
//this.renderTracker.addLvl0(0,6,0);
}
this.interactor.setCenter(camera.getBlockPos().getX(), camera.getBlockPos().getY(), camera.getBlockPos().getZ());
this.renderer.setupRender(frustum, camera);
this.renderer.setup(camera);
}
private static Matrix4f makeProjectionMatrix(float near, float far) {
@@ -147,6 +102,7 @@ public class VoxelCore {
return projection;
}
//TODO: Make a reverse z buffer
private static Matrix4f computeProjectionMat() {
return new Matrix4f(RenderSystem.getProjectionMatrix()).mulLocal(
makeProjectionMatrix(0.05f, MinecraftClient.getInstance().gameRenderer.getFarPlaneDistance()).invert()
@@ -160,16 +116,9 @@ public class VoxelCore {
matrices.push();
matrices.translate(-cameraX, -cameraY, -cameraZ);
matrices.pop();
//this.renderer.getModelManager().updateEntry(0, Blocks.DIRT_PATH.getDefaultState());
//this.renderer.getModelManager().updateEntry(0, Blocks.COMPARATOR.getDefaultState());
//this.renderer.getModelManager().updateEntry(0, Blocks.OAK_LEAVES.getDefaultState());
//var fb = Iris.getPipelineManager().getPipelineNullable().getSodiumTerrainPipeline().getTerrainSolidFramebuffer();
//fb.bind();
var projection = computeProjectionMat();
//var projection = RenderSystem.getProjectionMatrix();//computeProjectionMat();
var viewport = this.viewportSelector.getViewport();
viewport
.setProjection(projection)
@@ -183,7 +132,7 @@ public class VoxelCore {
this.renderer.renderFarAwayOpaque(viewport);
//Compute the SSAO of the rendered terrain
//this.postProcessing.computeSSAO(projection, matrices);
this.postProcessing.computeSSAO(projection, matrices);
//We can render the translucent directly after as it is the furthest translucent objects
this.renderer.renderFarAwayTranslucent(viewport);
@@ -202,9 +151,8 @@ public class VoxelCore {
debug.add("Saving service tasks: " + this.world.savingService.getTaskCount());
debug.add("Render service tasks: " + this.renderGen.getTaskCount());
*/
debug.add("I/S/R tasks: " + this.world.ingestService.getTaskCount() + "/"+this.world.savingService.getTaskCount()+"/"+this.renderGen.getTaskCount());
debug.add("Loaded cache sizes: " + Arrays.toString(this.world.getLoadedSectionCacheSizes()));
debug.add("Mesh cache count: " + this.renderGen.getMeshCacheCount());
debug.add("I/S tasks: " + this.world.ingestService.getTaskCount() + "/"+this.world.savingService.getTaskCount());
debug.add("SCS: " + Arrays.toString(this.world.getLoadedSectionCacheSizes()));
this.renderer.addDebugData(debug);
}
@@ -226,11 +174,9 @@ public class VoxelCore {
try {this.importer.shutdown();this.importer = null;} catch (Exception e) {e.printStackTrace();}
}
System.out.println("Shutting down voxel core");
try {this.renderGen.shutdown();} catch (Exception e) {e.printStackTrace();}
System.out.println("Render gen shut down");
try {this.world.shutdown();} catch (Exception e) {e.printStackTrace();}
System.out.println("World engine shut down");
try {this.renderer.shutdown(); this.viewportSelector.free(); this.modelManager.free();} catch (Exception e) {e.printStackTrace();}
try {this.renderer.shutdown(); this.viewportSelector.free();} catch (Exception e) {e.printStackTrace();}
System.out.println("Renderer shut down");
if (this.postProcessing!=null){try {this.postProcessing.shutdown();} catch (Exception e) {e.printStackTrace();}}
System.out.println("Voxel core shut down");
@@ -264,20 +210,4 @@ public class VoxelCore {
public WorldEngine getWorldEngine() {
return this.world;
}
private IRenderInterface<?> createRenderBackend() {
if (true) {
return new Gl46HierarchicalRenderer(this.modelManager);
} else if (false) {
return new Gl46MeshletsFarWorldRenderer(this.modelManager, VoxyConfig.CONFIG.geometryBufferSize, VoxyConfig.CONFIG.maxSections);
} else {
if (VoxyConfig.CONFIG.useMeshShaders()) {
return new NvMeshFarWorldRenderer(this.modelManager, VoxyConfig.CONFIG.geometryBufferSize, VoxyConfig.CONFIG.maxSections);
} else {
return new Gl46FarWorldRenderer(this.modelManager, VoxyConfig.CONFIG.geometryBufferSize, VoxyConfig.CONFIG.maxSections);
}
}
}
}

View File

@@ -89,6 +89,10 @@ public class BakedBlockEntityModel {
.texture(Float.intBitsToFloat(vert[7]), Float.intBitsToFloat(vert[8]));
}
}
public boolean isEmpty() {
return this.vertices.isEmpty();
}
}
private final List<BakedVertices> layers;
@@ -99,6 +103,7 @@ public class BakedBlockEntityModel {
public void renderOut() {
var vc = Tessellator.getInstance();
for (var layer : this.layers) {
if (layer.isEmpty()) continue;
var bb = vc.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR);
if (layer.layer instanceof RenderLayer.MultiPhase mp) {
Identifier textureId = mp.phases.texture.getId().orElse(null);

View File

@@ -3,7 +3,6 @@ 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;
@@ -11,9 +10,7 @@ import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.world.other.Mapper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.FluidBlock;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.MinecraftClient;
@@ -38,6 +35,7 @@ import org.lwjgl.system.MemoryUtil;
import java.util.*;
import java.util.stream.Stream;
import static me.cortex.voxy.client.core.model.ModelStore.MODEL_SIZE;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
import static org.lwjgl.opengl.GL11C.GL_NEAREST_MIPMAP_LINEAR;
@@ -55,7 +53,13 @@ import static org.lwjgl.opengl.GL45C.glTextureSubImage2D;
//TODO: support more than 65535 states, what should actually happen is a blockstate is registered, the model data is generated, then compared
// 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: NOTE!!! is it worth even uploading as a 16x16 texture, since automatic lod selection... doing 8x8 textures might be perfectly ok!!!
// this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving
public class ModelFactory {
public static final int MODEL_TEXTURE_SIZE = 16;
//TODO: replace the fluid BlockState with a client model id integer of the fluidState, requires looking up
// the fluid state in the mipper
@@ -65,14 +69,11 @@ public class ModelManager {
}
}
public static final int MODEL_SIZE = 64;
private final Biome DEFAULT_BIOME = MinecraftClient.getInstance().world.getRegistryManager().get(RegistryKeys.BIOME).get(BiomeKeys.PLAINS);
public final ModelTextureBakery bakery;
private final GlBuffer modelBuffer;
private final GlBuffer modelColourBuffer;
private final GlTexture textures;
private final int blockSampler = glGenSamplers();
private final int modelTextureSize;
//Model data might also contain a constant colour if the colour resolver produces a constant colour, this saves space in the
// section buffer reverse indexing
@@ -108,21 +109,20 @@ public class ModelManager {
private final int[] idMappings;
private final Object2IntOpenHashMap<ModelEntry> modelTexture2id = new Object2IntOpenHashMap<>();
private final Mapper mapper;
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;
this.bakery = new ModelTextureBakery(modelTextureSize, modelTextureSize);
this.modelBuffer = new GlBuffer(MODEL_SIZE * (1<<16));
this.modelColourBuffer = new GlBuffer(4 * (1<<16));
//TODO: NOTE!!! is it worth even uploading as a 16x16 texture, since automatic lod selection... doing 8x8 textures might be perfectly ok!!!
// this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving
public ModelFactory(Mapper mapper) {
this.mapper = mapper;
this.bakery = new ModelTextureBakery(MODEL_TEXTURE_SIZE, MODEL_TEXTURE_SIZE);
//TODO: figure out how to do mipping :blobfox_pineapple:
this.textures = new GlTexture().store(GL_RGBA8, 4, modelTextureSize*3*256,modelTextureSize*2*256);
this.metadataCache = new long[1<<16];
this.fluidStateLUT = new int[1<<16];
this.idMappings = new int[1<<20];//Max of 1 million blockstates mapping to 65k model states
@@ -141,15 +141,23 @@ public class ModelManager {
public void addEntry(int blockId) {
if (this.idMappings[blockId] != -1) {
System.err.println("Block id already added: " + blockId);
return;
}
this.addEntry(blockId, this.mapper.getBlockStateFromBlockId(blockId));
}
//TODO: what i need to do is seperate out fluid states from blockStates
//TODO: so need a few things, per face sizes and offsets, the sizes should be computed from the pixels and find the minimum bounding pixel
// while the depth is computed from the depth buffer data
public int addEntry(int blockId, BlockState blockState) {
public void addEntry(int blockId, BlockState blockState) {
if (this.idMappings[blockId] != -1) {
System.err.println("Block id already added: " + blockId + " for state: " + blockState);
return this.idMappings[blockId];
return;
}
boolean isFluid = blockState.getBlock() instanceof FluidBlock;
@@ -162,13 +170,13 @@ public class ModelManager {
//Insert into the fluid LUT
var fluidState = blockState.getFluidState().getBlockState();
//TODO:FIXME: PASS IN THE Mapper instead of grabbing it!!! THIS IS CRTICIAL TO FIX
int fluidStateId = ((IGetVoxelCore)MinecraftClient.getInstance().worldRenderer).getVoxelCore().getWorldEngine().getMapper().getIdForBlockState(fluidState);
int fluidStateId = this.mapper.getIdForBlockState(fluidState);
clientFluidStateId = this.idMappings[fluidStateId];
if (clientFluidStateId == -1) {
clientFluidStateId = this.addEntry(fluidStateId, fluidState);
this.addEntry(fluidStateId, fluidState);
clientFluidStateId = this.idMappings[fluidStateId];
}
}
@@ -178,10 +186,11 @@ public class ModelManager {
if (possibleDuplicate != -1) {//Duplicate found
this.idMappings[blockId] = possibleDuplicate;
modelId = possibleDuplicate;
return possibleDuplicate;
return;
} else {//Not a duplicate so create a new entry
modelId = this.modelTexture2id.size();
this.idMappings[blockId] = modelId;
//NOTE: we set the mapping at the very end so that race conditions with this and getMetadata dont occur
//this.idMappings[blockId] = modelId;
this.modelTexture2id.put(entry, modelId);
}
}
@@ -208,7 +217,8 @@ public class ModelManager {
long uploadPtr = UploadStream.INSTANCE.upload(this.modelBuffer, (long) modelId * MODEL_SIZE, MODEL_SIZE);
final long uploadPtrConst = MemoryUtil.nmemAlloc(MODEL_SIZE);
long uploadPtr = uploadPtrConst;
//TODO: implement;
@@ -282,7 +292,7 @@ public class ModelManager {
int writeCount = TextureUtils.getWrittenPixelCount(textureData[face], checkMode);
boolean faceCoversFullBlock = faceSize[0] == 0 && faceSize[2] == 0 &&
faceSize[1] == (this.modelTextureSize-1) && faceSize[3] == (this.modelTextureSize-1);
faceSize[1] == (MODEL_TEXTURE_SIZE-1) && faceSize[3] == (MODEL_TEXTURE_SIZE-1);
metadata |= faceCoversFullBlock?2:0;
@@ -294,7 +304,7 @@ public class ModelManager {
occludesFace &= offset < 0.1;//If the face is rendered far away from the other face, then it doesnt occlude
if (occludesFace) {
occludesFace &= ((float)writeCount)/(this.modelTextureSize * this.modelTextureSize) > 0.9;// only occlude if the face covers more than 90% of the face
occludesFace &= ((float)writeCount)/(MODEL_TEXTURE_SIZE * MODEL_TEXTURE_SIZE) > 0.9;// only occlude if the face covers more than 90% of the face
}
metadata |= occludesFace?1:0;
@@ -313,7 +323,7 @@ public class ModelManager {
//Scale face size from 0->this.modelTextureSize-1 to 0->15
for (int i = 0; i < 4; i++) {
faceSize[i] = Math.round((((float)faceSize[i])/(this.modelTextureSize-1))*15);
faceSize[i] = Math.round((((float)faceSize[i])/(MODEL_TEXTURE_SIZE-1))*15);
}
int faceModelData = 0;
@@ -348,20 +358,23 @@ public class ModelManager {
//modelFlags |= blockRenderLayer == RenderLayer.getSolid()?0:1;// should discard alpha
MemoryUtil.memPutInt(uploadPtr, modelFlags);
int[] biomeData = null;
int biomeIndex = -1;
//Temporary override to always be non biome specific
if (colourProvider == null) {
MemoryUtil.memPutInt(uploadPtr + 4, -1);//Set the default to nothing so that its faster on the gpu
} else if (!hasBiomeColourResolver) {
Biome defaultBiome = MinecraftClient.getInstance().world.getRegistryManager().get(RegistryKeys.BIOME).get(BiomeKeys.PLAINS);
MemoryUtil.memPutInt(uploadPtr + 4, captureColourConstant(colourProvider, blockState, defaultBiome)|0xFF000000);
MemoryUtil.memPutInt(uploadPtr + 4, captureColourConstant(colourProvider, blockState, DEFAULT_BIOME)|0xFF000000);
} else if (!this.biomes.isEmpty()) {
//Populate the list of biomes for the model state
int biomeIndex = this.modelsRequiringBiomeColours.size() * this.biomes.size();
biomeIndex = this.modelsRequiringBiomeColours.size() * this.biomes.size();
MemoryUtil.memPutInt(uploadPtr + 4, biomeIndex);
this.modelsRequiringBiomeColours.add(new Pair<>(modelId, blockState));
long clrUploadPtr = UploadStream.INSTANCE.upload(this.modelColourBuffer, biomeIndex * 4L, 4L * this.biomes.size());
for (var biome : this.biomes) {
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, blockState, biome)|0xFF000000); clrUploadPtr += 4;
//long clrUploadPtr = UploadStream.INSTANCE.upload(this.modelColourBuffer, biomeIndex * 4L, 4L * this.biomes.size());
biomeData = new int[this.biomes.size()];
for (int biomeId = 0; biomeId < this.biomes.size(); biomeId++) {
biomeData[biomeId] = captureColourConstant(colourProvider, blockState, this.biomes.get(biomeId))|0xFF000000;
}
}
@@ -371,13 +384,21 @@ public class ModelManager {
//TODO
this.putTextures(modelId, textureData);
var textureUpload = this.putTextures(modelId, textureData);
//glGenerateTextureMipmap(this.textures.id);
return modelId;
//Set the mapping at the very end
this.idMappings[blockId] = modelId;
new NewModelBufferDelta(modelId, uploadPtrConst, biomeIndex, biomeData, textureUpload);
}
public void addBiome(int id, Biome biome) {
throw new IllegalStateException("IMPLEMENT");
/*
this.biomes.add(biome);
if (this.biomes.size()-1 != id) {
throw new IllegalStateException("Biome ordering not consistent with biome id for biome " + biome + " expected id: " + (this.biomes.size()-1) + " got id: " + id);
@@ -391,12 +412,13 @@ public class ModelManager {
}
//Populate the list of biomes for the model state
int biomeIndex = (i++) * this.biomes.size();
MemoryUtil.memPutInt( UploadStream.INSTANCE.upload(this.modelBuffer, (entry.getLeft()*MODEL_SIZE)+ 4*6 + 4, 4), biomeIndex);
MemoryUtil.memPutInt(UploadStream.INSTANCE.upload(this.modelBuffer, (entry.getLeft()* MODEL_SIZE)+ 4*6 + 4, 4), biomeIndex);
long clrUploadPtr = UploadStream.INSTANCE.upload(this.modelColourBuffer, biomeIndex * 4L, 4L * this.biomes.size());
for (var biomeE : this.biomes) {
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, entry.getRight(), biomeE)|0xFF000000); clrUploadPtr += 4;
}
}
*/
}
@@ -506,95 +528,22 @@ public class ModelManager {
return biomeDependent[0];
}
public static boolean faceExists(long metadata, int face) {
return ((metadata>>(8*face))&0xFF)!=0xFF;
}
public static boolean faceCanBeOccluded(long metadata, int face) {
return ((metadata>>(8*face))&0b100)==0b100;
}
public static boolean faceOccludes(long metadata, int face) {
return faceExists(metadata, face) && ((metadata>>(8*face))&0b1)==0b1;
}
public static boolean faceUsesSelfLighting(long metadata, int face) {
return ((metadata>>(8*face))&0b1000) != 0;
}
public static boolean isDoubleSided(long metadata) {
return ((metadata>>(8*6))&4) != 0;
}
public static boolean isTranslucent(long metadata) {
return ((metadata>>(8*6))&2) != 0;
}
public static boolean containsFluid(long metadata) {
return ((metadata>>(8*6))&8) != 0;
}
public static boolean isFluid(long metadata) {
return ((metadata>>(8*6))&16) != 0;
}
public static boolean isBiomeColoured(long metadata) {
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;
}
private float[] computeModelDepth(ColourDepthTextureData[] textures, int checkMode) {
float[] res = new float[6];
for (var dir : Direction.values()) {
var data = textures[dir.getId()];
float fd = TextureUtils.computeDepth(data, TextureUtils.DEPTH_MODE_AVG, checkMode);//Compute the min float depth, smaller means closer to the camera, range 0-1
int depth = Math.round(fd * this.modelTextureSize);
int depth = Math.round(fd * MODEL_TEXTURE_SIZE);
//If fd is -1, it means that there was nothing rendered on that face and it should be discarded
if (fd < -0.1) {
res[dir.ordinal()] = -1;
} else {
res[dir.ordinal()] = ((float) depth)/this.modelTextureSize;
res[dir.ordinal()] = ((float) depth)/MODEL_TEXTURE_SIZE;
}
}
return res;
}
public long getModelMetadata(int blockId) {
int map = this.idMappings[blockId];
if (map == -1) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
map = this.idMappings[blockId];
}
if (map == -1) {
throw new IdNotYetComputedException(blockId);
}
return this.metadataCache[map];
}
public long getModelMetadataFromClientId(int clientId) {
return this.metadataCache[clientId];
}
public int getModelId(int blockId) {
int map = this.idMappings[blockId];
if (map == -1) {
@@ -603,6 +552,10 @@ public class ModelManager {
return map;
}
public boolean hasModelForBlockId(int blockId) {
return this.idMappings[blockId] != -1;
}
public int getFluidClientStateId(int clientBlockStateId) {
int map = this.fluidStateLUT[clientBlockStateId];
if (map == -1) {
@@ -611,24 +564,24 @@ public class ModelManager {
return map;
}
private void putTextures(int id, ColourDepthTextureData[] textures) {
int X = (id&0xFF) * this.modelTextureSize*3;
int Y = ((id>>8)&0xFF) * this.modelTextureSize*2;
public long getModelMetadataFromClientId(int clientId) {
return this.metadataCache[clientId];
}
private ModelTextureUpload putTextures(int id, ColourDepthTextureData[] textures) {
int X = (id&0xFF) * MODEL_TEXTURE_SIZE*3;
int Y = ((id>>8)&0xFF) * MODEL_TEXTURE_SIZE*2;
int texIndex = 0;
int[][] texData = new int[6*4][];
for (int subTex = 0; subTex < 6; subTex++) {
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);
GlStateManager._pixelStore(GlConst.GL_UNPACK_SKIP_ROWS, 0);
GlStateManager._pixelStore(GlConst.GL_UNPACK_ALIGNMENT, 4);
var current = textures[subTex].colour();
var next = new int[current.length>>1];
for (int i = 0; i < 4; i++) {
glTextureSubImage2D(this.textures.id, i, x>>i, y>>i, this.modelTextureSize>>i, this.modelTextureSize>>i, GL_RGBA, GL_UNSIGNED_BYTE, current);
texData[texIndex++] = Arrays.copyOf(current, current.length);
int size = this.modelTextureSize>>(i+1);
int size = MODEL_TEXTURE_SIZE>>(i+1);
for (int pX = 0; pX < size; pX++) {
for (int pY = 0; pY < size; pY++) {
int C00 = current[(pY*2)*size+pX*2];
@@ -643,29 +596,15 @@ public class ModelManager {
next = new int[current.length>>1];
}
}
}
public int getBufferId() {
return this.modelBuffer.id;
}
public int getTextureId() {
return this.textures.id;
return new ModelTextureUpload(id, texData);
}
public int getSamplerId() {
return this.blockSampler;
}
public int getColourBufferId() {
return this.modelColourBuffer.id;
}
public void free() {
this.bakery.free();
this.modelBuffer.free();
this.modelColourBuffer.free();
this.textures.free();
glDeleteSamplers(this.blockSampler);
}

View File

@@ -0,0 +1,44 @@
package me.cortex.voxy.client.core.model;
public abstract class ModelQueries {
public static boolean faceExists(long metadata, int face) {
return ((metadata>>(8*face))&0xFF)!=0xFF;
}
public static boolean faceCanBeOccluded(long metadata, int face) {
return ((metadata>>(8*face))&0b100)==0b100;
}
public static boolean faceOccludes(long metadata, int face) {
return faceExists(metadata, face) && ((metadata>>(8*face))&0b1)==0b1;
}
public static boolean faceUsesSelfLighting(long metadata, int face) {
return ((metadata>>(8*face))&0b1000) != 0;
}
public static boolean isDoubleSided(long metadata) {
return ((metadata>>(8*6))&4) != 0;
}
public static boolean isTranslucent(long metadata) {
return ((metadata>>(8*6))&2) != 0;
}
public static boolean containsFluid(long metadata) {
return ((metadata>>(8*6))&8) != 0;
}
public static boolean isFluid(long metadata) {
return ((metadata>>(8*6))&16) != 0;
}
public static boolean isBiomeColoured(long metadata) {
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;
}
}

View File

@@ -0,0 +1,26 @@
package me.cortex.voxy.client.core.model;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import static org.lwjgl.opengl.GL11.GL_RGBA8;
public class ModelStore {
public static final int MODEL_SIZE = 64;
private final GlBuffer modelBuffer;
private final GlBuffer modelColourBuffer;
private final GlTexture textures;
public ModelStore(int modelTextureSize) {
this.modelBuffer = new GlBuffer(MODEL_SIZE * (1<<16));
this.modelColourBuffer = new GlBuffer(4 * (1<<16));
this.textures = new GlTexture().store(GL_RGBA8, 4, modelTextureSize*3*256,modelTextureSize*2*256);
}
public void free() {
this.modelBuffer.free();
this.modelColourBuffer.free();
this.textures.free();
}
}

View File

@@ -0,0 +1,30 @@
package me.cortex.voxy.client.core.model;
import com.mojang.blaze3d.platform.GlConst;
import com.mojang.blaze3d.platform.GlStateManager;
import static me.cortex.voxy.client.core.model.ModelFactory.MODEL_TEXTURE_SIZE;
import static org.lwjgl.opengl.ARBDirectStateAccess.glTextureSubImage2D;
import static org.lwjgl.opengl.GL11.GL_RGBA;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
public record ModelTextureUpload(int id, int[][] data) {
public void upload(int textureId) {
GlStateManager._pixelStore(GlConst.GL_UNPACK_ROW_LENGTH, 0);
GlStateManager._pixelStore(GlConst.GL_UNPACK_SKIP_PIXELS, 0);
GlStateManager._pixelStore(GlConst.GL_UNPACK_SKIP_ROWS, 0);
GlStateManager._pixelStore(GlConst.GL_UNPACK_ALIGNMENT, 4);
int i = 0;
int X = (this.id&0xFF) * MODEL_TEXTURE_SIZE*3;
int Y = ((this.id>>8)&0xFF) * MODEL_TEXTURE_SIZE*2;
for (int face = 0; face < 6; face++) {
int x = X + (face>>1)*MODEL_TEXTURE_SIZE;
int y = Y + (face&1)*MODEL_TEXTURE_SIZE;
for (int mip = 0; mip < 4; mip++) {
glTextureSubImage2D(id, mip, x >> mip, y >> mip, MODEL_TEXTURE_SIZE >> mip, MODEL_TEXTURE_SIZE >> mip, GL_RGBA, GL_UNSIGNED_BYTE, this.data[i++]);
}
}
}
}

View File

@@ -0,0 +1,17 @@
package me.cortex.voxy.client.core.model;
import org.lwjgl.system.MemoryUtil;
public record NewModelBufferDelta(int modelClientId, long modelBufferChangesPtr, int biomeIndex, int[] biomeData, ModelTextureUpload textureUpload) {
public static NewModelBufferDelta empty(int modelClientId) {
return new NewModelBufferDelta(modelClientId, 0, -1, null, null);
}
public boolean isEmpty() {
return this.modelBufferChangesPtr == 0;
}
public void free() {
MemoryUtil.nmemFree(this.modelBufferChangesPtr);
}
}

View File

@@ -1,7 +0,0 @@
package me.cortex.voxy.client.core.model;
//Uses an off thread gl context to do the model baking on
public class OffThreadBakerySystem {
}

View File

@@ -0,0 +1,93 @@
package me.cortex.voxy.client.core.model;
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
import me.cortex.voxy.common.world.other.Mapper;
import net.minecraft.client.MinecraftClient;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL;
import java.lang.invoke.VarHandle;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.Semaphore;
//Uses an off thread gl context to do the model baking on
public class OffThreadModelBakerySystem {
//NOTE: Create a static final context offthread and dont close it, just reuse the context, since context creation is expensive
private static final long GL_CTX;
static {
GLFW.glfwMakeContextCurrent(0L);
GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, 0);
GL_CTX = GLFW.glfwCreateWindow(1, 1, "", 0, MinecraftClient.getInstance().getWindow().getHandle());
GLFW.glfwMakeContextCurrent(GL_CTX);
GL.createCapabilities();
GLFW.glfwMakeContextCurrent(MinecraftClient.getInstance().getWindow().getHandle());
}
private final ModelStore storage = new ModelStore(16);
public final ModelFactory factory;
private final ConcurrentLinkedDeque<NewModelBufferDelta> bufferDeltas = new ConcurrentLinkedDeque<>();
private final IntArrayFIFOQueue blockQueue = new IntArrayFIFOQueue();
private final Semaphore queueCounter = new Semaphore(0);
private final Thread bakingThread;
private volatile boolean running = true;
public OffThreadModelBakerySystem(Mapper mapper) {
this.factory = new ModelFactory(mapper);
this.bakingThread = new Thread(this::bakeryThread);
this.bakingThread.setName("Baking thread");
this.bakingThread.setPriority(3);
this.bakingThread.start();
}
private void bakeryThread() {
GLFW.glfwMakeContextCurrent(GL_CTX);
//FIXME: tile entities will probably need to be baked on the main render thread
while (true) {
this.queueCounter.acquireUninterruptibly();
if (!this.running) break;
int blockId;
synchronized (this.blockQueue) {
blockId = this.blockQueue.dequeueInt();
VarHandle.fullFence();//Ensure memory coherancy
}
this.factory.addEntry(blockId);
}
}
//Changes to the block table/atlas must be run on the main render thread
public void syncChanges() {
//TODO: FIXME!! the ordering of the uploads here and the order that _both_ model AND biome are done in the bakery thread MUST be the same!!!
}
public void shutdown() {
this.running = false;
this.queueCounter.release(999);
try {
this.bakingThread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.factory.free();
this.storage.free();
}
public void requestBlockBake(int blockId) {
synchronized (this.blockQueue) {
this.blockQueue.enqueue(blockId);
VarHandle.fullFence();//Ensure memory coherancy
this.queueCounter.release(1);
}
}
public void addDebugData(List<String> debug) {
}
}

View File

@@ -1,19 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.gl.GlBuffer;
import static org.lwjgl.opengl.GL30C.GL_R8UI;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL42.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL45C.nglClearNamedBufferData;
public class Gl46MeshletViewport extends Viewport<Gl46MeshletViewport> {
GlBuffer visibilityBuffer;
public Gl46MeshletViewport(Gl46MeshletsFarWorldRenderer renderer) {
this.visibilityBuffer = new GlBuffer(renderer.maxSections*4L).zero();
}
protected void delete0() {
this.visibilityBuffer.free();
}
}

View File

@@ -1,242 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.PrintfInjector;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.model.ModelManager;
import me.cortex.voxy.client.core.rendering.building.RenderDataFactory;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.mixin.joml.AccessFrustumIntersection;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.RenderLayer;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.system.MemoryUtil;
import static org.lwjgl.opengl.ARBDirectStateAccess.glGetNamedFramebufferAttachmentParameteriv;
import static org.lwjgl.opengl.ARBDirectStateAccess.glTextureParameteri;
import static org.lwjgl.opengl.ARBIndirectParameters.GL_PARAMETER_BUFFER_ARB;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL30.glBindBufferBase;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
import static org.lwjgl.opengl.GL31.glDrawElementsInstanced;
import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL40.glDrawElementsIndirect;
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
import static org.lwjgl.opengl.GL42.GL_FRAMEBUFFER_BARRIER_BIT;
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
import static org.lwjgl.opengl.GL43.*;
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
import static org.lwjgl.opengl.GL45.nglClearNamedBufferSubData;
import static org.lwjgl.opengl.GL45C.nglClearNamedBufferData;
import static org.lwjgl.opengl.NVMeshShader.glDrawMeshTasksNV;
import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV;
//TODO: NOTE
// can do "meshlet compaction"
// that is, meshlets are refered not by the meshlet id but by an 8 byte alligned index, (the quad index)
// this means that non full meshlets can have the tailing data be truncated and used in the next meshlet
// as long as the number of quads in the meshlet is stored in the header
// the shader can cull the verticies of any quad that has its index over the expected quuad count
// this could potentially result in a fair bit of memory savings (especially if used in normal mc terrain rendering)
public class Gl46MeshletsFarWorldRenderer extends AbstractFarWorldRenderer<Gl46MeshletViewport, DefaultGeometryManager> {
private final Shader lodShader = Shader.make()
.define("QUADS_PER_MESHLET", RenderDataFactory.QUADS_PER_MESHLET)
.add(ShaderType.VERTEX, "voxy:lod/gl46mesh/quads.vert")
.add(ShaderType.FRAGMENT, "voxy:lod/gl46mesh/quads.frag")
.compile();
private final Shader cullShader = Shader.make()
.define("QUADS_PER_MESHLET", RenderDataFactory.QUADS_PER_MESHLET)
.add(ShaderType.VERTEX, "voxy:lod/gl46mesh/cull.vert")
.add(ShaderType.FRAGMENT, "voxy:lod/gl46mesh/cull.frag")
.compile();
private final Shader meshletGenerator = Shader.make()
.define("QUADS_PER_MESHLET", RenderDataFactory.QUADS_PER_MESHLET)
.add(ShaderType.COMPUTE, "voxy:lod/gl46mesh/cmdgen.comp")
.compile();
private final Shader meshletCuller = Shader.make()
.define("QUADS_PER_MESHLET", RenderDataFactory.QUADS_PER_MESHLET)
.add(ShaderType.COMPUTE, "voxy:lod/gl46mesh/meshletculler.comp")
.compile();
private final GlBuffer glDrawIndirect;
private final GlBuffer meshletBuffer;
private final HiZBuffer hiZBuffer = new HiZBuffer();
private final int hizSampler = glGenSamplers();
public Gl46MeshletsFarWorldRenderer(ModelManager modelManager, int geometrySize, int maxSections) {
super(modelManager, new DefaultGeometryManager(alignUp(geometrySize*8L, 8* (RenderDataFactory.QUADS_PER_MESHLET+2)), maxSections, 8*(RenderDataFactory.QUADS_PER_MESHLET+2)));
this.glDrawIndirect = new GlBuffer(4*(4+5)).zero();
this.meshletBuffer = new GlBuffer(4*1000000).zero();//TODO: Make max meshlet count configurable, not just 1 million (even tho thats a max of 126 million quads per frame)
glSamplerParameteri(this.hizSampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);//This is so that using the shadow sampler works correctly
glTextureParameteri(this.hizSampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameteri(this.hizSampler, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glTextureParameteri(this.hizSampler, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
glTextureParameteri(this.hizSampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTextureParameteri(this.hizSampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTextureParameteri(this.hizSampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
/*
nglClearNamedBufferData(this.meshletBuffer.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
nglClearNamedBufferData(this.glDrawIndirect.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
*/
}
protected void bindResources(Gl46MeshletViewport viewport, boolean bindToDrawIndirect, boolean bindToDispatchIndirect, boolean bindHiz) {
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometry.geometryId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, bindToDrawIndirect?0:this.glDrawIndirect.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.meshletBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.geometry.metaId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, viewport.visibilityBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.models.getBufferId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, this.models.getColourBufferId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 8, this.lightDataBuffer.id);//Lighting LUT
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, bindToDrawIndirect?this.glDrawIndirect.id:0);
glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, bindToDispatchIndirect?this.glDrawIndirect.id:0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE_BYTE.id());
if (!bindHiz) {
//Bind the texture atlas
glBindSampler(0, this.models.getSamplerId());
glBindTextureUnit(0, this.models.getTextureId());
} else {
//Bind the hiz buffer
glBindSampler(0, this.hizSampler);
glBindTextureUnit(0, this.hiZBuffer.getHizTextureId());
}
}
private void updateUniformBuffer(Gl46MeshletViewport viewport) {
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, this.uniformBuffer.size());
//TODO:FIXME remove this.sx/sy/sz and make it based on the viewport!!!
var mat = new Matrix4f(viewport.projection).mul(viewport.modelView);
var innerTranslation = new Vector3f((float) (viewport.cameraX-(this.sx<<5)), (float) (viewport.cameraY-(this.sy<<5)), (float) (viewport.cameraZ-(this.sz<<5)));
mat.translate(-innerTranslation.x, -innerTranslation.y, -innerTranslation.z);
mat.getToAddress(ptr); ptr += 4*4*4;
MemoryUtil.memPutInt(ptr, this.sx); ptr += 4;
MemoryUtil.memPutInt(ptr, this.sy); ptr += 4;
MemoryUtil.memPutInt(ptr, this.sz); ptr += 4;
MemoryUtil.memPutInt(ptr, this.geometry.getSectionCount()); ptr += 4;
var planes = ((AccessFrustumIntersection)this.frustum).getPlanes();
for (var plane : planes) {
plane.getToAddress(ptr); ptr += 4*4;
}
innerTranslation.getToAddress(ptr); ptr += 4*3;
MemoryUtil.memPutInt(ptr, viewport.frameId++); ptr += 4;
MemoryUtil.memPutInt(ptr, viewport.width); ptr += 4;
MemoryUtil.memPutInt(ptr, viewport.height); ptr += 4;
}
@Override
public void renderFarAwayOpaque(Gl46MeshletViewport viewport) {
if (this.geometry.getSectionCount() == 0) {
return;
}
{//Mark all of the updated sections as being visible from last frame
for (int id : this.updatedSectionIds) {
long ptr = UploadStream.INSTANCE.upload(viewport.visibilityBuffer, id * 4L, 4);
MemoryUtil.memPutInt(ptr, viewport.frameId - 1);//(visible from last frame)
}
}
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
RenderLayer.getCutoutMipped().startDrawing();
this.updateUniformBuffer(viewport);
UploadStream.INSTANCE.commit();
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
nglClearNamedBufferSubData(this.glDrawIndirect.id, GL_R32UI, 0, 4*4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0);
this.lodShader.bind();
this.bindResources(viewport, true, false, false);
glDisable(GL_CULL_FACE);
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_BYTE, 4*4);
glEnable(GL_CULL_FACE);
glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
this.cullShader.bind();
this.bindResources(viewport, false, false, false);
glDepthMask(false);
glColorMask(false, false, false, false);
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3, GL_UNSIGNED_BYTE, (1 << 8) * 6, this.geometry.getSectionCount());
glColorMask(true, true, true, true);
glDepthMask(true);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
this.meshletGenerator.bind();
this.bindResources(viewport, false, false, false);
glDispatchCompute((this.geometry.getSectionCount()+63)/64, 1, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT);
var i = new int[1];
glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, i);
this.hiZBuffer.buildMipChain(i[0], MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight);
this.meshletCuller.bind();
this.bindResources(viewport, false, true, true);
glDispatchComputeIndirect(0);
glBindVertexArray(0);
glBindSampler(0, 0);
glBindTextureUnit(0, 0);
RenderLayer.getCutoutMipped().endDrawing();
}
@Override
public void renderFarAwayTranslucent(Gl46MeshletViewport viewport) {
}
@Override
protected Gl46MeshletViewport createViewport0() {
return new Gl46MeshletViewport(this);
}
@Override
public void shutdown() {
super.shutdown();
this.lodShader.free();
this.cullShader.free();
this.meshletGenerator.free();
this.meshletCuller.free();
this.glDrawIndirect.free();
this.meshletBuffer.free();
this.hiZBuffer.free();
glDeleteSamplers(this.hizSampler);
}
public static long alignUp(long n, long alignment) {
return (n + alignment - 1) & -alignment;
}
@Override
public boolean generateMeshlets() {
return true;
}
}

View File

@@ -4,10 +4,10 @@ import me.cortex.voxy.client.core.gl.GlFramebuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.geometry.OLD.AbstractFarWorldRenderer;
import org.lwjgl.opengl.GL11;
import static org.lwjgl.opengl.ARBDirectStateAccess.*;
import static org.lwjgl.opengl.GL11.glScaled;
import static org.lwjgl.opengl.GL11C.*;
import static org.lwjgl.opengl.GL30C.*;
import static org.lwjgl.opengl.GL33.glBindSampler;
@@ -17,7 +17,6 @@ import static org.lwjgl.opengl.GL33C.glSamplerParameteri;
import static org.lwjgl.opengl.GL42C.GL_FRAMEBUFFER_BARRIER_BIT;
import static org.lwjgl.opengl.GL42C.glMemoryBarrier;
import static org.lwjgl.opengl.GL43C.glCopyImageSubData;
import static org.lwjgl.opengl.GL45C.glNamedFramebufferTexture;
import static org.lwjgl.opengl.GL45C.glTextureBarrier;
public class HiZBuffer {

View File

@@ -1,32 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.rendering.hierarchical.HierarchicalOcclusionRenderer;
import me.cortex.voxy.common.world.other.Mapper;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.Frustum;
import java.util.List;
import static org.lwjgl.opengl.GL30.*;
public interface IRenderInterface <T extends Viewport> {
T createViewport();
void setupRender(Frustum frustum, Camera camera);
void renderFarAwayOpaque(T viewport);
void renderFarAwayTranslucent(T viewport);
void addDebugData(List<String> debug);
void shutdown();
void addBlockState(Mapper.StateEntry stateEntry);
void addBiome(Mapper.BiomeEntry biomeEntry);
boolean generateMeshlets();
}

View File

@@ -1,189 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.model.ModelManager;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.mixin.joml.AccessFrustumIntersection;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.Frustum;
import net.minecraft.client.render.RenderLayer;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL11C;
import org.lwjgl.system.MemoryUtil;
import java.util.List;
import static org.lwjgl.opengl.ARBIndirectParameters.GL_PARAMETER_BUFFER_ARB;
import static org.lwjgl.opengl.ARBIndirectParameters.glMultiDrawElementsIndirectCountARB;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL30.glBindBufferBase;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
import static org.lwjgl.opengl.GL31.glDrawElementsInstanced;
import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
import static org.lwjgl.opengl.GL42.GL_FRAMEBUFFER_BARRIER_BIT;
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BARRIER_BIT;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
import static org.lwjgl.opengl.NVMeshShader.glDrawMeshTasksNV;
import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV;
public class NvMeshFarWorldRenderer extends AbstractFarWorldRenderer<NvMeshViewport, DefaultGeometryManager> {
private final Shader terrain = Shader.make()
.add(ShaderType.TASK, "voxy:lod/nvmesh/primary.task")
.add(ShaderType.MESH, "voxy:lod/nvmesh/primary.mesh")
.add(ShaderType.FRAGMENT, "voxy:lod/nvmesh/primary.frag")
.compile();
private final Shader translucent = Shader.make()
.add(ShaderType.TASK, "voxy:lod/nvmesh/translucent.task")
.add(ShaderType.MESH, "voxy:lod/nvmesh/translucent.mesh")
.add(ShaderType.FRAGMENT, "voxy:lod/nvmesh/primary.frag")
.compile();
private final Shader cull = Shader.make()
.add(ShaderType.VERTEX, "voxy:lod/nvmesh/cull.vert")
.add(ShaderType.FRAGMENT, "voxy:lod/nvmesh/cull.frag")
.compile();
public NvMeshFarWorldRenderer(ModelManager modelManager, int geometrySize, int maxSections) {
super(modelManager, new DefaultGeometryManager(geometrySize*8L, maxSections));
}
private void updateUniform(NvMeshViewport viewport) {
long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, this.uniformBuffer.size());
var mat = new Matrix4f(viewport.projection).mul(viewport.modelView);
mat.getToAddress(ptr); ptr += 4*4*4;
var innerTranslation = new Vector3f((float) (viewport.cameraX-(this.sx<<5)), (float) (viewport.cameraY-(this.sy<<5)), (float) (viewport.cameraZ-(this.sz<<5)));
MemoryUtil.memPutInt(ptr, this.sx); ptr += 4;
MemoryUtil.memPutInt(ptr, this.sy); ptr += 4;
MemoryUtil.memPutInt(ptr, this.sz); ptr += 4;
MemoryUtil.memPutInt(ptr, this.geometry.getSectionCount()); ptr += 4;
innerTranslation.getToAddress(ptr); ptr += 4*3;
MemoryUtil.memPutInt(ptr, viewport.frameId++); ptr += 4;
}
private void bindResources(NvMeshViewport viewport) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id());
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometry.geometryId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.geometry.metaId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, viewport.visibilityBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.models.getBufferId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, this.models.getColourBufferId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.lightDataBuffer.id);//Lighting LUT
//Bind the texture atlas
glBindSampler(0, this.models.getSamplerId());
glBindTextureUnit(0, this.models.getTextureId());
}
@Override
public void renderFarAwayOpaque(NvMeshViewport viewport) {
{//TODO: move all this code into a common super method renderFarAwayTranslucent and make the current method renderFarAwayTranslucent0
if (this.geometry.getSectionCount() == 0) {
return;
}
{//Mark all of the updated sections as being visible from last frame
for (int id : this.updatedSectionIds) {
long ptr = UploadStream.INSTANCE.upload(viewport.visibilityBuffer, id * 4L, 4);
MemoryUtil.memPutInt(ptr, viewport.frameId - 1);//(visible from last frame)
}
}
}
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
//Update and upload data
this.updateUniform(viewport);
UploadStream.INSTANCE.commit();
this.terrain.bind();
RenderLayer.getCutoutMipped().startDrawing();
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
this.bindResources(viewport);
glDisable(GL_CULL_FACE);
glDrawMeshTasksNV(0, this.geometry.getSectionCount());
glEnable(GL_CULL_FACE);
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);
this.cull.bind();
this.bindResources(viewport);
glColorMask(false, false, false, false);
glDepthMask(false);
glEnable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3, GL_UNSIGNED_BYTE, (1 << 16) * 6 * 2, this.geometry.getSectionCount());
glDisable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
glDepthMask(true);
glColorMask(true, true, true, true);
glBindVertexArray(0);
glBindSampler(0, 0);
glBindTextureUnit(0, 0);
RenderLayer.getCutoutMipped().endDrawing();
}
@Override
public void renderFarAwayTranslucent(NvMeshViewport viewport) {
if (this.geometry.getSectionCount()==0) {
return;
}
RenderLayer.getTranslucent().startDrawing();
glBindVertexArray(AbstractFarWorldRenderer.STATIC_VAO);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
//TODO: maybe change this so the alpha isnt applied in the same way or something?? since atm the texture bakery uses a very hacky
// blend equation to make it avoid double applying translucency
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBindSampler(0, this.models.getSamplerId());
glBindTextureUnit(0, this.models.getTextureId());
this.translucent.bind();
this.bindResources(viewport);
glDrawMeshTasksNV(0, this.geometry.getSectionCount());
glEnable(GL_CULL_FACE);
glBindVertexArray(0);
glBindSampler(0, 0);
glBindTextureUnit(0, 0);
glDisable(GL_BLEND);
RenderLayer.getTranslucent().endDrawing();
}
@Override
protected NvMeshViewport createViewport0() {
return new NvMeshViewport(this);
}
@Override
public void shutdown() {
super.shutdown();
this.terrain.free();
this.translucent.free();
this.cull.free();
}
}

View File

@@ -1,19 +0,0 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.gl.GlBuffer;
import static org.lwjgl.opengl.GL30C.GL_R8UI;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL42.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL45C.glClearNamedBufferData;
public class NvMeshViewport extends Viewport<NvMeshViewport> {
GlBuffer visibilityBuffer;
public NvMeshViewport(NvMeshFarWorldRenderer renderer) {
this.visibilityBuffer = new GlBuffer(renderer.maxSections*4L).zero();
}
protected void delete0() {
this.visibilityBuffer.free();
}
}

View File

@@ -0,0 +1,56 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.model.OffThreadModelBakerySystem;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.common.world.WorldEngine;
import net.minecraft.client.render.Camera;
import java.util.List;
public class RenderService {
private final OffThreadModelBakerySystem modelService;
private final RenderGenerationService renderGen;
public RenderService(WorldEngine world) {
this.modelService = new OffThreadModelBakerySystem(world.getMapper());
this.renderGen = new RenderGenerationService(world, this.modelService, VoxyConfig.CONFIG.renderThreads, this::consumeRenderBuildResult, false);
this.renderGen.enqueueTask(0,0,0,0);
}
private void consumeRenderBuildResult(BuiltSection section) {
System.err.println("Section " + WorldEngine.pprintPos(section.position));
section.free();
}
public void setup(Camera camera) {
this.modelService.syncChanges();
}
public void renderFarAwayOpaque(Viewport viewport) {
}
public void renderFarAwayTranslucent(Viewport viewport) {
}
public void addDebugData(List<String> debug) {
this.modelService.addDebugData(debug);
this.renderGen.addDebugData(debug);
}
public Viewport<?> createViewport() {
return new RenderServiceViewport();
}
public void shutdown() {
this.modelService.shutdown();
this.renderGen.shutdown();
}
}

View File

@@ -0,0 +1,10 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.gl.GlBuffer;
public class RenderServiceViewport extends Viewport<RenderServiceViewport> {
@Override
protected void delete0() {
}
}

View File

@@ -4,12 +4,9 @@ import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.client.core.rendering.geometry.OLD.AbstractFarWorldRenderer;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.WorldSection;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.math.Direction;
import java.util.concurrent.ConcurrentHashMap;
//Tracks active sections, dispatches updates to the build system, everything related to rendering flows through here
public class RenderTracker {

View File

@@ -5,7 +5,7 @@ import org.joml.Matrix4f;
public abstract class Viewport <A extends Viewport<A>> {
public int width;
public int height;
int frameId;
public int frameId;
public Matrix4f projection;
public Matrix4f modelView;
public double cameraX;

View File

@@ -2,22 +2,21 @@ package me.cortex.voxy.client.core.rendering.building;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import me.cortex.voxy.client.core.Capabilities;
import me.cortex.voxy.client.core.model.ModelManager;
import me.cortex.voxy.client.core.model.ModelFactory;
import me.cortex.voxy.client.core.model.ModelQueries;
import me.cortex.voxy.client.core.util.Mesher2D;
import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.WorldSection;
import me.cortex.voxy.common.world.other.Mapper;
import net.minecraft.block.FluidBlock;
import org.lwjgl.system.MemoryUtil;
import java.lang.reflect.Array;
import java.util.Arrays;
public class RenderDataFactory {
private final WorldEngine world;
private final ModelManager modelMan;
private final ModelFactory modelMan;
private final Mesher2D negativeMesher = new Mesher2D(5, 15);
private final Mesher2D positiveMesher = new Mesher2D(5, 15);
@@ -39,7 +38,7 @@ public class RenderDataFactory {
private int maxX;
private int maxY;
private int maxZ;
public RenderDataFactory(WorldEngine world, ModelManager modelManager, boolean emitMeshlets) {
public RenderDataFactory(WorldEngine world, ModelFactory modelManager, boolean emitMeshlets) {
this.world = world;
this.modelMan = modelManager;
this.generateMeshlets = emitMeshlets;
@@ -333,14 +332,13 @@ public class RenderDataFactory {
if (Mapper.isAir(self)) continue;
int selfBlockId = Mapper.getBlockId(self);
long selfMetadata = this.modelMan.getModelMetadata(selfBlockId);
int selfClientModelId = this.modelMan.getModelId(selfBlockId);
long selfMetadata = this.modelMan.getModelMetadataFromClientId(selfClientModelId);
boolean putFace = false;
//Branch into 2 paths, the + direction and -direction, doing it at once makes it much faster as it halves the number of loops
if (ModelManager.faceExists(selfMetadata, axisId<<1) || ModelManager.containsFluid(selfMetadata)) {//- direction
if (ModelQueries.faceExists(selfMetadata, axisId<<1) || ModelQueries.containsFluid(selfMetadata)) {//- direction
long facingState = Mapper.AIR;
//Need to access the other connecting section
if (primary == 0) {
@@ -359,14 +357,16 @@ public class RenderDataFactory {
facingState = this.sectionCache[WorldSection.getIndex(x-aX, y-aY, z-aZ)];
}
if (!ModelManager.isFluid(selfMetadata)) {
putFace |= this.putFaceIfCan(this.negativeMesher, (axisId << 1), (axisId << 1)|1, self, selfMetadata, selfBlockId, facingState, a, b);
int facingClientModelId = this.modelMan.getModelId(Mapper.getBlockId(facingState));
long facingMetadata = this.modelMan.getModelMetadataFromClientId(facingClientModelId);
if (!ModelQueries.isFluid(selfMetadata)) {
putFace |= this.putFaceIfCan(this.negativeMesher, (axisId << 1), (axisId << 1)|1, self, selfMetadata, selfClientModelId, selfBlockId, facingState, facingMetadata, a, b);
}
if (ModelManager.containsFluid(selfMetadata)) {
putFace |= this.putFluidFaceIfCan(this.negativeFluidMesher, (axisId << 1), (axisId << 1)|1, self, selfMetadata, selfBlockId, facingState, a, b);
if (ModelQueries.containsFluid(selfMetadata)) {
putFace |= this.putFluidFaceIfCan(this.negativeFluidMesher, (axisId << 1), (axisId << 1)|1, self, selfMetadata, selfClientModelId, selfBlockId, facingState, facingMetadata, facingClientModelId, a, b);
}
}
if (ModelManager.faceExists(selfMetadata, (axisId<<1)|1) || ModelManager.containsFluid(selfMetadata)) {//+ direction
if (ModelQueries.faceExists(selfMetadata, (axisId<<1)|1) || ModelQueries.containsFluid(selfMetadata)) {//+ direction
long facingState = Mapper.AIR;
//Need to access the other connecting section
if (primary == 31) {
@@ -384,11 +384,14 @@ public class RenderDataFactory {
} else {
facingState = this.sectionCache[WorldSection.getIndex(x+aX, y+aY, z+aZ)];
}
if (!ModelManager.isFluid(selfMetadata)) {
putFace |= this.putFaceIfCan(this.positiveMesher, (axisId << 1) | 1, (axisId << 1), self, selfMetadata, selfBlockId, facingState, a, b);
int facingClientModelId = this.modelMan.getModelId(Mapper.getBlockId(facingState));
long facingMetadata = this.modelMan.getModelMetadataFromClientId(facingClientModelId);
if (!ModelQueries.isFluid(selfMetadata)) {
putFace |= this.putFaceIfCan(this.positiveMesher, (axisId << 1) | 1, (axisId << 1), self, selfMetadata, selfClientModelId, selfBlockId, facingState, facingMetadata, a, b);
}
if (ModelManager.containsFluid(selfMetadata)) {
putFace |= this.putFluidFaceIfCan(this.positiveFluidMesher, (axisId << 1) | 1, (axisId << 1), self, selfMetadata, selfBlockId, facingState, a, b);
if (ModelQueries.containsFluid(selfMetadata)) {
putFace |= this.putFluidFaceIfCan(this.positiveFluidMesher, (axisId << 1) | 1, (axisId << 1), self, selfMetadata, selfClientModelId, selfBlockId, facingState, facingMetadata, facingClientModelId, a, b);
}
}
@@ -414,15 +417,13 @@ public class RenderDataFactory {
//Returns true if a face was placed
private boolean putFluidFaceIfCan(Mesher2D mesher, int face, int opposingFace, long self, long metadata, int selfBlockId, long facingState, int a, int b) {
long facingMetadata = this.modelMan.getModelMetadata(Mapper.getBlockId(facingState));
int selfFluidClientId = this.modelMan.getFluidClientStateId(this.modelMan.getModelId(selfBlockId));
private boolean putFluidFaceIfCan(Mesher2D mesher, int face, int opposingFace, long self, long metadata, int selfClientModelId, int selfBlockId, long facingState, long facingMetadata, int facingClientModelId, int a, int b) {
int selfFluidClientId = this.modelMan.getFluidClientStateId(selfClientModelId);
long selfFluidMetadata = this.modelMan.getModelMetadataFromClientId(selfFluidClientId);
int facingFluidClientId = -1;
if (ModelManager.containsFluid(facingMetadata)) {
facingFluidClientId = this.modelMan.getFluidClientStateId(this.modelMan.getModelId(Mapper.getBlockId(facingState)));
if (ModelQueries.containsFluid(facingMetadata)) {
facingFluidClientId = this.modelMan.getFluidClientStateId(facingClientModelId);
}
//If both of the states are the same, then dont render the fluid face
@@ -431,18 +432,19 @@ public class RenderDataFactory {
}
if (facingFluidClientId != -1) {
//TODO: OPTIMIZE
if (this.world.getMapper().getBlockStateFromBlockId(selfBlockId).getBlock() == this.world.getMapper().getBlockStateFromBlockId(Mapper.getBlockId(facingState)).getBlock()) {
return false;
}
}
if (ModelManager.faceOccludes(facingMetadata, opposingFace)) {
if (ModelQueries.faceOccludes(facingMetadata, opposingFace)) {
return false;
}
//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.isFluid(metadata)) && ModelManager.faceOccludes(metadata, face)) {
if ((!ModelQueries.isFluid(metadata)) && ModelQueries.faceOccludes(metadata, face)) {
return false;
}
@@ -458,32 +460,28 @@ public class RenderDataFactory {
long otherFlags = 0;
otherFlags |= ModelManager.isTranslucent(selfFluidMetadata)?1L<<33:0;
otherFlags |= ModelManager.isDoubleSided(selfFluidMetadata)?1L<<34:0;
mesher.put(a, b, ((long)selfFluidClientId) | (((long) Mapper.getLightId(ModelManager.faceUsesSelfLighting(selfFluidMetadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelManager.isBiomeColoured(selfFluidMetadata)?1:0)) | otherFlags);
otherFlags |= ModelQueries.isTranslucent(selfFluidMetadata)?1L<<33:0;
otherFlags |= ModelQueries.isDoubleSided(selfFluidMetadata)?1L<<34:0;
mesher.put(a, b, ((long)selfFluidClientId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(selfFluidMetadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelQueries.isBiomeColoured(selfFluidMetadata)?1:0)) | otherFlags);
return true;
}
//Returns true if a face was placed
private boolean putFaceIfCan(Mesher2D mesher, int face, int opposingFace, long self, long metadata, int selfBlockId, long facingState, int a, int b) {
long facingMetadata = this.modelMan.getModelMetadata(Mapper.getBlockId(facingState));
private boolean putFaceIfCan(Mesher2D mesher, int face, int opposingFace, long self, long metadata, int clientModelId, int selfBlockId, long facingState, long facingMetadata, int a, int b) {
//If face can be occluded and is occluded from the facing block, then dont render the face
if (ModelManager.faceCanBeOccluded(metadata, face) && ModelManager.faceOccludes(facingMetadata, opposingFace)) {
if (ModelQueries.faceCanBeOccluded(metadata, face) && ModelQueries.faceOccludes(facingMetadata, opposingFace)) {
return false;
}
if (ModelManager.cullsSame(metadata) && selfBlockId == Mapper.getBlockId(facingState)) {
if (ModelQueries.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;
}
int clientModelId = this.modelMan.getModelId(selfBlockId);
long otherFlags = 0;
otherFlags |= ModelManager.isTranslucent(metadata)?1L<<33:0;
otherFlags |= ModelManager.isDoubleSided(metadata)?1L<<34:0;
mesher.put(a, b, ((long)clientModelId) | (((long) Mapper.getLightId(ModelManager.faceUsesSelfLighting(metadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelManager.isBiomeColoured(metadata)?1:0)) | otherFlags);
otherFlags |= ModelQueries.isTranslucent(metadata)?1L<<33:0;
otherFlags |= ModelQueries.isDoubleSided(metadata)?1L<<34:0;
mesher.put(a, b, ((long)clientModelId) | (((long) Mapper.getLightId(ModelQueries.faceUsesSelfLighting(metadata, face)?self:facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelQueries.isBiomeColoured(metadata)?1:0)) | otherFlags);
return true;
}

View File

@@ -1,23 +1,26 @@
package me.cortex.voxy.client.core.rendering.building;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import me.cortex.voxy.client.core.model.IdNotYetComputedException;
import me.cortex.voxy.client.core.model.ModelManager;
import me.cortex.voxy.client.core.model.ModelFactory;
import me.cortex.voxy.client.core.model.OffThreadModelBakerySystem;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.WorldSection;
import me.cortex.voxy.common.world.other.Mapper;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
import java.util.concurrent.ConcurrentHashMap;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.function.ToIntFunction;
//TODO: Add a render cache
public class RenderGenerationService {
public interface TaskChecker {boolean check(int lvl, int x, int y, int z);}
private record BuildTask(long position, Supplier<WorldSection> sectionSupplier) {}
private record BuildTask(long position, Supplier<WorldSection> sectionSupplier, boolean[] hasDoneModelRequest) {}
private volatile boolean running = true;
private final Thread[] workers;
@@ -26,15 +29,15 @@ public class RenderGenerationService {
private final Semaphore taskCounter = new Semaphore(0);
private final WorldEngine world;
private final ModelManager modelManager;
private final OffThreadModelBakerySystem modelBakery;
private final Consumer<BuiltSection> resultConsumer;
private final BuiltSectionMeshCache meshCache = new BuiltSectionMeshCache();
private final boolean emitMeshlets;
public RenderGenerationService(WorldEngine world, ModelManager modelManager, int workers, Consumer<BuiltSection> consumer, boolean emitMeshlets) {
public RenderGenerationService(WorldEngine world, OffThreadModelBakerySystem modelBakery, int workers, Consumer<BuiltSection> consumer, boolean emitMeshlets) {
this.emitMeshlets = emitMeshlets;
this.world = world;
this.modelManager = modelManager;
this.modelBakery = modelBakery;
this.resultConsumer = consumer;
this.workers = new Thread[workers];
for (int i = 0; i < workers; i++) {
@@ -45,10 +48,26 @@ public class RenderGenerationService {
}
}
//NOTE: the biomes are always fully populated/kept up to date
//Asks the Model system to bake all blocks that currently dont have a model
private void computeAndRequestRequiredModels(WorldSection section) {
var raw = section.copyData();//TODO: replace with copyDataTo and use a "thread local"/context array to reduce allocation rates
IntOpenHashSet seen = new IntOpenHashSet(128);
for (long state : raw) {
int block = Mapper.getBlockId(state);
if (!this.modelBakery.factory.hasModelForBlockId(block)) {
if (seen.add(block)) {
this.modelBakery.requestBlockBake(block);
}
}
}
}
//TODO: add a generated render data cache
private void renderWorker() {
//Thread local instance of the factory
var factory = new RenderDataFactory(this.world, this.modelManager, this.emitMeshlets);
var factory = new RenderDataFactory(this.world, this.modelBakery.factory, this.emitMeshlets);
while (this.running) {
this.taskCounter.acquireUninterruptibly();
if (!this.running) break;
@@ -67,17 +86,28 @@ public class RenderGenerationService {
try {
mesh = factory.generateMesh(section);
} catch (IdNotYetComputedException e) {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
if (task.hasDoneModelRequest[0]) {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
} else {
this.computeAndRequestRequiredModels(section);
}
//We need to reinsert the build task into the queue
//System.err.println("Render task failed to complete due to un-computed client id");
synchronized (this.taskQueue) {
this.taskQueue.computeIfAbsent(section.key, key->{this.taskCounter.release(); return task;});
var queuedTask = this.taskQueue.computeIfAbsent(section.key, (a)->task);
queuedTask.hasDoneModelRequest[0] = true;//Mark (or remark) the section as having chunks requested
if (queuedTask == task) {//use the == not .equal to see if we need to release a permit
this.taskCounter.release();//Since we put in queue, release permit
}
}
}
//TODO: if the section was _not_ built, maybe dont release it, or release it with the hint
section.release();
if (mesh != null) {
//TODO: if the mesh is null, need to clear the cache at that point
@@ -136,7 +166,7 @@ public class RenderGenerationService {
} else {
return null;
}
});
}, new boolean[1]);
});
}
}
@@ -196,4 +226,8 @@ public class RenderGenerationService {
}
this.meshCache.free();
}
public void addDebugData(List<String> debug) {
}
}

View File

@@ -0,0 +1,6 @@
package me.cortex.voxy.client.core.rendering.geometry;
//The geometry renderer, takes a list of section ids to render (gpu side buffer) and renders them
// Also manages base geometry info
public abstract class AbstractGeometryRenderer {
}

View File

@@ -1,4 +1,4 @@
package me.cortex.voxy.client.core.rendering;
package me.cortex.voxy.client.core.rendering.geometry.OLD;
//NOTE: an idea on how to do it is so that any render section, we _keep_ aquired (yes this will be very memory intensive)
// could maybe tosomething else
@@ -6,7 +6,8 @@ package me.cortex.voxy.client.core.rendering;
import com.mojang.blaze3d.systems.RenderSystem;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.model.ModelManager;
import me.cortex.voxy.client.core.model.ModelFactory;
import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
@@ -19,7 +20,6 @@ import net.minecraft.util.Identifier;
import org.joml.FrustumIntersection;
import org.lwjgl.system.MemoryUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
@@ -35,12 +35,12 @@ import static org.lwjgl.opengl.GL30.*;
//Todo: tinker with having the compute shader where each thread is a position to render? maybe idk
public abstract class AbstractFarWorldRenderer <T extends Viewport, J extends AbstractGeometryManager> implements IRenderInterface<T> {
public abstract class AbstractFarWorldRenderer <T extends Viewport, J extends AbstractGeometryManager> {
public static final int STATIC_VAO = glGenVertexArrays();
protected final GlBuffer uniformBuffer;
protected final J geometry;
protected final ModelManager models;
protected final ModelFactory models;
protected final GlBuffer lightDataBuffer;
protected final int maxSections;
@@ -58,7 +58,7 @@ public abstract class AbstractFarWorldRenderer <T extends Viewport, J extends Ab
private final ConcurrentLinkedDeque<Mapper.StateEntry> blockStateUpdates = new ConcurrentLinkedDeque<>();
private final ConcurrentLinkedDeque<Mapper.BiomeEntry> biomeUpdates = new ConcurrentLinkedDeque<>();
public AbstractFarWorldRenderer(ModelManager models, J geometry) {
public AbstractFarWorldRenderer(ModelFactory models, J geometry) {
this.maxSections = geometry.getMaxSections();
this.uniformBuffer = new GlBuffer(1024);
this.lightDataBuffer = new GlBuffer(256*4);//256 of uint

View File

@@ -1,4 +1,4 @@
package me.cortex.voxy.client.core.rendering;
package me.cortex.voxy.client.core.rendering.geometry.OLD;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;

View File

@@ -1,4 +1,4 @@
package me.cortex.voxy.client.core.rendering;
package me.cortex.voxy.client.core.rendering.geometry.OLD;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
@@ -12,8 +12,6 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
import org.lwjgl.system.MemoryUtil;
import java.util.concurrent.ConcurrentLinkedDeque;
public class DefaultGeometryManager extends AbstractGeometryManager {
private static final int SECTION_METADATA_SIZE = 32;
private final Long2IntOpenHashMap pos2id = new Long2IntOpenHashMap();

View File

@@ -1,30 +1,23 @@
package me.cortex.voxy.client.core.rendering;
package me.cortex.voxy.client.core.rendering.geometry.OLD;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.model.ModelManager;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.model.ModelFactory;
import me.cortex.voxy.client.core.rendering.SharedIndexBuffer;
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.client.render.RenderLayer;
import net.minecraft.client.util.math.MatrixStack;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL11C;
import org.lwjgl.system.MemoryUtil;
import java.util.List;
import static org.lwjgl.opengl.ARBIndirectParameters.GL_PARAMETER_BUFFER_ARB;
import static org.lwjgl.opengl.ARBIndirectParameters.glMultiDrawElementsIndirectCountARB;
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_SHORT;
import static org.lwjgl.opengl.GL11.glGetInteger;
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30C.GL_R8UI;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
import static org.lwjgl.opengl.GL42.*;
@@ -55,7 +48,7 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer<Gl46Viewport,
private final GlBuffer glCommandBuffer;
private final GlBuffer glCommandCountBuffer;
public Gl46FarWorldRenderer(ModelManager modelManager, int geometryBuffer, int maxSections) {
public Gl46FarWorldRenderer(ModelFactory modelManager, int geometryBuffer, int maxSections) {
super(modelManager, new DefaultGeometryManager(geometryBuffer*8L, maxSections));
this.glCommandBuffer = new GlBuffer(maxSections*5L*4 * 6);
this.glCommandCountBuffer = new GlBuffer(4*2);
@@ -69,8 +62,8 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer<Gl46Viewport,
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.glCommandCountBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.geometry.metaId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, viewport.visibilityBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.models.getBufferId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, this.models.getColourBufferId());
//glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.models.getBufferId());
//glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, this.models.getColourBufferId());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 8, this.lightDataBuffer.id);//Lighting LUT
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.glCommandBuffer.id);
glBindBuffer(GL_PARAMETER_BUFFER_ARB, this.glCommandCountBuffer.id);
@@ -78,7 +71,7 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer<Gl46Viewport,
//Bind the texture atlas
glBindSampler(0, this.models.getSamplerId());
glBindTextureUnit(0, this.models.getTextureId());
//glBindTextureUnit(0, this.models.getTextureId());
}
//FIXME: dont do something like this as it breaks multiviewport mods
@@ -198,7 +191,7 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer<Gl46Viewport,
glBindSampler(0, this.models.getSamplerId());
glBindTextureUnit(0, this.models.getTextureId());
//glBindTextureUnit(0, this.models.getTextureId());
//RenderSystem.blendFunc(GlStateManager.SrcFactor.ONE, GlStateManager.DstFactor.ONE);
this.lodShader.bind();

View File

@@ -1,14 +1,9 @@
package me.cortex.voxy.client.core.rendering;
package me.cortex.voxy.client.core.rendering.geometry.OLD;
import com.sun.jna.NativeLibrary;
import me.cortex.voxy.client.core.AbstractRenderWorldInteractor;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.PrintfInjector;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.model.ModelManager;
import me.cortex.voxy.client.core.model.ModelFactory;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.building.RenderDataFactory;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.client.core.rendering.hierarchical.DebugRenderer;
import me.cortex.voxy.client.core.rendering.hierarchical.HierarchicalOcclusionRenderer;
@@ -16,44 +11,24 @@ import me.cortex.voxy.client.core.rendering.hierarchical.INodeInteractor;
import me.cortex.voxy.client.core.rendering.hierarchical.MeshManager;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.mixin.joml.AccessFrustumIntersection;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.WorldSection;
import me.cortex.voxy.common.world.other.Mapper;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.Frustum;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.util.Identifier;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.system.MemoryUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.function.Consumer;
import static org.lwjgl.opengl.ARBDirectStateAccess.glTextureParameteri;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL30.glBindBufferBase;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
import static org.lwjgl.opengl.GL31.glDrawElementsInstanced;
import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL40.glDrawElementsIndirect;
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
import static org.lwjgl.opengl.GL42.GL_FRAMEBUFFER_BARRIER_BIT;
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
import static org.lwjgl.opengl.GL43.*;
import static org.lwjgl.opengl.GL45.glBindTextureUnit;
import static org.lwjgl.opengl.GL45.nglClearNamedBufferSubData;
public class Gl46HierarchicalRenderer implements IRenderInterface<Gl46HierarchicalViewport>, AbstractRenderWorldInteractor {
public class Gl46HierarchicalRenderer {
private final HierarchicalOcclusionRenderer sectionSelector;
private final MeshManager meshManager = new MeshManager();
@@ -76,25 +51,25 @@ public class Gl46HierarchicalRenderer implements IRenderInterface<Gl46Hierarchic
protected final ConcurrentLinkedDeque<BuiltSection> buildResults = new ConcurrentLinkedDeque<>();
private final ModelManager modelManager;
private final ModelFactory modelManager;
private RenderGenerationService sectionGenerationService;
private Consumer<BuiltSection> resultConsumer;
public Gl46HierarchicalRenderer(ModelManager model) {
public Gl46HierarchicalRenderer(ModelFactory model) {
this.modelManager = model;
this.sectionSelector = new HierarchicalOcclusionRenderer(new INodeInteractor() {
@Override
public void watchUpdates(long pos) {
//System.err.println("Watch: " + pos);
}
@Override
public void unwatchUpdates(long pos) {
//System.err.println("Unwatch: " + pos);
}
@Override
public void requestMesh(long pos) {
Gl46HierarchicalRenderer.this.sectionGenerationService.enqueueTask(
WorldEngine.getLevel(pos),
@@ -104,14 +79,13 @@ public class Gl46HierarchicalRenderer implements IRenderInterface<Gl46Hierarchic
);
}
@Override
public void setMeshUpdateCallback(Consumer<BuiltSection> mesh) {
Gl46HierarchicalRenderer.this.resultConsumer = mesh;
}
}, this.meshManager, this.printf);
}
@Override
public void setupRender(Frustum frustum, Camera camera) {
{//Tick upload and download queues
UploadStream.INSTANCE.tick();
@@ -143,7 +117,7 @@ public class Gl46HierarchicalRenderer implements IRenderInterface<Gl46Hierarchic
}
}
@Override
public void renderFarAwayOpaque(Gl46HierarchicalViewport viewport) {
//Process all the build results
while (!this.buildResults.isEmpty()) {
@@ -167,12 +141,12 @@ public class Gl46HierarchicalRenderer implements IRenderInterface<Gl46Hierarchic
this.printf.download();
}
@Override
public void renderFarAwayTranslucent(Gl46HierarchicalViewport viewport) {
}
@Override
public void addDebugData(List<String> debug) {
debug.add("Printf Queue: ");
debug.addAll(this.printfQueue);
@@ -185,12 +159,12 @@ public class Gl46HierarchicalRenderer implements IRenderInterface<Gl46Hierarchic
@Override
public void addBlockState(Mapper.StateEntry stateEntry) {
this.blockStateUpdates.add(stateEntry);
}
@Override
public void addBiome(Mapper.BiomeEntry biomeEntry) {
this.biomeUpdates.add(biomeEntry);
}
@@ -198,21 +172,10 @@ public class Gl46HierarchicalRenderer implements IRenderInterface<Gl46Hierarchic
@Override
public void processBuildResult(BuiltSection section) {
this.buildResults.add(section);
}
@Override
public void sectionUpdated(WorldSection worldSection) {
}
@Override
public void initPosition(int X, int Z) {
for (int x = -10; x <= 10; x++) {
for (int z = -10; z <= 10; z++) {
@@ -224,27 +187,19 @@ public class Gl46HierarchicalRenderer implements IRenderInterface<Gl46Hierarchic
}
}
@Override
public void setCenter(int x, int y, int z) {
}
@Override
public boolean generateMeshlets() {
return false;
}
@Override
public void setRenderGen(RenderGenerationService renderService) {
this.sectionGenerationService = renderService;
}
@Override
public Gl46HierarchicalViewport createViewport() {
return new Gl46HierarchicalViewport(this);
}
@@ -252,7 +207,7 @@ public class Gl46HierarchicalRenderer implements IRenderInterface<Gl46Hierarchic
@Override
public void shutdown() {
this.meshManager.free();
this.sectionSelector.free();

View File

@@ -1,6 +1,6 @@
package me.cortex.voxy.client.core.rendering;
package me.cortex.voxy.client.core.rendering.geometry.OLD;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.Viewport;
public class Gl46HierarchicalViewport extends Viewport<Gl46HierarchicalViewport> {
public Gl46HierarchicalViewport(Gl46HierarchicalRenderer renderer) {

View File

@@ -1,13 +1,10 @@
package me.cortex.voxy.client.core.rendering;
package me.cortex.voxy.client.core.rendering.geometry.OLD;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.Viewport;
import static org.lwjgl.opengl.ARBIndirectParameters.glMultiDrawElementsIndirectCountARB;
import static org.lwjgl.opengl.GL30C.GL_R8UI;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL42.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL45C.glClearNamedBufferData;
import static org.lwjgl.opengl.GL45C.nglClearNamedBufferData;
public class Gl46Viewport extends Viewport<Gl46Viewport> {
GlBuffer visibilityBuffer;

View File

@@ -3,8 +3,8 @@ package me.cortex.voxy.client.core.rendering.hierarchical;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.AbstractFarWorldRenderer;
import me.cortex.voxy.client.core.rendering.Gl46HierarchicalViewport;
import me.cortex.voxy.client.core.rendering.geometry.OLD.AbstractFarWorldRenderer;
import me.cortex.voxy.client.core.rendering.geometry.OLD.Gl46HierarchicalViewport;
import me.cortex.voxy.client.core.rendering.SharedIndexBuffer;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import net.minecraft.util.math.MathHelper;

View File

@@ -4,10 +4,9 @@ import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.PrintfInjector;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.Gl46HierarchicalViewport;
import me.cortex.voxy.client.core.rendering.geometry.OLD.Gl46HierarchicalViewport;
import me.cortex.voxy.client.core.rendering.HiZBuffer;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.mixin.joml.AccessFrustumIntersection;
import net.minecraft.util.math.MathHelper;
import org.joml.Matrix4f;
import org.joml.Vector3f;
@@ -88,6 +87,8 @@ public class HierarchicalOcclusionRenderer {
UploadStream.INSTANCE.commit();
//FIXME: need to have the hiz respect the stencil mask aswell to mask away normal terrain, (much increase perf)
//Make hiz
this.hiz.buildMipChain(depthBuffer, viewport.width, viewport.height);
glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT);

View File

@@ -2,7 +2,7 @@ package me.cortex.voxy.client.core.rendering.section;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.Gl46HierarchicalViewport;
import me.cortex.voxy.client.core.rendering.geometry.OLD.Gl46HierarchicalViewport;
//Takes in mesh ids from the hierachical traversal and may perform more culling then renders it
public abstract class AbstractSectionRenderer {

View File

@@ -2,7 +2,7 @@ package me.cortex.voxy.client.core.rendering.section;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.Gl46HierarchicalViewport;
import me.cortex.voxy.client.core.rendering.geometry.OLD.Gl46HierarchicalViewport;
//Uses MDIC to render the sections
public class MDICSectionRenderer extends AbstractSectionRenderer {

View File

@@ -3,6 +3,7 @@ package me.cortex.voxy.client.core.rendering.util;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.util.AllocationArena;
import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.util.UnsafeUtil;
import org.lwjgl.system.MemoryUtil;
public class BufferArena {
@@ -32,7 +33,7 @@ public class BufferArena {
return -1;
}
long uploadPtr = UploadStream.INSTANCE.upload(this.buffer, addr * this.elementSize, buffer.size);
MemoryUtil.memCopy(buffer.address, uploadPtr, buffer.size);
UnsafeUtil.memcpy(buffer.address, uploadPtr, buffer.size);
this.used += size;
return addr;
}

View File

@@ -0,0 +1,25 @@
package me.cortex.voxy.client.core.rendering.util;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.common.util.UnsafeUtil;
import org.lwjgl.system.MemoryUtil;
//Just a utility for making a deferred upload (make on other thread then upload on render thread)
public final class DeferredUpload {
public final long ptr;
private final long size;
private final long offset;
private final GlBuffer buffer;
public DeferredUpload(GlBuffer buffer, long offset, long size) {
this.ptr = MemoryUtil.nmemAlloc(size);
this.offset = offset;
this.buffer = buffer;
this.size = size;
}
public void upload() {
long upPtr = UploadStream.INSTANCE.upload(this.buffer, this.offset, this.size);
UnsafeUtil.memcpy(this.ptr, upPtr, this.size);
MemoryUtil.nmemFree(this.ptr);
}
}

View File

@@ -0,0 +1,15 @@
package me.cortex.voxy.client.mixin.minecraft;
import com.mojang.blaze3d.systems.RenderSystem;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(RenderSystem.class)
public class MixinRenderSystem {
@Inject(method = {"assertOnRenderThread", "assertOnRenderThreadOrInit"}, at = @At("HEAD"), cancellable = true)
private static void cancelAssert(CallbackInfo ci) {
ci.cancel();
}
}

View File

@@ -17,9 +17,9 @@ public class MixinRenderSectionManager {
@Inject(method = "onChunkRemoved", at = @At("HEAD"))
private void injectIngest(int x, int z, CallbackInfo ci) {
var core = ((IGetVoxelCore)(world.worldRenderer)).getVoxelCore();
var core = ((IGetVoxelCore)(this.world.worldRenderer)).getVoxelCore();
if (core != null && VoxyConfig.CONFIG.ingestEnabled) {
core.enqueueIngest(world.getChunk(x, z));
core.enqueueIngest(this.world.getChunk(x, z));
}
}
}

View File

@@ -5,8 +5,10 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.function.LongConsumer;
public abstract class StorageBackend {
public abstract void iterateStoredSectionPositions(LongConsumer consumer);
public abstract ByteBuffer getSectionData(long key);

View File

@@ -13,6 +13,7 @@ import org.apache.commons.lang3.stream.Streams;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.function.LongConsumer;
public class MemoryStorageBackend extends StorageBackend {
private final Long2ObjectMap<ByteBuffer>[] maps;
@@ -22,6 +23,19 @@ public class MemoryStorageBackend extends StorageBackend {
this(4);
}
private Long2ObjectMap<ByteBuffer> getMap(long key) {
return this.maps[(int) (RandomSeed.mixStafford13(RandomSeed.mixStafford13(key)^key)&(this.maps.length-1))];
}
@Override
public void iterateStoredSectionPositions(LongConsumer consumer) {
for (var map : this.maps) {
synchronized (map) {
map.keySet().forEach(consumer);
}
}
}
public MemoryStorageBackend(int slicesBitCount) {
this.maps = new Long2ObjectMap[1<<slicesBitCount];
for (int i = 0; i < this.maps.length; i++) {
@@ -29,10 +43,6 @@ public class MemoryStorageBackend extends StorageBackend {
}
}
private Long2ObjectMap<ByteBuffer> getMap(long key) {
return this.maps[(int) (RandomSeed.mixStafford13(RandomSeed.mixStafford13(key)^key)&(this.maps.length-1))];
}
@Override
public ByteBuffer getSectionData(long key) {
var map = this.getMap(key);

View File

@@ -11,6 +11,7 @@ import java.nio.ByteBuffer;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.LongConsumer;
import java.util.function.Supplier;
import static org.lwjgl.util.lmdb.LMDB.*;
@@ -82,6 +83,11 @@ public class LMDBStorageBackend extends StorageBackend {
}
}
@Override
public void iterateStoredSectionPositions(LongConsumer consumer) {
throw new IllegalStateException("Not yet implemented");
}
//TODO: make batch get and updates
public ByteBuffer getSectionData(long key) {
return this.synchronizedTransaction(() -> this.sectionDatabase.transaction(MDB_RDONLY, transaction->{

View File

@@ -10,6 +10,7 @@ import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.function.LongConsumer;
public class DelegatingStorageAdaptor extends StorageBackend {
protected final StorageBackend delegate;
@@ -17,6 +18,9 @@ public class DelegatingStorageAdaptor extends StorageBackend {
this.delegate = delegate;
}
@Override
public void iterateStoredSectionPositions(LongConsumer consumer) {this.delegate.iterateStoredSectionPositions(consumer);}
@Override
public ByteBuffer getSectionData(long key) {
return this.delegate.getSectionData(key);

View File

@@ -12,6 +12,7 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.LongConsumer;
//Segments the section data into multiple dbs
public class FragmentedStorageBackendAdaptor extends StorageBackend {
@@ -29,6 +30,11 @@ public class FragmentedStorageBackendAdaptor extends StorageBackend {
return (int) (RandomSeed.mixStafford13(RandomSeed.mixStafford13(key)^key)&(this.backends.length-1));
}
@Override
public void iterateStoredSectionPositions(LongConsumer consumer) {
throw new IllegalStateException("Not yet implemented");
}
//TODO: reencode the key to be shifted one less OR
// use like a mix64 to shuffle the key in getSegmentId so that
// multiple layers of spliced storage backends can be stacked

View File

@@ -7,6 +7,7 @@ import me.cortex.voxy.common.storage.config.StorageConfig;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.function.LongConsumer;
public class ReadonlyCachingLayer extends StorageBackend {
private final StorageBackend cache;
@@ -30,6 +31,11 @@ public class ReadonlyCachingLayer extends StorageBackend {
return result;
}
@Override
public void iterateStoredSectionPositions(LongConsumer consumer) {
throw new IllegalStateException("Not yet implemented");
}
@Override
public void setSectionData(long key, ByteBuffer data) {
this.cache.setSectionData(key, data);

View File

@@ -9,6 +9,7 @@ import redis.clients.jedis.JedisPool;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.function.LongConsumer;
public class RedisStorageBackend extends StorageBackend {
private final JedisPool pool;
@@ -29,6 +30,11 @@ public class RedisStorageBackend extends StorageBackend {
this.MAPPINGS = (prefix+"id_mappings").getBytes(StandardCharsets.UTF_8);
}
@Override
public void iterateStoredSectionPositions(LongConsumer consumer) {
throw new IllegalStateException("Not yet implemented");
}
@Override
public ByteBuffer getSectionData(long key) {
try (var jedis = this.pool.getResource()) {

View File

@@ -14,6 +14,7 @@ import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.LongConsumer;
public class RocksDBStorageBackend extends StorageBackend {
private final RocksDB db;
@@ -80,6 +81,11 @@ public class RocksDBStorageBackend extends StorageBackend {
}
}
@Override
public void iterateStoredSectionPositions(LongConsumer consumer) {
throw new IllegalStateException("Not yet implemented");
}
@Override
public ByteBuffer getSectionData(long key) {
try {

View File

@@ -13,7 +13,7 @@ public class MemoryBuffer extends TrackedObject {
public void cpyTo(long dst) {
super.assertNotFreed();
MemoryUtil.memCopy(this.address, dst, this.size);
UnsafeUtil.memcpy(this.address, dst, this.size);
}
@Override

View File

@@ -0,0 +1,21 @@
package me.cortex.voxy.common.util;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeUtil {
private static final Unsafe UNSAFE;
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
UNSAFE = (Unsafe)field.get(null);
} catch (Exception e) {throw new RuntimeException(e);}
}
public static void memcpy(long src, long dst, long length) {
UNSAFE.copyMemory(src, dst, length);
}
}

View File

@@ -1,5 +1,6 @@
package me.cortex.voxy.common.world;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import me.cortex.voxy.common.util.VolatileHolder;
import me.cortex.voxy.common.world.other.Mapper;
@@ -14,7 +15,10 @@ public class ActiveSectionTracker {
private final Long2ObjectOpenHashMap<VolatileHolder<WorldSection>>[] loadedSectionCache;
private final SectionLoader loader;
//private final SectionDataCache dataCache;
//private static final int SECONDARY_CACHE_CAPACITY = 256;
//private final Long2ObjectLinkedOpenHashMap<long[]> secondaryDataCache = new Long2ObjectLinkedOpenHashMap<>(SECONDARY_CACHE_CAPACITY*2);//Its x2 due to race conditions
@SuppressWarnings("unchecked")
public ActiveSectionTracker(int numSlicesBits, SectionLoader loader) {
@@ -43,6 +47,7 @@ public class ActiveSectionTracker {
return section;
}
}
//If this thread was the one to create the reference then its the thread to load the section
if (isLoader) {
var section = new WorldSection(lvl, x, y, z, this);
@@ -84,11 +89,13 @@ public class ActiveSectionTracker {
void tryUnload(WorldSection section) {
var cache = this.loadedSectionCache[this.getCacheArrayIndex(section.key)];
boolean removed = false;
synchronized (cache) {
if (section.trySetFreed()) {
if (cache.remove(section.key).obj != section) {
throw new IllegalStateException("Removed section not the same as the referenced section in the cache");
}
removed = true;
}
}
}

View File

@@ -1,48 +0,0 @@
package me.cortex.voxy.common.world;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
public class SectionDataCache {
private record MapPair(Reference2LongOpenHashMap<SoftReference<long[]>> ref2pos, Long2ObjectMap<SoftReference<long[]>> pos2ref) {
public MapPair() {
this(new Reference2LongOpenHashMap<>(), new Long2ObjectOpenHashMap<>());
}
}
private final MapPair[] maps;
private final ReferenceQueue<long[]> cleanupQueue;
public SectionDataCache(int sliceBits) {
this.cleanupQueue = new ReferenceQueue<>();
this.maps = new MapPair[1<<sliceBits];
for (int i = 0; i < this.maps.length; i++) {
this.maps[i] = new MapPair();
}
}
private MapPair getMap(long pos) {
return this.maps[(int) (ActiveSectionTracker.mixStafford13(pos)&(this.maps.length-1))];
}
public int loadAndPut(WorldSection section) {
var map = this.getMap(section.key);
synchronized (map) {
var entry = map.pos2ref.get(section.key);
if (entry == null) {//No entry in cache so put it in cache
map.pos2ref.put(section.key, new SoftReference<>(section.data));
return -1;
}
//var data = entry.data.get();
//if (data == null) {
// map.remove(section.key);
// return -1;
//}
//System.arraycopy(data, 0, section.data, 0, data.length);
return 0;
}
}
}

View File

@@ -95,6 +95,10 @@ public class WorldEngine {
return (int) ((id<<12)>>40);
}
public static String pprintPos(long pos) {
return getLevel(pos)+"@["+getX(pos)+", "+getY(pos)+", " + getZ(pos)+"]";
}
//Marks a section as dirty, enqueuing it for saving and or render data rebuilding
public void markDirty(WorldSection section) {
if (this.dirtyCallback != null) {

View File

@@ -64,6 +64,7 @@ public final class WorldSection {
return this.atomicState.get()>>1;
}
//TODO: add the ability to hint to the tracker that yes the section is unloaded, try to cache it in a secondary cache since it will be reused/needed later
public int release() {
int state = this.atomicState.addAndGet(-2);
if (state < 1) {

View File

@@ -7,6 +7,7 @@
"minecraft.MixinClientChunkManager",
"minecraft.MixinDebugHud",
"minecraft.MixinMinecraftClient",
"minecraft.MixinRenderSystem",
"minecraft.MixinWorldRenderer",
"nvidium.MixinRenderPipeline",
"sodium.MixinDefaultChunkRenderer",