Alot of work
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package me.cortex.voxelmon.core.voxelization;
|
||||
|
||||
public interface I3dByteSupplier {
|
||||
byte supply(int x, int y, int z);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
layout(binding=0) uniform sampler2D colourTexture;
|
||||
in vec2 uv;
|
||||
void main() {
|
||||
gl_Colour = texture(colourTexture, uv);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
void main() {
|
||||
gl_Position =
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -18,5 +18,6 @@
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.14.22"
|
||||
}
|
||||
},
|
||||
"accessWidener": "voxelmon.accesswidener"
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -8,12 +8,10 @@
|
||||
"minecraft.MixinDebugHud",
|
||||
"minecraft.MixinGameRenderer",
|
||||
"minecraft.MixinMinecraftClient",
|
||||
"minecraft.MixinWorldRenderer"
|
||||
"minecraft.MixinWorldRenderer",
|
||||
"joml.AccessFrustumIntersection"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
},
|
||||
"mixins": [
|
||||
"joml.AccessFrustumIntersection"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user