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 { public class Voxelmon implements ClientModInitializer {
@Override @Override
public void onInitializeClient() { 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.DebugUtil;
import me.cortex.voxelmon.core.util.RingUtil; import me.cortex.voxelmon.core.util.RingUtil;
import me.cortex.voxelmon.core.world.WorldEngine; import me.cortex.voxelmon.core.world.WorldEngine;
import net.minecraft.client.MinecraftClient;
//Can use ring logic //Can use ring logic
// i.e. when a player moves the rings of each lod change (how it was doing in the original attempt) // 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 //NOTE: This is in our render distance units, to convert to chunks at lvl 0 multiply by 2
int DIST = 16;//24; int DIST = 16;//24;
this.rings[0] = new TransitionRing2D(5, DIST, (x,z)->{ this.rings[0] = new TransitionRing2D(5, (int) Math.ceil(MinecraftClient.getInstance().gameRenderer.getViewDistance()/16)/2, (x, z)->{
if (true) { if (false) {
return; return;
} }
for (int y = -2; y < 10; y++) { for (int y = -2; y < 10; y++) {
@@ -97,6 +98,9 @@ public class DistanceTracker {
//Note radius is in shiftScale //Note radius is in shiftScale
private TransitionRing2D(int shiftSize, int radius, Transition2DCallback onEntry, Transition2DCallback onExit) { 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 //trigger just less than every shiftSize scale
this.triggerRangeSquared = 1<<((shiftSize<<1) - 1); this.triggerRangeSquared = 1<<((shiftSize<<1) - 1);
this.shiftSize = shiftSize; this.shiftSize = shiftSize;
@@ -111,15 +115,22 @@ public class DistanceTracker {
} }
public void update(int x, int z) { public void update(int x, int z) {
int dx = this.lastUpdateX - x; int MAX_STEPS_PER_UPDATE = 1;
int dz = this.lastUpdateZ - z;
int distSquared = dx*dx + dz*dz;
long dx = this.lastUpdateX - x;
long dz = this.lastUpdateZ - z;
long distSquared = dx*dx + dz*dz;
if (distSquared < this.triggerRangeSquared) { if (distSquared < this.triggerRangeSquared) {
return; return;
} }
//TODO: fixme: this last update needs to be incremented by a delta since
//Update the last update position //Update the last update position
this.lastUpdateX = x; int maxStep = this.triggerRangeSquared/2;
this.lastUpdateZ = z; 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(); Long2IntOpenHashMap ops = new Long2IntOpenHashMap();
int zcount = MAX_STEPS_PER_UPDATE;
int dir = nz<this.currentZ?-1:1; int dir = nz<this.currentZ?-1:1;
while (nz != this.currentZ) { while (nz != this.currentZ) {
for (int corner : this.cornerPoints) { for (int corner : this.cornerPoints) {
@@ -156,8 +167,11 @@ public class DistanceTracker {
//ops.addTo(Prel(0, -this.radius+Math.min(0, dir)), -dir); //ops.addTo(Prel(0, -this.radius+Math.min(0, dir)), -dir);
this.currentZ += dir; this.currentZ += dir;
if (--zcount == 0) break;
} }
int xcount = MAX_STEPS_PER_UPDATE;
dir = nx<this.currentX?-1:1; dir = nx<this.currentX?-1:1;
while (nx != this.currentX) { while (nx != this.currentX) {
@@ -174,6 +188,8 @@ public class DistanceTracker {
} }
this.currentX += dir; 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.BiomeColour;
import me.cortex.voxelmon.core.world.other.BlockStateColour; import me.cortex.voxelmon.core.world.other.BlockStateColour;
import me.cortex.voxelmon.core.world.other.ColourResolver; import me.cortex.voxelmon.core.world.other.ColourResolver;
import me.cortex.voxelmon.core.world.other.Mapper;
import me.cortex.voxelmon.importers.WorldImporter; import me.cortex.voxelmon.importers.WorldImporter;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
@@ -45,6 +46,18 @@ import static org.lwjgl.opengl.ARBFramebufferObject.glBindFramebuffer;
//There is strict forward only dataflow //There is strict forward only dataflow
//Ingest -> world engine -> raw render data -> render data //Ingest -> world engine -> raw render data -> render data
public class VoxelCore { 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(); public static VoxelCore INSTANCE = new VoxelCore();
private final WorldEngine world; private final WorldEngine world;
@@ -55,12 +68,11 @@ public class VoxelCore {
private final AbstractFarWorldRenderer renderer; private final AbstractFarWorldRenderer renderer;
private final PostProcessing postProcessing; private final PostProcessing postProcessing;
public VoxelCore() { public VoxelCore() {
//Trigger the shared index buffer loading //Trigger the shared index buffer loading
SharedIndexBuffer.INSTANCE.id(); SharedIndexBuffer.INSTANCE.id();
this.renderer = new Gl46FarWorldRenderer(); 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.renderTracker = new RenderTracker(this.world, this.renderer);
this.renderGen = new RenderGenerationService(this.world,4, this.renderTracker::processBuildResult); this.renderGen = new RenderGenerationService(this.world,4, this.renderTracker::processBuildResult);
@@ -71,6 +83,8 @@ public class VoxelCore {
this.postProcessing = new PostProcessing(); this.postProcessing = new PostProcessing();
this.world.getMapper().setCallbacks(this::stateUpdate, this::biomeUpdate);
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown)); Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
@@ -97,39 +111,38 @@ public class VoxelCore {
//WorldImporter importer = new WorldImporter(this.world, MinecraftClient.getInstance().world); //WorldImporter importer = new WorldImporter(this.world, MinecraftClient.getInstance().world);
//importer.importWorldAsyncStart(new File("saves/New World/region")); //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()) {
int tintMsk = 0;
if (biomeTintableAllFaces.contains(state.getBlock())) {
tintMsk |= (1<<6)-1;
}
if (biomeTintableUpFace.contains(state.getBlock())) {
tintMsk |= 1<<Direction.UP.getId();
}
if (waterTint.contains(state.getBlock())) {
tintMsk |= 1<<6;
}
this.renderer.enqueueUpdate(new BlockStateColour(i++, tintMsk, ColourResolver.resolveColour(state)));
} }
i = 0; for (var biome : this.world.getMapper().getBiomeEntries()) {
for (var biome : this.world.getMapper().getBiomes()) { this.biomeUpdate(biome);
long dualColour = ColourResolver.resolveBiomeColour(biome);
this.renderer.enqueueUpdate(new BiomeColour(i++, (int) dualColour, (int) (dualColour>>32)));
} }
} }
private void stateUpdate(Mapper.StateEntry entry) {
var state = entry.state;
int tintMsk = 0;
if (biomeTintableAllFaces.contains(state.getBlock())) {
tintMsk |= (1<<6)-1;
}
if (biomeTintableUpFace.contains(state.getBlock())) {
tintMsk |= 1<<Direction.UP.getId();
}
if (waterTint.contains(state.getBlock())) {
tintMsk |= 1<<6;
}
this.renderer.enqueueUpdate(new BlockStateColour(entry.id, tintMsk, ColourResolver.resolveColour(state)));
}
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) { public void enqueueIngest(WorldChunk worldChunk) {
this.world.ingestService.enqueueIngest(worldChunk); this.world.ingestService.enqueueIngest(worldChunk);
} }
@@ -145,9 +158,9 @@ public class VoxelCore {
DebugUtil.setPositionMatrix(matrices); DebugUtil.setPositionMatrix(matrices);
matrices.pop(); matrices.pop();
int boundFB = GlStateManager.getBoundFramebuffer(); //int boundFB = GlStateManager.getBoundFramebuffer();
this.postProcessing.setSize(MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight); //this.postProcessing.setSize(MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight);
this.postProcessing.bindClearFramebuffer(); //this.postProcessing.bindClearFramebuffer();
//TODO: FIXME: since we just bound the post processing FB the depth information isnt //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 // 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 // this is cause the terrain might not exist and so all the caves are visible causing hell for the
// occlusion culler // occlusion culler
this.renderer.renderFarAwayOpaque(matrices, cameraX, cameraY, cameraZ); this.renderer.renderFarAwayOpaque(matrices, cameraX, cameraY, cameraZ);
glBindFramebuffer(GL_FRAMEBUFFER, boundFB); //glBindFramebuffer(GL_FRAMEBUFFER, boundFB);
this.postProcessing.renderPost(boundFB); // this.postProcessing.renderPost(boundFB);
} }
public void addDebugInfo(List<String> debug) { 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) //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 // could maybe tosomething else
import com.mojang.blaze3d.systems.RenderSystem;
import me.cortex.voxelmon.core.gl.GlBuffer; 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.building.BuiltSectionGeometry;
import me.cortex.voxelmon.core.rendering.util.UploadStream; import me.cortex.voxelmon.core.rendering.util.UploadStream;
import me.cortex.voxelmon.core.world.other.BiomeColour; import me.cortex.voxelmon.core.world.other.BiomeColour;
import me.cortex.voxelmon.core.world.other.BlockStateColour; import me.cortex.voxelmon.core.world.other.BlockStateColour;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.Camera; import net.minecraft.client.render.Camera;
import net.minecraft.client.render.Frustum; import net.minecraft.client.render.Frustum;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import org.joml.FrustumIntersection; import org.joml.FrustumIntersection;
import org.joml.Matrix4f;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentLinkedDeque;
import static org.lwjgl.opengl.ARBMultiDrawIndirect.glMultiDrawElementsIndirect; 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.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, //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 // 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<>(); private final ConcurrentLinkedDeque<BiomeColour> biomeUpdateQueue = new ConcurrentLinkedDeque<>();
protected final GlBuffer stateDataBuffer; protected final GlBuffer stateDataBuffer;
protected final GlBuffer biomeDataBuffer; protected final GlBuffer biomeDataBuffer;
protected final GlBuffer light = null; protected final GlBuffer lightDataBuffer;
//Current camera base level section position //Current camera base level section position
@@ -68,6 +55,7 @@ public abstract class AbstractFarWorldRenderer {
//TODO: make these both dynamically sized //TODO: make these both dynamically sized
this.stateDataBuffer = new GlBuffer((1<<16)*28, 0);//Capacity for 1<<16 entries 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.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(); this.geometry = new GeometryManager();
} }
@@ -80,12 +68,26 @@ public abstract class AbstractFarWorldRenderer {
this.sy = camera.getBlockPos().getY() >> 5; this.sy = camera.getBlockPos().getY() >> 5;
this.sz = camera.getBlockPos().getZ() >> 5; this.sz = camera.getBlockPos().getZ() >> 5;
//TODO: move this to a render function that is only called //TODO: move this to a render function that is only called
// once per frame when using multi viewport mods // once per frame when using multi viewport mods
//it shouldent matter if its called multiple times a frame however, as its synced with fences //it shouldent matter if its called multiple times a frame however, as its synced with fences
UploadStream.INSTANCE.tick(); 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(); this.geometry.uploadResults();
//Upload any block state changes //Upload any block state changes
while (!this.stateUpdateQueue.isEmpty()) { while (!this.stateUpdateQueue.isEmpty()) {
@@ -129,5 +131,6 @@ public abstract class AbstractFarWorldRenderer {
this.uniformBuffer.free(); this.uniformBuffer.free();
this.stateDataBuffer.free(); this.stateDataBuffer.free();
this.biomeDataBuffer.free(); this.biomeDataBuffer.free();
this.lightDataBuffer.free();
} }
} }

View File

@@ -1,21 +1,16 @@
package me.cortex.voxelmon.core.rendering; package me.cortex.voxelmon.core.rendering;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import me.cortex.voxelmon.core.gl.GlBuffer; import me.cortex.voxelmon.core.gl.GlBuffer;
import me.cortex.voxelmon.core.gl.shader.Shader; import me.cortex.voxelmon.core.gl.shader.Shader;
import me.cortex.voxelmon.core.gl.shader.ShaderType; 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.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 me.cortex.voxelmon.mixin.joml.AccessFrustumIntersection;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.RenderLayer; import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Vector3f; import org.joml.Vector3f;
import org.joml.Vector4f;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import java.util.List; 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, 4, this.glVisibilityBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, this.stateDataBuffer.id);//State LUT 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, 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); glBindVertexArray(0);
} }
@@ -80,6 +75,7 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
//RenderSystem.defaultBlendFunc(); //RenderSystem.defaultBlendFunc();
this.updateUniformBuffer(stack, cx, cy, cz); this.updateUniformBuffer(stack, cx, cy, cz);
UploadStream.INSTANCE.commit(); UploadStream.INSTANCE.commit();
glBindVertexArray(this.vao); 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.GlTexture;
import me.cortex.voxelmon.core.gl.shader.Shader; import me.cortex.voxelmon.core.gl.shader.Shader;
import me.cortex.voxelmon.core.gl.shader.ShaderType; import me.cortex.voxelmon.core.gl.shader.ShaderType;
import org.lwjgl.opengl.GL11C;
import static org.lwjgl.opengl.ARBFramebufferObject.*; import static org.lwjgl.opengl.ARBFramebufferObject.*;
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glBindImageTexture; import static org.lwjgl.opengl.ARBShaderImageLoadStore.glBindImageTexture;
@@ -30,6 +31,11 @@ public class PostProcessing {
.add(ShaderType.COMPUTE, "voxelmon:lod/ssao/ssao.comp") .add(ShaderType.COMPUTE, "voxelmon:lod/ssao/ssao.comp")
.compile(); .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() { public PostProcessing() {
this.framebuffer = new GlFramebuffer(); 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 //Executes the post processing and emits to whatever framebuffer is currently bound via a blit
public void renderPost(int outputFb) { public void renderPost(int outputFb) {
this.ssao.bind(); this.ssao.bind();
glBindImageTexture(0, this.colour.id, 0, false,0, GL_READ_WRITE, GL_RGBA8);
glActiveTexture(GL_TEXTURE1); glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, this.depthStencil.id); 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); //glDispatchCompute(this.width/32, this.height/32, 1);
glTextureBarrier(); 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() { public void shutdown() {
@@ -74,5 +82,6 @@ public class PostProcessing {
if (this.colour != null) this.colour.free(); if (this.colour != null) this.colour.free();
if (this.depthStencil != null) this.depthStencil.free(); if (this.depthStencil != null) this.depthStencil.free();
this.ssao.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 z = 0; z < 32; z++) {
for (int x = 0; x < 32; x++) { for (int x = 0; x < 32; x++) {
var self = data[WorldSection.getIndex(x, y, z)]; var self = data[WorldSection.getIndex(x, y, z)];
if (self == Mapper.AIR) { if (Mapper.isAir(self)) {
continue; continue;
} }
long up = -1;
if (y < 31) { if (y < 31) {
var up = data[WorldSection.getIndex(x, y + 1, z)]; up = data[WorldSection.getIndex(x, y + 1, z)];
if (up != Mapper.AIR) { if (!Mapper.isTranslucent(up)) {
continue; continue;
} }
} }
@@ -66,19 +67,19 @@ public class RenderDataFactory {
connectedData = connectedSection.copyData(); connectedData = connectedSection.copyData();
connectedSection.release(); connectedSection.release();
} }
var up = connectedData[WorldSection.getIndex(x, 0, z)]; up = connectedData[WorldSection.getIndex(x, 0, z)];
if (up != Mapper.AIR) { if (!Mapper.isTranslucent(up)) {
continue; continue;
} }
} }
this.mesher.put(x, z, self); this.mesher.put(x, z, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
} }
} }
var quads = this.mesher.process(); var quads = this.mesher.process();
for (int i = 0; i < quads.length; i++) { for (int i = 0; i < quads.length; i++) {
var quad = quads[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; connectedData = null;
@@ -92,12 +93,13 @@ public class RenderDataFactory {
for (int y = 0; y < 32; y++) { for (int y = 0; y < 32; y++) {
for (int z = 0; z < 32; z++) { for (int z = 0; z < 32; z++) {
var self = data[WorldSection.getIndex(x, y, z)]; var self = data[WorldSection.getIndex(x, y, z)];
if (self == Mapper.AIR) { if (Mapper.isAir(self)) {
continue; continue;
} }
long up = -1;
if (x < 31) { if (x < 31) {
var up = data[WorldSection.getIndex(x + 1, y, z)]; up = data[WorldSection.getIndex(x + 1, y, z)];
if (up != Mapper.AIR) { if (!Mapper.isTranslucent(up)) {
continue; continue;
} }
} }
@@ -108,19 +110,19 @@ public class RenderDataFactory {
connectedData = connectedSection.copyData(); connectedData = connectedSection.copyData();
connectedSection.release(); connectedSection.release();
} }
var up = connectedData[WorldSection.getIndex(0, y, z)]; up = connectedData[WorldSection.getIndex(0, y, z)];
if (up != Mapper.AIR) { if (!Mapper.isTranslucent(up)) {
continue; continue;
} }
} }
this.mesher.put(y, z, self); this.mesher.put(y, z, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
} }
} }
var quads = this.mesher.process(); var quads = this.mesher.process();
for (int i = 0; i < quads.length; i++) { for (int i = 0; i < quads.length; i++) {
var quad = quads[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; connectedData = null;
@@ -134,12 +136,13 @@ public class RenderDataFactory {
for (int x = 0; x < 32; x++) { for (int x = 0; x < 32; x++) {
for (int y = 0; y < 32; y++) { for (int y = 0; y < 32; y++) {
var self = data[WorldSection.getIndex(x, y, z)]; var self = data[WorldSection.getIndex(x, y, z)];
if (self == Mapper.AIR) { if (Mapper.isAir(self)) {
continue; continue;
} }
long up = -1;
if (z < 31) { if (z < 31) {
var up = data[WorldSection.getIndex(x, y, z + 1)]; up = data[WorldSection.getIndex(x, y, z + 1)];
if (up != Mapper.AIR) { if (!Mapper.isTranslucent(up)) {
continue; continue;
} }
} }
@@ -150,19 +153,19 @@ public class RenderDataFactory {
connectedData = connectedSection.copyData(); connectedData = connectedSection.copyData();
connectedSection.release(); connectedSection.release();
} }
var up = connectedData[WorldSection.getIndex(x, y, 0)]; up = connectedData[WorldSection.getIndex(x, y, 0)];
if (up != Mapper.AIR) { if (!Mapper.isTranslucent(up)) {
continue; continue;
} }
} }
this.mesher.put(x, y, self); this.mesher.put(x, y, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
} }
} }
var quads = this.mesher.process(); var quads = this.mesher.process();
for (int i = 0; i < quads.length; i++) { for (int i = 0; i < quads.length; i++) {
var quad = quads[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; connectedData = null;
@@ -176,12 +179,13 @@ public class RenderDataFactory {
for (int y = 0; y < 32; y++) { for (int y = 0; y < 32; y++) {
for (int z = 0; z < 32; z++) { for (int z = 0; z < 32; z++) {
var self = data[WorldSection.getIndex(x, y, z)]; var self = data[WorldSection.getIndex(x, y, z)];
if (self == Mapper.AIR) { if (Mapper.isAir(self)) {
continue; continue;
} }
long up = -1;
if (x != 0) { if (x != 0) {
var up = data[WorldSection.getIndex(x - 1, y, z)]; up = data[WorldSection.getIndex(x - 1, y, z)];
if (up != Mapper.AIR) { if (!Mapper.isTranslucent(up)) {
continue; continue;
} }
} }
@@ -192,19 +196,19 @@ public class RenderDataFactory {
connectedData = connectedSection.copyData(); connectedData = connectedSection.copyData();
connectedSection.release(); connectedSection.release();
} }
var up = connectedData[WorldSection.getIndex(31, y, z)]; up = connectedData[WorldSection.getIndex(31, y, z)];
if (up != Mapper.AIR) { if (!Mapper.isTranslucent(up)) {
continue; continue;
} }
} }
this.mesher.put(y, z, self); this.mesher.put(y, z, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
} }
} }
var quads = this.mesher.process(); var quads = this.mesher.process();
for (int i = 0; i < quads.length; i++) { for (int i = 0; i < quads.length; i++) {
var quad = quads[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; connectedData = null;
@@ -218,12 +222,13 @@ public class RenderDataFactory {
for (int x = 0; x < 32; x++) { for (int x = 0; x < 32; x++) {
for (int y = 0; y < 32; y++) { for (int y = 0; y < 32; y++) {
var self = data[WorldSection.getIndex(x, y, z)]; var self = data[WorldSection.getIndex(x, y, z)];
if (self == Mapper.AIR) { if (Mapper.isAir(self)) {
continue; continue;
} }
long up = -1;
if (z != 0) { if (z != 0) {
var up = data[WorldSection.getIndex(x, y, z - 1)]; up = data[WorldSection.getIndex(x, y, z - 1)];
if (up != Mapper.AIR) { if (!Mapper.isTranslucent(up)) {
continue; continue;
} }
} }
@@ -234,19 +239,19 @@ public class RenderDataFactory {
connectedData = connectedSection.copyData(); connectedData = connectedSection.copyData();
connectedSection.release(); connectedSection.release();
} }
var up = connectedData[WorldSection.getIndex(x, y, 31)]; up = connectedData[WorldSection.getIndex(x, y, 31)];
if (up != Mapper.AIR) { if (!Mapper.isTranslucent(up)) {
continue; continue;
} }
} }
this.mesher.put(x, y, self); this.mesher.put(x, y, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
} }
} }
var quads = this.mesher.process(); var quads = this.mesher.process();
for (int i = 0; i < quads.length; i++) { for (int i = 0; i < quads.length; i++) {
var quad = quads[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; connectedData = null;
@@ -260,12 +265,13 @@ public class RenderDataFactory {
for (int x = 0; x < 32; x++) { for (int x = 0; x < 32; x++) {
for (int z = 0; z < 32; z++) { for (int z = 0; z < 32; z++) {
var self = data[WorldSection.getIndex(x, y, z)]; var self = data[WorldSection.getIndex(x, y, z)];
if (self == Mapper.AIR) { if (Mapper.isAir(self)) {
continue; continue;
} }
long up = -1;
if (y != 0) { if (y != 0) {
var up = data[WorldSection.getIndex(x, y - 1, z)]; up = data[WorldSection.getIndex(x, y - 1, z)];
if (up != Mapper.AIR) { if (!Mapper.isTranslucent(up)) {
continue; continue;
} }
} }
@@ -276,19 +282,19 @@ public class RenderDataFactory {
connectedData = connectedSection.copyData(); connectedData = connectedSection.copyData();
connectedSection.release(); connectedSection.release();
} }
var up = connectedData[WorldSection.getIndex(x, 31, z)]; up = connectedData[WorldSection.getIndex(x, 31, z)];
if (up != Mapper.AIR) { if (!Mapper.isTranslucent(up)) {
continue; continue;
} }
} }
this.mesher.put(x, z, self); this.mesher.put(x, z, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
} }
} }
var quads = this.mesher.process(); var quads = this.mesher.process();
for (int i = 0; i < quads.length; i++) { for (int i = 0; i < quads.length; i++) {
var quad = quads[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; 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.WorldEngine;
import me.cortex.voxelmon.core.world.WorldSection; import me.cortex.voxelmon.core.world.WorldSection;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.world.chunk.ChunkNibbleArray;
import net.minecraft.world.chunk.WorldChunk; import net.minecraft.world.chunk.WorldChunk;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -25,6 +26,7 @@ public class RenderGenerationService {
private final Thread[] workers; private final Thread[] workers;
private final Long2ObjectLinkedOpenHashMap<BuildTask> taskQueue = new Long2ObjectLinkedOpenHashMap<>(); private final Long2ObjectLinkedOpenHashMap<BuildTask> taskQueue = new Long2ObjectLinkedOpenHashMap<>();
private final Semaphore taskCounter = new Semaphore(0); private final Semaphore taskCounter = new Semaphore(0);
private final WorldEngine world; private final WorldEngine world;
private final Consumer<BuiltSectionGeometry> resultConsumer; private final Consumer<BuiltSectionGeometry> resultConsumer;
@@ -63,9 +65,14 @@ public class RenderGenerationService {
if (buildFlags != 0) { if (buildFlags != 0) {
var mesh = factory.generateMesh(section, buildFlags); var mesh = factory.generateMesh(section, buildFlags);
this.resultConsumer.accept(mesh.clone()); this.resultConsumer.accept(mesh.clone());
var prevCache = this.renderCache.put(mesh.position, mesh);
if (prevCache != null) { if (false) {
prevCache.free(); var prevCache = this.renderCache.put(mesh.position, mesh);
if (prevCache != null) {
prevCache.free();
}
} else {
mesh.free();
} }
} }
section.release(); section.release();
@@ -106,7 +113,7 @@ public class RenderGenerationService {
this.taskCounter.release(); this.taskCounter.release();
return new BuildTask(()->{ return new BuildTask(()->{
if (checker.check(lvl, x, y, z)) { if (checker.check(lvl, x, y, z)) {
return this.world.acquire(lvl, x, y, z); return this.world.acquireIfExists(lvl, x, y, z);
} else { } else {
return null; return null;
} }

View File

@@ -163,5 +163,13 @@ public class Mesher2D {
this.meshed.clear(); this.meshed.clear();
Arrays.fill(this.data, 0); 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; 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, public static VoxelizedSection convert(Mapper stateMapper,
PalettedContainer<BlockState> blockContainer, PalettedContainer<BlockState> blockContainer,
ReadableContainer<RegistryEntry<Biome>> biomeContainer, ReadableContainer<RegistryEntry<Biome>> biomeContainer,
I3dByteSupplier lightSupplier, ILightingSupplier lightSupplier,
int sx, int sx,
int sy, int sy,
int sz) { int sz) {
@@ -41,9 +42,10 @@ public class WorldConversionFactory {
int y = (oy<<2)|iy; int y = (oy<<2)|iy;
int z = (oz<<2)|iz; int z = (oz<<2)|iz;
var state = blockContainer.get(x, y, z); 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++; 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 { public class ActiveSectionTracker {
//Deserialize into the supplied section, returns true on success, false on failure //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 //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); long key = WorldEngine.getWorldSectionId(lvl, x, y, z);
var cache = this.loadedSectionCache[lvl]; var cache = this.loadedSectionCache[lvl];
VolatileHolder<WorldSection> holder = null; 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 this thread was the one to create the reference then its the thread to load the section
if (isLoader) { if (isLoader) {
var section = new WorldSection(lvl, x, y, z, this); 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 //TODO: Instead if throwing an exception do something better
throw new IllegalStateException("Unable to load section"); throw new IllegalStateException("Unable to load section");
} }
section.acquire(); section.acquire();
holder.obj = section; holder.obj = section;
if (nullOnEmpty && status == 1) {//If its air return null as stated, release the section aswell
section.release();
return null;
}
return section; return section;
} else { } else {
WorldSection section = null; WorldSection section = null;
@@ -59,7 +64,7 @@ public class ActiveSectionTracker {
return section; 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) { 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(); section.acquire();
var section2 = tracker.acquire(0,0,0,0); var section2 = tracker.acquire(0,0,0,0, false);
section.release(); section.release();
section.release(); section.release();
section = tracker.acquire(0,0,0,0); section = tracker.acquire(0,0,0,0, false);
section.release(); section.release();
} }

View File

@@ -1,23 +1,15 @@
package me.cortex.voxelmon.core.world; 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.Long2ShortOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.zstd.Zstd;
import java.nio.ByteBuffer; 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.*; import static org.lwjgl.util.zstd.Zstd.*;
public class SaveLoadSystem { public class SaveLoadSystem {
public static byte[] serialize(WorldSection section) { public static ByteBuffer serialize(WorldSection section) {
var data = section.copyData(); var data = section.copyData();
var compressed = new Short[data.length]; var compressed = new Short[data.length];
Long2ShortOpenHashMap LUT = new Long2ShortOpenHashMap(); Long2ShortOpenHashMap LUT = new Long2ShortOpenHashMap();
@@ -57,24 +49,17 @@ public class SaveLoadSystem {
raw.rewind(); raw.rewind();
ByteBuffer compressedData = MemoryUtil.memAlloc((int)ZSTD_COMPRESSBOUND(raw.remaining())); ByteBuffer compressedData = MemoryUtil.memAlloc((int)ZSTD_COMPRESSBOUND(raw.remaining()));
long compressedSize = ZSTD_compress(compressedData, raw, 15); long compressedSize = ZSTD_compress(compressedData, raw, 15);
byte[] out = new byte[(int) compressedSize];
compressedData.limit((int) compressedSize); compressedData.limit((int) compressedSize);
compressedData.get(out); compressedData.rewind();
MemoryUtil.memFree(raw); MemoryUtil.memFree(raw);
MemoryUtil.memFree(compressedData);
//Compress into a key + data pallet format //Compress into a key + data pallet format
return out; return compressedData;
} }
public static boolean deserialize(WorldSection section, byte[] data) { public static boolean deserialize(WorldSection section, ByteBuffer data) {
var buff = MemoryUtil.memAlloc(data.length);
buff.put(data);
buff.rewind();
var decompressed = MemoryUtil.memAlloc(32*32*32*4*2); var decompressed = MemoryUtil.memAlloc(32*32*32*4*2);
long size = ZSTD_decompress(decompressed, buff); long size = ZSTD_decompress(decompressed, data);
MemoryUtil.memFree(buff);
decompressed.limit((int) size); decompressed.limit((int) size);
long hash = 0; 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.SectionSavingService;
import me.cortex.voxelmon.core.world.service.VoxelIngestService; import me.cortex.voxelmon.core.world.service.VoxelIngestService;
import me.cortex.voxelmon.core.world.storage.StorageBackend; import me.cortex.voxelmon.core.world.storage.StorageBackend;
import org.lwjgl.system.MemoryUtil;
import java.io.File; import java.io.File;
import java.util.Arrays; import java.util.Arrays;
@@ -41,25 +42,36 @@ public class WorldEngine {
this.sectionTracker = new ActiveSectionTracker(maxMipLayers, this::unsafeLoadSection); this.sectionTracker = new ActiveSectionTracker(maxMipLayers, this::unsafeLoadSection);
this.savingService = new SectionSavingService(this, savingServiceWorkers); 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()); var data = this.storage.getSectionData(into.getKey());
if (data != null) { if (data != null) {
if (!SaveLoadSystem.deserialize(into, data)) { try {
this.storage.deleteSectionData(into.getKey()); if (!SaveLoadSystem.deserialize(into, data)) {
//TODO: regenerate the section from children this.storage.deleteSectionData(into.getKey());
Arrays.fill(into.data, Mapper.AIR); //TODO: regenerate the section from children
System.err.println("Section " + into.lvl + ", " + into.x + ", " + into.y + ", " + into.z + " was unable to load, setting to air"); Arrays.fill(into.data, Mapper.AIR);
return true; System.err.println("Section " + into.lvl + ", " + into.x + ", " + into.y + ", " + into.z + " was unable to load, setting to air");
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) { 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 //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.NbtIo;
import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.NbtOps;
import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.stat.Stat;
import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.Biome;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
@@ -17,9 +18,9 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.*;
import java.util.Comparator; import java.util.concurrent.ConcurrentHashMap;
import java.util.List; import java.util.function.Consumer;
//There are independent mappings for biome and block states, these get combined in the shader and allow for more //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 UNKNOWN_MAPPING = -1;
public static final int AIR = 0; 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 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 final ObjectArrayList<BiomeEntry> biomeId2biomeEntry = new ObjectArrayList<>();
private Consumer<StateEntry> newStateCallback;
private Consumer<BiomeEntry> newBiomeCallback;
public Mapper(StorageBackend storage) { public Mapper(StorageBackend storage) {
this.storage = storage; this.storage = storage;
//Insert air since its a special entry (index 0) //Insert air since its a special entry (index 0)
@@ -46,6 +50,20 @@ public class Mapper {
this.loadFromStorage(); 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() { private void loadFromStorage() {
var mappings = this.storage.getIdMappings(); var mappings = this.storage.getIdMappings();
List<StateEntry> sentries = new ArrayList<>(); List<StateEntry> sentries = new ArrayList<>();
@@ -88,7 +106,7 @@ public class Mapper {
private StateEntry registerNewBlockState(BlockState state) { private StateEntry registerNewBlockState(BlockState state) {
StateEntry entry = new StateEntry(this.blockId2stateEntry.size(), state); StateEntry entry = new StateEntry(this.blockId2stateEntry.size(), state);
this.block2stateEntry.put(state, entry); //this.block2stateEntry.put(state, entry);
this.blockId2stateEntry.add(entry); this.blockId2stateEntry.add(entry);
byte[] serialized = entry.serialize(); byte[] serialized = entry.serialize();
@@ -97,12 +115,14 @@ public class Mapper {
buffer.rewind(); buffer.rewind();
this.storage.putIdMapping(entry.id | (BLOCK_STATE_TYPE<<30), buffer); this.storage.putIdMapping(entry.id | (BLOCK_STATE_TYPE<<30), buffer);
MemoryUtil.memFree(buffer); MemoryUtil.memFree(buffer);
if (this.newStateCallback!=null)this.newStateCallback.accept(entry);
return entry; return entry;
} }
private BiomeEntry registerNewBiome(String biome) { private BiomeEntry registerNewBiome(String biome) {
BiomeEntry entry = new BiomeEntry(this.biome2biomeEntry.size(), biome); BiomeEntry entry = new BiomeEntry(this.biome2biomeEntry.size(), biome);
this.biome2biomeEntry.put(biome, entry); //this.biome2biomeEntry.put(biome, entry);
this.biomeId2biomeEntry.add(entry); this.biomeId2biomeEntry.add(entry);
byte[] serialized = entry.serialize(); byte[] serialized = entry.serialize();
@@ -111,62 +131,54 @@ public class Mapper {
buffer.rewind(); buffer.rewind();
this.storage.putIdMapping(entry.id | (BIOME_TYPE<<30), buffer); this.storage.putIdMapping(entry.id | (BIOME_TYPE<<30), buffer);
MemoryUtil.memFree(buffer); MemoryUtil.memFree(buffer);
if (this.newBiomeCallback!=null)this.newBiomeCallback.accept(entry);
return 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) { public long getBaseId(byte light, BlockState state, RegistryEntry<Biome> biome) {
StateEntry sentry = null; if (state.isAir()) return ((long)light)<<56;//Special case and fast return for air, dont care about the biome
BiomeEntry bentry = null; StateEntry sentry = this.block2stateEntry.computeIfAbsent(state, this::registerNewBlockState);
synchronized (this.block2stateEntry) {
sentry = this.block2stateEntry.get(state); String biomeId = biome.getKey().get().getValue().toString();
if (sentry == null) { BiomeEntry bentry = this.biome2biomeEntry.computeIfAbsent(biomeId, this::registerNewBiome);
sentry = this.registerNewBlockState(state);
}
}
synchronized (this.biome2biomeEntry) {
String biomeId = biome.getKey().get().getValue().toString();
bentry = this.biome2biomeEntry.get(biomeId);
if (bentry == null) {
bentry = this.registerNewBiome(biomeId);
}
}
return (Byte.toUnsignedLong(light)<<56)|(Integer.toUnsignedLong(bentry.id) << 47)|(Integer.toUnsignedLong(sentry.id)<<27); return (Byte.toUnsignedLong(light)<<56)|(Integer.toUnsignedLong(bentry.id) << 47)|(Integer.toUnsignedLong(sentry.id)<<27);
} }
public BlockState[] getBlockStates() { //TODO: fixme: synchronize access to this.blockId2stateEntry
synchronized (this.block2stateEntry) { public StateEntry[] getStateEntries() {
BlockState[] out = new BlockState[this.blockId2stateEntry.size()]; var set = new ArrayList<>(this.blockId2stateEntry);
int i = 0; StateEntry[] out = new StateEntry[set.size()];
for (var entry : this.blockId2stateEntry) { int i = 0;
if (entry.id != i++) { for (var entry : set) {
throw new IllegalStateException(); if (entry.id != i++) {
} throw new IllegalStateException();
out[i-1] = entry.state;
} }
return out; out[i-1] = entry;
} }
return out;
} }
public String[] getBiomes() { //TODO: fixme: synchronize access to this.biomeId2biomeEntry
synchronized (this.biome2biomeEntry) { public BiomeEntry[] getBiomeEntries() {
String[] out = new String[this.biome2biomeEntry.size()]; var set = new ArrayList<>(this.biomeId2biomeEntry);
int i = 0; BiomeEntry[] out = new BiomeEntry[set.size()];
for (var entry : this.biomeId2biomeEntry) { int i = 0;
if (entry.id != i++) { for (var entry : set) {
throw new IllegalStateException(); if (entry.id != i++) {
} throw new IllegalStateException();
out[i-1] = entry.biome;
} }
return out; out[i-1] = entry;
} }
return out;
} }
public static final class StateEntry { public static final class StateEntry {
private final int id; public final int id;
private final BlockState state; public final BlockState state;
public StateEntry(int id, BlockState state) { public StateEntry(int id, BlockState state) {
this.id = id; this.id = id;
this.state = state; this.state = state;
@@ -200,8 +212,8 @@ public class Mapper {
} }
public static final class BiomeEntry { public static final class BiomeEntry {
private final int id; public final int id;
private final String biome; public final String biome;
public BiomeEntry(int id, String biome) { public BiomeEntry(int id, String biome) {
this.id = id; 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: 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 //TODO: also average out the light level and set that as the new light level
//For now just take the most top corner //For now just take the most top corner
if (I111 != 0) { if (!Mapper.isAir(I111)) {
return I111; return I111;
} }
if (I110 != 0) { if (!Mapper.isAir(I110)) {
return I110; return I110;
} }
if (I011 != 0) { if (!Mapper.isAir(I011)) {
return I011; return I011;
} }
if (I010 != 0) { if (!Mapper.isAir(I010)) {
return I010; return I010;
} }
if (I101 != 0) { if (!Mapper.isAir(I101)) {
return I101; return I101;
} }
if (I100 != 0) { if (!Mapper.isAir(I100)) {
return I100; return I100;
} }
if (I001 != 0) { if (!Mapper.isAir(I001)) {
return I001; return I001;
} }
if (I000 != 0) { if (!Mapper.isAir(I000)) {
return I000; return I000;
} }
//TODO: need to account for different light levels of "air"
return 0; return 0;
} }
} }

View File

@@ -44,14 +44,9 @@ public class SectionSavingService {
section.assertNotFree(); section.assertNotFree();
section.inSaveQueue.set(false); 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); var saveData = SaveLoadSystem.serialize(section);
//Note: this is done like this because else the gc can collect the buffer before the transaction is completed this.world.storage.setSectionData(section.getKey(), saveData);
// thus the transaction reads from undefined memory MemoryUtil.memFree(saveData);
ByteBuffer buffer = MemoryUtil.memAlloc(saveData.length);
buffer.put(saveData).rewind();
this.world.storage.setSectionData(section.getKey(), buffer);
MemoryUtil.memFree(buffer);
section.release(); section.release();
} }

View File

@@ -1,28 +1,43 @@
package me.cortex.voxelmon.core.world.service; 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.VoxelizedSection;
import me.cortex.voxelmon.core.voxelization.WorldConversionFactory; import me.cortex.voxelmon.core.voxelization.WorldConversionFactory;
import me.cortex.voxelmon.core.world.WorldEngine; 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.WorldChunk;
import net.minecraft.world.chunk.light.LightStorage;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
public class VoxelIngestService { public class VoxelIngestService {
private volatile boolean running = true; private volatile boolean running = true;
private final Thread worker; private final Thread[] workers;
private final ConcurrentLinkedDeque<WorldChunk> ingestQueue = new ConcurrentLinkedDeque<>(); private final ConcurrentLinkedDeque<WorldChunk> ingestQueue = new ConcurrentLinkedDeque<>();
private final Semaphore ingestCounter = new Semaphore(0); private final Semaphore ingestCounter = new Semaphore(0);
private final WorldEngine world; private final ConcurrentHashMap<Long, Pair<ChunkNibbleArray, ChunkNibbleArray>> captureLightMap = new ConcurrentHashMap<>(1000,0.75f, 7);
public VoxelIngestService(WorldEngine world) {
this.worker = new Thread(this::ingestWorker);
this.worker.setDaemon(false);
this.worker.setName("Ingest service");
this.worker.start();
private final WorldEngine world;
public VoxelIngestService(WorldEngine world, int workers) {
this.world = world; 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() { private void ingestWorker() {
@@ -33,6 +48,7 @@ public class VoxelIngestService {
int i = chunk.getBottomSectionCoord() - 1; int i = chunk.getBottomSectionCoord() - 1;
for (var section : chunk.getSectionArray()) { for (var section : chunk.getSectionArray()) {
i++; i++;
var lighting = this.captureLightMap.remove(ChunkSectionPos.from(chunk.getPos(), i).asLong());
if (section.isEmpty()) { if (section.isEmpty()) {
this.world.insertUpdate(VoxelizedSection.createEmpty(chunk.getPos().x, i, chunk.getPos().z)); this.world.insertUpdate(VoxelizedSection.createEmpty(chunk.getPos().x, i, chunk.getPos().z));
} else { } else {
@@ -40,7 +56,20 @@ public class VoxelIngestService {
this.world.getMapper(), this.world.getMapper(),
section.getBlockStateContainer(), section.getBlockStateContainer(),
section.getBiomeContainer(), 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, chunk.getPos().x,
i, i,
chunk.getPos().z chunk.getPos().z
@@ -53,6 +82,24 @@ public class VoxelIngestService {
} }
public void enqueueIngest(WorldChunk chunk) { 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.ingestQueue.add(chunk);
this.ingestCounter.release(); this.ingestCounter.release();
} }
@@ -62,10 +109,20 @@ public class VoxelIngestService {
} }
public void shutdown() { public void shutdown() {
if (!this.worker.isAlive()) { boolean anyAlive = false;
System.err.println("Ingest worker already dead on shutdown! this is very very bad, check log for errors from this thread"); 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; 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 //Wait for the ingest to finish
while (this.ingestCounter.availablePermits() != 0) { while (this.ingestCounter.availablePermits() != 0) {
@@ -75,6 +132,10 @@ public class VoxelIngestService {
this.running = false; this.running = false;
this.ingestCounter.release(1000); this.ingestCounter.release(1000);
//Wait for thread to join //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; package me.cortex.voxelmon.core.world.storage;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.lmdb.MDBVal; import org.lwjgl.util.lmdb.MDBVal;
import java.io.File; import java.io.File;
@@ -80,7 +81,7 @@ public class StorageBackend {
} }
//TODO: make batch get and updates //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->{ return this.synchronizedTransaction(() -> this.sectionDatabase.transaction(MDB_RDONLY, transaction->{
var buff = transaction.stack.malloc(8); var buff = transaction.stack.malloc(8);
buff.putLong(0, key); buff.putLong(0, key);
@@ -88,9 +89,9 @@ public class StorageBackend {
if (bb == null) { if (bb == null) {
return null; return null;
} }
var res = new byte[bb.remaining()]; var copy = MemoryUtil.memAlloc(bb.remaining());
bb.get(res); MemoryUtil.memCopy(bb, copy);
return res; return copy;
})); }));
} }

View File

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

View File

@@ -6,9 +6,8 @@ import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.ModifyConstant; import org.spongepowered.asm.mixin.injection.ModifyConstant;
@Mixin(BackgroundRenderer.class) @Mixin(BackgroundRenderer.class)
public class MixinBackgroundRenderer { 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) { private static float changeFog(float fog) {
return 9999999f; return 9999999f;
} }

View File

@@ -8,7 +8,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(GameRenderer.class) @Mixin(GameRenderer.class)
public class MixinGameRenderer { 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) { public void method_32796(CallbackInfoReturnable<Float> cir) {
cir.setReturnValue(16 * 3000f); cir.setReturnValue(16 * 3000f);
cir.cancel(); 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) { private float redirectMax(float a, float b) {
return a; 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) { private float changeRD(GameRenderer instance) {
float viewDistance = instance.getViewDistance(); float viewDistance = instance.getViewDistance();
return 16*1512; 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 { 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); uint biomeId = extractBiomeId(quad);
State stateInfo = stateData[stateId]; State stateInfo = stateData[stateId];
colour = uint2vec4RGBA(stateInfo.faceColours[face]); colour = uint2vec4RGBA(stateInfo.faceColours[face]);
colour *= getLighting(extractLightId(quad));
if (((stateInfo.biomeTintMsk>>face)&1) == 1) { if (((stateInfo.biomeTintMsk>>face)&1) == 1) {
vec4 biomeColour = uint2vec4RGBA(biomeData[biomeId].foliage); vec4 biomeColour = uint2vec4RGBA(biomeData[biomeId].foliage);
colour *= biomeColour; colour *= biomeColour;

View File

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

View File

@@ -1,4 +1,5 @@
accessWidener v1 named accessWidener v1 named
accessible field net/minecraft/client/texture/SpriteContents image Lnet/minecraft/client/texture/NativeImage; 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/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.MixinDebugHud",
"minecraft.MixinGameRenderer", "minecraft.MixinGameRenderer",
"minecraft.MixinMinecraftClient", "minecraft.MixinMinecraftClient",
"minecraft.MixinWorldRenderer" "minecraft.MixinWorldRenderer",
"joml.AccessFrustumIntersection"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1
}, }
"mixins": [
"joml.AccessFrustumIntersection"
]
} }