Alot of work

This commit is contained in:
mcrcortex
2023-12-01 11:53:35 +10:00
parent 9b3f8da1a5
commit 6ef2902e0c
31 changed files with 398 additions and 249 deletions

View File

@@ -8,6 +8,6 @@ import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
public class Voxelmon implements ClientModInitializer {
@Override
public void onInitializeClient() {
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> TestSparseGenCommand.register(dispatcher));
//CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> TestSparseGenCommand.register(dispatcher));
}
}

View File

@@ -11,6 +11,7 @@ import me.cortex.voxelmon.core.rendering.building.RenderGenerationService;
import me.cortex.voxelmon.core.util.DebugUtil;
import me.cortex.voxelmon.core.util.RingUtil;
import me.cortex.voxelmon.core.world.WorldEngine;
import net.minecraft.client.MinecraftClient;
//Can use ring logic
// i.e. when a player moves the rings of each lod change (how it was doing in the original attempt)
@@ -27,8 +28,8 @@ public class DistanceTracker {
//NOTE: This is in our render distance units, to convert to chunks at lvl 0 multiply by 2
int DIST = 16;//24;
this.rings[0] = new TransitionRing2D(5, DIST, (x,z)->{
if (true) {
this.rings[0] = new TransitionRing2D(5, (int) Math.ceil(MinecraftClient.getInstance().gameRenderer.getViewDistance()/16)/2, (x, z)->{
if (false) {
return;
}
for (int y = -2; y < 10; y++) {
@@ -97,6 +98,9 @@ public class DistanceTracker {
//Note radius is in shiftScale
private TransitionRing2D(int shiftSize, int radius, Transition2DCallback onEntry, Transition2DCallback onExit) {
this(shiftSize, radius, onEntry, onExit, 0, 0, 0);
}
private TransitionRing2D(int shiftSize, int radius, Transition2DCallback onEntry, Transition2DCallback onExit, int ix, int iy, int iz) {
//trigger just less than every shiftSize scale
this.triggerRangeSquared = 1<<((shiftSize<<1) - 1);
this.shiftSize = shiftSize;
@@ -111,15 +115,22 @@ public class DistanceTracker {
}
public void update(int x, int z) {
int dx = this.lastUpdateX - x;
int dz = this.lastUpdateZ - z;
int distSquared = dx*dx + dz*dz;
int MAX_STEPS_PER_UPDATE = 1;
long dx = this.lastUpdateX - x;
long dz = this.lastUpdateZ - z;
long distSquared = dx*dx + dz*dz;
if (distSquared < this.triggerRangeSquared) {
return;
}
//TODO: fixme: this last update needs to be incremented by a delta since
//Update the last update position
this.lastUpdateX = x;
this.lastUpdateZ = z;
int maxStep = this.triggerRangeSquared/2;
this.lastUpdateX += Math.min(maxStep,Math.max(-maxStep, x-this.lastUpdateX));
this.lastUpdateZ += Math.min(maxStep,Math.max(-maxStep, z-this.lastUpdateZ));
@@ -137,7 +148,7 @@ public class DistanceTracker {
Long2IntOpenHashMap ops = new Long2IntOpenHashMap();
int zcount = MAX_STEPS_PER_UPDATE;
int dir = nz<this.currentZ?-1:1;
while (nz != this.currentZ) {
for (int corner : this.cornerPoints) {
@@ -156,8 +167,11 @@ public class DistanceTracker {
//ops.addTo(Prel(0, -this.radius+Math.min(0, dir)), -dir);
this.currentZ += dir;
if (--zcount == 0) break;
}
int xcount = MAX_STEPS_PER_UPDATE;
dir = nx<this.currentX?-1:1;
while (nx != this.currentX) {
@@ -174,6 +188,8 @@ public class DistanceTracker {
}
this.currentX += dir;
if (--xcount == 0) break;
}

View File

@@ -12,6 +12,7 @@ import me.cortex.voxelmon.core.world.WorldSection;
import me.cortex.voxelmon.core.world.other.BiomeColour;
import me.cortex.voxelmon.core.world.other.BlockStateColour;
import me.cortex.voxelmon.core.world.other.ColourResolver;
import me.cortex.voxelmon.core.world.other.Mapper;
import me.cortex.voxelmon.importers.WorldImporter;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
@@ -45,6 +46,18 @@ import static org.lwjgl.opengl.ARBFramebufferObject.glBindFramebuffer;
//There is strict forward only dataflow
//Ingest -> world engine -> raw render data -> render data
public class VoxelCore {
private static final Set<Block> biomeTintableAllFaces = new HashSet<>(List.of(Blocks.OAK_LEAVES, Blocks.JUNGLE_LEAVES, Blocks.ACACIA_LEAVES, Blocks.DARK_OAK_LEAVES, Blocks.VINE, Blocks.MANGROVE_LEAVES,
Blocks.TALL_GRASS, Blocks.LARGE_FERN,
Blocks.SPRUCE_LEAVES,
Blocks.BIRCH_LEAVES,
Blocks.PINK_PETALS,
Blocks.FERN, Blocks.GRASS, Blocks.POTTED_FERN));
private static final Set<Block> biomeTintableUpFace = new HashSet<>(List.of(Blocks.GRASS_BLOCK));
private static final Set<Block> waterTint = new HashSet<>(List.of(Blocks.WATER));
public static VoxelCore INSTANCE = new VoxelCore();
private final WorldEngine world;
@@ -55,12 +68,11 @@ public class VoxelCore {
private final AbstractFarWorldRenderer renderer;
private final PostProcessing postProcessing;
public VoxelCore() {
//Trigger the shared index buffer loading
SharedIndexBuffer.INSTANCE.id();
this.renderer = new Gl46FarWorldRenderer();
this.world = new WorldEngine(new File("storagefile.db"), 20, 5);//"storagefile.db"//"ethoslab.db"
this.world = new WorldEngine(new File("storagefile2.db"), 20, 5);//"storagefile.db"//"ethoslab.db"
this.renderTracker = new RenderTracker(this.world, this.renderer);
this.renderGen = new RenderGenerationService(this.world,4, this.renderTracker::processBuildResult);
@@ -71,6 +83,8 @@ public class VoxelCore {
this.postProcessing = new PostProcessing();
this.world.getMapper().setCallbacks(this::stateUpdate, this::biomeUpdate);
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
@@ -97,19 +111,19 @@ public class VoxelCore {
//WorldImporter importer = new WorldImporter(this.world, MinecraftClient.getInstance().world);
//importer.importWorldAsyncStart(new File("saves/New World/region"));
Set<Block> biomeTintableAllFaces = new HashSet<>(List.of(Blocks.OAK_LEAVES, Blocks.JUNGLE_LEAVES, Blocks.ACACIA_LEAVES, Blocks.DARK_OAK_LEAVES, Blocks.VINE, Blocks.MANGROVE_LEAVES,
Blocks.TALL_GRASS, Blocks.LARGE_FERN));
biomeTintableAllFaces.add(Blocks.SPRUCE_LEAVES);
biomeTintableAllFaces.add(Blocks.BIRCH_LEAVES);
biomeTintableAllFaces.add(Blocks.PINK_PETALS);
biomeTintableAllFaces.addAll(List.of(Blocks.FERN, Blocks.GRASS, Blocks.POTTED_FERN));
Set<Block> biomeTintableUpFace = new HashSet<>(List.of(Blocks.GRASS_BLOCK));
Set<Block> waterTint = new HashSet<>(List.of(Blocks.WATER));
for (var state : this.world.getMapper().getStateEntries()) {
this.stateUpdate(state);
}
int i = 0;
for (var state : this.world.getMapper().getBlockStates()) {
for (var biome : this.world.getMapper().getBiomeEntries()) {
this.biomeUpdate(biome);
}
}
private void stateUpdate(Mapper.StateEntry entry) {
var state = entry.state;
int tintMsk = 0;
if (biomeTintableAllFaces.contains(state.getBlock())) {
tintMsk |= (1<<6)-1;
@@ -120,16 +134,15 @@ public class VoxelCore {
if (waterTint.contains(state.getBlock())) {
tintMsk |= 1<<6;
}
this.renderer.enqueueUpdate(new BlockStateColour(i++, tintMsk, ColourResolver.resolveColour(state)));
this.renderer.enqueueUpdate(new BlockStateColour(entry.id, tintMsk, ColourResolver.resolveColour(state)));
}
i = 0;
for (var biome : this.world.getMapper().getBiomes()) {
long dualColour = ColourResolver.resolveBiomeColour(biome);
this.renderer.enqueueUpdate(new BiomeColour(i++, (int) dualColour, (int) (dualColour>>32)));
}
private void biomeUpdate(Mapper.BiomeEntry entry) {
long dualColour = ColourResolver.resolveBiomeColour(entry.biome);
this.renderer.enqueueUpdate(new BiomeColour(entry.id, (int) dualColour, (int) (dualColour>>32)));
}
public void enqueueIngest(WorldChunk worldChunk) {
this.world.ingestService.enqueueIngest(worldChunk);
}
@@ -145,9 +158,9 @@ public class VoxelCore {
DebugUtil.setPositionMatrix(matrices);
matrices.pop();
int boundFB = GlStateManager.getBoundFramebuffer();
this.postProcessing.setSize(MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight);
this.postProcessing.bindClearFramebuffer();
//int boundFB = GlStateManager.getBoundFramebuffer();
//this.postProcessing.setSize(MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight);
//this.postProcessing.bindClearFramebuffer();
//TODO: FIXME: since we just bound the post processing FB the depth information isnt
// copied over, we must do this manually and also copy it with respect to the
@@ -158,8 +171,8 @@ public class VoxelCore {
// this is cause the terrain might not exist and so all the caves are visible causing hell for the
// occlusion culler
this.renderer.renderFarAwayOpaque(matrices, cameraX, cameraY, cameraZ);
glBindFramebuffer(GL_FRAMEBUFFER, boundFB);
this.postProcessing.renderPost(boundFB);
//glBindFramebuffer(GL_FRAMEBUFFER, boundFB);
// this.postProcessing.renderPost(boundFB);
}
public void addDebugInfo(List<String> debug) {

View File

@@ -3,36 +3,23 @@ package me.cortex.voxelmon.core.rendering;
//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
import com.mojang.blaze3d.systems.RenderSystem;
import me.cortex.voxelmon.core.gl.GlBuffer;
import me.cortex.voxelmon.core.gl.shader.Shader;
import me.cortex.voxelmon.core.gl.shader.ShaderType;
import me.cortex.voxelmon.core.rendering.building.BuiltSectionGeometry;
import me.cortex.voxelmon.core.rendering.util.UploadStream;
import me.cortex.voxelmon.core.world.other.BiomeColour;
import me.cortex.voxelmon.core.world.other.BlockStateColour;
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.client.util.math.MatrixStack;
import org.joml.FrustumIntersection;
import org.joml.Matrix4f;
import org.lwjgl.system.MemoryUtil;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import static org.lwjgl.opengl.ARBMultiDrawIndirect.glMultiDrawElementsIndirect;
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_SHORT;
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
import static org.lwjgl.opengl.GL42.GL_COMMAND_BARRIER_BIT;
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
import static org.lwjgl.opengl.GL43.*;
//can make it so that register the key of the sections we have rendered, then when a section changes and is registered,
// dispatch an update to the render section data builder which then gets consumed by the render system and updates
@@ -53,7 +40,7 @@ public abstract class AbstractFarWorldRenderer {
private final ConcurrentLinkedDeque<BiomeColour> biomeUpdateQueue = new ConcurrentLinkedDeque<>();
protected final GlBuffer stateDataBuffer;
protected final GlBuffer biomeDataBuffer;
protected final GlBuffer light = null;
protected final GlBuffer lightDataBuffer;
//Current camera base level section position
@@ -68,6 +55,7 @@ public abstract class AbstractFarWorldRenderer {
//TODO: make these both dynamically sized
this.stateDataBuffer = new GlBuffer((1<<16)*28, 0);//Capacity for 1<<16 entries
this.biomeDataBuffer = new GlBuffer(512*4*2, 0);//capacity for 1<<9 entries
this.lightDataBuffer = new GlBuffer(256*4, 0);//256 of uint
this.geometry = new GeometryManager();
}
@@ -80,12 +68,26 @@ public abstract class AbstractFarWorldRenderer {
this.sy = camera.getBlockPos().getY() >> 5;
this.sz = camera.getBlockPos().getZ() >> 5;
//TODO: move this to a render function that is only called
// once per frame when using multi viewport mods
//it shouldent matter if its called multiple times a frame however, as its synced with fences
UploadStream.INSTANCE.tick();
//Update the lightmap
{
long upload = UploadStream.INSTANCE.upload(this.lightDataBuffer, 0, 256*4);
var lmt = MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().texture.getImage();
for (int light = 0; light < 256; light++) {
int x = light&0xF;
int y = ((light>>4)&0xF);
int sample = lmt.getColor(x,y);
sample = ((sample&0xFF0000)>>16)|(sample&0xFF00)|((sample&0xFF)<<16);
MemoryUtil.memPutInt(upload + (((x<<4)|(15-y))*4), sample|(0xFF<<28));//Skylight is inverted
}
}
this.geometry.uploadResults();
//Upload any block state changes
while (!this.stateUpdateQueue.isEmpty()) {
@@ -129,5 +131,6 @@ public abstract class AbstractFarWorldRenderer {
this.uniformBuffer.free();
this.stateDataBuffer.free();
this.biomeDataBuffer.free();
this.lightDataBuffer.free();
}
}

View File

@@ -1,21 +1,16 @@
package me.cortex.voxelmon.core.rendering;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import me.cortex.voxelmon.core.gl.GlBuffer;
import me.cortex.voxelmon.core.gl.shader.Shader;
import me.cortex.voxelmon.core.gl.shader.ShaderType;
import me.cortex.voxelmon.core.rendering.building.BuiltSectionGeometry;
import me.cortex.voxelmon.core.rendering.util.UploadStream;
import me.cortex.voxelmon.core.util.MemoryBuffer;
import me.cortex.voxelmon.core.world.WorldEngine;
import me.cortex.voxelmon.core.world.WorldSection;
import me.cortex.voxelmon.mixin.joml.AccessFrustumIntersection;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.util.math.MatrixStack;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.lwjgl.system.MemoryUtil;
import java.util.List;
@@ -67,7 +62,7 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.glVisibilityBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, this.stateDataBuffer.id);//State LUT
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.biomeDataBuffer.id);//Biome LUT
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, 0);//Lighting LUT
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, this.lightDataBuffer.id);//Lighting LUT
glBindVertexArray(0);
}
@@ -80,6 +75,7 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
//RenderSystem.defaultBlendFunc();
this.updateUniformBuffer(stack, cx, cy, cz);
UploadStream.INSTANCE.commit();
glBindVertexArray(this.vao);

View File

@@ -4,6 +4,7 @@ import me.cortex.voxelmon.core.gl.GlFramebuffer;
import me.cortex.voxelmon.core.gl.GlTexture;
import me.cortex.voxelmon.core.gl.shader.Shader;
import me.cortex.voxelmon.core.gl.shader.ShaderType;
import org.lwjgl.opengl.GL11C;
import static org.lwjgl.opengl.ARBFramebufferObject.*;
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glBindImageTexture;
@@ -30,6 +31,11 @@ public class PostProcessing {
.add(ShaderType.COMPUTE, "voxelmon:lod/ssao/ssao.comp")
.compile();
//private final Shader blit = Shader.make()
// .add(ShaderType.VERTEX, "voxelmon:lod/blit_nodepth/quad.vert")
// .add(ShaderType.FRAGMENT, "voxelmon:lod/blit_nodepth/quad.frag")
// .compile();
public PostProcessing() {
this.framebuffer = new GlFramebuffer();
}
@@ -61,12 +67,14 @@ public class PostProcessing {
//Executes the post processing and emits to whatever framebuffer is currently bound via a blit
public void renderPost(int outputFb) {
this.ssao.bind();
glBindImageTexture(0, this.colour.id, 0, false,0, GL_READ_WRITE, GL_RGBA8);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, this.depthStencil.id);
glBindImageTexture(0, this.colour.id, 0, false,0, GL_READ_WRITE, GL_RGBA8);
//glDispatchCompute(this.width/32, this.height/32, 1);
glTextureBarrier();
glBlitNamedFramebuffer(this.framebuffer.id, outputFb, 0,0, this.width, this.height, 0, 0, this.width, this.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, this.colour.id);
glDrawArrays(GL11C.GL_TRIANGLES, 0, 3);
}
public void shutdown() {
@@ -74,5 +82,6 @@ public class PostProcessing {
if (this.colour != null) this.colour.free();
if (this.depthStencil != null) this.depthStencil.free();
this.ssao.free();
//this.blit.free();
}
}

View File

@@ -50,12 +50,13 @@ public class RenderDataFactory {
for (int z = 0; z < 32; z++) {
for (int x = 0; x < 32; x++) {
var self = data[WorldSection.getIndex(x, y, z)];
if (self == Mapper.AIR) {
if (Mapper.isAir(self)) {
continue;
}
long up = -1;
if (y < 31) {
var up = data[WorldSection.getIndex(x, y + 1, z)];
if (up != Mapper.AIR) {
up = data[WorldSection.getIndex(x, y + 1, z)];
if (!Mapper.isTranslucent(up)) {
continue;
}
}
@@ -66,19 +67,19 @@ public class RenderDataFactory {
connectedData = connectedSection.copyData();
connectedSection.release();
}
var up = connectedData[WorldSection.getIndex(x, 0, z)];
if (up != Mapper.AIR) {
up = connectedData[WorldSection.getIndex(x, 0, z)];
if (!Mapper.isTranslucent(up)) {
continue;
}
}
this.mesher.put(x, z, self);
this.mesher.put(x, z, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
}
}
var quads = this.mesher.process();
for (int i = 0; i < quads.length; i++) {
var quad = quads[i];
this.outData.add(QuadFormat.encode(null, data[WorldSection.getIndex(Mesher2D.getX(quad), y, Mesher2D.getZ(quad))], 1, y, quad));
this.outData.add(QuadFormat.encode(null, this.mesher.getDataFromQuad(quad), 1, y, quad));
}
}
connectedData = null;
@@ -92,12 +93,13 @@ public class RenderDataFactory {
for (int y = 0; y < 32; y++) {
for (int z = 0; z < 32; z++) {
var self = data[WorldSection.getIndex(x, y, z)];
if (self == Mapper.AIR) {
if (Mapper.isAir(self)) {
continue;
}
long up = -1;
if (x < 31) {
var up = data[WorldSection.getIndex(x + 1, y, z)];
if (up != Mapper.AIR) {
up = data[WorldSection.getIndex(x + 1, y, z)];
if (!Mapper.isTranslucent(up)) {
continue;
}
}
@@ -108,19 +110,19 @@ public class RenderDataFactory {
connectedData = connectedSection.copyData();
connectedSection.release();
}
var up = connectedData[WorldSection.getIndex(0, y, z)];
if (up != Mapper.AIR) {
up = connectedData[WorldSection.getIndex(0, y, z)];
if (!Mapper.isTranslucent(up)) {
continue;
}
}
this.mesher.put(y, z, self);
this.mesher.put(y, z, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
}
}
var quads = this.mesher.process();
for (int i = 0; i < quads.length; i++) {
var quad = quads[i];
this.outData.add(QuadFormat.encode(null, data[WorldSection.getIndex(x, Mesher2D.getX(quad), Mesher2D.getZ(quad))], 5, x, quad));
this.outData.add(QuadFormat.encode(null, this.mesher.getDataFromQuad(quad), 5, x, quad));
}
}
connectedData = null;
@@ -134,12 +136,13 @@ public class RenderDataFactory {
for (int x = 0; x < 32; x++) {
for (int y = 0; y < 32; y++) {
var self = data[WorldSection.getIndex(x, y, z)];
if (self == Mapper.AIR) {
if (Mapper.isAir(self)) {
continue;
}
long up = -1;
if (z < 31) {
var up = data[WorldSection.getIndex(x, y, z + 1)];
if (up != Mapper.AIR) {
up = data[WorldSection.getIndex(x, y, z + 1)];
if (!Mapper.isTranslucent(up)) {
continue;
}
}
@@ -150,19 +153,19 @@ public class RenderDataFactory {
connectedData = connectedSection.copyData();
connectedSection.release();
}
var up = connectedData[WorldSection.getIndex(x, y, 0)];
if (up != Mapper.AIR) {
up = connectedData[WorldSection.getIndex(x, y, 0)];
if (!Mapper.isTranslucent(up)) {
continue;
}
}
this.mesher.put(x, y, self);
this.mesher.put(x, y, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
}
}
var quads = this.mesher.process();
for (int i = 0; i < quads.length; i++) {
var quad = quads[i];
this.outData.add(QuadFormat.encode(null, data[WorldSection.getIndex(Mesher2D.getX(quad), Mesher2D.getZ(quad), z)], 3, z, quad));
this.outData.add(QuadFormat.encode(null, this.mesher.getDataFromQuad(quad), 3, z, quad));
}
}
connectedData = null;
@@ -176,12 +179,13 @@ public class RenderDataFactory {
for (int y = 0; y < 32; y++) {
for (int z = 0; z < 32; z++) {
var self = data[WorldSection.getIndex(x, y, z)];
if (self == Mapper.AIR) {
if (Mapper.isAir(self)) {
continue;
}
long up = -1;
if (x != 0) {
var up = data[WorldSection.getIndex(x - 1, y, z)];
if (up != Mapper.AIR) {
up = data[WorldSection.getIndex(x - 1, y, z)];
if (!Mapper.isTranslucent(up)) {
continue;
}
}
@@ -192,19 +196,19 @@ public class RenderDataFactory {
connectedData = connectedSection.copyData();
connectedSection.release();
}
var up = connectedData[WorldSection.getIndex(31, y, z)];
if (up != Mapper.AIR) {
up = connectedData[WorldSection.getIndex(31, y, z)];
if (!Mapper.isTranslucent(up)) {
continue;
}
}
this.mesher.put(y, z, self);
this.mesher.put(y, z, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
}
}
var quads = this.mesher.process();
for (int i = 0; i < quads.length; i++) {
var quad = quads[i];
this.outData.add(QuadFormat.encode(null, data[WorldSection.getIndex(x, Mesher2D.getX(quad), Mesher2D.getZ(quad))], 4, x, quad));
this.outData.add(QuadFormat.encode(null, this.mesher.getDataFromQuad(quad), 4, x, quad));
}
}
connectedData = null;
@@ -218,12 +222,13 @@ public class RenderDataFactory {
for (int x = 0; x < 32; x++) {
for (int y = 0; y < 32; y++) {
var self = data[WorldSection.getIndex(x, y, z)];
if (self == Mapper.AIR) {
if (Mapper.isAir(self)) {
continue;
}
long up = -1;
if (z != 0) {
var up = data[WorldSection.getIndex(x, y, z - 1)];
if (up != Mapper.AIR) {
up = data[WorldSection.getIndex(x, y, z - 1)];
if (!Mapper.isTranslucent(up)) {
continue;
}
}
@@ -234,19 +239,19 @@ public class RenderDataFactory {
connectedData = connectedSection.copyData();
connectedSection.release();
}
var up = connectedData[WorldSection.getIndex(x, y, 31)];
if (up != Mapper.AIR) {
up = connectedData[WorldSection.getIndex(x, y, 31)];
if (!Mapper.isTranslucent(up)) {
continue;
}
}
this.mesher.put(x, y, self);
this.mesher.put(x, y, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
}
}
var quads = this.mesher.process();
for (int i = 0; i < quads.length; i++) {
var quad = quads[i];
this.outData.add(QuadFormat.encode(null, data[WorldSection.getIndex(Mesher2D.getX(quad), Mesher2D.getZ(quad), z)], 2, z, quad));
this.outData.add(QuadFormat.encode(null, this.mesher.getDataFromQuad(quad), 2, z, quad));
}
}
connectedData = null;
@@ -260,12 +265,13 @@ public class RenderDataFactory {
for (int x = 0; x < 32; x++) {
for (int z = 0; z < 32; z++) {
var self = data[WorldSection.getIndex(x, y, z)];
if (self == Mapper.AIR) {
if (Mapper.isAir(self)) {
continue;
}
long up = -1;
if (y != 0) {
var up = data[WorldSection.getIndex(x, y - 1, z)];
if (up != Mapper.AIR) {
up = data[WorldSection.getIndex(x, y - 1, z)];
if (!Mapper.isTranslucent(up)) {
continue;
}
}
@@ -276,19 +282,19 @@ public class RenderDataFactory {
connectedData = connectedSection.copyData();
connectedSection.release();
}
var up = connectedData[WorldSection.getIndex(x, 31, z)];
if (up != Mapper.AIR) {
up = connectedData[WorldSection.getIndex(x, 31, z)];
if (!Mapper.isTranslucent(up)) {
continue;
}
}
this.mesher.put(x, z, self);
this.mesher.put(x, z, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
}
}
var quads = this.mesher.process();
for (int i = 0; i < quads.length; i++) {
var quad = quads[i];
this.outData.add(QuadFormat.encode(null, data[WorldSection.getIndex(Mesher2D.getX(quad), y, Mesher2D.getZ(quad))], 0, y, quad));
this.outData.add(QuadFormat.encode(null, this.mesher.getDataFromQuad(quad), 0, y, quad));
}
}
connectedData = null;

View File

@@ -6,6 +6,7 @@ import me.cortex.voxelmon.core.rendering.RenderTracker;
import me.cortex.voxelmon.core.world.WorldEngine;
import me.cortex.voxelmon.core.world.WorldSection;
import net.minecraft.util.math.Direction;
import net.minecraft.world.chunk.ChunkNibbleArray;
import net.minecraft.world.chunk.WorldChunk;
import java.util.concurrent.ConcurrentHashMap;
@@ -25,6 +26,7 @@ public class RenderGenerationService {
private final Thread[] workers;
private final Long2ObjectLinkedOpenHashMap<BuildTask> taskQueue = new Long2ObjectLinkedOpenHashMap<>();
private final Semaphore taskCounter = new Semaphore(0);
private final WorldEngine world;
private final Consumer<BuiltSectionGeometry> resultConsumer;
@@ -63,10 +65,15 @@ public class RenderGenerationService {
if (buildFlags != 0) {
var mesh = factory.generateMesh(section, buildFlags);
this.resultConsumer.accept(mesh.clone());
if (false) {
var prevCache = this.renderCache.put(mesh.position, mesh);
if (prevCache != null) {
prevCache.free();
}
} else {
mesh.free();
}
}
section.release();
}
@@ -106,7 +113,7 @@ public class RenderGenerationService {
this.taskCounter.release();
return new BuildTask(()->{
if (checker.check(lvl, x, y, z)) {
return this.world.acquire(lvl, x, y, z);
return this.world.acquireIfExists(lvl, x, y, z);
} else {
return null;
}

View File

@@ -163,5 +163,13 @@ public class Mesher2D {
this.meshed.clear();
Arrays.fill(this.data, 0);
}
public long getDataFromQuad(int quad) {
return this.getData(getX(quad), getZ(quad));
}
public long getData(int x, int z) {
return this.data[this.getIdx(x, z)];
}
}

View File

@@ -1,5 +0,0 @@
package me.cortex.voxelmon.core.voxelization;
public interface I3dByteSupplier {
byte supply(int x, int y, int z);
}

View File

@@ -0,0 +1,7 @@
package me.cortex.voxelmon.core.voxelization;
import net.minecraft.block.BlockState;
public interface ILightingSupplier {
byte supply(int x, int y, int z, BlockState state);
}

View File

@@ -18,10 +18,11 @@ public class WorldConversionFactory {
return ((y<<2)|(z<<1)|x) + 4*4*4;
}
//TODO: add a local mapper cache since it should be smaller and faster
public static VoxelizedSection convert(Mapper stateMapper,
PalettedContainer<BlockState> blockContainer,
ReadableContainer<RegistryEntry<Biome>> biomeContainer,
I3dByteSupplier lightSupplier,
ILightingSupplier lightSupplier,
int sx,
int sy,
int sz) {
@@ -41,9 +42,10 @@ public class WorldConversionFactory {
int y = (oy<<2)|iy;
int z = (oz<<2)|iz;
var state = blockContainer.get(x, y, z);
if (!state.isAir()) {
byte light = lightSupplier.supply(x,y,z,state);
if (!(state.isAir() && (light==0))) {//TODO:FIXME:optimize this in such a way that having skylight access/no skylight means that an entire section is created, WHICH IS VERY BAD FOR PERFORMANCE!!!!
nonAir++;
current[I(ix, iy, iz)] = stateMapper.getBaseId(lightSupplier.supply(x,y,z), state, biome);
current[I(ix, iy, iz)] = stateMapper.getBaseId(light, state, biome);
}
}
}

View File

@@ -7,7 +7,7 @@ import java.lang.ref.Reference;
public class ActiveSectionTracker {
//Deserialize into the supplied section, returns true on success, false on failure
public interface SectionLoader {boolean load(WorldSection section);}
public interface SectionLoader {int load(WorldSection section);}
//Loaded section world cache, TODO: get rid of VolatileHolder and use something more sane
@@ -21,7 +21,7 @@ public class ActiveSectionTracker {
}
}
public WorldSection acquire(int lvl, int x, int y, int z) {
public WorldSection acquire(int lvl, int x, int y, int z, boolean nullOnEmpty) {
long key = WorldEngine.getWorldSectionId(lvl, x, y, z);
var cache = this.loadedSectionCache[lvl];
VolatileHolder<WorldSection> holder = null;
@@ -42,12 +42,17 @@ public class ActiveSectionTracker {
//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);
if (!this.loader.load(section)) {
int status = this.loader.load(section);
if (status < 0) {
//TODO: Instead if throwing an exception do something better
throw new IllegalStateException("Unable to load section");
}
section.acquire();
holder.obj = section;
if (nullOnEmpty && status == 1) {//If its air return null as stated, release the section aswell
section.release();
return null;
}
return section;
} else {
WorldSection section = null;
@@ -59,7 +64,7 @@ public class ActiveSectionTracker {
return section;
}
}
return this.acquire(lvl, x, y, z);
return this.acquire(lvl, x, y, z, nullOnEmpty);
}
}
@@ -85,14 +90,14 @@ public class ActiveSectionTracker {
public static void main(String[] args) {
var tracker = new ActiveSectionTracker(1, a->true);
var tracker = new ActiveSectionTracker(1, a->0);
var section = tracker.acquire(0,0,0,0);
var section = tracker.acquire(0,0,0,0, false);
section.acquire();
var section2 = tracker.acquire(0,0,0,0);
var section2 = tracker.acquire(0,0,0,0, false);
section.release();
section.release();
section = tracker.acquire(0,0,0,0);
section = tracker.acquire(0,0,0,0, false);
section.release();
}

View File

@@ -1,23 +1,15 @@
package me.cortex.voxelmon.core.world;
import it.unimi.dsi.fastutil.ints.Int2ShortOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.longs.Long2ShortOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.zstd.Zstd;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import java.util.Random;
import static org.lwjgl.util.zstd.Zstd.*;
public class SaveLoadSystem {
public static byte[] serialize(WorldSection section) {
public static ByteBuffer serialize(WorldSection section) {
var data = section.copyData();
var compressed = new Short[data.length];
Long2ShortOpenHashMap LUT = new Long2ShortOpenHashMap();
@@ -57,24 +49,17 @@ public class SaveLoadSystem {
raw.rewind();
ByteBuffer compressedData = MemoryUtil.memAlloc((int)ZSTD_COMPRESSBOUND(raw.remaining()));
long compressedSize = ZSTD_compress(compressedData, raw, 15);
byte[] out = new byte[(int) compressedSize];
compressedData.limit((int) compressedSize);
compressedData.get(out);
compressedData.rewind();
MemoryUtil.memFree(raw);
MemoryUtil.memFree(compressedData);
//Compress into a key + data pallet format
return out;
return compressedData;
}
public static boolean deserialize(WorldSection section, byte[] data) {
var buff = MemoryUtil.memAlloc(data.length);
buff.put(data);
buff.rewind();
public static boolean deserialize(WorldSection section, ByteBuffer data) {
var decompressed = MemoryUtil.memAlloc(32*32*32*4*2);
long size = ZSTD_decompress(decompressed, buff);
MemoryUtil.memFree(buff);
long size = ZSTD_decompress(decompressed, data);
decompressed.limit((int) size);
long hash = 0;

View File

@@ -7,6 +7,7 @@ import me.cortex.voxelmon.core.world.other.Mapper;
import me.cortex.voxelmon.core.world.service.SectionSavingService;
import me.cortex.voxelmon.core.world.service.VoxelIngestService;
import me.cortex.voxelmon.core.world.storage.StorageBackend;
import org.lwjgl.system.MemoryUtil;
import java.io.File;
import java.util.Arrays;
@@ -41,25 +42,36 @@ public class WorldEngine {
this.sectionTracker = new ActiveSectionTracker(maxMipLayers, this::unsafeLoadSection);
this.savingService = new SectionSavingService(this, savingServiceWorkers);
this.ingestService = new VoxelIngestService(this);
this.ingestService = new VoxelIngestService(this, 2);
}
private boolean unsafeLoadSection(WorldSection into) {
private int unsafeLoadSection(WorldSection into) {
var data = this.storage.getSectionData(into.getKey());
if (data != null) {
try {
if (!SaveLoadSystem.deserialize(into, data)) {
this.storage.deleteSectionData(into.getKey());
//TODO: regenerate the section from children
Arrays.fill(into.data, Mapper.AIR);
System.err.println("Section " + into.lvl + ", " + into.x + ", " + into.y + ", " + into.z + " was unable to load, setting to air");
return true;
return -1;
} else {
return 0;
}
} finally {
MemoryUtil.memFree(data);
}
} else {
return 1;
}
}
return true;
public WorldSection acquireIfExists(int lvl, int x, int y, int z) {
return this.sectionTracker.acquire(lvl, x, y, z, true);
}
public WorldSection acquire(int lvl, int x, int y, int z) {
return this.sectionTracker.acquire(lvl, x, y, z);
return this.sectionTracker.acquire(lvl, x, y, z, false);
}
//TODO: Fixme/optimize, cause as the lvl gets higher, the size of x,y,z gets smaller so i can dynamically compact the format

View File

@@ -10,6 +10,7 @@ import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NbtOps;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.stat.Stat;
import net.minecraft.world.biome.Biome;
import org.lwjgl.system.MemoryUtil;
@@ -17,9 +18,9 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
//There are independent mappings for biome and block states, these get combined in the shader and allow for more
@@ -32,10 +33,13 @@ public class Mapper {
public static final int UNKNOWN_MAPPING = -1;
public static final int AIR = 0;
private final Object2ObjectOpenHashMap<BlockState, StateEntry> block2stateEntry = new Object2ObjectOpenHashMap<>();
private final Map<BlockState, StateEntry> block2stateEntry = new ConcurrentHashMap<>(2000,0.75f, 10);
private final ObjectArrayList<StateEntry> blockId2stateEntry = new ObjectArrayList<>();
private final Object2ObjectOpenHashMap<String, BiomeEntry> biome2biomeEntry = new Object2ObjectOpenHashMap<>();
private final Map<String, BiomeEntry> biome2biomeEntry = new ConcurrentHashMap<>(2000,0.75f, 10);
private final ObjectArrayList<BiomeEntry> biomeId2biomeEntry = new ObjectArrayList<>();
private Consumer<StateEntry> newStateCallback;
private Consumer<BiomeEntry> newBiomeCallback;
public Mapper(StorageBackend storage) {
this.storage = storage;
//Insert air since its a special entry (index 0)
@@ -46,6 +50,20 @@ public class Mapper {
this.loadFromStorage();
}
public static boolean isTranslucent(long id) {
//Atm hardcode to air
return ((id>>27)&((1<<20)-1)) == 0;
}
public static boolean isAir(long id) {
return ((id>>27)&((1<<20)-1)) == 0;
}
public void setCallbacks(Consumer<StateEntry> stateCallback, Consumer<BiomeEntry> biomeCallback) {
this.newStateCallback = stateCallback;
this.newBiomeCallback = biomeCallback;
}
private void loadFromStorage() {
var mappings = this.storage.getIdMappings();
List<StateEntry> sentries = new ArrayList<>();
@@ -88,7 +106,7 @@ public class Mapper {
private StateEntry registerNewBlockState(BlockState state) {
StateEntry entry = new StateEntry(this.blockId2stateEntry.size(), state);
this.block2stateEntry.put(state, entry);
//this.block2stateEntry.put(state, entry);
this.blockId2stateEntry.add(entry);
byte[] serialized = entry.serialize();
@@ -97,12 +115,14 @@ public class Mapper {
buffer.rewind();
this.storage.putIdMapping(entry.id | (BLOCK_STATE_TYPE<<30), buffer);
MemoryUtil.memFree(buffer);
if (this.newStateCallback!=null)this.newStateCallback.accept(entry);
return entry;
}
private BiomeEntry registerNewBiome(String biome) {
BiomeEntry entry = new BiomeEntry(this.biome2biomeEntry.size(), biome);
this.biome2biomeEntry.put(biome, entry);
//this.biome2biomeEntry.put(biome, entry);
this.biomeId2biomeEntry.add(entry);
byte[] serialized = entry.serialize();
@@ -111,62 +131,54 @@ public class Mapper {
buffer.rewind();
this.storage.putIdMapping(entry.id | (BIOME_TYPE<<30), buffer);
MemoryUtil.memFree(buffer);
if (this.newBiomeCallback!=null)this.newBiomeCallback.accept(entry);
return entry;
}
//TODO:FIXME: IS VERY SLOW NEED TO MAKE IT LOCK FREE
//TODO:FIXME: IS VERY SLOW NEED TO MAKE IT LOCK FREE, or at minimum use a concurrent map
public long getBaseId(byte light, BlockState state, RegistryEntry<Biome> biome) {
StateEntry sentry = null;
BiomeEntry bentry = null;
synchronized (this.block2stateEntry) {
sentry = this.block2stateEntry.get(state);
if (sentry == null) {
sentry = this.registerNewBlockState(state);
}
}
synchronized (this.biome2biomeEntry) {
if (state.isAir()) return ((long)light)<<56;//Special case and fast return for air, dont care about the biome
StateEntry sentry = this.block2stateEntry.computeIfAbsent(state, this::registerNewBlockState);
String biomeId = biome.getKey().get().getValue().toString();
bentry = this.biome2biomeEntry.get(biomeId);
if (bentry == null) {
bentry = this.registerNewBiome(biomeId);
}
}
BiomeEntry bentry = this.biome2biomeEntry.computeIfAbsent(biomeId, this::registerNewBiome);
return (Byte.toUnsignedLong(light)<<56)|(Integer.toUnsignedLong(bentry.id) << 47)|(Integer.toUnsignedLong(sentry.id)<<27);
}
public BlockState[] getBlockStates() {
synchronized (this.block2stateEntry) {
BlockState[] out = new BlockState[this.blockId2stateEntry.size()];
//TODO: fixme: synchronize access to this.blockId2stateEntry
public StateEntry[] getStateEntries() {
var set = new ArrayList<>(this.blockId2stateEntry);
StateEntry[] out = new StateEntry[set.size()];
int i = 0;
for (var entry : this.blockId2stateEntry) {
for (var entry : set) {
if (entry.id != i++) {
throw new IllegalStateException();
}
out[i-1] = entry.state;
out[i-1] = entry;
}
return out;
}
}
public String[] getBiomes() {
synchronized (this.biome2biomeEntry) {
String[] out = new String[this.biome2biomeEntry.size()];
//TODO: fixme: synchronize access to this.biomeId2biomeEntry
public BiomeEntry[] getBiomeEntries() {
var set = new ArrayList<>(this.biomeId2biomeEntry);
BiomeEntry[] out = new BiomeEntry[set.size()];
int i = 0;
for (var entry : this.biomeId2biomeEntry) {
for (var entry : set) {
if (entry.id != i++) {
throw new IllegalStateException();
}
out[i-1] = entry.biome;
out[i-1] = entry;
}
return out;
}
}
public static final class StateEntry {
private final int id;
private final BlockState state;
public final int id;
public final BlockState state;
public StateEntry(int id, BlockState state) {
this.id = id;
this.state = state;
@@ -200,8 +212,8 @@ public class Mapper {
}
public static final class BiomeEntry {
private final int id;
private final String biome;
public final int id;
public final String biome;
public BiomeEntry(int id, String biome) {
this.id = id;

View File

@@ -10,30 +10,31 @@ public class Mipper {
//TODO: mip with respect to all the variables, what that means is take whatever has the highest count and return that
//TODO: also average out the light level and set that as the new light level
//For now just take the most top corner
if (I111 != 0) {
if (!Mapper.isAir(I111)) {
return I111;
}
if (I110 != 0) {
if (!Mapper.isAir(I110)) {
return I110;
}
if (I011 != 0) {
if (!Mapper.isAir(I011)) {
return I011;
}
if (I010 != 0) {
if (!Mapper.isAir(I010)) {
return I010;
}
if (I101 != 0) {
if (!Mapper.isAir(I101)) {
return I101;
}
if (I100 != 0) {
if (!Mapper.isAir(I100)) {
return I100;
}
if (I001 != 0) {
if (!Mapper.isAir(I001)) {
return I001;
}
if (I000 != 0) {
if (!Mapper.isAir(I000)) {
return I000;
}
//TODO: need to account for different light levels of "air"
return 0;
}
}

View File

@@ -44,14 +44,9 @@ public class SectionSavingService {
section.assertNotFree();
section.inSaveQueue.set(false);
//TODO: stop converting between all these types and just use a native buffer all the time
var saveData = SaveLoadSystem.serialize(section);
//Note: this is done like this because else the gc can collect the buffer before the transaction is completed
// thus the transaction reads from undefined memory
ByteBuffer buffer = MemoryUtil.memAlloc(saveData.length);
buffer.put(saveData).rewind();
this.world.storage.setSectionData(section.getKey(), buffer);
MemoryUtil.memFree(buffer);
this.world.storage.setSectionData(section.getKey(), saveData);
MemoryUtil.memFree(saveData);
section.release();
}

View File

@@ -1,28 +1,43 @@
package me.cortex.voxelmon.core.world.service;
import it.unimi.dsi.fastutil.Pair;
import me.cortex.voxelmon.core.voxelization.VoxelizedSection;
import me.cortex.voxelmon.core.voxelization.WorldConversionFactory;
import me.cortex.voxelmon.core.world.WorldEngine;
import net.minecraft.block.Blocks;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.world.LightType;
import net.minecraft.world.chunk.ChunkNibbleArray;
import net.minecraft.world.chunk.ChunkSection;
import net.minecraft.world.chunk.WorldChunk;
import net.minecraft.world.chunk.light.LightStorage;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.Semaphore;
public class VoxelIngestService {
private volatile boolean running = true;
private final Thread worker;
private final Thread[] workers;
private final ConcurrentLinkedDeque<WorldChunk> ingestQueue = new ConcurrentLinkedDeque<>();
private final Semaphore ingestCounter = new Semaphore(0);
private final WorldEngine world;
public VoxelIngestService(WorldEngine world) {
this.worker = new Thread(this::ingestWorker);
this.worker.setDaemon(false);
this.worker.setName("Ingest service");
this.worker.start();
private final ConcurrentHashMap<Long, Pair<ChunkNibbleArray, ChunkNibbleArray>> captureLightMap = new ConcurrentHashMap<>(1000,0.75f, 7);
private final WorldEngine world;
public VoxelIngestService(WorldEngine world, int workers) {
this.world = world;
this.workers = new Thread[workers];
for (int i = 0; i < workers; i++) {
var worker = new Thread(this::ingestWorker);
worker.setDaemon(false);
worker.setName("Ingest service #" + i);
worker.start();
this.workers[i] = worker;
}
}
private void ingestWorker() {
@@ -33,6 +48,7 @@ public class VoxelIngestService {
int i = chunk.getBottomSectionCoord() - 1;
for (var section : chunk.getSectionArray()) {
i++;
var lighting = this.captureLightMap.remove(ChunkSectionPos.from(chunk.getPos(), i).asLong());
if (section.isEmpty()) {
this.world.insertUpdate(VoxelizedSection.createEmpty(chunk.getPos().x, i, chunk.getPos().z));
} else {
@@ -40,7 +56,20 @@ public class VoxelIngestService {
this.world.getMapper(),
section.getBlockStateContainer(),
section.getBiomeContainer(),
(x, y, z) -> (byte) 0,
(x, y, z, state) -> {
if (lighting == null || ((lighting.first() != null && lighting.first().isUninitialized())&&(lighting.second()!=null&&lighting.second().isUninitialized()))) {
return (byte) 0xFF;
} else {
//Lighting is a piece of shit cause its done per face
int block = lighting.first()!=null?Math.min(15,lighting.first().get(x, y, z)):0xF;
int sky = lighting.second()!=null?Math.min(15,lighting.second().get(x, y, z)):0xF;
if (block<state.getLuminance()) {
block = state.getLuminance();
}
sky = 15-sky;//This is cause sky light is inverted which saves memory when saving empty sections
return (byte) (sky|(block<<4));
}
},
chunk.getPos().x,
i,
chunk.getPos().z
@@ -53,6 +82,24 @@ public class VoxelIngestService {
}
public void enqueueIngest(WorldChunk chunk) {
var lp = chunk.getWorld().getLightingProvider();
var blockLight = lp.get(LightType.BLOCK);
var skyLight = lp.get(LightType.SKY);
int i = chunk.getBottomSectionCoord() - 1;
for (var section : chunk.getSectionArray()) {
i++;
if (section == null) continue;
if (section.isEmpty()) continue;
var pos = ChunkSectionPos.from(chunk.getPos(), i);
if (lp.getStatus(LightType.BLOCK, pos) == LightStorage.Status.EMPTY)
continue;
var bl = blockLight.getLightSection(pos);
var sl = skyLight.getLightSection(pos);
if (bl == null && sl == null) continue;
bl = bl==null?null:bl.copy();
sl = sl==null?null:sl.copy();
this.captureLightMap.put(pos.asLong(), Pair.of(bl, sl));
}
this.ingestQueue.add(chunk);
this.ingestCounter.release();
}
@@ -62,10 +109,20 @@ public class VoxelIngestService {
}
public void shutdown() {
if (!this.worker.isAlive()) {
System.err.println("Ingest worker already dead on shutdown! this is very very bad, check log for errors from this thread");
boolean anyAlive = false;
boolean allAlive = true;
for (var worker : this.workers) {
anyAlive |= worker.isAlive();
allAlive &= worker.isAlive();
}
if (!anyAlive) {
System.err.println("Ingest workers already dead on shutdown! this is very very bad, check log for errors from this thread");
return;
}
if (!allAlive) {
System.err.println("Some ingest workers already dead on shutdown! this is very very bad, check log for errors from this thread");
}
//Wait for the ingest to finish
while (this.ingestCounter.availablePermits() != 0) {
@@ -75,6 +132,10 @@ public class VoxelIngestService {
this.running = false;
this.ingestCounter.release(1000);
//Wait for thread to join
try {this.worker.join();} catch (InterruptedException e) {throw new RuntimeException(e);}
try {
for (var worker : this.workers) {
worker.join();
}
} catch (InterruptedException e) {throw new RuntimeException(e);}
}
}

View File

@@ -1,6 +1,7 @@
package me.cortex.voxelmon.core.world.storage;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.lmdb.MDBVal;
import java.io.File;
@@ -80,7 +81,7 @@ public class StorageBackend {
}
//TODO: make batch get and updates
public byte[] getSectionData(long key) {
public ByteBuffer getSectionData(long key) {
return this.synchronizedTransaction(() -> this.sectionDatabase.transaction(MDB_RDONLY, transaction->{
var buff = transaction.stack.malloc(8);
buff.putLong(0, key);
@@ -88,9 +89,9 @@ public class StorageBackend {
if (bb == null) {
return null;
}
var res = new byte[bb.remaining()];
bb.get(res);
return res;
var copy = MemoryUtil.memAlloc(bb.remaining());
MemoryUtil.memCopy(bb, copy);
return copy;
}));
}

View File

@@ -169,7 +169,7 @@ public class WorldImporter {
this.world.getMapper(),
blockStates,
biomes,
(bx, by, bz) -> (byte) 0,
(bx, by, bz, state) -> (byte) 0,
x,
y,
z

View File

@@ -6,9 +6,8 @@ import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
@Mixin(BackgroundRenderer.class)
public class MixinBackgroundRenderer {
@ModifyConstant(method = "applyFog", constant = @Constant(floatValue = 192.0F))
@ModifyConstant(method = "applyFog", constant = @Constant(floatValue = 192.0F), require = 0)
private static float changeFog(float fog) {
return 9999999f;
}

View File

@@ -8,7 +8,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(GameRenderer.class)
public class MixinGameRenderer {
@Inject(method = "getFarPlaneDistance", at = @At("HEAD"), cancellable = true)
@Inject(method = "getFarPlaneDistance", at = @At("HEAD"), cancellable = true, require = 0)
public void method_32796(CallbackInfoReturnable<Float> cir) {
cir.setReturnValue(16 * 3000f);
cir.cancel();

View File

@@ -36,12 +36,12 @@ public abstract class MixinWorldRenderer {
@Redirect(method = "render", at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F"))
@Redirect(method = "render", at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F"), require = 0)
private float redirectMax(float a, float b) {
return a;
}
@Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/GameRenderer;getViewDistance()F"))
@Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/GameRenderer;getViewDistance()F"), require = 0)
private float changeRD(GameRenderer instance) {
float viewDistance = instance.getViewDistance();
return 16*1512;

View File

@@ -0,0 +1,5 @@
layout(binding=0) uniform sampler2D colourTexture;
in vec2 uv;
void main() {
gl_Colour = texture(colourTexture, uv);
}

View File

@@ -0,0 +1,4 @@
void main() {
gl_Position =
}

View File

@@ -66,5 +66,9 @@ layout(binding = 6, std430) readonly restrict buffer BiomeBuffer {
};
layout(binding = 7, std430) readonly restrict buffer LightingBuffer {
vec4 lightData[];
uint lightData[];
};
vec4 getLighting(uint index) {
return vec4((uvec4(lightData[index])>>uvec4(16,8,0,24))&uvec4(0xFF))*vec4(1f/255.0f);
}

View File

@@ -65,6 +65,9 @@ void main() {
uint biomeId = extractBiomeId(quad);
State stateInfo = stateData[stateId];
colour = uint2vec4RGBA(stateInfo.faceColours[face]);
colour *= getLighting(extractLightId(quad));
if (((stateInfo.biomeTintMsk>>face)&1) == 1) {
vec4 biomeColour = uint2vec4RGBA(biomeData[biomeId].foliage);
colour *= biomeColour;

View File

@@ -18,5 +18,6 @@
],
"depends": {
"fabricloader": ">=0.14.22"
}
},
"accessWidener": "voxelmon.accesswidener"
}

View File

@@ -2,3 +2,4 @@ accessWidener v1 named
accessible field net/minecraft/client/texture/SpriteContents image Lnet/minecraft/client/texture/NativeImage;
accessible field net/minecraft/client/render/Frustum frustumIntersection Lorg/joml/FrustumIntersection;
accessible field net/minecraft/client/render/LightmapTextureManager texture Lnet/minecraft/client/texture/NativeImageBackedTexture;

View File

@@ -8,12 +8,10 @@
"minecraft.MixinDebugHud",
"minecraft.MixinGameRenderer",
"minecraft.MixinMinecraftClient",
"minecraft.MixinWorldRenderer"
"minecraft.MixinWorldRenderer",
"joml.AccessFrustumIntersection"
],
"injectors": {
"defaultRequire": 1
},
"mixins": [
"joml.AccessFrustumIntersection"
]
}
}