Merge branch 'quad-format-rewrite'
This commit is contained in:
@@ -46,7 +46,9 @@ dependencies {
|
||||
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||
|
||||
modImplementation "maven.modrinth:sodium:mc1.20.3-0.5.5"
|
||||
//TODO: this is to eventually not need sodium installed as atm its just used for parsing shaders
|
||||
modRuntimeOnly "maven.modrinth:sodium:mc1.20.3-0.5.5"
|
||||
modCompileOnly "maven.modrinth:sodium:mc1.20.3-0.5.5"
|
||||
|
||||
modImplementation("maven.modrinth:cloth-config:13.0.121+fabric")
|
||||
modImplementation("maven.modrinth:modmenu:9.0.0")
|
||||
|
||||
@@ -8,7 +8,7 @@ yarn_mappings=1.20.4+build.1
|
||||
loader_version=0.15.0
|
||||
|
||||
# Mod Properties
|
||||
mod_version = 0.0.2-alpha
|
||||
mod_version = 0.0.3-alpha
|
||||
maven_group = me.cortex
|
||||
archives_base_name = zenith
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ public class ZenithConfig {
|
||||
|
||||
public boolean enabled = true;
|
||||
public int qualityScale = 20;
|
||||
public int maxSections = 200_000;
|
||||
public int geometryBufferSize = (1<<30)/8;
|
||||
public int ingestThreads = 2;
|
||||
public int savingThreads = 10;
|
||||
public int renderThreads = 5;
|
||||
|
||||
@@ -25,7 +25,7 @@ public class ZenithConfigScreenFactory implements ModMenuApi {
|
||||
private static Screen buildConfigScreen(Screen parent, ZenithConfig config) {
|
||||
ConfigBuilder builder = ConfigBuilder.create()
|
||||
.setParentScreen(parent)
|
||||
.setTitle(Text.translatable("title.zenith.config"));
|
||||
.setTitle(Text.translatable("zenith.config.title"));
|
||||
|
||||
|
||||
addGeneralCategory(builder, config);
|
||||
@@ -61,6 +61,18 @@ public class ZenithConfigScreenFactory implements ModMenuApi {
|
||||
.setDefaultValue(DEFAULT.qualityScale)
|
||||
.build());
|
||||
|
||||
category.addEntry(entryBuilder.startIntSlider(Text.translatable("zenith.config.general.geometryBuffer"), config.geometryBufferSize, (1<<27)/8, ((1<<31)-1)/8)
|
||||
.setTooltip(Text.translatable("zenith.config.general.geometryBuffer.tooltip"))
|
||||
.setSaveConsumer(val -> config.geometryBufferSize = val)
|
||||
.setDefaultValue(DEFAULT.geometryBufferSize)
|
||||
.build());
|
||||
|
||||
category.addEntry(entryBuilder.startIntSlider(Text.translatable("zenith.config.general.maxSections"), config.maxSections, 100_000, 400_000)
|
||||
.setTooltip(Text.translatable("zenith.config.general.maxSections.tooltip"))
|
||||
.setSaveConsumer(val -> config.maxSections = val)
|
||||
.setDefaultValue(DEFAULT.maxSections)
|
||||
.build());
|
||||
|
||||
category.addEntry(entryBuilder.startIntSlider(Text.translatable("zenith.config.general.compression"), config.savingCompressionLevel, 1, 21)
|
||||
.setTooltip(Text.translatable("zenith.config.general.compression.tooltip"))
|
||||
.setSaveConsumer(val -> config.savingCompressionLevel = val)
|
||||
|
||||
@@ -28,15 +28,17 @@ public class DistanceTracker {
|
||||
this.minYSection = MinecraftClient.getInstance().world.getBottomSectionCoord()/2;
|
||||
this.maxYSection = MinecraftClient.getInstance().world.getTopSectionCoord()/2;
|
||||
|
||||
this.rings[0] = new TransitionRing2D(5, MinecraftClient.getInstance().options.getViewDistance().getValue()/2, (x, z)->{
|
||||
for (int y = this.minYSection; y <= this.maxYSection; y++) {
|
||||
this.tracker.remLvl0(x, y, z);
|
||||
}
|
||||
}, (x, z) -> {
|
||||
for (int y = this.minYSection; y <= this.maxYSection; y++) {
|
||||
this.tracker.addLvl0(x, y, z);
|
||||
}
|
||||
});
|
||||
if (true) {
|
||||
this.rings[0] = new TransitionRing2D(5, MinecraftClient.getInstance().options.getViewDistance().getValue() / 2, (x, z) -> {
|
||||
for (int y = this.minYSection; y <= this.maxYSection; y++) {
|
||||
this.tracker.remLvl0(x, y, z);
|
||||
}
|
||||
}, (x, z) -> {
|
||||
for (int y = this.minYSection; y <= this.maxYSection; y++) {
|
||||
this.tracker.addLvl0(x, y, z);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//The rings 0+ start at 64 vanilla rd, no matter what the game is set at, that is if the game is set to 32 rd
|
||||
// there will still be 32 chunks untill the first lod drop
|
||||
|
||||
@@ -5,10 +5,6 @@ import me.cortex.zenith.client.core.rendering.*;
|
||||
import me.cortex.zenith.client.core.rendering.building.RenderGenerationService;
|
||||
import me.cortex.zenith.client.core.util.DebugUtil;
|
||||
import me.cortex.zenith.common.world.WorldEngine;
|
||||
import me.cortex.zenith.client.core.other.BiomeColour;
|
||||
import me.cortex.zenith.client.core.other.BlockStateColour;
|
||||
import me.cortex.zenith.client.core.other.ColourResolver;
|
||||
import me.cortex.zenith.common.world.other.Mapper;
|
||||
import me.cortex.zenith.client.importers.WorldImporter;
|
||||
import me.cortex.zenith.common.world.storage.FragmentedStorageBackendAdaptor;
|
||||
import net.minecraft.block.Block;
|
||||
@@ -16,7 +12,6 @@ import net.minecraft.block.Blocks;
|
||||
import net.minecraft.client.render.Camera;
|
||||
import net.minecraft.client.render.Frustum;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
|
||||
@@ -38,18 +33,6 @@ import java.util.*;
|
||||
//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.SHORT_GRASS,
|
||||
|
||||
Blocks.SPRUCE_LEAVES,
|
||||
Blocks.BIRCH_LEAVES,
|
||||
Blocks.PINK_PETALS,
|
||||
Blocks.FERN, 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));
|
||||
|
||||
|
||||
private final WorldEngine world;
|
||||
private final DistanceTracker distanceTracker;
|
||||
private final RenderGenerationService renderGen;
|
||||
@@ -65,13 +48,13 @@ public class VoxelCore {
|
||||
|
||||
//Trigger the shared index buffer loading
|
||||
SharedIndexBuffer.INSTANCE.id();
|
||||
this.renderer = new Gl46FarWorldRenderer();
|
||||
this.renderer = new Gl46FarWorldRenderer(ZenithConfig.CONFIG.geometryBufferSize, ZenithConfig.CONFIG.maxSections);
|
||||
System.out.println("Renderer initialized");
|
||||
this.world = new WorldEngine(new FragmentedStorageBackendAdaptor(new File(ZenithConfig.CONFIG.storagePath)), ZenithConfig.CONFIG.ingestThreads, ZenithConfig.CONFIG.savingThreads, ZenithConfig.CONFIG.savingCompressionLevel, 5);//"storagefile.db"//"ethoslab.db"
|
||||
System.out.println("World engine");
|
||||
|
||||
this.renderTracker = new RenderTracker(this.world, this.renderer);
|
||||
this.renderGen = new RenderGenerationService(this.world,ZenithConfig.CONFIG.renderThreads, this.renderTracker::processBuildResult);
|
||||
this.renderGen = new RenderGenerationService(this.world, this.renderer.getModelManager(), ZenithConfig.CONFIG.renderThreads, this.renderTracker::processBuildResult);
|
||||
this.world.setDirtyCallback(this.renderTracker::sectionUpdated);
|
||||
this.renderTracker.setRenderGen(this.renderGen);
|
||||
System.out.println("Render tracker and generator initialized");
|
||||
@@ -82,39 +65,21 @@ public class VoxelCore {
|
||||
|
||||
this.postProcessing = null;//new PostProcessing();
|
||||
|
||||
this.world.getMapper().setCallbacks(this::stateUpdate, this::biomeUpdate);
|
||||
this.world.getMapper().setCallbacks(this.renderer::addBlockState, a->{});
|
||||
|
||||
|
||||
////Resave the db incase it failed a recovery
|
||||
//this.world.getMapper().forceResaveStates();
|
||||
|
||||
|
||||
for (var state : this.world.getMapper().getStateEntries()) {
|
||||
this.stateUpdate(state);
|
||||
this.renderer.getModelManager().addEntry(state.id, state.state);
|
||||
}
|
||||
|
||||
for (var biome : this.world.getMapper().getBiomeEntries()) {
|
||||
this.biomeUpdate(biome);
|
||||
}
|
||||
System.out.println("Entry updates applied");
|
||||
//this.renderer.getModelManager().updateEntry(0, Blocks.GRASS_BLOCK.getDefaultState());
|
||||
|
||||
System.out.println("Voxel core initialized");
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -126,6 +91,7 @@ public class VoxelCore {
|
||||
if (this.firstTime) {
|
||||
this.distanceTracker.init(camera.getBlockPos().getX(), camera.getBlockPos().getZ());
|
||||
this.firstTime = false;
|
||||
//this.renderTracker.addLvl0(0,6,0);
|
||||
}
|
||||
this.distanceTracker.setCenter(camera.getBlockPos().getX(), camera.getBlockPos().getY(), camera.getBlockPos().getZ());
|
||||
this.renderer.setupRender(frustum, camera);
|
||||
@@ -136,6 +102,10 @@ public class VoxelCore {
|
||||
matrices.translate(-cameraX, -cameraY, -cameraZ);
|
||||
DebugUtil.setPositionMatrix(matrices);
|
||||
matrices.pop();
|
||||
//this.renderer.getModelManager().updateEntry(0, Blocks.DIRT_PATH.getDefaultState());
|
||||
|
||||
//this.renderer.getModelManager().updateEntry(0, Blocks.COMPARATOR.getDefaultState());
|
||||
//this.renderer.getModelManager().updateEntry(0, Blocks.OAK_LEAVES.getDefaultState());
|
||||
|
||||
//int boundFB = GlStateManager.getBoundFramebuffer();
|
||||
//this.postProcessing.setSize(MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight);
|
||||
@@ -149,11 +119,15 @@ public class VoxelCore {
|
||||
//TODO: have the renderer also render a bounding full face just like black boarders around lvl 0
|
||||
// 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);
|
||||
|
||||
//We can render the translucent directly after as it is the furthest translucent objects
|
||||
this.renderer.renderFarAwayTranslucent();
|
||||
}
|
||||
|
||||
public void addDebugInfo(List<String> debug) {
|
||||
|
||||
@@ -10,6 +10,11 @@ import static org.lwjgl.opengl.GL45C.glNamedBufferStorage;
|
||||
public class GlBuffer extends TrackedObject {
|
||||
public final int id;
|
||||
private final long size;
|
||||
|
||||
public GlBuffer(long size) {
|
||||
this(size, 0);
|
||||
}
|
||||
|
||||
public GlBuffer(long size, int flags) {
|
||||
this.id = glCreateBuffers();
|
||||
this.size = size;
|
||||
|
||||
@@ -21,10 +21,11 @@ public class GlFramebuffer extends TrackedObject {
|
||||
glDeleteFramebuffers(this.id);
|
||||
}
|
||||
|
||||
public void verify() {
|
||||
public GlFramebuffer verify() {
|
||||
int code;
|
||||
if ((code = glCheckNamedFramebufferStatus(this.id, GL_FRAMEBUFFER)) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
throw new IllegalStateException("Framebuffer incomplete with error code: " + code);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package me.cortex.zenith.client.core.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public record ColourDepthTextureData(int[] colour, int[] depth, int width, int height) {
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
var other = ((ColourDepthTextureData)obj);
|
||||
return Arrays.equals(other.colour, this.colour) && Arrays.equals(other.depth, this.depth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (this.width * 312337173 * (Arrays.hashCode(this.colour) ^ Arrays.hashCode(this.depth))) ^ this.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColourDepthTextureData clone() {
|
||||
return new ColourDepthTextureData(Arrays.copyOf(this.colour, this.colour.length), Arrays.copyOf(this.depth, this.depth.length), this.width, this.height);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,523 @@
|
||||
package me.cortex.zenith.client.core.model;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlConst;
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import me.cortex.zenith.client.core.gl.GlBuffer;
|
||||
import me.cortex.zenith.client.core.gl.GlTexture;
|
||||
import me.cortex.zenith.client.core.rendering.util.UploadStream;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.FluidBlock;
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.color.block.BlockColorProvider;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.client.render.RenderLayers;
|
||||
import net.minecraft.fluid.FluidState;
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.registry.RegistryKeys;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.world.BlockRenderView;
|
||||
import net.minecraft.world.LightType;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.biome.BiomeKeys;
|
||||
import net.minecraft.world.biome.ColorResolver;
|
||||
import net.minecraft.world.chunk.light.LightingProvider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
|
||||
import static org.lwjgl.opengl.GL11C.GL_NEAREST_MIPMAP_LINEAR;
|
||||
import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MAX_LOD;
|
||||
import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MIN_LOD;
|
||||
import static org.lwjgl.opengl.GL33.glDeleteSamplers;
|
||||
import static org.lwjgl.opengl.GL33.glGenSamplers;
|
||||
import static org.lwjgl.opengl.GL33C.glSamplerParameteri;
|
||||
import static org.lwjgl.opengl.GL45C.glTextureSubImage2D;
|
||||
|
||||
//Manages the storage and updating of model states, textures and colours
|
||||
|
||||
//Also has a fast long[] based metadata lookup for when the terrain mesher needs to look up the face occlusion data
|
||||
|
||||
//TODO: support more than 65535 states, what should actually happen is a blockstate is registered, the model data is generated, then compared
|
||||
// to all other models already loaded, if it is a duplicate, create a mapping from the id to the already loaded id, this will help with meshing aswell
|
||||
// as leaves and such will be able to be merged
|
||||
public class ModelManager {
|
||||
public static final int MODEL_SIZE = 64;
|
||||
private final ModelTextureBakery bakery;
|
||||
private final GlBuffer modelBuffer;
|
||||
private final GlTexture textures;
|
||||
private final int blockSampler = glGenSamplers();
|
||||
|
||||
private final int modelTextureSize;
|
||||
|
||||
//Model data might also contain a constant colour if the colour resolver produces a constant colour, this saves space in the
|
||||
// section buffer reverse indexing
|
||||
|
||||
//model data also contains if a face should be randomly rotated,flipped etc to get rid of moire effect
|
||||
// this would be done in the fragment shader
|
||||
|
||||
//The Meta-cache contains critical information needed for meshing, colour provider bit, per-face = is empty, has alpha, is solid, full width, full height
|
||||
// alpha means that some pixels have alpha values and belong in the translucent rendering layer,
|
||||
// is empty means that the face is air/shouldent be rendered as there is nothing there
|
||||
// is solid means that every pixel is fully opaque
|
||||
// full width, height, is if the blockmodel dimentions occupy a full block, e.g. comparator, some faces do some dont and some only in a specific axis
|
||||
|
||||
//FIXME: the issue is e.g. leaves are translucent but the alpha value is used to colour the leaves, so a block can have alpha but still be only made up of transparent or opaque pixels
|
||||
// will need to find a way to send this info to the shader via the material, if it is in the opaque phase render as transparent with blending shiz
|
||||
|
||||
//TODO: ADD an occlusion mask that can be queried (16x16 pixels takes up 4 longs) this mask shows what pixels are exactly occluded at the edge of the block
|
||||
// so that full block occlusion can work nicely
|
||||
|
||||
|
||||
//TODO: what might work maybe, is that all the transparent pixels should be set to the average of the other pixels
|
||||
// that way the block is always "fully occluding" (if the block model doesnt cover the entire thing), maybe
|
||||
// this has some issues with quad merging
|
||||
//TODO: ACTUALLY, full out all the transparent pixels that are _within_ the bounding box of the model
|
||||
// this will mean that when quad merging and rendering, the transparent pixels of the block where there shouldent be
|
||||
// might still work???
|
||||
|
||||
// this has an issue with scaffolding i believe tho, so maybe make it a probability to render??? idk
|
||||
private final long[] metadataCache;
|
||||
|
||||
//Provides a map from id -> model id as multiple ids might have the same internal model id
|
||||
private final int[] idMappings;
|
||||
private final Object2IntOpenHashMap<List<ColourDepthTextureData>> modelTexture2id = new Object2IntOpenHashMap<>();
|
||||
|
||||
public ModelManager(int modelTextureSize) {
|
||||
this.modelTextureSize = modelTextureSize;
|
||||
this.bakery = new ModelTextureBakery(modelTextureSize, modelTextureSize);
|
||||
this.modelBuffer = new GlBuffer(MODEL_SIZE * (1<<16));
|
||||
//TODO: figure out how to do mipping :blobfox_pineapple:
|
||||
this.textures = new GlTexture().store(GL_RGBA8, 1, modelTextureSize*3*256,modelTextureSize*2*256);
|
||||
this.metadataCache = new long[1<<16];
|
||||
this.idMappings = new int[1<<20];//Max of 1 million blockstates mapping to 65k model states
|
||||
Arrays.fill(this.idMappings, -1);
|
||||
|
||||
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_LOD, 0);
|
||||
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAX_LOD, 4);
|
||||
|
||||
this.modelTexture2id.defaultReturnValue(-1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//TODO: what i need to do is seperate out fluid states from blockStates
|
||||
|
||||
|
||||
//TODO: so need a few things, per face sizes and offsets, the sizes should be computed from the pixels and find the minimum bounding pixel
|
||||
// while the depth is computed from the depth buffer data
|
||||
public int addEntry(int blockId, BlockState blockState) {
|
||||
if (this.idMappings[blockId] != -1) {
|
||||
throw new IllegalArgumentException("Trying to add entry for duplicate id");
|
||||
}
|
||||
|
||||
boolean isFluid = blockState.getBlock() instanceof FluidBlock;
|
||||
int modelId = -1;
|
||||
var textureData = this.bakery.renderFaces(blockState, 123456, isFluid);
|
||||
{//Deduplicate same entries
|
||||
int possibleDuplicate = this.modelTexture2id.getInt(List.of(textureData));
|
||||
if (possibleDuplicate != -1) {//Duplicate found
|
||||
this.idMappings[blockId] = possibleDuplicate;
|
||||
modelId = possibleDuplicate;
|
||||
return possibleDuplicate;
|
||||
} else {//Not a duplicate so create a new entry
|
||||
modelId = this.modelTexture2id.size();
|
||||
this.idMappings[blockId] = modelId;
|
||||
this.modelTexture2id.put(Stream.of(textureData).map(ColourDepthTextureData::clone).toList(), modelId);
|
||||
}
|
||||
}
|
||||
|
||||
var colourProvider = MinecraftClient.getInstance().getBlockColors().providers.get(Registries.BLOCK.getRawId(blockState.getBlock()));
|
||||
|
||||
|
||||
RenderLayer blockRenderLayer = null;
|
||||
if (blockState.getBlock() instanceof FluidBlock) {
|
||||
blockRenderLayer = RenderLayers.getFluidLayer(blockState.getFluidState());
|
||||
} else {
|
||||
blockRenderLayer = RenderLayers.getBlockLayer(blockState);
|
||||
}
|
||||
|
||||
|
||||
int checkMode = blockRenderLayer==RenderLayer.getSolid()?TextureUtils.WRITE_CHECK_STENCIL:TextureUtils.WRITE_CHECK_ALPHA;
|
||||
|
||||
|
||||
|
||||
|
||||
long uploadPtr = UploadStream.INSTANCE.upload(this.modelBuffer, (long) modelId * MODEL_SIZE, MODEL_SIZE);
|
||||
|
||||
|
||||
//TODO: implement;
|
||||
// TODO: if it has a constant colour instead... idk why (apparently for things like spruce leaves)?? but premultiply the texture data by the constant colour
|
||||
boolean hasBiomeColourResolver = false;
|
||||
if (colourProvider != null) {
|
||||
hasBiomeColourResolver = isBiomeDependentColour(colourProvider, blockState);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//TODO: special case stuff like vines and glow lichen, where it can be represented by a single double sided quad
|
||||
// since that would help alot with perf of lots of vines, can be done by having one of the faces just not exist and the other be in no occlusion mode
|
||||
|
||||
var sizes = this.computeModelDepth(textureData, checkMode);
|
||||
|
||||
//TODO: THIS, note this can be tested for in 2 ways, re render the model with quad culling disabled and see if the result
|
||||
// is the same, (if yes then needs double sided quads)
|
||||
// another way to test it is if e.g. up and down havent got anything rendered but the sides do (e.g. all plants etc)
|
||||
boolean needsDoubleSidedQuads = (sizes[0] < -0.1 && sizes[1] < -0.1) || (sizes[2] < -0.1 && sizes[3] < -0.1) || (sizes[4] < -0.1 && sizes[5] < -0.1);
|
||||
|
||||
|
||||
|
||||
//Each face gets 1 byte, with the top 2 bytes being for whatever
|
||||
long metadata = 0;
|
||||
metadata |= hasBiomeColourResolver?1:0;
|
||||
metadata |= blockRenderLayer == RenderLayer.getTranslucent()?2:0;
|
||||
metadata |= needsDoubleSidedQuads?4:0;
|
||||
metadata |= (!blockState.getFluidState().isEmpty())?8:0;//Has a fluid state accosiacted with it
|
||||
|
||||
//TODO: add a bunch of control config options for overriding/setting options of metadata for each face of each type
|
||||
for (int face = 5; face != -1; face--) {//In reverse order to make indexing into the metadata long easier
|
||||
long faceUploadPtr = uploadPtr + 4L * face;//Each face gets 4 bytes worth of data
|
||||
metadata <<= 8;
|
||||
float offset = sizes[face];
|
||||
if (offset < -0.1) {//Face is empty, so ignore
|
||||
metadata |= 0xFF;//Mark the face as non-existent
|
||||
//Set to -1 as safepoint
|
||||
MemoryUtil.memPutInt(faceUploadPtr, -1);
|
||||
continue;
|
||||
}
|
||||
var faceSize = TextureUtils.computeBounds(textureData[face], checkMode);
|
||||
int writeCount = TextureUtils.getWrittenPixelCount(textureData[face], checkMode);
|
||||
|
||||
boolean faceCoversFullBlock = faceSize[0] == 0 && faceSize[2] == 0 &&
|
||||
faceSize[1] == (this.modelTextureSize-1) && faceSize[3] == (this.modelTextureSize-1);
|
||||
|
||||
metadata |= faceCoversFullBlock?2:0;
|
||||
|
||||
//TODO: add alot of config options for the following
|
||||
boolean occludesFace = true;
|
||||
occludesFace &= blockRenderLayer != RenderLayer.getTranslucent();//If its translucent, it doesnt occlude
|
||||
|
||||
//TODO: make this an option, basicly if the face is really close, it occludes otherwise it doesnt
|
||||
occludesFace &= offset < 0.1;//If the face is rendered far away from the other face, then it doesnt occlude
|
||||
|
||||
if (occludesFace) {
|
||||
occludesFace &= ((float)writeCount)/(this.modelTextureSize * this.modelTextureSize) > 0.9;// only occlude if the face covers more than 90% of the face
|
||||
}
|
||||
metadata |= occludesFace?1:0;
|
||||
|
||||
|
||||
|
||||
boolean canBeOccluded = true;
|
||||
//TODO: make this an option on how far/close
|
||||
canBeOccluded &= offset < 0.3;//If the face is rendered far away from the other face, then it cant be occluded
|
||||
|
||||
metadata |= canBeOccluded?4:0;
|
||||
|
||||
|
||||
|
||||
//Scale face size from 0->this.modelTextureSize-1 to 0->15
|
||||
for (int i = 0; i < 4; i++) {
|
||||
faceSize[i] = Math.round((((float)faceSize[i])/(this.modelTextureSize-1))*15);
|
||||
}
|
||||
|
||||
int faceModelData = 0;
|
||||
faceModelData |= faceSize[0] | (faceSize[1]<<4) | (faceSize[2]<<8) | (faceSize[3]<<12);
|
||||
faceModelData |= Math.round(offset*63)<<16;//Change the scale from 0->1 (ends inclusive) float to 0->63 (6 bits) NOTE! that 63 == 1.0f meaning its shifted all the way to the other side of the model
|
||||
//Still have 11 bits free
|
||||
|
||||
//Stuff like fences are solid, however they have extra side piece that mean it needs to have discard on
|
||||
int area = (faceSize[1]-faceSize[0]+1) * (faceSize[3]-faceSize[2]+1);
|
||||
boolean needsAlphaDiscard = ((float)writeCount)/area<0.9;//If the amount of area covered by written pixels is less than a threashold, disable discard as its not needed
|
||||
|
||||
needsAlphaDiscard |= blockRenderLayer != RenderLayer.getSolid();
|
||||
|
||||
faceModelData |= needsAlphaDiscard?1<<22:0;
|
||||
|
||||
faceModelData |= (!faceCoversFullBlock)?1<<23:0;
|
||||
|
||||
MemoryUtil.memPutInt(faceUploadPtr, faceModelData);
|
||||
}
|
||||
this.metadataCache[modelId] = metadata;
|
||||
|
||||
uploadPtr += 4*6;
|
||||
//Have 40 bytes free for remaining model data
|
||||
// todo: put in like the render layer type ig? along with colour resolver info
|
||||
int modelFlags = 0;
|
||||
modelFlags |= colourProvider != null?1:0;
|
||||
modelFlags |= hasBiomeColourResolver?2:0;//Basicly whether to use the next int as a colour or as a base index/id into a colour buffer for biome dependent colours
|
||||
|
||||
|
||||
//modelFlags |= blockRenderLayer == RenderLayer.getSolid()?0:1;// should discard alpha
|
||||
MemoryUtil.memPutInt(uploadPtr, modelFlags);
|
||||
//Temporary override to always be non biome specific
|
||||
if (colourProvider == null) {
|
||||
MemoryUtil.memPutInt(uploadPtr + 4, -1);//Set the default to nothing so that its faster on the gpu
|
||||
} else if ((!hasBiomeColourResolver) || true) {
|
||||
Biome defaultBiome = MinecraftClient.getInstance().world.getRegistryManager().get(RegistryKeys.BIOME).get(BiomeKeys.PLAINS);
|
||||
MemoryUtil.memPutInt(uploadPtr + 4, captureColourConstant(colourProvider, blockState, defaultBiome)|0xFF000000);
|
||||
} else {
|
||||
//Populate the list of biomes for the model state
|
||||
}
|
||||
|
||||
|
||||
//Note: if the layer isSolid then need to fill all the points in the texture where alpha == 0 with the average colour
|
||||
// of the surrounding blocks but only within the computed face size bounds
|
||||
//TODO
|
||||
|
||||
|
||||
this.putTextures(modelId, textureData);
|
||||
return modelId;
|
||||
}
|
||||
|
||||
//TODO: add a method to detect biome dependent colours (can do by detecting if getColor is ever called)
|
||||
// if it is, need to add it to a list and mark it as biome colour dependent or something then the shader
|
||||
// will either use the uint as an index or a direct colour multiplier
|
||||
private static int captureColourConstant(BlockColorProvider colorProvider, BlockState state, Biome biome) {
|
||||
return colorProvider.getColor(state, new BlockRenderView() {
|
||||
@Override
|
||||
public float getBrightness(Direction direction, boolean shaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLightLevel(LightType type, BlockPos pos) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightingProvider getLightingProvider() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColor(BlockPos pos, ColorResolver colorResolver) {
|
||||
return colorResolver.getColor(biome, 0, 0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity getBlockEntity(BlockPos pos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FluidState getFluidState(BlockPos pos) {
|
||||
return state.getFluidState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBottomY() {
|
||||
return 0;
|
||||
}
|
||||
}, BlockPos.ORIGIN, 0);
|
||||
}
|
||||
|
||||
private static boolean isBiomeDependentColour(BlockColorProvider colorProvider, BlockState state) {
|
||||
boolean[] biomeDependent = new boolean[1];
|
||||
colorProvider.getColor(state, new BlockRenderView() {
|
||||
@Override
|
||||
public float getBrightness(Direction direction, boolean shaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLightLevel(LightType type, BlockPos pos) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightingProvider getLightingProvider() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColor(BlockPos pos, ColorResolver colorResolver) {
|
||||
biomeDependent[0] = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity getBlockEntity(BlockPos pos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FluidState getFluidState(BlockPos pos) {
|
||||
return state.getFluidState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBottomY() {
|
||||
return 0;
|
||||
}
|
||||
}, BlockPos.ORIGIN, 0);
|
||||
return biomeDependent[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static boolean faceExists(long metadata, int face) {
|
||||
return ((metadata>>(8*face))&0xFF)!=0xFF;
|
||||
}
|
||||
|
||||
public static boolean faceCanBeOccluded(long metadata, int face) {
|
||||
return ((metadata>>(8*face))&0b100)==0b100;
|
||||
}
|
||||
|
||||
public static boolean faceOccludes(long metadata, int face) {
|
||||
return faceExists(metadata, face) && ((metadata>>(8*face))&0b1)==0b1;
|
||||
}
|
||||
|
||||
public static boolean isColoured(long metadata) {
|
||||
//TODO: THIS
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isDoubleSided(long metadata) {
|
||||
return ((metadata>>(8*6))&4) != 0;
|
||||
}
|
||||
|
||||
public static boolean isTranslucent(long metadata) {
|
||||
return ((metadata>>(8*6))&2) != 0;
|
||||
}
|
||||
|
||||
public static boolean containsFluid(long metadata) {
|
||||
return ((metadata>>(8*6))&8) != 0;
|
||||
}
|
||||
|
||||
public static boolean isBiomeColoured(long metadata) {
|
||||
return ((metadata>>(8*6))&1) != 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private float[] computeModelDepth(ColourDepthTextureData[] textures, int checkMode) {
|
||||
float[] res = new float[6];
|
||||
for (var dir : Direction.values()) {
|
||||
var data = textures[dir.getId()];
|
||||
float fd = TextureUtils.computeDepth(data, TextureUtils.DEPTH_MODE_MIN, checkMode);//Compute the min float depth, smaller means closer to the camera, range 0-1
|
||||
int depth = Math.round(fd * this.modelTextureSize);
|
||||
//If fd is -1, it means that there was nothing rendered on that face and it should be discarded
|
||||
if (fd < -0.1) {
|
||||
res[dir.ordinal()] = -1;
|
||||
} else {
|
||||
res[dir.ordinal()] = ((float) depth)/this.modelTextureSize;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
//TODO:FIXME: if the model is not already in the cache for some reason it renders black, need to figure out why
|
||||
public long getModelMetadata(int blockId) {
|
||||
int map = 0;
|
||||
while ((map = this.idMappings[blockId]) == -1) {
|
||||
Thread.onSpinWait();
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (map == -1) {
|
||||
throw new IllegalArgumentException("Id hasnt been computed yet: " + blockId);
|
||||
}
|
||||
long meta = 0;
|
||||
|
||||
while ((meta = this.metadataCache[map]) == 0) {
|
||||
Thread.onSpinWait();
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
public int getModelId(int blockId) {
|
||||
int map = this.idMappings[blockId];
|
||||
if (map == -1) {
|
||||
throw new IllegalArgumentException("Id hasnt been computed yet: " + blockId);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private void putTextures(int id, ColourDepthTextureData[] textures) {
|
||||
int X = (id&0xFF) * this.modelTextureSize*3;
|
||||
int Y = ((id>>8)&0xFF) * this.modelTextureSize*2;
|
||||
for (int subTex = 0; subTex < 6; subTex++) {
|
||||
int x = X + (subTex%3)*this.modelTextureSize;
|
||||
int y = Y + (subTex/3)*this.modelTextureSize;
|
||||
|
||||
GlStateManager._pixelStore(GlConst.GL_UNPACK_ROW_LENGTH, 0);
|
||||
GlStateManager._pixelStore(GlConst.GL_UNPACK_SKIP_PIXELS, 0);
|
||||
GlStateManager._pixelStore(GlConst.GL_UNPACK_SKIP_ROWS, 0);
|
||||
GlStateManager._pixelStore(GlConst.GL_UNPACK_ALIGNMENT, 4);
|
||||
glTextureSubImage2D(this.textures.id, 0, x, y, this.modelTextureSize, this.modelTextureSize, GL_RGBA, GL_UNSIGNED_BYTE, textures[subTex].colour());
|
||||
}
|
||||
}
|
||||
|
||||
public int getBufferId() {
|
||||
return this.modelBuffer.id;
|
||||
}
|
||||
|
||||
public int getTextureId() {
|
||||
return this.textures.id;
|
||||
}
|
||||
|
||||
public int getSamplerId() {
|
||||
return this.blockSampler;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
this.bakery.free();
|
||||
this.modelBuffer.free();
|
||||
this.textures.free();
|
||||
glDeleteSamplers(this.blockSampler);
|
||||
}
|
||||
|
||||
public void addDebugInfo(List<String> info) {
|
||||
info.add("BlockModels registered: " + this.modelTexture2id.size() + "/" + (1<<16));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package me.cortex.zenith.client.core.model;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlConst;
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.systems.VertexSorter;
|
||||
import me.cortex.zenith.client.core.gl.GlFramebuffer;
|
||||
import me.cortex.zenith.client.core.gl.GlTexture;
|
||||
import me.cortex.zenith.client.core.gl.shader.Shader;
|
||||
import me.cortex.zenith.client.core.gl.shader.ShaderLoader;
|
||||
import me.cortex.zenith.client.core.gl.shader.ShaderType;
|
||||
import me.jellysquid.mods.sodium.client.gl.shader.GlShader;
|
||||
import net.minecraft.block.*;
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gl.GlUniform;
|
||||
import net.minecraft.client.render.*;
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.fluid.FluidState;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.RotationAxis;
|
||||
import net.minecraft.util.math.random.LocalRandom;
|
||||
import net.minecraft.world.BlockRenderView;
|
||||
import net.minecraft.world.LightType;
|
||||
import net.minecraft.world.biome.ColorResolver;
|
||||
import net.minecraft.world.chunk.light.LightingProvider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11C;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.lwjgl.opengl.ARBFramebufferObject.*;
|
||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_FRAMEBUFFER_BARRIER_BIT;
|
||||
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glMemoryBarrier;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
|
||||
import static org.lwjgl.opengl.GL20C.glUniformMatrix4fv;
|
||||
import static org.lwjgl.opengl.GL20C.glUseProgram;
|
||||
import static org.lwjgl.opengl.GL45C.glBlitNamedFramebuffer;
|
||||
import static org.lwjgl.opengl.GL45C.glGetTextureImage;
|
||||
|
||||
//Builds a texture for each face of a model
|
||||
public class ModelTextureBakery {
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final GlTexture colourTex;
|
||||
private final GlTexture depthTex;
|
||||
private final GlFramebuffer framebuffer;
|
||||
private final Shader rasterShader = Shader.make()
|
||||
.add(ShaderType.VERTEX, "zenith:bakery/position_tex.vsh")
|
||||
.add(ShaderType.FRAGMENT, "zenith:bakery/position_tex.fsh")
|
||||
.compile();
|
||||
|
||||
private static final List<MatrixStack> FACE_VIEWS = new ArrayList<>();
|
||||
|
||||
|
||||
public ModelTextureBakery(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.colourTex = new GlTexture().store(GL_RGBA8, 1, width, height);
|
||||
this.depthTex = new GlTexture().store(GL_DEPTH24_STENCIL8, 1, width, height);
|
||||
this.framebuffer = new GlFramebuffer().bind(GL_COLOR_ATTACHMENT0, this.colourTex).bind(GL_DEPTH_STENCIL_ATTACHMENT, this.depthTex).verify();
|
||||
|
||||
//This is done to help make debugging easier
|
||||
FACE_VIEWS.clear();
|
||||
AddViews();
|
||||
}
|
||||
|
||||
private static void AddViews() {
|
||||
addView(-90,0, 0, false);//Direction.DOWN
|
||||
addView(90,0, 0, false);//Direction.UP
|
||||
addView(0,180, 0, true);//Direction.NORTH
|
||||
addView(0,0, 0, false);//Direction.SOUTH
|
||||
//TODO: check these arnt the wrong way round
|
||||
addView(0,90, 270, false);//Direction.EAST
|
||||
addView(0,270, 270, false);//Direction.WEST
|
||||
}
|
||||
|
||||
private static void addView(float pitch, float yaw, float rotation, boolean flipX) {
|
||||
var stack = new MatrixStack();
|
||||
stack.translate(0.5f,0.5f,0.5f);
|
||||
stack.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(rotation));
|
||||
stack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(pitch));
|
||||
stack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw));
|
||||
stack.translate(-0.5f,-0.5f,-0.5f);
|
||||
FACE_VIEWS.add(stack);
|
||||
}
|
||||
|
||||
|
||||
//TODO: For block entities, also somehow attempt to render the default block entity, e.g. chests and stuff
|
||||
// cause that will result in ok looking micro details in the terrain
|
||||
public ColourDepthTextureData[] renderFaces(BlockState state, long randomValue, boolean renderFluid) {
|
||||
var model = MinecraftClient.getInstance()
|
||||
.getBakedModelManager()
|
||||
.getBlockModels()
|
||||
.getModel(state);
|
||||
|
||||
int oldFB = GlStateManager.getBoundFramebuffer();
|
||||
var oldProjection = new Matrix4f(RenderSystem.getProjectionMatrix());
|
||||
GL11C.glViewport(0, 0, this.width, this.height);
|
||||
|
||||
RenderSystem.setProjectionMatrix(new Matrix4f().identity().set(new float[]{
|
||||
2,0,0,0,
|
||||
0, 2,0,0,
|
||||
0,0, -1f,0,
|
||||
-1,-1,0,1,
|
||||
}), VertexSorter.BY_Z);
|
||||
|
||||
glClearColor(0,0,0,0);
|
||||
glClearDepth(1);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, this.framebuffer.id);
|
||||
|
||||
|
||||
RenderLayer renderLayer = null;
|
||||
if (!renderFluid) {
|
||||
renderLayer = RenderLayers.getBlockLayer(state);
|
||||
} else {
|
||||
renderLayer = RenderLayers.getFluidLayer(state.getFluidState());
|
||||
}
|
||||
|
||||
|
||||
renderLayer.startDrawing();
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glDepthRange(0, 1);
|
||||
RenderSystem.depthMask(true);
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.enableDepthTest();
|
||||
RenderSystem.enableCull();
|
||||
RenderSystem.depthFunc(GL_LESS);
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
|
||||
glStencilFunc(GL_ALWAYS, 1, 0xFF);
|
||||
glStencilMask(0xFF);
|
||||
|
||||
this.rasterShader.bind();
|
||||
RenderSystem.bindTexture(RenderSystem.getShaderTexture(0));
|
||||
GlUniform.uniform1(0, 0);
|
||||
RenderSystem.activeTexture(GlConst.GL_TEXTURE0);
|
||||
|
||||
var faces = new ColourDepthTextureData[FACE_VIEWS.size()];
|
||||
for (int i = 0; i < faces.length; i++) {
|
||||
faces[i] = captureView(state, model, FACE_VIEWS.get(i), randomValue, i, renderFluid);
|
||||
//glBlitNamedFramebuffer(this.framebuffer.id, oldFB, 0,0,16,16,300*(i%3),300*(i/3),300*(i%3)+256,300*(i/3)+256, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
}
|
||||
|
||||
renderLayer.endDrawing();
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
|
||||
RenderSystem.setProjectionMatrix(oldProjection, VertexSorter.BY_DISTANCE);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, oldFB);
|
||||
GL11C.glViewport(GlStateManager.Viewport.getX(), GlStateManager.Viewport.getY(), GlStateManager.Viewport.getWidth(), GlStateManager.Viewport.getHeight());
|
||||
|
||||
//TODO: FIXME: fully revert the state of opengl
|
||||
|
||||
return faces;
|
||||
}
|
||||
|
||||
private ColourDepthTextureData captureView(BlockState state, BakedModel model, MatrixStack stack, long randomValue, int face, boolean renderFluid) {
|
||||
var vc = Tessellator.getInstance().getBuffer();
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
vc.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE);
|
||||
|
||||
if (!renderFluid) {
|
||||
renderQuads(vc, state, model, new MatrixStack(), randomValue);
|
||||
} else {
|
||||
MinecraftClient.getInstance().getBlockRenderManager().renderFluid(BlockPos.ORIGIN, new BlockRenderView() {
|
||||
@Override
|
||||
public float getBrightness(Direction direction, boolean shaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightingProvider getLightingProvider() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLightLevel(LightType type, BlockPos pos) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColor(BlockPos pos, ColorResolver colorResolver) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity getBlockEntity(BlockPos pos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//TODO: make it so it returns air on some positions, e.g. so from UP,
|
||||
@Override
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
//TODO:FIXME: Dont hardcode
|
||||
if (pos.equals(Direction.byId(face).getVector())) {
|
||||
return Blocks.AIR.getDefaultState();
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FluidState getFluidState(BlockPos pos) {
|
||||
if (pos.equals(Direction.byId(face).getVector())) {
|
||||
return Blocks.AIR.getDefaultState().getFluidState();
|
||||
}
|
||||
return state.getFluidState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBottomY() {
|
||||
return 0;
|
||||
}
|
||||
}, vc, state, state.getFluidState());
|
||||
}
|
||||
|
||||
|
||||
|
||||
float[] mat = new float[4*4];
|
||||
new Matrix4f(RenderSystem.getProjectionMatrix()).mul(stack.peek().getPositionMatrix()).get(mat);
|
||||
glUniformMatrix4fv(1, false, mat);
|
||||
BufferRenderer.draw(vc.end());
|
||||
|
||||
|
||||
if (state.hasBlockEntity()) {
|
||||
//TODO: finish BlockEntity raster
|
||||
//var entity = ((BlockEntityProvider)state).createBlockEntity(BlockPos.ORIGIN, state);
|
||||
//var renderer = MinecraftClient.getInstance().getBlockEntityRenderDispatcher().get(entity);
|
||||
//renderer.render();
|
||||
//entity.markRemoved();
|
||||
}
|
||||
|
||||
|
||||
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
|
||||
int[] colourData = new int[this.width*this.height];
|
||||
int[] depthData = new int[this.width*this.height];
|
||||
glGetTextureImage(this.colourTex.id, 0, GL_RGBA, GL_UNSIGNED_BYTE, colourData);
|
||||
glGetTextureImage(this.depthTex.id, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, depthData);
|
||||
return new ColourDepthTextureData(colourData, depthData, this.width, this.height);
|
||||
}
|
||||
|
||||
private static void renderQuads(BufferBuilder builder, BlockState state, BakedModel model, MatrixStack stack, long randomValue) {
|
||||
for (Direction direction : new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, null}) {
|
||||
var quads = model.getQuads(state, direction, new LocalRandom(randomValue));
|
||||
for (var quad : quads) {
|
||||
builder.quad(stack.peek(), quad, 0, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void free() {
|
||||
this.framebuffer.free();
|
||||
this.colourTex.free();
|
||||
this.depthTex.free();
|
||||
this.rasterShader.free();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package me.cortex.zenith.client.core.model;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
//Texturing utils to manipulate data from the model bakery
|
||||
public class TextureUtils {
|
||||
//Returns the number of non pixels not written to
|
||||
public static int getWrittenPixelCount(ColourDepthTextureData texture, int checkMode) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < texture.colour().length; i++) {
|
||||
count += wasPixelWritten(texture, checkMode, i)?1:0;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public static boolean isSolid(ColourDepthTextureData texture) {
|
||||
for (int pixel : texture.colour()) {
|
||||
if (((pixel>>24)&0xFF) != 255) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static final int WRITE_CHECK_STENCIL = 1;
|
||||
public static final int WRITE_CHECK_DEPTH = 2;
|
||||
public static final int WRITE_CHECK_ALPHA = 3;
|
||||
private static boolean wasPixelWritten(ColourDepthTextureData data, int mode, int index) {
|
||||
if (mode == WRITE_CHECK_STENCIL) {
|
||||
return (data.depth()[index]&0xFF)!=0;
|
||||
} else if (mode == WRITE_CHECK_DEPTH) {
|
||||
return (data.depth()[index]>>>8)!=((1<<24)-1);
|
||||
} else if (mode == WRITE_CHECK_ALPHA) {
|
||||
return ((data.colour()[index]>>>24)&0xff)!=0;
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public static final int DEPTH_MODE_AVG = 1;
|
||||
public static final int DEPTH_MODE_MAX = 2;
|
||||
public static final int DEPTH_MODE_MIN = 3;
|
||||
|
||||
|
||||
//Computes depth info based on written pixel data
|
||||
public static float computeDepth(ColourDepthTextureData texture, int mode, int checkMode) {
|
||||
final var colourData = texture.colour();
|
||||
final var depthData = texture.depth();
|
||||
long a = 0;
|
||||
long b = 0;
|
||||
if (mode == DEPTH_MODE_MIN) {
|
||||
a = Long.MAX_VALUE;
|
||||
}
|
||||
if (mode == DEPTH_MODE_MAX) {
|
||||
a = Long.MIN_VALUE;
|
||||
}
|
||||
for (int i = 0; i < colourData.length; i++) {
|
||||
if (!wasPixelWritten(texture, checkMode, i)) {
|
||||
continue;
|
||||
}
|
||||
int depth = depthData[i]>>>8;
|
||||
if (mode == DEPTH_MODE_AVG) {
|
||||
a++;
|
||||
b += depth;
|
||||
} else if (mode == DEPTH_MODE_MAX) {
|
||||
a = Math.max(a, depth);
|
||||
} else if (mode == DEPTH_MODE_MIN) {
|
||||
a = Math.min(a, depth);
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == DEPTH_MODE_AVG) {
|
||||
if (a == 0) {
|
||||
return -1;
|
||||
}
|
||||
return u2fdepth((int) (b/a));
|
||||
} else if (mode == DEPTH_MODE_MAX) {
|
||||
if (a == Long.MIN_VALUE) {
|
||||
return -1;
|
||||
}
|
||||
return u2fdepth((int) a);
|
||||
} else if (mode == DEPTH_MODE_MIN) {
|
||||
if (a == Long.MAX_VALUE) {
|
||||
return -1;
|
||||
}
|
||||
return u2fdepth((int) a);
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
private static float u2fdepth(int depth) {
|
||||
float depthF = (float) ((double)depth/((1<<24)-1));
|
||||
//https://registry.khronos.org/OpenGL-Refpages/gl4/html/glDepthRange.xhtml
|
||||
// due to this and the unsigned bullshit, i believe the depth value needs to get multiplied by 2
|
||||
depthF *= 2;
|
||||
if (depthF > 1.00001f) {
|
||||
throw new IllegalArgumentException("Depth greater than 1");
|
||||
}
|
||||
return depthF;
|
||||
}
|
||||
|
||||
|
||||
//NOTE: data goes from bottom left to top right (x first then y)
|
||||
public static int[] computeBounds(ColourDepthTextureData data, int checkMode) {
|
||||
final var depth = data.depth();
|
||||
//Compute x bounds first
|
||||
int minX = 0;
|
||||
minXCheck:
|
||||
do {
|
||||
for (int y = 0; y < data.height(); y++) {
|
||||
int idx = minX + (y * data.width());
|
||||
if (wasPixelWritten(data, checkMode, idx)) {
|
||||
break minXCheck;//pixel was written too so break from loop
|
||||
}
|
||||
}
|
||||
minX++;
|
||||
} while (minX != data.width());
|
||||
|
||||
int maxX = data.width()-1;
|
||||
maxXCheck:
|
||||
do {
|
||||
for (int y = data.height()-1; y!=-1; y--) {
|
||||
int idx = maxX + (y * data.width());
|
||||
if (wasPixelWritten(data, checkMode, idx)) {
|
||||
break maxXCheck;//pixel was written too so break from loop
|
||||
}
|
||||
}
|
||||
maxX--;
|
||||
} while (maxX != -1);
|
||||
//maxX++;
|
||||
|
||||
|
||||
//Compute y bounds
|
||||
int minY = 0;
|
||||
minYCheck:
|
||||
do {
|
||||
for (int x = 0; x < data.width(); x++) {
|
||||
int idx = (minY * data.height()) + x;
|
||||
if (wasPixelWritten(data, checkMode, idx)) {
|
||||
break minYCheck;//pixel was written too
|
||||
}
|
||||
}
|
||||
minY++;
|
||||
} while (minY != data.height());
|
||||
|
||||
|
||||
int maxY = data.height()-1;
|
||||
maxYCheck:
|
||||
do {
|
||||
for (int x = data.width()-1; x!=-1; x--) {
|
||||
int idx = (maxY * data.height()) + x;
|
||||
if (wasPixelWritten(data, checkMode, idx)) {
|
||||
break maxYCheck;//pixel was written too so break from loop
|
||||
}
|
||||
}
|
||||
maxY--;
|
||||
} while (maxY != -1);
|
||||
//maxY++;
|
||||
|
||||
return new int[]{minX, maxX, minY, maxY};
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package me.cortex.zenith.client.core.other;
|
||||
|
||||
public record BiomeColour(int id, int foliageColour, int waterColour) {
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package me.cortex.zenith.client.core.other;
|
||||
|
||||
public record BlockStateColour(int id, int biomeTintMsk, int[] faceColours) {
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package me.cortex.zenith.client.core.other;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.render.model.BakedQuad;
|
||||
import net.minecraft.client.texture.NativeImage;
|
||||
import net.minecraft.registry.RegistryKeys;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.random.LocalRandom;
|
||||
|
||||
public class ColourResolver {
|
||||
//TODO: sample from multiple random values and avg it
|
||||
public static int[] resolveColour(BlockState state) {
|
||||
return resolveColour(state, 1234567890L);
|
||||
}
|
||||
|
||||
//The way this works is it takes the and computes its colour, it then computes the area of the quad and the normal direction
|
||||
// it adds each area and colour to a per direcition colour
|
||||
// for non specific axis dimensions it takes the normal of each quad computes the dot between it and each of the directions
|
||||
// and averages that
|
||||
// if the colour doesnt exist for a specific axis set it to the average of the other axis and or make it translucent
|
||||
|
||||
//TODO: fixme: finish
|
||||
public static int[] resolveColour(BlockState state, long randomValue) {
|
||||
if (state == Blocks.AIR.getDefaultState()) {
|
||||
return new int[6];
|
||||
}
|
||||
int[][] builder = new int[6][5];
|
||||
var random = new LocalRandom(randomValue);
|
||||
if (state.getFluidState().isEmpty()) {
|
||||
for (Direction direction : new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, null}) {
|
||||
var quads = MinecraftClient.getInstance()
|
||||
.getBakedModelManager()
|
||||
.getBlockModels()
|
||||
.getModel(state)
|
||||
.getQuads(state, direction, random);
|
||||
for (var quad : quads) {
|
||||
long weightColour = resolveQuadColour(quad);
|
||||
int colour = (int) weightColour;
|
||||
int weight = (int) (weightColour>>32);
|
||||
if (direction == null) {
|
||||
//TODO: apply normal multiplication to weight
|
||||
for (int i = 0; i < 6; i++) {
|
||||
builder[i][0] += weight;
|
||||
builder[i][4] += weight * ((colour>>>24)&0xFF);
|
||||
builder[i][3] += weight * ((colour>>>16)&0xFF);
|
||||
builder[i][2] += weight * ((colour>>>8)&0xFF);
|
||||
builder[i][1] += weight * ((colour>>>0)&0xFF);
|
||||
}
|
||||
} else {
|
||||
builder[direction.getId()][0] += weight;
|
||||
builder[direction.getId()][4] += weight*((colour>>>24)&0xFF);
|
||||
builder[direction.getId()][3] += weight*((colour>>>16)&0xFF);
|
||||
builder[direction.getId()][2] += weight*((colour>>>8)&0xFF);
|
||||
builder[direction.getId()][1] += weight*((colour>>>0)&0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//TODO FIXME: need to account for both the fluid and block state at the same time
|
||||
//FIXME: make it not hacky and use the fluid handler thing from fabric
|
||||
|
||||
long weightColour = resolveNI(MinecraftClient.getInstance().getBakedModelManager().getBlockModels().getModelParticleSprite(state).getContents().image);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
builder[i][0] = 1;
|
||||
builder[i][1] += (weightColour>>0)&0xFF;
|
||||
builder[i][2] += (weightColour>>8)&0xFF;
|
||||
builder[i][3] += (weightColour>>16)&0xFF;
|
||||
builder[i][4] += (weightColour>>24)&0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
int[] out = new int[6];
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int c = builder[i][0];
|
||||
if (c == 0) {
|
||||
continue;
|
||||
}
|
||||
int r = builder[i][4]/c;
|
||||
int g = builder[i][3]/c;
|
||||
int b = builder[i][2]/c;
|
||||
int a = builder[i][1]/c;
|
||||
out[i] = (r<<24)|(g<<16)|(b<<8)|a;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static long resolveQuadColour(BakedQuad quad) {
|
||||
return resolveNI(quad.getSprite().getContents().image);
|
||||
}
|
||||
|
||||
private static long resolveNI(NativeImage image) {
|
||||
int r = 0;
|
||||
int g = 0;
|
||||
int b = 0;
|
||||
int a = 0;
|
||||
int count = 0;
|
||||
for (int y = 0; y < image.getHeight(); y++) {
|
||||
for (int x = 0; x < image.getWidth(); x++) {
|
||||
int colour = image.getColor(x, y);
|
||||
if (((colour >>> 24)&0xFF) == 0) {
|
||||
continue;
|
||||
}
|
||||
r += (colour >>> 0) & 0xFF;
|
||||
g += (colour >>> 8) & 0xFF;
|
||||
b += (colour >>> 16) & 0xFF;
|
||||
a += (colour >>> 24) & 0xFF;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
r /= count;
|
||||
g /= count;
|
||||
b /= count;
|
||||
a /= count;
|
||||
|
||||
int colour = (r<<24)|(g<<16)|(b<<8)|a;
|
||||
|
||||
return Integer.toUnsignedLong(colour)|(((long)count)<<32);
|
||||
}
|
||||
|
||||
|
||||
public static long resolveBiomeColour(String biomeId) {
|
||||
var biome = MinecraftClient.getInstance().world.getRegistryManager().get(RegistryKeys.BIOME).get(new Identifier(biomeId));
|
||||
if (biome == null) {
|
||||
System.err.println("Biome: " + biomeId + " doesnt exist in registry!");
|
||||
return 0;
|
||||
}
|
||||
int ARGBFoliage = biome.getFoliageColor();
|
||||
int ARGBWater = biome.getWaterColor();
|
||||
return Integer.toUnsignedLong(((ARGBFoliage&0xFFFFFF)<<8)|(ARGBFoliage>>>24)) | (Integer.toUnsignedLong(((ARGBWater&0xFFFFFF)<<8)|(ARGBWater>>>24))<<32);
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,11 @@ package me.cortex.zenith.client.core.rendering;
|
||||
// could maybe tosomething else
|
||||
|
||||
import me.cortex.zenith.client.core.gl.GlBuffer;
|
||||
import me.cortex.zenith.client.core.rendering.building.BuiltSectionGeometry;
|
||||
import me.cortex.zenith.client.core.model.ModelManager;
|
||||
import me.cortex.zenith.client.core.rendering.building.BuiltSection;
|
||||
import me.cortex.zenith.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.zenith.client.core.other.BiomeColour;
|
||||
import me.cortex.zenith.client.core.other.BlockStateColour;
|
||||
import me.cortex.zenith.common.world.other.Mapper;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.render.Camera;
|
||||
import net.minecraft.client.render.Frustum;
|
||||
@@ -16,6 +17,7 @@ import org.joml.FrustumIntersection;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
import static org.lwjgl.opengl.ARBMultiDrawIndirect.glMultiDrawElementsIndirect;
|
||||
@@ -35,14 +37,9 @@ public abstract class AbstractFarWorldRenderer {
|
||||
|
||||
protected final GlBuffer uniformBuffer;
|
||||
protected final GeometryManager geometry;
|
||||
|
||||
private final ConcurrentLinkedDeque<BlockStateColour> stateUpdateQueue = new ConcurrentLinkedDeque<>();
|
||||
private final ConcurrentLinkedDeque<BiomeColour> biomeUpdateQueue = new ConcurrentLinkedDeque<>();
|
||||
protected final GlBuffer stateDataBuffer;
|
||||
protected final GlBuffer biomeDataBuffer;
|
||||
protected final ModelManager models;
|
||||
protected final GlBuffer lightDataBuffer;
|
||||
|
||||
|
||||
//Current camera base level section position
|
||||
protected int sx;
|
||||
protected int sy;
|
||||
@@ -50,13 +47,12 @@ public abstract class AbstractFarWorldRenderer {
|
||||
|
||||
protected FrustumIntersection frustum;
|
||||
|
||||
public AbstractFarWorldRenderer() {
|
||||
this.uniformBuffer = new GlBuffer(1024, 0);
|
||||
//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();
|
||||
private final ConcurrentLinkedDeque<Mapper.StateEntry> blockStateUpdates = new ConcurrentLinkedDeque<>();
|
||||
public AbstractFarWorldRenderer(int geometrySize, int maxSections) {
|
||||
this.uniformBuffer = new GlBuffer(1024);
|
||||
this.lightDataBuffer = new GlBuffer(256*4);//256 of uint
|
||||
this.geometry = new GeometryManager(geometrySize*8L, maxSections);
|
||||
this.models = new ModelManager(16);
|
||||
}
|
||||
|
||||
protected abstract void setupVao();
|
||||
@@ -68,7 +64,6 @@ 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
|
||||
@@ -90,49 +85,38 @@ public abstract class AbstractFarWorldRenderer {
|
||||
//Upload any new geometry
|
||||
this.geometry.uploadResults();
|
||||
|
||||
//Upload any block state changes
|
||||
while (!this.stateUpdateQueue.isEmpty()) {
|
||||
var stateUpdate = this.stateUpdateQueue.pop();
|
||||
long ptr = UploadStream.INSTANCE.upload(this.stateDataBuffer, stateUpdate.id()*28L, 28);
|
||||
MemoryUtil.memPutInt(ptr, stateUpdate.biomeTintMsk()); ptr+=4;
|
||||
for (int faceColour : stateUpdate.faceColours()) {
|
||||
MemoryUtil.memPutInt(ptr, faceColour); ptr+=4;
|
||||
}
|
||||
}
|
||||
|
||||
//Upload any biome changes
|
||||
while (!this.biomeUpdateQueue.isEmpty()) {
|
||||
var biomeUpdate = this.biomeUpdateQueue.pop();
|
||||
long ptr = UploadStream.INSTANCE.upload(this.biomeDataBuffer, biomeUpdate.id()*8L, 8);
|
||||
MemoryUtil.memPutInt(ptr, biomeUpdate.foliageColour()); ptr+=4;
|
||||
MemoryUtil.memPutInt(ptr, biomeUpdate.waterColour()); ptr+=4;
|
||||
//Do any BlockChanges
|
||||
while (!this.blockStateUpdates.isEmpty()) {
|
||||
var update = this.blockStateUpdates.pop();
|
||||
this.models.addEntry(update.id, update.state);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void renderFarAwayOpaque(MatrixStack stack, double cx, double cy, double cz);
|
||||
|
||||
public void enqueueUpdate(BlockStateColour stateColour) {
|
||||
this.stateUpdateQueue.add(stateColour);
|
||||
}
|
||||
public abstract void renderFarAwayTranslucent();
|
||||
|
||||
public void enqueueUpdate(BiomeColour biomeColour) {
|
||||
this.biomeUpdateQueue.add(biomeColour);
|
||||
}
|
||||
|
||||
public void enqueueResult(BuiltSectionGeometry result) {
|
||||
public void enqueueResult(BuiltSection result) {
|
||||
this.geometry.enqueueResult(result);
|
||||
}
|
||||
|
||||
public void addDebugData(List<String> debug) {
|
||||
public void addBlockState(Mapper.StateEntry entry) {
|
||||
this.blockStateUpdates.add(entry);
|
||||
}
|
||||
|
||||
public void addDebugData(List<String> debug) {
|
||||
this.models.addDebugInfo(debug);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
glDeleteVertexArrays(this.vao);
|
||||
this.models.free();
|
||||
this.geometry.free();
|
||||
this.uniformBuffer.free();
|
||||
this.stateDataBuffer.free();
|
||||
this.biomeDataBuffer.free();
|
||||
this.lightDataBuffer.free();
|
||||
}
|
||||
|
||||
public ModelManager getModelManager() {
|
||||
return this.models;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import me.cortex.zenith.client.core.gl.GlBuffer;
|
||||
import me.cortex.zenith.client.core.rendering.building.BuiltSectionGeometry;
|
||||
import me.cortex.zenith.client.core.rendering.building.BuiltSection;
|
||||
import me.cortex.zenith.client.core.rendering.util.BufferArena;
|
||||
import me.cortex.zenith.client.core.rendering.util.UploadStream;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
@@ -14,21 +14,7 @@ import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
public class GeometryManager {
|
||||
private static final int SECTION_METADATA_SIZE = 32;
|
||||
|
||||
|
||||
private record SectionMeta(long position, long opaqueGeometryPtr, int opaqueQuadCount, long translucentGeometryPtr, int translucentQuadCount) {
|
||||
public void writeMetadata(long ptr) {
|
||||
//THIS IS DUE TO ENDIANNESS and that we are splitting a long into 2 ints
|
||||
MemoryUtil.memPutInt(ptr, (int) (this.position>>32)); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, (int) this.position); ptr += 4;
|
||||
|
||||
|
||||
MemoryUtil.memPutInt(ptr, (int) this.opaqueGeometryPtr); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, this.opaqueQuadCount); ptr += 4;
|
||||
}
|
||||
}
|
||||
|
||||
private final ConcurrentLinkedDeque<BuiltSectionGeometry> buildResults = new ConcurrentLinkedDeque<>();
|
||||
|
||||
private final ConcurrentLinkedDeque<BuiltSection> buildResults = new ConcurrentLinkedDeque<>();
|
||||
private int sectionCount = 0;
|
||||
private final Long2IntOpenHashMap pos2id = new Long2IntOpenHashMap();
|
||||
private final LongArrayList id2pos = new LongArrayList();
|
||||
@@ -37,37 +23,16 @@ public class GeometryManager {
|
||||
private final GlBuffer sectionMetaBuffer;
|
||||
private final BufferArena geometryBuffer;
|
||||
|
||||
|
||||
public GeometryManager() {
|
||||
this.sectionMetaBuffer = new GlBuffer(1L << 23, 0);
|
||||
this.geometryBuffer = new BufferArena((1L << 31) - 1024, 8);
|
||||
public GeometryManager(long geometryBufferSize, int maxSections) {
|
||||
this.sectionMetaBuffer = new GlBuffer(((long) maxSections) * SECTION_METADATA_SIZE);
|
||||
this.geometryBuffer = new BufferArena(geometryBufferSize, 8);
|
||||
this.pos2id.defaultReturnValue(-1);
|
||||
}
|
||||
|
||||
public void enqueueResult(BuiltSectionGeometry sectionGeometry) {
|
||||
this.buildResults.add(sectionGeometry);
|
||||
}
|
||||
|
||||
private SectionMeta createMeta(BuiltSectionGeometry geometry) {
|
||||
long geometryPtr = this.geometryBuffer.upload(geometry.geometryBuffer);
|
||||
|
||||
//TODO: support translucent geometry
|
||||
return new SectionMeta(geometry.position, geometryPtr, (int) (geometry.geometryBuffer.size/8), -1,0);
|
||||
}
|
||||
|
||||
private void freeMeta(SectionMeta meta) {
|
||||
if (meta.opaqueGeometryPtr != -1) {
|
||||
this.geometryBuffer.free(meta.opaqueGeometryPtr);
|
||||
}
|
||||
if (meta.translucentGeometryPtr != -1) {
|
||||
this.geometryBuffer.free(meta.translucentGeometryPtr);
|
||||
}
|
||||
}
|
||||
|
||||
void uploadResults() {
|
||||
while (!this.buildResults.isEmpty()) {
|
||||
var result = this.buildResults.pop();
|
||||
boolean isDelete = result.geometryBuffer == null && result.translucentGeometryBuffer == null;
|
||||
boolean isDelete = result.geometryBuffer == null;
|
||||
if (isDelete) {
|
||||
int id = -1;
|
||||
if ((id = this.pos2id.remove(result.position)) != -1) {
|
||||
@@ -140,6 +105,10 @@ public class GeometryManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void enqueueResult(BuiltSection sectionGeometry) {
|
||||
this.buildResults.add(sectionGeometry);
|
||||
}
|
||||
|
||||
public int getSectionCount() {
|
||||
return this.sectionCount;
|
||||
}
|
||||
@@ -160,9 +129,42 @@ public class GeometryManager {
|
||||
return this.sectionMetaBuffer.id;
|
||||
}
|
||||
|
||||
|
||||
public float getGeometryBufferUsage() {
|
||||
return this.geometryBuffer.usage();
|
||||
}
|
||||
|
||||
|
||||
//=========================================================================================================================================================================================
|
||||
//=========================================================================================================================================================================================
|
||||
//=========================================================================================================================================================================================
|
||||
|
||||
|
||||
//TODO: pack the offsets of each axis so that implicit face culling can work
|
||||
//Note! the opaquePreDataCount and translucentPreDataCount are never writen to the meta buffer, as they are indexed in reverse relative to the base opaque and translucent geometry
|
||||
private record SectionMeta(long position, int aabb, int geometryPtr, int size, int[] offsets) {
|
||||
public void writeMetadata(long ptr) {
|
||||
//THIS IS DUE TO ENDIANNESS and that we are splitting a long into 2 ints
|
||||
MemoryUtil.memPutInt(ptr, (int) (this.position>>32)); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, (int) this.position); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, (int) this.aabb); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, this.geometryPtr + this.offsets[0]); ptr += 4;
|
||||
|
||||
MemoryUtil.memPutInt(ptr, (this.offsets[1]-this.offsets[0])|((this.offsets[2]-this.offsets[1])<<16)); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, (this.offsets[3]-this.offsets[2])|((this.offsets[4]-this.offsets[3])<<16)); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, (this.offsets[5]-this.offsets[4])|((this.offsets[6]-this.offsets[5])<<16)); ptr += 4;
|
||||
MemoryUtil.memPutInt(ptr, (this.offsets[7]-this.offsets[6])|((this.size -this.offsets[7])<<16)); ptr += 4;
|
||||
}
|
||||
}
|
||||
|
||||
private SectionMeta createMeta(BuiltSection geometry) {
|
||||
int geometryPtr = (int) this.geometryBuffer.upload(geometry.geometryBuffer);
|
||||
return new SectionMeta(geometry.position, geometry.aabb, geometryPtr, (int) (geometry.geometryBuffer.size/8), geometry.offsets);
|
||||
}
|
||||
|
||||
private void freeMeta(SectionMeta meta) {
|
||||
if (meta.geometryPtr != -1) {
|
||||
this.geometryBuffer.free(meta.geometryPtr);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,29 +1,41 @@
|
||||
package me.cortex.zenith.client.core.rendering;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import me.cortex.zenith.client.core.gl.GlBuffer;
|
||||
import me.cortex.zenith.client.core.gl.shader.Shader;
|
||||
import me.cortex.zenith.client.core.gl.shader.ShaderType;
|
||||
import me.cortex.zenith.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.zenith.client.mixin.joml.AccessFrustumIntersection;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.block.WallMountedBlock;
|
||||
import net.minecraft.block.enums.BlockFace;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.opengl.ARBIndirectParameters;
|
||||
import org.lwjgl.opengl.GL11C;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.lwjgl.opengl.ARBIndirectParameters.GL_PARAMETER_BUFFER_ARB;
|
||||
import static org.lwjgl.opengl.ARBIndirectParameters.glMultiDrawElementsIndirectCountARB;
|
||||
import static org.lwjgl.opengl.ARBMultiDrawIndirect.glMultiDrawElementsIndirect;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
|
||||
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_SHORT;
|
||||
import static org.lwjgl.opengl.GL11.glGetInteger;
|
||||
import static org.lwjgl.opengl.GL30.glBindVertexArray;
|
||||
import static org.lwjgl.opengl.GL30C.GL_R8UI;
|
||||
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
|
||||
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
|
||||
import static org.lwjgl.opengl.GL42.*;
|
||||
import static org.lwjgl.opengl.GL42.GL_FRAMEBUFFER_BARRIER_BIT;
|
||||
import static org.lwjgl.opengl.GL43.*;
|
||||
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
|
||||
import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV;
|
||||
import static org.lwjgl.opengl.GL45C.glClearNamedBufferData;
|
||||
|
||||
public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
|
||||
private final Shader commandGen = Shader.make()
|
||||
@@ -42,11 +54,17 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
|
||||
.add(ShaderType.FRAGMENT, "zenith:lod/gl46/cull/raster.frag")
|
||||
.compile();
|
||||
|
||||
private final GlBuffer glCommandBuffer = new GlBuffer(200_000*5*4, 0);
|
||||
private final GlBuffer glVisibilityBuffer = new GlBuffer(200_000*4, 0);
|
||||
private final GlBuffer glCommandBuffer;
|
||||
private final GlBuffer glCommandCountBuffer;
|
||||
private final GlBuffer glVisibilityBuffer;
|
||||
|
||||
public Gl46FarWorldRenderer() {
|
||||
super();
|
||||
public Gl46FarWorldRenderer(int geometryBuffer, int maxSections) {
|
||||
super(geometryBuffer, maxSections);
|
||||
this.glCommandBuffer = new GlBuffer(maxSections*5L*4 * 6);
|
||||
this.glCommandCountBuffer = new GlBuffer(4*2);
|
||||
this.glVisibilityBuffer = new GlBuffer(maxSections*4L);
|
||||
glClearNamedBufferData(this.glCommandBuffer.id, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, new int[1]);
|
||||
glClearNamedBufferData(this.glVisibilityBuffer.id, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, new int[1]);
|
||||
setupVao();
|
||||
}
|
||||
|
||||
@@ -54,75 +72,19 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
|
||||
protected void setupVao() {
|
||||
glBindVertexArray(this.vao);
|
||||
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.glCommandBuffer.id);
|
||||
glBindBuffer(GL_PARAMETER_BUFFER_ARB, this.glCommandCountBuffer.id);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id());
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometry.geometryId());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.glCommandBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, this.geometry.metaId());
|
||||
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, 3, this.glCommandCountBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.geometry.metaId());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, this.glVisibilityBuffer.id);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, this.models.getBufferId());
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, this.lightDataBuffer.id);//Lighting LUT
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
public void renderFarAwayOpaque(MatrixStack stack, double cx, double cy, double cz) {
|
||||
if (this.geometry.getSectionCount() == 0) {
|
||||
return;
|
||||
}
|
||||
RenderLayer.getCutoutMipped().startDrawing();
|
||||
//RenderSystem.enableBlend();
|
||||
//RenderSystem.defaultBlendFunc();
|
||||
|
||||
this.updateUniformBuffer(stack, cx, cy, cz);
|
||||
|
||||
UploadStream.INSTANCE.commit();
|
||||
|
||||
glBindVertexArray(this.vao);
|
||||
this.commandGen.bind();
|
||||
glDispatchCompute((this.geometry.getSectionCount() + 127) / 128, 1, 1);
|
||||
glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_UNIFORM_BARRIER_BIT);
|
||||
|
||||
this.lodShader.bind();
|
||||
if (false) {//Bloody intel gpus
|
||||
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, 1000, 0);
|
||||
|
||||
//int count = this.geometry.getSectionCount()/1000;
|
||||
//for (int i = 0; i < 10; i++) {
|
||||
// glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, i*1000L*20, 1000, 0);
|
||||
//}
|
||||
//int rem = this.geometry.getSectionCount() - (count*1000);
|
||||
//if (rem != 0) {
|
||||
// glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, count*1000L*20, rem, 0);
|
||||
//}
|
||||
|
||||
} else {
|
||||
//TODO: swap to a multidraw indirect counted
|
||||
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, this.geometry.getSectionCount(), 0);
|
||||
}
|
||||
//ARBIndirectParameters.glMultiDrawElementsIndirectCountARB(
|
||||
|
||||
glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
|
||||
//TODO: add gpu occlusion culling here (after the lod drawing) (maybe, finish the rest of the PoC first)
|
||||
|
||||
|
||||
cullShader.bind();
|
||||
glColorMask(false, false, false, false);
|
||||
glDepthMask(false);
|
||||
|
||||
//glEnable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
|
||||
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3, GL_UNSIGNED_BYTE, (1 << 16) * 6 * 2, this.geometry.getSectionCount());
|
||||
//glDisable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
|
||||
|
||||
glDepthMask(true);
|
||||
glColorMask(true, true, true, true);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
glBindVertexArray(0);
|
||||
|
||||
RenderLayer.getCutoutMipped().endDrawing();
|
||||
}
|
||||
|
||||
//FIXME: dont do something like this as it breaks multiviewport mods
|
||||
private int frameId = 0;
|
||||
private void updateUniformBuffer(MatrixStack stack, double cx, double cy, double cz) {
|
||||
@@ -143,6 +105,99 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
|
||||
MemoryUtil.memPutInt(ptr, this.frameId++); ptr += 4;
|
||||
}
|
||||
|
||||
public void renderFarAwayOpaque(MatrixStack stack, double cx, double cy, double cz) {
|
||||
if (this.geometry.getSectionCount() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
//this.models.addEntry(0, Blocks.STONE.getDefaultState());
|
||||
|
||||
|
||||
RenderLayer.getCutoutMipped().startDrawing();
|
||||
int oldActiveTexture = glGetInteger(GL_ACTIVE_TEXTURE);
|
||||
//RenderSystem.enableBlend();
|
||||
//RenderSystem.defaultBlendFunc();
|
||||
|
||||
this.updateUniformBuffer(stack, cx, cy, cz);
|
||||
|
||||
UploadStream.INSTANCE.commit();
|
||||
|
||||
glBindVertexArray(this.vao);
|
||||
|
||||
|
||||
//Bind the texture atlas
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
int oldBoundTexture = glGetInteger(GL_TEXTURE_BINDING_2D);
|
||||
glBindSampler(0, this.models.getSamplerId());
|
||||
glBindTexture(GL_TEXTURE_2D, this.models.getTextureId());
|
||||
|
||||
glClearNamedBufferData(this.glCommandCountBuffer.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[1]);
|
||||
this.commandGen.bind();
|
||||
glDispatchCompute((this.geometry.getSectionCount() + 127) / 128, 1, 1);
|
||||
glMemoryBarrier(GL_COMMAND_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT | GL_UNIFORM_BARRIER_BIT);
|
||||
|
||||
this.lodShader.bind();
|
||||
glDisable(GL_CULL_FACE);
|
||||
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, 0, (int) (this.geometry.getSectionCount()*4.4), 0);
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
||||
glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
|
||||
|
||||
this.cullShader.bind();
|
||||
glColorMask(false, false, false, false);
|
||||
glDepthMask(false);
|
||||
|
||||
//glEnable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
|
||||
glDrawElementsInstanced(GL_TRIANGLES, 6 * 2 * 3, GL_UNSIGNED_BYTE, (1 << 16) * 6 * 2, this.geometry.getSectionCount());
|
||||
//glDisable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
|
||||
|
||||
glDepthMask(true);
|
||||
glColorMask(true, true, true, true);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
|
||||
//TODO: need to do temporal rasterization here
|
||||
|
||||
glBindVertexArray(0);
|
||||
glBindSampler(0, 0);
|
||||
GL11C.glBindTexture(GL_TEXTURE_2D, oldBoundTexture);
|
||||
glActiveTexture(oldActiveTexture);
|
||||
RenderLayer.getCutoutMipped().endDrawing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderFarAwayTranslucent() {
|
||||
RenderLayer.getTranslucent().startDrawing();
|
||||
glBindVertexArray(this.vao);
|
||||
glDisable(GL_CULL_FACE);
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.blendFuncSeparate(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SrcFactor.ONE, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
int oldActiveTexture = glGetInteger(GL_ACTIVE_TEXTURE);
|
||||
|
||||
glBindSampler(0, this.models.getSamplerId());
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
int oldBoundTexture = glGetInteger(GL_TEXTURE_BINDING_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, this.models.getTextureId());
|
||||
this.lodShader.bind();
|
||||
|
||||
glDepthMask(false);
|
||||
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, 400_000 * 4 * 5, 4, this.geometry.getSectionCount(), 0);
|
||||
glDepthMask(true);
|
||||
|
||||
glEnable(GL_CULL_FACE);
|
||||
glBindVertexArray(0);
|
||||
|
||||
|
||||
glBindSampler(0, 0);
|
||||
GL11C.glBindTexture(GL_TEXTURE_2D, oldBoundTexture);
|
||||
glActiveTexture(oldActiveTexture);
|
||||
RenderSystem.disableBlend();
|
||||
|
||||
RenderLayer.getTranslucent().endDrawing();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
super.shutdown();
|
||||
@@ -151,10 +206,12 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer {
|
||||
this.cullShader.free();
|
||||
this.glCommandBuffer.free();
|
||||
this.glVisibilityBuffer.free();
|
||||
this.glCommandCountBuffer.free();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDebugData(List<String> debug) {
|
||||
super.addDebugData(debug);
|
||||
debug.add("Geometry buffer usage: " + ((float)Math.round((this.geometry.getGeometryBufferUsage()*100000))/1000) + "%");
|
||||
debug.add("Render Sections: " + this.geometry.getSectionCount());
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package me.cortex.zenith.client.core.rendering;
|
||||
|
||||
//Manages the storage and updating of model states, textures and colours
|
||||
public class ModelManager {
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package me.cortex.zenith.client.core.rendering;
|
||||
|
||||
import me.cortex.zenith.client.core.gl.shader.Shader;
|
||||
import me.cortex.zenith.client.core.gl.shader.ShaderType;
|
||||
import me.cortex.zenith.client.core.rendering.util.UploadStream;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
|
||||
import static org.lwjgl.opengl.ARBMultiDrawIndirect.glMultiDrawElementsIndirect;
|
||||
import static org.lwjgl.opengl.GL30.glBindVertexArray;
|
||||
import static org.lwjgl.opengl.NVMeshShader.glDrawMeshTasksNV;
|
||||
|
||||
//TODO: make this a 2 phase culling system
|
||||
// first phase renders the terrain, in the terrain task shader it also checks if the section was not visible in the frustum but now is
|
||||
// and then renders it and marks it as being in the frustum
|
||||
public class NvFarWorldRenderer extends AbstractFarWorldRenderer {
|
||||
private final Shader primaryTerrainRaster = Shader.make()
|
||||
.add(ShaderType.TASK, "voxelmon:lod/nvmesh/primary.task")
|
||||
.add(ShaderType.MESH, "voxelmon:lod/nvmesh/primary.mesh")
|
||||
.add(ShaderType.FRAGMENT, "voxelmon:lod/nvmesh/primary.frag")
|
||||
.compile();
|
||||
@Override
|
||||
protected void setupVao() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderFarAwayOpaque(MatrixStack stack, double cx, double cy, double cz) {
|
||||
if (this.geometry.getSectionCount() == 0) {
|
||||
return;
|
||||
}
|
||||
RenderLayer.getCutoutMipped().startDrawing();
|
||||
|
||||
UploadStream.INSTANCE.commit();
|
||||
|
||||
glBindVertexArray(this.vao);
|
||||
this.primaryTerrainRaster.bind();
|
||||
glDrawMeshTasksNV(0, this.geometry.getSectionCount());
|
||||
glBindVertexArray(0);
|
||||
|
||||
|
||||
RenderLayer.getCutoutMipped().endDrawing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
super.shutdown();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package me.cortex.zenith.client.core.rendering;
|
||||
|
||||
import me.cortex.zenith.client.core.rendering.building.BuiltSectionGeometry;
|
||||
import me.cortex.zenith.client.core.rendering.building.BuiltSection;
|
||||
import me.cortex.zenith.client.core.rendering.building.RenderGenerationService;
|
||||
import me.cortex.zenith.common.world.WorldEngine;
|
||||
import me.cortex.zenith.common.world.WorldSection;
|
||||
@@ -41,7 +41,7 @@ public class RenderTracker {
|
||||
//Removes a lvl 0 section from the world renderer
|
||||
public void remLvl0(int x, int y, int z) {
|
||||
this.activeSections.remove(WorldEngine.getWorldSectionId(0, x, y, z));
|
||||
this.renderer.enqueueResult(new BuiltSectionGeometry(WorldEngine.getWorldSectionId(0, x, y, z), null, null));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(0, x, y, z)));
|
||||
this.renderGen.removeTask(0, x, y, z);
|
||||
}
|
||||
|
||||
@@ -63,14 +63,14 @@ public class RenderTracker {
|
||||
|
||||
this.renderGen.enqueueTask(lvl, x, y, z, this::shouldStillBuild, this::getBuildFlagsOrAbort);
|
||||
|
||||
this.renderer.enqueueResult(new BuiltSectionGeometry(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)), null, null));
|
||||
this.renderer.enqueueResult(new BuiltSectionGeometry(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)+1), null, null));
|
||||
this.renderer.enqueueResult(new BuiltSectionGeometry(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)), null, null));
|
||||
this.renderer.enqueueResult(new BuiltSectionGeometry(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1), null, null));
|
||||
this.renderer.enqueueResult(new BuiltSectionGeometry(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)), null, null));
|
||||
this.renderer.enqueueResult(new BuiltSectionGeometry(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1), null, null));
|
||||
this.renderer.enqueueResult(new BuiltSectionGeometry(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)), null, null));
|
||||
this.renderer.enqueueResult(new BuiltSectionGeometry(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1), null, null));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1))));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)+1)));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1))));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1)));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1))));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1)));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1))));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1)));
|
||||
|
||||
|
||||
this.renderGen.removeTask(lvl-1, (x<<1), (y<<1), (z<<1));
|
||||
@@ -95,7 +95,7 @@ public class RenderTracker {
|
||||
this.activeSections.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1), O);
|
||||
this.activeSections.remove(WorldEngine.getWorldSectionId(lvl, x, y, z));
|
||||
|
||||
this.renderer.enqueueResult(new BuiltSectionGeometry(lvl, x, y, z, null, null));
|
||||
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl, x, y, z)));
|
||||
this.renderGen.removeTask(lvl, x, y, z);
|
||||
|
||||
this.renderGen.enqueueTask(lvl - 1, (x<<1), (y<<1), (z<<1), this::shouldStillBuild, this::getBuildFlagsOrAbort);
|
||||
@@ -134,7 +134,7 @@ public class RenderTracker {
|
||||
//called by the RenderGenerationService about built geometry, the RenderTracker checks if it can use the result (e.g. the LoD hasnt changed/still correct etc)
|
||||
// and dispatches it to the renderer
|
||||
// it also batch collects the geometry sections until all the geometry for an operation is collected, then it executes the operation, its removes flickering
|
||||
public void processBuildResult(BuiltSectionGeometry section) {
|
||||
public void processBuildResult(BuiltSection section) {
|
||||
//Check that we still want the section
|
||||
if (this.activeSections.containsKey(section.position)) {
|
||||
this.renderer.enqueueResult(section);
|
||||
|
||||
@@ -14,7 +14,7 @@ public class SharedIndexBuffer {
|
||||
private final GlBuffer indexBuffer;
|
||||
|
||||
public SharedIndexBuffer() {
|
||||
this.indexBuffer = new GlBuffer((1<<16)*6*2 + 6*2*3, 0);
|
||||
this.indexBuffer = new GlBuffer((1<<16)*6*2 + 6*2*3);
|
||||
var quadIndexBuff = IndexUtil.generateQuadIndicesShort(16380);
|
||||
var cubeBuff = generateCubeIndexBuffer();
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package me.cortex.zenith.client.core.rendering.building;
|
||||
|
||||
import me.cortex.zenith.common.util.MemoryBuffer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
//TODO: also have an AABB size stored
|
||||
public final class BuiltSection {
|
||||
public final long position;
|
||||
public final int aabb;
|
||||
public final MemoryBuffer geometryBuffer;
|
||||
public final int[] offsets;
|
||||
|
||||
public BuiltSection(long position) {
|
||||
this(position, -1, null, null);
|
||||
}
|
||||
|
||||
public BuiltSection(long position, int aabb, MemoryBuffer geometryBuffer, int[] offsets) {
|
||||
this.position = position;
|
||||
this.aabb = aabb;
|
||||
this.geometryBuffer = geometryBuffer;
|
||||
this.offsets = offsets;
|
||||
if (offsets != null) {
|
||||
for (int i = 0; i < offsets.length-1; i++) {
|
||||
int delta = offsets[i+1] - offsets[i];
|
||||
if (delta<0||delta>=(1<<16)) {
|
||||
throw new IllegalArgumentException("Offsets out of range");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BuiltSection clone() {
|
||||
return new BuiltSection(this.position, this.aabb, this.geometryBuffer!=null?this.geometryBuffer.copy():null, this.offsets!=null?Arrays.copyOf(this.offsets, this.offsets.length):null);
|
||||
}
|
||||
|
||||
public void free() {
|
||||
if (this.geometryBuffer != null) {
|
||||
this.geometryBuffer.free();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package me.cortex.zenith.client.core.rendering.building;
|
||||
|
||||
import me.cortex.zenith.common.util.MemoryBuffer;
|
||||
import me.cortex.zenith.common.world.WorldEngine;
|
||||
|
||||
public class BuiltSectionGeometry {
|
||||
public final long position;
|
||||
public final MemoryBuffer geometryBuffer;
|
||||
public final MemoryBuffer translucentGeometryBuffer;
|
||||
|
||||
public BuiltSectionGeometry(int lvl, int x, int y, int z, MemoryBuffer geometryBuffer, MemoryBuffer translucentGeometryBuffer) {
|
||||
this(WorldEngine.getWorldSectionId(lvl, x, y, z), geometryBuffer, translucentGeometryBuffer);
|
||||
}
|
||||
public BuiltSectionGeometry(long position, MemoryBuffer geometryBuffer, MemoryBuffer translucentGeometryBuffer) {
|
||||
this.position = position;
|
||||
this.geometryBuffer = geometryBuffer;
|
||||
this.translucentGeometryBuffer = translucentGeometryBuffer;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
if (this.geometryBuffer != null) {
|
||||
this.geometryBuffer.free();
|
||||
}
|
||||
if (this.translucentGeometryBuffer != null) {
|
||||
this.translucentGeometryBuffer.free();
|
||||
}
|
||||
}
|
||||
|
||||
public BuiltSectionGeometry clone() {
|
||||
return new BuiltSectionGeometry(this.position, this.geometryBuffer!=null?this.geometryBuffer.copy():null, this.translucentGeometryBuffer!=null?this.translucentGeometryBuffer.copy():null);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package me.cortex.zenith.client.core.rendering.building;
|
||||
|
||||
|
||||
import me.cortex.zenith.client.core.model.ModelManager;
|
||||
import me.cortex.zenith.client.core.rendering.GeometryManager;
|
||||
import me.cortex.zenith.client.core.util.Mesher2D;
|
||||
import me.cortex.zenith.common.world.other.Mapper;
|
||||
import net.minecraft.client.color.block.BlockColors;
|
||||
|
||||
@@ -1,25 +1,42 @@
|
||||
package me.cortex.zenith.client.core.rendering.building;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import me.cortex.zenith.common.util.MemoryBuffer;
|
||||
import me.cortex.zenith.client.core.model.ModelManager;
|
||||
import me.cortex.zenith.client.core.util.Mesher2D;
|
||||
import me.cortex.zenith.common.util.MemoryBuffer;
|
||||
import me.cortex.zenith.common.world.WorldEngine;
|
||||
import me.cortex.zenith.common.world.WorldSection;
|
||||
import me.cortex.zenith.common.world.other.Mapper;
|
||||
import net.minecraft.block.FluidBlock;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
|
||||
public class RenderDataFactory {
|
||||
private final Mesher2D mesher = new Mesher2D(5,15);//15
|
||||
private final LongArrayList outData = new LongArrayList(1000);
|
||||
private final WorldEngine world;
|
||||
private final ModelManager modelMan;
|
||||
private final QuadEncoder encoder;
|
||||
|
||||
private final Mesher2D negativeMesher = new Mesher2D(5, 15);
|
||||
private final Mesher2D positiveMesher = new Mesher2D(5, 15);
|
||||
|
||||
private final long[] sectionCache = new long[32*32*32];
|
||||
private final long[] connectedSectionCache = new long[32*32*32];
|
||||
private final QuadEncoder encoder;
|
||||
public RenderDataFactory(WorldEngine world) {
|
||||
|
||||
private final LongArrayList doubleSidedQuadCollector = new LongArrayList();
|
||||
private final LongArrayList translucentQuadCollector = new LongArrayList();
|
||||
private final LongArrayList[] directionalQuadCollectors = new LongArrayList[]{new LongArrayList(), new LongArrayList(), new LongArrayList(), new LongArrayList(), new LongArrayList(), new LongArrayList()};
|
||||
|
||||
|
||||
private int minX;
|
||||
private int minY;
|
||||
private int minZ;
|
||||
private int maxX;
|
||||
private int maxY;
|
||||
private int maxZ;
|
||||
public RenderDataFactory(WorldEngine world, ModelManager modelManager) {
|
||||
this.world = world;
|
||||
this.modelMan = modelManager;
|
||||
this.encoder = new QuadEncoder(world.getMapper(), MinecraftClient.getInstance().getBlockColors(), MinecraftClient.getInstance().world);
|
||||
}
|
||||
|
||||
@@ -32,304 +49,492 @@ public class RenderDataFactory {
|
||||
|
||||
//buildMask in the lower 6 bits contains the faces to build, the next 6 bits are whether the edge face builds against
|
||||
// its neigbor or not (0 if it does 1 if it doesnt (0 is default behavior))
|
||||
public BuiltSectionGeometry generateMesh(WorldSection section, int buildMask) {
|
||||
//TODO: to speed it up more, check like section.isEmpty() and stuff like that, have masks for if a slice/layer is entirly air etc
|
||||
|
||||
//TODO: instead of having it check its neighbors with the same lod level, compare against 1 level lower, this will prevent cracks and seams from
|
||||
// appearing between lods
|
||||
|
||||
|
||||
//if (section.definitelyEmpty()) {//Fast path if its known the entire chunk is empty
|
||||
// return new BuiltSectionGeometry(section.getKey(), null, null);
|
||||
//}
|
||||
|
||||
public BuiltSection generateMesh(WorldSection section, int buildMask) {
|
||||
section.copyDataTo(this.sectionCache);
|
||||
var data = this.sectionCache;
|
||||
this.translucentQuadCollector.clear();
|
||||
this.doubleSidedQuadCollector.clear();
|
||||
for (var collector : this.directionalQuadCollectors) {
|
||||
collector.clear();
|
||||
}
|
||||
this.minX = Integer.MAX_VALUE;
|
||||
this.minY = Integer.MAX_VALUE;
|
||||
this.minZ = Integer.MAX_VALUE;
|
||||
this.maxX = Integer.MIN_VALUE;
|
||||
this.maxY = Integer.MIN_VALUE;
|
||||
this.maxZ = Integer.MIN_VALUE;
|
||||
|
||||
long[] connectedData = null;
|
||||
int dirId = Direction.UP.getId();
|
||||
if ((buildMask&(1<<dirId))!=0) {
|
||||
for (int y = 0; y < 32; y++) {
|
||||
this.mesher.reset();
|
||||
//TODO:NOTE! when doing face culling of translucent blocks,
|
||||
// if the connecting type of the translucent block is the same AND the face is full, discard it
|
||||
// this stops e.g. multiple layers of glass (and ocean) from having 3000 layers of quads etc
|
||||
|
||||
for (int z = 0; z < 32; z++) {
|
||||
for (int x = 0; x < 32; x++) {
|
||||
var self = data[WorldSection.getIndex(x, y, z)];
|
||||
if (Mapper.isAir(self)) {
|
||||
continue;
|
||||
}
|
||||
long up = -1;
|
||||
if (y < 31) {
|
||||
up = data[WorldSection.getIndex(x, y + 1, z)];
|
||||
if (!Mapper.isTranslucent(up)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (y == 31 && ((buildMask>>(6+dirId))&1) == 0) {
|
||||
//Load and copy the data into a local cache, TODO: optimize so its not doing billion of copies
|
||||
if (connectedData == null) {
|
||||
var connectedSection = this.world.acquire(section.lvl, section.x, section.y + 1, section.z);
|
||||
connectedSection.copyDataTo(this.connectedSectionCache);
|
||||
connectedData = this.connectedSectionCache;
|
||||
connectedSection.release();
|
||||
}
|
||||
up = connectedData[WorldSection.getIndex(x, 0, z)];
|
||||
if (!Mapper.isTranslucent(up)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
//Recodes the id to include the correct lighting
|
||||
this.mesher.put(x, z, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
|
||||
}
|
||||
}
|
||||
|
||||
var count = this.mesher.process();
|
||||
var array = this.mesher.getArray();
|
||||
for (int i = 0; i < count; i++) {
|
||||
var quad = array[i];
|
||||
this.outData.add(this.encoder.encode(this.mesher.getDataFromQuad(quad), 1, y, quad));
|
||||
}
|
||||
this.generateMeshForAxis(section, 0);//Direction.Axis.Y
|
||||
this.generateMeshForAxis(section, 1);//Direction.Axis.Z
|
||||
this.generateMeshForAxis(section, 2);//Direction.Axis.X
|
||||
|
||||
int quadCount = this.doubleSidedQuadCollector.size() + this.translucentQuadCollector.size();
|
||||
for (var collector : this.directionalQuadCollectors) {
|
||||
quadCount += collector.size();
|
||||
}
|
||||
|
||||
if (quadCount == 0) {
|
||||
return new BuiltSection(section.getKey());
|
||||
}
|
||||
|
||||
var buff = new MemoryBuffer(quadCount*8L);
|
||||
long ptr = buff.address;
|
||||
int[] offsets = new int[8];
|
||||
int coff = 0;
|
||||
|
||||
//Ordering is: translucent, double sided quads, directional quads
|
||||
offsets[0] = coff;
|
||||
for (long data : this.translucentQuadCollector) {
|
||||
MemoryUtil.memPutLong(ptr + ((coff++)*8L), data);
|
||||
}
|
||||
|
||||
offsets[1] = coff;
|
||||
for (long data : this.doubleSidedQuadCollector) {
|
||||
MemoryUtil.memPutLong(ptr + ((coff++)*8L), data);
|
||||
}
|
||||
|
||||
for (int face = 0; face < 6; face++) {
|
||||
offsets[face+2] = coff;
|
||||
for (long data : this.directionalQuadCollectors[face]) {
|
||||
MemoryUtil.memPutLong(ptr + ((coff++) * 8L), data);
|
||||
}
|
||||
connectedData = null;
|
||||
}
|
||||
|
||||
dirId = Direction.EAST.getId();
|
||||
if ((buildMask&(1<<dirId))!=0) {
|
||||
for (int x = 0; x < 32; x++) {
|
||||
this.mesher.reset();
|
||||
int aabb = 0;
|
||||
aabb |= this.minX;
|
||||
aabb |= this.minY<<5;
|
||||
aabb |= this.minZ<<10;
|
||||
aabb |= (this.maxX-this.minX)<<15;
|
||||
aabb |= (this.maxY-this.minY)<<20;
|
||||
aabb |= (this.maxZ-this.minZ)<<25;
|
||||
|
||||
for (int y = 0; y < 32; y++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
var self = data[WorldSection.getIndex(x, y, z)];
|
||||
if (Mapper.isAir(self)) {
|
||||
continue;
|
||||
}
|
||||
long up = -1;
|
||||
if (x < 31) {
|
||||
up = data[WorldSection.getIndex(x + 1, y, z)];
|
||||
if (!Mapper.isTranslucent(up)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (x == 31 && ((buildMask>>(6+dirId))&1) == 0) {
|
||||
//Load and copy the data into a local cache, TODO: optimize so its not doing billion of copies
|
||||
if (connectedData == null) {
|
||||
var connectedSection = this.world.acquire(section.lvl, section.x + 1, section.y, section.z);
|
||||
connectedSection.copyDataTo(this.connectedSectionCache);
|
||||
connectedData = this.connectedSectionCache;
|
||||
connectedSection.release();
|
||||
}
|
||||
up = connectedData[WorldSection.getIndex(0, y, z)];
|
||||
if (!Mapper.isTranslucent(up)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
this.mesher.put(y, z, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
|
||||
}
|
||||
}
|
||||
|
||||
var count = this.mesher.process();
|
||||
var array = this.mesher.getArray();
|
||||
for (int i = 0; i < count; i++) {
|
||||
var quad = array[i];
|
||||
this.outData.add(this.encoder.encode(this.mesher.getDataFromQuad(quad), 5, x, quad));
|
||||
}
|
||||
}
|
||||
connectedData = null;
|
||||
}
|
||||
|
||||
dirId = Direction.SOUTH.getId();
|
||||
if ((buildMask&(1<<dirId))!=0) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
this.mesher.reset();
|
||||
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int y = 0; y < 32; y++) {
|
||||
var self = data[WorldSection.getIndex(x, y, z)];
|
||||
if (Mapper.isAir(self)) {
|
||||
continue;
|
||||
}
|
||||
long up = -1;
|
||||
if (z < 31) {
|
||||
up = data[WorldSection.getIndex(x, y, z + 1)];
|
||||
if (!Mapper.isTranslucent(up)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (z == 31 && ((buildMask>>(6+dirId))&1) == 0) {
|
||||
//Load and copy the data into a local cache, TODO: optimize so its not doing billion of copies
|
||||
if (connectedData == null) {
|
||||
var connectedSection = this.world.acquire(section.lvl, section.x, section.y, section.z + 1);
|
||||
connectedSection.copyDataTo(this.connectedSectionCache);
|
||||
connectedData = this.connectedSectionCache;
|
||||
connectedSection.release();
|
||||
}
|
||||
up = connectedData[WorldSection.getIndex(x, y, 0)];
|
||||
if (!Mapper.isTranslucent(up)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
this.mesher.put(x, y, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
|
||||
}
|
||||
}
|
||||
|
||||
var count = this.mesher.process();
|
||||
var array = this.mesher.getArray();
|
||||
for (int i = 0; i < count; i++) {
|
||||
var quad = array[i];
|
||||
this.outData.add(this.encoder.encode(this.mesher.getDataFromQuad(quad), 3, z, quad));
|
||||
}
|
||||
}
|
||||
connectedData = null;
|
||||
}
|
||||
|
||||
dirId = Direction.WEST.getId();
|
||||
if ((buildMask&(1<<dirId))!=0) {
|
||||
for (int x = 31; x != -1; x--) {
|
||||
this.mesher.reset();
|
||||
|
||||
for (int y = 0; y < 32; y++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
var self = data[WorldSection.getIndex(x, y, z)];
|
||||
if (Mapper.isAir(self)) {
|
||||
continue;
|
||||
}
|
||||
long up = -1;
|
||||
if (x != 0) {
|
||||
up = data[WorldSection.getIndex(x - 1, y, z)];
|
||||
if (!Mapper.isTranslucent(up)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (x == 0 && ((buildMask>>(6+dirId))&1) == 0) {
|
||||
//Load and copy the data into a local cache, TODO: optimize so its not doing billion of copies
|
||||
if (connectedData == null) {
|
||||
var connectedSection = this.world.acquire(section.lvl, section.x - 1, section.y, section.z);
|
||||
connectedSection.copyDataTo(this.connectedSectionCache);
|
||||
connectedData = this.connectedSectionCache;
|
||||
connectedSection.release();
|
||||
}
|
||||
up = connectedData[WorldSection.getIndex(31, y, z)];
|
||||
if (!Mapper.isTranslucent(up)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
this.mesher.put(y, z, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
|
||||
}
|
||||
}
|
||||
|
||||
var count = this.mesher.process();
|
||||
var array = this.mesher.getArray();
|
||||
for (int i = 0; i < count; i++) {
|
||||
var quad = array[i];
|
||||
this.outData.add(this.encoder.encode(this.mesher.getDataFromQuad(quad), 4, x, quad));
|
||||
}
|
||||
}
|
||||
connectedData = null;
|
||||
}
|
||||
|
||||
dirId = Direction.NORTH.getId();
|
||||
if ((buildMask&(1<<dirId))!=0) {
|
||||
for (int z = 31; z != -1; z--) {
|
||||
this.mesher.reset();
|
||||
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int y = 0; y < 32; y++) {
|
||||
var self = data[WorldSection.getIndex(x, y, z)];
|
||||
if (Mapper.isAir(self)) {
|
||||
continue;
|
||||
}
|
||||
long up = -1;
|
||||
if (z != 0) {
|
||||
up = data[WorldSection.getIndex(x, y, z - 1)];
|
||||
if (!Mapper.isTranslucent(up)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (z == 0 && ((buildMask>>(6+dirId))&1) == 0) {
|
||||
//Load and copy the data into a local cache, TODO: optimize so its not doing billion of copies
|
||||
if (connectedData == null) {
|
||||
var connectedSection = this.world.acquire(section.lvl, section.x, section.y, section.z - 1);
|
||||
connectedSection.copyDataTo(this.connectedSectionCache);
|
||||
connectedData = this.connectedSectionCache;
|
||||
connectedSection.release();
|
||||
}
|
||||
up = connectedData[WorldSection.getIndex(x, y, 31)];
|
||||
if (!Mapper.isTranslucent(up)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
this.mesher.put(x, y, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
|
||||
}
|
||||
}
|
||||
|
||||
var count = this.mesher.process();
|
||||
var array = this.mesher.getArray();
|
||||
for (int i = 0; i < count; i++) {
|
||||
var quad = array[i];
|
||||
this.outData.add(this.encoder.encode(this.mesher.getDataFromQuad(quad), 2, z, quad));
|
||||
}
|
||||
}
|
||||
connectedData = null;
|
||||
}
|
||||
|
||||
dirId = Direction.DOWN.getId();
|
||||
if ((buildMask&(1<<dirId))!=0) {
|
||||
for (int y = 31; y != -1; y--) {
|
||||
this.mesher.reset();
|
||||
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
var self = data[WorldSection.getIndex(x, y, z)];
|
||||
if (Mapper.isAir(self)) {
|
||||
continue;
|
||||
}
|
||||
long up = -1;
|
||||
if (y != 0) {
|
||||
up = data[WorldSection.getIndex(x, y - 1, z)];
|
||||
if (!Mapper.isTranslucent(up)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (y == 0 && ((buildMask>>(6+dirId))&1) == 0) {
|
||||
//Load and copy the data into a local cache, TODO: optimize so its not doing billion of copies
|
||||
if (connectedData == null) {
|
||||
var connectedSection = this.world.acquire(section.lvl, section.x, section.y - 1, section.z);
|
||||
connectedSection.copyDataTo(this.connectedSectionCache);
|
||||
connectedData = this.connectedSectionCache;
|
||||
connectedSection.release();
|
||||
}
|
||||
up = connectedData[WorldSection.getIndex(x, 31, z)];
|
||||
if (!Mapper.isTranslucent(up)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
this.mesher.put(x, z, (self&~(0xFFL<<56))|(up&(0xFFL<<56)));
|
||||
}
|
||||
}
|
||||
|
||||
var count = this.mesher.process();
|
||||
var array = this.mesher.getArray();
|
||||
for (int i = 0; i < count; i++) {
|
||||
var quad = array[i];
|
||||
this.outData.add(this.encoder.encode(this.mesher.getDataFromQuad(quad), 0, y, quad));
|
||||
}
|
||||
}
|
||||
connectedData = null;
|
||||
}
|
||||
|
||||
|
||||
if (this.outData.isEmpty()) {
|
||||
return new BuiltSectionGeometry(section.getKey(), null, null);
|
||||
}
|
||||
|
||||
var output = new MemoryBuffer(this.outData.size()*8L);
|
||||
for (int i = 0; i < this.outData.size(); i++) {
|
||||
MemoryUtil.memPutLong(output.address + i * 8L, this.outData.getLong(i));
|
||||
}
|
||||
|
||||
this.outData.clear();
|
||||
return new BuiltSectionGeometry(section.getKey(), output, null);
|
||||
return new BuiltSection(section.getKey(), aabb, buff, offsets);
|
||||
}
|
||||
|
||||
|
||||
private void generateMeshForAxis(WorldSection section, int axisId) {
|
||||
int aX = axisId==2?1:0;
|
||||
int aY = axisId==0?1:0;
|
||||
int aZ = axisId==1?1:0;
|
||||
|
||||
//Note the way the connectedSectionCache works is that it reuses the section cache because we know we dont need the connectedSection
|
||||
// when we are on the other direction
|
||||
boolean obtainedOppositeSection0 = false;
|
||||
boolean obtainedOppositeSection31 = false;
|
||||
|
||||
|
||||
for (int primary = 0; primary < 32; primary++) {
|
||||
this.negativeMesher.reset();
|
||||
this.positiveMesher.reset();
|
||||
|
||||
for (int a = 0; a < 32; a++) {
|
||||
for (int b = 0; b < 32; b++) {
|
||||
int x = axisId==2?primary:a;
|
||||
int y = axisId==0?primary:(axisId==1?b:a);
|
||||
int z = axisId==1?primary:b;
|
||||
long self = this.sectionCache[WorldSection.getIndex(x,y,z)];
|
||||
if (Mapper.isAir(self)) continue;
|
||||
|
||||
int selfBlockId = Mapper.getBlockId(self);
|
||||
long selfMetadata = this.modelMan.getModelMetadata(selfBlockId);
|
||||
|
||||
boolean putFace = false;
|
||||
|
||||
//Branch into 2 paths, the + direction and -direction, doing it at once makes it much faster as it halves the number of loops
|
||||
|
||||
if (ModelManager.faceExists(selfMetadata, axisId<<1)) {//- direction
|
||||
long facingState = Mapper.AIR;
|
||||
//Need to access the other connecting section
|
||||
if (primary == 0) {
|
||||
if (!obtainedOppositeSection0) {
|
||||
var connectedSection = this.world.acquire(section.lvl, section.x - aX, section.y - aY, section.z - aZ);
|
||||
connectedSection.copyDataTo(this.connectedSectionCache);
|
||||
connectedSection.release();
|
||||
obtainedOppositeSection0 = true;
|
||||
}
|
||||
facingState = this.connectedSectionCache[WorldSection.getIndex(x*(1-aX)+(31*aX), y*(1-aY)+(31*aY), z*(1-aZ)+(31*aZ))];
|
||||
} else {
|
||||
facingState = this.sectionCache[WorldSection.getIndex(x-aX, y-aY, z-aZ)];
|
||||
}
|
||||
|
||||
putFace |= this.putFaceIfCan(this.negativeMesher, (axisId<<1), (axisId<<1)|1, self, selfMetadata, selfBlockId, facingState, a, b);
|
||||
}
|
||||
if (ModelManager.faceExists(selfMetadata, axisId<<1)) {//+ direction
|
||||
long facingState = Mapper.AIR;
|
||||
//Need to access the other connecting section
|
||||
if (primary == 31) {
|
||||
if (!obtainedOppositeSection31) {
|
||||
var connectedSection = this.world.acquire(section.lvl, section.x + aX, section.y + aY, section.z + aZ);
|
||||
connectedSection.copyDataTo(this.connectedSectionCache);
|
||||
connectedSection.release();
|
||||
obtainedOppositeSection31 = true;
|
||||
}
|
||||
facingState = this.connectedSectionCache[WorldSection.getIndex(x*(1-aX), y*(1-aY), z*(1-aZ))];
|
||||
} else {
|
||||
facingState = this.sectionCache[WorldSection.getIndex(x+aX, y+aY, z+aZ)];
|
||||
}
|
||||
|
||||
putFace |= this.putFaceIfCan(this.positiveMesher, (axisId<<1)|1, (axisId<<1), self, selfMetadata, selfBlockId, facingState, a, b);
|
||||
}
|
||||
|
||||
if (putFace) {
|
||||
this.minX = Math.min(this.minX, x);
|
||||
this.minY = Math.min(this.minY, y);
|
||||
this.minZ = Math.min(this.minZ, z);
|
||||
this.maxX = Math.max(this.maxX, x);
|
||||
this.maxY = Math.max(this.maxY, y);
|
||||
this.maxZ = Math.max(this.maxZ, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processMeshedFace(this.negativeMesher, axisId<<1, primary, this.directionalQuadCollectors[(axisId<<1)]);
|
||||
processMeshedFace(this.positiveMesher, (axisId<<1)|1, primary, this.directionalQuadCollectors[(axisId<<1)|1]);
|
||||
}
|
||||
}
|
||||
|
||||
//Returns true if a face was placed
|
||||
private boolean putFaceIfCan(Mesher2D mesher, int face, int opposingFace, long self, long metadata, int selfBlockId, long facingState, int a, int b) {
|
||||
long facingMetadata = this.modelMan.getModelMetadata(Mapper.getBlockId(facingState));
|
||||
|
||||
//If face can be occluded and is occluded from the facing block, then dont render the face
|
||||
if (ModelManager.faceCanBeOccluded(metadata, face) && ModelManager.faceOccludes(facingMetadata, opposingFace)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ModelManager.isTranslucent(metadata) && selfBlockId == Mapper.getBlockId(facingState)) {
|
||||
//If we are facing a block, and are translucent and it is the same block as us, cull the quad
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//TODO: if the model has a fluid state, it should compute if a fluid face needs to be injected
|
||||
// fluid face of type this.world.getMapper().getBlockStateFromId(self).getFluidState() and block type
|
||||
// this.world.getMapper().getBlockStateFromId(self).getFluidState().getBlockState()
|
||||
|
||||
//If we are a fluid and the oposing face is also a fluid need to make a closer check
|
||||
if (ModelManager.containsFluid(metadata) && ModelManager.containsFluid(facingMetadata)) {
|
||||
//if (this.world.getMapper().getBlockStateFromId(self).getFluidState() != this.world.getMapper().getBlockStateFromId(facingState).getFluidState()) {
|
||||
// TODO: need to inject a face here of type fluid, how? i have no god damn idea, probably add an auxilery mesher just for this
|
||||
//}
|
||||
|
||||
//Hackfix
|
||||
var selfBS = this.world.getMapper().getBlockStateFromId(self);
|
||||
if (selfBS.getBlock() instanceof FluidBlock && selfBS.getFluidState().getBlockState().getBlock() == this.world.getMapper().getBlockStateFromId(facingState).getFluidState().getBlockState().getBlock()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int clientModelId = this.modelMan.getModelId(selfBlockId);
|
||||
long otherFlags = 0;
|
||||
otherFlags |= ModelManager.isTranslucent(metadata)?1L<<33:0;
|
||||
otherFlags |= ModelManager.isDoubleSided(metadata)?1L<<34:0;
|
||||
mesher.put(a, b, ((long)clientModelId) | (((long) Mapper.getLightId(facingState))<<16) | ((((long) Mapper.getBiomeId(self))<<24) * (ModelManager.isBiomeColoured(metadata)?1:0)) | otherFlags);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void processMeshedFace(Mesher2D mesher, int face, int otherAxis, LongArrayList axisOutputGeometry) {
|
||||
//TODO: encode translucents and double sided quads to different global buffers
|
||||
|
||||
int count = mesher.process();
|
||||
var array = mesher.getArray();
|
||||
for (int i = 0; i < count; i++) {
|
||||
int quad = array[i];
|
||||
long data = mesher.getDataFromQuad(quad);
|
||||
long encodedQuad = Integer.toUnsignedLong(QuadEncoder.encodePosition(face, otherAxis, quad)) | ((data&0xFFFF)<<26) | (((data>>16)&0xFF)<<55) | (((data>>24)&0x1FF)<<46);
|
||||
|
||||
|
||||
if ((data&(1L<<33))!=0) {
|
||||
this.translucentQuadCollector.add(encodedQuad);
|
||||
} else if ((data&(1L<<34))!=0) {
|
||||
this.doubleSidedQuadCollector.add(encodedQuad);
|
||||
} else {
|
||||
axisOutputGeometry.add(encodedQuad);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
private static long encodeRaw(int face, int width, int height, int x, int y, int z, int blockId, int biomeId, int lightId) {
|
||||
return ((long)face) | (((long) width)<<3) | (((long) height)<<7) | (((long) z)<<11) | (((long) y)<<16) | (((long) x)<<21) | (((long) blockId)<<26) | (((long) biomeId)<<46) | (((long) lightId)<<55);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
|
||||
//outData.clear();
|
||||
|
||||
//var buff = new MemoryBuffer(8*8);
|
||||
//MemoryUtil.memPutLong(buff.address, encodeRaw(2, 0,1,0,0,0,159,0, 0));//92 515
|
||||
//MemoryUtil.memPutLong(buff.address+8, encodeRaw(3, 0,1,0,0,0,159,0, 0));//92 515
|
||||
//MemoryUtil.memPutLong(buff.address+16, encodeRaw(4, 1,2,0,0,0,159,0, 0));//92 515
|
||||
//MemoryUtil.memPutLong(buff.address+24, encodeRaw(5, 1,2,0,0,0,159,0, 0));//92 515
|
||||
//MemoryUtil.memPutLong(buff.address+32, encodeRaw(2, 0,1,0,0,1,159,0, 0));//92 515
|
||||
//MemoryUtil.memPutLong(buff.address+40, encodeRaw(3, 0,1,0,0,1,159,0, 0));//92 515
|
||||
//MemoryUtil.memPutLong(buff.address+48, encodeRaw(2, 0,1,0,0,2,159,0, 0));//92 515
|
||||
//MemoryUtil.memPutLong(buff.address+56, encodeRaw(3, 0,1,0,0,2,159,0, 0));//92 515
|
||||
|
||||
//int modelId = this.modelMan.getModelId(this.world.getMapper().getIdFromBlockState(Blocks.OAK_FENCE.getDefaultState()));
|
||||
//int modelId = this.modelMan.getModelId(this.world.getMapper().getIdFromBlockState(Blocks.OAK_FENCE.getDefaultState()));
|
||||
|
||||
//outData.add(encodeRaw(0, 0,0,0,0,0, modelId,0, 0));
|
||||
//outData.add(encodeRaw(1, 0,0,0,0,0, modelId,0, 0));
|
||||
//outData.add(encodeRaw(2, 0,0,0,0,0, modelId,0, 0));
|
||||
//outData.add(encodeRaw(3, 0,0,0,0,0, modelId,0, 0));
|
||||
//outData.add(encodeRaw(4, 0,0,0,0,0, modelId,0, 0));
|
||||
//outData.add(encodeRaw(5, 0,0,0,0,0, modelId,0, 0));
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
|
||||
Mesher2D mesher = new Mesher2D(5,15);
|
||||
|
||||
LongArrayList outData = new LongArrayList(1000);
|
||||
|
||||
//Up direction
|
||||
for (int y = 0; y < 32; y++) {
|
||||
mesher.reset();
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
long self = this.sectionCache[WorldSection.getIndex(x, y, z)];
|
||||
if (Mapper.isAir(self)) continue;
|
||||
|
||||
int selfBlockId = Mapper.getBlockId(self);
|
||||
long metadata = this.modelMan.getModelMetadata(selfBlockId);
|
||||
|
||||
//If the model doesnt have a face, then just skip it
|
||||
if (!ModelManager.faceExists(metadata, 1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long facingState = Mapper.AIR;
|
||||
//Need to access the other connecting section
|
||||
if (y == 31) {
|
||||
|
||||
} else {
|
||||
facingState = this.sectionCache[WorldSection.getIndex(x, y+1, z)];
|
||||
}
|
||||
|
||||
long facingMetadata = this.modelMan.getModelMetadata(Mapper.getBlockId(facingState));
|
||||
|
||||
//If face can be occluded and is occluded from the facing block, then dont render the face
|
||||
if (ModelManager.faceCanBeOccluded(metadata, 1) && ModelManager.faceOccludes(facingMetadata, 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int clientModelId = this.modelMan.getModelId(selfBlockId);
|
||||
|
||||
mesher.put(x, z, ((long)clientModelId) | (((long) Mapper.getLightId(facingState))<<16) | (((long) Mapper.getBiomeId(self))<<24));
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: encode translucents and double sided quads to different global buffers
|
||||
int count = mesher.process();
|
||||
var array = mesher.getArray();
|
||||
for (int i = 0; i < count; i++) {
|
||||
int quad = array[i];
|
||||
long data = mesher.getDataFromQuad(quad);
|
||||
outData.add(Integer.toUnsignedLong(QuadEncoder.encodePosition(1, y, quad)) | ((data&0xFFFF)<<26) | (((data>>16)&0xFF)<<55) | (((data>>24)&0x1FF)<<46));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//North direction
|
||||
for (int z = 0; z < 32; z++) {
|
||||
mesher.reset();
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int y = 0; y < 32; y++) {
|
||||
long self = this.sectionCache[WorldSection.getIndex(x, y, z)];
|
||||
if (Mapper.isAir(self)) continue;
|
||||
|
||||
int selfBlockId = Mapper.getBlockId(self);
|
||||
long metadata = this.modelMan.getModelMetadata(selfBlockId);
|
||||
|
||||
//If the model doesnt have a face, then just skip it
|
||||
if (!ModelManager.faceExists(metadata, 2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long facingState = Mapper.AIR;
|
||||
//Need to access the other connecting section
|
||||
if (z == 0) {
|
||||
|
||||
} else {
|
||||
facingState = this.sectionCache[WorldSection.getIndex(x, y, z-1)];
|
||||
}
|
||||
|
||||
long facingMetadata = this.modelMan.getModelMetadata(Mapper.getBlockId(facingState));
|
||||
|
||||
//If face can be occluded and is occluded from the facing block, then dont render the face
|
||||
if (ModelManager.faceCanBeOccluded(metadata, 2) && ModelManager.faceOccludes(facingMetadata, 3)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int clientModelId = this.modelMan.getModelId(selfBlockId);
|
||||
|
||||
mesher.put(x, y, ((long)clientModelId) | (((long) Mapper.getLightId(facingState))<<16) | (((long) Mapper.getBiomeId(self))<<24));
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: encode translucents and double sided quads to different global buffers
|
||||
int count = mesher.process();
|
||||
var array = mesher.getArray();
|
||||
for (int i = 0; i < count; i++) {
|
||||
int quad = array[i];
|
||||
long data = mesher.getDataFromQuad(quad);
|
||||
outData.add(Integer.toUnsignedLong(QuadEncoder.encodePosition(2, z, quad)) | ((data&0xFFFF)<<26) | (((data>>16)&0xFF)<<55) | (((data>>24)&0x1FF)<<46));
|
||||
}
|
||||
}
|
||||
|
||||
//South direction
|
||||
for (int z = 0; z < 32; z++) {
|
||||
mesher.reset();
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int y = 0; y < 32; y++) {
|
||||
long self = this.sectionCache[WorldSection.getIndex(x, y, z)];
|
||||
if (Mapper.isAir(self)) continue;
|
||||
|
||||
int selfBlockId = Mapper.getBlockId(self);
|
||||
long metadata = this.modelMan.getModelMetadata(selfBlockId);
|
||||
|
||||
//If the model doesnt have a face, then just skip it
|
||||
if (!ModelManager.faceExists(metadata, 3)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long facingState = Mapper.AIR;
|
||||
//Need to access the other connecting section
|
||||
if (z == 31) {
|
||||
|
||||
} else {
|
||||
facingState = this.sectionCache[WorldSection.getIndex(x, y, z+1)];
|
||||
}
|
||||
|
||||
long facingMetadata = this.modelMan.getModelMetadata(Mapper.getBlockId(facingState));
|
||||
|
||||
//If face can be occluded and is occluded from the facing block, then dont render the face
|
||||
if (ModelManager.faceCanBeOccluded(metadata, 3) && ModelManager.faceOccludes(facingMetadata, 2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int clientModelId = this.modelMan.getModelId(selfBlockId);
|
||||
|
||||
mesher.put(x, y, ((long)clientModelId) | (((long) Mapper.getLightId(facingState))<<16) | (((long) Mapper.getBiomeId(self))<<24));
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: encode translucents and double sided quads to different global buffers
|
||||
int count = mesher.process();
|
||||
var array = mesher.getArray();
|
||||
for (int i = 0; i < count; i++) {
|
||||
int quad = array[i];
|
||||
long data = mesher.getDataFromQuad(quad);
|
||||
outData.add(Integer.toUnsignedLong(QuadEncoder.encodePosition(3, z, quad)) | ((data&0xFFFF)<<26) | (((data>>16)&0xFF)<<55) | (((data>>24)&0x1FF)<<46));
|
||||
}
|
||||
}
|
||||
|
||||
//West direction
|
||||
for (int x = 0; x < 32; x++) {
|
||||
mesher.reset();
|
||||
for (int y = 0; y < 32; y++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
long self = this.sectionCache[WorldSection.getIndex(x, y, z)];
|
||||
if (Mapper.isAir(self)) continue;
|
||||
|
||||
int selfBlockId = Mapper.getBlockId(self);
|
||||
long metadata = this.modelMan.getModelMetadata(selfBlockId);
|
||||
|
||||
//If the model doesnt have a face, then just skip it
|
||||
if (!ModelManager.faceExists(metadata, 2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long facingState = Mapper.AIR;
|
||||
//Need to access the other connecting section
|
||||
if (x == 0) {
|
||||
|
||||
} else {
|
||||
facingState = this.sectionCache[WorldSection.getIndex(x-1, y, z)];
|
||||
}
|
||||
|
||||
long facingMetadata = this.modelMan.getModelMetadata(Mapper.getBlockId(facingState));
|
||||
|
||||
//If face can be occluded and is occluded from the facing block, then dont render the face
|
||||
if (ModelManager.faceCanBeOccluded(metadata, 4) && ModelManager.faceOccludes(facingMetadata, 5)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int clientModelId = this.modelMan.getModelId(selfBlockId);
|
||||
|
||||
mesher.put(y, z, ((long)clientModelId) | (((long) Mapper.getLightId(facingState))<<16) | (((long) Mapper.getBiomeId(self))<<24));
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: encode translucents and double sided quads to different global buffers
|
||||
int count = mesher.process();
|
||||
var array = mesher.getArray();
|
||||
for (int i = 0; i < count; i++) {
|
||||
int quad = array[i];
|
||||
long data = mesher.getDataFromQuad(quad);
|
||||
outData.add(Integer.toUnsignedLong(QuadEncoder.encodePosition(4, x, quad)) | ((data&0xFFFF)<<26) | (((data>>16)&0xFF)<<55) | (((data>>24)&0x1FF)<<46));
|
||||
}
|
||||
}
|
||||
|
||||
//East direction
|
||||
for (int x = 0; x < 32; x++) {
|
||||
mesher.reset();
|
||||
for (int y = 0; y < 32; y++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
long self = this.sectionCache[WorldSection.getIndex(x, y, z)];
|
||||
if (Mapper.isAir(self)) continue;
|
||||
|
||||
int selfBlockId = Mapper.getBlockId(self);
|
||||
long metadata = this.modelMan.getModelMetadata(selfBlockId);
|
||||
|
||||
//If the model doesnt have a face, then just skip it
|
||||
if (!ModelManager.faceExists(metadata, 2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long facingState = Mapper.AIR;
|
||||
//Need to access the other connecting section
|
||||
if (x == 31) {
|
||||
|
||||
} else {
|
||||
facingState = this.sectionCache[WorldSection.getIndex(x+1, y, z)];
|
||||
}
|
||||
|
||||
long facingMetadata = this.modelMan.getModelMetadata(Mapper.getBlockId(facingState));
|
||||
|
||||
//If face can be occluded and is occluded from the facing block, then dont render the face
|
||||
if (ModelManager.faceCanBeOccluded(metadata, 5) && ModelManager.faceOccludes(facingMetadata, 4)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int clientModelId = this.modelMan.getModelId(selfBlockId);
|
||||
|
||||
mesher.put(y, z, ((long)clientModelId) | (((long) Mapper.getLightId(facingState))<<16) | (((long) Mapper.getBiomeId(self))<<24));
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: encode translucents and double sided quads to different global buffers
|
||||
int count = mesher.process();
|
||||
var array = mesher.getArray();
|
||||
for (int i = 0; i < count; i++) {
|
||||
int quad = array[i];
|
||||
long data = mesher.getDataFromQuad(quad);
|
||||
outData.add(Integer.toUnsignedLong(QuadEncoder.encodePosition(5, x, quad)) | ((data&0xFFFF)<<26) | (((data>>16)&0xFF)<<55) | (((data>>24)&0x1FF)<<46));
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -1,6 +1,7 @@
|
||||
package me.cortex.zenith.client.core.rendering.building;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||
import me.cortex.zenith.client.core.model.ModelManager;
|
||||
import me.cortex.zenith.common.world.WorldEngine;
|
||||
import me.cortex.zenith.common.world.WorldSection;
|
||||
|
||||
@@ -22,10 +23,12 @@ public class RenderGenerationService {
|
||||
|
||||
private final Semaphore taskCounter = new Semaphore(0);
|
||||
private final WorldEngine world;
|
||||
private final Consumer<BuiltSectionGeometry> resultConsumer;
|
||||
private final ModelManager modelManager;
|
||||
private final Consumer<BuiltSection> resultConsumer;
|
||||
|
||||
public RenderGenerationService(WorldEngine world, int workers, Consumer<BuiltSectionGeometry> consumer) {
|
||||
public RenderGenerationService(WorldEngine world, ModelManager modelManager, int workers, Consumer<BuiltSection> consumer) {
|
||||
this.world = world;
|
||||
this.modelManager = modelManager;
|
||||
this.resultConsumer = consumer;
|
||||
this.workers = new Thread[workers];
|
||||
for (int i = 0; i < workers; i++) {
|
||||
@@ -36,12 +39,12 @@ public class RenderGenerationService {
|
||||
}
|
||||
}
|
||||
|
||||
private final ConcurrentHashMap<Long, BuiltSectionGeometry> renderCache = new ConcurrentHashMap<>(1000,0.75f,10);
|
||||
private final ConcurrentHashMap<Long, BuiltSection> renderCache = new ConcurrentHashMap<>(1000,0.75f,10);
|
||||
|
||||
//TODO: add a generated render data cache
|
||||
private void renderWorker() {
|
||||
//Thread local instance of the factory
|
||||
var factory = new RenderDataFactory(this.world);
|
||||
var factory = new RenderDataFactory(this.world, this.modelManager);
|
||||
while (this.running) {
|
||||
this.taskCounter.acquireUninterruptibly();
|
||||
if (!this.running) break;
|
||||
@@ -156,6 +159,6 @@ public class RenderGenerationService {
|
||||
while (!this.taskQueue.isEmpty()) {
|
||||
this.taskQueue.removeFirst();
|
||||
}
|
||||
this.renderCache.values().forEach(BuiltSectionGeometry::free);
|
||||
this.renderCache.values().forEach(BuiltSection::free);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public class BufferArena {
|
||||
}
|
||||
this.size = capacity;
|
||||
this.elementSize = elementSize;
|
||||
this.buffer = new GlBuffer(capacity, 0);
|
||||
this.buffer = new GlBuffer(capacity);
|
||||
this.allocationMap.setLimit(capacity/elementSize);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import net.minecraft.util.math.Box;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
public class DebugUtil {
|
||||
private static Matrix4f positionMatrix = new Matrix4f().identity();
|
||||
public static Matrix4f positionMatrix = new Matrix4f().identity();
|
||||
public static void setPositionMatrix(MatrixStack stack) {
|
||||
positionMatrix = new Matrix4f(stack.peek().getPositionMatrix());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package me.cortex.zenith.client.mixin.sodium;
|
||||
|
||||
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer;
|
||||
import me.jellysquid.mods.sodium.client.render.chunk.occlusion.OcclusionCuller;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(value = SodiumWorldRenderer.class, remap = false)
|
||||
public class MixinSodiumWorldRender {
|
||||
@Inject(method = "drawChunkLayer", at = @At("HEAD"), cancellable = true)
|
||||
private void cancelRender(RenderLayer renderLayer, MatrixStack matrixStack, double x, double y, double z, CallbackInfo ci) {
|
||||
//ci.cancel();
|
||||
}
|
||||
}
|
||||
@@ -51,18 +51,20 @@ public class Mapper {
|
||||
}
|
||||
|
||||
|
||||
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 static boolean shouldRenderFace(int dirId, long self, long other) {
|
||||
//TODO: fixme make it be with respect to the type itself e.g. water, glass etc
|
||||
return isTranslucent(other);
|
||||
public static int getBlockId(long id) {
|
||||
return (int) ((id>>27)&((1<<20)-1));
|
||||
}
|
||||
|
||||
public static int getBiomeId(long id) {
|
||||
return (int) ((id>>47)&0x1FF);
|
||||
}
|
||||
|
||||
public static int getLightId(long id) {
|
||||
return (int) ((id>>56)&0xFF);
|
||||
}
|
||||
|
||||
public void setCallbacks(Consumer<StateEntry> stateCallback, Consumer<BiomeEntry> biomeCallback) {
|
||||
@@ -183,6 +185,14 @@ public class Mapper {
|
||||
return this.blockId2stateEntry.get(blockId).state;
|
||||
}
|
||||
|
||||
public int getIdFromBlockState(BlockState state) {
|
||||
var entry = this.block2stateEntry.get(state);
|
||||
if (entry == null) {
|
||||
return -1;
|
||||
}
|
||||
return entry.id;
|
||||
}
|
||||
|
||||
//TODO: fixme: synchronize access to this.blockId2stateEntry
|
||||
public StateEntry[] getStateEntries() {
|
||||
var set = new ArrayList<>(this.blockId2stateEntry);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package me.cortex.zenith.common.world.storage;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ShortOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import me.cortex.zenith.common.world.storage.lmdb.LMDBStorageBackend;
|
||||
import net.minecraft.util.math.random.RandomSeed;
|
||||
|
||||
@@ -8,6 +11,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
|
||||
//Segments the section data into multiple dbs
|
||||
public class FragmentedStorageBackendAdaptor extends StorageBackend {
|
||||
@@ -57,12 +61,71 @@ public class FragmentedStorageBackendAdaptor extends StorageBackend {
|
||||
|
||||
@Override
|
||||
public void putIdMapping(int id, ByteBuffer data) {
|
||||
this.backends[0].putIdMapping(id, data);
|
||||
//Replicate the mappings over all the dbs to mean the chance of recovery in case of corruption is 30x
|
||||
for (var backend : this.backends) {
|
||||
backend.putIdMapping(id, data);
|
||||
}
|
||||
}
|
||||
|
||||
private record EqualingArray(byte[] bytes) {
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return Arrays.equals(this.bytes, ((EqualingArray)obj).bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(this.bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Int2ObjectOpenHashMap<byte[]> getIdMappingsData() {
|
||||
return this.backends[0].getIdMappingsData();
|
||||
Object2IntOpenHashMap<Int2ObjectOpenHashMap<EqualingArray>> verification = new Object2IntOpenHashMap<>();
|
||||
Int2ObjectOpenHashMap<EqualingArray> any = null;
|
||||
for (var backend : this.backends) {
|
||||
var mappings = backend.getIdMappingsData();
|
||||
if (mappings.isEmpty()) {
|
||||
//TODO: log a warning and attempt to replicate the data the other fragments
|
||||
continue;
|
||||
}
|
||||
var repackaged = new Int2ObjectOpenHashMap<EqualingArray>(mappings.size());
|
||||
for (var entry : mappings.int2ObjectEntrySet()) {
|
||||
repackaged.put(entry.getIntKey(), new EqualingArray(entry.getValue()));
|
||||
}
|
||||
verification.addTo(repackaged, 1);
|
||||
any = repackaged;
|
||||
}
|
||||
if (any == null) {
|
||||
return new Int2ObjectOpenHashMap<>();
|
||||
}
|
||||
|
||||
if (verification.size() != 1) {
|
||||
System.err.println("Error id mapping not matching across all fragments, attempting to recover");
|
||||
Object2IntMap.Entry<Int2ObjectOpenHashMap<EqualingArray>> maxEntry = null;
|
||||
for (var entry : verification.object2IntEntrySet()) {
|
||||
if (maxEntry == null) { maxEntry = entry; }
|
||||
else {
|
||||
if (maxEntry.getIntValue() < entry.getIntValue()) {
|
||||
maxEntry = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var mapping = maxEntry.getKey();
|
||||
|
||||
var out = new Int2ObjectOpenHashMap<byte[]>(mapping.size());
|
||||
for (var entry : mapping.int2ObjectEntrySet()) {
|
||||
out.put(entry.getIntKey(), entry.getValue().bytes);
|
||||
}
|
||||
return out;
|
||||
} else {
|
||||
var out = new Int2ObjectOpenHashMap<byte[]>(any.size());
|
||||
for (var entry : any.int2ObjectEntrySet()) {
|
||||
out.put(entry.getIntKey(), entry.getValue().bytes);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#version 430
|
||||
|
||||
layout(location=0) uniform sampler2D tex;
|
||||
in vec2 texCoord;
|
||||
out vec4 colour;
|
||||
void main() {
|
||||
colour = texture(tex, texCoord);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#version 430
|
||||
|
||||
in vec3 pos;
|
||||
in vec2 uv;
|
||||
|
||||
layout(location=1) uniform mat4 transform;
|
||||
out vec2 texCoord;
|
||||
|
||||
void main() {
|
||||
gl_Position = transform * vec4(pos, 1.0);
|
||||
texCoord = uv;
|
||||
}
|
||||
@@ -11,19 +11,22 @@ layout(binding = 0, std140) uniform SceneUniform {
|
||||
uint frameId;
|
||||
};
|
||||
|
||||
struct State {
|
||||
uint biomeTintMsk;
|
||||
uint faceColours[6];
|
||||
};
|
||||
|
||||
struct Biome {
|
||||
uint foliage;
|
||||
uint water;
|
||||
struct BlockModel {
|
||||
uint faceData[6];
|
||||
uint flagsA;
|
||||
uint colourTint;
|
||||
uint _pad[8];
|
||||
};
|
||||
|
||||
struct SectionMeta {
|
||||
uvec4 header;
|
||||
uvec4 drawdata;
|
||||
uint posA;
|
||||
uint posB;
|
||||
uint AABB;
|
||||
uint ptr;
|
||||
uint cntA;
|
||||
uint cntB;
|
||||
uint cntC;
|
||||
uint cntD;
|
||||
};
|
||||
|
||||
//TODO: see if making the stride 2*4*4 bytes or something cause you get that 16 byte write
|
||||
@@ -35,6 +38,8 @@ struct DrawCommand {
|
||||
uint baseInstance;
|
||||
};
|
||||
|
||||
layout(binding = 0) uniform sampler2D blockModelAtlas;
|
||||
|
||||
#ifndef Quad
|
||||
#define Quad ivec2
|
||||
#endif
|
||||
@@ -46,23 +51,24 @@ layout(binding = 2, std430) writeonly restrict buffer DrawBuffer {
|
||||
DrawCommand cmdBuffer[];
|
||||
};
|
||||
|
||||
layout(binding = 3, std430) readonly restrict buffer SectionBuffer {
|
||||
layout(binding = 3, std430) restrict buffer DrawCommandCountBuffer {
|
||||
uint opaqueDrawCount;
|
||||
uint translucentDrawCount;
|
||||
};
|
||||
|
||||
layout(binding = 4, std430) readonly restrict buffer SectionBuffer {
|
||||
SectionMeta sectionData[];
|
||||
};
|
||||
|
||||
#ifndef VISIBILITY_ACCESS
|
||||
#define VISIBILITY_ACCESS readonly
|
||||
#endif
|
||||
layout(binding = 4, std430) VISIBILITY_ACCESS restrict buffer VisibilityBuffer {
|
||||
layout(binding = 5, std430) VISIBILITY_ACCESS restrict buffer VisibilityBuffer {
|
||||
uint visibilityData[];
|
||||
};
|
||||
|
||||
layout(binding = 5, std430) readonly restrict buffer StateBuffer {
|
||||
State stateData[];
|
||||
};
|
||||
|
||||
layout(binding = 6, std430) readonly restrict buffer BiomeBuffer {
|
||||
Biome biomeData[];
|
||||
layout(binding = 6, std430) readonly restrict buffer ModelBuffer {
|
||||
BlockModel modelData[];
|
||||
};
|
||||
|
||||
layout(binding = 7, std430) readonly restrict buffer LightingBuffer {
|
||||
@@ -75,3 +81,5 @@ vec4 getLighting(uint index) {
|
||||
arr = arr & uvec4(0xFF);
|
||||
return vec4(arr)*vec4(1.0f/255.0f);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
//TODO: FIXME: this isnt actually correct cause depending on the face (i think) it could be 1/64 th of a position off
|
||||
// but im going to assume that since we are dealing with huge render distances, this shouldent matter that much
|
||||
float extractFaceIndentation(uint faceData) {
|
||||
return float((faceData>>16)&63)/63;
|
||||
}
|
||||
|
||||
vec4 extractFaceSizes(uint faceData) {
|
||||
return (vec4(faceData&0xF, (faceData>>4)&0xF, (faceData>>8)&0xF, (faceData>>12)&0xF)/16)+vec4(0,1f/16,0,1f/16);
|
||||
}
|
||||
|
||||
uint faceHasAlphaCuttout(uint faceData) {
|
||||
return (faceData>>22)&1;
|
||||
}
|
||||
|
||||
uint faceHasAlphaCuttoutOverride(uint faceData) {
|
||||
return (faceData>>23)&1;
|
||||
}
|
||||
@@ -7,6 +7,7 @@ layout(local_size_x = 128, local_size_y = 1, local_size_x = 1) in;
|
||||
#import <zenith:lod/gl46/bindings.glsl>
|
||||
#import <zenith:lod/gl46/frustum.glsl>
|
||||
#import <zenith:lod/gl46/section.glsl>
|
||||
#line 11
|
||||
|
||||
//https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_shader_16bit_storage.txt
|
||||
// adds support for uint8_t which can use for compact visibility buffer
|
||||
@@ -27,7 +28,16 @@ uint encodeLocalLodPos(uint detail, ivec3 pos) {
|
||||
}
|
||||
|
||||
|
||||
//TODO: swap to a multidraw indirect counted
|
||||
//Note: if i want reverse indexing i need to use the index buffer offset to offset
|
||||
void writeCmd(uint idx, uint encodedPos, uint offset, uint quadCount) {
|
||||
DrawCommand cmd;
|
||||
cmd.count = quadCount * 6;
|
||||
cmd.instanceCount = 1;
|
||||
cmd.firstIndex = 0;
|
||||
cmd.baseVertex = int(offset)<<2;
|
||||
cmd.baseInstance = encodedPos;
|
||||
cmdBuffer[idx] = cmd;
|
||||
}
|
||||
|
||||
void main() {
|
||||
if (gl_GlobalInvocationID.x >= sectionCount) {
|
||||
@@ -55,20 +65,79 @@ void main() {
|
||||
}
|
||||
|
||||
if (shouldRender) {
|
||||
DrawCommand cmd;
|
||||
cmd.count = extractQuadCount(meta) * 6;
|
||||
cmd.instanceCount = 1;
|
||||
cmd.firstIndex = 0;
|
||||
cmd.baseVertex = int(extractQuadStart(meta))<<2;
|
||||
cmd.baseInstance = encodeLocalLodPos(detail, ipos);
|
||||
cmdBuffer[gl_GlobalInvocationID.x] = cmd;
|
||||
} else {
|
||||
DrawCommand cmd;
|
||||
cmd.count = 0;
|
||||
cmd.instanceCount = 0;
|
||||
cmd.firstIndex = 0;
|
||||
cmd.baseVertex = 0;
|
||||
cmd.baseInstance = 0;
|
||||
cmdBuffer[gl_GlobalInvocationID.x] = cmd;
|
||||
uint encodedPos = encodeLocalLodPos(detail, ipos);
|
||||
uint ptr = extractQuadStart(meta);
|
||||
ivec3 relative = ipos-(baseSectionPos>>detail);
|
||||
|
||||
|
||||
|
||||
//TODO:FIXME: Figure out why these are in such a weird order
|
||||
uint msk = 0;
|
||||
msk |= uint(relative.y>-1)<<0;
|
||||
msk |= uint(relative.y<1 )<<1;
|
||||
msk |= uint(relative.z>-1)<<2;
|
||||
msk |= uint(relative.z<1 )<<3;
|
||||
msk |= uint(relative.x>-1)<<4;
|
||||
msk |= uint(relative.x<1 )<<5;
|
||||
|
||||
|
||||
uint cmdPtr = atomicAdd(opaqueDrawCount, bitCount(msk)+1);
|
||||
|
||||
|
||||
uint count = 0;
|
||||
//Translucency
|
||||
count = meta.cntA&0xFFFF;
|
||||
if (count != 0) {
|
||||
uint translucentCommandPtr = atomicAdd(translucentDrawCount, 1) + 400000;//FIXME: dont hardcode this offset
|
||||
writeCmd(translucentCommandPtr, encodedPos, ptr, count);
|
||||
}
|
||||
ptr += count;
|
||||
|
||||
//Double sided quads
|
||||
count = (meta.cntA>>16)&0xFFFF;
|
||||
writeCmd(cmdPtr++, encodedPos, ptr, count);
|
||||
ptr += count;
|
||||
|
||||
//Down
|
||||
count = (meta.cntB)&0xFFFF;
|
||||
if ((msk&(1<<0))!=0) {
|
||||
writeCmd(cmdPtr++, encodedPos, ptr, count);
|
||||
}
|
||||
ptr += count;
|
||||
|
||||
//Up
|
||||
count = (meta.cntB>>16)&0xFFFF;
|
||||
if ((msk&(1<<1))!=0) {
|
||||
writeCmd(cmdPtr++, encodedPos, ptr, count);
|
||||
}
|
||||
ptr += count;
|
||||
|
||||
//North
|
||||
count = (meta.cntC)&0xFFFF;
|
||||
if ((msk&(1<<2))!=0) {
|
||||
writeCmd(cmdPtr++, encodedPos, ptr, count);
|
||||
}
|
||||
ptr += count;
|
||||
|
||||
//South
|
||||
count = (meta.cntC>>16)&0xFFFF;
|
||||
if ((msk&(1<<3))!=0) {
|
||||
writeCmd(cmdPtr++, encodedPos, ptr, count);
|
||||
}
|
||||
ptr += count;
|
||||
|
||||
//West
|
||||
count = (meta.cntD)&0xFFFF;
|
||||
if ((msk&(1<<4))!=0) {
|
||||
writeCmd(cmdPtr++, encodedPos, ptr, count);
|
||||
}
|
||||
ptr += count;
|
||||
|
||||
//East
|
||||
count = (meta.cntD>>16)&0xFFFF;
|
||||
if ((msk&(1<<5))!=0) {
|
||||
writeCmd(cmdPtr++, encodedPos, ptr, count);
|
||||
}
|
||||
ptr += count;
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,16 @@ void main() {
|
||||
|
||||
uint detail = extractDetail(section);
|
||||
ivec3 ipos = extractPosition(section);
|
||||
ivec3 aabbOffset = extractAABBOffset(section);
|
||||
ivec3 size = extractAABBSize(section);
|
||||
|
||||
//Transform ipos with respect to the vertex corner
|
||||
ipos += ivec3(gl_VertexID&1, (gl_VertexID>>2)&1, (gl_VertexID>>1)&1);
|
||||
ivec3 pos = (((ipos<<detail)-baseSectionPos)<<5);
|
||||
pos += aabbOffset;
|
||||
pos -= (1<<detail);
|
||||
pos += (ivec3(gl_VertexID&1, (gl_VertexID>>2)&1, (gl_VertexID>>1)&1)*(size+2))*(1<<detail);
|
||||
|
||||
vec3 cornerPos = vec3(((ipos<<detail)-baseSectionPos)<<5);
|
||||
gl_Position = MVP * vec4(cornerPos,1);
|
||||
gl_Position = MVP * vec4(vec3(pos),1);
|
||||
|
||||
//Write to this id
|
||||
id = sid;
|
||||
|
||||
@@ -17,7 +17,7 @@ uint extractFace(uint64_t quad) {
|
||||
}
|
||||
|
||||
uint extractStateId(uint64_t quad) {
|
||||
return Eu32(quad, 20, 26);
|
||||
return Eu32(quad, 16, 26);
|
||||
}
|
||||
|
||||
uint extractBiomeId(uint64_t quad) {
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
#version 460 core
|
||||
layout(location = 0) in flat vec4 colour;
|
||||
layout(binding = 0) uniform sampler2D blockModelAtlas;
|
||||
|
||||
//TODO: need to fix when merged quads have discardAlpha set to false but they span multiple tiles
|
||||
// however they are not a full block
|
||||
|
||||
layout(location = 0) in vec2 uv;
|
||||
layout(location = 1) in flat vec2 baseUV;
|
||||
layout(location = 2) in flat vec4 colourTinting;
|
||||
layout(location = 3) in flat uint discardAlpha;
|
||||
|
||||
layout(location = 0) out vec4 outColour;
|
||||
void main() {
|
||||
//TODO: randomly discard the fragment with respect to the alpha value
|
||||
vec2 uv = mod(uv, vec2(1))*(1f/(vec2(3,2)*256f));
|
||||
vec4 colour = texture(blockModelAtlas, uv + baseUV);
|
||||
if (discardAlpha == 1 && colour.a == 0.0f) {
|
||||
discard;
|
||||
}
|
||||
outColour = colour * colourTinting;
|
||||
|
||||
outColour = colour;
|
||||
//outColour = vec4(uv + baseUV, 0, 1);
|
||||
}
|
||||
@@ -3,8 +3,13 @@
|
||||
|
||||
#import <zenith:lod/gl46/quad_format.glsl>
|
||||
#import <zenith:lod/gl46/bindings.glsl>
|
||||
#import <zenith:lod/gl46/block_model.glsl>
|
||||
#line 8
|
||||
|
||||
layout(location = 0) out flat vec4 colour;
|
||||
layout(location = 0) out vec2 uv;
|
||||
layout(location = 1) out flat vec2 baseUV;
|
||||
layout(location = 2) out flat vec4 colourTinting;
|
||||
layout(location = 3) out flat uint discardAlpha;
|
||||
|
||||
uint extractLodLevel() {
|
||||
return uint(gl_BaseInstance)>>29;
|
||||
@@ -20,85 +25,85 @@ vec4 uint2vec4RGBA(uint colour) {
|
||||
return vec4((uvec4(colour)>>uvec4(24,16,8,0))&uvec4(0xFF))/255;
|
||||
}
|
||||
|
||||
//Gets the face offset with respect to the face direction (e.g. some will be + some will be -)
|
||||
float getDepthOffset(uint faceData, uint face) {
|
||||
float offset = extractFaceIndentation(faceData);
|
||||
return offset * (1-((int(face)&1)*2));
|
||||
}
|
||||
|
||||
vec2 getFaceSizeOffset(uint faceData, uint corner) {
|
||||
vec4 faceOffsetsSizes = extractFaceSizes(faceData);
|
||||
return mix(faceOffsetsSizes.xz, -(1-faceOffsetsSizes.yw), bvec2(((corner>>1)&1)==1, (corner&1)==1));
|
||||
}
|
||||
|
||||
//TODO: add a mechanism so that some quads can ignore backface culling
|
||||
// this would help alot with stuff like crops as they would look kinda weird i think,
|
||||
// same with flowers etc
|
||||
void main() {
|
||||
int cornerIdx = gl_VertexID&3;
|
||||
|
||||
Quad quad = quadData[uint(gl_VertexID)>>2];
|
||||
vec3 innerPos = extractPos(quad);
|
||||
|
||||
uint face = extractFace(quad);
|
||||
uint modelId = extractStateId(quad);
|
||||
BlockModel model = modelData[modelId];
|
||||
uint faceData = model.faceData[face];
|
||||
|
||||
//Change the ordering due to backface culling
|
||||
//NOTE: when rendering, backface culling is disabled as we simply dispatch calls for each face
|
||||
// this has the advantage of having "unassigned" geometry, that is geometry where the backface isnt culled
|
||||
//if (face == 0 || (face>>1 != 0 && (face&1)==1)) {
|
||||
// cornerIdx ^= 1;
|
||||
//}
|
||||
|
||||
uint lodLevel = extractLodLevel();
|
||||
ivec3 lodCorner = ((extractRelativeLodPos()<<lodLevel) - (baseSectionPos&(ivec3((1<<lodLevel)-1))))<<5;
|
||||
vec3 corner = innerPos * (1<<lodLevel) + lodCorner;
|
||||
|
||||
//TODO: see if backface culling is even needed, since everything (should) be back culled already
|
||||
//Flip the quad rotation by its face (backface culling)
|
||||
if ((face&1) != 0) {
|
||||
cornerIdx ^= 1;
|
||||
vec2 faceOffset = getFaceSizeOffset(faceData, cornerIdx);
|
||||
ivec2 quadSize = extractSize(quad);
|
||||
vec2 respectiveQuadSize = vec2(quadSize * ivec2((cornerIdx>>1)&1, cornerIdx&1));
|
||||
vec2 size = (respectiveQuadSize + faceOffset) * (1<<lodLevel);
|
||||
|
||||
vec3 offset = vec3(size, (float(face&1) + getDepthOffset(faceData, face)) * (1<<lodLevel));
|
||||
|
||||
if ((face>>1) == 0) {//Up/down
|
||||
offset = offset.xzy;
|
||||
}
|
||||
if ((face>>1) == 0) {
|
||||
cornerIdx ^= 1;
|
||||
//Not needed, here for readability
|
||||
//if ((face>>1) == 1) {//north/south
|
||||
// offset = offset.xyz;
|
||||
//}
|
||||
if ((face>>1) == 2) {//west/east
|
||||
offset = offset.zxy;
|
||||
}
|
||||
|
||||
ivec2 size = extractSize(quad) * ivec2((cornerIdx>>1)&1, cornerIdx&1) * (1<<lodLevel);
|
||||
gl_Position = MVP * vec4(corner + offset,1);
|
||||
|
||||
vec3 pos = corner;
|
||||
|
||||
//NOTE: can just make instead of face, make it axis (can also make it 2 bit instead of 3 bit then)
|
||||
// since the only reason face is needed is to ensure backface culling orientation thing
|
||||
uint axis = face>>1;
|
||||
if (axis == 0) {
|
||||
pos.xz += size;
|
||||
pos.y += (face&1)<<lodLevel;
|
||||
} else if (axis == 1) {
|
||||
pos.xy += size;
|
||||
pos.z += (face&1)<<lodLevel;
|
||||
} else {
|
||||
pos.yz += size;
|
||||
pos.x += (face&1)<<lodLevel;
|
||||
}
|
||||
//Compute the uv coordinates
|
||||
vec2 modelUV = vec2(modelId&0xFF, (modelId>>8)&0xFF)*(1f/(256f));
|
||||
//TODO: make the face orientated by 2x3 so that division is not a integer div and modulo isnt needed
|
||||
// as these are very slow ops
|
||||
baseUV = modelUV + (vec2(face%3, face/3) * (1f/(vec2(3,2)*256f)));
|
||||
uv = respectiveQuadSize + faceOffset;//Add in the face offset for 0,0 uv
|
||||
|
||||
gl_Position = MVP * vec4(pos,1);
|
||||
discardAlpha = faceHasAlphaCuttout(faceData);
|
||||
|
||||
uint stateId = extractStateId(quad);
|
||||
uint biomeId = extractBiomeId(quad);
|
||||
State stateInfo = stateData[stateId];
|
||||
colour = uint2vec4RGBA(stateInfo.faceColours[face]);
|
||||
//We need to have a conditional override based on if the model size is < a full face + quadSize > 1
|
||||
discardAlpha |= uint(any(greaterThan(quadSize, ivec2(1)))) & faceHasAlphaCuttoutOverride(faceData);
|
||||
|
||||
colour *= getLighting(extractLightId(quad));
|
||||
//Compute lighting
|
||||
colourTinting = getLighting(extractLightId(quad));
|
||||
|
||||
if (((stateInfo.biomeTintMsk>>face)&1) == 1) {
|
||||
vec4 biomeColour = uint2vec4RGBA(biomeData[biomeId].foliage);
|
||||
colour *= biomeColour;
|
||||
}
|
||||
//Apply water tint
|
||||
if (((stateInfo.biomeTintMsk>>6)&1) == 1) {
|
||||
colour *= vec4(0.247, 0.463, 0.894, 1);
|
||||
}
|
||||
//Apply model colour tinting
|
||||
colourTinting *= uint2vec4RGBA(model.colourTint).yzwx;
|
||||
|
||||
//Apply face tint
|
||||
if (face == 0) {
|
||||
colour.xyz *= vec3(0.75, 0.75, 0.75);
|
||||
colourTinting.xyz *= vec3(0.75, 0.75, 0.75);
|
||||
} else if (face != 1) {
|
||||
colour.xyz *= vec3((float(face-2)/4)*0.6 + 0.4);
|
||||
colourTinting.xyz *= vec3((float(face-2)/4)*0.6 + 0.4);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
//gl_Position = MVP * vec4(vec3(((cornerIdx)&1)+10,10,((cornerIdx>>1)&1)+10),1);
|
||||
//uint i = uint(quad>>32);
|
||||
//uint i = uint(gl_BaseInstance);
|
||||
//i ^= i>>16;
|
||||
//i *= 124128573;
|
||||
//i ^= i>>16;
|
||||
//i *= 4211346123;
|
||||
//i ^= i>>16;
|
||||
//i *= 462312435;
|
||||
//i ^= i>>16;
|
||||
//i *= 542354341;
|
||||
//i ^= i>>16;
|
||||
|
||||
//uint i = uint(lodLevel+12)*215387625;
|
||||
//colour *= vec4(vec3(float((uint(i)>>2)&7)/7,float((uint(i)>>5)&7)/7,float((uint(i)>>8)&7)/7)*0.7+0.3,1);
|
||||
//colour = vec4(vec3(float((uint(i)>>2)&7)/7,float((uint(i)>>5)&7)/7,float((uint(i)>>8)&7)/7),1);
|
||||
}
|
||||
@@ -1,21 +1,25 @@
|
||||
uint extractDetail(SectionMeta section) {
|
||||
return section.header.x>>28;
|
||||
return section.posA>>28;
|
||||
}
|
||||
|
||||
ivec3 extractPosition(SectionMeta section) {
|
||||
int y = ((int(section.header.x)<<4)>>24);
|
||||
int x = (int(section.header.y)<<4)>>8;
|
||||
int z = int((section.header.x&((1<<20)-1))<<4);
|
||||
z |= int(section.header.y>>28);
|
||||
int y = ((int(section.posA)<<4)>>24);
|
||||
int x = (int(section.posB)<<4)>>8;
|
||||
int z = int((section.posA&((1<<20)-1))<<4);
|
||||
z |= int(section.posB>>28);
|
||||
z <<= 8;
|
||||
z >>= 8;
|
||||
return ivec3(x,y,z);
|
||||
}
|
||||
|
||||
uint extractQuadStart(SectionMeta meta) {
|
||||
return meta.header.z;
|
||||
return meta.ptr;
|
||||
}
|
||||
|
||||
uint extractQuadCount(SectionMeta meta) {
|
||||
return meta.header.w;
|
||||
ivec3 extractAABBOffset(SectionMeta meta) {
|
||||
return (ivec3(meta.AABB)>>ivec3(0,5,10))&31;
|
||||
}
|
||||
|
||||
ivec3 extractAABBSize(SectionMeta meta) {
|
||||
return ((ivec3(meta.AABB)>>ivec3(15,20,25))&31)+1;//The size is + 1 cause its always at least 1x1x1
|
||||
}
|
||||
|
||||
@@ -2,4 +2,5 @@ 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;
|
||||
accessible field net/minecraft/client/render/LightmapTextureManager texture Lnet/minecraft/client/texture/NativeImageBackedTexture;
|
||||
accessible field net/minecraft/client/color/block/BlockColors providers Lnet/minecraft/util/collection/IdList;
|
||||
@@ -15,6 +15,7 @@
|
||||
"defaultRequire": 1
|
||||
},
|
||||
"mixins": [
|
||||
"sodium.MixinOcclusionCuller"
|
||||
"sodium.MixinOcclusionCuller",
|
||||
"sodium.MixinSodiumWorldRender"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user